-
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.
Utility to export data to qc well log upscaling in Webviz
- Loading branch information
1 parent
c046250
commit 04aa696
Showing
4 changed files
with
288 additions
and
0 deletions.
There are no files selected for viewing
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,43 @@ | ||
from typing import List, Union, Dict | ||
from dataclasses import dataclass, field | ||
|
||
|
||
@dataclass | ||
class WellSource: | ||
names: List[int] = field(default_factory=list) | ||
trajectory: str = "Drilled trajectory" | ||
logrun: str = "log" | ||
|
||
|
||
@dataclass | ||
class BlockedWellSource: | ||
grid: str | ||
bwname: str | ||
names: List[int] = field(default_factory=list) | ||
|
||
|
||
@dataclass | ||
class Context: | ||
properties: Union[List[str], Dict[str, str]] | ||
selectors: Union[List[str], Dict[str, str]] | ||
|
||
|
||
@dataclass | ||
class WellContext(Context): | ||
wells: dict = field(default_factory=dict) | ||
|
||
def __post_init__(self): | ||
self.wells = WellSource(**self.wells) | ||
|
||
|
||
@dataclass | ||
class GridContext(Context): | ||
grid: str | ||
|
||
|
||
@dataclass | ||
class BlockedWellContext(Context): | ||
wells: dict = field(default_factory=dict) | ||
|
||
def __post_init__(self): | ||
self.wells = BlockedWellSource(**self.wells) |
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,75 @@ | ||
from pathlib import Path | ||
from typing import Optional, List, Union, Dict | ||
from dataclasses import asdict | ||
|
||
import pandas as pd | ||
from fmu.tools.qcproperties._grid2df import GridProps2df | ||
from fmu.tools.qcproperties._well2df import WellLogs2df | ||
from fmu.tools.qcdata import QCData | ||
|
||
from fmu.tools.rms.upscaling_qc._types import ( | ||
WellContext, | ||
GridContext, | ||
BlockedWellContext, | ||
) | ||
|
||
|
||
class RMSUpscalingQC: | ||
def __init__( | ||
self, project, well_data: dict, grid_data: dict, bw_data: dict | ||
) -> None: | ||
self._project = project | ||
self._well_data = WellContext(**well_data) | ||
self._grid_data = GridContext(**grid_data) | ||
self._bw_data = BlockedWellContext(**bw_data) | ||
self._validate_input() | ||
self._set_well_names_in_context() | ||
|
||
def _validate_input(self) -> None: | ||
if self._grid_data.grid != self._bw_data.wells.grid: | ||
raise ValueError("Different grids given for blocked well and grid.") | ||
|
||
@property | ||
def _well_names(self) -> List[str]: | ||
try: | ||
grid = self._project.grid_models[self._grid_data.grid] | ||
return grid.blocked_wells_set[self._bw_data.wells.bwname].get_well_names() | ||
except ValueError: | ||
pass | ||
|
||
def _set_well_names_in_context(self) -> None: | ||
self._well_data.wells.names = self._well_names | ||
self._bw_data.wells.names = self._well_names | ||
|
||
def _get_well_data(self) -> pd.DataFrame: | ||
_ = WellLogs2df( | ||
project=self._project, data=asdict(self._well_data), xtgdata=QCData() | ||
) | ||
return _.dataframe | ||
|
||
def _get_bw_data(self) -> pd.DataFrame: | ||
_ = WellLogs2df( | ||
project=self._project, | ||
data=asdict(self._bw_data), | ||
xtgdata=QCData(), | ||
blockedwells=True, | ||
) | ||
return _.dataframe | ||
|
||
def _get_grid_data(self) -> pd.DataFrame: | ||
_ = GridProps2df( | ||
project=self._project, data=asdict(self._grid_data), xtgdata=QCData() | ||
) | ||
return _.dataframe | ||
|
||
def to_disk(self, path: str = "../../share/results/tables/upscaling_qc") -> None: | ||
folder = Path(path) | ||
|
||
if not folder.parent.is_dir(): | ||
print(f"Cannot create folder. Ensure that {folder.parent} exists.") | ||
folder.mkdir(exist_ok=True) | ||
print("Extracting data...") | ||
self._get_well_data().to_csv(folder / "well.csv", index=False) | ||
self._get_bw_data().to_csv(folder / "bw.csv", index=False) | ||
self._get_grid_data().to_csv(folder / "grid.csv", index=False) | ||
print(f"Done. Output written to {folder}.") |
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,170 @@ | ||
"""Run tests in RMS. | ||
Creates a tmp RMS project in given version which is used as fixture for all other Roxar | ||
API dependent tests. | ||
This requires a ROXAPI license, and to be ran in a "roxenvbash" environment; hence | ||
the decorator "roxapilicense" | ||
""" | ||
from pathlib import Path | ||
from os.path import isdir | ||
import shutil | ||
import numpy as np | ||
import pytest | ||
|
||
import xtgeo | ||
|
||
try: | ||
import roxar | ||
import roxar.jobs | ||
|
||
except ImportError: | ||
pass | ||
|
||
from fmu.tools.rms.upscaling_qc.upscaling_qc import RMSUpscalingQC | ||
|
||
# ====================================================================================== | ||
# settings to create RMS project! | ||
|
||
TMPD = Path("TMP") | ||
TMPD.mkdir(parents=True, exist_ok=True) | ||
|
||
TPATH = Path("../xtgeo-testdata") | ||
PROJNAME = "tmp_project_qcupscaling.rmsxxx" | ||
|
||
PRJ = str(TMPD / PROJNAME) | ||
|
||
GRIDDATA1 = TPATH / "3dgrids/reek/reek_sim_grid.roff" | ||
PORODATA1 = TPATH / "3dgrids/reek/reek_sim_poro.roff" | ||
ZONEDATA1 = TPATH / "3dgrids/reek/reek_sim_zone.roff" | ||
GRIDNAME1 = "Simgrid" | ||
PORONAME1 = "PORO" | ||
ZONENAME1 = "Zone" | ||
|
||
WELLSFOLDER1 = TPATH / "wells/reek/1" | ||
WELLS1 = ["OP_1.w", "OP_2.w", "OP_6.w"] | ||
|
||
BW_JOB_SPEC = { | ||
"BlockedWellsName": "BW", | ||
"Continuous Blocked Log": [ | ||
{ | ||
"Name": "Poro", | ||
} | ||
], | ||
"Wells": [["Wells", "OP_1"], ["Wells", "OP_2"], ["Wells", "OP_6"]], | ||
"Zone Blocked Log": [ | ||
{ | ||
"Name": "Zonelog", | ||
"ScaleUpType": "SUBGRID_BIAS", | ||
"ThicknessWeighting": "MD_WEIGHT", | ||
"ZoneLogArray": [1, 2], | ||
} | ||
], | ||
} | ||
|
||
|
||
@pytest.mark.skipunlessroxar | ||
@pytest.fixture(name="roxar_project") | ||
def fixture_create_project(): | ||
"""Create a tmp RMS project for testing, populate with basic data. | ||
After the yield command, the teardown phase will remove the tmp RMS project. | ||
""" | ||
prj1 = str(PRJ) | ||
|
||
print("\n******** Setup RMS project!\n") | ||
if isdir(prj1): | ||
print("Remove existing project! (1)") | ||
shutil.rmtree(prj1) | ||
|
||
project = roxar.Project.create() | ||
|
||
rox = xtgeo.RoxUtils(project) | ||
print("Roxar version is", rox.roxversion) | ||
print("RMS version is", rox.rmsversion(rox.roxversion)) | ||
assert "1." in rox.roxversion | ||
|
||
for wfile in WELLS1: | ||
wobj = xtgeo.well_from_file(WELLSFOLDER1 / wfile) | ||
wobj.dataframe["Zonelog"] = 1 | ||
|
||
wobj.to_roxar(project, wobj.name, logrun="log", trajectory="Drilled trajectory") | ||
|
||
# populate with grid and props | ||
grd = xtgeo.grid_from_file(GRIDDATA1) | ||
grd.to_roxar(project, GRIDNAME1) | ||
por = xtgeo.gridproperty_from_file(PORODATA1, name=PORONAME1) | ||
por.to_roxar(project, GRIDNAME1, PORONAME1) | ||
zon = xtgeo.gridproperty_from_file(ZONEDATA1, name=ZONENAME1) | ||
zon.values = zon.values.astype(np.uint8) | ||
zon.values = 1 | ||
zon.to_roxar(project, GRIDNAME1, ZONENAME1) | ||
bw_job = roxar.jobs.Job.create( | ||
owner=["Grid models", "Simgrid", "Grid"], type="Block Wells", name="BW" | ||
) | ||
bw_job.set_arguments(BW_JOB_SPEC) | ||
bw_job.save() | ||
|
||
bw_job.execute(0) | ||
for well in project.wells: | ||
for traj in well.wellbore.trajectories: | ||
print(traj.name) | ||
for bw in project.grid_models["Simgrid"].blocked_wells_set["BW"].get_well_names(): | ||
print(bw) | ||
# save project (both an initla version and a work version) and exit | ||
project.save_as(prj1) | ||
project.close() | ||
|
||
yield prj1 | ||
|
||
print("\n******* Teardown RMS project!\n") | ||
|
||
if isdir(prj1): | ||
print("Remove existing project! (1)") | ||
shutil.rmtree(prj1) | ||
|
||
|
||
@pytest.mark.skipunlessroxar | ||
def test_upscaling_qc(roxar_project): | ||
"""Test qcreset metod in roxapi.""" | ||
# ================================================================================== | ||
# pylint: disable=invalid-name | ||
from fmu.tools.rms import qcreset | ||
|
||
rox = xtgeo.RoxUtils(roxar_project, readonly=True) | ||
project = rox.project | ||
|
||
well_data = { | ||
"selectors": { | ||
"ZONE": { | ||
"name": "Zonelog", | ||
} | ||
}, | ||
"properties": {"PORO": {"name": "Poro"}}, | ||
} | ||
bw_data = { | ||
"selectors": { | ||
"ZONE": { | ||
"name": "Zonelog", | ||
} | ||
}, | ||
"properties": {"PORO": {"name": "Poro"}}, | ||
"wells": {"grid": "Simgrid", "bwname": "BW"}, | ||
} | ||
grid_data = { | ||
"selectors": {"ZONE": {"name": "Zone"}}, | ||
"properties": ["PORO"], | ||
"grid": "Simgrid", | ||
} | ||
ups = RMSUpscalingQC( | ||
project=project, well_data=well_data, bw_data=bw_data, grid_data=grid_data | ||
) | ||
well_df = ups._get_well_data() | ||
bw_df = ups._get_bw_data() | ||
grid_df = ups._get_grid_data() | ||
for data in [well_df, bw_df, grid_df]: | ||
assert set(data.columns) == set(["ZONE", "PORO"]) | ||
assert well_df["PORO"].mean() == pytest.approx(0.1725, abs=0.0001) | ||
assert bw_df["PORO"].mean() == pytest.approx(0.1765, abs=0.0001) | ||
assert grid_df["PORO"].mean() == pytest.approx(0.1677, abs=0.0001) |