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

Issue:
Add the series of test functions from Genz (1984)
The paper by Genz (1984)[^1] contains a series of test functions used in numerical integration exercises. Some of the functions were often revisited in the context of sensitivity analysis (see, for instance, Zhang and Pandey (2014) [^2]).

Implement and include the functions in UQTestFuns:

- [x] Oscillatory (resolved via PR #416)
- [x] Product Peak (resolved via PR #413)
- [x] Corner Peak (resolved via PR #412)
- [x] Gaussian (resolved via PR #415)
- [x] Continuous (resolved via PR #414)
- [x] Discontinuous (resolved via PR #417)

[^1]: A. Genz, “Testing Multidimensional Integration Routines,” in Proc. of International Conference on Tools, Methods and Languages for Scientific and Engineering Computation, USA: Elsevier North-Holland, Inc., 1984, pp. 81–94.
[^2]: X. Zhang and M. D. Pandey, “An effective approximation for variance-based global sensitivity analysis,” Reliability Engineering & System Safety, vol. 121, pp. 164–174, Jan. 2014, doi: [10.1016/j.ress.2013.07.010](https://doi.org/10.1016/j.ress.2013.07.010).


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 568a779..7112345 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 M-dimensional product peak function from Genz (1984) for integration
+  exercises; one parameter set from the literature is included.
 - The M-dimensional corner peak function from Genz (1984) for integration
   and sensitivity analysis exercises.
 - The one-dimensional sine function from Currin et al. (1988) for metamodeling
diff --git a/docs/_toc.yml b/docs/_toc.yml
index e2eca1f..74c6d2b 100644
--- a/docs/_toc.yml
+++ b/docs/_toc.yml
@@ -96,6 +96,8 @@ parts:
             title: Gayton Hat
           - file: test-functions/genz-corner-peak
             title: Genz (Corner Peak)
+          - file: test-functions/genz-product-peak
+            title: Genz (Product Peak)
           - file: test-functions/gramacy-1d-sine
             title: Gramacy (2007) 1D Sine
           - file: test-functions/hyper-sphere
diff --git a/docs/fundamentals/integration.md b/docs/fundamentals/integration.md
index 3475bb0..4e0fd32 100644
--- a/docs/fundamentals/integration.md
+++ b/docs/fundamentals/integration.md
@@ -18,15 +18,16 @@ kernelspec:
 The table below listed the available test functions typically used
 in the testing and comparison of numerical integration method.
 
-|                                    Name                                     | Input Dimension |     Constructor     |
-|:---------------------------------------------------------------------------:|:---------------:|:-------------------:|
-|        {ref}`Bratley et al. (1992) A <test-functions:bratley1992a>`         |        M        |  `Bratley1992a()`   |
-|        {ref}`Bratley et al. (1992) B <test-functions:bratley1992b>`         |        M        |  `Bratley1992b()`   |
-|        {ref}`Bratley et al. (1992) C <test-functions:bratley1992c>`         |        M        |  `Bratley1992c()`   |
-|        {ref}`Bratley et al. (1992) D <test-functions:bratley1992d>`         |        M        |  `Bratley1992d()`   |
-|         {ref}`Genz (Corner Peak) <test-functions:genz-corner-peak>`         |        M        | `GenzCornerPeak()`  |
-|                  {ref}`Sobol'-G <test-functions:sobol-g>`                   |        M        |     `SobolG()`      |
-|            {ref}`Welch et al. (1992) <test-functions:welch1992>`            |       20        |    `Welch1992()`    |
+|                             Name                              | Input Dimension |     Constructor      |
+|:-------------------------------------------------------------:|:---------------:|:--------------------:|
+| {ref}`Bratley et al. (1992) A <test-functions:bratley1992a>`  |        M        |   `Bratley1992a()`   |
+| {ref}`Bratley et al. (1992) B <test-functions:bratley1992b>`  |        M        |   `Bratley1992b()`   |
+| {ref}`Bratley et al. (1992) C <test-functions:bratley1992c>`  |        M        |   `Bratley1992c()`   |
+| {ref}`Bratley et al. (1992) D <test-functions:bratley1992d>`  |        M        |   `Bratley1992d()`   |
+|  {ref}`Genz (Corner Peak) <test-functions:genz-corner-peak>`  |        M        |  `GenzCornerPeak()`  |
+| {ref}`Genz (Product Peak) <test-functions:genz-product-peak>` |        M        | `GenzProductPeak()`  |
+|           {ref}`Sobol'-G <test-functions:sobol-g>`            |        M        |      `SobolG()`      |
+|     {ref}`Welch et al. (1992) <test-functions:welch1992>`     |       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 d706a77..3b89416 100644
--- a/docs/references.bib
+++ b/docs/references.bib
@@ -999,4 +999,14 @@ @InProceedings{Genz1984
   location  = {Paris, France},
 }
 
+@Article{Jakeman2015,
+  author  = {Jakeman, J. D. and Eldred, M. S. and Sargsyan, K.},
+  journal = {Journal of Computational Physics},
+  title   = {Enhancingℓ1-minimization estimates of polynomial chaos expansions using basis selection},
+  year    = {2015},
+  pages   = {18--34},
+  volume  = {289},
+  doi     = {10.1016/j.jcp.2015.02.025},
+}
+
 @Comment{jabref-meta: databaseType:bibtex;}


Test patch:
diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md
index c49ff96..24df6d4 100644
--- a/docs/test-functions/available.md
+++ b/docs/test-functions/available.md
@@ -51,6 +51,7 @@ regardless of their typical applications.
 |                 {ref}`Friedman (10D) <test-functions:friedman-10d>`                 |       10        |         `Friedman10D()`         |
 |                    {ref}`Gayton Hat <test-functions:gayton-hat>`                    |        2        |          `GaytonHat()`          |
 |             {ref}`Genz (Corner Peak) <test-functions:genz-corner-peak>`             |        M        |       `GenzCornerPeak()`        |
+|            {ref}`Genz (Product Peak) <test-functions:genz-product-peak>`            |        M        |       `GenzProductPeak()`       |
 |           {ref}`Gramacy (2007) 1D Sine <test-functions:gramacy-1d-sine>`            |        1        |        `Gramacy1DSine()`        |
 |               {ref}`Hyper-sphere Bound <test-functions:hyper-sphere>`               |        2        |         `HyperSphere()`         |
 |                      {ref}`Ishigami <test-functions:ishigami>`                      |        3        |          `Ishigami()`           |
diff --git a/docs/test-functions/genz-corner-peak.md b/docs/test-functions/genz-corner-peak.md
index b702aed..a56f8e4 100644
--- a/docs/test-functions/genz-corner-peak.md
+++ b/docs/test-functions/genz-corner-peak.md
@@ -21,24 +21,29 @@ import matplotlib.pyplot as plt
 import uqtestfuns as uqtf
 ```
 
-The Genz corner peak function is an $M$-dimensional, scalar-valued
-function.
-The function was introduced in {cite}`Genz1984` to assess the accuracy
-of numerical integration routines.
-The function was featured in {cite}`Zhang2014` as a test function
-for a global sensitivity analysis method.
+The Genz corner peak function is an $M$-dimensional scalar-valued
+function commonly used to assess the accuracy of numerical
+integration routines.
+It is one of six functions introduced by Genz {cite}`Genz1984`;
+see the box below.
+
+The function was later featured in {cite}`Zhang2014` as a test function
+for a global sensitivity analysis method and in {cite}`Jakeman2015` for
+metamodeling applications.
 
 ```{note}
 Genz {cite}`Genz1984` introduced six challenging
 parameterized $M$-dimensional functions
 designed to test the performance of numerical integration routines:
 
+- {ref}`Corner peak <test-functions:genz-product-peak>` features a prominent
+  peak in the center of the multidimensional space.
 - {ref}`Corner peak <test-functions:genz-corner-peak>` features a prominent
   peak in one corner of the multidimensional space. (_this function_)
 
-The functions are further characterized by shift and scale parameters.
-While the shift parameter has minimal impact on the integral's value,
-the scale parameter significantly affects
+The functions are further characterized by offset (shift) and shape parameters.
+While the offset parameter has minimal impact on the integral's value,
+the shape parameter significantly affects
 the difficulty of the integration problem.
 ```
 
@@ -131,15 +136,16 @@ my_testfun = uqtf.GenzCornerPeak(input_dimension=6)
 
 ## Description
 
-The Genz corner peak function as follows:
+The Genz corner peak function is defined as:
 
 $$
 \mathcal{M}(\boldsymbol{x}; \boldsymbol{a}) = \left( 1 + \sum_{i = 1}^{M} a_i x_i \right)^{-(M + 1)}
 $$
+
 where $\boldsymbol{x} = \left( x_1, \ldots, x_M \right)$
 is the $M$-dimensional vector of input variables
 and $\boldsymbol{a} = \left( a_1, \ldots, a_M \right)$ is the $M$-dimensional
-vector of (fixed) scale parameters.
+vector of (fixed) shape parameters.
 Further details about these parameters are provided below.
 
 ## Probabilistic input
@@ -155,7 +161,7 @@ print(my_testfun.prob_input)
 ## Parameters
 
 The parameters of the Genz corner peak function consists
-of the vector of scale parameters $\boldsymbol{a}$,
+of the vector of shape (scale) parameters $\boldsymbol{a}$,
 which determine the extent of the corner peaking.
 Larger values of $\boldsymbol{a}$
 increase the prominence of the peak,
diff --git a/docs/test-functions/genz-product-peak.md b/docs/test-functions/genz-product-peak.md
new file mode 100644
index 0000000..99c9acd
--- /dev/null
+++ b/docs/test-functions/genz-product-peak.md
@@ -0,0 +1,210 @@
+---
+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:genz-product-peak)=
+# Genz Product Peak Function
+
+```{code-cell} ipython3
+import numpy as np
+import matplotlib.pyplot as plt
+import uqtestfuns as uqtf
+```
+
+The Genz product peak function is an $M$-dimensional scalar-valued
+function commonly used to assess the accuracy of numerical
+integration routines.
+It is one of six functions introduced by Genz {cite}`Genz1984`;
+see the box below.
+
+```{note}
+Genz {cite}`Genz1984` introduced six challenging
+parameterized $M$-dimensional functions
+designed to test the performance of numerical integration routines:
+
+- {ref}`Corner peak <test-functions:genz-product-peak>` features a prominent
+  peak in the center of the multidimensional space. (_this function_)
+- {ref}`Corner peak <test-functions:genz-corner-peak>` features a prominent
+  peak in one corner of the multidimensional space.
+
+The functions are further characterized by offset (shift) and shape parameters.
+While the offset parameter has minimal impact on the integral's value,
+the shape parameter significantly affects
+the difficulty of the integration problem.
+```
+
+The plots for one-dimensional and two-dimensional Genz product peak function
+with the default parameters can be seen below.
+
+```{code-cell} ipython3
+:tags: [remove-input]
+
+from mpl_toolkits.axes_grid1 import make_axes_locatable
+
+# --- Create 1D data from Genz Product Peak
+my_fun_1d = uqtf.GenzProductPeak(input_dimension=1)
+xx_1d = np.linspace(0, 1, 1000)[:, np.newaxis]
+yy_1d = my_fun_1d(xx_1d)
+
+# --- Create 2D data from Genz Product Peak
+my_fun_2d = uqtf.GenzProductPeak(input_dimension=2)
+mesh_2d = np.meshgrid(xx_1d, xx_1d)
+xx_2d = np.array(mesh_2d).T.reshape(-1, 2)
+yy_2d = my_fun_2d(xx_2d)
+
+# --- Create two-dimensional 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 Genz product peak")
+
+# 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 Genz product peak", 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 Genz product peak", 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.GenzProductPeak()
+```
+
+Check if it has been correctly instantiated:
+
+```{code-cell} ipython3
+print(my_testfun)
+```
+
+By default, the input dimension is set to $2$[^default_dimension].
+To create an instance with another value of input dimension,
+pass an integer to the parameter `input_dimension` (the first parameter).
+For example, to create an instance of the test function in six dimensions,
+type:
+
+```python
+my_testfun = uqtf.GenzProductPeak(input_dimension=6)
+```
+
+## Description
+
+The Genz product peak function is defined as:
+
+$$
+\mathcal{M}(\boldsymbol{x}; \boldsymbol{a}, \boldsymbol{b}) = \prod_{i = 1}^M \frac{1}{\left( a_i^{-2} + (x_i - b_i)^2 \right)}
+$$
+
+where $\boldsymbol{x} = \left( x_1, \ldots, x_M \right)$
+is the $M$-dimensional vector of input variables;
+and $\boldsymbol{a} = \left( a_1, \ldots, a_M \right)$ 
+$\boldsymbol{b} = \left( b_1, \ldots, b_M \right)$ are $M$-dimensional
+vectors corresponding to the (fixed) shape and offset parameters, respectively.
+Further details about these parameters are provided below.
+
+## Probabilistic input
+
+The input specification for the Genz product peak function is shown below.
+
+```{code-cell} ipython3
+:tags: [hide-input]
+
+print(my_testfun.prob_input)
+```
+
+## Parameters
+
+The parameters of the Genz corner peak function consists
+of the vector of shape parameters $\boldsymbol{a}$
+and the vector of offset parameters $\boldsymbol{b}$. 
+
+The shape parameters determines the extent of the product peaking;
+Larger values of $\boldsymbol{a}$
+increase the prominence of the peak,
+making the integration problem more challenging.
+The offset parameters, on the other hand, does not affect significantly
+the difficulty of the problem and can be chosen randomly.
+
+The default parameter is shown below.
+
+```{code-cell} ipython3
+:tags: [hide-input]
+
+print(my_testfun.parameters)
+```
+
+
+## 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}(\mathbf{X})$");
+plt.gcf().set_dpi(150);
+```
+
+## References
+
+```{bibliography}
+:filter: docname in docnames
+```
+
+[^default_dimension]: This default dimension applies to all variable dimension
+test functions. It will be used if the `input_dimension` argument is not given.
\ No newline at end of file
diff --git a/src/uqtestfuns/core/parameters.py b/src/uqtestfuns/core/parameters.py
index a79e72c..8618fdb 100644
--- a/src/uqtestfuns/core/parameters.py
+++ b/src/uqtestfuns/core/parameters.py
@@ -252,7 +252,7 @@ def _assert_value_assign(self, keyword, value):
         # When a parameter is assigned, be more forgiving
         if not isinstance(value, type_):
             warnings.warn(
-                message="Expected {type_} but got {type(value)}",
+                message=f"Expected {type_} but got {type(value)}",
                 category=UserWarning,
                 stacklevel=2,
             )
diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py
index e6242a3..a82b2ee 100644
--- a/src/uqtestfuns/test_functions/__init__.py
+++ b/src/uqtestfuns/test_functions/__init__.py
@@ -20,7 +20,7 @@
 from .franke import Franke1, Franke2, Franke3, Franke4, Franke5, Franke6
 from .friedman import Friedman6D, Friedman10D
 from .gayton_hat import GaytonHat
-from .genz import GenzCornerPeak
+from .genz import GenzCornerPeak, GenzProductPeak
 from .gramacy2007 import Gramacy1DSine
 from .hyper_sphere import HyperSphere
 from .ishigami import Ishigami
@@ -81,6 +81,7 @@
     "Friedman10D",
     "GaytonHat",
     "GenzCornerPeak",
+    "GenzProductPeak",
     "Gramacy1DSine",
     "HyperSphere",
     "Ishigami",
diff --git a/src/uqtestfuns/test_functions/genz.py b/src/uqtestfuns/test_functions/genz.py
index ac8f961..a2e70e6 100644
--- a/src/uqtestfuns/test_functions/genz.py
+++ b/src/uqtestfuns/test_functions/genz.py
@@ -5,16 +5,14 @@
 designed to test the performance of numerical integration routines:
 
 - Corner peak function features a prominent peak in one corner
-  of the multidimensional space.
+  of the multidimensional space. This function was featured as a test function
+  in sensitivity analysis ([2]) and metamodeling [3] exercises.
 
 The functions are further characterized by shift and scale parameters.
 While the shift parameter has minimal impact on the integral's value,
 the scale parameter significantly affects
 the difficulty of the integration problem.
 
-Some of the functions were featured as test functions in sensitivity
-analysis literature, e.g., [2].
-
 References
 ----------
 
@@ -26,6 +24,10 @@
    global sensitivity analysis,” Reliability Engineering & System Safety,
    vol. 121, pp. 164–174, 2014.
    DOI: 10.1016/j.ress.2013.07.010
+3. J. D. Jakeman, M. S. Eldred, and K. Sargsyan, “Enhancing l1-minimization
+   estimates of polynomial chaos expansions using basis selection,”
+   Journal of Computational Physics, vol. 289, pp. 18–34, 2015.
+   DOI: 10.1016/j.jcp.2015.02.025.
 """
 
 import numpy as np
@@ -37,7 +39,7 @@
 )
 from uqtestfuns.core.uqtestfun_abc import UQTestFunVarDimABC
 
-__all__ = ["GenzCornerPeak"]
+__all__ = ["GenzCornerPeak", "GenzProductPeak"]
 
 
 MARGINALS_GENZ1984: MarginalSpecs = [
@@ -49,16 +51,21 @@
     }
 ]
 
-COMMON_TAGS = ["integration", "sensitivity"]
-
 
 def _get_aa_genz_1984(input_dimension: int) -> np.ndarray:
-    """Construct a scaling array for the Genz function from [1]."""
+    """Construct an array of shape parameters for the Genz functions [1]."""
     aa = 5.0 * np.ones(input_dimension)
 
     return aa
 
 
+def _get_bb_genz_1984(input_dimension: int) -> np.ndarray:
+    """Construct an array of shape parameters for the Genz functions [1]."""
+    bb = 0.5 * np.ones(input_dimension)
+
+    return bb
+
+
 def _get_aa_zhang_2014_1(input_dimension: int) -> np.ndarray:
     """Construct a scaling array for the Genz function from [2] (3D case).
 
@@ -96,7 +103,7 @@ def evaluate_corner_peak(xx: np.ndarray, aa: np.ndarray) -> np.ndarray:
         M-Dimensional input values given by an N-by-M array where
         N is the number of input values.
     aa : np.ndarray
-        The vector of scale parameters of the Genz family of integrand
+        The vector of shape parameters of the Genz family of integrand
         functions; the larger the value the more difficult the integrations.
 
     Returns
@@ -115,7 +122,7 @@ def evaluate_corner_peak(xx: np.ndarray, aa: np.ndarray) -> np.ndarray:
 class GenzCornerPeak(UQTestFunVarDimABC):
     """A concrete implementation of the corner peak from Genz (1984)."""
 
-    _tags = COMMON_TAGS
+    _tags = ["integration", "metamodeling", "sensitivity"]
     _description = "Corner peak integrand from Genz (1984)"
     _available_inputs: ProbInputSpecs = {
         "Genz1984": {
@@ -131,14 +138,15 @@ class GenzCornerPeak(UQTestFunVarDimABC):
         "Genz1984": {
             "function_id": "GenzCornerPeak",
             "description": (
-                "Parameter set for the corner peak function from Genz (1984)"
+                "Parameter set for the corner peak function from Genz (1984); "
+                "constant shape and offset values"
             ),
             "declared_parameters": [
                 {
                     "keyword": "aa",
                     "value": _get_aa_genz_1984,
                     "type": np.ndarray,
-                    "description": "Scaling parameter",
+                    "description": "Shape parameter",
                 },
             ],
         },
@@ -146,14 +154,14 @@ class GenzCornerPeak(UQTestFunVarDimABC):
             "function_id": "GenzCornerPeak",
             "description": (
                 "Parameter set for the corner peak function from Zhang and "
-                "Pandey (2014); Section 4.4 (3D case)"
+                "Pandey (2014); Section 4.4 (3D case), linearly increasing"
             ),
             "declared_parameters": [
                 {
                     "keyword": "aa",
                     "value": _get_aa_zhang_2014_1,
                     "type": np.ndarray,
-                    "description": "Scaling parameter",
+                    "description": "Shape parameter",
                 },
             ],
         },
@@ -161,14 +169,14 @@ class GenzCornerPeak(UQTestFunVarDimABC):
             "function_id": "GenzCornerPeak",
             "description": (
                 "Parameter set for the corner peak function from Zhang and "
-                "Pandey (2014); Section 4.4 (10D case)"
+                "Pandey (2014); Section 4.4 (10D case), linearly increasing"
             ),
             "declared_parameters": [
                 {
                     "keyword": "aa",
                     "value": _get_aa_zhang_2014_2,
                     "type": np.ndarray,
-                    "description": "Scaling parameter",
+                    "description": "Shape parameter",
                 },
             ],
         },
@@ -176,3 +184,77 @@ class GenzCornerPeak(UQTestFunVarDimABC):
     _default_parameters_id = "Genz1984"
 
     evaluate = staticmethod(evaluate_corner_peak)  # type: ignore
+
+
+def evaluate_product_peak(
+    xx: np.ndarray, aa: np.ndarray, bb: np.ndarray
+) -> np.ndarray:
+    """Compute the product peak 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.
+    aa : np.ndarray
+        The vector of shape parameters of the Genz family of integrand
+        functions; the larger the value the more difficult the integrations.
+        The length of the vector must M.
+    bb : np.ndarray
+        The vector of offset parameters of the Genz family of integrand
+        functions; the values do not alter significantly the difficulty of
+        the integration problem.
+        The length of the vector must be M.
+
+    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.prod(1 / ((xx - bb) ** 2 + aa ** (-2)), axis=1)
+
+    return yy
+
+
+class GenzProductPeak(UQTestFunVarDimABC):
+    """A concrete implementation of the product peak from Genz (1984)."""
+
+    _tags = ["integration"]
+    _description = "Product peak integrand from Genz (1984)"
+    _available_inputs: ProbInputSpecs = {
+        "Genz1984": {
+            "function_id": "GenzProductPeak",
+            "description": (
+                "Input specification for the Genz family of integrand"
+            ),
+            "marginals": MARGINALS_GENZ1984,
+            "copulas": None,
+        }
+    }
+    _available_parameters: FunParamSpecs = {
+        "Genz1984": {
+            "function_id": "GenzProductPeak",
+            "description": (
+                "Parameter set for the product peak function from Genz (1984);"
+                " constant shape and offset values"
+            ),
+            "declared_parameters": [
+                {
+                    "keyword": "aa",
+                    "value": _get_aa_genz_1984,
+                    "type": np.ndarray,
+                    "description": "Shape parameter",
+                },
+                {
+                    "keyword": "bb",
+                    "value": _get_bb_genz_1984,
+                    "type": np.ndarray,
+                    "description": "Offset parameter",
+                },
+            ],
+        },
+    }
+    _default_parameters_id = "Genz1984"
+
+    evaluate = staticmethod(evaluate_product_peak)  # type: ignore
diff --git a/tests/builtin_test_functions/test_coffee_cup.py b/tests/builtin_test_functions/test_coffee_cup.py
index fa5aecd..a2a5267 100644
--- a/tests/builtin_test_functions/test_coffee_cup.py
+++ b/tests/builtin_test_functions/test_coffee_cup.py
@@ -52,7 +52,7 @@ def test_parameter_nts(n_ts):
     assert fun.output_dimension == n_ts
 
 
-@pytest.mark.parametrize("temp_0", [50, 60, 70])
+@pytest.mark.parametrize("temp_0", [50.0, 60.0, 70.0])
 def test_parameter_temp0(temp_0):
     # Create an instance
     fun = CoffeeCup()