Skip to content

Commit

Permalink
New implementation of the WellCompletion plugin (with WLF). (#1058)
Browse files Browse the repository at this point in the history
* Initial commit

started on reading of arrow files

Added WellCompletionNew

Implemented using EnsembleTableProvider to load arrow files

Started on the create_ensemble_dataset function

First implementation of create_ensemble_dataset

Some improvements to create dataset code

started implementing views

Implemented WLF structure

Implemented single realization

Implemented a view element

Wrote main plugin docstring

* Started implementing portable

* started implementing StratigraphyModel

* Stratigraphy model and portable

* webviz-store also on get_unit

* Docstrings and some changes to what is returned if file is not found

* Docstrings

* Cleaned up naming confusion

* logger and timer

* Renamed the plugin WellCompletion, removed kh arguments

* Removed print

* Sorting

* Deprecated old plugin

* changelog entry

* Formatting

* Review related changes

* Refactored the code according to new WLF best practice

* Removed plugin_ids file and added str and Enum to Ids.

* Moved Ids class into plugin class

* Added tour steps and removed imports from _views

* StrEnum and callback_typecheck

* callback_typecheck imported from webviz_config.utils

Co-authored-by: Øyvind Lind-Johansen <[email protected]>
  • Loading branch information
lindjoha and Øyvind Lind-Johansen authored Sep 15, 2022
1 parent 271cc5f commit 251810d
Show file tree
Hide file tree
Showing 14 changed files with 783 additions and 0 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [UNRELEASED] - YYYY-MM-DD

### Added

- [#1058](https://github.com/equinor/webviz-subsurface/pull/1058) - `WellCompletion` - New implementation of the `WellCompletions` plugin, which is faster, has more functionality (single realization) and utilizes the webviz layout framework (WLF).


### Fixed

- [#1094](https://github.com/equinor/webviz-subsurface/pull/1094) - Fixed issues with ambiguous truth of `pandas.Series` in `EnsembleSummaryProvider`.
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"WellCrossSectionFMU = webviz_subsurface.plugins:WellCrossSectionFMU",
"AssistedHistoryMatchingAnalysis = webviz_subsurface.plugins:AssistedHistoryMatchingAnalysis",
"WellCompletions = webviz_subsurface.plugins:WellCompletions",
"WellCompletion = webviz_subsurface.plugins:WellCompletion",
"WellLogViewer = webviz_subsurface.plugins:WellLogViewer",
"WellAnalysis = webviz_subsurface.plugins:WellAnalysis",
],
Expand Down
1 change: 1 addition & 0 deletions webviz_subsurface/_models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from .inplace_volumes_model import InplaceVolumesModel
from .observation_model import ObservationModel
from .parameter_model import ParametersModel
from .stratigraphy_model import StratigraphyModel
from .surface_leaflet_model import SurfaceLeafletModel
from .surface_set_model import SurfaceSetModel
from .well_attributes_model import WellAttributesModel
Expand Down
96 changes: 96 additions & 0 deletions webviz_subsurface/_models/stratigraphy_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import glob
import io
import json
import logging
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional, Tuple

from webviz_config.webviz_store import webvizstore


class StratigraphyModel:
"""Facilitates loading of a json file with stratigraphy.
The file needs to follow the format below. It is a tree structure, where each
node has a name, an optional `color` parameter, and an optional `subzones`
parameter which itself is a list of the same format.
It is assumed that the file is the same for all realizations, so it will only
read the first file it finds.
```json
[
{
"name": "ZoneA",
"color": "#FFFFFF",
"subzones": [
{
"name": "ZoneA.1"
},
{
"name": "ZoneA.2"
}
]
},
{
"name": "ZoneB",
"color": "#FFF000",
"subzones": [
{
"name": "ZoneB.1",
"color": "#FFF111"
},
{
"name": "ZoneB.2",
"subzones: {"name": "ZoneB.2.2"}
}
]
},
]
"""

def __init__(self, ens_name: str, ens_path: Path, stratigraphy_file: str):
self._ens_name = ens_name
self._ens_path = ens_path
self._stratigraphy_file = stratigraphy_file
self._data = list(json.load(self._load_data()))

def __repr__(self) -> str:
"""This is necessary for webvizstore to work on objects"""
return f"""
StratigraphyModel({self._ens_name!r}, {self._ens_path!r}, {self._stratigraphy_file!r})
"""

@property
def data(self) -> Optional[List[Dict[str, Any]]]:
"""Returns the stratigraphy if the list is not empty. If it is empty return None."""
if self._data:
return self._data
return None

@property
def webviz_store(self) -> Tuple[Callable, List[Dict]]:
return (
self._load_data,
[
{
"self": self,
}
],
)

@webvizstore
def _load_data(self) -> io.BytesIO:
"""Reads the stratigraphy file for an ensemble. It returns the data from the first
file it finds so it is assumed that the stratigraphy is equal for all realizations.
If no file is found, it returns a empty list. Because of webvizstore it is not possible
to return None.
"""

for filename in glob.glob(f"{self._ens_path}/{self._stratigraphy_file}"):
file_content = json.loads(Path(filename).read_text())
logging.debug(f"Stratigraphy loaded from file: {filename}")
return io.BytesIO(json.dumps(file_content).encode())
return io.BytesIO(json.dumps("").encode())
1 change: 1 addition & 0 deletions webviz_subsurface/plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
from ._tornado_plotter_fmu import TornadoPlotterFMU
from ._volumetric_analysis import VolumetricAnalysis
from ._well_analysis import WellAnalysis
from ._well_completion import WellCompletion
from ._well_completions import WellCompletions
from ._well_cross_section import WellCrossSection
from ._well_cross_section_fmu import WellCrossSectionFMU
Expand Down
1 change: 1 addition & 0 deletions webviz_subsurface/plugins/_well_completion/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from ._plugin import WellCompletion
198 changes: 198 additions & 0 deletions webviz_subsurface/plugins/_well_completion/_plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
from typing import Any, Callable, Dict, List, Tuple

from webviz_config import WebvizPluginABC, WebvizSettings
from webviz_config.utils import StrEnum

from webviz_subsurface._models import StratigraphyModel, WellAttributesModel

from ..._providers import EnsembleTableProviderFactory
from ._utils import WellCompletionDataModel
from ._views._well_completion_view import WellCompletionView, WellCompletionViewElement


class WellCompletion(WebvizPluginABC):
"""Vizualizes Eclipse well completions data per well. The data is grouped per well
and zone and can be filtered according to flexible well categories.
---
* **`ensembles`:** Which ensembles in `shared_settings` to visualize.
* **`wellcompletiondata_file`:** `.arrow` file with well completion data
* **`stratigraphy_file`:** `.json` file defining the stratigraphic levels
* **`well_attributes_file`:** `.json` file with categorical well attributes
* **`kh_unit`:** e.g. mD·m, will try to extract from eclipse files if defaulted
* **`kh_decimal_places`:**
---
The minimum requirement is to define `ensembles`.
**Well Completion data**
`wellcompletiondata_file` is a path to an `.arrow` file stored per realization (e.g in \
`share/results/tables/wellcompletiondata.arrow`). This file can be exported to disk by using the
`ECL2CSV` forward model in ERT with subcommand `wellcompletiondata`. This forward model will
read the eclipse `COMPDAT`, but then aggregate from layer to zone according to a given zone ➔
layer mapping `.lyr` file. If the `use_wellconnstatus` option is used, then the `OP/SH` status
of each well connection is deduced from `CPI` summary data which in som cases is more accurate
than the data coming from parsing the schedule file (f.ex if connections are changing status in
an `ACTION` keyword).
The reason for doing the layer ➔ zone aggregation in the FMU post processing instead of in the
plugin is to improve speed and to open up for more functionality.
**Stratigraphy file**
The `stratigraphy_file` file is intended to be generated per realization by an internal \
RMS script as part of the FMU workflow, but can also be set up manually and copied to each
realization. The stratigraphy is a tree structure, where each node has a name, an optional
`color` parameter, and an optional `subzones` parameter which itself is a list of the same
format.
```json
[
{
"name": "ZoneA",
"color": "#FFFFFF",
"subzones": [
{
"name": "ZoneA.1"
},
{
"name": "ZoneA.2"
}
]
},
{
"name": "ZoneB",
"color": "#FFF000",
"subzones": [
{
"name": "ZoneB.1",
"color": "#FFF111"
},
{
"name": "ZoneB.2",
"subzones: {"name": "ZoneB.2.2"}
}
]
},
]
```
The `stratigraphy_file` and the `zone_layer_mapping_file` will be combined to create the final \
stratigraphy. A node will be removed if the name or any of the subnode names are not \
present in the zone layer mapping. A Value Error is raised if any zones are present in the
zone layer mapping but not in the stratigraphy.
Colors can be supplied both trough the stratigraphy and through the zone_layer_mapping. \
The following prioritization will be applied:
1. Colors specified in the stratigraphy
2. Colors specified in the zone layer mapping lyr file
3. If none of the above is specified, theme colors will be added to the leaves of the tree
**Well Attributes file**
The `well_attributes_file` file is intended to be generated per realization by an internal \
RMS script as part of the FMU workflow. A sample script will be made available, but it is \
possible to manually set up the file and copy it to the correct folder on the scratch disk.\
The categorical well attributes are completely flexible.
The file should be a `.json` file on the following format:
```json
{
"version" : "0.1",
"wells" : [
{
"alias" : {
"eclipse" : "OP_1"
},
"attributes" : {
"mlt_singlebranch" : "mlt",
"structure" : "East",
"welltype" : "producer"
},
"name" : "OP_1"
},
{
"alias" : {
"eclipse" : "GI_1"
},
"attributes" : {
"mlt_singlebranch" : "singlebranch",
"structure" : "West",
"welltype" : "gas injector"
},
"name" : "GI_1"
},
]
}
```
**KH unit**
If defaulted, the plugin will look for the unit system of the Eclipse deck in the DATA file. \
The kh unit will be deduced from the unit system, e.g. mD·m if METRIC.
"""

class Ids(StrEnum):
WELL_COMPLETION_VIEW = "well-completion-view"

def __init__(
self,
webviz_settings: WebvizSettings,
ensembles: list,
wellcompletiondata_file: str = "share/results/tables/wellcompletiondata.arrow",
stratigraphy_file: str = "rms/output/zone/stratigraphy.json",
well_attributes_file: str = "rms/output/wells/well_attributes.json",
):

super().__init__(stretch=True)
factory = EnsembleTableProviderFactory.instance()

self._data_models = {}
for ens_name in ensembles:
ens_path = webviz_settings.shared_settings["scratch_ensembles"][ens_name]
self._data_models[ens_name] = WellCompletionDataModel(
ensemble_path=ens_path,
wellcompletion_provider=factory.create_from_per_realization_arrow_file(
ens_path, wellcompletiondata_file
),
stratigraphy_model=StratigraphyModel(
ens_name, ens_path, stratigraphy_file
),
well_attributes_model=WellAttributesModel(
ens_name, ens_path, well_attributes_file
),
theme_colors=webviz_settings.theme.plotly_theme["layout"]["colorway"],
)

self.add_view(
WellCompletionView(self._data_models),
self.Ids.WELL_COMPLETION_VIEW,
)

def add_webvizstore(self) -> List[Tuple[Callable, List[Dict]]]:
return [
webviz_store_tuple
for _, ens_data_model in self._data_models.items()
for webviz_store_tuple in ens_data_model.webviz_store
]

@property
def tour_steps(self) -> List[Dict[str, Any]]:
return [
{
"id": self.view(self.Ids.WELL_COMPLETION_VIEW)
.settings_group(WellCompletionView.Ids.SETTINGS)
.get_unique_id(),
"content": "Menu for selecting ensemble and other options",
},
{
"id": self.view(self.Ids.WELL_COMPLETION_VIEW)
.view_element(WellCompletionView.Ids.VIEW_ELEMENT)
.component_unique_id(WellCompletionViewElement.Ids.COMPONENT),
"content": "Visualization of the well completions. "
"Time slider for selecting which time steps to display. "
"Different vizualisation and filtering alternatives are available "
"in the upper right corner.",
},
]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from ._well_completion_data_model import WellCompletionDataModel
Loading

0 comments on commit 251810d

Please sign in to comment.