From 0f44c1dd639d4d54425e1f21282159f8138ca078 Mon Sep 17 00:00:00 2001 From: Maren Mahsereci Date: Fri, 26 Apr 2024 13:56:04 +0200 Subject: [PATCH 1/4] add BQLoopState BQOuterLoop and COV stopping condition --- emukit/core/loop/outer_loop.py | 7 ++ emukit/quadrature/loop/bq_loop_state.py | 74 +++++++++++++++++++ emukit/quadrature/loop/bq_outer_loop.py | 33 +++++++++ .../quadrature/loop/bq_stopping_conditions.py | 61 +++++++++++++++ emukit/quadrature/loop/vanilla_bq_loop.py | 9 ++- emukit/quadrature/loop/wsabil_loop.py | 9 ++- 6 files changed, 185 insertions(+), 8 deletions(-) create mode 100644 emukit/quadrature/loop/bq_loop_state.py create mode 100644 emukit/quadrature/loop/bq_outer_loop.py create mode 100644 emukit/quadrature/loop/bq_stopping_conditions.py diff --git a/emukit/core/loop/outer_loop.py b/emukit/core/loop/outer_loop.py index 5402f6f2..19922e4a 100644 --- a/emukit/core/loop/outer_loop.py +++ b/emukit/core/loop/outer_loop.py @@ -101,6 +101,7 @@ def run_loop( _log.info("Iteration {}".format(self.loop_state.iteration)) self._update_models() + self._update_loop_state_custom() new_x = self.candidate_point_calculator.compute_next_points(self.loop_state, context) _log.debug("Next suggested point(s): {}".format(new_x)) results = user_function.evaluate(new_x) @@ -109,8 +110,14 @@ def run_loop( self.iteration_end_event(self, self.loop_state) self._update_models() + self._update_loop_state_custom() _log.info("Finished outer loop") + def _update_loop_state_custom(self) -> None: + """This method is called after the models are updated. Override this function to store model quantities + in the loop state.""" + pass + def _update_models(self): for model_updater in self.model_updaters: model_updater.update(self.loop_state) diff --git a/emukit/quadrature/loop/bq_loop_state.py b/emukit/quadrature/loop/bq_loop_state.py new file mode 100644 index 00000000..c84e0610 --- /dev/null +++ b/emukit/quadrature/loop/bq_loop_state.py @@ -0,0 +1,74 @@ +# Copyright 2020-2024 The Emukit Authors. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + + +from typing import List, Optional + +import numpy as np + +from ...core.loop.loop_state import LoopState, create_loop_state +from ...core.loop.user_function_result import UserFunctionResult + + +class BQLoopState(LoopState): + """Contains the state of the BQ loop, which includes a history of all function evaluations and integral mean and + variance estimates. + + :param initial_results: + :param initial_integral_means: + :param initial_integral_vars: + + :raises ValueError: If ``initial_integral_means`` and ``initial_integral_vars`` have different length. + + """ + def __init__( + self, + initial_results: List[UserFunctionResult], + initial_integral_means: Optional[List[float]] = None, + initial_integral_vars: Optional[List[float]] = None, + ) -> None: + super().__init__(initial_results) + + self.integral_means = initial_integral_means + if self.integral_means is None: + self.integral_means = [] + + self.integral_vars = initial_integral_vars + if self.integral_vars is None: + self.integral_vars = [] + + if len(self.integral_means) != len(self.integral_vars): + raise ValueError( + f"initial integral means list must have same length ({len(self.integral_means)}) " + f"as initial integral variances list ({len(self.integral_vars)})." + ) + + def update_integral_stats(self, integral_mean: float, integral_var: float) -> None: + """Adds the latest integral mean and variance to the loop state. + + :param integral_mean: The latest integral mean estimate. + :param integral_var: The latest integral variance. + """ + self.integral_means.append(integral_mean) + self.integral_vars.append(integral_var) + + +def create_bq_loop_state( + x_init: np.ndarray, + y_init: np.ndarray, + initial_integral_means: Optional[List[float]] = None, + initial_integral_vars: Optional[List[float]] = None, + **kwargs, +) -> BQLoopState: + """Creates a bq loop state object using the provided data. + + :param x_init: x values for initial function evaluations. Shape: (n_initial_points x n_input_dims) + :param y_init: y values for initial function evaluations. Shape: (n_initial_points x n_output_dims) + :param initial_integral_means: + :param initial_integral_vars: + :param kwargs: extra outputs observed from a function evaluation. Shape: (n_initial_points x n_dims) + :return: The bq loop state. + """ + + loop_state = create_loop_state(x_init, y_init, **kwargs) + return BQLoopState(loop_state.results, initial_integral_means, initial_integral_vars) diff --git a/emukit/quadrature/loop/bq_outer_loop.py b/emukit/quadrature/loop/bq_outer_loop.py new file mode 100644 index 00000000..d02313d1 --- /dev/null +++ b/emukit/quadrature/loop/bq_outer_loop.py @@ -0,0 +1,33 @@ +# Copyright 2020-2024 The Emukit Authors. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + + +from typing import List, Union +from ...core.loop import OuterLoop + +from ...core.loop.candidate_point_calculators import CandidatePointCalculator +from ...core.loop.model_updaters import ModelUpdater + +from .bq_loop_state import BQLoopState + + +class BQOuterLoop(OuterLoop): + """Base class for a Bayesian quadrature outer loop. + + :param candidate_point_calculator: Finds next point(s) to evaluate. + :param model_updater: Updates the model with the new data and fits the model hyper-parameters. + :param loop_state: Object that keeps track of the history of the BQ loop. Default is None, resulting in empty + initial state. + """ + + def __init__( + self, + candidate_point_calculator: CandidatePointCalculator, + model_updater: Union[ModelUpdater, List[ModelUpdater]], + loop_state: BQLoopState = None, + ): + super().__init__(candidate_point_calculator, model_updater, loop_state) + + def _update_loop_state_custom(self) -> None: + integral_mean, integral_var = self.model.integrate() + self.loop_state.update_integral_stats(integral_mean, integral_var) diff --git a/emukit/quadrature/loop/bq_stopping_conditions.py b/emukit/quadrature/loop/bq_stopping_conditions.py new file mode 100644 index 00000000..15fa41ea --- /dev/null +++ b/emukit/quadrature/loop/bq_stopping_conditions.py @@ -0,0 +1,61 @@ +# Copyright 2020-2024 The Emukit Authors. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + + +import logging + +import numpy as np + +from ...core.loop.stopping_conditions import StoppingCondition + +from .bq_loop_state import BQLoopState +_log = logging.getLogger(__name__) + + +class CoefficientOfVariationStoppingCondition(StoppingCondition): + r"""Stops once the coefficient of variation (COV) falls below a threshold. + + The COV is given by + + .. math:: + COV = \frac{\sigma}{\mu} + + with :math:`\mu` and :math:`\sigma^2` the current mean and standard deviation of integral according to the + BQ posterior model. + + :param eps: Threshold under which the COV must fall. + :param delay: Number of times the stopping condition needs to be true in a row in order to stop. Defaults to 1. + + """ + + def __init__(self, eps: float, delay: int = 1) -> None: + + if delay < 1: + raise ValueError(f"delay ({delay}) must be and integer greater than zero.") + + self.eps = eps + self.delay = delay + self.times_true = 0 # counts how many times stopping had been triggered in a row + + def should_stop(self, loop_state: BQLoopState) -> bool: + if len(loop_state.integral_means) < 1: + return False + + m = loop_state.integral_means[-1] + v = loop_state.integral_vars[-1] + should_stop = (np.sqrt(v) / m) < self.eps + + if should_stop: + self.times_true += 1 + else: + self.times_true = 0 + + print(np.sqrt(v) / m) + print(should_stop, self.times_true) + should_stop = should_stop and (self.times_true >= self.delay) + print(should_stop) + print() + + if should_stop: + _log.info(f"Stopped as coefficient of variation is below threshold of {self.eps}.") + return should_stop diff --git a/emukit/quadrature/loop/vanilla_bq_loop.py b/emukit/quadrature/loop/vanilla_bq_loop.py index 743a3a18..1c497acf 100644 --- a/emukit/quadrature/loop/vanilla_bq_loop.py +++ b/emukit/quadrature/loop/vanilla_bq_loop.py @@ -6,15 +6,16 @@ from ...core.acquisition import Acquisition -from ...core.loop import FixedIntervalUpdater, ModelUpdater, OuterLoop, SequentialPointCalculator -from ...core.loop.loop_state import create_loop_state +from ...core.loop import FixedIntervalUpdater, ModelUpdater, SequentialPointCalculator from ...core.optimization import AcquisitionOptimizerBase, GradientAcquisitionOptimizer from ...core.parameter_space import ParameterSpace from ..acquisitions import IntegralVarianceReduction from ..methods import VanillaBayesianQuadrature +from .bq_loop_state import create_bq_loop_state +from .bq_outer_loop import BQOuterLoop -class VanillaBayesianQuadratureLoop(OuterLoop): +class VanillaBayesianQuadratureLoop(BQOuterLoop): """The loop for standard ('vanilla') Bayesian Quadrature. .. seealso:: @@ -46,7 +47,7 @@ def __init__( if acquisition_optimizer is None: acquisition_optimizer = GradientAcquisitionOptimizer(space) candidate_point_calculator = SequentialPointCalculator(acquisition, acquisition_optimizer) - loop_state = create_loop_state(model.X, model.Y) + loop_state = create_bq_loop_state(model.X, model.Y) super().__init__(candidate_point_calculator, model_updater, loop_state) diff --git a/emukit/quadrature/loop/wsabil_loop.py b/emukit/quadrature/loop/wsabil_loop.py index 16dbed36..2fbd3d28 100644 --- a/emukit/quadrature/loop/wsabil_loop.py +++ b/emukit/quadrature/loop/wsabil_loop.py @@ -5,15 +5,16 @@ """The WSABI-L loop""" -from ...core.loop import FixedIntervalUpdater, ModelUpdater, OuterLoop, SequentialPointCalculator -from ...core.loop.loop_state import create_loop_state +from ...core.loop import FixedIntervalUpdater, ModelUpdater, SequentialPointCalculator from ...core.optimization import AcquisitionOptimizerBase, GradientAcquisitionOptimizer from ...core.parameter_space import ParameterSpace from ..acquisitions import UncertaintySampling from ..methods import WSABIL +from .bq_loop_state import create_bq_loop_state +from .bq_outer_loop import BQOuterLoop -class WSABILLoop(OuterLoop): +class WSABILLoop(BQOuterLoop): """The loop for WSABI-L. .. rubric:: References @@ -44,7 +45,7 @@ def __init__( if acquisition_optimizer is None: acquisition_optimizer = GradientAcquisitionOptimizer(space) candidate_point_calculator = SequentialPointCalculator(acquisition, acquisition_optimizer) - loop_state = create_loop_state(model.X, model.Y) + loop_state = create_bq_loop_state(model.X, model.Y) super().__init__(candidate_point_calculator, model_updater, loop_state) From 695fa2292969d2daca329ce023cd9edfba0fb78b Mon Sep 17 00:00:00 2001 From: Maren Mahsereci Date: Fri, 26 Apr 2024 15:17:46 +0200 Subject: [PATCH 2/4] some simplifications --- emukit/core/loop/outer_loop.py | 10 ++--- emukit/quadrature/loop/bq_loop_state.py | 44 ++++--------------- emukit/quadrature/loop/bq_outer_loop.py | 20 ++++++--- .../quadrature/loop/bq_stopping_conditions.py | 2 +- 4 files changed, 28 insertions(+), 48 deletions(-) diff --git a/emukit/core/loop/outer_loop.py b/emukit/core/loop/outer_loop.py index 19922e4a..40cf2e9c 100644 --- a/emukit/core/loop/outer_loop.py +++ b/emukit/core/loop/outer_loop.py @@ -101,7 +101,7 @@ def run_loop( _log.info("Iteration {}".format(self.loop_state.iteration)) self._update_models() - self._update_loop_state_custom() + self._update_loop_state() new_x = self.candidate_point_calculator.compute_next_points(self.loop_state, context) _log.debug("Next suggested point(s): {}".format(new_x)) results = user_function.evaluate(new_x) @@ -110,12 +110,12 @@ def run_loop( self.iteration_end_event(self, self.loop_state) self._update_models() - self._update_loop_state_custom() + self._update_loop_state() _log.info("Finished outer loop") - def _update_loop_state_custom(self) -> None: - """This method is called after the models are updated. Override this function to store model quantities - in the loop state.""" + def _update_loop_state(self) -> None: + """This method is called after the models are updated. Override this function to store additional statistics + other than the collected points and function values in the loop state.""" pass def _update_models(self): diff --git a/emukit/quadrature/loop/bq_loop_state.py b/emukit/quadrature/loop/bq_loop_state.py index c84e0610..6a0b849b 100644 --- a/emukit/quadrature/loop/bq_loop_state.py +++ b/emukit/quadrature/loop/bq_loop_state.py @@ -14,34 +14,16 @@ class BQLoopState(LoopState): """Contains the state of the BQ loop, which includes a history of all function evaluations and integral mean and variance estimates. - :param initial_results: - :param initial_integral_means: - :param initial_integral_vars: - - :raises ValueError: If ``initial_integral_means`` and ``initial_integral_vars`` have different length. + :param initial_results: The results from previous integrand evaluations. """ - def __init__( - self, - initial_results: List[UserFunctionResult], - initial_integral_means: Optional[List[float]] = None, - initial_integral_vars: Optional[List[float]] = None, - ) -> None: - super().__init__(initial_results) - self.integral_means = initial_integral_means - if self.integral_means is None: - self.integral_means = [] + def __init__(self, initial_results: List[UserFunctionResult]) -> None: - self.integral_vars = initial_integral_vars - if self.integral_vars is None: - self.integral_vars = [] + super().__init__(initial_results) - if len(self.integral_means) != len(self.integral_vars): - raise ValueError( - f"initial integral means list must have same length ({len(self.integral_means)}) " - f"as initial integral variances list ({len(self.integral_vars)})." - ) + self.integral_means = [] + self.integral_vars = [] def update_integral_stats(self, integral_mean: float, integral_var: float) -> None: """Adds the latest integral mean and variance to the loop state. @@ -53,22 +35,14 @@ def update_integral_stats(self, integral_mean: float, integral_var: float) -> No self.integral_vars.append(integral_var) -def create_bq_loop_state( - x_init: np.ndarray, - y_init: np.ndarray, - initial_integral_means: Optional[List[float]] = None, - initial_integral_vars: Optional[List[float]] = None, - **kwargs, -) -> BQLoopState: - """Creates a bq loop state object using the provided data. +def create_bq_loop_state(x_init: np.ndarray, y_init: np.ndarray, **kwargs) -> BQLoopState: + """Creates a BQ loop state object using the provided data. :param x_init: x values for initial function evaluations. Shape: (n_initial_points x n_input_dims) :param y_init: y values for initial function evaluations. Shape: (n_initial_points x n_output_dims) - :param initial_integral_means: - :param initial_integral_vars: :param kwargs: extra outputs observed from a function evaluation. Shape: (n_initial_points x n_dims) - :return: The bq loop state. + :return: The BQ loop state. """ loop_state = create_loop_state(x_init, y_init, **kwargs) - return BQLoopState(loop_state.results, initial_integral_means, initial_integral_vars) + return BQLoopState(loop_state.results) diff --git a/emukit/quadrature/loop/bq_outer_loop.py b/emukit/quadrature/loop/bq_outer_loop.py index d02313d1..c6742e38 100644 --- a/emukit/quadrature/loop/bq_outer_loop.py +++ b/emukit/quadrature/loop/bq_outer_loop.py @@ -3,11 +3,10 @@ from typing import List, Union -from ...core.loop import OuterLoop +from ...core.loop import OuterLoop from ...core.loop.candidate_point_calculators import CandidatePointCalculator from ...core.loop.model_updaters import ModelUpdater - from .bq_loop_state import BQLoopState @@ -15,19 +14,26 @@ class BQOuterLoop(OuterLoop): """Base class for a Bayesian quadrature outer loop. :param candidate_point_calculator: Finds next point(s) to evaluate. - :param model_updater: Updates the model with the new data and fits the model hyper-parameters. + :param model_updaters: Updates the model with the new data and fits the model hyper-parameters. :param loop_state: Object that keeps track of the history of the BQ loop. Default is None, resulting in empty initial state. + + :raises ValueError: If more than one model updater is provided. + """ def __init__( self, candidate_point_calculator: CandidatePointCalculator, - model_updater: Union[ModelUpdater, List[ModelUpdater]], + model_updaters: Union[ModelUpdater, List[ModelUpdater]], loop_state: BQLoopState = None, ): - super().__init__(candidate_point_calculator, model_updater, loop_state) + if isinstance(model_updaters, list): + raise ValueError("The BQ loop only supports a single model.") + + super().__init__(candidate_point_calculator, model_updaters, loop_state) - def _update_loop_state_custom(self) -> None: - integral_mean, integral_var = self.model.integrate() + def _update_loop_state(self) -> None: + model = self.model_updaters[0].model # only works if there is a model, but for BQ nothing else makes sense + integral_mean, integral_var = model.integrate() self.loop_state.update_integral_stats(integral_mean, integral_var) diff --git a/emukit/quadrature/loop/bq_stopping_conditions.py b/emukit/quadrature/loop/bq_stopping_conditions.py index 15fa41ea..747dfcc6 100644 --- a/emukit/quadrature/loop/bq_stopping_conditions.py +++ b/emukit/quadrature/loop/bq_stopping_conditions.py @@ -7,8 +7,8 @@ import numpy as np from ...core.loop.stopping_conditions import StoppingCondition - from .bq_loop_state import BQLoopState + _log = logging.getLogger(__name__) From e24fb064a09f517068d77eea977985ed95160c23 Mon Sep 17 00:00:00 2001 From: Maren Mahsereci Date: Fri, 26 Apr 2024 15:49:49 +0200 Subject: [PATCH 3/4] adding changes to docs --- emukit/quadrature/loop/__init__.py | 6 ++++++ emukit/quadrature/loop/bayesian_monte_carlo_loop.py | 9 +++++---- emukit/quadrature/loop/bq_loop_state.py | 8 ++++---- emukit/quadrature/loop/bq_outer_loop.py | 8 ++++---- emukit/quadrature/loop/bq_stopping_conditions.py | 6 +++--- emukit/quadrature/loop/vanilla_bq_loop.py | 4 ++-- emukit/quadrature/loop/wsabil_loop.py | 4 ++-- 7 files changed, 26 insertions(+), 19 deletions(-) diff --git a/emukit/quadrature/loop/__init__.py b/emukit/quadrature/loop/__init__.py index ef650520..f49dabee 100644 --- a/emukit/quadrature/loop/__init__.py +++ b/emukit/quadrature/loop/__init__.py @@ -12,12 +12,18 @@ from .bayesian_monte_carlo_loop import BayesianMonteCarlo # noqa: F401 +from .bq_loop_state import QuadratureLoopState +from .bq_outer_loop import QuadratureOuterLoop +from .bq_stopping_conditions import CoefficientOfVariationStoppingCondition from .vanilla_bq_loop import VanillaBayesianQuadratureLoop # noqa: F401 from .wsabil_loop import WSABILLoop # noqa: F401 __all__ = [ + "QuadratureOuterLoop", "BayesianMonteCarlo", "VanillaBayesianQuadratureLoop", "WSABILLoop", + "QuadratureLoopState", "point_calculators", + "CoefficientOfVariationStoppingCondition", ] diff --git a/emukit/quadrature/loop/bayesian_monte_carlo_loop.py b/emukit/quadrature/loop/bayesian_monte_carlo_loop.py index 289461a6..ca6d5067 100644 --- a/emukit/quadrature/loop/bayesian_monte_carlo_loop.py +++ b/emukit/quadrature/loop/bayesian_monte_carlo_loop.py @@ -8,14 +8,15 @@ # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 -from ...core.loop import FixedIntervalUpdater, ModelUpdater, OuterLoop -from ...core.loop.loop_state import create_loop_state +from ...core.loop import FixedIntervalUpdater, ModelUpdater from ...core.parameter_space import ParameterSpace from ..loop.point_calculators import BayesianMonteCarloPointCalculator from ..methods import WarpedBayesianQuadratureModel +from .bq_loop_state import create_bq_loop_state +from .bq_outer_loop import QuadratureOuterLoop -class BayesianMonteCarlo(OuterLoop): +class BayesianMonteCarlo(QuadratureOuterLoop): """The loop for Bayesian Monte Carlo (BMC). @@ -61,7 +62,7 @@ def __init__(self, model: WarpedBayesianQuadratureModel, model_updater: ModelUpd space = ParameterSpace(model.reasonable_box_bounds.convert_to_list_of_continuous_parameters()) candidate_point_calculator = BayesianMonteCarloPointCalculator(model, space) - loop_state = create_loop_state(model.X, model.Y) + loop_state = create_bq_loop_state(model.X, model.Y) super().__init__(candidate_point_calculator, model_updater, loop_state) diff --git a/emukit/quadrature/loop/bq_loop_state.py b/emukit/quadrature/loop/bq_loop_state.py index 6a0b849b..c555365d 100644 --- a/emukit/quadrature/loop/bq_loop_state.py +++ b/emukit/quadrature/loop/bq_loop_state.py @@ -10,8 +10,8 @@ from ...core.loop.user_function_result import UserFunctionResult -class BQLoopState(LoopState): - """Contains the state of the BQ loop, which includes a history of all function evaluations and integral mean and +class QuadratureLoopState(LoopState): + """Contains the state of the BQ loop, which includes a history of all integrand evaluations and integral mean and variance estimates. :param initial_results: The results from previous integrand evaluations. @@ -35,7 +35,7 @@ def update_integral_stats(self, integral_mean: float, integral_var: float) -> No self.integral_vars.append(integral_var) -def create_bq_loop_state(x_init: np.ndarray, y_init: np.ndarray, **kwargs) -> BQLoopState: +def create_bq_loop_state(x_init: np.ndarray, y_init: np.ndarray, **kwargs) -> QuadratureLoopState: """Creates a BQ loop state object using the provided data. :param x_init: x values for initial function evaluations. Shape: (n_initial_points x n_input_dims) @@ -45,4 +45,4 @@ def create_bq_loop_state(x_init: np.ndarray, y_init: np.ndarray, **kwargs) -> BQ """ loop_state = create_loop_state(x_init, y_init, **kwargs) - return BQLoopState(loop_state.results) + return QuadratureLoopState(loop_state.results) diff --git a/emukit/quadrature/loop/bq_outer_loop.py b/emukit/quadrature/loop/bq_outer_loop.py index c6742e38..02ab2191 100644 --- a/emukit/quadrature/loop/bq_outer_loop.py +++ b/emukit/quadrature/loop/bq_outer_loop.py @@ -7,11 +7,11 @@ from ...core.loop import OuterLoop from ...core.loop.candidate_point_calculators import CandidatePointCalculator from ...core.loop.model_updaters import ModelUpdater -from .bq_loop_state import BQLoopState +from .bq_loop_state import QuadratureLoopState -class BQOuterLoop(OuterLoop): - """Base class for a Bayesian quadrature outer loop. +class QuadratureOuterLoop(OuterLoop): + """Base class for a Bayesian quadrature loop. :param candidate_point_calculator: Finds next point(s) to evaluate. :param model_updaters: Updates the model with the new data and fits the model hyper-parameters. @@ -26,7 +26,7 @@ def __init__( self, candidate_point_calculator: CandidatePointCalculator, model_updaters: Union[ModelUpdater, List[ModelUpdater]], - loop_state: BQLoopState = None, + loop_state: QuadratureLoopState = None, ): if isinstance(model_updaters, list): raise ValueError("The BQ loop only supports a single model.") diff --git a/emukit/quadrature/loop/bq_stopping_conditions.py b/emukit/quadrature/loop/bq_stopping_conditions.py index 747dfcc6..9cb49253 100644 --- a/emukit/quadrature/loop/bq_stopping_conditions.py +++ b/emukit/quadrature/loop/bq_stopping_conditions.py @@ -7,7 +7,7 @@ import numpy as np from ...core.loop.stopping_conditions import StoppingCondition -from .bq_loop_state import BQLoopState +from .bq_loop_state import QuadratureLoopState _log = logging.getLogger(__name__) @@ -20,7 +20,7 @@ class CoefficientOfVariationStoppingCondition(StoppingCondition): .. math:: COV = \frac{\sigma}{\mu} - with :math:`\mu` and :math:`\sigma^2` the current mean and standard deviation of integral according to the + where :math:`\mu` and :math:`\sigma^2` are the current mean and standard deviation of integral according to the BQ posterior model. :param eps: Threshold under which the COV must fall. @@ -37,7 +37,7 @@ def __init__(self, eps: float, delay: int = 1) -> None: self.delay = delay self.times_true = 0 # counts how many times stopping had been triggered in a row - def should_stop(self, loop_state: BQLoopState) -> bool: + def should_stop(self, loop_state: QuadratureLoopState) -> bool: if len(loop_state.integral_means) < 1: return False diff --git a/emukit/quadrature/loop/vanilla_bq_loop.py b/emukit/quadrature/loop/vanilla_bq_loop.py index 1c497acf..e0f82171 100644 --- a/emukit/quadrature/loop/vanilla_bq_loop.py +++ b/emukit/quadrature/loop/vanilla_bq_loop.py @@ -12,10 +12,10 @@ from ..acquisitions import IntegralVarianceReduction from ..methods import VanillaBayesianQuadrature from .bq_loop_state import create_bq_loop_state -from .bq_outer_loop import BQOuterLoop +from .bq_outer_loop import QuadratureOuterLoop -class VanillaBayesianQuadratureLoop(BQOuterLoop): +class VanillaBayesianQuadratureLoop(QuadratureOuterLoop): """The loop for standard ('vanilla') Bayesian Quadrature. .. seealso:: diff --git a/emukit/quadrature/loop/wsabil_loop.py b/emukit/quadrature/loop/wsabil_loop.py index 2fbd3d28..5264c0f6 100644 --- a/emukit/quadrature/loop/wsabil_loop.py +++ b/emukit/quadrature/loop/wsabil_loop.py @@ -11,10 +11,10 @@ from ..acquisitions import UncertaintySampling from ..methods import WSABIL from .bq_loop_state import create_bq_loop_state -from .bq_outer_loop import BQOuterLoop +from .bq_outer_loop import QuadratureOuterLoop -class WSABILLoop(BQOuterLoop): +class WSABILLoop(QuadratureOuterLoop): """The loop for WSABI-L. .. rubric:: References From addb62d33cdeee9c10e9baa6fddf9b5c8f97f43b Mon Sep 17 00:00:00 2001 From: Maren Mahsereci Date: Tue, 7 May 2024 17:55:09 +0200 Subject: [PATCH 4/4] docstring updates --- emukit/quadrature/loop/bq_outer_loop.py | 2 +- emukit/quadrature/loop/bq_stopping_conditions.py | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/emukit/quadrature/loop/bq_outer_loop.py b/emukit/quadrature/loop/bq_outer_loop.py index 02ab2191..49b30707 100644 --- a/emukit/quadrature/loop/bq_outer_loop.py +++ b/emukit/quadrature/loop/bq_outer_loop.py @@ -34,6 +34,6 @@ def __init__( super().__init__(candidate_point_calculator, model_updaters, loop_state) def _update_loop_state(self) -> None: - model = self.model_updaters[0].model # only works if there is a model, but for BQ nothing else makes sense + model = self.model_updaters[0].model # only works if there is one model, but for BQ nothing else makes sense integral_mean, integral_var = model.integrate() self.loop_state.update_integral_stats(integral_mean, integral_var) diff --git a/emukit/quadrature/loop/bq_stopping_conditions.py b/emukit/quadrature/loop/bq_stopping_conditions.py index 9cb49253..e4798a59 100644 --- a/emukit/quadrature/loop/bq_stopping_conditions.py +++ b/emukit/quadrature/loop/bq_stopping_conditions.py @@ -20,12 +20,15 @@ class CoefficientOfVariationStoppingCondition(StoppingCondition): .. math:: COV = \frac{\sigma}{\mu} - where :math:`\mu` and :math:`\sigma^2` are the current mean and standard deviation of integral according to the - BQ posterior model. + where :math:`\mu` and :math:`\sigma^2` are the current mean and variance respectively of the integral according to + the BQ posterior model. :param eps: Threshold under which the COV must fall. :param delay: Number of times the stopping condition needs to be true in a row in order to stop. Defaults to 1. + :raises ValueError: If `delay` is smaller than 1. + :raises ValueError: If `eps` is non-negative. + """ def __init__(self, eps: float, delay: int = 1) -> None: @@ -33,9 +36,12 @@ def __init__(self, eps: float, delay: int = 1) -> None: if delay < 1: raise ValueError(f"delay ({delay}) must be and integer greater than zero.") + if eps <= 0.0: + raise ValueError(f"eps ({eps}) must be positive.") + self.eps = eps self.delay = delay - self.times_true = 0 # counts how many times stopping had been triggered in a row + self.times_true = 0 # counts how many times stopping has been triggered in a row def should_stop(self, loop_state: QuadratureLoopState) -> bool: if len(loop_state.integral_means) < 1: @@ -50,11 +56,7 @@ def should_stop(self, loop_state: QuadratureLoopState) -> bool: else: self.times_true = 0 - print(np.sqrt(v) / m) - print(should_stop, self.times_true) should_stop = should_stop and (self.times_true >= self.delay) - print(should_stop) - print() if should_stop: _log.info(f"Stopped as coefficient of variation is below threshold of {self.eps}.")