-
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
63b6b3e
commit 416ad20
Showing
10 changed files
with
632 additions
and
7 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,46 @@ | ||
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 need for ``fmu.dataio``. | ||
|
||
Example: | ||
|
||
.. code-block:: python | ||
from fmu.dataio.export.rms import RmsVolumetricsExport | ||
... | ||
# 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 = RmsVolumetricsExport( | ||
project, | ||
"Geogrid", | ||
"geogrid_volumes", | ||
config_file="../whatever/global_variables.yml", | ||
tagname="vol", | ||
).export() | ||
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 RmsVolumetricsExport | ||
|
||
__all__ = ["RmsVolumetricsExport"] |
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,253 @@ | ||
from __future__ import annotations | ||
|
||
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 | ||
|
||
import fmu.dataio as dio | ||
from fmu.config.utilities import yaml_load | ||
from fmu.dataio._logging import null_logger | ||
|
||
try: | ||
import roxar as rmsapi # noqa | ||
import roxar.jobs as jobs # noqa | ||
from _roxar import Project as RmsProject # noqa | ||
except ImportError: | ||
pass | ||
|
||
try: | ||
import rmsapi # noqa | ||
import rmsapi.jobs as jobs # noqa | ||
from _rmsapi import Project as RmsProject # noqa | ||
except ImportError: | ||
pass | ||
|
||
|
||
_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 RmsVolumetricsExport: | ||
"""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. | ||
config_file: 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. | ||
global_config: Optional. The global config as a dictionary. If present, it will | ||
ovveride the ``config_file`` key. Hence using both config_file and | ||
global_config is not recommended. | ||
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 | ||
grid_name: str | ||
volume_job_name: str | ||
|
||
# optional and defaulted | ||
config_file: str | Path = "../../fmuconfig/output/global_variables.yml" | ||
global_config: dict = field(default_factory=dict) | ||
forcefolder: str = "" # unsure if we shall allow this? | ||
tagname: str = "vol" | ||
classification: str = "restricted" | ||
workflow: str = "rms volumetric run" | ||
include_zone_maps: bool = False | ||
include_total_maps: bool = False | ||
include_3dgrid_properties: bool = False | ||
|
||
# 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) | ||
|
||
@property | ||
def dataframe(self) -> pd.DataFrame: | ||
return self._dataframe | ||
|
||
@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.""" | ||
# TODO: This functionality should ideally be in fmu-config. | ||
_logger.debug("Set global config...") | ||
|
||
if self.global_config: | ||
self._global_config = self.global_config | ||
_logger.debug("Set global config (from input dictionary)... DONE!") | ||
return | ||
|
||
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_file}") | ||
_logger.debug("Set global config (from file)... 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.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." | ||
) | ||
|
||
_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 = dfr = pd.DataFrame.from_dict(dict_values) | ||
dfr.rename(columns=_RENAME_COLUMNS_FROM_RMS, 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.""" | ||
|
||
units = self.project.project_units | ||
_logger.debug("Units are %s", units) | ||
self._units = str(units) | ||
|
||
def _read_includes_etc(self) -> None: | ||
"""Handle other products (placeholder; in prep in the code).""" | ||
if self.include_zone_maps: | ||
raise NotImplementedError( | ||
"Including zone maps is currently not implemented" | ||
) | ||
if self.include_total_maps: | ||
raise NotImplementedError( | ||
"Including total maps is currently not implemented" | ||
) | ||
if self.include_3dgrid_properties: | ||
raise NotImplementedError( | ||
"Including 3D parameters is currently not implemented" | ||
) | ||
|
||
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 _process_data(self) -> None: | ||
"""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._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.grid_name, | ||
) | ||
|
||
out = edata.export(self.dataframe) | ||
_logger.debug("Volume result to: %s", out) | ||
return {"volume_table": out} | ||
|
||
def export(self) -> dict[str, str]: # public function | ||
"""Export the volume table.""" | ||
self._process_data() | ||
return self._export_volume_table() |
Oops, something went wrong.