From b13c2bad754d536e387633a91cb59fc62e69b12c Mon Sep 17 00:00:00 2001 From: Erich Suter Date: Wed, 27 Nov 2024 17:25:55 +0100 Subject: [PATCH] ENH: Faultroom export - add mapping of juxt.pos. to SMDA names (#724) --- src/fmu/dataio/_model/global_configuration.py | 31 ++++++++++ .../dataio/providers/objectdata/_faultroom.py | 23 ++++++- tests/conftest.py | 31 ++++++++++ tests/test_units/test_checksum_md5.py | 4 +- tests/test_units/test_global_configuration.py | 62 +++++++++++++++++++ .../test_objectdataprovider_class.py | 29 ++++++++- 6 files changed, 173 insertions(+), 7 deletions(-) diff --git a/src/fmu/dataio/_model/global_configuration.py b/src/fmu/dataio/_model/global_configuration.py index e4df23c73..534260d35 100644 --- a/src/fmu/dataio/_model/global_configuration.py +++ b/src/fmu/dataio/_model/global_configuration.py @@ -138,6 +138,37 @@ def __getitem__(self, item: str) -> StratigraphyElement: return self.root[item] + def _get_index(self, name: str) -> int: + """ + Get the index of a stratigraphic element by name. + """ + return list(self.root.keys()).index(name) + + + def get_official_name(self, name: str) -> str: + """ + Get the official SMDA name of a stratigraphic element. + name: name of stratigraphic element + """ + if name in self.root: + return self.root[name].name + return "" + + + def get_ordered_official_names(self, names: list[str]) -> list[str]: + """ + Retrieve the official SMDA names and order them according to + the stratigraphic column. + names: names of stratigraphic elements + """ + try: + sorted_indices = sorted([self._get_index(name) for name in names]) + return [self.get_official_name(list(self.root.keys())[idx]) + for idx in sorted_indices] + except ValueError: + return [] + + class GlobalConfiguration(BaseModel): """ Validates and manages the global configuration for the application. diff --git a/src/fmu/dataio/providers/objectdata/_faultroom.py b/src/fmu/dataio/providers/objectdata/_faultroom.py index 5d9381383..6b3e6cc61 100644 --- a/src/fmu/dataio/providers/objectdata/_faultroom.py +++ b/src/fmu/dataio/providers/objectdata/_faultroom.py @@ -7,8 +7,13 @@ from fmu.dataio._logging import null_logger from fmu.dataio._model.data import BoundingBox3D from fmu.dataio._model.enums import FMUClass, Layout +from fmu.dataio._model.global_configuration import ( + GlobalConfiguration, +) # TODO: OK to import? from fmu.dataio._model.specification import FaultRoomSurfaceSpecification -from fmu.dataio.readers import FaultRoomSurface +from fmu.dataio.readers import ( + FaultRoomSurface, +) # TODO: OK? Same as below, but without TYPE_CHECKING from ._base import ( ObjectDataProvider, @@ -66,11 +71,23 @@ def get_bbox(self) -> BoundingBox3D: def get_spec(self) -> FaultRoomSurfaceSpecification: """Derive data.spec for FaultRoomSurface""" logger.info("Get spec for FaultRoomSurface") + + juxtaposition_ordered_hw = [] + juxtaposition_ordered_fw = [] + if ( + isinstance(self.dataio.config, GlobalConfiguration) + and (strat := self.dataio.config.stratigraphy) + ): + juxtaposition_ordered_hw = strat.get_ordered_official_names( + self.obj.juxtaposition_hw) + juxtaposition_ordered_fw = strat.get_ordered_official_names( + self.obj.juxtaposition_fw) + return FaultRoomSurfaceSpecification( horizons=self.obj.horizons, faults=self.obj.faults, - juxtaposition_hw=self.obj.juxtaposition_hw, - juxtaposition_fw=self.obj.juxtaposition_fw, + juxtaposition_hw=juxtaposition_ordered_hw, + juxtaposition_fw=juxtaposition_ordered_fw, properties=self.obj.properties, name=self.obj.name, ) diff --git a/tests/conftest.py b/tests/conftest.py index 978942523..6b83890b2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,6 +5,7 @@ import logging import os import shutil +from copy import deepcopy from pathlib import Path import numpy as np @@ -18,6 +19,7 @@ from fmu.dataio._model import Root, fields, global_configuration from fmu.dataio.dataio import ExportData, read_metadata from fmu.dataio.providers._fmu import FmuEnv +from fmu.dataio.readers import FaultRoomSurface from .utils import _get_nested_pydantic_models, _metadata_examples @@ -460,6 +462,35 @@ def fixture_regsurf(): return xtgeo.RegularSurface(ncol=12, nrow=10, xinc=20, yinc=20, values=1234.0) +@pytest.fixture(name="faultroom_object", scope="module") +def fixture_faultroom_object(globalconfig2): + """Create a faultroom object.""" + logger.debug("Ran %s", _current_function_name()) + cfg = deepcopy(globalconfig2) + + horizons = cfg["rms"]["horizons"]["TOP_RES"] + faults = ["F1", "F2", "F3", "F4", "F5", "F6"] + juxtaposition_hw = cfg["rms"]["zones"]["ZONE_RES"] + juxtaposition_fw = cfg["rms"]["zones"]["ZONE_RES"] + juxtaposition = {"fw": juxtaposition_fw, "hw": juxtaposition_hw} + properties = [ + "Juxtaposition", + ] + coordinates = [[[1.1, 1.2, 1.3], [2.1, 2.2, 2.3]]] + features = [{"geometry": {"coordinates": coordinates}}] + name = cfg["access"]["asset"]["name"] + + faultroom_data = { + "horizons": horizons, + "faults": {"default": faults}, + "juxtaposition": juxtaposition, + "properties": properties, + "name": name, + } + + return FaultRoomSurface({"metadata": faultroom_data, "features": features}) + + @pytest.fixture(name="polygons", scope="module") def fixture_polygons(): """Create an xtgeo polygons.""" diff --git a/tests/test_units/test_checksum_md5.py b/tests/test_units/test_checksum_md5.py index f06fdf27c..ee0831705 100644 --- a/tests/test_units/test_checksum_md5.py +++ b/tests/test_units/test_checksum_md5.py @@ -180,7 +180,7 @@ def test_checksum_md5_for_dictionary(monkeypatch, tmp_path, globalconfig1): assert meta["file"]["checksum_md5"] == md5sum(export_path) -def test_checksum_md5_for_faultroom(monkeypatch, tmp_path, globalconfig1, rootpath): +def test_checksum_md5_for_faultroom(monkeypatch, tmp_path, globalconfig2, rootpath): """ Test that the MD5 hash in the metadata is equal to one computed for the exported file for a FaultRoomSurface @@ -194,7 +194,7 @@ def test_checksum_md5_for_faultroom(monkeypatch, tmp_path, globalconfig1, rootpa export_path = Path( ExportData( - config=globalconfig1, + config=globalconfig2, content="depth", name="myname", ).export(fault_room_surface) diff --git a/tests/test_units/test_global_configuration.py b/tests/test_units/test_global_configuration.py index f1e2daaf9..13063e6ad 100644 --- a/tests/test_units/test_global_configuration.py +++ b/tests/test_units/test_global_configuration.py @@ -88,3 +88,65 @@ def test_access_classification_logic(): "ssdl": {"rep_include": False}, } ) + + +def test_stratigraphy(edataobj2): + """Test the stratigraphy.""" + + strat = edataobj2.config.stratigraphy + + ###### Test get index ###### + + # Correct name + assert strat._get_index("Valysar") == 9 + + # Correct name + assert strat._get_index("Therys") == 10 + + # Incorrect name + with pytest.raises(ValueError): + strat._get_index("Garn") + + # Empty name + with pytest.raises(ValueError): + strat._get_index("") + + + ###### Test get official name ###### + + # Correct name + assert "Valysar Fm.".__eq__(strat.get_official_name("Valysar")) + + # Incorrect name + assert "".__eq__(strat.get_official_name("Ile")) + + # Empty name + assert "".__eq__(strat.get_official_name("")) + + + ###### Test stratigraphic ordering ###### + + # Correct order + assert strat.get_ordered_official_names(["Valysar", "Therys", "Volon"]) == [ + "Valysar Fm.", + "Therys Fm.", + "Volon Fm.", + ] + + # Incorrect order + assert strat.get_ordered_official_names(["Therys", "Volon", "Valysar"]) == [ + "Valysar Fm.", + "Therys Fm.", + "Volon Fm.", + ] + + # Single element + assert strat.get_ordered_official_names(["Volon"]) == [ + "Volon Fm.", + ] + + # Stratigraphic element not found + assert strat.get_ordered_official_names(["Valysar", "Ile"]) == [] + + # Empty list + assert strat.get_ordered_official_names([]) == [] diff --git a/tests/test_units/test_objectdataprovider_class.py b/tests/test_units/test_objectdataprovider_class.py index cbfd5a1a9..1373683c1 100644 --- a/tests/test_units/test_objectdataprovider_class.py +++ b/tests/test_units/test_objectdataprovider_class.py @@ -8,6 +8,8 @@ import fmu.dataio as dataio from fmu.dataio._definitions import ConfigurationError, ValidFormats +from fmu.dataio._model.specification import FaultRoomSurfaceSpecification +from fmu.dataio.providers.objectdata._faultroom import FaultRoomSurfaceProvider from fmu.dataio.providers.objectdata._provider import ( objectdata_provider_factory, ) @@ -23,7 +25,7 @@ def test_objectdata_regularsurface_derive_named_stratigraphy(regsurf, edataobj1): """Get name and some stratigaphic keys for a valid RegularSurface object .""" - # mimic the stripped parts of configuations for testing here + # mimic the stripped parts of configurations for testing here objdata = objectdata_provider_factory(regsurf, edataobj1) res = objdata._get_stratigraphy_element() @@ -35,7 +37,7 @@ def test_objectdata_regularsurface_derive_named_stratigraphy(regsurf, edataobj1) def test_objectdata_regularsurface_get_stratigraphy_element_differ(regsurf, edataobj2): """Get name and some stratigaphic keys for a valid RegularSurface object .""" - # mimic the stripped parts of configuations for testing here + # mimic the stripped parts of configurations for testing here objdata = objectdata_provider_factory(regsurf, edataobj2) res = objdata._get_stratigraphy_element() @@ -45,6 +47,29 @@ def test_objectdata_regularsurface_get_stratigraphy_element_differ(regsurf, edat assert res.stratigraphic is True +def test_objectdata_faultroom_fault_juxtaposition_get_stratigraphy_differ( + faultroom_object, edataobj2 +): + """ + Fault juxtaposition is a list of formations on the footwall and hangingwall sides. + Ensure that each name is converted to the official SMDA names. + """ + objdata = objectdata_provider_factory(faultroom_object, edataobj2) + assert isinstance(objdata, FaultRoomSurfaceProvider) + + frss = objdata.get_spec() + assert isinstance(frss, FaultRoomSurfaceSpecification) + + assert len(frss.juxtaposition_fw) == 3 + assert frss.juxtaposition_fw[0] == "Valysar Fm." + assert frss.juxtaposition_fw[1] == "Therys Fm." + assert frss.juxtaposition_fw[2] == "Volon Fm." + assert len(frss.juxtaposition_hw) == 3 + assert frss.juxtaposition_hw[0] == "Valysar Fm." + assert frss.juxtaposition_hw[1] == "Therys Fm." + assert frss.juxtaposition_hw[2] == "Volon Fm." + + def test_objectdata_regularsurface_validate_extension(regsurf, edataobj1): """Test a valid extension for RegularSurface object."""