Skip to content

Commit 84b8f33

Browse files
committed
Merge branch 'main' into joss
2 parents fbe903b + 42435b6 commit 84b8f33

32 files changed

+2725
-229
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ jobs:
3535
python-version-file: pyproject.toml
3636
- name: Check types with mypy
3737
run: |
38-
uv run --extra typing mypy
38+
uv run mypy
3939
4040
test:
4141
runs-on: ubuntu-22.04
@@ -49,11 +49,11 @@ jobs:
4949
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
5050
strategy:
5151
matrix:
52-
openfoam-version: [2406, 2006, 12, 9]
52+
openfoam-version: [2412, 2006, 12, 9]
5353
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
5454
slurm: [false]
5555
include:
56-
- openfoam-version: 2406
56+
- openfoam-version: 2412
5757
python-version: '3.13'
5858
slurm: true
5959
- openfoam-version: 12
@@ -84,7 +84,7 @@ jobs:
8484
uses: koesterlab/setup-slurm-action@v1
8585
- name: Install test dependencies
8686
run: |
87-
uv sync --extra test
87+
uv sync
8888
- name: Test with pytest
8989
run: |
9090
uv run pytest --cov=foamlib --cov-report xml

.github/workflows/docker.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ jobs:
8080
packages: write
8181
strategy:
8282
matrix:
83-
openfoam-version: [2406, 2312, 12, 11]
83+
openfoam-version: [2412, 2406, 12, 11]
8484
fail-fast: false
8585
steps:
8686
- name: Set up QEMU

CONTRIBUTING.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Contributing
2+
3+
## Setup
4+
5+
1. [Fork the repository on GitHub](https://github.com/gerlero/foamlib/fork)
6+
7+
1. Clone your fork locally
8+
9+
```bash
10+
git clone https://github.com/<your_username>/foamlib.git
11+
```
12+
13+
2. Install the project in editable mode in a virtual environment
14+
15+
```bash
16+
cd foamlib
17+
python3 -m venv .venv
18+
source .venv/bin/activate
19+
pip install -e .[dev]
20+
```
21+
22+
## Contributing changes via a pull request
23+
24+
1. Create a new branch for your changes
25+
26+
```bash
27+
git checkout -b my-new-feature
28+
```
29+
30+
2. Make your changes
31+
32+
3. Test your changes (see below for instructions)
33+
34+
4. Commit your changes
35+
36+
```bash
37+
git add .
38+
git commit -m "Add some feature"
39+
```
40+
41+
5. Push your changes to your fork
42+
43+
```bash
44+
git push origin my-new-feature
45+
```
46+
47+
6. [Open a pull request on GitHub](https://github.com/gerlero/foamlib/compare)
48+
49+
50+
## Checks
51+
52+
The following checks will be run by the CI pipeline, so it is recommended to run them locally before opening a pull request.
53+
54+
### Testing
55+
56+
Run the tests with:
57+
58+
```bash
59+
pytest
60+
```
61+
62+
### Type checking
63+
64+
Type check the code with:
65+
66+
```bash
67+
mypy
68+
```
69+
70+
### Linting
71+
72+
Lint the code with:
73+
74+
```bash
75+
ruff check
76+
```
77+
78+
### Formatting
79+
80+
Format the code with:
81+
82+
```bash
83+
ruff format
84+
```
85+
86+
### Documentation
87+
88+
Generate the documentation locally with:
89+
90+
```bash
91+
cd docs
92+
make html
93+
```

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
ARG BASE=python:3.12-slim
33

4-
ARG OPENFOAM_VERSION=2406
4+
ARG OPENFOAM_VERSION=2412
55
FROM microfluidica/openfoam:${OPENFOAM_VERSION} AS openfoam
66

77
ARG VIRTUAL_ENV=/opt/venv

README.md

Lines changed: 167 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,18 @@
1616

1717
**foamlib** provides a simple, modern, ergonomic and fast Python interface for interacting with [OpenFOAM](https://www.openfoam.com).
1818

19-
<p align="center">
20-
<img alt="benchmark" src="https://github.com/gerlero/foamlib/raw/main/benchmark.png" height="250">
21-
<br>
22-
<i>Parsing a </i>volVectorField<i> with 200k cells.</i>
23-
</p>
19+
<div align="center">
20+
<img alt="benchmark" src="https://github.com/gerlero/foamlib/raw/main/benchmark/benchmark.png" height="250">
21+
22+
Parsing a volVectorField with 200k cells.<sup>[1](#benchmark)</sup>
23+
</div>
24+
25+
26+
## 🚀 Introduction
27+
28+
**foamlib** is a Python package designed to simplify the manipulation of OpenFOAM cases and files. Its standalone parser makes it easy to work with OpenFOAM’s input/output files from Python, while its case-handling capabilities facilitate various execution workflows—reducing boilerplate code and enabling efficient Python-based pre- and post-processing, as well as simulation management.
29+
30+
Compared to [PyFoam](https://openfoamwiki.net/index.php/Contrib/PyFoam) and other similar tools like [fluidfoam](https://github.com/fluiddyn/fluidfoam), [fluidsimfoam](https://foss.heptapod.net/fluiddyn/fluidsimfoam), and [Ofpp](https://github.com/xu-xianghua/ofpp), **foamlib** offers advantages such as modern Python compatibility, support for binary-formatted fields, a fully type-hinted API, and asynchronous operations; making OpenFOAM workflows more accessible and streamlined.
2431

2532
## 👋 Basics
2633

@@ -151,6 +158,159 @@ case = FoamCase(Path(__file__).parent)
151158
case.run()
152159
```
153160
154-
## 📘 Documentation
161+
## ▶️ A complete example
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/).
303+
304+
## 🙋 Support
305+
306+
If you have any questions or need help, feel free to open a [discussion](https://github.com/gerlero/foamlib/discussions).
307+
308+
If you believe you have found a bug in **foamlib**, please open an [issue](https://github.com/gerlero/foamlib/issues).
309+
310+
## 🧑‍💻 Contributing
311+
312+
You're welcome to contribute to **foamlib**! Check out the [contributing guidelines](CONTRIBUTING.md) for more information.
313+
314+
## Footnotes
155315
156-
For more information, check out the [documentation](https://foamlib.readthedocs.io/).
316+
<a id="benchmark">[1]</a> foamlib 0.8.1 vs PyFoam 2023.7 on a MacBook Air (2020, M1) with 8 GB of RAM. [Benchmark script](benchmark/benchmark.py).
File renamed without changes.

benchmark/benchmark.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/usr/bin/env python3
2+
3+
"""Benchmark foamlib against PyFoam."""
4+
5+
import timeit
6+
7+
from foamlib import FoamFieldFile
8+
from PyFoam.RunDictionary.ParsedParameterFile import ParsedParameterFile
9+
10+
FoamFieldFile("U").internal_field = [[0.0, 0.0, 0.0]] * 200_000
11+
12+
print(
13+
f"foamlib: {min(timeit.repeat(lambda: FoamFieldFile('U').internal_field, number=1))} s"
14+
)
15+
print(
16+
f"PyFoam: {min(timeit.repeat(lambda: ParsedParameterFile('U')['internalField'], number=1))} s"
17+
)

benchmark/requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
foamlib==0.8.1
2+
PyFoam==2023.7

benchmark/ruff.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
extend = "../pyproject.toml"
2+
3+
[lint]
4+
extend-ignore = ["T201"]

foamlib/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""A Python interface for interacting with OpenFOAM."""
22

3-
__version__ = "0.8.4"
3+
__version__ = "0.8.8"
44

55
from ._cases import (
66
AsyncFoamCase,

0 commit comments

Comments
 (0)