mantis-rtm 0.1.1.dev0__tar.gz → 0.1.2__tar.gz
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.
- mantis_rtm-0.1.2/.github/workflows/publish-pypi.yml +47 -0
- mantis_rtm-0.1.2/.github/workflows/tests.yml +35 -0
- {mantis_rtm-0.1.1.dev0/src/mantis_rtm.egg-info → mantis_rtm-0.1.2}/PKG-INFO +1 -1
- mantis_rtm-0.1.2/changelog.md +67 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/docs/tutorial.md +130 -28
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/docs/tutorial.org +139 -3
- mantis_rtm-0.1.2/examples/velde_et_al_2016/kinetic_constants_fast.py +108 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/examples/velde_et_al_2016/kinetic_constants_slow.py +1 -1
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/examples/velde_et_al_2016/plot_layout.py +1 -1
- mantis_rtm-0.1.2/examples/velde_et_al_2016/run_velde_fast.py +75 -0
- mantis_rtm-0.1.1.dev0/examples/velde_et_al_2016/run_velde_model.py → mantis_rtm-0.1.2/examples/velde_et_al_2016/run_velde_slow.py +2 -1
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/src/mantis/_version.py +3 -3
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/src/mantis/diff_lib.py +71 -25
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/src/mantis/generate_equation_system.py +165 -143
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/src/mantis/model.py +124 -19
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/src/mantis/monolithic_solver.py +16 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/src/mantis/plot_data_new.py +11 -11
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2/src/mantis_rtm.egg-info}/PKG-INFO +1 -1
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/src/mantis_rtm.egg-info/SOURCES.txt +6 -1
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/src/mantis_rtm.egg-info/scm_file_list.json +6 -1
- mantis_rtm-0.1.2/src/mantis_rtm.egg-info/scm_version.json +8 -0
- mantis_rtm-0.1.2/tests/test_diagenetic_model.py +179 -0
- mantis_rtm-0.1.1.dev0/src/mantis_rtm.egg-info/scm_version.json +0 -8
- mantis_rtm-0.1.1.dev0/tests/test_diagenetic_model.py +0 -65
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/.gitignore +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/.python-version +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/LICENSE +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/README.md +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/examples/velde_et_al_2016/00readme.md +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/examples/velde_et_al_2016/fes_reactions.py +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/examples/velde_et_al_2016/species.py +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/pyproject.toml +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/pyrightconfig.json +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/requirements.txt +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/setup.cfg +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/src/.vscode/settings.json +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/src/mantis/__init__.py +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/src/mantis/live_plot_lib.py +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/src/mantis/monitor_plots.py +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/src/mantis/parallel_video.py +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/src/mantis/solvers_numba.py +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/src/mantis_rtm.egg-info/dependency_links.txt +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/src/mantis_rtm.egg-info/entry_points.txt +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/src/mantis_rtm.egg-info/requires.txt +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/src/mantis_rtm.egg-info/top_level.txt +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/tests/test_direct_solver_numpy.py +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/tests/test_equivalence.py +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/tests/test_fes_flux_limited.py +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/tests/test_fes_predictor_corrector.py +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/tests/test_fes_smooth_transition.py +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/tests/test_fes_symmetrical_picard.py +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/tests/test_generate_equation_system.py +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/tests/test_log_compression.py +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/tests/test_monod_linearization.py +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/tests/test_parallel_video.py +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/tests/test_plot_data_options.py +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/tests/test_reactant_coexistence.py +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/tests/test_s0_disproportionation.py +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/tests/test_solvers_numba.py +0 -0
- {mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/tests/test_video_queue_and_logging.py +0 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: Build and Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build-and-publish:
|
|
10
|
+
name: Build distribution and publish to PyPI
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
environment:
|
|
13
|
+
name: pypi
|
|
14
|
+
url: https://pypi.org/p/mantis-rtm
|
|
15
|
+
permissions:
|
|
16
|
+
id-token: write # Required for PyPI Trusted Publishing (OIDC)
|
|
17
|
+
contents: read # Required to checkout repository
|
|
18
|
+
|
|
19
|
+
steps:
|
|
20
|
+
- name: Check out repository
|
|
21
|
+
uses: actions/checkout@v4
|
|
22
|
+
with:
|
|
23
|
+
fetch-depth: 0 # Required for setuptools_scm to extract version from tags
|
|
24
|
+
|
|
25
|
+
- name: Set up Python
|
|
26
|
+
uses: actions/setup-python@v5
|
|
27
|
+
with:
|
|
28
|
+
python-version: "3.12"
|
|
29
|
+
|
|
30
|
+
- name: Install build tools
|
|
31
|
+
run: |
|
|
32
|
+
python -m pip install --upgrade pip
|
|
33
|
+
python -m pip install build twine
|
|
34
|
+
|
|
35
|
+
- name: Build source distribution and wheel
|
|
36
|
+
run: python -m build
|
|
37
|
+
|
|
38
|
+
- name: Verify distribution archives
|
|
39
|
+
run: twine check dist/*
|
|
40
|
+
|
|
41
|
+
- name: Publish to PyPI
|
|
42
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
43
|
+
# PyPI Trusted Publishing (OIDC) is used automatically when permissions.id-token is set.
|
|
44
|
+
# If you prefer using a classic PyPI API token instead of Trusted Publishing,
|
|
45
|
+
# add a repository secret named PYPI_API_TOKEN and uncomment the line below:
|
|
46
|
+
# with:
|
|
47
|
+
# password: ${{ secrets.PYPI_API_TOKEN }}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
name: Run test suite (${{ matrix.python-version }})
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
fail-fast: false
|
|
15
|
+
matrix:
|
|
16
|
+
python-version: ["3.12", "3.13"]
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- name: Check out repository
|
|
20
|
+
uses: actions/checkout@v4
|
|
21
|
+
with:
|
|
22
|
+
fetch-depth: 0
|
|
23
|
+
|
|
24
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
25
|
+
uses: actions/setup-python@v5
|
|
26
|
+
with:
|
|
27
|
+
python-version: ${{ matrix.python-version }}
|
|
28
|
+
|
|
29
|
+
- name: Install dependencies
|
|
30
|
+
run: |
|
|
31
|
+
python -m pip install --upgrade pip
|
|
32
|
+
python -m pip install -e ".[test]"
|
|
33
|
+
|
|
34
|
+
- name: Run pytest
|
|
35
|
+
run: pytest
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.2] - 2026-10-12
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **File Path Resolution & Diagnostic Error Handling**:
|
|
13
|
+
- Enhanced file path resolution in `DiageneticModel` (`_resolve`), searching the current working directory, the model script directory (`base_dir`), and fallback naming patterns (such as `<experiment>_reactions.py`).
|
|
14
|
+
- Implemented early file validation (`validate_simulation_files()`) during `DiageneticModel` initialization and setup to catch missing simulation files before simulation execution.
|
|
15
|
+
- Added informative error diagnostics (`[MANTIS ERROR]`) detailing exact search locations and resolution guidance when species, reactions, constants, limiters, or plot layout files are missing.
|
|
16
|
+
- Improved plot layout file resolution in `plot_data_new.load_layout_from_file` and `plot_summary` with clear error reporting and search path feedback.
|
|
17
|
+
- Added unit test suite in `tests/test_diagenetic_model.py` testing missing file scenarios, exit behavior, and directory resolution from both CWD and model base directories.
|
|
18
|
+
|
|
19
|
+
## [0.1.1] - 2026-10-12
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- **Steady-State Detection & Convergence**:
|
|
24
|
+
- Implemented a grid-independent spatial $L_2$ steady-state convergence check in `run_non_steady_state_solver_monolithic`. Computes a volume-weighted root-mean-square (RMS) change across grid cells per species and checks against `steady_state_tolerance` (or falls back to `dt_tolerance` if set).
|
|
25
|
+
- Added a minimum step requirement (step > 5) to prevent premature steady-state termination.
|
|
26
|
+
- **Model Parameters & Unit Conversions**:
|
|
27
|
+
- Added support for grid spacing parameters with unit conversion in `DiageneticModel`: `initial_spacing`, `reaction_zone_spacing`, and `max_spacing`.
|
|
28
|
+
- Added support for the `reaction_zone` tuple/list interval parameter with unit conversion.
|
|
29
|
+
- Added support for `steady_state_tolerance` and `dt_tolerance` solver tolerance parameters.
|
|
30
|
+
- Added `check_reactant_coexistence` boolean option and enabled the post-simulation reactant coexistence diagnostic by default.
|
|
31
|
+
- **Examples**:
|
|
32
|
+
- Added fast and slow simulation setups for the Velde et al. (2016) sediment diagenesis benchmark:
|
|
33
|
+
- `examples/velde_et_al_2016/run_velde_fast.py` & `kinetic_constants_fast.py`: fast configuration with pyrite precipitation.
|
|
34
|
+
- `examples/velde_et_al_2016/run_velde_slow.py` & `kinetic_constants_slow.py`: slow configuration tracking sulfur isotopes.
|
|
35
|
+
- **CI/CD & Automation**:
|
|
36
|
+
- Added GitHub Actions workflow `.github/workflows/tests.yml` to run automated test suites across Python 3.12, 3.13, and 3.14.
|
|
37
|
+
- Added GitHub Actions workflow `.github/workflows/publish-pypi.yml` for automated PyPI package publishing upon GitHub releases.
|
|
38
|
+
- **Documentation**:
|
|
39
|
+
- Added comprehensive documentation on physical units (`pint` integration) and non-uniform grid generation to `docs/tutorial.md` and `docs/tutorial.org`.
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- **Rate Laws & Equation System Generation**:
|
|
44
|
+
- Simplified particulate organic carbon (POC) respiration rate equations by removing redundant coefficient calculations.
|
|
45
|
+
- Simplified FeS precipitation and dissolution rate formulation by removing unnecessary Michaelis-Menten terms.
|
|
46
|
+
- Adjusted default smoothing parameter `fes_smooth_epsilon` from `0.05` to `0.01` for sharper, smoother reaction transitions.
|
|
47
|
+
- Dynamically extract and handle active POC species and kinetic parameter pairs instead of using hardcoded POC fast/slow loops.
|
|
48
|
+
- **Differential Operators Library (`diff_lib`)**:
|
|
49
|
+
- Added type annotations across numerical and differential operator functions.
|
|
50
|
+
- Improved formatting, line wrapping, indentation, and visual structure.
|
|
51
|
+
- **Project Packaging & Metadata**:
|
|
52
|
+
- Updated package distribution name to `mantis-rtm` and enabled dynamic versioning using `setuptools_scm`.
|
|
53
|
+
- Added `pint` as an explicit core dependency in `pyproject.toml` and `requirements.txt`.
|
|
54
|
+
- Added AGPL-3.0-or-later license configuration and classifiers.
|
|
55
|
+
- Configured project URLs (Homepage, Repository, Issues) and updated build ignore patterns in `.gitignore`.
|
|
56
|
+
- **Examples**:
|
|
57
|
+
- Corrected FeS dissolution rate constant in `examples/velde_et_al_2016/kinetic_constants_slow.py`.
|
|
58
|
+
- Adjusted broken axis limits in `examples/velde_et_al_2016/plot_layout.py` for clearer concentration profile visualization.
|
|
59
|
+
|
|
60
|
+
### Removed
|
|
61
|
+
|
|
62
|
+
- Removed obsolete `examples/velde_et_al_2016/run_velde_model.py` script.
|
|
63
|
+
- Removed legacy table of contents from tutorial documentation.
|
|
64
|
+
|
|
65
|
+
## [0.1.0] - 2026-10-11
|
|
66
|
+
|
|
67
|
+
- Initial tagged release with core MANTIS reactive transport solver and tutorial documentation.
|
|
@@ -1,31 +1,3 @@
|
|
|
1
|
-
- [Introduction: What is MANTIS?](#org5dbc758)
|
|
2
|
-
- [Overall Model Architecture: What is Where?](#org4417d27)
|
|
3
|
-
- [Step 1: Defining Chemical Species (`species.py`)](#orgc16f49a)
|
|
4
|
-
- [Understanding Phases: Dissolved vs. Solid vs. Mixed](#orgba057ca)
|
|
5
|
-
- [Example: Standard Species Configuration](#org10d01d0)
|
|
6
|
-
- [Handling Diffusion Coefficients](#orgd0812cc)
|
|
7
|
-
- [Custom Diffusion Functions (e.g., Dissolved Oxygen)](#org69559bb)
|
|
8
|
-
- [Step 2: Defining Kinetic Constants (`kinetic_constants_*.py`)](#org809df99)
|
|
9
|
-
- [Structure of `kinetic_constants_slow.py`](#org3c64ffa)
|
|
10
|
-
- [Step 3: Defining Reactions & Limiters (`fes_reactions.py`)](#org1fecbeb)
|
|
11
|
-
- [Understanding Limiters & Inhibitors](#org739ad2b)
|
|
12
|
-
- [Declaring Chemical Reactions](#org5336c1e)
|
|
13
|
-
- [Step 4: The Master Run Script (`run_velde_model.py`)](#org21fe2f4)
|
|
14
|
-
- [Step 5: Designing Plot Layouts (`plot_layout.py`)](#orgf185086)
|
|
15
|
-
- [Converting Model Units to Geochemical Units](#orgaefde21)
|
|
16
|
-
- [Defining Multi-Panel Profiles](#orgf5ff558)
|
|
17
|
-
- [Step 6: Running the Model & Producing Time-Lapse Videos](#orge7acd7b)
|
|
18
|
-
- [Standard Execution](#orgee831e4)
|
|
19
|
-
- [Generating a Time-Lapse Video](#orgcf4fea3)
|
|
20
|
-
- [Command-Line Options Reference](#orgf872aa8)
|
|
21
|
-
- [Solver Mechanics & Physical Realism (Geochemist's Alert!)](#orgfeddb58)
|
|
22
|
-
- [How to Ensure Physical Realism:](#org9a13232)
|
|
23
|
-
- [Summary & Next Steps](#org0cb5c1f)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
<a id="org5dbc758"></a>
|
|
28
|
-
|
|
29
1
|
# Introduction: What is MANTIS?
|
|
30
2
|
|
|
31
3
|
Welcome to **MANTIS** (\*M\*onolithic \*A\*ssembly of \*N\*onlinear \*T\*ransport \*I\*nteractions \*S\*olver)! If you are a geochemist, sedimentologist, or marine geologist, you are likely familiar with the classic picture of early diagenesis in marine sediments:
|
|
@@ -194,6 +166,85 @@ species = {
|
|
|
194
166
|
}
|
|
195
167
|
```
|
|
196
168
|
|
|
169
|
+
# Concentration Units & Conversions: Inputs, Internal Engine, and Outputs
|
|
170
|
+
|
|
171
|
+
A common source of confusion in diagenetic modeling is the distinction between porewater solute concentrations, solid sediment mineral concentrations, and geochemical laboratory units (such as $\text{wt}\%$ or $\mu\text{mol/g}$). MANTIS handles physical units transparently while maintaining strict mass conservation across phases.
|
|
172
|
+
|
|
173
|
+
## 1. Default Input Units
|
|
174
|
+
|
|
175
|
+
When defining your model setup and boundary conditions:
|
|
176
|
+
|
|
177
|
+
- **Driver Script Parameters (`run_velde_model.py`)**:
|
|
178
|
+
MANTIS supports human-readable strings parsed via Python's `pint` physical unit library:
|
|
179
|
+
- Lengths and depths: `"0.5 m"`, `"50 cm"`
|
|
180
|
+
- Times and time steps: `"0.5 kyr"`, `"1 year"`, `"24 hours"`
|
|
181
|
+
- Sedimentation rates: `"0.2 cm/yr"`, `"1 mm/a"`
|
|
182
|
+
- Bioturbation coefficients: `"4 cm^2/year"`, `"1e-9 m^2/s"`
|
|
183
|
+
|
|
184
|
+
- **Boundary Conditions (`species.py`)**:
|
|
185
|
+
- **Dissolved Solutes (`type: "dissolved"`)**:
|
|
186
|
+
Upper boundary concentrations can be specified as a numeric float or a unit string. The default numeric input is **$\text{mmol/L}$** (which is identical to $\text{mol/m}^3$ or $\text{mM}$).
|
|
187
|
+
```text
|
|
188
|
+
"bc": {"top": 28.2, "type": "dissolved"} # 28.2 mmol/L (seawater sulfate)
|
|
189
|
+
"bc": {"top": "280 umol/L", "type": "dissolved"} # parsed automatically to 0.28 mmol/L
|
|
190
|
+
```
|
|
191
|
+
- **Particulate Solids (`type: "particulate"`)**:
|
|
192
|
+
Deposition at the sediment-water interface is specified as a depositional surface flux:
|
|
193
|
+
```text
|
|
194
|
+
"bc": {"top": "12 umol/(cm^2 * yr)", "type": "particulate"}
|
|
195
|
+
```
|
|
196
|
+
MANTIS automatically converts this to internal flux units ($\text{mol}/(\text{m}^2 \cdot \text{s})$) during initialization.
|
|
197
|
+
|
|
198
|
+
## 2. How Units Are Handled Internally by the Solver
|
|
199
|
+
|
|
200
|
+
Under the hood, MANTIS standardizes all transport and reaction PDEs into SI-derived volumetric units:
|
|
201
|
+
$$\text{Internal Concentration Unit} = \mathbf{mol/m^3} \quad (\equiv 1\ \text{mmol/L} \equiv 1\ \text{mM} \equiv 1000\ \mu\text{mol/L} \equiv 1\ \mu\text{mol/cm}^3)$$
|
|
202
|
+
|
|
203
|
+
Crucially, concentrations are defined **per unit volume of their respective phase**:
|
|
204
|
+
1. **Liquid Solutes**: Expressed per volume of porewater ($\text{mol / m}^3_{\text{liquid}} = \text{mmol / L}_{\text{porewater}}$). The bulk sediment inventory is $\phi \cdot C_{\text{liquid}}$.
|
|
205
|
+
2. **Solid Minerals**: Expressed per volume of pure solid sediment ($\text{mol / m}^3_{\text{solid}} = \text{mmol / L}_{\text{solid}}$). The bulk sediment inventory is $(1 - \phi) \cdot C_{\text{solid}}$.
|
|
206
|
+
3. **Phase Transfer & Reactions**: Whenever a reaction couples a solid phase and a liquid phase (for example, $\text{Fe}^{2+} + \text{HS}^- \to \text{FeS}$), MANTIS scales the source and sink terms with the appropriate phase ratio ($(1-\phi)/\phi$ or $\phi/(1-\phi)$) so that mass is strictly conserved.
|
|
207
|
+
4. **Reaction Rates**: Solved internally in $\text{mol} / (\text{m}^3_{\text{phase}} \cdot \text{s})$.
|
|
208
|
+
5. **Depths and Transport**: Depth $z$ is in meters ($\text{m}$), time $t$ in seconds ($\text{s}$), and diffusion coefficients in $\text{m}^2/\text{s}$.
|
|
209
|
+
|
|
210
|
+
## 3. Output Units in Result Files (`.csv`)
|
|
211
|
+
|
|
212
|
+
When the simulation completes or exports data (`<model_name>.csv`):
|
|
213
|
+
- `c_<species>`: Reported directly in **$\text{mmol/L}$** (numerically equivalent to $\text{mol/m}^3$):
|
|
214
|
+
- Solutes (e.g., `c_SO4`, `c_O2`, `c_TS2`) are in **$\text{mmol / L of porewater}$**.
|
|
215
|
+
- Solids (e.g., `c_FeS`, `c_Fe3`, `c_POC_slow`) are in **$\text{mmol / L of pure solid}$**.
|
|
216
|
+
- `f_<species>`: Net production/consumption reaction rates in **$\text{mol} / (\text{m}^3 \cdot \text{s})$**.
|
|
217
|
+
- `d_<species>`: Isotope delta values (e.g., `d_SO4`, `d_FeS`) in **$\permil$** (per mil relative to VCDT).
|
|
218
|
+
- `z`: Depth below sediment-water interface in **meters** ($\text{m}$).
|
|
219
|
+
- `phi` and `w`: Porosity (fraction, dimensionless) and burial velocity ($\text{m/s}$).
|
|
220
|
+
|
|
221
|
+
## 4. Converting Solid Concentrations to Standard Geochemical Units
|
|
222
|
+
|
|
223
|
+
Geochemists typically report solid mineral abundances as dry-sediment mass fractions ($\text{wt}\%$) or mass-normalized molar concentrations ($\mu\text{mol/g}$). MANTIS includes built-in conversion utilities in `mantis.diff_lib`:
|
|
224
|
+
|
|
225
|
+
### A. Converting to $\mu\text{mol / g}$ (micromoles per gram of dry sediment)
|
|
226
|
+
To convert a solid concentration $C_{\text{solid}}$ ($\text{mmol/L of solid} = \mu\text{mol/cm}^3_{\text{solid}}$) to $\mu\text{mol/g}$ of dry sediment, divide by the mineral grain density $\rho_{\text{sed}}$ (typically $2.6\ \text{g/cm}^3$ for siliciclastic sediments):
|
|
227
|
+
$$C_{\mu\text{mol/g}} = \frac{C_{\text{solid}}}{\rho_{\text{sed}}}$$
|
|
228
|
+
|
|
229
|
+
In Python:
|
|
230
|
+
```python
|
|
231
|
+
from mantis.diff_lib import solid_conc_to_umol_per_g
|
|
232
|
+
|
|
233
|
+
# df.c_FeS is in mmol/L of solid; density in g/cm^3
|
|
234
|
+
c_fes_umol_g = solid_conc_to_umol_per_g(df.c_FeS, d=2.6)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### B. Converting to Dry Weight Percent ($\text{wt}\%$)
|
|
238
|
+
To convert $C_{\text{solid}}$ to weight percent of dry sediment matrix given molecular weight $M_w$ ($\text{g/mol}$):
|
|
239
|
+
$$\text{wt}\% = \frac{C_{\text{solid}} \cdot M_w}{\rho_{\text{sed}} \cdot 10^4}$$
|
|
240
|
+
|
|
241
|
+
In Python:
|
|
242
|
+
```python
|
|
243
|
+
from mantis.diff_lib import solid_conc_to_wt_percent
|
|
244
|
+
|
|
245
|
+
# Example: FeS (Mw = 87.91 g/mol, density = 2.6 g/cm^3)
|
|
246
|
+
fes_wt_pct = solid_conc_to_wt_percent(df.c_FeS, mw=87.91, d=2.6, phi=df.phi)
|
|
247
|
+
```
|
|
197
248
|
|
|
198
249
|
<a id="org809df99"></a>
|
|
199
250
|
|
|
@@ -403,6 +454,12 @@ if __name__ == "__main__":
|
|
|
403
454
|
"BI0": 0.0, # Set to 0.0 to disable; or e.g. "20 /year"
|
|
404
455
|
"depth": "0 cm",
|
|
405
456
|
},
|
|
457
|
+
|
|
458
|
+
# Spatial grid & reaction zone refinement (optional, defaults provided)
|
|
459
|
+
initial_spacing="0.1 mm", # Spacing at sediment-water interface (z=0)
|
|
460
|
+
reaction_zone=("0.1 cm", "1.0 cm"), # Critical depth window of sharp redox fronts
|
|
461
|
+
reaction_zone_spacing="0.05 mm", # Dense, uniform spacing in the reaction zone
|
|
462
|
+
max_spacing="1.0 cm", # Coarser spacing in deep sediment
|
|
406
463
|
)
|
|
407
464
|
|
|
408
465
|
# 3. Allow CLI overrides (e.g. running --video from the terminal)
|
|
@@ -423,6 +480,51 @@ if __name__ == "__main__":
|
|
|
423
480
|
```
|
|
424
481
|
|
|
425
482
|
|
|
483
|
+
## Spatial Grid Configuration & Reaction Zone Resolution
|
|
484
|
+
|
|
485
|
+
Diagenetic models must resolve spatial scales ranging from sub-millimeter oxygen penetration depths to deep sediment columns spanning decimeters to hundreds of meters. A uniform fine grid across a 50-meter column would require millions of cells and slow down calculations unnecessarily.
|
|
486
|
+
|
|
487
|
+
MANTIS solves this using an adaptive geometric-mesh builder (`make_grid2`) that refines where steep gradients occur and expands into the deep sediment.
|
|
488
|
+
|
|
489
|
+
### 1. How Grid Generation Works (`make_grid2`)
|
|
490
|
+
The grid structure is divided into four seamless stages from the sediment surface to depth:
|
|
491
|
+
1. **Surface Section**: Starts at `initial_spacing` and geometrically refines cells downward toward `reaction_zone[0]`.
|
|
492
|
+
2. **Reaction Zone Section**: Maintains a constant, ultra-fine cell size (`reaction_zone_spacing`) across the depth interval `reaction_zone = (z_start, z_end)`.
|
|
493
|
+
3. **Coarsening Section**: Below the reaction zone, cell size expands geometrically by factor $r = 1.05$ per cell.
|
|
494
|
+
4. **Deep Uniform Section**: Once cell size reaches `max_spacing`, the remainder of the domain down to `max_depth` uses uniform large cells.
|
|
495
|
+
|
|
496
|
+
### 2. The Geochemical Importance of Reaction Zone Resolution
|
|
497
|
+
Why does grid spacing in the reaction zone matter so much?
|
|
498
|
+
Fast bimolecular redox reactions—such as ferrous iron oxidation ($\text{Fe}^{2+} + \text{O}_2 \to \text{Fe}^{3+}$) or sulfide oxidation—occur across exceedingly narrow interfaces. The physical reaction front width $\delta_{\text{phys}}$ is governed by the balance between molecular diffusion and reaction kinetics:
|
|
499
|
+
$$\delta_{\text{phys}} \sim \sqrt{\frac{D_{\text{eff}}}{k \cdot C_{\text{scale}}}}$$
|
|
500
|
+
For transport-controlled reactions with large kinetic constants ($k = 10^7\ \text{M}^{-1}\text{s}^{-1}$), $\delta_{\text{phys}}$ is often **under $0.05\text{ mm}$**.
|
|
501
|
+
|
|
502
|
+
If your numerical grid cell size $\Delta z$ is larger than $2 \delta_{\text{phys}}$, the local Damköhler number is large ($Da_{II} \gg 1$). The finite-volume discretization cannot resolve the sharp counter-gradients, producing a numerical artifact called **artificial reactant coexistence**:
|
|
503
|
+
- $\text{Fe}^{2+}$ and $\text{O}_2$ artificially overlap in the same cell.
|
|
504
|
+
- Reactants diffuse past each other instead of reacting completely at the interface.
|
|
505
|
+
- Apparent reaction rates become smeared over several millimeters to centimeters.
|
|
506
|
+
|
|
507
|
+
### 3. Automated Coexistence Diagnostic
|
|
508
|
+
MANTIS automatically runs a post-simulation front diagnostic (`check_reactant_coexistence`) at the end of every run. If any reaction front is under-resolved, MANTIS outputs a clear warning:
|
|
509
|
+
```text
|
|
510
|
+
[WARNING: Numerical Reactant Co-existence Detected]
|
|
511
|
+
Reaction: 'Fe2 Oxidation by O2' (fe2 + O2)
|
|
512
|
+
Front location (peak): z = 0.21 cm (depth cell index: 21)
|
|
513
|
+
Local grid resolution: dz = 0.10 mm
|
|
514
|
+
Estimated physical front width: 0.01 mm
|
|
515
|
+
Front cell span (N_cells): 6 cell(s)
|
|
516
|
+
Reasons: Local grid cell dz (0.10 mm) > 2x physical front width (0.01 mm)
|
|
517
|
+
--> Action recommended: In make_grid2(), refine the mesh around z = [0.19, 0.23] cm to reaction_zone_spacing <= 0.01 mm.
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
When you see this warning, simply update your driver script's `model.configure()` with the recommended reaction zone window and spacing:
|
|
521
|
+
```python
|
|
522
|
+
model.configure(
|
|
523
|
+
reaction_zone=("0.15 cm", "0.30 cm"),
|
|
524
|
+
reaction_zone_spacing="0.01 mm",
|
|
525
|
+
)
|
|
526
|
+
```
|
|
527
|
+
|
|
426
528
|
<a id="orgf185086"></a>
|
|
427
529
|
|
|
428
530
|
# Step 5: Designing Plot Layouts (`plot_layout.py`)
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
#+TITLE: MANTIS: A Geoscientist's Guide to 1D Reactive-Transport Modeling
|
|
2
1
|
#+AUTHOR: MANTIS Development Team
|
|
3
2
|
#+DATE: [2026-10-11]
|
|
4
|
-
#+OPTIONS: toc:
|
|
5
|
-
|
|
3
|
+
#+OPTIONS: toc:nil
|
|
4
|
+
|
|
6
5
|
|
|
7
6
|
* Introduction: What is MANTIS?
|
|
8
7
|
|
|
@@ -155,6 +154,90 @@ species = {
|
|
|
155
154
|
}
|
|
156
155
|
#+END_SRC
|
|
157
156
|
|
|
157
|
+
* Concentration Units & Conversions: Inputs, Internal Engine, and Outputs
|
|
158
|
+
|
|
159
|
+
A common source of confusion in diagenetic modeling is the distinction between porewater solute concentrations, solid sediment mineral concentrations, and geochemical laboratory units (such as $\text{wt}\%$ or $\mu\text{mol/g}$). MANTIS handles physical units transparently while maintaining strict mass conservation across phases.
|
|
160
|
+
|
|
161
|
+
** 1. Default Input Units
|
|
162
|
+
|
|
163
|
+
When defining your model setup and boundary conditions:
|
|
164
|
+
|
|
165
|
+
- *Driver Script Parameters (=run_velde_model.py=)*:
|
|
166
|
+
MANTIS supports human-readable strings parsed via Python's =pint= physical unit library:
|
|
167
|
+
- Lengths and depths: ="0.5 m"=, ="50 cm"=
|
|
168
|
+
- Times and time steps: ="0.5 kyr"=, ="1 year"=, ="24 hours"=
|
|
169
|
+
- Sedimentation rates: ="0.2 cm/yr"=, ="1 mm/a"=
|
|
170
|
+
- Bioturbation coefficients: ="4 cm^2/year"=, ="1e-9 m^2/s"=
|
|
171
|
+
|
|
172
|
+
- *Boundary Conditions (=species.py=)*:
|
|
173
|
+
- *Dissolved Solutes (=type: "dissolved"=)*:
|
|
174
|
+
Upper boundary concentrations can be specified as a numeric float or a unit string. The default numeric input is *$\text{mmol/L}$* (which is identical to $\text{mol/m}^3$ or $\text{mM}$).
|
|
175
|
+
#+BEGIN_EXAMPLE
|
|
176
|
+
"bc": {"top": 28.2, "type": "dissolved"} # 28.2 mmol/L (seawater sulfate)
|
|
177
|
+
"bc": {"top": "280 umol/L", "type": "dissolved"} # parsed automatically to 0.28 mmol/L
|
|
178
|
+
#+END_EXAMPLE
|
|
179
|
+
- *Particulate Solids (=type: "particulate"=)*:
|
|
180
|
+
Deposition at the sediment-water interface is specified as a depositional surface flux:
|
|
181
|
+
#+BEGIN_EXAMPLE
|
|
182
|
+
"bc": {"top": "12 umol/(cm^2 * yr)", "type": "particulate"}
|
|
183
|
+
#+END_EXAMPLE
|
|
184
|
+
MANTIS automatically converts this to internal flux units ($\text{mol}/(\text{m}^2 \cdot \text{s})$) during initialization.
|
|
185
|
+
|
|
186
|
+
** 2. How Units Are Handled Internally by the Solver
|
|
187
|
+
|
|
188
|
+
Under the hood, MANTIS standardizes all transport and reaction PDEs into SI-derived volumetric units:
|
|
189
|
+
\[
|
|
190
|
+
\text{Internal Concentration Unit} = \mathbf{mol/m^3} \quad (\equiv 1\ \text{mmol/L} \equiv 1\ \text{mM} \equiv 1000\ \mu\text{mol/L} \equiv 1\ \mu\text{mol/cm}^3)
|
|
191
|
+
\]
|
|
192
|
+
|
|
193
|
+
Crucially, concentrations are defined *per unit volume of their respective phase*:
|
|
194
|
+
1. *Liquid Solutes*: Expressed per volume of porewater ($\text{mol / m}^3_{\text{liquid}} = \text{mmol / L}_{\text{porewater}}$). The bulk sediment inventory is $\phi \cdot C_{\text{liquid}}$.
|
|
195
|
+
2. *Solid Minerals*: Expressed per volume of pure solid sediment ($\text{mol / m}^3_{\text{solid}} = \text{mmol / L}_{\text{solid}}$). The bulk sediment inventory is $(1 - \phi) \cdot C_{\text{solid}}$.
|
|
196
|
+
3. *Phase Transfer & Reactions*: Whenever a reaction couples a solid phase and a liquid phase (for example, $\text{Fe}^{2+} + \text{HS}^- \to \text{FeS}$), MANTIS scales the source and sink terms with the appropriate phase ratio ($(1-\phi)/\phi$ or $\phi/(1-\phi)$) so that mass is strictly conserved.
|
|
197
|
+
4. *Reaction Rates*: Solved internally in $\text{mol} / (\text{m}^3_{\text{phase}} \cdot \text{s})$.
|
|
198
|
+
5. *Depths and Transport*: Depth $z$ is in meters ($\text{m}$), time $t$ in seconds ($\text{s}$), and diffusion coefficients in $\text{m}^2/\text{s}$.
|
|
199
|
+
|
|
200
|
+
** 3. Output Units in Result Files (=.csv=)
|
|
201
|
+
|
|
202
|
+
When the simulation completes or exports data (=<model_name>.csv=):
|
|
203
|
+
- =c_<species>=: Reported directly in *$\text{mmol/L}$* (numerically equivalent to $\text{mol/m}^3$):
|
|
204
|
+
- Solutes (e.g., =c_SO4=, =c_O2=, =c_TS2=) are in *$\text{mmol / L of porewater}$*.
|
|
205
|
+
- Solids (e.g., =c_FeS=, =c_Fe3=, =c_POC_slow=) are in *$\text{mmol / L of pure solid}$*.
|
|
206
|
+
- =f_<species>=: Net production/consumption reaction rates in *$\text{mol} / (\text{m}^3 \cdot \text{s})$*.
|
|
207
|
+
- =d_<species>=: Isotope delta values (e.g., =d_SO4=, =d_FeS=) in *$\permil$* (per mil relative to VCDT).
|
|
208
|
+
- =z=: Depth below sediment-water interface in *meters* ($\text{m}$).
|
|
209
|
+
- =phi= and =w=: Porosity (fraction, dimensionless) and burial velocity ($\text{m/s}$).
|
|
210
|
+
|
|
211
|
+
** 4. Converting Solid Concentrations to Standard Geochemical Units
|
|
212
|
+
|
|
213
|
+
Geochemists typically report solid mineral abundances as dry-sediment mass fractions ($\text{wt}\%$) or mass-normalized molar concentrations ($\mu\text{mol/g}$). MANTIS includes built-in conversion utilities in =mantis.diff_lib=:
|
|
214
|
+
|
|
215
|
+
*** A. Converting to $\mu\text{mol / g}$ (micromoles per gram of dry sediment)
|
|
216
|
+
To convert a solid concentration $C_{\text{solid}}$ ($\text{mmol/L of solid} = \mu\text{mol/cm}^3_{\text{solid}}$) to $\mu\text{mol/g}$ of dry sediment, divide by the mineral grain density $\rho_{\text{sed}}$ (typically $2.6\ \text{g/cm}^3$ for siliciclastic sediments):
|
|
217
|
+
\[
|
|
218
|
+
C_{\mu\text{mol/g}} = \frac{C_{\text{solid}}}{\rho_{\text{sed}}}
|
|
219
|
+
\]
|
|
220
|
+
In Python:
|
|
221
|
+
#+BEGIN_SRC python
|
|
222
|
+
from mantis.diff_lib import solid_conc_to_umol_per_g
|
|
223
|
+
|
|
224
|
+
# df.c_FeS is in mmol/L of solid; density in g/cm^3
|
|
225
|
+
c_fes_umol_g = solid_conc_to_umol_per_g(df.c_FeS, d=2.6)
|
|
226
|
+
#+END_SRC
|
|
227
|
+
|
|
228
|
+
*** B. Converting to Dry Weight Percent ($\text{wt}\%$)
|
|
229
|
+
To convert $C_{\text{solid}}$ to weight percent of dry sediment matrix given molecular weight $M_w$ ($\text{g/mol}$):
|
|
230
|
+
\[
|
|
231
|
+
\text{wt}\% = \frac{C_{\text{solid}} \cdot M_w}{\rho_{\text{sed}} \cdot 10^4}
|
|
232
|
+
\]
|
|
233
|
+
In Python:
|
|
234
|
+
#+BEGIN_SRC python
|
|
235
|
+
from mantis.diff_lib import solid_conc_to_wt_percent
|
|
236
|
+
|
|
237
|
+
# Example: FeS (Mw = 87.91 g/mol, density = 2.6 g/cm^3)
|
|
238
|
+
fes_wt_pct = solid_conc_to_wt_percent(df.c_FeS, mw=87.91, d=2.6, phi=df.phi)
|
|
239
|
+
#+END_SRC
|
|
240
|
+
|
|
158
241
|
* Step 2: Defining Kinetic Constants (=kinetic_constants_*.py=)
|
|
159
242
|
|
|
160
243
|
The kinetic constants file defines how fast reactions occur and the affinity thresholds of microbial communities.
|
|
@@ -349,6 +432,12 @@ if __name__ == "__main__":
|
|
|
349
432
|
"BI0": 0.0, # Set to 0.0 to disable; or e.g. "20 /year"
|
|
350
433
|
"depth": "0 cm",
|
|
351
434
|
},
|
|
435
|
+
|
|
436
|
+
# Spatial grid & reaction zone refinement (optional, defaults provided)
|
|
437
|
+
initial_spacing="0.1 mm", # Spacing at sediment-water interface (z=0)
|
|
438
|
+
reaction_zone=("0.1 cm", "1.0 cm"), # Critical depth window of sharp redox fronts
|
|
439
|
+
reaction_zone_spacing="0.05 mm", # Dense, uniform spacing in the reaction zone
|
|
440
|
+
max_spacing="1.0 cm", # Coarser spacing in deep sediment
|
|
352
441
|
)
|
|
353
442
|
|
|
354
443
|
# 3. Allow CLI overrides (e.g. running --video from the terminal)
|
|
@@ -368,6 +457,53 @@ if __name__ == "__main__":
|
|
|
368
457
|
result.plot_summary()
|
|
369
458
|
#+END_SRC
|
|
370
459
|
|
|
460
|
+
** Spatial Grid Configuration & Reaction Zone Resolution
|
|
461
|
+
|
|
462
|
+
Diagenetic models must resolve spatial scales ranging from sub-millimeter oxygen penetration depths to deep sediment columns spanning decimeters to hundreds of meters. A uniform fine grid across a 50-meter column would require millions of cells and slow down calculations unnecessarily.
|
|
463
|
+
|
|
464
|
+
MANTIS solves this using an adaptive geometric-mesh builder (=make_grid2=) that refines where steep gradients occur and expands into the deep sediment.
|
|
465
|
+
|
|
466
|
+
*** 1. How Grid Generation Works (=make_grid2=)
|
|
467
|
+
The grid structure is divided into four seamless stages from the sediment surface to depth:
|
|
468
|
+
1. *Surface Section*: Starts at =initial_spacing= and geometrically refines cells downward toward =reaction_zone[0]=.
|
|
469
|
+
2. *Reaction Zone Section*: Maintains a constant, ultra-fine cell size (=reaction_zone_spacing=) across the depth interval =reaction_zone = (z_start, z_end)=.
|
|
470
|
+
3. *Coarsening Section*: Below the reaction zone, cell size expands geometrically by factor $r = 1.05$ per cell.
|
|
471
|
+
4. *Deep Uniform Section*: Once cell size reaches =max_spacing=, the remainder of the domain down to =max_depth= uses uniform large cells.
|
|
472
|
+
|
|
473
|
+
*** 2. The Geochemical Importance of Reaction Zone Resolution
|
|
474
|
+
Why does grid spacing in the reaction zone matter so much?
|
|
475
|
+
Fast bimolecular redox reactions—such as ferrous iron oxidation ($\text{Fe}^{2+} + \text{O}_2 \to \text{Fe}^{3+}$) or sulfide oxidation—occur across exceedingly narrow interfaces. The physical reaction front width $\delta_{\text{phys}}$ is governed by the balance between molecular diffusion and reaction kinetics:
|
|
476
|
+
\[
|
|
477
|
+
\delta_{\text{phys}} \sim \sqrt{\frac{D_{\text{eff}}}{k \cdot C_{\text{scale}}}}
|
|
478
|
+
\]
|
|
479
|
+
For transport-controlled reactions with large kinetic constants ($k = 10^7\ \text{M}^{-1}\text{s}^{-1}$), $\delta_{\text{phys}}$ is often *under $0.05\text{ mm}$*.
|
|
480
|
+
|
|
481
|
+
If your numerical grid cell size $\Delta z$ is larger than $2 \delta_{\text{phys}}$, the local Damk\ouml{}hler number is large ($Da_{II} \gg 1$). The finite-volume discretization cannot resolve the sharp counter-gradients, producing a numerical artifact called *artificial reactant coexistence*:
|
|
482
|
+
- $\text{Fe}^{2+}$ and $\text{O}_2$ artificially overlap in the same cell.
|
|
483
|
+
- Reactants diffuse past each other instead of reacting completely at the interface.
|
|
484
|
+
- Apparent reaction rates become smeared over several millimeters to centimeters.
|
|
485
|
+
|
|
486
|
+
*** 3. Automated Coexistence Diagnostic
|
|
487
|
+
MANTIS automatically runs a post-simulation front diagnostic (=check_reactant_coexistence=) at the end of every run. If any reaction front is under-resolved, MANTIS outputs a clear warning:
|
|
488
|
+
#+BEGIN_EXAMPLE
|
|
489
|
+
[WARNING: Numerical Reactant Co-existence Detected]
|
|
490
|
+
Reaction: 'Fe2 Oxidation by O2' (fe2 + O2)
|
|
491
|
+
Front location (peak): z = 0.21 cm (depth cell index: 21)
|
|
492
|
+
Local grid resolution: dz = 0.10 mm
|
|
493
|
+
Estimated physical front width: 0.01 mm
|
|
494
|
+
Front cell span (N_cells): 6 cell(s)
|
|
495
|
+
Reasons: Local grid cell dz (0.10 mm) > 2x physical front width (0.01 mm)
|
|
496
|
+
--> Action recommended: In make_grid2(), refine the mesh around z = [0.19, 0.23] cm to reaction_zone_spacing <= 0.01 mm.
|
|
497
|
+
#+END_EXAMPLE
|
|
498
|
+
|
|
499
|
+
When you see this warning, simply update your driver script's =model.configure()= with the recommended reaction zone window and spacing:
|
|
500
|
+
#+BEGIN_SRC python
|
|
501
|
+
model.configure(
|
|
502
|
+
reaction_zone=("0.15 cm", "0.30 cm"),
|
|
503
|
+
reaction_zone_spacing="0.01 mm",
|
|
504
|
+
)
|
|
505
|
+
#+END_SRC
|
|
506
|
+
|
|
371
507
|
* Step 5: Designing Plot Layouts (=plot_layout.py=)
|
|
372
508
|
|
|
373
509
|
Geochemists usually want to see:
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Define reaction constants.
|
|
2
|
+
|
|
3
|
+
Note, Velde et al likely have a typo in the FeS equilibrium constant.
|
|
4
|
+
Rickard (2006) defines two reaction constants, one with Ksp = 3.5
|
|
5
|
+
and the other with Ksp = - 3.5. Here we use the latter since otherwise
|
|
6
|
+
we would never precipitate FeS unless H2S > 1 mmol/l
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_reaction_constants(
|
|
13
|
+
pH: float, phi: float | Any, k_values: dict = {}
|
|
14
|
+
) -> dict:
|
|
15
|
+
"""Convert reaction_constants into model units.
|
|
16
|
+
|
|
17
|
+
It is assumed that the reaction constants are in phase specific units.
|
|
18
|
+
This can be tricky, as Velde et al, state that their k-values are in
|
|
19
|
+
bulk units, but then they convert it before using (see Table 3). E.g.
|
|
20
|
+
they write:
|
|
21
|
+
|
|
22
|
+
(1−ϕ)⋅kSIR⋅[FeOOH]⋅[HS−]
|
|
23
|
+
|
|
24
|
+
which suggests that the (1−ϕ) is used to convert FeOOH from solid
|
|
25
|
+
phase concentration to bulk concentration. Since this code
|
|
26
|
+
tracks all concentrations in phase specific units, converting the
|
|
27
|
+
k-value by deviding by (1−ϕ), would cancel out, and we can use the
|
|
28
|
+
k-value as published.
|
|
29
|
+
"""
|
|
30
|
+
import pint
|
|
31
|
+
|
|
32
|
+
ureg = pint.UnitRegistry()
|
|
33
|
+
Q_ = ureg.Quantity
|
|
34
|
+
|
|
35
|
+
if hasattr(phi, "value"):
|
|
36
|
+
phi = phi.value
|
|
37
|
+
|
|
38
|
+
velde: dict = {
|
|
39
|
+
"POC_fast": [10, "1/year", "1/second"], # highly reactive OM
|
|
40
|
+
"POC_slow": [0.1, "1/year", "1/second"], # reactive OM, Velde et al
|
|
41
|
+
"Fe2_p_eq": [
|
|
42
|
+
696,
|
|
43
|
+
"dimensionless",
|
|
44
|
+
"dimensionless",
|
|
45
|
+
], # sorbed vs Fe2+ Fe2+ liq.
|
|
46
|
+
"FeS2_O2": [
|
|
47
|
+
1e-10,
|
|
48
|
+
"m^3/(mol*second)",
|
|
49
|
+
"m^3/(mol*second)",
|
|
50
|
+
], # FeS2 + O2 -> SO4, Halevy et al
|
|
51
|
+
"FeS_S0": [ # FeS + S0 -> FeS2
|
|
52
|
+
5e-8,
|
|
53
|
+
"m^3/(mol*second)",
|
|
54
|
+
"m^3/(mol*second)",
|
|
55
|
+
], # FeS + HS -> FeS2
|
|
56
|
+
"FeS_TS2": [
|
|
57
|
+
5e-8,
|
|
58
|
+
"m^3/(mol*second)",
|
|
59
|
+
"m^3/(mol*second)",
|
|
60
|
+
], # FeS + H2S -> FeS2, at 10C -> notes.org
|
|
61
|
+
"Hplus": [10 ** (-pH), "mol/l", "mol/m^3"],
|
|
62
|
+
# Oxidation reactions after Velde at eal 2016
|
|
63
|
+
"TS2_O2": [1e7, "cm^3/(umol*year)", "m^3/(mol*second)"],
|
|
64
|
+
"Fe3_hs": [
|
|
65
|
+
494,
|
|
66
|
+
"cm^3/(umol*year)",
|
|
67
|
+
"m^3/(mol*second)",
|
|
68
|
+
], # check with halevy
|
|
69
|
+
"FeS_O2": [1e7, "cm^3/(umol*year)", "m^3/(mol*second)"],
|
|
70
|
+
"Fe2_O2": [1e7, "cm^3/(umol*year)", "m^3/(mol*second)"],
|
|
71
|
+
# "Fe2p_ox": [1e73, "cm^3/(umol*year)", "m^3/(mol*second)"],
|
|
72
|
+
"S0_O2": [4e7, "cm^3/(umol*year)", "m^3/(mol*second)"],
|
|
73
|
+
# Iron sulfide reactions after Velde at eal 2016
|
|
74
|
+
"FeS_isp": [
|
|
75
|
+
1e4,
|
|
76
|
+
"umol/((cm^3*year))",
|
|
77
|
+
"mol/(m^3 *second)",
|
|
78
|
+
], # FeS precipitation
|
|
79
|
+
"FeS_isd": [3, "1/year", "1/second"], # FeS dissolution
|
|
80
|
+
"FeS_sp": [ # FeS equilibrium constant
|
|
81
|
+
10**-3.5,
|
|
82
|
+
"mol/l",
|
|
83
|
+
"mol/m^3 ",
|
|
84
|
+
],
|
|
85
|
+
# S0 disproportionation rate (first-order in S0)
|
|
86
|
+
"S0_dispro": [50, "1/year", "1/second"],
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for k, v in velde.items():
|
|
90
|
+
k_values[k] = Q_(f"{v[0]} {v[1]}").to(f"{v[2]}").magnitude
|
|
91
|
+
|
|
92
|
+
return velde, k_values
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
if __name__ == "__main__":
|
|
96
|
+
import pint
|
|
97
|
+
|
|
98
|
+
ureg = pint.UnitRegistry()
|
|
99
|
+
Q_ = ureg.Quantity
|
|
100
|
+
|
|
101
|
+
velde, k_values = get_reaction_constants(7.5, 0.8)
|
|
102
|
+
ureg = pint.UnitRegistry()
|
|
103
|
+
for k, v in velde.items():
|
|
104
|
+
n = Q_(f"{v[0]} {v[1]}").to(f"{v[2]}")
|
|
105
|
+
print(f"{k} = {n.magnitude:.2e} [{n.units:~P}]")
|
|
106
|
+
|
|
107
|
+
print(f"k.FeS_sp * k.Hplus: {k_values['FeS_sp'] * k_values['Hplus']:.2e}")
|
|
108
|
+
print(f"k.Hplus: {k_values['Hplus']:.2e}")
|
{mantis_rtm-0.1.1.dev0 → mantis_rtm-0.1.2}/examples/velde_et_al_2016/kinetic_constants_slow.py
RENAMED
|
@@ -76,7 +76,7 @@ def get_reaction_constants(
|
|
|
76
76
|
"umol/((cm^3*year))",
|
|
77
77
|
"mol/(m^3 *second)",
|
|
78
78
|
], # FeS precipitation
|
|
79
|
-
"FeS_isd": [3
|
|
79
|
+
"FeS_isd": [3, "1/year", "1/second"], # FeS dissolution
|
|
80
80
|
"FeS_sp": [ # FeS equilibrium constant
|
|
81
81
|
10**-3.5,
|
|
82
82
|
"mol/l",
|
|
@@ -168,7 +168,7 @@ def get_layout(df):
|
|
|
168
168
|
"xlim": (1e-4, None),
|
|
169
169
|
# "ylim": (-15, 75),
|
|
170
170
|
# "right": [[df.d_h2s, "d_h2s", {"color": "C1"}]],
|
|
171
|
-
"options-left": ["broken_axis(-45, -
|
|
171
|
+
"options-left": ["broken_axis(-45, -37, 20, 30)"],
|
|
172
172
|
},
|
|
173
173
|
})
|
|
174
174
|
|