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.
Files changed (87) hide show
  1. {pymrm-2.2.2 → pymrm-2.2.4}/CITATION.cff +1 -1
  2. {pymrm-2.2.2 → pymrm-2.2.4}/PKG-INFO +2 -3
  3. {pymrm-2.2.2 → pymrm-2.2.4}/README.md +1 -2
  4. {pymrm-2.2.2 → pymrm-2.2.4}/docs/compile_sphinx.md +6 -6
  5. pymrm-2.2.4/docs/pymrm-model-style-guide.md +552 -0
  6. pymrm-2.2.4/docs/sphinx/.gitignore +4 -0
  7. {pymrm-2.2.2 → pymrm-2.2.4}/docs/sphinx/conf.py +11 -0
  8. {pymrm-2.2.2 → pymrm-2.2.4}/scripts/build_documentation.sh +0 -10
  9. {pymrm-2.2.2 → pymrm-2.2.4}/src/pymrm/_version.py +3 -3
  10. {pymrm-2.2.2 → pymrm-2.2.4}/src/pymrm/solve.py +23 -3
  11. {pymrm-2.2.2 → pymrm-2.2.4}/src/pymrm.egg-info/PKG-INFO +2 -3
  12. {pymrm-2.2.2 → pymrm-2.2.4}/src/pymrm.egg-info/SOURCES.txt +2 -2
  13. {pymrm-2.2.2 → pymrm-2.2.4}/test/test_solve.py +11 -0
  14. {pymrm-2.2.2 → pymrm-2.2.4}/tutorials/diffusion_2D.ipynb +2 -2
  15. {pymrm-2.2.2 → pymrm-2.2.4}/tutorials/diffusion_model_class.ipynb +2 -2
  16. {pymrm-2.2.2 → pymrm-2.2.4}/tutorials/nonlinear_kinetics.ipynb +2 -2
  17. pymrm-2.2.2/docs/sphinx/modules.rst +0 -7
  18. pymrm-2.2.2/docs/sphinx/pymrm.rst +0 -77
  19. {pymrm-2.2.2 → pymrm-2.2.4}/.coveragerc +0 -0
  20. {pymrm-2.2.2 → pymrm-2.2.4}/.flake8 +0 -0
  21. {pymrm-2.2.2 → pymrm-2.2.4}/.github/black-formatting.yml +0 -0
  22. {pymrm-2.2.2 → pymrm-2.2.4}/.github/workflows/analysis.yml +0 -0
  23. {pymrm-2.2.2 → pymrm-2.2.4}/.github/workflows/build.yml +0 -0
  24. {pymrm-2.2.2 → pymrm-2.2.4}/.github/workflows/python-publish.yml +0 -0
  25. {pymrm-2.2.2 → pymrm-2.2.4}/.github/workflows/test.yml +0 -0
  26. {pymrm-2.2.2 → pymrm-2.2.4}/.github/workflows/update-citation-cff.yml +0 -0
  27. {pymrm-2.2.2 → pymrm-2.2.4}/.github/workflows/validate-citation-cff.yml +0 -0
  28. {pymrm-2.2.2 → pymrm-2.2.4}/.github/workflows/verification.yml +0 -0
  29. {pymrm-2.2.2 → pymrm-2.2.4}/.gitignore +0 -0
  30. {pymrm-2.2.2 → pymrm-2.2.4}/.gitlab-ci.yml +0 -0
  31. {pymrm-2.2.2 → pymrm-2.2.4}/.vscode/launch_debugpy.json +0 -0
  32. {pymrm-2.2.2 → pymrm-2.2.4}/.vscode/settings_pytest.json +0 -0
  33. {pymrm-2.2.2 → pymrm-2.2.4}/Dockerfile +0 -0
  34. {pymrm-2.2.2 → pymrm-2.2.4}/LICENSE +0 -0
  35. {pymrm-2.2.2 → pymrm-2.2.4}/MANIFEST.in +0 -0
  36. {pymrm-2.2.2 → pymrm-2.2.4}/docs/dependencies_format.md +0 -0
  37. {pymrm-2.2.2 → pymrm-2.2.4}/docs/helpers.md +0 -0
  38. {pymrm-2.2.2 → pymrm-2.2.4}/docs/installation.md +0 -0
  39. {pymrm-2.2.2 → pymrm-2.2.4}/docs/sphinx/Makefile +0 -0
  40. {pymrm-2.2.2 → pymrm-2.2.4}/docs/sphinx/index.rst +0 -0
  41. {pymrm-2.2.2 → pymrm-2.2.4}/docs/sphinx/make.bat +0 -0
  42. {pymrm-2.2.2 → pymrm-2.2.4}/environment.yml +0 -0
  43. {pymrm-2.2.2 → pymrm-2.2.4}/examples/counter_current_reaction.ipynb +0 -0
  44. {pymrm-2.2.2 → pymrm-2.2.4}/examples/counter_diffusion_reaction.ipynb +0 -0
  45. {pymrm-2.2.2 → pymrm-2.2.4}/examples/darcy_flow.ipynb +0 -0
  46. {pymrm-2.2.2 → pymrm-2.2.4}/examples/lid_driven_cavity.ipynb +0 -0
  47. {pymrm-2.2.2 → pymrm-2.2.4}/examples/membrane_module_2D.ipynb +0 -0
  48. {pymrm-2.2.2 → pymrm-2.2.4}/examples/membrane_module_2D_Sievert.ipynb +0 -0
  49. {pymrm-2.2.2 → pymrm-2.2.4}/examples/optimization_example.ipynb +0 -0
  50. {pymrm-2.2.2 → pymrm-2.2.4}/examples/particle_model_effectiveness.ipynb +0 -0
  51. {pymrm-2.2.2 → pymrm-2.2.4}/examples/surface_reaction.ipynb +0 -0
  52. {pymrm-2.2.2 → pymrm-2.2.4}/examples/tvd.ipynb +0 -0
  53. {pymrm-2.2.2 → pymrm-2.2.4}/examples/unsteady_convection_reaction.ipynb +0 -0
  54. {pymrm-2.2.2 → pymrm-2.2.4}/examples/unsteady_diffusion_reaction_2D.ipynb +0 -0
  55. {pymrm-2.2.2 → pymrm-2.2.4}/pyproject.toml +0 -0
  56. {pymrm-2.2.2 → pymrm-2.2.4}/requirements.txt +0 -0
  57. {pymrm-2.2.2 → pymrm-2.2.4}/scripts/run_examples.py +0 -0
  58. {pymrm-2.2.2 → pymrm-2.2.4}/scripts/run_exercises.py +0 -0
  59. {pymrm-2.2.2 → pymrm-2.2.4}/scripts/run_linter.sh +0 -0
  60. {pymrm-2.2.2 → pymrm-2.2.4}/scripts/run_notebook.py +0 -0
  61. {pymrm-2.2.2 → pymrm-2.2.4}/scripts/run_tutorials.py +0 -0
  62. {pymrm-2.2.2 → pymrm-2.2.4}/scripts/run_unit_tests.sh +0 -0
  63. {pymrm-2.2.2 → pymrm-2.2.4}/scripts/split_notebook.py +0 -0
  64. {pymrm-2.2.2 → pymrm-2.2.4}/setup.cfg +0 -0
  65. {pymrm-2.2.2 → pymrm-2.2.4}/src/pymrm/__init__.py +0 -0
  66. {pymrm-2.2.2 → pymrm-2.2.4}/src/pymrm/convect.py +0 -0
  67. {pymrm-2.2.2 → pymrm-2.2.4}/src/pymrm/coupling.py +0 -0
  68. {pymrm-2.2.2 → pymrm-2.2.4}/src/pymrm/grid.py +0 -0
  69. {pymrm-2.2.2 → pymrm-2.2.4}/src/pymrm/helpers.py +0 -0
  70. {pymrm-2.2.2 → pymrm-2.2.4}/src/pymrm/interpolate.py +0 -0
  71. {pymrm-2.2.2 → pymrm-2.2.4}/src/pymrm/numjac.py +0 -0
  72. {pymrm-2.2.2 → pymrm-2.2.4}/src/pymrm/operators.py +0 -0
  73. {pymrm-2.2.2 → pymrm-2.2.4}/src/pymrm.egg-info/dependency_links.txt +0 -0
  74. {pymrm-2.2.2 → pymrm-2.2.4}/src/pymrm.egg-info/requires.txt +0 -0
  75. {pymrm-2.2.2 → pymrm-2.2.4}/src/pymrm.egg-info/top_level.txt +0 -0
  76. {pymrm-2.2.2 → pymrm-2.2.4}/test/test_convect.py +0 -0
  77. {pymrm-2.2.2 → pymrm-2.2.4}/test/test_coupling.py +0 -0
  78. {pymrm-2.2.2 → pymrm-2.2.4}/test/test_csr_format.py +0 -0
  79. {pymrm-2.2.2 → pymrm-2.2.4}/test/test_grid.py +0 -0
  80. {pymrm-2.2.2 → pymrm-2.2.4}/test/test_helpers.py +0 -0
  81. {pymrm-2.2.2 → pymrm-2.2.4}/test/test_integration.py +0 -0
  82. {pymrm-2.2.2 → pymrm-2.2.4}/test/test_interpolate.py +0 -0
  83. {pymrm-2.2.2 → pymrm-2.2.4}/test/test_numjac.py +0 -0
  84. {pymrm-2.2.2 → pymrm-2.2.4}/test/test_operators.py +0 -0
  85. {pymrm-2.2.2 → pymrm-2.2.4}/tutorials/diffusion_first_order_kinetics.ipynb +0 -0
  86. {pymrm-2.2.2 → pymrm-2.2.4}/tutorials/introduction_and_start.ipynb +0 -0
  87. {pymrm-2.2.2 → pymrm-2.2.4}/tutorials/stationary_diffusion.ipynb +0 -0
@@ -1,6 +1,6 @@
1
1
  cff-version: 1.2.0
2
2
  title: "pymrm: Python package for Multiphase Reactor Modeling"
3
- version: "2.2.1"
3
+ version: "2.2.3"
4
4
  message: "If you use pymrm in your work, please cite the version you used."
5
5
  authors:
6
6
  - family-names: Peters
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pymrm
3
- Version: 2.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
  [![Python](https://img.shields.io/pypi/pyversions/pymrm.svg)](https://pypi.org/project/pymrm/)
62
62
  [![License](https://img.shields.io/github/license/computational-chemical-engineering/pymrm)](https://github.com/computational-chemical-engineering/pymrm/blob/main/LICENSE)
63
63
  [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
64
- [![DOI](https://zenodo.org/badge/901029258.svg)](https://zenodo.org/badge/latestdoi/901029258)
64
+ [![DOI](https://zenodo.org/badge/latestdoi/901029258.svg)](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
  [![Python](https://img.shields.io/pypi/pyversions/pymrm.svg)](https://pypi.org/project/pymrm/)
6
6
  [![License](https://img.shields.io/github/license/computational-chemical-engineering/pymrm)](https://github.com/computational-chemical-engineering/pymrm/blob/main/LICENSE)
7
7
  [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
8
- [![DOI](https://zenodo.org/badge/901029258.svg)](https://zenodo.org/badge/latestdoi/901029258)
8
+ [![DOI](https://zenodo.org/badge/latestdoi/901029258.svg)](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
- Then run `sphinx-apidoc` in the root directory
59
- ```bash
60
- sphinx-apidoc -o docs/sphinx src/pymrm/
61
- ```
62
- Following that, at the generated modules.rst to the index.rst in the specified folder
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
+ ![My figure](attachment:my_figure.png)
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
+ ![Bubble-column schematic](attachment:bubble_column_schematic.png)
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 `![My figure](attachment:my_figure.png)`.
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.
@@ -0,0 +1,4 @@
1
+ modules.rst
2
+ pymrm.rst
3
+ _build/
4
+ documentation_errors.txt
@@ -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.2'
22
- __version_tuple__ = version_tuple = (2, 2, 2)
21
+ __version__ = version = '2.2.4'
22
+ __version_tuple__ = version_tuple = (2, 2, 4)
23
23
 
24
- __commit_id__ = commit_id = 'g471977efe'
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
- return linalg.spsolve(jac_matrix, g, **kwargs)
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.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
  [![Python](https://img.shields.io/pypi/pyversions/pymrm.svg)](https://pypi.org/project/pymrm/)
62
62
  [![License](https://img.shields.io/github/license/computational-chemical-engineering/pymrm)](https://github.com/computational-chemical-engineering/pymrm/blob/main/LICENSE)
63
63
  [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
64
- [![DOI](https://zenodo.org/badge/901029258.svg)](https://zenodo.org/badge/latestdoi/901029258)
64
+ [![DOI](https://zenodo.org/badge/latestdoi/901029258.svg)](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 g(c, c_old):\n",
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: g(c, c_old), c_old, tol=1e-6, maxfev=10)\n",
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 g(self, c, c_old):\n",
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.g(c, c_old), c_old, maxfev=10)\n",
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 g(c, c_old):\n",
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: g(c, c_old), c_old, tol=1e-6, maxfev=10)\n",
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,7 +0,0 @@
1
- pymrm
2
- =====
3
-
4
- .. toctree::
5
- :maxdepth: 4
6
-
7
- pymrm
@@ -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