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 |
aka majordome.openfoam
postProcessing dataloader = 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
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, NotACaseErrorFoamCaseHandleThe 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).
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
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]
Call case.save() to write all modified dictionaries back to disk:
# case.save()