blocksolver 0.8.2__cp313-cp313-win_amd64.whl → 0.8.4__cp313-cp313-win_amd64.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.
blocksolver/__init__.py CHANGED
@@ -39,7 +39,7 @@ from .blqmr import (
39
39
  HAS_NUMBA,
40
40
  )
41
41
 
42
- __version__ = "0.8.2"
42
+ __version__ = "0.8.4"
43
43
  __author__ = "Qianqian Fang"
44
44
 
45
45
  __all__ = [
Binary file
blocksolver/blqmr.py CHANGED
@@ -50,7 +50,7 @@ try:
50
50
  from numba import njit
51
51
 
52
52
  HAS_NUMBA = True
53
- except ImportError:
53
+ except (ImportError, Exception) as e:
54
54
  HAS_NUMBA = False
55
55
 
56
56
  def njit(*args, **kwargs):
@@ -437,8 +437,101 @@ class BLQMRWorkspace:
437
437
  # Preconditioner Factory
438
438
  # =============================================================================
439
439
 
440
+ # Type alias for precond_type
441
+ PrecondType = Optional[Union[str, int]]
442
+
443
+
444
+ def _parse_precond_type_for_fortran(precond_type: PrecondType) -> int:
445
+ """
446
+ Convert precond_type to Fortran integer code.
447
+
448
+ Returns
449
+ -------
450
+ int
451
+ 0 = no preconditioning
452
+ 2 = ILU
453
+ 3 = diagonal/Jacobi
454
+ """
455
+ if precond_type is None or precond_type == "" or precond_type is False:
456
+ return 0
457
+
458
+ if isinstance(precond_type, int):
459
+ return precond_type
460
+
461
+ if isinstance(precond_type, str):
462
+ precond_lower = precond_type.lower()
463
+ if precond_lower in ("ilu", "ilu0", "ilut"):
464
+ return 2
465
+ elif precond_lower in ("diag", "jacobi"):
466
+ return 3
467
+ else:
468
+ # Unknown string, default to no preconditioning
469
+ warnings.warn(
470
+ f"Unknown precond_type '{precond_type}' for Fortran backend, using no preconditioning"
471
+ )
472
+ return 0
473
+
474
+ return 0
475
+
476
+
477
+ def _get_preconditioner_for_native(A, precond_type: PrecondType, M1_provided):
478
+ """
479
+ Create preconditioner for native Python backend.
480
+
481
+ Parameters
482
+ ----------
483
+ A : sparse matrix
484
+ System matrix
485
+ precond_type : None, '', str, or int
486
+ Preconditioner type specification
487
+ M1_provided : preconditioner or None
488
+ User-provided preconditioner (takes precedence)
440
489
 
441
- def make_preconditioner(A: sparse.spmatrix, precond_type: str = "diag", **kwargs):
490
+ Returns
491
+ -------
492
+ M1 : preconditioner or None
493
+ """
494
+ # If user provided M1, use it
495
+ if M1_provided is not None:
496
+ return M1_provided
497
+
498
+ # No preconditioning requested
499
+ if precond_type is None or precond_type == "" or precond_type is False:
500
+ return None
501
+
502
+ # Integer codes (for compatibility)
503
+ if isinstance(precond_type, int):
504
+ if precond_type == 0:
505
+ return None
506
+ elif precond_type == 2:
507
+ precond_str = "ilu"
508
+ elif precond_type == 3:
509
+ precond_str = "diag"
510
+ else:
511
+ precond_str = "ilu" # Default to ILU for other integers
512
+ else:
513
+ precond_str = precond_type
514
+
515
+ # Create preconditioner
516
+ try:
517
+ return make_preconditioner(A, precond_str)
518
+ except Exception as e:
519
+ # Fallback chain: try diag if ilu fails
520
+ if precond_str not in ("diag", "jacobi"):
521
+ try:
522
+ warnings.warn(
523
+ f"Preconditioner '{precond_str}' failed: {e}, falling back to diagonal"
524
+ )
525
+ return make_preconditioner(A, "diag")
526
+ except Exception:
527
+ pass
528
+ warnings.warn(f"All preconditioners failed, proceeding without preconditioning")
529
+ return None
530
+
531
+
532
+ def make_preconditioner(
533
+ A: sparse.spmatrix, precond_type: str = "diag", split: bool = False, **kwargs
534
+ ):
442
535
  """
443
536
  Create a preconditioner for iterative solvers.
444
537
 
@@ -449,25 +542,32 @@ def make_preconditioner(A: sparse.spmatrix, precond_type: str = "diag", **kwargs
449
542
  precond_type : str
450
543
  'diag' or 'jacobi': Diagonal (Jacobi) preconditioner
451
544
  'ilu' or 'ilu0': Incomplete LU with minimal fill
452
- 'ilut': Incomplete LU with threshold (better quality)
453
- 'lu': Full LU factorization (exact, use as reference)
454
- 'ssor': Symmetric SOR
545
+ 'ilut': Incomplete LU with threshold
546
+ 'lu': Full LU factorization
547
+ split : bool
548
+ If True, return sqrt(D) for split preconditioning (M1=M2=sqrt(D))
549
+ If False, return D for left preconditioning
455
550
  **kwargs : dict
456
- Additional parameters for ILU:
457
- - drop_tol: Drop tolerance (default: 1e-4 for ilut, 0 for ilu0)
458
- - fill_factor: Fill factor (default: 10 for ilut, 1 for ilu0)
551
+ Additional parameters
459
552
 
460
553
  Returns
461
554
  -------
462
555
  M : preconditioner object
463
- Preconditioner (use as M1 in blqmr)
556
+ For split Jacobi, use as: blqmr(A, b, M1=M, M2=M)
464
557
  """
465
558
  if precond_type in ("diag", "jacobi"):
466
559
  diag = A.diagonal().copy()
467
560
  diag[np.abs(diag) < 1e-14] = 1.0
468
- return sparse.diags(
469
- 1.0 / diag, format="csr"
470
- ) # Return inverse for preconditioning!
561
+
562
+ if split:
563
+ # For split preconditioning: return sqrt(D)
564
+ # Usage: M1 = M2 = sqrt(D), gives D^{-1/2} A D^{-1/2}
565
+ sqrt_diag = np.sqrt(diag)
566
+ return sparse.diags(sqrt_diag, format="csr")
567
+ else:
568
+ # For left preconditioning: return D
569
+ # Usage: M1 = D, M2 = None, gives D^{-1} A
570
+ return sparse.diags(diag, format="csr")
471
571
 
472
572
  elif precond_type == "ilu0":
473
573
  # ILU(0) - no fill-in, fast but may be poor quality
@@ -569,19 +669,43 @@ def _blqmr_python_impl(
569
669
  ws = workspace
570
670
  ws.reset()
571
671
 
572
- # Setup preconditioner
573
- if M1 is not None:
672
+ # Setup preconditioner - distinguish split vs left-only
673
+ use_split_precond = False
674
+ precond = None
675
+ precond_M1 = None
676
+ precond_M2 = None
677
+
678
+ if M1 is not None and M2 is not None:
679
+ # Split preconditioning: M1⁻¹ A M2⁻¹
680
+ use_split_precond = True
574
681
  if isinstance(M1, (_ILUPreconditioner, _LUPreconditioner)):
575
- precond = SparsePreconditioner(M1, M2)
682
+ precond_M1 = SparsePreconditioner(M1, None)
576
683
  elif sparse.issparse(M1):
577
- precond = SparsePreconditioner(M1, M2)
684
+ precond_M1 = SparsePreconditioner(M1, None)
578
685
  elif hasattr(M1, "solve"):
579
- # Custom preconditioner with .solve() method
580
- precond = M1 # Use directly
686
+ precond_M1 = M1
581
687
  else:
582
- precond = DensePreconditioner(M1, M2)
583
- else:
584
- precond = None
688
+ precond_M1 = DensePreconditioner(M1, None)
689
+
690
+ if isinstance(M2, (_ILUPreconditioner, _LUPreconditioner)):
691
+ precond_M2 = SparsePreconditioner(M2, None)
692
+ elif sparse.issparse(M2):
693
+ precond_M2 = SparsePreconditioner(M2, None)
694
+ elif hasattr(M2, "solve"):
695
+ precond_M2 = M2
696
+ else:
697
+ precond_M2 = DensePreconditioner(M2, None)
698
+
699
+ elif M1 is not None:
700
+ # Left-only preconditioning: M1⁻¹ A
701
+ if isinstance(M1, (_ILUPreconditioner, _LUPreconditioner)):
702
+ precond = SparsePreconditioner(M1, None)
703
+ elif sparse.issparse(M1):
704
+ precond = SparsePreconditioner(M1, None)
705
+ elif hasattr(M1, "solve"):
706
+ precond = M1
707
+ else:
708
+ precond = DensePreconditioner(M1, None)
585
709
 
586
710
  if x0 is None:
587
711
  x = np.zeros((n, m), dtype=dtype)
@@ -608,7 +732,14 @@ def _blqmr_python_impl(
608
732
  else:
609
733
  np.subtract(B, A @ x, out=ws.vt)
610
734
 
611
- if precond is not None:
735
+ # Apply preconditioner to initial residual
736
+ if use_split_precond:
737
+ # For split preconditioning, initial residual is just M1⁻¹ * (b - A*x0)
738
+ # because we're solving M1⁻¹ A M2⁻¹ y = M1⁻¹ b with y = M2*x
739
+ ws.vt[:] = precond_M1.solve(ws.vt)
740
+ if np.any(np.isnan(ws.vt)):
741
+ return x, 2, 1.0, 0, np.array([])
742
+ elif precond is not None:
612
743
  precond.solve(ws.vt, out=ws.vt)
613
744
  if np.any(np.isnan(ws.vt)):
614
745
  return x, 2, 1.0, 0, np.array([])
@@ -667,7 +798,16 @@ def _blqmr_python_impl(
667
798
  np.matmul(A, ws.v[:, :, t3], out=ws.Av)
668
799
 
669
800
  # Apply preconditioner
670
- if precond is not None:
801
+ if use_split_precond:
802
+ # Split preconditioning: M1⁻¹ * A * M2⁻¹ * v
803
+ tmp = precond_M2.solve(ws.v[:, :, t3]) # M2⁻¹ * v
804
+ if A_is_sparse:
805
+ tmp = A @ tmp # A * M2⁻¹ * v
806
+ else:
807
+ tmp = np.matmul(A, tmp)
808
+ ws.vt[:] = precond_M1.solve(tmp) - ws.v[:, :, t3n] @ ws.beta[:, :, t3].T
809
+ elif precond is not None:
810
+ # Left-only preconditioning: M⁻¹ * A * v
671
811
  precond.solve(ws.Av, out=ws.vt)
672
812
  ws.vt[:] = ws.vt - ws.v[:, :, t3n] @ ws.beta[:, :, t3].T
673
813
  else:
@@ -768,6 +908,11 @@ def _blqmr_python_impl(
768
908
  break
769
909
 
770
910
  resv = resv[:iter_count]
911
+
912
+ # For split preconditioning, recover x = M2⁻¹ * y
913
+ if use_split_precond:
914
+ x = precond_M2.solve(x)
915
+
771
916
  result = x.real if not is_complex_input else x
772
917
  return result, flag, relres, iter_count, resv
773
918
 
@@ -787,7 +932,7 @@ def blqmr_solve(
787
932
  tol: float = 1e-6,
788
933
  maxiter: Optional[int] = None,
789
934
  droptol: float = 0.001,
790
- use_precond: bool = True,
935
+ precond_type: PrecondType = "ilu",
791
936
  zero_based: bool = True,
792
937
  ) -> BLQMRResult:
793
938
  """
@@ -813,8 +958,12 @@ def blqmr_solve(
813
958
  Maximum iterations. Default is n.
814
959
  droptol : float, default 0.001
815
960
  Drop tolerance for ILU preconditioner (Fortran only).
816
- use_precond : bool, default True
817
- Whether to use ILU preconditioning.
961
+ precond_type : None, '', or str, default 'ilu'
962
+ Preconditioner type:
963
+ - None or '': No preconditioning
964
+ - 'ilu', 'ilu0', 'ilut': Incomplete LU
965
+ - 'diag', 'jacobi': Diagonal (Jacobi)
966
+ - For Fortran: integers 2 (ILU) or 3 (diagonal) also accepted
818
967
  zero_based : bool, default True
819
968
  If True, Ap and Ai use 0-based indexing (Python/C convention).
820
969
  If False, uses 1-based indexing (Fortran convention).
@@ -839,7 +988,7 @@ def blqmr_solve(
839
988
  tol=tol,
840
989
  maxiter=maxiter,
841
990
  droptol=droptol,
842
- use_precond=use_precond,
991
+ precond_type=precond_type,
843
992
  zero_based=zero_based,
844
993
  )
845
994
  else:
@@ -851,13 +1000,13 @@ def blqmr_solve(
851
1000
  x0=x0,
852
1001
  tol=tol,
853
1002
  maxiter=maxiter,
854
- use_precond=use_precond,
1003
+ precond_type=precond_type,
855
1004
  zero_based=zero_based,
856
1005
  )
857
1006
 
858
1007
 
859
1008
  def _blqmr_solve_fortran(
860
- Ap, Ai, Ax, b, *, x0, tol, maxiter, droptol, use_precond, zero_based
1009
+ Ap, Ai, Ax, b, *, x0, tol, maxiter, droptol, precond_type, zero_based
861
1010
  ) -> BLQMRResult:
862
1011
  """Fortran backend for blqmr_solve."""
863
1012
  n = len(Ap) - 1
@@ -877,10 +1026,10 @@ def _blqmr_solve_fortran(
877
1026
  Ap = Ap + 1
878
1027
  Ai = Ai + 1
879
1028
 
880
- dopcond = 1 if use_precond else 0
1029
+ pcond_type = _parse_precond_type_for_fortran(precond_type)
881
1030
 
882
1031
  x, flag, niter, relres = _blqmr.blqmr_solve_real(
883
- n, nnz, Ap, Ai, Ax, b, maxiter, tol, droptol, dopcond
1032
+ n, nnz, Ap, Ai, Ax, b, maxiter, tol, droptol, pcond_type
884
1033
  )
885
1034
 
886
1035
  return BLQMRResult(
@@ -889,7 +1038,7 @@ def _blqmr_solve_fortran(
889
1038
 
890
1039
 
891
1040
  def _blqmr_solve_native_csc(
892
- Ap, Ai, Ax, b, *, x0, tol, maxiter, use_precond, zero_based
1041
+ Ap, Ai, Ax, b, *, x0, tol, maxiter, precond_type, zero_based
893
1042
  ) -> BLQMRResult:
894
1043
  """Native Python backend for blqmr_solve with CSC input."""
895
1044
  n = len(Ap) - 1
@@ -900,15 +1049,7 @@ def _blqmr_solve_native_csc(
900
1049
 
901
1050
  A = sparse.csc_matrix((Ax, Ai, Ap), shape=(n, n))
902
1051
 
903
- M1 = None
904
- if use_precond:
905
- try:
906
- M1 = make_preconditioner(A, "ilu")
907
- except Exception:
908
- try:
909
- M1 = make_preconditioner(A, "diag") # FIX: Changed A_sp to A
910
- except Exception:
911
- M1 = None # Fall back to no preconditioning
1052
+ M1 = _get_preconditioner_for_native(A, precond_type, None)
912
1053
 
913
1054
  x, flag, relres, niter, resv = _blqmr_python_impl(
914
1055
  A, b, tol=tol, maxiter=maxiter, M1=M1, x0=x0
@@ -929,13 +1070,18 @@ def blqmr_solve_multi(
929
1070
  tol: float = 1e-6,
930
1071
  maxiter: Optional[int] = None,
931
1072
  droptol: float = 0.001,
932
- use_precond: bool = True,
1073
+ precond_type: PrecondType = "ilu",
933
1074
  zero_based: bool = True,
934
1075
  ) -> BLQMRResult:
935
1076
  """
936
1077
  Solve sparse linear system AX = B with multiple right-hand sides.
937
1078
 
938
1079
  Uses Fortran extension if available, otherwise falls back to pure Python.
1080
+
1081
+ Parameters
1082
+ ----------
1083
+ precond_type : None, '', or str, default 'ilu'
1084
+ Preconditioner type (see blqmr_solve for details)
939
1085
  """
940
1086
  n = len(Ap) - 1
941
1087
 
@@ -951,7 +1097,7 @@ def blqmr_solve_multi(
951
1097
  tol=tol,
952
1098
  maxiter=maxiter,
953
1099
  droptol=droptol,
954
- use_precond=use_precond,
1100
+ precond_type=precond_type,
955
1101
  zero_based=zero_based,
956
1102
  )
957
1103
  else:
@@ -962,13 +1108,13 @@ def blqmr_solve_multi(
962
1108
  B,
963
1109
  tol=tol,
964
1110
  maxiter=maxiter,
965
- use_precond=use_precond,
1111
+ precond_type=precond_type,
966
1112
  zero_based=zero_based,
967
1113
  )
968
1114
 
969
1115
 
970
1116
  def _blqmr_solve_multi_fortran(
971
- Ap, Ai, Ax, B, *, tol, maxiter, droptol, use_precond, zero_based
1117
+ Ap, Ai, Ax, B, *, tol, maxiter, droptol, precond_type, zero_based
972
1118
  ) -> BLQMRResult:
973
1119
  """Fortran backend for blqmr_solve_multi."""
974
1120
  n = len(Ap) - 1
@@ -987,10 +1133,11 @@ def _blqmr_solve_multi_fortran(
987
1133
  Ap = Ap + 1
988
1134
  Ai = Ai + 1
989
1135
 
990
- dopcond = 1 if use_precond else 0
1136
+ # Convert precond_type string to Fortran integer code
1137
+ pcond_type = _parse_precond_type_for_fortran(precond_type)
991
1138
 
992
1139
  X, flag, niter, relres = _blqmr.blqmr_solve_real_multi(
993
- n, nnz, nrhs, Ap, Ai, Ax, B, maxiter, tol, droptol, dopcond
1140
+ n, nnz, nrhs, Ap, Ai, Ax, B, maxiter, tol, droptol, pcond_type
994
1141
  )
995
1142
 
996
1143
  return BLQMRResult(
@@ -999,7 +1146,7 @@ def _blqmr_solve_multi_fortran(
999
1146
 
1000
1147
 
1001
1148
  def _blqmr_solve_multi_native(
1002
- Ap, Ai, Ax, B, *, tol, maxiter, use_precond, zero_based
1149
+ Ap, Ai, Ax, B, *, tol, maxiter, precond_type, zero_based
1003
1150
  ) -> BLQMRResult:
1004
1151
  """Native Python backend for blqmr_solve_multi."""
1005
1152
  n = len(Ap) - 1
@@ -1010,15 +1157,7 @@ def _blqmr_solve_multi_native(
1010
1157
 
1011
1158
  A = sparse.csc_matrix((Ax, Ai, Ap), shape=(n, n))
1012
1159
 
1013
- M1 = None
1014
- if use_precond:
1015
- try:
1016
- M1 = make_preconditioner(A, "ilu")
1017
- except Exception:
1018
- try:
1019
- M1 = make_preconditioner(A, "diag") # FIX: Changed A_sp to A
1020
- except Exception:
1021
- M1 = None # Fall back to no preconditioning
1160
+ M1 = _get_preconditioner_for_native(A, precond_type, None)
1022
1161
 
1023
1162
  if B.ndim == 1:
1024
1163
  B = B.reshape(-1, 1)
@@ -1081,7 +1220,7 @@ def blqmr(
1081
1220
  residual: bool = False,
1082
1221
  workspace: Optional[BLQMRWorkspace] = None,
1083
1222
  droptol: float = 0.001,
1084
- use_precond: bool = True,
1223
+ precond_type: PrecondType = "ilu",
1085
1224
  ) -> BLQMRResult:
1086
1225
  """
1087
1226
  Block Quasi-Minimal-Residual (BL-QMR) solver - main interface.
@@ -1097,9 +1236,10 @@ def blqmr(
1097
1236
  tol : float
1098
1237
  Convergence tolerance (default: 1e-6)
1099
1238
  maxiter : int, optional
1100
- Maximum iterations (default: n for Fortran, min(n, 20) for Python)
1239
+ Maximum iterations (default: n)
1101
1240
  M1, M2 : preconditioner, optional
1102
- Preconditioner M = M1 @ M2 (Python backend only)
1241
+ Custom preconditioners. If provided, precond_type is ignored.
1242
+ M = M1 @ M2 for split preconditioning (Python backend only)
1103
1243
  x0 : ndarray, optional
1104
1244
  Initial guess
1105
1245
  residual : bool
@@ -1108,8 +1248,13 @@ def blqmr(
1108
1248
  Pre-allocated workspace (Python backend only)
1109
1249
  droptol : float, default 0.001
1110
1250
  Drop tolerance for ILU preconditioner (Fortran backend only)
1111
- use_precond : bool, default True
1112
- Whether to use ILU preconditioning (Fortran backend only)
1251
+ precond_type : None, '', or str, default 'ilu'
1252
+ Preconditioner type (ignored if M1 is provided):
1253
+ - None or '': No preconditioning
1254
+ - 'ilu', 'ilu0', 'ilut': Incomplete LU
1255
+ - 'diag', 'jacobi': Diagonal (Jacobi)
1256
+ - 'lu': Full LU (expensive, for debugging)
1257
+ - For Fortran: integers 2 (ILU) or 3 (diagonal) also accepted
1113
1258
 
1114
1259
  Returns
1115
1260
  -------
@@ -1129,7 +1274,7 @@ def blqmr(
1129
1274
  maxiter=maxiter,
1130
1275
  x0=x0,
1131
1276
  droptol=droptol,
1132
- use_precond=use_precond,
1277
+ precond_type=precond_type,
1133
1278
  )
1134
1279
  else:
1135
1280
  return _blqmr_native(
@@ -1142,7 +1287,7 @@ def blqmr(
1142
1287
  x0=x0,
1143
1288
  residual=residual,
1144
1289
  workspace=workspace,
1145
- use_precond=use_precond,
1290
+ precond_type=precond_type,
1146
1291
  )
1147
1292
 
1148
1293
 
@@ -1154,7 +1299,7 @@ def _blqmr_fortran(
1154
1299
  maxiter: Optional[int],
1155
1300
  x0: Optional[np.ndarray],
1156
1301
  droptol: float,
1157
- use_precond: bool,
1302
+ precond_type: PrecondType,
1158
1303
  ) -> BLQMRResult:
1159
1304
  """Fortran backend for blqmr()."""
1160
1305
  A_csc = sparse.csc_matrix(A)
@@ -1176,7 +1321,7 @@ def _blqmr_fortran(
1176
1321
  Ap_f = np.asfortranarray(Ap + 1, dtype=np.int32)
1177
1322
  Ai_f = np.asfortranarray(Ai + 1, dtype=np.int32)
1178
1323
 
1179
- dopcond = 1 if use_precond else 0
1324
+ pcond_type = _parse_precond_type_for_fortran(precond_type)
1180
1325
 
1181
1326
  # Check if complex
1182
1327
  is_complex = np.iscomplexobj(A) or np.iscomplexobj(B)
@@ -1189,7 +1334,7 @@ def _blqmr_fortran(
1189
1334
  # Single RHS
1190
1335
  b_f = np.asfortranarray(B.ravel(), dtype=np.complex128)
1191
1336
  x, flag, niter, relres = _blqmr.blqmr_solve_complex(
1192
- n, nnz, Ap_f, Ai_f, Ax_f, b_f, maxiter, tol, droptol, dopcond
1337
+ n, nnz, Ap_f, Ai_f, Ax_f, b_f, maxiter, tol, droptol, pcond_type
1193
1338
  )
1194
1339
  return BLQMRResult(
1195
1340
  x=x.copy(), flag=int(flag), iter=int(niter), relres=float(relres)
@@ -1199,7 +1344,7 @@ def _blqmr_fortran(
1199
1344
  B_f = np.asfortranarray(B, dtype=np.complex128)
1200
1345
  nrhs = B_f.shape[1]
1201
1346
  X, flag, niter, relres = _blqmr.blqmr_solve_complex_multi(
1202
- n, nnz, nrhs, Ap_f, Ai_f, Ax_f, B_f, maxiter, tol, droptol, dopcond
1347
+ n, nnz, nrhs, Ap_f, Ai_f, Ax_f, B_f, maxiter, tol, droptol, pcond_type
1203
1348
  )
1204
1349
  return BLQMRResult(
1205
1350
  x=X.copy(), flag=int(flag), iter=int(niter), relres=float(relres)
@@ -1212,7 +1357,7 @@ def _blqmr_fortran(
1212
1357
  # Single RHS
1213
1358
  b_f = np.asfortranarray(B.ravel(), dtype=np.float64)
1214
1359
  x, flag, niter, relres = _blqmr.blqmr_solve_real(
1215
- n, nnz, Ap_f, Ai_f, Ax_f, b_f, maxiter, tol, droptol, dopcond
1360
+ n, nnz, Ap_f, Ai_f, Ax_f, b_f, maxiter, tol, droptol, pcond_type
1216
1361
  )
1217
1362
  return BLQMRResult(
1218
1363
  x=x.copy(), flag=int(flag), iter=int(niter), relres=float(relres)
@@ -1222,7 +1367,7 @@ def _blqmr_fortran(
1222
1367
  B_f = np.asfortranarray(B, dtype=np.float64)
1223
1368
  nrhs = B_f.shape[1]
1224
1369
  X, flag, niter, relres = _blqmr.blqmr_solve_real_multi(
1225
- n, nnz, nrhs, Ap_f, Ai_f, Ax_f, B_f, maxiter, tol, droptol, dopcond
1370
+ n, nnz, nrhs, Ap_f, Ai_f, Ax_f, B_f, maxiter, tol, droptol, pcond_type
1226
1371
  )
1227
1372
  return BLQMRResult(
1228
1373
  x=X.copy(), flag=int(flag), iter=int(niter), relres=float(relres)
@@ -1240,19 +1385,13 @@ def _blqmr_native(
1240
1385
  x0: Optional[np.ndarray],
1241
1386
  residual: bool,
1242
1387
  workspace: Optional[BLQMRWorkspace],
1243
- use_precond: bool,
1388
+ precond_type: PrecondType,
1244
1389
  ) -> BLQMRResult:
1245
1390
  """Native Python backend for blqmr()."""
1246
- # Auto-create preconditioner if requested and not provided
1247
- if use_precond and M1 is None:
1391
+ # Get preconditioner (user-provided M1 takes precedence)
1392
+ if M1 is None:
1248
1393
  A_sp = sparse.csc_matrix(A) if not sparse.issparse(A) else A
1249
- try:
1250
- M1 = make_preconditioner(A_sp, "ilu")
1251
- except Exception:
1252
- try:
1253
- M1 = make_preconditioner(A_sp, "diag")
1254
- except Exception:
1255
- M1 = None # Fall back to no preconditioning
1394
+ M1 = _get_preconditioner_for_native(A_sp, precond_type, None)
1256
1395
 
1257
1396
  x, flag, relres, niter, resv = _blqmr_python_impl(
1258
1397
  A,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: blocksolver
3
- Version: 0.8.2
3
+ Version: 0.8.4
4
4
  Summary: Block Quasi-Minimal-Residual sparse linear solver
5
5
  Keywords: sparse,linear-algebra,iterative-solver,qmr,fortran,umfpack
6
6
  Author-Email: Qianqian Fang <q.fang@neu.edu>
@@ -0,0 +1,7 @@
1
+ blocksolver-0.8.4.dist-info/METADATA,sha256=4s5MWTlfx-pnVrt5KaalrAVPmCflegDVOGiWnx5L23Y,13264
2
+ blocksolver-0.8.4.dist-info/WHEEL,sha256=suq8ARrxbiI7iLH3BgK-82uzxQ-4Hm-m8w01oCokrtA,85
3
+ blocksolver/_blqmr.cp313-win_amd64.pyd,sha256=a2pm7VIi67ok4vRc6YYurTKIO2kkJV4_mkNOtZrVVt0,34375165
4
+ blocksolver/_blqmr.cp313-win_amd64.dll.a,sha256=zdAww8dqSmL_SMRpDRzryczF4CYvLLdaoxCoVAeOiaU,1706
5
+ blocksolver/__init__.py,sha256=aC1Iq40CVJ_GhNLSnjnXaOeioHZV8hHw1yy349pF3VU,1982
6
+ blocksolver/blqmr.py,sha256=mrJ_ze4lYxS6uV4XGF66f1NhcwhYg6p3OERWXOGaTTU,46057
7
+ blocksolver-0.8.4.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- blocksolver-0.8.2.dist-info/METADATA,sha256=v05b9IAmx9fbUkF3hB6OmY354Bnt-uzv4FNqJmXRhuM,13264
2
- blocksolver-0.8.2.dist-info/WHEEL,sha256=suq8ARrxbiI7iLH3BgK-82uzxQ-4Hm-m8w01oCokrtA,85
3
- blocksolver/_blqmr.cp313-win_amd64.pyd,sha256=W70w3xwq3aaKbo4zNFtLYsqmi0a8Y0uR9-1kKdQH-k0,34348282
4
- blocksolver/_blqmr.cp313-win_amd64.dll.a,sha256=zdAww8dqSmL_SMRpDRzryczF4CYvLLdaoxCoVAeOiaU,1706
5
- blocksolver/__init__.py,sha256=aJUyjo4lOmok718swroWNLPFctuyfGzxdmPBJnpu_00,1982
6
- blocksolver/blqmr.py,sha256=kpkl32V8bWRGKtc8UOLoAor2yFzLryYcH_6zq02KpHo,41042
7
- blocksolver-0.8.2.dist-info/RECORD,,