pygeoinf 1.2.1__tar.gz → 1.2.3__tar.gz
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-1.2.1 → pygeoinf-1.2.3}/PKG-INFO +1 -1
- pygeoinf-1.2.3/pygeoinf/backus_gilbert.py +120 -0
- {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/direct_sum.py +10 -0
- {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/forward_problem.py +36 -15
- {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/hilbert_space.py +48 -0
- pygeoinf-1.2.3/pygeoinf/inversion.py +177 -0
- {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/linear_bayesian.py +2 -2
- {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/linear_optimisation.py +5 -5
- {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/nonlinear_forms.py +1 -0
- {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/nonlinear_operators.py +7 -0
- {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/nonlinear_optimisation.py +1 -1
- pygeoinf-1.2.3/pygeoinf/symmetric_space/__pycache__/__init__.cpython-311.pyc +0 -0
- pygeoinf-1.2.3/pygeoinf/symmetric_space/__pycache__/circle.cpython-311.pyc +0 -0
- pygeoinf-1.2.3/pygeoinf/symmetric_space/__pycache__/sphere.cpython-311.pyc +0 -0
- pygeoinf-1.2.3/pygeoinf/symmetric_space/__pycache__/symmetric_space.cpython-311.pyc +0 -0
- {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/symmetric_space/circle.py +11 -1
- {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/symmetric_space/sphere.py +14 -0
- {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pyproject.toml +1 -1
- pygeoinf-1.2.1/pygeoinf/backus_gilbert.py +0 -3
- pygeoinf-1.2.1/pygeoinf/inversion.py +0 -85
- {pygeoinf-1.2.1 → pygeoinf-1.2.3}/LICENSE +0 -0
- {pygeoinf-1.2.1 → pygeoinf-1.2.3}/README.md +0 -0
- {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/__init__.py +0 -0
- {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/gaussian_measure.py +0 -0
- {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/linear_forms.py +0 -0
- {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/linear_operators.py +0 -0
- {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/linear_solvers.py +0 -0
- {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/parallel.py +0 -0
- {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/random_matrix.py +0 -0
- {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/symmetric_space/__init__.py +0 -0
- {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/symmetric_space/symmetric_space.py +0 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module for Backus-Gilbert like methods for solving inference problems. To be done...
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .hilbert_space import HilbertSpace, Vector
|
|
6
|
+
from .linear_operators import LinearOperator
|
|
7
|
+
from .nonlinear_forms import NonLinearForm
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class HyperEllipsoid:
|
|
11
|
+
"""
|
|
12
|
+
A class for hyper-ellipsoids in a Hilbert Space. Such sets occur within
|
|
13
|
+
the context of Backus-Gilbert methods, both in terms of prior constraints
|
|
14
|
+
and posterior bounds on the property space.
|
|
15
|
+
|
|
16
|
+
The hyper-ellipsoid is defined through the inequality
|
|
17
|
+
|
|
18
|
+
(A(x-x_0), x-x_0)_{X} <= r**2,
|
|
19
|
+
|
|
20
|
+
where A is a self-adjoint linear operator on the space, X, x is an arbitrary vector, x_0 is the
|
|
21
|
+
centre, and r the radius.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
space: HilbertSpace,
|
|
27
|
+
radius: float,
|
|
28
|
+
/,
|
|
29
|
+
*,
|
|
30
|
+
centre: Vector = None,
|
|
31
|
+
operator: LinearOperator = None,
|
|
32
|
+
) -> None:
|
|
33
|
+
"""
|
|
34
|
+
Args:
|
|
35
|
+
space (HilbertSpace): The Hilbert space in which the hyper-ellipsoid is defined.
|
|
36
|
+
radius (float): The radius of the hyper-ellipsoid.
|
|
37
|
+
centre (Vector); The centre of the hyper-ellipsoid. The default is None which corresponds to
|
|
38
|
+
the zero-vector.
|
|
39
|
+
operator (LinearOperator): A self-adjoint operator on the space defining the hyper-ellipsoid.
|
|
40
|
+
The default is None which corresponds to the identity operator.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
if not isinstance(space, HilbertSpace):
|
|
44
|
+
raise ValueError("Input space must be a HilbertSpace")
|
|
45
|
+
self._space = space
|
|
46
|
+
|
|
47
|
+
if not radius > 0:
|
|
48
|
+
raise ValueError("Input radius must be positive.")
|
|
49
|
+
self._radius = radius
|
|
50
|
+
|
|
51
|
+
if operator is None:
|
|
52
|
+
self._operator = space.identity_operator()
|
|
53
|
+
else:
|
|
54
|
+
if not (operator.domain == space and operator.is_automorphism):
|
|
55
|
+
raise ValueError("Operator is not of the appropriate form.")
|
|
56
|
+
self._operator = operator
|
|
57
|
+
|
|
58
|
+
if centre is None:
|
|
59
|
+
self._centre = space.zero
|
|
60
|
+
else:
|
|
61
|
+
if not space.is_element(centre):
|
|
62
|
+
raise ValueError("The input centre does not lie in the space.")
|
|
63
|
+
self._centre = centre
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def space(self) -> HilbertSpace:
|
|
67
|
+
"""
|
|
68
|
+
Returns the HilbertSpace the hyper-ellipsoid is defined on.
|
|
69
|
+
"""
|
|
70
|
+
return self._space
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def radius(self) -> float:
|
|
74
|
+
"""
|
|
75
|
+
Returns the radius of the hyper-ellipsoid.
|
|
76
|
+
"""
|
|
77
|
+
return self._radius
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def operator(self) -> LinearOperator:
|
|
81
|
+
"""
|
|
82
|
+
Returns the operator for the hyper-ellipsoid.
|
|
83
|
+
"""
|
|
84
|
+
return self._operator
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def centre(self) -> Vector:
|
|
88
|
+
"""
|
|
89
|
+
Returns the centre of the hyper-ellipsoid.
|
|
90
|
+
"""
|
|
91
|
+
return self._centre
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def quadratic_form(self) -> NonLinearForm:
|
|
95
|
+
"""
|
|
96
|
+
Returns the mapping x -> (A(x-x_0), x-x_0)_{X} as a NonLinearForm.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
space = self.space
|
|
100
|
+
x0 = self.centre
|
|
101
|
+
A = self.operator
|
|
102
|
+
|
|
103
|
+
def mapping(x: Vector) -> float:
|
|
104
|
+
d = space.subtract(x, x0)
|
|
105
|
+
return space.inner_product(A(d), d)
|
|
106
|
+
|
|
107
|
+
def gradient(x: Vector) -> Vector:
|
|
108
|
+
d = space.subtract(x, x0)
|
|
109
|
+
return space.multiply(2, A(d))
|
|
110
|
+
|
|
111
|
+
def hessian(_: Vector) -> LinearOperator:
|
|
112
|
+
return A
|
|
113
|
+
|
|
114
|
+
return NonLinearForm(space, mapping, gradient=gradient, hessian=hessian)
|
|
115
|
+
|
|
116
|
+
def is_point(self, x: Vector) -> bool:
|
|
117
|
+
"""
|
|
118
|
+
True if x lies in the hyper-ellipsoid.
|
|
119
|
+
"""
|
|
120
|
+
return self.quadratic_form(x) <= self.radius**2
|
|
@@ -116,6 +116,16 @@ class HilbertSpaceDirectSum(HilbertSpace):
|
|
|
116
116
|
|
|
117
117
|
return self.subspaces == other.subspaces
|
|
118
118
|
|
|
119
|
+
def is_element(self, xs: Any) -> bool:
|
|
120
|
+
"""
|
|
121
|
+
Checks if a list of vectors is a valid element of the direct sum space.
|
|
122
|
+
"""
|
|
123
|
+
if not isinstance(xs, list):
|
|
124
|
+
return False
|
|
125
|
+
if len(xs) != self.number_of_subspaces:
|
|
126
|
+
return False
|
|
127
|
+
return all(space.is_element(x) for space, x in zip(self._spaces, xs))
|
|
128
|
+
|
|
119
129
|
@property
|
|
120
130
|
def subspaces(self) -> List[HilbertSpace]:
|
|
121
131
|
"""Returns the list of subspaces that form the direct sum."""
|
|
@@ -24,12 +24,14 @@ from scipy.stats import chi2
|
|
|
24
24
|
|
|
25
25
|
from .gaussian_measure import GaussianMeasure
|
|
26
26
|
from .direct_sum import ColumnLinearOperator
|
|
27
|
+
from .linear_operators import LinearOperator
|
|
28
|
+
|
|
27
29
|
|
|
28
30
|
# This block only runs for type checkers, not at runtime, to prevent
|
|
29
31
|
# circular import errors while still allowing type hints.
|
|
30
32
|
if TYPE_CHECKING:
|
|
31
33
|
from .hilbert_space import HilbertSpace, Vector
|
|
32
|
-
from .
|
|
34
|
+
from .nonlinear_operators import NonLinearOperator
|
|
33
35
|
|
|
34
36
|
|
|
35
37
|
class ForwardProblem:
|
|
@@ -43,10 +45,10 @@ class ForwardProblem:
|
|
|
43
45
|
|
|
44
46
|
def __init__(
|
|
45
47
|
self,
|
|
46
|
-
forward_operator:
|
|
48
|
+
forward_operator: NonLinearOperator,
|
|
47
49
|
/,
|
|
48
50
|
*,
|
|
49
|
-
data_error_measure: Optional[
|
|
51
|
+
data_error_measure: Optional[GaussianMeasure] = None,
|
|
50
52
|
) -> None:
|
|
51
53
|
"""Initializes the ForwardProblem.
|
|
52
54
|
|
|
@@ -57,8 +59,8 @@ class ForwardProblem:
|
|
|
57
59
|
from which data errors are assumed to be drawn. If None, the
|
|
58
60
|
data is considered to be error-free.
|
|
59
61
|
"""
|
|
60
|
-
self._forward_operator:
|
|
61
|
-
self._data_error_measure: Optional[
|
|
62
|
+
self._forward_operator: NonLinearOperator = forward_operator
|
|
63
|
+
self._data_error_measure: Optional[GaussianMeasure] = data_error_measure
|
|
62
64
|
if self.data_error_measure_set:
|
|
63
65
|
if self.data_space != data_error_measure.domain:
|
|
64
66
|
raise ValueError(
|
|
@@ -76,7 +78,7 @@ class ForwardProblem:
|
|
|
76
78
|
return self._data_error_measure is not None
|
|
77
79
|
|
|
78
80
|
@property
|
|
79
|
-
def data_error_measure(self) ->
|
|
81
|
+
def data_error_measure(self) -> GaussianMeasure:
|
|
80
82
|
"""The measure from which data errors are drawn."""
|
|
81
83
|
if not self.data_error_measure_set:
|
|
82
84
|
raise AttributeError("Data error measure has not been set.")
|
|
@@ -101,10 +103,31 @@ class LinearForwardProblem(ForwardProblem):
|
|
|
101
103
|
and `e` is a random error drawn from a Gaussian distribution.
|
|
102
104
|
"""
|
|
103
105
|
|
|
106
|
+
def __init__(
|
|
107
|
+
self,
|
|
108
|
+
forward_operator: LinearOperator,
|
|
109
|
+
/,
|
|
110
|
+
*,
|
|
111
|
+
data_error_measure: Optional[GaussianMeasure] = None,
|
|
112
|
+
) -> None:
|
|
113
|
+
"""
|
|
114
|
+
Args:
|
|
115
|
+
forward_operator: The operator that maps from the model space to the
|
|
116
|
+
data space.
|
|
117
|
+
data_error_measure: A Gaussian measure representing the distribution
|
|
118
|
+
from which data errors are assumed to be drawn. If None, the
|
|
119
|
+
data is considered to be error-free.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
if not isinstance(forward_operator, LinearOperator):
|
|
123
|
+
raise ValueError("Forward operator must be a linear operator.")
|
|
124
|
+
|
|
125
|
+
super().__init__(forward_operator, data_error_measure=data_error_measure)
|
|
126
|
+
|
|
104
127
|
@staticmethod
|
|
105
128
|
def from_direct_sum(
|
|
106
|
-
forward_problems: List[
|
|
107
|
-
) ->
|
|
129
|
+
forward_problems: List[LinearForwardProblem],
|
|
130
|
+
) -> LinearForwardProblem:
|
|
108
131
|
"""
|
|
109
132
|
Forms a joint forward problem from a list of separate problems.
|
|
110
133
|
|
|
@@ -144,7 +167,7 @@ class LinearForwardProblem(ForwardProblem):
|
|
|
144
167
|
joint_forward_operator, data_error_measure=data_error_measure
|
|
145
168
|
)
|
|
146
169
|
|
|
147
|
-
def data_measure(self, model:
|
|
170
|
+
def data_measure(self, model: Vector) -> GaussianMeasure:
|
|
148
171
|
"""
|
|
149
172
|
Returns the Gaussian measure for the data, given a specific model.
|
|
150
173
|
|
|
@@ -165,7 +188,7 @@ class LinearForwardProblem(ForwardProblem):
|
|
|
165
188
|
translation=self.forward_operator(model)
|
|
166
189
|
)
|
|
167
190
|
|
|
168
|
-
def synthetic_data(self, model:
|
|
191
|
+
def synthetic_data(self, model: Vector) -> Vector:
|
|
169
192
|
"""
|
|
170
193
|
Generates a synthetic data vector for a given model.
|
|
171
194
|
|
|
@@ -180,9 +203,7 @@ class LinearForwardProblem(ForwardProblem):
|
|
|
180
203
|
"""
|
|
181
204
|
return self.data_measure(model).sample()
|
|
182
205
|
|
|
183
|
-
def synthetic_model_and_data(
|
|
184
|
-
self, prior: "GaussianMeasure"
|
|
185
|
-
) -> Tuple["Vector", "Vector"]:
|
|
206
|
+
def synthetic_model_and_data(self, prior: GaussianMeasure) -> Tuple[Vector, Vector]:
|
|
186
207
|
"""
|
|
187
208
|
Generates a random model and corresponding synthetic data.
|
|
188
209
|
|
|
@@ -216,7 +237,7 @@ class LinearForwardProblem(ForwardProblem):
|
|
|
216
237
|
"""
|
|
217
238
|
return chi2.ppf(significance_level, self.data_space.dim)
|
|
218
239
|
|
|
219
|
-
def chi_squared(self, model:
|
|
240
|
+
def chi_squared(self, model: Vector, data: Vector) -> float:
|
|
220
241
|
"""
|
|
221
242
|
Calculates the chi-squared statistic for a given model and data.
|
|
222
243
|
|
|
@@ -250,7 +271,7 @@ class LinearForwardProblem(ForwardProblem):
|
|
|
250
271
|
return self.data_space.squared_norm(residual)
|
|
251
272
|
|
|
252
273
|
def chi_squared_test(
|
|
253
|
-
self, significance_level: float, model:
|
|
274
|
+
self, significance_level: float, model: Vector, data: Vector
|
|
254
275
|
) -> bool:
|
|
255
276
|
"""
|
|
256
277
|
Performs a chi-squared test for goodness of fit.
|
|
@@ -504,6 +504,14 @@ class DualHilbertSpace(HilbertSpace):
|
|
|
504
504
|
return NotImplemented
|
|
505
505
|
return self.underlying_space == other.underlying_space
|
|
506
506
|
|
|
507
|
+
def is_element(self, x: Any) -> bool:
|
|
508
|
+
"""
|
|
509
|
+
Checks if an object is a valid element of the dual space.
|
|
510
|
+
"""
|
|
511
|
+
from .linear_forms import LinearForm
|
|
512
|
+
|
|
513
|
+
return isinstance(x, LinearForm) and x.domain == self.underlying_space
|
|
514
|
+
|
|
507
515
|
@final
|
|
508
516
|
def duality_product(self, xp: LinearForm, x: Vector) -> float:
|
|
509
517
|
"""
|
|
@@ -583,6 +591,12 @@ class EuclideanSpace(HilbertSpace):
|
|
|
583
591
|
return NotImplemented
|
|
584
592
|
return self.dim == other.dim
|
|
585
593
|
|
|
594
|
+
def is_element(self, x: Any) -> bool:
|
|
595
|
+
"""
|
|
596
|
+
Checks if an object is a valid element of the space.
|
|
597
|
+
"""
|
|
598
|
+
return isinstance(x, np.ndarray) and len(x) == self.dim
|
|
599
|
+
|
|
586
600
|
|
|
587
601
|
class MassWeightedHilbertSpace(HilbertSpace):
|
|
588
602
|
"""
|
|
@@ -678,6 +692,40 @@ class MassWeightedHilbertSpace(HilbertSpace):
|
|
|
678
692
|
and (self.inverse_mass_operator == other.inverse_mass_operator)
|
|
679
693
|
)
|
|
680
694
|
|
|
695
|
+
def is_element(self, x: Any) -> bool:
|
|
696
|
+
"""
|
|
697
|
+
Checks if an object is a valid element of the space.
|
|
698
|
+
"""
|
|
699
|
+
return self.underlying_space.is_element(x)
|
|
700
|
+
|
|
701
|
+
def add(self, x: Vector, y: Vector) -> Vector:
|
|
702
|
+
"""Computes the sum of two vectors. Defaults to `x + y`."""
|
|
703
|
+
return self.underlying_space.add(x, y)
|
|
704
|
+
|
|
705
|
+
def subtract(self, x: Vector, y: Vector) -> Vector:
|
|
706
|
+
"""Computes the difference of two vectors. Defaults to `x - y`."""
|
|
707
|
+
return self.underlying_space.subtract(x, y)
|
|
708
|
+
|
|
709
|
+
def multiply(self, a: float, x: Vector) -> Vector:
|
|
710
|
+
"""Computes scalar multiplication. Defaults to `a * x`."""
|
|
711
|
+
return self.underlying_space.multiply(a, x)
|
|
712
|
+
|
|
713
|
+
def negative(self, x: Vector) -> Vector:
|
|
714
|
+
"""Computes the additive inverse of a vector. Defaults to `-1 * x`."""
|
|
715
|
+
return self.underlying_space.negative(x)
|
|
716
|
+
|
|
717
|
+
def ax(self, a: float, x: Vector) -> None:
|
|
718
|
+
"""Performs in-place scaling `x := a*x`. Defaults to `x *= a`."""
|
|
719
|
+
self.underlying_space.ax(a, x)
|
|
720
|
+
|
|
721
|
+
def axpy(self, a: float, x: Vector, y: Vector) -> None:
|
|
722
|
+
"""Performs in-place operation `y := y + a*x`. Defaults to `y += a*x`."""
|
|
723
|
+
self.underlying_space.axpy(a, x, y)
|
|
724
|
+
|
|
725
|
+
def copy(self, x: Vector) -> Vector:
|
|
726
|
+
"""Returns a deep copy of a vector. Defaults to `x.copy()`."""
|
|
727
|
+
return self.underlying_space.copy(x)
|
|
728
|
+
|
|
681
729
|
|
|
682
730
|
class MassWeightedHilbertModule(MassWeightedHilbertSpace, HilbertModule):
|
|
683
731
|
"""
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Provides the abstract base class for all inversion algorithms.
|
|
3
|
+
|
|
4
|
+
This module defines the `Inversion` class, which serves as a common
|
|
5
|
+
foundation for various methods that solve an inverse problem. Its primary role
|
|
6
|
+
is to maintain a reference to the `ForwardProblem` being solved, providing a
|
|
7
|
+
consistent interface and convenient access to the problem's core components like
|
|
8
|
+
the model space and data space.
|
|
9
|
+
|
|
10
|
+
It also includes helper methods to assert preconditions required by different
|
|
11
|
+
inversion techniques, such as the existence of a data error measure.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
|
|
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
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Inversion:
|
|
24
|
+
"""
|
|
25
|
+
A base class for inversion methods.
|
|
26
|
+
|
|
27
|
+
This class provides a common structure for different inversion and inference algorithms
|
|
28
|
+
(e.g., Bayesian, Least Squares). Its main purpose is to hold a reference
|
|
29
|
+
to the forward problem being solved and provide convenient access to its
|
|
30
|
+
properties. Subclasses should inherit from this class to implement a
|
|
31
|
+
specific inversion algorithm.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, forward_problem: ForwardProblem, /) -> None:
|
|
35
|
+
"""
|
|
36
|
+
Initializes the Inversion class.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
forward_problem: An instance of a forward problem that defines the
|
|
40
|
+
relationship between model parameters and data.
|
|
41
|
+
"""
|
|
42
|
+
self._forward_problem: ForwardProblem = forward_problem
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def forward_problem(self) -> ForwardProblem:
|
|
46
|
+
"""The forward problem associated with this inversion."""
|
|
47
|
+
return self._forward_problem
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def model_space(self) -> HilbertSpace:
|
|
51
|
+
"""The model space (domain) of the forward problem."""
|
|
52
|
+
return self.forward_problem.model_space
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def data_space(self) -> HilbertSpace:
|
|
56
|
+
"""The data space (codomain) of the forward problem."""
|
|
57
|
+
return self.forward_problem.data_space
|
|
58
|
+
|
|
59
|
+
def assert_data_error_measure(self) -> None:
|
|
60
|
+
"""
|
|
61
|
+
Checks if a data error measure is set in the forward problem.
|
|
62
|
+
|
|
63
|
+
This is a precondition for statistical inversion methods.
|
|
64
|
+
|
|
65
|
+
Raises:
|
|
66
|
+
AttributeError: If no data error measure has been set.
|
|
67
|
+
"""
|
|
68
|
+
if not self.forward_problem.data_error_measure_set:
|
|
69
|
+
raise AttributeError(
|
|
70
|
+
"A data error measure is required for this inversion method."
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def assert_inverse_data_covariance(self) -> None:
|
|
74
|
+
"""
|
|
75
|
+
Checks if the data error measure has an inverse covariance.
|
|
76
|
+
|
|
77
|
+
This is a precondition for methods that require the data precision
|
|
78
|
+
matrix (the inverse of the data error covariance).
|
|
79
|
+
|
|
80
|
+
Raises:
|
|
81
|
+
AttributeError: If no data error measure is set, or if the measure
|
|
82
|
+
does not have an inverse covariance operator defined.
|
|
83
|
+
"""
|
|
84
|
+
self.assert_data_error_measure()
|
|
85
|
+
if not self.forward_problem.data_error_measure.inverse_covariance_set:
|
|
86
|
+
raise AttributeError(
|
|
87
|
+
"An inverse data covariance (precision) operator is required for this inversion method."
|
|
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)
|
|
@@ -23,7 +23,7 @@ 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
|
|
|
@@ -33,7 +33,7 @@ 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
|
|
|
@@ -22,7 +22,7 @@ from __future__ import annotations
|
|
|
22
22
|
from typing import Optional, Union
|
|
23
23
|
|
|
24
24
|
from .nonlinear_operators import NonLinearOperator
|
|
25
|
-
from .inversion import
|
|
25
|
+
from .inversion import LinearInversion
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
from .forward_problem import LinearForwardProblem
|
|
@@ -31,7 +31,7 @@ from .linear_solvers import LinearSolver, IterativeLinearSolver
|
|
|
31
31
|
from .hilbert_space import Vector
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
class LinearLeastSquaresInversion(
|
|
34
|
+
class LinearLeastSquaresInversion(LinearInversion):
|
|
35
35
|
"""
|
|
36
36
|
Solves a linear inverse problem using Tikhonov-regularized least-squares.
|
|
37
37
|
|
|
@@ -89,7 +89,7 @@ class LinearLeastSquaresInversion(Inversion):
|
|
|
89
89
|
/,
|
|
90
90
|
*,
|
|
91
91
|
preconditioner: Optional[LinearOperator] = None,
|
|
92
|
-
) -> Union[
|
|
92
|
+
) -> Union[NonLinearOperator, LinearOperator]:
|
|
93
93
|
"""
|
|
94
94
|
Returns an operator that maps data to the least-squares solution.
|
|
95
95
|
|
|
@@ -137,7 +137,7 @@ class LinearLeastSquaresInversion(Inversion):
|
|
|
137
137
|
return inverse_normal_operator @ forward_operator.adjoint
|
|
138
138
|
|
|
139
139
|
|
|
140
|
-
class LinearMinimumNormInversion(
|
|
140
|
+
class LinearMinimumNormInversion(LinearInversion):
|
|
141
141
|
"""
|
|
142
142
|
Finds a regularized solution using the discrepancy principle.
|
|
143
143
|
|
|
@@ -168,7 +168,7 @@ class LinearMinimumNormInversion(Inversion):
|
|
|
168
168
|
maxiter: int = 100,
|
|
169
169
|
rtol: float = 1.0e-6,
|
|
170
170
|
atol: float = 0.0,
|
|
171
|
-
) -> Union[
|
|
171
|
+
) -> Union[NonLinearOperator, LinearOperator]:
|
|
172
172
|
"""
|
|
173
173
|
Returns an operator that maps data to the minimum-norm solution.
|
|
174
174
|
|
|
@@ -71,6 +71,13 @@ class NonLinearOperator:
|
|
|
71
71
|
"""True if the operator's domain and codomain have the same dimension."""
|
|
72
72
|
return self.domain.dim == self.codomain.dim
|
|
73
73
|
|
|
74
|
+
@property
|
|
75
|
+
def has_derivative(self) -> bool:
|
|
76
|
+
"""
|
|
77
|
+
Returns true if the operators derivative is implemented.
|
|
78
|
+
"""
|
|
79
|
+
return self._derivative is not None
|
|
80
|
+
|
|
74
81
|
def __call__(self, x: Any) -> Any:
|
|
75
82
|
"""Applies the operator's mapping to a vector."""
|
|
76
83
|
return self._mapping(x)
|
|
@@ -162,7 +162,7 @@ def line_search(
|
|
|
162
162
|
# Wrap the function.
|
|
163
163
|
def f(xc: np.ndarray) -> float:
|
|
164
164
|
x = domain.from_components(xc)
|
|
165
|
-
return
|
|
165
|
+
return form(x)
|
|
166
166
|
|
|
167
167
|
# Wrap the derivative. Note that this is given in
|
|
168
168
|
# terms of the components of the derivative (i.e., an element
|
|
Binary file
|
|
@@ -24,7 +24,7 @@ Key Classes
|
|
|
24
24
|
|
|
25
25
|
from __future__ import annotations
|
|
26
26
|
|
|
27
|
-
from typing import Callable, Tuple, Optional
|
|
27
|
+
from typing import Callable, Tuple, Optional, Any
|
|
28
28
|
import matplotlib.pyplot as plt
|
|
29
29
|
import numpy as np
|
|
30
30
|
from scipy.fft import rfft, irfft
|
|
@@ -336,6 +336,16 @@ class Lebesgue(CircleHelper, HilbertModule, AbstractInvariantLebesgueSpace):
|
|
|
336
336
|
|
|
337
337
|
return self.kmax == other.kmax and self.radius == other.radius
|
|
338
338
|
|
|
339
|
+
def is_element(self, u: Any) -> bool:
|
|
340
|
+
"""
|
|
341
|
+
Checks if an object is a valid element of the space.
|
|
342
|
+
"""
|
|
343
|
+
if not isinstance(u, np.ndarray):
|
|
344
|
+
return False
|
|
345
|
+
if not u.shape == (self.dim,):
|
|
346
|
+
return False
|
|
347
|
+
return True
|
|
348
|
+
|
|
339
349
|
def invariant_automorphism_from_index_function(self, g: Callable[[int], float]):
|
|
340
350
|
"""
|
|
341
351
|
Implements an invariant automorphism of the form f(Δ) using Fourier
|
|
@@ -448,6 +448,20 @@ class Lebesgue(SphereHelper, HilbertModule, AbstractInvariantLebesgueSpace):
|
|
|
448
448
|
|
|
449
449
|
return self.lmax == other.lmax and self.radius == other.radius
|
|
450
450
|
|
|
451
|
+
def is_element(self, x: Any) -> bool:
|
|
452
|
+
"""
|
|
453
|
+
Checks if an object is a valid element of the space.
|
|
454
|
+
"""
|
|
455
|
+
if not isinstance(x, sh.SHGrid):
|
|
456
|
+
return False
|
|
457
|
+
if not x.lmax == self.lmax:
|
|
458
|
+
return False
|
|
459
|
+
if not x.grid == self._grid_name():
|
|
460
|
+
return False
|
|
461
|
+
if not x.extend == self.extend:
|
|
462
|
+
return False
|
|
463
|
+
return True
|
|
464
|
+
|
|
451
465
|
def eigenfunction_norms(self) -> np.ndarray:
|
|
452
466
|
"""Returns a list of the norms of the eigenfunctions."""
|
|
453
467
|
return np.fromiter(
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Provides the abstract base class for all inversion algorithms.
|
|
3
|
-
|
|
4
|
-
This module defines the `Inversion` class, which serves as a common
|
|
5
|
-
foundation for various methods that solve an inverse problem. Its primary role
|
|
6
|
-
is to maintain a reference to the `ForwardProblem` being solved, providing a
|
|
7
|
-
consistent interface and convenient access to the problem's core components like
|
|
8
|
-
the model space and data space.
|
|
9
|
-
|
|
10
|
-
It also includes helper methods to assert preconditions required by different
|
|
11
|
-
inversion techniques, such as the existence of a data error measure.
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
from __future__ import annotations
|
|
15
|
-
|
|
16
|
-
from .forward_problem import LinearForwardProblem
|
|
17
|
-
from .hilbert_space import HilbertSpace
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class Inversion:
|
|
21
|
-
"""
|
|
22
|
-
An abstract base class for inversion methods.
|
|
23
|
-
|
|
24
|
-
This class provides a common structure for different inversion algorithms
|
|
25
|
-
(e.g., Bayesian, Least Squares). Its main purpose is to hold a reference
|
|
26
|
-
to the forward problem being solved and provide convenient access to its
|
|
27
|
-
properties. Subclasses should inherit from this class to implement a
|
|
28
|
-
specific inversion technique.
|
|
29
|
-
"""
|
|
30
|
-
|
|
31
|
-
def __init__(self, forward_problem: "LinearForwardProblem", /) -> None:
|
|
32
|
-
"""
|
|
33
|
-
Initializes the Inversion class.
|
|
34
|
-
|
|
35
|
-
Args:
|
|
36
|
-
forward_problem: An instance of a forward problem that defines the
|
|
37
|
-
relationship between model parameters and data.
|
|
38
|
-
"""
|
|
39
|
-
self._forward_problem: "LinearForwardProblem" = forward_problem
|
|
40
|
-
|
|
41
|
-
@property
|
|
42
|
-
def forward_problem(self) -> "LinearForwardProblem":
|
|
43
|
-
"""The forward problem associated with this inversion."""
|
|
44
|
-
return self._forward_problem
|
|
45
|
-
|
|
46
|
-
@property
|
|
47
|
-
def model_space(self) -> "HilbertSpace":
|
|
48
|
-
"""The model space (domain) of the forward problem."""
|
|
49
|
-
return self.forward_problem.model_space
|
|
50
|
-
|
|
51
|
-
@property
|
|
52
|
-
def data_space(self) -> "HilbertSpace":
|
|
53
|
-
"""The data space (codomain) of the forward problem."""
|
|
54
|
-
return self.forward_problem.data_space
|
|
55
|
-
|
|
56
|
-
def assert_data_error_measure(self) -> None:
|
|
57
|
-
"""
|
|
58
|
-
Checks if a data error measure is set in the forward problem.
|
|
59
|
-
|
|
60
|
-
This is a precondition for statistical inversion methods.
|
|
61
|
-
|
|
62
|
-
Raises:
|
|
63
|
-
AttributeError: If no data error measure has been set.
|
|
64
|
-
"""
|
|
65
|
-
if not self.forward_problem.data_error_measure_set:
|
|
66
|
-
raise AttributeError(
|
|
67
|
-
"A data error measure is required for this inversion method."
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
def assert_inverse_data_covariance(self) -> None:
|
|
71
|
-
"""
|
|
72
|
-
Checks if the data error measure has an inverse covariance.
|
|
73
|
-
|
|
74
|
-
This is a precondition for methods that require the data precision
|
|
75
|
-
matrix (the inverse of the data error covariance).
|
|
76
|
-
|
|
77
|
-
Raises:
|
|
78
|
-
AttributeError: If no data error measure is set, or if the measure
|
|
79
|
-
does not have an inverse covariance operator defined.
|
|
80
|
-
"""
|
|
81
|
-
self.assert_data_error_measure()
|
|
82
|
-
if not self.forward_problem.data_error_measure.inverse_covariance_set:
|
|
83
|
-
raise AttributeError(
|
|
84
|
-
"An inverse data covariance (precision) operator is required for this inversion method."
|
|
85
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|