From 0897cb55a9e2d83aa36f94d163e024d2113f9438 Mon Sep 17 00:00:00 2001 From: Miguel de la Varga Date: Tue, 4 Nov 2025 15:06:44 +0100 Subject: [PATCH 1/4] [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. --- gempy_engine/core/backend_tensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gempy_engine/core/backend_tensor.py b/gempy_engine/core/backend_tensor.py index 8f96523..c79263c 100644 --- a/gempy_engine/core/backend_tensor.py +++ b/gempy_engine/core/backend_tensor.py @@ -199,9 +199,9 @@ def _concatenate(tensors, axis=0, dtype=None): match type(tensors[0]): case _ if any(isinstance(t, numpy.ndarray) for t in tensors): return numpy.concatenate(tensors, axis=axis) - case torch.Tensor: + case _ if isinstance(tensors[0], torch.Tensor): return torch.cat(tensors, dim=axis) - raise TypeError("Unsupported tensor type") + raise TypeError(f"Unsupported tensor type: {type(tensors[0])}") def _transpose(tensor, axes=None): return tensor.transpose(axes[0], axes[1]) From 089c617de59f13b11a4ccb88c6c6cf672429ed34 Mon Sep 17 00:00:00 2001 From: Miguel de la Varga Date: Mon, 17 Nov 2025 15:01:47 +0100 Subject: [PATCH 2/4] [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. --- gempy_engine/API/model/model_api.py | 4 +- gempy_engine/core/data/geophysics_input.py | 54 +++++++++++++++---- .../modules/geophysics/fw_magnetic.py | 4 +- 3 files changed, 47 insertions(+), 15 deletions(-) diff --git a/gempy_engine/API/model/model_api.py b/gempy_engine/API/model/model_api.py index 2ee3910..a3d3d33 100644 --- a/gempy_engine/API/model/model_api.py +++ b/gempy_engine/API/model/model_api.py @@ -61,8 +61,8 @@ def compute_model(interpolation_input: InterpolationInput, options: Interpolatio try: if getattr(geophysics_input, 'mag_kernel', None) is not None and getattr(geophysics_input, 'susceptibilities', None) is not None: magnetics = compute_tmi( - geophysics_input=geophysics_input, - root_ouput=first_level_last_field + geophysics_input=geophysics_input.magnetics_input, + root_output=first_level_last_field ) else: magnetics = None diff --git a/gempy_engine/core/data/geophysics_input.py b/gempy_engine/core/data/geophysics_input.py index ce90dab..0a2dff0 100644 --- a/gempy_engine/core/data/geophysics_input.py +++ b/gempy_engine/core/data/geophysics_input.py @@ -6,16 +6,48 @@ from .encoders.converters import numpy_array_short_validator +@dataclass +class GravityInput: + tz: Annotated[np.ndarray, numpy_array_short_validator] + densities: Annotated[np.ndarray, numpy_array_short_validator] + + +@dataclass +class MagneticsInput: + mag_kernel: np.ndarray + susceptibilities: np.ndarray + igrf_params: dict + + @dataclass class GeophysicsInput: - # Gravity inputs (optional to allow magnetics-only workflows) - tz: Optional[Annotated[np.ndarray, numpy_array_short_validator]] = None - densities: Optional[Annotated[np.ndarray, numpy_array_short_validator]] = None - - # Magnetics inputs (optional for Phase 1) - # Pre-projected TMI kernel per voxel (per device geometry), shape: (n_voxels_per_device,) - mag_kernel: Optional[np.ndarray] = None - # Susceptibilities per geologic unit (dimensionless SI), shape: (n_units,) - susceptibilities: Optional[np.ndarray] = None - # IGRF parameters metadata used to build kernel (inclination, declination, intensity (nT)) - igrf_params: Optional[dict] = None \ No newline at end of file + gravity_input: Optional[GravityInput] = None + magnetics_input: Optional[MagneticsInput] = None + + @property + def tz(self) -> Optional[Annotated[np.ndarray, numpy_array_short_validator]]: + if self.gravity_input is not None: + return self.gravity_input.tz + return None + + @tz.setter + def tz(self, value: Optional[Annotated[np.ndarray, numpy_array_short_validator]]): + if value is not None: + if self.gravity_input is None: + self.gravity_input = GravityInput(tz=value, densities=None) + else: + self.gravity_input.tz = value + + @property + def densities(self) -> Optional[Annotated[np.ndarray, numpy_array_short_validator]]: + if self.gravity_input is not None: + return self.gravity_input.densities + return None + + @densities.setter + def densities(self, value: Optional[Annotated[np.ndarray, numpy_array_short_validator]]): + if value is not None: + if self.gravity_input is None: + self.gravity_input = GravityInput(tz=None, densities=value) + else: + self.gravity_input.densities = value diff --git a/gempy_engine/modules/geophysics/fw_magnetic.py b/gempy_engine/modules/geophysics/fw_magnetic.py index a382e67..94e2161 100644 --- a/gempy_engine/modules/geophysics/fw_magnetic.py +++ b/gempy_engine/modules/geophysics/fw_magnetic.py @@ -1,7 +1,7 @@ import numpy as np from .magnetic_gradient import _direction_cosines -from ...core.data.geophysics_input import GeophysicsInput +from ...core.data.geophysics_input import GeophysicsInput, MagneticsInput from ...core.data.interp_output import InterpOutput from ...core.backend_tensor import BackendTensor @@ -14,7 +14,7 @@ def map_susceptibilities_to_ids_basic(ids_geophysics_grid, susceptibilities): return susceptibilities[ids_geophysics_grid - 1] -def compute_tmi(geophysics_input: GeophysicsInput, root_output: InterpOutput) -> BackendTensor.t: +def compute_tmi(geophysics_input: MagneticsInput, root_output: InterpOutput) -> BackendTensor.t: """ Compute induced-only Total Magnetic Intensity (TMI) anomalies (nT) by combining precomputed per-voxel TMI kernel with voxel susceptibilities. From 5e227f7a6fc4820f1452b0964df42f7c65e0b0c2 Mon Sep 17 00:00:00 2001 From: Miguel de la Varga Date: Mon, 17 Nov 2025 15:36:32 +0100 Subject: [PATCH 3/4] [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. --- gempy_engine/API/model/model_api.py | 2 +- .../modules/geophysics/magnetic_gradient.py | 50 +++++++++---------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/gempy_engine/API/model/model_api.py b/gempy_engine/API/model/model_api.py index a3d3d33..05eb272 100644 --- a/gempy_engine/API/model/model_api.py +++ b/gempy_engine/API/model/model_api.py @@ -59,7 +59,7 @@ def compute_model(interpolation_input: InterpolationInput, options: Interpolatio # Magnetics (optional) try: - if getattr(geophysics_input, 'mag_kernel', None) is not None and getattr(geophysics_input, 'susceptibilities', None) is not None: + if getattr(geophysics_input, 'magnetics_input', None) is not None: magnetics = compute_tmi( geophysics_input=geophysics_input.magnetics_input, root_output=first_level_last_field diff --git a/gempy_engine/modules/geophysics/magnetic_gradient.py b/gempy_engine/modules/geophysics/magnetic_gradient.py index 015d567..26e08de 100644 --- a/gempy_engine/modules/geophysics/magnetic_gradient.py +++ b/gempy_engine/modules/geophysics/magnetic_gradient.py @@ -3,30 +3,6 @@ from gempy_engine.core.data.centered_grid import CenteredGrid -def _direction_cosines(inclination_deg: float, declination_deg: float) -> np.ndarray: - """Compute unit vector of Earth's field from inclination/declination. - - Convention: - - Inclination I: positive downward from horizontal, in degrees [-90, 90] - - Declination D: clockwise from geographic north toward east, in degrees [-180, 180] - - Returns unit vector l = [lx, ly, lz]. - """ - I = np.deg2rad(inclination_deg) - D = np.deg2rad(declination_deg) - cI = np.cos(I) - sI = np.sin(I) - cD = np.cos(D) - sD = np.sin(D) - # North (x), East (y), Down (z) convention - l = np.array([cI * cD, cI * sD, sI], dtype=float) - # Already unit length by construction, but normalize defensively - n = np.linalg.norm(l) - if n == 0: - return np.array([1.0, 0.0, 0.0], dtype=float) - return l / n - - def calculate_magnetic_gradient_components(centered_grid: CenteredGrid) -> np.ndarray: """ 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 over rectangular prism voxels using the formulas from Blakely (1995). The sign convention follows Talwani (z-axis positive downwards). """ - + voxel_centers = centered_grid.kernel_grid_centers center_x, center_y, center_z = voxel_centers[:, 0], voxel_centers[:, 1], voxel_centers[:, 2] @@ -163,3 +139,27 @@ def calculate_magnetic_gradient_tensor( result["V"] = V return result + + +def _direction_cosines(inclination_deg: float, declination_deg: float) -> np.ndarray: + """Compute unit vector of Earth's field from inclination/declination. + + Convention: + - Inclination I: positive downward from horizontal, in degrees [-90, 90] + - Declination D: clockwise from geographic north toward east, in degrees [-180, 180] + + Returns unit vector l = [lx, ly, lz]. + """ + I = np.deg2rad(inclination_deg) + D = np.deg2rad(declination_deg) + cI = np.cos(I) + sI = np.sin(I) + cD = np.cos(D) + sD = np.sin(D) + # North (x), East (y), Down (z) convention + l = np.array([cI * cD, cI * sD, sI], dtype=float) + # Already unit length by construction, but normalize defensively + n = np.linalg.norm(l) + if n == 0: + return np.array([1.0, 0.0, 0.0], dtype=float) + return l / n From 246479e71daa924a19a79142ec7af2645374382b Mon Sep 17 00:00:00 2001 From: Miguel de la Varga Date: Tue, 18 Nov 2025 14:12:22 +0100 Subject: [PATCH 4/4] [ENH] Add deprecation warning for legacy `GeophysicsInput` constructor and update initialization logic - Introduced `warnings.warn` to notify usage of deprecated `GeophysicsInput` constructor involving `tz` and `densities`. - Updated `GeophysicsInput` initialization to utilize `GravityInput` and `MagneticsInput` for better modularity. --- gempy_engine/core/data/geophysics_input.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/gempy_engine/core/data/geophysics_input.py b/gempy_engine/core/data/geophysics_input.py index 0a2dff0..642dcd7 100644 --- a/gempy_engine/core/data/geophysics_input.py +++ b/gempy_engine/core/data/geophysics_input.py @@ -1,4 +1,5 @@ -from dataclasses import dataclass +import warnings +from dataclasses import dataclass from typing import Annotated, Optional import numpy as np @@ -23,6 +24,18 @@ class MagneticsInput: class GeophysicsInput: gravity_input: Optional[GravityInput] = None magnetics_input: Optional[MagneticsInput] = None + + def __init__(self, gravity_input: Optional[GravityInput] = None, + magnetics_input: Optional[MagneticsInput] = None, + tz: Optional[Annotated[np.ndarray, numpy_array_short_validator]] = None, + densities: Optional[Annotated[np.ndarray, numpy_array_short_validator]] = None): + if gravity_input is not None: + self.gravity_input = gravity_input + else: + warnings.warn("Using deprecated GeophysicsInput constructor. Use GravityInput instead.", DeprecationWarning) + self.gravity_input = GravityInput(tz=tz, densities=densities) + if magnetics_input is not None: + self.magnetics_input = magnetics_input @property def tz(self) -> Optional[Annotated[np.ndarray, numpy_array_short_validator]]: