pygeoinf 1.2.0__py3-none-any.whl → 1.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pygeoinf/__init__.py +17 -4
- pygeoinf/backus_gilbert.py +120 -0
- pygeoinf/direct_sum.py +35 -77
- pygeoinf/forward_problem.py +39 -15
- pygeoinf/gaussian_measure.py +35 -10
- pygeoinf/hilbert_space.py +55 -9
- pygeoinf/inversion.py +101 -9
- pygeoinf/linear_bayesian.py +3 -3
- pygeoinf/linear_forms.py +137 -43
- pygeoinf/{operators.py → linear_operators.py} +279 -303
- pygeoinf/linear_optimisation.py +9 -9
- pygeoinf/linear_solvers.py +74 -7
- pygeoinf/nonlinear_forms.py +226 -0
- pygeoinf/nonlinear_operators.py +216 -0
- pygeoinf/nonlinear_optimisation.py +211 -0
- pygeoinf/parallel.py +1 -1
- pygeoinf/random_matrix.py +212 -72
- pygeoinf/symmetric_space/circle.py +12 -2
- pygeoinf/symmetric_space/sphere.py +15 -1
- pygeoinf/symmetric_space/symmetric_space.py +1 -1
- {pygeoinf-1.2.0.dist-info → pygeoinf-1.2.2.dist-info}/METADATA +1 -1
- pygeoinf-1.2.2.dist-info/RECORD +25 -0
- pygeoinf-1.2.0.dist-info/RECORD +0 -21
- {pygeoinf-1.2.0.dist-info → pygeoinf-1.2.2.dist-info}/LICENSE +0 -0
- {pygeoinf-1.2.0.dist-info → pygeoinf-1.2.2.dist-info}/WHEEL +0 -0
pygeoinf/__init__.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from .random_matrix import (
|
|
2
2
|
fixed_rank_random_range,
|
|
3
3
|
variable_rank_random_range,
|
|
4
|
+
random_range,
|
|
4
5
|
random_svd,
|
|
5
6
|
random_eig,
|
|
6
7
|
random_cholesky,
|
|
@@ -16,16 +17,22 @@ from .hilbert_space import (
|
|
|
16
17
|
)
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
from .
|
|
20
|
-
|
|
21
|
-
LinearOperator,
|
|
22
|
-
DiagonalLinearOperator,
|
|
20
|
+
from .nonlinear_forms import (
|
|
21
|
+
NonLinearForm,
|
|
23
22
|
)
|
|
24
23
|
|
|
24
|
+
|
|
25
25
|
from .linear_forms import (
|
|
26
26
|
LinearForm,
|
|
27
27
|
)
|
|
28
28
|
|
|
29
|
+
from .nonlinear_operators import NonLinearOperator
|
|
30
|
+
|
|
31
|
+
from .linear_operators import (
|
|
32
|
+
LinearOperator,
|
|
33
|
+
DiagonalLinearOperator,
|
|
34
|
+
)
|
|
35
|
+
|
|
29
36
|
|
|
30
37
|
from .gaussian_measure import (
|
|
31
38
|
GaussianMeasure,
|
|
@@ -45,7 +52,9 @@ from .linear_solvers import (
|
|
|
45
52
|
DirectLinearSolver,
|
|
46
53
|
LUSolver,
|
|
47
54
|
CholeskySolver,
|
|
55
|
+
EigenSolver,
|
|
48
56
|
IterativeLinearSolver,
|
|
57
|
+
ScipyIterativeSolver,
|
|
49
58
|
CGMatrixSolver,
|
|
50
59
|
BICGMatrixSolver,
|
|
51
60
|
BICGStabMatrixSolver,
|
|
@@ -61,3 +70,7 @@ from .linear_optimisation import (
|
|
|
61
70
|
)
|
|
62
71
|
|
|
63
72
|
from .linear_bayesian import LinearBayesianInversion, LinearBayesianInference
|
|
73
|
+
|
|
74
|
+
from .nonlinear_optimisation import (
|
|
75
|
+
ScipyUnconstrainedOptimiser,
|
|
76
|
+
)
|
|
@@ -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
|
pygeoinf/direct_sum.py
CHANGED
|
@@ -29,7 +29,7 @@ from scipy.linalg import block_diag
|
|
|
29
29
|
from joblib import Parallel, delayed
|
|
30
30
|
|
|
31
31
|
from .hilbert_space import HilbertSpace
|
|
32
|
-
from .
|
|
32
|
+
from .linear_operators import LinearOperator
|
|
33
33
|
from .linear_forms import LinearForm
|
|
34
34
|
from .parallel import parallel_compute_dense_matrix_from_scipy_op
|
|
35
35
|
|
|
@@ -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."""
|
|
@@ -307,35 +317,18 @@ class BlockLinearOperator(LinearOperator, BlockStructure):
|
|
|
307
317
|
self, galerkin: bool, parallel: bool, n_jobs: int
|
|
308
318
|
) -> np.ndarray:
|
|
309
319
|
"""Overloaded method to efficiently compute the dense matrix for a block operator."""
|
|
310
|
-
if not parallel:
|
|
311
|
-
block_matrices = [
|
|
312
|
-
[
|
|
313
|
-
self.block(i, j).matrix(
|
|
314
|
-
dense=True, galerkin=galerkin, parallel=False
|
|
315
|
-
)
|
|
316
|
-
for j in range(self.col_dim)
|
|
317
|
-
]
|
|
318
|
-
for i in range(self.row_dim)
|
|
319
|
-
]
|
|
320
|
-
return np.block(block_matrices)
|
|
321
320
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
self.block(i, j).matrix(
|
|
325
|
-
|
|
321
|
+
block_matrices = [
|
|
322
|
+
[
|
|
323
|
+
self.block(i, j).matrix(
|
|
324
|
+
dense=True, galerkin=galerkin, parallel=parallel, n_jobs=n_jobs
|
|
325
|
+
)
|
|
326
326
|
for j in range(self.col_dim)
|
|
327
327
|
]
|
|
328
|
+
for i in range(self.row_dim)
|
|
329
|
+
]
|
|
328
330
|
|
|
329
|
-
|
|
330
|
-
delayed(parallel_compute_dense_matrix_from_scipy_op)(op, n_jobs=1)
|
|
331
|
-
for op in block_scipy_ops
|
|
332
|
-
)
|
|
333
|
-
|
|
334
|
-
block_matrices = [
|
|
335
|
-
computed_blocks[i * self.col_dim : (i + 1) * self.col_dim]
|
|
336
|
-
for i in range(self.row_dim)
|
|
337
|
-
]
|
|
338
|
-
return np.block(block_matrices)
|
|
331
|
+
return np.block(block_matrices)
|
|
339
332
|
|
|
340
333
|
def __mapping(self, xs: List[Any]) -> List[Any]:
|
|
341
334
|
|
|
@@ -402,6 +395,7 @@ class ColumnLinearOperator(LinearOperator, BlockStructure):
|
|
|
402
395
|
x = domain.zero
|
|
403
396
|
for op, y in zip(self._operators, ys):
|
|
404
397
|
domain.axpy(1.0, op.adjoint(y), x)
|
|
398
|
+
print(op.adjoint(y).data)
|
|
405
399
|
return x
|
|
406
400
|
|
|
407
401
|
LinearOperator.__init__(
|
|
@@ -420,23 +414,11 @@ class ColumnLinearOperator(LinearOperator, BlockStructure):
|
|
|
420
414
|
self, galerkin: bool, parallel: bool, n_jobs: int
|
|
421
415
|
) -> np.ndarray:
|
|
422
416
|
"""Overloaded method to efficiently compute the dense matrix for a column operator."""
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
return np.vstack(block_matrices)
|
|
429
|
-
else:
|
|
430
|
-
block_scipy_ops = [
|
|
431
|
-
op.matrix(galerkin=galerkin, parallel=False) for op in self._operators
|
|
432
|
-
]
|
|
433
|
-
|
|
434
|
-
computed_blocks = Parallel(n_jobs=n_jobs)(
|
|
435
|
-
delayed(parallel_compute_dense_matrix_from_scipy_op)(op, n_jobs=1)
|
|
436
|
-
for op in block_scipy_ops
|
|
437
|
-
)
|
|
438
|
-
|
|
439
|
-
return np.vstack(computed_blocks)
|
|
417
|
+
block_matrices = [
|
|
418
|
+
op.matrix(dense=True, galerkin=galerkin, parallel=parallel, n_jobs=n_jobs)
|
|
419
|
+
for op in self._operators
|
|
420
|
+
]
|
|
421
|
+
return np.vstack(block_matrices)
|
|
440
422
|
|
|
441
423
|
|
|
442
424
|
class RowLinearOperator(LinearOperator, BlockStructure):
|
|
@@ -496,23 +478,11 @@ class RowLinearOperator(LinearOperator, BlockStructure):
|
|
|
496
478
|
self, galerkin: bool, parallel: bool, n_jobs: int
|
|
497
479
|
) -> np.ndarray:
|
|
498
480
|
"""Overloaded method to efficiently compute the dense matrix for a row operator."""
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
return np.hstack(block_matrices)
|
|
505
|
-
else:
|
|
506
|
-
block_scipy_ops = [
|
|
507
|
-
op.matrix(galerkin=galerkin, parallel=False) for op in self._operators
|
|
508
|
-
]
|
|
509
|
-
|
|
510
|
-
computed_blocks = Parallel(n_jobs=n_jobs)(
|
|
511
|
-
delayed(parallel_compute_dense_matrix_from_scipy_op)(op, n_jobs=1)
|
|
512
|
-
for op in block_scipy_ops
|
|
513
|
-
)
|
|
514
|
-
|
|
515
|
-
return np.hstack(computed_blocks)
|
|
481
|
+
block_matrices = [
|
|
482
|
+
op.matrix(dense=True, galerkin=galerkin, parallel=parallel, n_jobs=n_jobs)
|
|
483
|
+
for op in self._operators
|
|
484
|
+
]
|
|
485
|
+
return np.hstack(block_matrices)
|
|
516
486
|
|
|
517
487
|
|
|
518
488
|
class BlockDiagonalLinearOperator(LinearOperator, BlockStructure):
|
|
@@ -559,20 +529,8 @@ class BlockDiagonalLinearOperator(LinearOperator, BlockStructure):
|
|
|
559
529
|
self, galerkin: bool, parallel: bool, n_jobs: int
|
|
560
530
|
) -> np.ndarray:
|
|
561
531
|
"""Overloaded method to efficiently compute the dense matrix for a block-diagonal operator."""
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
return block_diag(*block_matrices)
|
|
568
|
-
else:
|
|
569
|
-
block_scipy_ops = [
|
|
570
|
-
op.matrix(galerkin=galerkin, parallel=False) for op in self._operators
|
|
571
|
-
]
|
|
572
|
-
|
|
573
|
-
computed_blocks = Parallel(n_jobs=n_jobs)(
|
|
574
|
-
delayed(parallel_compute_dense_matrix_from_scipy_op)(op, n_jobs=1)
|
|
575
|
-
for op in block_scipy_ops
|
|
576
|
-
)
|
|
577
|
-
|
|
578
|
-
return block_diag(*computed_blocks)
|
|
532
|
+
block_matrices = [
|
|
533
|
+
op.matrix(dense=True, galerkin=galerkin, parallel=parallel, n_jobs=n_jobs)
|
|
534
|
+
for op in self._operators
|
|
535
|
+
]
|
|
536
|
+
return block_diag(*block_matrices)
|
pygeoinf/forward_problem.py
CHANGED
|
@@ -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,14 +237,16 @@ 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
|
|
|
223
244
|
This measures the misfit between the predicted and observed data.
|
|
245
|
+
|
|
224
246
|
- If a data error measure with an inverse covariance `C_e^-1` is defined,
|
|
225
247
|
this is the weighted misfit: `(d - A(u))^T * C_e^-1 * (d - A(u))`.
|
|
226
248
|
- Otherwise, it is the squared L2 norm of the data residual: `||d - A(u)||^2`.
|
|
249
|
+
|
|
227
250
|
Args:
|
|
228
251
|
model: A vector from the model space.
|
|
229
252
|
data: An observed data vector from the data space.
|
|
@@ -231,6 +254,7 @@ class LinearForwardProblem(ForwardProblem):
|
|
|
231
254
|
Returns:
|
|
232
255
|
The chi-squared statistic.
|
|
233
256
|
"""
|
|
257
|
+
|
|
234
258
|
residual = self.data_space.subtract(data, self.forward_operator(model))
|
|
235
259
|
|
|
236
260
|
if self.data_error_measure_set:
|
|
@@ -247,7 +271,7 @@ class LinearForwardProblem(ForwardProblem):
|
|
|
247
271
|
return self.data_space.squared_norm(residual)
|
|
248
272
|
|
|
249
273
|
def chi_squared_test(
|
|
250
|
-
self, significance_level: float, model:
|
|
274
|
+
self, significance_level: float, model: Vector, data: Vector
|
|
251
275
|
) -> bool:
|
|
252
276
|
"""
|
|
253
277
|
Performs a chi-squared test for goodness of fit.
|
pygeoinf/gaussian_measure.py
CHANGED
|
@@ -30,7 +30,7 @@ from scipy.stats import multivariate_normal
|
|
|
30
30
|
|
|
31
31
|
from .hilbert_space import EuclideanSpace, HilbertModule
|
|
32
32
|
|
|
33
|
-
from .
|
|
33
|
+
from .linear_operators import (
|
|
34
34
|
LinearOperator,
|
|
35
35
|
DiagonalLinearOperator,
|
|
36
36
|
)
|
|
@@ -491,12 +491,16 @@ class GaussianMeasure:
|
|
|
491
491
|
|
|
492
492
|
def low_rank_approximation(
|
|
493
493
|
self,
|
|
494
|
-
|
|
494
|
+
size_estimate: int,
|
|
495
495
|
/,
|
|
496
496
|
*,
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
497
|
+
method: str = "variable",
|
|
498
|
+
max_rank: int = None,
|
|
499
|
+
power: int = 2,
|
|
500
|
+
rtol: float = 1e-4,
|
|
501
|
+
block_size: int = 10,
|
|
502
|
+
parallel: bool = False,
|
|
503
|
+
n_jobs: int = -1,
|
|
500
504
|
) -> GaussianMeasure:
|
|
501
505
|
"""
|
|
502
506
|
Constructs a low-rank approximation of the measure.
|
|
@@ -505,16 +509,37 @@ class GaussianMeasure:
|
|
|
505
509
|
can be much more efficient for sampling and storage.
|
|
506
510
|
|
|
507
511
|
Args:
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
method (
|
|
511
|
-
|
|
512
|
+
size_estimate: For 'fixed' method, the exact target rank. For 'variable'
|
|
513
|
+
method, this is the initial rank to sample.
|
|
514
|
+
method ({'variable', 'fixed'}): The algorithm to use.
|
|
515
|
+
- 'variable': (Default) Progressively samples to find the rank needed
|
|
516
|
+
to meet tolerance `rtol`, stopping at `max_rank`.
|
|
517
|
+
- 'fixed': Returns a basis with exactly `size_estimate` columns.
|
|
518
|
+
max_rank: For 'variable' method, a hard limit on the rank. Ignored if
|
|
519
|
+
method='fixed'. Defaults to min(m, n).
|
|
520
|
+
power: Number of power iterations to improve accuracy.
|
|
521
|
+
rtol: Relative tolerance for the 'variable' method. Ignored if
|
|
522
|
+
method='fixed'.
|
|
523
|
+
block_size: Number of new vectors to sample per iteration in 'variable'
|
|
524
|
+
method. Ignored if method='fixed'.
|
|
525
|
+
parallel: Whether to use parallel matrix multiplication.
|
|
526
|
+
n_jobs: Number of jobs for parallelism.
|
|
512
527
|
|
|
513
528
|
Returns:
|
|
514
529
|
GaussianMeasure: The new, low-rank Gaussian measure.
|
|
530
|
+
|
|
531
|
+
Notes:
|
|
532
|
+
Parallel implemention only currently possible with fixed-rank decompositions.
|
|
515
533
|
"""
|
|
516
534
|
covariance_factor = self.covariance.random_cholesky(
|
|
517
|
-
|
|
535
|
+
size_estimate,
|
|
536
|
+
method=method,
|
|
537
|
+
max_rank=max_rank,
|
|
538
|
+
power=power,
|
|
539
|
+
rtol=rtol,
|
|
540
|
+
block_size=block_size,
|
|
541
|
+
parallel=parallel,
|
|
542
|
+
n_jobs=n_jobs,
|
|
518
543
|
)
|
|
519
544
|
|
|
520
545
|
return GaussianMeasure(
|
pygeoinf/hilbert_space.py
CHANGED
|
@@ -38,7 +38,7 @@ import numpy as np
|
|
|
38
38
|
|
|
39
39
|
# This block only runs for type checkers, not at runtime
|
|
40
40
|
if TYPE_CHECKING:
|
|
41
|
-
from .
|
|
41
|
+
from .linear_operators import LinearOperator
|
|
42
42
|
from .linear_forms import LinearForm
|
|
43
43
|
|
|
44
44
|
# Define a generic type for vectors in a Hilbert space
|
|
@@ -223,15 +223,13 @@ class HilbertSpace(ABC):
|
|
|
223
223
|
# Final (Non-Overridable) Methods #
|
|
224
224
|
# ------------------------------------------------------------------- #
|
|
225
225
|
|
|
226
|
-
|
|
227
|
-
|
|
228
226
|
@final
|
|
229
227
|
@property
|
|
230
228
|
def coordinate_inclusion(self) -> LinearOperator:
|
|
231
229
|
"""
|
|
232
230
|
The linear operator mapping R^n component vectors into this space.
|
|
233
231
|
"""
|
|
234
|
-
from .
|
|
232
|
+
from .linear_operators import LinearOperator
|
|
235
233
|
|
|
236
234
|
domain = EuclideanSpace(self.dim)
|
|
237
235
|
|
|
@@ -257,7 +255,7 @@ class HilbertSpace(ABC):
|
|
|
257
255
|
"""
|
|
258
256
|
The linear operator projecting vectors from this space to R^n.
|
|
259
257
|
"""
|
|
260
|
-
from .
|
|
258
|
+
from .linear_operators import LinearOperator
|
|
261
259
|
|
|
262
260
|
codomain = EuclideanSpace(self.dim)
|
|
263
261
|
|
|
@@ -281,7 +279,7 @@ class HilbertSpace(ABC):
|
|
|
281
279
|
@property
|
|
282
280
|
def riesz(self) -> LinearOperator:
|
|
283
281
|
"""The Riesz map (dual to primal) as a `LinearOperator`."""
|
|
284
|
-
from .
|
|
282
|
+
from .linear_operators import LinearOperator
|
|
285
283
|
|
|
286
284
|
return LinearOperator.self_dual(self.dual, self.from_dual)
|
|
287
285
|
|
|
@@ -289,7 +287,7 @@ class HilbertSpace(ABC):
|
|
|
289
287
|
@property
|
|
290
288
|
def inverse_riesz(self) -> LinearOperator:
|
|
291
289
|
"""The inverse Riesz map (primal to dual) as a `LinearOperator`."""
|
|
292
|
-
from .
|
|
290
|
+
from .linear_operators import LinearOperator
|
|
293
291
|
|
|
294
292
|
return LinearOperator.self_dual(self, self.to_dual)
|
|
295
293
|
|
|
@@ -412,7 +410,7 @@ class HilbertSpace(ABC):
|
|
|
412
410
|
@final
|
|
413
411
|
def identity_operator(self) -> LinearOperator:
|
|
414
412
|
"""Returns the identity operator `I` on the space."""
|
|
415
|
-
from .
|
|
413
|
+
from .linear_operators import LinearOperator
|
|
416
414
|
|
|
417
415
|
return LinearOperator(
|
|
418
416
|
self,
|
|
@@ -433,7 +431,7 @@ class HilbertSpace(ABC):
|
|
|
433
431
|
Returns:
|
|
434
432
|
The zero linear operator.
|
|
435
433
|
"""
|
|
436
|
-
from .
|
|
434
|
+
from .linear_operators import LinearOperator
|
|
437
435
|
|
|
438
436
|
codomain = self if codomain is None else codomain
|
|
439
437
|
return LinearOperator(
|
|
@@ -506,6 +504,14 @@ class DualHilbertSpace(HilbertSpace):
|
|
|
506
504
|
return NotImplemented
|
|
507
505
|
return self.underlying_space == other.underlying_space
|
|
508
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
|
+
|
|
509
515
|
@final
|
|
510
516
|
def duality_product(self, xp: LinearForm, x: Vector) -> float:
|
|
511
517
|
"""
|
|
@@ -585,6 +591,12 @@ class EuclideanSpace(HilbertSpace):
|
|
|
585
591
|
return NotImplemented
|
|
586
592
|
return self.dim == other.dim
|
|
587
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
|
+
|
|
588
600
|
|
|
589
601
|
class MassWeightedHilbertSpace(HilbertSpace):
|
|
590
602
|
"""
|
|
@@ -680,6 +692,40 @@ class MassWeightedHilbertSpace(HilbertSpace):
|
|
|
680
692
|
and (self.inverse_mass_operator == other.inverse_mass_operator)
|
|
681
693
|
)
|
|
682
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
|
+
|
|
683
729
|
|
|
684
730
|
class MassWeightedHilbertModule(MassWeightedHilbertSpace, HilbertModule):
|
|
685
731
|
"""
|