diff --git a/AGENTS.md b/AGENTS.md index fb1505f2a..8890377c7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,7 +24,7 @@ NUMBA_CACHE_DIR=/tmp/numba_cache MPLCONFIGDIR=/tmp/matplotlib python -m pytest t ## Key Architecture -- **Non-linear searches** (`non_linear/search/`): MCMC (emcee), nested sampling (dynesty, nautilus), MLE (LBFGS, pyswarms) +- **Non-linear searches** (`non_linear/search/`): MCMC (emcee), nested sampling (dynesty, nautilus), MLE (LBFGS, BFGS, drawer) - **Model composition** (`mapper/`): `af.Model`, `af.Collection`, prior distributions - **Analysis** (`non_linear/analysis/`): base `af.Analysis` class with `log_likelihood_function` - **Aggregator** (`aggregator/`): results aggregation across runs diff --git a/CLAUDE.md b/CLAUDE.md index 4114cd7d6..e16c31da2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -18,8 +18,8 @@ Shared utilities (e.g. `test_mode`, `jax_wrapper`) belong in autoconf. - `autofit/` - Main package - `non_linear/` - Non-linear search algorithms - `search/mcmc/` - MCMC (emcee, zeus) - - `search/mle/` - Maximum likelihood (LBFGS, pyswarms) - - `search/nest/` - Nested sampling (dynesty, nautilus, ultranest) + - `search/mle/` - Maximum likelihood (LBFGS, BFGS, drawer) + - `search/nest/` - Nested sampling (dynesty, nautilus) - `samples/` - Posterior samples handling - `paths/` - Output path management - `analysis/` - Analysis base classes @@ -39,11 +39,10 @@ Shared utilities (e.g. `test_mode`, `jax_wrapper`) belong in autoconf. - `dynesty==2.1.4` - Nested sampling - `emcee>=3.1.6` - MCMC -- `pyswarms==1.3.0` - Particle swarm optimisation - `scipy<=1.14.0` - Optimisation - `SQLAlchemy==2.0.32` - Database backend - `anesthetic==2.8.14` - Posterior analysis/plotting -- Optional: `nautilus-sampler`, `ultranest`, `zeus-mcmc`, `getdist` +- Optional: `nautilus-sampler`, `zeus-mcmc`, `getdist` ## Running Tests diff --git a/autofit/__init__.py b/autofit/__init__.py index f4988acdb..54b5e2d55 100644 --- a/autofit/__init__.py +++ b/autofit/__init__.py @@ -82,12 +82,9 @@ from .non_linear.search.nest.nautilus.search import Nautilus from .non_linear.search.nest.dynesty.search.dynamic import DynestyDynamic from .non_linear.search.nest.dynesty.search.static import DynestyStatic -from .non_linear.search.nest.ultranest.search import UltraNest from .non_linear.search.mle.drawer.search import Drawer from .non_linear.search.mle.bfgs.search import BFGS from .non_linear.search.mle.bfgs.search import LBFGS -from .non_linear.search.mle.pyswarms.search.globe import PySwarmsGlobal -from .non_linear.search.mle.pyswarms.search.local import PySwarmsLocal from .non_linear.paths.abstract import AbstractPaths from .non_linear.paths import DirectoryPaths from .non_linear.paths import DatabasePaths diff --git a/autofit/config/non_linear/README.rst b/autofit/config/non_linear/README.rst index 9432dd852..23ec96cd2 100644 --- a/autofit/config/non_linear/README.rst +++ b/autofit/config/non_linear/README.rst @@ -6,4 +6,4 @@ Files - ``mcmc.yaml``: Settings default behaviour of MCMC non-linear searches (e.g. Emcee). - ``nest.yaml``: Settings default behaviour of nested sampler non-linear searches (e.g. Dynesty). -- ``mle.yaml``: Settings default behaviour of maximum likelihood estimator (mle) searches (e.g. PySwarms). \ No newline at end of file +- ``mle.yaml``: Settings default behaviour of maximum likelihood estimator (mle) searches (e.g. BFGS). \ No newline at end of file diff --git a/autofit/config/non_linear/mle.yaml b/autofit/config/non_linear/mle.yaml index f7f673da3..0d1c4c08b 100644 --- a/autofit/config/non_linear/mle.yaml +++ b/autofit/config/non_linear/mle.yaml @@ -2,53 +2,9 @@ # **PyAutoFit** supports the following maximum likelihood estimator (MLE) algorithms: -# - PySwarms: https://github.com/ljvmiranda921/pyswarms / https://pyswarms.readthedocs.io/en/latest/index.html - -# Settings in the [search], [run] and [options] entries are specific to each nested algorithm and should be +# Settings in the [search], [run] and [options] entries are specific to each algorithm and should be # determined by consulting that method's own readthedocs. -PySwarmsGlobal: - run: - iters: 2000 - search: - cognitive: 0.5 - ftol: -.inf - inertia: 0.9 - n_particles: 50 - social: 0.3 - initialize: # The method used to generate where walkers are initialized in parameter space {prior | ball}. - method: ball # priors: samples are initialized by randomly drawing from each parameter's prior. ball: samples are initialized by randomly drawing unit values from a narrow uniform distribution. - ball_lower_limit: 0.49 # The lower limit of the uniform distribution unit values are drawn from when initializing walkers using the ball method. - ball_upper_limit: 0.51 # The upper limit of the uniform distribution unit values are drawn from when initializing walkers using the ball method. - parallel: - number_of_cores: 1 # The number of cores the search is parallelized over by default, using Python multiprocessing. - printing: - silence: false # If True, the default print output of the non-linear search is silcened and not printed by the Python interpreter. - iterations_per_full_update: 500 # Non-linear search iterations between every full update, which outputs all visuals and result fits (e.g. model.result, search.summary), this exits the search and can be slow. - iterations_per_quick_update: 500 # Non-linear search iterations between every quick update, which just displays the maximum likelihood model fit. - remove_state_files_at_end: true # Whether to remove the savestate of the seach (e.g. the Emcee hdf5 file) at the end to save hard-disk space (results are still stored as PyAutoFit pickles and loadable). -PySwarmsLocal: - run: - iters: 2000 - search: - cognitive: 0.5 - ftol: -.inf - inertia: 0.9 - minkowski_p_norm: 2 - n_particles: 50 - number_of_k_neighbors: 3 - social: 0.3 - initialize: # The method used to generate where walkers are initialized in parameter space {prior | ball}. - method: ball # priors: samples are initialized by randomly drawing from each parameter's prior. ball: samples are initialized by randomly drawing unit values from a narrow uniform distribution. - ball_lower_limit: 0.49 # The lower limit of the uniform distribution unit values are drawn from when initializing walkers using the ball method. - ball_upper_limit: 0.51 # The upper limit of the uniform distribution unit values are drawn from when initializing walkers using the ball method. - parallel: - number_of_cores: 1 # The number of cores the search is parallelized over by default, using Python multiprocessing. - printing: - silence: false # If True, the default print output of the non-linear search is silcened and not printed by the Python interpreter. - iterations_per_full_update: 500 # Non-linear search iterations between every full update, which outputs all visuals and result fits (e.g. model.result, search.summary), this exits the search and can be slow. - iterations_per_quick_update: 500 # Non-linear search iterations between every quick update, which just displays the maximum likelihood model fit. - remove_state_files_at_end: true # Whether to remove the savestate of the seach (e.g. the Emcee hdf5 file) at the end to save hard-disk space (results are still stored as PyAutoFit pickles and loadable). BFGS: search: tol: null diff --git a/autofit/config/non_linear/nest.yaml b/autofit/config/non_linear/nest.yaml index 0e03006f8..b59cbf486 100644 --- a/autofit/config/non_linear/nest.yaml +++ b/autofit/config/non_linear/nest.yaml @@ -2,7 +2,6 @@ # - Dynesty: https://github.com/joshspeagle/dynesty / https://dynesty.readthedocs.io/en/latest/index.html # - Nautilus https://https://github.com/johannesulf/nautilus / https://nautilus-sampler.readthedocs.io/en/stable/index.html -# - UltraNest: https://github.com/JohannesBuchner/UltraNest / https://johannesbuchner.github.io/UltraNest/readme.html # Settings in the [search] and [run] entries are specific to each nested algorithm and should be determined by # consulting that MCMC method's own readthedocs. @@ -87,47 +86,3 @@ Nautilus: force_x1_cpu: false # Force Dynesty to not use Python multiprocessing Pool, which can fix issues on certain operating systems. printing: silence: false # If True, the default print output of the non-linear search is silenced and not printed by the Python interpreter. -UltraNest: - search: - draw_multiple: true - ndraw_max: 65536 - ndraw_min: 128 - num_bootstraps: 30 - num_test_samples: 2 - resume: true - run_num: null - storage_backend: hdf5 - vectorized: false - warmstart_max_tau: -1.0 - run: - cluster_num_live_points: 40 - dkl: 0.5 - dlogz: 0.5 - frac_remain: 0.01 - insertion_test_window: 10 - insertion_test_zscore_threshold: 2 - lepsilon: 0.001 - log_interval: null - max_iters: null - max_ncalls: null - max_num_improvement_loops: -1.0 - min_ess: 400 - min_num_live_points: 400 - show_status: true - update_interval_ncall: null - update_interval_volume_fraction: 0.8 - viz_callback: auto - stepsampler: - adaptive_nsteps: false - log: false - max_nsteps: 1000 - nsteps: 25 - region_filter: false - scale: 1.0 - stepsampler_cls: null - initialize: # The method used to generate where walkers are initialized in parameter space {prior}. - method: prior # priors: samples are initialized by randomly drawing from each parameter's prior. - parallel: - number_of_cores: 1 # The number of cores the search is parallelized over by default, using Python multiprocessing. - printing: - silence: false # If True, the default print output of the non-linear search is silenced and not printed by the Python interpreter. diff --git a/autofit/non_linear/samples/samples.py b/autofit/non_linear/samples/samples.py index d022d8563..876589647 100644 --- a/autofit/non_linear/samples/samples.py +++ b/autofit/non_linear/samples/samples.py @@ -39,7 +39,7 @@ def __init__( individual sample by the `NonLinearSearch` and return information on the likelihoods, errors, etc. This class stores samples of searches which provide maximum likelihood estimates of the model-fit (e.g. - PySwarms, LBFGS). + LBFGS). Parameters ---------- diff --git a/autofit/non_linear/search/mle/pyswarms/__init__.py b/autofit/non_linear/search/mle/pyswarms/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/autofit/non_linear/search/mle/pyswarms/search/__init__.py b/autofit/non_linear/search/mle/pyswarms/search/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/autofit/non_linear/search/mle/pyswarms/search/abstract.py b/autofit/non_linear/search/mle/pyswarms/search/abstract.py deleted file mode 100644 index c92b5d044..000000000 --- a/autofit/non_linear/search/mle/pyswarms/search/abstract.py +++ /dev/null @@ -1,323 +0,0 @@ -from typing import Dict, Optional - -import numpy as np - -from autofit import exc -from autofit.database.sqlalchemy_ import sa -from autofit.mapper.prior_model.abstract import AbstractPriorModel -from autofit.non_linear.fitness import Fitness -from autofit.non_linear.initializer import AbstractInitializer -from autofit.non_linear.search.mle.abstract_mle import AbstractMLE -from autofit.non_linear.samples.sample import Sample -from autofit.non_linear.samples.samples import Samples - - -class FitnessPySwarms(Fitness): - def __call__(self, parameters, *kwargs): - """ - Interfaces with any non-linear in order to fit a model to the data and return a log likelihood via - an `Analysis` class. - - The interface is described in full in the `__init__` docstring. - - `PySwarms` have a unique interface in that lists of parameters corresponding to multiple particles are - passed to the fitness function. A bespoke `__call__` method is therefore required to handle this. - - The figure of merit is the log posterior multiplied by -2.0, which is the chi-squared value that is minimized - by the `PySwarms` non-linear search. - - Parameters - ---------- - parameters - The parameters (typically a list) chosen by a non-linear search, which are mapped to an instance of the - model via its priors and fitted to the data. - kwargs - Addition key-word arguments that may be necessary for specific non-linear searches. - - Returns - ------- - The figure of merit returned to the non-linear search, which is either the log likelihood or log posterior. - """ - - if isinstance(parameters[0], float): - parameters = [parameters] - - figure_of_merit_list = [] - - for params_of_particle in parameters: - try: - instance = self.model.instance_from_vector(vector=params_of_particle) - log_likelihood = self.analysis.log_likelihood_function( - instance=instance - ) - log_prior = self.model.log_prior_list_from_vector( - vector=params_of_particle - ) - log_posterior = log_likelihood + sum(log_prior) - figure_of_merit = -2.0 * log_posterior - except exc.FitException: - figure_of_merit = np.nan - - if np.isnan(figure_of_merit): - figure_of_merit = -2.0 * self.resample_figure_of_merit - - figure_of_merit_list.append(figure_of_merit) - - return np.asarray(figure_of_merit_list) - - -class AbstractPySwarms(AbstractMLE): - def __init__( - self, - name: Optional[str] = None, - path_prefix: Optional[str] = None, - unique_tag: Optional[str] = None, - initializer: Optional[AbstractInitializer] = None, - iterations_per_quick_update: int = None, - iterations_per_full_update: int = None, - number_of_cores: int = None, - session: Optional[sa.orm.Session] = None, - **kwargs - ): - """ - A PySwarms Particle Swarm MLE global non-linear search. - - For a full description of PySwarms, checkout its Github and readthedocs webpages: - - https://github.com/ljvmiranda921/pyswarms - https://pyswarms.readthedocs.io/en/latest/index.html - - Parameters - ---------- - name - The name of the search, controlling the last folder results are output. - path_prefix - The path of folders prefixing the name folder where results are output. - unique_tag - The name of a unique tag for this model-fit, which will be given a unique entry in the sqlite database - and also acts as the folder after the path prefix and before the search name. - initializer - Generates the initialize samples of non-linear parameter space (see autofit.non_linear.initializer). - number_of_cores - The number of cores sampling is performed using a Python multiprocessing Pool instance. - session - An SQLalchemy session instance so the results of the model-fit are written to an SQLite database. - """ - - number_of_cores = ( - self._config("parallel", "number_of_cores") - if number_of_cores is None - else number_of_cores - ) - - super().__init__( - name=name, - path_prefix=path_prefix, - unique_tag=unique_tag, - initializer=initializer, - iterations_per_quick_update=iterations_per_quick_update, - iterations_per_full_update=iterations_per_full_update, - number_of_cores=number_of_cores, - session=session, - **kwargs - ) - - self.logger.debug("Creating PySwarms Search") - - def _fit(self, model: AbstractPriorModel, analysis): - """ - 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 = FitnessPySwarms( - model=model, - analysis=analysis, - fom_is_log_likelihood=False, - resample_figure_of_merit=-np.inf, - convert_to_chi_squared=True, - ) - - try: - search_internal = self.paths.load_search_internal() - - init_pos = search_internal.pos_history[-1] - total_iterations = len(search_internal.cost_history) - - self.logger.info( - "Resuming PySwarms non-linear search (previous samples found)." - ) - - except (FileNotFoundError, TypeError, AttributeError): - ( - unit_parameter_lists, - parameter_lists, - log_posterior_list, - ) = self.initializer.samples_from_model( - total_points=self.config_dict_search["n_particles"], - model=model, - fitness=fitness, - paths=self.paths, - n_cores=self.number_of_cores, - ) - - 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( - "Starting new PySwarms non-linear search (no previous samples found)." - ) - - ## TODO : Use actual limits - - vector_lower = model.vector_from_unit_vector( - unit_vector=[1e-6] * model.prior_count, - ) - vector_upper = model.vector_from_unit_vector( - unit_vector=[0.9999999] * model.prior_count, - ) - - 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)) - - while total_iterations < self.config_dict_run["iters"]: - search_internal = self.search_internal_from( - model=model, fitness=fitness, bounds=bounds, init_pos=init_pos - ) - - iterations_remaining = self.config_dict_run["iters"] - total_iterations - - iterations = min(self.iterations_per_full_update, iterations_remaining) - - if iterations > 0: - search_internal.optimize( - objective_func=fitness.call_wrap, iters=int(iterations) - ) - - total_iterations += iterations - - # TODO : Running PySwarms in NoteBook raises - # TODO: TypeError: cannot pickle '_hashlib.HMAC' object - - self.output_search_internal(search_internal=search_internal) - - self.perform_update( - model=model, - analysis=analysis, - during_analysis=True, - fitness=fitness, - search_internal=search_internal, - ) - - init_pos = search_internal.pos_history[-1] - - return search_internal, fitness - - def output_search_internal(self, search_internal): - try: - self.paths.save_search_internal( - obj=search_internal, - ) - except TypeError: - pass - - def samples_via_internal_from(self, model, search_internal=None): - """ - Returns a `Samples` object from the pyswarms internal results. - - The samples contain all information on the parameter space sampling (e.g. the parameters, - log likelihoods, etc.). - - The internal search results are converted from the native format used by the search to lists of values - (e.g. `parameter_lists`, `log_likelihood_list`). - - Parameters - ---------- - model - Maps input vectors of unit parameter values to physical values and model instances via priors. - """ - - if search_internal is None: - search_internal = self.paths.load_search_internal() - - search_internal_dict = { - "total_iterations": None, - "log_posterior_list": [ - -0.5 * cost for cost in search_internal.cost_history - ], - "time": self.timer.time if self.timer else None, - } - pos_history = search_internal.pos_history - - parameter_lists = [ - param.tolist() for parameters in pos_history for param in parameters - ] - parameter_lists_2 = [parameters.tolist()[0] for parameters in pos_history] - - log_posterior_list = search_internal_dict["log_posterior_list"] - 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=parameter_lists_2, - log_likelihood_list=log_likelihood_list, - log_prior_list=log_prior_list, - weight_list=weight_list, - ) - - return Samples( - model=model, - sample_list=sample_list, - samples_info=search_internal_dict, - ) - - def config_dict_test_mode_from(self, config_dict: Dict) -> Dict: - """ - Returns a configuration dictionary for test mode meaning that the sampler terminates as quickly as possible. - - Entries which set the total number of samples of the sampler (e.g. maximum calls, maximum likelihood - evaluations) are reduced to low values meaning it terminates nearly immediately. - - Parameters - ---------- - config_dict - The original configuration dictionary for this sampler which includes entries controlling how fast the - sampler terminates. - - Returns - ------- - A configuration dictionary where settings which control the sampler's number of samples are reduced so it - terminates as quickly as possible. - """ - return { - **config_dict, - "iters": 1, - } - - def search_internal_from(self, model, fitness, bounds, init_pos): - raise NotImplementedError() diff --git a/autofit/non_linear/search/mle/pyswarms/search/globe.py b/autofit/non_linear/search/mle/pyswarms/search/globe.py deleted file mode 100644 index a4d440ea2..000000000 --- a/autofit/non_linear/search/mle/pyswarms/search/globe.py +++ /dev/null @@ -1,87 +0,0 @@ -from typing import Optional - -from autofit.database.sqlalchemy_ import sa -from autofit.non_linear.initializer import AbstractInitializer -from autofit.non_linear.search.mle.pyswarms.search.abstract import AbstractPySwarms - - -class PySwarmsGlobal(AbstractPySwarms): - __identifier_fields__ = ( - "n_particles", - "cognitive", - "social", - "inertia", - ) - - def __init__( - self, - name: Optional[str] = None, - path_prefix: Optional[str] = None, - unique_tag: Optional[str] = None, - initializer: Optional[AbstractInitializer] = None, - iterations_per_full_update: int = None, - iterations_per_quick_update: int = None, - number_of_cores: int = None, - session: Optional[sa.orm.Session] = None, - **kwargs - ): - """ - A PySwarms Particle Swarm MLE global non-linear search. - - For a full description of PySwarms, checkout its Github and readthedocs webpages: - - https://github.com/ljvmiranda921/pyswarms - - https://pyswarms.readthedocs.io/en/latest/index.html - - Parameters - ---------- - name - The name of the search, controlling the last folder results are output. - path_prefix - The path of folders prefixing the name folder where results are output. - unique_tag - The name of a unique tag for this model-fit, which will be given a unique entry in the sqlite database - and also acts as the folder after the path prefix and before the search name. - initializer - Generates the initialize samples of non-linear parameter space (see autofit.non_linear.initializer). - number_of_cores - The number of cores sampling is performed using a Python multiprocessing Pool instance. - """ - - super().__init__( - name=name, - path_prefix=path_prefix, - unique_tag=unique_tag, - initializer=initializer, - iterations_per_quick_update=iterations_per_quick_update, - iterations_per_full_update=iterations_per_full_update, - number_of_cores=number_of_cores, - session=session, - **kwargs - ) - - self.logger.debug("Creating PySwarms Search") - - def search_internal_from(self, model, fitness, bounds, init_pos): - """Get the static Dynesty sampler which performs the non-linear search, passing it all associated input Dynesty - variables.""" - - import pyswarms - - options = { - "c1": self.config_dict_search["cognitive"], - "c2": self.config_dict_search["social"], - "w": self.config_dict_search["inertia"] - } - - filter_list = ["cognitive", "social", "inertia"] - config_dict = {key: value for key, value in self.config_dict_search.items() if key not in filter_list} - - return pyswarms.global_best.GlobalBestPSO( - dimensions=model.prior_count, - bounds=bounds, - init_pos=init_pos, - options=options, - **config_dict - ) \ No newline at end of file diff --git a/autofit/non_linear/search/mle/pyswarms/search/local.py b/autofit/non_linear/search/mle/pyswarms/search/local.py deleted file mode 100644 index 46c0759aa..000000000 --- a/autofit/non_linear/search/mle/pyswarms/search/local.py +++ /dev/null @@ -1,90 +0,0 @@ -from typing import Optional - -from autofit.database.sqlalchemy_ import sa -from autofit.non_linear.search.mle.pyswarms.search.abstract import AbstractPySwarms - - -class PySwarmsLocal(AbstractPySwarms): - __identifier_fields__ = ( - "n_particles", - "cognitive", - "social", - "inertia", - "number_of_k_neighbors", - "minkowski_p_norm" - ) - - def __init__( - self, - name: Optional[str] = None, - path_prefix: Optional[str] = None, - unique_tag: Optional[str] = None, - iterations_per_quick_update: int = None, - iterations_per_full_update: int = None, - number_of_cores: int = None, - session: Optional[sa.orm.Session] = None, - **kwargs - ): - """ - A PySwarms Particle Swarm MLE global non-linear search. - - For a full description of PySwarms, checkout its Github and readthedocs webpages: - - https://github.com/ljvmiranda921/pyswarms - - https://pyswarms.readthedocs.io/en/latest/index.html - - Parameters - ---------- - name - The name of the search, controlling the last folder results are output. - path_prefix - The path of folders prefixing the name folder where results are output. - unique_tag - The name of a unique tag for this model-fit, which will be given a unique entry in the sqlite database - and also acts as the folder after the path prefix and before the search name. - initializer - Generates the initialize samples of non-linear parameter space (see autofit.non_linear.initializer). - number_of_cores - The number of cores sampling is performed using a Python multiprocessing Pool instance. - """ - - super().__init__( - name=name, - path_prefix=path_prefix, - unique_tag=unique_tag, - iterations_per_quick_update=iterations_per_quick_update, - iterations_per_full_update=iterations_per_full_update, - number_of_cores=number_of_cores, - session=session, - **kwargs - ) - - self.logger.debug("Creating PySwarms Search") - - def search_internal_from(self, model, fitness, bounds, init_pos): - """ - Get the static Dynesty sampler which performs the non-linear search, passing it all associated input Dynesty - variables. - """ - - import pyswarms - - options = { - "c1": self.config_dict_search["cognitive"], - "c2": self.config_dict_search["social"], - "w": self.config_dict_search["inertia"], - "k": self.config_dict_search["number_of_k_neighbors"], - "p": self.config_dict_search["minkowski_p_norm"], - } - - filter_list = ["cognitive", "social", "inertia", "number_of_k_neighbors", "minkowski_p_norm"] - config_dict = {key: value for key, value in self.config_dict_search.items() if key not in filter_list} - - return pyswarms.local_best.LocalBestPSO( - dimensions=model.prior_count, - bounds=bounds, - init_pos=init_pos, - options=options, - **config_dict - ) \ No newline at end of file diff --git a/autofit/non_linear/search/nest/nautilus/search.py b/autofit/non_linear/search/nest/nautilus/search.py index b70b3778d..bec9472fa 100644 --- a/autofit/non_linear/search/nest/nautilus/search.py +++ b/autofit/non_linear/search/nest/nautilus/search.py @@ -47,8 +47,8 @@ def __init__( """ A Nautilus non-linear search. - Nautilus is an optional requirement and must be installed manually via the command `pip install ultranest`. - It is optional as it has certain dependencies which are generally straight forward to install (e.g. Cython). + Nautilus is an optional requirement and must be installed manually via the command `pip install nautilus-sampler`. + It is optional as it has certain dependencies which are generally straight forward to install. For a full description of Nautilus checkout its Github and documentation webpages: @@ -451,7 +451,7 @@ def samples_via_internal_from( self, model: AbstractPriorModel, search_internal=None ): """ - Returns a `Samples` object from the ultranest internal results. + Returns a `Samples` object from the nautilus internal results. The samples contain all information on the parameter space sampling (e.g. the parameters, log likelihoods, etc.). diff --git a/autofit/non_linear/search/nest/ultranest/__init__.py b/autofit/non_linear/search/nest/ultranest/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/autofit/non_linear/search/nest/ultranest/search.py b/autofit/non_linear/search/nest/ultranest/search.py deleted file mode 100644 index ee734156e..000000000 --- a/autofit/non_linear/search/nest/ultranest/search.py +++ /dev/null @@ -1,340 +0,0 @@ -import os -from typing import Dict, Optional - -from autofit.database.sqlalchemy_ import sa - -from autofit.mapper.prior_model.abstract import AbstractPriorModel -from autofit.non_linear.search.nest import abstract_nest -from autofit.non_linear.fitness import Fitness -from autofit.non_linear.samples.sample import Sample -from autofit.non_linear.samples.nest import SamplesNest - -class UltraNest(abstract_nest.AbstractNest): - __identifier_fields__ = ( - "draw_multiple", - "ndraw_min", - "ndraw_max", - "min_num_live_points", - "cluster_num_live_points", - "insertion_test_zscore_threshold", - "stepsampler_cls", - "nsteps" - ) - - def __init__( - self, - name: Optional[str] = None, - path_prefix: Optional[str] = None, - unique_tag: Optional[str] = None, - iterations_per_quick_update: int = None, - iterations_per_full_update: int = None, - number_of_cores: int = None, - session: Optional[sa.orm.Session] = None, - **kwargs - ): - """ - An UltraNest non-linear search. - - UltraNest is an optional requirement and must be installed manually via the command `pip install ultranest`. - It is optional as it has certain dependencies which are generally straight forward to install (e.g. Cython). - - For a full description of UltraNest and its Python wrapper PyUltraNest, checkout its Github and documentation - webpages: - - https://github.com/JohannesBuchner/UltraNest - https://johannesbuchner.github.io/UltraNest/readme.html - - Parameters - ---------- - name - The name of the search, controlling the last folder results are output. - path_prefix - The path of folders prefixing the name folder where results are output. - unique_tag - The name of a unique tag for this model-fit, which will be given a unique entry in the sqlite database - and also acts as the folder after the path prefix and before the search name. - iterations_per_full_update - The number of iterations performed between update (e.g. output latest model to hard-disk, visualization). - number_of_cores - The number of cores sampling is performed using a Python multiprocessing Pool instance. - session - An SQLalchemy session instance so the results of the model-fit are written to an SQLite database. - """ - - number_of_cores = ( - self._config("parallel", "number_of_cores") - if number_of_cores is None - else number_of_cores - ) - - super().__init__( - name=name, - path_prefix=path_prefix, - unique_tag=unique_tag, - iterations_per_quick_update=iterations_per_quick_update, - iterations_per_full_update=iterations_per_full_update, - number_of_cores=number_of_cores, - session=session, - **kwargs - ) - - for key, value in self.config_dict_stepsampler.items(): - setattr(self, key, value) - if self.config_dict_stepsampler["stepsampler_cls"] is None: - self.nsteps = None - - self.logger.debug("Creating UltraNest Search") - - def _fit(self, model: AbstractPriorModel, analysis): - """ - Fit a model using the search 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 includes the maximum log likelihood instance and full - set of accepted ssamples of the fit. - """ - - try: - import ultranest - except ModuleNotFoundError: - raise ModuleNotFoundError( - "\n--------------------\n" - "You are attempting to perform a model-fit using UltraNest. \n\n" - "However, the optional library UltraNest (https://johannesbuchner.github.io/UltraNest/index.html) is " - "not installed.\n\n" - "Install it via the command `pip install ultranest==3.6.2`.\n\n" - "----------------------" - ) - - fitness = Fitness( - model=model, - analysis=analysis, - paths=self.paths, - fom_is_log_likelihood=True, - resample_figure_of_merit=-1.0e99 - ) - - def prior_transform(cube): - return model.vector_from_unit_vector( - unit_vector=cube, - ) - - log_dir = self.paths.search_internal_path - - try: - checkpoint_exists = os.path.exists(log_dir / "chains") - except TypeError: - checkpoint_exists = False - - if checkpoint_exists: - self.logger.info( - "Resuming UltraNest non-linear search (previous samples found)." - ) - else: - self.logger.info( - "Starting new UltraNest non-linear search (no previous samples found)." - ) - - search_internal = ultranest.ReactiveNestedSampler( - param_names=model.parameter_names, - loglike=fitness.call_wrap, - transform=prior_transform, - log_dir=log_dir, - **self.config_dict_search - ) - - search_internal.stepsampler = self.stepsampler - - finished = False - - while not finished: - - try: - total_iterations = search_internal.ncall - except AttributeError: - total_iterations = 0 - - if self.config_dict_run["max_ncalls"] is not None: - iterations = self.config_dict_run["max_ncalls"] - else: - iterations = total_iterations + self.iterations_per_full_update - - if iterations > 0: - - filter_list = ["max_ncalls", "dkl", "lepsilon"] - config_dict_run = { - key: value for key, value - in self.config_dict_run.items() - if key - not in filter_list - } - - config_dict_run["update_interval_ncall"] = iterations - - search_internal.run( - max_ncalls=iterations, - **config_dict_run - ) - - self.paths.save_search_internal( - obj=search_internal.results, - ) - - iterations_after_run = search_internal.ncall - - if ( - total_iterations == iterations_after_run - or iterations_after_run == self.config_dict_run["max_ncalls"] - ): - finished = True - - if not finished: - - self.perform_update( - model=model, - analysis=analysis, - during_analysis=True, - fitness=fitness, - search_internal=search_internal - ) - - return search_internal, fitness - - def output_search_internal(self, search_internal): - """ - Output the sampler results to hard-disk in their internal format. - - UltraNest uses a backend to store and load results, therefore the outputting of the search internal to a - dill file is disabled. - - However, a dictionary of the search results is output to dill above. - - Parameters - ---------- - sampler - The nautilus sampler object containing the results of the model-fit. - """ - pass - - def samples_info_from(self, search_internal=None): - - search_internal = search_internal or self.paths.load_search_internal() - - return { - "log_evidence": search_internal["logz"], - "total_samples": search_internal["ncall"], - "total_accepted_samples": len(search_internal["weighted_samples"]["logl"]), - "time": self.timer.time if self.timer else None, - "number_live_points": self.config_dict_run["min_num_live_points"] - } - - def samples_via_internal_from(self, model: AbstractPriorModel, search_internal=None): - """ - Returns a `Samples` object from the ultranest internal results. - - The samples contain all information on the parameter space sampling (e.g. the parameters, - log likelihoods, etc.). - - The internal search results are converted from the native format used by the search to lists of values - (e.g. `parameter_lists`, `log_likelihood_list`). - - Parameters - ---------- - model - Maps input vectors of unit parameter values to physical values and model instances via priors. - """ - - search_internal = search_internal.results or self.paths.load_search_internal() - - parameters = search_internal["weighted_samples"]["points"] - log_likelihood_list = search_internal["weighted_samples"]["logl"] - log_prior_list = [ - sum(model.log_prior_list_from_vector(vector=vector)) for vector in parameters - ] - weight_list = search_internal["weighted_samples"]["weights"] - - sample_list = Sample.from_lists( - model=model, - parameter_lists=parameters, - log_likelihood_list=log_likelihood_list, - log_prior_list=log_prior_list, - weight_list=weight_list - ) - - return SamplesNest( - model=model, - sample_list=sample_list, - samples_info=self.samples_info_from(search_internal=search_internal), - ) - - def config_dict_test_mode_from(self, config_dict: Dict) -> Dict: - """ - Returns a configuration dictionary for test mode meaning that the sampler terminates as quickly as possible. - - Entries which set the total number of samples of the sampler (e.g. maximum calls, maximum likelihood - evaluations) are reduced to low values meaning it terminates nearly immediately. - - Parameters - ---------- - config_dict - The original configuration dictionary for this sampler which includes entries controlling how fast the - sampler terminates. - - Returns - ------- - A configuration dictionary where settings which control the sampler's number of samples are reduced so it - terminates as quickly as possible. - """ - return { - **config_dict, - "max_iters": 1, - "max_ncalls": 1, - } - - @property - def config_dict_stepsampler(self): - - config_dict = {} - - config_dict_step = self.config_type[self.__class__.__name__]["stepsampler"] - - for key, value in config_dict_step.items(): - try: - config_dict[key] = self.kwargs[key] - except KeyError: - config_dict[key] = value - - return config_dict - - @property - def stepsampler(self): - - from ultranest import stepsampler - - config_dict_stepsampler = self.config_dict_stepsampler - stepsampler_cls = config_dict_stepsampler["stepsampler_cls"] - config_dict_stepsampler.pop("stepsampler_cls") - - if stepsampler_cls is None: - return None - elif stepsampler_cls == "RegionMHSampler": - return stepsampler.RegionMHSampler(**config_dict_stepsampler) - elif stepsampler_cls == "AHARMSampler": - config_dict_stepsampler.pop("scale") - return stepsampler.AHARMSampler(**config_dict_stepsampler) - elif stepsampler_cls == "CubeMHSampler": - return stepsampler.CubeMHSampler(**config_dict_stepsampler) - elif stepsampler_cls == "CubeSliceSampler": - return stepsampler.CubeSliceSampler(**config_dict_stepsampler) - elif stepsampler_cls == "RegionSliceSampler": - return stepsampler.RegionSliceSampler(**config_dict_stepsampler) \ No newline at end of file diff --git a/docs/api/searches.rst b/docs/api/searches.rst index 6d070bf86..90c1c7121 100644 --- a/docs/api/searches.rst +++ b/docs/api/searches.rst @@ -26,7 +26,6 @@ Nested Samplers DynestyDynamic DynestyStatic - UltraNest MCMC ---- @@ -53,8 +52,6 @@ Maximum Likelihood Estimators BFGS LBFGS - PySwarmsLocal - PySwarmsGlobal There are also a number of tools which are used to customize the behaviour of non-linear searches in **PyAutoFit**, including directory output structure, parameter sample initialization and MCMC auto correlation analysis. diff --git a/docs/cookbooks/search.rst b/docs/cookbooks/search.rst index 141aa23ab..e557b1f20 100644 --- a/docs/cookbooks/search.rst +++ b/docs/cookbooks/search.rst @@ -24,9 +24,6 @@ It then provides example code for using every search: - **Zeus (MCMC)**: The Zeus ensemble sampler MCMC. - **DynestyDynamic (Nested Sampling)**: The Dynesty dynamic nested sampler. - **DynestyStatic (Nested Sampling)**: The Dynesty static nested sampler. -- **UltraNest (Nested Sampling)**: The UltraNest nested sampler. -- **PySwarmsGlobal (Particle Swarm Optimization)**: The global PySwarms particle swarm optimization -- **PySwarmsLocal (Particle Swarm Optimization)**: The local PySwarms particle swarm optimization. - **LBFGS**: The L-BFGS scipy optimization. Example Fit @@ -381,101 +378,6 @@ Information about Dynesty can be found at the following links: max_move=100, ) -UltraNest (Nested Sampling) ---------------------------- - -The UltraNest sampler is a Nested Sampling algorithm. It is a Python implementation of the -``Buchner ``_ algorithm. - -UltraNest is an optional requirement and must be installed manually via the command ``pip install ultranest``. -It is optional as it has certain dependencies which are generally straight forward to install (e.g. Cython). - -Information about UltraNest can be found at the following links: - -- https://github.com/JohannesBuchner/UltraNest -- https://johannesbuchner.github.io/UltraNest/readme.html - -.. code-block:: python - - search = af.UltraNest( - resume=True, - run_num=None, - num_test_samples=2, - draw_multiple=True, - num_bootstraps=30, - vectorized=False, - ndraw_min=128, - ndraw_max=65536, - storage_backend="hdf5", - warmstart_max_tau=-1, - update_interval_volume_fraction=0.8, - update_interval_ncall=None, - log_interval=None, - show_status=True, - viz_callback="auto", - dlogz=0.5, - dKL=0.5, - frac_remain=0.01, - Lepsilon=0.001, - min_ess=400, - max_iters=None, - max_ncalls=None, - max_num_improvement_loops=-1, - min_num_live_points=50, - cluster_num_live_points=40, - insertion_test_window=10, - insertion_test_zscore_threshold=2, - stepsampler_cls="RegionMHSampler", - nsteps=11, - ) - -PySwarmsGlobal --------------- - -The PySwarmsGlobal sampler is a Global Optimization algorithm. It is a Python implementation of the -``Bratley ``_ algorithm. - -Information about PySwarms can be found at the following links: - -- https://github.com/ljvmiranda921/pyswarms -- https://pyswarms.readthedocs.io/en/latest/index.html -- https://pyswarms.readthedocs.io/en/latest/api/pyswarms.single.html#module-pyswarms.single.global_best - -.. code-block:: python - - search = af.PySwarmsGlobal( - n_particles=50, - iters=1000, - cognitive=0.5, - social=0.3, - inertia=0.9, - ftol=-np.inf, - ) -PySwarmsLocal -------------- - -The PySwarmsLocal sampler is a Local Optimization algorithm. It is a Python implementation of the -``Bratley ``_ algorithm. - -Information about PySwarms can be found at the following links: - -- https://github.com/ljvmiranda921/pyswarms -- https://pyswarms.readthedocs.io/en/latest/index.html - - https://pyswarms.readthedocs.io/en/latest/api/pyswarms.single.html#module-pyswarms.single.global_best - -.. code-block:: python - - search = af.PySwarmsLocal( - n_particles=50, - iters=1000, - cognitive=0.5, - social=0.3, - inertia=0.9, - number_of_k_neighbors=3, - minkowski_p_norm=2, - ftol=-np.inf, - ) - LBFGS ----- diff --git a/docs/features/interpolate.rst b/docs/features/interpolate.rst index 62e237eb3..c412422b7 100644 --- a/docs/features/interpolate.rst +++ b/docs/features/interpolate.rst @@ -105,7 +105,7 @@ The interpolate at the end of the fits uses the maximum log likelihood model of __Search__ The model is fitted to the data using the nested sampling algorithm - Dynesty (https://johannesbuchner.github.io/UltraNest/readme.html). + Dynesty (https://dynesty.readthedocs.io/en/latest/). """ search = af.DynestyStatic( path_prefix=path.join("interpolate"), diff --git a/docs/installation/overview.rst b/docs/installation/overview.rst index ad3853de2..1e83199c0 100644 --- a/docs/installation/overview.rst +++ b/docs/installation/overview.rst @@ -34,8 +34,6 @@ Dependencies **emcee** https://github.com/dfm/emcee -**PySwarms** https://github.com/ljvmiranda921/pyswarms - **astropy** https://www.astropy.org/ **corner.py** https://github.com/dfm/corner.py diff --git a/docs/requirements.txt b/docs/requirements.txt index 10099ec48..7d86c6bb9 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -10,7 +10,7 @@ matplotlib networkx numpydoc>=1.0.0 pyprojroot==0.2.0 -pyswarms==1.3.0 + h5py>=2.10.0 SQLAlchemy==1.3.20 scipy>=1.5.1 diff --git a/files/citation.tex b/files/citation.tex index a3936681e..4f67ad8be 100644 --- a/files/citation.tex +++ b/files/citation.tex @@ -30,10 +30,6 @@ \section*{Software Citations} \href{https://github.com/numpy/numpy}{\textt{NumPy}} \citep{numpy} -\item -\href{https://github.com/ljvmiranda921/pyswarms}{\textt{PySwarms}} -\citep{pyswarms} - \item \href{https://www.python.org/}{\textt{Python}} \citep{python} @@ -46,10 +42,6 @@ \section*{Software Citations} \href{https://www.sqlite.org/index.html} \citep{sqlite} -\item -\href{https://github.com/JohannesBuchner/UltraNest}{\textt{UltraNest}} -\citep{ultranest} - \item \href{https://github.com/minaskar/zeus}{\textt{zeus}} \citep{zeus1, zeus2} diff --git a/files/citations.bib b/files/citations.bib index 84e01c822..9e4107837 100644 --- a/files/citations.bib +++ b/files/citations.bib @@ -130,16 +130,7 @@ @article{pyautofit title = {`PyAutoFit`: A Classy Probabilistic Programming Language for Model Composition and Fitting}, journal = {Journal of Open Source Software} } -@article{pyswarms, - author = {Lester James V. Miranda}, - title = "{P}y{S}warms, a research-toolkit for {P}article {S}warm {O}ptimization in {P}ython", - journal = {Journal of Open Source Software}, - year = {2018}, - volume = {3}, - issue = {21}, - doi = {10.21105/joss.00433}, - url = {https://doi.org/10.21105/joss.00433} -} + @book{python, author = {Van Rossum, Guido and Drake, Fred L.}, title = {Python 3 Reference Manual}, @@ -178,24 +169,7 @@ @misc{sqlite2020 year={2020}, author={Hipp, Richard D} } -@ARTICLE{ultranest, - author = {{Buchner}, Johannes}, - title = "{UltraNest - a robust, general purpose Bayesian inference engine}", - journal = {The Journal of Open Source Software}, - keywords = {C, Monte Carlo, Python, Nested Sampling, C++, Bayesian inference, Fortran, Bayes factors, Statistics - Computation, Astrophysics - Instrumentation and Methods for Astrophysics}, - year = 2021, - month = apr, - volume = {6}, - number = {60}, - eid = {3001}, - pages = {3001}, - doi = {10.21105/joss.03001}, -archivePrefix = {arXiv}, - eprint = {2101.09604}, - primaryClass = {stat.CO}, - adsurl = {https://ui.adsabs.harvard.edu/abs/2021JOSS....6.3001B}, - adsnote = {Provided by the SAO/NASA Astrophysics Data System} -} + @article{zeus1, title={zeus: A Python Implementation of the Ensemble Slice Sampling method}, author={Minas Karamanis and Florian Beutler}, diff --git a/files/citations.md b/files/citations.md index b78d136ea..6bb86280c 100644 --- a/files/citations.md +++ b/files/citations.md @@ -15,9 +15,9 @@ This work uses the following software packages: - `NumPy` https://github.com/numpy/numpy [@numpy] - `PyAutoFit` https://github.com/rhayes777/PyAutoFit [@pyautofit] - `PyMultiNest` https://github.com/JohannesBuchner/PyMultiNest [@multinest] [@pymultinest] -- `PySwarms` https://github.com/ljvmiranda921/pyswarms [@pyswarms] + - `Python` https://www.python.org/ [@python] - `Scipy` https://github.com/scipy/scipy [@scipy] - `SQLite` https://www.sqlite.org/index.html [@sqlite] -- `UltraNest` https://github.com/JohannesBuchner/UltraNest [@ultranest] + - `Zeus` https://github.com/minaskar/zeus [@zeus1] [@zeus2] \ No newline at end of file diff --git a/paper/paper.bib b/paper/paper.bib index ab5fdf2c5..b17b343e4 100644 --- a/paper/paper.bib +++ b/paper/paper.bib @@ -34,16 +34,6 @@ @article{corner title = {corner.py: Scatterplot matrices in Python}, journal = {The Journal of Open Source Software} } -@article{pyswarms, - author = {Lester James V. Miranda}, - title = "{P}y{S}warms, a research-toolkit for {P}article {S}warm {O}ptimization in {P}ython", - journal = {Journal of Open Source Software}, - year = {2018}, - volume = {3}, - issue = {21}, - doi = {10.21105/joss.00433}, - url = {https://doi.org/10.21105/joss.00433} -} @article{dynesty, author = {Speagle, Joshua S}, diff --git a/paper/paper.md b/paper/paper.md index 14b5fec55..d51c092bc 100644 --- a/paper/paper.md +++ b/paper/paper.md @@ -71,7 +71,7 @@ define the model and associated parameters in an expressive way that is tied to model fit then requires that a `PyAutoFit` `Analysis` class is written, which combines the data, model and likelihood function and defines how the model-fit is performed using a `NonLinearSearch`. The `NonLinearSearch` procedure is defined using an external inference library such as `dynesty` [@dynesty], `emcee` [@emcee] -or `PySwarms` [@pyswarms]. +or `scipy` [@scipy]. The `Analysis` class provides a model specific interface between `PyAutoFit` and the modeling software, allowing it to handle the 'heavy lifting' that comes with writing model-fitting software. This includes interfacing with the @@ -145,7 +145,7 @@ taken without a local `PyAutoFit` installation. - `matplotlib` https://github.com/matplotlib/matplotlib [@matplotlib] - `NumPy` https://github.com/numpy/numpy [@numpy] - `PyMultiNest` https://github.com/JohannesBuchner/PyMultiNest [@multinest] [@pymultinest] -- `PySwarms` https://github.com/ljvmiranda921/pyswarms [@pyswarms] + - `Scipy` https://github.com/scipy/scipy [@scipy] # Related Probabilistic Programming Languages diff --git a/pyproject.toml b/pyproject.toml index 700f7d9da..9f55aeeb0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ dependencies = [ "gprof2dot==2021.2.21", "matplotlib", "numpydoc>=1.0.0", - "pyswarms==1.3.0", + "h5py>=3.11.0", "SQLAlchemy==2.0.32", "scipy<=1.14.0", @@ -66,7 +66,7 @@ optional=[ "astropy>=5.0", "getdist==1.4", "nautilus-sampler==1.0.5", - "ultranest==4.3.2", + "zeus-mcmc==2.5.4", ] docs=[ diff --git a/test_autofit/config/non_linear/README.rst b/test_autofit/config/non_linear/README.rst index 9432dd852..23ec96cd2 100644 --- a/test_autofit/config/non_linear/README.rst +++ b/test_autofit/config/non_linear/README.rst @@ -6,4 +6,4 @@ Files - ``mcmc.yaml``: Settings default behaviour of MCMC non-linear searches (e.g. Emcee). - ``nest.yaml``: Settings default behaviour of nested sampler non-linear searches (e.g. Dynesty). -- ``mle.yaml``: Settings default behaviour of maximum likelihood estimator (mle) searches (e.g. PySwarms). \ No newline at end of file +- ``mle.yaml``: Settings default behaviour of maximum likelihood estimator (mle) searches (e.g. BFGS). \ No newline at end of file diff --git a/test_autofit/config/non_linear/mle.yaml b/test_autofit/config/non_linear/mle.yaml index 5268c91e4..e68dd9763 100644 --- a/test_autofit/config/non_linear/mle.yaml +++ b/test_autofit/config/non_linear/mle.yaml @@ -26,39 +26,3 @@ LBFGS: silence: false search: tol: null -PySwarmsGlobal: - initialize: - ball_lower_limit: 0.49 - ball_upper_limit: 0.51 - method: prior - parallel: - number_of_cores: 1 - printing: - silence: false - run: - iters: 2000 - search: - cognitive: 0.1 - ftol: -.inf - inertia: 0.3 - n_particles: 50 - social: 0.2 -PySwarmsLocal: - initialize: - ball_lower_limit: 0.49 - ball_upper_limit: 0.51 - method: prior - parallel: - number_of_cores: 1 - printing: - silence: false - run: - iters: 2000 - search: - cognitive: 0.1 - ftol: -.inf - inertia: 0.3 - minkowski_p_norm: 2 - n_particles: 50 - number_of_k_neighbors: 3 - social: 0.2 diff --git a/test_autofit/config/non_linear/nest.yaml b/test_autofit/config/non_linear/nest.yaml index 5f9200151..a54542e0d 100644 --- a/test_autofit/config/non_linear/nest.yaml +++ b/test_autofit/config/non_linear/nest.yaml @@ -25,6 +25,32 @@ DynestyDynamic: slices: 6 update_interval: 2.0 walks: 26 +Nautilus: + search: + n_live: 200 + n_update: null + enlarge_per_dim: 1.2 + n_points_min: null + split_threshold: 50 + n_networks: 2 + n_batch: 50 + n_like_new_bound: null + vectorized: false + seed: null + run: + f_live: 0.02 + n_shell: 1 + n_eff: 250 + n_like_max: .inf + discard_exploration: false + verbose: false + initialize: + method: prior + parallel: + number_of_cores: 2 + force_x1_cpu: false + printing: + silence: false DynestyStatic: initialize: method: prior diff --git a/test_autofit/config/visualize.yaml b/test_autofit/config/visualize.yaml index e32bec104..75b319e90 100644 --- a/test_autofit/config/visualize.yaml +++ b/test_autofit/config/visualize.yaml @@ -12,15 +12,6 @@ plots_search: likelihood_series: false # Whether to output the Emcee likelihood series figure during a non-linear search fit. time_series: false # Whether to output the Emcee time series figure during a non-linear search fit. trajectories: false # Whether to output the Emcee trajectories figure during a non-linear search fit. - pyswarms: - contour: false # Whether to output the PySwarms contour figure during a non-linear search fit. - cost_history: false # Whether to output the PySwarms cost_history figure during a non-linear search fit. - time_series: false # Whether to output the PySwarms time_series figure during a non-linear search fit. - trajectories: false # Whether to output the PySwarms trajectories figure during a non-linear search fit. - ultranest: - corner: false # Whether to output the Ultranest corner figure during a non-linear search fit. - runplot: false # Whether to output the Ultranest runplot figure during a non-linear search fit. - traceplot: false # Whether to output the Ultranest traceplot figure during a non-linear search fit. zeus: corner: false # Whether to output the Zeus corner figure during a non-linear search fit. likelihood_series: false # Whether to output the Zeus likelihood series figure during a non-linear search fit. diff --git a/test_autofit/non_linear/search/nest/test_nautilus.py b/test_autofit/non_linear/search/nest/test_nautilus.py new file mode 100644 index 000000000..a76f0454d --- /dev/null +++ b/test_autofit/non_linear/search/nest/test_nautilus.py @@ -0,0 +1,57 @@ +import pytest + +import autofit as af + +pytestmark = pytest.mark.filterwarnings("ignore::FutureWarning") + + +def test__loads_from_config_file_if_not_input(): + search = af.Nautilus( + n_live=500, + n_batch=200, + f_live=0.05, + n_eff=1000, + iterations_per_full_update=501, + number_of_cores=4, + ) + + assert search.iterations_per_full_update == 501 + + assert search.config_dict_search["n_live"] == 500 + assert search.config_dict_search["n_batch"] == 200 + assert search.config_dict_run["f_live"] == 0.05 + assert search.config_dict_run["n_eff"] == 1000 + assert search.number_of_cores == 4 + + search = af.Nautilus() + + assert search.iterations_per_full_update == 1e99 + + assert search.config_dict_search["n_live"] == 200 + assert search.config_dict_search["n_batch"] == 50 + assert search.config_dict_search["enlarge_per_dim"] == 1.2 + assert search.config_dict_search["split_threshold"] == 50 + assert search.config_dict_search["n_networks"] == 2 + assert search.config_dict_search["vectorized"] == False + assert search.config_dict_run["f_live"] == 0.02 + assert search.config_dict_run["n_shell"] == 1 + assert search.config_dict_run["n_eff"] == 250 + assert search.config_dict_run["discard_exploration"] == False + assert search.number_of_cores == 2 + + +def test__identifier_fields(): + search = af.Nautilus() + + assert "n_live" in search.__identifier_fields__ + assert "n_eff" in search.__identifier_fields__ + assert "n_shell" in search.__identifier_fields__ + + +def test__config_dict_test_mode(): + search = af.Nautilus() + + config_dict = {"n_like_max": float("inf"), "f_live": 0.01, "n_eff": 500} + test_config = search.config_dict_test_mode_from(config_dict) + + assert test_config["n_like_max"] == 1 diff --git a/test_autofit/non_linear/search/optimize/test_pyswarms.py b/test_autofit/non_linear/search/optimize/test_pyswarms.py deleted file mode 100644 index a073973a4..000000000 --- a/test_autofit/non_linear/search/optimize/test_pyswarms.py +++ /dev/null @@ -1,70 +0,0 @@ -import pytest - -import autofit as af - -pytestmark = pytest.mark.filterwarnings("ignore::FutureWarning") - - -def test__loads_from_config_file_correct(): - search = af.PySwarmsGlobal( - n_particles=51, - iters=2001, - cognitive=0.4, - social=0.5, - inertia=0.6, - initializer=af.InitializerBall(lower_limit=0.2, upper_limit=0.8), - iterations_per_full_update=10, - number_of_cores=2, - ) - - assert search.config_dict_search["n_particles"] == 51 - assert search.config_dict_search["cognitive"] == 0.4 - assert search.config_dict_run["iters"] == 2001 - assert isinstance(search.initializer, af.InitializerBall) - assert search.initializer.lower_limit == 0.2 - assert search.initializer.upper_limit == 0.8 - assert search.iterations_per_full_update == 10 - assert search.number_of_cores == 2 - - search = af.PySwarmsGlobal() - - assert search.config_dict_search["n_particles"] == 50 - assert search.config_dict_search["cognitive"] == 0.1 - assert search.config_dict_run["iters"] == 2000 - assert isinstance(search.initializer, af.InitializerPrior) - assert search.iterations_per_full_update == 1e99 - assert search.number_of_cores == 1 - - search = af.PySwarmsLocal( - n_particles=51, - iters=2001, - cognitive=0.4, - social=0.5, - inertia=0.6, - number_of_k_neighbors=4, - minkowski_p_norm=1, - initializer=af.InitializerBall(lower_limit=0.2, upper_limit=0.8), - iterations_per_full_update=10, - number_of_cores=2, - ) - - assert search.config_dict_search["n_particles"] == 51 - assert search.config_dict_search["cognitive"] == 0.4 - assert search.config_dict_run["iters"] == 2001 - assert isinstance(search.initializer, af.InitializerBall) - assert search.initializer.lower_limit == 0.2 - assert search.initializer.upper_limit == 0.8 - assert search.iterations_per_full_update == 10 - assert search.number_of_cores == 2 - - search = af.PySwarmsLocal() - - assert search.config_dict_search["n_particles"] == 50 - assert search.config_dict_search["cognitive"] == 0.1 - assert search.config_dict_run["iters"] == 2000 - assert isinstance(search.initializer, af.InitializerPrior) - assert search.iterations_per_full_update == 1e99 - assert search.number_of_cores == 1 - - - diff --git a/test_autofit/non_linear/search/test_abstract_search.py b/test_autofit/non_linear/search/test_abstract_search.py index 98de035bd..a7d1f8929 100644 --- a/test_autofit/non_linear/search/test_abstract_search.py +++ b/test_autofit/non_linear/search/test_abstract_search.py @@ -80,6 +80,38 @@ def test_raises(self, result): ) +class TestSearchConfig: + def test__config_dict_search_accessible(self): + search = af.DynestyStatic(nlive=100) + assert search.config_dict_search["nlive"] == 100 + + def test__config_dict_run_accessible(self): + search = af.DynestyStatic(dlogz=0.5) + assert search.config_dict_run["dlogz"] == 0.5 + + def test__unique_tag(self): + search = af.DynestyStatic(unique_tag="my_tag") + assert search.unique_tag == "my_tag" + + def test__path_prefix_and_name(self): + from pathlib import Path + + search = af.DynestyStatic( + path_prefix="prefix", + name="my_search", + ) + assert search.path_prefix == Path("prefix") + assert search.name == "my_search" + + def test__identifier_fields_differ_across_searches(self): + emcee = af.Emcee() + dynesty = af.DynestyStatic() + + assert emcee.__identifier_fields__ != dynesty.__identifier_fields__ + assert "nwalkers" in emcee.__identifier_fields__ + assert "nlive" in dynesty.__identifier_fields__ + + class TestLabels: def test_param_names(self): model = af.Model(af.m.MockClassx4)