pycsamt.ai.geology.lenses#
Rotated ellipsoidal lenses embedded in layered electrical geology.
Lens geometry is evaluated at cell centres. A lens may have a sharp boundary or a finite transition shell blended in log10-resistivity space. Overlap is never implicit: callers choose a conflict policy and the resulting overlap count is retained for auditing.
Functions
|
Insert electrical lenses into a layered model with explicit overlap rules. |
Classes
|
Define a rotated 2-D elliptical or 3-D ellipsoidal electrical body. |
|
Immutable layered model after one or more lens insertions. |
- class pycsamt.ai.geology.lenses.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)[source]
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
- name: str
- center_x_m: float
- center_z_m: float
- radius_x_m: float
- radius_z_m: float
- resistivity_ohm_m: float
- azimuth_deg: float = 0.0
- dip_deg: float = 0.0
- transition_fraction: float = 0.0
- property dimension: int[source]
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)[source]
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)[source]
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)[source]
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()[source]
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'
- classmethod from_dict(data)[source]
Restore a validated lens definition.
- Parameters:
data (mapping) – State returned by
to_dict().- Returns:
Immutable body definition.
- Return type:
Examples
>>> lens = EllipsoidalLens("a", 0, 1, 2, 1, 10) >>> EllipsoidalLens.from_dict(lens.to_dict()) == lens True
- class pycsamt.ai.geology.lenses.LensGeology(base, lenses, resistivity_ohm_m, lens_index, overlap_count, conflict_policy='error')[source]
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, ...]
- resistivity_ohm_m: ndarray
- lens_index: ndarray
- overlap_count: ndarray
- conflict_policy: str = 'error'
- property model_hash: str[source]
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)[source]
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()[source]
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()[source]
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)[source]
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)[source]
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.lenses.insert_lenses(base, lenses, *, conflict_policy='error')[source]
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