diff --git a/CHANGELOG.md b/CHANGELOG.md index 927396d..5717ef3 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 two-dimensional non-polynomial test function for metamodeling from + - The two-dimensional polynomial test function for metamodeling from Lim et al. (2002). - The three-dimensional sensitivity test function from Moon (2010). diff --git a/docs/_toc.yml b/docs/_toc.yml index 683f1e8..1f847bf 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -91,6 +91,8 @@ parts: title: Hyper-sphere Bound - file: test-functions/ishigami title: Ishigami + - file: test-functions/lim-non-poly + title: Lim et al. (2002) Non-Polynomial - file: test-functions/lim-poly title: Lim et al. (2002) Polynomial - file: test-functions/mclain-s1 diff --git a/docs/fundamentals/metamodeling.md b/docs/fundamentals/metamodeling.md index 68e94c0..9ef3076 100644 --- a/docs/fundamentals/metamodeling.md +++ b/docs/fundamentals/metamodeling.md @@ -36,6 +36,7 @@ in the comparison of metamodeling approaches. | {ref}`Friedman (6D) ` | 6 | `Friedman6D()` | | {ref}`Friedman (10D) ` | 10 | `Friedman10D()` | | {ref}`Gramacy (2007) 1D Sine ` | 1 | `Gramacy1DSine()` | +| {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()` | diff --git a/docs/test-functions/available.md b/docs/test-functions/available.md index 7b43083..e93da8b 100644 --- a/docs/test-functions/available.md +++ b/docs/test-functions/available.md @@ -50,6 +50,7 @@ regardless of their typical applications. | {ref}`Gramacy (2007) 1D Sine ` | 1 | `Gramacy1DSine()` | | {ref}`Hyper-sphere Bound ` | 2 | `HyperSphere()` | | {ref}`Ishigami ` | 3 | `Ishigami()` | +| {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()` | diff --git a/docs/test-functions/lim-non-poly.md b/docs/test-functions/lim-non-poly.md new file mode 100644 index 0000000..16638c1 --- /dev/null +++ b/docs/test-functions/lim-non-poly.md @@ -0,0 +1,150 @@ +--- +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:lim-non-poly)= +# Two-dimensional Non-Polynomial Function from Lim et al. (2002) + +```{code-cell} ipython3 +import numpy as np +import matplotlib.pyplot as plt +import uqtestfuns as uqtf +``` + +The non-polynomial test function from Lim et al. (2002) (or `LimNonPoly` for +short) is a two-dimensional scalar-valued function. +The function was used in {cite}`Lim2002` in the context of establishing the +connection between Gaussian process metamodel and polynomials. + +```{code-cell} ipython3 +:tags: [remove-input] + +from mpl_toolkits.axes_grid1 import make_axes_locatable + +my_fun = uqtf.LimNonPoly() + +# --- Create 2D data +xx_1d = np.linspace(0.0, 1.0, 1000)[:, np.newaxis] +mesh_2d = np.meshgrid(xx_1d, xx_1d) +xx_2d = np.array(mesh_2d).T.reshape(-1, 2) +yy_2d = my_fun(xx_2d) + +# --- Create two-dimensional plots +fig = plt.figure(figsize=(10, 5)) + +# Surface +axs_1 = plt.subplot(121, projection='3d') +axs_1.plot_surface( + mesh_2d[0], + mesh_2d[1], + yy_2d.reshape(1000,1000).T, + linewidth=0, + cmap="plasma", + antialiased=False, + alpha=0.5 +) +axs_1.set_xlabel("$x_1$", fontsize=14) +axs_1.set_ylabel("$x_2$", fontsize=14) +axs_1.set_zlabel("$\mathcal{M}(x_1, x_2)$", fontsize=14) +axs_1.set_title("Surface plot of LimPoly", fontsize=14) + +# Contour +axs_2 = plt.subplot(122) +cf = axs_2.contourf( + mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000).T, cmap="plasma", levels=10, +) +axs_2.set_xlabel("$x_1$", fontsize=14) +axs_2.set_ylabel("$x_2$", fontsize=14) +axs_2.set_title("Contour plot of LimPoly", fontsize=14) +divider = make_axes_locatable(axs_2) +cax = divider.append_axes('right', size='5%', pad=0.05) +fig.colorbar(cf, cax=cax, orientation='vertical') +axs_2.axis('scaled') + +fig.tight_layout(pad=4.0) +plt.gcf().set_dpi(75); +``` + + +## Test function instance + +To create a default instance of the test function: + +```{code-cell} ipython3 +my_testfun = uqtf.LimNonPoly() +``` + +Check if it has been correctly instantiated: + +```{code-cell} ipython3 +print(my_testfun) +``` + +## Description + +The test function is defined as follows[^location]: + +$$ +\mathcal{M}(\boldsymbol{x}) = \frac{(30 + 5 x_1 \sin{(5 x_1)}) (4 + \exp{(-5x_2)}) - 100}{6} +$$ +where $\boldsymbol{x} = \{ x_1, x_2 \}$ +is the two-dimensional vector of input variables further defined below. + +```{note} +This function is a rescaled version of Eq. (6) in {cite}`Welch1992`. +The function is also similar to its +{ref}`polynomial counterpart `; in fact, +the coefficients of the polynomial are chosen with that goal in mind. +``` + +## Probabilistic input + +The input consists of two uniformly distributed random variables as 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] + +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} +:style: unsrtalpha +:filter: docname in docnames +``` + +[^location]: See Eq. (28), Section 7, p. 121 {cite}`Lim2002`. \ No newline at end of file diff --git a/docs/test-functions/lim-poly.md b/docs/test-functions/lim-poly.md index 46373ee..8329967 100644 --- a/docs/test-functions/lim-poly.md +++ b/docs/test-functions/lim-poly.md @@ -75,8 +75,6 @@ fig.tight_layout(pad=4.0) plt.gcf().set_dpi(75); ``` -As shown in the plots above, the function features a saddle shaped surface. - ## Test function instance To create a default instance of the test function: @@ -93,7 +91,7 @@ print(my_testfun) ## Description -The test function is defined as follows: +The test function is defined as follows[^location]: $$ \mathcal{M}(\boldsymbol{x}) = 9 + \frac{5}{2} x_1 - \frac{35}{2} x_2 + \frac{5}{2} x_1 x_2 + 19 x_2^2 - \frac{15}{2} x_1^3 - \frac{5}{2} x_1 x_2^2 - \frac{11}{2} x_2^4 + x_1^3 x_2^2, @@ -101,6 +99,12 @@ $$ where $\boldsymbol{x} = \{ x_1, x_2 \}$ is the two-dimensional vector of input variables further defined below. +```{note} +The coefficients of the test function are chosen such that its global features +are similar to its +{ref}`non-polynomial counterpart `. +``` + ## Probabilistic input The input consists of two uniformly distributed random variables as shown @@ -140,3 +144,5 @@ plt.gcf().set_dpi(150); :style: unsrtalpha :filter: docname in docnames ``` + +[^location]: See Eq. (27), Section 7, p. 119 {cite}`Lim2002`. \ No newline at end of file diff --git a/src/uqtestfuns/test_functions/__init__.py b/src/uqtestfuns/test_functions/__init__.py index 1b950fa..d37fb8b 100644 --- a/src/uqtestfuns/test_functions/__init__.py +++ b/src/uqtestfuns/test_functions/__init__.py @@ -20,7 +20,7 @@ from .gramacy2007 import Gramacy1DSine from .hyper_sphere import HyperSphere from .ishigami import Ishigami -from .lim import LimPoly +from .lim import LimPoly, LimNonPoly from .oakley2002 import Oakley1D from .otl_circuit import OTLCircuit from .mclain import McLainS1, McLainS2, McLainS3, McLainS4, McLainS5 @@ -69,6 +69,7 @@ "Gramacy1DSine", "HyperSphere", "Ishigami", + "LimNonPoly", "LimPoly", "Oakley1D", "OTLCircuit", diff --git a/src/uqtestfuns/test_functions/lim.py b/src/uqtestfuns/test_functions/lim.py index ec27edf..db51647 100644 --- a/src/uqtestfuns/test_functions/lim.py +++ b/src/uqtestfuns/test_functions/lim.py @@ -39,7 +39,7 @@ def evaluate_poly(xx: np.ndarray) -> np.ndarray: - """Evaluate the non-polynomial test function from Lim et al (2002). + """Evaluate the polynomial test function from Lim et al (2002). The function is a polynomial with maximum total degree of 5. @@ -89,3 +89,51 @@ class LimPoly(UQTestFunFixDimABC): _available_parameters = None evaluate = staticmethod(evaluate_poly) # type: ignore + + +def evaluate_non_poly(xx: np.ndarray) -> np.ndarray: + """Evaluate the non-polynomial test function from Lim et al (2002). + + The function is a polynomial with maximum total degree of 5. + + Parameters + ---------- + xx : np.ndarray + Two-Dimensional input values given by N-by-2 arrays 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. + """ + trm_1 = 30 + 5 * xx[:, 0] * np.sin(5 * xx[:, 0]) + trm_2 = 4 + np.exp(-5 * xx[:, 1]) + + yy = (trm_1 * trm_2 - 100) / 6.0 + + return yy + + +class LimNonPoly(UQTestFunFixDimABC): + """An implementation of the 2D non-polynomial from Lim et al. (2002).""" + + _tags = ["metamodeling"] + _description = ( + "Two-dimensional non-polynomial function from Lim et al. (2002)" + ) + _available_inputs: ProbInputSpecs = { + "Lim2002": { + "function_id": "LimNonPoly", + "description": ( + "Input specification for the non-polynomial function " + "from Lim et al. (2002)" + ), + "marginals": MARGINALS_LIM2002, + "copulas": None, + } + } + _available_parameters = None + + evaluate = staticmethod(evaluate_non_poly) # type: ignore