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.
Files changed (31) hide show
  1. {pygeoinf-1.2.1 → pygeoinf-1.2.3}/PKG-INFO +1 -1
  2. pygeoinf-1.2.3/pygeoinf/backus_gilbert.py +120 -0
  3. {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/direct_sum.py +10 -0
  4. {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/forward_problem.py +36 -15
  5. {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/hilbert_space.py +48 -0
  6. pygeoinf-1.2.3/pygeoinf/inversion.py +177 -0
  7. {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/linear_bayesian.py +2 -2
  8. {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/linear_optimisation.py +5 -5
  9. {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/nonlinear_forms.py +1 -0
  10. {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/nonlinear_operators.py +7 -0
  11. {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/nonlinear_optimisation.py +1 -1
  12. pygeoinf-1.2.3/pygeoinf/symmetric_space/__pycache__/__init__.cpython-311.pyc +0 -0
  13. pygeoinf-1.2.3/pygeoinf/symmetric_space/__pycache__/circle.cpython-311.pyc +0 -0
  14. pygeoinf-1.2.3/pygeoinf/symmetric_space/__pycache__/sphere.cpython-311.pyc +0 -0
  15. pygeoinf-1.2.3/pygeoinf/symmetric_space/__pycache__/symmetric_space.cpython-311.pyc +0 -0
  16. {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/symmetric_space/circle.py +11 -1
  17. {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/symmetric_space/sphere.py +14 -0
  18. {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pyproject.toml +1 -1
  19. pygeoinf-1.2.1/pygeoinf/backus_gilbert.py +0 -3
  20. pygeoinf-1.2.1/pygeoinf/inversion.py +0 -85
  21. {pygeoinf-1.2.1 → pygeoinf-1.2.3}/LICENSE +0 -0
  22. {pygeoinf-1.2.1 → pygeoinf-1.2.3}/README.md +0 -0
  23. {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/__init__.py +0 -0
  24. {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/gaussian_measure.py +0 -0
  25. {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/linear_forms.py +0 -0
  26. {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/linear_operators.py +0 -0
  27. {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/linear_solvers.py +0 -0
  28. {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/parallel.py +0 -0
  29. {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/random_matrix.py +0 -0
  30. {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/symmetric_space/__init__.py +0 -0
  31. {pygeoinf-1.2.1 → pygeoinf-1.2.3}/pygeoinf/symmetric_space/symmetric_space.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pygeoinf
3
- Version: 1.2.1
3
+ Version: 1.2.3
4
4
  Summary: A package for solving geophysical inference and inverse problems
5
5
  License: BSD-3-Clause
6
6
  Author: David Al-Attar and Dan Heathcote
@@ -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 .linear_operators import LinearOperator
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: LinearOperator,
48
+ forward_operator: NonLinearOperator,
47
49
  /,
48
50
  *,
49
- data_error_measure: Optional["GaussianMeasure"] = None,
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: LinearOperator = forward_operator
61
- self._data_error_measure: Optional["GaussianMeasure"] = data_error_measure
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) -> "GaussianMeasure":
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["LinearForwardProblem"],
107
- ) -> "LinearForwardProblem":
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: "Vector") -> "GaussianMeasure":
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: "Vector") -> "Vector":
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: "Vector", data: "Vector") -> float:
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: "Vector", data: "Vector"
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 Inversion
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(Inversion):
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 Inversion
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(Inversion):
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[Operator, LinearOperator]:
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(Inversion):
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[Operator, LinearOperator]:
171
+ ) -> Union[NonLinearOperator, LinearOperator]:
172
172
  """
173
173
  Returns an operator that maps data to the minimum-norm solution.
174
174
 
@@ -16,6 +16,7 @@ if TYPE_CHECKING:
16
16
  from .hilbert_space import HilbertSpace, Vector
17
17
  from .linear_forms import LinearForm
18
18
  from .linear_operators import LinearOperator
19
+ from .nonlinear_operators import NonLinearOperator
19
20
 
20
21
 
21
22
  class NonLinearForm:
@@ -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 f(x)
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
@@ -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,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pygeoinf"
3
- version = "1.2.1"
3
+ version = "1.2.3"
4
4
  description = "A package for solving geophysical inference and inverse problems"
5
5
  authors = ["David Al-Attar and Dan Heathcote"]
6
6
  readme = "README.md"
@@ -1,3 +0,0 @@
1
- """
2
- Module for Backus-Gilbert like methods for solving inference problems. To be done...
3
- """
@@ -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