pygeoinf 1.2.0__py3-none-any.whl → 1.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pygeoinf/__init__.py +17 -4
- pygeoinf/backus_gilbert.py +120 -0
- pygeoinf/direct_sum.py +35 -77
- pygeoinf/forward_problem.py +39 -15
- pygeoinf/gaussian_measure.py +35 -10
- pygeoinf/hilbert_space.py +55 -9
- pygeoinf/inversion.py +101 -9
- pygeoinf/linear_bayesian.py +3 -3
- pygeoinf/linear_forms.py +137 -43
- pygeoinf/{operators.py → linear_operators.py} +279 -303
- pygeoinf/linear_optimisation.py +9 -9
- pygeoinf/linear_solvers.py +74 -7
- pygeoinf/nonlinear_forms.py +226 -0
- pygeoinf/nonlinear_operators.py +216 -0
- pygeoinf/nonlinear_optimisation.py +211 -0
- pygeoinf/parallel.py +1 -1
- pygeoinf/random_matrix.py +212 -72
- pygeoinf/symmetric_space/circle.py +12 -2
- pygeoinf/symmetric_space/sphere.py +15 -1
- pygeoinf/symmetric_space/symmetric_space.py +1 -1
- {pygeoinf-1.2.0.dist-info → pygeoinf-1.2.2.dist-info}/METADATA +1 -1
- pygeoinf-1.2.2.dist-info/RECORD +25 -0
- pygeoinf-1.2.0.dist-info/RECORD +0 -21
- {pygeoinf-1.2.0.dist-info → pygeoinf-1.2.2.dist-info}/LICENSE +0 -0
- {pygeoinf-1.2.0.dist-info → pygeoinf-1.2.2.dist-info}/WHEEL +0 -0
pygeoinf/inversion.py
CHANGED
|
@@ -13,22 +13,25 @@ inversion techniques, such as the existence of a data error measure.
|
|
|
13
13
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
from .hilbert_space import HilbertSpace
|
|
18
|
+
from .nonlinear_operators import NonLinearOperator
|
|
19
|
+
from .linear_operators import LinearOperator
|
|
20
|
+
from .forward_problem import LinearForwardProblem, ForwardProblem
|
|
18
21
|
|
|
19
22
|
|
|
20
23
|
class Inversion:
|
|
21
24
|
"""
|
|
22
|
-
|
|
25
|
+
A base class for inversion methods.
|
|
23
26
|
|
|
24
|
-
This class provides a common structure for different inversion algorithms
|
|
27
|
+
This class provides a common structure for different inversion and inference algorithms
|
|
25
28
|
(e.g., Bayesian, Least Squares). Its main purpose is to hold a reference
|
|
26
29
|
to the forward problem being solved and provide convenient access to its
|
|
27
30
|
properties. Subclasses should inherit from this class to implement a
|
|
28
|
-
specific inversion
|
|
31
|
+
specific inversion algorithm.
|
|
29
32
|
"""
|
|
30
33
|
|
|
31
|
-
def __init__(self, forward_problem:
|
|
34
|
+
def __init__(self, forward_problem: ForwardProblem, /) -> None:
|
|
32
35
|
"""
|
|
33
36
|
Initializes the Inversion class.
|
|
34
37
|
|
|
@@ -36,20 +39,20 @@ class Inversion:
|
|
|
36
39
|
forward_problem: An instance of a forward problem that defines the
|
|
37
40
|
relationship between model parameters and data.
|
|
38
41
|
"""
|
|
39
|
-
self._forward_problem:
|
|
42
|
+
self._forward_problem: ForwardProblem = forward_problem
|
|
40
43
|
|
|
41
44
|
@property
|
|
42
|
-
def forward_problem(self) ->
|
|
45
|
+
def forward_problem(self) -> ForwardProblem:
|
|
43
46
|
"""The forward problem associated with this inversion."""
|
|
44
47
|
return self._forward_problem
|
|
45
48
|
|
|
46
49
|
@property
|
|
47
|
-
def model_space(self) ->
|
|
50
|
+
def model_space(self) -> HilbertSpace:
|
|
48
51
|
"""The model space (domain) of the forward problem."""
|
|
49
52
|
return self.forward_problem.model_space
|
|
50
53
|
|
|
51
54
|
@property
|
|
52
|
-
def data_space(self) ->
|
|
55
|
+
def data_space(self) -> HilbertSpace:
|
|
53
56
|
"""The data space (codomain) of the forward problem."""
|
|
54
57
|
return self.forward_problem.data_space
|
|
55
58
|
|
|
@@ -83,3 +86,92 @@ class Inversion:
|
|
|
83
86
|
raise AttributeError(
|
|
84
87
|
"An inverse data covariance (precision) operator is required for this inversion method."
|
|
85
88
|
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class LinearInversion(Inversion):
|
|
92
|
+
"""
|
|
93
|
+
An abstract base class for linear inversion algorithms.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
def __init__(self, forward_problem: LinearForwardProblem, /) -> None:
|
|
97
|
+
"""
|
|
98
|
+
Initializes the LinearInversion class.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
forward_problem: An instance of a linear forward problem.
|
|
102
|
+
"""
|
|
103
|
+
if not isinstance(forward_problem, LinearForwardProblem):
|
|
104
|
+
raise ValueError("Forward problem must be a LinearForwardProblem.")
|
|
105
|
+
super().__init__(forward_problem)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class Inference(Inversion):
|
|
109
|
+
"""
|
|
110
|
+
A base class for inference algorithms. These methods inherit common functionality from
|
|
111
|
+
the inversion base class, but need not themselves derive from a specific inversion scheme.
|
|
112
|
+
|
|
113
|
+
Within an inference problem, the aim is to estimate some property of the unknown model,
|
|
114
|
+
and hence a property operator mapping from the model to a property space must be
|
|
115
|
+
specified.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
def __init__(
|
|
119
|
+
self, forward_problem: ForwardProblem, property_operator: NonLinearOperator
|
|
120
|
+
) -> None:
|
|
121
|
+
"""
|
|
122
|
+
Initializes the Inference class.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
forward_problem: An instance of a forward problem that defines the
|
|
126
|
+
relationship between model parameters and data.
|
|
127
|
+
property_operator: A mapping takes elements of the model space to
|
|
128
|
+
property vector of interest.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
super().__init__(forward_problem)
|
|
132
|
+
|
|
133
|
+
if property_operator.domain != self.model_space:
|
|
134
|
+
raise ValueError("Property operator incompatible with model space")
|
|
135
|
+
|
|
136
|
+
self._property_operator = property_operator
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def property_operator(self) -> NonLinearOperator:
|
|
140
|
+
"""
|
|
141
|
+
Returns the property operator.
|
|
142
|
+
"""
|
|
143
|
+
return self._property_operator
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def property_space(self) -> HilbertSpace:
|
|
147
|
+
"""
|
|
148
|
+
Returns the property space.
|
|
149
|
+
"""
|
|
150
|
+
return self.property_operator.codomain
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class LinearInference(Inference):
|
|
154
|
+
"""
|
|
155
|
+
A base class for linear inference algorithms.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
def __init__(
|
|
159
|
+
self, forward_problem: LinearForwardProblem, property_operator: LinearOperator
|
|
160
|
+
) -> None:
|
|
161
|
+
"""
|
|
162
|
+
Initializes the LinearInference class.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
forward_problem: An instance of a linear forward problem that defines the
|
|
166
|
+
relationship between model parameters and data.
|
|
167
|
+
property_operator: A linear mapping takes elements of the model space to
|
|
168
|
+
property vector of interest.
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
if not isinstance(forward_problem, LinearForwardProblem):
|
|
172
|
+
raise ValueError("Forward problem must be linear")
|
|
173
|
+
|
|
174
|
+
if not isinstance(property_operator, LinearOperator):
|
|
175
|
+
raise ValueError("Property mapping must be linear")
|
|
176
|
+
|
|
177
|
+
super().__init__(forward_problem, property_operator)
|
pygeoinf/linear_bayesian.py
CHANGED
|
@@ -23,17 +23,17 @@ Key Classes
|
|
|
23
23
|
from __future__ import annotations
|
|
24
24
|
from typing import Optional
|
|
25
25
|
|
|
26
|
-
from .inversion import
|
|
26
|
+
from .inversion import LinearInversion
|
|
27
27
|
from .gaussian_measure import GaussianMeasure
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
from .forward_problem import LinearForwardProblem
|
|
31
|
-
from .
|
|
31
|
+
from .linear_operators import LinearOperator
|
|
32
32
|
from .linear_solvers import LinearSolver, IterativeLinearSolver
|
|
33
33
|
from .hilbert_space import HilbertSpace, Vector
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
class LinearBayesianInversion(
|
|
36
|
+
class LinearBayesianInversion(LinearInversion):
|
|
37
37
|
"""
|
|
38
38
|
Solves a linear inverse problem using Bayesian methods.
|
|
39
39
|
|
pygeoinf/linear_forms.py
CHANGED
|
@@ -1,33 +1,36 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Provides the `LinearForm` class
|
|
2
|
+
Provides the `LinearForm` class for representing linear functionals.
|
|
3
3
|
|
|
4
4
|
A linear form is a linear mapping from a vector in a Hilbert space to a
|
|
5
|
-
scalar
|
|
6
|
-
elements of the dual space of a `HilbertSpace`.
|
|
7
|
-
|
|
8
|
-
A `LinearForm` can be thought of as a dual vector and is a fundamental component
|
|
9
|
-
for defining inner products and adjoint operators within the library.
|
|
5
|
+
scalar. This class provides a concrete, component-based representation for
|
|
6
|
+
elements of the dual space of a `HilbertSpace`. It inherits from `NonLinearForm`,
|
|
7
|
+
specializing it for the linear case.
|
|
10
8
|
"""
|
|
11
9
|
|
|
12
10
|
from __future__ import annotations
|
|
13
11
|
from typing import Callable, Optional, Any, TYPE_CHECKING
|
|
14
12
|
|
|
13
|
+
from joblib import Parallel, delayed
|
|
14
|
+
|
|
15
15
|
import numpy as np
|
|
16
16
|
|
|
17
|
+
from .nonlinear_forms import NonLinearForm
|
|
18
|
+
|
|
17
19
|
# This block only runs for type checkers, not at runtime
|
|
18
20
|
if TYPE_CHECKING:
|
|
19
|
-
from .hilbert_space import HilbertSpace, EuclideanSpace
|
|
20
|
-
from .
|
|
21
|
+
from .hilbert_space import HilbertSpace, EuclideanSpace, Vector
|
|
22
|
+
from .linear_operators import LinearOperator
|
|
21
23
|
|
|
22
24
|
|
|
23
|
-
class LinearForm:
|
|
25
|
+
class LinearForm(NonLinearForm):
|
|
24
26
|
"""
|
|
25
|
-
Represents a linear form
|
|
27
|
+
Represents a linear form as an efficient, component-based functional.
|
|
26
28
|
|
|
27
|
-
A `LinearForm` is an element of a dual `HilbertSpace
|
|
28
|
-
action on vectors from its `domain
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
A `LinearForm` is an element of a dual `HilbertSpace` and is defined by its
|
|
30
|
+
action on vectors from its `domain`. Internally, this action is represented
|
|
31
|
+
by a component vector. This class provides optimized arithmetic operations
|
|
32
|
+
and correctly defines the gradient (a constant vector) and the Hessian
|
|
33
|
+
(the zero operator) for any linear functional.
|
|
31
34
|
"""
|
|
32
35
|
|
|
33
36
|
def __init__(
|
|
@@ -35,29 +38,49 @@ class LinearForm:
|
|
|
35
38
|
domain: HilbertSpace,
|
|
36
39
|
/,
|
|
37
40
|
*,
|
|
38
|
-
mapping: Optional[Callable[[Any], float]] = None,
|
|
39
41
|
components: Optional[np.ndarray] = None,
|
|
42
|
+
mapping: Optional[Callable[[Vector], float]] = None,
|
|
43
|
+
parallel: bool = False,
|
|
44
|
+
n_jobs: int = -1,
|
|
40
45
|
) -> None:
|
|
41
46
|
"""
|
|
42
|
-
Initializes the LinearForm.
|
|
47
|
+
Initializes the LinearForm from a mapping or component vector.
|
|
48
|
+
|
|
49
|
+
A form must be defined by exactly one of two methods:
|
|
50
|
+
1. **components**: The explicit component vector representing the form.
|
|
51
|
+
2. **mapping**: A function `f(x)` that defines the form's action.
|
|
52
|
+
The components will be automatically computed from this mapping.
|
|
43
53
|
|
|
44
|
-
A form can be defined either by its functional mapping or directly
|
|
45
|
-
by its component vector. If a mapping is provided without components,
|
|
46
|
-
the components will be computed by evaluating the mapping on the
|
|
47
|
-
basis vectors of the domain.
|
|
48
54
|
|
|
49
55
|
Args:
|
|
50
56
|
domain: The Hilbert space on which the form is defined.
|
|
51
|
-
mapping: A function `f(x)` defining the action of the form.
|
|
52
57
|
components: The component representation of the form.
|
|
58
|
+
mapping: The functional mapping `f(x)`. Used if `components` is None.
|
|
59
|
+
parallel: Whether to use parallel computing components from the mapping.
|
|
60
|
+
n_jobs: The number of jobs to use for parallel computing.
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
AssertionError: If neither or both `mapping` and `components`
|
|
64
|
+
are specified.
|
|
65
|
+
|
|
66
|
+
Notes:
|
|
67
|
+
Parallel options only relevant if the form is defined by a mapping.
|
|
68
|
+
|
|
69
|
+
If both `components` and `mapping` are specified, `components`
|
|
70
|
+
will take precedence.
|
|
53
71
|
"""
|
|
54
72
|
|
|
55
|
-
|
|
73
|
+
super().__init__(
|
|
74
|
+
domain,
|
|
75
|
+
self._mapping_impl,
|
|
76
|
+
gradient=self._gradient_impl,
|
|
77
|
+
hessian=self._hessian_impl,
|
|
78
|
+
)
|
|
56
79
|
|
|
57
80
|
if components is None:
|
|
58
81
|
if mapping is None:
|
|
59
82
|
raise AssertionError("Neither mapping nor components specified.")
|
|
60
|
-
self._compute_components(mapping)
|
|
83
|
+
self._compute_components(mapping, parallel, n_jobs)
|
|
61
84
|
else:
|
|
62
85
|
self._components: np.ndarray = components
|
|
63
86
|
|
|
@@ -93,7 +116,7 @@ class LinearForm:
|
|
|
93
116
|
is the scalar result of the form's action.
|
|
94
117
|
"""
|
|
95
118
|
from .hilbert_space import EuclideanSpace
|
|
96
|
-
from .
|
|
119
|
+
from .linear_operators import LinearOperator
|
|
97
120
|
|
|
98
121
|
return LinearOperator(
|
|
99
122
|
self.domain,
|
|
@@ -108,10 +131,6 @@ class LinearForm:
|
|
|
108
131
|
"""
|
|
109
132
|
return LinearForm(self.domain, components=self.components.copy())
|
|
110
133
|
|
|
111
|
-
def __call__(self, x: Any) -> float:
|
|
112
|
-
"""Applies the linear form to a vector."""
|
|
113
|
-
return np.dot(self._components, self.domain.to_components(x))
|
|
114
|
-
|
|
115
134
|
def __neg__(self) -> LinearForm:
|
|
116
135
|
"""Returns the additive inverse of the form."""
|
|
117
136
|
return LinearForm(self.domain, components=-self._components)
|
|
@@ -128,13 +147,47 @@ class LinearForm:
|
|
|
128
147
|
"""Returns the division of the form by a scalar."""
|
|
129
148
|
return self * (1.0 / a)
|
|
130
149
|
|
|
131
|
-
def __add__(self, other: LinearForm) -> LinearForm:
|
|
132
|
-
"""
|
|
133
|
-
|
|
150
|
+
def __add__(self, other: NonLinearForm | LinearForm) -> NonLinearForm | LinearForm:
|
|
151
|
+
"""
|
|
152
|
+
Returns the sum of this form and another.
|
|
153
|
+
|
|
154
|
+
If `other` is also a `LinearForm`, this performs an optimized,
|
|
155
|
+
component-wise addition. Otherwise, it delegates to the general
|
|
156
|
+
implementation in the `NonLinearForm` base class.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
other: The form to add to this one.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
A `LinearForm` if adding two `LinearForm`s, otherwise a `NonLinearForm`.
|
|
163
|
+
"""
|
|
164
|
+
if isinstance(other, LinearForm):
|
|
165
|
+
return LinearForm(
|
|
166
|
+
self.domain, components=self.components + other.components
|
|
167
|
+
)
|
|
168
|
+
else:
|
|
169
|
+
return super().__add__(other)
|
|
170
|
+
|
|
171
|
+
def __sub__(self, other: NonLinearForm | LinearForm) -> NonLinearForm | LinearForm:
|
|
172
|
+
"""
|
|
173
|
+
Returns the difference of this form and another.
|
|
174
|
+
|
|
175
|
+
If `other` is also a `LinearForm`, this performs an optimized,
|
|
176
|
+
component-wise subtraction. Otherwise, it delegates to the general
|
|
177
|
+
implementation in the `NonLinearForm` base class.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
other: The form to subtract from this one.
|
|
134
181
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
182
|
+
Returns:
|
|
183
|
+
A `LinearForm` if subtracting two `LinearForm`s, otherwise a `NonLinearForm`.
|
|
184
|
+
"""
|
|
185
|
+
if isinstance(other, LinearForm):
|
|
186
|
+
return LinearForm(
|
|
187
|
+
self.domain, components=self.components - other.components
|
|
188
|
+
)
|
|
189
|
+
else:
|
|
190
|
+
return super().__sub__(other)
|
|
138
191
|
|
|
139
192
|
def __imul__(self, a: float) -> "LinearForm":
|
|
140
193
|
"""
|
|
@@ -156,14 +209,55 @@ class LinearForm:
|
|
|
156
209
|
"""Returns the string representation of the form's components."""
|
|
157
210
|
return self.components.__str__()
|
|
158
211
|
|
|
159
|
-
def _compute_components(
|
|
212
|
+
def _compute_components(
|
|
213
|
+
self,
|
|
214
|
+
mapping: Callable[[Any], float],
|
|
215
|
+
parallel: bool,
|
|
216
|
+
n_jobs: Optional[int],
|
|
217
|
+
):
|
|
218
|
+
"""Computes the component vector of the form, with an optional parallel backend."""
|
|
219
|
+
if not parallel:
|
|
220
|
+
self._components = np.zeros(self.domain.dim)
|
|
221
|
+
cx = np.zeros(self.domain.dim)
|
|
222
|
+
for i in range(self.domain.dim):
|
|
223
|
+
cx[i] = 1.0
|
|
224
|
+
x = self.domain.from_components(cx)
|
|
225
|
+
self._components[i] = mapping(x)
|
|
226
|
+
cx[i] = 0.0
|
|
227
|
+
else:
|
|
228
|
+
|
|
229
|
+
def compute_one_component(i: int) -> float:
|
|
230
|
+
"""
|
|
231
|
+
Computes a single component for a given basis vector index.
|
|
232
|
+
This function is sent to each parallel worker.
|
|
233
|
+
"""
|
|
234
|
+
|
|
235
|
+
# cx = np.zeros(self.domain.dim)
|
|
236
|
+
# cx[i] = 1.0
|
|
237
|
+
# x = self.domain.from_components(cx)
|
|
238
|
+
x = self.domain.basis_vector(i)
|
|
239
|
+
return mapping(x)
|
|
240
|
+
|
|
241
|
+
# Run the helper function in parallel for each dimension
|
|
242
|
+
results = Parallel(n_jobs=n_jobs)(
|
|
243
|
+
delayed(compute_one_component)(i) for i in range(self.domain.dim)
|
|
244
|
+
)
|
|
245
|
+
self._components = np.array(results)
|
|
246
|
+
|
|
247
|
+
def _mapping_impl(self, x: Vector) -> float:
|
|
248
|
+
"""
|
|
249
|
+
Maps a vector to its scalar value.
|
|
250
|
+
"""
|
|
251
|
+
return np.dot(self.components, self.domain.to_components(x))
|
|
252
|
+
|
|
253
|
+
def _gradient_impl(self, _: Vector) -> Vector:
|
|
254
|
+
"""
|
|
255
|
+
Computes the gradient of the form at a point.
|
|
256
|
+
"""
|
|
257
|
+
return self.domain.from_dual(self)
|
|
258
|
+
|
|
259
|
+
def _hessian_impl(self, _: Vector) -> LinearOperator:
|
|
160
260
|
"""
|
|
161
|
-
Computes the
|
|
261
|
+
Computes the Hessian of the form at a point.
|
|
162
262
|
"""
|
|
163
|
-
|
|
164
|
-
cx = np.zeros(self.domain.dim)
|
|
165
|
-
for i in range(self.domain.dim):
|
|
166
|
-
cx[i] = 1
|
|
167
|
-
x = self.domain.from_components(cx)
|
|
168
|
-
self._components[i] = mapping(x)
|
|
169
|
-
cx[i] = 0
|
|
263
|
+
return self.domain.zero_operator()
|