External pnextract / pnflow Benchmark¶
This report documents a controlled verification study of voids against a
fixed external reference dataset generated with the Imperial College
pnextract + pnflow workflow. After instrumenting the checked-in reference
codes, the benchmark is now interpreted in three layers:
- imported-CNM parity, where
voidssolves the savedpnextractnetwork, and - full-workflow comparison, where
voidsre-extracts the binary image withsnow2before solving, and - native maximal-ball comparison, where
voidsuses its dependency-free maximal-ball extractor with external-reservoir boundary pores on the flow axis.
That split matters scientifically. The first layer is now a same-network software-verification test, while the image-extraction layers remain end-to-end workflow comparisons against an independent external PNM pipeline.
The reproducible artifact for this report is notebook
notebooks/15_mwe_external_pnflow_benchmark.ipynb.
Goal¶
The benchmark answers the following question:
Given the same binary segmented volume, how different is the apparent permeability predicted by:
voidsafter importing the savedpnextractCNM network and solving it with explicitpnflowcompatibility enabled,voidsaftersnow2extraction and single-phase PNM solution, andvoidsafter native maximal-ball extraction and single-phase PNM solution, and- the saved external reference built from
pnextractnetwork extraction pluspnflowtransport simulation?
The imported-CNM branch is not a workflow-level comparison anymore. It is a
same-network transport cross-check against the checked-in pnflow code path.
The snow2 and native maximal-ball branches are still workflow-level
comparisons. A mismatch there is not automatically a voids bug, because the
two sides differ in:
- extracted topology
- pore and throat geometry assignment
- constitutive closure
- single-phase solver implementation
Governing Formulations¶
voids PNM Workflow¶
For each committed binary volume, voids:
- extracts a pore network with either
snow2or native maximal-ball extraction - prunes to the
x-spanning subnetwork - solves the steady graph pressure system
with throat fluxes
and apparent permeability from Darcy's law
For this benchmark, the voids side uses:
conductance_model = "valvatne_blunt"solver = "direct"- \(\mu = 1.0 \times 10^{-3}\) Pa s
For the imported-CNM branch, voids also enables the explicit importer option
pnflow_solver_box_compat=True. This reproduces a checked-in Imperial
preprocessing quirk that excludes the first physical pore from the solver box
and promotes it to a solver-boundary pore. That option is deliberately opt-in
in the public API because it reflects pnflow compatibility, not a generic
physical boundary rule.
External pnextract / pnflow Reference¶
The reference data committed in examples/data/external_pnflow_benchmark/
were generated earlier by:
- exporting the binary image to MetaImage format
- extracting a network with
pnextract - running
pnflowon the resulting*_node*.dat/*_link*.datfiles - recording the upscaled permeability and porosity from
*_upscaled.tsv
The current notebook does not rerun those binaries. It reads the committed
reference outputs instead. That is an intentional reproducibility choice: the
benchmark remains runnable even if pnextract or pnflow are unavailable in
future environments.
Scientifically, the correct statement is:
- the benchmark compares
voidsagainst a fixed external workflow reference - it does not re-verify future upstream
pnextract/pnflowrevisions - the imported-CNM branch now isolates the checked-in
pnflowsingle-phase transport path from extraction differences - the
snow2branch still mixes extraction and constitutive-model differences
Fixed Reference Dataset¶
The committed reference bundle lives in
examples/data/external_pnflow_benchmark/ and includes:
manifest.csvwith case metadata and file paths- exact binary benchmark volumes as
void_volume.npy - saved
pnflowreports (*_pnflow.prt,*_upscaled.tsv) - saved extracted-network files (
*_node*.dat,*_link*.dat)
This design matters because it makes the benchmark stable against future changes in:
- the random generator implementation
- local build details of the external codes
- availability of the external binaries
All cases in this report use:
- shape
(32, 32, 32) - flow axis
x - voxel size
2.0e-6 m - fluid viscosity
1.0e-3 Pa s
The five-case set is:
| Case | Target porosity | Blobiness | Seed used |
|---|---|---|---|
phi032_b14 |
0.32 | 1.4 | 401 |
phi035_b16 |
0.35 | 1.6 | 501 |
phi038_b18 |
0.38 | 1.8 | 601 |
phi040_b18 |
0.40 | 1.8 | 901 |
phi041_b20 |
0.41 | 2.0 | 701 |
Why The Two Methods Differ¶
Even when both workflows are implemented correctly, the benchmark branches do not answer the same question.
| Aspect | voids imported CNM | voids from image | External reference |
|---|---|---|
| Input geometry | Saved pnextract CNM | Same committed binary image | Same committed binary image |
| Extraction backend | Reuse saved external extraction | snow2 or native maximal-ball | pnextract |
| Unknowns | Imported CNM pores plus explicit pnflow compatibility on solver-box handling | One pressure unknown per retained pore | pnflow network unknowns on pnextract output |
| Conductance closure | valvatne_blunt conduit model, matched throat-by-throat to checked-in pnflow | valvatne_blunt conduit model | pnflow internal network model |
| Main question | Does voids reproduce the checked-in pnflow single-phase solve on the same pore network? | How different is the full voids image-to-network workflow from the external workflow? | Reference branch |
Therefore, near machine-precision agreement is expected for the imported-CNM branch, while much larger disagreement is expected for the image-re-extraction branch. That contrast is now the main signal this benchmark is meant to expose.
Figures¶

Left: voids permeability against the saved pnflow permeability with the
identity line. The imported-CNM points sit on the identity line at plotting
resolution once pnflow compatibility is enabled. Right: per-case relative
difference for the imported-CNM and snow2 branches.

Porosity-permeability trend for the five committed benchmark cases. This is
useful for checking whether voids and the external reference follow the same
macroscopic trend even when the pointwise values differ.
Results¶
The full CSV generated by the notebook is available here:
pnflow_5_case_results.csv.
pnflow_maxball_matched_physical_throat_summary.csv
and
pnflow_maxball_matched_physical_throats.csv
record the deeper physical-throat matching diagnostic generated from the
instrumented pnextract voxel-region outputs.
| Case | K_imported [m^2] |
K_pnflow [m^2] |
Imported rel. diff. [%] | K_snow2 [m^2] |
snow2 rel. diff. [%] |
K_maxball [m^2] |
maxball rel. diff. [%] |
|---|---|---|---|---|---|---|---|
phi032_b14 |
9.752e-15 |
9.752e-15 |
0.000024 |
6.097e-15 |
37.48 |
1.016e-14 |
4.00 |
phi035_b16 |
1.332e-14 |
1.332e-14 |
0.000031 |
1.702e-14 |
21.76 |
1.084e-14 |
18.64 |
phi038_b18 |
1.199e-14 |
1.199e-14 |
0.000235 |
2.092e-14 |
42.67 |
1.023e-14 |
14.68 |
phi040_b18 |
1.576e-14 |
1.576e-14 |
0.000301 |
2.033e-14 |
22.51 |
1.909e-14 |
17.45 |
phi041_b20 |
1.437e-14 |
1.437e-14 |
0.000125 |
3.926e-14 |
63.40 |
1.796e-14 |
19.98 |
Summary statistics for this five-case set:
- imported-CNM mean relative permeability difference:
0.000143 % - imported-CNM maximum relative permeability difference:
0.000301 % snow2mean relative permeability difference:37.56 %snow2maximum relative permeability difference:63.40 %- native maximal-ball mean relative permeability difference:
14.95 % - native maximal-ball maximum relative permeability difference:
19.98 % - imported-CNM physical pore counts match the saved
pnflowpore counts on all five cases - imported-CNM throat counts match the saved
pnflowthroat counts on all five cases - the native maximal-ball branch is closer than plain
snow2on mean permeability error for this five-case set, with mixed-sign residual errors across the five cases - after excluding explicit reservoir helper pores, native maximal-ball physical
pore-count differences are
1.19-7.25 %and physical throat-count differences are2.00-5.81 %across the five cases - the native maximal-ball branch now uses explicit helper boundary pores on the flow axis; this avoids imposing Dirichlet pressure directly at internal pore centers
- native maximal-ball conduit lengths are anchored on the ordered pair's second interface-supporting ball, matching the reference writer's use of its second-side throat ball when computing pore-to-throat lengths
Together, these checks are the key outcome of the investigation: once the same
pore network and the checked-in pnflow preprocessing are used, the
single-phase voids solve agrees with pnflow to plotting precision. The
remaining mismatch is therefore dominated by image-to-network extraction and
geometric reduction, especially conduit-area, shape-factor, and boundary
reservoir reduction details, not by the single-phase pressure solve.
The matched-throat diagnostic makes this more specific. Native physical
maximal-ball throats could be matched to 77-85 % of native physical
connections and 77-80 % of reference physical connections by voxel-region
overlap. On those matched physical throats, median radius differences are only
about 0.4-0.6 %, but median area differences are about 30-34 %, median
shape-factor differences are about 29 %, and median equivalent-conductance
differences are about 27-37 %. This points to conduit cross-section and
shape-factor reduction as the main remaining target.
The checked pnextract writer computes throat shape factors from
R^2 / (4 * |CrosArea|), where CrosArea is a vector of oriented interface
face counts. voids now exposes this convention for controlled experiments via
native maximal-ball extraction_kwargs={"throat_area_mode": "vector_magnitude"}.
voids also exposes a separate radius convention for the shape-factor
calculation via
extraction_kwargs={"throat_shape_factor_radius_mode": "surface_ball"}. These
are not defaults for the five-case benchmark because changing either scalar
convention alone did not improve the end-to-end permeability error. This
indicates that the remaining pnextract details, such as throat-surface ball
selection, oriented cross-area, and exported/raw throat-radius semantics, must
be matched together rather than one scalar convention at a time.
The one reference-logic change promoted to the default is the throat-length anchor: the native maximal-ball builder now uses the second side of each ordered region pair rather than whichever side has the larger supporting radius. On the five committed cases this slightly reduced the native maximal-ball mean relative permeability difference, but the change was adopted primarily because it matches the reference length construction.
We also tested a dependency-free internal approximation mode exposed as
backend="porespy_imperial". This still uses snow2, but starts from a
benchmark-tuned parameter profile (sigma=1.0, r_max=4,
boundary_width=1) chosen to move the five-case benchmark closer to the
committed pnextract reference without invoking any external binaries. On the
same five cases, that reduced the mean relative permeability difference from
about 37.56 % for the plain snow2 default to about 30.13 %, with the
maximum case dropping from about 63.40 % to about 47.50 %.
Interpretation¶
These results support the following conclusions:
- The
voidssingle-phase solver, conduit conductance model, and pressure boundary treatment reproduce the checked-in Imperialpnflowpath on the saved CNM networks to near machine precision once explicit compatibility is enabled. - The remaining mismatch in the image-extraction branches is morphology-sensitive and dominated by extraction differences rather than the single-phase solver.
- The imported-CNM parity result should be interpreted as compatibility with
the checked-in
pnflowcode path, not as proof that every Imperial preprocessing choice is a generic physical modeling rule. - The practical next target for closer workflow agreement is the native maximal-ball geometry/topology reduction, especially throat-surface grouping and high-connectivity cases, not a rewrite of the single-phase solver.
The practical interpretation is now split:
- the imported-CNM branch behaves like a strong software-verification test
- the
snow2and native maximal-ball branches remain external workflow comparisons between different image-to-network reductions
Limits Of This Verification¶
This report is intentionally narrow.
Important limits and assumptions:
- the reference is a committed saved dataset, not a rerun of the latest
upstream
pnextract/pnflowsource tree - the cases are small synthetic volumes, not real rocks
- the benchmark does not isolate extraction from constitutive-model effects
- the external reference was generated on one specific local build path, so it should be treated as a fixed comparison baseline
- agreement here does not imply agreement with direct-image references such as XLB