Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an implementation of McLain S3 function. #195

Merged
merged 1 commit into from
Jun 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
exercises, is added as a UQ test function.
- The two-dimensional (third) Franke function, relevant for metamodeling
exercises, is added as a UQ test function.
- The two-dimensional McLain functions (S1, S2, S5), relevant for metamodeling
exercises, are added as UQ test functions.
- The two-dimensional McLain functions (S1, S2, S3, S5), relevant for
metamodeling exercises, are added as UQ test functions.

## [0.1.1] - 2023-07-03

Expand Down
2 changes: 2 additions & 0 deletions docs/_toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ parts:
title: McLain S1
- file: test-functions/mclain-s2
title: McLain S2
- file: test-functions/mclain-s3
title: McLain S3
- file: test-functions/mclain-s5
title: McLain S5
- file: test-functions/piston
Expand Down
1 change: 1 addition & 0 deletions docs/fundamentals/metamodeling.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ in the comparison of metamodeling approaches.
| {ref}`OTL Circuit <test-functions:otl-circuit>` | 6 / 20 | `OTLCircuit()` |
| {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 S5 <test-functions:mclain-s5>` | 2 | `McLainS5()` |
| {ref}`Piston Simulation <test-functions:piston>` | 7 / 20 | `Piston()` |
| {ref}`Sulfur <test-functions:sulfur>` | 9 | `Sulfur()` |
Expand Down
1 change: 1 addition & 0 deletions docs/test-functions/available.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ available in the current UQTestFuns, regardless of their typical applications.
| {ref}`OTL Circuit <test-functions:otl-circuit>` | 6 / 20 | `OTLCircuit()` |
| {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 S5 <test-functions:mclain-s5>` | 2 | `McLainS5()` |
| {ref}`Piston Simulation <test-functions:piston>` | 7 / 20 | `Piston()` |
| {ref}`Sobol'-G <test-functions:sobol-g>` | M | `SobolG()` |
Expand Down
162 changes: 162 additions & 0 deletions docs/test-functions/mclain-s3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
---
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:mclain-s3)=
# McLain S2 Function

```{code-cell} ipython3
import numpy as np
import matplotlib.pyplot as plt
import uqtestfuns as uqtf
```

The McLain S3 function is a two-dimensional scalar-valued function.
The function was introduced in {cite}`McLain1974` as a test function for
procedures to construct contours from a given set of points.

```{note}
The McLain's test functions are a set of five two-dimensional functions
that mathematically defines surfaces. The functions are:

- {ref}`S1 <test-functions:mclain-s1>`: A part of a sphere
- {ref}`S2 <test-functions:mclain-s2>`: A steep hill rising from a plain
- {ref}`S3 <test-functions:mclain-s3>`: A less steep hill (this function)
- {ref}`S5 <test-functions:mclain-s5>`: A plateau and plain separated by a steep cliff
```

```{code-cell} ipython3
:tags: [remove-input]

from mpl_toolkits.axes_grid1 import make_axes_locatable

my_fun = uqtf.McLainS3()

# --- Create 2D data
xx_1d = np.linspace(1.0, 10.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),
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 McLain S3", fontsize=14)

# Contour
axs_2 = plt.subplot(122)
cf = axs_2.contourf(
mesh_2d[0], mesh_2d[1], yy_2d.reshape(1000, 1000), cmap="plasma"
)
axs_2.set_xlabel("$x_1$", fontsize=14)
axs_2.set_ylabel("$x_2$", fontsize=14)
axs_2.set_title("Contour plot of McLain S3", 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);
```

As shown in the plots above, the resulting surface resembles a gentler
hill (compared to {ref}`S2 <test-functions:mclain-s2>`) rising from a plain.
The location of the peak is at $(5.0, 5.0)$
and with the height of $1.0$.

```{note}
The McLain S3 function appeared in a modified form in the report
of Franke {cite}`Franke1979` (specifically the (5th) Franke function).

In fact, four of Franke's test functions are
slight modifications of McLain's, including the translation of the input domain
from $[1.0, 10.0]$ to $[0.0, 1.0]$.
```

## Test function instance

To create a default instance of the McLain S3 function:

```{code-cell} ipython3
my_testfun = uqtf.McLainS3()
```

Check if it has been correctly instantiated:

```{code-cell} ipython3
print(my_testfun)
```

## Description

The (2nd) Franke function is defined as follows:

$$
\mathcal{M}(\boldsymbol{x}) = \exp{\left[ -
0.25 \left( (x_1 - 5)^2 + (x_2 - 5)^2 \right) \right]}
$$
where $\boldsymbol{x} = \{ x_1, x_2 \}$
is the two-dimensional vector of input variables further defined below.

## Probabilistic input

Based on {cite}`McLain1974`, the probabilistic input model
for the function consists of two independent random variables as shown below.

```{code-cell} ipython3
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, color="#8da0cb");
plt.grid();
plt.ylabel("Counts [-]");
plt.xlabel("$\mathcal{M}(\mathbf{X})$");
plt.gcf().set_dpi(150);
```

## References

```{bibliography}
:style: plain
:filter: docname in docnames
```
3 changes: 2 additions & 1 deletion src/uqtestfuns/test_functions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from .ishigami import Ishigami
from .oakley_ohagan_1d import OakleyOHagan1D
from .otl_circuit import OTLCircuit
from .mclain import McLainS1, McLainS2, McLainS5
from .mclain import McLainS1, McLainS2, McLainS3, McLainS5
from .piston import Piston
from .sobol_g import SobolG
from .sulfur import Sulfur
Expand All @@ -33,6 +33,7 @@
"OTLCircuit",
"McLainS1",
"McLainS2",
"McLainS3",
"McLainS5",
"Piston",
"SobolG",
Expand Down
53 changes: 43 additions & 10 deletions src/uqtestfuns/test_functions/mclain.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

- S1: A part of a sphere
- S2: A steep hill rising from a plain
- S3: A less steep hill
- S5: A plateau and plain separated by a steep cliff

Four of the functions (S1-S3 and S5) appeared in modified forms in [2].
Expand All @@ -33,20 +34,20 @@
from ..core.uqtestfun_abc import UQTestFunABC
from .available import create_prob_input_from_available

__all__ = ["McLainS1", "McLainS2", "McLainS5"]
__all__ = ["McLainS1", "McLainS2", "McLainS3", "McLainS5"]

INPUT_MARGINALS_MCLAIN1974 = [ # From Ref. [1]
UnivDist(
name="X1",
distribution="uniform",
parameters=[1.0, 10.0],
description=None,
description="None",
),
UnivDist(
name="X2",
distribution="uniform",
parameters=[1.0, 10.0],
description=None,
description="None",
),
]

Expand Down Expand Up @@ -95,26 +96,39 @@ def _init(
UQTestFunABC.__init__(self, prob_input=prob_input, name=name)


def _eval_s1(self, xx: np.ndarray):
def _eval_s1(uqtestfun: UQTestFunABC, xx: np.ndarray):
"""Evaluate the McLain S1 function."""
yy = np.sqrt(64 - (xx[:, 0] - 5.5) ** 2 - (xx[:, 1] - 5.5) ** 2)

return yy


def _eval_s2(self, xx: np.ndarray):
def _eval_s2(uqtestfun: UQTestFunABC, xx: np.ndarray):
"""Evaluate the McLain S2 function."""
yy = np.exp(-1.0 * ((xx[:, 0] - 5) ** 2 + (xx[:, 1] - 5) ** 2))

return yy


def _eval_s5(self, xx: np.ndarray):
def _eval_s3(uqtestfun: UQTestFunABC, xx: np.ndarray):
"""Evaluate the McLain S3 function."""
yy = np.exp(-0.25 * ((xx[:, 0] - 5) ** 2 + (xx[:, 1] - 5) ** 2))

return yy


def _eval_s5(uqtestfun: UQTestFunABC, xx: np.ndarray):
"""Evaluate the McLain S5 function."""
yy = np.tanh(xx[:, 0] + xx[:, 1] - 11)

return yy


class McLainS1(UQTestFunABC):
"""A concrete implementation of the McLain S1 function."""
"""A concrete implementation of the McLain S1 function.

The function features a part of a sphere.
"""

_TAGS = COMMON_METADATA["_TAGS"]
_AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"]
Expand All @@ -136,20 +150,39 @@ class McLainS2(UQTestFunABC):
_AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"]
_AVAILABLE_PARAMETERS = COMMON_METADATA["_AVAILABLE_PARAMETERS"]
_DEFAULT_SPATIAL_DIMENSION = COMMON_METADATA["_DEFAULT_SPATIAL_DIMENSION"]
_DESCRIPTION = f"McLain S1 function {COMMON_METADATA['_DESCRIPTION']}"
_DESCRIPTION = f"McLain S2 function {COMMON_METADATA['_DESCRIPTION']}"

__init__ = _init # type: ignore
evaluate = _eval_s2


class McLainS3(UQTestFunABC):
"""A concrete implementation of the McLain S2 function.

The function features a less steep hill (compared to S2).
"""

_TAGS = COMMON_METADATA["_TAGS"]
_AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"]
_AVAILABLE_PARAMETERS = COMMON_METADATA["_AVAILABLE_PARAMETERS"]
_DEFAULT_SPATIAL_DIMENSION = COMMON_METADATA["_DEFAULT_SPATIAL_DIMENSION"]
_DESCRIPTION = f"McLain S3 function {COMMON_METADATA['_DESCRIPTION']}"

__init__ = _init # type: ignore
evaluate = _eval_s3


class McLainS5(UQTestFunABC):
"""A concrete implementation of the McLain S5 function."""
"""A concrete implementation of the McLain S5 function.

The function features two plateaus separated by a steep cliff.
"""

_TAGS = COMMON_METADATA["_TAGS"]
_AVAILABLE_INPUTS = COMMON_METADATA["_AVAILABLE_INPUTS"]
_AVAILABLE_PARAMETERS = COMMON_METADATA["_AVAILABLE_PARAMETERS"]
_DEFAULT_SPATIAL_DIMENSION = COMMON_METADATA["_DEFAULT_SPATIAL_DIMENSION"]
_DESCRIPTION = f"McLain S1 function {COMMON_METADATA['_DESCRIPTION']}"
_DESCRIPTION = f"McLain S5 function {COMMON_METADATA['_DESCRIPTION']}"

__init__ = _init # type: ignore
evaluate = _eval_s5