pygeoinf 1.0.9__py3-none-any.whl → 1.1.0__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.
@@ -1,10 +1,26 @@
1
1
  """
2
- Module for solving linear systems of equations involving abstract operators.
2
+ Provides a collection of solvers for linear systems of equations.
3
+
4
+ This module offers a unified interface for solving linear systems `A(x) = y`,
5
+ where `A` is a `LinearOperator`. It includes both direct methods based on
6
+ matrix factorization and iterative, matrix-free methods suitable for large-scale
7
+ problems.
8
+
9
+ The solvers are implemented as callable classes. An instance of a solver can
10
+ be called with an operator to produce a new operator representing its inverse.
11
+
12
+ Key Classes
13
+ -----------
14
+ - `LUSolver`, `CholeskySolver`: Direct solvers based on matrix factorization.
15
+ - `ScipyIterativeSolver`: A general wrapper for SciPy's iterative algorithms
16
+ (CG, GMRES, etc.) that operate on matrix representations.
17
+ - `CGSolver`: A pure, matrix-free implementation of the Conjugate Gradient
18
+ algorithm that operates directly on abstract Hilbert space vectors.
3
19
  """
4
20
 
5
21
  from __future__ import annotations
6
22
  from abc import ABC, abstractmethod
7
- from typing import Callable
23
+ from typing import Callable, Optional, Dict, Any
8
24
 
9
25
  import numpy as np
10
26
  from scipy.sparse.linalg import LinearOperator as ScipyLinOp
@@ -17,7 +33,7 @@ from scipy.linalg import (
17
33
  from scipy.sparse.linalg import gmres, bicgstab, cg, bicg
18
34
 
19
35
  from .operators import LinearOperator
20
- from .hilbert_space import T_vec
36
+ from .hilbert_space import Vector
21
37
 
22
38
 
23
39
  class LinearSolver(ABC):
@@ -130,34 +146,33 @@ class IterativeLinearSolver(LinearSolver):
130
146
  self,
131
147
  operator: LinearOperator,
132
148
  preconditioner: Optional[LinearOperator],
133
- y: T_vec,
134
- x0: Optional[T_vec],
135
- ) -> T_vec:
149
+ y: Vector,
150
+ x0: Optional[Vector],
151
+ ) -> Vector:
136
152
  """
137
153
  Solves the linear system Ax = y for x.
138
154
 
139
155
  Args:
140
156
  operator (LinearOperator): The operator A of the linear system.
141
157
  preconditioner (LinearOperator, optional): The preconditioner.
142
- y (T_vec): The right-hand side vector.
143
- x0 (T_vec, optional): The initial guess for the solution.
158
+ y (Vector): The right-hand side vector.
159
+ x0 (Vector, optional): The initial guess for the solution.
144
160
 
145
161
  Returns:
146
- T_vec: The solution vector x.
162
+ Vector: The solution vector x.
147
163
  """
148
164
 
149
165
  def solve_adjoint_linear_system(
150
166
  self,
151
167
  operator: LinearOperator,
152
168
  preconditioner: Optional[LinearOperator],
153
- x: T_vec,
154
- y0: Optional[T_vec],
155
- ) -> T_vec:
169
+ x: Vector,
170
+ y0: Optional[Vector],
171
+ ) -> Vector:
156
172
  """
157
173
  Solves the adjoint linear system A*y = x for y.
158
174
  """
159
- # Note: Preconditioner is not used for adjoint solve in this default impl.
160
- return self.solve_linear_system(operator.adjoint, None, x, y0)
175
+ return self.solve_linear_system(operator.adjoint, preconditioner.adjoint, x, y0)
161
176
 
162
177
  def __call__(
163
178
  self,
@@ -189,202 +204,75 @@ class IterativeLinearSolver(LinearSolver):
189
204
  )
190
205
 
191
206
 
192
- class CGMatrixSolver(IterativeLinearSolver):
207
+ class ScipyIterativeSolver(IterativeLinearSolver):
193
208
  """
194
- Iterative solver using SciPy's Conjugate Gradient (CG) algorithm on the
195
- operator's matrix representation. Assumes the operator is self-adjoint.
196
- """
197
-
198
- def __init__(
199
- self,
200
- /,
201
- *,
202
- galerkin: bool = False,
203
- rtol: float = 1.0e-5,
204
- atol: float = 0.0,
205
- maxiter: Optional[int] = None,
206
- callback: Optional[Callable[[np.ndarray], None]] = None,
207
- ) -> None:
208
- """
209
- Args:
210
- galerkin (bool): If True, use the Galerkin matrix representation.
211
- rtol (float): Relative tolerance for convergence.
212
- atol (float): Absolute tolerance for convergence.
213
- maxiter (int, optional): Maximum number of iterations.
214
- callback (callable, optional): User-supplied function to call
215
- after each iteration.
216
- """
217
- self._galerkin: bool = galerkin
218
- self._rtol: float = rtol
219
- self._atol: float = atol
220
- self._maxiter: Optional[int] = maxiter
221
- self._callback: Optional[Callable[[np.ndarray], None]] = callback
209
+ A general iterative solver that wraps SciPy's iterative algorithms.
222
210
 
223
- def solve_linear_system(
224
- self,
225
- operator: LinearOperator,
226
- preconditioner: Optional[LinearOperator],
227
- y: T_vec,
228
- x0: Optional[T_vec],
229
- ) -> T_vec:
230
- domain = operator.codomain
231
- matrix = operator.matrix(galerkin=self._galerkin)
232
-
233
- matrix_preconditioner = (
234
- None
235
- if preconditioner is None
236
- else preconditioner.matrix(galerkin=self._galerkin)
237
- )
238
-
239
- cx0 = None if x0 is None else domain.to_components(x0)
240
- cy = domain.to_components(y)
241
-
242
- cxp, _ = cg(
243
- matrix,
244
- cy,
245
- x0=cx0,
246
- rtol=self._rtol,
247
- atol=self._atol,
248
- maxiter=self._maxiter,
249
- M=matrix_preconditioner,
250
- callback=self._callback,
251
- )
252
- if self._galerkin:
253
- xp = domain.dual.from_components(cxp)
254
- return domain.from_dual(xp)
255
- else:
256
- return domain.from_components(cxp)
257
-
258
-
259
- class BICGMatrixSolver(IterativeLinearSolver):
260
- """
261
- Iterative solver using SciPy's Biconjugate Gradient (BiCG) algorithm on
262
- the operator's matrix representation. For general square operators.
211
+ This class provides a unified interface to SciPy's sparse iterative
212
+ solvers like `cg`, `gmres`, `bicgstab`, etc. The specific algorithm is chosen
213
+ during instantiation, and keyword arguments are passed directly to the
214
+ chosen SciPy function.
263
215
  """
264
216
 
265
- def __init__(
266
- self,
267
- /,
268
- *,
269
- galerkin: bool = False,
270
- rtol: float = 1.0e-5,
271
- atol: float = 0.0,
272
- maxiter: Optional[int] = None,
273
- callback: Optional[Callable[[np.ndarray], None]] = None,
274
- ) -> None:
275
- """
276
- Args:
277
- galerkin (bool): If True, use the Galerkin matrix representation.
278
- rtol (float): Relative tolerance for convergence.
279
- atol (float): Absolute tolerance for convergence.
280
- maxiter (int, optional): Maximum number of iterations.
281
- callback (callable, optional): User-supplied function to call
282
- after each iteration.
283
- """
284
- self._galerkin: bool = galerkin
285
- self._rtol: float = rtol
286
- self._atol: float = atol
287
- self._maxiter: Optional[int] = maxiter
288
- self._callback: Optional[Callable[[np.ndarray], None]] = callback
289
-
290
- def solve_linear_system(
291
- self,
292
- operator: LinearOperator,
293
- preconditioner: Optional[LinearOperator],
294
- y: T_vec,
295
- x0: Optional[T_vec],
296
- ) -> T_vec:
297
- domain = operator.codomain
298
- codomain = operator.domain
299
- matrix = operator.matrix(galerkin=self._galerkin)
300
-
301
- matrix_preconditioner = (
302
- None
303
- if preconditioner is None
304
- else preconditioner.matrix(galerkin=self._galerkin)
305
- )
306
-
307
- cx0 = None if x0 is None else domain.to_components(x0)
308
- cy = domain.to_components(y)
309
-
310
- cxp, _ = bicg(
311
- matrix,
312
- cy,
313
- x0=cx0,
314
- rtol=self._rtol,
315
- atol=self._atol,
316
- maxiter=self._maxiter,
317
- M=matrix_preconditioner,
318
- callback=self._callback,
319
- )
320
- if self._galerkin:
321
- xp = codomain.dual.from_components(cxp)
322
- return codomain.from_dual(xp)
323
- else:
324
- return codomain.from_components(cxp)
325
-
326
-
327
- class BICGStabMatrixSolver(IterativeLinearSolver):
328
- """
329
- Iterative solver using SciPy's Biconjugate Gradient Stabilized (BiCGSTAB)
330
- algorithm on the operator's matrix representation. For general square operators.
331
- """
217
+ _SOLVER_MAP = {
218
+ "cg": cg,
219
+ "bicg": bicg,
220
+ "bicgstab": bicgstab,
221
+ "gmres": gmres,
222
+ }
332
223
 
333
224
  def __init__(
334
225
  self,
226
+ method: str,
335
227
  /,
336
228
  *,
337
229
  galerkin: bool = False,
338
- rtol: float = 1.0e-5,
339
- atol: float = 0.0,
340
- maxiter: Optional[int] = None,
341
- callback: Optional[Callable[[np.ndarray], None]] = None,
230
+ **kwargs,
342
231
  ) -> None:
343
232
  """
344
233
  Args:
234
+ method (str): The name of the SciPy solver to use (e.g., 'cg', 'gmres').
345
235
  galerkin (bool): If True, use the Galerkin matrix representation.
346
- rtol (float): Relative tolerance for convergence.
347
- atol (float): Absolute tolerance for convergence.
348
- maxiter (int, optional): Maximum number of iterations.
349
- callback (callable, optional): User-supplied function to call
350
- after each iteration.
236
+ **kwargs: Keyword arguments to be passed directly to the SciPy solver
237
+ (e.g., rtol, atol, maxiter, restart).
351
238
  """
239
+ if method not in self._SOLVER_MAP:
240
+ raise ValueError(
241
+ f"Unknown solver method '{method}'. Available methods: {list(self._SOLVER_MAP.keys())}"
242
+ )
243
+
244
+ self._solver_func = self._SOLVER_MAP[method]
352
245
  self._galerkin: bool = galerkin
353
- self._rtol: float = rtol
354
- self._atol: float = atol
355
- self._maxiter: Optional[int] = maxiter
356
- self._callback: Optional[Callable[[np.ndarray], None]] = callback
246
+ self._solver_kwargs: Dict[str, Any] = kwargs
357
247
 
358
248
  def solve_linear_system(
359
249
  self,
360
250
  operator: LinearOperator,
361
251
  preconditioner: Optional[LinearOperator],
362
- y: T_vec,
363
- x0: Optional[T_vec],
364
- ) -> T_vec:
252
+ y: Vector,
253
+ x0: Optional[Vector],
254
+ ) -> Vector:
365
255
  domain = operator.codomain
366
256
  codomain = operator.domain
367
- matrix = operator.matrix(galerkin=self._galerkin)
368
257
 
258
+ matrix = operator.matrix(galerkin=self._galerkin)
369
259
  matrix_preconditioner = (
370
260
  None
371
261
  if preconditioner is None
372
262
  else preconditioner.matrix(galerkin=self._galerkin)
373
263
  )
374
264
 
375
- cx0 = None if x0 is None else domain.to_components(x0)
376
265
  cy = domain.to_components(y)
266
+ cx0 = None if x0 is None else domain.to_components(x0)
377
267
 
378
- cxp, _ = bicgstab(
268
+ cxp, _ = self._solver_func(
379
269
  matrix,
380
270
  cy,
381
271
  x0=cx0,
382
- rtol=self._rtol,
383
- atol=self._atol,
384
- maxiter=self._maxiter,
385
272
  M=matrix_preconditioner,
386
- callback=self._callback,
273
+ **self._solver_kwargs,
387
274
  )
275
+
388
276
  if self._galerkin:
389
277
  xp = codomain.dual.from_components(cxp)
390
278
  return codomain.from_dual(xp)
@@ -392,81 +280,20 @@ class BICGStabMatrixSolver(IterativeLinearSolver):
392
280
  return codomain.from_components(cxp)
393
281
 
394
282
 
395
- class GMRESMatrixSolver(IterativeLinearSolver):
396
- """
397
- Iterative solver using SciPy's Generalized Minimal Residual (GMRES)
398
- algorithm on the operator's matrix representation. For general square operators.
399
- """
283
+ def CGMatrixSolver(galerkin: bool = False, **kwargs) -> ScipyIterativeSolver:
284
+ return ScipyIterativeSolver("cg", galerkin=galerkin, **kwargs)
400
285
 
401
- def __init__(
402
- self,
403
- /,
404
- *,
405
- galerkin: bool = False,
406
- rtol: float = 1.0e-5,
407
- atol: float = 0.0,
408
- restart: Optional[int] = None,
409
- maxiter: Optional[int] = None,
410
- callback: Optional[Callable] = None,
411
- callback_type: Optional[str] = None,
412
- ) -> None:
413
- """
414
- Args:
415
- galerkin (bool): If True, use the Galerkin matrix representation.
416
- rtol (float): Relative tolerance for convergence.
417
- atol (float): Absolute tolerance for convergence.
418
- restart (int, optional): Number of iterations between restarts.
419
- maxiter (int, optional): Maximum number of iterations.
420
- callback (callable, optional): User-supplied function to call
421
- during iterations.
422
- callback_type (str, optional): Type of callback ("x", "pr_norm").
423
- """
424
- self._galerkin: bool = galerkin
425
- self._rtol: float = rtol
426
- self._atol: float = atol
427
- self._restart: Optional[int] = restart
428
- self._maxiter: Optional[int] = maxiter
429
- self._callback: Optional[Callable] = callback
430
- self._callback_type: Optional[str] = callback_type
431
286
 
432
- def solve_linear_system(
433
- self,
434
- operator: LinearOperator,
435
- preconditioner: Optional[LinearOperator],
436
- y: T_vec,
437
- x0: Optional[T_vec],
438
- ) -> T_vec:
439
- domain = operator.codomain
440
- codomain = operator.domain
441
- matrix = operator.matrix(galerkin=self._galerkin)
287
+ def BICGMatrixSolver(galerkin: bool = False, **kwargs) -> ScipyIterativeSolver:
288
+ return ScipyIterativeSolver("bicg", galerkin=galerkin, **kwargs)
442
289
 
443
- matrix_preconditioner = (
444
- None
445
- if preconditioner is None
446
- else preconditioner.matrix(galerkin=self._galerkin)
447
- )
448
290
 
449
- cx0 = None if x0 is None else domain.to_components(x0)
450
- cy = domain.to_components(y)
291
+ def BICGStabMatrixSolver(galerkin: bool = False, **kwargs) -> ScipyIterativeSolver:
292
+ return ScipyIterativeSolver("bicgstab", galerkin=galerkin, **kwargs)
451
293
 
452
- cxp, _ = gmres(
453
- matrix,
454
- cy,
455
- x0=cx0,
456
- rtol=self._rtol,
457
- atol=self._atol,
458
- restart=self._restart,
459
- maxiter=self._maxiter,
460
- M=matrix_preconditioner,
461
- callback=self._callback,
462
- callback_type=self._callback_type,
463
- )
464
294
 
465
- if self._galerkin:
466
- xp = codomain.dual.from_components(cxp)
467
- return codomain.from_dual(xp)
468
- else:
469
- return codomain.from_components(cxp)
295
+ def GMRESMatrixSolver(galerkin: bool = False, **kwargs) -> ScipyIterativeSolver:
296
+ return ScipyIterativeSolver("gmres", galerkin=galerkin, **kwargs)
470
297
 
471
298
 
472
299
  class CGSolver(IterativeLinearSolver):
@@ -485,7 +312,7 @@ class CGSolver(IterativeLinearSolver):
485
312
  rtol: float = 1.0e-5,
486
313
  atol: float = 0.0,
487
314
  maxiter: Optional[int] = None,
488
- callback: Optional[Callable[[T_vec], None]] = None,
315
+ callback: Optional[Callable[[Vector], None]] = None,
489
316
  ) -> None:
490
317
  """
491
318
  Args:
@@ -507,15 +334,15 @@ class CGSolver(IterativeLinearSolver):
507
334
  raise ValueError("maxiter must be None or positive")
508
335
  self._maxiter: Optional[int] = maxiter
509
336
 
510
- self._callback: Optional[Callable[[T_vec], None]] = callback
337
+ self._callback: Optional[Callable[[Vector], None]] = callback
511
338
 
512
339
  def solve_linear_system(
513
340
  self,
514
341
  operator: LinearOperator,
515
342
  preconditioner: Optional[LinearOperator],
516
- y: T_vec,
517
- x0: Optional[T_vec],
518
- ) -> T_vec:
343
+ y: Vector,
344
+ x0: Optional[Vector],
345
+ ) -> Vector:
519
346
  domain = operator.domain
520
347
  x = domain.zero if x0 is None else domain.copy(x0)
521
348