Skip to content

Commit

Permalink
fixup
Browse files Browse the repository at this point in the history
  • Loading branch information
jcrivenaes committed Jun 25, 2024
1 parent cf9f717 commit bdaebbe
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 84 deletions.
22 changes: 13 additions & 9 deletions docs/rms_oneliners.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,36 @@ a few lines will be needed.
Exporting volumetrics from RMS
------------------------------

Volumetrics in RMS is always done by so-called volume jobs. The intention with the simplification
is to use the RMS API behind the scene to retrieve all necessary data need for data io.
Volumetrics in RMS is always done in a so-called volume jobs. The intention with the simplification
is to use the RMS API behind the scene to retrieve all necessary data need for ``fmu.dataio``.

Example:

.. code-block:: python
from fmu.dataio.tools.rms import export_volumetrics
from fmu.dataio.export.rms import RmsVolumetricsExport
...
# here 'Geogrid' is the grid model, and 'geogrid_volumes' is tha name of the volume job
outfiles = export_volumetrics(project, "Geogrid", "geogrid_volumes", global_config=CFG)
# here 'Geogrid' is the grid model, and 'geogrid_volumes' is the name of the volume job
outfiles = RmsVolumetricsExport(project, "Geogrid", "geogrid_volumes").export()
print(f"Output volumes to {outfiles}")
Most ``dataio`` settings are here defaulted, but some keys can be altered optionally, e.g.:

.. code-block:: python
outfiles = export_volumetrics(
project, "Geogrid", "geogrid_volumes", global_config=CFG, tagname="vol"
)
outfiles = RmsVolumetricsExport(
project,
"Geogrid",
"geogrid_volumes",
config_file="../whatever/global_variables.yml",
tagname="vol",
).export()
Details
-------

.. automodule:: fmu.dataio.tools.rms.volumetrics
.. automodule:: fmu.dataio.export.rms.volumetrics
:members:
144 changes: 86 additions & 58 deletions src/fmu/dataio/export/rms/volumetrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from dataclasses import dataclass, field
from pathlib import Path
from typing import Final
from warnings import warn

import pandas as pd
from packaging.version import parse as versionparse
Expand All @@ -12,18 +13,20 @@
from fmu.dataio._logging import null_logger

try:
import rmsapi # type: ignore # noqa

from _rmsapi import Project as RmsProject # type: ignore # noqa
import roxar as rmsapi # type: ignore # noqa
import roxar.jobs as jobs # type: ignore # noqa
from _roxar import Project as RmsProject # type: ignore # noqa
except ImportError:
pass

try:
import roxar as rmsapi # type: ignore # noqa
from _roxar import Project as RmsProject # type: ignore # noqa
import rmsapi # type: ignore # noqa
import rmsapi.jobs as jobs # type: ignore # noqa
from _rmsapi import Project as RmsProject # type: ignore # noqa
except ImportError:
pass


_logger: Final = null_logger(__name__)

# rename columns to FMU standard
Expand Down Expand Up @@ -53,36 +56,40 @@

@dataclass
class RmsVolumetricsExport:
"""A simplified interface when exporting volume tables (etc) from RMS.
"""Simplified interface when exporting volume tables (and assosiated data) from RMS.
As the export_volumetrics may have multiple output (storing both tables, maps and
3D grids), the output from this function is always a dictionary. The table is
mandatory output, while maps and 3D grid are optional (not yet implemented).
mandatory output, while maps and 3D grid data are optional (not yet implemented).
Args:
project: The 'magic' project variable in RMS.
gridname: Name of 3D grid model in RMS.
volumejob_name: Name of the volume job.
global_config: The global configuration (may be redundant in a near future?)
forcefolder: As default, volume tables will be exported to the file structure,
and the folder name will be 'tables'. This can be overriden here. For
optional assosiated volume maps and grids, the default folder names
cannot be changed.
tagname: Defaulted to 'vol' for this function
classification: Use 'internal' or 'restricted' (default).
workflow: Information about he work flow; defaulted to 'rms volumetrics'.
include_zone_maps: If True, then all the zone maps that are toggled on in the
volume job are exported also (currently NOT implemented).
include_total_maps: If True, then all the total maps that are toggled on in the
volume job are exported also (currently NOT implemented).
include_3dgrid_propererties: If True, then all the 3D properties (also called
parameters) that are toggled on in the volume job are exported also,
together with the 3D grid geometry (currently NOT implemented).
grid_name: Name of 3D grid model in RMS.
volume_job_name: Name of the volume job.
global_config: Optional. The file path to global configuration. The default
here is "../../fmuconfig/output/global_variables.yml. Hence it can be
omitted for those who follows the standard.
forcefolder: Optional. As default, volume tables will be exported to the agreed
file structure, and the folder name will be 'tables'. This can be
overriden here, but there will be warnings. For optional assosiated
volume maps and grids, the default folder names cannot be changed.
tagname: Optional. Defaulted to 'vol' for this function. Tagnames are part of
file names, and should not be applied as metadata.
classification: Optional. Use 'internal' or 'restricted' (default).
workflow: Optional. Information about the work flow; defaulted to
'rms volumetrics'.
include_zone_maps: Optional. If True, then all the zone maps that are toggled on
in the volume job are exported also (currently NOT implemented).
include_total_maps: Optional. If True, then all the total maps that are toggled
on in the volume job are exported also (currently NOT implemented).
include_3dgrid_propererties: Optional. If True, then all the 3D properties
(also called parameters) that are toggled on in the volume job are exported
also, together with the 3D grid geometry (currently NOT implemented).
"""

project: RmsProject
gridname: str
volumejob_name: str
grid_name: str
volume_job_name: str

# optional and defaulted
config_file: str | Path = "../../fmuconfig/output/global_variables.yml"
Expand All @@ -96,9 +103,9 @@ class RmsVolumetricsExport:

# internal storage instance variables
_global_config: dict = field(default_factory=dict, init=False)
_voljob: dict = field(default_factory=dict, init=False)
_voltable_name: str = field(default="", init=False)
_dfr: pd.DataFrame = field(default_factory=pd.DataFrame, init=False)
_volume_job: dict = field(default_factory=dict, init=False)
_volume_table_name: str = field(default="", init=False)
_dataframe: pd.DataFrame = field(default_factory=pd.DataFrame, init=False)
_units: str = field(default="metric", init=False)

@property
Expand All @@ -118,48 +125,55 @@ def _check_rmsapi_version() -> None:
def _set_global_config(self) -> None:
"""Set the global config data by reading the file."""
# TODO: This functionality should ideally be in fmu-config.
global_config_path = Path(self.config)
_logger.debug("Set global config...")
global_config_path = Path(self.config_file)

if global_config_path.is_file():
self._global_config = yaml_load(global_config_path)
else:
raise ValueError(f"Cannot find file for global config: {self.config}")

def _parse_voljob_settings(self) -> None:
"""Get information out from the job API."""
_logger.debug("Check VOLJOB settings...")
self._voljob = rmsapi.jobs.Job.get_job(
owner=["Grid models", self.gridname, "Grid"],
raise ValueError(f"Cannot find file for global config: {self.config_file}")
_logger.debug("Set global config... DONE!")

def _rms_volume_job_settings(self) -> None:
"""Get information out from the RMS job API."""
_logger.debug("Check RMS VOLJOB settings...")
self._volume_job = jobs.Job.get_job(
owner=["Grid models", self.grid_name, "Grid"],
type="Volumetrics",
name=self.volumejob_name,
name=self.volume_job_name,
).get_arguments()
_logger.debug("Check VOLJOB settings... DONE")
_logger.debug("RMS VOLJOB settings... DONE")

def _read_voltable_from_rms(self) -> None:
"""Read the volume table from RMS."""
voltable = self._voljob.get("Report")
def _read_volume_table_name_from_rms(self) -> None:
"""Read the volume table name from RMS."""
_logger.debug("Read volume table name from RMS...")
voltable = self._volume_job.get("Report")
if isinstance(voltable, list):
voltable = voltable[0]
self._voltable_name = voltable.get("ReportTableName")
self._volume_table_name = voltable.get("ReportTableName")

if not self._voltable_name:
if not self._volume_table_name:
raise RuntimeError(
"You need to configure output to Report file: Report table "
"in the volumetric job. Provide a table name and rerun."
)

_logger.debug("The volume table is %s", self._voltable_name)
_logger.debug("The volume table name is %s", self._volume_table_name)
_logger.debug("Read volume table name from RMS... DONE")

def _voltable_as_dataframe(self) -> None:
"""Convert table to pandas dataframe"""
_logger.debug("Read values and convert to pandas dataframe...")
dict_values = (
self.project.volumetric_tables[self._voltable_name]
self.project.volumetric_tables[self._volume_table_name]
.get_data_table()
.to_dict()
)
_logger.debug("Dict values are: %s", dict_values)
self._dataframe = dfr = pd.DataFrame.from_dict(dict_values)
dfr.rename(columns=_RENAME_COLUMNS_FROM_RMS, inplace=True)
dfr.drop("REAL", axis=1, inplace=True)
dfr.drop("REAL", axis=1, inplace=True, errors="ignore")
_logger.debug("Read values and convert to pandas dataframe... DONE")

def _set_units(self) -> None:
"""See if the RMS project is defined in metric or feet."""
Expand All @@ -183,33 +197,47 @@ def _read_includes_etc(self) -> None:
"Including 3D parameters is currently not implemented"
)

def _warn_if_forcefolder(self):
if self.forcefolder:
warn(
"A 'forcefolder' is set. This is strongly discouraged and will be "
"removed in coming versions",
FutureWarning,
)

def _process_data(self):
"""Process all routines ^ except exports."""
_logger.debug("Process data...")
self._check_rmsapi_version()
self._set_global_config()
self._rms_volume_job_settings()
self._read_volume_table_name_from_rms()
self._voltable_as_dataframe()
self._set_units()
self._read_includes_etc()
self._warn_if_forcefolder()
_logger.debug("Process data... DONE")

def _export_volume_table(self) -> dict[str, str]:
"""Do the actual volume table export using dataio setup."""

edata = dio.ExportData(
config=self.config,
config=self._global_config,
content="volumes",
unit="m3" if self._units == "metric" else "ft3",
vertical_domain={"depth": "msl"},
workflow=self.workflow,
forcefolder=self.forcefolder,
classification=self.classification,
tagname=self.tagname,
name=self.gridname,
name=self.grid_name,
)

out = edata.export(self.dataframe)
_logger.debug("Volume result to: %s", out)
return {"volume_table": out}

def export(self):
def export(self) -> dict[str, str]: # public function
"""Export the volume table."""
self._check_rmsapi_version()
self._set_global_config()
self._parse_voljob_settings()
self._read_voltable_from_rms()
self._voltable_as_dataframe()
self._set_units()
self._read_includes_etc()

self._process_data()
return self._export_volume_table()
16 changes: 16 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,22 @@ def rmssetup(tmp_path_factory, global_config2_path):
return rmspath


@pytest.fixture(scope="module")
def rmssetup_with_fmuconfig(tmp_path_factory, global_config2_path):
"""Create the folder structure to mimic RMS project and standard global config."""

tmppath = tmp_path_factory.mktemp("revision")
rmspath = tmppath / "rms/model"
rmspath.mkdir(parents=True, exist_ok=True)
fmuconfigpath = tmppath / "fmuconfig/output"
fmuconfigpath.mkdir(parents=True, exist_ok=True)
shutil.copy(global_config2_path, fmuconfigpath)

logger.debug("Ran %s", _current_function_name())

return rmspath


@pytest.fixture(name="rmsglobalconfig", scope="module")
def fixture_rmsglobalconfig(rmssetup):
"""Read global config."""
Expand Down
4 changes: 3 additions & 1 deletion tests/test_export_rms/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,11 @@ def mock_rmsapi_package(monkeypatch):
mock_x_rmsapi = MagicMock()
monkeypatch.setitem(sys.modules, "_rmsapi", mock_x_rmsapi)
mock_rmsapi.__version__ = "1.7"
mock_jobs_rmsapi = MagicMock()
monkeypatch.setitem(sys.modules, "rmsapi.jobs", mock_jobs_rmsapi)

mock_rmsapi.jobs.Job.get_job(...).get_arguments.return_value = VOLJOB_PARAMS
yield mock_rmsapi, mock_x_rmsapi
yield mock_rmsapi, mock_x_rmsapi, mock_jobs_rmsapi


@pytest.fixture(autouse=True)
Expand Down
33 changes: 17 additions & 16 deletions tests/test_export_rms/test_export_rms_volumetrics.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"""Test the dataio running RMS spesici utility function for volumetrics"""

import os
from pathlib import Path

import fmu.dataio as dio
import fmu.dataio as dataio
import pandas as pd
import pytest
from fmu.dataio._logging import null_logger
Expand All @@ -22,33 +23,33 @@ def voltable_as_dataframe():

@inside_rms
def test_rms_volumetrics(
mock_project_variable, rmsglobalconfig, voltable_as_dataframe, monkeypatch
mock_project_variable, voltable_as_dataframe, rmssetup_with_fmuconfig, monkeypatch
):
"""See mocks in local conftest.py"""
import rmsapi
from fmu.dataio.export.rms import RmsExportVolumetrics

import rmsapi # type: ignore # noqa
import rmsapi.jobs as jobs # type: ignore # noqa

from fmu.dataio.export.rms import RmsVolumetricsExport

os.chdir(rmssetup_with_fmuconfig)

assert rmsapi.__version__ == "1.7"
assert "Report" in rmsapi.jobs.Job.get_job("whatever").get_arguments.return_value
assert "Report" in jobs.Job.get_job("whatever").get_arguments.return_value

task = volumetrics.RmsVolumetricsExport(
task = RmsVolumetricsExport(
mock_project_variable,
"Geogrid",
"geogrid_vol",
config=rmsglobalconfig,
)
task._check_rmsapi_version()
task._parse_voljob_settings()
task._get_voltable()
assert task._voltable_name == "geogrid_volumes"

task._process_data()
assert task._volume_table_name == "geogrid_volumes"

# patch the dataframe which originally shall be retrieved from RMS
monkeypatch.setattr(task, "_dataframe", voltable_as_dataframe)

out = task.export()

print("XXX OUT", out)

metadata = dio.read_metadata(out["volume_table"])
out = task._export_volume_table()
metadata = dataio.read_metadata(out["volume_table"])

assert "volumes" in metadata["data"]["content"]

0 comments on commit bdaebbe

Please sign in to comment.