Skip to content

Commit

Permalink
Utility to export data to qc well log upscaling in Webviz
Browse files Browse the repository at this point in the history
  • Loading branch information
HansKallekleiv committed Oct 26, 2021
1 parent c046250 commit 04aa696
Show file tree
Hide file tree
Showing 4 changed files with 288 additions and 0 deletions.
Empty file.
43 changes: 43 additions & 0 deletions src/fmu/tools/rms/upscaling_qc/_types.py
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)
75 changes: 75 additions & 0 deletions src/fmu/tools/rms/upscaling_qc/upscaling_qc.py
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}.")
170 changes: 170 additions & 0 deletions tests/rms/test_upscaling_qc.py
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)

0 comments on commit 04aa696

Please sign in to comment.