pygeoinf 1.3.5__tar.gz → 1.3.6__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.3.5 → pygeoinf-1.3.6}/PKG-INFO +1 -1
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/pygeoinf/linear_bayesian.py +53 -111
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/pygeoinf/subspaces.py +132 -40
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/pygeoinf/symmetric_space/sh_tools.py +18 -6
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/pygeoinf/symmetric_space/sphere.py +46 -58
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/pyproject.toml +1 -1
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/LICENSE +0 -0
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/README.md +0 -0
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/pygeoinf/__init__.py +0 -0
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/pygeoinf/auxiliary.py +0 -0
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/pygeoinf/backus_gilbert.py +0 -0
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/pygeoinf/checks/__init__.py +0 -0
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/pygeoinf/checks/hilbert_space.py +0 -0
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/pygeoinf/checks/linear_operators.py +0 -0
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/pygeoinf/checks/nonlinear_operators.py +0 -0
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/pygeoinf/direct_sum.py +0 -0
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/pygeoinf/forward_problem.py +0 -0
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/pygeoinf/gaussian_measure.py +0 -0
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/pygeoinf/hilbert_space.py +0 -0
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/pygeoinf/inversion.py +0 -0
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/pygeoinf/linear_forms.py +0 -0
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/pygeoinf/linear_operators.py +0 -0
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/pygeoinf/linear_optimisation.py +0 -0
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/pygeoinf/linear_solvers.py +0 -0
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/pygeoinf/nonlinear_forms.py +0 -0
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/pygeoinf/nonlinear_operators.py +0 -0
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/pygeoinf/nonlinear_optimisation.py +0 -0
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/pygeoinf/parallel.py +0 -0
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/pygeoinf/plot.py +0 -0
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/pygeoinf/random_matrix.py +0 -0
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/pygeoinf/symmetric_space/__init__.py +0 -0
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/pygeoinf/symmetric_space/circle.py +0 -0
- {pygeoinf-1.3.5 → pygeoinf-1.3.6}/pygeoinf/symmetric_space/symmetric_space.py +0 -0
|
@@ -9,10 +9,8 @@ Key Classes
|
|
|
9
9
|
-----------
|
|
10
10
|
- `LinearBayesianInversion`: Computes the posterior Gaussian measure `p(u|d)`
|
|
11
11
|
for the model `u` given observed data `d`.
|
|
12
|
-
- `LinearBayesianInference`: Extends the framework to compute the posterior
|
|
13
|
-
distribution for a derived property of the model.
|
|
14
12
|
- `ConstrainedLinearBayesianInversion`: Solves the inverse problem subject to
|
|
15
|
-
|
|
13
|
+
an affine constraint `u in A`.
|
|
16
14
|
"""
|
|
17
15
|
|
|
18
16
|
from __future__ import annotations
|
|
@@ -41,6 +39,11 @@ class LinearBayesianInversion(LinearInversion):
|
|
|
41
39
|
model_prior_measure: GaussianMeasure,
|
|
42
40
|
/,
|
|
43
41
|
) -> None:
|
|
42
|
+
"""
|
|
43
|
+
Args:
|
|
44
|
+
forward_problem: The forward problem linking the model to the data.
|
|
45
|
+
model_prior_measure: The prior Gaussian measure on the model space.
|
|
46
|
+
"""
|
|
44
47
|
super().__init__(forward_problem)
|
|
45
48
|
self._model_prior_measure: GaussianMeasure = model_prior_measure
|
|
46
49
|
|
|
@@ -52,16 +55,7 @@ class LinearBayesianInversion(LinearInversion):
|
|
|
52
55
|
@property
|
|
53
56
|
def normal_operator(self) -> LinearOperator:
|
|
54
57
|
"""
|
|
55
|
-
Returns the Bayesian
|
|
56
|
-
|
|
57
|
-
N = A Q A* + R
|
|
58
|
-
|
|
59
|
-
with A the forward operator (with A* its adjoint), Q the model
|
|
60
|
-
prior covariance, and R the data error covariance. For error-free
|
|
61
|
-
problems this operator is reduced to:
|
|
62
|
-
|
|
63
|
-
N = A Q A*
|
|
64
|
-
|
|
58
|
+
Returns the Bayesian Normal operator: N = A Q A* + R.
|
|
65
59
|
"""
|
|
66
60
|
forward_operator = self.forward_problem.forward_operator
|
|
67
61
|
model_prior_covariance = self.model_prior_measure.covariance
|
|
@@ -80,23 +74,10 @@ class LinearBayesianInversion(LinearInversion):
|
|
|
80
74
|
/,
|
|
81
75
|
*,
|
|
82
76
|
preconditioner: Optional[LinearOperator] = None,
|
|
83
|
-
):
|
|
77
|
+
) -> LinearOperator:
|
|
84
78
|
"""
|
|
85
|
-
Returns the Kalman gain operator
|
|
86
|
-
|
|
87
|
-
K = Q A* Ni
|
|
88
|
-
|
|
89
|
-
where Q is the model prior covariance, A the forward operator
|
|
90
|
-
(with adjoint A*), and Ni is the inverse of the normal operator.
|
|
91
|
-
|
|
92
|
-
Args:
|
|
93
|
-
solver: A linear solver for inverting the normal operator.
|
|
94
|
-
preconditioner: An optional preconditioner for.
|
|
95
|
-
|
|
96
|
-
Returns:
|
|
97
|
-
A LinearOperator for the Kalman gain.
|
|
79
|
+
Returns the Kalman gain operator K = Q A* N^-1.
|
|
98
80
|
"""
|
|
99
|
-
|
|
100
81
|
forward_operator = self.forward_problem.forward_operator
|
|
101
82
|
model_prior_covariance = self.model_prior_measure.covariance
|
|
102
83
|
normal_operator = self.normal_operator
|
|
@@ -121,37 +102,45 @@ class LinearBayesianInversion(LinearInversion):
|
|
|
121
102
|
preconditioner: Optional[LinearOperator] = None,
|
|
122
103
|
) -> GaussianMeasure:
|
|
123
104
|
"""
|
|
124
|
-
Returns the posterior Gaussian measure
|
|
105
|
+
Returns the posterior Gaussian measure p(u|d).
|
|
125
106
|
|
|
126
107
|
Args:
|
|
127
108
|
data: The observed data vector.
|
|
128
|
-
solver: A linear solver for inverting the normal operator
|
|
129
|
-
preconditioner: An optional preconditioner
|
|
109
|
+
solver: A linear solver for inverting the normal operator.
|
|
110
|
+
preconditioner: An optional preconditioner.
|
|
130
111
|
"""
|
|
131
112
|
data_space = self.data_space
|
|
132
113
|
model_space = self.model_space
|
|
133
114
|
forward_operator = self.forward_problem.forward_operator
|
|
134
115
|
model_prior_covariance = self.model_prior_measure.covariance
|
|
135
116
|
|
|
117
|
+
# 1. Compute Kalman Gain
|
|
136
118
|
kalman_gain = self.kalman_operator(solver, preconditioner=preconditioner)
|
|
137
119
|
|
|
138
|
-
#
|
|
120
|
+
# 2. Compute Posterior Mean
|
|
121
|
+
# Shift data: d - A(mu_u)
|
|
139
122
|
shifted_data = data_space.subtract(
|
|
140
123
|
data, forward_operator(self.model_prior_measure.expectation)
|
|
141
124
|
)
|
|
125
|
+
|
|
126
|
+
# Shift for noise mean: d - A(mu_u) - mu_e
|
|
142
127
|
if self.forward_problem.data_error_measure_set:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
128
|
+
error_expectation = self.forward_problem.data_error_measure.expectation
|
|
129
|
+
shifted_data = data_space.subtract(shifted_data, error_expectation)
|
|
130
|
+
else:
|
|
131
|
+
error_expectation = data_space.zero
|
|
132
|
+
|
|
146
133
|
mean_update = kalman_gain(shifted_data)
|
|
147
134
|
expectation = model_space.add(self.model_prior_measure.expectation, mean_update)
|
|
148
135
|
|
|
149
|
-
#
|
|
136
|
+
# 3. Compute Posterior Covariance (Implicitly)
|
|
137
|
+
# C_post = C_u - K A C_u
|
|
150
138
|
covariance = model_prior_covariance - (
|
|
151
139
|
kalman_gain @ forward_operator @ model_prior_covariance
|
|
152
140
|
)
|
|
153
141
|
|
|
154
|
-
#
|
|
142
|
+
# 4. Set up Posterior Sampling
|
|
143
|
+
# Logic: Can sample if prior is samplable AND (noise is absent OR samplable)
|
|
155
144
|
can_sample_prior = self.model_prior_measure.sample_set
|
|
156
145
|
can_sample_noise = (
|
|
157
146
|
not self.forward_problem.data_error_measure_set
|
|
@@ -160,19 +149,21 @@ class LinearBayesianInversion(LinearInversion):
|
|
|
160
149
|
|
|
161
150
|
if can_sample_prior and can_sample_noise:
|
|
162
151
|
|
|
163
|
-
if self.forward_problem.data_error_measure_set:
|
|
164
|
-
error_expectation = self.forward_problem.data_error_measure.expectation
|
|
165
|
-
|
|
166
152
|
def sample():
|
|
153
|
+
# a. Sample Prior
|
|
167
154
|
model_sample = self.model_prior_measure.sample()
|
|
155
|
+
|
|
156
|
+
# b. Calculate Residual
|
|
168
157
|
prediction = forward_operator(model_sample)
|
|
169
158
|
data_residual = data_space.subtract(data, prediction)
|
|
170
159
|
|
|
160
|
+
# c. Perturb Residual
|
|
171
161
|
if self.forward_problem.data_error_measure_set:
|
|
172
162
|
noise_raw = self.forward_problem.data_error_measure.sample()
|
|
173
163
|
epsilon = data_space.subtract(noise_raw, error_expectation)
|
|
174
164
|
data_space.axpy(1.0, epsilon, data_residual)
|
|
175
165
|
|
|
166
|
+
# d. Update
|
|
176
167
|
correction = kalman_gain(data_residual)
|
|
177
168
|
return model_space.add(model_sample, correction)
|
|
178
169
|
|
|
@@ -185,11 +176,13 @@ class LinearBayesianInversion(LinearInversion):
|
|
|
185
176
|
|
|
186
177
|
class ConstrainedLinearBayesianInversion(LinearInversion):
|
|
187
178
|
"""
|
|
188
|
-
Solves a linear inverse problem
|
|
189
|
-
affine subspace constraint `u in A`.
|
|
179
|
+
Solves a linear inverse problem subject to an affine subspace constraint.
|
|
190
180
|
|
|
191
|
-
This
|
|
192
|
-
|
|
181
|
+
This class enforces the constraint `u in A` using either:
|
|
182
|
+
1. Bayesian Conditioning (Default): p(u | d, u in A).
|
|
183
|
+
If A is defined geometrically (no explicit equation), an implicit
|
|
184
|
+
operator (I-P) is used, which requires a robust solver in the subspace.
|
|
185
|
+
2. Geometric Projection: Projects the unconstrained posterior onto A.
|
|
193
186
|
"""
|
|
194
187
|
|
|
195
188
|
def __init__(
|
|
@@ -205,8 +198,8 @@ class ConstrainedLinearBayesianInversion(LinearInversion):
|
|
|
205
198
|
Args:
|
|
206
199
|
forward_problem: The forward problem.
|
|
207
200
|
model_prior_measure: The unconstrained prior Gaussian measure.
|
|
208
|
-
constraint: The affine subspace A
|
|
209
|
-
geometric: If True, uses orthogonal projection
|
|
201
|
+
constraint: The affine subspace A.
|
|
202
|
+
geometric: If True, uses orthogonal projection (Euclidean metric).
|
|
210
203
|
If False (default), uses Bayesian conditioning.
|
|
211
204
|
"""
|
|
212
205
|
super().__init__(forward_problem)
|
|
@@ -214,88 +207,37 @@ class ConstrainedLinearBayesianInversion(LinearInversion):
|
|
|
214
207
|
self._constraint = constraint
|
|
215
208
|
self._geometric = geometric
|
|
216
209
|
|
|
217
|
-
|
|
218
|
-
raise ValueError(
|
|
219
|
-
"For Bayesian inversion, the subspace must be defined by a linear "
|
|
220
|
-
"equation (constraint operator). Use AffineSubspace.from_linear_equation."
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
def conditioned_prior_measure(
|
|
224
|
-
self,
|
|
225
|
-
solver: LinearSolver,
|
|
226
|
-
preconditioner: Optional[LinearOperator] = None,
|
|
227
|
-
) -> GaussianMeasure:
|
|
210
|
+
def conditioned_prior_measure(self) -> GaussianMeasure:
|
|
228
211
|
"""
|
|
229
|
-
Computes the prior measure conditioned on the constraint
|
|
230
|
-
|
|
231
|
-
Args:
|
|
232
|
-
solver: Linear solver used to invert the normal operator, BQB*.
|
|
233
|
-
preconditioner: Optional preconditioner for the constraint solver.
|
|
212
|
+
Computes the prior measure conditioned on the constraint.
|
|
234
213
|
"""
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
if self._geometric:
|
|
240
|
-
# --- Geometric Approach (Affine Mapping) ---
|
|
241
|
-
# Map: u -> P u + v
|
|
242
|
-
# P = I - B* (B B*)^-1 B
|
|
243
|
-
# v = B* (B B*)^-1 w
|
|
244
|
-
|
|
245
|
-
gram_operator = constraint_op @ constraint_op.adjoint
|
|
246
|
-
|
|
247
|
-
if isinstance(solver, IterativeLinearSolver):
|
|
248
|
-
inv_gram_operator = solver(gram_operator, preconditioner=preconditioner)
|
|
249
|
-
else:
|
|
250
|
-
inv_gram_operator = solver(gram_operator)
|
|
251
|
-
|
|
252
|
-
pseudo_inverse = constraint_op.adjoint @ inv_gram_operator
|
|
253
|
-
identity = self._unconstrained_prior.domain.identity_operator()
|
|
254
|
-
projector = identity - pseudo_inverse @ constraint_op
|
|
255
|
-
translation = pseudo_inverse(constraint_val)
|
|
256
|
-
|
|
257
|
-
return self._unconstrained_prior.affine_mapping(
|
|
258
|
-
operator=projector, translation=translation
|
|
259
|
-
)
|
|
260
|
-
|
|
261
|
-
else:
|
|
262
|
-
# --- Bayesian Approach (Statistical Conditioning) ---
|
|
263
|
-
# Treat the constraint as a noiseless observation: w = B(u)
|
|
264
|
-
|
|
265
|
-
constraint_problem = LinearForwardProblem(constraint_op)
|
|
266
|
-
constraint_inversion = LinearBayesianInversion(
|
|
267
|
-
constraint_problem, self._unconstrained_prior
|
|
268
|
-
)
|
|
269
|
-
|
|
270
|
-
return constraint_inversion.model_posterior_measure(
|
|
271
|
-
constraint_val, solver, preconditioner=preconditioner
|
|
272
|
-
)
|
|
214
|
+
return self._constraint.condition_gaussian_measure(
|
|
215
|
+
self._unconstrained_prior, geometric=self._geometric
|
|
216
|
+
)
|
|
273
217
|
|
|
274
218
|
def model_posterior_measure(
|
|
275
219
|
self,
|
|
276
220
|
data: Vector,
|
|
277
221
|
solver: LinearSolver,
|
|
278
|
-
|
|
222
|
+
/,
|
|
279
223
|
*,
|
|
280
224
|
preconditioner: Optional[LinearOperator] = None,
|
|
281
|
-
constraint_preconditioner: Optional[LinearOperator] = None,
|
|
282
225
|
) -> GaussianMeasure:
|
|
283
226
|
"""
|
|
284
|
-
Returns the posterior Gaussian measure
|
|
227
|
+
Returns the posterior Gaussian measure p(u | d, u in A).
|
|
285
228
|
|
|
286
229
|
Args:
|
|
287
230
|
data: Observed data vector.
|
|
288
231
|
solver: Solver for the data update (inverts A C_cond A* + Ce).
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
232
|
+
preconditioner: Preconditioner for the data update.
|
|
233
|
+
|
|
234
|
+
Note: The solver for the constraint update is managed internally by
|
|
235
|
+
the AffineSubspace object passed at initialization.
|
|
292
236
|
"""
|
|
293
|
-
# 1. Condition Prior
|
|
294
|
-
cond_prior = self.conditioned_prior_measure(
|
|
295
|
-
constraint_solver, preconditioner=constraint_preconditioner
|
|
296
|
-
)
|
|
237
|
+
# 1. Condition Prior
|
|
238
|
+
cond_prior = self.conditioned_prior_measure()
|
|
297
239
|
|
|
298
|
-
# 2. Solve Bayesian Inverse Problem
|
|
240
|
+
# 2. Solve Bayesian Inverse Problem with the new prior
|
|
299
241
|
bayes_inv = LinearBayesianInversion(self.forward_problem, cond_prior)
|
|
300
242
|
|
|
301
243
|
return bayes_inv.model_posterior_measure(
|
|
@@ -3,29 +3,25 @@ Defines classes for representing affine and linear subspaces.
|
|
|
3
3
|
|
|
4
4
|
The primary abstraction is the `AffineSubspace`, which represents a subset of
|
|
5
5
|
a Hilbert space defined by a translation and a closed linear tangent space.
|
|
6
|
-
`LinearSubspace` is a specialization where the translation is zero.
|
|
7
6
|
"""
|
|
8
7
|
|
|
9
8
|
from __future__ import annotations
|
|
10
9
|
from typing import List, Optional, Any, Callable, TYPE_CHECKING
|
|
11
10
|
import numpy as np
|
|
11
|
+
import warnings
|
|
12
12
|
|
|
13
13
|
from .linear_operators import LinearOperator
|
|
14
14
|
from .hilbert_space import HilbertSpace, Vector, EuclideanSpace
|
|
15
15
|
from .linear_solvers import LinearSolver, CholeskySolver, IterativeLinearSolver
|
|
16
16
|
|
|
17
17
|
if TYPE_CHECKING:
|
|
18
|
-
|
|
19
|
-
pass
|
|
18
|
+
from .gaussian_measure import GaussianMeasure
|
|
20
19
|
|
|
21
20
|
|
|
22
21
|
class OrthogonalProjector(LinearOperator):
|
|
23
22
|
"""
|
|
24
23
|
Internal engine for subspace projections.
|
|
25
|
-
|
|
26
24
|
Represents an orthogonal projection operator P = P* = P^2.
|
|
27
|
-
While this class can be used directly, it is generally recommended to use
|
|
28
|
-
`AffineSubspace` or `LinearSubspace` for high-level problem definitions.
|
|
29
25
|
"""
|
|
30
26
|
|
|
31
27
|
def __init__(
|
|
@@ -52,9 +48,8 @@ class OrthogonalProjector(LinearOperator):
|
|
|
52
48
|
basis_vectors: List[Vector],
|
|
53
49
|
orthonormalize: bool = True,
|
|
54
50
|
) -> OrthogonalProjector:
|
|
55
|
-
"""Constructs P
|
|
51
|
+
"""Constructs a projector P onto the span of the provided basis vectors."""
|
|
56
52
|
if not basis_vectors:
|
|
57
|
-
# Return zero operator if basis is empty
|
|
58
53
|
return domain.zero_operator(domain)
|
|
59
54
|
|
|
60
55
|
if orthonormalize:
|
|
@@ -78,7 +73,12 @@ class AffineSubspace:
|
|
|
78
73
|
translation: Optional[Vector] = None,
|
|
79
74
|
constraint_operator: Optional[LinearOperator] = None,
|
|
80
75
|
constraint_value: Optional[Vector] = None,
|
|
76
|
+
solver: Optional[LinearSolver] = None,
|
|
77
|
+
preconditioner: Optional[LinearOperator] = None,
|
|
81
78
|
) -> None:
|
|
79
|
+
"""
|
|
80
|
+
Initializes the AffineSubspace.
|
|
81
|
+
"""
|
|
82
82
|
self._projector = projector
|
|
83
83
|
|
|
84
84
|
if translation is None:
|
|
@@ -91,6 +91,15 @@ class AffineSubspace:
|
|
|
91
91
|
self._constraint_operator = constraint_operator
|
|
92
92
|
self._constraint_value = constraint_value
|
|
93
93
|
|
|
94
|
+
# Logic: If explicit equation exists, default to Cholesky.
|
|
95
|
+
# If implicit, leave None (requires robust solver from user).
|
|
96
|
+
if self._constraint_operator is not None and solver is None:
|
|
97
|
+
self._solver = CholeskySolver(galerkin=True)
|
|
98
|
+
else:
|
|
99
|
+
self._solver = solver
|
|
100
|
+
|
|
101
|
+
self._preconditioner = preconditioner
|
|
102
|
+
|
|
94
103
|
@property
|
|
95
104
|
def domain(self) -> HilbertSpace:
|
|
96
105
|
return self._projector.domain
|
|
@@ -108,27 +117,39 @@ class AffineSubspace:
|
|
|
108
117
|
return LinearSubspace(self._projector)
|
|
109
118
|
|
|
110
119
|
@property
|
|
111
|
-
def
|
|
120
|
+
def has_explicit_equation(self) -> bool:
|
|
121
|
+
"""True if defined by B(u)=w, False if defined only by geometry."""
|
|
112
122
|
return self._constraint_operator is not None
|
|
113
123
|
|
|
114
124
|
@property
|
|
115
125
|
def constraint_operator(self) -> LinearOperator:
|
|
126
|
+
"""
|
|
127
|
+
Returns B for {u | B(u)=w}.
|
|
128
|
+
Falls back to (I - P) if no explicit operator exists.
|
|
129
|
+
"""
|
|
116
130
|
if self._constraint_operator is None:
|
|
117
|
-
|
|
131
|
+
return self._projector.complement
|
|
118
132
|
return self._constraint_operator
|
|
119
133
|
|
|
120
134
|
@property
|
|
121
135
|
def constraint_value(self) -> Vector:
|
|
136
|
+
"""
|
|
137
|
+
Returns w for {u | B(u)=w}.
|
|
138
|
+
Falls back to (I - P)x0 if no explicit operator exists.
|
|
139
|
+
"""
|
|
122
140
|
if self._constraint_value is None:
|
|
123
|
-
|
|
141
|
+
complement = self._projector.complement
|
|
142
|
+
return complement(self._translation)
|
|
124
143
|
return self._constraint_value
|
|
125
144
|
|
|
126
145
|
def project(self, x: Vector) -> Vector:
|
|
146
|
+
"""Orthogonally projects x onto the affine subspace."""
|
|
127
147
|
diff = self.domain.subtract(x, self.translation)
|
|
128
148
|
proj_diff = self.projector(diff)
|
|
129
149
|
return self.domain.add(self.translation, proj_diff)
|
|
130
150
|
|
|
131
151
|
def is_element(self, x: Vector, rtol: float = 1e-6) -> bool:
|
|
152
|
+
"""Returns True if x lies in the subspace."""
|
|
132
153
|
proj = self.project(x)
|
|
133
154
|
diff = self.domain.subtract(x, proj)
|
|
134
155
|
norm_diff = self.domain.norm(diff)
|
|
@@ -136,6 +157,46 @@ class AffineSubspace:
|
|
|
136
157
|
scale = norm_x if norm_x > 1e-12 else 1.0
|
|
137
158
|
return norm_diff <= rtol * scale
|
|
138
159
|
|
|
160
|
+
def condition_gaussian_measure(
|
|
161
|
+
self, prior: GaussianMeasure, geometric: bool = False
|
|
162
|
+
) -> GaussianMeasure:
|
|
163
|
+
"""
|
|
164
|
+
Conditions a Gaussian measure on this subspace.
|
|
165
|
+
"""
|
|
166
|
+
if geometric:
|
|
167
|
+
# Geometric Projection: u -> P(u - x0) + x0
|
|
168
|
+
# Affine Map: u -> P(u) + (I-P)x0
|
|
169
|
+
shift = self.domain.subtract(
|
|
170
|
+
self.translation, self.projector(self.translation)
|
|
171
|
+
)
|
|
172
|
+
return prior.affine_mapping(operator=self.projector, translation=shift)
|
|
173
|
+
|
|
174
|
+
else:
|
|
175
|
+
# Bayesian Conditioning: u | B(u)=w
|
|
176
|
+
|
|
177
|
+
# Check for singular implicit operator usage
|
|
178
|
+
if not self.has_explicit_equation and self._solver is None:
|
|
179
|
+
raise ValueError(
|
|
180
|
+
"This subspace defines the constraint implicitly as (I-P)u = (I-P)x0. "
|
|
181
|
+
"The operator (I-P) is singular. You must provide a solver "
|
|
182
|
+
"capable of handling singular systems (e.g. MinRes) to the "
|
|
183
|
+
"AffineSubspace constructor."
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# Local imports
|
|
187
|
+
from .forward_problem import LinearForwardProblem
|
|
188
|
+
from .linear_bayesian import LinearBayesianInversion
|
|
189
|
+
|
|
190
|
+
solver = self._solver
|
|
191
|
+
preconditioner = self._preconditioner
|
|
192
|
+
|
|
193
|
+
constraint_problem = LinearForwardProblem(self.constraint_operator)
|
|
194
|
+
constraint_inversion = LinearBayesianInversion(constraint_problem, prior)
|
|
195
|
+
|
|
196
|
+
return constraint_inversion.model_posterior_measure(
|
|
197
|
+
self.constraint_value, solver, preconditioner=preconditioner
|
|
198
|
+
)
|
|
199
|
+
|
|
139
200
|
@classmethod
|
|
140
201
|
def from_linear_equation(
|
|
141
202
|
cls,
|
|
@@ -144,7 +205,7 @@ class AffineSubspace:
|
|
|
144
205
|
solver: Optional[LinearSolver] = None,
|
|
145
206
|
preconditioner: Optional[LinearOperator] = None,
|
|
146
207
|
) -> AffineSubspace:
|
|
147
|
-
"""Constructs
|
|
208
|
+
"""Constructs subspace from B(u)=w."""
|
|
148
209
|
domain = operator.domain
|
|
149
210
|
G = operator @ operator.adjoint
|
|
150
211
|
|
|
@@ -166,7 +227,12 @@ class AffineSubspace:
|
|
|
166
227
|
projector = OrthogonalProjector(domain, mapping, complement_projector=P_perp_op)
|
|
167
228
|
|
|
168
229
|
return cls(
|
|
169
|
-
projector,
|
|
230
|
+
projector,
|
|
231
|
+
translation,
|
|
232
|
+
constraint_operator=operator,
|
|
233
|
+
constraint_value=value,
|
|
234
|
+
solver=solver,
|
|
235
|
+
preconditioner=preconditioner,
|
|
170
236
|
)
|
|
171
237
|
|
|
172
238
|
@classmethod
|
|
@@ -176,18 +242,38 @@ class AffineSubspace:
|
|
|
176
242
|
basis_vectors: List[Vector],
|
|
177
243
|
translation: Optional[Vector] = None,
|
|
178
244
|
orthonormalize: bool = True,
|
|
245
|
+
solver: Optional[LinearSolver] = None,
|
|
246
|
+
preconditioner: Optional[LinearOperator] = None,
|
|
179
247
|
) -> AffineSubspace:
|
|
180
248
|
"""
|
|
181
|
-
Constructs
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
249
|
+
Constructs an affine subspace from a translation and a basis for the tangent space.
|
|
250
|
+
|
|
251
|
+
This method defines the subspace geometrically. The constraint is implicit:
|
|
252
|
+
(I - P)u = (I - P)x0.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
domain: The Hilbert space.
|
|
256
|
+
basis_vectors: Basis vectors for the tangent space V.
|
|
257
|
+
translation: A point x0 in the subspace.
|
|
258
|
+
orthonormalize: If True, orthonormalizes the basis.
|
|
259
|
+
solver: A linear solver capable of handling the singular operator (I-P).
|
|
260
|
+
Required if you intend to use this subspace for Bayesian conditioning.
|
|
261
|
+
preconditioner: Optional preconditioner for the solver.
|
|
186
262
|
"""
|
|
263
|
+
if solver is None:
|
|
264
|
+
warnings.warn(
|
|
265
|
+
"Constructing a subspace from a tangent basis without a solver. "
|
|
266
|
+
"This defines an implicit constraint with a singular operator. "
|
|
267
|
+
"Bayesian conditioning will fail; geometric projection remains available.",
|
|
268
|
+
UserWarning,
|
|
269
|
+
stacklevel=2,
|
|
270
|
+
)
|
|
271
|
+
|
|
187
272
|
projector = OrthogonalProjector.from_basis(
|
|
188
273
|
domain, basis_vectors, orthonormalize=orthonormalize
|
|
189
274
|
)
|
|
190
|
-
|
|
275
|
+
|
|
276
|
+
return cls(projector, translation, solver=solver, preconditioner=preconditioner)
|
|
191
277
|
|
|
192
278
|
@classmethod
|
|
193
279
|
def from_complement_basis(
|
|
@@ -198,24 +284,18 @@ class AffineSubspace:
|
|
|
198
284
|
orthonormalize: bool = True,
|
|
199
285
|
) -> AffineSubspace:
|
|
200
286
|
"""
|
|
201
|
-
Constructs
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
This automatically constructs the constraint operator B such that
|
|
205
|
-
the subspace is {u | B(u) = B(translation)}.
|
|
287
|
+
Constructs subspace from complement basis.
|
|
288
|
+
Constraint is explicit: <u, e_i> = <x0, e_i>.
|
|
206
289
|
"""
|
|
207
|
-
# 1. Orthonormalize basis for stability
|
|
208
290
|
if orthonormalize:
|
|
209
291
|
e_vectors = domain.gram_schmidt(basis_vectors)
|
|
210
292
|
else:
|
|
211
293
|
e_vectors = basis_vectors
|
|
212
294
|
|
|
213
|
-
# 2. Construct Projector P_perp
|
|
214
295
|
complement_projector = OrthogonalProjector.from_basis(
|
|
215
|
-
domain, e_vectors, orthonormalize=False
|
|
296
|
+
domain, e_vectors, orthonormalize=False
|
|
216
297
|
)
|
|
217
298
|
|
|
218
|
-
# 3. Construct Projector P = I - P_perp
|
|
219
299
|
def mapping(x: Any) -> Any:
|
|
220
300
|
return domain.subtract(x, complement_projector(x))
|
|
221
301
|
|
|
@@ -223,16 +303,12 @@ class AffineSubspace:
|
|
|
223
303
|
domain, mapping, complement_projector=complement_projector
|
|
224
304
|
)
|
|
225
305
|
|
|
226
|
-
# 4. Construct Constraint Operator B implicitly defined by the basis
|
|
227
|
-
# B: E -> R^k, u -> [<e_1, u>, ..., <e_k, u>]
|
|
228
|
-
# Since e_i are orthonormal, BB* = I, which is perfect for solvers.
|
|
229
306
|
codomain = EuclideanSpace(len(e_vectors))
|
|
230
307
|
|
|
231
308
|
def constraint_mapping(u: Vector) -> np.ndarray:
|
|
232
309
|
return np.array([domain.inner_product(e, u) for e in e_vectors])
|
|
233
310
|
|
|
234
311
|
def constraint_adjoint(c: np.ndarray) -> Vector:
|
|
235
|
-
# sum c_i e_i
|
|
236
312
|
res = domain.zero
|
|
237
313
|
for i, e in enumerate(e_vectors):
|
|
238
314
|
domain.axpy(c[i], e, res)
|
|
@@ -242,8 +318,6 @@ class AffineSubspace:
|
|
|
242
318
|
domain, codomain, constraint_mapping, adjoint_mapping=constraint_adjoint
|
|
243
319
|
)
|
|
244
320
|
|
|
245
|
-
# 5. Determine Constraint Value w = B(translation)
|
|
246
|
-
# If translation is None (zero), w is zero.
|
|
247
321
|
if translation is None:
|
|
248
322
|
_translation = domain.zero
|
|
249
323
|
w = codomain.zero
|
|
@@ -251,12 +325,20 @@ class AffineSubspace:
|
|
|
251
325
|
_translation = translation
|
|
252
326
|
w = B(_translation)
|
|
253
327
|
|
|
254
|
-
|
|
328
|
+
solver = CholeskySolver(galerkin=True)
|
|
329
|
+
|
|
330
|
+
return cls(
|
|
331
|
+
projector,
|
|
332
|
+
_translation,
|
|
333
|
+
constraint_operator=B,
|
|
334
|
+
constraint_value=w,
|
|
335
|
+
solver=solver,
|
|
336
|
+
)
|
|
255
337
|
|
|
256
338
|
|
|
257
339
|
class LinearSubspace(AffineSubspace):
|
|
258
340
|
"""
|
|
259
|
-
Represents a linear subspace (an affine subspace passing through
|
|
341
|
+
Represents a linear subspace (an affine subspace passing through the origin).
|
|
260
342
|
"""
|
|
261
343
|
|
|
262
344
|
def __init__(self, projector: OrthogonalProjector) -> None:
|
|
@@ -272,14 +354,19 @@ class LinearSubspace(AffineSubspace):
|
|
|
272
354
|
|
|
273
355
|
@classmethod
|
|
274
356
|
def from_kernel(
|
|
275
|
-
cls,
|
|
357
|
+
cls,
|
|
358
|
+
operator: LinearOperator,
|
|
359
|
+
solver: Optional[LinearSolver] = None,
|
|
360
|
+
preconditioner: Optional[LinearOperator] = None,
|
|
276
361
|
) -> LinearSubspace:
|
|
277
362
|
affine = AffineSubspace.from_linear_equation(
|
|
278
|
-
operator, operator.codomain.zero, solver
|
|
363
|
+
operator, operator.codomain.zero, solver, preconditioner
|
|
279
364
|
)
|
|
280
365
|
instance = cls(affine.projector)
|
|
281
366
|
instance._constraint_operator = operator
|
|
282
367
|
instance._constraint_value = operator.codomain.zero
|
|
368
|
+
instance._solver = affine._solver
|
|
369
|
+
instance._preconditioner = preconditioner
|
|
283
370
|
return instance
|
|
284
371
|
|
|
285
372
|
@classmethod
|
|
@@ -288,11 +375,16 @@ class LinearSubspace(AffineSubspace):
|
|
|
288
375
|
domain: HilbertSpace,
|
|
289
376
|
basis_vectors: List[Vector],
|
|
290
377
|
orthonormalize: bool = True,
|
|
378
|
+
solver: Optional[LinearSolver] = None,
|
|
379
|
+
preconditioner: Optional[LinearOperator] = None,
|
|
291
380
|
) -> LinearSubspace:
|
|
292
381
|
projector = OrthogonalProjector.from_basis(
|
|
293
382
|
domain, basis_vectors, orthonormalize=orthonormalize
|
|
294
383
|
)
|
|
295
|
-
|
|
384
|
+
instance = cls(projector)
|
|
385
|
+
instance._solver = solver
|
|
386
|
+
instance._preconditioner = preconditioner
|
|
387
|
+
return instance
|
|
296
388
|
|
|
297
389
|
@classmethod
|
|
298
390
|
def from_complement_basis(
|
|
@@ -304,8 +396,8 @@ class LinearSubspace(AffineSubspace):
|
|
|
304
396
|
affine = AffineSubspace.from_complement_basis(
|
|
305
397
|
domain, basis_vectors, translation=None, orthonormalize=orthonormalize
|
|
306
398
|
)
|
|
307
|
-
# Copy constraint info from the affine instance
|
|
308
399
|
instance = cls(affine.projector)
|
|
309
400
|
instance._constraint_operator = affine.constraint_operator
|
|
310
401
|
instance._constraint_value = affine.constraint_value
|
|
402
|
+
instance._solver = affine._solver
|
|
311
403
|
return instance
|
|
@@ -11,16 +11,28 @@ class SHVectorConverter:
|
|
|
11
11
|
"""
|
|
12
12
|
Handles conversion between pyshtools 3D coefficient arrays and 1D vectors.
|
|
13
13
|
|
|
14
|
-
This class
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
This class bridges the gap between the `pyshtools` 3D array format
|
|
15
|
+
(shape `[2, lmax+1, lmax+1]`) and the flat 1D vector format used in
|
|
16
|
+
linear algebra.
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
**Vector Layout:**
|
|
19
|
+
The output vector is ordered first by degree $l$ (ascending from `lmin` to `lmax`),
|
|
20
|
+
and then by order $m$ (ascending from $-l$ to $+l$).
|
|
21
|
+
|
|
22
|
+
The sequence of coefficients is:
|
|
23
|
+
|
|
24
|
+
.. math::
|
|
25
|
+
[u_{l_{min}, -l_{min}}, \dots, u_{l_{min}, l_{min}}, \quad
|
|
26
|
+
u_{l_{min}+1, -(l_{min}+1)}, \dots, u_{l_{min}+1, l_{min}+1}, \quad \dots]
|
|
27
|
+
|
|
28
|
+
**Example (lmin=0):**
|
|
29
|
+
|
|
30
|
+
.. math::
|
|
31
|
+
[u_{0,0}, \quad u_{1,-1}, u_{1,0}, u_{1,1}, \quad u_{2,-2}, u_{2,-1}, u_{2,0}, u_{2,1}, u_{2,2}, \dots]
|
|
20
32
|
|
|
21
33
|
Args:
|
|
22
34
|
lmax (int): The maximum spherical harmonic degree to include.
|
|
23
|
-
lmin (int): The minimum spherical harmonic degree to include. Defaults to
|
|
35
|
+
lmin (int): The minimum spherical harmonic degree to include. Defaults to 0.
|
|
24
36
|
"""
|
|
25
37
|
|
|
26
38
|
def __init__(self, lmax: int, lmin: int = 0):
|
|
@@ -532,30 +532,26 @@ class Lebesgue(SphereHelper, HilbertModule, AbstractInvariantLebesgueSpace):
|
|
|
532
532
|
)
|
|
533
533
|
|
|
534
534
|
def to_coefficient_operator(self, lmax: int, lmin: int = 0):
|
|
535
|
-
"""
|
|
536
|
-
Returns a LinearOperator
|
|
537
|
-
a vector of its spherical harmonic coefficients within the
|
|
538
|
-
specified range of degrees.
|
|
535
|
+
r"""
|
|
536
|
+
Returns a LinearOperator mapping a function to its spherical harmonic coefficients.
|
|
539
537
|
|
|
540
|
-
The
|
|
538
|
+
The operator maps an element of the Hilbert space to a vector in $\mathbb{R}^k$.
|
|
539
|
+
The coefficients in the output vector are ordered by degree $l$ (major)
|
|
540
|
+
and order $m$ (minor), from $-l$ to $+l$.
|
|
541
541
|
|
|
542
|
-
|
|
542
|
+
**Ordering:**
|
|
543
543
|
|
|
544
|
-
|
|
544
|
+
.. math::
|
|
545
|
+
u = [u_{0,0}, \quad u_{1,-1}, u_{1,0}, u_{1,1}, \quad u_{2,-2}, \dots, u_{2,2}, \quad \dots]
|
|
545
546
|
|
|
546
|
-
|
|
547
|
+
(assuming `lmin=0`).
|
|
547
548
|
|
|
548
549
|
Args:
|
|
549
550
|
lmax: The maximum spherical harmonic degree to include in the output.
|
|
550
|
-
lmin: The minimum spherical harmonic degree to include
|
|
551
|
-
Defaults to 0.
|
|
551
|
+
lmin: The minimum spherical harmonic degree to include. Defaults to 0.
|
|
552
552
|
|
|
553
553
|
Returns:
|
|
554
|
-
A LinearOperator
|
|
555
|
-
|
|
556
|
-
Notes:
|
|
557
|
-
This is a left inverse of the from_coefficient_operator so long a the
|
|
558
|
-
values for lmin and lmax are equal.
|
|
554
|
+
A LinearOperator mapping `SHGrid` -> `numpy.ndarray`.
|
|
559
555
|
"""
|
|
560
556
|
|
|
561
557
|
converter = SHVectorConverter(lmax, lmin)
|
|
@@ -577,27 +573,25 @@ class Lebesgue(SphereHelper, HilbertModule, AbstractInvariantLebesgueSpace):
|
|
|
577
573
|
return LinearOperator(self, codomain, mapping, adjoint_mapping=adjoint_mapping)
|
|
578
574
|
|
|
579
575
|
def from_coefficient_operator(self, lmax: int, lmin: int = 0):
|
|
580
|
-
"""
|
|
581
|
-
Returns a LinearOperator
|
|
582
|
-
to an element of the space.
|
|
576
|
+
r"""
|
|
577
|
+
Returns a LinearOperator mapping a vector of coefficients to a function.
|
|
583
578
|
|
|
584
|
-
The
|
|
579
|
+
The operator maps a vector in $\mathbb{R}^k$ to an element of the Hilbert space.
|
|
580
|
+
The input vector must follow the standard $l$-major, $m$-minor ordering.
|
|
585
581
|
|
|
586
|
-
|
|
582
|
+
**Ordering:**
|
|
587
583
|
|
|
588
|
-
|
|
584
|
+
.. math::
|
|
585
|
+
v = [u_{0,0}, \quad u_{1,-1}, u_{1,0}, u_{1,1}, \quad u_{2,-2}, \dots, u_{2,2}, \quad \dots]
|
|
586
|
+
|
|
587
|
+
(assuming `lmin=0`).
|
|
589
588
|
|
|
590
589
|
Args:
|
|
591
|
-
lmax: The maximum spherical harmonic degree
|
|
592
|
-
lmin: The minimum spherical harmonic degree
|
|
593
|
-
Defaults to 0.
|
|
590
|
+
lmax: The maximum spherical harmonic degree expected in the input.
|
|
591
|
+
lmin: The minimum spherical harmonic degree expected. Defaults to 0.
|
|
594
592
|
|
|
595
593
|
Returns:
|
|
596
|
-
A LinearOperator
|
|
597
|
-
|
|
598
|
-
Notes:
|
|
599
|
-
This is a right inverse of the to_coefficient_operator so long a the
|
|
600
|
-
values for lmin and lmax are equal.
|
|
594
|
+
A LinearOperator mapping `numpy.ndarray` -> `SHGrid`.
|
|
601
595
|
"""
|
|
602
596
|
|
|
603
597
|
converter = SHVectorConverter(lmax, lmin)
|
|
@@ -783,30 +777,26 @@ class Sobolev(SphereHelper, MassWeightedHilbertModule, AbstractInvariantSobolevS
|
|
|
783
777
|
)
|
|
784
778
|
|
|
785
779
|
def to_coefficient_operator(self, lmax: int, lmin: int = 0):
|
|
786
|
-
"""
|
|
787
|
-
Returns a LinearOperator
|
|
788
|
-
a vector of its spherical harmonic coefficients within the
|
|
789
|
-
specified range of degrees.
|
|
780
|
+
r"""
|
|
781
|
+
Returns a LinearOperator mapping a function to its spherical harmonic coefficients.
|
|
790
782
|
|
|
791
|
-
The
|
|
783
|
+
The operator maps an element of the Hilbert space to a vector in $\mathbb{R}^k$.
|
|
784
|
+
The coefficients in the output vector are ordered by degree $l$ (major)
|
|
785
|
+
and order $m$ (minor), from $-l$ to $+l$.
|
|
792
786
|
|
|
793
|
-
|
|
787
|
+
**Ordering:**
|
|
794
788
|
|
|
795
|
-
|
|
789
|
+
.. math::
|
|
790
|
+
u = [u_{0,0}, \quad u_{1,-1}, u_{1,0}, u_{1,1}, \quad u_{2,-2}, \dots, u_{2,2}, \quad \dots]
|
|
796
791
|
|
|
797
|
-
|
|
792
|
+
(assuming `lmin=0`).
|
|
798
793
|
|
|
799
794
|
Args:
|
|
800
795
|
lmax: The maximum spherical harmonic degree to include in the output.
|
|
801
|
-
lmin: The minimum spherical harmonic degree to include
|
|
802
|
-
Defaults to 0.
|
|
796
|
+
lmin: The minimum spherical harmonic degree to include. Defaults to 0.
|
|
803
797
|
|
|
804
798
|
Returns:
|
|
805
|
-
A LinearOperator
|
|
806
|
-
|
|
807
|
-
Notes:
|
|
808
|
-
This is a left inverse of the from_coefficient_operator so long a the
|
|
809
|
-
values for lmin and lmax are equal.
|
|
799
|
+
A LinearOperator mapping `SHGrid` -> `numpy.ndarray`.
|
|
810
800
|
"""
|
|
811
801
|
|
|
812
802
|
l2_operator = self.underlying_space.to_coefficient_operator(lmax, lmin)
|
|
@@ -816,27 +806,25 @@ class Sobolev(SphereHelper, MassWeightedHilbertModule, AbstractInvariantSobolevS
|
|
|
816
806
|
)
|
|
817
807
|
|
|
818
808
|
def from_coefficient_operator(self, lmax: int, lmin: int = 0):
|
|
819
|
-
"""
|
|
820
|
-
Returns a LinearOperator
|
|
821
|
-
to an element of the space.
|
|
809
|
+
r"""
|
|
810
|
+
Returns a LinearOperator mapping a vector of coefficients to a function.
|
|
822
811
|
|
|
823
|
-
The
|
|
812
|
+
The operator maps a vector in $\mathbb{R}^k$ to an element of the Hilbert space.
|
|
813
|
+
The input vector must follow the standard $l$-major, $m$-minor ordering.
|
|
824
814
|
|
|
825
|
-
|
|
815
|
+
**Ordering:**
|
|
826
816
|
|
|
827
|
-
|
|
817
|
+
.. math::
|
|
818
|
+
v = [u_{0,0}, \quad u_{1,-1}, u_{1,0}, u_{1,1}, \quad u_{2,-2}, \dots, u_{2,2}, \quad \dots]
|
|
819
|
+
|
|
820
|
+
(assuming `lmin=0`).
|
|
828
821
|
|
|
829
822
|
Args:
|
|
830
|
-
lmax: The maximum spherical harmonic degree
|
|
831
|
-
lmin: The minimum spherical harmonic degree
|
|
832
|
-
Defaults to 0.
|
|
823
|
+
lmax: The maximum spherical harmonic degree expected in the input.
|
|
824
|
+
lmin: The minimum spherical harmonic degree expected. Defaults to 0.
|
|
833
825
|
|
|
834
826
|
Returns:
|
|
835
|
-
A LinearOperator
|
|
836
|
-
|
|
837
|
-
Notes:
|
|
838
|
-
This is a right inverse of the to_coefficient_operator so long a the
|
|
839
|
-
values for lmin and lmax are equal.
|
|
827
|
+
A LinearOperator mapping `numpy.ndarray` -> `SHGrid`.
|
|
840
828
|
"""
|
|
841
829
|
|
|
842
830
|
l2_operator = self.underlying_space.from_coefficient_operator(lmax, lmin)
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|