pygeoinf 1.0.9__py3-none-any.whl → 1.1.1__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 CHANGED
@@ -8,7 +8,11 @@ from .random_matrix import (
8
8
 
9
9
  from .hilbert_space import (
10
10
  HilbertSpace,
11
+ DualHilbertSpace,
11
12
  EuclideanSpace,
13
+ HilbertModule,
14
+ MassWeightedHilbertSpace,
15
+ MassWeightedHilbertModule,
12
16
  )
13
17
 
14
18
 
@@ -18,7 +22,7 @@ from .operators import (
18
22
  DiagonalLinearOperator,
19
23
  )
20
24
 
21
- from .forms import (
25
+ from .linear_forms import (
22
26
  LinearForm,
23
27
  )
24
28
 
pygeoinf/direct_sum.py CHANGED
@@ -1,5 +1,24 @@
1
1
  """
2
- Module for direct sums of Hilbert spaces and related block operators.
2
+ Implements direct sums of Hilbert spaces and corresponding block operators.
3
+
4
+ This module provides tools for constructing larger, composite Hilbert spaces and
5
+ operators from smaller ones. This is essential for problems involving multiple
6
+ coupled fields or joint inversions where a single model is constrained by
7
+ data from different experiments.
8
+
9
+ Key Classes
10
+ -----------
11
+ - `HilbertSpaceDirectSum`: A `HilbertSpace` formed by the direct sum of a
12
+ list of other spaces. Vectors in this space are lists of vectors from the
13
+ component subspaces.
14
+ - `BlockLinearOperator`: A `LinearOperator` acting between direct sum spaces,
15
+ represented as a 2D grid (matrix) of sub-operators.
16
+ - `ColumnLinearOperator`: A specialized block operator mapping from a single
17
+ space to a direct sum space.
18
+ - `RowLinearOperator`: A specialized block operator mapping from a direct sum
19
+ space to a single space.
20
+ - `BlockDiagonalLinearOperator`: An efficient representation for block
21
+ operators with zero off-diagonal blocks.
3
22
  """
4
23
 
5
24
  from __future__ import annotations
@@ -9,15 +28,16 @@ import numpy as np
9
28
 
10
29
  from .hilbert_space import HilbertSpace
11
30
  from .operators import LinearOperator
12
- from .forms import LinearForm
31
+ from .linear_forms import LinearForm
13
32
 
14
33
 
15
34
  class HilbertSpaceDirectSum(HilbertSpace):
16
35
  """
17
36
  A Hilbert space formed from the direct sum of a list of other spaces.
18
37
 
19
- Vectors in this space are represented as lists of vectors, where each
20
- element of the list is a vector from the corresponding subspace.
38
+ A vector in this space is represented as a list of vectors, where the i-th
39
+ element of the list is a vector from the i-th component subspace. The
40
+ inner product is the sum of the inner products of the components.
21
41
  """
22
42
 
23
43
  def __init__(self, spaces: List[HilbertSpace]) -> None:
@@ -29,22 +49,58 @@ class HilbertSpaceDirectSum(HilbertSpace):
29
49
  in the direct sum.
30
50
  """
31
51
  self._spaces: List[HilbertSpace] = spaces
32
- dim = sum([space.dim for space in spaces])
33
- super().__init__(
34
- dim,
35
- self.__to_components,
36
- self.__from_components,
37
- self.__inner_product,
38
- self.__to_dual,
39
- self.__from_dual,
40
- add=self.__add,
41
- subtract=self.__subtract,
42
- multiply=self.__multiply,
43
- ax=self.__ax,
44
- axpy=self.__axpy,
45
- copy=self.__copy,
52
+ self._dim = sum([space.dim for space in spaces])
53
+
54
+ @property
55
+ def dim(self) -> int:
56
+ """Returns the dimension of the direct sum space."""
57
+ return self._dim
58
+
59
+ def to_components(self, xs: List[Any]) -> np.ndarray:
60
+ cs = [space.to_components(x) for space, x in zip(self._spaces, xs)]
61
+ return np.concatenate(cs, 0)
62
+
63
+ def from_components(self, c: np.ndarray) -> List[Any]:
64
+ xs = []
65
+ i = 0
66
+ for space in self._spaces:
67
+ j = i + space.dim
68
+ x = space.from_components(c[i:j])
69
+ xs.append(x)
70
+ i = j
71
+ return xs
72
+
73
+ def to_dual(self, xs: List[Any]) -> LinearForm:
74
+ if len(xs) != self.number_of_subspaces:
75
+ raise ValueError("Input list has incorrect number of vectors.")
76
+ return self.canonical_dual_isomorphism(
77
+ [space.to_dual(x) for space, x in zip(self._spaces, xs)]
46
78
  )
47
79
 
80
+ def from_dual(self, xp: LinearForm) -> List[Any]:
81
+ xps = self.canonical_dual_inverse_isomorphism(xp)
82
+ return [space.from_dual(xip) for space, xip in zip(self._spaces, xps)]
83
+
84
+ def add(self, xs: List[Any], ys: List[Any]) -> List[Any]:
85
+ return [space.add(x, y) for space, x, y in zip(self._spaces, xs, ys)]
86
+
87
+ def subtract(self, xs: List[Any], ys: List[Any]) -> List[Any]:
88
+ return [space.subtract(x, y) for space, x, y in zip(self._spaces, xs, ys)]
89
+
90
+ def multiply(self, a: float, xs: List[Any]) -> List[Any]:
91
+ return [space.multiply(a, x) for space, x in zip(self._spaces, xs)]
92
+
93
+ def ax(self, a: float, xs: List[Any]) -> None:
94
+ for space, x in zip(self._spaces, xs):
95
+ space.ax(a, x)
96
+
97
+ def axpy(self, a: float, xs: List[Any], ys: List[Any]) -> None:
98
+ for space, x, y in zip(self._spaces, xs, ys):
99
+ space.axpy(a, x, y)
100
+
101
+ def copy(self, xs: List[Any]) -> List[Any]:
102
+ return [space.copy(x) for space, x in zip(self._spaces, xs)]
103
+
48
104
  def __eq__(self, other: object) -> bool:
49
105
  """
50
106
  Checks for mathematical equality with another direct sum space.
@@ -55,7 +111,6 @@ class HilbertSpaceDirectSum(HilbertSpace):
55
111
  if not isinstance(other, HilbertSpaceDirectSum):
56
112
  return NotImplemented
57
113
 
58
- # This relies on the subspaces having their own __eq__ methods.
59
114
  return self.subspaces == other.subspaces
60
115
 
61
116
  @property
@@ -145,56 +200,6 @@ class HilbertSpaceDirectSum(HilbertSpace):
145
200
  i = j
146
201
  return xps
147
202
 
148
- def __to_components(self, xs: List[Any]) -> np.ndarray:
149
- cs = [space.to_components(x) for space, x in zip(self._spaces, xs)]
150
- return np.concatenate(cs, 0)
151
-
152
- def __from_components(self, c: np.ndarray) -> List[Any]:
153
- xs = []
154
- i = 0
155
- for space in self._spaces:
156
- j = i + space.dim
157
- x = space.from_components(c[i:j])
158
- xs.append(x)
159
- i = j
160
- return xs
161
-
162
- def __inner_product(self, x1s: List[Any], x2s: List[Any]) -> float:
163
- return sum(
164
- space.inner_product(x1, x2) for space, x1, x2 in zip(self._spaces, x1s, x2s)
165
- )
166
-
167
- def __to_dual(self, xs: List[Any]) -> LinearForm:
168
- if len(xs) != self.number_of_subspaces:
169
- raise ValueError("Input list has incorrect number of vectors.")
170
- return self.canonical_dual_isomorphism(
171
- [space.to_dual(x) for space, x in zip(self._spaces, xs)]
172
- )
173
-
174
- def __from_dual(self, xp: LinearForm) -> List[Any]:
175
- xps = self.canonical_dual_inverse_isomorphism(xp)
176
- return [space.from_dual(xip) for space, xip in zip(self._spaces, xps)]
177
-
178
- def __add(self, xs: List[Any], ys: List[Any]) -> List[Any]:
179
- return [space.add(x, y) for space, x, y in zip(self._spaces, xs, ys)]
180
-
181
- def __subtract(self, xs: List[Any], ys: List[Any]) -> List[Any]:
182
- return [space.subtract(x, y) for space, x, y in zip(self._spaces, xs, ys)]
183
-
184
- def __multiply(self, a: float, xs: List[Any]) -> List[Any]:
185
- return [space.multiply(a, x) for space, x in zip(self._spaces, xs)]
186
-
187
- def __ax(self, a: float, xs: List[Any]) -> None:
188
- for space, x in zip(self._spaces, xs):
189
- space.ax(a, x)
190
-
191
- def __axpy(self, a: float, xs: List[Any], ys: List[Any]) -> None:
192
- for space, x, y in zip(self._spaces, xs, ys):
193
- space.axpy(a, x, y)
194
-
195
- def __copy(self, xs: List[Any]) -> List[Any]:
196
- return [space.copy(x) for space, x in zip(self._spaces, xs)]
197
-
198
203
  def _subspace_projection_mapping(self, i: int, xs: List[Any]) -> Any:
199
204
  return xs[i]
200
205
 
@@ -213,14 +218,23 @@ class BlockStructure(ABC):
213
218
 
214
219
  @property
215
220
  def row_dim(self) -> int:
221
+ """
222
+ Returns the number of rows in the block structure.
223
+ """
216
224
  return self._row_dim
217
225
 
218
226
  @property
219
227
  def col_dim(self) -> int:
228
+ """
229
+ Returns the number of columns in the block structure.
230
+ """
220
231
  return self._col_dim
221
232
 
222
233
  @abstractmethod
223
234
  def block(self, i: int, j: int) -> "LinearOperator":
235
+ """
236
+ Returns the operator in the (i, j)-th sub-block.
237
+ """
224
238
  pass
225
239
 
226
240
  def _check_block_indices(self, i: int, j: int) -> None:
@@ -232,8 +246,12 @@ class BlockStructure(ABC):
232
246
 
233
247
  class BlockLinearOperator(LinearOperator, BlockStructure):
234
248
  """
235
- A linear operator between direct sums of Hilbert spaces, defined by a
236
- matrix of sub-operators (blocks).
249
+ A linear operator between direct sum spaces, defined by a matrix of sub-operators.
250
+
251
+ This operator acts like a matrix where each entry is itself a `LinearOperator`.
252
+ It maps a list of input vectors `[x_1, x_2, ...]` to a list of output
253
+ vectors `[y_1, y_2, ...]`. The constructor checks for dimensional
254
+ consistency between the blocks.
237
255
  """
238
256
 
239
257
  def __init__(self, blocks: List[List[LinearOperator]]) -> None:
@@ -309,8 +327,12 @@ class BlockLinearOperator(LinearOperator, BlockStructure):
309
327
 
310
328
  class ColumnLinearOperator(LinearOperator, BlockStructure):
311
329
  """
312
- An operator that maps a single Hilbert space to a direct sum of spaces.
313
- It is represented as a column of operators.
330
+ An operator that maps from a single space to a direct sum space.
331
+
332
+ It can be visualized as a column vector of operators, `[A_1, A_2, ...]^T`.
333
+ It takes a single input vector `x` and produces a list of output vectors
334
+ `[A_1(x), A_2(x), ...]`. This is often used to represent a joint forward
335
+ operator in an inverse problem.
314
336
  """
315
337
 
316
338
  def __init__(self, operators: List[LinearOperator]) -> None:
@@ -360,8 +382,12 @@ class ColumnLinearOperator(LinearOperator, BlockStructure):
360
382
 
361
383
  class RowLinearOperator(LinearOperator, BlockStructure):
362
384
  """
363
- An operator that maps a direct sum of spaces to a single Hilbert space,
364
- structured as a row of operator blocks.
385
+ An operator that maps from a direct sum space to a single space.
386
+
387
+ It can be visualized as a row vector of operators, `[A_1, A_2, ...]`.
388
+ It takes a list of input vectors `[x_1, x_2, ...]` and produces a single
389
+ output vector `y = A_1(x_1) + A_2(x_2) + ...`. The adjoint of a
390
+ `ColumnLinearOperator` is a `RowLinearOperator`.
365
391
  """
366
392
 
367
393
  def __init__(self, operators: List[LinearOperator]) -> None:
@@ -1,10 +1,20 @@
1
1
  """
2
- Module for defining forward problem classes.
3
-
4
- This module provides classes to represent inverse problem formulations, which
5
- relate unknown model parameters to observed data through a forward operator.
6
- It handles both deterministic (error-free) and statistical (with data errors)
7
- scenarios.
2
+ Defines the mathematical structure of a forward problem.
3
+
4
+ This module provides classes that encapsulate the core components of an
5
+ inverse problem. A forward problem describes the physical or mathematical
6
+ process that maps a set of unknown model parameters `u` to a set of observable
7
+ data `d`.
8
+
9
+ The module handles both the deterministic relationship `d = A(u)` and the more
10
+ realistic statistical model `d = A(u) + e`, where `e` represents random noise.
11
+
12
+ Key Classes
13
+ -----------
14
+ - `ForwardProblem`: A general class representing the link between a model
15
+ space and a data space via a forward operator, with an optional data error.
16
+ - `LinearForwardProblem`: A specialization for linear problems where the
17
+ forward operator is a `LinearOperator`.
8
18
  """
9
19
 
10
20
  from __future__ import annotations
@@ -18,7 +28,7 @@ from .direct_sum import ColumnLinearOperator
18
28
  # This block only runs for type checkers, not at runtime, to prevent
19
29
  # circular import errors while still allowing type hints.
20
30
  if TYPE_CHECKING:
21
- from .hilbert_space import HilbertSpace, T_vec
31
+ from .hilbert_space import HilbertSpace, Vector
22
32
  from .operators import LinearOperator
23
33
 
24
34
 
@@ -33,7 +43,7 @@ class ForwardProblem:
33
43
 
34
44
  def __init__(
35
45
  self,
36
- forward_operator: "LinearOperator",
46
+ forward_operator: LinearOperator,
37
47
  /,
38
48
  *,
39
49
  data_error_measure: Optional["GaussianMeasure"] = None,
@@ -47,7 +57,7 @@ class ForwardProblem:
47
57
  from which data errors are assumed to be drawn. If None, the
48
58
  data is considered to be error-free.
49
59
  """
50
- self._forward_operator: "LinearOperator" = forward_operator
60
+ self._forward_operator: LinearOperator = forward_operator
51
61
  self._data_error_measure: Optional["GaussianMeasure"] = data_error_measure
52
62
  if self.data_error_measure_set:
53
63
  if self.data_space != data_error_measure.domain:
@@ -56,7 +66,7 @@ class ForwardProblem:
56
66
  )
57
67
 
58
68
  @property
59
- def forward_operator(self) -> "LinearOperator":
69
+ def forward_operator(self) -> LinearOperator:
60
70
  """The forward operator, mapping from model to data space."""
61
71
  return self._forward_operator
62
72
 
@@ -88,9 +98,7 @@ class LinearForwardProblem(ForwardProblem):
88
98
  Represents a linear forward problem of the form `d = A(u) + e`.
89
99
 
90
100
  Here, `d` is the data, `A` is the linear forward operator, `u` is the model,
91
- and `e` is a random error drawn from a Gaussian distribution. This class
92
- provides methods for statistical analysis, such as generating synthetic data
93
- and performing chi-squared tests.
101
+ and `e` is a random error drawn from a Gaussian distribution.
94
102
  """
95
103
 
96
104
  @staticmethod
@@ -100,8 +108,9 @@ class LinearForwardProblem(ForwardProblem):
100
108
  """
101
109
  Forms a joint forward problem from a list of separate problems.
102
110
 
103
- This is useful when a single underlying model is observed through
104
- multiple, independent measurement systems.
111
+ This is a powerful tool for joint inversions, where a single underlying
112
+ model is observed through multiple, independent measurement systems
113
+ (e.g., different types of geophysical surveys).
105
114
 
106
115
  Args:
107
116
  forward_problems: A list of `LinearForwardProblem` instances that
@@ -110,10 +119,6 @@ class LinearForwardProblem(ForwardProblem):
110
119
  Returns:
111
120
  A single `LinearForwardProblem` where the data space is the direct
112
121
  sum of the individual data spaces.
113
-
114
- Raises:
115
- ValueError: If the list of problems is empty or if they do not all
116
- share the same model space.
117
122
  """
118
123
  if not forward_problems:
119
124
  raise ValueError("Cannot form a direct sum from an empty list.")
@@ -139,7 +144,7 @@ class LinearForwardProblem(ForwardProblem):
139
144
  joint_forward_operator, data_error_measure=data_error_measure
140
145
  )
141
146
 
142
- def data_measure(self, model: "T_vec") -> "GaussianMeasure":
147
+ def data_measure(self, model: "Vector") -> "GaussianMeasure":
143
148
  """
144
149
  Returns the Gaussian measure for the data, given a specific model.
145
150
 
@@ -160,7 +165,7 @@ class LinearForwardProblem(ForwardProblem):
160
165
  translation=self.forward_operator(model)
161
166
  )
162
167
 
163
- def synthetic_data(self, model: "T_vec") -> "T_vec":
168
+ def synthetic_data(self, model: "Vector") -> "Vector":
164
169
  """
165
170
  Generates a synthetic data vector for a given model.
166
171
 
@@ -177,7 +182,7 @@ class LinearForwardProblem(ForwardProblem):
177
182
 
178
183
  def synthetic_model_and_data(
179
184
  self, prior: "GaussianMeasure"
180
- ) -> Tuple["T_vec", "T_vec"]:
185
+ ) -> Tuple["Vector", "Vector"]:
181
186
  """
182
187
  Generates a random model and corresponding synthetic data.
183
188
 
@@ -211,24 +216,20 @@ class LinearForwardProblem(ForwardProblem):
211
216
  """
212
217
  return chi2.ppf(significance_level, self.data_space.dim)
213
218
 
214
- def chi_squared(self, model: "T_vec", data: "T_vec") -> float:
219
+ def chi_squared(self, model: "Vector", data: "Vector") -> float:
215
220
  """
216
221
  Calculates the chi-squared statistic for a given model and data.
217
222
 
218
- If a data error measure with an inverse covariance is defined, this is
219
- the weighted misfit: `(d - A(u))^T * C_e^-1 * (d - A(u))`. Otherwise,
220
- it is the squared norm of the data residual: `||d - A(u)||^2`.
221
-
223
+ This measures the misfit between the predicted and observed data.
224
+ - If a data error measure with an inverse covariance `C_e^-1` is defined,
225
+ this is the weighted misfit: `(d - A(u))^T * C_e^-1 * (d - A(u))`.
226
+ - Otherwise, it is the squared L2 norm of the data residual: `||d - A(u)||^2`.
222
227
  Args:
223
228
  model: A vector from the model space.
224
229
  data: An observed data vector from the data space.
225
230
 
226
231
  Returns:
227
232
  The chi-squared statistic.
228
-
229
- Raises:
230
- AttributeError: If a data error measure is set but its inverse
231
- covariance (precision operator) is not available.
232
233
  """
233
234
  residual = self.data_space.subtract(data, self.forward_operator(model))
234
235
 
@@ -246,7 +247,7 @@ class LinearForwardProblem(ForwardProblem):
246
247
  return self.data_space.squared_norm(residual)
247
248
 
248
249
  def chi_squared_test(
249
- self, significance_level: float, model: "T_vec", data: "T_vec"
250
+ self, significance_level: float, model: "Vector", data: "Vector"
250
251
  ) -> bool:
251
252
  """
252
253
  Performs a chi-squared test for goodness of fit.