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.
Files changed (28) hide show
  1. {pygeoinf-1.2.5 → pygeoinf-1.2.7}/PKG-INFO +1 -1
  2. {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/__init__.py +1 -4
  3. {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/gaussian_measure.py +1 -1
  4. {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/linear_operators.py +383 -45
  5. {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pyproject.toml +1 -1
  6. {pygeoinf-1.2.5 → pygeoinf-1.2.7}/LICENSE +0 -0
  7. {pygeoinf-1.2.5 → pygeoinf-1.2.7}/README.md +0 -0
  8. {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/backus_gilbert.py +0 -0
  9. {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/checks/hilbert_space.py +0 -0
  10. {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/checks/linear_operators.py +0 -0
  11. {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/checks/nonlinear_operators.py +0 -0
  12. {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/direct_sum.py +0 -0
  13. {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/forward_problem.py +0 -0
  14. {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/hilbert_space.py +0 -0
  15. {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/inversion.py +0 -0
  16. {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/linear_bayesian.py +0 -0
  17. {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/linear_forms.py +0 -0
  18. {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/linear_optimisation.py +0 -0
  19. {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/linear_solvers.py +0 -0
  20. {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/nonlinear_forms.py +0 -0
  21. {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/nonlinear_operators.py +0 -0
  22. {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/nonlinear_optimisation.py +0 -0
  23. {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/parallel.py +0 -0
  24. {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/random_matrix.py +0 -0
  25. {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/symmetric_space/__init__.py +0 -0
  26. {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/symmetric_space/circle.py +0 -0
  27. {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/symmetric_space/sphere.py +0 -0
  28. {pygeoinf-1.2.5 → pygeoinf-1.2.7}/pygeoinf/symmetric_space/symmetric_space.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pygeoinf
3
- Version: 1.2.5
3
+ Version: 1.2.7
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
@@ -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, galerkin=True
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 that is diagonal in its component representation.
1041
+ """A LinearOperator whose Galerkin representation is diagonal.
920
1042
 
921
- This provides an efficient implementation for diagonal linear operators.
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 representation."""
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
- This creates a new `DiagonalLinearOperator` where the function `f` has
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
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pygeoinf"
3
- version = "1.2.5"
3
+ version = "1.2.7"
4
4
  description = "A package for solving geophysical inference and inverse problems"
5
5
  authors = ["David Al-Attar and Dan Heathcote"]
6
6
  readme = "README.md"
File without changes
File without changes
File without changes
File without changes