-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ENH: add one-liner function for volumetrics export in RMS
- Loading branch information
1 parent
eeec6e1
commit a2ecdda
Showing
11 changed files
with
683 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
RMS targeted functions | ||
====================== | ||
|
||
For lowerering the user threshold, some "one-liner" functions have been made for RMS. The purpose | ||
is both to make it simpler for users to export certain items, and in addition secure a better | ||
consistency. Hence the end user is not burdened to provide details, and only a script with quite | ||
a few lines will be needed. | ||
|
||
Currently only volumes are exposed, but this will be extended in the near future. | ||
|
||
Exporting volumetrics from RMS | ||
------------------------------ | ||
|
||
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 needed for ``fmu.dataio``. | ||
|
||
Example: | ||
|
||
.. code-block:: python | ||
from fmu.dataio.export.rms import export_rms_volumetrics | ||
... | ||
# here 'Geogrid' is the grid model name, and 'geogrid_volumes' is the name of the volume job | ||
outfiles = export_rms_volumetrics(project, "Geogrid", "geogrid_volumes") | ||
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_rms_volumetrics( | ||
project, | ||
"Geogrid", | ||
"geogrid_volumes", | ||
global_variables="../whatever/global_variables.yml", | ||
tagname="vol", | ||
subfolder="volumes", | ||
) | ||
Details | ||
------- | ||
|
||
.. automodule:: fmu.dataio.export.rms.volumetrics | ||
:members: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,24 @@ | ||
"""Top-level package for fmu-dataio""" | ||
# noqa | ||
|
||
from fmu.dataio.dataio import AggregatedData # noqa # type: ignore | ||
from fmu.dataio.dataio import ExportData # noqa # type: ignore | ||
from fmu.dataio.dataio import InitializeCase # noqa # type: ignore | ||
from fmu.dataio.dataio import read_metadata # noqa | ||
from fmu.dataio.preprocessed import ExportPreprocessedData # noqa # type: ignore | ||
from fmu.dataio.dataio import ( | ||
AggregatedData, | ||
ExportData, | ||
InitializeCase, | ||
read_metadata, | ||
) | ||
from fmu.dataio.preprocessed import ExportPreprocessedData | ||
|
||
try: | ||
from .version import version | ||
|
||
__version__ = version | ||
except ImportError: | ||
__version__ = "0.0.0" | ||
|
||
__all__ = [ | ||
"AggregatedData", | ||
"ExportData", | ||
"InitializeCase", | ||
"read_metadata", | ||
"ExportPreprocessedData", | ||
] |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from .volumetrics import export_rms_volumetrics | ||
|
||
__all__ = ["export_rms_volumetrics"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
"""Handle rmsapi or roxar (deprecated version of rmsapi); only present inside RMS""" | ||
|
||
from __future__ import annotations | ||
|
||
import warnings | ||
from typing import TYPE_CHECKING, Any | ||
|
||
from fmu.dataio._logging import null_logger | ||
|
||
_logger = null_logger(__name__) | ||
|
||
|
||
def import_rms_package() -> dict[str, Any] | None: | ||
""" | ||
Attempts to import the 'rmsapi' package first. If 'rmsapi' is not available, | ||
it attempts to import the 'roxar' package while suppressing deprecation warnings. | ||
Returns a dictionary with the imported modules or raises ImportError if neither | ||
is available. | ||
""" | ||
try: | ||
import rmsapi | ||
import rmsapi.jobs as jobs | ||
|
||
return {"rmsapi": rmsapi, "jobs": jobs} | ||
except ImportError: | ||
try: | ||
with warnings.catch_warnings(): | ||
warnings.filterwarnings( | ||
"ignore", category=DeprecationWarning, module="roxar" | ||
) | ||
import roxar as rmsapi | ||
import roxar.jobs as jobs | ||
|
||
return {"rmsapi": rmsapi, "jobs": jobs} | ||
except ImportError: | ||
raise ImportError( | ||
"Neither 'roxar' nor 'rmsapi' are available. You have to be inside " | ||
"RMS to use this function." | ||
) | ||
|
||
|
||
if TYPE_CHECKING: | ||
import rmsapi | ||
import rmsapi.jobs | ||
|
||
_logger.debug("Importing both %s and %s", rmsapi, rmsapi.jobs) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,243 @@ | ||
from __future__ import annotations | ||
|
||
from dataclasses import dataclass, field | ||
from pathlib import Path | ||
from typing import Any, Final | ||
from warnings import warn | ||
|
||
import pandas as pd | ||
from packaging.version import parse as versionparse | ||
|
||
import fmu.dataio as dio | ||
from fmu.config.utilities import yaml_load | ||
from fmu.dataio._logging import null_logger | ||
|
||
from ._conditional_rms_imports import import_rms_package | ||
|
||
_modules = import_rms_package() | ||
if _modules: | ||
rmsapi = _modules["rmsapi"] | ||
jobs = _modules["jobs"] | ||
|
||
|
||
_logger: Final = null_logger(__name__) | ||
|
||
# rename columns to FMU standard | ||
_RENAME_COLUMNS_FROM_RMS: Final = { | ||
"Proj. real.": "REAL", | ||
"Zone": "ZONE", | ||
"Segment": "REGION", | ||
"Boundary": "LICENSE", | ||
"Facies": "FACIES", | ||
"BulkOil": "BULK_OIL", | ||
"NetOil": "NET_OIL", | ||
"PoreOil": "PORV_OIL", | ||
"HCPVOil": "HCPV_OIL", | ||
"STOIIP": "STOIIP_OIL", | ||
"AssociatedGas": "ASSOCIATEDGAS_OIL", | ||
"BulkGas": "BULK_GAS", | ||
"NetGas": "NET_GAS", | ||
"PoreGas": "PORV_GAS", | ||
"HCPVGas": "HCPV_GAS", | ||
"GIIP": "GIIP_GAS", | ||
"AssociatedLiquid": "ASSOCIATEDOIL_GAS", | ||
"Bulk": "BULK_TOTAL", | ||
"Net": "NET_TOTAL", | ||
"Pore": "PORV_TOTAL", | ||
} | ||
|
||
|
||
@dataclass | ||
class _ExportVolumetricsRMS: | ||
project: Any | ||
grid_name: str | ||
volume_job_name: str | ||
|
||
# optional and defaulted | ||
global_config: str | Path | dict = "../../fmuconfig/output/global_variables.yml" | ||
forcefolder: str = "" # allowed until deprecated | ||
subfolder: str = "" | ||
name: str = "" | ||
tagname: str = "vol" | ||
classification: str = "restricted" | ||
workflow: str = "rms volumetric run" | ||
|
||
# internal storage instance variables | ||
_global_config: dict = field(default_factory=dict, 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) | ||
|
||
def __post_init__(self) -> None: | ||
_logger.debug("Process data, estiblish state prior to export.") | ||
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._warn_if_forcefolder() | ||
_logger.debug("Process data... DONE") | ||
|
||
@staticmethod | ||
def _check_rmsapi_version() -> None: | ||
"""Check if we are working in a RMS API, and also check RMS versions?""" | ||
_logger.debug("Check API version...") | ||
if versionparse(rmsapi.__version__) < versionparse("1.7"): | ||
raise RuntimeError( | ||
"You need at least API version 1.7 (RMS 13.1) to use this function." | ||
) | ||
_logger.debug("Check API version... DONE") | ||
|
||
def _set_global_config(self) -> None: | ||
"""Set the global config data by reading the file.""" | ||
_logger.debug("Set global config...") | ||
|
||
if isinstance(self.global_config, dict): | ||
self._global_config = self.global_config | ||
_logger.debug("Set global config (from input dictionary)... DONE!") | ||
return | ||
|
||
global_config_path = Path(self.global_config) | ||
|
||
if not global_config_path.is_file(): | ||
raise FileNotFoundError( | ||
f"Cannot find file for global config: {self.global_config}" | ||
) | ||
self._global_config = yaml_load(global_config_path) | ||
_logger.debug("Read config from yaml... DONE") | ||
|
||
def _rms_volume_job_settings(self) -> None: | ||
"""Get information out from the RMS job API.""" | ||
_logger.debug("RMS VOLJOB settings...") | ||
self._volume_job = jobs.Job.get_job( | ||
owner=["Grid models", self.grid_name, "Grid"], | ||
type="Volumetrics", | ||
name=self.volume_job_name, | ||
).get_arguments() | ||
_logger.debug("RMS VOLJOB settings... DONE") | ||
|
||
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._volume_table_name = voltable.get("ReportTableName") | ||
|
||
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 the job." | ||
) | ||
|
||
_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._volume_table_name] | ||
.get_data_table() | ||
.to_dict() | ||
) | ||
_logger.debug("Dict values are: %s", dict_values) | ||
self._dataframe = pd.DataFrame.from_dict(dict_values) | ||
self._dataframe.rename(columns=_RENAME_COLUMNS_FROM_RMS, inplace=True) | ||
self._dataframe.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.""" | ||
|
||
units = self.project.project_units | ||
_logger.debug("Units are %s", units) | ||
self._units = str(units) | ||
|
||
def _warn_if_forcefolder(self) -> None: | ||
if self.forcefolder: | ||
warn( | ||
"A 'forcefolder' is set. This is strongly discouraged and will be " | ||
"removed in coming versions", | ||
FutureWarning, | ||
) | ||
|
||
def _export_volume_table(self) -> dict[str, str]: | ||
"""Do the actual volume table export using dataio setup.""" | ||
|
||
edata = dio.ExportData( | ||
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.name if self.name else f"{self.grid_name}_volumes", | ||
rep_include=False, | ||
) | ||
|
||
out = edata.export(self._dataframe) | ||
_logger.debug("Volume result to: %s", out) | ||
return {"volume_table": out} | ||
|
||
def export(self) -> dict[str, str]: | ||
"""Export the volume table.""" | ||
return self._export_volume_table() | ||
|
||
|
||
def export_rms_volumetrics( | ||
project: Any, | ||
grid_name: str, | ||
volume_job_name: str, | ||
global_config: str | Path | dict = "../../fmuconfig/output/global_variables.yml", | ||
forcefolder: str = "", # unsure if we shall allow this? | ||
subfolder: str = "", | ||
name: str = "", | ||
tagname: str = "", | ||
classification: str = "restricted", | ||
workflow: str = "rms volumetric run", | ||
) -> dict[str, str]: | ||
"""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 data are optional (not yet implemented). | ||
Args: | ||
project: The 'magic' project variable in RMS. | ||
grid_name: Name of 3D grid model in RMS. | ||
volume_job_name: Name of the volume job. | ||
global_config: Optional. The global config can either point to the | ||
global_variables file, or it can be a dictionary. As default, it assumes | ||
a the current standard in FMU: | ||
``'../../fmuconfig/output/global_variables.yml'`` | ||
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. | ||
subfolder: Name of subfolder for local storage, below the standard folder. | ||
name: Optional. Name of export item. Is defaulted to name of grid + '_volumes'. | ||
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'. | ||
""" | ||
|
||
return _ExportVolumetricsRMS( | ||
project, | ||
grid_name, | ||
volume_job_name, | ||
global_config=global_config, | ||
forcefolder=forcefolder, | ||
subfolder=subfolder, | ||
name=name, | ||
tagname=tagname, | ||
classification=classification, | ||
workflow=workflow, | ||
).export() |
Oops, something went wrong.