diff --git a/CHANGELOG.md b/CHANGELOG.md index 5994478..5bab18d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- The tree-dimensional exponential function from Dette and Pepelyshev (2010) + for metamodeling exercises. - The one-dimensional sine function from Higdon (2002) featuring behaviors that are different in two scales; for metamodeling (multi-resolution) exercises. diff --git a/docs/_toc.yml b/docs/_toc.yml index 3140359..eed03d3 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -70,6 +70,8 @@ parts: title: Damped Oscillator - file: test-functions/damped-oscillator-reliability title: Damped Oscillator Reliability + - file: test-functions/dette-exp + title: Dette & Pepelyshev (2010) Exponential - file: test-functions/flood title: Flood - file: test-functions/forrester diff --git a/docs/fundamentals/metamodeling.md b/docs/fundamentals/metamodeling.md index fb3710e..ad3b50b 100644 --- a/docs/fundamentals/metamodeling.md +++ b/docs/fundamentals/metamodeling.md @@ -18,48 +18,49 @@ kernelspec: The table below listed the available test functions typically used in the comparison of metamodeling approaches. -| Name | Input Dimension | Constructor | -|:----------------------------------------------------------------------:|:---------------:|:----------------------:| -| {ref}`Ackley ` | M | `Ackley()` | -| {ref}`Alemazkoor & Meidani (2018) 2D ` | 2 | `Alemazkoor2D()` | -| {ref}`Alemazkoor & Meidani (2018) 20D ` | 20 | `Alemazkoor20D()` | -| {ref}`Borehole ` | 8 | `Borehole()` | -| {ref}`Cheng and Sandu (2010) 2D ` | 2 | `Cheng2D` | -| {ref}`Coffee Cup Model ` | 2 | `CoffeeCup()` | -| {ref}`Currin et al. (1988) Sine ` | 1 | `CurrinSine()` | -| {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}`Friedman (6D) ` | 6 | `Friedman6D()` | -| {ref}`Friedman (10D) ` | 10 | `Friedman10D()` | -| {ref}`Genz (Corner Peak) ` | M | `GenzCornerPeak()` | -| {ref}`Gramacy (2007) 1D Sine ` | 1 | `Gramacy1DSine()` | -| {ref}`Higdon (2002) Sine ` | 1 | `HigdonSine()` | -| {ref}`Holsclaw et al. (2013) Sine ` | 1 | `HolsclawSine()` | -| {ref}`Lim et al. (2002) Non-Polynomial ` | 2 | `LimNonPoly()` | -| {ref}`Lim et al. (2002) Polynomial ` | 2 | `LimPoly()` | -| {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}`Robot Arm ` | 8 | `RobotArm()` | -| {ref}`Solar Cell Model ` | 5 | `SolarCell()` | -| {ref}`Sulfur ` | 9 | `Sulfur()` | -| {ref}`Undamped Oscillator ` | 6 | `UndampedOscillator()` | -| {ref}`Webster et al. (1996) 2D ` | 2 | `Webster2D()` | -| {ref}`Welch et al. (1992) ` | 20 | `Welch1992()` | -| {ref}`Wing Weight ` | 10 | `WingWeight()` | +| Name | Input Dimension | Constructor | +|:------------------------------------------------------------------------:|:---------------:|:----------------------:| +| {ref}`Ackley ` | M | `Ackley()` | +| {ref}`Alemazkoor & Meidani (2018) 2D ` | 2 | `Alemazkoor2D()` | +| {ref}`Alemazkoor & Meidani (2018) 20D ` | 20 | `Alemazkoor20D()` | +| {ref}`Borehole ` | 8 | `Borehole()` | +| {ref}`Cheng and Sandu (2010) 2D ` | 2 | `Cheng2D` | +| {ref}`Coffee Cup Model ` | 2 | `CoffeeCup()` | +| {ref}`Currin et al. (1988) Sine ` | 1 | `CurrinSine()` | +| {ref}`Damped Cosine ` | 1 | `DampedCosine()` | +| {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | +| {ref}`Dette & Pepelyshev (2010) Exponential ` | 3 | `DetteExp()` | +| {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}`Friedman (6D) ` | 6 | `Friedman6D()` | +| {ref}`Friedman (10D) ` | 10 | `Friedman10D()` | +| {ref}`Genz (Corner Peak) ` | M | `GenzCornerPeak()` | +| {ref}`Gramacy (2007) 1D Sine ` | 1 | `Gramacy1DSine()` | +| {ref}`Higdon (2002) Sine ` | 1 | `HigdonSine()` | +| {ref}`Holsclaw et al. (2013) Sine ` | 1 | `HolsclawSine()` | +| {ref}`Lim et al. (2002) Non-Polynomial ` | 2 | `LimNonPoly()` | +| {ref}`Lim et al. (2002) Polynomial ` | 2 | `LimPoly()` | +| {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}`Robot Arm ` | 8 | `RobotArm()` | +| {ref}`Solar Cell Model ` | 5 | `SolarCell()` | +| {ref}`Sulfur ` | 9 | `Sulfur()` | +| {ref}`Undamped Oscillator ` | 6 | `UndampedOscillator()` | +| {ref}`Webster et al. (1996) 2D ` | 2 | `Webster2D()` | +| {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()`` diff --git a/docs/references.bib b/docs/references.bib index 9fd1f3a..aee4893 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -1032,4 +1032,15 @@ @InBook{Higdon2002 doi = {10.1007/978-1-4471-0657-9_2}, } +@Article{Dette2010, + author = {Dette, Holger and Pepelyshev, Andrey}, + journal = {Technometrics}, + title = {Generalized latin hypercube design for computer experiments}, + year = {2010}, + number = {4}, + pages = {421--429}, + volume = {52}, + doi = {10.1198/tech.2010.09157}, +} + @Comment{jabref-meta: databaseType:bibtex;} diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index 4104209..737e35b 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -38,6 +38,7 @@ regardless of their typical applications. | {ref}`Damped Cosine ` | 1 | `DampedCosine()` | | {ref}`Damped Oscillator ` | 7 | `DampedOscillator()` | | {ref}`Damped Oscillator Reliability ` | 8 | `DampedOscillatorReliability()` | +| {ref}`Dette & Pepelyshev (2010) Exponential ` | 3 | `DetteExp()` | | {ref}`Flood ` | 8 | `Flood()` | | {ref}`Forrester et al. (2008) ` | 1 | `Forrester2008()` | | {ref}`Four-branch ` | 2 | `FourBranch()` | diff --git a/docs/test-functions/dette-exp.md b/docs/test-functions/dette-exp.md new file mode 100644 index 0000000..8c28d8d --- /dev/null +++ b/docs/test-functions/dette-exp.md @@ -0,0 +1,98 @@ +--- +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:dette-exp)= +# Exponential Function from Dette and Pepelyshev (2010) + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +The function is a three-dimensional, scalar-valued function that exhibits +asymptotic behavior where the function value approaches zero near the origin +and increases toward a value as the input moves farther away from the origin +in any direction. + +The function appeared in {cite}`Dette2010` as a test function for comparing +different experimental designs in the construction of metamodels. + +## Test function instance + +To create a default instance of the test function: + +```{code-cell} ipython3 +my_testfun = uqtf.DetteExp() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +## Description + +The test function is defined as[^location]: + +$$ +\mathcal{M}(\boldsymbol{x}) = 100 \left[ \exp{\left( -\frac{2}{x_1^{1.75}} \right)} + \exp{\left( -\frac{2}{x_2^{1.5}} \right)} + \exp{\left( -\frac{2}{x_3^{1.25}} \right)} \right], +$$ + +where $\boldsymbol{x} = \left( x_1, x_2, x_3 \right)$ is the three-dimensional +vector of input variables further defined below. + +## Probabilistic input + +The probabilistic input model for the test function is shown below. + +```{code-cell} ipython3 +:tags: [hide-input] + +print(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] + +my_testfun.prob_input.reset_rng(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); +``` + +## References + +```{bibliography} +:style: unsrtalpha +:filter: docname in docnames +``` + +[^location]: see Eq. (4), Section 3.1, p. 424, in {cite}`Dette2010`. diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index c7165ff..b756f6d 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -14,6 +14,7 @@ from .currin_sine import CurrinSine from .damped_cosine import DampedCosine from .damped_oscillator import DampedOscillator, DampedOscillatorReliability +from .dette import DetteExp from .flood import Flood from .forrester import Forrester2008 from .four_branch import FourBranch @@ -75,6 +76,7 @@ "DampedCosine", "DampedOscillator", "DampedOscillatorReliability", + "DetteExp", "Flood", "Forrester2008", "FourBranch", diff --git a/src/uqtestfuns/test_functions/dette.py b/src/uqtestfuns/test_functions/dette.py new file mode 100644 index 0000000..fcf6db4 --- /dev/null +++ b/src/uqtestfuns/test_functions/dette.py @@ -0,0 +1,70 @@ +""" +Module with an implementation of the functions from Dette and Pepelyshev (2010) + +The paper by Dette and Pepelyshev [1] contains several analytical test +functions used to compare different experimental designs for metamodeling +applications. + +References +---------- + +1. H. Dette and A. Pepelyshev, “Generalized Latin Hypercube Design for Computer + Experiments,” Technometrics, vol. 52, no. 4, pp. 421–429, 2010. + DOI: 10.1198/TECH.2010.09157. +""" + +import numpy as np + +from uqtestfuns.core.custom_typing import ProbInputSpecs +from uqtestfuns.core.uqtestfun_abc import UQTestFunFixDimABC + +__all__ = ["DetteExp"] + + +def evaluate_exp(xx: np.ndarray) -> np.ndarray: + """Evaluate the exponential function from Dette and Pepelyshev (2010). + + Parameters + ---------- + xx : np.ndarray + M-Dimensional input values given by an N-by-3 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.sum(np.exp(-2 / xx ** ([1.75, 1.5, 1.25])), axis=1) + + return 100 * yy + + +class DetteExp(UQTestFunFixDimABC): + """A concrete implementation of the exponential function.""" + + _tags = ["metamodeling"] + _description = "Exponential function from Dette and Pepelyshev (2010)" + _available_inputs: ProbInputSpecs = { + "Dette2010": { + "function_id": "DetteExp", + "description": ( + "Input specification for the exponential test function " + "from Dette and Pepelyshev et al. (2010)" + ), + "marginals": [ + { + "name": f"x_{i + 1}", + "distribution": "uniform", + "parameters": [0, 1], + "description": None, + } + for i in range(3) + ], + "copulas": None, + }, + } + _available_parameters = None + + evaluate = staticmethod(evaluate_exp) # type: ignore diff --git a/tests/conftest.py b/tests/conftest.py index 16d88bf..ca6521a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -69,7 +69,7 @@ def create_random_marginals(length: int) -> List[Marginal]: parameters = np.sort(1 + 2 * np.random.rand(3)) parameters[[0, 1]] = parameters[[1, 0]] # Insert sigma/beta as the second parameter - parameters = np.insert(parameters, 1, np.random.rand(1)) + parameters = np.insert(parameters, 1, 0.5 + np.random.rand(1)) elif distribution == "lognormal": # Limit the size of the parameters parameters = 1 + np.random.rand(2)