gambit is hosted by Hepforge, IPPP Durham
GAMBIT  v1.5.0-2191-ga4742ac
a Global And Modular Bsm Inference Tool
multinest.cpp
Go to the documentation of this file.
1 // GAMBIT: Global and Modular BSM Inference Tool
2 // *********************************************
10 //
16 
17 #include <vector>
18 #include <string>
19 #include <cmath>
20 #include <iostream>
21 #include <fstream>
22 #include <map>
23 #include <sstream>
24 #include <iomanip> // For debugging only
25 
30 
31 
32 namespace Gambit
33 {
34  namespace MultiNest
35  {
38  }
39 }
40 
43 
44 
48 
49 scanner_plugin(multinest, version(3, 10))
50 {
51  // An error is thrown if any of the following entries are not present in the inifile (none absolutely required for MultiNest).
53 
54  // Tell cmake system to search known paths for these libraries; any not found must be specified in config/scanner_locations.yaml.
55  reqd_libraries("nest3");
56 
57  // Pointer to the (log)likelihood function
58  scanPtr LogLike;
59 
62  {
63  // Retrieve the external likelihood calculator
64  LogLike = get_purpose(get_inifile_value<std::string>("like"));
65  if (LogLike->getRank() == 0) std::cout << "Loading MultiNest nested sampling plugin for ScannerBit." << std::endl;
66  }
67 
69  int plugin_main (void)
70  {
75  bool resume_mode = get_printer().resume_mode();
77 
78  // Retrieve the dimensionality of the scan.
79  int ma = get_dimension();
80 
81  // Retrieve the global option specifying the minimum interesting likelihood
82  double gl0 = get_inifile_value<double>("likelihood: model_invalid_for_lnlike_below");
83  // Retrieve the global option specifying the likelihood offset to use
84  double offset = get_inifile_value<double>("likelihood: lnlike_offset", 0.);
85  // Make sure the likleihood functor knows to apply the offset internally in ScannerBit
86  LogLike->setPurposeOffset(offset);
87  // Offset the minimum interesting likelihood by the offset
88  gl0 = gl0 + offset;
89 
90  // MultiNest algorithm options.
91  int IS (get_inifile_value<bool>("IS", true) ); // do Nested Importance Sampling?
92  int mmodal (get_inifile_value<bool>("mmodal", true) ); // do mode separation?
93  int ceff (get_inifile_value<bool>("ceff", false) ); // run in constant efficiency mode?
94  int nlive (get_inifile_value<int>("nlive", 1000) ); // number of live points
95  double efr (get_inifile_value<double>("efr", 0.8) ); // set the required efficiency
96  double tol (get_inifile_value<double>("tol", 0.5) ); // tol, defines the stopping criteria
97  int ndims = ma; // dimensionality (no. of free parameters)
98  int nPar = ma+2; // Total no. of parameters including free & derived; +2 == {point ID code, MPI rank}
99  int nClsPar (get_inifile_value<int>("nClsPar",std::min(ma,4))); // No. of parameters to do mode separation on; don't use more than 4
100  int updInt (get_inifile_value<int>("updInt", 1000) ); // after how many iterations feedback is required & the output files should be updated (*10 for dumper)
101  double Ztol (get_inifile_value<double>("Ztol", -1E90) ); // all the modes with logZ < Ztol are ignored
102  int maxModes (get_inifile_value<int>("maxModes", 100) ); // expected max no. of modes (used only for memory allocation)
103  int seed (get_inifile_value<int>("seed", -1) ); // random no. generator seed, if < 0 then take the seed from system clock
104  int fb (get_inifile_value<bool>("fb", true) ); // need feedback on standard output?
105  int resume ( resume_mode ); // resume from a previous job?
106  int outfile (get_inifile_value<bool>("outfile", true) ); // write output files?
107  double ln0 (get_inifile_value<double>("logZero",0.9999*gl0)); // points with loglike < logZero will be ignored by MultiNest
108  int maxiter (get_inifile_value<int>("maxiter", 0) ); // Max no. of iterations, a non-positive value means infinity.
109  int initMPI(0); // Initialise MPI in ScannerBit, not in MultiNest
110  void *context = 0; // any additional information user wants to pass (not required by MN)
111  // Which parameters to have periodic boundary conditions?
112  int pWrap[ndims];
113  for(int i = 0; i < ndims; i++) pWrap[i] = 0; // (need to do more work if we actually want to allow periodic BCs)
114 
115  // TODO: check what happens if resume mode is active but multinest native output is not written. I guess it will resume writing to the printer output, but actually start a new scan?
116 
117  // Root for output files
118  std::string root_str;
119  std::string defpath = Gambit::Utils::ensure_path_exists(
120  get_inifile_value<std::string>("default_output_path")+"MultiNest/native-"
121  ); // from gambit global default
122  root_str = get_inifile_value<std::string>("root", defpath);
123  char root[1000]; // I think MultiNest will truncate this to 100. But lets use a larger array just in case.
124  Gambit::Utils::strcpy2f(root, 1000, root_str);// (copy std::string into char array for transport to Fortran)
125 
126  if(resume==1 and outfile==0)
127  {
128  // It is stupid to be in resume mode while not writing output files.
129  // Means subsequent resumes will be impossible. Throw an error.
130  scan_error().raise(LOCAL_INFO,"Error from MultiNest ScannerBit plugin! Resume mode is activated, however "
131  "MultiNest native output files are set to not be written. These are needed "
132  "for resuming; please change this setting in your yaml file (set option \"outfile: 1\")");
133  }
134 
135  // Setup auxilliary streams. These are only needed by the master process,
136  // so let's create them only for that process
137  int myrank = get_printer().get_stream()->getRank(); // MPI rank of this process
138  if(myrank==0)
139  {
140  // Get inifile options for each print stream
141  Gambit::Options txt_options = get_inifile_node("aux_printer_txt_options");
142  //Gambit::Options stats_options = get_inifile_node("aux_printer_stats_options"); //FIXME
143  Gambit::Options live_options = get_inifile_node("aux_printer_live_options");
144 
145  // Options to desynchronise print streams from the main Gambit iterations. This allows for random access writing, or writing of global scan data.
146  //stats_options.setValue("synchronised",false); //FIXME
147  txt_options.setValue("synchronised",false);
148  live_options.setValue("synchronised",false);
149 
150  // Initialise auxiliary print streams
151  get_printer().new_stream("txt",txt_options);
152  //get_printer().new_stream("stats",stats_options); //FIXME
153  get_printer().new_stream("live",live_options);
154  }
155 
156  // Ensure that MPI processes have the same IDs for auxiliary print streams;
157  Gambit::Scanner::assign_aux_numbers("Posterior","LastLive");
158 
159  // Create the object that interfaces to the MultiNest LogLike callback function
160  Gambit::MultiNest::LogLikeWrapper loglwrapper(LogLike, get_printer());
162 
163  //Run MultiNest, passing callback functions for the loglike and dumper.
164  if(myrank == 0) std::cout << "Starting MultiNest run..." << std::endl;
165  run(IS, mmodal, ceff, nlive, tol, efr, ndims, nPar, nClsPar, maxModes, updInt, Ztol,
166  root, seed, pWrap, fb, resume, outfile, initMPI, ln0, maxiter,
168  if(myrank == 0) std::cout << "Multinest run finished!" << std::endl;
169  return 0;
170 
171  }
172 
173 }
174 
175 
179 
180 namespace Gambit {
181 
182  namespace MultiNest {
183 
185  // Note: we are using the c interface from cwrapper.f90, so the function
186  // signature is a little different than in the multinest examples.
187  double callback_loglike(double *Cube, int ndim, int npars, void*)
188  {
189  // Call global interface to ScannerBit loglikelihood function
190  // Could also pass this object in via context pointer, but that
191  // involves some casting and could risk a segfault.
192  return global_loglike_object->LogLike(Cube, ndim, npars);
193  }
194 
195  void callback_dumper(int nSamples, int nlive, int nPar, double *physLive,
196  double *posterior, double *paramConstr,
197  double maxLogLike, double logZ, double logZerr,
198  void*)
199  {
201  dumper(nSamples, nlive, nPar, physLive, posterior, paramConstr,
202  maxLogLike, logZ, logZerr);
203  }
205 
206 
209  : boundLogLike(loglike), boundPrinter(printer), dumper_runonce(false)
210  { }
211 
229  double LogLikeWrapper::LogLike(double *Cube, int ndim, int)
230  {
231  //convert C style array to C++ vector class
232  std::vector<double> unitpars(Cube, Cube + ndim);
233 
234  double lnew = boundLogLike(unitpars);
235 
236  // Extract the primary printer from the printer manager
237  //printer* primary_stream( boundPrinter.get_stream() );
238 
239  // Get, set and ouptut the process rank and this point's ID
240  int myrank = boundLogLike->getRank(); // MPI rank of this process
241  int pointID = boundLogLike->getPtID(); // point ID number
242  Cube[ndim+0] = myrank;
243  Cube[ndim+1] = pointID;
244 
245  // Done! (lnew will be used by MultiNest to guide the search)
246  return lnew;
247  }
248 
262 
275  void LogLikeWrapper::dumper(int nSamples, int nlive, int nPar, double *physLive, double *posterior, double* /*paramConstr*/,
276  double /*maxLogLike*/, double /*logZ*/, double /*logZerr*/)
277  {
278  int thisrank = boundPrinter.get_stream()->getRank(); // MPI rank of this process
279  if(thisrank!=0)
280  {
281  scan_err <<"Error! ScannerBit MultiNest plugin attempted to run 'dumper' function on a worker process "
282  <<"(thisrank=="<<thisrank<<")! MultiNest should only try to run this function on the master "
283  <<"process. Most likely this means that your multinest installation is not running in MPI mode "
284  <<"correctly, and is actually running independent scans on each process. Alternatively, the "
285  <<"version of MultiNest you are using may be too far ahead of what this plugin can handle, "
286  <<"if e.g. the described behaviour has changed since this plugin was written."
287  << scan_end;
288  }
289 
290  // Send signal to other processes to switch to higher min_logL value.
291  // MultiNest was sometimes getting stuck looking for live point candidates;
292  // increasing this above the MultiNext zero_LogL value should avoid that
293  // issue.
294  // We do this here because initial live point generation should be finished
295  // once the dumper runs, and we want the original min_logL value while generating
296  // live points.
297  if (!dumper_runonce)
298  {
299  dumper_runonce = true;
300  boundLogLike->switch_to_alternate_min_LogL();
301  std::cerr << "Multinest dumper first ran on process "<<boundLogLike->getRank()<<" at iteration "<<boundLogLike->getPtID()<<std::endl;
302  }
303 
304  // Get printers for each auxiliary stream
305  //printer* stats_stream( boundPrinter.get_stream("stats") ); //FIXME see below
306  printer* txt_stream( boundPrinter.get_stream("txt") );
307  printer* live_stream( boundPrinter.get_stream("live") );
308 
309  // Reset the print streams. WARNING! This potentially deletes the old data (here we overwrite it on purpose)
310  //stats_stream->reset(); // FIXME
311  txt_stream->reset();
312  live_stream->reset();
313 
314  // Ensure the "quantity" IDcode is UNIQUE across all printers! This way fancy printers
315  // have the option of ignoring duplicate writes and doing things like combine all the
316  // auxiliary streams into a single database. But must be able to assume IDcodes are
317  // unique for a given quanity to do this.
318  // Negative numbers not used by functors, so those are 'safe' to use here
319 
320  // FIXME this is buggy atm
321  // Stats file
322  // For now, MPIrank set to 0 and pointID set to -1, as not needed. Might change how this works later.
323  // Quantity Label IDcode MPIrank pointID
324  //stats_stream->print(maxLogLike, "maxLogLike", -1, 0, -1);
325  //stats_stream->print(logZ, "logZ", -2, 0, -1);
326  //stats_stream->print(logZerr, "logZerr", -3, 0, -1);
327 
328  // txt file stuff
329  // Send info for each point to printer one command at a time
330  int pointID; // ID number for each point
331  int myrank; // MPI rank which wrote each point
332 
333  // The discarded live points (and rejected candidate live points if IS = 1)
334  for( int i = 0; i < nSamples; i++ )
335  {
336  myrank = posterior[(nPar-2)*nSamples + i]; //MPI rank stored in second last entry of cube
337  pointID = posterior[(nPar-1)*nSamples + i]; //pointID stored in last entry of cube
338 
339  txt_stream->print( posterior[(nPar+1)*nSamples + i], "Posterior", myrank, pointID);
340  // Put rest of parameters into a vector for printing all together // TODO: not needed, delete?
341  // std::vector<double> parameters;
342  // for( int j = 0; j < nPar-2; j++ )
343  // {
344  // parameters.push_back( posterior[j*nSamples + i] );
345  // }
346  }
347 
348  // The last set of live points
349  for( int i = 0; i < nlive; i++ )
350  {
351  myrank = physLive[(nPar-2)*nlive + i]; //MPI rank number stored in second last entry of cube
352  pointID = physLive[(nPar-1)*nlive + i]; //pointID stored in last entry of cube
353  live_stream->print( true, "LastLive", myrank, pointID); // Flag which points were the last live set
354  // // Put rest of parameters into a vector for printing all together // TODO: not needed, delete?
355  // std::vector<double> parameters;
356  // for( int j = 0; j < nPar-2; j++ )
357  // {
358  // parameters.push_back( physLive[j*nlive + i] );
359  // }
360  // //live_stream->print(parameters, "Parameters", myrank, pointID);
361  }
362 
363  }
364 
365  }
366 
367 }
368 
scanner_plugin(multinest, version(3, 10))
================================================= Interface to ScannerBit
Definition: multinest.cpp:49
double LogLike(double *, int, int)
Main interface function from MultiNest to ScannerBit-supplied loglikelihood function.
Definition: multinest.cpp:229
Printers::BasePrinterManager printer_interface
typedef printer_interface_temp printer_interface;
#define LOCAL_INFO
Definition: local_info.hpp:34
void run(bool, bool, bool, int, double, double, int, int, int, int, int, double, char[], int, int[], bool, bool, bool, bool, double, int, double(*)(double *, int, int, void *), void(*)(int, int, int, double *, double *, double *, double, double, double, void *), void *)
double callback_loglike(double *, int, int, void *)
Plain-vanilla C-functions to pass to Multinest for the callbacks.
Definition: multinest.cpp:187
Declarations for the YAML options class.
General small utility functions.
EXPORT_SYMBOLS void strcpy2f(char *, int, str)
Copy a str to a character array, stripping the null termination character.
bool dumper_runonce
Variable to indicate whether the dumper function has been run at least once.
Definition: multinest.hpp:67
#define plugin_constructor
Runs when the plugin is loaded.
#define reqd_inifile_entries(...)
Tells ScannerBit that these tags are required.
#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.
virtual void reset(bool force=false)=0
Function to signal to the printer to write buffer contents to disk.
Printers::BaseBasePrinter printer
Type of the printer objects.
LogLikeWrapper * global_loglike_object
Global pointer to loglikelihood wrapper object, for use in the MultiNest callback functions...
Definition: multinest.cpp:37
void callback_dumper(int, int, int, double *, double *, double *, double, double, double, void *)
Definition: multinest.cpp:195
ScannerBit interface to Multinest 3.10.
EXPORT_SYMBOLS const str & ensure_path_exists(const str &)
Ensure that a path exists (and then return the path, for chaining purposes)
printer_interface & boundPrinter
Reference to a printer_interface object.
Definition: multinest.hpp:64
void dumper(int, int, int, double *, double *, double *, double, double, double)
Main interface to MultiNest dumper routine.
Definition: multinest.cpp:275
#define scan_end
EXPORT_SYMBOLS error & scan_error()
Scanner errors.
#define reqd_libraries(...)
Tells ScannerBit that these libraries are requested.
declaration for scanner module
scanPtr boundLogLike
Scanner pointer (points to the ScannerBit provided log-likelihood function)
Definition: multinest.hpp:61
void setValue(const KEYTYPE &key, const VALTYPE &val)
Basic setter, for adding extra options.
LogLikeWrapper(scanPtr, printer_interface &)
Constructor.
Definition: multinest.cpp:208
Class to connect multinest log-likelihood function and ScannerBit likelihood function.
Definition: multinest.hpp:57
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.
Gambit::Scanner::like_ptr scanPtr
Typedef for the ScannerBit pointer to the external loglikelihood function.
Definition: multinest.hpp:50