"""
Field function abstractions for parameter field evaluation.
This module provides ``FieldFunction``, an abstract base class for functions
that are evaluated over parameter field lattice coordinates, along with
concrete implementations (Identity, Sigmoid, Gaussian, Polynomial) and
a composition utility.
"""
from abc import ABC, abstractmethod
from typing import Any, Dict, Union, Tuple
import numpy as np
[docs]
class FieldFunction(ABC):
"""
Abstract base class for field functions with variable input/output dimensionality.
Subclasses implement ``_raw_function``; the ``__call__`` wrapper handles
dimensionality validation, reshaping, and output-range clipping.
Parameters
----------
input_dim : int, optional
Expected number of input dimensions (default is 1).
output_dim : int, optional
Number of output dimensions (default is 1).
output_range : tuple of float, optional
``(min, max)`` clipping range for output values
(default is ``(-inf, inf)``).
parameters : dict, optional
Arbitrary named parameters accessible as ``self.parameters``.
"""
[docs]
def __init__(self, input_dim: int = 1, output_dim: int = 1,
output_range: Tuple[float, float] = (float('-inf'), float('inf')),
parameters: Dict[str, Any] = None):
self.input_dim = input_dim
self.output_dim = output_dim
self.output_range = output_range
self.parameters = parameters or {}
@abstractmethod
def _raw_function(self, x: np.ndarray) -> np.ndarray:
"""
Core function logic to be implemented by subclasses.
Parameters
----------
x : numpy.ndarray
Input array with last dimension matching ``input_dim``.
Returns
-------
numpy.ndarray
Raw (unclipped) output values.
"""
pass
[docs]
def __call__(self, x: Union[float, list, np.ndarray]) -> Union[float, np.ndarray]:
"""
Evaluate the function on input *x*.
Validates input dimensionality, applies the raw function, reshapes
multi-dimensional output, and clips to ``output_range``.
Parameters
----------
x : float, list, or numpy.ndarray
Input value(s).
Returns
-------
float or numpy.ndarray
Clipped function output.
Raises
------
ValueError
If the last dimension of *x* does not match ``input_dim``.
"""
x = np.atleast_1d(x)
if x.shape[-1] != self.input_dim:
raise ValueError(f"Input dimensionality {x.shape[-1]} does not match expected {self.input_dim}")
result = self._raw_function(x)
if self.output_dim > 1:
result = result.reshape(-1, self.output_dim)
return np.clip(result, self.output_range[0], self.output_range[1])
[docs]
@staticmethod
def compose(*functions: 'FieldFunction') -> 'FieldFunction':
"""
Compose multiple FieldFunctions into a single callable.
Functions are applied right-to-left: ``compose(f, g)(x)`` computes
``f(g(x))``. Adjacent output/input dimensions must match.
Parameters
----------
*functions : FieldFunction
Two or more FieldFunction instances to compose.
Returns
-------
FieldFunction
A composed function with the input dimension of the last function
and the output dimension/range of the first.
Raises
------
ValueError
If fewer than two functions are given or dimensions are incompatible.
"""
if len(functions) < 2:
raise ValueError("At least two functions are required for composition.")
for i in range(len(functions) - 1):
if functions[i].input_dim != functions[i+1].output_dim:
raise ValueError(f"Output dimension of function {i+1} must match input dimension of function {i}.")
class ComposedFunction(FieldFunction):
def __init__(self, *funcs):
super().__init__(funcs[-1].input_dim, funcs[0].output_dim, funcs[0].output_range)
self.functions = funcs
def _raw_function(self, x):
for func in reversed(self.functions):
x = func._raw_function(x)
return x
return ComposedFunction(*functions)
[docs]
def set_parameters(self, **kwargs):
"""
Update the function's named parameters.
Parameters
----------
**kwargs
Parameter names and values to set or update.
"""
self.parameters.update(kwargs)
[docs]
class Identity(FieldFunction):
"""Identity function that returns its input unchanged."""
def _raw_function(self, x: np.ndarray) -> np.ndarray:
return x
[docs]
class Sigmoid(FieldFunction):
"""
Sigmoid (logistic) function mapping inputs to the (0, 1) range.
Parameters
----------
input_dim : int, optional
Number of input dimensions (default is 1).
"""
[docs]
def __init__(self, input_dim: int = 1):
super().__init__(input_dim, 1, (0, 1))
def _raw_function(self, x: np.ndarray) -> np.ndarray:
return 1 / (1 + np.exp(-x))
[docs]
class Gaussian(FieldFunction):
"""
Gaussian (bell curve) function.
Parameters
----------
input_dim : int, optional
Number of input dimensions (default is 1).
mu : float, optional
Mean of the Gaussian (default is 0).
sigma : float, optional
Standard deviation (default is 1).
"""
[docs]
def __init__(self, input_dim: int = 1, mu: float = 0, sigma: float = 1):
super().__init__(input_dim, 1, (0, 1), {'mu': mu, 'sigma': sigma})
def _raw_function(self, x: np.ndarray) -> np.ndarray:
return np.exp(-((x - self.parameters['mu']) ** 2) / (2 * self.parameters['sigma'] ** 2))
[docs]
class Polynomial(FieldFunction):
"""
Polynomial function defined by a list of coefficients.
Evaluates ``c[0] + c[1]*x + c[2]*x^2 + ...``
Parameters
----------
coefficients : list of float
Polynomial coefficients in ascending order of degree.
input_dim : int, optional
Number of input dimensions (default is 1).
"""
[docs]
def __init__(self, coefficients: list, input_dim: int = 1):
super().__init__(input_dim, 1, parameters={'coefficients': coefficients})
def _raw_function(self, x: np.ndarray) -> np.ndarray:
return sum(c * x**i for i, c in enumerate(self.parameters['coefficients']))