Source code for gwsource.config
"""Configuration of a trace: accounting mode, solver choice, strong sinks and cycle handling."""
from dataclasses import dataclass
ACCOUNTING_MODES = ("gross", "net")
SOLVERS = ("auto", "topological", "block", "lu")
[docs]
@dataclass
class TraceConfig:
"""Options for backward() and forward().
accounting: "gross" (default; one sink/source node per external term, outflow/inflow
normalisation) or "net" (net source/sink per cell, destination cells absorbing; the
reference method of Foley & Black 2013, for regression and comparison only).
solver: "auto" (topological substitution if the flow graph is acyclic, block-triangular
sparse LU otherwise), "topological" (fails on cyclic graphs), "block" (block-triangular
LU in strongly-connected-component order) or "lu" (SuperLU with COLAMD ordering on the
whole matrix; slow on large 3D grids, kept as a reference).
closed_loop_tol: a strongly connected set with no edge leaving it and no row whose exit
share (sink or source flow / cell throughflow) exceeds this is a closed recirculation
loop; its fractions are set to 0 and it is reported (result.closed_loops).
remove_cycles: net mode only; delete the smallest edge of each flow cycle until the graph is
acyclic, as the reference method does.
strong_sinks: backward traces only; package selectors (names, budget texts or types, any
case, as select(package=)) whose sink cells are absorbing, or "all" for every cell with
a sink term. A strong-sink cell passes no water on to its neighbours, as a cell that
stops particles (MODPATH 7 weak sink option "stop"). Gross: its F is its destination
share of its sink outflow, d_i / Snk_i, so water in a cell with sinks of several groups
is split by their flows. Net: destination cells stay fixed at 1 and the other
strong-sink cells absorb with f = 0. Default () keeps every cell mixing.
Any outflowing sink term (q < 0), however small, makes its cell absorbing, as the
MODPATH 7 stop option does. A package's *-TO-MVR record matches the package and is
one of its sink terms: MF6 splits a provider's outflow between the package record and
its -TO-MVR record, so Snk counts that outflow once, as their sum. trace_transient and
the ensembles pass this config to every step or realisation, so strong sinks apply to
each steady backward snapshot.
"""
accounting: str = "gross"
solver: str = "auto"
remove_cycles: bool = False
closed_loop_tol: float = 1e-12
strong_sinks: str | tuple[str, ...] = ()
def __post_init__(self) -> None:
"""Validate option values."""
assert self.accounting in ACCOUNTING_MODES, f"accounting must be one of {ACCOUNTING_MODES}"
assert self.solver in SOLVERS, f"solver must be one of {SOLVERS}"
assert not (self.remove_cycles and self.accounting != "net"), (
"remove_cycles is only available in net mode"
)
if self.strong_sinks != "all":
assert not isinstance(self.strong_sinks, str), (
'strong_sinks must be "all" or a collection of package names, e.g. ("WEL",)'
)
self.strong_sinks = tuple(self.strong_sinks)