pygeoinf 1.2.0__tar.gz → 1.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pygeoinf-1.2.0 → pygeoinf-1.2.2}/PKG-INFO +1 -1
- {pygeoinf-1.2.0 → pygeoinf-1.2.2}/pygeoinf/__init__.py +17 -4
- pygeoinf-1.2.2/pygeoinf/backus_gilbert.py +120 -0
- {pygeoinf-1.2.0 → pygeoinf-1.2.2}/pygeoinf/direct_sum.py +35 -77
- {pygeoinf-1.2.0 → pygeoinf-1.2.2}/pygeoinf/forward_problem.py +39 -15
- {pygeoinf-1.2.0 → pygeoinf-1.2.2}/pygeoinf/gaussian_measure.py +35 -10
- {pygeoinf-1.2.0 → pygeoinf-1.2.2}/pygeoinf/hilbert_space.py +55 -9
- pygeoinf-1.2.2/pygeoinf/inversion.py +177 -0
- {pygeoinf-1.2.0 → pygeoinf-1.2.2}/pygeoinf/linear_bayesian.py +3 -3
- pygeoinf-1.2.2/pygeoinf/linear_forms.py +263 -0
- pygeoinf-1.2.0/pygeoinf/operators.py → pygeoinf-1.2.2/pygeoinf/linear_operators.py +279 -303
- {pygeoinf-1.2.0 → pygeoinf-1.2.2}/pygeoinf/linear_optimisation.py +9 -9
- {pygeoinf-1.2.0 → pygeoinf-1.2.2}/pygeoinf/linear_solvers.py +74 -7
- pygeoinf-1.2.2/pygeoinf/nonlinear_forms.py +226 -0
- pygeoinf-1.2.2/pygeoinf/nonlinear_operators.py +216 -0
- pygeoinf-1.2.2/pygeoinf/nonlinear_optimisation.py +211 -0
- {pygeoinf-1.2.0 → pygeoinf-1.2.2}/pygeoinf/parallel.py +1 -1
- pygeoinf-1.2.2/pygeoinf/random_matrix.py +387 -0
- {pygeoinf-1.2.0 → pygeoinf-1.2.2}/pygeoinf/symmetric_space/circle.py +12 -2
- {pygeoinf-1.2.0 → pygeoinf-1.2.2}/pygeoinf/symmetric_space/sphere.py +15 -1
- {pygeoinf-1.2.0 → pygeoinf-1.2.2}/pygeoinf/symmetric_space/symmetric_space.py +1 -1
- {pygeoinf-1.2.0 → pygeoinf-1.2.2}/pyproject.toml +1 -1
- pygeoinf-1.2.0/pygeoinf/inversion.py +0 -85
- pygeoinf-1.2.0/pygeoinf/linear_forms.py +0 -169
- pygeoinf-1.2.0/pygeoinf/random_matrix.py +0 -247
- {pygeoinf-1.2.0 → pygeoinf-1.2.2}/LICENSE +0 -0
- {pygeoinf-1.2.0 → pygeoinf-1.2.2}/README.md +0 -0
- {pygeoinf-1.2.0 → pygeoinf-1.2.2}/pygeoinf/symmetric_space/__init__.py +0 -0
|
@@ -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
|
|
@@ -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)
|
|
@@ -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.
|
|
@@ -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(
|