A reduced-order Python repository for simulating espresso extraction as flow through a compressed porous puck. The repository couples:
- stochastic bimodal grind-size sampling for each cell in the puck,
- 2D heterogeneous radial-axial permeability and porosity fields,
- a Darcy pressure solve with an optional Forchheimer-style inertial correction,
- a simple two-pool extraction model with wetting/preinfusion effects,
- black-and-white plotting for publication-friendly figures.
This is not a CFD-grade or experimentally validated digital twin. It is a compact, modifiable research codebase intended for rapid hypothesis testing and visualization.
- User-controlled grind settings
- coarse median particle size
- fines median particle size
- fines mass/number fraction
- lognormal spreads for each mode
- Compression / packing controls
- uncompressed porosity
- compression level
- heterogeneous porosity fluctuations
- 2D heterogeneity
- correlated radial-axial random fields in grind size, fines fraction, and porosity
- Flow model
- Darcy pressure field solve in an axisymmetric radial-axial slice
- optional local Forchheimer correction for inertial loss at higher gradients
- Extraction model
- wetting state that ramps during preinfusion
- fast and slow soluble pools
- flow-dependent release kinetics
- Outputs
- timeseries CSV
- summary JSON
- compressed field file (
fields.npz) - black-and-white PNG plots
espresso-flow/
├── CITATION.cff
├── LICENSE
├── README.md
├── pyproject.toml
├── docs/
│ ├── demo_baseline/
│ └── demo_montecarlo/
├── examples/
│ ├── baseline_config.json
│ └── fast_flow_config.json
├── src/
│ └── espresso_darcy_model/
│ ├── __init__.py
│ ├── cli.py
│ ├── config.py
│ ├── medium.py
│ ├── plotting.py
│ ├── random_fields.py
│ ├── simulation.py
│ └── solver.py
└── tests/
├── test_medium.py
└── test_simulation.py
Create a virtual environment and install the package in editable mode:
python -m venv .venv
source .venv/bin/activate
pip install -e .Write a default configuration:
espresso-darcy template --output examples/default_config.jsonRun a single simulation:
espresso-darcy run --config examples/baseline_config.json --output outputs/baselineRun a Monte Carlo ensemble:
espresso-darcy montecarlo --config examples/baseline_config.json --runs 16 --output outputs/montecarloOverride key parameters from the command line:
espresso-darcy run --config examples/baseline_config.json --output outputs/finer_grind --coarse-median-um 280 --fines-fraction 0.24 --compression-level 0.62 --brew-bar 8.5Each cell samples particles from a bimodal lognormal mixture:
- coarse mode with median
coarse_median_um - fines mode with median
fines_median_um - mixture weight
fines_fraction
An effective Sauter mean diameter is computed from the sampled particles and used in a Kozeny–Carman-style permeability relation.
The local permeability is built from:
- sampled effective particle size,
- compressed porosity,
- a fines penalty term,
- a user-facing empirical calibration scale.
The permeability field is therefore emergent rather than directly prescribed.
The puck is represented as an axisymmetric radial-axial slice. Smooth Gaussian random fields perturb:
- local coarse median size,
- local fines fraction,
- local porosity.
A normalized pressure field is solved from
∇·(K ∇p) = 0
with fixed pressure on the top and bottom boundaries and no-flux on the side wall.
The main pressure solve is Darcy-based. If enabled, the local velocity magnitude is corrected with a quadratic Darcy–Forchheimer relation during post-processing and extraction-rate evaluation.
Each cell contains a fast and a slow soluble pool. Local release rates depend on:
- wetting state,
- local flow magnitude,
- brew temperature.
This creates channel-sensitive extraction maps without requiring a full advection-diffusion PDE.
A typical single run produces:
psd.png— bimodal particle-size histogramspermeability_map.png— grayscale permeability mappressure_flow_map.png— pressure contours with streamlinesbrew_timeseries.png— pressure, flow, beverage mass, TDS, yield, wetnessextraction_map.png— final local extraction pattern
All plots use a black/white or grayscale palette.
Sample output folders generated from the included example configurations are provided in docs/demo_baseline/ and docs/demo_montecarlo/.
Important configuration groups:
geometry: basket radius, puck depth, grid resolutiongrind: average size, fines fraction, distribution spreads, samples per cellpacking: porosity model, heterogeneity scales, permeability calibrationflow: viscosity, pressure schedule, Forchheimer switchextraction: dose, soluble fraction, kinetic coefficients, temperature
The default configuration is intentionally moderate rather than “correct” for every machine or coffee. You will usually want to calibrate:
packing.permeability_scalegrind.coarse_median_umgrind.fines_fractionpacking.compression_levelextraction.k_fast_sextraction.k_slow_s
against your own shot time and target beverage mass.
- Start from
examples/baseline_config.json. - Adjust average grind size and fines fraction until the flow trajectory is plausible.
- Tune
packing.permeability_scaleif the shot is globally too fast or too slow. - Tune
k_fast_sandk_slow_sto reach a desired extraction yield or TDS level. - Run
montecarloto inspect shot-to-shot variability from stochastic heterogeneity.
Install the dev extras and run:
pip install -e .[dev]
pytest- The extraction chemistry is reduced-order.
- The Forchheimer correction is local rather than part of a fully coupled nonlinear pressure solve.
- The wetting model is heuristic.
- Basket hardware, shower-screen details, puck fracture, fines migration, and temperature gradients are not explicitly resolved.
- Parameters are best treated as calibration knobs unless you have your own experimental data.
Please cite the software using the metadata in CITATION.cff.
This repository was designed to be compatible with common ideas from the espresso / porous-media literature:
- Darcy-style flow through compressed coffee beds
- Kozeny–Carman-inspired permeability scaling
- bimodal grind distributions with a fines population
- spatial heterogeneity and channel-sensitive extraction
- preinfusion / wetting effects
- optional inertial corrections at high gradients
- explicit fines migration,
- a coupled nonlinear Forchheimer pressure solve,
- unsaturated infiltration before full saturation,
- temperature transport,
- measured grind histograms instead of synthetic mixtures,
- calibration against shot-weight and refractometer data.