pygeoinf 1.3.8__tar.gz → 1.4.0__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.8 → pygeoinf-1.4.0}/PKG-INFO +3 -1
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pygeoinf/__init__.py +36 -0
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pygeoinf/gaussian_measure.py +79 -13
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pygeoinf/hilbert_space.py +15 -0
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pygeoinf/linear_operators.py +32 -0
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pygeoinf/plot.py +7 -1
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pygeoinf/preconditioners.py +1 -1
- pygeoinf-1.4.0/pygeoinf/subsets.py +845 -0
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pygeoinf/subspaces.py +173 -23
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pygeoinf/symmetric_space/circle.py +41 -1
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pygeoinf/symmetric_space/sphere.py +231 -22
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pygeoinf/symmetric_space/symmetric_space.py +167 -12
- pygeoinf-1.4.0/pygeoinf/symmetric_space/wigner.py +284 -0
- pygeoinf-1.4.0/pygeoinf/utils.py +15 -0
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pyproject.toml +5 -1
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/LICENSE +0 -0
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/README.md +0 -0
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pygeoinf/auxiliary.py +0 -0
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pygeoinf/backus_gilbert.py +0 -0
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pygeoinf/checks/__init__.py +0 -0
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pygeoinf/checks/hilbert_space.py +0 -0
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pygeoinf/checks/linear_operators.py +0 -0
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pygeoinf/checks/nonlinear_operators.py +0 -0
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pygeoinf/direct_sum.py +0 -0
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pygeoinf/forward_problem.py +0 -0
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pygeoinf/inversion.py +0 -0
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pygeoinf/linear_bayesian.py +0 -0
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pygeoinf/linear_forms.py +0 -0
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pygeoinf/linear_optimisation.py +0 -0
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pygeoinf/linear_solvers.py +0 -0
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pygeoinf/nonlinear_forms.py +0 -0
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pygeoinf/nonlinear_operators.py +0 -0
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pygeoinf/nonlinear_optimisation.py +0 -0
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pygeoinf/parallel.py +0 -0
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pygeoinf/random_matrix.py +0 -0
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pygeoinf/symmetric_space/__init__.py +0 -0
- {pygeoinf-1.3.8 → pygeoinf-1.4.0}/pygeoinf/symmetric_space/sh_tools.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pygeoinf
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: A package for solving geophysical inference and inverse problems
|
|
5
5
|
License: BSD-3-Clause
|
|
6
6
|
License-File: LICENSE
|
|
@@ -16,10 +16,12 @@ Provides-Extra: sphere
|
|
|
16
16
|
Requires-Dist: Cartopy (>=0.23.0,<0.24.0) ; extra == "sphere"
|
|
17
17
|
Requires-Dist: joblib (>=1.5.2,<2.0.0)
|
|
18
18
|
Requires-Dist: matplotlib (>=3.0.0)
|
|
19
|
+
Requires-Dist: numba (>=0.63.1,<0.64.0)
|
|
19
20
|
Requires-Dist: numpy (>=1.26.0)
|
|
20
21
|
Requires-Dist: pyqt6 (>=6.0.0)
|
|
21
22
|
Requires-Dist: pyshtools (>=4.0.0) ; extra == "sphere"
|
|
22
23
|
Requires-Dist: scipy (>=1.16.1)
|
|
24
|
+
Requires-Dist: threadpoolctl (>=3.6.0,<4.0.0)
|
|
23
25
|
Description-Content-Type: text/markdown
|
|
24
26
|
|
|
25
27
|
# pygeoinf: A Python Library for Geophysical Inference
|
|
@@ -104,8 +104,27 @@ from .nonlinear_optimisation import (
|
|
|
104
104
|
|
|
105
105
|
from .subspaces import OrthogonalProjector, AffineSubspace, LinearSubspace
|
|
106
106
|
|
|
107
|
+
from .subsets import (
|
|
108
|
+
Subset,
|
|
109
|
+
EmptySet,
|
|
110
|
+
UniversalSet,
|
|
111
|
+
Complement,
|
|
112
|
+
Intersection,
|
|
113
|
+
Union,
|
|
114
|
+
SublevelSet,
|
|
115
|
+
LevelSet,
|
|
116
|
+
ConvexSubset,
|
|
117
|
+
Ellipsoid,
|
|
118
|
+
NormalisedEllipsoid,
|
|
119
|
+
EllipsoidSurface,
|
|
120
|
+
Ball,
|
|
121
|
+
Sphere,
|
|
122
|
+
)
|
|
123
|
+
|
|
107
124
|
from .plot import plot_1d_distributions, plot_corner_distributions
|
|
108
125
|
|
|
126
|
+
from .utils import configure_threading
|
|
127
|
+
|
|
109
128
|
__all__ = [
|
|
110
129
|
# random_matrix
|
|
111
130
|
"fixed_rank_random_range",
|
|
@@ -184,7 +203,24 @@ __all__ = [
|
|
|
184
203
|
"OrthogonalProjector",
|
|
185
204
|
"AffineSubspace",
|
|
186
205
|
"LinearSubspace",
|
|
206
|
+
# Subsets
|
|
207
|
+
"Subset",
|
|
208
|
+
"EmptySet",
|
|
209
|
+
"UniversalSet",
|
|
210
|
+
"Complement",
|
|
211
|
+
"Intersection",
|
|
212
|
+
"Union",
|
|
213
|
+
"SublevelSet",
|
|
214
|
+
"LevelSet",
|
|
215
|
+
"ConvexSubset",
|
|
216
|
+
"Ellipsoid",
|
|
217
|
+
"NormalisedEllipsoid",
|
|
218
|
+
"EllipsoidSurface",
|
|
219
|
+
"Ball",
|
|
220
|
+
"Sphere",
|
|
187
221
|
# plot
|
|
188
222
|
"plot_1d_distributions",
|
|
189
223
|
"plot_corner_distributions",
|
|
224
|
+
# utils
|
|
225
|
+
"configure_threading",
|
|
190
226
|
]
|
|
@@ -27,7 +27,7 @@ import numpy as np
|
|
|
27
27
|
from scipy.linalg import eigh
|
|
28
28
|
from scipy.sparse import diags
|
|
29
29
|
from scipy.stats import multivariate_normal
|
|
30
|
-
|
|
30
|
+
from joblib import Parallel, delayed
|
|
31
31
|
|
|
32
32
|
from .hilbert_space import EuclideanSpace, HilbertModule, Vector
|
|
33
33
|
|
|
@@ -44,7 +44,6 @@ from .direct_sum import (
|
|
|
44
44
|
# This block is only processed by type checkers, not at runtime.
|
|
45
45
|
if TYPE_CHECKING:
|
|
46
46
|
from .hilbert_space import HilbertSpace
|
|
47
|
-
from .typing import Vector
|
|
48
47
|
|
|
49
48
|
|
|
50
49
|
class GaussianMeasure:
|
|
@@ -402,24 +401,52 @@ class GaussianMeasure:
|
|
|
402
401
|
raise NotImplementedError("A sample method is not set for this measure.")
|
|
403
402
|
return self._sample()
|
|
404
403
|
|
|
405
|
-
def samples(
|
|
406
|
-
|
|
404
|
+
def samples(
|
|
405
|
+
self, n: int, /, *, parallel: bool = False, n_jobs: int = -1
|
|
406
|
+
) -> List[Vector]:
|
|
407
|
+
"""
|
|
408
|
+
Returns a list of n random samples from the measure.
|
|
409
|
+
|
|
410
|
+
Args:
|
|
411
|
+
n: Number of samples to draw.
|
|
412
|
+
parallel: If True, draws samples in parallel.
|
|
413
|
+
n_jobs: Number of CPU cores to use. -1 means all available.
|
|
414
|
+
"""
|
|
407
415
|
if n < 1:
|
|
408
416
|
raise ValueError("Number of samples must be a positive integer.")
|
|
409
|
-
return [self.sample() for _ in range(n)]
|
|
410
417
|
|
|
411
|
-
|
|
412
|
-
|
|
418
|
+
if not parallel:
|
|
419
|
+
return [self.sample() for _ in range(n)]
|
|
420
|
+
|
|
421
|
+
return Parallel(n_jobs=n_jobs)(delayed(self.sample)() for _ in range(n))
|
|
422
|
+
|
|
423
|
+
def sample_expectation(
|
|
424
|
+
self, n: int, /, *, parallel: bool = False, n_jobs: int = -1
|
|
425
|
+
) -> Vector:
|
|
426
|
+
"""
|
|
427
|
+
Estimates the expectation by drawing n samples.
|
|
428
|
+
|
|
429
|
+
Args:
|
|
430
|
+
n: Number of samples to draw.
|
|
431
|
+
parallel: If True, draws samples in parallel.
|
|
432
|
+
n_jobs: Number of CPU cores to use. -1 means all available.
|
|
433
|
+
"""
|
|
413
434
|
if n < 1:
|
|
414
435
|
raise ValueError("Number of samples must be a positive integer.")
|
|
415
|
-
return self.domain.sample_expectation(
|
|
436
|
+
return self.domain.sample_expectation(
|
|
437
|
+
self.samples(n, parallel=parallel, n_jobs=n_jobs)
|
|
438
|
+
)
|
|
416
439
|
|
|
417
|
-
def sample_pointwise_variance(
|
|
440
|
+
def sample_pointwise_variance(
|
|
441
|
+
self, n: int, /, *, parallel: bool = False, n_jobs: int = -1
|
|
442
|
+
) -> Vector:
|
|
418
443
|
"""
|
|
419
444
|
Estimates the pointwise variance by drawing n samples.
|
|
420
445
|
|
|
421
|
-
|
|
422
|
-
|
|
446
|
+
Args:
|
|
447
|
+
n: Number of samples to draw.
|
|
448
|
+
parallel: If True, draws samples in parallel.
|
|
449
|
+
n_jobs: Number of CPU cores to use. -1 means all available.
|
|
423
450
|
"""
|
|
424
451
|
if not isinstance(self.domain, HilbertModule):
|
|
425
452
|
raise NotImplementedError(
|
|
@@ -428,7 +455,10 @@ class GaussianMeasure:
|
|
|
428
455
|
if n < 1:
|
|
429
456
|
raise ValueError("Number of samples must be a positive integer.")
|
|
430
457
|
|
|
431
|
-
|
|
458
|
+
# Draw samples
|
|
459
|
+
samples = self.samples(n, parallel=parallel, n_jobs=n_jobs)
|
|
460
|
+
|
|
461
|
+
# Compute variance using vector arithmetic
|
|
432
462
|
expectation = self.expectation
|
|
433
463
|
variance = self.domain.zero
|
|
434
464
|
|
|
@@ -439,6 +469,42 @@ class GaussianMeasure:
|
|
|
439
469
|
|
|
440
470
|
return variance
|
|
441
471
|
|
|
472
|
+
def sample_pointwise_std(
|
|
473
|
+
self, n: int, /, *, parallel: bool = False, n_jobs: int = -1
|
|
474
|
+
) -> Vector:
|
|
475
|
+
"""
|
|
476
|
+
Estimates the pointwise standard deviation by drawing n samples.
|
|
477
|
+
|
|
478
|
+
Args:
|
|
479
|
+
n: Number of samples to draw.
|
|
480
|
+
parallel: If True, draws samples in parallel.
|
|
481
|
+
n_jobs: Number of CPU cores to use. -1 means all available.
|
|
482
|
+
"""
|
|
483
|
+
variance = self.sample_pointwise_variance(n, parallel=parallel, n_jobs=n_jobs)
|
|
484
|
+
return self.domain.vector_sqrt(variance)
|
|
485
|
+
|
|
486
|
+
def with_dense_covariance(self, parallel: bool = False, n_jobs: int = -1):
|
|
487
|
+
"""
|
|
488
|
+
Forms a new Gaussian measure equivalent to the existing one, but
|
|
489
|
+
with its covariance matrix stored in dense form. The dense matrix
|
|
490
|
+
calculation can optionally be parallelised.
|
|
491
|
+
|
|
492
|
+
Args:
|
|
493
|
+
parallel: If True, computes the covariance in parallel.
|
|
494
|
+
n_jobs: Number of CPU cores to use. -1 means all available.
|
|
495
|
+
|
|
496
|
+
Returns:
|
|
497
|
+
The new Gaussian measure.
|
|
498
|
+
"""
|
|
499
|
+
|
|
500
|
+
covariance_matrix = self.covariance.matrix(
|
|
501
|
+
dense=True, galerkin=True, parallel=parallel, n_jobs=n_jobs
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
return GaussianMeasure.from_covariance_matrix(
|
|
505
|
+
self.domain, covariance_matrix, expectation=self.expectation
|
|
506
|
+
)
|
|
507
|
+
|
|
442
508
|
def affine_mapping(
|
|
443
509
|
self, /, *, operator: LinearOperator = None, translation: Vector = None
|
|
444
510
|
) -> GaussianMeasure:
|
|
@@ -516,7 +582,7 @@ class GaussianMeasure:
|
|
|
516
582
|
|
|
517
583
|
# Pass the parallelization arguments directly to the matrix creation method
|
|
518
584
|
cov_matrix = self.covariance.matrix(
|
|
519
|
-
dense=True, parallel=parallel, n_jobs=n_jobs
|
|
585
|
+
dense=True, galerkin=True, parallel=parallel, n_jobs=n_jobs
|
|
520
586
|
)
|
|
521
587
|
|
|
522
588
|
try:
|
|
@@ -547,6 +547,12 @@ class HilbertModule(HilbertSpace, ABC):
|
|
|
547
547
|
The product of the two vectors.
|
|
548
548
|
"""
|
|
549
549
|
|
|
550
|
+
@abstractmethod
|
|
551
|
+
def vector_sqrt(self, x: Vector) -> Vector:
|
|
552
|
+
"""
|
|
553
|
+
Returns the square root of a vector.
|
|
554
|
+
"""
|
|
555
|
+
|
|
550
556
|
|
|
551
557
|
class EuclideanSpace(HilbertSpace):
|
|
552
558
|
"""
|
|
@@ -829,3 +835,12 @@ class MassWeightedHilbertModule(MassWeightedHilbertSpace, HilbertModule):
|
|
|
829
835
|
is itself an instance of `HilbertModule`.
|
|
830
836
|
"""
|
|
831
837
|
return self.underlying_space.vector_multiply(x1, x2)
|
|
838
|
+
|
|
839
|
+
def vector_sqrt(self, x: Vector) -> Vector:
|
|
840
|
+
"""
|
|
841
|
+
Computes vector multiplication by delegating to the underlying space.
|
|
842
|
+
|
|
843
|
+
Note: This assumes the underlying space provided during initialization
|
|
844
|
+
is itself an instance of `HilbertModule`.
|
|
845
|
+
"""
|
|
846
|
+
return self.underlying_space.vector_sqrt(x)
|
|
@@ -102,10 +102,13 @@ class LinearOperator(NonLinearOperator, LinearOperatorAxiomChecks):
|
|
|
102
102
|
self.__adjoint_mapping: Callable[[Any], Any]
|
|
103
103
|
self.__dual_mapping: Callable[[Any], Any]
|
|
104
104
|
|
|
105
|
+
self.__using_default_dual_and_adjoint = False
|
|
106
|
+
|
|
105
107
|
if dual_mapping is None:
|
|
106
108
|
if adjoint_mapping is None:
|
|
107
109
|
self.__dual_mapping = self._dual_mapping_default
|
|
108
110
|
self.__adjoint_mapping = self._adjoint_mapping_from_dual
|
|
111
|
+
self.__using_default_dual_and_adjoint = True
|
|
109
112
|
else:
|
|
110
113
|
self.__adjoint_mapping = adjoint_mapping
|
|
111
114
|
self.__dual_mapping = self._dual_mapping_from_adjoint
|
|
@@ -919,6 +922,35 @@ class LinearOperator(NonLinearOperator, LinearOperatorAxiomChecks):
|
|
|
919
922
|
self, galerkin: bool, parallel: bool, n_jobs: int
|
|
920
923
|
) -> np.ndarray:
|
|
921
924
|
|
|
925
|
+
# Optimization: If the codomain is smaller than the domain, it is cheaper
|
|
926
|
+
# to compute the matrix of the adjoint/dual (which has fewer columns)
|
|
927
|
+
# and transpose the result.
|
|
928
|
+
|
|
929
|
+
# Note: This recursion naturally terminates because the adjoint/dual
|
|
930
|
+
# swaps the domain and codomain. In the recursive call,
|
|
931
|
+
# (codomain.dim < domain.dim) will be False, forcing the standard path.
|
|
932
|
+
|
|
933
|
+
# If the operator has its dual and adjoint actions done using the
|
|
934
|
+
# default implementation, this optimisation is skipped.
|
|
935
|
+
if (
|
|
936
|
+
self.codomain.dim < self.domain.dim
|
|
937
|
+
and not self.__using_default_dual_and_adjoint
|
|
938
|
+
):
|
|
939
|
+
if galerkin:
|
|
940
|
+
# For Galerkin representations: Matrix(L) = Matrix(L*).T
|
|
941
|
+
return self.adjoint.matrix(
|
|
942
|
+
dense=True, galerkin=True, parallel=parallel, n_jobs=n_jobs
|
|
943
|
+
).T
|
|
944
|
+
else:
|
|
945
|
+
# For Standard representations: Matrix(L) = Matrix(L').T
|
|
946
|
+
return self.dual.matrix(
|
|
947
|
+
dense=True, galerkin=False, parallel=parallel, n_jobs=n_jobs
|
|
948
|
+
).T
|
|
949
|
+
|
|
950
|
+
# --- Standard Column-wise Construction ---
|
|
951
|
+
# This block executes if optimization is not applicable (or in the
|
|
952
|
+
# recursive base case).
|
|
953
|
+
|
|
922
954
|
scipy_op_wrapper = self.matrix(galerkin=galerkin)
|
|
923
955
|
|
|
924
956
|
if not parallel:
|
|
@@ -220,6 +220,8 @@ def plot_corner_distributions(
|
|
|
220
220
|
show_plot: bool = True,
|
|
221
221
|
include_sigma_contours: bool = True,
|
|
222
222
|
colormap: str = "Blues",
|
|
223
|
+
parallel: bool = False,
|
|
224
|
+
n_jobs: int = -1,
|
|
223
225
|
):
|
|
224
226
|
"""
|
|
225
227
|
Create a corner plot for multi-dimensional posterior distributions.
|
|
@@ -233,6 +235,8 @@ def plot_corner_distributions(
|
|
|
233
235
|
show_plot: Whether to display the plot
|
|
234
236
|
include_sigma_contours: Whether to include 1-sigma contour lines
|
|
235
237
|
colormap: Colormap for 2D plots
|
|
238
|
+
parallel: Compute dense covariance matrix in parallel, default False.
|
|
239
|
+
n_jobs: Number of cores to use in parallel calculations, default -1.
|
|
236
240
|
|
|
237
241
|
Returns:
|
|
238
242
|
fig, axes: Figure and axes array
|
|
@@ -243,7 +247,9 @@ def plot_corner_distributions(
|
|
|
243
247
|
posterior_measure, "covariance"
|
|
244
248
|
):
|
|
245
249
|
mean_posterior = posterior_measure.expectation
|
|
246
|
-
cov_posterior = posterior_measure.covariance.matrix(
|
|
250
|
+
cov_posterior = posterior_measure.covariance.matrix(
|
|
251
|
+
dense=True, parallel=parallel, n_jobs=n_jobs
|
|
252
|
+
)
|
|
247
253
|
else:
|
|
248
254
|
raise ValueError(
|
|
249
255
|
"posterior_measure must have 'expectation' and 'covariance' attributes"
|
|
@@ -36,7 +36,7 @@ class JacobiPreconditioningMethod(LinearSolver):
|
|
|
36
36
|
method: str = "variable",
|
|
37
37
|
rtol: float = 1e-2,
|
|
38
38
|
block_size: int = 10,
|
|
39
|
-
parallel: bool =
|
|
39
|
+
parallel: bool = False,
|
|
40
40
|
n_jobs: int = -1,
|
|
41
41
|
) -> None:
|
|
42
42
|
# Damping is removed: the operator passed to __call__ is already damped
|