Skip to content

Commit 1a6df04

Browse files
CCS-213: Handle plume_groups (equinor#36)
* CCS-213: Start using plume group from containment results. * CCS-213: Cont using plume group from containment results. * CCS-213: Fixes for plume groups. * CCS-213: Add plume groups to filter. * CCS-213: Some minor fixes. * CCS-213: Minor fix. * Some tests. * CCS-213: Join lines before/after merge when coloring by plume groups. * CCS-213: Some refactoring. * CCS-213: Fix combine plume group with none as mark. * CCS-213: Minor fix. * CCS-213: Remove some prints. * CCS-213: Use starting 0-values for unmerged wells. * CCS-213: Fix for multiple realization. * CCS-213: Fix case without plume groups. * CCS-213: Minor fix. * CCS-213: Fix black. * CCS-213: Some refactoring. * CCS-213: Minor change, remove temp code.
1 parent 274c3c2 commit 1a6df04

File tree

7 files changed

+369
-30
lines changed

7 files changed

+369
-30
lines changed

webviz_subsurface/plugins/_co2_leakage/_plugin.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ def _set_callbacks(self) -> None:
291291
Input(self._settings_component(ViewSettings.Ids.REGION), "value"),
292292
Input(self._settings_component(ViewSettings.Ids.PHASE), "value"),
293293
Input(self._settings_component(ViewSettings.Ids.CONTAINMENT), "value"),
294+
Input(self._settings_component(ViewSettings.Ids.PLUME_GROUP), "value"),
294295
Input(self._settings_component(ViewSettings.Ids.COLOR_BY), "value"),
295296
Input(self._settings_component(ViewSettings.Ids.MARK_BY), "value"),
296297
Input(self._settings_component(ViewSettings.Ids.SORT_PLOT), "value"),
@@ -309,6 +310,7 @@ def update_graphs(
309310
region: Optional[str],
310311
phase: str,
311312
containment: str,
313+
plume_group: str,
312314
color_choice: str,
313315
mark_choice: Optional[str],
314316
sorting: str,
@@ -320,6 +322,7 @@ def update_graphs(
320322
region,
321323
phase,
322324
containment,
325+
plume_group,
323326
color_choice,
324327
mark_choice,
325328
sorting,

webviz_subsurface/plugins/_co2_leakage/_utilities/callbacks.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,7 @@ def process_containment_info(
441441
region: Optional[str],
442442
phase: str,
443443
containment: str,
444+
plume_group: str,
444445
color_choice: str,
445446
mark_choice: Optional[str],
446447
sorting: str,
@@ -450,10 +451,22 @@ def process_containment_info(
450451
mark_choice = "phase"
451452
zones = menu_options["zones"]
452453
regions = menu_options["regions"]
454+
plume_groups = menu_options["plume_groups"]
453455
if len(zones) > 0:
454456
zones = [zone_name for zone_name in zones if zone_name != "all"]
455457
if len(regions) > 0:
456458
regions = [reg_name for reg_name in regions if reg_name != "all"]
459+
if len(plume_groups) > 0:
460+
plume_groups = [pg_name for pg_name in plume_groups if pg_name != "all"]
461+
462+
def plume_sort_key(name: str):
463+
if name == "?":
464+
return 999
465+
else:
466+
return name.count("+")
467+
468+
plume_groups = sorted(plume_groups, key=plume_sort_key)
469+
457470
containments = ["hazardous", "outside", "contained"]
458471
phases = [phase for phase in menu_options["phases"] if phase != "total"]
459472
if "zone" in [mark_choice, color_choice]:
@@ -467,11 +480,13 @@ def process_containment_info(
467480
"regions": regions,
468481
"phase": phase,
469482
"containment": containment,
483+
"plume_group": plume_group,
470484
"color_choice": color_choice,
471485
"mark_choice": mark_choice,
472486
"sorting": sorting,
473487
"phases": phases,
474488
"containments": containments,
489+
"plume_groups": plume_groups,
475490
}
476491

477492

@@ -491,12 +506,18 @@ def set_plot_ids(
491506
if containment_info["region"] is not None
492507
else "None"
493508
)
509+
plume_group_str = (
510+
containment_info["plume_group"]
511+
if containment_info["plume_group"] is not None
512+
else "None"
513+
)
494514
plot_id = "-".join(
495515
(
496516
source,
497517
scale,
498518
zone_str,
499519
region_str,
520+
plume_group_str,
500521
str(containment_info["phase"]),
501522
str(containment_info["containment"]),
502523
containment_info["color_choice"],

webviz_subsurface/plugins/_co2_leakage/_utilities/co2volume.py

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def _get_marks(num_marks: int, mark_choice: str) -> List[str]:
7575
return [""] * num_marks
7676
if mark_choice == "containment":
7777
return ["x", "/", ""]
78-
if mark_choice in ["zone", "region"]:
78+
if mark_choice in ["zone", "region", "plume_group"]:
7979
base_pattern = ["", "/", "x", "-", "\\", "+", "|", "."]
8080
if num_marks > len(base_pattern):
8181
base_pattern *= int(np.ceil(num_marks / len(base_pattern)))
@@ -92,7 +92,7 @@ def _get_line_types(mark_options: List[str], mark_choice: str) -> List[str]:
9292
return ["solid"]
9393
if mark_choice == "containment":
9494
return ["dash", "dot", "solid"]
95-
if mark_choice in ["zone", "region"]:
95+
if mark_choice in ["zone", "region", "plume_group"]:
9696
if len(mark_options) > 8:
9797
warnings.warn(
9898
f"Large number of {mark_choice}s might make it hard "
@@ -239,7 +239,7 @@ def _filter_columns(
239239
) -> None:
240240
filter_columns = [
241241
col
242-
for col in ["phase", "containment", "zone", "region"]
242+
for col in ["phase", "containment", "zone", "region", "plume_group"]
243243
if col not in [mark_choice, color_choice]
244244
]
245245
for col in filter_columns:
@@ -269,6 +269,7 @@ def _add_sort_key_and_real(
269269
& (df["containment"] == "hazardous")
270270
& (df["zone"] == containment_info["zone"])
271271
& (df["region"] == containment_info["region"])
272+
& (df["plume_group"] == containment_info["plume_group"])
272273
]["amount"]
273274
)
274275
sort_value_secondary = np.sum(
@@ -277,6 +278,7 @@ def _add_sort_key_and_real(
277278
& (df["containment"] == "outside")
278279
& (df["zone"] == containment_info["zone"])
279280
& (df["region"] == containment_info["region"])
281+
& (df["plume_group"] == containment_info["plume_group"])
280282
]["amount"]
281283
)
282284
df["real"] = [label] * df.shape[0]
@@ -511,6 +513,73 @@ def _add_hover_info_in_field(
511513
prev_vals[date] = prev_val + amount
512514

513515

516+
def _connect_plume_groups(df: pd.DataFrame, color_choice: str, mark_choice: str):
517+
cols = ["realization"]
518+
if color_choice == "plume_group" and mark_choice != "none":
519+
cols.append(mark_choice)
520+
elif mark_choice == "plume_group":
521+
cols.append(color_choice)
522+
# Find points where plumes start or end, to connect the lines
523+
end_points = []
524+
start_points = []
525+
for plume_name, df_sub in df.groupby("plume_group"):
526+
if plume_name == "?":
527+
continue
528+
for _, df_sub2 in df_sub.groupby(cols):
529+
# Assumes the data frame is sorted on date
530+
mask_end = (
531+
(df_sub2["amount"] == 0.0)
532+
& (df_sub2["amount"].shift(1) > 0.0)
533+
& (df_sub2.index > 0)
534+
)
535+
mask_start = (
536+
(df_sub2["amount"] > 0.0)
537+
& (df_sub2["amount"].shift(1) == 0.0)
538+
& (df_sub2.index > 0)
539+
)
540+
first_index_end = mask_end.idxmax() if mask_end.any() else None
541+
first_index_start = mask_start.idxmax() if mask_start.any() else None
542+
transition_row_end = (
543+
df_sub2.loc[first_index_end] if first_index_end is not None else None
544+
)
545+
transition_row_start = (
546+
df_sub2.loc[first_index_start]
547+
if first_index_start is not None
548+
else None
549+
)
550+
if transition_row_end is not None:
551+
end_points.append(transition_row_end)
552+
# Replace 0 with np.nan for all dates after this
553+
date = str(transition_row_end["date"])
554+
df.loc[
555+
(df["plume_group"] == plume_name)
556+
& (df["amount"] == 0.0)
557+
& (df["date"] > date),
558+
"amount",
559+
] = np.nan
560+
if transition_row_start is not None:
561+
start_points.append(transition_row_start)
562+
for end_point in end_points:
563+
plume1 = end_point["plume_group"]
564+
row1 = end_point.drop(["amount", "plume_group", "name"])
565+
for start_point in start_points:
566+
plume2 = start_point["plume_group"]
567+
if plume1 in plume2 and len(plume1) < len(plume2):
568+
row2 = start_point.drop(["amount", "plume_group", "name"])
569+
if row1.equals(row2):
570+
row_to_change = df.eq(end_point).all(axis=1)
571+
if sum(row_to_change) == 1:
572+
df.loc[row_to_change == True, "amount"] = start_point["amount"]
573+
df["is_merged"] = ["+" in x for x in df["plume_group"].values]
574+
df.loc[
575+
(df["plume_group"] != "all")
576+
& (df["is_merged"] == True)
577+
& (df["amount"] == 0.0),
578+
"amount",
579+
] = np.nan
580+
df.drop(columns="is_merged", inplace=True)
581+
582+
514583
# pylint: disable=too-many-locals
515584
def generate_co2_time_containment_figure(
516585
table_provider: ContainmentDataProvider,
@@ -528,6 +597,12 @@ def generate_co2_time_containment_figure(
528597
active_cols_at_startup = list(
529598
options[options["line_type"].isin(["solid", "0px"])]["name"]
530599
)
600+
if "plume_group" in df:
601+
try:
602+
_connect_plume_groups(df, color_choice, mark_choice)
603+
except Exception:
604+
pass
605+
531606
fig = go.Figure()
532607
# Generate dummy scatters for legend entries
533608
dummy_args = {"x": df["date"], "mode": "lines", "hoverinfo": "none"}

webviz_subsurface/plugins/_co2_leakage/_utilities/containment_data_provider.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,11 @@ def extract_condensed_dataframe(
4545
co2_scale: Union[Co2MassScale, Co2VolumeScale],
4646
) -> pd.DataFrame:
4747
df = self._provider.get_column_data(self._provider.column_names())
48-
df = df.loc[(df["zone"] == "all") & (df["region"] == "all")]
48+
df = df.loc[
49+
(df["zone"] == "all")
50+
& (df["region"] == "all")
51+
& (df["plume_group"] == "all")
52+
]
4953
if co2_scale == Co2MassScale.MTONS:
5054
df.loc[:, "amount"] /= 1e9
5155
elif co2_scale == Co2MassScale.NORMALIZE:
@@ -82,6 +86,19 @@ def _get_menu_options(provider: EnsembleTableProvider) -> MenuOptions:
8286
for region in list(df["region"]):
8387
if region not in regions:
8488
regions.append(region)
89+
plume_groups = ["all"]
90+
for plume_group in list(df["plume_group"]):
91+
if plume_group not in plume_groups:
92+
plume_groups.append(plume_group)
93+
94+
def plume_sort_key(name: str):
95+
if name == "?":
96+
return 999
97+
else:
98+
return name.count("+")
99+
100+
plume_groups = sorted(plume_groups, key=plume_sort_key)
101+
85102
if "free_gas" in list(df["phase"]):
86103
phases = ["total", "free_gas", "trapped_gas", "aqueous"]
87104
else:
@@ -90,12 +107,21 @@ def _get_menu_options(provider: EnsembleTableProvider) -> MenuOptions:
90107
"zones": zones if len(zones) > 1 else [],
91108
"regions": regions if len(regions) > 1 else [],
92109
"phases": phases,
110+
"plume_groups": plume_groups if len(plume_groups) > 1 else [],
93111
}
94112

95113
@staticmethod
96114
def _validate(provider: EnsembleTableProvider) -> None:
97115
col_names = provider.column_names()
98-
required_columns = ["date", "amount", "phase", "containment", "zone", "region"]
116+
required_columns = [
117+
"date",
118+
"amount",
119+
"phase",
120+
"containment",
121+
"zone",
122+
"region",
123+
"plume_group",
124+
]
99125
missing_columns = [col for col in required_columns if col not in col_names]
100126
realization = provider.realizations()[0]
101127
if len(missing_columns) == 0:

webviz_subsurface/plugins/_co2_leakage/_utilities/generic.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ class MenuOptions(TypedDict):
177177
zones: List[str]
178178
regions: List[str]
179179
phases: List[str]
180+
plume_groups: List[str]
180181

181182

182183
class MapThresholds:

webviz_subsurface/plugins/_co2_leakage/_utilities/initialization.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ def init_dictionary_of_content(
274274
content["maps"] = has_maps
275275
content["zones"] = False
276276
content["regions"] = False
277+
content["plume_groups"] = False
277278
if content["mass"] or content["volume"]:
278279
content["zones"] = max(
279280
len(inner_dict["zones"]) > 0
@@ -285,6 +286,11 @@ def init_dictionary_of_content(
285286
for outer_dict in menu_options.values()
286287
for inner_dict in outer_dict.values()
287288
)
289+
content["plume_groups"] = max(
290+
len(inner_dict["plume_groups"]) > 0
291+
for outer_dict in menu_options.values()
292+
for inner_dict in outer_dict.values()
293+
)
288294
return content
289295

290296

0 commit comments

Comments
 (0)