Repository: damar-wicaksono/uqtestfuns (language: python)

Issue:
Add the three-dimension test function with Asymptotes from Dette and Pepelyshev (2010)
The three-dimensional function from Dette and Pepelyshev (2010)[^dette] is a test function used in the context of metamodeling (specifically, design of computer experiments to construct metamodels). The function reads as follows:

$$
\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} = \\{ x_1, x_2, x_3 \\}$ is the vector of input variables modeled as an independent uniform random variable in $[0, 1]^3$. The function features asymptotes at the left boundaries of the design space.

[^dette]: 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](https://doi.org/10.1198/TECH.2010.09157). See Eq. (4), Section 3.1.



Discussion / hints:

Fix patch:
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 <test-functions:ackley>`                  |        M        |       `Ackley()`       |
-|  {ref}`Alemazkoor & Meidani (2018) 2D <test-functions:alemazkoor-2d>`  |        2        |    `Alemazkoor2D()`    |
-| {ref}`Alemazkoor & Meidani (2018) 20D <test-functions:alemazkoor-20d>` |       20        |   `Alemazkoor20D()`    |
-|               {ref}`Borehole <test-functions:borehole>`                |        8        |      `Borehole()`      |
-|       {ref}`Cheng and Sandu (2010) 2D <test-functions:cheng2d>`        |        2        |       `Cheng2D`        |
-|          {ref}`Coffee Cup Model <test-functions:coffee-cup>`           |        2        |     `CoffeeCup()`      |
-|     {ref}`Currin et al. (1988) Sine <test-functions:currin-sine>`      |        1        |     `CurrinSine()`     |
-|          {ref}`Damped Cosine <test-functions:damped-cosine>`           |        1        |    `DampedCosine()`    |
-|      {ref}`Damped Oscillator <test-functions:damped-oscillator>`       |        7        |  `DampedOscillator()`  |
-|                  {ref}`Flood <test-functions:flood>`                   |        8        |       `Flood()`        |
-|       {ref}`Forrester et al. (2008) <test-functions:forrester>`        |        1        |   `Forrester2008()`    |
-|             {ref}`(1st) Franke <test-functions:franke-1>`              |        2        |      `Franke1()`       |
-|             {ref}`(2nd) Franke <test-functions:franke-2>`              |        2        |      `Franke2()`       |
-|             {ref}`(3rd) Franke <test-functions:franke-3>`              |        2        |      `Franke3()`       |
-|             {ref}`(4th) Franke <test-functions:franke-4>`              |        2        |      `Franke4()`       |
-|             {ref}`(5th) Franke <test-functions:franke-5>`              |        2        |      `Franke5()`       |
-|             {ref}`(6th) Franke <test-functions:franke-6>`              |        2        |      `Franke6()`       |
-|           {ref}`Friedman (6D) <test-functions:friedman-6d>`            |        6        |     `Friedman6D()`     |
-|          {ref}`Friedman (10D) <test-functions:friedman-10d>`           |       10        |    `Friedman10D()`     |
-|      {ref}`Genz (Corner Peak) <test-functions:genz-corner-peak>`       |        M        |   `GenzCornerPeak()`   |
-|     {ref}`Gramacy (2007) 1D Sine <test-functions:gramacy-1d-sine>`     |        1        |   `Gramacy1DSine()`    |
-|         {ref}`Higdon (2002) Sine <test-functions:higdon-sine>`         |        1        |     `HigdonSine()`     |
-|   {ref}`Holsclaw et al. (2013) Sine <test-functions:holsclaw-sine>`    |        1        |    `HolsclawSine()`    |
-| {ref}`Lim et al. (2002) Non-Polynomial <test-functions:lim-non-poly>`  |        2        |     `LimNonPoly()`     |
-|     {ref}`Lim et al. (2002) Polynomial <test-functions:lim-poly>`      |        2        |      `LimPoly()`       |
-|              {ref}`McLain S1 <test-functions:mclain-s1>`               |        2        |      `McLainS1()`      |
-|              {ref}`McLain S2 <test-functions:mclain-s2>`               |        2        |      `McLainS2()`      |
-|              {ref}`McLain S3 <test-functions:mclain-s3>`               |        2        |      `McLainS3()`      |
-|              {ref}`McLain S4 <test-functions:mclain-s4>`               |        2        |      `McLainS4()`      |
-|              {ref}`McLain S5 <test-functions:mclain-s5>`               |        2        |      `McLainS5()`      |
-|      {ref}`Oakley & O'Hagan (2002) 1D <test-functions:oakley-1d>`      |        1        |      `Oakley1D()`      |
-|            {ref}`OTL Circuit <test-functions:otl-circuit>`             |     6 / 20      |     `OTLCircuit()`     |
-|            {ref}`Piston Simulation <test-functions:piston>`            |     7 / 20      |       `Piston()`       |
-|              {ref}`Robot Arm <test-functions:robot-arm>`               |        8        |      `RobotArm()`      |
-|          {ref}`Solar Cell Model <test-functions:solar-cell>`           |        5        |     `SolarCell()`      |
-|                 {ref}`Sulfur <test-functions:sulfur>`                  |        9        |       `Sulfur()`       |
-|    {ref}`Undamped Oscillator <test-functions:undamped-oscillator>`     |        6        | `UndampedOscillator()` |
-|      {ref}`Webster et al. (1996) 2D <test-functions:webster-2d>`       |        2        |     `Webster2D()`      |
-|         {ref}`Welch et al. (1992) <test-functions:welch1992>`          |       20        |     `Welch1992()`      |
-|            {ref}`Wing Weight <test-functions:wing-weight>`             |       10        |     `WingWeight()`     |
+|                                   Name                                   | Input Dimension |      Constructor       |
+|:------------------------------------------------------------------------:|:---------------:|:----------------------:|
+|                  {ref}`Ackley <test-functions:ackley>`                   |        M        |       `Ackley()`       |
+|   {ref}`Alemazkoor & Meidani (2018) 2D <test-functions:alemazkoor-2d>`   |        2        |    `Alemazkoor2D()`    |
+|  {ref}`Alemazkoor & Meidani (2018) 20D <test-functions:alemazkoor-20d>`  |       20        |   `Alemazkoor20D()`    |
+|                {ref}`Borehole <test-functions:borehole>`                 |        8        |      `Borehole()`      |
+|        {ref}`Cheng and Sandu (2010) 2D <test-functions:cheng2d>`         |        2        |       `Cheng2D`        |
+|           {ref}`Coffee Cup Model <test-functions:coffee-cup>`            |        2        |     `CoffeeCup()`      |
+|      {ref}`Currin et al. (1988) Sine <test-functions:currin-sine>`       |        1        |     `CurrinSine()`     |
+|           {ref}`Damped Cosine <test-functions:damped-cosine>`            |        1        |    `DampedCosine()`    |
+|       {ref}`Damped Oscillator <test-functions:damped-oscillator>`        |        7        |  `DampedOscillator()`  |
+| {ref}`Dette & Pepelyshev (2010) Exponential <test-functions:dette-exp>`  |        3        |      `DetteExp()`      |
+|                   {ref}`Flood <test-functions:flood>`                    |        8        |       `Flood()`        |
+|        {ref}`Forrester et al. (2008) <test-functions:forrester>`         |        1        |   `Forrester2008()`    |
+|              {ref}`(1st) Franke <test-functions:franke-1>`               |        2        |      `Franke1()`       |
+|              {ref}`(2nd) Franke <test-functions:franke-2>`               |        2        |      `Franke2()`       |
+|              {ref}`(3rd) Franke <test-functions:franke-3>`               |        2        |      `Franke3()`       |
+|              {ref}`(4th) Franke <test-functions:franke-4>`               |        2        |      `Franke4()`       |
+|              {ref}`(5th) Franke <test-functions:franke-5>`               |        2        |      `Franke5()`       |
+|              {ref}`(6th) Franke <test-functions:franke-6>`               |        2        |      `Franke6()`       |
+|            {ref}`Friedman (6D) <test-functions:friedman-6d>`             |        6        |     `Friedman6D()`     |
+|           {ref}`Friedman (10D) <test-functions:friedman-10d>`            |       10        |    `Friedman10D()`     |
+|       {ref}`Genz (Corner Peak) <test-functions:genz-corner-peak>`        |        M        |   `GenzCornerPeak()`   |
+|      {ref}`Gramacy (2007) 1D Sine <test-functions:gramacy-1d-sine>`      |        1        |   `Gramacy1DSine()`    |
+|          {ref}`Higdon (2002) Sine <test-functions:higdon-sine>`          |        1        |     `HigdonSine()`     |
+|    {ref}`Holsclaw et al. (2013) Sine <test-functions:holsclaw-sine>`     |        1        |    `HolsclawSine()`    |
+|  {ref}`Lim et al. (2002) Non-Polynomial <test-functions:lim-non-poly>`   |        2        |     `LimNonPoly()`     |
+|      {ref}`Lim et al. (2002) Polynomial <test-functions:lim-poly>`       |        2        |      `LimPoly()`       |
+|               {ref}`McLain S1 <test-functions:mclain-s1>`                |        2        |      `McLainS1()`      |
+|               {ref}`McLain S2 <test-functions:mclain-s2>`                |        2        |      `McLainS2()`      |
+|               {ref}`McLain S3 <test-functions:mclain-s3>`                |        2        |      `McLainS3()`      |
+|               {ref}`McLain S4 <test-functions:mclain-s4>`                |        2        |      `McLainS4()`      |
+|               {ref}`McLain S5 <test-functions:mclain-s5>`                |        2        |      `McLainS5()`      |
+|       {ref}`Oakley & O'Hagan (2002) 1D <test-functions:oakley-1d>`       |        1        |      `Oakley1D()`      |
+|             {ref}`OTL Circuit <test-functions:otl-circuit>`              |     6 / 20      |     `OTLCircuit()`     |
+|             {ref}`Piston Simulation <test-functions:piston>`             |     7 / 20      |       `Piston()`       |
+|               {ref}`Robot Arm <test-functions:robot-arm>`                |        8        |      `RobotArm()`      |
+|           {ref}`Solar Cell Model <test-functions:solar-cell>`            |        5        |     `SolarCell()`      |
+|                  {ref}`Sulfur <test-functions:sulfur>`                   |        9        |       `Sulfur()`       |
+|     {ref}`Undamped Oscillator <test-functions:undamped-oscillator>`      |        6        | `UndampedOscillator()` |
+|       {ref}`Webster et al. (1996) 2D <test-functions:webster-2d>`        |        2        |     `Webster2D()`      |
+|          {ref}`Welch et al. (1992) <test-functions:welch1992>`           |       20        |     `Welch1992()`      |
+|             {ref}`Wing Weight <test-functions: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;}


Test patch:
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 <test-functions:damped-cosine>`                 |        1        |        `DampedCosine()`         |
 |             {ref}`Damped Oscillator <test-functions:damped-oscillator>`             |        7        |      `DampedOscillator()`       |
 | {ref}`Damped Oscillator Reliability <test-functions:damped-oscillator-reliability>` |        8        | `DampedOscillatorReliability()` |
+|       {ref}`Dette & Pepelyshev (2010) Exponential <test-functions:dette-exp>`       |        3        |          `DetteExp()`           |
 |                         {ref}`Flood <test-functions:flood>`                         |        8        |            `Flood()`            |
 |              {ref}`Forrester et al. (2008) <test-functions:forrester>`              |        1        |        `Forrester2008()`        |
 |                   {ref}`Four-branch <test-functions: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)