pymrm 2.2.2__tar.gz → 2.2.4__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.
- {pymrm-2.2.2 → pymrm-2.2.4}/CITATION.cff +1 -1
- {pymrm-2.2.2 → pymrm-2.2.4}/PKG-INFO +2 -3
- {pymrm-2.2.2 → pymrm-2.2.4}/README.md +1 -2
- {pymrm-2.2.2 → pymrm-2.2.4}/docs/compile_sphinx.md +6 -6
- pymrm-2.2.4/docs/pymrm-model-style-guide.md +552 -0
- pymrm-2.2.4/docs/sphinx/.gitignore +4 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/docs/sphinx/conf.py +11 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/scripts/build_documentation.sh +0 -10
- {pymrm-2.2.2 → pymrm-2.2.4}/src/pymrm/_version.py +3 -3
- {pymrm-2.2.2 → pymrm-2.2.4}/src/pymrm/solve.py +23 -3
- {pymrm-2.2.2 → pymrm-2.2.4}/src/pymrm.egg-info/PKG-INFO +2 -3
- {pymrm-2.2.2 → pymrm-2.2.4}/src/pymrm.egg-info/SOURCES.txt +2 -2
- {pymrm-2.2.2 → pymrm-2.2.4}/test/test_solve.py +11 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/tutorials/diffusion_2D.ipynb +2 -2
- {pymrm-2.2.2 → pymrm-2.2.4}/tutorials/diffusion_model_class.ipynb +2 -2
- {pymrm-2.2.2 → pymrm-2.2.4}/tutorials/nonlinear_kinetics.ipynb +2 -2
- pymrm-2.2.2/docs/sphinx/modules.rst +0 -7
- pymrm-2.2.2/docs/sphinx/pymrm.rst +0 -77
- {pymrm-2.2.2 → pymrm-2.2.4}/.coveragerc +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/.flake8 +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/.github/black-formatting.yml +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/.github/workflows/analysis.yml +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/.github/workflows/build.yml +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/.github/workflows/python-publish.yml +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/.github/workflows/test.yml +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/.github/workflows/update-citation-cff.yml +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/.github/workflows/validate-citation-cff.yml +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/.github/workflows/verification.yml +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/.gitignore +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/.gitlab-ci.yml +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/.vscode/launch_debugpy.json +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/.vscode/settings_pytest.json +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/Dockerfile +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/LICENSE +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/MANIFEST.in +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/docs/dependencies_format.md +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/docs/helpers.md +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/docs/installation.md +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/docs/sphinx/Makefile +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/docs/sphinx/index.rst +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/docs/sphinx/make.bat +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/environment.yml +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/examples/counter_current_reaction.ipynb +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/examples/counter_diffusion_reaction.ipynb +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/examples/darcy_flow.ipynb +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/examples/lid_driven_cavity.ipynb +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/examples/membrane_module_2D.ipynb +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/examples/membrane_module_2D_Sievert.ipynb +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/examples/optimization_example.ipynb +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/examples/particle_model_effectiveness.ipynb +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/examples/surface_reaction.ipynb +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/examples/tvd.ipynb +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/examples/unsteady_convection_reaction.ipynb +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/examples/unsteady_diffusion_reaction_2D.ipynb +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/pyproject.toml +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/requirements.txt +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/scripts/run_examples.py +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/scripts/run_exercises.py +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/scripts/run_linter.sh +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/scripts/run_notebook.py +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/scripts/run_tutorials.py +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/scripts/run_unit_tests.sh +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/scripts/split_notebook.py +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/setup.cfg +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/src/pymrm/__init__.py +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/src/pymrm/convect.py +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/src/pymrm/coupling.py +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/src/pymrm/grid.py +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/src/pymrm/helpers.py +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/src/pymrm/interpolate.py +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/src/pymrm/numjac.py +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/src/pymrm/operators.py +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/src/pymrm.egg-info/dependency_links.txt +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/src/pymrm.egg-info/requires.txt +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/src/pymrm.egg-info/top_level.txt +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/test/test_convect.py +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/test/test_coupling.py +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/test/test_csr_format.py +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/test/test_grid.py +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/test/test_helpers.py +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/test/test_integration.py +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/test/test_interpolate.py +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/test/test_numjac.py +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/test/test_operators.py +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/tutorials/diffusion_first_order_kinetics.ipynb +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/tutorials/introduction_and_start.ipynb +0 -0
- {pymrm-2.2.2 → pymrm-2.2.4}/tutorials/stationary_diffusion.ipynb +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pymrm
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.4
|
|
4
4
|
Summary: Functions for multiphase reactor modeling
|
|
5
5
|
Author-email: "E.A.J.F. Peters" <e.a.j.f.peters@tue.nl>, "M. van Sint Annaland" <M.v.SintAnnaland@tue.nl>, "M. Galanti" <m.galanti@tue.nl>, "D.R. Rieder" <d.r.rieder@tue.nl>
|
|
6
6
|
Maintainer-email: "E.A.J.F. Peters" <e.a.j.f.peters@tue.nl>
|
|
@@ -61,7 +61,7 @@ Dynamic: license-file
|
|
|
61
61
|
[](https://pypi.org/project/pymrm/)
|
|
62
62
|
[](https://github.com/computational-chemical-engineering/pymrm/blob/main/LICENSE)
|
|
63
63
|
[](https://github.com/psf/black)
|
|
64
|
-
[](https://zenodo.org/badge/latestdoi/901029258)
|
|
64
|
+
[](https://zenodo.org/badge/latestdoi/901029258)
|
|
65
65
|
|
|
66
66
|
|
|
67
67
|
## Overview
|
|
@@ -99,4 +99,3 @@ coefficient matrices.
|
|
|
99
99
|
## Contributing
|
|
100
100
|
|
|
101
101
|
Contributions are welcome! For suggestions or enhancements, please contact us or submit a pull request.
|
|
102
|
-
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://pypi.org/project/pymrm/)
|
|
6
6
|
[](https://github.com/computational-chemical-engineering/pymrm/blob/main/LICENSE)
|
|
7
7
|
[](https://github.com/psf/black)
|
|
8
|
-
[](https://zenodo.org/badge/latestdoi/901029258)
|
|
8
|
+
[](https://zenodo.org/badge/latestdoi/901029258)
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
## Overview
|
|
@@ -43,4 +43,3 @@ coefficient matrices.
|
|
|
43
43
|
## Contributing
|
|
44
44
|
|
|
45
45
|
Contributions are welcome! For suggestions or enhancements, please contact us or submit a pull request.
|
|
46
|
-
|
|
@@ -55,11 +55,11 @@ language = 'en'
|
|
|
55
55
|
html_theme = 'sphinx_rtd_theme'
|
|
56
56
|
html_static_path = ['_static']
|
|
57
57
|
```
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
The API `*.rst` pages (`modules.rst` and `pymrm.rst`) are generated automatically by
|
|
59
|
+
the Sphinx build configuration (`conf.py`) during `make html`, so they should not be
|
|
60
|
+
manually generated or committed.
|
|
61
|
+
|
|
62
|
+
Ensure `modules` is present in the `index.rst` toctree:
|
|
63
63
|
```bash
|
|
64
64
|
.. toctree::
|
|
65
65
|
:maxdepth: 2
|
|
@@ -72,4 +72,4 @@ Finally, you can invoke make to generate the documentation
|
|
|
72
72
|
# here we are in the root folder of the package
|
|
73
73
|
cd docs/sphinx
|
|
74
74
|
make html
|
|
75
|
-
```
|
|
75
|
+
```
|
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
# PyMRM Model Style Guide
|
|
2
|
+
|
|
3
|
+
This guide is intended for `pymrm` exercise solutions and class demonstrations.
|
|
4
|
+
It is based on the current `pymrm` tutorials and API, the extended teacher
|
|
5
|
+
solutions in `pymrm-book-teacher`, and the implementation patterns already used
|
|
6
|
+
in `exercises/solutions`.
|
|
7
|
+
|
|
8
|
+
The goal is not to make every model identical. The goal is to make them
|
|
9
|
+
predictable: same structure, same naming, same array conventions, and same
|
|
10
|
+
separation between physics, discretisation, solver, and plotting.
|
|
11
|
+
|
|
12
|
+
## 1. Design Principles
|
|
13
|
+
|
|
14
|
+
1. Prefer `pymrm` operators over hand-written finite-difference stencils.
|
|
15
|
+
2. Keep the physics readable: define balances, source terms, and boundary
|
|
16
|
+
conditions in code as directly as possible.
|
|
17
|
+
3. Separate constant linear operators from state-dependent nonlinear terms.
|
|
18
|
+
4. Use one consistent array layout across the whole model.
|
|
19
|
+
5. Make every model easy to validate against a limit case, analytical solution,
|
|
20
|
+
or known trend.
|
|
21
|
+
6. Keep plotting and reporting outside the core residual whenever possible.
|
|
22
|
+
|
|
23
|
+
## 2. Recommended Output Formats
|
|
24
|
+
|
|
25
|
+
Two formats are allowed.
|
|
26
|
+
|
|
27
|
+
### 2.1 Compact script format
|
|
28
|
+
|
|
29
|
+
Use this for:
|
|
30
|
+
- L1-L2 ODE examples
|
|
31
|
+
- single-purpose demonstrations
|
|
32
|
+
- short derivations where the numerical method itself is the teaching target
|
|
33
|
+
|
|
34
|
+
Required section order in a `.py` file or notebook:
|
|
35
|
+
|
|
36
|
+
1. Problem statement
|
|
37
|
+
2. Imports
|
|
38
|
+
3. Parameters
|
|
39
|
+
4. Model equations in code
|
|
40
|
+
5. Discretisation / solver loop
|
|
41
|
+
6. Post-processing
|
|
42
|
+
7. Short validation note
|
|
43
|
+
|
|
44
|
+
### 2.2 Class-based format
|
|
45
|
+
|
|
46
|
+
Use this by default for:
|
|
47
|
+
- all PDE models
|
|
48
|
+
- multicomponent models
|
|
49
|
+
- multiphase models
|
|
50
|
+
- reusable demos
|
|
51
|
+
- any model with more than one solve mode or more than one physical field
|
|
52
|
+
|
|
53
|
+
This is the preferred style from L3 onward.
|
|
54
|
+
|
|
55
|
+
## 3. Standard Section Order
|
|
56
|
+
|
|
57
|
+
For notebooks, use Markdown headings in this order:
|
|
58
|
+
|
|
59
|
+
1. `## Background`
|
|
60
|
+
2. `## Governing Equations`
|
|
61
|
+
3. `## Parameters and Assumptions`
|
|
62
|
+
4. `## PyMRM Implementation`
|
|
63
|
+
5. `## Results`
|
|
64
|
+
6. `## Validation`
|
|
65
|
+
7. `## Discussion`
|
|
66
|
+
|
|
67
|
+
For scripts, use comment banners in the same order.
|
|
68
|
+
|
|
69
|
+
### 3.1 Notebook formatting and figures
|
|
70
|
+
|
|
71
|
+
Notebooks should render clearly in three places:
|
|
72
|
+
|
|
73
|
+
1. A running Jupyter notebook or JupyterLab session.
|
|
74
|
+
2. The GitHub notebook preview.
|
|
75
|
+
3. The VS Code notebook editor.
|
|
76
|
+
|
|
77
|
+
Use portable notebook Markdown by default:
|
|
78
|
+
|
|
79
|
+
- Use ordinary Markdown headings, lists, links, tables, fenced code blocks, and
|
|
80
|
+
LaTeX equations.
|
|
81
|
+
- Use display equations with `$$ ... $$` and inline equations with `$...$`.
|
|
82
|
+
- Avoid MyST-only directives such as `{figure}`, `{grid}`,
|
|
83
|
+
admonitions, and colon-fenced layouts when the notebook must also be readable
|
|
84
|
+
outside the Jupyter Book build.
|
|
85
|
+
- Avoid raw HTML unless there is no practical Markdown alternative.
|
|
86
|
+
- Keep one conceptual step per Markdown or code cell. Long cells with many
|
|
87
|
+
unrelated equations, figures, and instructions are harder to review and more
|
|
88
|
+
likely to render inconsistently.
|
|
89
|
+
|
|
90
|
+
Generated figures:
|
|
91
|
+
|
|
92
|
+
- Figures produced by Python plotting functions should be created by code cells
|
|
93
|
+
when the notebook is run.
|
|
94
|
+
- Do not save generated plots to external image files just to include them in a
|
|
95
|
+
later Markdown cell.
|
|
96
|
+
- Use the inline Matplotlib backend, create figures explicitly with
|
|
97
|
+
`fig, ax = plt.subplots(...)`, label axes with symbol and unit, and call
|
|
98
|
+
`plt.show()` when helpful for reliable rendering.
|
|
99
|
+
- If a generated figure is useful in the GitHub preview, keep the cell output in
|
|
100
|
+
the notebook. The source of truth remains the plotting code, not an external
|
|
101
|
+
image file.
|
|
102
|
+
|
|
103
|
+
Static or externally prepared figures:
|
|
104
|
+
|
|
105
|
+
- Embed static figures as notebook attachments, not as separate files in a
|
|
106
|
+
`media/` directory, when the notebook is intended to be downloadable as a
|
|
107
|
+
single self-contained `.ipynb` file.
|
|
108
|
+
- Reference attached figures with standard Markdown:
|
|
109
|
+
|
|
110
|
+
```markdown
|
|
111
|
+

|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
- Use short lowercase file names without spaces, for example
|
|
115
|
+
`bubble_column_schematic.png`.
|
|
116
|
+
- Prefer PNG for raster figures and SVG only when it is known to render
|
|
117
|
+
correctly in Jupyter, GitHub, VS Code, and the Jupyter Book build.
|
|
118
|
+
- Keep a short italic caption directly below the image when a caption is needed,
|
|
119
|
+
because this renders consistently in ordinary notebook Markdown:
|
|
120
|
+
|
|
121
|
+
```markdown
|
|
122
|
+

|
|
123
|
+
|
|
124
|
+
*Schematic representation of the bubble-column model.*
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## 4. Naming Conventions
|
|
128
|
+
|
|
129
|
+
Use descriptive lowercase names for parameters and lowercase-with-underscores
|
|
130
|
+
for counts and coordinates.
|
|
131
|
+
|
|
132
|
+
### 4.1 Scalars
|
|
133
|
+
|
|
134
|
+
- `length`, `radius`, `dt`, `dz`, `dr`
|
|
135
|
+
- `velocity`, `d_ax`, `d_r`, `k_rxn`, `k_ext`, `u_wall`
|
|
136
|
+
- `t_end`, `maxfev`, `tol`
|
|
137
|
+
|
|
138
|
+
Short symbols are allowed only when they are standard and local:
|
|
139
|
+
|
|
140
|
+
- `v`, `D`, `k`, `T`, `R`
|
|
141
|
+
|
|
142
|
+
### 4.2 Grid and count variables
|
|
143
|
+
|
|
144
|
+
- `n_x`, `n_z`, `n_r`, `n_c`, `n_phase`
|
|
145
|
+
- `x_f`, `x_c`, `z_f`, `z_c`, `r_f`, `r_c`
|
|
146
|
+
|
|
147
|
+
Rule:
|
|
148
|
+
- suffix `_f` means face locations
|
|
149
|
+
- suffix `_c` means cell-center locations
|
|
150
|
+
- prefix `n_` means number of cells or fields
|
|
151
|
+
|
|
152
|
+
### 4.3 State variables
|
|
153
|
+
|
|
154
|
+
- `c` for concentration-only models
|
|
155
|
+
- `T` for temperature-only models
|
|
156
|
+
- `u` for combined state vectors, such as `[c, T]` or multi-field states
|
|
157
|
+
- `c_old`, `u_old` for previous-step states
|
|
158
|
+
|
|
159
|
+
### 4.4 Operator names
|
|
160
|
+
|
|
161
|
+
- `grad_mat`, `grad_bc`
|
|
162
|
+
- `conv_mat`, `conv_bc`
|
|
163
|
+
- `div_mat`
|
|
164
|
+
- `flux_mat`, `flux_bc`
|
|
165
|
+
- `jac_diff`, `jac_conv`, `jac_react`, `jac_accum`, `jac_const`
|
|
166
|
+
- `g_const`
|
|
167
|
+
- `numjac`
|
|
168
|
+
|
|
169
|
+
Do not mix names such as `Grad`, `Flux`, `Jac_const`, `construct_Jac`,
|
|
170
|
+
`init_Jac`, and `jac_const` in the same code base. Standardise on lowercase.
|
|
171
|
+
|
|
172
|
+
### 4.5 Residual naming
|
|
173
|
+
|
|
174
|
+
Prefer `residual(...)` over `g(...)`.
|
|
175
|
+
|
|
176
|
+
Use:
|
|
177
|
+
|
|
178
|
+
- `residual(...)` for the nonlinear algebraic balance
|
|
179
|
+
- `jac` for the Jacobian returned together with the residual
|
|
180
|
+
- `g_const` for the constant contribution to the residual
|
|
181
|
+
|
|
182
|
+
Rationale:
|
|
183
|
+
|
|
184
|
+
1. `residual` is immediately clear to students and readers.
|
|
185
|
+
2. `g` is compact but not descriptive enough for teaching material.
|
|
186
|
+
3. Keeping `g_const` is still fine because it is an internal implementation
|
|
187
|
+
detail rather than the public interface of the model.
|
|
188
|
+
|
|
189
|
+
## 5. Array Shape Conventions
|
|
190
|
+
|
|
191
|
+
Spatial axes come first. Non-spatial axes come last.
|
|
192
|
+
|
|
193
|
+
Preferred layouts:
|
|
194
|
+
|
|
195
|
+
- 1D single field: `(n_x,)`
|
|
196
|
+
- 1D multicomponent: `(n_x, n_c)`
|
|
197
|
+
- 1D two-phase single-component: `(n_z, n_phase, 1)`
|
|
198
|
+
- 1D two-phase multicomponent: `(n_z, n_phase, n_c)`
|
|
199
|
+
- 2D multicomponent: `(n_z, n_r, n_c)` or `(n_x, n_y, n_c)`
|
|
200
|
+
- coupled concentration-temperature field: `(n_r, 2)` or `(n_z, n_r, 2)`
|
|
201
|
+
|
|
202
|
+
Rules:
|
|
203
|
+
|
|
204
|
+
1. Use the same layout everywhere in one model.
|
|
205
|
+
2. Pass the correct `axis` explicitly to `pymrm` operators.
|
|
206
|
+
3. Keep components and phases in the final axes so broadcasting remains clear.
|
|
207
|
+
4. When flattening for linear algebra, only flatten at the residual/Jacobian
|
|
208
|
+
interface.
|
|
209
|
+
|
|
210
|
+
## 6. Boundary Conditions
|
|
211
|
+
|
|
212
|
+
Always use the `pymrm` convention:
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
bc = (
|
|
216
|
+
{"a": ..., "b": ..., "d": ...}, # left / lower boundary
|
|
217
|
+
{"a": ..., "b": ..., "d": ...}, # right / upper boundary
|
|
218
|
+
)
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Interpretation:
|
|
222
|
+
|
|
223
|
+
```text
|
|
224
|
+
a * normal_gradient + b * value = d
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Rules:
|
|
228
|
+
|
|
229
|
+
1. Define `bc` immediately after the grid and before operator assembly.
|
|
230
|
+
2. Add a short comment showing the physical meaning of each boundary.
|
|
231
|
+
3. For multicomponent or multiphase problems, shape `a`, `b`, and `d` so that
|
|
232
|
+
their array structure matches the non-spatial axes.
|
|
233
|
+
4. When using Robin or Danckwerts conditions, write the physical equation in a
|
|
234
|
+
nearby comment or Markdown cell before the dictionary.
|
|
235
|
+
|
|
236
|
+
Example:
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
# Danckwerts inlet: D * dc/dx + v * c = v * c_in
|
|
240
|
+
bc = (
|
|
241
|
+
{"a": d_ax, "b": velocity, "d": velocity * c_in},
|
|
242
|
+
{"a": 1.0, "b": 0.0, "d": 0.0}, # zero outlet gradient
|
|
243
|
+
)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## 7. Preferred Class Structure
|
|
247
|
+
|
|
248
|
+
Use this method order:
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
class ModelName:
|
|
252
|
+
def __init__(self, ...):
|
|
253
|
+
...
|
|
254
|
+
|
|
255
|
+
def _build_grid(self):
|
|
256
|
+
...
|
|
257
|
+
|
|
258
|
+
def _init_state(self, ...):
|
|
259
|
+
...
|
|
260
|
+
|
|
261
|
+
def _build_operators(self):
|
|
262
|
+
...
|
|
263
|
+
|
|
264
|
+
def reaction(self, u):
|
|
265
|
+
...
|
|
266
|
+
|
|
267
|
+
def residual(self, u, u_old=None):
|
|
268
|
+
...
|
|
269
|
+
|
|
270
|
+
def solve(self, ...):
|
|
271
|
+
...
|
|
272
|
+
|
|
273
|
+
def postprocess(self):
|
|
274
|
+
...
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Notes:
|
|
278
|
+
|
|
279
|
+
- Use `_build_grid` and `_build_operators` for one-time setup.
|
|
280
|
+
- Use `reaction(...)` or `source(...)` only for state-dependent physics.
|
|
281
|
+
- Use `residual(...)` as the single source of truth for Newton solves.
|
|
282
|
+
- Treat `g(...)` as legacy naming only when preserving older material.
|
|
283
|
+
- Keep `solve(...)` short. It should orchestrate, not define physics.
|
|
284
|
+
|
|
285
|
+
## 8. Preferred Residual Pattern
|
|
286
|
+
|
|
287
|
+
For implicit time stepping:
|
|
288
|
+
|
|
289
|
+
```python
|
|
290
|
+
def residual(self, u, u_old):
|
|
291
|
+
g_rxn, jac_rxn = self.numjac(self.reaction, u)
|
|
292
|
+
g = (
|
|
293
|
+
self.g_const
|
|
294
|
+
+ self.jac_const @ u.reshape((-1, 1))
|
|
295
|
+
- u_old.reshape((-1, 1)) / self.dt
|
|
296
|
+
- g_rxn.reshape((-1, 1))
|
|
297
|
+
)
|
|
298
|
+
jac = self.jac_const - jac_rxn
|
|
299
|
+
return g, jac
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
For steady problems:
|
|
303
|
+
|
|
304
|
+
```python
|
|
305
|
+
def residual(self, u):
|
|
306
|
+
g_rxn, jac_rxn = self.numjac(self.reaction, u)
|
|
307
|
+
g = self.g_const + self.jac_const @ u.reshape((-1, 1)) - g_rxn.reshape((-1, 1))
|
|
308
|
+
jac = self.jac_const - jac_rxn
|
|
309
|
+
return g, jac
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Rules:
|
|
313
|
+
|
|
314
|
+
1. Return both residual and Jacobian.
|
|
315
|
+
2. Keep the sign convention consistent across the whole model.
|
|
316
|
+
3. Store constant linear pieces in `self.g_const` and `self.jac_const`.
|
|
317
|
+
4. Keep nonlinear source terms outside `self.jac_const`.
|
|
318
|
+
|
|
319
|
+
## 9. Operator Assembly Rules
|
|
320
|
+
|
|
321
|
+
### 9.1 Diffusion
|
|
322
|
+
|
|
323
|
+
Use:
|
|
324
|
+
|
|
325
|
+
```python
|
|
326
|
+
grad_mat, grad_bc = construct_grad(shape, x_f, x_c, bc, axis=axis)
|
|
327
|
+
div_mat = construct_div(shape, x_f, nu=nu, axis=axis)
|
|
328
|
+
flux_mat = -diff_mat @ grad_mat
|
|
329
|
+
flux_bc = -diff_mat @ grad_bc
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### 9.2 Convection
|
|
333
|
+
|
|
334
|
+
Use:
|
|
335
|
+
|
|
336
|
+
```python
|
|
337
|
+
conv_mat, conv_bc = construct_convflux_upwind(shape, x_f, x_c, bc, v=v, axis=axis)
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
If TVD is used, keep the first-order upwind part in the constant Jacobian and
|
|
341
|
+
add the limiter correction separately.
|
|
342
|
+
|
|
343
|
+
### 9.3 Accumulation
|
|
344
|
+
|
|
345
|
+
Use:
|
|
346
|
+
|
|
347
|
+
```python
|
|
348
|
+
jac_accum = eye_array(n_total, format="csc") / dt
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
or `construct_coefficient_matrix(...)` if the accumulation coefficient varies
|
|
352
|
+
per field or per cell.
|
|
353
|
+
|
|
354
|
+
### 9.4 Geometry
|
|
355
|
+
|
|
356
|
+
Use `nu` explicitly:
|
|
357
|
+
|
|
358
|
+
- `nu=0` Cartesian 1D
|
|
359
|
+
- `nu=1` cylindrical radial
|
|
360
|
+
- `nu=2` spherical radial
|
|
361
|
+
|
|
362
|
+
Do not rely on memory for this. Put the geometry in a comment next to the call.
|
|
363
|
+
|
|
364
|
+
## 10. Nonlinear Source Terms
|
|
365
|
+
|
|
366
|
+
### 10.1 Preferred style
|
|
367
|
+
|
|
368
|
+
Define the physical source term separately:
|
|
369
|
+
|
|
370
|
+
```python
|
|
371
|
+
def reaction(self, c):
|
|
372
|
+
r = self.k_rxn * c[..., 0] * c[..., 1]
|
|
373
|
+
f = np.zeros_like(c)
|
|
374
|
+
f[..., 0] = -r
|
|
375
|
+
f[..., 1] = -r
|
|
376
|
+
f[..., 2] = r
|
|
377
|
+
return f
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### 10.2 Stoichiometric form
|
|
381
|
+
|
|
382
|
+
For ODE or reaction-network models, prefer:
|
|
383
|
+
|
|
384
|
+
```python
|
|
385
|
+
rates = ...
|
|
386
|
+
rhs = nu @ rates
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
This is especially useful in L1-L2, where the stoichiometric structure is part
|
|
390
|
+
of the teaching objective.
|
|
391
|
+
|
|
392
|
+
## 11. Solver Rules
|
|
393
|
+
|
|
394
|
+
1. Use `newton(...)` for nonlinear implicit steps and steady-state solves.
|
|
395
|
+
2. Use `NumJac(...)` when an analytical Jacobian is not trivial.
|
|
396
|
+
3. Use `clip_approach(...)` when positivity or boundedness is physically
|
|
397
|
+
required.
|
|
398
|
+
4. For linear steady problems, use `spsolve(...)` directly.
|
|
399
|
+
5. Do not rebuild constant sparse matrices inside the time loop.
|
|
400
|
+
|
|
401
|
+
Preferred `solve()` pattern:
|
|
402
|
+
|
|
403
|
+
```python
|
|
404
|
+
def solve(self, n_steps, callback=None):
|
|
405
|
+
for step in range(n_steps):
|
|
406
|
+
u_old = self.u.copy()
|
|
407
|
+
result = newton(lambda u: self.residual(u, u_old), self.u, maxfev=self.maxfev)
|
|
408
|
+
self.u = result.x.reshape(self.u.shape)
|
|
409
|
+
if callback is not None and step % self.output_interval == 0:
|
|
410
|
+
callback(step, self)
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
## 12. Plotting and Post-Processing
|
|
414
|
+
|
|
415
|
+
Rules:
|
|
416
|
+
|
|
417
|
+
1. Keep plotting outside `reaction(...)` and `residual(...)`.
|
|
418
|
+
2. Use a callback or dedicated plotting method for live demonstrations.
|
|
419
|
+
3. Label every axis with both symbol and unit.
|
|
420
|
+
4. Do not save notebook-generated plots as external files for later inclusion in
|
|
421
|
+
the same notebook. Generate them from code when the notebook is run.
|
|
422
|
+
5. If a plot should be visible in the GitHub preview, keep the executed output
|
|
423
|
+
in the notebook.
|
|
424
|
+
6. For static figures that are not generated by code, use notebook attachments
|
|
425
|
+
with ``.
|
|
426
|
+
7. Compute derived engineering quantities in named methods:
|
|
427
|
+
`effectiveness_factor()`, `apparent_rate()`, `cup_mixing_average()`,
|
|
428
|
+
`conversion()`, `selectivity()`.
|
|
429
|
+
|
|
430
|
+
## 13. Validation Requirements
|
|
431
|
+
|
|
432
|
+
Every model should include at least one of the following:
|
|
433
|
+
|
|
434
|
+
1. Analytical comparison
|
|
435
|
+
2. Limit-case comparison
|
|
436
|
+
3. Conservation check
|
|
437
|
+
4. Grid-independence check
|
|
438
|
+
5. Physical monotonicity or boundedness check
|
|
439
|
+
|
|
440
|
+
Examples:
|
|
441
|
+
|
|
442
|
+
- diffusion-only with homogeneous boundaries gives a flat profile
|
|
443
|
+
- first-order diffusion-reaction matches the analytical hyperbolic solution
|
|
444
|
+
- TVD scheme is sharper than FOU but remains bounded
|
|
445
|
+
- total mass is conserved in a closed reaction system
|
|
446
|
+
- concentration stays non-negative
|
|
447
|
+
|
|
448
|
+
## 14. Anti-Patterns to Avoid
|
|
449
|
+
|
|
450
|
+
Avoid these patterns even if they appear in older exercises.
|
|
451
|
+
|
|
452
|
+
1. Hidden dependence on globals inside physics functions
|
|
453
|
+
Example: `reaction(c)` reading `self.c` or `nx` from outer scope.
|
|
454
|
+
2. Mixed naming styles in one model
|
|
455
|
+
Example: `Jac_const`, `flux_bc`, `construct_Jac`.
|
|
456
|
+
3. Recomputing constant matrices inside every Newton iteration or time step.
|
|
457
|
+
4. Hand-building sparse stencils when `construct_grad`, `construct_div`, or
|
|
458
|
+
`construct_convflux_upwind` already express the same model.
|
|
459
|
+
5. Mixing plotting code into the residual or source-term functions.
|
|
460
|
+
6. Ambiguous state layout
|
|
461
|
+
Example: changing from `(n_x, n_c)` to `(n_c, n_x)` mid-notebook.
|
|
462
|
+
7. Boundary-condition comments that do not match the `{"a","b","d"}`
|
|
463
|
+
dictionary.
|
|
464
|
+
|
|
465
|
+
## 15. Recommended Minimal Template
|
|
466
|
+
|
|
467
|
+
```python
|
|
468
|
+
import numpy as np
|
|
469
|
+
from scipy.sparse import eye_array
|
|
470
|
+
from pymrm import construct_grad, construct_div, NumJac, newton
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
class ModelName:
|
|
474
|
+
def __init__(self):
|
|
475
|
+
# Parameters
|
|
476
|
+
self.length = 1.0
|
|
477
|
+
self.n_x = 100
|
|
478
|
+
self.d_eff = 1.0
|
|
479
|
+
self.k_rxn = 1.0
|
|
480
|
+
self.dt = 0.01
|
|
481
|
+
self.maxfev = 10
|
|
482
|
+
self.output_interval = 10
|
|
483
|
+
|
|
484
|
+
# Grid
|
|
485
|
+
self.x_f = np.linspace(0.0, self.length, self.n_x + 1)
|
|
486
|
+
self.x_c = 0.5 * (self.x_f[:-1] + self.x_f[1:])
|
|
487
|
+
|
|
488
|
+
# Boundary conditions
|
|
489
|
+
self.bc = (
|
|
490
|
+
{"a": 1.0, "b": 0.0, "d": 1.0},
|
|
491
|
+
{"a": 1.0, "b": 0.0, "d": 0.0},
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
# State
|
|
495
|
+
self.u = np.zeros((self.n_x,))
|
|
496
|
+
|
|
497
|
+
# Operators
|
|
498
|
+
self._build_operators()
|
|
499
|
+
|
|
500
|
+
def _build_operators(self):
|
|
501
|
+
grad_mat, grad_bc = construct_grad(self.u.shape, self.x_f, self.x_c, self.bc)
|
|
502
|
+
div_mat = construct_div(self.u.shape, self.x_f, nu=0)
|
|
503
|
+
|
|
504
|
+
flux_mat = -self.d_eff * grad_mat
|
|
505
|
+
flux_bc = -self.d_eff * grad_bc
|
|
506
|
+
|
|
507
|
+
jac_diff = div_mat @ flux_mat
|
|
508
|
+
g_diff_bc = div_mat @ flux_bc
|
|
509
|
+
jac_accum = eye_array(self.n_x, format="csc") / self.dt
|
|
510
|
+
|
|
511
|
+
self.g_const = g_diff_bc
|
|
512
|
+
self.jac_const = jac_accum + jac_diff
|
|
513
|
+
self.numjac = NumJac(self.u.shape)
|
|
514
|
+
|
|
515
|
+
def reaction(self, u):
|
|
516
|
+
return -self.k_rxn * u
|
|
517
|
+
|
|
518
|
+
def residual(self, u, u_old):
|
|
519
|
+
g_rxn, jac_rxn = self.numjac(self.reaction, u)
|
|
520
|
+
g = (
|
|
521
|
+
self.g_const
|
|
522
|
+
+ self.jac_const @ u.reshape((-1, 1))
|
|
523
|
+
- u_old.reshape((-1, 1)) / self.dt
|
|
524
|
+
- g_rxn.reshape((-1, 1))
|
|
525
|
+
)
|
|
526
|
+
jac = self.jac_const - jac_rxn
|
|
527
|
+
return g, jac
|
|
528
|
+
|
|
529
|
+
def solve(self, n_steps):
|
|
530
|
+
for _ in range(n_steps):
|
|
531
|
+
u_old = self.u.copy()
|
|
532
|
+
result = newton(lambda u: self.residual(u, u_old), self.u, maxfev=self.maxfev)
|
|
533
|
+
self.u = result.x.reshape(self.u.shape)
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
## 16. Recommended House Style for Future Material
|
|
537
|
+
|
|
538
|
+
If a new exercise or demo is written today, the preferred house style is:
|
|
539
|
+
|
|
540
|
+
1. Notebook first cell states the model, assumptions, and target quantity.
|
|
541
|
+
2. Code uses the class-based pattern for all PDE or multivariable models.
|
|
542
|
+
3. Array layout keeps spatial axes first and fields last.
|
|
543
|
+
4. Boundary conditions are always written in `{"a","b","d"}` form with a
|
|
544
|
+
matching physical equation.
|
|
545
|
+
5. Constant operators are assembled once.
|
|
546
|
+
6. Nonlinear terms live in `reaction(...)` or `source(...)`.
|
|
547
|
+
7. Residuals are exposed through `residual(...)`.
|
|
548
|
+
8. Plotting is separate from solving.
|
|
549
|
+
9. At least one validation step is shown.
|
|
550
|
+
|
|
551
|
+
This should be the default standard for future `pymrm` exercise solutions and
|
|
552
|
+
class demonstrations.
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import os
|
|
9
9
|
import sys
|
|
10
10
|
from pathlib import Path
|
|
11
|
+
from sphinx.ext.apidoc import main as apidoc_main
|
|
11
12
|
sys.path.insert(0, os.path.abspath('../..')) # this path should point at the root directory
|
|
12
13
|
sys.path.insert(0, str(Path('../..', 'src').resolve())) # patch so we don't need to install the repository for sphinx to work
|
|
13
14
|
|
|
@@ -43,3 +44,13 @@ language = 'en'
|
|
|
43
44
|
|
|
44
45
|
html_theme = 'sphinx_rtd_theme'
|
|
45
46
|
html_static_path = ['_static']
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def run_apidoc(_):
|
|
50
|
+
docs_dir = Path(__file__).resolve().parent
|
|
51
|
+
package_dir = docs_dir.parent.parent / 'src' / 'pymrm'
|
|
52
|
+
apidoc_main(['-f', '-o', str(docs_dir), str(package_dir)])
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def setup(app):
|
|
56
|
+
app.connect('builder-inited', run_apidoc)
|
|
@@ -5,16 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
pip install myst-parser # workaround. Should be included in Docker image?
|
|
7
7
|
pip install -e .
|
|
8
|
-
sphinx-apidoc -f -o docs/sphinx src/pymrm
|
|
9
|
-
if [[ "${CI}" == "true" ]]; then
|
|
10
|
-
api_rst_files=(docs/sphinx/pymrm.rst docs/sphinx/modules.rst)
|
|
11
|
-
if ! git diff --quiet HEAD -- "${api_rst_files[@]}"; then
|
|
12
|
-
echo "Detected outdated autogenerated Sphinx API files:"
|
|
13
|
-
git --no-pager diff HEAD -- "${api_rst_files[@]}"
|
|
14
|
-
echo "Run 'sphinx-apidoc -f -o docs/sphinx src/pymrm' and commit the updated rst files."
|
|
15
|
-
exit 1
|
|
16
|
-
fi
|
|
17
|
-
fi
|
|
18
8
|
cd docs/sphinx
|
|
19
9
|
mkdir -p _static
|
|
20
10
|
make html 2>documentation_errors.txt
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '2.2.
|
|
22
|
-
__version_tuple__ = version_tuple = (2, 2,
|
|
21
|
+
__version__ = version = '2.2.4'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 2, 4)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g9bb3f9b4f'
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"""Nonlinear-solver utilities used by :mod:`pymrm`."""
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
|
+
from scipy import sparse
|
|
4
5
|
from scipy.sparse import linalg
|
|
5
|
-
from scipy.linalg import norm
|
|
6
|
+
from scipy.linalg import norm, solve as dense_solve
|
|
6
7
|
from scipy.optimize import OptimizeResult
|
|
7
8
|
|
|
8
9
|
|
|
@@ -30,9 +31,13 @@ def newton(
|
|
|
30
31
|
Stopping tolerance on the infinity norm of the Newton update.
|
|
31
32
|
maxfev : int, optional
|
|
32
33
|
Maximum number of Newton iterations.
|
|
33
|
-
solver : {'spsolve', 'cg', 'bicgstab'} or callable, optional
|
|
34
|
+
solver : {'spsolve', 'cg', 'bicgstab', 'splu'} or callable, optional
|
|
34
35
|
Linear solver used for each Newton step. If ``None``, the routine picks
|
|
35
36
|
``'spsolve'`` for smaller systems and ``'bicgstab'`` for larger systems.
|
|
37
|
+
When ``'splu'`` is selected, the Jacobian returned by ``function`` is
|
|
38
|
+
expected to be an already-decomposed ``SuperLU`` object (as returned by
|
|
39
|
+
:func:`scipy.sparse.linalg.splu`), and the solve step calls its
|
|
40
|
+
``.solve()`` method directly.
|
|
36
41
|
A callable solver must accept ``(jac_matrix, rhs, **kwargs)`` and return
|
|
37
42
|
the solution vector.
|
|
38
43
|
lin_solver_kwargs : dict, optional
|
|
@@ -64,11 +69,17 @@ def newton(
|
|
|
64
69
|
if solver == "spsolve":
|
|
65
70
|
|
|
66
71
|
def linsolver(jac_matrix, g, **kwargs):
|
|
67
|
-
|
|
72
|
+
if sparse.issparse(jac_matrix):
|
|
73
|
+
return linalg.spsolve(jac_matrix, g, **kwargs)
|
|
74
|
+
return dense_solve(np.asarray(jac_matrix), np.asarray(g), **kwargs)
|
|
68
75
|
|
|
69
76
|
elif solver == "cg":
|
|
70
77
|
|
|
71
78
|
def linsolver(jac_matrix, g, **kwargs):
|
|
79
|
+
if not sparse.issparse(jac_matrix):
|
|
80
|
+
raise ValueError(
|
|
81
|
+
"solver='cg' requires a sparse Jacobian or a custom solver."
|
|
82
|
+
)
|
|
72
83
|
Jac_iLU = linalg.spilu(jac_matrix)
|
|
73
84
|
M = linalg.LinearOperator((n, n), Jac_iLU.solve)
|
|
74
85
|
dx_neg, info = linalg.cg(jac_matrix, g, M=M, **kwargs)
|
|
@@ -79,6 +90,10 @@ def newton(
|
|
|
79
90
|
elif solver == "bicgstab":
|
|
80
91
|
|
|
81
92
|
def linsolver(jac_matrix, g, **kwargs):
|
|
93
|
+
if not sparse.issparse(jac_matrix):
|
|
94
|
+
raise ValueError(
|
|
95
|
+
"solver='bicgstab' requires a sparse Jacobian or a custom solver."
|
|
96
|
+
)
|
|
82
97
|
Jac_iLU = linalg.spilu(jac_matrix)
|
|
83
98
|
M = linalg.LinearOperator((n, n), Jac_iLU.solve)
|
|
84
99
|
dx_neg, info = linalg.bicgstab(jac_matrix, g, M=M, **kwargs)
|
|
@@ -86,6 +101,11 @@ def newton(
|
|
|
86
101
|
raise RuntimeError(f"BICGSTAB did not converge, info={info}")
|
|
87
102
|
return dx_neg
|
|
88
103
|
|
|
104
|
+
elif solver == "splu":
|
|
105
|
+
|
|
106
|
+
def linsolver(jac_matrix, g, **kwargs):
|
|
107
|
+
return jac_matrix.solve(g)
|
|
108
|
+
|
|
89
109
|
elif callable(solver):
|
|
90
110
|
|
|
91
111
|
def linsolver(jac_matrix, g, **kwargs):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pymrm
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.4
|
|
4
4
|
Summary: Functions for multiphase reactor modeling
|
|
5
5
|
Author-email: "E.A.J.F. Peters" <e.a.j.f.peters@tue.nl>, "M. van Sint Annaland" <M.v.SintAnnaland@tue.nl>, "M. Galanti" <m.galanti@tue.nl>, "D.R. Rieder" <d.r.rieder@tue.nl>
|
|
6
6
|
Maintainer-email: "E.A.J.F. Peters" <e.a.j.f.peters@tue.nl>
|
|
@@ -61,7 +61,7 @@ Dynamic: license-file
|
|
|
61
61
|
[](https://pypi.org/project/pymrm/)
|
|
62
62
|
[](https://github.com/computational-chemical-engineering/pymrm/blob/main/LICENSE)
|
|
63
63
|
[](https://github.com/psf/black)
|
|
64
|
-
[](https://zenodo.org/badge/latestdoi/901029258)
|
|
64
|
+
[](https://zenodo.org/badge/latestdoi/901029258)
|
|
65
65
|
|
|
66
66
|
|
|
67
67
|
## Overview
|
|
@@ -99,4 +99,3 @@ coefficient matrices.
|
|
|
99
99
|
## Contributing
|
|
100
100
|
|
|
101
101
|
Contributions are welcome! For suggestions or enhancements, please contact us or submit a pull request.
|
|
102
|
-
|
|
@@ -24,12 +24,12 @@ docs/compile_sphinx.md
|
|
|
24
24
|
docs/dependencies_format.md
|
|
25
25
|
docs/helpers.md
|
|
26
26
|
docs/installation.md
|
|
27
|
+
docs/pymrm-model-style-guide.md
|
|
28
|
+
docs/sphinx/.gitignore
|
|
27
29
|
docs/sphinx/Makefile
|
|
28
30
|
docs/sphinx/conf.py
|
|
29
31
|
docs/sphinx/index.rst
|
|
30
32
|
docs/sphinx/make.bat
|
|
31
|
-
docs/sphinx/modules.rst
|
|
32
|
-
docs/sphinx/pymrm.rst
|
|
33
33
|
examples/counter_current_reaction.ipynb
|
|
34
34
|
examples/counter_diffusion_reaction.ipynb
|
|
35
35
|
examples/darcy_flow.ipynb
|
|
@@ -44,6 +44,17 @@ def test_newton_convergence_result():
|
|
|
44
44
|
assert "Converged" in sol.message
|
|
45
45
|
|
|
46
46
|
|
|
47
|
+
def test_newton_dense_jacobian_default_solver():
|
|
48
|
+
"""Newton should use a dense direct solve for dense Jacobians."""
|
|
49
|
+
|
|
50
|
+
def f_dense(x):
|
|
51
|
+
return np.array([x[0] ** 2 - 2]), np.array([[2 * x[0]]])
|
|
52
|
+
|
|
53
|
+
sol = newton(f_dense, np.array([1.0]))
|
|
54
|
+
assert sol.success
|
|
55
|
+
assert sol.x[0] == pytest.approx(np.sqrt(2), rel=1e-6)
|
|
56
|
+
|
|
57
|
+
|
|
47
58
|
def test_newton_solver_cg():
|
|
48
59
|
"""Newton with CG linear solver should converge."""
|
|
49
60
|
sol = newton(_f_sqrt2, np.array([1.5]), solver="cg")
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"\n",
|
|
93
93
|
"numjac = NumJac(shape) # Numerical Jacobian for reaction terms\n",
|
|
94
94
|
"\n",
|
|
95
|
-
"def
|
|
95
|
+
"def residual(c, c_old):\n",
|
|
96
96
|
" \"\"\"Compute the residual and Jacobian of the system of equations.\"\"\"\n",
|
|
97
97
|
" c_vec = c.reshape((-1, 1)) # Flatten concentration array\n",
|
|
98
98
|
" c_old_vec = c_old.reshape((-1, 1)) # Flatten old concentration array\n",
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
"c_old = np.zeros(shape)\n",
|
|
106
106
|
"\n",
|
|
107
107
|
"# Solve the system using Newton's method\n",
|
|
108
|
-
"result = newton(lambda c:
|
|
108
|
+
"result = newton(lambda c: residual(c, c_old), c_old, tol=1e-6, maxfev=10)\n",
|
|
109
109
|
"c = result.x.reshape(shape) # Reshape solution to original shape\n",
|
|
110
110
|
"\n",
|
|
111
111
|
"# Visualization\n",
|
|
@@ -216,7 +216,7 @@
|
|
|
216
216
|
" r = self.k * c[..., [0]] * c[..., [1]]\n",
|
|
217
217
|
" return r * np.array([[-1.0, -1.0, 1.0]])\n",
|
|
218
218
|
"\n",
|
|
219
|
-
" def
|
|
219
|
+
" def residual(self, c, c_old):\n",
|
|
220
220
|
" \"\"\"\n",
|
|
221
221
|
" Compute the residual vector and Jacobian matrix for the current time step.\n",
|
|
222
222
|
" \n",
|
|
@@ -244,7 +244,7 @@
|
|
|
244
244
|
" c_old = self.c.copy()\n",
|
|
245
245
|
"\n",
|
|
246
246
|
" # Solve the system using Newton's method\n",
|
|
247
|
-
" result = newton(lambda c: self.
|
|
247
|
+
" result = newton(lambda c: self.residual(c, c_old), c_old, maxfev=10)\n",
|
|
248
248
|
" self.c[...] = result.x.reshape((self.c.shape))\n",
|
|
249
249
|
"\n",
|
|
250
250
|
" # Invoke the callback function if provided\n",
|
|
@@ -195,7 +195,7 @@
|
|
|
195
195
|
"numjac = NumJac(shape)\n",
|
|
196
196
|
"\n",
|
|
197
197
|
"# Define the residual function g(c, c_old)\n",
|
|
198
|
-
"def
|
|
198
|
+
"def residual(c, c_old):\n",
|
|
199
199
|
" \"\"\"\n",
|
|
200
200
|
" Function to compute the residual of the system of equations.\n",
|
|
201
201
|
" \"\"\"\n",
|
|
@@ -216,7 +216,7 @@
|
|
|
216
216
|
"c_old = np.zeros(shape)\n",
|
|
217
217
|
"\n",
|
|
218
218
|
"# Solve the nonlinear system using Newton's method\n",
|
|
219
|
-
"result = newton(lambda c:
|
|
219
|
+
"result = newton(lambda c: residual(c, c_old), c_old, tol=1e-6, maxfev=10)\n",
|
|
220
220
|
"\n",
|
|
221
221
|
"# Reshape the solution to the original shape\n",
|
|
222
222
|
"c = result.x.reshape(shape)\n",
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
pymrm package
|
|
2
|
-
=============
|
|
3
|
-
|
|
4
|
-
Submodules
|
|
5
|
-
----------
|
|
6
|
-
|
|
7
|
-
pymrm.convect module
|
|
8
|
-
--------------------
|
|
9
|
-
|
|
10
|
-
.. automodule:: pymrm.convect
|
|
11
|
-
:members:
|
|
12
|
-
:show-inheritance:
|
|
13
|
-
:undoc-members:
|
|
14
|
-
|
|
15
|
-
pymrm.coupling module
|
|
16
|
-
---------------------
|
|
17
|
-
|
|
18
|
-
.. automodule:: pymrm.coupling
|
|
19
|
-
:members:
|
|
20
|
-
:show-inheritance:
|
|
21
|
-
:undoc-members:
|
|
22
|
-
|
|
23
|
-
pymrm.grid module
|
|
24
|
-
-----------------
|
|
25
|
-
|
|
26
|
-
.. automodule:: pymrm.grid
|
|
27
|
-
:members:
|
|
28
|
-
:show-inheritance:
|
|
29
|
-
:undoc-members:
|
|
30
|
-
|
|
31
|
-
pymrm.helpers module
|
|
32
|
-
--------------------
|
|
33
|
-
|
|
34
|
-
.. automodule:: pymrm.helpers
|
|
35
|
-
:members:
|
|
36
|
-
:show-inheritance:
|
|
37
|
-
:undoc-members:
|
|
38
|
-
|
|
39
|
-
pymrm.interpolate module
|
|
40
|
-
------------------------
|
|
41
|
-
|
|
42
|
-
.. automodule:: pymrm.interpolate
|
|
43
|
-
:members:
|
|
44
|
-
:show-inheritance:
|
|
45
|
-
:undoc-members:
|
|
46
|
-
|
|
47
|
-
pymrm.numjac module
|
|
48
|
-
-------------------
|
|
49
|
-
|
|
50
|
-
.. automodule:: pymrm.numjac
|
|
51
|
-
:members:
|
|
52
|
-
:show-inheritance:
|
|
53
|
-
:undoc-members:
|
|
54
|
-
|
|
55
|
-
pymrm.operators module
|
|
56
|
-
----------------------
|
|
57
|
-
|
|
58
|
-
.. automodule:: pymrm.operators
|
|
59
|
-
:members:
|
|
60
|
-
:show-inheritance:
|
|
61
|
-
:undoc-members:
|
|
62
|
-
|
|
63
|
-
pymrm.solve module
|
|
64
|
-
------------------
|
|
65
|
-
|
|
66
|
-
.. automodule:: pymrm.solve
|
|
67
|
-
:members:
|
|
68
|
-
:show-inheritance:
|
|
69
|
-
:undoc-members:
|
|
70
|
-
|
|
71
|
-
Module contents
|
|
72
|
-
---------------
|
|
73
|
-
|
|
74
|
-
.. automodule:: pymrm
|
|
75
|
-
:members:
|
|
76
|
-
:show-inheritance:
|
|
77
|
-
:undoc-members:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|