foamlib 0.9.4__tar.gz → 0.9.6__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 (67) hide show
  1. {foamlib-0.9.4 → foamlib-0.9.6}/PKG-INFO +3 -142
  2. {foamlib-0.9.4 → foamlib-0.9.6}/README.md +2 -141
  3. {foamlib-0.9.4 → foamlib-0.9.6}/docs/conf.py +1 -1
  4. foamlib-0.9.6/docs/example.rst +144 -0
  5. foamlib-0.9.6/docs/index.rst +25 -0
  6. {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/__init__.py +1 -1
  7. {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/_cases/_async.py +43 -31
  8. {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/_cases/_base.py +8 -8
  9. {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/_cases/_run.py +2 -2
  10. {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/_cases/_slurm.py +9 -9
  11. {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/_cases/_sync.py +46 -35
  12. {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/_files/_files.py +35 -29
  13. {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/_files/_parsing.py +6 -3
  14. {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/_files/_serialization.py +9 -6
  15. {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/_files/_types.py +20 -7
  16. {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_files/test_dumps.py +4 -0
  17. {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_files/test_parsing/test_loads.py +3 -0
  18. foamlib-0.9.4/docs/index.rst +0 -16
  19. {foamlib-0.9.4 → foamlib-0.9.6}/.devcontainer.json +0 -0
  20. {foamlib-0.9.4 → foamlib-0.9.6}/.dockerignore +0 -0
  21. {foamlib-0.9.4 → foamlib-0.9.6}/.git-blame-ignore-revs +0 -0
  22. {foamlib-0.9.4 → foamlib-0.9.6}/.github/dependabot.yml +0 -0
  23. {foamlib-0.9.4 → foamlib-0.9.6}/.github/workflows/ci.yml +0 -0
  24. {foamlib-0.9.4 → foamlib-0.9.6}/.github/workflows/docker.yml +0 -0
  25. {foamlib-0.9.4 → foamlib-0.9.6}/.github/workflows/dockerhub-description.yml +0 -0
  26. {foamlib-0.9.4 → foamlib-0.9.6}/.github/workflows/pypi-publish.yml +0 -0
  27. {foamlib-0.9.4 → foamlib-0.9.6}/.gitignore +0 -0
  28. {foamlib-0.9.4 → foamlib-0.9.6}/.readthedocs.yaml +0 -0
  29. {foamlib-0.9.4 → foamlib-0.9.6}/CONTRIBUTING.md +0 -0
  30. {foamlib-0.9.4 → foamlib-0.9.6}/Dockerfile +0 -0
  31. {foamlib-0.9.4 → foamlib-0.9.6}/LICENSE.txt +0 -0
  32. {foamlib-0.9.4 → foamlib-0.9.6}/benchmark/benchmark.png +0 -0
  33. {foamlib-0.9.4 → foamlib-0.9.6}/benchmark/benchmark.py +0 -0
  34. {foamlib-0.9.4 → foamlib-0.9.6}/benchmark/requirements.txt +0 -0
  35. {foamlib-0.9.4 → foamlib-0.9.6}/benchmark/ruff.toml +0 -0
  36. {foamlib-0.9.4 → foamlib-0.9.6}/docs/Makefile +0 -0
  37. {foamlib-0.9.4 → foamlib-0.9.6}/docs/cases.rst +0 -0
  38. {foamlib-0.9.4 → foamlib-0.9.6}/docs/files.rst +0 -0
  39. {foamlib-0.9.4 → foamlib-0.9.6}/docs/make.bat +0 -0
  40. {foamlib-0.9.4 → foamlib-0.9.6}/docs/ruff.toml +0 -0
  41. {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/_cases/__init__.py +0 -0
  42. {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/_cases/_subprocess.py +0 -0
  43. {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/_cases/_util.py +0 -0
  44. {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/_files/__init__.py +0 -0
  45. {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/_files/_io.py +0 -0
  46. {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/py.typed +0 -0
  47. {foamlib-0.9.4 → foamlib-0.9.6}/logo.png +0 -0
  48. {foamlib-0.9.4 → foamlib-0.9.6}/pyproject.toml +0 -0
  49. {foamlib-0.9.4 → foamlib-0.9.6}/tests/__init__.py +0 -0
  50. {foamlib-0.9.4 → foamlib-0.9.6}/tests/ruff.toml +0 -0
  51. {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_cases/__init__.py +0 -0
  52. {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_cases/test_cavity.py +0 -0
  53. {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_cases/test_cavity_async.py +0 -0
  54. {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_cases/test_flange.py +0 -0
  55. {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_cases/test_flange_async.py +0 -0
  56. {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_example.py +0 -0
  57. {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_files/__init__.py +0 -0
  58. {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_files/test_files.py +0 -0
  59. {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_files/test_parsing/__init__.py +0 -0
  60. {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_files/test_parsing/test_advanced.py +0 -0
  61. {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_files/test_parsing/test_basic.py +0 -0
  62. {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_files/test_parsing/test_decompose_par.py +0 -0
  63. {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_files/test_parsing/test_fields.py +0 -0
  64. {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_files/test_parsing/test_fv_schemes.py +0 -0
  65. {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_files/test_parsing/test_intermediate.py +0 -0
  66. {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_files/test_parsing/test_poly_mesh.py +0 -0
  67. {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_files/test_types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: foamlib
3
- Version: 0.9.4
3
+ Version: 0.9.6
4
4
  Summary: A Python interface for interacting with OpenFOAM
5
5
  Project-URL: Homepage, https://github.com/gerlero/foamlib
6
6
  Project-URL: Repository, https://github.com/gerlero/foamlib
@@ -196,148 +196,9 @@ case = FoamCase(Path(__file__).parent)
196
196
  case.run()
197
197
  ```
198
198
 
199
- ## ▶️ A complete example
199
+ ## 📘 Documentation
200
200
 
201
- The following is a fully self-contained example that demonstrates how to create an OpenFOAM case from scratch, run it, and analyze the results.
202
-
203
- <details>
204
-
205
- <summary>Example</summary>
206
-
207
- ```python
208
- #!/usr/bin/env python3
209
- """Check the diffusion of a scalar field in a scalarTransportFoam case."""
210
-
211
- import shutil
212
- from pathlib import Path
213
-
214
- import numpy as np
215
- from scipy.special import erfc
216
- from foamlib import FoamCase
217
-
218
- path = Path(__file__).parent / "diffusionCheck"
219
- shutil.rmtree(path, ignore_errors=True)
220
- path.mkdir(parents=True)
221
- (path / "system").mkdir()
222
- (path / "constant").mkdir()
223
- (path / "0").mkdir()
224
-
225
- case = FoamCase(path)
226
-
227
- with case.control_dict as f:
228
- f["application"] = "scalarTransportFoam"
229
- f["startFrom"] = "latestTime"
230
- f["stopAt"] = "endTime"
231
- f["endTime"] = 5
232
- f["deltaT"] = 1e-3
233
- f["writeControl"] = "adjustableRunTime"
234
- f["writeInterval"] = 1
235
- f["purgeWrite"] = 0
236
- f["writeFormat"] = "ascii"
237
- f["writePrecision"] = 6
238
- f["writeCompression"] = False
239
- f["timeFormat"] = "general"
240
- f["timePrecision"] = 6
241
- f["adjustTimeStep"] = False
242
- f["runTimeModifiable"] = False
243
-
244
- with case.fv_schemes as f:
245
- f["ddtSchemes"] = {"default": "Euler"}
246
- f["gradSchemes"] = {"default": "Gauss linear"}
247
- f["divSchemes"] = {"default": "none", "div(phi,U)": "Gauss linear", "div(phi,T)": "Gauss linear"}
248
- f["laplacianSchemes"] = {"default": "Gauss linear corrected"}
249
-
250
- with case.fv_solution as f:
251
- f["solvers"] = {"T": {"solver": "PBiCG", "preconditioner": "DILU", "tolerance": 1e-6, "relTol": 0}}
252
-
253
- with case.block_mesh_dict as f:
254
- f["scale"] = 1
255
- f["vertices"] = [
256
- [0, 0, 0],
257
- [1, 0, 0],
258
- [1, 0.5, 0],
259
- [1, 1, 0],
260
- [0, 1, 0],
261
- [0, 0.5, 0],
262
- [0, 0, 0.1],
263
- [1, 0, 0.1],
264
- [1, 0.5, 0.1],
265
- [1, 1, 0.1],
266
- [0, 1, 0.1],
267
- [0, 0.5, 0.1],
268
- ]
269
- f["blocks"] = [
270
- "hex", [0, 1, 2, 5, 6, 7, 8, 11], [400, 20, 1], "simpleGrading", [1, 1, 1],
271
- "hex", [5, 2, 3, 4, 11, 8, 9, 10], [400, 20, 1], "simpleGrading", [1, 1, 1],
272
- ]
273
- f["edges"] = []
274
- f["boundary"] = [
275
- ("inletUp", {"type": "patch", "faces": [[5, 4, 10, 11]]}),
276
- ("inletDown", {"type": "patch", "faces": [[0, 5, 11, 6]]}),
277
- ("outletUp", {"type": "patch", "faces": [[2, 3, 9, 8]]}),
278
- ("outletDown", {"type": "patch", "faces": [[1, 2, 8, 7]]}),
279
- ("walls", {"type": "wall", "faces": [[4, 3, 9, 10], [0, 1, 7, 6]]}),
280
- ("frontAndBack", {"type": "empty", "faces": [[0, 1, 2, 5], [5, 2, 3, 4], [6, 7, 8, 11], [11, 8, 9, 10]]}),
281
- ]
282
- f["mergePatchPairs"] = []
283
-
284
- with case.transport_properties as f:
285
- f["DT"] = f.Dimensioned(1e-3, f.DimensionSet(length=2, time=-1), "DT")
286
-
287
- with case[0]["U"] as f:
288
- f.dimensions = f.DimensionSet(length=1, time=-1)
289
- f.internal_field = [1, 0, 0]
290
- f.boundary_field = {
291
- "inletUp": {"type": "fixedValue", "value": [1, 0, 0]},
292
- "inletDown": {"type": "fixedValue", "value": [1, 0, 0]},
293
- "outletUp": {"type": "zeroGradient"},
294
- "outletDown": {"type": "zeroGradient"},
295
- "walls": {"type": "zeroGradient"},
296
- "frontAndBack": {"type": "empty"},
297
- }
298
-
299
- with case[0]["T"] as f:
300
- f.dimensions = f.DimensionSet(temperature=1)
301
- f.internal_field = 0
302
- f.boundary_field = {
303
- "inletUp": {"type": "fixedValue", "value": 0},
304
- "inletDown": {"type": "fixedValue", "value": 1},
305
- "outletUp": {"type": "zeroGradient"},
306
- "outletDown": {"type": "zeroGradient"},
307
- "walls": {"type": "zeroGradient"},
308
- "frontAndBack": {"type": "empty"},
309
- }
310
-
311
- case.run()
312
-
313
- x, y, z = case[0].cell_centers().internal_field.T
314
-
315
- end = x == x.max()
316
- x = x[end]
317
- y = y[end]
318
- z = z[end]
319
-
320
- DT = case.transport_properties["DT"].value
321
- U = case[0]["U"].internal_field[0]
322
-
323
- for time in case[1:]:
324
- if U*time.time < 2*x.max():
325
- continue
326
-
327
- T = time["T"].internal_field[end]
328
- analytical = 0.5 * erfc((y - 0.5) / np.sqrt(4 * DT * x/U))
329
- if np.allclose(T, analytical, atol=0.1):
330
- print(f"Time {time.time}: OK")
331
- else:
332
- raise RuntimeError(f"Time {time.time}: {T} != {analytical}")
333
- ```
334
-
335
- </details>
336
-
337
-
338
- ## 📘 API documentation
339
-
340
- For more information on how to use **foamlibs**'s classes and methods, check out the [documentation](https://foamlib.readthedocs.io/).
201
+ For details on how to use **foamlib**, check out the [documentation](https://foamlib.readthedocs.io/).
341
202
 
342
203
  ## 🙋 Support
343
204
 
@@ -158,148 +158,9 @@ case = FoamCase(Path(__file__).parent)
158
158
  case.run()
159
159
  ```
160
160
 
161
- ## ▶️ A complete example
161
+ ## 📘 Documentation
162
162
 
163
- The following is a fully self-contained example that demonstrates how to create an OpenFOAM case from scratch, run it, and analyze the results.
164
-
165
- <details>
166
-
167
- <summary>Example</summary>
168
-
169
- ```python
170
- #!/usr/bin/env python3
171
- """Check the diffusion of a scalar field in a scalarTransportFoam case."""
172
-
173
- import shutil
174
- from pathlib import Path
175
-
176
- import numpy as np
177
- from scipy.special import erfc
178
- from foamlib import FoamCase
179
-
180
- path = Path(__file__).parent / "diffusionCheck"
181
- shutil.rmtree(path, ignore_errors=True)
182
- path.mkdir(parents=True)
183
- (path / "system").mkdir()
184
- (path / "constant").mkdir()
185
- (path / "0").mkdir()
186
-
187
- case = FoamCase(path)
188
-
189
- with case.control_dict as f:
190
- f["application"] = "scalarTransportFoam"
191
- f["startFrom"] = "latestTime"
192
- f["stopAt"] = "endTime"
193
- f["endTime"] = 5
194
- f["deltaT"] = 1e-3
195
- f["writeControl"] = "adjustableRunTime"
196
- f["writeInterval"] = 1
197
- f["purgeWrite"] = 0
198
- f["writeFormat"] = "ascii"
199
- f["writePrecision"] = 6
200
- f["writeCompression"] = False
201
- f["timeFormat"] = "general"
202
- f["timePrecision"] = 6
203
- f["adjustTimeStep"] = False
204
- f["runTimeModifiable"] = False
205
-
206
- with case.fv_schemes as f:
207
- f["ddtSchemes"] = {"default": "Euler"}
208
- f["gradSchemes"] = {"default": "Gauss linear"}
209
- f["divSchemes"] = {"default": "none", "div(phi,U)": "Gauss linear", "div(phi,T)": "Gauss linear"}
210
- f["laplacianSchemes"] = {"default": "Gauss linear corrected"}
211
-
212
- with case.fv_solution as f:
213
- f["solvers"] = {"T": {"solver": "PBiCG", "preconditioner": "DILU", "tolerance": 1e-6, "relTol": 0}}
214
-
215
- with case.block_mesh_dict as f:
216
- f["scale"] = 1
217
- f["vertices"] = [
218
- [0, 0, 0],
219
- [1, 0, 0],
220
- [1, 0.5, 0],
221
- [1, 1, 0],
222
- [0, 1, 0],
223
- [0, 0.5, 0],
224
- [0, 0, 0.1],
225
- [1, 0, 0.1],
226
- [1, 0.5, 0.1],
227
- [1, 1, 0.1],
228
- [0, 1, 0.1],
229
- [0, 0.5, 0.1],
230
- ]
231
- f["blocks"] = [
232
- "hex", [0, 1, 2, 5, 6, 7, 8, 11], [400, 20, 1], "simpleGrading", [1, 1, 1],
233
- "hex", [5, 2, 3, 4, 11, 8, 9, 10], [400, 20, 1], "simpleGrading", [1, 1, 1],
234
- ]
235
- f["edges"] = []
236
- f["boundary"] = [
237
- ("inletUp", {"type": "patch", "faces": [[5, 4, 10, 11]]}),
238
- ("inletDown", {"type": "patch", "faces": [[0, 5, 11, 6]]}),
239
- ("outletUp", {"type": "patch", "faces": [[2, 3, 9, 8]]}),
240
- ("outletDown", {"type": "patch", "faces": [[1, 2, 8, 7]]}),
241
- ("walls", {"type": "wall", "faces": [[4, 3, 9, 10], [0, 1, 7, 6]]}),
242
- ("frontAndBack", {"type": "empty", "faces": [[0, 1, 2, 5], [5, 2, 3, 4], [6, 7, 8, 11], [11, 8, 9, 10]]}),
243
- ]
244
- f["mergePatchPairs"] = []
245
-
246
- with case.transport_properties as f:
247
- f["DT"] = f.Dimensioned(1e-3, f.DimensionSet(length=2, time=-1), "DT")
248
-
249
- with case[0]["U"] as f:
250
- f.dimensions = f.DimensionSet(length=1, time=-1)
251
- f.internal_field = [1, 0, 0]
252
- f.boundary_field = {
253
- "inletUp": {"type": "fixedValue", "value": [1, 0, 0]},
254
- "inletDown": {"type": "fixedValue", "value": [1, 0, 0]},
255
- "outletUp": {"type": "zeroGradient"},
256
- "outletDown": {"type": "zeroGradient"},
257
- "walls": {"type": "zeroGradient"},
258
- "frontAndBack": {"type": "empty"},
259
- }
260
-
261
- with case[0]["T"] as f:
262
- f.dimensions = f.DimensionSet(temperature=1)
263
- f.internal_field = 0
264
- f.boundary_field = {
265
- "inletUp": {"type": "fixedValue", "value": 0},
266
- "inletDown": {"type": "fixedValue", "value": 1},
267
- "outletUp": {"type": "zeroGradient"},
268
- "outletDown": {"type": "zeroGradient"},
269
- "walls": {"type": "zeroGradient"},
270
- "frontAndBack": {"type": "empty"},
271
- }
272
-
273
- case.run()
274
-
275
- x, y, z = case[0].cell_centers().internal_field.T
276
-
277
- end = x == x.max()
278
- x = x[end]
279
- y = y[end]
280
- z = z[end]
281
-
282
- DT = case.transport_properties["DT"].value
283
- U = case[0]["U"].internal_field[0]
284
-
285
- for time in case[1:]:
286
- if U*time.time < 2*x.max():
287
- continue
288
-
289
- T = time["T"].internal_field[end]
290
- analytical = 0.5 * erfc((y - 0.5) / np.sqrt(4 * DT * x/U))
291
- if np.allclose(T, analytical, atol=0.1):
292
- print(f"Time {time.time}: OK")
293
- else:
294
- raise RuntimeError(f"Time {time.time}: {T} != {analytical}")
295
- ```
296
-
297
- </details>
298
-
299
-
300
- ## 📘 API documentation
301
-
302
- For more information on how to use **foamlibs**'s classes and methods, check out the [documentation](https://foamlib.readthedocs.io/).
163
+ For details on how to use **foamlib**, check out the [documentation](https://foamlib.readthedocs.io/).
303
164
 
304
165
  ## 🙋 Support
305
166
 
@@ -7,7 +7,7 @@
7
7
  # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
8
8
 
9
9
  project = "foamlib"
10
- copyright = "2024, Gabriel S. Gerlero"
10
+ copyright = "2024-2025, Gabriel S. Gerlero"
11
11
  author = "Gabriel S. Gerlero"
12
12
 
13
13
  # -- General configuration ---------------------------------------------------
@@ -0,0 +1,144 @@
1
+ Example
2
+ =======
3
+
4
+ This example script sets up and runs a validation test case for the ``scalarTransportFoam`` solver, verifying the diffusion of a scalar field in a simplified 2D domain.
5
+
6
+ Overview
7
+ --------
8
+
9
+ - Creates a clean OpenFOAM case in the ``diffusionCheck`` subdirectory.
10
+ - Configures mesh geometry, solver settings, and initial/boundary conditions for scalar (``T``) and velocity (``U``) fields.
11
+ - Simulates a velocity-driven scalar transport where a temperature gradient is imposed across the inlet.
12
+ - Uses :class:`foamlib.FoamCase` and related utilities to manage OpenFOAM input files and execution.
13
+ - Computes an analytical solution using the complementary error function (:func:`scipy.special.erfc`) and compares it against numerical results.
14
+
15
+ Code
16
+ ----
17
+
18
+ .. code-block:: python
19
+
20
+ #!/usr/bin/env python3
21
+ """Check the diffusion of a scalar field in a scalarTransportFoam case."""
22
+
23
+ import shutil
24
+ from pathlib import Path
25
+
26
+ import numpy as np
27
+ from scipy.special import erfc
28
+ from foamlib import FoamCase, FoamFile
29
+
30
+ path = Path(__file__).parent / "diffusionCheck"
31
+ shutil.rmtree(path, ignore_errors=True)
32
+ path.mkdir(parents=True)
33
+ (path / "system").mkdir()
34
+ (path / "constant").mkdir()
35
+ (path / "0").mkdir()
36
+
37
+ case = FoamCase(path)
38
+
39
+ with case.control_dict as f:
40
+ f["application"] = "scalarTransportFoam"
41
+ f["startFrom"] = "latestTime"
42
+ f["stopAt"] = "endTime"
43
+ f["endTime"] = 5
44
+ f["deltaT"] = 1e-3
45
+ f["writeControl"] = "adjustableRunTime"
46
+ f["writeInterval"] = 1
47
+ f["purgeWrite"] = 0
48
+ f["writeFormat"] = "ascii"
49
+ f["writePrecision"] = 6
50
+ f["writeCompression"] = False
51
+ f["timeFormat"] = "general"
52
+ f["timePrecision"] = 6
53
+ f["adjustTimeStep"] = False
54
+ f["runTimeModifiable"] = False
55
+
56
+ with case.fv_schemes as f:
57
+ f["ddtSchemes"] = {"default": "Euler"}
58
+ f["gradSchemes"] = {"default": "Gauss linear"}
59
+ f["divSchemes"] = {"default": "none", "div(phi,U)": "Gauss linear", "div(phi,T)": "Gauss linear"}
60
+ f["laplacianSchemes"] = {"default": "Gauss linear corrected"}
61
+
62
+ with case.fv_solution as f:
63
+ f["solvers"] = {"T": {"solver": "PBiCG", "preconditioner": "DILU", "tolerance": 1e-6, "relTol": 0}}
64
+
65
+ with case.block_mesh_dict as f:
66
+ f["scale"] = 1
67
+ f["vertices"] = [
68
+ [0, 0, 0],
69
+ [1, 0, 0],
70
+ [1, 0.5, 0],
71
+ [1, 1, 0],
72
+ [0, 1, 0],
73
+ [0, 0.5, 0],
74
+ [0, 0, 0.1],
75
+ [1, 0, 0.1],
76
+ [1, 0.5, 0.1],
77
+ [1, 1, 0.1],
78
+ [0, 1, 0.1],
79
+ [0, 0.5, 0.1],
80
+ ]
81
+ f["blocks"] = [
82
+ "hex", [0, 1, 2, 5, 6, 7, 8, 11], [400, 20, 1], "simpleGrading", [1, 1, 1],
83
+ "hex", [5, 2, 3, 4, 11, 8, 9, 10], [400, 20, 1], "simpleGrading", [1, 1, 1],
84
+ ]
85
+ f["edges"] = []
86
+ f["boundary"] = [
87
+ ("inletUp", {"type": "patch", "faces": [[5, 4, 10, 11]]}),
88
+ ("inletDown", {"type": "patch", "faces": [[0, 5, 11, 6]]}),
89
+ ("outletUp", {"type": "patch", "faces": [[2, 3, 9, 8]]}),
90
+ ("outletDown", {"type": "patch", "faces": [[1, 2, 8, 7]]}),
91
+ ("walls", {"type": "wall", "faces": [[4, 3, 9, 10], [0, 1, 7, 6]]}),
92
+ ("frontAndBack", {"type": "empty", "faces": [[0, 1, 2, 5], [5, 2, 3, 4], [6, 7, 8, 11], [11, 8, 9, 10]]}),
93
+ ]
94
+ f["mergePatchPairs"] = []
95
+
96
+ with case.transport_properties as f:
97
+ f["DT"] = FoamFile.Dimensioned(1e-3, f.DimensionSet(length=2, time=-1), "DT")
98
+
99
+ with case[0]["U"] as f:
100
+ f.dimensions = FoamFile.DimensionSet(length=1, time=-1)
101
+ f.internal_field = [1, 0, 0]
102
+ f.boundary_field = {
103
+ "inletUp": {"type": "fixedValue", "value": [1, 0, 0]},
104
+ "inletDown": {"type": "fixedValue", "value": [1, 0, 0]},
105
+ "outletUp": {"type": "zeroGradient"},
106
+ "outletDown": {"type": "zeroGradient"},
107
+ "walls": {"type": "zeroGradient"},
108
+ "frontAndBack": {"type": "empty"},
109
+ }
110
+
111
+ with case[0]["T"] as f:
112
+ f.dimensions = FoamFile.DimensionSet(temperature=1)
113
+ f.internal_field = 0
114
+ f.boundary_field = {
115
+ "inletUp": {"type": "fixedValue", "value": 0},
116
+ "inletDown": {"type": "fixedValue", "value": 1},
117
+ "outletUp": {"type": "zeroGradient"},
118
+ "outletDown": {"type": "zeroGradient"},
119
+ "walls": {"type": "zeroGradient"},
120
+ "frontAndBack": {"type": "empty"},
121
+ }
122
+
123
+ case.run()
124
+
125
+ x, y, z = case[0].cell_centers().internal_field.T
126
+
127
+ end = x == x.max()
128
+ x = x[end]
129
+ y = y[end]
130
+ z = z[end]
131
+
132
+ DT = case.transport_properties["DT"].value
133
+ U = case[0]["U"].internal_field[0]
134
+
135
+ for time in case[1:]:
136
+ if U*time.time < 2*x.max():
137
+ continue
138
+
139
+ T = time["T"].internal_field[end]
140
+ analytical = 0.5 * erfc((y - 0.5) / np.sqrt(4 * DT * x/U))
141
+ if np.allclose(T, analytical, atol=0.1):
142
+ print(f"Time {time.time}: OK")
143
+ else:
144
+ raise RuntimeError(f"Time {time.time}: {T} != {analytical}")
@@ -0,0 +1,25 @@
1
+ .. image:: https://github.com/gerlero/foamlib/blob/main/logo.png?raw=true
2
+ :alt: foamlib logo
3
+ :width: 200 px
4
+ :target: https://github.com/gerlero/foamlib
5
+ =========================================================================
6
+
7
+ `foamlib <https://github.com/gerlero/foamlib>`_ is a modern Python package that simplifies working with OpenFOAM cases and files by providing a standalone parser for seamless interaction with OpenFOAM's input/output data. It includes robust case-handling capabilities that reduce boilerplate code and enable efficient pre-processing, post-processing, and simulation management directly from Python. With support for ASCII- and binary-formatted fields, a fully type-hinted API, and asynchronous operations, foamlib offers a streamlined, Pythonic approach to automating and managing OpenFOAM workflows.
8
+
9
+ Documentation
10
+ =============
11
+
12
+ .. toctree::
13
+ :maxdepth: 2
14
+
15
+ example
16
+ cases
17
+ files
18
+
19
+
20
+ Indices and tables
21
+ ==================
22
+
23
+ * :ref:`genindex`
24
+ * :ref:`modindex`
25
+ * :ref:`search`
@@ -1,6 +1,6 @@
1
1
  """A Python interface for interacting with OpenFOAM."""
2
2
 
3
- __version__ = "0.9.4"
3
+ __version__ = "0.9.6"
4
4
 
5
5
  from ._cases import (
6
6
  AsyncFoamCase,
@@ -44,13 +44,14 @@ class AsyncFoamCase(FoamCaseRunBase):
44
44
 
45
45
  Provides methods for running and cleaning cases, as well as accessing files.
46
46
 
47
- Access the time directories of the case as a sequence, e.g. `case[0]` or `case[-1]`.
48
- These will return `AsyncFoamCase.TimeDirectory` objects.
47
+ Access the time directories of the case as a sequence, e.g. ``case[0]`` or ``case[-1]``.
48
+ These will return :class:`AsyncFoamCase.TimeDirectory` objects.
49
49
 
50
50
  :param path: The path to the case directory. Defaults to the current working
51
51
  directory.
52
52
 
53
53
  Example usage: ::
54
+
54
55
  from foamlib import AsyncFoamCase
55
56
 
56
57
  case = AsyncFoamCase("path/to/case") # Load an OpenFOAM case
@@ -82,7 +83,7 @@ class AsyncFoamCase(FoamCaseRunBase):
82
83
 
83
84
  max_cpus = multiprocessing.cpu_count()
84
85
  """
85
- Maximum number of CPUs to use for running instances of `AsyncFoamCase` concurrently.
86
+ Maximum number of CPUs to use for running instances of :class:`AsyncFoamCase` concurrently.
86
87
 
87
88
  Defaults to the number of CPUs on the system.
88
89
  """
@@ -143,19 +144,23 @@ class AsyncFoamCase(FoamCaseRunBase):
143
144
  """
144
145
  Clean this case.
145
146
 
146
- If a `clean` or `Allclean` script is present in the case directory, it will be invoked.
147
+ If a ``clean`` or ``Allclean`` script is present in the case directory, it will be invoked.
147
148
  Otherwise, the case directory will be cleaned using these rules:
148
149
 
149
- - All time directories except `0` will be deleted.
150
- - The `0` time directory will be deleted if `0.orig` exists.
151
- - `processor*` directories will be deleted if a `system/decomposeParDict` file is present.
152
- - `constant/polyMesh` will be deleted if a `system/blockMeshDict` file is present.
153
- - All `log.*` files will be deleted.
150
+ - All time directories except ``0`` will be deleted.
151
+
152
+ - The ``0`` time directory will be deleted if ``0.orig`` exists.
153
+
154
+ - ``processor*`` directories will be deleted if a ``system/decomposeParDict`` file is present.
155
+
156
+ - ``constant/polyMesh`` will be deleted if a ``system/blockMeshDict`` file is present.
157
+
158
+ - All ``log.*`` files will be deleted.
154
159
 
155
160
  If this behavior is not appropriate for a case, it is recommended to write a custom
156
- `clean` script.
161
+ ``clean`` script.
157
162
 
158
- :param check: If True, raise a `CalledProcessError` if the clean script returns a
163
+ :param check: If True, raise a :class:`CalledProcessError` if the clean script returns a
159
164
  non-zero exit code.
160
165
  """
161
166
  for coro in self._clean_calls(check=check):
@@ -191,35 +196,40 @@ class AsyncFoamCase(FoamCaseRunBase):
191
196
  """
192
197
  Run this case, or a specified command in the context of this case.
193
198
 
194
- If `cmd` is given, this method will run the given command in the context of the case.
199
+ If ``cmd`` is given, this method will run the given command in the context of the case.
195
200
 
196
- If `cmd` is `None`, a series of heuristic rules will be used to run the case. This works as
201
+ If ``cmd`` is ``None``, a series of heuristic rules will be used to run the case. This works as
197
202
  follows:
198
203
 
199
- - If a `run`, `Allrun` or `Allrun-parallel` script is present in the case directory,
200
- it will be invoked. If both `run` and `Allrun` are present, `Allrun` will be used. If
201
- both `Allrun` and `Allrun-parallel` are present and `parallel` is `None`, an error will
202
- be raised.
203
- - If no run script is present but an `Allrun.pre` script exists, it will be invoked.
204
- - Otherwise, if a `system/blockMeshDict` file is present, the method will call
205
- `self.block_mesh()`.
206
- - Then, if a `0.orig` directory is present, it will call `self.restore_0_dir()`.
207
- - Then, if the case is to be run in parallel (see the `parallel` option) and no
208
- `processor*` directories exist but a`system/decomposeParDict` file is present, it will
209
- call `self.decompose_par()`.
204
+ - If a ``run``, ``Allrun`` or ``Allrun-parallel`` script is present in the case directory,
205
+ it will be invoked. If both ``run`` and ``Allrun`` are present, ``Allrun`` will be used. If
206
+ both ``Allrun`` and ``Allrun-parallel`` are present and ``parallel`` is ``None``, an error will
207
+ be raised.
208
+
209
+ - If no run script is present but an ``Allrun.pre`` script exists, it will be invoked.
210
+
211
+ - Otherwise, if a ``system/blockMeshDict`` file is present, the method will call
212
+ :meth:`block_mesh()`.
213
+
214
+ - Then, if a ``0.orig`` directory is present, it will call :meth:`restore_0_dir()`.
215
+
216
+ - Then, if the case is to be run in parallel (see the ``parallel`` option) and no
217
+ ``processor*`` directories exist but a ``system/decomposeParDict`` file is present, it will
218
+ call :meth:`decompose_par()`.
219
+
210
220
  - Then, it will run the case using the application specified in the `controlDict` file.
211
221
 
212
222
  If this behavior is not appropriate for a case, it is recommended to write a custom
213
- `run`, `Allrun`, `Allrun-parallel` or `Allrun.pre` script.
223
+ ``run``, ``Allrun``, ``Allrun-parallel`` or ``Allrun.pre`` script.
214
224
 
215
- :param cmd: The command to run. If `None`, run the case. If a sequence, the first element
216
- is the command and the rest are arguments. If a string, `cmd` is executed in a shell.
217
- :param parallel: If `True`, run in parallel using MPI. If None, autodetect whether to run
225
+ :param cmd: The command to run. If ``None``, run the case. If a sequence, the first element
226
+ is the command and the rest are arguments. If a string, ``cmd`` is executed in a shell.
227
+ :param parallel: If ``True``, run in parallel using MPI. If None, autodetect whether to run
218
228
  in parallel.
219
- :param cpus: The number of CPUs to use. If `None`, autodetect from to the case.
220
- :param check: If `True`, raise a `CalledProcessError` if any command returns a non-zero
229
+ :param cpus: The number of CPUs to use. If ``None``, autodetect from to the case.
230
+ :param check: If ``True``, raise a :class:`CalledProcessError` if any command returns a non-zero
221
231
  exit code.
222
- :param log: If `True`, log the command output to `log.*` files in the case directory.
232
+ :param log: If ``True``, log the command output to ``log.*`` files in the case directory.
223
233
  """
224
234
  for coro in self._run_calls(
225
235
  cmd=cmd, parallel=parallel, cpus=cpus, check=check, log=log
@@ -262,6 +272,7 @@ class AsyncFoamCase(FoamCaseRunBase):
262
272
  :return: The copy of the case.
263
273
 
264
274
  Example usage: ::
275
+
265
276
  import os
266
277
  from pathlib import Path
267
278
  from foamlib import AsyncFoamCase
@@ -298,6 +309,7 @@ class AsyncFoamCase(FoamCaseRunBase):
298
309
  :return: The clone of the case.
299
310
 
300
311
  Example usage: ::
312
+
301
313
  import os
302
314
  from pathlib import Path
303
315
  from foamlib import AsyncFoamCase
@@ -23,10 +23,10 @@ class FoamCaseBase(Sequence["FoamCaseBase.TimeDirectory"]):
23
23
 
24
24
  Provides methods for accessing files and time directories in the case, but does not
25
25
  provide methods for running the case or any commands. Users are encouraged to use
26
- `FoamCase` or `AsyncFoamCase` instead of this class.
26
+ :class:`FoamCase` or :class:`AsyncFoamCase` instead of this class.
27
27
 
28
- Access the time directories of the case as a sequence, e.g. `case[0]` or `case[-1]`.
29
- These will return `FoamCaseBase.TimeDirectory` objects.
28
+ Access the time directories of the case as a sequence, e.g. ``case[0]`` or ``case[-1]``.
29
+ These will return class:`FoamCaseBase.TimeDirectory` objects.
30
30
 
31
31
  :param path: The path to the case directory. Defaults to the current working
32
32
  directory.
@@ -39,11 +39,11 @@ class FoamCaseBase(Sequence["FoamCaseBase.TimeDirectory"]):
39
39
  """
40
40
  A time directory in an OpenFOAM case.
41
41
 
42
- Use to access field files in the directory (e.g. `time["U"]`). These will be
43
- returned as `FoamFieldFile` objects.
42
+ Use to access field files in the directory (e.g. ``time["U"]``). These will be
43
+ returned as :class:`FoamFieldFile` objects.
44
44
 
45
- It also behaves as a set of `FoamFieldFile` objects (e.g. it can be
46
- iterated over with `for field in time: ...`).
45
+ It also behaves as a set of :class:`FoamFieldFile` objects (e.g. it can be
46
+ iterated over with ``for field in time: ...``).
47
47
  """
48
48
 
49
49
  def __init__(self, path: os.PathLike[str] | str) -> None:
@@ -154,7 +154,7 @@ class FoamCaseBase(Sequence["FoamCaseBase.TimeDirectory"]):
154
154
 
155
155
  @property
156
156
  def _nsubdomains(self) -> int | None:
157
- """Return the number of subdomains as set in the decomposeParDict, or None if no decomposeParDict is found."""
157
+ """Return the number of subdomains as set in the decomposeParDict, or ``None`` if no decomposeParDict is found."""
158
158
  try:
159
159
  nsubdomains = self.decompose_par_dict["numberOfSubdomains"]
160
160
  if not isinstance(nsubdomains, int):