#8370: Add Python SummaryEnsemble helpers, examples and tests

This commit is contained in:
Magne Sjaastad committed 2026-10-16 09:57:12 +02:00
1 parent 60876e0bdd
commit d93aa839a6
6 files changed
+255 -8

No files matched your search

@@ -0,0 +1,22 @@
###############################################################################
# Read ensemble summary vectors for all realizations
###############################################################################
import sys
import rips
resinsight = rips.Instance.find()
project = resinsight.project
ensembles = project.summary_ensembles()
if not ensembles:
print("No summary ensemble found in the current project")
sys.exit(1)
ensemble = ensembles[0]
values = ensemble.vector_values("FOPT", time_resolution="YEAR")
for real, data in values.items():
print(real, data["values"])
@@ -12,19 +12,14 @@ resinsight = rips.Instance.find()
project = resinsight.project
# Summary ensembles and summary groups are both represented by
# SummaryCaseSubCollection. Use the 'is_ensemble' flag to pick the ensembles.
ensembles = [
collection
for collection in project.descendants(rips.SummaryCaseSubCollection)
if collection.is_ensemble
]
# Get all summary ensembles in the project
ensembles = project.summary_ensembles()
if not ensembles:
print("No summary ensemble found in the current project")
sys.exit(1)
summary_plot_collection = project.descendants(rips.SummaryPlotCollection)[0]
summary_plot_collection = project.summary_plot_collection()
for ensemble in ensembles:
print("Creating plots for ensemble: " + ensemble.summary_collection_name)
@@ -0,0 +1,31 @@
###############################################################################
# Compare multiple ensembles in the same plot, and add more subplots to an
# existing summary plot
#
###############################################################################
import sys
import rips
resinsight = rips.Instance.find()
project = resinsight.project
ensembles = project.summary_ensembles()
if len(ensembles) < 2:
print("At least two summary ensembles are required")
sys.exit(1)
summary_plot_collection = project.summary_plot_collection()
# One curve set per ensemble is created in the same subplot
plot = summary_plot_collection.new_summary_plot(ensembles=ensembles, address="FOPT")
# Append more subplots to the multi plot containing the first subplot
multi_plot = project.descendants(rips.MultiSummaryPlot)[-1]
multi_plot.append_subplot(ensembles=ensembles, address="FWPT")
multi_plot.append_subplot(ensembles=ensembles, address="FGPT")
multi_plot.number_of_columns = 2
multi_plot.update()
@@ -0,0 +1,32 @@
###############################################################################
# Create several subplots in the same summary multi plot
#
# Requires a project with at least one summary case
###############################################################################
import sys
import rips
resinsight = rips.Instance.find()
project = resinsight.project
summary_cases = project.descendants(rips.SummaryCase)
if not summary_cases:
print("At least one summary case is required")
sys.exit(1)
summary_plot_collection = project.summary_plot_collection()
# Create a multi plot with one subplot for each address
multi_plot = summary_plot_collection.new_summary_multi_plot(
summary_cases=[summary_cases[0]], addresses=["FOPT", "FWPT", "FGPT"]
)
# Subplots can be added later to the same multi plot
multi_plot.append_subplot(summary_cases=[summary_cases[0]], address="FOPR")
multi_plot.number_of_columns = 2
multi_plot.update()
print("Number of subplots:", len(multi_plot.descendants(rips.SummaryPlot)))
@@ -0,0 +1,37 @@
"""Convenience methods for working with summary ensembles from Python.
The methods are added to the ``SummaryEnsemble`` class, which is used
for both summary ensembles and summary groups. Use the ``is_ensemble`` flag to
pick the ensembles.
"""
from typing import Any, Dict, List
from .pdmobject import add_method
from .resinsight_classes import SummaryEnsemble
@add_method(SummaryEnsemble)
def vector_values(
self: SummaryEnsemble, address: str, time_resolution: str = "NONE"
) -> Dict[int, Dict[str, List[Any]]]:
"""Read one summary vector for all realizations in the ensemble.
Arguments:
address (str): Summary vector address, for example "FOPT" or "WOPT:A1"
time_resolution (str): "NONE" returns the original time steps of each
realization. Use "DAY", "WEEK", "MONTH", "QUARTER", "HALFYEAR" or
"YEAR" to resample all realizations to a common time axis.
Returns:
dict: Realization (summary case) id mapped to a dictionary with the
keys "time_steps" (seconds since epoch) and "values".
"""
result: Dict[int, Dict[str, List[Any]]] = {}
for summary_case in self.summary_cases():
data = summary_case.resample_values(address, time_resolution)
result[summary_case.id] = {
"time_steps": list(data.time_steps),
"values": list(data.values),
}
return result
@@ -0,0 +1,130 @@
import os
import shutil
import sys
import pytest
sys.path.insert(1, os.path.join(sys.path[0], "../../"))
import rips
import dataroot
SOURCE_DIR = dataroot.PATH + "/flow_diagnostics_test"
SUMMARY_FILES = ["SIMPLE_SUMMARY2.SMSPEC", "SIMPLE_SUMMARY2.UNSMRY"]
REALIZATION_COUNT = 3
def _create_ensemble_files(root_dir, name):
"""Copy the summary files into realization-N/iter-0 folders"""
smspec_files = []
for real in range(REALIZATION_COUNT):
folder = os.path.join(str(root_dir), name, f"realization-{real}", "iter-0")
os.makedirs(folder)
for file_name in SUMMARY_FILES:
shutil.copy(os.path.join(SOURCE_DIR, file_name), folder)
smspec_files.append(os.path.abspath(os.path.join(folder, SUMMARY_FILES[0])))
return smspec_files
def _import_ensemble(rips_instance, root_dir, name):
files = _create_ensemble_files(root_dir, name)
return rips_instance.project.import_summary_ensemble(
file_names=files, ensemble_name=name
)
def test_import_ensemble_and_list_cases(rips_instance, initialize_test, tmp_path):
ensemble = _import_ensemble(rips_instance, tmp_path, "ens_a")
assert ensemble.is_ensemble
assert ensemble.summary_collection_name == "ens_a"
assert len(ensemble.summary_cases()) == REALIZATION_COUNT
def test_vector_values(rips_instance, initialize_test, tmp_path):
ensemble = _import_ensemble(rips_instance, tmp_path, "ens_a")
values = ensemble.vector_values("FOPT", "NONE")
assert len(values) == REALIZATION_COUNT
for data in values.values():
assert len(data["values"]) == 60
assert len(data["time_steps"]) == 60
def test_set_vector_after_project_reload(rips_instance, initialize_test, tmp_path):
_import_ensemble(rips_instance, tmp_path, "ens_a")
project_path = str(tmp_path / "ensemble.rsp")
rips_instance.project.save(project_path)
rips_instance.project.close()
rips_instance.project.open(project_path)
ensemble = rips_instance.project.summary_ensembles()[0]
summary_case = ensemble.summary_cases()[0]
count = len(summary_case.available_time_steps().values)
# Realization addresses are created lazily after a project is loaded
summary_case.set_summary_values("FWCT_N", "", [0.5] * count)
with pytest.raises(rips.RipsError):
summary_case.set_summary_values("FOPT", "", [0.5] * count)
def test_multiple_ensembles_in_one_subplot(rips_instance, initialize_test, tmp_path):
ensemble_a = _import_ensemble(rips_instance, tmp_path, "ens_a")
ensemble_b = _import_ensemble(rips_instance, tmp_path, "ens_b")
collection = rips_instance.project.summary_plot_collection()
plot = collection.new_summary_plot(
ensembles=[ensemble_a, ensemble_b], address="FOPT"
)
assert plot is not None
curve_sets = plot.descendants(rips.EnsembleCurveSet)
assert len(curve_sets) == 2
assert len(rips_instance.project.descendants(rips.MultiSummaryPlot)) == 1
def test_append_subplot(rips_instance, initialize_test, tmp_path):
ensemble = _import_ensemble(rips_instance, tmp_path, "ens_a")
collection = rips_instance.project.summary_plot_collection()
collection.new_summary_plot(ensemble=ensemble, address="FOPT")
multi_plot = rips_instance.project.descendants(rips.MultiSummaryPlot)[0]
assert len(multi_plot.descendants(rips.SummaryPlot)) == 1
new_plot = multi_plot.append_subplot(ensembles=[ensemble], address="FWPT")
assert new_plot is not None
assert len(rips_instance.project.descendants(rips.MultiSummaryPlot)) == 1
assert len(multi_plot.descendants(rips.SummaryPlot)) == 2
with pytest.raises(Exception):
multi_plot.append_subplot()
def test_new_summary_multi_plot(rips_instance, initialize_test, tmp_path):
ensemble = _import_ensemble(rips_instance, tmp_path, "ens_a")
collection = rips_instance.project.summary_plot_collection()
multi_plot = collection.new_summary_multi_plot(
ensembles=[ensemble], addresses=["FOPT", "FWPT"]
)
assert multi_plot is not None
assert len(rips_instance.project.descendants(rips.MultiSummaryPlot)) == 1
assert len(multi_plot.descendants(rips.SummaryPlot)) == 2
with pytest.raises(Exception):
collection.new_summary_multi_plot(ensembles=[ensemble])
def test_project_summary_ensembles(rips_instance, initialize_test, tmp_path):
assert rips_instance.project.summary_ensembles() == []
ensemble = _import_ensemble(rips_instance, tmp_path, "ens_a")
ensembles = rips_instance.project.summary_ensembles()
assert len(ensembles) == 1
assert ensembles[0].summary_collection_name == ensemble.summary_collection_name
def test_project_summary_plot_collection(rips_instance, initialize_test):
collection = rips_instance.project.summary_plot_collection()
assert isinstance(collection, rips.SummaryPlotCollection)