From 518fd23cd9505dd7f786e5250a8538230e016ce1 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Thu, 1 Jun 2023 09:29:32 +0200 Subject: [PATCH 01/73] Pseudo-RNG is now attached to the `UnivDist` and `ProbInput`. - Following the best practice of using NumPy's pseudo-RNG, all instances of relevant classes (`UnivDist` and `ProbInput`) now include independent RNG to avoid using the global RNG. Seed number may be passed for reproducibility purposes. --- CHANGELOG.md | 9 +++++ docs/getting-started/creating-a-built-in.md | 33 ++++++++++++++----- .../core/prob_input/probabilistic_input.py | 31 ++++++++++++----- .../prob_input/univariate_distribution.py | 17 +++++++++- src/uqtestfuns/test_functions/ackley.py | 18 ++++++++-- src/uqtestfuns/test_functions/available.py | 7 +++- src/uqtestfuns/test_functions/borehole.py | 5 ++- .../test_functions/damped_oscillator.py | 5 ++- src/uqtestfuns/test_functions/flood.py | 5 ++- src/uqtestfuns/test_functions/ishigami.py | 5 ++- .../test_functions/oakley_ohagan_1d.py | 5 ++- src/uqtestfuns/test_functions/otl_circuit.py | 5 ++- src/uqtestfuns/test_functions/piston.py | 5 ++- src/uqtestfuns/test_functions/sobol_g.py | 6 +++- src/uqtestfuns/test_functions/sulfur.py | 5 ++- src/uqtestfuns/test_functions/wing_weight.py | 5 ++- .../test_test_functions.py | 28 ++++++---------- .../prob_input/test_multivariate_input.py | 18 ++++++++++ .../core/prob_input/test_univariate_input.py | 16 +++++++++ tests/core/test_uqtestfun.py | 10 ++++++ 20 files changed, 188 insertions(+), 50 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed6231b..4633e5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,16 @@ All notable changes to the UQTestFuns project is documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + ## [0.1.1] - 2023-07-03 +### Added + +- An instance of NumPy random number generator is now attached to instances of + `UnivDist` and `ProbInput`. The random seed + number may be passed to the corresponding constructor for reproducibility. + ### Fixed - v0.1.0 was erroneously already registered at PyPI; @@ -51,6 +59,7 @@ First public release of UQTestFuns. - CI/CD to build and serve the documentation on [ReadTheDocs](https://readthedocs.org/) - Mirror GitHub action to the [CASUS organization](https://github.com/casus) +[Unreleased]: https://github.com/damar-wicaksono/uqtestfuns/compare/main...dev [0.1.1]: https://github.com/damar-wicaksono/uqtestfuns/compare/v0.1.0...v0.1.1 [0.1.0]: https://github.com/damar-wicaksono/uqtestfuns/compare/v0.0.1...v0.1.0 [0.0.1]: https://github.com/damar-wicaksono/uqtestfuns/releases/tag/v0.0.1 diff --git a/docs/getting-started/creating-a-built-in.md b/docs/getting-started/creating-a-built-in.md index 9418e38..e856a95 100644 --- a/docs/getting-started/creating-a-built-in.md +++ b/docs/getting-started/creating-a-built-in.md @@ -47,7 +47,20 @@ in the context of metamodeling and sensitivity analysis. To instantiate a borehole test function, call the constructor as follows: ```{code-cell} ipython3 -my_testfun = uqtf.Borehole() +my_testfun = uqtf.Borehole(rng_seed_prob_input=176326) +``` + +```{note} +The parameter `rng_seed_prob_input` is optional; if not specified, +the system entropy is used to initialized +the [NumPy default random generator](https://numpy.org/doc/stable/reference/random/generator.html#numpy.random.default_rng). +The number is given here such that the example is more or less reproducible. + +In UQTestFuns, each instance of test function carries its own pseudo-random +number generator (RNG) for the corresponding probabilitic input model +to avoid using the global NumPy random RNG. +See this [blog post](https://albertcthomas.github.io/good-practices-random-number-generators/) +regarding some good practices on using NumPy RNG. ``` To verify whether the instance has been created, @@ -81,7 +94,7 @@ my_testfun(xx) ```{note} Calling the function on a set of input values automatically verifies the correctness of the input (its dimensionality and bounds). -Furthermore, the test function also accepts a vectorized input +Moreover, the test function accepts a vectorized input (that is, an $N$-by-$M$ array where $N$ and $M$ are the number of points and dimensions, respectively) ``` @@ -153,8 +166,8 @@ For instance, suppose we have a sample of size $5$ in $[-1, 1]^8$ for the borehole function: ```{code-cell} ipython3 -np.random.seed(42) -xx_sample_dom_1 = -1 + 2 * np.random.rand(5, 8) +rng_1 = np.random.default_rng(42) +xx_sample_dom_1 = rng_1.uniform(low=-1, high=1, size=(5, 8)) xx_sample_dom_1 ``` @@ -172,15 +185,17 @@ It is possible to transform values defined in another uniform domain. For example, the sample values in $[0, 1]^8$ (a unit hypercube): ```{code-cell} ipython3 -np.random.seed(42) -xx_sample_dom_2 = np.random.rand(5, 8) +rng_2 = np.random.default_rng(42) +xx_sample_dom_2 = rng_2.random((5, 8)) xx_sample_dom_2 ``` can be transformed to the domain of the borehole function as follows: ```{code-cell} ipython3 -xx_sample_trans_2 = my_testfun.transform_sample(xx_sample_dom_2, min_value=0.0, max_value=1.0) +xx_sample_trans_2 = my_testfun.transform_sample( + xx_sample_dom_2, min_value=0.0, max_value=1.0 +) xx_sample_trans_2 ``` @@ -188,8 +203,8 @@ Note that for a given sample, the bounds of the hypercube domain must be the same in all dimensions. The two transformed values above should be the same since -we reset the seed for the random number generator -each time we call `np.random.rand()`. +we use two instances of the default RNG with the same seed +to generate the random sample. ```{code-cell} ipython3 assert np.allclose(xx_sample_trans_1, xx_sample_trans_2) diff --git a/src/uqtestfuns/core/prob_input/probabilistic_input.py b/src/uqtestfuns/core/prob_input/probabilistic_input.py index e057a4a..e6f2cf6 100644 --- a/src/uqtestfuns/core/prob_input/probabilistic_input.py +++ b/src/uqtestfuns/core/prob_input/probabilistic_input.py @@ -8,9 +8,11 @@ from __future__ import annotations import numpy as np + +from dataclasses import dataclass, field +from numpy.random._generator import Generator from tabulate import tabulate from typing import Any, List, Optional, Union, Tuple -from dataclasses import dataclass, field from .univariate_distribution import UnivDist, FIELD_NAMES @@ -25,19 +27,26 @@ class ProbInput: Parameters ---------- marginals : Union[List[UnivDist], Tuple[UnivDist, ...]] - A list of one-dimensional marginals (univariate random variables) + A list of one-dimensional marginals (univariate random variables). copulas : Any Copulas between univariate inputs that define dependence structure - (currently not used) + (currently not used). name : str, optional - The name of the probabilistic input model + The name of the probabilistic input model. description : str, optional - The short description regarding the input model + The short description regarding the input model. + rng_seed : int, optional. + The seed used to initialize the pseudo-random number generator. + If not specified, the value is taken from the system entropy. Attributes ---------- spatial_dimension : int Number of constituents (random) input variables. + _rng : Generator + The default pseudo-random number generator of NumPy. + The generator is only created if or when needed (e.g., generating + a random sample from the distribution). """ spatial_dimension: int = field(init=False) @@ -45,6 +54,8 @@ class ProbInput: copulas: Any = None name: Optional[str] = None description: Optional[str] = None + rng_seed: Optional[int] = field(default=None, repr=False) + _rng: Optional[Generator] = field(init=False, default=None, repr=False) def __post_init__(self): self.spatial_dimension = len(self.marginals) @@ -88,13 +99,15 @@ def get_sample(self, sample_size: int = 1) -> np.ndarray: where :math:`N` and :math:`M` are the sample size and the number of spatial dimensions, respectively. """ + if self._rng is None: # pragma: no cover + # Create a pseudo-random number generator (lazy evaluation) + self._rng = np.random.default_rng(self.rng_seed) - xx = np.empty((sample_size, self.spatial_dimension)) - # Transform the sample in [0, 1] to the domain of the distribution + xx = self._rng.random((sample_size, self.spatial_dimension)) if not self.copulas: - # Independent inputs generate sample marginal by marginal + # Transform the sample in [0, 1] to the domain of the distribution for idx_dim, marginal in enumerate(self.marginals): - xx[:, idx_dim] = marginal.get_sample(sample_size) + xx[:, idx_dim] = marginal.icdf(xx[:, idx_dim]) else: raise ValueError("Copulas are not currently supported!") diff --git a/src/uqtestfuns/core/prob_input/univariate_distribution.py b/src/uqtestfuns/core/prob_input/univariate_distribution.py index 2feb1f5..7f0f36b 100644 --- a/src/uqtestfuns/core/prob_input/univariate_distribution.py +++ b/src/uqtestfuns/core/prob_input/univariate_distribution.py @@ -8,6 +8,7 @@ import numpy as np +from numpy.random._generator import Generator from numpy.typing import ArrayLike from dataclasses import dataclass, field from typing import Optional, Union @@ -42,6 +43,9 @@ class UnivDist: The name of the random variable description : str, optional The short text description of the random variable + rng_seed : int, optional. + The seed used to initialize the pseudo-random number generator. + If not specified, the value is taken from the system entropy. Attributes ---------- @@ -49,6 +53,10 @@ class UnivDist: The lower bound of the distribution upper : float The upper bound of the distribution + _rng : Generator + The default pseudo-random number generator of NumPy. + The generator is only created if or when needed (e.g., generating + a random sample from the distribution). """ distribution: str @@ -57,6 +65,8 @@ class UnivDist: description: Optional[str] = None lower: float = field(init=False, repr=False) upper: float = field(init=False, repr=False) + rng_seed: Optional[int] = field(default=None, repr=False) + _rng: Optional[Generator] = field(init=False, default=None, repr=False) def __post_init__(self) -> None: # Because frozen=True, post init must access self via setattr @@ -100,7 +110,12 @@ def transform_sample( def get_sample(self, sample_size: int = 1) -> np.ndarray: """Get a random sample from the distribution.""" - xx = np.random.rand(sample_size) + if self._rng is None: # pragma: no cover + # Create a pseudo-random number generator (lazy evaluation) + rng = np.random.default_rng(self.rng_seed) + object.__setattr__(self, "_rng", rng) + + xx = self._rng.random(sample_size) # type: ignore return get_icdf_values( xx, self.distribution, self.parameters, self.lower, self.upper diff --git a/src/uqtestfuns/test_functions/ackley.py b/src/uqtestfuns/test_functions/ackley.py index 07fcac2..c3f6d54 100644 --- a/src/uqtestfuns/test_functions/ackley.py +++ b/src/uqtestfuns/test_functions/ackley.py @@ -95,6 +95,14 @@ class Ackley(UQTestFunABC): parameters_selection : str, optional The selection of a parameters sets from a list of available parameter sets. This is a keyword only parameter. + name : str, optional + The name of the instance; if not given the default name is used. + This is a keyword only parameter. + rng_seed_prob_input : int, optional + The seed number for the pseudo-random number generator of the + corresponding `ProbInput`; if not given `None` is used + (taken from the system entropy). + This is a keyword only parameter. """ _TAGS = ["optimization", "metamodeling"] @@ -114,6 +122,7 @@ def __init__( prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, parameters_selection: Optional[str] = DEFAULT_PARAMETERS_SELECTION, name: Optional[str] = None, + rng_seed_prob_input: Optional[int] = None, ): # --- Arguments processing if not isinstance(spatial_dimension, int): @@ -124,7 +133,10 @@ def __init__( # Ackley is an M-dimensional test function, either given / use default # Create the input according to spatial dimension prob_input = create_prob_input_from_available( - prob_input_selection, AVAILABLE_INPUT_SPECS, spatial_dimension + prob_input_selection, + AVAILABLE_INPUT_SPECS, + spatial_dimension, + rng_seed_prob_input, ) # Create the parameters according to spatial dimension parameters = create_parameters_from_available( @@ -135,7 +147,9 @@ def __init__( name = Ackley.__name__ super().__init__( - prob_input=prob_input, parameters=parameters, name=name + prob_input=prob_input, + parameters=parameters, + name=name, ) def evaluate(self, xx: np.ndarray): diff --git a/src/uqtestfuns/test_functions/available.py b/src/uqtestfuns/test_functions/available.py index 9798466..0192997 100644 --- a/src/uqtestfuns/test_functions/available.py +++ b/src/uqtestfuns/test_functions/available.py @@ -10,6 +10,7 @@ def create_prob_input_from_available( input_selection: Optional[str], available_input_specs: dict, spatial_dimension: Optional[int] = None, + rng_seed: Optional[int] = None, ) -> Optional[ProbInput]: """Construct a Multivariate input given available specifications. @@ -22,6 +23,9 @@ def create_prob_input_from_available( spatial_dimension : int, optional The requested number of spatial dimensions, when applicable. Some specifications are functions of spatial dimension. + rng_seed : int, optional + The seed for the pseudo-random number generator; if not given then + the number is taken from the system entropy. Raises ------ @@ -41,9 +45,10 @@ def create_prob_input_from_available( description=input_specs["description"], marginals=marginals, copulas=input_specs["copulas"], + rng_seed=rng_seed, ) else: - prob_input = ProbInput(**input_specs) + prob_input = ProbInput(**input_specs, rng_seed=rng_seed) else: raise ValueError("Invalid selection!") diff --git a/src/uqtestfuns/test_functions/borehole.py b/src/uqtestfuns/test_functions/borehole.py index 4188630..183291d 100644 --- a/src/uqtestfuns/test_functions/borehole.py +++ b/src/uqtestfuns/test_functions/borehole.py @@ -142,10 +142,13 @@ def __init__( *, prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, name: Optional[str] = None, + rng_seed_prob_input: Optional[int] = None, ): # --- Arguments processing prob_input = create_prob_input_from_available( - prob_input_selection, AVAILABLE_INPUT_SPECS + prob_input_selection, + AVAILABLE_INPUT_SPECS, + rng_seed=rng_seed_prob_input, ) # Process the default name if name is None: diff --git a/src/uqtestfuns/test_functions/damped_oscillator.py b/src/uqtestfuns/test_functions/damped_oscillator.py index d79c279..8986f8d 100644 --- a/src/uqtestfuns/test_functions/damped_oscillator.py +++ b/src/uqtestfuns/test_functions/damped_oscillator.py @@ -142,10 +142,13 @@ def __init__( *, prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, name: Optional[str] = None, + rng_seed_prob_input: Optional[int] = None, ): # --- Arguments processing prob_input = create_prob_input_from_available( - prob_input_selection, AVAILABLE_INPUT_SPECS + prob_input_selection, + AVAILABLE_INPUT_SPECS, + rng_seed=rng_seed_prob_input, ) # Process the default name if name is None: diff --git a/src/uqtestfuns/test_functions/flood.py b/src/uqtestfuns/test_functions/flood.py index d3a2ec4..710a49e 100644 --- a/src/uqtestfuns/test_functions/flood.py +++ b/src/uqtestfuns/test_functions/flood.py @@ -124,10 +124,13 @@ def __init__( *, prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, name: Optional[str] = None, + rng_seed_prob_input: Optional[int] = None, ): # --- Arguments processing prob_input = create_prob_input_from_available( - prob_input_selection, AVAILABLE_INPUT_SPECS + prob_input_selection, + AVAILABLE_INPUT_SPECS, + rng_seed=rng_seed_prob_input, ) # Process the default name if name is None: diff --git a/src/uqtestfuns/test_functions/ishigami.py b/src/uqtestfuns/test_functions/ishigami.py index c231bde..19bf866 100644 --- a/src/uqtestfuns/test_functions/ishigami.py +++ b/src/uqtestfuns/test_functions/ishigami.py @@ -103,10 +103,13 @@ def __init__( prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, parameters_selection: Optional[str] = DEFAULT_PARAMETERS_SELECTION, name: Optional[str] = None, + rng_seed_prob_input: Optional[int] = None, ): # --- Arguments processing prob_input = create_prob_input_from_available( - prob_input_selection, AVAILABLE_INPUT_SPECS + prob_input_selection, + AVAILABLE_INPUT_SPECS, + rng_seed=rng_seed_prob_input, ) # Ishigami supports several different parameterizations parameters = create_parameters_from_available( diff --git a/src/uqtestfuns/test_functions/oakley_ohagan_1d.py b/src/uqtestfuns/test_functions/oakley_ohagan_1d.py index 8125a0f..cf2f62f 100644 --- a/src/uqtestfuns/test_functions/oakley_ohagan_1d.py +++ b/src/uqtestfuns/test_functions/oakley_ohagan_1d.py @@ -64,10 +64,13 @@ def __init__( *, prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, name: Optional[str] = None, + rng_seed_prob_input: Optional[int] = None, ): # --- Arguments processing prob_input = create_prob_input_from_available( - prob_input_selection, AVAILABLE_INPUT_SPECS + prob_input_selection, + AVAILABLE_INPUT_SPECS, + rng_seed=rng_seed_prob_input, ) # Process the default name if name is None: diff --git a/src/uqtestfuns/test_functions/otl_circuit.py b/src/uqtestfuns/test_functions/otl_circuit.py index ec91cb3..c4a653c 100644 --- a/src/uqtestfuns/test_functions/otl_circuit.py +++ b/src/uqtestfuns/test_functions/otl_circuit.py @@ -126,10 +126,13 @@ def __init__( *, prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, name: Optional[str] = None, + rng_seed_prob_input: Optional[int] = None, ): # --- Arguments processing prob_input = create_prob_input_from_available( - prob_input_selection, AVAILABLE_INPUT_SPECS + prob_input_selection, + AVAILABLE_INPUT_SPECS, + rng_seed=rng_seed_prob_input, ) # Process the default name if name is None: diff --git a/src/uqtestfuns/test_functions/piston.py b/src/uqtestfuns/test_functions/piston.py index 59b01f6..0617467 100644 --- a/src/uqtestfuns/test_functions/piston.py +++ b/src/uqtestfuns/test_functions/piston.py @@ -131,10 +131,13 @@ def __init__( *, prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, name: Optional[str] = None, + rng_seed_prob_input: Optional[int] = None, ): # --- Arguments processing prob_input = create_prob_input_from_available( - prob_input_selection, AVAILABLE_INPUT_SPECS + prob_input_selection, + AVAILABLE_INPUT_SPECS, + rng_seed=rng_seed_prob_input, ) # Process the default name if name is None: diff --git a/src/uqtestfuns/test_functions/sobol_g.py b/src/uqtestfuns/test_functions/sobol_g.py index 9296339..5f5fa89 100644 --- a/src/uqtestfuns/test_functions/sobol_g.py +++ b/src/uqtestfuns/test_functions/sobol_g.py @@ -220,6 +220,7 @@ def __init__( prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, parameters_selection: Optional[str] = DEFAULT_PARAMETERS_SELECTION, name: Optional[str] = None, + rng_seed_prob_input: Optional[int] = None, ): # --- Arguments processing if not isinstance(spatial_dimension, int): @@ -230,7 +231,10 @@ def __init__( # Sobol-G is an M-dimensional test function, either given / use default # Create the input according to spatial dimension prob_input = create_prob_input_from_available( - prob_input_selection, AVAILABLE_INPUT_SPECS, spatial_dimension + prob_input_selection, + AVAILABLE_INPUT_SPECS, + spatial_dimension, + rng_seed_prob_input, ) # Create the parameters according to spatial dimension parameters = create_parameters_from_available( diff --git a/src/uqtestfuns/test_functions/sulfur.py b/src/uqtestfuns/test_functions/sulfur.py index 5b35eeb..5f939d0 100644 --- a/src/uqtestfuns/test_functions/sulfur.py +++ b/src/uqtestfuns/test_functions/sulfur.py @@ -164,10 +164,13 @@ def __init__( *, prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, name: Optional[str] = None, + rng_seed_prob_input: Optional[int] = None, ): # --- Arguments processing prob_input = create_prob_input_from_available( - prob_input_selection, AVAILABLE_INPUT_SPECS + prob_input_selection, + AVAILABLE_INPUT_SPECS, + rng_seed=rng_seed_prob_input, ) # Process the default name if name is None: diff --git a/src/uqtestfuns/test_functions/wing_weight.py b/src/uqtestfuns/test_functions/wing_weight.py index 19c5cb9..c58c8cf 100644 --- a/src/uqtestfuns/test_functions/wing_weight.py +++ b/src/uqtestfuns/test_functions/wing_weight.py @@ -126,10 +126,13 @@ def __init__( *, prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, name: Optional[str] = None, + rng_seed_prob_input: Optional[int] = None, ): # --- Arguments processing prob_input = create_prob_input_from_available( - prob_input_selection, AVAILABLE_INPUT_SPECS + prob_input_selection, + AVAILABLE_INPUT_SPECS, + rng_seed=rng_seed_prob_input, ) # Process the default name if name is None: diff --git a/tests/builtin_test_functions/test_test_functions.py b/tests/builtin_test_functions/test_test_functions.py index 2322728..29133da 100644 --- a/tests/builtin_test_functions/test_test_functions.py +++ b/tests/builtin_test_functions/test_test_functions.py @@ -154,26 +154,22 @@ def test_transform_input(builtin_testfun): """Test transforming a set of input values in the default unif. domain.""" testfun = builtin_testfun + rng_seed = 32 # Create an instance - my_fun = testfun() + my_fun = testfun(rng_seed_prob_input=rng_seed) sample_size = 100 # Transformation from the default uniform domain to the input domain - np.random.seed(315) - # NOTE: Direct sample from the input property is done by column to column, - # for reproducibility using the same RNG seed the reference input must be - # filled in column by column as well with the. The call to NumPy random - # number generators below yields the same effect. - xx_1 = -1 + 2 * np.random.rand(my_fun.spatial_dimension, sample_size).T + rng = np.random.default_rng(rng_seed) + xx_1 = -1 + 2 * rng.random((sample_size, my_fun.spatial_dimension)) xx_1 = my_fun.transform_sample(xx_1) # Directly sample from the input property - np.random.seed(315) xx_2 = my_fun.prob_input.get_sample(sample_size) - # Assertion: two sampled values are equal + # Assertion: Both samples are equal because the seed is identical assert np.allclose(xx_1, xx_2) @@ -181,26 +177,22 @@ def test_transform_input_non_default(builtin_testfun): """Test transforming an input from non-default domain.""" testfun = builtin_testfun + rng_seed = 1232 # Create an instance - my_fun = testfun() + my_fun = testfun(rng_seed_prob_input=rng_seed) sample_size = 100 # Transformation from non-default uniform domain to the input domain - np.random.seed(315) - # NOTE: Direct sample from the input property is done by column to column, - # for reproducibility using the same RNG seed the reference input must be - # filled in column by column as well with the. The call to NumPy random - # number generators below yields the same effect. - xx_1 = np.random.rand(my_fun.spatial_dimension, sample_size).T + rng = np.random.default_rng(rng_seed) + xx_1 = rng.random((sample_size, my_fun.spatial_dimension)) xx_1 = my_fun.transform_sample(xx_1, min_value=0.0, max_value=1.0) # Directly sample from the input property - np.random.seed(315) xx_2 = my_fun.prob_input.get_sample(sample_size) - # Assertion: two sampled values are equal + # Assertion: Both samples are equal because the seed is identical assert np.allclose(xx_1, xx_2) diff --git a/tests/core/prob_input/test_multivariate_input.py b/tests/core/prob_input/test_multivariate_input.py index 62a28ad..d48b5be 100644 --- a/tests/core/prob_input/test_multivariate_input.py +++ b/tests/core/prob_input/test_multivariate_input.py @@ -217,6 +217,24 @@ def test_repr_html(): assert my_multivariate_input._repr_html_() == str_ref +@pytest.mark.parametrize("spatial_dimension", [1, 2, 10, 100]) +def test_pass_random_seed(spatial_dimension): + """Test passing random seed to the constructor.""" + marginals = create_random_marginals(spatial_dimension) + + # Create two instances with an identical seed number + rng_seed = 42 + my_input_1 = ProbInput(marginals, rng_seed=rng_seed) + my_input_2 = ProbInput(marginals, rng_seed=rng_seed) + + # Generate sample points + xx_1 = my_input_1.get_sample(1000) + xx_2 = my_input_2.get_sample(1000) + + # Assertion: Both samples are identical because the seed is identical + assert np.allclose(xx_1, xx_2) + + # # def test_get_cdf_values(): # """Test the CDF values from an instance of UnivariateInput.""" diff --git a/tests/core/prob_input/test_univariate_input.py b/tests/core/prob_input/test_univariate_input.py index 9979a99..b51960b 100644 --- a/tests/core/prob_input/test_univariate_input.py +++ b/tests/core/prob_input/test_univariate_input.py @@ -216,3 +216,19 @@ def test_cdf_monotonously_increasing(univariate_input: Any) -> None: # Assertion assert np.all(yy_diff >= 0.0) + + +def test_pass_random_seed(): + """Test passing random seed to the constructor.""" + + # Create two instances with an identical seed number + rng_seed = 42 + my_input_1 = UnivDist("uniform", [0, 1], rng_seed=rng_seed) + my_input_2 = UnivDist("uniform", [0, 1], rng_seed=rng_seed) + + # Generate sample points + xx_1 = my_input_1.get_sample(1000) + xx_2 = my_input_2.get_sample(1000) + + # Assertion: Both samples are equal because the seed is identical + assert np.allclose(xx_1, xx_2) diff --git a/tests/core/test_uqtestfun.py b/tests/core/test_uqtestfun.py index c018c08..52ea75c 100644 --- a/tests/core/test_uqtestfun.py +++ b/tests/core/test_uqtestfun.py @@ -1,6 +1,7 @@ """ Test module for UQTestFun class, a generic class for generic UQ test function. """ +import numpy as np import pytest from uqtestfuns import UQTestFun, ProbInput @@ -82,3 +83,12 @@ def test_invalid_input(uqtestfun): with pytest.raises(TypeError): UQTestFun(**uqtestfun_dict) + + +def test_empty_uqtestfun(): + """Test creation of an empty UQTestFun instance.""" + my_fun = UQTestFun(lambda x: x) + + with pytest.raises(ValueError): + xx = np.random.rand(10) + my_fun.transform_sample(xx) From a8b625b0c471f2e37e1478666a40eb3485878876 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Thu, 1 Jun 2023 11:50:19 +0200 Subject: [PATCH 02/73] Add an implementation of the (first) Franke function - The (first) Franke function has been added as a test function to UQTestFuns. --- CHANGELOG.md | 2 + docs/_toc.yml | 2 + docs/development/setting-up-dev-env.md | 4 +- docs/fundamentals/metamodeling.md | 5 +- docs/references.bib | 22 ++++ docs/test-functions/available.md | 1 + docs/test-functions/franke-1.md | 152 ++++++++++++++++++++++ src/uqtestfuns/core/uqtestfun_abc.py | 7 - src/uqtestfuns/test_functions/__init__.py | 2 + src/uqtestfuns/test_functions/franke_1.py | 123 +++++++++++++++++ 10 files changed, 309 insertions(+), 11 deletions(-) create mode 100644 docs/test-functions/franke-1.md create mode 100644 src/uqtestfuns/test_functions/franke_1.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 4633e5e..406f2fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - An instance of NumPy random number generator is now attached to instances of `UnivDist` and `ProbInput`. The random seed number may be passed to the corresponding constructor for reproducibility. +- The (first) Franke function, relevant for metamodeling exercises, + is added as a UQ test function. ### Fixed diff --git a/docs/_toc.yml b/docs/_toc.yml index 273503b..b28a121 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -29,6 +29,8 @@ parts: title: Damped Oscillator - file: test-functions/flood title: Flood + - file: test-functions/franke-1 + title: (First) Franke - file: test-functions/ishigami title: Ishigami - file: test-functions/oakley-ohagan-1d diff --git a/docs/development/setting-up-dev-env.md b/docs/development/setting-up-dev-env.md index 84dcd20..ffe2f42 100644 --- a/docs/development/setting-up-dev-env.md +++ b/docs/development/setting-up-dev-env.md @@ -105,7 +105,7 @@ a popular open-source package and environment management system. We assume that all the example commands given below are executed from the UQTestFuns root source directory. -#### Using `venv` +### Using `venv` Since v3.5, Python includes `venv` module to create and manage virtual environments. To set up a virtual environment using the `venv` module: @@ -127,7 +127,7 @@ To set up a virtual environment using the `venv` module: $ deactivate ``` -#### Using `conda` +### Using `conda` You may also create a virtual environment via [`conda`](https://conda.io/projects/conda/en/latest/index.html) which is included in the [Anaconda distribution of Python](https://www.anaconda.com/). diff --git a/docs/fundamentals/metamodeling.md b/docs/fundamentals/metamodeling.md index f22f73e..4704c4b 100644 --- a/docs/fundamentals/metamodeling.md +++ b/docs/fundamentals/metamodeling.md @@ -20,9 +20,10 @@ in the comparison of metamodeling approaches. | Name | Spatial Dimension | Constructor | |:-----------------------------------------------------------:|:-----------------:|:--------------------:| | {ref}`Ackley ` | M | `Ackley()` | -| {ref}`Borehole ` | 8 | `Borehole()` | +| {ref}`Borehole ` | 8 | `Borehole()` | | {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | -| {ref}`Flood ` | 8 | `Flood()` | +| {ref}`Flood ` | 8 | `Flood()` | +| {ref}`(First) Franke ` | 2 | `Franke1` | | {ref}`Oakley-O'Hagan 1D ` | 1 | `OakleyOHagan1D()` | | {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | | {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | diff --git a/docs/references.bib b/docs/references.bib index 2e3727a..23d075c 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -411,4 +411,26 @@ @InBook{Dixon1978 booktitle = {Towards global optimization 2}, } +@Article{Haaland2011, + author = {Ben Haaland and Peter Z. G. Qian}, + journal = {The Annals of Statistics}, + title = {Accurate emulators for large-scale computer experiments}, + year = {2011}, + number = {6}, + pages = {2974--3002}, + volume = {39}, + doi = {10.1214/11-aos929}, +} + +@TechReport{Franke1979, + author = {Richard Franke}, + institution = {Naval Postgraduate School}, + title = {A critical comparison of some methods for interpolation of scattered data}, + year = {1979}, + address = {Monterey, Canada}, + number = {NPS53-79-003}, + type = {techreport}, + url = {https://core.ac.uk/reader/36727660}, +} + @Comment{jabref-meta: databaseType:bibtex;} diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index 806971b..a0a6234 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -24,6 +24,7 @@ available in the current UQTestFuns, regardless of their typical applications. | {ref}`Borehole ` | 8 | `Borehole()` | | {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | | {ref}`Flood ` | 8 | `Flood()` | +| {ref}`(First) Franke ` | 2 | `Franke1()` | | {ref}`Ishigami ` | 3 | `Ishigami()` | | {ref}`Oakley-O'Hagan 1D ` | 1 | `OakleyOHagan1D()` | | {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | diff --git a/docs/test-functions/franke-1.md b/docs/test-functions/franke-1.md new file mode 100644 index 0000000..7ec6e3c --- /dev/null +++ b/docs/test-functions/franke-1.md @@ -0,0 +1,152 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:franke-1)= +# (First) Franke Function + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +The (first) Franke function is a two-dimensional scalar-valued function. +The function was first introduced in {cite}`Franke1979` in the context of +interpolation problem and was used in {cite}`Haaland2011` in the context of +metamodeling. + +The Franke's original report {cite}`Franke1979` contains in total +six two-dimensional test functions. +The first function that appeared is commonly known simply as +the "Franke function". + +```{code-cell} ipython3 +:tags: [remove-input] + +from mpl_toolkits.axes_grid1 import make_axes_locatable + +my_fun = uqtf.Franke1() + +# --- Create 2D data +xx_1d = np.linspace(0.0, 1.0, 1000)[:, np.newaxis] +mesh_2d = np.meshgrid(xx_1d, xx_1d) +xx_2d = np.array(mesh_2d).T.reshape(-1, 2) +yy_2d = my_fun(xx_2d) + +# --- Create two-dimensional plots +fig = plt.figure(figsize=(10, 5)) + +# Surface +axs_1 = plt.subplot(121, projection='3d') +axs_1.plot_surface( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000,1000), + linewidth=0, + cmap="plasma", + antialiased=False, + alpha=0.5 +) +axs_1.set_xlabel("$x_1$", fontsize=14) +axs_1.set_ylabel("$x_2$", fontsize=14) +axs_1.set_zlabel("$\mathcal{M}(x_1, x_2)$", fontsize=14) +axs_1.set_title("Surface plot of (1st) Franke", fontsize=14) + +# Contour +axs_2 = plt.subplot(122) +cf = axs_2.contourf( + mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000), cmap="plasma" +) +axs_2.set_xlabel("$x_1$", fontsize=14) +axs_2.set_ylabel("$x_2$", fontsize=14) +axs_2.set_title("Contour plot of (1st) Franke", fontsize=14) +divider = make_axes_locatable(axs_2) +cax = divider.append_axes('right', size='5%', pad=0.05) +fig.colorbar(cf, cax=cax, orientation='vertical') +axs_2.axis('scaled') + +fig.tight_layout(pad=3.0) +plt.gcf().set_dpi(75); +``` + +As shown in the plots above, the surface consists of two Gaussian peaks and +a Gaussian dip on a surface sloping down toward the upper right boundary +(i.e., $[1.0, 1.0]$). + +## Test function instance + +To create a default instance of the Franke function: + +```{code-cell} ipython3 +my_testfun = uqtf.Franke1() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +## Description + +The Franke function is defined as follows: + +$$ +\begin{align} + \mathcal{M}(\boldsymbol{x}) = & 0.75 \exp{\left( -0.25 \left( (x_1 - 2)^2 + (x_2 - 2)^2 \right) \right) } \\ + & + 0.75 \exp{\left( -1.00 \left( \frac{(x_1 + 1)^2}{49} + \frac{(x_2 + 1)^2}{10} \right) \right)} \\ + & + 0.50 \exp{\left( -0.25 \left( (x_1 - 7)^2 + (x_2 - 3)^2 \right) \right)} \\ + & - 0.20 \exp{\left( -1.00 \left( (x_1 - 4)^2 + (x_2 - 7)^2 \right) \right)} \\ +\end{align} +$$ +where $\boldsymbol{x} = \{ x_1, x_2 \}$ +is the two-dimensional vector of input variables further defined below. + +## Probabilistic input + +Based on {cite}`Franke1979`, the probabilistic input model +for the function consists of two independent random variables as shown below. + +```{code-cell} ipython3 +my_testfun.prob_input +``` + +## Reference results + +This section provides several reference results of typical UQ analyses involving +the test function. + +### Sample histogram + +Shown below is the histogram of the output based on $100'000$ random points: + +```{code-cell} ipython3 +:tags: [hide-input] + +xx_test = my_testfun.prob_input.get_sample(100000) +yy_test = my_testfun(xx_test) + +plt.hist(yy_test, bins="auto", color="#8da0cb"); +plt.grid(); +plt.ylabel("Counts [-]"); +plt.xlabel("$\mathcal{M}(\mathbf{X})$"); +plt.gcf().set_dpi(150); +``` + +## References + +```{bibliography} +:style: plain +:filter: docname in docnames +``` \ No newline at end of file diff --git a/src/uqtestfuns/core/uqtestfun_abc.py b/src/uqtestfuns/core/uqtestfun_abc.py index ec1cbd4..d3f67ca 100644 --- a/src/uqtestfuns/core/uqtestfun_abc.py +++ b/src/uqtestfuns/core/uqtestfun_abc.py @@ -47,13 +47,6 @@ class UQTestFunABC(abc.ABC): default_spatial_dimension : Optional[int] The default spatial dimension of a UQ test function. If 'None' then the function is a variable dimensional test function. - spatial_dimension : int - The number of spatial dimension (i.e., input variables) to the UQ test - function. This number is derived directly from ``prob_input``. - evaluate : Callable - Implementation of a UQ test function as a Callable. - Note that when calling an instance of the class on a set of input - values, the input values are first verified before evaluating them. """ def __init__( diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index be55b71..80e86e6 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -4,6 +4,7 @@ from .ackley import Ackley from .borehole import Borehole from .damped_oscillator import DampedOscillator +from .franke_1 import Franke1 from .flood import Flood from .ishigami import Ishigami from .oakley_ohagan_1d import OakleyOHagan1D @@ -21,6 +22,7 @@ "Borehole", "DampedOscillator", "Flood", + "Franke1", "Ishigami", "OakleyOHagan1D", "OTLCircuit", diff --git a/src/uqtestfuns/test_functions/franke_1.py b/src/uqtestfuns/test_functions/franke_1.py new file mode 100644 index 0000000..0d62b2a --- /dev/null +++ b/src/uqtestfuns/test_functions/franke_1.py @@ -0,0 +1,123 @@ +""" +Module with an implementation of the (first) Franke function. + +The (first) Franke function is a two-dimensional scalar-valued function. +The function was first introduced in [1] in the context of interpolation +problem and was used in the context of metamodeling in [2]. + +The Franke's original report [1] contains in total six two-dimensional test +functions. The first function that appeared is commonly known as the +"Franke function". + +References +---------- + +1. Richard Franke, "A critical comparison of some methods for interpolation + of scattered data," Naval Postgraduate School, Monterey, Canada, + Technical Report No. NPS53-79-003, 1979. + URL: https://core.ac.uk/reader/36727660 +2. Ben Haaland and Peter Z. G. Qian, “Accurate emulators for large-scale + computer experiments,” The Annals of Statistics, vol. 39, no. 6, + pp. 2974-3002, 2011. DOI: 10.1214/11-AOS929 +""" +import numpy as np + +from typing import Optional + +from ..core.prob_input.univariate_distribution import UnivDist +from ..core.uqtestfun_abc import UQTestFunABC +from .available import create_prob_input_from_available + +__all__ = ["Franke1"] + +INPUT_MARGINALS_FRANKE1979 = [ # From Ref. [1] + UnivDist( + name="X1", + distribution="uniform", + parameters=[0.0, 1.0], + ), + UnivDist( + name="X2", + distribution="uniform", + parameters=[0.0, 1.0], + ), +] + +AVAILABLE_INPUT_SPECS = { + "Franke1979": { + "name": "Franke-1979", + "description": ( + "Input specification for the (first) Franke function " + "from Franke (1979)." + ), + "marginals": INPUT_MARGINALS_FRANKE1979, + "copulas": None, + } +} + +DEFAULT_INPUT_SELECTION = "Franke1979" + + +class Franke1(UQTestFunABC): + """A concrete implementation of the (first) Franke function.""" + + _TAGS = ["metamodeling"] + + _AVAILABLE_INPUTS = tuple(AVAILABLE_INPUT_SPECS.keys()) + + _AVAILABLE_PARAMETERS = None + + _DEFAULT_SPATIAL_DIMENSION = 2 + + _DESCRIPTION = "(First) Franke function from Franke (1979)" + + def __init__( + self, + *, + prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, + name: Optional[str] = None, + rng_seed_prob_input: Optional[int] = None, + ): + # --- Arguments processing + prob_input = create_prob_input_from_available( + prob_input_selection, + AVAILABLE_INPUT_SPECS, + rng_seed=rng_seed_prob_input, + ) + # Process the default name + if name is None: + name = Franke1.__name__ + + super().__init__(prob_input=prob_input, name=name) + + def evaluate(self, xx: np.ndarray): + """Evaluate the (first) Franke function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + Two-Dimensional input values given by N-by-2 arrays where + N is the number of input values. + + Returns + ------- + np.ndarray + The output of the (first) Franke function evaluated + on the input values. + The output is a 1-dimensional array of length N. + """ + + xx0 = 9 * xx[:, 0] + xx1 = 9 * xx[:, 1] + + # Compute the (first) Franke function + term_1 = 0.75 * np.exp(-0.25 * ((xx0 - 2) ** 2 + (xx1 - 2) ** 2)) + term_2 = 0.75 * np.exp( + -1.00 * ((xx0 + 1) ** 2 / 49.0 + (xx1 + 1) ** 2 / 10.0) + ) + term_3 = 0.50 * np.exp(-0.25 * ((xx0 - 7) ** 2 + (xx1 - 3) ** 2)) + term_4 = 0.20 * np.exp(-1.00 * ((xx0 - 4) ** 2 + (xx1 - 7) ** 2)) + + yy = term_1 + term_2 + term_3 - term_4 + + return yy From ad9e73a932e5643b915df0b10dbb9056a9db34af Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Thu, 1 Jun 2023 13:29:34 +0200 Subject: [PATCH 03/73] Add an implementation of the (third) Franke function - The (third) Franke function has been added as a test function to UQTestFuns. The function is a two-dimensional function that features a saddle shaped surface. - Fix wrong entries in the CHANGELOG.md. --- CHANGELOG.md | 10 +- docs/_toc.yml | 2 + docs/fundamentals/metamodeling.md | 3 +- docs/test-functions/available.md | 3 +- docs/test-functions/franke-1.md | 6 +- docs/test-functions/franke-3.md | 147 ++++++++++++++++++++++ src/uqtestfuns/test_functions/__init__.py | 2 + src/uqtestfuns/test_functions/franke_3.py | 112 +++++++++++++++++ 8 files changed, 276 insertions(+), 9 deletions(-) create mode 100644 docs/test-functions/franke-3.md create mode 100644 src/uqtestfuns/test_functions/franke_3.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 406f2fe..a0c4465 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,15 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [0.1.1] - 2023-07-03 - ### Added - An instance of NumPy random number generator is now attached to instances of `UnivDist` and `ProbInput`. The random seed number may be passed to the corresponding constructor for reproducibility. -- The (first) Franke function, relevant for metamodeling exercises, - is added as a UQ test function. +- The two-dimensional (first) Franke function, relevant for metamodeling + exercises, is added as a UQ test function. +- The two-dimensional (third) Franke function, relevant for metamodeling + exercises, is added as a UQ test function. + +## [0.1.1] - 2023-07-03 ### Fixed diff --git a/docs/_toc.yml b/docs/_toc.yml index b28a121..d0f3757 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -31,6 +31,8 @@ parts: title: Flood - file: test-functions/franke-1 title: (First) Franke + - file: test-functions/franke-3 + title: (Third) Franke - file: test-functions/ishigami title: Ishigami - file: test-functions/oakley-ohagan-1d diff --git a/docs/fundamentals/metamodeling.md b/docs/fundamentals/metamodeling.md index 4704c4b..15dcb7a 100644 --- a/docs/fundamentals/metamodeling.md +++ b/docs/fundamentals/metamodeling.md @@ -23,7 +23,8 @@ in the comparison of metamodeling approaches. | {ref}`Borehole ` | 8 | `Borehole()` | | {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | | {ref}`Flood ` | 8 | `Flood()` | -| {ref}`(First) Franke ` | 2 | `Franke1` | +| {ref}`(1st) Franke ` | 2 | `Franke1()` | +| {ref}`(3rd) Franke ` | 2 | `Franke3()` | | {ref}`Oakley-O'Hagan 1D ` | 1 | `OakleyOHagan1D()` | | {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | | {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index a0a6234..bddaaf4 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -24,7 +24,8 @@ available in the current UQTestFuns, regardless of their typical applications. | {ref}`Borehole ` | 8 | `Borehole()` | | {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | | {ref}`Flood ` | 8 | `Flood()` | -| {ref}`(First) Franke ` | 2 | `Franke1()` | +| {ref}`(1st) Franke ` | 2 | `Franke1()` | +| {ref}`(3rd) Franke ` | 2 | `Franke3()` | | {ref}`Ishigami ` | 3 | `Ishigami()` | | {ref}`Oakley-O'Hagan 1D ` | 1 | `OakleyOHagan1D()` | | {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | diff --git a/docs/test-functions/franke-1.md b/docs/test-functions/franke-1.md index 7ec6e3c..7de3111 100644 --- a/docs/test-functions/franke-1.md +++ b/docs/test-functions/franke-1.md @@ -13,7 +13,7 @@ kernelspec: --- (test-functions:franke-1)= -# (First) Franke Function +# (1st) Franke Function ```{code-cell} ipython3 import numpy as np @@ -28,8 +28,8 @@ metamodeling. The Franke's original report {cite}`Franke1979` contains in total six two-dimensional test functions. -The first function that appeared is commonly known simply as -the "Franke function". +The first function that appeared in the report is commonly known simply as +the "Franke function" (without further specification). ```{code-cell} ipython3 :tags: [remove-input] diff --git a/docs/test-functions/franke-3.md b/docs/test-functions/franke-3.md new file mode 100644 index 0000000..66f31e0 --- /dev/null +++ b/docs/test-functions/franke-3.md @@ -0,0 +1,147 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:franke-3)= +# (3rd) Franke Function + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +The (third) Franke function is a two-dimensional scalar-valued function. +The function was first introduced in {cite}`Franke1979` in the context of +interpolation problem. + +```{note} +The Franke's original report {cite}`Franke1979` contains in total +six two-dimensional test functions. +The first function that appeared in the report is commonly known simply as +the "{ref}`Franke function `" (without further +specification). +``` + +```{code-cell} ipython3 +:tags: [remove-input] + +from mpl_toolkits.axes_grid1 import make_axes_locatable + +my_fun = uqtf.Franke3() + +# --- Create 2D data +xx_1d = np.linspace(0.0, 1.0, 1000)[:, np.newaxis] +mesh_2d = np.meshgrid(xx_1d, xx_1d) +xx_2d = np.array(mesh_2d).T.reshape(-1, 2) +yy_2d = my_fun(xx_2d) + +# --- Create two-dimensional plots +fig = plt.figure(figsize=(10, 5)) + +# Surface +axs_1 = plt.subplot(121, projection='3d') +axs_1.plot_surface( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000,1000), + linewidth=0, + cmap="plasma", + antialiased=False, + alpha=0.5 +) +axs_1.set_xlabel("$x_1$", fontsize=14) +axs_1.set_ylabel("$x_2$", fontsize=14) +axs_1.set_zlabel("$\mathcal{M}(x_1, x_2)$", fontsize=14) +axs_1.set_title("Surface plot of (3rd) Franke", fontsize=14) + +# Contour +axs_2 = plt.subplot(122) +cf = axs_2.contourf( + mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000), cmap="plasma" +) +axs_2.set_xlabel("$x_1$", fontsize=14) +axs_2.set_ylabel("$x_2$", fontsize=14) +axs_2.set_title("Contour plot of (3rd) Franke", fontsize=14) +divider = make_axes_locatable(axs_2) +cax = divider.append_axes('right', size='5%', pad=0.05) +fig.colorbar(cf, cax=cax, orientation='vertical') +axs_2.axis('scaled') + +fig.tight_layout(pad=3.0) +plt.gcf().set_dpi(75); +``` + +As shown in the plots above, the function features a saddle shaped surface. + +## Test function instance + +To create a default instance of the (third) Franke function: + +```{code-cell} ipython3 +my_testfun = uqtf.Franke3() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +## Description + +The Franke function is defined as follows: + +$$ +\mathcal{M}(\boldsymbol{x}) = \frac{1.25 + \cos{(5.4 x_2)}}{6 (1 + (3 x_1 - 1)^2)} +$$ +where $\boldsymbol{x} = \{ x_1, x_2 \}$ +is the two-dimensional vector of input variables further defined below. + +## Probabilistic input + +Based on {cite}`Franke1979`, the probabilistic input model +for the function consists of two independent random variables as shown below. + +```{code-cell} ipython3 +my_testfun.prob_input +``` + +## Reference results + +This section provides several reference results of typical UQ analyses involving +the test function. + +### Sample histogram + +Shown below is the histogram of the output based on $100'000$ random points: + +```{code-cell} ipython3 +:tags: [hide-input] + +xx_test = my_testfun.prob_input.get_sample(100000) +yy_test = my_testfun(xx_test) + +plt.hist(yy_test, bins="auto", color="#8da0cb"); +plt.grid(); +plt.ylabel("Counts [-]"); +plt.xlabel("$\mathcal{M}(\mathbf{X})$"); +plt.gcf().set_dpi(150); +``` + +## References + +```{bibliography} +:style: plain +:filter: docname in docnames +``` \ No newline at end of file diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index 80e86e6..ef5996b 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -5,6 +5,7 @@ from .borehole import Borehole from .damped_oscillator import DampedOscillator from .franke_1 import Franke1 +from .franke_3 import Franke3 from .flood import Flood from .ishigami import Ishigami from .oakley_ohagan_1d import OakleyOHagan1D @@ -23,6 +24,7 @@ "DampedOscillator", "Flood", "Franke1", + "Franke3", "Ishigami", "OakleyOHagan1D", "OTLCircuit", diff --git a/src/uqtestfuns/test_functions/franke_3.py b/src/uqtestfuns/test_functions/franke_3.py new file mode 100644 index 0000000..50339c5 --- /dev/null +++ b/src/uqtestfuns/test_functions/franke_3.py @@ -0,0 +1,112 @@ +""" +Module with an implementation of the (third) Franke function. + +The (third) Franke function is a two-dimensional scalar-valued function. +The function was first introduced in [1] in the context of interpolation +problem. It features a saddle shaped surface. + +The Franke's original report [1] contains in total six two-dimensional test +functions. The first function that appeared is commonly known as the +"Franke function". + +References +---------- + +1. Richard Franke, "A critical comparison of some methods for interpolation + of scattered data," Naval Postgraduate School, Monterey, Canada, + Technical Report No. NPS53-79-003, 1979. + URL: https://core.ac.uk/reader/36727660 +""" +import numpy as np + +from typing import Optional + +from ..core.prob_input.univariate_distribution import UnivDist +from ..core.uqtestfun_abc import UQTestFunABC +from .available import create_prob_input_from_available + +__all__ = ["Franke3"] + +INPUT_MARGINALS_FRANKE1979 = [ # From Ref. [1] + UnivDist( + name="X1", + distribution="uniform", + parameters=[0.0, 1.0], + ), + UnivDist( + name="X2", + distribution="uniform", + parameters=[0.0, 1.0], + ), +] + +AVAILABLE_INPUT_SPECS = { + "Franke1979": { + "name": "Franke-1979", + "description": ( + "Input specification for the (third) Franke function " + "from Franke (1979)." + ), + "marginals": INPUT_MARGINALS_FRANKE1979, + "copulas": None, + } +} + +DEFAULT_INPUT_SELECTION = "Franke1979" + + +class Franke3(UQTestFunABC): + """A concrete implementation of the (first) Franke function.""" + + _TAGS = ["metamodeling"] + + _AVAILABLE_INPUTS = tuple(AVAILABLE_INPUT_SPECS.keys()) + + _AVAILABLE_PARAMETERS = None + + _DEFAULT_SPATIAL_DIMENSION = 2 + + _DESCRIPTION = "(Third) Franke function from Franke (1979)" + + def __init__( + self, + *, + prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, + name: Optional[str] = None, + rng_seed_prob_input: Optional[int] = None, + ): + # --- Arguments processing + prob_input = create_prob_input_from_available( + prob_input_selection, + AVAILABLE_INPUT_SPECS, + rng_seed=rng_seed_prob_input, + ) + # Process the default name + if name is None: + name = Franke3.__name__ + + super().__init__(prob_input=prob_input, name=name) + + def evaluate(self, xx: np.ndarray): + """Evaluate the (third) Franke function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + Two-Dimensional input values given by N-by-2 arrays where + N is the number of input values. + + Returns + ------- + np.ndarray + The output of the (third) Franke function evaluated + on the input values. + The output is a 1-dimensional array of length N. + """ + # Compute the (third) Franke function + term_1 = 1.25 + np.cos(5.4 * xx[:, 1]) + term_2 = 6 * (1 + (3 * xx[:, 0] - 1) ** 2) + + yy = term_1 / term_2 + + return yy From 1bc5807ea875223cc3315539d4f3e420197ce7f0 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Thu, 1 Jun 2023 14:26:01 +0200 Subject: [PATCH 04/73] Add an implementation of the (second) Franke function - The (second) Franke function has been added as a test function to UQTestFuns. The function is a two-dimensional function that features a sharp (albeit continuous) rise. --- CHANGELOG.md | 2 + docs/_toc.yml | 6 +- docs/fundamentals/metamodeling.md | 1 + docs/references.bib | 11 ++ docs/test-functions/available.md | 1 + docs/test-functions/franke-1.md | 4 +- docs/test-functions/franke-2.md | 151 ++++++++++++++++++++++ docs/test-functions/franke-3.md | 2 +- src/uqtestfuns/test_functions/__init__.py | 2 + src/uqtestfuns/test_functions/franke_1.py | 4 +- src/uqtestfuns/test_functions/franke_2.py | 114 ++++++++++++++++ src/uqtestfuns/test_functions/franke_3.py | 4 +- 12 files changed, 293 insertions(+), 9 deletions(-) create mode 100644 docs/test-functions/franke-2.md create mode 100644 src/uqtestfuns/test_functions/franke_2.py diff --git a/CHANGELOG.md b/CHANGELOG.md index a0c4465..f3376a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 number may be passed to the corresponding constructor for reproducibility. - The two-dimensional (first) Franke function, relevant for metamodeling exercises, is added as a UQ test function. +- The two-dimensional (second) Franke function, relevant for metamodeling + exercises, is added as a UQ test function. - The two-dimensional (third) Franke function, relevant for metamodeling exercises, is added as a UQ test function. diff --git a/docs/_toc.yml b/docs/_toc.yml index d0f3757..d2b44d7 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -30,9 +30,11 @@ parts: - file: test-functions/flood title: Flood - file: test-functions/franke-1 - title: (First) Franke + title: (1st) Franke + - file: test-functions/franke-2 + title: (2nd) Franke - file: test-functions/franke-3 - title: (Third) Franke + title: (3rd) Franke - file: test-functions/ishigami title: Ishigami - file: test-functions/oakley-ohagan-1d diff --git a/docs/fundamentals/metamodeling.md b/docs/fundamentals/metamodeling.md index 15dcb7a..342287f 100644 --- a/docs/fundamentals/metamodeling.md +++ b/docs/fundamentals/metamodeling.md @@ -24,6 +24,7 @@ in the comparison of metamodeling approaches. | {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | | {ref}`Flood ` | 8 | `Flood()` | | {ref}`(1st) Franke ` | 2 | `Franke1()` | +| {ref}`(2nd) Franke ` | 2 | `Franke2()` | | {ref}`(3rd) Franke ` | 2 | `Franke3()` | | {ref}`Oakley-O'Hagan 1D ` | 1 | `OakleyOHagan1D()` | | {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | diff --git a/docs/references.bib b/docs/references.bib index 23d075c..2dd3f58 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -433,4 +433,15 @@ @TechReport{Franke1979 url = {https://core.ac.uk/reader/36727660}, } +@Article{McLain1974, + author = {D. H. McLain}, + journal = {The Computer Journal}, + title = {Drawing contours from arbitrary data points}, + year = {1974}, + number = {4}, + pages = {318--324}, + volume = {17}, + doi = {10.1093/comjnl/17.4.318}, +} + @Comment{jabref-meta: databaseType:bibtex;} diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index bddaaf4..f42506c 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -25,6 +25,7 @@ available in the current UQTestFuns, regardless of their typical applications. | {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | | {ref}`Flood ` | 8 | `Flood()` | | {ref}`(1st) Franke ` | 2 | `Franke1()` | +| {ref}`(2nd) Franke ` | 2 | `Franke2()` | | {ref}`(3rd) Franke ` | 2 | `Franke3()` | | {ref}`Ishigami ` | 3 | `Ishigami()` | | {ref}`Oakley-O'Hagan 1D ` | 1 | `OakleyOHagan1D()` | diff --git a/docs/test-functions/franke-1.md b/docs/test-functions/franke-1.md index 7de3111..e8ff933 100644 --- a/docs/test-functions/franke-1.md +++ b/docs/test-functions/franke-1.md @@ -76,13 +76,13 @@ cax = divider.append_axes('right', size='5%', pad=0.05) fig.colorbar(cf, cax=cax, orientation='vertical') axs_2.axis('scaled') -fig.tight_layout(pad=3.0) +fig.tight_layout(pad=4.0) plt.gcf().set_dpi(75); ``` As shown in the plots above, the surface consists of two Gaussian peaks and a Gaussian dip on a surface sloping down toward the upper right boundary -(i.e., $[1.0, 1.0]$). +(i.e., $(1.0, 1.0)$). ## Test function instance diff --git a/docs/test-functions/franke-2.md b/docs/test-functions/franke-2.md new file mode 100644 index 0000000..646ab86 --- /dev/null +++ b/docs/test-functions/franke-2.md @@ -0,0 +1,151 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:franke-2)= +# (2nd) Franke Function + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +The (second) Franke function is a two-dimensional scalar-valued function. +The function was introduced in {cite}`Franke1979` in the context of +interpolation problem. It is based on a similar function used in +{cite}`McLain1974` (specifically, the function S5) also in the context of +interpolation problem albeit in a modified form. + +```{note} +The Franke's original report {cite}`Franke1979` contains in total +six two-dimensional test functions. +The first function that appeared in the report is commonly known simply as +the "Franke function" (without further specification). +``` + +```{code-cell} ipython3 +:tags: [remove-input] + +from mpl_toolkits.axes_grid1 import make_axes_locatable + +my_fun = uqtf.Franke2() + +# --- Create 2D data +xx_1d = np.linspace(0.0, 1.0, 1000)[:, np.newaxis] +mesh_2d = np.meshgrid(xx_1d, xx_1d) +xx_2d = np.array(mesh_2d).T.reshape(-1, 2) +yy_2d = my_fun(xx_2d) + +# --- Create two-dimensional plots +fig = plt.figure(figsize=(10, 5)) + +# Surface +axs_1 = plt.subplot(121, projection='3d') +axs_1.plot_surface( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000,1000), + linewidth=0, + cmap="plasma", + antialiased=False, + alpha=0.5 +) +axs_1.set_xlabel("$x_1$", fontsize=14) +axs_1.set_ylabel("$x_2$", fontsize=14) +axs_1.set_zlabel("$\mathcal{M}(x_1, x_2)$", fontsize=14) +axs_1.set_title("Surface plot of (2nd) Franke", fontsize=14) + +# Contour +axs_2 = plt.subplot(122) +cf = axs_2.contourf( + mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000), cmap="plasma" +) +axs_2.set_xlabel("$x_1$", fontsize=14) +axs_2.set_ylabel("$x_2$", fontsize=14) +axs_2.set_title("Contour plot of (2nd) Franke", fontsize=14) +divider = make_axes_locatable(axs_2) +cax = divider.append_axes('right', size='5%', pad=0.05) +fig.colorbar(cf, cax=cax, orientation='vertical') +axs_2.axis('scaled') + +fig.tight_layout(pad=4.0) +plt.gcf().set_dpi(75); +``` + +As shown in the plots above, the function features two nearly flat regions +of height $0.0$ and (approximately) $\frac{2}{9}$. +The two regions are joined by a sharp rise that runs diagonally from +$(0.0, 0.0)$ to $(1.0, 1.0)$. + +## Test function instance + +To create a default instance of the (second) Franke function: + +```{code-cell} ipython3 +my_testfun = uqtf.Franke2() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +## Description + +The (2nd) Franke function is defined as follows: + +$$ +\mathcal{M}(\boldsymbol{x}) = \frac{1}{9} \left( \tanh{(9 (x_2 - x_1))} + 1 \right) +$$ +where $\boldsymbol{x} = \{ x_1, x_2 \}$ +is the two-dimensional vector of input variables further defined below. + +## Probabilistic input + +Based on {cite}`Franke1979`, the probabilistic input model +for the function consists of two independent random variables as shown below. + +```{code-cell} ipython3 +my_testfun.prob_input +``` + +## Reference results + +This section provides several reference results of typical UQ analyses involving +the test function. + +### Sample histogram + +Shown below is the histogram of the output based on $100'000$ random points: + +```{code-cell} ipython3 +:tags: [hide-input] + +xx_test = my_testfun.prob_input.get_sample(100000) +yy_test = my_testfun(xx_test) + +plt.hist(yy_test, bins="auto", color="#8da0cb"); +plt.grid(); +plt.ylabel("Counts [-]"); +plt.xlabel("$\mathcal{M}(\mathbf{X})$"); +plt.gcf().set_dpi(150); +``` + +## References + +```{bibliography} +:style: plain +:filter: docname in docnames +``` \ No newline at end of file diff --git a/docs/test-functions/franke-3.md b/docs/test-functions/franke-3.md index 66f31e0..f2c2270 100644 --- a/docs/test-functions/franke-3.md +++ b/docs/test-functions/franke-3.md @@ -78,7 +78,7 @@ cax = divider.append_axes('right', size='5%', pad=0.05) fig.colorbar(cf, cax=cax, orientation='vertical') axs_2.axis('scaled') -fig.tight_layout(pad=3.0) +fig.tight_layout(pad=4.0) plt.gcf().set_dpi(75); ``` diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index ef5996b..316820d 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -5,6 +5,7 @@ from .borehole import Borehole from .damped_oscillator import DampedOscillator from .franke_1 import Franke1 +from .franke_2 import Franke2 from .franke_3 import Franke3 from .flood import Flood from .ishigami import Ishigami @@ -24,6 +25,7 @@ "DampedOscillator", "Flood", "Franke1", + "Franke2", "Franke3", "Ishigami", "OakleyOHagan1D", diff --git a/src/uqtestfuns/test_functions/franke_1.py b/src/uqtestfuns/test_functions/franke_1.py index 0d62b2a..5274f59 100644 --- a/src/uqtestfuns/test_functions/franke_1.py +++ b/src/uqtestfuns/test_functions/franke_1.py @@ -6,8 +6,8 @@ problem and was used in the context of metamodeling in [2]. The Franke's original report [1] contains in total six two-dimensional test -functions. The first function that appeared is commonly known as the -"Franke function". +functions. The first function that appeared in the report is commonly known +as the "Franke function". References ---------- diff --git a/src/uqtestfuns/test_functions/franke_2.py b/src/uqtestfuns/test_functions/franke_2.py new file mode 100644 index 0000000..1a2fb78 --- /dev/null +++ b/src/uqtestfuns/test_functions/franke_2.py @@ -0,0 +1,114 @@ +""" +Module with an implementation of the (second) Franke function. + +The (second) Franke function is a two-dimensional scalar-valued function. +The function was introduced in [1] in the context of interpolation +problem. The function was adapted from the test function S5 in [2] by +translating its domain and modifying the function slightly to +"enchance the visual aspects of the surface". + +The Franke's original report [1] contains in total six two-dimensional test +functions. The first function that appeared in the report is commonly known +as the "Franke function". + +References +---------- + +1. Richard Franke, "A critical comparison of some methods for interpolation + of scattered data," Naval Postgraduate School, Monterey, Canada, + Technical Report No. NPS53-79-003, 1979. + URL: https://core.ac.uk/reader/36727660 +2. D. H. McLain, "Drawing contours from arbitrary data points," The Computer + Journal, vol. 17, no. 4, pp. 318-324, 1974. + DOI: 10.1093/comjnl/17.4.318 +""" +import numpy as np + +from typing import Optional + +from ..core.prob_input.univariate_distribution import UnivDist +from ..core.uqtestfun_abc import UQTestFunABC +from .available import create_prob_input_from_available + +__all__ = ["Franke2"] + +INPUT_MARGINALS_FRANKE1979 = [ # From Ref. [1] + UnivDist( + name="X1", + distribution="uniform", + parameters=[0.0, 1.0], + ), + UnivDist( + name="X2", + distribution="uniform", + parameters=[0.0, 1.0], + ), +] + +AVAILABLE_INPUT_SPECS = { + "Franke1979": { + "name": "Franke-1979", + "description": ( + "Input specification for the (second) Franke function " + "from Franke (1979)." + ), + "marginals": INPUT_MARGINALS_FRANKE1979, + "copulas": None, + } +} + +DEFAULT_INPUT_SELECTION = "Franke1979" + + +class Franke2(UQTestFunABC): + """A concrete implementation of the (second) Franke function.""" + + _TAGS = ["metamodeling"] + + _AVAILABLE_INPUTS = tuple(AVAILABLE_INPUT_SPECS.keys()) + + _AVAILABLE_PARAMETERS = None + + _DEFAULT_SPATIAL_DIMENSION = 2 + + _DESCRIPTION = "(Second) Franke function from Franke (1979)" + + def __init__( + self, + *, + prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, + name: Optional[str] = None, + rng_seed_prob_input: Optional[int] = None, + ): + # --- Arguments processing + prob_input = create_prob_input_from_available( + prob_input_selection, + AVAILABLE_INPUT_SPECS, + rng_seed=rng_seed_prob_input, + ) + # Process the default name + if name is None: + name = Franke2.__name__ + + super().__init__(prob_input=prob_input, name=name) + + def evaluate(self, xx: np.ndarray): + """Evaluate the (second) Franke function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + Two-Dimensional input values given by N-by-2 arrays where + N is the number of input values. + + Returns + ------- + np.ndarray + The output of the (second) Franke function evaluated + on the input values. + The output is a 1-dimensional array of length N. + """ + # Compute the (second) Franke function + yy = (np.tanh(9 * (xx[:, 1] - xx[:, 0])) + 1) / 9.0 + + return yy diff --git a/src/uqtestfuns/test_functions/franke_3.py b/src/uqtestfuns/test_functions/franke_3.py index 50339c5..598fd9a 100644 --- a/src/uqtestfuns/test_functions/franke_3.py +++ b/src/uqtestfuns/test_functions/franke_3.py @@ -6,8 +6,8 @@ problem. It features a saddle shaped surface. The Franke's original report [1] contains in total six two-dimensional test -functions. The first function that appeared is commonly known as the -"Franke function". +functions. The first function that appeared in the report is commonly known +as the "Franke function". References ---------- From 7b327e102fc69c7a86dbcd4a8968a444f0e4298a Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Thu, 1 Jun 2023 16:38:22 +0200 Subject: [PATCH 05/73] Add an implementation of the McLain S5 function - The function is a two-dimensional function that features a sharp (albeit continuous) rise. --- CHANGELOG.md | 2 + docs/_toc.yml | 2 + docs/fundamentals/metamodeling.md | 1 + docs/test-functions/available.md | 1 + docs/test-functions/mclain-s5.md | 159 ++++++++++++++++++++++ src/uqtestfuns/test_functions/__init__.py | 2 + src/uqtestfuns/test_functions/mclain.py | 115 ++++++++++++++++ 7 files changed, 282 insertions(+) create mode 100644 docs/test-functions/mclain-s5.md create mode 100644 src/uqtestfuns/test_functions/mclain.py diff --git a/CHANGELOG.md b/CHANGELOG.md index f3376a4..8614f46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 exercises, is added as a UQ test function. - The two-dimensional (third) Franke function, relevant for metamodeling exercises, is added as a UQ test function. +- The two-dimensional McLain S5 function, relevant for metamodeling exercises, + is added as a UQ test function ## [0.1.1] - 2023-07-03 diff --git a/docs/_toc.yml b/docs/_toc.yml index d2b44d7..a466909 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -41,6 +41,8 @@ parts: title: Oakley-O'Hagan 1D - file: test-functions/otl-circuit title: OTL Circuit + - file: test-functions/mclain-s5 + title: McLain S5 - file: test-functions/piston title: Piston Simulation - file: test-functions/sobol-g diff --git a/docs/fundamentals/metamodeling.md b/docs/fundamentals/metamodeling.md index 342287f..05f4a05 100644 --- a/docs/fundamentals/metamodeling.md +++ b/docs/fundamentals/metamodeling.md @@ -28,6 +28,7 @@ in the comparison of metamodeling approaches. | {ref}`(3rd) Franke ` | 2 | `Franke3()` | | {ref}`Oakley-O'Hagan 1D ` | 1 | `OakleyOHagan1D()` | | {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | +| {ref}`McLain S5 ` | 2 | `McLainS5()` | | {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | | {ref}`Sulfur ` | 9 | `Sulfur()` | | {ref}`Wing Weight ` | 10 | `WingWeight()` | diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index f42506c..da98553 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -30,6 +30,7 @@ available in the current UQTestFuns, regardless of their typical applications. | {ref}`Ishigami ` | 3 | `Ishigami()` | | {ref}`Oakley-O'Hagan 1D ` | 1 | `OakleyOHagan1D()` | | {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | +| {ref}`McLain S5 ` | 2 | `McLainS5()` | | {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | | {ref}`Sobol'-G ` | M | `SobolG()` | | {ref}`Sulfur ` | 9 | `Sulfur()` | diff --git a/docs/test-functions/mclain-s5.md b/docs/test-functions/mclain-s5.md new file mode 100644 index 0000000..92a4d4e --- /dev/null +++ b/docs/test-functions/mclain-s5.md @@ -0,0 +1,159 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:mclain-s5)= +# McLain S5 Function + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +The McLain S5 function is a two-dimensional scalar-valued function. +The function was introduced in {cite}`McLain1974` in the context of a procedure +for drawing contours from a given set of points. + +```{note} +The McLain's test functions are a set of five two-dimensional functions +that mathematically defines a surface. The functions are: + +- {ref}`S5 `: A plateau and plain separated by a steep cliff (this function) +``` + +```{code-cell} ipython3 +:tags: [remove-input] + +from mpl_toolkits.axes_grid1 import make_axes_locatable + +my_fun = uqtf.McLainS5() + +# --- Create 2D data +xx_1d = np.linspace(1.0, 10.0, 1000)[:, np.newaxis] +mesh_2d = np.meshgrid(xx_1d, xx_1d) +xx_2d = np.array(mesh_2d).T.reshape(-1, 2) +yy_2d = my_fun(xx_2d) + +# --- Create two-dimensional plots +fig = plt.figure(figsize=(10, 5)) + +# Surface +axs_1 = plt.subplot(121, projection='3d') +axs_1.plot_surface( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000,1000), + linewidth=0, + cmap="plasma", + antialiased=False, + alpha=0.5 +) +axs_1.set_xlabel("$x_1$", fontsize=14) +axs_1.set_ylabel("$x_2$", fontsize=14) +axs_1.set_zlabel("$\mathcal{M}(x_1, x_2)$", fontsize=14) +axs_1.set_title("Surface plot of McLain S5", fontsize=14) + +# Contour +axs_2 = plt.subplot(122) +cf = axs_2.contourf( + mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000), cmap="plasma" +) +axs_2.set_xlabel("$x_1$", fontsize=14) +axs_2.set_ylabel("$x_2$", fontsize=14) +axs_2.set_title("Contour plot of McLain S5", fontsize=14) +divider = make_axes_locatable(axs_2) +cax = divider.append_axes('right', size='5%', pad=0.05) +fig.colorbar(cf, cax=cax, orientation='vertical') +axs_2.axis('scaled') + +fig.tight_layout(pad=4.0) +plt.gcf().set_dpi(75); +``` + +As shown in the plots above, the function features two nearly flat regions +of height $0.0$ and (approximately) $1.0$. +The two regions are joined by a sharp rise that runs diagonally from +$(0.0, 10.0)$ to $(10.0, 0.0)$. + +```{note} +The McLain S5 function appeared in a modified form in the report +of Franke {cite}`Franke1979` +(specifically the {ref}`(2nd) Franke function `). + +In fact, four of Franke's test functions are +slight modifications of McLain's, including the translation of the input domain +from $[1.0, 10.0]$ to $[0.0, 1.0]$. +``` + +## Test function instance + +To create a default instance of the McLain S5 function: + +```{code-cell} ipython3 +my_testfun = uqtf.McLainS5() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +## Description + +The (2nd) Franke function is defined as follows: + +$$ +\mathcal{M}(\boldsymbol{x}) = \tanh{\left( x_1 + x_2 - 11 \right)} +$$ +where $\boldsymbol{x} = \{ x_1, x_2 \}$ +is the two-dimensional vector of input variables further defined below. + +## Probabilistic input + +Based on {cite}`McLain1974`, the probabilistic input model +for the function consists of two independent random variables as shown below. + +```{code-cell} ipython3 +my_testfun.prob_input +``` + +## Reference results + +This section provides several reference results of typical UQ analyses involving +the test function. + +### Sample histogram + +Shown below is the histogram of the output based on $100'000$ random points: + +```{code-cell} ipython3 +:tags: [hide-input] + +xx_test = my_testfun.prob_input.get_sample(100000) +yy_test = my_testfun(xx_test) + +plt.hist(yy_test, bins="auto", color="#8da0cb"); +plt.grid(); +plt.ylabel("Counts [-]"); +plt.xlabel("$\mathcal{M}(\mathbf{X})$"); +plt.gcf().set_dpi(150); +``` + +## References + +```{bibliography} +:style: plain +:filter: docname in docnames +``` \ No newline at end of file diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index 316820d..0ba1fe5 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -11,6 +11,7 @@ from .ishigami import Ishigami from .oakley_ohagan_1d import OakleyOHagan1D from .otl_circuit import OTLCircuit +from .mclain import McLainS5 from .piston import Piston from .sobol_g import SobolG from .sulfur import Sulfur @@ -30,6 +31,7 @@ "Ishigami", "OakleyOHagan1D", "OTLCircuit", + "McLainS5", "Piston", "SobolG", "Sulfur", diff --git a/src/uqtestfuns/test_functions/mclain.py b/src/uqtestfuns/test_functions/mclain.py new file mode 100644 index 0000000..b488f43 --- /dev/null +++ b/src/uqtestfuns/test_functions/mclain.py @@ -0,0 +1,115 @@ +""" +Module with an implementation of the McLain's test functions. + +The McLain's test functions consists of five two-dimensional scalar-valued +functions. The functions were introduced in [1] in the context of drawing +contours from a given set of points. + +There are five test functions in McLain's paper each models a mathematically +defined surface: + +- S5: A plateau and plain separated by a steep cliff + +Four of the functions (S2-S5) appeared in modified forms in [2]. + +References +---------- + +1. D. H. McLain, "Drawing contours from arbitrary data points," The Computer + Journal, vol. 17, no. 4, pp. 318-324, 1974. + DOI: 10.1093/comjnl/17.4.318 +2. Richard Franke, "A critical comparison of some methods for interpolation + of scattered data," Naval Postgraduate School, Monterey, Canada, + Technical Report No. NPS53-79-003, 1979. + URL: https://core.ac.uk/reader/36727660 +""" +import numpy as np + +from typing import Optional + +from ..core.prob_input.univariate_distribution import UnivDist +from ..core.uqtestfun_abc import UQTestFunABC +from .available import create_prob_input_from_available + +__all__ = ["McLainS5"] + +INPUT_MARGINALS_MCLAIN1974 = [ # From Ref. [1] + UnivDist( + name="X1", + distribution="uniform", + parameters=[1.0, 10.0], + ), + UnivDist( + name="X2", + distribution="uniform", + parameters=[1.0, 10.0], + ), +] + +AVAILABLE_INPUT_SPECS = { + "McLain1974": { + "name": "McLain-1974", + "description": ( + "Input specification for the McLain's test functions " + "from McLain (1974)." + ), + "marginals": INPUT_MARGINALS_MCLAIN1974, + "copulas": None, + } +} + +DEFAULT_INPUT_SELECTION = "McLain1974" + + +class McLainS5(UQTestFunABC): + """A concrete implementation of the McLain S5 function.""" + + _TAGS = ["metamodeling"] + + _AVAILABLE_INPUTS = tuple(AVAILABLE_INPUT_SPECS.keys()) + + _AVAILABLE_PARAMETERS = None + + _DEFAULT_SPATIAL_DIMENSION = 2 + + _DESCRIPTION = "McLain S5 function from McLain (1974)" + + def __init__( + self, + *, + prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, + name: Optional[str] = None, + rng_seed_prob_input: Optional[int] = None, + ): + # --- Arguments processing + prob_input = create_prob_input_from_available( + prob_input_selection, + AVAILABLE_INPUT_SPECS, + rng_seed=rng_seed_prob_input, + ) + # Process the default name + if name is None: + name = McLainS5.__name__ + + super().__init__(prob_input=prob_input, name=name) + + def evaluate(self, xx: np.ndarray): + """Evaluate the McLain S5 function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + Two-Dimensional input values given by N-by-2 arrays where + N is the number of input values. + + Returns + ------- + np.ndarray + The output of the McLain S5 function evaluated + on the input values. + The output is a 1-dimensional array of length N. + """ + # Compute the (second) Franke function + yy = np.tanh(xx[:, 0] + xx[:, 1] - 11) + + return yy From 31bc436f81b53d568724cf8b73a3a7a3814d4c55 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Thu, 1 Jun 2023 17:12:10 +0200 Subject: [PATCH 06/73] Add an implementation of the McLain S1 function - The function is a two-dimensional function that features a part of a sphere. --- CHANGELOG.md | 4 +- docs/_toc.yml | 2 + docs/fundamentals/metamodeling.md | 1 + docs/test-functions/available.md | 1 + docs/test-functions/mclain-s1.md | 156 ++++++++++++++++++++++ docs/test-functions/mclain-s5.md | 3 +- src/uqtestfuns/test_functions/__init__.py | 3 +- src/uqtestfuns/test_functions/mclain.py | 59 +++++++- 8 files changed, 223 insertions(+), 6 deletions(-) create mode 100644 docs/test-functions/mclain-s1.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 8614f46..8da798b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,8 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 exercises, is added as a UQ test function. - The two-dimensional (third) Franke function, relevant for metamodeling exercises, is added as a UQ test function. -- The two-dimensional McLain S5 function, relevant for metamodeling exercises, - is added as a UQ test function +- The two-dimensional McLain functions (S1, S5), relevant for metamodeling + exercises, are added as UQ test functions. ## [0.1.1] - 2023-07-03 diff --git a/docs/_toc.yml b/docs/_toc.yml index a466909..8754bf1 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -41,6 +41,8 @@ parts: title: Oakley-O'Hagan 1D - file: test-functions/otl-circuit title: OTL Circuit + - file: test-functions/mclain-s1 + title: McLain S1 - file: test-functions/mclain-s5 title: McLain S5 - file: test-functions/piston diff --git a/docs/fundamentals/metamodeling.md b/docs/fundamentals/metamodeling.md index 05f4a05..f655bd6 100644 --- a/docs/fundamentals/metamodeling.md +++ b/docs/fundamentals/metamodeling.md @@ -28,6 +28,7 @@ in the comparison of metamodeling approaches. | {ref}`(3rd) Franke ` | 2 | `Franke3()` | | {ref}`Oakley-O'Hagan 1D ` | 1 | `OakleyOHagan1D()` | | {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | +| {ref}`McLain S1 ` | 2 | `McLainS1()` | | {ref}`McLain S5 ` | 2 | `McLainS5()` | | {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | | {ref}`Sulfur ` | 9 | `Sulfur()` | diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index da98553..45420fc 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -30,6 +30,7 @@ available in the current UQTestFuns, regardless of their typical applications. | {ref}`Ishigami ` | 3 | `Ishigami()` | | {ref}`Oakley-O'Hagan 1D ` | 1 | `OakleyOHagan1D()` | | {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | +| {ref}`McLain S1 ` | 2 | `McLainS1()` | | {ref}`McLain S5 ` | 2 | `McLainS5()` | | {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | | {ref}`Sobol'-G ` | M | `SobolG()` | diff --git a/docs/test-functions/mclain-s1.md b/docs/test-functions/mclain-s1.md new file mode 100644 index 0000000..b3f82a4 --- /dev/null +++ b/docs/test-functions/mclain-s1.md @@ -0,0 +1,156 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:mclain-s1)= +# McLain S1 Function + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +The McLain S1 function is a two-dimensional scalar-valued function. +The function was introduced in {cite}`McLain1974` in the context of a procedure +for drawing contours from a given set of points. + +```{note} +The McLain's test functions are a set of five two-dimensional functions +that mathematically defines surfaces. The functions are: + +- {ref}`S1 `: A part of a sphere (this function) +- {ref}`S5 `: A plateau and plain separated by a steep cliff +``` + +```{code-cell} ipython3 +:tags: [remove-input] + +from mpl_toolkits.axes_grid1 import make_axes_locatable + +my_fun = uqtf.McLainS1() + +# --- Create 2D data +xx_1d = np.linspace(1.0, 10.0, 1000)[:, np.newaxis] +mesh_2d = np.meshgrid(xx_1d, xx_1d) +xx_2d = np.array(mesh_2d).T.reshape(-1, 2) +yy_2d = my_fun(xx_2d) + +# --- Create two-dimensional plots +fig = plt.figure(figsize=(10, 5)) + +# Surface +axs_1 = plt.subplot(121, projection='3d') +axs_1.plot_surface( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000,1000), + linewidth=0, + cmap="plasma", + antialiased=False, + alpha=0.5 +) +axs_1.set_xlabel("$x_1$", fontsize=14) +axs_1.set_ylabel("$x_2$", fontsize=14) +axs_1.set_zlabel("$\mathcal{M}(x_1, x_2)$", fontsize=14) +axs_1.set_title("Surface plot of McLain S1", fontsize=14) + +# Contour +axs_2 = plt.subplot(122) +cf = axs_2.contourf( + mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000), cmap="plasma" +) +axs_2.set_xlabel("$x_1$", fontsize=14) +axs_2.set_ylabel("$x_2$", fontsize=14) +axs_2.set_title("Contour plot of McLain S1", fontsize=14) +divider = make_axes_locatable(axs_2) +cax = divider.append_axes('right', size='5%', pad=0.05) +fig.colorbar(cf, cax=cax, orientation='vertical') +axs_2.axis('scaled') + +fig.tight_layout(pad=4.0) +plt.gcf().set_dpi(75); +``` + +As shown in the plots above, the resulting surface is a part of a sphere. + +```{note} +The McLain S1 function appeared in a modified form in the report +of Franke {cite}`Franke1979` (specifically the (6th) Franke function). + +In fact, four of Franke's test functions are +slight modifications of McLain's, including the translation of the input domain +from $[1.0, 10.0]$ to $[0.0, 1.0]$. +``` + +## Test function instance + +To create a default instance of the McLain S1 function: + +```{code-cell} ipython3 +my_testfun = uqtf.McLainS1() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +## Description + +The (2nd) Franke function is defined as follows: + +$$ +\mathcal{M}(\boldsymbol{x}) = \left( 64 - (x_1 - 5.5)^2 - (x_2 - 5.5)^2 \right)^{0.5} +$$ +where $\boldsymbol{x} = \{ x_1, x_2 \}$ +is the two-dimensional vector of input variables further defined below. + +## Probabilistic input + +Based on {cite}`McLain1974`, the probabilistic input model +for the function consists of two independent random variables as shown below. + +```{code-cell} ipython3 +my_testfun.prob_input +``` + +## Reference results + +This section provides several reference results of typical UQ analyses involving +the test function. + +### Sample histogram + +Shown below is the histogram of the output based on $100'000$ random points: + +```{code-cell} ipython3 +:tags: [hide-input] + +xx_test = my_testfun.prob_input.get_sample(100000) +yy_test = my_testfun(xx_test) + +plt.hist(yy_test, bins="auto", color="#8da0cb"); +plt.grid(); +plt.ylabel("Counts [-]"); +plt.xlabel("$\mathcal{M}(\mathbf{X})$"); +plt.gcf().set_dpi(150); +``` + +## References + +```{bibliography} +:style: plain +:filter: docname in docnames +``` \ No newline at end of file diff --git a/docs/test-functions/mclain-s5.md b/docs/test-functions/mclain-s5.md index 92a4d4e..a1889a8 100644 --- a/docs/test-functions/mclain-s5.md +++ b/docs/test-functions/mclain-s5.md @@ -27,8 +27,9 @@ for drawing contours from a given set of points. ```{note} The McLain's test functions are a set of five two-dimensional functions -that mathematically defines a surface. The functions are: +that mathematically defines surfaces. The functions are: +- {ref}`S1 `: A part of a sphere - {ref}`S5 `: A plateau and plain separated by a steep cliff (this function) ``` diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index 0ba1fe5..6f96dcc 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -11,7 +11,7 @@ from .ishigami import Ishigami from .oakley_ohagan_1d import OakleyOHagan1D from .otl_circuit import OTLCircuit -from .mclain import McLainS5 +from .mclain import McLainS1, McLainS5 from .piston import Piston from .sobol_g import SobolG from .sulfur import Sulfur @@ -31,6 +31,7 @@ "Ishigami", "OakleyOHagan1D", "OTLCircuit", + "McLainS1", "McLainS5", "Piston", "SobolG", diff --git a/src/uqtestfuns/test_functions/mclain.py b/src/uqtestfuns/test_functions/mclain.py index b488f43..88de3f5 100644 --- a/src/uqtestfuns/test_functions/mclain.py +++ b/src/uqtestfuns/test_functions/mclain.py @@ -8,9 +8,10 @@ There are five test functions in McLain's paper each models a mathematically defined surface: +- S1: A part of a sphere - S5: A plateau and plain separated by a steep cliff -Four of the functions (S2-S5) appeared in modified forms in [2]. +Four of the functions (S1-S3 and S5) appeared in modified forms in [2]. References ---------- @@ -61,6 +62,60 @@ DEFAULT_INPUT_SELECTION = "McLain1974" +class McLainS1(UQTestFunABC): + """A concrete implementation of the McLain S1 function.""" + + _TAGS = ["metamodeling"] + + _AVAILABLE_INPUTS = tuple(AVAILABLE_INPUT_SPECS.keys()) + + _AVAILABLE_PARAMETERS = None + + _DEFAULT_SPATIAL_DIMENSION = 2 + + _DESCRIPTION = "McLain S1 function from McLain (1974)" + + def __init__( + self, + *, + prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, + name: Optional[str] = None, + rng_seed_prob_input: Optional[int] = None, + ): + # --- Arguments processing + prob_input = create_prob_input_from_available( + prob_input_selection, + AVAILABLE_INPUT_SPECS, + rng_seed=rng_seed_prob_input, + ) + # Process the default name + if name is None: + name = McLainS1.__name__ + + super().__init__(prob_input=prob_input, name=name) + + def evaluate(self, xx: np.ndarray): + """Evaluate the McLain S1 function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + Two-Dimensional input values given by N-by-2 arrays where + N is the number of input values. + + Returns + ------- + np.ndarray + The output of the McLain S1 function evaluated + on the input values. + The output is a 1-dimensional array of length N. + """ + # Compute the McLain S1 function + yy = np.sqrt(64 - (xx[:, 0] - 5.5) ** 2 - (xx[:, 1] - 5.5) ** 2) + + return yy + + class McLainS5(UQTestFunABC): """A concrete implementation of the McLain S5 function.""" @@ -109,7 +164,7 @@ def evaluate(self, xx: np.ndarray): on the input values. The output is a 1-dimensional array of length N. """ - # Compute the (second) Franke function + # Compute the McLain S5 yy = np.tanh(xx[:, 0] + xx[:, 1] - 11) return yy From 4eb8c443b17897ca3c05432e4a0ee62002b8cc03 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Fri, 2 Jun 2023 17:08:21 +0200 Subject: [PATCH 07/73] Add an implementation of McLain S2 function. - The function is a two-dimensional function that resembles a steep hill rising from a plain. --- CHANGELOG.md | 2 +- docs/_toc.yml | 2 + docs/fundamentals/metamodeling.md | 1 + docs/test-functions/available.md | 1 + docs/test-functions/mclain-s1.md | 5 +- docs/test-functions/mclain-s2.md | 159 ++++++++++++++++++++ docs/test-functions/mclain-s5.md | 5 +- src/uqtestfuns/test_functions/__init__.py | 3 +- src/uqtestfuns/test_functions/mclain.py | 167 ++++++++++------------ 9 files changed, 248 insertions(+), 97 deletions(-) create mode 100644 docs/test-functions/mclain-s2.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 8da798b..c51ba77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 exercises, is added as a UQ test function. - The two-dimensional (third) Franke function, relevant for metamodeling exercises, is added as a UQ test function. -- The two-dimensional McLain functions (S1, S5), relevant for metamodeling +- The two-dimensional McLain functions (S1, S2, S5), relevant for metamodeling exercises, are added as UQ test functions. ## [0.1.1] - 2023-07-03 diff --git a/docs/_toc.yml b/docs/_toc.yml index 8754bf1..c46c126 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -43,6 +43,8 @@ parts: title: OTL Circuit - file: test-functions/mclain-s1 title: McLain S1 + - file: test-functions/mclain-s2 + title: McLain S2 - file: test-functions/mclain-s5 title: McLain S5 - file: test-functions/piston diff --git a/docs/fundamentals/metamodeling.md b/docs/fundamentals/metamodeling.md index f655bd6..109a400 100644 --- a/docs/fundamentals/metamodeling.md +++ b/docs/fundamentals/metamodeling.md @@ -29,6 +29,7 @@ in the comparison of metamodeling approaches. | {ref}`Oakley-O'Hagan 1D ` | 1 | `OakleyOHagan1D()` | | {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | | {ref}`McLain S1 ` | 2 | `McLainS1()` | +| {ref}`McLain S2 ` | 2 | `McLainS2()` | | {ref}`McLain S5 ` | 2 | `McLainS5()` | | {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | | {ref}`Sulfur ` | 9 | `Sulfur()` | diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index 45420fc..04cb311 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -31,6 +31,7 @@ available in the current UQTestFuns, regardless of their typical applications. | {ref}`Oakley-O'Hagan 1D ` | 1 | `OakleyOHagan1D()` | | {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | | {ref}`McLain S1 ` | 2 | `McLainS1()` | +| {ref}`McLain S2 ` | 2 | `McLainS2()` | | {ref}`McLain S5 ` | 2 | `McLainS5()` | | {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | | {ref}`Sobol'-G ` | M | `SobolG()` | diff --git a/docs/test-functions/mclain-s1.md b/docs/test-functions/mclain-s1.md index b3f82a4..45661fe 100644 --- a/docs/test-functions/mclain-s1.md +++ b/docs/test-functions/mclain-s1.md @@ -22,14 +22,15 @@ import uqtestfuns as uqtf ``` The McLain S1 function is a two-dimensional scalar-valued function. -The function was introduced in {cite}`McLain1974` in the context of a procedure -for drawing contours from a given set of points. +The function was introduced in {cite}`McLain1974` as a test function for +procedures to construct contours from a given set of points. ```{note} The McLain's test functions are a set of five two-dimensional functions that mathematically defines surfaces. The functions are: - {ref}`S1 `: A part of a sphere (this function) +- {ref}`S2 `: A steep hill rising from a plain - {ref}`S5 `: A plateau and plain separated by a steep cliff ``` diff --git a/docs/test-functions/mclain-s2.md b/docs/test-functions/mclain-s2.md new file mode 100644 index 0000000..0ca2bc6 --- /dev/null +++ b/docs/test-functions/mclain-s2.md @@ -0,0 +1,159 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:mclain-s2)= +# McLain S2 Function + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +The McLain S2 function is a two-dimensional scalar-valued function. +The function was introduced in {cite}`McLain1974` as a test function for +procedures to construct contours from a given set of points. + +```{note} +The McLain's test functions are a set of five two-dimensional functions +that mathematically defines surfaces. The functions are: + +- {ref}`S1 `: A part of a sphere +- {ref}`S2 `: A steep hill rising from a plain (this function) +- {ref}`S5 `: A plateau and plain separated by a steep cliff +``` + +```{code-cell} ipython3 +:tags: [remove-input] + +from mpl_toolkits.axes_grid1 import make_axes_locatable + +my_fun = uqtf.McLainS2() + +# --- Create 2D data +xx_1d = np.linspace(1.0, 10.0, 1000)[:, np.newaxis] +mesh_2d = np.meshgrid(xx_1d, xx_1d) +xx_2d = np.array(mesh_2d).T.reshape(-1, 2) +yy_2d = my_fun(xx_2d) + +# --- Create two-dimensional plots +fig = plt.figure(figsize=(10, 5)) + +# Surface +axs_1 = plt.subplot(121, projection='3d') +axs_1.plot_surface( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000,1000), + linewidth=0, + cmap="plasma", + antialiased=False, + alpha=0.5 +) +axs_1.set_xlabel("$x_1$", fontsize=14) +axs_1.set_ylabel("$x_2$", fontsize=14) +axs_1.set_zlabel("$\mathcal{M}(x_1, x_2)$", fontsize=14) +axs_1.set_title("Surface plot of McLain S2", fontsize=14) + +# Contour +axs_2 = plt.subplot(122) +cf = axs_2.contourf( + mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000), cmap="plasma" +) +axs_2.set_xlabel("$x_1$", fontsize=14) +axs_2.set_ylabel("$x_2$", fontsize=14) +axs_2.set_title("Contour plot of McLain S2", fontsize=14) +divider = make_axes_locatable(axs_2) +cax = divider.append_axes('right', size='5%', pad=0.05) +fig.colorbar(cf, cax=cax, orientation='vertical') +axs_2.axis('scaled') + +fig.tight_layout(pad=4.0) +plt.gcf().set_dpi(75); +``` + +As shown in the plots above, the resulting surface resembles a steep hill +rising from a plain. The location of the peak is at $(5.0, 5.0)$ +and with the height of $1.0$. + +```{note} +The McLain S1 function appeared in a modified form in the report +of Franke {cite}`Franke1979` (specifically the (6th) Franke function). + +In fact, four of Franke's test functions are +slight modifications of McLain's, including the translation of the input domain +from $[1.0, 10.0]$ to $[0.0, 1.0]$. +``` + +## Test function instance + +To create a default instance of the McLain S1 function: + +```{code-cell} ipython3 +my_testfun = uqtf.McLainS2() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +## Description + +The (2nd) Franke function is defined as follows: + +$$ +\mathcal{M}(\boldsymbol{x}) = \exp{\left[ - \left( (x_1 - 5)^2 + (x_2 - 5)^2 \right) \right]} +$$ +where $\boldsymbol{x} = \{ x_1, x_2 \}$ +is the two-dimensional vector of input variables further defined below. + +## Probabilistic input + +Based on {cite}`McLain1974`, the probabilistic input model +for the function consists of two independent random variables as shown below. + +```{code-cell} ipython3 +my_testfun.prob_input +``` + +## Reference results + +This section provides several reference results of typical UQ analyses involving +the test function. + +### Sample histogram + +Shown below is the histogram of the output based on $100'000$ random points: + +```{code-cell} ipython3 +:tags: [hide-input] + +xx_test = my_testfun.prob_input.get_sample(100000) +yy_test = my_testfun(xx_test) + +plt.hist(yy_test, color="#8da0cb"); +plt.grid(); +plt.ylabel("Counts [-]"); +plt.xlabel("$\mathcal{M}(\mathbf{X})$"); +plt.gcf().set_dpi(150); +``` + +## References + +```{bibliography} +:style: plain +:filter: docname in docnames +``` \ No newline at end of file diff --git a/docs/test-functions/mclain-s5.md b/docs/test-functions/mclain-s5.md index a1889a8..55313c0 100644 --- a/docs/test-functions/mclain-s5.md +++ b/docs/test-functions/mclain-s5.md @@ -22,14 +22,15 @@ import uqtestfuns as uqtf ``` The McLain S5 function is a two-dimensional scalar-valued function. -The function was introduced in {cite}`McLain1974` in the context of a procedure -for drawing contours from a given set of points. +The function was introduced in {cite}`McLain1974` as a test function for +procedures to construct contours from a given set of points. ```{note} The McLain's test functions are a set of five two-dimensional functions that mathematically defines surfaces. The functions are: - {ref}`S1 `: A part of a sphere +- {ref}`S2 `: A steep hill rising from a plain - {ref}`S5 `: A plateau and plain separated by a steep cliff (this function) ``` diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index 6f96dcc..8b5c043 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -11,7 +11,7 @@ from .ishigami import Ishigami from .oakley_ohagan_1d import OakleyOHagan1D from .otl_circuit import OTLCircuit -from .mclain import McLainS1, McLainS5 +from .mclain import McLainS1, McLainS2, McLainS5 from .piston import Piston from .sobol_g import SobolG from .sulfur import Sulfur @@ -32,6 +32,7 @@ "OakleyOHagan1D", "OTLCircuit", "McLainS1", + "McLainS2", "McLainS5", "Piston", "SobolG", diff --git a/src/uqtestfuns/test_functions/mclain.py b/src/uqtestfuns/test_functions/mclain.py index 88de3f5..7ec8543 100644 --- a/src/uqtestfuns/test_functions/mclain.py +++ b/src/uqtestfuns/test_functions/mclain.py @@ -9,6 +9,7 @@ defined surface: - S1: A part of a sphere +- S2: A steep hill rising from a plain - S5: A plateau and plain separated by a steep cliff Four of the functions (S1-S3 and S5) appeared in modified forms in [2]. @@ -32,18 +33,20 @@ from ..core.uqtestfun_abc import UQTestFunABC from .available import create_prob_input_from_available -__all__ = ["McLainS5"] +__all__ = ["McLainS1", "McLainS2", "McLainS5"] INPUT_MARGINALS_MCLAIN1974 = [ # From Ref. [1] UnivDist( name="X1", distribution="uniform", parameters=[1.0, 10.0], + description=None, ), UnivDist( name="X2", distribution="uniform", parameters=[1.0, 10.0], + description=None, ), ] @@ -62,109 +65,91 @@ DEFAULT_INPUT_SELECTION = "McLain1974" -class McLainS1(UQTestFunABC): - """A concrete implementation of the McLain S1 function.""" +COMMON_METADATA = dict( + _TAGS=["metamodeling"], + _AVAILABLE_INPUTS=tuple(AVAILABLE_INPUT_SPECS.keys()), + _AVAILABLE_PARAMETERS=None, + _DEFAULT_SPATIAL_DIMENSION=2, + _DESCRIPTION="from McLain (1974)", +) + + +def _init( + self, + *, + prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, + name: Optional[str] = None, + rng_seed_prob_input: Optional[int] = None, +) -> None: + """A common __init__ for all McLain's test functions.""" + # --- Arguments processing + prob_input = create_prob_input_from_available( + prob_input_selection, + AVAILABLE_INPUT_SPECS, + rng_seed=rng_seed_prob_input, + ) + # Process the default name + if name is None: + name = self.__class__.__name__ - _TAGS = ["metamodeling"] + UQTestFunABC.__init__(self, prob_input=prob_input, name=name) - _AVAILABLE_INPUTS = tuple(AVAILABLE_INPUT_SPECS.keys()) - _AVAILABLE_PARAMETERS = None +def _eval_s1(self, xx: np.ndarray): + yy = np.sqrt(64 - (xx[:, 0] - 5.5) ** 2 - (xx[:, 1] - 5.5) ** 2) - _DEFAULT_SPATIAL_DIMENSION = 2 + return yy - _DESCRIPTION = "McLain S1 function from McLain (1974)" - def __init__( - self, - *, - prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, - name: Optional[str] = None, - rng_seed_prob_input: Optional[int] = None, - ): - # --- Arguments processing - prob_input = create_prob_input_from_available( - prob_input_selection, - AVAILABLE_INPUT_SPECS, - rng_seed=rng_seed_prob_input, - ) - # Process the default name - if name is None: - name = McLainS1.__name__ +def _eval_s2(self, xx: np.ndarray): + yy = np.exp(-1.0 * ((xx[:, 0] - 5) ** 2 + (xx[:, 1] - 5) ** 2)) - super().__init__(prob_input=prob_input, name=name) + return yy - def evaluate(self, xx: np.ndarray): - """Evaluate the McLain S1 function on a set of input values. - Parameters - ---------- - xx : np.ndarray - Two-Dimensional input values given by N-by-2 arrays where - N is the number of input values. +def _eval_s5(self, xx: np.ndarray): + yy = np.tanh(xx[:, 0] + xx[:, 1] - 11) - Returns - ------- - np.ndarray - The output of the McLain S1 function evaluated - on the input values. - The output is a 1-dimensional array of length N. - """ - # Compute the McLain S1 function - yy = np.sqrt(64 - (xx[:, 0] - 5.5) ** 2 - (xx[:, 1] - 5.5) ** 2) + return yy + + +class McLainS1(UQTestFunABC): + """A concrete implementation of the McLain S1 function.""" - return yy + _TAGS = COMMON_METADATA["_TAGS"] + _AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"] + _AVAILABLE_PARAMETERS = COMMON_METADATA["_AVAILABLE_PARAMETERS"] + _DEFAULT_SPATIAL_DIMENSION = COMMON_METADATA["_DEFAULT_SPATIAL_DIMENSION"] + _DESCRIPTION = f"McLain S1 function {COMMON_METADATA['_DESCRIPTION']}" + + __init__ = _init # type: ignore + evaluate = _eval_s1 + + +class McLainS2(UQTestFunABC): + """A concrete implementation of the McLain S2 function. + + The function features a steep hill rising from a plain. + """ + + _TAGS = COMMON_METADATA["_TAGS"] + _AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"] + _AVAILABLE_PARAMETERS = COMMON_METADATA["_AVAILABLE_PARAMETERS"] + _DEFAULT_SPATIAL_DIMENSION = COMMON_METADATA["_DEFAULT_SPATIAL_DIMENSION"] + _DESCRIPTION = f"McLain S1 function {COMMON_METADATA['_DESCRIPTION']}" + + __init__ = _init # type: ignore + evaluate = _eval_s2 class McLainS5(UQTestFunABC): """A concrete implementation of the McLain S5 function.""" - _TAGS = ["metamodeling"] - - _AVAILABLE_INPUTS = tuple(AVAILABLE_INPUT_SPECS.keys()) - - _AVAILABLE_PARAMETERS = None - - _DEFAULT_SPATIAL_DIMENSION = 2 - - _DESCRIPTION = "McLain S5 function from McLain (1974)" - - def __init__( - self, - *, - prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, - name: Optional[str] = None, - rng_seed_prob_input: Optional[int] = None, - ): - # --- Arguments processing - prob_input = create_prob_input_from_available( - prob_input_selection, - AVAILABLE_INPUT_SPECS, - rng_seed=rng_seed_prob_input, - ) - # Process the default name - if name is None: - name = McLainS5.__name__ - - super().__init__(prob_input=prob_input, name=name) - - def evaluate(self, xx: np.ndarray): - """Evaluate the McLain S5 function on a set of input values. - - Parameters - ---------- - xx : np.ndarray - Two-Dimensional input values given by N-by-2 arrays where - N is the number of input values. - - Returns - ------- - np.ndarray - The output of the McLain S5 function evaluated - on the input values. - The output is a 1-dimensional array of length N. - """ - # Compute the McLain S5 - yy = np.tanh(xx[:, 0] + xx[:, 1] - 11) - - return yy + _TAGS = COMMON_METADATA["_TAGS"] + _AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"] + _AVAILABLE_PARAMETERS = COMMON_METADATA["_AVAILABLE_PARAMETERS"] + _DEFAULT_SPATIAL_DIMENSION = COMMON_METADATA["_DEFAULT_SPATIAL_DIMENSION"] + _DESCRIPTION = f"McLain S1 function {COMMON_METADATA['_DESCRIPTION']}" + + __init__ = _init # type: ignore + evaluate = _eval_s5 From d83a37b09160ed82d82054a3b006aad1e1659c9e Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Fri, 2 Jun 2023 18:13:47 +0200 Subject: [PATCH 08/73] Add an implementation of McLain S3 function. - The function is a two-dimensional function that resembles a gentler hill rising from a plain. --- CHANGELOG.md | 4 +- docs/_toc.yml | 2 + docs/fundamentals/metamodeling.md | 1 + docs/test-functions/available.md | 1 + docs/test-functions/mclain-s3.md | 162 ++++++++++++++++++++++ src/uqtestfuns/test_functions/__init__.py | 3 +- src/uqtestfuns/test_functions/mclain.py | 53 +++++-- 7 files changed, 213 insertions(+), 13 deletions(-) create mode 100644 docs/test-functions/mclain-s3.md diff --git a/CHANGELOG.md b/CHANGELOG.md index c51ba77..1fad30a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,8 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 exercises, is added as a UQ test function. - The two-dimensional (third) Franke function, relevant for metamodeling exercises, is added as a UQ test function. -- The two-dimensional McLain functions (S1, S2, S5), relevant for metamodeling - exercises, are added as UQ test functions. +- The two-dimensional McLain functions (S1, S2, S3, S5), relevant for + metamodeling exercises, are added as UQ test functions. ## [0.1.1] - 2023-07-03 diff --git a/docs/_toc.yml b/docs/_toc.yml index c46c126..f013af3 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -45,6 +45,8 @@ parts: title: McLain S1 - file: test-functions/mclain-s2 title: McLain S2 + - file: test-functions/mclain-s3 + title: McLain S3 - file: test-functions/mclain-s5 title: McLain S5 - file: test-functions/piston diff --git a/docs/fundamentals/metamodeling.md b/docs/fundamentals/metamodeling.md index 109a400..c75cc68 100644 --- a/docs/fundamentals/metamodeling.md +++ b/docs/fundamentals/metamodeling.md @@ -30,6 +30,7 @@ in the comparison of metamodeling approaches. | {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | | {ref}`McLain S1 ` | 2 | `McLainS1()` | | {ref}`McLain S2 ` | 2 | `McLainS2()` | +| {ref}`McLain S3 ` | 2 | `McLainS3()` | | {ref}`McLain S5 ` | 2 | `McLainS5()` | | {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | | {ref}`Sulfur ` | 9 | `Sulfur()` | diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index 04cb311..dc353c0 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -32,6 +32,7 @@ available in the current UQTestFuns, regardless of their typical applications. | {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | | {ref}`McLain S1 ` | 2 | `McLainS1()` | | {ref}`McLain S2 ` | 2 | `McLainS2()` | +| {ref}`McLain S3 ` | 2 | `McLainS3()` | | {ref}`McLain S5 ` | 2 | `McLainS5()` | | {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | | {ref}`Sobol'-G ` | M | `SobolG()` | diff --git a/docs/test-functions/mclain-s3.md b/docs/test-functions/mclain-s3.md new file mode 100644 index 0000000..f67d707 --- /dev/null +++ b/docs/test-functions/mclain-s3.md @@ -0,0 +1,162 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:mclain-s3)= +# McLain S2 Function + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +The McLain S3 function is a two-dimensional scalar-valued function. +The function was introduced in {cite}`McLain1974` as a test function for +procedures to construct contours from a given set of points. + +```{note} +The McLain's test functions are a set of five two-dimensional functions +that mathematically defines surfaces. The functions are: + +- {ref}`S1 `: A part of a sphere +- {ref}`S2 `: A steep hill rising from a plain +- {ref}`S3 `: A less steep hill (this function) +- {ref}`S5 `: A plateau and plain separated by a steep cliff +``` + +```{code-cell} ipython3 +:tags: [remove-input] + +from mpl_toolkits.axes_grid1 import make_axes_locatable + +my_fun = uqtf.McLainS3() + +# --- Create 2D data +xx_1d = np.linspace(1.0, 10.0, 1000)[:, np.newaxis] +mesh_2d = np.meshgrid(xx_1d, xx_1d) +xx_2d = np.array(mesh_2d).T.reshape(-1, 2) +yy_2d = my_fun(xx_2d) + +# --- Create two-dimensional plots +fig = plt.figure(figsize=(10, 5)) + +# Surface +axs_1 = plt.subplot(121, projection='3d') +axs_1.plot_surface( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000,1000), + linewidth=0, + cmap="plasma", + antialiased=False, + alpha=0.5 +) +axs_1.set_xlabel("$x_1$", fontsize=14) +axs_1.set_ylabel("$x_2$", fontsize=14) +axs_1.set_zlabel("$\mathcal{M}(x_1, x_2)$", fontsize=14) +axs_1.set_title("Surface plot of McLain S3", fontsize=14) + +# Contour +axs_2 = plt.subplot(122) +cf = axs_2.contourf( + mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000), cmap="plasma" +) +axs_2.set_xlabel("$x_1$", fontsize=14) +axs_2.set_ylabel("$x_2$", fontsize=14) +axs_2.set_title("Contour plot of McLain S3", fontsize=14) +divider = make_axes_locatable(axs_2) +cax = divider.append_axes('right', size='5%', pad=0.05) +fig.colorbar(cf, cax=cax, orientation='vertical') +axs_2.axis('scaled') + +fig.tight_layout(pad=4.0) +plt.gcf().set_dpi(75); +``` + +As shown in the plots above, the resulting surface resembles a gentler +hill (compared to {ref}`S2 `) rising from a plain. +The location of the peak is at $(5.0, 5.0)$ +and with the height of $1.0$. + +```{note} +The McLain S3 function appeared in a modified form in the report +of Franke {cite}`Franke1979` (specifically the (5th) Franke function). + +In fact, four of Franke's test functions are +slight modifications of McLain's, including the translation of the input domain +from $[1.0, 10.0]$ to $[0.0, 1.0]$. +``` + +## Test function instance + +To create a default instance of the McLain S3 function: + +```{code-cell} ipython3 +my_testfun = uqtf.McLainS3() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +## Description + +The (2nd) Franke function is defined as follows: + +$$ +\mathcal{M}(\boldsymbol{x}) = \exp{\left[ - +0.25 \left( (x_1 - 5)^2 + (x_2 - 5)^2 \right) \right]} +$$ +where $\boldsymbol{x} = \{ x_1, x_2 \}$ +is the two-dimensional vector of input variables further defined below. + +## Probabilistic input + +Based on {cite}`McLain1974`, the probabilistic input model +for the function consists of two independent random variables as shown below. + +```{code-cell} ipython3 +my_testfun.prob_input +``` + +## Reference results + +This section provides several reference results of typical UQ analyses involving +the test function. + +### Sample histogram + +Shown below is the histogram of the output based on $100'000$ random points: + +```{code-cell} ipython3 +:tags: [hide-input] + +xx_test = my_testfun.prob_input.get_sample(100000) +yy_test = my_testfun(xx_test) + +plt.hist(yy_test, color="#8da0cb"); +plt.grid(); +plt.ylabel("Counts [-]"); +plt.xlabel("$\mathcal{M}(\mathbf{X})$"); +plt.gcf().set_dpi(150); +``` + +## References + +```{bibliography} +:style: plain +:filter: docname in docnames +``` \ No newline at end of file diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index 8b5c043..a64809d 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -11,7 +11,7 @@ from .ishigami import Ishigami from .oakley_ohagan_1d import OakleyOHagan1D from .otl_circuit import OTLCircuit -from .mclain import McLainS1, McLainS2, McLainS5 +from .mclain import McLainS1, McLainS2, McLainS3, McLainS5 from .piston import Piston from .sobol_g import SobolG from .sulfur import Sulfur @@ -33,6 +33,7 @@ "OTLCircuit", "McLainS1", "McLainS2", + "McLainS3", "McLainS5", "Piston", "SobolG", diff --git a/src/uqtestfuns/test_functions/mclain.py b/src/uqtestfuns/test_functions/mclain.py index 7ec8543..00faa1f 100644 --- a/src/uqtestfuns/test_functions/mclain.py +++ b/src/uqtestfuns/test_functions/mclain.py @@ -10,6 +10,7 @@ - S1: A part of a sphere - S2: A steep hill rising from a plain +- S3: A less steep hill - S5: A plateau and plain separated by a steep cliff Four of the functions (S1-S3 and S5) appeared in modified forms in [2]. @@ -33,20 +34,20 @@ from ..core.uqtestfun_abc import UQTestFunABC from .available import create_prob_input_from_available -__all__ = ["McLainS1", "McLainS2", "McLainS5"] +__all__ = ["McLainS1", "McLainS2", "McLainS3", "McLainS5"] INPUT_MARGINALS_MCLAIN1974 = [ # From Ref. [1] UnivDist( name="X1", distribution="uniform", parameters=[1.0, 10.0], - description=None, + description="None", ), UnivDist( name="X2", distribution="uniform", parameters=[1.0, 10.0], - description=None, + description="None", ), ] @@ -95,26 +96,39 @@ def _init( UQTestFunABC.__init__(self, prob_input=prob_input, name=name) -def _eval_s1(self, xx: np.ndarray): +def _eval_s1(uqtestfun: UQTestFunABC, xx: np.ndarray): + """Evaluate the McLain S1 function.""" yy = np.sqrt(64 - (xx[:, 0] - 5.5) ** 2 - (xx[:, 1] - 5.5) ** 2) return yy -def _eval_s2(self, xx: np.ndarray): +def _eval_s2(uqtestfun: UQTestFunABC, xx: np.ndarray): + """Evaluate the McLain S2 function.""" yy = np.exp(-1.0 * ((xx[:, 0] - 5) ** 2 + (xx[:, 1] - 5) ** 2)) return yy -def _eval_s5(self, xx: np.ndarray): +def _eval_s3(uqtestfun: UQTestFunABC, xx: np.ndarray): + """Evaluate the McLain S3 function.""" + yy = np.exp(-0.25 * ((xx[:, 0] - 5) ** 2 + (xx[:, 1] - 5) ** 2)) + + return yy + + +def _eval_s5(uqtestfun: UQTestFunABC, xx: np.ndarray): + """Evaluate the McLain S5 function.""" yy = np.tanh(xx[:, 0] + xx[:, 1] - 11) return yy class McLainS1(UQTestFunABC): - """A concrete implementation of the McLain S1 function.""" + """A concrete implementation of the McLain S1 function. + + The function features a part of a sphere. + """ _TAGS = COMMON_METADATA["_TAGS"] _AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"] @@ -136,20 +150,39 @@ class McLainS2(UQTestFunABC): _AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"] _AVAILABLE_PARAMETERS = COMMON_METADATA["_AVAILABLE_PARAMETERS"] _DEFAULT_SPATIAL_DIMENSION = COMMON_METADATA["_DEFAULT_SPATIAL_DIMENSION"] - _DESCRIPTION = f"McLain S1 function {COMMON_METADATA['_DESCRIPTION']}" + _DESCRIPTION = f"McLain S2 function {COMMON_METADATA['_DESCRIPTION']}" __init__ = _init # type: ignore evaluate = _eval_s2 +class McLainS3(UQTestFunABC): + """A concrete implementation of the McLain S2 function. + + The function features a less steep hill (compared to S2). + """ + + _TAGS = COMMON_METADATA["_TAGS"] + _AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"] + _AVAILABLE_PARAMETERS = COMMON_METADATA["_AVAILABLE_PARAMETERS"] + _DEFAULT_SPATIAL_DIMENSION = COMMON_METADATA["_DEFAULT_SPATIAL_DIMENSION"] + _DESCRIPTION = f"McLain S3 function {COMMON_METADATA['_DESCRIPTION']}" + + __init__ = _init # type: ignore + evaluate = _eval_s3 + + class McLainS5(UQTestFunABC): - """A concrete implementation of the McLain S5 function.""" + """A concrete implementation of the McLain S5 function. + + The function features two plateaus separated by a steep cliff. + """ _TAGS = COMMON_METADATA["_TAGS"] _AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"] _AVAILABLE_PARAMETERS = COMMON_METADATA["_AVAILABLE_PARAMETERS"] _DEFAULT_SPATIAL_DIMENSION = COMMON_METADATA["_DEFAULT_SPATIAL_DIMENSION"] - _DESCRIPTION = f"McLain S1 function {COMMON_METADATA['_DESCRIPTION']}" + _DESCRIPTION = f"McLain S5 function {COMMON_METADATA['_DESCRIPTION']}" __init__ = _init # type: ignore evaluate = _eval_s5 From 8a5900bf20c4488fb8ab36db8b35e7a59562c8ad Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Mon, 5 Jun 2023 13:19:14 +0200 Subject: [PATCH 09/73] Add an implementation of the McLain S4 function. - The McLain S4 test function features two plateaus separated by a steep cliff. --- docs/_toc.yml | 2 + docs/fundamentals/metamodeling.md | 1 + docs/test-functions/available.md | 1 + docs/test-functions/mclain-s1.md | 16 ++- docs/test-functions/mclain-s2.md | 22 ++-- docs/test-functions/mclain-s3.md | 15 ++- docs/test-functions/mclain-s4.md | 152 +++++++++++++++++++++ docs/test-functions/mclain-s5.md | 14 +- src/uqtestfuns/test_functions/__init__.py | 3 +- src/uqtestfuns/test_functions/mclain.py | 154 +++++++++++++++++----- 10 files changed, 316 insertions(+), 64 deletions(-) create mode 100644 docs/test-functions/mclain-s4.md diff --git a/docs/_toc.yml b/docs/_toc.yml index f013af3..28973c3 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -47,6 +47,8 @@ parts: title: McLain S2 - file: test-functions/mclain-s3 title: McLain S3 + - file: test-functions/mclain-s4 + title: McLain S4 - file: test-functions/mclain-s5 title: McLain S5 - file: test-functions/piston diff --git a/docs/fundamentals/metamodeling.md b/docs/fundamentals/metamodeling.md index c75cc68..984abb8 100644 --- a/docs/fundamentals/metamodeling.md +++ b/docs/fundamentals/metamodeling.md @@ -31,6 +31,7 @@ in the comparison of metamodeling approaches. | {ref}`McLain S1 ` | 2 | `McLainS1()` | | {ref}`McLain S2 ` | 2 | `McLainS2()` | | {ref}`McLain S3 ` | 2 | `McLainS3()` | +| {ref}`McLain S4 ` | 2 | `McLainS4()` | | {ref}`McLain S5 ` | 2 | `McLainS5()` | | {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | | {ref}`Sulfur ` | 9 | `Sulfur()` | diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index dc353c0..795de16 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -33,6 +33,7 @@ available in the current UQTestFuns, regardless of their typical applications. | {ref}`McLain S1 ` | 2 | `McLainS1()` | | {ref}`McLain S2 ` | 2 | `McLainS2()` | | {ref}`McLain S3 ` | 2 | `McLainS3()` | +| {ref}`McLain S4 ` | 2 | `McLainS4()` | | {ref}`McLain S5 ` | 2 | `McLainS5()` | | {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | | {ref}`Sobol'-G ` | M | `SobolG()` | diff --git a/docs/test-functions/mclain-s1.md b/docs/test-functions/mclain-s1.md index 45661fe..4795371 100644 --- a/docs/test-functions/mclain-s1.md +++ b/docs/test-functions/mclain-s1.md @@ -29,8 +29,10 @@ procedures to construct contours from a given set of points. The McLain's test functions are a set of five two-dimensional functions that mathematically defines surfaces. The functions are: -- {ref}`S1 `: A part of a sphere (this function) +- {ref}`S1 `: A part of a sphere (_this function_) - {ref}`S2 `: A steep hill rising from a plain +- {ref}`S3 `: A less steep hill +- {ref}`S4 `: A long narrow hill - {ref}`S5 `: A plateau and plain separated by a steep cliff ``` @@ -84,14 +86,16 @@ plt.gcf().set_dpi(75); ``` As shown in the plots above, the resulting surface is a part of a sphere. +The center of the sphere is at $(5.5, 5.5)$ and the maximum height is +$8.0$. ```{note} The McLain S1 function appeared in a modified form in the report of Franke {cite}`Franke1979` (specifically the (6th) Franke function). -In fact, four of Franke's test functions are -slight modifications of McLain's, including the translation of the input domain -from $[1.0, 10.0]$ to $[0.0, 1.0]$. +In fact, four of the Franke's test functions (2, 4, 5, and 6) are +slight modifications of the McLain's, including the translation +of the input domain from $[1.0, 10.0]$ to $[0.0, 1.0]$. ``` ## Test function instance @@ -110,7 +114,7 @@ print(my_testfun) ## Description -The (2nd) Franke function is defined as follows: +The McLain S1 function is defined as follows: $$ \mathcal{M}(\boldsymbol{x}) = \left( 64 - (x_1 - 5.5)^2 - (x_2 - 5.5)^2 \right)^{0.5} @@ -154,4 +158,4 @@ plt.gcf().set_dpi(150); ```{bibliography} :style: plain :filter: docname in docnames -``` \ No newline at end of file +``` diff --git a/docs/test-functions/mclain-s2.md b/docs/test-functions/mclain-s2.md index 0ca2bc6..f7b26cf 100644 --- a/docs/test-functions/mclain-s2.md +++ b/docs/test-functions/mclain-s2.md @@ -30,7 +30,9 @@ The McLain's test functions are a set of five two-dimensional functions that mathematically defines surfaces. The functions are: - {ref}`S1 `: A part of a sphere -- {ref}`S2 `: A steep hill rising from a plain (this function) +- {ref}`S2 `: A steep hill rising from a plain (_this function_) +- {ref}`S3 `: A less steep hill +- {ref}`S4 `: A long narrow hill - {ref}`S5 `: A plateau and plain separated by a steep cliff ``` @@ -85,20 +87,20 @@ plt.gcf().set_dpi(75); As shown in the plots above, the resulting surface resembles a steep hill rising from a plain. The location of the peak is at $(5.0, 5.0)$ -and with the height of $1.0$. +and with the maximum height of $1.0$. ```{note} -The McLain S1 function appeared in a modified form in the report -of Franke {cite}`Franke1979` (specifically the (6th) Franke function). +The McLain S2 function appeared in a modified form in the report +of Franke {cite}`Franke1979` (specifically the (5th) Franke function). -In fact, four of Franke's test functions are -slight modifications of McLain's, including the translation of the input domain -from $[1.0, 10.0]$ to $[0.0, 1.0]$. +In fact, four of the Franke's test functions (2, 4, 5, and 6) are +slight modifications of the McLain's, including the translation +of the input domain from $[1.0, 10.0]$ to $[0.0, 1.0]$. ``` ## Test function instance -To create a default instance of the McLain S1 function: +To create a default instance of the McLain S2 function: ```{code-cell} ipython3 my_testfun = uqtf.McLainS2() @@ -112,7 +114,7 @@ print(my_testfun) ## Description -The (2nd) Franke function is defined as follows: +The McLain S2 function is defined as follows: $$ \mathcal{M}(\boldsymbol{x}) = \exp{\left[ - \left( (x_1 - 5)^2 + (x_2 - 5)^2 \right) \right]} @@ -156,4 +158,4 @@ plt.gcf().set_dpi(150); ```{bibliography} :style: plain :filter: docname in docnames -``` \ No newline at end of file +``` diff --git a/docs/test-functions/mclain-s3.md b/docs/test-functions/mclain-s3.md index f67d707..280ce99 100644 --- a/docs/test-functions/mclain-s3.md +++ b/docs/test-functions/mclain-s3.md @@ -13,7 +13,7 @@ kernelspec: --- (test-functions:mclain-s3)= -# McLain S2 Function +# McLain S3 Function ```{code-cell} ipython3 import numpy as np @@ -31,7 +31,8 @@ that mathematically defines surfaces. The functions are: - {ref}`S1 `: A part of a sphere - {ref}`S2 `: A steep hill rising from a plain -- {ref}`S3 `: A less steep hill (this function) +- {ref}`S3 `: A less steep hill (_this function_) +- {ref}`S4 `: A long narrow hill - {ref}`S5 `: A plateau and plain separated by a steep cliff ``` @@ -87,13 +88,13 @@ plt.gcf().set_dpi(75); As shown in the plots above, the resulting surface resembles a gentler hill (compared to {ref}`S2 `) rising from a plain. The location of the peak is at $(5.0, 5.0)$ -and with the height of $1.0$. +and with the maximum height of $1.0$. ```{note} The McLain S3 function appeared in a modified form in the report -of Franke {cite}`Franke1979` (specifically the (5th) Franke function). +of Franke {cite}`Franke1979` (specifically the (4th) Franke function). -In fact, four of Franke's test functions are +In fact, four of the Franke's test functions (2, 4, 5, and 6) are slight modifications of McLain's, including the translation of the input domain from $[1.0, 10.0]$ to $[0.0, 1.0]$. ``` @@ -114,7 +115,7 @@ print(my_testfun) ## Description -The (2nd) Franke function is defined as follows: +The McLain S3 function is defined as follows: $$ \mathcal{M}(\boldsymbol{x}) = \exp{\left[ - @@ -159,4 +160,4 @@ plt.gcf().set_dpi(150); ```{bibliography} :style: plain :filter: docname in docnames -``` \ No newline at end of file +``` diff --git a/docs/test-functions/mclain-s4.md b/docs/test-functions/mclain-s4.md new file mode 100644 index 0000000..21a30fc --- /dev/null +++ b/docs/test-functions/mclain-s4.md @@ -0,0 +1,152 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:mclain-s4)= +# McLain S4 Function + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +The McLain S4 function is a two-dimensional scalar-valued function. +The function was introduced in {cite}`McLain1974` as a test function for +procedures to construct contours from a given set of points. + +```{note} +The McLain's test functions are a set of five two-dimensional functions +that mathematically defines surfaces. The functions are: + +- {ref}`S1 `: A part of a sphere +- {ref}`S2 `: A steep hill rising from a plain +- {ref}`S3 `: A less steep hill +- {ref}`S4 `: A long narrow hill (_this function_) +- {ref}`S5 `: A plateau and plain separated by a steep cliff +``` + +```{code-cell} ipython3 +:tags: [remove-input] + +from mpl_toolkits.axes_grid1 import make_axes_locatable + +my_fun = uqtf.McLainS4() + +# --- Create 2D data +xx_1d = np.linspace(1.0, 10.0, 1000)[:, np.newaxis] +mesh_2d = np.meshgrid(xx_1d, xx_1d) +xx_2d = np.array(mesh_2d).T.reshape(-1, 2) +yy_2d = my_fun(xx_2d) + +# --- Create two-dimensional plots +fig = plt.figure(figsize=(10, 5)) + +# Surface +axs_1 = plt.subplot(121, projection='3d') +axs_1.plot_surface( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000,1000), + linewidth=0, + cmap="plasma", + antialiased=False, + alpha=0.5 +) +axs_1.set_xlabel("$x_1$", fontsize=14) +axs_1.set_ylabel("$x_2$", fontsize=14) +axs_1.set_zlabel("$\mathcal{M}(x_1, x_2)$", fontsize=14) +axs_1.set_title("Surface plot of McLain S4", fontsize=14) + +# Contour +axs_2 = plt.subplot(122) +cf = axs_2.contourf( + mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000), cmap="plasma" +) +axs_2.set_xlabel("$x_1$", fontsize=14) +axs_2.set_ylabel("$x_2$", fontsize=14) +axs_2.set_title("Contour plot of McLain S4", fontsize=14) +divider = make_axes_locatable(axs_2) +cax = divider.append_axes('right', size='5%', pad=0.05) +fig.colorbar(cf, cax=cax, orientation='vertical') +axs_2.axis('scaled') + +fig.tight_layout(pad=4.0) +plt.gcf().set_dpi(75); +``` + +As shown in the plots above, the resulting surface consists of a long narrow +hill running diagonally from $(0.0, 10.0)$ to $(10.0, 0.0)$. +The maximum height is $1.0$ at $(5.5, 5.5)$. + +## Test function instance + +To create a default instance of the McLain S4 function: + +```{code-cell} ipython3 +my_testfun = uqtf.McLainS4() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +## Description + +The McLain S4 function is defined as follows: + +$$ +\mathcal{M}(\boldsymbol{x}) = \exp{\left[ -1 \left( (x_1 + x_2 - 11)^2 + \frac{(x_1 - x_2)^2}{10} \right) \right]} +$$ +where $\boldsymbol{x} = \{ x_1, x_2 \}$ +is the two-dimensional vector of input variables further defined below. + +## Probabilistic input + +Based on {cite}`McLain1974`, the probabilistic input model +for the function consists of two independent random variables as shown below. + +```{code-cell} ipython3 +my_testfun.prob_input +``` + +## Reference results + +This section provides several reference results of typical UQ analyses involving +the test function. + +### Sample histogram + +Shown below is the histogram of the output based on $100'000$ random points: + +```{code-cell} ipython3 +:tags: [hide-input] + +xx_test = my_testfun.prob_input.get_sample(100000) +yy_test = my_testfun(xx_test) + +plt.hist(yy_test, color="#8da0cb"); +plt.grid(); +plt.ylabel("Counts [-]"); +plt.xlabel("$\mathcal{M}(\mathbf{X})$"); +plt.gcf().set_dpi(150); +``` + +## References + +```{bibliography} +:style: plain +:filter: docname in docnames +``` diff --git a/docs/test-functions/mclain-s5.md b/docs/test-functions/mclain-s5.md index 55313c0..aac987e 100644 --- a/docs/test-functions/mclain-s5.md +++ b/docs/test-functions/mclain-s5.md @@ -31,7 +31,9 @@ that mathematically defines surfaces. The functions are: - {ref}`S1 `: A part of a sphere - {ref}`S2 `: A steep hill rising from a plain -- {ref}`S5 `: A plateau and plain separated by a steep cliff (this function) +- {ref}`S3 `: A less steep hill +- {ref}`S4 `: A long narrow hill +- {ref}`S5 `: A plateau and plain separated by a steep cliff (_this function_) ``` ```{code-cell} ipython3 @@ -93,9 +95,9 @@ The McLain S5 function appeared in a modified form in the report of Franke {cite}`Franke1979` (specifically the {ref}`(2nd) Franke function `). -In fact, four of Franke's test functions are -slight modifications of McLain's, including the translation of the input domain -from $[1.0, 10.0]$ to $[0.0, 1.0]$. +In fact, four of the Franke's test functions (2, 4, 5, and 6) are +slight modifications of the McLain's, including the translation +of the input domain from $[1.0, 10.0]$ to $[0.0, 1.0]$. ``` ## Test function instance @@ -114,7 +116,7 @@ print(my_testfun) ## Description -The (2nd) Franke function is defined as follows: +The McLain S5 function is defined as follows: $$ \mathcal{M}(\boldsymbol{x}) = \tanh{\left( x_1 + x_2 - 11 \right)} @@ -158,4 +160,4 @@ plt.gcf().set_dpi(150); ```{bibliography} :style: plain :filter: docname in docnames -``` \ No newline at end of file +``` diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index a64809d..de2732c 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -11,7 +11,7 @@ from .ishigami import Ishigami from .oakley_ohagan_1d import OakleyOHagan1D from .otl_circuit import OTLCircuit -from .mclain import McLainS1, McLainS2, McLainS3, McLainS5 +from .mclain import McLainS1, McLainS2, McLainS3, McLainS4, McLainS5 from .piston import Piston from .sobol_g import SobolG from .sulfur import Sulfur @@ -34,6 +34,7 @@ "McLainS1", "McLainS2", "McLainS3", + "McLainS4", "McLainS5", "Piston", "SobolG", diff --git a/src/uqtestfuns/test_functions/mclain.py b/src/uqtestfuns/test_functions/mclain.py index 00faa1f..32e8288 100644 --- a/src/uqtestfuns/test_functions/mclain.py +++ b/src/uqtestfuns/test_functions/mclain.py @@ -11,6 +11,7 @@ - S1: A part of a sphere - S2: A steep hill rising from a plain - S3: A less steep hill +- S4: A long narrow hill - S5: A plateau and plain separated by a steep cliff Four of the functions (S1-S3 and S5) appeared in modified forms in [2]. @@ -34,7 +35,7 @@ from ..core.uqtestfun_abc import UQTestFunABC from .available import create_prob_input_from_available -__all__ = ["McLainS1", "McLainS2", "McLainS3", "McLainS5"] +__all__ = ["McLainS1", "McLainS2", "McLainS3", "McLainS4", "McLainS5"] INPUT_MARGINALS_MCLAIN1974 = [ # From Ref. [1] UnivDist( @@ -96,34 +97,6 @@ def _init( UQTestFunABC.__init__(self, prob_input=prob_input, name=name) -def _eval_s1(uqtestfun: UQTestFunABC, xx: np.ndarray): - """Evaluate the McLain S1 function.""" - yy = np.sqrt(64 - (xx[:, 0] - 5.5) ** 2 - (xx[:, 1] - 5.5) ** 2) - - return yy - - -def _eval_s2(uqtestfun: UQTestFunABC, xx: np.ndarray): - """Evaluate the McLain S2 function.""" - yy = np.exp(-1.0 * ((xx[:, 0] - 5) ** 2 + (xx[:, 1] - 5) ** 2)) - - return yy - - -def _eval_s3(uqtestfun: UQTestFunABC, xx: np.ndarray): - """Evaluate the McLain S3 function.""" - yy = np.exp(-0.25 * ((xx[:, 0] - 5) ** 2 + (xx[:, 1] - 5) ** 2)) - - return yy - - -def _eval_s5(uqtestfun: UQTestFunABC, xx: np.ndarray): - """Evaluate the McLain S5 function.""" - yy = np.tanh(xx[:, 0] + xx[:, 1] - 11) - - return yy - - class McLainS1(UQTestFunABC): """A concrete implementation of the McLain S1 function. @@ -137,7 +110,26 @@ class McLainS1(UQTestFunABC): _DESCRIPTION = f"McLain S1 function {COMMON_METADATA['_DESCRIPTION']}" __init__ = _init # type: ignore - evaluate = _eval_s1 + + def evaluate(self, xx: np.ndarray): + """Evaluate the McLain S1 function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + Two-Dimensional input values given by N-by-2 arrays where + N is the number of input values. + + Returns + ------- + np.ndarray + The output of the McLain S1 function evaluated + on the input values. + The output is a 1-dimensional array of length N. + """ + yy = np.sqrt(64 - (xx[:, 0] - 5.5) ** 2 - (xx[:, 1] - 5.5) ** 2) + + return yy class McLainS2(UQTestFunABC): @@ -153,11 +145,29 @@ class McLainS2(UQTestFunABC): _DESCRIPTION = f"McLain S2 function {COMMON_METADATA['_DESCRIPTION']}" __init__ = _init # type: ignore - evaluate = _eval_s2 + + def evaluate(self, xx: np.ndarray): + """Evaluate the McLain S2 function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + Two-Dimensional input values given by N-by-2 arrays where + N is the number of input values. + + Returns + ------- + np.ndarray + The output of the McLain S2 function evaluated on the input values. + The output is a 1-dimensional array of length N. + """ + yy = np.exp(-1.0 * ((xx[:, 0] - 5) ** 2 + (xx[:, 1] - 5) ** 2)) + + return yy class McLainS3(UQTestFunABC): - """A concrete implementation of the McLain S2 function. + """A concrete implementation of the McLain S3 function. The function features a less steep hill (compared to S2). """ @@ -169,7 +179,65 @@ class McLainS3(UQTestFunABC): _DESCRIPTION = f"McLain S3 function {COMMON_METADATA['_DESCRIPTION']}" __init__ = _init # type: ignore - evaluate = _eval_s3 + + def evaluate(self, xx: np.ndarray): + """Evaluate the McLain S3 function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + Two-Dimensional input values given by N-by-2 arrays where + N is the number of input values. + + Returns + ------- + np.ndarray + The output of the McLain S3 function evaluated on the input values. + The output is a 1-dimensional array of length N. + """ + yy = np.exp(-0.25 * ((xx[:, 0] - 5) ** 2 + (xx[:, 1] - 5) ** 2)) + + return yy + + +class McLainS4(UQTestFunABC): + """A concrete implementation of the McLain S4 function. + + The function features a long narrow hill. + """ + + _TAGS = COMMON_METADATA["_TAGS"] + _AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"] + _AVAILABLE_PARAMETERS = COMMON_METADATA["_AVAILABLE_PARAMETERS"] + _DEFAULT_SPATIAL_DIMENSION = COMMON_METADATA["_DEFAULT_SPATIAL_DIMENSION"] + _DESCRIPTION = f"McLain S4 function {COMMON_METADATA['_DESCRIPTION']}" + + __init__ = _init # type: ignore + + def evaluate(self, xx: np.ndarray): + """Evaluate the McLain S4 function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + Two-Dimensional input values given by N-by-2 arrays where + N is the number of input values. + + Returns + ------- + np.ndarray + The output of the McLain S4 function evaluated on the input values. + The output is a 1-dimensional array of length N. + """ + yy = np.exp( + -1 + * ( + (xx[:, 0] + xx[:, 1] - 11) ** 2 + + (xx[:, 0] - xx[:, 1]) ** 2 / 10.0 + ) + ) + + return yy class McLainS5(UQTestFunABC): @@ -185,4 +253,22 @@ class McLainS5(UQTestFunABC): _DESCRIPTION = f"McLain S5 function {COMMON_METADATA['_DESCRIPTION']}" __init__ = _init # type: ignore - evaluate = _eval_s5 + + def evaluate(self, xx: np.ndarray): + """Evaluate the McLain S5 function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + Two-Dimensional input values given by N-by-2 arrays where + N is the number of input values. + + Returns + ------- + np.ndarray + The output of the McLain S5 function evaluated on the input values. + The output is a 1-dimensional array of length N. + """ + yy = np.tanh(xx[:, 0] + xx[:, 1] - 11) + + return yy From 0cd3e8901ac73fd88c9e9250cee631e5a1e838dc Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Mon, 5 Jun 2023 13:23:42 +0200 Subject: [PATCH 10/73] Update CHANGELOG.md. --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fad30a..26c5480 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,8 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 exercises, is added as a UQ test function. - The two-dimensional (third) Franke function, relevant for metamodeling exercises, is added as a UQ test function. -- The two-dimensional McLain functions (S1, S2, S3, S5), relevant for - metamodeling exercises, are added as UQ test functions. +- The two-dimensional McLain functions (S1, S2, S3, S4, and S5), + relevant for metamodeling exercises, are added as UQ test functions. ## [0.1.1] - 2023-07-03 From 438cc00b0da6bb44e9bc962dc3443d1f786c56f2 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Mon, 5 Jun 2023 15:50:26 +0200 Subject: [PATCH 11/73] Add an implementation of the (4th) Franke function. - Reorganize all the available Franke test functions into a single module. - Add a test suite for the McLain test functions to check the correctness of some analytical behaviors. - Fix issue with plotting 2D surface in the docs; wrong orientation. --- docs/_toc.yml | 2 + docs/fundamentals/metamodeling.md | 1 + docs/test-functions/ackley.md | 4 +- docs/test-functions/available.md | 1 + docs/test-functions/franke-1.md | 28 ++- docs/test-functions/franke-2.md | 38 ++- docs/test-functions/franke-3.md | 28 ++- docs/test-functions/franke-4.md | 165 ++++++++++++ docs/test-functions/mclain-s1.md | 6 +- docs/test-functions/mclain-s2.md | 6 +- docs/test-functions/mclain-s3.md | 9 +- docs/test-functions/mclain-s4.md | 4 +- docs/test-functions/mclain-s5.md | 6 +- docs/test-functions/sobol-g.md | 4 +- src/uqtestfuns/test_functions/__init__.py | 5 +- src/uqtestfuns/test_functions/franke.py | 265 ++++++++++++++++++++ src/uqtestfuns/test_functions/franke_1.py | 123 --------- src/uqtestfuns/test_functions/franke_2.py | 114 --------- src/uqtestfuns/test_functions/franke_3.py | 112 --------- src/uqtestfuns/test_functions/mclain.py | 1 - tests/builtin_test_functions/test_franke.py | 51 ++++ tests/builtin_test_functions/test_mclain.py | 102 ++++++++ 22 files changed, 674 insertions(+), 401 deletions(-) create mode 100644 docs/test-functions/franke-4.md create mode 100644 src/uqtestfuns/test_functions/franke.py delete mode 100644 src/uqtestfuns/test_functions/franke_1.py delete mode 100644 src/uqtestfuns/test_functions/franke_2.py delete mode 100644 src/uqtestfuns/test_functions/franke_3.py create mode 100644 tests/builtin_test_functions/test_franke.py create mode 100644 tests/builtin_test_functions/test_mclain.py diff --git a/docs/_toc.yml b/docs/_toc.yml index 28973c3..093348f 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -35,6 +35,8 @@ parts: title: (2nd) Franke - file: test-functions/franke-3 title: (3rd) Franke + - file: test-functions/franke-4 + title: (4th) Franke - file: test-functions/ishigami title: Ishigami - file: test-functions/oakley-ohagan-1d diff --git a/docs/fundamentals/metamodeling.md b/docs/fundamentals/metamodeling.md index 984abb8..1a252af 100644 --- a/docs/fundamentals/metamodeling.md +++ b/docs/fundamentals/metamodeling.md @@ -26,6 +26,7 @@ in the comparison of metamodeling approaches. | {ref}`(1st) Franke ` | 2 | `Franke1()` | | {ref}`(2nd) Franke ` | 2 | `Franke2()` | | {ref}`(3rd) Franke ` | 2 | `Franke3()` | +| {ref}`(4th) Franke ` | 2 | `Franke4()` | | {ref}`Oakley-O'Hagan 1D ` | 1 | `OakleyOHagan1D()` | | {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | | {ref}`McLain S1 ` | 2 | `McLainS1()` | diff --git a/docs/test-functions/ackley.md b/docs/test-functions/ackley.md index 43dd214..22f91a7 100644 --- a/docs/test-functions/ackley.md +++ b/docs/test-functions/ackley.md @@ -62,7 +62,7 @@ axs_2 = plt.subplot(132, projection='3d') axs_2.plot_surface( mesh_2d[0], mesh_2d[1], - yy_2d.reshape(1000,1000), + yy_2d.reshape(1000,1000).T, linewidth=0, cmap="plasma", antialiased=False, @@ -76,7 +76,7 @@ axs_2.set_title("Surface plot of 2D Ackley", fontsize=14) # Contour axs_3 = plt.subplot(133) cf = axs_3.contourf( - mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000), cmap="plasma" + mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000).T, cmap="plasma" ) axs_3.set_xlabel("$x_1$", fontsize=14) axs_3.set_ylabel("$x_2$", fontsize=14) diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index 795de16..dd11b9c 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -27,6 +27,7 @@ available in the current UQTestFuns, regardless of their typical applications. | {ref}`(1st) Franke ` | 2 | `Franke1()` | | {ref}`(2nd) Franke ` | 2 | `Franke2()` | | {ref}`(3rd) Franke ` | 2 | `Franke3()` | +| {ref}`(4th) Franke ` | 2 | `Franke4()` | | {ref}`Ishigami ` | 3 | `Ishigami()` | | {ref}`Oakley-O'Hagan 1D ` | 1 | `OakleyOHagan1D()` | | {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | diff --git a/docs/test-functions/franke-1.md b/docs/test-functions/franke-1.md index e8ff933..b70d4c2 100644 --- a/docs/test-functions/franke-1.md +++ b/docs/test-functions/franke-1.md @@ -21,15 +21,27 @@ import matplotlib.pyplot as plt import uqtestfuns as uqtf ``` -The (first) Franke function is a two-dimensional scalar-valued function. +The (1st) Franke function is a two-dimensional scalar-valued function. The function was first introduced in {cite}`Franke1979` in the context of interpolation problem and was used in {cite}`Haaland2011` in the context of metamodeling. +```{note} The Franke's original report {cite}`Franke1979` contains in total -six two-dimensional test functions. -The first function that appeared in the report is commonly known simply as -the "Franke function" (without further specification). +six two-dimensional test functions: + +- {ref}`(1st) Franke function `: Two Gaussian peaks + and a Gaussian dip on a surface slopping down the upper right boundary + (_this function_) +- {ref}`(2nd) Franke function `: Two nearly flat + regions joined by a sharp rise running diagonally +- {ref}`(3rd) Franke function `: A saddle shaped + surface +- {ref}`(4th) Franke function `: A Gaussian hill + that slopes off in a gentle fashion + +The term "Franke function" typically only refers to the (1st) Franke function. +``` ```{code-cell} ipython3 :tags: [remove-input] @@ -52,7 +64,7 @@ axs_1 = plt.subplot(121, projection='3d') axs_1.plot_surface( mesh_2d[0], mesh_2d[1], - yy_2d.reshape(1000,1000), + yy_2d.reshape(1000,1000).T, linewidth=0, cmap="plasma", antialiased=False, @@ -66,7 +78,7 @@ axs_1.set_title("Surface plot of (1st) Franke", fontsize=14) # Contour axs_2 = plt.subplot(122) cf = axs_2.contourf( - mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000), cmap="plasma" + mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000).T, cmap="plasma" ) axs_2.set_xlabel("$x_1$", fontsize=14) axs_2.set_ylabel("$x_2$", fontsize=14) @@ -82,7 +94,7 @@ plt.gcf().set_dpi(75); As shown in the plots above, the surface consists of two Gaussian peaks and a Gaussian dip on a surface sloping down toward the upper right boundary -(i.e., $(1.0, 1.0)$). +(i.e., at $(1.0, 1.0)$). ## Test function instance @@ -149,4 +161,4 @@ plt.gcf().set_dpi(150); ```{bibliography} :style: plain :filter: docname in docnames -``` \ No newline at end of file +``` diff --git a/docs/test-functions/franke-2.md b/docs/test-functions/franke-2.md index 646ab86..7887a75 100644 --- a/docs/test-functions/franke-2.md +++ b/docs/test-functions/franke-2.md @@ -21,17 +21,24 @@ import matplotlib.pyplot as plt import uqtestfuns as uqtf ``` -The (second) Franke function is a two-dimensional scalar-valued function. +The (2nd) Franke function is a two-dimensional scalar-valued function. The function was introduced in {cite}`Franke1979` in the context of -interpolation problem. It is based on a similar function used in -{cite}`McLain1974` (specifically, the function S5) also in the context of -interpolation problem albeit in a modified form. +interpolation problem. ```{note} The Franke's original report {cite}`Franke1979` contains in total -six two-dimensional test functions. -The first function that appeared in the report is commonly known simply as -the "Franke function" (without further specification). +six two-dimensional test functions: + +- {ref}`(1st) Franke function `: Two Gaussian peaks + and a Gaussian dip on a surface slopping down the upper right boundary +- {ref}`(2nd) Franke function `: Two nearly flat + regions joined by a sharp rise running diagonally (_this function_) +- {ref}`(3rd) Franke function `: A saddle shaped + surface +- {ref}`(4th) Franke function `: A Gaussian hill + that slopes off in a gentle fashion + +The term "Franke function" typically only refers to the (1st) Franke function. ``` ```{code-cell} ipython3 @@ -55,7 +62,7 @@ axs_1 = plt.subplot(121, projection='3d') axs_1.plot_surface( mesh_2d[0], mesh_2d[1], - yy_2d.reshape(1000,1000), + yy_2d.reshape(1000,1000).T, linewidth=0, cmap="plasma", antialiased=False, @@ -69,7 +76,7 @@ axs_1.set_title("Surface plot of (2nd) Franke", fontsize=14) # Contour axs_2 = plt.subplot(122) cf = axs_2.contourf( - mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000), cmap="plasma" + mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000).T, cmap="plasma" ) axs_2.set_xlabel("$x_1$", fontsize=14) axs_2.set_ylabel("$x_2$", fontsize=14) @@ -88,9 +95,18 @@ of height $0.0$ and (approximately) $\frac{2}{9}$. The two regions are joined by a sharp rise that runs diagonally from $(0.0, 0.0)$ to $(1.0, 1.0)$. +```{note} +The (2nd) Franke function is a modified form of the {ref}`McLain S5 function ` +{cite}`McLain1974`. + +Specifically, the domain of the function is translated from $[1.0, 10.0]^2$ +to $[0.0, 1.0]^2$ with some additional slight modifications to "enhance the +visual aspects" of the resulting surfaces. +``` + ## Test function instance -To create a default instance of the (second) Franke function: +To create a default instance of the (2nd) Franke function: ```{code-cell} ipython3 my_testfun = uqtf.Franke2() @@ -148,4 +164,4 @@ plt.gcf().set_dpi(150); ```{bibliography} :style: plain :filter: docname in docnames -``` \ No newline at end of file +``` diff --git a/docs/test-functions/franke-3.md b/docs/test-functions/franke-3.md index f2c2270..480e72b 100644 --- a/docs/test-functions/franke-3.md +++ b/docs/test-functions/franke-3.md @@ -21,16 +21,24 @@ import matplotlib.pyplot as plt import uqtestfuns as uqtf ``` -The (third) Franke function is a two-dimensional scalar-valued function. +The (3rd) Franke function is a two-dimensional scalar-valued function. The function was first introduced in {cite}`Franke1979` in the context of interpolation problem. ```{note} The Franke's original report {cite}`Franke1979` contains in total -six two-dimensional test functions. -The first function that appeared in the report is commonly known simply as -the "{ref}`Franke function `" (without further -specification). +six two-dimensional test functions: + +- {ref}`(1st) Franke function `: Two Gaussian peaks + and a Gaussian dip on a surface slopping down the upper right boundary +- {ref}`(2nd) Franke function `: Two nearly flat + regions joined by a sharp rise running diagonally +- {ref}`(3rd) Franke function `: A saddle shaped + surface (_this function_) +- {ref}`(4th) Franke function `: A Gaussian hill + that slopes off in a gentle fashion + +The term "Franke function" typically only refers to the (1st) Franke function. ``` ```{code-cell} ipython3 @@ -54,7 +62,7 @@ axs_1 = plt.subplot(121, projection='3d') axs_1.plot_surface( mesh_2d[0], mesh_2d[1], - yy_2d.reshape(1000,1000), + yy_2d.reshape(1000,1000).T, linewidth=0, cmap="plasma", antialiased=False, @@ -68,7 +76,7 @@ axs_1.set_title("Surface plot of (3rd) Franke", fontsize=14) # Contour axs_2 = plt.subplot(122) cf = axs_2.contourf( - mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000), cmap="plasma" + mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000).T, cmap="plasma" ) axs_2.set_xlabel("$x_1$", fontsize=14) axs_2.set_ylabel("$x_2$", fontsize=14) @@ -86,7 +94,7 @@ As shown in the plots above, the function features a saddle shaped surface. ## Test function instance -To create a default instance of the (third) Franke function: +To create a default instance of the (3rd) Franke function: ```{code-cell} ipython3 my_testfun = uqtf.Franke3() @@ -100,7 +108,7 @@ print(my_testfun) ## Description -The Franke function is defined as follows: +The (3rd) Franke function is defined as follows: $$ \mathcal{M}(\boldsymbol{x}) = \frac{1.25 + \cos{(5.4 x_2)}}{6 (1 + (3 x_1 - 1)^2)} @@ -144,4 +152,4 @@ plt.gcf().set_dpi(150); ```{bibliography} :style: plain :filter: docname in docnames -``` \ No newline at end of file +``` diff --git a/docs/test-functions/franke-4.md b/docs/test-functions/franke-4.md new file mode 100644 index 0000000..5d839dd --- /dev/null +++ b/docs/test-functions/franke-4.md @@ -0,0 +1,165 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:franke-4)= +# (4th) Franke Function + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +The (4th) Franke function is a two-dimensional scalar-valued function. +The function was first introduced in {cite}`Franke1979` in the context of +interpolation problem. + +```{note} +The Franke's original report {cite}`Franke1979` contains in total +six two-dimensional test functions: + +- {ref}`(1st) Franke function `: Two Gaussian peaks + and a Gaussian dip on a surface slopping down the upper right boundary +- {ref}`(2nd) Franke function `: Two nearly flat + regions joined by a sharp rise running diagonally +- {ref}`(3rd) Franke function `: A saddle shaped + surface +- {ref}`(4th) Franke function `: A Gaussian hill + that slopes off in a gentle fashion (_this function_) + +The term "Franke function" typically only refers to the (1st) Franke function. +``` + +```{code-cell} ipython3 +:tags: [remove-input] + +from mpl_toolkits.axes_grid1 import make_axes_locatable + +my_fun = uqtf.Franke4() + +# --- Create 2D data +xx_1d = np.linspace(0.0, 1.0, 1000)[:, np.newaxis] +mesh_2d = np.meshgrid(xx_1d, xx_1d) +xx_2d = np.array(mesh_2d).T.reshape(-1, 2) +yy_2d = my_fun(xx_2d) + +# --- Create two-dimensional plots +fig = plt.figure(figsize=(10, 5)) + +# Surface +axs_1 = plt.subplot(121, projection='3d') +axs_1.plot_surface( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000,1000).T, + linewidth=0, + cmap="plasma", + antialiased=False, + alpha=0.5 +) +axs_1.set_xlabel("$x_1$", fontsize=14) +axs_1.set_ylabel("$x_2$", fontsize=14) +axs_1.set_zlabel("$\mathcal{M}(x_1, x_2)$", fontsize=14) +axs_1.set_title("Surface plot of (4th) Franke", fontsize=14) + +# Contour +axs_2 = plt.subplot(122) +cf = axs_2.contourf( + mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000).T, cmap="plasma" +) +axs_2.set_xlabel("$x_1$", fontsize=14) +axs_2.set_ylabel("$x_2$", fontsize=14) +axs_2.set_title("Contour plot of (4th) Franke", fontsize=14) +divider = make_axes_locatable(axs_2) +cax = divider.append_axes('right', size='5%', pad=0.05) +fig.colorbar(cf, cax=cax, orientation='vertical') +axs_2.axis('scaled') + +fig.tight_layout(pad=4.0) +plt.gcf().set_dpi(75); +``` + +As shown in the plots above, the function features a Gaussian hill that slopes +in a gentle fashion. + +```{note} +The (4th) Franke function is a modified form of the {ref}`McLain S3 function ` +{cite}`McLain1974`. + +Specifically, the domain of the function is translated from $[1.0, 10.0]^2$ +to $[0.0, 1.0]^2$ with some additional slight modifications to "enhance the +visual aspects" of the resulting surfaces. +``` + +## Test function instance + +To create a default instance of the (4th) Franke function: + +```{code-cell} ipython3 +my_testfun = uqtf.Franke4() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +## Description + +The (4th) Franke function is defined as follows: + +$$ +\mathcal{M}(\boldsymbol{x}) = \frac{1}{3} \exp{\left[ -\frac{81}{16} \left( (x_1 - 0.5)^2 + (x_2 - 0.5)^2 \right) \right]} +$$ +where $\boldsymbol{x} = \{ x_1, x_2 \}$ +is the two-dimensional vector of input variables further defined below. + +## Probabilistic input + +Based on {cite}`Franke1979`, the probabilistic input model +for the function consists of two independent random variables as shown below. + +```{code-cell} ipython3 +my_testfun.prob_input +``` + +## Reference results + +This section provides several reference results of typical UQ analyses involving +the test function. + +### Sample histogram + +Shown below is the histogram of the output based on $100'000$ random points: + +```{code-cell} ipython3 +:tags: [hide-input] + +xx_test = my_testfun.prob_input.get_sample(100000) +yy_test = my_testfun(xx_test) + +plt.hist(yy_test, bins="auto", color="#8da0cb"); +plt.grid(); +plt.ylabel("Counts [-]"); +plt.xlabel("$\mathcal{M}(\mathbf{X})$"); +plt.gcf().set_dpi(150); +``` + +## References + +```{bibliography} +:style: plain +:filter: docname in docnames +``` diff --git a/docs/test-functions/mclain-s1.md b/docs/test-functions/mclain-s1.md index 4795371..319a563 100644 --- a/docs/test-functions/mclain-s1.md +++ b/docs/test-functions/mclain-s1.md @@ -57,7 +57,7 @@ axs_1 = plt.subplot(121, projection='3d') axs_1.plot_surface( mesh_2d[0], mesh_2d[1], - yy_2d.reshape(1000,1000), + yy_2d.reshape(1000, 1000).T, linewidth=0, cmap="plasma", antialiased=False, @@ -71,7 +71,7 @@ axs_1.set_title("Surface plot of McLain S1", fontsize=14) # Contour axs_2 = plt.subplot(122) cf = axs_2.contourf( - mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000), cmap="plasma" + mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000).T, cmap="plasma" ) axs_2.set_xlabel("$x_1$", fontsize=14) axs_2.set_ylabel("$x_2$", fontsize=14) @@ -95,7 +95,7 @@ of Franke {cite}`Franke1979` (specifically the (6th) Franke function). In fact, four of the Franke's test functions (2, 4, 5, and 6) are slight modifications of the McLain's, including the translation -of the input domain from $[1.0, 10.0]$ to $[0.0, 1.0]$. +of the input domain from $[1.0, 10.0]^2$ to $[0.0, 1.0]^2$. ``` ## Test function instance diff --git a/docs/test-functions/mclain-s2.md b/docs/test-functions/mclain-s2.md index f7b26cf..08d2795 100644 --- a/docs/test-functions/mclain-s2.md +++ b/docs/test-functions/mclain-s2.md @@ -57,7 +57,7 @@ axs_1 = plt.subplot(121, projection='3d') axs_1.plot_surface( mesh_2d[0], mesh_2d[1], - yy_2d.reshape(1000,1000), + yy_2d.reshape(1000, 1000).T, linewidth=0, cmap="plasma", antialiased=False, @@ -71,7 +71,7 @@ axs_1.set_title("Surface plot of McLain S2", fontsize=14) # Contour axs_2 = plt.subplot(122) cf = axs_2.contourf( - mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000), cmap="plasma" + mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000).T, cmap="plasma" ) axs_2.set_xlabel("$x_1$", fontsize=14) axs_2.set_ylabel("$x_2$", fontsize=14) @@ -95,7 +95,7 @@ of Franke {cite}`Franke1979` (specifically the (5th) Franke function). In fact, four of the Franke's test functions (2, 4, 5, and 6) are slight modifications of the McLain's, including the translation -of the input domain from $[1.0, 10.0]$ to $[0.0, 1.0]$. +of the input domain from $[1.0, 10.0]^2$ to $[0.0, 1.0]^2$. ``` ## Test function instance diff --git a/docs/test-functions/mclain-s3.md b/docs/test-functions/mclain-s3.md index 280ce99..baa8dbc 100644 --- a/docs/test-functions/mclain-s3.md +++ b/docs/test-functions/mclain-s3.md @@ -57,7 +57,7 @@ axs_1 = plt.subplot(121, projection='3d') axs_1.plot_surface( mesh_2d[0], mesh_2d[1], - yy_2d.reshape(1000,1000), + yy_2d.reshape(1000, 1000).T, linewidth=0, cmap="plasma", antialiased=False, @@ -71,7 +71,7 @@ axs_1.set_title("Surface plot of McLain S3", fontsize=14) # Contour axs_2 = plt.subplot(122) cf = axs_2.contourf( - mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000), cmap="plasma" + mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000).T, cmap="plasma" ) axs_2.set_xlabel("$x_1$", fontsize=14) axs_2.set_ylabel("$x_2$", fontsize=14) @@ -92,11 +92,12 @@ and with the maximum height of $1.0$. ```{note} The McLain S3 function appeared in a modified form in the report -of Franke {cite}`Franke1979` (specifically the (4th) Franke function). +of Franke {cite}`Franke1979` +(specifically the {ref}`(4th) Franke function `). In fact, four of the Franke's test functions (2, 4, 5, and 6) are slight modifications of McLain's, including the translation of the input domain -from $[1.0, 10.0]$ to $[0.0, 1.0]$. +from $[1.0, 10.0]^2$ to $[0.0, 1.0]^2$. ``` ## Test function instance diff --git a/docs/test-functions/mclain-s4.md b/docs/test-functions/mclain-s4.md index 21a30fc..4ed53f8 100644 --- a/docs/test-functions/mclain-s4.md +++ b/docs/test-functions/mclain-s4.md @@ -57,7 +57,7 @@ axs_1 = plt.subplot(121, projection='3d') axs_1.plot_surface( mesh_2d[0], mesh_2d[1], - yy_2d.reshape(1000,1000), + yy_2d.reshape(1000, 1000).T, linewidth=0, cmap="plasma", antialiased=False, @@ -71,7 +71,7 @@ axs_1.set_title("Surface plot of McLain S4", fontsize=14) # Contour axs_2 = plt.subplot(122) cf = axs_2.contourf( - mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000), cmap="plasma" + mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000).T, cmap="plasma" ) axs_2.set_xlabel("$x_1$", fontsize=14) axs_2.set_ylabel("$x_2$", fontsize=14) diff --git a/docs/test-functions/mclain-s5.md b/docs/test-functions/mclain-s5.md index aac987e..9b94b00 100644 --- a/docs/test-functions/mclain-s5.md +++ b/docs/test-functions/mclain-s5.md @@ -57,7 +57,7 @@ axs_1 = plt.subplot(121, projection='3d') axs_1.plot_surface( mesh_2d[0], mesh_2d[1], - yy_2d.reshape(1000,1000), + yy_2d.reshape(1000, 1000).T, linewidth=0, cmap="plasma", antialiased=False, @@ -71,7 +71,7 @@ axs_1.set_title("Surface plot of McLain S5", fontsize=14) # Contour axs_2 = plt.subplot(122) cf = axs_2.contourf( - mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000), cmap="plasma" + mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000).T, cmap="plasma" ) axs_2.set_xlabel("$x_1$", fontsize=14) axs_2.set_ylabel("$x_2$", fontsize=14) @@ -97,7 +97,7 @@ of Franke {cite}`Franke1979` In fact, four of the Franke's test functions (2, 4, 5, and 6) are slight modifications of the McLain's, including the translation -of the input domain from $[1.0, 10.0]$ to $[0.0, 1.0]$. +of the input domain from $[1.0, 10.0]^2$ to $[0.0, 1.0]^2$. ``` ## Test function instance diff --git a/docs/test-functions/sobol-g.md b/docs/test-functions/sobol-g.md index 30f76ee..a62bb8a 100644 --- a/docs/test-functions/sobol-g.md +++ b/docs/test-functions/sobol-g.md @@ -62,7 +62,7 @@ axs_2 = plt.subplot(132, projection='3d') axs_2.plot_surface( mesh_2d[0], mesh_2d[1], - yy_2d.reshape(1000,1000), + yy_2d.reshape(1000, 1000).T, cmap="plasma", linewidth=0, antialiased=False, @@ -76,7 +76,7 @@ axs_2.set_title("Surface plot of 2D Sobol'-G", fontsize=14) # Contour axs_3 = plt.subplot(133) cf = axs_3.contourf( - mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000), cmap="plasma" + mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000).T, cmap="plasma" ) axs_3.set_xlabel("$x_1$", fontsize=14) axs_3.set_ylabel("$x_2$", fontsize=14) diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index de2732c..c11c795 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -4,9 +4,7 @@ from .ackley import Ackley from .borehole import Borehole from .damped_oscillator import DampedOscillator -from .franke_1 import Franke1 -from .franke_2 import Franke2 -from .franke_3 import Franke3 +from .franke import Franke1, Franke2, Franke3, Franke4 from .flood import Flood from .ishigami import Ishigami from .oakley_ohagan_1d import OakleyOHagan1D @@ -28,6 +26,7 @@ "Franke1", "Franke2", "Franke3", + "Franke4", "Ishigami", "OakleyOHagan1D", "OTLCircuit", diff --git a/src/uqtestfuns/test_functions/franke.py b/src/uqtestfuns/test_functions/franke.py new file mode 100644 index 0000000..494bc8b --- /dev/null +++ b/src/uqtestfuns/test_functions/franke.py @@ -0,0 +1,265 @@ +""" +Module with an implementation of the Franke's test function. + +The Franke's test functions are a set of six two-dimensional scalar-valued +functions. The functions were introduced in [1] in the context of +scattered data interpolation and was used in the context of metamodeling +in [2]. + +The six functions are: + +- (1st) Franke function: Two Gaussian peaks and a Gaussian dip on a surface + slopping down toward the upper right boundary. +- (2nd) Franke function: Two nearly flat regions joined by a sharp rise running + diagonally. +- (3rd) Franke function: A saddle shaped surface. +- (4th) Franke function: A Gaussian hill that slopes off in a gentle fashion. + +Four of the Franke functions, namely the 2nd, 4th, 5th, and 6th are modified +from [3] (namely, S5, S3, S2 and S1, respectively). + +While the Franke's original report [1] contains in total six two-dimensional +test functions, only the first Franke function is commonly known +as the "Franke function". + +References +---------- + +1. Richard Franke, "A critical comparison of some methods for interpolation + of scattered data," Naval Postgraduate School, Monterey, Canada, + Technical Report No. NPS53-79-003, 1979. + URL: https://core.ac.uk/reader/36727660 +2. Ben Haaland and Peter Z. G. Qian, “Accurate emulators for large-scale + computer experiments,” The Annals of Statistics, vol. 39, no. 6, + pp. 2974-3002, 2011. DOI: 10.1214/11-AOS929 +3. D. H. McLain, "Drawing contours from arbitrary data points," The Computer + Journal, vol. 17, no. 4, pp. 318-324, 1974. + DOI: 10.1093/comjnl/17.4.318 +""" +import numpy as np + +from typing import Optional + +from ..core.prob_input.univariate_distribution import UnivDist +from ..core.uqtestfun_abc import UQTestFunABC +from .available import create_prob_input_from_available + +__all__ = ["Franke1", "Franke2", "Franke3", "Franke4"] + +INPUT_MARGINALS_FRANKE1979 = [ # From Ref. [1] + UnivDist( + name="X1", + distribution="uniform", + parameters=[0.0, 1.0], + description="None", + ), + UnivDist( + name="X2", + distribution="uniform", + parameters=[0.0, 1.0], + description="None", + ), +] + +AVAILABLE_INPUT_SPECS = { + "Franke1979": { + "name": "Franke-1979", + "description": ( + "Input specification for the Franke's test functions " + "from Franke (1979)." + ), + "marginals": INPUT_MARGINALS_FRANKE1979, + "copulas": None, + } +} + +DEFAULT_INPUT_SELECTION = "Franke1979" + +COMMON_METADATA = dict( + _TAGS=["metamodeling"], + _AVAILABLE_INPUTS=tuple(AVAILABLE_INPUT_SPECS.keys()), + _AVAILABLE_PARAMETERS=None, + _DEFAULT_SPATIAL_DIMENSION=2, + _DESCRIPTION="from Franke (1979)", +) + + +def _init( + self, + *, + prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, + name: Optional[str] = None, + rng_seed_prob_input: Optional[int] = None, +) -> None: + """A common __init__ for all Franke's test functions.""" + # --- Arguments processing + prob_input = create_prob_input_from_available( + prob_input_selection, + AVAILABLE_INPUT_SPECS, + rng_seed=rng_seed_prob_input, + ) + # Process the default name + if name is None: + name = self.__class__.__name__ + + UQTestFunABC.__init__(self, prob_input=prob_input, name=name) + + +class Franke1(UQTestFunABC): + """A concrete implementation of the (1st) Franke function. + + The function features two Gaussian peaks and a Gaussian dip. + """ + + _TAGS = COMMON_METADATA["_TAGS"] + _AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"] + _AVAILABLE_PARAMETERS = COMMON_METADATA["_AVAILABLE_PARAMETERS"] + _DEFAULT_SPATIAL_DIMENSION = COMMON_METADATA["_DEFAULT_SPATIAL_DIMENSION"] + _DESCRIPTION = f"(1st) Franke function {COMMON_METADATA['_DESCRIPTION']}" + + __init__ = _init # type: ignore + + def evaluate(self, xx: np.ndarray): + """Evaluate the (1st) Franke function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + Two-Dimensional input values given by N-by-2 arrays where + N is the number of input values. + + Returns + ------- + np.ndarray + The output of the (1st) Franke function evaluated + on the input values. + The output is a 1-dimensional array of length N. + """ + + xx0 = 9 * xx[:, 0] + xx1 = 9 * xx[:, 1] + + # Compute the (first) Franke function + term_1 = 0.75 * np.exp(-0.25 * ((xx0 - 2) ** 2 + (xx1 - 2) ** 2)) + term_2 = 0.75 * np.exp( + -1.00 * ((xx0 + 1) ** 2 / 49.0 + (xx1 + 1) ** 2 / 10.0) + ) + term_3 = 0.50 * np.exp(-0.25 * ((xx0 - 7) ** 2 + (xx1 - 3) ** 2)) + term_4 = 0.20 * np.exp(-1.00 * ((xx0 - 4) ** 2 + (xx1 - 7) ** 2)) + + yy = term_1 + term_2 + term_3 - term_4 + + return yy + + +class Franke2(UQTestFunABC): + """A concrete implementation of the (2nd) Franke function. + + The function features two plateaus joined by a steep hill. + """ + + _TAGS = COMMON_METADATA["_TAGS"] + _AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"] + _AVAILABLE_PARAMETERS = COMMON_METADATA["_AVAILABLE_PARAMETERS"] + _DEFAULT_SPATIAL_DIMENSION = COMMON_METADATA["_DEFAULT_SPATIAL_DIMENSION"] + _DESCRIPTION = f"(2nd) Franke function {COMMON_METADATA['_DESCRIPTION']}" + + __init__ = _init # type: ignore + + def evaluate(self, xx: np.ndarray): + """Evaluate the (2nd) Franke function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + Two-Dimensional input values given by N-by-2 arrays where + N is the number of input values. + + Returns + ------- + np.ndarray + The output of the (2nd) Franke function evaluated + on the input values. + The output is a 1-dimensional array of length N. + """ + yy = (np.tanh(9 * (xx[:, 1] - xx[:, 0])) + 1) / 9.0 + + return yy + + +class Franke3(UQTestFunABC): + """A concrete implementation of the (3rd) Franke function. + + The function features a saddle shaped surface. + """ + + _TAGS = COMMON_METADATA["_TAGS"] + _AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"] + _AVAILABLE_PARAMETERS = COMMON_METADATA["_AVAILABLE_PARAMETERS"] + _DEFAULT_SPATIAL_DIMENSION = COMMON_METADATA["_DEFAULT_SPATIAL_DIMENSION"] + _DESCRIPTION = f"(3rd) Franke function {COMMON_METADATA['_DESCRIPTION']}" + + __init__ = _init # type: ignore + + def evaluate(self, xx: np.ndarray): + """Evaluate the (3rd) Franke function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + Two-Dimensional input values given by N-by-2 arrays where + N is the number of input values. + + Returns + ------- + np.ndarray + The output of the (3rd) Franke function evaluated + on the input values. + The output is a 1-dimensional array of length N. + """ + term_1 = 1.25 + np.cos(5.4 * xx[:, 1]) + term_2 = 6 * (1 + (3 * xx[:, 0] - 1) ** 2) + + yy = term_1 / term_2 + + return yy + + +class Franke4(UQTestFunABC): + """A concrete implementation of the (4th) Franke function. + + The function features a gentle Gaussian hill. + """ + + _TAGS = COMMON_METADATA["_TAGS"] + _AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"] + _AVAILABLE_PARAMETERS = COMMON_METADATA["_AVAILABLE_PARAMETERS"] + _DEFAULT_SPATIAL_DIMENSION = COMMON_METADATA["_DEFAULT_SPATIAL_DIMENSION"] + _DESCRIPTION = f"(4th) Franke function {COMMON_METADATA['_DESCRIPTION']}" + + __init__ = _init # type: ignore + + def evaluate(self, xx: np.ndarray): + """Evaluate the (4th) Franke function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + Two-Dimensional input values given by N-by-2 arrays where + N is the number of input values. + + Returns + ------- + np.ndarray + The output of the (4th) Franke function evaluated + on the input values. + The output is a 1-dimensional array of length N. + """ + yy = ( + np.exp( + -81.0 / 16.0 * ((xx[:, 0] - 0.5) ** 2 + (xx[:, 1] - 0.5) ** 2) + ) + / 3.0 + ) + + return yy diff --git a/src/uqtestfuns/test_functions/franke_1.py b/src/uqtestfuns/test_functions/franke_1.py deleted file mode 100644 index 5274f59..0000000 --- a/src/uqtestfuns/test_functions/franke_1.py +++ /dev/null @@ -1,123 +0,0 @@ -""" -Module with an implementation of the (first) Franke function. - -The (first) Franke function is a two-dimensional scalar-valued function. -The function was first introduced in [1] in the context of interpolation -problem and was used in the context of metamodeling in [2]. - -The Franke's original report [1] contains in total six two-dimensional test -functions. The first function that appeared in the report is commonly known -as the "Franke function". - -References ----------- - -1. Richard Franke, "A critical comparison of some methods for interpolation - of scattered data," Naval Postgraduate School, Monterey, Canada, - Technical Report No. NPS53-79-003, 1979. - URL: https://core.ac.uk/reader/36727660 -2. Ben Haaland and Peter Z. G. Qian, “Accurate emulators for large-scale - computer experiments,” The Annals of Statistics, vol. 39, no. 6, - pp. 2974-3002, 2011. DOI: 10.1214/11-AOS929 -""" -import numpy as np - -from typing import Optional - -from ..core.prob_input.univariate_distribution import UnivDist -from ..core.uqtestfun_abc import UQTestFunABC -from .available import create_prob_input_from_available - -__all__ = ["Franke1"] - -INPUT_MARGINALS_FRANKE1979 = [ # From Ref. [1] - UnivDist( - name="X1", - distribution="uniform", - parameters=[0.0, 1.0], - ), - UnivDist( - name="X2", - distribution="uniform", - parameters=[0.0, 1.0], - ), -] - -AVAILABLE_INPUT_SPECS = { - "Franke1979": { - "name": "Franke-1979", - "description": ( - "Input specification for the (first) Franke function " - "from Franke (1979)." - ), - "marginals": INPUT_MARGINALS_FRANKE1979, - "copulas": None, - } -} - -DEFAULT_INPUT_SELECTION = "Franke1979" - - -class Franke1(UQTestFunABC): - """A concrete implementation of the (first) Franke function.""" - - _TAGS = ["metamodeling"] - - _AVAILABLE_INPUTS = tuple(AVAILABLE_INPUT_SPECS.keys()) - - _AVAILABLE_PARAMETERS = None - - _DEFAULT_SPATIAL_DIMENSION = 2 - - _DESCRIPTION = "(First) Franke function from Franke (1979)" - - def __init__( - self, - *, - prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, - name: Optional[str] = None, - rng_seed_prob_input: Optional[int] = None, - ): - # --- Arguments processing - prob_input = create_prob_input_from_available( - prob_input_selection, - AVAILABLE_INPUT_SPECS, - rng_seed=rng_seed_prob_input, - ) - # Process the default name - if name is None: - name = Franke1.__name__ - - super().__init__(prob_input=prob_input, name=name) - - def evaluate(self, xx: np.ndarray): - """Evaluate the (first) Franke function on a set of input values. - - Parameters - ---------- - xx : np.ndarray - Two-Dimensional input values given by N-by-2 arrays where - N is the number of input values. - - Returns - ------- - np.ndarray - The output of the (first) Franke function evaluated - on the input values. - The output is a 1-dimensional array of length N. - """ - - xx0 = 9 * xx[:, 0] - xx1 = 9 * xx[:, 1] - - # Compute the (first) Franke function - term_1 = 0.75 * np.exp(-0.25 * ((xx0 - 2) ** 2 + (xx1 - 2) ** 2)) - term_2 = 0.75 * np.exp( - -1.00 * ((xx0 + 1) ** 2 / 49.0 + (xx1 + 1) ** 2 / 10.0) - ) - term_3 = 0.50 * np.exp(-0.25 * ((xx0 - 7) ** 2 + (xx1 - 3) ** 2)) - term_4 = 0.20 * np.exp(-1.00 * ((xx0 - 4) ** 2 + (xx1 - 7) ** 2)) - - yy = term_1 + term_2 + term_3 - term_4 - - return yy diff --git a/src/uqtestfuns/test_functions/franke_2.py b/src/uqtestfuns/test_functions/franke_2.py deleted file mode 100644 index 1a2fb78..0000000 --- a/src/uqtestfuns/test_functions/franke_2.py +++ /dev/null @@ -1,114 +0,0 @@ -""" -Module with an implementation of the (second) Franke function. - -The (second) Franke function is a two-dimensional scalar-valued function. -The function was introduced in [1] in the context of interpolation -problem. The function was adapted from the test function S5 in [2] by -translating its domain and modifying the function slightly to -"enchance the visual aspects of the surface". - -The Franke's original report [1] contains in total six two-dimensional test -functions. The first function that appeared in the report is commonly known -as the "Franke function". - -References ----------- - -1. Richard Franke, "A critical comparison of some methods for interpolation - of scattered data," Naval Postgraduate School, Monterey, Canada, - Technical Report No. NPS53-79-003, 1979. - URL: https://core.ac.uk/reader/36727660 -2. D. H. McLain, "Drawing contours from arbitrary data points," The Computer - Journal, vol. 17, no. 4, pp. 318-324, 1974. - DOI: 10.1093/comjnl/17.4.318 -""" -import numpy as np - -from typing import Optional - -from ..core.prob_input.univariate_distribution import UnivDist -from ..core.uqtestfun_abc import UQTestFunABC -from .available import create_prob_input_from_available - -__all__ = ["Franke2"] - -INPUT_MARGINALS_FRANKE1979 = [ # From Ref. [1] - UnivDist( - name="X1", - distribution="uniform", - parameters=[0.0, 1.0], - ), - UnivDist( - name="X2", - distribution="uniform", - parameters=[0.0, 1.0], - ), -] - -AVAILABLE_INPUT_SPECS = { - "Franke1979": { - "name": "Franke-1979", - "description": ( - "Input specification for the (second) Franke function " - "from Franke (1979)." - ), - "marginals": INPUT_MARGINALS_FRANKE1979, - "copulas": None, - } -} - -DEFAULT_INPUT_SELECTION = "Franke1979" - - -class Franke2(UQTestFunABC): - """A concrete implementation of the (second) Franke function.""" - - _TAGS = ["metamodeling"] - - _AVAILABLE_INPUTS = tuple(AVAILABLE_INPUT_SPECS.keys()) - - _AVAILABLE_PARAMETERS = None - - _DEFAULT_SPATIAL_DIMENSION = 2 - - _DESCRIPTION = "(Second) Franke function from Franke (1979)" - - def __init__( - self, - *, - prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, - name: Optional[str] = None, - rng_seed_prob_input: Optional[int] = None, - ): - # --- Arguments processing - prob_input = create_prob_input_from_available( - prob_input_selection, - AVAILABLE_INPUT_SPECS, - rng_seed=rng_seed_prob_input, - ) - # Process the default name - if name is None: - name = Franke2.__name__ - - super().__init__(prob_input=prob_input, name=name) - - def evaluate(self, xx: np.ndarray): - """Evaluate the (second) Franke function on a set of input values. - - Parameters - ---------- - xx : np.ndarray - Two-Dimensional input values given by N-by-2 arrays where - N is the number of input values. - - Returns - ------- - np.ndarray - The output of the (second) Franke function evaluated - on the input values. - The output is a 1-dimensional array of length N. - """ - # Compute the (second) Franke function - yy = (np.tanh(9 * (xx[:, 1] - xx[:, 0])) + 1) / 9.0 - - return yy diff --git a/src/uqtestfuns/test_functions/franke_3.py b/src/uqtestfuns/test_functions/franke_3.py deleted file mode 100644 index 598fd9a..0000000 --- a/src/uqtestfuns/test_functions/franke_3.py +++ /dev/null @@ -1,112 +0,0 @@ -""" -Module with an implementation of the (third) Franke function. - -The (third) Franke function is a two-dimensional scalar-valued function. -The function was first introduced in [1] in the context of interpolation -problem. It features a saddle shaped surface. - -The Franke's original report [1] contains in total six two-dimensional test -functions. The first function that appeared in the report is commonly known -as the "Franke function". - -References ----------- - -1. Richard Franke, "A critical comparison of some methods for interpolation - of scattered data," Naval Postgraduate School, Monterey, Canada, - Technical Report No. NPS53-79-003, 1979. - URL: https://core.ac.uk/reader/36727660 -""" -import numpy as np - -from typing import Optional - -from ..core.prob_input.univariate_distribution import UnivDist -from ..core.uqtestfun_abc import UQTestFunABC -from .available import create_prob_input_from_available - -__all__ = ["Franke3"] - -INPUT_MARGINALS_FRANKE1979 = [ # From Ref. [1] - UnivDist( - name="X1", - distribution="uniform", - parameters=[0.0, 1.0], - ), - UnivDist( - name="X2", - distribution="uniform", - parameters=[0.0, 1.0], - ), -] - -AVAILABLE_INPUT_SPECS = { - "Franke1979": { - "name": "Franke-1979", - "description": ( - "Input specification for the (third) Franke function " - "from Franke (1979)." - ), - "marginals": INPUT_MARGINALS_FRANKE1979, - "copulas": None, - } -} - -DEFAULT_INPUT_SELECTION = "Franke1979" - - -class Franke3(UQTestFunABC): - """A concrete implementation of the (first) Franke function.""" - - _TAGS = ["metamodeling"] - - _AVAILABLE_INPUTS = tuple(AVAILABLE_INPUT_SPECS.keys()) - - _AVAILABLE_PARAMETERS = None - - _DEFAULT_SPATIAL_DIMENSION = 2 - - _DESCRIPTION = "(Third) Franke function from Franke (1979)" - - def __init__( - self, - *, - prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, - name: Optional[str] = None, - rng_seed_prob_input: Optional[int] = None, - ): - # --- Arguments processing - prob_input = create_prob_input_from_available( - prob_input_selection, - AVAILABLE_INPUT_SPECS, - rng_seed=rng_seed_prob_input, - ) - # Process the default name - if name is None: - name = Franke3.__name__ - - super().__init__(prob_input=prob_input, name=name) - - def evaluate(self, xx: np.ndarray): - """Evaluate the (third) Franke function on a set of input values. - - Parameters - ---------- - xx : np.ndarray - Two-Dimensional input values given by N-by-2 arrays where - N is the number of input values. - - Returns - ------- - np.ndarray - The output of the (third) Franke function evaluated - on the input values. - The output is a 1-dimensional array of length N. - """ - # Compute the (third) Franke function - term_1 = 1.25 + np.cos(5.4 * xx[:, 1]) - term_2 = 6 * (1 + (3 * xx[:, 0] - 1) ** 2) - - yy = term_1 / term_2 - - return yy diff --git a/src/uqtestfuns/test_functions/mclain.py b/src/uqtestfuns/test_functions/mclain.py index 32e8288..2fb97dc 100644 --- a/src/uqtestfuns/test_functions/mclain.py +++ b/src/uqtestfuns/test_functions/mclain.py @@ -66,7 +66,6 @@ DEFAULT_INPUT_SELECTION = "McLain1974" - COMMON_METADATA = dict( _TAGS=["metamodeling"], _AVAILABLE_INPUTS=tuple(AVAILABLE_INPUT_SPECS.keys()), diff --git a/tests/builtin_test_functions/test_franke.py b/tests/builtin_test_functions/test_franke.py new file mode 100644 index 0000000..7d61052 --- /dev/null +++ b/tests/builtin_test_functions/test_franke.py @@ -0,0 +1,51 @@ +""" +Test module for the Franke test functions. + +Notes +----- +- The tests defined in this module deals with + the correctness of the evaluation. +""" +import numpy as np + +from uqtestfuns import Franke2, Franke4 + + +def test_franke2(): + """Test the (2nd) Franke function.""" + my_fun = Franke2() + + yy_ref = 0.0 + xx = np.array([[1.0, 0.0]]) + + # Assertion: The minimum is known + assert np.isclose(yy_ref, my_fun(xx)) + + yy_ref = 2.0 / 9.0 + xx = np.array([[0.0, 1.0]]) + + # Assertion: The maximum is known + assert np.isclose(yy_ref, my_fun(xx)) + + xx_test = my_fun.prob_input.get_sample(100000) + yy_test = my_fun(xx_test) + + # Assertion: The maximum is indeed a maximum + assert np.all(yy_test <= yy_ref) + + +def test_franke4(): + """Test the (4th) Franke function""" + franke_fun = Franke4() + + yy_ref = 1.0 / 3.0 + xx = np.array([[0.5, 0.5]]) + + # Assertion: The maximum is known + assert np.isclose(yy_ref, franke_fun(xx)) + + xx_test = franke_fun.prob_input.get_sample(100000) + yy_test = franke_fun(xx_test) + + # Assertion: The maximum is indeed a maximum + assert np.all(yy_test <= yy_ref) diff --git a/tests/builtin_test_functions/test_mclain.py b/tests/builtin_test_functions/test_mclain.py new file mode 100644 index 0000000..9b243eb --- /dev/null +++ b/tests/builtin_test_functions/test_mclain.py @@ -0,0 +1,102 @@ +""" +Test module for the McLain test functions. + +Notes +----- +- The tests defined in this module deals with + the correctness of the evaluation. +""" +import numpy as np + +from uqtestfuns import McLainS1, McLainS2, McLainS3, McLainS4, McLainS5 + + +def test_mclain_s1(): + """Test the McLain S1 function.""" + my_fun = McLainS1() + + yy_ref = 8.0 + xx = np.array([[5.5, 5.5]]) + + # Assertion: The maximum is known + assert np.isclose(yy_ref, my_fun(xx)) + + xx_test = my_fun.prob_input.get_sample(100000) + yy_test = my_fun(xx_test) + + # Assertion: The maximum is indeed a maximum + assert np.all(yy_test <= yy_ref) + + +def test_mclain_s2(): + """Test the McLain S2 function.""" + my_fun = McLainS2() + + yy_ref = 1.0 + xx = np.array([[5.0, 5.0]]) + + # Assertion: The maximum is known + assert np.isclose(yy_ref, my_fun(xx)) + + xx_test = my_fun.prob_input.get_sample(100000) + yy_test = my_fun(xx_test) + + # Assertion: The maximum is indeed a maximum + assert np.all(yy_test <= yy_ref) + + +def test_mclain_s3(): + """Test the McLain S3 function.""" + my_fun = McLainS3() + + yy_ref = 1.0 + xx = np.array([[5.0, 5.0]]) + + # Assertion: The maximum is known + assert np.isclose(yy_ref, my_fun(xx)) + + xx_test = my_fun.prob_input.get_sample(100000) + yy_test = my_fun(xx_test) + + # Assertion: The maximum is indeed a maximum + assert np.all(yy_test <= yy_ref) + + +def test_mclain_s4(): + """Test the McLain S4 function.""" + my_fun = McLainS4() + + yy_ref = 1.0 + xx = np.array([[5.5, 5.5]]) + + # Assertion: The maximum is known + assert np.isclose(yy_ref, my_fun(xx)) + + xx_test = my_fun.prob_input.get_sample(100000) + yy_test = my_fun(xx_test) + + # Assertion: The maximum is indeed a maximum + assert np.all(yy_test <= yy_ref) + + +def test_mclain_s5(): + """Test the McLain S5 function.""" + my_fun = McLainS5() + + yy_ref = -1.0 + xx = np.array([[1.0, 1.0]]) + + # Assertion: The minimum is known + assert np.isclose(yy_ref, my_fun(xx)) + + yy_ref = 1.0 + xx = np.array([[10.0, 10.0]]) + + # Assertion: The maximum is known + assert np.isclose(yy_ref, my_fun(xx)) + + xx_test = my_fun.prob_input.get_sample(100000) + yy_test = my_fun(xx_test) + + # Assertion: The maximum is indeed a maximum + assert np.all(yy_test <= yy_ref) From d2f6179625e5d299dbb52fd2077fb59528b38bd7 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Mon, 5 Jun 2023 16:34:57 +0200 Subject: [PATCH 12/73] Add an implementation of the (5th) Franke function. - The function features a steep Gaussian hill that approaches zero at the boundaries. --- CHANGELOG.md | 8 +- docs/_toc.yml | 2 + docs/fundamentals/metamodeling.md | 1 + docs/test-functions/available.md | 1 + docs/test-functions/franke-1.md | 2 + docs/test-functions/franke-2.md | 2 + docs/test-functions/franke-3.md | 2 + docs/test-functions/franke-4.md | 3 + docs/test-functions/franke-5.md | 168 ++++++++++++++++++++ src/uqtestfuns/test_functions/__init__.py | 3 +- src/uqtestfuns/test_functions/franke.py | 44 ++++- tests/builtin_test_functions/test_franke.py | 19 ++- tests/builtin_test_functions/test_mclain.py | 8 +- 13 files changed, 253 insertions(+), 10 deletions(-) create mode 100644 docs/test-functions/franke-5.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 26c5480..11b4727 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,12 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - An instance of NumPy random number generator is now attached to instances of `UnivDist` and `ProbInput`. The random seed number may be passed to the corresponding constructor for reproducibility. -- The two-dimensional (first) Franke function, relevant for metamodeling - exercises, is added as a UQ test function. -- The two-dimensional (second) Franke function, relevant for metamodeling - exercises, is added as a UQ test function. -- The two-dimensional (third) Franke function, relevant for metamodeling - exercises, is added as a UQ test function. +- The two-dimensional Franke functions (1st, 2nd, 3rd, 4th, 5th), + relevant for metamodeling exercises, are added as UQ test functions. - The two-dimensional McLain functions (S1, S2, S3, S4, and S5), relevant for metamodeling exercises, are added as UQ test functions. diff --git a/docs/_toc.yml b/docs/_toc.yml index 093348f..39742e9 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -37,6 +37,8 @@ parts: title: (3rd) Franke - file: test-functions/franke-4 title: (4th) Franke + - file: test-functions/franke-5 + title: (5th) Franke - file: test-functions/ishigami title: Ishigami - file: test-functions/oakley-ohagan-1d diff --git a/docs/fundamentals/metamodeling.md b/docs/fundamentals/metamodeling.md index 1a252af..1033ebb 100644 --- a/docs/fundamentals/metamodeling.md +++ b/docs/fundamentals/metamodeling.md @@ -27,6 +27,7 @@ in the comparison of metamodeling approaches. | {ref}`(2nd) Franke ` | 2 | `Franke2()` | | {ref}`(3rd) Franke ` | 2 | `Franke3()` | | {ref}`(4th) Franke ` | 2 | `Franke4()` | +| {ref}`(5th) Franke ` | 2 | `Franke5()` | | {ref}`Oakley-O'Hagan 1D ` | 1 | `OakleyOHagan1D()` | | {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | | {ref}`McLain S1 ` | 2 | `McLainS1()` | diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index dd11b9c..8cd877a 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -28,6 +28,7 @@ available in the current UQTestFuns, regardless of their typical applications. | {ref}`(2nd) Franke ` | 2 | `Franke2()` | | {ref}`(3rd) Franke ` | 2 | `Franke3()` | | {ref}`(4th) Franke ` | 2 | `Franke4()` | +| {ref}`(5th) Franke ` | 2 | `Franke5()` | | {ref}`Ishigami ` | 3 | `Ishigami()` | | {ref}`Oakley-O'Hagan 1D ` | 1 | `OakleyOHagan1D()` | | {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | diff --git a/docs/test-functions/franke-1.md b/docs/test-functions/franke-1.md index b70d4c2..4cd3b23 100644 --- a/docs/test-functions/franke-1.md +++ b/docs/test-functions/franke-1.md @@ -39,6 +39,8 @@ six two-dimensional test functions: surface - {ref}`(4th) Franke function `: A Gaussian hill that slopes off in a gentle fashion +- {ref}`(5th) Franke function `: A steep Gaussian hill + that approaches zero at the boundaries The term "Franke function" typically only refers to the (1st) Franke function. ``` diff --git a/docs/test-functions/franke-2.md b/docs/test-functions/franke-2.md index 7887a75..0af0144 100644 --- a/docs/test-functions/franke-2.md +++ b/docs/test-functions/franke-2.md @@ -37,6 +37,8 @@ six two-dimensional test functions: surface - {ref}`(4th) Franke function `: A Gaussian hill that slopes off in a gentle fashion +- {ref}`(5th) Franke function `: A steep Gaussian hill + that approaches zero at the boundaries The term "Franke function" typically only refers to the (1st) Franke function. ``` diff --git a/docs/test-functions/franke-3.md b/docs/test-functions/franke-3.md index 480e72b..1c6062b 100644 --- a/docs/test-functions/franke-3.md +++ b/docs/test-functions/franke-3.md @@ -37,6 +37,8 @@ six two-dimensional test functions: surface (_this function_) - {ref}`(4th) Franke function `: A Gaussian hill that slopes off in a gentle fashion +- {ref}`(5th) Franke function `: A steep Gaussian hill + that approaches zero at the boundaries The term "Franke function" typically only refers to the (1st) Franke function. ``` diff --git a/docs/test-functions/franke-4.md b/docs/test-functions/franke-4.md index 5d839dd..f48969f 100644 --- a/docs/test-functions/franke-4.md +++ b/docs/test-functions/franke-4.md @@ -37,6 +37,8 @@ six two-dimensional test functions: surface - {ref}`(4th) Franke function `: A Gaussian hill that slopes off in a gentle fashion (_this function_) +- {ref}`(5th) Franke function `: A steep Gaussian hill + that approaches zero at the boundaries The term "Franke function" typically only refers to the (1st) Franke function. ``` @@ -92,6 +94,7 @@ plt.gcf().set_dpi(75); As shown in the plots above, the function features a Gaussian hill that slopes in a gentle fashion. +The maximum of the function is located at $(0.5, 0.5)$ with a height of $\frac{1}{3}$. ```{note} The (4th) Franke function is a modified form of the {ref}`McLain S3 function ` diff --git a/docs/test-functions/franke-5.md b/docs/test-functions/franke-5.md new file mode 100644 index 0000000..8190757 --- /dev/null +++ b/docs/test-functions/franke-5.md @@ -0,0 +1,168 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:franke-5)= +# (5th) Franke Function + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +The (5th) Franke function is a two-dimensional scalar-valued function. +The function was first introduced in {cite}`Franke1979` in the context of +interpolation problem. + +```{note} +The Franke's original report {cite}`Franke1979` contains in total +six two-dimensional test functions: + +- {ref}`(1st) Franke function `: Two Gaussian peaks + and a Gaussian dip on a surface slopping down the upper right boundary +- {ref}`(2nd) Franke function `: Two nearly flat + regions joined by a sharp rise running diagonally +- {ref}`(3rd) Franke function `: A saddle shaped + surface +- {ref}`(4th) Franke function `: A Gaussian hill + that slopes off in a gentle fashion +- {ref}`(5th) Franke function `: A steep Gaussian hill + that approaches zero at the boundaries (_this function_) + +The term "Franke function" typically only refers to the (1st) Franke function. +``` + +```{code-cell} ipython3 +:tags: [remove-input] + +from mpl_toolkits.axes_grid1 import make_axes_locatable + +my_fun = uqtf.Franke5() + +# --- Create 2D data +xx_1d = np.linspace(0.0, 1.0, 1000)[:, np.newaxis] +mesh_2d = np.meshgrid(xx_1d, xx_1d) +xx_2d = np.array(mesh_2d).T.reshape(-1, 2) +yy_2d = my_fun(xx_2d) + +# --- Create two-dimensional plots +fig = plt.figure(figsize=(10, 5)) + +# Surface +axs_1 = plt.subplot(121, projection='3d') +axs_1.plot_surface( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000, 1000).T, + linewidth=0, + cmap="plasma", + antialiased=False, + alpha=0.5 +) +axs_1.set_xlabel("$x_1$", fontsize=14) +axs_1.set_ylabel("$x_2$", fontsize=14) +axs_1.set_zlabel("$\mathcal{M}(x_1, x_2)$", fontsize=14) +axs_1.set_title("Surface plot of (5th) Franke", fontsize=14) + +# Contour +axs_2 = plt.subplot(122) +cf = axs_2.contourf( + mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000).T, cmap="plasma" +) +axs_2.set_xlabel("$x_1$", fontsize=14) +axs_2.set_ylabel("$x_2$", fontsize=14) +axs_2.set_title("Contour plot of (5th) Franke", fontsize=14) +divider = make_axes_locatable(axs_2) +cax = divider.append_axes('right', size='5%', pad=0.05) +fig.colorbar(cf, cax=cax, orientation='vertical') +axs_2.axis('scaled') + +fig.tight_layout(pad=4.0) +plt.gcf().set_dpi(75); +``` + +As shown in the plots above, the function features a Gaussian hill that slopes +in a steeper fashion as compared to the {ref}`(4th) Franke function `. +The maximum of the function is located at $(0.5, 0.5)$ with a height of $\frac{1}{3}$. + +```{note} +The (5th) Franke function is a modified form of the {ref}`McLain S2 function ` +{cite}`McLain1974`. + +Specifically, the domain of the function is translated from $[1.0, 10.0]^2$ +to $[0.0, 1.0]^2$ with some additional slight modifications to "enhance the +visual aspects" of the resulting surfaces. +``` + +## Test function instance + +To create a default instance of the (5th) Franke function: + +```{code-cell} ipython3 +my_testfun = uqtf.Franke5() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +## Description + +The (4th) Franke function is defined as follows: + +$$ +\mathcal{M}(\boldsymbol{x}) = \frac{1}{3} \exp{\left[ -\frac{81}{4} \left( (x_1 - 0.5)^2 + (x_2 - 0.5)^2 \right) \right]} +$$ +where $\boldsymbol{x} = \{ x_1, x_2 \}$ +is the two-dimensional vector of input variables further defined below. + +## Probabilistic input + +Based on {cite}`Franke1979`, the probabilistic input model +for the function consists of two independent random variables as shown below. + +```{code-cell} ipython3 +my_testfun.prob_input +``` + +## Reference results + +This section provides several reference results of typical UQ analyses involving +the test function. + +### Sample histogram + +Shown below is the histogram of the output based on $100'000$ random points: + +```{code-cell} ipython3 +:tags: [hide-input] + +xx_test = my_testfun.prob_input.get_sample(100000) +yy_test = my_testfun(xx_test) + +plt.hist(yy_test, bins="auto", color="#8da0cb"); +plt.grid(); +plt.ylabel("Counts [-]"); +plt.xlabel("$\mathcal{M}(\mathbf{X})$"); +plt.gcf().set_dpi(150); +``` + +## References + +```{bibliography} +:style: plain +:filter: docname in docnames +``` diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index c11c795..7ff5b60 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -4,7 +4,7 @@ from .ackley import Ackley from .borehole import Borehole from .damped_oscillator import DampedOscillator -from .franke import Franke1, Franke2, Franke3, Franke4 +from .franke import Franke1, Franke2, Franke3, Franke4, Franke5 from .flood import Flood from .ishigami import Ishigami from .oakley_ohagan_1d import OakleyOHagan1D @@ -27,6 +27,7 @@ "Franke2", "Franke3", "Franke4", + "Franke5", "Ishigami", "OakleyOHagan1D", "OTLCircuit", diff --git a/src/uqtestfuns/test_functions/franke.py b/src/uqtestfuns/test_functions/franke.py index 494bc8b..83166db 100644 --- a/src/uqtestfuns/test_functions/franke.py +++ b/src/uqtestfuns/test_functions/franke.py @@ -14,6 +14,8 @@ diagonally. - (3rd) Franke function: A saddle shaped surface. - (4th) Franke function: A Gaussian hill that slopes off in a gentle fashion. +- (5th) Franke function: A steep Gaussian hill which becomes almost zero at + the boundaries. Four of the Franke functions, namely the 2nd, 4th, 5th, and 6th are modified from [3] (namely, S5, S3, S2 and S1, respectively). @@ -44,7 +46,7 @@ from ..core.uqtestfun_abc import UQTestFunABC from .available import create_prob_input_from_available -__all__ = ["Franke1", "Franke2", "Franke3", "Franke4"] +__all__ = ["Franke1", "Franke2", "Franke3", "Franke4", "Franke5"] INPUT_MARGINALS_FRANKE1979 = [ # From Ref. [1] UnivDist( @@ -263,3 +265,43 @@ def evaluate(self, xx: np.ndarray): ) return yy + + +class Franke5(UQTestFunABC): + """A concrete implementation of the (5th) Franke function. + + The function features a steep Gaussian hill. + """ + + _TAGS = COMMON_METADATA["_TAGS"] + _AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"] + _AVAILABLE_PARAMETERS = COMMON_METADATA["_AVAILABLE_PARAMETERS"] + _DEFAULT_SPATIAL_DIMENSION = COMMON_METADATA["_DEFAULT_SPATIAL_DIMENSION"] + _DESCRIPTION = f"(4th) Franke function {COMMON_METADATA['_DESCRIPTION']}" + + __init__ = _init # type: ignore + + def evaluate(self, xx: np.ndarray): + """Evaluate the (5th) Franke function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + Two-Dimensional input values given by N-by-2 arrays where + N is the number of input values. + + Returns + ------- + np.ndarray + The output of the (5th) Franke function evaluated + on the input values. + The output is a 1-dimensional array of length N. + """ + yy = ( + np.exp( + -81.0 / 4.0 * ((xx[:, 0] - 0.5) ** 2 + (xx[:, 1] - 0.5) ** 2) + ) + / 3.0 + ) + + return yy diff --git a/tests/builtin_test_functions/test_franke.py b/tests/builtin_test_functions/test_franke.py index 7d61052..902d3ee 100644 --- a/tests/builtin_test_functions/test_franke.py +++ b/tests/builtin_test_functions/test_franke.py @@ -8,7 +8,7 @@ """ import numpy as np -from uqtestfuns import Franke2, Franke4 +from uqtestfuns.test_functions import Franke2, Franke4, Franke5 def test_franke2(): @@ -49,3 +49,20 @@ def test_franke4(): # Assertion: The maximum is indeed a maximum assert np.all(yy_test <= yy_ref) + + +def test_franke5(): + """Test the (5th) Franke function""" + franke_fun = Franke5() + + yy_ref = 1.0 / 3.0 + xx = np.array([[0.5, 0.5]]) + + # Assertion: The maximum is known + assert np.isclose(yy_ref, franke_fun(xx)) + + xx_test = franke_fun.prob_input.get_sample(100000) + yy_test = franke_fun(xx_test) + + # Assertion: The maximum is indeed a maximum + assert np.all(yy_test <= yy_ref) diff --git a/tests/builtin_test_functions/test_mclain.py b/tests/builtin_test_functions/test_mclain.py index 9b243eb..b097bd2 100644 --- a/tests/builtin_test_functions/test_mclain.py +++ b/tests/builtin_test_functions/test_mclain.py @@ -8,7 +8,13 @@ """ import numpy as np -from uqtestfuns import McLainS1, McLainS2, McLainS3, McLainS4, McLainS5 +from uqtestfuns.test_functions import ( + McLainS1, + McLainS2, + McLainS3, + McLainS4, + McLainS5, +) def test_mclain_s1(): From 2fc2cde8b46222173c6de5ed4585dd55ac7ccdc7 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Mon, 5 Jun 2023 16:57:33 +0200 Subject: [PATCH 13/73] Add an implementation of the (6th) Franke function. - The test function features a part of a sphere. --- CHANGELOG.md | 2 +- docs/_toc.yml | 2 + docs/fundamentals/metamodeling.md | 1 + docs/test-functions/available.md | 1 + docs/test-functions/franke-1.md | 1 + docs/test-functions/franke-2.md | 1 + docs/test-functions/franke-3.md | 1 + docs/test-functions/franke-4.md | 1 + docs/test-functions/franke-5.md | 1 + docs/test-functions/franke-6.md | 170 ++++++++++++++++++++ src/uqtestfuns/test_functions/__init__.py | 3 +- src/uqtestfuns/test_functions/franke.py | 44 ++++- tests/builtin_test_functions/test_franke.py | 19 ++- 13 files changed, 242 insertions(+), 5 deletions(-) create mode 100644 docs/test-functions/franke-6.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 11b4727..4dec649 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - An instance of NumPy random number generator is now attached to instances of `UnivDist` and `ProbInput`. The random seed number may be passed to the corresponding constructor for reproducibility. -- The two-dimensional Franke functions (1st, 2nd, 3rd, 4th, 5th), +- The two-dimensional Franke functions (1st, 2nd, 3rd, 4th, 5th, and 6th), relevant for metamodeling exercises, are added as UQ test functions. - The two-dimensional McLain functions (S1, S2, S3, S4, and S5), relevant for metamodeling exercises, are added as UQ test functions. diff --git a/docs/_toc.yml b/docs/_toc.yml index 39742e9..c1248a2 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -39,6 +39,8 @@ parts: title: (4th) Franke - file: test-functions/franke-5 title: (5th) Franke + - file: test-functions/franke-6 + title: (6th) Franke - file: test-functions/ishigami title: Ishigami - file: test-functions/oakley-ohagan-1d diff --git a/docs/fundamentals/metamodeling.md b/docs/fundamentals/metamodeling.md index 1033ebb..89a361b 100644 --- a/docs/fundamentals/metamodeling.md +++ b/docs/fundamentals/metamodeling.md @@ -28,6 +28,7 @@ in the comparison of metamodeling approaches. | {ref}`(3rd) Franke ` | 2 | `Franke3()` | | {ref}`(4th) Franke ` | 2 | `Franke4()` | | {ref}`(5th) Franke ` | 2 | `Franke5()` | +| {ref}`(6th) Franke ` | 2 | `Franke6()` | | {ref}`Oakley-O'Hagan 1D ` | 1 | `OakleyOHagan1D()` | | {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | | {ref}`McLain S1 ` | 2 | `McLainS1()` | diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index 8cd877a..55b7574 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -29,6 +29,7 @@ available in the current UQTestFuns, regardless of their typical applications. | {ref}`(3rd) Franke ` | 2 | `Franke3()` | | {ref}`(4th) Franke ` | 2 | `Franke4()` | | {ref}`(5th) Franke ` | 2 | `Franke5()` | +| {ref}`(6th) Franke ` | 2 | `Franke6()` | | {ref}`Ishigami ` | 3 | `Ishigami()` | | {ref}`Oakley-O'Hagan 1D ` | 1 | `OakleyOHagan1D()` | | {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | diff --git a/docs/test-functions/franke-1.md b/docs/test-functions/franke-1.md index 4cd3b23..566f62c 100644 --- a/docs/test-functions/franke-1.md +++ b/docs/test-functions/franke-1.md @@ -41,6 +41,7 @@ six two-dimensional test functions: that slopes off in a gentle fashion - {ref}`(5th) Franke function `: A steep Gaussian hill that approaches zero at the boundaries +- {ref}`(6th) Franke function `: A part of a sphere The term "Franke function" typically only refers to the (1st) Franke function. ``` diff --git a/docs/test-functions/franke-2.md b/docs/test-functions/franke-2.md index 0af0144..c5ed011 100644 --- a/docs/test-functions/franke-2.md +++ b/docs/test-functions/franke-2.md @@ -39,6 +39,7 @@ six two-dimensional test functions: that slopes off in a gentle fashion - {ref}`(5th) Franke function `: A steep Gaussian hill that approaches zero at the boundaries +- {ref}`(6th) Franke function `: A part of a sphere The term "Franke function" typically only refers to the (1st) Franke function. ``` diff --git a/docs/test-functions/franke-3.md b/docs/test-functions/franke-3.md index 1c6062b..f0a3473 100644 --- a/docs/test-functions/franke-3.md +++ b/docs/test-functions/franke-3.md @@ -39,6 +39,7 @@ six two-dimensional test functions: that slopes off in a gentle fashion - {ref}`(5th) Franke function `: A steep Gaussian hill that approaches zero at the boundaries +- {ref}`(6th) Franke function `: A part of a sphere The term "Franke function" typically only refers to the (1st) Franke function. ``` diff --git a/docs/test-functions/franke-4.md b/docs/test-functions/franke-4.md index f48969f..4edc4fa 100644 --- a/docs/test-functions/franke-4.md +++ b/docs/test-functions/franke-4.md @@ -39,6 +39,7 @@ six two-dimensional test functions: that slopes off in a gentle fashion (_this function_) - {ref}`(5th) Franke function `: A steep Gaussian hill that approaches zero at the boundaries +- {ref}`(6th) Franke function `: A part of a sphere The term "Franke function" typically only refers to the (1st) Franke function. ``` diff --git a/docs/test-functions/franke-5.md b/docs/test-functions/franke-5.md index 8190757..14cc5b9 100644 --- a/docs/test-functions/franke-5.md +++ b/docs/test-functions/franke-5.md @@ -39,6 +39,7 @@ six two-dimensional test functions: that slopes off in a gentle fashion - {ref}`(5th) Franke function `: A steep Gaussian hill that approaches zero at the boundaries (_this function_) +- {ref}`(6th) Franke function `: A part of a sphere The term "Franke function" typically only refers to the (1st) Franke function. ``` diff --git a/docs/test-functions/franke-6.md b/docs/test-functions/franke-6.md new file mode 100644 index 0000000..53550e3 --- /dev/null +++ b/docs/test-functions/franke-6.md @@ -0,0 +1,170 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:franke-6)= +# (6th) Franke Function + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +The (6th) Franke function is a two-dimensional scalar-valued function. +The function was first introduced in {cite}`Franke1979` in the context of +interpolation problem. + +```{note} +The Franke's original report {cite}`Franke1979` contains in total +six two-dimensional test functions: + +- {ref}`(1st) Franke function `: Two Gaussian peaks + and a Gaussian dip on a surface slopping down the upper right boundary +- {ref}`(2nd) Franke function `: Two nearly flat + regions joined by a sharp rise running diagonally +- {ref}`(3rd) Franke function `: A saddle shaped + surface +- {ref}`(4th) Franke function `: A Gaussian hill + that slopes off in a gentle fashion +- {ref}`(5th) Franke function `: A steep Gaussian hill + that approaches zero at the boundaries +- {ref}`(6th) Franke function `: A part of a sphere + (_this function_) + +The term "Franke function" typically only refers to the (1st) Franke function. +``` + +```{code-cell} ipython3 +:tags: [remove-input] + +from mpl_toolkits.axes_grid1 import make_axes_locatable + +my_fun = uqtf.Franke6() + +# --- Create 2D data +xx_1d = np.linspace(0.0, 1.0, 1000)[:, np.newaxis] +mesh_2d = np.meshgrid(xx_1d, xx_1d) +xx_2d = np.array(mesh_2d).T.reshape(-1, 2) +yy_2d = my_fun(xx_2d) + +# --- Create two-dimensional plots +fig = plt.figure(figsize=(10, 5)) + +# Surface +axs_1 = plt.subplot(121, projection='3d') +axs_1.plot_surface( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000, 1000).T, + linewidth=0, + cmap="plasma", + antialiased=False, + alpha=0.5 +) +axs_1.set_xlabel("$x_1$", fontsize=14) +axs_1.set_ylabel("$x_2$", fontsize=14) +axs_1.set_zlabel("$\mathcal{M}(x_1, x_2)$", fontsize=14) +axs_1.set_title("Surface plot of (6th) Franke", fontsize=14) + +# Contour +axs_2 = plt.subplot(122) +cf = axs_2.contourf( + mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000).T, cmap="plasma" +) +axs_2.set_xlabel("$x_1$", fontsize=14) +axs_2.set_ylabel("$x_2$", fontsize=14) +axs_2.set_title("Contour plot of (6th) Franke", fontsize=14) +divider = make_axes_locatable(axs_2) +cax = divider.append_axes('right', size='5%', pad=0.05) +fig.colorbar(cf, cax=cax, orientation='vertical') +axs_2.axis('scaled') + +fig.tight_layout(pad=4.0) +plt.gcf().set_dpi(75); +``` + +As shown in the plots above, the function features a part of a sphere +with a radius of $\frac{8}{9}$ +and a center at $(\frac{1}{2}, \frac{1}{2}, -\frac{1}{2})$. + +```{note} +The (6th) Franke function is a modified form of the {ref}`McLain S1 function ` +{cite}`McLain1974`. + +Specifically, the domain of the function is translated from $[1.0, 10.0]^2$ +to $[0.0, 1.0]^2$ with some additional slight modifications to "enhance the +visual aspects" of the resulting surfaces. +``` + +## Test function instance + +To create a default instance of the (6th) Franke function: + +```{code-cell} ipython3 +my_testfun = uqtf.Franke6() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +## Description + +The (4th) Franke function is defined as follows: + +$$ +\mathcal{M}(\boldsymbol{x}) = \left( 64 - 81 \left( (x_1 - 0.5)^2 + (x_2 - 0.5)^2 \right) \right)^{0.5} - 0.5 +$$ +where $\boldsymbol{x} = \{ x_1, x_2 \}$ +is the two-dimensional vector of input variables further defined below. + +## Probabilistic input + +Based on {cite}`Franke1979`, the probabilistic input model +for the function consists of two independent random variables as shown below. + +```{code-cell} ipython3 +my_testfun.prob_input +``` + +## Reference results + +This section provides several reference results of typical UQ analyses involving +the test function. + +### Sample histogram + +Shown below is the histogram of the output based on $100'000$ random points: + +```{code-cell} ipython3 +:tags: [hide-input] + +xx_test = my_testfun.prob_input.get_sample(100000) +yy_test = my_testfun(xx_test) + +plt.hist(yy_test, bins="auto", color="#8da0cb"); +plt.grid(); +plt.ylabel("Counts [-]"); +plt.xlabel("$\mathcal{M}(\mathbf{X})$"); +plt.gcf().set_dpi(150); +``` + +## References + +```{bibliography} +:style: plain +:filter: docname in docnames +``` diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index 7ff5b60..4369d87 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -4,7 +4,7 @@ from .ackley import Ackley from .borehole import Borehole from .damped_oscillator import DampedOscillator -from .franke import Franke1, Franke2, Franke3, Franke4, Franke5 +from .franke import Franke1, Franke2, Franke3, Franke4, Franke5, Franke6 from .flood import Flood from .ishigami import Ishigami from .oakley_ohagan_1d import OakleyOHagan1D @@ -28,6 +28,7 @@ "Franke3", "Franke4", "Franke5", + "Franke6", "Ishigami", "OakleyOHagan1D", "OTLCircuit", diff --git a/src/uqtestfuns/test_functions/franke.py b/src/uqtestfuns/test_functions/franke.py index 83166db..b495ce8 100644 --- a/src/uqtestfuns/test_functions/franke.py +++ b/src/uqtestfuns/test_functions/franke.py @@ -16,6 +16,7 @@ - (4th) Franke function: A Gaussian hill that slopes off in a gentle fashion. - (5th) Franke function: A steep Gaussian hill which becomes almost zero at the boundaries. +- (6th) Franke function: A part of a sphere. Four of the Franke functions, namely the 2nd, 4th, 5th, and 6th are modified from [3] (namely, S5, S3, S2 and S1, respectively). @@ -46,7 +47,7 @@ from ..core.uqtestfun_abc import UQTestFunABC from .available import create_prob_input_from_available -__all__ = ["Franke1", "Franke2", "Franke3", "Franke4", "Franke5"] +__all__ = ["Franke1", "Franke2", "Franke3", "Franke4", "Franke5", "Franke6"] INPUT_MARGINALS_FRANKE1979 = [ # From Ref. [1] UnivDist( @@ -277,7 +278,7 @@ class Franke5(UQTestFunABC): _AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"] _AVAILABLE_PARAMETERS = COMMON_METADATA["_AVAILABLE_PARAMETERS"] _DEFAULT_SPATIAL_DIMENSION = COMMON_METADATA["_DEFAULT_SPATIAL_DIMENSION"] - _DESCRIPTION = f"(4th) Franke function {COMMON_METADATA['_DESCRIPTION']}" + _DESCRIPTION = f"(5th) Franke function {COMMON_METADATA['_DESCRIPTION']}" __init__ = _init # type: ignore @@ -305,3 +306,42 @@ def evaluate(self, xx: np.ndarray): ) return yy + + +class Franke6(UQTestFunABC): + """A concrete implementation of the (6th) Franke function. + + The function features a part of a sphere. + """ + + _TAGS = COMMON_METADATA["_TAGS"] + _AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"] + _AVAILABLE_PARAMETERS = COMMON_METADATA["_AVAILABLE_PARAMETERS"] + _DEFAULT_SPATIAL_DIMENSION = COMMON_METADATA["_DEFAULT_SPATIAL_DIMENSION"] + _DESCRIPTION = f"(6th) Franke function {COMMON_METADATA['_DESCRIPTION']}" + + __init__ = _init # type: ignore + + def evaluate(self, xx: np.ndarray): + """Evaluate the (6th) Franke function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + Two-Dimensional input values given by N-by-2 arrays where + N is the number of input values. + + Returns + ------- + np.ndarray + The output of the (6th) Franke function evaluated + on the input values. + The output is a 1-dimensional array of length N. + """ + yy = ( + np.sqrt(64 - 81 * ((xx[:, 0] - 0.5) ** 2 + (xx[:, 1] - 0.5) ** 2)) + / 9.0 + - 0.5 + ) + + return yy diff --git a/tests/builtin_test_functions/test_franke.py b/tests/builtin_test_functions/test_franke.py index 902d3ee..54251db 100644 --- a/tests/builtin_test_functions/test_franke.py +++ b/tests/builtin_test_functions/test_franke.py @@ -8,7 +8,7 @@ """ import numpy as np -from uqtestfuns.test_functions import Franke2, Franke4, Franke5 +from uqtestfuns.test_functions import Franke2, Franke4, Franke5, Franke6 def test_franke2(): @@ -66,3 +66,20 @@ def test_franke5(): # Assertion: The maximum is indeed a maximum assert np.all(yy_test <= yy_ref) + + +def test_franke6(): + """Test the (6th) Franke function""" + franke_fun = Franke6() + + yy_ref = 8.0 / 9.0 - 1.0 / 2.0 + xx = np.array([[0.5, 0.5]]) + + # Assertion: The maximum is known + assert np.isclose(yy_ref, franke_fun(xx)) + + xx_test = franke_fun.prob_input.get_sample(100000) + yy_test = franke_fun(xx_test) + + # Assertion: The maximum is indeed a maximum + assert np.all(yy_test <= yy_ref) From 72d0c5b746122198254117510c762b2a36c2c02d Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Thu, 8 Jun 2023 16:30:04 +0200 Subject: [PATCH 14/73] Add an implementation of the Welch et al. (1992) function - The function is a 20-dimensional analytical function that features some strong non-linearities, pair interaction effects, and inert input variables. - Add a new section in the docs for integration test function. - Update description of the Sobol'-G function as an integration test function. --- docs/_toc.yml | 8 +- docs/fundamentals/integration.md | 33 +++++ docs/fundamentals/metamodeling.md | 1 + docs/fundamentals/sensitivity.md | 1 + docs/references.bib | 11 ++ docs/test-functions/available.md | 1 + docs/test-functions/sobol-g.md | 16 ++- docs/test-functions/welch1992.md | 141 +++++++++++++++++++++ src/uqtestfuns/test_functions/__init__.py | 2 + src/uqtestfuns/test_functions/sobol_g.py | 4 +- src/uqtestfuns/test_functions/welch1992.py | 127 +++++++++++++++++++ src/uqtestfuns/utils.py | 8 +- 12 files changed, 346 insertions(+), 7 deletions(-) create mode 100644 docs/fundamentals/integration.md create mode 100644 docs/test-functions/welch1992.md create mode 100644 src/uqtestfuns/test_functions/welch1992.py diff --git a/docs/_toc.yml b/docs/_toc.yml index c1248a2..a6debe0 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -14,10 +14,12 @@ parts: chapters: - file: fundamentals/metamodeling title: Metamodeling - - file: fundamentals/optimization - title: Optimization - file: fundamentals/sensitivity title: Sensitivity Analysis + - file: fundamentals/integration + title: Integration + - file: fundamentals/optimization + title: Optimization - file: test-functions/available title: All Available Functions sections: @@ -63,6 +65,8 @@ parts: title: Sobol'-G - file: test-functions/sulfur title: Sulfur + - file: test-functions/welch1992 + title: Welch et al. (1992) - file: test-functions/wing-weight title: Wing Weight - caption: Probabilistic Input diff --git a/docs/fundamentals/integration.md b/docs/fundamentals/integration.md new file mode 100644 index 0000000..c931fb1 --- /dev/null +++ b/docs/fundamentals/integration.md @@ -0,0 +1,33 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +# Test Functions for Numerical Integration + +The table below listed the available test functions typically used +in the testing and comparison of numerical integration method. + +| Name | Spatial Dimension | Constructor | +|:-----------------------------------------------------:|:-----------------:|:--------------------:| +| {ref}`Sobol'-G ` | M | `SobolG()` | +| {ref}`Welch et al. (1992) ` | 20 | `Welch1992()` | + +In a Python terminal, you can list all the available functions relevant +for metamodeling applications using ``list_functions()`` and filter the results +using the ``tag`` parameter: + +```{code-cell} ipython3 +import uqtestfuns as uqtf + +uqtf.list_functions(tag="integration") +``` diff --git a/docs/fundamentals/metamodeling.md b/docs/fundamentals/metamodeling.md index 89a361b..e5e60d3 100644 --- a/docs/fundamentals/metamodeling.md +++ b/docs/fundamentals/metamodeling.md @@ -38,6 +38,7 @@ in the comparison of metamodeling approaches. | {ref}`McLain S5 ` | 2 | `McLainS5()` | | {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | | {ref}`Sulfur ` | 9 | `Sulfur()` | +| {ref}`Welch1992 ` | 20 | `Welch1992()` | | {ref}`Wing Weight ` | 10 | `WingWeight()` | In a Python terminal, you can list all the available functions relevant diff --git a/docs/fundamentals/sensitivity.md b/docs/fundamentals/sensitivity.md index 53ff337..a454d75 100644 --- a/docs/fundamentals/sensitivity.md +++ b/docs/fundamentals/sensitivity.md @@ -27,6 +27,7 @@ in the comparison of sensitivity analysis methods. | {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | | {ref}`Sobol'-G ` | M | `SobolG()` | | {ref}`Sulfur ` | 9 | `Sulfur()` | +| {ref}`Welch et al. (1992) ` | 20 | `Welch1992()` | | {ref}`Wing Weight ` | 10 | `WingWeight()` | In a Python terminal, you can list all the available functions relevant diff --git a/docs/references.bib b/docs/references.bib index 2dd3f58..7aaf556 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -444,4 +444,15 @@ @Article{McLain1974 doi = {10.1093/comjnl/17.4.318}, } +@Article{Welch1992, + author = {William J. Welch and Robert. J. Buck and Jerome Sacks and Henry P. Wynn and Toby J. Mitchell and Max D. Morris}, + journal = {Technometrics}, + title = {Screening, predicting, and computer experiments}, + year = {1992}, + number = {1}, + pages = {15}, + volume = {34}, + doi = {10.2307/1269548}, +} + @Comment{jabref-meta: databaseType:bibtex;} diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index 55b7574..c066efb 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -41,6 +41,7 @@ available in the current UQTestFuns, regardless of their typical applications. | {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | | {ref}`Sobol'-G ` | M | `SobolG()` | | {ref}`Sulfur ` | 9 | `Sulfur()` | +| {ref}`Welch1992 ` | 20 | `Welch1992()` | | {ref}`Wing Weight ` | 10 | `WingWeight()` | In a Python terminal, you can list all the available functions diff --git a/docs/test-functions/sobol-g.md b/docs/test-functions/sobol-g.md index a62bb8a..ac93425 100644 --- a/docs/test-functions/sobol-g.md +++ b/docs/test-functions/sobol-g.md @@ -200,12 +200,21 @@ plt.xlabel("$\mathcal{M}(\mathbf{X})$"); plt.gcf().set_dpi(150); ``` +### Definite integration + +The integral value of the function over the whole domain $[0, 1]^M$ +is analytical: + +$$ +\int_{[0, 1]^M} \mathcal{M}(\boldsymbol{x}) \; d\boldsymbol{x} = 1.0. +$$ + ### Moments estimation The mean and variance of the Sobol'-G function can be computed analytically, and the results are: -- $\mathbb{E}[Y] = 1.0$ +- $\mathbb{E}[Y] = 1.0$[^integral] - $\mathbb{V}[Y] = \prod_{m = 1}^{M} \frac{\frac{4}{3} + 2 a_m + a_m^2}{(1 + a_m)^2} - 1$ Notice that the values of these two moments depend on the choice of the parameter values. @@ -364,4 +373,7 @@ tabulate( ```{bibliography} :style: plain :filter: docname in docnames -``` \ No newline at end of file +``` + +[^integral]: The expected value is the same as the integral over the domain +because the input is uniform in a unit hypercube. \ No newline at end of file diff --git a/docs/test-functions/welch1992.md b/docs/test-functions/welch1992.md new file mode 100644 index 0000000..f037441 --- /dev/null +++ b/docs/test-functions/welch1992.md @@ -0,0 +1,141 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:welch1992)= +# Welch et al. (1992) Function + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +The Welch et al. (1992) test function is a 20-dimensional scalar valued +function. The function features some strong non-linearities as well as some +pair interaction effects. Furthermore, a couple of two input variables +are set to be inert. + +The function was introduced in Welch et al. (1992) {cite}`Welch1992` +as a test function[^location] for metamodeling and sensitivity analysis purposes. +The function is also suitable for testing multi-dimensional integration +algorithms. + +## Test function instance + +To create a default instance of the test function: + +```{code-cell} ipython3 +my_testfun = uqtf.Welch1992() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +## Description + +The Welch et al. (1992) test function is defined as follows: + +$$ +\begin{aligned} +\mathcal{M}(\boldsymbol{x}) = + & \frac{5 x_{12}}{1 + x_1} + 5 (x_4 - x_{20})^2 + x_5 + 40 x_{19}^3 - 5 x_{19} \\ + & + 0.05 x_2 + 0.08 x_3 - 0.03 x_6 + 0.03 x_7 \\ + & - 0.09 x_9 - 0.01 x_{10} - 0.07 x_{11} + 0.25 x_{13}^2 \\ + & - 0.04 x_{14} + 0.06 x_{15} - 0.01 x_{17} - 0.03 x_{18}, +\end{aligned} +$$ + +where $\boldsymbol{x} = \{ x_1, \ldots, x_{20} \}$ is the 20-dimensional vector +of input variables further defined below. +Notice that the input variables $x_8$ and $x_{16}$ are inert +and therefore, missing from the expression. + +## Probabilistic input + +Based on {cite}`Welch1992`, the test function is defined on the +20-dimensional input space $[-0.5, 0.5]^{20}$. +This input can be modeled using 20 inpedendent uniform random variables +shown in the table below. + +```{code-cell} ipython3 +my_testfun.prob_input +``` + +## Reference Results + +This section provides several reference results of typical analyses involving +the test function. + +### Sample histogram + +Shown below is the histogram of the output based on $100'000$ random points: + +```{code-cell} ipython3 +:tags: [hide-input] + +np.random.seed(42) +xx_test = my_testfun.prob_input.get_sample(100000) +yy_test = my_testfun(xx_test) + +plt.hist(yy_test, bins="auto", color="#8da0cb"); +plt.grid(); +plt.ylabel("Counts [-]"); +plt.xlabel("$\mathcal{M}(\mathbf{X})$"); +plt.gcf().set_dpi(150); +``` + +### Definite integration + +The integral value of the test function over the domain $[-0.5, 0.5]^{20}$ +is analytical: + +$$ +\int_{[-0.5, 0.5]^{20}} \mathcal{M}(\boldsymbol{x}) \; d\boldsymbol{x} = \frac{41}{48}. +$$ + +### Moments estimation + +The moments of the test function may be derived analytically; the first two +(i.e., the expected value and the variance) are given below. + +#### Expected value + +Due to the fact that the function domain is a hypercube +and the input variables are uniformly distributed, +the expected value of the function is the same as the integral value +over the domain: + +$$ +\mathbb{E}[\mathcal{M}] = \frac{41}{48}. +$$ + +#### Variance + +The analytical value (albeit inexact) for the variance is given as follows: + +$$ +\mathbb{V}[\mathcal{M}] \approx 5.2220543. +$$ + +## References + +```{bibliography} +:style: unsrtalpha +:filter: docname in docnames +``` + +[^location]: see Section 3.1, p. 20 in {cite}`Welch1992`. \ No newline at end of file diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index 4369d87..85c5fcc 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -13,6 +13,7 @@ from .piston import Piston from .sobol_g import SobolG from .sulfur import Sulfur +from .welch1992 import Welch1992 from .wing_weight import WingWeight # NOTE: Import the new test function implementation class from its respective @@ -40,5 +41,6 @@ "Piston", "SobolG", "Sulfur", + "Welch1992", "WingWeight", ] diff --git a/src/uqtestfuns/test_functions/sobol_g.py b/src/uqtestfuns/test_functions/sobol_g.py index 5f5fa89..f3b4754 100644 --- a/src/uqtestfuns/test_functions/sobol_g.py +++ b/src/uqtestfuns/test_functions/sobol_g.py @@ -203,7 +203,7 @@ def _get_params_crestaux_2007(spatial_dimension: int) -> np.ndarray: class SobolG(UQTestFunABC): """A concrete implementation of the M-dimensional Sobol'-G function.""" - _TAGS = ["sensitivity"] + _TAGS = ["sensitivity", "integration"] _AVAILABLE_INPUTS = tuple(AVAILABLE_INPUT_SPECS.keys()) @@ -242,7 +242,7 @@ def __init__( ) # Process the default name if name is None: - name = SobolG.__name__ + name = self.__class__.__name__ super().__init__( prob_input=prob_input, parameters=parameters, name=name diff --git a/src/uqtestfuns/test_functions/welch1992.py b/src/uqtestfuns/test_functions/welch1992.py new file mode 100644 index 0000000..e5dd2d1 --- /dev/null +++ b/src/uqtestfuns/test_functions/welch1992.py @@ -0,0 +1,127 @@ +""" +Module with an implementation of the Welch et al. (1992) test function. + +The Welch1992 test function is a 20-dimensional scalar-valued function. +The function features some strong non-linear effects as well as some pair +interaction effects. Furthermore, two input variables (namely x8 and x16) +are inert. + +The function was introduced in [1] as a test function for metamodeling and +sensitivity analysis purposes. The function is also suitable for testing +multi-dimensional integration algorithms. + +References +---------- + +1. William J. Welch, Robert J. Buck, Jerome Sacks, Henry P. Wynn, + Toby J. Mitchell, and Max D. Morris, "Screening, predicting, and computer + experiments," Technometrics, vol. 34, no. 1, pp. 15-25, 1992. + DOI: 10.2307/1269548 +""" +import numpy as np + +from typing import Optional + +from ..core.prob_input.univariate_distribution import UnivDist +from ..core.uqtestfun_abc import UQTestFunABC +from .available import create_prob_input_from_available + +__all__ = ["Welch1992"] + +INPUT_MARGINALS_WELCH1992 = [ + UnivDist( + name=f"x{i}", + distribution="uniform", + parameters=[-0.5, 0.5], + description="None", + ) + for i in range(1, 20 + 1) +] + +AVAILABLE_INPUT_SPECS = { + "Welch1992": { + "name": "Welch1992", + "description": ( + "Input specification for the Welch1992 test function " + "from Welch et al. (1992)" + ), + "marginals": INPUT_MARGINALS_WELCH1992, + } +} + +DEFAULT_INPUT_SELECTION = "Welch1992" + + +class Welch1992(UQTestFunABC): + """A concrete implementation of the Welch et al. (1992) test function.""" + + _TAGS = ["metamodeling", "sensitivity", "integration"] + + _AVAILABLE_INPUTS = tuple(AVAILABLE_INPUT_SPECS.keys()) + + _AVAILABLE_PARAMETERS = None + + _DEFAULT_SPATIAL_DIMENSION = 20 + + _DESCRIPTION = "20-Dimensional function from Welch et al. (1992)" + + def __init__( + self, + *, + prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, + name: Optional[str] = None, + rng_seed_prob_input: Optional[int] = None, + ): + # --- Arguments processing + prob_input = create_prob_input_from_available( + prob_input_selection, + AVAILABLE_INPUT_SPECS, + rng_seed=rng_seed_prob_input, + ) + # Process the default name + if name is None: + name = self.__class__.__name__ + + super().__init__(prob_input=prob_input, name=name) + + def evaluate(self, xx: np.ndarray): + """Evaluate the Welch et al. (1992) function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + 1-Dimensional input values given by an N-by-1 array + where N is the number of input values. + + Returns + ------- + np.ndarray + The output of the test function evaluated on the input values. + The output is a 1-dimensional array of length N. + + Notes + ----- + - The input variables xx[:, 7] (x8) and xx[:, 15] (x16) are inert and + therefore, does not appear in the computation below. + """ + yy = ( + (5 * xx[:, 11]) / (1 + xx[:, 0]) + + 5 * (xx[:, 3] - xx[:, 19]) ** 2 + + xx[:, 4] + + 40 * xx[:, 18] ** 3 + - 5 * xx[:, 18] + + 0.05 * xx[:, 1] + + 0.08 * xx[:, 2] + - 0.03 * xx[:, 5] + + 0.03 * xx[:, 6] + - 0.09 * xx[:, 8] + - 0.01 * xx[:, 9] + - 0.07 * xx[:, 10] + + 0.25 * xx[:, 12] ** 2 + - 0.04 * xx[:, 13] + + 0.06 * xx[:, 14] + - 0.01 * xx[:, 16] + - 0.03 * xx[:, 17] + ) + + return yy diff --git a/src/uqtestfuns/utils.py b/src/uqtestfuns/utils.py index 6222fba..9d715ba 100644 --- a/src/uqtestfuns/utils.py +++ b/src/uqtestfuns/utils.py @@ -8,7 +8,13 @@ from typing import List, Optional, Tuple, Any from types import ModuleType -SUPPORTED_TAGS = ["sensitivity", "optimization", "metamodeling", "reliability"] +SUPPORTED_TAGS = [ + "sensitivity", + "optimization", + "metamodeling", + "reliability", + "integration", +] def get_available_classes( From cb7d81c7126f38a0f9a182d632a96f5cbbfbd5f4 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Fri, 9 Jun 2023 13:55:53 +0200 Subject: [PATCH 15/73] Add an implementation of Bratley1992d function - The function is M-dimensional used for numerical integration and global sensitivity analysis. - Minor editorial fix on the Welch1992 function. --- CHANGELOG.md | 6 + docs/_toc.yml | 2 + docs/fundamentals/integration.md | 9 +- docs/fundamentals/sensitivity.md | 25 +- docs/references.bib | 34 +++ docs/test-functions/available.md | 51 +++-- docs/test-functions/bratley1992d.md | 226 +++++++++++++++++++ docs/test-functions/welch1992.md | 13 +- src/uqtestfuns/test_functions/__init__.py | 2 + src/uqtestfuns/test_functions/bratley1992.py | 181 +++++++++++++++ src/uqtestfuns/test_functions/welch1992.py | 2 +- 11 files changed, 503 insertions(+), 48 deletions(-) create mode 100644 docs/test-functions/bratley1992d.md create mode 100644 src/uqtestfuns/test_functions/bratley1992.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dec649..6173e41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 relevant for metamodeling exercises, are added as UQ test functions. - The two-dimensional McLain functions (S1, S2, S3, S4, and S5), relevant for metamodeling exercises, are added as UQ test functions. +- An implementation of the Welch et al. (1992) test function, + a 20-dimensional function used in the context of metamodeling and sensitivity + analysis. +- An implementation of M-dimensional test functions (#4) from Bratley et al. + (1992) useful for testing multi-dimensional numerical integrations as well + as global sensitivity analysis. ## [0.1.1] - 2023-07-03 diff --git a/docs/_toc.yml b/docs/_toc.yml index a6debe0..4f3b4da 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -27,6 +27,8 @@ parts: title: Ackley - file: test-functions/borehole title: Borehole + - file: test-functions/bratley1992d + title: Bratley et al. (1992) D - file: test-functions/damped-oscillator title: Damped Oscillator - file: test-functions/flood diff --git a/docs/fundamentals/integration.md b/docs/fundamentals/integration.md index c931fb1..5bd5d8d 100644 --- a/docs/fundamentals/integration.md +++ b/docs/fundamentals/integration.md @@ -17,10 +17,11 @@ kernelspec: The table below listed the available test functions typically used in the testing and comparison of numerical integration method. -| Name | Spatial Dimension | Constructor | -|:-----------------------------------------------------:|:-----------------:|:--------------------:| -| {ref}`Sobol'-G ` | M | `SobolG()` | -| {ref}`Welch et al. (1992) ` | 20 | `Welch1992()` | +| Name | Spatial Dimension | Constructor | +|:------------------------------------------------------------:|:-----------------:|:----------------:| +| {ref}`Bratley et al. (1992) D ` | M | `Bratley1992d()` | +| {ref}`Sobol'-G ` | M | `SobolG()` | +| {ref}`Welch et al. (1992) ` | 20 | `Welch1992()` | In a Python terminal, you can list all the available functions relevant for metamodeling applications using ``list_functions()`` and filter the results diff --git a/docs/fundamentals/sensitivity.md b/docs/fundamentals/sensitivity.md index a454d75..cb9bcfd 100644 --- a/docs/fundamentals/sensitivity.md +++ b/docs/fundamentals/sensitivity.md @@ -17,18 +17,19 @@ kernelspec: The table below listed the available test functions typically used in the comparison of sensitivity analysis methods. -| Name | Spatial Dimension | Constructor | -|:-----------------------------------------------------------:|:-----------------:|:--------------------:| -| {ref}`Borehole ` | 8 | `Borehole()` | -| {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | -| {ref}`Flood ` | 8 | `Flood()` | -| {ref}`Ishigami ` | 3 | `Ishigami()` | -| {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | -| {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | -| {ref}`Sobol'-G ` | M | `SobolG()` | -| {ref}`Sulfur ` | 9 | `Sulfur()` | -| {ref}`Welch et al. (1992) ` | 20 | `Welch1992()` | -| {ref}`Wing Weight ` | 10 | `WingWeight()` | +| Name | Spatial Dimension | Constructor | +|:------------------------------------------------------------:|:-----------------:|:--------------------:| +| {ref}`Borehole ` | 8 | `Borehole()` | +| {ref}`Bratley et al. (1992) D ` | M | `Bratley1992d()` | +| {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | +| {ref}`Flood ` | 8 | `Flood()` | +| {ref}`Ishigami ` | 3 | `Ishigami()` | +| {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | +| {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | +| {ref}`Sobol'-G ` | M | `SobolG()` | +| {ref}`Sulfur ` | 9 | `Sulfur()` | +| {ref}`Welch et al. (1992) ` | 20 | `Welch1992()` | +| {ref}`Wing Weight ` | 10 | `WingWeight()` | In a Python terminal, you can list all the available functions relevant for metamodeling applications using ``list_functions()`` and filter the results diff --git a/docs/references.bib b/docs/references.bib index 7aaf556..f9a0b9e 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -455,4 +455,38 @@ @Article{Welch1992 doi = {10.2307/1269548}, } +@Article{Bratley1992, + author = {Paul Bratley and Bennett L. Fox and Harald Niederreiter}, + journal = {{ACM} Transactions on Modeling and Computer Simulation}, + title = {Implementation and tests of low-discrepancy sequences}, + year = {1992}, + number = {3}, + pages = {195--213}, + volume = {2}, + doi = {10.1145/146382.146385}, +} + +@Article{Saltelli2010, + author = {Andrea Saltelli and Paola Annoni and Ivano Azzini and Francesca Campolongo and Marco Ratto and Stefano Tarantola}, + journal = {Computer Physics Communications}, + title = {Variance based sensitivity analysis of model output. Design and estimator for the total sensitivity index}, + year = {2010}, + number = {2}, + pages = {259--270}, + volume = {181}, + doi = {10.1016/j.cpc.2009.09.018}, + publisher = {Elsevier {BV}}, +} + +@Article{Kucherenko2009, + author = {S. Kucherenko and M. Rodriguez-Fernandez and C. Pantelides and N. Shah}, + journal = {Reliability Engineering \& System Safety}, + title = {{Monte Carlo} evaluation of derivative-based global sensitivity measures}, + year = {2009}, + number = {7}, + pages = {1135--1148}, + volume = {94}, + doi = {10.1016/j.ress.2008.05.006}, +} + @Comment{jabref-meta: databaseType:bibtex;} diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index c066efb..8ff0127 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -18,31 +18,32 @@ kernelspec: The table below lists all the available _classic_ test functions from the literature available in the current UQTestFuns, regardless of their typical applications. -| Name | Spatial Dimension | Constructor | -|:-----------------------------------------------------------:|:-----------------:|:--------------------:| -| {ref}`Ackley ` | M | `Ackley()` | -| {ref}`Borehole ` | 8 | `Borehole()` | -| {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | -| {ref}`Flood ` | 8 | `Flood()` | -| {ref}`(1st) Franke ` | 2 | `Franke1()` | -| {ref}`(2nd) Franke ` | 2 | `Franke2()` | -| {ref}`(3rd) Franke ` | 2 | `Franke3()` | -| {ref}`(4th) Franke ` | 2 | `Franke4()` | -| {ref}`(5th) Franke ` | 2 | `Franke5()` | -| {ref}`(6th) Franke ` | 2 | `Franke6()` | -| {ref}`Ishigami ` | 3 | `Ishigami()` | -| {ref}`Oakley-O'Hagan 1D ` | 1 | `OakleyOHagan1D()` | -| {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | -| {ref}`McLain S1 ` | 2 | `McLainS1()` | -| {ref}`McLain S2 ` | 2 | `McLainS2()` | -| {ref}`McLain S3 ` | 2 | `McLainS3()` | -| {ref}`McLain S4 ` | 2 | `McLainS4()` | -| {ref}`McLain S5 ` | 2 | `McLainS5()` | -| {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | -| {ref}`Sobol'-G ` | M | `SobolG()` | -| {ref}`Sulfur ` | 9 | `Sulfur()` | -| {ref}`Welch1992 ` | 20 | `Welch1992()` | -| {ref}`Wing Weight ` | 10 | `WingWeight()` | +| Name | Spatial Dimension | Constructor | +|:------------------------------------------------------------:|:-----------------:|:--------------------:| +| {ref}`Ackley ` | M | `Ackley()` | +| {ref}`Borehole ` | 8 | `Borehole()` | +| {ref}`Bratley et al. (1992) D ` | M | `Bratley1992d()` | +| {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | +| {ref}`Flood ` | 8 | `Flood()` | +| {ref}`(1st) Franke ` | 2 | `Franke1()` | +| {ref}`(2nd) Franke ` | 2 | `Franke2()` | +| {ref}`(3rd) Franke ` | 2 | `Franke3()` | +| {ref}`(4th) Franke ` | 2 | `Franke4()` | +| {ref}`(5th) Franke ` | 2 | `Franke5()` | +| {ref}`(6th) Franke ` | 2 | `Franke6()` | +| {ref}`Ishigami ` | 3 | `Ishigami()` | +| {ref}`Oakley-O'Hagan 1D ` | 1 | `OakleyOHagan1D()` | +| {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | +| {ref}`McLain S1 ` | 2 | `McLainS1()` | +| {ref}`McLain S2 ` | 2 | `McLainS2()` | +| {ref}`McLain S3 ` | 2 | `McLainS3()` | +| {ref}`McLain S4 ` | 2 | `McLainS4()` | +| {ref}`McLain S5 ` | 2 | `McLainS5()` | +| {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | +| {ref}`Sobol'-G ` | M | `SobolG()` | +| {ref}`Sulfur ` | 9 | `Sulfur()` | +| {ref}`Welch1992 ` | 20 | `Welch1992()` | +| {ref}`Wing Weight ` | 10 | `WingWeight()` | In a Python terminal, you can list all the available functions along with the corresponding constructor using ``list_functions()``: diff --git a/docs/test-functions/bratley1992d.md b/docs/test-functions/bratley1992d.md new file mode 100644 index 0000000..124cdb1 --- /dev/null +++ b/docs/test-functions/bratley1992d.md @@ -0,0 +1,226 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:bratley1992d)= +# Bratley et al. (1992) D function + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +The Bratley et al. (1992) D function (or `Bratley1992d` function for short), +is an $M$-dimensional scalar-valued function. +The function was introduced in {cite}`Bratley1992` as a test function +for multi-dimensional numerical integration using low discrepancy sequences. +It was used in {cite}`Kucherenko2009` and {cite}`Saltelli2010` in the context +of global sensitivity analysis. + +```{note} +There are four other test functions used in Bratley et al. {cite}`Bratley1992` + +``` + +The plots for one-dimensional and two-dimensional `Bratley1992d` functions +are shown below. + +```{code-cell} ipython3 +:tags: [remove-input] + +from mpl_toolkits.axes_grid1 import make_axes_locatable + +# --- Create 1D data +my_bratley1992d_1d = uqtf.Bratley1992d(spatial_dimension=1) +xx_1d = np.linspace(0, 1, 1000)[:, np.newaxis] +yy_1d = my_bratley1992d_1d(xx_1d) + +# --- Create 2D data +my_bratley1992d_2d = uqtf.Bratley1992d(spatial_dimension=2) +mesh_2d = np.meshgrid(xx_1d, xx_1d) +xx_2d = np.array(mesh_2d).T.reshape(-1, 2) +yy_2d = my_bratley1992d_2d(xx_2d) + +# --- Create a series of plots +fig = plt.figure(figsize=(15, 5)) + +# 1D +axs_1 = plt.subplot(131) +axs_1.plot(xx_1d, yy_1d, color="#8da0cb") +axs_1.grid() +axs_1.set_xlabel("$x$", fontsize=14) +axs_1.set_ylabel("$\mathcal{M}(x)$", fontsize=14) +axs_1.set_title("1D Bratley1992d") + +# Surface +axs_2 = plt.subplot(132, projection='3d') +axs_2.plot_surface( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000, 1000).T, + cmap="plasma", + linewidth=0, + antialiased=False, + alpha=0.5 +) +axs_2.set_xlabel("$x_1$", fontsize=14) +axs_2.set_ylabel("$x_2$", fontsize=14) +axs_2.set_zlabel("$\mathcal{M}(x_1, x_2)$", fontsize=14) +axs_2.set_title("Surface plot of 2D Bratley1992d", fontsize=14) + +# Contour +axs_3 = plt.subplot(133) +cf = axs_3.contourf( + mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000).T, cmap="plasma" +) +axs_3.set_xlabel("$x_1$", fontsize=14) +axs_3.set_ylabel("$x_2$", fontsize=14) +axs_3.set_title("Contour plot of 2D Bratley1992d", fontsize=14) +divider = make_axes_locatable(axs_3) +cax = divider.append_axes('right', size='5%', pad=0.05) +fig.colorbar(cf, cax=cax, orientation='vertical') +axs_3.axis('scaled') + +fig.tight_layout(pad=3.0) +plt.gcf().set_dpi(150); +``` + +## Test function instance + +To create a default instance of the test function: + +```{code-cell} ipython3 +my_testfun = uqtf.Bratley1992d() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +By default, the spatial dimension is set to $2$. +To create an instance with another value of spatial dimension, +pass an integer to the parameter `spatial_dimension` (or as the first argument). +For example, to create an instance of 10-dimensional `Bratley1992d` function, +type: + +```{code-cell} ipython3 +my_testfun = uqtf.Bratley1992d(spatial_dimension=10) +``` + +Or alternatively, pass the dimension as the first argument: + +```{code-cell} ipython3 +my_testfun = uqtf.Bratley1992d(10) +``` + +## Description + +The `Bratley1992d` function is defined as follows[^location]: + +$$ +\mathcal{M}(\boldsymbol{x}) = \sum_{m = 1}^{M} (-1)^m \prod_{i = 1}^{m} x_m, +$$ + +where $\boldsymbol{x} = \{ x_1, \ldots, x_M \}$ +is the $M$-dimensional vector of input variables further defined below. + +## Probabilistic input + +Based on {cite}`Bratley1992`, the test function is integrated over the +hypercube domain of $[0, 1]^M$. This specification was adopted in +the application of the function as global sensitivity analysis test functions +(see {cite}`Kucherenko2009, Saltelli2010`). + +Such an input specification can be modeled using an $M$ independent uniform +random variables as shown in the table below. + +| No. | Name | Distribution | Parameters | Description | +|:---------:|:--------:|:-------------:|:----------:|:-----------:| +| 1 | $x_1$ | uniform | [0.0 1.0] | N/A | +| $\vdots$ | $\vdots$ | $\vdots$ | $\vdots$ | $\vdots$ | +| M | $x_M$ | uniform | [0.0 1.0] | N/A | + + +## Reference results + +This section provides several reference results of typical UQ analyses involving +the test function. + +### Definite integration + +The integral value of the function over the domain of $[0.0, 1.0]^M$ +is analytical: + +$$ +I[\mathcal{M}] (M) \equiv \int_{[0, 1]^M} \mathcal{M}(\boldsymbol{x}) \; d\boldsymbol{x} = - \frac{1}{3} \left( 1 - \left( - \frac{1}{2} \right)^M \right). +$$ + +### Moments + +The moments of the test function are analytically known +and the first two moments are given below. + +#### Expected value + +Due to the domain being a hypercube, +the above integral value over the domain is the same as the expected value: + +$$ +\mathbb{E}[\mathcal{M}](M) = - \frac{1}{3} \left( 1 - \left( - \frac{1}{2} \right)^M \right). +$$ + +#### Variance + +The analytical value for the variance {cite}`Saltelli2010` is given as: + +$$ +\mathbb{V}[\mathcal{M}](M) = \frac{1}{10} \left(\frac{1}{3}\right)^M + \frac{1}{18} - \frac{1}{9} \left( \frac{1}{2} \right)^{2M} + (-1)^{M+1} \frac{2}{45} \left( \frac{1}{2} \right)^M +$$ + +### Sensitivity analysis + +Some sensitivity measures of the test function are known analytically; +they make the function ideal for testing sensitivity analysis methods. + +#### Total-effect Sobol' indices + +The total-effect Sobol' indices for the input variable $m$ +is given below as a function of the total number of dimensions $M$: + +$$ +ST_m(M) = \frac{\mathbb{E}[\mathcal{M}^2](M) - E(m) - \frac{1}{4} (T_1(m, M) - 2 T_2(m, M) + T_3(m, M)) - T_4(m, M) - T_5(m, M)}{\mathbb{V}[\mathcal{M}](M)}, +$$ + +where: + +- $\mathbb{E}[\mathcal{M}^2](M) = \frac{1}{6} \left( 1 - \left( \frac{1}{3} \right)^M \right) + \frac{4}{15} \left( (-1)^{M+1} \left( \frac{1}{2} \right)^M + \left(\frac{1}{3} \right)^M \right)$ +- $E(m) = \frac{1}{6} \left( 1 - \left( \frac{1}{3} \right)^{m-1} \right) + \frac{4}{15} \left( (-1)^{m} \left( \frac{1}{2} \right)^{m-1} + \left(\frac{1}{3} \right)^{m-1} \right)$ +- $T_1(m, M) = \frac{1}{2} \left( \frac{1}{3} \right)^{m-2} \left( 1 - \left( \frac{1}{3} \right)^{M - m + 1} \right)$ +- $T_2(m, M) = \frac{1}{2} \left( \left( \frac{1}{3} \right)^{m-1} - \left( \frac{1}{3} \right)^M \right)$ +- $T_3(m, M) = \frac{3}{5} \left( 4 \left( \frac{1}{3} \right)^{M+1} + (-1)^{m+M} \left( \frac{1}{2} \right)^{M - m - 1} \left( \frac{1}{3} \right)^m \right)$ +- $T_4(m, M) = \frac{1}{5} \left( (-1)^{m+1} \left( \frac{1}{3} \right) \left( \frac{1}{2} \right)^{m - 3} - 4 \left( \frac{1}{3} \right)^m \right)$ +- $T_5(m, M) = \frac{1}{5} \left( (-1)^{M+1} \left( \frac{1}{3} \right) \left( \frac{1}{2} \right)^{M-2} + (-1)^{M + m - 1} \left( \frac{1}{3} \right)^m \left( \frac{1}{2} \right)^{M - m - 1} \right)$ +- $\mathbb{V}[\mathcal{M}](M)$ is the variance of the function as given in the previous section. + +## References + +```{bibliography} +:style: unsrtalpha +:filter: docname in docnames +``` + +[^location]: see Section 5.1, p. 207 (test function no. 4) +in {cite}`Bratley1992`. diff --git a/docs/test-functions/welch1992.md b/docs/test-functions/welch1992.md index f037441..e4f37bc 100644 --- a/docs/test-functions/welch1992.md +++ b/docs/test-functions/welch1992.md @@ -21,13 +21,14 @@ import matplotlib.pyplot as plt import uqtestfuns as uqtf ``` -The Welch et al. (1992) test function is a 20-dimensional scalar valued -function. The function features some strong non-linearities as well as some +The Welch et al. (1992) test function (or the `Welch1992` function for short) +is a 20-dimensional scalar-valued function. +The function features some strong non-linearities as well as some pair interaction effects. Furthermore, a couple of two input variables are set to be inert. The function was introduced in Welch et al. (1992) {cite}`Welch1992` -as a test function[^location] for metamodeling and sensitivity analysis purposes. +as a test function for metamodeling and sensitivity analysis purposes. The function is also suitable for testing multi-dimensional integration algorithms. @@ -47,7 +48,7 @@ print(my_testfun) ## Description -The Welch et al. (1992) test function is defined as follows: +The `Welch1992` function is defined as follows[^location]: $$ \begin{aligned} @@ -68,7 +69,7 @@ and therefore, missing from the expression. Based on {cite}`Welch1992`, the test function is defined on the 20-dimensional input space $[-0.5, 0.5]^{20}$. -This input can be modeled using 20 inpedendent uniform random variables +This input can be modeled using 20 independent uniform random variables shown in the table below. ```{code-cell} ipython3 @@ -104,7 +105,7 @@ The integral value of the test function over the domain $[-0.5, 0.5]^{20}$ is analytical: $$ -\int_{[-0.5, 0.5]^{20}} \mathcal{M}(\boldsymbol{x}) \; d\boldsymbol{x} = \frac{41}{48}. +I[\mathcal{M}] = \int_{[-0.5, 0.5]^{20}} \mathcal{M}(\boldsymbol{x}) \; d\boldsymbol{x} = \frac{41}{48}. $$ ### Moments estimation diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index 85c5fcc..14a0024 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -3,6 +3,7 @@ """ from .ackley import Ackley from .borehole import Borehole +from .bratley1992 import Bratley1992d from .damped_oscillator import DampedOscillator from .franke import Franke1, Franke2, Franke3, Franke4, Franke5, Franke6 from .flood import Flood @@ -22,6 +23,7 @@ __all__ = [ "Ackley", "Borehole", + "Bratley1992d", "DampedOscillator", "Flood", "Franke1", diff --git a/src/uqtestfuns/test_functions/bratley1992.py b/src/uqtestfuns/test_functions/bratley1992.py new file mode 100644 index 0000000..62ccb95 --- /dev/null +++ b/src/uqtestfuns/test_functions/bratley1992.py @@ -0,0 +1,181 @@ +""" +Module with an implementation of the Bratley et al. (1992) test functions. + +The Bratley et al. (1992) paper [1] contains four M-dimensional scalar-valued +functions for testing multi-dimensional numerical integrations using low +discrepancy sequences. The functions was used in [2] and [3] in the context +of global sensitivity analysis. + +The four functions are: + +- Bratley1992d: A sum of products integrand; integration test function #4 + +The Bratley1992d function may also be referred to as the "Bratley function" +or "Bratley et al. (1992) function" in the literature. + +References +---------- + +1. Paul Bratley, Bennet L. Fox, and Harald Niederreiter, "Implementation and + tests of low-discrepancy sequences," ACM Transactions on Modeling and + Computer Simulation, vol. 2, no. 3, pp. 195-213, 1992. + DOI:10.1145/146382.146385 +2. S. Kucherenko, M. Rodriguez-Fernandez, C. Pantelides, and N. Shah, + “Monte Carlo evaluation of derivative-based global sensitivity measures,” + Reliability Engineering & System Safety, vol. 94, pp. 1137–1148, 2009. + DOI:10.1016/j.ress.2008.05.006 +3. A. Saltelli, P. Annoni, I. Azzini, F. Campolongo, M. Ratto, + and S. Tarantola, “Variance based sensitivity analysis of model output. + Design and estimator for the total sensitivity index,” Computer Physics + Communications, vol. 181, no. 2, pp. 259–270, 2010, + DOI:10.1016/j.cpc.2009.09.018 +""" +import numpy as np + +from typing import List, Optional + +from ..core.uqtestfun_abc import UQTestFunABC +from ..core.prob_input.univariate_distribution import UnivDist +from .available import create_prob_input_from_available + +__all__ = ["Bratley1992d"] + + +def _bratley_input(spatial_dimension: int) -> List[UnivDist]: + """Create a list of marginals for the M-dimensional Bratley test functions. + + Parameters + ---------- + spatial_dimension : int + The requested spatial dimension of the probabilistic input model. + + Returns + ------- + List[UnivDist] + A list of marginals for the multivariate input following Ref. [1] + """ + marginals = [] + for i in range(spatial_dimension): + marginals.append( + UnivDist( + name=f"X{i + 1}", + distribution="uniform", + parameters=[0.0, 1.0], + description="None", + ) + ) + + return marginals + + +AVAILABLE_INPUT_SPECS = { + "Bratley1992": { + "name": "Bratley1992", + "description": ( + "Integration domain of the functions from Bratley et al. (1992)" + ), + "marginals": _bratley_input, + "copulas": None, + }, +} + +DEFAULT_INPUT_SELECTION = "Bratley1992" + +# The dimension is variable so define a default for fallback +DEFAULT_DIMENSION_SELECTION = 2 + +# Common metadata used in each class definition of Bratley test functions +COMMON_METADATA = dict( + _TAGS=["integration", "sensitivity"], + _AVAILABLE_INPUTS=tuple(AVAILABLE_INPUT_SPECS.keys()), + _AVAILABLE_PARAMETERS=None, + _DEFAULT_SPATIAL_DIMENSION=None, + _DESCRIPTION="from Bratley et al. (1992)", +) + + +def _init( + self, + spatial_dimension: int = DEFAULT_DIMENSION_SELECTION, + *, + prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, + name: Optional[str] = None, + rng_seed_prob_input: Optional[int] = None, +) -> None: + """A common __init__ for all Bratley1992 test functions.""" + # --- Arguments processing + if not isinstance(spatial_dimension, int): + raise TypeError( + f"Spatial dimension is expected to be of 'int'. " + f"Got {type(spatial_dimension)} instead." + ) + # A Bratley1992 test function is an M-dimensional test function + # Create the input according to spatial dimension + prob_input = create_prob_input_from_available( + prob_input_selection, + AVAILABLE_INPUT_SPECS, + spatial_dimension, + rng_seed_prob_input, + ) + # Process the default name + if name is None: + name = self.__class__.__name__ + + UQTestFunABC.__init__(self, prob_input=prob_input, name=name) + + +class Bratley1992d(UQTestFunABC): + """A concrete implementation of the function 4 from Bratley et al. (1988). + + Parameters + ---------- + spatial_dimension : int + The requested number of spatial_dimension. If not specified, + the default is set to 2. + prob_input_selection : str, optional + The selection of a probabilistic input model from a list of + available specifications. This is a keyword only parameter. + name : str, optional + The name of the instance; if not given the default name is used. + This is a keyword only parameter. + rng_seed_prob_input : int, optional + The seed number for the pseudo-random number generator of the + corresponding `ProbInput`; if not given `None` is used + (taken from the system entropy). + This is a keyword only parameter. + """ + + _TAGS = COMMON_METADATA["_TAGS"] + _AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"] + _AVAILABLE_PARAMETERS = COMMON_METADATA["_AVAILABLE_PARAMETERS"] + _DEFAULT_SPATIAL_DIMENSION = COMMON_METADATA["_DEFAULT_SPATIAL_DIMENSION"] + _DESCRIPTION = ( + f"Integration test function #4 {COMMON_METADATA['_DESCRIPTION']}" + ) + + __init__ = _init # type: ignore + + def evaluate(self, xx: np.ndarray): + """Evaluate the test function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + M-Dimensional input values given by an N-by-M array where + N is the number of input values. + + Returns + ------- + np.ndarray + The output of the test function evaluated on the input values. + The output is a 1-dimensional array of length N. + """ + + num_points, num_dim = xx.shape + yy = np.zeros(num_points) + + # Compute the function + for j in range(num_dim): + yy += (-1) ** (j + 1) * np.prod(xx[:, : j + 1], axis=1) + + return yy diff --git a/src/uqtestfuns/test_functions/welch1992.py b/src/uqtestfuns/test_functions/welch1992.py index e5dd2d1..182457c 100644 --- a/src/uqtestfuns/test_functions/welch1992.py +++ b/src/uqtestfuns/test_functions/welch1992.py @@ -42,7 +42,7 @@ "Welch1992": { "name": "Welch1992", "description": ( - "Input specification for the Welch1992 test function " + "Input specification for the test function " "from Welch et al. (1992)" ), "marginals": INPUT_MARGINALS_WELCH1992, From 118bdddbde54299b86f0974f63f59efc469aa69e Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Tue, 13 Jun 2023 09:46:33 +0200 Subject: [PATCH 16/73] All class attributes are now lower case - The class attributes of UQTestFunABC are now written in lower case letters to conform with Python convention. These attributes may still be read-only but there is already a mechanism to prevent rewriting the value (no setter). --- .../adding-test-function-implementation.md | 10 +-- src/uqtestfuns/core/uqtestfun.py | 10 +-- src/uqtestfuns/core/uqtestfun_abc.py | 52 +++++--------- src/uqtestfuns/helpers.py | 10 +-- src/uqtestfuns/test_functions/ackley.py | 10 +-- src/uqtestfuns/test_functions/borehole.py | 10 +-- src/uqtestfuns/test_functions/bratley1992.py | 22 +++--- .../test_functions/damped_oscillator.py | 10 +-- src/uqtestfuns/test_functions/flood.py | 10 +-- src/uqtestfuns/test_functions/franke.py | 70 +++++++++---------- src/uqtestfuns/test_functions/ishigami.py | 10 +-- src/uqtestfuns/test_functions/mclain.py | 60 ++++++++-------- .../test_functions/oakley_ohagan_1d.py | 10 +-- src/uqtestfuns/test_functions/otl_circuit.py | 10 +-- src/uqtestfuns/test_functions/piston.py | 10 +-- src/uqtestfuns/test_functions/sobol_g.py | 10 +-- src/uqtestfuns/test_functions/sulfur.py | 10 +-- src/uqtestfuns/test_functions/welch1992.py | 10 +-- src/uqtestfuns/test_functions/wing_weight.py | 10 +-- tests/builtin_test_functions/test_ishigami.py | 2 +- tests/builtin_test_functions/test_sobol_g.py | 2 +- .../test_test_functions.py | 14 ++-- tests/core/test_uqtestfun.py | 2 +- 23 files changed, 179 insertions(+), 195 deletions(-) diff --git a/docs/development/adding-test-function-implementation.md b/docs/development/adding-test-function-implementation.md index f17b05d..32af6f1 100644 --- a/docs/development/adding-test-function-implementation.md +++ b/docs/development/adding-test-function-implementation.md @@ -329,15 +329,15 @@ They are useful for the organization and bookkeeping of the test functions. ```python class Branin(UQTestFunABC): ... - _TAGS = ["optimization"] # Application tags + _tags = ["optimization"] # Application tags - _AVAILABLE_INPUTS = tuple(AVAILABLE_INPUT_SPECS.keys()) # Input selection keywords + _available_inputs = tuple(AVAILABLE_INPUT_SPECS.keys()) # Input selection keywords - _AVAILABLE_PARAMETERS = tuple(AVAILABLE_PARAMETERS.keys()) # Parameters selection keywords + _available_parameters = tuple(AVAILABLE_PARAMETERS.keys()) # Parameters selection keywords - _DEFAULT_SPATIAL_DIMENSION = 2 # Spatial dimension of the function + _default_spatial_dimension = 2 # Spatial dimension of the function - _DESCRIPTION = "Branin function from Dixon and Szegö (1978)" # Short description + _description = "Branin function from Dixon and Szegö (1978)" # Short description ... ``` diff --git a/src/uqtestfuns/core/uqtestfun.py b/src/uqtestfuns/core/uqtestfun.py index 0d02559..e864ccc 100644 --- a/src/uqtestfuns/core/uqtestfun.py +++ b/src/uqtestfuns/core/uqtestfun.py @@ -13,15 +13,15 @@ class UQTestFun(UQTestFunABC): """Generic concrete class of UQ test function.""" - _TAGS = None + _tags = None - _AVAILABLE_INPUTS = None + _available_inputs = None - _AVAILABLE_PARAMETERS = None + _available_parameters = None - _DEFAULT_SPATIAL_DIMENSION = None + _default_spatial_dimension = None - _DESCRIPTION = None + _description = None def __init__( self, diff --git a/src/uqtestfuns/core/uqtestfun_abc.py b/src/uqtestfuns/core/uqtestfun_abc.py index d3f67ca..f9b372e 100644 --- a/src/uqtestfuns/core/uqtestfun_abc.py +++ b/src/uqtestfuns/core/uqtestfun_abc.py @@ -12,11 +12,11 @@ __all__ = ["UQTestFunABC"] CLASS_HIDDEN_ATTRIBUTES = [ - "_TAGS", - "_AVAILABLE_INPUTS", - "_AVAILABLE_PARAMETERS", - "_DEFAULT_SPATIAL_DIMENSION", - "_DESCRIPTION", + "_tags", + "_available_inputs", + "_available_parameters", + "_default_spatial_dimension", + "_description", ] @@ -31,23 +31,7 @@ def __set__(self, owner_self, owner_cls): class UQTestFunABC(abc.ABC): - """An abstract class for UQ test functions. - - Attributes - ---------- - tags : List[str] - A List of tags to classify a test function given known field of - applications in the literature. This is an abstract class property. - available_inputs : Optional[Tuple[str, ...]] - A tuple of available probabilistic input model specification in the - literature. This is an abstract class property. - available_parameters : Optional[Tuple[str, ...]] - A tuple of available set of parameter values in the literature. - This is an abstract class property. - default_spatial_dimension : Optional[int] - The default spatial dimension of a UQ test function. If 'None' then - the function is a variable dimensional test function. - """ + """An abstract class for UQ test functions.""" def __init__( self, @@ -89,29 +73,29 @@ def __init_subclass__(cls): ) @classproperty - def TAGS(cls) -> Optional[List[str]]: + def tags(cls) -> Optional[List[str]]: """Tags to classify different UQ test functions.""" - return cls._TAGS # type: ignore + return cls._tags # type: ignore @classproperty - def AVAILABLE_INPUTS(cls) -> Optional[Tuple[str, ...]]: + def available_inputs(cls) -> Optional[Tuple[str, ...]]: """All the keys to the available probabilistic input specifications.""" - return cls._AVAILABLE_INPUTS # type: ignore + return cls._available_inputs # type: ignore @classproperty - def AVAILABLE_PARAMETERS(cls) -> Optional[Tuple[str, ...]]: + def available_parameters(cls) -> Optional[Tuple[str, ...]]: """All the keys to the available set of parameter values.""" - return cls._AVAILABLE_PARAMETERS # type: ignore + return cls._available_parameters # type: ignore @classproperty - def DEFAULT_SPATIAL_DIMENSION(cls) -> Optional[int]: + def default_spatial_dimension(cls) -> Optional[int]: """To store the default dimension of a test function.""" - return cls._DEFAULT_SPATIAL_DIMENSION # type: ignore + return cls._default_spatial_dimension # type: ignore @classproperty - def DESCRIPTION(cls) -> Optional[str]: + def description(cls) -> Optional[str]: """Short description of the UQ test function.""" - return cls._DESCRIPTION # type: ignore + return cls._description # type: ignore @property def prob_input(self) -> Optional[ProbInput]: @@ -145,7 +129,7 @@ def spatial_dimension(self) -> int: if self._prob_input is not None: return self._prob_input.spatial_dimension else: - return self.DEFAULT_SPATIAL_DIMENSION + return self.default_spatial_dimension @property def name(self) -> Optional[str]: @@ -227,7 +211,7 @@ def __str__(self): out = ( f"Name : {self.name}\n" f"Spatial dimension : {self.spatial_dimension}\n" - f"Description : {self.DESCRIPTION}" + f"Description : {self.description}" ) return out diff --git a/src/uqtestfuns/helpers.py b/src/uqtestfuns/helpers.py index 53a6909..9ef490b 100644 --- a/src/uqtestfuns/helpers.py +++ b/src/uqtestfuns/helpers.py @@ -108,13 +108,13 @@ def list_functions( ): available_class = available_classes_dict[available_class_name] - default_spatial_dimension = available_class.DEFAULT_SPATIAL_DIMENSION + default_spatial_dimension = available_class.default_spatial_dimension if not default_spatial_dimension: default_spatial_dimension = "M" - tags = ", ".join(available_class.TAGS) + tags = ", ".join(available_class.tags) - description = available_class.DESCRIPTION + description = available_class.description value = [ idx + 1, @@ -227,7 +227,7 @@ def _get_functions_from_dimension( available_class_path, ) in available_classes.items(): default_spatial_dimension = ( - available_class_path.DEFAULT_SPATIAL_DIMENSION + available_class_path.default_spatial_dimension ) if not default_spatial_dimension: default_spatial_dimension = "m" @@ -248,7 +248,7 @@ def _get_functions_from_tag( available_class_name, available_class_path, ) in available_classes.items(): - tags = available_class_path.TAGS + tags = available_class_path.tags if tag in tags: values.append(available_class_name) diff --git a/src/uqtestfuns/test_functions/ackley.py b/src/uqtestfuns/test_functions/ackley.py index c3f6d54..05a4cf8 100644 --- a/src/uqtestfuns/test_functions/ackley.py +++ b/src/uqtestfuns/test_functions/ackley.py @@ -105,15 +105,15 @@ class Ackley(UQTestFunABC): This is a keyword only parameter. """ - _TAGS = ["optimization", "metamodeling"] + _tags = ["optimization", "metamodeling"] - _AVAILABLE_INPUTS = tuple(AVAILABLE_INPUT_SPECS.keys()) + _available_inputs = tuple(AVAILABLE_INPUT_SPECS.keys()) - _AVAILABLE_PARAMETERS = tuple(AVAILABLE_PARAMETERS.keys()) + _available_parameters = tuple(AVAILABLE_PARAMETERS.keys()) - _DEFAULT_SPATIAL_DIMENSION = None + _default_spatial_dimension = None - _DESCRIPTION = "Ackley function from Ackley (1987)" + _description = "Ackley function from Ackley (1987)" def __init__( self, diff --git a/src/uqtestfuns/test_functions/borehole.py b/src/uqtestfuns/test_functions/borehole.py index 183291d..44b83ce 100644 --- a/src/uqtestfuns/test_functions/borehole.py +++ b/src/uqtestfuns/test_functions/borehole.py @@ -127,15 +127,15 @@ class Borehole(UQTestFunABC): """A concrete implementation of the Borehole function.""" - _TAGS = ["metamodeling", "sensitivity"] + _tags = ["metamodeling", "sensitivity"] - _AVAILABLE_INPUTS = tuple(AVAILABLE_INPUT_SPECS.keys()) + _available_inputs = tuple(AVAILABLE_INPUT_SPECS.keys()) - _AVAILABLE_PARAMETERS = None + _available_parameters = None - _DEFAULT_SPATIAL_DIMENSION = 8 + _default_spatial_dimension = 8 - _DESCRIPTION = "Borehole function from Harper and Gupta (1983)" + _description = "Borehole function from Harper and Gupta (1983)" def __init__( self, diff --git a/src/uqtestfuns/test_functions/bratley1992.py b/src/uqtestfuns/test_functions/bratley1992.py index 62ccb95..cab9301 100644 --- a/src/uqtestfuns/test_functions/bratley1992.py +++ b/src/uqtestfuns/test_functions/bratley1992.py @@ -86,11 +86,11 @@ def _bratley_input(spatial_dimension: int) -> List[UnivDist]: # Common metadata used in each class definition of Bratley test functions COMMON_METADATA = dict( - _TAGS=["integration", "sensitivity"], - _AVAILABLE_INPUTS=tuple(AVAILABLE_INPUT_SPECS.keys()), - _AVAILABLE_PARAMETERS=None, - _DEFAULT_SPATIAL_DIMENSION=None, - _DESCRIPTION="from Bratley et al. (1992)", + _tags=["integration", "sensitivity"], + _available_inputs=tuple(AVAILABLE_INPUT_SPECS.keys()), + _available_parameters=None, + _default_spatial_dimension=None, + _description="from Bratley et al. (1992)", ) @@ -145,12 +145,12 @@ class Bratley1992d(UQTestFunABC): This is a keyword only parameter. """ - _TAGS = COMMON_METADATA["_TAGS"] - _AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"] - _AVAILABLE_PARAMETERS = COMMON_METADATA["_AVAILABLE_PARAMETERS"] - _DEFAULT_SPATIAL_DIMENSION = COMMON_METADATA["_DEFAULT_SPATIAL_DIMENSION"] - _DESCRIPTION = ( - f"Integration test function #4 {COMMON_METADATA['_DESCRIPTION']}" + _tags = COMMON_METADATA["_tags"] + _available_inputs = COMMON_METADATA["_available_inputs"] + _available_parameters = COMMON_METADATA["_available_parameters"] + _default_spatial_dimension = COMMON_METADATA["_default_spatial_dimension"] + _description = ( + f"Integration test function #4 {COMMON_METADATA['_description']}" ) __init__ = _init # type: ignore diff --git a/src/uqtestfuns/test_functions/damped_oscillator.py b/src/uqtestfuns/test_functions/damped_oscillator.py index 8986f8d..f674407 100644 --- a/src/uqtestfuns/test_functions/damped_oscillator.py +++ b/src/uqtestfuns/test_functions/damped_oscillator.py @@ -125,15 +125,15 @@ class DampedOscillator(UQTestFunABC): """A concrete implementation of the Damped oscillator test function.""" - _TAGS = ["metamodeling", "sensitivity"] + _tags = ["metamodeling", "sensitivity"] - _AVAILABLE_INPUTS = tuple(AVAILABLE_INPUT_SPECS.keys()) + _available_inputs = tuple(AVAILABLE_INPUT_SPECS.keys()) - _AVAILABLE_PARAMETERS = None + _available_parameters = None - _DEFAULT_SPATIAL_DIMENSION = 8 + _default_spatial_dimension = 8 - _DESCRIPTION = ( + _description = ( "Damped oscillator model from Igusa and Der Kiureghian (1985)" ) diff --git a/src/uqtestfuns/test_functions/flood.py b/src/uqtestfuns/test_functions/flood.py index 710a49e..0737e8d 100644 --- a/src/uqtestfuns/test_functions/flood.py +++ b/src/uqtestfuns/test_functions/flood.py @@ -109,15 +109,15 @@ class Flood(UQTestFunABC): """Concrete implementation of the Flood model test function.""" - _TAGS = ["metamodeling", "sensitivity"] + _tags = ["metamodeling", "sensitivity"] - _AVAILABLE_INPUTS = tuple(AVAILABLE_INPUT_SPECS.keys()) + _available_inputs = tuple(AVAILABLE_INPUT_SPECS.keys()) - _AVAILABLE_PARAMETERS = None + _available_parameters = None - _DEFAULT_SPATIAL_DIMENSION = 8 + _default_spatial_dimension = 8 - _DESCRIPTION = "Flood model from Iooss and Lemaître (2015)" + _description = "Flood model from Iooss and Lemaître (2015)" def __init__( self, diff --git a/src/uqtestfuns/test_functions/franke.py b/src/uqtestfuns/test_functions/franke.py index b495ce8..0956ef5 100644 --- a/src/uqtestfuns/test_functions/franke.py +++ b/src/uqtestfuns/test_functions/franke.py @@ -79,11 +79,11 @@ DEFAULT_INPUT_SELECTION = "Franke1979" COMMON_METADATA = dict( - _TAGS=["metamodeling"], - _AVAILABLE_INPUTS=tuple(AVAILABLE_INPUT_SPECS.keys()), - _AVAILABLE_PARAMETERS=None, - _DEFAULT_SPATIAL_DIMENSION=2, - _DESCRIPTION="from Franke (1979)", + _tags=["metamodeling"], + _available_inputs=tuple(AVAILABLE_INPUT_SPECS.keys()), + _available_parameters=None, + _default_spatial_dimension=2, + _description="from Franke (1979)", ) @@ -114,11 +114,11 @@ class Franke1(UQTestFunABC): The function features two Gaussian peaks and a Gaussian dip. """ - _TAGS = COMMON_METADATA["_TAGS"] - _AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"] - _AVAILABLE_PARAMETERS = COMMON_METADATA["_AVAILABLE_PARAMETERS"] - _DEFAULT_SPATIAL_DIMENSION = COMMON_METADATA["_DEFAULT_SPATIAL_DIMENSION"] - _DESCRIPTION = f"(1st) Franke function {COMMON_METADATA['_DESCRIPTION']}" + _tags = COMMON_METADATA["_tags"] + _available_inputs = COMMON_METADATA["_available_inputs"] + _available_parameters = COMMON_METADATA["_available_parameters"] + _default_spatial_dimension = COMMON_METADATA["_default_spatial_dimension"] + _description = f"(1st) Franke function {COMMON_METADATA['_description']}" __init__ = _init # type: ignore @@ -161,11 +161,11 @@ class Franke2(UQTestFunABC): The function features two plateaus joined by a steep hill. """ - _TAGS = COMMON_METADATA["_TAGS"] - _AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"] - _AVAILABLE_PARAMETERS = COMMON_METADATA["_AVAILABLE_PARAMETERS"] - _DEFAULT_SPATIAL_DIMENSION = COMMON_METADATA["_DEFAULT_SPATIAL_DIMENSION"] - _DESCRIPTION = f"(2nd) Franke function {COMMON_METADATA['_DESCRIPTION']}" + _tags = COMMON_METADATA["_tags"] + _available_inputs = COMMON_METADATA["_available_inputs"] + _available_parameters = COMMON_METADATA["_available_parameters"] + _default_spatial_dimension = COMMON_METADATA["_default_spatial_dimension"] + _description = f"(2nd) Franke function {COMMON_METADATA['_description']}" __init__ = _init # type: ignore @@ -196,11 +196,11 @@ class Franke3(UQTestFunABC): The function features a saddle shaped surface. """ - _TAGS = COMMON_METADATA["_TAGS"] - _AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"] - _AVAILABLE_PARAMETERS = COMMON_METADATA["_AVAILABLE_PARAMETERS"] - _DEFAULT_SPATIAL_DIMENSION = COMMON_METADATA["_DEFAULT_SPATIAL_DIMENSION"] - _DESCRIPTION = f"(3rd) Franke function {COMMON_METADATA['_DESCRIPTION']}" + _tags = COMMON_METADATA["_tags"] + _available_inputs = COMMON_METADATA["_available_inputs"] + _available_parameters = COMMON_METADATA["_available_parameters"] + _default_spatial_dimension = COMMON_METADATA["_default_spatial_dimension"] + _description = f"(3rd) Franke function {COMMON_METADATA['_description']}" __init__ = _init # type: ignore @@ -234,11 +234,11 @@ class Franke4(UQTestFunABC): The function features a gentle Gaussian hill. """ - _TAGS = COMMON_METADATA["_TAGS"] - _AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"] - _AVAILABLE_PARAMETERS = COMMON_METADATA["_AVAILABLE_PARAMETERS"] - _DEFAULT_SPATIAL_DIMENSION = COMMON_METADATA["_DEFAULT_SPATIAL_DIMENSION"] - _DESCRIPTION = f"(4th) Franke function {COMMON_METADATA['_DESCRIPTION']}" + _tags = COMMON_METADATA["_tags"] + _available_inputs = COMMON_METADATA["_available_inputs"] + _available_parameters = COMMON_METADATA["_available_parameters"] + _default_spatial_dimension = COMMON_METADATA["_default_spatial_dimension"] + _description = f"(4th) Franke function {COMMON_METADATA['_description']}" __init__ = _init # type: ignore @@ -274,11 +274,11 @@ class Franke5(UQTestFunABC): The function features a steep Gaussian hill. """ - _TAGS = COMMON_METADATA["_TAGS"] - _AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"] - _AVAILABLE_PARAMETERS = COMMON_METADATA["_AVAILABLE_PARAMETERS"] - _DEFAULT_SPATIAL_DIMENSION = COMMON_METADATA["_DEFAULT_SPATIAL_DIMENSION"] - _DESCRIPTION = f"(5th) Franke function {COMMON_METADATA['_DESCRIPTION']}" + _tags = COMMON_METADATA["_tags"] + _available_inputs = COMMON_METADATA["_available_inputs"] + _available_parameters = COMMON_METADATA["_available_parameters"] + _default_spatial_dimension = COMMON_METADATA["_default_spatial_dimension"] + _description = f"(5th) Franke function {COMMON_METADATA['_description']}" __init__ = _init # type: ignore @@ -314,11 +314,11 @@ class Franke6(UQTestFunABC): The function features a part of a sphere. """ - _TAGS = COMMON_METADATA["_TAGS"] - _AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"] - _AVAILABLE_PARAMETERS = COMMON_METADATA["_AVAILABLE_PARAMETERS"] - _DEFAULT_SPATIAL_DIMENSION = COMMON_METADATA["_DEFAULT_SPATIAL_DIMENSION"] - _DESCRIPTION = f"(6th) Franke function {COMMON_METADATA['_DESCRIPTION']}" + _tags = COMMON_METADATA["_tags"] + _available_inputs = COMMON_METADATA["_available_inputs"] + _available_parameters = COMMON_METADATA["_available_parameters"] + _default_spatial_dimension = COMMON_METADATA["_default_spatial_dimension"] + _description = f"(6th) Franke function {COMMON_METADATA['_description']}" __init__ = _init # type: ignore diff --git a/src/uqtestfuns/test_functions/ishigami.py b/src/uqtestfuns/test_functions/ishigami.py index 19bf866..114ee63 100644 --- a/src/uqtestfuns/test_functions/ishigami.py +++ b/src/uqtestfuns/test_functions/ishigami.py @@ -87,15 +87,15 @@ class Ishigami(UQTestFunABC): """A concrete implementation of the Ishigami function.""" - _TAGS = ["sensitivity"] + _tags = ["sensitivity"] - _AVAILABLE_INPUTS = tuple(AVAILABLE_INPUT_SPECS.keys()) + _available_inputs = tuple(AVAILABLE_INPUT_SPECS.keys()) - _AVAILABLE_PARAMETERS = tuple(AVAILABLE_PARAMETERS.keys()) + _available_parameters = tuple(AVAILABLE_PARAMETERS.keys()) - _DEFAULT_SPATIAL_DIMENSION = 3 + _default_spatial_dimension = 3 - _DESCRIPTION = "Ishigami function from Ishigami and Homma (1991)" + _description = "Ishigami function from Ishigami and Homma (1991)" def __init__( self, diff --git a/src/uqtestfuns/test_functions/mclain.py b/src/uqtestfuns/test_functions/mclain.py index 2fb97dc..f2ea379 100644 --- a/src/uqtestfuns/test_functions/mclain.py +++ b/src/uqtestfuns/test_functions/mclain.py @@ -67,11 +67,11 @@ DEFAULT_INPUT_SELECTION = "McLain1974" COMMON_METADATA = dict( - _TAGS=["metamodeling"], - _AVAILABLE_INPUTS=tuple(AVAILABLE_INPUT_SPECS.keys()), - _AVAILABLE_PARAMETERS=None, - _DEFAULT_SPATIAL_DIMENSION=2, - _DESCRIPTION="from McLain (1974)", + _tags=["metamodeling"], + _available_inputs=tuple(AVAILABLE_INPUT_SPECS.keys()), + _available_parameters=None, + _default_spatial_dimension=2, + _description="from McLain (1974)", ) @@ -102,11 +102,11 @@ class McLainS1(UQTestFunABC): The function features a part of a sphere. """ - _TAGS = COMMON_METADATA["_TAGS"] - _AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"] - _AVAILABLE_PARAMETERS = COMMON_METADATA["_AVAILABLE_PARAMETERS"] - _DEFAULT_SPATIAL_DIMENSION = COMMON_METADATA["_DEFAULT_SPATIAL_DIMENSION"] - _DESCRIPTION = f"McLain S1 function {COMMON_METADATA['_DESCRIPTION']}" + _tags = COMMON_METADATA["_tags"] + _available_inputs = COMMON_METADATA["_available_inputs"] + _available_parameters = COMMON_METADATA["_available_parameters"] + _default_spatial_dimension = COMMON_METADATA["_default_spatial_dimension"] + _description = f"McLain S1 function {COMMON_METADATA['_description']}" __init__ = _init # type: ignore @@ -137,11 +137,11 @@ class McLainS2(UQTestFunABC): The function features a steep hill rising from a plain. """ - _TAGS = COMMON_METADATA["_TAGS"] - _AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"] - _AVAILABLE_PARAMETERS = COMMON_METADATA["_AVAILABLE_PARAMETERS"] - _DEFAULT_SPATIAL_DIMENSION = COMMON_METADATA["_DEFAULT_SPATIAL_DIMENSION"] - _DESCRIPTION = f"McLain S2 function {COMMON_METADATA['_DESCRIPTION']}" + _tags = COMMON_METADATA["_tags"] + _available_inputs = COMMON_METADATA["_available_inputs"] + _available_parameters = COMMON_METADATA["_available_parameters"] + _default_spatial_dimension = COMMON_METADATA["_default_spatial_dimension"] + _description = f"McLain S2 function {COMMON_METADATA['_description']}" __init__ = _init # type: ignore @@ -171,11 +171,11 @@ class McLainS3(UQTestFunABC): The function features a less steep hill (compared to S2). """ - _TAGS = COMMON_METADATA["_TAGS"] - _AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"] - _AVAILABLE_PARAMETERS = COMMON_METADATA["_AVAILABLE_PARAMETERS"] - _DEFAULT_SPATIAL_DIMENSION = COMMON_METADATA["_DEFAULT_SPATIAL_DIMENSION"] - _DESCRIPTION = f"McLain S3 function {COMMON_METADATA['_DESCRIPTION']}" + _tags = COMMON_METADATA["_tags"] + _available_inputs = COMMON_METADATA["_available_inputs"] + _available_parameters = COMMON_METADATA["_available_parameters"] + _default_spatial_dimension = COMMON_METADATA["_default_spatial_dimension"] + _description = f"McLain S3 function {COMMON_METADATA['_description']}" __init__ = _init # type: ignore @@ -205,11 +205,11 @@ class McLainS4(UQTestFunABC): The function features a long narrow hill. """ - _TAGS = COMMON_METADATA["_TAGS"] - _AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"] - _AVAILABLE_PARAMETERS = COMMON_METADATA["_AVAILABLE_PARAMETERS"] - _DEFAULT_SPATIAL_DIMENSION = COMMON_METADATA["_DEFAULT_SPATIAL_DIMENSION"] - _DESCRIPTION = f"McLain S4 function {COMMON_METADATA['_DESCRIPTION']}" + _tags = COMMON_METADATA["_tags"] + _available_inputs = COMMON_METADATA["_available_inputs"] + _available_parameters = COMMON_METADATA["_available_parameters"] + _default_spatial_dimension = COMMON_METADATA["_default_spatial_dimension"] + _description = f"McLain S4 function {COMMON_METADATA['_description']}" __init__ = _init # type: ignore @@ -245,11 +245,11 @@ class McLainS5(UQTestFunABC): The function features two plateaus separated by a steep cliff. """ - _TAGS = COMMON_METADATA["_TAGS"] - _AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"] - _AVAILABLE_PARAMETERS = COMMON_METADATA["_AVAILABLE_PARAMETERS"] - _DEFAULT_SPATIAL_DIMENSION = COMMON_METADATA["_DEFAULT_SPATIAL_DIMENSION"] - _DESCRIPTION = f"McLain S5 function {COMMON_METADATA['_DESCRIPTION']}" + _tags = COMMON_METADATA["_tags"] + _available_inputs = COMMON_METADATA["_available_inputs"] + _available_parameters = COMMON_METADATA["_available_parameters"] + _default_spatial_dimension = COMMON_METADATA["_default_spatial_dimension"] + _description = f"McLain S5 function {COMMON_METADATA['_description']}" __init__ = _init # type: ignore diff --git a/src/uqtestfuns/test_functions/oakley_ohagan_1d.py b/src/uqtestfuns/test_functions/oakley_ohagan_1d.py index cf2f62f..ce63f95 100644 --- a/src/uqtestfuns/test_functions/oakley_ohagan_1d.py +++ b/src/uqtestfuns/test_functions/oakley_ohagan_1d.py @@ -49,15 +49,15 @@ class OakleyOHagan1D(UQTestFunABC): """A concrete implementation of the 1D Oakley-O'Hagan test function.""" - _TAGS = ["metamodeling"] + _tags = ["metamodeling"] - _AVAILABLE_INPUTS = tuple(AVAILABLE_INPUT_SPECS.keys()) + _available_inputs = tuple(AVAILABLE_INPUT_SPECS.keys()) - _AVAILABLE_PARAMETERS = None + _available_parameters = None - _DEFAULT_SPATIAL_DIMENSION = 1 + _default_spatial_dimension = 1 - _DESCRIPTION = "One-dimensional function from Oakley and O'Hagan (2002)" + _description = "One-dimensional function from Oakley and O'Hagan (2002)" def __init__( self, diff --git a/src/uqtestfuns/test_functions/otl_circuit.py b/src/uqtestfuns/test_functions/otl_circuit.py index c4a653c..a4525f0 100644 --- a/src/uqtestfuns/test_functions/otl_circuit.py +++ b/src/uqtestfuns/test_functions/otl_circuit.py @@ -108,15 +108,15 @@ class OTLCircuit(UQTestFunABC): """A concrete implementation of the OTL circuit test function.""" - _TAGS = ["metamodeling", "sensitivity"] + _tags = ["metamodeling", "sensitivity"] - _AVAILABLE_INPUTS = tuple(AVAILABLE_INPUT_SPECS.keys()) + _available_inputs = tuple(AVAILABLE_INPUT_SPECS.keys()) - _AVAILABLE_PARAMETERS = None + _available_parameters = None - _DEFAULT_SPATIAL_DIMENSION = 6 + _default_spatial_dimension = 6 - _DESCRIPTION = ( + _description = ( "Output transformerless (OTL) circuit model " "from Ben-Ari and Steinberg (2007)" ) diff --git a/src/uqtestfuns/test_functions/piston.py b/src/uqtestfuns/test_functions/piston.py index 0617467..e066ec1 100644 --- a/src/uqtestfuns/test_functions/piston.py +++ b/src/uqtestfuns/test_functions/piston.py @@ -116,15 +116,15 @@ class Piston(UQTestFunABC): """A concrete implementation of the Piston simulation test function.""" - _TAGS = ["metamodeling", "sensitivity"] + _tags = ["metamodeling", "sensitivity"] - _AVAILABLE_INPUTS = tuple(AVAILABLE_INPUT_SPECS.keys()) + _available_inputs = tuple(AVAILABLE_INPUT_SPECS.keys()) - _AVAILABLE_PARAMETERS = None + _available_parameters = None - _DEFAULT_SPATIAL_DIMENSION = 7 + _default_spatial_dimension = 7 - _DESCRIPTION = "Piston simulation model from Ben-Ari and Steinberg (2007)" + _description = "Piston simulation model from Ben-Ari and Steinberg (2007)" def __init__( self, diff --git a/src/uqtestfuns/test_functions/sobol_g.py b/src/uqtestfuns/test_functions/sobol_g.py index f3b4754..79a9209 100644 --- a/src/uqtestfuns/test_functions/sobol_g.py +++ b/src/uqtestfuns/test_functions/sobol_g.py @@ -203,15 +203,15 @@ def _get_params_crestaux_2007(spatial_dimension: int) -> np.ndarray: class SobolG(UQTestFunABC): """A concrete implementation of the M-dimensional Sobol'-G function.""" - _TAGS = ["sensitivity", "integration"] + _tags = ["sensitivity", "integration"] - _AVAILABLE_INPUTS = tuple(AVAILABLE_INPUT_SPECS.keys()) + _available_inputs = tuple(AVAILABLE_INPUT_SPECS.keys()) - _AVAILABLE_PARAMETERS = tuple(AVAILABLE_PARAMETERS.keys()) + _available_parameters = tuple(AVAILABLE_PARAMETERS.keys()) - _DEFAULT_SPATIAL_DIMENSION = None + _default_spatial_dimension = None - _DESCRIPTION = "Sobol'-G function from Radović et al. (1996)" + _description = "Sobol'-G function from Radović et al. (1996)" def __init__( self, diff --git a/src/uqtestfuns/test_functions/sulfur.py b/src/uqtestfuns/test_functions/sulfur.py index 5f939d0..2bd88a8 100644 --- a/src/uqtestfuns/test_functions/sulfur.py +++ b/src/uqtestfuns/test_functions/sulfur.py @@ -149,15 +149,15 @@ class Sulfur(UQTestFunABC): """A concrete implementation of the Sulfur model test function.""" - _TAGS = ["metamodeling", "sensitivity"] + _tags = ["metamodeling", "sensitivity"] - _AVAILABLE_INPUTS = tuple(AVAILABLE_INPUT_SPECS.keys()) + _available_inputs = tuple(AVAILABLE_INPUT_SPECS.keys()) - _AVAILABLE_PARAMETERS = None + _available_parameters = None - _DEFAULT_SPATIAL_DIMENSION = 9 + _default_spatial_dimension = 9 - _DESCRIPTION = "Sulfur model from Charlson et al. (1992)" + _description = "Sulfur model from Charlson et al. (1992)" def __init__( self, diff --git a/src/uqtestfuns/test_functions/welch1992.py b/src/uqtestfuns/test_functions/welch1992.py index 182457c..867d0b9 100644 --- a/src/uqtestfuns/test_functions/welch1992.py +++ b/src/uqtestfuns/test_functions/welch1992.py @@ -55,15 +55,15 @@ class Welch1992(UQTestFunABC): """A concrete implementation of the Welch et al. (1992) test function.""" - _TAGS = ["metamodeling", "sensitivity", "integration"] + _tags = ["metamodeling", "sensitivity", "integration"] - _AVAILABLE_INPUTS = tuple(AVAILABLE_INPUT_SPECS.keys()) + _available_inputs = tuple(AVAILABLE_INPUT_SPECS.keys()) - _AVAILABLE_PARAMETERS = None + _available_parameters = None - _DEFAULT_SPATIAL_DIMENSION = 20 + _default_spatial_dimension = 20 - _DESCRIPTION = "20-Dimensional function from Welch et al. (1992)" + _description = "20-Dimensional function from Welch et al. (1992)" def __init__( self, diff --git a/src/uqtestfuns/test_functions/wing_weight.py b/src/uqtestfuns/test_functions/wing_weight.py index c58c8cf..d5f8e20 100644 --- a/src/uqtestfuns/test_functions/wing_weight.py +++ b/src/uqtestfuns/test_functions/wing_weight.py @@ -111,15 +111,15 @@ class WingWeight(UQTestFunABC): """A concrete implementation of the wing weight test function.""" - _TAGS = ["metamodeling", "sensitivity"] + _tags = ["metamodeling", "sensitivity"] - _AVAILABLE_INPUTS = tuple(AVAILABLE_INPUT_SPECS.keys()) + _available_inputs = tuple(AVAILABLE_INPUT_SPECS.keys()) - _AVAILABLE_PARAMETERS = None + _available_parameters = None - _DEFAULT_SPATIAL_DIMENSION = 10 + _default_spatial_dimension = 10 - _DESCRIPTION = "Wing weight model from Forrester et al. (2008)" + _description = "Wing weight model from Forrester et al. (2008)" def __init__( self, diff --git a/tests/builtin_test_functions/test_ishigami.py b/tests/builtin_test_functions/test_ishigami.py index b266a58..7da41d9 100644 --- a/tests/builtin_test_functions/test_ishigami.py +++ b/tests/builtin_test_functions/test_ishigami.py @@ -13,7 +13,7 @@ import uqtestfuns.test_functions.ishigami as ishigami_mod # Test for different parameters to the Ishigami function -available_parameters = Ishigami.AVAILABLE_PARAMETERS +available_parameters = Ishigami.available_parameters @pytest.fixture(params=available_parameters) diff --git a/tests/builtin_test_functions/test_sobol_g.py b/tests/builtin_test_functions/test_sobol_g.py index 82b8503..21a356e 100644 --- a/tests/builtin_test_functions/test_sobol_g.py +++ b/tests/builtin_test_functions/test_sobol_g.py @@ -11,7 +11,7 @@ from uqtestfuns import SobolG -available_parameters = SobolG.AVAILABLE_PARAMETERS +available_parameters = SobolG.available_parameters def test_wrong_param_selection(): diff --git a/tests/builtin_test_functions/test_test_functions.py b/tests/builtin_test_functions/test_test_functions.py index 29133da..b72227b 100644 --- a/tests/builtin_test_functions/test_test_functions.py +++ b/tests/builtin_test_functions/test_test_functions.py @@ -13,8 +13,8 @@ import copy from conftest import assert_call -from uqtestfuns.utils import get_available_classes +from uqtestfuns.utils import get_available_classes from uqtestfuns import test_functions AVAILABLE_FUNCTION_CLASSES = get_available_classes(test_functions) @@ -71,11 +71,11 @@ def test_create_instance_with_prob_input(builtin_testfun): my_prob_input = copy.copy(my_fun.prob_input) # Create an instance without probabilistic input - if testfun_class.AVAILABLE_INPUTS is not None: + if testfun_class.available_inputs is not None: my_fun_2 = testfun_class(prob_input_selection=None) assert my_fun_2.prob_input is None assert my_fun_2.spatial_dimension == ( - testfun_class.DEFAULT_SPATIAL_DIMENSION + testfun_class.default_spatial_dimension ) # Assign the probabilistic input @@ -102,7 +102,7 @@ def test_create_instance_with_parameters(builtin_testfun): my_fun = testfun_class() parameters = my_fun.parameters - if testfun_class.AVAILABLE_PARAMETERS is not None: + if testfun_class.available_parameters is not None: my_fun_2 = testfun_class(parameters_selection=None) assert my_fun_2.parameters is None my_fun_2.parameters = parameters @@ -116,7 +116,7 @@ def test_available_inputs(builtin_testfun): testfun_class = builtin_testfun - available_inputs = testfun_class.AVAILABLE_INPUTS + available_inputs = testfun_class.available_inputs for available_input in available_inputs: assert_call(testfun_class, prob_input_selection=available_input) @@ -127,7 +127,7 @@ def test_available_parameters(builtin_testfun): testfun_class = builtin_testfun - available_parameters = testfun_class.AVAILABLE_PARAMETERS + available_parameters = testfun_class.available_parameters if available_parameters is not None: for available_parameter in available_parameters: @@ -234,7 +234,7 @@ def test_evaluate_wrong_input_domain(builtin_testfun): def test_evaluate_invalid_spatial_dim(builtin_testfun): """Test if an exception is raised if invalid spatial dimension is given.""" - if builtin_testfun.DEFAULT_SPATIAL_DIMENSION is None: + if builtin_testfun.default_spatial_dimension is None: with pytest.raises(TypeError): builtin_testfun(spatial_dimension="10") diff --git a/tests/core/test_uqtestfun.py b/tests/core/test_uqtestfun.py index 52ea75c..1f48f57 100644 --- a/tests/core/test_uqtestfun.py +++ b/tests/core/test_uqtestfun.py @@ -68,7 +68,7 @@ def test_str(uqtestfun): str_ref = ( f"Name : {uqtestfun_instance.name}\n" f"Spatial dimension : {uqtestfun_instance.spatial_dimension}\n" - f"Description : {uqtestfun_instance.DESCRIPTION}" + f"Description : {uqtestfun_instance.description}" ) assert uqtestfun_instance.__str__() == str_ref From 137f7753b648782bc6e84644eb0892664eaf6694 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Tue, 13 Jun 2023 15:16:12 +0200 Subject: [PATCH 17/73] Probabilistic input specification is now stored as NamedTuple - For a lightweight data storage (free of methods), NamedTuple has been adopted to store the specifications of univariate marginals and probabilistic input models. --- src/uqtestfuns/core/prob_input/input_spec.py | 63 +++++++++++ .../core/prob_input/probabilistic_input.py | 33 +++++- .../prob_input/univariate_distribution.py | 23 ++++ src/uqtestfuns/test_functions/ackley.py | 37 ++++--- src/uqtestfuns/test_functions/available.py | 100 +++++++++--------- src/uqtestfuns/test_functions/borehole.py | 59 ++++++----- src/uqtestfuns/test_functions/bratley1992.py | 33 +++--- .../test_functions/damped_oscillator.py | 41 +++---- src/uqtestfuns/test_functions/flood.py | 43 ++++---- src/uqtestfuns/test_functions/franke.py | 31 +++--- src/uqtestfuns/test_functions/ishigami.py | 34 +++--- src/uqtestfuns/test_functions/mclain.py | 31 +++--- .../test_functions/oakley_ohagan_1d.py | 28 ++--- src/uqtestfuns/test_functions/otl_circuit.py | 53 +++++----- src/uqtestfuns/test_functions/piston.py | 55 +++++----- src/uqtestfuns/test_functions/sobol_g.py | 32 +++--- src/uqtestfuns/test_functions/sulfur.py | 45 ++++---- src/uqtestfuns/test_functions/welch1992.py | 29 ++--- src/uqtestfuns/test_functions/wing_weight.py | 49 +++++---- .../test_test_functions.py | 2 +- tests/core/test_input_spec.py | 94 ++++++++++++++++ 21 files changed, 590 insertions(+), 325 deletions(-) create mode 100644 src/uqtestfuns/core/prob_input/input_spec.py create mode 100644 tests/core/test_input_spec.py diff --git a/src/uqtestfuns/core/prob_input/input_spec.py b/src/uqtestfuns/core/prob_input/input_spec.py new file mode 100644 index 0000000..0f7a9fe --- /dev/null +++ b/src/uqtestfuns/core/prob_input/input_spec.py @@ -0,0 +1,63 @@ +""" +Module with definition of data structure to hold information on prob. input. + +The probabilistic input specification should be stored in a built-in Python +data type that contains the only information required to construct +a ProbInput instance. The container type should be free of custom methods. +""" +from typing import Any, Callable, List, NamedTuple, Optional, Tuple, Union + + +__all__ = ["MarginalSpec", "ProbInputSpec", "ProbInputSpecVarDim"] + + +class MarginalSpec(NamedTuple): + """A univariate marginal distribution specification. + + Parameters + ---------- + name : str + The name of the univariate marginal. + distribution : str + The name of the distribution of the univariate marginal. + parameters : Union[List[Union[int, float]], Tuple[Union[int, float], ...]] + Parameter values of the distribution. + description : str + Short description of the univariate marginal. + """ + + name: str + distribution: str + parameters: Union[List[Union[int, float]], Tuple[Union[int, float], ...]] + description: str + + +class ProbInputSpec(NamedTuple): + """All the information required for constructing a ProbInput instance. + + Parameters + ---------- + name : str + The name of the probabilistic input model. + description : str + A short description of the probabilistic input model. + marginals : Union[Callable, List[MarginalSpec]] + A list of univariate marginal specifications or a callable + to construct the list of marginal specifications. + copulas : Optional[Any] + The copula specification of the probabilistic input model. + """ + + name: str + description: str + marginals: List[MarginalSpec] + copulas: Optional[Any] + + +class ProbInputSpecVarDim(NamedTuple): + """All the information to construct a ProbInput w/ variable dimension.""" + + name: str + description: str + marginals_generator: Callable[[int], List[MarginalSpec]] + copulas: Optional[Any] diff --git a/src/uqtestfuns/core/prob_input/probabilistic_input.py b/src/uqtestfuns/core/prob_input/probabilistic_input.py index e6f2cf6..b60bff4 100644 --- a/src/uqtestfuns/core/prob_input/probabilistic_input.py +++ b/src/uqtestfuns/core/prob_input/probabilistic_input.py @@ -15,7 +15,7 @@ from typing import Any, List, Optional, Union, Tuple from .univariate_distribution import UnivDist, FIELD_NAMES - +from .input_spec import ProbInputSpec, ProbInputSpecVarDim __all__ = ["ProbInput"] @@ -183,6 +183,37 @@ def _repr_html_(self): return table + @classmethod + def from_spec( + cls, + prob_input_spec: Union[ProbInputSpec, ProbInputSpecVarDim], + *, + spatial_dimension: Optional[int] = None, + rng_seed: Optional[int] = None, + ): + """Create an instance from a ProbInputSpec instance.""" + + if isinstance(prob_input_spec, ProbInputSpecVarDim): + if spatial_dimension is None: + raise ValueError("Spatial dimension must be specified!") + marginals_gen = prob_input_spec.marginals_generator + marginals_spec = marginals_gen(spatial_dimension) + else: + marginals_spec = prob_input_spec.marginals + + # Create a list of UnivDist instances representing univariate marginals + marginals = [ + UnivDist.from_spec(marginal) for marginal in marginals_spec + ] + + return cls( + marginals=marginals, + copulas=prob_input_spec.copulas, + name=prob_input_spec.name, + description=prob_input_spec.description, + rng_seed=rng_seed, + ) + def _get_values_as_list( univ_inputs: Union[List[UnivDist], Tuple[UnivDist, ...]], diff --git a/src/uqtestfuns/core/prob_input/univariate_distribution.py b/src/uqtestfuns/core/prob_input/univariate_distribution.py index 7f0f36b..168f397 100644 --- a/src/uqtestfuns/core/prob_input/univariate_distribution.py +++ b/src/uqtestfuns/core/prob_input/univariate_distribution.py @@ -21,6 +21,7 @@ get_cdf_values, get_icdf_values, ) +from .input_spec import MarginalSpec from ...global_settings import ARRAY_FLOAT __all__ = ["UnivDist"] @@ -157,3 +158,25 @@ def icdf(self, xx: Union[float, np.ndarray]) -> ARRAY_FLOAT: return get_icdf_values( xx, self.distribution, self.parameters, self.lower, self.upper ) + + @classmethod + def from_spec( + cls, marginal_spec: MarginalSpec, rng_seed: Optional[int] = None + ): + """Create an instance of UnivDist from a marginal specification. + + Parameters + ---------- + marginal_spec : MarginalSpec + The specification for the univariate marginal. + rng_seed : int, optional + The seed used to initialize the pseudo-random number generator. + If not specified, the value is taken from the system entropy. + """ + return cls( + distribution=marginal_spec.distribution, + parameters=marginal_spec.parameters, + name=marginal_spec.name, + description=marginal_spec.description, + rng_seed=rng_seed, + ) diff --git a/src/uqtestfuns/test_functions/ackley.py b/src/uqtestfuns/test_functions/ackley.py index 05a4cf8..acdb61d 100644 --- a/src/uqtestfuns/test_functions/ackley.py +++ b/src/uqtestfuns/test_functions/ackley.py @@ -24,16 +24,16 @@ from typing import List, Optional from ..core.uqtestfun_abc import UQTestFunABC -from ..core.prob_input.univariate_distribution import UnivDist +from ..core.prob_input.input_spec import MarginalSpec, ProbInputSpecVarDim from .available import ( - create_prob_input_from_available, + create_prob_input_from_spec, create_parameters_from_available, ) __all__ = ["Ackley"] -def _ackley_input(spatial_dimension: int) -> List[UnivDist]: +def _ackley_input(spatial_dimension: int) -> List[MarginalSpec]: """Create a list of marginals for the M-dimensional Ackley function. Parameters @@ -43,13 +43,13 @@ def _ackley_input(spatial_dimension: int) -> List[UnivDist]: Returns ------- - List[UnivDist] + List[MarginalSpec] A list of marginals for the multivariate input following Ref. [1] """ marginals = [] for i in range(spatial_dimension): marginals.append( - UnivDist( + MarginalSpec( name=f"X{i + 1}", distribution="uniform", parameters=[-32.768, 32.768], @@ -61,16 +61,17 @@ def _ackley_input(spatial_dimension: int) -> List[UnivDist]: AVAILABLE_INPUT_SPECS = { - "Ackley1987": { - "name": "Ackley-Ackley-1987", - "description": ( + "Ackley1987": ProbInputSpecVarDim( + name="Ackley-Ackley-1987", + description=( "Search domain for the Ackley function from Ackley (1987)." ), - "marginals": _ackley_input, - "copulas": None, - }, + marginals_generator=_ackley_input, + copulas=None, + ), } + DEFAULT_INPUT_SELECTION = "Ackley1987" AVAILABLE_PARAMETERS = {"Ackley1987": np.array([20, 0.2, 2 * np.pi])} @@ -130,13 +131,17 @@ def __init__( f"Spatial dimension is expected to be of 'int'. " f"Got {type(spatial_dimension)} instead." ) + + # Get the ProbInputSpec from available + if prob_input_selection is None: + prob_input_spec = None + else: + prob_input_spec = AVAILABLE_INPUT_SPECS[prob_input_selection] + # Ackley is an M-dimensional test function, either given / use default # Create the input according to spatial dimension - prob_input = create_prob_input_from_available( - prob_input_selection, - AVAILABLE_INPUT_SPECS, - spatial_dimension, - rng_seed_prob_input, + prob_input = create_prob_input_from_spec( + prob_input_spec, spatial_dimension, rng_seed_prob_input ) # Create the parameters according to spatial dimension parameters = create_parameters_from_available( diff --git a/src/uqtestfuns/test_functions/available.py b/src/uqtestfuns/test_functions/available.py index 0192997..7133351 100644 --- a/src/uqtestfuns/test_functions/available.py +++ b/src/uqtestfuns/test_functions/available.py @@ -2,57 +2,9 @@ Helpers module to construct probabilistic input and parameters. """ from types import FunctionType -from typing import Any, Optional +from typing import Any, Dict, Optional, Union from ..core import ProbInput - - -def create_prob_input_from_available( - input_selection: Optional[str], - available_input_specs: dict, - spatial_dimension: Optional[int] = None, - rng_seed: Optional[int] = None, -) -> Optional[ProbInput]: - """Construct a Multivariate input given available specifications. - - Parameters - ---------- - input_selection : str - Which available input specifications to construct. - available_input_specs : dict - Dictionary of available probabilistic input specifications. - spatial_dimension : int, optional - The requested number of spatial dimensions, when applicable. - Some specifications are functions of spatial dimension. - rng_seed : int, optional - The seed for the pseudo-random number generator; if not given then - the number is taken from the system entropy. - - Raises - ------ - ValueError - If the input selection keyword not in the list of available - specifications. - """ - if input_selection is None: - return None - - if input_selection in available_input_specs: - input_specs = available_input_specs[input_selection] - if isinstance(input_specs["marginals"], FunctionType): - marginals = input_specs["marginals"](spatial_dimension) - prob_input = ProbInput( - name=input_specs["name"], - description=input_specs["description"], - marginals=marginals, - copulas=input_specs["copulas"], - rng_seed=rng_seed, - ) - else: - prob_input = ProbInput(**input_specs, rng_seed=rng_seed) - else: - raise ValueError("Invalid selection!") - - return prob_input +from ..core.prob_input.input_spec import ProbInputSpec, ProbInputSpecVarDim def create_parameters_from_available( @@ -84,3 +36,51 @@ def create_parameters_from_available( raise ValueError("Invalid parameters selection!") return parameters + + +def get_prob_input_spec( + prob_input_selection: Optional[str], + available_input_specs: Dict[str, ProbInputSpec], +) -> Optional[ProbInputSpec]: + """Get ProbInputSpec from the available specifications. + + Parameters + ---------- + """ + if prob_input_selection is None: + return None + + if prob_input_selection in available_input_specs: + prob_input_spec = available_input_specs[prob_input_selection] + else: + raise KeyError("Invalid ProbInput selection!") + + return prob_input_spec + + +def create_prob_input_from_spec( + prob_input_spec: Optional[Union[ProbInputSpec, ProbInputSpecVarDim]], + spatial_dimension: Optional[int] = None, + rng_seed: Optional[int] = None, +) -> Optional[ProbInput]: + """Construct a Multivariate input given available specifications. + + Parameters + ---------- + prob_input_spec : Union[ProbInputSpec, ProbInputSpecVarDim], optional + The specification of a probabilistic input model. + spatial_dimension : int, optional + The requested number of spatial dimensions, when applicable. + Some specifications are functions of spatial dimension. + rng_seed : int, optional + The seed for the pseudo-random number generator; if not given then + the number is taken from the system entropy. + """ + if prob_input_spec is None: + return None + + prob_input = ProbInput.from_spec( + prob_input_spec, spatial_dimension=spatial_dimension, rng_seed=rng_seed + ) + + return prob_input diff --git a/src/uqtestfuns/test_functions/borehole.py b/src/uqtestfuns/test_functions/borehole.py index 44b83ce..7021c71 100644 --- a/src/uqtestfuns/test_functions/borehole.py +++ b/src/uqtestfuns/test_functions/borehole.py @@ -25,57 +25,57 @@ from typing import Optional -from ..core.prob_input.univariate_distribution import UnivDist from ..core.uqtestfun_abc import UQTestFunABC -from .available import create_prob_input_from_available +from ..core.prob_input.input_spec import MarginalSpec, ProbInputSpec +from .available import get_prob_input_spec, create_prob_input_from_spec __all__ = ["Borehole"] # From Ref. [1] INPUT_MARGINALS_HARPER1983 = [ - UnivDist( + MarginalSpec( name="rw", distribution="normal", parameters=[0.10, 0.0161812], description="radius of the borehole [m]", ), - UnivDist( + MarginalSpec( name="r", distribution="lognormal", parameters=[7.71, 1.0056], description="radius of influence [m]", ), - UnivDist( + MarginalSpec( name="Tu", distribution="uniform", parameters=[63070.0, 115600.0], description="transmissivity of upper aquifer [m^2/year]", ), - UnivDist( + MarginalSpec( name="Hu", distribution="uniform", parameters=[990.0, 1100.0], description="potentiometric head of upper aquifer [m]", ), - UnivDist( + MarginalSpec( name="Tl", distribution="uniform", parameters=[63.1, 116.0], description="transmissivity of lower aquifer [m^2/year]", ), - UnivDist( + MarginalSpec( name="Hl", distribution="uniform", parameters=[700.0, 820.0], description="potentiometric head of lower aquifer [m]", ), - UnivDist( + MarginalSpec( name="L", distribution="uniform", parameters=[1120.0, 1680.0], description="length of the borehole [m]", ), - UnivDist( + MarginalSpec( name="Kw", distribution="uniform", parameters=[9985.0, 12045.0], @@ -86,13 +86,13 @@ # From Ref. [2] INPUT_MARGINALS_MORRIS1993 = list(INPUT_MARGINALS_HARPER1983) INPUT_MARGINALS_MORRIS1993[0:2] = [ - UnivDist( + MarginalSpec( name="rw", distribution="uniform", parameters=[0.05, 0.15], description="radius of the borehole [m]", ), - UnivDist( + MarginalSpec( name="r", distribution="uniform", parameters=[100, 50000], @@ -101,24 +101,24 @@ ] AVAILABLE_INPUT_SPECS = { - "Harper1983": { - "name": "Borehole-Harper-1983", - "description": ( + "Harper1983": ProbInputSpec( + name="Borehole-Harper-1983", + description=( "Probabilistic input model of the Borehole model " "from Harper and Gupta (1983)." ), - "marginals": INPUT_MARGINALS_HARPER1983, - "copulas": None, - }, - "Morris1993": { - "name": "Borehole-Morris-1993", - "description": ( + marginals=INPUT_MARGINALS_HARPER1983, + copulas=None, + ), + "Morris1993": ProbInputSpec( + name="Borehole-Morris-1993", + description=( "Probabilistic input model of the Borehole model " "from Morris et al. (1993)." ), - "marginals": INPUT_MARGINALS_MORRIS1993, - "copulas": None, - }, + marginals=INPUT_MARGINALS_MORRIS1993, + copulas=None, + ), } DEFAULT_INPUT_SELECTION = "Harper1983" @@ -145,10 +145,13 @@ def __init__( rng_seed_prob_input: Optional[int] = None, ): # --- Arguments processing - prob_input = create_prob_input_from_available( - prob_input_selection, - AVAILABLE_INPUT_SPECS, - rng_seed=rng_seed_prob_input, + # Get the ProbInputSpec from available + prob_input_spec = get_prob_input_spec( + prob_input_selection, AVAILABLE_INPUT_SPECS + ) + # Create a ProbInput + prob_input = create_prob_input_from_spec( + prob_input_spec, rng_seed=rng_seed_prob_input ) # Process the default name if name is None: diff --git a/src/uqtestfuns/test_functions/bratley1992.py b/src/uqtestfuns/test_functions/bratley1992.py index cab9301..c96f5ff 100644 --- a/src/uqtestfuns/test_functions/bratley1992.py +++ b/src/uqtestfuns/test_functions/bratley1992.py @@ -35,13 +35,13 @@ from typing import List, Optional from ..core.uqtestfun_abc import UQTestFunABC -from ..core.prob_input.univariate_distribution import UnivDist -from .available import create_prob_input_from_available +from ..core.prob_input.input_spec import MarginalSpec, ProbInputSpecVarDim +from .available import create_prob_input_from_spec __all__ = ["Bratley1992d"] -def _bratley_input(spatial_dimension: int) -> List[UnivDist]: +def _bratley_input(spatial_dimension: int) -> List[MarginalSpec]: """Create a list of marginals for the M-dimensional Bratley test functions. Parameters @@ -51,13 +51,13 @@ def _bratley_input(spatial_dimension: int) -> List[UnivDist]: Returns ------- - List[UnivDist] + List[MarginalSpec] A list of marginals for the multivariate input following Ref. [1] """ marginals = [] for i in range(spatial_dimension): marginals.append( - UnivDist( + MarginalSpec( name=f"X{i + 1}", distribution="uniform", parameters=[0.0, 1.0], @@ -69,14 +69,14 @@ def _bratley_input(spatial_dimension: int) -> List[UnivDist]: AVAILABLE_INPUT_SPECS = { - "Bratley1992": { - "name": "Bratley1992", - "description": ( + "Bratley1992": ProbInputSpecVarDim( + name="Bratley1992", + description=( "Integration domain of the functions from Bratley et al. (1992)" ), - "marginals": _bratley_input, - "copulas": None, - }, + marginals_generator=_bratley_input, + copulas=None, + ), } DEFAULT_INPUT_SELECTION = "Bratley1992" @@ -104,6 +104,12 @@ def _init( ) -> None: """A common __init__ for all Bratley1992 test functions.""" # --- Arguments processing + # Get the ProbInputSpec from available + if prob_input_selection is None: + prob_input_spec = None + else: + prob_input_spec = AVAILABLE_INPUT_SPECS[prob_input_selection] + if not isinstance(spatial_dimension, int): raise TypeError( f"Spatial dimension is expected to be of 'int'. " @@ -111,9 +117,8 @@ def _init( ) # A Bratley1992 test function is an M-dimensional test function # Create the input according to spatial dimension - prob_input = create_prob_input_from_available( - prob_input_selection, - AVAILABLE_INPUT_SPECS, + prob_input = create_prob_input_from_spec( + prob_input_spec, spatial_dimension, rng_seed_prob_input, ) diff --git a/src/uqtestfuns/test_functions/damped_oscillator.py b/src/uqtestfuns/test_functions/damped_oscillator.py index f674407..4426090 100644 --- a/src/uqtestfuns/test_functions/damped_oscillator.py +++ b/src/uqtestfuns/test_functions/damped_oscillator.py @@ -34,15 +34,15 @@ from typing import Optional -from ..core.prob_input.univariate_distribution import UnivDist +from ..core.prob_input.input_spec import MarginalSpec, ProbInputSpec from ..core.uqtestfun_abc import UQTestFunABC from .utils import lognorm2norm_mean, lognorm2norm_std -from .available import create_prob_input_from_available +from .available import get_prob_input_spec, create_prob_input_from_spec __all__ = ["DampedOscillator"] INPUT_MARGINALS_DERKIUREGHIAN1991 = [ # From [2] - UnivDist( + MarginalSpec( name="Mp", distribution="lognormal", parameters=[ @@ -51,7 +51,7 @@ ], description="Primary mass", ), - UnivDist( + MarginalSpec( name="Ms", distribution="lognormal", parameters=[ @@ -60,7 +60,7 @@ ], description="Secondary mass", ), - UnivDist( + MarginalSpec( name="Kp", distribution="lognormal", parameters=[ @@ -69,7 +69,7 @@ ], description="Primary spring stiffness", ), - UnivDist( + MarginalSpec( name="Ks", distribution="lognormal", parameters=[ @@ -78,7 +78,7 @@ ], description="Secondary spring stiffness", ), - UnivDist( + MarginalSpec( name="Zeta_p", distribution="lognormal", parameters=[ @@ -87,7 +87,7 @@ ], description="Primary damping ratio", ), - UnivDist( + MarginalSpec( name="Zeta_s", distribution="lognormal", parameters=[ @@ -96,7 +96,7 @@ ], description="Secondary damping ratio", ), - UnivDist( + MarginalSpec( name="S0", distribution="lognormal", parameters=[ @@ -108,15 +108,15 @@ ] AVAILABLE_INPUT_SPECS = { - "DerKiureghian1991": { - "name": "Damped-Oscillator-Der-Kiureghian-1991", - "description": ( + "DerKiureghian1991": ProbInputSpec( + name="Damped-Oscillator-Der-Kiureghian-1991", + description=( "Probabilistic input model for the Damped Oscillator model " "from Der Kiureghian and De Stefano (1991)." ), - "marginals": INPUT_MARGINALS_DERKIUREGHIAN1991, - "copulas": None, - }, + marginals=INPUT_MARGINALS_DERKIUREGHIAN1991, + copulas=None, + ), } DEFAULT_INPUT_SELECTION = "DerKiureghian1991" @@ -145,10 +145,13 @@ def __init__( rng_seed_prob_input: Optional[int] = None, ): # --- Arguments processing - prob_input = create_prob_input_from_available( - prob_input_selection, - AVAILABLE_INPUT_SPECS, - rng_seed=rng_seed_prob_input, + # Get the ProbInputSpec from available + prob_input_spec = get_prob_input_spec( + prob_input_selection, AVAILABLE_INPUT_SPECS + ) + # Create a ProbInput + prob_input = create_prob_input_from_spec( + prob_input_spec, rng_seed=rng_seed_prob_input ) # Process the default name if name is None: diff --git a/src/uqtestfuns/test_functions/flood.py b/src/uqtestfuns/test_functions/flood.py index 0737e8d..7a7506e 100644 --- a/src/uqtestfuns/test_functions/flood.py +++ b/src/uqtestfuns/test_functions/flood.py @@ -34,56 +34,56 @@ from typing import Optional -from ..core.prob_input.univariate_distribution import UnivDist +from ..core.prob_input.input_spec import MarginalSpec, ProbInputSpec from ..core.uqtestfun_abc import UQTestFunABC -from .available import create_prob_input_from_available +from .available import get_prob_input_spec, create_prob_input_from_spec __all__ = ["Flood"] INPUT_MARGINALS_IOOSS2015 = [ # From Ref. [1] - UnivDist( + MarginalSpec( name="Q", distribution="trunc-gumbel", parameters=[1013.0, 558.0, 500.0, 3000.0], description="Maximum annual flow rate [m^3/s]", ), - UnivDist( + MarginalSpec( name="Ks", distribution="trunc-normal", parameters=[30.0, 8.0, 15.0, np.inf], description="Strickler coefficient [m^(1/3)/s]", ), - UnivDist( + MarginalSpec( name="Zv", distribution="triangular", parameters=[49.0, 51.0, 50.0], description="River downstream level [m]", ), - UnivDist( + MarginalSpec( name="Zm", distribution="triangular", parameters=[54.0, 56.0, 55.0], description="River upstream level [m]", ), - UnivDist( + MarginalSpec( name="Hd", distribution="uniform", parameters=[7.0, 9.0], description="Dyke height [m]", ), - UnivDist( + MarginalSpec( name="Cb", distribution="triangular", parameters=[55.0, 56.0, 55.5], description="Bank level [m]", ), - UnivDist( + MarginalSpec( name="L", distribution="triangular", parameters=[4990.0, 5010.0, 5000.0], description="Length of the river stretch [m]", ), - UnivDist( + MarginalSpec( name="B", distribution="triangular", parameters=[295.0, 305.0, 300.0], @@ -92,15 +92,15 @@ ] AVAILABLE_INPUT_SPECS = { - "Iooss2015": { - "name": "Flood-Iooss-2015", - "description": ( + "Iooss2015": ProbInputSpec( + name="Flood-Iooss-2015", + description=( "Probabilistic input model for the Flood model " "from Iooss and Lemaître (2015)." ), - "marginals": INPUT_MARGINALS_IOOSS2015, - "copulas": None, - } + marginals=INPUT_MARGINALS_IOOSS2015, + copulas=None, + ), } DEFAULT_INPUT_SELECTION = "Iooss2015" @@ -127,10 +127,13 @@ def __init__( rng_seed_prob_input: Optional[int] = None, ): # --- Arguments processing - prob_input = create_prob_input_from_available( - prob_input_selection, - AVAILABLE_INPUT_SPECS, - rng_seed=rng_seed_prob_input, + # Get the ProbInputSpec from available + prob_input_spec = get_prob_input_spec( + prob_input_selection, AVAILABLE_INPUT_SPECS + ) + # Create a ProbInput + prob_input = create_prob_input_from_spec( + prob_input_spec, rng_seed=rng_seed_prob_input ) # Process the default name if name is None: diff --git a/src/uqtestfuns/test_functions/franke.py b/src/uqtestfuns/test_functions/franke.py index 0956ef5..b638bb6 100644 --- a/src/uqtestfuns/test_functions/franke.py +++ b/src/uqtestfuns/test_functions/franke.py @@ -43,20 +43,20 @@ from typing import Optional -from ..core.prob_input.univariate_distribution import UnivDist +from ..core.prob_input.input_spec import MarginalSpec, ProbInputSpec from ..core.uqtestfun_abc import UQTestFunABC -from .available import create_prob_input_from_available +from .available import get_prob_input_spec, create_prob_input_from_spec __all__ = ["Franke1", "Franke2", "Franke3", "Franke4", "Franke5", "Franke6"] INPUT_MARGINALS_FRANKE1979 = [ # From Ref. [1] - UnivDist( + MarginalSpec( name="X1", distribution="uniform", parameters=[0.0, 1.0], description="None", ), - UnivDist( + MarginalSpec( name="X2", distribution="uniform", parameters=[0.0, 1.0], @@ -65,15 +65,15 @@ ] AVAILABLE_INPUT_SPECS = { - "Franke1979": { - "name": "Franke-1979", - "description": ( + "Franke1979": ProbInputSpec( + name="Franke-1979", + description=( "Input specification for the Franke's test functions " "from Franke (1979)." ), - "marginals": INPUT_MARGINALS_FRANKE1979, - "copulas": None, - } + marginals=INPUT_MARGINALS_FRANKE1979, + copulas=None, + ), } DEFAULT_INPUT_SELECTION = "Franke1979" @@ -96,10 +96,13 @@ def _init( ) -> None: """A common __init__ for all Franke's test functions.""" # --- Arguments processing - prob_input = create_prob_input_from_available( - prob_input_selection, - AVAILABLE_INPUT_SPECS, - rng_seed=rng_seed_prob_input, + # Get the ProbInputSpec from available + prob_input_spec = get_prob_input_spec( + prob_input_selection, AVAILABLE_INPUT_SPECS + ) + # Create a ProbInput + prob_input = create_prob_input_from_spec( + prob_input_spec, rng_seed=rng_seed_prob_input ) # Process the default name if name is None: diff --git a/src/uqtestfuns/test_functions/ishigami.py b/src/uqtestfuns/test_functions/ishigami.py index 114ee63..77680d8 100644 --- a/src/uqtestfuns/test_functions/ishigami.py +++ b/src/uqtestfuns/test_functions/ishigami.py @@ -31,10 +31,11 @@ from typing import Optional -from ..core.prob_input.univariate_distribution import UnivDist +from ..core.prob_input.input_spec import MarginalSpec, ProbInputSpec from ..core.uqtestfun_abc import UQTestFunABC from .available import ( - create_prob_input_from_available, + get_prob_input_spec, + create_prob_input_from_spec, create_parameters_from_available, ) @@ -42,19 +43,19 @@ INPUT_MARGINALS_ISHIGAMI1991 = [ - UnivDist( + MarginalSpec( name="X1", distribution="uniform", parameters=[-np.pi, np.pi], description="None", ), - UnivDist( + MarginalSpec( name="X2", distribution="uniform", parameters=[-np.pi, np.pi], description="None", ), - UnivDist( + MarginalSpec( name="X3", distribution="uniform", parameters=[-np.pi, np.pi], @@ -63,15 +64,15 @@ ] AVAILABLE_INPUT_SPECS = { - "Ishigami1991": { - "name": "Ishigami-1991", - "description": ( + "Ishigami1991": ProbInputSpec( + name="Ishigami-1991", + description=( "Probabilistic input model for the Ishigami function " "from Ishigami and Homma (1991)." ), - "marginals": INPUT_MARGINALS_ISHIGAMI1991, - "copulas": None, - } + marginals=INPUT_MARGINALS_ISHIGAMI1991, + copulas=None, + ), } DEFAULT_INPUT_SELECTION = "Ishigami1991" @@ -106,10 +107,13 @@ def __init__( rng_seed_prob_input: Optional[int] = None, ): # --- Arguments processing - prob_input = create_prob_input_from_available( - prob_input_selection, - AVAILABLE_INPUT_SPECS, - rng_seed=rng_seed_prob_input, + # Get the ProbInputSpec from available + prob_input_spec = get_prob_input_spec( + prob_input_selection, AVAILABLE_INPUT_SPECS + ) + # Create a ProbInput + prob_input = create_prob_input_from_spec( + prob_input_spec, rng_seed=rng_seed_prob_input ) # Ishigami supports several different parameterizations parameters = create_parameters_from_available( diff --git a/src/uqtestfuns/test_functions/mclain.py b/src/uqtestfuns/test_functions/mclain.py index f2ea379..aa1b389 100644 --- a/src/uqtestfuns/test_functions/mclain.py +++ b/src/uqtestfuns/test_functions/mclain.py @@ -31,20 +31,20 @@ from typing import Optional -from ..core.prob_input.univariate_distribution import UnivDist +from ..core.prob_input.input_spec import MarginalSpec, ProbInputSpec from ..core.uqtestfun_abc import UQTestFunABC -from .available import create_prob_input_from_available +from .available import get_prob_input_spec, create_prob_input_from_spec __all__ = ["McLainS1", "McLainS2", "McLainS3", "McLainS4", "McLainS5"] INPUT_MARGINALS_MCLAIN1974 = [ # From Ref. [1] - UnivDist( + MarginalSpec( name="X1", distribution="uniform", parameters=[1.0, 10.0], description="None", ), - UnivDist( + MarginalSpec( name="X2", distribution="uniform", parameters=[1.0, 10.0], @@ -53,15 +53,15 @@ ] AVAILABLE_INPUT_SPECS = { - "McLain1974": { - "name": "McLain-1974", - "description": ( + "McLain1974": ProbInputSpec( + name="McLain-1974", + description=( "Input specification for the McLain's test functions " "from McLain (1974)." ), - "marginals": INPUT_MARGINALS_MCLAIN1974, - "copulas": None, - } + marginals=INPUT_MARGINALS_MCLAIN1974, + copulas=None, + ), } DEFAULT_INPUT_SELECTION = "McLain1974" @@ -84,10 +84,13 @@ def _init( ) -> None: """A common __init__ for all McLain's test functions.""" # --- Arguments processing - prob_input = create_prob_input_from_available( - prob_input_selection, - AVAILABLE_INPUT_SPECS, - rng_seed=rng_seed_prob_input, + # Get the ProbInputSpec from available + prob_input_spec = get_prob_input_spec( + prob_input_selection, AVAILABLE_INPUT_SPECS + ) + # Create a ProbInput + prob_input = create_prob_input_from_spec( + prob_input_spec, rng_seed=rng_seed_prob_input ) # Process the default name if name is None: diff --git a/src/uqtestfuns/test_functions/oakley_ohagan_1d.py b/src/uqtestfuns/test_functions/oakley_ohagan_1d.py index ce63f95..dcc4a40 100644 --- a/src/uqtestfuns/test_functions/oakley_ohagan_1d.py +++ b/src/uqtestfuns/test_functions/oakley_ohagan_1d.py @@ -17,14 +17,14 @@ from typing import Optional -from ..core.prob_input.univariate_distribution import UnivDist +from ..core.prob_input.input_spec import MarginalSpec, ProbInputSpec from ..core.uqtestfun_abc import UQTestFunABC -from .available import create_prob_input_from_available +from .available import get_prob_input_spec, create_prob_input_from_spec __all__ = ["OakleyOHagan1D"] INPUT_MARGINALS_OAKLEY2002 = [ - UnivDist( + MarginalSpec( name="x", distribution="normal", parameters=[0.0, 4.0], @@ -33,14 +33,15 @@ ] AVAILABLE_INPUT_SPECS = { - "Oakley2002": { - "name": "Oakley-OHagan-2002", - "description": ( + "Oakley2002": ProbInputSpec( + name="Oakley-OHagan-2002", + description=( "Probabilistic input model for the one-dimensional function " "from Oakley-O'Hagan function (2002)" ), - "marginals": INPUT_MARGINALS_OAKLEY2002, - } + marginals=INPUT_MARGINALS_OAKLEY2002, + copulas=None, + ), } DEFAULT_INPUT_SELECTION = "Oakley2002" @@ -67,10 +68,13 @@ def __init__( rng_seed_prob_input: Optional[int] = None, ): # --- Arguments processing - prob_input = create_prob_input_from_available( - prob_input_selection, - AVAILABLE_INPUT_SPECS, - rng_seed=rng_seed_prob_input, + # Get the ProbInputSpec from available + prob_input_spec = get_prob_input_spec( + prob_input_selection, AVAILABLE_INPUT_SPECS + ) + # Create a ProbInput + prob_input = create_prob_input_from_spec( + prob_input_spec, rng_seed=rng_seed_prob_input ) # Process the default name if name is None: diff --git a/src/uqtestfuns/test_functions/otl_circuit.py b/src/uqtestfuns/test_functions/otl_circuit.py index a4525f0..5cfeb0a 100644 --- a/src/uqtestfuns/test_functions/otl_circuit.py +++ b/src/uqtestfuns/test_functions/otl_circuit.py @@ -25,44 +25,44 @@ from copy import copy from typing import Optional -from ..core.prob_input.univariate_distribution import UnivDist +from ..core.prob_input.input_spec import MarginalSpec, ProbInputSpec from ..core.uqtestfun_abc import UQTestFunABC -from .available import create_prob_input_from_available +from .available import get_prob_input_spec, create_prob_input_from_spec __all__ = ["OTLCircuit"] INPUT_MARGINALS_BENARI2007 = [ - UnivDist( + MarginalSpec( name="Rb1", distribution="uniform", parameters=[50.0, 150.0], description="Resistance b1 [kOhm]", ), - UnivDist( + MarginalSpec( name="Rb2", distribution="uniform", parameters=[25.0, 70.0], description="Resistance b2 [kOhm]", ), - UnivDist( + MarginalSpec( name="Rf", distribution="uniform", parameters=[0.5, 3.0], description="Resistance f [kOhm]", ), - UnivDist( + MarginalSpec( name="Rc1", distribution="uniform", parameters=[1.2, 2.5], description="Resistance c1 [kOhm]", ), - UnivDist( + MarginalSpec( name="Rc2", distribution="uniform", parameters=[0.25, 1.20], description="Resistance c2 [kOhm]", ), - UnivDist( + MarginalSpec( name="beta", distribution="uniform", parameters=[50.0, 300.0], @@ -73,7 +73,7 @@ INPUT_MARGINALS_MOON2010 = [copy(_) for _ in INPUT_MARGINALS_BENARI2007] for i in range(14): INPUT_MARGINALS_MOON2010.append( - UnivDist( + MarginalSpec( name=f"Inert {i+1}", distribution="uniform", parameters=[100.0, 200.0], @@ -82,24 +82,24 @@ ) AVAILABLE_INPUT_SPECS = { - "BenAri2007": { - "name": "OTL-Circuit-Ben-Ari-2007", - "description": ( + "BenAri2007": ProbInputSpec( + name="OTL-Circuit-Ben-Ari-2007", + description=( "Probabilistic input model for the OTL Circuit function " "from Ben-Ari and Steinberg (2007)." ), - "marginals": INPUT_MARGINALS_BENARI2007, - "copulas": None, - }, - "Moon2010": { - "name": "OTL-Circuit-Moon-2010", - "description": ( + marginals=INPUT_MARGINALS_BENARI2007, + copulas=None, + ), + "Moon2010": ProbInputSpec( + name="OTL-Circuit-Moon-2010", + description=( "Probabilistic input model for the OTL Circuit function " "from Moon (2010)." ), - "marginals": INPUT_MARGINALS_MOON2010, - "copulas": None, - }, + marginals=INPUT_MARGINALS_MOON2010, + copulas=None, + ), } DEFAULT_INPUT_SELECTION = "BenAri2007" @@ -129,10 +129,13 @@ def __init__( rng_seed_prob_input: Optional[int] = None, ): # --- Arguments processing - prob_input = create_prob_input_from_available( - prob_input_selection, - AVAILABLE_INPUT_SPECS, - rng_seed=rng_seed_prob_input, + # Get the ProbInputSpec from available + prob_input_spec = get_prob_input_spec( + prob_input_selection, AVAILABLE_INPUT_SPECS + ) + # Create a ProbInput + prob_input = create_prob_input_from_spec( + prob_input_spec, rng_seed=rng_seed_prob_input ) # Process the default name if name is None: diff --git a/src/uqtestfuns/test_functions/piston.py b/src/uqtestfuns/test_functions/piston.py index e066ec1..d1de8e3 100644 --- a/src/uqtestfuns/test_functions/piston.py +++ b/src/uqtestfuns/test_functions/piston.py @@ -25,51 +25,51 @@ from copy import copy from typing import Optional -from ..core.prob_input.univariate_distribution import UnivDist +from ..core.prob_input.input_spec import MarginalSpec, ProbInputSpec from ..core.uqtestfun_abc import UQTestFunABC -from .available import create_prob_input_from_available +from .available import get_prob_input_spec, create_prob_input_from_spec __all__ = ["Piston"] # Marginals specification from [1] INPUT_MARGINALS_BENARI2007 = [ - UnivDist( + MarginalSpec( name="M", distribution="uniform", parameters=[30.0, 60.0], description="Piston weight [kg]", ), - UnivDist( + MarginalSpec( name="S", distribution="uniform", parameters=[0.005, 0.020], description="Piston surface area [m^2]", ), - UnivDist( + MarginalSpec( name="V0", distribution="uniform", parameters=[0.002, 0.010], description="Initial gas volume [m^3]", ), - UnivDist( + MarginalSpec( name="k", distribution="uniform", parameters=[1000.0, 5000.0], description="Spring coefficient [N/m]", ), - UnivDist( + MarginalSpec( name="P0", distribution="uniform", parameters=[90000.0, 110000.0], description="Atmospheric pressure [N/m^2]", ), - UnivDist( + MarginalSpec( name="Ta", distribution="uniform", parameters=[290.0, 296.0], description="Ambient temperature [K]", ), - UnivDist( + MarginalSpec( name="T0", distribution="uniform", parameters=[340.0, 360.0], @@ -81,7 +81,7 @@ INPUT_MARGINALS_MOON2010 = [copy(_) for _ in INPUT_MARGINALS_BENARI2007] for i in range(13): INPUT_MARGINALS_MOON2010.append( - UnivDist( + MarginalSpec( name=f"Inert {i+1}", distribution="uniform", parameters=[100.0, 200.0], @@ -90,24 +90,24 @@ ) AVAILABLE_INPUT_SPECS = { - "BenAri2007": { - "name": "Piston-Ben-Ari-2007", - "description": ( + "BenAri2007": ProbInputSpec( + name="Piston-Ben-Ari-2007", + description=( "Probabilistic input model for the Piston simulation model " "from Ben-Ari and Steinberg (2007)." ), - "marginals": INPUT_MARGINALS_BENARI2007, - "copulas": None, - }, - "Moon2010": { - "name": "Piston-Moon-2010", - "description": ( + marginals=INPUT_MARGINALS_BENARI2007, + copulas=None, + ), + "Moon2010": ProbInputSpec( + name="Piston-Moon-2010", + description=( "Probabilistic input model for the Piston simulation model " "from Moon (2010)." ), - "marginals": INPUT_MARGINALS_MOON2010, - "copulas": None, - }, + marginals=INPUT_MARGINALS_MOON2010, + copulas=None, + ), } DEFAULT_INPUT_SELECTION = "BenAri2007" @@ -134,10 +134,13 @@ def __init__( rng_seed_prob_input: Optional[int] = None, ): # --- Arguments processing - prob_input = create_prob_input_from_available( - prob_input_selection, - AVAILABLE_INPUT_SPECS, - rng_seed=rng_seed_prob_input, + # Get the ProbInputSpec from available + prob_input_spec = get_prob_input_spec( + prob_input_selection, AVAILABLE_INPUT_SPECS + ) + # Create a ProbInput + prob_input = create_prob_input_from_spec( + prob_input_spec, rng_seed=rng_seed_prob_input ) # Process the default name if name is None: diff --git a/src/uqtestfuns/test_functions/sobol_g.py b/src/uqtestfuns/test_functions/sobol_g.py index 79a9209..09f8af5 100644 --- a/src/uqtestfuns/test_functions/sobol_g.py +++ b/src/uqtestfuns/test_functions/sobol_g.py @@ -49,17 +49,17 @@ from typing import List, Optional -from ..core.prob_input.univariate_distribution import UnivDist +from ..core.prob_input.input_spec import MarginalSpec, ProbInputSpecVarDim from ..core.uqtestfun_abc import UQTestFunABC from .available import ( - create_prob_input_from_available, + create_prob_input_from_spec, create_parameters_from_available, ) __all__ = ["SobolG"] -def _create_sobol_input(spatial_dimension: int) -> List[UnivDist]: +def _create_sobol_input(spatial_dimension: int) -> List[MarginalSpec]: """Construct an input instance for a given dimension according to [1]. Parameters @@ -69,14 +69,14 @@ def _create_sobol_input(spatial_dimension: int) -> List[UnivDist]: Returns ------- - List[UnivDist] + List[MarginalSpec] A list of M marginals as UnivariateInput instances to construct the MultivariateInput. """ marginals = [] for i in range(spatial_dimension): marginals.append( - UnivDist( + MarginalSpec( name=f"X{i + 1}", distribution="uniform", parameters=[0.0, 1.0], @@ -88,15 +88,15 @@ def _create_sobol_input(spatial_dimension: int) -> List[UnivDist]: AVAILABLE_INPUT_SPECS = { - "Radovic1996": { - "name": "Sobol-G-Radovic-1996", - "description": ( + "Radovic1996": ProbInputSpecVarDim( + name="Sobol-G-Radovic-1996", + description=( "Probabilistic input model for the Sobol'-G function " "from Radović et al. (1996)." ), - "marginals": _create_sobol_input, - "copulas": None, - }, + marginals_generator=_create_sobol_input, + copulas=None, + ), } DEFAULT_INPUT_SELECTION = "Radovic1996" @@ -223,6 +223,11 @@ def __init__( rng_seed_prob_input: Optional[int] = None, ): # --- Arguments processing + # Get the ProbInputSpec from available + if prob_input_selection is None: + prob_input_spec = None + else: + prob_input_spec = AVAILABLE_INPUT_SPECS[prob_input_selection] if not isinstance(spatial_dimension, int): raise TypeError( f"Spatial dimension is expected to be of 'int'. " @@ -230,9 +235,8 @@ def __init__( ) # Sobol-G is an M-dimensional test function, either given / use default # Create the input according to spatial dimension - prob_input = create_prob_input_from_available( - prob_input_selection, - AVAILABLE_INPUT_SPECS, + prob_input = create_prob_input_from_spec( + prob_input_spec, spatial_dimension, rng_seed_prob_input, ) diff --git a/src/uqtestfuns/test_functions/sulfur.py b/src/uqtestfuns/test_functions/sulfur.py index 2bd88a8..0a87d97 100644 --- a/src/uqtestfuns/test_functions/sulfur.py +++ b/src/uqtestfuns/test_functions/sulfur.py @@ -62,64 +62,64 @@ from typing import Optional -from ..core.prob_input.univariate_distribution import UnivDist +from ..core.prob_input.input_spec import MarginalSpec, ProbInputSpec from ..core.uqtestfun_abc import UQTestFunABC -from .available import create_prob_input_from_available +from .available import get_prob_input_spec, create_prob_input_from_spec __all__ = ["Sulfur"] INPUT_MARGINALS_PENNER1994 = [ # From [3] (Table 2) - UnivDist( + MarginalSpec( name="Q", distribution="lognormal", parameters=[np.log(71.0), np.log(1.15)], description="Source strength of anthropogenic Sulfur [10^12 g/year]", ), - UnivDist( + MarginalSpec( name="Y", distribution="lognormal", parameters=[np.log(0.5), np.log(1.5)], description="Fraction of SO2 oxidized to SO4(2-) aerosol [-]", ), - UnivDist( + MarginalSpec( name="L", distribution="lognormal", parameters=[np.log(5.5), np.log(1.5)], description="Average lifetime of atmospheric SO4(2-) [days]", ), - UnivDist( + MarginalSpec( name="Psi_e", distribution="lognormal", parameters=[np.log(5.0), np.log(1.4)], description="Aerosol mass scattering efficiency [m^2/g]", ), - UnivDist( + MarginalSpec( name="beta", distribution="lognormal", parameters=[np.log(0.3), np.log(1.3)], description="Fraction of light scattered upward hemisphere [-]", ), - UnivDist( + MarginalSpec( name="f_Psi_e", distribution="lognormal", parameters=[np.log(1.7), np.log(1.2)], description="Fractional increase in aerosol scattering efficiency " "due to hygroscopic growth [-]", ), - UnivDist( + MarginalSpec( name="T^2", distribution="lognormal", parameters=[np.log(0.58), np.log(1.4)], description="Square of atmospheric " "transmittance above aerosol layer [-]", ), - UnivDist( + MarginalSpec( name="(1-Ac)", distribution="lognormal", parameters=[np.log(0.39), np.log(1.1)], description="Fraction of earth not covered by cloud [-]", ), - UnivDist( + MarginalSpec( name="(1-Rs)^2", distribution="lognormal", parameters=[np.log(0.72), np.log(1.2)], @@ -128,15 +128,15 @@ ] AVAILABLE_INPUT_SPECS = { - "Penner1994": { - "name": "Sulfur-Penner-1994", - "description": ( + "Penner1994": ProbInputSpec( + name="Sulfur-Penner-1994", + description=( "Probabilistic input model for the Sulfur model " "from Penner et al. (1994)." ), - "marginals": INPUT_MARGINALS_PENNER1994, - "copulas": None, - } + marginals=INPUT_MARGINALS_PENNER1994, + copulas=None, + ), } DEFAULT_INPUT_SELECTION = "Penner1994" @@ -167,10 +167,13 @@ def __init__( rng_seed_prob_input: Optional[int] = None, ): # --- Arguments processing - prob_input = create_prob_input_from_available( - prob_input_selection, - AVAILABLE_INPUT_SPECS, - rng_seed=rng_seed_prob_input, + # Get the ProbInputSpec from available + prob_input_spec = get_prob_input_spec( + prob_input_selection, AVAILABLE_INPUT_SPECS + ) + # Create a ProbInput + prob_input = create_prob_input_from_spec( + prob_input_spec, rng_seed=rng_seed_prob_input ) # Process the default name if name is None: diff --git a/src/uqtestfuns/test_functions/welch1992.py b/src/uqtestfuns/test_functions/welch1992.py index 867d0b9..3c3520a 100644 --- a/src/uqtestfuns/test_functions/welch1992.py +++ b/src/uqtestfuns/test_functions/welch1992.py @@ -22,14 +22,15 @@ from typing import Optional -from ..core.prob_input.univariate_distribution import UnivDist +from ..core.prob_input.input_spec import MarginalSpec, ProbInputSpec from ..core.uqtestfun_abc import UQTestFunABC -from .available import create_prob_input_from_available +from .available import get_prob_input_spec, create_prob_input_from_spec __all__ = ["Welch1992"] + INPUT_MARGINALS_WELCH1992 = [ - UnivDist( + MarginalSpec( name=f"x{i}", distribution="uniform", parameters=[-0.5, 0.5], @@ -39,14 +40,15 @@ ] AVAILABLE_INPUT_SPECS = { - "Welch1992": { - "name": "Welch1992", - "description": ( + "Welch1992": ProbInputSpec( + name="Welch1992", + description=( "Input specification for the test function " "from Welch et al. (1992)" ), - "marginals": INPUT_MARGINALS_WELCH1992, - } + marginals=INPUT_MARGINALS_WELCH1992, + copulas=None, + ), } DEFAULT_INPUT_SELECTION = "Welch1992" @@ -73,10 +75,13 @@ def __init__( rng_seed_prob_input: Optional[int] = None, ): # --- Arguments processing - prob_input = create_prob_input_from_available( - prob_input_selection, - AVAILABLE_INPUT_SPECS, - rng_seed=rng_seed_prob_input, + # Get the ProbInputSpec from available + prob_input_spec = get_prob_input_spec( + prob_input_selection, AVAILABLE_INPUT_SPECS + ) + # Create a ProbInput + prob_input = create_prob_input_from_spec( + prob_input_spec, rng_seed=rng_seed_prob_input ) # Process the default name if name is None: diff --git a/src/uqtestfuns/test_functions/wing_weight.py b/src/uqtestfuns/test_functions/wing_weight.py index d5f8e20..f81486c 100644 --- a/src/uqtestfuns/test_functions/wing_weight.py +++ b/src/uqtestfuns/test_functions/wing_weight.py @@ -23,69 +23,69 @@ from typing import Optional -from ..core.prob_input.univariate_distribution import UnivDist +from ..core.prob_input.input_spec import MarginalSpec, ProbInputSpec from ..core.uqtestfun_abc import UQTestFunABC -from .available import create_prob_input_from_available +from .available import get_prob_input_spec, create_prob_input_from_spec from .utils import deg2rad __all__ = ["WingWeight"] INPUT_MARGINALS_FORRESTER2008 = [ - UnivDist( + MarginalSpec( name="Sw", distribution="uniform", parameters=[150.0, 200.0], description="wing area [ft^2]", ), - UnivDist( + MarginalSpec( name="Wfw", distribution="uniform", parameters=[220.0, 300.0], description="weight of fuel in the wing [lb]", ), - UnivDist( + MarginalSpec( name="A", distribution="uniform", parameters=[6.0, 10.0], description="aspect ratio [-]", ), - UnivDist( + MarginalSpec( name="Lambda", distribution="uniform", parameters=[-10.0, 10.0], description="quarter-chord sweep [degrees]", ), - UnivDist( + MarginalSpec( name="q", distribution="uniform", parameters=[16.0, 45.0], description="dynamic pressure at cruise [lb/ft^2]", ), - UnivDist( + MarginalSpec( name="lambda", distribution="uniform", parameters=[0.5, 1.0], description="taper ratio [-]", ), - UnivDist( + MarginalSpec( name="tc", distribution="uniform", parameters=[0.08, 0.18], description="aerofoil thickness to chord ratio [-]", ), - UnivDist( + MarginalSpec( name="Nz", distribution="uniform", parameters=[2.5, 6.0], description="ultimate load factor [-]", ), - UnivDist( + MarginalSpec( name="Wdg", distribution="uniform", parameters=[1700, 2500], description="flight design gross weight [lb]", ), - UnivDist( + MarginalSpec( name="Wp", distribution="uniform", parameters=[0.025, 0.08], @@ -94,15 +94,15 @@ ] AVAILABLE_INPUT_SPECS = { - "Forrester2008": { - "name": "Wing-Weight-Forrester-2008", - "description": ( + "Forrester2008": ProbInputSpec( + name="Wing-Weight-Forrester-2008", + description=( "Probabilistic input model for the Wing Weight model " "from Forrester et al. (2008)." ), - "marginals": INPUT_MARGINALS_FORRESTER2008, - "copulas": None, - } + marginals=INPUT_MARGINALS_FORRESTER2008, + copulas=None, + ), } DEFAULT_INPUT_SELECTION = "Forrester2008" @@ -129,14 +129,17 @@ def __init__( rng_seed_prob_input: Optional[int] = None, ): # --- Arguments processing - prob_input = create_prob_input_from_available( - prob_input_selection, - AVAILABLE_INPUT_SPECS, - rng_seed=rng_seed_prob_input, + # Get the ProbInputSpec from available + prob_input_spec = get_prob_input_spec( + prob_input_selection, AVAILABLE_INPUT_SPECS + ) + # Create a ProbInput + prob_input = create_prob_input_from_spec( + prob_input_spec, rng_seed=rng_seed_prob_input ) # Process the default name if name is None: - name = WingWeight.__name__ + name = self.__class__.__name__ super().__init__(prob_input=prob_input, name=name) diff --git a/tests/builtin_test_functions/test_test_functions.py b/tests/builtin_test_functions/test_test_functions.py index b72227b..5a18058 100644 --- a/tests/builtin_test_functions/test_test_functions.py +++ b/tests/builtin_test_functions/test_test_functions.py @@ -241,5 +241,5 @@ def test_evaluate_invalid_spatial_dim(builtin_testfun): def test_evaluate_invalid_input_selection(builtin_testfun): """Test if an exception is raised if invalid input selection is given.""" - with pytest.raises(ValueError): + with pytest.raises(KeyError): builtin_testfun(prob_input_selection=100) diff --git a/tests/core/test_input_spec.py b/tests/core/test_input_spec.py new file mode 100644 index 0000000..6b064ed --- /dev/null +++ b/tests/core/test_input_spec.py @@ -0,0 +1,94 @@ +""" +Test module for the prob. input specification class. +""" +from typing import List + +from uqtestfuns.core.prob_input.input_spec import ( + MarginalSpec, + ProbInputSpec, + ProbInputSpecVarDim, +) + + +def test_marginalspec(): + """Test the creation of MarginalSpec NamedTuple.""" + + name = "T0" + distribution = "uniform" + parameters = [340.0, 360.0] + description = "Filling gas temperature" + + # Create a MarginalSpec + my_marginalspec = MarginalSpec( + name=name, + distribution=distribution, + parameters=parameters, + description=description, + ) + + # Assertions + assert my_marginalspec.name == name + assert my_marginalspec.distribution == distribution + assert my_marginalspec.parameters == parameters + assert my_marginalspec.description == description + + +def test_probinputspec_list(): + """Test the creation of ProbInputSpec NamedTuple w/ list of marginals.""" + + # Create a list of marginals + marginals = _create_marginals(10) + + # Create a ProbInputSpec + name = "Some input" + description = "Probabilistic input model from somewhere" + copulas = None + my_probinputspec = ProbInputSpec( + name=name, + description=description, + marginals=marginals, + copulas=copulas, + ) + + # Assertions + assert my_probinputspec.name == name + assert my_probinputspec.description == description + assert my_probinputspec.copulas == copulas + assert my_probinputspec.marginals == marginals + + +def test_probinputspec_vardim(): + """Test the creation of ProbInputSpec w/ a callable as marginal.""" + + # Create a list of marginals + marginals_gen = _create_marginals + + # Create a ProbInputSpec + name = "Some input" + description = "Probabilistic input model from somewhere" + copulas = None + my_probinputspec = ProbInputSpecVarDim( + name=name, + description=description, + marginals_generator=marginals_gen, + copulas=copulas, + ) + + # Assertions + assert my_probinputspec.name == name + assert my_probinputspec.description == description + assert my_probinputspec.copulas == copulas + assert my_probinputspec.marginals_generator == marginals_gen + + +def _create_marginals(spatial_dimension: int) -> List[MarginalSpec]: + """Create a list of test marginals.""" + return [ + MarginalSpec( + name=f"x{i + 1}", + distribution="uniform", + parameters=[0.0, 1.0], + description="None", + ) + for i in range(spatial_dimension) + ] From 5cec764dd0b8566773e91bdf4eb2c0c74a308b28 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Mon, 19 Jun 2023 10:27:49 +0200 Subject: [PATCH 18/73] Refactor the abstract base class - Two abstract base classes are now available: `UQTestFunBareABC` and `UQTestFunABC`. The former is used as a bare abstract class that requires only evaluation function and probabilistic input model, while the latter requires a couple of additional metadata (e.g., tags and description) to store test functions available in the literature. - All currently available UQ test functions have been refactored to use the new abstract base class. - The documentation has been updated accordingly. --- CHANGELOG.md | 5 + docs/_toc.yml | 2 + docs/api/input-spec.rst | 27 ++ docs/api/overview.md | 10 +- docs/api/uqtestfun-abc.rst | 23 +- .../adding-test-function-implementation.md | 285 ++++++------- docs/test-functions/ackley.md | 13 +- docs/test-functions/bratley1992d.md | 16 +- docs/test-functions/sobol-g.md | 15 +- src/uqtestfuns/__init__.py | 5 +- src/uqtestfuns/core/__init__.py | 10 +- src/uqtestfuns/core/prob_input/input_spec.py | 33 +- .../core/prob_input/probabilistic_input.py | 4 +- .../prob_input/univariate_distribution.py | 6 +- src/uqtestfuns/core/uqtestfun.py | 37 +- src/uqtestfuns/core/uqtestfun_abc.py | 376 +++++++++++++----- src/uqtestfuns/helpers.py | 19 +- src/uqtestfuns/test_functions/ackley.py | 143 ++----- src/uqtestfuns/test_functions/available.py | 86 ---- src/uqtestfuns/test_functions/borehole.py | 121 +++--- src/uqtestfuns/test_functions/bratley1992.py | 124 ++---- .../test_functions/damped_oscillator.py | 102 ++--- src/uqtestfuns/test_functions/flood.py | 126 +++--- src/uqtestfuns/test_functions/franke.py | 314 +++++++-------- src/uqtestfuns/test_functions/ishigami.py | 113 ++---- src/uqtestfuns/test_functions/mclain.py | 227 +++++------ .../test_functions/oakley_ohagan_1d.py | 100 ++--- src/uqtestfuns/test_functions/otl_circuit.py | 142 +++---- src/uqtestfuns/test_functions/piston.py | 157 +++----- src/uqtestfuns/test_functions/sobol_g.py | 103 ++--- src/uqtestfuns/test_functions/sulfur.py | 156 +++----- src/uqtestfuns/test_functions/welch1992.py | 124 +++--- src/uqtestfuns/test_functions/wing_weight.py | 116 ++---- tests/builtin_test_functions/test_ishigami.py | 4 +- tests/builtin_test_functions/test_sobol_g.py | 4 +- .../test_test_functions.py | 11 +- .../prob_input/test_multivariate_input.py | 77 ++++ tests/core/test_input_spec.py | 12 +- tests/core/test_uqtestfun.py | 13 +- 39 files changed, 1454 insertions(+), 1807 deletions(-) create mode 100644 docs/api/input-spec.rst delete mode 100644 src/uqtestfuns/test_functions/available.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 6173e41..fed25f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - An implementation of M-dimensional test functions (#4) from Bratley et al. (1992) useful for testing multi-dimensional numerical integrations as well as global sensitivity analysis. +- Two base classes are now available `UQTestFunBareABC` and `UQTestFunABC`. + The former is used to implement a _bare_ UQ test function + (with only `evaluate()` and `ProbInput`), while the latter is used to + implement _published_ UQ test functions in the code base (i.e., with + additional metadata such as tags and description). ## [0.1.1] - 2023-07-03 diff --git a/docs/_toc.yml b/docs/_toc.yml index 4f3b4da..c289cb2 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -116,6 +116,8 @@ parts: title: ProbInput - file: api/univdist title: UnivDist + - file: api/input-spec + title: ProbInputSpec - caption: Development chapters: - file: development/overview diff --git a/docs/api/input-spec.rst b/docs/api/input-spec.rst new file mode 100644 index 0000000..bca5e92 --- /dev/null +++ b/docs/api/input-spec.rst @@ -0,0 +1,27 @@ +.. _api_reference_input_spec: + +Input Specification +=================== + +.. automodule:: uqtestfuns.core.prob_input.input_spec + +.. _api_reference_input_spec_univdist: + +``UnivDistSpec`` +---------------- + +.. autoclass:: uqtestfuns.core.prob_input.input_spec.UnivDistSpec + +.. _api_reference_input_spec_fixdim: + +``ProbInputSpecFixDim`` +----------------------- + +.. autoclass:: uqtestfuns.core.prob_input.input_spec.ProbInputSpecFixDim + +.. _api_reference_input_spec_vardim: + +``ProbInputSpecVarDim`` +----------------------- + +.. autoclass:: uqtestfuns.core.prob_input.input_spec.ProbInputSpecVarDim diff --git a/docs/api/overview.md b/docs/api/overview.md index df6d3d6..0e81ffe 100644 --- a/docs/api/overview.md +++ b/docs/api/overview.md @@ -8,8 +8,10 @@ To make sense of how the objects in UQTestFuns are organized, let's start from the top, the {ref}`built-in test functions `: - Each of the built-in UQ test functions is a concrete implementation of the - {ref}`api_reference_uqtestfun_abc`. Therefore, all the instances share the - same underlying interfaces regardless of their particular classes. + abstract base class {ref}`UQTestFunABC `. + The base class, in turns, is derived + from {ref}`UQTestFunABC `). + Therefore, all the instances share the same underlying interfaces. In particular, all instances share, among other things, the ``evaluate()`` method, the ``prob_input`` property, and the ``parameters`` property [^essence]. - The ``prob_input`` property stores the underlying probabilistic input model @@ -25,6 +27,10 @@ let's start from the top, the {ref}`built-in test functions ` ``NamedTuple`` + are defined, namely {ref}`api_reference_input_spec_univdist`, + {ref}`api_reference_input_spec_fixdim`, and {ref}`api_reference_input_spec_vardim`. ```{note} To facilitate the creation of a custom UQ test function diff --git a/docs/api/uqtestfun-abc.rst b/docs/api/uqtestfun-abc.rst index 83ce0af..9bcd5f6 100644 --- a/docs/api/uqtestfun-abc.rst +++ b/docs/api/uqtestfun-abc.rst @@ -1,11 +1,24 @@ -.. _api_reference_uqtestfun_abc: +.. _api_reference_uqtestfun_module_abc: -``UQTestFunABC`` Abstract Base Class -==================================== +Abstract Base Classes +===================== .. automodule:: uqtestfuns.core.uqtestfun_abc -.. autoclass:: uqtestfuns.core.uqtestfun_abc.UQTestFunABC + +.. _api_reference_uqtestfun_bare_abc: + +``UQTestFunBareABC`` Abstract Base Class +---------------------------------------- + +.. autoclass:: uqtestfuns.core.uqtestfun_abc.UQTestFunBareABC :members: - :special-members: __init__ + +.. _api_reference_uqtestfun_abc: + +``UQTestFunABC`` Abstract Base Class +------------------------------------ + +.. autoclass:: uqtestfuns.core.uqtestfun_abc.UQTestFunABC + :members: diff --git a/docs/development/adding-test-function-implementation.md b/docs/development/adding-test-function-implementation.md index 32af6f1..240724a 100644 --- a/docs/development/adding-test-function-implementation.md +++ b/docs/development/adding-test-function-implementation.md @@ -14,7 +14,7 @@ as explained {ref}`here `. ``` Similar to creating a new test function on runtime, -we are going to use the Branin function as the motivating problem. +we are going to use the Branin function {cite}`Dixon1978` as the motivating problem. The function is defined as follows: $$ @@ -55,7 +55,6 @@ If you have a look at the directory you'll see the following (or something simil ├── test_functions <- Sub-package that contains all UQ test function modules │ ├── __init__.py │ ├── ackley.py <- An implementation of the Ackley function -│ ├── available.py <- Utility functions of the sub-package │ ├── borehole.py <- An implementation of the borehole function │ ├── damped_oscillator.py <- An implementation of the damped oscillator model │ ├── ... @@ -109,14 +108,8 @@ Here are the few things we usually use: ```python import numpy as np -from typing import Optional - from ..core.uqtestfun_abc import UQTestFunABC -from ..core.prob_input.univariate_distribution import UnivDist -from .available import ( - create_prob_input_from_available, - create_parameters_from_available, -) +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecFixDim __all__ = ["Branin"] ``` @@ -124,106 +117,98 @@ __all__ = ["Branin"] Here are some explanations: - NumPy is usually a must, especially for implementing the evaluation function. -- All test functions are concrete implementations of the abstract base class `UQTestFunABC`. -- One-dimensional marginals are defined using the `UnivDist` class. -- `create_prob_input_from_available` and `create_parameters_from_available` are internal functions used to allow users to select a particular probabilistic input - and/or parameters specifications (from several available selections) using a keyword passed in the class constructor. - -### Implementing a concrete class +- All built-in test functions are concrete implementations of the abstract base + class `UQTestFunABC`. +- The specification for a probabilistic input model is stored in `UnivDistSpec` + (the one-dimensional marginal specification) and `ProbInputSpecFixDim` + (the probabilistic input model). These are lightweight _containers_ + (meaning no custom methods) of all the information required to construct, + later on, `UnivDist` and `ProbInput` instances, respectively. -Each built-in test function is an implementation of the abstract base class `UQTestFunABC`. -`UQTestFunABC` prescribes an abstract method called `evaluate` -and a few class-level properties that must be implemented in the concrete class. -More about them is below. - -But first, we create a new class called `Branin` derived from this abstract base class: - -```python -class Branin(UQTestFunABC): - ... +```{note} +There is also the corresponding `ProbInputSpecVarDim` to store the information +required to construct probabilistic input model with variable dimension. ``` -Afterward, we need to define the constructor of the class. -The rules regarding the signature of the constructor are not written in stone (yet, or ever). -But keep in mind that you should be able to call the constructor without any obligatory parameters. +### Implementing a concrete evaluation function -Following the precedence of how signatures for the other test functions are written, we write the constructor along with its body: +For an implementation of a test function, create a top module-level function +(conventionally named `evaluate()` if there is only one test function in the +module): ```python -class Branin(UQTestFunABC): - """A concrete implementation of the 2-dimensional Branin test function. +def evaluate(xx: np.ndarray, parameters: Any) -> np.ndarray: + """Evaluate the Branin function on a set of input values. Parameters ---------- - spatial_dimension : int - The requested number of spatial_dimension. If not specified, - the default is set to 2. - prob_input_selection : str, optional - The selection of a probabilistic input model from a list of - available specifications. This is a keyword-only parameter. - parameters_selection : str, optional - The selection of a parameters set from a list of available - parameter sets. This is a keyword-only parameter. + xx : np.ndarray + 2-Dimensional input values given by an N-by-2 array where + N is the number of input values. + parameters : Any + The parameters of the test function (six numbers) + + Returns + ------- + np.ndarray + The output of the Branin function evaluated on the input values. + The output is a 1-dimensional array of length N. """ - - def __init__( - self, - *, - prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, - parameters_selection: Optional[str] = DEFAULT_PARAMETERS_SELECTION, - name: Optional[str] = None, - ): - # --- Arguments processing - # Create a probabilistic input model based on the selection - prob_input = create_prob_input_from_available( - prob_input_selection, AVAILABLE_INPUT_SPECS - ) - # Select parameters - parameters = create_parameters_from_available( - parameters_selection, AVAILABLE_PARAMETERS + params = parameters + yy = ( + params[0] + * ( + xx[:, 1] + - params[1] * xx[:, 0] ** 2 + + params[2] * xx[:, 0] + - params[3] ) - # Process the default name - if name is None: - name = Branin.__name__ + ** 2 + + params[4] * (1 - params[5]) * np.cos(xx[:, 0]) + + params[4] + ) - super().__init__( - prob_input=prob_input, parameters=parameters, name=name - ) + return yy ``` -Here we expose the following (optional) parameters in the constructor: - -- `prob_input_selection`: so a keyword selecting a particular probabilistic input specification can be passed -- `parameters_selection`: so a keyword selecting a particular set of parameters values can be passed -- `name`: so a custom name of an instance can be passed - -Notice that these parameters are all optional and must be given as keyword arguments (it's good to be explicit). - -There are a couple of missing things in the constructor definition above: - -- `DEFAULT_INPUT_SELECTION` and `AVAILABLE_INPUT_SPECS` -- `DEFAULT_PARAMETERS_SELECTION` and `AVAILABLE_PARAMETERS` +Notice that for a test function with parameters, the signature should also +include the parameters. ### Specifying probabilistic input model -`AVAILABLE_INPUT_SPECS` is a module-level variable that stores a dictionary containing -all the available input specifications available in UQTestFuns for the Branin function. +The specification of the probabilistic model is stored in a module-level +dictionary. While you're free to name the dictionary anything you like, +the convention is `AVAILABLE_INPUT_SPECS`. We define the variable as follows: ```python AVAILABLE_INPUT_SPECS = { - "Dixon1978": { - "name": "Branin-Dixon-2008", - "description": ( + "Dixon1978": ProbInputSpecFixDim( + name="Branin-Dixon-2008", + description=( "Search domain for the Branin function from Dixon and Szegö (1978)." ), - "marginals": INPUT_MARGINALS_DIXON1978, - "copulas": None, - } + marginals=[ + UnivDistSpec( + name="x1", + distribution="uniform", + parameters=[-5, 10], + description="None", + ), + UnivDistSpec( + name="x2", + distribution="uniform", + parameters=[0.0, 15.0], + description="None", + ) + ], + copulas=None, + ), } ``` -Each element of this dictionary contains the arguments to create an input model as an instance of the `ProbInput` class. +Each key-value pair of this dictionary contains a complete specification +to create an input model as an instance of the `ProbInput` class. For this particular example, we only have one input specification available. If there were more, we would add them here in the dictionary each with a unique keyword. @@ -231,46 +216,17 @@ If there were more, we would add them here in the dictionary each with a unique The keyword is, by convention, the citation key of the particular reference as listed in the BibTeX file. ``` -In the code above, we store the specification of the marginal inside another variable. -The marginals specification is a list of instances of `UnivDist` describing each of the input variables. - -```python -INPUT_MARGINALS_DIXON1978 = [ - UnivDist( - name="x1", - distribution="uniform", - parameters=[-5, 10], - description="None", - ), - UnivDist( - name="x2", - distribution="uniform", - parameters=[0.0, 15.0], - description="None", - ), -] -``` - -Finally, we need to tell UQTestFuns which input specification needs to be used by default. -We put the keyword selecting the default specification inside the variable `DEFAULT_INPUT_SELECTION`: - -```python -DEFAULT_INPUT_SELECTION = "Dixon1978" -``` +Also notice that in the code above, we store the specifications of the marginals +in a list of {ref}`api_reference_input_spec_univdist`. +Each element of this list is used to create an instance of `UnivDist`. With that, we have completed the input specification of the Branin function. ### Specifying parameters -Similar to the previous step, `DEFAULT_PARAMETERS_SELECTION` is a module-level variable that stores a string keyword referring -to the default set of parameter values for the Branin function. - -```python -DEFAULT_PARAMETERS_SELECTION = "Dixon1978" -``` - -As before, the keyword refers to a dictionary that contains all the available parameter values. -For this example, we need to define the dictionary inside a module-level variable called `AVAILABLE_PARAMETERS`: +For a parameterized test function, we also need to define a module-level +dictionary that stores the parameter sets. +Conventionally, we name this variable `AVAILABLE_PARAMETERS`: ```python AVAILABLE_PARAMETERS = { @@ -280,66 +236,49 @@ AVAILABLE_PARAMETERS = { } ``` -### Implementing a concrete evaluation method - -For an implementation of a test function, the abstract method `evaluate()` must be implemented. +The value of the parameters can be of any type, as long as it is consistent +with how the parameters are going to be consumed by the `evaluate()` function. -```python - ... - def evaluate(self, xx: np.ndarray): - """Evaluate the Branin function on a set of input values. - - Parameters - ---------- - xx : np.ndarray - 2-Dimensional input values given by an N-by-2 array where - N is the number of input values. - - Returns - ------- - np.ndarray - The output of the Branin func. evaluated on the input values. - The output is a 1-dimensional array of length N. - """ - params = self.parameters - yy = ( - params[0] - * ( - xx[:, 1] - - params[1] * xx[:, 0] ** 2 - + params[2] * xx[:, 0] - - params[3] - ) - ** 2 - + params[4] * (1 - params[5]) * np.cos(xx[:, 0]) - + params[4] - ) +As before, if there are multiple parameter sets available in the literature, +additional key-value pair should be added here. - return yy -``` +### Implementing a concrete class -Notice that the stored function parameters are accessible as a property of the instance and can be accessed via `self`; -the parameter should not be directly passed in the calling of the method. +Each built-in test function is an implementation of the abstract base class +`UQTestFunABC`. +A concrete implementation of this base class requires the following: -### Adding concrete class properties +- a static method named `eval_()` +- several class-level properties, namely: `_tags`, `_description`, + `_available_inputs`, `_available_parameters`, `_default_spatial_dimension`, + `_default_input`, and `_default_parameters`. -Several class properties must be defined for a concrete implementation. -They are useful for the organization and bookkeeping of the test functions. +The full definition of the class for the Branin test function is shown below. ```python -class Branin(UQTestFunABC): - ... +class Branin(UQTestFunFixDimABC): + """A concrete implementation of the Branin test function.""" + _tags = ["optimization"] # Application tags + _description = "Branin function from Dixon and Szegö (1978)" # Short description + _available_inputs = AVAILABLE_INPUT_SPECS # As defined above + _available_parameters = AVAILABLE_PARAMETERS # As defined above + _default_spatial_dimension = 2 # Spatial dimension of the function + _default_input = "Dixon1978" # Optional, if only one input is available + _default_parameters = "Dixon1978" # Optional, if only one set of parameters is available - _available_inputs = tuple(AVAILABLE_INPUT_SPECS.keys()) # Input selection keywords + eval_ = staticmethod(evaluate) # assuming `evaluate()` has been defined +``` - _available_parameters = tuple(AVAILABLE_PARAMETERS.keys()) # Parameters selection keywords +There is no need to define an `__init__()` method. +We will use the default `__init__()` from the base class. - _default_spatial_dimension = 2 # Spatial dimension of the function - _description = "Branin function from Dixon and Szegö (1978)" # Short description - ... -``` +Notice the two last class properties: `_default_input` and `_default_parameters`. +In case of only one input specification (resp. set of parameters) is available, +these properties are optional. +With more than one specification (resp. set), you must explicitly tell UQTestFuns +which specification and set should be used by default (i.e., when not specified). With this, the test function implementation is complete. We now need to import the test function at the package level. @@ -355,13 +294,14 @@ from .branin import Branin ... __all__ = [ - ... + ..., "Branin", - ... + ..., ] ``` -Now you can check if all has been correctly set up by listing the available built-in function from a Python terminal. +Now you can check if all has been correctly set up by listing the available +built-in functions from a Python terminal. ```python >>> import uqtestfuns as uqtf @@ -432,3 +372,10 @@ on how to add the documentation for a test function. Congratulations you just successfully implemented your first UQ test function and add it to the code base! + +## References + +```{bibliography} +:style: unsrtalpha +:filter: docname in docnames +``` diff --git a/docs/test-functions/ackley.md b/docs/test-functions/ackley.md index 22f91a7..693976a 100644 --- a/docs/test-functions/ackley.md +++ b/docs/test-functions/ackley.md @@ -104,21 +104,15 @@ Check if it has been correctly instantiated: print(my_testfun) ``` -By default, the spatial dimension is set to $2$. +By default, the spatial dimension is set to $2$[^default_dimension]. To create an instance with another value of spatial dimension, -pass an integer to the parameter `spatial_dimension` (or as the first argument). +pass an integer to the parameter `spatial_dimension` (keyword only). For example, to create an instance of 10-dimensional Ackley function, type: ```{code-cell} ipython3 my_testfun = uqtf.Ackley(spatial_dimension=10) ``` -Or alternatively, pass the dimension as the first argument: - -```{code-cell} ipython3 -my_testfun = uqtf.Ackley(10) -``` - In the subsequent section, this 10-dimensional Ackley function will be used for illustration. @@ -185,3 +179,6 @@ $\mathcal{M}(\boldsymbol{x}^*) = 0$ at $x_m^* = 0,\, m = 1, \ldots, M$. :style: unsrt :filter: docname in docnames ``` + +[^default_dimension]: This default dimension applies to all variable dimension +test functions. It will be used if the `spatial_dimension` argument is not given. diff --git a/docs/test-functions/bratley1992d.md b/docs/test-functions/bratley1992d.md index 124cdb1..731a637 100644 --- a/docs/test-functions/bratley1992d.md +++ b/docs/test-functions/bratley1992d.md @@ -29,8 +29,7 @@ It was used in {cite}`Kucherenko2009` and {cite}`Saltelli2010` in the context of global sensitivity analysis. ```{note} -There are four other test functions used in Bratley et al. {cite}`Bratley1992` - +There are four other test functions used in Bratley et al. {cite}`Bratley1992`. ``` The plots for one-dimensional and two-dimensional `Bratley1992d` functions @@ -110,9 +109,9 @@ Check if it has been correctly instantiated: print(my_testfun) ``` -By default, the spatial dimension is set to $2$. +By default, the spatial dimension is set to $2$[^default_dimension]. To create an instance with another value of spatial dimension, -pass an integer to the parameter `spatial_dimension` (or as the first argument). +pass an integer to the parameter `spatial_dimension` (keyword only). For example, to create an instance of 10-dimensional `Bratley1992d` function, type: @@ -120,12 +119,6 @@ type: my_testfun = uqtf.Bratley1992d(spatial_dimension=10) ``` -Or alternatively, pass the dimension as the first argument: - -```{code-cell} ipython3 -my_testfun = uqtf.Bratley1992d(10) -``` - ## Description The `Bratley1992d` function is defined as follows[^location]: @@ -224,3 +217,6 @@ where: [^location]: see Section 5.1, p. 207 (test function no. 4) in {cite}`Bratley1992`. + +[^default_dimension]: This default dimension applies to all variable dimension +test functions. It will be used if the `spatial_dimension` argument is not given. diff --git a/docs/test-functions/sobol-g.md b/docs/test-functions/sobol-g.md index ac93425..0e39a54 100644 --- a/docs/test-functions/sobol-g.md +++ b/docs/test-functions/sobol-g.md @@ -104,9 +104,9 @@ Check if it has been correctly instantiated: print(my_testfun) ``` -By default, the spatial dimension is set to $2$. +By default, the spatial dimension is set to $2$[^default_dimension]. To create an instance with another value of spatial dimension, -pass an integer to the parameter `spatial_dimension` (or as the first argument). +pass an integer to the parameter `spatial_dimension` (keyword only). For example, to create an instance of the Sobol'-G function in six dimensions, type: @@ -114,12 +114,6 @@ type: my_testfun = uqtf.SobolG(spatial_dimension=6) ``` -Or, alternatively as the first argument: - -```{code-cell} ipython3 -my_testfun = uqtf.SobolG(6) -``` - In the subsequent section, the function will be illustrated using six dimensions. @@ -376,4 +370,7 @@ tabulate( ``` [^integral]: The expected value is the same as the integral over the domain -because the input is uniform in a unit hypercube. \ No newline at end of file +because the input is uniform in a unit hypercube. + +[^default_dimension]: This default dimension applies to all variable dimension +test functions. It will be used if the `spatial_dimension` argument is not given. diff --git a/src/uqtestfuns/__init__.py b/src/uqtestfuns/__init__.py index 92d109b..c8252bb 100644 --- a/src/uqtestfuns/__init__.py +++ b/src/uqtestfuns/__init__.py @@ -5,9 +5,10 @@ from .core import UnivDist from .core import ProbInput -from .core import UQTestFunABC +from .core import UQTestFunBareABC, UQTestFunABC from .core import UQTestFun + from . import test_functions from .test_functions import * # noqa @@ -22,6 +23,8 @@ "UnivDist", "ProbInput", "UQTestFunABC", + "UQTestFunBareABC", + "UQTestFunABC", "UQTestFun", "test_functions", "UQMetaFunSpec", diff --git a/src/uqtestfuns/core/__init__.py b/src/uqtestfuns/core/__init__.py index 5b0c925..0e7f3e6 100644 --- a/src/uqtestfuns/core/__init__.py +++ b/src/uqtestfuns/core/__init__.py @@ -3,7 +3,13 @@ """ from .prob_input.univariate_distribution import UnivDist from .prob_input.probabilistic_input import ProbInput -from .uqtestfun_abc import UQTestFunABC +from .uqtestfun_abc import UQTestFunBareABC, UQTestFunABC from .uqtestfun import UQTestFun -__all__ = ["UnivDist", "ProbInput", "UQTestFunABC", "UQTestFun"] +__all__ = [ + "UnivDist", + "ProbInput", + "UQTestFunBareABC", + "UQTestFunABC", + "UQTestFun", +] diff --git a/src/uqtestfuns/core/prob_input/input_spec.py b/src/uqtestfuns/core/prob_input/input_spec.py index 0f7a9fe..fce0baf 100644 --- a/src/uqtestfuns/core/prob_input/input_spec.py +++ b/src/uqtestfuns/core/prob_input/input_spec.py @@ -1,17 +1,20 @@ """ Module with definition of data structure to hold information on prob. input. -The probabilistic input specification should be stored in a built-in Python +A probabilistic input specification may be stored in a built-in Python data type that contains the only information required to construct -a ProbInput instance. The container type should be free of custom methods. +a ``UnivDist`` (for one-dimensional marginals) and ``ProbInput`` +(for probabilistic input models) instances. +The container type should be free of custom methods and derived from +``NamedTuple`` class to provide typing information. """ from typing import Any, Callable, List, NamedTuple, Optional, Tuple, Union -__all__ = ["MarginalSpec", "ProbInputSpec", "ProbInputSpecVarDim"] +__all__ = ["UnivDistSpec", "ProbInputSpecFixDim", "ProbInputSpecVarDim"] -class MarginalSpec(NamedTuple): +class UnivDistSpec(NamedTuple): """A univariate marginal distribution specification. Parameters @@ -26,14 +29,14 @@ class MarginalSpec(NamedTuple): Short description of the univariate marginal. """ - name: str + name: Optional[str] distribution: str parameters: Union[List[Union[int, float]], Tuple[Union[int, float], ...]] - description: str + description: Optional[str] -class ProbInputSpec(NamedTuple): - """All the information required for constructing a ProbInput instance. +class ProbInputSpecFixDim(NamedTuple): + """All the information to construct a ProbInput w/ a fixed dimension. Parameters ---------- @@ -41,23 +44,23 @@ class ProbInputSpec(NamedTuple): The name of the probabilistic input model. description : str A short description of the probabilistic input model. - marginals : Union[Callable, List[MarginalSpec]] + marginals : Union[Callable, List[UnivDistSpec]] A list of univariate marginal specifications or a callable to construct the list of marginal specifications. copulas : Optional[Any] The copula specification of the probabilistic input model. """ - name: str - description: str - marginals: List[MarginalSpec] + name: Optional[str] + description: Optional[str] + marginals: List[UnivDistSpec] copulas: Optional[Any] class ProbInputSpecVarDim(NamedTuple): """All the information to construct a ProbInput w/ variable dimension.""" - name: str - description: str - marginals_generator: Callable[[int], List[MarginalSpec]] + name: Optional[str] + description: Optional[str] + marginals_generator: Callable[[int], List[UnivDistSpec]] copulas: Optional[Any] diff --git a/src/uqtestfuns/core/prob_input/probabilistic_input.py b/src/uqtestfuns/core/prob_input/probabilistic_input.py index b60bff4..c679531 100644 --- a/src/uqtestfuns/core/prob_input/probabilistic_input.py +++ b/src/uqtestfuns/core/prob_input/probabilistic_input.py @@ -15,7 +15,7 @@ from typing import Any, List, Optional, Union, Tuple from .univariate_distribution import UnivDist, FIELD_NAMES -from .input_spec import ProbInputSpec, ProbInputSpecVarDim +from .input_spec import ProbInputSpecFixDim, ProbInputSpecVarDim __all__ = ["ProbInput"] @@ -186,7 +186,7 @@ def _repr_html_(self): @classmethod def from_spec( cls, - prob_input_spec: Union[ProbInputSpec, ProbInputSpecVarDim], + prob_input_spec: Union[ProbInputSpecFixDim, ProbInputSpecVarDim], *, spatial_dimension: Optional[int] = None, rng_seed: Optional[int] = None, diff --git a/src/uqtestfuns/core/prob_input/univariate_distribution.py b/src/uqtestfuns/core/prob_input/univariate_distribution.py index 168f397..9c8568b 100644 --- a/src/uqtestfuns/core/prob_input/univariate_distribution.py +++ b/src/uqtestfuns/core/prob_input/univariate_distribution.py @@ -21,7 +21,7 @@ get_cdf_values, get_icdf_values, ) -from .input_spec import MarginalSpec +from .input_spec import UnivDistSpec from ...global_settings import ARRAY_FLOAT __all__ = ["UnivDist"] @@ -161,13 +161,13 @@ def icdf(self, xx: Union[float, np.ndarray]) -> ARRAY_FLOAT: @classmethod def from_spec( - cls, marginal_spec: MarginalSpec, rng_seed: Optional[int] = None + cls, marginal_spec: UnivDistSpec, rng_seed: Optional[int] = None ): """Create an instance of UnivDist from a marginal specification. Parameters ---------- - marginal_spec : MarginalSpec + marginal_spec : UnivDistSpec The specification for the univariate marginal. rng_seed : int, optional The seed used to initialize the pseudo-random number generator. diff --git a/src/uqtestfuns/core/uqtestfun.py b/src/uqtestfuns/core/uqtestfun.py index e864ccc..8f3c59b 100644 --- a/src/uqtestfuns/core/uqtestfun.py +++ b/src/uqtestfuns/core/uqtestfun.py @@ -4,38 +4,41 @@ """ from typing import Any, Callable, Optional -from .uqtestfun_abc import UQTestFunABC +from .uqtestfun_abc import UQTestFunBareABC from .prob_input.probabilistic_input import ProbInput __all__ = ["UQTestFun"] -class UQTestFun(UQTestFunABC): - """Generic concrete class of UQ test function.""" +class UQTestFun(UQTestFunBareABC): + """Generic concrete class of UQ test function. - _tags = None - - _available_inputs = None - - _available_parameters = None - - _default_spatial_dimension = None - - _description = None + Parameters + ---------- + evaluate : Callable + The evaluation function of the UQ test function implemented as a + Python callable. + prob_input : ProbInput + The probabilistic input model of the UQ test function. + parameters : Any, optional + The parameters set of the UQ test function. + If not specified, `None` is used. + name : str, optional + The name of the UQ test function. + """ def __init__( self, evaluate: Callable, - prob_input: Optional[ProbInput] = None, + prob_input: ProbInput, parameters: Optional[Any] = None, name: Optional[str] = None, ): self._evaluate = evaluate - super().__init__(prob_input, parameters, name) def evaluate(self, xx): - if self.parameters is None: - return self._evaluate(xx) - else: + if self.parameters is not None: return self._evaluate(xx, self.parameters) + + return self._evaluate(xx) diff --git a/src/uqtestfuns/core/uqtestfun_abc.py b/src/uqtestfuns/core/uqtestfun_abc.py index f9b372e..030dee6 100644 --- a/src/uqtestfuns/core/uqtestfun_abc.py +++ b/src/uqtestfuns/core/uqtestfun_abc.py @@ -1,24 +1,27 @@ """ -This module provides an abstract base class for defining a test function class. +This module provides abstract base classes for defining a test function class. """ import abc import numpy as np -from typing import Optional, Any, Tuple, List +from typing import Any, Callable, Dict, List, Optional, Union from .prob_input.probabilistic_input import ProbInput +from .prob_input.input_spec import ProbInputSpecFixDim, ProbInputSpecVarDim from .utils import create_canonical_uniform_input -__all__ = ["UQTestFunABC"] +__all__ = ["UQTestFunBareABC", "UQTestFunABC"] CLASS_HIDDEN_ATTRIBUTES = [ "_tags", + "_description", "_available_inputs", "_available_parameters", "_default_spatial_dimension", - "_description", ] +DEFAULT_DIMENSION = 2 + class classproperty(property): """Decorator w/ descriptor to get and set class-level attributes.""" @@ -30,87 +33,48 @@ def __set__(self, owner_self, owner_cls): raise AttributeError("can't set attribute") -class UQTestFunABC(abc.ABC): - """An abstract class for UQ test functions.""" +class UQTestFunBareABC(abc.ABC): + """An abstract class for a bare UQ test functions. + + Parameters + ---------- + prob_input : ProbInput + The probabilistic input model of the UQ test function. + parameters : Any, optional + A set of parameters. By default, it is None. + name : str, optional + The name of the UQ test function. By default, it is None. + + Notes + ----- + - A bare UQ test function only includes the evaluation function, + probabilistic input model, parameters, and a (optional) name. + """ def __init__( self, - prob_input: Optional[ProbInput] = None, + prob_input: ProbInput, parameters: Optional[Any] = None, name: Optional[str] = None, ): - """Default constructor for the UQTestFunABC. - - Parameters - ---------- - prob_input : ProbInput - Multivariate probabilistic input model. - parameters : Any - Parameters to the test function. Once set, the parameters are held - constant during function evaluation. It may, however, be modified - (by passing a new value) once an instance has been created. - name : str, optional - Name of the instance. - """ - if not (prob_input is None or isinstance(prob_input, ProbInput)): - raise TypeError( - f"Probabilistic input model must be either 'None' or " - f"of 'MultivariateInput' type! Got instead {type(prob_input)}." - ) - - self._prob_input = prob_input + self.prob_input = prob_input self._parameters = parameters self._name = name - @classmethod - def __init_subclass__(cls): - """Verify if concrete class has all the required hidden attributes.""" - for class_hidden_attribute in CLASS_HIDDEN_ATTRIBUTES: - if not hasattr(cls, class_hidden_attribute): - raise NotImplementedError( - f"Class {cls} lacks required {class_hidden_attribute!r} " - f"class attribute." - ) - - @classproperty - def tags(cls) -> Optional[List[str]]: - """Tags to classify different UQ test functions.""" - return cls._tags # type: ignore - - @classproperty - def available_inputs(cls) -> Optional[Tuple[str, ...]]: - """All the keys to the available probabilistic input specifications.""" - return cls._available_inputs # type: ignore - - @classproperty - def available_parameters(cls) -> Optional[Tuple[str, ...]]: - """All the keys to the available set of parameter values.""" - return cls._available_parameters # type: ignore - - @classproperty - def default_spatial_dimension(cls) -> Optional[int]: - """To store the default dimension of a test function.""" - return cls._default_spatial_dimension # type: ignore - - @classproperty - def description(cls) -> Optional[str]: - """Short description of the UQ test function.""" - return cls._description # type: ignore - @property - def prob_input(self) -> Optional[ProbInput]: + def prob_input(self) -> ProbInput: """The probabilistic input model of the UQ test function.""" return self._prob_input @prob_input.setter - def prob_input(self, value: Optional[ProbInput]): + def prob_input(self, value: ProbInput): """The setter for probabilistic input model of the UQ test function.""" - if value is None or isinstance(value, ProbInput): + if isinstance(value, ProbInput): self._prob_input = value else: raise TypeError( - f"Probabilistic input model must be either 'None' or " - f"of 'MultivariateInput' types! Got instead {type(value)}." + f"Probabilistic input model must be either a 'None' or " + f"of a 'ProbInput' type! Got instead {type(value)}." ) @property @@ -123,19 +87,16 @@ def parameters(self, value: Any): """The setter for the parameters of the test function.""" self._parameters = value - @property - def spatial_dimension(self) -> int: - """The dimension (number of input variables) of the test function.""" - if self._prob_input is not None: - return self._prob_input.spatial_dimension - else: - return self.default_spatial_dimension - @property def name(self) -> Optional[str]: """The name of the UQ test function.""" return self._name + @property + def spatial_dimension(self) -> int: + """The spatial dimension of the UQ test function.""" + return self.prob_input.spatial_dimension + def transform_sample( self, xx: np.ndarray, @@ -160,7 +121,7 @@ def transform_sample( Transformed sampled values from the specified uniform domain to the domain of the function as defined the `input` property. """ - if self.prob_input is None: + if self.prob_input is None or self.spatial_dimension is None: raise ValueError( "There is not ProbInput attached to the function! " "A sample can't be generated." @@ -185,27 +146,243 @@ def transform_sample( return xx_trans + def __str__(self): + out = ( + f"Name : {self.name}\n" + f"Spatial dimension : {self.spatial_dimension}" + ) + + return out + + def __call__(self, xx): + """Evaluation of the test function by calling the instance.""" + if self.prob_input and self.spatial_dimension: + # Verify the shape of the input + _verify_sample_shape(xx, self.spatial_dimension) + + if self.prob_input is not None: + # If ProbInput is attached, verify the domain of the input + for dim_idx in range(self.spatial_dimension): + lb = self.prob_input.marginals[dim_idx].lower + ub = self.prob_input.marginals[dim_idx].upper + _verify_sample_domain( + xx[:, dim_idx], min_value=lb, max_value=ub + ) + + return self.evaluate(xx) + @abc.abstractmethod - def evaluate(self, xx: np.ndarray): - """Evaluate the concrete test function implementation on points.""" + def evaluate(self, *args): + """Abstract method for the implementation of the UQ test function.""" pass - def __call__(self, xx: np.ndarray): - """Evaluation of the test function by calling the instance.""" - # Verify the shape of the input - _verify_sample_shape(xx, self.spatial_dimension) +class UQTestFunABC(UQTestFunBareABC): + """An abstract class for (published) UQ test functions. + + Parameters + ---------- + spatial_dimension : int, optional + The spatial dimension of the UQ test function. + This is used only when the function supports variable dimension; + otherwise, if specified, an exception is raised. + In the case of functions with variable dimension, the default dimension + is set to 2. + This is a keyword only parameter. + prob_input_selection : str, optional + The selection of probabilistic input model; this is used when there are + multiple input specifications in the literature. + This is a keyword only parameter. + parameters_selection : str, optional + The selection of parameters set; this is used when there are multiple + sets of parameters available in the literature. + This is a keyword only parameter. + name : str, optional + The name of the UQ test function. + If not given, `None` is used as name. + This is a keyword only parameter. + rng_seed_prob_input : int, optional + The seed number for the pseudo-random number generator of the + corresponding `ProbInput`; if not given, `None` is used + (taken from the system entropy). + This is a keyword only parameter. + + Notes + ----- + - A published UQ test function includes a couple of additional metadata, + namely tags and description. + + Raises + ------ + KeyError + If selection is not in the list of available inputs and parameters. + TypeError + If spatial dimension is specified for a UQ test function with + a fixed dimension. + """ - if self.prob_input is not None: - # If ProbInput is attached, verify the domain of the input - for dim_idx in range(self.spatial_dimension): - lb = self.prob_input.marginals[dim_idx].lower - ub = self.prob_input.marginals[dim_idx].upper - _verify_sample_domain( - xx[:, dim_idx], min_value=lb, max_value=ub + _default_input: Optional[str] = None + _default_parameters: Optional[str] = None + + def __init__( + self, + *, + spatial_dimension: Optional[int] = None, + prob_input_selection: Optional[str] = None, + parameters_selection: Optional[str] = None, + name: Optional[str] = None, + rng_seed_prob_input: Optional[int] = None, + ): + # --- Create a probabilistic input model + # Select the probabilistic input model + available_inputs = self.available_inputs + if not prob_input_selection: + prob_input_selection = self.default_input + if prob_input_selection not in available_inputs: + print(prob_input_selection) + raise KeyError( + "Input selection is not in the available specifications." + ) + prob_input_spec = available_inputs[prob_input_selection] + + # Determine the dimensionality of the test function + if isinstance(prob_input_spec, ProbInputSpecFixDim): + if spatial_dimension: + raise TypeError("Fixed test dimension!") + if not spatial_dimension: + spatial_dimension = DEFAULT_DIMENSION + + # Create a ProbInput instance + if isinstance(prob_input_spec, ProbInputSpecVarDim): + prob_input = ProbInput.from_spec( + prob_input_spec, + spatial_dimension=spatial_dimension, + rng_seed=rng_seed_prob_input, + ) + else: + prob_input = ProbInput.from_spec( + prob_input_spec, rng_seed=rng_seed_prob_input + ) + + # --- Select the parameters set, when applicable + available_parameters = self.available_parameters + if available_parameters: + if not parameters_selection: + parameters_selection = self.default_parameters + if parameters_selection not in available_parameters: + raise KeyError( + "Parameters selection is not in the available sets." ) + parameters = available_parameters[parameters_selection] - return self.evaluate(xx) + # If the parameters set is a function of spatial dimension + if isinstance(prob_input_spec, ProbInputSpecVarDim): + if isinstance(parameters, Callable): # type: ignore + parameters = parameters(spatial_dimension) + + else: + parameters = None + + # --- Process the default name + if name is None: + name = self.__class__.__name__ + + # --- Initialize the parent class + super().__init__( + prob_input=prob_input, parameters=parameters, name=name + ) + + @classmethod + def __init_subclass__(cls): + """Verify if concrete class has all the required hidden attributes. + + Raises + ------ + NotImplementedError + If required attributes are not implemented in the concrete class. + ValueError + If default input and parameters selections are not specified + when there are multiple of them. + KeyError + If the selections for the default input and parameters set are + not available. + """ + for class_hidden_attribute in CLASS_HIDDEN_ATTRIBUTES: + # Some class attributes must be specified + if not hasattr(cls, class_hidden_attribute): + raise NotImplementedError( + f"Class {cls} lacks required {class_hidden_attribute!r} " + f"class attribute." + ) + + # Parse default input selection + if cls.default_input: + if cls.default_input not in cls.available_inputs: + raise KeyError("Input selection is not available!") + else: + if len(cls.available_inputs) > 1: + raise ValueError( + "There are multiple available input specifications, " + "the default input selection must be specified!" + ) + else: + # If only one is available, use it without being specified + cls._default_input = list(cls.available_inputs.keys())[0] + + # Parse default parameters set selection + if cls.available_parameters: + if cls.default_parameters: + if cls.default_parameters not in cls.available_parameters: + raise KeyError("Parameters selection is not available!") + + else: + if len(cls.available_parameters) > 1: + raise ValueError( + "There are multiple available parameters sets, " + "the default input selection must be specified!" + ) + else: + # If only one is available, use it without being specified + cls._default_parameters = list( + cls.available_parameters.keys() + )[0] + + @classproperty + def tags(cls) -> List[str]: + """Tags to classify different UQ test functions.""" + return cls._tags # type: ignore + + @classproperty + def available_inputs( + cls, + ) -> Union[Dict[str, ProbInputSpecFixDim], Dict[str, ProbInputSpecVarDim]]: + """All the keys to the available probabilistic input specifications.""" + return cls._available_inputs # type: ignore + + @classproperty + def default_input(cls) -> Optional[str]: + """The key to the default probabilistic input specification.""" + return cls._default_input # type: ignore + + @classproperty + def available_parameters(cls) -> Optional[Dict[str, Any]]: + """All the keys to the available set of parameter values.""" + return cls._available_parameters # type: ignore + + @classproperty + def default_parameters(cls) -> Optional[str]: + """The key to the default set of parameters.""" + return cls._default_parameters # type: ignore + + @classproperty + def default_spatial_dimension(cls) -> Optional[int]: + """To store the default dimension of a test function.""" + return cls._default_spatial_dimension # type: ignore + + @classproperty + def description(cls) -> Optional[str]: + """Short description of the UQ test function.""" + return cls._description # type: ignore def __str__(self): out = ( @@ -216,6 +393,25 @@ def __str__(self): return out + def evaluate(self, xx): + """Concrete implementation, actual function is delegated to eval_().""" + if self.parameters is None: + return self.__class__.eval_(xx) + else: + return self.__class__.eval_(xx, self.parameters) + + @staticmethod + @abc.abstractmethod + def eval_(*args): + """Static method for the concrete function implementation. + + Notes + ----- + - The function evaluation is implemented as a static method so the + function can be added without being bounded to the instance. + """ + pass + def _verify_sample_shape(xx: np.ndarray, num_cols: int): """Verify the number of columns of the input sample array. diff --git a/src/uqtestfuns/helpers.py b/src/uqtestfuns/helpers.py index 9ef490b..16b2134 100644 --- a/src/uqtestfuns/helpers.py +++ b/src/uqtestfuns/helpers.py @@ -9,7 +9,7 @@ from tabulate import tabulate as tbl # 'tabulate' is used as a parameter name from .utils import get_available_classes, SUPPORTED_TAGS from . import test_functions -from typing import List, Optional, Tuple, Union, Dict +from typing import List, Optional, Union from .core import UQTestFunABC @@ -54,9 +54,7 @@ def list_functions( _verify_input_args(spatial_dimension, tag, tabulate) # --- Get all the available classes that implement the test functions - available_classes: List[Tuple[str, UQTestFunABC]] = get_available_classes( - test_functions - ) + available_classes = get_available_classes(test_functions) available_classes_dict = dict(available_classes) # --- Filter according to the requested spatial dimension @@ -108,9 +106,12 @@ def list_functions( ): available_class = available_classes_dict[available_class_name] - default_spatial_dimension = available_class.default_spatial_dimension - if not default_spatial_dimension: + if not available_class.default_spatial_dimension: default_spatial_dimension = "M" + else: + default_spatial_dimension = ( + available_class.default_spatial_dimension + ) tags = ", ".join(available_class.tags) @@ -212,7 +213,7 @@ def _verify_input_args( def _get_functions_from_dimension( - available_classes: Dict[str, UQTestFunABC], + available_classes: dict, spatial_dimension: Union[int, str], ) -> List[str]: """Get the function keys that satisfy the spatial dimension filter.""" @@ -238,9 +239,7 @@ def _get_functions_from_dimension( return values -def _get_functions_from_tag( - available_classes: Dict[str, UQTestFunABC], tag: str -) -> List[str]: +def _get_functions_from_tag(available_classes: dict, tag: str) -> List[str]: """Get the function keys that satisfy the tag filter.""" values = [] diff --git a/src/uqtestfuns/test_functions/ackley.py b/src/uqtestfuns/test_functions/ackley.py index acdb61d..af0fa8d 100644 --- a/src/uqtestfuns/test_functions/ackley.py +++ b/src/uqtestfuns/test_functions/ackley.py @@ -21,19 +21,15 @@ """ import numpy as np -from typing import List, Optional +from typing import List, Tuple from ..core.uqtestfun_abc import UQTestFunABC -from ..core.prob_input.input_spec import MarginalSpec, ProbInputSpecVarDim -from .available import ( - create_prob_input_from_spec, - create_parameters_from_available, -) +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecVarDim __all__ = ["Ackley"] -def _ackley_input(spatial_dimension: int) -> List[MarginalSpec]: +def _ackley_input(spatial_dimension: int) -> List[UnivDistSpec]: """Create a list of marginals for the M-dimensional Ackley function. Parameters @@ -43,13 +39,13 @@ def _ackley_input(spatial_dimension: int) -> List[MarginalSpec]: Returns ------- - List[MarginalSpec] + List[UnivDistSpec] A list of marginals for the multivariate input following Ref. [1] """ marginals = [] for i in range(spatial_dimension): marginals.append( - MarginalSpec( + UnivDistSpec( name=f"X{i + 1}", distribution="uniform", parameters=[-32.768, 32.768], @@ -62,7 +58,7 @@ def _ackley_input(spatial_dimension: int) -> List[MarginalSpec]: AVAILABLE_INPUT_SPECS = { "Ackley1987": ProbInputSpecVarDim( - name="Ackley-Ackley-1987", + name="Ackley1987", description=( "Search domain for the Ackley function from Ackley (1987)." ), @@ -71,117 +67,46 @@ def _ackley_input(spatial_dimension: int) -> List[MarginalSpec]: ), } - -DEFAULT_INPUT_SELECTION = "Ackley1987" - AVAILABLE_PARAMETERS = {"Ackley1987": np.array([20, 0.2, 2 * np.pi])} -DEFAULT_PARAMETERS_SELECTION = "Ackley1987" - -# The dimension is variable so define a default for fallback -DEFAULT_DIMENSION_SELECTION = 2 - -class Ackley(UQTestFunABC): - """A concrete implementation of the M-dimensional Ackley test function. +def evaluate(xx: np.ndarray, parameters: Tuple[float, float, float]): + """Evaluate the Ackley function on a set of input values. Parameters ---------- - spatial_dimension : int - The requested number of spatial_dimension. If not specified, - the default is set to 2. - prob_input_selection : str, optional - The selection of a probabilistic input model from a list of - available specifications. This is a keyword only parameter. - parameters_selection : str, optional - The selection of a parameters sets from a list of available - parameter sets. This is a keyword only parameter. - name : str, optional - The name of the instance; if not given the default name is used. - This is a keyword only parameter. - rng_seed_prob_input : int, optional - The seed number for the pseudo-random number generator of the - corresponding `ProbInput`; if not given `None` is used - (taken from the system entropy). - This is a keyword only parameter. - """ - - _tags = ["optimization", "metamodeling"] - - _available_inputs = tuple(AVAILABLE_INPUT_SPECS.keys()) - - _available_parameters = tuple(AVAILABLE_PARAMETERS.keys()) - - _default_spatial_dimension = None + xx : np.ndarray + M-Dimensional input values given by an N-by-M array where + N is the number of input values. + parameters : Tuple[float, float, float] + A tuple of length 3 for the parameters of the Ackley function. - _description = "Ackley function from Ackley (1987)" + Returns + ------- + np.ndarray + The output of the Ackley function evaluated on the input values. + The output is a 1-dimensional array of length N. + """ - def __init__( - self, - spatial_dimension: int = DEFAULT_DIMENSION_SELECTION, - *, - prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, - parameters_selection: Optional[str] = DEFAULT_PARAMETERS_SELECTION, - name: Optional[str] = None, - rng_seed_prob_input: Optional[int] = None, - ): - # --- Arguments processing - if not isinstance(spatial_dimension, int): - raise TypeError( - f"Spatial dimension is expected to be of 'int'. " - f"Got {type(spatial_dimension)} instead." - ) + m = xx.shape[1] + a, b, c = parameters - # Get the ProbInputSpec from available - if prob_input_selection is None: - prob_input_spec = None - else: - prob_input_spec = AVAILABLE_INPUT_SPECS[prob_input_selection] + # Compute the Ackley function + term_1 = -1 * a * np.exp(-1 * b * np.sqrt(np.sum(xx**2, axis=1) / m)) + term_2 = -1 * np.exp(np.sum(np.cos(c * xx), axis=1) / m) - # Ackley is an M-dimensional test function, either given / use default - # Create the input according to spatial dimension - prob_input = create_prob_input_from_spec( - prob_input_spec, spatial_dimension, rng_seed_prob_input - ) - # Create the parameters according to spatial dimension - parameters = create_parameters_from_available( - parameters_selection, AVAILABLE_PARAMETERS, spatial_dimension - ) - # Process the default name - if name is None: - name = Ackley.__name__ - - super().__init__( - prob_input=prob_input, - parameters=parameters, - name=name, - ) - - def evaluate(self, xx: np.ndarray): - """Evaluate the Ackley function on a set of input values. - - Parameters - ---------- - xx : np.ndarray - M-Dimensional input values given by an N-by-M array where - N is the number of input values. - params : tuple - A tuple of length 3 for the parameters of the Ackley function. + yy = term_1 + term_2 + a + np.exp(1) - Returns - ------- - np.ndarray - The output of the Ackley function evaluated on the input values. - The output is a 1-dimensional array of length N. - """ + return yy - m = xx.shape[1] - a, b, c = self.parameters - # Compute the Ackley function - term_1 = -1 * a * np.exp(-1 * b * np.sqrt(np.sum(xx**2, axis=1) / m)) - term_2 = -1 * np.exp(np.sum(np.cos(c * xx), axis=1) / m) +class Ackley(UQTestFunABC): + """A concrete implementation of the M-dimensional Ackley test function.""" - yy = term_1 + term_2 + a + np.exp(1) + _tags = ["optimization", "metamodeling"] + _description = "Optimization test function from Ackley (1987)" + _available_inputs = AVAILABLE_INPUT_SPECS + _available_parameters = AVAILABLE_PARAMETERS + _default_spatial_dimension = None # Indicate that this is variable dim. - return yy + eval_ = staticmethod(evaluate) diff --git a/src/uqtestfuns/test_functions/available.py b/src/uqtestfuns/test_functions/available.py deleted file mode 100644 index 7133351..0000000 --- a/src/uqtestfuns/test_functions/available.py +++ /dev/null @@ -1,86 +0,0 @@ -""" -Helpers module to construct probabilistic input and parameters. -""" -from types import FunctionType -from typing import Any, Dict, Optional, Union -from ..core import ProbInput -from ..core.prob_input.input_spec import ProbInputSpec, ProbInputSpecVarDim - - -def create_parameters_from_available( - param_selection: Optional[str], - available_parameters: dict, - spatial_dimension: Optional[int] = None, -) -> Any: - """Construct a set of parameters given available specifications. - - Parameters - ---------- - param_selection : str - Which parameter specification to construct. - available_parameters : dict - Dictionary of available parameters specifications. - spatial_dimension : int, optional - The requested number of spatial dimensions, when applicable. - Some specifications are functions of spatial dimension. - """ - - if param_selection is None: - return None - - if param_selection in available_parameters: - parameters = available_parameters[param_selection] - if isinstance(parameters, FunctionType): - parameters = parameters(spatial_dimension) - else: - raise ValueError("Invalid parameters selection!") - - return parameters - - -def get_prob_input_spec( - prob_input_selection: Optional[str], - available_input_specs: Dict[str, ProbInputSpec], -) -> Optional[ProbInputSpec]: - """Get ProbInputSpec from the available specifications. - - Parameters - ---------- - """ - if prob_input_selection is None: - return None - - if prob_input_selection in available_input_specs: - prob_input_spec = available_input_specs[prob_input_selection] - else: - raise KeyError("Invalid ProbInput selection!") - - return prob_input_spec - - -def create_prob_input_from_spec( - prob_input_spec: Optional[Union[ProbInputSpec, ProbInputSpecVarDim]], - spatial_dimension: Optional[int] = None, - rng_seed: Optional[int] = None, -) -> Optional[ProbInput]: - """Construct a Multivariate input given available specifications. - - Parameters - ---------- - prob_input_spec : Union[ProbInputSpec, ProbInputSpecVarDim], optional - The specification of a probabilistic input model. - spatial_dimension : int, optional - The requested number of spatial dimensions, when applicable. - Some specifications are functions of spatial dimension. - rng_seed : int, optional - The seed for the pseudo-random number generator; if not given then - the number is taken from the system entropy. - """ - if prob_input_spec is None: - return None - - prob_input = ProbInput.from_spec( - prob_input_spec, spatial_dimension=spatial_dimension, rng_seed=rng_seed - ) - - return prob_input diff --git a/src/uqtestfuns/test_functions/borehole.py b/src/uqtestfuns/test_functions/borehole.py index 7021c71..965d166 100644 --- a/src/uqtestfuns/test_functions/borehole.py +++ b/src/uqtestfuns/test_functions/borehole.py @@ -23,59 +23,56 @@ """ import numpy as np -from typing import Optional - from ..core.uqtestfun_abc import UQTestFunABC -from ..core.prob_input.input_spec import MarginalSpec, ProbInputSpec -from .available import get_prob_input_spec, create_prob_input_from_spec +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecFixDim __all__ = ["Borehole"] # From Ref. [1] INPUT_MARGINALS_HARPER1983 = [ - MarginalSpec( + UnivDistSpec( name="rw", distribution="normal", parameters=[0.10, 0.0161812], description="radius of the borehole [m]", ), - MarginalSpec( + UnivDistSpec( name="r", distribution="lognormal", parameters=[7.71, 1.0056], description="radius of influence [m]", ), - MarginalSpec( + UnivDistSpec( name="Tu", distribution="uniform", parameters=[63070.0, 115600.0], description="transmissivity of upper aquifer [m^2/year]", ), - MarginalSpec( + UnivDistSpec( name="Hu", distribution="uniform", parameters=[990.0, 1100.0], description="potentiometric head of upper aquifer [m]", ), - MarginalSpec( + UnivDistSpec( name="Tl", distribution="uniform", parameters=[63.1, 116.0], description="transmissivity of lower aquifer [m^2/year]", ), - MarginalSpec( + UnivDistSpec( name="Hl", distribution="uniform", parameters=[700.0, 820.0], description="potentiometric head of lower aquifer [m]", ), - MarginalSpec( + UnivDistSpec( name="L", distribution="uniform", parameters=[1120.0, 1680.0], description="length of the borehole [m]", ), - MarginalSpec( + UnivDistSpec( name="Kw", distribution="uniform", parameters=[9985.0, 12045.0], @@ -86,13 +83,13 @@ # From Ref. [2] INPUT_MARGINALS_MORRIS1993 = list(INPUT_MARGINALS_HARPER1983) INPUT_MARGINALS_MORRIS1993[0:2] = [ - MarginalSpec( + UnivDistSpec( name="rw", distribution="uniform", parameters=[0.05, 0.15], description="radius of the borehole [m]", ), - MarginalSpec( + UnivDistSpec( name="r", distribution="uniform", parameters=[100, 50000], @@ -101,7 +98,7 @@ ] AVAILABLE_INPUT_SPECS = { - "Harper1983": ProbInputSpec( + "Harper1983": ProbInputSpecFixDim( name="Borehole-Harper-1983", description=( "Probabilistic input model of the Borehole model " @@ -110,7 +107,7 @@ marginals=INPUT_MARGINALS_HARPER1983, copulas=None, ), - "Morris1993": ProbInputSpec( + "Morris1993": ProbInputSpecFixDim( name="Borehole-Morris-1993", description=( "Probabilistic input model of the Borehole model " @@ -124,67 +121,45 @@ DEFAULT_INPUT_SELECTION = "Harper1983" -class Borehole(UQTestFunABC): - """A concrete implementation of the Borehole function.""" +def evaluate(xx: np.ndarray) -> np.ndarray: + """Evaluate the Borehole function on a set of input values. - _tags = ["metamodeling", "sensitivity"] + Parameters + ---------- + xx : np.ndarray + 8-Dimensional input values given by N-by-8 arrays where + N is the number of input values. - _available_inputs = tuple(AVAILABLE_INPUT_SPECS.keys()) + Returns + ------- + np.ndarray + The output of the Borehole function evaluated on the input values. + The output is a 1-dimensional array of length N. + """ + # Compute the Borehole function + nom = 2 * np.pi * xx[:, 2] * (xx[:, 3] - xx[:, 5]) + denom_1 = np.log(xx[:, 1] / xx[:, 0]) + denom_2 = ( + 2 + * xx[:, 6] + * xx[:, 2] + / (np.log(xx[:, 1] / xx[:, 0]) * xx[:, 0] ** 2 * xx[:, 7]) + ) + denom_3 = xx[:, 2] / xx[:, 4] - _available_parameters = None + yy = nom / (denom_1 * (1 + denom_2 + denom_3)) + + return yy - _default_spatial_dimension = 8 +class Borehole(UQTestFunABC): + """A concrete implementation of the Borehole function.""" + + _tags = ["metamodeling", "sensitivity"] _description = "Borehole function from Harper and Gupta (1983)" + _available_inputs = AVAILABLE_INPUT_SPECS + _available_parameters = None + _default_input = DEFAULT_INPUT_SELECTION + _default_spatial_dimension = 8 - def __init__( - self, - *, - prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, - name: Optional[str] = None, - rng_seed_prob_input: Optional[int] = None, - ): - # --- Arguments processing - # Get the ProbInputSpec from available - prob_input_spec = get_prob_input_spec( - prob_input_selection, AVAILABLE_INPUT_SPECS - ) - # Create a ProbInput - prob_input = create_prob_input_from_spec( - prob_input_spec, rng_seed=rng_seed_prob_input - ) - # Process the default name - if name is None: - name = Borehole.__name__ - - super().__init__(prob_input=prob_input, name=name) - - def evaluate(self, xx): - """Evaluate the Borehole function on a set of input values. - - Parameters - ---------- - xx : np.ndarray - 8-Dimensional input values given by N-by-8 arrays where - N is the number of input values. - - Returns - ------- - np.ndarray - The output of the Borehole function evaluated on the input values. - The output is a 1-dimensional array of length N. - """ - # Compute the Borehole function - nom = 2 * np.pi * xx[:, 2] * (xx[:, 3] - xx[:, 5]) - denom_1 = np.log(xx[:, 1] / xx[:, 0]) - denom_2 = ( - 2 - * xx[:, 6] - * xx[:, 2] - / (np.log(xx[:, 1] / xx[:, 0]) * xx[:, 0] ** 2 * xx[:, 7]) - ) - denom_3 = xx[:, 2] / xx[:, 4] - - yy = nom / (denom_1 * (1 + denom_2 + denom_3)) - - return yy + eval_ = staticmethod(evaluate) diff --git a/src/uqtestfuns/test_functions/bratley1992.py b/src/uqtestfuns/test_functions/bratley1992.py index c96f5ff..6709481 100644 --- a/src/uqtestfuns/test_functions/bratley1992.py +++ b/src/uqtestfuns/test_functions/bratley1992.py @@ -32,16 +32,15 @@ """ import numpy as np -from typing import List, Optional +from typing import List from ..core.uqtestfun_abc import UQTestFunABC -from ..core.prob_input.input_spec import MarginalSpec, ProbInputSpecVarDim -from .available import create_prob_input_from_spec +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecVarDim __all__ = ["Bratley1992d"] -def _bratley_input(spatial_dimension: int) -> List[MarginalSpec]: +def _bratley_input(spatial_dimension: int) -> List[UnivDistSpec]: """Create a list of marginals for the M-dimensional Bratley test functions. Parameters @@ -51,13 +50,13 @@ def _bratley_input(spatial_dimension: int) -> List[MarginalSpec]: Returns ------- - List[MarginalSpec] + List[UnivDistSpec] A list of marginals for the multivariate input following Ref. [1] """ marginals = [] for i in range(spatial_dimension): marginals.append( - MarginalSpec( + UnivDistSpec( name=f"X{i + 1}", distribution="uniform", parameters=[0.0, 1.0], @@ -79,108 +78,51 @@ def _bratley_input(spatial_dimension: int) -> List[MarginalSpec]: ), } -DEFAULT_INPUT_SELECTION = "Bratley1992" - -# The dimension is variable so define a default for fallback -DEFAULT_DIMENSION_SELECTION = 2 - # Common metadata used in each class definition of Bratley test functions COMMON_METADATA = dict( _tags=["integration", "sensitivity"], - _available_inputs=tuple(AVAILABLE_INPUT_SPECS.keys()), + _available_inputs=AVAILABLE_INPUT_SPECS, _available_parameters=None, _default_spatial_dimension=None, _description="from Bratley et al. (1992)", ) -def _init( - self, - spatial_dimension: int = DEFAULT_DIMENSION_SELECTION, - *, - prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, - name: Optional[str] = None, - rng_seed_prob_input: Optional[int] = None, -) -> None: - """A common __init__ for all Bratley1992 test functions.""" - # --- Arguments processing - # Get the ProbInputSpec from available - if prob_input_selection is None: - prob_input_spec = None - else: - prob_input_spec = AVAILABLE_INPUT_SPECS[prob_input_selection] - - if not isinstance(spatial_dimension, int): - raise TypeError( - f"Spatial dimension is expected to be of 'int'. " - f"Got {type(spatial_dimension)} instead." - ) - # A Bratley1992 test function is an M-dimensional test function - # Create the input according to spatial dimension - prob_input = create_prob_input_from_spec( - prob_input_spec, - spatial_dimension, - rng_seed_prob_input, - ) - # Process the default name - if name is None: - name = self.__class__.__name__ - - UQTestFunABC.__init__(self, prob_input=prob_input, name=name) - - -class Bratley1992d(UQTestFunABC): - """A concrete implementation of the function 4 from Bratley et al. (1988). +def evaluate_bratley1992d(xx: np.ndarray): + """Evaluate the test function on a set of input values. Parameters ---------- - spatial_dimension : int - The requested number of spatial_dimension. If not specified, - the default is set to 2. - prob_input_selection : str, optional - The selection of a probabilistic input model from a list of - available specifications. This is a keyword only parameter. - name : str, optional - The name of the instance; if not given the default name is used. - This is a keyword only parameter. - rng_seed_prob_input : int, optional - The seed number for the pseudo-random number generator of the - corresponding `ProbInput`; if not given `None` is used - (taken from the system entropy). - This is a keyword only parameter. - """ + xx : np.ndarray + M-Dimensional input values given by an N-by-M array where + N is the number of input values. - _tags = COMMON_METADATA["_tags"] - _available_inputs = COMMON_METADATA["_available_inputs"] - _available_parameters = COMMON_METADATA["_available_parameters"] - _default_spatial_dimension = COMMON_METADATA["_default_spatial_dimension"] - _description = ( - f"Integration test function #4 {COMMON_METADATA['_description']}" - ) + Returns + ------- + np.ndarray + The output of the test function evaluated on the input values. + The output is a 1-dimensional array of length N. + """ - __init__ = _init # type: ignore + num_points, num_dim = xx.shape + yy = np.zeros(num_points) - def evaluate(self, xx: np.ndarray): - """Evaluate the test function on a set of input values. + # Compute the function + for j in range(num_dim): + yy += (-1) ** (j + 1) * np.prod(xx[:, : j + 1], axis=1) - Parameters - ---------- - xx : np.ndarray - M-Dimensional input values given by an N-by-M array where - N is the number of input values. + return yy - Returns - ------- - np.ndarray - The output of the test function evaluated on the input values. - The output is a 1-dimensional array of length N. - """ - num_points, num_dim = xx.shape - yy = np.zeros(num_points) +class Bratley1992d(UQTestFunABC): + """An implementation of the test function 4 from Bratley et al. (1988).""" - # Compute the function - for j in range(num_dim): - yy += (-1) ** (j + 1) * np.prod(xx[:, : j + 1], axis=1) + _tags = COMMON_METADATA["_tags"] + _description = ( + f"Integration test function #4 {COMMON_METADATA['_description']}" + ) + _available_inputs = COMMON_METADATA["_available_inputs"] + _available_parameters = COMMON_METADATA["_available_parameters"] + _default_spatial_dimension = None - return yy + eval_ = staticmethod(evaluate_bratley1992d) diff --git a/src/uqtestfuns/test_functions/damped_oscillator.py b/src/uqtestfuns/test_functions/damped_oscillator.py index 4426090..1ca9999 100644 --- a/src/uqtestfuns/test_functions/damped_oscillator.py +++ b/src/uqtestfuns/test_functions/damped_oscillator.py @@ -32,17 +32,14 @@ """ import numpy as np -from typing import Optional - -from ..core.prob_input.input_spec import MarginalSpec, ProbInputSpec +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecFixDim from ..core.uqtestfun_abc import UQTestFunABC from .utils import lognorm2norm_mean, lognorm2norm_std -from .available import get_prob_input_spec, create_prob_input_from_spec __all__ = ["DampedOscillator"] INPUT_MARGINALS_DERKIUREGHIAN1991 = [ # From [2] - MarginalSpec( + UnivDistSpec( name="Mp", distribution="lognormal", parameters=[ @@ -51,7 +48,7 @@ ], description="Primary mass", ), - MarginalSpec( + UnivDistSpec( name="Ms", distribution="lognormal", parameters=[ @@ -60,7 +57,7 @@ ], description="Secondary mass", ), - MarginalSpec( + UnivDistSpec( name="Kp", distribution="lognormal", parameters=[ @@ -69,7 +66,7 @@ ], description="Primary spring stiffness", ), - MarginalSpec( + UnivDistSpec( name="Ks", distribution="lognormal", parameters=[ @@ -78,7 +75,7 @@ ], description="Secondary spring stiffness", ), - MarginalSpec( + UnivDistSpec( name="Zeta_p", distribution="lognormal", parameters=[ @@ -87,7 +84,7 @@ ], description="Primary damping ratio", ), - MarginalSpec( + UnivDistSpec( name="Zeta_s", distribution="lognormal", parameters=[ @@ -96,7 +93,7 @@ ], description="Secondary damping ratio", ), - MarginalSpec( + UnivDistSpec( name="S0", distribution="lognormal", parameters=[ @@ -108,8 +105,8 @@ ] AVAILABLE_INPUT_SPECS = { - "DerKiureghian1991": ProbInputSpec( - name="Damped-Oscillator-Der-Kiureghian-1991", + "DerKiureghian1991": ProbInputSpecFixDim( + name="DampedOscillator-DerKiureghian1991", description=( "Probabilistic input model for the Damped Oscillator model " "from Der Kiureghian and De Stefano (1991)." @@ -119,68 +116,9 @@ ), } -DEFAULT_INPUT_SELECTION = "DerKiureghian1991" - - -class DampedOscillator(UQTestFunABC): - """A concrete implementation of the Damped oscillator test function.""" - - _tags = ["metamodeling", "sensitivity"] - - _available_inputs = tuple(AVAILABLE_INPUT_SPECS.keys()) - - _available_parameters = None - - _default_spatial_dimension = 8 - - _description = ( - "Damped oscillator model from Igusa and Der Kiureghian (1985)" - ) - def __init__( - self, - *, - prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, - name: Optional[str] = None, - rng_seed_prob_input: Optional[int] = None, - ): - # --- Arguments processing - # Get the ProbInputSpec from available - prob_input_spec = get_prob_input_spec( - prob_input_selection, AVAILABLE_INPUT_SPECS - ) - # Create a ProbInput - prob_input = create_prob_input_from_spec( - prob_input_spec, rng_seed=rng_seed_prob_input - ) - # Process the default name - if name is None: - name = DampedOscillator.__name__ - - super().__init__(prob_input=prob_input, name=name) - - def evaluate(self, xx: np.ndarray): - """Evaluate the damped oscillator model on a set of input values. - - Parameters - ---------- - xx : np.ndarray - A 7-dimensional input values given by an N-by-7 array - where N is the number of input values. - - Returns - ------- - np.ndarray - The output of the damped oscillator model test function, i.e., - the relative displacement of the secondary spring. - """ - yy = evaluate_mean_square_displacement(xx) - - return np.sqrt(yy) - - -def evaluate_mean_square_displacement(xx: np.ndarray): - """Evaluate the mean-square displacement of the damped oscillator model. +def evaluate(xx: np.ndarray) -> np.ndarray: + """Evaluate the rms displacement of the damped oscillator model. Parameters ---------- @@ -231,4 +169,18 @@ def evaluate_mean_square_displacement(xx: np.ndarray): # NOTE: This is squared displacement xx_s = first_term * second_term * third_term - return xx_s + return np.sqrt(xx_s) + + +class DampedOscillator(UQTestFunABC): + """A concrete implementation of the Damped oscillator test function.""" + + _tags = ["metamodeling", "sensitivity"] + _description = ( + "Damped oscillator model from Igusa and Der Kiureghian (1985)" + ) + _available_inputs = AVAILABLE_INPUT_SPECS + _available_parameters = None + _default_spatial_dimension = 8 + + eval_ = staticmethod(evaluate) diff --git a/src/uqtestfuns/test_functions/flood.py b/src/uqtestfuns/test_functions/flood.py index 7a7506e..630e46c 100644 --- a/src/uqtestfuns/test_functions/flood.py +++ b/src/uqtestfuns/test_functions/flood.py @@ -32,58 +32,55 @@ """ import numpy as np -from typing import Optional - -from ..core.prob_input.input_spec import MarginalSpec, ProbInputSpec +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecFixDim from ..core.uqtestfun_abc import UQTestFunABC -from .available import get_prob_input_spec, create_prob_input_from_spec __all__ = ["Flood"] INPUT_MARGINALS_IOOSS2015 = [ # From Ref. [1] - MarginalSpec( + UnivDistSpec( name="Q", distribution="trunc-gumbel", parameters=[1013.0, 558.0, 500.0, 3000.0], description="Maximum annual flow rate [m^3/s]", ), - MarginalSpec( + UnivDistSpec( name="Ks", distribution="trunc-normal", parameters=[30.0, 8.0, 15.0, np.inf], description="Strickler coefficient [m^(1/3)/s]", ), - MarginalSpec( + UnivDistSpec( name="Zv", distribution="triangular", parameters=[49.0, 51.0, 50.0], description="River downstream level [m]", ), - MarginalSpec( + UnivDistSpec( name="Zm", distribution="triangular", parameters=[54.0, 56.0, 55.0], description="River upstream level [m]", ), - MarginalSpec( + UnivDistSpec( name="Hd", distribution="uniform", parameters=[7.0, 9.0], description="Dyke height [m]", ), - MarginalSpec( + UnivDistSpec( name="Cb", distribution="triangular", parameters=[55.0, 56.0, 55.5], description="Bank level [m]", ), - MarginalSpec( + UnivDistSpec( name="L", distribution="triangular", parameters=[4990.0, 5010.0, 5000.0], description="Length of the river stretch [m]", ), - MarginalSpec( + UnivDistSpec( name="B", distribution="triangular", parameters=[295.0, 305.0, 300.0], @@ -92,8 +89,8 @@ ] AVAILABLE_INPUT_SPECS = { - "Iooss2015": ProbInputSpec( - name="Flood-Iooss-2015", + "Iooss2015": ProbInputSpecFixDim( + name="Flood-Iooss2015", description=( "Probabilistic input model for the Flood model " "from Iooss and Lemaître (2015)." @@ -103,75 +100,50 @@ ), } -DEFAULT_INPUT_SELECTION = "Iooss2015" +def evaluate(xx: np.ndarray) -> np.ndarray: + """Evaluate the flood model test function on a set of input values. -class Flood(UQTestFunABC): - """Concrete implementation of the Flood model test function.""" + Parameters + ---------- + xx : np.ndarray + A six-dimensional input values given by an N-by-8 array + where N is the number of input values. - _tags = ["metamodeling", "sensitivity"] + Returns + ------- + np.ndarray + The output of the flood model test function, i.e., + the height of a river. + The output is a one-dimensional array of length N. + """ + qq = xx[:, 0] # Maximum annual flow rate + kk_s = xx[:, 1] # Strickler coefficient + zz_v = xx[:, 2] # River downstream level + zz_m = xx[:, 3] # River upstream level + hh_d = xx[:, 4] # Dyke height + cc_b = xx[:, 5] # Bank level + ll = xx[:, 6] # Length of the river stretch + bb = xx[:, 7] # River width - _available_inputs = tuple(AVAILABLE_INPUT_SPECS.keys()) + # Compute the maximum annual height of the river [m] + hh_w = (qq / (bb * kk_s * np.sqrt((zz_m - zz_v) / ll))) ** 0.6 - _available_parameters = None + # Compute the maximum annual underflow [m] + # NOTE: The sign compared to [1] has been inverted below, a negative + # value indicates an overflow + ss = cc_b + hh_d - zz_v - hh_w + + return ss - _default_spatial_dimension = 8 +class Flood(UQTestFunABC): + """Concrete implementation of the Flood model test function.""" + + _tags = ["metamodeling", "sensitivity"] _description = "Flood model from Iooss and Lemaître (2015)" + _available_inputs = AVAILABLE_INPUT_SPECS + _available_parameters = None + _default_spatial_dimension = 8 - def __init__( - self, - *, - prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, - name: Optional[str] = None, - rng_seed_prob_input: Optional[int] = None, - ): - # --- Arguments processing - # Get the ProbInputSpec from available - prob_input_spec = get_prob_input_spec( - prob_input_selection, AVAILABLE_INPUT_SPECS - ) - # Create a ProbInput - prob_input = create_prob_input_from_spec( - prob_input_spec, rng_seed=rng_seed_prob_input - ) - # Process the default name - if name is None: - name = Flood.__name__ - - super().__init__(prob_input=prob_input, name=name) - - def evaluate(self, xx): - """Evaluate the flood model test function on a set of input values. - - Parameters - ---------- - xx : np.ndarray - A six-dimensional input values given by an N-by-8 array - where N is the number of input values. - - Returns - ------- - np.ndarray - The output of the flood model test function, i.e., - the height of a river. - The output is a one-dimensional array of length N. - """ - qq = xx[:, 0] # Maximum annual flow rate - kk_s = xx[:, 1] # Strickler coefficient - zz_v = xx[:, 2] # River downstream level - zz_m = xx[:, 3] # River upstream level - hh_d = xx[:, 4] # Dyke height - cc_b = xx[:, 5] # Bank level - ll = xx[:, 6] # Length of the river stretch - bb = xx[:, 7] # River width - - # Compute the maximum annual height of the river [m] - hh_w = (qq / (bb * kk_s * np.sqrt((zz_m - zz_v) / ll))) ** 0.6 - - # Compute the maximum annual underflow [m] - # NOTE: The sign compared to [1] has been inverted below, a negative - # value indicates an overflow - ss = cc_b + hh_d - zz_v - hh_w - - return ss + eval_ = staticmethod(evaluate) diff --git a/src/uqtestfuns/test_functions/franke.py b/src/uqtestfuns/test_functions/franke.py index b638bb6..c88a2e8 100644 --- a/src/uqtestfuns/test_functions/franke.py +++ b/src/uqtestfuns/test_functions/franke.py @@ -41,22 +41,19 @@ """ import numpy as np -from typing import Optional - -from ..core.prob_input.input_spec import MarginalSpec, ProbInputSpec +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecFixDim from ..core.uqtestfun_abc import UQTestFunABC -from .available import get_prob_input_spec, create_prob_input_from_spec __all__ = ["Franke1", "Franke2", "Franke3", "Franke4", "Franke5", "Franke6"] INPUT_MARGINALS_FRANKE1979 = [ # From Ref. [1] - MarginalSpec( + UnivDistSpec( name="X1", distribution="uniform", parameters=[0.0, 1.0], description="None", ), - MarginalSpec( + UnivDistSpec( name="X2", distribution="uniform", parameters=[0.0, 1.0], @@ -65,11 +62,10 @@ ] AVAILABLE_INPUT_SPECS = { - "Franke1979": ProbInputSpec( - name="Franke-1979", + "Franke1979": ProbInputSpecFixDim( + name="Franke1979", description=( - "Input specification for the Franke's test functions " - "from Franke (1979)." + "Input specification for the test functions from Franke (1979)." ), marginals=INPUT_MARGINALS_FRANKE1979, copulas=None, @@ -80,35 +76,44 @@ COMMON_METADATA = dict( _tags=["metamodeling"], - _available_inputs=tuple(AVAILABLE_INPUT_SPECS.keys()), + _available_inputs=AVAILABLE_INPUT_SPECS, _available_parameters=None, _default_spatial_dimension=2, _description="from Franke (1979)", ) -def _init( - self, - *, - prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, - name: Optional[str] = None, - rng_seed_prob_input: Optional[int] = None, -) -> None: - """A common __init__ for all Franke's test functions.""" - # --- Arguments processing - # Get the ProbInputSpec from available - prob_input_spec = get_prob_input_spec( - prob_input_selection, AVAILABLE_INPUT_SPECS - ) - # Create a ProbInput - prob_input = create_prob_input_from_spec( - prob_input_spec, rng_seed=rng_seed_prob_input +def evaluate_franke1(xx: np.ndarray): + """Evaluate the (1st) Franke function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + Two-Dimensional input values given by N-by-2 arrays where + N is the number of input values. + + Returns + ------- + np.ndarray + The output of the (1st) Franke function evaluated + on the input values. + The output is a 1-dimensional array of length N. + """ + + xx0 = 9 * xx[:, 0] + xx1 = 9 * xx[:, 1] + + # Compute the (first) Franke function + term_1 = 0.75 * np.exp(-0.25 * ((xx0 - 2) ** 2 + (xx1 - 2) ** 2)) + term_2 = 0.75 * np.exp( + -1.00 * ((xx0 + 1) ** 2 / 49.0 + (xx1 + 1) ** 2 / 10.0) ) - # Process the default name - if name is None: - name = self.__class__.__name__ + term_3 = 0.50 * np.exp(-0.25 * ((xx0 - 7) ** 2 + (xx1 - 3) ** 2)) + term_4 = 0.20 * np.exp(-1.00 * ((xx0 - 4) ** 2 + (xx1 - 7) ** 2)) - UQTestFunABC.__init__(self, prob_input=prob_input, name=name) + yy = term_1 + term_2 + term_3 - term_4 + + return yy class Franke1(UQTestFunABC): @@ -118,44 +123,33 @@ class Franke1(UQTestFunABC): """ _tags = COMMON_METADATA["_tags"] + _description = f"(1st) Franke function {COMMON_METADATA['_description']}" _available_inputs = COMMON_METADATA["_available_inputs"] _available_parameters = COMMON_METADATA["_available_parameters"] _default_spatial_dimension = COMMON_METADATA["_default_spatial_dimension"] - _description = f"(1st) Franke function {COMMON_METADATA['_description']}" - - __init__ = _init # type: ignore - def evaluate(self, xx: np.ndarray): - """Evaluate the (1st) Franke function on a set of input values. + eval_ = staticmethod(evaluate_franke1) - Parameters - ---------- - xx : np.ndarray - Two-Dimensional input values given by N-by-2 arrays where - N is the number of input values. - Returns - ------- - np.ndarray - The output of the (1st) Franke function evaluated - on the input values. - The output is a 1-dimensional array of length N. - """ +def evaluate_franke2(xx: np.ndarray): + """Evaluate the (2nd) Franke function on a set of input values. - xx0 = 9 * xx[:, 0] - xx1 = 9 * xx[:, 1] + Parameters + ---------- + xx : np.ndarray + Two-Dimensional input values given by N-by-2 arrays where + N is the number of input values. - # Compute the (first) Franke function - term_1 = 0.75 * np.exp(-0.25 * ((xx0 - 2) ** 2 + (xx1 - 2) ** 2)) - term_2 = 0.75 * np.exp( - -1.00 * ((xx0 + 1) ** 2 / 49.0 + (xx1 + 1) ** 2 / 10.0) - ) - term_3 = 0.50 * np.exp(-0.25 * ((xx0 - 7) ** 2 + (xx1 - 3) ** 2)) - term_4 = 0.20 * np.exp(-1.00 * ((xx0 - 4) ** 2 + (xx1 - 7) ** 2)) - - yy = term_1 + term_2 + term_3 - term_4 + Returns + ------- + np.ndarray + The output of the (2nd) Franke function evaluated + on the input values. + The output is a 1-dimensional array of length N. + """ + yy = (np.tanh(9 * (xx[:, 1] - xx[:, 0])) + 1) / 9.0 - return yy + return yy class Franke2(UQTestFunABC): @@ -165,32 +159,36 @@ class Franke2(UQTestFunABC): """ _tags = COMMON_METADATA["_tags"] + _description = f"(2nd) Franke function {COMMON_METADATA['_description']}" _available_inputs = COMMON_METADATA["_available_inputs"] _available_parameters = COMMON_METADATA["_available_parameters"] _default_spatial_dimension = COMMON_METADATA["_default_spatial_dimension"] - _description = f"(2nd) Franke function {COMMON_METADATA['_description']}" - __init__ = _init # type: ignore + eval_ = staticmethod(evaluate_franke2) + + +def evaluate_franke3(xx: np.ndarray): + """Evaluate the (3rd) Franke function on a set of input values. - def evaluate(self, xx: np.ndarray): - """Evaluate the (2nd) Franke function on a set of input values. + Parameters + ---------- + xx : np.ndarray + Two-Dimensional input values given by N-by-2 arrays where + N is the number of input values. - Parameters - ---------- - xx : np.ndarray - Two-Dimensional input values given by N-by-2 arrays where - N is the number of input values. + Returns + ------- + np.ndarray + The output of the (3rd) Franke function evaluated + on the input values. + The output is a 1-dimensional array of length N. + """ + term_1 = 1.25 + np.cos(5.4 * xx[:, 1]) + term_2 = 6 * (1 + (3 * xx[:, 0] - 1) ** 2) - Returns - ------- - np.ndarray - The output of the (2nd) Franke function evaluated - on the input values. - The output is a 1-dimensional array of length N. - """ - yy = (np.tanh(9 * (xx[:, 1] - xx[:, 0])) + 1) / 9.0 + yy = term_1 / term_2 - return yy + return yy class Franke3(UQTestFunABC): @@ -200,35 +198,36 @@ class Franke3(UQTestFunABC): """ _tags = COMMON_METADATA["_tags"] + _description = f"(3rd) Franke function {COMMON_METADATA['_description']}" _available_inputs = COMMON_METADATA["_available_inputs"] _available_parameters = COMMON_METADATA["_available_parameters"] _default_spatial_dimension = COMMON_METADATA["_default_spatial_dimension"] - _description = f"(3rd) Franke function {COMMON_METADATA['_description']}" - __init__ = _init # type: ignore + eval_ = staticmethod(evaluate_franke3) - def evaluate(self, xx: np.ndarray): - """Evaluate the (3rd) Franke function on a set of input values. - Parameters - ---------- - xx : np.ndarray - Two-Dimensional input values given by N-by-2 arrays where - N is the number of input values. +def evaluate_franke4(xx: np.ndarray): + """Evaluate the (4th) Franke function on a set of input values. - Returns - ------- - np.ndarray - The output of the (3rd) Franke function evaluated - on the input values. - The output is a 1-dimensional array of length N. - """ - term_1 = 1.25 + np.cos(5.4 * xx[:, 1]) - term_2 = 6 * (1 + (3 * xx[:, 0] - 1) ** 2) + Parameters + ---------- + xx : np.ndarray + Two-Dimensional input values given by N-by-2 arrays where + N is the number of input values. - yy = term_1 / term_2 + Returns + ------- + np.ndarray + The output of the (4th) Franke function evaluated + on the input values. + The output is a 1-dimensional array of length N. + """ + yy = ( + np.exp(-81.0 / 16.0 * ((xx[:, 0] - 0.5) ** 2 + (xx[:, 1] - 0.5) ** 2)) + / 3.0 + ) - return yy + return yy class Franke4(UQTestFunABC): @@ -238,37 +237,36 @@ class Franke4(UQTestFunABC): """ _tags = COMMON_METADATA["_tags"] + _description = f"(4th) Franke function {COMMON_METADATA['_description']}" _available_inputs = COMMON_METADATA["_available_inputs"] _available_parameters = COMMON_METADATA["_available_parameters"] _default_spatial_dimension = COMMON_METADATA["_default_spatial_dimension"] - _description = f"(4th) Franke function {COMMON_METADATA['_description']}" - __init__ = _init # type: ignore + eval_ = staticmethod(evaluate_franke4) - def evaluate(self, xx: np.ndarray): - """Evaluate the (4th) Franke function on a set of input values. - Parameters - ---------- - xx : np.ndarray - Two-Dimensional input values given by N-by-2 arrays where - N is the number of input values. +def evaluate_franke5(xx: np.ndarray): + """Evaluate the (5th) Franke function on a set of input values. - Returns - ------- - np.ndarray - The output of the (4th) Franke function evaluated - on the input values. - The output is a 1-dimensional array of length N. - """ - yy = ( - np.exp( - -81.0 / 16.0 * ((xx[:, 0] - 0.5) ** 2 + (xx[:, 1] - 0.5) ** 2) - ) - / 3.0 - ) + Parameters + ---------- + xx : np.ndarray + Two-Dimensional input values given by N-by-2 arrays where + N is the number of input values. - return yy + Returns + ------- + np.ndarray + The output of the (5th) Franke function evaluated + on the input values. + The output is a 1-dimensional array of length N. + """ + yy = ( + np.exp(-81.0 / 4.0 * ((xx[:, 0] - 0.5) ** 2 + (xx[:, 1] - 0.5) ** 2)) + / 3.0 + ) + + return yy class Franke5(UQTestFunABC): @@ -278,37 +276,37 @@ class Franke5(UQTestFunABC): """ _tags = COMMON_METADATA["_tags"] + _description = f"(5th) Franke function {COMMON_METADATA['_description']}" _available_inputs = COMMON_METADATA["_available_inputs"] _available_parameters = COMMON_METADATA["_available_parameters"] _default_spatial_dimension = COMMON_METADATA["_default_spatial_dimension"] - _description = f"(5th) Franke function {COMMON_METADATA['_description']}" - __init__ = _init # type: ignore + eval_ = staticmethod(evaluate_franke5) + - def evaluate(self, xx: np.ndarray): - """Evaluate the (5th) Franke function on a set of input values. +def evaluate_franke6(xx: np.ndarray): + """Evaluate the (6th) Franke function on a set of input values. - Parameters - ---------- - xx : np.ndarray - Two-Dimensional input values given by N-by-2 arrays where - N is the number of input values. + Parameters + ---------- + xx : np.ndarray + Two-Dimensional input values given by N-by-2 arrays where + N is the number of input values. - Returns - ------- - np.ndarray - The output of the (5th) Franke function evaluated - on the input values. - The output is a 1-dimensional array of length N. - """ - yy = ( - np.exp( - -81.0 / 4.0 * ((xx[:, 0] - 0.5) ** 2 + (xx[:, 1] - 0.5) ** 2) - ) - / 3.0 - ) + Returns + ------- + np.ndarray + The output of the (6th) Franke function evaluated + on the input values. + The output is a 1-dimensional array of length N. + """ + yy = ( + np.sqrt(64 - 81 * ((xx[:, 0] - 0.5) ** 2 + (xx[:, 1] - 0.5) ** 2)) + / 9.0 + - 0.5 + ) - return yy + return yy class Franke6(UQTestFunABC): @@ -318,33 +316,9 @@ class Franke6(UQTestFunABC): """ _tags = COMMON_METADATA["_tags"] + _description = f"(6th) Franke function {COMMON_METADATA['_description']}" _available_inputs = COMMON_METADATA["_available_inputs"] _available_parameters = COMMON_METADATA["_available_parameters"] _default_spatial_dimension = COMMON_METADATA["_default_spatial_dimension"] - _description = f"(6th) Franke function {COMMON_METADATA['_description']}" - __init__ = _init # type: ignore - - def evaluate(self, xx: np.ndarray): - """Evaluate the (6th) Franke function on a set of input values. - - Parameters - ---------- - xx : np.ndarray - Two-Dimensional input values given by N-by-2 arrays where - N is the number of input values. - - Returns - ------- - np.ndarray - The output of the (6th) Franke function evaluated - on the input values. - The output is a 1-dimensional array of length N. - """ - yy = ( - np.sqrt(64 - 81 * ((xx[:, 0] - 0.5) ** 2 + (xx[:, 1] - 0.5) ** 2)) - / 9.0 - - 0.5 - ) - - return yy + eval_ = staticmethod(evaluate_franke6) diff --git a/src/uqtestfuns/test_functions/ishigami.py b/src/uqtestfuns/test_functions/ishigami.py index 77680d8..b514c05 100644 --- a/src/uqtestfuns/test_functions/ishigami.py +++ b/src/uqtestfuns/test_functions/ishigami.py @@ -29,33 +29,28 @@ """ import numpy as np -from typing import Optional +from typing import Tuple -from ..core.prob_input.input_spec import MarginalSpec, ProbInputSpec +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecFixDim from ..core.uqtestfun_abc import UQTestFunABC -from .available import ( - get_prob_input_spec, - create_prob_input_from_spec, - create_parameters_from_available, -) __all__ = ["Ishigami"] INPUT_MARGINALS_ISHIGAMI1991 = [ - MarginalSpec( + UnivDistSpec( name="X1", distribution="uniform", parameters=[-np.pi, np.pi], description="None", ), - MarginalSpec( + UnivDistSpec( name="X2", distribution="uniform", parameters=[-np.pi, np.pi], description="None", ), - MarginalSpec( + UnivDistSpec( name="X3", distribution="uniform", parameters=[-np.pi, np.pi], @@ -64,8 +59,8 @@ ] AVAILABLE_INPUT_SPECS = { - "Ishigami1991": ProbInputSpec( - name="Ishigami-1991", + "Ishigami1991": ProbInputSpecFixDim( + name="Ishigami1991", description=( "Probabilistic input model for the Ishigami function " "from Ishigami and Homma (1991)." @@ -75,8 +70,6 @@ ), } -DEFAULT_INPUT_SELECTION = "Ishigami1991" - AVAILABLE_PARAMETERS = { "Ishigami1991": (7, 0.05), # from [1] "Sobol1999": (7, 0.1), # from [2] @@ -85,71 +78,41 @@ DEFAULT_PARAMETERS_SELECTION = "Ishigami1991" -class Ishigami(UQTestFunABC): - """A concrete implementation of the Ishigami function.""" +def evaluate(xx: np.ndarray, parameters: Tuple[float, float]): + """Evaluate the Ishigami function on a set of input values. - _tags = ["sensitivity"] + Parameters + ---------- + xx : np.ndarray + 3-Dimensional input values given by N-by-3 arrays where + N is the number of input values. + parameters : Tuple[float, float] + Tuple of two values as the parameters of the function. - _available_inputs = tuple(AVAILABLE_INPUT_SPECS.keys()) + Returns + ------- + np.ndarray + The output of the Ishigami function evaluated on the input values. + The output is a 1-dimensional array of length N. + """ + # Compute the Ishigami function + term_1 = np.sin(xx[:, 0]) + term_2 = parameters[0] * np.sin(xx[:, 1]) ** 2 + term_3 = parameters[1] * xx[:, 2] ** 4 * np.sin(xx[:, 0]) - _available_parameters = tuple(AVAILABLE_PARAMETERS.keys()) + yy = term_1 + term_2 + term_3 + + return yy - _default_spatial_dimension = 3 +class Ishigami(UQTestFunABC): + """An implementation of the Ishigami test function.""" + + _tags = ["sensitivity"] _description = "Ishigami function from Ishigami and Homma (1991)" + _available_inputs = AVAILABLE_INPUT_SPECS + _available_parameters = AVAILABLE_PARAMETERS + _default_parameters = DEFAULT_PARAMETERS_SELECTION + _default_spatial_dimension = 3 - def __init__( - self, - *, - prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, - parameters_selection: Optional[str] = DEFAULT_PARAMETERS_SELECTION, - name: Optional[str] = None, - rng_seed_prob_input: Optional[int] = None, - ): - # --- Arguments processing - # Get the ProbInputSpec from available - prob_input_spec = get_prob_input_spec( - prob_input_selection, AVAILABLE_INPUT_SPECS - ) - # Create a ProbInput - prob_input = create_prob_input_from_spec( - prob_input_spec, rng_seed=rng_seed_prob_input - ) - # Ishigami supports several different parameterizations - parameters = create_parameters_from_available( - parameters_selection, AVAILABLE_PARAMETERS - ) - # Process the default name - if name is None: - name = Ishigami.__name__ - - super().__init__( - prob_input=prob_input, parameters=parameters, name=name - ) - - def evaluate(self, xx): - """Evaluate the Ishigami function on a set of input values. - - Parameters - ---------- - xx : np.ndarray - 3-Dimensional input values given by N-by-3 arrays where - N is the number of input values. - params : tuple - Tuple of two values as the parameters of the function. - - Returns - ------- - np.ndarray - The output of the Ishigami function evaluated on the input values. - The output is a 1-dimensional array of length N. - """ - # Compute the Ishigami function - parameters = self.parameters - term_1 = np.sin(xx[:, 0]) - term_2 = parameters[0] * np.sin(xx[:, 1]) ** 2 - term_3 = parameters[1] * xx[:, 2] ** 4 * np.sin(xx[:, 0]) - - yy = term_1 + term_2 + term_3 - - return yy + eval_ = staticmethod(evaluate) diff --git a/src/uqtestfuns/test_functions/mclain.py b/src/uqtestfuns/test_functions/mclain.py index aa1b389..b99bdd4 100644 --- a/src/uqtestfuns/test_functions/mclain.py +++ b/src/uqtestfuns/test_functions/mclain.py @@ -29,22 +29,19 @@ """ import numpy as np -from typing import Optional - -from ..core.prob_input.input_spec import MarginalSpec, ProbInputSpec +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecFixDim from ..core.uqtestfun_abc import UQTestFunABC -from .available import get_prob_input_spec, create_prob_input_from_spec __all__ = ["McLainS1", "McLainS2", "McLainS3", "McLainS4", "McLainS5"] INPUT_MARGINALS_MCLAIN1974 = [ # From Ref. [1] - MarginalSpec( + UnivDistSpec( name="X1", distribution="uniform", parameters=[1.0, 10.0], description="None", ), - MarginalSpec( + UnivDistSpec( name="X2", distribution="uniform", parameters=[1.0, 10.0], @@ -53,7 +50,7 @@ ] AVAILABLE_INPUT_SPECS = { - "McLain1974": ProbInputSpec( + "McLain1974": ProbInputSpecFixDim( name="McLain-1974", description=( "Input specification for the McLain's test functions " @@ -64,39 +61,34 @@ ), } -DEFAULT_INPUT_SELECTION = "McLain1974" - COMMON_METADATA = dict( _tags=["metamodeling"], - _available_inputs=tuple(AVAILABLE_INPUT_SPECS.keys()), + _available_inputs=AVAILABLE_INPUT_SPECS, _available_parameters=None, _default_spatial_dimension=2, _description="from McLain (1974)", ) -def _init( - self, - *, - prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, - name: Optional[str] = None, - rng_seed_prob_input: Optional[int] = None, -) -> None: - """A common __init__ for all McLain's test functions.""" - # --- Arguments processing - # Get the ProbInputSpec from available - prob_input_spec = get_prob_input_spec( - prob_input_selection, AVAILABLE_INPUT_SPECS - ) - # Create a ProbInput - prob_input = create_prob_input_from_spec( - prob_input_spec, rng_seed=rng_seed_prob_input - ) - # Process the default name - if name is None: - name = self.__class__.__name__ +def evaluate_mclain_s1(xx: np.ndarray) -> np.ndarray: + """Evaluate the McLain S1 function on a set of input values. - UQTestFunABC.__init__(self, prob_input=prob_input, name=name) + Parameters + ---------- + xx : np.ndarray + Two-Dimensional input values given by N-by-2 arrays where + N is the number of input values. + + Returns + ------- + np.ndarray + The output of the McLain S1 function evaluated + on the input values. + The output is a 1-dimensional array of length N. + """ + yy = np.sqrt(64 - (xx[:, 0] - 5.5) ** 2 - (xx[:, 1] - 5.5) ** 2) + + return yy class McLainS1(UQTestFunABC): @@ -106,32 +98,32 @@ class McLainS1(UQTestFunABC): """ _tags = COMMON_METADATA["_tags"] + _description = f"McLain S1 function {COMMON_METADATA['_description']}" _available_inputs = COMMON_METADATA["_available_inputs"] _available_parameters = COMMON_METADATA["_available_parameters"] _default_spatial_dimension = COMMON_METADATA["_default_spatial_dimension"] - _description = f"McLain S1 function {COMMON_METADATA['_description']}" - __init__ = _init # type: ignore + eval_ = staticmethod(evaluate_mclain_s1) + - def evaluate(self, xx: np.ndarray): - """Evaluate the McLain S1 function on a set of input values. +def evaluate_mclain_s2(xx: np.ndarray) -> np.ndarray: + """Evaluate the McLain S2 function on a set of input values. - Parameters - ---------- - xx : np.ndarray - Two-Dimensional input values given by N-by-2 arrays where - N is the number of input values. + Parameters + ---------- + xx : np.ndarray + Two-Dimensional input values given by N-by-2 arrays where + N is the number of input values. - Returns - ------- - np.ndarray - The output of the McLain S1 function evaluated - on the input values. - The output is a 1-dimensional array of length N. - """ - yy = np.sqrt(64 - (xx[:, 0] - 5.5) ** 2 - (xx[:, 1] - 5.5) ** 2) + Returns + ------- + np.ndarray + The output of the McLain S2 function evaluated on the input values. + The output is a 1-dimensional array of length N. + """ + yy = np.exp(-1.0 * ((xx[:, 0] - 5) ** 2 + (xx[:, 1] - 5) ** 2)) - return yy + return yy class McLainS2(UQTestFunABC): @@ -141,31 +133,32 @@ class McLainS2(UQTestFunABC): """ _tags = COMMON_METADATA["_tags"] + _description = f"McLain S2 function {COMMON_METADATA['_description']}" _available_inputs = COMMON_METADATA["_available_inputs"] _available_parameters = COMMON_METADATA["_available_parameters"] _default_spatial_dimension = COMMON_METADATA["_default_spatial_dimension"] - _description = f"McLain S2 function {COMMON_METADATA['_description']}" - __init__ = _init # type: ignore + eval_ = staticmethod(evaluate_mclain_s2) + - def evaluate(self, xx: np.ndarray): - """Evaluate the McLain S2 function on a set of input values. +def evaluate_mclain_s3(xx: np.ndarray) -> np.ndarray: + """Evaluate the McLain S3 function on a set of input values. - Parameters - ---------- - xx : np.ndarray - Two-Dimensional input values given by N-by-2 arrays where - N is the number of input values. + Parameters + ---------- + xx : np.ndarray + Two-Dimensional input values given by N-by-2 arrays where + N is the number of input values. - Returns - ------- - np.ndarray - The output of the McLain S2 function evaluated on the input values. - The output is a 1-dimensional array of length N. - """ - yy = np.exp(-1.0 * ((xx[:, 0] - 5) ** 2 + (xx[:, 1] - 5) ** 2)) + Returns + ------- + np.ndarray + The output of the McLain S3 function evaluated on the input values. + The output is a 1-dimensional array of length N. + """ + yy = np.exp(-0.25 * ((xx[:, 0] - 5) ** 2 + (xx[:, 1] - 5) ** 2)) - return yy + return yy class McLainS3(UQTestFunABC): @@ -175,31 +168,35 @@ class McLainS3(UQTestFunABC): """ _tags = COMMON_METADATA["_tags"] + _description = f"McLain S3 function {COMMON_METADATA['_description']}" _available_inputs = COMMON_METADATA["_available_inputs"] _available_parameters = COMMON_METADATA["_available_parameters"] _default_spatial_dimension = COMMON_METADATA["_default_spatial_dimension"] - _description = f"McLain S3 function {COMMON_METADATA['_description']}" - __init__ = _init # type: ignore + eval_ = staticmethod(evaluate_mclain_s3) - def evaluate(self, xx: np.ndarray): - """Evaluate the McLain S3 function on a set of input values. - Parameters - ---------- - xx : np.ndarray - Two-Dimensional input values given by N-by-2 arrays where - N is the number of input values. +def evaluate_mclain_s4(xx: np.ndarray) -> np.ndarray: + """Evaluate the McLain S4 function on a set of input values. - Returns - ------- - np.ndarray - The output of the McLain S3 function evaluated on the input values. - The output is a 1-dimensional array of length N. - """ - yy = np.exp(-0.25 * ((xx[:, 0] - 5) ** 2 + (xx[:, 1] - 5) ** 2)) + Parameters + ---------- + xx : np.ndarray + Two-Dimensional input values given by N-by-2 arrays where + N is the number of input values. + + Returns + ------- + np.ndarray + The output of the McLain S4 function evaluated on the input values. + The output is a 1-dimensional array of length N. + """ + yy = np.exp( + -1 + * ((xx[:, 0] + xx[:, 1] - 11) ** 2 + (xx[:, 0] - xx[:, 1]) ** 2 / 10.0) + ) - return yy + return yy class McLainS4(UQTestFunABC): @@ -209,37 +206,32 @@ class McLainS4(UQTestFunABC): """ _tags = COMMON_METADATA["_tags"] + _description = f"McLain S4 function {COMMON_METADATA['_description']}" _available_inputs = COMMON_METADATA["_available_inputs"] _available_parameters = COMMON_METADATA["_available_parameters"] _default_spatial_dimension = COMMON_METADATA["_default_spatial_dimension"] - _description = f"McLain S4 function {COMMON_METADATA['_description']}" - __init__ = _init # type: ignore + eval_ = staticmethod(evaluate_mclain_s4) - def evaluate(self, xx: np.ndarray): - """Evaluate the McLain S4 function on a set of input values. - Parameters - ---------- - xx : np.ndarray - Two-Dimensional input values given by N-by-2 arrays where - N is the number of input values. +def evaluate_mclain_s5(xx: np.ndarray) -> np.ndarray: + """Evaluate the McLain S5 function on a set of input values. - Returns - ------- - np.ndarray - The output of the McLain S4 function evaluated on the input values. - The output is a 1-dimensional array of length N. - """ - yy = np.exp( - -1 - * ( - (xx[:, 0] + xx[:, 1] - 11) ** 2 - + (xx[:, 0] - xx[:, 1]) ** 2 / 10.0 - ) - ) + Parameters + ---------- + xx : np.ndarray + Two-Dimensional input values given by N-by-2 arrays where + N is the number of input values. - return yy + Returns + ------- + np.ndarray + The output of the McLain S5 function evaluated on the input values. + The output is a 1-dimensional array of length N. + """ + yy = np.tanh(xx[:, 0] + xx[:, 1] - 11) + + return yy class McLainS5(UQTestFunABC): @@ -249,28 +241,9 @@ class McLainS5(UQTestFunABC): """ _tags = COMMON_METADATA["_tags"] + _description = f"McLain S5 function {COMMON_METADATA['_description']}" _available_inputs = COMMON_METADATA["_available_inputs"] _available_parameters = COMMON_METADATA["_available_parameters"] _default_spatial_dimension = COMMON_METADATA["_default_spatial_dimension"] - _description = f"McLain S5 function {COMMON_METADATA['_description']}" - - __init__ = _init # type: ignore - - def evaluate(self, xx: np.ndarray): - """Evaluate the McLain S5 function on a set of input values. - - Parameters - ---------- - xx : np.ndarray - Two-Dimensional input values given by N-by-2 arrays where - N is the number of input values. - - Returns - ------- - np.ndarray - The output of the McLain S5 function evaluated on the input values. - The output is a 1-dimensional array of length N. - """ - yy = np.tanh(xx[:, 0] + xx[:, 1] - 11) - return yy + eval_ = staticmethod(evaluate_mclain_s5) diff --git a/src/uqtestfuns/test_functions/oakley_ohagan_1d.py b/src/uqtestfuns/test_functions/oakley_ohagan_1d.py index dcc4a40..05e83f2 100644 --- a/src/uqtestfuns/test_functions/oakley_ohagan_1d.py +++ b/src/uqtestfuns/test_functions/oakley_ohagan_1d.py @@ -15,89 +15,59 @@ """ import numpy as np -from typing import Optional - -from ..core.prob_input.input_spec import MarginalSpec, ProbInputSpec +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecFixDim from ..core.uqtestfun_abc import UQTestFunABC -from .available import get_prob_input_spec, create_prob_input_from_spec __all__ = ["OakleyOHagan1D"] -INPUT_MARGINALS_OAKLEY2002 = [ - MarginalSpec( - name="x", - distribution="normal", - parameters=[0.0, 4.0], - description="None", - ), -] - AVAILABLE_INPUT_SPECS = { - "Oakley2002": ProbInputSpec( - name="Oakley-OHagan-2002", + "Oakley2002": ProbInputSpecFixDim( + name="Oakley2002", description=( "Probabilistic input model for the one-dimensional function " - "from Oakley-O'Hagan function (2002)" + "from Oakley and O'Hagan (2002)" ), - marginals=INPUT_MARGINALS_OAKLEY2002, + marginals=[ + UnivDistSpec( + name="x", + distribution="normal", + parameters=[0.0, 4.0], + description="None", + ) + ], copulas=None, ), } -DEFAULT_INPUT_SELECTION = "Oakley2002" +def evaluate(xx: np.ndarray) -> np.ndarray: + """Evaluate the 1D Oakley-O'Hagan function on a set of input values. -class OakleyOHagan1D(UQTestFunABC): - """A concrete implementation of the 1D Oakley-O'Hagan test function.""" + Parameters + ---------- + xx : np.ndarray + 1-Dimensional input values given by an N-by-1 array + where N is the number of input values. - _tags = ["metamodeling"] + Returns + ------- + np.ndarray + The output of the 1D Oakley-O'Hagan function evaluated + on the input values. + The output is a 1-dimensional array of length N. + """ + yy = 5 + xx + np.cos(xx) - _available_inputs = tuple(AVAILABLE_INPUT_SPECS.keys()) + return yy - _available_parameters = None - _default_spatial_dimension = 1 +class OakleyOHagan1D(UQTestFunABC): + """A concrete implementation of the 1D Oakley-O'Hagan test function.""" + _tags = ["metamodeling"] _description = "One-dimensional function from Oakley and O'Hagan (2002)" + _available_inputs = AVAILABLE_INPUT_SPECS + _available_parameters = None + _default_spatial_dimension = 1 - def __init__( - self, - *, - prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, - name: Optional[str] = None, - rng_seed_prob_input: Optional[int] = None, - ): - # --- Arguments processing - # Get the ProbInputSpec from available - prob_input_spec = get_prob_input_spec( - prob_input_selection, AVAILABLE_INPUT_SPECS - ) - # Create a ProbInput - prob_input = create_prob_input_from_spec( - prob_input_spec, rng_seed=rng_seed_prob_input - ) - # Process the default name - if name is None: - name = OakleyOHagan1D.__name__ - - super().__init__(prob_input=prob_input, name=name) - - def evaluate(self, xx: np.ndarray): - """Evaluate the 1D Oakley-O'Hagan function on a set of input values. - - Parameters - ---------- - xx : np.ndarray - 1-Dimensional input values given by an N-by-1 array - where N is the number of input values. - - Returns - ------- - np.ndarray - The output of the 1D Oakley-O'Hagan function evaluated - on the input values. - The output is a 1-dimensional array of length N. - """ - yy = 5 + xx + np.cos(xx) - - return yy + eval_ = staticmethod(evaluate) diff --git a/src/uqtestfuns/test_functions/otl_circuit.py b/src/uqtestfuns/test_functions/otl_circuit.py index 5cfeb0a..adaa315 100644 --- a/src/uqtestfuns/test_functions/otl_circuit.py +++ b/src/uqtestfuns/test_functions/otl_circuit.py @@ -23,46 +23,44 @@ import numpy as np from copy import copy -from typing import Optional -from ..core.prob_input.input_spec import MarginalSpec, ProbInputSpec +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecFixDim from ..core.uqtestfun_abc import UQTestFunABC -from .available import get_prob_input_spec, create_prob_input_from_spec __all__ = ["OTLCircuit"] INPUT_MARGINALS_BENARI2007 = [ - MarginalSpec( + UnivDistSpec( name="Rb1", distribution="uniform", parameters=[50.0, 150.0], description="Resistance b1 [kOhm]", ), - MarginalSpec( + UnivDistSpec( name="Rb2", distribution="uniform", parameters=[25.0, 70.0], description="Resistance b2 [kOhm]", ), - MarginalSpec( + UnivDistSpec( name="Rf", distribution="uniform", parameters=[0.5, 3.0], description="Resistance f [kOhm]", ), - MarginalSpec( + UnivDistSpec( name="Rc1", distribution="uniform", parameters=[1.2, 2.5], description="Resistance c1 [kOhm]", ), - MarginalSpec( + UnivDistSpec( name="Rc2", distribution="uniform", parameters=[0.25, 1.20], description="Resistance c2 [kOhm]", ), - MarginalSpec( + UnivDistSpec( name="beta", distribution="uniform", parameters=[50.0, 300.0], @@ -73,7 +71,7 @@ INPUT_MARGINALS_MOON2010 = [copy(_) for _ in INPUT_MARGINALS_BENARI2007] for i in range(14): INPUT_MARGINALS_MOON2010.append( - MarginalSpec( + UnivDistSpec( name=f"Inert {i+1}", distribution="uniform", parameters=[100.0, 200.0], @@ -82,8 +80,8 @@ ) AVAILABLE_INPUT_SPECS = { - "BenAri2007": ProbInputSpec( - name="OTL-Circuit-Ben-Ari-2007", + "BenAri2007": ProbInputSpecFixDim( + name="OTLCircuit-BenAri2007", description=( "Probabilistic input model for the OTL Circuit function " "from Ben-Ari and Steinberg (2007)." @@ -91,8 +89,8 @@ marginals=INPUT_MARGINALS_BENARI2007, copulas=None, ), - "Moon2010": ProbInputSpec( - name="OTL-Circuit-Moon-2010", + "Moon2010": ProbInputSpecFixDim( + name="OTLCircuit-Moon2010", description=( "Probabilistic input model for the OTL Circuit function " "from Moon (2010)." @@ -105,82 +103,60 @@ DEFAULT_INPUT_SELECTION = "BenAri2007" -class OTLCircuit(UQTestFunABC): - """A concrete implementation of the OTL circuit test function.""" +def evaluate(xx: np.ndarray) -> np.ndarray: + """Evaluate the OTL circuit test function on a set of input values. - _tags = ["metamodeling", "sensitivity"] + Parameters + ---------- + xx : np.ndarray + (At least) 6-dimensional input values given by N-by-6 arrays + where N is the number of input values. - _available_inputs = tuple(AVAILABLE_INPUT_SPECS.keys()) + Returns + ------- + np.ndarray + The output of the OTL circuit test function, + i.e., the mid-point voltage in Volt. + The output is a one-dimensional array of length N. - _available_parameters = None + Notes + ----- + - The variant of this test function has 14 additional inputs, + but they are all taken to be inert and therefore should not affect + the output. + """ + rr_b1 = xx[:, 0] # Resistance b1 + rr_b2 = xx[:, 1] # Resistance b2 + rr_f = xx[:, 2] # Resistance f + rr_c1 = xx[:, 3] # Resistance c1 + rr_c2 = xx[:, 4] # Resistance c2 + beta = xx[:, 5] # Current gain + + # Compute the voltage across b1 + vb1 = 12 * rr_b2 / (rr_b1 + rr_b2) + + # Compute the mid-point voltage + denom = beta * (rr_c2 + 9) + rr_f + term_1 = ((vb1 + 0.74) * beta * (rr_c2 + 9)) / denom + term_2 = 11.35 * rr_f / denom + term_3 = 0.74 * rr_f * beta * (rr_c2 + 9) / (rr_c1 * denom) + + vm = term_1 + term_2 + term_3 + + return vm - _default_spatial_dimension = 6 +class OTLCircuit(UQTestFunABC): + """A concrete implementation of the OTL circuit test function.""" + + _tags = ["metamodeling", "sensitivity"] + _default_spatial_dimension = 6 _description = ( "Output transformerless (OTL) circuit model " "from Ben-Ari and Steinberg (2007)" ) + _available_inputs = AVAILABLE_INPUT_SPECS + _available_parameters = None + _default_input = DEFAULT_INPUT_SELECTION - def __init__( - self, - *, - prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, - name: Optional[str] = None, - rng_seed_prob_input: Optional[int] = None, - ): - # --- Arguments processing - # Get the ProbInputSpec from available - prob_input_spec = get_prob_input_spec( - prob_input_selection, AVAILABLE_INPUT_SPECS - ) - # Create a ProbInput - prob_input = create_prob_input_from_spec( - prob_input_spec, rng_seed=rng_seed_prob_input - ) - # Process the default name - if name is None: - name = OTLCircuit.__name__ - - super().__init__(prob_input=prob_input, name=name) - - def evaluate(self, xx: np.ndarray) -> np.ndarray: - """Evaluate the OTL circuit test function on a set of input values. - - Parameters - ---------- - xx : np.ndarray - (At least) 6-dimensional input values given by N-by-6 arrays - where N is the number of input values. - - Returns - ------- - np.ndarray - The output of the OTL circuit test function, - i.e., the mid-point voltage in Volt. - The output is a one-dimensional array of length N. - - Notes - ----- - - The variant of this test function has 14 additional inputs, - but they are all taken to be inert and therefore should not affect - the output. - """ - rr_b1 = xx[:, 0] # Resistance b1 - rr_b2 = xx[:, 1] # Resistance b2 - rr_f = xx[:, 2] # Resistance f - rr_c1 = xx[:, 3] # Resistance c1 - rr_c2 = xx[:, 4] # Resistance c2 - beta = xx[:, 5] # Current gain - - # Compute the voltage across b1 - vb1 = 12 * rr_b2 / (rr_b1 + rr_b2) - - # Compute the mid-point voltage - denom = beta * (rr_c2 + 9) + rr_f - term_1 = ((vb1 + 0.74) * beta * (rr_c2 + 9)) / denom - term_2 = 11.35 * rr_f / denom - term_3 = 0.74 * rr_f * beta * (rr_c2 + 9) / (rr_c1 * denom) - - vm = term_1 + term_2 + term_3 - - return vm + eval_ = staticmethod(evaluate) diff --git a/src/uqtestfuns/test_functions/piston.py b/src/uqtestfuns/test_functions/piston.py index d1de8e3..a6e3466 100644 --- a/src/uqtestfuns/test_functions/piston.py +++ b/src/uqtestfuns/test_functions/piston.py @@ -23,53 +23,51 @@ import numpy as np from copy import copy -from typing import Optional -from ..core.prob_input.input_spec import MarginalSpec, ProbInputSpec +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecFixDim from ..core.uqtestfun_abc import UQTestFunABC -from .available import get_prob_input_spec, create_prob_input_from_spec __all__ = ["Piston"] # Marginals specification from [1] INPUT_MARGINALS_BENARI2007 = [ - MarginalSpec( + UnivDistSpec( name="M", distribution="uniform", parameters=[30.0, 60.0], description="Piston weight [kg]", ), - MarginalSpec( + UnivDistSpec( name="S", distribution="uniform", parameters=[0.005, 0.020], description="Piston surface area [m^2]", ), - MarginalSpec( + UnivDistSpec( name="V0", distribution="uniform", parameters=[0.002, 0.010], description="Initial gas volume [m^3]", ), - MarginalSpec( + UnivDistSpec( name="k", distribution="uniform", parameters=[1000.0, 5000.0], description="Spring coefficient [N/m]", ), - MarginalSpec( + UnivDistSpec( name="P0", distribution="uniform", parameters=[90000.0, 110000.0], description="Atmospheric pressure [N/m^2]", ), - MarginalSpec( + UnivDistSpec( name="Ta", distribution="uniform", parameters=[290.0, 296.0], description="Ambient temperature [K]", ), - MarginalSpec( + UnivDistSpec( name="T0", distribution="uniform", parameters=[340.0, 360.0], @@ -81,7 +79,7 @@ INPUT_MARGINALS_MOON2010 = [copy(_) for _ in INPUT_MARGINALS_BENARI2007] for i in range(13): INPUT_MARGINALS_MOON2010.append( - MarginalSpec( + UnivDistSpec( name=f"Inert {i+1}", distribution="uniform", parameters=[100.0, 200.0], @@ -90,8 +88,8 @@ ) AVAILABLE_INPUT_SPECS = { - "BenAri2007": ProbInputSpec( - name="Piston-Ben-Ari-2007", + "BenAri2007": ProbInputSpecFixDim( + name="Piston-BenAri2007", description=( "Probabilistic input model for the Piston simulation model " "from Ben-Ari and Steinberg (2007)." @@ -99,8 +97,8 @@ marginals=INPUT_MARGINALS_BENARI2007, copulas=None, ), - "Moon2010": ProbInputSpec( - name="Piston-Moon-2010", + "Moon2010": ProbInputSpecFixDim( + name="Piston-Moon2010", description=( "Probabilistic input model for the Piston simulation model " "from Moon (2010)." @@ -113,87 +111,62 @@ DEFAULT_INPUT_SELECTION = "BenAri2007" +def evaluate(xx: np.ndarray) -> np.ndarray: + """Evaluate the Piston simulation test function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + (At least) 6-dimensional input values given by N-by-6 arrays + where N is the number of input values. + + Returns + ------- + np.ndarray + The output of the Piston simulation test function, + The output is a one-dimensional array of length N. + + Notes + ----- + - The variant of this test function has 13 additional inputs, + but they are all taken to be inert and therefore should not affect + the output. + """ + mm = xx[:, 0] # piston weight + ss = xx[:, 1] # piston surface area + vv_0 = xx[:, 2] # initial gas volume + kk = xx[:, 3] # spring coefficient + pp_0 = xx[:, 4] # atmospheric pressure + tt_a = xx[:, 5] # ambient temperature + tt_0 = xx[:, 6] # filling gas temperature + + # Compute the force + aa = pp_0 * ss + 19.62 * mm - kk * vv_0 / ss + + # Compute the force difference + daa = np.sqrt(aa**2 + 4.0 * kk * pp_0 * vv_0 * tt_a / tt_0) - aa + + # Compute the volume difference + vv = ss / 2.0 / kk * daa + + # Compute the cycle time + cc = ( + 2.0 + * np.pi + * np.sqrt(mm / (kk + ss**2 * pp_0 * vv_0 * tt_a / tt_0 / vv**2)) + ) + + return cc + + class Piston(UQTestFunABC): """A concrete implementation of the Piston simulation test function.""" _tags = ["metamodeling", "sensitivity"] - - _available_inputs = tuple(AVAILABLE_INPUT_SPECS.keys()) - + _description = "Piston simulation model from Ben-Ari and Steinberg (2007)" + _available_inputs = AVAILABLE_INPUT_SPECS _available_parameters = None - _default_spatial_dimension = 7 + _default_input = DEFAULT_INPUT_SELECTION - _description = "Piston simulation model from Ben-Ari and Steinberg (2007)" - - def __init__( - self, - *, - prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, - name: Optional[str] = None, - rng_seed_prob_input: Optional[int] = None, - ): - # --- Arguments processing - # Get the ProbInputSpec from available - prob_input_spec = get_prob_input_spec( - prob_input_selection, AVAILABLE_INPUT_SPECS - ) - # Create a ProbInput - prob_input = create_prob_input_from_spec( - prob_input_spec, rng_seed=rng_seed_prob_input - ) - # Process the default name - if name is None: - name = Piston.__name__ - - super().__init__(prob_input=prob_input, name=name) - - def evaluate(self, xx: np.ndarray) -> np.ndarray: - """Evaluate the OTL circuit test function on a set of input values. - - Parameters - ---------- - xx : np.ndarray - (At least) 6-dimensional input values given by N-by-6 arrays - where N is the number of input values. - - Returns - ------- - np.ndarray - The output of the OTL circuit test function, - i.e., the mid-point voltage in Volt. - The output is a one-dimensional array of length N. - - Notes - ----- - - The variant of this test function has 14 additional inputs, - but they are all taken to be inert and therefore should not affect - the output. - """ - mm = xx[:, 0] # piston weight - ss = xx[:, 1] # piston surface area - vv_0 = xx[:, 2] # initial gas volume - kk = xx[:, 3] # spring coefficient - pp_0 = xx[:, 4] # atmospheric pressure - tt_a = xx[:, 5] # ambient temperature - tt_0 = xx[:, 6] # filling gas temperature - - # Compute the force - aa = pp_0 * ss + 19.62 * mm - kk * vv_0 / ss - - # Compute the force difference - daa = np.sqrt(aa**2 + 4.0 * kk * pp_0 * vv_0 * tt_a / tt_0) - aa - - # Compute the volume difference - vv = ss / 2.0 / kk * daa - - # Compute the cycle time - cc = ( - 2.0 - * np.pi - * np.sqrt( - mm / (kk + ss**2 * pp_0 * vv_0 * tt_a / tt_0 / vv**2) - ) - ) - - return cc + eval_ = staticmethod(evaluate) diff --git a/src/uqtestfuns/test_functions/sobol_g.py b/src/uqtestfuns/test_functions/sobol_g.py index 09f8af5..d2afcc9 100644 --- a/src/uqtestfuns/test_functions/sobol_g.py +++ b/src/uqtestfuns/test_functions/sobol_g.py @@ -47,19 +47,15 @@ """ import numpy as np -from typing import List, Optional +from typing import List -from ..core.prob_input.input_spec import MarginalSpec, ProbInputSpecVarDim +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecVarDim from ..core.uqtestfun_abc import UQTestFunABC -from .available import ( - create_prob_input_from_spec, - create_parameters_from_available, -) __all__ = ["SobolG"] -def _create_sobol_input(spatial_dimension: int) -> List[MarginalSpec]: +def _create_sobol_input(spatial_dimension: int) -> List[UnivDistSpec]: """Construct an input instance for a given dimension according to [1]. Parameters @@ -69,14 +65,14 @@ def _create_sobol_input(spatial_dimension: int) -> List[MarginalSpec]: Returns ------- - List[MarginalSpec] + List[UnivDistSpec] A list of M marginals as UnivariateInput instances to construct the MultivariateInput. """ marginals = [] for i in range(spatial_dimension): marginals.append( - MarginalSpec( + UnivDistSpec( name=f"X{i + 1}", distribution="uniform", parameters=[0.0, 1.0], @@ -200,74 +196,35 @@ def _get_params_crestaux_2007(spatial_dimension: int) -> np.ndarray: DEFAULT_DIMENSION_SELECTION = 2 -class SobolG(UQTestFunABC): - """A concrete implementation of the M-dimensional Sobol'-G function.""" +def evaluate(xx: np.ndarray, parameters): + """Evaluate the Sobol-G function on a set of input values. - _tags = ["sensitivity", "integration"] + Parameters + ---------- + xx : np.ndarray + M-Dimensional input values given by an N-by-M array where + N is the number of input values. - _available_inputs = tuple(AVAILABLE_INPUT_SPECS.keys()) + Returns + ------- + np.ndarray + The output of the Sobol-G function evaluated on the input values. + The output is a 1-dimensional array of length N. + """ + params = parameters + yy = np.prod(((np.abs(4 * xx - 2) + params) / (1 + params)), axis=1) - _available_parameters = tuple(AVAILABLE_PARAMETERS.keys()) + return yy - _default_spatial_dimension = None - _description = "Sobol'-G function from Radović et al. (1996)" - - def __init__( - self, - spatial_dimension: int = DEFAULT_DIMENSION_SELECTION, - *, - prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, - parameters_selection: Optional[str] = DEFAULT_PARAMETERS_SELECTION, - name: Optional[str] = None, - rng_seed_prob_input: Optional[int] = None, - ): - # --- Arguments processing - # Get the ProbInputSpec from available - if prob_input_selection is None: - prob_input_spec = None - else: - prob_input_spec = AVAILABLE_INPUT_SPECS[prob_input_selection] - if not isinstance(spatial_dimension, int): - raise TypeError( - f"Spatial dimension is expected to be of 'int'. " - f"Got {type(spatial_dimension):!r} instead." - ) - # Sobol-G is an M-dimensional test function, either given / use default - # Create the input according to spatial dimension - prob_input = create_prob_input_from_spec( - prob_input_spec, - spatial_dimension, - rng_seed_prob_input, - ) - # Create the parameters according to spatial dimension - parameters = create_parameters_from_available( - parameters_selection, AVAILABLE_PARAMETERS, spatial_dimension - ) - # Process the default name - if name is None: - name = self.__class__.__name__ +class SobolG(UQTestFunABC): + """An implementation of the M-dimensional Sobol'-G test function.""" - super().__init__( - prob_input=prob_input, parameters=parameters, name=name - ) + _tags = ["sensitivity", "integration"] + _description = "Sobol'-G function from Radović et al. (1996)" + _available_inputs = AVAILABLE_INPUT_SPECS + _available_parameters = AVAILABLE_PARAMETERS + _default_parameters = DEFAULT_PARAMETERS_SELECTION + _default_spatial_dimension = None - def evaluate(self, xx: np.ndarray): - """Evaluate the Sobol-G function on a set of input values. - - Parameters - ---------- - xx : np.ndarray - M-Dimensional input values given by an N-by-M array where - N is the number of input values. - - Returns - ------- - np.ndarray - The output of the Sobol-G function evaluated on the input values. - The output is a 1-dimensional array of length N. - """ - params = self.parameters - yy = np.prod(((np.abs(4 * xx - 2) + params) / (1 + params)), axis=1) - - return yy + eval_ = staticmethod(evaluate) diff --git a/src/uqtestfuns/test_functions/sulfur.py b/src/uqtestfuns/test_functions/sulfur.py index 0a87d97..5c4ff40 100644 --- a/src/uqtestfuns/test_functions/sulfur.py +++ b/src/uqtestfuns/test_functions/sulfur.py @@ -60,66 +60,63 @@ """ import numpy as np -from typing import Optional - -from ..core.prob_input.input_spec import MarginalSpec, ProbInputSpec +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecFixDim from ..core.uqtestfun_abc import UQTestFunABC -from .available import get_prob_input_spec, create_prob_input_from_spec __all__ = ["Sulfur"] INPUT_MARGINALS_PENNER1994 = [ # From [3] (Table 2) - MarginalSpec( + UnivDistSpec( name="Q", distribution="lognormal", parameters=[np.log(71.0), np.log(1.15)], description="Source strength of anthropogenic Sulfur [10^12 g/year]", ), - MarginalSpec( + UnivDistSpec( name="Y", distribution="lognormal", parameters=[np.log(0.5), np.log(1.5)], description="Fraction of SO2 oxidized to SO4(2-) aerosol [-]", ), - MarginalSpec( + UnivDistSpec( name="L", distribution="lognormal", parameters=[np.log(5.5), np.log(1.5)], description="Average lifetime of atmospheric SO4(2-) [days]", ), - MarginalSpec( + UnivDistSpec( name="Psi_e", distribution="lognormal", parameters=[np.log(5.0), np.log(1.4)], description="Aerosol mass scattering efficiency [m^2/g]", ), - MarginalSpec( + UnivDistSpec( name="beta", distribution="lognormal", parameters=[np.log(0.3), np.log(1.3)], description="Fraction of light scattered upward hemisphere [-]", ), - MarginalSpec( + UnivDistSpec( name="f_Psi_e", distribution="lognormal", parameters=[np.log(1.7), np.log(1.2)], description="Fractional increase in aerosol scattering efficiency " "due to hygroscopic growth [-]", ), - MarginalSpec( + UnivDistSpec( name="T^2", distribution="lognormal", parameters=[np.log(0.58), np.log(1.4)], description="Square of atmospheric " "transmittance above aerosol layer [-]", ), - MarginalSpec( + UnivDistSpec( name="(1-Ac)", distribution="lognormal", parameters=[np.log(0.39), np.log(1.1)], description="Fraction of earth not covered by cloud [-]", ), - MarginalSpec( + UnivDistSpec( name="(1-Rs)^2", distribution="lognormal", parameters=[np.log(0.72), np.log(1.2)], @@ -128,8 +125,8 @@ ] AVAILABLE_INPUT_SPECS = { - "Penner1994": ProbInputSpec( - name="Sulfur-Penner-1994", + "Penner1994": ProbInputSpecFixDim( + name="Sulfur-Penner1994", description=( "Probabilistic input model for the Sulfur model " "from Penner et al. (1994)." @@ -146,86 +143,63 @@ DAYS_IN_YEAR = 365 # [days] +def evaluate(xx: np.ndarray) -> np.ndarray: + """Evaluate the Sulfur model test function on a set of input values. + + References + ---------- + xx : np.ndarray + A nine-dimensional input values given by an N-by-9 array + where N is the number of input values. + + Returns + ------- + np.ndarray + The output of the Sulfur model test function, i.e., + the direct radiative forcing by sulfate aerosols. + """ + # Source strength of anthropogenic Sulfur (initially given in Teragram) + qq = xx[:, 0] * 1e12 + # Fraction of SO2 oxidized to SO4(2-) aerosol + yy = xx[:, 1] + # Average lifetime of atmospheric SO4(2-) + ll = xx[:, 2] + # Aerosol mass scattering efficiency + psi_e = xx[:, 3] + # Fraction of light scattered upward hemisphere + beta = xx[:, 4] + # Fractional increase in aerosol scattering eff. due hygroscopic growth + ff_psi = xx[:, 5] + # Square of atmospheric transmittance above aerosol layer + tt_sq = xx[:, 6] + # Fraction of earth not covered by cloud + aa_c_complement = xx[:, 7] + # Square of surface coalbedo + co_rr_s_sq = xx[:, 8] + + # Sulfate burden (Eq. (5) in [1], notation from [2]) + # NOTE: Factor 3.0 due to conversion of mass from S to SO4(2-) + # NOTE: Factor 1/365.0 due to average lifetime is given in [days] + # while qq is in [gS / year] + sulfate_burden = 3.0 * qq * yy * ll / EARTH_AREA / DAYS_IN_YEAR + + # Loading of sulfate aerosol (Eq. (4) in [1], notation from [2]) + sulfate_loading = psi_e * ff_psi * sulfate_burden + + # Direct radiative forcing by sulfate aerosols + factor_1 = SOLAR_CONSTANT * aa_c_complement * tt_sq * co_rr_s_sq * beta + dd_f = -0.5 * factor_1 * sulfate_loading + + return dd_f + + class Sulfur(UQTestFunABC): """A concrete implementation of the Sulfur model test function.""" _tags = ["metamodeling", "sensitivity"] - - _available_inputs = tuple(AVAILABLE_INPUT_SPECS.keys()) - + _description = "Sulfur model from Charlson et al. (1992)" + _available_inputs = AVAILABLE_INPUT_SPECS _available_parameters = None - _default_spatial_dimension = 9 - _description = "Sulfur model from Charlson et al. (1992)" - - def __init__( - self, - *, - prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, - name: Optional[str] = None, - rng_seed_prob_input: Optional[int] = None, - ): - # --- Arguments processing - # Get the ProbInputSpec from available - prob_input_spec = get_prob_input_spec( - prob_input_selection, AVAILABLE_INPUT_SPECS - ) - # Create a ProbInput - prob_input = create_prob_input_from_spec( - prob_input_spec, rng_seed=rng_seed_prob_input - ) - # Process the default name - if name is None: - name = Sulfur.__name__ - - super().__init__(prob_input=prob_input, name=name) - - def evaluate(self, xx): - """Evaluate the Sulfur model test function on a set of input values. - - References - ---------- - xx : np.ndarray - A nine-dimensional input values given by an N-by-9 array - where N is the number of input values. - - Returns - ------- - np.ndarray - The output of the Sulfur model test function, i.e., - the direct radiative forcing by sulfate aerosols. - """ - # Source strength of anthropogenic Sulfur (initially given in Teragram) - qq = xx[:, 0] * 1e12 - # Fraction of SO2 oxidized to SO4(2-) aerosol - yy = xx[:, 1] - # Average lifetime of atmospheric SO4(2-) - ll = xx[:, 2] - # Aerosol mass scattering efficiency - psi_e = xx[:, 3] - # Fraction of light scattered upward hemisphere - beta = xx[:, 4] - # Fractional increase in aerosol scattering eff. due hygroscopic growth - ff_psi = xx[:, 5] - # Square of atmospheric transmittance above aerosol layer - tt_sq = xx[:, 6] - # Fraction of earth not covered by cloud - aa_c_complement = xx[:, 7] - # Square of surface coalbedo - co_rr_s_sq = xx[:, 8] - - # Sulfate burden (Eq. (5) in [1], notation from [2]) - # NOTE: Factor 3.0 due to conversion of mass from S to SO4(2-) - # NOTE: Factor 1/365.0 due to average lifetime is given in [days] - # while qq is in [gS / year] - sulfate_burden = 3.0 * qq * yy * ll / EARTH_AREA / DAYS_IN_YEAR - - # Loading of sulfate aerosol (Eq. (4) in [1], notation from [2]) - sulfate_loading = psi_e * ff_psi * sulfate_burden - - # Direct radiative forcing by sulfate aerosols - factor_1 = SOLAR_CONSTANT * aa_c_complement * tt_sq * co_rr_s_sq * beta - dd_f = -0.5 * factor_1 * sulfate_loading - - return dd_f + eval_ = staticmethod(evaluate) diff --git a/src/uqtestfuns/test_functions/welch1992.py b/src/uqtestfuns/test_functions/welch1992.py index 3c3520a..d118668 100644 --- a/src/uqtestfuns/test_functions/welch1992.py +++ b/src/uqtestfuns/test_functions/welch1992.py @@ -20,17 +20,14 @@ """ import numpy as np -from typing import Optional - -from ..core.prob_input.input_spec import MarginalSpec, ProbInputSpec +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecFixDim from ..core.uqtestfun_abc import UQTestFunABC -from .available import get_prob_input_spec, create_prob_input_from_spec __all__ = ["Welch1992"] INPUT_MARGINALS_WELCH1992 = [ - MarginalSpec( + UnivDistSpec( name=f"x{i}", distribution="uniform", parameters=[-0.5, 0.5], @@ -40,7 +37,7 @@ ] AVAILABLE_INPUT_SPECS = { - "Welch1992": ProbInputSpec( + "Welch1992": ProbInputSpecFixDim( name="Welch1992", description=( "Input specification for the test function " @@ -54,79 +51,56 @@ DEFAULT_INPUT_SELECTION = "Welch1992" +def evaluate(xx: np.ndarray) -> np.ndarray: + """Evaluate the Welch et al. (1992) function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + 1-Dimensional input values given by an N-by-1 array + where N is the number of input values. + + Returns + ------- + np.ndarray + The output of the test function evaluated on the input values. + The output is a 1-dimensional array of length N. + + Notes + ----- + - The input variables xx[:, 7] (x8) and xx[:, 15] (x16) are inert and + therefore, does not appear in the computation below. + """ + yy = ( + (5 * xx[:, 11]) / (1 + xx[:, 0]) + + 5 * (xx[:, 3] - xx[:, 19]) ** 2 + + xx[:, 4] + + 40 * xx[:, 18] ** 3 + - 5 * xx[:, 18] + + 0.05 * xx[:, 1] + + 0.08 * xx[:, 2] + - 0.03 * xx[:, 5] + + 0.03 * xx[:, 6] + - 0.09 * xx[:, 8] + - 0.01 * xx[:, 9] + - 0.07 * xx[:, 10] + + 0.25 * xx[:, 12] ** 2 + - 0.04 * xx[:, 13] + + 0.06 * xx[:, 14] + - 0.01 * xx[:, 16] + - 0.03 * xx[:, 17] + ) + + return yy + + class Welch1992(UQTestFunABC): """A concrete implementation of the Welch et al. (1992) test function.""" _tags = ["metamodeling", "sensitivity", "integration"] - - _available_inputs = tuple(AVAILABLE_INPUT_SPECS.keys()) - + _description = "20-Dimensional function from Welch et al. (1992)" + _available_inputs = AVAILABLE_INPUT_SPECS _available_parameters = None - _default_spatial_dimension = 20 - _description = "20-Dimensional function from Welch et al. (1992)" - - def __init__( - self, - *, - prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, - name: Optional[str] = None, - rng_seed_prob_input: Optional[int] = None, - ): - # --- Arguments processing - # Get the ProbInputSpec from available - prob_input_spec = get_prob_input_spec( - prob_input_selection, AVAILABLE_INPUT_SPECS - ) - # Create a ProbInput - prob_input = create_prob_input_from_spec( - prob_input_spec, rng_seed=rng_seed_prob_input - ) - # Process the default name - if name is None: - name = self.__class__.__name__ - - super().__init__(prob_input=prob_input, name=name) - - def evaluate(self, xx: np.ndarray): - """Evaluate the Welch et al. (1992) function on a set of input values. - - Parameters - ---------- - xx : np.ndarray - 1-Dimensional input values given by an N-by-1 array - where N is the number of input values. - - Returns - ------- - np.ndarray - The output of the test function evaluated on the input values. - The output is a 1-dimensional array of length N. - - Notes - ----- - - The input variables xx[:, 7] (x8) and xx[:, 15] (x16) are inert and - therefore, does not appear in the computation below. - """ - yy = ( - (5 * xx[:, 11]) / (1 + xx[:, 0]) - + 5 * (xx[:, 3] - xx[:, 19]) ** 2 - + xx[:, 4] - + 40 * xx[:, 18] ** 3 - - 5 * xx[:, 18] - + 0.05 * xx[:, 1] - + 0.08 * xx[:, 2] - - 0.03 * xx[:, 5] - + 0.03 * xx[:, 6] - - 0.09 * xx[:, 8] - - 0.01 * xx[:, 9] - - 0.07 * xx[:, 10] - + 0.25 * xx[:, 12] ** 2 - - 0.04 * xx[:, 13] - + 0.06 * xx[:, 14] - - 0.01 * xx[:, 16] - - 0.03 * xx[:, 17] - ) - - return yy + eval_ = staticmethod(evaluate) diff --git a/src/uqtestfuns/test_functions/wing_weight.py b/src/uqtestfuns/test_functions/wing_weight.py index f81486c..1278d57 100644 --- a/src/uqtestfuns/test_functions/wing_weight.py +++ b/src/uqtestfuns/test_functions/wing_weight.py @@ -21,71 +21,68 @@ """ import numpy as np -from typing import Optional - -from ..core.prob_input.input_spec import MarginalSpec, ProbInputSpec +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecFixDim from ..core.uqtestfun_abc import UQTestFunABC -from .available import get_prob_input_spec, create_prob_input_from_spec from .utils import deg2rad __all__ = ["WingWeight"] INPUT_MARGINALS_FORRESTER2008 = [ - MarginalSpec( + UnivDistSpec( name="Sw", distribution="uniform", parameters=[150.0, 200.0], description="wing area [ft^2]", ), - MarginalSpec( + UnivDistSpec( name="Wfw", distribution="uniform", parameters=[220.0, 300.0], description="weight of fuel in the wing [lb]", ), - MarginalSpec( + UnivDistSpec( name="A", distribution="uniform", parameters=[6.0, 10.0], description="aspect ratio [-]", ), - MarginalSpec( + UnivDistSpec( name="Lambda", distribution="uniform", parameters=[-10.0, 10.0], description="quarter-chord sweep [degrees]", ), - MarginalSpec( + UnivDistSpec( name="q", distribution="uniform", parameters=[16.0, 45.0], description="dynamic pressure at cruise [lb/ft^2]", ), - MarginalSpec( + UnivDistSpec( name="lambda", distribution="uniform", parameters=[0.5, 1.0], description="taper ratio [-]", ), - MarginalSpec( + UnivDistSpec( name="tc", distribution="uniform", parameters=[0.08, 0.18], description="aerofoil thickness to chord ratio [-]", ), - MarginalSpec( + UnivDistSpec( name="Nz", distribution="uniform", parameters=[2.5, 6.0], description="ultimate load factor [-]", ), - MarginalSpec( + UnivDistSpec( name="Wdg", distribution="uniform", parameters=[1700, 2500], description="flight design gross weight [lb]", ), - MarginalSpec( + UnivDistSpec( name="Wp", distribution="uniform", parameters=[0.025, 0.08], @@ -94,7 +91,7 @@ ] AVAILABLE_INPUT_SPECS = { - "Forrester2008": ProbInputSpec( + "Forrester2008": ProbInputSpecFixDim( name="Wing-Weight-Forrester-2008", description=( "Probabilistic input model for the Wing Weight model " @@ -105,69 +102,44 @@ ), } -DEFAULT_INPUT_SELECTION = "Forrester2008" +def evaluate(xx: np.ndarray) -> np.ndarray: + """Evaluate the Wing Weight function on a set of input values. -class WingWeight(UQTestFunABC): - """A concrete implementation of the wing weight test function.""" + Parameters + ---------- + xx : np.ndarray + 10-Dimensional input values given by N-by-10 arrays where + N is the number of input values. - _tags = ["metamodeling", "sensitivity"] + Returns + ------- + np.ndarray + The output of the Wing Weight function evaluated + on the input values. + The output is a 1-dimensional array of length N. + """ + # Compute the Wing Weight function + term_1 = 0.036 * xx[:, 0] ** 0.758 * xx[:, 1] ** 0.0035 + term_2 = (xx[:, 2] / np.cos(deg2rad(xx[:, 3])) ** 2) ** 0.6 + term_3 = xx[:, 4] ** 0.006 + term_4 = xx[:, 5] ** 0.04 + term_5 = (100 * xx[:, 6] / np.cos(np.pi / 180.0 * xx[:, 3])) ** (-0.3) + term_6 = (xx[:, 7] * xx[:, 8]) ** 0.49 + term_7 = xx[:, 0] * xx[:, 9] - _available_inputs = tuple(AVAILABLE_INPUT_SPECS.keys()) + yy = term_1 * term_2 * term_3 * term_4 * term_5 * term_6 + term_7 - _available_parameters = None + return yy - _default_spatial_dimension = 10 +class WingWeight(UQTestFunABC): + """A concrete implementation of the wing weight test function.""" + + _tags = ["metamodeling", "sensitivity"] _description = "Wing weight model from Forrester et al. (2008)" + _available_inputs = AVAILABLE_INPUT_SPECS + _available_parameters = None + _default_spatial_dimension = 10 - def __init__( - self, - *, - prob_input_selection: Optional[str] = DEFAULT_INPUT_SELECTION, - name: Optional[str] = None, - rng_seed_prob_input: Optional[int] = None, - ): - # --- Arguments processing - # Get the ProbInputSpec from available - prob_input_spec = get_prob_input_spec( - prob_input_selection, AVAILABLE_INPUT_SPECS - ) - # Create a ProbInput - prob_input = create_prob_input_from_spec( - prob_input_spec, rng_seed=rng_seed_prob_input - ) - # Process the default name - if name is None: - name = self.__class__.__name__ - - super().__init__(prob_input=prob_input, name=name) - - def evaluate(self, xx): - """Evaluate the Wing Weight function on a set of input values. - - Parameters - ---------- - xx : np.ndarray - 10-Dimensional input values given by N-by-10 arrays where - N is the number of input values. - - Returns - ------- - np.ndarray - The output of the Wing Weight function evaluated - on the input values. - The output is a 1-dimensional array of length N. - """ - # Compute the Wing Weight function - term_1 = 0.036 * xx[:, 0] ** 0.758 * xx[:, 1] ** 0.0035 - term_2 = (xx[:, 2] / np.cos(deg2rad(xx[:, 3])) ** 2) ** 0.6 - term_3 = xx[:, 4] ** 0.006 - term_4 = xx[:, 5] ** 0.04 - term_5 = (100 * xx[:, 6] / np.cos(np.pi / 180.0 * xx[:, 3])) ** (-0.3) - term_6 = (xx[:, 7] * xx[:, 8]) ** 0.49 - term_7 = xx[:, 0] * xx[:, 9] - - yy = term_1 * term_2 * term_3 * term_4 * term_5 * term_6 + term_7 - - return yy + eval_ = staticmethod(evaluate) diff --git a/tests/builtin_test_functions/test_ishigami.py b/tests/builtin_test_functions/test_ishigami.py index 7da41d9..172cae0 100644 --- a/tests/builtin_test_functions/test_ishigami.py +++ b/tests/builtin_test_functions/test_ishigami.py @@ -13,7 +13,7 @@ import uqtestfuns.test_functions.ishigami as ishigami_mod # Test for different parameters to the Ishigami function -available_parameters = Ishigami.available_parameters +available_parameters = list(Ishigami.available_parameters.keys()) @pytest.fixture(params=available_parameters) @@ -72,5 +72,5 @@ def test_different_parameters(param_selection): def test_wrong_param_selection(): """Test a wrong selection of the parameters.""" - with pytest.raises(ValueError): + with pytest.raises(KeyError): Ishigami(parameters_selection="marelli1") diff --git a/tests/builtin_test_functions/test_sobol_g.py b/tests/builtin_test_functions/test_sobol_g.py index 21a356e..83ce262 100644 --- a/tests/builtin_test_functions/test_sobol_g.py +++ b/tests/builtin_test_functions/test_sobol_g.py @@ -11,12 +11,12 @@ from uqtestfuns import SobolG -available_parameters = SobolG.available_parameters +available_parameters = list(SobolG.available_parameters.keys()) def test_wrong_param_selection(): """Test a wrong selection of the parameters.""" - with pytest.raises(ValueError): + with pytest.raises(KeyError): SobolG(parameters_selection="marelli1") diff --git a/tests/builtin_test_functions/test_test_functions.py b/tests/builtin_test_functions/test_test_functions.py index 5a18058..bebdbf1 100644 --- a/tests/builtin_test_functions/test_test_functions.py +++ b/tests/builtin_test_functions/test_test_functions.py @@ -72,11 +72,7 @@ def test_create_instance_with_prob_input(builtin_testfun): # Create an instance without probabilistic input if testfun_class.available_inputs is not None: - my_fun_2 = testfun_class(prob_input_selection=None) - assert my_fun_2.prob_input is None - assert my_fun_2.spatial_dimension == ( - testfun_class.default_spatial_dimension - ) + my_fun_2 = testfun_class() # Assign the probabilistic input my_fun_2.prob_input = my_prob_input @@ -103,8 +99,7 @@ def test_create_instance_with_parameters(builtin_testfun): parameters = my_fun.parameters if testfun_class.available_parameters is not None: - my_fun_2 = testfun_class(parameters_selection=None) - assert my_fun_2.parameters is None + my_fun_2 = testfun_class() my_fun_2.parameters = parameters assert my_fun_2.parameters is parameters else: @@ -234,7 +229,7 @@ def test_evaluate_wrong_input_domain(builtin_testfun): def test_evaluate_invalid_spatial_dim(builtin_testfun): """Test if an exception is raised if invalid spatial dimension is given.""" - if builtin_testfun.default_spatial_dimension is None: + if hasattr(builtin_testfun, "default_spatial_dimension"): with pytest.raises(TypeError): builtin_testfun(spatial_dimension="10") diff --git a/tests/core/prob_input/test_multivariate_input.py b/tests/core/prob_input/test_multivariate_input.py index d48b5be..1194272 100644 --- a/tests/core/prob_input/test_multivariate_input.py +++ b/tests/core/prob_input/test_multivariate_input.py @@ -4,6 +4,12 @@ from typing import List, Any from uqtestfuns.core.prob_input.probabilistic_input import ProbInput +from uqtestfuns.core.prob_input.univariate_distribution import UnivDist +from uqtestfuns.core.prob_input.input_spec import ( + UnivDistSpec, + ProbInputSpecFixDim, + ProbInputSpecVarDim, +) from conftest import create_random_marginals @@ -235,6 +241,77 @@ def test_pass_random_seed(spatial_dimension): assert np.allclose(xx_1, xx_2) +@pytest.mark.parametrize("spatial_dimension", [1, 2, 10, 100]) +def test_create_from_spec(spatial_dimension): + """Test creating an instance from specification NamedTuple""" + marginals: List[UnivDist] = create_random_marginals(spatial_dimension) + + # Create a ProbInputSpecFixDim + name = "Test Name" + description = "Test description" + copulas = None + prob_spec = ProbInputSpecFixDim( + name=name, + description=description, + marginals=[ + UnivDistSpec( + name=marginal.name, + description=marginal.description, + distribution=marginal.distribution, + parameters=marginal.parameters, # type: ignore + ) + for marginal in marginals + ], + copulas=copulas, + ) + + # Create from spec + my_input = ProbInput.from_spec(prob_spec) + + # Assertions + assert my_input.name == name + assert my_input.description == description + assert my_input.copulas == copulas + + # Create a ProbInputSpecVarDim + def _test_vardim(spatial_dimension): + marginals_spec = [ + UnivDistSpec( + name=f"x{i+1}", + description="None", + distribution="uniform", + parameters=[0, 1], + ) + for i in range(spatial_dimension) + ] + + return marginals_spec + + name = "Test Name" + description = "Test description" + copulas = None + prob_spec_vardim = ProbInputSpecVarDim( + name=name, + description=description, + marginals_generator=_test_vardim, + copulas=copulas, + ) + + # Create from spec + my_input = ProbInput.from_spec( + prob_spec_vardim, spatial_dimension=spatial_dimension + ) + + # Assertions + assert my_input.name == name + assert my_input.description == description + assert my_input.copulas == copulas + assert my_input.spatial_dimension == spatial_dimension + + with pytest.raises(ValueError): + ProbInput.from_spec(prob_spec_vardim) + + # # def test_get_cdf_values(): # """Test the CDF values from an instance of UnivariateInput.""" diff --git a/tests/core/test_input_spec.py b/tests/core/test_input_spec.py index 6b064ed..609705b 100644 --- a/tests/core/test_input_spec.py +++ b/tests/core/test_input_spec.py @@ -4,8 +4,8 @@ from typing import List from uqtestfuns.core.prob_input.input_spec import ( - MarginalSpec, - ProbInputSpec, + UnivDistSpec, + ProbInputSpecFixDim, ProbInputSpecVarDim, ) @@ -19,7 +19,7 @@ def test_marginalspec(): description = "Filling gas temperature" # Create a MarginalSpec - my_marginalspec = MarginalSpec( + my_marginalspec = UnivDistSpec( name=name, distribution=distribution, parameters=parameters, @@ -43,7 +43,7 @@ def test_probinputspec_list(): name = "Some input" description = "Probabilistic input model from somewhere" copulas = None - my_probinputspec = ProbInputSpec( + my_probinputspec = ProbInputSpecFixDim( name=name, description=description, marginals=marginals, @@ -81,10 +81,10 @@ def test_probinputspec_vardim(): assert my_probinputspec.marginals_generator == marginals_gen -def _create_marginals(spatial_dimension: int) -> List[MarginalSpec]: +def _create_marginals(spatial_dimension: int) -> List[UnivDistSpec]: """Create a list of test marginals.""" return [ - MarginalSpec( + UnivDistSpec( name=f"x{i + 1}", distribution="uniform", parameters=[0.0, 1.0], diff --git a/tests/core/test_uqtestfun.py b/tests/core/test_uqtestfun.py index 1f48f57..db8b75a 100644 --- a/tests/core/test_uqtestfun.py +++ b/tests/core/test_uqtestfun.py @@ -1,7 +1,6 @@ """ Test module for UQTestFun class, a generic class for generic UQ test function. """ -import numpy as np import pytest from uqtestfuns import UQTestFun, ProbInput @@ -67,8 +66,7 @@ def test_str(uqtestfun): str_ref = ( f"Name : {uqtestfun_instance.name}\n" - f"Spatial dimension : {uqtestfun_instance.spatial_dimension}\n" - f"Description : {uqtestfun_instance.description}" + f"Spatial dimension : {uqtestfun_instance.spatial_dimension}" ) assert uqtestfun_instance.__str__() == str_ref @@ -83,12 +81,3 @@ def test_invalid_input(uqtestfun): with pytest.raises(TypeError): UQTestFun(**uqtestfun_dict) - - -def test_empty_uqtestfun(): - """Test creation of an empty UQTestFun instance.""" - my_fun = UQTestFun(lambda x: x) - - with pytest.raises(ValueError): - xx = np.random.rand(10) - my_fun.transform_sample(xx) From 18ce5c7aecabc26e7e8db2e9a7c14648c16b3d9a Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Mon, 19 Jun 2023 14:57:46 +0200 Subject: [PATCH 19/73] Add an implementation of the test function of Forrester et al. (2008) - The function is a one-dimensional test function used in the context of optimization using metamodels. --- docs/_toc.yml | 2 + docs/fundamentals/metamodeling.md | 1 + docs/fundamentals/optimization.md | 1 + docs/test-functions/ackley.md | 2 +- docs/test-functions/available.md | 1 + docs/test-functions/forrester.md | 131 +++++++++++++++++++++ src/uqtestfuns/test_functions/__init__.py | 4 +- src/uqtestfuns/test_functions/forrester.py | 62 ++++++++++ 8 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 docs/test-functions/forrester.md create mode 100644 src/uqtestfuns/test_functions/forrester.py diff --git a/docs/_toc.yml b/docs/_toc.yml index c289cb2..68ca6e1 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -33,6 +33,8 @@ parts: title: Damped Oscillator - file: test-functions/flood title: Flood + - file: test-functions/forrester + title: Forrester et al. (2008) - file: test-functions/franke-1 title: (1st) Franke - file: test-functions/franke-2 diff --git a/docs/fundamentals/metamodeling.md b/docs/fundamentals/metamodeling.md index e5e60d3..afb938a 100644 --- a/docs/fundamentals/metamodeling.md +++ b/docs/fundamentals/metamodeling.md @@ -23,6 +23,7 @@ in the comparison of metamodeling approaches. | {ref}`Borehole ` | 8 | `Borehole()` | | {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | | {ref}`Flood ` | 8 | `Flood()` | +| {ref}`Forrester et al. (2008) ` | 1 | `Forrester2008()` | | {ref}`(1st) Franke ` | 2 | `Franke1()` | | {ref}`(2nd) Franke ` | 2 | `Franke2()` | | {ref}`(3rd) Franke ` | 2 | `Franke3()` | diff --git a/docs/fundamentals/optimization.md b/docs/fundamentals/optimization.md index 1455007..1f26f06 100644 --- a/docs/fundamentals/optimization.md +++ b/docs/fundamentals/optimization.md @@ -20,6 +20,7 @@ in the comparison of global optimization methods. | Name | Spatial Dimension | Constructor | |:-----------------------------------------------------------:|:-----------------:|:--------------------:| | {ref}`Ackley ` | M | `Ackley()` | +| {ref}`Forrester et al. (2008) ` | 1 | `Forrester2008()` | In a Python terminal, you can list all the available functions relevant for optimization applications using ``list_functions()`` and filter the results diff --git a/docs/test-functions/ackley.md b/docs/test-functions/ackley.md index 693976a..788eea8 100644 --- a/docs/test-functions/ackley.md +++ b/docs/test-functions/ackley.md @@ -171,7 +171,7 @@ plt.gcf().set_dpi(150); ### Optimum values The global optimum value of the Ackley function is -$\mathcal{M}(\boldsymbol{x}^*) = 0$ at $x_m^* = 0,\, m = 1, \ldots, M$. +$\mathcal{M}(\boldsymbol{x}^*) = 0$ at $x_m^* = 0,\, m = 1, \ldots, M$. ## References diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index 8ff0127..1435b90 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -25,6 +25,7 @@ available in the current UQTestFuns, regardless of their typical applications. | {ref}`Bratley et al. (1992) D ` | M | `Bratley1992d()` | | {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | | {ref}`Flood ` | 8 | `Flood()` | +| {ref}`Forrester et al. (2008) ` | 1 | `Forrester2008()` | | {ref}`(1st) Franke ` | 2 | `Franke1()` | | {ref}`(2nd) Franke ` | 2 | `Franke2()` | | {ref}`(3rd) Franke ` | 2 | `Franke3()` | diff --git a/docs/test-functions/forrester.md b/docs/test-functions/forrester.md new file mode 100644 index 0000000..1f41cb4 --- /dev/null +++ b/docs/test-functions/forrester.md @@ -0,0 +1,131 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:forrester)= +# One-dimensional (1D) Forrester et al. (2008) Function + +The 1D Forrester et al. (2008) function (or `Forrester2008` function for short) +is a one-dimensional scalar-valued function. +It was used in {cite}`Forrester2008` as a test function for illustrating +optimization using metamodels. + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +A plot of the function is shown below for $x \in [0, 1]$. + +```{code-cell} ipython3 +:tags: [remove-input] + +my_testfun = uqtf.Forrester2008() +xx = np.linspace(0, 1, 1000)[:, np.newaxis] +yy = my_testfun(xx) + +# --- Create the plot +plt.plot(xx, yy, color="#8da0cb") +plt.grid() +plt.xlabel("$x$") +plt.ylabel("$\mathcal{M}(x)$") +plt.ylim([-10, 15]) +plt.scatter( + np.array([0.14258919, 0.75724876, 0.5240772]), + np.array([-0.98632541, -6.02074006, 0.98632541]), + color="k", + marker="x", + s=50, +) +plt.gcf().tight_layout(pad=3.0) +plt.gcf().set_dpi(150); +``` + +As can be seen in the plot above, the function features a multimodal shape +with one global minimum ($\approx -6.02074006$ at $x = 0.75724876$), +one global maximum ($\approx -0.98632541$ at $x = 014258919$), +and an inflection point with zero gradient ($\approx -6.02074006$ at $x = 0.5240772$). + +## Test function instance + +To create a default instance of the test function: + +```{code-cell} ipython3 +my_testfun = uqtf.Forrester2008() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +## Description + +The test function is analytically defined as follows: + +$$ +\mathcal{M}(x) = (6 x - 2)^2 \sin{(12 x - 4)} +$$ +where $x$ is defined below. + +## Input + +Based on {cite}`Forrester2008`, the search domain of the +function is in $[0, 1]$. +In UQTestFuns, this search domain can be represented as probabilistic input +using the uniform distribution with a marginal shown in the table below. + +```{code-cell} ipython3 +my_testfun.prob_input +``` + +## Reference results + +This section provides several reference results of typical UQ analyses involving +the test function. + +### Sample histogram + +Shown below is the histogram of the output based on $100'000$ random points: + +```{code-cell} ipython3 +:tags: [hide-input] + +np.random.seed(42) +xx_test = my_testfun.prob_input.get_sample(100000) +yy_test = my_testfun(xx_test) + +plt.hist(yy_test, bins="auto", color="#8da0cb"); +plt.grid(); +plt.ylabel("Counts [-]"); +plt.xlabel("$\mathcal{M}(X)$"); +plt.gcf().tight_layout(pad=3.0) +plt.gcf().set_dpi(150); +``` + +### Optimum values + +The optimum values of the function are: + +- Global minimum: $\mathcal{M}(\boldsymbol{x}^*) \approx -6.02074006$ at $x^* = 0.75724876$. +- Local minimum: $\mathcal{M}(\boldsymbol{x}^*) \approx -0.98632541$ at $x^* = 014258919$. +- Inflection: $\mathcal{M}(\boldsymbol{x}^*) \approx -6.02074006$ at $x^* = 0.5240772$. + +## References + +```{bibliography} +:style: unsrtalpha +:filter: docname in docnames +``` diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index 14a0024..0a90c8a 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -5,8 +5,9 @@ from .borehole import Borehole from .bratley1992 import Bratley1992d from .damped_oscillator import DampedOscillator -from .franke import Franke1, Franke2, Franke3, Franke4, Franke5, Franke6 from .flood import Flood +from .forrester import Forrester2008 +from .franke import Franke1, Franke2, Franke3, Franke4, Franke5, Franke6 from .ishigami import Ishigami from .oakley_ohagan_1d import OakleyOHagan1D from .otl_circuit import OTLCircuit @@ -26,6 +27,7 @@ "Bratley1992d", "DampedOscillator", "Flood", + "Forrester2008", "Franke1", "Franke2", "Franke3", diff --git a/src/uqtestfuns/test_functions/forrester.py b/src/uqtestfuns/test_functions/forrester.py new file mode 100644 index 0000000..9ea652a --- /dev/null +++ b/src/uqtestfuns/test_functions/forrester.py @@ -0,0 +1,62 @@ +""" +Module with an implementation of the Forrester et al. (2008) test function. + +The Forrester2008 test function is a one-dimensional scalar-valued function. +The function is multimodal, with a single global minimum, a single local +minimum, and a zero-gradient at the inflection point. + +References +---------- + +1. William J. Welch, Robert J. Buck, Jerome Sacks, Henry P. Wynn, + Toby J. Mitchell, and Max D. Morris, "Screening, predicting, and computer + experiments," Technometrics, vol. 34, no. 1, pp. 15-25, 1992. + DOI: 10.2307/1269548 +""" +import numpy as np + +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecFixDim +from ..core.uqtestfun_abc import UQTestFunABC + +__all__ = ["Forrester2008"] + +AVAILABLE_INPUT_SPECS = { + "Forrester2008": ProbInputSpecFixDim( + name="Forrester2008", + description=( + "Input specification for the 1D test function " + "from Forrester et al. (2008)" + ), + marginals=[ + UnivDistSpec( + name="x", + distribution="uniform", + parameters=[0, 1], + description="None", + ) + ], + copulas=None, + ), +} + + +def evaluate(xx: np.ndarray) -> np.ndarray: + """The evaluation function for the Forrester et al. (2008) function. + + Parameters + ---------- + + """ + return (6 * xx - 2) ** 2 * np.sin(12 * xx - 4) + + +class Forrester2008(UQTestFunABC): + """An implementation of the 1D function of Forrester et al. (2008).""" + + _tags = ["optimization", "metamodeling"] + _description = "One-dimensional function from Forrester et al. (2008)" + _available_inputs = AVAILABLE_INPUT_SPECS + _available_parameters = None + _default_spatial_dimension = 1 + + eval_ = staticmethod(evaluate) From 5743b12ff6823e23d7f5ca023d3bbfc6acb86b9d Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Mon, 19 Jun 2023 14:57:46 +0200 Subject: [PATCH 20/73] Add an implementation of the test function of Forrester et al. (2008) - The function is a one-dimensional test function used in the context of optimization using metamodels. --- CHANGELOG.md | 3 + docs/_toc.yml | 2 + docs/fundamentals/metamodeling.md | 1 + docs/fundamentals/optimization.md | 1 + docs/test-functions/ackley.md | 2 +- docs/test-functions/available.md | 1 + docs/test-functions/forrester.md | 131 +++++++++++++++++++++ src/uqtestfuns/test_functions/__init__.py | 4 +- src/uqtestfuns/test_functions/forrester.py | 62 ++++++++++ 9 files changed, 205 insertions(+), 2 deletions(-) create mode 100644 docs/test-functions/forrester.md create mode 100644 src/uqtestfuns/test_functions/forrester.py diff --git a/CHANGELOG.md b/CHANGELOG.md index fed25f3..27d1137 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 (with only `evaluate()` and `ProbInput`), while the latter is used to implement _published_ UQ test functions in the code base (i.e., with additional metadata such as tags and description). +- An implementation of the one-dimensional function from Forrester et al. + (2008). The function was used as a test function for optimization approaches + using metamodels. ## [0.1.1] - 2023-07-03 diff --git a/docs/_toc.yml b/docs/_toc.yml index c289cb2..68ca6e1 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -33,6 +33,8 @@ parts: title: Damped Oscillator - file: test-functions/flood title: Flood + - file: test-functions/forrester + title: Forrester et al. (2008) - file: test-functions/franke-1 title: (1st) Franke - file: test-functions/franke-2 diff --git a/docs/fundamentals/metamodeling.md b/docs/fundamentals/metamodeling.md index e5e60d3..afb938a 100644 --- a/docs/fundamentals/metamodeling.md +++ b/docs/fundamentals/metamodeling.md @@ -23,6 +23,7 @@ in the comparison of metamodeling approaches. | {ref}`Borehole ` | 8 | `Borehole()` | | {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | | {ref}`Flood ` | 8 | `Flood()` | +| {ref}`Forrester et al. (2008) ` | 1 | `Forrester2008()` | | {ref}`(1st) Franke ` | 2 | `Franke1()` | | {ref}`(2nd) Franke ` | 2 | `Franke2()` | | {ref}`(3rd) Franke ` | 2 | `Franke3()` | diff --git a/docs/fundamentals/optimization.md b/docs/fundamentals/optimization.md index 1455007..1f26f06 100644 --- a/docs/fundamentals/optimization.md +++ b/docs/fundamentals/optimization.md @@ -20,6 +20,7 @@ in the comparison of global optimization methods. | Name | Spatial Dimension | Constructor | |:-----------------------------------------------------------:|:-----------------:|:--------------------:| | {ref}`Ackley ` | M | `Ackley()` | +| {ref}`Forrester et al. (2008) ` | 1 | `Forrester2008()` | In a Python terminal, you can list all the available functions relevant for optimization applications using ``list_functions()`` and filter the results diff --git a/docs/test-functions/ackley.md b/docs/test-functions/ackley.md index 693976a..788eea8 100644 --- a/docs/test-functions/ackley.md +++ b/docs/test-functions/ackley.md @@ -171,7 +171,7 @@ plt.gcf().set_dpi(150); ### Optimum values The global optimum value of the Ackley function is -$\mathcal{M}(\boldsymbol{x}^*) = 0$ at $x_m^* = 0,\, m = 1, \ldots, M$. +$\mathcal{M}(\boldsymbol{x}^*) = 0$ at $x_m^* = 0,\, m = 1, \ldots, M$. ## References diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index 8ff0127..1435b90 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -25,6 +25,7 @@ available in the current UQTestFuns, regardless of their typical applications. | {ref}`Bratley et al. (1992) D ` | M | `Bratley1992d()` | | {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | | {ref}`Flood ` | 8 | `Flood()` | +| {ref}`Forrester et al. (2008) ` | 1 | `Forrester2008()` | | {ref}`(1st) Franke ` | 2 | `Franke1()` | | {ref}`(2nd) Franke ` | 2 | `Franke2()` | | {ref}`(3rd) Franke ` | 2 | `Franke3()` | diff --git a/docs/test-functions/forrester.md b/docs/test-functions/forrester.md new file mode 100644 index 0000000..1f41cb4 --- /dev/null +++ b/docs/test-functions/forrester.md @@ -0,0 +1,131 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:forrester)= +# One-dimensional (1D) Forrester et al. (2008) Function + +The 1D Forrester et al. (2008) function (or `Forrester2008` function for short) +is a one-dimensional scalar-valued function. +It was used in {cite}`Forrester2008` as a test function for illustrating +optimization using metamodels. + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +A plot of the function is shown below for $x \in [0, 1]$. + +```{code-cell} ipython3 +:tags: [remove-input] + +my_testfun = uqtf.Forrester2008() +xx = np.linspace(0, 1, 1000)[:, np.newaxis] +yy = my_testfun(xx) + +# --- Create the plot +plt.plot(xx, yy, color="#8da0cb") +plt.grid() +plt.xlabel("$x$") +plt.ylabel("$\mathcal{M}(x)$") +plt.ylim([-10, 15]) +plt.scatter( + np.array([0.14258919, 0.75724876, 0.5240772]), + np.array([-0.98632541, -6.02074006, 0.98632541]), + color="k", + marker="x", + s=50, +) +plt.gcf().tight_layout(pad=3.0) +plt.gcf().set_dpi(150); +``` + +As can be seen in the plot above, the function features a multimodal shape +with one global minimum ($\approx -6.02074006$ at $x = 0.75724876$), +one global maximum ($\approx -0.98632541$ at $x = 014258919$), +and an inflection point with zero gradient ($\approx -6.02074006$ at $x = 0.5240772$). + +## Test function instance + +To create a default instance of the test function: + +```{code-cell} ipython3 +my_testfun = uqtf.Forrester2008() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +## Description + +The test function is analytically defined as follows: + +$$ +\mathcal{M}(x) = (6 x - 2)^2 \sin{(12 x - 4)} +$$ +where $x$ is defined below. + +## Input + +Based on {cite}`Forrester2008`, the search domain of the +function is in $[0, 1]$. +In UQTestFuns, this search domain can be represented as probabilistic input +using the uniform distribution with a marginal shown in the table below. + +```{code-cell} ipython3 +my_testfun.prob_input +``` + +## Reference results + +This section provides several reference results of typical UQ analyses involving +the test function. + +### Sample histogram + +Shown below is the histogram of the output based on $100'000$ random points: + +```{code-cell} ipython3 +:tags: [hide-input] + +np.random.seed(42) +xx_test = my_testfun.prob_input.get_sample(100000) +yy_test = my_testfun(xx_test) + +plt.hist(yy_test, bins="auto", color="#8da0cb"); +plt.grid(); +plt.ylabel("Counts [-]"); +plt.xlabel("$\mathcal{M}(X)$"); +plt.gcf().tight_layout(pad=3.0) +plt.gcf().set_dpi(150); +``` + +### Optimum values + +The optimum values of the function are: + +- Global minimum: $\mathcal{M}(\boldsymbol{x}^*) \approx -6.02074006$ at $x^* = 0.75724876$. +- Local minimum: $\mathcal{M}(\boldsymbol{x}^*) \approx -0.98632541$ at $x^* = 014258919$. +- Inflection: $\mathcal{M}(\boldsymbol{x}^*) \approx -6.02074006$ at $x^* = 0.5240772$. + +## References + +```{bibliography} +:style: unsrtalpha +:filter: docname in docnames +``` diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index 14a0024..0a90c8a 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -5,8 +5,9 @@ from .borehole import Borehole from .bratley1992 import Bratley1992d from .damped_oscillator import DampedOscillator -from .franke import Franke1, Franke2, Franke3, Franke4, Franke5, Franke6 from .flood import Flood +from .forrester import Forrester2008 +from .franke import Franke1, Franke2, Franke3, Franke4, Franke5, Franke6 from .ishigami import Ishigami from .oakley_ohagan_1d import OakleyOHagan1D from .otl_circuit import OTLCircuit @@ -26,6 +27,7 @@ "Bratley1992d", "DampedOscillator", "Flood", + "Forrester2008", "Franke1", "Franke2", "Franke3", diff --git a/src/uqtestfuns/test_functions/forrester.py b/src/uqtestfuns/test_functions/forrester.py new file mode 100644 index 0000000..9ea652a --- /dev/null +++ b/src/uqtestfuns/test_functions/forrester.py @@ -0,0 +1,62 @@ +""" +Module with an implementation of the Forrester et al. (2008) test function. + +The Forrester2008 test function is a one-dimensional scalar-valued function. +The function is multimodal, with a single global minimum, a single local +minimum, and a zero-gradient at the inflection point. + +References +---------- + +1. William J. Welch, Robert J. Buck, Jerome Sacks, Henry P. Wynn, + Toby J. Mitchell, and Max D. Morris, "Screening, predicting, and computer + experiments," Technometrics, vol. 34, no. 1, pp. 15-25, 1992. + DOI: 10.2307/1269548 +""" +import numpy as np + +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecFixDim +from ..core.uqtestfun_abc import UQTestFunABC + +__all__ = ["Forrester2008"] + +AVAILABLE_INPUT_SPECS = { + "Forrester2008": ProbInputSpecFixDim( + name="Forrester2008", + description=( + "Input specification for the 1D test function " + "from Forrester et al. (2008)" + ), + marginals=[ + UnivDistSpec( + name="x", + distribution="uniform", + parameters=[0, 1], + description="None", + ) + ], + copulas=None, + ), +} + + +def evaluate(xx: np.ndarray) -> np.ndarray: + """The evaluation function for the Forrester et al. (2008) function. + + Parameters + ---------- + + """ + return (6 * xx - 2) ** 2 * np.sin(12 * xx - 4) + + +class Forrester2008(UQTestFunABC): + """An implementation of the 1D function of Forrester et al. (2008).""" + + _tags = ["optimization", "metamodeling"] + _description = "One-dimensional function from Forrester et al. (2008)" + _available_inputs = AVAILABLE_INPUT_SPECS + _available_parameters = None + _default_spatial_dimension = 1 + + eval_ = staticmethod(evaluate) From 6fda996e178f974988ad439100c86e621c1309f7 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Thu, 22 Jun 2023 09:35:29 +0200 Subject: [PATCH 21/73] One-dimensional test functions now return one-dimensional array - Previously, all one-dimensional test functions return two-dimensional arrays with a single column; this behavior is inconsistent with the other test functions. --- src/uqtestfuns/test_functions/forrester.py | 4 +++- src/uqtestfuns/test_functions/oakley_ohagan_1d.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/uqtestfuns/test_functions/forrester.py b/src/uqtestfuns/test_functions/forrester.py index 9ea652a..1b50971 100644 --- a/src/uqtestfuns/test_functions/forrester.py +++ b/src/uqtestfuns/test_functions/forrester.py @@ -47,7 +47,9 @@ def evaluate(xx: np.ndarray) -> np.ndarray: ---------- """ - return (6 * xx - 2) ** 2 * np.sin(12 * xx - 4) + yy = (6 * xx[:, 0] - 2) ** 2 * np.sin(12 * xx[:, 0] - 4) + + return yy class Forrester2008(UQTestFunABC): diff --git a/src/uqtestfuns/test_functions/oakley_ohagan_1d.py b/src/uqtestfuns/test_functions/oakley_ohagan_1d.py index 05e83f2..17bb373 100644 --- a/src/uqtestfuns/test_functions/oakley_ohagan_1d.py +++ b/src/uqtestfuns/test_functions/oakley_ohagan_1d.py @@ -56,7 +56,7 @@ def evaluate(xx: np.ndarray) -> np.ndarray: on the input values. The output is a 1-dimensional array of length N. """ - yy = 5 + xx + np.cos(xx) + yy = 5 + xx[:, 0] + np.cos(xx[:, 0]) return yy From 3008271bf7fc0e3f8bfd223e86936c9f4cfa1d0c Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Thu, 22 Jun 2023 09:54:34 +0200 Subject: [PATCH 22/73] Change the date in CHANGELOG.md - The ISO format YYYY-MM-DD has been adopted. --- CHANGELOG.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27d1137..3ed4fda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 (2008). The function was used as a test function for optimization approaches using metamodels. -## [0.1.1] - 2023-07-03 +### Changed + +- The date format in CHANGELOG.md has been changed from YYYY-DD-MM to the + ISO format YYYY-MM-DD. + +## [0.1.1] - 2023-03-07 ### Fixed @@ -39,7 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 latest version of UQTestFuns that was planned for v0.1.0 is now v0.1.1 - Missing link in CHANGELOG.md -## [0.1.0] - 2023-07-03 +## [0.1.0] - 2023-03-07 ### Added @@ -60,7 +65,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Relax the numerical tolerance of a test (i.e., univariate beta distribution) - Minor edit in the docs -## [0.0.1] - 2023-06-03 +## [0.0.1] - 2023-03-06 First public release of UQTestFuns. From 0d94d4727d42e8b7cfaf238aed1429c264bfcfdf Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Thu, 22 Jun 2023 13:54:12 +0200 Subject: [PATCH 23/73] Add the integral test function #2 from Bratley et al. (1992) - The function is the product of a trigonometric function. --- docs/_toc.yml | 2 + docs/fundamentals/integration.md | 1 + docs/test-functions/available.md | 1 + docs/test-functions/bratley1992b.md | 237 +++++++++++++++++++ docs/test-functions/bratley1992d.md | 7 +- src/uqtestfuns/test_functions/__init__.py | 3 +- src/uqtestfuns/test_functions/bratley1992.py | 52 +++- 7 files changed, 299 insertions(+), 4 deletions(-) create mode 100644 docs/test-functions/bratley1992b.md diff --git a/docs/_toc.yml b/docs/_toc.yml index 68ca6e1..1cc2007 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -27,6 +27,8 @@ parts: title: Ackley - file: test-functions/borehole title: Borehole + - file: test-functions/bratley1992b + title: Bratley et al. (1992) B - file: test-functions/bratley1992d title: Bratley et al. (1992) D - file: test-functions/damped-oscillator diff --git a/docs/fundamentals/integration.md b/docs/fundamentals/integration.md index 5bd5d8d..ef268ab 100644 --- a/docs/fundamentals/integration.md +++ b/docs/fundamentals/integration.md @@ -19,6 +19,7 @@ in the testing and comparison of numerical integration method. | Name | Spatial Dimension | Constructor | |:------------------------------------------------------------:|:-----------------:|:----------------:| +| {ref}`Bratley et al. (1992) B ` | M | `Bratley1992b()` | | {ref}`Bratley et al. (1992) D ` | M | `Bratley1992d()` | | {ref}`Sobol'-G ` | M | `SobolG()` | | {ref}`Welch et al. (1992) ` | 20 | `Welch1992()` | diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index 1435b90..d9ec41d 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -22,6 +22,7 @@ available in the current UQTestFuns, regardless of their typical applications. |:------------------------------------------------------------:|:-----------------:|:--------------------:| | {ref}`Ackley ` | M | `Ackley()` | | {ref}`Borehole ` | 8 | `Borehole()` | +| {ref}`Bratley et al. (1992) B ` | M | `Bratley1992b()` | | {ref}`Bratley et al. (1992) D ` | M | `Bratley1992d()` | | {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | | {ref}`Flood ` | 8 | `Flood()` | diff --git a/docs/test-functions/bratley1992b.md b/docs/test-functions/bratley1992b.md new file mode 100644 index 0000000..c7ddca3 --- /dev/null +++ b/docs/test-functions/bratley1992b.md @@ -0,0 +1,237 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:bratley1992b)= +# Bratley et al. (1992) B function + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +The Bratley et al. (1992) B function (or `Bratley1992b` function for short), +is an $M$-dimensional scalar-valued function. +The function was introduced in {cite}`Bratley1992` as a test function +for multi-dimensional numerical integration using low discrepancy sequences. + +```{note} +There are four other test functions used in Bratley et al. {cite}`Bratley1992`: + +- {ref}`Bratley et al. (1992) B `: + A product of trigonometric function (_this function_) +- {ref}`Bratley et al. (1992) D `: + A sum of product +``` + +The plots for one-dimensional and two-dimensional `Bratley1992b` functions +are shown below. + +```{code-cell} ipython3 +:tags: [remove-input] + +from mpl_toolkits.axes_grid1 import make_axes_locatable + +# --- Create 1D data +my_bratley1992b_1d = uqtf.Bratley1992b(spatial_dimension=1) +xx_1d = np.linspace(0, 1, 1000)[:, np.newaxis] +yy_1d = my_bratley1992b_1d(xx_1d) + +# --- Create 2D data +my_bratley1992b_2d = uqtf.Bratley1992b(spatial_dimension=2) +mesh_2d = np.meshgrid(xx_1d, xx_1d) +xx_2d = np.array(mesh_2d).T.reshape(-1, 2) +yy_2d = my_bratley1992b_2d(xx_2d) + +# --- Create a series of plots +fig = plt.figure(figsize=(15, 5)) + +# 1D +axs_1 = plt.subplot(131) +axs_1.plot(xx_1d, yy_1d, color="#8da0cb") +axs_1.grid() +axs_1.set_xlabel("$x$", fontsize=14) +axs_1.set_ylabel("$\mathcal{M}(x)$", fontsize=14) +axs_1.set_title("1D Bratley1992b") + +# Surface +axs_2 = plt.subplot(132, projection='3d') +axs_2.plot_surface( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000, 1000).T, + cmap="plasma", + linewidth=0, + antialiased=False, + alpha=0.5 +) +axs_2.set_xlabel("$x_1$", fontsize=14) +axs_2.set_ylabel("$x_2$", fontsize=14) +axs_2.set_zlabel("$\mathcal{M}(x_1, x_2)$", fontsize=14) +axs_2.set_title("Surface plot of 2D Bratley1992b", fontsize=14) + +# Contour +axs_3 = plt.subplot(133) +cf = axs_3.contourf( + mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000).T, cmap="plasma" +) +axs_3.set_xlabel("$x_1$", fontsize=14) +axs_3.set_ylabel("$x_2$", fontsize=14) +axs_3.set_title("Contour plot of 2D Bratley1992b", fontsize=14) +divider = make_axes_locatable(axs_3) +cax = divider.append_axes('right', size='5%', pad=0.05) +fig.colorbar(cf, cax=cax, orientation='vertical') +axs_3.axis('scaled') + +fig.tight_layout(pad=3.0) +plt.gcf().set_dpi(150); +``` + +## Test function instance + +To create a default instance of the test function: + +```{code-cell} ipython3 +my_testfun = uqtf.Bratley1992b() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +By default, the spatial dimension is set to $2$[^default_dimension]. +To create an instance with another value of spatial dimension, +pass an integer to the parameter `spatial_dimension` (keyword only). +For example, to create an instance of 10-dimensional `Bratley1992b` function, +type: + +```{code-cell} ipython3 +my_testfun = uqtf.Bratley1992b(spatial_dimension=10) +``` + +## Description + +The `Bratley1992b` function is defined as follows[^location]: + +$$ +\mathcal{M}(\boldsymbol{x}) = \prod_{m = 1}^{M} m \cos{(m x)}, +$$ + +where $\boldsymbol{x} = \{ x_1, \ldots, x_M \}$ +is the $M$-dimensional vector of input variables further defined below. + +## Probabilistic input + +Based on {cite}`Bratley1992`, the test function is integrated over the +hypercube domain of $[0, 1]^M$. +Such an input specification can be modeled using an $M$ independent uniform +random variables as shown in the table below. + +| No. | Name | Distribution | Parameters | Description | +|:---------:|:--------:|:-------------:|:----------:|:-----------:| +| 1 | $x_1$ | uniform | [0.0 1.0] | N/A | +| $\vdots$ | $\vdots$ | $\vdots$ | $\vdots$ | $\vdots$ | +| M | $x_M$ | uniform | [0.0 1.0] | N/A | + + +## Reference results + +This section provides several reference results of typical UQ analyses involving +the test function. + +### Definite integration + +The integral value of the function over the domain of $[0.0, 1.0]^M$ +is analytical: + +$$ +I[\mathcal{M}] (M) \equiv \int_{[0, 1]^M} \mathcal{M}(\boldsymbol{x}) \; d\boldsymbol{x} = \prod_{m = 1}^M \sin{(m)}. +$$ + +The table below shows the numerical values of the integral +for several selected dimensions. + +| Dimension | $I[\mathcal{M}]$ | +|:---------:|:---------------------------:| +| 1 | $8.4147098 \times 10^{-1}$ | +| 2 | $7.6514740 \times 10^{-1}$ | +| 3 | $1.0797761 \times 10^{-1}$ | +| 4 | $-8.1717723 \times 10^{-2}$ | +| 5 | $7.8361108 \times 10^{-2}$ | +| 6 | $-2.1895308 \times 10^{-2}$ | +| 7 | $-1.4384924 \times 10^{-2}$ | +| 8 | $-1.4231843 \times 10^{-2}$ | +| 9 | $-5.8652056 \times 10^{-3}$ | +| 10 | $3.1907957 \times 10^{-3}$ | + +The absolute value of the integral is monotonically decreasing function +of the number of dimensions. Asymptotically, it is zero. +In the original paper of Bratley {cite}`Bratley1992`, the integration was +carried out for the function in dimension eight. + +### Moments + +The moments of the test function are analytically known +and the first two moments are given below. + +#### Expected value + +Due to the domain being a hypercube, +the above integral value over the domain is the same as the expected value: + +$$ +\mathbb{E}[\mathcal{M}](M) = \prod_{m = 1}^M \sin{(m)}. +$$ + +#### Variance + +The analytical value for the variance is given as follows: + +$$ +\mathbb{V}[\mathcal{M}](M) = \frac{1}{4} \prod_{m = 1}^M m (2 m + \sin{(2m)}) - \left( \prod_{m = 1}^M \sin{m} \right)^2. +$$ + +The table below shows the numerical values of the variance +for several selected dimensions. + +| Dimension | $I[\mathcal{M}]$ | +|:---------:|:--------------------------:| +| 1 | $1.9250938 \times 10^{-2}$ | +| 2 | $5.9397772 \times 10^{-1}$ | +| 3 | $5.0486051 \times 10^{0}$ | +| 4 | $4.5481851 \times 10^{1}$ | +| 5 | $5.3766707 \times 10^{2}$ | +| 6 | $9.245366 \times 10^{3}$ | +| 7 | $2.4253890 \times 10^{5}$ | +| 8 | $7.6215893 \times 10^{6}$ | +| 9 | $2.9579601 \times 10^{8}$ | +| 10 | $1.5464914 \times 10^{10}$ | + +The variance grows as the number of dimensions and becomes unbounded +for a very large dimension. + +## References + +```{bibliography} +:style: unsrtalpha +:filter: docname in docnames +``` + +[^location]: see Section 5.1, p. 207 (test function no. 2) +in {cite}`Bratley1992`. + +[^default_dimension]: This default dimension applies to all variable dimension +test functions. It will be used if the `spatial_dimension` argument is not given. diff --git a/docs/test-functions/bratley1992d.md b/docs/test-functions/bratley1992d.md index 731a637..43eaca4 100644 --- a/docs/test-functions/bratley1992d.md +++ b/docs/test-functions/bratley1992d.md @@ -29,7 +29,12 @@ It was used in {cite}`Kucherenko2009` and {cite}`Saltelli2010` in the context of global sensitivity analysis. ```{note} -There are four other test functions used in Bratley et al. {cite}`Bratley1992`. +There are four other test functions used in Bratley et al. {cite}`Bratley1992`: + +- {ref}`Bratley et al. (1992) B `: + A product of trigonometric function +- {ref}`Bratley et al. (1992) D `: + A sum of product (_this function_) ``` The plots for one-dimensional and two-dimensional `Bratley1992d` functions diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index 0a90c8a..4bb5b22 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -3,7 +3,7 @@ """ from .ackley import Ackley from .borehole import Borehole -from .bratley1992 import Bratley1992d +from .bratley1992 import Bratley1992b, Bratley1992d from .damped_oscillator import DampedOscillator from .flood import Flood from .forrester import Forrester2008 @@ -24,6 +24,7 @@ __all__ = [ "Ackley", "Borehole", + "Bratley1992b", "Bratley1992d", "DampedOscillator", "Flood", diff --git a/src/uqtestfuns/test_functions/bratley1992.py b/src/uqtestfuns/test_functions/bratley1992.py index 6709481..abd6870 100644 --- a/src/uqtestfuns/test_functions/bratley1992.py +++ b/src/uqtestfuns/test_functions/bratley1992.py @@ -8,6 +8,8 @@ The four functions are: +- Bratley1992b: A product of trigonometric function; + integration test function #2 - Bratley1992d: A sum of products integrand; integration test function #4 The Bratley1992d function may also be referred to as the "Bratley function" @@ -37,7 +39,7 @@ from ..core.uqtestfun_abc import UQTestFunABC from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecVarDim -__all__ = ["Bratley1992d"] +__all__ = ["Bratley1992b", "Bratley1992d"] def _bratley_input(spatial_dimension: int) -> List[UnivDistSpec]: @@ -88,6 +90,49 @@ def _bratley_input(spatial_dimension: int) -> List[UnivDistSpec]: ) +def evaluate_bratley1992b(xx: np.ndarray): + """Evaluate the test function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + M-Dimensional input values given by an N-by-M array where + N is the number of input values. + + Returns + ------- + np.ndarray + The output of the test function evaluated on the input values. + The output is a 1-dimensional array of length N. + """ + + num_dim = xx.shape[1] + + # Compute the function + ii = np.arange(1, num_dim + 1) + yy = np.prod(ii * np.cos(ii * xx), axis=1) + + return yy + + +class Bratley1992b(UQTestFunABC): + """An implementation of the test function 2 from Bratley et al. (1992). + + The function (used as an integrand) is a product of a trigonometric + function. + """ + + _tags = COMMON_METADATA["_tags"] + _description = ( + f"Integration test function #2 {COMMON_METADATA['_description']}" + ) + _available_inputs = COMMON_METADATA["_available_inputs"] + _available_parameters = COMMON_METADATA["_available_parameters"] + _default_spatial_dimension = None + + eval_ = staticmethod(evaluate_bratley1992b) + + def evaluate_bratley1992d(xx: np.ndarray): """Evaluate the test function on a set of input values. @@ -115,7 +160,10 @@ def evaluate_bratley1992d(xx: np.ndarray): class Bratley1992d(UQTestFunABC): - """An implementation of the test function 4 from Bratley et al. (1988).""" + """An implementation of the test function 4 from Bratley et al. (1992). + + The function (used as an integrand) is a sum of products. + """ _tags = COMMON_METADATA["_tags"] _description = ( From c142c5ab2a9c15f6520706613a1889820e322ff5 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Thu, 22 Jun 2023 15:33:47 +0200 Subject: [PATCH 24/73] Add the integral test function #3 from Bratley et al. (1992) - The function is a product of Chebyshev polynomials of the first kind (with different degrees). --- CHANGELOG.md | 2 +- docs/_toc.yml | 2 + docs/fundamentals/integration.md | 13 +- docs/test-functions/available.md | 1 + docs/test-functions/bratley1992b.md | 2 + docs/test-functions/bratley1992c.md | 185 +++++++++++++++++++ docs/test-functions/bratley1992d.md | 2 + src/uqtestfuns/test_functions/__init__.py | 3 +- src/uqtestfuns/test_functions/bratley1992.py | 48 ++++- 9 files changed, 249 insertions(+), 9 deletions(-) create mode 100644 docs/test-functions/bratley1992c.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ed4fda..85f8389 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - An implementation of the Welch et al. (1992) test function, a 20-dimensional function used in the context of metamodeling and sensitivity analysis. -- An implementation of M-dimensional test functions (#4) from Bratley et al. +- Three M-dimensional test functions (#2, #3, #4) from Bratley et al. (1992) useful for testing multi-dimensional numerical integrations as well as global sensitivity analysis. - Two base classes are now available `UQTestFunBareABC` and `UQTestFunABC`. diff --git a/docs/_toc.yml b/docs/_toc.yml index 1cc2007..27276cc 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -29,6 +29,8 @@ parts: title: Borehole - file: test-functions/bratley1992b title: Bratley et al. (1992) B + - file: test-functions/bratley1992c + title: Bratley et al. (1992) C - file: test-functions/bratley1992d title: Bratley et al. (1992) D - file: test-functions/damped-oscillator diff --git a/docs/fundamentals/integration.md b/docs/fundamentals/integration.md index ef268ab..14c7a0c 100644 --- a/docs/fundamentals/integration.md +++ b/docs/fundamentals/integration.md @@ -17,12 +17,13 @@ kernelspec: The table below listed the available test functions typically used in the testing and comparison of numerical integration method. -| Name | Spatial Dimension | Constructor | -|:------------------------------------------------------------:|:-----------------:|:----------------:| -| {ref}`Bratley et al. (1992) B ` | M | `Bratley1992b()` | -| {ref}`Bratley et al. (1992) D ` | M | `Bratley1992d()` | -| {ref}`Sobol'-G ` | M | `SobolG()` | -| {ref}`Welch et al. (1992) ` | 20 | `Welch1992()` | +| Name | Spatial Dimension | Constructor | +|:------------------------------------------------------------:|:-----------------:|:------------------:| +| {ref}`Bratley et al. (1992) B ` | M | `Bratley1992b()` | +| {ref}`Bratley et al. (1992) C ` | M | `Bratley1992c()` | +| {ref}`Bratley et al. (1992) D ` | M | `Bratley1992d()` | +| {ref}`Sobol'-G ` | M | `SobolG()` | +| {ref}`Welch et al. (1992) ` | 20 | `Welch1992()` | In a Python terminal, you can list all the available functions relevant for metamodeling applications using ``list_functions()`` and filter the results diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index d9ec41d..9351f66 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -23,6 +23,7 @@ available in the current UQTestFuns, regardless of their typical applications. | {ref}`Ackley ` | M | `Ackley()` | | {ref}`Borehole ` | 8 | `Borehole()` | | {ref}`Bratley et al. (1992) B ` | M | `Bratley1992b()` | +| {ref}`Bratley et al. (1992) C ` | M | `Bratley1992c()` | | {ref}`Bratley et al. (1992) D ` | M | `Bratley1992d()` | | {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | | {ref}`Flood ` | 8 | `Flood()` | diff --git a/docs/test-functions/bratley1992b.md b/docs/test-functions/bratley1992b.md index c7ddca3..b9f806f 100644 --- a/docs/test-functions/bratley1992b.md +++ b/docs/test-functions/bratley1992b.md @@ -31,6 +31,8 @@ There are four other test functions used in Bratley et al. {cite}`Bratley1992`: - {ref}`Bratley et al. (1992) B `: A product of trigonometric function (_this function_) +- {ref}`Bratley et al. (1992) C `: + A product of the Chebyshev polynomial of the first kind - {ref}`Bratley et al. (1992) D `: A sum of product ``` diff --git a/docs/test-functions/bratley1992c.md b/docs/test-functions/bratley1992c.md new file mode 100644 index 0000000..c146570 --- /dev/null +++ b/docs/test-functions/bratley1992c.md @@ -0,0 +1,185 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:bratley1992c)= +# Bratley et al. (1992) C function + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +The Bratley et al. (1992) C function (or `Bratley1992c` function for short), +is an $M$-dimensional scalar-valued function. +The function was introduced in {cite}`Bratley1992` as a test function +for multi-dimensional numerical integration using low discrepancy sequences. + +```{note} +There are four other test functions used in Bratley et al. {cite}`Bratley1992`: + +- {ref}`Bratley et al. (1992) B `: + A product of trigonometric function +- {ref}`Bratley et al. (1992) C `: + A product of the Chebyshev polynomial of the first kind (_this function_) +- {ref}`Bratley et al. (1992) D `: + A sum of product +``` + +The plots for one-dimensional and two-dimensional `Bratley1992c` functions +are shown below. + +```{code-cell} ipython3 +:tags: [remove-input] + +from mpl_toolkits.axes_grid1 import make_axes_locatable + +# --- Create 1D data +my_bratley1992c_1d = uqtf.Bratley1992c(spatial_dimension=1) +xx_1d = np.linspace(0, 1, 1000)[:, np.newaxis] +yy_1d = my_bratley1992c_1d(xx_1d) + +# --- Create 2D data +my_bratley1992c_2d = uqtf.Bratley1992c(spatial_dimension=2) +mesh_2d = np.meshgrid(xx_1d, xx_1d) +xx_2d = np.array(mesh_2d).T.reshape(-1, 2) +yy_2d = my_bratley1992c_2d(xx_2d) + +# --- Create a series of plots +fig = plt.figure(figsize=(15, 5)) + +# 1D +axs_1 = plt.subplot(131) +axs_1.plot(xx_1d, yy_1d, color="#8da0cb") +axs_1.grid() +axs_1.set_xlabel("$x$", fontsize=14) +axs_1.set_ylabel("$\mathcal{M}(x)$", fontsize=14) +axs_1.set_title("1D Bratley1992d") + +# Surface +axs_2 = plt.subplot(132, projection='3d') +axs_2.plot_surface( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000, 1000).T, + cmap="plasma", + linewidth=0, + antialiased=False, + alpha=0.5 +) +axs_2.set_xlabel("$x_1$", fontsize=14) +axs_2.set_ylabel("$x_2$", fontsize=14) +axs_2.set_zlabel("$\mathcal{M}(x_1, x_2)$", fontsize=14) +axs_2.set_title("Surface plot of 2D Bratley1992d", fontsize=14) + +# Contour +axs_3 = plt.subplot(133) +cf = axs_3.contourf( + mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000).T, cmap="plasma" +) +axs_3.set_xlabel("$x_1$", fontsize=14) +axs_3.set_ylabel("$x_2$", fontsize=14) +axs_3.set_title("Contour plot of 2D Bratley1992d", fontsize=14) +divider = make_axes_locatable(axs_3) +cax = divider.append_axes('right', size='5%', pad=0.05) +fig.colorbar(cf, cax=cax, orientation='vertical') +axs_3.axis('scaled') + +fig.tight_layout(pad=3.0) +plt.gcf().set_dpi(150); +``` + +## Test function instance + +To create a default instance of the test function: + +```{code-cell} ipython3 +my_testfun = uqtf.Bratley1992c() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +By default, the spatial dimension is set to $2$[^default_dimension]. +To create an instance with another value of spatial dimension, +pass an integer to the parameter `spatial_dimension` (keyword only). +For example, to create an instance of 10-dimensional `Bratley1992c` function, +type: + +```{code-cell} ipython3 +my_testfun = uqtf.Bratley1992d(spatial_dimension=10) +``` + +## Description + +The `Bratley1992d` function is defined as follows[^location]: + +$$ +\mathcal{M}(\boldsymbol{x}) = \prod_{m = 1}^M T_{n_m} (2 x_m - 1) +$$ + +where $n_m = m \bmod 4 + 1$ and +$\boldsymbol{x} = \{ x_1, \ldots, x_M \}$ +is the $M$-dimensional vector of input variables further defined below. + +## Probabilistic input + +Based on {cite}`Bratley1992`, the test function is integrated over the +hypercube domain of $[0, 1]^M$. +Such an input specification can be modeled using an $M$ independent uniform +random variables as shown in the table below. + +| No. | Name | Distribution | Parameters | Description | +|:---------:|:--------:|:-------------:|:----------:|:-----------:| +| 1 | $x_1$ | uniform | [0.0 1.0] | N/A | +| $\vdots$ | $\vdots$ | $\vdots$ | $\vdots$ | $\vdots$ | +| M | $x_M$ | uniform | [0.0 1.0] | N/A | + + +## Reference results + +This section provides several reference results of typical UQ analyses involving +the test function. + +### Definite integration + +The integral value of the function over the domain of $[0.0, 1.0]^M$ +is analytical: + +$$ +I[\mathcal{M}] (M) \equiv \int_{[0, 1]^M} \mathcal{M}(\boldsymbol{x}) \; d\boldsymbol{x} = +\begin{cases} +-\frac{1}{3}, & M = 1 \\ +0, & M \neq 1 +\end{cases}. +$$ + +Due to the domain being a hypercube, +the above integral value over the domain is the same as the expected value. + +## References + +```{bibliography} +:style: unsrtalpha +:filter: docname in docnames +``` + +[^location]: see Section 5.1, p. 207 (test function no. 3) +in {cite}`Bratley1992`. + +[^default_dimension]: This default dimension applies to all variable dimension +test functions. It will be used if the `spatial_dimension` argument is not given. diff --git a/docs/test-functions/bratley1992d.md b/docs/test-functions/bratley1992d.md index 43eaca4..afa8c3a 100644 --- a/docs/test-functions/bratley1992d.md +++ b/docs/test-functions/bratley1992d.md @@ -33,6 +33,8 @@ There are four other test functions used in Bratley et al. {cite}`Bratley1992`: - {ref}`Bratley et al. (1992) B `: A product of trigonometric function +- {ref}`Bratley et al. (1992) C `: + A product of the Chebyshev polynomial of the first kind - {ref}`Bratley et al. (1992) D `: A sum of product (_this function_) ``` diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index 4bb5b22..d536e4c 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -3,7 +3,7 @@ """ from .ackley import Ackley from .borehole import Borehole -from .bratley1992 import Bratley1992b, Bratley1992d +from .bratley1992 import Bratley1992b, Bratley1992c, Bratley1992d from .damped_oscillator import DampedOscillator from .flood import Flood from .forrester import Forrester2008 @@ -25,6 +25,7 @@ "Ackley", "Borehole", "Bratley1992b", + "Bratley1992c", "Bratley1992d", "DampedOscillator", "Flood", diff --git a/src/uqtestfuns/test_functions/bratley1992.py b/src/uqtestfuns/test_functions/bratley1992.py index abd6870..4799da7 100644 --- a/src/uqtestfuns/test_functions/bratley1992.py +++ b/src/uqtestfuns/test_functions/bratley1992.py @@ -34,12 +34,13 @@ """ import numpy as np +from scipy.special import eval_chebyt from typing import List from ..core.uqtestfun_abc import UQTestFunABC from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecVarDim -__all__ = ["Bratley1992b", "Bratley1992d"] +__all__ = ["Bratley1992b", "Bratley1992c", "Bratley1992d"] def _bratley_input(spatial_dimension: int) -> List[UnivDistSpec]: @@ -133,6 +134,51 @@ class Bratley1992b(UQTestFunABC): eval_ = staticmethod(evaluate_bratley1992b) +def evaluate_bratley1992c(xx: np.ndarray): + """Evaluate the test function #3 of Bratley et al. (1992). + + Parameters + ---------- + xx : np.ndarray + M-Dimensional input values given by an N-by-M array where + N is the number of input values. + + Returns + ------- + np.ndarray + The output of the test function evaluated on the input values. + The output is a 1-dimensional array of length N. + """ + + num_points, num_dim = xx.shape + yy = np.ones(num_points) + + # Compute the function + for m in range(1, num_dim + 1): + mi = m % 4 + 1 + yy *= eval_chebyt(mi, 2 * xx[:, m - 1] - 1) + + return yy + + +class Bratley1992c(UQTestFunABC): + """An implementation of the test function 2 from Bratley et al. (1992). + + The function (used as an integrand) is a product of a trigonometric + function. + """ + + _tags = COMMON_METADATA["_tags"] + _description = ( + f"Integration test function #3 {COMMON_METADATA['_description']}" + ) + _available_inputs = COMMON_METADATA["_available_inputs"] + _available_parameters = COMMON_METADATA["_available_parameters"] + _default_spatial_dimension = None + + eval_ = staticmethod(evaluate_bratley1992c) + + def evaluate_bratley1992d(xx: np.ndarray): """Evaluate the test function on a set of input values. From fa3f70783e03a464a739827c64ba475792f26e9a Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Fri, 23 Jun 2023 18:24:57 +0200 Subject: [PATCH 25/73] Add an implementation of the Bratley et al. (1992) A function - The function is an integral test function that features a product of an absolute function. - Several new sets of parameterization for the Sobol'-G function from Bratley et al. (1992) and Saltelli and Sobol' (1995). - Fix the main citation of the Sobol'-G function. --- CHANGELOG.md | 13 +- docs/_toc.yml | 2 + docs/fundamentals/integration.md | 15 +- docs/references.bib | 11 + docs/test-functions/available.md | 1 + docs/test-functions/bratley1992a.md | 225 +++++++++++++++++++ docs/test-functions/bratley1992b.md | 8 +- docs/test-functions/bratley1992c.md | 21 +- docs/test-functions/bratley1992d.md | 8 +- docs/test-functions/sobol-g.md | 38 +++- src/uqtestfuns/test_functions/__init__.py | 3 +- src/uqtestfuns/test_functions/bratley1992.py | 47 +++- src/uqtestfuns/test_functions/sobol_g.py | 116 +++++++--- 13 files changed, 448 insertions(+), 60 deletions(-) create mode 100644 docs/test-functions/bratley1992a.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 85f8389..9f7a01b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,9 +19,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - An implementation of the Welch et al. (1992) test function, a 20-dimensional function used in the context of metamodeling and sensitivity analysis. -- Three M-dimensional test functions (#2, #3, #4) from Bratley et al. - (1992) useful for testing multi-dimensional numerical integrations as well - as global sensitivity analysis. +- Four M-dimensional test functions from Bratley et al. (1992) useful for + testing multi-dimensional numerical integrations as well as + global sensitivity analysis. +- Add a new parameterization to the Sobol'-G function taken from + Bratley et al. (1992) and Saltelli and Sobol' (1995). - Two base classes are now available `UQTestFunBareABC` and `UQTestFunABC`. The former is used to implement a _bare_ UQ test function (with only `evaluate()` and `ProbInput`), while the latter is used to @@ -36,6 +38,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The date format in CHANGELOG.md has been changed from YYYY-DD-MM to the ISO format YYYY-MM-DD. +### Fixed + +- The original citation for the Sobol'-G function has been fixed; + it is now referred to Saltelli and Sobol' (1995). + ## [0.1.1] - 2023-03-07 ### Fixed diff --git a/docs/_toc.yml b/docs/_toc.yml index 27276cc..51c0e63 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -27,6 +27,8 @@ parts: title: Ackley - file: test-functions/borehole title: Borehole + - file: test-functions/bratley1992a + title: Bratley et al. (1992) A - file: test-functions/bratley1992b title: Bratley et al. (1992) B - file: test-functions/bratley1992c diff --git a/docs/fundamentals/integration.md b/docs/fundamentals/integration.md index 14c7a0c..360ae28 100644 --- a/docs/fundamentals/integration.md +++ b/docs/fundamentals/integration.md @@ -17,13 +17,14 @@ kernelspec: The table below listed the available test functions typically used in the testing and comparison of numerical integration method. -| Name | Spatial Dimension | Constructor | -|:------------------------------------------------------------:|:-----------------:|:------------------:| -| {ref}`Bratley et al. (1992) B ` | M | `Bratley1992b()` | -| {ref}`Bratley et al. (1992) C ` | M | `Bratley1992c()` | -| {ref}`Bratley et al. (1992) D ` | M | `Bratley1992d()` | -| {ref}`Sobol'-G ` | M | `SobolG()` | -| {ref}`Welch et al. (1992) ` | 20 | `Welch1992()` | +| Name | Spatial Dimension | Constructor | +|:------------------------------------------------------------:|:-----------------:|:-------------------:| +| {ref}`Bratley et al. (1992) A ` | M | `Bratley1992a()` | +| {ref}`Bratley et al. (1992) B ` | M | `Bratley1992b()` | +| {ref}`Bratley et al. (1992) C ` | M | `Bratley1992c()` | +| {ref}`Bratley et al. (1992) D ` | M | `Bratley1992d()` | +| {ref}`Sobol'-G ` | M | `SobolG()` | +| {ref}`Welch et al. (1992) ` | 20 | `Welch1992()` | In a Python terminal, you can list all the available functions relevant for metamodeling applications using ``list_functions()`` and filter the results diff --git a/docs/references.bib b/docs/references.bib index f9a0b9e..4cf7feb 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -489,4 +489,15 @@ @Article{Kucherenko2009 doi = {10.1016/j.ress.2008.05.006}, } +@Article{Saltelli1995, + author = {Saltelli, Andrea and Sobol', Ilya M.}, + journal = {Reliability Engineering \& System Safety}, + title = {About the use of rank transformation in sensitivity analysis of model output}, + year = {1995}, + number = {3}, + pages = {225--239}, + volume = {50}, + doi = {10.1016/0951-8320(95)00099-2}, +} + @Comment{jabref-meta: databaseType:bibtex;} diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index 9351f66..0b200c9 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -22,6 +22,7 @@ available in the current UQTestFuns, regardless of their typical applications. |:------------------------------------------------------------:|:-----------------:|:--------------------:| | {ref}`Ackley ` | M | `Ackley()` | | {ref}`Borehole ` | 8 | `Borehole()` | +| {ref}`Bratley et al. (1992) A ` | M | `Bratley1992a()` | | {ref}`Bratley et al. (1992) B ` | M | `Bratley1992b()` | | {ref}`Bratley et al. (1992) C ` | M | `Bratley1992c()` | | {ref}`Bratley et al. (1992) D ` | M | `Bratley1992d()` | diff --git a/docs/test-functions/bratley1992a.md b/docs/test-functions/bratley1992a.md new file mode 100644 index 0000000..e238d17 --- /dev/null +++ b/docs/test-functions/bratley1992a.md @@ -0,0 +1,225 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:bratley1992a)= +# Bratley et al. (1992) A function + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +The Bratley et al. (1992) A function (or `Bratley1992a` function for short), +is an $M$-dimensional scalar-valued function. +The function was introduced in {cite}`Bratley1992` as a test function +for multi-dimensional numerical integration using low discrepancy sequences. + +```{note} +There are four other test functions used in Bratley et al. {cite}`Bratley1992`: + +- {ref}`Bratley et al. (1992) A `: + A product of an absolute function (_this function_) +- {ref}`Bratley et al. (1992) B `: + A product of a trigonometric function +- {ref}`Bratley et al. (1992) C `: + A product of the Chebyshev polynomial of the first kind +- {ref}`Bratley et al. (1992) D `: + A sum of product + +The function was reintroduced in {cite}`Saltelli1995` with additional +parameters for global sensitivity analysis purposes. +The "generalized" function became known as the {ref}`Sobol'-G `. +``` + +The plots for one-dimensional and two-dimensional `Bratley1992a` functions +are shown below. + +```{code-cell} ipython3 +:tags: [remove-input] + +from mpl_toolkits.axes_grid1 import make_axes_locatable + +# --- Create 1D data +my_bratley1992a_1d = uqtf.Bratley1992a(spatial_dimension=1) +xx_1d = np.linspace(0, 1, 1000)[:, np.newaxis] +yy_1d = my_bratley1992a_1d(xx_1d) + +# --- Create 2D data +my_bratley1992a_2d = uqtf.Bratley1992a(spatial_dimension=2) +mesh_2d = np.meshgrid(xx_1d, xx_1d) +xx_2d = np.array(mesh_2d).T.reshape(-1, 2) +yy_2d = my_bratley1992a_2d(xx_2d) + +# --- Create a series of plots +fig = plt.figure(figsize=(15, 5)) + +# 1D +axs_1 = plt.subplot(131) +axs_1.plot(xx_1d, yy_1d, color="#8da0cb") +axs_1.grid() +axs_1.set_xlabel("$x$", fontsize=14) +axs_1.set_ylabel("$\mathcal{M}(x)$", fontsize=14) +axs_1.set_title("1D Bratley1992a") + +# Surface +axs_2 = plt.subplot(132, projection='3d') +axs_2.plot_surface( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000, 1000).T, + cmap="plasma", + linewidth=0, + antialiased=False, + alpha=0.5 +) +axs_2.set_xlabel("$x_1$", fontsize=14) +axs_2.set_ylabel("$x_2$", fontsize=14) +axs_2.set_zlabel("$\mathcal{M}(x_1, x_2)$", fontsize=14) +axs_2.set_title("Surface plot of 2D Bratley1992a", fontsize=14) + +# Contour +axs_3 = plt.subplot(133) +cf = axs_3.contourf( + mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000).T, cmap="plasma" +) +axs_3.set_xlabel("$x_1$", fontsize=14) +axs_3.set_ylabel("$x_2$", fontsize=14) +axs_3.set_title("Contour plot of 2D Bratley1992a", fontsize=14) +divider = make_axes_locatable(axs_3) +cax = divider.append_axes('right', size='5%', pad=0.05) +fig.colorbar(cf, cax=cax, orientation='vertical') +axs_3.axis('scaled') + +fig.tight_layout(pad=3.0) +plt.gcf().set_dpi(150); +``` + +## Test function instance + +To create a default instance of the test function: + +```{code-cell} ipython3 +my_testfun = uqtf.Bratley1992a() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +By default, the spatial dimension is set to $2$[^default_dimension]. +To create an instance with another value of spatial dimension, +pass an integer to the parameter `spatial_dimension` (keyword only). +For example, to create an instance of 10-dimensional `Bratley1992a` function, +type: + +```{code-cell} ipython3 +my_testfun = uqtf.Bratley1992a(spatial_dimension=10) +``` + +## Description + +The `Bratley1992a` function is defined as follows[^location]: + +$$ +\mathcal{M}(\boldsymbol{x}) = \prod_{m = 1}^{M} \lvert 4 x_m - 2 \rvert, +$$ + +where $\boldsymbol{x} = \{ x_1, \ldots, x_M \}$ +is the $M$-dimensional vector of input variables further defined below. + +## Probabilistic input + +Based on {cite}`Bratley1992`, the test function is integrated over the +hypercube domain of $[0, 1]^M$. +Such an input specification can be modeled using an $M$ independent uniform +random variables as shown in the table below. + +| No. | Name | Distribution | Parameters | Description | +|:---------:|:--------:|:-------------:|:----------:|:-----------:| +| 1 | $x_1$ | uniform | [0.0 1.0] | N/A | +| $\vdots$ | $\vdots$ | $\vdots$ | $\vdots$ | $\vdots$ | +| M | $x_M$ | uniform | [0.0 1.0] | N/A | + + +## Reference results + +This section provides several reference results of typical UQ analyses involving +the test function. + +### Definite integration + +The integral value of the function over the domain of $[0.0, 1.0]^M$ +is analytical: + +$$ +I[\mathcal{M}] (M) \equiv \int_{[0, 1]^M} \mathcal{M}(\boldsymbol{x}) \; d\boldsymbol{x} = 1.0. +$$ + +### Moments + +The moments of the test function are analytically known +and the first two moments are given below. + +#### Expected value + +Due to the domain being a hypercube, +the above integral value over the domain is the same as the expected value: + +$$ +\mathbb{E}[\mathcal{M}](M) = 1.0. +$$ + +#### Variance + +The analytical value for the variance is given as follows: + +$$ +\mathbb{V}[\mathcal{M}](M) = \left( \frac{4}{3} \right)^M - 1 +$$ + +The table below shows the numerical values of the variance +for several selected dimensions. + +| Dimension | $I[\mathcal{M}]$ | +|:---------:|:---------------------------:| +| 1 | $3.3333333 \times 10^{-1}$ | +| 2 | $7.7777778 \times 10^{-1}$ | +| 3 | $1.3703704 \times 10^{0}$ | +| 4 | $2.1604938 \times 10^{0}$ | +| 5 | $3.2139918 \times 10^{0}$ | +| 6 | $4.6186557 \times 10^{0}$ | +| 7 | $6.4915409 \times 10^{0}$ | +| 8 | $8.9887212 \times 10^{0}$ | +| 9 | $1.2318295 \times 10^{1}$ | +| 10 | $1.6757727 \times 10^{1}$ | + + +The variance grows as the number of dimensions and becomes unbounded +for a very large dimension. + +## References + +```{bibliography} +:style: unsrtalpha +:filter: docname in docnames +``` + +[^location]: see Section 5.1, p. 207 (test function no. 1) +in {cite}`Bratley1992`. + +[^default_dimension]: This default dimension applies to all variable dimension +test functions. It will be used if the `spatial_dimension` argument is not given. diff --git a/docs/test-functions/bratley1992b.md b/docs/test-functions/bratley1992b.md index b9f806f..1b66619 100644 --- a/docs/test-functions/bratley1992b.md +++ b/docs/test-functions/bratley1992b.md @@ -29,12 +29,18 @@ for multi-dimensional numerical integration using low discrepancy sequences. ```{note} There are four other test functions used in Bratley et al. {cite}`Bratley1992`: +- {ref}`Bratley et al. (1992) A `: + A product of an absolute function - {ref}`Bratley et al. (1992) B `: - A product of trigonometric function (_this function_) + A product of a trigonometric function (_this function_) - {ref}`Bratley et al. (1992) C `: A product of the Chebyshev polynomial of the first kind - {ref}`Bratley et al. (1992) D `: A sum of product + +The function was reintroduced in {cite}`Saltelli1995` with additional +parameters for global sensitivity analysis purposes. +The "generalized" function became known as the {ref}`Sobol'-G `. ``` The plots for one-dimensional and two-dimensional `Bratley1992b` functions diff --git a/docs/test-functions/bratley1992c.md b/docs/test-functions/bratley1992c.md index c146570..5f14c36 100644 --- a/docs/test-functions/bratley1992c.md +++ b/docs/test-functions/bratley1992c.md @@ -29,12 +29,18 @@ for multi-dimensional numerical integration using low discrepancy sequences. ```{note} There are four other test functions used in Bratley et al. {cite}`Bratley1992`: +- {ref}`Bratley et al. (1992) A `: + A product of an absolute function - {ref}`Bratley et al. (1992) B `: - A product of trigonometric function + A product of a trigonometric function - {ref}`Bratley et al. (1992) C `: A product of the Chebyshev polynomial of the first kind (_this function_) - {ref}`Bratley et al. (1992) D `: A sum of product + +The function was reintroduced in {cite}`Saltelli1995` with additional +parameters for global sensitivity analysis purposes. +The "generalized" function became known as the {ref}`Sobol'-G `. ``` The plots for one-dimensional and two-dimensional `Bratley1992c` functions @@ -65,7 +71,7 @@ axs_1.plot(xx_1d, yy_1d, color="#8da0cb") axs_1.grid() axs_1.set_xlabel("$x$", fontsize=14) axs_1.set_ylabel("$\mathcal{M}(x)$", fontsize=14) -axs_1.set_title("1D Bratley1992d") +axs_1.set_title("1D Bratley1992c") # Surface axs_2 = plt.subplot(132, projection='3d') @@ -81,7 +87,7 @@ axs_2.plot_surface( axs_2.set_xlabel("$x_1$", fontsize=14) axs_2.set_ylabel("$x_2$", fontsize=14) axs_2.set_zlabel("$\mathcal{M}(x_1, x_2)$", fontsize=14) -axs_2.set_title("Surface plot of 2D Bratley1992d", fontsize=14) +axs_2.set_title("Surface plot of 2D Bratley1992c", fontsize=14) # Contour axs_3 = plt.subplot(133) @@ -90,7 +96,7 @@ cf = axs_3.contourf( ) axs_3.set_xlabel("$x_1$", fontsize=14) axs_3.set_ylabel("$x_2$", fontsize=14) -axs_3.set_title("Contour plot of 2D Bratley1992d", fontsize=14) +axs_3.set_title("Contour plot of 2D Bratley1992c", fontsize=14) divider = make_axes_locatable(axs_3) cax = divider.append_axes('right', size='5%', pad=0.05) fig.colorbar(cf, cax=cax, orientation='vertical') @@ -121,18 +127,19 @@ For example, to create an instance of 10-dimensional `Bratley1992c` function, type: ```{code-cell} ipython3 -my_testfun = uqtf.Bratley1992d(spatial_dimension=10) +my_testfun = uqtf.Bratley1992c(spatial_dimension=10) ``` ## Description -The `Bratley1992d` function is defined as follows[^location]: +The `Bratley1992c` function is defined as follows[^location]: $$ \mathcal{M}(\boldsymbol{x}) = \prod_{m = 1}^M T_{n_m} (2 x_m - 1) $$ -where $n_m = m \bmod 4 + 1$ and +where $T_{n_m}$ is the Chebyshev polynomial (of the first kind) +of degree $n_m$, $n_m = m \bmod 4 + 1$, and $\boldsymbol{x} = \{ x_1, \ldots, x_M \}$ is the $M$-dimensional vector of input variables further defined below. diff --git a/docs/test-functions/bratley1992d.md b/docs/test-functions/bratley1992d.md index afa8c3a..c714e79 100644 --- a/docs/test-functions/bratley1992d.md +++ b/docs/test-functions/bratley1992d.md @@ -31,12 +31,18 @@ of global sensitivity analysis. ```{note} There are four other test functions used in Bratley et al. {cite}`Bratley1992`: +- {ref}`Bratley et al. (1992) A `: + A product of an absolute function - {ref}`Bratley et al. (1992) B `: - A product of trigonometric function + A product of a trigonometric function - {ref}`Bratley et al. (1992) C `: A product of the Chebyshev polynomial of the first kind - {ref}`Bratley et al. (1992) D `: A sum of product (_this function_) + +The function was reintroduced in {cite}`Saltelli1995` with additional +parameters for global sensitivity analysis purposes. +The "generalized" function became known as the {ref}`Sobol'-G `. ``` The plots for one-dimensional and two-dimensional `Bratley1992d` functions diff --git a/docs/test-functions/sobol-g.md b/docs/test-functions/sobol-g.md index 0e39a54..d31ea96 100644 --- a/docs/test-functions/sobol-g.md +++ b/docs/test-functions/sobol-g.md @@ -21,6 +21,18 @@ algorithms (e.g., quasi-Monte-Carlo; see for instance {cite}`Sobol1998`. Later on, it becomes a popular testing function for global sensitivity analysis methods; see, for instances, {cite}`Marrel2008, Marrel2009, Kucherenko2011`. + +The Sobol'-G function is an M-dimensional scalar-valued function. +It was introduced in [1] for testing numerical integration algorithms +(e.g., quasi-Monte-Carlo; see also for instance [2] and [3]). +The current form (and name) was from [4] and used in the context of global +sensitivity analysis. There, the function was generalized by introducing +a set of parameters that determines the importance of each input variable. +Later on, it becomes a popular testing function for global sensitivity analysis +methods; see, for instances, [5], [6], and [7]. + + + ```{code-cell} ipython3 import numpy as np import matplotlib.pyplot as plt @@ -119,7 +131,7 @@ using six dimensions. ## Description -The Sobol'-G function is defined as follows: +The Sobol'-G function is defined as follows[^location]: $$ \mathcal{M}(\boldsymbol{x}) = \prod_{m = 1}^M \frac{\lvert 4 x_m - 2 \rvert + a_m}{1 + a_m} @@ -146,15 +158,17 @@ input variable. There are several sets of parameters used in the literature as shown in the table below. -| No. | Value | Keyword | Source | Remark | -|:---:|:-------------------------------------------------:|:------------------------:|:-----------------------------------:|:----------------------------------------------------------------:| -| 1 | $a_1 = \ldots = a_M = 0.01$ | `Sobol1998-1` | {cite}`Sobol1998` (choice 1) | The supremum of the function grows exponentially at $2^M$ | -| 2 | $a_1 = \ldots = a_M = 1.0$ | `Sobol1998-2` | {cite}`Sobol1998` (choice 2) | The supremum of the function grows exponentially at $1.5^M$ | -| 3 | $a_m = m$
$\, 1 \leq m \leq M$ | `Sobol1998-3` | {cite}`Sobol1998` (choice 3) | The supremum of the function grows linearly at $1 + \frac{M}{2}$ | -| 4 | $a_m = m^2$
$1 \leq m \leq M$ | `Sobol1998-4` | {cite}`Sobol1998` (choice 4) | The supremum is bounded at $1.0$ | -| 5 | $a_1 = a_2 = 0.0$
$a_3 = \ldots = a_M = 6.52$ | `Kucherenko2011-2a` | {cite}`Kucherenko2011` (Problem 2A) | Originally, $M = 100$ | -| 6 | $a_m = 6,52$
$1 \leq m \leq M$ | `Kucherenko2011-3b` | {cite}`Kucherenko2011` (Problem 3B) | | -| 7 | $a_m = \frac{m - 1}{2.0}$
$1 \leq m \leq M$ | `Crestaux2007` (default) | {cite}`Crestaux2007` | | +| No. | Value | Keyword | Source | Remark | +|:---:|:--------------------------------------------------------------:|:------------------------:|:--------------------------------------------------------------:|:----------------------------------------------------------------------------------------------:| +| 1 | $a_1 = \ldots = a_M = 0$ | `Saltelli1995-1` | {cite}`Saltelli1995` (Example 1) (also {cite}`Bratley1992`) | All input variables are equally important | +| 2 | $a_1 = a_2 = 0.0$
$a_3 = 3$
$a_3 = \ldots = a_M = 9.9$ | `Saltelli1995-2` | {cite}`Saltelli1995` (Example 2) | The first two are important, the next is moderately important, and the rest is non-influential | +| 3 | $a_m = \frac{m - 1}{2.0}$
$1 \leq m \leq M$ | `Saltelli1995-3` | {cite}`Saltelli1995` (Example 3) (also {cite}`Crestaux2007` ) | The most important input is the first one, the least is the last one | +| 4 | $a_1 = \ldots = a_M = 0.01$ | `Sobol1998-1` | {cite}`Sobol1998` (choice 1) | The supremum of the function grows exponentially at $2^M$ | +| 5 | $a_1 = \ldots = a_M = 1.0$ | `Sobol1998-2` | {cite}`Sobol1998` (choice 2) | The supremum of the function grows exponentially at $1.5^M$ | +| 6 | $a_m = m$
$\, 1 \leq m \leq M$ | `Sobol1998-3` | {cite}`Sobol1998` (choice 3) | The supremum of the function grows linearly at $1 + \frac{M}{2}$ | +| 7 | $a_m = m^2$
$1 \leq m \leq M$ | `Sobol1998-4` | {cite}`Sobol1998` (choice 4) | The supremum is bounded at $1.0$ | +| 8 | $a_1 = a_2 = 0.0$
$a_3 = \ldots = a_M = 6.52$ | `Kucherenko2011-2a` | {cite}`Kucherenko2011` (Problem 2A) | Originally, $M = 100$ | +| 9 | $a_m = 6,52$
$1 \leq m \leq M$ | `Kucherenko2011-3b` | {cite}`Kucherenko2011` (Problem 3B) | | ```{note} The parameter values used in {cite}`Marrel2008` and {cite}`Marrel2009` @@ -365,10 +379,12 @@ tabulate( ## References ```{bibliography} -:style: plain +:style: unsrtalpha :filter: docname in docnames ``` +[^location]: see Eqs. (23) and (24), p. 234 in {cite}`Saltelli1995`. + [^integral]: The expected value is the same as the integral over the domain because the input is uniform in a unit hypercube. diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index d536e4c..f89fde2 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -3,7 +3,7 @@ """ from .ackley import Ackley from .borehole import Borehole -from .bratley1992 import Bratley1992b, Bratley1992c, Bratley1992d +from .bratley1992 import Bratley1992a, Bratley1992b, Bratley1992c, Bratley1992d from .damped_oscillator import DampedOscillator from .flood import Flood from .forrester import Forrester2008 @@ -24,6 +24,7 @@ __all__ = [ "Ackley", "Borehole", + "Bratley1992a", "Bratley1992b", "Bratley1992c", "Bratley1992d", diff --git a/src/uqtestfuns/test_functions/bratley1992.py b/src/uqtestfuns/test_functions/bratley1992.py index 4799da7..3f2d2e2 100644 --- a/src/uqtestfuns/test_functions/bratley1992.py +++ b/src/uqtestfuns/test_functions/bratley1992.py @@ -37,10 +37,12 @@ from scipy.special import eval_chebyt from typing import List +from .sobol_g import evaluate as evaluate_sobol_g + from ..core.uqtestfun_abc import UQTestFunABC from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecVarDim -__all__ = ["Bratley1992b", "Bratley1992c", "Bratley1992d"] +__all__ = ["Bratley1992a", "Bratley1992b", "Bratley1992c", "Bratley1992d"] def _bratley_input(spatial_dimension: int) -> List[UnivDistSpec]: @@ -91,6 +93,49 @@ def _bratley_input(spatial_dimension: int) -> List[UnivDistSpec]: ) +def evaluate_bratley1992a(xx: np.ndarray): + """Evaluate the test function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + M-Dimensional input values given by an N-by-M array where + N is the number of input values. + + Returns + ------- + np.ndarray + The output of the test function evaluated on the input values. + The output is a 1-dimensional array of length N. + """ + # The function is the Sobol'-G function with all the parameters set to 0. + parameters = np.zeros(xx.shape[1]) + yy = evaluate_sobol_g(xx, parameters) + + return yy + + +class Bratley1992a(UQTestFunABC): + """An implementation of the test function 1 from Bratley et al. (1992). + + The function (used as an integrand) is a product of an absolute function. + + Notes + ----- + - The function is the Sobol'-G function with all the parameters set to 0. + """ + + _tags = COMMON_METADATA["_tags"] + _description = ( + f"Integration test function #1 {COMMON_METADATA['_description']}" + ) + _available_inputs = COMMON_METADATA["_available_inputs"] + _available_parameters = COMMON_METADATA["_available_parameters"] + _default_spatial_dimension = None + + eval_ = staticmethod(evaluate_bratley1992a) + + def evaluate_bratley1992b(xx: np.ndarray): """Evaluate the test function on a set of input values. diff --git a/src/uqtestfuns/test_functions/sobol_g.py b/src/uqtestfuns/test_functions/sobol_g.py index d2afcc9..d52f151 100644 --- a/src/uqtestfuns/test_functions/sobol_g.py +++ b/src/uqtestfuns/test_functions/sobol_g.py @@ -3,42 +3,52 @@ The Sobol'-G function is an M-dimensional scalar-valued function. It was introduced in [1] for testing numerical integration algorithms -(e.g., quasi-Monte-Carlo; see for instance [2]. +(e.g., quasi-Monte-Carlo; see also for instance [2] and [3]). +The current form (and name) was from [4] and used in the context of global +sensitivity analysis. There, the function was generalized by introducing +a set of parameters that determines the importance of each input variable. Later on, it becomes a popular testing function for global sensitivity analysis -methods; see, for instances, [3], [4], and [5]. +methods; see, for instances, [5], [6], and [7]. -The parameters of the Sobol'-G function (i.e., the coefficients) determine -the importance of each input variable. There are several sets of parameters used in the literature. Notes ----- -- The parameters used in [3] and [4] correspond to - the parameter choice 3 in [1]. +- The parameters used in [5] and [6] correspond to + the parameter choice 3 in [3]. References ---------- -1. I. Radović, I. M. Sobol’, and R. F. Tichy, “Quasi-Monte Carlo Methods for + +1. Paul Bratley, Bennet L. Fox, and Harald Niederreiter, "Implementation and + tests of low-discrepancy sequences," ACM Transactions on Modeling and + Computer Simulation, vol. 2, no. 3, pp. 195-213, 1992. + DOI:10.1145/146382.146385 +2. I. Radović, I. M. Sobol’, and R. F. Tichy, “Quasi-Monte Carlo Methods for Numerical Integration: Comparison of Different Low Discrepancy Sequences,” Monte Carlo Methods and Applications, vol. 2, no. 1, pp. 1–14, 1996. DOI: 10.1515/mcma.1996.2.1.1. -2. I. M. Sobol’, “On quasi-Monte Carlo integrations,” Mathematics and Computers +3. I. M. Sobol’, “On quasi-Monte Carlo integrations,” Mathematics and Computers in Simulation, vol. 47, no. 2–5, pp. 103–112, 1998. DOI: 10.1016/S0378-4754(98)00096-2 -3. A. Marrel, B. Iooss, F. Van Dorpe, and E. Volkova, “An efficient +4. A. Saltelli and I. M. Sobol’, “About the use of rank transformation in + sensitivity analysis of model output,” Reliability Engineering + & System Safety, vol. 50, no. 3, pp. 225–239, 1995. + DOI: 10.1016/0951-8320(95)00099-2. +5. A. Marrel, B. Iooss, F. Van Dorpe, and E. Volkova, “An efficient methodology for modeling complex computer codes with Gaussian processes,” Computational Statistics & Data Analysis, vol. 52, no. 10, pp. 4731–4744, 2008. DOI: 10.1016/j.csda.2008.03.026 -4. A. Marrel, B. Iooss, B. Laurent, and O. Roustant, “Calculations of Sobol +6. A. Marrel, B. Iooss, B. Laurent, and O. Roustant, “Calculations of Sobol indices for the Gaussian process metamodel,” Reliability Engineering & System Safety, vol. 94, no. 3, pp. 742–751, 2009. DOI: 10.1016/j.ress.2008.07.008 -5. S. Kucherenko, B. Feil, N. Shah, and W. Mauntz, “The identification of +7. S. Kucherenko, B. Feil, N. Shah, and W. Mauntz, “The identification of model effective dimensions using global sensitivity analysis,” Reliability Engineering & System Safety, vol. 96, no. 4, pp. 440–449, 2011. DOI: 10.1016/j.ress.2010.11.003 -6. T. Crestaux, J.-M. Martinez, O. Le Maître, and O. Lafitte, “Polynomial +8. T. Crestaux, J.-M. Martinez, O. Le Maître, and O. Lafitte, “Polynomial chaos expansion for uncertainties quantification and sensitivity analysis,” presented at the Fifth International Conference on Sensitivity Analysis of Model Output, 2007. @@ -98,6 +108,62 @@ def _create_sobol_input(spatial_dimension: int) -> List[UnivDistSpec]: DEFAULT_INPUT_SELECTION = "Radovic1996" +def _get_params_saltelli_1995_1(spatial_dimension: int) -> np.ndarray: + """Construct a parameter array for Sobol'-G according to example 1 in [4]. + + Notes + ----- + - The function was most probably first appear in its original form + in [1] (without parameters). + - With the selected parameters, all input variables are equally important. + """ + yy = np.zeros(spatial_dimension) + + return yy + + +def _get_params_saltelli_1995_2(spatial_dimension: int) -> np.ndarray: + """Construct a parameter array for Sobol'-G according to example 2 in [4]. + + Notes + ----- + - With the selected parameters, the first two input variables are + important, one is moderately important, and the rest is non-influential. + - Originally, the dimension is limited to 8-dimensions; if more dimensions + are used then the remaining dimension is also non-influential. + """ + yy = np.zeros(spatial_dimension) + if spatial_dimension > 0: + yy[0] = 0 + + if spatial_dimension > 1: + yy[1] = 0 + + if spatial_dimension > 2: + yy[2] = 3 + + if spatial_dimension > 3: + yy[3:] = 9 + + return yy + + +def _get_params_saltelli_1995_3(spatial_dimension: int) -> np.ndarray: + """Construct a parameter array for Sobol'-G according to example 3 in [4]. + + Notes + ----- + - With the selected parameters, the first input variable is the most + important and the importance of the remaining variables is decreasing. + - Originally, the dimension is limited to 20-dimensions; if more dimensions + are used then the remaining dimension is also non-influential. + - The parameter set is also used in [8]. + """ + yy = (np.arange(1, spatial_dimension + 1) - 1) / 2.0 + + return yy + + def _get_params_sobol_1998_1(spatial_dimension: int) -> np.ndarray: """Construct a parameter array for Sobol'-G according to choice 1 in [2]. @@ -173,30 +239,22 @@ def _get_params_kucherenko_2011_3b(spatial_dimension: int) -> np.ndarray: return yy -def _get_params_crestaux_2007(spatial_dimension: int) -> np.ndarray: - """Construct a parameter array for Sobol'-G according to [6].""" - yy = (np.arange(1, spatial_dimension + 1) - 1) / 2.0 - - return yy - - AVAILABLE_PARAMETERS = { + "Saltelli1995-1": _get_params_saltelli_1995_1, + "Saltelli1995-2": _get_params_saltelli_1995_2, + "Saltelli1995-3": _get_params_saltelli_1995_3, "Sobol1998-1": _get_params_sobol_1998_1, "Sobol1998-2": _get_params_sobol_1998_2, "Sobol1998-3": _get_params_sobol_1998_3, "Sobol1998-4": _get_params_sobol_1998_4, "Kucherenko2011-2a": _get_params_kucherenko_2011_2a, "Kucherenko2011-3b": _get_params_kucherenko_2011_3b, - "Crestaux2007": _get_params_crestaux_2007, } -DEFAULT_PARAMETERS_SELECTION = "Crestaux2007" - -# The dimension is variable, it requires a default for fallback -DEFAULT_DIMENSION_SELECTION = 2 +DEFAULT_PARAMETERS_SELECTION = "Saltelli1995-3" -def evaluate(xx: np.ndarray, parameters): +def evaluate(xx: np.ndarray, parameters: np.ndarray): """Evaluate the Sobol-G function on a set of input values. Parameters @@ -204,6 +262,8 @@ def evaluate(xx: np.ndarray, parameters): xx : np.ndarray M-Dimensional input values given by an N-by-M array where N is the number of input values. + parameters : np.ndarray + The parameters (i.e., coefficients) of the Sobol'-G function. Returns ------- @@ -211,8 +271,8 @@ def evaluate(xx: np.ndarray, parameters): The output of the Sobol-G function evaluated on the input values. The output is a 1-dimensional array of length N. """ - params = parameters - yy = np.prod(((np.abs(4 * xx - 2) + params) / (1 + params)), axis=1) + aa = parameters + yy = np.prod(((np.abs(4 * xx - 2) + aa) / (1 + aa)), axis=1) return yy @@ -221,7 +281,7 @@ class SobolG(UQTestFunABC): """An implementation of the M-dimensional Sobol'-G test function.""" _tags = ["sensitivity", "integration"] - _description = "Sobol'-G function from Radović et al. (1996)" + _description = "Sobol'-G function from Saltelli and Sobol' (1995)" _available_inputs = AVAILABLE_INPUT_SPECS _available_parameters = AVAILABLE_PARAMETERS _default_parameters = DEFAULT_PARAMETERS_SELECTION From a7b178672ef5be9aef271241d3bfc9931116710f Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Fri, 23 Jun 2023 18:45:18 +0200 Subject: [PATCH 26/73] The output of list_functions() now appears w/ scroll in the docs - To limit the length of the page in the docs when the output of `list_functions()` is rendered, the output is now scrollable. --- docs/fundamentals/integration.md | 2 ++ docs/fundamentals/metamodeling.md | 2 ++ docs/fundamentals/optimization.md | 2 ++ docs/fundamentals/sensitivity.md | 2 ++ docs/test-functions/available.md | 2 ++ 5 files changed, 10 insertions(+) diff --git a/docs/fundamentals/integration.md b/docs/fundamentals/integration.md index 360ae28..b92d43a 100644 --- a/docs/fundamentals/integration.md +++ b/docs/fundamentals/integration.md @@ -31,6 +31,8 @@ for metamodeling applications using ``list_functions()`` and filter the results using the ``tag`` parameter: ```{code-cell} ipython3 +:tags: ["output_scroll"] + import uqtestfuns as uqtf uqtf.list_functions(tag="integration") diff --git a/docs/fundamentals/metamodeling.md b/docs/fundamentals/metamodeling.md index afb938a..cd45ce1 100644 --- a/docs/fundamentals/metamodeling.md +++ b/docs/fundamentals/metamodeling.md @@ -47,6 +47,8 @@ for metamodeling applications using ``list_functions()`` and filter the results using the ``tag`` parameter: ```{code-cell} ipython3 +:tags: ["output_scroll"] + import uqtestfuns as uqtf uqtf.list_functions(tag="metamodeling") diff --git a/docs/fundamentals/optimization.md b/docs/fundamentals/optimization.md index 1f26f06..96b15e5 100644 --- a/docs/fundamentals/optimization.md +++ b/docs/fundamentals/optimization.md @@ -27,6 +27,8 @@ for optimization applications using ``list_functions()`` and filter the results using the ``tag`` parameter: ```{code-cell} ipython3 +:tags: ["output_scroll"] + import uqtestfuns as uqtf uqtf.list_functions(tag="optimization") diff --git a/docs/fundamentals/sensitivity.md b/docs/fundamentals/sensitivity.md index cb9bcfd..4f624ca 100644 --- a/docs/fundamentals/sensitivity.md +++ b/docs/fundamentals/sensitivity.md @@ -36,6 +36,8 @@ for metamodeling applications using ``list_functions()`` and filter the results using the ``tag`` parameter: ```{code-cell} ipython3 +:tags: ["output_scroll"] + import uqtestfuns as uqtf uqtf.list_functions(tag="sensitivity") diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index 0b200c9..4a0e245 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -53,6 +53,8 @@ In a Python terminal, you can list all the available functions along with the corresponding constructor using ``list_functions()``: ```{code-cell} ipython3 +:tags: ["output_scroll"] + import uqtestfuns as uqtf uqtf.list_functions() From 9fc627b7baed0fa5af91e3f76cf2be6dd83fbbd8 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Mon, 26 Jun 2023 10:30:03 +0200 Subject: [PATCH 27/73] Add an implementation of the Grammacy 1D sine function. - The function features two-regimes: a sine-cosine mixture and a linear function. The function was used in the context of metamodeling with non-stationary Gaussian processes. --- docs/_toc.yml | 2 + docs/fundamentals/metamodeling.md | 49 +++--- docs/references.bib | 10 ++ docs/test-functions/available.md | 61 ++++---- docs/test-functions/gramacy-1d-sine.md | 144 ++++++++++++++++++ src/uqtestfuns/test_functions/__init__.py | 2 + src/uqtestfuns/test_functions/gramacy2007.py | 104 +++++++++++++ .../test_grammacy_1dsine.py | 48 ++++++ 8 files changed, 366 insertions(+), 54 deletions(-) create mode 100644 docs/test-functions/gramacy-1d-sine.md create mode 100644 src/uqtestfuns/test_functions/gramacy2007.py create mode 100644 tests/builtin_test_functions/test_grammacy_1dsine.py diff --git a/docs/_toc.yml b/docs/_toc.yml index 51c0e63..72c26c4 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -53,6 +53,8 @@ parts: title: (5th) Franke - file: test-functions/franke-6 title: (6th) Franke + - file: test-functions/gramacy-1d-sine + title: Gramacy (2007) Sine - file: test-functions/ishigami title: Ishigami - file: test-functions/oakley-ohagan-1d diff --git a/docs/fundamentals/metamodeling.md b/docs/fundamentals/metamodeling.md index cd45ce1..88bf422 100644 --- a/docs/fundamentals/metamodeling.md +++ b/docs/fundamentals/metamodeling.md @@ -17,30 +17,31 @@ kernelspec: The table below listed the available test functions typically used in the comparison of metamodeling approaches. -| Name | Spatial Dimension | Constructor | -|:-----------------------------------------------------------:|:-----------------:|:--------------------:| -| {ref}`Ackley ` | M | `Ackley()` | -| {ref}`Borehole ` | 8 | `Borehole()` | -| {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | -| {ref}`Flood ` | 8 | `Flood()` | -| {ref}`Forrester et al. (2008) ` | 1 | `Forrester2008()` | -| {ref}`(1st) Franke ` | 2 | `Franke1()` | -| {ref}`(2nd) Franke ` | 2 | `Franke2()` | -| {ref}`(3rd) Franke ` | 2 | `Franke3()` | -| {ref}`(4th) Franke ` | 2 | `Franke4()` | -| {ref}`(5th) Franke ` | 2 | `Franke5()` | -| {ref}`(6th) Franke ` | 2 | `Franke6()` | -| {ref}`Oakley-O'Hagan 1D ` | 1 | `OakleyOHagan1D()` | -| {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | -| {ref}`McLain S1 ` | 2 | `McLainS1()` | -| {ref}`McLain S2 ` | 2 | `McLainS2()` | -| {ref}`McLain S3 ` | 2 | `McLainS3()` | -| {ref}`McLain S4 ` | 2 | `McLainS4()` | -| {ref}`McLain S5 ` | 2 | `McLainS5()` | -| {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | -| {ref}`Sulfur ` | 9 | `Sulfur()` | -| {ref}`Welch1992 ` | 20 | `Welch1992()` | -| {ref}`Wing Weight ` | 10 | `WingWeight()` | +| Name | Spatial Dimension | Constructor | +|:--------------------------------------------------------------:|:-----------------:|:--------------------:| +| {ref}`Ackley ` | M | `Ackley()` | +| {ref}`Borehole ` | 8 | `Borehole()` | +| {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | +| {ref}`Flood ` | 8 | `Flood()` | +| {ref}`Forrester et al. (2008) ` | 1 | `Forrester2008()` | +| {ref}`(1st) Franke ` | 2 | `Franke1()` | +| {ref}`(2nd) Franke ` | 2 | `Franke2()` | +| {ref}`(3rd) Franke ` | 2 | `Franke3()` | +| {ref}`(4th) Franke ` | 2 | `Franke4()` | +| {ref}`(5th) Franke ` | 2 | `Franke5()` | +| {ref}`(6th) Franke ` | 2 | `Franke6()` | +| {ref}`Gramacy (2007) 1D Sine ` | 1 | `Gramacy1DSine()` | +| {ref}`Oakley-O'Hagan 1D ` | 1 | `OakleyOHagan1D()` | +| {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | +| {ref}`McLain S1 ` | 2 | `McLainS1()` | +| {ref}`McLain S2 ` | 2 | `McLainS2()` | +| {ref}`McLain S3 ` | 2 | `McLainS3()` | +| {ref}`McLain S4 ` | 2 | `McLainS4()` | +| {ref}`McLain S5 ` | 2 | `McLainS5()` | +| {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | +| {ref}`Sulfur ` | 9 | `Sulfur()` | +| {ref}`Welch1992 ` | 20 | `Welch1992()` | +| {ref}`Wing Weight ` | 10 | `WingWeight()` | In a Python terminal, you can list all the available functions relevant for metamodeling applications using ``list_functions()`` and filter the results diff --git a/docs/references.bib b/docs/references.bib index 4cf7feb..b1f3ec7 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -500,4 +500,14 @@ @Article{Saltelli1995 doi = {10.1016/0951-8320(95)00099-2}, } +@Article{Gramacy2007, + author = {Robert B. Gramacy}, + journal = {Journal of Statistical Software}, + title = {{tgp}: an {R} package for {Bayesian} nonstationary, semiparametric nonlinear regression and design by {Treed Gaussian Process} models}, + year = {2007}, + number = {9}, + volume = {19}, + doi = {10.18637/jss.v019.i09}, +} + @Comment{jabref-meta: databaseType:bibtex;} diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index 4a0e245..19fbb9c 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -18,36 +18,37 @@ kernelspec: The table below lists all the available _classic_ test functions from the literature available in the current UQTestFuns, regardless of their typical applications. -| Name | Spatial Dimension | Constructor | -|:------------------------------------------------------------:|:-----------------:|:--------------------:| -| {ref}`Ackley ` | M | `Ackley()` | -| {ref}`Borehole ` | 8 | `Borehole()` | -| {ref}`Bratley et al. (1992) A ` | M | `Bratley1992a()` | -| {ref}`Bratley et al. (1992) B ` | M | `Bratley1992b()` | -| {ref}`Bratley et al. (1992) C ` | M | `Bratley1992c()` | -| {ref}`Bratley et al. (1992) D ` | M | `Bratley1992d()` | -| {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | -| {ref}`Flood ` | 8 | `Flood()` | -| {ref}`Forrester et al. (2008) ` | 1 | `Forrester2008()` | -| {ref}`(1st) Franke ` | 2 | `Franke1()` | -| {ref}`(2nd) Franke ` | 2 | `Franke2()` | -| {ref}`(3rd) Franke ` | 2 | `Franke3()` | -| {ref}`(4th) Franke ` | 2 | `Franke4()` | -| {ref}`(5th) Franke ` | 2 | `Franke5()` | -| {ref}`(6th) Franke ` | 2 | `Franke6()` | -| {ref}`Ishigami ` | 3 | `Ishigami()` | -| {ref}`Oakley-O'Hagan 1D ` | 1 | `OakleyOHagan1D()` | -| {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | -| {ref}`McLain S1 ` | 2 | `McLainS1()` | -| {ref}`McLain S2 ` | 2 | `McLainS2()` | -| {ref}`McLain S3 ` | 2 | `McLainS3()` | -| {ref}`McLain S4 ` | 2 | `McLainS4()` | -| {ref}`McLain S5 ` | 2 | `McLainS5()` | -| {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | -| {ref}`Sobol'-G ` | M | `SobolG()` | -| {ref}`Sulfur ` | 9 | `Sulfur()` | -| {ref}`Welch1992 ` | 20 | `Welch1992()` | -| {ref}`Wing Weight ` | 10 | `WingWeight()` | +| Name | Spatial Dimension | Constructor | +|:--------------------------------------------------------------:|:-----------------:|:--------------------:| +| {ref}`Ackley ` | M | `Ackley()` | +| {ref}`Borehole ` | 8 | `Borehole()` | +| {ref}`Bratley et al. (1992) A ` | M | `Bratley1992a()` | +| {ref}`Bratley et al. (1992) B ` | M | `Bratley1992b()` | +| {ref}`Bratley et al. (1992) C ` | M | `Bratley1992c()` | +| {ref}`Bratley et al. (1992) D ` | M | `Bratley1992d()` | +| {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | +| {ref}`Flood ` | 8 | `Flood()` | +| {ref}`Forrester et al. (2008) ` | 1 | `Forrester2008()` | +| {ref}`(1st) Franke ` | 2 | `Franke1()` | +| {ref}`(2nd) Franke ` | 2 | `Franke2()` | +| {ref}`(3rd) Franke ` | 2 | `Franke3()` | +| {ref}`(4th) Franke ` | 2 | `Franke4()` | +| {ref}`(5th) Franke ` | 2 | `Franke5()` | +| {ref}`(6th) Franke ` | 2 | `Franke6()` | +| {ref}`Gramacy (2007) 1D Sine ` | 1 | `Gramacy1DSine()` | +| {ref}`Ishigami ` | 3 | `Ishigami()` | +| {ref}`Oakley-O'Hagan 1D ` | 1 | `OakleyOHagan1D()` | +| {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | +| {ref}`McLain S1 ` | 2 | `McLainS1()` | +| {ref}`McLain S2 ` | 2 | `McLainS2()` | +| {ref}`McLain S3 ` | 2 | `McLainS3()` | +| {ref}`McLain S4 ` | 2 | `McLainS4()` | +| {ref}`McLain S5 ` | 2 | `McLainS5()` | +| {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | +| {ref}`Sobol'-G ` | M | `SobolG()` | +| {ref}`Sulfur ` | 9 | `Sulfur()` | +| {ref}`Welch1992 ` | 20 | `Welch1992()` | +| {ref}`Wing Weight ` | 10 | `WingWeight()` | In a Python terminal, you can list all the available functions along with the corresponding constructor using ``list_functions()``: diff --git a/docs/test-functions/gramacy-1d-sine.md b/docs/test-functions/gramacy-1d-sine.md new file mode 100644 index 0000000..0cdb316 --- /dev/null +++ b/docs/test-functions/gramacy-1d-sine.md @@ -0,0 +1,144 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:gramacy-1d-sine)= +# Gramacy (2007) One-dimensional (1D) Sine Function + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +The Gramacy (2007) one-dimensional (1D) sine function +(or `Gramacy1DSine` function for short) +is a scalar-valued function that features two regimes: one part is a mixture +of sines and cosines, and another part is a linear function. +The function was introduced in {cite}`Gramacy2007` in the context of +metamodeling with non-stationary Gaussian processes. + +In the original paper, the response of the function is disturbed by an +independent identically distributed (i.i.d) Gaussian noise. + +A plot of the function is shown below for $x \in [0, 20]$. + +```{code-cell} ipython3 +:tags: [remove-input] + +my_testfun_noisy = uqtf.Gramacy1DSine() +my_testfun_noiseless = uqtf.Gramacy1DSine(parameters_selection="noiseless") +rng = np.random.default_rng(3423) +my_testfun_noisy.parameters = rng + + +xx = np.linspace(0, 20, 100)[:, np.newaxis] +yy_noisy = my_testfun_noisy(xx) +yy_noiseless = my_testfun_noiseless(xx) + +# --- Create the plot +plt.plot(xx, yy_noiseless, color="#8da0cb") +plt.scatter(xx, yy_noisy, color="#8da0cb", marker="x", s=50) +plt.grid() +plt.xlabel("$x$") +plt.ylabel("$\mathcal{M}(x)$") +plt.gcf().tight_layout(pad=3.0) +plt.gcf().set_dpi(150); +``` + +Note that the function is discontinous at $x = 9.6%$ which also pinpoint +the change of regime. + +## Test function instance + +To create a default instance of the test function: + +```{code-cell} ipython3 +my_testfun = uqtf.Gramacy1DSine() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +## Description + +The test function is analytically defined as follows[^location]: + +$$ +\mathcal{M}(x) = \begin{cases} +\sin{(\frac{\pi x}{5})} + \frac{1}{5} \cos{(\frac{4 \pi x}{5})}, & x \leq 9.6 \\ +\frac{1}{10} x - 1, & x > 9.6 +\end{cases} +$$ +where $x$ is defined below. + +The response of the function is disturbed by an i.i.d Gaussian noise, +such that: + +$$ +y(x) = \mathcal{M}(x) + \varepsilon, +$$ +where $\varepsilon \sim \mathcal{N}(0, \sigma_n = 0.1)$. + +## Probabilistic input + +Based on {cite}`Gramacy2007`, the domain of the function is $[0, 20]$. +This input can be modeled with a single uniform random variable shown below. + +```{code-cell} ipython3 +my_testfun.prob_input +``` + +## Parameters + +The parameters of the test function is a NumPy random number generator with +which the Gaussian random noise is generated. +Other available parameters are shown in the table below. + +| No. | Value | Keyword | Source | +|:---:|:-----------------------------------------------:|:-----------------:|:-------------------:| +| 1 | $\varepsilon \sim \mathcal{N}(0, \sigma_n=0.1)$ | `noisy` (default) | {cite}`Gramacy2007` | +| 2 | $\varepsilon = 0$ | `noiseless` | | + +Alternatively, to create an instance of the `Gramacy1DSine` function with +different parameters, type + +```python +my_testfun = uqtf.Gramacy1DSine(parameters_selection="noiseless") +``` + +````{note} +To use a custom random number generator (perhaps with a fixed seed number), +create a default test function and use the custom generator as the parameters +after the instance has been +created. +For example: +```python +my_testfun = uqtf.Gramacy1DSine() +my_rng = np.random.default_rng(322345) +my_testfun.parameters = my_rng +``` +```` + +## References + +```{bibliography} +:style: unsrtalpha +:filter: docname in docnames +``` + +[^location]: see Section 4.2, p. 17, Eq. (16) in {cite}`Gramacy2007`; +also the actual implementation as an R code not far below that. \ No newline at end of file diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index f89fde2..018bffb 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -8,6 +8,7 @@ from .flood import Flood from .forrester import Forrester2008 from .franke import Franke1, Franke2, Franke3, Franke4, Franke5, Franke6 +from .gramacy2007 import Gramacy1DSine from .ishigami import Ishigami from .oakley_ohagan_1d import OakleyOHagan1D from .otl_circuit import OTLCircuit @@ -37,6 +38,7 @@ "Franke4", "Franke5", "Franke6", + "Gramacy1DSine", "Ishigami", "OakleyOHagan1D", "OTLCircuit", diff --git a/src/uqtestfuns/test_functions/gramacy2007.py b/src/uqtestfuns/test_functions/gramacy2007.py new file mode 100644 index 0000000..bac5995 --- /dev/null +++ b/src/uqtestfuns/test_functions/gramacy2007.py @@ -0,0 +1,104 @@ +""" +Module with an implementation of the 1D Gramacy (2007) test function. + +The Gramacy (2007) one-dimensional sine function is a scalar-valued function +that features two regimes: one part is a mixture of sines and cosines and +another part is a linear function. The function was introduced in [1] +as a test function for non-stationary Gaussian process metamodeling +by partitioning the input parameter space. + +In its original usage, the response is disturbed by an i.i.d Gaussian noise. + +References +---------- + +1. Robert B. Gramacy, “tgp: An R Package for Bayesian nonstationary, + semiparametric nonlinear regression and design by Treed Gaussian Process + models,” Journal of Statistical Software, vol. 19, no. 9, 2007. + DOI: 10.18637/jss.v019.i09. +""" +import numpy as np + +from numpy.random._generator import Generator +from typing import Optional + +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecFixDim +from ..core.uqtestfun_abc import UQTestFunABC + +__all__ = ["Gramacy1DSine"] + +AVAILABLE_INPUT_SPECS = { + "Gramacy2007": ProbInputSpecFixDim( + name="Gramacy2007", + description=( + "Input model for the one-dimensional function " + "from Gramacy (2007)" + ), + marginals=[ + UnivDistSpec( + name="x", + distribution="uniform", + parameters=[0.0, 20.0], + description="None", + ) + ], + copulas=None, + ), +} + +AVAILABLE_PARAMETERS = { + "noisy": np.random.default_rng(), + "noiseless": None, +} + +DEFAULT_PARAMETERS_SELECTION = "noisy" + + +def evaluate_1dsine( + xx: np.ndarray, parameters: Optional[Generator] = None +) -> np.ndarray: + """Evaluate the 1D Gramacy (2007) Sine function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + 1-Dimensional input values given by an N-by-1 array + where N is the number of input values. + + parameters : Generator, optional + A random number generator to generate the noise. + + Returns + ------- + np.ndarray + The output of the 1D Gramacy (2007) function evaluated + on the input values. + The output is a 1-dimensional array of length N. + """ + yy = np.zeros(len(xx)) + idx_1 = xx[:, 0] <= 9.6 + idx_2 = xx[:, 0] > 9.6 + yy[idx_1] = np.sin(0.2 * np.pi * xx[idx_1, 0]) + 0.2 * np.cos( + 0.8 * np.pi * xx[idx_1, 0] + ) + yy[idx_2] = -1 + 0.1 * xx[idx_2, 0] + + if parameters is not None: + yy_noise = parameters.normal(size=len(xx), scale=0.1) + + return yy + yy_noise + + return yy + + +class Gramacy1DSine(UQTestFunABC): + """A concrete implementation of the 1D Gramacy (2007) Sine function.""" + + _tags = ["metamodeling"] + _description = "One-dimensional sine function from Gramacy (2007)" + _available_inputs = AVAILABLE_INPUT_SPECS + _available_parameters = AVAILABLE_PARAMETERS + _default_spatial_dimension = 1 + _default_parameters = "noisy" + + eval_ = staticmethod(evaluate_1dsine) diff --git a/tests/builtin_test_functions/test_grammacy_1dsine.py b/tests/builtin_test_functions/test_grammacy_1dsine.py new file mode 100644 index 0000000..e006e06 --- /dev/null +++ b/tests/builtin_test_functions/test_grammacy_1dsine.py @@ -0,0 +1,48 @@ +""" +Test module for the Grammacy1DSine test function. + +Notes +----- +- The tests defined in this module deals with + the correctness of the evaluation. +""" +import pytest + +from uqtestfuns import Gramacy1DSine +import uqtestfuns.test_functions.gramacy2007 as gramacy_mod + +from conftest import assert_call + +# Test for different parameters to the Ishigami function +available_parameters = list(Gramacy1DSine.available_parameters.keys()) + + +@pytest.fixture(params=available_parameters) +def gramacy1dsine_fun(request): + gramacy1dsine = Gramacy1DSine(parameters_selection=request.param) + + return gramacy1dsine + + +@pytest.mark.parametrize("param_selection", available_parameters) +def test_different_parameters(param_selection): + """Test selecting different built-in parameters.""" + + # Create an instance of Ishigami function with a specified param. selection + my_testfun = Gramacy1DSine(parameters_selection=param_selection) + + # Assertion + assert ( + my_testfun.parameters + == gramacy_mod.AVAILABLE_PARAMETERS[param_selection] + ) + + # Assert Call + xx = my_testfun.prob_input.get_sample(10) + assert_call(my_testfun, xx) + + +def test_wrong_param_selection(): + """Test a wrong selection of the parameters.""" + with pytest.raises(KeyError): + Gramacy1DSine(parameters_selection="marelli1") From ad3a68962a18a84034add0640a7da9dddc6b3572 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Mon, 26 Jun 2023 15:01:53 +0200 Subject: [PATCH 28/73] Dim-dependent param. must have keyword "spatial_dimension" to be called. - If a dimension-dependent function is used as parameters to a UQ test function then it must have keyword "spatial_dimension" for the function to be called with spatial_dimension during an object instantiation. Otherwise, the function is left as is (not called). - Increase the test coverage ratio for the Sobol-G function. - Add a test for the __str__() method of all instance of UQTestFunABC. - An instance of ProbInput is assumed to be always attached to an instance of UQTestFunABC, no additional checking required. --- src/uqtestfuns/core/uqtestfun_abc.py | 42 +++++++++---------- src/uqtestfuns/test_functions/sobol_g.py | 2 - tests/builtin_test_functions/test_sobol_g.py | 6 +-- .../test_test_functions.py | 21 +++++++++- 4 files changed, 41 insertions(+), 30 deletions(-) diff --git a/src/uqtestfuns/core/uqtestfun_abc.py b/src/uqtestfuns/core/uqtestfun_abc.py index 030dee6..517401e 100644 --- a/src/uqtestfuns/core/uqtestfun_abc.py +++ b/src/uqtestfuns/core/uqtestfun_abc.py @@ -4,6 +4,7 @@ import abc import numpy as np +from inspect import signature from typing import Any, Callable, Dict, List, Optional, Union from .prob_input.probabilistic_input import ProbInput @@ -29,7 +30,7 @@ class classproperty(property): def __get__(self, owner_self, owner_cls): return self.fget(owner_cls) # type: ignore - def __set__(self, owner_self, owner_cls): + def __set__(self, owner_self, owner_cls): # pragma: no cover raise AttributeError("can't set attribute") @@ -73,8 +74,8 @@ def prob_input(self, value: ProbInput): self._prob_input = value else: raise TypeError( - f"Probabilistic input model must be either a 'None' or " - f"of a 'ProbInput' type! Got instead {type(value)}." + f"Probabilistic input model must be of " + f"a 'ProbInput' type! Got instead {type(value)}." ) @property @@ -121,11 +122,6 @@ def transform_sample( Transformed sampled values from the specified uniform domain to the domain of the function as defined the `input` property. """ - if self.prob_input is None or self.spatial_dimension is None: - raise ValueError( - "There is not ProbInput attached to the function! " - "A sample can't be generated." - ) # Verify the uniform bounds assert min_value < max_value, ( @@ -156,18 +152,14 @@ def __str__(self): def __call__(self, xx): """Evaluation of the test function by calling the instance.""" - if self.prob_input and self.spatial_dimension: - # Verify the shape of the input - _verify_sample_shape(xx, self.spatial_dimension) - - if self.prob_input is not None: - # If ProbInput is attached, verify the domain of the input - for dim_idx in range(self.spatial_dimension): - lb = self.prob_input.marginals[dim_idx].lower - ub = self.prob_input.marginals[dim_idx].upper - _verify_sample_domain( - xx[:, dim_idx], min_value=lb, max_value=ub - ) + # Verify the shape of the input + _verify_sample_shape(xx, self.spatial_dimension) + + # Verify the domain of the input + for dim_idx in range(self.spatial_dimension): + lb = self.prob_input.marginals[dim_idx].lower + ub = self.prob_input.marginals[dim_idx].upper + _verify_sample_domain(xx[:, dim_idx], min_value=lb, max_value=ub) return self.evaluate(xx) @@ -266,7 +258,7 @@ def __init__( # --- Select the parameters set, when applicable available_parameters = self.available_parameters - if available_parameters: + if available_parameters is not None: if not parameters_selection: parameters_selection = self.default_parameters if parameters_selection not in available_parameters: @@ -278,7 +270,11 @@ def __init__( # If the parameters set is a function of spatial dimension if isinstance(prob_input_spec, ProbInputSpecVarDim): if isinstance(parameters, Callable): # type: ignore - parameters = parameters(spatial_dimension) + func_signature = signature(parameters).parameters + if "spatial_dimension" in func_signature: + parameters = parameters( + spatial_dimension=spatial_dimension + ) else: parameters = None @@ -402,7 +398,7 @@ def evaluate(self, xx): @staticmethod @abc.abstractmethod - def eval_(*args): + def eval_(*args): # pragma: no cover """Static method for the concrete function implementation. Notes diff --git a/src/uqtestfuns/test_functions/sobol_g.py b/src/uqtestfuns/test_functions/sobol_g.py index d52f151..ac878cd 100644 --- a/src/uqtestfuns/test_functions/sobol_g.py +++ b/src/uqtestfuns/test_functions/sobol_g.py @@ -133,8 +133,6 @@ def _get_params_saltelli_1995_2(spatial_dimension: int) -> np.ndarray: are used then the remaining dimension is also non-influential. """ yy = np.zeros(spatial_dimension) - if spatial_dimension > 0: - yy[0] = 0 if spatial_dimension > 1: yy[1] = 0 diff --git a/tests/builtin_test_functions/test_sobol_g.py b/tests/builtin_test_functions/test_sobol_g.py index 83ce262..f99ab94 100644 --- a/tests/builtin_test_functions/test_sobol_g.py +++ b/tests/builtin_test_functions/test_sobol_g.py @@ -9,7 +9,7 @@ import numpy as np import pytest -from uqtestfuns import SobolG +from uqtestfuns.test_functions import SobolG available_parameters = list(SobolG.available_parameters.keys()) @@ -22,7 +22,7 @@ def test_wrong_param_selection(): # ATTENTION: some parameters choice (e.g., "sobol-1") # can't be estimated properly with low N at high dimension -@pytest.mark.parametrize("spatial_dimension", [1, 2, 10]) +@pytest.mark.parametrize("spatial_dimension", [1, 2, 3, 10]) @pytest.mark.parametrize("params_selection", available_parameters) def test_compute_mean(spatial_dimension, params_selection): """Test the mean computation as the result is analytical.""" @@ -50,7 +50,7 @@ def test_compute_mean(spatial_dimension, params_selection): # ATTENTION: parameters with "Sobol-1" is unstable at large dimension >= 15 -@pytest.mark.parametrize("spatial_dimension", [1, 2, 10]) +@pytest.mark.parametrize("spatial_dimension", [1, 2, 3, 10]) @pytest.mark.parametrize("params_selection", available_parameters) def test_compute_variance(spatial_dimension, params_selection): """Test the variance computation as the result is analytical.""" diff --git a/tests/builtin_test_functions/test_test_functions.py b/tests/builtin_test_functions/test_test_functions.py index bebdbf1..553800f 100644 --- a/tests/builtin_test_functions/test_test_functions.py +++ b/tests/builtin_test_functions/test_test_functions.py @@ -12,16 +12,18 @@ import pytest import copy +from typing import Type + from conftest import assert_call from uqtestfuns.utils import get_available_classes -from uqtestfuns import test_functions +from uqtestfuns import test_functions, UQTestFunABC AVAILABLE_FUNCTION_CLASSES = get_available_classes(test_functions) @pytest.fixture(params=AVAILABLE_FUNCTION_CLASSES) -def builtin_testfun(request): +def builtin_testfun(request) -> Type[UQTestFunABC]: _, testfun = request.param return testfun @@ -145,6 +147,21 @@ def test_call_instance(builtin_testfun): assert_call(my_fun, xx) +def test_str(builtin_testfun): + """Test the __str__() method of a test function instance.""" + + # Create an instance + my_fun = builtin_testfun() + + str_ref = ( + f"Name : {my_fun.name}\n" + f"Spatial dimension : {my_fun.spatial_dimension}\n" + f"Description : {my_fun.description}" + ) + + assert my_fun.__str__() == str_ref + + def test_transform_input(builtin_testfun): """Test transforming a set of input values in the default unif. domain.""" From 3eb398eea6a87bf4ca9761b4c0d1210675e6e47f Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Mon, 26 Jun 2023 15:46:42 +0200 Subject: [PATCH 29/73] Bibliography style in the docs is now 'unsrtalpha'. - It means, the style uses alphanumeric reference labels, and citations are sorted by the order of appearance in a single page. --- docs/test-functions/ackley.md | 2 +- docs/test-functions/borehole.md | 2 +- docs/test-functions/damped-oscillator.md | 2 +- docs/test-functions/flood.md | 2 +- docs/test-functions/franke-1.md | 1 - docs/test-functions/franke-2.md | 2 +- docs/test-functions/franke-3.md | 2 +- docs/test-functions/franke-4.md | 2 +- docs/test-functions/franke-5.md | 2 +- docs/test-functions/franke-6.md | 2 +- docs/test-functions/ishigami.md | 2 +- docs/test-functions/mclain-s1.md | 2 +- docs/test-functions/mclain-s2.md | 2 +- docs/test-functions/mclain-s3.md | 2 +- docs/test-functions/mclain-s4.md | 2 +- docs/test-functions/mclain-s5.md | 2 +- docs/test-functions/oakley-ohagan-1d.md | 2 +- docs/test-functions/otl-circuit.md | 2 +- docs/test-functions/piston.md | 2 +- docs/test-functions/sulfur.md | 2 +- docs/test-functions/wing-weight.md | 2 +- 21 files changed, 20 insertions(+), 21 deletions(-) diff --git a/docs/test-functions/ackley.md b/docs/test-functions/ackley.md index 788eea8..be6d053 100644 --- a/docs/test-functions/ackley.md +++ b/docs/test-functions/ackley.md @@ -176,7 +176,7 @@ $\mathcal{M}(\boldsymbol{x}^*) = 0$ at $x_m^* = 0,\, m = 1, \ldots, M$. ## References ```{bibliography} -:style: unsrt +:style: unsrtalpha :filter: docname in docnames ``` diff --git a/docs/test-functions/borehole.md b/docs/test-functions/borehole.md index d36e71e..4098f85 100644 --- a/docs/test-functions/borehole.md +++ b/docs/test-functions/borehole.md @@ -224,6 +224,6 @@ tabulate( ## References ```{bibliography} -:style: unsrt +:style: unsrtalpha :filter: docname in docnames ``` diff --git a/docs/test-functions/damped-oscillator.md b/docs/test-functions/damped-oscillator.md index bc6dacf..bc62a0f 100644 --- a/docs/test-functions/damped-oscillator.md +++ b/docs/test-functions/damped-oscillator.md @@ -221,6 +221,6 @@ tabulate( ## References ```{bibliography} -:style: plain +:style: unsrtalpha :filter: docname in docnames ``` \ No newline at end of file diff --git a/docs/test-functions/flood.md b/docs/test-functions/flood.md index 05e4c1b..13a793c 100644 --- a/docs/test-functions/flood.md +++ b/docs/test-functions/flood.md @@ -210,6 +210,6 @@ tabulate( ## References ```{bibliography} -:style: plain +:style: unsrtalpha :filter: docname in docnames ``` \ No newline at end of file diff --git a/docs/test-functions/franke-1.md b/docs/test-functions/franke-1.md index 566f62c..7158472 100644 --- a/docs/test-functions/franke-1.md +++ b/docs/test-functions/franke-1.md @@ -162,6 +162,5 @@ plt.gcf().set_dpi(150); ## References ```{bibliography} -:style: plain :filter: docname in docnames ``` diff --git a/docs/test-functions/franke-2.md b/docs/test-functions/franke-2.md index c5ed011..55e340a 100644 --- a/docs/test-functions/franke-2.md +++ b/docs/test-functions/franke-2.md @@ -165,6 +165,6 @@ plt.gcf().set_dpi(150); ## References ```{bibliography} -:style: plain +:style: unsrtalpha :filter: docname in docnames ``` diff --git a/docs/test-functions/franke-3.md b/docs/test-functions/franke-3.md index f0a3473..f98187a 100644 --- a/docs/test-functions/franke-3.md +++ b/docs/test-functions/franke-3.md @@ -153,6 +153,6 @@ plt.gcf().set_dpi(150); ## References ```{bibliography} -:style: plain +:style: unsrtalpha :filter: docname in docnames ``` diff --git a/docs/test-functions/franke-4.md b/docs/test-functions/franke-4.md index 4edc4fa..195b37d 100644 --- a/docs/test-functions/franke-4.md +++ b/docs/test-functions/franke-4.md @@ -164,6 +164,6 @@ plt.gcf().set_dpi(150); ## References ```{bibliography} -:style: plain +:style: unsrtalpha :filter: docname in docnames ``` diff --git a/docs/test-functions/franke-5.md b/docs/test-functions/franke-5.md index 14cc5b9..d4138a6 100644 --- a/docs/test-functions/franke-5.md +++ b/docs/test-functions/franke-5.md @@ -164,6 +164,6 @@ plt.gcf().set_dpi(150); ## References ```{bibliography} -:style: plain +:style: unsrtalpha :filter: docname in docnames ``` diff --git a/docs/test-functions/franke-6.md b/docs/test-functions/franke-6.md index 53550e3..b2bad03 100644 --- a/docs/test-functions/franke-6.md +++ b/docs/test-functions/franke-6.md @@ -165,6 +165,6 @@ plt.gcf().set_dpi(150); ## References ```{bibliography} -:style: plain +:style: unsrtalpha :filter: docname in docnames ``` diff --git a/docs/test-functions/ishigami.md b/docs/test-functions/ishigami.md index fd15cd4..c7102ec 100644 --- a/docs/test-functions/ishigami.md +++ b/docs/test-functions/ishigami.md @@ -312,6 +312,6 @@ the same. ## References ```{bibliography} -:style: plain +:style: unsrtalpha :filter: docname in docnames ``` diff --git a/docs/test-functions/mclain-s1.md b/docs/test-functions/mclain-s1.md index 319a563..bd44958 100644 --- a/docs/test-functions/mclain-s1.md +++ b/docs/test-functions/mclain-s1.md @@ -156,6 +156,6 @@ plt.gcf().set_dpi(150); ## References ```{bibliography} -:style: plain +:style: unsrtalpha :filter: docname in docnames ``` diff --git a/docs/test-functions/mclain-s2.md b/docs/test-functions/mclain-s2.md index 08d2795..3b24c24 100644 --- a/docs/test-functions/mclain-s2.md +++ b/docs/test-functions/mclain-s2.md @@ -156,6 +156,6 @@ plt.gcf().set_dpi(150); ## References ```{bibliography} -:style: plain +:style: unsrtalpha :filter: docname in docnames ``` diff --git a/docs/test-functions/mclain-s3.md b/docs/test-functions/mclain-s3.md index baa8dbc..1196fc8 100644 --- a/docs/test-functions/mclain-s3.md +++ b/docs/test-functions/mclain-s3.md @@ -159,6 +159,6 @@ plt.gcf().set_dpi(150); ## References ```{bibliography} -:style: plain +:style: unsrtalpha :filter: docname in docnames ``` diff --git a/docs/test-functions/mclain-s4.md b/docs/test-functions/mclain-s4.md index 4ed53f8..731e9d0 100644 --- a/docs/test-functions/mclain-s4.md +++ b/docs/test-functions/mclain-s4.md @@ -147,6 +147,6 @@ plt.gcf().set_dpi(150); ## References ```{bibliography} -:style: plain +:style: unsrtalpha :filter: docname in docnames ``` diff --git a/docs/test-functions/mclain-s5.md b/docs/test-functions/mclain-s5.md index 9b94b00..8618fa5 100644 --- a/docs/test-functions/mclain-s5.md +++ b/docs/test-functions/mclain-s5.md @@ -158,6 +158,6 @@ plt.gcf().set_dpi(150); ## References ```{bibliography} -:style: plain +:style: unsrtalpha :filter: docname in docnames ``` diff --git a/docs/test-functions/oakley-ohagan-1d.md b/docs/test-functions/oakley-ohagan-1d.md index 93e0a23..2b158d3 100644 --- a/docs/test-functions/oakley-ohagan-1d.md +++ b/docs/test-functions/oakley-ohagan-1d.md @@ -219,6 +219,6 @@ tabulate( ## References ```{bibliography} -:style: plain +:style: unsrtalpha :filter: docname in docnames ``` diff --git a/docs/test-functions/otl-circuit.md b/docs/test-functions/otl-circuit.md index 5123096..bdd10a8 100644 --- a/docs/test-functions/otl-circuit.md +++ b/docs/test-functions/otl-circuit.md @@ -225,6 +225,6 @@ tabulate( ## References ```{bibliography} -:style: plain +:style: unsrtalpha :filter: docname in docnames ``` \ No newline at end of file diff --git a/docs/test-functions/piston.md b/docs/test-functions/piston.md index 3151087..d1f16a5 100644 --- a/docs/test-functions/piston.md +++ b/docs/test-functions/piston.md @@ -228,6 +228,6 @@ tabulate( ## References ```{bibliography} -:style: plain +:style: unsrtalpha :filter: docname in docnames ``` \ No newline at end of file diff --git a/docs/test-functions/sulfur.md b/docs/test-functions/sulfur.md index 8c84ca0..a0cc321 100644 --- a/docs/test-functions/sulfur.md +++ b/docs/test-functions/sulfur.md @@ -457,6 +457,6 @@ tabulate( ## References ```{bibliography} -:style: plain +:style: unsrtalpha :filter: docname in docnames ``` \ No newline at end of file diff --git a/docs/test-functions/wing-weight.md b/docs/test-functions/wing-weight.md index 1d25c16..cec6828 100644 --- a/docs/test-functions/wing-weight.md +++ b/docs/test-functions/wing-weight.md @@ -197,6 +197,6 @@ tabulate( ## References ```{bibliography} -:style: plain +:style: unsrtalpha :filter: docname in docnames ``` \ No newline at end of file From 3b50ca82cd14833431c5a446630b0cb39450fb99 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Mon, 26 Jun 2023 15:59:45 +0200 Subject: [PATCH 30/73] Update the Zenodo DOI in README.md with the current version. - Update the CHANGELOG.md with important changes so far (unreleased). --- CHANGELOG.md | 9 +++++++++ README.md | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f7a01b..1de63a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,16 +32,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - An implementation of the one-dimensional function from Forrester et al. (2008). The function was used as a test function for optimization approaches using metamodels. +- An implementation of the Gramacy (2007) one-dimensional sine function, + a function with two regimes. ### Changed - The date format in CHANGELOG.md has been changed from YYYY-DD-MM to the ISO format YYYY-MM-DD. +- The bibliography style in the docs has been changed to 'unsrtalpha' + (alphanumeric labels, sorted by order of appearance). ### Fixed - The original citation for the Sobol'-G function has been fixed; it is now referred to Saltelli and Sobol' (1995). +- If a function is used as parameters in a test function (e.g., if + variable dimension), then it must have the keyword parameter + "spatial_dimension" for the function to be called when an instance of + a UQ test function is created. +- One-dimensional test function now returns a one-dimensional array. ## [0.1.1] - 2023-03-07 diff --git a/README.md b/README.md index cfa3cbc..80f5364 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # UQTestFuns -[![DOI](http://img.shields.io/badge/DOI-10.5281/zenodo.7701904-blue.svg?style=flat-square)](https://doi.org/10.5281/zenodo.7701904) +[![DOI](http://img.shields.io/badge/DOI-10.5281/zenodo.7703917-blue.svg?style=flat-square)](https://zenodo.org/record/7703917) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg?style=flat-square)](https://github.com/psf/black) [![Python 3.8](https://img.shields.io/badge/python-3.7-blue.svg?style=flat-square)](https://www.python.org/downloads/release/python-370/) [![License](https://img.shields.io/github/license/damar-wicaksono/uqtestfuns?style=flat-square)](https://choosealicense.com/licenses/mit/) From 617da7f6ed1a15d1665a9c31c7ccbc8d17be283f Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Mon, 26 Jun 2023 16:57:56 +0200 Subject: [PATCH 31/73] When list_functions() is called with "tag", it is no longer displayed. - To save terminal space, when list_functions() is called with the tag argument, the application tags are no longer displayed. --- CHANGELOG.md | 2 + .../about-uq-test-functions.md | 2 +- src/uqtestfuns/helpers.py | 67 +++++++++++++------ 3 files changed, 48 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1de63a9..eb8c5e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ISO format YYYY-MM-DD. - The bibliography style in the docs has been changed to 'unsrtalpha' (alphanumeric labels, sorted by order of appearance). +- When `list_functions()` is called with a `tag` argument, + then the application tags are no longer displayed to save terminal spaces. ### Fixed diff --git a/docs/getting-started/about-uq-test-functions.md b/docs/getting-started/about-uq-test-functions.md index 1f05b65..9d3c816 100644 --- a/docs/getting-started/about-uq-test-functions.md +++ b/docs/getting-started/about-uq-test-functions.md @@ -212,6 +212,6 @@ Satisfying all the above requirements is exactly the goal of the UQTestFuns package. ```{bibliography} -:style: plain +:style: unsrtalpha :filter: docname in docnames ``` diff --git a/src/uqtestfuns/helpers.py b/src/uqtestfuns/helpers.py index 16b2134..d045bf9 100644 --- a/src/uqtestfuns/helpers.py +++ b/src/uqtestfuns/helpers.py @@ -92,13 +92,21 @@ def list_functions( return constructors # --- Create a tabulated view of the list - header_names = [ - "No.", - "Constructor", - "Spatial Dimension", - "Application", - "Description", - ] + if tag is None: + header_names = [ + "No.", + "Constructor", + "Dimension", + "Application", + "Description", + ] + else: + header_names = [ + "No.", + "Constructor", + "Dimension", + "Description", + ] values = [] for idx, available_class_name in enumerate( @@ -113,26 +121,41 @@ def list_functions( available_class.default_spatial_dimension ) - tags = ", ".join(available_class.tags) - description = available_class.description - value = [ - idx + 1, - f"{available_class_name}()", - f"{default_spatial_dimension}", - tags, - f"{description}", - ] + if tag is None: + tags = ", ".join(available_class.tags) + value = [ + idx + 1, + f"{available_class_name}()", + f"{default_spatial_dimension}", + tags, + f"{description}", + ] + else: + value = [ + idx + 1, + f"{available_class_name}()", + f"{default_spatial_dimension}", + f"{description}", + ] values.append(value) - table = tbl( - values, - headers=header_names, - stralign="center", - colalign=("center", "center", "center", "center", "left"), - ) + if tag is None: + table = tbl( + values, + headers=header_names, + stralign="center", + colalign=("center", "center", "center", "center", "left"), + ) + else: + table = tbl( + values, + headers=header_names, + stralign="center", + colalign=("center", "center", "center", "left"), + ) print(table) From 9ef956fc1e618c0d08a4acfbd82a0e83185ee16c Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Mon, 26 Jun 2023 17:24:11 +0200 Subject: [PATCH 32/73] Follow the current best-practice of single-sourcing package version. - 'pkg_resources' has been deprecated as an API and should not be used to fetch the package version. Instead the function 'version' from 'importlib.metadata' (>= Python 3.8) or 'importlib_metadata.metadata' (< Python 3.8) is used. --- setup.cfg | 1 + src/uqtestfuns/__init__.py | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index ca2dcc2..58fadfc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -33,6 +33,7 @@ install_requires = numpy>=1.13.3 scipy>=1.7.3 tabulate>=0.8.10 + importlib-metadata>=1.0; python_version < "3.8" [options.packages.find] where = src diff --git a/src/uqtestfuns/__init__.py b/src/uqtestfuns/__init__.py index c8252bb..4961a00 100644 --- a/src/uqtestfuns/__init__.py +++ b/src/uqtestfuns/__init__.py @@ -1,7 +1,7 @@ """ This is the package init for UQTestFuns. """ -import pkg_resources +import sys from .core import UnivDist from .core import ProbInput @@ -17,7 +17,12 @@ from .helpers import list_functions -__version__ = pkg_resources.require("uqtestfuns")[0].version +if sys.version_info >= (3, 8): + from importlib import metadata +else: + import importlib_metadata as metadata + +__version__ = metadata.version("uqtestfuns") __all__ = [ "UnivDist", From 2c10445c1de3c5f07658dbea55cb33c8462a2bb4 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Mon, 26 Jun 2023 18:18:53 +0200 Subject: [PATCH 33/73] Add a CITATION file to the repo. --- CITATION.cff | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 CITATION.cff diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..9090e55 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,40 @@ +# This CITATION.cff file was generated with cffinit. +# Visit https://bit.ly/cffinit to generate yours today! + +cff-version: 1.2.0 +title: UQTestFuns +message: >- + If you use this software, please cite it using the + metadata from this file. +type: software +authors: + - given-names: Damar + family-names: Wicaksono + affiliation: CASUS/HZDR + orcid: 'https://orcid.org/0000-0001-8587-7730' +identifiers: + - type: doi + value: 10.5281/zenodo.7703917 + description: The Zenodo URL of version 0.1.1 of the package +repository-code: 'https://github.com/damar-wicaksono/uqtestfuns' +url: 'https://uqtestfuns.readthedocs.io/en/latest/' +repository-artifact: 'https://pypi.org/project/uqtestfuns/' +abstract: >- + UQTestFuns is an open-source Python3 library of test + functions commonly used within the uncertainty + quantification (UQ) community. The package aims to provide + a lightweight implementation (with minimal dependencies) + of many test functions available in the UQ literature, + a single entry point (combining models and their + probabilistic input specification) to a wide range of test + functions, and an opportunity for an open-source contribution + where new test functions and reference results are posted. +keywords: + - uncertainty-quantification + - test-functions + - sensitivity-analysis + - metamodeling +license: MIT +commit: f6a2c9247a59a18a42451592059a2bf16e3c3647 +version: 0.1.1 +date-released: '2023-03-07' From 0d9b3c46022805bf307d449c7be1b43e2a66bb3a Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Mon, 26 Jun 2023 19:00:09 +0200 Subject: [PATCH 34/73] Update organization files for v0.2.0 release. - CHANGELOG, README, CITATION, setup.cfg all have been updated accordingly. - The one-dimensional test function `OakleyOHagan1D` has been renamed to `Oakley1D`. --- CHANGELOG.md | 33 +++++++++++-------- CITATION.cff | 9 +++-- README.md | 2 +- docs/_toc.yml | 4 +-- docs/fundamentals/metamodeling.md | 2 +- docs/test-functions/available.md | 2 +- .../{oakley-ohagan-1d.md => oakley-1d.md} | 18 +++++----- setup.cfg | 2 +- src/uqtestfuns/test_functions/__init__.py | 4 +-- .../{oakley_ohagan_1d.py => oakley2002.py} | 13 ++++---- 10 files changed, 48 insertions(+), 41 deletions(-) rename docs/test-functions/{oakley-ohagan-1d.md => oakley-1d.md} (92%) rename src/uqtestfuns/test_functions/{oakley_ohagan_1d.py => oakley2002.py} (80%) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb8c5e9..6b9c181 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,33 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.2.0] - 2023-06-26 + ### Added -- An instance of NumPy random number generator is now attached to instances of - `UnivDist` and `ProbInput`. The random seed - number may be passed to the corresponding constructor for reproducibility. - The two-dimensional Franke functions (1st, 2nd, 3rd, 4th, 5th, and 6th), relevant for metamodeling exercises, are added as UQ test functions. - The two-dimensional McLain functions (S1, S2, S3, S4, and S5), relevant for metamodeling exercises, are added as UQ test functions. -- An implementation of the Welch et al. (1992) test function, - a 20-dimensional function used in the context of metamodeling and sensitivity - analysis. +- An implementation of the Welch et al. (1992) test function, a 20-dimensional + function used in the context of metamodeling and sensitivity analysis. - Four M-dimensional test functions from Bratley et al. (1992) useful for testing multi-dimensional numerical integrations as well as - global sensitivity analysis. + global sensitivity analysis methods. - Add a new parameterization to the Sobol'-G function taken from Bratley et al. (1992) and Saltelli and Sobol' (1995). -- Two base classes are now available `UQTestFunBareABC` and `UQTestFunABC`. - The former is used to implement a _bare_ UQ test function - (with only `evaluate()` and `ProbInput`), while the latter is used to - implement _published_ UQ test functions in the code base (i.e., with - additional metadata such as tags and description). - An implementation of the one-dimensional function from Forrester et al. (2008). The function was used as a test function for optimization approaches using metamodels. - An implementation of the Gramacy (2007) one-dimensional sine function, a function with two regimes. +- Two base classes are now available `UQTestFunBareABC` and `UQTestFunABC`. + The former is used to implement a _bare_ UQ test function + (with only `evaluate()` and `ProbInput`), while the latter is used to + implement _published_ UQ test functions in the code base (i.e., with + additional metadata such as tags and description). +- An instance of NumPy random number generator is now attached to instances of + `UnivDist` and `ProbInput`. The random seed number may be passed + to the corresponding constructor for reproducibility. +- CITATION.cff file to the code base. ### Changed @@ -43,6 +45,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 (alphanumeric labels, sorted by order of appearance). - When `list_functions()` is called with a `tag` argument, then the application tags are no longer displayed to save terminal spaces. +- The one-dimensional `OakleyOHagan1D` function has been renamed to + `Oakley1D`. ### Fixed @@ -51,7 +55,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - If a function is used as parameters in a test function (e.g., if variable dimension), then it must have the keyword parameter "spatial_dimension" for the function to be called when an instance of - a UQ test function is created. + a UQ test function is created. This is to allow an arbitrary function + (without a parameter named "spatial_dimension") to be a parameter of + UQ test function. - One-dimensional test function now returns a one-dimensional array. ## [0.1.1] - 2023-03-07 @@ -101,6 +107,7 @@ First public release of UQTestFuns. - Mirror GitHub action to the [CASUS organization](https://github.com/casus) [Unreleased]: https://github.com/damar-wicaksono/uqtestfuns/compare/main...dev +[0.2.0]: https://github.com/damar-wicaksono/uqtestfuns/compare/v0.1.1...v0.2.0 [0.1.1]: https://github.com/damar-wicaksono/uqtestfuns/compare/v0.1.0...v0.1.1 [0.1.0]: https://github.com/damar-wicaksono/uqtestfuns/compare/v0.0.1...v0.1.0 [0.0.1]: https://github.com/damar-wicaksono/uqtestfuns/releases/tag/v0.0.1 diff --git a/CITATION.cff b/CITATION.cff index 9090e55..3af41ca 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -14,8 +14,8 @@ authors: orcid: 'https://orcid.org/0000-0001-8587-7730' identifiers: - type: doi - value: 10.5281/zenodo.7703917 - description: The Zenodo URL of version 0.1.1 of the package + value: 10.5281/zenodo.7703922 + description: The Zenodo URL of version 0.2.0 of the package repository-code: 'https://github.com/damar-wicaksono/uqtestfuns' url: 'https://uqtestfuns.readthedocs.io/en/latest/' repository-artifact: 'https://pypi.org/project/uqtestfuns/' @@ -35,6 +35,5 @@ keywords: - sensitivity-analysis - metamodeling license: MIT -commit: f6a2c9247a59a18a42451592059a2bf16e3c3647 -version: 0.1.1 -date-released: '2023-03-07' +version: 0.2.0 +date-released: '2023-06-26' diff --git a/README.md b/README.md index 80f5364..860799f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # UQTestFuns -[![DOI](http://img.shields.io/badge/DOI-10.5281/zenodo.7703917-blue.svg?style=flat-square)](https://zenodo.org/record/7703917) +[![DOI](http://img.shields.io/badge/DOI-10.5281/zenodo.7703922-blue.svg?style=flat-square)](https://doi.org/10.5281/zenodo.7703922) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg?style=flat-square)](https://github.com/psf/black) [![Python 3.8](https://img.shields.io/badge/python-3.7-blue.svg?style=flat-square)](https://www.python.org/downloads/release/python-370/) [![License](https://img.shields.io/github/license/damar-wicaksono/uqtestfuns?style=flat-square)](https://choosealicense.com/licenses/mit/) diff --git a/docs/_toc.yml b/docs/_toc.yml index 72c26c4..e0b6754 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -57,8 +57,8 @@ parts: title: Gramacy (2007) Sine - file: test-functions/ishigami title: Ishigami - - file: test-functions/oakley-ohagan-1d - title: Oakley-O'Hagan 1D + - file: test-functions/oakley-1d + title: Oakley & O'Hagan (2002) 1D - file: test-functions/otl-circuit title: OTL Circuit - file: test-functions/mclain-s1 diff --git a/docs/fundamentals/metamodeling.md b/docs/fundamentals/metamodeling.md index 88bf422..82cfb2f 100644 --- a/docs/fundamentals/metamodeling.md +++ b/docs/fundamentals/metamodeling.md @@ -31,7 +31,7 @@ in the comparison of metamodeling approaches. | {ref}`(5th) Franke ` | 2 | `Franke5()` | | {ref}`(6th) Franke ` | 2 | `Franke6()` | | {ref}`Gramacy (2007) 1D Sine ` | 1 | `Gramacy1DSine()` | -| {ref}`Oakley-O'Hagan 1D ` | 1 | `OakleyOHagan1D()` | +| {ref}`Oakley and O'Hagan (2002) 1D ` | 1 | `Oakley1D()` | | {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | | {ref}`McLain S1 ` | 2 | `McLainS1()` | | {ref}`McLain S2 ` | 2 | `McLainS2()` | diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index 19fbb9c..1b402f4 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -37,7 +37,7 @@ available in the current UQTestFuns, regardless of their typical applications. | {ref}`(6th) Franke ` | 2 | `Franke6()` | | {ref}`Gramacy (2007) 1D Sine ` | 1 | `Gramacy1DSine()` | | {ref}`Ishigami ` | 3 | `Ishigami()` | -| {ref}`Oakley-O'Hagan 1D ` | 1 | `OakleyOHagan1D()` | +| {ref}`Oakley and O'Hagan (2002) 1D ` | 1 | `Oakley1D()` | | {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | | {ref}`McLain S1 ` | 2 | `McLainS1()` | | {ref}`McLain S2 ` | 2 | `McLainS2()` | diff --git a/docs/test-functions/oakley-ohagan-1d.md b/docs/test-functions/oakley-1d.md similarity index 92% rename from docs/test-functions/oakley-ohagan-1d.md rename to docs/test-functions/oakley-1d.md index 2b158d3..839b685 100644 --- a/docs/test-functions/oakley-ohagan-1d.md +++ b/docs/test-functions/oakley-1d.md @@ -12,12 +12,13 @@ kernelspec: name: python3 --- -(test-functions:oakley-ohagan-1d)= -# One-dimensional (1D) Oakley-O'Hagan Function +(test-functions:oakley-1d)= +# Oakley and O'Hagan (2002) One-dimensional (1D) Function -The 1D Oakley-O'Hagan function is a one-dimensional scalar-valued function. -It was used in {cite}`Oakley2002` as a test function for illustrating -metamodeling and uncertainty propagation approaches. +The 1D function from Oakley and O'Hagan (2002) (or `Oakley1D` function +for short) is a scalar-valued test function. +It was used in {cite}`Oakley2002` as a test function for illustrating metamodeling +and uncertainty propagation approaches. ```{code-cell} ipython3 import numpy as np @@ -30,7 +31,7 @@ A plot of the function is shown below for $x \in [-12, 12]$. ```{code-cell} ipython3 :tags: [remove-input] -my_testfun = uqtf.OakleyOHagan1D() +my_testfun = uqtf.Oakley1D() xx = np.linspace(-12, 12, 1000)[:, np.newaxis] yy = my_testfun(xx) @@ -45,11 +46,10 @@ plt.gcf().set_dpi(150); ## Test function instance -To create a default instance of the one-dimensional Oakley-O'Hagan -test function: +To create a default instance of the test function: ```{code-cell} ipython3 -my_testfun = uqtf.OakleyOHagan1D() +my_testfun = uqtf.Oakley1D() ``` Check if it has been correctly instantiated: diff --git a/setup.cfg b/setup.cfg index 58fadfc..57f37a9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = uqtestfuns -version = 0.1.1 +version = 0.2.0 url = https://github.com/damar-wicaksono/uqtestfuns author = Damar Wicaksono author_email = damar.wicaksono@outlook.com diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index 018bffb..63589a6 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -10,7 +10,7 @@ from .franke import Franke1, Franke2, Franke3, Franke4, Franke5, Franke6 from .gramacy2007 import Gramacy1DSine from .ishigami import Ishigami -from .oakley_ohagan_1d import OakleyOHagan1D +from .oakley2002 import Oakley1D from .otl_circuit import OTLCircuit from .mclain import McLainS1, McLainS2, McLainS3, McLainS4, McLainS5 from .piston import Piston @@ -40,7 +40,7 @@ "Franke6", "Gramacy1DSine", "Ishigami", - "OakleyOHagan1D", + "Oakley1D", "OTLCircuit", "McLainS1", "McLainS2", diff --git a/src/uqtestfuns/test_functions/oakley_ohagan_1d.py b/src/uqtestfuns/test_functions/oakley2002.py similarity index 80% rename from src/uqtestfuns/test_functions/oakley_ohagan_1d.py rename to src/uqtestfuns/test_functions/oakley2002.py index 17bb373..94e4dc9 100644 --- a/src/uqtestfuns/test_functions/oakley_ohagan_1d.py +++ b/src/uqtestfuns/test_functions/oakley2002.py @@ -1,8 +1,9 @@ """ -Module with an implementation of the 1D Oakley-O'Hagan test function. +Module with implementations of test functions from Oakley and O'Hagan (2002). -The 1D Oakley-O'Hagan test function is a one-dimensional scalar-valued -function. It was used in [1] as a test function for illustrating metamodeling +The 1D test function from Oakley and O'Hagan (2002) (or `Oakley1D` function +for short) is a one-dimensional scalar-valued function. +It was used in [1] as a test function for illustrating metamodeling and uncertainty propagation approaches. References @@ -18,7 +19,7 @@ from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecFixDim from ..core.uqtestfun_abc import UQTestFunABC -__all__ = ["OakleyOHagan1D"] +__all__ = ["Oakley1D"] AVAILABLE_INPUT_SPECS = { "Oakley2002": ProbInputSpecFixDim( @@ -61,8 +62,8 @@ def evaluate(xx: np.ndarray) -> np.ndarray: return yy -class OakleyOHagan1D(UQTestFunABC): - """A concrete implementation of the 1D Oakley-O'Hagan test function.""" +class Oakley1D(UQTestFunABC): + """An implementation of the 1D function from Oakley & O'Hagan (2002).""" _tags = ["metamodeling"] _description = "One-dimensional function from Oakley and O'Hagan (2002)" From d64663ade96fae83b51a988532359b0ad17bce37 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Tue, 27 Jun 2023 10:39:52 +0200 Subject: [PATCH 35/73] Add an implementation of the Gayton Hat function. - The function is a two-dimensional function used in the context of reliability analysis. - A new docs section on the list of functions for reliability analysis has been added. --- docs/_toc.yml | 4 + docs/fundamentals/reliability.md | 34 ++++ docs/references.bib | 10 + docs/test-functions/available.md | 1 + docs/test-functions/gayton-hat.md | 191 ++++++++++++++++++++ docs/test-functions/gramacy-1d-sine.md | 2 +- src/uqtestfuns/test_functions/__init__.py | 2 + src/uqtestfuns/test_functions/gayton_hat.py | 81 +++++++++ 8 files changed, 324 insertions(+), 1 deletion(-) create mode 100644 docs/fundamentals/reliability.md create mode 100644 docs/test-functions/gayton-hat.md create mode 100644 src/uqtestfuns/test_functions/gayton_hat.py diff --git a/docs/_toc.yml b/docs/_toc.yml index e0b6754..57b7911 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -16,6 +16,8 @@ parts: title: Metamodeling - file: fundamentals/sensitivity title: Sensitivity Analysis + - file: fundamentals/reliability + title: Reliability Analysis - file: fundamentals/integration title: Integration - file: fundamentals/optimization @@ -53,6 +55,8 @@ parts: title: (5th) Franke - file: test-functions/franke-6 title: (6th) Franke + - file: test-functions/gayton-hat + title: Gayton Hat - file: test-functions/gramacy-1d-sine title: Gramacy (2007) Sine - file: test-functions/ishigami diff --git a/docs/fundamentals/reliability.md b/docs/fundamentals/reliability.md new file mode 100644 index 0000000..2cbde32 --- /dev/null +++ b/docs/fundamentals/reliability.md @@ -0,0 +1,34 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +# Test Functions for Reliability Analysis + +The table below listed the available test functions typically used +in the comparison of reliability analysis methods. + +| Name | Spatial Dimension | Constructor | +|:---------------------------------------------:|:-----------------:|:--------------:| +| {ref}`Gayton Hat ` | 2 | `GaytonHat()` | + +In a Python terminal, you can list all the available functions relevant +for metamodeling applications using ``list_functions()`` and filter the results +using the ``tag`` parameter: + +```{code-cell} ipython3 +:tags: ["output_scroll"] + +import uqtestfuns as uqtf + +uqtf.list_functions(tag="reliability") +``` diff --git a/docs/references.bib b/docs/references.bib index b1f3ec7..fb96ea7 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -510,4 +510,14 @@ @Article{Gramacy2007 doi = {10.18637/jss.v019.i09}, } +@Article{Echard2013, + author = {B. Echard and N. Gayton and M. Lemaire and N. Relun}, + journal = {Reliability Engineering \& System Safety}, + title = {A combined Importance Sampling and Kriging reliability method for small failure probabilities with time-demanding numerical models}, + year = {2013}, + pages = {232--240}, + volume = {111}, + doi = {10.1016/j.ress.2012.10.008}, +} + @Comment{jabref-meta: databaseType:bibtex;} diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index 1b402f4..92fe892 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -35,6 +35,7 @@ available in the current UQTestFuns, regardless of their typical applications. | {ref}`(4th) Franke ` | 2 | `Franke4()` | | {ref}`(5th) Franke ` | 2 | `Franke5()` | | {ref}`(6th) Franke ` | 2 | `Franke6()` | +| {ref}`Gayton Hat ` | 2 | `GaytonHat()` | | {ref}`Gramacy (2007) 1D Sine ` | 1 | `Gramacy1DSine()` | | {ref}`Ishigami ` | 3 | `Ishigami()` | | {ref}`Oakley and O'Hagan (2002) 1D ` | 1 | `Oakley1D()` | diff --git a/docs/test-functions/gayton-hat.md b/docs/test-functions/gayton-hat.md new file mode 100644 index 0000000..d9e24ff --- /dev/null +++ b/docs/test-functions/gayton-hat.md @@ -0,0 +1,191 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:gayton-hat)= +# Gayton Hat Function + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +The Gayton Hat function is a two-dimensional limit-state function used +in {cite}`Echard2013` as a test function for reliability analysis algorithms. + +The plots of the function are shown below. The left plot shows the contour +plot with a single contour line at function value of $0.0$ (the limit-state +surface) and the right plot shows the same plot with $10^6$ sample points +overlaid. + +```{code-cell} ipython3 +:tags: [remove-input] + +my_fun = uqtf.GaytonHat(rng_seed_prob_input=237324) +xx = my_fun.prob_input.get_sample(1000000) +yy = my_fun(xx) +idx_neg = yy <= 0.0 +idx_pos = yy > 0.0 + +lb_1 = my_fun.prob_input.marginals[0].lower +ub_1 = my_fun.prob_input.marginals[0].upper +lb_2 = my_fun.prob_input.marginals[1].lower +ub_2 = my_fun.prob_input.marginals[1].upper + +# Create 2-dimensional grid +xx_1 = np.linspace(lb_1, ub_1, 1000)[:, np.newaxis] +xx_2 = np.linspace(lb_2, ub_2, 1000)[:, np.newaxis] +mesh_2d = np.meshgrid(xx_1, xx_2) +xx_2d = np.array(mesh_2d).T.reshape(-1, 2) +yy_2d = my_fun(xx_2d) + +# --- Create the plot +fig = plt.figure(figsize=(10, 5)) + +# Contour plot +axs_1 = plt.subplot(121) +cf = axs_1.contour( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000, 1000).T, + levels=0, + colors=["#ca0020"], +) +axs_1.set_xlim([lb_1, ub_1]) +axs_1.set_ylim([lb_2, ub_2]) +axs_1.set_xlabel("$U_1$", fontsize=14) +axs_1.set_ylabel("$U_2$", fontsize=14) +#axs_1.set_title("Contour plot", fontsize=14) +axs_1.set_aspect("equal", "box") +axs_1.clabel(cf, inline=True, fontsize=14) + +# Scatter plot +axs_2 = plt.subplot(122) +cf = axs_2.contour( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000, 1000).T, + levels=0, + colors=["#ca0020"], +) +axs_2.scatter( + xx[idx_neg, 0], + xx[idx_neg, 1], + color="#ca0020", + marker=".", + s=30, + label="$\mathcal{M}(x) \leq 0$" +) +axs_2.scatter( + xx[idx_pos, 0], + xx[idx_pos, 1], + color="#0571b0", + marker=".", + s=30, + label="$\mathcal{M}(x) > 0$" +) +axs_2.set_xlim([lb_1, ub_1]) +axs_2.set_ylim([lb_2, ub_2]) +axs_2.set_xlabel("$U_1$", fontsize=14) +axs_2.set_ylabel("$U_2$", fontsize=14) +#axs_2.set_title("Scatter plot", fontsize=14) +axs_2.set_aspect("equal", "box") +axs_2.clabel(cf, inline=True, fontsize=14) +axs_2.legend(fontsize=14, loc="lower right"); + +plt.gcf().tight_layout(pad=3.0) +plt.gcf().set_dpi(150); +``` + +## Test function instance + +To create a default instance of the test function: + +```{code-cell} ipython3 +my_testfun = uqtf.GaytonHat() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +## Description + +The test function is analytically defined as follows: + +$$ +\mathcal{M}(\boldsymbol{x}) = 0.5 (U_1 - 2)^2 - 1.5 (U_2 - 5)^3 - 3, +$$ + +where $\boldsymbol{x} = \{ U_1, U_2 \}$ is the two-dimensional vector of +input variables further defined below. + +## Probabilistic input + +Based on {cite}`Echard2013`, the probabilistic input model for +the test function consists of two independent standard normal random variables +(see the table below). + +```{code-cell} ipython3 +my_testfun.prob_input +``` + +## Reference results + +This section provides several reference results of typical UQ analyses involving +the test function. + +### Sample histogram + +Shown below is the histogram of the output based on $10^6$ random points: + +```{code-cell} ipython3 +:tags: [hide-input] + +xx_test = my_testfun.prob_input.get_sample(1000000) +yy_test = my_testfun(xx_test) +idx_pos = yy_test > 0 +idx_neg = yy_test <= 0 + +plt.hist(yy_test[idx_pos], bins="auto", color="#0571b0"); +plt.hist(yy_test[idx_neg], bins="auto", color="#ca0020"); +plt.axvline(0, linewidth=1.5, color="#ca0020"); + +plt.grid(); +plt.ylabel("Counts [-]"); +plt.xlabel("$\mathcal{M}(\mathbf{X})$"); +plt.gcf().set_dpi(150); +``` + +### Failure probability ($P_f$) + +Some reference values for the failure probability $P_f$ from the literature +are summarized in the table below. + +| Method | $N$ | $\hat{P}_f$ | $\mathrm{CoV}[\hat{P}_f]$ | Source | Remark | +|:------:|:-------------------:|:---------------------:|:-------------------------:|:------------------:|--------------------------------| +| MCS | $5 \times 10^7$ | $2.85 \times 10^{-5}$ | $2.64 \%$ | {cite}`Echard2013` | Median over $100$ replications | +| FORM | $19$ | $4.21 \times 10^{-5}$ | — | {cite}`Echard2013` | Median over $100$ replications | +| IS | $19 + \times 10^4$ | $2.86 \times 10^{-5}$ | $2.39 \%$ | {cite}`Echard2013` | Median over $100$ replications | +| AK+IS | $19 + 7$ | $2.86 \times 10^{-5}$ | $2.39 \%$ | {cite}`Echard2013` | Median over $100$ replications | + + +## References + +```{bibliography} +:style: unsrtalpha +:filter: docname in docnames +``` diff --git a/docs/test-functions/gramacy-1d-sine.md b/docs/test-functions/gramacy-1d-sine.md index 0cdb316..2e74c80 100644 --- a/docs/test-functions/gramacy-1d-sine.md +++ b/docs/test-functions/gramacy-1d-sine.md @@ -56,7 +56,7 @@ plt.gcf().tight_layout(pad=3.0) plt.gcf().set_dpi(150); ``` -Note that the function is discontinous at $x = 9.6%$ which also pinpoint +Note that the function is discontinuous at $x = 9.6%$ which also pinpoints the change of regime. ## Test function instance diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index 63589a6..cb14246 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -8,6 +8,7 @@ from .flood import Flood from .forrester import Forrester2008 from .franke import Franke1, Franke2, Franke3, Franke4, Franke5, Franke6 +from .gayton_hat import GaytonHat from .gramacy2007 import Gramacy1DSine from .ishigami import Ishigami from .oakley2002 import Oakley1D @@ -38,6 +39,7 @@ "Franke4", "Franke5", "Franke6", + "GaytonHat", "Gramacy1DSine", "Ishigami", "Oakley1D", diff --git a/src/uqtestfuns/test_functions/gayton_hat.py b/src/uqtestfuns/test_functions/gayton_hat.py new file mode 100644 index 0000000..4f0c7d0 --- /dev/null +++ b/src/uqtestfuns/test_functions/gayton_hat.py @@ -0,0 +1,81 @@ +""" +Module with an implementation of the Gayton Hat test function. + +The Gayton Hat function is a two-dimensional limit-state function used +in [1] as a test function for reliability analysis algorithms. + +References +---------- + +1. B. Echard, N. Gayton, M. Lemaire, and N. Relun, “A combined Importance + Sampling and Kriging reliability method for small failure probabilities + with time-demanding numerical models”, Reliability Engineering + and System Safety, vol. 111, pp. 232-240, 2013. + DOI: 10.1016/j.ress.2012.10.008 +""" +import numpy as np + +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecFixDim +from ..core.uqtestfun_abc import UQTestFunABC + +__all__ = ["GaytonHat"] + + +AVAILABLE_INPUT_SPECS = { + "Echard2013": ProbInputSpecFixDim( + name="Echard2013", + description=( + "Probabilistic input for the Gayton Hat function " + "from Echard et al. (2013)" + ), + marginals=[ + UnivDistSpec( + name="U1", + distribution="normal", + parameters=[0, 1], + description="None", + ), + UnivDistSpec( + name="U2", + distribution="normal", + parameters=[0, 1], + description="None", + ), + ], + copulas=None, + ), +} + + +def evaluate(xx: np.ndarray) -> np.ndarray: + """Evaluate the Gayton Hat function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + 1-Dimensional input values given by an N-by-1 array + where N is the number of input values. + + Returns + ------- + np.ndarray + The output of the test function evaluated on the input values. + The output is a 1-dimensional array of length N. + """ + yy = 0.5 * (xx[:, 0] - 2) ** 2 - 1.5 * (xx[:, 1] - 5) ** 3 - 3 + + return yy + + +class GaytonHat(UQTestFunABC): + """A concrete implementation of the Gayton Hat test function.""" + + _tags = ["reliability"] + _description = ( + "Two-Dimensional Gayton Hat function from Echard et al. (2013)" + ) + _available_inputs = AVAILABLE_INPUT_SPECS + _available_parameters = None + _default_spatial_dimension = 2 + + eval_ = staticmethod(evaluate) From 55dbfe4699f0aeff20297818eee0c9db4f280ac1 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Tue, 27 Jun 2023 17:39:39 +0200 Subject: [PATCH 36/73] Add an implementation of the damped oscillator reliability problem - The eight-dimensional reliability problem is based on the Damped Oscillator model already in the code base. --- CHANGELOG.md | 9 + docs/_toc.yml | 2 + docs/fundamentals/reliability.md | 7 +- docs/references.bib | 20 ++ docs/test-functions/available.md | 65 +++---- .../damped-oscillator-reliability.md | 172 ++++++++++++++++++ docs/test-functions/damped-oscillator.md | 16 +- docs/test-functions/gayton-hat.md | 51 ++++-- src/uqtestfuns/test_functions/__init__.py | 3 +- .../test_functions/damped_oscillator.py | 136 +++++++++++++- 10 files changed, 416 insertions(+), 65 deletions(-) create mode 100644 docs/test-functions/damped-oscillator-reliability.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b9c181..2b2cb96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- The two-dimensional Gayton Hat function from Echard et al. (2013) used + in the context of reliability analysis. +- The eight-dimensional damped oscillator reliability problem from + Der Kiureghian and De Stefano (1990); the problem is based on the existing + Damped Oscillator model in the code base. +- New docs section on list of functions for reliability analysis. + ## [0.2.0] - 2023-06-26 ### Added diff --git a/docs/_toc.yml b/docs/_toc.yml index 57b7911..4bbb7d7 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -39,6 +39,8 @@ parts: title: Bratley et al. (1992) D - file: test-functions/damped-oscillator title: Damped Oscillator + - file: test-functions/damped-oscillator-reliability + title: Damped Oscillator Reliability - file: test-functions/flood title: Flood - file: test-functions/forrester diff --git a/docs/fundamentals/reliability.md b/docs/fundamentals/reliability.md index 2cbde32..1192117 100644 --- a/docs/fundamentals/reliability.md +++ b/docs/fundamentals/reliability.md @@ -17,9 +17,10 @@ kernelspec: The table below listed the available test functions typically used in the comparison of reliability analysis methods. -| Name | Spatial Dimension | Constructor | -|:---------------------------------------------:|:-----------------:|:--------------:| -| {ref}`Gayton Hat ` | 2 | `GaytonHat()` | +| Name | Spatial Dimension | Constructor | +|:-----------------------------------------------------------------------------------:|:-----------------:|:-------------------------------:| +| {ref}`Damped Oscillator Reliability ` | 8 | `DampedOscillatorReliability()` | +| {ref}`Gayton Hat ` | 2 | `GaytonHat()` | In a Python terminal, you can list all the available functions relevant for metamodeling applications using ``list_functions()`` and filter the results diff --git a/docs/references.bib b/docs/references.bib index fb96ea7..62df905 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -520,4 +520,24 @@ @Article{Echard2013 doi = {10.1016/j.ress.2012.10.008}, } +@TechReport{DerKiureghian1990, + author = {Der Kiureghian, Armen and De Stefano, Mario}, + institution = {Department of Civil and Environmental Engineering, University of California, Berkeley}, + title = {An efficient algorithm for second-order reliability analysis}, + year = {1990}, + number = {{UCB/SEMM-90/20}}, + type = {techreport}, +} + +@Article{Bourinet2011, + author = {J.-M. Bourinet and F. Deheeger and M. Lemaire}, + journal = {Structural Safety}, + title = {Assessing small failure probabilities by combined subset simulation and support vector machines}, + year = {2011}, + number = {6}, + pages = {343--353}, + volume = {33}, + doi = {10.1016/j.strusafe.2011.06.001}, +} + @Comment{jabref-meta: databaseType:bibtex;} diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index 92fe892..ac5867f 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -18,38 +18,39 @@ kernelspec: The table below lists all the available _classic_ test functions from the literature available in the current UQTestFuns, regardless of their typical applications. -| Name | Spatial Dimension | Constructor | -|:--------------------------------------------------------------:|:-----------------:|:--------------------:| -| {ref}`Ackley ` | M | `Ackley()` | -| {ref}`Borehole ` | 8 | `Borehole()` | -| {ref}`Bratley et al. (1992) A ` | M | `Bratley1992a()` | -| {ref}`Bratley et al. (1992) B ` | M | `Bratley1992b()` | -| {ref}`Bratley et al. (1992) C ` | M | `Bratley1992c()` | -| {ref}`Bratley et al. (1992) D ` | M | `Bratley1992d()` | -| {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | -| {ref}`Flood ` | 8 | `Flood()` | -| {ref}`Forrester et al. (2008) ` | 1 | `Forrester2008()` | -| {ref}`(1st) Franke ` | 2 | `Franke1()` | -| {ref}`(2nd) Franke ` | 2 | `Franke2()` | -| {ref}`(3rd) Franke ` | 2 | `Franke3()` | -| {ref}`(4th) Franke ` | 2 | `Franke4()` | -| {ref}`(5th) Franke ` | 2 | `Franke5()` | -| {ref}`(6th) Franke ` | 2 | `Franke6()` | -| {ref}`Gayton Hat ` | 2 | `GaytonHat()` | -| {ref}`Gramacy (2007) 1D Sine ` | 1 | `Gramacy1DSine()` | -| {ref}`Ishigami ` | 3 | `Ishigami()` | -| {ref}`Oakley and O'Hagan (2002) 1D ` | 1 | `Oakley1D()` | -| {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | -| {ref}`McLain S1 ` | 2 | `McLainS1()` | -| {ref}`McLain S2 ` | 2 | `McLainS2()` | -| {ref}`McLain S3 ` | 2 | `McLainS3()` | -| {ref}`McLain S4 ` | 2 | `McLainS4()` | -| {ref}`McLain S5 ` | 2 | `McLainS5()` | -| {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | -| {ref}`Sobol'-G ` | M | `SobolG()` | -| {ref}`Sulfur ` | 9 | `Sulfur()` | -| {ref}`Welch1992 ` | 20 | `Welch1992()` | -| {ref}`Wing Weight ` | 10 | `WingWeight()` | +| Name | Spatial Dimension | Constructor | +|:-----------------------------------------------------------------------------------:|:-----------------:|:-------------------------------:| +| {ref}`Ackley ` | M | `Ackley()` | +| {ref}`Borehole ` | 8 | `Borehole()` | +| {ref}`Bratley et al. (1992) A ` | M | `Bratley1992a()` | +| {ref}`Bratley et al. (1992) B ` | M | `Bratley1992b()` | +| {ref}`Bratley et al. (1992) C ` | M | `Bratley1992c()` | +| {ref}`Bratley et al. (1992) D ` | M | `Bratley1992d()` | +| {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | +| {ref}`Damped Oscillator Reliability ` | 8 | `DampedOscillatorReliability()` | +| {ref}`Flood ` | 8 | `Flood()` | +| {ref}`Forrester et al. (2008) ` | 1 | `Forrester2008()` | +| {ref}`(1st) Franke ` | 2 | `Franke1()` | +| {ref}`(2nd) Franke ` | 2 | `Franke2()` | +| {ref}`(3rd) Franke ` | 2 | `Franke3()` | +| {ref}`(4th) Franke ` | 2 | `Franke4()` | +| {ref}`(5th) Franke ` | 2 | `Franke5()` | +| {ref}`(6th) Franke ` | 2 | `Franke6()` | +| {ref}`Gayton Hat ` | 2 | `GaytonHat()` | +| {ref}`Gramacy (2007) 1D Sine ` | 1 | `Gramacy1DSine()` | +| {ref}`Ishigami ` | 3 | `Ishigami()` | +| {ref}`Oakley and O'Hagan (2002) 1D ` | 1 | `Oakley1D()` | +| {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | +| {ref}`McLain S1 ` | 2 | `McLainS1()` | +| {ref}`McLain S2 ` | 2 | `McLainS2()` | +| {ref}`McLain S3 ` | 2 | `McLainS3()` | +| {ref}`McLain S4 ` | 2 | `McLainS4()` | +| {ref}`McLain S5 ` | 2 | `McLainS5()` | +| {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | +| {ref}`Sobol'-G ` | M | `SobolG()` | +| {ref}`Sulfur ` | 9 | `Sulfur()` | +| {ref}`Welch1992 ` | 20 | `Welch1992()` | +| {ref}`Wing Weight ` | 10 | `WingWeight()` | In a Python terminal, you can list all the available functions along with the corresponding constructor using ``list_functions()``: diff --git a/docs/test-functions/damped-oscillator-reliability.md b/docs/test-functions/damped-oscillator-reliability.md new file mode 100644 index 0000000..5291b33 --- /dev/null +++ b/docs/test-functions/damped-oscillator-reliability.md @@ -0,0 +1,172 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:damped-oscillator-reliability)= +# Damped Oscillator Reliability + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +The damped oscillator reliability problem is an eight-dimensional reliability +analysis test function {cite}`DerKiureghian1990, DerKiureghian1991, Bourinet2011, Dubourg2011`. + +```{note} +The reliability analysis variant differs from +the {ref}`base model `. +The base model computes the mean-square relative displacement of +the secondary spring without reference to the performance of the system. +``` + +## Test function instance + +To create a default instance of the test function: + +```{code-cell} ipython3 +my_testfun = uqtf.DampedOscillatorReliability() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +## Description + +The problem poses the reliability of a damped oscillator system +as defined in {cite}`Igusa1985` (see {ref}`Damped Oscillator `). +The reliability of the system depends on the secondary spring as described +by the following performance function {cite}`DerKiureghian1990, Dubourg2011` [^location]: + +$$ +g(\boldsymbol{X}; p) = F_s - p k_s \mathcal{M}(M_p, M_s, K_p, K_s, \zeta_p, \zeta_s, S_0), +$$ + +$$ +\mathcal{M}(M_p, M_s, K_p, K_s, \zeta_p, \zeta_s, S_0) = \left( \pi \frac{S_0}{4 \zeta_s \omega_s^3} \frac{\zeta_a \zeta_s}{\zeta_p \zeta_s (4 \zeta_a^2 + \theta^2) + \gamma \zeta_a^2} \frac{(\zeta_p \omega_p^3 + \zeta_s \omega_s^3) \omega_p}{4 \zeta_a \omega_a^4} \right)^{0.5} +$$ + +$$ +\begin{aligned} + \omega_p & = \left( \frac{k_p}{m_p}\right)^{0.5} & \omega_s & = \left(\frac{k_s}{m_s}\right)^{0.5} & \omega_a & = \frac{\omega_p + \omega_s}{2}\\ + \gamma & = \frac{m_s}{m_p} & \zeta_a & = \frac{\zeta_p + \zeta_s}{2} & \theta & = \frac{\omega_p - \omega_s}{\omega_a} \\ +\end{aligned} +$$ + +where $\boldsymbol{X} = \{ M_p, M_s, K_p, K_s, \zeta_p, \zeta_s, S_0, F_s \}$ +is the eight-dimensional random vector of input variables further defined below +and $p$ is the parameter of the function (i.e., the peak factor). + +The failure event and the failure probability are defined as +$g(\boldsymbol{x}) \leq 0$ and $\mathbb{P}[g(\boldsymbol{x}) \leq 0]$, +respectively. + +## Probabilistic input + +Based on {cite}`DerKiureghian1991`, the probabilistic input model +for the damped oscillator reliability consists of eight independent random +variables with marginal distributions shown in the table below. + +```{code-cell} ipython3 +my_testfun.prob_input +``` + +In the literature, the force capacity of the secondary spring (i.e., $F_s$) +have different probabilistic specifications as summarized in the table below. + +| $F_S$ | Keyword | Source | +|:----------------------------------------:|:------------------------------:|:-------------------------:| +| $\mathcal{N}_{\mathrm{log}}(15.0, 1.50)$ | `DerKiureghian1990a` (default) | {cite}`DerKiureghian1990` | +| $\mathcal{N}_{\mathrm{log}}(21.5, 2.15)$ | `DerKiureghian1990b` | {cite}`DerKiureghian1990` | +| $\mathcal{N}_{\mathrm{log}}(27.5, 2.75)$ | `DerKiureghian1990c` | {cite}`DerKiureghian1990` | + +Note that the parameters of the log-normal distribution given above correspond +to the mean and standard deviation of the log-normal distribution +(and not the mean and standard deviation of the underlying normal distribution). + +With the higher mean of $F_s$, the failure probability becomes smaller. + +## Parameters + +The performance function contains a single parameter (peak factor). +From {cite}`DerKiureghian1991` this parameter is set to $3$. + +## Reference results + +This section provides several reference results of typical UQ analyses involving +the test function. + +### Sample histogram + +Shown below is the histogram of the output based on $10^6$ random points: + +```{code-cell} ipython3 +:tags: [hide-input] + +xx_test = my_testfun.prob_input.get_sample(1000000) +yy_test = my_testfun(xx_test) +idx_pos = yy_test > 0 +idx_neg = yy_test <= 0 + +hist_pos = plt.hist(yy_test, bins="auto", color="#0571b0") +plt.hist(yy_test[idx_neg], bins=hist_pos[1], color="#ca0020") +plt.axvline(0, linewidth=1.0, color="#ca0020") + +plt.grid() +plt.ylabel("Counts [-]") +plt.xlabel("$\mathcal{M}(\mathbf{X})$") +plt.gcf().set_dpi(150); +``` + +### Failure probability + +Some reference values for the failure probability $P_f$ and from the literature +are summarized in the table below ($\mu_{F_s}$ is the log-normal distribution +mean of $F_s$). + +| $\mu_{F_s}$ | Method | $N$ | $\hat{P}_f$ | $\mathrm{CoV}[\hat{P}_f]$ | Source | +|:-----------:|:-----------------------:|:---------------:|:---------------------:|:-------------------------:|:---------------------------------------:| +| $15$ | FORM | $1'179$ | $2.19 \times 10^{-2}$ | — | {cite}`DerKiureghian1990, Bourinet2011` | +| | Subset Sim. | $3 \times 10^5$ | $4.63 \times 10^{-3}$ | $< 3\%$ | {cite}`Dubourg2011` | +| | Meta-IS[^meta-is] | $464 + 600$ | $4.80 \times 10^{-3}$ | $< 5\%$ | {cite}`Dubourg2011` | +| | SVM[^svm] + Subset Sim. | $1'719$ | $4.78 \times 10^{-3}$ | $< 4\%$ | {cite}`Bourinet2011` | +| $21.5$ | FORM | $2'520$ | $3.50 \times 10^{-4}$ | — | {cite}`DerKiureghian1990, Bourinet2011` | +| | Subset Sim. | $5 \times 10^5$ | $4.75 \times 10^{-5}$ | $< 4\%$ | {cite}`Dubourg2011` | +| | Meta-IS | $336 + 400$ | $4.46 \times 10^{-5}$ | $< 5\%$ | {cite}`Dubourg2011` | +| | SVM + Subset Sim. | $2'865$ | $4.42 \times 10^{-5}$ | $< 7\%$ | {cite}`Bourinet2011` | +| $27.5$ | FORM | $2'727$ | $3.91 \times 10^{-6}$ | — | {cite}`DerKiureghian1990, Bourinet2011` | +| | Subset Sim. | $7 \times 10^5$ | $3.47 \times 10^{-7}$ | $< 5\%$ | {cite}`Dubourg2011` | +| | Meta-IS | $480 + 200$ | $3.76 \times 10^{-7}$ | $< 5\%$ | {cite}`Dubourg2011` | +| | SVM + Subset Sim. | $4'011$ | $3.66 \times 10^{-7}$ | $< 10\%$ | {cite}`Bourinet2011` | + +Note that in the table above the total number of model evaluations for +metamodel-based Importance Sampling (Meta-IS) is the sum of training runs +and the correction runs {cite}`Dubourg2011`. + +## References + +```{bibliography} +:style: unsrtalpha +:filter: docname in docnames +``` + +[^location]: see, for instance, +Eqs. (5.5) and (5.7), pp. 184-185 in {cite}`Dubourg2011`. + +[^meta-is]: Metamodel-based Importance Sampling + +[^svm]: Support Vector Machines diff --git a/docs/test-functions/damped-oscillator.md b/docs/test-functions/damped-oscillator.md index bc62a0f..ae884d6 100644 --- a/docs/test-functions/damped-oscillator.md +++ b/docs/test-functions/damped-oscillator.md @@ -26,10 +26,10 @@ The model was first proposed in {cite}`Igusa1985` and used in the context of reliability analysis in {cite}`DerKiureghian1991, Dubourg2011`. ```{note} -The reliability analysis variant differs from this base model. -Used in the context of reliability analysis, the model also includes additional -parameters to a capacity factor and load such that the performance function can -be computed. +The {ref}`reliability analysis variant ` +differs from this base model. Used in the context of reliability analysis, +the model also includes additional parameters of a capacity factor and +load such that the performance function can be computed. This base model only computes the relative displacement of the spring. ``` @@ -54,10 +54,10 @@ primary-secondary mechanical system characterized by two masses, two springs, and the corresponding damping ratios. Originally, the model computes the mean-square relative displacement of the secondary spring under a white noise base acceleration using -the following analytical formula: +the following analytical formula[^location]: $$ -\mathcal{M}(\boldsymbol{x}) = k_s \left( \pi \frac{S_0}{4 \zeta_s \omega_s^3} \frac{\zeta_a \zeta_s}{\zeta_p \zeta_s (4 \zeta_a^2 + \theta^2) + \gamma \zeta_a^2} \frac{(\zeta_p \omega_p^3 + \zeta_s \omega_s^3) \omega_p}{4 \zeta_a \omega_a^4} \right)^{0.5} +\mathcal{M}(\boldsymbol{x}) = \left( \pi \frac{S_0}{4 \zeta_s \omega_s^3} \frac{\zeta_a \zeta_s}{\zeta_p \zeta_s (4 \zeta_a^2 + \theta^2) + \gamma \zeta_a^2} \frac{(\zeta_p \omega_p^3 + \zeta_s \omega_s^3) \omega_p}{4 \zeta_a \omega_a^4} \right)^{0.5} $$ $$ @@ -223,4 +223,6 @@ tabulate( ```{bibliography} :style: unsrtalpha :filter: docname in docnames -``` \ No newline at end of file +``` + +[^location]: see, for instance, Eqs. (5.5), pp. 184 in {cite}`Dubourg2011`. \ No newline at end of file diff --git a/docs/test-functions/gayton-hat.md b/docs/test-functions/gayton-hat.md index d9e24ff..829cc67 100644 --- a/docs/test-functions/gayton-hat.md +++ b/docs/test-functions/gayton-hat.md @@ -21,12 +21,13 @@ import matplotlib.pyplot as plt import uqtestfuns as uqtf ``` -The Gayton Hat function is a two-dimensional limit-state function used +The Gayton Hat function is a two-dimensional function used in {cite}`Echard2013` as a test function for reliability analysis algorithms. -The plots of the function are shown below. The left plot shows the contour +The plots of the function are shown below. The left plot shows the surface +plot of the performance function, the center plot shows the contour plot with a single contour line at function value of $0.0$ (the limit-state -surface) and the right plot shows the same plot with $10^6$ sample points +surface), and the right plot shows the same plot with $10^6$ sample points overlaid. ```{code-cell} ipython3 @@ -53,8 +54,23 @@ yy_2d = my_fun(xx_2d) # --- Create the plot fig = plt.figure(figsize=(10, 5)) +# Surface +axs_0 = plt.subplot(131, projection='3d') +axs_0.plot_surface( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000,1000).T, + linewidth=0, + cmap="plasma", + antialiased=False, + alpha=0.5 +) +axs_0.set_xlabel("$U_1$", fontsize=14) +axs_0.set_ylabel("$U_2$", fontsize=14) +axs_0.set_zlabel("$g$", fontsize=14) + # Contour plot -axs_1 = plt.subplot(121) +axs_1 = plt.subplot(132) cf = axs_1.contour( mesh_2d[0], mesh_2d[1], @@ -71,7 +87,7 @@ axs_1.set_aspect("equal", "box") axs_1.clabel(cf, inline=True, fontsize=14) # Scatter plot -axs_2 = plt.subplot(122) +axs_2 = plt.subplot(133) cf = axs_2.contour( mesh_2d[0], mesh_2d[1], @@ -104,7 +120,7 @@ axs_2.set_aspect("equal", "box") axs_2.clabel(cf, inline=True, fontsize=14) axs_2.legend(fontsize=14, loc="lower right"); -plt.gcf().tight_layout(pad=3.0) +plt.gcf().tight_layout(pad=4.0) plt.gcf().set_dpi(150); ``` @@ -124,15 +140,20 @@ print(my_testfun) ## Description -The test function is analytically defined as follows: +The test function (i.e., the performance function) is analytically defined +as follows: $$ -\mathcal{M}(\boldsymbol{x}) = 0.5 (U_1 - 2)^2 - 1.5 (U_2 - 5)^3 - 3, +g(\boldsymbol{X}) = 0.5 (U_1 - 2)^2 - 1.5 (U_2 - 5)^3 - 3, $$ -where $\boldsymbol{x} = \{ U_1, U_2 \}$ is the two-dimensional vector of +where $\boldsymbol{X} = \{ U_1, U_2 \}$ is the two-dimensional random vector of input variables further defined below. +The failure event and the failure probability are defined as +$g(\boldsymbol{x}) \leq 0$ and $\mathbb{P}[g(\boldsymbol{x}) \leq 0]$, +respectively. + ## Probabilistic input Based on {cite}`Echard2013`, the probabilistic input model for @@ -160,13 +181,13 @@ yy_test = my_testfun(xx_test) idx_pos = yy_test > 0 idx_neg = yy_test <= 0 -plt.hist(yy_test[idx_pos], bins="auto", color="#0571b0"); -plt.hist(yy_test[idx_neg], bins="auto", color="#ca0020"); -plt.axvline(0, linewidth=1.5, color="#ca0020"); +hist_pos = plt.hist(yy_test, bins="auto", color="#0571b0") +plt.hist(yy_test[idx_neg], bins=hist_pos[1], color="#ca0020") +plt.axvline(0, linewidth=1.0, color="#ca0020") -plt.grid(); -plt.ylabel("Counts [-]"); -plt.xlabel("$\mathcal{M}(\mathbf{X})$"); +plt.grid() +plt.ylabel("Counts [-]") +plt.xlabel("$\mathcal{M}(\mathbf{X})$") plt.gcf().set_dpi(150); ``` diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index cb14246..30de704 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -4,7 +4,7 @@ from .ackley import Ackley from .borehole import Borehole from .bratley1992 import Bratley1992a, Bratley1992b, Bratley1992c, Bratley1992d -from .damped_oscillator import DampedOscillator +from .damped_oscillator import DampedOscillator, DampedOscillatorReliability from .flood import Flood from .forrester import Forrester2008 from .franke import Franke1, Franke2, Franke3, Franke4, Franke5, Franke6 @@ -31,6 +31,7 @@ "Bratley1992c", "Bratley1992d", "DampedOscillator", + "DampedOscillatorReliability", "Flood", "Forrester2008", "Franke1", diff --git a/src/uqtestfuns/test_functions/damped_oscillator.py b/src/uqtestfuns/test_functions/damped_oscillator.py index 1ca9999..d15126f 100644 --- a/src/uqtestfuns/test_functions/damped_oscillator.py +++ b/src/uqtestfuns/test_functions/damped_oscillator.py @@ -5,8 +5,9 @@ that computes the relative displacement of a secondary spring under a white noise base acceleration. The model was first proposed in [1] and used in the context of reliability -analysis in [2] and [3]. Note, however, that the reliability analysis -variant differs from this base model. +analysis in, for examples, [2], [3], [4], and [5]. +Note, however, that the reliability analysis variant differs +from the base model. Used in the context of reliability analysis, the model also includes additional parameters related to a capacity factor and load such that the performance function can be computed. @@ -21,11 +22,18 @@ two‐degree‐of‐freedom equipment‐structure systems,” Journal of Engineering Mechanics, vol. 111, no. 1, pp. 1–19, 1985. DOI: 10.1061/(ASCE)0733-9399(1985)111:1(1) -2. Armen Der Kiureghian and Mario De Stefano, “Efficient algorithm for +2. Armen Der Kiureghian and Mario De Stefano, "An efficient algorithm for + second-order reliability analysis," Department of Civil and Environmental + Engineering, University of California, Berkeley, UCB/SEMM-90/20, 1990. +3. Armen Der Kiureghian and Mario De Stefano, “Efficient algorithm for second‐order reliability analysis,” Journal of Engineering Mechanics, vol. 117, no. 12, pp. 2904–2923, 1991. DOI: 10.1061/(ASCE)0733-9399(1991)117:12(2904) -3. Vincent Dubourg, “Adaptive surrogate models for reliability analysis +4. J.-M. Bourinet, F. Deheeger, and M. Lemaire, “Assessing small failure + probabilities by combined subset simulation and Support Vector Machines,” + Structural Safety, vol. 33, no. 6, pp. 343–353, 2011. + DOI: 10.1016/j.strusafe.2011.06.001. +5. Vincent Dubourg, “Adaptive surrogate models for reliability analysis and reliability-based design optimization,” Université Blaise Pascal - Clermont II, Clermont-Ferrand, France, 2011. URL: https://sites.google.com/site/vincentdubourg/phd-thesis @@ -36,7 +44,7 @@ from ..core.uqtestfun_abc import UQTestFunABC from .utils import lognorm2norm_mean, lognorm2norm_std -__all__ = ["DampedOscillator"] +__all__ = ["DampedOscillator", "DampedOscillatorReliability"] INPUT_MARGINALS_DERKIUREGHIAN1991 = [ # From [2] UnivDistSpec( @@ -104,7 +112,7 @@ ), ] -AVAILABLE_INPUT_SPECS = { +AVAILABLE_INPUT_SPECS_BASE = { "DerKiureghian1991": ProbInputSpecFixDim( name="DampedOscillator-DerKiureghian1991", description=( @@ -117,6 +125,75 @@ } +AVAILABLE_INPUT_SPECS_RELIABILITY = { + "DerKiureghian1990a": ProbInputSpecFixDim( + name="DampedOscillatorReliability-DerKiureghian1990a", + description=( + "Probabilistic input #1 for the damped oscillator reliability " + "from Der Kiureghian and De Stefano (1990)" + ), + marginals=INPUT_MARGINALS_DERKIUREGHIAN1991 + + [ + UnivDistSpec( + name="Fs", + distribution="lognormal", + parameters=[ + lognorm2norm_mean(15.0, 0.1 * 15.0), + lognorm2norm_std(15.0, 0.1 * 15.0), + ], + description="Force capacity of the secondary spring", + ), + ], + copulas=None, + ), + "DerKiureghian1990b": ProbInputSpecFixDim( + name="DampedOscillatorReliability-DerKiureghian1990b", + description=( + "Probabilistic input #2 for the damped oscillator reliability " + "from Der Kiureghian and De Stefano (1990)" + ), + marginals=INPUT_MARGINALS_DERKIUREGHIAN1991 + + [ + UnivDistSpec( + name="Fs", + distribution="lognormal", + parameters=[ + lognorm2norm_mean(21.5, 0.1 * 21.5), + lognorm2norm_std(21.5, 0.1 * 21.5), + ], + description="Force capacity of the secondary spring", + ), + ], + copulas=None, + ), + "DerKiureghian1990c": ProbInputSpecFixDim( + name="DampedOscillatorReliability-DerKiureghian1990c", + description=( + "Probabilistic input #3 for the damped oscillator reliability " + "from Der Kiureghian and De Stefano (1990)" + ), + marginals=INPUT_MARGINALS_DERKIUREGHIAN1991 + + [ + UnivDistSpec( + name="Fs", + distribution="lognormal", + parameters=[ + lognorm2norm_mean(27.5, 0.1 * 27.5), + lognorm2norm_std(27.5, 0.1 * 27.5), + ], + description="Force capacity of the secondary spring", + ), + ], + copulas=None, + ), +} + +# peak factor +AVAILABLE_PARAMETERS_RELIABILITY = { + "DerKiureghian1990": 3, +} + + def evaluate(xx: np.ndarray) -> np.ndarray: """Evaluate the rms displacement of the damped oscillator model. @@ -179,8 +256,53 @@ class DampedOscillator(UQTestFunABC): _description = ( "Damped oscillator model from Igusa and Der Kiureghian (1985)" ) - _available_inputs = AVAILABLE_INPUT_SPECS + _available_inputs = AVAILABLE_INPUT_SPECS_BASE _available_parameters = None _default_spatial_dimension = 8 eval_ = staticmethod(evaluate) + + +def evaluate_reliability(xx: np.ndarray, parameters: float): + """Evaluate the performance function of the system reliability. + + Parameters + ---------- + xx : np.ndarray + An 8-dimensional input values given by an N-by-7 array + where N is the number of input values. + parameters : float + The peak factor of the system. + + Returns + ------- + np.ndarray + The system's performance evaluation. If less than or equal to zero, + the system is in failed state. + The output is a 1-dimensional array of length N. + """ + rms_disp = evaluate(xx[:, :-1]) # root-mean-square displacement + kk_s = xx[:, 3] # Secondary spring stiffness + ff_s = xx[:, -1] # Force capacity of the secondary spring + + pf = parameters # peak factor + + yy = ff_s - pf * kk_s * rms_disp + + return yy + + +class DampedOscillatorReliability(UQTestFunABC): + """A concrete implementation of the Damped oscillator reliability func.""" + + _tags = ["reliability"] + _description = ( + "Performance function from Der Kiureghian and De Stefano (1990)" + ) + _available_inputs = AVAILABLE_INPUT_SPECS_RELIABILITY + _available_parameters = AVAILABLE_PARAMETERS_RELIABILITY + _default_spatial_dimension = 8 + _default_input = "DerKiureghian1990a" + _default_parameters = "DerKiureghian1990" + + eval_ = staticmethod(evaluate_reliability) From 0685dd476ec8f42e59e18f8cae462cdf37d77d65 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Tue, 27 Jun 2023 18:54:58 +0200 Subject: [PATCH 37/73] Add an implementation of the hyper-sphere bound reliability problem. - The reliability problem is a two-dimensional problem and taken from Li et al. (2018). --- CHANGELOG.md | 2 + docs/_toc.yml | 2 + docs/fundamentals/reliability.md | 1 + docs/references.bib | 12 + docs/test-functions/available.md | 1 + .../damped-oscillator-reliability.md | 8 +- docs/test-functions/gayton-hat.md | 8 +- docs/test-functions/hyper-sphere.md | 217 ++++++++++++++++++ src/uqtestfuns/test_functions/__init__.py | 2 + src/uqtestfuns/test_functions/hyper_sphere.py | 80 +++++++ 10 files changed, 324 insertions(+), 9 deletions(-) create mode 100644 docs/test-functions/hyper-sphere.md create mode 100644 src/uqtestfuns/test_functions/hyper_sphere.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b2cb96..e7f9d48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The eight-dimensional damped oscillator reliability problem from Der Kiureghian and De Stefano (1990); the problem is based on the existing Damped Oscillator model in the code base. +- The two-dimensional hyper-sphere bound reliability problem + from Li et al. (2018). - New docs section on list of functions for reliability analysis. ## [0.2.0] - 2023-06-26 diff --git a/docs/_toc.yml b/docs/_toc.yml index 4bbb7d7..050bd6c 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -59,6 +59,8 @@ parts: title: (6th) Franke - file: test-functions/gayton-hat title: Gayton Hat + - file: test-functions/hyper-sphere + title: Hyper-sphere Bound - file: test-functions/gramacy-1d-sine title: Gramacy (2007) Sine - file: test-functions/ishigami diff --git a/docs/fundamentals/reliability.md b/docs/fundamentals/reliability.md index 1192117..2762e47 100644 --- a/docs/fundamentals/reliability.md +++ b/docs/fundamentals/reliability.md @@ -21,6 +21,7 @@ in the comparison of reliability analysis methods. |:-----------------------------------------------------------------------------------:|:-----------------:|:-------------------------------:| | {ref}`Damped Oscillator Reliability ` | 8 | `DampedOscillatorReliability()` | | {ref}`Gayton Hat ` | 2 | `GaytonHat()` | +| {ref}`Hyper-sphere Bound ` | 2 | `HyperSphere()` | In a Python terminal, you can list all the available functions relevant for metamodeling applications using ``list_functions()`` and filter the results diff --git a/docs/references.bib b/docs/references.bib index 62df905..84e9975 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -540,4 +540,16 @@ @Article{Bourinet2011 doi = {10.1016/j.strusafe.2011.06.001}, } +@Article{Li2018, + author = {Xu Li and Chunlin Gong and Liangxian Gu and Wenkun Gao and Zhao Jing and Hua Su}, + journal = {Structural Safety}, + title = {A sequential surrogate method for reliability analysis based on radial basis function}, + year = {2018}, + month = {jul}, + pages = {42--53}, + volume = {73}, + doi = {10.1016/j.strusafe.2018.02.005}, + publisher = {Elsevier {BV}}, +} + @Comment{jabref-meta: databaseType:bibtex;} diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index ac5867f..ebb1536 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -38,6 +38,7 @@ available in the current UQTestFuns, regardless of their typical applications. | {ref}`(6th) Franke ` | 2 | `Franke6()` | | {ref}`Gayton Hat ` | 2 | `GaytonHat()` | | {ref}`Gramacy (2007) 1D Sine ` | 1 | `Gramacy1DSine()` | +| {ref}`Hyper-sphere Bound ` | 2 | `HyperSphere()` | | {ref}`Ishigami ` | 3 | `Ishigami()` | | {ref}`Oakley and O'Hagan (2002) 1D ` | 1 | `Oakley1D()` | | {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | diff --git a/docs/test-functions/damped-oscillator-reliability.md b/docs/test-functions/damped-oscillator-reliability.md index 5291b33..9a5c20c 100644 --- a/docs/test-functions/damped-oscillator-reliability.md +++ b/docs/test-functions/damped-oscillator-reliability.md @@ -53,7 +53,7 @@ The reliability of the system depends on the secondary spring as described by the following performance function {cite}`DerKiureghian1990, Dubourg2011` [^location]: $$ -g(\boldsymbol{X}; p) = F_s - p k_s \mathcal{M}(M_p, M_s, K_p, K_s, \zeta_p, \zeta_s, S_0), +g(\boldsymbol{x}; p) = F_s - p k_s \mathcal{M}(M_p, M_s, K_p, K_s, \zeta_p, \zeta_s, S_0), $$ $$ @@ -67,9 +67,9 @@ $$ \end{aligned} $$ -where $\boldsymbol{X} = \{ M_p, M_s, K_p, K_s, \zeta_p, \zeta_s, S_0, F_s \}$ -is the eight-dimensional random vector of input variables further defined below -and $p$ is the parameter of the function (i.e., the peak factor). +where $\boldsymbol{x} = \{ M_p, M_s, K_p, K_s, \zeta_p, \zeta_s, S_0, F_s \}$ +is the eight-dimensional vector of input variables probabilistically defined +further below and $p$ is the parameter of the function (i.e., the peak factor). The failure event and the failure probability are defined as $g(\boldsymbol{x}) \leq 0$ and $\mathbb{P}[g(\boldsymbol{x}) \leq 0]$, diff --git a/docs/test-functions/gayton-hat.md b/docs/test-functions/gayton-hat.md index 829cc67..302c746 100644 --- a/docs/test-functions/gayton-hat.md +++ b/docs/test-functions/gayton-hat.md @@ -82,7 +82,6 @@ axs_1.set_xlim([lb_1, ub_1]) axs_1.set_ylim([lb_2, ub_2]) axs_1.set_xlabel("$U_1$", fontsize=14) axs_1.set_ylabel("$U_2$", fontsize=14) -#axs_1.set_title("Contour plot", fontsize=14) axs_1.set_aspect("equal", "box") axs_1.clabel(cf, inline=True, fontsize=14) @@ -115,7 +114,6 @@ axs_2.set_xlim([lb_1, ub_1]) axs_2.set_ylim([lb_2, ub_2]) axs_2.set_xlabel("$U_1$", fontsize=14) axs_2.set_ylabel("$U_2$", fontsize=14) -#axs_2.set_title("Scatter plot", fontsize=14) axs_2.set_aspect("equal", "box") axs_2.clabel(cf, inline=True, fontsize=14) axs_2.legend(fontsize=14, loc="lower right"); @@ -144,11 +142,11 @@ The test function (i.e., the performance function) is analytically defined as follows: $$ -g(\boldsymbol{X}) = 0.5 (U_1 - 2)^2 - 1.5 (U_2 - 5)^3 - 3, +g(\boldsymbol{x}) = 0.5 (u_1 - 2)^2 - 1.5 (u_2 - 5)^3 - 3, $$ -where $\boldsymbol{X} = \{ U_1, U_2 \}$ is the two-dimensional random vector of -input variables further defined below. +where $\boldsymbol{x} = \{ u_1, u_2 \}$ is the two-dimensional vector of +input variables probabilistically defined further below. The failure event and the failure probability are defined as $g(\boldsymbol{x}) \leq 0$ and $\mathbb{P}[g(\boldsymbol{x}) \leq 0]$, diff --git a/docs/test-functions/hyper-sphere.md b/docs/test-functions/hyper-sphere.md new file mode 100644 index 0000000..194add4 --- /dev/null +++ b/docs/test-functions/hyper-sphere.md @@ -0,0 +1,217 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:hyper-sphere)= +# Hyper-sphere Bound + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +The hyper-sphere bound problem is a two-dimensional function used +in {cite}`Li2018` as a test function for reliability analysis algorithms. + +The plots of the function are shown below. The left plot shows the surface +plot of the performance function, the center plot shows the contour +plot with a single contour line at function value of $0.0$ (the limit-state +surface), and the right plot shows the same plot with $10^6$ sample points +overlaid. + +```{code-cell} ipython3 +:tags: [remove-input] + +my_fun = uqtf.HyperSphere(rng_seed_prob_input=237324) +xx = my_fun.prob_input.get_sample(1000000) +yy = my_fun(xx) +idx_neg = yy <= 0.0 +idx_pos = yy > 0.0 + +lb_1 = my_fun.prob_input.marginals[0].lower +ub_1 = my_fun.prob_input.marginals[0].upper +lb_2 = my_fun.prob_input.marginals[1].lower +ub_2 = my_fun.prob_input.marginals[1].upper + +# Create 2-dimensional grid +xx_1 = np.linspace(lb_1, ub_1, 1000)[:, np.newaxis] +xx_2 = np.linspace(lb_2, ub_2, 1000)[:, np.newaxis] +mesh_2d = np.meshgrid(xx_1, xx_2) +xx_2d = np.array(mesh_2d).T.reshape(-1, 2) +yy_2d = my_fun(xx_2d) + +# --- Create the plot +fig = plt.figure(figsize=(10, 5)) + +# Surface +axs_0 = plt.subplot(131, projection='3d') +axs_0.plot_surface( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000,1000).T, + linewidth=0, + cmap="plasma", + antialiased=False, + alpha=0.5 +) +axs_0.set_xlabel("$X_1$", fontsize=14) +axs_0.set_ylabel("$X_2$", fontsize=14) +axs_0.set_zlabel("$g$", fontsize=14) + +# Contour plot +axs_1 = plt.subplot(132) +cf = axs_1.contour( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000, 1000).T, + levels=0, + colors=["#ca0020"], +) +axs_1.set_xlim([lb_1, ub_1]) +axs_1.set_ylim([lb_2, ub_2]) +axs_1.set_xlabel("$X_1$", fontsize=14) +axs_1.set_ylabel("$X_2$", fontsize=14) +axs_1.set_aspect("equal", "box") +axs_1.clabel(cf, inline=True, fontsize=14) + +# Scatter plot +axs_2 = plt.subplot(133) +cf = axs_2.contour( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000, 1000).T, + levels=0, + colors=["#ca0020"], +) +axs_2.scatter( + xx[idx_neg, 0], + xx[idx_neg, 1], + color="#ca0020", + marker=".", + s=30, + label="$\mathcal{M}(x) \leq 0$" +) +axs_2.scatter( + xx[idx_pos, 0], + xx[idx_pos, 1], + color="#0571b0", + marker=".", + s=30, + label="$\mathcal{M}(x) > 0$" +) +axs_2.set_xlim([lb_1, ub_1]) +axs_2.set_ylim([lb_2, ub_2]) +axs_2.set_xlabel("$X_1$", fontsize=14) +axs_2.set_ylabel("$X_2$", fontsize=14) +axs_2.set_aspect("equal", "box") +axs_2.clabel(cf, inline=True, fontsize=14) +axs_2.legend(fontsize=14, loc="lower right"); + +plt.gcf().tight_layout(pad=4.0) +plt.gcf().set_dpi(150); +``` + +## Test function instance + +To create a default instance of the test function: + +```{code-cell} ipython3 +my_testfun = uqtf.HyperSphere() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +## Description + +The test function (i.e., the performance function) is analytically defined +as follows [^location]: + +$$ +g(\boldsymbol{x}) = 1 - x_1^3 - x_2^3 +$$ + +where $\boldsymbol{x} = \{ x_1, x_2 \}$ is the two-dimensional vector of +input variables probabilistically defined further below. + +The failure event and the failure probability are defined as +$g(\boldsymbol{x}) \leq 0$ and $\mathbb{P}[g(\boldsymbol{x}) \leq 0]$, +respectively. + +## Probabilistic input + +Based on {cite}`Li2018`, the probabilistic input model for +the test function consists of two independent standard normal random variables +(see the table below). + +```{code-cell} ipython3 +my_testfun.prob_input +``` + +## Reference results + +This section provides several reference results of typical UQ analyses involving +the test function. + +### Sample histogram + +Shown below is the histogram of the output based on $10^6$ random points: + +```{code-cell} ipython3 +:tags: [hide-input] + +xx_test = my_testfun.prob_input.get_sample(1000000) +yy_test = my_testfun(xx_test) +idx_pos = yy_test > 0 +idx_neg = yy_test <= 0 + +hist_pos = plt.hist(yy_test, bins="auto", color="#0571b0") +plt.hist(yy_test[idx_neg], bins=hist_pos[1], color="#ca0020") +plt.axvline(0, linewidth=1.0, color="#ca0020") + +plt.grid() +plt.ylabel("Counts [-]") +plt.xlabel("$\mathcal{M}(\mathbf{X})$") +plt.gcf().set_dpi(150); +``` + +### Failure probability ($P_f$) + +Some reference values for the failure probability $P_f$ from the literature +are summarized in the table below. + +| Method | $N$ | $\hat{P}_f$ | $\mathrm{CoV}[\hat{P}_f]$ | Source | +|:-----------:|:------:|:----------------------:|:-------------------------:|:--------------:| +| MCS | $10^6$ | $3.381 \times 10^{-2}$ | —[^error-li] | {cite}`Li2018` | +| FORM | $15$ | $1.891 \times 10^{-2}$ | — | {cite}`Li2018` | +| SORM | $20$ | $2.672 \times 10^{-2}$ | — | {cite}`Li2018` | +| SSRM[^ssrm] | $12$ | $3.381 \times 10^{-2}$ | —[^error-li] | {cite}`Li2018` | + + +## References + +```{bibliography} +:style: unsrtalpha +:filter: docname in docnames +``` + +[^location]: see Eq. (10) in {cite}`Li2018`. + +[^ssrm]: Sequential surrogate reliability method + +[^error-li]: The coefficient of variations of the failure probability estimates +in {cite}`Li2018` were not reported. diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index 30de704..8376120 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -10,6 +10,7 @@ from .franke import Franke1, Franke2, Franke3, Franke4, Franke5, Franke6 from .gayton_hat import GaytonHat from .gramacy2007 import Gramacy1DSine +from .hyper_sphere import HyperSphere from .ishigami import Ishigami from .oakley2002 import Oakley1D from .otl_circuit import OTLCircuit @@ -42,6 +43,7 @@ "Franke6", "GaytonHat", "Gramacy1DSine", + "HyperSphere", "Ishigami", "Oakley1D", "OTLCircuit", diff --git a/src/uqtestfuns/test_functions/hyper_sphere.py b/src/uqtestfuns/test_functions/hyper_sphere.py new file mode 100644 index 0000000..b099193 --- /dev/null +++ b/src/uqtestfuns/test_functions/hyper_sphere.py @@ -0,0 +1,80 @@ +""" +Module with an implementation of the hyper-sphere bound reliability problem. + +The reliability problem is a two-dimensional function as described in +Li et al. (2018). + +References +---------- + +1. X. Li, C. Gong, L. Gu, W. Gao, Z. Jing, and H. Su, “A sequential surrogate + method for reliability analysis based on radial basis function,” + Structural Safety, vol. 73, pp. 42–53, 2018. + DOI: 10.1016/j.strusafe.2018.02.005. +""" +import numpy as np + +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecFixDim +from ..core.uqtestfun_abc import UQTestFunABC + +__all__ = ["HyperSphere"] + + +AVAILABLE_INPUT_SPECS = { + "Li2018": ProbInputSpecFixDim( + name="Li2018", + description=( + "Probabilistic input for the hyper-sphere reliability problem " + "from Li et al. (2018)" + ), + marginals=[ + UnivDistSpec( + name="X1", + distribution="normal", + parameters=[0.5, 0.2], + description="None", + ), + UnivDistSpec( + name="X2", + distribution="normal", + parameters=[0.5, 0.2], + description="None", + ), + ], + copulas=None, + ), +} + + +def evaluate(xx: np.ndarray) -> np.ndarray: + """Evaluate the hyper-sphere performance function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + 1-Dimensional input values given by an N-by-1 array + where N is the number of input values. + + Returns + ------- + np.ndarray + The output of the test function evaluated on the input values. + The output is a 1-dimensional array of length N. + """ + yy = 1 - xx[:, 0] ** 3 - xx[:, 1] ** 3 + + return yy + + +class HyperSphere(UQTestFunABC): + """A concrete implementation of the hyper-sphere reliability problem.""" + + _tags = ["reliability"] + _description = ( + "Hyper-sphere bound reliability problem from Li et al. (2018)" + ) + _available_inputs = AVAILABLE_INPUT_SPECS + _available_parameters = None + _default_spatial_dimension = 2 + + eval_ = staticmethod(evaluate) From 08ec78516e29d004ccb5c28a31b613498d653f30 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Thu, 29 Jun 2023 11:55:05 +0200 Subject: [PATCH 38/73] Add an implementation of the 2D cantilever beam function. - The test function is a reliability analysis problem from Rajashekhar and Ellington (1993). --- CHANGELOG.md | 2 + docs/_toc.yml | 6 +- docs/fundamentals/reliability.md | 1 + docs/references.bib | 38 ++- docs/test-functions/available.md | 1 + docs/test-functions/cantilever-beam-2d.md | 286 ++++++++++++++++++ docs/test-functions/gayton-hat.md | 16 +- docs/test-functions/hyper-sphere.md | 16 +- src/uqtestfuns/test_functions/__init__.py | 2 + .../test_functions/cantilever_beam_2d.py | 114 +++++++ 10 files changed, 455 insertions(+), 27 deletions(-) create mode 100644 docs/test-functions/cantilever-beam-2d.md create mode 100644 src/uqtestfuns/test_functions/cantilever_beam_2d.py diff --git a/CHANGELOG.md b/CHANGELOG.md index e7f9d48..6d0280c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Damped Oscillator model in the code base. - The two-dimensional hyper-sphere bound reliability problem from Li et al. (2018). +- The two-dimensional cantilever beam reliability problem from + Rajashekhar and Ellington (1993). - New docs section on list of functions for reliability analysis. ## [0.2.0] - 2023-06-26 diff --git a/docs/_toc.yml b/docs/_toc.yml index 050bd6c..f8b0294 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -37,6 +37,8 @@ parts: title: Bratley et al. (1992) C - file: test-functions/bratley1992d title: Bratley et al. (1992) D + - file: test-functions/cantilever-beam-2d + title: Cantilever Beam (2D) - file: test-functions/damped-oscillator title: Damped Oscillator - file: test-functions/damped-oscillator-reliability @@ -59,10 +61,10 @@ parts: title: (6th) Franke - file: test-functions/gayton-hat title: Gayton Hat + - file: test-functions/gramacy-1d-sine + title: Gramacy (2007) 1D Sine - file: test-functions/hyper-sphere title: Hyper-sphere Bound - - file: test-functions/gramacy-1d-sine - title: Gramacy (2007) Sine - file: test-functions/ishigami title: Ishigami - file: test-functions/oakley-1d diff --git a/docs/fundamentals/reliability.md b/docs/fundamentals/reliability.md index 2762e47..3387c4f 100644 --- a/docs/fundamentals/reliability.md +++ b/docs/fundamentals/reliability.md @@ -19,6 +19,7 @@ in the comparison of reliability analysis methods. | Name | Spatial Dimension | Constructor | |:-----------------------------------------------------------------------------------:|:-----------------:|:-------------------------------:| +| {ref}`2D Cantilever Beam ` | 2 | `CantileverBeam2D ` | | {ref}`Damped Oscillator Reliability ` | 8 | `DampedOscillatorReliability()` | | {ref}`Gayton Hat ` | 2 | `GaytonHat()` | | {ref}`Hyper-sphere Bound ` | 2 | `HyperSphere()` | diff --git a/docs/references.bib b/docs/references.bib index 84e9975..80567c8 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -541,15 +541,35 @@ @Article{Bourinet2011 } @Article{Li2018, - author = {Xu Li and Chunlin Gong and Liangxian Gu and Wenkun Gao and Zhao Jing and Hua Su}, - journal = {Structural Safety}, - title = {A sequential surrogate method for reliability analysis based on radial basis function}, - year = {2018}, - month = {jul}, - pages = {42--53}, - volume = {73}, - doi = {10.1016/j.strusafe.2018.02.005}, - publisher = {Elsevier {BV}}, + author = {Xu Li and Chunlin Gong and Liangxian Gu and Wenkun Gao and Zhao Jing and Hua Su}, + journal = {Structural Safety}, + title = {A sequential surrogate method for reliability analysis based on radial basis function}, + year = {2018}, + pages = {42--53}, + volume = {73}, + doi = {10.1016/j.strusafe.2018.02.005}, +} + +@Article{Rajashekhar1993, + author = {Malur R. Rajashekhar and Bruce R. Ellingwood}, + journal = {Structural Safety}, + title = {A new look at the response surface approach for reliability analysis}, + year = {1993}, + number = {3}, + pages = {205--220}, + volume = {12}, + doi = {10.1016/0167-4730(93)90003-j}, +} + +@Article{Schueremans2005, + author = {Luc Schueremans and Dionys Van Gemert}, + journal = {Structural Safety}, + title = {Benefit of splines and neural networks in simulation based structural reliability analysis}, + year = {2005}, + number = {3}, + pages = {246--261}, + volume = {27}, + doi = {10.1016/j.strusafe.2004.11.001}, } @Comment{jabref-meta: databaseType:bibtex;} diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index ebb1536..25b642c 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -26,6 +26,7 @@ available in the current UQTestFuns, regardless of their typical applications. | {ref}`Bratley et al. (1992) B ` | M | `Bratley1992b()` | | {ref}`Bratley et al. (1992) C ` | M | `Bratley1992c()` | | {ref}`Bratley et al. (1992) D ` | M | `Bratley1992d()` | +| {ref}`2D Cantilever Beam ` | 2 | `CantileverBeam2D ` | | {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | | {ref}`Damped Oscillator Reliability ` | 8 | `DampedOscillatorReliability()` | | {ref}`Flood ` | 8 | `Flood()` | diff --git a/docs/test-functions/cantilever-beam-2d.md b/docs/test-functions/cantilever-beam-2d.md new file mode 100644 index 0000000..e4dd57e --- /dev/null +++ b/docs/test-functions/cantilever-beam-2d.md @@ -0,0 +1,286 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:cantilever-beam-2d)= +# Two-dimensional (2D) Cantilever Beam Reliability Problem + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + + +The 2D cantilever beam problem is a reliability test function from +{cite}`Rajashekhar1993`. +This is an often revisited problem in reliability analysis +(see, for instance, {cite}`Li2018`). + +The plots of the function are shown below. The left plot shows the surface +plot of the performance function, the center plot shows the contour +plot with a single contour line at function value of $0.0$ (the limit-state +surface), and the right plot shows the same plot with $10^6$ sample points +overlaid. + +```{code-cell} ipython3 +:tags: [remove-input] + +my_fun = uqtf.CantileverBeam2D(rng_seed_prob_input=237324) +xx = my_fun.prob_input.get_sample(1000000) +yy = my_fun(xx) +idx_neg = yy <= 0.0 +idx_pos = yy > 0.0 + +# The plotting bounds are specifically chosen for the 2D cantilever beam +lb_1 = 0.1 +ub_1 = np.max(xx[:, 0]) +lb_2 = 75 +ub_2 = np.max(xx[:, 1]) + +# Create 2-dimensional grid +xx_1 = np.linspace(lb_1, ub_1, 1000)[:, np.newaxis] +xx_2 = np.linspace(lb_2, ub_2, 1000)[:, np.newaxis] +mesh_2d = np.meshgrid(xx_1, xx_2) +xx_2d = np.array(mesh_2d).T.reshape(-1, 2) +yy_2d = my_fun(xx_2d) + +# --- Create the plot +fig = plt.figure(figsize=(15, 5)) + +# Surface +axs_0 = plt.subplot(131, projection='3d') +axs_0.plot_surface( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000,1000).T, + linewidth=0, + cmap="plasma", + antialiased=False, + alpha=0.5 +) +axs_0.set_xlabel("$X_1$", fontsize=14) +axs_0.set_ylabel("$X_2$", fontsize=14) +axs_0.set_zlabel("$g$", fontsize=14) + +# Contour plot +axs_1 = plt.subplot(132) +cf = axs_1.contour( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000, 1000).T, + levels=0, + colors=["#ca0020"], +) +axs_1.set_xlim([lb_1, ub_1]) +axs_1.set_ylim([lb_2, ub_2]) +axs_1.set_xlabel("$X_1$", fontsize=14) +axs_1.set_ylabel("$X_2$", fontsize=14) +axs_1.clabel(cf, inline=True, fontsize=14) + +# Scatter plot +axs_2 = plt.subplot(133) +cf = axs_2.contour( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000, 1000).T, + levels=0, + colors=["#ca0020"], +) +axs_2.scatter( + xx[idx_neg, 0], + xx[idx_neg, 1], + color="#ca0020", + marker=".", + s=30, + label="$g(x) \leq 0$" +) +axs_2.scatter( + xx[idx_pos, 0], + xx[idx_pos, 1], + color="#0571b0", + marker=".", + s=30, + label="$g(x) > 0$" +) +axs_2.set_xlim([lb_1, ub_1]) +axs_2.set_ylim([lb_2, ub_2]) +axs_2.set_xlabel("$X_1$", fontsize=14) +axs_2.set_ylabel("$X_2$", fontsize=14) +axs_2.clabel(cf, inline=True, fontsize=14) +axs_2.legend(fontsize=14, loc="upper right") + +fig.tight_layout(pad=4.0); +plt.gcf().set_dpi(150); +``` + +## Test function instance + +To create a default instance of the test function: + +```{code-cell} ipython3 +my_testfun = uqtf.CantileverBeam2D() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +## Description + +The problem consists of a cantilever beam with a rectangular cross-section +subjected to uniformly distributed loading. The maximum deflection at the free +end is taken to be the performance criterion and the performance function +reads[^location]: + +$$ +g(\boldsymbol{x}) = \frac{l}{325} - \frac{w b l^4}{8 E I}, +$$ + +where $I$, the moment inertia of the cross-section, is given as: + +$$ +I = \frac{b h^3}{12}. +$$ + +By plugging in the above expression to the performance function, the following +expression for the performance function is obtained: + +$$ +g(\boldsymbol{x}) = \frac{l}{325} - \frac{12 l^4 w}{8 E h^3}, +$$ + +where $\boldsymbol{x} = \{ w, h \}$ is the two-dimensional vector of +input variables, namely the load per unit area and the depth of +the cross-section. +These inputs are probabilistically defined further below. + +The parameters of the test function, namely $E$ (the beam's modulus +of elasticity) and $l$ (the span of the beam) are set to +$2.6 \times 10^{4} \; \mathrm{[MPa]}$ and $6.0 \; \mathrm{[m]}$, respectively. + +The failure event and the failure probability are defined as +$g(\boldsymbol{x}) \leq 0$ and $\mathbb{P}[g(\boldsymbol{X}) \leq 0]$, +respectively. + +## Probabilistic input + +Based on {cite}`Rajashekhar1993`, the probabilistic input model for +the test function consists of two independent standard normal random variables +(see the table below). + +```{code-cell} ipython3 +my_testfun.prob_input +``` + +## Reference results + +This section provides several reference results of typical UQ analyses involving +the test function. + +### Sample histogram + +Shown below is the histogram of the output based on $10^6$ random points: + +```{code-cell} ipython3 +:tags: [hide-input] + +def is_outlier(points, thresh=3.5): + """ + Returns a boolean array with True if points are outliers and False + otherwise. + + This is taken from: + https://stackoverflow.com/questions/11882393/matplotlib-disregard-outliers-when-plotting + + Parameters: + ----------- + points : An numobservations by numdimensions array of observations + thresh : The modified z-score to use as a threshold. Observations with + a modified z-score (based on the median absolute deviation) greater + than this value will be classified as outliers. + + Returns: + -------- + mask : A numobservations-length boolean array. + + References: + ---------- + Boris Iglewicz and David Hoaglin (1993), "Volume 16: How to Detect and + Handle Outliers", The ASQC Basic References in Quality Control: + Statistical Techniques, Edward F. Mykytka, Ph.D., Editor. + """ + if len(points.shape) == 1: + points = points[:,None] + median = np.median(points, axis=0) + diff = np.sum((points - median)**2, axis=-1) + diff = np.sqrt(diff) + med_abs_deviation = np.median(diff) + + modified_z_score = 0.6745 * diff / med_abs_deviation + + return modified_z_score > thresh + +xx_test = my_testfun.prob_input.get_sample(1000000) +yy_test = my_testfun(xx_test) +yy_test = yy_test[~is_outlier(yy_test, thresh=10)] +idx_pos = yy_test > 0 +idx_neg = yy_test <= 0 + +hist_pos = plt.hist(yy_test, bins="auto", color="#0571b0") +plt.hist(yy_test[idx_neg], bins=hist_pos[1], color="#ca0020") +plt.axvline(0, linewidth=1.0, color="#ca0020") + +plt.grid() +plt.ylabel("Counts [-]") +plt.xlabel("$\mathcal{M}(\mathbf{X})$") +plt.gcf().set_dpi(150); +``` + +### Failure probability ($P_f$) + +Some reference values for the failure probability $P_f$ from the literature +are summarized in the table below. + +| Method | $N$ | $\hat{P}_f$ | $\mathrm{CoV}[\hat{P}_f]$ | Source | Remark | +|:------------:|:--------:|:------------------------:|:-------------------------:|:-----------------------:|-----------------------------------------| +| MCS | $10^6$ | $9.594 \times 10^{-3}$ | — | {cite}`Li2018` | Monte Carlo Simulation (MCS) | +| FORM | $27$ | $9.88 \times 10^{-3}$ | — | {cite}`Li2018` | — | +| FORM | — | $9.9031 \times 10^{-3}$ | — | {cite}`Rajashekhar1993` | First-order Second-moment method | +| SORM | $32$ | $9.88 \times 10^{-3}$ | — | {cite}`Li2018` | — | +| IS | $10^3$ | $9.6071 \times 10^{-3}$ | — | {cite}`Rajashekhar1993` | Importance Sampling (IS) | +| IS | $9'312$ | $1.00 \times 10^{-2}$ | — | {cite}`Schueremans2005` | — | +| IS + RS | $2'192$ | $9.00 \times 10^{-3}$ | — | {cite}`Schueremans2005` | IS + Response Surface (RS) | +| IS + SP | $358$ | $1.00 \times 10^{-2}$ | — | {cite}`Schueremans2005` | IS + Splines (SP) | +| IS + NN | $63$ | $1.20 \times 10^{-2}$ | — | {cite}`Schueremans2005` | IS + Neural Networks (NN) | +| DS | $551$ | $1.000 \times 10^{-2}$ | — | {cite}`Schueremans2005` | Directional sampling (DS) | +| DS + RS | $60$ | $6.00 \times 10^{-3}$ | — | {cite}`Schueremans2005` | DS + Response Surface (RS) | +| DS + SP | $57$ | $7.00 \times 10^{-3}$ | — | {cite}`Schueremans2005` | DS + Splines (SP | +| DS + NN | $40$ | $8.00 \times 10^{-3}$ | — | {cite}`Schueremans2005` | DS + Neural Networks (NN) | +| SSRM | $18$ | $9.499 \times 10^{-3}$ | — | {cite}`Li2018` | Sequential surrogate reliability method | +| Bucher's | — | $1.37538 \times 10^{-2}$ | — | {cite}`Rajashekhar1993` | — | +| Approach A-0 | — | $9.5410 \times 10^{-3}$ | — | {cite}`Rajashekhar1993` | — | +| Approach A-1 | — | $9.6398 \times 10^{-3}$ | — | {cite}`Rajashekhar1993` | — | +| Approach A-2 | — | $1.11508 \times 10^{-2}$ | — | {cite}`Rajashekhar1993` | — | +| Approach A-3 | — | $9.5410 \times 10^{-3}$ | — | {cite}`Rajashekhar1993` | — | + +## References + +```{bibliography} +:style: unsrtalpha +:filter: docname in docnames +``` + +[^location]: see Eq. (10), p. 213 in {cite}`Rajashekhar1993`. \ No newline at end of file diff --git a/docs/test-functions/gayton-hat.md b/docs/test-functions/gayton-hat.md index 302c746..2fc3de6 100644 --- a/docs/test-functions/gayton-hat.md +++ b/docs/test-functions/gayton-hat.md @@ -65,8 +65,8 @@ axs_0.plot_surface( antialiased=False, alpha=0.5 ) -axs_0.set_xlabel("$U_1$", fontsize=14) -axs_0.set_ylabel("$U_2$", fontsize=14) +axs_0.set_xlabel("$x_1$", fontsize=14) +axs_0.set_ylabel("$x_2$", fontsize=14) axs_0.set_zlabel("$g$", fontsize=14) # Contour plot @@ -80,8 +80,8 @@ cf = axs_1.contour( ) axs_1.set_xlim([lb_1, ub_1]) axs_1.set_ylim([lb_2, ub_2]) -axs_1.set_xlabel("$U_1$", fontsize=14) -axs_1.set_ylabel("$U_2$", fontsize=14) +axs_1.set_xlabel("$x_1$", fontsize=14) +axs_1.set_ylabel("$x_2$", fontsize=14) axs_1.set_aspect("equal", "box") axs_1.clabel(cf, inline=True, fontsize=14) @@ -100,7 +100,7 @@ axs_2.scatter( color="#ca0020", marker=".", s=30, - label="$\mathcal{M}(x) \leq 0$" + label="$g(x) \leq 0$" ) axs_2.scatter( xx[idx_pos, 0], @@ -108,12 +108,12 @@ axs_2.scatter( color="#0571b0", marker=".", s=30, - label="$\mathcal{M}(x) > 0$" + label="$g(x) > 0$" ) axs_2.set_xlim([lb_1, ub_1]) axs_2.set_ylim([lb_2, ub_2]) -axs_2.set_xlabel("$U_1$", fontsize=14) -axs_2.set_ylabel("$U_2$", fontsize=14) +axs_2.set_xlabel("$x_1$", fontsize=14) +axs_2.set_ylabel("$x_2$", fontsize=14) axs_2.set_aspect("equal", "box") axs_2.clabel(cf, inline=True, fontsize=14) axs_2.legend(fontsize=14, loc="lower right"); diff --git a/docs/test-functions/hyper-sphere.md b/docs/test-functions/hyper-sphere.md index 194add4..e99297c 100644 --- a/docs/test-functions/hyper-sphere.md +++ b/docs/test-functions/hyper-sphere.md @@ -65,8 +65,8 @@ axs_0.plot_surface( antialiased=False, alpha=0.5 ) -axs_0.set_xlabel("$X_1$", fontsize=14) -axs_0.set_ylabel("$X_2$", fontsize=14) +axs_0.set_xlabel("$x_1$", fontsize=14) +axs_0.set_ylabel("$x_2$", fontsize=14) axs_0.set_zlabel("$g$", fontsize=14) # Contour plot @@ -80,8 +80,8 @@ cf = axs_1.contour( ) axs_1.set_xlim([lb_1, ub_1]) axs_1.set_ylim([lb_2, ub_2]) -axs_1.set_xlabel("$X_1$", fontsize=14) -axs_1.set_ylabel("$X_2$", fontsize=14) +axs_1.set_xlabel("$x_1$", fontsize=14) +axs_1.set_ylabel("$x_2$", fontsize=14) axs_1.set_aspect("equal", "box") axs_1.clabel(cf, inline=True, fontsize=14) @@ -100,7 +100,7 @@ axs_2.scatter( color="#ca0020", marker=".", s=30, - label="$\mathcal{M}(x) \leq 0$" + label="$g(x) \leq 0$" ) axs_2.scatter( xx[idx_pos, 0], @@ -108,12 +108,12 @@ axs_2.scatter( color="#0571b0", marker=".", s=30, - label="$\mathcal{M}(x) > 0$" + label="$g(x) > 0$" ) axs_2.set_xlim([lb_1, ub_1]) axs_2.set_ylim([lb_2, ub_2]) -axs_2.set_xlabel("$X_1$", fontsize=14) -axs_2.set_ylabel("$X_2$", fontsize=14) +axs_2.set_xlabel("$x_1$", fontsize=14) +axs_2.set_ylabel("$x_2$", fontsize=14) axs_2.set_aspect("equal", "box") axs_2.clabel(cf, inline=True, fontsize=14) axs_2.legend(fontsize=14, loc="lower right"); diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index 8376120..91e9b53 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -4,6 +4,7 @@ from .ackley import Ackley from .borehole import Borehole from .bratley1992 import Bratley1992a, Bratley1992b, Bratley1992c, Bratley1992d +from .cantilever_beam_2d import CantileverBeam2D from .damped_oscillator import DampedOscillator, DampedOscillatorReliability from .flood import Flood from .forrester import Forrester2008 @@ -31,6 +32,7 @@ "Bratley1992b", "Bratley1992c", "Bratley1992d", + "CantileverBeam2D", "DampedOscillator", "DampedOscillatorReliability", "Flood", diff --git a/src/uqtestfuns/test_functions/cantilever_beam_2d.py b/src/uqtestfuns/test_functions/cantilever_beam_2d.py new file mode 100644 index 0000000..90b61ed --- /dev/null +++ b/src/uqtestfuns/test_functions/cantilever_beam_2d.py @@ -0,0 +1,114 @@ +""" +Module with an implementation of the 2D cantilever beam reliability problem. + +The 2D cantilever beam problem is a reliability test function from [1]. +This is an often revisited problem in reliability analysis ([2]). + +The problem consists of a cantilever beam with a rectangular cross-section +subjected to uniformly distributed loading. +The maximum deflection at the free end is taken to be the performance +criterion. + + +References +---------- +1. Malur R. Rajashekhar and Bruce R. Ellingwood, “A new look at the response + surface approach for reliability analysis,” Structural Safety, + vol. 12, no. 3, pp. 205–220, 1993. + DOI: 10.1016/0167-4730(93)90003-J +2. X. Li, C. Gong, L. Gu, W. Gao, Z. Jing, and H. Su, “A sequential surrogate + method for reliability analysis based on radial basis function,” + Structural Safety, vol. 73, pp. 42–53, 2018. + DOI: 10.1016/j.strusafe.2018.02.005. +""" +import numpy as np + +from typing import Tuple + +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecFixDim +from ..core.uqtestfun_abc import UQTestFunABC + +__all__ = ["CantileverBeam2D"] + +AVAILABLE_INPUT_SPECS = { + "Rajashekhar1993": ProbInputSpecFixDim( + name="Cantilever2D-Rajashekhar1993", + description=( + "Probabilistic input model for the 2D cantilever beam problem " + "from Rajashekhar and Ellingwood (1993)" + ), + marginals=[ + UnivDistSpec( + name="W", + distribution="normal", + parameters=[1000.0, 200.0], + description="Load per unit area [N/m^2]", + ), + UnivDistSpec( + name="H", + distribution="normal", + parameters=[250.0, 37.5], + description="Depth of the cross-section [mm]", + ), + ], + copulas=None, + ), +} + +AVAILABLE_PARAMETERS = { + "Rajashekhar1993": ( + 2.6e4, # Modulus of elasticity [MPa] + 6.0e3, # Span of the beam [mm] + ) +} + + +def evaluate(xx: np.ndarray, parameters: Tuple[float, float]) -> np.ndarray: + """Evaluate the 2D cantilever beam function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + A two-dimensional input values given by an N-by-2 array + where N is the number of input values. + + parameters : Tuple[float, float] + The parameters of the test function, namely the modulus of elasticity + and the span of the beam. + + Returns + ------- + np.ndarray + The performance function (serviceability) of the cantilever beam + system. If negative, the system is in failed state. + The output is a one-dimensional array of length N. + """ + # Get parameters + mod_el = parameters[0] + beam_span = parameters[1] + + mod_el *= 1e6 # from [MPa] to [Pa] + + # Get the input + ww = xx[:, 0] # Load per unit area [N/m^2] ([Pa]) + hh = xx[:, 1] # Depth of the cross-section [mm] + + # Compute the performance function + yy = beam_span / 325 - 12 / 8 * beam_span**4 / mod_el * ww / hh**3 + + return yy + + +class CantileverBeam2D(UQTestFunABC): + """Concrete implementation of the 2D cantilever beam reliability.""" + + _tags = ["reliability"] + _description = ( + "Two-dimensional cantilever beam reliability " + "from Rajashekhar and Ellington (1993)" + ) + _available_inputs = AVAILABLE_INPUT_SPECS + _available_parameters = AVAILABLE_PARAMETERS + _default_spatial_dimension = 2 + + eval_ = staticmethod(evaluate) From 77cc456376c15de1595c0463ce7884901198911d Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Thu, 29 Jun 2023 14:55:00 +0200 Subject: [PATCH 39/73] Add an implementation of the four-branch function. - The two-dimensional function is a reliability analysis benchmark problem from Katsuki and Frangopol (1994). --- CHANGELOG.md | 2 + docs/_toc.yml | 10 +- docs/fundamentals/metamodeling.md | 4 +- docs/fundamentals/reliability.md | 1 + docs/references.bib | 57 ++++- docs/test-functions/available.md | 5 +- docs/test-functions/four-branch.md | 232 ++++++++++++++++++ src/uqtestfuns/test_functions/__init__.py | 2 + .../test_functions/cantilever_beam_2d.py | 10 +- src/uqtestfuns/test_functions/four_branch.py | 123 ++++++++++ src/uqtestfuns/test_functions/gayton_hat.py | 2 +- 11 files changed, 429 insertions(+), 19 deletions(-) create mode 100644 docs/test-functions/four-branch.md create mode 100644 src/uqtestfuns/test_functions/four_branch.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d0280c..2bf2f78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 from Li et al. (2018). - The two-dimensional cantilever beam reliability problem from Rajashekhar and Ellington (1993). +- The two-dimensional four-branch function for reliability analysis + from Katsuki and Frangopol (1994). - New docs section on list of functions for reliability analysis. ## [0.2.0] - 2023-06-26 diff --git a/docs/_toc.yml b/docs/_toc.yml index f8b0294..230c045 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -47,6 +47,8 @@ parts: title: Flood - file: test-functions/forrester title: Forrester et al. (2008) + - file: test-functions/four-branch + title: Four-branch - file: test-functions/franke-1 title: (1st) Franke - file: test-functions/franke-2 @@ -67,10 +69,6 @@ parts: title: Hyper-sphere Bound - file: test-functions/ishigami title: Ishigami - - file: test-functions/oakley-1d - title: Oakley & O'Hagan (2002) 1D - - file: test-functions/otl-circuit - title: OTL Circuit - file: test-functions/mclain-s1 title: McLain S1 - file: test-functions/mclain-s2 @@ -81,6 +79,10 @@ parts: title: McLain S4 - file: test-functions/mclain-s5 title: McLain S5 + - file: test-functions/oakley-1d + title: Oakley & O'Hagan (2002) 1D + - file: test-functions/otl-circuit + title: OTL Circuit - file: test-functions/piston title: Piston Simulation - file: test-functions/sobol-g diff --git a/docs/fundamentals/metamodeling.md b/docs/fundamentals/metamodeling.md index 82cfb2f..5d93518 100644 --- a/docs/fundamentals/metamodeling.md +++ b/docs/fundamentals/metamodeling.md @@ -31,13 +31,13 @@ in the comparison of metamodeling approaches. | {ref}`(5th) Franke ` | 2 | `Franke5()` | | {ref}`(6th) Franke ` | 2 | `Franke6()` | | {ref}`Gramacy (2007) 1D Sine ` | 1 | `Gramacy1DSine()` | -| {ref}`Oakley and O'Hagan (2002) 1D ` | 1 | `Oakley1D()` | -| {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | | {ref}`McLain S1 ` | 2 | `McLainS1()` | | {ref}`McLain S2 ` | 2 | `McLainS2()` | | {ref}`McLain S3 ` | 2 | `McLainS3()` | | {ref}`McLain S4 ` | 2 | `McLainS4()` | | {ref}`McLain S5 ` | 2 | `McLainS5()` | +| {ref}`Oakley and O'Hagan (2002) 1D ` | 1 | `Oakley1D()` | +| {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | | {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | | {ref}`Sulfur ` | 9 | `Sulfur()` | | {ref}`Welch1992 ` | 20 | `Welch1992()` | diff --git a/docs/fundamentals/reliability.md b/docs/fundamentals/reliability.md index 3387c4f..5a6769e 100644 --- a/docs/fundamentals/reliability.md +++ b/docs/fundamentals/reliability.md @@ -21,6 +21,7 @@ in the comparison of reliability analysis methods. |:-----------------------------------------------------------------------------------:|:-----------------:|:-------------------------------:| | {ref}`2D Cantilever Beam ` | 2 | `CantileverBeam2D ` | | {ref}`Damped Oscillator Reliability ` | 8 | `DampedOscillatorReliability()` | +| {ref}`Four-branch ` | 2 | `FourBranch()` | | {ref}`Gayton Hat ` | 2 | `GaytonHat()` | | {ref}`Hyper-sphere Bound ` | 2 | `HyperSphere()` | diff --git a/docs/references.bib b/docs/references.bib index 80567c8..817ebc0 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -541,13 +541,14 @@ @Article{Bourinet2011 } @Article{Li2018, - author = {Xu Li and Chunlin Gong and Liangxian Gu and Wenkun Gao and Zhao Jing and Hua Su}, - journal = {Structural Safety}, - title = {A sequential surrogate method for reliability analysis based on radial basis function}, - year = {2018}, - pages = {42--53}, - volume = {73}, - doi = {10.1016/j.strusafe.2018.02.005}, + author = {Xu Li and Chunlin Gong and Liangxian Gu and Wenkun Gao and Zhao Jing and Hua Su}, + journal = {Structural Safety}, + title = {A sequential surrogate method for reliability analysis based on radial basis function}, + year = {2018}, + pages = {42--53}, + volume = {73}, + doi = {10.1016/j.strusafe.2018.02.005}, + publisher = {10.1061/AJRUA6.0000870 5}, } @Article{Rajashekhar1993, @@ -572,4 +573,46 @@ @Article{Schueremans2005 doi = {10.1016/j.strusafe.2004.11.001}, } +@Article{Schoebi2017, + author = {R. Schöbi and B. Sudret and S. Marelli}, + journal = {{ASCE}-{ASME} Journal of Risk and Uncertainty in Engineering Systems, Part A: Civil Engineering}, + title = {Rare event estimation using polynomial-chaos kriging}, + year = {2017}, + number = {2}, + volume = {3}, + doi = {10.1061/ajrua6.0000870}, +} + +@Article{Katsuki1994, + author = {Satoshi Katsuki and Dan M. Frangopol}, + journal = {Journal of Engineering Mechanics}, + title = {Hyperspace division method for structural reliability}, + year = {1994}, + number = {11}, + pages = {2405--2427}, + volume = {120}, + doi = {10.1061/(asce)0733-9399(1994)120:11(2405)}, +} + +@PhdThesis{Waarts2000, + author = {Paul Hendrik Waarts}, + school = {Civil Engineering and Geosciences, TU Delt}, + title = {Structural reliability using finite element analysis - an appraisal of {DARS}: Directional adaptive response surface sampling}, + year = {2000}, + address = {Delft, The Netherlands}, + type = {phdthesis}, + url = {https://repository.tudelft.nl/islandora/object/uuid:6e6d9a76-fd12-4220-9dc1-36515b3f638d}, +} + +@Article{Echard2011, + author = {B. Echard and N. Gayton and M. Lemaire}, + journal = {Structural Safety}, + title = {{AK}-{MCS}: an active learning reliability method combining kriging and {Monte Carlo} simulation}, + year = {2011}, + number = {2}, + pages = {145--154}, + volume = {33}, + doi = {10.1016/j.strusafe.2011.01.002}, +} + @Comment{jabref-meta: databaseType:bibtex;} diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index 25b642c..bef8644 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -31,6 +31,7 @@ available in the current UQTestFuns, regardless of their typical applications. | {ref}`Damped Oscillator Reliability ` | 8 | `DampedOscillatorReliability()` | | {ref}`Flood ` | 8 | `Flood()` | | {ref}`Forrester et al. (2008) ` | 1 | `Forrester2008()` | +| {ref}`Four-branch ` | 2 | `FourBranch()` | | {ref}`(1st) Franke ` | 2 | `Franke1()` | | {ref}`(2nd) Franke ` | 2 | `Franke2()` | | {ref}`(3rd) Franke ` | 2 | `Franke3()` | @@ -41,13 +42,13 @@ available in the current UQTestFuns, regardless of their typical applications. | {ref}`Gramacy (2007) 1D Sine ` | 1 | `Gramacy1DSine()` | | {ref}`Hyper-sphere Bound ` | 2 | `HyperSphere()` | | {ref}`Ishigami ` | 3 | `Ishigami()` | -| {ref}`Oakley and O'Hagan (2002) 1D ` | 1 | `Oakley1D()` | -| {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | | {ref}`McLain S1 ` | 2 | `McLainS1()` | | {ref}`McLain S2 ` | 2 | `McLainS2()` | | {ref}`McLain S3 ` | 2 | `McLainS3()` | | {ref}`McLain S4 ` | 2 | `McLainS4()` | | {ref}`McLain S5 ` | 2 | `McLainS5()` | +| {ref}`Oakley and O'Hagan (2002) 1D ` | 1 | `Oakley1D()` | +| {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | | {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | | {ref}`Sobol'-G ` | M | `SobolG()` | | {ref}`Sulfur ` | 9 | `Sulfur()` | diff --git a/docs/test-functions/four-branch.md b/docs/test-functions/four-branch.md new file mode 100644 index 0000000..54b4d82 --- /dev/null +++ b/docs/test-functions/four-branch.md @@ -0,0 +1,232 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:four-branch)= +# Four-branch Function + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +The two-dimensional four-branch function was introduced in {cite}`Katsuki1994` +and became a commonly used test function for reliability analysis algorithms +(see, for instance, {cite}`Waarts2000, Schueremans2005, Echard2011, Schoebi2017`) +The test function describes the failure of a series system with four distinct +performance function components. + +The plots of the function are shown below. The left plot shows the surface +plot of the performance function, the center plot shows the contour +plot with a single contour line at function value of $0.0$ (the limit-state +surface), and the right plot shows the same plot with $10^6$ sample points +overlaid. + +```{code-cell} ipython3 +:tags: [remove-input] + +my_fun = uqtf.FourBranch(rng_seed_prob_input=237324) +xx = my_fun.prob_input.get_sample(1000000) +yy = my_fun(xx) +idx_neg = yy <= 0.0 +idx_pos = yy > 0.0 + +lb_1 = my_fun.prob_input.marginals[0].lower +ub_1 = my_fun.prob_input.marginals[0].upper +lb_2 = my_fun.prob_input.marginals[1].lower +ub_2 = my_fun.prob_input.marginals[1].upper + +# Create 2-dimensional grid +xx_1 = np.linspace(lb_1, ub_1, 1000)[:, np.newaxis] +xx_2 = np.linspace(lb_2, ub_2, 1000)[:, np.newaxis] +mesh_2d = np.meshgrid(xx_1, xx_2) +xx_2d = np.array(mesh_2d).T.reshape(-1, 2) +yy_2d = my_fun(xx_2d) + +# --- Create the plot +fig = plt.figure(figsize=(10, 5)) + +# Surface +axs_0 = plt.subplot(131, projection='3d') +axs_0.plot_surface( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000,1000).T, + linewidth=0, + cmap="plasma", + antialiased=False, + alpha=0.5 +) +axs_0.set_xlabel("$x_1$", fontsize=14) +axs_0.set_ylabel("$x_2$", fontsize=14) +axs_0.set_zlabel("$g$", fontsize=14) + +# Contour plot +axs_1 = plt.subplot(132) +cf = axs_1.contour( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000, 1000).T, + levels=0, + colors=["#ca0020"], +) +axs_1.set_xlim([lb_1, ub_1]) +axs_1.set_ylim([lb_2, ub_2]) +axs_1.set_xlabel("$x_1$", fontsize=14) +axs_1.set_ylabel("$x_2$", fontsize=14) +axs_1.set_aspect("equal", "box") +axs_1.clabel(cf, inline=True, fontsize=14) + +# Scatter plot +axs_2 = plt.subplot(133) +cf = axs_2.contour( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000, 1000).T, + levels=0, + colors=["#ca0020"], +) +axs_2.scatter( + xx[idx_neg, 0], + xx[idx_neg, 1], + color="#ca0020", + marker=".", + s=30, + label="$g(x) \leq 0$" +) +axs_2.scatter( + xx[idx_pos, 0], + xx[idx_pos, 1], + color="#0571b0", + marker=".", + s=30, + label="$g(x) > 0$" +) +axs_2.set_xlim([lb_1, ub_1]) +axs_2.set_ylim([lb_2, ub_2]) +axs_2.set_xlabel("$x_1$", fontsize=14) +axs_2.set_ylabel("$x_2$", fontsize=14) +axs_2.set_aspect("equal", "box") +axs_2.clabel(cf, inline=True, fontsize=14) +axs_2.legend(fontsize=14, loc="lower right"); + +plt.gcf().tight_layout(pad=4.0) +plt.gcf().set_dpi(150); +``` + +## Test function instance + +To create a default instance of the test function: + +```{code-cell} ipython3 +my_testfun = uqtf.FourBranch() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +## Description + +The test function (i.e., the performance function) is analytically defined +as follows: + +$$ +g(\boldsymbol{x}) = \min \begin{Bmatrix} +3 + 0.1 (x_1 - x_2)^2 - \frac{x_1 + x_2}{\sqrt{2}} \\ +3 + 0.1 (x_1 - x_2)^2 + \frac{x_1 + x_2}{\sqrt{2}} \\ +(x_1 - x_2) + p \\ +(x_2 - x_1) + p \\ +\end{Bmatrix} +$$ + +where $\boldsymbol{x} = \{ x_1, x_2 \}$ is the two-dimensional vector of +input variables probabilistically defined further below and $p$ is the +parameter of the test function (see the corresponding section below). + +The failure event and the failure probability are defined as +$g(\boldsymbol{x}) \leq 0$ and $\mathbb{P}[g(\boldsymbol{x}) \leq 0]$, +respectively. + +## Probabilistic input + +Based on {cite}`Katsuki1994`, the probabilistic input model for +the test function consists of two independent standard normal random variables +(see the table below). + +```{code-cell} ipython3 +my_testfun.prob_input +``` + +## Parameters + +The test function is parameterized by a single value $p$. Some values available +in the literature is given in the table below. + +| $p$ | Keyword | Source | +|:--------------------:|:---------------------------:|:-----------------------:| +| $3.5 \sqrt{2}$ | `Katsuki1994` | {cite}`Katsuki1994` | +| $\frac{6}{\sqrt{2}}$ | `Schueremans2005` (default) | {cite}`Schueremans2005` | + +## Reference results + +This section provides several reference results of typical UQ analyses involving +the test function. + +### Sample histogram + +Shown below is the histogram of the output based on $10^6$ random points: + +```{code-cell} ipython3 +:tags: [hide-input] + +xx_test = my_testfun.prob_input.get_sample(1000000) +yy_test = my_testfun(xx_test) +idx_pos = yy_test > 0 +idx_neg = yy_test <= 0 + +hist_pos = plt.hist(yy_test, bins="auto", color="#0571b0") +plt.hist(yy_test[idx_neg], bins=hist_pos[1], color="#ca0020") +plt.axvline(0, linewidth=1.0, color="#ca0020") + +plt.grid() +plt.ylabel("Counts [-]") +plt.xlabel("$\mathcal{M}(\mathbf{X})$") +plt.gcf().set_dpi(150); +``` + +### Failure probability ($P_f$) + +Some reference values for the failure probability $P_f$ from the literature +are summarized in the table below. + +| $p$ | Method | $N$ | $\hat{P}_f$ | $\mathrm{CoV}[\hat{P}_f]$ | Source | +|:--------------------:|:------------:|:-----------:|:-------------------------:|:-------------------------:|:------------------------:| +| $3.5 \sqrt{2}$ | Analytical | — | $2.185961 \times 10^{-3}$ | — | {cite}`Waarts2000` | +| | MCS | $10^5$ | $2.185961 \times 10^{-3}$ | — | {cite}`Waarts2000` | +| | FORM | $32$ | $2.326291 \times 10^{-4}$ | — | {cite}`Waarts2000` | +| | SORM | $12$ | $8.740315 \times 10^{-4}$ | — | {cite}`Waarts2000` | +| $\frac{6}{\sqrt{2}}$ | MCS | $10^8$ | $4.460 \times 10^{-3}$ | $1.5 \%$ | {cite}`Schoebi2017` | +| | MCS | $10^6$ | $4.416 \times 10^{-3}$ | $0.15 \%$ | {cite}`Echard2011` | +| | IS | $1'469$ | $4.9 \times 10^{-3}$ | — | {cite}`Echard2011` | + + +## References + +```{bibliography} +:style: unsrtalpha +:filter: docname in docnames +``` diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index 91e9b53..5ca5ad1 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -8,6 +8,7 @@ from .damped_oscillator import DampedOscillator, DampedOscillatorReliability from .flood import Flood from .forrester import Forrester2008 +from .four_branch import FourBranch from .franke import Franke1, Franke2, Franke3, Franke4, Franke5, Franke6 from .gayton_hat import GaytonHat from .gramacy2007 import Gramacy1DSine @@ -37,6 +38,7 @@ "DampedOscillatorReliability", "Flood", "Forrester2008", + "FourBranch", "Franke1", "Franke2", "Franke3", diff --git a/src/uqtestfuns/test_functions/cantilever_beam_2d.py b/src/uqtestfuns/test_functions/cantilever_beam_2d.py index 90b61ed..212a431 100644 --- a/src/uqtestfuns/test_functions/cantilever_beam_2d.py +++ b/src/uqtestfuns/test_functions/cantilever_beam_2d.py @@ -2,7 +2,7 @@ Module with an implementation of the 2D cantilever beam reliability problem. The 2D cantilever beam problem is a reliability test function from [1]. -This is an often revisited problem in reliability analysis ([2]). +This is an often revisited problem in reliability analysis ([2], [3]). The problem consists of a cantilever beam with a rectangular cross-section subjected to uniformly distributed loading. @@ -16,10 +16,14 @@ surface approach for reliability analysis,” Structural Safety, vol. 12, no. 3, pp. 205–220, 1993. DOI: 10.1016/0167-4730(93)90003-J -2. X. Li, C. Gong, L. Gu, W. Gao, Z. Jing, and H. Su, “A sequential surrogate +2. Luc Schueremans and Dionys Van Gemert, “Benefit of splines and neural + networks in simulation based structural reliability analysis,” Structural + Safety, vol. 27, no. 3, pp. 246–261, 2005. + DOI: 10.1016/j.strusafe.2004.11.001 +3. X. Li, C. Gong, L. Gu, W. Gao, Z. Jing, and H. Su, “A sequential surrogate method for reliability analysis based on radial basis function,” Structural Safety, vol. 73, pp. 42–53, 2018. - DOI: 10.1016/j.strusafe.2018.02.005. + DOI: 10.1016/j.strusafe.2018.02.005 """ import numpy as np diff --git a/src/uqtestfuns/test_functions/four_branch.py b/src/uqtestfuns/test_functions/four_branch.py new file mode 100644 index 0000000..9d211c8 --- /dev/null +++ b/src/uqtestfuns/test_functions/four_branch.py @@ -0,0 +1,123 @@ +""" +Module with an implementation of the four-branch test function. + +The two-dimensional four-branch function introduced in [1] is a reliability +benchmark problem (see, for instance, [2], [3], [4], [5]). +The test function describes the failure of a series system with four distinct +performance function components. + +References +---------- + +1. Satoshi Katsuki and Dan M. Frangopol, “Hyperspace division method for + structural Reliability,” Journal of Engineering Mechanic, vol. 120, no. 11, + pp. 2405–2427, 1994. + DOI: 10.1061/(ASCE)0733-9399(1994)120:11(2405) +2. Paul Hendrik Waarts, “Structural reliability using finite element + analysis - an appraisal of DARS: Directional adaptive response surface + sampling," Civil Engineering and Geosciences, TU Delft, Delft, + The Netherlands, 2000. +3. Luc Schueremans and Dionys Van Gemert, “Benefit of splines and neural + networks in simulation based structural reliability analysis,” Structural + Safety, vol. 27, no. 3, pp. 246–261, 2005. + DOI: 10.1016/j.strusafe.2004.11.001 +4. B. Echard, N. Gayton, and M. Lemaire, “AK-MCS: An active learning + reliability method combining Kriging and Monte Carlo Simulation,” + Structural Safety, vol. 33, no. 2, pp. 145–154, 2011. + DOI: 10.1016/j.strusafe.2011.01.002 +5. Roland Schöbi, Bruno Sudret, and Stefano Marelli, “Rare event estimation + using polynomial-chaos kriging,” ASCE-ASME Journal Risk and Uncertainty + in Engineering System, Part A: Civil Engineering, vol. 3, no. 2, + p. D4016002, 2017. + DOI: 10.1061/AJRUA6.0000870. +""" +import numpy as np + +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecFixDim +from ..core.uqtestfun_abc import UQTestFunABC + +__all__ = ["FourBranch"] + + +AVAILABLE_INPUT_SPECS = { + "Katsuki1994": ProbInputSpecFixDim( + name="FourBranch-Katsuki1994", + description=( + "Probabilistic input for the four-branch function " + "from Katsuki and Frangopol (1994)" + ), + marginals=[ + UnivDistSpec( + name="X1", + distribution="normal", + parameters=[0, 1], + description="None", + ), + UnivDistSpec( + name="X2", + distribution="normal", + parameters=[0, 1], + description="None", + ), + ], + copulas=None, + ), +} + +AVAILABLE_PARAMETERS = { + "Katsuki1994": 3.5 * np.sqrt(2), + "Schueremans2005": 6.0 / np.sqrt(2), +} + + +def evaluate(xx: np.ndarray, parameters: float) -> np.ndarray: + """Evaluate the four-branch function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + A two-Dimensional input values given by an N-by-2 array + where N is the number of input values. + parameters : float + The parameter of the test function; a single float. + + Returns + ------- + np.ndarray + The output of the test function evaluated on the input values. + The output is a 1-dimensional array of length N. + """ + p = parameters + + # Compute the performance function components + yy_1 = ( + 3.0 + + 0.1 * (xx[:, 0] - xx[:, 1]) ** 2 + - (xx[:, 0] + xx[:, 1]) / np.sqrt(2) + ) + yy_2 = ( + 3.0 + + 0.1 * (xx[:, 0] - xx[:, 1]) ** 2 + + (xx[:, 0] + xx[:, 1]) / np.sqrt(2) + ) + yy_3 = xx[:, 0] - xx[:, 1] + p + yy_4 = -1 * xx[:, 0] + xx[:, 1] + p + + yy = np.vstack((yy_1, yy_2, yy_3, yy_4)) + + return np.min(yy, axis=0) + + +class FourBranch(UQTestFunABC): + """A concrete implementation of the four-branch test function.""" + + _tags = ["reliability"] + _description = ( + "Series system reliability from Katsuki and Frangopol (1994)" + ) + _available_inputs = AVAILABLE_INPUT_SPECS + _available_parameters = AVAILABLE_PARAMETERS + _default_spatial_dimension = 2 + _default_parameters = "Schueremans2005" + + eval_ = staticmethod(evaluate) diff --git a/src/uqtestfuns/test_functions/gayton_hat.py b/src/uqtestfuns/test_functions/gayton_hat.py index 4f0c7d0..42e73b2 100644 --- a/src/uqtestfuns/test_functions/gayton_hat.py +++ b/src/uqtestfuns/test_functions/gayton_hat.py @@ -53,7 +53,7 @@ def evaluate(xx: np.ndarray) -> np.ndarray: Parameters ---------- xx : np.ndarray - 1-Dimensional input values given by an N-by-1 array + A two-Dimensional input values given by an N-by-2 array where N is the number of input values. Returns From 1ba56ff509b5220f08775aa8ec969ebd01dcd7ed Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Thu, 29 Jun 2023 17:13:55 +0200 Subject: [PATCH 40/73] Add an implementation of the speed reducer shaft test function. - The five-dimensional function is a reliability analysis benchmark problem from Du and Sudjianto (2004). --- CHANGELOG.md | 2 + docs/_toc.yml | 2 + docs/fundamentals/reliability.md | 1 + docs/glossary.md | 31 +++++ docs/references.bib | 11 ++ docs/test-functions/available.md | 1 + docs/test-functions/cantilever-beam-2d.md | 12 +- .../damped-oscillator-reliability.md | 34 +++-- docs/test-functions/four-branch.md | 14 +- docs/test-functions/gayton-hat.md | 14 +- docs/test-functions/hyper-sphere.md | 14 +- docs/test-functions/speed-reducer-shaft.md | 123 ++++++++++++++++++ src/uqtestfuns/test_functions/__init__.py | 2 + .../test_functions/speed_reducer_shaft.py | 118 +++++++++++++++++ src/uqtestfuns/test_functions/utils.py | 40 ++++++ 15 files changed, 374 insertions(+), 45 deletions(-) create mode 100644 docs/test-functions/speed-reducer-shaft.md create mode 100644 src/uqtestfuns/test_functions/speed_reducer_shaft.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bf2f78..c0e72f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Rajashekhar and Ellington (1993). - The two-dimensional four-branch function for reliability analysis from Katsuki and Frangopol (1994). +- The five-dimensional speed reducer shaft reliability problem + from Du and Sudjianto (2004). - New docs section on list of functions for reliability analysis. ## [0.2.0] - 2023-06-26 diff --git a/docs/_toc.yml b/docs/_toc.yml index 230c045..9832cd6 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -87,6 +87,8 @@ parts: title: Piston Simulation - file: test-functions/sobol-g title: Sobol'-G + - file: test-functions/speed-reducer-shaft + title: Speed Reducer Shaft - file: test-functions/sulfur title: Sulfur - file: test-functions/welch1992 diff --git a/docs/fundamentals/reliability.md b/docs/fundamentals/reliability.md index 5a6769e..702bee3 100644 --- a/docs/fundamentals/reliability.md +++ b/docs/fundamentals/reliability.md @@ -24,6 +24,7 @@ in the comparison of reliability analysis methods. | {ref}`Four-branch ` | 2 | `FourBranch()` | | {ref}`Gayton Hat ` | 2 | `GaytonHat()` | | {ref}`Hyper-sphere Bound ` | 2 | `HyperSphere()` | +| {ref}`Speed Reducer Shaft ` | 5 | `SpeedReducerShaft()` | In a Python terminal, you can list all the available functions relevant for metamodeling applications using ``list_functions()`` and filter the results diff --git a/docs/glossary.md b/docs/glossary.md index f4177f3..19c0732 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -6,6 +6,12 @@ CDF Cumulative distribution function, denoted by $F_X(x; \circ)$ where $\circ$ is a placeholder for the distribution parameter(s). +FORM + First-order reliability method + +FOSPA + First-order Saddlepoint Approximation reliability method {cite}`Du2004` + ICDF Inverse cumulative distribution function, denoted by $F^{-1}_X(x; \circ)$ where $\circ$ is a placeholder for the distribution parameter(s). @@ -15,12 +21,37 @@ ICDF ICDF is also known as the _quantile function_ or _percent point function_. +IS + Importance sampling + +MCS + Monte-Carlo simulation + PDF Probability density function, denoted by $f_X(x; \circ)$ where $\circ$ is a placeholder for the distribution parameter(s). +SORM + Second-order reliability method + +SS + Subset simulation + +SSRM + Sequential surrogate reliability method {cite}`Li2018` + Support The support of the probability density function, that is, the subset of the function domain whose elements are not mapped to zero; denoted by $\mathcal{D}_X$. + +SVM + Support Vector Machines ``` + +## References + +```{bibliography} +:style: unsrtalpha +:filter: docname in docnames +``` \ No newline at end of file diff --git a/docs/references.bib b/docs/references.bib index 817ebc0..4c62c5e 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -615,4 +615,15 @@ @Article{Echard2011 doi = {10.1016/j.strusafe.2011.01.002}, } +@Article{Du2004, + author = {Xiaoping Du and Agus Sudjianto}, + journal = {{AIAA} Journal}, + title = {First order saddlepoint approximation for reliability analysis}, + year = {2004}, + number = {6}, + pages = {1199--1207}, + volume = {42}, + doi = {10.2514/1.3877}, +} + @Comment{jabref-meta: databaseType:bibtex;} diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index bef8644..219da96 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -51,6 +51,7 @@ available in the current UQTestFuns, regardless of their typical applications. | {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | | {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | | {ref}`Sobol'-G ` | M | `SobolG()` | +| {ref}`Speed Reducer Shaft ` | 5 | `SpeedReducerShaft()` | | {ref}`Sulfur ` | 9 | `Sulfur()` | | {ref}`Welch1992 ` | 20 | `Welch1992()` | | {ref}`Wing Weight ` | 10 | `WingWeight()` | diff --git a/docs/test-functions/cantilever-beam-2d.md b/docs/test-functions/cantilever-beam-2d.md index e4dd57e..2bf5ba9 100644 --- a/docs/test-functions/cantilever-beam-2d.md +++ b/docs/test-functions/cantilever-beam-2d.md @@ -256,12 +256,12 @@ are summarized in the table below. | Method | $N$ | $\hat{P}_f$ | $\mathrm{CoV}[\hat{P}_f]$ | Source | Remark | |:------------:|:--------:|:------------------------:|:-------------------------:|:-----------------------:|-----------------------------------------| -| MCS | $10^6$ | $9.594 \times 10^{-3}$ | — | {cite}`Li2018` | Monte Carlo Simulation (MCS) | -| FORM | $27$ | $9.88 \times 10^{-3}$ | — | {cite}`Li2018` | — | -| FORM | — | $9.9031 \times 10^{-3}$ | — | {cite}`Rajashekhar1993` | First-order Second-moment method | -| SORM | $32$ | $9.88 \times 10^{-3}$ | — | {cite}`Li2018` | — | -| IS | $10^3$ | $9.6071 \times 10^{-3}$ | — | {cite}`Rajashekhar1993` | Importance Sampling (IS) | -| IS | $9'312$ | $1.00 \times 10^{-2}$ | — | {cite}`Schueremans2005` | — | +| {term}`MCS` | $10^6$ | $9.594 \times 10^{-3}$ | — | {cite}`Li2018` | — | +| {term}`FORM` | $27$ | $9.88 \times 10^{-3}$ | — | {cite}`Li2018` | — | +| {term}`FORM` | — | $9.9031 \times 10^{-3}$ | — | {cite}`Rajashekhar1993` | — | +| {term}`SORM` | $32$ | $9.88 \times 10^{-3}$ | — | {cite}`Li2018` | — | +| {term}`IS` | $10^3$ | $9.6071 \times 10^{-3}$ | — | {cite}`Rajashekhar1993` | Importance Sampling (IS) | +| {term}`IS` | $9'312$ | $1.00 \times 10^{-2}$ | — | {cite}`Schueremans2005` | — | | IS + RS | $2'192$ | $9.00 \times 10^{-3}$ | — | {cite}`Schueremans2005` | IS + Response Surface (RS) | | IS + SP | $358$ | $1.00 \times 10^{-2}$ | — | {cite}`Schueremans2005` | IS + Splines (SP) | | IS + NN | $63$ | $1.20 \times 10^{-2}$ | — | {cite}`Schueremans2005` | IS + Neural Networks (NN) | diff --git a/docs/test-functions/damped-oscillator-reliability.md b/docs/test-functions/damped-oscillator-reliability.md index 9a5c20c..b45b0b3 100644 --- a/docs/test-functions/damped-oscillator-reliability.md +++ b/docs/test-functions/damped-oscillator-reliability.md @@ -72,7 +72,7 @@ is the eight-dimensional vector of input variables probabilistically defined further below and $p$ is the parameter of the function (i.e., the peak factor). The failure event and the failure probability are defined as -$g(\boldsymbol{x}) \leq 0$ and $\mathbb{P}[g(\boldsymbol{x}) \leq 0]$, +$g(\boldsymbol{x}) \leq 0$ and $\mathbb{P}[g(\boldsymbol{X}) \leq 0]$, respectively. ## Probabilistic input @@ -138,20 +138,20 @@ Some reference values for the failure probability $P_f$ and from the literature are summarized in the table below ($\mu_{F_s}$ is the log-normal distribution mean of $F_s$). -| $\mu_{F_s}$ | Method | $N$ | $\hat{P}_f$ | $\mathrm{CoV}[\hat{P}_f]$ | Source | -|:-----------:|:-----------------------:|:---------------:|:---------------------:|:-------------------------:|:---------------------------------------:| -| $15$ | FORM | $1'179$ | $2.19 \times 10^{-2}$ | — | {cite}`DerKiureghian1990, Bourinet2011` | -| | Subset Sim. | $3 \times 10^5$ | $4.63 \times 10^{-3}$ | $< 3\%$ | {cite}`Dubourg2011` | -| | Meta-IS[^meta-is] | $464 + 600$ | $4.80 \times 10^{-3}$ | $< 5\%$ | {cite}`Dubourg2011` | -| | SVM[^svm] + Subset Sim. | $1'719$ | $4.78 \times 10^{-3}$ | $< 4\%$ | {cite}`Bourinet2011` | -| $21.5$ | FORM | $2'520$ | $3.50 \times 10^{-4}$ | — | {cite}`DerKiureghian1990, Bourinet2011` | -| | Subset Sim. | $5 \times 10^5$ | $4.75 \times 10^{-5}$ | $< 4\%$ | {cite}`Dubourg2011` | -| | Meta-IS | $336 + 400$ | $4.46 \times 10^{-5}$ | $< 5\%$ | {cite}`Dubourg2011` | -| | SVM + Subset Sim. | $2'865$ | $4.42 \times 10^{-5}$ | $< 7\%$ | {cite}`Bourinet2011` | -| $27.5$ | FORM | $2'727$ | $3.91 \times 10^{-6}$ | — | {cite}`DerKiureghian1990, Bourinet2011` | -| | Subset Sim. | $7 \times 10^5$ | $3.47 \times 10^{-7}$ | $< 5\%$ | {cite}`Dubourg2011` | -| | Meta-IS | $480 + 200$ | $3.76 \times 10^{-7}$ | $< 5\%$ | {cite}`Dubourg2011` | -| | SVM + Subset Sim. | $4'011$ | $3.66 \times 10^{-7}$ | $< 10\%$ | {cite}`Bourinet2011` | +| $\mu_{F_s}$ | Method | $N$ | $\hat{P}_f$ | $\mathrm{CoV}[\hat{P}_f]$ | Source | +|:-----------:|:------------------------:|:---------------:|:---------------------:|:-------------------------:|:---------------------------------------:| +| $15$ | {term}`FORM` | $1'179$ | $2.19 \times 10^{-2}$ | — | {cite}`DerKiureghian1990, Bourinet2011` | +| | {term}`SS` | $3 \times 10^5$ | $4.63 \times 10^{-3}$ | $< 3\%$ | {cite}`Dubourg2011` | +| | Meta-IS[^meta-is] | $464 + 600$ | $4.80 \times 10^{-3}$ | $< 5\%$ | {cite}`Dubourg2011` | +| | {term}`SVM` + {term}`SS` | $1'719$ | $4.78 \times 10^{-3}$ | $< 4\%$ | {cite}`Bourinet2011` | +| $21.5$ | {term}`FORM` | $2'520$ | $3.50 \times 10^{-4}$ | — | {cite}`DerKiureghian1990, Bourinet2011` | +| | {term}`SS` | $5 \times 10^5$ | $4.75 \times 10^{-5}$ | $< 4\%$ | {cite}`Dubourg2011` | +| | Meta-{term}`IS` | $336 + 400$ | $4.46 \times 10^{-5}$ | $< 5\%$ | {cite}`Dubourg2011` | +| | {term}`SVM` + {term}`SS` | $2'865$ | $4.42 \times 10^{-5}$ | $< 7\%$ | {cite}`Bourinet2011` | +| $27.5$ | {term}`FORM` | $2'727$ | $3.91 \times 10^{-6}$ | — | {cite}`DerKiureghian1990, Bourinet2011` | +| | {term}`SS` | $7 \times 10^5$ | $3.47 \times 10^{-7}$ | $< 5\%$ | {cite}`Dubourg2011` | +| | Meta-IS | $480 + 200$ | $3.76 \times 10^{-7}$ | $< 5\%$ | {cite}`Dubourg2011` | +| | {term}`SVM` + {term}`SS` | $4'011$ | $3.66 \times 10^{-7}$ | $< 10\%$ | {cite}`Bourinet2011` | Note that in the table above the total number of model evaluations for metamodel-based Importance Sampling (Meta-IS) is the sum of training runs @@ -164,9 +164,7 @@ and the correction runs {cite}`Dubourg2011`. :filter: docname in docnames ``` -[^location]: see, for instance, +[^location]: see, for instance, Eqs. (5.5) and (5.7), pp. 184-185 in {cite}`Dubourg2011`. [^meta-is]: Metamodel-based Importance Sampling - -[^svm]: Support Vector Machines diff --git a/docs/test-functions/four-branch.md b/docs/test-functions/four-branch.md index 54b4d82..3786375 100644 --- a/docs/test-functions/four-branch.md +++ b/docs/test-functions/four-branch.md @@ -145,7 +145,7 @@ The test function (i.e., the performance function) is analytically defined as follows: $$ -g(\boldsymbol{x}) = \min \begin{Bmatrix} +g(\boldsymbol{x}; p) = \min \begin{Bmatrix} 3 + 0.1 (x_1 - x_2)^2 - \frac{x_1 + x_2}{\sqrt{2}} \\ 3 + 0.1 (x_1 - x_2)^2 + \frac{x_1 + x_2}{\sqrt{2}} \\ (x_1 - x_2) + p \\ @@ -216,12 +216,12 @@ are summarized in the table below. | $p$ | Method | $N$ | $\hat{P}_f$ | $\mathrm{CoV}[\hat{P}_f]$ | Source | |:--------------------:|:------------:|:-----------:|:-------------------------:|:-------------------------:|:------------------------:| | $3.5 \sqrt{2}$ | Analytical | — | $2.185961 \times 10^{-3}$ | — | {cite}`Waarts2000` | -| | MCS | $10^5$ | $2.185961 \times 10^{-3}$ | — | {cite}`Waarts2000` | -| | FORM | $32$ | $2.326291 \times 10^{-4}$ | — | {cite}`Waarts2000` | -| | SORM | $12$ | $8.740315 \times 10^{-4}$ | — | {cite}`Waarts2000` | -| $\frac{6}{\sqrt{2}}$ | MCS | $10^8$ | $4.460 \times 10^{-3}$ | $1.5 \%$ | {cite}`Schoebi2017` | -| | MCS | $10^6$ | $4.416 \times 10^{-3}$ | $0.15 \%$ | {cite}`Echard2011` | -| | IS | $1'469$ | $4.9 \times 10^{-3}$ | — | {cite}`Echard2011` | +| | {term}`MCS` | $10^5$ | $2.185961 \times 10^{-3}$ | — | {cite}`Waarts2000` | +| | {term}`FORM` | $32$ | $2.326291 \times 10^{-4}$ | — | {cite}`Waarts2000` | +| | {term}`SORM` | $12$ | $8.740315 \times 10^{-4}$ | — | {cite}`Waarts2000` | +| $\frac{6}{\sqrt{2}}$ | {term}`MCS` | $10^8$ | $4.460 \times 10^{-3}$ | $1.5 \%$ | {cite}`Schoebi2017` | +| | {term}`MCS` | $10^6$ | $4.416 \times 10^{-3}$ | $0.15 \%$ | {cite}`Echard2011` | +| | {term}`IS` | $1'469$ | $4.9 \times 10^{-3}$ | — | {cite}`Echard2011` | ## References diff --git a/docs/test-functions/gayton-hat.md b/docs/test-functions/gayton-hat.md index 2fc3de6..017f395 100644 --- a/docs/test-functions/gayton-hat.md +++ b/docs/test-functions/gayton-hat.md @@ -194,12 +194,12 @@ plt.gcf().set_dpi(150); Some reference values for the failure probability $P_f$ from the literature are summarized in the table below. -| Method | $N$ | $\hat{P}_f$ | $\mathrm{CoV}[\hat{P}_f]$ | Source | Remark | -|:------:|:-------------------:|:---------------------:|:-------------------------:|:------------------:|--------------------------------| -| MCS | $5 \times 10^7$ | $2.85 \times 10^{-5}$ | $2.64 \%$ | {cite}`Echard2013` | Median over $100$ replications | -| FORM | $19$ | $4.21 \times 10^{-5}$ | — | {cite}`Echard2013` | Median over $100$ replications | -| IS | $19 + \times 10^4$ | $2.86 \times 10^{-5}$ | $2.39 \%$ | {cite}`Echard2013` | Median over $100$ replications | -| AK+IS | $19 + 7$ | $2.86 \times 10^{-5}$ | $2.39 \%$ | {cite}`Echard2013` | Median over $100$ replications | +| Method | $N$ | $\hat{P}_f$ | $\mathrm{CoV}[\hat{P}_f]$ | Source | Remark | +|:-------------:|:-------------------:|:---------------------:|:-------------------------:|:------------------:|--------------------------------| +| {term}`MCS` | $5 \times 10^7$ | $2.85 \times 10^{-5}$ | $2.64 \%$ | {cite}`Echard2013` | Median over $100$ replications | +| {term}`FORM` | $19$ | $4.21 \times 10^{-5}$ | — | {cite}`Echard2013` | Median over $100$ replications | +| {term}`IS` | $19 + \times 10^4$ | $2.86 \times 10^{-5}$ | $2.39 \%$ | {cite}`Echard2013` | Median over $100$ replications | +| AK+IS[^ak-is] | $19 + 7$ | $2.86 \times 10^{-5}$ | $2.39 \%$ | {cite}`Echard2013` | Median over $100$ replications | ## References @@ -208,3 +208,5 @@ are summarized in the table below. :style: unsrtalpha :filter: docname in docnames ``` + +[^ak-is]: Adaptive-Kriging importance sampling diff --git a/docs/test-functions/hyper-sphere.md b/docs/test-functions/hyper-sphere.md index e99297c..6389593 100644 --- a/docs/test-functions/hyper-sphere.md +++ b/docs/test-functions/hyper-sphere.md @@ -194,12 +194,12 @@ plt.gcf().set_dpi(150); Some reference values for the failure probability $P_f$ from the literature are summarized in the table below. -| Method | $N$ | $\hat{P}_f$ | $\mathrm{CoV}[\hat{P}_f]$ | Source | -|:-----------:|:------:|:----------------------:|:-------------------------:|:--------------:| -| MCS | $10^6$ | $3.381 \times 10^{-2}$ | —[^error-li] | {cite}`Li2018` | -| FORM | $15$ | $1.891 \times 10^{-2}$ | — | {cite}`Li2018` | -| SORM | $20$ | $2.672 \times 10^{-2}$ | — | {cite}`Li2018` | -| SSRM[^ssrm] | $12$ | $3.381 \times 10^{-2}$ | —[^error-li] | {cite}`Li2018` | +| Method | $N$ | $\hat{P}_f$ | $\mathrm{CoV}[\hat{P}_f]$ | Source | +|:-------------:|:------:|:----------------------:|:-------------------------:|:--------------:| +| {term}`MCS` | $10^6$ | $3.381 \times 10^{-2}$ | —[^error-li] | {cite}`Li2018` | +| {term}`FORM` | $15$ | $1.891 \times 10^{-2}$ | — | {cite}`Li2018` | +| {term}`SORM` | $20$ | $2.672 \times 10^{-2}$ | — | {cite}`Li2018` | +| {term}`SSRM` | $12$ | $3.381 \times 10^{-2}$ | —[^error-li] | {cite}`Li2018` | ## References @@ -211,7 +211,5 @@ are summarized in the table below. [^location]: see Eq. (10) in {cite}`Li2018`. -[^ssrm]: Sequential surrogate reliability method - [^error-li]: The coefficient of variations of the failure probability estimates in {cite}`Li2018` were not reported. diff --git a/docs/test-functions/speed-reducer-shaft.md b/docs/test-functions/speed-reducer-shaft.md new file mode 100644 index 0000000..bfa4984 --- /dev/null +++ b/docs/test-functions/speed-reducer-shaft.md @@ -0,0 +1,123 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:speed-reducer-shaft)= +# Speed Reducer Shaft + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +The speed reducer shaft test function is a five-dimensional scalar-valued +test function introduced in {cite}`Du2004`. It is used as a test function for reliability +analysis algorithms (see, for instance, {cite}`Du2004, Li2018`). + +## Test function instance + +To create a default instance of the test function: + +```{code-cell} ipython3 +my_testfun = uqtf.SpeedReducerShaft() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +## Description + +The function models the performance of a shaft in a speed reducer {cite}`Du2004`. +The performance is defined as the strength of the shaft subtracted by the +stress as follows[^location]: + +$$ +g(\boldsymbol{x}) = S - \frac{32}{\pi D^3} \sqrt{\frac{F^2 L^2}{16} + T^2}, +$$ + +where $\boldsymbol{x} = \{ S, D, F, L, T \}$ +is the five-dimensional vector of input variables probabilistically defined +further below. + +The failure event and the failure probability are defined as +$g(\boldsymbol{x}) \leq 0$ and $\mathbb{P}[g(\boldsymbol{X}) \leq 0]$, +respectively. + +## Probabilistic input + +Based on {cite}`Du2004`, the probabilistic input model +for the speed reducer shaft reliability problem consists of five independent +random variables with marginal distributions shown in the table below. + +```{code-cell} ipython3 +my_testfun.prob_input +``` + +Note that the variables $F$, $D$, and $L$ must be first converted to their +corresponding SI units (i.e., $[\mathrm{Pa}]$, $[\mathrm{m}]$, +and $[\mathrm{m}]$, respectively) before the values are plugged +into the formula above. + + +## Reference results + +This section provides several reference results of typical UQ analyses involving +the test function. + +### Sample histogram + +Shown below is the histogram of the output based on $10^6$ random points: + +```{code-cell} ipython3 +:tags: [hide-input] + +xx_test = my_testfun.prob_input.get_sample(1000000) +yy_test = my_testfun(xx_test) +idx_pos = yy_test > 0 +idx_neg = yy_test <= 0 + +hist_pos = plt.hist(yy_test, bins="auto", color="#0571b0") +plt.hist(yy_test[idx_neg], bins=hist_pos[1], color="#ca0020") +plt.axvline(0, linewidth=1.0, color="#ca0020") + +plt.grid() +plt.ylabel("Counts [-]") +plt.xlabel("$\mathcal{M}(\mathbf{X})$") +plt.gcf().set_dpi(150); +``` + +### Failure probability + +Some reference values for the failure probability $P_f$ and from the literature +are summarized in the table below ($\mu_{F_s}$ is the log-normal distribution +mean of $F_s$). + +| Method | $N$ | $\hat{P}_f$ | $\mathrm{CoV}[\hat{P}_f]$ | Source | +|:-------------:|:-------:|:-----------------------:|:-------------------------:|:--------------------------:| +| {term}`MCS` | $10^6$ | $7.850 \times 10^{-4}$ | — | {cite}`Du2004` (Table 11) | +| {term}`FORM` | $1'472$ | $7.007 \times 10^{-7}$ | — | {cite}`Du2004` (Table 11) | +| {term}`SORM` | $1'514$ | $4.3581 \times 10^{-7}$ | — | {cite}`Du2004` (Table 11) | +| {term}`FOSPA` | $102$ | $6.1754 \times 10^{-4}$ | — | {cite}`Du2004` (Table 11) | + +## References + +```{bibliography} +:style: unsrtalpha +:filter: docname in docnames +``` + +[^location]: see Eq. (34), p. 1205 in {cite}`Du2004`. diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index 5ca5ad1..d324353 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -19,6 +19,7 @@ from .mclain import McLainS1, McLainS2, McLainS3, McLainS4, McLainS5 from .piston import Piston from .sobol_g import SobolG +from .speed_reducer_shaft import SpeedReducerShaft from .sulfur import Sulfur from .welch1992 import Welch1992 from .wing_weight import WingWeight @@ -58,6 +59,7 @@ "McLainS5", "Piston", "SobolG", + "SpeedReducerShaft", "Sulfur", "Welch1992", "WingWeight", diff --git a/src/uqtestfuns/test_functions/speed_reducer_shaft.py b/src/uqtestfuns/test_functions/speed_reducer_shaft.py new file mode 100644 index 0000000..a0ee2c8 --- /dev/null +++ b/src/uqtestfuns/test_functions/speed_reducer_shaft.py @@ -0,0 +1,118 @@ +""" +Module with an implementation of the speed reducer shaft test function. + +The speed reducer shaft test function is a five-dimensional scalar-valued +test function introduced in [1]. It is used as a test function for reliability +analysis algorithms (see, for instance, [1], [2]). + +The function models the performance of a shaft in a speed reducer. +The performance is defined as the strength of the shaft subtracted by the +stress. If the value is negative, then the system is in failed state. + +References +---------- +1. Xiaoping Du and Agus Sudjianto, “First order saddlepoint approximation + for reliability analysis,” AIAA Journal, vol. 42, no. 6, pp. 1199–1207, + 2004. + DOI: 10.2514/1.3877 +2. X. Li, C. Gong, L. Gu, W. Gao, Z. Jing, and H. Su, “A sequential surrogate + method for reliability analysis based on radial basis function,” + Structural Safety, vol. 73, pp. 42–53, 2018. + DOI: 10.1016/j.strusafe.2018.02.005 +""" +import numpy as np + +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecFixDim +from ..core.uqtestfun_abc import UQTestFunABC +from .utils import gumbel_max_mu, gumbel_max_beta + +__all__ = ["SpeedReducerShaft"] + + +AVAILABLE_INPUT_SPECS = { + "Du2004": ProbInputSpecFixDim( + name="SpeedReducerShaft-Du2004", + description=( + "Probabilistic input model for the speed reducer shaft problem " + "from Du and Sudjianto (2004)" + ), + marginals=[ + UnivDistSpec( + name="D", + distribution="normal", + parameters=[39, 0.1], + description="Shaft diameter [mm]", + ), + UnivDistSpec( + name="L", + distribution="normal", + parameters=[400, 0.1], + description="Shaft span [mm]", + ), + UnivDistSpec( + name="F", + distribution="gumbel", + parameters=[gumbel_max_mu(1500, 350), gumbel_max_beta(350)], + description="External force [N]", + ), + UnivDistSpec( + name="T", + distribution="normal", + parameters=[250, 35], + description="Torque [Nm]", + ), + UnivDistSpec( + name="S", + distribution="uniform", + parameters=[70, 80], + description="Strength [MPa]", + ), + ], + copulas=None, + ), +} + + +def evaluate(xx: np.ndarray) -> np.ndarray: + """Evaluate the speed reducer shaft test function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + A five-dimensional input values given by N-by-5 arrays + where N is the number of input values. + + Returns + ------- + np.ndarray + The output of the speed reducer shaft test function. + If negative, then the system is in failed state. + The output is a one-dimensional array of length N. + """ + dd = xx[:, 0] # diameter [mm] + ll = xx[:, 1] # span [mm] + ff = xx[:, 2] # external force [N] + tt = xx[:, 3] # torque [Nm] + ss = xx[:, 4] # strength [MPa] + + # NOTE: Convert [MPa] to [Pa] and [mm] to [m] + yy = ss * 1e6 - (32 / np.pi / (dd / 1e3) ** 3) * np.sqrt( + ff**2 * (ll / 1e3) ** 2 / 16 + tt**2 + ) + + return yy + + +class SpeedReducerShaft(UQTestFunABC): + """A concrete implementation of the speed reducer shaft function.""" + + _tags = ["reliability"] + _description = ( + "Reliability of a shaft in a speed reducer " + "from Du and Sudjianto (2004)" + ) + _available_inputs = AVAILABLE_INPUT_SPECS + _available_parameters = None + _default_spatial_dimension = 5 + + eval_ = staticmethod(evaluate) diff --git a/src/uqtestfuns/test_functions/utils.py b/src/uqtestfuns/test_functions/utils.py index b92395d..8e7a332 100644 --- a/src/uqtestfuns/test_functions/utils.py +++ b/src/uqtestfuns/test_functions/utils.py @@ -74,3 +74,43 @@ def lognorm2norm_mean(lognormal_mean: float, lognormal_std: float) -> float: normal_mean = np.log(lognormal_mean) - normal_std**2 / 2.0 return normal_mean + + +def gumbel_max_mu(mean: float, std: float) -> float: + """Get the mu (location) parameter of a Gumbel (max.) distribution. + + Parameters + ---------- + mean : float + The mean of a Gumbel (max.) distribution. + std : float + The standard deviation of a Gumbel (max.) distribution. + + Returns + ------- + float + The mu (location) parameter of the Gumbel (max.) distribution. + """ + beta = gumbel_max_beta(std) + + mu = mean - beta * np.euler_gamma + + return mu + + +def gumbel_max_beta(std: float) -> float: + """Get the beta (scale) parameter of the Gumbel (max.) distribution. + + Parameters + ---------- + std : float + The standard deviation of a Gumbel (max.) distribution. + + Returns + ------- + float + The beta (scale) parameter of the Gumbel (max.) distribution. + """ + beta = std * np.sqrt(6) / np.pi + + return beta From c1b33270b3ea1b147416b0e1bed63c7421ad8ea3 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Thu, 29 Jun 2023 23:40:41 +0200 Subject: [PATCH 41/73] Add an implementation of the circular pipe crack problem. - This is a two-dimensional reliability analysis test function and taken from Verma et al. (2015). --- CHANGELOG.md | 2 + docs/_toc.yml | 2 + docs/fundamentals/reliability.md | 3 +- docs/references.bib | 10 + docs/test-functions/available.md | 3 +- docs/test-functions/cantilever-beam-2d.md | 24 +- docs/test-functions/circular-pipe-crack.md | 228 ++++++++++++++++++ docs/test-functions/four-branch.md | 26 +- docs/test-functions/gayton-hat.md | 27 ++- docs/test-functions/hyper-sphere.md | 26 +- src/uqtestfuns/test_functions/__init__.py | 2 + .../test_functions/circular_pipe_crack.py | 117 +++++++++ 12 files changed, 425 insertions(+), 45 deletions(-) create mode 100644 docs/test-functions/circular-pipe-crack.md create mode 100644 src/uqtestfuns/test_functions/circular_pipe_crack.py diff --git a/CHANGELOG.md b/CHANGELOG.md index c0e72f2..4d1af67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 from Katsuki and Frangopol (1994). - The five-dimensional speed reducer shaft reliability problem from Du and Sudjianto (2004). +- The two-dimensional reliability problem of a circular pipe crack + under a bending moment under Verma et al. (2015). - New docs section on list of functions for reliability analysis. ## [0.2.0] - 2023-06-26 diff --git a/docs/_toc.yml b/docs/_toc.yml index 9832cd6..dac55e0 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -39,6 +39,8 @@ parts: title: Bratley et al. (1992) D - file: test-functions/cantilever-beam-2d title: Cantilever Beam (2D) + - file: test-functions/circular-pipe-crack + title: Circular Pipe Crack - file: test-functions/damped-oscillator title: Damped Oscillator - file: test-functions/damped-oscillator-reliability diff --git a/docs/fundamentals/reliability.md b/docs/fundamentals/reliability.md index 702bee3..3a9b999 100644 --- a/docs/fundamentals/reliability.md +++ b/docs/fundamentals/reliability.md @@ -19,7 +19,8 @@ in the comparison of reliability analysis methods. | Name | Spatial Dimension | Constructor | |:-----------------------------------------------------------------------------------:|:-----------------:|:-------------------------------:| -| {ref}`2D Cantilever Beam ` | 2 | `CantileverBeam2D ` | +| {ref}`Cantilever Beam (2D) ` | 2 | `CantileverBeam2D ` | +| {ref}`Circular Pipe Crack ` | 2 | `CircularPipeCrack()` | | {ref}`Damped Oscillator Reliability ` | 8 | `DampedOscillatorReliability()` | | {ref}`Four-branch ` | 2 | `FourBranch()` | | {ref}`Gayton Hat ` | 2 | `GaytonHat()` | diff --git a/docs/references.bib b/docs/references.bib index 4c62c5e..6e0bab7 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -626,4 +626,14 @@ @Article{Du2004 doi = {10.2514/1.3877}, } +@InCollection{Verma2015, + author = {Ajit Kumar Verma and Srividya Ajit and Durga Rao Karanki}, + booktitle = {Springer Series in Reliability Engineering}, + publisher = {Springer London}, + title = {Structural reliability}, + year = {2015}, + pages = {257--292}, + doi = {10.1007/978-1-4471-6269-8_8}, +} + @Comment{jabref-meta: databaseType:bibtex;} diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index 219da96..cc28636 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -26,7 +26,8 @@ available in the current UQTestFuns, regardless of their typical applications. | {ref}`Bratley et al. (1992) B ` | M | `Bratley1992b()` | | {ref}`Bratley et al. (1992) C ` | M | `Bratley1992c()` | | {ref}`Bratley et al. (1992) D ` | M | `Bratley1992d()` | -| {ref}`2D Cantilever Beam ` | 2 | `CantileverBeam2D ` | +| {ref}`Cantilever Beam (2D) ` | 2 | `CantileverBeam2D ` | +| {ref}`Circular Pipe Crack ` | 2 | `CircularPipeCrack()` | | {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | | {ref}`Damped Oscillator Reliability ` | 8 | `DampedOscillatorReliability()` | | {ref}`Flood ` | 8 | `Flood()` | diff --git a/docs/test-functions/cantilever-beam-2d.md b/docs/test-functions/cantilever-beam-2d.md index 2bf5ba9..4952062 100644 --- a/docs/test-functions/cantilever-beam-2d.md +++ b/docs/test-functions/cantilever-beam-2d.md @@ -69,9 +69,9 @@ axs_0.plot_surface( antialiased=False, alpha=0.5 ) -axs_0.set_xlabel("$X_1$", fontsize=14) -axs_0.set_ylabel("$X_2$", fontsize=14) -axs_0.set_zlabel("$g$", fontsize=14) +axs_0.set_xlabel("$X_1$", fontsize=18) +axs_0.set_ylabel("$X_2$", fontsize=18) +axs_0.set_zlabel("$g$", fontsize=18) # Contour plot axs_1 = plt.subplot(132) @@ -81,12 +81,14 @@ cf = axs_1.contour( yy_2d.reshape(1000, 1000).T, levels=0, colors=["#ca0020"], + linewidths=[3.0], ) axs_1.set_xlim([lb_1, ub_1]) axs_1.set_ylim([lb_2, ub_2]) -axs_1.set_xlabel("$X_1$", fontsize=14) -axs_1.set_ylabel("$X_2$", fontsize=14) -axs_1.clabel(cf, inline=True, fontsize=14) +axs_1.set_xlabel("$x_1$", fontsize=18) +axs_1.set_ylabel("$x_2$", fontsize=18) +axs_1.tick_params(labelsize=16) +axs_1.clabel(cf, inline=True, fontsize=18) # Scatter plot axs_2 = plt.subplot(133) @@ -96,6 +98,7 @@ cf = axs_2.contour( yy_2d.reshape(1000, 1000).T, levels=0, colors=["#ca0020"], + linewidths=[3.0], ) axs_2.scatter( xx[idx_neg, 0], @@ -115,10 +118,11 @@ axs_2.scatter( ) axs_2.set_xlim([lb_1, ub_1]) axs_2.set_ylim([lb_2, ub_2]) -axs_2.set_xlabel("$X_1$", fontsize=14) -axs_2.set_ylabel("$X_2$", fontsize=14) -axs_2.clabel(cf, inline=True, fontsize=14) -axs_2.legend(fontsize=14, loc="upper right") +axs_2.set_xlabel("$x_1$", fontsize=18) +axs_2.set_ylabel("$x_2$", fontsize=18) +axs_2.tick_params(labelsize=16) +axs_2.clabel(cf, inline=True, fontsize=18) +axs_2.legend(fontsize=18, loc="upper right"); fig.tight_layout(pad=4.0); plt.gcf().set_dpi(150); diff --git a/docs/test-functions/circular-pipe-crack.md b/docs/test-functions/circular-pipe-crack.md new file mode 100644 index 0000000..77a23b8 --- /dev/null +++ b/docs/test-functions/circular-pipe-crack.md @@ -0,0 +1,228 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:circular-pipe-crack)= +# Circular Pipe Crack + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +The two-dimensional circular pipe crack reliability problem +was introduced in {cite}`Verma2015` and used, for instance, in {cite}`Li2018`. + +The plots of the function are shown below. The left plot shows the surface +plot of the performance function, the center plot shows the contour +plot with a single contour line at function value of $0.0$ (the limit-state +surface), and the right plot shows the same plot with $10^6$ sample points +overlaid. + +```{code-cell} ipython3 +:tags: [remove-input] + +my_fun = uqtf.CircularPipeCrack(rng_seed_prob_input=237324) +xx = my_fun.prob_input.get_sample(1000000) +yy = my_fun(xx) +idx_neg = yy <= 0.0 +idx_pos = yy > 0.0 + +lb_1 = my_fun.prob_input.marginals[0].lower +ub_1 = my_fun.prob_input.marginals[0].upper +lb_2 = my_fun.prob_input.marginals[1].lower +ub_2 = my_fun.prob_input.marginals[1].upper + +# Create 2-dimensional grid +xx_1 = np.linspace(lb_1, ub_1, 1000)[:, np.newaxis] +xx_2 = np.linspace(lb_2, ub_2, 1000)[:, np.newaxis] +mesh_2d = np.meshgrid(xx_1, xx_2) +xx_2d = np.array(mesh_2d).T.reshape(-1, 2) +yy_2d = my_fun(xx_2d) + +# --- Create the plot +fig = plt.figure(figsize=(15, 5)) + +# Surface +axs_0 = plt.subplot(131, projection='3d') +axs_0.plot_surface( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000,1000).T, + linewidth=0, + cmap="plasma", + antialiased=False, + alpha=0.5 +) +#axs_0.view_init(30, 135) +axs_0.set_xlabel("$x_1$", fontsize=18) +axs_0.set_ylabel("$x_2$", fontsize=18) +axs_0.set_zlabel("$g$", fontsize=18) + +# Contour plot +axs_1 = plt.subplot(132) +cf = axs_1.contour( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000, 1000).T, + levels=0, + colors=["#ca0020"], + linewidths=[3.0], +) +axs_1.set_xlim([lb_1, ub_1]) +axs_1.set_ylim([lb_2, ub_2]) +axs_1.set_xlabel("$x_1$", fontsize=18) +axs_1.set_ylabel("$x_2$", fontsize=18) +axs_1.tick_params(labelsize=16) +axs_1.clabel(cf, inline=True, fontsize=18) + +# Scatter plot +axs_2 = plt.subplot(133) +cf = axs_2.contour( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000, 1000).T, + levels=0, + colors=["#ca0020"], + linewidths=[3.0], +) +axs_2.scatter( + xx[idx_neg, 0], + xx[idx_neg, 1], + color="#ca0020", + marker=".", + s=30, + label="$g(x) \leq 0$" +) +axs_2.scatter( + xx[idx_pos, 0], + xx[idx_pos, 1], + color="#0571b0", + marker=".", + s=30, + label="$g(x) > 0$" +) +axs_2.set_xlim([lb_1, ub_1]) +axs_2.set_ylim([lb_2, ub_2]) +axs_2.set_xlabel("$x_1$", fontsize=18) +axs_2.set_ylabel("$x_2$", fontsize=18) +axs_2.tick_params(labelsize=16) +axs_2.clabel(cf, inline=True, fontsize=18) +axs_2.legend(fontsize=18, loc="lower right"); + +plt.gcf().tight_layout(pad=4.0) +plt.gcf().set_dpi(150); +``` + +## Test function instance + +To create a default instance of the test function: + +```{code-cell} ipython3 +my_testfun = uqtf.CircularPipeCrack() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +## Description + +The system under consideration is as a circular pipe with a circumferential +through-wall crack under a bending moment. +The performance function is analytically defined as follows[^location]: + +$$ +g(\boldsymbol{x}; \boldsymbol{p}) = 4 t \sigma_f R^2 \left( \cos{(\frac{\theta}{2})} - \frac{1}{2} \sin{(\theta)} \right) - M +$$ + +where $\boldsymbol{x} = \{ \sigma_f, \theta \}$ is the two-dimensional vector of +input variables probabilistically defined further below; +and $\boldsymbol{p} = \{ t, R, M \}$ is the vector of parameters. + +The failure event and the failure probability are defined as +$g(\boldsymbol{x}) \leq 0$ and $\mathbb{P}[g(\boldsymbol{x}) \leq 0]$, +respectively. + +## Probabilistic input + +Based on {cite}`Echard2013`, the probabilistic input model for +the test function consists of two independent standard normal random variables +(see the table below). + +```{code-cell} ipython3 +my_testfun.prob_input +``` + +## Parameters + +From {cite}`Verma2015`, the values of the parameters are as follows: + +| Parameter | Value | Description | +|:---------:|:----------------------:|-----------------------------------------| +| $t$ | $3.377 \times 10^{-1}$ | Radius of the pipe $[\mathrm{m}]$ | +| $R$ | $3.377 \times 10^{-2}$ | Thickness of the pipe $[\mathrm{m}]$ | +| $M$ | $3.0$ | Applied bending moment $[\mathrm{MNm}]$ | + +## Reference results + +This section provides several reference results of typical UQ analyses involving +the test function. + +### Sample histogram + +Shown below is the histogram of the output based on $10^6$ random points: + +```{code-cell} ipython3 +:tags: [hide-input] + +xx_test = my_testfun.prob_input.get_sample(1000000) +yy_test = my_testfun(xx_test) +idx_pos = yy_test > 0 +idx_neg = yy_test <= 0 + +hist_pos = plt.hist(yy_test, bins="auto", color="#0571b0") +plt.hist(yy_test[idx_neg], bins=hist_pos[1], color="#ca0020") +plt.axvline(0, linewidth=1.0, color="#ca0020") + +plt.grid() +plt.ylabel("Counts [-]") +plt.xlabel("$\mathcal{M}(\mathbf{X})$") +plt.gcf().set_dpi(150); +``` + +### Failure probability ($P_f$) + +Some reference values for the failure probability $P_f$ from the literature +are summarized in the table below. + +| Method | $N$ | $\hat{P}_f$ | $\mathrm{CoV}[\hat{P}_f]$ | Source | +|:------------:|:------:|:-----------------------:|:-------------------------:|:-----------------:| +| {term}`MCS` | $10^6$ | $3.4353 \times 10^{-2}$ | — | {cite}`Li2018` | +| {term}`FORM` | $6$ | $3.3065 \times 10^{-2}$ | — | {cite}`Verma2015` | +| {term}`FORM` | $9$ | $3.3065 \times 10^{-2}$ | — | {cite}`Li2018` | +| {term}`SORM` | $14$ | $3.4211 \times 10^{-2}$ | — | {cite}`Li2018` | +| {term}`SSRM` | $7$ | $3.4347 \times 10^{-2}$ | — | {cite}`Li2018` | + + +## References + +```{bibliography} +:style: unsrtalpha +:filter: docname in docnames +``` + +[^location]: see Example 5, p. 266 in {cite}`Verma2015`. \ No newline at end of file diff --git a/docs/test-functions/four-branch.md b/docs/test-functions/four-branch.md index 3786375..67f7a16 100644 --- a/docs/test-functions/four-branch.md +++ b/docs/test-functions/four-branch.md @@ -55,7 +55,7 @@ xx_2d = np.array(mesh_2d).T.reshape(-1, 2) yy_2d = my_fun(xx_2d) # --- Create the plot -fig = plt.figure(figsize=(10, 5)) +fig = plt.figure(figsize=(15, 5)) # Surface axs_0 = plt.subplot(131, projection='3d') @@ -68,9 +68,9 @@ axs_0.plot_surface( antialiased=False, alpha=0.5 ) -axs_0.set_xlabel("$x_1$", fontsize=14) -axs_0.set_ylabel("$x_2$", fontsize=14) -axs_0.set_zlabel("$g$", fontsize=14) +axs_0.set_xlabel("$x_1$", fontsize=18) +axs_0.set_ylabel("$x_2$", fontsize=18) +axs_0.set_zlabel("$g$", fontsize=18) # Contour plot axs_1 = plt.subplot(132) @@ -80,13 +80,15 @@ cf = axs_1.contour( yy_2d.reshape(1000, 1000).T, levels=0, colors=["#ca0020"], + linewidths=[3.0], ) axs_1.set_xlim([lb_1, ub_1]) axs_1.set_ylim([lb_2, ub_2]) -axs_1.set_xlabel("$x_1$", fontsize=14) -axs_1.set_ylabel("$x_2$", fontsize=14) +axs_1.set_xlabel("$x_1$", fontsize=18) +axs_1.set_ylabel("$x_2$", fontsize=18) +axs_1.tick_params(labelsize=16) axs_1.set_aspect("equal", "box") -axs_1.clabel(cf, inline=True, fontsize=14) +axs_1.clabel(cf, inline=True, fontsize=18) # Scatter plot axs_2 = plt.subplot(133) @@ -96,6 +98,7 @@ cf = axs_2.contour( yy_2d.reshape(1000, 1000).T, levels=0, colors=["#ca0020"], + linewidths=[3.0], ) axs_2.scatter( xx[idx_neg, 0], @@ -115,11 +118,12 @@ axs_2.scatter( ) axs_2.set_xlim([lb_1, ub_1]) axs_2.set_ylim([lb_2, ub_2]) -axs_2.set_xlabel("$x_1$", fontsize=14) -axs_2.set_ylabel("$x_2$", fontsize=14) +axs_2.set_xlabel("$x_1$", fontsize=18) +axs_2.set_ylabel("$x_2$", fontsize=18) +axs_2.tick_params(labelsize=16) axs_2.set_aspect("equal", "box") -axs_2.clabel(cf, inline=True, fontsize=14) -axs_2.legend(fontsize=14, loc="lower right"); +axs_2.clabel(cf, inline=True, fontsize=18) +axs_2.legend(fontsize=18, loc="lower right"); plt.gcf().tight_layout(pad=4.0) plt.gcf().set_dpi(150); diff --git a/docs/test-functions/gayton-hat.md b/docs/test-functions/gayton-hat.md index 017f395..dc06735 100644 --- a/docs/test-functions/gayton-hat.md +++ b/docs/test-functions/gayton-hat.md @@ -52,7 +52,7 @@ xx_2d = np.array(mesh_2d).T.reshape(-1, 2) yy_2d = my_fun(xx_2d) # --- Create the plot -fig = plt.figure(figsize=(10, 5)) +fig = plt.figure(figsize=(15, 5)) # Surface axs_0 = plt.subplot(131, projection='3d') @@ -65,9 +65,10 @@ axs_0.plot_surface( antialiased=False, alpha=0.5 ) -axs_0.set_xlabel("$x_1$", fontsize=14) -axs_0.set_ylabel("$x_2$", fontsize=14) -axs_0.set_zlabel("$g$", fontsize=14) +axs_0.view_init(30, -45) +axs_0.set_xlabel("$x_1$", fontsize=18) +axs_0.set_ylabel("$x_2$", fontsize=18) +axs_0.set_zlabel("$g$", fontsize=18) # Contour plot axs_1 = plt.subplot(132) @@ -77,13 +78,15 @@ cf = axs_1.contour( yy_2d.reshape(1000, 1000).T, levels=0, colors=["#ca0020"], + linewidths=[3.0], ) axs_1.set_xlim([lb_1, ub_1]) axs_1.set_ylim([lb_2, ub_2]) -axs_1.set_xlabel("$x_1$", fontsize=14) -axs_1.set_ylabel("$x_2$", fontsize=14) +axs_1.set_xlabel("$x_1$", fontsize=18) +axs_1.set_ylabel("$x_2$", fontsize=18) +axs_1.tick_params(labelsize=16) axs_1.set_aspect("equal", "box") -axs_1.clabel(cf, inline=True, fontsize=14) +axs_1.clabel(cf, inline=True, fontsize=18) # Scatter plot axs_2 = plt.subplot(133) @@ -93,6 +96,7 @@ cf = axs_2.contour( yy_2d.reshape(1000, 1000).T, levels=0, colors=["#ca0020"], + linewidths=[3.0], ) axs_2.scatter( xx[idx_neg, 0], @@ -112,11 +116,12 @@ axs_2.scatter( ) axs_2.set_xlim([lb_1, ub_1]) axs_2.set_ylim([lb_2, ub_2]) -axs_2.set_xlabel("$x_1$", fontsize=14) -axs_2.set_ylabel("$x_2$", fontsize=14) +axs_2.set_xlabel("$x_1$", fontsize=18) +axs_2.set_ylabel("$x_2$", fontsize=18) +axs_2.tick_params(labelsize=16) axs_2.set_aspect("equal", "box") -axs_2.clabel(cf, inline=True, fontsize=14) -axs_2.legend(fontsize=14, loc="lower right"); +axs_2.clabel(cf, inline=True, fontsize=18) +axs_2.legend(fontsize=18, loc="lower right"); plt.gcf().tight_layout(pad=4.0) plt.gcf().set_dpi(150); diff --git a/docs/test-functions/hyper-sphere.md b/docs/test-functions/hyper-sphere.md index 6389593..7d107e0 100644 --- a/docs/test-functions/hyper-sphere.md +++ b/docs/test-functions/hyper-sphere.md @@ -52,7 +52,7 @@ xx_2d = np.array(mesh_2d).T.reshape(-1, 2) yy_2d = my_fun(xx_2d) # --- Create the plot -fig = plt.figure(figsize=(10, 5)) +fig = plt.figure(figsize=(15, 5)) # Surface axs_0 = plt.subplot(131, projection='3d') @@ -65,9 +65,9 @@ axs_0.plot_surface( antialiased=False, alpha=0.5 ) -axs_0.set_xlabel("$x_1$", fontsize=14) -axs_0.set_ylabel("$x_2$", fontsize=14) -axs_0.set_zlabel("$g$", fontsize=14) +axs_0.set_xlabel("$x_1$", fontsize=18) +axs_0.set_ylabel("$x_2$", fontsize=18) +axs_0.set_zlabel("$g$", fontsize=18) # Contour plot axs_1 = plt.subplot(132) @@ -77,13 +77,15 @@ cf = axs_1.contour( yy_2d.reshape(1000, 1000).T, levels=0, colors=["#ca0020"], + linewidths=[3.0], ) axs_1.set_xlim([lb_1, ub_1]) axs_1.set_ylim([lb_2, ub_2]) -axs_1.set_xlabel("$x_1$", fontsize=14) -axs_1.set_ylabel("$x_2$", fontsize=14) +axs_1.set_xlabel("$x_1$", fontsize=18) +axs_1.set_ylabel("$x_2$", fontsize=18) +axs_1.tick_params(labelsize=16) axs_1.set_aspect("equal", "box") -axs_1.clabel(cf, inline=True, fontsize=14) +axs_1.clabel(cf, inline=True, fontsize=18) # Scatter plot axs_2 = plt.subplot(133) @@ -93,6 +95,7 @@ cf = axs_2.contour( yy_2d.reshape(1000, 1000).T, levels=0, colors=["#ca0020"], + linewidths=[3.0], ) axs_2.scatter( xx[idx_neg, 0], @@ -112,11 +115,12 @@ axs_2.scatter( ) axs_2.set_xlim([lb_1, ub_1]) axs_2.set_ylim([lb_2, ub_2]) -axs_2.set_xlabel("$x_1$", fontsize=14) -axs_2.set_ylabel("$x_2$", fontsize=14) +axs_2.set_xlabel("$x_1$", fontsize=18) +axs_2.set_ylabel("$x_2$", fontsize=18) +axs_2.tick_params(labelsize=16) axs_2.set_aspect("equal", "box") -axs_2.clabel(cf, inline=True, fontsize=14) -axs_2.legend(fontsize=14, loc="lower right"); +axs_2.clabel(cf, inline=True, fontsize=18) +axs_2.legend(fontsize=18, loc="lower right"); plt.gcf().tight_layout(pad=4.0) plt.gcf().set_dpi(150); diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index d324353..33cfc91 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -5,6 +5,7 @@ from .borehole import Borehole from .bratley1992 import Bratley1992a, Bratley1992b, Bratley1992c, Bratley1992d from .cantilever_beam_2d import CantileverBeam2D +from .circular_pipe_crack import CircularPipeCrack from .damped_oscillator import DampedOscillator, DampedOscillatorReliability from .flood import Flood from .forrester import Forrester2008 @@ -35,6 +36,7 @@ "Bratley1992c", "Bratley1992d", "CantileverBeam2D", + "CircularPipeCrack", "DampedOscillator", "DampedOscillatorReliability", "Flood", diff --git a/src/uqtestfuns/test_functions/circular_pipe_crack.py b/src/uqtestfuns/test_functions/circular_pipe_crack.py new file mode 100644 index 0000000..f92312a --- /dev/null +++ b/src/uqtestfuns/test_functions/circular_pipe_crack.py @@ -0,0 +1,117 @@ +""" +Module with an implementation of the circular pipe crack reliability problem. + +The two-dimensional reliability problem was introduced in [1] and used, +for instance, in [2]. + +The system under consideration is as a circular pipe with a circumferential +through-wall crack under a bending moment. +If the value of the performance function is negative, +then the system is in failed state. + +References +---------- +1. A. K. Verma, S. Ajit, and D. R. Karanki, “Structural Reliability,” + in Reliability and Safety Engineering, in Springer Series + in Reliability Engineering. London: Springer London, 2016, pp. 257–292. + DOI: 10.1007/978-1-4471-6269-8_8 +2. X. Li, C. Gong, L. Gu, W. Gao, Z. Jing, and H. Su, “A sequential surrogate + method for reliability analysis based on radial basis function,” + Structural Safety, vol. 73, pp. 42–53, 2018. + DOI: 10.1016/j.strusafe.2018.02.005 +""" +import numpy as np + +from typing import Tuple + +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecFixDim +from ..core.uqtestfun_abc import UQTestFunABC + + +__all__ = ["CircularPipeCrack"] + + +AVAILABLE_INPUT_SPECS = { + "Verma2015": ProbInputSpecFixDim( + name="CircularPipeCrack-Verma2015", + description=( + "Probabilistic input model for the circular pipe crack problem " + "from Verma et al. (2015)" + ), + marginals=[ + UnivDistSpec( + name="sigma_f", + distribution="normal", + parameters=[301.079, 14.78], + description="flow stress [MNm]", + ), + UnivDistSpec( + name="theta", + distribution="normal", + parameters=[0.503, 0.049], + description="half crack angle [-]", + ), + ], + copulas=None, + ), +} + +AVAILABLE_PARAMETERS = { + "Verma2016": ( + 3.377e-1, # Radius of the pipe [m] + 3.377e-2, # Thickness of the pipe [m] + 3.0, # Applied bending moment [Nm] + ), +} + + +def evaluate( + xx: np.ndarray, parameters: Tuple[float, float, float] +) -> np.ndarray: + """Evaluate the circular pipe crack reliability on a set of input values. + + Parameters + ---------- + xx : np.ndarray + A two-dimensional input values given by N-by-2 arrays + where N is the number of input values. + parameters : Tuple[float, float, float] + The parameters of the test function, namely (and in the following + order) the radius of the pipe, the thickness of the pipe, + and the applied bending moment. + + Returns + ------- + np.ndarray + The performance evaluation of the circular pipe reliability. + If negative, then the system is in failed state. + The output is a one-dimensional array of length N. + """ + # Get parameters + pipe_radius, pipe_thickness, bending_moment = parameters + + # NOTE: Convert the flow stress from [MNm] to [Nm] + yy = ( + 4 + * pipe_thickness + * xx[:, 0] + * pipe_radius**2 + * (np.cos(xx[:, 1] / 2) - 0.5 * np.sin(xx[:, 1])) + - bending_moment + ) + + return yy + + +class CircularPipeCrack(UQTestFunABC): + """A concrete implementation of the circular pipe crack problem.""" + + _tags = ["reliability"] + _description = ( + "Circular pipe under bending moment from Verma et al. (2015)" + ) + _available_inputs = AVAILABLE_INPUT_SPECS + _available_parameters = AVAILABLE_PARAMETERS + _default_spatial_dimension = 2 + + eval_ = staticmethod(evaluate) From 950de4f8645490dea6568d40ba534afb522f8705 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Mon, 3 Jul 2023 17:07:12 +0200 Subject: [PATCH 42/73] Add a brief introduction to reliability analysis - Minor changes across documentation of reliability analysis test functions. --- docs/fundamentals/reliability.md | 177 ++++++++++++++++++ docs/getting-started/creating-a-built-in.md | 2 + docs/references.bib | 8 + docs/test-functions/cantilever-beam-2d.md | 20 +- docs/test-functions/circular-pipe-crack.md | 14 +- .../damped-oscillator-reliability.md | 7 +- docs/test-functions/four-branch.md | 9 +- docs/test-functions/gayton-hat.md | 4 +- docs/test-functions/hyper-sphere.md | 4 +- docs/test-functions/sobol-g.md | 22 +-- .../test_functions/cantilever_beam_2d.py | 4 +- .../test_functions/circular_pipe_crack.py | 2 +- .../test_functions/damped_oscillator.py | 6 +- src/uqtestfuns/test_functions/four_branch.py | 2 +- src/uqtestfuns/test_functions/gayton_hat.py | 2 +- src/uqtestfuns/test_functions/hyper_sphere.py | 2 +- src/uqtestfuns/test_functions/sobol_g.py | 8 +- .../test_functions/speed_reducer_shaft.py | 2 +- 18 files changed, 243 insertions(+), 52 deletions(-) diff --git a/docs/fundamentals/reliability.md b/docs/fundamentals/reliability.md index 3a9b999..3cef637 100644 --- a/docs/fundamentals/reliability.md +++ b/docs/fundamentals/reliability.md @@ -38,3 +38,180 @@ import uqtestfuns as uqtf uqtf.list_functions(tag="reliability") ``` + +## About reliability analysis + +Consider a system whose performance is defined by a _performance function_[^lsf] +$g$ whose values, in turn, depend on: + +- $\boldsymbol{x}_p$: the (uncertain) input variables of the underlying computational model $\mathcal{M}$ +- $\boldsymbol{x}_s$: additional (uncertain) input variables that affects + the performance of the system, but not part of inputs to $\mathcal{M}$ +- $\boldsymbol{p}$: an additional set of _deterministic_ parameters of the system + +Combining these variables and parameters as an input to the performance function $g$: + +$$ +g(\boldsymbol{x}; \boldsymbol{p}) = g(\mathcal{M}(\boldsymbol{x}_p), \boldsymbol{x}_s; \boldsymbol{p}). +$$ + +where $\boldsymbol{x} = \{ \boldsymbol{x}_p, \boldsymbol{x}_s \}$. + +The system is said to be in _failure state_ if and only if $g(\boldsymbol{x}; \boldsymbol{p}) \leq 0$; +the set of all values $\{ \boldsymbol{x}, \boldsymbol{p} \}$ such that $g(\boldsymbol{x}; \boldsymbol{p}) \leq 0$ +is called the _failure domain_. + +Conversely, the system is said to be in _safe state_ +if and only if $g(\boldsymbol{x}; \boldsymbol{p}) > 0$; +the set of all values $\{ \boldsymbol{x}, \boldsymbol{p} \}$ +such that $g(\boldsymbol{x}; \boldsymbol{p}) > 0$ +is called the _safe domain_. + +```{note} +For example, taken in the context of nuclear engineering, +a performance function may be defined for the fuel cladding temperature. +The maximum temperature during a transient is computed using a computation model. +A hypothetical regulatory limit of the maximum temperature is provided +but with an uncertainty considering the variability in the high-temperature +performance within the cladding population. +The system is in failure state if the maximum temperature during a transient +as computed by the model exceeds the regulatory limit; vice versa, +the system is in safe state if the maximum temperature +does not exceed the regulatory limit. +``` + +_Reliability analysis_[^rare-event] concerns with estimating +the failure probability of a system with a given performance function $g$. +For a given joint probability density function (PDF) $f_{\boldsymbol{X}}$ +of the uncertain input variables $\boldsymbol{X} = \{ \boldsymbol{X}_p, \boldsymbol{X}_s \}$, +the failure probability $P_f$ of the system is defined as follows {cite}`Sudret2012, Verma2015`: + +$$ +P_f \equiv \mathbb{P}[g(\boldsymbol{X}; \boldsymbol{p}) \leq 0] = \int_{\{ \boldsymbol{x} | g(\boldsymbol{x}; \boldsymbol{p}) \leq 0 \}} f_{\boldsymbol{X}} (\boldsymbol{x}) \, \; d\boldsymbol{x}. +$$ + +Evaluating the above integral is, in general, non-trivial because the domain +of integration is only provided implicitly and the number of dimensions may +be large. + +A series of plots below illustrates the reliability analysis problem +in two dimensions. +The left plot shows the limit-state surface +(i.e., $\boldsymbol{x}$ where $g(\boldsymbol{x}) = 0$) in the +input parameter space. +In the middle plot, $10^6$ sample points are randomly generated +and overlaid. +The red points (resp. blue points) indicate that the points fall in the +failure domain (resp. safe domain). +Finally, the histogram on the right shows the failure probability, i.e., +the area under the histogram such that $g(\boldsymbol{x}) \leq 0$. +The task of reliability analysis methods is to estimate the failure probability +accurately with as few function/model evaluations as possible. + +```{code-cell} ipython3 +:tags: [remove-input] + +import numpy as np +import matplotlib.pyplot as plt + +my_fun = uqtf.CircularPipeCrack(rng_seed_prob_input=237324) +xx = my_fun.prob_input.get_sample(1000000) +yy = my_fun(xx) +idx_neg = yy <= 0.0 +idx_pos = yy > 0.0 + +lb_1 = my_fun.prob_input.marginals[0].lower +ub_1 = my_fun.prob_input.marginals[0].upper +lb_2 = my_fun.prob_input.marginals[1].lower +ub_2 = my_fun.prob_input.marginals[1].upper + +# Create 2-dimensional grid +xx_1 = np.linspace(lb_1, ub_1, 1000)[:, np.newaxis] +xx_2 = np.linspace(lb_2, ub_2, 1000)[:, np.newaxis] +mesh_2d = np.meshgrid(xx_1, xx_2) +xx_2d = np.array(mesh_2d).T.reshape(-1, 2) +yy_2d = my_fun(xx_2d) + +# --- Create the plot +fig = plt.figure(figsize=(15, 5)) + +# Contour plot +axs_1 = plt.subplot(131) +cf = axs_1.contour( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000, 1000).T, + levels=0, + colors=["#ca0020"], + linewidths=[3.0], +) +axs_1.set_xlim([lb_1, ub_1]) +axs_1.set_ylim([lb_2, ub_2]) +axs_1.set_xlabel("$x_1$", fontsize=18) +axs_1.set_ylabel("$x_2$", fontsize=18) +axs_1.tick_params(labelsize=16) +axs_1.clabel(cf, inline=True, fontsize=18) + +# Scatter plot +axs_2 = plt.subplot(132) +cf = axs_2.contour( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000, 1000).T, + levels=0, + colors=["#ca0020"], + linewidths=[3.0], +) +axs_2.scatter( + xx[idx_neg, 0], + xx[idx_neg, 1], + color="#ca0020", + marker=".", + s=30, + label="$g(x) \leq 0$" +) +axs_2.scatter( + xx[idx_pos, 0], + xx[idx_pos, 1], + color="#0571b0", + marker=".", + s=30, + label="$g(x) > 0$" +) +axs_2.set_xlim([lb_1, ub_1]) +axs_2.set_ylim([lb_2, ub_2]) +axs_2.set_xlabel("$x_1$", fontsize=18) +axs_2.set_ylabel("$x_2$", fontsize=18) +axs_2.tick_params(labelsize=16) +axs_2.clabel(cf, inline=True, fontsize=18) +axs_2.legend(fontsize=18, loc="lower right"); + +# Histogram +axs_3 = plt.subplot(133) +xx_test = my_fun.prob_input.get_sample(1000000) +yy_test = my_fun(xx_test) +idx_pos = yy_test > 0 +idx_neg = yy_test <= 0 + +hist_pos = axs_3.hist(yy_test, bins="auto", color="#0571b0") +axs_3.hist(yy_test[idx_neg], bins=hist_pos[1], color="#ca0020") +axs_3.axvline(0, linewidth=1.0, color="#ca0020") + +axs_3.grid() +axs_3.set_ylabel("Counts [-]", fontsize=18) +axs_3.set_xlabel("$g(\mathbf{X})$", fontsize=18) + +plt.gcf().tight_layout(pad=4.0) +plt.gcf().set_dpi(150); +``` + +## References + +```{bibliography} +:style: unsrtalpha +:filter: docname in docnames +``` + +[^lsf]: also called _limit-state function_ + +[^rare-event]: also called rare-events estimation \ No newline at end of file diff --git a/docs/getting-started/creating-a-built-in.md b/docs/getting-started/creating-a-built-in.md index e856a95..853bd30 100644 --- a/docs/getting-started/creating-a-built-in.md +++ b/docs/getting-started/creating-a-built-in.md @@ -32,6 +32,8 @@ import uqtestfuns as uqtf To list all the test functions currently available: ```{code-cell} ipython3 +:tags: ["output_scroll"] + uqtf.list_functions() ``` diff --git a/docs/references.bib b/docs/references.bib index 6e0bab7..61db6ee 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -636,4 +636,12 @@ @InCollection{Verma2015 doi = {10.1007/978-1-4471-6269-8_8}, } +@InProceedings{Sudret2012, + author = {Bruno Sudret}, + booktitle = {Proceedings of the 5th Asian-Pacific Symposium on Structural Reliability and its Applications}, + title = {Meta-models for structural reliability and uncertainty quantification}, + year = {2012}, + doi = {10.3850/978-981-07-2219-7_p321}, +} + @Comment{jabref-meta: databaseType:bibtex;} diff --git a/docs/test-functions/cantilever-beam-2d.md b/docs/test-functions/cantilever-beam-2d.md index 4952062..7666a22 100644 --- a/docs/test-functions/cantilever-beam-2d.md +++ b/docs/test-functions/cantilever-beam-2d.md @@ -145,15 +145,15 @@ print(my_testfun) ## Description The problem consists of a cantilever beam with a rectangular cross-section -subjected to uniformly distributed loading. The maximum deflection at the free -end is taken to be the performance criterion and the performance function -reads[^location]: +subjected to a uniformly distributed loading. +The maximum deflection at the free end is taken to be the performance criterion +and the performance function reads[^location]: $$ g(\boldsymbol{x}) = \frac{l}{325} - \frac{w b l^4}{8 E I}, $$ -where $I$, the moment inertia of the cross-section, is given as: +where $I$, the moment inertia of the cross-section, is given as follows: $$ I = \frac{b h^3}{12}. @@ -163,7 +163,7 @@ By plugging in the above expression to the performance function, the following expression for the performance function is obtained: $$ -g(\boldsymbol{x}) = \frac{l}{325} - \frac{12 l^4 w}{8 E h^3}, +g(\boldsymbol{x}; \boldsymbol{p}) = \frac{l}{325} - \frac{12 l^4 w}{8 E h^3}, $$ where $\boldsymbol{x} = \{ w, h \}$ is the two-dimensional vector of @@ -171,12 +171,14 @@ input variables, namely the load per unit area and the depth of the cross-section. These inputs are probabilistically defined further below. -The parameters of the test function, namely $E$ (the beam's modulus -of elasticity) and $l$ (the span of the beam) are set to +The parameters of the test function $\boldsymbol{p} = \{ E, l \}$, +namely the beam's modulus of elasticity ($E$) +and the span of the beam ($l$) are set to $2.6 \times 10^{4} \; \mathrm{[MPa]}$ and $6.0 \; \mathrm{[m]}$, respectively. -The failure event and the failure probability are defined as -$g(\boldsymbol{x}) \leq 0$ and $\mathbb{P}[g(\boldsymbol{X}) \leq 0]$, +The failure state and the failure probability are defined as +$g(\boldsymbol{x}; \boldsymbol{p}) \leq 0$ +and $\mathbb{P}[g(\boldsymbol{X}; \boldsymbol{p}) \leq 0]$, respectively. ## Probabilistic input diff --git a/docs/test-functions/circular-pipe-crack.md b/docs/test-functions/circular-pipe-crack.md index 77a23b8..4f345a9 100644 --- a/docs/test-functions/circular-pipe-crack.md +++ b/docs/test-functions/circular-pipe-crack.md @@ -146,20 +146,20 @@ through-wall crack under a bending moment. The performance function is analytically defined as follows[^location]: $$ -g(\boldsymbol{x}; \boldsymbol{p}) = 4 t \sigma_f R^2 \left( \cos{(\frac{\theta}{2})} - \frac{1}{2} \sin{(\theta)} \right) - M +g(\boldsymbol{x}; \boldsymbol{p}) = 4 t \sigma_f R^2 \left( \cos{\left(\frac{\theta}{2}\right)} - \frac{1}{2} \sin{(\theta)} \right) - M $$ where $\boldsymbol{x} = \{ \sigma_f, \theta \}$ is the two-dimensional vector of input variables probabilistically defined further below; -and $\boldsymbol{p} = \{ t, R, M \}$ is the vector of parameters. +and $\boldsymbol{p} = \{ t, R, M \}$ is the vector of deterministic parameters. -The failure event and the failure probability are defined as -$g(\boldsymbol{x}) \leq 0$ and $\mathbb{P}[g(\boldsymbol{x}) \leq 0]$, -respectively. +The failure state and the failure probability are defined as +$g(\boldsymbol{x}; \boldsymbol{p}) \leq 0$ +and $\mathbb{P}[g(\boldsymbol{X}; \boldsymbol{p}) \leq 0]$, respectively. ## Probabilistic input -Based on {cite}`Echard2013`, the probabilistic input model for +Based on {cite}`Verma2015`, the probabilistic input model for the test function consists of two independent standard normal random variables (see the table below). @@ -200,7 +200,7 @@ plt.axvline(0, linewidth=1.0, color="#ca0020") plt.grid() plt.ylabel("Counts [-]") -plt.xlabel("$\mathcal{M}(\mathbf{X})$") +plt.xlabel("$g(\mathbf{X})$") plt.gcf().set_dpi(150); ``` diff --git a/docs/test-functions/damped-oscillator-reliability.md b/docs/test-functions/damped-oscillator-reliability.md index b45b0b3..9aae1b1 100644 --- a/docs/test-functions/damped-oscillator-reliability.md +++ b/docs/test-functions/damped-oscillator-reliability.md @@ -69,10 +69,11 @@ $$ where $\boldsymbol{x} = \{ M_p, M_s, K_p, K_s, \zeta_p, \zeta_s, S_0, F_s \}$ is the eight-dimensional vector of input variables probabilistically defined -further below and $p$ is the parameter of the function (i.e., the peak factor). +further below and $p$ is a deterministic parameter of the function +(i.e., the peak factor). -The failure event and the failure probability are defined as -$g(\boldsymbol{x}) \leq 0$ and $\mathbb{P}[g(\boldsymbol{X}) \leq 0]$, +The failure state and the failure probability are defined as +$g(\boldsymbol{x}; p) \leq 0$ and $\mathbb{P}[g(\boldsymbol{X}; p) \leq 0]$, respectively. ## Probabilistic input diff --git a/docs/test-functions/four-branch.md b/docs/test-functions/four-branch.md index 67f7a16..d38f7d7 100644 --- a/docs/test-functions/four-branch.md +++ b/docs/test-functions/four-branch.md @@ -158,11 +158,12 @@ g(\boldsymbol{x}; p) = \min \begin{Bmatrix} $$ where $\boldsymbol{x} = \{ x_1, x_2 \}$ is the two-dimensional vector of -input variables probabilistically defined further below and $p$ is the -parameter of the test function (see the corresponding section below). +input variables probabilistically defined further below +and $p$ is a deterministic parameter of the test function +(see the corresponding section below). -The failure event and the failure probability are defined as -$g(\boldsymbol{x}) \leq 0$ and $\mathbb{P}[g(\boldsymbol{x}) \leq 0]$, +The failure state and the failure probability are defined as +$g(\boldsymbol{x}; p) \leq 0$ and $\mathbb{P}[g(\boldsymbol{X}; p) \leq 0]$, respectively. ## Probabilistic input diff --git a/docs/test-functions/gayton-hat.md b/docs/test-functions/gayton-hat.md index dc06735..bdf4725 100644 --- a/docs/test-functions/gayton-hat.md +++ b/docs/test-functions/gayton-hat.md @@ -153,8 +153,8 @@ $$ where $\boldsymbol{x} = \{ u_1, u_2 \}$ is the two-dimensional vector of input variables probabilistically defined further below. -The failure event and the failure probability are defined as -$g(\boldsymbol{x}) \leq 0$ and $\mathbb{P}[g(\boldsymbol{x}) \leq 0]$, +The failure state and the failure probability are defined as +$g(\boldsymbol{x}) \leq 0$ and $\mathbb{P}[g(\boldsymbol{X}) \leq 0]$, respectively. ## Probabilistic input diff --git a/docs/test-functions/hyper-sphere.md b/docs/test-functions/hyper-sphere.md index 7d107e0..992be67 100644 --- a/docs/test-functions/hyper-sphere.md +++ b/docs/test-functions/hyper-sphere.md @@ -152,8 +152,8 @@ $$ where $\boldsymbol{x} = \{ x_1, x_2 \}$ is the two-dimensional vector of input variables probabilistically defined further below. -The failure event and the failure probability are defined as -$g(\boldsymbol{x}) \leq 0$ and $\mathbb{P}[g(\boldsymbol{x}) \leq 0]$, +The failure state and the failure probability are defined as +$g(\boldsymbol{x}) \leq 0$ and $\mathbb{P}[g(\boldsymbol{X}) \leq 0]$, respectively. ## Probabilistic input diff --git a/docs/test-functions/sobol-g.md b/docs/test-functions/sobol-g.md index d31ea96..3a233e6 100644 --- a/docs/test-functions/sobol-g.md +++ b/docs/test-functions/sobol-g.md @@ -158,17 +158,17 @@ input variable. There are several sets of parameters used in the literature as shown in the table below. -| No. | Value | Keyword | Source | Remark | -|:---:|:--------------------------------------------------------------:|:------------------------:|:--------------------------------------------------------------:|:----------------------------------------------------------------------------------------------:| -| 1 | $a_1 = \ldots = a_M = 0$ | `Saltelli1995-1` | {cite}`Saltelli1995` (Example 1) (also {cite}`Bratley1992`) | All input variables are equally important | -| 2 | $a_1 = a_2 = 0.0$
$a_3 = 3$
$a_3 = \ldots = a_M = 9.9$ | `Saltelli1995-2` | {cite}`Saltelli1995` (Example 2) | The first two are important, the next is moderately important, and the rest is non-influential | -| 3 | $a_m = \frac{m - 1}{2.0}$
$1 \leq m \leq M$ | `Saltelli1995-3` | {cite}`Saltelli1995` (Example 3) (also {cite}`Crestaux2007` ) | The most important input is the first one, the least is the last one | -| 4 | $a_1 = \ldots = a_M = 0.01$ | `Sobol1998-1` | {cite}`Sobol1998` (choice 1) | The supremum of the function grows exponentially at $2^M$ | -| 5 | $a_1 = \ldots = a_M = 1.0$ | `Sobol1998-2` | {cite}`Sobol1998` (choice 2) | The supremum of the function grows exponentially at $1.5^M$ | -| 6 | $a_m = m$
$\, 1 \leq m \leq M$ | `Sobol1998-3` | {cite}`Sobol1998` (choice 3) | The supremum of the function grows linearly at $1 + \frac{M}{2}$ | -| 7 | $a_m = m^2$
$1 \leq m \leq M$ | `Sobol1998-4` | {cite}`Sobol1998` (choice 4) | The supremum is bounded at $1.0$ | -| 8 | $a_1 = a_2 = 0.0$
$a_3 = \ldots = a_M = 6.52$ | `Kucherenko2011-2a` | {cite}`Kucherenko2011` (Problem 2A) | Originally, $M = 100$ | -| 9 | $a_m = 6,52$
$1 \leq m \leq M$ | `Kucherenko2011-3b` | {cite}`Kucherenko2011` (Problem 3B) | | +| No. | Value | Keyword | Source | Remark | +|:---:|:--------------------------------------------------------------:|:--------------------------:|:--------------------------------------------------------------:|:----------------------------------------------------------------------------------------------:| +| 1 | $a_1 = \ldots = a_M = 0$ | `Saltelli1995-1` | {cite}`Saltelli1995` (Example 1) (also {cite}`Bratley1992`) | All input variables are equally important | +| 2 | $a_1 = a_2 = 0.0$
$a_3 = 3$
$a_3 = \ldots = a_M = 9.9$ | `Saltelli1995-2` | {cite}`Saltelli1995` (Example 2) | The first two are important, the next is moderately important, and the rest is non-influential | +| 3 | $a_m = \frac{m - 1}{2.0}$
$1 \leq m \leq M$ | `Saltelli1995-3` (default) | {cite}`Saltelli1995` (Example 3) (also {cite}`Crestaux2007` ) | The most important input is the first one, the least is the last one | +| 4 | $a_1 = \ldots = a_M = 0.01$ | `Sobol1998-1` | {cite}`Sobol1998` (choice 1) | The supremum of the function grows exponentially at $2^M$ | +| 5 | $a_1 = \ldots = a_M = 1.0$ | `Sobol1998-2` | {cite}`Sobol1998` (choice 2) | The supremum of the function grows exponentially at $1.5^M$ | +| 6 | $a_m = m$
$\, 1 \leq m \leq M$ | `Sobol1998-3` | {cite}`Sobol1998` (choice 3) | The supremum of the function grows linearly at $1 + \frac{M}{2}$ | +| 7 | $a_m = m^2$
$1 \leq m \leq M$ | `Sobol1998-4` | {cite}`Sobol1998` (choice 4) | The supremum is bounded at $1.0$ | +| 8 | $a_1 = a_2 = 0.0$
$a_3 = \ldots = a_M = 6.52$ | `Kucherenko2011-2a` | {cite}`Kucherenko2011` (Problem 2A) | Originally, $M = 100$ | +| 9 | $a_m = 6,52$
$1 \leq m \leq M$ | `Kucherenko2011-3b` | {cite}`Kucherenko2011` (Problem 3B) | | ```{note} The parameter values used in {cite}`Marrel2008` and {cite}`Marrel2009` diff --git a/src/uqtestfuns/test_functions/cantilever_beam_2d.py b/src/uqtestfuns/test_functions/cantilever_beam_2d.py index 212a431..e4c67df 100644 --- a/src/uqtestfuns/test_functions/cantilever_beam_2d.py +++ b/src/uqtestfuns/test_functions/cantilever_beam_2d.py @@ -38,7 +38,7 @@ "Rajashekhar1993": ProbInputSpecFixDim( name="Cantilever2D-Rajashekhar1993", description=( - "Probabilistic input model for the 2D cantilever beam problem " + "Input model for the cantilever beam problem " "from Rajashekhar and Ellingwood (1993)" ), marginals=[ @@ -108,7 +108,7 @@ class CantileverBeam2D(UQTestFunABC): _tags = ["reliability"] _description = ( - "Two-dimensional cantilever beam reliability " + "Cantilever beam reliability problem " "from Rajashekhar and Ellington (1993)" ) _available_inputs = AVAILABLE_INPUT_SPECS diff --git a/src/uqtestfuns/test_functions/circular_pipe_crack.py b/src/uqtestfuns/test_functions/circular_pipe_crack.py index f92312a..b0cca25 100644 --- a/src/uqtestfuns/test_functions/circular_pipe_crack.py +++ b/src/uqtestfuns/test_functions/circular_pipe_crack.py @@ -35,7 +35,7 @@ "Verma2015": ProbInputSpecFixDim( name="CircularPipeCrack-Verma2015", description=( - "Probabilistic input model for the circular pipe crack problem " + "Input model for the circular pipe crack problem " "from Verma et al. (2015)" ), marginals=[ diff --git a/src/uqtestfuns/test_functions/damped_oscillator.py b/src/uqtestfuns/test_functions/damped_oscillator.py index d15126f..f1989cf 100644 --- a/src/uqtestfuns/test_functions/damped_oscillator.py +++ b/src/uqtestfuns/test_functions/damped_oscillator.py @@ -129,7 +129,7 @@ "DerKiureghian1990a": ProbInputSpecFixDim( name="DampedOscillatorReliability-DerKiureghian1990a", description=( - "Probabilistic input #1 for the damped oscillator reliability " + "Input model #1 for the damped oscillator reliability " "from Der Kiureghian and De Stefano (1990)" ), marginals=INPUT_MARGINALS_DERKIUREGHIAN1991 @@ -149,7 +149,7 @@ "DerKiureghian1990b": ProbInputSpecFixDim( name="DampedOscillatorReliability-DerKiureghian1990b", description=( - "Probabilistic input #2 for the damped oscillator reliability " + "Input model #2 for the damped oscillator reliability " "from Der Kiureghian and De Stefano (1990)" ), marginals=INPUT_MARGINALS_DERKIUREGHIAN1991 @@ -169,7 +169,7 @@ "DerKiureghian1990c": ProbInputSpecFixDim( name="DampedOscillatorReliability-DerKiureghian1990c", description=( - "Probabilistic input #3 for the damped oscillator reliability " + "Input model #3 for the damped oscillator reliability " "from Der Kiureghian and De Stefano (1990)" ), marginals=INPUT_MARGINALS_DERKIUREGHIAN1991 diff --git a/src/uqtestfuns/test_functions/four_branch.py b/src/uqtestfuns/test_functions/four_branch.py index 9d211c8..e239ede 100644 --- a/src/uqtestfuns/test_functions/four_branch.py +++ b/src/uqtestfuns/test_functions/four_branch.py @@ -43,7 +43,7 @@ "Katsuki1994": ProbInputSpecFixDim( name="FourBranch-Katsuki1994", description=( - "Probabilistic input for the four-branch function " + "Input model for the four-branch function " "from Katsuki and Frangopol (1994)" ), marginals=[ diff --git a/src/uqtestfuns/test_functions/gayton_hat.py b/src/uqtestfuns/test_functions/gayton_hat.py index 42e73b2..4ede290 100644 --- a/src/uqtestfuns/test_functions/gayton_hat.py +++ b/src/uqtestfuns/test_functions/gayton_hat.py @@ -25,7 +25,7 @@ "Echard2013": ProbInputSpecFixDim( name="Echard2013", description=( - "Probabilistic input for the Gayton Hat function " + "Input model for the Gayton Hat function " "from Echard et al. (2013)" ), marginals=[ diff --git a/src/uqtestfuns/test_functions/hyper_sphere.py b/src/uqtestfuns/test_functions/hyper_sphere.py index b099193..2b0973e 100644 --- a/src/uqtestfuns/test_functions/hyper_sphere.py +++ b/src/uqtestfuns/test_functions/hyper_sphere.py @@ -24,7 +24,7 @@ "Li2018": ProbInputSpecFixDim( name="Li2018", description=( - "Probabilistic input for the hyper-sphere reliability problem " + "Input model for the hyper-sphere reliability problem " "from Li et al. (2018)" ), marginals=[ diff --git a/src/uqtestfuns/test_functions/sobol_g.py b/src/uqtestfuns/test_functions/sobol_g.py index ac878cd..7331a67 100644 --- a/src/uqtestfuns/test_functions/sobol_g.py +++ b/src/uqtestfuns/test_functions/sobol_g.py @@ -94,18 +94,18 @@ def _create_sobol_input(spatial_dimension: int) -> List[UnivDistSpec]: AVAILABLE_INPUT_SPECS = { - "Radovic1996": ProbInputSpecVarDim( - name="Sobol-G-Radovic-1996", + "Saltelli1995": ProbInputSpecVarDim( + name="Sobol-G-Saltelli1995", description=( "Probabilistic input model for the Sobol'-G function " - "from Radović et al. (1996)." + "from Saltelli and Sobol' (1995)" ), marginals_generator=_create_sobol_input, copulas=None, ), } -DEFAULT_INPUT_SELECTION = "Radovic1996" +DEFAULT_INPUT_SELECTION = "Saltelli1995" def _get_params_saltelli_1995_1(spatial_dimension: int) -> np.ndarray: diff --git a/src/uqtestfuns/test_functions/speed_reducer_shaft.py b/src/uqtestfuns/test_functions/speed_reducer_shaft.py index a0ee2c8..759c99a 100644 --- a/src/uqtestfuns/test_functions/speed_reducer_shaft.py +++ b/src/uqtestfuns/test_functions/speed_reducer_shaft.py @@ -33,7 +33,7 @@ "Du2004": ProbInputSpecFixDim( name="SpeedReducerShaft-Du2004", description=( - "Probabilistic input model for the speed reducer shaft problem " + "Input model for the speed reducer shaft problem " "from Du and Sudjianto (2004)" ), marginals=[ From 3c2d6d3a9728076db95e4be79841fe123f1769c4 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Fri, 30 Jun 2023 10:51:37 +0200 Subject: [PATCH 43/73] Add the draft paper for JOSS submission - Add a GitHub action for building the draft PDF in GitHub. - Add a list of authors - Draft the Summary section - Draft the Acknowledgment section - Draft the Statement of need section - Draft the Package overview section - Remove unnecessary sections from the template --- .github/workflows/draft-pdf.yml | 27 +++ joss/paper.bib | 264 ++++++++++++++++++++++++++ joss/paper.md | 327 ++++++++++++++++++++++++++++++++ joss/reliability.png | Bin 0 -> 480951 bytes joss/uq-framework.png | Bin 0 -> 24594 bytes 5 files changed, 618 insertions(+) create mode 100644 .github/workflows/draft-pdf.yml create mode 100644 joss/paper.bib create mode 100644 joss/paper.md create mode 100644 joss/reliability.png create mode 100644 joss/uq-framework.png diff --git a/.github/workflows/draft-pdf.yml b/.github/workflows/draft-pdf.yml new file mode 100644 index 0000000..c7e63a5 --- /dev/null +++ b/.github/workflows/draft-pdf.yml @@ -0,0 +1,27 @@ +name: Typesetting draft +on: + push: + branches: + - dev-joss + +jobs: + paper: + runs-on: ubuntu-latest + name: Paper Draft + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Build draft PDF + uses: openjournals/openjournals-draft-action@master + with: + journal: joss + # This should be the path to the paper within your repo. + paper-path: ./joss/paper.md + - name: Upload + uses: actions/upload-artifact@v1 + with: + name: paper + # This is the output path where Pandoc will write the compiled + # PDF. Note, this should be the same directory as the input + # paper.md + path: ./joss/paper.pdf \ No newline at end of file diff --git a/joss/paper.bib b/joss/paper.bib new file mode 100644 index 0000000..4747bd5 --- /dev/null +++ b/joss/paper.bib @@ -0,0 +1,264 @@ +@article{Pearson:2017, + url = {http://adsabs.harvard.edu/abs/2017arXiv170304627P}, + Archiveprefix = {arXiv}, + Author = {{Pearson}, S. and {Price-Whelan}, A.~M. and {Johnston}, K.~V.}, + Eprint = {1703.04627}, + Journal = {ArXiv e-prints}, + Keywords = {Astrophysics - Astrophysics of Galaxies}, + Month = mar, + Title = {{Gaps in Globular Cluster Streams: Pal 5 and the Galactic Bar}}, + Year = 2017 +} + +@book{Binney:2008, + url = {http://adsabs.harvard.edu/abs/2008gady.book.....B}, + Author = {{Binney}, J. and {Tremaine}, S.}, + Booktitle = {Galactic Dynamics: Second Edition, by James Binney and Scott Tremaine.~ISBN 978-0-691-13026-2 (HB).~Published by Princeton University Press, Princeton, NJ USA, 2008.}, + Publisher = {Princeton University Press}, + Title = {{Galactic Dynamics: Second Edition}}, + Year = 2008 +} + +@article{gaia, + author = {{Gaia Collaboration}}, + title = "{The Gaia mission}", + journal = {Astronomy and Astrophysics}, + archivePrefix = "arXiv", + eprint = {1609.04153}, + primaryClass = "astro-ph.IM", + keywords = {space vehicles: instruments, Galaxy: structure, astrometry, parallaxes, proper motions, telescopes}, + year = 2016, + month = nov, + volume = 595, + doi = {10.1051/0004-6361/201629272}, + url = {http://adsabs.harvard.edu/abs/2016A%26A...595A...1G}, +} + +@article{astropy, + author = {{Astropy Collaboration}}, + title = "{Astropy: A community Python package for astronomy}", + journal = {Astronomy and Astrophysics}, + archivePrefix = "arXiv", + eprint = {1307.6212}, + primaryClass = "astro-ph.IM", + keywords = {methods: data analysis, methods: miscellaneous, virtual observatory tools}, + year = 2013, + month = oct, + volume = 558, + doi = {10.1051/0004-6361/201322068}, + url = {http://adsabs.harvard.edu/abs/2013A%26A...558A..33A} +} + +@misc{fidgit, + author = {A. M. Smith and K. Thaney and M. Hahnel}, + title = {Fidgit: An ungodly union of GitHub and Figshare}, + year = {2020}, + publisher = {GitHub}, + journal = {GitHub repository}, + url = {https://github.com/arfon/fidgit} +} + +@misc{VLSE:2013, + author = {Surjanovic, S. and Bingham, D.}, + title = {Virtual Library of Simulation Experiments: Test Functions and Datasets}, + year = 2013, + howpublished = {Retrieved June 30, 2023, from \url{http://www.sfu.ca/~ssurjano}} +} + +@Misc{GdR:2008, + author = {{GdR MASCOT-NUM}}, + howpublished = {Retrieved June 30, 2023, from \url{https://www.gdr-mascotnum.fr/benchmarks.html}}, + title = {Benchmark proposals of {GdR}}, + year = {2008}, +} + +@misc{UQWorld:2019, + author = {UQWorld}, + title = {Benchmark page of UQWorld, the applied uncertainty quantification community}, + year = 2019, + howpublished = {Retrieved June 30, 2023, from \url{https://uqworld.org/c/uq-with-uqlab/benchmarks}} +} + +@misc{Rozsas:2019, + author = {Árpád Rózsás and Arthur Slobbe}, + title = {Repository and Black-box Reliability Challenge 2019}, + year = 2019, + howpublished = {Retrieved June 30, 2023, from \url{https://gitlab.com/rozsasarpi/rprepo/}} +} + +@article{Tennoe:2018a, + author = {S. Tennøe and G. Halnes and G. T. Einevoll}, + title = {Uncertainpy: A {Python} toolbox for uncertainty quantification and sensitivity analysis in computational neuroscience}, + journal = {Frontiers in Neuroinformatics}, + year = 2018, + month = aug, + volume = 12, + number = 49, + doi = {10.3389/fninf.2018.00049}, +} + + +@misc{Tennoe:2018b, + author = {S. Tennøe}, + title = {Uncertainpy: A {Python} toolbox for uncertainty quantification and sensitivity analysis in computational neuroscience}, + year = {2018}, + publisher = {GitHub}, + journal = {GitHub repository}, + url = {https://github.com/simetenn/uncertainpy} +} + +@InProceedings{Fekhari2021, + author = {Elias Fekhari and Michaël Baudin and Vincent Chabridon and Youssef Jebroun}, + booktitle = {4th International Conference on Uncertainty Quantification in Computational Sciences and Engineering}, + title = {{otbenchmark}: An open source {Python} package for benchmarking and validating uncertainty quantification algorithms}, + year = {2021}, + doi = {10.7712/120221.8034.19093}, + location = {Athens, Greece}, +} + +@Misc{Jakeman2019, + author = {John D. Jakeman}, + title = {{PyApprox}}, + year = {2019}, + journal = {GitHub repository}, + publisher = {GitHub}, + url = {https://github.com/sandialabs/pyapprox}, +} + +@Misc{Baudin2021, + author = {Michaël Baudin and Youssef Jebroun and Elias Fekhari and Vincent Chabridon}, + title = {{otbenchmark}}, + year = {2021}, + journal = {GitHub repository}, + publisher = {GitHub}, + url = {https://github.com/mbaudin47/otbenchmark}, +} + +@Misc{Bouhlel2023, + author = {Mohamed Amine Bouhlel and John Hwang and Nathalie Bartoli and Rémi Lafage and Joseph Morlier and Joaquim Martins}, + title = {{Surrogate Modeling Toolbox}}, + year = {2023}, + journal = {GitHub repository}, + publisher = {GitHub}, + url = {https://github.com/SMTorg/smt}, +} + +@InCollection{Baudin2017, + author = {Michaël Baudin and Anne Dutfoy and Bertrand Iooss and Anne-Laure Popelin}, + booktitle = {Handbook of Uncertainty Quantification}, + publisher = {Springer International Publishing}, + title = {{OpenTURNS}: An industrial software for uncertainty quantification in simulation}, + year = {2017}, + pages = {2001--2038}, + doi = {10.1007/978-3-319-12385-1_64}, +} + +@InProceedings{Sudret2017, + author = {Bruno Sudret and Stefano Marelli and Joe Wiart}, + booktitle = {2017 11th European Conference on Antennas and Propagation ({EUCAP})}, + title = {Surrogate models for uncertainty quantification: An overview}, + year = {2017}, + publisher = {{IEEE}}, + doi = {10.23919/eucap.2017.7928679}, +} + +@InProceedings{Sudret2012, + author = {Bruno Sudret}, + booktitle = {Proceedings of the 5th Asian-Pacific Symposium on Structural Reliability and its Applications}, + title = {Meta-models for structural reliability and uncertainty quantification}, + year = {2012}, + doi = {10.3850/978-981-07-2219-7_p321}, +} + +@InCollection{Iooss2015, + author = {Bertrand Iooss and Paul Lema{\^{\i}}tre}, + booktitle = {Uncertainty Management in Simulation-Optimization of Complex Systems}, + publisher = {Springer {US}}, + title = {A review on global sensitivity analysis methods}, + year = {2015}, + pages = {101--122}, + doi = {10.1007/978-1-4899-7547-8_5}, +} + +@PhdThesis{Sudret2007, + author = {Bruno Sudret}, + school = {Université Blaise Pascal - Clermont}, + title = {Uncertainty propagation and sensitivity analysis in mechanical models \textemdash {Contributions} to structural reliability and stochastic spectral methods.}, + year = {2007}, + address = {Clermont-Ferrand, France}, + type = {habilitation thesis}, + abstract = {This thesis is a contribution to the resolution of the reliability-based design optimization problem. This probabilistic design approach is aimed at considering the uncertainty attached to the system of interest in order to provide optimal and safe solutions. The safety level is quantified in the form of a probability of failure. Then, the optimization problem consists in ensuring that this failure probability remains less than a threshold specified by the stakeholders. The resolution of this problem requires a high number of calls to the limit-state design function underlying the reliability analysis. Hence it becomes cumbersome when the limit-state function involves an expensive-to-evaluate numerical model (e.g. a finite element model). In this context, this manuscript proposes a surrogate-based strategy where the limit-state function is progressively replaced by a Kriging meta-model. A special interest has been given to quantifying, reducing and eventually eliminating the error introduced by the use of this meta-model instead of the original model. The proposed methodology is applied to the design of geometrically imperfect shells prone to buckling.}, +} + +@InCollection{Verma2015, + author = {Ajit Kumar Verma and Srividya Ajit and Durga Rao Karanki}, + booktitle = {Springer Series in Reliability Engineering}, + publisher = {Springer London}, + title = {Structural reliability}, + year = {2015}, + pages = {257--292}, + doi = {10.1007/978-1-4471-6269-8_8}, +} + +@Article{Charlson1992, + author = {R. J. Charlson and S. E. Schwartz and J. M. Hales and R. D. Cess and J. A. Coakley and J. E. Hansen and D. J. Hofmann}, + journal = {Science}, + title = {Climate forcing by anthropogenic aerosols}, + year = {1992}, + number = {5043}, + pages = {423--430}, + volume = {255}, + doi = {10.1126/science.255.5043.423}, +} + +@Article{Li2018, + author = {Xu Li and Chunlin Gong and Liangxian Gu and Wenkun Gao and Zhao Jing and Hua Su}, + journal = {Structural Safety}, + title = {A sequential surrogate method for reliability analysis based on radial basis function}, + year = {2018}, + pages = {42--53}, + volume = {73}, + doi = {10.1016/j.strusafe.2018.02.005}, +} + +@TechReport{Harper1983, + author = {Harper, William V. and Gupta, Sumant K.}, + title = {Sensitivity/uncertainty analysis of a borehole scenario comparing latin hypercube sampling and deterministic sensitivity approaches}, + year = {1983}, + address = {Columbus, Ohio}, + number = {{BMI}/{ONWI}-516}, + school = {Office of Nuclear Waste Isolation, Battelle Memorial Institute}, + url = {https://inldigitallibrary.inl.gov/PRR/84393.pdf}, +} + +@Book{Saltelli2007, + author = {Andrea Saltelli and Marco Ratto and Terry Andres and Francesca Campolongo and Jessica Cariboni and Debora Gatelli and Michaela Saisana and Stefano Tarantola}, + publisher = {Wiley}, + title = {Global sensitivity analysis. The primer}, + year = {2007}, + doi = {10.1002/9780470725184}, +} + +@Article{Bouhlel2019, + author = {Bouhlel, Mohamed Amine and Hwang, John T. and Bartoli, Nathalie and Lafage, R{\'{e}}mi and Morlier, Joseph and Martins, Joaquim R. R. A.}, + journal = {Advances in Engineering Software}, + title = {A {Python} surrogate modeling framework with derivatives}, + year = {2019}, + pages = {102662}, + volume = {135}, + doi = {10.1016/j.advengsoft.2019.03.005}, + publisher = {Elsevier {BV}}, +} + +@Article{Saltelli1995, + author = {Saltelli, Andrea and Sobol', Ilya M.}, + journal = {Reliability Engineering \& System Safety}, + title = {About the use of rank transformation in sensitivity analysis of model output}, + year = {1995}, + number = {3}, + pages = {225--239}, + volume = {50}, + doi = {10.1016/0951-8320(95)00099-2}, +} + +@Comment{jabref-meta: databaseType:bibtex;} diff --git a/joss/paper.md b/joss/paper.md new file mode 100644 index 0000000..8089cae --- /dev/null +++ b/joss/paper.md @@ -0,0 +1,327 @@ +--- +title: 'UQTestFuns: A Python3 library of uncertainty quantification (UQ) test functions' +tags: + - Python + - uncertainty quantification + - metamodeling + - surrogate modeling + - sensitivity analysis + - reliability analysis + - rare-event estimations +authors: + - name: Damar Wicaksono + orcid: 0000-0000-0000-0000 + affiliation: 1 # (Multiple affiliations must be quoted) + corresponding: true + - name: Michael Hecht + affiliation: 1 +affiliations: + - name: Center for Advanced Systems Understanding (CASUS) - Helmholzt-Zentrum Dresden-Rossendorf (HZDR), Germany + index: 1 +date: 30 June 2023 +bibliography: paper.bib + +# Optional fields if submitting to a AAS journal too, see this blog post: +# https://blog.joss.theoj.org/2018/12/a-new-collaboration-with-aas-publishing +# aas-doi: 10.3847/xxxxx <- update this with the DOI from AAS once you know it. +# aas-journal: Astrophysical Journal <- The name of the AAS journal. +--- + +# Summary + +New methods and algorithms are continously being developed within +the applied uncertainty quantification (UQ) framework whose activities include, +among others, metamodeling, uncertainty propagation, reliability analysis, +and sensitivity analysis. +During the development phase of such methods, researchers and developers often +rely on test functions taken from the literature +to validate a newly proposed method. +Afterward, these test functions are employed as a common ground to benchmark +the performance of the method with other methods in terms of +some accuracy measures with respect to the number of function evaluations. + +`UQTestFuns` is an open-source Python3 library of test functions commonly used +within the applied UQ community. Specifically, the package aims to provide: + +- a lightweight implementation, with minimal dependencies of many test functions + available in the UQ literature; +- a single entry point, combining test functions and their probabilistic input + specifications, to a wide range of test functions; +- an opportunity for an open-source contribution where new test functions + are implemented and reference results are posted. + +All in all, `UQTestFuns` aims to save the reseachers' and developers' time +from reimplementing many of the commonly used test functions and to provide +a summary reference to the test functions and their applications. + +# Statement of need + +New methods and algorithms for solving a particular type of UQ analyses +(reliability analysis, sensitivity analysis, etc.) are continuously being +developed. +While such methods are eventually aimed at solving real-world problems—typically +involve a complex expensive-to-evaluate computational model—researchers +and developers may prefer to initially use the so-called UQ test functions +for validation and benchmarking purposes. + +UQ test functions are, in principle, mathematical functions taken as blackboxes; +they take a set of input values and produce output values. +However, in a typical UQ analysis, the input variables are considered uncertain +and thus modeled probabilistically. +The results of a UQ analysis, in general, depend not only on the computational +model under consideration but also on the specification of the input variables. +Consequently, a UQ test function consists of both the specification +of the function as well as the probabilistic specification of the inputs. +These test functions are widely used because: + +- test functions are _fast to evaluate_, at least _faster_ than their real-world counterparts; +- there are _many of them available_ in the literature for various types of analyses; +- while a UQ analysis usually takes the computational model of interest + as a black box, the test functions _are known_ + (they are not black boxes per se) such that a thorough diagnosis + of a newly proposed method can be performed; +- test functions provide _a common ground_ for comparing the performance of + a given method with that of other available methods + in solving the same class of problem. + +Several online sources provide a wide range of test functions relevant to +the UQ community. For example, and by no means an exhaustive list: + +- The Virtual Library of Simulation Experiments (VLSE) [@VLSE:2013]: + This site is arguably the definitive repository for (but not exclusively) UQ test functions. + It provides over a hundred test functions for numerous applications; + each test function is described in a dedicated page that includes + implementations in MATLAB and R. +- The Benchmark proposals of GdR [@GdR:2008]: + The site provides a series of documents that contain test function + specifications. +- The Benchmark page of UQWorld [@UQWorld:2019]: + This community site provides a selection of test functions + in metamodeling, sensitivity analysis, and reliability analysis along + with their implementation in MATLAB. +- RPrepo—a reliability problems repository [@Rozsas:2019]: + This repository contains numerous reliability analysis test functions + implemented in Python. + +Common to all these online resources (except for RPrepo) are the requirements +to implement the test function oneself following the specification, +or, when available, to download each of the test functions separately. + +Alternatively, UQ analysis packages are often shipped +with a selection of test functions either +for illustration, validation, or benchmarking. +Below are some examples from the Python applied UQ community +(the numbers are as of 2023-06-30; once again, +the list is non-exhaustive): + +- UncertainPy [@Tennoe:2018a; @Tennoe:2018b] includes 8 test functions (mainly in + the context of neuroscience) for illustrating the package capabilities. +- PyApprox [@Jakeman2019] includes 18 test functions, + including some non-algebraic functions for benchmarking purposes. +- Surrogate Modeling Toolbox (SMT) [@Bouhlel2019; @Bouhlel2023] includes + 11 analytical and engineering problems for benchmarking purposes. +- OpenTURNS [@Baudin2017] has its own separate benchmark package + called `otbenchmark` [@Fekhari2021; @Baudin2021] that includes + 37 test functions. + +These open-source packages already provide a wide variety of functions +implemented in Python. +The problem is that these functions are part of the respective package. +To get access to the test functions belonging to a package, +the whole analysis package must be installed first. +Furthermore, test functions from a given package are often implemented in such a way +that is tightly coupled with the package itself. +To use the test functions belonging to a package, +one may need to learn some basic usage and specific terminologies of the package. + +`UQTestFuns` is mostly comparable to the package `otbenchmark` in its aim. +Both also acknowledge the particularity of UQ test functions +that require combining a test function +and the corresponding probabilistic input specification. +There are, however, major differences: + +- `UQTestFuns` is more lightweight with fewer dependencies, + while `otbenchmark` is coupled to the package OpenTURNS. + This is to be expected as one of `otbenchmark`'s main goals + is to provide the OpenTURNS development team with a tool + for helping with the implementation of new algorithms. +- `UQTestFuns` is more modest in its scope, that is, simply to provide + a library of UQ test functions implemented in Python + with a consistent interface and an online reference + (similar to that of VLSE [@VLSE:2013]), and not, + as in the case of `otbenchmark`, + an automated benchmark framework[^benchmark] [@Fekhari2021]. + +# Package overview + +Consider a computational model that is represented as an $M$-dimensional +black-box function: +\begin{equation*} +\mathcal{M}: \boldsymbol{x} \in \mathcal{D}_{\boldsymbol{X}} \subseteq \mathbb{R}^M \mapsto y = \mathcal{M}(\boldsymbol{x}), +\end{equation*} +where $\mathcal{D}_{\boldsymbol{X}}$ and $y$ denote the input domain +and the quantity of interest (QoI), respectively. + +In practice, the exact values of the input variables are not exactly known +and may be considered uncertain. +The ensuing analysis involving uncertain input variables can be formalized +in the uncertainty quantification (UQ) framework following @Sudret2007 +as illustrated in \autoref{fig:uq-framework}. + +![Uncertainty quantification (UQ) framework, adapted from @Sudret2007.\label{fig:uq-framework}](uq-framework.png){ width=80% } + +The framework starts with the computational model $\mathcal{M}$ +taken as a black box as defined above. +Then, it moves on to the quantification of the input uncertainties. +This uncertainty is modeled probabilistically +such that the input variables are represented as a random vector +equipped with joint probability density function (PDF) +$f_{\boldsymbol{X}} \in \mathcal{D}_{\boldsymbol{X}} \subseteq \mathbb{R}^M$. + +Afterward, the uncertainties from the input variables are propagated through +the computational model $\mathcal{M}$. +The quantity of interest $y$, as computed by $\mathcal{M}$, +now becomes as random variable: +$$ +Y = \mathcal{M}(\boldsymbol{X}),\; \boldsymbol{X} \sim f_{\boldsymbol{X}}. +$$ +This leads to various downstream analyses such as reliability analysis, +sensitivity analysis, and metamodeling. +In `UQTestFuns`, these are currently the three main classifications +of UQ test functions by their applications in the literature[^classifications]. + +## Reliability analysis + +Consider the circular pipe crack reliability test function, +a $2$-dimensional function for testing reliability analysis methods +[@Verma2015; @Li2018]: +$$ +g(\boldsymbol{x}; \boldsymbol{p}) = \mathcal{M}(\boldsymbol{x}; t, R) - M = 4 t \sigma_f R^2 \left( \cos{\left(\frac{\theta}{2}\right)} - \frac{1}{2} \sin{(\theta)} \right) - M +$$ +where $\boldsymbol{x} = \{ \sigma_f, \theta \}$ is the two-dimensional vector of +input variables probabilistically defined further below; +and $\boldsymbol{p} = \{ t, R, M \}$ is the vector of (deterministic) parameters. + +In a reliability analysis problem, +a computational model $\mathcal{M}$ is often combined with another set of parameters +(either uncertain or deterministic) to define the so-called +_performance function_ or _limit-state function_ of a system denoted by $g$. +The task of reliability analysis methods is to estimate the failure probability +of the system defined as follows [@Sudret2012]: +$$ +P_f = \mathbb{P}[g(\boldsymbol{X}; \boldsymbol{p}) \leq 0] = \int_{\mathcal{D}_f = \{ \boldsymbol{x} | g(\boldsymbol{x}; \boldsymbol{p}) \leq 0 \}} f_{\boldsymbol{X}} (\boldsymbol{x}) \; d\boldsymbol{x} +$$ +where $g(\boldsymbol{x}) \leq 0.0$ is defined as a _failure state_. +The difficulty of evaluating the integral above stems from the fact +that the integration domain $D_f$ is defined implicitly. + +The circular pipe crack problem can be created in `UQTestFuns` as follows: +```python +>>> import uqtestfuns as uqtf +>>> circular_pipe = uqtf.CircularPipeCrack() +``` + +As explained above, the probabilistic input model is an integral part +of a UQ test function (the results depend on it). +In `UQTestFuns`, the input model according to the original specification is attached +to the instance of the test function: +``` +>>> print(circular_pipe.prob_input) +Name : CircularPipeCrack-Verma2015 +Spatial Dim. : 2 +Description : Prob. input model for the circular pipe crack problem from... +Marginals : + No. Name Distribution Parameters Description +----- ------- -------------- ----------------- -------------------- + 1 sigma_f normal [301.079 14.78 ] flow stress [MNm] + 2 theta normal [0.503 0.049] half crack angle [-] + +Copulas : None +``` +The probabilistic input model instance can be used, among other things, +to transform a set of values in a given domain (say, the unit hypercube $[0, 1]^M$) +to the domain of the test function. + +The limit-state surface (i.e., where $g(\boldsymbol{x}) = 0$) +for the circular pipe crack problem is shown in \autoref{fig:reliability} (left plot). +In the middle, $10^6$ random sample points are overlaid; +each point is classified whether it is in failure (red) or safe domain (blue). +As illustrated in the histogram (right plot), +the task of the analysis is to accurately estimate the probability where $g(\boldsymbol{X}) \leq 0$ +(and with as few as function evaluations as possible). + +![Illustration of reliability analysis: Circular pipe crack problem.\label{fig:reliability}](reliability.png){ width=90% } + +## Sensitivity analysis + +Consider the Sobol'-G function, +an $M$-dimensional function commonly used +for testing sensitivity analysis methods[@Saltelli1995]: +$$ +\mathcal{M}(\boldsymbol{x}) = \prod_{m = 1}^M \frac{\lvert 4 x_m - 2 \rvert + a_m}{1 + a_m}, +$$ +where $\boldsymbol{x} = \{ x_1, \ldots, x_M \}$ is the $M$-dimensional vector +of independent uniform random variables in $[0, 1]^M$; +and $\boldsymbol{a} = \{ a_m = \frac{m - 1}{2.0}, m = 1, \ldots, M \}$ +is the set of parameters. + +The tasks of sensitivity analysis methods are to ascertain +either qualitatively or quantitatively +the most important input variables (for _factor prioritization_) +and/or the least important input variables +(for _factor fixing_). +See @Saltelli2007 and @Iooss2015 for details on the topic. + +The Sobol'-G test function can be created in `UQTestFuns` +for any given dimension. +For instance, to create a $6$-dimensional Sobol'-G function: +```python +>>> sobol_g = uqtf.SobolG(spatial_dimension=6) +``` +As before, the probabilistic input model of the function as prescribed +in the original specification is attached to the instance of test function +(i.e., the `prob_input` property). + +## Metamodeling + +In practice, the computational model $\mathcal{M}$ is often complex. +Because a UQ analysis typically involves evaluating $\mathcal{M}$ +numerous times ($\sim 10^2$ — $10^6$), +the analysis may become intractable if $\mathcal{M}$ is expensive to evaluate. +As a consequence, in many UQ analyses, a metamodel (surrogate model) is employed. +With a limited number of full model ($\mathcal{M}$) evaluations, +such a metamodel should be able to capture the most important aspects +of the input/output mapping +and can, therefore, be used to replace $\mathcal{M}$ in the analysis. + +While not a goal of UQ analyses per se, +metamodeling is nowadays an indispensable component of the UQ framework [@Sudret2017]. +`UQTestFuns` also includes test functions from the literature that are used +as test functions in a metamodeling exercise. + +## Documentation + +The online documentation of `UQTestFuns` is an important aspect of the project. +It includes a detailed description of each of the available UQ test functions, +their references, and, when applicable, published results of a UQ analysis +conducted using the test function. +Guides on how to add additional test functions as well as +to update the documentation are also available. + +The package documentation is available +on the `UQTestFuns` [readthedocs](https://uqtestfuns.readthedocs.io). + +# Acknowledgements + +This work was partly funded by the Center for Advanced Systems Understanding +(CASUS) that is financed by Germany's Federal Ministry of Education and Research +(BMBF) and by the Saxony Ministry for Science, Culture and Tourism (SMWK) +with tax funds on the basis of budget approved by the Saxony Sate Parliament. + +# References + +[^benchmark]: A fully functional benchmark suite may, however, be in the future +built on top of `UQTestFuns`. + +[^classifications]: The classifications are not mutually exclusive; +a given UQ test function may be applied in several contexts. \ No newline at end of file diff --git a/joss/reliability.png b/joss/reliability.png new file mode 100644 index 0000000000000000000000000000000000000000..f46e39831f5f1a5b5ed50f71c4cb155550adbbff GIT binary patch literal 480951 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

dFb2w`~y%Os`LA#LtqU_-su6SGxei`b@$vt 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

?PNQ(l_0pReo}m>`hXl8;0e8%$tz4Bl&Y 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 q8t%_q+|L=s$-N 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%0AA@a;mI(_kO@5f?vT_%Fx>9c_u#Oj%11 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 literal 0 HcmV?d00001 diff --git a/joss/uq-framework.png b/joss/uq-framework.png new file mode 100644 index 0000000000000000000000000000000000000000..ac33241305797d63ccacd84a1342ff9849c44ce0 GIT binary patch literal 24594 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 literal 0 HcmV?d00001 From 50caf171ad09a53246ed1e1ef8c32324b2067581 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Mon, 3 Jul 2023 17:20:43 +0200 Subject: [PATCH 44/73] Prepare the release of v0.3.0 - Update the CHANGELOG.md. - Update the Zenodo link in the README.md - Update the new version and Zenodo link in the citation file - Update the new version in setup.cfg --- CHANGELOG.md | 6 +++++- CITATION.cff | 8 ++++---- README.md | 2 +- setup.cfg | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d1af67..d246962 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.3.0] - 2023-07-03 + ### Added - The two-dimensional Gayton Hat function from Echard et al. (2013) used @@ -24,7 +26,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 from Du and Sudjianto (2004). - The two-dimensional reliability problem of a circular pipe crack under a bending moment under Verma et al. (2015). -- New docs section on list of functions for reliability analysis. +- New docs section on list of functions for reliability analysis including + a brief description on the reliability analysis problem. ## [0.2.0] - 2023-06-26 @@ -126,6 +129,7 @@ First public release of UQTestFuns. - Mirror GitHub action to the [CASUS organization](https://github.com/casus) [Unreleased]: https://github.com/damar-wicaksono/uqtestfuns/compare/main...dev +[0.3.0]: https://github.com/damar-wicaksono/uqtestfuns/compare/v0.2.0...v0.3.0 [0.2.0]: https://github.com/damar-wicaksono/uqtestfuns/compare/v0.1.1...v0.2.0 [0.1.1]: https://github.com/damar-wicaksono/uqtestfuns/compare/v0.1.0...v0.1.1 [0.1.0]: https://github.com/damar-wicaksono/uqtestfuns/compare/v0.0.1...v0.1.0 diff --git a/CITATION.cff b/CITATION.cff index 3af41ca..56d9762 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -14,8 +14,8 @@ authors: orcid: 'https://orcid.org/0000-0001-8587-7730' identifiers: - type: doi - value: 10.5281/zenodo.7703922 - description: The Zenodo URL of version 0.2.0 of the package + value: 10.5281/zenodo.8109901 + description: The Zenodo URL of version 0.3.0 of the package repository-code: 'https://github.com/damar-wicaksono/uqtestfuns' url: 'https://uqtestfuns.readthedocs.io/en/latest/' repository-artifact: 'https://pypi.org/project/uqtestfuns/' @@ -35,5 +35,5 @@ keywords: - sensitivity-analysis - metamodeling license: MIT -version: 0.2.0 -date-released: '2023-06-26' +version: 0.3.0 +date-released: '2023-07-03' diff --git a/README.md b/README.md index 860799f..7697356 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # UQTestFuns -[![DOI](http://img.shields.io/badge/DOI-10.5281/zenodo.7703922-blue.svg?style=flat-square)](https://doi.org/10.5281/zenodo.7703922) +[![DOI](http://img.shields.io/badge/DOI-10.5281/zenodo.8109901-blue.svg?style=flat-square)](https://doi.org/10.5281/zenodo.8109901) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg?style=flat-square)](https://github.com/psf/black) [![Python 3.8](https://img.shields.io/badge/python-3.7-blue.svg?style=flat-square)](https://www.python.org/downloads/release/python-370/) [![License](https://img.shields.io/github/license/damar-wicaksono/uqtestfuns?style=flat-square)](https://choosealicense.com/licenses/mit/) diff --git a/setup.cfg b/setup.cfg index 57f37a9..39670b6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = uqtestfuns -version = 0.2.0 +version = 0.3.0 url = https://github.com/damar-wicaksono/uqtestfuns author = Damar Wicaksono author_email = damar.wicaksono@outlook.com From 12ab494d3ad1fd06dd51c41f3a344eb1038017d1 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Mon, 3 Jul 2023 18:26:18 +0200 Subject: [PATCH 45/73] Replace UncertainPy with SALib. --- joss/paper.bib | 21 +++++++++++++++++++++ joss/paper.md | 4 ++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/joss/paper.bib b/joss/paper.bib index 4747bd5..13d28b9 100644 --- a/joss/paper.bib +++ b/joss/paper.bib @@ -261,4 +261,25 @@ @Article{Saltelli1995 doi = {10.1016/0951-8320(95)00099-2}, } +@Article{Herman2017, + author = {Jon Herman and Will Usher}, + journal = {The Journal of Open Source Software}, + title = {{SALib}: An open-source python library for sensitivity analysis}, + year = {2017}, + number = {9}, + pages = {97}, + volume = {2}, + doi = {10.21105/joss.00097}, +} + +@Article{Iwanaga2022, + author = {Takuya Iwanaga and William Usher and Jonathan Herman}, + journal = {Socio-Environmental Systems Modelling}, + title = {Toward {SALib} 2.0: Advancing the accessibility and interpretability of global sensitivity analyses}, + year = {2022}, + pages = {18155}, + volume = {4}, + doi = {10.18174/sesmo.18155}, +} + @Comment{jabref-meta: databaseType:bibtex;} diff --git a/joss/paper.md b/joss/paper.md index f658e3b..fb648ec 100644 --- a/joss/paper.md +++ b/joss/paper.md @@ -114,8 +114,8 @@ Below are some examples from the Python applied UQ community (the numbers are as of 2023-06-30; once again, the list is non-exhaustive): -- UncertainPy [@Tennoe:2018a; @Tennoe:2018b] includes 8 test functions (mainly in - the context of neuroscience) for illustrating the package capabilities. +- SALib [@Herman2017; @Iwanaga2022] includes 6 test functions + mainly for illustrating the package capabilities. - PyApprox [@Jakeman2019] includes 18 test functions, including some non-algebraic functions for benchmarking purposes. - Surrogate Modeling Toolbox (SMT) [@Bouhlel2019; @Bouhlel2023] includes From 7fa06287843ef924c2bc66439ab33cbbae078363 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Tue, 4 Jul 2023 09:13:29 +0200 Subject: [PATCH 46/73] Fix typos and add ORCID ID's --- joss/paper.md | 138 +++++++++++++++++++++++++++----------------------- 1 file changed, 76 insertions(+), 62 deletions(-) diff --git a/joss/paper.md b/joss/paper.md index fb648ec..f8ca0bc 100644 --- a/joss/paper.md +++ b/joss/paper.md @@ -10,13 +10,14 @@ tags: - rare-event estimations authors: - name: Damar Wicaksono - orcid: 0000-0000-0000-0000 + orcid: 0000-0001-8587-7730 affiliation: 1 # (Multiple affiliations must be quoted) corresponding: true - name: Michael Hecht + orcid: 0000-0001-9214-8253 affiliation: 1 affiliations: - - name: Center for Advanced Systems Understanding (CASUS) - Helmholzt-Zentrum Dresden-Rossendorf (HZDR), Germany + - name: Center for Advanced Systems Understanding (CASUS) - Helmholtz-Zentrum Dresden-Rossendorf (HZDR), Germany index: 1 date: 30 June 2023 bibliography: paper.bib @@ -29,7 +30,7 @@ bibliography: paper.bib # Summary -New methods and algorithms are continously being developed within +New methods and algorithms are continuously being developed within the applied uncertainty quantification (UQ) framework whose activities include, among others, metamodeling, uncertainty propagation, reliability analysis, and sensitivity analysis. @@ -43,15 +44,15 @@ some accuracy measures with respect to the number of function evaluations. `UQTestFuns` is an open-source Python3 library of test functions commonly used within the applied UQ community. Specifically, the package aims to provide: -- a lightweight implementation, with minimal dependencies of many test functions - available in the UQ literature; +- a lightweight implementation, with minimal dependencies, + of many test functions available in the UQ literature; - a single entry point, combining test functions and their probabilistic input - specifications, to a wide range of test functions; + specifications, to a wide range of test functions; - an opportunity for an open-source contribution where new test functions - are implemented and reference results are posted. + are implemented and reference results are posted. -All in all, `UQTestFuns` aims to save the reseachers' and developers' time -from reimplementing many of the commonly used test functions and to provide +All in all, `UQTestFuns` aims to save the researchers' and developers' time +from reimplementing many of the commonly used test functions and provide a summary reference to the test functions and their applications. # Statement of need @@ -59,13 +60,13 @@ a summary reference to the test functions and their applications. New methods and algorithms for solving a particular type of UQ analyses (reliability analysis, sensitivity analysis, etc.) are continuously being developed. -While such methods are eventually aimed at solving real-world problems—typically -involve a complex expensive-to-evaluate computational model—researchers -and developers may prefer to initially use the so-called UQ test functions -for validation and benchmarking purposes. +While such methods are eventually aimed at solving +real-world problems—typically involve complex expensive-to-evaluate +computational models—researchers and developers may prefer to initially +use the so-called UQ test functions for validation and benchmarking purposes. -UQ test functions are, in principle, mathematical functions taken as black boxes; -they take a set of input values and produce output values. +UQ test functions are, in principle, mathematical functions taken +as black boxes; they take a set of input values and produce output values. However, in a typical UQ analysis, the input variables are considered uncertain and thus modeled probabilistically. The results of a UQ analysis, in general, depend not only on the computational @@ -74,23 +75,26 @@ Consequently, a UQ test function consists of both the specification of the function as well as the probabilistic specification of the inputs. These test functions are widely used because: -- test functions are _fast to evaluate_, at least _faster_ than their real-world counterparts; -- there are _many of them available_ in the literature for various types of analyses; +- test functions are _fast to evaluate_, at least _faster_ + than their real-world counterparts; +- there are _many of them available_ in the literature + for various types of analyses; - while a UQ analysis usually takes the computational model of interest as a black box, the test functions _are known_ (they are not black boxes per se) such that a thorough diagnosis of a newly proposed method can be performed; - test functions provide _a common ground_ for comparing the performance of a given method with that of other available methods - in solving the same class of problem. + in solving the same class of problems. Several online sources provide a wide range of test functions relevant to the UQ community. For example, and by no means an exhaustive list: - The Virtual Library of Simulation Experiments (VLSE) [@VLSE:2013]: - This site is arguably the definitive repository for (but not exclusively) UQ test functions. + This site is arguably the definitive repository + for (but not exclusively) UQ test functions. It provides over a hundred test functions for numerous applications; - each test function is described in a dedicated page that includes + each test function is described on a dedicated page that includes implementations in MATLAB and R. - The Benchmark proposals of GdR [@GdR:2008]: The site provides a series of documents that contain test function @@ -103,19 +107,20 @@ the UQ community. For example, and by no means an exhaustive list: This repository contains numerous reliability analysis test functions implemented in Python. -Common to all these online resources (except for RPrepo) are the requirements +Common to all these online resources are the requirements to implement the test function oneself following the specification, -or, when available, to download each of the test functions separately. +or, when available, to download each of the test functions separately +(except for RPrepo). Alternatively, UQ analysis packages are often shipped with a selection of test functions either for illustration, validation, or benchmarking. -Below are some examples from the Python applied UQ community +Below are some examples from the applied UQ community in Python (the numbers are as of 2023-06-30; once again, the list is non-exhaustive): - SALib [@Herman2017; @Iwanaga2022] includes 6 test functions - mainly for illustrating the package capabilities. + mainly for illustrating the package capabilities in the examples. - PyApprox [@Jakeman2019] includes 18 test functions, including some non-algebraic functions for benchmarking purposes. - Surrogate Modeling Toolbox (SMT) [@Bouhlel2019; @Bouhlel2023] includes @@ -129,14 +134,15 @@ implemented in Python. The problem is that these functions are part of the respective package. To get access to the test functions belonging to a package, the whole analysis package must be installed first. -Furthermore, test functions from a given package are often implemented in such a way +Furthermore, test functions from a given package +are often implemented in such a way that is tightly coupled with the package itself. To use the test functions belonging to a package, one may need to learn some basic usage and specific terminologies of the package. `UQTestFuns` is mostly comparable to the package `otbenchmark` in its aim. Both also acknowledge the particularity of UQ test functions -that require combining a test function +that requires combining a test function and the corresponding probabilistic input specification. There are, however, some major differences: @@ -157,9 +163,9 @@ There are, however, some major differences: Consider a computational model that is represented as an $M$-dimensional black-box function: \begin{equation*} -\mathcal{M}: \boldsymbol{x} \in \mathcal{D}_{\boldsymbol{X}} \subseteq \mathbb{R}^M \mapsto y = \mathcal{M}(\boldsymbol{x}), +\mathcal{M}: \mathbf{x} \in \mathcal{D}_{\mathbf{X}} \subseteq \mathbb{R}^M \mapsto y = \mathcal{M}(\mathbf{x}), \end{equation*} -where $\mathcal{D}_{\boldsymbol{X}}$ and $y$ denote the input domain +where $\mathcal{D}_{\mathbf{X}}$ and $y$ denote the input domain and the quantity of interest (QoI), respectively. In practice, the exact values of the input variables are not exactly known @@ -175,15 +181,15 @@ taken as a black box as defined above. Then, it moves on to the quantification of the input uncertainties. This uncertainty is modeled probabilistically such that the input variables are represented as a random vector -equipped with joint probability density function (PDF) -$f_{\boldsymbol{X}} \in \mathcal{D}_{\boldsymbol{X}} \subseteq \mathbb{R}^M$. +equipped with a joint probability density function (PDF) +$f_{\mathbf{X}}: \mathbf{x} \in \mathcal{D}_{\mathbf{X}} \subseteq \mathbb{R}^M \mapsto \mathbb{R}$. Afterward, the uncertainties from the input variables are propagated through the computational model $\mathcal{M}$. The quantity of interest $y$, as computed by $\mathcal{M}$, -now becomes as random variable: +now becomes a random variable: $$ -Y = \mathcal{M}(\boldsymbol{X}),\; \boldsymbol{X} \sim f_{\boldsymbol{X}}. +Y = \mathcal{M}(\mathbf{X}),\; \mathbf{X} \sim f_{\mathbf{X}}. $$ This leads to various downstream analyses such as reliability analysis, sensitivity analysis, and metamodeling. @@ -196,11 +202,11 @@ Consider the circular pipe crack reliability test function, a $2$-dimensional function for testing reliability analysis methods [@Verma2015; @Li2018]: $$ -g(\boldsymbol{x}; \boldsymbol{p}) = \mathcal{M}(\boldsymbol{x}; t, R) - M = 4 t \sigma_f R^2 \left( \cos{\left(\frac{\theta}{2}\right)} - \frac{1}{2} \sin{(\theta)} \right) - M +g(\mathbf{x}; \mathbf{p}) = \mathcal{M}(\mathbf{x}; t, R) - M = 4 t \sigma_f R^2 \left( \cos{\left(\frac{\theta}{2}\right)} - \frac{1}{2} \sin{(\theta)} \right) - M $$ -where $\boldsymbol{x} = \{ \sigma_f, \theta \}$ is the two-dimensional vector of +where $\mathbf{x} = \{ \sigma_f, \theta \}$ is the two-dimensional vector of input variables probabilistically defined further below; -and $\boldsymbol{p} = \{ t, R, M \}$ is the vector of (deterministic) parameters. +and $\mathbf{p} = \{ t, R, M \}$ is the vector of (deterministic) parameters. In a reliability analysis problem, a computational model $\mathcal{M}$ is often combined with another set of parameters @@ -209,9 +215,9 @@ _performance function_ or _limit-state function_ of a system denoted by $g$. The task of reliability analysis methods is to estimate the failure probability of the system defined as follows [@Sudret2012]: $$ -P_f = \mathbb{P}[g(\boldsymbol{X}; \boldsymbol{p}) \leq 0] = \int_{\mathcal{D}_f = \{ \boldsymbol{x} | g(\boldsymbol{x}; \boldsymbol{p}) \leq 0 \}} f_{\boldsymbol{X}} (\boldsymbol{x}) \; d\boldsymbol{x} +P_f = \mathbb{P}[g(\mathbf{X}; \mathbf{p}) \leq 0] = \int_{\mathcal{D}_f = \{ \mathbf{x} | g(\mathbf{x}; \mathbf{p}) \leq 0 \}} f_{\mathbf{X}} (\mathbf{x}) \; d\mathbf{x} $$ -where $g(\boldsymbol{x}) \leq 0.0$ is defined as a _failure state_. +where $g(\mathbf{x}) \leq 0.0$ is defined as a _failure state_. The difficulty of evaluating the integral above stems from the fact that the integration domain $D_f$ is defined implicitly. @@ -221,15 +227,17 @@ The circular pipe crack problem can be created in `UQTestFuns` as follows: >>> circular_pipe = uqtf.CircularPipeCrack() ``` -As explained above, the probabilistic input model is an integral part -of a UQ test function (the results depend on it). -In `UQTestFuns`, the input model according to the original specification is attached -to the instance of the test function: +The resulting instance is _callable_ and can be called +with a set of input values. +As explained, the probabilistic input model is an integral part +of a UQ test function. +In `UQTestFuns`, the input model according to the original specification +is attached to the instance of the test function: ``` >>> print(circular_pipe.prob_input) Name : CircularPipeCrack-Verma2015 Spatial Dim. : 2 -Description : Prob. input model for the circular pipe crack problem from... +Description : Input model for the circular pipe crack problem from Verma... Marginals : No. Name Distribution Parameters Description ----- ------- -------------- ----------------- -------------------- @@ -239,16 +247,18 @@ Marginals : Copulas : None ``` The probabilistic input model instance can be used, among other things, -to transform a set of values in a given domain (say, the unit hypercube $[0, 1]^M$) -to the domain of the test function. +to transform a set of values in a given domain +(say, the unit hypercube $[0, 1]^M$) to the domain of the test function. -The limit-state surface (i.e., where $g(\boldsymbol{x}) = 0$) -for the circular pipe crack problem is shown in \autoref{fig:reliability} (left plot). +The limit-state surface (i.e., where $g(\mathbf{x}) = 0$) +for the circular pipe crack problem is shown in \autoref{fig:reliability} +(left plot). In the middle, $10^6$ random sample points are overlaid; each point is classified whether it is in failure (red) or safe domain (blue). As illustrated in the histogram (right plot), -the task of the analysis is to accurately estimate the probability where $g(\boldsymbol{X}) \leq 0$ -(and with as few as function evaluations as possible). +the task of the analysis is to accurately estimate the probability +where $g(\mathbf{X}) \leq 0$ +and with as few function evaluations as possible. ![Illustration of reliability analysis: Circular pipe crack problem.\label{fig:reliability}](reliability.png){ width=90% } @@ -258,11 +268,11 @@ Consider the Sobol'-G function, an $M$-dimensional function commonly used for testing sensitivity analysis methods [@Saltelli1995]: $$ -\mathcal{M}(\boldsymbol{x}) = \prod_{m = 1}^M \frac{\lvert 4 x_m - 2 \rvert + a_m}{1 + a_m}, +\mathcal{M}(\mathbf{x}) = \prod_{m = 1}^M \frac{\lvert 4 x_m - 2 \rvert + a_m}{1 + a_m}, $$ -where $\boldsymbol{x} = \{ x_1, \ldots, x_M \}$ is the $M$-dimensional vector +where $\mathbf{x} = \{ x_1, \ldots, x_M \}$ is the $M$-dimensional vector of independent uniform random variables in $[0, 1]^M$; -and $\boldsymbol{a} = \{ a_m = \frac{m - 1}{2.0}, m = 1, \ldots, M \}$ +and $\mathbf{a} = \{ a_m = \frac{m - 1}{2.0}, m = 1, \ldots, M \}$ is the set of parameters. The tasks of sensitivity analysis methods are to ascertain @@ -279,7 +289,7 @@ For instance, to create a $6$-dimensional Sobol'-G function: >>> sobol_g = uqtf.SobolG(spatial_dimension=6) ``` As before, the probabilistic input model of the function as prescribed -in the original specification is attached to the instance of test function +in the original specification is attached to the instance of the test function (i.e., the `prob_input` property). ## Metamodeling @@ -288,14 +298,16 @@ In practice, the computational model $\mathcal{M}$ is often complex. Because a UQ analysis typically involves evaluating $\mathcal{M}$ numerous times ($\sim 10^2$ — $10^6$), the analysis may become intractable if $\mathcal{M}$ is expensive to evaluate. -As a consequence, in many UQ analyses, a metamodel (surrogate model) is employed. -With a limited number of full model ($\mathcal{M}$) evaluations, +As a consequence, in many UQ analyses, a metamodel (surrogate model) +is employed. +Based on a limited number of model ($\mathcal{M}$) evaluations, such a metamodel should be able to capture the most important aspects -of the input/output mapping -and can, therefore, be used to replace $\mathcal{M}$ in the analysis. +of the input/output mapping but having much less cost per evaluation; +it can, therefore, be used to replace $\mathcal{M}$ in the analysis. While not a goal of UQ analyses per se, -metamodeling is nowadays an indispensable component of the UQ framework [@Sudret2017]. +metamodeling is nowadays an indispensable component +of the UQ framework [@Sudret2017]. `UQTestFuns` also includes test functions from the literature that are used as test functions in a metamodeling exercise. @@ -303,7 +315,7 @@ as test functions in a metamodeling exercise. The online documentation of `UQTestFuns` is an important aspect of the project. It includes a detailed description of each of the available UQ test functions, -their references, and, when applicable, published results of a UQ analysis +their references, and when applicable, published results of a UQ analysis conducted using the test function. Guides on how to add additional test functions as well as to update the documentation are also available. @@ -314,9 +326,11 @@ on the `UQTestFuns` [readthedocs](https://uqtestfuns.readthedocs.io). # Acknowledgements This work was partly funded by the Center for Advanced Systems Understanding -(CASUS) that is financed by Germany's Federal Ministry of Education and Research -(BMBF) and by the Saxony Ministry for Science, Culture and Tourism (SMWK) -with tax funds on the basis of budget approved by the Saxony Sate Parliament. +(CASUS) that is financed by Germany's Federal Ministry of Education +and Research (BMBF) and by the Saxony Ministry for Science, Culture +and Tourism (SMWK) +with tax funds on the basis of a budget approved +by the Saxony State Parliament. # References @@ -324,4 +338,4 @@ with tax funds on the basis of budget approved by the Saxony Sate Parliament. built on top of `UQTestFuns`. [^classifications]: The classifications are not mutually exclusive; -a given UQ test function may be applied in several contexts. \ No newline at end of file +a given UQ test function may be applied in several contexts. From 608da611d5671d037bcd446210b09d2a37e5d013 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Wed, 5 Jul 2023 17:33:14 +0200 Subject: [PATCH 47/73] Revise paper according to M. Hecht's review. - Revise the Summary section. - Revise the Statement of need section. - Revise the opening of the Package overview section. - Revise the Reliability analysis subsection. - Revise the Sensitivity analysis subsection. - Clean up BibTeX file. --- joss/paper.bib | 137 ++++++++--------- joss/paper.md | 395 ++++++++++++++++++++++++++----------------------- 2 files changed, 274 insertions(+), 258 deletions(-) diff --git a/joss/paper.bib b/joss/paper.bib index 13d28b9..38a44a3 100644 --- a/joss/paper.bib +++ b/joss/paper.bib @@ -1,63 +1,3 @@ -@article{Pearson:2017, - url = {http://adsabs.harvard.edu/abs/2017arXiv170304627P}, - Archiveprefix = {arXiv}, - Author = {{Pearson}, S. and {Price-Whelan}, A.~M. and {Johnston}, K.~V.}, - Eprint = {1703.04627}, - Journal = {ArXiv e-prints}, - Keywords = {Astrophysics - Astrophysics of Galaxies}, - Month = mar, - Title = {{Gaps in Globular Cluster Streams: Pal 5 and the Galactic Bar}}, - Year = 2017 -} - -@book{Binney:2008, - url = {http://adsabs.harvard.edu/abs/2008gady.book.....B}, - Author = {{Binney}, J. and {Tremaine}, S.}, - Booktitle = {Galactic Dynamics: Second Edition, by James Binney and Scott Tremaine.~ISBN 978-0-691-13026-2 (HB).~Published by Princeton University Press, Princeton, NJ USA, 2008.}, - Publisher = {Princeton University Press}, - Title = {{Galactic Dynamics: Second Edition}}, - Year = 2008 -} - -@article{gaia, - author = {{Gaia Collaboration}}, - title = "{The Gaia mission}", - journal = {Astronomy and Astrophysics}, - archivePrefix = "arXiv", - eprint = {1609.04153}, - primaryClass = "astro-ph.IM", - keywords = {space vehicles: instruments, Galaxy: structure, astrometry, parallaxes, proper motions, telescopes}, - year = 2016, - month = nov, - volume = 595, - doi = {10.1051/0004-6361/201629272}, - url = {http://adsabs.harvard.edu/abs/2016A%26A...595A...1G}, -} - -@article{astropy, - author = {{Astropy Collaboration}}, - title = "{Astropy: A community Python package for astronomy}", - journal = {Astronomy and Astrophysics}, - archivePrefix = "arXiv", - eprint = {1307.6212}, - primaryClass = "astro-ph.IM", - keywords = {methods: data analysis, methods: miscellaneous, virtual observatory tools}, - year = 2013, - month = oct, - volume = 558, - doi = {10.1051/0004-6361/201322068}, - url = {http://adsabs.harvard.edu/abs/2013A%26A...558A..33A} -} - -@misc{fidgit, - author = {A. M. Smith and K. Thaney and M. Hahnel}, - title = {Fidgit: An ungodly union of GitHub and Figshare}, - year = {2020}, - publisher = {GitHub}, - journal = {GitHub repository}, - url = {https://github.com/arfon/fidgit} -} - @misc{VLSE:2013, author = {Surjanovic, S. and Bingham, D.}, title = {Virtual Library of Simulation Experiments: Test Functions and Datasets}, @@ -182,7 +122,7 @@ @InCollection{Iooss2015 @PhdThesis{Sudret2007, author = {Bruno Sudret}, - school = {Université Blaise Pascal - Clermont}, + school = {Université Blaise Pascal - Clermont, France}, title = {Uncertainty propagation and sensitivity analysis in mechanical models \textemdash {Contributions} to structural reliability and stochastic spectral methods.}, year = {2007}, address = {Clermont-Ferrand, France}, @@ -200,17 +140,6 @@ @InCollection{Verma2015 doi = {10.1007/978-1-4471-6269-8_8}, } -@Article{Charlson1992, - author = {R. J. Charlson and S. E. Schwartz and J. M. Hales and R. D. Cess and J. A. Coakley and J. E. Hansen and D. J. Hofmann}, - journal = {Science}, - title = {Climate forcing by anthropogenic aerosols}, - year = {1992}, - number = {5043}, - pages = {423--430}, - volume = {255}, - doi = {10.1126/science.255.5043.423}, -} - @Article{Li2018, author = {Xu Li and Chunlin Gong and Liangxian Gu and Wenkun Gao and Zhao Jing and Hua Su}, journal = {Structural Safety}, @@ -282,4 +211,68 @@ @Article{Iwanaga2022 doi = {10.18174/sesmo.18155}, } +@Article{Eck2015, + author = {Vinzenz Gregor Eck and Wouter Paulus Donders and Jacob Sturdy and Jonathan Feinberg and Tammo Delhaas and Leif Rune Hellevik and Wouter Huberts}, + journal = {International Journal for Numerical Methods in Biomedical Engineering}, + title = {A guide to uncertainty quantification and sensitivity analysis for cardiovascular applications}, + year = {2015}, + number = {8}, + volume = {32}, + doi = {10.1002/cnm.2755}, +} + +@Article{Wicaksono2016, + author = {Damar Wicaksono and Omar Zerkak and Andreas Pautz}, + journal = {Nuclear Science and Engineering}, + title = {Global sensitivity analysis of transient code output applied to a reflood experiment model using the {TRACE} code}, + year = {2016}, + number = {3}, + pages = {400--429}, + volume = {184}, + doi = {10.13182/nse16-37}, +} + +@Article{Adelmann2019, + author = {Andreas Adelmann}, + journal = {{SIAM}/{ASA} Journal on Uncertainty Quantification}, + title = {On nonintrusive uncertainty quantification and surrogate model construction in particle accelerator modeling}, + year = {2019}, + number = {2}, + pages = {383--416}, + volume = {7}, + doi = {10.1137/16m1061928}, +} + +@Article{Castellon2023, + author = {Dario Fernandez Castellon and Aksel Fenerci and {\O}yvind Wiig Petersen and Ole {\O}iseth}, + journal = {Reliability Engineering {\&}amp$\mathsemicolon$ System Safety}, + title = {Full long-term buffeting analysis of suspension bridges using {Gaussian} process surrogate modelling and importance sampling {Monte Carlo} simulations}, + year = {2023}, + volume = {235}, + doi = {10.1016/j.ress.2023.109211}, +} + +@Article{Virtanen2020, + author = {Pauli Virtanen and Ralf Gommers and Travis E. Oliphant and Matt Haberland and Tyler Reddy and David Cournapeau and Evgeni Burovski and Pearu Peterson and Warren Weckesser and Jonathan Bright and St{\'{e}}fan J. van der Walt and Matthew Brett and Joshua Wilson and K. Jarrod Millman and Nikolay Mayorov and Andrew R. J. Nelson and Eric Jones and Robert Kern and Eric Larson and C J Carey and {\.{I}}lhan Polat and Yu Feng and Eric W. Moore and Jake VanderPlas and Denis Laxalde and Josef Perktold and Robert Cimrman and Ian Henriksen and E. A. Quintero and Charles R. Harris and Anne M. Archibald and Ant{\^{o}}nio H. Ribeiro and Fabian Pedregosa and Paul van Mulbregt and Aditya Vijaykumar and Alessandro Pietro Bardelli and Alex Rothberg and Andreas Hilboll and Andreas Kloeckner and Anthony Scopatz and Antony Lee and Ariel Rokem and C. Nathan Woods and Chad Fulton and Charles Masson and Christian Häggström and Clark Fitzgerald and David A. Nicholson and David R. Hagen and Dmitrii V. Pasechnik and Emanuele Olivetti and Eric Martin and Eric Wieser and Fabrice Silva and Felix Lenders and Florian Wilhelm and G. Young and Gavin A. Price and Gert-Ludwig Ingold and Gregory E. Allen and Gregory R. Lee and Herv{\'{e}} Audren and Irvin Probst and Jörg P. Dietrich and Jacob Silterra and James T Webber and Janko Slavi{\v{c}} and Joel Nothman and Johannes Buchner and Johannes Kulick and Johannes L. Schönberger and Jos{\'{e}} Vin{\'{\i}}cius de Miranda Cardoso and Joscha Reimer and Joseph Harrington and Juan Luis Cano Rodr{\'{\i}}guez and Juan Nunez-Iglesias and Justin Kuczynski and Kevin Tritz and Martin Thoma and Matthew Newville and Matthias Kümmerer and Maximilian Bolingbroke and Michael Tartre and Mikhail Pak and Nathaniel J. Smith and Nikolai Nowaczyk and Nikolay Shebanov and Oleksandr Pavlyk and Per A. Brodtkorb and Perry Lee and Robert T. McGibbon and Roman Feldbauer and Sam Lewis and Sam Tygier and Scott Sievert and Sebastiano Vigna and Stefan Peterson and Surhud More and Tadeusz Pudlik and Takuya Oshima and Thomas J. Pingel and Thomas P. Robitaille and Thomas Spura and Thouis R. Jones and Tim Cera and Tim Leslie and Tiziano Zito and Tom Krauss and Utkarsh Upadhyay and Yaroslav O. Halchenko and Yoshiki V{\'{a}}zquez-Baeza and}, + journal = {Nature Methods}, + title = {{SciPy} 1.0: fundamental algorithms for scientific computing in {Python}}, + year = {2020}, + number = {3}, + pages = {261--272}, + volume = {17}, + doi = {10.1038/s41592-019-0686-2}, + publisher = {Springer Science and Business Media {LLC}}, +} + +@Article{Harris2020, + author = {Charles R. Harris and K. Jarrod Millman and St{\'{e}}fan J. van der Walt and Ralf Gommers and Pauli Virtanen and David Cournapeau and Eric Wieser and Julian Taylor and Sebastian Berg and Nathaniel J. Smith and Robert Kern and Matti Picus and Stephan Hoyer and Marten H. van Kerkwijk and Matthew Brett and Allan Haldane and Jaime Fern{\'{a}}ndez del R{\'{\i}}o and Mark Wiebe and Pearu Peterson and Pierre G{\'{e}}rard-Marchant and Kevin Sheppard and Tyler Reddy and Warren Weckesser and Hameer Abbasi and Christoph Gohlke and Travis E. Oliphant}, + journal = {Nature}, + title = {Array programming with {NumPy}}, + year = {2020}, + number = {7825}, + pages = {357--362}, + volume = {585}, + doi = {10.1038/s41586-020-2649-2}, +} + @Comment{jabref-meta: databaseType:bibtex;} diff --git a/joss/paper.md b/joss/paper.md index f8ca0bc..7d6df93 100644 --- a/joss/paper.md +++ b/joss/paper.md @@ -2,12 +2,14 @@ title: 'UQTestFuns: A Python3 library of uncertainty quantification (UQ) test functions' tags: - Python + - test functions + - benchmark - uncertainty quantification - metamodeling - surrogate modeling - sensitivity analysis - reliability analysis - - rare-event estimations + - rare event estimation authors: - name: Damar Wicaksono orcid: 0000-0001-8587-7730 @@ -21,152 +23,158 @@ affiliations: index: 1 date: 30 June 2023 bibliography: paper.bib - -# Optional fields if submitting to a AAS journal too, see this blog post: -# https://blog.joss.theoj.org/2018/12/a-new-collaboration-with-aas-publishing -# aas-doi: 10.3847/xxxxx <- update this with the DOI from AAS once you know it. -# aas-journal: Astrophysical Journal <- The name of the AAS journal. --- # Summary -New methods and algorithms are continuously being developed within -the applied uncertainty quantification (UQ) framework whose activities include, -among others, metamodeling, uncertainty propagation, reliability analysis, -and sensitivity analysis. -During the development phase of such methods, researchers and developers often -rely on test functions taken from the literature -to validate a newly proposed method. -Afterward, these test functions are employed as a common ground to benchmark -the performance of the method with other methods in terms of -some accuracy measures with respect to the number of function evaluations. - -`UQTestFuns` is an open-source Python3 library of test functions commonly used -within the applied UQ community. Specifically, the package aims to provide: - -- a lightweight implementation, with minimal dependencies, - of many test functions available in the UQ literature; -- a single entry point, combining test functions and their probabilistic input - specifications, to a wide range of test functions; -- an opportunity for an open-source contribution where new test functions - are implemented and reference results are posted. - -All in all, `UQTestFuns` aims to save the researchers' and developers' time -from reimplementing many of the commonly used test functions and provide -a summary reference to the test functions and their applications. +Researchers are continuously developing novel methods and algorithms +in the field of applied uncertainty quantification (UQ). +During the development phase of a novel method or algorithm, +researchers and developers often rely on test functions +taken from the literature for validation purposes. +Afterward, they employ these test functions as a common ground +to compare the performance of the novel method +against that of the state-of-the-art methods +in terms of some accuracy and efficiency measures. + +`UQTestFuns` is an open-source Python3 library of test functions +commonly used within the applied UQ community. +Specifically, the package provides: + +- an **implementation with minimal dependencies** + (i.e., NumPy and SciPy) **and a common interface** of many test functions + available in the UQ literature +- a **single entry point** collecting test functions _and_ + their probabilistic input specifications in a single Python package +- an **opportunity for an open-source contribution** + where one can implement new test functions and post reference results. + +`UQTestFuns` aims to save the researchers' and developers' time +from having to reimplement many of the commonly used test functions +themselves. # Statement of need -New methods and algorithms for solving a particular type of UQ analyses -(reliability analysis, sensitivity analysis, etc.) are continuously being -developed. -While such methods are eventually aimed at solving -real-world problems—typically involve complex expensive-to-evaluate -computational models—researchers and developers may prefer to initially -use the so-called UQ test functions for validation and benchmarking purposes. - -UQ test functions are, in principle, mathematical functions taken -as black boxes; they take a set of input values and produce output values. -However, in a typical UQ analysis, the input variables are considered uncertain +The field of uncertainty quantification (UQ) in applied science and engineering +has grown rapidly in recent years. +Novel methods and algorithms for metamodeling (surrogate modeling), +reliability, and sensitivity analysis are being continuously developed. +While such methods are aimed at addressing real-world problems, +often involving large-scale complex computer models—from nuclear +[@Wicaksono2016] to civil engineering [@Castellon2023], +from physics [@Adelmann2019] to biomedicine [@Eck2015]—researchers +and developers may prefer to use the so-called UQ test functions +for validation and benchmarking purposes. + +UQ test functions are mathematical functions taken as black boxes; +they take a set of input values and produce output values. +In a typical UQ analysis, the input variables are considered _uncertain_ and thus modeled probabilistically. The results of a UQ analysis, in general, depend not only on the computational model under consideration but also on the specification of the input variables. Consequently, a UQ test function consists of both the specification -of the function as well as the probabilistic specification of the inputs. -These test functions are widely used because: - -- test functions are _fast to evaluate_, at least _faster_ - than their real-world counterparts; -- there are _many of them available_ in the literature - for various types of analyses; -- while a UQ analysis usually takes the computational model of interest - as a black box, the test functions _are known_ - (they are not black boxes per se) such that a thorough diagnosis - of a newly proposed method can be performed; -- test functions provide _a common ground_ for comparing the performance of - a given method with that of other available methods - in solving the same class of problems. - -Several online sources provide a wide range of test functions relevant to -the UQ community. For example, and by no means an exhaustive list: - -- The Virtual Library of Simulation Experiments (VLSE) [@VLSE:2013]: - This site is arguably the definitive repository - for (but not exclusively) UQ test functions. - It provides over a hundred test functions for numerous applications; - each test function is described on a dedicated page that includes - implementations in MATLAB and R. -- The Benchmark proposals of GdR [@GdR:2008]: - The site provides a series of documents that contain test function - specifications. -- The Benchmark page of UQWorld [@UQWorld:2019]: - This community site provides a selection of test functions - in metamodeling, sensitivity analysis, and reliability analysis along - with their implementation in MATLAB. -- RPrepo—a reliability problems repository [@Rozsas:2019]: - This repository contains numerous reliability analysis test functions - implemented in Python. - -Common to all these online resources are the requirements -to implement the test function oneself following the specification, -or, when available, to download each of the test functions separately -(except for RPrepo). - -Alternatively, UQ analysis packages are often shipped -with a selection of test functions either -for illustration, validation, or benchmarking. -Below are some examples from the applied UQ community in Python -(the numbers are as of 2023-06-30; once again, -the list is non-exhaustive): - -- SALib [@Herman2017; @Iwanaga2022] includes 6 test functions +of the function as well as probabilistic distribution specification +of the inputs. + +UQ test functions are widely used in the community for several reasons: + +- Test functions are _fast to evaluate_, + at least _faster_ than their real-world counterparts. +- There are many test functions _available in the literature_ + for various types of analyses. +- Although test functions are taken as black boxes, + _the features of test functions are known_; + this knowledge enables a thorough diagnosis of a method. +- Test functions provide _a common ground_ for comparing the performance + of various methods in solving the same class of problems. + +Several efforts have been made to provide relevant UQ test functions +to the community. +For instance, researchers may refer to the following online resources +to obtain UQ test functions (the list is by no means exhaustive): + +- The Virtual Library of Simulation Experiments (VLSE) [@VLSE:2013]: This site + is arguably the definitive repository for (but not exclusively) UQ test + functions. It provides over a hundred test functions + for numerous applications; each test function is described + on a dedicated page that includes implementations in MATLAB and R. +- The Benchmark proposals of GdR [@GdR:2008]: The site provides a series of + documents that contain test function specifications. +- The Benchmark page of UQWorld [@UQWorld:2019]: This community site provides + a selection of test functions for metamodeling, sensitivity analysis, + and reliability analysis exercises along with their implementation in MATLAB. +- RPrepo—a reliability problems repository [@Rozsas:2019]: This + repository contains numerous reliability analysis test functions implemented + in Python. It is not, however, a stand-alone Python package. + +Using these online resources, one either needs to download each test function +separately[^rprepo] or implement the test functions following +the provided formula (in the programming language of their choice). + +As an alternative way for obtaining test functions, +UQ analysis packages are often shipped with a selection of test functions +of their own either for illustration, validation, or benchmarking purposes. +Examples from the applied UQ community in the Python ecosystem are +(the numbers are as of 2023-06-30; once again, the list is non-exhaustive): + +- SALib [@Herman2017; @Iwanaga2022]: Six test functions mainly for illustrating the package capabilities in the examples. -- PyApprox [@Jakeman2019] includes 18 test functions, +- PyApprox [@Jakeman2019]: 18 test functions, including some non-algebraic functions for benchmarking purposes. -- Surrogate Modeling Toolbox (SMT) [@Bouhlel2019; @Bouhlel2023] includes +- Surrogate Modeling Toolbox (SMT) [@Bouhlel2019; @Bouhlel2023]: 11 analytical and engineering problems for benchmarking purposes. -- OpenTURNS [@Baudin2017] has its own separate benchmark package - called `otbenchmark` [@Fekhari2021; @Baudin2021] that includes - 37 test functions. - -These open-source packages already provide a wide variety of functions -implemented in Python. -The problem is that these functions are part of the respective package. -To get access to the test functions belonging to a package, -the whole analysis package must be installed first. -Furthermore, test functions from a given package -are often implemented in such a way -that is tightly coupled with the package itself. -To use the test functions belonging to a package, -one may need to learn some basic usage and specific terminologies of the package. - -`UQTestFuns` is mostly comparable to the package `otbenchmark` in its aim. -Both also acknowledge the particularity of UQ test functions -that requires combining a test function -and the corresponding probabilistic input specification. +- OpenTURNS [@Baudin2017]: 37 test functions packaged separately + as `otbenchmark` [@Fekhari2021; @Baudin2021] for benchmarking purposes. + +These open-source packages already provide a wide variety of functions +implemented in Python. Except for `otbenchmark`, the problem is that +these functions are part of the respective package. To get access to +the test functions belonging to a package, the whole analysis package +must be installed first. Furthermore, test functions from a given package are +often implemented in such a way that is tightly coupled with the package +itself. To use or extend the test functions belonging to an analysis package, +one may need to first learn some basic usage and specific terminologies +of the package. + +`UQTestFuns` aims to solve this problem by collecting UQ test functions +into a single Python package +with a few dependencies (i.e., NumPy [@Harris2020] +and SciPy [@Virtanen2020]). +The package enables researchers +to conveniently access commonly used UQ test functions implemented in Python. +Thanks to a common interface, +researchers can use the available test functions +and extend the package with new test functions with minimal overhead. + +Regarding its aim, `UQTestFuns` is mostly comparable +to the package `otbenchmark`. +Both also acknowledge the particularity of UQ test functions that requires +combining a test function and the corresponding probabilistic input +specification. There are, however, some major differences: -- `UQTestFuns` is more lightweight with fewer dependencies, - while `otbenchmark` is coupled to the package OpenTURNS. - This is to be expected as one of `otbenchmark`'s main goals - is to provide the OpenTURNS development team with a tool - for helping with the implementation of new algorithms. -- `UQTestFuns` is more modest in its scope, that is, simply to provide +- `UQTestFuns` has fewer dependencies and is more lean + in its implementations, while `otbenchmark` is built on top of + and coupled to OpenTURNS. + This is to be expected as one of `otbenchmark`'s main goals is to provide + the OpenTURNS development team with a tool for helping + with the implementation of new algorithms. +- `UQTestFuns` is more modest in its scope, that is, simply to provide a library of UQ test functions implemented in Python - with a consistent interface and an online reference - (similar to that of VLSE [@VLSE:2013]), and not, - as in the case of `otbenchmark`, + with a consistent interface and an online reference + (similar to that of VLSE [@VLSE:2013]), + and not, as in the case of `otbenchmark`, an automated benchmark framework[^benchmark] [@Fekhari2021]. # Package overview Consider a computational model that is represented as an $M$-dimensional black-box function: -\begin{equation*} -\mathcal{M}: \mathbf{x} \in \mathcal{D}_{\mathbf{X}} \subseteq \mathbb{R}^M \mapsto y = \mathcal{M}(\mathbf{x}), -\end{equation*} +$$ +\mathcal{M}: \mathbf{x} \in \mathcal{D}_{\mathbf{X}} \subseteq \mathbb{R}^M \mapsto y = \mathcal{M}(\mathbf{x}), +$$ where $\mathcal{D}_{\mathbf{X}}$ and $y$ denote the input domain -and the quantity of interest (QoI), respectively. +and the quantity of interest (QoI), respectively. In practice, the exact values of the input variables are not exactly known and may be considered uncertain. @@ -174,52 +182,55 @@ The ensuing analysis involving uncertain input variables can be formalized in the uncertainty quantification (UQ) framework following @Sudret2007 as illustrated in \autoref{fig:uq-framework}. -![Uncertainty quantification (UQ) framework, adapted from @Sudret2007.\label{fig:uq-framework}](uq-framework.png){ width=80% } +![Uncertainty quantification (UQ) framework, adapted from @Sudret2007.\label{fig:uq-framework}](uq-framework.png){ width=70% } -The framework starts with the computational model $\mathcal{M}$ -taken as a black box as defined above. -Then, it moves on to the quantification of the input uncertainties. -This uncertainty is modeled probabilistically -such that the input variables are represented as a random vector -equipped with a joint probability density function (PDF) +The framework starts from the center, +with the computational model $\mathcal{M}$ taken as a black box +as defined above. +Then it moves on to the probabilistic modeling +of the (uncertain) input variables. +Under the probabilistic modeling, +the uncertain input variables are replaced by a random vector equipped +with a joint probability density function (PDF) $f_{\mathbf{X}}: \mathbf{x} \in \mathcal{D}_{\mathbf{X}} \subseteq \mathbb{R}^M \mapsto \mathbb{R}$. -Afterward, the uncertainties from the input variables are propagated through +Afterward, the uncertainties of the input variables are propagated through the computational model $\mathcal{M}$. -The quantity of interest $y$, as computed by $\mathcal{M}$, -now becomes a random variable: +The quantity of interest $y$ now becomes a random variable: +$$ +Y = \mathcal{M}(\mathbf{X}),\; \mathbf{X} \sim f_{\mathbf{X}}. $$ -Y = \mathcal{M}(\mathbf{X}),\; \mathbf{X} \sim f_{\mathbf{X}}. -$$ -This leads to various downstream analyses such as reliability analysis, -sensitivity analysis, and metamodeling. +This leads to various downstream analyses such as _reliability analysis_, +_sensitivity analysis_, +and _metamodeling_ (or _surrogate modeling_). In `UQTestFuns`, these are currently the three main classifications of UQ test functions by their applications in the literature[^classifications]. ## Reliability analysis -Consider the circular pipe crack reliability test function, -a $2$-dimensional function for testing reliability analysis methods -[@Verma2015; @Li2018]: -$$ -g(\mathbf{x}; \mathbf{p}) = \mathcal{M}(\mathbf{x}; t, R) - M = 4 t \sigma_f R^2 \left( \cos{\left(\frac{\theta}{2}\right)} - \frac{1}{2} \sin{(\theta)} \right) - M -$$ -where $\mathbf{x} = \{ \sigma_f, \theta \}$ is the two-dimensional vector of -input variables probabilistically defined further below; +To illustrate the test functions included in `UQTestFuns`, +consider the circular pipe crack reliability problem, a $2$-dimensional +function for testing reliability analysis methods [@Verma2015; @Li2018]: +$$ +g(\mathbf{x}; \mathbf{p}) = \mathcal{M}(\mathbf{x}; t, R) - M = 4 t \sigma_f R^2 \left( \cos{\left(\frac{\theta}{2}\right)} - \frac{1}{2} \sin{(\theta)} \right) - M, +$$ +where $\mathbf{x} = \{ \sigma_f, \theta \}$ is the two-dimensional vector +of input variables probabilistically defined further below; and $\mathbf{p} = \{ t, R, M \}$ is the vector of (deterministic) parameters. - + In a reliability analysis problem, -a computational model $\mathcal{M}$ is often combined with another set of parameters -(either uncertain or deterministic) to define the so-called -_performance function_ or _limit-state function_ of a system denoted by $g$. -The task of reliability analysis methods is to estimate the failure probability -of the system defined as follows [@Sudret2012]: -$$ -P_f = \mathbb{P}[g(\mathbf{X}; \mathbf{p}) \leq 0] = \int_{\mathcal{D}_f = \{ \mathbf{x} | g(\mathbf{x}; \mathbf{p}) \leq 0 \}} f_{\mathbf{X}} (\mathbf{x}) \; d\mathbf{x} -$$ -where $g(\mathbf{x}) \leq 0.0$ is defined as a _failure state_. -The difficulty of evaluating the integral above stems from the fact -that the integration domain $D_f$ is defined implicitly. +a computational model $\mathcal{M}$ is often combined +with another set of parameters (either uncertain or deterministic) +to define the so-called _performance function_ or _limit-state function_ +of a system denoted by $g$. +The task for a reliability analysis method is to estimate the failure +probability of the system defined as [@Sudret2012]: +\begin{equation}\label{eq:pf} +P_f = \mathbb{P}[g(\mathbf{X}; \mathbf{p}) \leq 0] = \int_{\mathcal{D}_f = \{ \mathbf{x} | g(\mathbf{x}; \mathbf{p}) \leq 0 \}} f_{\mathbf{X}} (\mathbf{x}) \; d\mathbf{x}, +\end{equation} +where $g(\mathbf{x}; \mathbf{p}) \leq 0.0$ is defined as a _failure state_. +The difficulty of evaluating the integral above stems +from the fact that the integration domain $D_f$ is defined implicitly. The circular pipe crack problem can be created in `UQTestFuns` as follows: ```python @@ -228,17 +239,19 @@ The circular pipe crack problem can be created in `UQTestFuns` as follows: ``` The resulting instance is _callable_ and can be called -with a set of input values. -As explained, the probabilistic input model is an integral part -of a UQ test function. -In `UQTestFuns`, the input model according to the original specification -is attached to the instance of the test function: +with a set of valid input values. +The probabilistic input model is an integral part of a UQ test function; +indeed, according to \autoref{eq:pf}, the analysis results depend on it. +Therefore, in `UQTestFuns`, the input model following +the original specification is always attached to the instance +of the test function: ``` >>> print(circular_pipe.prob_input) Name : CircularPipeCrack-Verma2015 Spatial Dim. : 2 Description : Input model for the circular pipe crack problem from Verma... Marginals : + No. Name Distribution Parameters Description ----- ------- -------------- ----------------- -------------------- 1 sigma_f normal [301.079 14.78 ] flow stress [MNm] @@ -246,52 +259,60 @@ Marginals : Copulas : None ``` -The probabilistic input model instance can be used, among other things, -to transform a set of values in a given domain -(say, the unit hypercube $[0, 1]^M$) to the domain of the test function. +This probabilistic input model instance can be used to transform +a set of values in a given domain (say, the unit hypercube $[0, 1]^M$) +to the domain of the test function. The limit-state surface (i.e., where $g(\mathbf{x}) = 0$) for the circular pipe crack problem is shown in \autoref{fig:reliability} -(left plot). -In the middle, $10^6$ random sample points are overlaid; +(left plot). In the middle plot, $10^6$ random sample points are overlaid; each point is classified whether it is in failure (red) or safe domain (blue). -As illustrated in the histogram (right plot), -the task of the analysis is to accurately estimate the probability -where $g(\mathbf{X}) \leq 0$ -and with as few function evaluations as possible. +The histogram (right plot) shows the proportion of points +that fall in the failure and safe domain. ![Illustration of reliability analysis: Circular pipe crack problem.\label{fig:reliability}](reliability.png){ width=90% } +As illustrated in the previous series of plots, +the task for a reliability analysis method is to estimate +the probability where $g(\mathbf{X}) \leq 0$ as accurately +and with as few function evaluations as possible. +`UQTestFuns` includes test functions used in reliability analysis exercises +in various dimensions having different complexities of the limit-state surface. + ## Sensitivity analysis -Consider the Sobol'-G function, -an $M$-dimensional function commonly used -for testing sensitivity analysis methods [@Saltelli1995]: +As another illustration, this time in the context of sensitivity analysis, +consider the Sobol'-G function, an established sensitivity analysis +test function [@Saltelli1995] included in `UQTestFuns`: $$ \mathcal{M}(\mathbf{x}) = \prod_{m = 1}^M \frac{\lvert 4 x_m - 2 \rvert + a_m}{1 + a_m}, $$ where $\mathbf{x} = \{ x_1, \ldots, x_M \}$ is the $M$-dimensional vector of independent uniform random variables in $[0, 1]^M$; and $\mathbf{a} = \{ a_m = \frac{m - 1}{2.0}, m = 1, \ldots, M \}$ -is the set of parameters. +is the set of (deterministic) parameters. -The tasks of sensitivity analysis methods are to ascertain -either qualitatively or quantitatively -the most important input variables (for _factor prioritization_) -and/or the least important input variables -(for _factor fixing_). -See @Saltelli2007 and @Iooss2015 for details on the topic. - -The Sobol'-G test function can be created in `UQTestFuns` -for any given dimension. +Unlike the previous test function example, +the Sobol'-G test function is a variable-dimension test function +and can be defined for any given dimension. For instance, to create a $6$-dimensional Sobol'-G function: -```python ->>> sobol_g = uqtf.SobolG(spatial_dimension=6) +```python +>>> sobol_g = uqtf.SobolG(spatial_dimension=6) ``` As before, the probabilistic input model of the function as prescribed in the original specification is attached to the instance of the test function (i.e., the `prob_input` property). +The task of a sensitivity analysis method is to ascertain either qualitatively +or quantitatively the most important input variables +(for _factor prioritization_) or the least important input variables +(for _factor fixing/screening_); +for details on this topic, please refer to @Saltelli2007 and @Iooss2015. +`UQTestFuns` includes test functions used in sensitivity analysis exercises +in various dimensions ($M > 1$) +having different complexities in terms of the interactions +between input variables. + ## Metamodeling In practice, the computational model $\mathcal{M}$ is often complex. @@ -334,6 +355,8 @@ by the Saxony State Parliament. # References +[^rprepo]: except for RPrepo, which allows for downloading the whole repository. + [^benchmark]: A fully functional benchmark suite may, however, be in the future built on top of `UQTestFuns`. From c8f44104d2bc3e056284da39cb962c9123d3ea4d Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Thu, 6 Jul 2023 19:37:27 +0200 Subject: [PATCH 48/73] Update the docs following the JOSS paper draft. - Such that the information between the two is consistent. - Fix minor typo in the parameters of the Sobol'-G function. --- CHANGELOG.md | 9 +++ docs/fundamentals/sensitivity.md | 29 +++++----- .../about-uq-test-functions.md | 58 ++++++++++--------- docs/index.md | 14 ++--- docs/references.bib | 11 ++++ docs/test-functions/sobol-g.md | 22 +++---- 6 files changed, 85 insertions(+), 58 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d246962..9083edf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Some background information in the documentation has been changed + to match the description in the JOSS paper draft. + +### Fixed + +- A mistake in one the parameter values of the Sobol'-G function has been fixed. + ## [0.3.0] - 2023-07-03 ### Added diff --git a/docs/fundamentals/sensitivity.md b/docs/fundamentals/sensitivity.md index 4f624ca..c19ee2e 100644 --- a/docs/fundamentals/sensitivity.md +++ b/docs/fundamentals/sensitivity.md @@ -17,19 +17,22 @@ kernelspec: The table below listed the available test functions typically used in the comparison of sensitivity analysis methods. -| Name | Spatial Dimension | Constructor | -|:------------------------------------------------------------:|:-----------------:|:--------------------:| -| {ref}`Borehole ` | 8 | `Borehole()` | -| {ref}`Bratley et al. (1992) D ` | M | `Bratley1992d()` | -| {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | -| {ref}`Flood ` | 8 | `Flood()` | -| {ref}`Ishigami ` | 3 | `Ishigami()` | -| {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | -| {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | -| {ref}`Sobol'-G ` | M | `SobolG()` | -| {ref}`Sulfur ` | 9 | `Sulfur()` | -| {ref}`Welch et al. (1992) ` | 20 | `Welch1992()` | -| {ref}`Wing Weight ` | 10 | `WingWeight()` | +| Name | Spatial Dimension | Constructor | +|:---------------------------------------------------------------------:|:-----------------:|:--------------------:| +| {ref}`Borehole ` | 8 | `Borehole()` | +| {ref}`Bratley et al. (1992) A ` | M | `Bratley1992a()` | +| {ref}`Bratley et al. (1992) B ` | M | `Bratley1992b()` | +| {ref}`Bratley et al. (1992) C ` | M | `Bratley1992c()` | +| {ref}`Bratley et al. (1992) D ` | M | `Bratley1992d()` | +| {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | +| {ref}`Flood ` | 8 | `Flood()` | +| {ref}`Ishigami ` | 3 | `Ishigami()` | +| {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | +| {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | +| {ref}`Sobol'-G ` | M | `SobolG()` | +| {ref}`Sulfur ` | 9 | `Sulfur()` | +| {ref}`Welch et al. (1992) ` | 20 | `Welch1992()` | +| {ref}`Wing Weight ` | 10 | `WingWeight()` | In a Python terminal, you can list all the available functions relevant for metamodeling applications using ``list_functions()`` and filter the results diff --git a/docs/getting-started/about-uq-test-functions.md b/docs/getting-started/about-uq-test-functions.md index 9d3c816..175bc7c 100644 --- a/docs/getting-started/about-uq-test-functions.md +++ b/docs/getting-started/about-uq-test-functions.md @@ -62,7 +62,7 @@ Granted, this model is an oversimplification of the real situation and most prob But as far as a test function goes, this function exhibits some challenging features for a UQ analysis method. Specifically, the function: -- is multidimensional +- is multi-dimensional - contains non-uniform random variables - involves some interaction terms between the input variables @@ -76,24 +76,25 @@ science and engineering encompasses many activities, including uncertainty propagation, sensitivity analysis, reliability analysis, optimization, etc. New methods for each of the UQ analyses are continuously being developed. -Although such a method is eventually aimed at solving -real-world problems—typically involved a complex expensive-to-evaluate computational model—, -during the development phase, -developers prefer to use the so-called _test functions_ -for validation and benchmarking purposes because: +Although such a method is eventually aimed at addressing +real-world problems—typically involved a complex expensive-to-evaluate computational model— +researchers and developers may prefer to use the so-called _test functions_ +during the development phase for validation and benchmarking purposes. ```{margin} Many UQ test functions have analytical forms, but this is not in any way a requirement ``` -- test functions are _fast to evaluate_, at least, _faster_ than the real ones -- there are _many of them available_ in the literature - for various types of analyses -- while a UQ analysis usually takes the computational model of interest as a blackbox, - the _test functions are known_ - (and for some, the results are also analytically known) - such that developers can do proper diagnostics based on particular structures - of the function +In particular, UQ test functions are widely used in the community for several +reasons: + +- Test functions are _fast to evaluate_, at least, _faster_ than the real ones. +- There are _many of them available_ in the literature + for various types of analyses. +- Although test functions are taken as black boxes, _their features are known_; + this enables a thorough diagnosis of a UQ method. +- Test functions provide _a common ground_ for comparing the performance of + various UQ methods in solving the same class of problems. Assuming that real models are expensive to evaluate, the cost of analysis is typically measured in the number of function evaluations @@ -107,7 +108,7 @@ Online resources to get UQ test functions Several online resources provide a wide range of test functions relevant to the applied UQ community. -For example: +For example and by no means an exhaustive list: - The [Virtual Library of Simulation Experiments: Test Functions and Datasets](https://www.sfu.ca/~ssurjano/index.html) is the definitive repository for UQ test functions. @@ -122,10 +123,14 @@ For example: Although the implementations themselves are of generic MATLAB, they are geared towards usage in [UQLab](https://uqlab.com) (a framework for UQ in MATLAB). +- [RPrepo](https://gitlab.com/rozsasarpi/rprepo)—a reliability problems: This + repository contains numerous reliability analysis test functions implemented + in Python. It is not, however, a stand-alone Python package. Common to all these online resources are the requirement to either: -- implement the test function oneself following the specification, or +- implement the test function oneself following the specification + in the programming language of choice, or - when available, download each of the test functions separately. Both are neither time-efficient nor convenient. @@ -137,9 +142,11 @@ Alternative sources of UQ test functions: test functions inside an analysis pack Alternatively, in a given programming language, some UQ analysis packages are often shipped with a selection of test functions either for illustration, validation, or benchmarking. -Some examples within the Python UQ community are -(the data below is as of 2023-02-28): +Some examples from the applied UQ community in the Python ecosystem are +(the data below is as of 2023-06-30): +- [SALib](https://github.com/SALib/SALib) includes six test functions mainly + for illustrating the package capabilities in the examples {cite}`Herman2017`. - [UncertainPy](https://github.com/simetenn/uncertainpy) includes 8 test functions (mainly in the context of neuroscience) for illustrating the package capabilities {cite}`Tennoee2018`. @@ -198,15 +205,12 @@ We think "yes" And yet, we think none of them is satisfactory. Specifically, none of them provides: -- _a lightweight implementation_ (with minimal dependencies) - of many test functions available in the UQ literature; - this means our package will be free of any implementations - of any UQ analysis methods resulting in a minimal overhead - in setting up the test functions, -- _a single entry point_ (combining models and input specification) - to a wide range of test functions, -- an opportunity for an _open-source contribution_ where new test functions are - added and new reference results are posted. +- an implementation _with minimal dependencies_ (i.e., NumPy and SciPy) and + _a common interface_ of many test functions available in the UQ literature +- a _single entry point_ collecting test functions _and_ their probabilistic + input specifications in a single Python package +- an _opportunity for an open-source contribution_, supporting + the implementation of new test functions or posting reference results. Satisfying all the above requirements is exactly the goal of the UQTestFuns package. diff --git a/docs/index.md b/docs/index.md index 4a782bc..3d7725a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,14 +2,14 @@ UQTestFuns is an open-source Python3 library of test functions commonly used within the applied uncertainty quantification (UQ) community. -The package aims to provide: +Specifically, the package provides: -- a _lightweight implementation_ (with minimal dependencies) - of many test functions available in the UQ literature -- a _single entry point_ (combining models and their probabilistic input - specification) to a wide range of test functions -- an _opportunity for an open-source contribution_ where new test functions and - reference results are posted. +- an implementation _with minimal dependencies_ (i.e., NumPy and SciPy) and + _a common interface_ of many test functions available in the UQ literature +- a _single entry point_ collecting test functions _and_ their probabilistic + input specifications in a single Python package +- an _opportunity for an open-source contribution_, supporting + the implementation of new test functions or posting reference results. ::::{grid} :gutter: 2 diff --git a/docs/references.bib b/docs/references.bib index 61db6ee..25dccf5 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -644,4 +644,15 @@ @InProceedings{Sudret2012 doi = {10.3850/978-981-07-2219-7_p321}, } +@Article{Herman2017, + author = {Jon Herman and Will Usher}, + journal = {The Journal of Open Source Software}, + title = {{SALib}: An open-source python library for sensitivity analysis}, + year = {2017}, + number = {9}, + pages = {97}, + volume = {2}, + doi = {10.21105/joss.00097}, +} + @Comment{jabref-meta: databaseType:bibtex;} diff --git a/docs/test-functions/sobol-g.md b/docs/test-functions/sobol-g.md index 3a233e6..ea384db 100644 --- a/docs/test-functions/sobol-g.md +++ b/docs/test-functions/sobol-g.md @@ -158,17 +158,17 @@ input variable. There are several sets of parameters used in the literature as shown in the table below. -| No. | Value | Keyword | Source | Remark | -|:---:|:--------------------------------------------------------------:|:--------------------------:|:--------------------------------------------------------------:|:----------------------------------------------------------------------------------------------:| -| 1 | $a_1 = \ldots = a_M = 0$ | `Saltelli1995-1` | {cite}`Saltelli1995` (Example 1) (also {cite}`Bratley1992`) | All input variables are equally important | -| 2 | $a_1 = a_2 = 0.0$
$a_3 = 3$
$a_3 = \ldots = a_M = 9.9$ | `Saltelli1995-2` | {cite}`Saltelli1995` (Example 2) | The first two are important, the next is moderately important, and the rest is non-influential | -| 3 | $a_m = \frac{m - 1}{2.0}$
$1 \leq m \leq M$ | `Saltelli1995-3` (default) | {cite}`Saltelli1995` (Example 3) (also {cite}`Crestaux2007` ) | The most important input is the first one, the least is the last one | -| 4 | $a_1 = \ldots = a_M = 0.01$ | `Sobol1998-1` | {cite}`Sobol1998` (choice 1) | The supremum of the function grows exponentially at $2^M$ | -| 5 | $a_1 = \ldots = a_M = 1.0$ | `Sobol1998-2` | {cite}`Sobol1998` (choice 2) | The supremum of the function grows exponentially at $1.5^M$ | -| 6 | $a_m = m$
$\, 1 \leq m \leq M$ | `Sobol1998-3` | {cite}`Sobol1998` (choice 3) | The supremum of the function grows linearly at $1 + \frac{M}{2}$ | -| 7 | $a_m = m^2$
$1 \leq m \leq M$ | `Sobol1998-4` | {cite}`Sobol1998` (choice 4) | The supremum is bounded at $1.0$ | -| 8 | $a_1 = a_2 = 0.0$
$a_3 = \ldots = a_M = 6.52$ | `Kucherenko2011-2a` | {cite}`Kucherenko2011` (Problem 2A) | Originally, $M = 100$ | -| 9 | $a_m = 6,52$
$1 \leq m \leq M$ | `Kucherenko2011-3b` | {cite}`Kucherenko2011` (Problem 3B) | | +| No. | Value | Keyword | Source | Remark | +|:---:|:----------------------------------------------------------:|:--------------------------:|:--------------------------------------------------------------:|:----------------------------------------------------------------------------------------------:| +| 1 | $a_1 = \ldots = a_M = 0$ | `Saltelli1995-1` | {cite}`Saltelli1995` (Example 1) (also {cite}`Bratley1992`) | All input variables are equally important | +| 2 | $a_1 = a_2 = 0$
$a_3 = 3$
$a_3 = \ldots = a_M = 9$ | `Saltelli1995-2` | {cite}`Saltelli1995` (Example 2) | The first two are important, the next is moderately important, and the rest is non-influential | +| 3 | $a_m = \frac{m - 1}{2.0}$
$1 \leq m \leq M$ | `Saltelli1995-3` (default) | {cite}`Saltelli1995` (Example 3) (also {cite}`Crestaux2007` ) | The most important input is the first one, the least is the last one | +| 4 | $a_1 = \ldots = a_M = 0.01$ | `Sobol1998-1` | {cite}`Sobol1998` (choice 1) | The supremum of the function grows exponentially at $2^M$ | +| 5 | $a_1 = \ldots = a_M = 1.0$ | `Sobol1998-2` | {cite}`Sobol1998` (choice 2) | The supremum of the function grows exponentially at $1.5^M$ | +| 6 | $a_m = m$
$\, 1 \leq m \leq M$ | `Sobol1998-3` | {cite}`Sobol1998` (choice 3) | The supremum of the function grows linearly at $1 + \frac{M}{2}$ | +| 7 | $a_m = m^2$
$1 \leq m \leq M$ | `Sobol1998-4` | {cite}`Sobol1998` (choice 4) | The supremum is bounded at $1.0$ | +| 8 | $a_1 = a_2 = 0.0$
$a_3 = \ldots = a_M = 6.52$ | `Kucherenko2011-2a` | {cite}`Kucherenko2011` (Problem 2A) | Originally, $M = 100$ | +| 9 | $a_m = 6,52$
$1 \leq m \leq M$ | `Kucherenko2011-3b` | {cite}`Kucherenko2011` (Problem 3B) | | ```{note} The parameter values used in {cite}`Marrel2008` and {cite}`Marrel2009` From 48eac717e102bb75288695a50aa079147cd9dc69 Mon Sep 17 00:00:00 2001 From: Damar Canggih Wicaksono Date: Thu, 6 Jul 2023 19:04:19 +0200 Subject: [PATCH 49/73] Latest update based on M. Hecht's comments. --- joss/paper.md | 39 ++++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/joss/paper.md b/joss/paper.md index 7d6df93..8da4d87 100644 --- a/joss/paper.md +++ b/joss/paper.md @@ -35,7 +35,7 @@ taken from the literature for validation purposes. Afterward, they employ these test functions as a common ground to compare the performance of the novel method against that of the state-of-the-art methods -in terms of some accuracy and efficiency measures. +in terms of accuracy and efficiency measures. `UQTestFuns` is an open-source Python3 library of test functions commonly used within the applied UQ community. @@ -46,8 +46,8 @@ Specifically, the package provides: available in the UQ literature - a **single entry point** collecting test functions _and_ their probabilistic input specifications in a single Python package -- an **opportunity for an open-source contribution** - where one can implement new test functions and post reference results. +- an **opportunity for an open-source contribution**, supporting + the implementation of new test functions and posting reference results. `UQTestFuns` aims to save the researchers' and developers' time from having to reimplement many of the commonly used test functions @@ -82,11 +82,10 @@ UQ test functions are widely used in the community for several reasons: at least _faster_ than their real-world counterparts. - There are many test functions _available in the literature_ for various types of analyses. -- Although test functions are taken as black boxes, - _the features of test functions are known_; - this knowledge enables a thorough diagnosis of a method. +- Although test functions are taken as black boxes, _their features are known_; + this knowledge enables a thorough diagnosis of a UQ method. - Test functions provide _a common ground_ for comparing the performance - of various methods in solving the same class of problems. + of various UQ methods in solving the same class of problems. Several efforts have been made to provide relevant UQ test functions to the community. @@ -108,12 +107,12 @@ to obtain UQ test functions (the list is by no means exhaustive): in Python. It is not, however, a stand-alone Python package. Using these online resources, one either needs to download each test function -separately[^rprepo] or implement the test functions following -the provided formula (in the programming language of their choice). +separately[^rprepo] or implement the functions following +the provided formula (in the programming language of choice). As an alternative way for obtaining test functions, UQ analysis packages are often shipped with a selection of test functions -of their own either for illustration, validation, or benchmarking purposes. +of their own, either for illustration, validation, or benchmarking purposes. Examples from the applied UQ community in the Python ecosystem are (the numbers are as of 2023-06-30; once again, the list is non-exhaustive): @@ -153,12 +152,11 @@ combining a test function and the corresponding probabilistic input specification. There are, however, some major differences: -- `UQTestFuns` has fewer dependencies and is more lean - in its implementations, while `otbenchmark` is built on top of - and coupled to OpenTURNS. - This is to be expected as one of `otbenchmark`'s main goals is to provide - the OpenTURNS development team with a tool for helping - with the implementation of new algorithms. +- One of the `otbenchmark`'s main aims is to provide the OpenTURNS development + team with a tool for helping with the implementation of new algorithms. + As such, it is built on top of and coupled to OpenTURNS. + `UQTestFuns`, on the other hand, has fewer dependencies and is more lean + in its implementations. - `UQTestFuns` is more modest in its scope, that is, simply to provide a library of UQ test functions implemented in Python with a consistent interface and an online reference @@ -275,7 +273,7 @@ that fall in the failure and safe domain. As illustrated in the previous series of plots, the task for a reliability analysis method is to estimate the probability where $g(\mathbf{X}) \leq 0$ as accurately -and with as few function evaluations as possible. +and with as few model evaluations as possible. `UQTestFuns` includes test functions used in reliability analysis exercises in various dimensions having different complexities of the limit-state surface. @@ -306,12 +304,11 @@ in the original specification is attached to the instance of the test function The task of a sensitivity analysis method is to ascertain either qualitatively or quantitatively the most important input variables (for _factor prioritization_) or the least important input variables -(for _factor fixing/screening_); +(for _factor fixing/screening_) with as few model evaluations as possible; for details on this topic, please refer to @Saltelli2007 and @Iooss2015. `UQTestFuns` includes test functions used in sensitivity analysis exercises -in various dimensions ($M > 1$) -having different complexities in terms of the interactions -between input variables. +in various dimensions having different complexities +in terms of the interactions between input variables. ## Metamodeling From 9a5590acc7a26b836694a69e533836518a00a539 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Thu, 6 Jul 2023 19:54:13 +0200 Subject: [PATCH 50/73] Add Python v3.11 as a TOX environment for testing. - The GitHub action as well as the setup.cfg has been updated accordingly. - Rename the GitHub actions workflow for testing and packaging. --- .github/workflows/main.yml | 4 +++- setup.cfg | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9c3759c..409480f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: Packaging +name: Testing and packaging on: - push @@ -52,6 +52,8 @@ jobs: toxenv: "py38" - version: "3.9" toxenv: "py39" + - version: "3.11" + toxenv: "py311" steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4.0.0 diff --git a/setup.cfg b/setup.cfg index 39670b6..1f7f518 100644 --- a/setup.cfg +++ b/setup.cfg @@ -84,6 +84,7 @@ envlist = py38 py39 py310 + py311 [testenv] deps = From 7ba3d0b39cf35b50d577b99b267358b896c99a81 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Fri, 7 Jul 2023 10:46:58 +0200 Subject: [PATCH 51/73] Add an implementation of the convex failure domain problem. - The problem is a two-dimensional function for reliability analysis exercises from Borri and Speranzini (1997). --- CHANGELOG.md | 5 + docs/_toc.yml | 2 + docs/fundamentals/reliability.md | 1 + docs/references.bib | 11 + docs/test-functions/available.md | 1 + docs/test-functions/convex-fail-domain.md | 249 ++++++++++++++++++ src/uqtestfuns/test_functions/__init__.py | 2 + .../test_functions/convex_fail_domain.py | 88 +++++++ 8 files changed, 359 insertions(+) create mode 100644 docs/test-functions/convex-fail-domain.md create mode 100644 src/uqtestfuns/test_functions/convex_fail_domain.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 9083edf..4bfcaab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- The two-dimensional convex failure domain problem for reliability + analysis from Borri and Speranzini (1997). + ### Changed - Some background information in the documentation has been changed diff --git a/docs/_toc.yml b/docs/_toc.yml index dac55e0..797ee52 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -41,6 +41,8 @@ parts: title: Cantilever Beam (2D) - file: test-functions/circular-pipe-crack title: Circular Pipe Crack + - file: test-functions/convex-fail-domain + title: Convex Failure Domain - file: test-functions/damped-oscillator title: Damped Oscillator - file: test-functions/damped-oscillator-reliability diff --git a/docs/fundamentals/reliability.md b/docs/fundamentals/reliability.md index 3cef637..3a4b41e 100644 --- a/docs/fundamentals/reliability.md +++ b/docs/fundamentals/reliability.md @@ -21,6 +21,7 @@ in the comparison of reliability analysis methods. |:-----------------------------------------------------------------------------------:|:-----------------:|:-------------------------------:| | {ref}`Cantilever Beam (2D) ` | 2 | `CantileverBeam2D ` | | {ref}`Circular Pipe Crack ` | 2 | `CircularPipeCrack()` | +| {ref}`Convex Failure Domain ` | 2 | `ConvexFailDomain()` | | {ref}`Damped Oscillator Reliability ` | 8 | `DampedOscillatorReliability()` | | {ref}`Four-branch ` | 2 | `FourBranch()` | | {ref}`Gayton Hat ` | 2 | `GaytonHat()` | diff --git a/docs/references.bib b/docs/references.bib index 25dccf5..54817b4 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -655,4 +655,15 @@ @Article{Herman2017 doi = {10.21105/joss.00097}, } +@Article{Borri1997, + author = {Antonio Borri and Emanuela Speranzini}, + journal = {Structural Safety}, + title = {Structural reliability analysis using a standard deterministic finite element code}, + year = {1997}, + number = {4}, + pages = {361--382}, + volume = {19}, + doi = {10.1016/s0167-4730(97)00017-9}, +} + @Comment{jabref-meta: databaseType:bibtex;} diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index cc28636..2f64fad 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -28,6 +28,7 @@ available in the current UQTestFuns, regardless of their typical applications. | {ref}`Bratley et al. (1992) D ` | M | `Bratley1992d()` | | {ref}`Cantilever Beam (2D) ` | 2 | `CantileverBeam2D ` | | {ref}`Circular Pipe Crack ` | 2 | `CircularPipeCrack()` | +| {ref}`Convex Failure Domain ` | 2 | `ConvexFailDomain()` | | {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | | {ref}`Damped Oscillator Reliability ` | 8 | `DampedOscillatorReliability()` | | {ref}`Flood ` | 8 | `Flood()` | diff --git a/docs/test-functions/convex-fail-domain.md b/docs/test-functions/convex-fail-domain.md new file mode 100644 index 0000000..5dfce24 --- /dev/null +++ b/docs/test-functions/convex-fail-domain.md @@ -0,0 +1,249 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:convex-fail-domain)= +# Convex Failure Domain Reliability Problem + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +The Convex failure domain is a test function +from {cite}`Borri1997` for reliability analysis exercises {cite}`Waarts2000`. + +The plots of the function are shown below. The left plot shows the surface +plot of the performance function, the center plot shows the contour +plot with a single contour line at function value of $0.0$ (the limit-state +surface), and the right plot shows the same plot with $10^6$ sample points +overlaid. + +```{code-cell} ipython3 +:tags: [remove-input] + +my_fun = uqtf.ConvexFailDomain(rng_seed_prob_input=237324) +xx = my_fun.prob_input.get_sample(1000000) +yy = my_fun(xx) +idx_neg = yy <= 0.0 +idx_pos = yy > 0.0 + +lb_1 = my_fun.prob_input.marginals[0].lower +ub_1 = my_fun.prob_input.marginals[0].upper +lb_2 = my_fun.prob_input.marginals[1].lower +ub_2 = my_fun.prob_input.marginals[1].upper + +# Create 2-dimensional grid +xx_1 = np.linspace(lb_1, ub_1, 1000)[:, np.newaxis] +xx_2 = np.linspace(lb_2, ub_2, 1000)[:, np.newaxis] +mesh_2d = np.meshgrid(xx_1, xx_2) +xx_2d = np.array(mesh_2d).T.reshape(-1, 2) +yy_2d = my_fun(xx_2d) + +# --- Create the plot +fig = plt.figure(figsize=(15, 5)) + +# Surface +axs_0 = plt.subplot(131, projection='3d') +axs_0.plot_surface( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000,1000).T, + linewidth=0, + cmap="plasma", + antialiased=False, + alpha=0.5 +) +axs_0.set_xlabel("$X_1$", fontsize=18) +axs_0.set_ylabel("$X_2$", fontsize=18) +axs_0.set_zlabel("$g$", fontsize=18) + +# Contour plot +axs_1 = plt.subplot(132) +cf = axs_1.contour( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000, 1000).T, + levels=0, + colors=["#ca0020"], + linewidths=[3.0], +) +axs_1.set_xlim([lb_1, ub_1]) +axs_1.set_ylim([lb_2, ub_2]) +axs_1.set_xlabel("$x_1$", fontsize=18) +axs_1.set_ylabel("$x_2$", fontsize=18) +axs_1.tick_params(labelsize=16) +axs_1.clabel(cf, inline=True, fontsize=18) + +# Scatter plot +axs_2 = plt.subplot(133) +cf = axs_2.contour( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000, 1000).T, + levels=0, + colors=["#ca0020"], + linewidths=[3.0], +) +axs_2.scatter( + xx[idx_neg, 0], + xx[idx_neg, 1], + color="#ca0020", + marker=".", + s=30, + label="$g(x) \leq 0$" +) +axs_2.scatter( + xx[idx_pos, 0], + xx[idx_pos, 1], + color="#0571b0", + marker=".", + s=30, + label="$g(x) > 0$" +) +axs_2.set_xlim([lb_1, ub_1]) +axs_2.set_ylim([lb_2, ub_2]) +axs_2.set_xlabel("$x_1$", fontsize=18) +axs_2.set_ylabel("$x_2$", fontsize=18) +axs_2.tick_params(labelsize=16) +axs_2.clabel(cf, inline=True, fontsize=18) +axs_2.legend(fontsize=18, loc="lower right"); + +fig.tight_layout(pad=4.0); +plt.gcf().set_dpi(150); +``` + +## Test function instance + +To create a default instance of the test function: + +```{code-cell} ipython3 +my_testfun = uqtf.ConvexFailDomain() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +## Description + +The test function (i.e., the performance function) is analytically defined +as follows: + +$$ +g(\boldsymbol{x}) = 0.1 (x_1 - x_2)^2 - \frac{(x_1 + x_2)}{\sqrt{2}} + 2.5, +$$ + +where $\boldsymbol{x} = \{ x_1, x_2 \}$ is the two-dimensional vector of +input variables probabilistically defined further below. + +The failure state and the failure probability are defined as +$g(\boldsymbol{x}) \leq 0$ +and $\mathbb{P}[g(\boldsymbol{X}) \leq 0]$, respectively. + +## Probabilistic input + +Based on {cite}`Waarts2000`, the probabilistic input model for +the test function consists of two independent standard normal random variables +(see the table below). + +```{code-cell} ipython3 +my_testfun.prob_input +``` + +## Reference results + +This section provides several reference results of typical UQ analyses involving +the test function. + +### Sample histogram + +Shown below is the histogram of the output based on $10^6$ random points: + +```{code-cell} ipython3 +:tags: [hide-input] + +def is_outlier(points, thresh=3.5): + """ + Returns a boolean array with True if points are outliers and False + otherwise. + + This is taken from: + https://stackoverflow.com/questions/11882393/matplotlib-disregard-outliers-when-plotting + + Parameters: + ----------- + points : An numobservations by numdimensions array of observations + thresh : The modified z-score to use as a threshold. Observations with + a modified z-score (based on the median absolute deviation) greater + than this value will be classified as outliers. + + Returns: + -------- + mask : A numobservations-length boolean array. + + References: + ---------- + Boris Iglewicz and David Hoaglin (1993), "Volume 16: How to Detect and + Handle Outliers", The ASQC Basic References in Quality Control: + Statistical Techniques, Edward F. Mykytka, Ph.D., Editor. + """ + if len(points.shape) == 1: + points = points[:,None] + median = np.median(points, axis=0) + diff = np.sum((points - median)**2, axis=-1) + diff = np.sqrt(diff) + med_abs_deviation = np.median(diff) + + modified_z_score = 0.6745 * diff / med_abs_deviation + + return modified_z_score > thresh + +xx_test = my_testfun.prob_input.get_sample(1000000) +yy_test = my_testfun(xx_test) +yy_test = yy_test[~is_outlier(yy_test, thresh=10)] +idx_pos = yy_test > 0 +idx_neg = yy_test <= 0 + +hist_pos = plt.hist(yy_test, bins="auto", color="#0571b0") +plt.hist(yy_test[idx_neg], bins=hist_pos[1], color="#ca0020") +plt.axvline(0, linewidth=1.0, color="#ca0020") + +plt.grid() +plt.ylabel("Counts [-]") +plt.xlabel("$\mathcal{M}(\mathbf{X})$") +plt.gcf().set_dpi(150); +``` + +### Failure probability ($P_f$) + +Some reference values for the failure probability $P_f$ from the literature +are summarized in the table below. + +| Method | $N$ | $\hat{P}_f$ | $\mathrm{CoV}[\hat{P}_f]$ | Source | Remark | +|:------------:|:--------:|:-----------------------:|:-------------------------:|:------------------:|---------------------------| +| Exact | — | $4.2692 \times 10^{-3}$ | — | {cite}`Waarts2000` | Annex E.6 Table: Results | +| {term}`FORM` | $8$ | $6.2097 \times 10^{-3}$ | — | {cite}`Waarts2000` | Annex E.6 Table: Results | +| {term}`MCS` | $10^{4}$ | $4.2692 \times 10^{-3}$ | $0.02$ | {cite}`Waarts2000` | Annex E.6 Table: Results | + +## References + +```{bibliography} +:style: unsrtalpha +:filter: docname in docnames +``` + +[^location]: see Annex E.6, p.154 in {cite}`Waarts2000`. \ No newline at end of file diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index 33cfc91..ba14679 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -6,6 +6,7 @@ from .bratley1992 import Bratley1992a, Bratley1992b, Bratley1992c, Bratley1992d from .cantilever_beam_2d import CantileverBeam2D from .circular_pipe_crack import CircularPipeCrack +from .convex_fail_domain import ConvexFailDomain from .damped_oscillator import DampedOscillator, DampedOscillatorReliability from .flood import Flood from .forrester import Forrester2008 @@ -37,6 +38,7 @@ "Bratley1992d", "CantileverBeam2D", "CircularPipeCrack", + "ConvexFailDomain", "DampedOscillator", "DampedOscillatorReliability", "Flood", diff --git a/src/uqtestfuns/test_functions/convex_fail_domain.py b/src/uqtestfuns/test_functions/convex_fail_domain.py new file mode 100644 index 0000000..6756486 --- /dev/null +++ b/src/uqtestfuns/test_functions/convex_fail_domain.py @@ -0,0 +1,88 @@ +""" +Module with an implementation of the convex failure domain problem. + +The two-dimensional function is a reliability analysis problem used, +for instance, in [1] and [2]. + +References +---------- +1. A. Borri and E. Speranzini, “Structural reliability analysis using a + standard deterministic finite element code,” Structural Safety, vol. 19, + no. 4, pp. 361–382, 1997. DOI: 10.1016/S0167-4730(97)00017-9 +2. Paul Hendrik Waarts, “Structural reliability using finite element + analysis - an appraisal of DARS: Directional adaptive response surface + sampling," Civil Engineering and Geosciences, TU Delft, Delft, + The Netherlands, 2000. +""" +import numpy as np + +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecFixDim +from ..core.uqtestfun_abc import UQTestFunABC + +__all__ = ["ConvexFailDomain"] + +AVAILABLE_INPUT_SPECS = { + "Borri1997": ProbInputSpecFixDim( + name="ConvexFailDomain-Borri1997", + description=( + "Input model for the convex failure domain problem " + "from Borri and Speranzini (1997)" + ), + marginals=[ + UnivDistSpec( + name="X1", + distribution="normal", + parameters=[0, 1], + description="None", + ), + UnivDistSpec( + name="X2", + distribution="normal", + parameters=[0, 1], + description="None", + ), + ], + copulas=None, + ), +} + + +def evaluate(xx: np.ndarray) -> np.ndarray: + """Evaluate the convex failure domain function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + A two-dimensional input values given by an N-by-2 array + where N is the number of input values. + + Returns + ------- + np.ndarray + The performance function of the problem. + If negative, the system is in failed state. + The output is a one-dimensional array of length N. + """ + + # Compute the performance function + yy = ( + 0.1 * (xx[:, 0] - xx[:, 1]) ** 2 + - (xx[:, 0] + xx[:, 1]) / np.sqrt(2) + + 2.5 + ) + + return yy + + +class ConvexFailDomain(UQTestFunABC): + """Concrete implementation of the Convex failure domain reliability.""" + + _tags = ["reliability"] + _description = ( + "Convex failure domain problem from Borri and Speranzini (1997)" + ) + _available_inputs = AVAILABLE_INPUT_SPECS + _available_parameters = None + _default_spatial_dimension = 2 + + eval_ = staticmethod(evaluate) From 9a4d71c709cda69669a02dac808ca57a708dfa53 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Fri, 7 Jul 2023 11:38:19 +0200 Subject: [PATCH 52/73] Add an implementation of the quadratic RS problem. - The two-dimensional reliability analysis problem is a variant of the classic RS problem with one quadratic term. The function is implemented from Waarts (2000). --- CHANGELOG.md | 5 +- docs/_toc.yml | 2 + docs/fundamentals/reliability.md | 1 + docs/test-functions/available.md | 1 + docs/test-functions/convex-fail-domain.md | 2 +- docs/test-functions/rs-quadratic.md | 248 ++++++++++++++++++ src/uqtestfuns/test_functions/__init__.py | 2 + src/uqtestfuns/test_functions/rs_quadratic.py | 76 ++++++ 8 files changed, 335 insertions(+), 2 deletions(-) create mode 100644 docs/test-functions/rs-quadratic.md create mode 100644 src/uqtestfuns/test_functions/rs_quadratic.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bfcaab..c268ea0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - The two-dimensional convex failure domain problem for reliability - analysis from Borri and Speranzini (1997). + analysis exercises from Borri and Speranzini (1997). +- The two-dimensional Quadratic RS problem for reliability analysis + exercises from Waarts (2000). This is a variant of the classic RS + problem with one quadratic term. ### Changed diff --git a/docs/_toc.yml b/docs/_toc.yml index 797ee52..c3a0f5c 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -89,6 +89,8 @@ parts: title: OTL Circuit - file: test-functions/piston title: Piston Simulation + - file: test-functions/rs-quadratic + title: RS - Quadratic - file: test-functions/sobol-g title: Sobol'-G - file: test-functions/speed-reducer-shaft diff --git a/docs/fundamentals/reliability.md b/docs/fundamentals/reliability.md index 3a4b41e..15e3810 100644 --- a/docs/fundamentals/reliability.md +++ b/docs/fundamentals/reliability.md @@ -26,6 +26,7 @@ in the comparison of reliability analysis methods. | {ref}`Four-branch ` | 2 | `FourBranch()` | | {ref}`Gayton Hat ` | 2 | `GaytonHat()` | | {ref}`Hyper-sphere Bound ` | 2 | `HyperSphere()` | +| {ref}`RS - Quadratic ` | 2 | `RSQuadratic()` | | {ref}`Speed Reducer Shaft ` | 5 | `SpeedReducerShaft()` | In a Python terminal, you can list all the available functions relevant diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index 2f64fad..6fcbf15 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -52,6 +52,7 @@ available in the current UQTestFuns, regardless of their typical applications. | {ref}`Oakley and O'Hagan (2002) 1D ` | 1 | `Oakley1D()` | | {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | | {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | +| {ref}`RS - Quadratic ` | 2 | `RSQuadratic()` | | {ref}`Sobol'-G ` | M | `SobolG()` | | {ref}`Speed Reducer Shaft ` | 5 | `SpeedReducerShaft()` | | {ref}`Sulfur ` | 9 | `Sulfur()` | diff --git a/docs/test-functions/convex-fail-domain.md b/docs/test-functions/convex-fail-domain.md index 5dfce24..3c2082c 100644 --- a/docs/test-functions/convex-fail-domain.md +++ b/docs/test-functions/convex-fail-domain.md @@ -156,7 +156,7 @@ and $\mathbb{P}[g(\boldsymbol{X}) \leq 0]$, respectively. ## Probabilistic input -Based on {cite}`Waarts2000`, the probabilistic input model for +Based on {cite}`Borri1997`, the probabilistic input model for the test function consists of two independent standard normal random variables (see the table below). diff --git a/docs/test-functions/rs-quadratic.md b/docs/test-functions/rs-quadratic.md new file mode 100644 index 0000000..a15316c --- /dev/null +++ b/docs/test-functions/rs-quadratic.md @@ -0,0 +1,248 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:rs-quadratic)= +# Quadratic RS Reliability Problem + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +The Quadratic RS reliability problem is a variant of the classic RS problem +with one quadratic term {cite}`Waarts2000`. + +The plots of the function are shown below. The left plot shows the surface +plot of the performance function, the center plot shows the contour +plot with a single contour line at function value of $0.0$ (the limit-state +surface), and the right plot shows the same plot with $10^6$ sample points +overlaid. + +```{code-cell} ipython3 +:tags: [remove-input] + +my_fun = uqtf.RSQuadratic(rng_seed_prob_input=237324) +xx = my_fun.prob_input.get_sample(1000000) +yy = my_fun(xx) +idx_neg = yy <= 0.0 +idx_pos = yy > 0.0 + +lb_1 = my_fun.prob_input.marginals[0].lower +ub_1 = my_fun.prob_input.marginals[0].upper +lb_2 = my_fun.prob_input.marginals[1].lower +ub_2 = my_fun.prob_input.marginals[1].upper + +# Create 2-dimensional grid +xx_1 = np.linspace(lb_1, ub_1, 1000)[:, np.newaxis] +xx_2 = np.linspace(lb_2, ub_2, 1000)[:, np.newaxis] +mesh_2d = np.meshgrid(xx_1, xx_2) +xx_2d = np.array(mesh_2d).T.reshape(-1, 2) +yy_2d = my_fun(xx_2d) + +# --- Create the plot +fig = plt.figure(figsize=(15, 5)) + +# Surface +axs_0 = plt.subplot(131, projection='3d') +axs_0.plot_surface( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000,1000).T, + linewidth=0, + cmap="plasma", + antialiased=False, + alpha=0.5 +) +axs_0.set_xlabel("$X_1$", fontsize=18) +axs_0.set_ylabel("$X_2$", fontsize=18) +axs_0.set_zlabel("$g$", fontsize=18) + +# Contour plot +axs_1 = plt.subplot(132) +cf = axs_1.contour( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000, 1000).T, + levels=0, + colors=["#ca0020"], + linewidths=[3.0], +) +axs_1.set_xlim([lb_1, ub_1]) +axs_1.set_ylim([lb_2, ub_2]) +axs_1.set_xlabel("$x_1$", fontsize=18) +axs_1.set_ylabel("$x_2$", fontsize=18) +axs_1.tick_params(labelsize=16) +axs_1.clabel(cf, inline=True, fontsize=18) + +# Scatter plot +axs_2 = plt.subplot(133) +cf = axs_2.contour( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000, 1000).T, + levels=0, + colors=["#ca0020"], + linewidths=[3.0], +) +axs_2.scatter( + xx[idx_neg, 0], + xx[idx_neg, 1], + color="#ca0020", + marker=".", + s=30, + label="$g(x) \leq 0$" +) +axs_2.scatter( + xx[idx_pos, 0], + xx[idx_pos, 1], + color="#0571b0", + marker=".", + s=30, + label="$g(x) > 0$" +) +axs_2.set_xlim([lb_1, ub_1]) +axs_2.set_ylim([lb_2, ub_2]) +axs_2.set_xlabel("$x_1$", fontsize=18) +axs_2.set_ylabel("$x_2$", fontsize=18) +axs_2.tick_params(labelsize=16) +axs_2.clabel(cf, inline=True, fontsize=18) +axs_2.legend(fontsize=18, loc="lower right"); + +fig.tight_layout(pad=4.0); +plt.gcf().set_dpi(150); +``` + +## Test function instance + +To create a default instance of the test function: + +```{code-cell} ipython3 +my_testfun = uqtf.RSQuadratic() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +## Description + +The test function (i.e., the performance function) is analytically defined +as follows: + +$$ +g(\boldsymbol{x}) = x_1 - x_2^2, +$$ + +where $\boldsymbol{x} = \{ x_1, x_2 \}$ is the two-dimensional vector of +input variables probabilistically defined further below. + +The failure state and the failure probability are defined as +$g(\boldsymbol{x}) \leq 0$ +and $\mathbb{P}[g(\boldsymbol{X}) \leq 0]$, respectively. + +## Probabilistic input + +Based on {cite}`Waarts2000`, the probabilistic input model for +the test function consists of two independent standard normal random variables +(see the table below). + +```{code-cell} ipython3 +my_testfun.prob_input +``` + +## Reference results + +This section provides several reference results of typical UQ analyses involving +the test function. + +### Sample histogram + +Shown below is the histogram of the output based on $10^6$ random points: + +```{code-cell} ipython3 +:tags: [hide-input] + +def is_outlier(points, thresh=3.5): + """ + Returns a boolean array with True if points are outliers and False + otherwise. + + This is taken from: + https://stackoverflow.com/questions/11882393/matplotlib-disregard-outliers-when-plotting + + Parameters: + ----------- + points : An numobservations by numdimensions array of observations + thresh : The modified z-score to use as a threshold. Observations with + a modified z-score (based on the median absolute deviation) greater + than this value will be classified as outliers. + + Returns: + -------- + mask : A numobservations-length boolean array. + + References: + ---------- + Boris Iglewicz and David Hoaglin (1993), "Volume 16: How to Detect and + Handle Outliers", The ASQC Basic References in Quality Control: + Statistical Techniques, Edward F. Mykytka, Ph.D., Editor. + """ + if len(points.shape) == 1: + points = points[:,None] + median = np.median(points, axis=0) + diff = np.sum((points - median)**2, axis=-1) + diff = np.sqrt(diff) + med_abs_deviation = np.median(diff) + + modified_z_score = 0.6745 * diff / med_abs_deviation + + return modified_z_score > thresh + +xx_test = my_testfun.prob_input.get_sample(1000000) +yy_test = my_testfun(xx_test) +yy_test = yy_test[~is_outlier(yy_test, thresh=10)] +idx_pos = yy_test > 0 +idx_neg = yy_test <= 0 + +hist_pos = plt.hist(yy_test, bins="auto", color="#0571b0") +plt.hist(yy_test[idx_neg], bins=hist_pos[1], color="#ca0020") +plt.axvline(0, linewidth=1.0, color="#ca0020") + +plt.grid() +plt.ylabel("Counts [-]") +plt.xlabel("$\mathcal{M}(\mathbf{X})$") +plt.gcf().set_dpi(150); +``` + +### Failure probability ($P_f$) + +Some reference values for the failure probability $P_f$ from the literature +are summarized in the table below. + +| Method | $N$ | $\hat{P}_f$ | $\mathrm{CoV}[\hat{P}_f]$ | Source | Remark | +|:------------:|:--------:|:-----------------------:|:-------------------------:|:------------------:|--------------------------| +| Exact | — | $2.7009 \times 10^{-4}$ | — | {cite}`Waarts2000` | Annex E.3 Table: Results | +| {term}`FORM` | $12$ | $2.6023 \times 10^{-4}$ | — | {cite}`Waarts2000` | Annex E.3 Table: Results | + +## References + +```{bibliography} +:style: unsrtalpha +:filter: docname in docnames +``` + +[^location]: see Annex E.3, p.151 in {cite}`Waarts2000`. \ No newline at end of file diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index ba14679..630b081 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -20,6 +20,7 @@ from .otl_circuit import OTLCircuit from .mclain import McLainS1, McLainS2, McLainS3, McLainS4, McLainS5 from .piston import Piston +from .rs_quadratic import RSQuadratic from .sobol_g import SobolG from .speed_reducer_shaft import SpeedReducerShaft from .sulfur import Sulfur @@ -62,6 +63,7 @@ "McLainS4", "McLainS5", "Piston", + "RSQuadratic", "SobolG", "SpeedReducerShaft", "Sulfur", diff --git a/src/uqtestfuns/test_functions/rs_quadratic.py b/src/uqtestfuns/test_functions/rs_quadratic.py new file mode 100644 index 0000000..5316bd5 --- /dev/null +++ b/src/uqtestfuns/test_functions/rs_quadratic.py @@ -0,0 +1,76 @@ +""" +Module with an implementation of the Quadratic RS reliability problem. + +The two-dimensional function is a variant of the classic RS reliability problem +with one quadratic term [1]. + +References +---------- +1. Paul Hendrik Waarts, “Structural reliability using finite element + analysis - an appraisal of DARS: Directional adaptive response surface + sampling," Civil Engineering and Geosciences, TU Delft, Delft, + The Netherlands, 2000. +""" +import numpy as np + +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecFixDim +from ..core.uqtestfun_abc import UQTestFunABC + +__all__ = ["RSQuadratic"] + +AVAILABLE_INPUT_SPECS = { + "Waarts2000": ProbInputSpecFixDim( + name="RSQuadratic-Waarts2000", + description="Input model for the quadratic RS from Waarts (2000)", + marginals=[ + UnivDistSpec( + name="X1", + distribution="normal", + parameters=[11.0, 1.0], + description="None", + ), + UnivDistSpec( + name="X2", + distribution="normal", + parameters=[1.5, 0.5], + description="None", + ), + ], + copulas=None, + ), +} + + +def evaluate(xx: np.ndarray) -> np.ndarray: + """Evaluate the quadratic RS function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + A two-dimensional input values given by an N-by-2 array + where N is the number of input values. + + Returns + ------- + np.ndarray + The performance function of the problem. + If negative, the system is in failed state. + The output is a one-dimensional array of length N. + """ + + # Compute the performance function + yy = xx[:, 0] - xx[:, 1] ** 2 + + return yy + + +class RSQuadratic(UQTestFunABC): + """Concrete implementation of the quadratic RS reliability problem.""" + + _tags = ["reliability"] + _description = "RS problem w/ one quadratic term from Waarts (2000)" + _available_inputs = AVAILABLE_INPUT_SPECS + _available_parameters = None + _default_spatial_dimension = 2 + + eval_ = staticmethod(evaluate) From c01f0e432ef8cfe2b8eebc777af8d3520bb44ad6 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Fri, 7 Jul 2023 11:58:04 +0200 Subject: [PATCH 53/73] Add PyPI badge from Shields in the README. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7697356..e941566 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ # UQTestFuns [![DOI](http://img.shields.io/badge/DOI-10.5281/zenodo.8109901-blue.svg?style=flat-square)](https://doi.org/10.5281/zenodo.8109901) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg?style=flat-square)](https://github.com/psf/black) -[![Python 3.8](https://img.shields.io/badge/python-3.7-blue.svg?style=flat-square)](https://www.python.org/downloads/release/python-370/) +[![Python 3.7](https://img.shields.io/badge/python-3.7-blue.svg?style=flat-square)](https://www.python.org/downloads/release/python-370/) [![License](https://img.shields.io/github/license/damar-wicaksono/uqtestfuns?style=flat-square)](https://choosealicense.com/licenses/mit/) +[![PyPI](https://img.shields.io/pypi/v/uqtestfuns?style=flat-square)](https://pypi.org/project/uqtestfuns/) | Branches | Status | |:--------------------------------------------------------------------------:|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| From e5c3ab30c2d0325d973d9e46ebff4732f71f1167 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Fri, 7 Jul 2023 12:52:31 +0200 Subject: [PATCH 54/73] Add an implementation of the 1D damped cosine function. - The function is taken from an example in Santner (2018) for metamodeling exercises. --- CHANGELOG.md | 2 + docs/_toc.yml | 2 + docs/fundamentals/metamodeling.md | 1 + docs/references.bib | 8 + docs/test-functions/available.md | 1 + docs/test-functions/damped-cosine.md | 226 ++++++++++++++++++ docs/test-functions/forrester.md | 2 +- docs/test-functions/gramacy-1d-sine.md | 2 +- docs/test-functions/oakley-1d.md | 2 +- src/uqtestfuns/test_functions/__init__.py | 2 + .../test_functions/damped_cosine.py | 70 ++++++ 11 files changed, 315 insertions(+), 3 deletions(-) create mode 100644 docs/test-functions/damped-cosine.md create mode 100644 src/uqtestfuns/test_functions/damped_cosine.py diff --git a/CHANGELOG.md b/CHANGELOG.md index c268ea0..c8ad641 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The two-dimensional Quadratic RS problem for reliability analysis exercises from Waarts (2000). This is a variant of the classic RS problem with one quadratic term. +- The one-dimensional damped cosine function for metamodeling exercises + from an example in Santner et al. (2018). ### Changed diff --git a/docs/_toc.yml b/docs/_toc.yml index c3a0f5c..1d48cd3 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -43,6 +43,8 @@ parts: title: Circular Pipe Crack - file: test-functions/convex-fail-domain title: Convex Failure Domain + - file: test-functions/damped-cosine + title: Damped Cosine - file: test-functions/damped-oscillator title: Damped Oscillator - file: test-functions/damped-oscillator-reliability diff --git a/docs/fundamentals/metamodeling.md b/docs/fundamentals/metamodeling.md index 5d93518..942c02b 100644 --- a/docs/fundamentals/metamodeling.md +++ b/docs/fundamentals/metamodeling.md @@ -21,6 +21,7 @@ in the comparison of metamodeling approaches. |:--------------------------------------------------------------:|:-----------------:|:--------------------:| | {ref}`Ackley ` | M | `Ackley()` | | {ref}`Borehole ` | 8 | `Borehole()` | +| {ref}`Damped Cosine ` | 1 | `DampedCosine()` | | {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | | {ref}`Flood ` | 8 | `Flood()` | | {ref}`Forrester et al. (2008) ` | 1 | `Forrester2008()` | diff --git a/docs/references.bib b/docs/references.bib index 54817b4..f9cd365 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -666,4 +666,12 @@ @Article{Borri1997 doi = {10.1016/s0167-4730(97)00017-9}, } +@Book{Santner2018, + author = {Thomas J. Santner and Brian J. Williams and William I. Notz}, + publisher = {Springer New York}, + title = {The design and analysis of computer experiments}, + year = {2018}, + doi = {10.1007/978-1-4939-8847-1}, +} + @Comment{jabref-meta: databaseType:bibtex;} diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index 6fcbf15..80f86f9 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -29,6 +29,7 @@ available in the current UQTestFuns, regardless of their typical applications. | {ref}`Cantilever Beam (2D) ` | 2 | `CantileverBeam2D ` | | {ref}`Circular Pipe Crack ` | 2 | `CircularPipeCrack()` | | {ref}`Convex Failure Domain ` | 2 | `ConvexFailDomain()` | +| {ref}`Damped Cosine ` | 1 | `DampedCosine()` | | {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | | {ref}`Damped Oscillator Reliability ` | 8 | `DampedOscillatorReliability()` | | {ref}`Flood ` | 8 | `Flood()` | diff --git a/docs/test-functions/damped-cosine.md b/docs/test-functions/damped-cosine.md new file mode 100644 index 0000000..88e14ea --- /dev/null +++ b/docs/test-functions/damped-cosine.md @@ -0,0 +1,226 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:damped-cosine)= +# One-dimensional (1D) Damped Cosine Function + +The 1D damped cosine function from Santner et al. {cite}`Santner2018` is +a scalar-valued test function for metamodeling exercises. + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +A plot of the function is shown below for $x \in [0, 1]$. + +```{code-cell} ipython3 +:tags: [remove-input] + +my_testfun = uqtf.DampedCosine() +lb = my_testfun.prob_input.marginals[0].lower +ub = my_testfun.prob_input.marginals[0].upper +xx = np.linspace(lb, ub, 1000)[:, np.newaxis] +yy = my_testfun(xx) + +# --- Create the plot +plt.plot(xx, yy, color="#8da0cb") +plt.grid() +plt.xlabel("$x$") +plt.ylabel("$\mathcal{M}(x)$") +plt.gcf().tight_layout(pad=3.0) +plt.gcf().set_dpi(150); +``` + +## Test function instance + +To create a default instance of the test function: + +```{code-cell} ipython3 +my_testfun = uqtf.DampedCosine() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +## Description + +The test function is analytically defined as follows[^location]: + +$$ +\mathcal{M}(x) = e^{(-1.4 x)} \cos{(3.5 \pi x)}, +$$ +where $x$ is defined below. + +## Probabilistic input + +Based on {cite}`Santner2018`, the domain of the function is in $[0, 1]$. +In UQTestFuns, this domain can be represented as a probabilistic input model +using the uniform distribution shown in the table below. + +```{code-cell} ipython3 +my_testfun.prob_input +``` + +## Reference results + +This section provides several reference results of typical UQ analyses involving +the test function. + +### Sample histogram + +Shown below is the histogram of the output based on $100'000$ random points: + +```{code-cell} ipython3 +:tags: [hide-input] + +np.random.seed(42) +xx_test = my_testfun.prob_input.get_sample(100000) +yy_test = my_testfun(xx_test) + +plt.hist(yy_test, bins="auto", color="#8da0cb"); +plt.grid(); +plt.ylabel("Counts [-]"); +plt.xlabel("$\mathcal{M}(X)$"); +plt.gcf().tight_layout(pad=3.0) +plt.gcf().set_dpi(150); +``` + +### Moment estimations + +Shown below is the convergence of a direct Monte-Carlo estimation of +the output mean and variance with increasing sample sizes. + +```{code-cell} ipython3 +:tags: [hide-input] + +np.random.seed(42) +sample_sizes = np.array([1e1, 1e2, 1e3, 1e4, 1e5, 1e6], dtype=int) +mean_estimates = np.empty((len(sample_sizes), 50)) +var_estimates = np.empty((len(sample_sizes), 50)) + +for i, sample_size in enumerate(sample_sizes): + for j in range(50): + xx_test = my_testfun.prob_input.get_sample(sample_size) + yy_test = my_testfun(xx_test) + mean_estimates[i, j] = np.mean(yy_test) + var_estimates[i, j] = np.var(yy_test) + +# --- Compute the error associated with the estimates +mean_estimates_errors = np.std(mean_estimates, axis=1) +var_estimates_errors = np.std(var_estimates, axis=1) + +# --- Plot the mean and variance estimates +fig, ax_1 = plt.subplots(figsize=(6,4)) + +# --- Mean plot +ax_1.errorbar( + sample_sizes, + mean_estimates[:,0], + yerr=2.0*mean_estimates_errors, + marker="o", + color="#66c2a5", + label="Mean" +) +ax_1.set_xlim([5, 2e6]) +ax_1.set_xlabel("Sample size") +ax_1.set_ylabel("Output mean estimate") +ax_1.set_xscale("log"); +ax_2 = ax_1.twinx() + +# --- Variance plot +ax_2.errorbar( + sample_sizes+1, + var_estimates[:,0], + yerr=1.96*var_estimates_errors, + marker="o", + color="#fc8d62", + label="Variance", +) +ax_2.set_ylabel("Output variance estimate") + +# Add the two plots together to have a common legend +ln_1, labels_1 = ax_1.get_legend_handles_labels() +ln_2, labels_2 = ax_2.get_legend_handles_labels() +ax_2.legend(ln_1 + ln_2, labels_1 + labels_2, loc=0) + +plt.grid() +fig.set_dpi(150) +``` + +The tabulated results for each sample size is shown below. + +```{code-cell} ipython3 +:tags: [hide-input] + +from tabulate import tabulate + +# --- Compile data row-wise +outputs =[] + +for ( + sample_size, + mean_estimate, + mean_estimate_error, + var_estimate, + var_estimate_error, +) in zip( + sample_sizes, + mean_estimates[:,0], + 2.0*mean_estimates_errors, + var_estimates[:,0], + 2.0*var_estimates_errors, +): + outputs += [ + [ + sample_size, + mean_estimate, + mean_estimate_error, + var_estimate, + var_estimate_error, + "Monte-Carlo", + ], + ] + +header_names = [ + "Sample size", + "Mean", + "Mean error", + "Variance", + "Variance error", + "Remark", +] + +tabulate( + outputs, + numalign="center", + stralign="center", + tablefmt="html", + floatfmt=(".1e", ".4e", ".4e", ".4e", ".4e", "s"), + headers=header_names +) +``` + +## References + +```{bibliography} +:style: unsrtalpha +:filter: docname in docnames +``` + +[^location]: see Example 3.3 in {cite}`Santner2018`. \ No newline at end of file diff --git a/docs/test-functions/forrester.md b/docs/test-functions/forrester.md index 1f41cb4..b657cb0 100644 --- a/docs/test-functions/forrester.md +++ b/docs/test-functions/forrester.md @@ -76,7 +76,7 @@ print(my_testfun) The test function is analytically defined as follows: $$ -\mathcal{M}(x) = (6 x - 2)^2 \sin{(12 x - 4)} +\mathcal{M}(x) = (6 x - 2)^2 \sin{(12 x - 4)}, $$ where $x$ is defined below. diff --git a/docs/test-functions/gramacy-1d-sine.md b/docs/test-functions/gramacy-1d-sine.md index 2e74c80..e011307 100644 --- a/docs/test-functions/gramacy-1d-sine.md +++ b/docs/test-functions/gramacy-1d-sine.md @@ -81,7 +81,7 @@ $$ \mathcal{M}(x) = \begin{cases} \sin{(\frac{\pi x}{5})} + \frac{1}{5} \cos{(\frac{4 \pi x}{5})}, & x \leq 9.6 \\ \frac{1}{10} x - 1, & x > 9.6 -\end{cases} +\end{cases}, $$ where $x$ is defined below. diff --git a/docs/test-functions/oakley-1d.md b/docs/test-functions/oakley-1d.md index 839b685..b24f3ea 100644 --- a/docs/test-functions/oakley-1d.md +++ b/docs/test-functions/oakley-1d.md @@ -63,7 +63,7 @@ print(my_testfun) The test function is analytically defined as follows: $$ -\mathcal{M}(x) = 5 + x + \cos{x} +\mathcal{M}(x) = 5 + x + \cos{x}, $$ where $x$ is probabilistically defined below. diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index 630b081..330fc4c 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -7,6 +7,7 @@ from .cantilever_beam_2d import CantileverBeam2D from .circular_pipe_crack import CircularPipeCrack from .convex_fail_domain import ConvexFailDomain +from .damped_cosine import DampedCosine from .damped_oscillator import DampedOscillator, DampedOscillatorReliability from .flood import Flood from .forrester import Forrester2008 @@ -40,6 +41,7 @@ "CantileverBeam2D", "CircularPipeCrack", "ConvexFailDomain", + "DampedCosine", "DampedOscillator", "DampedOscillatorReliability", "Flood", diff --git a/src/uqtestfuns/test_functions/damped_cosine.py b/src/uqtestfuns/test_functions/damped_cosine.py new file mode 100644 index 0000000..54dfa21 --- /dev/null +++ b/src/uqtestfuns/test_functions/damped_cosine.py @@ -0,0 +1,70 @@ +""" +Module with an implementation the one-dimensional damped cosine function. + + +References +---------- + +1. Jeremy Oakley and Anthony O'Hagan, "Bayesian inference for the uncertainty + distribution of computer model outputs," Biometrika , Vol. 89, No. 4, + p. 769-784, 2002. + DOI: 10.1093/biomet/89.4.769 +""" +import numpy as np + +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecFixDim +from ..core.uqtestfun_abc import UQTestFunABC + +__all__ = ["DampedCosine"] + +AVAILABLE_INPUT_SPECS = { + "Santner2018": ProbInputSpecFixDim( + name="Santner2018", + description=( + "Input model for the one-dimensional damped cosine " + "from Santner et al. (2018)" + ), + marginals=[ + UnivDistSpec( + name="x", + distribution="uniform", + parameters=[0.0, 1.0], + description="None", + ) + ], + copulas=None, + ), +} + + +def evaluate(xx: np.ndarray) -> np.ndarray: + """Evaluate the 1D damped cosine function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + 1-Dimensional input values given by an N-by-1 array + where N is the number of input values. + + Returns + ------- + np.ndarray + The output of the 1D Oakley-O'Hagan function evaluated + on the input values. + The output is a 1-dimensional array of length N. + """ + yy = np.exp(-1.4 * xx[:, 0]) * np.cos(3.5 * np.pi * xx[:, 0]) + + return yy + + +class DampedCosine(UQTestFunABC): + """An implementation of the 1D damped cosine from Santner et al. (2018).""" + + _tags = ["metamodeling"] + _description = "One-dimensional damped cosine from Santner et al. (2018)" + _available_inputs = AVAILABLE_INPUT_SPECS + _available_parameters = None + _default_spatial_dimension = 1 + + eval_ = staticmethod(evaluate) From b4c7786af363a9cf8c84eebc382dade0632336b3 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Fri, 7 Jul 2023 14:17:54 +0200 Subject: [PATCH 55/73] Add an implementation of the circular bar RS problem. - The two-dimensional reliability problem is a variation of the classic RS problem put in the context of mechanical failure of a bar under axial stress. The function is taken from an example in Verma et al. (2015). --- CHANGELOG.md | 2 + docs/_toc.yml | 2 + docs/fundamentals/reliability.md | 1 + docs/glossary.md | 3 + docs/test-functions/available.md | 1 + docs/test-functions/rs-circular-bar.md | 256 ++++++++++++++++++ src/uqtestfuns/test_functions/__init__.py | 2 + .../test_functions/rs_circular_bar.py | 86 ++++++ 8 files changed, 353 insertions(+) create mode 100644 docs/test-functions/rs-circular-bar.md create mode 100644 src/uqtestfuns/test_functions/rs_circular_bar.py diff --git a/CHANGELOG.md b/CHANGELOG.md index c8ad641..9e81ec1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 problem with one quadratic term. - The one-dimensional damped cosine function for metamodeling exercises from an example in Santner et al. (2018). +- The two-dimensional circular bar RS problem for reliability analysis + exercises taken from an example in Verma et al. (2015). ### Changed diff --git a/docs/_toc.yml b/docs/_toc.yml index 1d48cd3..e007f45 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -91,6 +91,8 @@ parts: title: OTL Circuit - file: test-functions/piston title: Piston Simulation + - file: test-functions/rs-circular-bar + title: RS - Circular Bar - file: test-functions/rs-quadratic title: RS - Quadratic - file: test-functions/sobol-g diff --git a/docs/fundamentals/reliability.md b/docs/fundamentals/reliability.md index 15e3810..5cab416 100644 --- a/docs/fundamentals/reliability.md +++ b/docs/fundamentals/reliability.md @@ -26,6 +26,7 @@ in the comparison of reliability analysis methods. | {ref}`Four-branch ` | 2 | `FourBranch()` | | {ref}`Gayton Hat ` | 2 | `GaytonHat()` | | {ref}`Hyper-sphere Bound ` | 2 | `HyperSphere()` | +| {ref}`RS - Circular Bar ` | 2 | `RSCircularBar()` | | {ref}`RS - Quadratic ` | 2 | `RSQuadratic()` | | {ref}`Speed Reducer Shaft ` | 5 | `SpeedReducerShaft()` | diff --git a/docs/glossary.md b/docs/glossary.md index 19c0732..bae257e 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -9,6 +9,9 @@ CDF FORM First-order reliability method +FOSM + First-order Second-moment method + FOSPA First-order Saddlepoint Approximation reliability method {cite}`Du2004` diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index 80f86f9..136c5ca 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -53,6 +53,7 @@ available in the current UQTestFuns, regardless of their typical applications. | {ref}`Oakley and O'Hagan (2002) 1D ` | 1 | `Oakley1D()` | | {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | | {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | +| {ref}`RS - Circular Bar ` | 2 | `RSCircularBar()` | | {ref}`RS - Quadratic ` | 2 | `RSQuadratic()` | | {ref}`Sobol'-G ` | M | `SobolG()` | | {ref}`Speed Reducer Shaft ` | 5 | `SpeedReducerShaft()` | diff --git a/docs/test-functions/rs-circular-bar.md b/docs/test-functions/rs-circular-bar.md new file mode 100644 index 0000000..fd7b11e --- /dev/null +++ b/docs/test-functions/rs-circular-bar.md @@ -0,0 +1,256 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:rs-circular-bar)= +# Circular Bar RS Reliability Problem + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +The circular bar RS reliability problem from {cite}`Verma2015` is a variation +on a theme of the classic RS reliability problem. This particular variant +put it in the context of a circular bar subjected to an axial force. + +The plots of the function are shown below. The left plot shows the surface +plot of the performance function, the center plot shows the contour +plot with a single contour line at function value of $0.0$ (the limit-state +surface), and the right plot shows the same plot with $10^6$ sample points +overlaid. + +```{code-cell} ipython3 +:tags: [remove-input] + +my_fun = uqtf.RSCircularBar(rng_seed_prob_input=237324) +xx = my_fun.prob_input.get_sample(1000000) +yy = my_fun(xx) +idx_neg = yy <= 0.0 +idx_pos = yy > 0.0 + +lb_1 = my_fun.prob_input.marginals[0].lower +ub_1 = my_fun.prob_input.marginals[0].upper +lb_2 = my_fun.prob_input.marginals[1].lower +ub_2 = my_fun.prob_input.marginals[1].upper + +# Create 2-dimensional grid +xx_1 = np.linspace(lb_1, ub_1, 1000)[:, np.newaxis] +xx_2 = np.linspace(lb_2, ub_2, 1000)[:, np.newaxis] +mesh_2d = np.meshgrid(xx_1, xx_2) +xx_2d = np.array(mesh_2d).T.reshape(-1, 2) +yy_2d = my_fun(xx_2d) + +# --- Create the plot +fig = plt.figure(figsize=(15, 5)) + +# Surface +axs_0 = plt.subplot(131, projection='3d') +axs_0.plot_surface( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000,1000).T, + linewidth=0, + cmap="plasma", + antialiased=False, + alpha=0.5 +) +axs_0.set_xlabel("$X_1$", fontsize=18) +axs_0.set_ylabel("$X_2$", fontsize=18) +axs_0.set_zlabel("$g$", fontsize=18) + +# Contour plot +axs_1 = plt.subplot(132) +cf = axs_1.contour( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000, 1000).T, + levels=0, + colors=["#ca0020"], + linewidths=[3.0], +) +axs_1.set_xlim([lb_1, ub_1]) +axs_1.set_ylim([lb_2, ub_2]) +axs_1.set_xlabel("$x_1$", fontsize=18) +axs_1.set_ylabel("$x_2$", fontsize=18) +axs_1.tick_params(labelsize=16) +axs_1.clabel(cf, inline=True, fontsize=18) + +# Scatter plot +axs_2 = plt.subplot(133) +cf = axs_2.contour( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000, 1000).T, + levels=0, + colors=["#ca0020"], + linewidths=[3.0], +) +axs_2.scatter( + xx[idx_neg, 0], + xx[idx_neg, 1], + color="#ca0020", + marker=".", + s=30, + label="$g(x) \leq 0$" +) +axs_2.scatter( + xx[idx_pos, 0], + xx[idx_pos, 1], + color="#0571b0", + marker=".", + s=30, + label="$g(x) > 0$" +) +axs_2.set_xlim([lb_1, ub_1]) +axs_2.set_ylim([lb_2, ub_2]) +axs_2.set_xlabel("$x_1$", fontsize=18) +axs_2.set_ylabel("$x_2$", fontsize=18) +axs_2.tick_params(labelsize=16) +axs_2.clabel(cf, inline=True, fontsize=18) +axs_2.legend(fontsize=18, loc="lower right"); + +fig.tight_layout(pad=4.0); +plt.gcf().set_dpi(150); +``` + +## Test function instance + +To create a default instance of the test function: + +```{code-cell} ipython3 +my_testfun = uqtf.RSCircularBar() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +## Description + +The reliability problem consists of a carbon-steel circular bar subjected +to an axial force. +The test function (i.e., the performance function) is analytically defined +as follows[^location]: + +$$ +g(\boldsymbol{x}; p) = Y - \frac{F}{\frac{1}{4} \pi d^2}, +$$ + +where $\boldsymbol{x} = \{ Y, F \}$ is the two-dimensional vector of +input variables probabilistically defined further below; +and $p = \{ d \}$ is the deterministic parameter of the function. + +The failure state and the failure probability are defined as +$g(\boldsymbol{x}; p) \leq 0$ +and $\mathbb{P}[g(\boldsymbol{X}; p) \leq 0]$, respectively. + +## Probabilistic input + +Based on {cite}`Verma2015`, the probabilistic input model for +the test function consists of two independent standard normal random variables +(see the table below). + +```{code-cell} ipython3 +my_testfun.prob_input +``` + +## Parameter + +The parameter of the function is $d$ which from {cite}`Verma2015` is set to +$25\;\mathrm{[mm]}$. + +## Reference results + +This section provides several reference results of typical UQ analyses involving +the test function. + +### Sample histogram + +Shown below is the histogram of the output based on $10^6$ random points: + +```{code-cell} ipython3 +:tags: [hide-input] + +def is_outlier(points, thresh=3.5): + """ + Returns a boolean array with True if points are outliers and False + otherwise. + + This is taken from: + https://stackoverflow.com/questions/11882393/matplotlib-disregard-outliers-when-plotting + + Parameters: + ----------- + points : An numobservations by numdimensions array of observations + thresh : The modified z-score to use as a threshold. Observations with + a modified z-score (based on the median absolute deviation) greater + than this value will be classified as outliers. + + Returns: + -------- + mask : A numobservations-length boolean array. + + References: + ---------- + Boris Iglewicz and David Hoaglin (1993), "Volume 16: How to Detect and + Handle Outliers", The ASQC Basic References in Quality Control: + Statistical Techniques, Edward F. Mykytka, Ph.D., Editor. + """ + if len(points.shape) == 1: + points = points[:,None] + median = np.median(points, axis=0) + diff = np.sum((points - median)**2, axis=-1) + diff = np.sqrt(diff) + med_abs_deviation = np.median(diff) + + modified_z_score = 0.6745 * diff / med_abs_deviation + + return modified_z_score > thresh + +xx_test = my_testfun.prob_input.get_sample(1000000) +yy_test = my_testfun(xx_test) +yy_test = yy_test[~is_outlier(yy_test, thresh=10)] +idx_pos = yy_test > 0 +idx_neg = yy_test <= 0 + +hist_pos = plt.hist(yy_test, bins="auto", color="#0571b0") +plt.hist(yy_test[idx_neg], bins=hist_pos[1], color="#ca0020") +plt.axvline(0, linewidth=1.0, color="#ca0020") + +plt.grid() +plt.ylabel("Counts [-]") +plt.xlabel("$\mathcal{M}(\mathbf{X})$") +plt.gcf().set_dpi(150); +``` + +### Failure probability ($P_f$) + +Some reference values for the failure probability $P_f$ from the literature +are summarized in the table below. + +| Method | $N$ | $\hat{P}_f$ | $\mathrm{CoV}[\hat{P}_f]$ | Source | Remark | +|:------------:|:--------:|:-----------------------:|:-------------------------:|:-----------------:|--------------------------| +| {term}`FOSM` | $12$ | $9.4231 \times 10^{-5}$ | — | {cite}`Verma2015` | $\beta = 3.734$ (p. 261) | + +## References + +```{bibliography} +:style: unsrtalpha +:filter: docname in docnames +``` + +[^location]: see Example 1, Chapter 8, p. 260 in {cite}`Verma2015`. \ No newline at end of file diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index 330fc4c..90db5c5 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -21,6 +21,7 @@ from .otl_circuit import OTLCircuit from .mclain import McLainS1, McLainS2, McLainS3, McLainS4, McLainS5 from .piston import Piston +from .rs_circular_bar import RSCircularBar from .rs_quadratic import RSQuadratic from .sobol_g import SobolG from .speed_reducer_shaft import SpeedReducerShaft @@ -65,6 +66,7 @@ "McLainS4", "McLainS5", "Piston", + "RSCircularBar", "RSQuadratic", "SobolG", "SpeedReducerShaft", diff --git a/src/uqtestfuns/test_functions/rs_circular_bar.py b/src/uqtestfuns/test_functions/rs_circular_bar.py new file mode 100644 index 0000000..62e6373 --- /dev/null +++ b/src/uqtestfuns/test_functions/rs_circular_bar.py @@ -0,0 +1,86 @@ +""" +Module with an implementation of the circular bar RS problem. + +The two-dimensional function is a variant of the classic RS reliability problem +with one quadratic term [1]. + +References +---------- +1. A. K. Verma, S. Ajit, and D. R. Karanki, “Structural Reliability,” + in Reliability and Safety Engineering, in Springer Series + in Reliability Engineering. London: Springer London, 2016, pp. 257–292. + DOI: 10.1007/978-1-4471-6269-8_8 +""" +import numpy as np + +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecFixDim +from ..core.uqtestfun_abc import UQTestFunABC + +__all__ = ["RSCircularBar"] + +AVAILABLE_INPUT_SPECS = { + "Verma2016": ProbInputSpecFixDim( + name="RSCircularBar-Verma2016", + description=( + "Input model for the circular bar RS from Verma et al. (2016)" + ), + marginals=[ + UnivDistSpec( + name="Y", + distribution="normal", + parameters=[250.0, 25.0], + description="Material mean yield strength [MPa]", + ), + UnivDistSpec( + name="F", + distribution="normal", + parameters=[70.0, 7.0], + description="Force mean value [kN]", + ), + ], + copulas=None, + ), +} + +AVAILABLE_PARAMETERS = { + "Verma2016": 25, # bar diameter in [mm] +} + + +def evaluate(xx: np.ndarray, parameter: float) -> np.ndarray: + """Evaluate the circular bar RS problem on a set of input values. + + Parameters + ---------- + xx : np.ndarray + A two-dimensional input values given by an N-by-2 array + where N is the number of input values. + + Returns + ------- + np.ndarray + The performance function of the problem. + If negative, the system is in failed state. + The output is a one-dimensional array of length N. + """ + # Convert to SI units + yy_ = xx[:, 0] * 1e6 # From [MPa] to [Pa] + ff = xx[:, 1] * 1e3 # From [kN] to [N] + diam = parameter * 1e-3 # From [mm] to [m] + + # Compute the performance function + yy = yy_ - ff / (np.pi * diam**2 / 4) + + return yy + + +class RSCircularBar(UQTestFunABC): + """Concrete implementation of the circular bar RS reliability problem.""" + + _tags = ["reliability"] + _description = "RS problem as a circular bar from Verma et al. (2016)" + _available_inputs = AVAILABLE_INPUT_SPECS + _available_parameters = AVAILABLE_PARAMETERS + _default_spatial_dimension = 2 + + eval_ = staticmethod(evaluate) From 81957bbec4df8b4405e47a0dee5acd2576b69751 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Fri, 7 Jul 2023 15:02:03 +0200 Subject: [PATCH 56/73] Add `reset_rng()` method for `UnivDist` and `ProbInput` classes. - The method when called (with an optional seed number) will create a new RNG attached to the instances. --- .../core/prob_input/probabilistic_input.py | 13 ++++++++++ .../prob_input/univariate_distribution.py | 13 ++++++++++ .../prob_input/test_multivariate_input.py | 24 +++++++++++++++++++ .../core/prob_input/test_univariate_input.py | 22 +++++++++++++++++ 4 files changed, 72 insertions(+) diff --git a/src/uqtestfuns/core/prob_input/probabilistic_input.py b/src/uqtestfuns/core/prob_input/probabilistic_input.py index c679531..9b47230 100644 --- a/src/uqtestfuns/core/prob_input/probabilistic_input.py +++ b/src/uqtestfuns/core/prob_input/probabilistic_input.py @@ -113,6 +113,19 @@ def get_sample(self, sample_size: int = 1) -> np.ndarray: return xx + def reset_rng(self, rng_seed: Optional[int]) -> None: + """Reset the random number generator. + + Parameters + ---------- + rng_seed : int, optional. + The seed used to initialize the pseudo-random number generator. + If not specified, the value is taken from the system entropy. + """ + rng = np.random.default_rng(rng_seed) + object.__setattr__(self, "_rng", rng) + object.__setattr__(self, "rng_seed", rng_seed) + def pdf(self, xx: np.ndarray) -> np.ndarray: """Get the PDF value of the distribution on a set of values. diff --git a/src/uqtestfuns/core/prob_input/univariate_distribution.py b/src/uqtestfuns/core/prob_input/univariate_distribution.py index 9c8568b..9195b54 100644 --- a/src/uqtestfuns/core/prob_input/univariate_distribution.py +++ b/src/uqtestfuns/core/prob_input/univariate_distribution.py @@ -122,6 +122,19 @@ def get_sample(self, sample_size: int = 1) -> np.ndarray: xx, self.distribution, self.parameters, self.lower, self.upper ) + def reset_rng(self, rng_seed: Optional[int]) -> None: + """Reset the random number generator. + + Parameters + ---------- + rng_seed : int, optional. + The seed used to initialize the pseudo-random number generator. + If not specified, the value is taken from the system entropy. + """ + rng = np.random.default_rng(rng_seed) + object.__setattr__(self, "_rng", rng) + object.__setattr__(self, "rng_seed", rng_seed) + def pdf(self, xx: Union[float, np.ndarray]) -> ARRAY_FLOAT: """Compute the PDF of the distribution on a set of values.""" # TODO: check if you put a scalar inside diff --git a/tests/core/prob_input/test_multivariate_input.py b/tests/core/prob_input/test_multivariate_input.py index 1194272..3f47093 100644 --- a/tests/core/prob_input/test_multivariate_input.py +++ b/tests/core/prob_input/test_multivariate_input.py @@ -241,6 +241,30 @@ def test_pass_random_seed(spatial_dimension): assert np.allclose(xx_1, xx_2) +@pytest.mark.parametrize("spatial_dimension", [1, 2, 10, 100]) +def test_reset_rng(spatial_dimension): + """Test resetting the RNG once an instance has been created.""" + marginals = create_random_marginals(spatial_dimension) + + # Create two instances with an identical seed number + rng_seed = 42 + my_input = ProbInput(marginals, rng_seed=rng_seed) + + # Generate sample points + xx_1 = my_input.get_sample(1000) + xx_2 = my_input.get_sample(1000) + + # Assertion: Both samples should not be equal + assert not np.allclose(xx_1, xx_2) + + # Reset the RNG and generate new sample points + my_input.reset_rng(rng_seed) + xx_2 = my_input.get_sample(1000) + + # Assertion: Both samples should now be equal + assert np.allclose(xx_1, xx_2) + + @pytest.mark.parametrize("spatial_dimension", [1, 2, 10, 100]) def test_create_from_spec(spatial_dimension): """Test creating an instance from specification NamedTuple""" diff --git a/tests/core/prob_input/test_univariate_input.py b/tests/core/prob_input/test_univariate_input.py index b51960b..345b929 100644 --- a/tests/core/prob_input/test_univariate_input.py +++ b/tests/core/prob_input/test_univariate_input.py @@ -232,3 +232,25 @@ def test_pass_random_seed(): # Assertion: Both samples are equal because the seed is identical assert np.allclose(xx_1, xx_2) + + +def test_reset_rng(): + """Test resetting the RNG once an instance has been created.""" + + # Create two instances with an identical seed number + rng_seed = 42 + my_input = UnivDist("uniform", [0, 1], rng_seed=rng_seed) + + # Generate sample points + xx_1 = my_input.get_sample(1000) + xx_2 = my_input.get_sample(1000) + + # Assertion: Both samples should not be equal + assert not np.allclose(xx_1, xx_2) + + # Reset the RNG and generate new sample + my_input.reset_rng(rng_seed) + xx_2 = my_input.get_sample(1000) + + # Assertion: Both samples should now be equal + assert np.allclose(xx_1, xx_2) From d57e0fecdebc77d9466e00e60d266e4deeb286ab Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Fri, 7 Jul 2023 15:20:15 +0200 Subject: [PATCH 57/73] Remove `rng_seed_prob_input` from the constructor of UQTestFunABC. - To reset an RNG attached to an instance of `ProbInput`, use the `reset_rng()` method of an instance of `ProbInput`, optionally with a seed number. --- CHANGELOG.md | 8 +++++ docs/getting-started/creating-a-built-in.md | 30 ++++++++++--------- src/uqtestfuns/core/uqtestfun_abc.py | 11 +------ .../test_test_functions.py | 6 ++-- 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e81ec1..751a0d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,9 +18,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 from an example in Santner et al. (2018). - The two-dimensional circular bar RS problem for reliability analysis exercises taken from an example in Verma et al. (2015). +- New instance method for `UnivDist` and `ProbInput` classes + called `reset_rng()`. When called (optionally with a seed number), + a new instance of NumPy default RNG will be created and attached to the instance. ### Changed +- `rng_seed_prob_input` keyword parameter has been removed from the list + of parameters to the constructor of all UQ test functions. + The accepted way to reset an RNG with a seed is to use the instance + method `reset_rng()` (optionally with a seed number) + of the `ProbInput` instance attached. - Some background information in the documentation has been changed to match the description in the JOSS paper draft. diff --git a/docs/getting-started/creating-a-built-in.md b/docs/getting-started/creating-a-built-in.md index 853bd30..2ebf150 100644 --- a/docs/getting-started/creating-a-built-in.md +++ b/docs/getting-started/creating-a-built-in.md @@ -49,20 +49,7 @@ in the context of metamodeling and sensitivity analysis. To instantiate a borehole test function, call the constructor as follows: ```{code-cell} ipython3 -my_testfun = uqtf.Borehole(rng_seed_prob_input=176326) -``` - -```{note} -The parameter `rng_seed_prob_input` is optional; if not specified, -the system entropy is used to initialized -the [NumPy default random generator](https://numpy.org/doc/stable/reference/random/generator.html#numpy.random.default_rng). -The number is given here such that the example is more or less reproducible. - -In UQTestFuns, each instance of test function carries its own pseudo-random -number generator (RNG) for the corresponding probabilitic input model -to avoid using the global NumPy random RNG. -See this [blog post](https://albertcthomas.github.io/good-practices-random-number-generators/) -regarding some good practices on using NumPy RNG. +my_testfun = uqtf.Borehole() ``` To verify whether the instance has been created, @@ -149,6 +136,21 @@ plt.ylabel("Counts [-]") plt.gcf().set_dpi(150); ``` +```{note} +An `ProbInput` instance has a method called `reset_rng()`; +You can call this method to create a new underlying RNG +perhaps with a seed number. +In that case, the seed number is optional; if not specified, +the system entropy is used to initialized +the [NumPy default random generator](https://numpy.org/doc/stable/reference/random/generator.html#numpy.random.default_rng). + +In UQTestFuns, each instance of probabilistic input model carries +its own pseudo-random number generator (RNG) +to avoid using the global NumPy random RNG. +See this [blog post](https://albertcthomas.github.io/good-practices-random-number-generators/) +regarding some good practices on using NumPy RNG. +``` + ## Transformation to the function domain Some UQ methods often produce sample points in a hypercube domain diff --git a/src/uqtestfuns/core/uqtestfun_abc.py b/src/uqtestfuns/core/uqtestfun_abc.py index 517401e..b22e9b7 100644 --- a/src/uqtestfuns/core/uqtestfun_abc.py +++ b/src/uqtestfuns/core/uqtestfun_abc.py @@ -193,11 +193,6 @@ class UQTestFunABC(UQTestFunBareABC): The name of the UQ test function. If not given, `None` is used as name. This is a keyword only parameter. - rng_seed_prob_input : int, optional - The seed number for the pseudo-random number generator of the - corresponding `ProbInput`; if not given, `None` is used - (taken from the system entropy). - This is a keyword only parameter. Notes ----- @@ -223,7 +218,6 @@ def __init__( prob_input_selection: Optional[str] = None, parameters_selection: Optional[str] = None, name: Optional[str] = None, - rng_seed_prob_input: Optional[int] = None, ): # --- Create a probabilistic input model # Select the probabilistic input model @@ -249,12 +243,9 @@ def __init__( prob_input = ProbInput.from_spec( prob_input_spec, spatial_dimension=spatial_dimension, - rng_seed=rng_seed_prob_input, ) else: - prob_input = ProbInput.from_spec( - prob_input_spec, rng_seed=rng_seed_prob_input - ) + prob_input = ProbInput.from_spec(prob_input_spec) # --- Select the parameters set, when applicable available_parameters = self.available_parameters diff --git a/tests/builtin_test_functions/test_test_functions.py b/tests/builtin_test_functions/test_test_functions.py index 553800f..095f49d 100644 --- a/tests/builtin_test_functions/test_test_functions.py +++ b/tests/builtin_test_functions/test_test_functions.py @@ -169,7 +169,8 @@ def test_transform_input(builtin_testfun): rng_seed = 32 # Create an instance - my_fun = testfun(rng_seed_prob_input=rng_seed) + my_fun = testfun() + my_fun.prob_input.reset_rng(rng_seed) sample_size = 100 @@ -192,7 +193,8 @@ def test_transform_input_non_default(builtin_testfun): rng_seed = 1232 # Create an instance - my_fun = testfun(rng_seed_prob_input=rng_seed) + my_fun = testfun() + my_fun.prob_input.reset_rng(rng_seed) sample_size = 100 From 36901488a264f4cab45be105c40ddf83bad43469 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Fri, 7 Jul 2023 15:33:54 +0200 Subject: [PATCH 58/73] Update the docs with the new interface for rng seed setup. - Instead of creating an instance of UQ test function with a seed number, the RNG attached to `ProbInput` should be resetted if required with the seed number. --- docs/fundamentals/reliability.md | 3 ++- docs/test-functions/cantilever-beam-2d.md | 3 ++- docs/test-functions/circular-pipe-crack.md | 3 ++- docs/test-functions/convex-fail-domain.md | 3 ++- docs/test-functions/four-branch.md | 3 ++- docs/test-functions/gayton-hat.md | 3 ++- docs/test-functions/hyper-sphere.md | 3 ++- docs/test-functions/rs-circular-bar.md | 3 ++- docs/test-functions/rs-quadratic.md | 3 ++- 9 files changed, 18 insertions(+), 9 deletions(-) diff --git a/docs/fundamentals/reliability.md b/docs/fundamentals/reliability.md index 5cab416..db77f09 100644 --- a/docs/fundamentals/reliability.md +++ b/docs/fundamentals/reliability.md @@ -117,7 +117,8 @@ accurately with as few function/model evaluations as possible. import numpy as np import matplotlib.pyplot as plt -my_fun = uqtf.CircularPipeCrack(rng_seed_prob_input=237324) +my_fun = uqtf.CircularPipeCrack() +my_fun.prob_input.reset_rng(237324) xx = my_fun.prob_input.get_sample(1000000) yy = my_fun(xx) idx_neg = yy <= 0.0 diff --git a/docs/test-functions/cantilever-beam-2d.md b/docs/test-functions/cantilever-beam-2d.md index 7666a22..4821cce 100644 --- a/docs/test-functions/cantilever-beam-2d.md +++ b/docs/test-functions/cantilever-beam-2d.md @@ -36,7 +36,8 @@ overlaid. ```{code-cell} ipython3 :tags: [remove-input] -my_fun = uqtf.CantileverBeam2D(rng_seed_prob_input=237324) +my_fun = uqtf.CantileverBeam2D() +my_fun.prob_input.reset_rng(237324) xx = my_fun.prob_input.get_sample(1000000) yy = my_fun(xx) idx_neg = yy <= 0.0 diff --git a/docs/test-functions/circular-pipe-crack.md b/docs/test-functions/circular-pipe-crack.md index 4f345a9..e223149 100644 --- a/docs/test-functions/circular-pipe-crack.md +++ b/docs/test-functions/circular-pipe-crack.md @@ -33,7 +33,8 @@ overlaid. ```{code-cell} ipython3 :tags: [remove-input] -my_fun = uqtf.CircularPipeCrack(rng_seed_prob_input=237324) +my_fun = uqtf.CircularPipeCrack() +my_fun.prob_input.reset_rng(237324) xx = my_fun.prob_input.get_sample(1000000) yy = my_fun(xx) idx_neg = yy <= 0.0 diff --git a/docs/test-functions/convex-fail-domain.md b/docs/test-functions/convex-fail-domain.md index 3c2082c..dd55f7f 100644 --- a/docs/test-functions/convex-fail-domain.md +++ b/docs/test-functions/convex-fail-domain.md @@ -33,7 +33,8 @@ overlaid. ```{code-cell} ipython3 :tags: [remove-input] -my_fun = uqtf.ConvexFailDomain(rng_seed_prob_input=237324) +my_fun = uqtf.ConvexFailDomain() +my_fun.prob_input.reset_rng(237324) xx = my_fun.prob_input.get_sample(1000000) yy = my_fun(xx) idx_neg = yy <= 0.0 diff --git a/docs/test-functions/four-branch.md b/docs/test-functions/four-branch.md index d38f7d7..cd7d389 100644 --- a/docs/test-functions/four-branch.md +++ b/docs/test-functions/four-branch.md @@ -36,7 +36,8 @@ overlaid. ```{code-cell} ipython3 :tags: [remove-input] -my_fun = uqtf.FourBranch(rng_seed_prob_input=237324) +my_fun = uqtf.FourBranch() +my_fun.prob_input.reset_rng(237324) xx = my_fun.prob_input.get_sample(1000000) yy = my_fun(xx) idx_neg = yy <= 0.0 diff --git a/docs/test-functions/gayton-hat.md b/docs/test-functions/gayton-hat.md index bdf4725..b03805f 100644 --- a/docs/test-functions/gayton-hat.md +++ b/docs/test-functions/gayton-hat.md @@ -33,7 +33,8 @@ overlaid. ```{code-cell} ipython3 :tags: [remove-input] -my_fun = uqtf.GaytonHat(rng_seed_prob_input=237324) +my_fun = uqtf.GaytonHat() +my_fun.prob_input.reset_rng(237324) xx = my_fun.prob_input.get_sample(1000000) yy = my_fun(xx) idx_neg = yy <= 0.0 diff --git a/docs/test-functions/hyper-sphere.md b/docs/test-functions/hyper-sphere.md index 992be67..495afed 100644 --- a/docs/test-functions/hyper-sphere.md +++ b/docs/test-functions/hyper-sphere.md @@ -33,7 +33,8 @@ overlaid. ```{code-cell} ipython3 :tags: [remove-input] -my_fun = uqtf.HyperSphere(rng_seed_prob_input=237324) +my_fun = uqtf.HyperSphere() +my_fun.prob_input.reset_rng(237324) xx = my_fun.prob_input.get_sample(1000000) yy = my_fun(xx) idx_neg = yy <= 0.0 diff --git a/docs/test-functions/rs-circular-bar.md b/docs/test-functions/rs-circular-bar.md index fd7b11e..516b59d 100644 --- a/docs/test-functions/rs-circular-bar.md +++ b/docs/test-functions/rs-circular-bar.md @@ -34,7 +34,8 @@ overlaid. ```{code-cell} ipython3 :tags: [remove-input] -my_fun = uqtf.RSCircularBar(rng_seed_prob_input=237324) +my_fun = uqtf.RSCircularBar() +my_fun.prob_input.reset_rng(237324) xx = my_fun.prob_input.get_sample(1000000) yy = my_fun(xx) idx_neg = yy <= 0.0 diff --git a/docs/test-functions/rs-quadratic.md b/docs/test-functions/rs-quadratic.md index a15316c..0647f22 100644 --- a/docs/test-functions/rs-quadratic.md +++ b/docs/test-functions/rs-quadratic.md @@ -33,7 +33,8 @@ overlaid. ```{code-cell} ipython3 :tags: [remove-input] -my_fun = uqtf.RSQuadratic(rng_seed_prob_input=237324) +my_fun = uqtf.RSQuadratic() +my_fun.prob_input.reset_rng(237324) xx = my_fun.prob_input.get_sample(1000000) yy = my_fun(xx) idx_neg = yy <= 0.0 From cc919f52ddc6c594cc5240635d692782ea0e38c7 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Fri, 7 Jul 2023 16:44:12 +0200 Subject: [PATCH 59/73] Add an implementation of the 2D function from Webster et al. (1996). - The function is a polynomial function with random input, one of them is non-uniform. --- CHANGELOG.md | 2 + docs/_toc.yml | 2 + docs/fundamentals/metamodeling.md | 53 +++--- docs/references.bib | 10 ++ docs/test-functions/available.md | 1 + docs/test-functions/webster-2d.md | 152 ++++++++++++++++++ src/uqtestfuns/test_functions/__init__.py | 2 + .../test_functions/rs_circular_bar.py | 2 + src/uqtestfuns/test_functions/webster.py | 79 +++++++++ tests/builtin_test_functions/test_sobol_g.py | 2 +- 10 files changed, 278 insertions(+), 27 deletions(-) create mode 100644 docs/test-functions/webster-2d.md create mode 100644 src/uqtestfuns/test_functions/webster.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 751a0d1..71e8cbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - New instance method for `UnivDist` and `ProbInput` classes called `reset_rng()`. When called (optionally with a seed number), a new instance of NumPy default RNG will be created and attached to the instance. +- The two-dimensional polynomial function with random inputs + from Webster et al. (1996) for metamodeling exercises. ### Changed diff --git a/docs/_toc.yml b/docs/_toc.yml index e007f45..e6260aa 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -101,6 +101,8 @@ parts: title: Speed Reducer Shaft - file: test-functions/sulfur title: Sulfur + - file: test-functions/webster-2d + title: Webster et al. (1996) 2D - file: test-functions/welch1992 title: Welch et al. (1992) - file: test-functions/wing-weight diff --git a/docs/fundamentals/metamodeling.md b/docs/fundamentals/metamodeling.md index 942c02b..7f8693b 100644 --- a/docs/fundamentals/metamodeling.md +++ b/docs/fundamentals/metamodeling.md @@ -17,32 +17,33 @@ kernelspec: The table below listed the available test functions typically used in the comparison of metamodeling approaches. -| Name | Spatial Dimension | Constructor | -|:--------------------------------------------------------------:|:-----------------:|:--------------------:| -| {ref}`Ackley ` | M | `Ackley()` | -| {ref}`Borehole ` | 8 | `Borehole()` | -| {ref}`Damped Cosine ` | 1 | `DampedCosine()` | -| {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | -| {ref}`Flood ` | 8 | `Flood()` | -| {ref}`Forrester et al. (2008) ` | 1 | `Forrester2008()` | -| {ref}`(1st) Franke ` | 2 | `Franke1()` | -| {ref}`(2nd) Franke ` | 2 | `Franke2()` | -| {ref}`(3rd) Franke ` | 2 | `Franke3()` | -| {ref}`(4th) Franke ` | 2 | `Franke4()` | -| {ref}`(5th) Franke ` | 2 | `Franke5()` | -| {ref}`(6th) Franke ` | 2 | `Franke6()` | -| {ref}`Gramacy (2007) 1D Sine ` | 1 | `Gramacy1DSine()` | -| {ref}`McLain S1 ` | 2 | `McLainS1()` | -| {ref}`McLain S2 ` | 2 | `McLainS2()` | -| {ref}`McLain S3 ` | 2 | `McLainS3()` | -| {ref}`McLain S4 ` | 2 | `McLainS4()` | -| {ref}`McLain S5 ` | 2 | `McLainS5()` | -| {ref}`Oakley and O'Hagan (2002) 1D ` | 1 | `Oakley1D()` | -| {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | -| {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | -| {ref}`Sulfur ` | 9 | `Sulfur()` | -| {ref}`Welch1992 ` | 20 | `Welch1992()` | -| {ref}`Wing Weight ` | 10 | `WingWeight()` | +| Name | Spatial Dimension | Constructor | +|:--------------------------------------------------------------:|:-----------------:|:---------------------------:| +| {ref}`Ackley ` | M | `Ackley()` | +| {ref}`Borehole ` | 8 | `Borehole()` | +| {ref}`Damped Cosine ` | 1 | `DampedCosine()` | +| {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | +| {ref}`Flood ` | 8 | `Flood()` | +| {ref}`Forrester et al. (2008) ` | 1 | `Forrester2008()` | +| {ref}`(1st) Franke ` | 2 | `Franke1()` | +| {ref}`(2nd) Franke ` | 2 | `Franke2()` | +| {ref}`(3rd) Franke ` | 2 | `Franke3()` | +| {ref}`(4th) Franke ` | 2 | `Franke4()` | +| {ref}`(5th) Franke ` | 2 | `Franke5()` | +| {ref}`(6th) Franke ` | 2 | `Franke6()` | +| {ref}`Gramacy (2007) 1D Sine ` | 1 | `Gramacy1DSine()` | +| {ref}`McLain S1 ` | 2 | `McLainS1()` | +| {ref}`McLain S2 ` | 2 | `McLainS2()` | +| {ref}`McLain S3 ` | 2 | `McLainS3()` | +| {ref}`McLain S4 ` | 2 | `McLainS4()` | +| {ref}`McLain S5 ` | 2 | `McLainS5()` | +| {ref}`Oakley and O'Hagan (2002) 1D ` | 1 | `Oakley1D()` | +| {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | +| {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | +| {ref}`Webster et al. (1996) 2D ` | 2 | `Webster2D()` | +| {ref}`Sulfur ` | 9 | `Sulfur()` | +| {ref}`Welch1992 ` | 20 | `Welch1992()` | +| {ref}`Wing Weight ` | 10 | `WingWeight()` | In a Python terminal, you can list all the available functions relevant for metamodeling applications using ``list_functions()`` and filter the results diff --git a/docs/references.bib b/docs/references.bib index f9cd365..e99476c 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -674,4 +674,14 @@ @Book{Santner2018 doi = {10.1007/978-1-4939-8847-1}, } +@TechReport{Webster1996, + author = {Webster, M. D. and Tatang, M. A. and McRae, G. J.}, + institution = {Massachusetts Institute of Technology}, + title = {Application of the probabilistic collocation method for an uncertainty analysis of a simple ocean model}, + year = {1996}, + address = {Cambridge, MA}, + number = {Joint Program Report Series No. 4}, + url = {http://globalchange.mit.edu/publication/15670}, +} + @Comment{jabref-meta: databaseType:bibtex;} diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index 136c5ca..aad8f20 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -58,6 +58,7 @@ available in the current UQTestFuns, regardless of their typical applications. | {ref}`Sobol'-G ` | M | `SobolG()` | | {ref}`Speed Reducer Shaft ` | 5 | `SpeedReducerShaft()` | | {ref}`Sulfur ` | 9 | `Sulfur()` | +| {ref}`Webster et al. (1996) 2D ` | 2 | `Webster2D()` | | {ref}`Welch1992 ` | 20 | `Welch1992()` | | {ref}`Wing Weight ` | 10 | `WingWeight()` | diff --git a/docs/test-functions/webster-2d.md b/docs/test-functions/webster-2d.md new file mode 100644 index 0000000..0a574b8 --- /dev/null +++ b/docs/test-functions/webster-2d.md @@ -0,0 +1,152 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:webster-2d)= +# Two-Dimensional Function from Webster et al. (1996) + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +The 2D function introduced in {cite}`Webster1996` is a polynomial function. +It was used to illustrate the construction of a polynomial chaos expansion +metamodel (via stochastic collocation) +having uncertain (random) input variables. + +```{code-cell} ipython3 +:tags: [remove-input] + +from mpl_toolkits.axes_grid1 import make_axes_locatable + +my_fun = uqtf.Webster2D() + +# --- Create 2D data +lb_1 = my_fun.prob_input.marginals[0].lower +ub_1 = my_fun.prob_input.marginals[0].upper +lb_2 = ( + my_fun.prob_input.marginals[1].parameters[0] + - 3 * my_fun.prob_input.marginals[1].parameters[1] +) +ub_2 = ( + my_fun.prob_input.marginals[1].parameters[0] + + 3 * my_fun.prob_input.marginals[1].parameters[1] +) + +xx_1 = np.linspace(lb_1, ub_1, 1000)[:, np.newaxis] +xx_2 = np.linspace(lb_2, ub_2, 1000)[:, np.newaxis] +mesh_2d = np.meshgrid(xx_1, xx_2) +xx_2d = np.array(mesh_2d).T.reshape(-1, 2) +yy_2d = my_fun(xx_2d) + +# --- Create two-dimensional plots +fig = plt.figure(figsize=(10, 5)) + +# Surface +axs_1 = plt.subplot(121, projection='3d') +axs_1.plot_surface( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000,1000).T, + linewidth=0, + cmap="plasma", + antialiased=False, + alpha=0.5 +) +axs_1.set_xlabel("$x_1$", fontsize=14) +axs_1.set_ylabel("$x_2$", fontsize=14) +axs_1.set_zlabel("$\mathcal{M}(x_1, x_2)$", fontsize=14) +axs_1.set_title("Surface plot of Webster 2D", fontsize=14) + +# Contour +axs_2 = plt.subplot(122) +cf = axs_2.contourf( + mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000).T, cmap="plasma" +) +axs_2.set_xlim([lb_1, ub_1]) +axs_2.set_ylim([lb_2, ub_2]) +axs_2.set_xlabel("$x_1$", fontsize=14) +axs_2.set_ylabel("$x_2$", fontsize=14) +axs_2.set_title("Contour plot of Webster 2D", fontsize=14) +divider = make_axes_locatable(axs_2) +cax = divider.append_axes('right', size='5%', pad=0.05) +fig.colorbar(cf, cax=cax, orientation='vertical') + +fig.tight_layout(pad=4.0) +plt.gcf().set_dpi(75); +``` + +## Test function instance + +To create a default instance of the test function: + +```{code-cell} ipython3 +my_testfun = uqtf.Webster2D() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +## Description + +The Webster 2D function is defined as follows[^location]: + +$$ +\mathcal{M}(\boldsymbol{x}) = A^2 + B^3, +$$ +where $\boldsymbol{x} = \{ A, B \}$ +is the two-dimensional vector of input variables further defined below. + +## Probabilistic input + +Based on {cite}`Webster1996`, the probabilistic input model +for the function consists of two independent random variables as shown below. + +```{code-cell} ipython3 +my_testfun.prob_input +``` + +## Reference results + +This section provides several reference results of typical UQ analyses involving +the test function. + +### Sample histogram + +Shown below is the histogram of the output based on $100'000$ random points: + +```{code-cell} ipython3 +:tags: [hide-input] + +xx_test = my_testfun.prob_input.get_sample(100000) +yy_test = my_testfun(xx_test) + +plt.hist(yy_test, bins="auto", color="#8da0cb"); +plt.grid(); +plt.ylabel("Counts [-]"); +plt.xlabel("$\mathcal{M}(\mathbf{X})$"); +plt.gcf().set_dpi(150); +``` + +## References + +```{bibliography} +:filter: docname in docnames +``` + +[^location]: see Eq. (8), Section 2.2, p. 4 in {cite}`Webster1996`. \ No newline at end of file diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index 90db5c5..0cf760e 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -26,6 +26,7 @@ from .sobol_g import SobolG from .speed_reducer_shaft import SpeedReducerShaft from .sulfur import Sulfur +from .webster import Webster2D from .welch1992 import Welch1992 from .wing_weight import WingWeight @@ -71,6 +72,7 @@ "SobolG", "SpeedReducerShaft", "Sulfur", + "Webster2D", "Welch1992", "WingWeight", ] diff --git a/src/uqtestfuns/test_functions/rs_circular_bar.py b/src/uqtestfuns/test_functions/rs_circular_bar.py index 62e6373..43051d7 100644 --- a/src/uqtestfuns/test_functions/rs_circular_bar.py +++ b/src/uqtestfuns/test_functions/rs_circular_bar.py @@ -55,6 +55,8 @@ def evaluate(xx: np.ndarray, parameter: float) -> np.ndarray: xx : np.ndarray A two-dimensional input values given by an N-by-2 array where N is the number of input values. + parameter : float + The parameter of the function (i.e., the diameter of the bar in [mm]). Returns ------- diff --git a/src/uqtestfuns/test_functions/webster.py b/src/uqtestfuns/test_functions/webster.py new file mode 100644 index 0000000..0da069e --- /dev/null +++ b/src/uqtestfuns/test_functions/webster.py @@ -0,0 +1,79 @@ +""" +Module with an implementation of the 2D function from Webster et al. (1996). + +The two-dimensional function is polynomial with random input variables. +It was introduced in [1] and used to illustrate the construction of polynomial +chaos expansion metamodel. + +References +---------- +1. M. Webster, M. A. Tatang, and G. J. McRae, “Application of the probabilistic + collocation method for an uncertainty analysis of a simple ocean model,” + Massachusetts Institute of Technology, Cambridge, MA, + Joint Program Report Series 4, 1996. + [Online]. Available: http://globalchange.mit.edu/publication/15670 +""" +import numpy as np + +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecFixDim +from ..core.uqtestfun_abc import UQTestFunABC + +__all__ = ["Webster2D"] + + +AVAILABLE_INPUT_SPECS = { + "Webster1996": ProbInputSpecFixDim( + name="Webster1996", + description=( + "Input specification for the 2D function " + "from Webster et al. (1996)" + ), + marginals=[ + UnivDistSpec( + name="A", + distribution="uniform", + parameters=[1.0, 10.0], + description="None", + ), + UnivDistSpec( + name="B", + distribution="normal", + parameters=[2.0, 1.0], + description="None", + ), + ], + copulas=None, + ), +} + + +def evaluate(xx: np.ndarray): + """Evaluate the 2D Webster function on a set of input values. + + Parameters + ---------- + xx : np.ndarray + Two-Dimensional input values given by N-by-2 arrays where + N is the number of input values. + + Returns + ------- + np.ndarray + The output of the 2D Webster function evaluated on the input values. + The output is a 1-dimensional array of length N. + """ + yy = xx[:, 0] ** 2 + xx[:, 1] ** 3 + + return yy + + +class Webster2D(UQTestFunABC): + """A concrete implementation of the function from Webster et al. (1996).""" + + _tags = ["metamodeling"] + _description = "2D polynomial function from Webster et al. (1996)." + _available_inputs = AVAILABLE_INPUT_SPECS + _available_parameters = None + _default_spatial_dimension = 2 + + eval_ = staticmethod(evaluate) diff --git a/tests/builtin_test_functions/test_sobol_g.py b/tests/builtin_test_functions/test_sobol_g.py index f99ab94..0b3f607 100644 --- a/tests/builtin_test_functions/test_sobol_g.py +++ b/tests/builtin_test_functions/test_sobol_g.py @@ -37,7 +37,7 @@ def test_compute_mean(spatial_dimension, params_selection): assert my_fun.prob_input is not None # Compute mean via Monte Carlo - xx = my_fun.prob_input.get_sample(500000) + xx = my_fun.prob_input.get_sample(1000000) yy = my_fun(xx) mean_mc = np.mean(yy) From 2793adc4327008350e466d2a200d7d5849ae42c2 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Fri, 7 Jul 2023 17:14:36 +0200 Subject: [PATCH 60/73] Update paper; package rationale. --- joss/paper.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/joss/paper.md b/joss/paper.md index 8da4d87..27469b9 100644 --- a/joss/paper.md +++ b/joss/paper.md @@ -155,8 +155,9 @@ There are, however, some major differences: - One of the `otbenchmark`'s main aims is to provide the OpenTURNS development team with a tool for helping with the implementation of new algorithms. As such, it is built on top of and coupled to OpenTURNS. - `UQTestFuns`, on the other hand, has fewer dependencies and is more lean - in its implementations. + `UQTestFuns`, on the other hand, has fewer dependencies and is leaner + in its implementations; + it is more agnostic with respect to any particular UQ analysis package. - `UQTestFuns` is more modest in its scope, that is, simply to provide a library of UQ test functions implemented in Python with a consistent interface and an online reference From bf68164b80b231e6d9f1e75dff69006a2f1d9e9d Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Fri, 7 Jul 2023 18:05:11 +0200 Subject: [PATCH 61/73] Prepare the release of v0.4.0 --- CHANGELOG.md | 13 +++++++---- CITATION.cff | 21 +++++++++-------- README.md | 39 +++++++++++++++++++------------- docs/test-functions/available.md | 5 ++-- setup.cfg | 3 ++- 5 files changed, 49 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71e8cbc..609a3ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.4.0] - 2023-07-07 + ### Added - The two-dimensional convex failure domain problem for reliability @@ -18,11 +20,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 from an example in Santner et al. (2018). - The two-dimensional circular bar RS problem for reliability analysis exercises taken from an example in Verma et al. (2015). -- New instance method for `UnivDist` and `ProbInput` classes - called `reset_rng()`. When called (optionally with a seed number), - a new instance of NumPy default RNG will be created and attached to the instance. - The two-dimensional polynomial function with random inputs from Webster et al. (1996) for metamodeling exercises. +- New instance method for `UnivDist` and `ProbInput` classes + called `reset_rng()`. When called (optionally with a seed number), a new + instance of NumPy default RNG will be created and attached to the instance. +- GitHub actions now include testing on Python v3.11 via Tox. ### Changed @@ -36,7 +39,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- A mistake in one the parameter values of the Sobol'-G function has been fixed. +- A mistake in one the parameter values of the Sobol'-G function + has been fixed. ## [0.3.0] - 2023-07-03 @@ -160,6 +164,7 @@ First public release of UQTestFuns. - Mirror GitHub action to the [CASUS organization](https://github.com/casus) [Unreleased]: https://github.com/damar-wicaksono/uqtestfuns/compare/main...dev +[0.4.0]: https://github.com/damar-wicaksono/uqtestfuns/compare/v0.3.0...v0.4.0 [0.3.0]: https://github.com/damar-wicaksono/uqtestfuns/compare/v0.2.0...v0.3.0 [0.2.0]: https://github.com/damar-wicaksono/uqtestfuns/compare/v0.1.1...v0.2.0 [0.1.1]: https://github.com/damar-wicaksono/uqtestfuns/compare/v0.1.0...v0.1.1 diff --git a/CITATION.cff b/CITATION.cff index 56d9762..6e8c4a4 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -14,8 +14,8 @@ authors: orcid: 'https://orcid.org/0000-0001-8587-7730' identifiers: - type: doi - value: 10.5281/zenodo.8109901 - description: The Zenodo URL of version 0.3.0 of the package + value: 10.5281/zenodo.8125015 + description: The Zenodo URL of version 0.4.0 of the package repository-code: 'https://github.com/damar-wicaksono/uqtestfuns' url: 'https://uqtestfuns.readthedocs.io/en/latest/' repository-artifact: 'https://pypi.org/project/uqtestfuns/' @@ -23,17 +23,20 @@ abstract: >- UQTestFuns is an open-source Python3 library of test functions commonly used within the uncertainty quantification (UQ) community. The package aims to provide - a lightweight implementation (with minimal dependencies) + an implementation with minimal dependencies and a common interface of many test functions available in the UQ literature, - a single entry point (combining models and their - probabilistic input specification) to a wide range of test - functions, and an opportunity for an open-source contribution - where new test functions and reference results are posted. + a single entry point collecting test functions and their probabilistic + input specifications in a single Python package, + and an opportunity for an open-source contribution, + supporting the implementation of new test functions + or posting reference results. keywords: - uncertainty-quantification - test-functions - sensitivity-analysis - metamodeling + - reliability-analysis + - surrogate-modeling license: MIT -version: 0.3.0 -date-released: '2023-07-03' +version: 0.4.0 +date-released: '2023-07-07' diff --git a/README.md b/README.md index e941566..035756c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # UQTestFuns -[![DOI](http://img.shields.io/badge/DOI-10.5281/zenodo.8109901-blue.svg?style=flat-square)](https://doi.org/10.5281/zenodo.8109901) +[![DOI](http://img.shields.io/badge/DOI-10.5281/zenodo.8125015-blue.svg?style=flat-square)](https://doi.org/10.5281/zenodo.8125015) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg?style=flat-square)](https://github.com/psf/black) [![Python 3.7](https://img.shields.io/badge/python-3.7-blue.svg?style=flat-square)](https://www.python.org/downloads/release/python-370/) [![License](https://img.shields.io/github/license/damar-wicaksono/uqtestfuns?style=flat-square)](https://choosealicense.com/licenses/mit/) @@ -12,15 +12,15 @@ UQTestFuns is an open-source Python3 library of test functions commonly used -within the uncertainty quantification (UQ) community. -The package aims to provide: +within the applied uncertainty quantification (UQ) community. +Specifically, the package provides: -- a _lightweight implementation_ (with minimal dependencies) of - many test functions available in the UQ literature -- a _single entry point_ (combining models and their probabilistic input - specification) to a wide range of test functions -- an opportunity for an _open-source contribution_ where new test functions and - reference results are posted. +- an implementation _with minimal dependencies_ (i.e., NumPy and SciPy) and + _a common interface_ of many test functions available in the UQ literature +- a _single entry point_ collecting test functions _and_ their probabilistic + input specifications in a single Python package +- an _opportunity for an open-source contribution_, supporting + the implementation of new test functions or posting reference results. In short, UQTestFuns is an homage to the [Virtual Library of Simulation Experiments (VLSE)](https://www.sfu.ca/~ssurjano/). @@ -33,13 +33,16 @@ To list the available functions: ```python-repl >>> import uqtestfuns as uqtf >>> uqtf.list_functions() - No. Constructor Spatial Dimension Application Description ------ ------------------ ------------------- -------------------------- ---------------------------------------------------------------------------- - 1 Ackley() M optimization, metamodeling Ackley function from Ackley (1987) - 2 Borehole() 8 metamodeling, sensitivity Borehole function from Harper and Gupta (1983) - 3 DampedOscillator() 8 metamodeling, sensitivity Damped oscillator model from Igusa and Der Kiureghian (1985) - 4 Flood() 8 metamodeling, sensitivity Flood model from Iooss and Lemaître (2015) - 5 Ishigami() 3 sensitivity Ishigami function from Ishigami and Homma (1991) +No. Constructor Dimension Application Description +----- ----------------------------- ----------- -------------------------------------- ---------------------------------------------------------------------------- + 1 Ackley() M optimization, metamodeling Optimization test function from Ackley (1987) + 2 Borehole() 8 metamodeling, sensitivity Borehole function from Harper and Gupta (1983) + 3 Bratley1992a() M integration, sensitivity Integration test function #1 from Bratley et al. (1992) + 4 Bratley1992b() M integration, sensitivity Integration test function #2 from Bratley et al. (1992) + 5 Bratley1992c() M integration, sensitivity Integration test function #3 from Bratley et al. (1992) + 6 Bratley1992d() M integration, sensitivity Integration test function #4 from Bratley et al. (1992) + 7 CantileverBeam2D() 2 reliability Cantilever beam reliability problem from Rajashekhar and Ellington (1993) + 8 CircularPipeCrack() 2 reliability Circular pipe under bending moment from Verma et al. (2015) ... ``` @@ -155,6 +158,10 @@ UQTestFuns is currently maintained by: - Damar Wicaksono ([HZDR/CASUS](https://www.casus.science/)) +with scientific supervision from: + +- Michael Hecht ([HZDR/CASUS](https://www.casus.science/)) + ## License diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index aad8f20..ad9a1de 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -15,8 +15,9 @@ kernelspec: (test-functions:available)= # All Available Functions -The table below lists all the available _classic_ test functions from the literature -available in the current UQTestFuns, regardless of their typical applications. +The table below lists all the test functions available in UQTestFuns +from the uncertainty quantification (UQ) literature, +regardless of their typical applications. | Name | Spatial Dimension | Constructor | |:-----------------------------------------------------------------------------------:|:-----------------:|:-------------------------------:| diff --git a/setup.cfg b/setup.cfg index 1f7f518..1bad8cf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = uqtestfuns -version = 0.3.0 +version = 0.4.0 url = https://github.com/damar-wicaksono/uqtestfuns author = Damar Wicaksono author_email = damar.wicaksono@outlook.com @@ -18,6 +18,7 @@ classifiers = Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 Topic :: Scientific/Engineering Topic :: Scientific/Engineering :: Mathematics Topic :: Software Development :: Libraries :: Python Modules From 055bc12c70c35ba04cdd6a7b8fd4500e70384701 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Fri, 7 Jul 2023 22:45:39 +0200 Subject: [PATCH 62/73] Remove unused references in the BibTeX file. --- joss/paper.bib | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/joss/paper.bib b/joss/paper.bib index 38a44a3..ad74579 100644 --- a/joss/paper.bib +++ b/joss/paper.bib @@ -26,27 +26,6 @@ @misc{Rozsas:2019 howpublished = {Retrieved June 30, 2023, from \url{https://gitlab.com/rozsasarpi/rprepo/}} } -@article{Tennoe:2018a, - author = {S. Tennøe and G. Halnes and G. T. Einevoll}, - title = {Uncertainpy: A {Python} toolbox for uncertainty quantification and sensitivity analysis in computational neuroscience}, - journal = {Frontiers in Neuroinformatics}, - year = 2018, - month = aug, - volume = 12, - number = 49, - doi = {10.3389/fninf.2018.00049}, -} - - -@misc{Tennoe:2018b, - author = {S. Tennøe}, - title = {Uncertainpy: A {Python} toolbox for uncertainty quantification and sensitivity analysis in computational neuroscience}, - year = {2018}, - publisher = {GitHub}, - journal = {GitHub repository}, - url = {https://github.com/simetenn/uncertainpy} -} - @InProceedings{Fekhari2021, author = {Elias Fekhari and Michaël Baudin and Vincent Chabridon and Youssef Jebroun}, booktitle = {4th International Conference on Uncertainty Quantification in Computational Sciences and Engineering}, From 11e16747f4cdc89ce71345fa277a5da84740090f Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Wed, 23 Aug 2023 18:26:23 +0200 Subject: [PATCH 63/73] Add additional tutorials for UQTestFuns in the docs. - The tutorials include how functions in UQTestFuns may be used in a sensitivity analysis or reliability analysis exercise. - This is part of resolving the issues raised by the reviewer in the JOSS submission. --- docs/_config.yml | 1 + docs/_toc.yml | 2 + docs/getting-started/tutorial-reliability.md | 316 +++++++++++++ docs/getting-started/tutorial-sensitivity.md | 458 +++++++++++++++++++ docs/references.bib | 64 +++ setup.cfg | 1 + 6 files changed, 842 insertions(+) create mode 100644 docs/getting-started/tutorial-reliability.md create mode 100644 docs/getting-started/tutorial-sensitivity.md diff --git a/docs/_config.yml b/docs/_config.yml index c10acc0..48d6d41 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -37,3 +37,4 @@ sphinx: - 'sphinx.ext.autodoc' - 'sphinx.ext.napoleon' - 'sphinx.ext.intersphinx' + - 'sphinx_proof' diff --git a/docs/_toc.yml b/docs/_toc.yml index e6260aa..9c08532 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -8,6 +8,8 @@ parts: chapters: - file: getting-started/obtaining-and-installing - file: getting-started/creating-a-built-in + - file: getting-started/tutorial-sensitivity + - file: getting-started/tutorial-reliability - file: getting-started/creating-a-custom - file: getting-started/about-uq-test-functions - caption: Test Functions diff --git a/docs/getting-started/tutorial-reliability.md b/docs/getting-started/tutorial-reliability.md new file mode 100644 index 0000000..dd8eeb6 --- /dev/null +++ b/docs/getting-started/tutorial-reliability.md @@ -0,0 +1,316 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(getting-started:tutorial-reliability)= +# Tutorial: Reliability Analysis + +UQTestFuns includes several test functions from the literature employed +in a reliability analysis exercise. +In this tutorial, you'll implement a method to estimate the failure probability +of a computational model and test the implementation using a test function +available in UQTestFuns. + +By the end of this tutorial, you'll get an idea how a function from UQTestFuns +is used to test a reliability analysis method. + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +## Reliability analysis + +Consider a system whose performance is defined by a _performance function_[^lsf] +$g$ whose values, in turn, depend on: + +- $\boldsymbol{x}_p$: the (uncertain) input variables + of an underlying computational model $\mathcal{M}$ +- $\boldsymbol{x}_s$: additional (uncertain) input variables that affects + the performance of the system, but not part of inputs to $\mathcal{M}$ +- $\boldsymbol{p}$: an additional set of _deterministic_ parameters of the system + +Combining these variables and parameters as an input to the performance function $g$: + +$$ +g(\boldsymbol{x}; \boldsymbol{p}) = g(\mathcal{M}(\boldsymbol{x}_p), \boldsymbol{x}_s; \boldsymbol{p}), +$$ + +where $\boldsymbol{x} = \{ \boldsymbol{x}_p, \boldsymbol{x}_s \}$. + +The system is said to be in _failure state_ if and only if +$g(\boldsymbol{x}; \boldsymbol{p}) \leq 0$; +the set of all values $\{ \boldsymbol{x}, \boldsymbol{p} \}$ +such that $g(\boldsymbol{x}; \boldsymbol{p}) \leq 0$ +is called the _failure domain_. + +Conversely, the system is said to be in _safe state_ +if and only if $g(\boldsymbol{x}; \boldsymbol{p}) > 0$; +the set of all values $\{ \boldsymbol{x}, \boldsymbol{p} \}$ +such that $g(\boldsymbol{x}; \boldsymbol{p}) > 0$ +is called the _safe domain_. + +### Failure probability + +_Reliability analysis_[^rare-event] concerns with estimating +the failure probability of a system with a given performance function $g$. +For a given joint probability density function (PDF) $f_{\boldsymbol{X}}$ +of the uncertain input variables $\boldsymbol{X} = \{ \boldsymbol{X}_p, \boldsymbol{X}_s \}$, +the failure probability $P_f$ of the system is defined +as follows {cite}`Sudret2012, Verma2015`: + +$$ +P_f \equiv \mathbb{P}[g(\boldsymbol{X}; \boldsymbol{p}) \leq 0] = \int_{\{ \boldsymbol{x} | g(\boldsymbol{x}; \boldsymbol{p}) \leq 0 \}} f_{\boldsymbol{X}} (\boldsymbol{x}) \, \; d\boldsymbol{x}. +$$ + +Evaluating the above integral is, in general, non-trivial because the domain +of integration is only provided implicitly and the number of dimensions may +be large. + +### Monte-Carlo estimation + +```{warning} +The Monte-Carlo method implemented below is one of the most straightforward +and robust approach to estimate (small) failure probability. +However, the method is rarely used for practical applications due to its +high computational cost. +It is used here in this tutorial simply as an illustration. +Numerous methods have been developed to efficiently and accurately estimate +the failure probability of a computational model. +``` + +The failure probability of a computational model given probabilistic inputs +may be directly estimated using a Monte-Carlo simulation. +Such a method is straightforward to implement though potentially computationally +expensive as the chance of observing a failure event in a typical reliability +analysis problem is very small. + +An alternative formulation of the failure probability following +{cite}`Beck2015` that aligns well with Monte-Carlo simulation is given below: + +$$ +Pf \equiv \mathbb{P}[g(\boldsymbol{X}; \boldsymbol{p}) \leq 0] = \int_{\mathcal{D}_{\boldsymbol{X}}} \mathbb{I}[g(\boldsymbol{x} \leq 0)] f_{\boldsymbol{X}} (\boldsymbol{x}) \, d\boldsymbol{x}, +$$ +where $\mathbb{I}[g(\boldsymbol{x}; \boldsymbol{p}) \leq 0]$ is the indicator function such that: + +$$ +\mathbb{I}[g(\boldsymbol{x}; \boldsymbol{p}) \leq 0] = + \begin{cases} + 1, g(\boldsymbol{x}; \boldsymbol{p}) \leq 0 \\ + 0, g(\boldsymbol{x}; \boldsymbol{p}) > 0. \\ + \end{cases} +$$ + +The Monte-Carlo estimate of the failure probability is given as follows: + +$$ +P_f \approx \widehat{P}_f = \frac{1}{N} \sum^N_{i = 1} \mathbb{I}[g(\boldsymbol{x}^{(i)} \leq 0], +$$ +where $N$ is the number of Monte-Carlo sample points. + +To assess the accuracy of the estimate given by the above equation, +the coefficient of variation ($\mathrm{CoV}$) is often used: + +$$ +\mathrm{CoV}[\widehat{P}_f] \equiv \frac{\left( \mathbb{V}[\widehat{P}_f] \right)^{0.5}}{\mathbb{E}[\widehat{P}_f]} = \left( \frac{1 - \widehat{P}_f}{N \widehat{P}_f} \right)^{0.5}, +$$ + +The Monte-Carlo simulation method to estimate the failure probability is +summarized in {prf:ref}`MC Simulation Pf`. + +```{prf:algorithm} Monte-Carlo simulation for estimating $P_f$ +:label: MC Simulation Pf + +**Inputs** A performance function $g$, random input variables $\boldsymbol{X}$, number of MC sample points $N$ + +**Output** $\widehat{P}_f$ and $\mathrm{CoV}[\widehat{P}_f]$ + +1. $N_f \leftarrow 0$ +2. For $i = 1$ to $N$: + + 1. Sample $\boldsymbol{x}^{(i)}$ from $\boldsymbol{X}$ + 2. Evaluate $g(\boldsymbol{x}^{(i); \boldsymbol{p}})$ + 3. If $g(\boldsymbol{x}^{(i)}; \boldsymbol{p}) \leq 0$: + + - $N_f \leftarrow N_f + 1$ + + 3. $\widehat{P}_f = \frac{N_f}{N}$ + 4. $\mathrm{CoV}[\widehat{P}_f] = \left( \frac{1 - \widehat{P}_f}{N \widehat{P}_f} \right)^{0.5}$ +``` + +{prf:ref}`MC Simulation Pf` is implemented in a Python function that assumes +the performance function can be evaluated in a vectorized manner +and a probabilistic model of the relevant inputs has been defined such that +sample points can be generated from them. + +```{note} +And indeed, test functions included in UQTestFuns are all given with the +corresponding probabilistic input model according to the literature. +``` + +```{code-cell} ipython3 +:tags: [hide-input] + +def estimate_pf(performance_function, prob_input, sample_size): + """Estimate failure probability via MC simulation. + + Parameters + ---------- + performance_function + The function to be evaluated; accepts a vector of values as the input. + prob_input + The probabilistic input model of the performance function. + sample_size + The Monte-Carlo simulation sample size. + + Returns + ------- + Tuple + The estimated failure probability and its coefficient of variation (CoV). + """ + + xx = prob_input.get_sample(sample_size) + yy = performance_function(xx) + + pf = np.sum(yy <= 0) / sample_size + cov = np.sqrt((1 - pf) / (sample_size * pf)) + + return pf, cov +``` + +## Two-dimensional cantilever beam reliability problem + +To test the implemented algorithm above, we choose the two-dimensional +cantilever beam reliability problem included in UQTestFuns. Several published +results are available for this problem. + +The reliability problem consists of a cantilever beam with a rectangular +cross-section subjected to a uniformly distributed loading. +The maximum deflection at the free end is taken to be the performance criterion +such that the performance function reads: + +$$ +g(\boldsymbol{x}; \boldsymbol{p}) = \frac{l}{325} - \frac{12 l^4 w}{8 E h^3}, +$$ +where $\boldsymbol{x} = \{ w, h \}$ is the two-dimensional vector of +input variables, namely the load per unit area ($w$) +and the depth of the cross section ($h$); +and $\boldsymbol{p} = \{ E l\}$ is the vector of parameters, +namely the modulus of elasticity of the beam ($E$) and the span of the beam +($l$). + +To create an instance of the cantilever beam function: + +```{code-cell} ipython3 +cantilever = uqtf.CantileverBeam2D() +``` + +The input variables $w$ and $h$ are probabilistically defined according +to the table below. + +```{code-cell} ipython3 +cantilever.prob_input +``` + +The default values of the parameters $E$ and $l$ are: + +```{code-cell} ipython3 +cantilever.parameters +``` + +For reproducibility of this tutorial, set the seed number of the +pseudo-random generator attached to the probabilistic input model: + +```{code-cell} ipython3 +cantilever.prob_input.reset_rng(245634) +``` + +Finally, several published results of the failure probability of the problem +are as follows: + +- $\widehat{P}_f = 9.88 \times 10^{-3}$ using {term}`FORM` with $27$ + performance function evaluations ({cite}`Li2018`) +- $\widehat{P}_f = 9.6071 \times 10^{-3}$ using {term}`IS` with $10^3$ + performance function evaluations ({cite}`Rajashekhar1993`) +- $\widehat{P}_f = 9.499 \times 10^{-3}$ using sequential surrogate reliability + method with $18$ performance function evaluations ({cite}`Li2018`) + +## Failure probability estimation + +To observe the convergence of the estimation procedure implemented above, +several Monte-Carlo sample sizes are used: + +```{code-cell} ipython3 +sample_sizes = 5**np.arange(3, 12) +pf_estimates = np.zeros(len(sample_sizes)) +cov_estimates = np.zeros(len(sample_sizes)) + +for i, sample_size in enumerate(sample_sizes): + pf_estimates[i], cov_estimates[i] = estimate_pf( + cantilever, cantilever.prob_input, sample_size + ) +``` + +The estimated failure probability as a function of sample size is plotted +below. The uncertainty band around the estimate is also included and it +corresponds to one standard deviation. + +```{code-cell} ipython3 +:tags: [hide-input] + +fig, ax = plt.subplots(1, 1) + +ax.plot(sample_sizes, pf_estimates, color="k", linewidth=1.0) +ax.plot( + sample_sizes, pf_estimates * (1 + cov_estimates), color="k", linestyle="--" +) +ax.plot( + sample_sizes, pf_estimates * (1 - cov_estimates), color="k", linestyle="--" +) +ax.fill_between( + sample_sizes, + pf_estimates * (1 + cov_estimates), + pf_estimates * (1 - cov_estimates), + color="gray", +) +ax.grid() +ax.set_xscale("log") +ax.set_xlabel("MC sample size", fontsize=14) +ax.set_ylabel(r"$\widehat{P}_f$", fontsize=14); +``` + +The final estimate of the failure probability +(from $>10^7$ function evaluations) is: + +```{code-cell} ipython3 +print(f"{pf_estimates[-1]:1.4e}") +``` + +This values seems to be consistent with the provided published results +albeit with a much higher computational cost. + +The challenge for a reliability analysis method is to accurately and +efficiently (as few performance function evaluations as possible) +estimate the failure probability. + +## References + +```{bibliography} +:style: unsrtalpha +:filter: docname in docnames +``` + +[^lsf]: also called _limit-state function_ + +[^rare-event]: also called rare-events estimation \ No newline at end of file diff --git a/docs/getting-started/tutorial-sensitivity.md b/docs/getting-started/tutorial-sensitivity.md new file mode 100644 index 0000000..9375387 --- /dev/null +++ b/docs/getting-started/tutorial-sensitivity.md @@ -0,0 +1,458 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(getting-started:tutorial-sensitivity)= +# Tutorial: Sensitivity Analysis + +UQTestFuns includes a wide range of test functions from the literature +employed in a sensitivity analysis exercise. +In this tutorial, you'll implement a sensitivity analysis method and test +the implemented method using a test function available in UQTestFuns +(the popular Ishigami function). +Afterward, you'll compare the results against analytical results. + +By the end of this tutorial, you'll get an idea how a function from UQTestFuns +is used to test a sensitivity analysis method. + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +## Global sensitivity analysis + +Sensitivity analysis is a model inference technique whose overarching goal is +to understand the inputs/outputs relationship of a complex +(potentially, black box) model. Specific goals include: + +- _identification of inputs that drive output variability_ that leads to + _factor prioritization_, that is, identifying which inputs that result in + the largest reduction in the output variability. +- _identification of non-influential inputs_ that leads to _factor fixing_, + that is, identifying which inputs that can be fixed at any value without + affecting the output. + +A _global_ sensitivity analysis is often distinguished from a _local_ analysis. +A local analysis is concerned with the effects +of perturbation around a particular instance of the inputs +(e.g., their nominal values) on the output. +A global analysis, on the other hand, is concerned with the effects of +variations over all possible instances of inputs +within their respective uncertainty range on the output. + +### Variance decomposition + +Consider an $M$-dimensional mathematical function +$\mathcal{M}: \mathcal{D}_{\boldsymbol{X}} \in [0, 1]^M \mapsto \mathbb{R}$ that represents +a computational model of interest. + +Due to uncertain inputs, represented as an $M$-dimensional random vector $\boldsymbol{X}$, +the output of the model becomes a random variable $Y = \mathcal{M}(\boldsymbol{X})$. +It is further assumed that the components $X_m$'s of the random vector $\boldsymbol{X}$ +are mutually independent such that the joint probability density function $f_{\boldsymbol{X}}$ +reads: + +$$ +f_{\boldsymbol{X}}(\boldsymbol{x}) = \prod_{m = 1}^M f_{X_m}(x_m), +$$ + +where $f_{X_m}$ is the marginal probability density function of $X_m$. + +The Hoeffding-Sobol' variance decomposition of random variable $Y$ reads as follows: + +$$ +\mathbb{V}[Y] = \sum_{m = 1}^M V_i + \sum_{1 \leq i < j \leq M} V_{m, n} + \ldots + V_{1, \ldots, M}, +$$ + +where the terms are partial variances defined below: + +- $V_m = \mathbb{V}_{X_m} \left[ \mathbb{E}_{X_{\sim m}}\left[ Y | X_m \right]\right]$ +- $V_{m, n} = \mathbb{V}_{X_{\{m, n\}}} \left[ \mathbb{E}_{X_{\sim \{m, n\}}}\left[ Y | X_m, X_n \right]\right]$ +- etc. + +### Sobol' sensitivity indices + +By normalizing the above equation with the total variance $\mathbb{V}[Y]$, we obtain the following expression: + +$$ +1 = \sum_{m = 1}^M S_m + \sum_{1 \leq m < n \leq M} S_{m, n} + \ldots + S_{1, \ldots, M}, +$$ + +where now each term is a normalized partial variance. +These normalized terms are called Sobol' sensitivity indices and there are $2^M - 1$ (where $M$ is the number of dimension) +indices. + +Of particular importance is the _first-order_ (or _main-effect_) Sobol' index $S_m$ defined as follows {cite}`Sobol1993`: + +$$ +S_m = \frac{\mathbb{V}_{X_m} \left[ \mathbb{E}_{X_{\sim m}}\left[ Y | \boldsymbol{X}_m \right]\right]}{\mathbb{V}[Y]}. +$$ + +This index indicates the importance of a particular input variable on the output variance. These indices are aligned +with the aforementioned _factor prioritization_ goal of sensitivity analysis. + +Another sensitivity index of particular importance is the _total-effect_ Sobol' index defined for input variable $m$ +below {cite}`Homma1996`: + +$$ +ST_m = 1 - \frac{\mathbb{V}_{\boldsymbol{X}_{\sim m}}[\mathbb{E}_{X_m}[Y | \boldsymbol{X}_{\sim m}]]}{\mathbb{V}[Y]}. +$$ + +### Monte-Carlo estimation + +```{warning} +A method to estimate the main-effect and total-effect indices via +a Monte-Carlo simulation is implemented here following the most +naive and straightforward approach. +It is only to serve as an illustration. +This implementation is, however, not the state-of-the-art approach to estimate +the indices (see, for instance, {cite}`Saltelli2002, Saltelli2010`). +Please refer to a dedicated sensitivity analysis and uncertainty +quantification package for a proper analysis. +``` + +The estimation of the Sobol' sensitivity indices as defined +in the above equations can be directly carried out using +a Monte-Carlo simulation. +The most straightforward, though rather naive and computationally expensive, +method is to use a nested loop for the +computation of the conditional variances and expectations appearing +in both indices. + +For instance, in the estimation of the first-order index of, say, +input variable $x_m$, the outer loop samples values of $X_m$ +while the inner loop samples values of $\boldsymbol{X}_{\sim m}$ +(i.e., all input variables except $x_m$). +The cost of the analysis in terms of the model evaluations for estimating +the first-order index for _each input variable_ is $N^2$ where $N$ +is the Monte-Carlo sample size. +It is expected that size of $N$ is between $10^3$ and $10^6$. + +{prf:ref}`Brute Force MC First-Order` below illustrates +the procedure to compute main-effect Sobol' indices. + +```{prf:algorithm} Brute force MC for estimating all $S_m$ +:label: Brute Force MC First-Order + +**Inputs** A computational model $\mathcal{M}$, random input variables $\boldsymbol{X} = \{ X_1, \ldots, X_M \}$, number of MC sample points $N$ + +**Output** First-order Sobol' sensitivity indices $S_m$ for $m = 1, \ldots, M$ + +For $m = 1$ to $M$: + +1. $\Sigma_i \leftarrow 0$ +2. $\Sigma_i^2 \leftarrow 0$ +3. For $i = 1$ to $N$: + + 1. Sample $x_m^{(i)}$ from $X_m$ + 2. $\Sigma_j \leftarrow 0$ + 3. For $j = 1$ to $N$: + + 1. Sample $\boldsymbol{x}_{\sim m}^{(j)}$ from $\boldsymbol{X}_{\sim m}$ + 2. $\Sigma_j \leftarrow \Sigma_j + \mathcal{M}(x_m^{(j)}, \boldsymbol{x}_{\sim m}^{(j)})$ + + 4. $\mathbb{E}_{\boldsymbol{X}_{\sim m}}\left[ Y | X_m \right]^{(i)} \leftarrow \frac{1}{N} \Sigma_j$ + 5. $\Sigma_i \leftarrow \Sigma_i + \mathbb{E}_{\boldsymbol{X}_{\sim m}}\left[ Y | X_m \right]^{(i)}$ + 6. $\Sigma_{i^2} \leftarrow \Sigma_{i^2} + \left( \mathbb{E}_{\boldsymbol{X}_{\sim m}}\left[ Y | X_m \right]^{(i)} \right)^2$ +4. $\mathbb{V}_{X_m} \left[ \mathbb{E}_{\boldsymbol{X}_{\sim m}}\left[ Y | X_m \right]\right] \leftarrow \frac{1}{N} \Sigma_{i^2} - +\left( \frac{1}{N} \Sigma_{i} \right)^2$ +5. $S_m \leftarrow \frac{\mathbb{V}_{X_m} \left[ \mathbb{E}_{\boldsymbol{X}_{\sim m}}\left[ Y | X_m \right]\right]}{\mathbb{V}[Y]}$ +``` + +The output variance $\mathbb{V}[Y]$ used in the above algorithm can be computed following {prf:ref}`Brute Force Output Variance`. + +```{prf:algorithm} Brute force MC for estimating output variance $\mathbb{V}[Y]$ +:label: Brute Force Output Variance + +**Inputs** A computational model $\mathcal{M}$, random input variables $\boldsymbol{X}$, number of MC sample points $N$ + +**Output** Output variance $\mathbb{V}[Y]$ + +1. $\Sigma_i \leftarrow 0$ +2. $\Sigma_i^2 \leftarrow 0$ +3. For $i = 1$ to $N$: + + 1. Sample $\boldsymbol{x}^{(i)}$ from $\boldsymbol{X}$ + 2. $\Sigma_i \leftarrow \Sigma_i + \mathcal{M}(\boldsymbol{x}^{(i)})$ + 3. $\Sigma_{i^2} \leftarrow \Sigma_{i^2} + \left( \mathcal{M}(\boldsymbol{x}^{(i)} \right)^2$ + +4. $\mathbb{V}[Y] \leftarrow \frac{1}{N} \Sigma_{i^2} - \left( \frac{1}{N} \Sigma_i \right)^2$ +``` + +Similar Monte-Carlo algorithm can be devised to compute +the total-effect Sobol' indices +as shown in {prf:ref}`Brute Force MC Total-Effect`. + +```{prf:algorithm} Brute force MC for estimating all $ST_m$ +:label: Brute Force MC Total-Effect + +**Inputs** A computational model $\mathcal{M}$, random input variables $\boldsymbol{X} = \{ X_1, \ldots, X_M \}$, number of MC sample points $N$ + +**Output** First-order Sobol' sensitivity indices $S_m$ for $m = 1, \ldots, M$ + +For $m = 1$ to $M$: + +1. $\Sigma_i \leftarrow 0$ +2. $\Sigma_i^2 \leftarrow 0$ +3. For $i = 1$ to $N$: + + 1. Sample $\boldsymbol{x}_{\sim m}^{(i)}$ from $\boldsymbol{X}_{\sim m}$ + 2. $\Sigma_j \leftarrow 0$ + 3. For $j = 1$ to $N$: + + 1. Sample $x_{m}^{(j)}$ from $X_m$ + 2. $\Sigma_j \leftarrow \Sigma_j + \mathcal{M}(x_m^{(j)}, \boldsymbol{x}_{\sim m}^{(j)})$ + + 4. $\mathbb{E}_{X_m}\left[ Y | \boldsymbol{X}_{\sim m} \right]^{(i)} \leftarrow \frac{1}{N} \Sigma_j$ + 5. $\Sigma_i \leftarrow \Sigma_i + \mathbb{E}_{X_m}\left[ Y | \boldsymbol{X}_{\sim m} \right]^{(i)}$ + 6. $\Sigma_{i^2} \leftarrow \Sigma_{i^2} + \left( \mathbb{E}_{X_m}\left[ Y | \boldsymbol{X}_{\sim m} \right]^{(i)} \right)^2$ +4. $\mathbb{V}_{\boldsymbol{X}_{\sim m}} \left[ \mathbb{E}_{X_m}\left[ Y | \boldsymbol{X}_{\sim m} \right]\right] \leftarrow \frac{1}{N} \Sigma_{i^2} - +\left( \frac{1}{N} \Sigma_{i} \right)^2$ +5. $ST_m \leftarrow 1 - \frac{\mathbb{V}_{\boldsymbol{X}_{\sim m}} \left[ \mathbb{E}_{X_m}\left[ Y | \boldsymbol{X}_{\sim m} \right]\right]}{\mathbb{V}[Y]}$ +``` + +These algorithms are implemented in a Python function that returns +the main-effect and total-effect Sobol' indices +of all the input variables of a computational model. +The function assumes that a probabilistic input model of the computational +model has been defined such that sample points may be generated from them. + +```{note} +And indeed, test functions included in UQTestFuns are all given with the +corresponding probabilistic input model according to the literature. +``` + +```{code-cell} ipython3 +:tags: [hide-input] + +def estimate_sobol_indices(my_func, prob_input, num_sample): + """Estimate the first-order and total-effect Sobol' indices via MC. + + Parameters + ---------- + my_func + The function (or computational model) to analyze. + prob_input + The probabilistic input model of the function. + num_sample + The Monte-Carlo sample size. + + Returns + ------- + A tuple of NumPy array: first-order and total-effect Sobol' indices each + has a length of the number of input variables. + """ + + # --- Compute output variance + xx = prob_input.get_sample(num_sample) + yy = my_func(xx) + var_yy = np.var(yy) + + num_dim = prob_input.spatial_dimension + + # --- Compute first-order Sobol' indices + first_order = np.zeros(num_dim) + for m in range(num_dim): + xx_m = prob_input.marginals[m].get_sample(num_sample) + exp_nm = np.zeros(num_sample) + for i in range(num_sample): + xx = prob_input.get_sample(num_sample) + # Replace the m-th column + xx[:, m] = xx_m[i] + yy = my_func(xx) + exp_nm[i] = np.mean(yy) + var_m = np.var(exp_nm) + first_order[m] = var_m / var_yy + + # --- Compute total-effect Sobol' indices + total_effect = np.zeros(num_dim) + for m in range(num_dim): + xx = prob_input.get_sample(num_sample) + exp_m = np.zeros(num_sample) + for i in range(num_sample): + xx_m = np.repeat(xx[i:i+1], num_sample, axis=0) + xx_m[:, m] = prob_input.marginals[m].get_sample(num_sample) + yy = my_func(xx_m) + exp_m[i] = np.mean(yy) + var_nm = np.var(exp_m) + total_effect[m] = 1 - var_nm / var_yy + + return first_order, total_effect +``` + +## Ishigami function + +To test the implemented algorithm above, +we choose the popular Ishigami function {cite}`Ishigami1991` whose +analytical values for the sensitivity indices are known. +The function is highly non-linear and non-monotonous and given as follows: + +$$ +\mathcal{M}(\boldsymbol{x}) = \sin(x_1) + a \sin^2(x_2) + b \, x_3^4 \sin(x_1) +$$ +where $\boldsymbol{x} = \{ x_1, x_2, x_3 \}$ is the three-dimensional vector of +input variables further defined in the table below, +and $a$ and $b$ are parameters of the function. + +To create an instance of the Ishigami function: + +```{code-cell} ipython3 +ishigami = uqtf.Ishigami() +``` + +The input variables of the function are probabilistically defined according +to the table below. + +```{code-cell} ipython3 +ishigami.prob_input +``` + +Finally, the default values for the parameters $a$ and $b$ are: + +```{code-cell} ipython3 +ishigami.parameters +``` + +For reproducibility of this tutorial, set the seed number for the pseudo-random +generator attached to the probabilistic input model: + +```{code-cell} ipython3 +ishigami.prob_input.reset_rng(452397) +``` + +The variance of the Ishigami function can be analytically derived +and it is a function of the parameters: + +$$ +\mathbb{V}[Y] = \frac{a^2}{8} + \frac{b \pi^4}{5} + \frac{b^2 \pi^8}{18} + \frac{1}{2}. +$$ + +The analytical sensitivity indices are also available as shown in the table +below also as functions of the parameters. + +| Input variable | $S_m$ | $ST_m$ | +|:---------------:|:---------------------------------------------------------------:|:-----------------------------------------------------------------------------------------------------------:| +| $1$ | $\frac{1}{\mathbb{V}[Y]} \frac{1}{2} (1 + \frac{b \pi^4}{5})^2$ | $\frac{1}{\mathbb{V}[Y]} \left( \frac{1}{2} \, (1 + \frac{b \pi^4}{5})^2 + \frac{8 b^2 \pi^8}{225} \right)$ | +| $2$ | $\frac{1}{\mathbb{V}[Y]} \frac{a^2}{8}$ | $\frac{a^2}{8 \mathbb{V}[Y]}$ | +| $3$ | $0.0$ | $\frac{8 b^2 \pi^8}{225 \mathbb{V}[Y]}$ | + +Notice that while $X_3$ does not directly influence the output variance +by itself (it's main-effect index is zero), +it does influence the output variance via interaction with $X_1$. +Furthermore, $X_2$ has no interaction effect whatsoever +as its main-effect and total-effect indices are the same. + +For later comparison, we define a Python function that returns +the analytical values of the Sobol' indices of the Ishigami function. + +```{code-cell} ipython3 +:tags: [hide-input] + +def compute_sobol_indices(a, b): + """Compute the analytical Sobol' indices for the Ishigami function.""" + + # --- Compute the variance + var_y = a**2 / 8 + b * np.pi**4 / 5 + b**2 * np.pi**8 / 18 + 1 / 2 + + # --- Compute the first-order Sobol' indices + first_order = np.zeros(3) + first_order[0] = (1 + b * np.pi**4 / 5)**2 / 2 + first_order[1] = a**2 / 8 + first_order[2] = 0 + + # --- Compute the total-effect Sobol' indices + total_effect = np.zeros(3) + total_effect[2] = 8 * b**2 * np.pi**8 / 225 + total_effect[1] = first_order[1] + total_effect[0] = first_order[0] + total_effect[2] + + return first_order / var_y, total_effect / var_y +``` + +## Sobol' indices estimation + +To observe the convergence of the estimation procedure implemented above, +several Monte-Carlo sample sizes are used: + +```{code-cell} ipython3 +sample_sizes = np.arange(0, 3500, 500)[1:] +first_order_indices = np.zeros((len(sample_sizes), ishigami.spatial_dimension)) +total_effect_indices = np.zeros((len(sample_sizes), ishigami.spatial_dimension)) + +for i, sample_size in enumerate(sample_sizes): + first_order_indices[i, :], total_effect_indices[i, :] = estimate_sobol_indices( + ishigami, ishigami.prob_input, sample_size + ) +``` + +Compute the analytical values for the given parameters: + +```{code-cell} ipython3 +first_order_ref, total_effect_ref = compute_sobol_indices(*ishigami.parameters) +``` + +The estimated indices as a function of sample size is plotted below. + +```{code-cell} ipython3 +:tags: [hide-input] + +fig, axs = plt.subplots(1, 2, figsize=(10, 5)) + +axs[0].plot(sample_sizes, first_order_indices[:, 0], color="#e41a1c") +axs[0].plot(sample_sizes, first_order_indices[:, 1], color="#377eb8") +axs[0].plot(sample_sizes, first_order_indices[:, 2], color="#4daf4a") + +axs[0].axhline(first_order_ref[0], linestyle="--", color="#e41a1c") +axs[0].axhline(first_order_ref[1], linestyle="--", color="#377eb8") +axs[0].axhline(first_order_ref[2], linestyle="--", color="#4daf4a") + +axs[0].set_xscale("log") +axs[0].set_ylim([-0.1, 1]) +axs[0].set_title("First-order Sobol' indices") +axs[0].set_xlabel("MC sample size", fontsize=14) +axs[0].set_ylabel("Sensivity index", fontsize=14) + +axs[1].plot(sample_sizes, total_effect_indices[:, 0], color="#e41a1c", label=r"$x_1$") +axs[1].plot(sample_sizes, total_effect_indices[:, 1], color="#377eb8", label=r"$x_2$") +axs[1].plot(sample_sizes, total_effect_indices[:, 2], color="#4daf4a", label=r"$x_3$") + +axs[1].axhline(total_effect_ref[0], linestyle="--", color="#e41a1c") +axs[1].axhline(total_effect_ref[1], linestyle="--", color="#377eb8") +axs[1].axhline(total_effect_ref[2], linestyle="--", color="#4daf4a") + +axs[1].set_xscale("log") +axs[1].set_ylim([-0.1, 1]) +axs[1].set_title("Total-effect Sobol' indices") +axs[1].set_xlabel("MC sample size", fontsize=14) +axs[1].legend() + +fig.tight_layout(pad=3.0) +plt.gcf().set_dpi(150); +``` + +The implemented method seems to estimate the indices relatively well; +increasing the Monte-Carlo sample size would improve the accuracy of +the estimates but with a high computational cost. + +The challenge for a sensitivity analysis method is to ascertain the importance +(or non-importance) of input variables either qualitatively or quantitatively +with as few computational model evaluations as possible. + +## References + +```{bibliography} +:style: unsrtalpha +:filter: docname in docnames +``` diff --git a/docs/references.bib b/docs/references.bib index e99476c..1a269b7 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -684,4 +684,68 @@ @TechReport{Webster1996 url = {http://globalchange.mit.edu/publication/15670}, } +@Article{Saltelli2002, + author = {Andrea Saltelli}, + journal = {Computer Physics Communications}, + title = {Making best use of model evaluations to compute sensitivity indices}, + year = {2002}, + number = {2}, + pages = {280--297}, + volume = {145}, + doi = {10.1016/s0010-4655(02)00280-1}, +} + +@Article{Li2001, + author = {Genyuan Li and Carey Rosenthal and Herschel Rabitz}, + journal = {The Journal of Physical Chemistry A}, + title = {High dimensional model representations}, + year = {2001}, + number = {33}, + pages = {7765--7777}, + volume = {105}, + doi = {10.1021/jp010450t}, +} + +@Article{Hoeffding1948, + author = {Wassily Hoeffding}, + journal = {The Annals of Mathematical Statistics}, + title = {A class of statistics with asymptotically normal distribution}, + year = {1948}, + number = {3}, + pages = {293--325}, + volume = {19}, + doi = {10.1214/aoms/1177730196}, +} + +@Article{Homma1996, + author = {Toshimitsu Homma and Andrea Saltelli}, + journal = {Reliability Engineering {\&} System Safety}, + title = {Importance measures in global sensitivity analysis of nonlinear models10.1007/978-3-319-11259-6_24-1}, + year = {1996}, + number = {1}, + pages = {1--17}, + volume = {52}, + doi = {10.1016/0951-8320(96)00002-6}, +} + +@Article{Sobol1993, + author = {Sobol', Ilya M.}, + journal = {Mathematical Modelling and Computational Experiments}, + title = {Sensitivity estimates for nonlinear mathematical models}, + year = {1993}, + number = {4}, + pages = {407--414}, + volume = {1}, +} + +@InCollection{Beck2015, + author = {James L. Beck and Konstantin M. Zuev}, + booktitle = {Handbook of Uncertainty Quantification}, + publisher = {Springer International Publishing}, + title = {Rare-event simulation}, + year = {2015}, + pages = {1--26}, + doi = {10.1007/978-3-319-11259-6_24-1}, +} + @Comment{jabref-meta: databaseType:bibtex;} diff --git a/setup.cfg b/setup.cfg index 1bad8cf..31d67a6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -54,6 +54,7 @@ docs = jupyter-book==0.13.2 sphinx==4.5.0 matplotlib>=3.7.0 + sphinx-proof>=0.1.3 all = uqtestfuns[dev] uqtestfuns[docs] From e75d568766cb2e005f5eccae0d9f78c850d77a6a Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Thu, 24 Aug 2023 10:38:11 +0200 Subject: [PATCH 64/73] Reorganize tutorials in the docs. - Previous getting started guides are now part of the tutorials. - Add a reference section in "About UQ Test Functions" and "Tutorial: Create Built-in Test Functions". - Fix Issue #296: the keyword `spatial_dimension` is mandatory to create a test function with a specified dimension. --- docs/_toc.yml | 14 ++++-- .../about-uq-test-functions.md | 2 + ...t-in.md => tutorial-built-in-functions.md} | 37 +++++++++------ ...custom.md => tutorial-custom-functions.md} | 46 +++++++++++-------- docs/getting-started/tutorial-reliability.md | 2 +- docs/getting-started/tutorial-sensitivity.md | 6 ++- docs/getting-started/tutorials.md | 11 +++++ docs/index.md | 13 ++---- 8 files changed, 82 insertions(+), 49 deletions(-) rename docs/getting-started/{creating-a-built-in.md => tutorial-built-in-functions.md} (90%) rename docs/getting-started/{creating-a-custom.md => tutorial-custom-functions.md} (91%) create mode 100644 docs/getting-started/tutorials.md diff --git a/docs/_toc.yml b/docs/_toc.yml index 9c08532..e698966 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -7,10 +7,16 @@ parts: - caption: Getting Started chapters: - file: getting-started/obtaining-and-installing - - file: getting-started/creating-a-built-in - - file: getting-started/tutorial-sensitivity - - file: getting-started/tutorial-reliability - - file: getting-started/creating-a-custom + - file: getting-started/tutorials + sections: + - file: getting-started/tutorial-built-in-functions + title: Built-in Test Functions + - file: getting-started/tutorial-sensitivity + title: Sensitivity Analysis + - file: getting-started/tutorial-reliability + title: Reliability Analysis + - file: getting-started/tutorial-custom-functions + title: Custom Test Functions - file: getting-started/about-uq-test-functions - caption: Test Functions chapters: diff --git a/docs/getting-started/about-uq-test-functions.md b/docs/getting-started/about-uq-test-functions.md index 175bc7c..be23b87 100644 --- a/docs/getting-started/about-uq-test-functions.md +++ b/docs/getting-started/about-uq-test-functions.md @@ -215,6 +215,8 @@ Specifically, none of them provides: Satisfying all the above requirements is exactly the goal of the UQTestFuns package. +## References + ```{bibliography} :style: unsrtalpha :filter: docname in docnames diff --git a/docs/getting-started/creating-a-built-in.md b/docs/getting-started/tutorial-built-in-functions.md similarity index 90% rename from docs/getting-started/creating-a-built-in.md rename to docs/getting-started/tutorial-built-in-functions.md index 2ebf150..b534acf 100644 --- a/docs/getting-started/creating-a-built-in.md +++ b/docs/getting-started/tutorial-built-in-functions.md @@ -12,10 +12,18 @@ kernelspec: name: python3 --- -(getting-started:creating-a-built-in)= -# Creating a Built-in Test Function +(getting-started:tutorial-built-in-functions)= +# Tutorial: Create Built-in Test Functions -_Built-in test functions_ are test functions that are delivered with UQTestFuns. +UQTestFuns includes a wide range of test functions from the uncertainty +quantification community; these functions are referred to +as the _built-in test functions_. +This tutorial provides you with an overview of the package; +you'll learn about the built-in test functions, their common interfaces, +as well as their important properties and methods. + +By the end of this tutorial, you'll be able to create any test function +available in UQTestFuns and access its basic but important functionalities. UQTestFuns is designed to work with minimal dependency within the numerical Python ecosystem. @@ -43,8 +51,8 @@ as well as a short description. ## A Callable instance -Take, for instance, the {ref}`borehole ` function, -an eight-dimensional test function typically used +Take, for instance, the {ref}`borehole ` function +{cite}`Harper1983`, an eight-dimensional test function typically used in the context of metamodeling and sensitivity analysis. To instantiate a borehole test function, call the constructor as follows: @@ -228,7 +236,7 @@ In principle, these parameter values can be anything: numerical values, flags, selection using strings, etc. For instance, consider the {ref}`Ishigami ` function -defined as follows: +{cite}`Ishigami1991` defined as follows: $$ \mathcal{M}(\boldsymbol{x}) = \sin{(x_1)} + a \sin^2{(x_2)} + b x_3^4 \sin{(x_1)} @@ -289,7 +297,8 @@ Some test functions support a _variable dimension_, meaning that an instance of a test function can be constructed for any number (positive integer, please) of spatial dimension. -Consider, for instance, the {ref}`Sobol'-G ` function, +Consider, for instance, the {ref}`Sobol'-G ` function +{cite}`Saltelli1995`, a test function whose dimension can be varied and a popular choice in the context of sensitivity analysis. It is defined as follows: @@ -302,11 +311,10 @@ of input variables, and $\boldsymbol{a} = \{ a_1, \ldots, a_M \}$ are parameters of the function. To create a six-dimensional Sobol'-G function, -use the parameter `spatial_dimension` -(or the first positional parameter) to specify the desired dimensionality: +use the parameter `spatial_dimension` to specify the desired dimensionality: ```{code-cell} ipython3 -my_testfun = uqtf.SobolG(spatial_dimension=6) # Alternatively, uqtf.SobolG(6) +my_testfun = uqtf.SobolG(spatial_dimension=6) ``` Verify that the function is indeed a six-dimension one: @@ -321,8 +329,9 @@ and: print(my_testfun.prob_input) ``` -```{note} -Only test functions that support variable dimensions can accept `spatial_dimension` -argument. Such functions are indicated with `M` as its spatial dimension -in the list produced by `uqtf.list_functions()`. +## References + +```{bibliography} +:style: unsrtalpha +:filter: docname in docnames ``` diff --git a/docs/getting-started/creating-a-custom.md b/docs/getting-started/tutorial-custom-functions.md similarity index 91% rename from docs/getting-started/creating-a-custom.md rename to docs/getting-started/tutorial-custom-functions.md index f768667..45e8ab9 100644 --- a/docs/getting-started/creating-a-custom.md +++ b/docs/getting-started/tutorial-custom-functions.md @@ -13,22 +13,12 @@ kernelspec: name: python3 --- -(getting-started:creating-a-custom)= -# Creating a Custom Test Function +(getting-started:tutorial-custom-functions)= +# Tutorial: Create a Custom Test Function -In essence, an uncertainty quantification (UQ) test function -contains the following components: - -- _an evaluation function_ that takes inputs and produces outputs; - we can think of such functions as a blackbox -- _a probabilistic input model_ that specifies the input to - the blackbox function as a joint random variable; - the results of a UQ analysis depend on this specification -- (optionally) _a set of parameters_ that completes - the test function specification; these parameters once set are kept during - the function evaluation (for example, flag, numerical tolerance, - and time-step size). - +You may add an uncertainty quantification (UQ) test functions to UQTestFuns +such that they share the same interface like +the {ref}`built-in ones `. There are two ways to add a new test function to UQTestFuns: 1. _Interactively_ within a given Python session; @@ -37,15 +27,35 @@ There are two ways to add a new test function to UQTestFuns: within the UQTestFuns package; the test function will available after importing the package. -This section will give a walkthrough on how to add a function interactively; -to add a new test function as a Python module, -please refer to the relevant section in the Developer's Guide. +In this tutorial, you'll learn how to add a test function interactively. +If you want to add a new test function as a Python module, +please refer to the {ref}`relevant section ` +in the Developer's Guide. ```{code-cell} ipython3 import numpy as np import uqtestfuns as uqtf ``` +## Uncertainty quantification test functions revisited + +In essence, an uncertainty quantification (UQ) test function +contains the following components: + +- _an evaluation function_ that takes inputs and produces outputs; + we can think of such functions as a blackbox +- _a probabilistic input model_ that specifies the input to + the blackbox function as a joint random variable; + the results of a UQ analysis depend on this specification +- (optionally) _a set of parameters_ that completes + the test function specification; these parameters once set are kept during + the function evaluation (for example, flag, numerical tolerance, + and time-step size). + +When defining a new generic UQ test function, it's a good idea to think about +the candidate test function in those three terms and be ready with their +specifications. + ## Branin function Suppose we want to add the two-dimensional Branin (or Branin-Hoo) function diff --git a/docs/getting-started/tutorial-reliability.md b/docs/getting-started/tutorial-reliability.md index dd8eeb6..50d8091 100644 --- a/docs/getting-started/tutorial-reliability.md +++ b/docs/getting-started/tutorial-reliability.md @@ -13,7 +13,7 @@ kernelspec: --- (getting-started:tutorial-reliability)= -# Tutorial: Reliability Analysis +# Tutorial: Test a Reliability Analysis Method UQTestFuns includes several test functions from the literature employed in a reliability analysis exercise. diff --git a/docs/getting-started/tutorial-sensitivity.md b/docs/getting-started/tutorial-sensitivity.md index 9375387..f062922 100644 --- a/docs/getting-started/tutorial-sensitivity.md +++ b/docs/getting-started/tutorial-sensitivity.md @@ -13,7 +13,7 @@ kernelspec: --- (getting-started:tutorial-sensitivity)= -# Tutorial: Sensitivity Analysis +# Tutorial: Test a Sensitivity Analysis Method UQTestFuns includes a wide range of test functions from the literature employed in a sensitivity analysis exercise. @@ -403,7 +403,9 @@ Compute the analytical values for the given parameters: first_order_ref, total_effect_ref = compute_sobol_indices(*ishigami.parameters) ``` -The estimated indices as a function of sample size is plotted below. +The estimated indices as a function of sample size is plotted below; +the estimated values are in solid lines while the analytical values are in +dashed lines. ```{code-cell} ipython3 :tags: [hide-input] diff --git a/docs/getting-started/tutorials.md b/docs/getting-started/tutorials.md new file mode 100644 index 0000000..05774db --- /dev/null +++ b/docs/getting-started/tutorials.md @@ -0,0 +1,11 @@ +(getting-started:tutorials)= +# UQTestFuns Tutorials + +These tutorials are recommended as an introduction to UQTestFuns. + +| If you want to... | Go to... | +|------------------------------------------------------------------------|-------------------------------------------------------------------------------------| +| get a quick overview of UQTestFuns and its basic usage | {ref}`Create Built-in Test Functions ` | +| learn how to use test functions in the context of sensitivity analysis | {ref}`Test a Sensitivity Analysis Method ` | +| learn how to use test functions in the context of reliability analysis | {ref}`Test a Reliability Analysis Method ` | +| create a custom test function on the fly | {ref}`Create a Custom Function ` | diff --git a/docs/index.md b/docs/index.md index 3d7725a..f8f93b1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -17,22 +17,15 @@ Specifically, the package provides: :::{grid-item-card} Getting Started :text-align: center New to, but ready to use, UQTestFuns? -You can check out the guide on how to create a built-in test function -or a custom one. +You can check out the tutorials! Need some background info first? Check out the what & why of these test functions. +++ -```{button-ref} getting-started:creating-a-built-in +```{button-ref} getting-started:tutorials :ref-type: myst :color: primary :outline: -How to create a built-in test function -``` -```{button-ref} getting-started:creating-a-custom -:ref-type: myst -:color: primary -:outline: -How to create a custom test function +To the UQTestFuns Tutorials ``` ```{button-ref} getting-started:about-uq-test-functions :ref-type: myst From b406782030e4bb6e4659356bb75adfef670d65c4 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Thu, 24 Aug 2023 12:05:57 +0200 Subject: [PATCH 65/73] Remove JOSS paper from the `dev` branch. - Because the submission is still under review. --- joss/paper.bib | 257 ------------------------------ joss/paper.md | 362 ------------------------------------------ joss/reliability.png | Bin 480951 -> 0 bytes joss/uq-framework.png | Bin 24594 -> 0 bytes 4 files changed, 619 deletions(-) delete mode 100644 joss/paper.bib delete mode 100644 joss/paper.md delete mode 100644 joss/reliability.png delete mode 100644 joss/uq-framework.png diff --git a/joss/paper.bib b/joss/paper.bib deleted file mode 100644 index ad74579..0000000 --- a/joss/paper.bib +++ /dev/null @@ -1,257 +0,0 @@ -@misc{VLSE:2013, - author = {Surjanovic, S. and Bingham, D.}, - title = {Virtual Library of Simulation Experiments: Test Functions and Datasets}, - year = 2013, - howpublished = {Retrieved June 30, 2023, from \url{http://www.sfu.ca/~ssurjano}} -} - -@Misc{GdR:2008, - author = {{GdR MASCOT-NUM}}, - howpublished = {Retrieved June 30, 2023, from \url{https://www.gdr-mascotnum.fr/benchmarks.html}}, - title = {Benchmark proposals of {GdR}}, - year = {2008}, -} - -@misc{UQWorld:2019, - author = {UQWorld}, - title = {Benchmark page of UQWorld, the applied uncertainty quantification community}, - year = 2019, - howpublished = {Retrieved June 30, 2023, from \url{https://uqworld.org/c/uq-with-uqlab/benchmarks}} -} - -@misc{Rozsas:2019, - author = {Árpád Rózsás and Arthur Slobbe}, - title = {Repository and Black-box Reliability Challenge 2019}, - year = 2019, - howpublished = {Retrieved June 30, 2023, from \url{https://gitlab.com/rozsasarpi/rprepo/}} -} - -@InProceedings{Fekhari2021, - author = {Elias Fekhari and Michaël Baudin and Vincent Chabridon and Youssef Jebroun}, - booktitle = {4th International Conference on Uncertainty Quantification in Computational Sciences and Engineering}, - title = {{otbenchmark}: An open source {Python} package for benchmarking and validating uncertainty quantification algorithms}, - year = {2021}, - doi = {10.7712/120221.8034.19093}, - location = {Athens, Greece}, -} - -@Misc{Jakeman2019, - author = {John D. Jakeman}, - title = {{PyApprox}}, - year = {2019}, - journal = {GitHub repository}, - publisher = {GitHub}, - url = {https://github.com/sandialabs/pyapprox}, -} - -@Misc{Baudin2021, - author = {Michaël Baudin and Youssef Jebroun and Elias Fekhari and Vincent Chabridon}, - title = {{otbenchmark}}, - year = {2021}, - journal = {GitHub repository}, - publisher = {GitHub}, - url = {https://github.com/mbaudin47/otbenchmark}, -} - -@Misc{Bouhlel2023, - author = {Mohamed Amine Bouhlel and John Hwang and Nathalie Bartoli and Rémi Lafage and Joseph Morlier and Joaquim Martins}, - title = {{Surrogate Modeling Toolbox}}, - year = {2023}, - journal = {GitHub repository}, - publisher = {GitHub}, - url = {https://github.com/SMTorg/smt}, -} - -@InCollection{Baudin2017, - author = {Michaël Baudin and Anne Dutfoy and Bertrand Iooss and Anne-Laure Popelin}, - booktitle = {Handbook of Uncertainty Quantification}, - publisher = {Springer International Publishing}, - title = {{OpenTURNS}: An industrial software for uncertainty quantification in simulation}, - year = {2017}, - pages = {2001--2038}, - doi = {10.1007/978-3-319-12385-1_64}, -} - -@InProceedings{Sudret2017, - author = {Bruno Sudret and Stefano Marelli and Joe Wiart}, - booktitle = {2017 11th European Conference on Antennas and Propagation ({EUCAP})}, - title = {Surrogate models for uncertainty quantification: An overview}, - year = {2017}, - publisher = {{IEEE}}, - doi = {10.23919/eucap.2017.7928679}, -} - -@InProceedings{Sudret2012, - author = {Bruno Sudret}, - booktitle = {Proceedings of the 5th Asian-Pacific Symposium on Structural Reliability and its Applications}, - title = {Meta-models for structural reliability and uncertainty quantification}, - year = {2012}, - doi = {10.3850/978-981-07-2219-7_p321}, -} - -@InCollection{Iooss2015, - author = {Bertrand Iooss and Paul Lema{\^{\i}}tre}, - booktitle = {Uncertainty Management in Simulation-Optimization of Complex Systems}, - publisher = {Springer {US}}, - title = {A review on global sensitivity analysis methods}, - year = {2015}, - pages = {101--122}, - doi = {10.1007/978-1-4899-7547-8_5}, -} - -@PhdThesis{Sudret2007, - author = {Bruno Sudret}, - school = {Université Blaise Pascal - Clermont, France}, - title = {Uncertainty propagation and sensitivity analysis in mechanical models \textemdash {Contributions} to structural reliability and stochastic spectral methods.}, - year = {2007}, - address = {Clermont-Ferrand, France}, - type = {habilitation thesis}, - abstract = {This thesis is a contribution to the resolution of the reliability-based design optimization problem. This probabilistic design approach is aimed at considering the uncertainty attached to the system of interest in order to provide optimal and safe solutions. The safety level is quantified in the form of a probability of failure. Then, the optimization problem consists in ensuring that this failure probability remains less than a threshold specified by the stakeholders. The resolution of this problem requires a high number of calls to the limit-state design function underlying the reliability analysis. Hence it becomes cumbersome when the limit-state function involves an expensive-to-evaluate numerical model (e.g. a finite element model). In this context, this manuscript proposes a surrogate-based strategy where the limit-state function is progressively replaced by a Kriging meta-model. A special interest has been given to quantifying, reducing and eventually eliminating the error introduced by the use of this meta-model instead of the original model. The proposed methodology is applied to the design of geometrically imperfect shells prone to buckling.}, -} - -@InCollection{Verma2015, - author = {Ajit Kumar Verma and Srividya Ajit and Durga Rao Karanki}, - booktitle = {Springer Series in Reliability Engineering}, - publisher = {Springer London}, - title = {Structural reliability}, - year = {2015}, - pages = {257--292}, - doi = {10.1007/978-1-4471-6269-8_8}, -} - -@Article{Li2018, - author = {Xu Li and Chunlin Gong and Liangxian Gu and Wenkun Gao and Zhao Jing and Hua Su}, - journal = {Structural Safety}, - title = {A sequential surrogate method for reliability analysis based on radial basis function}, - year = {2018}, - pages = {42--53}, - volume = {73}, - doi = {10.1016/j.strusafe.2018.02.005}, -} - -@TechReport{Harper1983, - author = {Harper, William V. and Gupta, Sumant K.}, - title = {Sensitivity/uncertainty analysis of a borehole scenario comparing latin hypercube sampling and deterministic sensitivity approaches}, - year = {1983}, - address = {Columbus, Ohio}, - number = {{BMI}/{ONWI}-516}, - school = {Office of Nuclear Waste Isolation, Battelle Memorial Institute}, - url = {https://inldigitallibrary.inl.gov/PRR/84393.pdf}, -} - -@Book{Saltelli2007, - author = {Andrea Saltelli and Marco Ratto and Terry Andres and Francesca Campolongo and Jessica Cariboni and Debora Gatelli and Michaela Saisana and Stefano Tarantola}, - publisher = {Wiley}, - title = {Global sensitivity analysis. The primer}, - year = {2007}, - doi = {10.1002/9780470725184}, -} - -@Article{Bouhlel2019, - author = {Bouhlel, Mohamed Amine and Hwang, John T. and Bartoli, Nathalie and Lafage, R{\'{e}}mi and Morlier, Joseph and Martins, Joaquim R. R. A.}, - journal = {Advances in Engineering Software}, - title = {A {Python} surrogate modeling framework with derivatives}, - year = {2019}, - pages = {102662}, - volume = {135}, - doi = {10.1016/j.advengsoft.2019.03.005}, - publisher = {Elsevier {BV}}, -} - -@Article{Saltelli1995, - author = {Saltelli, Andrea and Sobol', Ilya M.}, - journal = {Reliability Engineering \& System Safety}, - title = {About the use of rank transformation in sensitivity analysis of model output}, - year = {1995}, - number = {3}, - pages = {225--239}, - volume = {50}, - doi = {10.1016/0951-8320(95)00099-2}, -} - -@Article{Herman2017, - author = {Jon Herman and Will Usher}, - journal = {The Journal of Open Source Software}, - title = {{SALib}: An open-source python library for sensitivity analysis}, - year = {2017}, - number = {9}, - pages = {97}, - volume = {2}, - doi = {10.21105/joss.00097}, -} - -@Article{Iwanaga2022, - author = {Takuya Iwanaga and William Usher and Jonathan Herman}, - journal = {Socio-Environmental Systems Modelling}, - title = {Toward {SALib} 2.0: Advancing the accessibility and interpretability of global sensitivity analyses}, - year = {2022}, - pages = {18155}, - volume = {4}, - doi = {10.18174/sesmo.18155}, -} - -@Article{Eck2015, - author = {Vinzenz Gregor Eck and Wouter Paulus Donders and Jacob Sturdy and Jonathan Feinberg and Tammo Delhaas and Leif Rune Hellevik and Wouter Huberts}, - journal = {International Journal for Numerical Methods in Biomedical Engineering}, - title = {A guide to uncertainty quantification and sensitivity analysis for cardiovascular applications}, - year = {2015}, - number = {8}, - volume = {32}, - doi = {10.1002/cnm.2755}, -} - -@Article{Wicaksono2016, - author = {Damar Wicaksono and Omar Zerkak and Andreas Pautz}, - journal = {Nuclear Science and Engineering}, - title = {Global sensitivity analysis of transient code output applied to a reflood experiment model using the {TRACE} code}, - year = {2016}, - number = {3}, - pages = {400--429}, - volume = {184}, - doi = {10.13182/nse16-37}, -} - -@Article{Adelmann2019, - author = {Andreas Adelmann}, - journal = {{SIAM}/{ASA} Journal on Uncertainty Quantification}, - title = {On nonintrusive uncertainty quantification and surrogate model construction in particle accelerator modeling}, - year = {2019}, - number = {2}, - pages = {383--416}, - volume = {7}, - doi = {10.1137/16m1061928}, -} - -@Article{Castellon2023, - author = {Dario Fernandez Castellon and Aksel Fenerci and {\O}yvind Wiig Petersen and Ole {\O}iseth}, - journal = {Reliability Engineering {\&}amp$\mathsemicolon$ System Safety}, - title = {Full long-term buffeting analysis of suspension bridges using {Gaussian} process surrogate modelling and importance sampling {Monte Carlo} simulations}, - year = {2023}, - volume = {235}, - doi = {10.1016/j.ress.2023.109211}, -} - -@Article{Virtanen2020, - author = {Pauli Virtanen and Ralf Gommers and Travis E. Oliphant and Matt Haberland and Tyler Reddy and David Cournapeau and Evgeni Burovski and Pearu Peterson and Warren Weckesser and Jonathan Bright and St{\'{e}}fan J. van der Walt and Matthew Brett and Joshua Wilson and K. Jarrod Millman and Nikolay Mayorov and Andrew R. J. Nelson and Eric Jones and Robert Kern and Eric Larson and C J Carey and {\.{I}}lhan Polat and Yu Feng and Eric W. Moore and Jake VanderPlas and Denis Laxalde and Josef Perktold and Robert Cimrman and Ian Henriksen and E. A. Quintero and Charles R. Harris and Anne M. Archibald and Ant{\^{o}}nio H. Ribeiro and Fabian Pedregosa and Paul van Mulbregt and Aditya Vijaykumar and Alessandro Pietro Bardelli and Alex Rothberg and Andreas Hilboll and Andreas Kloeckner and Anthony Scopatz and Antony Lee and Ariel Rokem and C. Nathan Woods and Chad Fulton and Charles Masson and Christian Häggström and Clark Fitzgerald and David A. Nicholson and David R. Hagen and Dmitrii V. Pasechnik and Emanuele Olivetti and Eric Martin and Eric Wieser and Fabrice Silva and Felix Lenders and Florian Wilhelm and G. Young and Gavin A. Price and Gert-Ludwig Ingold and Gregory E. Allen and Gregory R. Lee and Herv{\'{e}} Audren and Irvin Probst and Jörg P. Dietrich and Jacob Silterra and James T Webber and Janko Slavi{\v{c}} and Joel Nothman and Johannes Buchner and Johannes Kulick and Johannes L. Schönberger and Jos{\'{e}} Vin{\'{\i}}cius de Miranda Cardoso and Joscha Reimer and Joseph Harrington and Juan Luis Cano Rodr{\'{\i}}guez and Juan Nunez-Iglesias and Justin Kuczynski and Kevin Tritz and Martin Thoma and Matthew Newville and Matthias Kümmerer and Maximilian Bolingbroke and Michael Tartre and Mikhail Pak and Nathaniel J. Smith and Nikolai Nowaczyk and Nikolay Shebanov and Oleksandr Pavlyk and Per A. Brodtkorb and Perry Lee and Robert T. McGibbon and Roman Feldbauer and Sam Lewis and Sam Tygier and Scott Sievert and Sebastiano Vigna and Stefan Peterson and Surhud More and Tadeusz Pudlik and Takuya Oshima and Thomas J. Pingel and Thomas P. Robitaille and Thomas Spura and Thouis R. Jones and Tim Cera and Tim Leslie and Tiziano Zito and Tom Krauss and Utkarsh Upadhyay and Yaroslav O. Halchenko and Yoshiki V{\'{a}}zquez-Baeza and}, - journal = {Nature Methods}, - title = {{SciPy} 1.0: fundamental algorithms for scientific computing in {Python}}, - year = {2020}, - number = {3}, - pages = {261--272}, - volume = {17}, - doi = {10.1038/s41592-019-0686-2}, - publisher = {Springer Science and Business Media {LLC}}, -} - -@Article{Harris2020, - author = {Charles R. Harris and K. Jarrod Millman and St{\'{e}}fan J. van der Walt and Ralf Gommers and Pauli Virtanen and David Cournapeau and Eric Wieser and Julian Taylor and Sebastian Berg and Nathaniel J. Smith and Robert Kern and Matti Picus and Stephan Hoyer and Marten H. van Kerkwijk and Matthew Brett and Allan Haldane and Jaime Fern{\'{a}}ndez del R{\'{\i}}o and Mark Wiebe and Pearu Peterson and Pierre G{\'{e}}rard-Marchant and Kevin Sheppard and Tyler Reddy and Warren Weckesser and Hameer Abbasi and Christoph Gohlke and Travis E. Oliphant}, - journal = {Nature}, - title = {Array programming with {NumPy}}, - year = {2020}, - number = {7825}, - pages = {357--362}, - volume = {585}, - doi = {10.1038/s41586-020-2649-2}, -} - -@Comment{jabref-meta: databaseType:bibtex;} diff --git a/joss/paper.md b/joss/paper.md deleted file mode 100644 index 27469b9..0000000 --- a/joss/paper.md +++ /dev/null @@ -1,362 +0,0 @@ ---- -title: 'UQTestFuns: A Python3 library of uncertainty quantification (UQ) test functions' -tags: - - Python - - test functions - - benchmark - - uncertainty quantification - - metamodeling - - surrogate modeling - - sensitivity analysis - - reliability analysis - - rare event estimation -authors: - - name: Damar Wicaksono - orcid: 0000-0001-8587-7730 - affiliation: 1 # (Multiple affiliations must be quoted) - corresponding: true - - name: Michael Hecht - orcid: 0000-0001-9214-8253 - affiliation: 1 -affiliations: - - name: Center for Advanced Systems Understanding (CASUS) - Helmholtz-Zentrum Dresden-Rossendorf (HZDR), Germany - index: 1 -date: 30 June 2023 -bibliography: paper.bib ---- - -# Summary - -Researchers are continuously developing novel methods and algorithms -in the field of applied uncertainty quantification (UQ). -During the development phase of a novel method or algorithm, -researchers and developers often rely on test functions -taken from the literature for validation purposes. -Afterward, they employ these test functions as a common ground -to compare the performance of the novel method -against that of the state-of-the-art methods -in terms of accuracy and efficiency measures. - -`UQTestFuns` is an open-source Python3 library of test functions -commonly used within the applied UQ community. -Specifically, the package provides: - -- an **implementation with minimal dependencies** - (i.e., NumPy and SciPy) **and a common interface** of many test functions - available in the UQ literature -- a **single entry point** collecting test functions _and_ - their probabilistic input specifications in a single Python package -- an **opportunity for an open-source contribution**, supporting - the implementation of new test functions and posting reference results. - -`UQTestFuns` aims to save the researchers' and developers' time -from having to reimplement many of the commonly used test functions -themselves. - -# Statement of need - -The field of uncertainty quantification (UQ) in applied science and engineering -has grown rapidly in recent years. -Novel methods and algorithms for metamodeling (surrogate modeling), -reliability, and sensitivity analysis are being continuously developed. -While such methods are aimed at addressing real-world problems, -often involving large-scale complex computer models—from nuclear -[@Wicaksono2016] to civil engineering [@Castellon2023], -from physics [@Adelmann2019] to biomedicine [@Eck2015]—researchers -and developers may prefer to use the so-called UQ test functions -for validation and benchmarking purposes. - -UQ test functions are mathematical functions taken as black boxes; -they take a set of input values and produce output values. -In a typical UQ analysis, the input variables are considered _uncertain_ -and thus modeled probabilistically. -The results of a UQ analysis, in general, depend not only on the computational -model under consideration but also on the specification of the input variables. -Consequently, a UQ test function consists of both the specification -of the function as well as probabilistic distribution specification -of the inputs. - -UQ test functions are widely used in the community for several reasons: - -- Test functions are _fast to evaluate_, - at least _faster_ than their real-world counterparts. -- There are many test functions _available in the literature_ - for various types of analyses. -- Although test functions are taken as black boxes, _their features are known_; - this knowledge enables a thorough diagnosis of a UQ method. -- Test functions provide _a common ground_ for comparing the performance - of various UQ methods in solving the same class of problems. - -Several efforts have been made to provide relevant UQ test functions -to the community. -For instance, researchers may refer to the following online resources -to obtain UQ test functions (the list is by no means exhaustive): - -- The Virtual Library of Simulation Experiments (VLSE) [@VLSE:2013]: This site - is arguably the definitive repository for (but not exclusively) UQ test - functions. It provides over a hundred test functions - for numerous applications; each test function is described - on a dedicated page that includes implementations in MATLAB and R. -- The Benchmark proposals of GdR [@GdR:2008]: The site provides a series of - documents that contain test function specifications. -- The Benchmark page of UQWorld [@UQWorld:2019]: This community site provides - a selection of test functions for metamodeling, sensitivity analysis, - and reliability analysis exercises along with their implementation in MATLAB. -- RPrepo—a reliability problems repository [@Rozsas:2019]: This - repository contains numerous reliability analysis test functions implemented - in Python. It is not, however, a stand-alone Python package. - -Using these online resources, one either needs to download each test function -separately[^rprepo] or implement the functions following -the provided formula (in the programming language of choice). - -As an alternative way for obtaining test functions, -UQ analysis packages are often shipped with a selection of test functions -of their own, either for illustration, validation, or benchmarking purposes. -Examples from the applied UQ community in the Python ecosystem are -(the numbers are as of 2023-06-30; once again, the list is non-exhaustive): - -- SALib [@Herman2017; @Iwanaga2022]: Six test functions - mainly for illustrating the package capabilities in the examples. -- PyApprox [@Jakeman2019]: 18 test functions, - including some non-algebraic functions for benchmarking purposes. -- Surrogate Modeling Toolbox (SMT) [@Bouhlel2019; @Bouhlel2023]: - 11 analytical and engineering problems for benchmarking purposes. -- OpenTURNS [@Baudin2017]: 37 test functions packaged separately - as `otbenchmark` [@Fekhari2021; @Baudin2021] for benchmarking purposes. - -These open-source packages already provide a wide variety of functions -implemented in Python. Except for `otbenchmark`, the problem is that -these functions are part of the respective package. To get access to -the test functions belonging to a package, the whole analysis package -must be installed first. Furthermore, test functions from a given package are -often implemented in such a way that is tightly coupled with the package -itself. To use or extend the test functions belonging to an analysis package, -one may need to first learn some basic usage and specific terminologies -of the package. - -`UQTestFuns` aims to solve this problem by collecting UQ test functions -into a single Python package -with a few dependencies (i.e., NumPy [@Harris2020] -and SciPy [@Virtanen2020]). -The package enables researchers -to conveniently access commonly used UQ test functions implemented in Python. -Thanks to a common interface, -researchers can use the available test functions -and extend the package with new test functions with minimal overhead. - -Regarding its aim, `UQTestFuns` is mostly comparable -to the package `otbenchmark`. -Both also acknowledge the particularity of UQ test functions that requires -combining a test function and the corresponding probabilistic input -specification. -There are, however, some major differences: - -- One of the `otbenchmark`'s main aims is to provide the OpenTURNS development - team with a tool for helping with the implementation of new algorithms. - As such, it is built on top of and coupled to OpenTURNS. - `UQTestFuns`, on the other hand, has fewer dependencies and is leaner - in its implementations; - it is more agnostic with respect to any particular UQ analysis package. -- `UQTestFuns` is more modest in its scope, that is, simply to provide - a library of UQ test functions implemented in Python - with a consistent interface and an online reference - (similar to that of VLSE [@VLSE:2013]), - and not, as in the case of `otbenchmark`, - an automated benchmark framework[^benchmark] [@Fekhari2021]. - -# Package overview - -Consider a computational model that is represented as an $M$-dimensional -black-box function: -$$ -\mathcal{M}: \mathbf{x} \in \mathcal{D}_{\mathbf{X}} \subseteq \mathbb{R}^M \mapsto y = \mathcal{M}(\mathbf{x}), -$$ -where $\mathcal{D}_{\mathbf{X}}$ and $y$ denote the input domain -and the quantity of interest (QoI), respectively. - -In practice, the exact values of the input variables are not exactly known -and may be considered uncertain. -The ensuing analysis involving uncertain input variables can be formalized -in the uncertainty quantification (UQ) framework following @Sudret2007 -as illustrated in \autoref{fig:uq-framework}. - -![Uncertainty quantification (UQ) framework, adapted from @Sudret2007.\label{fig:uq-framework}](uq-framework.png){ width=70% } - -The framework starts from the center, -with the computational model $\mathcal{M}$ taken as a black box -as defined above. -Then it moves on to the probabilistic modeling -of the (uncertain) input variables. -Under the probabilistic modeling, -the uncertain input variables are replaced by a random vector equipped -with a joint probability density function (PDF) -$f_{\mathbf{X}}: \mathbf{x} \in \mathcal{D}_{\mathbf{X}} \subseteq \mathbb{R}^M \mapsto \mathbb{R}$. - -Afterward, the uncertainties of the input variables are propagated through -the computational model $\mathcal{M}$. -The quantity of interest $y$ now becomes a random variable: -$$ -Y = \mathcal{M}(\mathbf{X}),\; \mathbf{X} \sim f_{\mathbf{X}}. -$$ -This leads to various downstream analyses such as _reliability analysis_, -_sensitivity analysis_, -and _metamodeling_ (or _surrogate modeling_). -In `UQTestFuns`, these are currently the three main classifications -of UQ test functions by their applications in the literature[^classifications]. - -## Reliability analysis - -To illustrate the test functions included in `UQTestFuns`, -consider the circular pipe crack reliability problem, a $2$-dimensional -function for testing reliability analysis methods [@Verma2015; @Li2018]: -$$ -g(\mathbf{x}; \mathbf{p}) = \mathcal{M}(\mathbf{x}; t, R) - M = 4 t \sigma_f R^2 \left( \cos{\left(\frac{\theta}{2}\right)} - \frac{1}{2} \sin{(\theta)} \right) - M, -$$ -where $\mathbf{x} = \{ \sigma_f, \theta \}$ is the two-dimensional vector -of input variables probabilistically defined further below; -and $\mathbf{p} = \{ t, R, M \}$ is the vector of (deterministic) parameters. - -In a reliability analysis problem, -a computational model $\mathcal{M}$ is often combined -with another set of parameters (either uncertain or deterministic) -to define the so-called _performance function_ or _limit-state function_ -of a system denoted by $g$. -The task for a reliability analysis method is to estimate the failure -probability of the system defined as [@Sudret2012]: -\begin{equation}\label{eq:pf} -P_f = \mathbb{P}[g(\mathbf{X}; \mathbf{p}) \leq 0] = \int_{\mathcal{D}_f = \{ \mathbf{x} | g(\mathbf{x}; \mathbf{p}) \leq 0 \}} f_{\mathbf{X}} (\mathbf{x}) \; d\mathbf{x}, -\end{equation} -where $g(\mathbf{x}; \mathbf{p}) \leq 0.0$ is defined as a _failure state_. -The difficulty of evaluating the integral above stems -from the fact that the integration domain $D_f$ is defined implicitly. - -The circular pipe crack problem can be created in `UQTestFuns` as follows: -```python ->>> import uqtestfuns as uqtf ->>> circular_pipe = uqtf.CircularPipeCrack() -``` - -The resulting instance is _callable_ and can be called -with a set of valid input values. -The probabilistic input model is an integral part of a UQ test function; -indeed, according to \autoref{eq:pf}, the analysis results depend on it. -Therefore, in `UQTestFuns`, the input model following -the original specification is always attached to the instance -of the test function: -``` ->>> print(circular_pipe.prob_input) -Name : CircularPipeCrack-Verma2015 -Spatial Dim. : 2 -Description : Input model for the circular pipe crack problem from Verma... -Marginals : - - No. Name Distribution Parameters Description ------ ------- -------------- ----------------- -------------------- - 1 sigma_f normal [301.079 14.78 ] flow stress [MNm] - 2 theta normal [0.503 0.049] half crack angle [-] - -Copulas : None -``` -This probabilistic input model instance can be used to transform -a set of values in a given domain (say, the unit hypercube $[0, 1]^M$) -to the domain of the test function. - -The limit-state surface (i.e., where $g(\mathbf{x}) = 0$) -for the circular pipe crack problem is shown in \autoref{fig:reliability} -(left plot). In the middle plot, $10^6$ random sample points are overlaid; -each point is classified whether it is in failure (red) or safe domain (blue). -The histogram (right plot) shows the proportion of points -that fall in the failure and safe domain. - -![Illustration of reliability analysis: Circular pipe crack problem.\label{fig:reliability}](reliability.png){ width=90% } - -As illustrated in the previous series of plots, -the task for a reliability analysis method is to estimate -the probability where $g(\mathbf{X}) \leq 0$ as accurately -and with as few model evaluations as possible. -`UQTestFuns` includes test functions used in reliability analysis exercises -in various dimensions having different complexities of the limit-state surface. - -## Sensitivity analysis - -As another illustration, this time in the context of sensitivity analysis, -consider the Sobol'-G function, an established sensitivity analysis -test function [@Saltelli1995] included in `UQTestFuns`: -$$ -\mathcal{M}(\mathbf{x}) = \prod_{m = 1}^M \frac{\lvert 4 x_m - 2 \rvert + a_m}{1 + a_m}, -$$ -where $\mathbf{x} = \{ x_1, \ldots, x_M \}$ is the $M$-dimensional vector -of independent uniform random variables in $[0, 1]^M$; -and $\mathbf{a} = \{ a_m = \frac{m - 1}{2.0}, m = 1, \ldots, M \}$ -is the set of (deterministic) parameters. - -Unlike the previous test function example, -the Sobol'-G test function is a variable-dimension test function -and can be defined for any given dimension. -For instance, to create a $6$-dimensional Sobol'-G function: -```python ->>> sobol_g = uqtf.SobolG(spatial_dimension=6) -``` -As before, the probabilistic input model of the function as prescribed -in the original specification is attached to the instance of the test function -(i.e., the `prob_input` property). - -The task of a sensitivity analysis method is to ascertain either qualitatively -or quantitatively the most important input variables -(for _factor prioritization_) or the least important input variables -(for _factor fixing/screening_) with as few model evaluations as possible; -for details on this topic, please refer to @Saltelli2007 and @Iooss2015. -`UQTestFuns` includes test functions used in sensitivity analysis exercises -in various dimensions having different complexities -in terms of the interactions between input variables. - -## Metamodeling - -In practice, the computational model $\mathcal{M}$ is often complex. -Because a UQ analysis typically involves evaluating $\mathcal{M}$ -numerous times ($\sim 10^2$ — $10^6$), -the analysis may become intractable if $\mathcal{M}$ is expensive to evaluate. -As a consequence, in many UQ analyses, a metamodel (surrogate model) -is employed. -Based on a limited number of model ($\mathcal{M}$) evaluations, -such a metamodel should be able to capture the most important aspects -of the input/output mapping but having much less cost per evaluation; -it can, therefore, be used to replace $\mathcal{M}$ in the analysis. - -While not a goal of UQ analyses per se, -metamodeling is nowadays an indispensable component -of the UQ framework [@Sudret2017]. -`UQTestFuns` also includes test functions from the literature that are used -as test functions in a metamodeling exercise. - -## Documentation - -The online documentation of `UQTestFuns` is an important aspect of the project. -It includes a detailed description of each of the available UQ test functions, -their references, and when applicable, published results of a UQ analysis -conducted using the test function. -Guides on how to add additional test functions as well as -to update the documentation are also available. - -The package documentation is available -on the `UQTestFuns` [readthedocs](https://uqtestfuns.readthedocs.io). - -# Acknowledgements - -This work was partly funded by the Center for Advanced Systems Understanding -(CASUS) that is financed by Germany's Federal Ministry of Education -and Research (BMBF) and by the Saxony Ministry for Science, Culture -and Tourism (SMWK) -with tax funds on the basis of a budget approved -by the Saxony State Parliament. - -# References - -[^rprepo]: except for RPrepo, which allows for downloading the whole repository. - -[^benchmark]: A fully functional benchmark suite may, however, be in the future -built on top of `UQTestFuns`. - -[^classifications]: The classifications are not mutually exclusive; -a given UQ test function may be applied in several contexts. diff --git a/joss/reliability.png b/joss/reliability.png deleted file mode 100644 index f46e39831f5f1a5b5ed50f71c4cb155550adbbff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 480951 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

dFb2w`~y%Os`LA#LtqU_-su6SGxei`b@$vt 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

?PNQ(l_0pReo}m>`hXl8;0e8%$tz4Bl&Y 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 q8t%_q+|L=s$-N zMOGSwan8^3lj3&i8=VL9)5cIy z>Fd6}`}W-`N@5N>FRr?vH8gb1;tevf`w}+DNuE@!PozU=7Kc0}s#u;=&twSY4~q?a z#Y}u+6D_(>V{!{-MTFE3hA~92ZHh=(i`Znfc%8Lw!wOF07duHf4KJYP``luq`|w#F z?oCF%ptM-YII*S;KuTR&cD_uKi)!soCn~>Lm~T`g}aU%YYn#lhOJDDl`y4^ zcZZ5ufe6WjUAwdY24+O7ji$VOxs?({1)s+@tTt5x3h@&c!dj{|O$)%o4!iErf<)1zPB>+F%T~6C)NMLFojk;3Qy&*_)V% z*X4l5G)#bz;RcF$3z>cCOC812eKMLU)@Du$?db%qk>{k|;Bs;VwMRj+l1X?iVLe;V z)ktK|FSo~otY|`3cEs>*;=xHjw>OM1Ahf0qBd@s|0CI$jS;0?dq0OmsM==h(p$PE< zXjt;3>|?sY(GB~G4n`Lq6{*~g_~Wy3PqWF$d-2sar)oK-5%ej7I(MSBi*H=`ihdVaGy*BU?3tT)|8$B?=FEsJTqmaV;_J6%SHa6*T)Zovm z=GbyAXKxV$o?L>piy!ZE_u$+R17&4KB}M#&0$a8y_@S`stg1nIEJ^JFT>0~zKmcG} zPe+m^$UNc>?zuy)*3%yXpYeVBU88GMYVH`tG-Ri}^=Q5DWc5%~H;DFGtE>#Dy( zn;26R^U7P7hNV{o*B9klYu>2$|M|Nu&T}r>6v&h)+Vdb=2iChRC#;Fver136r7%VP zzp73GG;_Rwi=;(7RNK9rKUv0y$Y#mpu&~W$aoEq$81qv^zuOH}@@}#bH7wyH^>_u! zEuy2f1?YO10q67_3v^&k5|kntFitEj;-)1ap{#(VNiL9VeVEV@k=y_}Af#Ww$o-&< z*1EMY^!KK6Y?G1xWo#!kttfjUWw=QWO7;LH_(gLxo@m0xqV27$ZLJRzn#`i+F z8oOZSge+l%o<;Dd{YP6IP@e>$$JfSx-sZ!811@Tk?`f#-l&2 zSZ_`ViAObtmIT2WhsJOMYLG|X=%zPW4|Kf-MXM zwyp-a%>jQbv;=3 z)Y~?!#DL}gP+7^+VZ&N90CR9e>asbm*0TCdQDn83nkM`p#kpn(EacHCM;D?_7m~bd zU25{=+p;D6y}UU)4eIn`b>c9Vi@6Rch@tb?1EF5g8f5@5h&9XupRc*z#G+J~iN=vSc zHJxLubtl<_bTXf6>!nPI{N6m!>rpJssR`cZGjDzla!r4}a-f-n5#^c)(=0W)#I5!t z=4NJQf3Fo?hE{BMRtG7Q6F$G1r+mhP(>vvm$mp@Ne1Ig#81=TuGPeQ3;+~$*B1`Ne zSy}(#Q7~()q6Gkt>)->~sd9?qA0ZI^l08SbV+~6;_Vv4`|En7j`4MAxZ=6S-!evP6 zh=Ml;K(&(iUI`RG1fqs7mp};)x9xpieZ*O&#*DT0R~OTpwO?R8Byo>|IfnZ zq)QQ=aEw<)^=L+Zs40Rbt;WDs>$(qf-~^UTVMjM{wC^01&4q0$GeVHxc)M4=Me<&0 z;CLbR@?dV|!f&+wDW<_1F4lWL^05e6}9(0G}I>fwFkE)6J+FBDWaeMY{byIBpP@kB@+SU$YTR-?t`PVma#k*vu5x|ox+4 zr9I_c^jUvj+K610;En;?XGLk?i!&<0k^Y*=$UaI)EMhlZ$QW7hzdVIe23%bRvW>Ev0 zQwOlT&uL}&`~F8V<5+;j1~vn&ceL)Tq;JXI1QPm)cRfrEG*E~eJQr9rK~l_`Ev0H> z3&Muv%BMq=&UxOumjy-dV!e6u<^@5P!Y^(p0nwT+#BPp+1VU)p2;CQrhLIv@RUXFE zf7n}CZ$C>Z17+K82uT~!ra=WUtMvTI;m&sBgj0Ro6{DqU+QAj;ML-nkk#_B0Y-_Bb zGhIx?;bx757r7eXxf`x=Xx&8k220o81=CiH!zn5gfc^EAs%THF?>9HR}ibmsN&cQan<8q|J+v8-TS zdctl+eU6u#Z!wyUCQrS55#fufrUqlMaql+WeT3>4LeB$@!Ru{YC27^|CovAa=F@kN z1X$IAI8LIQY&78oOD9oPdlGgHSNjfMHRMFFX$KOOUY8OXwB7nV%`fUj+>t>_X)3BT zP8)5usMA9j#u8VcWPuAvp=)@2oumMczBLYfAOefR@zd2{0`5yl!GRo0yrAb~a#uY{ zXSckAz5WGxs2m%SF*%d68U=wjHk!jyJ3&^IO{%s%1$Rlx`V*JLi`;y?u}`TtL`-U7 z$72m$i;`iZDGhZUwtCXJcQ%QCu4(%G$!@VXvE964_*_=Uac;AdTie<)kIXD1KsgKF zDbURUbo}oAyIR}4Yr`I^;TKJ(#kT$nbmBfg6WjtP1!0lP1Fgpr&YzlINMb;y7xEVq zQl~T`eJiT6C+R3vuBulHstI3mIQQbO@A<{lleZ!>z*IChg3?*FIK%<3vZ`4`IyZ!& zSR-3lIH8NJZ!5sm1t9|@MROJwg%gouSieB!3kU&t3soUy16Se+!A=^qhltw_qN2tH z*>)=F?MU)ZqozC)A%h~BOdvMIqGYoO6@Pmc?Lj|VXxVae!>IoSmh-q?GqBsx!E#yu z5X<)VSu%+3nHwxykkCG~GQ5LvP;G4IJtXkt&aPg=;T1X zuSSqmW%&199g5T=>+$2#-)$n4%^y8F20eO%G16fKzcs`U0>4xAsHM;Wg|Z<7t;l&B zI*BLSf8!~+>=to6Y(s1`fu9G4JtO%5lN3BA5HuX=xMM=m z74hUqYZo^`V@O?IZiYstN%p$@z-{|0R^;gG>xo`DZWGtBBCKZX7x8#Xq>bASEuT@*TYWB_14q8EV= z?uCI6zwhv#n-C|kY8ypJ3X2koK>2a-#4Xek=y`5YfwQx7Bg&sb{~zUS5AD^(n%s|l z?re1_509@uSDeywZ;FJRUbiz6_Y_9gFWcU)lhsjHZ5n^3E^ffke%}uGEB>PGqkb4=Gh&3Lot$&2oA;H1o7GNDmjMz>r%#Vz1|eM$6eNNsvpC?sIJ z=lQX?0$M0)rHfL#B<~L*#PRUY8CALfy9pEp%*j|6lv)oHM7C8I9!{ls60L&vAe<7Z zO5Rvl^6PBKM1GpX9L&o%>JBSTQuOk>)AYqb1Mha5b{etLZ7+x-j$*t%q#RIsF;5|= zYu}AvnIBdr;V7*y6hPrYCQlB@C#jxqeW90!$Kwhf9Y{Bws8mA95^{~GzrN>uT=i5; z1~FZQY5D}!R5_MYxeIkmx_J}jE-bOrsT&){W30dou#{Ya_Px$42MdDvm7lE7C0zIN zP^b3=&dEp;Vw1X8qQ%c#hic0aSufvq;k3(2(Tymjc+hcq8;SQb5uAJTNjXe1?n{{I z41kbmQVm4_-jhivR|_^^cpw*m$~t0|Q?Om7%_MWMhK#Ba(x-cL86Ao?OgprPR&xM zKeL$@b(C;#k_lMW&uu5rXpxybvE9k4hUcGftHbF{97{8=4{LHO$8#=G(t1$$5UPsL zX&vj*pYf%xcyW(}6b+p)O71%D?GzzGhv8G~160MMis3~OiBUp9QjSBYo1Vw|dq6{7#+u(@<#dn^u~et$4jY`{ zBq=( z@c64)L9{KnoWK+D=lI2(Du4hjHgKOjIg}&@SfBz%hP?HG6pB!sAw=!vH7x=VW)zXg zGlYXGH%=lo81^(4ol#U6d$7*U+t&85cpMyE1$3z~u!uAJhx zNSUGqg{9mm?aFr?8n-WmiWVsvQlbERxTG3_{r06O_ki|-9iL-EAzEp(EGQQO`wh28 zf=TK{`Xz@>I7TUZEaooYObWhmQ~HFA{qmusSsgy6@fI&@%(LeN2(d}-spX1OHoWX{ z<2K$sP>bya_T%NZXjPF6zh~+x6OWvQ`|Y9E48m4qiWv>zvSrRb#j>>NFM-Xl%u3t6 zkl#uEi6!S`=|e=xVuRo11p=fxZX208xcAaqLeNLp@bSC3Z(9lcz)# zXinqpT?-uPhlpdl|LdvWNHI*xQ+XoiJA(CbP4@Y%LV`h*Wu{o@Fn%}-SKV78AP4fr zHPvM(1)!RE$a%#=3eSS(1SmBx3M|S~N#0l9hCu>FU*PJJzhkN!>f#LPjd{6%UPteW zl#K+=4hlMcWTlG_Pqj&YN@=xh+_31o?g=*gOL8JtP8*&~aj)pTbzw&WXcEjx1);T?p*m=HRn3<)zW>5alT_ zm>g6m1;yRgCVAOG$b+fGJJ1*vq*LtO|FKu>ST}fKx5@u0_zpu=?ptGWT0$fVK*+(LC@NKWqr)W6W)Z+UgT7)wfKHi?gSLWr`2bK)t2 zgAuemxhIhhK13ce9A#r1rW~|#SWhw@Vl9nd7R#$w#z3V;-RZ2s=a9WouM}Y~^f(wX z>+C&_JmsLa0#*!^>*NK~qY=lvdy^C$B-Y`+%)xu|lSTuk0LO1j$>I%8er~nT_AEZ# z?<#V<8vhO&U!^7=IedLjf7*#BB|n#)LtlL-U^KjivLhd^MnULHPfz&`{G(D(ED1Nx z#azZkL$Hsy$VbQhG|EI8$nqiZ+@KYc!{bY>_yYl6I+oKVgv9$cUov-D7yCTc4qRby^WUe@f>h!o``o+{h@os7Rx#@653U8l`TNw%$|eyfBi+t z5;c|7+a_~ci=X?2(3?;MtRsjUsS`BL{mlhIsPf={ITivRm_>pvD({G`1n5zgG63zE@FK7m-vU=E|s#h2Bt79K=Uf(W|STpEgMW8i|xxWO2wOqiK z&p5fFn!%Ow`SPU~SR(O!q3jH*g8}Z(>R1JD#7RwpNOMYcUNF_pFdBORWnFA9;isy# z3e?H7N`qzW#y*YxTlV9@`Hi#kd&%jp18>5&#m{$k%FNLUYjWtYEpmO%pQc)*Ha{<+BWp6pv6+ApJh*-^k(=qIp)sF&_h<6k7@CnF^;GNY+vumh>xC4^l$*%)`0FVYFy^|LLoy**vEi7)nudSaxs|nnWZ;zy%KAUgJTEH@@ zs#1Czcxg%8xuxH2@!10p5V7g6<7Y7`_9oT*6bd5UQiRkK)RaPS5p`*xqfH(3vTV?; z<8+kLAg!icB67A2nA&$+V!juzInTnL9Q(H%e|Zxz4e{=V+hgGH%y7AR3ehv?J*%9= zTC0k*l6>(9?Drr9dxxdDh&Xr8sZ*yK7>I;^#tk_TUIZAm0Z6Q(AAt>Y;X?+$6gA>Y zkC;w}IG+k@lDTh9Nq~C)sF$-3lAms)g1?2=+bj90;~)7TsfGG&J97!kf3tmHs-gZ9 zeUq%^jp}Us3#gAR7^AI5(j=(i&^W)7T7;3gJl>@OS!b|n4oQ?)rcVl6doeF8i+W#B zyN{{57<|DfWPfFshp51d$omEcBp7NP-e;-9`dqq*(1>bS2xh8JVL!^mY@kL6^Chn@ir%t;H)stNst4t7MKLp zI^GDLQzS(CjM@=5m-3}?rtDpcxt%w*Q2q9wRV*T(!N}KI-3x-`Cedw_VIMEzrg$l~ zRF%pANWC0ByosMbWN@|j84yJj);mT6P*8}^XR>mvsXj0`Who$U_N<87^d6;jScUu9(#!`M~_hCvlUzbL>Xl3*MlMS)H`y!>4Dq zyn?iBAFeJew5~Q%^-y z5Qumyc^MPG#NI@CKl%|~$~BqHLyX@@ExO=QZ1Fz@#8+F z3t~mV=gK8g(kE7V+TEjihb06n8>r2(OIZzI0<+@X9J^`VThsuz!as5n!Ygvz3awpO zvA#4n9qO6a8pmmm*RG|fuAZMX6oI`COV@fS+jW0VoX@o29XDMOY0k0&Y-OEPxItk6 zQf86k@rYFu(E5he361)qQN6VsnhWWmkkAQ%r7Yqd7Wx4Qlwdd=RPgqK?#)C-$?_`t z1E_tXZnz}FeO@qxiW~fhu;eZ=3c`93Dv2R-dY+HT+nY(lv>X%Tp4-)dpQZl;sJ^k+ zhbR(&6@G&PsM-KN)9VIbXs|w)WD_iTLXZ7$a+p>h#{+uLbpu}KBI6}sP z2GneY6o2tnSm`T1d{b4s54#H-1`?^76z7gHG{VPsZn^?*YClj$HmS?J<*9Aw+bcPB z=uQ?;H83&ZjZT{)o)wRg0uZDi#i{CDrO1)3@#SbH`92S70HWH$TMDj~1sH?Pc_uba zwWHwuk8}v@<$91vnG)(=9}*A{$zCEkfAw}&fH?w`kG%=~GICiueS~Wv7W=6Qi3{Rd z6<_GrodE-+ELa(AO6k1JJxu)JGLwtgbef9x|3ALo1fI)$eIGYxzMZC;YP3)!GeVnX z$Wju`Nl8(uWM{@MVUUpZG*giILbNDC)76RKjA|#u24*WWPn@J8BK{YaVl78KIgu=v1s)dcgqST`{ z6evB}=H94m%_U}3JfMoaVPGox2qFFSBHz(MmrI8zT^zNQOBSp5{zB!ms1S=M_OD2` zNhKx#$3GD@hIp_HfP$QM50)l`dwGPn0JE_IJXnB%qRSKJLvF#P2H8cV}^BCxQ zAupE)z5nvKQEXS&jN&#e<`COZqnkUDu#2ES+MZ&|>2T=;IwJyyraUuZtOH!LtI3Ql zBU&^nVhW+TpmOU2Ioc?(uZ6+?M-gIPwO+or9RIbJN;h6Lz~aS-+{8qm*^YzWpvCXT zaZNWz@hg6j2PyLzLt(h)co~Iac!};(V!OLWXBCtOBv`~e-+4f)Fj@m#ntqH11|j?)~o?_bSZrq=bdt9Y!fwCPjM zeK;%?v#;>T!5u5H0eRxDhSrzsf~z1mN(H34c$idVI~EFMb{;nG#v5~o8B<10A2z7f z#0!G^4Zy1})gVnm#i<6ni|9NznLU9{cs@Mx~Gk=s&;nn@17`!03s2xCNC% z*Mk2}`dpj(b;uUTCX0f8V#nQN?9is)BWkpoGb>iVXviaRs96#TWy|AlBYV!h`bT0u zZG~gY0CP|%7t$sVrdP?x1l5TdSvwH(JZ$APft*0aBgTQF>l(yp#D&7o&BK}|mznrY zq;MqrCz)!aIF`L98Jxl1L^Gx?m9ag6VrV((@wljxf5GdRB~B6|Gaa{~FrBH3KmD$K z55jsaRMtV5IQZ^e`33S2BEKba7X|QCJxndNW5?d=<%&VF|d2Y>ZGPh^>%;ZYdE3fIPs}%fg&`6^Qs@^-NhFmQoKmYRO@S zv^^@BcEo@Kl%JrSaDo#j{|S_-t6Qj^N9UKBbnE3K2R!IkTjijBEQ0Ns2xVTzM#ace zxrT>}bbb2>Ttx@~AmbSoXXVoA-y0?W`;r1LhE`891dKp?A6B;>6$G%G6<`g8j!4fu z^p8OiHrK|Bgz?{QwhFx;&Jh;K5o-t@?xJg4S9>y!<-S}WJkTEWWn}qiN;Ug98+j;2 zE@VX3f+&Kq$p8JpJhDwE1eglEBv2*!>Pg73Q6yQ7;)glqdm_-%F=lH3rFsA(laIR| z-1L;h262yymq$^@RQIehRK+G6YFndT-Gc41iF)Z zENn&L8XHAQQ@Q~yP`F=XJTq3z*Jnqkt5apCFwZP zg$ZWYs#S|4C>R-ebm>)mcPSu1b-q{oF=v;Czc}gqf%}LoHHKggYbumtcOcG?*T81D zby8eIQTkL$DX28lM0@)txXrnROw1O!HL)fDC3BaG&)L4yBn;E<2F>jf|@98 z3CAzvM9sbjt`@_)V*F}g^*DqLM4x<9q3Na4FS+s&@f;;NAt7%ZN%VS7Bo#5fIW*fC z?>V3o?}onYidH@C(D9@8mV{AFNG=e#j=#M)RWq3$0x^Lt3Arz(rnTC9q@YZYv;n(G zVbsegqcW+E*n7?Xnp7M_@v=7{HxH9kOFr&a85u(WsdG+ErM`Fd+~qiq*)@Pg zCK%tf*>SEpKAo^%zj?CeW#C|WA@nr{WMH$bZr)AsH>zyOOE@l(OO~p66qUXayGAWxN<_1F!jgV@ znqGI>XuSE_Ev@uTs4q?aei|F&KQib*+rDh^48K=J(>L*yxR+!>5YeeD&$SYkr+d?t znHh;??s?p1*ZC!Je@Gl{CBYPEe<~rFDAYwD)o7cmO2VC3Z<^k4PhJ*OTVc~ZriucM zMY?J1-O088hy|ap30!~zFR&*gO4EWPZWdkNsl6Ilk$~Er@Omxmb+l339=z>E1A=Lz zZB8mmPt2~GU)DuigAa8qqz#OTPw=>!kJ(39Mj)~9M)V!yTHeNd5V@h`*-6NI{2H}`7f`e)!0w`%8s1%;g zR?Pmk>ysE`4gy@-ZA57L&2)y?l}9Q|Sh`>kbnK76Tgmnh50*gY?+4EVWXO6WIUT)y z+Yi*LfW)U@a(VRTqV19%6i5fsRR(D2Nlc~^v{6vd3kgk*H%Ct{x-7}}01Q7C!YOsq z1RAhszpVRWP{L)V1vfutZrZnhTE7+P-4)T*bN(L=Ubot=b`liSwIFxjlsM_HGzxVI zuOqY~!inq=qGRIsrK_Aml#~yE^pJTsod%er)xWHpxC?l5E0t(Vn8X2xnbh0{jJUC9 z^QYIbLG9T2bA{2C#s*pOkG8Gf?c!JPkD1#dtChUHj;nmf6o-c8@l)jKtcdlB>&7S# zrgWZZAO$VGN9{OpAW|7dJs04j_zvMh_LCBzrj5S)g>Fagk(0}U?neav1Dm!$3r_r^ zTBYgv^JC(C?t2pOWMDpSsan$$x$cO%GAq@Pn{4OBY3vzoSo2T$a#943(GuG!P^A3{ zC6pAdF*b2X?5f*3$)k+OUH4Eo1P%j_)Iw}TA%|l=y#j7Xb~lDCNT)n-oYp9>qm=yk z7II4_3B&9{n|`6U4$jofuFZQ?n0LV^_p!b9=oTN(GyeJi7`y2VFhd;ixw|_PpMuaW zV)8VcN}8bkMoILM)__h~l&{&~!fCa~%nr&G#>jNzF?kX-Z-t9NObL;M0Vb%)d)1gz zTlOr3-74gu!;Mk^Mt1t{roQZS_MP4xPch_Tvu@m@OBH{`Tf6lJU&6|b(@}jko_(Tx zQX52K`t_tjBkffIAu+%NH7@)6*PJdx?#WIRf={R`Le`W?GOYqfGM~MMbe+7P;zr-q zBfVV>C#!gd^=J=nS^KSujN3Rm$=~c~2sw;LV($ENKW(3}hfdP(d5+Eo+qp3-yWXse zP|lS2Un2|dBM6HF$HU0VjBzt2aTFNeUZm(B>F;1Co3u}zbz2|;UV**d9k`?%s?TzA zBpxTR5ZKfd*5-q1wTztlhVORj!cN`{@WBW zlBK>5 z+lVbqEKFiEy~~)BcGCaVacdeNJ#Gy9!^Xn(dknMUtAz(WqCzY~zaH6p;oxr0cz8S& z3ONhVjJF~ww$UapRD9ex>?JK9X{k>A7t7<|nei}gv32#lg$kecP0s^Le=nZ?0eso{ zNVH=llMx`ZxJjh}X+^jvY^~iSOMaf@*hl3w#8MKqwx9nm`tHXWK6bjw7Nf$YgR;m< z8eiS@VS91q9~-}#xc;dUPGWgX?;G{%APmz=s}<+rp$Zq30GY57$pOttE!8io3M`=K z&7hnuMh#9VLTg+>5t{vK@PEL3oB6n%%b(7FynqT(_XCDNP0=?fc&_BC=BfoLZSwiK5#K7B^#qUn-eH z6513Av0ttDH*Oli2+Jc+z-R|HX%id2tjD^g{sHpg9gE+m4$IG1qVL>Bm^*i?cf?De z=06_9S|x)ggh6H{P-x{*j!K1B>Nh};ZtZVXFGHYBATAZ5@%Fp4u-E8p$F+Q|6-A2svmDZpq&5gwsXq1?zti@u@l_}>j_yl z`*!Np&fI#<5`@2j9Wz_T?M(#phqo~8nsCnOgIw#ubS9*v8gMUhEQB(OO5ZpID7+06^iD`K$=}_I_}w ztCInxoun(55$WZWJn&gpzVp$$1E>12rhhemH+9&~-23#`#lJ9-P7sv|^vEZfnjuEA zKh(WS!Wsf6NygNKVJ$uU=w+~s^J%8VLtogWG?D>!D)}?EY&zfNN|E0j2{RJ0nHD1W zxq4;CH$N^XRc>E>rl^kU(`dt#$x`Jx$0-@x#|L9p#FkF&JO8-5@V}YR^~Ws-ck~~n z-3SKMsj=NuKqZH7Vw{(?=h(KPkmN-wTu2+%67){y6$nC)1gYrgtVJ`eLsVR1c(Y({ zZn8o3R^!t{v&6AKe2_yVC$rEva^fSp`$u=I0x68%cN{YV6ue8pH6v83X(u zgQ%MWOzd7f(|w;!L}r*!5pV(sCCvdzgdT`l7s-;aR)s~)Q_#UtPr$|2si33&s=bNW z@dk-GDbiwaTKY+0^6V^M)B1wOL`&moUFYXpnEK9|vG;Rp&xTr9#Qx(OIpIGESFN4Xacd&Fs1l7L2G8}Q{4CIy@>t+%) zs^#DFyGpeI6B8qy(Yqz=&8_n1ZRQ*2egWr9KjG0x+DqM{!ARIZ3xTAzqP3{F-NTdW zgP=85luVUPIxWy<&Xc^cdMJ6L$_E+paSUog^G!Se0?CQX8dt8&%@D~E^D%^JqB43( z)ZeNXM`5Vv$>sz?9@t6UCozd3A*5stMZje35h=kUCzscIFz4dDa_=&G?T$wSJ_Veo z<`9#1K6-QD)Bpmfox9S;s)Y3)Y!L>fqyM*2c|R|SFAQO3U*b^)xOI!BHZ&KI!>I(- zp~~5a`_t}P^fsZ4${s)qbvm*8tF*w+fsM#*mC^k*nB=RVgVm*mwb$U5u(kQ}k!9IG zxX8%?b!3d{fxXhsMt=R(@eV}4MyMETZq72aG&w$Zdqk2b9iKx=3(aS5hXXIitejTf zztPpk`R(nEDRYii`mu1K6<7N@kGs+zi>+W%$=EuW1yV!61kq%W5DJk#a1wuFkKEb~AQmE>hh@wUyCY1)M9*7XT_INpT*McZYuAsS_T1Q@; zkP06cU&=D^BKm`I{?et=h@$*QyO))wbo8mt*t)&9gAQa7J(Y) zlttdm=s*3K9dh^WL72ucpV%%Yd{oP(9fJyizlCiE!4)$Tnh0gT4QxXuw*2wgEhM3+dz+LuI%R~*bd zv5h3umq1=!CH$}Z8ZmnLPXe8bW%0g1In*EjW8jN(94i~ETY|+jMlBJf94E_}P<`g@ zO@Ez6d*0lnl1Ly7@gM{PryOD*Y%8CV((ZI- zK+^jMy&w~M(uxS*73<5`)(92G3E6?}?J{L&CcYgSOxMa7jJR?3@RL1zHZ&d#|2`_*WmC}YNU zqTBiL-XXAmb$xS6we8@%!q@g9xc@^#Zc!8Dj{*mOV5yPXGBNS107suiMQfbR$bw6t zl7ID@PcG)luJU|+`tJL_>29@OUiD7Y0_?+l zBo6y3GgI3k#PBO*N2il0UY1y;N--}5c5UrX4k~a!&P{Mh{6CM`U#)@XjiaXX`yMFH z6-dndfY~34H?VmWkD?{ri+4R_B80ss8&?GjjSK>nYDoIc*Y--IG)xwcjZ=67jWoZ_ zT4(;29i!G2t^GUt_bXS&nJrlMd8pT7sM%?#A096^aP2br`Z$X)<19cdKBct{K&tlD zyCOxf>>UDM;!x#8TYK`kl5k8^)kv)Vl1(O{My#$J?r@tz8`vBH229? ziu%1e%{&>u_G#^&^*l6Z;L54;dnPsib^-^=T^0;wG`6Tv_@noXKrrpCr(*W zP$l*OwNDd&4t=a~ydaa%`qQY~c_j&fUwCGo!>uM?@l=N%Kc}02+WYYZBCozab3B-t zvtJ3Vh{0@mZcqwxn`Y(rrP{7gCX0ZNU}UF6%k!MX}8Gj;j{9Kbv?;zO(i0y(jH3BSFr5dpS8*@n* zACS5&IJU?r84c>4A{9MA@lZ@}k9u4lqWj5Vyj<$W|C!GIYK`?+nt%NNEX|PV0S=SN zW-hvUzjw*F0#@2Oar~C=nHuK#MT>iRq=JVh4!2aW_E$F;-aD|i3Z|O?*xit@M)?6Yr0I_tKORU((KCB2In_im}uCsE$WrM zv0}!jjQ6%f6Ux=8`)@>kepD+wY7jlD!k3S#5n#N0vSyl<9uIr$7i3(!5LT^Is^afh zvhCepGyuhyqnk;XN)DjDgAO>asHX?thi{)A<5f{nuD-2*Lw{zlSz<5Yk=3#CzQ>3Q z7XLpxX-sAJ#mr-;V+WGTRT!V^`-kh8BPu#R-lEqYySBQu+Bt59PmO4#zSD+jkpPY` zN04HaVv$A}j57L%kaFwv%3yE|X$`z{p1nTHeZ_&`M~bJ4MZUJySXj)DbiAm?)|N;- zJ_jc_pBoktVTj*HG9WqC5%g1iYQ+GOoTU0)%KUM*>nZ~xtSh+X75)c zCKi^Ii_^aw0-&jg48NQ00o^(WO39t5F_Dowe+Thwp*DV}F}{CxVjyY|7xmMgZU{Yl zzcb%x9GE&`kVaI!>w#SJiqlUM_&zc%Ev+ax)oS&`p9_}j5nmAMCNXtMpd$>={ij|LS1(O%@MAX9AEc)>5M>{toJr zL%2#!#wg;K>5p6L*`UrRKY#tjKJA@~t;i(hJtQf9;*{wtY{>%9i|rji#Lub z;sFz3kDrh7YlQ*n51UAO@;#SSiek~srGVk;1r-U&ViJGtb7MNLQRxBdv73B4qt<@d=2 z2Fcge@UlOoxK^Wt{2$;EM7?i9Vmokdy@W{w(akPzB-z~b=c83%kq`y{U;Es+G8M6b zKC10ksEhiW?*KR2LirV%9Y4OWK6ecv&hyvW|4I!`lxi@gHiCcdnuof{p)a1H(2zg# z)Bl*Pmvxv4)hvGZ`$?!kE@}y>`o8Y(@2A$Y;_6lP0dL9TZ3!Y8dl=pY$C}|)5d10v zRY33m%~=u}e+G3{ihljIA6h$gR51#g^Gmp7dWTSJB&!l(g%6Zfkea=fiPiAOUH2EY;zylBxabWwZ5OSA^Q!M&Vi&W;JW4Q8>Sp&>_NzYQjMW#&^^qRCWu@GzgUwiup$J?G6P&xudeCX%c2O{zcOyab zBKrfwebePYnrVnAI{gj*$FPk}KMklSJ+703u1YmyEt0vW71yw(Cxl4n;K2(1&3WQ! z#E#;(a69yY#5}4lqX9o;1)~}NpXQiAA3BbN%loSbI*Z$rsm$^?4UAtQ^m`*@uHgBJ zte^?;SE`|!gJ5M$C^2(LxET_cNiAwRbSF=@eNLcRSPb>~N1MJHw!xEO8#DEebp`5s zd!RG2^Sd76Rs$z_#gk1$G1wHnm0zoi#?FoS5z|XL&DLZ=SB^eZMjh1ceW=VX=LnAT z+0V{xK}8ex|6@HJJ;tmjl@7?ConVDyk<@Y!O_r!_^{ARNlF2k;rUl1tgXSaxbv5po za*`)1_=*h=5MNTi-{cbrVEpKA5^B^@2Eo==s30}3ub;g<%N-+xt0VvG`Z`RU;zRw% z@`-;6?#k%nV7xRTYNkpMV@2&G=J;}A%KM2UOt(0-aWeQFHhbX>AM6mokW<5xzrJ&` z-d`og!{f{AAr|Jt>!;pk@5{U2tV+yR(I~xd_HNX+V+Xs2SN!Gu91<$$M^{iBYvBrA zW%tak_|)nQq>HYnbFMyGv5${s=MBFNJJR`1@23&zNEP?8fzL*RTAUTd~2{ul?%EyVLUI7yyu` zDHyW89!lLT(wqH#VuJvkt~vSi7a|PjFLn9&$8#ugWRgyy_F#oQpCL{dIl^42!U`;f zxMSVcqcrHcBb5O>cS}UysW2`5u(f*;6WDMcTOz6^)MV;O&C>9=y?w)bMT6Yk_eMwS zRX8?9o*KIzIM@)tBWZK0#Gx_$@xXgF&jN4Hm!S^IJHjoWDTQ(bE$NDUHPW%ilFriO(5n_s=-%OW{>ye0+Re237kG$PB*|<<)2hf}8d|clcT4)|U*D_sd+H?ClU4 z)jgOWy8fEiw#n0*ctX6!UOE(qT`rP)*O&OdYY_92M@j#InWfOMeM|PFH*bg+p6pV{ z7RjXS({cfC!!CMjTCC~%_hR>fU9~=CFPwSM-tO2N@7X}hWPwpq2-5g)^RLG$aq?3S zZVRy+Mck{v+Plap8DBR7CnBarV|Djklw1qZh5H9+KGeI+SaxpYtvm6oJeaG>GJCzz z*!Y9MzK^av+GE9seB0aivX`0J>*{hV@GmzF-gDg)VR_8N))23~ReL)+)=3&$P@FF7 zY*dnEWSP!)vAwN^K>tW^c7B!)dOM{oQvM$nf-EK57 z{$OuX-TRymLKV)ruOj?)7C(`SxJjUv{ZNynGF1|Di>v58%Q_U0YO z9P3&){Y3PHCGU4eU0ri<(~WBE=?Kg$D!RoEH@OU*TTDwMUBf;h6dQ? zn!NwCTEE-E{_Gdj%* zQ*Ozk%S)-tVwh*#-B(CM|M|0N0~6}DZ|lC-#25w>`<-~H)folEk|2==`K(f1l~|PB zS_|pA6vpsn%CI#1@hac}FK%x-6L{*;)k`(iyYc?;)Vm&BDDDclc5m!IV=DzSe{`+u z3&=iTQ)?pa+S4NzplPjxAG^eRsiw9tt5wItp*(xpk$4W)S2BHV@B7b=M~oWVc}B-; zUvT};?EBiVdQ07YV6tkC)4O{3Hrb&EOB=uDVvZ6mexh2t^sos75rwD0+^Fflz2_g;pe}J>syv%n!!<*com)<3|oD~MUp2xFiTOL7^gMz*0DG9W!)(_H&pz$Sq`kT3rP$>TtjMmfmGa! zynA8zy56Y#8W$@o0jgB1g5|9~lrCQ61tQbqaWD@t%QCobMt=kVhR@CyeJ4b(S5nx! zFZ$JLySPtkU4~&XJE?!gujamS49KNc^R8iHW0aQdkw@z{3q@Ft+2_w2@(gH@6v{p* z8gy>nNRXnOOzz75lCafHUIWQgkc#o(V0wWl zCFqb99lc2gMf}2V=CApI{CB{X$$eHa`=039$M#b+3|b%$kAiRDWaIn5lW#vnxI8(} z%33UZOZOD(M57Nj=NmpW4pn=lPPw1zNZ?etx_x(R*WC-J{`j|Cn*81OciPS=S4V0x z+v3}s>00<^ajRi6nh2L0_`9s>sVE-e^ohH3PqvgI98K%4>lsp&yHiD)qgk1a!^&89 zg-w|A#$Hc0K^Z>n;S9R9>Ce2M{miG%&hTxZu{1Oy6tPH(0{e~hc$YV_va+HlqYqiP z6$Ousj6f>0<;msmYNUm;KOLra#za8bieePmHoI#zl|MYTODMn-!8H#dg5(5CKSk+u z>aTh;SEa9R{+FiahP%7k)|OX+AN@A-SwsB$QOQM;vXnF^(r2 z%vw!yEB;;4wI$5k-F->O_a1wFAD3R`?yBMZ!FD;n{HyD?FSWfCX}?*1G5w&|g^`Wi zdo0Rcs(ow^5&4iSEG`gl`Rw@?KM!6%ism1;2FRwxst7)|EV&l7crbaZ-E~s|r;b;F z*3C-v?E-(R`XM+zzBut}i6;bKvE5}B)^CUHOuj!{Yz!$Vt-o0&1a zXesZrqK1Otey;*1BcMQHx7m-I9uo?%u<-COJH^^zIXSuO=`SmH53z0*&MR+SSCylE zvd&cPlT@SIdx!Tqm%6I;(>uh4E+=%?esmaJ{O{fPM{wI~^V&&IF{O2qjt6*y6yF(@ zd#Nv#X0zdTm0-&m*LimE8e8h}n|oZkv(Dic<22gbs+ra4qL9|SOTalcQAmLYvALMn zSk|ye>!r!OQ2B}F+}=Z)-)|i?tI(e;SNn6$V67-sOq)u8-J%$EBa&G$^(t&EL$3#O zRHNhs%*fynLFCV*7UJ)qS@Rs+>UDlafQpWm5yxi;KHnUm+zRf`MJbI^`rPp@ORG}x zS_W(O&zT(|`bMdU_GuQD66cn)ffKEcJhHC8Jc<~~FK!Lc+#1Gv>8Q$X4P9=oyY*r< z+q(XIKc4>wPgRcdE*wO>b)CL^Lz{&zTRKVeaQMFvjxRd(fsK__MLScjVfhlDob_wg z+WOrr7z@z#*=?E4xu{_cr%jlHNQ=*D!ODz`bDn$(IOJJ}ogV3ptqf?C)Tll@y3)UK zeUKuStjJ(dt3Co0xrB8~-E-D+t=2wO?^4t_c|AJ1`Pv+|?XPkA{4b^s^PNX%E2h#} zNZfXC)b+vkFx;CLP39_7fK49}nA};{k<+~3kxd(DeB@7WURjS|y8}`>_Fsf1_~OJ%f!CNyTHTSqb_3d#YBljUUYI;#MA=m@o3CGhz3j zvz^zS@8%e0m>|5LPE3`k-7$48R;uAuSVrpLpk;}h!X`f5Bx?rhP9dp2_=tPrXQn#= z{JnyFe;IyOTU$uHIP5|4yKSzUa*+KC9e5Vm{WXt`;y%vCvos!?^qad}@%7Li+p4I- zi@7`Q^fnF^3>Hh*bhwY}KSJRM*~jAI)~3jS2KLSBS%2mH-QUR9&w8n=LQu*=LhN+s zUB~aodQ21B9*5}p&7WK`o?~?)g;VUuO0D?cL|hP?OT75qi&xzx$hFB?>~(EYaS!`` zL<=`YQf)c8(&-Q)Aepts`r0R(RH8oCXKG*znqvQjA? z4{+8Tun*od6~Xf=l-%p$7J0iq*ZZvgYcF0u)5QGA&(D0n{?VNM1KktI$R4;5HVM(| ze{VyXNB0C?Bw}5_!--Jtit?W6-A2922*&i(+FW%;mNNqxQWHeVrkBjVIGtB)rl1}u zs`-(GY}?`a{_1BM?kVPsKYfr`*0x@5w12*kkop$?hsJm~o%7BT5*g!9o{#0a`utaR z)Z%1xohXkICtFji_2S~LnCN2~-MR0Y4A~onU1F{Nc0o=PI6OGN$u&$yAZnr~?Xj@h z^FzMn0eupmZ^2DGaCUNY8)9OU!d61_E{Xw#1Y)0)TR|-L6KO_NUmb0j%=z9lTp9|j zhJ0>lYpSL0g6GH7T_<2LRsh?*HDvjfk&$sLA;E{5B9Ir^J^0%+lGzQ6ZQ_slP%of( zI969nP22BJ4fdNRCb~uKsdg-2lATgnUu2E!S?h~@=k+=t82Zo27dEX6c?A>^tX`E# zG(9ve3yRNx=D}KuC70~NRKq!3X|C3ADe-&MAwNsvA4BYBWrK&#G}SSGDZc#uz9#CByQOSv+ z;Uh4MoX?fV4{YyB8TxcnmJ9U}+X{8&<%GvMNUT2GHp4A3Tl|9XzFUE)R_iyut|2rO8due{b>42;3oLYU!@B5mC= z8KnSS@31IZ4RU!8@4oi3%NgogHTj3`o}Bo2MY~x<4iu-N5`&N_G-_xA(b1uijIPU| zP9MN7R52|!g_IVc=~lB*2XSx-g<8Grm$qj$HH?fAHF|+|vf&j5YhZc9aG4$PA?AhE zg7-)kx_YqROawB|UTs6hcX_PxL_|^k%u1js;j|`B-|t6!V|Jr%U6{MOdy199n5gF) zgsblEC?mTlzq+YX9q)a8ZRTK7(PD3|y{XZDd#il-P;{Ko4Lo>M@Q4Lbl?%{&4x$46E^2mY|-aEfdHfk`u66A#$+JGY0_d zr)j&?==w5R$Y+XunDfPZ07DDirF^9LNMsF?4H>{@~|&B!+f_+*B4~62j)i#XWr1 zlS9T->)*NdZSug05eijzeDV<5exmB5nu^MehqZ|S8p zX=_6-`0>f^XLo+c?P32Www?W7Vmab6k_dTNMJ7T$^+(JHB`!DjHK%W!d8J4Jy?*=r z%<07A1`EBkri&DZfDZ$HVvT8nIsc|x-EdplV6|+67Zjb6Eu|r|0mdr z9Lfh&{PZ^?)UzIR`c(=GTn4W2Dp1s)h+51Fr!s_c#L^eM_C;9o>uv^3V+l7`=o`lT z3X>M(3A0pVnPe5OyY@;|=lg~Wd@3sY*+NselXPN7#}f+w?V7bUqfRbu z!Yq4zZ+PMu_c6KU;qEZ$@Kb@ya7q4ojX~khm?R6!!a1QQogEAvF0P{?*OfZtAoOep z1Hv1{6yVp0G!c9X?BX%mMuLn8a6;aUkQN!G>my0h#LuTRXkKk?b#;D%OMi@S^XH=v zFUo`=$Dte&n7{u;Ywp-cW#KV3`!EBJi3|-{n^Q+^J2cWvnlB6Gw8n2Y=MH%}dVbYd z=Awv4Eu6da8gC5ac|AE1V~0NfDC6gGUE8+AVW=(eQ;e*Dozk4yCuqdKoo5;=ak@;k zy6_CNQV~n4JK<6F(TWSZ-@NKq06!AZ?RMGC|N4{j61jBh{yvT~_y^F$PcB$4z7>2E zeISOL?@fSE=ZSsk%FlBOeLz8G)|e&=MlZ*16raFWpd#$(gN%N_%zi__X0rn8y=)DZ zP1*h12A-}OinQE4`u88};u3U+uW^P26tr%n{X~Wb7?Puzfc<#aUf;a`%&~k?D-J5M zn1;E3H6)=GPOQ}MdeA>&^Gr<^kY^e^R3tSeL1P?3jZov9Tyqb~rRzzlwXZN-ju6j4 zbbEQwk7M$+Z`k51hkbczDY0qEf?RSB2gd%unM02m!XJ6210+Gf$|_f-_NW9LU}Z%D zB`EWkkotwNmOkK=izWt7y=y7e)ktPTiyssrDmp%XtJk;Q(&u&In#$kr^lVdayPlsb z{cBIpS<9^Del8B$tB=e%?XKwYJ335bHU1;GaSn7DJDwH~?Q|l76%^mB0rIj3Jo6^JYY^dv-;c zgw$WPs@>E;kGPQG7+@0m^HzOSt;i^JXJC32!6wBot1`4u;ffg#-hvllQ&|AG2dpro$rtA5m3hr=~ZQ^HJxBq59e!Hkj$1yrJJC z0wZ&wwSZ&KbR2{kN7BR?zh6-v(bvv&aKk->Ji8Yx|6-MzYf~8rtJM*YFJ+OgP?FWE zd#S}5a039V=;(-3V|jt%y*=-9YCrC+IvJ<)>6$?D^PDE5UHXq$b1x*WzqWFVMXu#= z^Pm^EaGwnFz>%fKc`2OT?q!eCG&;i2_I&0;)sTdesm%9I1fp03wG*t+XWKAVOi*x% zsmsDS#@&4tSRlkDU}deyb{yOaf`1dTKOa#2ZiMHgYYD}pn11H?d6+tji;K^zmN15U zUJndf*pE7b{E)Wy;1lX8eUY|dh^|9oQRrn{QU-Gdvqkwfhv8CrR6B-|fGq*qRx^&e zgzwylRK5;o%Z|!|uqrX26bPTzC^yP&-roCoT@!@#LK}=_!hGl_D7LJeKWD0Ao?U0S zQ7li>2o^P%Y)HIpDHJr%KD7J@eemS+v}fN4~hkNMhjil|7hv5X2+7( z+fj^;jz0HCvCL9WSqm)#(T*JX2$z~piGHTB^3*Wy)YRwCoL)}h=5TA(aDq#f55Iib`7fymq{H@yM)ksOA#UjHQrA_~JhKyfB;_kIXi1>brncfeL{$R! zP9aNAL^9(4b7^!O-Viy%vuRBNZomBV+(U@vY0XR?%2y;drjqiAjZ;3#=H}jxc7Gvz zOQ*1?{q06CU33Lb+d1(?98+v+9Bp@Tj+nH|Sytzgy{@#G<62mO?wUKD4@aG3Fk)(- zx`zV$ea>(GYt_tDLrer7{0!CAybA8EO`!FAk@kq#B7x}d<*74Clq;uRzp#tc!==5w zT_&j;ec*+Lf-$WGSp|7`XiGiQPJ?0syuq_ykVO2%i$zV~7fZ6#h6+|iz)~)coZeuP zAhgv6d+uw8)>M;oz`X8+XOQR?J9%|PcI{DAl?jul)@~%dBpid>k?3fxtQh(Z@a+W@ zRI=WT5FGobsYd)DdHtrQrt&F%G0Rd6XX1tSlrFYQVUn^)R&FeM9iaZ{T8s7KMXwti zWTUiQ5goh>Q7cPHYtN_}NPK;x!BWj7tXxw1`MnjgN}MtWeD|`u$`Bk{uQn(0iA^mr zV4tq~r7Q+H;~@EF^pHLkgayqc89W6>X)VFVaJ`F5YN0WEuR(^3WRJJonxJ$tkT`SP z4y87>w)6~Q(oyYYQG6IG73A=GW6`sVBWEp9J3E| zTQkMZ><)RHqKoM=61`AOifm1R2(WMB@?1&uu-)V8^%&ZHflWiJO zK52SsN~jEWt<*m6Dhf!?T`}sn+m3magRI!|5al-8Nfw@&^zr~(7*p?PaPjUiVXkF6 zmex%z&Kvtb2CTTTUnbOHaMvY+G21ynR?R7+q(#$Itr`nDzo}I;HTy^J;Yo7Qw!_j9()a zz^No`R>P>2^PrW8UF74MmQPKV`H1O%s0$_ISB>MEexz}x_k>rV&G-kyuQm^jjSjV` z$&o+k;or^S2ptg>6?O8rVDhm;jd{nycvqZD``N#sN|;ogD8&2==ZVfZ>04aBzv*9ND_`F<4ix6{X8FidFQVD)IRq+C*C zN57x}4rSq6-N+ifNm;-&?}PZ;WFK9Y%^C(>1nWC7R8UhTQ94gB)k9Y zV*^ndD|RheP}HVpn49>Eyq?~DOtww0&MpOZVT%tA&vS_L!TwfK=#-Dzh(qum z-_n9H6gm|Gbl8XIE#i#7RII{tx=vh3p)IZTK!srdm%ohaW}$=XW75?*l0UUaDd~eF=E(0qRi zD9$I68`dQ}P52T*j@6^6vx+QL9`qC9P8iIF-y&{cTW73W(Hr8E~M07 zpcY%=u|wJ@0fA2HFCcGrxxBRr+y6yg~DIWq|4{Oy}Y)Dai0s}!q5 zUZ*9*uWRKDo_qbdU0(g>J2UJnoC7A!)C8`GO+>gA+!*p~Kve$tqzSj}@|!|)y>Dxz zjFLQOR;Li?6y8`SJ0MCu+O~mps#P%A+_16==Z;xFXsv$sEM@N`bnilR!X4_uL)Y-^ z(EJagGA*P{^J<0F?)>P2ljHKw%70K}m0#v_tq{%`(tFVQJ*R{KRXRW~+|_nwcOL2? zYwAvi<%xWE^r1F zLnPwRy)l=EK0-c&Im3~+LZ03E zxTCar6KgKdY3}iKclYM7m;&9ke?<}1f{0aIvQ|BRryN$EvrLyw%SLeBrz6=YboB(4 zJMr@$yq2ZWs;a6=R-nsdQ6**k3u)$2?aS8ImeMWaJ5rlS##j*ZlMP3N!21kP5syK; zAx@6CXWEnNPSEZYW4m-S%*xI$jK^%#*1T@Kf7=_kZ%NvZl#Hy0X)YQ#w0CqgA<*-I zvtsNU8)|Ju?2|}n+WCsBZvif47UxmTlEM7o_h_8vN!uu~W3Qx?L)F$u@FG*_#+Fe) zR_X8!(NJ(qQ=ylq+)rG7(=#i6gR)vUI_J6LoO<&dnB~RD8b_0p6Ypv^3+C$fy7Uy9 z%H$gSG=ZS^;zremte5&`$p$@*qsaE^<8m$JjCyoiIm2$|Phm&hRWY5b=L|5YyGSHBFbM8hlw5nFvE{)vDa_?kk!{n^aaeHtz>docS1ty%sM-~zzA51uXrWC$AQhP2 zl>l2a-r2rvp`r>I<@>I|Sx;L8MFE)Fu(jTUrja;{HY!A^)L~UDj7BA5ki_kR=7Gx~ zHc+MB2xM`ZM{QfE7(=E)d|kuXy7TWssH2ADh59OAzz($z?oCH4ubCGiKvMbLoZrub zlDR)k7jD5lo|zn7q5N{ktXzy{{)(9iugz5RPQ*ICmW+tj47f4hU)w$R`c^MQ)TkSm zhj4R;PIlPq4@T+@teaF%`ahY{<|j_`oBdk)Dy%%cBBSN+x;y2(O;;ZnRv!!H>w2-f zYskZsdy~^#gVm=apnir<<#r~o04mWIN0T<9j1Uu$iCo>uDc=ePWb#n_UXaSgJnX@r zFs2&MVH*`3o82BA(}=Td^ZN|!!MmG>HXup727B2v$FW-aZbe5Qzmy|rN(mvDmMgVf zaa`vEWqIDXjchB2s=_9Y{yq*K?{dl$5$31b`$thpWL~Wd;vPQ5!!krn^ z%OSQIHuI=o;1_xNm976I-#1>qyv&A!)6~e5TVs3JoDlz4B#<+)s>V&E9MWKehpp5K zgFJwYs|m2AW^ruA%1|ayhTaLsYwl4E3M2m7&`x=NgRShRnHu|{k&Wk?UF%xGrv+JeWahBBrwC7vW9qrB!ak9 zblIY`9XU1^wH(mLhDCY|s&wgrOU>7DH_ZW2+@g3cnD4ipS#g_*lTrUl`|~sN7bEMI z7JYKknsDTWm$C9KUfJqB(oAq~|L^#^-eLCwLt!t^2Gvv+;ESNwy1Sc7zFz&|nzfE% z`hfqfkde2(9Z~68+bnNf_dTHk4szv3@GA!?tCtJjli6RZ9n(EW>SA&YA=wlg&8@~5 zB`zx%Sj)?V3Ez@IF^6OgLj{sK%D&Ro)zwTZ5A>T<9pzB)&4csa3mymNG&Dn-nGUu# z^-W~C16QS(HzPKfoBD%U@XKu^VZej_uKH9@isI;0bMAZ{69WYu8Lh22Itsj2E`D)V z%PYu_s06GJ?YsyaKFN4fv$OoUyaTn$g)fmeK_k?rlgcUB?E_Mb&EVd3?~ECGjOk*2 zZsdnJJs017F+Vk@(KM9!z~FwL)O1I!m9ucqVQnt(`vo#|PkrMOERiMHXQiQ=d)?^u z7U6=SWMS_fW1Urfx*S|s`wA*5yBuV=O`Fo=6!p7-#zhQdFJScyOE3)cEC_D@W;`O= zv3B3?Stt(``}>Ngs;2PWogMwJKDbCCxxRX)gH~PvVv1uAl&v zX6e*g7--49P@_`OU+=q-PGUJZd^0}U2zB!TiKr&Ipc;3P&>(X3T($N0U&&Kc1j&JK zzgZj(4G{6zwQyWaE{ocDKt%mQx;!(aCUMu4&c%M3&a-x!P9Y*JJREF|#?gR3)o;o* z`1xEE3ouhV$z5^hlXKmTp<IVu%ehY4dGW717deGOX=)0im^AqAb zb4Ij|^i?@Hij_6-wP9<%n@#Ro;{5O`fDu#~VaR@8cofs}QaKTh6>M$geDlB!`9VK- zEZcr?vvFaG<643>kG_eQ$tf6}1ecj}ty_kF&0 zc;#no*u=Sa*opdx&8z)@t<(y+_~oA~ddY|m<6Lt5s^*a+RXnSs4qWBfVzgo)qYF0O z2!d}@|35ONU!gTgOdx0y4j=V_PgxH&a9n-0%QX9Rr``}8gNn#MS`#Wf7Zbam&!`Ld}fzKqkQXWPK z@tKa|zXKDDMH<$d{{k@|6FV`IKw^y|oFmUK0rgxb8E*1HBlaT|_wo?ranc_k@Ad81 zTE4Q+Scr$rw)plP&eBU>;g~SM=QGkG}k9#xw!<(j$Mh$(8MNzlBj`ZrItG-@uf~7kF@J(e|KO4Q?A=)05 z>TS3--JwtaiRI+8k3~s?wHp+8N}D4xTpQl!w3an5o)ZJaDt|`fQ-lbwBJ-R#BNPOo zF{ai-xtKKYYUBy`lt}>kvU?;K|2?mE$(pTqcAxey#s;hLdl!UDMA$P=v!L$9q-ez& zU6?aKY)K-vEs7To(95|7UFyc)d~4#s*yu@3a26!E0AqEUk5ok+-r*p zp`KM2Q0XEK8y7isA$U@tH2mmz``F#oqZdjKcV>T)6^Jo9RWP_Wp^U}K>YQsW7u(m> zwC;AicS??oUQg40HV}~?`k2@^4D}?18ADp2vfDtEipWm(RSk@)Ah7)E8o&5^GHl)s zce5l_{~U|h-SgHxw=j18T2FU(u+%5kuH)zyp!k!)B#cN`(Dmz2X~5kTq6qhzgANRiUA^%|)l1z>C2+)gIp>w%9xVqKM}WJm%iiD?!10@KPb#6mo$*WSK>8Lbs1tKM*P;y&19I)Y73|GN_=L zMlQcg(R`GSE(+8NZw97)Dlxw%W5~d6d8&$%C>xb&xzjqUKNHne?z5Ep~-rb zS6wSK_IK-~%EMy?e7!jDp%{{rD{(%3^y2pMnK=2g? zcpZIEsQ*MoQm`oJrL$nphk|-ADAfl-XVwfZ8(cKa6brW}99=&uU)?U3!*OiD<9*-w zXvE;)2hVS0hm1c>OgLAxjt=Ip&2{D}%NVV;KT(phH^4dW8xvOOey#FXs$w$*mMjY3 zbzk5uQ<4lS_fuqBVMlE(1ML8}1F# z4ovZ?fv8!_lQI}$u#Y**B5!{z=)x8H5PgA`kkdUsp*i;uW;LOKS7=p?a$>ELj7)@Wfs>5*H+`iH&-P62eb!Xb2Rr=*bW|)zZoa7 z*+J;@8zEbfDQ>xmj=8f;yo2?L6V4U6`sFd#F6!>+JtaL+c3#9-dEDC8hON4=>4Vd% z+|%#6p7%ws*AMZWLLybM^j{&+navs{QyxP99+ z@ihbFrUE{&|otgEI;&yujM0**bkIRp{W7DL5oW#R&o!KqFfubMqx$ zwH{dn+>!tUN`n7Tldj9iJ;-GN*n>E^(^11jLYkpcyVGag~CcIDv!<#88-0v3q8;sNGvEv5qf0goZ%q;FOY766a`L?W(S@u zO6?gDlFthX8@E;*8@?ja@t{!et)@Qc-DQnce)$0hRnrArZDZ}@w)sp-7OWXbRTFE; zb}zUgRxI?^GV7)oDF-WBi^D80t@-@+?$_tftS8gRg3^lzwFmY(^_2xQd2O`ulp5?W zvS|vj%UKha(Rpc=xP_7Fzy8B%10P0)czK_)!r{9IDUfHGhDE-U5*vs5;z{f+h7tvYOVu#XsE)+Wd(Xoohll+q5(C%a=(hF z>$z0^$41G!t7~g&{5LH4{IG~xj!j3{3#;!5~gJ7DGn8_6lrb`(I0qpP=5UU z77qVU33@>api~8y3s(tgryB(oR*Gp`$7K{9X&SF`<*?-LDODTF6`@K%U%dTQXb%-4 zUR>M0yZn#A|Bt;lkIOm#!iSrgukpn&W6Lf>MC)XUq-7{1DqD-TQAjF{R_%=$OC^yO z?TM0BrJ|^YQEAbJQd)=>+O+TWT<3G&>dwq}e!u7M=jk=0+wERHpZ9X!=UnGH*Aecs z=2$)zP>Nw39j7|2k*C*QeQ?RHO_Dd0Dz(t|U(aWmS{r|V=!&S2=XVw1)Io{`C1L+$ zzk+VHLO_Hyra>gq0f|e)`091X@>kza2DBqodM|)fH3@qK){hnlR4awlbenyfE#*e$ z*tL=rE{awiq5mH9>e}4B`DNw^w!@~Cd>aD^t9FXL7|sw#tc>_F&Y-v>!xWBf8XfA? zqqc+ud=U9mrFTP&&d}Y6tw;ZDmy`xLo}y6i*>TM+!5@mGY!=_wH?J98CpujEK(7{h zMrV9VG}*FP^db`M-`P$LXi>UGo(Uf=wwK04_-?>Yuonx?4zRG&um1G-r{ zZSIre(o>FHarDa*%6R429y$s@0KUU!qqV#?;0YeE_%PHSq&dEzeu>d%v)=B!_`YpW zN3*ZUU&t~|Q_5{!n}bkzVVrAc@9T$|vD1umK>3;)D+wvjUDs~@N|SRx8D1d;Ov!3( zDCXnVd`)0nlg3F@BH0X~*n|r4)@wWfg3b&qHA}*ImGEY1)82c={aiSeLqqFsY=sqNS+xZ9sL!gE%w0Qq*Xao1n~V6v7-B=0)WDm@)TUdt zfBCn$EU+3`SD(?b{if>)ADz<~`Lp`ZUG-7FoKc%FbhP>ws+oA;bAbS-t^2D9K|eCsF)?ZJrb)ASD)w1-UvNGI&%FIGX>edRE4mK9b-K&zWTC2oA zK5<3I5c0BURu$oSJPT&elj=GqA8Y@1o5}V##h0flPA>9#KX4~Gx1p2zTXG+G4IB)i zYr1l+>{(Dc1@2S%sN0dVWvitxA(b;ed(=XRiWN?Q&N}A+VAxS9Xm30!Bh?q0C{p-) z>83Ul)~*5V@gDQaBL18OaRggZf@N|ffYFzb^eCX0jEGUiIVT`-L{d8387FTi0_KT8 zggd~@k4}be8@sgz>)t}d%SpkA%jqQsT0!dM#^W+wgIV##{Z}=zyD};lt!wL7eX-|+ zT8Kwt)TO7lknPSBNq1MXE*?ryQ40tb6JC0v*}rh3U`|}53uv$PQj+}izlTxsVP^+f zqW{zGI$84KX;0G@2R_wgx^(KyE5JBbJBYTC&^+ zY^i4%0S)5Pr_pb-@Qf-Q%z6 zr*01=ESy)c5wu$F_@Q@hCaz7-QCNh`Wn3VL;~vjYr_Gc+%jspyIqWrlfdnvj`iSMt zB#XTp5AQ?q@N}Vs@;dKIk$cM=GaY6s6f~ifRPy{AcVIGJ{=U|^=m#i~vr)vo)= zSa!Rp*HJt9MwFT8Be9GcAi|+0HR)cL4svJOWo(y4-XcR*-bK~OJLzr4J?2V(nAvW$ z_K24W{t_)tyvAMh@Xv~zx zk&QWbw8Jg0anZcW9nlYOrne6#Y!DE>qjOk(#G|mY->XR_u{kAgmj@&bE?hXCF_Icz zEmG*#I@p~0UX=NM<`1WRtN@o$J*|-%QB=3p?tdb2CH7@xyFYl~K-djWIkZV~i3|q3=j<^j?v)@NqVyKpXMN8P#g2J@CxDk|PUdydRcUtj^2Y!K}hX zKJ_9Gwv68RsQTh`wTnWRs^*i7d_B16fLgA!ycYOYYhbw1`crkLG^bM;mBVDG?ueZp zYWbf0=Ddnd@|&Hlq%OYQHt3lok|>6LdZ!;HAsi7o>`}WXU{icZsuIx5E6RqTF_a;A8g-N7}dES|6-qT|QhgP1qi+JfF(A({{A=++&WHcRn}& zU^+4|0J1^j{km20kJ=n(YDpcuTPewb8hDb@CgPx^!R$tPuMdM2@8?S;D#_VwGo*69 zd76W_mFy$`kauBn4M4zYm*A_tBY2lU{NkDqA>&+8sCQ-Wz411YwfH)W>$^ z&Vvq_m5yXfuKiIDZ72bP?wO8w;MRLx+CLNJ>->=pJDso4N2jMR!97NIuLI0Bz$YZ=NY-RHy(g@((`!ZOU+0<-5ugmi^9`%?h`tg` zf{UzS*I9-Fl|?ZWuh3&)^BOfXufu6FRCP_~JkQF)V~e;A6^5#0WjqPZbpB(7Tj+MR$~7c{+Ho|$r!?wd${SaikwXb{zP48PC}dsR~~8JPm6qw zuENXxwaipj7a-G1iB)Ch)aq>mG|s{iCQai`JUzNgE%EqXyH>Mcfj-;tV&rQ`AV6f7 zXXILJrylfZG5=Xj7agMzI$1Ggwm=Wu%;!oM9+xMs%7<pgr%IaXLPcbaWh(%Q6G!+#o2~`i|EC*w5boOWfBp)y(ttJEr ztB(z}{HE5`Gk7I9W7)@Bg)Y$kneO^&1qx_UZO9{{tqV|0!MpSpDvdjaJ_}SOY+03a z1ax(GicMkFH8buy-0>ldF_f<^5C4$jwXtbaobR)J5I&aI(vXJ(2+@ih)OvP94RVV zkMK=H&C5LUvykCQ?R&SGM8b&CIcCkuI+-H(#My#TIV3)D()(o177T}U_ug9g0yj3E@^i))AT z*6exCv|fVV&Y>1VG_x0{%7z`t!c1Eg6~IJ*50F6c6yCYWGu(hcCdQlrELIq4hyvO8 z)YBi&GwFrk{(SxA&v!Ytic-5;$b?Rd48%EW@YkHffG8)G#TtxCaO6&5>zA-MZ_LjY zqv^%(?mm!{m34UZ_^EmG4-joP3tl>R+V+gOA3`fg;ixmnc4f(cCT}??)mWPc^dV=f zPabH6@lUH!>$m-o>bxq;QFGoU60&7lmKP#hwQgW4R;FQJ?@XBPcoYhKv+hy*6!uS1 zD9gk0dJ!9$5H~Y1?-+jtPw5d#2Hdy&eY)6ct;;Vz;V9!ipt^tmevc8t_Z}t9UbTC* z{5exnkgJ8Z-1F?zEMfwvYFQj)6zAY5P!Yub@ZG@k{GV;la2Q5tU`$Q;sP!-Dmz@T> zw5vh@S;d-F{NV&YpkdO9IvIZsm~|4X6~>v$-tI0DfQ zNGSjqjg@d*o7y^*bFOfnNNnM=pxJOE4q_2)?EY(KWYmh+H#A6Ms(fjciuE&+lzV>X zK9}XId>R#js%)3cpO=Mo_uVgp`^;ic7r3d{*v@a9|b(FnsNJ9DF)1ExWoXZMXZMd zYwfY#P9Yzbg$kO*l)}y#G{z_~We+~-U15~A&KdN2j_f&7X5iuqLKBn7F^^@RX`pM_ z-qB&f`laK00@FM7=mX;FM4!vblyFn{bQMMlsp>N|F-;z+AJfx(>;h0S55gw+`E}kk zJD!;Ga2WY$wz^H7C$GGb#2s5gijTIXwfxztpgrv?6)~&U$}@%%R;`-EltE)|f-XYH z@Af!Cf+L@o$i6`|I9o#O-$TtdXi;?-E+oTFx`5>olNo1=w8aw=1SGrYs`Eh8rQe3T)4s$T zY?4e=3-AzZK>e7ZnW|jkK%Egua)acm7_kk1t--RtsG33UWd;(z@u*q!S+)B&(4w>s z*vI!ys0vg5uRrHKuy4mHsbWN&{W3Ql7fu_;&QoJ7rL`@M|en2-u; z-(%DL@u0_IVr7iY6!ndM_s48SY^7YF9#BRFv(N(M6`Q>MM8x*C0RA{D&Y&Et2xlf!Jr*CFlFcQ=7a4_`4YA1ogsj(y?3rbr( z>95|meDmjTl>Yj%8Xz*cJ+6pjsy^SQm||XTQiS5ghrI+6GgYY0)4K11a~OdLQl39`GX6CkF-vA##pXfh>q8=)WqB@0p#L92}A%Ex=(n^@m`AlsTRL7Fv4-=b= z5qTh)Pf`GXSmPHR9=-}&T}`yr9 zUCo_n$&B>QbEs%hx7}(;$}Kp#3IfzqmrX)Z98>zLsRQ%;c>PQbTXZ%Qx&tT9 z0GatQb!VNt#9W!uS8vQ>;fupNSWyeO(v-5glLD`X8`!nk)@W2+o4XMi12DniRm@st zAW3vDa8j+A`Wvc|BP~*DMVvFxjsBkLC^^l75WoUZ@>Pb=X!Ii8b+nG5C_3JpF${)6 z%FgGBe;D|AgMSXjn81C4yaf&~gZYKMTa@Zz`DTPx-@4$r$#V_OF!XnDShfMY~K#ys>h32k|^dT`A%v?q_8$G&}@%#V_5JcOyJXOtS<3bZ8A3s6>ZxO@GyC$GB|T*GGYpZ``bW!$7Q zmCYF#EvydQk*!tcI_miB$Xc!ayI$WEQV!X=Mj~NNgp>E4a(=_>GMQn^{O7sUX0}`i z5wsELU9R?Xzn8tv)csLT%?6jn8T# zt$@_VP>97P7U0>+H$Q9*MWjQt4zDd#$j7Vp+*%*LRW(k~+0mSL$7PdXR59TR_L}oD zE!Dqw(XqxPN;YV?ZP0k`XmTO9^Oi-a$QM;aye|huTNmDU2^Aid+nxAe}3~0s<{d|66cp#s*!rL^X^r5OM82sB@-CQWyOoU&mn$~TgwNBm;34t>j zkikRm`Qib|@L6hGS^W9cWX~#8!_7uUw<(!JX3}SV&$_98#iV{jo0(nJqCXM{tVdy8kdpp15q@* zvK1?q@ z+g<&(*%BsefAW=StcSXs9(KZN>k*nS+S6)XaTI-1j{L>-D#D_#8kU}fV4aY44=z{f z&dv3xsi#J5rx6+`RF|KKim9b4F7SdqA9jNHunAz?EZVPN1)NqVwB5TJLXVn>n@R!g(7(AA%TA}G>ux(1@=Ly(W#V@&J?VCRBf zSxc7V0(3f>;XcizZ1b57qaShZIb-u<)Jz@52`2J+N`A>%RBRtIaP&Dv{$Kv=RFLv< z+b<=#R(p%p?k%PRxAaP2Ytrt`U;K?t!^qK-GHsg8X_;@Xb(IUfyYgImiAa#dD#iY; zr%xN~6gH~yP#kJ)i7uK{X!sw)k+SC~;-yT|9M{YQE7Gxrk=PY-Vh{D7H$rXx%4bqI zo^rB0-;gjoNb6zm``u8AvlL4H$B!Rh-4sC7OcL(FdQJTpNgS-)%{NMj?RMP2V2yz# zj&z0ltJFz;)ee=_?r<-p(3IBil{k)cXRySWW}~O!Gth=@N4tY9@tu7p#Kk4~$@l#f z-E12b6jqH_yGZtFWg*FLP;1yEGZVSb^yPNsPepOEW$;g$+2Tc zDn3K{o@)Z7=MvR5@TNsw6u#iB$e}Gli=9LDdW}+C6t6CTFN| zhqMcwp`-x8NGc+P+9?s0y&3YID}3_2wh%u%3%xgIH5tH(Ain4Oei&Dh_nh6t9AQq~ z8`iBejge;WvKPon=NUg~rJ z90Kz4)FleHbnXTkD+Plr5u;a^>qTNDSt!HP?eC~->W*gYQsgc(Lc`!;%ipCgoe83b z3~!F<>9i#n-aRbYCCZsJ+4sN|oktoCIi1vxp=>apcm0M9nM~WdX4qwz?(X@(7^#%J zeRQN|)RiKsbS&X)G(HKkK?HVUEJ((V17A`vd?w%1K51-BTW^@)hp-l3=eH=@9++G%eWXgUty{B<|`)SEzS^q z1rGHFHs5A9J-#ggdDZ3qLT#^CR0BB`J(hospsLcrA(RPZBbhUwYAGe^%60&MXy|s> zE)GWb9AamG9-Pr^j2hFHGp7?SITJ#FiCDI#;d_+Dvj%Mx`N*s#R+YtT^sT}{Tw8nS z(4k#^0JM!BZ_v^JxIq#K#BRuFlm<3JjU&Tf$GF^WE2&T7QG%%0AA@a;mI(_kO@5f?vT_%Fx>9c_u#Oj%11 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 diff --git a/joss/uq-framework.png b/joss/uq-framework.png deleted file mode 100644 index ac33241305797d63ccacd84a1342ff9849c44ce0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24594 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 From 72a3a1f1e0e27d4fa12652ea59ad265972caecd4 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Thu, 24 Aug 2023 12:10:19 +0200 Subject: [PATCH 66/73] Readd JOSS paper to the branch. --- joss/paper.bib | 257 ++++++++++++++++++++++++++++++ joss/paper.md | 362 ++++++++++++++++++++++++++++++++++++++++++ joss/reliability.png | Bin 0 -> 480951 bytes joss/uq-framework.png | Bin 0 -> 24594 bytes 4 files changed, 619 insertions(+) create mode 100644 joss/paper.bib create mode 100644 joss/paper.md create mode 100644 joss/reliability.png create mode 100644 joss/uq-framework.png diff --git a/joss/paper.bib b/joss/paper.bib new file mode 100644 index 0000000..ad74579 --- /dev/null +++ b/joss/paper.bib @@ -0,0 +1,257 @@ +@misc{VLSE:2013, + author = {Surjanovic, S. and Bingham, D.}, + title = {Virtual Library of Simulation Experiments: Test Functions and Datasets}, + year = 2013, + howpublished = {Retrieved June 30, 2023, from \url{http://www.sfu.ca/~ssurjano}} +} + +@Misc{GdR:2008, + author = {{GdR MASCOT-NUM}}, + howpublished = {Retrieved June 30, 2023, from \url{https://www.gdr-mascotnum.fr/benchmarks.html}}, + title = {Benchmark proposals of {GdR}}, + year = {2008}, +} + +@misc{UQWorld:2019, + author = {UQWorld}, + title = {Benchmark page of UQWorld, the applied uncertainty quantification community}, + year = 2019, + howpublished = {Retrieved June 30, 2023, from \url{https://uqworld.org/c/uq-with-uqlab/benchmarks}} +} + +@misc{Rozsas:2019, + author = {Árpád Rózsás and Arthur Slobbe}, + title = {Repository and Black-box Reliability Challenge 2019}, + year = 2019, + howpublished = {Retrieved June 30, 2023, from \url{https://gitlab.com/rozsasarpi/rprepo/}} +} + +@InProceedings{Fekhari2021, + author = {Elias Fekhari and Michaël Baudin and Vincent Chabridon and Youssef Jebroun}, + booktitle = {4th International Conference on Uncertainty Quantification in Computational Sciences and Engineering}, + title = {{otbenchmark}: An open source {Python} package for benchmarking and validating uncertainty quantification algorithms}, + year = {2021}, + doi = {10.7712/120221.8034.19093}, + location = {Athens, Greece}, +} + +@Misc{Jakeman2019, + author = {John D. Jakeman}, + title = {{PyApprox}}, + year = {2019}, + journal = {GitHub repository}, + publisher = {GitHub}, + url = {https://github.com/sandialabs/pyapprox}, +} + +@Misc{Baudin2021, + author = {Michaël Baudin and Youssef Jebroun and Elias Fekhari and Vincent Chabridon}, + title = {{otbenchmark}}, + year = {2021}, + journal = {GitHub repository}, + publisher = {GitHub}, + url = {https://github.com/mbaudin47/otbenchmark}, +} + +@Misc{Bouhlel2023, + author = {Mohamed Amine Bouhlel and John Hwang and Nathalie Bartoli and Rémi Lafage and Joseph Morlier and Joaquim Martins}, + title = {{Surrogate Modeling Toolbox}}, + year = {2023}, + journal = {GitHub repository}, + publisher = {GitHub}, + url = {https://github.com/SMTorg/smt}, +} + +@InCollection{Baudin2017, + author = {Michaël Baudin and Anne Dutfoy and Bertrand Iooss and Anne-Laure Popelin}, + booktitle = {Handbook of Uncertainty Quantification}, + publisher = {Springer International Publishing}, + title = {{OpenTURNS}: An industrial software for uncertainty quantification in simulation}, + year = {2017}, + pages = {2001--2038}, + doi = {10.1007/978-3-319-12385-1_64}, +} + +@InProceedings{Sudret2017, + author = {Bruno Sudret and Stefano Marelli and Joe Wiart}, + booktitle = {2017 11th European Conference on Antennas and Propagation ({EUCAP})}, + title = {Surrogate models for uncertainty quantification: An overview}, + year = {2017}, + publisher = {{IEEE}}, + doi = {10.23919/eucap.2017.7928679}, +} + +@InProceedings{Sudret2012, + author = {Bruno Sudret}, + booktitle = {Proceedings of the 5th Asian-Pacific Symposium on Structural Reliability and its Applications}, + title = {Meta-models for structural reliability and uncertainty quantification}, + year = {2012}, + doi = {10.3850/978-981-07-2219-7_p321}, +} + +@InCollection{Iooss2015, + author = {Bertrand Iooss and Paul Lema{\^{\i}}tre}, + booktitle = {Uncertainty Management in Simulation-Optimization of Complex Systems}, + publisher = {Springer {US}}, + title = {A review on global sensitivity analysis methods}, + year = {2015}, + pages = {101--122}, + doi = {10.1007/978-1-4899-7547-8_5}, +} + +@PhdThesis{Sudret2007, + author = {Bruno Sudret}, + school = {Université Blaise Pascal - Clermont, France}, + title = {Uncertainty propagation and sensitivity analysis in mechanical models \textemdash {Contributions} to structural reliability and stochastic spectral methods.}, + year = {2007}, + address = {Clermont-Ferrand, France}, + type = {habilitation thesis}, + abstract = {This thesis is a contribution to the resolution of the reliability-based design optimization problem. This probabilistic design approach is aimed at considering the uncertainty attached to the system of interest in order to provide optimal and safe solutions. The safety level is quantified in the form of a probability of failure. Then, the optimization problem consists in ensuring that this failure probability remains less than a threshold specified by the stakeholders. The resolution of this problem requires a high number of calls to the limit-state design function underlying the reliability analysis. Hence it becomes cumbersome when the limit-state function involves an expensive-to-evaluate numerical model (e.g. a finite element model). In this context, this manuscript proposes a surrogate-based strategy where the limit-state function is progressively replaced by a Kriging meta-model. A special interest has been given to quantifying, reducing and eventually eliminating the error introduced by the use of this meta-model instead of the original model. The proposed methodology is applied to the design of geometrically imperfect shells prone to buckling.}, +} + +@InCollection{Verma2015, + author = {Ajit Kumar Verma and Srividya Ajit and Durga Rao Karanki}, + booktitle = {Springer Series in Reliability Engineering}, + publisher = {Springer London}, + title = {Structural reliability}, + year = {2015}, + pages = {257--292}, + doi = {10.1007/978-1-4471-6269-8_8}, +} + +@Article{Li2018, + author = {Xu Li and Chunlin Gong and Liangxian Gu and Wenkun Gao and Zhao Jing and Hua Su}, + journal = {Structural Safety}, + title = {A sequential surrogate method for reliability analysis based on radial basis function}, + year = {2018}, + pages = {42--53}, + volume = {73}, + doi = {10.1016/j.strusafe.2018.02.005}, +} + +@TechReport{Harper1983, + author = {Harper, William V. and Gupta, Sumant K.}, + title = {Sensitivity/uncertainty analysis of a borehole scenario comparing latin hypercube sampling and deterministic sensitivity approaches}, + year = {1983}, + address = {Columbus, Ohio}, + number = {{BMI}/{ONWI}-516}, + school = {Office of Nuclear Waste Isolation, Battelle Memorial Institute}, + url = {https://inldigitallibrary.inl.gov/PRR/84393.pdf}, +} + +@Book{Saltelli2007, + author = {Andrea Saltelli and Marco Ratto and Terry Andres and Francesca Campolongo and Jessica Cariboni and Debora Gatelli and Michaela Saisana and Stefano Tarantola}, + publisher = {Wiley}, + title = {Global sensitivity analysis. The primer}, + year = {2007}, + doi = {10.1002/9780470725184}, +} + +@Article{Bouhlel2019, + author = {Bouhlel, Mohamed Amine and Hwang, John T. and Bartoli, Nathalie and Lafage, R{\'{e}}mi and Morlier, Joseph and Martins, Joaquim R. R. A.}, + journal = {Advances in Engineering Software}, + title = {A {Python} surrogate modeling framework with derivatives}, + year = {2019}, + pages = {102662}, + volume = {135}, + doi = {10.1016/j.advengsoft.2019.03.005}, + publisher = {Elsevier {BV}}, +} + +@Article{Saltelli1995, + author = {Saltelli, Andrea and Sobol', Ilya M.}, + journal = {Reliability Engineering \& System Safety}, + title = {About the use of rank transformation in sensitivity analysis of model output}, + year = {1995}, + number = {3}, + pages = {225--239}, + volume = {50}, + doi = {10.1016/0951-8320(95)00099-2}, +} + +@Article{Herman2017, + author = {Jon Herman and Will Usher}, + journal = {The Journal of Open Source Software}, + title = {{SALib}: An open-source python library for sensitivity analysis}, + year = {2017}, + number = {9}, + pages = {97}, + volume = {2}, + doi = {10.21105/joss.00097}, +} + +@Article{Iwanaga2022, + author = {Takuya Iwanaga and William Usher and Jonathan Herman}, + journal = {Socio-Environmental Systems Modelling}, + title = {Toward {SALib} 2.0: Advancing the accessibility and interpretability of global sensitivity analyses}, + year = {2022}, + pages = {18155}, + volume = {4}, + doi = {10.18174/sesmo.18155}, +} + +@Article{Eck2015, + author = {Vinzenz Gregor Eck and Wouter Paulus Donders and Jacob Sturdy and Jonathan Feinberg and Tammo Delhaas and Leif Rune Hellevik and Wouter Huberts}, + journal = {International Journal for Numerical Methods in Biomedical Engineering}, + title = {A guide to uncertainty quantification and sensitivity analysis for cardiovascular applications}, + year = {2015}, + number = {8}, + volume = {32}, + doi = {10.1002/cnm.2755}, +} + +@Article{Wicaksono2016, + author = {Damar Wicaksono and Omar Zerkak and Andreas Pautz}, + journal = {Nuclear Science and Engineering}, + title = {Global sensitivity analysis of transient code output applied to a reflood experiment model using the {TRACE} code}, + year = {2016}, + number = {3}, + pages = {400--429}, + volume = {184}, + doi = {10.13182/nse16-37}, +} + +@Article{Adelmann2019, + author = {Andreas Adelmann}, + journal = {{SIAM}/{ASA} Journal on Uncertainty Quantification}, + title = {On nonintrusive uncertainty quantification and surrogate model construction in particle accelerator modeling}, + year = {2019}, + number = {2}, + pages = {383--416}, + volume = {7}, + doi = {10.1137/16m1061928}, +} + +@Article{Castellon2023, + author = {Dario Fernandez Castellon and Aksel Fenerci and {\O}yvind Wiig Petersen and Ole {\O}iseth}, + journal = {Reliability Engineering {\&}amp$\mathsemicolon$ System Safety}, + title = {Full long-term buffeting analysis of suspension bridges using {Gaussian} process surrogate modelling and importance sampling {Monte Carlo} simulations}, + year = {2023}, + volume = {235}, + doi = {10.1016/j.ress.2023.109211}, +} + +@Article{Virtanen2020, + author = {Pauli Virtanen and Ralf Gommers and Travis E. Oliphant and Matt Haberland and Tyler Reddy and David Cournapeau and Evgeni Burovski and Pearu Peterson and Warren Weckesser and Jonathan Bright and St{\'{e}}fan J. van der Walt and Matthew Brett and Joshua Wilson and K. Jarrod Millman and Nikolay Mayorov and Andrew R. J. Nelson and Eric Jones and Robert Kern and Eric Larson and C J Carey and {\.{I}}lhan Polat and Yu Feng and Eric W. Moore and Jake VanderPlas and Denis Laxalde and Josef Perktold and Robert Cimrman and Ian Henriksen and E. A. Quintero and Charles R. Harris and Anne M. Archibald and Ant{\^{o}}nio H. Ribeiro and Fabian Pedregosa and Paul van Mulbregt and Aditya Vijaykumar and Alessandro Pietro Bardelli and Alex Rothberg and Andreas Hilboll and Andreas Kloeckner and Anthony Scopatz and Antony Lee and Ariel Rokem and C. Nathan Woods and Chad Fulton and Charles Masson and Christian Häggström and Clark Fitzgerald and David A. Nicholson and David R. Hagen and Dmitrii V. Pasechnik and Emanuele Olivetti and Eric Martin and Eric Wieser and Fabrice Silva and Felix Lenders and Florian Wilhelm and G. Young and Gavin A. Price and Gert-Ludwig Ingold and Gregory E. Allen and Gregory R. Lee and Herv{\'{e}} Audren and Irvin Probst and Jörg P. Dietrich and Jacob Silterra and James T Webber and Janko Slavi{\v{c}} and Joel Nothman and Johannes Buchner and Johannes Kulick and Johannes L. Schönberger and Jos{\'{e}} Vin{\'{\i}}cius de Miranda Cardoso and Joscha Reimer and Joseph Harrington and Juan Luis Cano Rodr{\'{\i}}guez and Juan Nunez-Iglesias and Justin Kuczynski and Kevin Tritz and Martin Thoma and Matthew Newville and Matthias Kümmerer and Maximilian Bolingbroke and Michael Tartre and Mikhail Pak and Nathaniel J. Smith and Nikolai Nowaczyk and Nikolay Shebanov and Oleksandr Pavlyk and Per A. Brodtkorb and Perry Lee and Robert T. McGibbon and Roman Feldbauer and Sam Lewis and Sam Tygier and Scott Sievert and Sebastiano Vigna and Stefan Peterson and Surhud More and Tadeusz Pudlik and Takuya Oshima and Thomas J. Pingel and Thomas P. Robitaille and Thomas Spura and Thouis R. Jones and Tim Cera and Tim Leslie and Tiziano Zito and Tom Krauss and Utkarsh Upadhyay and Yaroslav O. Halchenko and Yoshiki V{\'{a}}zquez-Baeza and}, + journal = {Nature Methods}, + title = {{SciPy} 1.0: fundamental algorithms for scientific computing in {Python}}, + year = {2020}, + number = {3}, + pages = {261--272}, + volume = {17}, + doi = {10.1038/s41592-019-0686-2}, + publisher = {Springer Science and Business Media {LLC}}, +} + +@Article{Harris2020, + author = {Charles R. Harris and K. Jarrod Millman and St{\'{e}}fan J. van der Walt and Ralf Gommers and Pauli Virtanen and David Cournapeau and Eric Wieser and Julian Taylor and Sebastian Berg and Nathaniel J. Smith and Robert Kern and Matti Picus and Stephan Hoyer and Marten H. van Kerkwijk and Matthew Brett and Allan Haldane and Jaime Fern{\'{a}}ndez del R{\'{\i}}o and Mark Wiebe and Pearu Peterson and Pierre G{\'{e}}rard-Marchant and Kevin Sheppard and Tyler Reddy and Warren Weckesser and Hameer Abbasi and Christoph Gohlke and Travis E. Oliphant}, + journal = {Nature}, + title = {Array programming with {NumPy}}, + year = {2020}, + number = {7825}, + pages = {357--362}, + volume = {585}, + doi = {10.1038/s41586-020-2649-2}, +} + +@Comment{jabref-meta: databaseType:bibtex;} diff --git a/joss/paper.md b/joss/paper.md new file mode 100644 index 0000000..27469b9 --- /dev/null +++ b/joss/paper.md @@ -0,0 +1,362 @@ +--- +title: 'UQTestFuns: A Python3 library of uncertainty quantification (UQ) test functions' +tags: + - Python + - test functions + - benchmark + - uncertainty quantification + - metamodeling + - surrogate modeling + - sensitivity analysis + - reliability analysis + - rare event estimation +authors: + - name: Damar Wicaksono + orcid: 0000-0001-8587-7730 + affiliation: 1 # (Multiple affiliations must be quoted) + corresponding: true + - name: Michael Hecht + orcid: 0000-0001-9214-8253 + affiliation: 1 +affiliations: + - name: Center for Advanced Systems Understanding (CASUS) - Helmholtz-Zentrum Dresden-Rossendorf (HZDR), Germany + index: 1 +date: 30 June 2023 +bibliography: paper.bib +--- + +# Summary + +Researchers are continuously developing novel methods and algorithms +in the field of applied uncertainty quantification (UQ). +During the development phase of a novel method or algorithm, +researchers and developers often rely on test functions +taken from the literature for validation purposes. +Afterward, they employ these test functions as a common ground +to compare the performance of the novel method +against that of the state-of-the-art methods +in terms of accuracy and efficiency measures. + +`UQTestFuns` is an open-source Python3 library of test functions +commonly used within the applied UQ community. +Specifically, the package provides: + +- an **implementation with minimal dependencies** + (i.e., NumPy and SciPy) **and a common interface** of many test functions + available in the UQ literature +- a **single entry point** collecting test functions _and_ + their probabilistic input specifications in a single Python package +- an **opportunity for an open-source contribution**, supporting + the implementation of new test functions and posting reference results. + +`UQTestFuns` aims to save the researchers' and developers' time +from having to reimplement many of the commonly used test functions +themselves. + +# Statement of need + +The field of uncertainty quantification (UQ) in applied science and engineering +has grown rapidly in recent years. +Novel methods and algorithms for metamodeling (surrogate modeling), +reliability, and sensitivity analysis are being continuously developed. +While such methods are aimed at addressing real-world problems, +often involving large-scale complex computer models—from nuclear +[@Wicaksono2016] to civil engineering [@Castellon2023], +from physics [@Adelmann2019] to biomedicine [@Eck2015]—researchers +and developers may prefer to use the so-called UQ test functions +for validation and benchmarking purposes. + +UQ test functions are mathematical functions taken as black boxes; +they take a set of input values and produce output values. +In a typical UQ analysis, the input variables are considered _uncertain_ +and thus modeled probabilistically. +The results of a UQ analysis, in general, depend not only on the computational +model under consideration but also on the specification of the input variables. +Consequently, a UQ test function consists of both the specification +of the function as well as probabilistic distribution specification +of the inputs. + +UQ test functions are widely used in the community for several reasons: + +- Test functions are _fast to evaluate_, + at least _faster_ than their real-world counterparts. +- There are many test functions _available in the literature_ + for various types of analyses. +- Although test functions are taken as black boxes, _their features are known_; + this knowledge enables a thorough diagnosis of a UQ method. +- Test functions provide _a common ground_ for comparing the performance + of various UQ methods in solving the same class of problems. + +Several efforts have been made to provide relevant UQ test functions +to the community. +For instance, researchers may refer to the following online resources +to obtain UQ test functions (the list is by no means exhaustive): + +- The Virtual Library of Simulation Experiments (VLSE) [@VLSE:2013]: This site + is arguably the definitive repository for (but not exclusively) UQ test + functions. It provides over a hundred test functions + for numerous applications; each test function is described + on a dedicated page that includes implementations in MATLAB and R. +- The Benchmark proposals of GdR [@GdR:2008]: The site provides a series of + documents that contain test function specifications. +- The Benchmark page of UQWorld [@UQWorld:2019]: This community site provides + a selection of test functions for metamodeling, sensitivity analysis, + and reliability analysis exercises along with their implementation in MATLAB. +- RPrepo—a reliability problems repository [@Rozsas:2019]: This + repository contains numerous reliability analysis test functions implemented + in Python. It is not, however, a stand-alone Python package. + +Using these online resources, one either needs to download each test function +separately[^rprepo] or implement the functions following +the provided formula (in the programming language of choice). + +As an alternative way for obtaining test functions, +UQ analysis packages are often shipped with a selection of test functions +of their own, either for illustration, validation, or benchmarking purposes. +Examples from the applied UQ community in the Python ecosystem are +(the numbers are as of 2023-06-30; once again, the list is non-exhaustive): + +- SALib [@Herman2017; @Iwanaga2022]: Six test functions + mainly for illustrating the package capabilities in the examples. +- PyApprox [@Jakeman2019]: 18 test functions, + including some non-algebraic functions for benchmarking purposes. +- Surrogate Modeling Toolbox (SMT) [@Bouhlel2019; @Bouhlel2023]: + 11 analytical and engineering problems for benchmarking purposes. +- OpenTURNS [@Baudin2017]: 37 test functions packaged separately + as `otbenchmark` [@Fekhari2021; @Baudin2021] for benchmarking purposes. + +These open-source packages already provide a wide variety of functions +implemented in Python. Except for `otbenchmark`, the problem is that +these functions are part of the respective package. To get access to +the test functions belonging to a package, the whole analysis package +must be installed first. Furthermore, test functions from a given package are +often implemented in such a way that is tightly coupled with the package +itself. To use or extend the test functions belonging to an analysis package, +one may need to first learn some basic usage and specific terminologies +of the package. + +`UQTestFuns` aims to solve this problem by collecting UQ test functions +into a single Python package +with a few dependencies (i.e., NumPy [@Harris2020] +and SciPy [@Virtanen2020]). +The package enables researchers +to conveniently access commonly used UQ test functions implemented in Python. +Thanks to a common interface, +researchers can use the available test functions +and extend the package with new test functions with minimal overhead. + +Regarding its aim, `UQTestFuns` is mostly comparable +to the package `otbenchmark`. +Both also acknowledge the particularity of UQ test functions that requires +combining a test function and the corresponding probabilistic input +specification. +There are, however, some major differences: + +- One of the `otbenchmark`'s main aims is to provide the OpenTURNS development + team with a tool for helping with the implementation of new algorithms. + As such, it is built on top of and coupled to OpenTURNS. + `UQTestFuns`, on the other hand, has fewer dependencies and is leaner + in its implementations; + it is more agnostic with respect to any particular UQ analysis package. +- `UQTestFuns` is more modest in its scope, that is, simply to provide + a library of UQ test functions implemented in Python + with a consistent interface and an online reference + (similar to that of VLSE [@VLSE:2013]), + and not, as in the case of `otbenchmark`, + an automated benchmark framework[^benchmark] [@Fekhari2021]. + +# Package overview + +Consider a computational model that is represented as an $M$-dimensional +black-box function: +$$ +\mathcal{M}: \mathbf{x} \in \mathcal{D}_{\mathbf{X}} \subseteq \mathbb{R}^M \mapsto y = \mathcal{M}(\mathbf{x}), +$$ +where $\mathcal{D}_{\mathbf{X}}$ and $y$ denote the input domain +and the quantity of interest (QoI), respectively. + +In practice, the exact values of the input variables are not exactly known +and may be considered uncertain. +The ensuing analysis involving uncertain input variables can be formalized +in the uncertainty quantification (UQ) framework following @Sudret2007 +as illustrated in \autoref{fig:uq-framework}. + +![Uncertainty quantification (UQ) framework, adapted from @Sudret2007.\label{fig:uq-framework}](uq-framework.png){ width=70% } + +The framework starts from the center, +with the computational model $\mathcal{M}$ taken as a black box +as defined above. +Then it moves on to the probabilistic modeling +of the (uncertain) input variables. +Under the probabilistic modeling, +the uncertain input variables are replaced by a random vector equipped +with a joint probability density function (PDF) +$f_{\mathbf{X}}: \mathbf{x} \in \mathcal{D}_{\mathbf{X}} \subseteq \mathbb{R}^M \mapsto \mathbb{R}$. + +Afterward, the uncertainties of the input variables are propagated through +the computational model $\mathcal{M}$. +The quantity of interest $y$ now becomes a random variable: +$$ +Y = \mathcal{M}(\mathbf{X}),\; \mathbf{X} \sim f_{\mathbf{X}}. +$$ +This leads to various downstream analyses such as _reliability analysis_, +_sensitivity analysis_, +and _metamodeling_ (or _surrogate modeling_). +In `UQTestFuns`, these are currently the three main classifications +of UQ test functions by their applications in the literature[^classifications]. + +## Reliability analysis + +To illustrate the test functions included in `UQTestFuns`, +consider the circular pipe crack reliability problem, a $2$-dimensional +function for testing reliability analysis methods [@Verma2015; @Li2018]: +$$ +g(\mathbf{x}; \mathbf{p}) = \mathcal{M}(\mathbf{x}; t, R) - M = 4 t \sigma_f R^2 \left( \cos{\left(\frac{\theta}{2}\right)} - \frac{1}{2} \sin{(\theta)} \right) - M, +$$ +where $\mathbf{x} = \{ \sigma_f, \theta \}$ is the two-dimensional vector +of input variables probabilistically defined further below; +and $\mathbf{p} = \{ t, R, M \}$ is the vector of (deterministic) parameters. + +In a reliability analysis problem, +a computational model $\mathcal{M}$ is often combined +with another set of parameters (either uncertain or deterministic) +to define the so-called _performance function_ or _limit-state function_ +of a system denoted by $g$. +The task for a reliability analysis method is to estimate the failure +probability of the system defined as [@Sudret2012]: +\begin{equation}\label{eq:pf} +P_f = \mathbb{P}[g(\mathbf{X}; \mathbf{p}) \leq 0] = \int_{\mathcal{D}_f = \{ \mathbf{x} | g(\mathbf{x}; \mathbf{p}) \leq 0 \}} f_{\mathbf{X}} (\mathbf{x}) \; d\mathbf{x}, +\end{equation} +where $g(\mathbf{x}; \mathbf{p}) \leq 0.0$ is defined as a _failure state_. +The difficulty of evaluating the integral above stems +from the fact that the integration domain $D_f$ is defined implicitly. + +The circular pipe crack problem can be created in `UQTestFuns` as follows: +```python +>>> import uqtestfuns as uqtf +>>> circular_pipe = uqtf.CircularPipeCrack() +``` + +The resulting instance is _callable_ and can be called +with a set of valid input values. +The probabilistic input model is an integral part of a UQ test function; +indeed, according to \autoref{eq:pf}, the analysis results depend on it. +Therefore, in `UQTestFuns`, the input model following +the original specification is always attached to the instance +of the test function: +``` +>>> print(circular_pipe.prob_input) +Name : CircularPipeCrack-Verma2015 +Spatial Dim. : 2 +Description : Input model for the circular pipe crack problem from Verma... +Marginals : + + No. Name Distribution Parameters Description +----- ------- -------------- ----------------- -------------------- + 1 sigma_f normal [301.079 14.78 ] flow stress [MNm] + 2 theta normal [0.503 0.049] half crack angle [-] + +Copulas : None +``` +This probabilistic input model instance can be used to transform +a set of values in a given domain (say, the unit hypercube $[0, 1]^M$) +to the domain of the test function. + +The limit-state surface (i.e., where $g(\mathbf{x}) = 0$) +for the circular pipe crack problem is shown in \autoref{fig:reliability} +(left plot). In the middle plot, $10^6$ random sample points are overlaid; +each point is classified whether it is in failure (red) or safe domain (blue). +The histogram (right plot) shows the proportion of points +that fall in the failure and safe domain. + +![Illustration of reliability analysis: Circular pipe crack problem.\label{fig:reliability}](reliability.png){ width=90% } + +As illustrated in the previous series of plots, +the task for a reliability analysis method is to estimate +the probability where $g(\mathbf{X}) \leq 0$ as accurately +and with as few model evaluations as possible. +`UQTestFuns` includes test functions used in reliability analysis exercises +in various dimensions having different complexities of the limit-state surface. + +## Sensitivity analysis + +As another illustration, this time in the context of sensitivity analysis, +consider the Sobol'-G function, an established sensitivity analysis +test function [@Saltelli1995] included in `UQTestFuns`: +$$ +\mathcal{M}(\mathbf{x}) = \prod_{m = 1}^M \frac{\lvert 4 x_m - 2 \rvert + a_m}{1 + a_m}, +$$ +where $\mathbf{x} = \{ x_1, \ldots, x_M \}$ is the $M$-dimensional vector +of independent uniform random variables in $[0, 1]^M$; +and $\mathbf{a} = \{ a_m = \frac{m - 1}{2.0}, m = 1, \ldots, M \}$ +is the set of (deterministic) parameters. + +Unlike the previous test function example, +the Sobol'-G test function is a variable-dimension test function +and can be defined for any given dimension. +For instance, to create a $6$-dimensional Sobol'-G function: +```python +>>> sobol_g = uqtf.SobolG(spatial_dimension=6) +``` +As before, the probabilistic input model of the function as prescribed +in the original specification is attached to the instance of the test function +(i.e., the `prob_input` property). + +The task of a sensitivity analysis method is to ascertain either qualitatively +or quantitatively the most important input variables +(for _factor prioritization_) or the least important input variables +(for _factor fixing/screening_) with as few model evaluations as possible; +for details on this topic, please refer to @Saltelli2007 and @Iooss2015. +`UQTestFuns` includes test functions used in sensitivity analysis exercises +in various dimensions having different complexities +in terms of the interactions between input variables. + +## Metamodeling + +In practice, the computational model $\mathcal{M}$ is often complex. +Because a UQ analysis typically involves evaluating $\mathcal{M}$ +numerous times ($\sim 10^2$ — $10^6$), +the analysis may become intractable if $\mathcal{M}$ is expensive to evaluate. +As a consequence, in many UQ analyses, a metamodel (surrogate model) +is employed. +Based on a limited number of model ($\mathcal{M}$) evaluations, +such a metamodel should be able to capture the most important aspects +of the input/output mapping but having much less cost per evaluation; +it can, therefore, be used to replace $\mathcal{M}$ in the analysis. + +While not a goal of UQ analyses per se, +metamodeling is nowadays an indispensable component +of the UQ framework [@Sudret2017]. +`UQTestFuns` also includes test functions from the literature that are used +as test functions in a metamodeling exercise. + +## Documentation + +The online documentation of `UQTestFuns` is an important aspect of the project. +It includes a detailed description of each of the available UQ test functions, +their references, and when applicable, published results of a UQ analysis +conducted using the test function. +Guides on how to add additional test functions as well as +to update the documentation are also available. + +The package documentation is available +on the `UQTestFuns` [readthedocs](https://uqtestfuns.readthedocs.io). + +# Acknowledgements + +This work was partly funded by the Center for Advanced Systems Understanding +(CASUS) that is financed by Germany's Federal Ministry of Education +and Research (BMBF) and by the Saxony Ministry for Science, Culture +and Tourism (SMWK) +with tax funds on the basis of a budget approved +by the Saxony State Parliament. + +# References + +[^rprepo]: except for RPrepo, which allows for downloading the whole repository. + +[^benchmark]: A fully functional benchmark suite may, however, be in the future +built on top of `UQTestFuns`. + +[^classifications]: The classifications are not mutually exclusive; +a given UQ test function may be applied in several contexts. diff --git a/joss/reliability.png b/joss/reliability.png new file mode 100644 index 0000000000000000000000000000000000000000..f46e39831f5f1a5b5ed50f71c4cb155550adbbff GIT binary patch literal 480951 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

dFb2w`~y%Os`LA#LtqU_-su6SGxei`b@$vt zh4yvxrSR)9q0$*-AcP__Q8k5FhYQ>$XrM#E^M4~*Swl}#tr1< zg!(<11LWW%kw|=MDM2Bp2XV7t+hNcSsmCW!1nMZB|0NB6CD=g#9W+T=js%j!Q)>yf5Q^W@DcoKQoXG+ zqgaygYf+Fyp>hG5Wl<2?dnlGo6v%jYO6;IM&EeXA2Vlyd-;Hb#hI!e<%P4kvw>25* z_dzcG)UBa+{>J9UrX}-LW@E#@5->PC!BP%Pg?5L?&(ez@dEW3a zYK0P{Cm?TxIQp$Jk_2`9d-UQ^q1T(IT(&{H)M?qA+HtY-1W!q0)<{n8%NcwGIE|t*V}HCDXDR)P@qP5~HJL;h`zl?Ro_8Oi_D#Jw(6hHM zk1;0+NdS@`0=}E!GGt@p;1adinCq0TRN4_!=K7kliJphP7mUWcxak`%_adnWwLks8 zdTaJAlkTfCmx`@ysl~>CmUK=ngd<)i<%hqWxQ=9k2Dhxb-ZV7ul(eVV8`n=^EchU- zT3y+MHc<22Bv!g!+0sL4FM;XcmLqQXKRIG{{mp0LUt(5k`Ym-$tGsEyUu@a$F&?;~ zb)hoxd^$PHfrhdzf^Oc0iludxvS&r3ox?;Y{ygG7DnV82fkc8QPF$WVMr^M}m`b#Z zg);x6QZ?3tuRJ0h{{d<(EnQ!0=yzBb|6p&<^e3kzpJ9+DFU5L(dQY(r{)f?KS4Ny7 zh+wn;vp9GVxnI1ji=9H4O-^C*%i~#b?&_0o3s1LZAv4XyV5S{{IHi9Jb1O4Pg+LYF z^N-E@B)&a=iOA@@sUJ6ppKr~ECfV6pMPNy-ZDlyhroPx-!_`gwOb#`# zsYDMd08xW2A4xV5u^TGo`txj?|9ASE{>?p;a1t~3my&5EnOM_{t#vVeWXDOp${3rl z>jECDfP_!3dlK%Y*FO8SgA!_ejqq+s2%r2t95n94dm#beS4p2ys+eCEX7DRLf0`ru zN>rlQ8G(9Fhr;d>N~xVM9l-ap+=kqi@g>|&@lAw~5MGw9_Ko^S1iWX$>QWoNHcO2!-+r>wmbv&eIzikYC7Y5QBJi zFwKxAm)5n1_hf^-%!+>E-V$y2zxG^w1p4g@uZa*SJ%_PdNZejA`zd>kbum?qM_T&5d-wK02=}@1ay>+782cji1ar$1siTyPU#ww6W0_E z)b$R`3PQmn4aN+h9OXby#74N^0;sGOm@{KmOQyH86$_rS@2lsOW* zj~$hPZL?%w*Bj1^bnit+z~03dL>&DRoXy2n3s4MLzr6g~`chK<5NEmaAtMB&GQ8dw zZtcj!3-*~IJ0T2hDFN^lqO)MC?Bc~ggtp+ovR3OucA>9(u1+42+vPNWbRdYXvB|Ns zHK43ZgBn-ZDOF_Il}A^~A=sP{22wLe9;b#*!_L^46ss$`@6MiX(B)_>yGD;>0LEeq z)&Acg9-tzB2r*nJyp`L)c@s`vT6fAW0s;`pU+d!gS>{j7P~ik4+p~>!6&5;YvWCb4 zr|0h%^TjHO=B2ZI9jB~J4Q3wFSR}JOgj(Z7Vv7Qi{$YIf6+M*fm_WEpCqyrRcG$*g z{r`{xfUv%(UgF1#{_VsEZf*JsChRHm51{>3hGVy&McqayCB@s?Ef>Licc zF2^r|sa3qv6ex5RYFrV#C=ea%fh(XvrEo#61(IYZEZniYf^Wm5Dj9m?MeCz}#Ldjp zJx6~mcqw#_}bV1Zi4EL=_rG!>%!ys zU@760ech?b1s@~Jj#yC5Ohyc4WxqBp*1o@mpnty(d(!OqJ52YW_}te%p@jiqt3M2Y zPK9X`MH2MdW_wO)E-;*yG3FcFi=guTKGwU@3GfW<|8lLZV*aTOxvU>4W2)d;&UEYO zMPX6%@$4hyzsE2OJ9nyWr5)1(K#B%%_Ck@T+^+L(sfL3g%}Qc#3YF@|7e|t01H**t z^R1Wp?+sS(VvnL{Zu<1>drvldmgoF^RMiY}5Q0lJ)*6;|@S%CNR%pf5#~wdEP-la! zOFEmG9jeG3%R;B?2HrHP2~PKO{zD%79(!Fqj(K8)AMmV%d!3-eX_fK*V=VxH$t^Rb z@rt1Z?EnpEbnsR942^DRUM=-ex%!*H8@5`F8(g|d+z(tj!-YBT*}AGb-=B$HT!>FK zv*ur`g$1Z4ham>oMpXs5*K8riScLs2u@4nu`7ta|!w^H{G3rr7L`A?Z{ z2OqN6O`@tV$`ij7fcm!5EVRz5O86j>Vxao@4t9nsfVcU^TifaN__u1)rT=o8PYsxT zsMHV~dC%x--F=aWwez6gzQHPa8D`!RG6>%O2sE!Ajq^D%m&F+@bAeh>`yMGnUX4@3-64hHPKjvF9Ls7iP@SHQYP_MPH-Gq#PTX{s0sSYM~^@T!Fg)4%^csvzGU zePuOv7b&!lSbQ7`R2a1Ue&jpAzCrE<2d%ppsdLHU_>{eB&0e0tljs3e_(N5{b!FZWXqR&3%3U*VT`W^VK)L>3nj5RRZCe=@ctDVcv%h~vTUvTO(N?TnFseo!=)_cp zUBPrj4&eim)I*~A4TXRJ+eVOwEbo)K(OqQaM=~?4iscRpg68OoVg6YtukT8v7Y9RG z`^1Flw-5JcW@`nMVA^?GTbn{8zhR<&ksWHV-3gP*6RmND3r;siw;a6l;jPVO((b*# z8i-)ft)l( zS_VObmuKxUjNK_7*TV!y`hXXh)W|#17|$tyU2%kch%Om`*o(Tlx@fS+KFP@gs9FAB zJLv7enRaow-`v9{dhRmR{6(xTjmC$t(#`@c%i1VS?kx?08S|AeNbU&z=3Lq|1f9p4MuN(lIjPeRpZ|XAKQK7p%@@>p z16tIZQdgE>=E=%$BT?(5y%gY&KGp`C^ra`?pgq*4|RBF77hIT{XaG?-%zlt75if%_m(%(J~z9_ z8>UF__9Q(b`V478vj{|AT0&dB9_g_9GEs;Fq5neP-A7a;%Bi2FqNF{h$Oj4%a&Ifw zJr_I~=QaDq{o8#OlfDv{$`D0fRQQcX;+TGZeB1#aELN4CT~VSscgNZqukO37Y2f(f zl%P%L+asbjI)8;#V9jbHD!FB59>GM0lO`?6%00r?uzn65x)zmG3X!ulMV0r;`#mpO zM+zbsmrZ+f+4xjig_+~M&B~-PJ}FcnFZXzUCGmVOF=WIUGqpfxpRw{yz@+1GXjPd5 zcVz&v?aUM)aZet+mQ*%d|5jxh@`05e|*!;^a;ZliOH=7^ZBkXNdhv zVPc`Vp*LVhhf%i}IiDt*L;p#FH7ekI64V5P84&oC#(Go!R8!TiV_ZbztwqG(vy7|K;#V&?(WYdV)SSDV4I!06%EFUv(SWm(5&yJV*ST7(IItX6uxnIac0#`z}_*lZd!S_4%do|*A$u9ow4(F}ViprZ^_UkTo=d0+! z2Q5sXwF1DBOL?}!_~1cgQB6&I!Pp7HkfZ0&OyaoOGtog9O&l!0Us{-1z{p^dCrX;s zY3eZeh`S2*V6-<5E%3XMXzOV2P^WHDJ{L8*+eIJbYBy^e@)ecW-Z(I~JRM7+7SF|kbE-m1jhp~JrhgkqMxRBz_n=ce#Q3{f5@ zwe|EEB&l-g`72eDcqkLKj_ArZbkjdan+k>YRYfz!U}d2h$FCqO5Cc|T7-VkUgZ0H2 z2Jv)#*da?=$A}6(nzm^3iJ4mQ$lqT$ScS7xU|FmDi4|^~-waRwit+ zp6^Th-Nf%)BBFXyzBp4ddr1`(c4H@sp{NSXXPZbeGyqb7rwCIa5X!8{oOx#^2;Nmx zk}csbyYnbSEX^YT&$-@BA#u*31{qA)NA z#ZYcois1^W2pf#H4J#YlZYJ-f<>h5GM>8oc?M|Lcx#vQFzFqMtG~B6tREIp(hX%YO z*C$TMZyo%e8XqFy{ar;6++}@()bLYHlM(3aH<49pS9_-Hq0=2UMnhD#Wr*@-JL0SK z_pk6f(fr~Ej>NjT%xpEf8T;2((QNX>ME}A&=<=V&Ez5*Dg4D6OU8`gDbVq&=W-Hnq zI&{JjMr~1~#T20Pl=Y2-)#X78{-Dnyglng(Y2J5^Ef3b!m1ao{Voel_2y}$-KH!0L zv2JM`o6uXO$pTG}K#QgL#?T(Gm#+0v}uSu2L~i`_}+ z8&xlh)Pf6K7Wm=>PMF-5%{B#kKO{XXi=P9U;lgo0VS@KPdE)2&9LG49J}aGXq@oEv zu=??04Od!)TZFx}K}khiT2A%NHqJ3oL~bE!uBk*7CeF#wmKw36v?so9-BziUAMWqQ z!&uJ%`F#>xvrAs#Bau&^egZ>(2!=m7&?j(#q@M5emFEZGL=HI4-FUaqp^tx)8rN-8 zk6Z>C(&ja;8Gomx4xMQ#3_Gh*=8nzpDkEqjkFjKocjJ1%N1MlaXK*Z0f==6obt&; zVcL`m>uKd*M}v+wCF+Lsq^e8hC7&g&HgMwK_RxMsRUW9?14L#6T8LdlUo`R?_9ZS+ zzrTK|is^&Yq=};*qPl(`3IU;jpuB_g6zV-`8a3L{-d?GtZ3vWFnKeoFl=5trDRISf zG{b8Dqsi?U)$*RC<*lycTjAyZlWTe>o2#%S+eR32^eooS%&_7|kN zUG^r<{tHX~m+;@IYQ-m73=Z+%3QKXQE*$+MF$dz+Y|OFsMo+&M+~wlL-xLuo+?@F+(NUO zB;jz<#o!t#vXbm_GbWBQx&rI@-fue^S9u0z8ZyC{P9+1kJ{S7LG@(YRL+Ub1xA(9L z^B{xMC|eH`4XG=mvK9>&C(b(zesYHqS)v|giHRqW)jD$i_NCn^-JZywNKU;o%@Y}o z_w|7yVy_sIGqGRVu3~AVz5~tL4m_ioV$&M;uhD!yy;%mYJZSZy-J5PU{Duc84oFo{ zIAXy6bHAI4^LV-GE%~j)IrGNCkQKdfthzH9l$?Cf&0+L(C>bh8j+`fmA?cIe?@Ub{ zUJISAg9~;&VsA*v7PP}jEcFc=1ei?!xrqpr;_+nQtS1H$0RTXpO|JQx46Ys{PZU1E z+R7Kf)VEHML9ZqtIIcq|6aw78j9RNO1WCv$&ecgcf!ObRE7WRTYV|FX^BN7Iu2QOD z3OoL>{63EX>W=+VzoX%RoiO}zpp~MIrpulmO8u`(1iG z8m_Xd-_=bQ#@l10jC6I6!jMRhKU~=XNf0!zdk$;(b(d`Ch;)NhPktK%n6Rb2vLvL@ zX0MzppI=7hD#gE-1XFJQki!_#5yB75eCypLU^{`Hb&m(><(Mx;1Qks3J>O%w+eg-& zwm9MUQZ=p5qAPK+rA_HPv1Bgcr0GShG?Kh+yZ`?X_7-4Kt=;=D=h%S>q9|Y?2m(qs zC?W#VDJi0KNOxGEsGx)>4Uz*xcd7^@ox{*bH$x2_-`X>tqsRC6zrKB4XM_XH?ES=w z`@YwDa6jvrKuD+;z=h~d5aRGayonq15h4nm>WUUGfGN#j(J5YWi|ljga|5&+3ujoC zHb6BPASLE`Jtx4nEf^RgtArNL+WkJfHCM<2WpCRuU`=6_A)_?%I{OeTO|{xk9?Jj> zNnvCT@t*!y2@#sP?Y<~0o5C_^Tt)C@CFBl9$rSc5vhYPBY(L9`7UY!gwt#cUhM4&VRoU%4s;(7(Mr~!PP(?FTjy{ zH|x^Y)^eF){p~p}ZtluAJiYl3Q5U!-hx%~Tj`?pbz5xTju(sA5|s?sYi?coI5be_$@mDM-zhKgxO zVE{7fG?F3l3Q+2iq0d=`c$m`;J!KVzp!Gn61rFu7`?Im@_Z_i)`ZozFCreFkx&FW! zv%IA+k||wiI<7BP+CmbVZ)~!~sL80r3o%zS4^CXzVs8G1hZBWFP3;l&<(fZAhE~L* zJ8GpS7*h%RwkzAv2=6miCWc$dbLq&__=ndf?Pc3nRBXLN8IYiGwA4bj%n>gaNefPW z6ngk0N32?4i(x$60x+=eGO(*gLWpm7oU5SXaoRvmx`m?$^X|n=(Sjnsj<(NZds83W z^{$?eHNykXj?1>2)xEbimrk=W2^kUoZTaPExsixt?*1J72BYfsR>Mc<09WDNy&k4R zE@ry_S$;TmrPEN*40XT{m@QU~V>O)&@i1ty#aEzS;}$^Xw^Cz@KQ{k`meIc{Sc-db z3A=@Bk^APY6A=nvKP#0QE#bL(miAqu9&s3izE%?@EuT_Ltpd29uU%#K0ZawbZ|tdy zdC&UZf4Yw)xFvdA(a5pa9ONznR{oz}Zp7vgMwBQNirXnAGn2ig7LbKo`hy}m-cSWC z>2D^es;GD@a$|@g&!qSI9vTWE=fxK<9C0ZxUYz1Ecu;+N^3igSgXPjnFq}DlLMn5b z)?;I?j#gwM@l(h{{QHqDz$bjXyu9vYg)8-Qj{e_k?M>UpYL&Wu#WMRcem-0}X5(r3 z?>w6kb8{hig>U*lHEclzA|xTZ=S$DFsaZBR=7Bzo>J@k4bX&c!Et~`gzQAqq+BKKa z9%5L}V)~QWs%r!6nArTRk8SI=;I*wE`H#AzP70(;Kvj{4nl@qfK>#Ijv%;6R=m8*lhDUm)gpw0xCLWXt{lGgZ-a9}5pVZ>&xNJfRg z%;_o%)&*KqaPB;G+I8`*ix3h}y5*Aw=&SqodY|RIW2CU#{qB4#7Am9D0Q(@`1=<0& z>v?n4%v{s2L@;)4Cs(j|bAYm160}B@)$4wTC+3Fpenx~eHC{oM-(mtv&k#E#&Fgzh zq#clDdBlY5Q{okczAYs{DezS_8R-ASlLFBF0Kh+JZYBrKN*bxd2~~4*OZM7%>S_hK zi1?-~ZFZNA)w^|yQ?Jv@hq@{r0H$T$rq!B;}#wMr0Pr1499s+g~6bOu;H8leVc0+_B zxCQB}dFBX`i5_;JeX7ZYoB3jr&h^8@C~;1P`ma3=Qmt!xu8d?iz!e4jpmoC_>XX2yKmSaGAC}2$7gWAh1xO2*P&IeaHvb4EzqC zV!z1mFwO=U;~BZ6B9x1ex2sGs%H%>v8GzvzmP>z%(V!_(`sXUo zwtG;IT57iN`=)|5X6>`!*f@4JE zl88udlTEp48&m=%I%dx`5q|apf)V0&${PLIAA`yM^(9kvB^pq$egfB_1R9d2$>IQg zC}}#ud#aJzb&iAT){wPk%IS=uij{$YvWDW(_Zf7r{wZCe=!3S8Oe?jJFaY_nE3%6M zIMQCntaW0OLGhOm7&pe-;d{YVwx!%XP8=*t;4tt`Gm&XMx86`w7F^oUn%uMP_c1&|j)V%8VrX4BC%3LY-EF?K0)Lo~?O#5I8aZS5UdgOLaR_|Zb^eGAMDZpya}Mn3jI8^bH#g{{;Swn=72?R4Qmo$M;$3h^kr%)pTF9aH}G z>mB^QNwd!Es%gh8gE}7?0r!pkEogc*4%WBHhmzT6(?iVPj5|~#$0QC8;x>UCbE2mx z@5W|hU#0_ICvR{j6+(d zM4F4E{sSt2LP;rLy_>Zu0I5OPS;BGGNvgqW&9>o8LVwGql$J=tLt|q4y4U{NwQCi= zo50%9cS5gSnVMiWwxyKFnVzCxqcWRiR;dRMV#kH_!qJ*}g*29(iyINXaCW)L%4|Oj z`D55EaZuxVosW;tV)@!Zv6nAjIsJs_-;`MW4HhE)*09)ot3%L-M^D-FX0Bj`KWaKKUF+XXFXx7nsM@G8$V#dgdy4k$ z=aR4uoI0u^RU&s2lk4L$evR(p-}iE~ip0t-=;$eJY%p2m8d>t;0qz_s3|&WK64uC7 zZ>hsPBA-zm5hfTcw|v0P(OqXiF8bV=E@BhExTci59DHXENqUq9yXOKu8f28_v@imz zP{RMg$y?EzH^jv3R)>Q_>pzBtX$=i3D1@<4ygqjPc#kg*0@Fr(p9@|n$EfLsWeMRu zI|qjhPRM@h!zJ0U#F4tt$#93>Yb6aGFqCmADOd7r?(6HjDJJutJ9iGH@X4!-&-)O~ zyt7DZm58gG3~?a-eTg#Lo52rhoRm5c1nwSfmV z>B>ca{rdGvUB1(Tfe>V6qe2#09gF3MMbzJtniTF#=8$%8jyBZcDJlJOS}wV|0`2x& z6E)HFPjF}e)H_A)kSk3o4=`A<5KB;!^pjQWt)jqsxWC3P5duQQEU--p4&4Gvp73Wvm&)0K_)MpAu9UC?MaO>miBi^oB1 zMaJlILu^X8hmHNpO$EJkQg!tU9dQ1J4OPuXm zT)y&4N=jN_OtC0>$?Cc~?B;S`+H#*qUjo68P9zmPr5t2ZCBiE|c8E(zg!WECdP_Un znyTHkxYdhNO7Qii5m24V=i!rP4>o1T1mk{*NQg63y}KcS>@PB=XV_%5$xnX5&Baa& zg6@ybb8;&0+qbU};BC7u^SmV3Wq(Pnid?>YSpy7Q^i%42k-emM`ho5q((iP`ls9kF z?Nn66;!j3tlN zwS)(3W*rK;c_p>28p!iNw+qJ?=Wmo0B^Bp^No7q{Q(<1(xiwNHE9Pw8VNsH6mZ#D7 zvh{najIG=avFBdxJ0gghs3#p2RJtiJOHxeTSbA~l*;{;n zT|2FKzFP5NH~bGLmo77IF5~7{<@K-B-iPVz1!!q#(8a}qiyzVc{ga_(HqMH35rvygLD%gX@DH7=D}4 z3)KSF-K}f0CuCa3A_E+?N$5863yD9*pbP}yk@uzUuly@zmQ3HoS_M#IFI+!L)3uf( zYbK%iTy4DnW=Ee6T!=L0$eYee1sTWOD!d*c>Uw|jBcUw zRHv~nI|LHNv|h&%g@tE7EKYSO0bR-Z zHBBw3v@HcgG~Jn#{5>OHCKQ18Hc44omm zNxFCo7*q=kvs&sE?{Yov2wY4|Ot)_&%* zU|Qu*@1AxzkaB!{yh6A>5>`xY7G5B6?_nF&G#wV)yB&X3JQ)U+#}6U(A6X@06a2Q2 zX(e5ZX+I}-w7ROP_lrJ#LQC26FxhDuK~*%)gT=)Nxm|!Rq5`H9 zV-c@pVD#aqS4x$&!fs?*mbGO#+Zi|U96gk6UX?U4zh=ETSlC95&H9M7LT>RaW4G%< zoik+d5cblC)?F&Uy5Ctnz~A4tMTW;1W(}oN;{0wBa;j8-i78To_Ah_tw(Qjk*1Est zh6gLf%*KWEhZa3agZR3Ax6#e0?TwQaCG0xZyeYoc z5mHpGsuS~AcNh125Z6ujJD}=vpPLj+aa8!8ich57hlDwCv}889mE5gihlEN(y3UO| z^eoi;-I5cp;}l?zEBw6Zj7;rAwXzj{_l`Wx7HSiPuFblj9aZD(oHLW=Ay+MnunMHv zzbPS+?6JM|yf?TW|Ap(I7({z&MfTH+)y!#Q0Olycgf=%fXIl?T^#GOV46yV8XS;^? zWtgbzPo;t^$D89Sd@C@RU=z!oItB*u7(S7$75#U9V2^uX3Oln5{eU?+O-{qFOv7(0 z4`ez87z9`VN#geD0(aAT5kBAeMvYN=BhBOtxwGVjbjrYi>#N!Q%lw{MT( zjlB~1Bdp&)V8uO%U`zWW&JHBEM>g@_%*@Qvz&)0nxS}pUc4WimaFIho{?j?~^2W+H zr?E*&3^I|zJ$=C>%X?31=AC}04W`_`>a3O|gAey^$Cy=Nq2%@-3dz z6E&s%xb#j<)+SN@tlYF!3To$ z{L6BQ!_5uKlaGrw6!(Z$4R_Kwuf+E&9_6tURQo5lpe00$H2 z4(2(@P6egh1jA396K?LdWUe4)wKj-gav%oeGHLxjxPdHw_#`HU)H8?O&Oo?H-`Q|Q z&Q5Tc=sgAh5AQjp3UKkxwvy%E%=RnN^vwGA+nO|l`!VvYfKUTzZ0T!Itb5H7KQWc8qp9TF!$iuN0;Mk$anDT}Ud2lqC9bqH zmQ7o>4fb6!5mWTRBJ>7XRPAJUK^6$D>~-*Hy_37#vR6xTM%{<~D03`8VTlCh<600s zb^>S>O}*;kFx9RABN)Hz#bs?{BVBxniAf5AQRTi;HyJFVX3c@fg3GeEd%F1b>w`i7 zbJ2Hp4O&xI!O-#r(!_@K-s@RUhipE+`q8*D5B4QcD%UhRb5&xfqr%_A80ylr`C z^gZEpV8H0srB>pT7|#z?ArbdQ%83M_BEQ_kG#eMIh3yVV%Y@d(KE+jYx-3oI#~@U^ z;xxG_?-}1?X&<@GLx6c#L=^%~h64&XwXsn?Z$WrtR*Xi(y?fG4?8c35-_haKWv)|} zlgcRNs(%8N|4mhc&yz)U^I8wR0PCYR7PX!gzU~OAGm?Jr+f6X?^J{=pMI2xb#HBar z+V~vU!1uZt87_>}A(L`~MI#pebbXVb^xi%;1|4ktD~qP_IGzt%uTj~?{Y9e0Cndf- z*)P-qUwAsOw{4k7_Sn9fc>li6KuLLdr?{Qm+r?3vztx<*UWaLQ++ePvRS@43=`8Ne zP`B5Wmzb0_SMl{#(VjppzIkaK7r)8o^5EIm&~H_$N&c^kwI1U6Smrsyh}w#l$Q|Zf{6{bz)VCKa87L{|Y%1ndHsu$|4)-_xfxI z5U44;lv77XN7IM&m3erOQ*(Z2$mz;4P1tw*5)#T?v6r>C&qdNS2;JL~g@m-_0GNBp z9ugE9I%!{gal5yG6o1}8Yvi;R+L;pL&pe228eF<9@|c;Js_}%!ndjjTS%W8wG1K-% zkUU?xy^~CI^`0N^pq1>+o6od1ruIp`tV%)xj3^4tyC~&v61vvfiq-}{76Kj6`l|)I z$e5^gM0S&X_NeY!zFS0Ps06F~!Z+g9b(|>}wFQ~Y6S0W~`z8InZtGWGhiFg{LM|x9 zIH}QToB1J0w@m`h89>||>*4Argy!u-6(Y;&8$vEiiBPWq2<78}!NE0LRbI`SH%d*{O@%==jM2WnyTFFaeo8%Ui5bPVX$G(seYK&gg+*%Dod1T)XvhZS z@DzJN>D~JM-GxmZ24P{HmoG<`bHNACItVcZ(o4T*hb8y%_@!C~!sWgP}GPaiLw-(O_2k@HJ# z+~;FnFpQCTdZuea+0G9S;4hkhKyv7Pyla)N7|=FIlY#QxjP(G{ZK5SXF5}GK=_(?aESN-po$oUq|8EYDz5Lk{zPYaBJRYZ!asPR*$F`daT0_N4k7Dg}eE~r>LNEz#)O2-zxHSI? zaf_0rT`XIa3*U18NbUvjMNF49dV2TA3 zu=~hvYp9jIROK<5#&(ybYRA+pO2;bg}Fg4(3zp{ z4K%owHmHDh2^EgIL*%XunytQUXM#yxD32T%UPil3yU8}=FHd&)+v*5O7S0q-N@?$C zOH@wskBKREDY-ZMGqZj5rp%APk`Ovhv+{|V6=Db85s3=J2E9U zucV}8SI31Lt6?ic!mc`9QnxivDn+$a>|+MqYXEI#zeF|X#vMNX?;arAdy8Zydo!M^ zq8bT1zR*&mI?M6O==E(B#Gkff1;$7Kh0P$_`4KSW!+ndMwr!?oDvVto`FU+}HrS%o z(()x;Omyx!3n9b#BZq}gg)XHxZJpIJma<50tmxYV^!f$y&_fZbCiXgCiWAe1XltdR z__=jBTUC>qPK;WlfJf(nhcK^;g4W_cOt>BTcLGU#8b-=9eppJz3c z9I=)eS(y*{XKG0Qigp~i5BasH2aT|+x@NA~W!^C1?Z#c@SCx|#>FYjnBq;CcABA$5 zi1%0S#()xAk*#zk<|3ubS$D`OcON_%1zNtFJiU(kNv6oUg}x8;_-~h|X;;#RY(q43 zH`8_~FZ61<{U=H#W!$q7LhLt|5^onM?(jHnM1uMM*1<7xhswO`VO6Mq{2=)g+1?wQ z-oh*$)3l@FhViBY8-A4c^Y38q{wFJ2o!$5v9#VSW&z_cLixAT~C5Udfxxu^?)|f2q zko++G_M55Bo68qDm5pAEj_$@1@kml3o$HCw>>DyaP#qNPnzwH~u9iXw328Wj$bh7y%aB0Y1@jOf z2tCCxE`Qcjnk?7kRQQy4$!g^S@<61^ITL=-^B4pTK1O|={+%kFd8v=wJd$eEm`%MM zcu;?Px{3jO%yG0Sf`Qtrl5OQ?M$%=-xYAUK8J9|WSt#;J6!sUnGzI+PgN*8YFBm&N znn{!TP^f&`Ur{Q%4aghKmJsJm%L9Dd+nZQX0TLBRpF_OG&)b|vjh_d@fSy~;>5H{9 zU3TMy+)HO#DNc$Ko2XDe9R^O@F}&tAurb&fgzVb%CoNKCEeRbrxr8$9`zB}>!8y8|7p|a8;{q++)A|4QJiB1xS7zkWb4#bL#JpV5uBWI@ zIowQwUI=TM&MsDd2aRlTGRe;OVJOx#Hk_%nLy)7nZ_BJ$%(2!6o4Xn$^!fIi(>zPO z43{odrkz5mB#+o{fbd2K4+C%^Z*YAXzwFG$QQXm|ROw$o8X{Fs_wSSYQ}C~qiT~58 za__;P17O~@eaKB^Va#rAA&EHs!j#XLV)&bTD%8e9|CU+z7bgVR~i zL;wwl{4-+F9kDw+Jen5QcBje7Bk0)gRMAxuRmAx5!=#H!Vxela`g$_a_#h1_5x?ZomCCx5!d}%|SYrC>6v+-x**!FXW;1 z2LX^y8>zL+(|QIFis$Z;9#`FfT;9|XU}cp}Rz)|{2Wt($GnZdo1gVY*hz8t$HvXv2 zD-Pu7bnAM`K0kj4o#wtyKyjHx`TbVEyw`x_M(B@Yg{}j{u{+zqG1hpbEp1qmDdshgd zb?^_p5;lc~IkLP=nfU&9AR-V&gC*w_N%^#4lIMQd=e+|9gM;-4IpR0Het&i`&e0(peSc@Nb<1i7;u6$77iZFUJxFyb3IMiWL8@m zaO>X2!8*!*aeY8VmuvJ#P zoc*2GtM?w@$)QEYRnV&NAo;58Q|oyxojQE$*-99T1G1$iWa!W5po^o;-}$K}pJ?B4kVyp<3$Zbx)WVY0un$0ik@bd64^qb#8c`^Z-*?VvnQvz^5noq;(%e?U%G_vOKWXaPX2V6i!0~5 z>-y4k7C*IAv?(st0^b5s~`^#&u7VtMLns0!fUt7y$>(L#)3RAp=hR1 znl+gD2T30!`p7Ci-WZ3z@Nj78gNE*Oe6otmH|^h(g{Wc@RCkpDvV@9~#Sgls-(;M$ zh`i#hyAS$J=%SW6@f#1^s<_#s_9;>^Q3a|I+P7MT?Jbesi=;nGGm>v0B2x9-^{T_PD zwODd7efJeDa#XduUxZ^0sDG!DsAN$q4mY_LHmu#w6gat<5!v@ zdRIWx-tDVvhgFGR9tm;9ME=i$c}Q9?J^}}~{Y19*n!U9thCc#^_WqatWkb5eM@x@a zfcnOg^K?grg`GLjLPUr?Gbg@D*x21)@&MTk?8HlvCDCs`81;Gm!jI)sNf~CK?B*zj zaVBWaKQvS*=tX&SzFK86fIqHTx<`^BTmc6isPJjqIufFh;t&_}0c!1%@^12F>*+&6 zJ$Q?_=W5qThNyPUqU&hdNKZhc{9J~DyX-;&py%-2)w9ImJKKKBOV4g+UbVBZBip4* zx74)lg1Y!1%3YM6MwCRd2`$rM&z?OyHr)XtB9$b?jrA;mEwBR>ucWgR7#QRtT7lH> zt}lRq><+$xYt^glPliaH$nJT1V4*tq`{C@V-jc2-oX=%GT)nOof9LXdweH6&VDMU? zEIpyN_D*sPX{ez4_#kfocn&WpTE=w})?vyCFdOi(cM%$zvJ{>aTOY#FZ4t^>jD6wZIlsd0*z?!HwGW zxh}}$4MM)A`051xWZcWwua&pAHi3WTHrIH$v^a#$>s6GMmCYEmva$kHq-cDz;8Nf0 zGS@HfOMG9`xqYf+jH7^&+4brJF95ssL@Zw)f`ozQlVYPZf5#k@r0mglBVeVZrL)eH z5^zR+t&ev*-6nTfKm1VSq=TJIRSs8J{*TyX{sjpy;`uKlbN6MNSDs0{7e}Phq@6Bv zjM!-r`h&s%DpT}kCIOuic&@~$VBl|iyw=A9J6MjT~- zxq@({y1F{^e6y3+VY<}iUYE49npS%T(zFT`A$U@SmMr3u#CZ|DI|t71GSDVUrp(y1 z+9fl@Z=FujH%aoQHv?P9NY}5%?_||1q`~L| z3;!Fl+baApi$B&vW&QZ+?G3|fgB-23j|1_7(TOwrp1YTd>p|Rx0<)k_vqSV$dWpRo zIzN3SG&FqLr;mG_P1BuSg`vR!$ARmP;|RL&Go#a8HSUuHbt|Rw6)pj}fAEFnT9v}F zIQq3wR7rJ}4ML!d$8F77x4SK91e9B}NN)}jrgG4(nZy9zZhWr&32EmZ_mpvNwLY_9g#ZIa> zncAQ}E*`L)8^FZ)`-mLz05eO?{@{hmHPX00&s^Gwb`zrm31M%RA0w)ZUih~YAESy% zdEu-G=u#veJiCFyd_Q{1s<)w+F+zw_8439K&ZtWKbuSuFwhNZ-qVogzh69SelNsWX zA5#gv!#E?G&3ICzCwdm}F`~@rS(0?lxy$08oVi<2-bKv9+$iPs+@#a!$Xj-}#E3fA zKq3ZsFK2Oa@jTt(0|yQ~I<_JPR8)l9(%n281Rr^G^4bkAJuA$UWhH}(ACplyf;qzHO@JjVLU$`Dcv!DtZJAt65ggy3%C9%I_AChNP@y%iN^ zs3?Q>o9Pj^qdPq__^a;_S+xLZ6$jcu2*@-OhXFOg=Np#V?&!?&uR`?)tVYH^I?*w+ zGt?-D=a7e(s*dW!w;|5ZGYH~jRLx`!p|e?gf`mJobjnP(X39Qzxnz8oL9xZcBte&U zJbH~cidu9I z2d$avY`@ROjyaOgt9Zxmk>s!Ij_JN1DkEc1hLW=59s4!po_wUlc<*jPB;BXC6W64u zdF~?W)E#FnPJ%2(YHgvU70L%7Mcz-I!F;~;i%T*xzw&=UslCx~bL;(NTGYaM1Z>Y61HcAn6l;=^+Z7!ujo#0bCE~v$=(kbD;KJgrQ{;5a>$o zgovXR$}~Sg2D~z1y*QQCU;-bZQR-UI<%Eca5xyeFtV0okua@UjI*Fi71vwfsh@$`* zZ-dzySn8831^+k+O{3m{UK#t4;w#rK%pbv==jZ*1!29zyRGX!~eS3s2y#C4If+0HA zZA54XxdLMVo$YvQQUK`lBw%jT;70lIqOQn;lgVEjI>R3W44{d#TMxsnL=$}Ke=Kqg z2~`(Oe*r->!RXpDv3Cv@hZrXihMvgX!L6?N_iEtMpN|uW zZ{8*d0)RpubkZTKYtL!(z?B|6@a}6$6;bn>UWuS3VPRoUbhgvIgg?a&J=sAUy z61-|IaDe3Aq{4?1DWZWIQkA!-w->-Pje%((T3-+XR*l=95Ww|6Vgw5X9bM1};{!X2 z(~}erGkz@J{*cs4=fR!RrXX}nM%Ya|_lVAZA^=VtW_jnu2f>y^0})l6;q8T5niWu) z+$v)-ub_@|U3l7z+d{ntWlMx4%ZJeyzKTIh=;7*m)it)zlmiE&ZbHV|Fc#TI= zuuI#NDp{!+A3qVcFFD3AwLnw0sJzt~`54BUpI{LokM#}OmzX48ABXL|)S)A?xfpO` z!&O!uC`%~Tup6m8HQkXV0o5-Ey{^R?4AA&kT245DHmuH|g;GtcAE%XGpRQLWQD)VYMkMru zb^Pe;);6fi8aN+TQ<%4>f0^WxO;9S^T1$4{B&f+m2;QX;uy1Y8&__xSR<9Y~lL_Nh z07vrrA)&~2JO)%a8mh#&v-(vlu%ENzxI1Z<8(XjTzbg9d@R&G3#1~~Fm?gQz2UKs6 z^u=?&+#u^R%H>H+A;WEd$lsWS#BRx^nneda@Jm#jJ;UHMQS{Tszdrc+fI~OI;gr2M z#)*oi9eqxufk)d=vF|beG3`2%*p+e8Af@{g5U94jE}w}X*8mNO%qCWq-M1$?wl{t} zJ-i4q?iEg5$;_}WHPe$`h#P?02Yb3lkm~PI==X^OY?uI4#w2tIQvudXZppm2xFG({ z#~A2`vHn2B@|m|cu_yzwY814eyA1-e%Y+C81qEj)w_j`&I}A|d25nB7l}vBO8W9sC zQyd%H)}SG}IO1ImhmnMNkl=WMTfK}(z9c=|e>?SV|GKki|jKl-x@#OIM zkcSE<7%_Rk7&#yP_MuG6z&<`u*$0K4Tbdm`ih>MOk+(YQ{sw2e%K`ye8;UJ&zq_dzTXQtFO z!<(&4kkBMN`+3LKikcVuC3bej00y}>KvEY$gaQj|Eq-YqIXMxY{ZxDO-7JG7FkA6UM(yd*TXTLMM!<(U551EZdc^A*miZt*J|-yV&qQ$R zIKnfF{&;*X+mBD{EgT^)Gv_<}x1Q+5n$sYqi1gf+HU4En|5mY)c%n&&LE{G%>t72vQDf9ow zgk$w**_{V418Rr~#rlm;nGDq(KlXh9vYEbn#4RXk^p_^#&orZiPs8 z8cdB02AQL^Z6!u(6@OmIbo$*Ga`}S{>OF zm$s>L>h$qp!GN_gCxv(WO^-2X#!0bfLb=()gy%_*efr#y!N!mrZ!mC5aUmgD< zMWt=~4jQ+dPu5?#sihS*Q-l`=D#%S$V(!$R23najY&WO`->v{(xZ!-nT^OiC=={hC zI|TkBPnWo_$R4G>u6rp(z3^n5017PGek$%Iz4ORjkSqG!so*i-|1T_N&u{ly? zvap%s!O&3k2@T1m7fic&jA}q6mOtw0rH43WUH8>EV7VxMI}7jUoye1zVNXNo zoYVyVteAiAHeyPlz9UDke0R8@lVU+Z0l(9HETk+6AmI>$+6mg_s|Ur(y~ta@__`e> zkv?g>EgaeFxqBWy7zHxJX0{IHG)n$EnL);Qn6$~H8X(L#dMX-{05D(_g?gbZ+5XR~ zJm)P<&BWY#qZ2Sr{RJJ)OGSPZBefhVqeX4OMtaGzq)b*cou8x=AZfPG8g-a)rgL3K|;iiAhH3UZ+u- zvKhM5rzdfn$ij;%UK{-KvbwVX)7pvDjNR+J*|(b*agc5$@WA4Oby}k0mls-|lN2s3 zptB1UK!DRi8+>79XrO<7H=Ov6PyF;w zJ~W}T1ZZ6xU}6Y$DnBI?p1gm)J-En15k-93L}{o(B6lnBqIwO{Fb0Qxw0oRRKxVZ z0hJaHxqv?;5@f@F33Zf85SM%X!>kU9rqUcxm}c+Bnx5>cm=pwKZ1qI7~ZCHa59hAGKdhIJ#2U)PK%ruieg4A|k!B08;xkvozSoE4CtkJ{zMPk*VDn zho0a#=pXyQ?qJ|GB*ud_r#>`kIwgf$n|P1lm9rbJM~@ywlAinb@B4%YK#%b^Bx~(H z2wL!n$A_zeLJUmYYRTRHBqPtBYzlZ2Lo(MZ@AXbv$H!k@UXdJze6xFyVfc;=6d3vx zlm=txJgpx}Os|FXx_M%p&eaRbge_;Bx6YKo-fc?>FD*_?Cg~l1NVArQ6hb*8Q)r!m z46&H)&!-~$yn_g)ZFA^X9t0`kZPwtL2}Z^pOH&=6AdW5!Uw~+jnUytF*$(C=(J;cP zaykqlr-9vNRslsJTt3#bRce8yOKM7{VuP&mgANQfW5ne|{Hf>Piw_3PNYF$aR0`E! zSr_*ZB6C?hdjO>-1hA#_q3C6mK%0^g#OZKLh`1QqvNsneWdq=MXo{4}q| ze2m&!smcQ6h;;E27bPmj=-ez8P*8UYWon^%3O!KFcLjA2`IeH(a5rKhe!o1IiL*z+ z?XqNs?7HfzD%c-axZ$ZG`@fF?Vb=}B9|gy+LzdsoAwWnc(+y&4_2kM<%1L)8e^qPziuTAi#zi1h61@sRv>{<*Xq)9v@i?4|L10Xw{eQga@GhZ_w?y9} zTA}}2eF|rRR_cv1wyHNh|7pt5J&0)~^I^}x>)J78qA*U_W+lSV@;1Bn=%#d9TN)wc zr#5N$dJX@(iOyKBpXEVcPl6=PNf+_*z$=03;?E~#%p#T=Gm_G176?ZXAPow#R6uT< ztbgt^qK*U{j!J+QG|LdJwIp!uA*YUou#VW=0#S4ugXW-czM9!hgDAj}pF7DKymZi6 z*-1j~g9aUTK@scb`s{bb2_uBAUZjffY7vkC7rN#smabkuW$K}_R(TU7-ag@$uU^YP z@OD%I8w7NwB9xSLnB^{vAO%!V3@$C3ATqdvv9gR`Ct4gW;1XuYq2+A-rA(PveoRgX z9Uqil`#|zL!&Ok~?c@4}7*PG7qjQ~o3^ei|*V(alG^~8aT38J`=73U22C-w25CzR} z3&3ZC_Qg=32^y7o&=l5D$J z*I_+8L{ZB(J5(d(Z0kQ{ySC0wMxlq8st$rPR4kRcC$aXF0qh$%yI07jF5J3kk#J*c?+eKo2kj%6nr0S1F>wLrSDO z;oRu}q6zmTq6bgpZEmw@`@*2ZT~azH!N#1elk&Snnb8l{GjY2z`u%6|writG{!!*< z=zGMa^~L;b10XE5KPna>?{UkAs%BPCzI|WiF}9`B7m~`Ags0)BJ*7_~ssr~NDw4sI zPenBz@zp0zv7!3dqr*7L`Y)6m(+Z(;m9eiPjsPd!AsF4{EC0%b(#XSiLx=# z4k*w4x1x*_krG@yn}{sUm-~1ak<$2?Q0OjkjBOQa>q~#MxcwDan`h37Ee)Y^3{#^Q zkZ!15S3k!ZoWyU_>41}y8IU2d%JUEA33I@7=mSame_fw15IsvpjmRS`lo!nQmm}qt z(D9qdO&RnGEp3{!eH&6XY3NOegZ`?JUO{i>yFq+!FtTg zUmq0cu=qgj4&qntk(Xb!h03or{o0GuV^*Qu%H|2=P3Acn27An3SR|9HbQw$dbuD5( zNb+Y*1wVW4MXI+8C!wbI*S-Aqm1kN9vNa#!r*_QALGKahr)4xZP;nyR zi3kE5O*?|rX+xt7;yeosk45(wZVmZk&-w9{6tTl0Y z`trzLB1kMKur9i>xM-{Gu}N5iT!%$=Sb)xL@g4SPbs%641vHkp)NtKNm(IxYS)vD} z2b>~u$?wBGXSecG4NS#O$LTKpww9azfq+GS#KLxj0@WOkZ%Wmj8AUfuvwjxQkhbbT z!ZXoF(z<2hV0JZWc6icFCu-|%o8#ayoiAfJ4XUW(C-0OD7|^REo0B%HdOwi&UfR9t z$+R=6EA5Gj)7W^6Y;0fdFw7 zP7N9HVt7c}z#P$lf5L z+cd(CX<*SOo57Z76h8@nV&}TX3U6vr^wV<7#iY$vCt4I zOh76WIaFtwK#i`GW(m9})C4yP?IWk8zQ3$bQlI@fq@P7dquBz2B|8v3UZ$t7NZv6Q zE}MXq^TAdzhEnbm7rF6JyAIjsAW(BLPfWi?BEng>_3rvPTXi>Mr_Mg5(tESe!yc&> zXhw>Qz}Y*6(H%ej3C3{@Nx2a$?b)-P9n)vHiL0xZcV&pVT74PfWj&eGsHWOc$LU`; z=cz{Y`}R>tM9vF7jbG5%3(sxKk5~@qzM}x@uN4d3voB~oEPbJS5=fEBL?#^iY^?bO z>DI0KP);Ux7ps)tJXJ9@st(G#U2IQWI=7*T%Ym&`K*;U-hTmjcH|%rEn|{$oAKnb$ zg54$Nw0rK*5(a4m6Fz`S(siY=Ggo}N{F{gVb%$g}S_LDOcarUWQNvnv2h=l4000Js zg-u}}Bely@?HRyT1VW#Gz5|NEwb0F!05P8l#P-mLQd~z@++pbZ(T5Ko7KGb;*+sR2 zNPSycTI?3b6#hTD-U6)3ta~5EanzYnK@3pRKtT}@>4u?1q@-IxxX5&&lU+QuCTzYp-ir8EPtaDMyGDpXv4 zBp6A}B@L&ps|OuG*Jw61S9#$TSPO^@Rx(ZS`*ASk!(Ie{?tStxC13uxiB@reJ#}d} zbaQI+YK-A95W+UQ3j7I)ObtCnT8~`Zv{7=}&POPusKrD2*9ekkP_fh07$s`8xon`Lr$vG=~pHzBhie&k&HZevHVgPZc*e=^zi#naTp%I12C&=j20vR=7fv z3e!Ywy!p58idgw$iS4WG4A-v6OF3g1nyq&2?D&3B>y%~&?d5rjsSN==xIh#+N%$V} zj^BB3#E3t%juB9_fLwSK@Vw#x3(kjj63c*POqO7Gz?;`h{qTVbrE`H&bNj@V2+f02 zhQ5Aq73T;%hjOSppmov$e9qwopnsbKu7{Wo(7(X9dE&(5!9g%|pg^GxM?@U3Kjvrq#LU1i$flV`HQXI4%AgNQdy;zMvTa+D6973PSAN272 zi`70R$-JtHd%*K(mQUB7T{7PzxT81Vor8ep94SxdXG9eHgB}i4BsjCURu8`VDn11* zIKD1Zcsfeo*!Yyl54naL=SRFD%9zQv zq&*hmAFdFDOu@#JfxyBjh84drfHl5Yf*Zqumx!CVdez6puwV5dk&3** z?!yaL&vBl@LMpovQBN0@9atT)*>SpYhO4!-SbuL`6j%_WdeL z4l{!RZpmbqQEKaPhQbGXIZv81Ak*<)vVNM&oJ+K8D)z zqhr*--nht4wrAH)+(9CQR@+4!hvJX;2}9h2s&V-$v$&QAY6Y-Siov~fy-b4jFt=rY!6(*+n$C3WH~U-%K&-Nva#hv@UfQYtxl(Yyzt`t+W(1a zX=wLiJrzsKOd@)SZh^*$Wo{lbM~jZOOT9r2&;ncK5( zaEie7$p`5Kf?a52z(q6Ydp|A;Ti(1s@E!!ErQHIysbW45B@tIMm7r7;s1hlKi+4fF zUlU3$MqQtSxaI`NP&K1+ZJPSdjR9rQG3j1#b8~YYp0~VmM2e$20oXlOkamVNvwU~9 zNo$6VI24zvgN2iasYkh5*nbG7hE56v0SuZcP}Ovuv2p9G8i?Y>!!JVuw9`j%kQD4G zZDSU4=v=AmmQO(O;cL$jGTEsq?%P6)?aRw1G(YYDHXsRXKmwl5U12Hfhv3!5-2uo_ zq0PJZP;yX~Q(%)ql8oHizpiaZl1A@}2hy_C0tw&oCJ&Wde}%(t7*SYovilOBLR_;! zLY*GD{diz`@_ty7@#spA5Ik}XH@{JNm; zT>^ln`hfaE$`p>o@F|FXa?JWukUA(tr};ug z1b~(Gcj8T(M{9ZNrWUqzPj+m6j3)`Y^d4hdtN!g|!;%KN*?HoZy)kP?t)=zddMEc* zFdsX(RY)sdYfEQ|lF#8Lhz|lwKn|XzJfPR0BXs@t?TiMsUvsth6-_<6@*j9lE5W44 z!#zy`>y*ZFd?##=rs?4~tX=~;7Dt+IQNohFDu!MpzUL{4}M+bu` z&^Lp$%ALzv(wjCm+ z%ViP6!2qNvCFGAn3(vpst?4EN);EkG66-Z>$0=|naxW}DjD?*F22YL}i!I6c7EVcW z98&8LilqQL+BMVz!fl8U|&IqI>PXJ)Gh-3J=5`Xk^z z6r&{LfbsuPdN`B_8BpO&GZTokd3}v+^d(|~ zYi*jdkA1CS8+aU3K&_LWY9sRtVqj)&nX!$)|5}goceB^^gm0M-3SGT+Z4ype8?HGx zX936g_?utCBT&;i_nOb8z-A7qJoMx2R$BRYml`>MBVyWKuqMU6Fq62ks_XLWIS&BQ zB2W|D4Fda&B9y;MyudMAlIp*<>3S}~(o`>>?+kzE?d7&(R||S)Lb%%L6}FxgNX)pf z=VMS_7DcmgR-dxyFxjlIN@hvWV}eR`bZzSc#tZCqrT4`!ecIp^vAFCys#|yJD3t#% z9_MZCGIflm} z8|X@l&$#;7ZJeRu84ECn)WM8vB&Xg1_jK5r8zPB|9PB6hxsW(5LZzYc-dQd^kPEb* zO3pb8DWXr0`Yv!h`2eini8W53<=?$`Z`lEtQTZoub?GR1$a;QW8U9O*<6{2bT9Hnq zf8lNo_fjY%WxWCaJ$_H!%>|Y%4MW$mv(~<-L7DdVPaWw)5q>zm%T|+NYsu~JP9%#< zYcZ?!A3IqVFa`cZ8fAOwKbqrREnaO7hr1J=hmq$D5*l?qR`MuBYi6-CT0>u{H4MB zV+L0Rr43$AhCAIV=Gs_VVnQu)P$3D>?Y{}Em$`X*T5^R21Q5L@i;s^tt>76+qDfFp zO@Sr=UuHs}{s@9M27VkCc=7R~WO(B&7ezpbBsdy8JFg?@=f)2*x}|T!BIAQaMvU`m zJFZ-L>DpG7K&~L!6YEd z-bDdA} zj>QROz7Y};nZy{SG$NmCIs}dsj=kn}pRnHbyLYLvE1I%K>0U`YR_s!clHPi}7? zV$-^6HCdot6i=e}<;GN}(X3y!>3%_S(r{Y$a%Y72S%vsV;Y>i~rL_)kuyL~@PDOF(%Fh|Oi!gD;4`AC7R4O!i{pxB*8Y2V_`&a;{e6amQ-}IbBoyP}&Dorbxn3#5U`%j|3|J!H(;6xr9 z?wjCq#jj+&=uMeqQy)5L*9O4wALFyncyi}jyaTk0mK|H7pR|Z!0;=d z0lM^{hW*V5mLZJ`scnhaC@8i1J(FIV!gIIU>Dyl?ZMZ7H)fwDY_Nc64l^>d74K$p1 zPP|WnQE*7IvEb?Xy48MxjVhYv;pBZD_BF*BZk3Gs?Aia63Pu0BM?>CykVEW5%7xfH1Obs5K$JoP_lH$J);)XHvwQQX+}Zttl|%9J zHrsc~cy9|qB~Zf3W@_u>QH#IKCUzvg{^K#dGJ81ly?}+!(yV$B|3cMhSm11`f@mEodnOU3wAoV(|z0VP0hd>kgX{&&_~J?e1P zIdh#e)=nTnOu}QIsR&Xk*pDAx4v@ZhA8eX01~Gdx3Pu5!{iN3}{f5P~r(kp+B_DtS4eQ$WveU6gpoNLv-bWwwt%*uT*?*KuJYiTG)YC}8_Ct{YNQ6;GAlr(L6U)EYnnyhEiaIOE~3P{oL=fJ+$`1r-w;F; z!Z|FieG)@U;ox;K7nd@h2VDqfWhCR%er&M7V)oKHA`rl8S^-*5B6J@p_Ei54YP|(u zzbt@v<$$sZ9yH(w4~6vf^x)w*k9gQhE-Mv>Su#2tjX0cJl5ob37dNfAkGu%SC|Y0p zXG0p$?H69kQoa0I@|+AG8MSE*M;E*sz91&wYQLfgA{Ni3){=X&pR1&9_&^$l?n%!c z@tdS@``<3(kXUbJuGULdiS)e10xQ>imJEn4o_A_{AFHd`R54T-mvlm6-8q0vqws~a zUrxSSY`;P}tt!1WYf?V;eZu6g=@ z_!g(Q_v9(0o;=9_c?X6HEam#-EdTakh{GSwv67*XiMdRtl6eP6e9Kmk?jZT({=U;uroE6-$by2n6+$LBwdW@w)0>#fYW-Gy&roR18hS`!`O>b|b6A5h@O z`yC?D&+cK<1A-71{O;rCZE|j{N!vM@kZr^q)ZVAsN%+$|#ygNicrq%H23eTsF4{tQ zCYF$m5S7#|6+-eZ+oHNX8suG;gzKwsE+Mgy_$PRujS(LKsGei)FW#r~)yNnddc<>< zFdN5nklXLaV+yLdU$=s5%h6Sp zIZuDk!>Paq8yBE8yi;nhnB;pGuIyyk!0WlK%2OrNC;f=wXl>2MrW)Q_QkE}B4!_ar z<4+dmz%nEA=Kom`-Ed#W04E}HtLWINB*cH%4BVm$t{v(zI@2E4ZWes6l|!E6{@cyI zOJ7BBEJW9VOkAlxRE?1=P8{V>zv>{B{Q-oC1bKTSpcjZHEOTl`FX7hsOifD^_7wq4baO2i46~O0s;a>z$ka_bG-u@{_3F+fRc{vn?r@R z6sIoir)H3j-F@J3qcwiyKTZlnsW>Nv5(N?KEzq1}P~!sBOBZ)Ge3u3TD39G>Ebb(r ziA8;XHb;$3OnOC2)XiT??rd?5P}~U!nKg?e?~8Pwl%j|oJ~-o1Z6lK-Y@I zRj4U@Sk5WU-i6-~w-R@eye8~O_PGVrat0X0L{$Ha6SxjMKua`Y8J1fgs3QP&d=EfP z)Kdl8u6%yK8V$Xqft?H@$^%%CxCT?qQArUa9z zanMm<#<~!kK@V7_(zyLGe^SvjWp&%iPi>;CpSd=)iva(AO_t(+cKG@+fKZPAI9?>t zsf$w#uiaqtzTy5KjflhFVwYD`QG1_NAKSaiT-JfmfwPWhQ+xRLtJc%0@H6f@@Vn|- zm`6g{uj(0MDuUfApjIP)NHl=v%&D3aVodfY=Gp7NTTV`Fe3ZM&Rv{P4KtCH39$Xr{ z$GXxf3rgaS9%JvV1BuXP#pyq#Bl&8T@+yXmaOq9Feo_`gxi|F{eNJoY$D96lnesoz zWpAW8!yOmhp~T=lbf4pgCL8pYe-tl|eLjAb=l4r4`#Nz)@yX(B`s^ykI%Nt0j>#oQ zT=1RyYXD+B=5lV8SKNp;O@k&Wb6`Bd*XG9^WS?w6H(z4H2wE|qWu2VQ@2E{dKsB#yu*q?GhxO2mmoHTO$o{}T7Url7-G6*TB>RoWEpJRTig5zz z02p=Sz6wZO~ z$Reo5T6(QZ6YZe1frVC)y2%1$Pp1}~aQF}Z0@S&nylvQ8&kY6!DM)k%HY?6~{W^p8 zwg-epxI&|O62xw>r}2if9U~5Lz=I88Q9$qFxV^r>X+HQb6p?A&<|%9i z!~?niD2L|}%4E{TjCvC2vGgNkdkCDjJctE`KiZExXJd08@6Iv9^F^QMYBn6onapG3 z9dM7r`~2EBTX}=#|L6#QvKg;RIS)P=1&Y@<9zvoU>@OrrsCG6i{0~;vJ?X2L!Rry< zqe?-C0Rkz^d+oVe8I8sC)2X%l8u?L7dWqoEoo!4v`K%t-b$X^ z$bEQTC4KAPV)1)z`F!^?Ck70V%7v%s^XKD`o^Op8@Z8MumWSde+Zi19=k5UHXxa`F za7O^30j;{uEqCrh+gcECrAKI3VD@56?fx|5A@F zqSmX2L$VzJVB5ZbHub9enXu0|fq>Hr%?W4&t4E?}1Hlk+u0ay{M9`(rkD2;XYF zK>(OvPVbRZ!#*@~X?7HTHspx=@u zFe#5yBmn&N{2x4Q?#^rl6+>9CT9H%o=5&vKLb>}58T;=E|7f5rDez&aRDMGdqttljE8+qYenE0GmasyxolRI}81n;Bq(zr}p z7Zh(!(yGHsWX{QbDGnOMZ0h z`X`5)%J|lk-#mH#>J!_h6NuF7d)C5<;Z-wU@rp^Jfqu4FsK*nAq@X!h zEa;|C5R5P!2O4ff8CT#MFd@d;L%k(p5&H&EmME>HJmX#rABt zsToW_weSV2)C7&*uLntOpUCVp%vtx7SMWH1XpI;)t&I1?KzjqkANErVH;jOYb>&cW z0O9Ebm*t&AknH+h*ql8SGrx0FzN(g0eyo4SLEkD$VZQ=TxvkjQvwK1M<<^+$E)n0x zvCx{zl}=}nA~)AGYp(KC<8h#?UqZ7xpayvtzPWT&vm$xWCk8Q?K#=wy|5!VhrJipY z4`PYvp4EYMjU+mtE;R-^lhNM1dGlf(pyd>=Ar8P>=UbiSuKAX~O}V}k!cl|A^!0LO ztrHsM5bnlKK_$b|dOvoje|?m#B(Et?3Uof)fdq6F&>CV`g)1a% z{n+6b>-D@_#wSQwZ=x>k<88^^DkU0il3+83z_IqZ;?1s=#Ud{+pAK&;h0G|)F>=cW zl~gAXQ!iyJUhq;%d+?cL3bi=VZ%t@bNe#{d2}xQ|U{5*EX{k?U(_?N9np{92Dzi`J zf|kg+P`alDbW|C2b#=2HXzP6{Hq^Syz8)66{)suw^=hT{RQDq**rJY zQ|JL#r7N%YwrExjQY^2N6VlIURSmOcc+0%&>Uzhz=sCXCQ`ERI{AQtTt*)ArFexY1 zX-JgNXx-t{lPlERWIO#Xi4hl4ZKg@uPbzP%MZt3;_0`ET+V#wiyh-V=vk|AF(tO8V zfz_cgxwPe-O>tAz>U$21eWt9U*O><}^H@tY)W@8C*z7@cvMg7K4QZC9u z^KJ~rxG(3I>Bkci8qUDS`&0on2;@LvHm!j&`ZC?6@a@Yr`Nsc-#~8@T$(4ema6YwY zRmGExBr)WX}&AW4?`glShwC5-X17KlDE+RhKWyRGw0rS?IuBWTBYr#i1B z8YKm>nQ4$csk~$k#G4ApJhUrqK@Wmb#o|SOc906t2JH}|B-DszX#OjK=2(ryXEfsB z$~TP=sj{yRJR1Ufn-`>R7@-q*o4pqXgL!xB;ol+llktrp7q?y|<(#2F`W=N)=y?KZ zxNNf!09IEK8J68n+qo~AL@mLYH(&AQs9(qn&=LO8+A0kmQSD^zwSrqI&~~u0yl0D| zn)8dj^7f+N`gl24)R4KTNaAkyBl2Ty)l0!9giPJW_sx1&=On$%cy}HPFa-o;x-G3? zMdZecuxREbqc>R0Y~E3cO=sG5i`;F_t;@&y+d6OdBx%D_WEQJVo!J@ImLCh$BnY9Z zy5T^ABZR?^(DgdnvF#AvdPhwVlVTSh-{rQ=YU=Qw-}%DpD{@M9X3$NZ1`yalK)DWd zUpgMWl^A@4;vRKG6nRqEuMa^W>D1;(st8 z$?3IL?Xm7QVSMn(wF8v7u*RaREMsg`?7w?2#*2N8etPV3Xva1uA(?2XcX6-@7~c#K?OJ}cpMv`(Hc`^fy9 zsN>+=iNq%R3E%Yv!5k5>f7q@T>ULaWOxA9HY7fr`lg~=GB%`4M&yb#{t1LJEBgrM1 zp@d}dp-)rc9;zk9;?sF6F|=?as z8GkwpmA-#TL84vm>7JaUfhBzg$Us#i_yg?Vf<;1IVBg7ww^Z&xPNP``{%X)tvZehkFhe6J~8}<_xp1 z%(-~ij@Hxbm&+DymTOv*-IoUH=BF1AVr#ooT5E?0rlB$8LMf@&>$nBbTe!JevKyXm zGUmZ6`!jV_{`c@9D$ttjB(0)ORFu}hPWkouH};b z&gvLz!~S?MRPB#Ga;8+7b|IsEN+##Q!xnkEN=^}(o4ad?q_sxxH}ag<4i8o(TPNk1 zmimopV->^c^vkE)&N14DM_oq0g=bu9J<;c!Jo#dE;#9X~g8l$=Cx+WXAY?g^Si50F zs&uJ&OqcwZQV;rQ*OPbX;dD$Yk}`YR57JiaLzLL_NCZS1Fz1uEmtG6vyC0);yah`| z;gOQRl&M<@bY5qN9>haHIy8KJueLKl)${_AGMk1Cb~0lZN==B-KcAW9mM|Is!&vR{ z=OfN$@ZgT>OmxqctM>E64(w{MX_C-!THVyTyf*=^<=-*JYHxd&bFY!&4U{xZ>L`o4 z>dtxp^L5DP*=l@F%F;H6dAYU?5x3qluclXCGVSqMxRZ`kWF4tXDy>(=PWMxG75vl8 zeDbZXT-g9^Mfo|Y!Z1WecD+Ye5|eZNV3jq-Lk%olTz@RvXESh))xc+2fcUnYP?W53 zUumqwZjgd7} z=bM7+?K-R(5`cVZzhS(RfiaHu931!ZQ8lO+P6+JHh0Uqd3TOpK3?impx1liffm3u= z7UK+Qtnf@>!qZKz(B)eF9PIqy4;f(n2PsvfZ{a*Q#2peE_q#4n{n3ylG223yLT>-K z$SN;CpMA|NM`Wbw>XF)wW=X*z&7w`BmgCSU_dN}5mGBhNRU>tmnuxH*RB@fd*9xGk`5E{plfJBwYDXg(Lb77OJFn~gqNz}7Nv6eead>>C{j?%&qM zoyN$ga)x8MJTWz8OZN;|5w9~++>Ag>x&Zx!OTKC z7Fnq>AeWy4C_q3)7wH)w;7*jxT!_rPlu$;wlbe+poqzO9^cz-kJKBE>6eVw(o#|!Z zP5fzuNCJ#9l*!v4rPd%L)LAX>Cz_IRc`||M5&02JC|L0GMRST#tcv%|W)#DnxQ`Yu zi3nK4SdX7>$LGn70Dh+H$*QKV{PRy*b92Wdip{^x(@iBa)Q%07M;$GP1t(&nzLa}t z`<}hw;)3(a|dAyFRH{LIUQ(8}LNk0E2>= z>{k#P++GDWOItWR+wBFn0k36;;N%>fXgeP5!sdQZ=f76GRnN_ll(c|)#VC!XO z?Kb{64sETBOLkV%nP1FnZNkXqU6pf2H8+LMj3LMpPd(k?lSQIRY7D`vai1o>d|J3#3%<+pcgA#UTeNx4Ont?_vM)sqcmqK%@IM#kV*lSq^q)2{ zd(C8b(*x*TjLx*+8j3Zt7#U%qwIiMuxAQ$lfT(C4koV+<9 zVY3t|xp>Syy11NLUcsm(0`Kcjepve#L*P~6JrHc29Uw%8eys3t@df$h862rzn;o05e3>+=ExTXA-l6^m~C$q5`hr6P{eD-+3R==lF89acWfjO4WwN?Y+rrSk8l^3a_I<9-s6qUCzu~s}uUH zDI}|9>sxJHux7Vf#LPQhNu6WXHmzPnw&jWh?vQ5I%(5~dv)NjbQJ2k8ScyH+e>;rZ zPTx3PZ0e|F8?)Nda0~3QsXc!D&*#D{V9oHG{^f7g)kCn;eb5QLkZk5e`-Jq9L*j4yPng;4GW}tXXlp7V3bGpku})g z#y9t;UfxXINy8G)(41D4N4H=-2opl_J*Miv>DrPnbA z=#FHT9zG;JZMvD9DzqiIcKykI?Um~b@F-x7X*Vw9UKx63Wad@IAXUZrmvQFod3J8o zou0lG;uwRFoywU;=Y^+FM|+yFON^$~s4$onIbyii#mnspQ-sL>pj`?sw z{z!d?)>{Shl|XB^ofNFzXQM~o{}E%-)(`hquK0P2t}FNLCwam-6r47QneM>)g&5ig zw;DTMa5%VoYktzF94U?j=ZApGDC9mBsUCzDC?VFiQk?b`GH+S*N?2`iX|(djbtPpQ~E4Y;Y!35$%q z60@Io#ec@H8ekk!pYWY&J#)x}iDF_135|MAxom8Je%Qt0`R5GGDkAWHeR?7(=PNPc ztoadrRN+2-F1eTQ-uU+Fhv9c+F$aT>m!`H+*DbpCs=kKvsIg_h$X%`FVofh*ID$=l zI+=z(q_3T2j=dK6Dts2ZqNJvxAPr93r}Al-Za$0Y3W8#isM*l7u$yFb&l&`Ytb8e0 zQg9@~A>==Yj8{E^u!Zsw*b+Yd+oNo6s-3o77*|Z(CrtU<0Y#=0k|FW=EbVtY8X z#3z8I`Y(*dkm*+0Fg?%SI_luza;m#qpK3d}a(-#t=t8ss=O{zG`}5YU1FHo$+6eb5 z9dg)(_2w1i!~h0~BF4*Qg_29W@Z*MCeO%aYO3T~TM^+S0^j*ylv-|takJ6kp`u)+( zd})r_(is!-^8=rT)z(0$Cx0o-NZw((WS6Mhd0tY$Vz)>+HP@=W2sb!=d<|P04T#GL z3G1c8N%eZMcgx)|-jkU8q$|HB5Pj89ibH71CjcJtfe4a8CC8MUkhK&%aM$wtEVC1~ zE;XrtP0|_2UMO;Lv7<7@(J|o{UvsoWH4I+0701f@IM;V80o9$=9@&6mkz%Z+7fqd~DluTR%K2Vdr~4_O$v~{dN7i zC{+Tt5|bQpfg+g2T8US_RL&fxhTzfog|eDWEj{=(`RFU&D=2DUbp{fW6No<2L(k7t zTc|;R_y8SgO7NC6nV_B6V_I!<*wlzVqTYOwdQElx=D+v;j_b8=$T$B)4dI;s*Q3HC zX##|VZ({tYqA|t16UIxN;-)xI{B^Lb;^dfLkZ2C$3nhf-cs53KQS2Y&Y?4<+5csay*rqs z5<7kM+(;sirlLAIWv{JXP2uFI((Mf|k$XowRRU<;Wi+=HgnScovWK4LF-|se9mmKL zi>c*81d^aL^>h<~MZqw|qy)vBOjGA2>i*f`)FVe9|5+WvX=PEGN(jJ){C2!z1yD&3VhrcBr-n8~6?h6gH^1L=uda3A& znC~7kieZ*udv^dC;ct5a-bus;)Jgh)zjzVQ_cUDe@ad?@!iV<3P$m~RlJ6^!IGGmQ zexmaW(d8G!?Mmrzo4O)qIR7{!vstHTUb`E;q#$Ftx?w+M@3ib3GC%Beq*h7A*9Uj9 z1*E#5ED&Wk%p3r>!GN#7Res^C=G!?fKhYk2+xtQbtO0n3*9|e>lY{Uye_Rb`kzM9r zl2cJ3aoKDRAAG0n4K5D+*!cBi0KFHRH}BP^=++}EHd`A(jS2m2KappC?Y$G2lZn3M zQXi$0L>uEpCIg}Eetf~nWFr~bGkkXAYckLPvLR&Z1=-nVPHUX+5Ru0T;F^*`pvjA4 zB|97AFuLwjSWPi{cTyjc^U-$Qe}k`6D~^eg*gZH*xnY1S`Ir2wN01h>rvz&!)fCz> zX`k)ZDoWhSu+CW#eXI@y?se2oAAd&d`o_35$!qf6f}w0#Mmxj%P*DDZ{-~&HnpkU+ z3hdJ7j>)fUe_<9ZeM|^4+}o-<&}V zb!_=?(DB>Vw?_w@Q9xKT3Ww8yt6{mdB|kqzMFJeO$^s#hulq&8n`= zM7B!=3i(!_edkb?<1Uq_TwV>|L)Pf>}% z(qWcRwBxS6ci`@feCtsX4lbiqbRMqOHeXTrSS9R~o$GArXWAG2(;C2AD!WA4`YycE z4D+=*QCTxA**h30NB}q-I`G!aT-?;oT1%{!UGetxxzV*h@m?IcnkT0F=r8jYf8UY} z&havr)nl>uMrMqVP+mMh*vf*npRKB>L(bDlxT% z(kb({om3$c^@np;`QoVpONEcEc*(vn%j-jGtj2ClzpY8$ctItGK&R141fc!6K4H)@ zL)WJ+hppjokrmZBqL%LOq?#TQ#g6nvsoZLl#AP~ZM|Q}Hw(??CXI(I+cy zyYq84!tEOt;4`<+SbI@t_r>p-xXGHLY;VfxM!jh5f@OW;p_n?u<(p=XnYTE@b{@cU zQ#Mb^xJk)n(#L8&(MXRE&@@6}&*{*bHQNGIqzw?nuLxqUKTD#=w#D^)L$dm3BN&fy zB_x(ebR+@ws==_(q9n##;gX~$B_A(3fuwj=T!tt97ldgusK zIcCy(0J~$RTbWD3`3U@PZ*A2RlqdD~%1fI~5cNimwT=<9okA0|cS-!++VN-lqeW5( z`D%JT?u-rft_HWMvTg4jp4auu9ZXo=t4ki+?w$WKu+Lu+Ob`>3rSXA9cUn z7n(zr5YU!nnPg}n;TgK$&~p9jAfPHKi`xynq`p&GwFl?&i&&WC|O(y#Qo!S zG%WJ2^r%Sd)t;xD1-s(~G91emWhcQ#>|gwE({Xt%yr7_5yYP4Fjk>kM1|)hsjt-gX z8F3L8Tso}k*KKlmaS#kMb+>hj+nJZk-r7f3JewR%v#cPDU35*x#4AR>RF z_=&NarF1{L&UyfCsJJf`+USB?mY!}tCC3ZX@>vs%CL@`{UoAHI-tnb+)dAy3dJc`a zasg=q?1gY4bk30)ZgTGy0kP3W-vsx87IlcSlKU}mcn+0%xC&N0SC*}TuKtSc5-$Ln zR$QIaJ2&`femV>A3m6ZWhVP|{==j*^9a~Mw8BHUus4z8?px58I9-3cvGQ7V8x5>TK zg9A1?43@If#n)ENAC%NFez19Y$M;dPy$>A0DWrUKfOi-U_&rbjFY1__O%ItNnrcx$ z7A_!LH9erX^fJgIwe9s?!rhXqEJhJg+Y$X_Z^@CeX>{VbJ?6yoPMXNcPO zaceiQv_Uux7msO3#X1*)e5E;X=Y@T#z&b;$2=r|q)D8lQ9R2qjYv6_*5rq6Xbh*kW zs%GDeah_?}i>7D!urisG1@b?mX?RbA--p&}fER)`8ZT7{V^nwAE?mvZS{Qn1>J`B9 ze8=X=OvAImT731tyuc|-YrMfwD=Mta>r17aYK065F?mbQop_Qq<+YO25V{8AkUx@7 zpk)>X84B7kYO&LRNlJlBgwhkY4>EdBrEjVS;Ko_mvPHamn}}yiOQ6VPmEv@0F}C(6 zi2~jc5ifRE^%41@+vZ!JzB9gW$CzpHJ^AuAy|-rKNHP}r91L71_YoCE3_~C53xws> z^%zY>CbvsNj)i@8bu0>kK%A>{RvC0`hY$(@2Qz2!qblFrK@ZxDKYwjTym{=oxnLA% zL9gxFr+=OqDr#&$+oIZ3Y_gm`;d|%F?(e^_jkNUr1jf|)hPZ1slSGiEc;v<6)bGMn zys&)bNHSkZl9gxQTHKv8xCq=)MMqf-!q2l-wa4*0=;?VsX!;HNMy9A5qkNgfnZ9p0 z35AmbiJCi;ckAJJ0n^G>vLVsZ((=e}uekWZkTif=iIB>K%ZU2tf>kJZEOT#XGoCOj z>&S|kdGB+Xe7nG(!8N=$;`&`*s&B`Zy>Jlr5t1@kvoN+qnZWAg?3j^^X!AZn7dJ;0 z#RgAt5i&wb`Aphdmk`ur!k=SJYuZ%&z(7K-Jw`^wR|vNXuG{v)GQA+^VB;3$yk=a; z;UGhJhPa(Y3V0E7#bx$I3h<$rFcAMPLm)hY7!^2q0qt3bB~1Y-4jYjwbPtt(1NaY5 z7<~;J@25ram*5+URN4$@J3iSvJkC&lO_V~ehadMQ+zYcovQ6)$gmT_czGOrC#oKwq?;*LUfv!#A}qNNA;8c1 zW!nLQXI#o41@m}vwiW!m$%ijf(sdhxQ_BN2$TS-^nP|JpMl{(A_xzy2Je$#!McdXU z%y~8|g`emBf79*?1$~ffE;rBNqlD$fB0V_h0NyLyq&5x)rpc%aRPy_i9~SO@uBILW(>CF&$paeh%$}UAEV?i7NMT(6ATRH5!K;tW3lz>I<=4^ zra(3j4bn7sWqEp;Ls=Ak`XR{b8bptY`3hlT768T2*`pStkwLojGD2+7bs3H-HYs+t zfFi7bz)q+X4}F*8_obgf7QExri;H5uv&TwX9mq)DsD5R=U_cVfw;DU1 z``B54$&`|vJ8Ul9>fs?+j7rI3ObJ?m$_{x@YiLfa`%pTvi1cz@2b1FIdv5l*&6qI8 z)HV;j^J7y}lRQN@lb+<2gY{wSsVvioe+180JIJY6Z%MyQ|LUxtZ9|)nTi?+~L%xbyjBn@QE-1CEEWzOI_HpyP5kpAPw94_)OHDoO6e4{lV- z+6W=(1cc;9AU!~5Ug7-=yUp>cIM+F;AhBGsmh*2Ji1qeXSS1B)+&c;j8;?L1@u&gr z)AwF@1NOr-ev8%53})^dK3)uQIa-zUjU%3B#2g25$)uR)^+ji!MZKyx%T>BJQvoum zD6jaUr{Vd$zVN)&Dh_0hI_l>{nsNq6lYQ-|;PGLl! zny}q+{s_Qy#v1!+O!$?C5FkBQWWLc8YH8CY*GcvRK$3L}|5f(Z3@DR8ytA-q?NHX# z9-|ZD8T062F*C(vfl-kak6x|YoL50f#D!NXez1`#$gT3!1a{yLGHaqB%a^KZZ=o?8 zw#3O(NcmD_cNr#%929q$*R&SHWnL=zdoy$xSAbxV|HwdKhbM)dL1lGT=HNp;D~pMd zQO&w&s9Wpam2dNiK8u~!i&6@l;N+Lzrbw@jF;V?RBp_2771}%`ud;`2YzV?1d z6yLscz+%s*|6GDmYoeNGUWGZONAy`AgL>2o3guy;Uz4AN@|lLy|GsnR*GAqua&041 z3_p7;<$d#@p=@ANnadZ<6e|O8z zfhjegBjBF1_KKDK@kq%3%do6YytC^(+4!V@E^0}v-xCcvuc^r@OqIp@3&|LZFr}K6 zg`;J2c$6c5zkbmae&cEc&lPtnm6=R+oV8e}>^KgKydt|dx-Z{EuaLYyWd4o-(`aUb z8>55M)h`HW#(N`v*}A9DWcafCSYJ@ERmjON*`c&rcjoYM$(aW{&?kdYl4Hcr7W$e) zy4!!%5*!&^3`TC_L+-odMkGgGl5Ep0?CC1t5DM0A{H zD*st=h6vSyeNhVVWs=35ucD?;PO*)KdSO@#eE>!n^;h3L!}PUr20%gy7J_Is2PIVN_y+ znaz?$({Z@_+Iyj`+7G=VID(u<>$R)=;*YFYbC0^|yNvT5JY4wSBU5>glI2=twslb( zqP5!UG(2qF8Dxs8j;~0Smx8ogP0`q!70hvoDn`*z601C4`zguuHZIE{O9BE+vvX^C z=W|Bz_iiK!4#0P;Mlx-Ttbgp}%at=evxh_+Bs8C80d1JID+%H}V5y+A-a8=t)}b`5pwy-bP2ip!gDR#+sALR`}&(RS&Qpi8gy*t_fr-IcbJ zj$2vmi_g=6%}^q-mHWhp0f&|0{X+hfMPWa5Jm7)M56pQ~t$4F#)>y#U`lDLvk6A$A zfnwuPky-#Ye_Uxde)x9G$5<@JDl)Z2&b5V5HBJSHNBs}1A^rM7VE?H(pW+ySHfI?6 zoBj{#mXL{StqbEdyeDL0@~q}%c{YtENz~eop%4$-Dy?Qdd!5~S_EX&!;nPuT8-_P; z7%8KyE1u^*wui%^NuM)IkeVoDVCpaNg+8=XhviZ~He^$>rM^7ZU-EhB*AB;Pq2BkE zTt^3HD7TptVoMaI&BkPXUa>k%D;%_IXSMcWUJ}c40DIBDoJQBV#xzI}k3>ebJ7VcQ zBe`8o-kZ_8Koc2pFNhRx?Rl!ZZ9YK3n7*ZeTO*%-AfY%vla)lwm$^$j4le0BNb}=T zp!+eYUY!|1l;y$wyal*CyGBU~_d$Xc{DSM>Kh80)+eQo3vV&2ibDnN)`;LQwUtWV! z3fXFzZORzyN?Vqy+_@-Xmg#qddl zTFkeGl912QIU{AVEl6+=jljz)o-| z_8A;msRs`b8n8Z$O9{cG(6=$s%pJdif)IzRt9HkIe1EB^Ct9zEDN`4N zhIB&+sZBS7##B!c&YfY!2TSLXGj>yBYSV;EvD97Az5+~zhyicAYZZ(E0|)3w?^D3r z_Xg2)25;`g%JZzoN^R!25#nK@n`GxUYSJgGOWp~bM&Hx#`ZfGoSQm_IVWN=irp%PJ z+-bCVbwJ(9^>2Bv&{N{qN*d#s0t+|o3mw|7A{WAolbEClg5D6P`E)`tU>NSEa-VT1 zT(~b_am{K1!E8dfHDOGaJ)=g*5VG+6JfDd*_bmfmZZ0Ru!ui?4cO@Tmg(d!9@KoC% z4=oIG$`rb`d##4iiuqQKEy*Y-5CIb61-*TRp;@6;ut~N%6pvg7Rm(0Kj=dYjy|yX- zII~EjpstG4EPT89sl7$AipX5QI+0jk_~T!j8RL<+#uHHW|1tI+P)(*=+py>Lb38K^ z1_cypGuWwvG^KN-hzN+%tAaE`5vfug9hHuvf`}AB0i~DFAr>Igq)F(YAT>Y;9RkU> z?*JOkyS{(@&sq~0S4f_6m%XojUDxgjcU*Qk=JN7oPrHBNGu`HbJ}{UasXVFGquUg6 zbeWBeqi}IyizDb&%#PD9PQUVi<}WDu+dbJT!i#bypg#cn>A>q{6M!UuVZ(kLLLpo3 zIM0E=EhSC+8$G>j=sXiXq4K4BeQIi~xh~M<)ISGB8ZQcMa(&QS2>3)Bv-)d}lM}b1 z>+juvs8UJt=uPV@Ig$~a$5SE5_cn?}Y^3YugUY#uSUUaT%3u@lVL3%pM{L3XFk-b@ z4#?g8pP!eDKJ=Yb?FOoK#d7w&I4<}tKAsQwY<*`FTYokGS(8NH4`i5}8w31+rSIw6 z6|)$~)RmuAI5mABF2ppMmXg4(wb*fNs>}Jt#*;pOQw>T0?@lyyJ2Hgy@Iklt#5mQ{ zqY9Shi1y@aY5A2~iseTg_>3C-pz}EZp7J?+!IaeQB${#kBUP5rJi)zh}sN>88)c z&WeqBXfuU%TfGu4_uo{P+liY{%N0v9wW=Ne;a6dnFWuDXHH*htemO;d|2(x5L9g70xOIBECpGjYWgBH)Am4OmI?xP=nO@@)Zw<{gT|`fBfQ# ziCVYB{&_wp-XbO#IwV}()T64uIK5yS%e4aRj2si3Se4B6gCGlUxox3F$Icq0R31Nk z^3+Tg+aKTs?=BcxY6L%jetPkxyi#yU7wm*SSy`1m1XlV^n0@_$BhD~{_C6aG zX^AJ&umI5%txR(bn9KJZrk~b-(LPUXOF1X2#R2uv>6>UxPzB0K^KAU*xMGT9cF6G}uzAFb%dG_5nS4Wl$ zsuCW1Ati(8F^KT^3`l~>(R_91EdcP_3cep{Wh`OB4QYCaO7rOGcqq8kUJ2AAw;l75 z3mpO_w1J*2JN2I^4I(h-UcR)iS6KRZiK(YoexKWe^w#XZ4K_F!uWFtb7K~D;F=VU_ zH|QxsY8RY`ra!JP5c6m?KI`QH7S=63LKUJ#`m5ew;&RTmOGFQkH&?is_?)91bUBAZ zYa7&D?YQZu_2b3X7SmTn-v)2Dy*150o$NcCA5c( z9;?aC?octkyycF*1YS$57B{<-FR4r=rt@2jc@UEyxmzV>Q4KMrq~hlqMm>8T0VUDY zxoqQt5bfo{&77;LpPyFp?A!}$V(B0$oZCfCz_vP8dQb33sgo{!0;jeASdWDbjP~oQ zV@{pR&+RG^L*SRSun`;79Zl)mw5<|I#>g$|!c{=9W<|cAJ|LY$^mK#VYideL`SzWs zpt&C_(uRRMrV7xuErI154wa^m$Lqi-{pwjZt1h?qG_M|~vWjMvy-DbUoh8WeKqwz$ z5$TW-elRSADYIOh4JY&Zy$yC8r{P*^t5Z)S$|H5xs9|u+KW5 z7Rf9z`4{XxGdXTv6sRjSuxz+JMhiTAJWSAbt+3k(WR_=A=_{sQ7L>a~Rrf$~ZAx_H zEv9w%zKp*ARhT*S30z_a!O%HCmB&{M36{Y4RZo#PH_<5y9Om#2Ojh~8?d^L5K&5iv z%*h{*d7Hw=%ES_>+Y9L+90#+8d&POei*y z5D_uzHhWb2k{%m8{m$$>Ijh-O|KA-JIs~C6 z(htcnoqe#KE|LL%V@%LFdgR$xfli3ijod5Si!xbNvFfCm^5fCh5&TbjfkDbi!PBI<$x zXc;i+HBTc|vSNe-Pn@)S1y^guQnYyFc2zP>>OeFfLrfL`}*!3EH8Ae~-PNr@B$OJW*8Q=0b@Ft-7# zTyhj{^(f-i9XxgIoD3KQ8{+JLKH=NqKf;x>rxvrcDj(pAlM?#$r0qFk*1mt=GL>`a zoJ_CJfqQb@+g&t6gydx$t7c8??B+_ABLX5Keu-YRQ$JSDmbF_-0Mhu6hlsTveR3t{ zKGsRsEIyvf^uUS7@yrdUIghH-1Z*d1t~~;NrQLq(n~$|Vbue`F#=_lZ^2AQdO5ykr zo?gn$5Z|?=&B{HnLSme28=s3M@u6LS{@@LmW=9&sA01Y$1~S+HINw7N3Sbwgz*ZgR z)#Eg8S5->SC(H~Yv|<8U4bU}N05RvTZnAOEtw`5HiZ*m`1U)}9JJ2w=t2FRGEs4r= z)LyGc{QUfkQDbh?Ptq)l&3hQzco z1_@Z=(g_KcaQo8P>y5fXhAdDuS5+hm)jh4e2k~#+%<_MdV5()a0XL!&l%4|zw91&h zO{L}B%a+JReyjPZnQww)OA8yM`uGsnoK^A#EX!9u&VZI24Nce;t@nV_p%yP~|dGwBXM|Uby;(VCzmRv>P zzeu@y-A?;`<13^~UU;qAe1<%2mPXD>^>ea*;@)pxKAfJ0a7mzxT@4lg!5jr3{dM9= zZ|vp5XQqAD*b%K|ifPW+hg&-`*18)>2eO<>ZoO=VCvl<)cvl0jaAgOLO_$E~kb6oE z@rJK!L=RLxzH9?HI>b3{b4ca&@XxaYp^|+tSzyqwV~pgbS3@K1^JKMn6UyURFz?w1 ztPt-D&;EKT;?IDfZkw;&fTgaS{cD6qWy^Cchpc!;QChiOyZ@x)A816ZAJ6}pi7gDT zXzb-!r)I818qACnF}>aYg*HC@4EUsf8R8;F)Htz7FBruDZlW_KassA;A6DE{Q&x^| z@gNJzPk|}b5-?_a9+e|Ltc8Uwec3~eA5J7ZePIHuhD3@mZv06a*DGbZ2@hDb>eFI#mtMx0{DUQ}$|byXI_bYvU)Fw3WeH z{2~pcyr9n&hy%N*(Vk+Lk8AX7fBoidBSc6K&~IeAB9s6LzmBLh?9BUZoCHn_wpfk$ z|5e`Rd$1>{_Uc_AXJNF7Y(hc#UWP|r3-apsdA*KF&8Q8ja|#$ss)izngvxhpE?@%W z=1>oG%L}SYV9|Az`i99F^9T>yF&GyHX*S!g_*)`ut1cF=XteuI_AgojmOcu3XAWPZ z7Y#eEHQ%`d&K;lrCNCUwT!n?>-Scul30Nhc!0!om9A|D`D+5Ikf8WQ~b|nKn$NSr1 zK7%YZ7@D)5s-Vti;X$Xz8*g5iF^{=^eE@Wvq5W%99?qcKuZHLX=3Zkx^}9C=^IONm zKriR{_3xboq8kI&&2n~gmDT8+kniJhq0U@XEJwWa&&R_B7-yTrK^VtskWfk59RN=B zeBU3GSQp{Yv6RrT)C`AL|6rVk+X~ZNFBgeJrh{(dRMZQuZaHVwiLz@q?WuX9yywp! zI(^jeRslti?AN_FO-6D68%Vy_PBU#PY4jlO_t<-|Kn|@kw#U}B6&fTbBmzpGfVmV7 z%`H5uMn>XSIaeOzC7@M7(YN~?7S$o_h}KetbZqgBS+iw6ay*EEZ{j_PB?EIB-yqq~ z3BD$VB8!jX2SL=jUJ7nH%T&eX(UT)5%&_AmoY-7*ZQn*VEdA9dMaJ)#AFef4&598U zJTlUly)mZX#}}vSA<0TgQWli#t_#9UK$Go`7w|0_KChU~#ZLR9kV9Eh&EjKATZxG+Ivs+feTsL-6z| z$n+Wgto8l`uGwt2jq>C~G4P0St1nIv+scbx{=B4)6M{Zdub;unNDvtpU#!3r(r{?& zB)KfAG-7CyMvjG6k1jpJit!$EH?ERz6bt2%UBTOyUei`2n#oQyd1jg7=Xb%lRmi*a zys(@Xe!0AWm#2y(YsRkMb`Sv^QB(3M8@UW75357M{(;E+1FSf^ube?12B1d{hCM+V zLjL(^&N_`)DRJstioCeNda0Rys<*MsL2()0tESGp>j!w z#6UJ$32`6S18y(dq@gpT33msesaK$n9PUz_)(SW=blcwgdC#}$9o>Y0lap6s7Y6}b z&rj%!D0tdTtd7sRnM6-J*WbUCk5*ntTaFSBVys|22lilM`qLw7C{O7XJ|kV=I}mp8 zuf=|C5zmptbdAAhJ#UOT*Fr@H`+Eyl>WekfhO6n|UWu-i;9z2Tc+d?bJlRG>J`5{< z0KF*em%tFxJs~8Mo9z=s0oxJ3g}nGGIm$%qXt7GjL1ua2;K&`W#>FV&Qu%&*T~`?^y;0D8Xo9n)8Ws)<7gm$XD4L)g7- zGtm$`%G0}qe?}|(AaU2$26979>gy1oSS3;K6O?4y*6#^ zwa)XBk74%w;jJaN{GBfxpiG-sH@(9thK0LPpnN!)p7E{UX9L3@F+o*JVP9aO7S}qj z-6ELi&!!f^@`Kk7@8?KEc(2<(Zjnz4POLm9z3{dxlV5qBn59nc-4F}4#gSzE-Cu|; zOx+>GwdcB-l(j!%rnB3xaAB`M3jXNmBZbJpr}YdY!x0f2ai@1KOzs0t=(Ow=2>W(J zORrm1jqyJIgk+7F(-YYra|7}tz+6ELnJ>|OnkabiU=mMa;9zWH{&B~5*qNG^-Rg-3 z5Huly8rM#B${)?OW8@lUix|(<2OFvy96##7ci66%?|K@WQbYO~RPTopc9yse6e-b# z@g9Bk=O%#F7N^xxa+>%}I~ z%Zl?H2<;$tk#h#C!twKiDas&+RXGXoeO?F-LMpHv$I*KZc$An%LT%tC4AeX1V%@IcT>9!oV+B+3`GX_7l6w(mmx%Wdpzv`y;IL+sJZ{b6G^COQ|LDlR`5y){W=+d^w8 zE!nXv31P1y1U2x)Xyi;wGx}ZEGCmCWgZTP7#{kggvBzqySi8zhefsn?GSfYyWPBW1 zTLpA=-4D6&sAqb4&B9%|8-}NplirKdQ++TzdrNqQILJfvwCtT!G$fwCn4ggll1g=Y zamuHTE<6(B*adB#hR8rqle`(x29}1ceBoI_((6y9N*~c5Wz0a+%dt*s664w&hu2PK zqyf?>!bZz?d=amlihbTz-f^g|rL`m_1oS zaqf@^`@Og8Fq!$}@C?zMYqbp&>XsBEhi$SSq`DL z5`;R0TOoQFJ+*2057?yX-|~5XXGiFZYx8^jLJn{LA?u9vjSgMxJZ<>bJI3XNkYsJb&ed3662 zEI)0Og8tYAYt98}cO80_gvN%W8bWNV-*XYYZT%4AfOv8Fc7-9{mIdnX#}I%LG~zS*&@Wwj*h;^CS%5Thi0hSkkh^OxNRvj;}BDrj%h(t?qE+)&A=#>sCM zQaT70mg*@VAsY}0KDY7M(GLh#vvBlC`pV!V0-+lC8SA{Waz7XC@7>rR1_kKN&yyf$ zOdKlkU3chfIf247xv~WUo!!r(2P{jeKjS8g8xCP^LX5xgu~6A?G8dJ4NVLEmg2wAL zmFl4siqnC9_8xw;5B#XD%1HWZPWVn<4t;LLKp|F%g}lGXWH0jpcCCVLVtvv)nc=?F&6KaY<7)Ldalxxy4M!AsxH!w zs3p$U9L@`CPL(U0Vlq^{B2Fh76d=25lC zyDMO6$>|7`WIJA!iSgCWkiUYDXU*KZSN6jYJB#fE2hJd*BIK3x<9P-oMHd7dT85oD zmS#%_1%dhlW~b}WVKH)R0(o_$E}Hin$L-Czk&q)0H=|PK@)@vcR7CPJao1!_2%37_ zV3VzVLfwdF{NoxE2zrM908_oFin-V4T3xGh*0HWuG3rV?u6hx0Kb1zem9Z2& zqyy!=_>??fWMt?g*FRdkdG_!#(s*9-COxu~IA!PHa6#Hy=aqUSBAbQEhXYo_NJ55C zg25x=Np)+YGU3;ye~@e4$XCn55!XWL9#SaUNq09?bP?*J@aLqa18on!$lj!yvqupQ+gA5>leilfdXhJu(q@G^O%{hV1|YwzRHBKmGnqq!PIaTshhqQyYx^&i zNKK8b4G}Df#AgsWl2^l|rvk`>cs#4V2{TIE?tU)<6Zk=}MZ5~(oZIV8=A3~MpwOXe z41x_5>E1C-OXs!9k8i5ZFFxF?1|RAgIO$Xgj3>Q|PIiqAfIZu6iHp+Hfi$NISiSv2 zY-yZ}#YEX?+d+f*o4cQhi~OQj&MDn+>(7>we8u6m6i0Hzh2a@P1yCb0^X`c%MlE8Z*6g>3hD}e0^O5d`amU^v`lYq{FWXQFA z1n%bMk)E|#bUbv)$e5!wjGYyp=8pNrmB}jYONLw6xzmOSaGrtk+B$@mNxO6O=q8KM ze>FeRr|J#VvmgbyvS!2bM}`?K2CvO2Y~Od>x}XzOF~}f|@~t~5y&&7)QNEj!YM|4V zOCJHbh2ULkX{X^(CXdS|jjJflk-Cn7CRbtiC?T`2t0m%9-}C!A&l|0zc#v@l7+7{! zof&ztUw*kpi&st0j|nY4F!HMh!{xcbdfqeNsb3CNu26Yq_rbsZKpfE%Cr=s?t)G^GB6Ldj8x?y|lhFNi;_h>#934NOV z6r=o3mqo^i?RFdS<~Uiq7@vTGU<2S;-f6ePu1O#wX_C(snr@s{S`=%UyT}sDT8P&uPHg#9Fj(BTDe2#KsJiOG9I8(Q?>yEJVw2Js2(tMDvr%8t zPxmKV$+>rxIZU*a`-1zP3)1sOwk0zvtO;HOAkq#1({%8eqAaM*jML(RK;9fVlmsHt zwJK!-d`7K$yU$&3xH>|A@kkuU{$m#ckkcT2OVWR#j_MwUV@s&kd7r~EfWxuqPhMUq z;3k5%VlzNLw$P#v`W7DJ%&JJRo64}D2DLDTR<)1Gy+keBf4XPK0_Rw z?Krc+AFw?UoKnUNX|17Rxm9_IYGHi*7N+$@-2cBaK^x)dYr!C9>=lOv_Cq|>k%kMR zzmpYkjTvMgWMd;e$OHAU#V-4OxI;JB**{_-qKjQph^L~YL?=bbRj^Y&jCY}$5HJmD z)zzVGi*Ip@&px!NSCmuOkN}AM?o~7yBr0Suy0HLL6jnD-idzB*Q!UinjR%Ja{KGyj zxja2zxP>U!292^&qkicWJgiBn5fnZ4g0Tkl*z|^*A}4b7!$0c<7H7X@$iKrEYM^D4 zKplZ=T8WRB*WLVH#Q((DMbX$ zFe2o)v5z+pGznPQbFTEXiU3Y%@l8~?Ix#RyX>#=~s~aTeN%OZWv@3wi4U!~nkvSKP z+u_NdOq{b7A#LZ4z9)f&j0NOMr=qngN6Tr)hYQ~tOfH+4n!cEy=F-+HO;4EjFKE=x zDauc$$EGZF)$-nP4p8UKh>pO$fst5gRCaHRI@PJCVv9NzyHcv;i9ajk zlN`RsP+G2QXvmKIWjI}%6h*)jo|SaGRm@a7ZxvR0y=6a9;E9h%8TWd(ink@(9rB(k zwgC@FS#2{%5<@LMVQiBEmGEbM*-Wy*^cNQ(QA?4D&DKkfTlPI75|?sHoIuccK)6$o zUf~UGfR1=iIxrW{5O(w~NvlO5-al}|O(#BKtjJKYfx>+y|J}X80Ae0F>$3157WX^T z0&VpWChXZ#c*nOjJ0MR*aXqN4oHC{dUJB0hlb6^feci60g>MwI$%h-r^uE$BL=HaQ zcVa3LBU)%aq7o0#V$%yKl4Dkn4l^>Ir2YO8OW z5fE9K{Fgo!L2FR}KCbneGZZTowr`fJ6FMTxB*gp9jI`YGs`GLSceM1|f^b~q{I43q z4F?S)Qtnyi#~Cro`^^Ih+bMJ(DS@*t;>DK>m)aiQZ8vE3{k)SLOJ;(#t<*v4ODXJW z+d@ZPk|wyfmtgCps-@ULb!XrDCQ;i4Y0>Y`O*N&8LP!+=1WKe#s6rN>h$adfA878Y zqvYiP+{AkMbfvUkXca?(6VLCDRO#?@L`1}W-yDYNvExU8`K$#hV(t^+yr#MN2~rd8`##cfWKk01%j z+zB0`XsqDqG5$&p(AJk zL0k{6`zHazZBEFd_lsP=#w)&Jp-#K#@D582;_2=fj0gR7>mrWX;XY{#vAap^DXGLV zdA4=jikTUe)(_*RB0pw#4O3HK2aZQir6HyM+@JhRKv62F+ty1p6h-y{ZAMS<r_(ni0}@LpNhUzg3IR<3 zOizFNXa4{!aKZi1=ar1=J7E5=|3$!`v{bYB5EB6-Iv)A!L7xVHABg|L@9vd4F>|YX zjse=zi06+-q4@34>Im9}V2{R%R|$Q&X1z(>&v;lFku5RP;@p-CBgbD0>aF%58o|wQ zMMNb>9AWn4E>BY&7)wgTQ*DN;uB*Fk-MB-#f;I3{eo>cG$&vP7*1f?FZ#3^Sw1(L$)K>S2CNgv_uW4o3C6FFw8xIC`Qn=BG{is=_ez40_5#U z$;sBCnD4X8pt)8B*1x=ak24<8s3EUB0=x8~G2QinLVsmZ-GrU19unHB@-0=BK_G(& zkKM2;l>KzwhIcEpK219j{wh(XWydZKUw5^b+E2dYE4^qdj>9b*Uf}joQZhA7vgvw; zK7fl{3d1@9*LN^CHI<^eM^ZL+HCJOdVv=@l4T>m zJGZIW4Rd5hSkvOlqPugmqAzk4J@m&Uek1v5ze0oup6!RcPmzFl0=22(qCq!}0aylu zkjXs?KE4!OZ{tHn#tvX;!BGt6ur+6~>Gir$TEa z)Mj>1900`Aajr81bkhhg(F21SCtuZ|&bNa-MnnZ`+`qGJk)6c{k;HcY!@+E%^?lm|IyS@c^LQ$VgXKsM&P&XwptI zf6kL-=A)~K?R`L~`uw=!A|t5PBtF;WpLJna&*9n>M`0g(T3pOy#pfCAT_(yV>PtMU4m>o4Tlx9P&RvtONpFg=N7isNr6Yrh-qtGW z7^5eCkR+P8Hr6lIlH$Sx0_wv14rfaFx$1zLelF|NQRg;t(%?+e;)(M}Hp6h7c)FSJ zNbx#2ghpvm{*#|+WO{M!p)rn?wM~~XVc%wYcG6k3GQz1pqNd0HX|kO_@wbB9k&doj z$>Dd;x}8!a7LV9rOjhyTNj5H*6-6^;#*=d1=6Wk);R~NM)>qAw2kmADFF)8Vc)g#P zTz2p=x6jK|*?y6+_XjK}X1xPFfrrt579Xbl&}CW<53Jnr4(rVE=<#Zi%W3?Kt=hG& z`QNt9mG9{YP2xwkDz=GuyH*1DEU!(Q&cAcrW2wHTnbNy+%VOusCBDB}IsDPbI;lK7 z_u)jZ)qfF9)8eHB^&0)&q@O8p2fL}8q+ert7vHn{2j*n4EqKs~<$D9iN+0Q`FKe|}8D>kVZ)a=s?2?P$~6+j(X@v!sX-UrfLN7vUG5 zsVC3;hokBwT;6r^gdb5Olom|%J*buNHzwC7d=tTuZc8jq6#3Y_SpFotW8VCWcj*$8 zHh+5yqxTPvyeP5CB$ue_rEhCeJ!x->#WhQPy4W8UlTTtE6R=h5s64j*}j zY#LprRO#jb`webdnNRi6=B}dLgq4?te*AoI(->XxMeeC`Yt1JZCgpQiIvsv^2zaM;!|gt1qEMo z=x8&abt;v*`SdB#RI^p=P<)(jNpW}T$j+&j;T7k`HNge*Y48Lm_01-DUXSWse?rC{ z@)V;BFm)ezfv-sv?W+5ueGbtz;h#gbxMje7t8>&V;)r&yEKhTJ`p?Mj_Ni1ES|NuC zret9|JH1Em?U_yIlZ0}DfTtjv$qg$-?9l*nB!0qw&LZhyh3q1rLydgpJPl&<}|$*vh!1Syo575g^p?67zFv(?ab>_Elbt#BUiNvv30rXHGn2VKX}_`bgPwhM9H#0q1UBEk0!ORDshnJI5TYmBnaq6^ zPJC;pp;(S{`=V-LHFV4icE+ZoFIHy9$lrI6!fJgod$H{rw?zD?U($}A!`@nKG}$-! zV6pBRVhwOW!}r3054(gz2n2%0)EU5$2ERYn60&@&VO;LzX5!kc+}xBsM$zZ;nSSpK zFldwr1Jy>M`{y-DI#P7gmp*>EbDa`PeKa~7_v@Cl$v8X~=dAd>Fe`M&0gWJAyD`s? zK|!gd;{yjhsmVNFBX!(~OPqvO6YZQDY?6&E_s(*e(u9h|kTZXA^T^~j8Nj)^?#nZG z3gFR-;fa^JW?MR*ENs9Q)gzgv^hSbIv%|?s0{owE??F8T$w@qGC(w{t0ap%I$(|c$ zqc1xcf$|DTPQw5kk~5MZ&rSoiPqq(uR(L1_*S%n1r(7A_tbTP|(#R4F+%e)?p3vne zO*-h-?vul~GF>5xPOr9Fe#!4wY7{B;NY!eT^$6Dv@8^O?Sl-}UJpT4mn~sMq8?nx5 z;HuZ!p-6luQ@`oO#`ZKy<>E1)gzazO!bUF~lb4cg5$hdTgGou>=w7?kVS*;3@6~lb zWrwS3i59U`>46}IE(9)lk4N%Zr0~?y_sERjfg|~nf&r4b+DobPZ!8CUdIVve(qy2W zOlxiK6!t*lwWQhzg+>m|?`UHxUXFQ0YAt0H+*~2*U`cTz<8QnzrbErcxxGVd^&P@x zRVu%7;JB0G%8tLS4(pttLPg1t2JV#Rs;sL>>8 zP%;*q2>F4FVl}|J&#WpnR)9mq#k2fL6$%OSxwyvw?UKUnTEVG+fhhRZ;pb&aI^m8- z_Rd~W#QMtn{pU-CzlHO5lozZY@AQQ94cXV^U2cX%FDr!jEQKBPe9x}!(ovK;YMR$q z5!5G+{s;Y|$jmosi~9PVi8LF{FcYgGw#IQR&8#X6*HB_PQMoi$v}k{0E%i*Rb)^Y@A-$l!~U<2z4g<0w}r29c?@yW}FP04}JgcE@Kz9u0)j{0DgG;{Q8$Z zBXs)CE};O#3#aRY8W9yquJ-?h&ePkejD0(RyXhGnJWpUpi+!e)}_RLLI?cngfv zg>ch~*`Y0vqyzXzXp3Go`Eudl0cI!pXBk*$t5+JB!*3?oir<j=<`pQ>6_Tm${%DTVS0K|Ae3Rkj~}H`_Xjd^j{?tXJ|x>7cdzI zpaVN}_~h*{Pj=sj+>ljeg?KqZ~W;?4|X7?#gyB_^ThdTgi z5VJk%uaJPPmEhfB;thL<;QXHEvbl^=Y~$#t$W6~jj~$H(!+Zlli+%z|H(5E-V`(X+ ziSBNeioRKL1C^*C?&ag-ZW+MP?HiANDpEnSD5A*0g<)|Q`IM_!o-GCVk zq4B@SABW8i04!n_POij*6sKLXa0bYIsJvntwFXTADE*CpFszpER(`CObuk`mI8R~} zjz86%67Ks)fi(4J$1rqBC}#Mg70XsP<$FR;PUJA`p{yD6dx(U`LNgCbK95(3?5ytb zlXke_JJ3&)3|pdz?wI$v^1XsQIxWJ547L<(uoiAEViH&-x5WY{^`-Cpb+!9#MHjfg z*d#H9K*=@12OQ7Mt7@O}(`zWM<&DURpVNCH$57Qi3Kdn?J3OU(H!gq1taLW|u>lt8 z@O{>C1i`mW(YkT8?oD~9g4$*tkbSa2y+3H##;-V`%ZFAWvUERdOVy;wEJH(syt3%e z))*?qH+(_nWTX!zGBtk1GoK)tUMOsucP!~2Q}aX2w7mXr941+Yy$45VWBPD)dHQZ9 zlH{`+SJ(cfr)$^?xWEvVS)Fe!SQ{^ZQXq^Q2@)u^yS{fpU%5-wN2)}CrHm+-fTsim zxWymljTAQL2CrV8ydbzso5%04+H9&*YWYXZxEr#B1ykM8>gQuF$xg2y)8BhrKwNI# zzP@X535BAT^Al0IVR>yU-9m>gwc{%|Oyq59of1j4D|fv7DATl%A=xx(M8LSMnR%*z zLGV`&f|%;s^Wjsk?Yc#hcGA7BuEY>JIu0_)&RqnGIdjgGixPP39Ug!|)WB!-068qi zm{1y={4Q4?**shL$#I=hh&<`HZ9A1aL6xs+iSISNIO)#g=Wp--ffCHY0PERCRkio+ z%d3WgBrAEGc)}Rlx#kfj%;Qn}OFPqZY0*B~>f;^rtFr+fRZmR&+umP;4|R1f_x_&r zHaD?BRzuJ(x+UM?`|a`5t_J;de_s1mDqxICmoHydQhH@qGUXC+gq#!ZD^O?4b;=oL z65wLU7Lni!^8+zIt=Ji$sy{Zj)+LXII{nJJ_?1hFSk~_L>aQfzEkKomcbV6ic+S^Z zeWm>TYg|@o?)Z*C=W4Z)s<*Y=MAIyzJU$_x(Wo8sCVffzK9p&u1Ba2~FSe)e?n=7% z^h&ahUVgcmkPVlVeV!rQsuLNAd;A!~L+QZRK6|w!x6&LAQh8=(qMRz?rVB$;t!KId z=$@`0KZ?>{`^&w)J>yd`>+@!nahb9r`2I|-F< z27j@64(ED103o@(@sE!fE{;H{s>7|_G%r9$OqI*+%dvJO%szFD{`A++)WCd`tx3CH zbvC?8-p^B25sSrhF?ahSRZV;= z{Z&q*o?F=(9p(PIoKoo|w#iHT0mnN*}m=T@XUV zE5AyF)|mQ8!NgHk5D+jD?=N>(X>$<3Pp8d<;w#rKoU2B9bYVh5R|!Tqv{rHZmQ1MQ zl2ebDO}dw(CPAd&I>v?7xyoCfO|Up9G}C`Phho_j1Qk{4)IUts9Bo*xbM#@5YvHCY zvo<~>*yRnB1-}Q2mXs;Z5l#u2$DQ!i=}zB1+>0^khR0v`?3Js;yp);meA}rl=3*%( z8}emd-f`ND4#FnxT=&M=_{>Sq%Wqi;IxIl2=fnL>(4+s^c1uTGtVD^#3icNHTMEy~ zR8s!?Qv6yH-tEXC_+r!gMAo#yhs2a>x65Wd6Mf{5lSkKtlxZ;na`A|1@nDa8u;@bRD zc*~c~b;)ybhQnh1Me&X{f-iAbv+wlhEfXjopX<$2EA>H-6s`Et(y#wt9H!D%UE4*z zH?;2>_a0mtv``~WZprz&76o>hRO28Xk%)>E65;^MdysZje*$`eM4bldW^P(%$!h;0tMo~ zjmwRhy!utLNGmI8WOFSUor{erJNJZ_E=m<0Mu~IBc@rN}{nG3%TEI#h2KL201pe9L zqWM=j+K<%FKTqO~s?0bZ8d_Usd=zG>>oK5)cYZelTQv5^Pd+D`ss7h(+l*-nOfxX; z)phYjClX)3?!~HwPl@`; z(=ptqt7|F_!&g%EDoO5;-M|HupfUpo#M^&5Dj*j{Lqk!|)@rfWIX8!DL(LjTdOrhw zVDtDCw!Z=Wu3*h(?(357lbpSidH2f7aKyID z$E{qRSO~wjV}9-1ds4W5VfV|!&G300=i9RE!c>QaLeYPu3%5?H>`e{J+snn462`$f zqRGkZlxf0&kibzFVY!=yr$@5CDU%S|iH8R2n8z4?{mc*;5T}w)hQgxt|1mD?+Gd5T zF;Tr2FFLeQy{q2MYHo4DNci2ZwJZIwJ@)77>3+*a?Zrx=Jj)jk!fn%m&p8j)XMOj> zPW3|r6Q$ObMVOSzxgB)_0G$S;h4FB)7QZ^N#t-xqx7E?2)lu1?76<}w4-nhZ+K;uv z#%WM}gd~Ze0=90?%sSB!12p^m1|63 zE;#>l_1x2_Ibviy7_oCIU`nt7Zu79WW|yrw{EclL;9Sn1<}VMHoD{Au-mh9|IdUPO zc{@hxi%Wf2`JnKKW`N7_uoIWg!<)~)G{^bHn1BY&3=L!)4%HEPEkMBbiI*YuSAh1n z8=i>v-ezFGV!KM514y-OF0UC=Kec5BAUPRdgkn>#St zSDBV6HAP7lW;m|(6~Vzk9dqbFuc&CBNwI{GyjM6KF(v0OivYGN;$0Kz?{o!4TEwQS z&f`Vz0iS#Nmk6AE)nC^>J=DmQg?67qyDt3)byP+8CsZbObIY7gV%)8mmp200?nrTA z+@UP9n{J9&*~K1NP`b8mIek}P6l!A|Q&I3u&@MQ%4ghnQasYyvf7AlOocDd6mb`QEQDN$@GIW~d*Dj=sLCrRCr+q!DObpA&M4wGlP7gT}w>07oB& z6Bs-0@8SI$$886*jiS}N0$8&=mmfzd5o>iPg+C8jjD}|h0R~CF%dqEgwP0xFiGJgQ zhWL)_GNQJg^(5&lAMc5U26LB)dSy6V_h^hf638Deb@DlX&vjVnZVwcFuP_w3>WPB| z1o*-TO+|PqGkJl;DhYYiWfzRju#2{?Vb1&Q6Yx!=M(+K-i=N|eQV8WZU@rjK69-bh z=AItTWuNvn!|zW{UY019xcU9l%npVVTaw(&G`hoH%NO(Ot&*D|tTel!qBB{j&scfd zR&~+42l}yfyan`kP9+QHPuMTwNPTg~tFMAoYPs;vRB9h8X%Sjmo>$uyYK12c6ZQ}w zAswGu?x=Ws>TT&ryKEW6#J4kv33vAzEslt3w6EQ&3SuRLI$tjMoU#iD3>|MeKrd*k z3-pL={bpxZY=jiI9X@td(E{{8Q@o(6qq;j#(OkNa(GTW!;+yEdguFsH|rsxP~h zI^J-gRI`$cACQDn`|ou5ITIC0#4AYt*U_|52gwVAyP?3&2_iO z)zF-gf}jwK-jRR+=$eSE6LPntX+Lh={pI+z^Z5eC+7~CAjE^g4+JtWj$b*imMI4+i zzMLDDqib8MTiNe%F#rAhEY(MU&MYrCAx@v$?UL9KKRq|wCUJcx$pyv~J-4}NvH!Rx z&?k6#36vMfc0CYzi9q*nfkD;@=oA_*jv**1rF;9wB-=rtgWR`GK2NUxe7v_OPB+t+ zvnM^qxaG5DZtbL9Z+pLq9#?Jn$9c=_V=>0HYKFvFx;ILxL@ma6a2*=M$$oYG5G*<{ zjiWg!uKAU>|4GJ4GYP98qHIShTAE?5t>3=DN$>%5Zc^L{7Lw~33`h+st)fgLb@T5C zLT>`-NJ#>ha1K-2^aFX5Va64LSm)qJFNG?6d){xvJ=?f?6995IY31Hm!9fi6S=&zB z+d%dMf`ZbMT!Fv1>e=H>DAmjJvhtz6G-v(dwc=cX*oII&!Qu}w6i||V7V=~aR?Yu`ClZJW?KpqUswFuBGw>v2_gA(T%RCPlJ10~ z{F6#D#P(tHlBp6a87A5nahR~vB6{v5?GRh|il*e_2cTQw|AAj?zv8Gw98lT{{eOu* zQd2@)mzf4?`S3;xnLgJob1$C#EInugS5q=UTK58A@|(V!0|BMgGln?l58e&e%_~Ig z>U-6}P@Z1Hiw^yd~~5iqdz1<+>wsamUOh%9nXoA)zS zfWbWZ8E*y8S) zS4a@*jW1rD6zX3y`c84wXRJ6~Gz!&{<;Yu*l^c#<9x{-nymoKP^rKeMu_QtTUUl$t zW8{gexAw@;<_?NaN;bRHPZ_F?%}sH*T&bTdZ1rmYyi`ZBpy!d4&!x1(dUNS~>&t@$ zR1P+#o;xV1m+Ecgpp9^*hb&$H31bX;!!tPpKdKrQ)ky8y&{EWT92#zhpsvZL1Ug_d zSu=E#-t!?X0PfZ1K}4PK(9y9XCPTd-P)x-`ZshkhwS!t|xn;pPk0E0VE7P5Mf^-`j z#xBOgYoOtv97}Lk`Rj8vxw`*VRrn;Ones%XQg|^gyAS&)*gSB5ykAj*U-)j0{Y;Yu zTFen>qoC*v_x8cmx?)TYb@DlkSP0C_V5D0L;%a)XS#1tz6!KiB8|3SWwAujcp9>+h6W~%)3fE4hrczIc` z3QYJpzEUf$jLV+ZP1Dc%J>HdM%?dU+*YG%n2Z=d&WZkdjc5Yzi3A=$>m)<_Ljx)2; z{n-*Ocr8wGN%p#4e#Nm|Z2df$ICXd)INX)MX8-01&@zvxb^QsCdRrhb#IJroo&%4Q z-iLTTxBT&1Y!-~}muDYTLl5HGxc!??67J&V%`~?_C>S??7uB2ZTqkB$9O2u!M}W~{ z=|n7o4N(O$q7Pm<(K9?!p7$`*lq1cWDjV5P8!?#2HL7;yOAyb+ez5$glC?Nrqb0*~ zxX(c6Vph#yc(~+SOz=ZQT9H`tPEzcXmM?XHVb$3Ku~-haW9C z9iUDcfIhzdJ^r*c2&7C}>=6eRMy$krXagP(tIz%QMxV^B+=j`xG_o7yLPIBCnHF;@0@OLvQKYZ)5qzRL*`cCk%r>^K#x#8+y;`M%6 zb*YpuhvK~?hGTDDr6W4pQn66L&Yfz!8zmG5+;Rm)-(->u?^N&XJBun5Ke~5jfdTPh zID?0OBSAc}$6%6~R-g~kOyhM1HECr#lg#hvpTB?@3H2pGpyUNGHU=Z-D2>n#-i(Y4 zdV_6<9tS{u;){f+M#jeN4H^;GOp`rUiC7b%18B}7&J$Pd&(XZ?qWxy*Z~P!1D{dyY zHgGQHd6#L-+$cAyGao57`4vs6jYENql@PBgyDSu9Yj<)oAu{rrN9{dS^yAexH0&}w zn!D6xZ1G!~dT_=R| z%qjOSyA;$Ldyd#H>@_#aEbBEF0B#U~LIk4V>+IRHsQnHy7keh}?}yqmnR$M@X83BuFRGY)bk*Hkp;rJvVTz`krp-YJ`xer@h`vN|g_&i#eX{nAzXP zUFrY^xQCTy5k){+ejO9H_nGV7deuX!q(^k`!P<1-%%kE89zFld$rl1qxCyht&AV@T zRvJUAsBj%?@dPFBV6!TZ!{Vy{&LL@X48YxWdAbsLT#QLiO2=@RSN z%)QkGN-S;?M5M68K|x@>1^fq};buq(MN0imS1l&4R)s(*Y}=0Lxv#sNd!HOwk4b-l zkbM9dp|xts`}Q^#JU-HS#T*u{3>pzHjhS5QFv#+cpAYjlP0P%r3svMGD2$vyRkh6{ zA!&1YDk(r!>J;Hn!u{%n1Nu6ZO~%KRBN`9Lqg{OCcyey-i(QV_g1S{~@yhanq;HS# zKyHFsnU@5ciP%Zv!C143*GG9Z20uLTV=pIU+Ijf8L8n|(pB$F9EnULcX$H`L+Gzt5vFVJ@_~rN@R9vnM+)pojfncZ?Uk!f7-@-$e+oM4nCKn%`;hpw6_tOR_ISU(Ul2Km>P=L(;xOE?eU7*M&2I?9ufO^|p@Q<<7*| zoblo_hWOEa5do*Y$DMY)^|dPH8o=+YSWg=}_%28}y86sKeCa%tlXNOt-o@E>)w65$ zf;IiTcXLV63r1C_{CH5*`T}GQaIUcG93je`|JF}gSM5W~cTUjigk|{*@q^X1`YG5w zH;;slA0sGpM?8-AdB0~~Lf4;#7lKT}x1u+%VWmJ=W=Ues^wI z^Xo6^hPoGfUmcb|q_OO%jQSWT=zEYKl~$d3um@;{fGPz9hxZ6R=6uW(+dYO0hlj5) z=KFO!PXIEF@&k!rHM1%wGqV%TWI1**L@mO8x|uuXt-pvM*R> zoLkM!gfMX%siq~sA67`8sxb2Sh}w%AAAWWI@ayqPFlQPZk*D24n=~3f5Hk_IDAaiu zD7u$SH6n_BQkWE9m^^i92{H>#S?`63rQpp61V2BW+kkQu+FM(SPMJ}^ME&-5Qo{Di zgv?kXV;+Bd=3ETHv2|9`2C^e7u9xc1@;++j^uDbg?awaj#b)U*29kKZOn$utcdtn| zlz}kGVM@|9)b3ND;edV-2vD&V8YH(r+kq{!^VsXC>eo?bhoH}SC~Uu{fl9rbLqcEyRCM&f1jz>1>E?xB(b}kY(Ly5o6tw`4PN>+~j7jPKb_8~wS=Koi}p+{WHZFl8Z& zg=zGr*Jrv}#9{hV(Q+7xMTgep=#5YMh+nrgF>OW{d7#4e8u(F5EP^9eqkxH7vJTPP z`r8aWx30N{G$N*2%F4=L+S-%>#NXm0EOoEIKZd>?d^OL9uyj_F@}Kqiy@0s?uG7+W z`nZ5kK!?IILW7Ua;J|I?U zZ055c4a66U&ZXxxwdnNN8+m*t z%`+o`yI*!yg`&d+Z;Fli-b{eIjHca2Qf+}Ba;iQ~U7+0Y zlF6xaB$sOMWI2QCbf((MLV?Cwhd7=t3zj~;Vx@d(Kq)2~ckc|t5(aO(y=U3M6rIqO zJjgzy_?wTl7p-=3$syJ9Tn(UH!+G6uvX=(Y8SB1F?Mv;nq;l1{jMLb_--|2n`9 zp_bHQ=$XHU7kWj6yhcBd_O)k+5a9ttUpqumaP_X&&$ci&eSHlee_ku@;Qs(I%t*wtNtKaUp3h)i1 ztn}bj6@Wy6y7VwaN`rMpCyrfAX54!&1n{ncgq6-UA}a8#x5zbZ*2Ra10ZBe7p$qD0 zf{xDwV~q<0=RU`sdpbZ4eP_ye-53;&S7rx<&2Vo*Pb}83hMGE7p%{|;~z^K{px|) zNg_rscB57E?0ETLNR!uZqfz$)`V#`6ezVeh0nX@1>r;`>Ve$|!S(mi#{{AQe+xe;z=I-Voag1GuN5-vf|2n!j5t8{V zKMST;Vm~yJe6*X{zlz?X+L}m3KhFdE2Bp4vdGv>$S(B5WmZb6e{WcoYKc~YAJhVJ8 zl#t~I)LFhr%e*-8ZRL6tRtm)o@It_;0;cu$A>#3lIqXc2&xI)GBkcVm(lUy5X&*-J|b+i2u`+ zKics=fX%DS=LuaYj~af`qX$ehq4KPXQwUF8QswYRL{2VEXJ59%tL{Cr3zIkZ`;vyw z?rAUUP7|#Ve&?$MC##`5!@ioFn%)&xuDjZ|fZ9=RL>jl9V-psw?(8rW!r6l5esZO; zr9D8-K=wy4t1r&{Rpz}b_JLLB(OSPH^g5=^aCb{nfG9Z>e>MX;X2EWypjhtOyZ7Yi z&5fq;$&{K>=d?Cxe#I$2;(xV7P*JfrfGF+xo=tQY3I#g;i;&=IYVNU`z^qH&M#p&l!N*T_nw5;|18}8> z$CwU+WrA?HLYB(D3ll_}&ThN$T6#2lZDwI0zdn;MxuBWfvu61`yXx+E>Ejjf8>k5f z79CU-E8l3bs(;y76L)hwF?h_%bPRTqCPI%{0XJ7K!v~tx@@!JU0=~;!BKOA2o{hHUes>*nwVPpBKADV z<&kBBQ~ATd^$Eh|8ct}6L4O<)%Z?yRr|EgnTeao@^bJ`4L4`=JGJM3?R3Sd+G%70w zxcPtm^qIbFSx?l|pzL;`^~x>8?OF1AC#*9);-bDzmQE zANNb*WG~~3FJWobd$hC*AU3Y5e{(>iJ3}Dz>V_0&WG%Sv2b{q|LRF^-%D!8dll{&a zvR%})V{CG(KH36R`#S@dpSfW(dDf?Hg;hMzWOVct7ghA-qt>5phsLKI;zE3u5+>$T zxwbKmLkS7i^z^DZ;KdFA%|71!)w`RmhL>oe>yJi z^gSuzq}VlJ#GDIW?9XIy@w>uN(_4##;2_6=Th>+C@ts=hh$sLVv*v=&$=hr|Y`nCU z9^^Dx)&tGR=jL?8Q6NKkt~F?DbL_)V9#7rAdm>BtvW1N5W4W_}CRaAU$_+##(NCBbdOBTe_d1q#xeOfGOGUR?*|Er0qzraF?L8!lJY|FJzYe4DAl%pSpl z_=sS!j`8dgmOxk56Wz4LT8_SxKBseahP7pW1k}~fuDX7|`uXJxHMJvBN6;@fk2kXS zBf|CiFR;kv+gDjK+`(%L8H%s*EhS~=>3Na4l;cmx;_w4=)D`w z;u=%L+VZjl`mGt~VRQDCK;ZYWxbGKxOYO&$+(U=vwixmTQS>^kw1I3Vt6=+)5pCh& zs!)K>G;1`BpxsYoXf0(EN!xJ)=(MC+Mh@OZ-@NhFqIr4_Hpb8kVu|0dL>?+%CdTiM zdOVg9MyU@jxlX_g{vPnvnlZOkzSErnO~W6dkkmqNpdUtslY$Lq=9&n)YFF4)55>)) zEU!|j$_uvjTcM#RDYk<*p`#Co?@fIYz5k6F<)PU;0JPMJ+SWSYfG%ihBe5Ci3!J+c_$7qYc1ST8 zP7S4p@v*d9UbDcjwEQ;1a}U*p3$Cft$7if5x!wX{)S=%C1;W;6!o#%~DA>)(M7;Wo zdxs%Jc1jDFV z?&22iurp)qeHu#;*Tn5z4uf;BeQxmeW7Sa3CL8HdJfi!ph)Dr*OG=@iC})=XswdK8 z86k&JHkMgb%0<C?fcC=7h&>*2c`&b^GFyfhP%(}FNWDWLI}TNHt-jV&QqiTIx#t^KV0{+ z1(1g1l$2*t4*gdVB_=sNy+IUW<>%RX31@X^LoA&4iMTfW#zD9E>4}>+7!@J()<3(T zy1~bebXPsObGEF)f`#Dm^=b>PE~tk8nEE9Ot>_>2i>%e@Nm6#j4eawif|ACZw)Wni zS|mzW{mb$$kCgR4$9Q1Fi;E=D9d?~~{)#ccZP)3?dNb-lV?}r>*~*&I=TJjm$I;!7j~`)#DgNu8y|&l05H<4rMUmZ9dl{w+CNLRDZ?KqeVIGE zE@u#N6fuAsB73fAm<3@bK$d{^*_CglvuFW@1s>fUotnStd?DCfs!Y4Tfrqi;TahuF zFRCerr8JBRMB%_Qg*EZLACe!fKF`daQ#+Hg>tf*BxA?kcVdrl=4i*pkiRr;T_eH=| z#Zrb#3?>2O+@%Aj6OZ)R<BF>Yk8Rw+xA9M{wx0bldJ+^eK~QG z_gU3p>uyqQ>R6JWB5$^rw6b)3X-Gol3btYIq}j2uWBd82*lXp8k!()SX8!L!VK!ex z{4gMt%1fqRYuFGi_lK+BAsD#V==QB9YfHN01mB8R* zJm#&>mAay!gDWG?#3Cu0WE-Pma3g2WMH>}CYi;D}E;^|U2_Ip0r&AFFeA2wCz%FAHOk-QK3dp3!lq1nzvedl5UANTw> zV6@gQQv5(l1MEbzxx0L?l&wPN_Kn}V@Uhd3FpD?}7h>-U%`5&-o;-V=rZg{>AC zMZqHzsA+S1-22v*dhjx)c|WKJ9pbBqL&QRrTbVI7Hhy|oN#vAe*<5wN;*6>LN+m~i zC9_l{ito9?M~1lvUgqNO-L`0UARqd+i7KrRvNIE?3oaSZ#$>=`lG$-@&j}vW z`^=K%m!HLBf_oV79+>#WvPL<|fH%9(c9n%f99pwSD(@9A&8k)1)3#; zpy}_E51er{NeINUAN4-2Jirgi-;K+F{x*pW`aqk@d9OqGo1&BHxq7Sf!>C5*5ofY8 zx#&n;r>ov3BSfxa9iO&ik?U&0rsi#dP7&+sOvbC9vrQw0YHj)P6K!P{?5<|v4UPrkHp#Ru79aCD+=vF#iVkQoHt8>uMRi$YZb##3y zSJ@nt>!5I@4y_MO|7Rw;t=jHD@#0mTrFTILL-p^?3 zgW@Tkk@Ow8HA&2$n@_4Zp1p^;Yt5k#+t0t(jk>XeB5;AZu5S)A2aA>pk6cOE0dnn^ z{Iya%_!;H1<66^#-TITJgLV4y`7X!c^n)s)I{|TyHEW=COApfC96yye&;)Sp>wxr) zlRNiOCedP4d*iq2FB77ibQ*fWC?g({H}AeBLqe>nKxN4jGwzhqo?c;i%?goFQIMcg z_+5PnpMNuG(F4aAv}wZHz$#c@Dqil?U|G)>)Usfh(c%*%(|PF>c2qr(pZ zC%I!nRjM!3+cn~fH6v>!9R{8j0owXqI9ikmcV65a=nSluGjJa)5ME}yvHLMz{4eaT)1+rxoYStZV zOHwX%CIEc{2pFvUPQDpu^^lWsrIeC

?PNQ(l_0pReo}m>`hXl8;0e8%$tz4Bl&Y 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 q8t%_q+|L=s$-N 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%0AA@a;mI(_kO@5f?vT_%Fx>9c_u#Oj%11 z4y=MC6ZBI~$hhEp9;t^n8%iOJhK;79qM%PJPu|RZOUKS=wY|PNgMsL;O>2;bpxPI* z4|MtmR-B^x4HP5q3X%r?5So&bvVT!&8QcVol1iwOw7}4fk;3qrb)M(Qcj5gQe9-<5f}X z<zwri_}V z4F96ipNuJf`{gf)-07#T@X zRQr&M;EhY0`LPO%TNe-ymNcYS-_|FFi=C8CPobXu5)9Xsyt;}&jdVL}A}1+4Y=9Tz zMuhsQXSe1^%+s+uMdHd$UEa`t&+N@WRMG$^J0pu4TPU_QMqqZBD%-oTTC@W5;AC-= zY8@jwPFgI~-WlPJqJ-<(-G1xW5Gqw#u+m3!7L|}tQv5G8q?6dlh$%onLfsZr8VkJg z_FoMX9I4NaxA~LEd!yX0t;6;Y%!H=R%RcWj!mmB>N~*o=hN7F7Nt)_!jFcE_GMr+c zu@+!RRJm!M&^sbVo$sA?b}QvAm~}!24xw4pe+-nK?-p;JuQM3ob z%Q7Tq^C?nsawtxDknp_))jiad#3{t+L&@{&z&w;FVb-S32?IhPoR0OMO)`-TjUH?E z6sexJ2yTu*#KuGbP1MHK5m62Ygt&=1rZss__T^G|YKy)2HkRi`m2LQHrm zNBJc-_P|q?)4rs=cy5{89M>?B#N@0{TOMb>bbAk1j**X;#kOx+#Weq2s3kWIDN#2)z;b9#*o4Fi z^5rw9_$0O9Y!x13EioQtE4d zRZ0{*$!}}Ym|>A&63USrP>#J+opZ7;OFod5GWg_3hL!|S;9~Er8%nny%p!|S`TkH) zk7mwnhm5gOt`-%{bxi5J=z(P zR?ZF&S}#vrFlxaP<-=ArYkp&*X{QZt*y1E@L%A-w!r^>`>h$#3^IfbGI228{X+f7m2>_bTym4IauQPIPSsEkw|P9RTUiE^X}mJRpeXl#xG6ym#5R6j5b_vH09=*nr+}G+2X=f9!s!uB z$KmeVqjx97`;93>A6DVcBX>Q&QTQZRb#z}(b&*NHi+&E45!b;iwjn-bgzQv1c`>jc z{(b5&wtJ`oi|M%oq#-41P-#oMtbCz3OoxO-j1iq(RLRv+fPTr+B)`!uW%4reAS1Gd zs_b3Wk9=SAJuG)QWHHZh*^30L6B`#UT(q>m zzrdYwu4(GQ7+lLXmM@PWHE{}rhHPBuB24sjubP9p`m&sY~#KqO8 z&%Vh60yQ=w+*Q3h%WbFM3jCqGqT)X;tXpLMjNEa)?MSfALsm<3nMjHuPXmjqMS$nB zZH726j?(i%Q~?floogF(d)qPonWT92F5BH55R^sh77)wZ(Fy$CU^Ar%h>4FQ*e>7?}&o_MIQO(^QZpj-cGk}9D?64hAO%^dtBT^5Ya0Nw5%&HN3Tou?@j6U68fDh(8hw5lw|O}s zOCZ*DHFsV`&il^2jvc3_%;lKs1K+b_q7&XZM{3C`BMl}36|F_j!}quX#oI^|^a}nv zGSiLu?*P$3pxinbvhqqa&F*!lCQL0V2H^?9I-+7#~LC{mY# zeoJ+0CCJK4$oZP&vTVuk-%fYIX~IvcK=d9II4Vj*T|Arg7bu&oY4P%)C=?hJr8&$v zS$k8KwO@0{TgctItWvwD`c3-#AiHP9IB(CCax| zu|%J3?hx#QOl?tXB~o{!5*a76_XGNA^H2(fM68Sp?BPaIkqjw5@4ddZ;(1BQtTIir zJrB&NwrRG~miVvbiY!Pp3qH|9%A)F`Zx7MxvdOrg?tZBX?4bGrh)}Y~-XTr~^#k0@ z7l4aUSfxu{8DQDPC`&_C@46^6SToAervP_U;lpxlD)tL402M`9iyGLnE^JA!D2u8!*Fwb^xXm%1Sq2y;fjhi zH~Xv2z}%2+ha~raxG11A ztniQH-gSO-o`AOXjb=&qUJPnRS8c*cqN-A~(`+(fH#C0>?U)X68&X`cY+yUGuPqmVCl1@57Up)5Xw4Mb^I`!C@_a_}~dvS7$pxBn$KmWt|vPf5!0pzM9 z%!SO{%4e4lke-{d#P8R=*@0M#B;#q>Kqu1F7ezs&SVU=J_8=gw-0?H(E6uIfty@vX zoHTesrkCw@RwR6E$;b?~b=2w6{;LdS63JpLe}VNbr;qU;1AEy^Z}oEs7*gltmI7Ef z5(}Tzrp`mDCZOmNbO?wwv}QoX1ItGb(-EV*z|ag^BT?cmSoFj|IK;88L-=w4(Uz7? zyPB)qf%a z`hdU&{JOV+v<}EGG_-03l5~ma&R@$#KSM{^%GP`YgR==q>fm%{FhM?-_b_om>Cs1S zy|Q_aGZ7aIkNLH%>rg(*UAdkefI7{R#EE|jVbgY4ANfvNa^wk3QMCvnWhbegc=sgn z=arR<2_+IZ@I=}cwOG_KqoqI#?)_fubIzH^H-reVV>rX{EnE}yKsH0mYG()s787D~ zpVje3ITd_TaXPCheJ0;YUIlTDa1F7jcqg-35%_>ajau&2#LCPZZ3ZagsvG~RKA#F= zDIs^3(+dC~7`u#>4vXINeD*rs%)B_Cw2fVh_$(a8G=?x%Ug`ty&Ejjr%5tk0+0^^Y zieQdWwFfYWn+ZODQ6MsBDwDt@Mf|f{ufI=2XIKXR{Y8E44C$dy6pokG@4l{lfi~G( z{^iSSv0Pnn;>JDmg*QR12l!`m0J<0+^95{t(|DmsMsjmt#oOueZ?6O^g>X<{LsIQt zT#+dM>mz#$@qeTv0&7*`l6d{K)0hE%n;!C7XqYIWCSphu53a<&xgPl*`KP0pnCJER z)KC&3U8M3%>Ur}zupcKhqt5uHS&8Lk4cL%Q)QA`#clElo=Cpx09-hB@SZ{~F*L_>{ zy-%8|)+PbU;mC4#kloMMfR9ecl! z5aVSAW1jEXDXV4CPM&39r%FIS*I0RBQNp}GV&JyuSiOfgd3G;^sHk_+)zRcwUJmWr3x}* zgtt`$_yYZd$m)u@{2zUR0M<&>^ea9vqurwi-#ceAhp&b!(oz&=U9^6TfNExC)X6or zCV%P8aH%-lJ!KBZOUK>1cBC`1C>vSYTa8P(25@S3&=zqhq|YYrO&6i38=UM_?z#L;>Xf7 z2hS5y0{CSig0rK`3zhOjvlq{%tX0JV$qEvnrhJ5-CC9eHP{#~!={x-JVV!bv(cr4NbXUu>v`XwB%TGE$CuqTOfTp!CX zvYL`=tZjo@oRQ-v1s}HjmeBi=?~1iBc`~HSzX3*ZwO6V0EsvWsfbL8~b6Zd~o ziul4?l?Qr)*k5``tormjnoVFni^%2AgcCa--mRkCA`1EXa#ho3>GbES5`r~XauMso zLxkWNJy&GkMTqOuFa~{aZMELy*I;)5evz-vu8Brn&`(U#Z*=5vxh;E=3|>d84d+(8La0_i=%DqBiDQ-;%{2Suh7V_mim8OxIpw%41^njySN0LHm-1&)sl_W ziJIUoqO(*$C7Ot41apB)DGRGYF=RY>X($ZRg}8Top74VwNQ>}5aj-6U4eK)h%<%JF z;A{EW8`Dx_nLL~1I+0;#I$;IW{PJ0LDLr%lsf6A*W-0D(|KG zHm7|o3lY+ADDBr%NHPW`^q#Zpz83MJ>|W>Z9Pf*K`cY0ncck0O=d&lr@sIS-+@5vP zH>T9@Nn3HqCvLULnyb#|_ABjN#pwpQm^Ug~{=r&{Uwne{B$g>DS~pii<3@Y#qTK4& zZ>9yiRcoBA?q_@G%v%oLd{wc~O;r=r?r$gC7qAYvFFq{dYxd>%dhd2zt1{djn9#eh z;9Ai?;$dIBx3T*3h1hpd%=IBD^H}_&-)#AmIfhnQi#=Nt-;De}%eRm$L=x9EGh=ug zp*O+veWOK=jvsMOL2Q?F;=8e}?IzQ$@}F!KF}c9N9z89yx24;ee{rmDvq~83y7VL8 z5-(*A1bpn%ipp8$q3CX6tl9#zxD{5x%36!9 zvW;i4Tq)LPL@ZsBes$QlDzr`U^(t0q%kkm#kas0}T(c7v+|}!!=E!dw_qJ=wlouSY zJ1~zGf^s}X4?`MQKDta{@o6#(FJ?r!Pj}z+6Gx!M<@B+ZmH7MOynFuS%Vog_&Eg z!*LUy*L~}v65HQn-Zd63d~rFF>^+~FQTy?ws8#MBL_7$HpPH;+Khd1*H8MEG$2Q($ zpV64yaWy=qyZiC`__@N&#jpppswQ-h$L(=Vy(c1vuGzC$!2#=i5yGymj!*t^lQkY@ zZwD=>7S+7$beV>d5KuwwTxa6Do5jTsQt29fmUZ-ZU|-N}M{L0gm8QS(35eA$9D@m& z)5Os#`i_wAGQGX8Nz`E?#kO>G?v+TCnz-%WGDbx5pxEn?drwZBJQ*tk@{>%jYHpco zm|frm@0>R;xC5gQvR`PR<;LE=*8%@HBYnR~Gl#g0JLHtWF=F5e&Fg?Zomyj*S10y6 zKq=m+^qw&1XFDbK@29dT^T;d#lhwAj3TNE70bEgG;s&rY&+KOVNHSK)2EVQ!MA*NT zmP?g}nVFe?TSI66R4>kf;4;o(9RJkAHf9Ks4CH}+2(qUbqVXNuJTSeVO? zTWzea7#xU%02lKeW1ssugebCKub2|w9O(Czg=k-2{6AUt#t%?=^lzIt^(>L!l;QHp z^uDO5ScvO>=G&7l#c@943|-;Fu!O6&HMw|+w)wrzt6sF_?Wc$jH6s5sX|f1zllvVg zOAQYYSh3FaZ$SgAaL+iFvzvO?*Y}D6AA5*o&}O8B z0+|ro;}1Iq60AOE+3^n;-90+-)5G8sNZ-4~Y!q_=_@sy&=SHc-9BSAN+=`MOw6OE= zIjQG?_JIa%Y(^a(K{Xc`-x)>XUz`yIY$NvZUbDMMW`eJXS6bPxAKU+FdfqsK;+@p* z0B}uE`{Dhq6a!Y}QTjhDEQ}^dc;W={vG)GUxOSh7-f8 z24<^*3wMA5!p|DJq7C^Rc8#la4g21Vt6xZj-ZwpHt*~JO<*#x`LgDkzfh*Q!)eB+- zRJ`xKXDmmR8q%PUtxJ~J=r}S&tOy2rt@;y#s0W@_OMx-upt0TfCpt-<%UNsvWfZx_ z84s^Fr%lhe@A!V>Qtydw%If4~93^EwY4_f$G0swBH8J2wV;6UQPkbY4ZGbw6B2_lk z3s-s|c%_2(6EN~C0`uxjDG4=o9H}{==unclL0e$=Qva<)@-*MgimoHeJaz;-A9S?7 z)tq+jx#EPw)rtRkyF@vkxdVZd=076lUAS=JF27;O`7A|y0XLAD{)oaqMJiw?=7W?p zg3b*->uZXNekhMNQRRR%fhWncBB02!N}X!89>um|9@o^umM>z#4@H5^G26FST~k`g zIPh9|C}PtEABLz~mii6P{O4i!IRjvkgH$^jQXL5S0ej7mpW8LwuD^r0K{u3FdnRJn z1`|yE_~_^9qu(+q>}3`>)p7JEZbg4>EVJ8uedip;UnS#ShE;qku#t5-Y{eV{sLDcG zX@7|w@KmaFfMFy3c(GT%{Qk6|GiN)_O!FVlZvBynD0F#SuPc|fqUFV~6z}*4<266C z{L=_Uv$*t?3=U`%TrGO!@yvfpl!n+73!0!LB(l{y6!G2YuapVKYABK#xCCfi2;dj3 ze(;SE{vu9&WQt{f1UwYjT$ZZB=U{sSzF3w}L&_H2nwJvbVnO2JZrno}oS+&Pxl zaUdAp>ge#ZPkHN)4;{Yp`q$t8>2Oz0MRS6mP^xkxK0dy=s=mISzUgS5_ey0PZpy=0 zC%Rxw?3S2dMAXiI{*JS~BT{TI;x-@e#8?ekbQbEOd=AQ|J3uc*foOOj2D!+X$YWqm zKQ9=&^u}3V*g!|NC-!k&uMga?=O_)3V-S9hQ#T|P9;JP`#IR)z3v;zeWPcTgIE=nZ zxf|4hSqu+v>=r`uCdLOLw{jV1MaDwC-{+oQPFt`rPS_W-zrekgq+Qi&W347ltR_@I zQeqp)=gILWUPdNf#jQjt!Z_&yvM9#7P2A7i+$OPiis>=eiLacJxw{8-=Ul?N~S$}tzT+cik0a&EV8C`0!&P#aAk$u77|cUQXi15OC0N8VJ~-oMgPtUM`qLY1AcF?xhNFG(rO=w}t$oczJoKbf zZVqdRyVZVuZF1x2+gf;D=?->ZoTl%r60%tl|4R|%;y=Cpi@FNN8*RvfP2^PWgBi>!A1B(H@Ay{;9me|nfZGTQMjCa-NFK_i+ zlvjOwN%arkd|$Qki#7HNi|l-h4+H3RZDD^L2S=B6r7QDTlUP=kaC1$4&88TnaIU($ z8W|gZJrMvsD!-qFYklNf+Qe)bj$7rDndcFFU?8I`?k8)bGvxYq{f7-44KNfsVGZr- zeS7x2a5UCj`!mNI58i~gb+a;h`^Cmk@J6@0==3XxzXP;?{bOVu zvVt9XCJEWs_p7ycG$Agsv;Sg^vi!-m!IcA$#|ia13IE7)WVBQ}A5MyY(=$1@I!e0#)zc&PIUH-e%P6Wr zr1@1~^ZrW{L&wh-WwWWq7pJO|h2O^RORHn~Jt=Xm_teK-@&5+)Fh9UL=u^XW;S>zN ze0H?W_;lC2&}4^SznE=ufA)j+SU6!=LnHvorj(XhiKJ^WL`k}=h^6eA_VeB{useF7 zNrGa}FZaTNe}t4iQ&OE`4Vb0;;ck^m@#y{st0o>he}@Pg%jsxoKXka`)f|d3BUIS< zlFd|}^;wT?!dF=$+8}t(&x<>7wtx03GIN973Ao@5sd+ z^l#z3!<`B&r!dt!^Lr`ovl-9U;6Q6)n^yK^DQj_$wzjW+$R-JlUo7+Jj>G2#y?*3l zc!JeHOS)dyYm4(=ENEtvO$5Cf702#P*@7;sOcSG3kPnlH90n_tEsOt{mXTdMc`1@( zBcAPSQY`Bdl#l*>2I*fmf4-+n)*0y=z+7!# zHfg{(*wwz>`#jEM;&T2U#$jC@D;z}YikcP0LqF@4+Y*2{@i-AoAKg1)?K&+z%2|z7 z6vKx84X+JG`2*!9W>H(ujW57Ix4+LY-AUkTmMQ@{{{36Nyy~dYfxd}Tl1C8u z%I_uje$GCz$3Q0O+SsvR{FI6i$MDe*_Ps5$hd&zzFyQBJ^osUQW_j-Hw@-xLOUoh; zM%5A%3#4~xBu%OKi3BMk&>tRx54_Zx2+?rMZuZ+T_h(oO>T+!9)*tJ&%xueT0kI!| z2lzSB%z=jU;YYe8nx7Y3zD;0OKqF? zKHA6n+Q_{VW2Gf*m(pA)wc)Lrq?H5G1bjTKQ}6RIS*V*A{uQ4ZKO|A!&H4TA7I{MV zo{)48BT`U}Xw=z9+hz?ztp{zI$)i!#1C=xUM}g%4G@ir;7=9m(xj|x4h z_bXDxsk5hk#WnJp6PR_nz}|BotCQLWPKVRNFGw-$A3 zLa?5ZP(Us5`+lkoN9)eD)Dq+gvW4DH7XV^fzi!>y76v;^4N;Xgaw$~uQz=!1CHdn7 zKE)=Pm8rgY!{TM+HRa8-fxKs`Y~i@q#?QyzYHFv;w#D0wJf@WOQvZpArq29Z^%*vZ!5SB*AX4rikykis zMpiu}^+>f^)Tkp9G%M;OSp4F}9FhP*cg}T8NhjFc6?Wv<5d)N%S2X(i!|S=6PU>>n z4;f$fh+j&{NWI-0_|>4}DjSn{^@(mYr;8FIM1d@rWn$ohzYbVLC6ygM85cq9uyX4a z1I_&vIOxpcMSGidRP;-w6t&cryrC1tP+(;UA47)*INEA$=(nXDIA-4IhRx5g-Rx?Y zvg(WB+4`Pos-xX3|CKc$IB!2w2AC&W`X7Do@B@jQ`>#VHSO?6UB~-ua*_BVF3?tM7 z3EatBarQ%i)h|ea1A=(VCY3k>avG5%;q|y6f zV3N|cuWd_@vhR}>5iNuh!2`Q#Ep7_wu zY2k?IvsS~xhqwj#DJ={zer3s63=G$059c{s(SfhcL?u^weLmB`efveVIHngRQ6pXl zq@WJ^9sXH8&J6i9z0d3@x`naytJj8J)s0?hcagT;5%+AP{KT`7GcTT4M}#MDZ*Plq zYB@{dK@xS*sA>`-I$%uaF#hR&t&70sIzjdz;Q;Ait$k&20d3oLenF>W5q<2>`j-T8 zMG5G|G*JHQsG^O^VsfoPCr=0E?g_fOCKdSlpG;lDD7PNF8)&+Ut0)DFJGQ&K%GE@b z;eK+Z27v>zvXC2?^Bq{DWs+>@Cm=;NY54r}(s0Yu+l+qw)WB(}zgY0b)=-up!i8GH zATb+eD{i{Jzi_C;Cw&feL_-ZrQo1xCKx#6li$3RpHo~l%HV?;lGl;agJo{@emU>0$ zRwr3`W0xhJx)Kw!XX4>$-lf=cId17Yj+m?K2W)j6|2A}+c!Y?#?6y9d-TVY`Ly;$m z22CUL#H!iLT~Tr#*XkOa9kR#Rh7bWLdp*a0Dwuo&S?CV$wZ8K^Uh2*IFm5ZkB34an z8_+^*Q|4%)xqQ5gOs1?82xI!QUYyu(WD6uocLKc5COb!P$(#e&ntEDt4kdmWI-glK zP6g~kI`^6_#T-VT`t3u@j`@E9E^W5svo+&zwLEG8vff%z*W@WiPHSRM`XIe}F zd7F>MGz#zSxEgc5oMH@i<0ZCTeKr)Pi=!)r4F7l<44T(h}`T1GEM*I8#VE}0D!f@K*# zm{+67WK6gkPmo7Go{hFiV3}{;D5wKmb8OKfL@2lTN=|Hv58KATbOB6*9=i4^36l+f~-1{`;R|18hyp-#OCpMK|nV7vfFp za>evXzesjbh3!bf(Y(<3cu~Nvvs%Eq`iXQ4;@Y(B9=tE#*UN0VT(98uF}j2J?y@HO z?g3U@vP_)iyMyfe_jThlqbg^q3eO@=6D*m_Bp4Qm8L5R%58L91vTEd8E+3!F#f#wd zNaP@E{veUd1}Tpu#vYoBe7t&3_2sz5OMC-F=8f9`r=XZ5;M!%?2U-6*k=5%Idjjbk z<8)~%1_D?XIvquKZX|725~ffl{#=`ni`STrl(-gcI|+tshosMC+~~~|9BNZ2(l%%} z2|$@;V>mbDW}_N}7^>4i+$DvHR3|~kv%i<`IAzDG+1)x4VjN+3NZz44AuVm+NZ>MD z%nA8Nm3eAG@9U=jgy@g&lx|i*d+t?y|NC-$e_(osu%YcqaSVx!Y`7W_*Gn|%P({~h z;@SQIH)Ir_$-QRI&xI5hMr+@{kI>Y?enm!k)>uNIXfFw1MITB!;Z{a!7=TgNP*TSW zz`)Jy&+E^rm5Dw*#Ng6zLML2!1mq2pvr@Z+>cgpY3p)&L$xfk#A#e3woTK-blSJK< zLhF{KGK9qzZe;)pN#JMKkX<8xX-~%QqgS3C6Hwb4Hb-@C>1mMuOW!#f(EMDYm zyr_Q~dG5#JL;g{_zvnXFmSQA(Oek*N3f=D1jLJz|yWZdRUf-1)Q`5F5`cQUFK+~Y6 zQ$cB!L$D<+m&(eASxtzMcnjOazUI0dk!1gfXC`6cE<=ashwRSd-tq8I^~NI18vQJj zY)bXV9h>vC9@*wK7I1__(yElvS4WYme_L-;S={m^X#H%`Ioz{ZzyJvM?P}+f$VpHc zS5oI!T#Y|3l-~J(^^vo1&dGEnFa=qxXTJDTCW(K}RO9sEygL4p;%f003n1m}A1yYV z@cHi(en+TKcDvUsMN@8bg~Y8i#j$@4F=S=q$DWw*;AJFAv7YrBo;bmLpOBEy+z;a~ z*t~;<%8Wn5@d%)lX6&-#+Ho;4 zZXod{cKm!Nh}8Ghl1{u_7}twrQt~TZ*Eud+A!dCSzX_4(6rU_(Rkv=2Q)_<1;%G+Q z4gmHBZ60{Kb%;ovTuk}=TeISZxr(&<|Ne#?TALmaBz$e>*!}V4+z*hyX&csWu~6BE zc2Y(Vba4*uM7zZuSah>}4Pbt%KImWfO0f5m=O)nZl}Xynwz z80Zk&K_vQ~D>^AAp};v46yMaWCJm!_ISTKwJ!Bd_Ex^Uso8jVf^%rwh`4$rUw@;__ z$n&bcBp$9*f#6eI=K4|%T#d1ISzi?e_6NYjjd%#^0E+^FtIhb&$6!z*T+BUdN#F=_ zNRlnzHHr@T&E(?E(--36&2A9ncofK{XK_r;_`?HHFj_eBV73HG5XHTyAN<>z>KiS8 z(78z>`|hbXW%);HW?wnmJaf?FNzetY3u=))q0WaMEbBVB%Q9Y{onXMng=&$NDq0c)RTy^ef{9DU5EQ%&|qrpbBFrPg`JuW-;H0+ghw*B zPoMxZw;Q!_?^n8;pDL*v$BO6pbZq6D@M_M#gq=VeG5HiJB-tOn7ex--v*|+z6M8eR za#66p{+Z^*`8MXU)4rF<#;-k_@_aZPOEFiH$8qSC zqS-Z6NaS`~wp7SrM9lv0FF_q`G4{v8Z5$ML8-;S%O-&^qmL58Nld@gzw9ov^FD?Op z^-Q2*-ns#lTbeDDuIm5LF@EPRO8+{7r2x)6o1nvY}MnQBu?$|D2Tl|N1$`=t))#AB(?ip3tE= zR5`4un}OcB@keqz_{#tt%%LOb|HTJxIPqdNNIk)(g{buMy84P*(d2eRj!hQg9j3F! zsgh3v|8;$yL>0-dlo2@i^}MYpLAq>e4>Kt4%#I*1An#TFb%d=THhxnx7`4wNkP#(X zgTi^Lu#VCc5fD)DpEow7$^rj6$EiV*wppm)y=+6Rj^r6_thxczs&WTAj##Vc+7EoVMe0T2mFpno5&dL*wRgllEE?a*V^_+0ATw(* znmrdo-h%N$63yF0AGDDKf__CU#_-})Mb3R;5bM&;W3xe7$)p7V0_mlD^ zHJM%0Yu-|^f%G^o7L1>EF5CX~1CK)&p31Y=^g^Ncr*CI0o=*ZmZUsvrAVGo&bUu0x z5|FeVZCnh^8F3+Iyp%QnZF+fgemuW;7FE{oYxR|ObXffBpI}RBp zd4?Nz#G{Xyr>#3xjv($d3|KcdFUp!9=WZS!?6K!r4|e)d?jDuWOHsoeW2$uY=ut+1 z38CALU@3b_^cDjnIF1mC?hO*O+@u=-#jhKOMDgdl`v@-Y9vojC$}-(k|F==%-GkaZ z!W4*G)CZl;e4jKKc$uMS=7Tpzx40&<|}lB~tZt1Jmy4u+g#wmFvECsw{!4 zVb!J1^;9U0o6B@Z@T6rLx%>hG0@DKjr}5;ig1xIn``SGu$6l&Wt*K*yJoTY(wBOnM zu^V#I-LImKpGMxXr@nX)B23J&iwigKpM;>zX>8>aL!E$^3>dhTYUE(xO)vjA_BQ43 z{!bV8VgpL$wYLBq6FpV?gsNur(7TYdau_y*K?$$lMw6w~qK%5V&@*olsT!dCd+uiA zv9DS7{r~A}#$L+x$?e~a*7)n}ZvBf%8h;sA&6_U}qRP2)%fTx{mVvt%c~mMj4wb*U z>a613?H+2gx$G2zzd?;9(!3;0m5B=Wh4d~ERb^qE_ zscBquqVU#ioaT$r|MJq=Y4fs>b-6*;4&04^DYz*qMQd=nzIQW#(C zAn})l48VL+c%ouz6$m-jRVO4Q(6>^jy;G3leuA4`3Xi}#KiLLjE)&a}R=Dzt)a1+6 z2ykji2M)=Mb<}&Cj&fZRKZni-5ruUzWbsfDS4%ZFcoC&4LOvWu>Jpkl%A)(Q9w|8E z$~RL%=5ff*T+dUFBIR{T_w^AAn9_04+G(k|X>;e!jfWx;BjG=J>FHmn=eKo!;s9Nc z@QbrjKC%Ogxn|}xPo}Bk;(F?KvoLbysaOHb@cUgQgPOBGm~nl0c~(7%SE^G{cMn&J z^1K1&Nm<^zJiPI%@E#}4$mr;c)aaK{IzmC}J4o}o=jP_}_ij91w2Xw}NWFt;xd?x! zK~#e)LaOk)wd)X}_Cd;!n}%@#3Hx=A@haQ6Brf( zHJhP4D;f*w+^nnt?UpK1Nf?G{Sag2Ik`B)`0QN?_4u<>v7gOIi3g>G!7BxdBKvHxz z$pH!CqI9a60gD@!Y|M1)R3`KMt+xYT6jB-)H513~^CbPcl+YaNUl>51v+ z=pbFPcbErPy-VV&4&6H$c-F`esiRhB0{o0u_tjbebKx5E8|x=8SUB^ zEYQW%0O8nMx@f78Z@Alj#f|Pd)n(LUDA^G zJ*dFP+O5l8dz3WfAJN|){EY{{{M{2Dc-wugW*L6{G@Q3vd|kP3aRb-Bbyv0KvlhfM zANXW*p+sXyEgr~N9JMQ;>;#1-q%!GRKDQd2Y7%RlJr%gjgNV% zRoha!M+$2u4SrogqE>5e1?q0T~l0tRiv$@Knu zGPwzs$M#@rVZwETqZD+`#T9<_zL2_jP&b*#q>VK?=YpTG?Wd~NihgC3j8yl73X zakekmZQ0UjCbN(Clu8W0yj^|Zgy*>o^;bz`u$F~PY}!yq-l-CKzRV1$B1Q-Hlc;(u z#z+lx23*CD)0b>s4_ydM=Z+;72T0y-lI!=%E-=&ZuMKNIM#WvCLUisJ zSf~oX+S*#B;6EXEy2@WLJyRfa_NcbEtwx$89?xV>4zZLP?s^W1%f}g`X#m075r3EU z1*8t>*SA69tuJ`AKe!JcpJ1v@(^{4>!pKN16f?T!weG9ci|xhINS}au2c5}82c_7?3_}c?3du;dNY|M6#pOg-a8=5YwI2!<25EGYD}=8SYrW=8bK^5C5a70Q4o}# z2!e=87>WfcH@O-`KtfSbiV+c&7D3dZnL<%Sh$tWkl7N6z14tM6)_Mlf-1mKNe*b^h zKN3Y`X3lfYK6|gd*4oS9k$fJ;?^pBGdeTvwpG;Wzj?Yp0ymH}b!kBSE(_cI;axOyx zol~RCFHRZ*QNLX}^bH*BrDu>A<1@4HAHMUQVx+;l1TPeFia&@20&Tg-c1TA!K6C1H z`9aXf=}C02?mO8TGw9{^2u`x`Ppq$ejEHJ!Y+CD}d4Z4f(N&A6-qoF5Ctp9vOSTES zG=uB4xRrE^q*3;ALEvEMEjHLH{bHBJ8;`II*w9tP3!_U{QjbgiClI z$8FiObKh3b#}D+D*=cxapGO*gNSQwkceda5ozA*&M`P*Oc?Rhbr_3gD4$`6aVr#<{ z<2_0r05VRLmLoD@vT_(C_|{`kaGG>NxO!cEtT zm067$a21%x zX>fTmVFEix#OUeAQvJ!>qT-0Ww={N13;f@E_$*+hEvDgG%~e{2(Vfwu{WH(!J-<%v z`(V7OLSb%&`e=0r5PS_41ed-)rjjSV5g)wYUQ6l?G9|7 z%8YA~e1?#5X*la|*|G`<`F6VJ0EJwG`6)u?iiCX|##4Nry9592CPj_|4ivA2^jlhHhsG&^< znq+f*;GV>hI^i(d%v@2KR{k#k??t{=SdXX~GfGM}OB+jY6 zaM6L5r_X@hd5bsB5i)qc9upwh6|nv?t2bf*LS(&X6rCsw5gzCG8LDqZwh}aQ$)jm% zh!y>vcEq1JG6Pd9KA`a=jg9Yv$F?i13prUj;>Bcmp&F-P=1@U*P&u3s=`j3IzGDmb zD+y6JS`np1uzl~u&_@|CTGy5+^~0ksogSc5Q_T8fFcz5a6w!J0t9NSjVUGy_xj4i5B<6a_T$F*Q z#O3VKlV`?RY6>0th=D+%ClN$P)-$i_39_&SOzlxFMA?)KTMC39gGU}36~VASgcfe% ziSC#jdXza`qufgM zF?<#tw+bip_r_Ud z`2<3OdT--b{pE-rAxoMJjQ8jxKhK#Ml8ktpO=fUoF^%O7>NR(vp7eXFRs@)KuEj`H zF1Ge98@L}45e*EsY}vwT3V|G|T7@YPzKIEJr*}oF_g;?2X18&7UxGn0*$3c4^!in(^NdW2dfnX-C2znrP%_B7OQ1o=Uj5qSNL7f$HfN%<0v)bgj6$ z_QVtgckAkwCtnR-XX20VB&Iqc zGoeyOa{V|_4qHlN(F)i;KX}rytovE`yi*I!&B7hjf&6B}JAX!FF)rLaK6CHJU=wdK z)I&-CUdR-UkeZ;+te6&OWA!ga#{hT45T$>lFn&8a$%faf++4JJkaZ&s(bQ zR^r~y)hfjxt8~a0PK)z6pM*0yIQ!>W18P^n@eLWdX|^kaEU941Dk}n+m5kPGkY?jW zl=4=oUgK~GjwvetSKqCf?cd^iun13e{HzKF>Z=w7O=+5o7Q=_L zRG6Hz%z-;${BM`XmLluy;Umme@Y5y+`+4e zc~*{Yz|+`ANph+`DK77-2@AU~&BR-#9~)TA$L{^T77RQ#UUa*?X(SV_%7|p9Y@_2OR-Ps~z3v z0tU7Ps`kwW1_mC{e#Nig5f{y1{q+?lTUlIuH?v(bGrvZ(Yr(O>tY+|?zEt!tyOOOc z&1*(1ENjp|F-5fkC+B!eOHSWEHe;lH1t1b{_~vbuSOcuEFVNq*!%;=y($0H@3tGFDw|BLyMyxv4gs@Ei|?skYJ8-FbKi+Vt@BXoqm zJ29j31R2vptqdc<^GJ29uL^F*e82%NOl{H1G>@^Ek9>5Ek6e(nVT`0W(enX7)@!4$ z_F*7nCZ`)kEgN$u`rT6WUnn!UmS2-Y+kAYB_#uq_-c*ov{eDgw8fX-aB1iVoPt!@0S<^5Fm( zr`sd}Ldi)V?1+yEm3=6HOo4%c`my`a+NMpkQC1h`jZMWtz9LSIk5^=a(I{)kv>x^< zQ*nLIA`aS#{Ga{3ML6h!^EDDWZvZGaeybfD!;xZ`ouXTH5TJ7<-o_=TV7ljWmSlhn z$35}~F2xUA7bES>Wz-;Z^A78OS+g0M^py1Er+(`R=qq$^SahO~5_tEg(c})Sk1;}n zKTq{Wq5X^*qc9|pWu8$HKI|CIXbe^A-uU_= zop*V#YLJ|KD6N40M{Rf_sKM^0G#@jowTh)eiF2|*f54k#Ei<=>J`-dyfc~)Q#sMqe zAmaa09s{d3?}^G+oT6uMI74?qJV}gd=Xjw+pJ5u#tg>B*!7+WhkN)lFk-Cb6HkXz; z>Se3;x2pGlg!2&R6phPr;M`!#GR@_`Z|A=^l19aCgqu*^YfEbIsz(t#v4J==N8FqK zk&c(Jp)xyq;heis(uZMP7hir;UPpjX(?4$Dvqzzi(16B;LI>|7*|1yGfnax^5$EAb zoC45ZHmfrYU*wc&d=a0~G%Afq9+*1=@P!JB5eqapY)z9fFzh_$@*T*1Hc9gplD}>W zu-x^0RiH%<(qi0HI1-n0EQ^BChA_8^zx9XFC|q&pmfbtMbRkEw1-Rq_U60PNMXZ$) zItcn19v`kz_B80TY6f0 zd)o1(XTZ$@1$G)H-q^{8g}u2X`J6y}(S)PL6xCcB9$jt5ZL?MK1lTjvFeUs9x<~LH z7WV>BI*x2{L1in;0>7&Gfz5nEt$QagFI&?UI9L`a=yM&`lxuER=MzL?rwAxW!t7xS zpddsR`N{bIY!q8FgMu2)06cF&F6b_vPx{NIL-Z*pbP!k{vSObPAn!R*i)b<*P*2Lt zA)xz)-tTIZRbXxNd;fSrUIFf?gmG~IQ5RX>_BE>dhlZQ5q+0uWnX@lrjS-!x-630t z{j&mmhy>DN$!=q-h^8oBdFE$<8nF1*ZPGlob&uP1 z8QZtkT`}npZal5ED0lT@7n-Fkt4w&%`(!da?HAiUSy|aybP-kwG_DD75g(Yp)e|4q zaKS*GPyRw@#zMENg^A3zl#5LKkv^>-Jp6ulw4Lv<9=o9EZ_0gG`>DUF5<~MH>_ac? z!>LyB?zg3BQ?us2=N0UX;1EVqPYA|Ev?k;Un-8i<5aMyLhVHlSYgk|5i z-Q+-*>^2P2$wKcee#&@{oM+gCi;NPiHc;}Y(DAh5Ogl6i-Z)VD5;bs6IsqsLw%x=d zl5Bak0om`v)>tRp+p=N(V3$UkOvM^xS{GH9q7|?NX1*c!i}}_si_4@4^2 zfE#$_Ehu#R6O*KN9CAh;u`b?3nn$!$VmtbfDi>o5fvF9O#p!vny^(|W*Gzxx8{wet zn4Me=U^4%!*~wOnuzuKTvdAmdafdm_+L9{^)G~3GK1F<0Q#!VYcT*^O(Du_60bs+nyin>N<@O_Lp9m9ouwN>X0$ggAa6N0q1yb1Q z1U0yvcopNc2hrM#^DO?lT4#eA>V(y@o}Jvy)!L5#bG7oZT58hOdXp&HnMwr$rk2NYzPF_x78P&AVLtH@W2KI(u>*$ zbE-nM(>DEeb*eB0NmKF!rHWTB3ahPpvFLp+M?w^Qzqr=1zHq*ZbHiz|&TIjB8(dV$ z4friBLn0U+AKxSbC`C2_|pAY$R4-hw#q@q~&{=VhM%i_!t zrZZk94S6_ASRs=KfPO6p7_mo>5^!X@p(+%msI_A|DonjtJB%q_-t+0|^rce^*X;oE zrL3mJz49uEL{tIA(w3+JRrSiR6HDA1J@k$RESzdA6$L zP-A}TUdno-G1k$#dB~+H=#I!696l80*CeQI*s))J9KtByJyHaITE*GVd~py1Tn@H3 z1l=$8pQE!;l7yI-@>c0ReV&IG`!k>tuxGHW`4P1 zD`mLZf%EXnmo6P8-ORcfh*zup!nkgo$*~yKjcK@k37w)XEEr_MNdiX+q1$w)swZH{ zo)T#Rexie&c7@AZ1vc}H8A1Y4PqqehIF^p>Xlk4ir{bf@h8UZ2Bx-gW`~_}hTF=IH zF!Sz?u)FDm^bmM~zIw6Uw;jO-h{71+@5rN8MQ#i^4>;+5v|&O@)eG+ATGCGiYC{_c z7|VzbdgX4E-p-mQ@2Z_T7C3KfCIy1T^!UE1!9rnwA89|h5(T&7s(6}j1gEi&6T;FL z=+#tMRd1{B#!ATGweL6>*q$0}%ss`$_2z}ONd*^|HePJJE;u;lV%TZ4kp}m(>>n1;Z8lS#AYM z6Nm(IWWNWxJV7GFQJ-Su#%*H%ofR* zBz1@wk3cA{Ui=+VUc4{Es)PAYB1IZ1^om1vS4DUT2-(K$^iueOV{5ZAyQg$2jU$k{_ z=VJ9Z8zR7EMA_*4s*ZBuc0|fnkWLma`JSYYG?SJCP)M(7;lhO{=!C{$T%1I2fpsOT zI=JRC9#(`T+$U86sE-w^zWaP6P-C^TQ%P>%oLQmBMhbG$*&Rf$^LFKF z_YsHL^svB1+L2W@oT_a{3u-Jf8DTx1%du zkhI1QM~>(*KRs(KYUd^{-s#)3YRI}MDWi-NLiI+;tXV}YUN*$k&@iaI0mab5@Pw!+ zG=w*snN7h)ye!Vc7P!)TI7!Ip#KkqcD)7j>@m{a5QEvs9mVr!EhO;zRt>ViNjA2(Y z+$^=U)aYT2$g>=Ah%lZHM~GFWaqft$`_685p}`c3DD|&*^KE*`*|3NLS9*v2%2ztp zav!hsxc+J#vpk|x0>umqJhrN;N@)1AF@nW04+spbnyG`tV4oTQ*ku^IU6fRapuho| zEoV24cwI3qP%5^HAL5sTi2BSyl#!?1zI0{Q@O87Lq!@7)zCKW(rZ~EP5DT=Vqs5D4IS$Ahf2(sf?FiQzzO`wx^p3jp7+LIOmgw24$aar@FscRnOzMIUDp6&)AU4`k<8H} z92Xg!j@n6F!?z5o{MSg9F-pm?;nAWzS^fEg4ahr|Xe;s|v8n*zo^cnz`vITPzH+F- z)*_%!Mo6xdUh#kCmkF^bJZ0q#_r3CEs)6+31dt#;^MwKcw9!J~U>u@lz_kPykrO*L z>rd&$n%}5Pm(MI3d2Ydd>-E3lpSnyAYrDttKdEg6p9eCRf|(j&dWF>(%QV}h2--fC}4TGDaUPApyD#FFG1`315ye?m9+lvhj4i7`-4!a^an3u-z|a%W}w5C z;@C0Q^Q*pjzo-U2KZ{wp!JKl4W&#uR5h0^AHn-$VbBOr!$+-#UOFfcSh0ULjhWK&x zsuhX%amdAcA)`c318j{%>rQep`um>uXL3k0C{@-!JQ#AxU#inM*K`JIv6q+(zCjX+ zYoEiBcgR@)2D0h6h6Sl?jhbibRrl1;KQ+QY3Sx~dK^ZiTj606uxr!iXT_rH<$P$J2 za(=wq-qeF_+|j~3L`2wKP6kQk30=>Im1i2F{}ot2u&kb&6|k%zXgsc{I%V!d77cO! zKqV||18gfc27|I`YYHIEjFW@1@7`?<*5CCy0s*rrU*td1cjW+I!b~CO$}A~8Pkv3r zZ#5(CX)n>R`P4sTxs*n6SpWTwEmuC`$GZ;>Y;cXP7G|=C2tzvPoxr(dztxIl0=GZ2 zE6-F&w0=9|&yt{#!{zs8{5CJmTW^=^bzf#Gh@W;Q*luj)%BV~h-9EN`e^|vCuz^6) zJkX3j?qUx6B*Kiqeem$cchBX@$LjnFnuQZdw-#XkC=CB%#tclMGl&{3OYUyfL0mws zEmiu!mUJX${=>ByxNTYrYCM1Di6T8{C8Dt*I2J?YRlzuE4xid9KU@3s2MkXJZaR_m z!)x7itkRcRq(Oqw>{HFg34GqUcofER@Xwj#}M;J|v$ZU$t7OvVC+0yz$AYF}UH zhB=n~bCiljceYEBuv%0@s(3T!766J+MN-)wbSF1hT3`P%_lV0}rcC%G7fUNcvrA%- z4P&;^MD+X*{Gc~Pes0EZ|HAC z0wm$Yh{NmZ*^^Y47!>}Z_IXMPnZ6%OKT8wi$8s*Pp7F@H1l{+ zLk^;T#IM@dZI^q@94L95QTb1Pbs)4mw&?b5F4Rqn%m){Q)QR1oRbp|B)7MPeBSUl? zy}>oW-8o{AeG|Up6znK6F|%6p)Td5$>-;LwA{bX7zT-IEY#g?|t5GX|vG1hTqV^lx z_cv-aI}N?G-t&j8t7hIXCq_mlwtL=;y(CMZd%F`evlwlV2zEcWy-im&LhKWk?lsX4 zQj92B#7(dhM~ju+^5}nvTH+==iM-%);=|?*p(7_P$vLn<zo3wYQ}&V9RyV;v5*LDtwIo22?Jr##Tk z&p4m^0bfO1<<=@JgY8(`49~F&MfC-D^LA`bT2|Wmfs3-@A8Y-Pw=VTsWZvO*f2RaES zh@gj~d10uoF1^`j>c{q&=p(Rhryd^NZn_DF!%0k`1DAvvZpx|DUNzvQg=zQL+uKQ1 zW6P1ufF;#9H&gJtuTwBMo}yPThazk&YY^$lZ`GH$K|x!cs9vz@b>;jk2_PLqnL6CF z9QtsW6b_#jGyspQBBRKY*3Dl@BK;;!+H+0%k>HMtym|6@)JEB<+)sd#R-;EvZi+V% z+*QKFW4K&>u@X$i%}AY>RFgW)XUHWp-XRM3q<6jpVsT(a{o9*P$$SxaX=AW%-FSk; zENcmdKf=nBRixh(gKhzL6Di3&bsRK`iQ=qwaLY_Vz|nyd#oRqP>M#EoP0v~;*a-&1 zMKVTT_Livj+MdVl*e4x8{Q|r0_%jZBSSz`m*s#s59dJ?SG<-oyTEY*M4nf>x`82Xs49z`)_Uu zS(fHFbee2SSxuiYP>V{A8bqzKG}C+1cTcjQbZ^EG=)jdAdb=;xixq* z;ImiNplpt1T!@Rh=+2k3ALIHp{YuJgdzFF~Tg*;wH@S-?5y_l%nOpn-K~QMwvH?Ne z3%l>nisH-zPFc6toZ$rB6DwQRX`lXdIttuC=YF!!(N6M0iDNA@dYa~o8#!ZtwcY9& zHwhf|_fyZS*n6hpAD_xn>uLCi+y)S=NX?r}YE@$OJ4IH<=U+dH>r{!;^+D6xWJZfTH%oWHc6aUqltZgYu;+wpDqjTBInFN6qijeK2wc!RPTEe|J+{cM-W^Pr7<-(uhvR{p}rY(<6hZ@@8%^(-6R&FE8gZBRhYL(g1$@$`y) zdku26BV+`!>81C?_iOqd1CkPkp#k#P(U`;52EFb;Q}aTtf3XZvrYH+|c#!={Jm(c= z_%n;X!lXik^_w6kp!^#-_?*wZz%_CP&pCpHUk)MyB0v;rbPNqqEG|YV6&DY5%ziA@ zyA#UhIi7IZBdkFD!M&heSAxebP!=1Xp!S=&0H#jSnZxB*cNN%-sVD5%~d}5WNaSne0^dYS}qw^hxX#z1$=v!#*4dSl5%hIxtE@ z#pRg9{Sk}v#ekgV+1D4WcP&l;)GvK}WHAVjZ&Tff0vzR}DVrxD5%xq&FN2fi>{6Q7 zWT&E7*h^L;dTeqUO)+2`*&_qnB1yy4ztc$dbI@9}$~irT++|3Y!;P%_NpxVM!|oWx zFiN?Lr1yYKxGwg|{8L3@ZevbXzX}(*3{)VxRcYu!WuT!ql?seC_DK9njL{2`L)TK@ zZCb70`US^)Ca}m2W0zFsp+z%&VsOym2uC5a}i4FW+~u)g$^?g<<69~ zUWLY)!^zPaS09usBDGK52G)9Deb=iz;YD%7?J8wU3K_uI^k#J~G^kUd0jUUf$UR2t zp*5@s(`0__2|(r94q(-HF6)s_>l)lvJ7vfF zi}?cO#z+xk#pSra1u+bGvf-JFu*{{l;NEhXmAiv*E;A^6BQOP3Q)KB1Q2QPM=&dBP zotIm<+(4$dXfD)cc2uPUjb-m;Jm|b};&Q%u(wYHGLpf>GNbY{d3^?;WngHiNkTm&5N_u=ocF&QfOKKti=Tt;b|KB`pjx0WPSHJ|d56O%3v} z#8#pV*n)*g0E1Ggy9hWnzWU~Z(P_X9XJ`@?1;`UPD1yhHN&r~bVoT}NHA z+me6A<2Vd*LGtU%FCv1U3#eP9tLn?I4xEeSfp|a8R3UAz$Vqe23(t3Y&nNN5*@kRF zx2cQbzvJT<=IX5x`L4+LQ*Bn6OTatN^+e~AIBA?(DBY$5Ktb3eIFS9$xh91^o!hu+ zXGMW`%bvT%or|PYG_5gEHx)*cCal;R(qSL{aV6C6g4LKx%BU9WhE< zVh!}CVB(LRb_iX~j}aCtLAl%dvcXz?y~GYbf%Q-Q@Pblfw!A5P_~B*s(p$%1MH5ll zimceu@jCL96&z&x3HDTsElI*eR&Tw#Lw*pniin(mJv*?P=eIv|IJ})i(z+$6_U-2n zXSI(6ROXwYhK2#&z&32#P*UNLy99{yvZlJraq@1c;#=}+|8|v(`N4e4*0W3YFpy|U ztkFPW2J}?nW6|vp6!GX)X`wy1814c!2SgelGSCZfSC3+^*JM#`;CQ-QOp`E;zlkCU zj6M{Rl}A7Jxve=0HmaJEH0Uhm-ZGhamH?4h)ihMOn9C6$d=Om&gK>0XlvtC&kHl(X z7?7WUBP8Ka64ieEn>icK$OzofLvFxK-Yn=Z08^Y7z!VKwZLl~GU>VDJWi>3$?NR~Z z0ZSvfFcDim6Wg^VcyqP%=#yJ<(DSQTLY;FmW4{QSC!@#?Cv(}~GUzp}V)+3mP zdQJ8t-_`h#5!!K)(e>T3An7d68!GavSgYQ>nH}4zDMi;r1D-q{mMX|FD-eoxlAj}M zwFriIbopJ995s_k3|xNPylg_j9me7q9Cy7>Tw{F+@5{Q3Kb{- zkC9AXm1Y~4x&%d~5t_@rq4(A6 z6;bK&2)$wKLWrCOii2~GG7mrmu1R7?N4iR4xLG|!h;%-BBEr^ zK$J8~a+#a)Y5&+i04-4`rck7Gzz4!IGy!8}o=lRdWI^~WAwr={`6&cRtpV?fp862> zyy#eTjU77>hZJ>+MGxmo0sVC+xhHbrq<*c3+%5Y1I)v(gr<<>qa;MEZlAt%P2)(A- zl0@6YJaQ)@&ObsZlln}7pJ024isgeuT3I>jH63{R{n}lQR)T}q9Vhimxm|-hRRH0B zLv`RUR`s`C4)niz3ch_muDdt#YAn&~yn3rSL%M*+RjJ75rlyN6V4 zqyXY*Om9O31+(d+wuK+nHtCa20LbR#{o;barqFD{58jiO=Gu&kkT28z9kDUExqqQZ zD@2OjtM%QsO`2f!>yQ!a!{%?Ojh&89QFlTzbW;mAo?=AuMeo}UO3tDSQrjetr$TDL z7?%7TL%{V-fOAT8o(WX?>^5@IBAcTe+>+#MlWu`6CGVV3QZquK?0Gj*p{l1QSb)x# z&(^BL&k8-POl=BX%2Z^PoF#|Bhz<13p2g!$A!%_#>dO8<1Iz zh*L?>N7MVZ%_vY@=voljyk0-4zmeTYWc}GXilWt-P=k&=xMbxoE<-)GN9LWpSZC62 z-Ce9Gk{`#0Mv#i*vkw3}@wm>{*!yrEZQ3*xnf2HMgO4l&`!Xv7jKEXhd8IwTsoRed zE6N1rV~4}Uek6wLBu}85VutLUtBv=W5q%o(JP}}3RX4~oiYkyB$*a=u2L}XfGWEkcAg6E2`5f8m%ZR!=L8#*#aEWsum;g?k=SX`jh@t zI$VIubQ8FJ!Z_QC^kWJ|2GcL`8?Nf2H&zwTnV{R;=l@)N$U$cZqG*ocD3k=V%N#BowF{lavS4(vNM>q#|F@rtJLG;Q}2PeLBkDgR6c=*7$S{9?EtAh5E6{7l+;U z2C9y0P2QEsCg%XKaub)cN(Pt_MjTHk$khU#ZpWeWIhWmrRjXd?mejiR@$Q6)VL>25 zdg@fPIenu5LS-7Kn~JW2n;W8d=9YWdB1l1{Z4Z`Anlu~4k`#bhfU+3p3fVr zyC9DHvc&kuFF&J1`ha&Ab%W~{rXmSJjcX9I@K>F|%-nu%`{3=dUB3aL7)A4+^OcGV zsI#qm3%@~PZuwnMLkY4RUfFWEF%k%Z2G$dh0i;;-Nt(;tAk7Xrg&D2Znj+2L!NJL| zYGcF(m+$u>D3EK@(QqP#|&K10@e9_czG_}mV9z>zWJ-wB#Pb6?N%NP);^ zIeTl^)uFFM#{t&`R1_66!OCn`Dv)SZzu7Fb|E{m&Xdh%O0S`I_CEXK1Xag%M^mB|L z6wYn~cx5fD=oz7L=i1&-tgJGSZa*AbjON`_qfs6vuTkq}1=?mjnL|oa#w8i-Tr?_w z>Ik~CaUns4u{g@O8;-8C8mLx9lIl<`8?C{$ICpEuManoKoWOn5RTAM+M)Kz~^Z|1# zhHg^r-d)R-#1zl_Tk2b->>O+#^>GVKwpATO+P53RAX1bxPe$cE8sckI*6q9i*`(@0APqUP})fSvPj8^`qXN{I+jd7WRIt9E5vWA8`02(l3o-@sBUfi!ZAgad{IH z6OA(8*O5+l?(0Z*aSPUok*)_^tyA1bBb173f%(7bH?n@8&a5&ogFOPV5Rd>KIBwGj z7ay7LyAo!4%={D0#KVW|zFZ=AZZWP>Vb82yWc~n;OsUa&vdCkq5sGD@NhRcLn*|Q2 zNB#?_GcrCKK3N8ps*c1C(uWhkls_dkA~MJA687wUkaR$o2PPVv-Uni51qjhdev$TH z5txkUP6xoG;{s$`)2h0W{e|5Z@)0921GC$33lu&}VZ$4SE?cU)fkKIwd_vw+CSv0i z6FWfK)Er8gl+ryF5XDQ@X)HiKCeP<*3 zY13YdBA*&1$wm=<1!xDxBU<{qv)lHTCGB{*a%OG5>RKa|V&wiE$bS1=Cz&XY&qs=t zCr$ajFZG|rUwc%X%R3qBSmZkUC7xwrs2*-AJLx1=wf%UAHJd2CmN(+9jV;K%^TB z5wB_`+tB%~s0uEa*a3X-&b1|z66gXrHf`U6dTRM%3QdTlYZQ+Wlbk1!tr}!@_b*%J z+_C`bw4~`7#n)Tqk$ZHiVM{TAO!`IJ`QTstZ>AODw2gyuC7RNX_%A!QP_7)DzTq2_ z+<&|^^PkKymQLzprwFE~@l*C8ep5S+R$=N84g24$*XtzN-o|yY2B)VbV5I2j#w6Pk z^r+{c%^KwYsA=C&jlCC;gLGC0EfozIfqLY(V5@M{-*(iT3LSf>@b9cVu@+@VYk=Xd z+9?%6XBve|be1bJE#27Ba9*iQAanhPz+GYU{zU;CGC9z5;X(o!8s)5H%wE<8gzOf@nCifcAU zcmNfU3gvJhgFxrrG#*Rahz=7Q8*k*u@`8H10cTavf%jZ5*%bwTpf;N-IzbUj(a~Yb z5?GIiCW!N*7S-eg`8$8m3fB0&&X+t`>mo{PM*IPJ;kx8V)M&C{aAgTN$zF*<5f}|q zP;R(+=cz({r5mdQM+;!_MwJeF2Vfz^=_}YzxWV_B30lVsclGhi9E-$kG!s3qB(zgU zVgwu)s;ejq)T>qm|Nolx`;%3!l_b8-roYxYFj)5bY9WL9h8axWzY%`hg6=Gc8MNS< zUlv{Pk6g*E+#xx17M%f-DXK#0qkuk~lFtu(G-#ozR&~S2<$QE>bY=}> zKlyH`_{v6eK2ShC=dmyl9&#hKFUh1$a>xbYtMZ@2r$7Y&%SRtz6FcO-_)9oFyt!`h zwmPg^=enSrSr7iR)to;?1z8UYN4TL4uKpfque_1b+{$i4Up@n#5Pk)xk)pF~@Wh>9 zrEH~ZaP0CGU7UM*Tj#r!^5MVY(NzfQ5k=C!TMxtYE-_M zE%KdO8#@(RtSZ>*d|p^FA9PIicpuK4gQgwFB++IP;zgSw;sH=HgYHVeE?v@TXdpI6 zT182&4-)*PIza_F^c+Ie9xyqZca=Ur&X=koz_y9GHB;~iGuG`kQR@ZW#v7Eih*Afv zaDj>s^3I8wK9eVLrd76T;(=#ju~SQ-=%OIU`cPlTU+r;?vIN$4V7=NY9rivz?^US^ zJ!HaI?)1eVDA_VkVQ}TUP6ptc2V%K0^8~+aDI@I7JYg2}yFeA6pSG8WpVyO)>=2EnJ@8c^Ju|??t-q(-dj1m}e>qg{F~X4(%xUIN z5j`9`)%C^ZlAhnt?)QQT@~8N>4^;=Q(!IcQ4KY7v3eBo6mKNb)kfA}g7oJP0nM>nX z(E~kgfLzRu#khddeVp|`;r9rzYg${`7(~$rg(@4Y1;(t02XV$ovBk>#yE=rU*6|)}fa~5SufBd@hhh9hyNdVwi z;P_|Q!&0`6kI z8fAHuNf?yNXl%PfBUzW@M{I!Egi)5(8Kzm3t45ZcYLdumm_kNn1;{7Ay&eP>Ap+NL zV><^uK-d-2DxTb}$%2E-@y89t36*oyyEGAUFP82Ddv7f==Fs@JJYII9K@}qIg~^T& ziL8b8f~Ni1`Qs3yQG9_;6hwg{r#rZ;1j{iY;n@e`9tjn#0b@1Fq}JRX6_9qA3%)db z+S#sDf1_Ew2E_K30K@8B^xp#N!IL^pvwVX^!t^<*Xcrb~*TC~Nuur40SIcw-*S#1M zL5IAQbUMIYQBFSq2|WW|!iRdShp8$D)jasde$EJEtfr+$K#$L|ilgLYEBQR25)Lg= z1XlD_D}MNHO7=3a703=og19eRl`u%x^xispP)SZ+Q(KDW{Izc#iWQ+Q?Yw8_=I>li zIVkBZF_nMGw5F;1DMq!s4geNiz5!CRgieEes{(ui!5R|U;6gah0b$e}WY>$tF;FK7 z46~|G6q<|8poTHZkYHLA$;t{H7tXGZiltBwK&WnCG9YRtwSKIYCntW-lU!5+CvpO2 zAF$0>l}6&!=CzXpPX&v#KFbf4)rNh~9=l}N5$qaUX-u3R_4h@gJkA6;c`|7Kx0jJK zUI_w#51y;OBoaj@xt!K=Lp@6GTiaT}B$EzwMpfHPiN7I>SfPep5YR(gx5!|zmYMi1 zkE81z+k$m}CUBX0_x%&J_0f`0vb(|MmU19zVPJyL^~uJB7L8 z;!W`1Igx}%az7fC9kCA!L+1_s(Rm;oY{afQP6H(Ln$L>O@jBZ1HLY>w!I%j5c_duxO z0vVkFh4c6rFjDlWy1(wR8-9yl?=i{A3vceRg~##)gO>%fVx$Hyb>9povc9&2lCch3 zg>HsmWCkKs_Oq94O%gi|Z~+vXO!QMpMZKJqNb-&30C&-rC^(~wu9}>3gWVHR<^oWC zEwTrPlkaILfVrv!V%~8<>aK*1B0Oq*MOlp#HmKsH>LJ^e6k;T%$VG(Jfp>ixd0WIqB$IL^D!y)ElWF7I8yVn4X*Ezx0JaP8TAl+HtV2_tkL1RN`wN*Wgd z|6ExWikQC|K&PV0RFUUBT3M1C;x&2266-~Regu3a^f5x$C3jGw%#ohf643_(b#Fxi z7-iG}(8{zb4b=-htUf}kKU-(J;U=_rmX?0~1&AZSkE*f*?Gbh|(cD23A`8SmcpR-~ zA+FFd5+V-(h?~L_^cdA8@IzdRJfDa(xm@noA&L{qy;i1qK(b&(HI-rKbADg;#q^}h~r@Y*~hRxsl=@rb-$QA8J<+8EaGxnEszZ9ox5=s633@T`Q4>tjImuuFZ+$^y)QUp zK~bPkHZ()mbsjY{kX+@K5q5m#ZW5Z)3H40uEpKmfDXLZ*| z55bHbG}0QiANuJ%WHa@B{UQ>UC>|(rCC)6^9)XZ;AgMte;zNoY`6(BA{eCxfE}q5m z_Y!{D=hm|CZnDlOEEyaj&X(!_+5I`PLasSM08+TT({fxPxu-E~Mi>bGOeL@MI*`9Z z8;0dwzn!DM?+LdT;UHhUfa3^=IhDR&j5-E3#mCGucM5QZDe^APdlSsW5Q{C)mn`U( z@SBk0`xlh|Aoy+KVpizDxyv95jK^Wd@>jptRGz zgo2p1<8?T)zp`DmohTN%UyD#I>oqFfLbHKx8Wq^rQH7_v-0K7MHXZ|=Gq$A1_H)1n zqsE(j{h8NRcVNFvjjRG z`~$vU(#|J2-u#fgw?k|;R9xekd1tHAehgJHWGke+g1gxDv%4KK61vg8J#wN9#!XTD z^<#$CFEDDqr(9NT}6p%SiMhMTlAq-f{so?8;JN%X}(RBE6;Pum^w8{;B;j;C9gX zaKg&{Uo#Mp6bb+%uJ8aZwPWUQ4oOrv1Rs%BjbCIG;*re}%#u(s3yyYmL(6ZF zatKuFirno>O5c-{p|vZ|>Tr&rw^-Q~bO-tDRE8qo!Iz zw%~gff=cl_srY_Yi5e zjg*v?3~i5a3xs2hsB_(h4pIs-^k&$6Zt0QSCMJ{z=@iP(hf8|hvEypC)0|CGL-Qn% zrjfEDDG0~%z|;}#LgArdGqB{PZj!e*xhq^S+;=|2m8kthdF73kev$kMka99W7n#Jw zZH*J^Yt8*upCUSNsZrS=TcB0(yyi3~+$x!ZXQsg3Mg}&YfFNn=^gG4HJDJQzW>28J zY-&7Bq1B%Jx2K(WQT&28A$IP@UtvpKov@|g0RvPi$G9vCG=E~u6sFo|p6aLs|*^EjR(WSJ~ zLuX`R7jql#rRg;FWI#?lvh4{js{OcUlzccyp2+IPTiWzH@KxnFB%w`%U}e?YKo$%6 z0>Q3h-2R2Day|hJRB$x4%H7_8^nmzQwEGyhRjUBT{>`kLr!S7bEnwgQ2g7>oB(XI} zpH7enZEgOqsQ>~0eC-nDdrZo65yexLMt|(UZgd8u4sM?-w5fFf!lNzDrr<$}kwz@p ztj$nlW+Ehowx9SV#xP+vYxMnqeH49)&?lr$mKLFvN}k0qKVz?&F+Q9i4yZf9 z&6z}cA`);KT3&BGRO4z1X_ur1A+J#bT|%#k=@T7K$N;mHdyeumrm#&(H1j?ST%b^1 z4AS-1OEKL5*VkBf0P)VgY#i{8`Qh5$G8NheGg^r!M#7kjbF-=H+*k=*ZMMksG5m-o z3T>QQ@AMl3LbAs|p5sJTP*l3Ff;bs=+HZ^$qKWWdhYIoMcwD%%HSF$p2*NiBTj7p4 z27YMG%Fw^0*tb_n(+p7tRBxhkMgI`NcV{uQasP&}#vp9)o$f?r#tLZ#Q_)%&RmX4R+#3E#!ztP5RasbC~6qs(otO*Jli6w3y^Y{Hm##PhWVX>DlRpmov;(W&(EhxBl8Cdo?!2 zBT=xYJv7hejUfytFK`G04wh-)s|GnBGHx^yzlAy$*`z^k0}>j6@sD@57K;)uwwfLd z%W5VUP54<@cMQ zr=Uhf80o?)nli3&@iD2qHw;2a-Chc_}){T3TpP)^+?j77@raM?l==A$P8!qe=9 zC!3iq_ZZiVLIH!^C-6z#j+q_8JPY-s`-M|UIcw&dgY%Q ztPI;q642Tgf*-J8(u+@{^!>!-=8tAd{V_;xC?O}nD1h)46%}4N!_H-Qzp#6o$q`f) zR|=IV+a)dE@LsbW`<09~#7NiRRYMMfI6d(e?8^jkH*)123e!bh)voQ|QF_Lzf{8h* z8P4(Hc!L7l>RT{i5s=blppjRcw(x0wuXM-~K_%~n*#+f`W}NRU*D)=!4s|%NR!j#- z80fNl0~PRvjj4E90YGDIi|q8yMh%hpm!92aOg+)EoXmMKTg_dSnNv|dlWNsxOrIk3 zq`ZJxs*v^0&Az7kE#I_nEN82YfrHaGX!fTkTNppoiJI&@hIsBL|Q5ALZ!6! ztFOLF3mf&*nqP0pUWi0uOWprz_(W)*T{53CF2b-tpVzEfMfxHqQPJ#-^E>G6*Z@Tr zDUK`W44-x?C9kjnNGC5I`t^0C#!aX6%N2@fOU16nB45%HG1Dp?9^qq=WS-!@e0AmE zO^Bkm(yRpX$?{6tDWKxYz>mpeaY=0|a^7&VKT&fCfFyk#CLLIiYPqVYZ5KGz6XDQ z$?v~!?%$T_zbp4|3nKdOp8NL|5&ieH{*NX3Zz%a6OY+}P^6yLX|Bq1erE42flr;e9 z!q1)ieYksUQr(q9S;h6H|K2ze8TSyYTSMirn3 z;;pTw%-8mKd(QXI`J4aySN&OHz9ii4X{5mZmtTGfu%@~#7ux4*2`pMbPLl&Hxxr=4 zTz1T9*;vvGIANdQ6xQ_;^~89Tsa>O|=*;$Sh7-F=eI7P0N)8Et(&RaADBe`2r}hcB_#x9 z03ev>=W;TU=(pb@`pUNOcmLeq+M_`O5f*TxKWmUCqP{fSKMWHfKH{UR-6<|W=5gy8uXk5$H(hS zKWky~oHTR3|fX@3`X&iyb64Qvd{J6BS|W*O0kXK9#YB0KrZz_E1u9JULB6%G7BuG0GrL%#B zD|k1C#mN-Ap!XBx><*-eOa$&Ev5sPp7tw6Y`tWOiLilDQ#_r)9icv44be(@CCKch2 zR!&xj=J2&O(*CZBJ2!n26m)b_DKXO2)Rc32xY_WrDk@jBdsOqA`cY}io-%bd%&wtR z7dut?PXK}>-$YX)zbS45{|(}hav=w;MmNCE9A-}6tx6N(4gc`J{#tAaMpf+9!dveB zJJCchbRIu$Tqe5e^@)}2;G&{-3aKG2lF(;~%ZwSY*NJ6Gooe;48gN_e)pj)dvjF?V zU!TNRn0D&_;STihevD%yxL#5ch<| zCD->%0{j$ny$}8l=`CYhU>Hl3h7+aRy|#qlrJ7+x7*SFJHt1B&nz~l*@lNl#wRg-xbvw2rLl0i%OSK#Ilh*j^CiV(i_jbK zK~vmuL9Yp`j$O9Hh7IFSc!aR&;3w?YX~utdGwkk!WPBW+{V~E*O`rnGFI=c&3)#Oz z#RdP3o|IN#VPB+{05{wf@)+S#rO^nqEbZWBS2MdYbZHabcma^EoAASU(1f(W9%L^H z_0n81BS!Jgs-BdGk zI6Ki|Ucqauc$%(aRAk0C!`@4C-3`WVVtc^JlZ7@}&|gkK<6OD0=S8hvd!~WY^Pvi>CV|o|d%I;*IkROL}V`IX>8cgFHpKFbqhU+(ltC-}d>t(?2Pe_&vlT zAK{-FW?Jh&a!WsiZP0kGT1894%mn5zP}3CH^r2z1)nX5)t&*Q*P}q#r@;Uga=*p`G5rs&z#;Lgp5G$Wq3cezk5XFAoi$D4ZY8can`7UjG?b3Yxq6Cz|A(l34Kx(_gaIx-nOm1HCcnQZ$| z8pq+|#D9TZQ>G;_U+6C=2d*YwaMPZC++Gt&-T(?*crzT#I;ZS@P{r`ghc;Wdq62p2 zL5lVYwCIG%Mak}0PDz zMn*=FHJ_qKWK<8R{?db#-MsP6vwvM~ylvYA238aq)rUH6LDi=23E=tRHg|CF98ROV zj0>stFBj6IuK^){lH}Ytk;Q^m4ZOz2(vW{Qj+NoYc2_ns1StU6%~1M4twO_Yq~Fty z-+BFjJ-mZf6uTR_*ofIJ%a*G!$=$)}1-FZ#jgU;2yl+{R(WTE6}BjVBi^ zo+I4d(hsz)oINWD?5J8*4zga3%qBBUbn@Lg%8q&}VEH7fS>niS^F^+cDb$R-a& zFWczBXJBJ?T@Lfo$PRzHyPO>#cJy=G3oZ(AiYhFW6K10x2(M~s@cJYCi8-}4%^ZqU z)k=qV8B+`x%U_|8-Me`i26?a^}#&FU3>!khnP&xWV67K z9Q)rs8(v_mgBBIj14a*(TbM??3RkyY=q*?eaO-*L4z@<8o=H3A8PM3B#u zZo}-} zz;{!*clSCce<1x%i+;}8uXQi+j_VIj#wj@&K2vy^pi_gH@RCua!<+}rK_Lt=y2k=m z5{7rtXFt=4giDrmhip}JFkNZeNB7}qXcq>5UGn+sa)0^kw;w2}n^=#2o*NTV*)l|L z>x+)D3|?ne;KDbm2UkA&L5hhm^BN|icS~O8=jU_6Zu}DU$#|!QZ7RZREVdlHAik`$ zM^kF`XPlZKF%9-{Q5@yzn8!srE&!bT) z8)iC{jkZ!+B40JGtDv6Oam;@jG`MDZ3Xq5_I2 zpxiVK5|R6yIwcB(isp?Cc?0DZm#ul>a!OGU!9+KL0x2NL<|5Drxyiimw}JibjyCh( zJZGNG^JGTw?eF`2>$iSuz3W}?nlg-^X|eH6PsI}Dr(gS?E&;91v8|nKNkQGuKvIo8 z{0-+)2L@C10~ElyXd5$-2oaF-_jFe@xr*ZUVP!}`?7ezPb!KB};1-0t4KPfpWWmId zR0ub@(F;18XYzJnKh|yDC)uutmRBn**Sxy5z%b-J0YzVf95xps^`1$o@@+zpD9Xfh z^J+Pv14(Xlk>0-iA25f|;6fkxhmmmQAJ(=Z3yfsx5}xz$&m4t2@b!4B+T!kHftwQx zA{1oIpy&3Wv=;=n)6z-~LV+`h*@&?K@lqMWW~cZa=Wnyt52V7tcnc}*Kh|97`Q0fB0c?TOms0r4FQc$GAOOhy(9s`oI;|4#m&BIz!`}hKlggm5u%hk|MHi(M5H~3< z+>c>VdGIrP({`|^k2q~QmTXi#?7xD69&|^~|^DLnwu!jr?4#q0)Uds{mk5BA# zZY-DT$1IsRvIU`U9yBwe+bLhu{}sAPeJ9PApvjbj10ximz?jOlK(AQgm6e&9S!8(q z`gO15>DY@!hCUBKDh+2BV71RxP$%j6^6EDJcwy~s%69d;(2Xh=8c_nZUKg-$tvh=* zeCv3ib$CW!3Q3NXM%@-XrRHK2U;z>}>MudKcHm2Q)%|?o%vXLxdshxRS&U?G@oWPu z^8X5`%58^c(2rx>K61*wB=biiE1zX}4;5z@G-%^U*t5SAO>et^ySZ-g4N8(rkzN!p z8_5fX7K+R-9*#y(`xRzAmXkk(L@CQ!3BV}t$@_-bzsHSd zq|L<2NEXt%FV+&gm2?T`?i$HhuO`zi#tl<#We$@kO-gDj+;kDJ0x6$`7L18Gn!Z}5I)D?gF%pM)1_eGjsnxQwX+Ic0Qye9^ubvi17nN$^NCq~)L2F6 zaVn6*q5HpdW7XEOwGojh>Gj%dum4lIkN%J$D{d~Z{7>8K3nD>dD7BMgSse`V01jRh zz4W+o;{Xf$K+h!#v*G4FTiHotU$uOUv^QkRgkyk>pL4JGDWD!*Tm*ETP2Afg-S9vU zsZ)*+{N$`|P~mowS6SOOaG&`1u>TUDK^C)nF~&7jnvLetM-xfc_DtG;;J_)Pj1wnL z$ovs|I{0L)X9pj89=QD0?J-EZTX3U~Tvxp>Q#g>o|Gay{V5V5IL2S?fcxTmBkZ-+` zD74Rv=^)228$wp>DVtK3&lWkHn@fM#C)(&WK-BFcq{x=lw1+J>0gVVLJF6 z!MGW(EHE^Z&zEgQfBR)Nhh_w^B>}-=?WU>=v`QfRe{Yp*&<#wH^ys2sq!Y|IJ-rxs zBDzVk9v8~v))~r}d*(llS0V<&BChMLu{KsMXVhc}j3lL<<;(#@Oc|2(j@#oFEm~A^ zc(sjg<^M4liagr}kZc6WFaOF&8uj0=9CBY*ppN$~vE6-7 zG#O(yVFqu3;57en10s2LQz7Nvm+yKMXBHXbr!J*tW=>;w8R4>+3{faFZJL?~KYtb+ zH*)YJ6!mhoRajw}>U?=8 z@tEb6#Lx4vQq1oj=ZTOk_b4@gHZf^itxYQvQ{JQcC zc_KCu;T%9J9>tt5$C*iNE23gr@w+mIcT#j9j#wrl?0q$Xk9E1Ec)+>?hNx_?vIXIQd1)%?Clu^L|W$9@OTg?e5An&R)DaI>4 zHR~MMmcoDx^zW^T_0!6LDlD0TY#AoU&ku(%scLq0Uwfz?WQb?=?;hj>#D^);Y}jp5 zQx4}0u~jUsk`yjnr9_fLcLH3Vz4CWtf{>qE=~Z+Mw^I2sL%Gg44X?8+J!Xb$X%a&eWdqDRuvQ$sV>cwp}&E5M|5+uZT+%5ImZPaa4m z9Gk&X+}Z$A$^8(=CMF}vwIbgUyCAtV@&X&}k75G1@H&Lfden2CesOhrnd?68mWK!s z76i0Y4wW5^gfSbnwi)4;9OUe5h}V6a&1Sp< zW>a@$vJ^)hgh#s(QcEiXY_?%~%Vl&YmNZqN9`O*3010W8s2a>5ObSHm6R%)wW})vB zM>Y_+`O`PXK*CR&S3Y|6Cc3HX3xw)*=%#BowmT^AHZGDq!u}y!b@$A_Oqc}_ht@(- zBJ_Q*T6S^muSnQr4&#tsQ#p){O23#^B3d{1y~${)z>QcK)*#|T;`a>{Sa>tI_Qk))HKpG9a~+0C|NcPO7l zC@{4AVx_4~nqSL93U~mjWT6{2@o7vQkuox-S!U0Y1H}0TDM)-p>=}kt*jS!P&G>cu zV&Mj&J{inELJzY81|k-RS2p8m#vq<8*g8RZoBT6Wp>7^$8$s3vJP6d*53reGx))B$ zpYdw#prd6{j0V$bX8~9HH*W$=$ss>RvUjbI+BLs<`VG((3NeSM@fqCKbU|;SnD<0& zgv<_3kE8o#ryJ_?7#8H@m4z2_YJ`zIO|F7OT=H}vl(pi=`%aB!GHLS zdP5blyGReVy9cW6G5GJn=aGgC?GK<^U8$#la}?Q*Vg2oMoKgg#>-6aNO*y(!ptd7F z+{G?1`W9FyNdv?fva3Z8DIdQ*y+V}Igs$P-GJ25Hib4)U#Xo+e%qG9d=?T;w79RCJ zHuGUwNz6xT-&TnlMt+ z=>^coIgWFGDl&9WIa&;^mA~1*Sw;ZH_xSdq&hsi#X5aE&Iy7Mf8bbE?J2|K#yfI~+ z1?NJspEc9RHXizIs2JJ0^imY=*V)1r33QKc?%y#_96=AJj5_u1N+cY`Ax!dvx6@j@ z(tHUVO8P2AHAO|;Ey`*cPtP6Ptj8p$jf8B9ev&ie2i`uEVw3Xh>yG+Z_n_+u%42}cK99AQa-|qMB_>mUSF1v?g>f{8 ztf)~U5>`br`QvGLyUYLfMwpy1)(K__9)WppbKkQq5mH2|d0K@T&BfL&%o0p&6P zHX)G-<78VNZgg?rlxugh#B~=t&ogNZFjuU-ZE5_nM6e!i+i}<)U!GP)hl=vT$cwE! ziXt0Z+fgVhhao$UH>+l{0n{L)(mq=b50ISZi*;mImB9tU1?i4cMbdq~G%If;9H9%xf(?Z1e%9RM@!mqOe(T zO>ixtfvPy#(vs6k6kY577UBT521~jw;u9Y zKFO@}c^OpgSZM$OfVjMJFd7c0nGK))bRFAHVMpjWBc2>y+j$N^K^}D(ef#Ku7!uD* zzun#;Todi!d-s?Lizeb)ut^3J3ny0lSfOqo8{FlQrz8R?S9jV(wT&|D3Pc`WW%(Pq z;0D*VaL1UwI*Dyn>{zw5B<~ANDxM8xcm#v=Zu1y`Q0O#Svw1VSayAr1`!`dTxjN^W zJO?K#=`<_~nO+Zhf6}CIG}u`I-M%GZeoRS+`j}b)XY)lJSQa4ce~USLRQUL(OmO=H z?Bym-urSU~c-#O$-x+O&iZGG=-;6siK9iK*aEW-h2HPu|omS+X2L zJ;CVhvk*Na1w63vW7P%-{pz}S1H^q-53@#9eJWJzq>KWO!5zyR$`I}g^;ULsvIB>E zgcI(}XH5U}{tw!2?44w%X=;OXCV*S7Br|>(khk^*glm&Y70B$q2R#SIPUXF)c{Y$! z;L(BH>MB!P@N5DiDJaOOVerY+xDPt9kSn%A*~SHfGxR%pI6XI`$m9b$R`^R79C>e_ z%%;?fG}!LMcsU{gB|!i>MhqCv|A#2`oc3=`uJL)|hj*XB39%Gnr3eSuDaT1)$#fsV zf&hx;a{-)`&SXu7x>*O!LxQCW4t#-5N7V+Cu8!Z(Bb#R`gD2XB?JnE#h`r-5?$nYu zTl#)0&3R@-%X!&iqj0wHzk=YzM1f6UMZGXo#mp1l85u_Wi5kNmZo&!e z?yg;H+W+o!VzH5RF`vvSe)!BakYVGfy}6c_`1FSe)vY_{yfQiKHyf6BSYV6aFOam9 zzZ^GS4e+%ahcrdsWLv#$sum$VNciXEbyJ5LMl4~cQ&SzlJW!48zq$^g=lhU~U$ z-t|Mk*$t>;*OtX!jR?S>PnaC~92HVk-HU_H7_|uWe}+MqT@b)r*OdxDzNiztKol2=kAu*5WQaa_%Q0;FwQ9n^s9nz2%m zx|JzvgjZSOt?X>T4w?)BC64K&zS2&uJbc+Jz3g2US}^ z0TA2;qgn|fCMr7}!$>^&tc&bHMpO{f+5oI#Hl-k=G52Ngt7f12`y(?D^yjx2@I|;$ z;Eh!)?}PPytmUxxbLDCs@IyJ);%b=j8c5S6w70NK@cODXd+tpUE<7_@b*`*|^Nt{u zla0{c#QhY{F*sN*5n;$tlhuw)oeYAhINl$k+?JrY51T)p zkno<>>Fj2bh;a{nzqF9Ci&AG>l>1MHI2#zvV(~D&G!05&wf&3NBwD;u)L^PfYVHiA z#2284a2!f^JeIJL8)wae&K|1Eqjxi*M8r!EUE`xx5WL1-KL#3w$bzdg+&E$o`d#;t zno1>PxQ_L{HeRbL_6+IRPjao3q;;N&1Gyt2A}G6VT%7?@Qvl9hpp@&%v&MT~jhyoq6}W|Zq- zzF7?iybaM;Vx{32$kI?_kR6P`K5gY?g2Eb_^GgHVf*=Vq3C{4hFRrp8O>MHbMsA)# z;TEPvQYj67;lhR9{SNM&%E5hA$MlcVf`QqnY_y=AGY>)qQSC**dh^JdMp+X)u@=Aq zC$ofLRe9gKtJQX~Y$#}{BSw%M#k#|Bm{c0b4p&$QC99>t2Yl5L3^2&aW`<2=4i|Lj zO4~WyoRIcBuq@?mqYPM*`4myKrxy#907i6XT4%1Ohh_eBNA?Or6PDZt9;ZWe#2o1B zjO|m_kg`$GrpozMgq1BBV{9}r`s7USa1KrlLbI*UUh z88K*;33>oX)u=L|9}^2Q9(CM7*y)+@wg`tCQm=*ytqa0bt(9Iu4}ScsphbXsXmnZ( zm18NKfj786?a(2Lg9lpOjT4j!kdf4w3I$DzwW{Liu%D2)KBV9Vc71dJ3Ar=6em!n@ zm~11CijEI_Mzk0pvfDI~L$h#Hi%pZZAi3CE5=6!rftroDzjLWd_PTQFFj09^>ff!IcG2ijBHP)HlehUaRA1T5K1 z4Sf2<9e8u^OTzog^-s_}v{=HAT4<$V1)--xH^(QjJgJ2 z=B-Y=tjmC$1eH4P^a}tC{~)e){nE7T`~$HctSKVvAYyvuPRe)y?RBBj6;3_!V}-tc zs1C3L0eK7SUnI@3LH(|0V)8MfYj{HDkp!HpmMC#t?JwsIv@PX!)q&>6xbDv5p$|I1 zdZv(hP0i2;R%|9BL?)o)k9+!aD^{2$y(_X|V_!9Qp>qt#jU})H7PC7emCL2`rRsPH zwg(zdE21<&v=X!j^O9`y-TA=@KqDFLoC}m;8$np&(C(9g!OTzjYSk{S_qx&D{`epzL!2A5U!lh65^ro;31i}HWf;i)QoMO*>U14icS+v@Exc|KWz zEoxFnK|&XR*{ME4PnGhvEoQ5oH^Mh_^G=BU=n&(71raR_0sO?OsSK5OQe~)Ws_41v zpJPDAdjB5`Xw*co7_#Sxp7lk4?fV}aGlk!@*gxoIUvwqm`zr>c-aq^?Th$5tM?bez z6`K6fkE!3>lfJST7ZUpa+2Tm=oB-{=4HXDo&%eOFfqMD#|F`nH2QP^Zr5F8RZq5Hj zw?l8B(D0(m@cUDCh%)&X{a|SR0Oa$cpWycwSBgTG7yaP6R#7PTq8}QfSt=}zW~pc& z8mq!0Xf_~CLF22q3L3YLMbMmpGzCqtz*W!$3s?kAus~DL1PfRMO|U>y&;$!y1x>Jk zMbHEbGzCqtz*W!$3s?kAus~DL1PfRMO|U>y&;$!y1x>JkMbHEbGzCqtz*W!$3s?kA zus~DL1PfRMO|U>y&;$!y1x>JkMbHEbG=+ccV8Q*O4@DzGhqz~ixG#D8sfCD@#;1GU z*FW|XKktduzo$9#xgMG`AB&(l^Jxm2GoP!VIrFgynlqoKpg94#3L1a_i=YV>XbPHO w0gIps7HA5ZV1cWk2^O#jnqYyZaPXh9Ce@B@T=Y&0|7*cqYs-|6*Zt+c0Svj}3jhEB literal 0 HcmV?d00001 diff --git a/joss/uq-framework.png b/joss/uq-framework.png new file mode 100644 index 0000000000000000000000000000000000000000..ac33241305797d63ccacd84a1342ff9849c44ce0 GIT binary patch literal 24594 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 literal 0 HcmV?d00001 From 225ee53b08aefc9f2fdc8abd6588ba8b7163c5bf Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Thu, 24 Aug 2023 14:34:39 +0200 Subject: [PATCH 67/73] Fix Issue #295: Be more explicit about the need for UQTestFuns. - A statement of need for UQTestFuns is made more explicit in the landing page with a link to a more detailed discussion. - Minor edit on the "About UQ Test Functions" edit. - Fix Issue #289: remove an unnecessary sentence. - Fix Issue #288: fix missing subscript in a notation. --- .../adding-test-function-implementation.md | 3 ++- docs/getting-started/about-uq-test-functions.md | 15 ++++++++++++--- docs/index.md | 12 +++++++++--- docs/test-functions/bratley1992b.md | 2 +- docs/test-functions/speed-reducer-shaft.md | 3 +-- 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/docs/development/adding-test-function-implementation.md b/docs/development/adding-test-function-implementation.md index 240724a..6f443af 100644 --- a/docs/development/adding-test-function-implementation.md +++ b/docs/development/adding-test-function-implementation.md @@ -2,7 +2,8 @@ # Adding a New Test Function Implementation In this guide, we will explain how to implement a new UQ test function into the UQTestFuns code base. -A test function may be added on runtime using the ``UQTestFun`` class as illustrated {ref}`here `. +A test function may be added on runtime using the ``UQTestFun`` class +as illustrated {ref}`here `. However, adding the test function directly to the code base is advantageous that it becomes exposed to the high-level convenient functionalities (such as `list_functions()`). diff --git a/docs/getting-started/about-uq-test-functions.md b/docs/getting-started/about-uq-test-functions.md index be23b87..84c6aaa 100644 --- a/docs/getting-started/about-uq-test-functions.md +++ b/docs/getting-started/about-uq-test-functions.md @@ -1,8 +1,11 @@ (getting-started:about-uq-test-functions)= # About UQ Test Functions -If you're interested in some background information about the what and why of uncertainty quantification (UQ) test functions, -their role within UQ analysis methods development, and how to usually get them, read on. +This page provides some background information about the what and why +of uncertainty quantification (UQ) test functions, +their role within UQ analysis methods development, +how to usually get them, +as well as the motivation behind UQTestFuns. ## What are UQ test functions @@ -212,8 +215,14 @@ Specifically, none of them provides: - an _opportunity for an open-source contribution_, supporting the implementation of new test functions or posting reference results. -Satisfying all the above requirements is exactly the goal +Satisfying all the above requirements is exactly the goal of the UQTestFuns package. +In essence, UQTestFuns aims to save the researchers' and developers' time +from having to reimplement many of the commonly used test functions from the +UQ literature themselves. +The available functions in UQTestFuns are ready to use +for testing and benchmarking purposes. + ## References diff --git a/docs/index.md b/docs/index.md index f8f93b1..260bb58 100644 --- a/docs/index.md +++ b/docs/index.md @@ -11,6 +11,12 @@ Specifically, the package provides: - an _opportunity for an open-source contribution_, supporting the implementation of new test functions or posting reference results. +UQTestFuns aims to save the researchers' and developers' time from having to +reimplement many of the commonly used test functions (and the corresponding +probabilistic input specifications) from the UQ literature themselves. +More background information regarding UQ test functions and UQTestFuns +can be found {ref}`here `. + ::::{grid} :gutter: 2 @@ -31,15 +37,15 @@ To the UQTestFuns Tutorials :ref-type: myst :color: primary :outline: -The what & why of UQ test functions +About UQ Test Functions ``` ::: :::{grid-item-card} User Guide :text-align: center -Be sure to browse through all the available test functions in UQTestFuns; -they are also crudely classified into their usage in typical UQ analyses. +Browse through all the available test functions in UQTestFuns; +they are crudely classified into their usage in typical UQ analyses. Need a reference on how to define a probabilistic input model, there's a dedicated section on that! +++ diff --git a/docs/test-functions/bratley1992b.md b/docs/test-functions/bratley1992b.md index 1b66619..a4785ae 100644 --- a/docs/test-functions/bratley1992b.md +++ b/docs/test-functions/bratley1992b.md @@ -135,7 +135,7 @@ my_testfun = uqtf.Bratley1992b(spatial_dimension=10) The `Bratley1992b` function is defined as follows[^location]: $$ -\mathcal{M}(\boldsymbol{x}) = \prod_{m = 1}^{M} m \cos{(m x)}, +\mathcal{M}(\boldsymbol{x}) = \prod_{m = 1}^{M} m \cos{(m x_m)}, $$ where $\boldsymbol{x} = \{ x_1, \ldots, x_M \}$ diff --git a/docs/test-functions/speed-reducer-shaft.md b/docs/test-functions/speed-reducer-shaft.md index bfa4984..c98477f 100644 --- a/docs/test-functions/speed-reducer-shaft.md +++ b/docs/test-functions/speed-reducer-shaft.md @@ -103,8 +103,7 @@ plt.gcf().set_dpi(150); ### Failure probability Some reference values for the failure probability $P_f$ and from the literature -are summarized in the table below ($\mu_{F_s}$ is the log-normal distribution -mean of $F_s$). +are summarized in the table below. | Method | $N$ | $\hat{P}_f$ | $\mathrm{CoV}[\hat{P}_f]$ | Source | |:-------------:|:-------:|:-----------------------:|:-------------------------:|:--------------------------:| From 75f8e1f823d9b3dac5499cb68aea3e5c7adcdb10 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Tue, 19 Sep 2023 18:54:02 +0200 Subject: [PATCH 68/73] Add authors contribution statement. --- joss/paper.bib | 4 ++-- joss/paper.md | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/joss/paper.bib b/joss/paper.bib index ad74579..7077d8a 100644 --- a/joss/paper.bib +++ b/joss/paper.bib @@ -232,7 +232,7 @@ @Article{Castellon2023 } @Article{Virtanen2020, - author = {Pauli Virtanen and Ralf Gommers and Travis E. Oliphant and Matt Haberland and Tyler Reddy and David Cournapeau and Evgeni Burovski and Pearu Peterson and Warren Weckesser and Jonathan Bright and St{\'{e}}fan J. van der Walt and Matthew Brett and Joshua Wilson and K. Jarrod Millman and Nikolay Mayorov and Andrew R. J. Nelson and Eric Jones and Robert Kern and Eric Larson and C J Carey and {\.{I}}lhan Polat and Yu Feng and Eric W. Moore and Jake VanderPlas and Denis Laxalde and Josef Perktold and Robert Cimrman and Ian Henriksen and E. A. Quintero and Charles R. Harris and Anne M. Archibald and Ant{\^{o}}nio H. Ribeiro and Fabian Pedregosa and Paul van Mulbregt and Aditya Vijaykumar and Alessandro Pietro Bardelli and Alex Rothberg and Andreas Hilboll and Andreas Kloeckner and Anthony Scopatz and Antony Lee and Ariel Rokem and C. Nathan Woods and Chad Fulton and Charles Masson and Christian Häggström and Clark Fitzgerald and David A. Nicholson and David R. Hagen and Dmitrii V. Pasechnik and Emanuele Olivetti and Eric Martin and Eric Wieser and Fabrice Silva and Felix Lenders and Florian Wilhelm and G. Young and Gavin A. Price and Gert-Ludwig Ingold and Gregory E. Allen and Gregory R. Lee and Herv{\'{e}} Audren and Irvin Probst and Jörg P. Dietrich and Jacob Silterra and James T Webber and Janko Slavi{\v{c}} and Joel Nothman and Johannes Buchner and Johannes Kulick and Johannes L. Schönberger and Jos{\'{e}} Vin{\'{\i}}cius de Miranda Cardoso and Joscha Reimer and Joseph Harrington and Juan Luis Cano Rodr{\'{\i}}guez and Juan Nunez-Iglesias and Justin Kuczynski and Kevin Tritz and Martin Thoma and Matthew Newville and Matthias Kümmerer and Maximilian Bolingbroke and Michael Tartre and Mikhail Pak and Nathaniel J. Smith and Nikolai Nowaczyk and Nikolay Shebanov and Oleksandr Pavlyk and Per A. Brodtkorb and Perry Lee and Robert T. McGibbon and Roman Feldbauer and Sam Lewis and Sam Tygier and Scott Sievert and Sebastiano Vigna and Stefan Peterson and Surhud More and Tadeusz Pudlik and Takuya Oshima and Thomas J. Pingel and Thomas P. Robitaille and Thomas Spura and Thouis R. Jones and Tim Cera and Tim Leslie and Tiziano Zito and Tom Krauss and Utkarsh Upadhyay and Yaroslav O. Halchenko and Yoshiki V{\'{a}}zquez-Baeza and}, + author = {Virtanen, Pauli and Gommers, Ralf and Oliphant, Travis E. and Haberland, Matt and Reddy, Tyler and Cournapeau, David and Burovski, Evgeni and others}, journal = {Nature Methods}, title = {{SciPy} 1.0: fundamental algorithms for scientific computing in {Python}}, year = {2020}, @@ -244,7 +244,7 @@ @Article{Virtanen2020 } @Article{Harris2020, - author = {Charles R. Harris and K. Jarrod Millman and St{\'{e}}fan J. van der Walt and Ralf Gommers and Pauli Virtanen and David Cournapeau and Eric Wieser and Julian Taylor and Sebastian Berg and Nathaniel J. Smith and Robert Kern and Matti Picus and Stephan Hoyer and Marten H. van Kerkwijk and Matthew Brett and Allan Haldane and Jaime Fern{\'{a}}ndez del R{\'{\i}}o and Mark Wiebe and Pearu Peterson and Pierre G{\'{e}}rard-Marchant and Kevin Sheppard and Tyler Reddy and Warren Weckesser and Hameer Abbasi and Christoph Gohlke and Travis E. Oliphant}, + author = {Charles R. Harris and K. Jarrod Millman and St{\'{e}}fan J. van der Walt and Ralf Gommers and Pauli Virtanen and David Cournapeau and Eric Wieser and others}, journal = {Nature}, title = {Array programming with {NumPy}}, year = {2020}, diff --git a/joss/paper.md b/joss/paper.md index 27469b9..ff05d35 100644 --- a/joss/paper.md +++ b/joss/paper.md @@ -342,7 +342,13 @@ to update the documentation are also available. The package documentation is available on the `UQTestFuns` [readthedocs](https://uqtestfuns.readthedocs.io). -# Acknowledgements +# Authors contribution statement and acknowledgments + +The contributions to this paper are listed according to the CRediT +(Contributor Roles Taxonomy). **Damar Wicaksono**: Conceptualization, +methodology, software, validation, writing - original draft. +**Michael Hecht**: Conceptualization, writing - review and editing, +project administration, and funding acquisition. This work was partly funded by the Center for Advanced Systems Understanding (CASUS) that is financed by Germany's Federal Ministry of Education From 9b0f99b533bbfea3dbefecbe0d41c0aa0c628578 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Fri, 27 Oct 2023 13:43:38 +0200 Subject: [PATCH 69/73] Add an implementation of the low-dim high-deg test function The two-dimensional polynomial test function of high-degree from Alemazkoor and Meidani (2008) has been implemented. --- CHANGELOG.md | 5 + docs/_toc.yml | 2 + docs/fundamentals/metamodeling.md | 55 ++++---- docs/references.bib | 10 ++ docs/test-functions/alemazkoor-2d.md | 142 ++++++++++++++++++++ docs/test-functions/available.md | 1 + src/uqtestfuns/test_functions/__init__.py | 2 + src/uqtestfuns/test_functions/alemazkoor.py | 87 ++++++++++++ 8 files changed, 277 insertions(+), 27 deletions(-) create mode 100644 docs/test-functions/alemazkoor-2d.md create mode 100644 src/uqtestfuns/test_functions/alemazkoor.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 609a3ea..a3cf1fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- The two-dimensional polynomial function of high-degree for metamodeling + exercises from Alemazkoor and Meidani (2008). + ## [0.4.0] - 2023-07-07 ### Added diff --git a/docs/_toc.yml b/docs/_toc.yml index e698966..a928c6c 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -35,6 +35,8 @@ parts: sections: - file: test-functions/ackley title: Ackley + - file: test-functions/alemazkoor-2d + title: Alemazkoor & Meidani (2018) 2D - file: test-functions/borehole title: Borehole - file: test-functions/bratley1992a diff --git a/docs/fundamentals/metamodeling.md b/docs/fundamentals/metamodeling.md index 7f8693b..a995b61 100644 --- a/docs/fundamentals/metamodeling.md +++ b/docs/fundamentals/metamodeling.md @@ -17,33 +17,34 @@ kernelspec: The table below listed the available test functions typically used in the comparison of metamodeling approaches. -| Name | Spatial Dimension | Constructor | -|:--------------------------------------------------------------:|:-----------------:|:---------------------------:| -| {ref}`Ackley ` | M | `Ackley()` | -| {ref}`Borehole ` | 8 | `Borehole()` | -| {ref}`Damped Cosine ` | 1 | `DampedCosine()` | -| {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | -| {ref}`Flood ` | 8 | `Flood()` | -| {ref}`Forrester et al. (2008) ` | 1 | `Forrester2008()` | -| {ref}`(1st) Franke ` | 2 | `Franke1()` | -| {ref}`(2nd) Franke ` | 2 | `Franke2()` | -| {ref}`(3rd) Franke ` | 2 | `Franke3()` | -| {ref}`(4th) Franke ` | 2 | `Franke4()` | -| {ref}`(5th) Franke ` | 2 | `Franke5()` | -| {ref}`(6th) Franke ` | 2 | `Franke6()` | -| {ref}`Gramacy (2007) 1D Sine ` | 1 | `Gramacy1DSine()` | -| {ref}`McLain S1 ` | 2 | `McLainS1()` | -| {ref}`McLain S2 ` | 2 | `McLainS2()` | -| {ref}`McLain S3 ` | 2 | `McLainS3()` | -| {ref}`McLain S4 ` | 2 | `McLainS4()` | -| {ref}`McLain S5 ` | 2 | `McLainS5()` | -| {ref}`Oakley and O'Hagan (2002) 1D ` | 1 | `Oakley1D()` | -| {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | -| {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | -| {ref}`Webster et al. (1996) 2D ` | 2 | `Webster2D()` | -| {ref}`Sulfur ` | 9 | `Sulfur()` | -| {ref}`Welch1992 ` | 20 | `Welch1992()` | -| {ref}`Wing Weight ` | 10 | `WingWeight()` | +| Name | Spatial Dimension | Constructor | +|:------------------------------------------------------------------:|:-----------------:|:--------------------:| +| {ref}`Ackley ` | M | `Ackley()` | +| {ref}`Low-Dimensional High-Degree ` | 2 | `Alemazkoor2D()` | +| {ref}`Borehole ` | 8 | `Borehole()` | +| {ref}`Damped Cosine ` | 1 | `DampedCosine()` | +| {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | +| {ref}`Flood ` | 8 | `Flood()` | +| {ref}`Forrester et al. (2008) ` | 1 | `Forrester2008()` | +| {ref}`(1st) Franke ` | 2 | `Franke1()` | +| {ref}`(2nd) Franke ` | 2 | `Franke2()` | +| {ref}`(3rd) Franke ` | 2 | `Franke3()` | +| {ref}`(4th) Franke ` | 2 | `Franke4()` | +| {ref}`(5th) Franke ` | 2 | `Franke5()` | +| {ref}`(6th) Franke ` | 2 | `Franke6()` | +| {ref}`Gramacy (2007) 1D Sine ` | 1 | `Gramacy1DSine()` | +| {ref}`McLain S1 ` | 2 | `McLainS1()` | +| {ref}`McLain S2 ` | 2 | `McLainS2()` | +| {ref}`McLain S3 ` | 2 | `McLainS3()` | +| {ref}`McLain S4 ` | 2 | `McLainS4()` | +| {ref}`McLain S5 ` | 2 | `McLainS5()` | +| {ref}`Oakley and O'Hagan (2002) 1D ` | 1 | `Oakley1D()` | +| {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | +| {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | +| {ref}`Webster et al. (1996) 2D ` | 2 | `Webster2D()` | +| {ref}`Sulfur ` | 9 | `Sulfur()` | +| {ref}`Welch1992 ` | 20 | `Welch1992()` | +| {ref}`Wing Weight ` | 10 | `WingWeight()` | In a Python terminal, you can list all the available functions relevant for metamodeling applications using ``list_functions()`` and filter the results diff --git a/docs/references.bib b/docs/references.bib index 1a269b7..39b1439 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -748,4 +748,14 @@ @InCollection{Beck2015 doi = {10.1007/978-3-319-11259-6_24-1}, } +@Article{Alemazkoor2018, + author = {Negin Alemazkoor and Hadi Meidani}, + journal = {Journal of Computational Physics}, + title = {A near-optimal sampling strategy for sparse recovery of polynomial chaos expansions}, + year = {2018}, + pages = {137--151}, + volume = {371}, + doi = {10.1016/j.jcp.2018.05.025}, +} + @Comment{jabref-meta: databaseType:bibtex;} diff --git a/docs/test-functions/alemazkoor-2d.md b/docs/test-functions/alemazkoor-2d.md new file mode 100644 index 0000000..f372c36 --- /dev/null +++ b/docs/test-functions/alemazkoor-2d.md @@ -0,0 +1,142 @@ +--- +jupytext: + formats: ipynb,md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.14.1 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +(test-functions:alemazkoor-2d)= +# Low-Dimensional High-Degree Function from Alemazkoor and Meidani (2008) + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +The low-dimensional high-degree test function +(or `Alemazkoor2D` for short) is a two-dimensional polynomial function. +It was introduced in {cite}`Alemazkoor2018` as a test function for +a metamodeling exercise (i.e., sparse polynomial chaos expansion). +The function features a low-dimensional polynomial function (2D) +with a high degree (a total degree of $20$). + +The surface and contour plots of the `Alemazkoor2D` function are shown below. + +```{code-cell} ipython3 +:tags: [remove-input] + +from mpl_toolkits.axes_grid1 import make_axes_locatable + +my_fun = uqtf.Alemazkoor2D() + +# --- Create 2D data +xx_1d = np.linspace(-1, 1, 1000)[:, np.newaxis] +mesh_2d = np.meshgrid(xx_1d, xx_1d) +xx_2d = np.array(mesh_2d).T.reshape(-1, 2) +yy_2d = my_fun(xx_2d) + +# --- Create two-dimensional plots +fig = plt.figure(figsize=(10, 5)) + +# Surface +axs_1 = plt.subplot(121, projection='3d') +axs_1.plot_surface( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000, 1000).T, + linewidth=0, + cmap="plasma", + antialiased=False, + alpha=0.5 +) +axs_1.set_xlabel("$x_1$", fontsize=14) +axs_1.set_ylabel("$x_2$", fontsize=14) +axs_1.set_zlabel("$\mathcal{M}(x_1, x_2)$", fontsize=14) +axs_1.set_title("Surface plot of Alemazkoor2D", fontsize=14) + +# Contour +axs_2 = plt.subplot(122) +cf = axs_2.contourf( + mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000).T, cmap="plasma" +) +axs_2.set_xlabel("$x_1$", fontsize=14) +axs_2.set_ylabel("$x_2$", fontsize=14) +axs_2.set_title("Contour plot of Alemazkoor2D", fontsize=14) +divider = make_axes_locatable(axs_2) +cax = divider.append_axes('right', size='5%', pad=0.05) +fig.colorbar(cf, cax=cax, orientation='vertical') +axs_2.axis('scaled') + +fig.tight_layout(pad=4.0) +plt.gcf().set_dpi(75); +``` + +## Test function instance + +To create a default instance of the `Alemazkoor2D` function: + +```{code-cell} ipython3 +my_testfun = uqtf.Alemazkoor2D() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +## Description + +The McLain S1 function is defined as follows: + +$$ +\mathcal{M}(\boldsymbol{x}) = \sum_{i = 1}^{5} x_1^{2i} x_2^{2i} +$$ +where $\boldsymbol{x} = \{ x_1, x_2 \}$ +is the two-dimensional vector of input variables further defined below. + +## Probabilistic input + +Based on {cite}`Alemazkoor2018`, the probabilistic input model +for the function consists of two independent random variables as shown below. + +```{code-cell} ipython3 +my_testfun.prob_input +``` + +## Reference results + +This section provides several reference results of typical UQ analyses +involving the test function. + +### Sample histogram + +Shown below is the histogram of the output based on $100'000$ random points: + +```{code-cell} ipython3 +:tags: [hide-input] + +xx_test = my_testfun.prob_input.get_sample(100000) +yy_test = my_testfun(xx_test) + +plt.hist(yy_test, bins="auto", color="#8da0cb"); +plt.grid(); +plt.ylabel("Counts [-]"); +plt.xlabel("$\mathcal{M}(\mathbf{X})$"); +plt.gcf().set_dpi(150); +``` + +## References + +```{bibliography} +:style: unsrtalpha +:filter: docname in docnames +``` diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index ad9a1de..22864af 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -22,6 +22,7 @@ regardless of their typical applications. | Name | Spatial Dimension | Constructor | |:-----------------------------------------------------------------------------------:|:-----------------:|:-------------------------------:| | {ref}`Ackley ` | M | `Ackley()` | +| {ref}`Low-Dimensional High-Degree ` | 2 | `Alemazkoor2D()` | | {ref}`Borehole ` | 8 | `Borehole()` | | {ref}`Bratley et al. (1992) A ` | M | `Bratley1992a()` | | {ref}`Bratley et al. (1992) B ` | M | `Bratley1992b()` | diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index 0cf760e..b85c4e7 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -17,6 +17,7 @@ from .gramacy2007 import Gramacy1DSine from .hyper_sphere import HyperSphere from .ishigami import Ishigami +from .alemazkoor import Alemazkoor2D from .oakley2002 import Oakley1D from .otl_circuit import OTLCircuit from .mclain import McLainS1, McLainS2, McLainS3, McLainS4, McLainS5 @@ -35,6 +36,7 @@ __all__ = [ "Ackley", + "Alemazkoor2D", "Borehole", "Bratley1992a", "Bratley1992b", diff --git a/src/uqtestfuns/test_functions/alemazkoor.py b/src/uqtestfuns/test_functions/alemazkoor.py new file mode 100644 index 0000000..c3331f6 --- /dev/null +++ b/src/uqtestfuns/test_functions/alemazkoor.py @@ -0,0 +1,87 @@ +""" +Module with an implementation of Alemazkoor & Meidani (2008) test functions. + +There are two test functions from [1]. +One features a low-dimensional polynomial function with a high degree +(a total degree of 20). + +The functions were used as test functions +for a metamodeling exercise (i.e., sparse polynomial chaos expansion) in [1]. + +References +---------- + +1. Negin Alemazkoor and Hadi Meidani, "A near-optimal sampling strategy for + sparse recovery of polynomial chaos expansions," Journal of Computational + Physics, vol. 371, pp. 137-151, 2018. + DOI: 10.1016/j.jcp.2018.05.025 +""" +import numpy as np + +from ..core.prob_input.input_spec import UnivDistSpec, ProbInputSpecFixDim +from ..core.uqtestfun_abc import UQTestFunABC + +__all__ = ["Alemazkoor2D"] + +AVAILABLE_INPUT_SPECS_2D = { + "Alemazkoor2018": ProbInputSpecFixDim( + name="Alemazkoor2018", + description=( + "Input specification for the 2D test function " + "from Alemazkoor & Meidani (2018)" + ), + marginals=[ + UnivDistSpec( + name="X1", + distribution="uniform", + parameters=[-1, 1], + description="None", + ), + UnivDistSpec( + name="X2", + distribution="uniform", + parameters=[-1, 1], + description="None", + ), + ], + copulas=None, + ), +} + + +def evaluate_2d(xx: np.ndarray) -> np.ndarray: + """The evaluation for the Alemazkoor & Meidani (2018) 2D function. + + Parameters + ---------- + xx : np.ndarray + A two-dimensional input values given by an N-by-2 array + where N is the number of input values. + + Returns + ------- + np.ndarray + The output of the test function evaluated on the input values. + The output is a 1-dimensional array of length N. + """ + yy = np.zeros(xx.shape[0]) + + for i in range(1, 6): + yy += xx[:, 0] ** (2 * i) * xx[:, 1] ** (2 * i) + + return yy + + +class Alemazkoor2D(UQTestFunABC): + """An implementation of the 2D function of Alemazkoor & Meidani (2018).""" + + _tags = ["metamodeling"] + _description = ( + "Two-dimensional high-degree polynomial from Alemazkoor " + "& Meidani (2018)" + ) + _available_inputs = AVAILABLE_INPUT_SPECS_2D + _available_parameters = None + _default_spatial_dimension = 2 + + eval_ = staticmethod(evaluate_2d) From 1fa6e8f5675f2596add51469f5280e40dab27dbd Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Fri, 27 Oct 2023 14:19:14 +0200 Subject: [PATCH 70/73] Fix wrong link to the stable docs in the README This commit should resolve Issue #303. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 035756c..ba0fe42 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ | Branches | Status | |:--------------------------------------------------------------------------:|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [`main`](https://github.com/damar-wicaksono/uqtestfuns/tree/main) (stable) | ![build](https://img.shields.io/github/actions/workflow/status/damar-wicaksono/uqtestfuns/main.yml?branch=main&style=flat-square) [![codecov](https://img.shields.io/codecov/c/github/damar-wicaksono/uqtestfuns/main?logo=CodeCov&style=flat-square&token=Y6YQEPJ1TT)](https://app.codecov.io/gh/damar-wicaksono/uqtestfuns/tree/main) [![Docs](https://readthedocs.org/projects/uqtestfuns/badge/?version=stable&style=flat-square)](https://uqtestfuns.readthedocs.io/en/latest/?badge=stable) | +| [`main`](https://github.com/damar-wicaksono/uqtestfuns/tree/main) (stable) | ![build](https://img.shields.io/github/actions/workflow/status/damar-wicaksono/uqtestfuns/main.yml?branch=main&style=flat-square) [![codecov](https://img.shields.io/codecov/c/github/damar-wicaksono/uqtestfuns/main?logo=CodeCov&style=flat-square&token=Y6YQEPJ1TT)](https://app.codecov.io/gh/damar-wicaksono/uqtestfuns/tree/main) [![Docs](https://readthedocs.org/projects/uqtestfuns/badge/?version=stable&style=flat-square)](https://uqtestfuns.readthedocs.io/en/stable/?badge=stable) | | [`dev`](https://github.com/damar-wicaksono/uqtestfuns/tree/dev) (latest) | ![build](https://img.shields.io/github/actions/workflow/status/damar-wicaksono/uqtestfuns/main.yml?branch=dev&style=flat-square) [![codecov](https://img.shields.io/codecov/c/github/damar-wicaksono/uqtestfuns/dev?logo=CodeCov&style=flat-square&token=Y6YQEPJ1TT)](https://app.codecov.io/gh/damar-wicaksono/uqtestfuns/tree/dev) [![Docs](https://readthedocs.org/projects/uqtestfuns/badge/?version=latest&style=flat-square)](https://uqtestfuns.readthedocs.io/en/latest/?badge=latest) | From 91b9c2a370e12436c2862f8865e676a4f31a76cf Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Fri, 27 Oct 2023 14:32:40 +0200 Subject: [PATCH 71/73] Update the in-code docs for the Forrester function The in-code documentation has been updated to conform with the package standards. --- src/uqtestfuns/test_functions/forrester.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/uqtestfuns/test_functions/forrester.py b/src/uqtestfuns/test_functions/forrester.py index 1b50971..24fac50 100644 --- a/src/uqtestfuns/test_functions/forrester.py +++ b/src/uqtestfuns/test_functions/forrester.py @@ -45,7 +45,16 @@ def evaluate(xx: np.ndarray) -> np.ndarray: Parameters ---------- + xx : np.ndarray + One-dimensional input values given by an N-by-1 array + where N is the number of input values. + Returns + ------- + np.ndarray + The output of the 1D Forrester et al. (2008) function evaluated + on the input values. + The output is a 1-dimensional array of length N. """ yy = (6 * xx[:, 0] - 2) ** 2 * np.sin(12 * xx[:, 0] - 4) From d5f19804bde96154fc632488d8865e42a7303b23 Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Fri, 27 Oct 2023 14:48:38 +0200 Subject: [PATCH 72/73] Fix minor typo. --- joss/paper.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/joss/paper.md b/joss/paper.md index ff05d35..ca1e251 100644 --- a/joss/paper.md +++ b/joss/paper.md @@ -346,7 +346,7 @@ on the `UQTestFuns` [readthedocs](https://uqtestfuns.readthedocs.io). The contributions to this paper are listed according to the CRediT (Contributor Roles Taxonomy). **Damar Wicaksono**: Conceptualization, -methodology, software, validation, writing - original draft. +methodology, software, validation, and writing - original draft. **Michael Hecht**: Conceptualization, writing - review and editing, project administration, and funding acquisition. From d7306b192d328929aba5169a80d5c65ee8cd03bc Mon Sep 17 00:00:00 2001 From: Damar Wicaksono Date: Fri, 27 Oct 2023 15:25:52 +0200 Subject: [PATCH 73/73] Prepare the release of v0.4.1 --- CHANGELOG.md | 17 +++++++ CITATION.cff | 8 +-- README.md | 24 ++++----- docs/fundamentals/metamodeling.md | 56 ++++++++++----------- docs/glossary.md | 4 +- docs/test-functions/alemazkoor-2d.md | 10 ++-- docs/test-functions/available.md | 4 +- setup.cfg | 2 +- src/uqtestfuns/test_functions/alemazkoor.py | 2 +- 9 files changed, 72 insertions(+), 55 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3cf1fc..4c3568e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.4.1] - 2023-10-27 + ### Added - The two-dimensional polynomial function of high-degree for metamodeling exercises from Alemazkoor and Meidani (2008). +- New tutorials (how the package may be used in a sensitivity analysis or + reliability analysis exercises) have been added to the documentation + following the review process in the submission of the package + to the Journal of Open Source Software (JOSS). + +### Changed + +- The documentation landing page now includes explicit statement regarding + the purpose of the package. + +### Fixed + +- Several typos in the documentation have been fixed with an additional + minor improvements overall. ## [0.4.0] - 2023-07-07 @@ -169,6 +185,7 @@ First public release of UQTestFuns. - Mirror GitHub action to the [CASUS organization](https://github.com/casus) [Unreleased]: https://github.com/damar-wicaksono/uqtestfuns/compare/main...dev +[0.4.1]: https://github.com/damar-wicaksono/uqtestfuns/compare/v0.4.0...v0.4.1 [0.4.0]: https://github.com/damar-wicaksono/uqtestfuns/compare/v0.3.0...v0.4.0 [0.3.0]: https://github.com/damar-wicaksono/uqtestfuns/compare/v0.2.0...v0.3.0 [0.2.0]: https://github.com/damar-wicaksono/uqtestfuns/compare/v0.1.1...v0.2.0 diff --git a/CITATION.cff b/CITATION.cff index 6e8c4a4..2cb299f 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -14,8 +14,8 @@ authors: orcid: 'https://orcid.org/0000-0001-8587-7730' identifiers: - type: doi - value: 10.5281/zenodo.8125015 - description: The Zenodo URL of version 0.4.0 of the package + value: 10.5281/zenodo.10047512 + description: The Zenodo URL of version 0.4.1 of the package repository-code: 'https://github.com/damar-wicaksono/uqtestfuns' url: 'https://uqtestfuns.readthedocs.io/en/latest/' repository-artifact: 'https://pypi.org/project/uqtestfuns/' @@ -38,5 +38,5 @@ keywords: - reliability-analysis - surrogate-modeling license: MIT -version: 0.4.0 -date-released: '2023-07-07' +version: 0.4.1 +date-released: '2023-10-27' diff --git a/README.md b/README.md index ba0fe42..0f477f7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # UQTestFuns -[![DOI](http://img.shields.io/badge/DOI-10.5281/zenodo.8125015-blue.svg?style=flat-square)](https://doi.org/10.5281/zenodo.8125015) +[![DOI](http://img.shields.io/badge/DOI-10.5281/zenodo.10047512-blue.svg?style=flat-square)](https://doi.org/10.5281/zenodo.10047512) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg?style=flat-square)](https://github.com/psf/black) [![Python 3.7](https://img.shields.io/badge/python-3.7-blue.svg?style=flat-square)](https://www.python.org/downloads/release/python-370/) [![License](https://img.shields.io/github/license/damar-wicaksono/uqtestfuns?style=flat-square)](https://choosealicense.com/licenses/mit/) @@ -33,16 +33,17 @@ To list the available functions: ```python-repl >>> import uqtestfuns as uqtf >>> uqtf.list_functions() -No. Constructor Dimension Application Description + No. Constructor Dimension Application Description ----- ----------------------------- ----------- -------------------------------------- ---------------------------------------------------------------------------- 1 Ackley() M optimization, metamodeling Optimization test function from Ackley (1987) - 2 Borehole() 8 metamodeling, sensitivity Borehole function from Harper and Gupta (1983) - 3 Bratley1992a() M integration, sensitivity Integration test function #1 from Bratley et al. (1992) - 4 Bratley1992b() M integration, sensitivity Integration test function #2 from Bratley et al. (1992) - 5 Bratley1992c() M integration, sensitivity Integration test function #3 from Bratley et al. (1992) - 6 Bratley1992d() M integration, sensitivity Integration test function #4 from Bratley et al. (1992) - 7 CantileverBeam2D() 2 reliability Cantilever beam reliability problem from Rajashekhar and Ellington (1993) - 8 CircularPipeCrack() 2 reliability Circular pipe under bending moment from Verma et al. (2015) + 2 Alemazkoor2D() 2 metamodeling Two-dimensional high-degree polynomial from Alemazkoor & Meidani (2018) + 3 Borehole() 8 metamodeling, sensitivity Borehole function from Harper and Gupta (1983) + 4 Bratley1992a() M integration, sensitivity Integration test function #1 from Bratley et al. (1992) + 5 Bratley1992b() M integration, sensitivity Integration test function #2 from Bratley et al. (1992) + 6 Bratley1992c() M integration, sensitivity Integration test function #3 from Bratley et al. (1992) + 7 Bratley1992d() M integration, sensitivity Integration test function #4 from Bratley et al. (1992) + 8 CantileverBeam2D() 2 reliability Cantilever beam reliability problem from Rajashekhar and Ellington (1993) + 9 CircularPipeCrack() 2 reliability Circular pipe under bending moment from Verma et al. (2015) ... ``` @@ -158,9 +159,8 @@ UQTestFuns is currently maintained by: - Damar Wicaksono ([HZDR/CASUS](https://www.casus.science/)) -with scientific supervision from: - -- Michael Hecht ([HZDR/CASUS](https://www.casus.science/)) +under the Mathematical Foundations of Complex System Science Group +led by Michael Hecht ([HZDR/CASUS](https://www.casus.science/)) at CASUS. ## License diff --git a/docs/fundamentals/metamodeling.md b/docs/fundamentals/metamodeling.md index a995b61..89e4c8c 100644 --- a/docs/fundamentals/metamodeling.md +++ b/docs/fundamentals/metamodeling.md @@ -17,34 +17,34 @@ kernelspec: The table below listed the available test functions typically used in the comparison of metamodeling approaches. -| Name | Spatial Dimension | Constructor | -|:------------------------------------------------------------------:|:-----------------:|:--------------------:| -| {ref}`Ackley ` | M | `Ackley()` | -| {ref}`Low-Dimensional High-Degree ` | 2 | `Alemazkoor2D()` | -| {ref}`Borehole ` | 8 | `Borehole()` | -| {ref}`Damped Cosine ` | 1 | `DampedCosine()` | -| {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | -| {ref}`Flood ` | 8 | `Flood()` | -| {ref}`Forrester et al. (2008) ` | 1 | `Forrester2008()` | -| {ref}`(1st) Franke ` | 2 | `Franke1()` | -| {ref}`(2nd) Franke ` | 2 | `Franke2()` | -| {ref}`(3rd) Franke ` | 2 | `Franke3()` | -| {ref}`(4th) Franke ` | 2 | `Franke4()` | -| {ref}`(5th) Franke ` | 2 | `Franke5()` | -| {ref}`(6th) Franke ` | 2 | `Franke6()` | -| {ref}`Gramacy (2007) 1D Sine ` | 1 | `Gramacy1DSine()` | -| {ref}`McLain S1 ` | 2 | `McLainS1()` | -| {ref}`McLain S2 ` | 2 | `McLainS2()` | -| {ref}`McLain S3 ` | 2 | `McLainS3()` | -| {ref}`McLain S4 ` | 2 | `McLainS4()` | -| {ref}`McLain S5 ` | 2 | `McLainS5()` | -| {ref}`Oakley and O'Hagan (2002) 1D ` | 1 | `Oakley1D()` | -| {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | -| {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | -| {ref}`Webster et al. (1996) 2D ` | 2 | `Webster2D()` | -| {ref}`Sulfur ` | 9 | `Sulfur()` | -| {ref}`Welch1992 ` | 20 | `Welch1992()` | -| {ref}`Wing Weight ` | 10 | `WingWeight()` | +| Name | Spatial Dimension | Constructor | +|:--------------------------------------------------------------------:|:-----------------:|:--------------------:| +| {ref}`Ackley ` | M | `Ackley()` | +| {ref}`Alemazkoor & Meidani (2018) 2D ` | 2 | `Alemazkoor2D()` | +| {ref}`Borehole ` | 8 | `Borehole()` | +| {ref}`Damped Cosine ` | 1 | `DampedCosine()` | +| {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | +| {ref}`Flood ` | 8 | `Flood()` | +| {ref}`Forrester et al. (2008) ` | 1 | `Forrester2008()` | +| {ref}`(1st) Franke ` | 2 | `Franke1()` | +| {ref}`(2nd) Franke ` | 2 | `Franke2()` | +| {ref}`(3rd) Franke ` | 2 | `Franke3()` | +| {ref}`(4th) Franke ` | 2 | `Franke4()` | +| {ref}`(5th) Franke ` | 2 | `Franke5()` | +| {ref}`(6th) Franke ` | 2 | `Franke6()` | +| {ref}`Gramacy (2007) 1D Sine ` | 1 | `Gramacy1DSine()` | +| {ref}`McLain S1 ` | 2 | `McLainS1()` | +| {ref}`McLain S2 ` | 2 | `McLainS2()` | +| {ref}`McLain S3 ` | 2 | `McLainS3()` | +| {ref}`McLain S4 ` | 2 | `McLainS4()` | +| {ref}`McLain S5 ` | 2 | `McLainS5()` | +| {ref}`Oakley & O'Hagan (2002) 1D ` | 1 | `Oakley1D()` | +| {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | +| {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | +| {ref}`Webster et al. (1996) 2D ` | 2 | `Webster2D()` | +| {ref}`Sulfur ` | 9 | `Sulfur()` | +| {ref}`Welch1992 ` | 20 | `Welch1992()` | +| {ref}`Wing Weight ` | 10 | `WingWeight()` | In a Python terminal, you can list all the available functions relevant for metamodeling applications using ``list_functions()`` and filter the results diff --git a/docs/glossary.md b/docs/glossary.md index bae257e..f0012f2 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -39,7 +39,7 @@ SORM SS Subset simulation - + SSRM Sequential surrogate reliability method {cite}`Li2018` @@ -47,7 +47,7 @@ Support The support of the probability density function, that is, the subset of the function domain whose elements are not mapped to zero; denoted by $\mathcal{D}_X$. - + SVM Support Vector Machines ``` diff --git a/docs/test-functions/alemazkoor-2d.md b/docs/test-functions/alemazkoor-2d.md index f372c36..99b1cfe 100644 --- a/docs/test-functions/alemazkoor-2d.md +++ b/docs/test-functions/alemazkoor-2d.md @@ -13,7 +13,7 @@ kernelspec: --- (test-functions:alemazkoor-2d)= -# Low-Dimensional High-Degree Function from Alemazkoor and Meidani (2008) +# Two-dimensional Function from Alemazkoor and Meidani (2018) ```{code-cell} ipython3 import numpy as np @@ -21,11 +21,11 @@ import matplotlib.pyplot as plt import uqtestfuns as uqtf ``` -The low-dimensional high-degree test function -(or `Alemazkoor2D` for short) is a two-dimensional polynomial function. -It was introduced in {cite}`Alemazkoor2018` as a test function for +The test function from {cite}`Alemazkoor2018` (or `Alemazkoor2D` for short) +is a two-dimensional polynomial function. +It was used as a test function for a metamodeling exercise (i.e., sparse polynomial chaos expansion). -The function features a low-dimensional polynomial function (2D) +The function features a low-dimensional polynomial function (two-dimensional) with a high degree (a total degree of $20$). The surface and contour plots of the `Alemazkoor2D` function are shown below. diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index 22864af..cc76550 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -22,7 +22,7 @@ regardless of their typical applications. | Name | Spatial Dimension | Constructor | |:-----------------------------------------------------------------------------------:|:-----------------:|:-------------------------------:| | {ref}`Ackley ` | M | `Ackley()` | -| {ref}`Low-Dimensional High-Degree ` | 2 | `Alemazkoor2D()` | +| {ref}`Alemazkoor & Meidani (2018) 2D ` | 2 | `Alemazkoor2D()` | | {ref}`Borehole ` | 8 | `Borehole()` | | {ref}`Bratley et al. (1992) A ` | M | `Bratley1992a()` | | {ref}`Bratley et al. (1992) B ` | M | `Bratley1992b()` | @@ -52,7 +52,7 @@ regardless of their typical applications. | {ref}`McLain S3 ` | 2 | `McLainS3()` | | {ref}`McLain S4 ` | 2 | `McLainS4()` | | {ref}`McLain S5 ` | 2 | `McLainS5()` | -| {ref}`Oakley and O'Hagan (2002) 1D ` | 1 | `Oakley1D()` | +| {ref}`Oakley & O'Hagan (2002) 1D ` | 1 | `Oakley1D()` | | {ref}`OTL Circuit ` | 6 / 20 | `OTLCircuit()` | | {ref}`Piston Simulation ` | 7 / 20 | `Piston()` | | {ref}`RS - Circular Bar ` | 2 | `RSCircularBar()` | diff --git a/setup.cfg b/setup.cfg index 31d67a6..fa1cc98 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = uqtestfuns -version = 0.4.0 +version = 0.4.1 url = https://github.com/damar-wicaksono/uqtestfuns author = Damar Wicaksono author_email = damar.wicaksono@outlook.com diff --git a/src/uqtestfuns/test_functions/alemazkoor.py b/src/uqtestfuns/test_functions/alemazkoor.py index c3331f6..8a5ee87 100644 --- a/src/uqtestfuns/test_functions/alemazkoor.py +++ b/src/uqtestfuns/test_functions/alemazkoor.py @@ -25,7 +25,7 @@ AVAILABLE_INPUT_SPECS_2D = { "Alemazkoor2018": ProbInputSpecFixDim( - name="Alemazkoor2018", + name="2D-Alemazkoor2018", description=( "Input specification for the 2D test function " "from Alemazkoor & Meidani (2018)"