Note
Go to the end to download the full example code.
Extract solver residuals to a CSV¶
residuals(mesh) returns a workflow seeded with an AggregatedDataSet
that contains one row per (field, solver, metric, iteration) tuple. This
is what you pipe into @postProcess.Table("residuals.csv") to log solver
performance without running simpleFoam with solverPerformance
enabled separately.
Residuals are populated by the running solver — a freshly loaded mesh has
an empty solverPerformanceDict, so this how-to demonstrates the API
shape. Run it after a few solver steps for non-empty output.
Open the case¶
import os
import subprocess
import pyOFTools.patch_pybfoam # noqa: F401
from pyOFTools import clone_example
CASE = clone_example("damBreak")
subprocess.run(
["./Allrun"],
cwd=CASE,
check=True,
env={**os.environ},
capture_output=True,
text=True,
)
from pybFoam import Time, fvMesh
time = Time(str(CASE.parent), CASE.name)
mesh = fvMesh(time)
Build and inspect the residual workflow¶
from pyOFTools.builders import residuals
wf = residuals(mesh)
dataset = wf.compute()
print(f"residuals returned {len(dataset.values)} rows")
if dataset.values:
print("headers:", dataset.headers)
for row in dataset.grouped_values[:5]:
print(" ", row)
else:
print("(empty — solverPerformanceDict has not been populated by a solver run)")
residuals returned 0 rows
(empty — solverPerformanceDict has not been populated by a solver run)
In-situ usage¶
In practice, this is what the decorator looks like:
from pyOFTools.postprocessor import PostProcessorBase
from pyOFTools.builders import residuals
postProcess = PostProcessorBase()
@postProcess.Table("residuals.csv")
def solver_residuals(mesh):
return residuals(mesh)
The CSV gets one row per field per inner iteration per write step — long format, trivial to pivot with pandas.
Total running time of the script: (0 minutes 10.574 seconds)