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:
objectImmutable 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)
- 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:
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:
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.
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:
- 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:
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:
Examples
>>> GeologyGrid.regular_2d(nx=2, nz=2, dx_m=1, dz_m=1).to_dict()[ ... "dimension" ... ] 2
- class pycsamt.ai.geology.GaussianCorrelation(length_x_m, length_z_m, length_y_m=None, azimuth_deg=0.0)#
Bases:
objectAnisotropic 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
- property anisotropy_xz: float#
Return horizontal-to-vertical correlation-length ratio.
- Returns:
length_x_m / length_z_m.- Return type:
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:
TypeError – If
gridis notGeologyGrid.ValueError – If a 3-D grid has no
length_y_m.
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:
Examples
>>> GaussianCorrelation(10, 2).to_dict()["kind"] 'gaussian'
Bases:
objectImmutable 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
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:
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
Return JSON-compatible generation provenance without field values.
- Returns:
Grid, correlation, seed, boundary, and standardization state.
- Return type:
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
Return a copy with a requested sample mean and standard deviation.
- Parameters:
- Returns:
New field with unchanged spatial pattern and
standardized=Falseunless the requested values are exactly zero and one.- Return type:
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)
Persist values and provenance in a pickle-free NPZ archive.
- Parameters:
path (str or pathlib.Path) – Destination archive.
- Returns:
Requested destination path.
- Return type:
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
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:
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:
objectEmpirical 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
- 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") –
periodicsynthesizes directly on the requested grid.reflectsynthesizes 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:
- 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:
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:
objectOne 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.
- grid: GeologyGrid#
- 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_FAMILIESorOOD_BENCHMARK_FAMILIES.seed (int) – Explicit root seed.
configuration (mapping) – Frozen mapping containing
base_layered,id_families, andood_familiessections.regime ({"id", "ood"}, default="id") – Distribution regime.
- Returns:
Positive resistivity model and concrete sampled provenance.
- Return type:
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:
objectDefine 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_stdis 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), ... )
- heterogeneity: GaussianCorrelation | None = None#
- property log10_resistivity: float#
Return the median resistivity in log10 ohm metres.
- Returns:
log10(resistivity_ohm_m).- Return type:
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:
Examples
>>> ElectricalLayer("unit", 100).to_dict()["resistivity_ohm_m"] 100.0
- 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:
objectImmutable 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, ...]#
- property n_layers: int#
Return the number of stratigraphic units.
- Returns:
Number of electrical layers.
- Return type:
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:
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:
- 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
indexandindex + 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:
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:
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:
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:
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:
- 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:
objectDefine 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
- 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:
Examples
>>> EllipsoidalLens("a", 0, 1, 2, 1, 10).to_dict()["name"] 'a'
- class pycsamt.ai.geology.LensGeology(base, lenses, resistivity_ohm_m, lens_index, overlap_count, conflict_policy='error')#
Bases:
objectImmutable 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
-1outside 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, ...]#
- property model_hash: str#
Return a digest of base, lenses, final values, and overlap policy.
- Returns:
Platform-stable SHA-256 digest.
- Return type:
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:
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:
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:
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:
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") –
errorrejects geometric overlap.firstorlastgives precedence by declaration order. The remaining policies select the lowest or highest blended cell resistivity.
- Returns:
New immutable model;
baseremains unchanged.- Return type:
- Raises:
ValueError – If bodies are empty, incompatible, duplicate-named, non-intersecting, or overlap under the
errorpolicy.
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:
objectImmutable 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#
- property relief_m: float#
Return maximum minus minimum terrain elevation.
- Returns:
Relief in metres.
- Return type:
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:
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:
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:
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:
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:
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:
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.topoand 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 frompycsamt.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:
- 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'))
|
Regular geological grids and anisotropic correlated Gaussian fields. |
|
Correlated stratigraphic interfaces and layered resistivity priors. |
|
Rotated ellipsoidal lenses embedded in layered electrical geology. |
|
Physics-facing topographic surfaces on geological model grids. |
|
Seeded geological families for two-dimensional EM benchmarks. |