diff --git a/pints/_optimisers/__init__.py b/pints/_optimisers/__init__.py index 363a4ee2a..17c99d86a 100644 --- a/pints/_optimisers/__init__.py +++ b/pints/_optimisers/__init__.py @@ -34,15 +34,23 @@ class Optimiser(pints.Loggable, pints.TunableMethod): Parameters ---------- x0 - A starting point for searches in the parameter space. This value may be - used directly (for example as the initial position of a particle in + A starting point for searches in the parameter space. This must be a + 1-dimensional vector, and its length will determine the dimensionality + of the search space. The initial position ``x0`` may may be used + directly (for example as the initial position of a particle in :class:`PSO`) or indirectly (for example as the center of a distribution in :class:`XNES`). sigma0 An optional initial standard deviation around ``x0``. Can be specified either as a scalar value (one standard deviation for all coordinates) or as an array with one entry per dimension. Not all methods will use - this information. + this information, and some methods will only use part of the provided + information (e.g. ``CMA-ES`` will only use the smallest value in + ``sigma0``). If no value for ``sigma0`` is provided, a guess will be + made. If a :meth:`range` can be obtained from + provided boundaries, then 1/6th of this range will be used. If not, the + value will be set as ``abs(x0[i]) / 3`` for any ``i`` where + ``x0[i] != 0`` or as ``1`` where ``x0[i] == 0``. boundaries An optional set of boundaries on the parameter space. @@ -340,17 +348,14 @@ class OptimisationController(object): An :class:`pints.ErrorMeasure` or a :class:`pints.LogPDF` that evaluates points in the parameter space. x0 - The starting point for searches in the parameter space. This value may - be used directly (for example as the initial position of a particle in - :class:`PSO`) or indirectly (for example as the center of a - distribution in :class:`XNES`). + The starting point for searches in the parameter space. For details, + see :class:`Optimiser`. sigma0 - An optional initial standard deviation around ``x0``. Can be specified - either as a scalar value (one standard deviation for all coordinates) - or as an array with one entry per dimension. Not all methods will use - this information. + An optional initial standard deviation around ``x0``. For details, see + :class:`Optimiser`. boundaries - An optional set of boundaries on the parameter space. + An optional set of boundaries on the parameter space. For details, see + :class:`Optimiser`. transformation An optional :class:`pints.Transformation` to allow the optimiser to search in a transformed parameter space. If used, points shown or @@ -359,6 +364,7 @@ class OptimisationController(object): method The class of :class:`pints.Optimiser` to use for the optimisation. If no method is specified, :class:`CMAES` is used. + """ def __init__( diff --git a/pints/_optimisers/_cmaes.py b/pints/_optimisers/_cmaes.py index b223a1c41..83b5ffc65 100644 --- a/pints/_optimisers/_cmaes.py +++ b/pints/_optimisers/_cmaes.py @@ -18,6 +18,24 @@ class CMAES(pints.PopulationBasedOptimiser): CMA-ES stands for Covariance Matrix Adaptation Evolution Strategy, and is designed for non-linear derivative-free optimization problems. + To initialise, set an initial position ``x0``, an initial covariance + ``sigma0``, and optionally a set of :class:`pints.Boundaries`. + + CMA-ES is designed for multivariate optimisation, so ``x0`` should have a + dimension of 2 or greater. + + The initial covariance ``sigma0`` should be a scalar (one standard + deviation for all parameters), but for compatibility with other optimisers + an array is also accepted: in this case the smallest value in ``sigma0`` + will be used. If no ``sigma0`` is given a guess will be made based on the + boundaries (if provided) or ``x0``, see :meth:`Optimiser` for details. The + method's authors suggest choosing ``sigma0`` such that the optimum is + expected to be within ``3 * sigma0`` from the initial position ``x0``. + + :class:`Rectangular boundaries` are supported + natively. Other boundary shapes are supported by passing ``nan`` to ``cma`` + for any point requested outside of the boundaries. + Extends :class:`PopulationBasedOptimiser`. References @@ -38,6 +56,11 @@ class CMAES(pints.PopulationBasedOptimiser): def __init__(self, x0, sigma0=None, boundaries=None): super(CMAES, self).__init__(x0, sigma0, boundaries) + # 1-D is not supported + if len(x0) < 2: + raise ValueError( + '1-dimensional optimisation is not supported by CMA-ES.') + # Set initial state self._running = False self._ready_for_tell = False diff --git a/pints/tests/test_mcmc_controller.py b/pints/tests/test_mcmc_controller.py index 98220805b..affb33884 100755 --- a/pints/tests/test_mcmc_controller.py +++ b/pints/tests/test_mcmc_controller.py @@ -690,6 +690,35 @@ def test_log_pdf_storage_in_memory_single(self): likelihoods = [self.log_likelihood(x) for x in chain] self.assertTrue(np.all(evals[i] == likelihoods)) + # Test with a sensitivity-using method + mcmc = pints.MCMCController(self.log_posterior, n_chains, xs) + mcmc.set_max_iterations(n_iterations) + mcmc.set_log_to_screen(False) + mcmc.set_log_pdf_storage(True) + chains = mcmc.run() + + # Test shape of returned array + evals = mcmc.log_pdfs() + self.assertEqual(len(evals.shape), 3) + self.assertEqual(evals.shape[0], n_chains) + self.assertEqual(evals.shape[1], n_iterations) + self.assertEqual(evals.shape[2], 3) + + # Test returned values + for i, chain in enumerate(chains): + posteriors = [self.log_posterior(x) for x in chain] + self.assertTrue(np.all(evals[i, :, 0] == posteriors)) + + likelihoods = [self.log_likelihood(x) for x in chain] + self.assertTrue(np.all(evals[i, :, 1] == likelihoods)) + + priors = [self.log_prior(x) for x in chain] + self.assertTrue(np.all(evals[i, :, 2] == priors)) + + + + + # Test disabling again mcmc = pints.MCMCController(self.log_posterior, n_chains, xs) mcmc.set_max_iterations(n_iterations) diff --git a/pints/tests/test_opt_cmaes.py b/pints/tests/test_opt_cmaes.py index 7536149d0..8cf985055 100755 --- a/pints/tests/test_opt_cmaes.py +++ b/pints/tests/test_opt_cmaes.py @@ -7,6 +7,8 @@ # copyright notice and full license details. # import unittest +import warnings + import numpy as np import pints @@ -124,8 +126,15 @@ def test_ask_tell(self): self.assertEqual(opt.f_guessed(), np.inf) # Test deprecated xbest and fbest - self.assertEqual(list(opt.xbest()), list(x)) - self.assertEqual(opt.fbest(), np.inf) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + self.assertEqual(list(opt.xbest()), list(x)) + self.assertEqual(len(w), 1) + self.assertIn('xbest` is deprecated', str(w[-1].message)) + with warnings.catch_warnings(record=True) as w: + self.assertEqual(opt.fbest(), np.inf) + self.assertEqual(len(w), 1) + self.assertIn('fbest` is deprecated', str(w[-1].message)) # Tell before ask self.assertRaisesRegex( @@ -154,6 +163,11 @@ def test_name(self): opt = method(np.array([0, 1.01])) self.assertIn('CMA-ES', opt.name()) + def test_one_dimensional(self): + # Tests 1-d is not supported + self.assertRaisesRegex( + ValueError, '1-dimensional optimisation is', pints.CMAES, [1]) + if __name__ == '__main__': print('Add -v for more debug output')