def __init__(self, command_line, path): """ The Data class holds the cosmological information, the parameters from the MCMC run, the information coming from the likelihoods. It is a wide collections of information, with in particular two main dictionaries: cosmo_arguments and mcmc_parameters. It defines several useful **methods**. The following ones are called just once, at initialization: * :func:`fill_mcmc_parameters` * :func:`read_file` * :func:`read_version` * :func:`group_parameters_in_blocks` On the other hand, these two following functions are called every step. * :func:`check_for_slow_step` * :func:`update_cosmo_arguments` Finally, the convenient method :func:`get_mcmc_parameters` will be called in many places, to return the proper list of desired parameters. It has a number of different **attributes**, and the more important ones are listed here: * :attr:`boundary_loglike` * :attr:`cosmo_arguments` * :attr:`mcmc_parameters` * :attr:`need_cosmo_update` * :attr:`log_flag` .. note:: The `experiments` attribute is extracted from the parameter file, and contains the list of likelihoods to use .. note:: The path argument will be used in case it is a first run, and hence a new folder is created. If starting from an existing folder, this dictionary will be compared with the one extracted from the log.param, and will use the latter while warning the user. .. warning:: New in version 2.0.0, you can now specify an oversampling of the nuisance parameters, to hasten the execution of a run with likelihoods that have many of them. You should specify a new field in the parameter file, `data.over_sampling = [1, ...]`, that contains a 1 on the first element, and then the over sampling of the desired likelihoods. This array must have the same size as the number of blocks (1 for the cosmo + 1 for each likelihood with varying nuisance parameters). You need to call the code with the flag `-j jast` for it to be used. To create an instance of this class, one must feed the following parameters and keyword arguments: Parameters ---------- command_line : NameSpace NameSpace containing the input from the :mod:`parser_mp`. It stores the input parameter file, the jumping methods, the output folder, etc... Most of the information extracted from the command_file will be transformed into :class:`Data` attributes, whenever it felt meaningful to do so. path : dict Contains a dictionary of important local paths. It is used here to find the cosmological module location. """ # Initialisation of the random seed rd.seed() # Store the parameter file self.param = command_line.param # Recover jumping method from command_line self.jumping = command_line.jumping self.jumping_factor = command_line.jumping_factor # Store the rest of the command line self.command_line = command_line # Initialise the path dictionnary. self.path = {} self.boundary_loglike = -1e30 """ Define the boundary loglike, the value used to defined a loglike that is out of bounds. If a point in the parameter space is affected to this value, it will be automatically rejected, hence increasing the multiplicity of the last accepted point. """ # Creation of the two main dictionnaries: self.cosmo_arguments = {} """ Simple dictionary that will serve as a communication interface with the cosmological code. It contains all the parameters for the code that will not be set to their default values. It is updated from :attr:`mcmc_parameters`. :rtype: dict """ self.mcmc_parameters = od() """ Ordered dictionary of dictionaries, it contains everything needed by the :mod:`mcmc` module for the MCMC procedure. Every parameter name will be the key of a dictionary, containing the initial configuration, role, status, last accepted point and current point. :rtype: ordereddict """ # Arguments for PyMultiNest self.NS_param_names = [] self.NS_arguments = {} """ Dictionary containing the parameters needed by the PyMultiNest sampler. It is filled just before the run of the sampler. Those parameters not defined will be set to the default value of PyMultiNest. :rtype: dict """ # Initialise the experiments attribute self.experiments = [] # Initialise the oversampling setting self.over_sampling = [] """ List storing the respective over sampling of the parameters. The first entry, applied to the cosmological parameters, will always be 1. Setting it to anything else would simply rescale the whole process. If not specified otherwise in the parameter file, all other numbers will be set to 1 as well. :rtype: list """ # Default value for the number of steps self.N = 10 # Create the variable out, and out_name, which will be initialised # later by the :mod:`io_mp` module self.out = None self.out_name = '' # If the parameter file is not a log.param, the path will be read # before reading the parameter file. if self.param.find('log.param') == -1: self.path.update(path) # Read from the parameter file to fill properly the mcmc_parameters # dictionary. self.fill_mcmc_parameters() # Test if the recovered path agrees with the one extracted from # the configuration file. if self.path != {}: if not self.path.has_key('root'): self.path.update({'root': path['root']}) if self.path != path: warnings.warn("Your code location in the log.param file is " "in contradiction with your .conf file. " "I will use the one from log.param.") # Determine which cosmological code is in use if self.path['cosmo'].find('class') != -1: self.cosmological_module_name = 'CLASS' else: self.cosmological_module_name = None # check for MPI try: from mpi4py import MPI comm = MPI.COMM_WORLD rank = comm.Get_rank() except ImportError: # set all chains to master if no MPI rank = 0 # Recover the cosmological code version (and git hash if relevant). # To implement a new cosmological code, please add another case to the # test below. if self.cosmological_module_name == 'CLASS': # Official version number common_file_path = os.path.join(self.path['cosmo'], 'include', 'common.h') with open(common_file_path, 'r') as common_file: for line in common_file: if line.find('_VERSION_') != -1: self.version = line.split()[-1].replace('"', '') break if not command_line.silent and not rank: print 'with CLASS %s' % self.version # Git version number and branch try: # This nul_file helps to get read of a potential useless error # message with open(os.devnull, "w") as nul_file: self.git_version = sp.Popen( ["git", "rev-parse", "HEAD"], cwd=self.path['cosmo'], stdout=sp.PIPE, stderr=nul_file).communicate()[0].strip() self.git_branch = sp.Popen( ["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd=self.path['cosmo'], stdout=sp.PIPE, stderr=nul_file).communicate()[0].strip() except (sp.CalledProcessError, OSError): # Note, OSError seems to be raised on some systems, instead of # sp.CalledProcessError - which seems to be linked to the # existence of os.devnull, so now both error are caught. warnings.warn( "Running CLASS from a non version-controlled repository") self.git_version, self.git_branch = '', '' # If using an existing log.param, read in and compare this number # to the already stored one if self.param.find('log.param') != -1: try: version, git_version, git_branch = self.read_version( self.param_file) if version != self.version: warnings.warn( "Your version of CLASS: %s" % self.version + " does not match the one used previously" + " in this folder (%s)." % version + " Proceed with caution") else: if self.git_branch != git_branch: warnings.warn( "CLASS set to branch %s" % self.git_branch + ", wrt. the one used in the log.param:" + " %s." % git_branch) if self.git_version != git_version: warnings.warn( "CLASS set to version %s" % self.git_version + ", wrt. the one used in the log.param:" + " %s." % git_version) except AttributeError: # This error is raised when the regular expression match # failed - due to comparing to an old log.param that did # not have this feature properly implemented. Ignore this. pass else: raise io_mp.CosmologicalModuleError( "If you want to check for another cosmological module version" " please add an elif clause to this part") # End of initialisation with the parameter file self.param_file.close() self.log_flag = False """ Stores the information whether or not the likelihood data files need to be written down in the log.param file. Initially at False. :rtype: bool """ self.need_cosmo_update = True """ `added in version 1.1.1`. It stores the truth value of whether the cosmological block of parameters was changed from one step to another. See :meth:`group_parameters_in_blocks` :rtype: bool """ # logging the parameter file (only if folder does not exist !) ## temporary variable for readability log_param = os.path.join(command_line.folder, 'log.param') if (os.path.exists(command_line.folder) and not os.path.exists(log_param)): if command_line.param is not None: warnings.warn( "Detecting empty folder, logging the parameter file") io_mp.log_parameters(self, command_line) self.log_flag = True if not os.path.exists(command_line.folder): os.makedirs(command_line.folder) # Logging of parameters io_mp.log_parameters(self, command_line) self.log_flag = True if not command_line.silent and not rank: print '\nTesting likelihoods for:\n ->', print ', '.join(self.experiments) + '\n' self.initialise_likelihoods(self.experiments) # Storing parameters by blocks of speed self.group_parameters_in_blocks() # Finally, log the cosmo_arguments used. This comes in the end, because # it can be modified inside the likelihoods init functions if self.log_flag: io_mp.log_cosmo_arguments(self, command_line) io_mp.log_default_configuration(self, command_line) # Log plotting parameter names file for compatibility with GetDist io_mp.log_parameter_names(self, command_line)
def compute_lkl(cosmo, data): """ Compute the likelihood, given the current point in parameter space. This function now performs a test before calling the cosmological model (**new in version 1.2**). If any cosmological parameter changed, the flag :code:`data.need_cosmo_update` will be set to :code:`True`, from the routine :func:`check_for_slow_step <data.Data.check_for_slow_step>`. Returns ------- loglike : float The log of the likelihood (:math:`\\frac{-\chi^2}2`) computed from the sum of the likelihoods of the experiments specified in the input parameter file. This function returns :attr:`data.boundary_loglkie <data.data.boundary_loglike>`, defined in the module :mod:`data` if *i)* the current point in the parameter space has hit a prior edge, or *ii)* the cosmological module failed to compute the model. This value is chosen to be extremly small (large negative value), so that the step will always be rejected. """ from classy import CosmoSevereError, CosmoComputationError # If the cosmological module has already been called once, and if the # cosmological parameters have changed, then clean up, and compute. if cosmo.state and data.need_cosmo_update is True: cosmo.struct_cleanup() # If the data needs to change, then do a normal call to the cosmological # compute function. Note that, even if need_cosmo update is True, this # function must be called if the jumping factor is set to zero. Indeed, # this means the code is called for only one point, to set the fiducial # model. if ((data.need_cosmo_update) or (not cosmo.state) or (data.jumping_factor == 0)): # Prepare the cosmological module with the new set of parameters cosmo.set(data.cosmo_arguments) # Compute the model, keeping track of the errors # In classy.pyx, we made use of two type of python errors, to handle # two different situations. # - CosmoSevereError is returned if a parameter was not properly set # during the initialisation (for instance, you entered Ommega_cdm # instead of Omega_cdm). Then, the code exits, to prevent running with # imaginary parameters. This behaviour is also used in case you want to # kill the process. # - CosmoComputationError is returned if Class fails to compute the # output given the parameter values. This will be considered as a valid # point, but with minimum likelihood, so will be rejected, resulting in # the choice of a new point. try: cosmo.compute(["lensing"]) except CosmoComputationError as failure_message: sys.stderr.write(str(failure_message) + '\n') sys.stderr.flush() return data.boundary_loglike except CosmoSevereError as critical_message: raise io_mp.CosmologicalModuleError( "Something went wrong when calling CLASS" + str(critical_message)) except KeyboardInterrupt: raise io_mp.CosmologicalModuleError("You interrupted execution") # For each desired likelihood, compute its value against the theoretical # model loglike = 0 # This flag holds the information whether a fiducial model was written. In # this case, the log likelihood returned will be '1j', meaning the # imaginary number i. flag_wrote_fiducial = 0 for likelihood in data.lkl.itervalues(): if likelihood.need_update is True: value = likelihood.loglkl(cosmo, data) # Storing the result likelihood.backup_value = value # Otherwise, take the existing value else: value = likelihood.backup_value loglike += value # In case the fiducial file was written, store this information if value == 1j: flag_wrote_fiducial += 1 # Compute the derived parameters if relevant if data.get_mcmc_parameters(['derived']) != []: try: derived = cosmo.get_current_derived_parameters( data.get_mcmc_parameters(['derived'])) for name, value in derived.iteritems(): data.mcmc_parameters[name]['current'] = value except AttributeError: # This happens if the classy wrapper is still using the old # convention, expecting data as the input parameter cosmo.get_current_derived_parameters(data) except CosmoSevereError: raise io_mp.CosmologicalModuleError( "Could not write the current derived parameters") for elem in data.get_mcmc_parameters(['derived']): data.mcmc_parameters[elem]['current'] /= \ data.mcmc_parameters[elem]['scale'] # If fiducial files were created, inform the user, and exit if flag_wrote_fiducial > 0: if flag_wrote_fiducial == len(data.lkl): raise io_mp.FiducialModelWritten( "Fiducial file(s) was(were) created, please start a new chain") else: raise io_mp.FiducialModelWritten( "Some previously non-existing fiducial files were created, " + "but potentially not all of them. Please check now manually" + " on the headers, of the corresponding that all parameters " + "are coherent for your tested models") return loglike