Skip to content

Commit 6b2ad60

Browse files
authored
[REFACTOR] Restructure geophysics input classes for better magnetics and gravity handling (#41)
# Refactor Geophysics Input Structure and Improve Tensor Type Handling ## [FIX] Improve tensor type matching and error reporting in backend_tensor.py - Fix inconsistent tensor type matching for PyTorch tensors - Enhance error message to include unsupported tensor type details ## [ENH] Refactor magnetics input structure and update TMI computation - Introduced `GravityInput` and `MagneticsInput` dataclasses to encapsulate related geophysical fields - Updated `GeophysicsInput` to utilize `GravityInput` and `MagneticsInput` for improved modularity - Modified `compute_tmi` to accept `MagneticsInput` instead of `GeophysicsInput` for better type specificity - Adjusted `model_api.py` to align with new `MagneticsInput` structure ## [ENH] Refactor magnetics input handling and relocate `_direction_cosines` - Simplified magnetics input condition check in `model_api.py` by replacing `mag_kernel` and `susceptibilities` with `magnetics_input` - Moved `_direction_cosines` method within `magnetic_gradient.py` for better modularity and code organization
2 parents b49c6fa + 246479e commit 6b2ad60

File tree

5 files changed

+89
-44
lines changed

5 files changed

+89
-44
lines changed

gempy_engine/API/model/model_api.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,10 @@ def compute_model(interpolation_input: InterpolationInput, options: Interpolatio
5959

6060
# Magnetics (optional)
6161
try:
62-
if getattr(geophysics_input, 'mag_kernel', None) is not None and getattr(geophysics_input, 'susceptibilities', None) is not None:
62+
if getattr(geophysics_input, 'magnetics_input', None) is not None:
6363
magnetics = compute_tmi(
64-
geophysics_input=geophysics_input,
65-
root_ouput=first_level_last_field
64+
geophysics_input=geophysics_input.magnetics_input,
65+
root_output=first_level_last_field
6666
)
6767
else:
6868
magnetics = None

gempy_engine/core/backend_tensor.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,9 +199,9 @@ def _concatenate(tensors, axis=0, dtype=None):
199199
match type(tensors[0]):
200200
case _ if any(isinstance(t, numpy.ndarray) for t in tensors):
201201
return numpy.concatenate(tensors, axis=axis)
202-
case torch.Tensor:
202+
case _ if isinstance(tensors[0], torch.Tensor):
203203
return torch.cat(tensors, dim=axis)
204-
raise TypeError("Unsupported tensor type")
204+
raise TypeError(f"Unsupported tensor type: {type(tensors[0])}")
205205

206206
def _transpose(tensor, axes=None):
207207
return tensor.transpose(axes[0], axes[1])
Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,66 @@
1-
from dataclasses import dataclass
1+
import warnings
2+
from dataclasses import dataclass
23
from typing import Annotated, Optional
34

45
import numpy as np
56

67
from .encoders.converters import numpy_array_short_validator
78

89

10+
@dataclass
11+
class GravityInput:
12+
tz: Annotated[np.ndarray, numpy_array_short_validator]
13+
densities: Annotated[np.ndarray, numpy_array_short_validator]
14+
15+
16+
@dataclass
17+
class MagneticsInput:
18+
mag_kernel: np.ndarray
19+
susceptibilities: np.ndarray
20+
igrf_params: dict
21+
22+
923
@dataclass
1024
class GeophysicsInput:
11-
# Gravity inputs (optional to allow magnetics-only workflows)
12-
tz: Optional[Annotated[np.ndarray, numpy_array_short_validator]] = None
13-
densities: Optional[Annotated[np.ndarray, numpy_array_short_validator]] = None
14-
15-
# Magnetics inputs (optional for Phase 1)
16-
# Pre-projected TMI kernel per voxel (per device geometry), shape: (n_voxels_per_device,)
17-
mag_kernel: Optional[np.ndarray] = None
18-
# Susceptibilities per geologic unit (dimensionless SI), shape: (n_units,)
19-
susceptibilities: Optional[np.ndarray] = None
20-
# IGRF parameters metadata used to build kernel (inclination, declination, intensity (nT))
21-
igrf_params: Optional[dict] = None
25+
gravity_input: Optional[GravityInput] = None
26+
magnetics_input: Optional[MagneticsInput] = None
27+
28+
def __init__(self, gravity_input: Optional[GravityInput] = None,
29+
magnetics_input: Optional[MagneticsInput] = None,
30+
tz: Optional[Annotated[np.ndarray, numpy_array_short_validator]] = None,
31+
densities: Optional[Annotated[np.ndarray, numpy_array_short_validator]] = None):
32+
if gravity_input is not None:
33+
self.gravity_input = gravity_input
34+
else:
35+
warnings.warn("Using deprecated GeophysicsInput constructor. Use GravityInput instead.", DeprecationWarning)
36+
self.gravity_input = GravityInput(tz=tz, densities=densities)
37+
if magnetics_input is not None:
38+
self.magnetics_input = magnetics_input
39+
40+
@property
41+
def tz(self) -> Optional[Annotated[np.ndarray, numpy_array_short_validator]]:
42+
if self.gravity_input is not None:
43+
return self.gravity_input.tz
44+
return None
45+
46+
@tz.setter
47+
def tz(self, value: Optional[Annotated[np.ndarray, numpy_array_short_validator]]):
48+
if value is not None:
49+
if self.gravity_input is None:
50+
self.gravity_input = GravityInput(tz=value, densities=None)
51+
else:
52+
self.gravity_input.tz = value
53+
54+
@property
55+
def densities(self) -> Optional[Annotated[np.ndarray, numpy_array_short_validator]]:
56+
if self.gravity_input is not None:
57+
return self.gravity_input.densities
58+
return None
59+
60+
@densities.setter
61+
def densities(self, value: Optional[Annotated[np.ndarray, numpy_array_short_validator]]):
62+
if value is not None:
63+
if self.gravity_input is None:
64+
self.gravity_input = GravityInput(tz=None, densities=value)
65+
else:
66+
self.gravity_input.densities = value

gempy_engine/modules/geophysics/fw_magnetic.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import numpy as np
22

33
from .magnetic_gradient import _direction_cosines
4-
from ...core.data.geophysics_input import GeophysicsInput
4+
from ...core.data.geophysics_input import GeophysicsInput, MagneticsInput
55
from ...core.data.interp_output import InterpOutput
66
from ...core.backend_tensor import BackendTensor
77

@@ -14,7 +14,7 @@ def map_susceptibilities_to_ids_basic(ids_geophysics_grid, susceptibilities):
1414
return susceptibilities[ids_geophysics_grid - 1]
1515

1616

17-
def compute_tmi(geophysics_input: GeophysicsInput, root_output: InterpOutput) -> BackendTensor.t:
17+
def compute_tmi(geophysics_input: MagneticsInput, root_output: InterpOutput) -> BackendTensor.t:
1818
"""
1919
Compute induced-only Total Magnetic Intensity (TMI) anomalies (nT) by combining
2020
precomputed per-voxel TMI kernel with voxel susceptibilities.

gempy_engine/modules/geophysics/magnetic_gradient.py

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,6 @@
33
from gempy_engine.core.data.centered_grid import CenteredGrid
44

55

6-
def _direction_cosines(inclination_deg: float, declination_deg: float) -> np.ndarray:
7-
"""Compute unit vector of Earth's field from inclination/declination.
8-
9-
Convention:
10-
- Inclination I: positive downward from horizontal, in degrees [-90, 90]
11-
- Declination D: clockwise from geographic north toward east, in degrees [-180, 180]
12-
13-
Returns unit vector l = [lx, ly, lz].
14-
"""
15-
I = np.deg2rad(inclination_deg)
16-
D = np.deg2rad(declination_deg)
17-
cI = np.cos(I)
18-
sI = np.sin(I)
19-
cD = np.cos(D)
20-
sD = np.sin(D)
21-
# North (x), East (y), Down (z) convention
22-
l = np.array([cI * cD, cI * sD, sI], dtype=float)
23-
# Already unit length by construction, but normalize defensively
24-
n = np.linalg.norm(l)
25-
if n == 0:
26-
return np.array([1.0, 0.0, 0.0], dtype=float)
27-
return l / n
28-
29-
306
def calculate_magnetic_gradient_components(centered_grid: CenteredGrid) -> np.ndarray:
317
"""
328
Calculate the 6 independent magnetic gradient tensor components (V1-V6) for each voxel.
@@ -51,7 +27,7 @@ def calculate_magnetic_gradient_components(centered_grid: CenteredGrid) -> np.nd
5127
over rectangular prism voxels using the formulas from Blakely (1995).
5228
The sign convention follows Talwani (z-axis positive downwards).
5329
"""
54-
30+
5531
voxel_centers = centered_grid.kernel_grid_centers
5632
center_x, center_y, center_z = voxel_centers[:, 0], voxel_centers[:, 1], voxel_centers[:, 2]
5733

@@ -163,3 +139,27 @@ def calculate_magnetic_gradient_tensor(
163139
result["V"] = V
164140

165141
return result
142+
143+
144+
def _direction_cosines(inclination_deg: float, declination_deg: float) -> np.ndarray:
145+
"""Compute unit vector of Earth's field from inclination/declination.
146+
147+
Convention:
148+
- Inclination I: positive downward from horizontal, in degrees [-90, 90]
149+
- Declination D: clockwise from geographic north toward east, in degrees [-180, 180]
150+
151+
Returns unit vector l = [lx, ly, lz].
152+
"""
153+
I = np.deg2rad(inclination_deg)
154+
D = np.deg2rad(declination_deg)
155+
cI = np.cos(I)
156+
sI = np.sin(I)
157+
cD = np.cos(D)
158+
sD = np.sin(D)
159+
# North (x), East (y), Down (z) convention
160+
l = np.array([cI * cD, cI * sD, sI], dtype=float)
161+
# Already unit length by construction, but normalize defensively
162+
n = np.linalg.norm(l)
163+
if n == 0:
164+
return np.array([1.0, 0.0, 0.0], dtype=float)
165+
return l / n

0 commit comments

Comments
 (0)