mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-26 07:00:17 -05:00
Python: Add scriptable Data Filters API for combined filters
Expose the case-level RimDataFilterCollection (introduced for
case-shared cell filters) to Python so scripts can build combined
filters with property-filter and IJK-range children without going
through a view.
Scriptable surface:
- RimEclipseCase.m_dataFilterCollection -> case.data_filter_collection()
- RimDataFilterCollection (object + Filters child array) -> filters()
- RimEclipsePropertyFilter (object + m_lowerBound + m_upperBound) ->
pf.lower_bound, pf.upper_bound
- RimCellRangeFilter (object + start_index_{i,j,k}, cell_count_{i,j,k})
New CAF object methods under ProjectDataModelCommands/:
- RimcDataFilterCollection: add_combined_filter(name, combine_mode)
- RimcCombinedFilter: add_property_filter(result_variable, result_type)
and add_range_filter(name, start_{i,j,k}, cell_count_{i,j,k}).
Bounds are not part of add_property_filter because the Python class
generator emits a bare nan literal as the default for double fields
initialised to quiet_NaN, which fails to import. Bounds are set via
the now-scriptable lower_bound/upper_bound fields after creation,
matching the update() pattern used elsewhere in the API.
RicEclipsePropertyFilterFeatureImpl::addPropertyFilterToCombinedFilter
now returns the new RimEclipsePropertyFilter* so the CAF method can
hand it back to Python. setDefaults is relaxed to be view-tolerant:
case-level filters have no Rim3dView ancestor and the eclipse case is
already wired by RimEclipsePropertyFilter::setCase via the owning
collection's onItemsChanged hook.
End-to-end coverage in tests/test_combined_filter.py: round-trip
create, mode toggle, property-filter add/mutate, range-filter
add/mutate. Example script in PythonExamples builds an AND combined
filter that gates PERMX between 100 and 20000 against a K=5..10
slice.
This commit is contained in:
1 parent
4ed6d70b73
commit
eb70d32eb1
13 files changed
+528
-24
No files matched your search
+55
@@ -0,0 +1,55 @@
|
||||
#######################################################
|
||||
#
|
||||
# Adds a case-level "Data Filters" combined filter that
|
||||
# AND-combines a PERMX property filter with a K-slice
|
||||
# range filter (K = 5..10), then shows how to mutate
|
||||
# the property filter's lower bound after creation.
|
||||
#
|
||||
# Assumes a case is already loaded in the running
|
||||
# ResInsight instance.
|
||||
#
|
||||
#######################################################
|
||||
|
||||
import rips
|
||||
|
||||
resinsight = rips.Instance.find()
|
||||
|
||||
# Use the first open case in the project. Add a guard so the script fails
|
||||
# loudly if the user runs it against an empty project.
|
||||
cases = resinsight.project.cases()
|
||||
assert cases, "No case loaded in ResInsight. Open a case before running this script."
|
||||
case = cases[0]
|
||||
|
||||
# Case-level Data Filters: shared by every view of the case.
|
||||
data_filters = case.data_filter_collection()
|
||||
|
||||
# AND-combined parent.
|
||||
combined = data_filters.add_combined_filter(
|
||||
name="PERMX 100..20000 AND K=5..10",
|
||||
combine_mode="AND",
|
||||
)
|
||||
|
||||
# PERMX property filter, then override the bounds populated by setToDefaultValues.
|
||||
permx = combined.add_property_filter(
|
||||
result_variable="PERMX",
|
||||
result_type="STATIC_NATIVE",
|
||||
)
|
||||
permx.lower_bound = 100.0
|
||||
permx.upper_bound = 20000.0
|
||||
permx.update()
|
||||
|
||||
# K-slice filter for K = 5..10. 1-based Eclipse indexing — start_k=5, count=6 covers 5..10.
|
||||
combined.add_range_filter(
|
||||
name="K=5..10",
|
||||
start_k=5,
|
||||
cell_count_k=6,
|
||||
)
|
||||
|
||||
# Mutate the lower bound later via field assignment. Re-read from the project
|
||||
# tree to confirm the change round-trips.
|
||||
permx.lower_bound = 250.0
|
||||
permx.update()
|
||||
|
||||
permx_refreshed = combined.filters()[0]
|
||||
print(f"PERMX lower_bound after update: {permx_refreshed.lower_bound}")
|
||||
print(f"PERMX upper_bound after update: {permx_refreshed.upper_bound}")
|
||||
@@ -0,0 +1,96 @@
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.insert(1, os.path.join(sys.path[0], "../../"))
|
||||
|
||||
import dataroot
|
||||
|
||||
|
||||
def _open_case(rips_instance):
|
||||
project = rips_instance.project.open(
|
||||
dataroot.PATH + "/TEST10K_FLT_LGR_NNC/10KWithWellLog.rsp"
|
||||
)
|
||||
return project.cases()[0]
|
||||
|
||||
|
||||
def test_combined_filter_round_trip(rips_instance, initialize_test):
|
||||
case = _open_case(rips_instance)
|
||||
|
||||
data_filters = case.data_filter_collection()
|
||||
assert data_filters is not None
|
||||
assert len(data_filters.filters()) == 0
|
||||
|
||||
combined = data_filters.add_combined_filter(name="My Combined", combine_mode="OR")
|
||||
assert combined is not None
|
||||
assert combined.name == "My Combined"
|
||||
assert combined.combine_mode == "OR"
|
||||
assert len(data_filters.filters()) == 1
|
||||
|
||||
|
||||
def test_combined_filter_add_property_filter(rips_instance, initialize_test):
|
||||
case = _open_case(rips_instance)
|
||||
data_filters = case.data_filter_collection()
|
||||
|
||||
combined = data_filters.add_combined_filter(combine_mode="AND")
|
||||
|
||||
pf = combined.add_property_filter(
|
||||
result_variable="SOIL", result_type="DYNAMIC_NATIVE"
|
||||
)
|
||||
assert pf is not None
|
||||
assert len(combined.filters()) == 1
|
||||
|
||||
pf2 = combined.add_property_filter(
|
||||
result_variable="PRESSURE", result_type="DYNAMIC_NATIVE"
|
||||
)
|
||||
assert pf2 is not None
|
||||
assert len(combined.filters()) == 2
|
||||
|
||||
# Bounds are populated by setToDefaultValues based on the result range; override
|
||||
# them via the scriptable fields.
|
||||
pf2.lower_bound = 150.0
|
||||
pf2.upper_bound = 250.0
|
||||
pf2.update()
|
||||
|
||||
refreshed = combined.filters()[1]
|
||||
assert refreshed.lower_bound == 150.0
|
||||
assert refreshed.upper_bound == 250.0
|
||||
|
||||
|
||||
def test_combined_filter_add_range_filter(rips_instance, initialize_test):
|
||||
case = _open_case(rips_instance)
|
||||
data_filters = case.data_filter_collection()
|
||||
|
||||
combined = data_filters.add_combined_filter()
|
||||
|
||||
rf = combined.add_range_filter(name="K-slice 5", start_k=5, cell_count_k=1)
|
||||
assert rf is not None
|
||||
assert "K-slice 5" in rf.name
|
||||
assert rf.start_index_k == 5
|
||||
assert rf.cell_count_k == 1
|
||||
|
||||
# Defaults from setDefaultValues should populate I/J using grid extent (>= 1).
|
||||
assert rf.start_index_i >= 1
|
||||
assert rf.start_index_j >= 1
|
||||
assert rf.cell_count_i >= 1
|
||||
assert rf.cell_count_j >= 1
|
||||
|
||||
# Mutate via field assignment + update.
|
||||
rf.cell_count_k = 3
|
||||
rf.update()
|
||||
|
||||
refreshed = combined.filters()[0]
|
||||
assert refreshed.cell_count_k == 3
|
||||
|
||||
|
||||
def test_combined_filter_mode_toggle(rips_instance, initialize_test):
|
||||
case = _open_case(rips_instance)
|
||||
data_filters = case.data_filter_collection()
|
||||
|
||||
combined = data_filters.add_combined_filter(combine_mode="OR")
|
||||
assert combined.combine_mode == "OR"
|
||||
|
||||
combined.combine_mode = "AND"
|
||||
combined.update()
|
||||
|
||||
refreshed = data_filters.filters()[0]
|
||||
assert refreshed.combine_mode == "AND"
|
||||
Reference in new issue
Block a user