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.ABC
Base 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 evaluateMap
instances.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
arg
through 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
arr
is 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]==1
the 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
A
be aMap
instance andB=A.shifter(y)
withy
some 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
other
is 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
other
is another map, then it returns the composition of the map withother
.If
other
is an array with compatible shape, then it calls the map onother
.If
other
is 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
other
is 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.Map
Base 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_cst
and the methodjacobianT
are 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 evaluateDifferentiableMap
instances.-
__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.Map
Stack 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=-1
uses 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.MapStack
Alias 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=-1
uses 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.MapStack
Alias 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=-1
uses 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.DifferentiableMap
Stack 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_cst
and the methodjacobianT
are 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=-1
uses 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.DiffMapStack
Alias 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=-1
uses 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.DiffMapStack
Alias 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=-1
uses all available cores.joblib_backend (str) –
Joblib backend (more details here).
-