3  OpenFOAM

aka majordome.openfoam

3.1 OpenFOAM

3.1.1 Loading postProcessing data

loader = FoamTabularData(["../data/foam/tabular.dat"])
loader.table.head()
Time sum(phi)
0 0.0000 -0.00101
1 0.0001 -0.00101
2 0.0002 -0.00101
3 0.0003 -0.00101
4 0.0004 -0.00101
loader = FoamLagrangianTable(["../data/foam/lagrangian.dat"])
loader.table.head()
Time currentProc coordinatesa coordinatesb coordinatesc coordinatesd celli tetFacei tetPti facei ... Uy Uz rho age tTurb UTurbx UTurby UTurbz T Cp
0 20 0 0 0.690399 0.086755 0.222846 15002 28457 1 28457 ... 0.252794 0.230030 2500 0.017538 0.000130 0.339988 0.400311 1.301360 1987.03 850
1 20 0 0 0.038371 0.102108 0.859521 13596 28447 2 28447 ... 0.003129 0.001433 2500 0.013401 0.000106 -1.390020 -1.555300 -0.695051 1968.10 850
2 20 0 0 0.402984 0.083853 0.513163 13612 28448 1 28448 ... -0.612065 0.328071 2500 0.011585 0.000482 2.148070 -2.966400 1.389950 1915.91 850
3 20 0 0 0.301188 0.555775 0.143037 13596 28447 1 28447 ... 0.862154 0.051318 2500 0.013615 0.000128 0.097994 -0.135601 -0.063630 1968.20 850
4 20 0 0 0.833575 0.107882 0.058543 14348 28454 1 28454 ... 0.243624 0.030407 2500 0.019982 0.000261 1.647390 1.779620 0.671343 1990.20 850

5 rows × 31 columns

4 OpenFOAM case manipulation

The majordome.openfoam module provides a wrapper to a Rust-powered AST parser (crate majordome-foam) and dynamic Python interface for inspecting, modifying, and managing OpenFOAM cases cleanly. This example demonstrates how to use FoamCaseHandle to interact directly with OpenFOAM dictionary files without any hassle.

We start by importing the required tools:

from pathlib import Path
from majordome.openfoam import FoamCaseHandle, NotACaseError

4.1 Initializing FoamCaseHandle

The first step is to initialize a FoamCaseHandle pointing to the case directory. FoamCaseHandle can be instantiated either by providing an explicit root directory or by omitting arguments to target the current working directory. A case is recognized as valid if it contains both a constant/ directory and a system/controlDict file.

# Point to the 01-pitzDaily case directory
case = FoamCaseHandle("../data/foam/cases/01-pitzDaily", zero_name="0")

print(f"Case root: {Path(*case.root_dir.parts[-4:])}")
print(f"Is valid OpenFOAM case? {case.is_valid}")
Case root: data\foam\cases\01-pitzDaily
Is valid OpenFOAM case? True

If FoamCaseHandle is pointed to a directory that is not a valid OpenFOAM case, is_valid returns False, and attempting to access case dictionaries raises NotACaseError:

invalid_handle = FoamCaseHandle(Path(".."))
print(f"Invalid case check: {invalid_handle.is_valid}")

try:
    _ = invalid_handle.controlDict
except NotACaseError as e:
    print(f"Caught expected error: {e}")
Invalid case check: False
Caught expected error: Directory 'D:\kompanion\repos\majordome\docs' is not a valid OpenFOAM case (missing 'constant/' directory or 'system/controlDict' file).

4.2 Inspecting and modifying system dictionaries

Class FoamDictFile provides support to managing arbitrary dictionaries. For instance, accessing case.controlDict automatically parses and returns a strongly-typed ControlDict instance based on that base type. You can update simulation control attributes directly on the handle:

# Access controlDict attributes dynamically
print(f"Original values:")
print(f"Application ....: {case.controlDict.application}")
print(f"Start from .....: {case.controlDict.start_from}")
print(f"Start time .....: {case.controlDict.start_time}")
print(f"Stop at ........: {case.controlDict.stop_at}")
print(f"End time .......: {case.controlDict.end_time}")
print(f"Delta T ........: {case.controlDict.delta_t}")
print(f"Write Interval .: {case.controlDict.write_interval}")

# Update simulation control parameters
case.controlDict.end_time = 1500
case.controlDict.write_interval = 100

print("\nModified parameters:")
print(f"End Time .......: {case.controlDict.end_time}")
print(f"Write Interval .: {case.controlDict.write_interval}")
Original values:
Application ....: <builtins.FoamDict object at 0x000002C7102790B0>
Start from .....: latestTime
Start time .....: 0
Stop at ........: endTime
End time .......: 0.3
Delta T ........: 0.0001
Write Interval .: 0.01

Modified parameters:
End Time .......: 1500
Write Interval .: 100

You can also convert the dictionary to a string in OpenFOAM format using the to_foam() method.

print(case.controlDict.to_foam())
/*--------------------------------*- C++ -*----------------------------------*\
  =========                 |
  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
   \\    /   O peration     | Website:  https://openfoam.org
    \\  /    A nd           | Version:  13
     \\/     M anipulation  |
\*---------------------------------------------------------------------------*/

FoamFile
{
    format    ascii;

    class     dictionary;

    location  system;

    object    controlDict;
}

// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

solver             incompressibleFluid;

startFrom          latestTime;

startTime          0;

stopAt             endTime;

endTime            1500;

deltaT             0.0001;

writeControl       adjustableRunTime;

writeInterval      100;

purgeWrite         0;

writeFormat        ascii;

writePrecision     6;

writeCompression   false;

timeFormat         general;

timePrecision      6;

runTimeModifiable  true;

adjustTimeStep     true;

maxCo              5;

// ************************************************************************* //

One can also manage discretization schemes (fvSchemes) dynamically:

schemes = case.fvSchemes

print("ddtSchemes ..:", schemes.ddt_schemes.to_foam())
print("gradSchemes .:", schemes.grad_schemes.to_foam())

# Update a divergence scheme entry
schemes.set_div_scheme("div(phi,U)", "bounded Gauss limitedLinearV 1")
print(schemes.div_schemes.to_foam())
ddtSchemes ..: default  Euler;
gradSchemes .: default  "Gauss linear";
default                        none;

div(phi,U)                     "bounded Gauss limitedLinearV 1";

div(phi,k)                     "Gauss upwind";

div(phi,epsilon)               "Gauss upwind";

div(phi,R)                     "Gauss upwind";

div(R)                         "Gauss linear";

div(phi,nuTilda)               "Gauss upwind";

div((nuEff*dev2(T(grad(U)))))  "Gauss linear";

The same is also possible for solver controls (fvSolution) dictionaries.

sol = case.fvSolution
print("Solvers block keys:", sol.solvers.keys())

# Update solver tolerance for pressure 'p'
sol.set_solver_option("p", "tolerance", 1e-7)
print("Updated p tolerance:", sol.get("solvers/p/tolerance"))
Solvers block keys: ['p', 'pFinal', '"(U|k|epsilon)"', '"(U|k|epsilon)Final"']
Updated p tolerance: 1e-07

4.3 Accessing initial/boundary field files

For fields, the management is specialized through FieldFile. Field files in 0/ (or the user configured zero_name in FoamCaseHandle) are dynamically resolved by field variable name (e.g. case.p, case.U):

p_field = case.p
print("p Dimensions:", p_field.dimensions)
print("p Internal Field:", p_field.internal_field)

U_field = case.U
print("U Dimensions:", U_field.dimensions)
p Dimensions: [0, 2, -2, 0, 0, 0, 0]
p Internal Field: uniform 0
U Dimensions: [0, 1, -1, 0, 0, 0, 0]

4.4 Persisting modifications

Call case.save() to write all modified dictionaries back to disk:

# case.save()