Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 26 additions & 5 deletions fvgp/fvgp.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,10 @@ class fvGP(GP):
* ``"sparseSolve"`` — direct sparse solve via scipy.
* ``"sparseCGpre"`` — preconditioned conjugate-gradient. The preconditioner type
is selected by ``args["sparse_preconditioner_type"]`` (default ``"ilu"``;
also ``"ic"``/``"incomplete_cholesky"``, ``"block_jacobi"``,
``"schwarz"``/``"additive_schwarz"``, or ``"amg"`` (requires pyamg)).
also compiled incomplete Cholesky ``"ichol"``/``"ic"``/``"incomplete_cholesky"``,
zero-fill ``"ichol0"``, legacy Python IC(0) ``"native_ic"``/``"native_ichol"``,
``"block_jacobi"``, ``"schwarz"``/``"additive_schwarz"``, or ``"amg"``
(requires pyamg). The compiled IC options require the optional ``ilupp`` package.
* ``"sparseMINRESpre"`` — preconditioned MINRES; same preconditioner choices.
* ``"sparseCGpre_<type>"`` / ``"sparseMINRESpre_<type>"`` — shortcut that sets
``args["sparse_preconditioner_type"]`` to ``<type>`` (e.g. ``"sparseCGpre_amg"``).
Expand Down Expand Up @@ -239,8 +241,8 @@ class fvGP(GP):
- "sparse_krylov_warm_start" : True/False; default = False — feed the
previous training iteration's ``KVinvY`` as ``x0`` to the next solve
- "sparse_preconditioner_type" : str; default = "ilu". One of "ilu",
"ic"/"ichol"/"incomplete_cholesky", "block_jacobi", "schwarz"/
"additive_schwarz", "amg" (requires pyamg)
"ichol"/"ic"/"incomplete_cholesky", "ichol0", "native_ic"/"native_ichol",
"block_jacobi", "schwarz"/"additive_schwarz", "amg" (requires pyamg)
- "sparse_preconditioner_refresh_interval" : int; default = 1 —
reuse the cached preconditioner for up to N consecutive solves
before rebuilding. ``set_KV`` always force-refreshes.
Expand All @@ -250,12 +252,31 @@ class fvGP(GP):
additive Schwarz
- "sparse_preconditioner_drop_tol" / "sparse_preconditioner_fill_factor"
— forwarded to scipy ``spilu`` for "ilu"
- "sparse_preconditioner_ichol_fill_in" / "sparse_preconditioner_ichol_threshold"
— forwarded to ``ilupp`` thresholded IC for "ichol"
- "sparse_preconditioner_amg_*" — forwarded to pyamg
(``max_levels``, ``max_coarse``, ``strength``, ``cycle``, etc.)
- "sparse_preconditioner_shift" / "_growth" / "_attempts" — diagonal
shift retry knobs for "ic" / "block_jacobi" / "additive_schwarz" when
shift retry knobs for legacy "native_ic"/"native_ichol" / "block_jacobi" / "additive_schwarz" when
a local Cholesky encounters a non-PD block

Practical sparse-solver guidance:

- Keep compact-support covariance matrices genuinely sparse before using global
factor-based preconditioners. If the kernel support is too broad, ILU/IC
failures are usually memory/fill failures, not proof that Krylov solvers are bad.
- Prefer ``sparseCGpre_*`` as the primary path for covariance systems, which are
symmetric positive definite in the usual case. ``MINRES`` can be useful as a
comparison, but it may need a stricter ``sparse_minres_tol`` to satisfy a raw
relative residual check.
- Sweep preconditioner parameters at the target scale. For ILU, ``drop_tol`` and
``fill_factor`` control a memory/solve-time tradeoff; a slightly more expressive
factor can cost only marginally more to build but reduce solve time substantially.
- For repeated nearby K+V updates, enable ``sparse_krylov_warm_start`` and reuse
preconditioners with ``sparse_preconditioner_refresh_interval``. The best refresh
interval is problem-dependent because preconditioner build cost can be comparable
to an unconditioned solve.

Cholesky compute-device routing:

- "Chol_factor_compute_device" : str; default = "cpu"/"gpu"
Expand Down
31 changes: 26 additions & 5 deletions fvgp/gp.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,10 @@ class GP:
* ``"sparseSolve"`` — direct sparse solve via scipy.
* ``"sparseCGpre"`` — preconditioned conjugate-gradient. The preconditioner type
is selected by ``args["sparse_preconditioner_type"]`` (default ``"ilu"``;
also ``"ic"``/``"incomplete_cholesky"``, ``"block_jacobi"``,
``"schwarz"``/``"additive_schwarz"``, or ``"amg"`` (requires pyamg)).
also compiled incomplete Cholesky ``"ichol"``/``"ic"``/``"incomplete_cholesky"``,
zero-fill ``"ichol0"``, legacy Python IC(0) ``"native_ic"``/``"native_ichol"``,
``"block_jacobi"``, ``"schwarz"``/``"additive_schwarz"``, or ``"amg"``
(requires pyamg). The compiled IC options require the optional ``ilupp`` package.
* ``"sparseMINRESpre"`` — preconditioned MINRES; same preconditioner choices.
* ``"sparseCGpre_<type>"`` / ``"sparseMINRESpre_<type>"`` — shortcut that sets
``args["sparse_preconditioner_type"]`` to ``<type>`` (e.g. ``"sparseCGpre_amg"``).
Expand Down Expand Up @@ -240,8 +242,8 @@ class GP:
- "sparse_krylov_warm_start" : True/False; default = False — feed the
previous training iteration's ``KVinvY`` as ``x0`` to the next solve
- "sparse_preconditioner_type" : str; default = "ilu". One of "ilu",
"ic"/"ichol"/"incomplete_cholesky", "block_jacobi", "schwarz"/
"additive_schwarz", "amg" (requires pyamg)
"ichol"/"ic"/"incomplete_cholesky", "ichol0", "native_ic"/"native_ichol",
"block_jacobi", "schwarz"/"additive_schwarz", "amg" (requires pyamg)
- "sparse_preconditioner_refresh_interval" : int; default = 1 —
reuse the cached preconditioner for up to N consecutive solves
before rebuilding. ``set_KV`` always force-refreshes.
Expand All @@ -251,12 +253,31 @@ class GP:
additive Schwarz
- "sparse_preconditioner_drop_tol" / "sparse_preconditioner_fill_factor"
— forwarded to scipy ``spilu`` for "ilu"
- "sparse_preconditioner_ichol_fill_in" / "sparse_preconditioner_ichol_threshold"
— forwarded to ``ilupp`` thresholded IC for "ichol"
- "sparse_preconditioner_amg_*" — forwarded to pyamg
(``max_levels``, ``max_coarse``, ``strength``, ``cycle``, etc.)
- "sparse_preconditioner_shift" / "_growth" / "_attempts" — diagonal
shift retry knobs for "ic" / "block_jacobi" / "additive_schwarz" when
shift retry knobs for legacy "native_ic"/"native_ichol" / "block_jacobi" / "additive_schwarz" when
a local Cholesky encounters a non-PD block

Practical sparse-solver guidance:

- Keep compact-support covariance matrices genuinely sparse before using global
factor-based preconditioners. If the kernel support is too broad, ILU/IC
failures are usually memory/fill failures, not proof that Krylov solvers are bad.
- Prefer ``sparseCGpre_*`` as the primary path for covariance systems, which are
symmetric positive definite in the usual case. ``MINRES`` can be useful as a
comparison, but it may need a stricter ``sparse_minres_tol`` to satisfy a raw
relative residual check.
- Sweep preconditioner parameters at the target scale. For ILU, ``drop_tol`` and
``fill_factor`` control a memory/solve-time tradeoff; a slightly more expressive
factor can cost only marginally more to build but reduce solve time substantially.
- For repeated nearby K+V updates, enable ``sparse_krylov_warm_start`` and reuse
preconditioners with ``sparse_preconditioner_refresh_interval``. The best refresh
interval is problem-dependent because preconditioner build cost can be comparable
to an unconditioned solve.

Cholesky compute-device routing:

- "Chol_factor_compute_device" : str; default = "cpu"/"gpu"
Expand Down
10 changes: 9 additions & 1 deletion fvgp/gp_kv.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def __init__(self,
self.Preconditioner_signature = None
self.Preconditioner_KV_shape = None
self.Preconditioner_reuse_counter = 0
self.Last_preconditioner_error = None
self.allowed_modes = ["Chol", "CholInv", "Inv", "sparseMINRES", "sparseCG",
"sparseLU", "sparseMINRESpre", "sparseCGpre",
"sparseMINRESpre_<type>", "sparseCGpre_<type>",
Expand Down Expand Up @@ -121,6 +122,7 @@ def _reset_sparse_preconditioner(self):
self.Preconditioner_signature = None
self.Preconditioner_KV_shape = None
self.Preconditioner_reuse_counter = 0
self.Last_preconditioner_error = None

def _can_reuse_sparse_preconditioner(self, KV):
if self.mode not in self._PRECONDITIONED_MODES:
Expand All @@ -140,9 +142,12 @@ def _build_sparse_preconditioner_or_none(self, KV):
try:
factor, operator = calculate_sparse_preconditioner(KV, args=self.args)
except Exception as exc:
self.Last_preconditioner_error = f"{type(exc).__name__}: {exc}"
warnings.warn(
f"Failed to build sparse preconditioner for mode {self.mode}; "
"falling back to the unpreconditioned iterative solve."
f"falling back to the unpreconditioned iterative solve. "
f"Reason: {self.Last_preconditioner_error}. "
f"{sparse_preconditioner_failure_guidance(self.args)}"
)
logger.warning("Sparse preconditioner construction failed for {}: {}", self.mode, exc)
return None, None
Expand Down Expand Up @@ -172,6 +177,7 @@ def _get_or_refresh_preconditioner(self, KV, force_refresh=False):
self.Preconditioner_signature = self._preconditioner_signature()
self.Preconditioner_KV_shape = KV.shape
self.Preconditioner_reuse_counter = 0
self.Last_preconditioner_error = None
return operator

##################################################################
Expand Down Expand Up @@ -505,6 +511,7 @@ def __getstate__(self):
Preconditioner_signature=self.Preconditioner_signature,
Preconditioner_KV_shape=self.Preconditioner_KV_shape,
Preconditioner_reuse_counter=self.Preconditioner_reuse_counter,
Last_preconditioner_error=self.Last_preconditioner_error,
custom_obj=self.custom_obj,
allowed_modes=self.allowed_modes,
logdet_KV=self.logdet_KV
Expand All @@ -520,6 +527,7 @@ def __setstate__(self, state):
("Preconditioner_signature", None),
("Preconditioner_KV_shape", None),
("Preconditioner_reuse_counter", 0),
("Last_preconditioner_error", None),
):
if attr not in self.__dict__:
setattr(self, attr, default)
Loading
Loading