Skip to content

Commit e4d914a

Browse files
Basic operators from functions to classes (#496)
* Changed Operator functions to classes * Updated directionalderivative.py * Updated basicoperators * Added name * Updated docstrings * Updated docstrings * Made methods protected * Made the calc static * Made the changes * Updated Gradient * Updated Gradient * minor: quick-fix * minor: quick-fix * minor: updated second_ddop
1 parent d5c6252 commit e4d914a

File tree

8 files changed

+201
-191
lines changed

8 files changed

+201
-191
lines changed

pylops/basicoperators/block.py

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,45 @@
11
__all__ = ["Block"]
22

3+
import multiprocessing as mp
4+
35
from typing import Iterable, Optional
46

57
from pylops import LinearOperator
68
from pylops.basicoperators import HStack, VStack
7-
from pylops.utils.typing import DTypeLike
9+
from pylops.utils.typing import DTypeLike, NDArray
810

911

10-
def _Block(
11-
ops: Iterable[Iterable[LinearOperator]],
12-
dtype: Optional[DTypeLike] = None,
13-
_HStack=HStack,
14-
_VStack=VStack,
15-
args_HStack: Optional[dict] = None,
16-
args_VStack: Optional[dict] = None,
17-
) -> LinearOperator:
12+
class _Block(LinearOperator):
1813
"""Block operator.
1914
2015
Used to be able to provide operators from different libraries to
2116
Block.
2217
"""
23-
if args_HStack is None:
24-
args_HStack = {}
25-
if args_VStack is None:
26-
args_VStack = {}
27-
hblocks = [_HStack(hblock, dtype=dtype, **args_HStack) for hblock in ops]
28-
return _VStack(hblocks, dtype=dtype, **args_VStack)
29-
30-
31-
def Block(
32-
ops: Iterable[Iterable[LinearOperator]],
33-
nproc: int = 1,
34-
dtype: Optional[DTypeLike] = None,
35-
) -> LinearOperator:
18+
def __init__(self, ops: Iterable[Iterable[LinearOperator]],
19+
dtype: Optional[DTypeLike] = None,
20+
_HStack=HStack,
21+
_VStack=VStack,
22+
args_HStack: Optional[dict] = None,
23+
args_VStack: Optional[dict] = None, name: str = 'B'):
24+
if args_HStack is None:
25+
self.args_HStack = {}
26+
else:
27+
self.args_HStack = args_HStack
28+
if args_VStack is None:
29+
self.args_VStack = {}
30+
else:
31+
self.args_VStack = args_VStack
32+
hblocks = [_HStack(hblock, dtype=dtype, **self.args_HStack) for hblock in ops]
33+
super().__init__(Op=_VStack(ops=hblocks, dtype=dtype, **self.args_VStack), name=name)
34+
35+
def _matvec(self, x: NDArray) -> NDArray:
36+
return super()._matvec(x)
37+
38+
def _rmatvec(self, x: NDArray) -> NDArray:
39+
return super()._rmatvec(x)
40+
41+
42+
class Block(_Block):
3643
r"""Block operator.
3744
3845
Create a block operator from N lists of M linear operators each.
@@ -116,4 +123,9 @@ def Block(
116123
\end{bmatrix}
117124
118125
"""
119-
return _Block(ops, dtype=dtype, args_VStack={"nproc": nproc})
126+
def __init__(self, ops: Iterable[Iterable[LinearOperator]],
127+
nproc: int = 1,
128+
dtype: Optional[DTypeLike] = None):
129+
if nproc > 1:
130+
self.pool = mp.Pool(processes=nproc)
131+
super().__init__(ops=ops, dtype=dtype, args_VStack={"nproc": nproc})

pylops/basicoperators/directionalderivative.py

Lines changed: 55 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,7 @@
88
from pylops.utils.typing import DTypeLike, InputDimsLike, NDArray
99

1010

11-
def FirstDirectionalDerivative(
12-
dims: InputDimsLike,
13-
v: NDArray,
14-
sampling: int = 1,
15-
edge: bool = False,
16-
kind: str = "centered",
17-
dtype: DTypeLike = "float64",
18-
) -> LinearOperator:
11+
class FirstDirectionalDerivative(LinearOperator):
1912
r"""First Directional derivative.
2013
2114
Apply a directional derivative operator to a multi-dimensional array
@@ -42,11 +35,6 @@ def FirstDirectionalDerivative(
4235
dtype : :obj:`str`, optional
4336
Type of elements in input array.
4437
45-
Returns
46-
-------
47-
ddop : :obj:`pylops.LinearOperator`
48-
First directional derivative linear operator
49-
5038
Notes
5139
-----
5240
The FirstDirectionalDerivative applies a first-order derivative
@@ -71,25 +59,39 @@ def FirstDirectionalDerivative(
7159
operator with :math:`\mathbf{v}` along the main diagonal.
7260
7361
"""
74-
Gop = Gradient(dims, sampling=sampling, edge=edge, kind=kind, dtype=dtype)
75-
if v.ndim == 1:
76-
Dop = Diagonal(v, dims=[len(dims)] + list(dims), axis=0, dtype=dtype)
77-
else:
78-
Dop = Diagonal(v.ravel(), dtype=dtype)
79-
Sop = Sum(dims=[len(dims)] + list(dims), axis=0, dtype=dtype)
80-
ddop = Sop * Dop * Gop
81-
ddop.dims = ddop.dimsd = dims
82-
ddop.sampling = sampling
83-
return ddop
84-
85-
86-
def SecondDirectionalDerivative(
87-
dims: InputDimsLike,
88-
v: NDArray,
89-
sampling: int = 1,
90-
edge: bool = False,
91-
dtype: DTypeLike = "float64",
92-
) -> LinearOperator:
62+
63+
def __init__(self, dims: InputDimsLike,
64+
v: NDArray,
65+
sampling: int = 1,
66+
edge: bool = False,
67+
kind: str = "centered",
68+
dtype: DTypeLike = "float64",
69+
name: str = 'F'):
70+
self.sampling = sampling
71+
self.edge = edge
72+
self.kind = kind
73+
self.v = v
74+
Op = self._calc_first_ddop(dims=dims, sampling=sampling, edge=edge, kind=kind, dtype=dtype, v=v)
75+
super().__init__(Op=Op, name=name)
76+
77+
def _matvec(self, x: NDArray) -> NDArray:
78+
return super()._matvec(x)
79+
80+
def _rmatvec(self, x: NDArray) -> NDArray:
81+
return super()._rmatvec(x)
82+
83+
@staticmethod
84+
def _calc_first_ddop(dims: InputDimsLike, v: NDArray, sampling: int, edge: bool, kind: str, dtype: DTypeLike):
85+
Gop = Gradient(dims, sampling=sampling, edge=edge, kind=kind, dtype=dtype)
86+
if v.ndim == 1:
87+
Dop = Diagonal(v, dims=[len(dims)] + list(dims), axis=0, dtype=dtype)
88+
else:
89+
Dop = Diagonal(v.ravel(), dtype=dtype)
90+
Sop = Sum(dims=[len(dims)] + list(dims), axis=0, dtype=dtype)
91+
return Sop * Dop * Gop
92+
93+
94+
class SecondDirectionalDerivative(LinearOperator):
9395
r"""Second Directional derivative.
9496
9597
Apply a second directional derivative operator to a multi-dimensional array
@@ -114,11 +116,6 @@ def SecondDirectionalDerivative(
114116
dtype : :obj:`str`, optional
115117
Type of elements in input array.
116118
117-
Returns
118-
-------
119-
ddop : :obj:`pylops.LinearOperator`
120-
Second directional derivative linear operator
121-
122119
Notes
123120
-----
124121
The SecondDirectionalDerivative applies a second-order derivative
@@ -135,8 +132,24 @@ def SecondDirectionalDerivative(
135132
This operator is sometimes also referred to as directional Laplacian
136133
in the literature.
137134
"""
138-
Dop = FirstDirectionalDerivative(dims, v, sampling=sampling, edge=edge, dtype=dtype)
139-
ddop = -Dop.H * Dop
140-
ddop.dims = ddop.dimsd = dims
141-
ddop.sampling = sampling
142-
return ddop
135+
136+
def __init__(self, dims: InputDimsLike, v: NDArray, sampling: int = 1, edge: bool = False,
137+
dtype: DTypeLike = "float64", name: str = 'S'):
138+
self.dims = dims
139+
self.v = v
140+
self.sampling = sampling
141+
self.edge = edge
142+
Op = self._calc_second_ddop(dims=dims, v=v, sampling=sampling, edge=edge, dtype=dtype)
143+
super().__init__(Op=Op, name=name)
144+
145+
def _matvec(self, x: NDArray) -> NDArray:
146+
return super()._matvec(x)
147+
148+
def _rmatvec(self, x: NDArray) -> NDArray:
149+
return super()._rmatvec(x)
150+
151+
@staticmethod
152+
def _calc_second_ddop(dims: InputDimsLike, v: NDArray, sampling: int, edge: bool, dtype: DTypeLike):
153+
Dop = FirstDirectionalDerivative(dims=dims, v=v, sampling=sampling, edge=edge, dtype=dtype)
154+
ddop = -Dop.H * Dop
155+
return ddop

pylops/basicoperators/gradient.py

Lines changed: 31 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,10 @@
55
from pylops import LinearOperator
66
from pylops.basicoperators import FirstDerivative, VStack
77
from pylops.utils._internal import _value_or_sized_to_tuple
8-
from pylops.utils.typing import DTypeLike, InputDimsLike
8+
from pylops.utils.typing import DTypeLike, InputDimsLike, NDArray
99

1010

11-
def Gradient(
12-
dims: Union[int, InputDimsLike],
13-
sampling: int = 1,
14-
edge: bool = False,
15-
kind: str = "centered",
16-
dtype: DTypeLike = "float64",
17-
) -> LinearOperator:
11+
class Gradient(LinearOperator):
1812
r"""Gradient.
1913
2014
Apply gradient operator to a multi-dimensional array.
@@ -36,11 +30,6 @@ def Gradient(
3630
dtype : :obj:`str`, optional
3731
Type of elements in input array.
3832
39-
Returns
40-
-------
41-
l2op : :obj:`pylops.LinearOperator`
42-
Gradient linear operator
43-
4433
Notes
4534
-----
4635
The Gradient operator applies a first-order derivative to each dimension of
@@ -69,26 +58,33 @@ def Gradient(
6958
axes are instead summed together.
7059
7160
"""
72-
dims = _value_or_sized_to_tuple(dims)
73-
ndims = len(dims)
74-
sampling = _value_or_sized_to_tuple(sampling, repeat=ndims)
75-
76-
gop = VStack(
77-
[
78-
FirstDerivative(
79-
dims,
80-
axis=iax,
81-
sampling=sampling[iax],
82-
edge=edge,
83-
kind=kind,
84-
dtype=dtype,
85-
)
61+
62+
def __init__(self,
63+
dims: Union[int, InputDimsLike],
64+
sampling: int = 1,
65+
edge: bool = False,
66+
kind: str = "centered",
67+
dtype: DTypeLike = "float64", name: str = 'G'):
68+
dims = _value_or_sized_to_tuple(dims)
69+
ndims = len(dims)
70+
sampling = _value_or_sized_to_tuple(sampling, repeat=ndims)
71+
self.sampling = sampling
72+
self.edge = edge
73+
self.kind = kind
74+
Op = VStack([FirstDerivative(
75+
dims=dims,
76+
axis=iax,
77+
sampling=sampling[iax],
78+
edge=edge,
79+
kind=kind,
80+
dtype=dtype,
81+
)
8682
for iax in range(ndims)
87-
]
88-
)
89-
gop.dims = dims
90-
gop.dimsd = (ndims, *gop.dims)
91-
gop.sampling = sampling
92-
gop.edge = edge
93-
gop.kind = kind
94-
return gop
83+
])
84+
super().__init__(Op=Op, dims=dims, dimsd=(ndims, *dims), name=name)
85+
86+
def _matvec(self, x: NDArray) -> NDArray:
87+
return super()._matvec(x)
88+
89+
def _rmatvec(self, x: NDArray) -> NDArray:
90+
return super()._rmatvec(x)

pylops/basicoperators/laplacian.py

Lines changed: 41 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,16 @@
22

33

44
from typing import Tuple
5+
from pylops.utils.typing import NDArray
56

67
from numpy.core.multiarray import normalize_axis_index
78

8-
from pylops import LinearOperator, aslinearoperator
9+
from pylops import LinearOperator
910
from pylops.basicoperators import SecondDerivative
1011
from pylops.utils.typing import DTypeLike, InputDimsLike
1112

1213

13-
def Laplacian(
14-
dims: InputDimsLike,
15-
axes: InputDimsLike = (-2, -1),
16-
weights: Tuple[float, ...] = (1, 1),
17-
sampling: Tuple[float, ...] = (1, 1),
18-
edge: bool = False,
19-
kind: str = "centered",
20-
dtype: DTypeLike = "float64",
21-
) -> LinearOperator:
14+
class Laplacian(LinearOperator):
2215
r"""Laplacian.
2316
2417
Apply second-order centered Laplacian operator to a multi-dimensional array.
@@ -47,11 +40,6 @@ def Laplacian(
4740
dtype : :obj:`str`, optional
4841
Type of elements in input array.
4942
50-
Returns
51-
-------
52-
l2op : :obj:`pylops.LinearOperator`
53-
Laplacian linear operator
54-
5543
Raises
5644
------
5745
ValueError
@@ -69,27 +57,42 @@ def Laplacian(
6957
/ (\Delta x \Delta y)
7058
7159
"""
72-
axes = tuple(normalize_axis_index(ax, len(dims)) for ax in axes)
73-
if not (len(axes) == len(weights) == len(sampling)):
74-
raise ValueError("axes, weights, and sampling have different size")
75-
76-
l2op = SecondDerivative(
77-
dims, axis=axes[0], sampling=sampling[0], edge=edge, kind=kind, dtype=dtype
78-
)
79-
dims, dimsd = l2op.dims, l2op.dimsd
80-
81-
l2op *= weights[0]
82-
for ax, samp, weight in zip(axes[1:], sampling[1:], weights[1:]):
83-
l2op += weight * SecondDerivative(
84-
dims, axis=ax, sampling=samp, edge=edge, dtype=dtype
85-
)
8660

87-
l2op = aslinearoperator(l2op)
88-
l2op.dims = dims
89-
l2op.dimsd = dimsd
90-
l2op.axes = axes
91-
l2op.weights = weights
92-
l2op.sampling = sampling
93-
l2op.edge = edge
94-
l2op.kind = kind
95-
return l2op
61+
def __init__(self, dims: InputDimsLike,
62+
axes: InputDimsLike = (-2, -1),
63+
weights: Tuple[float, ...] = (1, 1),
64+
sampling: Tuple[float, ...] = (1, 1),
65+
edge: bool = False,
66+
kind: str = "centered",
67+
dtype: DTypeLike = "float64", name: str = "L"):
68+
axes = tuple(normalize_axis_index(ax, len(dims)) for ax in axes)
69+
if not (len(axes) == len(weights) == len(sampling)):
70+
raise ValueError("axes, weights, and sampling have different size")
71+
self.axes = axes
72+
self.weights = weights
73+
self.sampling = sampling
74+
self.edge = edge
75+
self.kind = kind
76+
Op = self._calc_l2op(dims=dims, axes=axes, sampling=sampling, edge=edge, kind=kind, dtype=dtype,
77+
weights=weights)
78+
super().__init__(Op=Op, name=name)
79+
80+
def _matvec(self, x: NDArray) -> NDArray:
81+
return super()._matvec(x)
82+
83+
def _rmatvec(self, x: NDArray) -> NDArray:
84+
return super()._rmatvec(x)
85+
86+
@staticmethod
87+
def _calc_l2op(dims: InputDimsLike, axes: InputDimsLike, weights: Tuple[float, ...], sampling: Tuple[float, ...],
88+
edge: bool, kind: str, dtype: DTypeLike):
89+
l2op = SecondDerivative(
90+
dims, axis=axes[0], sampling=sampling[0], edge=edge, kind=kind, dtype=dtype
91+
)
92+
dims = l2op.dims
93+
l2op *= weights[0]
94+
for ax, samp, weight in zip(axes[1:], sampling[1:], weights[1:]):
95+
l2op += weight * SecondDerivative(
96+
dims, axis=ax, sampling=samp, edge=edge, dtype=dtype
97+
)
98+
return l2op

0 commit comments

Comments
 (0)