Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
[ENH][TEST] Improve serialization validation and update tests
Add detailed validation for serialized GeoModel instances, including debugging for string differences and hash checks. Introduce `verify_model_serialization` with pre/post-deserialization checks, and update gravity tests for structured verification and data type consistency.
  • Loading branch information
Leguark committed May 25, 2025
commit a4126b2ecda2ea0b15a29a687558076622e8f306
70 changes: 66 additions & 4 deletions gempy/modules/serialization/save_load.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Literal

import warnings

from ...core.data import GeoModel
Expand All @@ -23,10 +25,10 @@ def save_model(model: GeoModel, path: str | None = None, validate_serialization:
ValueError
If the file has an extension other than .gempy
"""

# Warning about preview
warnings.warn("This function is still in development. It may not work as expected.")

# Define the valid extension for gempy models
VALID_EXTENSION = ".gempy"
if path is None:
Expand Down Expand Up @@ -89,7 +91,7 @@ def load_model(path: str) -> GeoModel:

# Warning about preview
warnings.warn("This function is still in development. It may not work as expected.")

VALID_EXTENSION = ".gempy"

# Check if path has the valid extension
Expand Down Expand Up @@ -131,10 +133,70 @@ def _to_binary(header_json, body_) -> bytes:


def _validate_serialization(original_model, model_deserialized):
if False:
_verify_models(model_deserialized, original_model)

a = hash(original_model.structural_frame.surface_points_copy.data.tobytes())
b = hash(model_deserialized.structural_frame.surface_points_copy.data.tobytes())
o_a = hash(original_model.structural_frame.orientations_copy.data.tobytes())
o_b = hash(model_deserialized.structural_frame.orientations_copy.data.tobytes())
assert a == b, "Hashes for surface points are not equal"
assert o_a == o_b, "Hashes for orientations are not equal"
assert model_deserialized.__str__() == original_model.__str__()
original_model___str__ = original_model.__str__()
deserialized___str__ = model_deserialized.__str__()
if original_model___str__ != deserialized___str__:
# Find first char that is not the same
for i in range(min(len(original_model___str__), len(deserialized___str__))):
if original_model___str__[i] != deserialized___str__[i]:
break
print(f"First difference at index {i}:")
i1 = 50
print(f"Original: {original_model___str__[i - i1:i + i1]}")
print(f"Deserialized: {deserialized___str__[i - i1:i + i1]}")

assert deserialized___str__ == original_model___str__


def verify_model_serialization(model: GeoModel, verify_moment: Literal["before", "after"], file_name: str):
"""
Verifies the serialization and deserialization process of a GeoModel instance
by ensuring the serialized JSON and binary data match during either the
initial or post-process phase, based on the specified verification moment.

Args:
model: The GeoModel instance to be verified.
verify_moment: A literal value specifying whether to verify the model
before or after the deserialization process. Accepts "before"
or "after" as valid inputs.
file_name: The filename to associate with the verification process for
logging or output purposes.

Raises:
ValueError: If `verify_moment` is not set to "before" or "after".
"""
model_json = model.model_dump_json(by_alias=True, indent=4)

# Compress the binary data
zlib = require_zlib()
compressed_binary = zlib.compress(model.structural_frame.input_tables_binary)

binary_file = _to_binary(model_json, compressed_binary)

model_deserialized = _deserialize_binary_file(binary_file)

original_model = model
original_model.meta.creation_date = "<DATE_IGNORED>"
model_deserialized.meta.creation_date = "<DATE_IGNORED>"
from verify_helper import verify_json
if verify_moment == "before":
verify_json(
item=original_model.model_dump_json(by_alias=True, indent=4),
name=file_name
)
elif verify_moment == "after":
verify_json(
item=model_deserialized.model_dump_json(by_alias=True, indent=4),
name=file_name
)
else:
raise ValueError("Invalid model parameter")
21 changes: 10 additions & 11 deletions test/test_modules/_geophysics_TO_UPDATE/test_gravity.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
# Importing auxiliary libraries
import numpy as np

from gempy.modules.serialization.save_load import verify_model_serialization


def test_gravity():
color_generator = gp.data.ColorsGenerator()
Expand Down Expand Up @@ -64,9 +66,9 @@ def test_gravity():

gp.set_centered_grid(
grid=geo_model.grid,
centers=np.array([[6, 0, 4]]),
resolution=np.array([10, 10, 100]),
radius=np.array([16000, 16000, 16000]) # ? This radius makes 0 sense but it is the original one in gempy v2
centers=np.array([[6, 0, 4]], dtype="float"),
resolution=np.array([10, 10, 100], dtype="float"),
radius=np.array([16000, 16000, 16000], dtype="float") # ? This radius makes 0 sense but it is the original one in gempy v2
)

gravity_gradient = gp.calculate_gravity_gradient(geo_model.grid.centered_grid)
Expand All @@ -75,15 +77,12 @@ def test_gravity():
densities=np.array([2.6, 2.4, 3.2]),
)

model_json = geo_model.model_dump_json(by_alias=True, indent=4)

return
from pydantic_core import from_json

json = from_json(model_json, allow_partial=True)
model_deserialized = gp.data.GeoModel.model_validate(json)
verify_model_serialization(
model=geo_model,
verify_moment="after",
file_name=f"verify/{geo_model.meta.name}"
)

return
gp.compute_model(geo_model)

print(geo_model.solutions.gravity)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
{
"meta": {
"name": "2-layers",
"creation_date": "<DATE_IGNORED>",
"last_modification_date": null,
"owner": null
},
"structural_frame": {
"structural_groups": [
{
"name": "default",
"elements": [
{
"name": "surface1",
"is_active": true,
"_color": "#015482",
"surface_points": {
"name_id_map": {
"surface1": 57292991
},
"_model_transform": null
},
"orientations": {
"name_id_map": {
"surface1": 57292991
},
"_model_transform": null
},
"scalar_field_at_interface": null,
"_id": -1
},
{
"name": "surface2",
"is_active": true,
"_color": "#9f0052",
"surface_points": {
"name_id_map": {
"surface2": 21816406
},
"_model_transform": null
},
"orientations": {
"name_id_map": null,
"_model_transform": null
},
"scalar_field_at_interface": null,
"_id": -1
}
],
"structural_relation": 1,
"fault_relations": null,
"faults_input_data": null,
"solution": null
}
],
"is_dirty": true,
"basement_color": "#ffbe00",
"binary_meta_data": {
"sp_binary_length": 144
}
},
"grid": {
"_octree_grid": null,
"_dense_grid": {
"resolution": [
500,
1,
500
],
"extent": [
0.0,
12.0,
-2.0,
2.0,
0.0,
4.0
],
"_transform": null
},
"_custom_grid": null,
"_topography": null,
"_sections": null,
"_centered_grid": {
"centers": [
[
6.0,
0.0,
4.0
]
],
"resolution": [
10.0,
10.0,
100.0
],
"radius": [
16000.0,
16000.0,
16000.0
]
},
"_transform": null,
"_octree_levels": -1,
"active_grids": 1058
},
"input_transform": {
"position": [
-6.0,
-0.0,
-2.51
],
"rotation": [
0.0,
0.0,
0.0
],
"scale": [
0.08333333333333333,
0.16778523489932887,
0.08333333333333333
],
"_is_default_transform": false,
"_cached_pivot": null
},
"_interpolation_options": {
"kernel_options": {
"range": 1.7,
"c_o": 10.0,
"uni_degree": 1,
"i_res": 4.0,
"gi_res": 2.0,
"number_dimensions": 3,
"kernel_function": "cubic",
"kernel_solver": 1,
"compute_condition_number": false,
"optimizing_condition_number": false,
"condition_number": null
},
"evaluation_options": {
"_number_octree_levels": 1,
"_number_octree_levels_surface": 4,
"octree_curvature_threshold": -1.0,
"octree_error_threshold": 1.0,
"octree_min_level": 2,
"mesh_extraction": true,
"mesh_extraction_masking_options": 3,
"mesh_extraction_fancy": true,
"evaluation_chunk_size": 500000,
"compute_scalar_gradient": false,
"verbose": false
},
"debug": true,
"cache_mode": 3,
"cache_model_name": "2-layers",
"block_solutions_type": 2,
"sigmoid_slope": 5000000,
"debug_water_tight": false
}
}