2.16.3.5. pycsamt.geology.structural#

Structural geology — field measurements and fault traces.

Independent structural evidence to check an interpretation against, the same role Borehole plays for lithology. None of this is electromagnetic: positions are profile-relative metres (x), exactly like Borehole and StratigraphicLog – real-world placement (lat/lon) is handled separately by pycsamt.site and pycsamt.gis.

Two kinds of field measurement are distinguished, matching how a stereonet actually plots them:

  • StructuralMeasurement – a planar feature (bedding, foliation, joint, cleavage, contact, fault plane, unconformity surface), recorded as strike/dip/dip-direction.

  • LinearMeasurement – a linear feature (fold axis, lineation, slickenline), recorded as trend/plunge.

FaultTrace is a distinct, coarser entity: where a fault crosses the 2-D profile itself, which side is downthrown, and (if known) the throw – the piece that plugs directly into the structural-continuity review questions in Interpretation workflow (“do apparent boundary offsets align with known structures?”). StructuralModel collects all three per profile.

Angle conventions#

This is the one place the rest of pycsamt does not already agree with itself: MT geoelectric strike (pycsamt.emtools.strike) is axial and wrapped to (-90, 90]; pycsamt.gis/pycsamt.site azimuth is a directed compass bearing, [0, 360); the synthetic geometry in pycsamt.ai.geology.lenses is axial mod-180 for an unrelated reason (ellipse symmetry). None of those fit real field data, so this module uses the convention modern digital field-mapping tools (FieldMove, StraboSpot) record directly from a compass-clinometer:

  • strike_deg, trend_deg, dip_direction_deg – compass bearings, [0, 360), degrees clockwise from north.

  • dip_deg, plunge_deg – angle below horizontal, [0, 90].

Storing dip_direction_deg alongside strike_deg (rather than only a right-hand-rule pair) is deliberate: the two are cross-validated against each other in StructuralMeasurement.validate() (dip direction must be within tolerance of strike +/- 90 degrees), which catches a transposed field-notebook entry that a bare right-hand-rule number would not. StructuralMeasurement.from_right_hand_rule() builds one from just dip direction and dip, for anyone who prefers recording that way.

Classes

FaultTrace(x, dip_deg, downthrown_side[, ...])

Where a fault crosses the 2-D profile.

LinearMeasurement(x, kind, trend_deg, plunge_deg)

A linear structural field measurement.

StructuralMeasurement(x, kind, strike_deg, ...)

A planar structural field measurement.

StructuralModel(*[, planar, linear, faults, ...])

Collection of structural evidence along one survey profile.

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

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()[source]

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[source]

Whether dip_direction_deg is consistent with strike_deg.

classmethod from_right_hand_rule(x, kind, dip_direction_deg, dip_deg, **kwargs)[source]

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.structural.LinearMeasurement(x, kind, trend_deg, plunge_deg, z=None, station=None, confidence=1.0, notes='')[source]

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()[source]

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.structural.FaultTrace(x, dip_deg, downthrown_side, sense='unknown', throw_m=None, strike_deg=None, z_top=None, confidence=1.0, evidence='', notes='')[source]

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()[source]

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.structural.StructuralModel(*, planar=None, linear=None, faults=None, metadata=None)[source]

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)[source]
Parameters:

measurement (StructuralMeasurement)

Return type:

None

add_linear(measurement)[source]
Parameters:

measurement (LinearMeasurement)

Return type:

None

add_fault(fault)[source]
Parameters:

fault (FaultTrace)

Return type:

None

within(x_min, x_max)[source]

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

Parameters:
Return type:

StructuralModel

nearest(x, *, kind='faults', max_distance=None)[source]

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=',')[source]

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()[source]

Return a shallow dictionary representation.

Return type:

dict