def dict(self) -> dict: """ A dictionary representation of this object """ from autofit.mapper.prior_model.abstract import AbstractPriorModel from autofit.mapper.prior_model.collection import CollectionPriorModel from autofit.mapper.prior_model.prior_model import PriorModel from autofit.mapper.prior.tuple_prior import TuplePrior if isinstance(self, CollectionPriorModel): type_ = "collection" elif isinstance(self, AbstractPriorModel) and self.prior_count == 0: type_ = "instance" elif isinstance(self, PriorModel): type_ = "model" elif isinstance(self, TuplePrior): type_ = "tuple_prior" else: raise AssertionError( f"{self.__class__.__name__} cannot be serialised to dict") dict_ = {"type": type_} for key, value in self._dict.items(): try: if not isinstance(value, ModelObject): value = AbstractPriorModel.from_instance(value) value = value.dict() except AttributeError: pass except TypeError: pass dict_[key] = value return dict_
def from_results_internal( cls, results_internal: np.ndarray, log_posterior_list: np.ndarray, model: AbstractPriorModel, total_iterations: int, time: Optional[float] = None, ): """ The `Samples` classes in **PyAutoFit** provide an interface between the results of a `NonLinearSearch` (e.g. as files on your hard-disk) and Python. To create a `Samples` object after an `pyswarms` model-fit the results must be converted from the native format used by `pyswarms` (which are numpy ndarrays) to lists of values, the format used by the **PyAutoFit** `Samples` objects. This classmethod performs this conversion before creating a `PySwarmsSamples` object. Parameters ---------- results_internal The Pyswarms results in their native internal format from which the samples are computed. log_posterior_list The log posterior of the PySwarms accepted samples. model Maps input vectors of unit parameter values to physical values and model instances via priors. total_iterations The total number of PySwarms iterations, which cannot be estimated from the sample list (which contains only accepted samples). time The time taken to perform the model-fit, which is passed around `Samples` objects for outputting information on the overall fit. """ parameter_lists = [ param.tolist() for parameters in results_internal for param in parameters ] log_prior_list = model.log_prior_list_from( parameter_lists=parameter_lists) log_likelihood_list = [ lp - prior for lp, prior in zip(log_posterior_list, log_prior_list) ] weight_list = len(log_likelihood_list) * [1.0] sample_list = Sample.from_lists( model=model, parameter_lists=[ parameters.tolist()[0] for parameters in results_internal ], log_likelihood_list=log_likelihood_list, log_prior_list=log_prior_list, weight_list=weight_list) return PySwarmsSamples( model=model, sample_list=sample_list, total_iterations=total_iterations, time=time, results_internal=results_internal, )
def __setitem__(self, key, value): obj = AbstractPriorModel.from_object(value) try: obj.id = getattr(self, str(key)).id except AttributeError: pass setattr(self, str(key), obj)
def fit( self, model: abstract.AbstractPriorModel, analysis: abstract_search.Analysis ): best_likelihood = float("-inf") best_instance = None likelihoods = list() for list_ in make_lists( no_dimensions=model.prior_count, step_size=self.step_size ): instance = model.instance_from_unit_vector( list_ ) likelihood = analysis.log_likelihood_function( instance ) likelihoods.append(likelihood) if likelihood > best_likelihood: best_likelihood = likelihood best_instance = instance return Result( samples=MockSamples( max_log_likelihood_instance=best_instance, log_likelihood_list=likelihoods, gaussian_tuples=None ), model=model )
def from_results_internal( cls, results_internal: Results, model: AbstractPriorModel, number_live_points: int, unconverged_sample_size: int = 100, time: Optional[float] = None, ): """ The `Samples` classes in **PyAutoFit** provide an interface between the results of a `NonLinearSearch` (e.g. as files on your hard-disk) and Python. To create a `Samples` object after a `dynesty` model-fit the results must be converted from the native format used by `dynesty` to lists of values, the format used by the **PyAutoFit** `Samples` objects. This classmethod performs this conversion before creating a `DynestySamples` object. Parameters ---------- results_internal The `dynesty` results in their native internal format from which the samples are computed. model Maps input vectors of unit parameter values to physical values and model instances via priors. number_live_points The number of live points used by the `dynesty` search. unconverged_sample_size If the samples are for a search that is yet to convergence, a reduced set of samples are used to provide a rough estimate of the parameters. The number of samples is set by this parameter. time The time taken to perform the model-fit, which is passed around `Samples` objects for outputting information on the overall fit. """ parameter_lists = results_internal.samples.tolist() log_prior_list = model.log_prior_list_from(parameter_lists=parameter_lists) log_likelihood_list = list(results_internal.logl) try: weight_list = list( np.exp(np.asarray(results_internal.logwt) - results_internal.logz[-1]) ) except: weight_list = results_internal["weights"] sample_list = Sample.from_lists( model=model, parameter_lists=parameter_lists, log_likelihood_list=log_likelihood_list, log_prior_list=log_prior_list, weight_list=weight_list, ) return DynestySamples( model=model, sample_list=sample_list, number_live_points=number_live_points, unconverged_sample_size=unconverged_sample_size, time=time, results_internal=results_internal, )
def __setattr__(self, key, value): if key.startswith("_"): super().__setattr__(key, value) else: try: super().__setattr__(key, AbstractPriorModel.from_object(value)) except AttributeError: pass
def from_dict(d): """ Recursively parse a dictionary returning the model, collection or instance that is represents. Parameters ---------- d A dictionary representation of some object Returns ------- An instance """ from autofit.mapper.prior_model.abstract import AbstractPriorModel from autofit.mapper.prior_model.collection import CollectionPriorModel from autofit.mapper.prior_model.prior_model import PriorModel from autofit.mapper.prior.prior import Prior from autofit.mapper.prior.prior import TuplePrior if not isinstance( d, dict ): return d type_ = d["type"] if type_ == "model": instance = PriorModel( get_class( d.pop("class_path") ) ) elif type_ == "collection": instance = CollectionPriorModel() elif type_ == "instance": cls = get_class( d.pop("class_path") ) instance = object.__new__(cls) elif type_ == "tuple_prior": instance = TuplePrior() else: return Prior.from_dict(d) d.pop("type") for key, value in d.items(): setattr( instance, key, AbstractPriorModel.from_dict(value) ) return instance
def instance_for_model(self, model: AbstractPriorModel): """ Create an instance from this sample for a model Parameters ---------- model The model the this sample was taken from Returns ------- The instance corresponding to this sample """ try: return model.instance_from_vector( self.parameter_lists_for_model(model)) except KeyError: paths = model.model_component_and_parameter_names return model.instance_from_vector( self.parameter_lists_for_model(model, paths))
def samples_in_test_mode(self, total_points: int, model: AbstractPriorModel): """ Generate the initial points of the non-linear search in test mode. Like normal, test model draws points, by randomly drawing unit values from a uniform distribution between the ball_lower_limit and ball_upper_limit values. However, the log likelihood function is bypassed and all likelihoods are returned with a value -1.0e99. This is so that integration testing of large-scale model-fitting projects can be performed efficiently by bypassing sampling of points using the `log_likelihood_function`. Parameters ---------- total_points The number of points in non-linear paramemter space which initial points are created for. model An object that represents possible instances of some model with a given dimensionality which is the number of free dimensions of the model. """ unit_parameter_lists = [] parameter_lists = [] figure_of_merit_list = [] point_index = 0 while point_index < total_points: unit_parameter_list = model.random_unit_vector_within_limits( lower_limit=self.lower_limit, upper_limit=self.upper_limit) parameter_list = model.vector_from_unit_vector( unit_vector=unit_parameter_list) unit_parameter_lists.append(unit_parameter_list) parameter_lists.append(parameter_list) figure_of_merit_list.append(-1.0e99) point_index += 1 return unit_parameter_lists, parameter_lists, figure_of_merit_list
def instance_for_model(self, model: AbstractPriorModel): """ Create an instance from this sample for a model Parameters ---------- model The model the this sample was taken from Returns ------- The instance corresponding to this sample """ try: if self.is_path_kwargs: return model.instance_from_path_arguments(self.kwargs) else: return model.instance_from_prior_name_arguments(self.kwargs) except KeyError: # TODO: Does this get used? If so, why? return model.instance_from_vector( self.parameter_lists_for_model(model))
def _fit(self, model: AbstractPriorModel, analysis, log_likelihood_cap=None): if self.fit_fast: result = self._fit_fast(model=model, analysis=analysis) return result if model.prior_count == 0: raise AssertionError( "There are no priors associated with the model!") if model.prior_count != len(model.unique_prior_paths): raise AssertionError( "Prior count doesn't match number of unique prior paths") index = 0 unit_vector = model.prior_count * [0.5] while True: try: instance = model.instance_from_unit_vector(unit_vector) fit = analysis.log_likelihood_function(instance) break except exc.FitException as e: unit_vector[index] += 0.1 if unit_vector[index] >= 1: raise e index = (index + 1) % model.prior_count samples = MockSamples( samples=samples_with_log_likelihood_list(self.sample_multiplier * fit), model=model, gaussian_tuples=[ (prior.mean, prior.width if math.isfinite(prior.width) else 1.0) for prior in sorted(model.priors, key=lambda prior: prior.id) ], ) self.paths.save_samples(samples) return MockResult( model=model, samples=samples, )
def from_results_internal( cls, results_internal, model: AbstractPriorModel, auto_correlation_settings: AutoCorrelationsSettings, unconverged_sample_size: int = 100, time: Optional[float] = None, ): """ The `Samples` classes in **PyAutoFit** provide an interface between the results of a `NonLinearSearch` (e.g. as files on your hard-disk) and Python. To create a `Samples` object after an `Zeus` model-fit the results must be converted from the native format used by `Zeus` (which is a HDFBackend) to lists of values, the format used by the **PyAutoFit** `Samples` objects. This classmethod performs this conversion before creating a `ZeusSamples` object. Parameters ---------- results_internal The MCMC results in their native internal format from which the samples are computed. model Maps input vectors of unit parameter values to physical values and model instances via priors. auto_correlations_settings Customizes and performs auto correlation calculations performed during and after the search. unconverged_sample_size If the samples are for a search that is yet to convergence, a reduced set of samples are used to provide a rough estimate of the parameters. The number of samples is set by this parameter. time The time taken to perform the model-fit, which is passed around `Samples` objects for outputting information on the overall fit. """ parameter_lists = results_internal.get_chain(flat=True).tolist() log_posterior_list = results_internal.get_log_prob(flat=True).tolist() log_prior_list = model.log_prior_list_from( parameter_lists=parameter_lists) log_likelihood_list = [ log_posterior - log_prior for log_posterior, log_prior in zip( log_posterior_list, log_prior_list) ] weight_list = len(log_likelihood_list) * [1.0] sample_list = Sample.from_lists( model=model, parameter_lists=parameter_lists, log_likelihood_list=log_likelihood_list, log_prior_list=log_prior_list, weight_list=weight_list) return ZeusSamples( model=model, sample_list=sample_list, auto_correlation_settings=auto_correlation_settings, unconverged_sample_size=unconverged_sample_size, time=time, results_internal=results_internal, )
def _fit(self, model: AbstractPriorModel, analysis, log_likelihood_cap=None): """ Fit a model using PySwarms and the Analysis class which contains the data and returns the log likelihood from instances of the model, which the `NonLinearSearch` seeks to maximize. Parameters ---------- model : ModelMapper The model which generates instances for different points in parameter space. analysis : Analysis Contains the data and the log likelihood function which fits an instance of the model to the data, returning the log likelihood the `NonLinearSearch` maximizes. Returns ------- A result object comprising the Samples object that inclues the maximum log likelihood instance and full chains used by the fit. """ fitness_function = self.fitness_function_from_model_and_analysis( model=model, analysis=analysis ) if self.paths.is_object("points"): init_pos = self.paths.load_object("points")[-1] total_iterations = self.paths.load_object("total_iterations") self.logger.info("Existing PySwarms samples found, resuming non-linear search.") else: unit_parameter_lists, parameter_lists, log_posterior_list = self.initializer.samples_from_model( total_points=self.config_dict_search["n_particles"], model=model, fitness_function=fitness_function, ) init_pos = np.zeros(shape=(self.config_dict_search["n_particles"], model.prior_count)) for index, parameters in enumerate(parameter_lists): init_pos[index, :] = np.asarray(parameters) total_iterations = 0 self.logger.info("No PySwarms samples found, beginning new non-linear search. ") ## TODO : Use actual limits vector_lower = model.vector_from_unit_vector( unit_vector=[1e-6] * model.prior_count, ignore_prior_limits=True ) vector_upper = model.vector_from_unit_vector( unit_vector=[0.9999999] * model.prior_count, ignore_prior_limits=True ) lower_bounds = [lower for lower in vector_lower] upper_bounds = [upper for upper in vector_upper] bounds = (np.asarray(lower_bounds), np.asarray(upper_bounds)) self.logger.info("Running PySwarmsGlobal Optimizer...") while total_iterations < self.config_dict_run["iters"]: pso = self.sampler_from( model=model, fitness_function=fitness_function, bounds=bounds, init_pos=init_pos ) iterations_remaining = self.config_dict_run["iters"] - total_iterations iterations = min(self.iterations_per_update, iterations_remaining) if iterations > 0: pso.optimize(objective_func=fitness_function.__call__, iters=iterations) total_iterations += iterations self.paths.save_object( "total_iterations", total_iterations ) self.paths.save_object( "points", pso.pos_history ) self.paths.save_object( "log_posterior_list", [-0.5 * cost for cost in pso.cost_history] ) self.perform_update( model=model, analysis=analysis, during_analysis=True ) init_pos = self.paths.load_object("points")[-1] self.logger.info("PySwarmsGlobal complete")
def samples_from_model(self, total_points: int, model: AbstractPriorModel, fitness_function, use_prior_medians: bool = False): """ Generate the initial points of the non-linear search, by randomly drawing unit values from a uniform distribution between the ball_lower_limit and ball_upper_limit values. Parameters ---------- total_points The number of points in non-linear paramemter space which initial points are created for. model An object that represents possible instances of some model with a given dimensionality which is the number of free dimensions of the model. """ if conf.instance["general"]["test"]["test_mode"]: return self.samples_in_test_mode(total_points=total_points, model=model) logger.info( "Generating initial samples of model, which are subject to prior limits and other constraints." ) unit_parameter_lists = [] parameter_lists = [] figures_of_merit_list = [] point_index = 0 while point_index < total_points: if not use_prior_medians: unit_parameter_list = model.random_unit_vector_within_limits( lower_limit=self.lower_limit, upper_limit=self.upper_limit) try: parameter_list = model.vector_from_unit_vector( unit_vector=unit_parameter_list) except exc.PriorLimitException: continue else: unit_parameter_list = [0.5] * model.prior_count parameter_list = model.vector_from_unit_vector( unit_vector=unit_parameter_list) try: figure_of_merit = fitness_function.figure_of_merit_from( parameter_list=parameter_list) if np.isnan(figure_of_merit): raise exc.FitException unit_parameter_lists.append(unit_parameter_list) parameter_lists.append(parameter_list) figures_of_merit_list.append(figure_of_merit) point_index += 1 except exc.FitException: pass return unit_parameter_lists, parameter_lists, figures_of_merit_list
def append(self, item): setattr(self, str(self.item_number), AbstractPriorModel.from_object(item)) self.item_number += 1
def as_model(self, model_classes=tuple()): from autofit.mapper.prior_model.abstract import AbstractPriorModel return AbstractPriorModel.from_instance(self, model_classes)
def add_dict_items(self, item_dict): for key, value in item_dict.items(): setattr(self, key, AbstractPriorModel.from_object(value))