pygeoinf 1.1.8__py3-none-any.whl → 1.1.9__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/direct_sum.py +103 -0
- pygeoinf/gaussian_measure.py +3 -1
- pygeoinf/linear_solvers.py +1 -3
- pygeoinf/operators.py +137 -14
- pygeoinf/parallel.py +73 -0
- pygeoinf/random_matrix.py +22 -5
- {pygeoinf-1.1.8.dist-info → pygeoinf-1.1.9.dist-info}/METADATA +2 -1
- {pygeoinf-1.1.8.dist-info → pygeoinf-1.1.9.dist-info}/RECORD +10 -9
- {pygeoinf-1.1.8.dist-info → pygeoinf-1.1.9.dist-info}/LICENSE +0 -0
- {pygeoinf-1.1.8.dist-info → pygeoinf-1.1.9.dist-info}/WHEEL +0 -0
pygeoinf/direct_sum.py
CHANGED
|
@@ -25,10 +25,13 @@ from __future__ import annotations
|
|
|
25
25
|
from abc import ABC, abstractmethod
|
|
26
26
|
from typing import List, Any
|
|
27
27
|
import numpy as np
|
|
28
|
+
from scipy.linalg import block_diag
|
|
29
|
+
from joblib import Parallel, delayed
|
|
28
30
|
|
|
29
31
|
from .hilbert_space import HilbertSpace
|
|
30
32
|
from .operators import LinearOperator
|
|
31
33
|
from .linear_forms import LinearForm
|
|
34
|
+
from .parallel import parallel_compute_dense_matrix_from_scipy_op
|
|
32
35
|
|
|
33
36
|
|
|
34
37
|
class HilbertSpaceDirectSum(HilbertSpace):
|
|
@@ -300,6 +303,40 @@ class BlockLinearOperator(LinearOperator, BlockStructure):
|
|
|
300
303
|
self._check_block_indices(i, j)
|
|
301
304
|
return self._blocks[i][j]
|
|
302
305
|
|
|
306
|
+
def _compute_dense_matrix(
|
|
307
|
+
self, galerkin: bool, parallel: bool, n_jobs: int
|
|
308
|
+
) -> np.ndarray:
|
|
309
|
+
"""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
|
+
|
|
322
|
+
else:
|
|
323
|
+
block_scipy_ops = [
|
|
324
|
+
self.block(i, j).matrix(galerkin=galerkin)
|
|
325
|
+
for i in range(self.row_dim)
|
|
326
|
+
for j in range(self.col_dim)
|
|
327
|
+
]
|
|
328
|
+
|
|
329
|
+
computed_blocks = Parallel(n_jobs=n_jobs)(
|
|
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)
|
|
339
|
+
|
|
303
340
|
def __mapping(self, xs: List[Any]) -> List[Any]:
|
|
304
341
|
|
|
305
342
|
ys = []
|
|
@@ -379,6 +416,28 @@ class ColumnLinearOperator(LinearOperator, BlockStructure):
|
|
|
379
416
|
raise IndexError("Column index out of range for ColumnLinearOperator.")
|
|
380
417
|
return self._operators[i]
|
|
381
418
|
|
|
419
|
+
def _compute_dense_matrix(
|
|
420
|
+
self, galerkin: bool, parallel: bool, n_jobs: int
|
|
421
|
+
) -> np.ndarray:
|
|
422
|
+
"""Overloaded method to efficiently compute the dense matrix for a column operator."""
|
|
423
|
+
if not parallel:
|
|
424
|
+
block_matrices = [
|
|
425
|
+
op.matrix(dense=True, galerkin=galerkin, parallel=False)
|
|
426
|
+
for op in self._operators
|
|
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)
|
|
440
|
+
|
|
382
441
|
|
|
383
442
|
class RowLinearOperator(LinearOperator, BlockStructure):
|
|
384
443
|
"""
|
|
@@ -433,6 +492,28 @@ class RowLinearOperator(LinearOperator, BlockStructure):
|
|
|
433
492
|
raise IndexError("Row index out of range for RowLinearOperator.")
|
|
434
493
|
return self._operators[j]
|
|
435
494
|
|
|
495
|
+
def _compute_dense_matrix(
|
|
496
|
+
self, galerkin: bool, parallel: bool, n_jobs: int
|
|
497
|
+
) -> np.ndarray:
|
|
498
|
+
"""Overloaded method to efficiently compute the dense matrix for a row operator."""
|
|
499
|
+
if not parallel:
|
|
500
|
+
block_matrices = [
|
|
501
|
+
op.matrix(dense=True, galerkin=galerkin, parallel=False)
|
|
502
|
+
for op in self._operators
|
|
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)
|
|
516
|
+
|
|
436
517
|
|
|
437
518
|
class BlockDiagonalLinearOperator(LinearOperator, BlockStructure):
|
|
438
519
|
"""
|
|
@@ -473,3 +554,25 @@ class BlockDiagonalLinearOperator(LinearOperator, BlockStructure):
|
|
|
473
554
|
domain = self._operators[j].domain
|
|
474
555
|
codomain = self._operators[i].codomain
|
|
475
556
|
return domain.zero_operator(codomain)
|
|
557
|
+
|
|
558
|
+
def _compute_dense_matrix(
|
|
559
|
+
self, galerkin: bool, parallel: bool, n_jobs: int
|
|
560
|
+
) -> np.ndarray:
|
|
561
|
+
"""Overloaded method to efficiently compute the dense matrix for a block-diagonal operator."""
|
|
562
|
+
if not parallel:
|
|
563
|
+
block_matrices = [
|
|
564
|
+
op.matrix(dense=True, galerkin=galerkin, parallel=False)
|
|
565
|
+
for op in self._operators
|
|
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)
|
pygeoinf/gaussian_measure.py
CHANGED
|
@@ -484,7 +484,9 @@ class GaussianMeasure:
|
|
|
484
484
|
)
|
|
485
485
|
|
|
486
486
|
return multivariate_normal(
|
|
487
|
-
mean=self.expectation,
|
|
487
|
+
mean=self.expectation,
|
|
488
|
+
cov=self.covariance.matrix(dense=True),
|
|
489
|
+
allow_singular=True,
|
|
488
490
|
)
|
|
489
491
|
|
|
490
492
|
def low_rank_approximation(
|
pygeoinf/linear_solvers.py
CHANGED
|
@@ -173,9 +173,7 @@ class IterativeLinearSolver(LinearSolver):
|
|
|
173
173
|
Solves the adjoint linear system A*y = x for y.
|
|
174
174
|
"""
|
|
175
175
|
adjoint_preconditioner = (
|
|
176
|
-
None
|
|
177
|
-
if preconditioner is None
|
|
178
|
-
else preconditioner.adjoint
|
|
176
|
+
None if preconditioner is None else preconditioner.adjoint
|
|
179
177
|
)
|
|
180
178
|
return self.solve_linear_system(operator.adjoint, adjoint_preconditioner, x, y0)
|
|
181
179
|
|
pygeoinf/operators.py
CHANGED
|
@@ -35,12 +35,36 @@ from .random_matrix import (
|
|
|
35
35
|
random_eig as rm_eig,
|
|
36
36
|
)
|
|
37
37
|
|
|
38
|
+
from .parallel import parallel_compute_dense_matrix_from_scipy_op
|
|
39
|
+
|
|
38
40
|
# This block only runs for type checkers, not at runtime
|
|
39
41
|
if TYPE_CHECKING:
|
|
40
42
|
from .hilbert_space import HilbertSpace, EuclideanSpace
|
|
41
43
|
from .linear_forms import LinearForm
|
|
42
44
|
|
|
43
45
|
|
|
46
|
+
def _parallel_scipy_op_col(scipy_op: ScipyLinOp, j: int, domain_dim: int) -> np.ndarray:
|
|
47
|
+
"""
|
|
48
|
+
A top-level helper that applies a scipy.LinearOperator to a basis vector.
|
|
49
|
+
|
|
50
|
+
This function is simple and serializable ("picklable").
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
scipy_op: The SciPy LinearOperator wrapper for the matrix action.
|
|
54
|
+
j: The index of the basis vector (column) to compute.
|
|
55
|
+
domain_dim: The dimension of the domain space.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
The j-th column of the dense matrix as a NumPy array.
|
|
59
|
+
"""
|
|
60
|
+
# Create the j-th component basis vector
|
|
61
|
+
cx = np.zeros(domain_dim)
|
|
62
|
+
cx[j] = 1.0
|
|
63
|
+
|
|
64
|
+
# Apply the SciPy wrapper, which handles all necessary conversions
|
|
65
|
+
return scipy_op @ cx
|
|
66
|
+
|
|
67
|
+
|
|
44
68
|
class Operator:
|
|
45
69
|
"""
|
|
46
70
|
A general, potentially non-linear operator between two Hilbert spaces.
|
|
@@ -489,7 +513,13 @@ class LinearOperator(Operator):
|
|
|
489
513
|
return self._thread_safe
|
|
490
514
|
|
|
491
515
|
def matrix(
|
|
492
|
-
self,
|
|
516
|
+
self,
|
|
517
|
+
/,
|
|
518
|
+
*,
|
|
519
|
+
dense: bool = False,
|
|
520
|
+
galerkin: bool = False,
|
|
521
|
+
parallel: bool = False,
|
|
522
|
+
n_jobs: int = -1,
|
|
493
523
|
) -> Union[ScipyLinOp, np.ndarray]:
|
|
494
524
|
"""
|
|
495
525
|
Returns a matrix representation of the operator.
|
|
@@ -529,12 +559,17 @@ class LinearOperator(Operator):
|
|
|
529
559
|
self-adjoint, its Galerkin matrix will be **symmetric**, which is a
|
|
530
560
|
prerequisite for algorithms like the Conjugate Gradient method.
|
|
531
561
|
|
|
562
|
+
parallel (bool): If True, use parallel computing. Defaults to False.
|
|
563
|
+
This is only relevant for dense matrices.
|
|
564
|
+
n_jobs (int): Number of parallel jobs. Defaults to -1.
|
|
565
|
+
This is only relevant for dense matrices.
|
|
566
|
+
|
|
532
567
|
Returns:
|
|
533
568
|
Union[ScipyLinOp, np.ndarray]: The matrix representation of the
|
|
534
569
|
operator, either as a dense array or a matrix-free object.
|
|
535
570
|
"""
|
|
536
571
|
if dense:
|
|
537
|
-
return self._compute_dense_matrix(galerkin)
|
|
572
|
+
return self._compute_dense_matrix(galerkin, parallel, n_jobs)
|
|
538
573
|
else:
|
|
539
574
|
if galerkin:
|
|
540
575
|
|
|
@@ -597,9 +632,35 @@ class LinearOperator(Operator):
|
|
|
597
632
|
galerkin: bool = False,
|
|
598
633
|
rtol: float = 1e-3,
|
|
599
634
|
method: str = "fixed",
|
|
635
|
+
parallel: bool = False,
|
|
636
|
+
n_jobs: int = -1,
|
|
600
637
|
) -> Tuple[LinearOperator, "DiagonalLinearOperator", LinearOperator]:
|
|
601
638
|
"""
|
|
602
639
|
Computes an approximate SVD using a randomized algorithm.
|
|
640
|
+
|
|
641
|
+
Args:
|
|
642
|
+
rank (int): The desired rank of the SVD.
|
|
643
|
+
power (int): The power of the random matrix.
|
|
644
|
+
galerkin (bool): If True, use the Galerkin representation.
|
|
645
|
+
rtol (float): The relative tolerance for the SVD.
|
|
646
|
+
method (str): The method to use for the SVD.
|
|
647
|
+
- "fixed": Use a fixed rank SVD.
|
|
648
|
+
- "variable": Use a variable rank SVD.
|
|
649
|
+
parallel (bool): If True, use parallel computing. Defaults to False.
|
|
650
|
+
Only used with fixed rank method.
|
|
651
|
+
n_jobs (int): Number of parallel jobs. Defaults to -1.
|
|
652
|
+
Only used with fixed rank method.
|
|
653
|
+
|
|
654
|
+
Returns:
|
|
655
|
+
left (LinearOperator): The left singular vector matrix.
|
|
656
|
+
singular_values (DiagonalLinearOperator): The singular values.
|
|
657
|
+
right (LinearOperator): The right singular vector matrix.
|
|
658
|
+
|
|
659
|
+
Notes:
|
|
660
|
+
The right factor is in transposed form. This means the original
|
|
661
|
+
operator can be approximated as:
|
|
662
|
+
A = left @ singular_values @ right
|
|
663
|
+
|
|
603
664
|
"""
|
|
604
665
|
from .hilbert_space import EuclideanSpace
|
|
605
666
|
|
|
@@ -610,7 +671,9 @@ class LinearOperator(Operator):
|
|
|
610
671
|
|
|
611
672
|
qr_factor: np.ndarray
|
|
612
673
|
if method == "fixed":
|
|
613
|
-
qr_factor = fixed_rank_random_range(
|
|
674
|
+
qr_factor = fixed_rank_random_range(
|
|
675
|
+
matrix, rank, power=power, parallel=parallel, n_jobs=n_jobs
|
|
676
|
+
)
|
|
614
677
|
elif method == "variable":
|
|
615
678
|
qr_factor = variable_rank_random_range(matrix, rank, power=power, rtol=rtol)
|
|
616
679
|
else:
|
|
@@ -648,10 +711,34 @@ class LinearOperator(Operator):
|
|
|
648
711
|
power: int = 0,
|
|
649
712
|
rtol: float = 1e-3,
|
|
650
713
|
method: str = "fixed",
|
|
714
|
+
parallel: bool = False,
|
|
715
|
+
n_jobs: int = -1,
|
|
651
716
|
) -> Tuple[LinearOperator, "DiagonalLinearOperator"]:
|
|
652
717
|
"""
|
|
653
718
|
Computes an approximate eigendecomposition for a self-adjoint
|
|
654
719
|
operator using a randomized algorithm.
|
|
720
|
+
|
|
721
|
+
Args:
|
|
722
|
+
rank (int): The desired rank of the eigendecomposition.
|
|
723
|
+
power (int): The power of the random matrix.
|
|
724
|
+
rtol (float): The relative tolerance for the eigendecomposition.
|
|
725
|
+
method (str): The method to use for the eigendecomposition.
|
|
726
|
+
- "fixed": Use a fixed rank eigendecomposition.
|
|
727
|
+
- "variable": Use a variable rank eigendecomposition.
|
|
728
|
+
parallel (bool): If True, use parallel computing. Defaults to False.
|
|
729
|
+
Only used with fixed rank method.
|
|
730
|
+
n_jobs (int): Number of parallel jobs. Defaults to -1.
|
|
731
|
+
Only used with fixed rank method.
|
|
732
|
+
|
|
733
|
+
Returns:
|
|
734
|
+
expansion (LinearOperator): A linear operator that maps coefficients
|
|
735
|
+
in the eigen-basis to the resulting vector.
|
|
736
|
+
eigenvalues (DiagonalLinearOperator): The eigenvalues.
|
|
737
|
+
|
|
738
|
+
Notes:
|
|
739
|
+
The original operator can be approximated as:
|
|
740
|
+
A = expansion @ eigenvalues @ expansion.adjoint
|
|
741
|
+
|
|
655
742
|
"""
|
|
656
743
|
from .hilbert_space import EuclideanSpace
|
|
657
744
|
|
|
@@ -663,7 +750,9 @@ class LinearOperator(Operator):
|
|
|
663
750
|
|
|
664
751
|
qr_factor: np.ndarray
|
|
665
752
|
if method == "fixed":
|
|
666
|
-
qr_factor = fixed_rank_random_range(
|
|
753
|
+
qr_factor = fixed_rank_random_range(
|
|
754
|
+
matrix, rank, power=power, parallel=parallel, n_jobs=n_jobs
|
|
755
|
+
)
|
|
667
756
|
elif method == "variable":
|
|
668
757
|
qr_factor = variable_rank_random_range(matrix, rank, power=power, rtol=rtol)
|
|
669
758
|
else:
|
|
@@ -687,11 +776,34 @@ class LinearOperator(Operator):
|
|
|
687
776
|
power: int = 0,
|
|
688
777
|
rtol: float = 1e-3,
|
|
689
778
|
method: str = "fixed",
|
|
779
|
+
parallel: bool = False,
|
|
780
|
+
n_jobs: int = -1,
|
|
690
781
|
) -> LinearOperator:
|
|
691
782
|
"""
|
|
692
783
|
Computes an approximate Cholesky decomposition for a positive-definite
|
|
693
784
|
self-adjoint operator using a randomized algorithm.
|
|
785
|
+
|
|
786
|
+
Args:
|
|
787
|
+
rank (int): The desired rank of the Cholesky decomposition.
|
|
788
|
+
power (int): The power of the random matrix.
|
|
789
|
+
rtol (float): The relative tolerance for the Cholesky decomposition.
|
|
790
|
+
method (str): The method to use for the Cholesky decomposition.
|
|
791
|
+
- "fixed": Use a fixed rank Cholesky decomposition.
|
|
792
|
+
- "variable": Use a variable rank Cholesky decomposition.
|
|
793
|
+
parallel (bool): If True, use parallel computing. Defaults to False.
|
|
794
|
+
Only used with fixed rank method.
|
|
795
|
+
n_jobs (int): Number of parallel jobs. Defaults to -1.
|
|
796
|
+
Only used with fixed rank method.
|
|
797
|
+
|
|
798
|
+
Returns:
|
|
799
|
+
factor (LinearOperator): A linear operator from a Euclidean space
|
|
800
|
+
into the domain of the operator.
|
|
801
|
+
|
|
802
|
+
Notes:
|
|
803
|
+
The original operator can be approximated as:
|
|
804
|
+
A = factor @ factor.adjoint
|
|
694
805
|
"""
|
|
806
|
+
|
|
695
807
|
from .hilbert_space import EuclideanSpace
|
|
696
808
|
|
|
697
809
|
assert self.is_automorphism
|
|
@@ -702,7 +814,9 @@ class LinearOperator(Operator):
|
|
|
702
814
|
|
|
703
815
|
qr_factor: np.ndarray
|
|
704
816
|
if method == "fixed":
|
|
705
|
-
qr_factor = fixed_rank_random_range(
|
|
817
|
+
qr_factor = fixed_rank_random_range(
|
|
818
|
+
matrix, rank, power=power, parallel=parallel, n_jobs=n_jobs
|
|
819
|
+
)
|
|
706
820
|
elif method == "variable":
|
|
707
821
|
qr_factor = variable_rank_random_range(matrix, rank, power=power, rtol=rtol)
|
|
708
822
|
else:
|
|
@@ -732,15 +846,24 @@ class LinearOperator(Operator):
|
|
|
732
846
|
xp = self.__dual_mapping(yp)
|
|
733
847
|
return self.domain.from_dual(xp)
|
|
734
848
|
|
|
735
|
-
def _compute_dense_matrix(
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
849
|
+
def _compute_dense_matrix(
|
|
850
|
+
self, galerkin: bool, parallel: bool, n_jobs: int
|
|
851
|
+
) -> np.ndarray:
|
|
852
|
+
|
|
853
|
+
scipy_op_wrapper = self.matrix(galerkin=galerkin)
|
|
854
|
+
|
|
855
|
+
if not parallel:
|
|
856
|
+
matrix = np.zeros((self.codomain.dim, self.domain.dim))
|
|
857
|
+
cx = np.zeros(self.domain.dim)
|
|
858
|
+
for i in range(self.domain.dim):
|
|
859
|
+
cx[i] = 1.0
|
|
860
|
+
matrix[:, i] = (scipy_op_wrapper @ cx)[:]
|
|
861
|
+
cx[i] = 0.0
|
|
862
|
+
return matrix
|
|
863
|
+
else:
|
|
864
|
+
return parallel_compute_dense_matrix_from_scipy_op(
|
|
865
|
+
scipy_op_wrapper, n_jobs=n_jobs
|
|
866
|
+
)
|
|
744
867
|
|
|
745
868
|
def __neg__(self) -> LinearOperator:
|
|
746
869
|
domain = self.domain
|
pygeoinf/parallel.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A collection of helper functions for parallel computation using Joblib.
|
|
3
|
+
|
|
4
|
+
These functions are designed to be top-level to ensure they can be
|
|
5
|
+
"pickled" (serialized) and sent to worker processes by libraries like
|
|
6
|
+
multiprocessing or its wrapper, Joblib.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
from typing import TYPE_CHECKING, Union
|
|
11
|
+
import numpy as np
|
|
12
|
+
from joblib import Parallel, delayed
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from scipy.sparse.linalg import LinearOperator as ScipyLinOp
|
|
16
|
+
|
|
17
|
+
MatrixLike = Union[np.ndarray, ScipyLinOp]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def parallel_mat_mat(A: "MatrixLike", B: np.ndarray, n_jobs: int = -1) -> np.ndarray:
|
|
21
|
+
"""
|
|
22
|
+
Computes the matrix product A @ B in parallel by applying A to each column of B.
|
|
23
|
+
|
|
24
|
+
This is particularly useful when A is a LinearOperator whose action is
|
|
25
|
+
computationally expensive.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
A: The matrix or LinearOperator to apply.
|
|
29
|
+
B: The matrix whose columns will be operated on.
|
|
30
|
+
n_jobs: The number of CPU cores to use. -1 means all available.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
The result of the matrix product A @ B as a dense NumPy array.
|
|
34
|
+
"""
|
|
35
|
+
columns = Parallel(n_jobs=n_jobs)(
|
|
36
|
+
delayed(A.__matmul__)(B[:, i]) for i in range(B.shape[1])
|
|
37
|
+
)
|
|
38
|
+
return np.column_stack(columns)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def parallel_compute_dense_matrix_from_scipy_op(
|
|
42
|
+
scipy_op: "ScipyLinOp", n_jobs: int = -1
|
|
43
|
+
) -> np.ndarray:
|
|
44
|
+
"""
|
|
45
|
+
Computes the dense matrix representation of a scipy.LinearOperator in parallel.
|
|
46
|
+
|
|
47
|
+
It builds the matrix column by column by applying the operator to each
|
|
48
|
+
basis vector.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
scipy_op: The SciPy LinearOperator wrapper for the matrix action.
|
|
52
|
+
n_jobs: The number of CPU cores to use. -1 means all available.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
The dense matrix as a NumPy array.
|
|
56
|
+
"""
|
|
57
|
+
codomain_dim, domain_dim = scipy_op.shape
|
|
58
|
+
columns = Parallel(n_jobs=n_jobs)(
|
|
59
|
+
delayed(_worker_compute_scipy_op_col)(scipy_op, j, domain_dim)
|
|
60
|
+
for j in range(domain_dim)
|
|
61
|
+
)
|
|
62
|
+
return np.column_stack(columns)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _worker_compute_scipy_op_col(
|
|
66
|
+
scipy_op: "ScipyLinOp", j: int, domain_dim: int
|
|
67
|
+
) -> np.ndarray:
|
|
68
|
+
"""
|
|
69
|
+
(Internal worker) Computes a single column of a SciPy LinearOperator's matrix.
|
|
70
|
+
"""
|
|
71
|
+
cx = np.zeros(domain_dim)
|
|
72
|
+
cx[j] = 1.0
|
|
73
|
+
return scipy_op @ cx
|
pygeoinf/random_matrix.py
CHANGED
|
@@ -25,13 +25,18 @@ from scipy.linalg import (
|
|
|
25
25
|
)
|
|
26
26
|
from scipy.sparse.linalg import LinearOperator as ScipyLinOp
|
|
27
27
|
|
|
28
|
+
from .parallel import parallel_mat_mat
|
|
28
29
|
|
|
29
30
|
# A type for objects that act like matrices (numpy arrays or SciPy LinearOperators)
|
|
30
31
|
MatrixLike = Union[np.ndarray, ScipyLinOp]
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
def fixed_rank_random_range(
|
|
34
|
-
matrix: MatrixLike,
|
|
35
|
+
matrix: MatrixLike,
|
|
36
|
+
rank: int,
|
|
37
|
+
power: int = 0,
|
|
38
|
+
parallel: bool = False,
|
|
39
|
+
n_jobs: int = -1,
|
|
35
40
|
) -> np.ndarray:
|
|
36
41
|
"""
|
|
37
42
|
Computes an orthonormal basis for a fixed-rank approximation of a matrix's range.
|
|
@@ -53,17 +58,29 @@ def fixed_rank_random_range(
|
|
|
53
58
|
Notes:
|
|
54
59
|
Based on Algorithm 4.4 in Halko et al. 2011.
|
|
55
60
|
"""
|
|
56
|
-
|
|
57
61
|
m, n = matrix.shape
|
|
58
62
|
random_matrix = np.random.randn(n, rank)
|
|
59
63
|
|
|
60
|
-
|
|
64
|
+
if parallel:
|
|
65
|
+
product_matrix = parallel_mat_mat(matrix, random_matrix, n_jobs)
|
|
66
|
+
else:
|
|
67
|
+
product_matrix = matrix @ random_matrix
|
|
68
|
+
|
|
61
69
|
qr_factor, _ = qr(product_matrix, overwrite_a=True, mode="economic")
|
|
62
70
|
|
|
63
71
|
for _ in range(power):
|
|
64
|
-
|
|
72
|
+
if parallel:
|
|
73
|
+
tilde_product_matrix = parallel_mat_mat(matrix.T, qr_factor, n_jobs)
|
|
74
|
+
else:
|
|
75
|
+
tilde_product_matrix = matrix.T @ qr_factor
|
|
76
|
+
|
|
65
77
|
tilde_qr_factor, _ = qr(tilde_product_matrix, overwrite_a=True, mode="economic")
|
|
66
|
-
|
|
78
|
+
|
|
79
|
+
if parallel:
|
|
80
|
+
product_matrix = parallel_mat_mat(matrix, tilde_qr_factor, n_jobs)
|
|
81
|
+
else:
|
|
82
|
+
product_matrix = matrix @ tilde_qr_factor
|
|
83
|
+
|
|
67
84
|
qr_factor, _ = qr(product_matrix, overwrite_a=True, mode="economic")
|
|
68
85
|
|
|
69
86
|
return qr_factor
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: pygeoinf
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.9
|
|
4
4
|
Summary: A package for solving geophysical inference and inverse problems
|
|
5
5
|
License: BSD-3-Clause
|
|
6
6
|
Author: David Al-Attar and Dan Heathcote
|
|
@@ -11,6 +11,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.13
|
|
13
13
|
Requires-Dist: Cartopy (>=0.23.0,<0.24.0)
|
|
14
|
+
Requires-Dist: joblib (>=1.5.2,<2.0.0)
|
|
14
15
|
Requires-Dist: matplotlib (>=3.0.0)
|
|
15
16
|
Requires-Dist: numpy (>=1.26.0)
|
|
16
17
|
Requires-Dist: pyqt6 (>=6.0.0)
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
pygeoinf/__init__.py,sha256=uYRwiHKi4nvzUBwQJxNdt8OZodFQ_nj5rUv2YkDXL9Q,1185
|
|
2
|
-
pygeoinf/direct_sum.py,sha256=
|
|
2
|
+
pygeoinf/direct_sum.py,sha256=ppBWJLjwF1MPF3bL1tJf_ADt_jZw4tnxhiC1DUqPEew,20919
|
|
3
3
|
pygeoinf/forward_problem.py,sha256=RnnVBMg8Ih7TqZylfSkQ7pdHEowEfCkTiXiKFrxBpnM,9754
|
|
4
|
-
pygeoinf/gaussian_measure.py,sha256=
|
|
4
|
+
pygeoinf/gaussian_measure.py,sha256=X31D-NoBB5f7uagIRsAjTPqAJqcyTOpgUUvBnaWmiAw,22872
|
|
5
5
|
pygeoinf/hilbert_space.py,sha256=90aaUPUBCqsEuXroOwCmvbRFhi1vf6F9bS4pU3DCseI,23369
|
|
6
6
|
pygeoinf/inversion.py,sha256=p9k_iDVgJGLM1cGlT-0rgRwqdYVdsYC_euTXZk3kuOc,3199
|
|
7
7
|
pygeoinf/linear_bayesian.py,sha256=aIOzTZbjJtdtwHKh5e01iS8iMiyr8XuwGx91udS3VK4,9624
|
|
8
8
|
pygeoinf/linear_forms.py,sha256=Uizipi67i1Sd6m0TzsrJd99Xreo_6V8Db0gMy76fG6g,5953
|
|
9
9
|
pygeoinf/linear_optimisation.py,sha256=7lklTRRBGkz8M9WsfvkDl-eoGkc4Ty7BOJq7LWkdxCg,11091
|
|
10
|
-
pygeoinf/linear_solvers.py,sha256=
|
|
11
|
-
pygeoinf/operators.py,sha256=
|
|
12
|
-
pygeoinf/
|
|
10
|
+
pygeoinf/linear_solvers.py,sha256=zNYd7QNxzEMGp3sz1RWsyAT-Scv3yFv7FH0ZkK2OMZU,12270
|
|
11
|
+
pygeoinf/operators.py,sha256=_t_UqYBIk4rWdRe98hre39sPaoRRbNejOtcvabtf0x8,38010
|
|
12
|
+
pygeoinf/parallel.py,sha256=E148IAKiXojWe6sq3iYtHl1XGAW8w6xaYbI7LOM9oKc,2269
|
|
13
|
+
pygeoinf/random_matrix.py,sha256=Q9jgQVpMOy8jR3s057DzHeKrKLdvwRktF-n_oVZ0xbs,8231
|
|
13
14
|
pygeoinf/symmetric_space/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
15
|
pygeoinf/symmetric_space/circle.py,sha256=eUZPIDwg19RTCK3-bvbARg1g9RQcnV462MTlDaI-l5Y,17716
|
|
15
16
|
pygeoinf/symmetric_space/sphere.py,sha256=yjmES34jWLi4-EhU_RDILYn1y7_YrvgrTyT2qL2mLVg,21534
|
|
16
17
|
pygeoinf/symmetric_space/symmetric_space.py,sha256=lEAshbb55qL0iX84H423Pt35Af89Iy2pfB18JCPheX8,17970
|
|
17
|
-
pygeoinf-1.1.
|
|
18
|
-
pygeoinf-1.1.
|
|
19
|
-
pygeoinf-1.1.
|
|
20
|
-
pygeoinf-1.1.
|
|
18
|
+
pygeoinf-1.1.9.dist-info/LICENSE,sha256=GrTQnKJemVi69FSbHprq60KN0OJGsOSR-joQoTq-oD8,1501
|
|
19
|
+
pygeoinf-1.1.9.dist-info/METADATA,sha256=k1ymJcpBAr3TlSWhLaOun8fnO34ZQ0FSbMQGkE5DMuM,15363
|
|
20
|
+
pygeoinf-1.1.9.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
|
21
|
+
pygeoinf-1.1.9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|