pygeoinf 1.2.5__tar.gz → 1.2.7__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.5 → pygeoinf-1.2.7}/PKG-INFO +1 -1
- {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/__init__.py +1 -4
- {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/gaussian_measure.py +1 -1
- {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/linear_operators.py +383 -45
- {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pyproject.toml +1 -1
- {pygeoinf-1.2.5 → pygeoinf-1.2.7}/LICENSE +0 -0
- {pygeoinf-1.2.5 → pygeoinf-1.2.7}/README.md +0 -0
- {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/backus_gilbert.py +0 -0
- {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/checks/hilbert_space.py +0 -0
- {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/checks/linear_operators.py +0 -0
- {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/checks/nonlinear_operators.py +0 -0
- {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/direct_sum.py +0 -0
- {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/forward_problem.py +0 -0
- {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/hilbert_space.py +0 -0
- {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/inversion.py +0 -0
- {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/linear_bayesian.py +0 -0
- {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/linear_forms.py +0 -0
- {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/linear_optimisation.py +0 -0
- {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/linear_solvers.py +0 -0
- {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/nonlinear_forms.py +0 -0
- {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/nonlinear_operators.py +0 -0
- {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/nonlinear_optimisation.py +0 -0
- {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/parallel.py +0 -0
- {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/random_matrix.py +0 -0
- {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/symmetric_space/__init__.py +0 -0
- {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/symmetric_space/circle.py +0 -0
- {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/symmetric_space/sphere.py +0 -0
- {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/symmetric_space/symmetric_space.py +0 -0
|
@@ -32,10 +32,7 @@ from .linear_forms import (
|
|
|
32
32
|
|
|
33
33
|
from .nonlinear_operators import NonLinearOperator
|
|
34
34
|
|
|
35
|
-
from .linear_operators import
|
|
36
|
-
LinearOperator,
|
|
37
|
-
DiagonalLinearOperator,
|
|
38
|
-
)
|
|
35
|
+
from .linear_operators import LinearOperator, DiagonalLinearOperator, NormalSumOperator
|
|
39
36
|
|
|
40
37
|
|
|
41
38
|
from .gaussian_measure import (
|
|
@@ -187,7 +187,7 @@ class GaussianMeasure:
|
|
|
187
187
|
)
|
|
188
188
|
euclidean = EuclideanSpace(domain.dim)
|
|
189
189
|
covariance_factor = DiagonalLinearOperator(
|
|
190
|
-
euclidean, domain, standard_deviations
|
|
190
|
+
euclidean, domain, standard_deviations
|
|
191
191
|
)
|
|
192
192
|
return GaussianMeasure(
|
|
193
193
|
covariance_factor=covariance_factor,
|
|
@@ -18,12 +18,16 @@ Key Classes
|
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
20
|
from __future__ import annotations
|
|
21
|
-
from typing import Callable, List, Optional, Any, Union, Tuple, TYPE_CHECKING
|
|
21
|
+
from typing import Callable, List, Optional, Any, Union, Tuple, TYPE_CHECKING, Dict
|
|
22
|
+
|
|
23
|
+
from collections import defaultdict
|
|
22
24
|
|
|
23
25
|
import numpy as np
|
|
24
26
|
from scipy.sparse.linalg import LinearOperator as ScipyLinOp
|
|
25
27
|
from scipy.sparse import diags
|
|
26
28
|
|
|
29
|
+
from joblib import Parallel, delayed
|
|
30
|
+
|
|
27
31
|
# from .operators import Operator
|
|
28
32
|
from .nonlinear_operators import NonLinearOperator
|
|
29
33
|
|
|
@@ -504,6 +508,124 @@ class LinearOperator(NonLinearOperator, LinearOperatorAxiomChecks):
|
|
|
504
508
|
rmatmat=rmatmat,
|
|
505
509
|
)
|
|
506
510
|
|
|
511
|
+
def extract_diagonal(
|
|
512
|
+
self,
|
|
513
|
+
/,
|
|
514
|
+
*,
|
|
515
|
+
galerkin: bool = True,
|
|
516
|
+
parallel: bool = False,
|
|
517
|
+
n_jobs: int = -1,
|
|
518
|
+
) -> np.ndarray:
|
|
519
|
+
"""
|
|
520
|
+
Computes the main diagonal of the operator's matrix representation.
|
|
521
|
+
|
|
522
|
+
This method is highly parallelizable and memory-efficient, as it
|
|
523
|
+
avoids forming the full dense matrix.
|
|
524
|
+
|
|
525
|
+
Args:
|
|
526
|
+
galerkin: If True, computes the diagonal of the Galerkin matrix.
|
|
527
|
+
parallel: If True, computes the entries in parallel.
|
|
528
|
+
n_jobs: Number of parallel jobs to use.
|
|
529
|
+
|
|
530
|
+
Returns:
|
|
531
|
+
A NumPy array containing the diagonal entries.
|
|
532
|
+
"""
|
|
533
|
+
|
|
534
|
+
dim = min(self.domain.dim, self.codomain.dim)
|
|
535
|
+
jobs = n_jobs if parallel else 1
|
|
536
|
+
|
|
537
|
+
def compute_entry(i: int) -> float:
|
|
538
|
+
"""Worker function to compute a single diagonal entry."""
|
|
539
|
+
e_i = self.domain.basis_vector(i)
|
|
540
|
+
L_e_i = self(e_i)
|
|
541
|
+
|
|
542
|
+
if galerkin:
|
|
543
|
+
return self.domain.inner_product(e_i, L_e_i)
|
|
544
|
+
else:
|
|
545
|
+
return self.codomain.to_components(L_e_i)[i]
|
|
546
|
+
|
|
547
|
+
diagonal_entries = Parallel(n_jobs=jobs)(
|
|
548
|
+
delayed(compute_entry)(i) for i in range(dim)
|
|
549
|
+
)
|
|
550
|
+
return np.array(diagonal_entries)
|
|
551
|
+
|
|
552
|
+
def extract_diagonals(
|
|
553
|
+
self,
|
|
554
|
+
offsets: List[int],
|
|
555
|
+
/,
|
|
556
|
+
*,
|
|
557
|
+
galerkin: bool = True,
|
|
558
|
+
parallel: bool = False,
|
|
559
|
+
n_jobs: int = -1,
|
|
560
|
+
) -> Tuple[np.ndarray, List[int]]:
|
|
561
|
+
"""
|
|
562
|
+
Computes specified diagonals of the operator's matrix representation.
|
|
563
|
+
|
|
564
|
+
This is a memory-efficient and parallelizable method that computes
|
|
565
|
+
the matrix one column at a time.
|
|
566
|
+
|
|
567
|
+
Args:
|
|
568
|
+
offsets: A list of diagonal offsets to extract (e.g., [0] for
|
|
569
|
+
the main diagonal, [-1, 0, 1] for a tridiagonal matrix).
|
|
570
|
+
galerkin: If True, computes the diagonals of the Galerkin matrix.
|
|
571
|
+
parallel: If True, computes columns in parallel.
|
|
572
|
+
n_jobs: Number of parallel jobs to use.
|
|
573
|
+
|
|
574
|
+
Returns:
|
|
575
|
+
A tuple containing:
|
|
576
|
+
- A NumPy array where each row is a diagonal.
|
|
577
|
+
- The list of offsets.
|
|
578
|
+
This format is compatible with scipy.sparse.spdiags.
|
|
579
|
+
"""
|
|
580
|
+
dim = min(self.domain.dim, self.codomain.dim)
|
|
581
|
+
jobs = n_jobs if parallel else 1
|
|
582
|
+
|
|
583
|
+
# Prepare a thread-safe dictionary to store results
|
|
584
|
+
|
|
585
|
+
results: Dict[int, Dict[int, float]] = defaultdict(dict)
|
|
586
|
+
|
|
587
|
+
def compute_column_entries(j: int) -> Dict[int, Dict[int, float]]:
|
|
588
|
+
"""
|
|
589
|
+
Worker function to compute all needed entries for column j.
|
|
590
|
+
"""
|
|
591
|
+
e_j = self.domain.basis_vector(j)
|
|
592
|
+
L_e_j = self(e_j)
|
|
593
|
+
|
|
594
|
+
col_results = defaultdict(dict)
|
|
595
|
+
|
|
596
|
+
for k in offsets:
|
|
597
|
+
i = j - k
|
|
598
|
+
if 0 <= i < dim:
|
|
599
|
+
if galerkin:
|
|
600
|
+
e_i = self.domain.basis_vector(i)
|
|
601
|
+
val = self.domain.inner_product(e_i, L_e_j)
|
|
602
|
+
else:
|
|
603
|
+
val = self.codomain.to_components(L_e_j)[i]
|
|
604
|
+
col_results[k][i] = val
|
|
605
|
+
return col_results
|
|
606
|
+
|
|
607
|
+
# Run the computation in parallel
|
|
608
|
+
column_data = Parallel(n_jobs=jobs)(
|
|
609
|
+
delayed(compute_column_entries)(j) for j in range(dim)
|
|
610
|
+
)
|
|
611
|
+
|
|
612
|
+
# Aggregate results from the parallel computation
|
|
613
|
+
for col_dict in column_data:
|
|
614
|
+
for k, entries in col_dict.items():
|
|
615
|
+
results[k].update(entries)
|
|
616
|
+
|
|
617
|
+
# Format the results for spdiags
|
|
618
|
+
# The array must have padding for shorter off-diagonals.
|
|
619
|
+
diagonals_array = np.zeros((len(offsets), dim))
|
|
620
|
+
for idx, k in enumerate(offsets):
|
|
621
|
+
diag_entries = results[k]
|
|
622
|
+
for i, val in diag_entries.items():
|
|
623
|
+
j = i + k
|
|
624
|
+
if 0 <= j < dim:
|
|
625
|
+
diagonals_array[idx, j] = val
|
|
626
|
+
|
|
627
|
+
return diagonals_array, offsets
|
|
628
|
+
|
|
507
629
|
def random_svd(
|
|
508
630
|
self,
|
|
509
631
|
size_estimate: int,
|
|
@@ -916,13 +1038,12 @@ class LinearOperator(NonLinearOperator, LinearOperatorAxiomChecks):
|
|
|
916
1038
|
|
|
917
1039
|
|
|
918
1040
|
class DiagonalLinearOperator(LinearOperator):
|
|
919
|
-
"""A LinearOperator
|
|
1041
|
+
"""A LinearOperator whose Galerkin representation is diagonal.
|
|
920
1042
|
|
|
921
|
-
This
|
|
1043
|
+
This class defines a self-adjoint operator from its diagonal eigenvalues.
|
|
922
1044
|
Its key feature is support for **functional calculus**, allowing for the
|
|
923
|
-
direct computation of operator functions like inverse (`.inverse`) or
|
|
924
|
-
|
|
925
|
-
square root (`.sqrt`) by applying the function to the diagonal entries.
|
|
1045
|
+
direct computation of operator functions like its inverse (`.inverse`) or
|
|
1046
|
+
square root (`.sqrt`).
|
|
926
1047
|
"""
|
|
927
1048
|
|
|
928
1049
|
def __init__(
|
|
@@ -931,27 +1052,19 @@ class DiagonalLinearOperator(LinearOperator):
|
|
|
931
1052
|
codomain: HilbertSpace,
|
|
932
1053
|
diagonal_values: np.ndarray,
|
|
933
1054
|
/,
|
|
934
|
-
*,
|
|
935
|
-
galerkin: bool = False,
|
|
936
1055
|
) -> None:
|
|
937
1056
|
"""
|
|
938
|
-
Initializes the DiagonalLinearOperator
|
|
939
|
-
|
|
940
|
-
Args:
|
|
941
|
-
domain (HilbertSpace): The domain of the operator.
|
|
942
|
-
codomain (HilbertSpace): The codomain of the operator.
|
|
943
|
-
diagonal_values (np.ndarray): The diagonal entries of the
|
|
944
|
-
operator's matrix representation.
|
|
945
|
-
galerkin (bool): If True, use the Galerkin representation.
|
|
1057
|
+
Initializes the DiagonalLinearOperator from its diagonal
|
|
1058
|
+
Galerkin matrix entries (eigenvalues).
|
|
946
1059
|
"""
|
|
947
1060
|
|
|
948
1061
|
assert domain.dim == codomain.dim
|
|
949
1062
|
assert domain.dim == len(diagonal_values)
|
|
950
1063
|
self._diagonal_values: np.ndarray = diagonal_values
|
|
1064
|
+
|
|
1065
|
+
# The operator is defined by its diagonal Galerkin matrix.
|
|
951
1066
|
matrix = diags([diagonal_values], [0])
|
|
952
|
-
operator = LinearOperator.from_matrix(
|
|
953
|
-
domain, codomain, matrix, galerkin=galerkin
|
|
954
|
-
)
|
|
1067
|
+
operator = LinearOperator.from_matrix(domain, codomain, matrix, galerkin=True)
|
|
955
1068
|
super().__init__(
|
|
956
1069
|
operator.domain,
|
|
957
1070
|
operator.codomain,
|
|
@@ -959,41 +1072,266 @@ class DiagonalLinearOperator(LinearOperator):
|
|
|
959
1072
|
adjoint_mapping=operator.adjoint,
|
|
960
1073
|
)
|
|
961
1074
|
|
|
1075
|
+
def _compute_dense_matrix(
|
|
1076
|
+
self, galerkin: bool, parallel: bool, n_jobs: int
|
|
1077
|
+
) -> np.ndarray:
|
|
1078
|
+
"""
|
|
1079
|
+
Overloaded method to efficiently compute the dense matrix.
|
|
1080
|
+
"""
|
|
1081
|
+
if galerkin:
|
|
1082
|
+
# Fast path: This is how the operator is defined.
|
|
1083
|
+
return np.diag(self._diagonal_values)
|
|
1084
|
+
else:
|
|
1085
|
+
# The user wants the standard matrix, which may differ from the
|
|
1086
|
+
# diagonal Galerkin matrix in non-Euclidean spaces. Fall back
|
|
1087
|
+
# to the safe, general method for this conversion.
|
|
1088
|
+
return super()._compute_dense_matrix(galerkin, parallel, n_jobs)
|
|
1089
|
+
|
|
962
1090
|
@property
|
|
963
1091
|
def diagonal_values(self) -> np.ndarray:
|
|
964
|
-
"""The diagonal entries of the operator's matrix
|
|
1092
|
+
"""The diagonal entries of the operator's Galerkin matrix."""
|
|
965
1093
|
return self._diagonal_values
|
|
966
1094
|
|
|
967
|
-
def function(self, f: Callable[[float], float]) -> DiagonalLinearOperator:
|
|
968
|
-
"""Applies a function to the operator via functional calculus.
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
been applied to each of the diagonal entries. For example,
|
|
972
|
-
`op.function(lambda x: 1/x)` computes the inverse.
|
|
973
|
-
|
|
974
|
-
Args:
|
|
975
|
-
f: A scalar function to apply to the diagonal values.
|
|
976
|
-
|
|
977
|
-
Returns:
|
|
978
|
-
A new `DiagonalLinearOperator` with the transformed diagonal.
|
|
979
|
-
"""
|
|
980
|
-
diagonal_values = np.array([f(x) for x in self.diagonal_values])
|
|
981
|
-
return DiagonalLinearOperator(self.domain, self.codomain, diagonal_values)
|
|
1095
|
+
def function(self, f: Callable[[float], float]) -> "DiagonalLinearOperator":
|
|
1096
|
+
"""Applies a function to the operator via functional calculus."""
|
|
1097
|
+
new_diagonal_values = np.array([f(x) for x in self.diagonal_values])
|
|
1098
|
+
return DiagonalLinearOperator(self.domain, self.codomain, new_diagonal_values)
|
|
982
1099
|
|
|
983
1100
|
@property
|
|
984
|
-
def inverse(self) -> DiagonalLinearOperator:
|
|
985
|
-
"""
|
|
986
|
-
The inverse of the operator, computed via functional calculus.
|
|
987
|
-
Requires all diagonal values to be non-zero.
|
|
988
|
-
"""
|
|
1101
|
+
def inverse(self) -> "DiagonalLinearOperator":
|
|
1102
|
+
"""The inverse of the operator, computed via functional calculus."""
|
|
989
1103
|
assert all(val != 0 for val in self.diagonal_values)
|
|
990
1104
|
return self.function(lambda x: 1 / x)
|
|
991
1105
|
|
|
992
1106
|
@property
|
|
993
|
-
def sqrt(self) -> DiagonalLinearOperator:
|
|
994
|
-
"""
|
|
995
|
-
The square root of the operator, computed via functional calculus.
|
|
996
|
-
Requires all diagonal values to be non-negative.
|
|
997
|
-
"""
|
|
1107
|
+
def sqrt(self) -> "DiagonalLinearOperator":
|
|
1108
|
+
"""The square root of the operator, computed via functional calculus."""
|
|
998
1109
|
assert all(val >= 0 for val in self._diagonal_values)
|
|
999
1110
|
return self.function(np.sqrt)
|
|
1111
|
+
|
|
1112
|
+
def extract_diagonal(
|
|
1113
|
+
self,
|
|
1114
|
+
/,
|
|
1115
|
+
*,
|
|
1116
|
+
galerkin: bool = True,
|
|
1117
|
+
parallel: bool = False,
|
|
1118
|
+
n_jobs: int = -1,
|
|
1119
|
+
) -> np.ndarray:
|
|
1120
|
+
"""Overrides base method for efficiency."""
|
|
1121
|
+
if galerkin:
|
|
1122
|
+
return self._diagonal_values
|
|
1123
|
+
else:
|
|
1124
|
+
return super().extract_diagonal(
|
|
1125
|
+
galerkin=galerkin, parallel=parallel, n_jobs=n_jobs
|
|
1126
|
+
)
|
|
1127
|
+
|
|
1128
|
+
def extract_diagonals(
|
|
1129
|
+
self,
|
|
1130
|
+
offsets: List[int],
|
|
1131
|
+
/,
|
|
1132
|
+
*,
|
|
1133
|
+
galerkin: bool = True,
|
|
1134
|
+
parallel: bool = False,
|
|
1135
|
+
n_jobs: int = -1,
|
|
1136
|
+
) -> Tuple[np.ndarray, List[int]]:
|
|
1137
|
+
"""Overrides base method for efficiency."""
|
|
1138
|
+
if galerkin:
|
|
1139
|
+
dim = self.domain.dim
|
|
1140
|
+
diagonals_array = np.zeros((len(offsets), dim))
|
|
1141
|
+
for i, offset in enumerate(offsets):
|
|
1142
|
+
if offset == 0:
|
|
1143
|
+
diagonals_array[i, :] = self._diagonal_values
|
|
1144
|
+
return diagonals_array, offsets
|
|
1145
|
+
else:
|
|
1146
|
+
return super().extract_diagonals(
|
|
1147
|
+
offsets, galerkin=galerkin, parallel=parallel, n_jobs=n_jobs
|
|
1148
|
+
)
|
|
1149
|
+
|
|
1150
|
+
|
|
1151
|
+
class NormalSumOperator(LinearOperator):
|
|
1152
|
+
"""
|
|
1153
|
+
Represents a self-adjoint operator of the form N = A @ Q @ A.adjoint + B.
|
|
1154
|
+
|
|
1155
|
+
The operators Q and B are expected to be self-adjoint for the resulting
|
|
1156
|
+
operator to be mathematically correct.
|
|
1157
|
+
|
|
1158
|
+
Q and B are optional. If Q is None, it defaults to the identity operator.
|
|
1159
|
+
If B is None, it defaults to the zero operator.
|
|
1160
|
+
|
|
1161
|
+
This class uses operator algebra for a concise definition and provides an
|
|
1162
|
+
optimized, parallelizable method for computing its dense Galerkin matrix.
|
|
1163
|
+
"""
|
|
1164
|
+
|
|
1165
|
+
def __init__(
|
|
1166
|
+
self,
|
|
1167
|
+
A: LinearOperator,
|
|
1168
|
+
Q: Optional[LinearOperator] = None,
|
|
1169
|
+
B: Optional[LinearOperator] = None,
|
|
1170
|
+
) -> None:
|
|
1171
|
+
|
|
1172
|
+
# This operator's domain is the codomain of A.
|
|
1173
|
+
op_domain = A.codomain
|
|
1174
|
+
|
|
1175
|
+
if Q is None:
|
|
1176
|
+
# Q must be an operator on the domain of A.
|
|
1177
|
+
Q = A.domain.identity_operator()
|
|
1178
|
+
|
|
1179
|
+
if B is None:
|
|
1180
|
+
# B must be an operator on the codomain of A.
|
|
1181
|
+
B = op_domain.zero_operator()
|
|
1182
|
+
|
|
1183
|
+
if A.domain != Q.domain:
|
|
1184
|
+
raise ValueError("The domain of A must match the domain of Q.")
|
|
1185
|
+
if op_domain != B.domain:
|
|
1186
|
+
raise ValueError("The domain of B must match the codomain of A.")
|
|
1187
|
+
|
|
1188
|
+
self._A = A
|
|
1189
|
+
self._Q = Q
|
|
1190
|
+
self._B = B
|
|
1191
|
+
|
|
1192
|
+
# The compositional definition now works with correct dimensions.
|
|
1193
|
+
composite_op = self._A @ self._Q @ self._A.adjoint + self._B
|
|
1194
|
+
|
|
1195
|
+
super().__init__(
|
|
1196
|
+
composite_op.domain,
|
|
1197
|
+
composite_op.codomain,
|
|
1198
|
+
composite_op,
|
|
1199
|
+
adjoint_mapping=composite_op,
|
|
1200
|
+
)
|
|
1201
|
+
|
|
1202
|
+
def _compute_dense_matrix(
|
|
1203
|
+
self, galerkin: bool, parallel: bool, n_jobs: int
|
|
1204
|
+
) -> np.ndarray:
|
|
1205
|
+
"""
|
|
1206
|
+
Overloaded method using the matrix-free approach for Q and a cleaner
|
|
1207
|
+
implementation leveraging the base class's methods.
|
|
1208
|
+
"""
|
|
1209
|
+
if not galerkin:
|
|
1210
|
+
# The optimization is specific to the Galerkin representation.
|
|
1211
|
+
return super()._compute_dense_matrix(galerkin, parallel, n_jobs) #
|
|
1212
|
+
|
|
1213
|
+
domain_Y = self._A.codomain #
|
|
1214
|
+
dim = self.domain.dim #
|
|
1215
|
+
jobs = n_jobs if parallel else 1
|
|
1216
|
+
|
|
1217
|
+
# Step 1: Get component vectors c(v_j) where v_j = A*(e_j)
|
|
1218
|
+
print("Step 1: Computing components of v_j = A*(e_j)...")
|
|
1219
|
+
a_star_mat = self._A.adjoint.matrix(
|
|
1220
|
+
dense=True, galerkin=False, parallel=parallel, n_jobs=n_jobs
|
|
1221
|
+
) #
|
|
1222
|
+
|
|
1223
|
+
# Step 2: Reconstruct v_j vectors and compute w_j = Q(v_j)
|
|
1224
|
+
print("Step 2: Computing w_j = Q(v_j)...")
|
|
1225
|
+
v_vectors = [domain_Y.from_components(a_star_mat[:, j]) for j in range(dim)] #
|
|
1226
|
+
w_vectors = Parallel(n_jobs=jobs)(delayed(self._Q)(v_j) for v_j in v_vectors) #
|
|
1227
|
+
|
|
1228
|
+
# Step 3: Compute the inner product matrix M_AQA = <v_i, w_j>
|
|
1229
|
+
print("Step 3: Computing inner product matrix M_AQA...")
|
|
1230
|
+
|
|
1231
|
+
def compute_row(i: int) -> np.ndarray:
|
|
1232
|
+
"""Computes the i-th row of the inner product matrix."""
|
|
1233
|
+
v_i = v_vectors[i]
|
|
1234
|
+
return np.array([domain_Y.inner_product(v_i, w_j) for w_j in w_vectors]) #
|
|
1235
|
+
|
|
1236
|
+
rows = Parallel(n_jobs=jobs)(delayed(compute_row)(i) for i in range(dim))
|
|
1237
|
+
m_aqa_mat = np.vstack(rows)
|
|
1238
|
+
|
|
1239
|
+
# Step 4: Compute B_mat
|
|
1240
|
+
print("Step 4: Computing matrix for B...")
|
|
1241
|
+
b_mat = self._B.matrix(
|
|
1242
|
+
dense=True, galerkin=True, parallel=parallel, n_jobs=n_jobs
|
|
1243
|
+
) #
|
|
1244
|
+
|
|
1245
|
+
return m_aqa_mat + b_mat
|
|
1246
|
+
|
|
1247
|
+
def extract_diagonal(
|
|
1248
|
+
self,
|
|
1249
|
+
/,
|
|
1250
|
+
*,
|
|
1251
|
+
galerkin: bool = True,
|
|
1252
|
+
parallel: bool = False,
|
|
1253
|
+
n_jobs: int = -1,
|
|
1254
|
+
) -> np.ndarray:
|
|
1255
|
+
"""Overrides base method for efficiency."""
|
|
1256
|
+
if not galerkin:
|
|
1257
|
+
return super().extract_diagonal(
|
|
1258
|
+
galerkin=galerkin, parallel=parallel, n_jobs=n_jobs
|
|
1259
|
+
)
|
|
1260
|
+
|
|
1261
|
+
diag_B = self._B.extract_diagonal(
|
|
1262
|
+
galerkin=True, parallel=parallel, n_jobs=n_jobs
|
|
1263
|
+
)
|
|
1264
|
+
|
|
1265
|
+
dim = self.domain.dim
|
|
1266
|
+
jobs = n_jobs if parallel else 1
|
|
1267
|
+
|
|
1268
|
+
def compute_entry(i: int) -> float:
|
|
1269
|
+
e_i = self.domain.basis_vector(i)
|
|
1270
|
+
v_i = self._A.adjoint(e_i)
|
|
1271
|
+
w_i = self._Q(v_i)
|
|
1272
|
+
return self._A.domain.inner_product(v_i, w_i)
|
|
1273
|
+
|
|
1274
|
+
diag_AQA_T = Parallel(n_jobs=jobs)(
|
|
1275
|
+
delayed(compute_entry)(i) for i in range(dim)
|
|
1276
|
+
)
|
|
1277
|
+
|
|
1278
|
+
return np.array(diag_AQA_T) + diag_B
|
|
1279
|
+
|
|
1280
|
+
def extract_diagonals(
|
|
1281
|
+
self,
|
|
1282
|
+
offsets: List[int],
|
|
1283
|
+
/,
|
|
1284
|
+
*,
|
|
1285
|
+
galerkin: bool = True,
|
|
1286
|
+
parallel: bool = False,
|
|
1287
|
+
n_jobs: int = -1,
|
|
1288
|
+
) -> Tuple[np.ndarray, List[int]]:
|
|
1289
|
+
"""Overrides base method for efficiency."""
|
|
1290
|
+
if not galerkin:
|
|
1291
|
+
return super().extract_diagonals(
|
|
1292
|
+
offsets, galerkin=galerkin, parallel=parallel, n_jobs=n_jobs
|
|
1293
|
+
)
|
|
1294
|
+
|
|
1295
|
+
diagonals_B, _ = self._B.extract_diagonals(
|
|
1296
|
+
offsets, galerkin=True, parallel=parallel, n_jobs=n_jobs
|
|
1297
|
+
)
|
|
1298
|
+
|
|
1299
|
+
dim = self.domain.dim
|
|
1300
|
+
jobs = n_jobs if parallel else 1
|
|
1301
|
+
|
|
1302
|
+
# Pre-compute A*e_i for all i
|
|
1303
|
+
v_vectors = Parallel(n_jobs=jobs)(
|
|
1304
|
+
delayed(self._A.adjoint)(self.domain.basis_vector(i)) for i in range(dim)
|
|
1305
|
+
)
|
|
1306
|
+
|
|
1307
|
+
def compute_column_entries(j: int) -> Dict[int, Dict[int, float]]:
|
|
1308
|
+
col_results = defaultdict(dict)
|
|
1309
|
+
v_j = v_vectors[j]
|
|
1310
|
+
w_j = self._Q(v_j)
|
|
1311
|
+
|
|
1312
|
+
for k in offsets:
|
|
1313
|
+
i = j - k
|
|
1314
|
+
if 0 <= i < dim:
|
|
1315
|
+
v_i = v_vectors[i]
|
|
1316
|
+
val = self._A.domain.inner_product(v_i, w_j)
|
|
1317
|
+
col_results[k][i] = val
|
|
1318
|
+
return col_results
|
|
1319
|
+
|
|
1320
|
+
column_data = Parallel(n_jobs=jobs)(
|
|
1321
|
+
delayed(compute_column_entries)(j) for j in range(dim)
|
|
1322
|
+
)
|
|
1323
|
+
|
|
1324
|
+
results: Dict[int, Dict[int, float]] = defaultdict(dict)
|
|
1325
|
+
for col_dict in column_data:
|
|
1326
|
+
for k, entries in col_dict.items():
|
|
1327
|
+
results[k].update(entries)
|
|
1328
|
+
|
|
1329
|
+
diagonals_array = np.zeros((len(offsets), dim))
|
|
1330
|
+
for idx, k in enumerate(offsets):
|
|
1331
|
+
diag_entries = results[k]
|
|
1332
|
+
for i, val in diag_entries.items():
|
|
1333
|
+
j = i + k
|
|
1334
|
+
if 0 <= j < dim:
|
|
1335
|
+
diagonals_array[idx, j] = val
|
|
1336
|
+
|
|
1337
|
+
return diagonals_array + diagonals_B, offsets
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|