Multidimensional Maps¶
Module: pycsou.core.map
Abstract classes for multidimensional (potentially nonlinear) maps.
|
Base class for multidimensional maps. |
|
Base class for multidimensional differentiable maps. |
|
Stack maps together. |
|
Stack differentiable maps together. |
-
class
Map(shape: Tuple[int, int], is_linear: bool = False, is_differentiable: bool = False)[source]¶ Bases:
abc.ABCBase class for multidimensional maps.
Any instance/subclass of this class must at least implement the abstract method
__call__. This class supports the following arithmetic operators+,-,*,@,**and/, implemented with the class methods__add__/__radd__,__sub__/__neg__,__mul__/__rmul__,__matmul__,__pow__,__truediv__. Such arithmetic operators can be used to add, substract, scale, compose, exponentiate or evaluateMapinstances.Examples
Consider four maps: two nonlinear functionals \(f_1:\mathbb{R}^{10}\to \mathbb{R}\), \(f_2:\mathbb{R}^{10}\to \mathbb{R}\) and two linear operators \(L_1:\mathbb{R}^{10}\to \mathbb{R}^{10}\), \(L_2:\mathbb{R}^{10}\to \mathbb{R}^{10}\).
>>> print(f1.shape, f2.shape, L1.shape, L2.shape) (1, 10) (1, 10) (10, 10) (10, 10)
We can combine linearly/compose the maps with consistent domains/ranges:
>>> f3 = f1 / 3 + np.pi * f2 >>> np.allclose(f3(x), f1(x) / 3 + np.pi * f2(x)) True >>> L3 = L1 * 3 - (L2 ** 2) / 6 >>> np.allclose(L3(x), L1(x) * 3 - (L2(L2(x))) / 6) True >>> f4 = f3 * L3 >>> np.allclose(f4(x), f3(L3(x))) True
Note that multiplying a map with an array is the same as evaluating the map at the array.
>>> np.allclose(f3 * x, f3(x)) True
The multiplication operator
@can also be used in place of*, in compliance with Numpy’s interface:>>> np.allclose(f3 * x, f3 @ x) True >>> np.allclose((f1 * L1)(x), (f1 @ L1)(x)) True
Finally, maps can be shifted via the method
shifter:>>> f5=f4.shifter(shift=2 * x) >>> np.allclose(f5(x), f4(x + 2 * x)) True
-
abstract
__call__(arg: Union[numbers.Number, numpy.ndarray]) → Union[numbers.Number, numpy.ndarray][source]¶ Call self as a function.
- Parameters
arg (Union[Number, np.ndarray]) – Argument of the map.
- Returns
Value of
argthrough the map.- Return type
Union[Number, np.ndarray]
-
apply_along_axis(arr: numpy.ndarray, axis: int = 0) → numpy.ndarray[source]¶ Apply the map to 1-D slices along the given axis.
- Parameters
arr (np.ndarray) – Input array.
axis (int) – Axis along which
arris sliced.
- Returns
The output array. The shape of the latter is identical to the shape of
arr, except along the specified axis dimension. This axis is removed, and replaced with new dimensions equal toself.shape[0]. Ifself.shape[0]==1the output array will have one fewer dimensions thanarr.- Return type
np.ndarray
- Raises
ValueError – If
arr.shape[axis] != self.shape[1].
Examples
>>> from pycsou.linop import DownSampling >>> from pycsou.func import SquaredL2Norm >>> D=DownSampling(size=20, downsampling_factor=2) >>> arr=np.arange(200).reshape(20,2,5) >>> out = D.apply_along_axis(arr, axis=0) >>> out.shape (10, 2, 5) >>> ff = SquaredL2Norm(dim=2) >>> out = ff.apply_along_axis(arr, axis=1) >>> out.shape (20, 5)
-
shifter(shift: Union[numbers.Number, numpy.ndarray]) → pycsou.core.map.MapShifted[source]¶ Returns a shifted version of the map.
- Parameters
shift (Union[Number, np.ndarray]) – Shift vector.
- Returns
Shifted map.
- Return type
MapShifted
Notes
Let
Abe aMapinstance andB=A.shifter(y)withysome vector in the domain ofA. Then we have:B(x)=A(x+y).
-
__add__(other: pycsou.core.map.Map) → pycsou.core.map.MapSum[source]¶ Add a map with another map instance.
- Parameters
other (Union['Map', np.ndarray]) – The other map or array to be added.
- Returns
Sum of the map with another map.
- Return type
MapSum- Raises
NotImplementedError – If
otheris not a map.
-
__mul__(other: Union[numbers.Number, Map, numpy.ndarray]) → Union[pycsou.core.map.MapComp, numpy.ndarray][source]¶ Multiply a map with another map, a scalar, or an array.
The behaviour of this method depends on the type of
other:If
otheris another map, then it returns the composition of the map withother.If
otheris an array with compatible shape, then it calls the map onother.If
otheris a scalar, then it multiplies the map with this scalar.
- Parameters
other (Union[Number, 'Map', np.ndarray]) – Scalar, map or array with which to multiply.
- Returns
Composition of the map with another map, or product with a scalar or array.
- Return type
MapComp, np.ndarray- Raises
NotImplementedError – If
otheris not a scalar, a map or an array.
See also
__matmul__(),__rmul__()
-
__matmul__(other: pycsou.core.map.Map) → pycsou.core.map.MapComp[source]¶ Alias for
__mul__()offered to comply with Numpy’s interface.
-
__sub__(other: Union[Map, numpy.ndarray]) → Union[MapSum, MapBias][source]¶ Substracts a map, scalar, or array to another map. Alias for
self.__add__(other.__neg__()).
-
abstract
-
class
DifferentiableMap(shape: Tuple[int, int], is_linear: bool = False, lipschitz_cst: float = inf, diff_lipschitz_cst: float = inf)[source]¶ Bases:
pycsou.core.map.MapBase class for multidimensional differentiable maps.
Any instance/subclass of this class must at least implement the abstract methods
__call__andjacobianT. The attributeslipschitz_cst,diff_lipschitz_cstand the methodjacobianTare automatically updated using standard differentiation rules when scaling, composing , summing or shifting differentiable maps.Examples
Consider three differentiable maps: a nonlinear differentiable functional \(f:\mathbb{R}^{10}\to \mathbb{R}\), and two linear operators \(L_1:\mathbb{R}^{10}\to \mathbb{R}^{10}\), \(L_2:\mathbb{R}^{10}\to \mathbb{R}^{10}\).
>>> print(f.lipschitz_cst, f.diff_lipschitz_cst) inf 2 >>> print(np.round(L1.lipschitz_cst,1), np.round(L1.diff_lipschitz_cst,1)) 2.0 2.0 >>> print(np.round(L2.lipschitz_cst,1), np.round(L2.diff_lipschitz_cst,1)) 1.5 1.5 >>> L3 = 2 * L1 + (L2 ** 2) / 3 >>> np.allclose(L3.lipschitz_cst, 2 * L1.lipschitz_cst + (L2.lipschitz_cst ** 2)/3) True >>> map_ = f * L1 >>> np.allclose(map_.lipschitz_cst, f.lipschitz_cst * L1.lipschitz_cst) True >>> np.allclose(map_.jacobianT(x), L1.jacobianT(x) * f.jacobianT(L1(x))) True
Notes
This class supports the following arithmetic operators
+,-,*,@,**and/, implemented with the class methods__add__/__radd__,__sub__/__neg__,__mul__/__rmul__,__matmul__,__pow__,__truediv__. Such arithmetic operators can be used to add, substract, scale, compose, exponentiate or evaluateDifferentiableMapinstances.-
__init__(shape: Tuple[int, int], is_linear: bool = False, lipschitz_cst: float = inf, diff_lipschitz_cst: float = inf)[source]¶ - Parameters
is_linear (bool) – Whether the differentiable map is linear or not.
lipschitz_cst (float) – Lispchitz constant of the differentiable map if it exists/is known. Default to \(+\infty\).
diff_lipschitz_cst (float) – Lispchitz constant of the derivative of the differentiable map if it exists/is known. Default to \(+\infty\).
-
abstract
jacobianT(arg: Union[numbers.Number, numpy.ndarray]) → LinearOperator[source]¶ Transpose of the Jacobian matrix of the differentiable map evaluated at
arg.- Parameters
arg (Union[Number, np.ndarray]) – Point at which the transposed Jacobian matrix is evaluated.
- Returns
Linear operator associated to the transposed Jacobian matrix.
- Return type
-
gradient(arg: Union[numbers.Number, numpy.ndarray]) → LinearOperator[source]¶ Alias for
self.jacobianT.
-
compute_lipschitz_cst()[source]¶ User-implemented method to compute the Lipschitz constant of the map.
-
-
class
MapStack(*maps, axis: int, n_jobs: int = 1, joblib_backend: str = 'loky')[source]¶ Bases:
pycsou.core.map.MapStack maps together.
This class constructs a map by stacking multiple maps together, either vertically (
axis=0) or horizontally (axis=1):Vertical stacking: Consider a collection \(\{L_i:\mathbb{R}^{N}\to \mathbb{R}^{M_i}, i=1,\ldots, k\}\) of maps. Their vertical stacking is defined as the operator
\[\begin{split}V:\begin{cases}\mathbb{R}^{N}\to \mathbb{R}^{M_1}\times \cdots \times\mathbb{R}^{M_k}\\ \mathbf{x}\mapsto (L_1\mathbf{x},\ldots, L_k\mathbf{x}). \end{cases}\end{split}\]Horizontal stacking: Consider a collection \(\{L_i:\mathbb{R}^{N_i}\to \mathbb{R}^{M}, i=1,\ldots, k\}\) of maps. Their horizontal stacking is defined as the operator
\[\begin{split}H:\begin{cases}\mathbb{R}^{N_1}\times \cdots \times\mathbb{R}^{N_k}\to \mathbb{R}^{M}\\ (\mathbf{x}_1,\ldots, \mathbf{x}_k)\mapsto \sum_{i=1}^k L_i \mathbf{x}_i. \end{cases}\end{split}\]
Examples
Consider three maps \(K_1:\mathbb{R}^{10}\to \mathbb{R}\), \(K_2:\mathbb{R}^{20}\to \mathbb{R}\) and \(K_3:\mathbb{R}^{10}\to \mathbb{R}^{10}\)
>>> V = MapStack(K1, K3, axis=0) >>> V.shape (11, 10) >>> np.allclose(V(x), np.concatenate((K1(x).flatten(), K3(x).flatten()))) True >>> parV = MapStack(K1, K3, axis=0, n_jobs=-1) >>> np.allclose(V(x), parV(x)) True >>> H = MapStack(K1,K2, axis=1) >>> H.shape (1, 30) >>> np.allclose(H(np.concatenate((x,y))), K1(x) + K2(y)) True >>> parH = MapStack(K1,K2, axis=1, n_jobs=-1) >>> np.allclose(H(np.concatenate((x,y))), parH(np.concatenate((x,y)))) True
See also
-
__init__(*maps, axis: int, n_jobs: int = 1, joblib_backend: str = 'loky')[source]¶ - Parameters
maps (Map) – List of maps to stack.
axis – Stacking direction: 0 for vertical and 1 for horizontal stacking.
n_jobs (int) – Number of cores to be used for parallel evaluation of the map stack. If
n_jobs==1, the map stack is evaluated sequentially, otherwise it is evaluated in parallel. Settingn_jobs=-1uses all available cores.joblib_backend (str) – Joblib backend (more details here).
-
class
MapVStack(*maps, n_jobs: int = 1, joblib_backend: str = 'loky')[source]¶ Bases:
pycsou.core.map.MapStackAlias for vertical stacking, equivalent to
MapStack(*maps, axis=0).Examples
>>> V1 = MapStack(K1, K3, axis=0) >>> V2 = MapVStack(K1, K3) >>> np.allclose(V1(x), V2(x)) True
-
__init__(*maps, n_jobs: int = 1, joblib_backend: str = 'loky')[source]¶ - Parameters
maps (Map) – List of maps to stack.
n_jobs (int) – Number of cores to be used for parallel evaluation of the map stack. If
n_jobs==1, the map stack is evaluated sequentially, otherwise it is evaluated in parallel. Settingn_jobs=-1uses all available cores.joblib_backend (str) –
Joblib backend (more details here).
-
-
class
MapHStack(*maps, n_jobs: int = 1, joblib_backend: str = 'loky')[source]¶ Bases:
pycsou.core.map.MapStackAlias for horizontal stacking, equivalent to
MapStack(*maps, axis=1).Examples
>>> V1 = MapStack(K1, K2, axis=1) >>> V2 = MapHStack(K1, K2) >>> np.allclose(V1(np.arange(V1.shape[1])), V2(np.arange(V1.shape[1]))) True
-
__init__(*maps, n_jobs: int = 1, joblib_backend: str = 'loky')[source]¶ - Parameters
maps (Map) – List of maps to stack.
n_jobs (int) – Number of cores to be used for parallel evaluation of the map stack. If
n_jobs==1, the map stack is evaluated sequentially, otherwise it is evaluated in parallel. Settingn_jobs=-1uses all available cores.joblib_backend (str) –
Joblib backend (more details here).
-
-
class
DiffMapStack(*diffmaps, axis: int, n_jobs: int = 1, joblib_backend: str = 'loky')[source]¶ Bases:
pycsou.core.map.MapStack,pycsou.core.map.DifferentiableMapStack differentiable maps together.
This class constructs a differentiable map by stacking multiple maps together, either vertically (
axis=0) or horizontally (axis=1). The attributeslipschitz_cst,diff_lipschitz_cstand the methodjacobianTare inferred from those of the stacked maps.Vertical stacking: Consider a collection \(\{L_i:\mathbb{R}^{N}\to \mathbb{R}^{M_i}, i=1,\ldots, k\}\) of maps, with Lipschitz constants \(\{\beta_i\in\mathbb{R}_+\cup\{+\infty\}, i=1,\ldots, k\}\), Jacobian matrices \(\{\mathbf{J}_i(\mathbf{x})\in\mathbb{R}^{M_i\times N}, i=1,\ldots, k\}\) for some \(\mathbf{x}\in\mathbb{R}^N\). Then the vertically stacked operator
\[\begin{split}V:\begin{cases}\mathbb{R}^{N}\to \mathbb{R}^{M_1}\times \cdots \times\mathbb{R}^{M_k}\\ \mathbf{x}\mapsto (L_1\mathbf{x},\ldots, L_k\mathbf{x}). \end{cases}\end{split}\]has a Lipschitz constant bounded by \(\sqrt{\sum_{i=1}^k \beta_i^2}\). Moreover the Jacobian matrix of \(V\) is obtained by stacking the individual Jacobian matrices vertically:
\[\begin{split}\mathbf{J}(\mathbf{x})=\left[\begin{array}{c}\mathbf{J}_1(\mathbf{x})\\\vdots\\ \mathbf{J}_k(\mathbf{x}) \end{array}\right]\in\mathbb{R}^{(\sum_{i=1}^k M_i)\times N}.\end{split}\]Horizontal stacking: Consider a collection \(\{L_i:\mathbb{R}^{N_i}\to \mathbb{R}^{M}, i=1,\ldots, k\}\) of maps, with Lipschitz constants \(\{\beta_i\in\mathbb{R}_+\cup\{+\infty\}, i=1,\ldots, k\}\), Jacobian matrices \(\{\mathbf{J}_i(\mathbf{x}_i)\in\mathbb{R}^{M\times N_i}, i=1,\ldots, k\}\) for some \(\mathbf{x}_i\in\mathbb{R}^{N_i}\). Then the horizontally stacked operator
\[\begin{split}H:\begin{cases}\mathbb{R}^{N_1}\times \cdots \times\mathbb{R}^{N_k}\to \mathbb{R}^{M}\\ (\mathbf{x}_1,\ldots, \mathbf{x}_k)\mapsto \sum_{i=1}^k L_i \mathbf{x}_i. \end{cases}\end{split}\]has a Lipschitz constant bounded by \(\max_{i=1}^k \beta_i\). Moreover the Jacobian matrix of \(H\) is obtained by stacking the individual Jacobian matrices horizontally:
\[\mathbf{J}(\mathbf{x}_1,\ldots,\mathbf{x}_k)=\left[\begin{array}{c}\mathbf{J}_1(\mathbf{x}_1)&\cdots& \mathbf{J}_k(\mathbf{x}_k) \end{array}\right]\in\mathbb{R}^{M\times (\sum_{i=1}^k N_i)}.\]
Examples
>>> V = DiffMapStack(K1, K3, axis=0) >>> np.allclose(V.lipschitz_cst, np.sqrt(K1.lipschitz_cst ** 2 + K3.lipschitz_cst ** 2)) True >>> parV = DiffMapStack(K1, K3, axis=0, n_jobs=-1) >>> np.allclose(V.jacobianT(x) * np.ones(shape=(V.jacobianT(x).shape[1])), parV.jacobianT(x) * np.ones(shape=(V.jacobianT(x).shape[1]))) True
See also
-
__init__(*diffmaps, axis: int, n_jobs: int = 1, joblib_backend: str = 'loky')[source]¶ - Parameters
diffmaps (DifferentiableMap) – List of differentiable maps to be stacked
axis (int) – Stacking direction: 0 for vertical and 1 for horizontal stacking.
n_jobs (int) – Number of cores to be used for parallel evaluation of the map stack and its Jacobian matrix. If
n_jobs==1, the map stack and its Jacobian matrix are evaluated sequentially, otherwise they are evaluated in parallel. Settingn_jobs=-1uses all available cores.joblib_backend (str) –
Joblib backend (more details here).
-
jacobianT(arg: Union[numbers.Number, numpy.ndarray]) → Union[LinOpHStack, LinOpVStack][source]¶ Transpose of the Jacobian matrix of the differentiable map evaluated at
arg.- Parameters
arg (Union[Number, np.ndarray]) – Point at which the transposed Jacobian matrix is evaluated.
- Returns
Linear operator associated to the transposed Jacobian matrix.
- Return type
-
class
DiffMapVStack(*diffmaps, n_jobs: int = 1, joblib_backend: str = 'loky')[source]¶ Bases:
pycsou.core.map.DiffMapStackAlias for vertical stacking of differentiable maps, equivalent to
DiffMapStack(*maps, axis=0).-
__init__(*diffmaps, n_jobs: int = 1, joblib_backend: str = 'loky')[source]¶ - Parameters
diffmaps (DifferentiableMap) – List of differentiable maps to be stacked
n_jobs (int) – Number of cores to be used for parallel evaluation of the map stack and its Jacobian matrix. If
n_jobs==1, the map stack and its Jacobian matrix are evaluated sequentially, otherwise they are evaluated in parallel. Settingn_jobs=-1uses all available cores.joblib_backend (str) –
Joblib backend (more details here).
-
-
class
DiffMapHStack(*diffmaps, n_jobs: int = 1, joblib_backend: str = 'loky')[source]¶ Bases:
pycsou.core.map.DiffMapStackAlias for horizontal stacking of differentiable maps, equivalent to
DiffMapStack(*maps, axis=1).-
__init__(*diffmaps, n_jobs: int = 1, joblib_backend: str = 'loky')[source]¶ - Parameters
diffmaps (DifferentiableMap) – List of differentiable maps to be stacked.
n_jobs (int) – Number of cores to be used for parallel evaluation of the map stack and its Jacobian matrix. If
n_jobs==1, the map stack and its Jacobian matrix are evaluated sequentially, otherwise they are evaluated in parallel. Settingn_jobs=-1uses all available cores.joblib_backend (str) –
Joblib backend (more details here).
-