Source code for gemseo.uncertainty

# 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.
"""Uncertainty quantification and management.

The package :mod:`~gemseo.uncertainty` provides several functionalities
to quantify and manage uncertainties.
Most of them can be used from the high-level functions provided by this module.

The sub-package :mod:`~gemseo.uncertainty.distributions` offers an abstract level
for probability distributions, as well as interfaces to the OpenTURNS and SciPy ones.
It is also possible to fit a probability distribution from data
or select the most likely one from a list of candidates.
These distributions can be used to define random variables in a :class:`.ParameterSpace`
before propagating these uncertainties through a system of :class:`.MDODiscipline`,
by means of a :class:`.DOEScenario`.

.. seealso::

    :class:`.OTDistribution`
    :class:`.SPDistribution`
    :class:`.OTDistributionFitter`

The sub-package :mod:`~gemseo.uncertainty.sensitivity` offers an abstract level
for sensitivity analysis, as well as concrete features.
These sensitivity analyses compute indices by means of various methods:
correlation measures, Morris technique and Sobol' variance decomposition.
This sub-package is based in particular on OpenTURNS.

.. seealso::

    :class:`.CorrelationAnalysis`
    :class:`.MorrisAnalysis`
    :class:`.SobolAnalysis`
    :class:`.HSICAnalysis`

The sub-package :mod:`~gemseo.uncertainty.statistics` offers an abstract level
for statistics, as well as parametric and empirical versions.
Empirical statistics are estimated from a :class:`.Dataset`
while parametric statistics are analytical properties of a :class:`.Distribution`
fitted from a :class:`.Dataset`.

.. seealso::

    :class:`.EmpiricalStatistics`
    :class:`.ParametricStatistics`
"""

from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from collections.abc import Collection
    from collections.abc import Iterable
    from collections.abc import Sequence
    from pathlib import Path

    from gemseo.algos.parameter_space import ParameterSpace
    from gemseo.core.discipline import MDODiscipline
    from gemseo.datasets.dataset import Dataset
    from gemseo.uncertainty.distributions.distribution import Distribution
    from gemseo.uncertainty.sensitivity.analysis import SensitivityAnalysis
    from gemseo.uncertainty.statistics.statistics import Statistics


[docs] def get_available_distributions(base_class_name: str = "Distribution") -> list[str]: """Get the available probability distributions. Args: base_class_name: The name of the base class of the probability distributions, e.g. ``"Distribution"``, ``"OTDistribution"`` or ``"SPDistribution"``. Returns: The names of the available probability distributions. """ from gemseo.uncertainty.distributions.factory import DistributionFactory factory = DistributionFactory() class_names = factory.class_names if base_class_name == "Distribution": return class_names return [ class_name for class_name in class_names if base_class_name in [cls.__name__ for cls in factory.get_class(class_name).mro()] ]
[docs] def create_distribution( variable: str, distribution_name: str, dimension: int = 1, **options, ) -> Distribution: """Create a distribution. Args: variable: The name of the random variable. distribution_name: The name of a class implementing a probability distribution, e.g. 'OTUniformDistribution' or 'SPDistribution'. dimension: The dimension of the random variable. **options: The distribution options. Examples: >>> from gemseo.uncertainty import create_distribution >>> >>> distribution = create_distribution( ... "x", "OTNormalDistribution", dimension=2, mu=1, sigma=2 ... ) >>> print(distribution) Normal(mu=1, sigma=2) >>> print(distribution.mean, distribution.standard_deviation) [1. 1.] [2. 2.] >>> samples = distribution.compute_samples(10) >>> print(samples.shape) (10, 2) """ from gemseo.uncertainty.distributions.factory import DistributionFactory factory = DistributionFactory() return factory.create( distribution_name, variable=variable, dimension=dimension, **options )
[docs] def get_available_sensitivity_analyses() -> list[str]: """Get the available sensitivity analyses.""" from gemseo.uncertainty.sensitivity.factory import SensitivityAnalysisFactory factory = SensitivityAnalysisFactory() return factory.available_sensitivity_analyses
[docs] def create_statistics( dataset: Dataset, variable_names: Iterable[str] | None = None, tested_distributions: Sequence[str] | None = None, fitting_criterion: str = "BIC", selection_criterion: str = "best", level: float = 0.05, name: str | None = None, ) -> Statistics: """Create a statistics toolbox, either parametric or empirical. If parametric, the toolbox selects a distribution from candidates, based on a fitting criterion and on a selection strategy. Args: dataset: A dataset. variable_names: The variables of interest. If ``None``, consider all the variables from dataset. tested_distributions: The names of the tested distributions. fitting_criterion: The name of a goodness-of-fit criterion, measuring how the distribution fits the data. Use :meth:`.ParametricStatistics.get_criteria` to get the available criteria. selection_criterion: The name of a selection criterion to select a distribution from candidates. Either 'first' or 'best'. level: A test level, i.e. the risk of committing a Type 1 error, that is an incorrect rejection of a true null hypothesis, for criteria based on a test hypothesis. name: A name for the statistics toolbox instance. If ``None``, use the concatenation of class and dataset names. Returns: A statistics toolbox. Examples: >>> from gemseo import ( ... create_discipline, ... create_parameter_space, ... create_scenario, ... ) >>> from gemseo.uncertainty import create_statistics >>> >>> expressions = {"y1": "x1+2*x2", "y2": "x1-3*x2"} >>> discipline = create_discipline( ... "AnalyticDiscipline", expressions=expressions ... ) >>> >>> parameter_space = create_parameter_space() >>> parameter_space.add_random_variable( ... "x1", "OTUniformDistribution", minimum=-1, maximum=1 ... ) >>> parameter_space.add_random_variable( ... "x2", "OTNormalDistribution", mu=0.5, sigma=2 ... ) >>> >>> scenario = create_scenario( ... [discipline], ... "DisciplinaryOpt", ... "y1", ... parameter_space, ... scenario_type="DOE", ... ) >>> scenario.execute({"algo": "OT_MONTE_CARLO", "n_samples": 100}) >>> >>> dataset = scenario.to_dataset(opt_naming=False) >>> >>> statistics = create_statistics(dataset) >>> mean = statistics.compute_mean() """ from gemseo.uncertainty.statistics.empirical import EmpiricalStatistics as EmpStats from gemseo.uncertainty.statistics.parametric import ( ParametricStatistics as ParamStats, ) if tested_distributions is None: statistical_analysis = EmpStats(dataset, variable_names, name) else: statistical_analysis = ParamStats( dataset, tested_distributions, variable_names, fitting_criterion, level, selection_criterion, name, ) return statistical_analysis
[docs] def create_sensitivity_analysis( analysis: str, disciplines: Collection[MDODiscipline], parameter_space: ParameterSpace, **options, ) -> SensitivityAnalysis: """Create the sensitivity analysis. Args: analysis: The name of a sensitivity analysis class. disciplines: The disciplines. parameter_space: A parameter space. **options: The DOE algorithm options. Returns: The toolbox for these sensitivity indices. Examples: >>> from gemseo import create_discipline, create_parameter_space >>> from gemseo.uncertainty import create_sensitivity_analysis >>> >>> expressions = {"y1": "x1+2*x2", "y2": "x1-3*x2"} >>> discipline = create_discipline( ... "AnalyticDiscipline", expressions=expressions ... ) >>> >>> parameter_space = create_parameter_space() >>> parameter_space.add_random_variable( ... "x1", "OTUniformDistribution", minimum=-1, maximum=1 ... ) >>> parameter_space.add_random_variable( ... "x2", "OTNormalDistribution", mu=0.5, sigma=2 ... ) >>> >>> analysis = create_sensitivity_analysis( ... "CorrelationIndices", [discipline], parameter_space, n_samples=1000 ... ) >>> indices = analysis.compute_indices() """ from gemseo.uncertainty.sensitivity.factory import SensitivityAnalysisFactory factory = SensitivityAnalysisFactory() name = analysis if "Analysis" not in name: name += "Analysis" name = name[0].upper() + name[1:] return factory.create(name, disciplines, parameter_space, **options)
[docs] def load_sensitivity_analysis(file_path: str | Path) -> SensitivityAnalysis: """Load a sensitivity analysis from the disk. Args: file_path: The path to the file. Returns: The sensitivity analysis. """ from gemseo.uncertainty.sensitivity.analysis import SensitivityAnalysis return SensitivityAnalysis.from_pickle(file_path)