From 75c1cedaec17d749c3d5bc951857bddfe303c65d Mon Sep 17 00:00:00 2001 From: Magne Sjaastad Date: Mon, 20 Apr 2026 15:07:17 +0200 Subject: [PATCH] #13806 Python: Add API for creating discrete grid properties Add a data_type parameter to set_active_cell_property, set_active_cell_property_async and set_grid_property so callers can upload INTEGER-typed discrete properties in addition to the existing FLOAT properties. Previously a discrete property required the result name to end with "NUM"; the explicit data type makes the intent independent of the name. The gRPC service now forwards the selected data type to RigCaseCellResultsData::createCategoryResult, and the Python tests cover both float and integer property uploads. --- .../RigCaseCellResultsData.cpp | 14 +++++++ .../RigCaseCellResultsData.h | 1 + GrpcInterface/GrpcProtos/Properties.proto | 6 +++ GrpcInterface/Python/rips/case.py | 12 ++++++ .../Python/rips/tests/test_properties.py | 37 +++++++++++++++++++ GrpcInterface/RiaGrpcPropertiesService.cpp | 8 ++++ 6 files changed, 78 insertions(+) diff --git a/ApplicationLibCode/ReservoirDataModel/RigCaseCellResultsData.cpp b/ApplicationLibCode/ReservoirDataModel/RigCaseCellResultsData.cpp index e3e8280c62..2cdbdbac34 100644 --- a/ApplicationLibCode/ReservoirDataModel/RigCaseCellResultsData.cpp +++ b/ApplicationLibCode/ReservoirDataModel/RigCaseCellResultsData.cpp @@ -1298,6 +1298,20 @@ void RigCaseCellResultsData::createResultEntry( const RigEclipseResultAddress& r findOrCreateScalarResultIndex( resultAddress, needsToBeStored ); } +//-------------------------------------------------------------------------------------------------- +/// Update the data type of an existing result entry. Equality/ordering on RigEclipseResultAddress +/// ignores dataType, so overwriting a property with a different dataType does not update the +/// persisted metadata unless we explicitly patch it here. +//-------------------------------------------------------------------------------------------------- +bool RigCaseCellResultsData::updateResultDataType( const RigEclipseResultAddress& resultAddress, RiaDefines::ResultDataType dataType ) +{ + size_t scalarResultIndex = findScalarResultIndexFromAddress( resultAddress ); + if ( scalarResultIndex == cvf::UNDEFINED_SIZE_T ) return false; + + m_resultInfos[scalarResultIndex].m_resultAddress.setDataType( dataType ); + return true; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ReservoirDataModel/RigCaseCellResultsData.h b/ApplicationLibCode/ReservoirDataModel/RigCaseCellResultsData.h index 64be7a7e3f..45a91dee67 100644 --- a/ApplicationLibCode/ReservoirDataModel/RigCaseCellResultsData.h +++ b/ApplicationLibCode/ReservoirDataModel/RigCaseCellResultsData.h @@ -140,6 +140,7 @@ public: bool hasResultEntry( const RigEclipseResultAddress& resultAddress ) const; bool isResultLoaded( const RigEclipseResultAddress& resultAddress ) const; void createResultEntry( const RigEclipseResultAddress& resultAddress, bool needsToBeStored ); + bool updateResultDataType( const RigEclipseResultAddress& resultAddress, RiaDefines::ResultDataType dataType ); void createPlaceholderResultEntries(); void computeDepthRelatedResults(); void computeCellVolumes(); diff --git a/GrpcInterface/GrpcProtos/Properties.proto b/GrpcInterface/GrpcProtos/Properties.proto index eaa10aeec0..9df10d0add 100644 --- a/GrpcInterface/GrpcProtos/Properties.proto +++ b/GrpcInterface/GrpcProtos/Properties.proto @@ -30,6 +30,11 @@ enum PropertyType { UNDEFINED = 999; } +enum PropertyDataType { + FLOAT = 0; + INTEGER = 1; +} + message AvailablePropertiesRequest { CaseRequest case_request = 1; PropertyType property_type = 2; @@ -45,6 +50,7 @@ message PropertyRequest { int32 time_step = 4; int32 grid_index = 5; PorosityModelType porosity_model = 6; + PropertyDataType data_type = 7; } message TimeStep { int32 index = 1; } diff --git a/GrpcInterface/Python/rips/case.py b/GrpcInterface/Python/rips/case.py index 17ff1f6260..5379891f67 100644 --- a/GrpcInterface/Python/rips/case.py +++ b/GrpcInterface/Python/rips/case.py @@ -830,6 +830,7 @@ def set_active_cell_property_async( property_name, time_step, porosity_model: str = "MATRIX_MODEL", + data_type: str = "FLOAT", ): """Set cell property for all active cells Async. Takes an iterator to the input values. For argument details, see :ref:`Result Definition ` @@ -839,15 +840,18 @@ def set_active_cell_property_async( property_name(str): name of an Eclipse property time_step(int): the time step for which to get the property for porosity_model(str): string enum + data_type(str): property data type ("FLOAT" or "INTEGER"). Use "INTEGER" to create a discrete/category property. """ property_type_enum = Properties_pb2.PropertyType.Value(property_type) porosity_model_enum = Case_pb2.PorosityModelType.Value(porosity_model) + data_type_enum = Properties_pb2.PropertyDataType.Value(data_type) request = Properties_pb2.PropertyRequest( case_request=self.__request(), property_type=property_type_enum, property_name=property_name, time_step=time_step, porosity_model=porosity_model_enum, + data_type=data_type_enum, ) request_iterator = self.__generate_property_input_iterator(values_iterator, request) @@ -862,6 +866,7 @@ def set_active_cell_property( property_name, time_step, porosity_model: str = "MATRIX_MODEL", + data_type: str = "FLOAT", ): """Set a cell property for all active cells. For argument details, see :ref:`Result Definition ` @@ -871,15 +876,18 @@ def set_active_cell_property( property_name(str): name of an Eclipse property time_step(int): the time step for which to get the property for porosity_model(str): string enum + data_type(str): property data type ("FLOAT" or "INTEGER"). Use "INTEGER" to create a discrete/category property. """ property_type_enum = Properties_pb2.PropertyType.Value(property_type) porosity_model_enum = Case_pb2.PorosityModelType.Value(porosity_model) + data_type_enum = Properties_pb2.PropertyDataType.Value(data_type) request = Properties_pb2.PropertyRequest( case_request=self.__request(), property_type=property_type_enum, property_name=property_name, time_step=time_step, porosity_model=porosity_model_enum, + data_type=data_type_enum, ) request_iterator = self.__generate_property_input_chunks(values, request) reply = self.__properties_stub.SetActiveCellProperty(request_iterator) @@ -896,6 +904,7 @@ def set_grid_property( time_step, grid_index=0, porosity_model: str = "MATRIX_MODEL", + data_type: str = "FLOAT", ): """Set a cell property for all grid cells. For argument details, see :ref:`Result Definition ` @@ -906,9 +915,11 @@ def set_grid_property( time_step(int): the time step for which to get the property for grid_index(int): index to the grid we're setting values for porosity_model(str): string enum + data_type(str): property data type ("FLOAT" or "INTEGER"). Use "INTEGER" to create a discrete/category property. """ property_type_enum = Properties_pb2.PropertyType.Value(property_type) porosity_model_enum = Case_pb2.PorosityModelType.Value(porosity_model) + data_type_enum = Properties_pb2.PropertyDataType.Value(data_type) request = Properties_pb2.PropertyRequest( case_request=self.__request(), property_type=property_type_enum, @@ -916,6 +927,7 @@ def set_grid_property( time_step=time_step, grid_index=grid_index, porosity_model=porosity_model_enum, + data_type=data_type_enum, ) request_iterator = self.__generate_property_input_chunks(values, request) reply = self.__properties_stub.SetGridProperty(request_iterator) diff --git a/GrpcInterface/Python/rips/tests/test_properties.py b/GrpcInterface/Python/rips/tests/test_properties.py index 191cb1f33e..70c7ae5c57 100644 --- a/GrpcInterface/Python/rips/tests/test_properties.py +++ b/GrpcInterface/Python/rips/tests/test_properties.py @@ -98,6 +98,43 @@ def test_10k_PoroPermX(rips_instance, initialize_test): checkResults(poro, permx, poroPermX) +def test_10k_set_integer_active_cell_property(rips_instance, initialize_test): + casePath = dataroot.PATH + "/TEST10K_FLT_LGR_NNC/TEST10K_FLT_LGR_NNC.EGRID" + case = rips_instance.project.load_case(path=casePath) + + results = case.active_cell_property("STATIC_NATIVE", "PORO", 0) + integer_values = [int(v * 100) for v in results] + + # A name that does not end with "NUM" - only the data_type flag should + # cause this to be treated as a discrete/category property. + case.set_active_cell_property( + integer_values, "GENERATED", "MY_DISCRETE_ACTIVE", 0, data_type="INTEGER" + ) + + round_trip = case.active_cell_property("GENERATED", "MY_DISCRETE_ACTIVE", 0) + assert len(round_trip) == len(integer_values) + for expected, actual in zip(integer_values, round_trip): + assert expected == int(actual) + + +def test_10k_set_integer_grid_property(rips_instance, initialize_test): + casePath = dataroot.PATH + "/TEST10K_FLT_LGR_NNC/TEST10K_FLT_LGR_NNC.EGRID" + case = rips_instance.project.load_case(path=casePath) + + grid = case.grid() + grid_cell_count = grid.cell_count() + integer_values = [i % 4 for i in range(grid_cell_count)] + + case.set_grid_property( + integer_values, "GENERATED", "MY_DISCRETE_GRID", 0, data_type="INTEGER" + ) + + round_trip = case.grid_property("GENERATED", "MY_DISCRETE_GRID", 0) + assert len(round_trip) == len(integer_values) + for expected, actual in zip(integer_values, round_trip): + assert expected == int(actual) + + def test_exportPropertyInView(rips_instance, initialize_test): case_path = dataroot.PATH + "/TEST10K_FLT_LGR_NNC/TEST10K_FLT_LGR_NNC.EGRID" case = rips_instance.project.load_case(case_path) diff --git a/GrpcInterface/RiaGrpcPropertiesService.cpp b/GrpcInterface/RiaGrpcPropertiesService.cpp index 932acd9026..119fb6d1ea 100644 --- a/GrpcInterface/RiaGrpcPropertiesService.cpp +++ b/GrpcInterface/RiaGrpcPropertiesService.cpp @@ -104,10 +104,18 @@ public: m_resultAddress = RigEclipseResultAddress( resultType, QString::fromStdString( request->property_name() ) ); + auto dataType = ( request->data_type() == rips::PropertyDataType::INTEGER ) + ? RiaDefines::ResultDataType::INTEGER + : RiaDefines::ResultDataType::FLOAT; + m_resultAddress.setDataType( dataType ); + if ( resultData->ensureKnownResultLoaded( m_resultAddress ) ) { if ( timeStep < resultData->timeStepCount( m_resultAddress ) ) { + // Equality on RigEclipseResultAddress ignores dataType, so an existing entry + // keeps its original dataType. Patch it when overwriting from the client. + if ( m_clientStreamer ) resultData->updateResultDataType( m_resultAddress, dataType ); initResultAccess( caseData, request->grid_index(), m_porosityModel, timeStep, m_resultAddress ); return grpc::Status::OK; }