porousGasificationFoam (PGF) is an OpenFOAM solver for thermochemical conversion in porous media. A solid phase, represented as a porosity field, is coupled to a gas flowing through the void space. Mass, momentum, and energy are exchanged through the porosity. All chemistry — gas-phase, heterogeneous (gas–solid), and solid decomposition — is defined per case. With no reactions defined, both phases stay inert. The solid can be stationary or move under gravity and other external forces, shrink, or disappear entirely as conversion proceeds.
The continuum transport equations are solved with the Finite Volume Method (OpenFOAM). An optional coupling to a Discrete Element Method solver YADE provides PGF with a velocity field for the solid momentum equation. Solid material properties, initial distribution of porosity throughout a domain, reaction kinetics, and many other parameters — all these are user inputs — the solver is geometry- and chemistry-agnostic.
Example application areas:
- Biomass pyrolysis, gasification, and combustion (the solver's origin)
- Wood fires and waste incineration
- Coffee bean roasting
- Peat smouldering
- Metal-foam manufacturing
…and other processes that share a reactive or transforming porous solid exchanging mass and heat with a gas. The name porousGasificationFoam reflects the solver's origins in biomass-to-syngas gasification (feedstock + air/O₂/steam/CO₂ → H₂/CO), not a limit on what it can model.
- License: GNU GPL v3
- Target: OpenFOAM-v2406
- Optional: DEM coupling via YADE (
WITH_YADE=1at build time) - Earlier versions for OpenFOAM 8 and foam-extend 4.1 exist but are no longer actively maintained.
Part I — Practical Guide
- Quick Start
- Project Structure
- Build System
- Tutorial Cases
- Input File Reference
- Required Initial Fields
- Tips for Preparing New Simulations
- Utilities
- Regression Testing
- Troubleshooting
Part II — Physics and Implementation
Development Workflow · Citation · Contributors
Build, run, configure, debug. Most readers spend their time here.
- OpenFOAM-v2406
- MPI (for parallel runs)
- Optional: YADE DEM library (for DEM coupling — set
WITH_YADE=1)
# 1. Source OpenFOAM (adjust paths for your installation)
source /path/to/OpenFOAM-v2406/etc/bashrc
# 2. Build everything
cd /path/to/porousGasificationFoam
./Allwmake
# 3. Test installation
cd tutorials/cases/charOnlyMoveCases/parallel
./AllrunFor a full reactive-flow demonstration see gasifier. The complete list is under Tutorial Cases. To run every case: cd tutorials && ./TestAllCases.sh. To clean a build: ./Allwclean.
porousGasificationFoam/
├── porousGasificationFoam/ # main solver source (time loop, equations)
├── porousGasificationMedia/ # libraries (pyrolysis, chemistry, radiation, DEM)
├── tutorials/cases/ # tutorial cases (input examples)
├── utilities/ # auxiliary tools (setPorosity, totalMass, …)
├── applications/test/ # regression framework
├── build.sh # fine-grained build script
└── doc/Doxygen/ # API documentation source
| Target | Type | Dependency |
|---|---|---|
DEM (if WITH_YADE=1) |
library | — |
| fieldPorosityModel | library | — |
| radiationModels | library | solid thermo |
| thermophysicalModels | library suite | — |
| pyrolysisModels | library | all above |
| porousGasificationFoam | executable | all libraries |
| utilities | executable suite | — |
The build.sh script operates in two modes: build or clean. For the selected mode it builds or cleans all active targets. Targets are switched on or off via CLI flags.
Examples:
./build.sh build --apps-only # set app targets to 1 and libs to 0, build targets with 1 set
./build.sh clean --reset-all --radiationModels # set all targets to 0; set radiationModels to 1, clean only radiationModels (later flags overwrite previous ones)
./build.sh build --all --no-DEM # skip DEM, build all the rest
# Aliases
./Allwmake → ./build.sh build --all
./Allwclean → ./build.sh clean --allRun build.sh --help for all options.
./build.sh build --yade--yade enables the DEM library target and compiles the solver with WITH_YADE=1, which activates the #ifdef WITH_YADE DEM coupling blocks. Without this flag the solver is built without DEM support and Yade will hang waiting for MPI communication.
Requires YADE installed with the OpenFOAM coupling module.
cd doc/Doxygen
./AllwmakeOutput: $WM_PROJECT_DIR/doc/Doxygen/html/index.html. Requires doxygen and graphviz.
All 14 cases under tutorials/cases/:
| Case | Description | Notable features |
|---|---|---|
microTGA/ |
Micro-scale TGA (3 variants: MeanTemp, P1, Template) | Radiation on, template reference |
macroTGA_688K/ |
Macro-scale TGA at 688 K | — |
macroTGA_688K_fine/ |
Macro-scale TGA at 688 K, refined mesh | — |
macroTGA_879K/ |
Macro-scale TGA at 879 K | — |
macroTGA_879K_fine/ |
Macro-scale TGA at 879 K, refined mesh | — |
macroTGA_experimentalData/ |
TGA with experimental data comparison | — |
gasifier/ |
Fixed-bed gasifier | - |
flatPlate/ |
Flat plate reactive flow | — |
biomassPressureDrop/ |
Pressure drop through biomass bed | — |
charOnlyMoveCases/parallel/ |
Moving char (no reactions), 4-proc parallel | Solid material motion test |
charOnlyMoveCases/parallel-recon/ |
Parallel + reconstructPar | Small fast parallel regression |
charOnlyMoveCases/serial/ |
Serial cut-down of parallel |
Regression fixture (prescribed-Us transport) |
charOnlyMoveCases/serial_m2/ |
Serial, 2× mesh refinement | Convergence study |
charOnlyMoveCases/serial_m4/ |
Serial, 4× mesh refinement | Convergence study |
MicroTGA-DEM/ |
DEM-coupled micro TGA | Requires YADE |
DEM_UsInterp_*/ |
DEM solid velocity interpolation tests | Requires YADE |
Defines solid chemistry configuration — the most important file for reaction kinetics.
chemistry on;
solidChemistryType
{
solver solidOde;
method ODESolidHeterogeneousChemistryModel;
solidThermoType const<constRad<constThermo<constRho>>>;
}
initialChemicalTimeStep 1e-5;
solidReactionEnergyFromEnthalpy false; // true = use Hf, false = use heatReact
stoichiometricReactions false; // true = mass-conserving stoichiometry
diffusionLimitedReactions true; // enable mass-transfer limitation
showRelativeReactionRates false;
solidOdeCoeffs
{
solver seulex; // ODE integrator
}
species // Pyrolysis gas names (must match gas species fields in 0/)
(
CO N2 O2
);
solidReactions
(
// Format (3 lines per reaction):
// irreversibleSolidArrheniusHeterogeneousReaction
// wood + 1.25 O2 = 0.5 ash + 1.75 CO
// (5.61e9 1.96e4 300 -6.22e6 1.0 1.0)
);Reaction parameter format: (A Ta Tcrit heatOfReaction n1 n2 ...)
| Parameter | Meaning | Units |
|---|---|---|
A |
Pre-exponential factor | varies |
Ta |
Activation temperature Ea/R |
[K] |
Tcrit |
Minimum temperature for reaction | [K] |
heatOfReaction |
Heat released/absorbed (<0 = exothermic) | [J/kg] |
n1, n2, ... |
Reaction order for each LHS species in order | — |
Defines solid components and their thermophysical properties.
thermoType solidMixtureThermo<constHeterogeneous>;
solidComponents
(
ash wood
);
woodCoeffs
{
transport { K 0.341; } // [W/m/K]
thermodynamics { Cp 1800; Hf -1.04e6; } // [J/kg/K], [J/kg]
density { rho 1050; } // [kg/m³]
};
ashCoeffs { ... }Three thermo variants:
solidThermoType |
Cp(T) | Transport | Notes |
|---|---|---|---|
constHeterogeneous |
constant | constant | Simplest, adequate for most cases |
expoHeterogeneous |
c0·(T/Tref)^n0 |
exponential | T-dependent Cp and K |
linearHeterogeneous |
constant | linear | T-dependent K = a·T + b |
active true;
heterogeneousPyrolysisModel volPyrolysis;
pyrolysisCoeffs
{
subintegrateHeatTransfer true;
bedCollapse false; // enable bed motion
criticalPorosity 0.9999; // porosity threshold for bed collapse
replenish false; // fuel replenishment model
}heatTransferModel constCONV;
Parameters
{
h 8; // Heat transfer coefficient [W/m²/K]
SAV 468; // Specific surface area [1/m]
}Volumetric heat transfer rate: Q = h · SAV · (T_gas - T_solid) [W/m³].
specieTransferModel constST;
Parameters
{
h 0.017; // Mass transfer coefficient
SAV 468; // Specific surface area [1/m]
}nly used when diffusionLimitedReactions true.
Adds a Forchheimer (quadratic) resistance term to the momentum equation on top of the linear Darcy term. If the file is absent, forchheimerCoeff defaults to 0 and only the Darcy term Df·U is applied.
forchheimerCoeff 3.21e6;Effective momentum sink: −µ_eff · D · ⟨u⟩ − F_c · ρ_f · |⟨u⟩| · √3 · D / |D|. The Forchheimer term matters for high-velocity flow through coarse beds. For slow flow inside a fine porous medium the linear Darcy term is usually sufficient. Unlike Df (a per-cell field), forchheimerCoeff is a single scalar applied everywhere.
radiation on;
heterogeneousRadiationModel heterogeneousP1;
solverFreq 1;
heterogeneousAbsorptionEmissionModel heterogeneousConstantAbsorptionEmission;
heterogeneousConstantAbsorptionEmissionCoeffs
{
a a [ 0 -1 0 0 0 0 0 ] 0; // Absorption coefficient [1/m]
as as [ 0 -1 0 0 0 0 0 ] 0; // Scattering coefficient [1/m]
borderAs borderAs [ 0 -1 0 0 0 0 0 ] 180; // Surface absorption [1/m]
E E [ 1 -1 -3 0 0 0 0 ] 0.0; // Emissive power
borderL borderL [ 0 1 0 0 0 0 0 ] 1.5e-3; // Surface layer [m]
}solvers
{
Ts { solver PCG; preconditioner DIC; tolerance 1e-10; } // tight tolerance!
rhos { solver PCG; preconditioner DIC; }
}
PIMPLE
{
momentumPredictor yes;
nOuterCorrectors 2;
nCorrectors 2;
nNonOrthogonalCorrectors 0;
}The Ts (solid temperature) solver requires a tight tolerance (1e-10) for stability.
div(phiSolid) Gauss upwind;application porousGasificationFoam;
adjustTimeStep yes;
maxCo 5; // Courant number limit (fluid)
maxDi 5000; // Diffusion number limit (solid)| Field | Type | Description |
|---|---|---|
p |
volScalarField | Pressure [Pa] |
U |
volVectorField | Velocity [m/s] |
T |
volScalarField | Gas temperature [K] |
Ts |
volScalarField | Solid temperature [K] |
rhos |
volScalarField | Solid density [kg/m³] |
porosityF |
volScalarField | Porosity (0 = solid, 1 = gas) |
porosityF0 |
volScalarField | Initial porosity (archival copy) |
Df |
volTensorField | Darcy resistance tensor |
anisotropyK |
volTensorField | Optional, READ_IF_PRESENT. Anisotropic multiplier for the solid thermal conductivity used in the solid energy equation (K_eff = K · (1−φ) · anisotropyK). Defaults to the unit tensor if the file is absent. |
<gasName> (e.g. O2, N2, CO) |
volScalarField | Gas species mass fractions |
Y<solidName> (e.g. Ywood, Yash) |
volScalarField | Solid species mass fractions |
Ydefault |
volScalarField | Default field for unmatched gas species (set to 0) |
YsDefault |
volScalarField | Default field for unmatched solid species (set to 0) |
Notes:
Dfis a tensor field. For isotropic porous media, use a diagonal tensor with large values (e.g.1e9) in gas-only regions and smaller values (based on permeability) in porous zones. Use thesetPorosityutility to create appropriate fields.- Solid species field names:
Y+ component name fromsolidThermophysicalProperties(e.g.wood→Ywood). - Gas species field names match the
specieslist inchemistryProperties.
Practical notes that are easy to get wrong on a first PGF case.
Three approaches, in increasing order of complexity:
setFields(with or withoutsetSet): the standard OpenFOAM workflow. Specify asetFieldsDictthat setsporosityFinside a cell zone to the desired void fraction and leaves the rest at1.0.setFieldsalone is sufficient for simple geometries. Pair withsetSet(batch mode) for more involved selection logic. Other fields (T,Ts, solid species mass fractions, …) can be initialised the same way. Seetutorials/cases/macroTGA_*for an example chain (blockMesh→setSet→refineHexMesh→setFields).- STL +
setFields: for non-trivial bed geometries, build an STL of the porous region in Salome or Blender and feed it tosetSet/setFieldsvia asurfaceToCellselector. setPorosityutility (possibly outdated): a code-driven generator (seeutilities/setPorosity/). The user-editable description of the medium lives inmedium.H— the tool must be recompiled after each change. Kept for backward compatibility with older cases and parametric sweeps. For new cases, prefer approaches 1 and 2 above.
Entries in solidThermophysicalProperties (rho, K, Cp, Hf) describe the pure solid — i.e. the material at porosityF = 0. The macroscopic bulk density inside the bed is then ρ · (1 − φ). PGF reconstructs it from porosityF and the intrinsic rho. Literature often reports bulk values instead, so be careful: feeding a bulk density into rho understates the solid by a factor of (1 − φ).
Pyrolysis pseudo-species (e.g. lumped tar, targas) generally have no JANAF coefficients. In thermo.compressibleGas pick an existing species from $FOAM_ETC/thermoData whose molar mass and Cp(T) are the best available proxy for the pseudo-species. Document the substitution in the case to make the assumption auditable later.
The heterogeneous radiation coefficients (a, as, borderAs, borderL, E) rarely appear in literature with the right semantics. A workable approach is to tune them on a small TGA-like case so that the simulated heating rate matches an experiment, then reuse the tuned values across geometries that share the same surface chemistry and pellet morphology.
Gas-phase reactions are typically orders of magnitude faster than heterogeneous solid reactions. When both are active, gas chemistry dominates the chemistry-limited time step and can make slow processes (e.g. gasification of a whole bed) very expensive. Practical implications:
- For slow heterogeneous processes, prefer chemistry mechanisms with the minimum gas-phase detail needed for the result you care about.
maxCo,maxDi, andinitialChemicalTimeStepincontrolDict/chemistryPropertiesinteract — tightening any one of them can mask the actual bottleneck. Check the solver log for which limit is binding before tuning.
- Run
setPorosity(if used) beforedecomposePar— it operates on the reconstructed mesh. - The
totalMassPorousGasificationFoamutility also requires a reconstructed case. RunreconstructParfirst, or operate on the un-decomposed run. paraView -builtincan visualise the decomposed processor directories directly without reconstruction — useful for very large cases.- Re-running a regression baseline must use the same
numberOfSubdomainsas the original — floating-point sensitivity to decomposition is real (see the "Regression Testing" section below).
setPorosity(possibly outdated) — generatesporosityFandDffields from medium parameters (particle diameter, tortuosity, permeability). Editable medium description inutilities/setPorosity/medium.H. Kept for backward compatibility — for new cases prefer thesetFields+ STL workflow (see Tips).totalMassPorousGasificationFoam— post-processing diagnostic that writestotalMass.txt(time, integrated solid mass∫ρ_s · (1−porosityF) dV) for the run. Operates on a reconstructed case — runreconstructParfirst if the case was decomposed.
Numerical regression tests live under applications/test/regression/. The framework runs selected tutorial cases, extracts a small set of summary scalars produced by OpenFOAM volFieldValue function objects, and diffs the results against a committed reference baseline within a numerical tolerance.
The point is to give refactors a safety net: any change that perturbs the selected scalars beyond tolerance fails the regression and surfaces a clear diff.
The framework lives under applications/test/regression/ (top-level Allrun/Allclean, cases.list, tools/runCase.sh, tools/compareScalars.py). Per-case bits live in tutorials/cases/<caseName>/system/regressionFunctions (volFieldValue blocks included from controlDict) and tutorials/cases/<caseName>/reference/postProcessing/ (committed baseline).
Two steps per case:
- Drop a
system/regressionFunctionsfile describing the metrics to extract (seetutorials/cases/charOnlyMoveCases/serial/system/regressionFunctionsfor the pilot example). Add#includeIfPresent "regressionFunctions"at the bottom ofsystem/controlDictinside afunctions { ... }block. - Add the case path to
applications/test/regression/cases.list.
Then capture a baseline (see below).
Run the case with the OpenFOAM toolchain available and commit the resulting postProcessing/ under reference/:
cd tutorials/cases/charOnlyMoveCases/serial
./Allclean
./Allrun
mkdir -p reference
cp -r postProcessing reference/
git add reference
git commit -m "Capture charOnlyMoveCases/serial regression baseline"After commit, every subsequent run of applications/test/regression/Allrun compares fresh output against reference/postProcessing/ and reports PASS/FAIL.
cd applications/test/regression
./Allrun # run every case in cases.list
./Allrun --case charOnlyMoveCases/serial # run a single case
./Allrun --rtol 1e-3 # override default relative toleranceExit code 0 means all cases within tolerance — non-zero means at least one case failed. The script prints a per-case PASS/FAIL summary and, on failure, the rows that diverged.
tools/compareScalars.py walks reference/postProcessing/<funcName>/<time>/ files and, for each, locates the matching freshly-produced file under postProcessing/<funcName>/<time>/. It compares row-by-row, column-by-column with the rule:
pass if abs(a - b) <= atol + rtol * max(|a|, |b|)
Defaults: rtol=1e-4, atol=1e-12. Both can be overridden per-run. The header line of OpenFOAM volFieldValue.dat files is parsed, so column names appear in failure reports.
- Comparison is over scalar reductions extracted by function objects, not full field comparisons. A regression that perfectly cancels in every selected scalar will go undetected. Add more metrics to catch a wider class of bugs.
- Floating-point sensitivity to MPI decomposition is real: rerun on the same
numberOfSubdomainsas the baseline. The pilot casecharOnlyMoveCases/serialruns serial (1 proc) by design — it is a small, fast fixture for the prescribed-Ussolid-transport path. (The fullcharOnlyMoveCases/paralleltutorial is large and its 4-proc run currently hits a processor-boundary breakdown in the moving-solid immersed boundary, so it is not used as a regression baseline.) Because the fixture is serial, this regression does not exercise the parallel path. - The reference baseline is only as correct as the run that produced it. When you change the model intentionally, regenerate the baseline and state why in the commit message.
| Symptom | Likely cause | Fix |
|---|---|---|
Cannot find library |
porousGasificationMediaDirectories not sourced |
Run source porousGasificationMediaDirectories |
undefined symbol |
Library version mismatch or incomplete rebuild | ./build.sh clean --all && ./build.sh build --all |
| Simulation crashes immediately | Missing initial fields | Check solidComponents match 0/Y* files |
| Negative temperatures | Too large time step, or aggressive reaction parameters | Reduce deltaT, maxCo, maxDi, or initialChemicalTimeStep |
| Porosity exceeds 1 or goes below 0 | Bed collapse too aggressive | Reduce deltaT or adjust criticalPorosity |
| Mass fractions do not sum to 1 | Missing Ydefault field or gas species |
Check all gas species have corresponding 0/ files |
| Slow convergence in pressure | Tight PIMPLE settings | Increase nCorrectors or relax p tolerance |
Parallel: decomposePar fails |
Missing decompose constraints | Ensure Ts, porosityF, porosityF0 use calculated or zeroGradient BCs |
A tour, not a reference. Equations, units, and algorithm choices live in the source files that implement them — Description blocks in the file banner, block comments above the relevant function, and inline narrative inside. This section narrates the flow and points at where to look.
Gas and solid coexist in every cell, distinguished by the porosity field porosityF ∈ [0, 1]: 1.0 is pure gas, 0.0 is pure solid, in-between is a porous medium containing both phases. Solid bulk density is rhos = rho · (1 − porosityF), where rho is the intrinsic solid material density. Mass, momentum, and energy are exchanged between phases via coupling source terms computed by the pyrolysis/chemistry model.
The main loop is in porousGasificationFoam/porousGasificationFoam.C. Each piece of work is pulled in via an #include, so the file reads top-to-bottom as a sequence. The steps below cite the include or function that does the work:
- Time-step control — Courant (gas), diffusion (solid), and chemistry timescale are combined into one stable
deltaT. SeesetMultiRegionDeltaT.HandupdateChemistryTimeStep.H. - DEM coupling (compiled only when
WITH_YADEis defined) — particle positions and velocities are exchanged with YADE, then the interpolated solid-velocity field is computed (raw per-cell average, then Laplace-smoothed into adjacent solid cells). SeelambdaDotModel::update()inporousGasificationMedia/DEM/lambdaDotModel.C. - Radiation — heterogeneous radiation model (
heterogeneousP1orheterogeneousMeanTemp) updates the solid radiative source term. SeeporousGasificationFoam/radiation.HandporousGasificationMedia/radiationModels/. - Solid phase evolution — the heart of the solver: per-cell chemistry ODE, solid species mass conservation, porosity evolution (with optional bed-collapse), and the solid energy equation. See
volPyrolysis::evolveRegion()inporousGasificationMedia/pyrolysisModels/pyrolysisModel/volPyrolysis/volPyrolysis.C. - Gas continuity — gas-phase density update with the solid-to-gas mass source. See
porousGasificationFoam/rhoEqn.H. - PIMPLE loop — momentum (
UEqn.H) with Darcy/Forchheimer porous resistance, gas species (YEqn.H), gas energy (EEqn.H), and pressure correction (pEqn.HorpcEqn.Hdepending onpimple.consistent()). - Turbulence correction —
turbulence->correct()in the main loop.
| Question | Where to look |
|---|---|
| What equation does this step solve? Which terms, which units? | Description block in the file banner and the comment block above the relevant evolve* / solve* / *Eqn function in the corresponding .C/.H. |
| Which input dictionary keys does X read? | Part I → Input File Reference. |
| What initial fields does a case need? | Part I → Required Initial Fields. |
| Where does this field get constructed? | porousGasificationFoam/createFields.H for solver fields; createDEMFields.H for DEM-coupled fields. |
| How does the solver layout map to OpenFOAM modules? | Part I → Project Structure. |
If you find a discrepancy between this tour and what the code does, the code wins — please open an issue or PR.
Contributing to this repository. New contributors: start here.
Branches use a short prefix that signals intent, followed by a kebab-case descriptive name. Five prefixes cover everything:
| Prefix | Used for |
|---|---|
feature/ |
New user-facing capability — a new solver field, model, or tutorial exercising real functionality. |
fix/ |
Observable bug becomes right. |
refactor/ |
Code restructure intended not to change behaviour. Flag for careful review. |
docs/ |
Documentation only (README, AGENTS.md, comments). |
chore/ |
Everything else meta — formatter configs, build/Make tweaks, .gitignore, dependency bumps, tooling. |
Examples: feature/UsInterp-laplace-smoothing, fix/regression-allrun-set-u, chore/format-and-docs.
A single branch may bundle multiple low-risk meta concerns (e.g. formatter, docs, and dev-workflow changes can ride on one chore/... branch). Anything that can affect numerical results stays on its own branch.
The repository uses squash merge — each PR becomes one commit on main, whose message is the PR title followed by the PR body. This keeps main linear and easy to scan with git log --oneline, while preserving the why and verification context inside git log / git show.
Because the squash commit is permanent and per-branch commits are not, the convention below applies to PR titles and bodies, not to individual commits on your working branch. Commit however you like locally; the squash collapses it.
<type>[(<scope>)]: <imperative subject>
<type>is one offeat,fix,refactor,docs,chore— the same vocabulary as branch prefixes (featis the short form offeature).<scope>is optional. Use it when it adds clarity (e.g.fix(regression): ...vs an unrelated bug fix). Plausible scopes in this repo:regression,DEM,solver,tutorials,README,AGENTS,pyrolysis,chemistry.- Subject is in imperative mood, lowercase first letter after the colon, ≤72 characters, no trailing period.
Examples:
feat(DEM): add UsInterp Laplace smoothing for solid velocity
fix(regression): keep Allrun/Allclean working under set -u
docs(README): clarify default chemistry
chore: apply clang-format across solver
Opening a PR auto-populates the description from .github/pull_request_template.md. Three sections:
- Summary — one or two sentences on what the PR changes. Always fill this in.
- Why — motivation: symptom, missing capability, or bug. Skip if the diff is obviously self-justifying (typo fix, formatter run).
- Verification — what you actually ran or checked: build target, tutorial cases, regression suite, residual sanity. Skip if the change cannot affect runtime (README only).
Aim for terse and specific. The body becomes part of main's history once squash-merged, so future-you (and git log) benefit from precision now.
Source code is the source of truth for physics, equations, units, and algorithm choices. Comments live next to the implementation they describe — OpenFOAM-style file banner with a Description block, //- briefs above function declarations, and // ... narrative inside function bodies for non-obvious steps.
The README's Part II is a tour: it narrates the per-step flow and points into the code. It does not restate equations or implementation detail — those belong in the source file that implements them, where they cannot drift unnoticed during refactors.
When you submit a PR:
- Behaviour change → update the relevant code-comment block in the same commit.
- Loop structure or step ordering change → update Part II's per-step tour too.
- New documentation insight → if it's about what the code does, write it as a code comment. If it's about how to use the solver or how to navigate the codebase, it belongs in the README.
If you catch a Part II claim that's already in the code as a comment, replace it with a pointer in the same PR. The migration is opportunistic — no need to wait for a dedicated cleanup branch.
If you use this solver, please cite:
Żuk, P. J., Tużnik, B., Rymarz, T., Kwiatkowski, K., Dudyński, M., Galeazzo, F. C., & Krieger Filho, G. C. (2022). OpenFOAM solver for thermal and chemical conversion in porous media. Computer Physics Communications, 278, 108407.
Paweł Jan Żuk, Bartosz Tużnik, Tadeusz Rymarz, Ali Ebrahimi Pure, Kamil Kwiatkowski, Marek Dudyński, Flavio C. C. Galeazzo, Guenther C. Krieger Filho, Filip Mróz (foam-extend-4.1 to v2406 port)
This repository ships an AGENTS.md at the root with operating instructions for AI coding assistants (Claude Code, OpenCode, anything following the AGENTS.md / CLAUDE.md conventions). Open it if you collaborate with an agent on this codebase.