|
16 | 16 |
|
17 | 17 | **foamlib** provides a simple, modern, ergonomic and fast Python interface for interacting with [OpenFOAM](https://www.openfoam.com). |
18 | 18 |
|
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. |
24 | 31 |
|
25 | 32 | ## 👋 Basics |
26 | 33 |
|
@@ -151,6 +158,159 @@ case = FoamCase(Path(__file__).parent) |
151 | 158 | case.run() |
152 | 159 | ``` |
153 | 160 |
|
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 |
155 | 315 |
|
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). |
0 commit comments