pygeoinf 1.2.0__py3-none-any.whl → 1.2.1__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 +3 -0
- pygeoinf/direct_sum.py +24 -77
- pygeoinf/forward_problem.py +4 -1
- pygeoinf/gaussian_measure.py +35 -10
- pygeoinf/hilbert_space.py +7 -9
- pygeoinf/linear_bayesian.py +1 -1
- pygeoinf/linear_forms.py +137 -43
- pygeoinf/{operators.py → linear_operators.py} +279 -303
- pygeoinf/linear_optimisation.py +4 -4
- pygeoinf/linear_solvers.py +74 -7
- pygeoinf/nonlinear_forms.py +225 -0
- pygeoinf/nonlinear_operators.py +209 -0
- pygeoinf/nonlinear_optimisation.py +211 -0
- pygeoinf/parallel.py +1 -1
- pygeoinf/random_matrix.py +212 -72
- pygeoinf/symmetric_space/circle.py +1 -1
- pygeoinf/symmetric_space/sphere.py +1 -1
- pygeoinf/symmetric_space/symmetric_space.py +1 -1
- {pygeoinf-1.2.0.dist-info → pygeoinf-1.2.1.dist-info}/METADATA +1 -1
- pygeoinf-1.2.1.dist-info/RECORD +25 -0
- pygeoinf-1.2.0.dist-info/RECORD +0 -21
- {pygeoinf-1.2.0.dist-info → pygeoinf-1.2.1.dist-info}/LICENSE +0 -0
- {pygeoinf-1.2.0.dist-info → pygeoinf-1.2.1.dist-info}/WHEEL +0 -0
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
2
|
+
Provides classes for linear operators between Hilbert spaces.
|
|
3
3
|
|
|
4
|
-
This module
|
|
5
|
-
`HilbertSpace` objects. It
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
This module is the primary tool for defining and manipulating linear mappings
|
|
5
|
+
between `HilbertSpace` objects. It provides a powerful `LinearOperator` class
|
|
6
|
+
that supports a rich algebra and includes numerous factory methods for
|
|
7
|
+
convenient construction from matrices, forms, or tensor products.
|
|
8
8
|
|
|
9
9
|
Key Classes
|
|
10
10
|
-----------
|
|
11
|
-
- `Operator`: A general, potentially non-linear operator defined by a simple
|
|
12
|
-
mapping function.
|
|
13
11
|
- `LinearOperator`: The main workhorse for linear algebra. It represents a
|
|
14
|
-
linear map and provides rich functionality, including composition
|
|
15
|
-
adjoints (`.adjoint`), duals (`.dual`), and matrix representations
|
|
16
|
-
|
|
12
|
+
linear map `L(x) = Ax` and provides rich functionality, including composition
|
|
13
|
+
(`@`), adjoints (`.adjoint`), duals (`.dual`), and matrix representations
|
|
14
|
+
(`.matrix`).
|
|
17
15
|
- `DiagonalLinearOperator`: A specialized, efficient implementation for linear
|
|
18
|
-
operators that are diagonal in their component representation,
|
|
19
|
-
functional calculus.
|
|
16
|
+
operators that are diagonal in their component representation, notable for
|
|
17
|
+
supporting functional calculus (e.g., `.inverse`, `.sqrt`).
|
|
20
18
|
"""
|
|
21
19
|
|
|
22
20
|
from __future__ import annotations
|
|
@@ -26,10 +24,11 @@ import numpy as np
|
|
|
26
24
|
from scipy.sparse.linalg import LinearOperator as ScipyLinOp
|
|
27
25
|
from scipy.sparse import diags
|
|
28
26
|
|
|
27
|
+
# from .operators import Operator
|
|
28
|
+
from .nonlinear_operators import NonLinearOperator
|
|
29
29
|
|
|
30
30
|
from .random_matrix import (
|
|
31
|
-
|
|
32
|
-
variable_rank_random_range,
|
|
31
|
+
random_range,
|
|
33
32
|
random_svd as rm_svd,
|
|
34
33
|
random_cholesky as rm_chol,
|
|
35
34
|
random_eig as rm_eig,
|
|
@@ -43,94 +42,18 @@ if TYPE_CHECKING:
|
|
|
43
42
|
from .linear_forms import LinearForm
|
|
44
43
|
|
|
45
44
|
|
|
46
|
-
|
|
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
|
|
45
|
+
class LinearOperator(NonLinearOperator):
|
|
46
|
+
"""A linear operator between two Hilbert spaces.
|
|
66
47
|
|
|
48
|
+
This class represents a linear map `L(x) = Ax` and provides rich
|
|
49
|
+
functionality for linear algebraic operations. It specializes
|
|
50
|
+
`NonLinearOperator`, correctly defining its derivative as the operator
|
|
51
|
+
itself.
|
|
67
52
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def __init__(
|
|
74
|
-
self,
|
|
75
|
-
domain: HilbertSpace,
|
|
76
|
-
codomain: HilbertSpace,
|
|
77
|
-
mapping: Callable[[Any], Any],
|
|
78
|
-
) -> None:
|
|
79
|
-
"""
|
|
80
|
-
Initializes the Operator.
|
|
81
|
-
|
|
82
|
-
Args:
|
|
83
|
-
domain (HilbertSpace): Domain of the operator.
|
|
84
|
-
codomain (HilbertSpace): Codomain of the operator.
|
|
85
|
-
mapping (callable): The function defining the mapping from the
|
|
86
|
-
domain to the codomain.
|
|
87
|
-
"""
|
|
88
|
-
self._domain: HilbertSpace = domain
|
|
89
|
-
self._codomain: HilbertSpace = codomain
|
|
90
|
-
self.__mapping: Callable[[Any], Any] = mapping
|
|
91
|
-
|
|
92
|
-
@property
|
|
93
|
-
def domain(self) -> HilbertSpace:
|
|
94
|
-
"""The domain of the operator."""
|
|
95
|
-
return self._domain
|
|
96
|
-
|
|
97
|
-
@property
|
|
98
|
-
def codomain(self) -> HilbertSpace:
|
|
99
|
-
"""The codomain of the operator."""
|
|
100
|
-
return self._codomain
|
|
101
|
-
|
|
102
|
-
@property
|
|
103
|
-
def is_automorphism(self) -> bool:
|
|
104
|
-
"""True if the operator maps a space into itself."""
|
|
105
|
-
return self.domain == self.codomain
|
|
106
|
-
|
|
107
|
-
@property
|
|
108
|
-
def is_square(self) -> bool:
|
|
109
|
-
"""True if the operator's domain and codomain have the same dimension."""
|
|
110
|
-
return self.domain.dim == self.codomain.dim
|
|
111
|
-
|
|
112
|
-
@property
|
|
113
|
-
def linear(self) -> bool:
|
|
114
|
-
"""False for a general operator. Overridden by LinearOperator."""
|
|
115
|
-
return False
|
|
116
|
-
|
|
117
|
-
def __call__(self, x: Any) -> Any:
|
|
118
|
-
"""Applies the operator's mapping to a vector."""
|
|
119
|
-
return self.__mapping(x)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
class LinearOperator(Operator):
|
|
123
|
-
"""
|
|
124
|
-
A linear operator between two Hilbert spaces.
|
|
125
|
-
|
|
126
|
-
This class is the primary workhorse for linear algebraic operations. An
|
|
127
|
-
operator can be defined "on the fly" from a callable mapping. The class
|
|
128
|
-
automatically derives the associated `adjoint` and `dual` operators,
|
|
129
|
-
which are fundamental for solving linear systems and for optimization.
|
|
130
|
-
|
|
131
|
-
It supports a rich algebra, including composition (`@`), addition (`+`),
|
|
132
|
-
and scalar multiplication (`*`). Operators can also be represented as
|
|
133
|
-
dense or matrix-free (`scipy`) matrices for use with numerical solvers.
|
|
53
|
+
Key features include operator algebra (`@`, `+`, `*`), automatic
|
|
54
|
+
derivation of adjoint (`.adjoint`) and dual (`.dual`) operators, and
|
|
55
|
+
multiple matrix representations (`.matrix()`) for use with numerical
|
|
56
|
+
solvers.
|
|
134
57
|
"""
|
|
135
58
|
|
|
136
59
|
def __init__(
|
|
@@ -159,7 +82,10 @@ class LinearOperator(Operator):
|
|
|
159
82
|
dual_base (LinearOperator, optional): Internal use for duals.
|
|
160
83
|
adjoint_base (LinearOperator, optional): Internal use for adjoints.
|
|
161
84
|
"""
|
|
162
|
-
super().__init__(
|
|
85
|
+
super().__init__(
|
|
86
|
+
domain, codomain, self._mapping_impl, derivative=self._derivative_impl
|
|
87
|
+
)
|
|
88
|
+
self._mapping = mapping
|
|
163
89
|
self._dual_base: Optional[LinearOperator] = dual_base
|
|
164
90
|
self._adjoint_base: Optional[LinearOperator] = adjoint_base
|
|
165
91
|
self._thread_safe: bool = thread_safe
|
|
@@ -329,47 +255,25 @@ class LinearOperator(Operator):
|
|
|
329
255
|
"""
|
|
330
256
|
Creates a LinearOperator from its matrix representation.
|
|
331
257
|
|
|
332
|
-
This factory
|
|
333
|
-
|
|
334
|
-
vectors of the abstract Hilbert space vectors. The `galerkin` flag
|
|
335
|
-
determines how this matrix action is interpreted.
|
|
258
|
+
This factory defines a `LinearOperator` using a concrete matrix that
|
|
259
|
+
acts on the component vectors of the abstract Hilbert space vectors.
|
|
336
260
|
|
|
337
261
|
Args:
|
|
338
|
-
domain
|
|
339
|
-
codomain
|
|
340
|
-
matrix
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
component vector `c_y` of the output vector `y`.
|
|
349
|
-
|
|
350
|
-
- **`galerkin=True`: Galerkin (or "Weak Form") Representation**
|
|
351
|
-
This interpretation is standard in the finite element method (FEM)
|
|
352
|
-
and other variational techniques. The matrix `M` maps the component
|
|
353
|
-
vector `c_x` of an input `x` to the component vector `c_yp` of the
|
|
354
|
-
*dual* of the output vector `y`.
|
|
355
|
-
|
|
356
|
-
- **Matrix Entries**: The matrix elements are defined by inner
|
|
357
|
-
products with basis vectors: `M_ij = inner_product(A(b_j), b_i)`,
|
|
358
|
-
where `b_j` are domain basis vectors and `b_i` are codomain
|
|
359
|
-
basis vectors.
|
|
360
|
-
- **Use Case**: This is critically important for preserving the
|
|
361
|
-
mathematical properties of an operator. For example, if an operator
|
|
362
|
-
`A` is self-adjoint, its Galerkin matrix `M` will be **symmetric**
|
|
363
|
-
(`M.T == M`). This allows the use of highly efficient numerical
|
|
364
|
-
methods like the Conjugate Gradient solver or Cholesky
|
|
365
|
-
factorization, which rely on symmetry. The standard component
|
|
366
|
-
matrix of a self-adjoint operator is generally not symmetric
|
|
367
|
-
unless the basis is orthonormal.
|
|
262
|
+
domain: The operator's domain space.
|
|
263
|
+
codomain: The operator's codomain space.
|
|
264
|
+
matrix: The matrix representation (NumPy array or SciPy
|
|
265
|
+
LinearOperator). Shape must be `(codomain.dim, domain.dim)`.
|
|
266
|
+
galerkin: If `True`, the matrix is interpreted in its "weak form"
|
|
267
|
+
or Galerkin representation (`M_ij = <basis_j, A(basis_i)>`),
|
|
268
|
+
which maps a vector's components to the components of its
|
|
269
|
+
*dual*. This is crucial as it ensures a self-adjoint
|
|
270
|
+
operator is represented by a symmetric matrix. If `False`
|
|
271
|
+
(default), it's a standard component-to-component map.
|
|
368
272
|
|
|
369
273
|
Returns:
|
|
370
|
-
|
|
371
|
-
defined by the provided matrix and interpretation.
|
|
274
|
+
A new `LinearOperator` defined by the matrix action.
|
|
372
275
|
"""
|
|
276
|
+
|
|
373
277
|
assert matrix.shape == (codomain.dim, domain.dim)
|
|
374
278
|
|
|
375
279
|
if galerkin:
|
|
@@ -521,53 +425,28 @@ class LinearOperator(Operator):
|
|
|
521
425
|
parallel: bool = False,
|
|
522
426
|
n_jobs: int = -1,
|
|
523
427
|
) -> Union[ScipyLinOp, np.ndarray]:
|
|
524
|
-
"""
|
|
525
|
-
Returns a matrix representation of the operator.
|
|
428
|
+
"""Returns a matrix representation of the operator.
|
|
526
429
|
|
|
527
|
-
This
|
|
528
|
-
|
|
430
|
+
This provides a concrete matrix that represents the operator's action
|
|
431
|
+
on the underlying component vectors.
|
|
529
432
|
|
|
530
433
|
Args:
|
|
531
|
-
dense
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
flag is crucial for correctly using the matrix with numerical solvers.
|
|
543
|
-
|
|
544
|
-
- **`galerkin=False` (Default): Standard Component Mapping**
|
|
545
|
-
The returned matrix `M` performs a standard component-to-component
|
|
546
|
-
mapping.
|
|
547
|
-
- **`matvec` action**: Takes the component vector `c_x` of an input `x`
|
|
548
|
-
and returns the component vector `c_y` of the output `y`.
|
|
549
|
-
- **`rmatvec` action**: Corresponds to the matrix of the **dual operator**, `A'`.
|
|
550
|
-
|
|
551
|
-
- **`galerkin=True`: Galerkin (or "Weak Form") Representation**
|
|
552
|
-
The returned matrix `M` represents the operator in a weak form, mapping
|
|
553
|
-
components of a vector to components of a dual vector.
|
|
554
|
-
- **`matvec` action**: Takes the component vector `c_x` of an input `x`
|
|
555
|
-
and returns the component vector `c_yp` of the *dual* of the output `y`.
|
|
556
|
-
- **`rmatvec` action**: Corresponds to the matrix of the **adjoint operator**, `A*`.
|
|
557
|
-
- **Key Property**: This representation is designed to preserve fundamental
|
|
558
|
-
mathematical properties. For instance, if the `LinearOperator` is
|
|
559
|
-
self-adjoint, its Galerkin matrix will be **symmetric**, which is a
|
|
560
|
-
prerequisite for algorithms like the Conjugate Gradient method.
|
|
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.
|
|
434
|
+
dense: If `True`, returns a dense `numpy.ndarray`. If `False`
|
|
435
|
+
(default), returns a memory-efficient, matrix-free
|
|
436
|
+
`scipy.sparse.linalg.LinearOperator`.
|
|
437
|
+
galerkin: If `True`, the returned matrix is the Galerkin
|
|
438
|
+
representation, whose `rmatvec` corresponds to the
|
|
439
|
+
**adjoint** operator. If `False` (default), the `rmatvec`
|
|
440
|
+
corresponds to the **dual** operator. The Galerkin form is
|
|
441
|
+
essential for algorithms that rely on symmetry/self-adjointness.
|
|
442
|
+
parallel: If `True` and `dense=True`, computes the matrix columns
|
|
443
|
+
in parallel.
|
|
444
|
+
n_jobs: Number of parallel jobs to use. `-1` uses all available cores.
|
|
566
445
|
|
|
567
446
|
Returns:
|
|
568
|
-
|
|
569
|
-
operator, either as a dense array or a matrix-free object.
|
|
447
|
+
The matrix representation, either dense or matrix-free.
|
|
570
448
|
"""
|
|
449
|
+
|
|
571
450
|
if dense:
|
|
572
451
|
return self._compute_dense_matrix(galerkin, parallel, n_jobs)
|
|
573
452
|
else:
|
|
@@ -625,31 +504,38 @@ class LinearOperator(Operator):
|
|
|
625
504
|
|
|
626
505
|
def random_svd(
|
|
627
506
|
self,
|
|
628
|
-
|
|
507
|
+
size_estimate: int,
|
|
629
508
|
/,
|
|
630
509
|
*,
|
|
631
|
-
power: int = 0,
|
|
632
510
|
galerkin: bool = False,
|
|
633
|
-
|
|
634
|
-
|
|
511
|
+
method: str = "variable",
|
|
512
|
+
max_rank: int = None,
|
|
513
|
+
power: int = 2,
|
|
514
|
+
rtol: float = 1e-4,
|
|
515
|
+
block_size: int = 10,
|
|
635
516
|
parallel: bool = False,
|
|
636
517
|
n_jobs: int = -1,
|
|
637
|
-
) -> Tuple[LinearOperator,
|
|
518
|
+
) -> Tuple[LinearOperator, DiagonalLinearOperator, LinearOperator]:
|
|
638
519
|
"""
|
|
639
520
|
Computes an approximate SVD using a randomized algorithm.
|
|
640
521
|
|
|
641
522
|
Args:
|
|
642
|
-
|
|
643
|
-
|
|
523
|
+
size_estimate: For 'fixed' method, the exact target rank. For 'variable'
|
|
524
|
+
method, this is the initial rank to sample.
|
|
644
525
|
galerkin (bool): If True, use the Galerkin representation.
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
526
|
+
method ({'variable', 'fixed'}): The algorithm to use.
|
|
527
|
+
- 'variable': (Default) Progressively samples to find the rank needed
|
|
528
|
+
to meet tolerance `rtol`, stopping at `max_rank`.
|
|
529
|
+
- 'fixed': Returns a basis with exactly `size_estimate` columns.
|
|
530
|
+
max_rank: For 'variable' method, a hard limit on the rank. Ignored if
|
|
531
|
+
method='fixed'. Defaults to min(m, n).
|
|
532
|
+
power: Number of power iterations to improve accuracy.
|
|
533
|
+
rtol: Relative tolerance for the 'variable' method. Ignored if
|
|
534
|
+
method='fixed'.
|
|
535
|
+
block_size: Number of new vectors to sample per iteration in 'variable'
|
|
536
|
+
method. Ignored if method='fixed'.
|
|
537
|
+
parallel: Whether to use parallel matrix multiplication.
|
|
538
|
+
n_jobs: Number of jobs for parallelism.
|
|
653
539
|
|
|
654
540
|
Returns:
|
|
655
541
|
left (LinearOperator): The left singular vector matrix.
|
|
@@ -659,25 +545,25 @@ class LinearOperator(Operator):
|
|
|
659
545
|
Notes:
|
|
660
546
|
The right factor is in transposed form. This means the original
|
|
661
547
|
operator can be approximated as:
|
|
662
|
-
|
|
663
|
-
|
|
548
|
+
A = left @ singular_values @ right
|
|
664
549
|
"""
|
|
665
550
|
from .hilbert_space import EuclideanSpace
|
|
666
551
|
|
|
667
552
|
matrix = self.matrix(galerkin=galerkin)
|
|
668
553
|
m, n = matrix.shape
|
|
669
554
|
k = min(m, n)
|
|
670
|
-
rank = rank if rank <= k else k
|
|
671
555
|
|
|
672
|
-
qr_factor
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
556
|
+
qr_factor = random_range(
|
|
557
|
+
matrix,
|
|
558
|
+
size_estimate if size_estimate < k else k,
|
|
559
|
+
method=method,
|
|
560
|
+
max_rank=max_rank,
|
|
561
|
+
power=power,
|
|
562
|
+
rtol=rtol,
|
|
563
|
+
block_size=block_size,
|
|
564
|
+
parallel=parallel,
|
|
565
|
+
n_jobs=n_jobs,
|
|
566
|
+
)
|
|
681
567
|
|
|
682
568
|
left_factor_mat, singular_values, right_factor_transposed = rm_svd(
|
|
683
569
|
matrix, qr_factor
|
|
@@ -705,39 +591,40 @@ class LinearOperator(Operator):
|
|
|
705
591
|
|
|
706
592
|
def random_eig(
|
|
707
593
|
self,
|
|
708
|
-
|
|
594
|
+
size_estimate: int,
|
|
709
595
|
/,
|
|
710
596
|
*,
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
597
|
+
method: str = "variable",
|
|
598
|
+
max_rank: int = None,
|
|
599
|
+
power: int = 2,
|
|
600
|
+
rtol: float = 1e-4,
|
|
601
|
+
block_size: int = 10,
|
|
714
602
|
parallel: bool = False,
|
|
715
603
|
n_jobs: int = -1,
|
|
716
|
-
) -> Tuple[LinearOperator,
|
|
604
|
+
) -> Tuple[LinearOperator, DiagonalLinearOperator]:
|
|
717
605
|
"""
|
|
718
|
-
Computes an approximate
|
|
719
|
-
operator using a randomized algorithm.
|
|
606
|
+
Computes an approximate eigen-decomposition using a randomized algorithm.
|
|
720
607
|
|
|
721
608
|
Args:
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
609
|
+
size_estimate: For 'fixed' method, the exact target rank. For 'variable'
|
|
610
|
+
method, this is the initial rank to sample.
|
|
611
|
+
method ({'variable', 'fixed'}): The algorithm to use.
|
|
612
|
+
- 'variable': (Default) Progressively samples to find the rank needed
|
|
613
|
+
to meet tolerance `rtol`, stopping at `max_rank`.
|
|
614
|
+
- 'fixed': Returns a basis with exactly `size_estimate` columns.
|
|
615
|
+
max_rank: For 'variable' method, a hard limit on the rank. Ignored if
|
|
616
|
+
method='fixed'. Defaults to min(m, n).
|
|
617
|
+
power: Number of power iterations to improve accuracy.
|
|
618
|
+
rtol: Relative tolerance for the 'variable' method. Ignored if
|
|
619
|
+
method='fixed'.
|
|
620
|
+
block_size: Number of new vectors to sample per iteration in 'variable'
|
|
621
|
+
method. Ignored if method='fixed'.
|
|
622
|
+
parallel: Whether to use parallel matrix multiplication.
|
|
623
|
+
n_jobs: Number of jobs for parallelism.
|
|
732
624
|
|
|
733
625
|
Returns:
|
|
734
|
-
expansion (LinearOperator):
|
|
735
|
-
|
|
736
|
-
eigenvalues (DiagonalLinearOperator): The eigenvalues.
|
|
737
|
-
|
|
738
|
-
Notes:
|
|
739
|
-
The original operator can be approximated as:
|
|
740
|
-
A = expansion @ eigenvalues @ expansion.adjoint
|
|
626
|
+
expansion (LinearOperator): Mapping from coefficients in eigen-basis to vectors.
|
|
627
|
+
eigenvaluevalues (DiagonalLinearOperator): The eigenvalues values.
|
|
741
628
|
|
|
742
629
|
"""
|
|
743
630
|
from .hilbert_space import EuclideanSpace
|
|
@@ -746,17 +633,18 @@ class LinearOperator(Operator):
|
|
|
746
633
|
matrix = self.matrix(galerkin=True)
|
|
747
634
|
m, n = matrix.shape
|
|
748
635
|
k = min(m, n)
|
|
749
|
-
rank = rank if rank <= k else k
|
|
750
636
|
|
|
751
|
-
qr_factor
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
637
|
+
qr_factor = random_range(
|
|
638
|
+
matrix,
|
|
639
|
+
size_estimate if size_estimate < k else k,
|
|
640
|
+
method=method,
|
|
641
|
+
max_rank=max_rank,
|
|
642
|
+
power=power,
|
|
643
|
+
rtol=rtol,
|
|
644
|
+
block_size=block_size,
|
|
645
|
+
parallel=parallel,
|
|
646
|
+
n_jobs=n_jobs,
|
|
647
|
+
)
|
|
760
648
|
|
|
761
649
|
eigenvectors, eigenvalues = rm_eig(matrix, qr_factor)
|
|
762
650
|
euclidean = EuclideanSpace(qr_factor.shape[1])
|
|
@@ -770,12 +658,14 @@ class LinearOperator(Operator):
|
|
|
770
658
|
|
|
771
659
|
def random_cholesky(
|
|
772
660
|
self,
|
|
773
|
-
|
|
661
|
+
size_estimate: int,
|
|
774
662
|
/,
|
|
775
663
|
*,
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
664
|
+
method: str = "variable",
|
|
665
|
+
max_rank: int = None,
|
|
666
|
+
power: int = 2,
|
|
667
|
+
rtol: float = 1e-4,
|
|
668
|
+
block_size: int = 10,
|
|
779
669
|
parallel: bool = False,
|
|
780
670
|
n_jobs: int = -1,
|
|
781
671
|
) -> LinearOperator:
|
|
@@ -784,16 +674,21 @@ class LinearOperator(Operator):
|
|
|
784
674
|
self-adjoint operator using a randomized algorithm.
|
|
785
675
|
|
|
786
676
|
Args:
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
677
|
+
size_estimate: For 'fixed' method, the exact target rank. For 'variable'
|
|
678
|
+
method, this is the initial rank to sample.
|
|
679
|
+
method ({'variable', 'fixed'}): The algorithm to use.
|
|
680
|
+
- 'variable': (Default) Progressively samples to find the rank needed
|
|
681
|
+
to meet tolerance `rtol`, stopping at `max_rank`.
|
|
682
|
+
- 'fixed': Returns a basis with exactly `size_estimate` columns.
|
|
683
|
+
max_rank: For 'variable' method, a hard limit on the rank. Ignored if
|
|
684
|
+
method='fixed'. Defaults to min(m, n).
|
|
685
|
+
power: Number of power iterations to improve accuracy.
|
|
686
|
+
rtol: Relative tolerance for the 'variable' method. Ignored if
|
|
687
|
+
method='fixed'.
|
|
688
|
+
block_size: Number of new vectors to sample per iteration in 'variable'
|
|
689
|
+
method. Ignored if method='fixed'.
|
|
690
|
+
parallel: Whether to use parallel matrix multiplication.
|
|
691
|
+
n_jobs: Number of jobs for parallelism.
|
|
797
692
|
|
|
798
693
|
Returns:
|
|
799
694
|
factor (LinearOperator): A linear operator from a Euclidean space
|
|
@@ -810,17 +705,18 @@ class LinearOperator(Operator):
|
|
|
810
705
|
matrix = self.matrix(galerkin=True)
|
|
811
706
|
m, n = matrix.shape
|
|
812
707
|
k = min(m, n)
|
|
813
|
-
rank = rank if rank <= k else k
|
|
814
708
|
|
|
815
|
-
qr_factor
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
709
|
+
qr_factor = random_range(
|
|
710
|
+
matrix,
|
|
711
|
+
size_estimate if size_estimate < k else k,
|
|
712
|
+
method=method,
|
|
713
|
+
max_rank=max_rank,
|
|
714
|
+
power=power,
|
|
715
|
+
rtol=rtol,
|
|
716
|
+
block_size=block_size,
|
|
717
|
+
parallel=parallel,
|
|
718
|
+
n_jobs=n_jobs,
|
|
719
|
+
)
|
|
824
720
|
|
|
825
721
|
cholesky_factor = rm_chol(matrix, qr_factor)
|
|
826
722
|
|
|
@@ -831,6 +727,12 @@ class LinearOperator(Operator):
|
|
|
831
727
|
galerkin=True,
|
|
832
728
|
)
|
|
833
729
|
|
|
730
|
+
def _mapping_impl(self, x: Any) -> Any:
|
|
731
|
+
return self._mapping(x)
|
|
732
|
+
|
|
733
|
+
def _derivative_impl(self, _: Any) -> LinearOperator:
|
|
734
|
+
return self
|
|
735
|
+
|
|
834
736
|
def _dual_mapping_default(self, yp: Any) -> LinearForm:
|
|
835
737
|
from .linear_forms import LinearForm
|
|
836
738
|
|
|
@@ -899,55 +801,126 @@ class LinearOperator(Operator):
|
|
|
899
801
|
def __truediv__(self, a: float) -> LinearOperator:
|
|
900
802
|
return self * (1.0 / a)
|
|
901
803
|
|
|
902
|
-
def __add__(
|
|
903
|
-
|
|
904
|
-
|
|
804
|
+
def __add__(
|
|
805
|
+
self, other: NonLinearOperator | LinearOperator
|
|
806
|
+
) -> NonLinearOperator | LinearOperator:
|
|
807
|
+
"""Returns the sum of this operator and another.
|
|
905
808
|
|
|
906
|
-
|
|
907
|
-
|
|
809
|
+
If `other` is also a `LinearOperator`, this performs an optimized
|
|
810
|
+
addition that preserves linearity and correctly defines the new
|
|
811
|
+
operator's `adjoint`. Otherwise, it delegates to the general
|
|
812
|
+
implementation in the `NonLinearOperator` base class.
|
|
908
813
|
|
|
909
|
-
|
|
910
|
-
|
|
814
|
+
Args:
|
|
815
|
+
other: The operator to add to this one.
|
|
911
816
|
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
817
|
+
Returns:
|
|
818
|
+
A new `LinearOperator` if adding two linear operators, otherwise
|
|
819
|
+
a `NonLinearOperator`.
|
|
820
|
+
"""
|
|
915
821
|
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
822
|
+
if isinstance(other, LinearOperator):
|
|
823
|
+
domain = self.domain
|
|
824
|
+
codomain = self.codomain
|
|
919
825
|
|
|
920
|
-
|
|
921
|
-
|
|
826
|
+
def mapping(x: Any) -> Any:
|
|
827
|
+
return codomain.add(self(x), other(x))
|
|
922
828
|
|
|
923
|
-
|
|
924
|
-
|
|
829
|
+
def adjoint_mapping(y: Any) -> Any:
|
|
830
|
+
return domain.add(self.adjoint(y), other.adjoint(y))
|
|
925
831
|
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
832
|
+
return LinearOperator(
|
|
833
|
+
domain, codomain, mapping, adjoint_mapping=adjoint_mapping
|
|
834
|
+
)
|
|
835
|
+
else:
|
|
836
|
+
return super().__add__(other)
|
|
929
837
|
|
|
930
|
-
def
|
|
931
|
-
|
|
932
|
-
|
|
838
|
+
def __sub__(
|
|
839
|
+
self, other: NonLinearOperator | LinearOperator
|
|
840
|
+
) -> NonLinearOperator | LinearOperator:
|
|
841
|
+
"""Returns the difference between this operator and another.
|
|
933
842
|
|
|
934
|
-
|
|
935
|
-
|
|
843
|
+
If `other` is also a `LinearOperator`, this performs an optimized
|
|
844
|
+
subtraction that preserves linearity and correctly defines the new
|
|
845
|
+
operator's `adjoint`. Otherwise, it delegates to the general
|
|
846
|
+
implementation in the `NonLinearOperator` base class.
|
|
936
847
|
|
|
937
|
-
|
|
938
|
-
|
|
848
|
+
Args:
|
|
849
|
+
other: The operator to subtract from this one.
|
|
939
850
|
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
851
|
+
Returns:
|
|
852
|
+
A new `LinearOperator` if subtracting two linear operators,
|
|
853
|
+
otherwise a `NonLinearOperator`.
|
|
854
|
+
"""
|
|
855
|
+
|
|
856
|
+
if isinstance(other, LinearOperator):
|
|
857
|
+
|
|
858
|
+
domain = self.domain
|
|
859
|
+
codomain = self.codomain
|
|
860
|
+
|
|
861
|
+
def mapping(x: Any) -> Any:
|
|
862
|
+
return codomain.subtract(self(x), other(x))
|
|
863
|
+
|
|
864
|
+
def adjoint_mapping(y: Any) -> Any:
|
|
865
|
+
return domain.subtract(self.adjoint(y), other.adjoint(y))
|
|
866
|
+
|
|
867
|
+
return LinearOperator(
|
|
868
|
+
domain, codomain, mapping, adjoint_mapping=adjoint_mapping
|
|
869
|
+
)
|
|
870
|
+
else:
|
|
871
|
+
return super().__sub__(other)
|
|
872
|
+
|
|
873
|
+
def __matmul__(
|
|
874
|
+
self, other: NonLinearOperator | LinearOperator
|
|
875
|
+
) -> NonLinearOperator | LinearOperator:
|
|
876
|
+
"""Composes this operator with another using the @ symbol.
|
|
877
|
+
|
|
878
|
+
The composition `(self @ other)` results in a new operator that
|
|
879
|
+
first applies `other` and then applies `self`, i.e.,
|
|
880
|
+
`(self @ other)(x) = self(other(x))`.
|
|
881
|
+
|
|
882
|
+
If `other` is also a `LinearOperator`, this creates a new `LinearOperator`
|
|
883
|
+
whose adjoint is correctly defined using the composition rule:
|
|
884
|
+
`(L1 @ L2)* = L2* @ L1*`. Otherwise, it delegates to the general
|
|
885
|
+
`NonLinearOperator` implementation.
|
|
886
|
+
|
|
887
|
+
Args:
|
|
888
|
+
other: The operator to compose with (the right-hand operator).
|
|
889
|
+
|
|
890
|
+
Returns:
|
|
891
|
+
A new `LinearOperator` if composing two linear operators,
|
|
892
|
+
otherwise a `NonLinearOperator`.
|
|
893
|
+
"""
|
|
894
|
+
|
|
895
|
+
if isinstance(other, LinearOperator):
|
|
896
|
+
domain = other.domain
|
|
897
|
+
codomain = self.codomain
|
|
898
|
+
|
|
899
|
+
def mapping(x: Any) -> Any:
|
|
900
|
+
return self(other(x))
|
|
901
|
+
|
|
902
|
+
def adjoint_mapping(y: Any) -> Any:
|
|
903
|
+
return other.adjoint(self.adjoint(y))
|
|
904
|
+
|
|
905
|
+
return LinearOperator(
|
|
906
|
+
domain, codomain, mapping, adjoint_mapping=adjoint_mapping
|
|
907
|
+
)
|
|
908
|
+
|
|
909
|
+
else:
|
|
910
|
+
return super().__matmul__(other)
|
|
943
911
|
|
|
944
912
|
def __str__(self) -> str:
|
|
945
913
|
return self.matrix(dense=True).__str__()
|
|
946
914
|
|
|
947
915
|
|
|
948
916
|
class DiagonalLinearOperator(LinearOperator):
|
|
949
|
-
"""
|
|
950
|
-
|
|
917
|
+
"""A LinearOperator that is diagonal in its component representation.
|
|
918
|
+
|
|
919
|
+
This provides an efficient implementation for diagonal linear operators.
|
|
920
|
+
Its key feature is support for **functional calculus**, allowing for the
|
|
921
|
+
direct computation of operator functions like inverse (`.inverse`) or
|
|
922
|
+
|
|
923
|
+
square root (`.sqrt`) by applying the function to the diagonal entries.
|
|
951
924
|
"""
|
|
952
925
|
|
|
953
926
|
def __init__(
|
|
@@ -989,21 +962,24 @@ class DiagonalLinearOperator(LinearOperator):
|
|
|
989
962
|
"""The diagonal entries of the operator's matrix representation."""
|
|
990
963
|
return self._diagonal_values
|
|
991
964
|
|
|
992
|
-
def function(self, f: Callable[[float], float]) ->
|
|
993
|
-
"""
|
|
994
|
-
Applies a function to the operator via functional calculus.
|
|
965
|
+
def function(self, f: Callable[[float], float]) -> DiagonalLinearOperator:
|
|
966
|
+
"""Applies a function to the operator via functional calculus.
|
|
995
967
|
|
|
996
|
-
This creates a new DiagonalLinearOperator where
|
|
997
|
-
|
|
968
|
+
This creates a new `DiagonalLinearOperator` where the function `f` has
|
|
969
|
+
been applied to each of the diagonal entries. For example,
|
|
970
|
+
`op.function(lambda x: 1/x)` computes the inverse.
|
|
998
971
|
|
|
999
972
|
Args:
|
|
1000
|
-
f: A function
|
|
973
|
+
f: A scalar function to apply to the diagonal values.
|
|
974
|
+
|
|
975
|
+
Returns:
|
|
976
|
+
A new `DiagonalLinearOperator` with the transformed diagonal.
|
|
1001
977
|
"""
|
|
1002
978
|
diagonal_values = np.array([f(x) for x in self.diagonal_values])
|
|
1003
979
|
return DiagonalLinearOperator(self.domain, self.codomain, diagonal_values)
|
|
1004
980
|
|
|
1005
981
|
@property
|
|
1006
|
-
def inverse(self) ->
|
|
982
|
+
def inverse(self) -> DiagonalLinearOperator:
|
|
1007
983
|
"""
|
|
1008
984
|
The inverse of the operator, computed via functional calculus.
|
|
1009
985
|
Requires all diagonal values to be non-zero.
|
|
@@ -1012,7 +988,7 @@ class DiagonalLinearOperator(LinearOperator):
|
|
|
1012
988
|
return self.function(lambda x: 1 / x)
|
|
1013
989
|
|
|
1014
990
|
@property
|
|
1015
|
-
def sqrt(self) ->
|
|
991
|
+
def sqrt(self) -> DiagonalLinearOperator:
|
|
1016
992
|
"""
|
|
1017
993
|
The square root of the operator, computed via functional calculus.
|
|
1018
994
|
Requires all diagonal values to be non-negative.
|