Source code for romcomma.user.functions

#  BSD 3-Clause License.
# 
#  Copyright (c) 2019-2024 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 romcomma.base.definitions 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[..., float]: return self._call @property def loc(self) -> NP.VectorLike: return self._loc @property def scale(self) -> NP.VectorLike: return self._scale @property def m(self) -> int: return self._m @property def kwargs(self) -> NP.VectorLike: return self._kwargs def __call__(self, X: NP.Matrix, **kwargs) -> NP.Matrix: return np.reshape(self._call(self._loc + self._scale * X[:, :self._m], **(self._kwargs | kwargs)), (X.shape[0], 1))
[docs] def __init__(self, call: Callable[NP.Matrix, float], loc: NP.VectorLike, scale: NP.VectorLike, m: int, **kwargs): """ A scalar function, which calls ``call(loc + scale * X[:, :m], **kwargs)``. Args: call: This 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)
[docs] 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
_ISHIGAMI = {'call': SALib.test_functions.Ishigami.evaluate, 'loc': -np.pi, 'scale': 2 * np.pi} #: The Ishigami function without data. _SOBOL_G = {'call': SALib.test_functions.Sobol_G.evaluate, 'loc': 0, 'scale': 1} #: Modified Sobol G-function without data. _OAKLEY2004 = {'call': SALib.test_functions.oakley2004.evaluate, 'loc': -1, 'scale': 2} #: Modified Oakley & O'Hagan (2004) function without data.
[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)
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), ) #: 3 example Ishigami functions, requiring ``M >= 3``. 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), ) #: 3 example modified Sobol G-functions, requiring ``M >= 5``. OAKLEY2004_5 = Vector(name='oakley2004', lin7=Scalar(**_OAKLEY2004, m=5, A=[linspace(start=5.0, stop=5.0 / 2, shape=[5, ]), ] + [np.zeros([5])] * 2, M=np.zeros([5, 5])), quad7=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])), balanced_quad7=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 >= 5``. OAKLEY2004 = Vector(name='oakley2004', lin7=Scalar(**_OAKLEY2004, m=7, A=[linspace(start=7.0, stop=7.0 / 2, shape=[7, ]), ] + [np.zeros([7])] * 2, M=np.zeros([7, 7])), quad7=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])), balanced_quad7=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])), ) #: 3 example modified Oakley & O'Hagan (2004) functions, requiring ``M >= 7``. ALL = Vector.concat(name='all', vectors=(ISHIGAMI, SOBOL_G, OAKLEY2004)) #: The concatenation of ISHIGAMI, SOBOL_G, OAKLEY2004.