Skip to main content

Rule-based 3D geological reservoir modeling

Project description

ResMill

Rule-based 3D geological reservoir modeling in Python.

PyPI version Python License: MIT

Created by Ilgar Baghishov and Elnara Rustamzade.

ResMill generates geologically plausible synthetic 3D reservoir models — turbidite lobes, fluvial channel systems, deltas, and Gaussian heterogeneity fields — from a handful of physical parameters, with no commercial software required. It is a Python-native, pip-installable tool for subsurface modeling in oil & gas, groundwater, and carbon-storage workflows.

The modeling engines are derived from two established bodies of work:

  • Fluvial / channel / delta engine — ported from ALLUVSIM, the event-based fluvial simulator of Michael J. Pyrcz, Jeff B. Boisvert, and Clayton V. Deutsch (Pyrcz et al., 2009).
  • Turbidite-lobe generation — based on the rule-based lobe models of Wen Pan, Honggeun Jo, and Michael J. Pyrcz (Jo & Pyrcz, 2019; Jo et al., 2021).

See References for full citations.


Install

pip install resmill

For development (tests, notebooks):

git clone https://github.com/IlgarBaghishov/ResMill.git
cd ResMill
pip install -e ".[dev]"

Requires Python ≥ 3.10. Dependencies: NumPy, SciPy, Numba, Matplotlib.


Quick start

import resmill as rm

# A layer owns only its grid geometry (sizes in metres).
lobe = rm.LobeLayer(nx=64, ny=64, nz=32, x_len=640, y_len=640, z_len=32, top_depth=5000)

# create_geology() runs the physics and fills the property arrays.
lobe.create_geology(poro_ave=0.20, perm_ave=1.5, poro_std=0.03, perm_std=0.5, ntg=0.7)

# Outputs are (nx, ny, nz) numpy arrays:
lobe.poro_mat   # porosity, 0–1
lobe.perm_mat   # permeability, mD
lobe.active     # 0/1 reservoir (sand) mask
lobe.facies     # facies-class codes

# Built-in visualization (3 orthogonal slices as a cube):
rm.plot_cube_slices(lobe, title="Turbidite lobes")

How it works

ResMill has three core pieces:

  1. Layer — the base class. A layer is constructed with grid geometry only: nx, ny, nz (cell counts), x_len, y_len, z_len (extent in metres), top_depth (metres), and optional dip. No physics in the constructor.

  2. create_geology(...) — each layer type implements this. It runs the rule-based / stochastic engine and populates the property arrays. All physics parameters live here, not in __init__.

  3. Reservoir([layer_top, …, layer_bottom]) — stacks layers vertically into one model. Layers are listed top → bottom; every layer must share the same nx, ny, x_len, y_len, and each layer's base depth must equal the next layer's top_depth (the constructor validates this).

All arrays are shaped (nx, ny, nz) using meshgrid(..., indexing='ij').

Output properties

Attribute Meaning Units / values
poro_mat porosity linear 0–1
perm_mat permeability mD (linear)
active reservoir (sand) mask 0 / 1
facies facies class see table below

facies semantics vary by layer: channel/delta use the full multi-class Alluvsim codes; LobeLayer.facies is a lobe index; GaussianLayer has no multi-class facies — use active for its sand mask.

⚠️ Permeability units gotcha. For LobeLayer and GaussianLayer, the perm_ave / perm_std inputs are in log10(mD) space (e.g. perm_ave=1.5 → ~32 mD mean; sensible range 0–4). The output perm_mat is still linear mD. Passing a linear value like perm_ave=500 overflows and saturates the output. poro_ave / poro_std stay in linear 0–1.


Interpreting the figures

Every gallery image below is a 1×3 panel through the mid-planes of the model:

  • XY — map view (plan view) at mid-depth.
  • XZ and YZ — vertical cross-sections.

Sections follow the library's own convention (rm.plot_slices, rm.plot_cube_slices, origin='lower'): the vertical axis is the Z cell index increasing upward, so the top of the deposited interval is at the top of each section and the base is at the bottom.

Channel and delta models are coloured by the Alluvsim facies classes:

Code Facies Description
-1 FF floodplain (non-reservoir background)
0 FFCH abandoned-channel mud plug
1 CS crevasse splay
2 LV levee
3 LA lateral-accretion point bar
4 CH active channel fill

The facies are ordered by reservoir quality (FF < FFCH < CS < LV < LA < CH).

All gallery figures are reproducible from a clean checkout with docs/make_readme_figures.py.

Shared setup for the gallery snippets

Every snippet below assumes this preamble:

import resmill as rm

# Standard 64×64×32 grid (640×640×32 m).
GRID = dict(nx=64, ny=64, nz=32, x_len=640, y_len=640, z_len=32, top_depth=0)

Gallery — geology types and presets

LobeLayer — turbidite lobes

Deep-water lobes deposited by sediment-gravity flows, with compensational stacking (younger lobes preferentially fill topographic lows), optional upthinning toward lobe margins, and Bouma-sequence grading. Produces clean sand lobe bodies in a mud background.

lobe = rm.LobeLayer(**GRID)
lobe.create_geology(poro_ave=0.20, perm_ave=1.5, poro_std=0.03, perm_std=0.5,
                    ntg=0.7, r_ave=180, r_std=30, asp=1.5, upthinning=True)
rm.plot_slices(lobe)

LobeLayer

GaussianLayer — SGS heterogeneity field

Sequential Gaussian simulation produces a spatially-correlated continuous porosity/permeability field, thresholded to a target net-to-gross. Use it as a heterogeneous background or for sand/shale distributions without discrete geobodies.

gauss = rm.GaussianLayer(**GRID)
gauss.create_geology(poro_ave=0.18, perm_ave=1.5, poro_std=0.04, perm_std=0.5, ntg=0.6)
rm.plot_slices(gauss.poro_mat, axis=2)   # Z slices of the porosity field

GaussianLayer

ChannelLayer — fluvial systems

A single, faithful port of the ALLUVSIM event-based engine: channels meander, migrate, avulse, cut off oxbows, and build levees and crevasse splays. Behaviour is driven by importable presets that reproduce Pyrcz's canonical fluvial reservoir architectures — pass any preset with **PRESET and override individual parameters as needed.

from resmill.layers.channel import (
    PV_SHOESTRING, CB_JIGSAW, CB_LABYRINTH, SH_DISTAL, SH_PROXIMAL, MEANDER_OXBOW,
)

PV_SHOESTRING — paleo-valley shoestring

Low net-to-gross (~0.10): a single high-sinuosity channel with prominent lateral-accretion point bars, leaving isolated "shoestring" sand bodies encased in floodplain mud.

ch = rm.ChannelLayer(**GRID)
ch.create_geology(seed=42, **PV_SHOESTRING)
rm.plot_slices(ch)

PV_SHOESTRING

CB_JIGSAW — channel-and-bar jigsaw

Moderate NTG (~0.30) with heavy avulsion-inside, so channel-and-bar bodies amalgamate and interlock like a jigsaw, separated by FFCH mud plugs.

ch = rm.ChannelLayer(**GRID)
ch.create_geology(seed=42, **CB_JIGSAW)
rm.plot_slices(ch)

CB_JIGSAW

CB_LABYRINTH — labyrinthine channel bodies

Many aggradation events with low avulsion, giving isolated, poorly-connected channel bodies threaded through mud — a labyrinthine connectivity pattern.

ch = rm.ChannelLayer(**GRID)
ch.create_geology(seed=42, **CB_LABYRINTH)
rm.plot_slices(ch)

CB_LABYRINTH

SH_DISTAL — distal sand sheet

High NTG (~0.50) with thick levee blankets, producing sheet-like, well-connected distal sandstone.

ch = rm.ChannelLayer(**GRID)
ch.create_geology(seed=42, **SH_DISTAL)
rm.plot_slices(ch)

SH_DISTAL

SH_PROXIMAL — proximal sand sheet

High NTG (~0.40) from heavy avulsion plus wide, shallow channels that amalgamate into a proximal sand sheet.

ch = rm.ChannelLayer(**GRID)
ch.create_geology(seed=42, **SH_PROXIMAL)
rm.plot_slices(ch)

SH_PROXIMAL

MEANDER_OXBOW — meander belt with oxbow mud plugs

A single sinuous channel that migrates across the floodplain until tight bends neck-cut into oxbow loops; each cutoff is painted as an FFCH mud plug, giving the classic neck-cutoff → oxbow lake → mud-plug succession stacked into a multi-storey meander belt.

ch = rm.ChannelLayer(**GRID)
ch.create_geology(seed=42, **MEANDER_OXBOW)
rm.plot_slices(ch)

MEANDER_OXBOW

DeltaLayer — distributary delta

Built on the same fluvial engine (defaults from the DELTA_FAN preset): a trunk channel that bifurcates into a distributary network fanning across the grid, stacked over several generations. Knobs like trunk_length_fraction, progradation_fraction, branch_spread_deg, and paint_mouth_bars shape the fan.

delta = rm.DeltaLayer(**GRID)
delta.create_geology(seed=3)            # DELTA_FAN defaults are applied automatically
rm.plot_slices(delta)

DeltaLayer


Stacking layers into a reservoir

Layers are stacked top → bottom; each layer's base depth must meet the next layer's top_depth.

import resmill as rm
from resmill.layers.channel import MEANDER_OXBOW

nx, ny, x_len, y_len = 64, 64, 640, 640

# Top: delta fan, 10 m thick (5000–5010 m)
top = rm.DeltaLayer(nx=nx, ny=ny, nz=10, x_len=x_len, y_len=y_len, z_len=10, top_depth=5000)
top.create_geology(seed=3)

# Middle: turbidite-lobe blanket, 10 m thick (5010–5020 m)
mid = rm.LobeLayer(nx=nx, ny=ny, nz=10, x_len=x_len, y_len=y_len, z_len=10, top_depth=5010)
mid.create_geology(poro_ave=0.20, perm_ave=1.5, poro_std=0.03, perm_std=0.5, ntg=0.7)

# Bottom: meander belt with oxbow mud plugs, 12 m thick (5020–5032 m)
bot = rm.ChannelLayer(nx=nx, ny=ny, nz=12, x_len=x_len, y_len=y_len, z_len=12, top_depth=5020)
bot.create_geology(seed=1, **MEANDER_OXBOW)

reservoir = rm.Reservoir([top, mid, bot])
print(reservoir.poro_mat.shape)         # (64, 64, 32)
rm.plot_cube_slices(reservoir.poro_mat, title="Stacked reservoir — porosity")

Layer & preset reference

Layer Preset(s) Geology
LobeLayer Turbidite lobes; compensational stacking, upthinning, Bouma grading
GaussianLayer SGS continuous porosity/perm heterogeneity field
ChannelLayer PV_SHOESTRING Isolated shoestring channel sands (low NTG)
CB_JIGSAW Amalgamated channel-and-bar jigsaw
CB_LABYRINTH Isolated labyrinthine channel bodies
SH_DISTAL Distal sheet sandstone (high NTG)
SH_PROXIMAL Proximal amalgamated sand sheet
MEANDER_OXBOW Multi-storey meander belt with oxbow mud plugs
DeltaLayer DELTA_FAN Prograding distributary-fan delta

Channel presets are plain dicts — start from one and override any parameter, e.g. ch.create_geology(seed=0, **PV_SHOESTRING, NTGtarget=0.15).


References

If you use ResMill in published work, please cite the methods it is built on:

  • ALLUVSIM (fluvial / channel / delta engine): Pyrcz, M. J., Boisvert, J. B., & Deutsch, C. V. (2009). ALLUVSIM: A program for event-based stochastic modeling of fluvial depositional systems. Computers & Geosciences, 35(8), 1671–1685.
  • Rule-based lobe models: Jo, H., & Pyrcz, M. J. (2019). Robust Rule-Based Aggradational Lobe Reservoir Models. Natural Resources Research, 29(2), 1193–1213.
  • Deep-water lobe modeling: Jo, H., Pan, W., Santos, J. E., Jung, H., & Pyrcz, M. J. (2021). Machine Learning Assisted History Matching for a Deepwater Lobe System. Journal of Petroleum Science and Engineering, 207, 109086.

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

resmill-0.1.1.tar.gz (89.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

resmill-0.1.1-py3-none-any.whl (84.7 kB view details)

Uploaded Python 3

File details

Details for the file resmill-0.1.1.tar.gz.

File metadata

  • Download URL: resmill-0.1.1.tar.gz
  • Upload date:
  • Size: 89.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.9

File hashes

Hashes for resmill-0.1.1.tar.gz
Algorithm Hash digest
SHA256 e2b1f15089dc1944e5e0fc351e789e8e3633d8eed56b65460f900911d9975092
MD5 430b6d699e0e5070faf410c9afda588b
BLAKE2b-256 168e723cf79f15b747a87015404e0e31145f3a6376ebb0a56f0b90a6e78a519f

See more details on using hashes here.

File details

Details for the file resmill-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: resmill-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 84.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.9

File hashes

Hashes for resmill-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 6adee662794aa9500fb928d1b647ede8090ce7d9f400139aaa29fd235e152b24
MD5 601d8b45b10c20e9059c45ed9fe0439f
BLAKE2b-256 54bba42311592b05d4927c1d1073e26254036059caf5e87997455015194277b3

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page