2.16. pycsamt.geology#

General-purpose geological domain knowledge with no electromagnetic dependency: resistivity-to-lithology classification, the literature-compiled rock/fluid property table behind it, pluggable rock-property sources, ground-truth borehole logs, and field structural measurements (strike/dip, trend/plunge, fault traces). pycsamt.interp builds on this package (see pycsamt.interp.ModelCalibrator) rather than the other way round.

pycsamt.geology — general-purpose geological domain knowledge.

This package holds earth-science concepts that are not themselves electromagnetic: a resistivity-to-lithology classification engine, a literature-compiled rock/fluid property table, ground-truth borehole logs, and (as the package grows) structural-geology primitives. Nothing here imports pycsamt.interp.ResistivityModel or any other EM concept; pycsamt.interp depends on this package, not the other way round.

For the interpretation workflow that turns an EM resistivity model into a calibrated, geologically classified section — the part that does know about resistivity models, boreholes-as-constraints, and misfit review — see pycsamt.interp (ModelCalibrator in particular). pycsamt.interp.RockDatabase, pycsamt.interp.Borehole, and the other classes below remain importable from pycsamt.interp for backward compatibility; this package is their canonical home.

2.16.1. Quickstart#

>>> from pycsamt.geology import RockDatabase, Borehole, FaultTrace
>>>
>>> db = RockDatabase.default()
>>> db.classify(250.0).name
'Granite (weathered)'
>>>
>>> bh = Borehole.from_csv("boreholes/Bo.csv", x=1050.0)
>>>
>>> fault = FaultTrace(x=500.0, dip_deg=70.0, downthrown_side="right")

2.16.2. Package layout#

lithology

RockDatabase, RockEntry, StratigraphicLog, Layer — resistivity-to-lithology classification engine only; the built-in table itself lives in rock_library.

rock_library

BUILTIN_ROCKS — the literature-compiled rock/ fluid resistivity table behind RockDatabase.default(), kept separate so it can grow without touching classification logic.

rock_providers

RockPropertyProvider, LocalRockPropertyProvider, RemoteRockPropertyProvider — pluggable rock-property sources behind RockDatabase.from_url() and RockDatabase.from_provider(), with local caching and fallback-to-default on failure.

borehole

Borehole, Interval — ground-truth depth-interval data; readers for CSV and LAS 2.0.

structural

StructuralMeasurement (planar: strike/dip/dip-direction), LinearMeasurement (linear: trend/plunge), and FaultTrace (where a fault crosses the profile) — field structural evidence; StructuralModel collects all three per profile with CSV I/O and nearest/within queries.

References

class pycsamt.geology.RockDatabase(entries, *, metadata=None)#

Bases: PyCSAMTObject, MetadataMixin

Extensible rock physics database for EM resistivity interpretation.

Parameters:
  • entries (list of RockEntry) – The database entries. default() returns the built-in set.

  • metadata (dict)

Example

>>> db = RockDatabase.default()
>>> db.classify(180.0).name
'Granite (weathered)'
metadata: dict#
property entries: tuple[RockEntry, ...]#

Read-only view of the database entries, in insertion order.

classmethod default()#

Return a database pre-loaded with the built-in rock entries.

Entries come from pycsamt.geology.rock_library.BUILTIN_ROCKS; see that module for the literature the ranges are drawn from.

Return type:

RockDatabase

classmethod from_csv(path)#

Load from a CSV file.

Required columns: name, rho_min, rho_max Optional columns: color, description, code, source

Parameters:

path (str | Path)

Return type:

RockDatabase

classmethod from_provider(provider)#

Build a database from any RockPropertyProvider.

This is the generic entry point behind from_url(); call it directly when provider is something other than a plain URL fetch, such as a provider pre-configured with authentication or a provider composing several sources.

Parameters:

provider (RockPropertyProvider)

Return type:

RockDatabase

classmethod from_url(url, *, cache_dir=None, ttl_seconds=86400.0, timeout=10.0, force=False, fallback=True)#

Fetch a database from url, with local caching and fallback.

url must serve a JSON array of objects using the same fields as RockEntry (name, rho_min, rho_max, and optionally color, description, code, source). No public rock-resistivity service is bundled or assumed by default – point this at a project- or organisation-controlled endpoint (an internal API, a shared JSON file on a file server, and so on).

Parameters:
  • url (str) – Location to fetch the JSON entry list from.

  • cache_dir (path-like, optional) – Override the local cache location. Defaults to $PYCSAMT_ROCKDB_CACHE or ~/.pycsamt/rock_db.

  • ttl_seconds (float) – Reuse a cached response younger than this many seconds instead of re-fetching. Default one day.

  • timeout (float) – Network timeout in seconds for the fetch itself.

  • force (bool) – Re-fetch even if a fresh cache entry exists.

  • fallback (bool) – If the fetch fails (network, timeout, malformed response) and no usable cache entry exists, fall back to default() instead of raising. Set to False to surface the failure instead.

Return type:

RockDatabase

See also

pycsamt.geology.rock_providers.RemoteRockPropertyProvider

The provider implementing the fetch/cache/fallback policy used here.

classify(rho_ohm_m, method='nearest')#

Return the best-matching rock entry for rho_ohm_m.

Parameters:
  • rho_ohm_m (float) – Resistivity in Ω·m (linear).

  • method ({'nearest', 'overlap'}) – 'nearest': log-distance to midpoint. 'overlap': first entry whose range brackets rho_ohm_m.

Return type:

RockEntry

classify_column(rho_log10)#

Classify every cell in a log10-rho depth column.

Parameters:

rho_log10 (ndarray)

Return type:

list[RockEntry]

class pycsamt.geology.RockEntry(name, rho_min, rho_max, color='#AAAAAA', description='', code=0, source='')#

Bases: PyCSAMTObject

A single entry in the rock physics database.

Parameters:
  • name (str) – Geological unit / lithology name.

  • rho_min (float) – Resistivity range in Ω·m (linear scale).

  • rho_max (float) – Resistivity range in Ω·m (linear scale).

  • color (str) – Hex colour code for plotting (e.g. '#28B463').

  • description (str) – Optional free-text note.

  • code (int) – Integer code used in LAS exports.

  • source (str) – Optional literature citation for this range (e.g. 'Palacky (1988)'). Empty when unspecified, as for entries loaded from a CSV without a source column.

name: str#
rho_min: float#
rho_max: float#
color: str = '#AAAAAA'#
description: str = ''#
code: int = 0#
source: str = ''#
property rho_mid: float#
property log_rho_mid: float#
contains(rho_ohm_m)#
Parameters:

rho_ohm_m (float)

Return type:

bool

class pycsamt.geology.Layer(top, bottom, rho_log10, lithology, color='#AAAAAA', confidence=1.0)#

Bases: PyCSAMTObject

One geological unit in a pseudo-stratigraphic log.

Parameters:
  • top (float) – Depth in metres (positive downward).

  • bottom (float) – Depth in metres (positive downward).

  • rho_log10 (float) – Representative \(\log_{10}(\rho)\) of the layer.

  • lithology (str) – Rock name from RockDatabase.

  • color (str) – Hex colour for plotting.

  • confidence (float) – Fraction of depth cells whose DB classification matches the reported lithology (0 – 1).

top: float#
bottom: float#
rho_log10: float#
lithology: str#
color: str = '#AAAAAA'#
confidence: float = 1.0#
property thickness: float#
property rho_ohm_m: float#
class pycsamt.geology.StratigraphicLog(station_name, station_x, z_centers, rho_log10, layers)#

Bases: PyCSAMTObject

Per-station pseudo-stratigraphic depth profile.

Constructed from a 1-D log10-rho column and a RockDatabase, it merges adjacent cells that share the same lithology into discrete Layer objects.

Parameters:
  • station_name (str)

  • station_x (float)

  • z_centers (ndarray (n_z,)) – Depth cell centres, metres.

  • rho_log10 (ndarray (n_z,)) – \(\log_{10}(\rho)\) for each depth cell.

  • layers (list of Layer) – Merged geological units (assembled by from_column()).

classmethod from_column(station_name, x, z_centers, rho_log10, db=None, *, merge_tolerance=0.2)#

Build a log from a 1-D resistivity column.

Parameters:
  • station_name (str)

  • x (float) – Station position, metres.

  • z_centers (ndarray (n_z,))

  • rho_log10 (ndarray (n_z,))

  • db (RockDatabase, optional) – Defaults to RockDatabase.default().

  • merge_tolerance (float) – Log10-rho difference threshold for merging adjacent cells into one layer (default 0.2 decade).

Return type:

StratigraphicLog

to_dataframe()#

Return layers as a pandas.DataFrame.

to_dict()#

Return a shallow dictionary representation.

Return type:

dict

class pycsamt.geology.Borehole(name, x, intervals, *, collar_elevation=0.0)#

Bases: PyCSAMTObject

Borehole / well log with depth-interval data.

Parameters:
  • name (str) – Well / borehole identifier.

  • x (float) – Position along the survey profile, metres.

  • collar_elevation (float) – Surface elevation at the borehole collar, metres a.s.l. Used only when elevation-corrected exports are requested.

  • intervals (list of Interval) – Depth-interval log, sorted ascending by top.

intervals: list[Interval]#
interval_at_depth(z)#

Return the interval that contains depth z, or None.

Parameters:

z (float)

Return type:

Interval | None

tres_at_depth(z)#

Return TRES (Ω·m) at depth z, or None if unknown.

Parameters:

z (float)

Return type:

float | None

lithology_at_depth(z)#

Return the lithology name at depth z, or None.

Parameters:

z (float)

Return type:

str | None

tres_column(z_centers)#

Return TRES values at z_centers as a float array.

Depths not covered by any interval are nan.

Parameters:

z_centers (ndarray)

Return type:

ndarray

property max_depth: float#
property min_depth: float#
classmethod from_csv(path, *, name=None, x=0.0, collar_elevation=0.0, delimiter=',', top_col='top', bottom_col='bottom', lithology_col='lithology', resistivity_col='resistivity')#

Load from a CSV file.

Expected columns (case-insensitive header): top, bottom, lithology[, resistivity]

Parameters:
  • path (path-like)

  • name (str, optional) – Defaults to the file stem.

  • x (float) – Profile position.

  • collar_elevation (float)

  • delimiter (str)

  • top_col (str) – Column header names.

  • bottom_col (str) – Column header names.

  • lithology_col (str) – Column header names.

  • resistivity_col (str) – Column header names.

Return type:

Borehole

classmethod from_las(path, *, x=0.0, collar_elevation=0.0, depth_curve='DEPT', resistivity_curve='RESD', lithology_curve='LITH', null_value=-9999.25, step=None)#

Load from a LAS 2.0 well-log file.

Converts the continuous depth log into discrete intervals by grouping consecutive samples with the same lithology code.

Parameters:
  • path (path-like)

  • x (float) – Profile position.

  • depth_curve (str) – Curve mnemonics. lithology_curve may be None to assign a generic label.

  • resistivity_curve (str) – Curve mnemonics. lithology_curve may be None to assign a generic label.

  • lithology_curve (str | None) – Curve mnemonics. lithology_curve may be None to assign a generic label.

  • null_value (float) – LAS null sentinel replaced with nan.

  • step (float, optional) – If provided, override the step value from the LAS header.

  • collar_elevation (float)

Return type:

Borehole

to_dataframe()#

Return intervals as a pandas.DataFrame.

to_dict()#

Return a shallow dictionary representation.

Return type:

dict

class pycsamt.geology.Interval(top, bottom, lithology, resistivity=None)#

Bases: PyCSAMTObject

A single depth interval in a borehole log.

Parameters:
  • top (float) – Depth to the top of the interval, metres.

  • bottom (float) – Depth to the bottom of the interval, metres.

  • lithology (str) – Geological formation / lithology name.

  • resistivity (float or None) – True resistivity (TRES) in Ω·m (linear, not log₁₀). None when no electrical measurement is available.

top: float#
bottom: float#
lithology: str#
resistivity: float | None = None#
property thickness: float#
contains(z)#
Parameters:

z (float)

Return type:

bool

class pycsamt.geology.StructuralMeasurement(x, kind, strike_deg, dip_deg, dip_direction_deg, z=None, station=None, confidence=1.0, notes='', dip_direction_tolerance_deg=20.0)#

Bases: PyCSAMTObject

A planar structural field measurement.

Parameters:
  • x (float) – Position along the survey profile, metres.

  • kind (str) – Feature type, e.g. 'bedding', 'foliation', 'joint', 'cleavage', 'contact', 'fault_plane', 'unconformity'. Free text – not an enforced enumeration, matching lithology.

  • strike_deg (float) – Compass strike, degrees clockwise from north, [0, 360) as measured. Normalised on construction; not reduced modulo 180, so the raw field reading is preserved.

  • dip_deg (float) – Dip angle below horizontal, degrees, [0, 90].

  • dip_direction_deg (float) – Compass direction the surface dips toward, [0, 360). Must be within dip_direction_tolerance_deg of strike_deg + 90 or strike_deg - 90 (mod 360); raises ValueError otherwise, since a wider mismatch usually means one of the two readings was transposed in the field notebook.

  • z (float, optional) – Depth (positive downward) or elevation of the measurement, metres. None for a surface outcrop reading with no associated depth.

  • station (str, optional) – Field station or outcrop label.

  • confidence (float) – Subjective reading confidence, [0, 1] (default 1.0).

  • notes (str) – Free-text field note.

  • dip_direction_tolerance_deg (float)

Examples

>>> m = StructuralMeasurement(
...     x=500.0, kind="bedding", strike_deg=45.0, dip_deg=30.0,
...     dip_direction_deg=135.0,
... )
>>> m.dip_azimuth_ok
True
x: float#
kind: str#
strike_deg: float#
dip_deg: float#
dip_direction_deg: float#
z: float | None = None#
station: str | None = None#
confidence: float = 1.0#
notes: str = ''#
dip_direction_tolerance_deg: float = 20.0#
validate()#

Re-check and re-normalise this measurement’s fields.

Called automatically by __post_init__, and by update()/clone() after they set new attribute values – both go through this method rather than __post_init__ (which only runs once, at construction), so a clone(dip_direction_deg=...) that breaks the strike/dip-direction consistency check is caught rather than silently accepted.

Return type:

None

property dip_azimuth_ok: bool#

Whether dip_direction_deg is consistent with strike_deg.

classmethod from_right_hand_rule(x, kind, dip_direction_deg, dip_deg, **kwargs)#

Build from a dip-direction/dip pair, deriving strike.

Strike is set to dip_direction_deg - 90 (mod 360), the right-hand-rule convention: facing along strike with the dip direction to your right.

Parameters:
Return type:

StructuralMeasurement

class pycsamt.geology.LinearMeasurement(x, kind, trend_deg, plunge_deg, z=None, station=None, confidence=1.0, notes='')#

Bases: PyCSAMTObject

A linear structural field measurement.

Parameters:
  • x (float) – Position along the survey profile, metres.

  • kind (str) – Feature type, e.g. 'fold_axis', 'lineation', 'slickenline', 'fold_hinge', 'intersection_lineation'. Free text, as with StructuralMeasurement.

  • trend_deg (float) – Compass direction the line plunges toward, degrees clockwise from north, [0, 360).

  • plunge_deg (float) – Angle below horizontal, degrees, [0, 90].

  • z (float, optional)

  • station (str, optional)

  • confidence (float)

  • notes (str)

Examples

>>> LinearMeasurement(x=500.0, kind="fold_axis", trend_deg=210.0, plunge_deg=15.0)
LinearMeasurement(x=500.0 m, 'fold_axis', 210/15)
x: float#
kind: str#
trend_deg: float#
plunge_deg: float#
z: float | None = None#
station: str | None = None#
confidence: float = 1.0#
notes: str = ''#
validate()#

Re-check and re-normalise this measurement’s fields.

Called by __post_init__ and by update/clone; see StructuralMeasurement.validate().

Return type:

None

class pycsamt.geology.FaultTrace(x, dip_deg, downthrown_side, sense='unknown', throw_m=None, strike_deg=None, z_top=None, confidence=1.0, evidence='', notes='')#

Bases: PyCSAMTObject

Where a fault crosses the 2-D profile.

Parameters:
  • x (float) – Profile position where the fault is picked, metres.

  • dip_deg (float) – Apparent dip of the fault plane in the section, degrees, [0, 90] (0 = flat detachment, 90 = vertical). This is the angle a 2-D EM section can actually constrain; the true 3-D dip differs unless the profile happens to run perpendicular to strike. Pass strike_deg separately when the true attitude is independently known (surface mapping, borehole).

  • downthrown_side ({'left', 'right'}) – Which side of x – toward decreasing or increasing profile distance – is downthrown.

  • sense ({'normal', 'reverse', 'strike_slip', 'unknown'}) – Kinematic sense, where known (default 'unknown').

  • throw_m (float, optional) – Vertical displacement, metres (magnitude; direction is carried by downthrown_side). None when unknown or unmeasured.

  • strike_deg (float, optional) – True compass strike, when independently known.

  • z_top (float, optional) – Depth to the top of the picked trace, metres. None for a surface trace or when unconstrained.

  • confidence (float)

  • evidence (str) – Free-text source, e.g. 'resistivity offset', 'borehole', 'surface mapping'.

  • notes (str)

Examples

>>> FaultTrace(x=500.0, dip_deg=70.0, downthrown_side="right", throw_m=12.0)
FaultTrace(x=500.0 m, dip=70 deg, down=right, throw=12.0 m)
x: float#
dip_deg: float#
downthrown_side: str#
sense: str = 'unknown'#
throw_m: float | None = None#
strike_deg: float | None = None#
z_top: float | None = None#
confidence: float = 1.0#
evidence: str = ''#
notes: str = ''#
validate()#

Re-check and re-normalise this trace’s fields.

Called by __post_init__ and by update/clone; see StructuralMeasurement.validate().

Return type:

None

class pycsamt.geology.StructuralModel(*, planar=None, linear=None, faults=None, metadata=None)#

Bases: PyCSAMTObject, MetadataMixin

Collection of structural evidence along one survey profile.

Parameters:

Examples

>>> model = StructuralModel(
...     faults=[FaultTrace(x=500.0, dip_deg=70.0, downthrown_side="right")],
... )
>>> len(model.faults)
1
planar: list[StructuralMeasurement]#
linear: list[LinearMeasurement]#
faults: list[FaultTrace]#
metadata: dict#
add_planar(measurement)#
Parameters:

measurement (StructuralMeasurement)

Return type:

None

add_linear(measurement)#
Parameters:

measurement (LinearMeasurement)

Return type:

None

add_fault(fault)#
Parameters:

fault (FaultTrace)

Return type:

None

within(x_min, x_max)#

Return a new model restricted to x_min <= x <= x_max.

Parameters:
Return type:

StructuralModel

nearest(x, *, kind='faults', max_distance=None)#

Return the item of kind nearest to profile position x.

Parameters:
  • x (float)

  • kind ({'faults', 'planar', 'linear'})

  • max_distance (float, optional) – Return None if the nearest item is farther than this (metres), instead of returning a distant match silently.

Return type:

StructuralMeasurement | LinearMeasurement | FaultTrace | None

classmethod from_csv(*, planar_path=None, linear_path=None, faults_path=None, delimiter=',')#

Load a model from up to three CSV files, one per evidence type.

Expected columns (case-insensitive header, optional columns may be omitted):

  • planar_pathx, kind, strike_deg, dip_deg, dip_direction_deg[, z, station, confidence, notes]

  • linear_pathx, kind, trend_deg, plunge_deg[, z, station, confidence, notes]

  • faults_pathx, dip_deg, downthrown_side[, sense, throw_m, strike_deg, z_top, confidence, evidence, notes]

Any path left as None yields an empty list for that evidence type.

Parameters:
Return type:

StructuralModel

to_dict()#

Return a shallow dictionary representation.

Return type:

dict

2.16.3. Geology Modules#

pycsamt.geology.borehole

Borehole — ground-truth data model for EM geological interpretation.

pycsamt.geology.lithology

Lithology — resistivity-to-geology classification for EM methods.

pycsamt.geology.rock_library

Built-in rock/fluid resistivity table for pycsamt.geology.lithology.

pycsamt.geology.rock_providers

Pluggable rock-property sources for RockDatabase.

pycsamt.geology.structural

Structural geology — field measurements and fault traces.