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

Issue:
Add the One-Dimensional Sine Function from Currin et al. (1988)
The one-dimensional sine function from Currin et al. (1988)[^Currin] was used as a test function for Gaussian process metamodeling exercise and it is defined as follows:

$$
\mathcal{M}(x) = \sin{\left( 2 \pi (x - 0.1) \right)},
$$

where $x$ is a uniform random variable in $[0, 1]$.

[^Currin]: C. Currin, T. Mitchell, M. Morris, and D. Ylvisaker,  “A Bayesian Approach to the Design and Analysis of Computer Experiments,” ORNL-6498, 814584, Jan. 1988. doi: [10.2172/814584](https://doi.org/10.2172/814584).



Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4a4a2dc..e7076f5 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 Currin et al. (1988) for metamodeling
+  exercise.
 - The two-dimensional, time-dependent (vector-valued) cooling cup model
   for metamodeling exercise.
 - The 8-dimensional robot arm function for metamodeling exercises.
diff --git a/docs/_toc.yml b/docs/_toc.yml
index 342d705..6affa25 100644
--- a/docs/_toc.yml
+++ b/docs/_toc.yml
@@ -62,6 +62,8 @@ parts:
             title: Coffee Cup
           - file: test-functions/convex-fail-domain
             title: Convex Failure Domain
+          - file: test-functions/currin-sine
+            title: Currin Sine
           - file: test-functions/damped-cosine
             title: Damped Cosine
           - file: test-functions/damped-oscillator
diff --git a/docs/fundamentals/metamodeling.md b/docs/fundamentals/metamodeling.md
index 0a96b1a..29011b3 100644
--- a/docs/fundamentals/metamodeling.md
+++ b/docs/fundamentals/metamodeling.md
@@ -18,44 +18,45 @@ 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}`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}`Gramacy (2007) 1D Sine <test-functions:gramacy-1d-sine>`      |        1        |    `Gramacy1DSine()`    |
-|  {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 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}`Gramacy (2007) 1D Sine <test-functions:gramacy-1d-sine>`     |        1        |   `Gramacy1DSine()`    |
+| {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 33fea4b..50d1300 100644
--- a/docs/references.bib
+++ b/docs/references.bib
@@ -964,4 +964,14 @@ @Article{Richardson2020
   doi     = {10.5334/jors.303},
 }
 
+@TechReport{Currin1988,
+  author      = {Currin, C. and Mitchell, T. and Morris, M. and Ylvisaker, D.},
+  institution = {Oak Ridge National Laboratory},
+  title       = {A {Bayesian} approach to the design and analysis of computer experiments},
+  year        = {1988},
+  address     = {Oak Ridge, Tennessee},
+  number      = {ORNL-6498},
+  doi         = {10.2172/814584},
+}
+
 @Comment{jabref-meta: databaseType:bibtex;}


Test patch:
diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md
index fe5f835..fed97e8 100644
--- a/docs/test-functions/available.md
+++ b/docs/test-functions/available.md
@@ -33,6 +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}`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()`       |
diff --git a/docs/test-functions/coffee-cup.md b/docs/test-functions/coffee-cup.md
index 600171a..810495b 100644
--- a/docs/test-functions/coffee-cup.md
+++ b/docs/test-functions/coffee-cup.md
@@ -73,7 +73,7 @@ print(my_testfun)
 
 The temperature evolution of a coffee cup, $T(t)$, as it cools down
 to an ambient temperature, $T_{\text{amb}}$, is described by
-the following initial value problem (IVP):
+the following initial value problem (IVP)[^location]:
 
 $$
 \frac{dT (t)}{dt} = - \kappa (T (t) - T_{\text{amb}}), T \in [ 0, t_e ],
@@ -148,4 +148,4 @@ must be recognized by the method.
 :filter: docname in docnames
 ```
 
-[^location]: See Eqs. (9-12), Section 3.1 in {cite}`Constantine2015`.
+[^location]: See Eqs. (18-20), Section 4.1 in {cite}`Tennoee2018`.
diff --git a/docs/test-functions/currin-sine.md b/docs/test-functions/currin-sine.md
new file mode 100644
index 0000000..8affa94
--- /dev/null
+++ b/docs/test-functions/currin-sine.md
@@ -0,0 +1,122 @@
+---
+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:currin-sine)=
+# Sine Function from Currin et al. (1988)
+
+```{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}`Currin1988` as an example introducing Gaussian
+process metamodeling method.
+
+A plot of the function is shown below..
+
+```{code-cell} ipython3
+:tags: [remove-input]
+
+my_testfun = uqtf.CurrinSine()
+
+xx = np.linspace(0, 1, 100)[:, np.newaxis]
+yy = my_testfun(xx)
+
+xx_train = np.array([[0.0], [0.25], [0.5], [0.75], [1.0]])
+yy_train = my_testfun(xx_train)
+
+# --- 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
+$x = \{ 0.00, 0.25, 0.50, 0.75, 1.00 \}$ to serve as the training data;
+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.CurrinSine()
+```
+
+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) = \sin{\left( 2 \, \pi \, \left( x - 0.1 \right) \right)},
+$$
+
+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 Eq. (5.1), Section 5.1, p. 20, in {cite}`Currin1988`.
diff --git a/src/uqtestfuns/core/prob_input/probabilistic_input.py b/src/uqtestfuns/core/prob_input/probabilistic_input.py
index 4becfd5..83ea564 100644
--- a/src/uqtestfuns/core/prob_input/probabilistic_input.py
+++ b/src/uqtestfuns/core/prob_input/probabilistic_input.py
@@ -215,6 +215,9 @@ def __str__(self):
             disable_numparse=True,
         )
 
+        if self.input_dimension == 1:
+            return table
+
         # Temporary solution for independence copula
         copulas = "Independence" if self.copulas is None else self.copulas
 
diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py
index 16015b8..0b78891 100644
--- a/src/uqtestfuns/test_functions/__init__.py
+++ b/src/uqtestfuns/test_functions/__init__.py
@@ -11,6 +11,7 @@
 from .circular_pipe_crack import CircularPipeCrack
 from .coffee_cup import CoffeeCup
 from .convex_fail_domain import ConvexFailDomain
+from .currin_sine import CurrinSine
 from .damped_cosine import DampedCosine
 from .damped_oscillator import DampedOscillator, DampedOscillatorReliability
 from .flood import Flood
@@ -62,6 +63,7 @@
     "CircularPipeCrack",
     "CoffeeCup",
     "ConvexFailDomain",
+    "CurrinSine",
     "DampedCosine",
     "DampedOscillator",
     "DampedOscillatorReliability",
diff --git a/src/uqtestfuns/test_functions/currin_sine.py b/src/uqtestfuns/test_functions/currin_sine.py
new file mode 100644
index 0000000..06030f8
--- /dev/null
+++ b/src/uqtestfuns/test_functions/currin_sine.py
@@ -0,0 +1,71 @@
+"""
+Module with an implementation of the Sine function from Currin et al. (1988).
+
+The one-dimensional, scalar-valued function was featured in [1] as an example
+for Gaussian process metamodeling.
+
+References
+----------
+
+1. C. Currin, T. Mitchell, M. Morris, and D. Ylvisaker,
+   “A Bayesian Approach to the Design and Analysis of Computer Experiments,”
+   ORNL-6498, 814584, 1988.
+   DOI: 10.2172/814584
+"""
+
+import numpy as np
+
+from uqtestfuns.core.custom_typing import ProbInputSpecs
+from uqtestfuns.core.uqtestfun_abc import UQTestFunFixDimABC
+
+__all__ = ["CurrinSine"]
+
+
+AVAILABLE_INPUTS: ProbInputSpecs = {
+    "Currin1988": {
+        "function_id": "CurrinSine",
+        "description": (
+            "Input model for the Sine function from Currin et al. (1988)"
+        ),
+        "marginals": [
+            {
+                "name": "x",
+                "distribution": "uniform",
+                "parameters": [0.0, 1.0],
+                "description": None,
+            },
+        ],
+        "copulas": None,
+    },
+}
+
+
+def evaluate(xx: np.ndarray) -> np.ndarray:
+    """Evaluate the sine fcn from Currin et al (1988) 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 = np.sin(2 * np.pi * (xx[:, 0] - 0.1))
+
+    return yy
+
+
+class CurrinSine(UQTestFunFixDimABC):
+    """A concrete implementation of the sine fcn from Currin et al (1988)."""
+
+    _tags = ["metamodeling"]
+    _description = "Sine function from Currin et al. (1988)"
+    _available_inputs = AVAILABLE_INPUTS
+    _available_parameters = None
+
+    evaluate = staticmethod(evaluate)  # type: ignore