@@ -0,0 +1,6 @@
Loading
 1 + """Callables with in- and output shape information supporting algebraic operations.""" 2 + 3 + from . import _algebra 4 + from ._algebra_fallbacks import ScaledFunction, SumFunction 5 + from ._function import Function, LambdaFunction 6 + from ._zero import Zero

@@ -7,7 +7,7 @@
Loading
 7 7 8 8 import numpy as np 9 9 10 - from probnum import _function, randvars, utils as _utils 10 + from probnum import functions, randvars, utils as _utils 11 11 from probnum.randprocs import kernels 12 12 from probnum.typing import DTypeLike, ShapeLike, ShapeType 13 13
@@ -56,7 +56,7 @@
Loading
 56 56  input_shape: ShapeLike, 57 57  output_shape: ShapeLike, 58 58  dtype: DTypeLike, 59 -  mean: Optional[_function.Function] = None, 59 +  mean: Optional[functions.Function] = None, 60 60  cov: Optional[kernels.Kernel] = None, 61 61  ): 62 62  self._input_shape = _utils.as_shape(input_shape)
@@ -75,7 +75,7 @@
Loading
 75 75 76 76  # Mean function 77 77  if mean is not None: 78 -  if not isinstance(mean, _function.Function): 78 +  if not isinstance(mean, functions.Function): 79 79  raise TypeError("The mean function must have type probnum.Function.") 80 80 81 81  if mean.input_shape != self._input_shape:
@@ -177,7 +177,7 @@
Loading
 177 177  raise NotImplementedError 178 178 179 179  @property 180 -  def mean(self) -> _function.Function: 180 +  def mean(self) -> functions.Function: 181 181  r"""Mean function :math:m(x) := \mathbb{E}[f(x)] of the random process.""" 182 182  if self._mean is None: 183 183  raise NotImplementedError

@@ -0,0 +1,69 @@
Loading
 1 + r"""Algebraic operations on :class:Function\ s.""" 2 + 3 + from ._algebra_fallbacks import SumFunction 4 + from ._function import Function 5 + from ._zero import Zero 6 + 7 + ############ 8 + # Function # 9 + ############ 10 + 11 + 12 + @Function.__add__.register # pylint: disable=no-member 13 + def _(self, other: Function) -> SumFunction: 14 +  return SumFunction(self, other) 15 + 16 + 17 + @Function.__add__.register # pylint: disable=no-member 18 + def _(self, other: SumFunction) -> SumFunction: 19 +  return SumFunction(self, *other.summands) 20 + 21 + 22 + @Function.__add__.register # pylint: disable=no-member 23 + def _(self, other: Zero) -> Function: # pylint: disable=unused-argument 24 +  return self 25 + 26 + 27 + @Function.__sub__.register # pylint: disable=no-member 28 + def _(self, other: Function) -> SumFunction: 29 +  return SumFunction(self, -other) 30 + 31 + 32 + @Function.__sub__.register # pylint: disable=no-member 33 + def _(self, other: Zero) -> Function: # pylint: disable=unused-argument 34 +  return self 35 + 36 + 37 + ############### 38 + # SumFunction # 39 + ############### 40 + 41 + 42 + @SumFunction.__add__.register # pylint: disable=no-member 43 + def _(self, other: Function) -> SumFunction: 44 +  return SumFunction(*self.summands, other) 45 + 46 + 47 + @SumFunction.__add__.register # pylint: disable=no-member 48 + def _(self, other: SumFunction) -> SumFunction: 49 +  return SumFunction(*self.summands, *other.summands) 50 + 51 + 52 + @SumFunction.__sub__.register # pylint: disable=no-member 53 + def _(self, other: Function) -> SumFunction: 54 +  return SumFunction(*self.summands, -other) 55 + 56 + 57 + ######## 58 + # Zero # 59 + ######## 60 + 61 + 62 + @Zero.__add__.register # pylint: disable=no-member 63 + def _(self, other: Function) -> Function: # pylint: disable=unused-argument 64 +  return other 65 + 66 + 67 + @Zero.__sub__.register # pylint: disable=no-member 68 + def _(self, other: Function) -> Function: # pylint: disable=unused-argument 69 +  return -other

@@ -6,7 +6,7 @@
Loading
 6 6 functions with stochastic output. 7 7 """ 8 8 9 - from . import kernels, mean_fns 9 + from . import kernels 10 10 from ._gaussian_process import GaussianProcess 11 11 from ._random_process import RandomProcess 12 12

@@ -5,7 +5,7 @@
Loading
 5 5 import numpy as np 6 6 import scipy.stats 7 7 8 - from probnum import _function, randvars, utils 8 + from probnum import functions, randvars, utils 9 9 from probnum.randprocs import _random_process, kernels 10 10 from probnum.randprocs.markov import _transition, continuous, discrete 11 11 from probnum.typing import ShapeLike
@@ -31,7 +31,7 @@
Loading
 31 31  input_shape=input_shape, 32 32  output_shape=output_shape, 33 33  dtype=np.dtype(np.float_), 34 -  mean=_function.LambdaFunction( 34 +  mean=functions.LambdaFunction( 35 35  lambda x: self.__call__(args=x).mean, 36 36  input_shape=input_shape, 37 37  output_shape=output_shape,

@@ -3,12 +3,13 @@
Loading
 3 3 from __future__ import annotations 4 4 5 5 import abc 6 + import functools 6 7 from typing import Callable 7 8 8 9 import numpy as np 9 10 10 - from . import utils 11 - from .typing import ArrayLike, ShapeLike, ShapeType 11 + from probnum import utils 12 + from probnum.typing import ArrayLike, ShapeLike, ShapeType 12 13 13 14 14 15 class Function(abc.ABC):
@@ -17,6 +18,8 @@
Loading
 17 18  This class represents a, uni- or multivariate, scalar- or tensor-valued, 18 19  mathematical function. Hence, the call method should not have any observable 19 20  side-effects. 21 +  Instances of this class can be added and multiplied by a scalar, which means that 22 +  they are elements of a vector space. 20 23 21 24  Parameters 22 25  ----------
@@ -29,7 +32,7 @@
Loading
 29 32  See Also 30 33  -------- 31 34  LambdaFunction : Define a :class:Function from an anonymous function. 32 -  ~probnum.randprocs.mean_fns.Zero : Zero mean function of a random process. 35 +  ~probnum.functions.Zero : Zero function. 33 36  """ 34 37 35 38  def __init__(self, input_shape: ShapeLike, output_shape: ShapeLike = ()) -> None:
@@ -112,6 +115,39 @@
Loading
 112 115  def _evaluate(self, x: np.ndarray) -> np.ndarray: 113 116  pass 114 117 118 +  def __neg__(self): 119 +  return -1.0 * self 120 + 121 +  @functools.singledispatchmethod 122 +  def __add__(self, other): 123 +  return NotImplemented 124 + 125 +  @functools.singledispatchmethod 126 +  def __sub__(self, other): 127 +  return NotImplemented 128 + 129 +  @functools.singledispatchmethod 130 +  def __mul__(self, other): 131 +  if np.ndim(other) == 0: 132 +  from ._algebra_fallbacks import ( # pylint: disable=import-outside-toplevel 133 +  ScaledFunction, 134 +  ) 135 + 136 +  return ScaledFunction(function=self, scalar=other) 137 + 138 +  return NotImplemented 139 + 140 +  @functools.singledispatchmethod 141 +  def __rmul__(self, other): 142 +  if np.ndim(other) == 0: 143 +  from ._algebra_fallbacks import ( # pylint: disable=import-outside-toplevel 144 +  ScaledFunction, 145 +  ) 146 + 147 +  return ScaledFunction(function=self, scalar=other) 148 + 149 +  return NotImplemented 150 + 115 151 116 152 class LambdaFunction(Function): 117 153  """Define a :class:Function from a given :class:callable.
@@ -131,7 +167,7 @@
Loading
 131 167  Examples 132 168  -------- 133 169  >>> import numpy as np 134 -  >>> from probnum import LambdaFunction 170 +  >>> from probnum.functions import LambdaFunction 135 171  >>> fn = LambdaFunction(fn=lambda x: 2 * x + 1, input_shape=(2,), output_shape=(2,)) 136 172  >>> fn(np.array([[1, 2], [4, 5]])) 137 173  array([[ 3, 5], 138 174 imilarity index 53% 139 175 ename from src/probnum/randprocs/mean_fns.py 140 176 ename to src/probnum/functions/_zero.py

@@ -1,10 +1,10 @@
Loading
 1 - """Mean functions of random processes.""" 1 + """The zero function.""" 2 2 3 - import numpy as np 3 + import functools 4 4 5 - from .. import _function 5 + import numpy as np 6 6 7 - __all__ = ["Zero"] 7 + from . import _function 8 8 9 9 10 10 class Zero(_function.Function):
@@ -15,3 +15,11 @@
Loading
 15 15  x, 16 16  shape=x.shape[: x.ndim - self._input_ndim] + self._output_shape, 17 17  ) 18 + 19 +  @functools.singledispatchmethod 20 +  def __add__(self, other): 21 +  return super().__add__(other) 22 + 23 +  @functools.singledispatchmethod 24 +  def __sub__(self, other): 25 +  return super().__sub__(other)

@@ -8,7 +8,7 @@
Loading
 8 8 from probnum.typing import ArrayLike 9 9 10 10 from . import _random_process, kernels 11 - from .. import _function 11 + from .. import functions 12 12 13 13 14 14 class GaussianProcess(_random_process.RandomProcess[ArrayLike, np.ndarray]):
@@ -35,7 +35,7 @@
Loading
 35 35  Define a Gaussian process with a zero mean function and RBF kernel. 36 36 37 37  >>> import numpy as np 38 -  >>> from probnum.randprocs.mean_fns import Zero 38 +  >>> from probnum.functions import Zero 39 39  >>> from probnum.randprocs.kernels import ExpQuad 40 40  >>> from probnum.randprocs import GaussianProcess 41 41  >>> mu = Zero(input_shape=()) # zero-mean function
@@ -58,10 +58,10 @@
Loading
 58 58 59 59  def __init__( 60 60  self, 61 -  mean: _function.Function, 61 +  mean: functions.Function, 62 62  cov: kernels.Kernel, 63 63  ): 64 -  if not isinstance(mean, _function.Function): 64 +  if not isinstance(mean, functions.Function): 65 65  raise TypeError("The mean function must have type probnum.Function.") 66 66 67 67  super().__init__(

@@ -26,6 +26,7 @@
Loading
 26 26 from . import ( 27 27  diffeq, 28 28  filtsmooth, 29 +  functions, 29 30  linalg, 30 31  linops, 31 32  problems,
@@ -34,23 +35,18 @@
Loading
 34 35  randvars, 35 36  utils, 36 37 ) 37 - from ._function import Function, LambdaFunction 38 38 from ._version import version as __version__ 39 39 from .randvars import asrandvar 40 40 41 41 # Public classes and functions. Order is reflected in documentation. 42 42 __all__ = [ 43 43  "asrandvar", 44 -  "Function", 45 -  "LambdaFunction", 46 44  "ProbabilisticNumericalMethod", 47 45  "StoppingCriterion", 48 46  "LambdaStoppingCriterion", 49 47 ] 50 48 51 49 # Set correct module paths. Corrects links and module paths in documentation. 52 - Function.__module__ = "probnum" 53 - LambdaFunction.__module__ = "probnum" 54 50 ProbabilisticNumericalMethod.__module__ = "probnum" 55 51 StoppingCriterion.__module__ = "probnum" 56 52 LambdaStoppingCriterion.__module__ = "probnum"

@@ -0,0 +1,141 @@
Loading
 1 + r"""Fallback implementation for algebraic operations on :class:Function\ s.""" 2 + 3 + from __future__ import annotations 4 + 5 + import functools 6 + import operator 7 + 8 + import numpy as np 9 + 10 + from probnum import utils 11 + from probnum.typing import ScalarLike, ScalarType 12 + 13 + from ._function import Function 14 + 15 + 16 + class SumFunction(Function): 17 +  r"""Pointwise sum of :class:Function\ s. 18 + 19 +  Given functions :math:f_1, \dotsc, f_n \colon \mathbb{R}^n \to \mathbb{R}^m, this 20 +  defines a new function 21 + 22 +  .. math:: 23 +  \sum_{i = 1}^n f_i \colon \mathbb{R}^n \to \mathbb{R}^m, 24 +  x \mapsto \sum_{i = 1}^n f_i(x). 25 + 26 +  Parameters 27 +  ---------- 28 +  *summands 29 +  The functions :math:f_1, \dotsc, f_n. 30 +  """ 31 + 32 +  def __init__(self, *summands: Function) -> None: 33 +  if not all(isinstance(summand, Function) for summand in summands): 34 +  raise TypeError( 35 +  "The functions to be added must be objects of type Function." 36 +  ) 37 + 38 +  if not all( 39 +  summand.input_shape == summands[0].input_shape for summand in summands 40 +  ): 41 +  raise ValueError( 42 +  "The functions to be added must all have the same input shape." 43 +  ) 44 + 45 +  if not all( 46 +  summand.output_shape == summands[0].output_shape for summand in summands 47 +  ): 48 +  raise ValueError( 49 +  "The functions to be added must all have the same output shape." 50 +  ) 51 + 52 +  self._summands = summands 53 + 54 +  super().__init__( 55 +  input_shape=summands[0].input_shape, 56 +  output_shape=summands[0].output_shape, 57 +  ) 58 + 59 +  @property 60 +  def summands(self) -> tuple[SumFunction, ...]: 61 +  r"""The functions :math:f_1, \dotsc, f_n to be added.""" 62 +  return self._summands 63 + 64 +  def _evaluate(self, x: np.ndarray) -> np.ndarray: 65 +  return functools.reduce( 66 +  operator.add, (summand(x) for summand in self._summands) 67 +  ) 68 + 69 +  @functools.singledispatchmethod 70 +  def __add__(self, other): 71 +  return super().__add__(other) 72 + 73 +  @functools.singledispatchmethod 74 +  def __sub__(self, other): 75 +  return super().__sub__(other) 76 + 77 + 78 + class ScaledFunction(Function): 79 +  r"""Function multiplied pointwise with a scalar. 80 + 81 +  Given a function :math:f \colon \mathbb{R}^n \to \mathbb{R}^m and a scalar 82 +  :math:\alpha \in \mathbb{R}, this defines a new function 83 + 84 +  .. math:: 85 +  \alpha f \colon \mathbb{R}^n \to \mathbb{R}^m, 86 +  x \mapsto (\alpha f)(x) = \alpha f(x). 87 + 88 +  Parameters 89 +  ---------- 90 +  function 91 +  The function :math:f. 92 +  scalar 93 +  The scalar :math:\alpha. 94 +  """ 95 + 96 +  def __init__(self, function: Function, scalar: ScalarLike): 97 +  if not isinstance(function, Function): 98 +  raise TypeError( 99 +  "The function to be scaled must be an object of type Function." 100 +  ) 101 + 102 +  self._function = function 103 +  self._scalar = utils.as_numpy_scalar(scalar) 104 + 105 +  super().__init__( 106 +  input_shape=self._function.input_shape, 107 +  output_shape=self._function.output_shape, 108 +  ) 109 + 110 +  @property 111 +  def function(self) -> Function: 112 +  r"""The function :math:f.""" 113 +  return self._function 114 + 115 +  @property 116 +  def scalar(self) -> ScalarType: 117 +  r"""The scalar :math:\alpha.""" 118 +  return self._scalar 119 + 120 +  def _evaluate(self, x: np.ndarray) -> np.ndarray: 121 +  return self._scalar * self._function(x) 122 + 123 +  @functools.singledispatchmethod 124 +  def __mul__(self, other): 125 +  if np.ndim(other) == 0: 126 +  return ScaledFunction( 127 +  function=self._function, 128 +  scalar=self._scalar * np.asarray(other), 129 +  ) 130 + 131 +  return super().__mul__(other) 132 + 133 +  @functools.singledispatchmethod 134 +  def __rmul__(self, other): 135 +  if np.ndim(other) == 0: 136 +  return ScaledFunction( 137 +  function=self._function, 138 +  scalar=np.asarray(other) * self._scalar, 139 +  ) 140 + 141 +  return super().__rmul__(other) 0 142 imilarity index 76% 1 143 ename from src/probnum/_function.py 2 144 ename to src/probnum/functions/_function.py
Files Coverage
src/probnum 90.28%
Project Totals (197 files) 90.28%
 1 coverage:  2  precision: 2  3  status:  4  project:  5  default:  6  target: auto  7  threshold: 1%  8  patch:  9  default:  10  target: 90%  11  threshold: 1%  12 13 comment:  14  layout: "reach, diff, files"  15  behavior: default  16  require_changes: true 
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading