From Outcrop
to Implicit Model
Everything a programmer needs to know about geology to build correct, defensible 3-D models — organized around the decisions you'll actually make in software like GemPy, Leapfrog, or Petrel. No fieldwork required.
What rock is, and how it remembers time
Before you can model anything you need the vocabulary: what rocks are made of, how they form, and the single rule (superposition) that lets us read geological history out of vertical position.
Rock types & the rock cycle
All rock falls into three families, defined by how they formed — this matters for modelling because each family produces a different kind of 3-D geometry.
| Family | Forms by | Typical geometry | Examples |
|---|---|---|---|
| Sedimentary | Particles deposited in layers, then compacted/cemented | Tabular, layered, follows superposition | Sandstone, Shale, Limestone, Coal |
| Igneous | Magma cooling (underground = intrusive, surface = extrusive) | Cross-cutting blobs, sheets, or layered flows | Granite, Basalt, Obsidian |
| Metamorphic | Existing rock altered by heat/pressure without melting | Often deformed, foliated, complex basement shapes | Marble, Schist, Gneiss |
Geologic time & the Law of Superposition
The Law of Superposition is the single most important rule for modelling: in an undisturbed sequence, depth = age. Three companion principles refine it:
| Principle | States |
|---|---|
| Original horizontality | Sediments are deposited as roughly horizontal layers — if you see tilted layers, something deformed them after deposition |
| Lateral continuity | A layer extends in all directions until it thins out, hits a barrier, or erodes — useful for correlating boreholes that are far apart |
| Cross-cutting relationships | Anything that cuts through a rock (fault, intrusion, erosion surface) is younger than the rock it cuts |
The stratigraphic column
A stratigraphic column
is simply a vertical list of layers at a location, oldest at the bottom. It's the direct ancestor of
a borehole log and is exactly the data structure a model's surfaces list represents.
Geological time itself is organized hierarchically — useful vocabulary when reading literature or maps:
| Unit (largest→smallest) | Example |
|---|---|
| Eon | Phanerozoic |
| Era | Mesozoic |
| Period | Jurassic |
| Epoch | Late Jurassic |
Minerals vs. rocks (quick note)
A mineral is a naturally occurring, chemically-defined crystalline solid (e.g. quartz, feldspar, calcite). A rock is an aggregate of one or more minerals. For 3-D geological modelling you almost always work at the rock/lithology level, not individual minerals — mineral composition matters more for geochemistry and ore-grade estimation, which is a separate (though related) discipline from the geometric modelling this course focuses on.
How rocks deform, break, and bend
Real rock is rarely flat and undisturbed. This tier covers the deformation vocabulary that maps directly onto modelling primitives: orientations, folds, faults, unconformities, and intrusions.
Strike, dip & orientation
Every tilted surface has two measurements that fully describe its 3-D orientation:
| Measurement | Definition | Range |
|---|---|---|
| Dip angle | Steepest angle of the surface from horizontal | 0°(flat)–90°(vertical) |
| Dip direction | Compass direction the surface tilts toward (downslope) | 0°–360° |
| Strike | Direction of a horizontal line on the surface — always 90° from dip direction | 0°–360° |
(0,0,1). Converting:
import numpy as np def dip_to_normal(dip_deg, dip_dir_deg): """Convert dip angle + dip direction to a unit normal vector (Gx, Gy, Gz).""" dip = np.radians(dip_deg) dip_dir = np.radians(dip_dir_deg) gx = np.sin(dip) * np.sin(dip_dir) gy = np.sin(dip) * np.cos(dip_dir) gz = np.cos(dip) return (gx, gy, gz) # Flat layer: dip=0 → (0, 0, 1) — points straight up # Vertical layer dipping east: dip=90, dip_dir=90 → (1, 0, 0)
Folds
Folds form when layers bend under compressional stress rather than fracturing. The two basic shapes:
| Type | Shape | Oldest rock at | Limbs dip |
|---|---|---|---|
| Anticline | Arches up (∩) | Core/centre | Away from the axis |
| Syncline | Sags down (∪) | Outer edges | Toward the axis |
Fold geometry vocabulary
Faults
A fault is a fracture along which blocks of rock have moved relative to each other. Faults are classified by the direction of relative motion:
| Type | Motion | Stress regime | Hanging wall vs footwall |
|---|---|---|---|
| Normal | Extensional | Tension (pulling apart) | Hanging wall moves down relative to footwall |
| Reverse | Compressional | Compression (pushing together) | Hanging wall moves up relative to footwall |
| Thrust | Compressional, low angle | Strong compression | Reverse fault with dip < 45° |
| Strike-slip | Lateral / horizontal | Shear | No significant vertical offset |
Key fault vocabulary
- Hanging wall — the block of rock above an inclined fault plane
- Footwall — the block below an inclined fault plane
- Throw — vertical component of displacement
- Heave — horizontal component of displacement
- Fault zone / damage zone — fractured rock surrounding the main fault surface
Unconformities
An unconformity is a buried surface representing a gap in time — older rock was deformed, uplifted, and eroded, then younger sediment was deposited on top of the erosion surface.
| Type | Below the surface | Above the surface |
|---|---|---|
| Angular unconformity | Tilted/folded layers, truncated | Flat-lying younger layers |
| Disconformity | Parallel layers with an erosional gap | Parallel younger layers (same orientation) |
| Nonconformity | Igneous or metamorphic basement | Sedimentary layers on top |
Intrusions
Igneous intrusions form when magma pushes into existing rock and solidifies underground — these cut across stratigraphy rather than following it.
| Shape | Orientation | Typical scale |
|---|---|---|
| Dike | Steep/vertical sheet, cuts across layering | Metres to kilometres long, thin |
| Sill | Sub-horizontal sheet, parallel to layering | Can be very extensive laterally |
| Stock / Pluton | Irregular, roughly equant blob | Kilometre-scale |
| Batholith | Massive irregular body (many plutons merged) | Tens to hundreds of km |
Joints & fractures (and why you usually skip them)
Joints are fractures with no appreciable displacement — unlike faults, the rock on either side hasn't moved relative to the other side. They're geologically important (control groundwater flow, rock strength, blasting patterns in mining) but are almost never modelled as discrete surfaces in a regional 3-D model.
What raw geological data actually looks like
Before any data reaches your model, it comes from one of a handful of source formats. Knowing how to read these is what separates "I ran the interpolator" from "I understand what my model represents."
Geological maps
A geological map shows which rock unit is exposed at the surface across an area, using colour-coded polygons, plus strike/dip symbols and contact lines (boundaries between units).
| Symbol | Meaning |
|---|---|
| Colour-filled polygon | One rock unit outcropping at surface |
| Solid line | Observed contact (boundary) between units |
| Dashed line | Inferred/uncertain contact |
| Strike & dip tick (┳ rotated) | Orientation measurement at that point — long line = strike, short tick = dip direction, number = dip angle |
| Thick line with teeth/barbs | Fault trace (barbs point toward hanging wall on thrust faults) |
surface_points and
orientations input data for a 3-D model.
Boreholes & well logs
A borehole is a vertical (or deviated) hole drilled into the ground; the resulting
well log
records which rock type was encountered at which depth. This is the single most common and
most reliable source of surface_points data.
# Typical well log: a list of (top_depth, bottom_depth, lithology) well_log = [ (0, 15, 'Topsoil'), (15, 120, 'Limestone'), (120, 280, 'Shale'), (280, 500, 'Sandstone'), ] collar_elevation = 1450 # surface elevation at the well (metres) well_x, well_y = 2400, 1180 # well location # Convert each TOP of a unit into a (X, Y, Z, surface) input point # Z must be ELEVATION → collar_elevation minus depth surface_points = [] for top_depth, bottom_depth, lith in well_log[1:]: # skip topsoil top = ground level z = collar_elevation - top_depth surface_points.append((well_x, well_y, z, lith)) print(surface_points) # [(2400, 1180, 1435, 'Limestone'), (2400, 1180, 1330, 'Shale'), (2400, 1180, 1170, 'Sandstone')]
Seismic sections
Seismic reflection surveys send sound waves into the ground and record echoes off rock boundaries, producing a 2-D (or 3-D volume of) image where horizontal bands roughly correspond to layer boundaries. Common in oil & gas exploration.
| Term | Meaning |
|---|---|
| Reflector | A bright/dark band corresponding to a strong contrast in rock properties — usually a layer boundary |
| Two-way time (TWT) | Vertical axis is often in milliseconds (sound travel time), not metres — must be converted via a velocity model to get true depth |
| Picking | The manual or automated process of tracing a reflector across the section to extract its (X, Y, Z) geometry |
surface_points input, often
thousands of points per surface, far denser than borehole-derived data. They're an excellent
complement to sparse borehole data: boreholes give precise depth control, seismic gives lateral shape.
Cross-sections
A geological cross-section is a vertical "slice" through the Earth showing rock layers as they
would appear if you cut the ground open along a line. It's the standard way geologists communicate
a 3-D interpretation in 2-D, and it's exactly what tools like plot_2d() in GemPy generate.
Outcrop & field measurements
An outcrop is a place where rock is exposed at the surface. Geologists visit outcrops with a compass-clinometer to directly measure strike and dip by placing the tool against the rock face — this is the most direct, ground-truth source of orientation data.
| Field data | Captured as | Becomes in your model |
|---|---|---|
| GPS location + which unit is exposed | (X, Y, Z, unit name) | surface_points |
| Compass-clinometer reading | (strike, dip) at a point | orientations |
| Hand-drawn field sketch | Annotated cross-section | Used for QA/validation against your model |
How geology becomes a computable model
This tier is the translation layer — the geology-specific concepts you need to use any implicit 3-D modelling software correctly, including GemPy, Leapfrog, SKUA-GOCAD, or Petrel.
Implicit vs. explicit modelling
There are two fundamentally different approaches to building a 3-D geological model:
| Approach | How surfaces are defined | Pros | Cons |
|---|---|---|---|
| Explicit | Geologist manually digitizes/draws each surface as a mesh, section by section | Full manual control, exact match to interpretation | Labour-intensive, hard to update, doesn't scale |
| Implicit | An algorithm (e.g. co-kriging) fits a continuous scalar field through scattered data points; surfaces are extracted as isocontours of that field | Fast, automatically consistent, easy to update with new data | Less direct manual control; quality depends on input data density/quality |
Interpolation behaviour — what the algorithm is actually doing
Most implicit modelling software (including GemPy) uses a form of kriging — specifically universal co-kriging with both surface points and orientation (gradient) constraints. You don't need the full mathematical derivation, but you do need the behavioural intuition:
- The interpolator fits a smooth scalar field where surface points all sit on the same isovalue (e.g. value = 0)
- Orientation data constrain the gradient (steepness/direction) of that field at given points
- Away from data, the field interpolates smoothly — it does not know about geological features you haven't told it about
- Sparse data + uniform orientations → smooth, "boring" surfaces; tight folds and abrupt features need denser sampling near them
Series, order & relations — encoding geological history
A series groups surfaces that share one continuous depositional/deformation event. The order of series, and the relation between adjacent series, is how you encode the geological history (Tier 1 & 2 concepts) into the software:
| You observed… | Encode as… |
|---|---|
| A normal stack of sedimentary layers | One series, surfaces ordered youngest→oldest |
| An angular unconformity | Two series; younger series set to "erode" relation |
| A fault offsetting layers | Separate fault series, ordered before the series it cuts, flagged is_fault |
| An igneous intrusion | Separate series, typically computed/overprinted last |
| Two faults where one offsets the other | Two fault series with an explicit fault-fault relation matrix |
Anisotropy & range — tuning the interpolator
Geological layers usually extend much further laterally than vertically — a limestone bed might span 10 km horizontally but be only 50 m thick. Anisotropy settings tell the interpolator about this directional bias so it doesn't treat horizontal and vertical distances equally.
| Parameter | Controls | Effect if too small | Effect if too large |
|---|---|---|---|
| Range | Maximum distance over which data points influence the interpolation | Overfitting — noisy, patchy surfaces | Over-smoothing — real features get washed out |
| Anisotropy ratio | Relative horizontal vs. vertical influence | Vertically "smeared" surfaces | Layers appear unrealistically flat |
Uncertainty in geological models
Unlike a CAD model of a manufactured object, a geological model is always an interpretation of incomplete data — there is no ground truth to check against except more drilling. Understanding where uncertainty comes from is essential for using a model responsibly.
Beyond the standard workflow
The topics that separate a working model from a defensible, production-grade one — structural restoration, complex fault networks, formal validation, and domain-specific practice.
Structural restoration — checking your model is physically possible
Structural restoration "undoes" deformation on a model — unfolding folds and closing faults — to check whether the pre-deformation state makes geological sense (e.g. layers should restore to roughly horizontal, without gaps or overlaps, which would indicate an impossible interpretation).
Complex fault networks
Real basins often have dozens of interacting faults rather than one isolated fault. Key concepts for handling this in software:
| Concept | Meaning |
|---|---|
| Fault-fault relations | Which fault offsets which — encoded as a directed relation/precedence matrix |
| Splay faults | Smaller faults branching off a main fault |
| Relay ramps | Zones where displacement transfers between two overlapping, non-connected faults |
| Horst & graben | Uplifted block (horst) between two normal faults / down-dropped block (graben) between two normal faults |
# Rows/columns = series, in computation order. # A 1 at [i, j] means series i offsets/affects series j. import numpy as np fault_relations = np.array([ # FaultA FaultB FaultC Strat [0, 1, 1, 1], # FaultA offsets B, C, and stratigraphy [0, 0, 1, 1], # FaultB offsets C and stratigraphy [0, 0, 0, 1], # FaultC offsets only stratigraphy [0, 0, 0, 0], # Stratigraphy offsets nothing ]) geo_model.set_fault_relation(fault_relations)
Model validation, QA & uncertainty quantification
Expert practice treats a geological model as a hypothesis to be tested, not a final answer. Standard checks:
- Cross-validation against held-out data — remove some known points, run the model, check how close the prediction is to the held-out truth
- Thickness sanity checks — verify modelled layer thickness stays within geologically reasonable bounds across the model
- Stochastic/Monte Carlo runs — perturb uncertain inputs (e.g. borehole pick depths) within their error bounds and re-run many times to get a probability map of where each lithology likely occurs
- Geological plausibility review — a domain expert visually checks the model against regional geological knowledge that isn't captured in the raw data
import numpy as np n_realizations = 100 results = [] for i in range(n_realizations): # Perturb each borehole pick within its measurement uncertainty perturbed_z = original_z + np.random.normal(0, measurement_error_std) geo_model.modify_surface_points(point_id, Z=perturbed_z) sol = gp.compute_model(geo_model) results.append(sol.lith_block) # Probability of a given voxel being a particular lithology: results = np.array(results) # shape: (n_realizations, n_voxels) prob_sandstone = (results == SANDSTONE_ID).mean(axis=0)
Domain case studies
Ore body modelling
Focus shifts from clean stratigraphic layers to irregular mineralized zones, often defined by grade thresholds (e.g. "anywhere with >0.5% copper") rather than lithological boundaries. Combines implicit geometric modelling with geostatistical grade estimation (a separate kriging problem on the property values themselves, not just the geometry).
Reservoir characterization
Heavy reliance on seismic-derived horizons (Tier 3.3) combined with sparse, expensive well data. Structural restoration (5.1) is routinely used to validate fault interpretations before committing to a reservoir model that informs multi-million dollar drilling decisions.
Aquifer mapping
Models focus on permeable vs. impermeable layer geometry to predict groundwater flow paths. Property fields (hydraulic conductivity) are often more important than precise lithological boundaries — a geological model here typically feeds into a separate groundwater flow simulator.
Subsurface profiling for construction
Shallow, high-resolution models (tens of metres, not kilometres) focused on soil/rock competency for foundation design — dense borehole data over a small area is typical, with less reliance on inferred structural geology.
Full Glossary
| Term | Definition |
|---|---|
| Anisotropy | Directional bias in spatial correlation (e.g. stronger horizontally than vertically) |
| Anticline | Upward-arching fold; oldest rock at the core |
| Borehole | A drilled hole used to sample subsurface rock at depth |
| Cross-cutting relationship | Principle: anything cutting through rock is younger than the rock it cuts |
| Dike | Steep igneous sheet intrusion cutting across host rock layering |
| Dip | The angle a surface makes with horizontal |
| Dip direction | Compass direction toward which a surface tilts downward |
| Fault | A fracture with measurable displacement of rock on either side |
| Fold | A bend in rock layers from compressional stress without fracturing |
| Footwall | The rock block below an inclined fault plane |
| Graben | Down-dropped block between two normal faults |
| Hanging wall | The rock block above an inclined fault plane |
| Horst | Uplifted block between two normal faults |
| Igneous rock | Rock formed from cooled magma |
| Implicit modelling | Building surfaces via interpolation of scattered constraint data, rather than manual drawing |
| Joint | A fracture with no measurable displacement |
| Kriging | A geostatistical interpolation method using a spatial covariance model |
| Lateral continuity | Principle: layers extend until they thin, are bounded, or eroded |
| Metamorphic rock | Rock altered by heat/pressure without melting |
| Nonconformity | Unconformity where sedimentary rock overlies igneous/metamorphic basement |
| Onlap | Relation where younger sediment drapes over older topography without eroding it |
| Original horizontality | Principle: sediments are deposited roughly horizontal |
| Outcrop | Rock exposed at the surface, not covered by soil |
| Reflector | A boundary visible in seismic data, usually a layer boundary |
| Restoration | Reversing deformation on a model to check physical plausibility |
| Sedimentary rock | Rock formed from deposited and compacted particles |
| Series | A group of surfaces sharing one continuous geological event, in modelling software |
| Sill | Sub-horizontal igneous sheet intrusion parallel to host layering |
| Strike | Direction of a horizontal line on a tilted surface, perpendicular to dip direction |
| Stratigraphy | The study of rock layers — their sequence, composition, and age |
| Superposition | Principle: in undisturbed strata, older rock lies below younger rock |
| Syncline | Downward-sagging fold; oldest rock at the outer edges |
| Throw | Vertical component of fault displacement |
| Unconformity | A buried erosion surface representing a gap in geological time |