From 6d19e4aefadfdbdf8f9f71d6f77c453f0915d6d9 Mon Sep 17 00:00:00 2001 From: Michael Clerx Date: Wed, 18 Dec 2024 19:53:14 +0000 Subject: [PATCH] Updated docs for optimiser and CMA-ES. Added proper exception for CMA-ES not supporting 1-d. Closes #1695. Closes #1693. #Closes #1504. Closes #1524. --- CHANGELOG.md | 2 ++ pints/_optimisers/__init__.py | 30 ++++++++++++++++++------------ pints/_optimisers/_cmaes.py | 24 ++++++++++++++++++++++++ pints/tests/test_opt_cmaes.py | 18 ++++++++++++++++-- 4 files changed, 60 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfd93e8ad..00e66ce45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file. - [#1505](https://github.com/pints-team/pints/pull/1505) Added notes to `ErrorMeasure` and `LogPDF` to say parameters must be real and continuous. - [#1499](https://github.com/pints-team/pints/pull/1499) Added a log-uniform prior class. ### Changed +- [#1698](https://github.com/pints-team/pints/pull/1698) CMA-ES now raises a more informative exception when an unsupported 1-d optimisation is attempted. - [#1503](https://github.com/pints-team/pints/pull/1503) Stopped showing time units in controller logs, because the units change depending on the output type (see #1467). ### Deprecated ### Removed @@ -18,6 +19,7 @@ All notable changes to this project will be documented in this file. - [#1505](https://github.com/pints-team/pints/pull/1505) Fixed issues with toy problems that accept invalid inputs. + ## [0.5.0] - 2023-07-27 ### Added 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..6cb55c443 100644 --- a/pints/_optimisers/_cmaes.py +++ b/pints/_optimisers/_cmaes.py @@ -18,6 +18,25 @@ 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 + by the ``cma`` module natively. Other boundary shapes are also supported + but will be handled by ``PINTS`` (which will pass ``nan`` to ``cma`` for + any point requested outside of the boundaries). + Extends :class:`PopulationBasedOptimiser`. References @@ -38,6 +57,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_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')