darcy_flow — permeability from steady pressure data¶
| At a glance | |
|---|---|
| Problem | Log-permeability of a closed reservoir from sparse pressure gauges under several well configurations (hydraulic tomography) |
| Unknown | Y = log k, 32² |
| Physics | −∇·(k∇p) = well sources with no-flow boundaries; harmonic-mean finite volumes, PCG, implicit-function-theorem adjoint |
| Measurement | gauge pressures on an 8 × 8 sensor lattice for 6 injector / producer configurations; data on a 2× grid in float64 |
| Difficulty classes | smooth, channels |
| Baselines | grid |
| Metrics | PSNR · SSIM · MSE · relative error · high-permeability IoU |
| Run | nefi run darcy_flow --smoke · full: nefi run configs/darcy_flow_full.yaml --device cuda |
Gallery run (smoke preset: 16², 4 well configurations; 400 steps, 3.9 s on a CPU): PSNR 24.3 dB.
Recover the log-permeability Y = log k of a closed 2-D reservoir from sparse pressure gauges
recorded for several injector/producer well configurations (hydraulic tomography / history
matching; Bear 1972, Yeh & Liu 2000, Oliver, Reynolds & Liu 2008):
from nefi.instances.darcy_flow import DarcyFlow
out = DarcyFlow(n=16, n_configs=4, n_sensors=6, steps=(50, 100), lr=1e-2, n_octaves=3).run(seed=0)
out.metrics # {"psnr", "ssim", "mse", "relative_error", "high_k_iou"} (on log k)
nefi run darcy_flow --smoke, nefi run configs/darcy_flow_full.yaml --device cuda.
Example: python examples/darcy_flow.py --config configs/darcy_flow_smoke.yaml.
Physics and discretization (nefi.instances.darcy_flow.operator)¶
DarcyOperator(domain, wells, sensors=..., obs_shape=...)— anEllipticOperatorwithsigma_transform="exp"(fieldlog_k), harmonic-mean faces, pure-Neumann Jacobi-PCG (the balanced well rates satisfy the compatibility condition; the solver still projects and pins the mean pressure), implicit-function adjoint, warm starts, one right-hand side per configuration.- Wells (
well_configurations): configurationchas an injector (+Q) at angleθ_c = well_angle + πc/n_configsand a producer (−Q) opposite, atwell_radius × Lfrom the centre; point sources are cloud-in-cell spread (point_source_rhs), so the discrete rate is exact and the source moves smoothly with its position at every resolution. - Sensors (
sensor_mask): a regularn_sensors × n_sensorslattice snapped to cells; for each configuration gauges withinwell_exclusioncells of its wells are dropped (the well-cell pressure has a grid-dependent log singularity and is not a resolution-consistent observable). - Observable — the pressure solved on the operator's grid is sampled on the native grid (bilinear from coarser curriculum grids, cell averages from finer ones) and referenced to the mean over the gauges (gauge pressure). Coarse stages therefore predict the native sensors directly: at 32² the coarse (16²) model misfit at the true field is 5.6 % of the signal (vs 19 % when the data were instead averaged onto coarse cells).
Data (inverse-crime guard)¶
Same physics on a 2× finer grid in float64, gauge values = native cell averages, Gaussian noise
noise_std × max|p|; fidelity_tag = "elliptic-fv-2x-float64". Scenes (DarcyScenes, closed-form
in position so fine and native grids agree):
| class | content |
|---|---|
smooth |
log-normal Y = μ + s·G, G a unit-variance Gaussian random field with squared-exponential covariance of length corr_length (random Fourier features) |
channels |
weaker background (channel_bg_std) + 1–3 sinuous high-permeability channels (+channel_contrast in Y) |
channel_width must span ≥ 2 native cells (smoke: 0.12–0.16 at 16²; full: 0.05–0.08 at 64²).
Inversion¶
NeuralField (ReLU, few octaves) with Bounded(log_k_min, log_k_max) head → k = exp(Y); masked
MSE + isotropic TV(Y); two-stage curriculum (n/2 → n if n/2 ≥ 12, else both at n);
baseline grid.
Metrics¶
psnr, ssim, mse, relative_error on Y = log k, and high_k_iou: IoU of
Y > mean(Y_gt) + 1·std(Y_gt) (the flow paths).
Configuration (DarcyConfig)¶
| field | smoke | full | meaning |
|---|---|---|---|
n, length |
16, 1 | 64, 1 | grid / side length |
n_configs, well_radius, well_rate, well_angle |
4, 0.3, 1, 0.3 | 8, 0.3, 1, 0.3 | wells |
n_sensors, sensor_jitter, sensor_seed, well_exclusion |
6, 0, 12345, 1.5 | 12, 0, 12345, 1.5 | gauges |
tol, grad_mode, warm_start, check_every |
1e-6, ift, true, 1 | 1e-6, ift, true, 8 | PDE solver |
viscosity, bc, well_spread, face_mode |
1, neumann, linear, harmonic | same | physics |
scene, log_k_mean, log_k_std, corr_length |
smooth, 0, 0.8, 0.15 | channels, 0, 0.8, 0.15 | scenes |
channel_contrast, channel_bg_std, n_channels, channel_width |
2.5, 0.4, (1, 3), (0.12, 0.16) | 2.5, 0.4, (1, 3), (0.05, 0.08) | channels |
channel_amplitude, channel_wavelength, clip_margin |
(0.05, 0.12), (0.4, 0.8), 0.2 | same | channel meanders, scene clamp |
log_k_min, log_k_max |
−3, 4 | −3, 4 | head bounds |
noise_std, supersample |
2e-3, 2 | 2e-3, 2 | data |
hidden, depth, n_octaves, activation |
64, 3, 3, relu | 256, 6, 4, relu | field |
steps, lr, tv |
(50, 100), 1e-2, 1e-5 | (1000, 3000), 5e-3, 1e-5 | optimization |
Smoke result (seed 0, smooth, CPU ≈ 7 s): neural PSNR 20.8 dB / SSIM 0.83 vs uniform 12.8 dB /
0.00; grid 22.0 dB / 0.88. At 32² (6 configurations, 8×8 gauges, 450 steps, mean of both classes):
neural PSNR 24.1 dB / SSIM 0.77 / high-k IoU 0.64.
