mantis-rtm 0.1.1.dev0__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.
@@ -0,0 +1,219 @@
1
+ """
2
+ Numba-accelerated numerical kernels for direct assembled reactive transport solvers.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import numba as nb
8
+ import numpy as np
9
+
10
+
11
+ @nb.njit(fastmath=True)
12
+ def solve_tridiagonal_thomas(
13
+ ab: np.ndarray,
14
+ b: np.ndarray,
15
+ out: np.ndarray,
16
+ c_prime: np.ndarray,
17
+ ) -> None:
18
+ """
19
+ O(N) In-place Thomas algorithm (TDMA) for tridiagonal systems in SciPy banded (1, 1) layout.
20
+
21
+ Matrix layout in ab (shape 3, N):
22
+ - row 0: superdiagonal (ab[0, 1:] contains A[i, i+1] for i = 0..N-2)
23
+ - row 1: main diagonal (ab[1, :] contains A[i, i] for i = 0..N-1)
24
+ - row 2: subdiagonal (ab[2, :-1] contains A[i+1, i] for i = 0..N-2)
25
+
26
+ b: RHS vector of shape (N,)
27
+ out: solution vector of shape (N,), updated in-place
28
+ c_prime: work scratch array of shape (N - 1,)
29
+ """
30
+ n = len(b)
31
+ denom0 = ab[1, 0]
32
+ c_prime[0] = ab[0, 1] / denom0
33
+ out[0] = b[0] / denom0
34
+
35
+ # Forward elimination
36
+ for i in range(1, n - 1):
37
+ ai = ab[2, i - 1]
38
+ denom = ab[1, i] - ai * c_prime[i - 1]
39
+ c_prime[i] = ab[0, i + 1] / denom
40
+ out[i] = (b[i] - ai * out[i - 1]) / denom
41
+
42
+ ai_last = ab[2, n - 2]
43
+ denom_last = ab[1, n - 1] - ai_last * c_prime[n - 2]
44
+ out[n - 1] = (b[n - 1] - ai_last * out[n - 2]) / denom_last
45
+
46
+ # Back substitution
47
+ for i in range(n - 2, -1, -1):
48
+ out[i] -= c_prime[i] * out[i + 1]
49
+
50
+
51
+ @nb.njit(fastmath=True)
52
+ def compute_species_residual_wrms(
53
+ curr_val: np.ndarray,
54
+ prev_val: np.ndarray,
55
+ inner_tol: float,
56
+ atol: float,
57
+ ) -> float:
58
+ """
59
+ Computes WRMS norm for a single species:
60
+ sqrt( (1/N) * sum( ( |c_curr - c_prev| / (inner_tol * |c_curr| + atol) )^2 ) )
61
+ """
62
+ n = len(curr_val)
63
+ sum_sq = 0.0
64
+ for i in range(n):
65
+ c = curr_val[i]
66
+ diff = abs(c - prev_val[i])
67
+ scale = inner_tol * abs(c) + atol
68
+ ratio = diff / scale
69
+ if np.isnan(ratio) or np.isinf(ratio):
70
+ return np.inf
71
+ sum_sq += ratio * ratio
72
+ return np.sqrt(sum_sq / n)
73
+
74
+
75
+ @nb.njit(fastmath=True)
76
+ def compute_species_residual_linf(
77
+ curr_val: np.ndarray,
78
+ prev_val: np.ndarray,
79
+ inner_tol: float,
80
+ atol: float,
81
+ ) -> float:
82
+ """
83
+ Computes L-infinity norm for a single species:
84
+ max( |c_curr - c_prev| / (inner_tol * |c_curr| + atol) )
85
+ """
86
+ n = len(curr_val)
87
+ max_ratio = 0.0
88
+ for i in range(n):
89
+ c = curr_val[i]
90
+ diff = abs(c - prev_val[i])
91
+ scale = inner_tol * abs(c) + atol
92
+ ratio = diff / scale
93
+ if np.isnan(ratio) or np.isinf(ratio):
94
+ return np.inf
95
+ if ratio > max_ratio:
96
+ max_ratio = ratio
97
+ return max_ratio
98
+
99
+
100
+ @nb.njit(fastmath=True)
101
+ def solve_block_tridiagonal_thomas(
102
+ A: np.ndarray,
103
+ B: np.ndarray,
104
+ C: np.ndarray,
105
+ D: np.ndarray,
106
+ out: np.ndarray,
107
+ C_prime: np.ndarray,
108
+ D_prime: np.ndarray,
109
+ ) -> None:
110
+ """
111
+ O(N * S^3) In-place Block Tridiagonal Matrix Algorithm (Block TDMA) for 1D monolithic systems.
112
+
113
+ Solves the block-tridiagonal linear system:
114
+ A_i * U_{i-1} + B_i * U_i + C_i * U_{i+1} = D_i for i = 0 .. N-1
115
+
116
+ where:
117
+ - N is the number of 1D spatial cells
118
+ - S is the number of coupled chemical species
119
+ - A has shape (N, S): diagonal elements of subdiagonal spatial block A_i (coupling to cell i-1)
120
+ - B has shape (N, S, S): dense diagonal block for cell i (transport + chemical Jacobian)
121
+ - C has shape (N, S): diagonal elements of superdiagonal spatial block C_i (coupling to cell i+1)
122
+ - D has shape (N, S): RHS / residual vector at each cell
123
+ - out has shape (N, S): solution vector, updated in-place
124
+ - C_prime has shape (N, S, S): scratch array for forward block elimination
125
+ - D_prime has shape (N, S): scratch array for forward RHS elimination
126
+ """
127
+ N, S = D.shape
128
+ M = np.empty((S, S), dtype=np.float64)
129
+ piv = np.empty(S, dtype=np.int64)
130
+ y = np.empty(S, dtype=np.float64)
131
+ rhs = np.empty(S, dtype=np.float64)
132
+
133
+ # Forward elimination sweep
134
+ for i in range(N):
135
+ # 1. Form M_i = B_i - A_i * C'_{i-1}
136
+ for r in range(S):
137
+ for c in range(S):
138
+ M[r, c] = B[i, r, c]
139
+ if i > 0:
140
+ for r in range(S):
141
+ ar = A[i, r]
142
+ for c in range(S):
143
+ M[r, c] -= ar * C_prime[i - 1, r, c]
144
+
145
+ # 2. Form rhs = D_i - A_i * D'_{i-1}
146
+ for r in range(S):
147
+ rhs[r] = D[i, r]
148
+ if i > 0:
149
+ for r in range(S):
150
+ rhs[r] -= A[i, r] * D_prime[i - 1, r]
151
+
152
+ # 3. LU factorization of M with partial pivoting
153
+ for k in range(S):
154
+ piv[k] = k
155
+ for k in range(S):
156
+ max_r = k
157
+ max_v = abs(M[k, k])
158
+ for r in range(k + 1, S):
159
+ v = abs(M[r, k])
160
+ if v > max_v:
161
+ max_v = v
162
+ max_r = r
163
+ if max_r != k:
164
+ tmp_p = piv[k]
165
+ piv[k] = piv[max_r]
166
+ piv[max_r] = tmp_p
167
+ for col in range(S):
168
+ tmp = M[k, col]
169
+ M[k, col] = M[max_r, col]
170
+ M[max_r, col] = tmp
171
+ diag = M[k, k]
172
+ if abs(diag) > 1e-30:
173
+ for r in range(k + 1, S):
174
+ M[r, k] /= diag
175
+ f = M[r, k]
176
+ for col in range(k + 1, S):
177
+ M[r, col] -= f * M[k, col]
178
+
179
+ # 4. Solve M * D'_i = rhs
180
+ # Forward substitution Ly = P * rhs
181
+ for r in range(S):
182
+ val = rhs[piv[r]]
183
+ for col in range(r):
184
+ val -= M[r, col] * y[col]
185
+ y[r] = val
186
+ # Back substitution Ux = y
187
+ for r in range(S - 1, -1, -1):
188
+ val = y[r]
189
+ for col in range(r + 1, S):
190
+ val -= M[r, col] * D_prime[i, col]
191
+ D_prime[i, r] = val / (M[r, r] if abs(M[r, r]) > 1e-30 else 1e-30)
192
+
193
+ # 5. If i < N - 1, solve M * C'_i[:, j] = C[i, j] * e_j for each column j
194
+ if i < N - 1:
195
+ for j in range(S):
196
+ cj = C[i, j]
197
+ # Forward Ly = P * (cj * e_j)
198
+ for r in range(S):
199
+ val = cj if piv[r] == j else 0.0
200
+ for col in range(r):
201
+ val -= M[r, col] * y[col]
202
+ y[r] = val
203
+ # Back Ux = y
204
+ for r in range(S - 1, -1, -1):
205
+ val = y[r]
206
+ for col in range(r + 1, S):
207
+ val -= M[r, col] * C_prime[i, col, j]
208
+ C_prime[i, r, j] = val / (M[r, r] if abs(M[r, r]) > 1e-30 else 1e-30)
209
+
210
+ # Back substitution sweep
211
+ for r in range(S):
212
+ out[N - 1, r] = D_prime[N - 1, r]
213
+ for i in range(N - 2, -1, -1):
214
+ for r in range(S):
215
+ val = D_prime[i, r]
216
+ for col in range(S):
217
+ val -= C_prime[i, r, col] * out[i + 1, col]
218
+ out[i, r] = val
219
+
@@ -0,0 +1,175 @@
1
+ Metadata-Version: 2.4
2
+ Name: mantis-rtm
3
+ Version: 0.1.1.dev0
4
+ Summary: MANTIS: A Monolithic Assembly of Nonlinear Transport Interactions Solver
5
+ Author-email: Uli Wortmann <uli.wortmann@utoronto.ca>
6
+ License-Expression: AGPL-3.0-or-later
7
+ Project-URL: Homepage, https://github.com/uliw/mantis
8
+ Project-URL: Repository, https://github.com/uliw/mantis
9
+ Project-URL: Issues, https://github.com/uliw/mantis/issues
10
+ Keywords: reactive transport modeling,reaction transport modeling,geochemistry,monolithic solver,diagenesis,block newton,advection diffusion reaction
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Topic :: Scientific/Engineering
14
+ Classifier: Topic :: Scientific/Engineering :: Chemistry
15
+ Classifier: Topic :: Scientific/Engineering :: Physics
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Operating System :: OS Independent
21
+ Requires-Python: >=3.12
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: numpy
25
+ Requires-Dist: pandas
26
+ Requires-Dist: matplotlib
27
+ Requires-Dist: numba
28
+ Requires-Dist: pint
29
+ Requires-Dist: watchdog
30
+ Provides-Extra: dev
31
+ Requires-Dist: ruff; extra == "dev"
32
+ Requires-Dist: twine; extra == "dev"
33
+ Requires-Dist: build; extra == "dev"
34
+ Provides-Extra: test
35
+ Requires-Dist: pytest; extra == "test"
36
+ Requires-Dist: pytest-cov; extra == "test"
37
+ Requires-Dist: scipy; extra == "test"
38
+ Provides-Extra: doc
39
+ Requires-Dist: sphinx; extra == "doc"
40
+ Dynamic: license-file
41
+
42
+ # MANTIS: A *M*onolithic *A*ssembly of *N*onlinear *T*ransport *I*nteractions *S*olver
43
+
44
+ **MANTIS** is a high-performance 1D reactive-transport modeling (RTM) framework written in Python, designed to simulate stiff, tightly coupled advection-diffusion-reaction networks.
45
+
46
+ ---
47
+
48
+ ## What is MANTIS?
49
+
50
+ Reactive-transport systems in natural environments—such as marine sediments, groundwater aquifers, and geochemical reactors—involve simultaneous transport (advection, molecular diffusion, bio-irrigation, and bioturbation) coupled with complex networks of nonlinear chemical, mineral, and biological reactions.
51
+
52
+ MANTIS provides:
53
+ - **Declarative Geochemical Model Definition**: Define chemical species, phases, reaction stoichiometries, and kinetic limiters (Monod, inhibition, threshold laws) in intuitive, human-readable Python configuration files.
54
+ - **Automated Symbolic Jacobian Generation**: Automatically parses reaction networks and compiles analytical chemical Jacobians using symbolic computer algebra (`SymPy`), eliminating error-prone manual differentiation.
55
+ - **Monolithic Block Newton-Raphson Solver**: Solves all chemical species and transport equations simultaneously in a coupled block-tridiagonal system using a Numba-accelerated Block Thomas algorithm.
56
+ - **Adaptive Time Stepping**: Features PID-controlled adaptive time stepping with automatic step rejection and convergence recovery.
57
+ - **Unified Declarative Interface**: Configure and run simulations through `DiageneticModel`, complete with automatic CLI argument handling, checkpointing, publication-ready summary plots, and asynchronous time-lapse video generation.
58
+
59
+ ---
60
+
61
+ ## How is MANTIS Different from Other Python RTM Packages?
62
+
63
+ Most existing Python and geochemical reactive-transport modeling packages rely on **operator splitting** (such as Sequential Non-Iterative Approach [SNIA] or Sequential Iterative Approach [SIA]) or **decoupled Picard (Gauss-Seidel) iterations**:
64
+
65
+ 1. **Monolithic Coupling vs. Operator Splitting**:
66
+ - *Operator splitting* alternates between solving pure transport and pure batch chemistry over a time step. While simple, it introduces splitting errors and often requires tiny time steps ($\Delta t$ on the order of seconds or hours) to avoid artificial oscillations or numerical divergence when reactions are fast and stiff.
67
+ - *Picard iterations* solve each chemical species sequentially, lagging cross-species couplings from the previous iteration. At sharp reaction fronts (e.g., where sulfide meets oxygen or ferric iron), this decoupling frequently causes severe numerical lag oscillations.
68
+ - **MANTIS solves the entire system monolithically**: Transport and reactions for all species are assembled into a single unified block-tridiagonal linear system at each Newton step. This achieves true simultaneous coupling and quadratic Newton convergence.
69
+
70
+ 2. **Exact Symbolic Jacobians vs. Finite Differences**:
71
+ - Rather than approximating derivatives via costly numerical perturbations (finite differences) or requiring users to manually code and maintain hundreds of lines of Jacobian terms, MANTIS computes exact analytical Jacobians symbolically via `SymPy` ahead of time and compiles them to optimized machine code.
72
+
73
+ 3. **High-Performance Native Numba Engine**:
74
+ - The core block-tridiagonal solver and residual evaluations run directly in compiled machine code via Numba. This delivers C-like performance without external heavy PDE framework dependencies.
75
+
76
+ 4. **Unconditional Numerical Stability**:
77
+ - Thanks to its fully implicit monolithic formulation, MANTIS does not suffer from Courant-Friedrichs-Lewy (CFL) or diffusion time step stability restrictions, enabling long-term simulations (centuries to millennia) with large time steps.
78
+
79
+ ---
80
+
81
+ ## Scope and General Applicability
82
+
83
+ While the bundled examples and default templates are geared toward **marine sediment early diagenesis** (e.g., organic matter mineralization cascades, dissimilatory iron and sulfate reduction, iron sulfide and pyrite precipitation, and stable sulfur isotope fractionation $\delta^{34}\text{S}$), **the underlying numerical formulation is domain-agnostic**.
84
+
85
+ The monolithic advection-diffusion-reaction solver can be directly applied to any 1D reactive transport problem involving:
86
+ - Stiff, multi-species nonlinear reaction networks
87
+ - Combined particulate (solid) and solute (porewater/fluid) phases
88
+ - Advection, molecular and eddy diffusion, and nonlocal exchange processes
89
+ - Stable isotope fractionation and multi-isotope tracking
90
+
91
+ ---
92
+
93
+ ## Current Limitations
94
+
95
+ While MANTIS provides robust and unconditionally stable solutions for stiff reactive transport, the current release has the following limitations:
96
+
97
+ 1. **Time-Invariant Spatial Mesh**:
98
+ - The spatial grid $z$ is static throughout the simulation. Dynamic grid adaptation, moving boundaries, or expanding domains (e.g., physical sediment column growth over time) are not currently supported.
99
+ 2. **Time-Invariant Porosity**:
100
+ - The porosity profile $\phi(z)$ is stationary in time (steady-state compaction). Transient sediment compaction or compaction-driven dynamic fluid expulsion over time is not currently modeled.
101
+ 3. **Time-Invariant Temperature**:
102
+ - The temperature profile $T(z)$ is steady-state. Dynamic thermal evolution and time-dependent diffusion coefficients or rate constants driven by non-isothermal thermal transients are not currently supported.
103
+
104
+ ---
105
+
106
+ ## Installation
107
+
108
+ MANTIS requires Python 3.12 or newer. Clone the repository and install in editable mode:
109
+
110
+ ```bash
111
+ git clone https://github.com/uliw/mantis.git
112
+ cd mantis
113
+ pip install -e .
114
+ ```
115
+
116
+ Dependencies include `numpy`, `pandas`, `matplotlib`, `numba`, `sympy`, `chempy`, and `watchdog`.
117
+
118
+ ---
119
+
120
+ ## Quick Start Example
121
+
122
+ A typical simulation directory contains declarative configuration files defining species, reactions, rate constants, and plot layouts:
123
+
124
+ ```python
125
+ from mantis import DiageneticModel
126
+
127
+ # 1. Initialize the model (auto-detects species.py, reactions, constants in the directory)
128
+ model = DiageneticModel(
129
+ reactions="fes_reactions.py",
130
+ constants="kinetic_constants_slow.py",
131
+ layout="plot_layout.py",
132
+ experiment="marine_diagenesis",
133
+ )
134
+
135
+ # 2. Configure environmental and grid parameters
136
+ model.configure(
137
+ max_depth="0.5 m",
138
+ t_end="2 kyr",
139
+ dt_init="1 day",
140
+ dt_max="1 year",
141
+ w="0.2 cm/yr",
142
+ phi=0.8,
143
+ bioturbation={
144
+ "BT0": "4 cm^2/year",
145
+ "depth": "6 cm",
146
+ "attenuation": "2 cm",
147
+ },
148
+ )
149
+
150
+ # 3. Enable CLI flag overrides (--video, --solver-tol, --t-end, etc.)
151
+ model.parse_cli_args()
152
+
153
+ # 4. Run the simulation
154
+ result = model.run()
155
+
156
+ # 5. Export results and summary plots
157
+ df, csv_path = result.save_csv()
158
+ result.plot_summary()
159
+ ```
160
+
161
+ ---
162
+
163
+ ## Running Tests
164
+
165
+ Run the unit test suite using `pytest`:
166
+
167
+ ```bash
168
+ pytest
169
+ ```
170
+
171
+ ---
172
+
173
+ ## License
174
+
175
+ This project is licensed under the GPL-3.0 License. See the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,17 @@
1
+ mantis/__init__.py,sha256=Pkpwm4bu1o2hEdFHQL65jl8DSV71rTm2BrrfUYzmIbs,201
2
+ mantis/_version.py,sha256=C7_2xyONPOLOpAzR9m7Juwyz8wzzwt9M7TtjzKDO_so,533
3
+ mantis/diff_lib.py,sha256=qTTH11q01uSVYKIK9a8F8KQ8juX1IOFhC2Co1Y9QuD8,66362
4
+ mantis/generate_equation_system.py,sha256=Ke_lgkCdz-4Jvjdj7l6rK9H-S7KbhJSYo2u8Ft2LXDU,109938
5
+ mantis/live_plot_lib.py,sha256=kSTzb6wHNUzLt5p16mjJW-Ro3JphQ08hmtzyfHQuRS4,21827
6
+ mantis/model.py,sha256=Um0Z4mBfMDERl3oRNHHhokwofD24dYqtnNiQucC3O3w,40771
7
+ mantis/monitor_plots.py,sha256=Thm0PVeZJSQI4FdYWnixKpAaoEL4i0aX1uS6xiydb8Y,5408
8
+ mantis/monolithic_solver.py,sha256=Rbk7rFYIuX-DCY_WcTxPh4L6WF9CVHGzkHka1u4r-xM,37019
9
+ mantis/parallel_video.py,sha256=82hDPUJmzyF1QjmXkIExjGxepeTc8lKA3tSBJh9BuAE,16964
10
+ mantis/plot_data_new.py,sha256=dzM9ZKv_kM16Qp6Y43NfRF2Kmqg7cc-v_6CNsctJiiA,45232
11
+ mantis/solvers_numba.py,sha256=cDUX-tD_SX6OiJ8e4pNQrN6lS9dBw2nnS7qC-np6KQw,7057
12
+ mantis_rtm-0.1.1.dev0.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
13
+ mantis_rtm-0.1.1.dev0.dist-info/METADATA,sha256=XhBZoVB3XbeVrwMDErCApFsyu0HhiQVJ82HpZOAPDPs,8554
14
+ mantis_rtm-0.1.1.dev0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
15
+ mantis_rtm-0.1.1.dev0.dist-info/entry_points.txt,sha256=XKk3i1AqyWf20_zgSsECVGLewZVW7c_qEf6L37IyvkY,304
16
+ mantis_rtm-0.1.1.dev0.dist-info/top_level.txt,sha256=pHGQmbUmUmGR68I95aUwKhubm_r957NOoGxwoGHlRbI,7
17
+ mantis_rtm-0.1.1.dev0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,6 @@
1
+ [console_scripts]
2
+ generate-equation-system = mantis.generate_equation_system:main
3
+ generate-equations = mantis.generate_equation_system:main
4
+ generate_equation_system = mantis.generate_equation_system:main
5
+ generate_equations = mantis.generate_equation_system:main
6
+ plot_data_new = mantis.plot_data_new:main