mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-25 22:50:25 -05:00
Python: Add filtered_cells API for evaluating a data filter on a case
Adds case.filtered_cells(filter, time_step, grid_index) which applies any RimCellFilter to an Eclipse case and returns a per-cell 0/1 mask. The mask length and ordering match case.grid_property(..., grid_index), so callers can combine the two vectors element-wise. Implemented as a scriptable Rimc method (PdmVoidObjectMethod) on RimEclipseCase that writes the mask via the existing key-value store, with a thin Python wrapper mirroring the visible_cells() pattern. No proto or gRPC service changes.
This commit is contained in:
1 parent
c9456bba62
commit
ddcfef736b
4 files changed
+233
-1
No files matched your search
@@ -1579,3 +1579,36 @@ def replace_corner_point_grid(
|
||||
zcorn_key=zcorn_key,
|
||||
actnum_key=actnum_key,
|
||||
)
|
||||
|
||||
|
||||
@add_method(EclipseCase)
|
||||
def filtered_cells(self, filter, time_step: int = 0, grid_index: int = 0) -> List[int]:
|
||||
"""Apply a cell filter to this case and return a per-cell 0/1 mask.
|
||||
|
||||
The returned list has the same length and ordering as
|
||||
``case.grid_property("STATIC_NATIVE", "PERMX", grid_index)``, so the two
|
||||
vectors can be combined element-wise.
|
||||
|
||||
Arguments:
|
||||
filter (CellFilter): The cell filter to apply (e.g. a CombinedFilter
|
||||
obtained from ``case.data_filter_collection().filters()``).
|
||||
time_step (int): Time step index used by property-filter children.
|
||||
grid_index (int): Grid index (0 = main grid, >0 = LGRs).
|
||||
|
||||
Returns:
|
||||
List[int]: 1 if the cell passes the filter, 0 otherwise.
|
||||
"""
|
||||
mask_key = "{}_{}".format(uuid.uuid4(), "filtered_cells")
|
||||
project = self.ancestor(rips.project.Project)
|
||||
if not project:
|
||||
raise RuntimeError("Unable to get project from case")
|
||||
try:
|
||||
self.filtered_cells_internal(
|
||||
filter=filter,
|
||||
mask_key=mask_key,
|
||||
time_step=time_step,
|
||||
grid_index=grid_index,
|
||||
)
|
||||
return [int(v) for v in project.key_values(mask_key)]
|
||||
finally:
|
||||
project.remove_key_values(mask_key)
|
||||
@@ -0,0 +1,100 @@
|
||||
import sys
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
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_filtered_cells_length_matches_grid_property(rips_instance, initialize_test):
|
||||
"""Returned mask length matches grid_property length for the same grid."""
|
||||
case = _open_case(rips_instance)
|
||||
|
||||
combined = case.data_filter_collection().add_combined_filter(combine_mode="AND")
|
||||
mask = case.filtered_cells(filter=combined, time_step=0, grid_index=0)
|
||||
permx = case.grid_property("STATIC_NATIVE", "PERMX", 0)
|
||||
|
||||
assert len(mask) == len(permx)
|
||||
assert len(mask) > 0
|
||||
|
||||
|
||||
def test_filtered_cells_empty_combined_passes_all(rips_instance, initialize_test):
|
||||
"""An empty combined filter (no children) keeps every cell visible."""
|
||||
case = _open_case(rips_instance)
|
||||
|
||||
combined = case.data_filter_collection().add_combined_filter(combine_mode="AND")
|
||||
mask = case.filtered_cells(filter=combined)
|
||||
|
||||
assert all(v == 1 for v in mask)
|
||||
|
||||
|
||||
def test_filtered_cells_range_filter_narrows(rips_instance, initialize_test):
|
||||
"""A K-slice range filter restricts the mask to that slab."""
|
||||
case = _open_case(rips_instance)
|
||||
|
||||
combined = case.data_filter_collection().add_combined_filter(combine_mode="AND")
|
||||
rf = combined.add_range_filter(name="K=5", start_k=5, cell_count_k=1)
|
||||
rf.update()
|
||||
|
||||
dims = case.grid(0).dimensions()
|
||||
expected_in_slab = dims.i * dims.j
|
||||
|
||||
mask = case.filtered_cells(filter=combined, grid_index=0)
|
||||
assert sum(mask) == expected_in_slab
|
||||
|
||||
|
||||
def test_filtered_cells_property_filter_pins_ordering(rips_instance, initialize_test):
|
||||
"""Property-filter mask agrees with the grid_property values cell-by-cell.
|
||||
|
||||
For a PERMX in [lower, upper] filter, mask[i]==1 iff lower <= permx[i] <= upper.
|
||||
Identical ordering across the two vectors is the whole point of this API.
|
||||
"""
|
||||
case = _open_case(rips_instance)
|
||||
|
||||
permx = case.grid_property("STATIC_NATIVE", "PERMX", 0)
|
||||
|
||||
finite = [v for v in permx if v == v] # NaN-safe
|
||||
assert finite, "PERMX has no finite values in this case"
|
||||
lower = sorted(finite)[len(finite) // 4]
|
||||
upper = sorted(finite)[(3 * len(finite)) // 4]
|
||||
|
||||
combined = case.data_filter_collection().add_combined_filter(combine_mode="AND")
|
||||
pf = combined.add_property_filter(
|
||||
result_variable="PERMX", result_type="STATIC_NATIVE"
|
||||
)
|
||||
pf.lower_bound = lower
|
||||
pf.upper_bound = upper
|
||||
pf.update()
|
||||
|
||||
mask = case.filtered_cells(filter=combined, grid_index=0)
|
||||
assert len(mask) == len(permx)
|
||||
|
||||
mismatches = 0
|
||||
for i, (m, v) in enumerate(zip(mask, permx)):
|
||||
in_range = lower <= v <= upper
|
||||
if bool(m) != bool(in_range):
|
||||
mismatches += 1
|
||||
if mismatches <= 3:
|
||||
print(
|
||||
f" mismatch at i={i}: mask={m}, permx={v}, range=[{lower},{upper}]"
|
||||
)
|
||||
assert mismatches == 0
|
||||
|
||||
|
||||
def test_filtered_cells_null_filter_rejected(rips_instance, initialize_test):
|
||||
"""Calling the internal method with a null filter is rejected by C++."""
|
||||
case = _open_case(rips_instance)
|
||||
|
||||
with pytest.raises(Exception):
|
||||
case.filtered_cells_internal(
|
||||
filter=None, mask_key="ignored", time_step=0, grid_index=0
|
||||
)
|
||||
Reference in new issue
Block a user