Source code for rc.data.functions

#  BSD 3-Clause License.
# 
#  Copyright (c) 2019-2025 Robert A. Milton. All rights reserved.
# 
#  Redistribution and use in source and binary forms, with or without modification, are permitted provided that
#  the following conditions are met:
#
#  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
#  following disclaimer.
#
#  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
#  following disclaimer in the documentation and/or other materials provided with the distribution.
#
#  3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or
#  promote products derived from this software without specific prior written permission.
#
#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
#  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
#  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
#  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
#  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
#  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
#  OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
#  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

""" Test functions, taken from `SALib <https://salib.readthedocs.io/en/latest/api/SALib.test_functions.html>`_."""

from __future__ import annotations

from rc.base import *
import SALib.test_functions.Ishigami, SALib.test_functions.Sobol_G, SALib.test_functions.oakley2004


[docs] class Scalar: """A scalar function ``scalar`` such that ``scalar(x, kwargs)`` calls ``self.call(self.loc + self.scale * x[:, :self.m], **(self.kwargs | kwargs)``.""" @property def call(self) -> Callable[Np.Matrix, float]: return self._call @property def loc(self) -> Np.Vector: return self._loc @property def scale(self) -> Np.Vector: return self._scale @property def m(self) -> int: return self._m @property def kwargs(self) -> dict[str, Np.Array]: return self._kwargs def __call__(self, x: Np.Matrix, **kwargs: Np.Matrix) -> Np.Matrix: return np.reshape(self._call(self._loc + self._scale * x[:, :self._m], **(self._kwargs | kwargs)), (x.shape[0], 1)) def __init__(self, call: Callable[Np.Matrix, float], loc: Np.Vector, scale: Np.Vector, m: int, **kwargs: Np.Array): """ A scalar function, which calls ``call(loc + scale * x[:, :m], **kwargs)``. Args: call: The SALib function called. loc: Input offset. scale: Input scale. m: The number of input dimensions. **kwargs: Function data applied to call. """ self._call = call self._loc = loc self._scale = scale self._m = m self._kwargs = kwargs
[docs] class Vector(dict): """ A vector functon, which is little more than a named dictionary of Scalar functions, such that ``vector(x, **kwargs)`` concatenates ``scalar(x, **kwargs)`` for each dictionary item ``key: Scalar``. """
[docs] @classmethod def concat(cls, name: str, vectors: Sequence[Vector]) -> Vector: """ Concatenate vectors. Args: name: The name of the returned ``Vector``. vectors: A sequence of ``Vector`` functions to concatenate. Returns: The concatenation of ``vectors``, named ``name``. """ result = cls(name) for vector in vectors: result.update({f'{vector.name}.{key}': scalar for key, scalar in vector.items()}) return result
@property def name(self) -> str: return self._name @property def meta(self) -> Dict: """ Meta data for providing to ``data.storage``.""" return {'name': self.name, 'call': {l: function for l, function in enumerate(self.keys())}}
[docs] def subVector(self, name: str, scalars: Sequence[str]) -> Vector: """ Create a subVector of ``self``. Args: name: The name of the ``subVector``. scalars: The keys of the items of ``self`` to be included in subVector. Returns: A new instance of ``Vector`` named ``name`` containing the ``Scalars`` keyed ``scalars``. Effectively the pseudo-slice ``self[scalars]``. """ return Vector(name, **{scalar: self[scalar] for scalar in scalars})
def __call__(self, x: Np.Matrix, **kwargs) -> Np.Matrix: return np.concatenate([scalar(x, **kwargs) for scalar in self.values()], axis = 1) def __init__(self, name: str, **kwargs: Scalar): """ Construct a vector function. Args: name: The name of this ``Vector``. **kwargs: The Dict of ``Scalar``s comprising this ``Vector``. """ super().__init__(**kwargs) self._name = name
#: The Ishigami function without data. _ISHIGAMI = {'call': SALib.test_functions.Ishigami.evaluate, 'loc': -np.pi, 'scale': 2 * np.pi} #: Modified Sobol G-function without data. _SOBOL_G = {'call': SALib.test_functions.Sobol_G.evaluate, 'loc': 0, 'scale': 1} #: Modified Oakley & O'Hagan (2004) function without data. _OAKLEY2004 = {'call': SALib.test_functions.oakley2004.evaluate, 'loc': -1, 'scale': 2}
[docs] def linspace(start: float, stop: float, shape: Sequence[int]) -> Np.Matrix: """ A multi-dimensional version of ``np.linspace``, distributing values throughout ``shape``. Args: start: Start value, which will be returned in ``linspace(...)[0,...,0]``. stop: Stop value, which will be returned in ``linspace(...)[-1,...,-1]``. shape: The ``linspace.shape`` to return. Returns: ``np.reshape(np.linspace(start, stop, int(np.prod(shape)), endpoint=True), newshape=shape)``. """ return np.reshape(np.linspace(start, stop, int(np.prod(shape)), endpoint = True), newshape = shape)
#: Three example Ishigami functions, requiring ``M >= 3``. ISHIGAMI = Vector(name = 'ishigami', standard = Scalar(**_ISHIGAMI, m = 3, A = 7.0, B = 0.1), balanced = Scalar(**_ISHIGAMI, m = 3, A = 20.0, B = 1.0), sin = Scalar(**_ISHIGAMI, m = 3, A = 0.0, B = 0.0), ) #: Three example modified Sobol G-functions, requiring ``M >= 5``. SOBOL_G = Vector(name = 'sobol_g', weak5_2 = Scalar(**_SOBOL_G, m = 5, a = np.array([3, 6, 9, 18, 27]), alpha = np.ones((5,)) * 2.0), strong5_2 = Scalar(**_SOBOL_G, m = 5, a = np.array([1 / 2, 1, 2, 4, 8]), alpha = np.ones((5,)) * 2.0), strong5_4 = Scalar(**_SOBOL_G, m = 5, a = np.array([1 / 2, 1, 2, 4, 8]), alpha = np.ones((5,)) * 4.0), ) #: Three example modified Oakley & O'Hagan (2004) functions, requiring ``M >= 5``. OAKLEY2004_5 = Vector(name = 'oakley2004', lin = Scalar(**_OAKLEY2004, m = 5, A = [linspace(start = 5.0, stop = 5.0 / 2, shape = [5, ]), ] + [ np.zeros([5])] * 2, M = np.zeros([5, 5])), quad = Scalar(**_OAKLEY2004, m = 5, A = [linspace(start = 5.0, stop = 5.0 / 2, shape = [5, ]), ] + [ np.zeros([5])] * 2, M = linspace(start = 5.0, stop = 1.0, shape = [5, 5])), rev = Scalar(**_OAKLEY2004, m = 5, A = [-linspace(start = 5.0, stop = 5.0 / 2, shape = [5, ]), ] + [ np.zeros([5])] * 2, M = linspace(start = 1.0, stop = 5.0, shape = [5, 5])), ) #: 3 example modified Oakley & O'Hagan (2004) functions, requiring ``M >= 7``. OAKLEY2004 = Vector(name = 'oakley2004', lin = Scalar(**_OAKLEY2004, m = 7, A = [linspace(start = 7.0, stop = 7.0 / 2, shape = [7, ]), ] + [ np.zeros([7])] * 2, M = np.zeros([7, 7])), quad = Scalar(**_OAKLEY2004, m = 7, A = [linspace(start = 7.0, stop = 7.0 / 2, shape = [7, ]), ] + [ np.zeros([7])] * 2, M = linspace(start = 7.0, stop = 1.0, shape = [7, 7])), rev = Scalar(**_OAKLEY2004, m = 7, A = [-linspace(start = 7.0, stop = 7.0 / 2, shape = [7, ]), ] + [ np.zeros([7])] * 2, M = linspace(start = 1.0, stop = 7.0, shape = [7, 7])), ) #: The concatenation of ISHIGAMI, SOBOL_G, OAKLEY2004. ALL = Vector.concat(name = 'all', vectors = (ISHIGAMI, SOBOL_G, OAKLEY2004))