2.25.3.3. pycsamt.ai.geology#

Geological grids, correlated fields, layered models, lenses, and topography priors for synthetic-data generation.

Spatially correlated geological priors for synthetic EM datasets.

Generators in this package will create complete 2-D sections and 3-D volumes before responses are simulated. This prevents production training labels from being assembled by tiling unrelated 1-D models.

Planned primitives include correlated random fields, layers, dipping interfaces, faults, lenses, resistive/conductive bodies, and topography. All generators must accept explicit random-number state and expose provenance.

class pycsamt.ai.geology.GeologyGrid(x_m, z_m, y_m=None, crs=None)#

Bases: object

Immutable regular cell-centre grid for 2-D or 3-D geological priors.

Parameters:
  • x_m (array-like) – Strictly increasing horizontal and depth cell centres in metres.

  • z_m (array-like) – Strictly increasing horizontal and depth cell centres in metres.

  • y_m (array-like or None, optional) – Strictly increasing second horizontal axis. Omit it for a 2-D profile.

  • crs (str or None, optional) – Coordinate reference system identifier for horizontal coordinates.

Examples

Construct a 2-D grid directly:

>>> grid = GeologyGrid(x_m=[50, 150, 250], z_m=[25, 75])
>>> grid.shape
(2, 3)
>>> grid.dimension
2

Use regular_3d() for a volume:

>>> volume = GeologyGrid.regular_3d(
...     nx=4, ny=3, nz=2, dx_m=100, dy_m=200, dz_m=50
... )
>>> volume.shape
(2, 3, 4)
x_m: ndarray#
z_m: ndarray#
y_m: ndarray | None = None#
crs: str | None = None#
classmethod regular_2d(*, nx, nz, dx_m, dz_m, x_origin_m=0.0, z_origin_m=0.0, crs=None)#

Construct a uniform 2-D cell-centre grid.

Parameters:
  • nx (int) – At least two cells in the horizontal and depth directions.

  • nz (int) – At least two cells in the horizontal and depth directions.

  • dx_m (float) – Positive cell widths in metres.

  • dz_m (float) – Positive cell widths in metres.

  • x_origin_m (float, default=0.0) – Coordinate of the outer edge before the first cell centre.

  • z_origin_m (float, default=0.0) – Coordinate of the outer edge before the first cell centre.

  • crs (str or None, optional) – Coordinate reference system identifier.

Returns:

Grid whose centres begin one half-cell from each origin.

Return type:

GeologyGrid

Examples

>>> grid = GeologyGrid.regular_2d(nx=3, nz=2, dx_m=100, dz_m=50)
>>> grid.x_m.tolist(), grid.z_m.tolist()
([50.0, 150.0, 250.0], [25.0, 75.0])
classmethod regular_3d(*, nx, ny, nz, dx_m, dy_m, dz_m, x_origin_m=0.0, y_origin_m=0.0, z_origin_m=0.0, crs=None)#

Construct a uniform 3-D cell-centre grid.

Parameters:
  • nx (int) – At least two cells along each axis.

  • ny (int) – At least two cells along each axis.

  • nz (int) – At least two cells along each axis.

  • dx_m (float) – Positive cell widths in metres.

  • dy_m (float) – Positive cell widths in metres.

  • dz_m (float) – Positive cell widths in metres.

  • x_origin_m (float, default=0.0) – Outer-edge coordinate before the first centre on each axis.

  • y_origin_m (float, default=0.0) – Outer-edge coordinate before the first centre on each axis.

  • z_origin_m (float, default=0.0) – Outer-edge coordinate before the first centre on each axis.

  • crs (str or None, optional) – Coordinate reference system identifier.

Returns:

Uniform grid with canonical shape (nz, ny, nx).

Return type:

GeologyGrid

Examples

>>> grid = GeologyGrid.regular_3d(
...     nx=2, ny=3, nz=4, dx_m=10, dy_m=20, dz_m=5
... )
>>> grid.shape
(4, 3, 2)
property dimension: int#

Return whether the grid is two- or three-dimensional.

Returns:

Spatial dimension.

Return type:

{2, 3}

Examples

>>> GeologyGrid.regular_2d(nx=2, nz=2, dx_m=1, dz_m=1).dimension
2
property shape: tuple[int, ...]#

Return the canonical geological array shape.

Returns:

(nz, nx) in 2-D or (nz, ny, nx) in 3-D.

Return type:

tuple of int

Examples

>>> GeologyGrid.regular_2d(nx=5, nz=3, dx_m=1, dz_m=1).shape
(3, 5)
property spacing_m: tuple[float, ...]#

Return regular spacing in canonical array-axis order.

Returns:

(dz, dx) in 2-D or (dz, dy, dx) in 3-D.

Return type:

tuple of float

Raises:

ValueError – If any coordinate axis is not regularly spaced.

Examples

>>> GeologyGrid.regular_2d(nx=2, nz=2, dx_m=100, dz_m=25).spacing_m
(25.0, 100.0)
property extent_m: dict[str, tuple[float, float]]#

Return outer cell-edge extents along all available axes.

Returns:

Axis names mapped to (minimum_edge, maximum_edge) in metres.

Return type:

dict

Examples

>>> grid = GeologyGrid.regular_2d(nx=2, nz=2, dx_m=100, dz_m=50)
>>> grid.extent_m["x"]
(0.0, 200.0)
to_dict()#

Return a JSON-serializable grid definition.

Returns:

Schema version, coordinates, and CRS.

Return type:

dict

Examples

>>> GeologyGrid.regular_2d(nx=2, nz=2, dx_m=1, dz_m=1).to_dict()[
...     "dimension"
... ]
2
classmethod from_dict(data)#

Restore a validated serialized grid.

Parameters:

data (mapping) – State returned by to_dict().

Returns:

Immutable grid.

Return type:

GeologyGrid

Examples

>>> grid = GeologyGrid.regular_2d(nx=2, nz=2, dx_m=1, dz_m=1)
>>> GeologyGrid.from_dict(grid.to_dict()).shape == grid.shape
True
class pycsamt.ai.geology.GaussianCorrelation(length_x_m, length_z_m, length_y_m=None, azimuth_deg=0.0)#

Bases: object

Anisotropic Gaussian spatial-correlation model.

Parameters:
  • length_x_m (float) – Positive horizontal and vertical correlation lengths in metres for C(h) = exp(-0.5 * (h / length)**2).

  • length_z_m (float) – Positive horizontal and vertical correlation lengths in metres for C(h) = exp(-0.5 * (h / length)**2).

  • length_y_m (float or None, optional) – Second horizontal length required for 3-D generation.

  • azimuth_deg (float, default=0.0) – Clockwise rotation of horizontal principal axes. It affects 3-D fields and is normalized to [0, 180) because Gaussian axes are bidirectional.

Examples

>>> model = GaussianCorrelation(1000, 100, length_y_m=500, azimuth_deg=210)
>>> model.azimuth_deg
30.0
>>> model.anisotropy_xz
10.0
length_x_m: float#
length_z_m: float#
length_y_m: float | None = None#
azimuth_deg: float = 0.0#
property anisotropy_xz: float#

Return horizontal-to-vertical correlation-length ratio.

Returns:

length_x_m / length_z_m.

Return type:

float

Examples

>>> GaussianCorrelation(500, 100).anisotropy_xz
5.0
validate_grid(grid)#

Validate dimensional compatibility with a geological grid.

Parameters:

grid (GeologyGrid) – Grid on which the model will be sampled.

Returns:

Successful return means all required correlation lengths exist.

Return type:

None

Raises:

Examples

>>> grid = GeologyGrid.regular_2d(nx=2, nz=2, dx_m=1, dz_m=1)
>>> GaussianCorrelation(2, 1).validate_grid(grid) is None
True
to_dict()#

Return a JSON-serializable correlation model.

Returns:

Versioned Gaussian lengths and azimuth.

Return type:

dict

Examples

>>> GaussianCorrelation(10, 2).to_dict()["kind"]
'gaussian'
classmethod from_dict(data)#

Restore a validated Gaussian correlation model.

Parameters:

data (mapping) – State returned by to_dict().

Returns:

Immutable model.

Return type:

GaussianCorrelation

Examples

>>> model = GaussianCorrelation(10, 2)
>>> GaussianCorrelation.from_dict(model.to_dict()) == model
True
class pycsamt.ai.geology.CorrelatedField(values, grid, correlation, seed, boundary='reflect', standardized=True)#

Bases: object

Immutable generated scalar field with complete generation provenance.

Parameters:
  • values (ndarray) – Finite values shaped exactly like grid.shape.

  • grid (GeologyGrid) – Spatial cell-centre grid.

  • correlation (GaussianCorrelation) – Requested correlation model.

  • seed (int) – Recorded random seed.

  • boundary ({"periodic", "reflect"}, default="reflect") – Spectral boundary policy used during generation.

  • standardized (bool, default=True) – Whether the stored sample was standardized to zero sample mean and unit sample standard deviation.

Examples

>>> grid = GeologyGrid.regular_2d(nx=8, nz=6, dx_m=100, dz_m=50)
>>> field = generate_gaussian_field(
...     grid, GaussianCorrelation(300, 100), seed=4
... )
>>> field.values.shape
(6, 8)
>>> field.seed
4
values: ndarray#
grid: GeologyGrid#
correlation: GaussianCorrelation#
seed: int#
boundary: str = 'reflect'#
standardized: bool = True#
property field_hash: str#

Return a digest covering values and generation configuration.

Returns:

Lowercase SHA-256 digest. Values are hashed as contiguous little- endian float64 bytes for platform-stable identity.

Return type:

str

Examples

>>> grid = GeologyGrid.regular_2d(nx=4, nz=4, dx_m=1, dz_m=1)
>>> field = generate_gaussian_field(
...     grid, GaussianCorrelation(2, 1), seed=0
... )
>>> len(field.field_hash)
64
provenance()#

Return JSON-compatible generation provenance without field values.

Returns:

Grid, correlation, seed, boundary, and standardization state.

Return type:

dict

Examples

>>> grid = GeologyGrid.regular_2d(nx=4, nz=4, dx_m=1, dz_m=1)
>>> field = generate_gaussian_field(
...     grid, GaussianCorrelation(2, 1), seed=0
... )
>>> field.provenance()["seed"]
0
rescale(*, mean=0.0, standard_deviation=1.0)#

Return a copy with a requested sample mean and standard deviation.

Parameters:
  • mean (float, default=0.0) – Target arithmetic mean.

  • standard_deviation (float, default=1.0) – Positive target population standard deviation.

Returns:

New field with unchanged spatial pattern and standardized=False unless the requested values are exactly zero and one.

Return type:

CorrelatedField

Examples

>>> grid = GeologyGrid.regular_2d(nx=8, nz=8, dx_m=1, dz_m=1)
>>> field = generate_gaussian_field(
...     grid, GaussianCorrelation(2, 1), seed=1
... ).rescale(mean=2, standard_deviation=3)
>>> (
...     round(float(np.mean(field.values)), 12),
...     round(float(np.std(field.values)), 12),
... )
(2.0, 3.0)
to_npz(path)#

Persist values and provenance in a pickle-free NPZ archive.

Parameters:

path (str or pathlib.Path) – Destination archive.

Returns:

Requested destination path.

Return type:

pathlib.Path

Examples

>>> from tempfile import TemporaryDirectory
>>> grid = GeologyGrid.regular_2d(nx=4, nz=4, dx_m=1, dz_m=1)
>>> field = generate_gaussian_field(
...     grid, GaussianCorrelation(2, 1), seed=0
... )
>>> with TemporaryDirectory() as directory:
...     path = field.to_npz(Path(directory) / "field.npz")
...     restored = CorrelatedField.from_npz(path)
>>> restored.field_hash == field.field_hash
True
classmethod from_npz(path)#

Load and validate a field archive without enabling pickle.

Parameters:

path (str or pathlib.Path) – Archive written by to_npz().

Returns:

Immutable restored field.

Return type:

CorrelatedField

Examples

>>> from tempfile import TemporaryDirectory
>>> field = generate_gaussian_field(
...     GeologyGrid.regular_2d(nx=3, nz=3, dx_m=1, dz_m=1),
...     GaussianCorrelation(2, 1),
...     seed=3,
... )
>>> with TemporaryDirectory() as directory:
...     path = field.to_npz(Path(directory) / "f.npz")
...     restored = CorrelatedField.from_npz(path)
>>> np.array_equal(restored.values, field.values)
True
class pycsamt.ai.geology.DirectionalVariogram(axis, lag_m, semivariance, pair_count)#

Bases: object

Empirical semivariance along one canonical geological axis.

Parameters:
  • axis ({"x", "y", "z"}) – Direction along which cell pairs were compared.

  • lag_m (ndarray) – Positive lag distances and corresponding finite semivariances.

  • semivariance (ndarray) – Positive lag distances and corresponding finite semivariances.

  • pair_count (ndarray of int) – Number of finite cell pairs supporting each lag.

Examples

>>> result = DirectionalVariogram("x", [1, 2], [0.2, 0.5], [10, 8])
>>> result.n_lags
2
axis: str#
lag_m: ndarray#
semivariance: ndarray#
pair_count: ndarray#
property n_lags: int#

Return the number of empirical lag bins.

Returns:

Length of the variogram arrays.

Return type:

int

Examples

>>> DirectionalVariogram("z", [1], [0.1], [4]).n_lags
1
pycsamt.ai.geology.generate_gaussian_field(grid, correlation, *, seed, boundary='reflect', standardize=True)#

Generate a deterministic anisotropic Gaussian random field.

Parameters:
  • grid (GeologyGrid) – Regular 2-D or 3-D cell-centre grid.

  • correlation (GaussianCorrelation) – Requested Gaussian covariance lengths and horizontal azimuth.

  • seed (int) – Explicit seed recorded in the returned field.

  • boundary ({"reflect", "periodic"}, default="reflect") – periodic synthesizes directly on the requested grid. reflect synthesizes on a grid doubled along each axis and crops the centre, reducing wrap-around correlation at opposite model edges.

  • standardize (bool, default=True) – Shift and scale the realized sample to zero mean and unit population standard deviation.

Returns:

Immutable field with complete generation provenance.

Return type:

CorrelatedField

Raises:

ValueError – If the grid is irregular, correlation model is incompatible, or the filtered realization is numerically constant.

Examples

>>> grid = GeologyGrid.regular_2d(nx=32, nz=16, dx_m=100, dz_m=50)
>>> model = GaussianCorrelation(length_x_m=500, length_z_m=100)
>>> first = generate_gaussian_field(grid, model, seed=12)
>>> second = generate_gaussian_field(grid, model, seed=12)
>>> np.array_equal(first.values, second.values)
True
>>> (
...     abs(round(float(np.mean(first.values)), 12)),
...     round(float(np.std(first.values)), 12),
... )
(0.0, 1.0)
pycsamt.ai.geology.directional_variogram(field, axis, *, max_lag_cells=None)#

Calculate an unbinned empirical directional semivariogram.

Parameters:
  • field (CorrelatedField) – Finite scalar field.

  • axis ({"x", "y", "z"}) – Geological direction. "y" is unavailable for 2-D fields.

  • max_lag_cells (int or None, optional) – Maximum positive integer cell offset. The default is half the selected axis length, with at least one lag.

Returns:

Lag distance, mean half-squared difference, and pair count.

Return type:

DirectionalVariogram

Examples

>>> grid = GeologyGrid.regular_2d(nx=16, nz=8, dx_m=100, dz_m=50)
>>> field = generate_gaussian_field(
...     grid, GaussianCorrelation(300, 100), seed=1
... )
>>> variogram = directional_variogram(field, "x", max_lag_cells=3)
>>> variogram.n_lags
3
>>> variogram.pair_count[0] > variogram.pair_count[-1]
True
class pycsamt.ai.geology.BenchmarkGeology(family, regime, seed, grid, resistivity_ohm_m, parameters)#

Bases: object

One generated benchmark resistivity model with provenance.

Parameters:
  • family (str) – Frozen geological family name.

  • regime ({"id", "ood"}) – Whether the realization belongs to the in-distribution or structural out-of-distribution family set.

  • seed (int) – Root geology seed.

  • grid (GeologyGrid) – Shared regular 2-D grid.

  • resistivity_ohm_m (ndarray) – Positive finite resistivity shaped like grid.

  • parameters (mapping) – Concrete sampled geological parameters.

family: str#
regime: str#
seed: int#
grid: GeologyGrid#
resistivity_ohm_m: ndarray#
parameters: Mapping[str, Any]#
property model_hash: str#

Return a SHA-256 covering model values and provenance.

Returns:

Platform-stable hexadecimal model digest.

Return type:

str

to_dict()#

Return JSON-compatible generation provenance.

Returns:

Family, regime, seed, grid, and concrete sampled parameters.

Return type:

dict

pycsamt.ai.geology.generate_benchmark_geology(grid, family, *, seed, configuration, regime='id')#

Generate one deterministic ID or structural-OOD geology.

Parameters:
  • grid (GeologyGrid) – Regular 2-D benchmark grid.

  • family (str) – Family from ID_BENCHMARK_FAMILIES or OOD_BENCHMARK_FAMILIES.

  • seed (int) – Explicit root seed.

  • configuration (mapping) – Frozen mapping containing base_layered, id_families, and ood_families sections.

  • regime ({"id", "ood"}, default="id") – Distribution regime.

Returns:

Positive resistivity model and concrete sampled provenance.

Return type:

BenchmarkGeology

Examples

>>> grid = GeologyGrid.regular_2d(nx=8, nz=6, dx_m=200, dz_m=100)
>>> config = {
...     "base_layered": {...},
...     "id_families": {...},
...     "ood_families": {...},
... }
>>> model = generate_benchmark_geology(
...     grid, "layered", seed=0, configuration=config
... )
>>> model.resistivity_ohm_m.shape
(6, 8)
class pycsamt.ai.geology.ElectricalLayer(name, resistivity_ohm_m, log10_std=0.0, heterogeneity=None, resistivity_bounds_ohm_m=None)#

Bases: object

Define the electrical distribution of one stratigraphic unit.

Parameters:
  • name (str) – Unique non-empty unit name.

  • resistivity_ohm_m (float) – Positive median resistivity in ohm metres.

  • log10_std (float, default=0.0) – Non-negative standard deviation of within-unit log10 resistivity.

  • heterogeneity (GaussianCorrelation or None, optional) – Spatial correlation model required when log10_std is positive.

  • resistivity_bounds_ohm_m (tuple of float or None, optional) – Positive inclusive lower/upper clipping bounds.

Examples

A homogeneous conductive cover:

>>> cover = ElectricalLayer("conductive cover", 10.0)
>>> cover.log10_resistivity
1.0

A heterogeneous basement:

>>> basement = ElectricalLayer(
...     "basement",
...     1000.0,
...     log10_std=0.2,
...     heterogeneity=GaussianCorrelation(1000, 200),
...     resistivity_bounds_ohm_m=(100, 5000),
... )
name: str#
resistivity_ohm_m: float#
log10_std: float = 0.0#
heterogeneity: GaussianCorrelation | None = None#
resistivity_bounds_ohm_m: tuple[float, float] | None = None#
property log10_resistivity: float#

Return the median resistivity in log10 ohm metres.

Returns:

log10(resistivity_ohm_m).

Return type:

float

Examples

>>> ElectricalLayer("unit", 100).log10_resistivity
2.0
to_dict()#

Return a JSON-serializable unit definition.

Returns:

Electrical distribution and optional correlation state.

Return type:

dict

Examples

>>> ElectricalLayer("unit", 100).to_dict()["resistivity_ohm_m"]
100.0
classmethod from_dict(data)#

Restore a validated electrical layer.

Parameters:

data (mapping) – State returned by to_dict().

Returns:

Immutable unit definition.

Return type:

ElectricalLayer

Examples

>>> unit = ElectricalLayer("unit", 50)
>>> ElectricalLayer.from_dict(unit.to_dict()) == unit
True
class pycsamt.ai.geology.LayeredGeology(grid, layers, interface_depth_m, layer_index, resistivity_ohm_m, seed, boundary, interface_policy, minimum_thickness_m, adjusted_interface_fraction=0.0, generation_config=<factory>)#

Bases: object

Immutable discretized layered electrical geology.

Parameters:
  • grid (GeologyGrid) – Target model grid.

  • layers (sequence of ElectricalLayer) – Units ordered shallowest to deepest.

  • interface_depth_m (ndarray) – Interface surfaces shaped (n_layer - 1, *horizontal_shape).

  • layer_index (ndarray of int) – Zero-based unit index for every model cell, shaped like grid.

  • resistivity_ohm_m (ndarray) – Positive cell resistivities shaped like grid.

  • seed (int) – Root generation seed.

  • boundary (str) – Recorded generation policies.

  • interface_policy (str) – Recorded generation policies.

  • minimum_thickness_m (float) – Enforced minimum vertical separation.

  • adjusted_interface_fraction (float) – Fraction of interface samples changed by projection.

  • generation_config (mapping, optional) – Finite JSON-compatible requested interface means, relief amplitudes, and correlation models needed to regenerate the surfaces.

Examples

>>> grid = GeologyGrid.regular_2d(nx=8, nz=6, dx_m=100, dz_m=50)
>>> layers = [
...     ElectricalLayer("cover", 10),
...     ElectricalLayer("basement", 1000),
... ]
>>> model = generate_layered_geology(grid, layers, [150], seed=1)
>>> model.resistivity_ohm_m.shape
(6, 8)
>>> model.n_layers
2
grid: GeologyGrid#
layers: tuple[ElectricalLayer, ...]#
interface_depth_m: ndarray#
layer_index: ndarray#
resistivity_ohm_m: ndarray#
seed: int#
boundary: str#
interface_policy: str#
minimum_thickness_m: float#
adjusted_interface_fraction: float = 0.0#
generation_config: Mapping[str, Any]#
property n_layers: int#

Return the number of stratigraphic units.

Returns:

Number of electrical layers.

Return type:

int

Examples

>>> grid = GeologyGrid.regular_2d(nx=2, nz=2, dx_m=1, dz_m=1)
>>> model = generate_layered_geology(
...     grid,
...     [ElectricalLayer("a", 1), ElectricalLayer("b", 2)],
...     [1],
...     seed=0,
... )
>>> model.n_layers
2
property model_hash: str#

Return a platform-stable digest of model values and provenance.

Returns:

SHA-256 digest.

Return type:

str

Examples

>>> grid = GeologyGrid.regular_2d(nx=2, nz=2, dx_m=1, dz_m=1)
>>> model = generate_layered_geology(
...     grid,
...     [ElectricalLayer("a", 1), ElectricalLayer("b", 2)],
...     [1],
...     seed=0,
... )
>>> len(model.model_hash)
64
layer_mask(layer)#

Return a read-only Boolean mask for one unit.

Parameters:

layer (int or str) – Zero-based index or exact layer name.

Returns:

Mask shaped like the geological grid.

Return type:

ndarray of bool

Raises:
  • KeyError – If a named unit is unavailable.

  • IndexError – If a numeric index is outside the layer range.

Examples

>>> grid = GeologyGrid.regular_2d(nx=2, nz=2, dx_m=1, dz_m=1)
>>> model = generate_layered_geology(
...     grid,
...     [ElectricalLayer("a", 1), ElectricalLayer("b", 2)],
...     [1],
...     seed=0,
... )
>>> model.layer_mask("a").shape
(2, 2)
interface(index)#

Return one read-only interface-depth surface.

Parameters:

index (int) – Zero-based interface between layers index and index + 1.

Returns:

Shape (nx,) in 2-D or (ny, nx) in 3-D.

Return type:

ndarray

Examples

>>> grid = GeologyGrid.regular_2d(nx=3, nz=2, dx_m=1, dz_m=1)
>>> model = generate_layered_geology(
...     grid,
...     [ElectricalLayer("a", 1), ElectricalLayer("b", 2)],
...     [1],
...     seed=0,
... )
>>> model.interface(0).shape
(3,)
summary()#

Return compact JSON-compatible geological diagnostics.

Returns:

Shape, unit fractions, interface ranges, resistivity range, and interface-adjustment fraction.

Return type:

dict

Examples

>>> grid = GeologyGrid.regular_2d(nx=2, nz=2, dx_m=1, dz_m=1)
>>> model = generate_layered_geology(
...     grid,
...     [ElectricalLayer("a", 1), ElectricalLayer("b", 2)],
...     [1],
...     seed=0,
... )
>>> model.summary()["n_layers"]
2
provenance()#

Return generation provenance without cell arrays.

Returns:

Grid, layer definitions, seed, and interface policies.

Return type:

dict

Examples

>>> grid = GeologyGrid.regular_2d(nx=2, nz=2, dx_m=1, dz_m=1)
>>> model = generate_layered_geology(
...     grid,
...     [ElectricalLayer("a", 1), ElectricalLayer("b", 2)],
...     [1],
...     seed=0,
... )
>>> model.provenance()["seed"]
0
to_npz(path)#

Persist the layered model in a pickle-free compressed archive.

Parameters:

path (str or pathlib.Path) – Destination NPZ path.

Returns:

Requested destination.

Return type:

pathlib.Path

Examples

>>> from tempfile import TemporaryDirectory
>>> grid = GeologyGrid.regular_2d(nx=2, nz=2, dx_m=1, dz_m=1)
>>> model = generate_layered_geology(
...     grid,
...     [ElectricalLayer("a", 1), ElectricalLayer("b", 2)],
...     [1],
...     seed=0,
... )
>>> with TemporaryDirectory() as directory:
...     path = model.to_npz(Path(directory) / "layers.npz")
...     restored = LayeredGeology.from_npz(path)
>>> restored.model_hash == model.model_hash
True
classmethod from_npz(path)#

Load and validate a layered model without enabling pickle.

Parameters:

path (str or pathlib.Path) – Archive written by to_npz().

Returns:

Immutable restored model.

Return type:

LayeredGeology

Examples

>>> from tempfile import TemporaryDirectory
>>> model = generate_layered_geology(
...     GeologyGrid.regular_2d(nx=2, nz=2, dx_m=1, dz_m=1),
...     [ElectricalLayer("a", 1), ElectricalLayer("b", 2)],
...     [1],
...     seed=0,
... )
>>> with TemporaryDirectory() as directory:
...     path = model.to_npz(Path(directory) / "m.npz")
...     restored = LayeredGeology.from_npz(path)
>>> np.array_equal(restored.layer_index, model.layer_index)
True
pycsamt.ai.geology.generate_layered_geology(grid, layers, mean_interface_depth_m, *, seed, interface_relief_std_m=0.0, interface_correlation=None, minimum_thickness_m=None, interface_policy='project', boundary='reflect')#

Generate a correlated 2-D section or 3-D layered electrical volume.

Parameters:
  • grid (GeologyGrid) – Regular target grid.

  • layers (sequence of ElectricalLayer) – Units ordered shallowest to deepest. At least one is required.

  • mean_interface_depth_m (sequence of float) – One mean depth for every adjacent layer pair.

  • seed (int) – Explicit root seed. Labeled child seeds independently drive interfaces and within-layer heterogeneity.

  • interface_relief_std_m (float or sequence, default=0.0) – Interface depth standard deviations in metres.

  • interface_correlation (GaussianCorrelation, sequence, or None, optional) – Horizontal correlation model(s). Required for every interface with positive relief. A single model is shared across interfaces.

  • minimum_thickness_m (float or None, optional) – Minimum separation between top boundary, interfaces, and bottom boundary. Default is one vertical cell.

  • interface_policy ({"project", "raise"}, default="project") – Project invalid/crossing surfaces into the feasible domain or reject the realization. Projection fraction is recorded on the result.

  • boundary ({"reflect", "periodic"}, default="reflect") – Boundary policy for all correlated fields.

Returns:

Immutable interfaces, unit indices, resistivity, and provenance.

Return type:

LayeredGeology

Raises:

ValueError – If layer/interface counts disagree, thickness is infeasible, relief lacks correlation, a 3-D correlation lacks length_y_m, or strict interface validation fails.

Examples

Laterally varying three-layer 2-D model:

>>> grid = GeologyGrid.regular_2d(nx=24, nz=16, dx_m=100, dz_m=50)
>>> units = [
...     ElectricalLayer("cover", 20),
...     ElectricalLayer("sediments", 100),
...     ElectricalLayer("basement", 1000),
... ]
>>> model = generate_layered_geology(
...     grid,
...     units,
...     [200, 500],
...     seed=5,
...     interface_relief_std_m=[30, 50],
...     interface_correlation=GaussianCorrelation(600, 100),
... )
>>> model.interface_depth_m.shape
(2, 24)
>>> set(np.unique(model.layer_index)) <= {0, 1, 2}
True
class pycsamt.ai.geology.EllipsoidalLens(name, center_x_m, center_z_m, radius_x_m, radius_z_m, resistivity_ohm_m, center_y_m=None, radius_y_m=None, azimuth_deg=0.0, dip_deg=0.0, transition_fraction=0.0)#

Bases: object

Define a rotated 2-D elliptical or 3-D ellipsoidal electrical body.

Parameters:
  • name (str) – Unique non-empty body name.

  • center_x_m (float) – Horizontal and depth coordinates of the body centre in metres.

  • center_z_m (float) – Horizontal and depth coordinates of the body centre in metres.

  • radius_x_m (float) – Positive principal semi-axis lengths in metres.

  • radius_z_m (float) – Positive principal semi-axis lengths in metres.

  • resistivity_ohm_m (float) – Positive target resistivity at the body core.

  • center_y_m (float or None, optional) – Second horizontal centre and radius. Both are required on a 3-D grid and both must be omitted on a 2-D grid.

  • radius_y_m (float or None, optional) – Second horizontal centre and radius. Both are required on a 3-D grid and both must be omitted on a 2-D grid.

  • azimuth_deg (float, default=0.0) – Clockwise rotation of the horizontal x principal axis in 3-D.

  • dip_deg (float, default=0.0) – Downward rotation of the x principal axis toward increasing depth. It rotates the x-z ellipse in 2-D and the azimuthal major-z plane in 3-D.

  • transition_fraction (float, default=0.0) – Fraction of the normalized outer radius occupied by a smooth transition shell. It must lie in [0, 1). Zero gives a sharp boundary.

Examples

A dipping conductive lens in a 2-D section:

>>> lens = EllipsoidalLens(
...     "conductor",
...     center_x_m=1000,
...     center_z_m=400,
...     radius_x_m=500,
...     radius_z_m=100,
...     resistivity_ohm_m=5,
...     dip_deg=20,
... )
>>> lens.dimension
2

A 3-D ellipsoid additionally declares y geometry:

>>> body = EllipsoidalLens(
...     "body", 0, 300, 400, 100, 10, center_y_m=0, radius_y_m=200
... )
>>> body.dimension
3
name: str#
center_x_m: float#
center_z_m: float#
radius_x_m: float#
radius_z_m: float#
resistivity_ohm_m: float#
center_y_m: float | None = None#
radius_y_m: float | None = None#
azimuth_deg: float = 0.0#
dip_deg: float = 0.0#
transition_fraction: float = 0.0#
property dimension: int#

Return the dimensionality implied by the lens geometry.

Returns:

Two when y geometry is absent, otherwise three.

Return type:

{2, 3}

Examples

>>> EllipsoidalLens("a", 0, 1, 2, 1, 10).dimension
2
validate_grid(grid, *, require_intersection=True)#

Validate dimensional and spatial compatibility with a grid.

Parameters:
  • grid (GeologyGrid) – Candidate 2-D section or 3-D volume.

  • require_intersection (bool, default=True) – Require at least one cell centre inside the body.

Returns:

Successful return means the lens can be rasterized.

Return type:

None

Raises:

ValueError – If dimensions differ or no cell centre intersects the lens.

Examples

>>> grid = GeologyGrid.regular_2d(nx=4, nz=4, dx_m=100, dz_m=50)
>>> lens = EllipsoidalLens("a", 200, 100, 150, 75, 10)
>>> lens.validate_grid(grid) is None
True
normalized_radius(grid)#

Evaluate dimensionless ellipsoidal radius at every cell centre.

Parameters:

grid (GeologyGrid) – Grid with the same dimensionality as the lens.

Returns:

Array shaped like grid. Values at or below one are inside the lens; zero is the geometric centre.

Return type:

ndarray

Examples

>>> grid = GeologyGrid.regular_2d(nx=4, nz=4, dx_m=100, dz_m=50)
>>> lens = EllipsoidalLens("a", 200, 100, 150, 75, 10)
>>> lens.normalized_radius(grid).shape
(4, 4)
blend_weight(grid)#

Return the lens contribution weight at every cell centre.

Parameters:

grid (GeologyGrid) – Compatible geological grid.

Returns:

Values in [0, 1]. A sharp lens is one inside and zero outside; a transition shell uses cubic smoothstep interpolation.

Return type:

ndarray

Examples

>>> grid = GeologyGrid.regular_2d(nx=4, nz=4, dx_m=100, dz_m=50)
>>> lens = EllipsoidalLens(
...     "a", 200, 100, 150, 75, 10, transition_fraction=0.2
... )
>>> np.all(
...     (lens.blend_weight(grid) >= 0) & (lens.blend_weight(grid) <= 1)
... )
True
to_dict()#

Return a JSON-serializable lens definition.

Returns:

Versioned geometry, orientation, electrical value, and transition.

Return type:

dict

Examples

>>> EllipsoidalLens("a", 0, 1, 2, 1, 10).to_dict()["name"]
'a'
classmethod from_dict(data)#

Restore a validated lens definition.

Parameters:

data (mapping) – State returned by to_dict().

Returns:

Immutable body definition.

Return type:

EllipsoidalLens

Examples

>>> lens = EllipsoidalLens("a", 0, 1, 2, 1, 10)
>>> EllipsoidalLens.from_dict(lens.to_dict()) == lens
True
class pycsamt.ai.geology.LensGeology(base, lenses, resistivity_ohm_m, lens_index, overlap_count, conflict_policy='error')#

Bases: object

Immutable layered model after one or more lens insertions.

Parameters:
  • base (LayeredGeology) – Self-contained stratigraphic model before lens insertion.

  • lenses (sequence of EllipsoidalLens) – Bodies in declared precedence order.

  • resistivity_ohm_m (ndarray) – Final positive resistivity model shaped like base.grid.

  • lens_index (ndarray of int) – Assigned body index per cell, or -1 outside assigned bodies.

  • overlap_count (ndarray of int) – Number of geometric lens envelopes covering each cell.

  • conflict_policy ({"error", "first", "last", "most_conductive", "most_resistive"}) – Policy used where body envelopes overlap.

Examples

>>> grid = GeologyGrid.regular_2d(nx=8, nz=6, dx_m=100, dz_m=50)
>>> base = __import__(
...     "pycsamt.ai.geology", fromlist=[""]
... ).generate_layered_geology(
...     grid, [ElectricalLayer("earth", 100)], [], seed=0
... )
>>> model = insert_lenses(
...     base, [EllipsoidalLens("lens", 400, 150, 200, 75, 10)]
... )
>>> model.resistivity_ohm_m.shape
(6, 8)
base: LayeredGeology#
lenses: tuple[EllipsoidalLens, ...]#
resistivity_ohm_m: ndarray#
lens_index: ndarray#
overlap_count: ndarray#
conflict_policy: str = 'error'#
property model_hash: str#

Return a digest of base, lenses, final values, and overlap policy.

Returns:

Platform-stable SHA-256 digest.

Return type:

str

Examples

>>> grid = GeologyGrid.regular_2d(nx=4, nz=4, dx_m=100, dz_m=50)
>>> base = __import__(
...     "pycsamt.ai.geology", fromlist=[""]
... ).generate_layered_geology(
...     grid, [ElectricalLayer("earth", 100)], [], seed=0
... )
>>> model = insert_lenses(
...     base, [EllipsoidalLens("lens", 200, 100, 100, 50, 10)]
... )
>>> len(model.model_hash)
64
lens_mask(lens)#

Return cells assigned to one lens after conflict resolution.

Parameters:

lens (int or str) – Zero-based lens index or exact body name.

Returns:

Read-only assigned-cell mask.

Return type:

ndarray of bool

Examples

>>> grid = GeologyGrid.regular_2d(nx=4, nz=4, dx_m=100, dz_m=50)
>>> base = __import__(
...     "pycsamt.ai.geology", fromlist=[""]
... ).generate_layered_geology(
...     grid, [ElectricalLayer("earth", 100)], [], seed=0
... )
>>> model = insert_lenses(
...     base, [EllipsoidalLens("lens", 200, 100, 100, 50, 10)]
... )
>>> model.lens_mask("lens").any()
True
summary()#

Return JSON-compatible lens occupancy and resistivity diagnostics.

Returns:

Assigned fractions, overlap fraction, resistivity range, and hashes.

Return type:

dict

Examples

>>> grid = GeologyGrid.regular_2d(nx=4, nz=4, dx_m=100, dz_m=50)
>>> base = __import__(
...     "pycsamt.ai.geology", fromlist=[""]
... ).generate_layered_geology(
...     grid, [ElectricalLayer("earth", 100)], [], seed=0
... )
>>> model = insert_lenses(
...     base, [EllipsoidalLens("lens", 200, 100, 100, 50, 10)]
... )
>>> model.summary()["n_lenses"]
1
provenance()#

Return generation provenance without final cell arrays.

Returns:

Base provenance/hash, lens definitions, and conflict policy.

Return type:

dict

Examples

>>> grid = GeologyGrid.regular_2d(nx=4, nz=4, dx_m=100, dz_m=50)
>>> base = __import__(
...     "pycsamt.ai.geology", fromlist=[""]
... ).generate_layered_geology(
...     grid, [ElectricalLayer("earth", 100)], [], seed=0
... )
>>> model = insert_lenses(
...     base, [EllipsoidalLens("lens", 200, 100, 100, 50, 10)]
... )
>>> model.provenance()["conflict_policy"]
'error'
to_npz(path)#

Persist base geology and lenses in one pickle-free NPZ archive.

Parameters:

path (str or pathlib.Path) – Destination archive.

Returns:

Requested destination path.

Return type:

pathlib.Path

Examples

>>> from tempfile import TemporaryDirectory
>>> grid = GeologyGrid.regular_2d(nx=4, nz=4, dx_m=100, dz_m=50)
>>> base = __import__(
...     "pycsamt.ai.geology", fromlist=[""]
... ).generate_layered_geology(
...     grid, [ElectricalLayer("earth", 100)], [], seed=0
... )
>>> model = insert_lenses(
...     base, [EllipsoidalLens("lens", 200, 100, 100, 50, 10)]
... )
>>> with TemporaryDirectory() as directory:
...     path = model.to_npz(Path(directory) / "lenses.npz")
...     restored = LensGeology.from_npz(path)
>>> restored.model_hash == model.model_hash
True
classmethod from_npz(path)#

Load and validate a self-contained lens-geology archive.

Parameters:

path (str or pathlib.Path) – Archive written by to_npz().

Returns:

Immutable restored base and body model.

Return type:

LensGeology

Examples

>>> from tempfile import TemporaryDirectory
>>> grid = GeologyGrid.regular_2d(nx=4, nz=4, dx_m=100, dz_m=50)
>>> base = __import__(
...     "pycsamt.ai.geology", fromlist=[""]
... ).generate_layered_geology(
...     grid, [ElectricalLayer("earth", 100)], [], seed=0
... )
>>> model = insert_lenses(
...     base, [EllipsoidalLens("lens", 200, 100, 100, 50, 10)]
... )
>>> with TemporaryDirectory() as directory:
...     restored = LensGeology.from_npz(
...         model.to_npz(Path(directory) / "m.npz")
...     )
>>> np.array_equal(restored.resistivity_ohm_m, model.resistivity_ohm_m)
True
pycsamt.ai.geology.insert_lenses(base, lenses, *, conflict_policy='error')#

Insert electrical lenses into a layered model with explicit overlap rules.

Parameters:
  • base (LayeredGeology) – Stratigraphic resistivity model to modify immutably.

  • lenses (sequence of EllipsoidalLens) – One or more uniquely named compatible bodies.

  • conflict_policy ({"error", "first", "last", "most_conductive", "most_resistive"}, default="error") – error rejects geometric overlap. first or last gives precedence by declaration order. The remaining policies select the lowest or highest blended cell resistivity.

Returns:

New immutable model; base remains unchanged.

Return type:

LensGeology

Raises:

ValueError – If bodies are empty, incompatible, duplicate-named, non-intersecting, or overlap under the error policy.

Examples

>>> from pycsamt.ai.geology import generate_layered_geology
>>> grid = GeologyGrid.regular_2d(nx=12, nz=8, dx_m=100, dz_m=50)
>>> base = generate_layered_geology(
...     grid, [ElectricalLayer("earth", 100)], [], seed=0
... )
>>> lens = EllipsoidalLens(
...     "conductor", 600, 200, 250, 100, 5, transition_fraction=0.2
... )
>>> result = insert_lenses(base, [lens])
>>> np.min(result.resistivity_ohm_m) < 100
True
>>> np.array_equal(base.resistivity_ohm_m, np.full(grid.shape, 100.0))
True
class pycsamt.ai.geology.TopographicSurface(grid, elevation_m, reference_elevation_m, vertical_datum='metres above sea level', source='array', interpolation_method='linear', sample_coordinates_m=None, sample_elevation_m=None, station_names=())#

Bases: object

Immutable terrain elevation raster aligned to a geological grid.

Parameters:
  • grid (GeologyGrid) – Grid whose horizontal cell centres are sampled by the surface.

  • elevation_m (ndarray) – Elevation above vertical_datum. Shape is (nx,) in 2-D or (ny, nx) in 3-D.

  • reference_elevation_m (float) – Elevation corresponding to geological depth zero. Using the maximum terrain elevation keeps all surface depths non-negative.

  • vertical_datum (str, default="metres above sea level") – Human-readable vertical datum and unit description.

  • source (str, default="array") – Provenance label such as "sites", "dem", or "array".

  • interpolation_method ({"linear", "nearest", "cubic"}, default="linear") – Method used to rasterize samples.

  • sample_coordinates_m (ndarray or None, optional) – Projected sample x positions in 2-D or x/y positions in 3-D.

  • sample_elevation_m (ndarray or None, optional) – Elevations corresponding to sample coordinates.

  • station_names (sequence of str, optional) – Station identifiers corresponding to samples.

Examples

>>> grid = GeologyGrid.regular_2d(nx=4, nz=3, dx_m=100, dz_m=50)
>>> surface = TopographicSurface(grid, [100, 110, 105, 95], 110)
>>> surface.relief_m
15.0
>>> surface.earth_mask().shape
(3, 4)
grid: GeologyGrid#
elevation_m: ndarray#
reference_elevation_m: float#
vertical_datum: str = 'metres above sea level'#
source: str = 'array'#
interpolation_method: str = 'linear'#
sample_coordinates_m: ndarray | None = None#
sample_elevation_m: ndarray | None = None#
station_names: tuple[str, ...] = ()#
property relief_m: float#

Return maximum minus minimum terrain elevation.

Returns:

Relief in metres.

Return type:

float

Examples

>>> grid = GeologyGrid.regular_2d(nx=2, nz=2, dx_m=1, dz_m=1)
>>> TopographicSurface(grid, [10, 13], 13).relief_m
3.0
property surface_depth_m: ndarray#

Return terrain depth below the configured reference elevation.

Returns:

Horizontal surface shaped like elevation_m. Positive values lie below reference depth zero; negative values lie above it.

Return type:

ndarray

Examples

>>> grid = GeologyGrid.regular_2d(nx=2, nz=2, dx_m=1, dz_m=1)
>>> TopographicSurface(grid, [10, 8], 10).surface_depth_m.tolist()
[0.0, 2.0]
property surface_hash: str#

Return a platform-stable terrain and provenance digest.

Returns:

SHA-256 digest.

Return type:

str

Examples

>>> grid = GeologyGrid.regular_2d(nx=2, nz=2, dx_m=1, dz_m=1)
>>> len(TopographicSurface(grid, [10, 9], 10).surface_hash)
64
local_depth_m()#

Return each cell centre’s signed depth below local terrain.

Returns:

Array shaped like grid. Non-negative values are in the earth; negative values are above terrain and belong to the air region.

Return type:

ndarray

Examples

>>> grid = GeologyGrid.regular_2d(nx=2, nz=2, dx_m=1, dz_m=1)
>>> surface = TopographicSurface(grid, [10, 9], 10)
>>> surface.local_depth_m().shape
(2, 2)
earth_mask()#

Return cells on or below the local terrain surface.

Returns:

Read-only physics-facing earth mask shaped like grid.

Return type:

ndarray of bool

Examples

>>> grid = GeologyGrid.regular_2d(nx=2, nz=2, dx_m=1, dz_m=1)
>>> TopographicSurface(grid, [10, 9], 10).earth_mask().dtype == bool
True
air_mask()#

Return cells strictly above the local terrain surface.

Returns:

Logical complement of earth_mask().

Return type:

ndarray of bool

Examples

>>> grid = GeologyGrid.regular_2d(nx=2, nz=2, dx_m=1, dz_m=1)
>>> surface = TopographicSurface(grid, [10, 9], 10)
>>> np.array_equal(surface.air_mask(), ~surface.earth_mask())
True
slope_degrees()#

Return terrain slope magnitude at horizontal cell centres.

Returns:

Slope angle in degrees, shaped like elevation_m.

Return type:

ndarray

Examples

>>> grid = GeologyGrid.regular_2d(nx=3, nz=2, dx_m=10, dz_m=1)
>>> slope = TopographicSurface(grid, [0, 10, 20], 20).slope_degrees()
>>> np.allclose(slope, 45)
True
summary()#

Return compact JSON-compatible terrain diagnostics.

Returns:

Elevation range, relief, slopes, air fraction, source, and datum.

Return type:

dict

Examples

>>> grid = GeologyGrid.regular_2d(nx=2, nz=2, dx_m=1, dz_m=1)
>>> TopographicSurface(grid, [10, 9], 10).summary()["relief_m"]
1.0
provenance()#

Return complete JSON-compatible surface provenance.

Returns:

Grid, datum, source, interpolation, samples, and station names.

Return type:

dict

Examples

>>> grid = GeologyGrid.regular_2d(nx=2, nz=2, dx_m=1, dz_m=1)
>>> TopographicSurface(grid, [10, 9], 10).provenance()["source"]
'array'
to_npz(path)#

Persist terrain and provenance in a pickle-free NPZ archive.

Parameters:

path (str or pathlib.Path) – Destination archive.

Returns:

Requested destination.

Return type:

pathlib.Path

Examples

>>> from tempfile import TemporaryDirectory
>>> surface = TopographicSurface(
...     GeologyGrid.regular_2d(nx=2, nz=2, dx_m=1, dz_m=1), [10, 9], 10
... )
>>> with TemporaryDirectory() as directory:
...     restored = TopographicSurface.from_npz(
...         surface.to_npz(Path(directory) / "topo.npz")
...     )
>>> restored.surface_hash == surface.surface_hash
True
classmethod from_npz(path)#

Load and validate a topographic archive without enabling pickle.

Parameters:

path (str or pathlib.Path) – Archive written by to_npz().

Returns:

Immutable restored surface.

Return type:

TopographicSurface

Examples

>>> from tempfile import TemporaryDirectory
>>> surface = TopographicSurface(
...     GeologyGrid.regular_2d(nx=2, nz=2, dx_m=1, dz_m=1), [10, 9], 10
... )
>>> with TemporaryDirectory() as directory:
...     restored = TopographicSurface.from_npz(
...         surface.to_npz(Path(directory) / "t.npz")
...     )
>>> np.array_equal(restored.elevation_m, surface.elevation_m)
True
pycsamt.ai.geology.interpolate_topography(grid, sample_coordinates_m, sample_elevation_m, *, interpolation_method='linear', reference_elevation_m=None, vertical_datum='metres above sea level', source='array', station_names=())#

Interpolate projected elevation samples onto a geological grid.

Parameters:
  • grid (GeologyGrid) – Target 2-D or 3-D grid.

  • sample_coordinates_m (ndarray) – Shape (n,) or (n, 1) x positions in 2-D; shape (n, 2) x/y projected coordinates in 3-D.

  • sample_elevation_m (ndarray, shape (n,)) – Finite elevations in metres above the declared datum.

  • interpolation_method ({"linear", "nearest", "cubic"}, default="linear") – Interpolation method. Values outside the convex hull are filled from nearest samples rather than extrapolated polynomials.

  • reference_elevation_m (float or None, optional) – Geological depth-zero elevation. Default is maximum raster elevation.

  • vertical_datum (str, optional) – Provenance labels.

  • source (str, optional) – Provenance labels.

  • station_names (sequence of str, optional) – Unique labels for every sample.

Returns:

Immutable raster and original samples.

Return type:

TopographicSurface

Examples

>>> grid = GeologyGrid.regular_2d(nx=4, nz=3, dx_m=100, dz_m=50)
>>> surface = interpolate_topography(grid, [0, 400], [100, 120])
>>> surface.elevation_m.shape
(4,)
pycsamt.ai.geology.topography_from_sites(sites, grid, *, station_names=None, coordinates_m=None, interpolation_method='linear', reference_elevation_m=None, vertical_datum='metres above sea level', profile_origin_m=None, allow_all_zero=False)#

Extract station elevations with pycsamt.topo and rasterize them.

Parameters:
  • sites (Sites or EDI-like collection) – Any container accepted by pycsamt.topo.extract_elevation().

  • grid (GeologyGrid) – Target geology grid.

  • station_names (sequence of str or None, optional) – Requested station order/subset. Matching is case-insensitive.

  • coordinates_m (ndarray or None, optional) – Required projected x/y coordinates for a 3-D grid. For 2-D, when supplied as (n, 2), cumulative projected chainage replaces the latitude/longitude-derived chainage from pycsamt.topo.

  • interpolation_method (str) – Forwarded to interpolate_topography().

  • reference_elevation_m (float | None) – Forwarded to interpolate_topography().

  • vertical_datum (str) – Forwarded to interpolate_topography().

  • profile_origin_m (float or None, optional) – X coordinate assigned to zero chainage in 2-D. Default is the grid’s minimum outer x edge.

  • allow_all_zero (bool, default=False) – Permit an all-zero elevation collection. False rejects the ambiguity between genuine sea-level terrain and missing EDI elevations.

Returns:

Physics-facing raster with original station samples retained.

Return type:

TopographicSurface

Raises:

ValueError – If station alignment, elevations, chainage, or required 3-D projected coordinates are invalid.

Examples

>>> from types import SimpleNamespace
>>> stations = [
...     SimpleNamespace(
...         Head=SimpleNamespace(dataid="S00", elev=100, lat=5, lon=-3)
...     ),
...     SimpleNamespace(
...         Head=SimpleNamespace(dataid="S01", elev=110, lat=5, lon=-2.999)
...     ),
... ]
>>> grid = GeologyGrid.regular_2d(nx=3, nz=2, dx_m=50, dz_m=20)
>>> surface = topography_from_sites(stations, grid)
>>> surface.source, surface.station_names
('sites', ('S00', 'S01'))

pycsamt.ai.geology.fields

Regular geological grids and anisotropic correlated Gaussian fields.

pycsamt.ai.geology.layers

Correlated stratigraphic interfaces and layered resistivity priors.

pycsamt.ai.geology.lenses

Rotated ellipsoidal lenses embedded in layered electrical geology.

pycsamt.ai.geology.topography

Physics-facing topographic surfaces on geological model grids.

pycsamt.ai.geology.benchmark

Seeded geological families for two-dimensional EM benchmarks.