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

Issue:
Add the one-dimensional test function from Holsclaw et al. (2013)
The test function reads:

$$
\mathcal{M}(x) = \frac{x \sin{ \left( x \right) } }{10},
$$

where $x$ is uniformly distributed in $[0, 10]$.

The function was introduced in [^Holsclaw2013] as a test function for modeling derivative curves using Gaussian process.
In the paper, an i.i.d noise of $\mathcal{N}(0, 0.3)$ is added to the response.

[^Holsclaw2013]: T. Holsclaw *et al.*, “Gaussian Process Modeling of Derivative Curves,” *Technometrics*, vol. 55, no. 1, pp. 57–67, Feb. 2013, doi: [10.1080/00401706.2012.723918](https://doi.org/10.1080/00401706.2012.723918).
[^location]: See the second example of Section 4 in [^Holsclaw2013].


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 89fc96e..bf71e5a 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 One-dimensional sine function from Holsclaw et al. (2013) for
+  metamodeling exercises.
 - The M-dimensional discontinuous function from Genz (1984) for integration and
   sensitivity analysis exercises; one parameter set from the literature
   is included.
diff --git a/docs/_toc.yml b/docs/_toc.yml
index 8d35e34..a685320 100644
--- a/docs/_toc.yml
+++ b/docs/_toc.yml
@@ -108,6 +108,8 @@ parts:
             title: Genz (Product Peak)
           - file: test-functions/gramacy-1d-sine
             title: Gramacy (2007) 1D Sine
+          - file: test-functions/holsclaw-sine
+            title: Holsclaw Sine
           - file: test-functions/hyper-sphere
             title: Hyper-sphere Bound
           - file: test-functions/ishigami
diff --git a/docs/fundamentals/metamodeling.md b/docs/fundamentals/metamodeling.md
index 813290a..5d53fcf 100644
--- a/docs/fundamentals/metamodeling.md
+++ b/docs/fundamentals/metamodeling.md
@@ -26,7 +26,7 @@ in the comparison of metamodeling approaches.
 |               {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 Sine <test-functions:currin-sine>`             |        1        |     `CurrinSine()`     |
+|     {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()`        |
@@ -41,6 +41,7 @@ in the comparison of metamodeling approaches.
 |          {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}`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()`      |
diff --git a/docs/references.bib b/docs/references.bib
index 3b89416..cc4a60e 100644
--- a/docs/references.bib
+++ b/docs/references.bib
@@ -1009,4 +1009,15 @@ @Article{Jakeman2015
   doi     = {10.1016/j.jcp.2015.02.025},
 }
 
+@Article{Holsclaw2013,
+  author  = {Holsclaw, Tracy and Sansó, Bruno and Lee, Herbert K. H. and Heitmann, Katrin and Habib, Salman and Higdon, David and Alam, Ujjaini},
+  journal = {Technometrics},
+  title   = {Gaussian process modeling of derivative curves},
+  year    = {2013},
+  number  = {1},
+  pages   = {57--67},
+  volume  = {55},
+  doi     = {10.1080/00401706.2012.723918},
+}
+
 @Comment{jabref-meta: databaseType:bibtex;}


Test patch:
diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md
index d49ebe1..80a3148 100644
--- a/docs/test-functions/available.md
+++ b/docs/test-functions/available.md
@@ -33,7 +33,7 @@ regardless of their typical applications.
 |              {ref}`Cheng and Sandu (2010) 2D <test-functions:cheng2d>`              |        2        |           `Cheng2D `            |
 |           {ref}`Circular Pipe Crack <test-functions:circular-pipe-crack>`           |        2        |      `CircularPipeCrack()`      |
 |                 {ref}`Coffee Cup Model <test-functions:coffee-cup>`                 |        2        |          `CoffeeCup()`          |
-|                   {ref}`Currin Sine <test-functions:currin-sine>`                   |        1        |         `CurrinSine()`          |
+|            {ref}`Currin et al. (1988) Sine <test-functions:currin-sine>`            |        1        |         `CurrinSine()`          |
 |          {ref}`Convex Failure Domain <test-functions:convex-fail-domain>`           |        2        |      `ConvexFailDomain()`       |
 |                 {ref}`Damped Cosine <test-functions:damped-cosine>`                 |        1        |        `DampedCosine()`         |
 |             {ref}`Damped Oscillator <test-functions:damped-oscillator>`             |        7        |      `DampedOscillator()`       |
@@ -57,6 +57,7 @@ regardless of their typical applications.
 |             {ref}`Genz (Oscillatory) <test-functions:genz-oscillatory>`             |        M        |       `GenzOscillatory()`       |
 |            {ref}`Genz (Product Peak) <test-functions:genz-product-peak>`            |        M        |       `GenzProductPeak()`       |
 |           {ref}`Gramacy (2007) 1D Sine <test-functions:gramacy-1d-sine>`            |        1        |        `Gramacy1DSine()`        |
+|          {ref}`Holsclaw et al. (2013) Sine <test-functions:holsclaw-sine>`          |        1        |        `HolsclawSine()`         |
 |               {ref}`Hyper-sphere Bound <test-functions:hyper-sphere>`               |        2        |         `HyperSphere()`         |
 |                      {ref}`Ishigami <test-functions:ishigami>`                      |        3        |          `Ishigami()`           |
 |        {ref}`Lim et al. (2002) Non-Polynomial <test-functions:lim-non-poly>`        |        2        |         `LimNonPoly()`          |
diff --git a/docs/test-functions/holsclaw-sine.md b/docs/test-functions/holsclaw-sine.md
new file mode 100644
index 0000000..84a1988
--- /dev/null
+++ b/docs/test-functions/holsclaw-sine.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:holsclaw-sine)=
+# Sine Function from Holsclaw et al. (2013)
+
+```{code-cell} ipython3
+import numpy as np
+import matplotlib.pyplot as plt
+import uqtestfuns as uqtf
+```
+
+The function is a simple one-dimensional, scalar-valued test function.
+It was featured in {cite}`Holsclaw2013` as an example for computing
+the derivative of a curve using Gaussian process.
+
+A plot of the function is shown below..
+
+```{code-cell} ipython3
+:tags: [remove-input]
+
+rng = np.random.default_rng(93025)
+my_testfun = uqtf.HolsclawSine()
+
+xx = np.linspace(0, 10.0, 100)[:, np.newaxis]
+yy = my_testfun(xx)
+
+xx_train = np.linspace(0, 10, 100)[:, np.newaxis]
+yy_train = my_testfun(xx_train) + rng.normal(0, 0.3, size=(100,1))
+
+# --- Create the plot
+plt.plot(xx, yy, color="#8da0cb")
+plt.scatter(xx_train, yy_train, color="#8da0cb")
+plt.grid()
+plt.xlabel("$x$")
+plt.ylabel("$\mathcal{M}(x)$")
+plt.gcf().tight_layout(pad=3.0)
+plt.gcf().set_dpi(150);
+```
+
+```{note}
+In the original paper, the function was evaluated at 100 equispaced points
+in $[0, 10.0]$ with added i.i.d noise from $\mathcal{N} \sim (0, 0.3)$;
+these points are shown in the above plot.
+```
+
+## Test function instance
+
+To create a default instance of the test function:
+
+```{code-cell} ipython3
+my_testfun = uqtf.HolsclawSine()
+```
+
+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) = \frac{x \, \sin{(x)}}{10},
+$$
+
+where $x$ is 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 Section 4, p. 62, in {cite}`Holsclaw2013`.
diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py
index d32e3c1..4e4c966 100644
--- a/src/uqtestfuns/test_functions/__init__.py
+++ b/src/uqtestfuns/test_functions/__init__.py
@@ -29,6 +29,7 @@
     GenzProductPeak,
 )
 from .gramacy2007 import Gramacy1DSine
+from .holsclaw_sine import HolsclawSine
 from .hyper_sphere import HyperSphere
 from .ishigami import Ishigami
 from .lim import LimPoly, LimNonPoly
@@ -55,8 +56,6 @@
 from .wing_weight import WingWeight
 
 # NOTE: Import the new test function implementation class from its respective
-# module manually here and update the list below.
-
 __all__ = [
     "Ackley",
     "Alemazkoor2D",
@@ -94,6 +93,7 @@
     "GenzOscillatory",
     "GenzProductPeak",
     "Gramacy1DSine",
+    "HolsclawSine",
     "HyperSphere",
     "Ishigami",
     "LimNonPoly",
@@ -124,3 +124,4 @@
     "Welch1992",
     "WingWeight",
 ]
+# module manually here and update the list below.
diff --git a/src/uqtestfuns/test_functions/holsclaw_sine.py b/src/uqtestfuns/test_functions/holsclaw_sine.py
new file mode 100644
index 0000000..177f7d2
--- /dev/null
+++ b/src/uqtestfuns/test_functions/holsclaw_sine.py
@@ -0,0 +1,71 @@
+"""
+Module with an implementation of the Sine function from Holsclaw et al. (2013).
+
+The one-dimensional, scalar-valued function was featured in [1] as an example
+for Gaussian process metamodeling.
+
+References
+----------
+
+1. T. Holsclawy, B. Sansó, H. K H. Lee, K. Heitmann, S. Habib, D. Higdon, and
+   U. Alam, “Gaussian Process Modeling of Derivative Curves,” Technometrics,
+   vol. 55, no. 1, pp. 57–67, 2013.
+   DOI: 10.1080/00401706.2012.723918.
+"""
+
+import numpy as np
+
+from uqtestfuns.core.custom_typing import ProbInputSpecs
+from uqtestfuns.core.uqtestfun_abc import UQTestFunFixDimABC
+
+__all__ = ["HolsclawSine"]
+
+
+AVAILABLE_INPUTS: ProbInputSpecs = {
+    "Holsclaw2013": {
+        "function_id": "HolsclawSine",
+        "description": (
+            "Input model for the sine function from Holsclaw et al. (2013)"
+        ),
+        "marginals": [
+            {
+                "name": "x",
+                "distribution": "uniform",
+                "parameters": [0.0, 10.0],
+                "description": None,
+            },
+        ],
+        "copulas": None,
+    },
+}
+
+
+def evaluate(xx: np.ndarray) -> np.ndarray:
+    """Evaluate the Holsclaw 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.
+
+    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 = xx * np.sin(xx) / 10
+
+    return yy
+
+
+class HolsclawSine(UQTestFunFixDimABC):
+    """A concrete implementation of the Holsclaw sine function."""
+
+    _tags = ["metamodeling"]
+    _description = "Sine function from Holsclaw et al. (2013)"
+    _available_inputs = AVAILABLE_INPUTS
+    _available_parameters = None
+
+    evaluate = staticmethod(evaluate)  # type: ignore