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.
- {foamlib-0.9.4 → foamlib-0.9.6}/PKG-INFO +3 -142
- {foamlib-0.9.4 → foamlib-0.9.6}/README.md +2 -141
- {foamlib-0.9.4 → foamlib-0.9.6}/docs/conf.py +1 -1
- foamlib-0.9.6/docs/example.rst +144 -0
- foamlib-0.9.6/docs/index.rst +25 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/__init__.py +1 -1
- {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/_cases/_async.py +43 -31
- {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/_cases/_base.py +8 -8
- {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/_cases/_run.py +2 -2
- {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/_cases/_slurm.py +9 -9
- {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/_cases/_sync.py +46 -35
- {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/_files/_files.py +35 -29
- {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/_files/_parsing.py +6 -3
- {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/_files/_serialization.py +9 -6
- {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/_files/_types.py +20 -7
- {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_files/test_dumps.py +4 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_files/test_parsing/test_loads.py +3 -0
- foamlib-0.9.4/docs/index.rst +0 -16
- {foamlib-0.9.4 → foamlib-0.9.6}/.devcontainer.json +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/.dockerignore +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/.git-blame-ignore-revs +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/.github/dependabot.yml +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/.github/workflows/ci.yml +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/.github/workflows/docker.yml +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/.github/workflows/dockerhub-description.yml +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/.github/workflows/pypi-publish.yml +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/.gitignore +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/.readthedocs.yaml +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/CONTRIBUTING.md +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/Dockerfile +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/LICENSE.txt +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/benchmark/benchmark.png +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/benchmark/benchmark.py +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/benchmark/requirements.txt +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/benchmark/ruff.toml +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/docs/Makefile +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/docs/cases.rst +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/docs/files.rst +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/docs/make.bat +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/docs/ruff.toml +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/_cases/__init__.py +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/_cases/_subprocess.py +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/_cases/_util.py +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/_files/__init__.py +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/_files/_io.py +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/foamlib/py.typed +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/logo.png +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/pyproject.toml +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/tests/__init__.py +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/tests/ruff.toml +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_cases/__init__.py +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_cases/test_cavity.py +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_cases/test_cavity_async.py +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_cases/test_flange.py +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_cases/test_flange_async.py +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_example.py +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_files/__init__.py +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_files/test_files.py +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_files/test_parsing/__init__.py +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_files/test_parsing/test_advanced.py +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_files/test_parsing/test_basic.py +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_files/test_parsing/test_decompose_par.py +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_files/test_parsing/test_fields.py +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_files/test_parsing/test_fv_schemes.py +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_files/test_parsing/test_intermediate.py +0 -0
- {foamlib-0.9.4 → foamlib-0.9.6}/tests/test_files/test_parsing/test_poly_mesh.py +0 -0
- {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.
|
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
|
-
##
|
199
|
+
## 📘 Documentation
|
200
200
|
|
201
|
-
|
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
|
-
##
|
161
|
+
## 📘 Documentation
|
162
162
|
|
163
|
-
|
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`
|
@@ -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.
|
48
|
-
These will return
|
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
|
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
|
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
|
150
|
-
|
151
|
-
-
|
152
|
-
|
153
|
-
-
|
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
|
-
|
161
|
+
``clean`` script.
|
157
162
|
|
158
|
-
:param check: If True, raise 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
|
199
|
+
If ``cmd`` is given, this method will run the given command in the context of the case.
|
195
200
|
|
196
|
-
If
|
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
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
-
|
205
|
-
|
206
|
-
-
|
207
|
-
|
208
|
-
|
209
|
-
call
|
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
|
-
|
223
|
+
``run``, ``Allrun``, ``Allrun-parallel`` or ``Allrun.pre`` script.
|
214
224
|
|
215
|
-
:param cmd: The command to run. If
|
216
|
-
is the command and the rest are arguments. If a string,
|
217
|
-
:param parallel: If
|
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
|
220
|
-
:param check: If
|
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
|
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
|
-
|
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.
|
29
|
-
These will return
|
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.
|
43
|
-
returned as
|
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
|
46
|
-
iterated over with
|
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):
|