gambit is hosted by Hepforge, IPPP Durham
GAMBIT  v1.5.0-2191-ga4742ac
a Global And Modular Bsm Inference Tool
polychord.cpp
Go to the documentation of this file.
1 // GAMBIT: Global and Modular BSM Inference Tool
2 // *********************************************
10 //
24 
25 #include <vector>
26 #include <string>
27 #include <cmath>
28 #include <iostream>
29 #include <fstream>
30 #include <map>
31 #include <sstream>
32 #include <iomanip> // For debugging only
33 #include <algorithm>
34 #include <numeric>
35 
40 
41 
42 namespace Gambit
43 {
44  namespace PolyChord
45  {
48  }
49 }
50 
53 
54 
58 
59 scanner_plugin(polychord, version(1, 17, 1))
60 {
61  // An error is thrown if any of the following entries are not present in the inifile (none absolutely required for PolyChord).
63 
64  // Tell cmake system to search known paths for these libraries; any not found must be specified in config/scanner_locations.yaml.
65  reqd_libraries("chord");
66 
67  // Pointer to the (log)likelihood function
68  scanPtr LogLike;
69 
72  {
73  // Retrieve the external likelihood calculator
74  LogLike = get_purpose(get_inifile_value<std::string>("like"));
75  if (LogLike->getRank() == 0) std::cout << "Loading PolyChord nested sampling plugin for ScannerBit." << std::endl;
76  }
77 
79  int plugin_main (void)
80  {
85  bool resume_mode = get_printer().resume_mode();
87 
88  // Retrieve the dimensionality of the scan.
89  int ma = get_dimension();
90 
91  // Retrieve the global option specifying the minimum interesting likelihood
92  double gl0 = get_inifile_value<double>("likelihood: model_invalid_for_lnlike_below");
93  // Retrieve the global option specifying the likelihood offset to use
94  double offset = get_inifile_value<double>("likelihood: lnlike_offset", 0.);
95  // Make sure the likleihood functor knows to apply the offset internally in ScannerBit
96  LogLike->setPurposeOffset(offset);
97  // Offset the minimum interesting likelihood by the offset
98  gl0 = gl0 + offset;
99 
100  // Retrieve whether the scanned parameters should be added to the native output and determine nderived
101  int nderived = 2;
102  if (get_inifile_value<bool>("print_parameters_in_native_output", false)) nderived += ma;
103 
104  // Initialise polychord settings
105  Settings settings(ma, nderived);
106 
107  // Create the object that interfaces to the PolyChord LogLike callback function
108  Gambit::PolyChord::LogLikeWrapper loglwrapper(LogLike, get_printer());
110 
111  // ---------- Compute an ordering for fast and slow parameters
112  // Read list of fast parameters from file
113  std::vector<std::string> fast_params = get_inifile_value<std::vector<std::string>>("fast_params", {});
114  // Get list of parameters used from loglikelihood
115  std::vector<std::string> varied_params = LogLike->getShownParameters();
116  std::vector<std::string> all_params = LogLike->getParameters();
117 
118  // Compute the set difference between fast_params and all_params to check if there are any fast_params not included in all_params
119  std::set<std::string> set_fast_params(fast_params.begin(), fast_params.end()), set_params(all_params.begin(), all_params.end()), diff;
120  std::set_difference(set_fast_params.begin(), set_fast_params.end(), set_params.begin(), set_params.end(),std::inserter(diff,diff.begin()));
121  if (diff.size())
122  {
123  // Raise an error if any specified fast_params are not actually being sampled over.
124  std::string error_message = "You have specified:\n";
125  for (auto param : diff) error_message += param + "\n" ;
126  error_message += "as fast param(s), but the list of params is:\n";
127  for (auto param : all_params) error_message += param + "\n";
128  scan_error().raise(LOCAL_INFO,error_message);
129  }
130 
131  // Compute the locations in PolyChord's unit hypercube, ordering from slow to fast
132  // This defaults to nDims if there are no fast parameters, or if all parameters are fast.
133  // grade_dims is a vector of integers that indicates the number of slow and fast parameters
134  settings.grade_dims.clear();
135  unsigned int i = 0;
136  // Run through all the parameters, and if they're slow parameters
137  // give them an index i, then increment i
138  for (auto param : varied_params)
139  if (std::find(fast_params.begin(), fast_params.end(),param) == fast_params.end())
140  Gambit::PolyChord::global_loglike_object->index_map[param] = (i++);
141  unsigned int nslow = i;
142  if(nslow!=0) settings.grade_dims.push_back(nslow);
143 
144  // Do the same for the fast parameters
145  for (auto param : varied_params)
146  if (std::find(fast_params.begin(), fast_params.end(),param) != fast_params.end())
147  Gambit::PolyChord::global_loglike_object->index_map[param] = (i++);
148  unsigned int nfast = i-nslow;
149  if (nfast>0) settings.grade_dims.push_back(nfast);
150 
151  if (nslow>0 and nfast>0)
152  {
153  // Specify the fraction of time to spend in the slow parameters.
154  double frac_slow = get_inifile_value<double>("frac_slow",0.75);
155  settings.grade_frac = std::vector<double>({frac_slow, 1-frac_slow});
156  }
157  else if (nslow==0) // In the unusual case of all fast parameters, there's only one grade.
158  nslow = nfast;
159  // ---------- End computation of ordering for fast and slow parameters
160 
161  // PolyChord algorithm options.
162  settings.nlive = get_inifile_value<int>("nlive", settings.nDims*25); // number of live points
163  settings.num_repeats = get_inifile_value<int>("num_repeats", nslow*2); // length of slice sampling chain
164  settings.nprior = get_inifile_value<int>("nprior", settings.nlive*10); // number of prior samples to begin algorithm with
165  settings.do_clustering = get_inifile_value<bool>("do_clustering", true); // Whether or not to perform clustering
166  settings.feedback = get_inifile_value<int>("fb", 1); // Feedback level
167  settings.precision_criterion = get_inifile_value<double>("tol", 0.5); // Stopping criterion (consistent with multinest)
168  settings.logzero = get_inifile_value<double>("logzero",gl0);
169  settings.max_ndead = get_inifile_value<double>("maxiter", -1); // Max no. of iterations, a negative value means infinity (different in comparison with multinest).
170  settings.maximise = get_inifile_value<bool>("maximise", false); // Whether to run a maximisation algorithm once the run is finished
171  settings.boost_posterior = 0; // Increase the number of posterior samples produced
172  bool outfile (get_inifile_value<bool>("outfile", true)); // write output files?
173  settings.posteriors = outfile;
174  settings.equals = outfile;
175  settings.cluster_posteriors = outfile;
176  settings.write_paramnames = outfile;
177  settings.write_stats = outfile;
178  settings.write_live = outfile;
179  settings.write_dead = outfile;
180  settings.write_prior = outfile;
181  settings.write_resume = outfile;
182  settings.read_resume = resume_mode;
183  settings.compression_factor = get_inifile_value<double>("compression_factor",0.36787944117144233);
184  settings.base_dir = get_inifile_value<std::string>("default_output_path")+"PolyChord";
185  settings.file_root = get_inifile_value<std::string>("root", "native");
186  settings.seed = get_inifile_value<int>("seed",-1);
187 
189  Gambit::Utils::ensure_path_exists(settings.base_dir + "/clusters/");
190 
191  // Point the boundSettings to the settings in use
193 
194  // Set the speed threshold for the printer.
195  // Evaluations of speeds >= threshold are not printed
196  // Speeds start at 0
198  get_inifile_value<int>("printer_speed_threshold",settings.grade_dims.size());
199 
200  if(resume_mode==1 and outfile==0)
201  {
202  // It is stupid to be in resume mode while not writing output files.
203  // Means subsequent resumes will be impossible. Throw an error.
204  scan_error().raise(LOCAL_INFO,"Error from PolyChord ScannerBit plugin! Resume mode is activated, however "
205  "PolyChord native output files are set to not be written. These are needed "
206  "for resuming; please change this setting in your yaml file (set option \"outfile: 1\")");
207  }
208 
209  // Setup auxilliary streams. These are only needed by the master process,
210  // so let's create them only for that process
211  int myrank = get_printer().get_stream()->getRank(); // MPI rank of this process
212  if(myrank==0)
213  {
214  // Get inifile options for each print stream
215  Gambit::Options txt_options = get_inifile_node("aux_printer_txt_options");
216  //Gambit::Options stats_options = get_inifile_node("aux_printer_stats_options"); //FIXME
217  Gambit::Options live_options = get_inifile_node("aux_printer_live_options");
218 
219  // Options to desynchronise print streams from the main Gambit iterations. This allows for random access writing, or writing of global scan data.
220  //stats_options.setValue("synchronised",false); //FIXME
221  txt_options.setValue("synchronised",false);
222  live_options.setValue("synchronised",false);
223 
224  // Initialise auxiliary print streams
225  get_printer().new_stream("txt",txt_options);
226  //get_printer().new_stream("stats",stats_options); //FIXME
227  get_printer().new_stream("live",live_options);
228  }
229 
230  // Ensure that MPI processes have the same IDs for auxiliary print streams;
231  Gambit::Scanner::assign_aux_numbers("Posterior","LastLive");
232 
233  //Run PolyChord, passing callback functions for the loglike and dumper.
234  if(myrank == 0) std::cout << "Starting PolyChord run..." << std::endl;
236  if(myrank == 0) std::cout << "PolyChord run finished!" << std::endl;
237  return 0;
238 
239  }
240 
241 }
242 
243 
247 
248 namespace Gambit {
249 
250  namespace PolyChord {
251 
253  // Note: we are using the c interface from cwrapper.f90, so the function
254  // signature is a little different than in the polychord examples.
255  double callback_loglike(double *Cube, int ndim, double* phi, int nderived)
256  {
257  // Call global interface to ScannerBit loglikelihood function
258  // Could also pass this object in via context pointer, but that
259  // involves some casting and could risk a segfault.
260  return global_loglike_object->LogLike(Cube, ndim, phi, nderived);
261  }
262 
263  void callback_dumper(int ndead, int nlive, int npars,
264  double *live, double *dead, double* logweights,
265  double logZ, double logZerr)
266  {
268  dumper(ndead, nlive, npars, live, dead, logweights, logZ, logZerr);
269  }
271 
272 
275  : boundLogLike(loglike), boundPrinter(printer)
276  { }
277 
291  double LogLikeWrapper::LogLike(double *Cube, int ndim, double* phi, int nderived)
292  {
293  // Cached "below threshold" unitcube parameters of the previous point.
294  static int ndim_threshold =
295  std::accumulate( boundSettings.grade_dims.begin(),
297  0);
298  static std::vector<double> prev_slow_unit(ndim_threshold);
299 
300  //convert C style array to C++ vector class, reordering parameters slow->fast
301  std::vector<std::string> params = boundLogLike->getShownParameters();
302  std::vector<double> unitpars(ndim);
303  for (auto i=0; i<ndim; i++)
304  unitpars[i] = Cube[index_map[params[i]]];
305  std::vector<double> derived(phi, phi + nderived);
306 
307  // Disable the printer when the unitcube parameters with speeds below
308  // the threshold have not changed and enable it otherwise
309  // (It might be probably already enabled again at that point)
310  if ( ndim_threshold < ndim
311  && std::equal(prev_slow_unit.begin(),prev_slow_unit.end(),Cube) )
312  {
313  boundLogLike->getPrinter().disable();
314  }
315  else
316  {
317  boundLogLike->getPrinter().enable();
318  }
319  prev_slow_unit = std::vector<double>(Cube,Cube+ndim_threshold);
320 
321  double lnew = boundLogLike(unitpars);
322 
323  // Done! (lnew will be used by PolyChord to guide the search)
324 
325  // Get the transformed parameters and add them as derived parameters
326  if (nderived > 2)
327  {
328  std::unordered_map<std::string,double> param_map;
329  boundLogLike->getPrior().transform(unitpars, param_map);
330  for (auto& param: param_map)
331  {
332  // param_map contains ALL parameters.
333  // We just need the ones which are varied (i.e. the keys of index_map)
334  if (index_map.find(param.first) != index_map.end())
335  phi[index_map[param.first]] = param.second;
336  }
337  }
338 
339  // Get, set and ouptut the process rank and this point's ID
340  int myrank = boundLogLike->getRank(); // MPI rank of this process
341  int pointID = boundLogLike->getPtID(); // point ID number
342  phi[nderived - 2] = myrank;
343  phi[nderived - 1] = pointID;
344 
345  return lnew;
346  }
347 
359 
366  void LogLikeWrapper::dumper(int ndead, int nlive, int npars,
367  double *live, double *dead, double* logweights,
368  double /*logZ*/, double /*logZerr*/)
369  {
370  int thisrank = boundPrinter.get_stream()->getRank(); // MPI rank of this process
371  if(thisrank!=0)
372  {
373  scan_err <<"Error! ScannerBit PolyChord plugin attempted to run 'dumper' function on a worker process "
374  <<"(thisrank=="<<thisrank<<")! PolyChord should only try to run this function on the master "
375  <<"process. Most likely this means that your PolyChord installation is not running in MPI mode "
376  <<"correctly, and is actually running independent scans on each process. Alternatively, the "
377  <<"version of PolyChord you are using may be too far ahead of what this plugin can handle, "
378  <<"if e.g. the described behaviour has changed since this plugin was written."
379  << scan_end;
380  }
381 
382  // Write a file at first run of dumper to specify the index of a given dataset.
383  static bool first = true;
384  if (first)
385  {
386  int ndim = boundSettings.nDims;
387  int nderived = boundSettings.nDerived;
388 
389  // Construct the inversed index map
390  // map[polychord_hypercube] = {name, gambit_hypercube}
391  std::vector<std::string> params = boundLogLike->getShownParameters();
392  std::map<int,std::pair<std::string,int>> inversed_map;
393  for (int i=0; i<ndim; ++i)
394  inversed_map[index_map[params[i]]] = {params[i],i};
395 
396  std::ostringstream fname;
397  fname << boundSettings.base_dir << "/" <<boundSettings.file_root << ".indices";
398  std::ofstream ofs(fname.str());
399 
400  int index = 0;
401  ofs << index++ << "\t" << "Posterior" << std::endl;
402  ofs << index++ << "\t" << "-2*(" << boundLogLike->getPurpose() << ")" << std::endl;
403 
404  for (int i=0; i<ndim; ++i)
405  ofs << index++ << "\t" << "unitCubeParameters[" << inversed_map[i].second <<"]" << std::endl;
406  for (int i=0; i<nderived -2; ++i)
407  ofs << index++ << "\t" << inversed_map[i].first << std::endl;
408 
409  ofs << index++ << "\t" << "MPIrank" << std::endl;
410  ofs << index++ << "\t" << "pointID" << std::endl;
411 
412  ofs.close();
413  }
414  first = false;
415 
416  // Get printers for each auxiliary stream
417  printer* txt_stream( boundPrinter.get_stream("txt") );
418  printer* live_stream( boundPrinter.get_stream("live") );
419 
420  // Reset the print streams. WARNING! This potentially deletes the old data (here we overwrite it on purpose)
421  txt_stream->reset();
422  live_stream->reset();
423 
424  // Ensure the "quantity" IDcode is UNIQUE across all printers! This way fancy printers
425  // have the option of ignoring duplicate writes and doing things like combine all the
426  // auxiliary streams into a single database. But must be able to assume IDcodes are
427  // unique for a given quanity to do this.
428  // Negative numbers not used by functors, so those are 'safe' to use here
429 
430  // The discarded live points (and rejected candidate live points if IS = 1)
431  for( int i_dead = 0; i_dead < ndead; i_dead++ )
432  {
433  int myrank = dead[npars*i_dead + npars-4]; //MPI rank stored in fourth last column
434  int pointID = dead[npars*i_dead + npars-3]; //pointID stored in third last column
435  double logw = logweights[i_dead]; //posterior weight stored in logweights
436  double birth = dead[npars*i_dead + npars-2]; // birth contours
437  double death = dead[npars*i_dead + npars-1]; // death contours
438  txt_stream->print( std::exp(logw), "Posterior", myrank, pointID);
439  txt_stream->print( birth, "LogLike_birth", myrank, pointID);
440  txt_stream->print( death, "LogLike_death", myrank, pointID);
441  }
442 
443  // The last set of live points
444  for( int i_live = 0; i_live < nlive; i_live++ )
445  {
446  int myrank = live[npars*i_live + npars-4]; //MPI rank stored in fourth last column
447  int pointID = live[npars*i_live + npars-3]; //pointID stored in third last column
448  live_stream->print( true, "LastLive", myrank, pointID); // Flag which points were the last live set
449  }
450 
451  }
452 
453  }
454 
455 }
456 
std::vector< double > grade_frac
Definition: polychord.hpp:62
LogLikeWrapper * global_loglike_object
Global pointer to loglikelihood wrapper object, for use in the PolyChord callback functions...
Definition: polychord.cpp:47
bool write_stats
Definition: polychord.hpp:54
void dumper(int, int, int, double *, double *, double *, double, double)
Main interface to PolyChord dumper routine.
Definition: polychord.cpp:366
int num_repeats
Definition: polychord.hpp:39
bool read_resume
Definition: polychord.hpp:53
printer_interface & boundPrinter
Reference to a printer_interface object.
Definition: polychord.hpp:98
Printers::BasePrinterManager printer_interface
typedef printer_interface_temp printer_interface;
#define LOCAL_INFO
Definition: local_info.hpp:34
bool cluster_posteriors
Definition: polychord.hpp:50
double callback_loglike(double *, int, double *, int)
C-functions to pass to PolyChord for the callbacks.
Definition: polychord.cpp:255
void callback_dumper(int, int, int, double *, double *, double *, double, double)
Definition: polychord.cpp:263
scanPtr boundLogLike
Scanner pointer (points to the ScannerBit provided log-likelihood function)
Definition: polychord.hpp:95
std::string base_dir
Definition: polychord.hpp:60
std::unordered_map< std::string, int > index_map
Definition: polychord.hpp:114
bool write_prior
Definition: polychord.hpp:57
Declarations for the YAML options class.
General small utility functions.
double compression_factor
Definition: polychord.hpp:59
bool posteriors
Definition: polychord.hpp:48
Class to connect PolyChord log-likelihood function and ScannerBit likelihood function.
Definition: polychord.hpp:91
double boost_posterior
Definition: polychord.hpp:47
#define plugin_constructor
Runs when the plugin is loaded.
int max_ndead
Definition: polychord.hpp:46
std::unordered_map< std::string, double > transform(const std::vector< double > &vec)
#define reqd_inifile_entries(...)
Tells ScannerBit that these tags are required.
bool write_live
Definition: polychord.hpp:55
double precision_criterion
Definition: polychord.hpp:44
#define scan_err
Defined to macros to output errors in the form: scan_err << "error" << scan_end; scan_warn << "warnin...
#define plugin_main(...)
Declaration of the main function which will be ran by the interface.
int seed
Definition: polychord.hpp:66
std::string file_root
Definition: polychord.hpp:61
double LogLike(double *, int, double *, int)
Main interface function from PolyChord to ScannerBit-supplied loglikelihood function.
Definition: polychord.cpp:291
ScannerBit interface to PolyChord 1.17.1.
virtual void reset(bool force=false)=0
Function to signal to the printer to write buffer contents to disk.
void run_polychord(double(*loglikelihood)(double *, int, double *, int), void(*dumper)(int, int, int, double *, double *, double *, double, double), Settings)
int nDims
Definition: polychord.hpp:36
Printers::BaseBasePrinter printer
Type of the printer objects.
int nlive
Definition: polychord.hpp:38
int printer_speed_threshold
Disable printing for speeds greater and equal than Speeds start at 0 A value of -1 means that all eva...
Definition: polychord.hpp:122
EXPORT_SYMBOLS const str & ensure_path_exists(const str &)
Ensure that a path exists (and then return the path, for chaining purposes)
Gambit::Scanner::like_ptr scanPtr
Typedef for the ScannerBit pointer to the external loglikelihood function.
Definition: polychord.hpp:84
bool equals
Definition: polychord.hpp:49
int nDerived
Definition: polychord.hpp:37
bool write_paramnames
Definition: polychord.hpp:52
bool write_resume
Definition: polychord.hpp:51
#define scan_end
EXPORT_SYMBOLS error & scan_error()
Scanner errors.
#define reqd_libraries(...)
Tells ScannerBit that these libraries are requested.
Settings boundSettings
copy of the settings in use.
Definition: polychord.hpp:117
int feedback
Definition: polychord.hpp:43
scanner_plugin(polychord, version(1, 17, 1))
================================================= Interface to ScannerBit
Definition: polychord.cpp:59
declaration for scanner module
void setValue(const KEYTYPE &key, const VALTYPE &val)
Basic setter, for adding extra options.
LogLikeWrapper(scanPtr, printer_interface &)
Constructor.
Definition: polychord.cpp:274
double logzero
Definition: polychord.hpp:45
TODO: see if we can use this one:
Definition: Analysis.hpp:33
A small wrapper object for &#39;options&#39; nodes.
likelihood container for scanner plugins.
bool do_clustering
Definition: polychord.hpp:42
bool maximise
Definition: polychord.hpp:58
bool write_dead
Definition: polychord.hpp:56
std::vector< int > grade_dims
Definition: polychord.hpp:63
int nprior
Definition: polychord.hpp:40