#12773 Python: Add API for creating valve templates

- Add RimcValveTemplateCollection GRPC command with add_template method
- Support ICD, ICV, and AICD valve types with custom parameters
- Add setter methods for orifice diameter and flow coefficient
- Include Python tests and example demonstrating usage
- API: valve_templates.add_template(completion_type='AICD', orifice_diameter=7.3, flow_coefficient=0.2)
This commit is contained in:
Kristian Bendiksen
2025-08-19 08:35:45 +02:00
committed by Magne Sjaastad
parent c5326593c4
commit e58a8cd8cd
7 changed files with 372 additions and 2 deletions
@@ -169,6 +169,22 @@ void RimValveTemplate::setUserLabel( const QString& userLabel )
m_userLabel = userLabel;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimValveTemplate::setOrificeDiameter( double diameter )
{
m_orificeDiameter = diameter;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimValveTemplate::setFlowCoefficient( double coefficient )
{
m_flowCoefficient = coefficient;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -228,6 +244,22 @@ RimValveTemplate* RimValveTemplate::createAicdTemplate( const RiaOpmParserTools:
return aicdTemplate;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RimValveTemplate::defaultOrificeDiameter()
{
return 8.0;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RimValveTemplate::defaultFlowCoefficient()
{
return 0.7;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -49,9 +49,14 @@ public:
QString typeLabel() const;
QString fullLabel() const;
void setUserLabel( const QString& userLabel );
void setOrificeDiameter( double diameter );
void setFlowCoefficient( double coefficient );
void setAicdParameter( AICDParameters parameter, double value );
static double defaultOrificeDiameter();
static double defaultFlowCoefficient();
static RimValveTemplate* createAicdTemplate( const RiaOpmParserTools::AicdTemplateValues& aicdParameters, int templateNumber );
protected:
@@ -32,6 +32,7 @@ set(SOURCE_GROUP_HEADER_FILES
${CMAKE_CURRENT_LIST_DIR}/RimcPolygonCollection.h
${CMAKE_CURRENT_LIST_DIR}/RimcRegularSurface.h
${CMAKE_CURRENT_LIST_DIR}/RimcPerforationInterval.h
${CMAKE_CURRENT_LIST_DIR}/RimcValveTemplateCollection.h
)
set(SOURCE_GROUP_SOURCE_FILES
@@ -68,6 +69,7 @@ set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RimcPolygonCollection.cpp
${CMAKE_CURRENT_LIST_DIR}/RimcRegularSurface.cpp
${CMAKE_CURRENT_LIST_DIR}/RimcPerforationInterval.cpp
${CMAKE_CURRENT_LIST_DIR}/RimcValveTemplateCollection.cpp
)
list(APPEND CODE_HEADER_FILES ${SOURCE_GROUP_HEADER_FILES})
@@ -0,0 +1,105 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2025- Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RimcValveTemplateCollection.h"
#include "RimValveTemplate.h"
#include "RimValveTemplateCollection.h"
#include "cafPdmAbstractFieldScriptingCapability.h"
#include "cafPdmFieldScriptingCapability.h"
CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimValveTemplateCollection, RimcValveTemplateCollection_add_template, "AddTemplate" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimcValveTemplateCollection_add_template::RimcValveTemplateCollection_add_template( caf::PdmObjectHandle* self )
: caf::PdmObjectCreationMethod( self )
{
CAF_PDM_InitObject( "Add Valve Template", "", "", "Add a new valve template" );
CAF_PDM_InitScriptableField( &m_completionType,
"CompletionType",
caf::AppEnum<RiaDefines::WellPathComponentType>( RiaDefines::WellPathComponentType::ICD ),
"",
"",
"",
"Completion type (ICD, ICV, or AICD)" );
CAF_PDM_InitScriptableField( &m_orificeDiameter, "OrificeDiameter", RimValveTemplate::defaultOrificeDiameter(), "", "", "", "Orifice diameter" );
CAF_PDM_InitScriptableField( &m_flowCoefficient, "FlowCoefficient", RimValveTemplate::defaultFlowCoefficient(), "", "", "", "Flow coefficient" );
CAF_PDM_InitScriptableField( &m_userLabel, "UserLabel", QString( "" ), "", "", "", "User-defined label for the template" );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<caf::PdmObjectHandle*, QString> RimcValveTemplateCollection_add_template::execute()
{
RimValveTemplateCollection* valveTemplateCollection = self<RimValveTemplateCollection>();
if ( !valveTemplateCollection )
{
return std::unexpected( QString( "Invalid valve template collection" ) );
}
// Validate completion type
RiaDefines::WellPathComponentType completionType = m_completionType();
if ( completionType != RiaDefines::WellPathComponentType::ICD && completionType != RiaDefines::WellPathComponentType::ICV &&
completionType != RiaDefines::WellPathComponentType::AICD )
{
return std::unexpected( QString( "Invalid completion type. Must be ICD, ICV, or AICD" ) );
}
// Create new valve template
RimValveTemplate* newTemplate = new RimValveTemplate();
newTemplate->setType( completionType );
newTemplate->setUnitSystem( valveTemplateCollection->defaultUnitSystemType() );
// Set user label or generate default
QString userLabel = m_userLabel();
if ( userLabel.isEmpty() )
{
// Generate default label based on type and count
auto templates = valveTemplateCollection->valveTemplates();
int count = static_cast<int>( templates.size() ) + 1;
userLabel = QString( "Template %1" ).arg( count );
}
newTemplate->setUserLabel( userLabel );
// Update the name to reflect the type and user label
newTemplate->setName( newTemplate->fullLabel() );
// Set unit-specific defaults first, then override with user values
newTemplate->setDefaultValuesFromUnits();
newTemplate->setOrificeDiameter( m_orificeDiameter() );
newTemplate->setFlowCoefficient( m_flowCoefficient() );
// Add to collection
valveTemplateCollection->addValveTemplate( newTemplate );
valveTemplateCollection->updateAllRequiredEditors();
return newTemplate;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimcValveTemplateCollection_add_template::classKeywordReturnedType() const
{
return RimValveTemplate::classKeywordStatic();
}
@@ -0,0 +1,49 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2025- Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RiaDefines.h"
#include "RimValveTemplateCollection.h"
#include "cafAppEnum.h"
#include "cafPdmField.h"
#include "cafPdmObjectHandle.h"
#include "cafPdmObjectMethod.h"
#include <QString>
//==================================================================================================
///
//==================================================================================================
class RimcValveTemplateCollection_add_template : public caf::PdmObjectCreationMethod
{
CAF_PDM_HEADER_INIT;
public:
RimcValveTemplateCollection_add_template( caf::PdmObjectHandle* self );
std::expected<caf::PdmObjectHandle*, QString> execute() override;
QString classKeywordReturnedType() const override;
private:
caf::PdmField<caf::AppEnum<RiaDefines::WellPathComponentType>> m_completionType;
caf::PdmField<double> m_orificeDiameter;
caf::PdmField<double> m_flowCoefficient;
caf::PdmField<QString> m_userLabel;
};
@@ -0,0 +1,106 @@
#!/usr/bin/env python3
"""
Example demonstrating how to create valve templates programmatically using the Python API.
This example shows:
1. Creating different types of valve templates (ICD, ICV, AICD)
2. Setting custom parameters for orifice diameter and flow coefficient
3. Using the created templates in well completions
"""
import rips
def main():
# Connect to ResInsight instance
resinsight = rips.Instance.find()
print("Creating valve templates...")
# Get the valve template collection
valve_templates = resinsight.project.valve_templates()
# Check initial number of templates
initial_templates = valve_templates.valve_definitions()
print(f"Initial number of valve templates: {len(initial_templates)}")
# Create an ICD template with default values
print("\n1. Creating ICD template with default values")
icd_template = valve_templates.add_template(completion_type="ICD")
print(f" Created: {icd_template.name}")
print(f" Orifice Diameter: {icd_template.orifice_diameter}")
print(f" Flow Coefficient: {icd_template.flow_coefficient}")
# Create an ICV template with custom values
print("\n2. Creating ICV template with custom values")
icv_template = valve_templates.add_template(
completion_type="ICV",
orifice_diameter=12.5,
flow_coefficient=0.85,
user_label="Custom ICV for High Flow",
)
print(f" Created: {icv_template.name}")
print(f" Orifice Diameter: {icv_template.orifice_diameter}")
print(f" Flow Coefficient: {icv_template.flow_coefficient}")
# Create an AICD template (as suggested in the issue)
print("\n3. Creating AICD template as suggested in issue #12773")
aicd_template = valve_templates.add_template(
completion_type="AICD",
orifice_diameter=7.3,
flow_coefficient=0.2,
user_label="Issue Example AICD",
)
print(f" Created: {aicd_template.name}")
print(f" Orifice Diameter: {aicd_template.orifice_diameter}")
print(f" Flow Coefficient: {aicd_template.flow_coefficient}")
# Show all valve templates
current_templates = valve_templates.valve_definitions()
print(f"\nTotal valve templates now: {len(current_templates)}")
print("All valve templates:")
for i, template in enumerate(current_templates):
print(f" {i + 1}. {template.name}")
# Example of using the new template in a completion (requires a loaded case with well paths)
print("\n4. Example usage in well completion (requires loaded case)")
try:
# This assumes you have a case loaded with well paths
well_paths = resinsight.project.well_paths()
if well_paths:
well_path = well_paths[0]
print(f" Using well path: {well_path.name}")
# Add a perforation interval
perf_interval = well_path.append_perforation_interval(
start_md=2450, end_md=2500, diameter=0.25, skin_factor=0.1
)
# Add a valve using our new template
valve = perf_interval.add_valve(
template=aicd_template, start_md=2451, end_md=2499, valve_count=3
)
print(f" Created valve: {valve.name}")
print(f" Number of valves in interval: {len(perf_interval.valves())}")
else:
print(" No well paths available - skipping completion example")
print(" Load a case with well paths to see this in action")
except Exception as e:
print(f" Could not create completion example: {e}")
print(" This is expected if no case/well paths are loaded")
print("\nExample completed successfully!")
print("\nAPI Usage Summary:")
print("- valve_templates.add_template(completion_type='ICD') # Use defaults")
print(
"- valve_templates.add_template(completion_type='AICD', orifice_diameter=7.3, flow_coefficient=0.2)"
)
print(
"- valve_templates.add_template(completion_type='ICV', user_label='My Custom Template')"
)
if __name__ == "__main__":
main()
+73 -2
View File
@@ -206,7 +206,7 @@ def test_10k_intersection_add_well_perforation_interval_with_invalid_valves(
with pytest.raises(rips.RipsError):
# end_md < start_md
valve = perf_interval.add_valve(
perf_interval.add_valve(
template=valve_templates.valve_definitions()[0],
start_md=1000,
end_md=800,
@@ -215,9 +215,80 @@ def test_10k_intersection_add_well_perforation_interval_with_invalid_valves(
with pytest.raises(rips.RipsError):
# zero valves
valve = perf_interval.add_valve(
perf_interval.add_valve(
template=valve_templates.valve_definitions()[0],
start_md=400,
end_md=800,
valve_count=0,
)
def test_valve_template_creation(rips_instance, initialize_test):
"""Test creating valve templates through Python API"""
valve_templates = rips_instance.project.valve_templates()
# Check initial state - should have 3 default templates
initial_count = len(valve_templates.valve_definitions())
assert initial_count == 3
# Test creating ICD template with default values
icd_template = valve_templates.add_template(completion_type="ICD")
assert icd_template is not None
assert icd_template.orifice_diameter == 8.0 # Default value
assert icd_template.flow_coefficient == 0.7 # Default value
assert len(valve_templates.valve_definitions()) == initial_count + 1
# Test creating ICV template with custom values
icv_template = valve_templates.add_template(
completion_type="ICV",
orifice_diameter=10.5,
flow_coefficient=0.9,
user_label="Custom ICV Template",
)
assert icv_template is not None
assert icv_template.orifice_diameter == 10.5
assert icv_template.flow_coefficient == 0.9
assert "Custom ICV Template" in icv_template.name
assert len(valve_templates.valve_definitions()) == initial_count + 2
# Test creating AICD template
aicd_template = valve_templates.add_template(
completion_type="AICD",
orifice_diameter=7.3,
flow_coefficient=0.2,
user_label="Test AICD",
)
assert aicd_template is not None
assert aicd_template.orifice_diameter == 7.3
assert aicd_template.flow_coefficient == 0.2
assert "Test AICD" in aicd_template.name
assert len(valve_templates.valve_definitions()) == initial_count + 3
# Test that the new templates can be used in valves
case_root_path = dataroot.PATH + "/TEST10K_FLT_LGR_NNC"
case_path = case_root_path + "/TEST10K_FLT_LGR_NNC.EGRID"
rips_instance.project.load_case(path=case_path)
well_path_files = [case_root_path + "/wellpath_a.dev"]
rips_instance.project.import_well_paths(well_path_files)
wells = rips_instance.project.well_paths()
well_path = wells[0]
# Add perforation with our new template
perf_interval = well_path.append_perforation_interval(2450, 2460, 0.25, 0.1)
valve = perf_interval.add_valve(
template=icd_template,
start_md=2451,
end_md=2459,
valve_count=1,
)
assert valve is not None
assert len(perf_interval.valves()) == 1
def test_valve_template_invalid_completion_type(rips_instance, initialize_test):
"""Test error handling for invalid completion types"""
valve_templates = rips_instance.project.valve_templates()
# Test invalid completion type
with pytest.raises(rips.RipsError):
valve_templates.add_template(completion_type="INVALID_TYPE")