Source code for gemseo_umdo.statistics.multilevel.mlmc_mlcv.mlmc_mlcv

# Copyright 2021 IRT Saint Exupéry, https://www.irt-saintexupery.com
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License version 3 as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
"""Multilevel Monte Carlo with multilevel control variates (MLMC-MLCV)."""
from __future__ import annotations

from typing import Sequence

from gemseo.algos.parameter_space import ParameterSpace
from gemseo.core.mdofunctions.mdo_function import MDOFunction
from numpy import array
from strenum import StrEnum

from gemseo_umdo.statistics.multilevel.mlmc.mlmc import MLMC
from gemseo_umdo.statistics.multilevel.mlmc_mlcv.level import Level
from gemseo_umdo.statistics.multilevel.mlmc_mlcv.pilots.factory import (
    MLMCMLCVPilotFactory,
)


[docs]class MLMCMLCV(MLMC): """Multilevel Monte Carlo with multilevel control variates (MLMC-MLCV)."""
[docs] class Variant(StrEnum): """A variant of the MLMC-MLCV algorithm.""" MLMC_MLCV = "MLMC-MLCV" MLMC_MLCV_0 = "MLMC-MLCV[0]" MLMC_CV = "MLMC-CV" MLMC_CV_0 = "MLMC-CV[0]"
_PILOT_FACTORY = MLMCMLCVPilotFactory __g_l: tuple[MDOFunction] r"""The control variates $g_0$, $g_1$, ..., $g_L$. $g_\ell$ is an approximation of $f_\ell$. """ __h_l: tuple[MDOFunction] r"""The control variates $h_1$, ..., $h_L$. $h_\ell$ is an approximation of $f_\ell-f_{\ell-1}$. """ __variant: Variant """A variant of the algorithm.""" def __init__( # noqa: D107 self, levels: Sequence[Level], uncertain_space: ParameterSpace, n_samples: float, pilot: str = "Mean", variant: Variant = Variant.MLMC_MLCV, seed: int = 0, ) -> None: self.__g_l = tuple(level.surrogate_model[0] for level in levels) for l, g_l in enumerate(self.__g_l): # noqa: E741 g_l.name = f"g[{l}]" self.__h_l = tuple( level.difference_surrogate_model[0] for l, level in enumerate(levels) # noqa: E741 if l != 0 ) for l, h_l in enumerate(self.__h_l): # noqa: E741 h_l.name = f"h[{l+1}]" self.__variant = variant super().__init__( levels, uncertain_space, n_samples, pilot_statistic_name=pilot, seed=seed, ) self._algorithm_name = variant.value self._pilot_statistic_estimator_parameters = [ array([level.surrogate_model[1] for level in levels]), array( [ level.difference_surrogate_model[1] for l, level in enumerate(levels) # noqa: E741 if l != 0 ] ), variant, ] def _add_functions_to_samplers(self) -> None: # At level l, sample the models f[l] and f[l-1]. super()._add_functions_to_samplers() # At level l, sample some surrogate models. for l, sampler in enumerate(self._samplers): # noqa: E741 positions = self.get_surrogate_positions(l, self._n_levels, self.__variant) surrogate_models = self.__h_l[positions] if l else self.__g_l[positions] for surrogate_model in surrogate_models: sampler.add_function(surrogate_model)
[docs] @classmethod def get_surrogate_positions( cls, level: int, n_levels: int, variant: Variant ) -> slice: r"""Return the positions of the surrogate models for given level and variant. These are their positions in a sequence starting to count at 0. So, the position of $g_\ell$ is $\ell$ for $\ell\in\{0,\ldots,L\}$ while the position of $h_\ell$ is $\ell-1$ for $\ell\in\{1,\ldots,L\}$. Args: level: The level of the telescopic sum. n_levels: The number of levels. variant: The variant of the algorithm. Returns: The positions of the surrogate models. See Also: El Amri et al., Table 1, Multilevel Surrogate-based Control Variates, 2023. """ if level == 0: if variant == cls.Variant.MLMC_MLCV: return slice(0, n_levels) elif variant == cls.Variant.MLMC_CV: return slice(0, 1) elif variant == cls.Variant.MLMC_MLCV_0: return slice(0, 2) elif variant == cls.Variant.MLMC_CV_0: return slice(0, 1) else: if variant == cls.Variant.MLMC_MLCV: return slice(0, n_levels - 1) elif variant == cls.Variant.MLMC_CV: return slice(level - 1, level) elif variant == cls.Variant.MLMC_MLCV_0: return slice(0, 1) return slice(0, 0)