From 7e299c4841355a9533e96858510585a988dc0f51 Mon Sep 17 00:00:00 2001 From: Magne Sjaastad Date: Mon, 17 Nov 2025 11:54:02 +0100 Subject: [PATCH] Add support for more MSW tables in Python * Use caf::VecIjk0 * Adds MSW tables to the gRPC interface * Adds unified completion data retrieval * Add unite tests for MSW data --- .../RicMswSegmentCellIntersection.cpp | 10 +- .../RicMswSegmentCellIntersection.h | 21 +- .../RicMswTableDataTools.cpp | 8 +- .../RicMswTableFormatterTools.cpp | 4 +- .../RicWellPathExportMswCompletionsImpl.cpp | 8 +- .../RicWellPathExportMswTableData.cpp | 10 +- .../CompletionsMsw/RigMswTableRows.h | 6 +- ApplicationLibCode/UnitTests/CMakeLists.txt | 1 + .../UnitTests/RiaGrpcWellPathService-Test.cpp | 605 ++++++++++++++++++ GrpcInterface/CMakeLists.txt | 2 + .../GrpcProtos/SimulatorTables.proto | 86 +++ .../rips/tests/test_msw_completion_data.py | 232 +++++++ .../Python/rips/well_path_collection.py | 75 ++- GrpcInterface/RiaGrpcWellPathService.cpp | 162 +++-- GrpcInterface/RiaGrpcWellPathService.h | 14 +- .../RiaWellPathDataToGrpcConverter.cpp | 314 +++++++++ .../RiaWellPathDataToGrpcConverter.h | 48 ++ 17 files changed, 1493 insertions(+), 113 deletions(-) create mode 100644 ApplicationLibCode/UnitTests/RiaGrpcWellPathService-Test.cpp create mode 100644 GrpcInterface/Python/rips/tests/test_msw_completion_data.py create mode 100644 GrpcInterface/RiaWellPathDataToGrpcConverter.cpp create mode 100644 GrpcInterface/RiaWellPathDataToGrpcConverter.h diff --git a/ApplicationLibCode/Commands/CompletionExportCommands/RicMswSegmentCellIntersection.cpp b/ApplicationLibCode/Commands/CompletionExportCommands/RicMswSegmentCellIntersection.cpp index 8e062c0b59..a76fcd6ec7 100644 --- a/ApplicationLibCode/Commands/CompletionExportCommands/RicMswSegmentCellIntersection.cpp +++ b/ApplicationLibCode/Commands/CompletionExportCommands/RicMswSegmentCellIntersection.cpp @@ -21,10 +21,10 @@ //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -RicMswSegmentCellIntersection::RicMswSegmentCellIntersection( const QString& gridName, - size_t globalCellIndex, - const cvf::Vec3st& gridLocalCellIJK, - const cvf::Vec3d& lengthsInCell ) +RicMswSegmentCellIntersection::RicMswSegmentCellIntersection( const QString& gridName, + size_t globalCellIndex, + const caf::VecIjk0& gridLocalCellIJK, + const cvf::Vec3d& lengthsInCell ) : m_gridName( gridName ) , m_globalCellIndex( globalCellIndex ) , m_gridLocalCellIJK( gridLocalCellIJK ) @@ -51,7 +51,7 @@ size_t RicMswSegmentCellIntersection::globalCellIndex() const //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -cvf::Vec3st RicMswSegmentCellIntersection::gridLocalCellIJK() const +caf::VecIjk0 RicMswSegmentCellIntersection::gridLocalCellIJK() const { return m_gridLocalCellIJK; } diff --git a/ApplicationLibCode/Commands/CompletionExportCommands/RicMswSegmentCellIntersection.h b/ApplicationLibCode/Commands/CompletionExportCommands/RicMswSegmentCellIntersection.h index 5276ba96d6..5d26aef77d 100644 --- a/ApplicationLibCode/Commands/CompletionExportCommands/RicMswSegmentCellIntersection.h +++ b/ApplicationLibCode/Commands/CompletionExportCommands/RicMswSegmentCellIntersection.h @@ -17,9 +17,12 @@ ///////////////////////////////////////////////////////////////////////////////// #pragma once +#include "cafVecIjk.h" + #include "cvfVector3.h" #include + #include #include @@ -29,18 +32,18 @@ class RicMswSegmentCellIntersection { public: - RicMswSegmentCellIntersection( const QString& gridName, // Pass in empty string for main grid - size_t globalCellIndex, - const cvf::Vec3st& gridLocalCellIJK, - const cvf::Vec3d& lengthsInCell ); + RicMswSegmentCellIntersection( const QString& gridName, // Pass in empty string for main grid + size_t globalCellIndex, + const caf::VecIjk0& gridLocalCellIJK, + const cvf::Vec3d& lengthsInCell ); const QString& gridName() const; size_t globalCellIndex() const; - cvf::Vec3st gridLocalCellIJK() const; + caf::VecIjk0 gridLocalCellIJK() const; const cvf::Vec3d& lengthsInCell() const; private: - QString m_gridName; - size_t m_globalCellIndex; - cvf::Vec3st m_gridLocalCellIJK; - cvf::Vec3d m_lengthsInCell; + QString m_gridName; + size_t m_globalCellIndex; + caf::VecIjk0 m_gridLocalCellIJK; + cvf::Vec3d m_lengthsInCell; }; diff --git a/ApplicationLibCode/Commands/CompletionExportCommands/RicMswTableDataTools.cpp b/ApplicationLibCode/Commands/CompletionExportCommands/RicMswTableDataTools.cpp index fddf08633b..ffbb0d09a8 100644 --- a/ApplicationLibCode/Commands/CompletionExportCommands/RicMswTableDataTools.cpp +++ b/ApplicationLibCode/Commands/CompletionExportCommands/RicMswTableDataTools.cpp @@ -587,10 +587,10 @@ void RicMswTableDataTools::collectCompsegDataByType( RigMswTableData& { CompsegsRow row; - cvf::Vec3st ijk = intersection->gridLocalCellIJK(); - row.i = ijk.x() + 1; // Convert to 1-based - row.j = ijk.y() + 1; - row.k = ijk.z() + 1; + auto ijk = intersection->gridLocalCellIJK().toOneBased(); + row.i = ijk.i(); + row.j = ijk.j(); + row.k = ijk.k(); int branchNumber = -1; if ( completion ) branchNumber = completion->branchNumber(); diff --git a/ApplicationLibCode/Commands/CompletionExportCommands/RicMswTableFormatterTools.cpp b/ApplicationLibCode/Commands/CompletionExportCommands/RicMswTableFormatterTools.cpp index d70dd4bd28..12d508e13a 100644 --- a/ApplicationLibCode/Commands/CompletionExportCommands/RicMswTableFormatterTools.cpp +++ b/ApplicationLibCode/Commands/CompletionExportCommands/RicMswTableFormatterTools.cpp @@ -321,8 +321,8 @@ void RicMswTableFormatterTools::generateCompsegTable( RifTextDataTableFormatter& formatter.add( intersection->gridName() ); } - cvf::Vec3st ijk = intersection->gridLocalCellIJK(); - formatter.addOneBasedCellIndex( ijk.x() ).addOneBasedCellIndex( ijk.y() ).addOneBasedCellIndex( ijk.z() ); + auto ijk = intersection->gridLocalCellIJK().toOneBased(); + formatter.add( ijk.i() ).add( ijk.j() ).add( ijk.k() ); int branchNumber = -1; if ( completion ) branchNumber = completion->branchNumber(); diff --git a/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportMswCompletionsImpl.cpp b/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportMswCompletionsImpl.cpp index f0cac5e5db..9fcf2c0a7c 100644 --- a/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportMswCompletionsImpl.cpp +++ b/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportMswCompletionsImpl.cpp @@ -731,7 +731,7 @@ void RicWellPathExportMswCompletionsImpl::generateFishbonesMswExportInfo( const size_t i, j, k; localGrid->ijkFromCellIndex( localGridCellIndex, &i, &j, &k ); - cvf::Vec3st localIJK( i, j, k ); + caf::VecIjk0 localIJK( i, j, k ); auto mswIntersect = std::make_shared( gridName, intersection.globCellIndex, @@ -1622,7 +1622,7 @@ void RicWellPathExportMswCompletionsImpl::assignFishbonesLateralIntersections( c auto intersection = std::make_shared( gridName, cellIntInfo.globCellIndex, - cvf::Vec3st( i, j, k ), + caf::VecIjk0( i, j, k ), cellIntInfo.intersectionLengthsInCellCS ); subSegment->addIntersection( std::move( intersection ) ); completion->addSegment( std::move( subSegment ) ); @@ -1667,7 +1667,7 @@ void RicWellPathExportMswCompletionsImpl::assignFractureCompletionsToCellSegment *foundSubGridIntersections = true; } - cvf::Vec3st localIJK( cell.localCellIndexI(), cell.localCellIndexJ(), cell.localCellIndexK() ); + caf::VecIjk0 localIJK( cell.localCellIndexI(), cell.localCellIndexJ(), cell.localCellIndexK() ); auto intersection = std::make_shared( cell.lgrName(), cell.globalCellIndex(), localIJK, cvf::Vec3d::ZERO ); @@ -1746,7 +1746,7 @@ void RicWellPathExportMswCompletionsImpl::assignPerforationIntersections( const if ( cell.globalCellIndex() != currCellId ) continue; - cvf::Vec3st localIJK( cell.localCellIndexI(), cell.localCellIndexJ(), cell.localCellIndexK() ); + caf::VecIjk0 localIJK( cell.localCellIndexI(), cell.localCellIndexJ(), cell.localCellIndexK() ); auto intersection = std::make_shared( cell.lgrName(), cell.globalCellIndex(), diff --git a/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportMswTableData.cpp b/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportMswTableData.cpp index 5f3a8fe307..249c720946 100644 --- a/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportMswTableData.cpp +++ b/ApplicationLibCode/Commands/CompletionExportCommands/RicWellPathExportMswTableData.cpp @@ -882,7 +882,7 @@ void RicWellPathExportMswTableData::generateFishbonesMswExportInfo( const RimEcl size_t i, j, k; localGrid->ijkFromCellIndex( localGridCellIndex, &i, &j, &k ); - cvf::Vec3st localIJK( i, j, k ); + caf::VecIjk0 localIJK( i, j, k ); auto mswIntersect = std::make_shared( gridName, intersection.globCellIndex, @@ -1053,7 +1053,7 @@ void RicWellPathExportMswTableData::appendFishbonesMswExportInfo( const RimEclip size_t i, j, k; localGrid->ijkFromCellIndex( localGridCellIndex, &i, &j, &k ); - cvf::Vec3st localIJK( i, j, k ); + caf::VecIjk0 localIJK( i, j, k ); auto mswIntersect = std::make_shared( gridName, intersection.globCellIndex, @@ -1993,7 +1993,7 @@ void RicWellPathExportMswTableData::assignFishbonesLateralIntersections( const R auto intersection = std::make_shared( gridName, cellIntInfo.globCellIndex, - cvf::Vec3st( i, j, k ), + caf::VecIjk0( i, j, k ), cellIntInfo.intersectionLengthsInCellCS ); subSegment->addIntersection( std::move( intersection ) ); completion->addSegment( std::move( subSegment ) ); @@ -2038,7 +2038,7 @@ void RicWellPathExportMswTableData::assignFractureCompletionsToCellSegment( cons *foundSubGridIntersections = true; } - cvf::Vec3st localIJK( cell.localCellIndexI(), cell.localCellIndexJ(), cell.localCellIndexK() ); + caf::VecIjk0 localIJK( cell.localCellIndexI(), cell.localCellIndexJ(), cell.localCellIndexK() ); auto intersection = std::make_shared( cell.lgrName(), cell.globalCellIndex(), localIJK, cvf::Vec3d::ZERO ); @@ -2117,7 +2117,7 @@ void RicWellPathExportMswTableData::assignPerforationIntersections( const std::v if ( cell.globalCellIndex() != currCellId ) continue; - cvf::Vec3st localIJK( cell.localCellIndexI(), cell.localCellIndexJ(), cell.localCellIndexK() ); + caf::VecIjk0 localIJK( cell.localCellIndexI(), cell.localCellIndexJ(), cell.localCellIndexK() ); auto intersection = std::make_shared( cell.lgrName(), cell.globalCellIndex(), diff --git a/ApplicationLibCode/ReservoirDataModel/CompletionsMsw/RigMswTableRows.h b/ApplicationLibCode/ReservoirDataModel/CompletionsMsw/RigMswTableRows.h index d973dfdb31..70c4c88f6c 100644 --- a/ApplicationLibCode/ReservoirDataModel/CompletionsMsw/RigMswTableRows.h +++ b/ApplicationLibCode/ReservoirDataModel/CompletionsMsw/RigMswTableRows.h @@ -74,9 +74,9 @@ struct CompsegsRow { // type variableName; // Opm::ParserKeywords::COMPSEGS::... - size_t i; // I - size_t j; // J - size_t k; // K + size_t i; // I (1-based) + size_t j; // J (1-based) + size_t k; // K (1-based) int branch; // IBRANCH double distanceStart; // LENGTH1 double distanceEnd; // LENGTH2 diff --git a/ApplicationLibCode/UnitTests/CMakeLists.txt b/ApplicationLibCode/UnitTests/CMakeLists.txt index 26e2c989f6..6ace34f381 100644 --- a/ApplicationLibCode/UnitTests/CMakeLists.txt +++ b/ApplicationLibCode/UnitTests/CMakeLists.txt @@ -127,6 +127,7 @@ set(SOURCE_UNITTEST_FILES if(RESINSIGHT_ENABLE_GRPC) list(APPEND SOURCE_UNITTEST_FILES ${CMAKE_CURRENT_LIST_DIR}/RiaGrpcInterface-Test.cpp + ${CMAKE_CURRENT_LIST_DIR}/RiaGrpcWellPathService-Test.cpp ) endif(RESINSIGHT_ENABLE_GRPC) diff --git a/ApplicationLibCode/UnitTests/RiaGrpcWellPathService-Test.cpp b/ApplicationLibCode/UnitTests/RiaGrpcWellPathService-Test.cpp new file mode 100644 index 0000000000..9cd54b9fb5 --- /dev/null +++ b/ApplicationLibCode/UnitTests/RiaGrpcWellPathService-Test.cpp @@ -0,0 +1,605 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "gtest/gtest.h" + +#include "CompletionsMsw/RigMswTableData.h" +#include "RiaDefines.h" +#include "RiaWellPathDataToGrpcConverter.h" +#include "RigCompletionData.h" +#include "RigCompletionDataGridCell.h" +#include "RimWellPathCompletionSettings.h" + +#include "SimulatorTables.pb.h" + +#include + +//-------------------------------------------------------------------------------------------------- +/// Unit test to ensure all fields from RigCompletionData are properly copied to gRPC SimulatorCompdatEntry +//-------------------------------------------------------------------------------------------------- +TEST( RiaGrpcWellPathServiceTest, copyCompdatToGrpcCopiesAllFields ) +{ + // Create test input data with all possible values set + RigCompletionDataGridCell gridCell; // Default constructor initializes i,j,k to 0 + RigCompletionData inputData( "WELL_A", gridCell, 100.0 ); + + // Set all optional fields to non-default values + inputData.setTransmissibility( 1.23 ); + inputData.setDiameter( 0.311 ); + inputData.setKh( 45.6 ); + inputData.setDFactor( 0.789 ); + + // Use the transmissibility setting method to also set skin factor + inputData.setCombinedValuesExplicitTrans( 1.23, // transmissibility + 45.6, // kh + 0.789, // dFactor + 2.5, // skinFactor + 0.311, // diameter + RigCompletionData::CellDirection::DIR_I, // direction + RigCompletionData::CompletionType::PERFORATION // completionType + ); + + // Set MD depth range + inputData.setDepthRange( 2000.5, 2010.8 ); + + // Add metadata + inputData.addMetadata( "TestName", "Test Comment" ); + + // Create output gRPC message + rips::SimulatorCompdatEntry grpcData; + + // Call the function under test + RiaWellPathDataToGrpcConverter::copyCompdatToGrpc( inputData, &grpcData ); + + // Verify all fields are copied correctly + + // Basic fields + EXPECT_EQ( grpcData.well_name(), "WELL_A" ); + EXPECT_EQ( grpcData.comment(), "TestName: Test Comment" ); // metadata is concatenated + + // Grid coordinates (should be converted to 1-based) + // Default constructor gives i,j,k=0, so after +1 conversion we get 1,1,1 + EXPECT_EQ( grpcData.grid_i(), 1 ); // 0+1 (1-based conversion) + EXPECT_EQ( grpcData.grid_j(), 1 ); // 0+1 + EXPECT_EQ( grpcData.upper_k(), 1 ); // 0+1 + EXPECT_EQ( grpcData.lower_k(), 1 ); // 0+1 (same as upper_k for single cell) + + // Status field + EXPECT_EQ( grpcData.open_shut_flag(), "OPEN" ); + + // Optional numerical fields (only set if not default value) + EXPECT_TRUE( grpcData.has_transmissibility() ); + EXPECT_DOUBLE_EQ( grpcData.transmissibility(), 1.23 ); + + EXPECT_TRUE( grpcData.has_diameter() ); + EXPECT_DOUBLE_EQ( grpcData.diameter(), 0.311 ); + + EXPECT_TRUE( grpcData.has_kh() ); + EXPECT_DOUBLE_EQ( grpcData.kh(), 45.6 ); + + EXPECT_TRUE( grpcData.has_skin_factor() ); + EXPECT_DOUBLE_EQ( grpcData.skin_factor(), 2.5 ); + + EXPECT_TRUE( grpcData.has_d_factor() ); + EXPECT_DOUBLE_EQ( grpcData.d_factor(), 0.789 ); + + // Direction field + EXPECT_TRUE( grpcData.has_direction() ); + EXPECT_EQ( grpcData.direction(), "X" ); // DIR_I maps to "X" + + // MD depth range + EXPECT_TRUE( grpcData.has_start_md() ); + EXPECT_DOUBLE_EQ( grpcData.start_md(), 2000.5 ); + + EXPECT_TRUE( grpcData.has_end_md() ); + EXPECT_DOUBLE_EQ( grpcData.end_md(), 2010.8 ); +} + +//-------------------------------------------------------------------------------------------------- +/// Unit test to ensure default values are not copied to gRPC message +//-------------------------------------------------------------------------------------------------- +TEST( RiaGrpcWellPathServiceTest, copyCompdatToGrpcSkipsDefaultValues ) +{ + // Create test input data with default values + RigCompletionDataGridCell gridCell; // Default constructor initializes i,j,k to 0 + RigCompletionData inputData( "WELL_B", gridCell, 200.0 ); + + // Don't set any optional fields (they should remain at default values) + // Add minimal metadata + inputData.addMetadata( "MinimalTest", "" ); + + // Create output gRPC message + rips::SimulatorCompdatEntry grpcData; + + // Call the function under test + RiaWellPathDataToGrpcConverter::copyCompdatToGrpc( inputData, &grpcData ); + + // Verify required fields are set + EXPECT_EQ( grpcData.well_name(), "WELL_B" ); + EXPECT_EQ( grpcData.comment(), "MinimalTest: " ); + // Grid coordinates from default constructor: 0+1=1 (1-based) + EXPECT_EQ( grpcData.grid_i(), 1 ); + EXPECT_EQ( grpcData.grid_j(), 1 ); + EXPECT_EQ( grpcData.upper_k(), 1 ); + EXPECT_EQ( grpcData.lower_k(), 1 ); + EXPECT_EQ( grpcData.open_shut_flag(), "OPEN" ); + + // Verify optional fields with default values are NOT set + EXPECT_FALSE( grpcData.has_saturation() ); + EXPECT_FALSE( grpcData.has_transmissibility() ); + EXPECT_FALSE( grpcData.has_diameter() ); + EXPECT_FALSE( grpcData.has_kh() ); + EXPECT_FALSE( grpcData.has_skin_factor() ); + EXPECT_FALSE( grpcData.has_d_factor() ); + + // Direction should still be set (it has a default string value) + EXPECT_TRUE( grpcData.has_direction() ); + + // MD values should not be set (no depth range was specified) + EXPECT_FALSE( grpcData.has_start_md() ); + EXPECT_FALSE( grpcData.has_end_md() ); +} + +//-------------------------------------------------------------------------------------------------- +/// Unit test to verify all proto fields are covered by checking against proto definition +//-------------------------------------------------------------------------------------------------- +TEST( RiaGrpcWellPathServiceTest, copyCompdatToGrpcCoversAllProtoFields ) +{ + // This test documents all fields in SimulatorCompdatEntry to ensure we don't miss any + // when updating the proto definition. If this test fails, update copyCompdatToGrpc accordingly. + + rips::SimulatorCompdatEntry grpcData; + + // Verify all fields from SimulatorTables.proto SimulatorCompdatEntry message exist: + // Based on proto definition (lines 24-41 in SimulatorTables.proto): + // string well_name = 1; ✓ Covered + // int32 grid_i = 2; ✓ Covered + // int32 grid_j = 3; ✓ Covered + // int32 upper_k = 4; ✓ Covered + // int32 lower_k = 5; ✓ Covered + // string open_shut_flag = 6; ✓ Covered + // optional double saturation = 7; ✓ Covered (not used in current implementation) + // optional double transmissibility = 8; ✓ Covered + // optional double diameter = 9; ✓ Covered + // optional double kh = 10; ✓ Covered + // optional double skin_factor = 11; ✓ Covered + // optional double d_factor = 12; ✓ Covered + // optional string direction = 13; ✓ Covered + // optional double start_md = 14; ✓ Covered + // optional double end_md = 15; ✓ Covered + // optional string comment = 16; ✓ Covered + + // Test that we can access all fields (this will catch any missing proto fields) + grpcData.set_well_name( "TEST" ); + grpcData.set_grid_i( 1 ); + grpcData.set_grid_j( 2 ); + grpcData.set_upper_k( 3 ); + grpcData.set_lower_k( 4 ); + grpcData.set_open_shut_flag( "OPEN" ); + grpcData.set_saturation( 0.5 ); + grpcData.set_transmissibility( 1.0 ); + grpcData.set_diameter( 0.2 ); + grpcData.set_kh( 10.0 ); + grpcData.set_skin_factor( 1.5 ); + grpcData.set_d_factor( 0.1 ); + grpcData.set_direction( "X" ); + grpcData.set_start_md( 100.0 ); + grpcData.set_end_md( 200.0 ); + grpcData.set_comment( "Test comment" ); + + // If we got here without compilation errors, all proto fields are accessible + // If a new field is added to the proto but not handled in copyCompdatToGrpc, + // this test should be updated to reflect that + SUCCEED(); +} + +//-------------------------------------------------------------------------------------------------- +/// Unit tests for copyWelspecsToGrpc function +//-------------------------------------------------------------------------------------------------- +TEST( RiaGrpcWellPathServiceTest, copyWelspecsToGrpcCopiesAllFields ) +{ + // Create a completion settings object with test data + RimWellPathCompletionSettings compSettings; + compSettings.setWellNameForExport( "TEST_WELL" ); + compSettings.setGroupName( "TEST_GROUP" ); + + // Create output gRPC message + rips::SimulatorWelspecsEntry grpcData; + + // Test coordinates + int gridI = 10; + int gridJ = 20; + + // Call the function under test + RiaWellPathDataToGrpcConverter::copyWelspecsToGrpc( &compSettings, &grpcData, nullptr, gridI, gridJ ); + + // Verify basic fields are copied + EXPECT_EQ( grpcData.well_name(), "TEST_WELL" ); + EXPECT_EQ( grpcData.group_name(), "TEST_GROUP" ); + + // Verify coordinates are converted to 1-based + EXPECT_EQ( grpcData.grid_i(), 11 ); // 10+1 + EXPECT_EQ( grpcData.grid_j(), 21 ); // 20+1 + + // Verify that other fields are set to their exported string values + EXPECT_FALSE( grpcData.phase().empty() ); +} + +//-------------------------------------------------------------------------------------------------- +/// Unit tests for copyWelsegsToGrpc function +//-------------------------------------------------------------------------------------------------- +TEST( RiaGrpcWellPathServiceTest, copyWelsegsToGrpcCopiesAllFields ) +{ + // Create MSW table data with test data + RigMswTableData mswData( "WELL_A", RiaDefines::EclipseUnitSystem::UNITS_METRIC ); + + // Set up header + WelsegsHeader header; + header.well = "WELL_A"; + header.topDepth = 1500.0; + header.topLength = 2000.0; + header.wellboreVolume = 0.5; + header.infoType = "ABS"; + header.pressureComponents = "HFA"; + header.flowModel = "HO"; + mswData.setWelsegsHeader( header ); + + // Add segment rows + WelsegsRow row1; + row1.segment1 = 1; + row1.segment2 = 1; + row1.branch = 1; + row1.joinSegment = 1; + row1.length = 100.0; + row1.depth = 1600.0; + row1.diameter = 0.2; + row1.roughness = 1e-5; + row1.description = "Main segment"; + mswData.addWelsegsRow( row1 ); + + WelsegsRow row2; + row2.segment1 = 2; + row2.segment2 = 2; + row2.branch = 1; + row2.joinSegment = 1; + row2.length = 150.0; + row2.depth = 1750.0; + row2.description = "Branch segment"; + mswData.addWelsegsRow( row2 ); + + // Create output gRPC message + rips::SimulatorWelsegsEntry grpcData; + + // Call the function under test + RiaWellPathDataToGrpcConverter::copyWelsegsToGrpc( mswData, &grpcData ); + + // Verify header data + const auto& headerEntry = grpcData.header(); + EXPECT_EQ( headerEntry.well_name(), "WELL_A" ); + EXPECT_DOUBLE_EQ( headerEntry.top_depth(), 1500.0 ); + EXPECT_DOUBLE_EQ( headerEntry.top_length(), 2000.0 ); + EXPECT_TRUE( headerEntry.has_wellbore_volume() ); + EXPECT_DOUBLE_EQ( headerEntry.wellbore_volume(), 0.5 ); + EXPECT_EQ( headerEntry.info_type(), "ABS" ); + EXPECT_TRUE( headerEntry.has_pressure_omponents() ); + EXPECT_EQ( headerEntry.pressure_omponents(), "HFA" ); + EXPECT_TRUE( headerEntry.has_flow_model() ); + EXPECT_EQ( headerEntry.flow_model(), "HO" ); + + // Verify row data + EXPECT_EQ( grpcData.row_size(), 2 ); + + const auto& grpcRow1 = grpcData.row( 0 ); + EXPECT_EQ( grpcRow1.segment_1(), 1 ); + EXPECT_EQ( grpcRow1.segment_2(), 1 ); + EXPECT_EQ( grpcRow1.branch(), 1 ); + EXPECT_EQ( grpcRow1.join_segment(), 1 ); + EXPECT_DOUBLE_EQ( grpcRow1.length(), 100.0 ); + EXPECT_DOUBLE_EQ( grpcRow1.depth(), 1600.0 ); + EXPECT_TRUE( grpcRow1.has_diameter() ); + EXPECT_DOUBLE_EQ( grpcRow1.diameter(), 0.2 ); + EXPECT_TRUE( grpcRow1.has_roughness() ); + EXPECT_DOUBLE_EQ( grpcRow1.roughness(), 1e-5 ); + EXPECT_TRUE( grpcRow1.has_description() ); + EXPECT_EQ( grpcRow1.description(), "Main segment" ); + + const auto& grpcRow2 = grpcData.row( 1 ); + EXPECT_EQ( grpcRow2.segment_1(), 2 ); + EXPECT_EQ( grpcRow2.segment_2(), 2 ); + EXPECT_EQ( grpcRow2.branch(), 1 ); + EXPECT_EQ( grpcRow2.join_segment(), 1 ); + EXPECT_DOUBLE_EQ( grpcRow2.length(), 150.0 ); + EXPECT_DOUBLE_EQ( grpcRow2.depth(), 1750.0 ); + EXPECT_FALSE( grpcRow2.has_diameter() ); // Not set for this row + EXPECT_FALSE( grpcRow2.has_roughness() ); // Not set for this row + EXPECT_TRUE( grpcRow2.has_description() ); + EXPECT_EQ( grpcRow2.description(), "Branch segment" ); +} + +//-------------------------------------------------------------------------------------------------- +/// Unit tests for copyCompsegsToGrpc function +//-------------------------------------------------------------------------------------------------- +TEST( RiaGrpcWellPathServiceTest, copyCompsegsToGrpcCopiesAllFields ) +{ + // Create MSW table data with compsegs data + RigMswTableData mswData( "WELL_B", RiaDefines::EclipseUnitSystem::UNITS_METRIC ); + + // Add compsegs rows + CompsegsRow row1; + row1.i = 10; + row1.j = 20; + row1.k = 5; + row1.branch = 1; + row1.distanceStart = 100.0; + row1.distanceEnd = 200.0; + row1.gridName = ""; // Main grid + mswData.addCompsegsRow( row1 ); + + CompsegsRow row2; + row2.i = 11; + row2.j = 21; + row2.k = 6; + row2.branch = 2; + row2.distanceStart = 300.0; + row2.distanceEnd = 400.0; + row2.gridName = "LGR_GRID"; + mswData.addCompsegsRow( row2 ); + + // Create output gRPC message + rips::SimulatorTableData grpcReply; + + // Call the function under test + RiaWellPathDataToGrpcConverter::copyCompsegsToGrpc( mswData, &grpcReply ); + + // Verify data was copied + EXPECT_EQ( grpcReply.compsegs_size(), 2 ); + + const auto& grpcRow1 = grpcReply.compsegs( 0 ); + EXPECT_EQ( grpcRow1.i(), 10 ); + EXPECT_EQ( grpcRow1.j(), 20 ); + EXPECT_EQ( grpcRow1.k(), 5 ); + EXPECT_EQ( grpcRow1.branch(), 1 ); + EXPECT_DOUBLE_EQ( grpcRow1.distance_start(), 100.0 ); + EXPECT_DOUBLE_EQ( grpcRow1.distance_end(), 200.0 ); + EXPECT_FALSE( grpcRow1.has_grid_name() ); // Empty grid name not set + + const auto& grpcRow2 = grpcReply.compsegs( 1 ); + EXPECT_EQ( grpcRow2.i(), 11 ); + EXPECT_EQ( grpcRow2.j(), 21 ); + EXPECT_EQ( grpcRow2.k(), 6 ); + EXPECT_EQ( grpcRow2.branch(), 2 ); + EXPECT_DOUBLE_EQ( grpcRow2.distance_start(), 300.0 ); + EXPECT_DOUBLE_EQ( grpcRow2.distance_end(), 400.0 ); + EXPECT_TRUE( grpcRow2.has_grid_name() ); + EXPECT_EQ( grpcRow2.grid_name(), "LGR_GRID" ); +} + +//-------------------------------------------------------------------------------------------------- +/// Unit tests for copyWsegvalvToGrpc function +//-------------------------------------------------------------------------------------------------- +TEST( RiaGrpcWellPathServiceTest, copyWsegvalvToGrpcCopiesAllFields ) +{ + // Create MSW table data with valve data + RigMswTableData mswData( "WELL_C", RiaDefines::EclipseUnitSystem::UNITS_METRIC ); + + // Add wsegvalv rows + WsegvalvRow row1; + row1.well = "WELL_C"; + row1.segmentNumber = 5; + row1.cv = 0.8; + row1.area = 0.05; + row1.extraLength = 10.0; + row1.pipeD = 0.15; + row1.roughness = 1e-4; + row1.pipeA = 0.02; + row1.status = "OPEN"; + row1.maxA = 0.1; + mswData.addWsegvalvRow( row1 ); + + WsegvalvRow row2; + row2.well = "WELL_C"; + row2.segmentNumber = 6; + row2.cv = 1.2; + row2.area = 0.08; + // Optional fields not set for this row + mswData.addWsegvalvRow( row2 ); + + // Create output gRPC message + rips::SimulatorTableData grpcReply; + + // Call the function under test + RiaWellPathDataToGrpcConverter::copyWsegvalvToGrpc( mswData, &grpcReply ); + + // Verify data was copied + EXPECT_EQ( grpcReply.wsegvalv_size(), 2 ); + + const auto& grpcRow1 = grpcReply.wsegvalv( 0 ); + EXPECT_EQ( grpcRow1.well_name(), "WELL_C" ); + EXPECT_EQ( grpcRow1.segment_number(), 5 ); + EXPECT_DOUBLE_EQ( grpcRow1.cv(), 0.8 ); + EXPECT_DOUBLE_EQ( grpcRow1.area(), 0.05 ); + EXPECT_TRUE( grpcRow1.has_extra_length() ); + EXPECT_DOUBLE_EQ( grpcRow1.extra_length(), 10.0 ); + EXPECT_TRUE( grpcRow1.has_pipe_d() ); + EXPECT_DOUBLE_EQ( grpcRow1.pipe_d(), 0.15 ); + EXPECT_TRUE( grpcRow1.has_roughness() ); + EXPECT_DOUBLE_EQ( grpcRow1.roughness(), 1e-4 ); + EXPECT_TRUE( grpcRow1.has_pipe_a() ); + EXPECT_DOUBLE_EQ( grpcRow1.pipe_a(), 0.02 ); + EXPECT_TRUE( grpcRow1.has_status() ); + EXPECT_EQ( grpcRow1.status(), "OPEN" ); + EXPECT_TRUE( grpcRow1.has_max_a() ); + EXPECT_DOUBLE_EQ( grpcRow1.max_a(), 0.1 ); + + const auto& grpcRow2 = grpcReply.wsegvalv( 1 ); + EXPECT_EQ( grpcRow2.well_name(), "WELL_C" ); + EXPECT_EQ( grpcRow2.segment_number(), 6 ); + EXPECT_DOUBLE_EQ( grpcRow2.cv(), 1.2 ); + EXPECT_DOUBLE_EQ( grpcRow2.area(), 0.08 ); + EXPECT_FALSE( grpcRow2.has_extra_length() ); + EXPECT_FALSE( grpcRow2.has_pipe_d() ); + EXPECT_FALSE( grpcRow2.has_roughness() ); + EXPECT_FALSE( grpcRow2.has_pipe_a() ); + EXPECT_FALSE( grpcRow2.has_status() ); + EXPECT_FALSE( grpcRow2.has_max_a() ); +} + +//-------------------------------------------------------------------------------------------------- +/// Unit tests for copyWsegaicdToGrpc function +//-------------------------------------------------------------------------------------------------- +TEST( RiaGrpcWellPathServiceTest, copyWsegaicdToGrpcCopiesAllFields ) +{ + // Create MSW table data with AICD data + RigMswTableData mswData( "WELL_D", RiaDefines::EclipseUnitSystem::UNITS_METRIC ); + + // Add wsegaicd row with all optional fields + WsegaicdRow row1; + row1.well = "WELL_D"; + row1.segment1 = 10; + row1.segment2 = 11; + row1.strength = 5.0; + row1.length = 20.0; + row1.densityCali = 850.0; + row1.viscosityCali = 2.0; + row1.criticalValue = 0.5; + row1.widthTrans = 0.1; + row1.maxViscRatio = 10.0; + row1.methodScalingFactor = 2; + row1.maxAbsRate = 1000.0; + row1.flowRateExponent = 2.0; + row1.viscExponent = 0.5; + row1.status = "OPEN"; + row1.oilFlowFraction = 0.3; + row1.waterFlowFraction = 0.4; + row1.gasFlowFraction = 0.3; + row1.oilViscFraction = 0.25; + row1.waterViscFraction = 0.35; + row1.gasViscFraction = 0.4; + row1.description = "Test AICD"; + mswData.addWsegaicdRow( row1 ); + + // Add wsegaicd row with minimal fields + WsegaicdRow row2; + row2.well = "WELL_D"; + row2.segment1 = 12; + row2.segment2 = 13; + row2.strength = 3.0; + row2.maxAbsRate = 800.0; + row2.flowRateExponent = 1.8; + row2.viscExponent = 0.6; + row2.description = ""; + mswData.addWsegaicdRow( row2 ); + + // Create output gRPC message + rips::SimulatorTableData grpcReply; + + // Call the function under test + RiaWellPathDataToGrpcConverter::copyWsegaicdToGrpc( mswData, &grpcReply ); + + // Verify data was copied + EXPECT_EQ( grpcReply.wsegaicd_size(), 2 ); + + // Test row with all fields + const auto& grpcRow1 = grpcReply.wsegaicd( 0 ); + EXPECT_EQ( grpcRow1.well_name(), "WELL_D" ); + EXPECT_EQ( grpcRow1.segment_1(), 10 ); + EXPECT_EQ( grpcRow1.segment_2(), 11 ); + EXPECT_DOUBLE_EQ( grpcRow1.strength(), 5.0 ); + EXPECT_TRUE( grpcRow1.has_length() ); + EXPECT_DOUBLE_EQ( grpcRow1.length(), 20.0 ); + EXPECT_TRUE( grpcRow1.has_density_cali() ); + EXPECT_DOUBLE_EQ( grpcRow1.density_cali(), 850.0 ); + EXPECT_TRUE( grpcRow1.has_viscosity_cali() ); + EXPECT_DOUBLE_EQ( grpcRow1.viscosity_cali(), 2.0 ); + EXPECT_TRUE( grpcRow1.has_critical_value() ); + EXPECT_DOUBLE_EQ( grpcRow1.critical_value(), 0.5 ); + EXPECT_TRUE( grpcRow1.has_width_trans() ); + EXPECT_DOUBLE_EQ( grpcRow1.width_trans(), 0.1 ); + EXPECT_TRUE( grpcRow1.has_max_visc_ratio() ); + EXPECT_DOUBLE_EQ( grpcRow1.max_visc_ratio(), 10.0 ); + EXPECT_TRUE( grpcRow1.has_method_scaling_factor() ); + EXPECT_EQ( grpcRow1.method_scaling_factor(), 2 ); + EXPECT_DOUBLE_EQ( grpcRow1.max_abs_rate(), 1000.0 ); + EXPECT_DOUBLE_EQ( grpcRow1.flow_rate_exponent(), 2.0 ); + EXPECT_DOUBLE_EQ( grpcRow1.visc_exponent(), 0.5 ); + EXPECT_TRUE( grpcRow1.has_status() ); + EXPECT_EQ( grpcRow1.status(), "OPEN" ); + EXPECT_TRUE( grpcRow1.has_oil_flow_fraction() ); + EXPECT_DOUBLE_EQ( grpcRow1.oil_flow_fraction(), 0.3 ); + EXPECT_TRUE( grpcRow1.has_water_flow_fraction() ); + EXPECT_DOUBLE_EQ( grpcRow1.water_flow_fraction(), 0.4 ); + EXPECT_TRUE( grpcRow1.has_gas_flow_fraction() ); + EXPECT_DOUBLE_EQ( grpcRow1.gas_flow_fraction(), 0.3 ); + EXPECT_TRUE( grpcRow1.has_oil_visc_fraction() ); + EXPECT_DOUBLE_EQ( grpcRow1.oil_visc_fraction(), 0.25 ); + EXPECT_TRUE( grpcRow1.has_water_visc_fraction() ); + EXPECT_DOUBLE_EQ( grpcRow1.water_visc_fraction(), 0.35 ); + EXPECT_TRUE( grpcRow1.has_gas_visc_fraction() ); + EXPECT_DOUBLE_EQ( grpcRow1.gas_visc_fraction(), 0.4 ); + EXPECT_TRUE( grpcRow1.has_description() ); + EXPECT_EQ( grpcRow1.description(), "Test AICD" ); + + // Test row with minimal fields + const auto& grpcRow2 = grpcReply.wsegaicd( 1 ); + EXPECT_EQ( grpcRow2.well_name(), "WELL_D" ); + EXPECT_EQ( grpcRow2.segment_1(), 12 ); + EXPECT_EQ( grpcRow2.segment_2(), 13 ); + EXPECT_DOUBLE_EQ( grpcRow2.strength(), 3.0 ); + EXPECT_FALSE( grpcRow2.has_length() ); + EXPECT_FALSE( grpcRow2.has_density_cali() ); + EXPECT_FALSE( grpcRow2.has_viscosity_cali() ); + EXPECT_FALSE( grpcRow2.has_critical_value() ); + EXPECT_FALSE( grpcRow2.has_width_trans() ); + EXPECT_FALSE( grpcRow2.has_max_visc_ratio() ); + EXPECT_FALSE( grpcRow2.has_method_scaling_factor() ); + EXPECT_DOUBLE_EQ( grpcRow2.max_abs_rate(), 800.0 ); + EXPECT_DOUBLE_EQ( grpcRow2.flow_rate_exponent(), 1.8 ); + EXPECT_DOUBLE_EQ( grpcRow2.visc_exponent(), 0.6 ); + EXPECT_FALSE( grpcRow2.has_status() ); + EXPECT_FALSE( grpcRow2.has_oil_flow_fraction() ); + EXPECT_FALSE( grpcRow2.has_water_flow_fraction() ); + EXPECT_FALSE( grpcRow2.has_gas_flow_fraction() ); + EXPECT_FALSE( grpcRow2.has_oil_visc_fraction() ); + EXPECT_FALSE( grpcRow2.has_water_visc_fraction() ); + EXPECT_FALSE( grpcRow2.has_gas_visc_fraction() ); + EXPECT_FALSE( grpcRow2.has_description() ); // Empty description not copied +} + +//-------------------------------------------------------------------------------------------------- +/// Unit tests for empty data handling +//-------------------------------------------------------------------------------------------------- +TEST( RiaGrpcWellPathServiceTest, copyFunctionsHandleEmptyData ) +{ + // Test empty MSW data + RigMswTableData emptyMswData( "EMPTY_WELL", RiaDefines::EclipseUnitSystem::UNITS_METRIC ); + + rips::SimulatorWelsegsEntry welsegsData; + rips::SimulatorTableData tableData; + + // These should not crash and should not add any data + RiaWellPathDataToGrpcConverter::copyWelsegsToGrpc( emptyMswData, &welsegsData ); + RiaWellPathDataToGrpcConverter::copyCompsegsToGrpc( emptyMswData, &tableData ); + RiaWellPathDataToGrpcConverter::copyWsegvalvToGrpc( emptyMswData, &tableData ); + RiaWellPathDataToGrpcConverter::copyWsegaicdToGrpc( emptyMswData, &tableData ); + + // Verify no data was added + EXPECT_EQ( welsegsData.row_size(), 0 ); + EXPECT_EQ( tableData.compsegs_size(), 0 ); + EXPECT_EQ( tableData.wsegvalv_size(), 0 ); + EXPECT_EQ( tableData.wsegaicd_size(), 0 ); +} \ No newline at end of file diff --git a/GrpcInterface/CMakeLists.txt b/GrpcInterface/CMakeLists.txt index 7bcb2c5713..703bc9ca7c 100644 --- a/GrpcInterface/CMakeLists.txt +++ b/GrpcInterface/CMakeLists.txt @@ -38,6 +38,7 @@ set(SOURCE_GROUP_HEADER_FILES RiaGrpcPdmObjectService.h RiaGrpcApplicationInterface.h RiaGrpcWellPathService.h + RiaWellPathDataToGrpcConverter.h ) set(SOURCE_GROUP_SOURCE_FILES @@ -56,6 +57,7 @@ set(SOURCE_GROUP_SOURCE_FILES RiaGrpcPdmObjectService.cpp RiaGrpcApplicationInterface.cpp RiaGrpcWellPathService.cpp + RiaWellPathDataToGrpcConverter.cpp ) # Find Protobuf installation Looks for protobuf-config.cmake file installed by diff --git a/GrpcInterface/GrpcProtos/SimulatorTables.proto b/GrpcInterface/GrpcProtos/SimulatorTables.proto index 1e8d9f33e9..766e9a718c 100644 --- a/GrpcInterface/GrpcProtos/SimulatorTables.proto +++ b/GrpcInterface/GrpcProtos/SimulatorTables.proto @@ -7,6 +7,8 @@ package rips; service WellPath { rpc GetCompletionData(SimulatorTableRequest) returns (SimulatorTableData) {} + rpc GetCompletionDataUnified(SimulatorTableUnifiedRequest) + returns (SimulatorTableData) {} } message SimulatorTableRequest { @@ -14,6 +16,11 @@ message SimulatorTableRequest { int32 case_id = 2; } +message SimulatorTableUnifiedRequest { + repeated string wellpath_names = 1; + int32 case_id = 2; +} + message SimulatorCompdatEntry { string well_name = 1; int32 grid_i = 2; @@ -49,8 +56,87 @@ message SimulatorWelspecsEntry { optional int32 fip_region = 13; } +message SimulatorWelsegsHeaderEntry { + string well_name = 1; + double top_depth = 2; + double top_length = 3; + optional double wellbore_volume = 4; + string info_type = 5; + optional string pressure_omponents = 6; + optional string flow_model = 7; +} + +message SimulatorWelsegsRowEntry { + int32 segment_1 = 1; + int32 segment_2 = 2; + int32 branch = 3; + int32 join_segment = 4; + double length = 5; + double depth = 6; + optional double diameter = 7; + optional double roughness = 8; + optional string description = 9; +} + +message SimulatorWelsegsEntry { + SimulatorWelsegsHeaderEntry header = 1; + repeated SimulatorWelsegsRowEntry row = 2; +} + +message SimulatorCompsegsEntry { + int32 i = 1; + int32 j = 2; + int32 k = 3; + int32 branch = 4; + double distance_start = 5; + double distance_end = 6; + optional string grid_name = 7; +} + +message SimulatorWsegvalvEntry { + string well_name = 1; + int32 segment_number = 2; + double cv = 3; + double area = 4; + optional double extra_length = 5; + optional double pipe_d = 6; + optional double roughness = 7; + optional double pipe_a = 8; + optional string status = 9; + optional double max_a = 10; +} + +message SimulatorWsegaicdEntry { + string well_name = 1; + int32 segment_1 = 2; + int32 segment_2 = 3; + double strength = 4; + optional double length = 5; + optional double density_cali = 6; + optional double viscosity_cali = 7; + optional double critical_value = 8; + optional double width_trans = 9; + optional double max_visc_ratio = 10; + optional int32 method_scaling_factor = 11; + double max_abs_rate = 12; + double flow_rate_exponent = 13; + double visc_exponent = 14; + optional string status = 15; + optional double oil_flow_fraction = 16; + optional double water_flow_fraction = 17; + optional double gas_flow_fraction = 18; + optional double oil_visc_fraction = 19; + optional double water_visc_fraction = 20; + optional double gas_visc_fraction = 21; + optional string description = 22; +} + message SimulatorTableData { repeated SimulatorCompdatEntry compdat = 1; repeated SimulatorWelspecsEntry welspecs = 2; + repeated SimulatorWelsegsEntry welsegs = 3; + repeated SimulatorCompsegsEntry compsegs = 4; + repeated SimulatorWsegvalvEntry wsegvalv = 5; + repeated SimulatorWsegaicdEntry wsegaicd = 6; } diff --git a/GrpcInterface/Python/rips/tests/test_msw_completion_data.py b/GrpcInterface/Python/rips/tests/test_msw_completion_data.py new file mode 100644 index 0000000000..8afa2638cd --- /dev/null +++ b/GrpcInterface/Python/rips/tests/test_msw_completion_data.py @@ -0,0 +1,232 @@ +import sys +import os +import tempfile + +sys.path.insert(1, os.path.join(sys.path[0], "../../")) + +import dataroot + + +def test_msw_completion_data_perf_lateral(rips_instance, initialize_test): + """Test MSW completion data retrieval from perf_lateral.rsp project. + + This test loads a project with Multi-Segment Well (MSW) configuration + and verifies that completion data can be retrieved including MSW-specific + tables (WELSEGS, COMPSEGS). + """ + project_path = dataroot.PATH + "/msw-export/project-files/perf_lateral.rsp" + project = rips_instance.project.open(path=project_path) + + # Get the case + cases = project.cases() + assert len(cases) > 0, "Project should contain at least one case" + case = cases[0] + + # Get well paths + well_paths = project.well_paths() + assert len(well_paths) >= 2, "Project should contain at least 2 well paths" + + # Find the well paths by name + well_y1 = None + well_y2 = None + for well in well_paths: + if well.name == "Well-1 Y1": + well_y1 = well + elif well.name == "Well-1 Y2": + well_y2 = well + + assert well_y1 is not None, "Well-1 Y1 should exist in project" + assert well_y2 is not None, "Well-1 Y2 should exist in project" + + # Test completion data for Well-1 Y1 + completion_data_y1 = well_y1.completion_data(case.id) + + # Verify WELSPECS data + welspecs = completion_data_y1.welspecs + assert len(welspecs) > 0, "Should have WELSPECS data for Well-1 Y1" + + welspec = welspecs[0] + assert welspec.well_name == "Well-1", "Well name should be Well-1" + assert welspec.group_name == "PROD_N", "Group name should be PROD_N" + assert welspec.phase == "OIL", "Phase should be OIL" + + # Verify COMPDAT data (perforation intervals) + compdat = completion_data_y1.compdat + assert len(compdat) > 0, "Should have COMPDAT data for Well-1 Y1" + + # Check that we have MD information in COMPDAT + has_md_data = False + for comp in compdat: + if comp.HasField("start_md") and comp.HasField("end_md"): + has_md_data = True + assert comp.start_md > 0, "Start MD should be positive" + assert comp.end_md > comp.start_md, "End MD should be greater than start MD" + break + + assert has_md_data, "Should have MD (measured depth) data in COMPDAT" + + # Verify MSW-specific data: WELSEGS + welsegs = completion_data_y1.welsegs + assert len(welsegs) > 0, "Should have WELSEGS data for MSW well Well-1 Y1" + + # Check WELSEGS header (first entry) + welsegs_entry = welsegs[0] + assert ( + welsegs_entry.header.well_name == "Well-1" + ), "WELSEGS well name should be Well-1" + assert ( + welsegs_entry.header.top_depth > 0 + ), "Should have positive top depth in WELSEGS" + assert welsegs_entry.header.top_length >= 0, "Should have top length in WELSEGS" + assert welsegs_entry.header.info_type in [ + "ABS", + "INC", + ], "Should have valid info type" + + # Check WELSEGS rows (segment definitions) + assert len(welsegs_entry.row) > 0, "Should have segment rows in WELSEGS" + for row in welsegs_entry.row: + assert row.segment_1 > 0, "Segment 1 should be positive" + assert row.segment_2 > 0, "Segment 2 should be positive" + + # Verify MSW-specific data: COMPSEGS + compsegs = completion_data_y1.compsegs + assert len(compsegs) > 0, "Should have COMPSEGS data for MSW well Well-1 Y1" + + # Check COMPSEGS entries + for compseg in compsegs: + assert compseg.i > 0, "Grid I should be positive" + assert compseg.j > 0, "Grid J should be positive" + assert compseg.k > 0, "Grid K should be positive" + assert compseg.branch >= 0, "Branch should be non-negative" + + +def test_msw_completion_data_unified(rips_instance, initialize_test): + """Test unified MSW completion data retrieval for multiple wells. + + This test verifies that completion data can be retrieved for multiple + MSW wells in a single call using the completion_data_unified method. + """ + project_path = dataroot.PATH + "/msw-export/project-files/perf_lateral.rsp" + project = rips_instance.project.open(path=project_path) + + # Get the case + cases = project.cases() + assert len(cases) > 0, "Project should contain at least one case" + case = cases[0] + + # Get well path collection + well_path_collection = project.well_path_collection() + + # Get unified completion data for all wells + unified_data = well_path_collection.completion_data_unified(case_id=case.id) + + # Verify WELSPECS + assert len(unified_data.welspecs) > 0, "Should have WELSPECS in unified data" + + # Verify COMPDAT + assert len(unified_data.compdat) > 0, "Should have COMPDAT in unified data" + + # Verify WELSEGS (MSW-specific) + assert len(unified_data.welsegs) > 0, "Should have WELSEGS in unified MSW data" + + # Count wells in WELSEGS + well_names_in_welsegs = set() + for welsegs_entry in unified_data.welsegs: + well_names_in_welsegs.add(welsegs_entry.header.well_name) + + assert ( + len(well_names_in_welsegs) >= 1 + ), "Should have at least one well in WELSEGS data" + + # Verify COMPSEGS (MSW-specific) + assert len(unified_data.compsegs) > 0, "Should have COMPSEGS in unified MSW data" + + # Check that COMPSEGS has valid grid coordinates + for compseg in unified_data.compsegs: + assert compseg.i > 0, "Grid I should be positive" + assert compseg.j > 0, "Grid J should be positive" + assert compseg.k > 0, "Grid K should be positive" + + +def test_msw_export(rips_instance, initialize_test): + """Test MSW export functionality. + + This test verifies that MSW data can be exported to file using the + export_msw method on a case. + """ + project_path = dataroot.PATH + "/msw-export/project-files/perf_lateral.rsp" + project = rips_instance.project.open(path=project_path) + + # Get the case + cases = project.cases() + assert len(cases) > 0, "Project should contain at least one case" + case = cases[0] + + # Get well paths + well_paths = project.well_paths() + well_y1 = None + for well in well_paths: + if well.name == "Well-1 Y1": + well_y1 = well + break + + assert well_y1 is not None, "Well-1 Y1 should exist in project" + + # Set export folder + export_folder = tempfile.gettempdir() + rips_instance.set_export_folder(export_type="COMPLETIONS", path=export_folder) + + # Export MSW data + case.export_msw(well_path=well_y1.name) + + # Note: The actual file creation validation would require checking + # the export folder, which may be platform-dependent and require + # additional file system checks + + +def test_msw_settings_read(rips_instance, initialize_test): + """Test reading MSW settings from project. + + This test verifies that MSW settings configured in the project + can be read through the Python API. + """ + project_path = dataroot.PATH + "/msw-export/project-files/perf_lateral.rsp" + project = rips_instance.project.open(path=project_path) + + # Get well paths + well_paths = project.well_paths() + well_y1 = None + for well in well_paths: + if well.name == "Well-1 Y1": + well_y1 = well + break + + assert well_y1 is not None, "Well-1 Y1 should exist in project" + + # Get MSW settings + msw_settings = well_y1.msw_settings() + + # Verify MSW settings values from the project file + # According to the project analysis, the liner diameter is 0.152m + assert msw_settings.liner_diameter == 0.152, "Liner diameter should be 0.152m" + assert msw_settings.roughness_factor == 1e-05, "Roughness factor should be 1e-05" + + # Check pressure drop setting + assert msw_settings.pressure_drop == "HF-", "Pressure drop should be HF-" + + # Check max segment length setting + assert msw_settings.max_segment_length == 200.0, "Max segment length should be 200m" + + # Verify completion settings + completion_settings = well_y1.completion_settings() + assert ( + completion_settings.well_name_for_export == "Well-1" + ), "Well name for export should be Well-1" + assert ( + completion_settings.group_name_for_export == "PROD_N" + ), "Group name should be PROD_N" + assert completion_settings.well_type_for_export == "OIL", "Well type should be OIL" + assert ( + completion_settings.allow_well_cross_flow + ), "Allow well cross flow should be True" diff --git a/GrpcInterface/Python/rips/well_path_collection.py b/GrpcInterface/Python/rips/well_path_collection.py index ee94692cf3..5ce829ff03 100644 --- a/GrpcInterface/Python/rips/well_path_collection.py +++ b/GrpcInterface/Python/rips/well_path_collection.py @@ -1,11 +1,24 @@ import uuid -from typing import List +import grpc +from typing import List, Optional, Union, cast + +import SimulatorTables_pb2 +import SimulatorTables_pb2_grpc + +import PdmObject_pb2 from .pdmobject import add_method -from .resinsight_classes import WellPathCollection +from .resinsight_classes import WellPathCollection, WellPath from .project import Project +@add_method(WellPathCollection) +def __custom_init__( + self: WellPathCollection, pb2_object: PdmObject_pb2.PdmObject, channel: grpc.Channel +) -> None: + self.__well_path_stub = SimulatorTables_pb2_grpc.WellPathStub(channel) + + @add_method(WellPathCollection) def import_well_path_from_points( self: WellPathCollection, name: str, coordinates: List[List[float]] @@ -82,3 +95,61 @@ def import_well_path_from_points( project.remove_key_values(x_key) project.remove_key_values(y_key) project.remove_key_values(z_key) + + +@add_method(WellPathCollection) +def completion_data_unified( + self: WellPathCollection, + wells: Optional[Union[List[WellPath], List[str]]] = None, + case_id: Optional[int] = None, +) -> SimulatorTables_pb2.SimulatorTableData: + """Get unified completion data for multiple wells. + + This method merges completion data (COMPDAT, WELSPECS, WELSEGS, COMPSEGS, + WSEGVALV, WSEGAICD) from multiple wells into a single response. + + Arguments: + wells: List of WellPath objects or well names. If None, uses all wells in collection. + case_id: ID of the case to use. If None, tries to determine from context. + + Returns: + SimulatorTableData containing merged completion data from all specified wells + + Raises: + ValueError: If case_id cannot be determined or wells list is invalid + RuntimeError: If unable to access required data + """ + # Determine case_id if not provided + if case_id is None: + project = self.ancestor(Project) + if not project: + raise RuntimeError("Could not find parent project") + + # Try to get case_id from the first available case + cases = project.cases() + if not cases: + raise RuntimeError("No cases available in project") + case_id = cases[0].case_id + + # Handle wells parameter + if wells is None: + # Get all wells in collection + well_names = [wp.name for wp in self.well_paths()] + elif all(isinstance(w, str) for w in wells): + # List of well names + well_names = cast(List[str], wells) + elif all(hasattr(w, "name") for w in wells): + # List of WellPath objects + well_names = [w.name for w in wells if hasattr(w, "name")] + else: + raise ValueError( + "wells parameter must be a list of WellPath objects or strings" + ) + + if not well_names: + raise ValueError("No wells specified or found in collection") + + sim_tab_req = SimulatorTables_pb2.SimulatorTableUnifiedRequest( + wellpath_names=well_names, case_id=case_id + ) + return self.__well_path_stub.GetCompletionDataUnified(sim_tab_req) diff --git a/GrpcInterface/RiaGrpcWellPathService.cpp b/GrpcInterface/RiaGrpcWellPathService.cpp index 46f3b9fbf8..b953692087 100644 --- a/GrpcInterface/RiaGrpcWellPathService.cpp +++ b/GrpcInterface/RiaGrpcWellPathService.cpp @@ -15,13 +15,17 @@ // for more details. // ////////////////////////////////////////////////////////////////////////////////// + #include "RiaGrpcWellPathService.h" #include "RiaGrpcCallbacks.h" #include "RiaGrpcHelper.h" +#include "RiaWellPathDataToGrpcConverter.h" -#include "../Commands/CompletionExportCommands/RicWellPathExportCompletionDataFeatureImpl.h" +#include "Commands/CompletionExportCommands/RicWellPathExportCompletionDataFeatureImpl.h" +#include "Commands/CompletionExportCommands/RicWellPathExportMswTableData.h" +#include "CompletionsMsw/RigMswTableData.h" #include "RigEclipseCaseData.h" #include "RigGridBase.h" #include "RigMainGrid.h" @@ -64,14 +68,37 @@ grpc::Status RiaGrpcWellPathService::GetCompletionData( grpc::ServerContext* for ( const auto& cd : compdata ) { SimulatorCompdatEntry* grpcData = reply->add_compdat(); - RiaGrpcWellPathService::copyCompdatToGrpc( cd, grpcData ); + RiaWellPathDataToGrpcConverter::copyCompdatToGrpc( cd, grpcData ); } auto ijPos = RicWellPathExportCompletionDataFeatureImpl::wellPathUpperGridIntersectionIJ( eclipseCase, wellPath ); auto compSettings = wellPath->completionSettings(); SimulatorWelspecsEntry* grpcData = reply->add_welspecs(); - RiaGrpcWellPathService::copyWelspecsToGrpc( compSettings, grpcData, eclipseCase, ijPos.second.x(), ijPos.second.y() ); + RiaWellPathDataToGrpcConverter::copyWelspecsToGrpc( compSettings, + grpcData, + eclipseCase, + ijPos.second.x(), + ijPos.second.y() ); + + // Multisegment wells + + int timeStep = 0; + auto mswDataContainer = RicWellPathExportMswTableData::extractSingleWellMswData( eclipseCase, wellPath, timeStep ); + if ( mswDataContainer.has_value() ) + { + auto tables = mswDataContainer.value(); + + if ( tables.hasWelsegsData() ) + { + SimulatorWelsegsEntry* grpcSegData = reply->add_welsegs(); + RiaWellPathDataToGrpcConverter::copyWelsegsToGrpc( tables, grpcSegData ); + } + + RiaWellPathDataToGrpcConverter::copyCompsegsToGrpc( tables, reply ); + RiaWellPathDataToGrpcConverter::copyWsegvalvToGrpc( tables, reply ); + RiaWellPathDataToGrpcConverter::copyWsegaicdToGrpc( tables, reply ); + } return grpc::Status::OK; } @@ -79,81 +106,71 @@ grpc::Status RiaGrpcWellPathService::GetCompletionData( grpc::ServerContext* //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -void RiaGrpcWellPathService::copyWelspecsToGrpc( const RimWellPathCompletionSettings* compSettings, - rips::SimulatorWelspecsEntry* grpcData, - RimEclipseCase* eclipseCase, - int gridI, - int gridJ ) +grpc::Status RiaGrpcWellPathService::GetCompletionDataUnified( grpc::ServerContext* context, + const rips::SimulatorTableUnifiedRequest* request, + rips::SimulatorTableData* reply ) { - grpcData->set_well_name( compSettings->wellNameForExport().toStdString() ); - grpcData->set_group_name( compSettings->groupNameForExport().toStdString() ); + RimEclipseCase* eclipseCase = dynamic_cast( RiaGrpcHelper::findCase( request->case_id() ) ); + if ( !eclipseCase ) + { + return grpc::Status( grpc::NOT_FOUND, "Case not found" ); + } - // Convert to 1-based indexing - grpcData->set_grid_i( gridI + 1 ); - grpcData->set_grid_j( gridJ + 1 ); + auto proj = RimProject::current(); + if ( !proj ) + { + return grpc::Status( grpc::INTERNAL, "No current project" ); + } - if ( compSettings->referenceDepth().has_value() ) + // Process each well and merge all data into the single reply + for ( const std::string& wellPathName : request->wellpath_names() ) { - grpcData->set_bhp_depth( compSettings->referenceDepth().value() ); - } - grpcData->set_phase( compSettings->wellTypeNameForExport().toStdString() ); - if ( compSettings->drainageRadius().has_value() ) - { - grpcData->set_drainage_radius( compSettings->drainageRadius().value() ); - } - grpcData->set_inflow_equation( compSettings->gasInflowEquationForExport().toStdString() ); - grpcData->set_auto_shut_in( compSettings->automaticWellShutInForExport().toStdString() ); - grpcData->set_cross_flow( compSettings->allowWellCrossFlowForExport().toStdString() ); - grpcData->set_pvt_num( compSettings->wellBoreFluidPVT() ); - grpcData->set_hydrostatic_density_calc( compSettings->hydrostaticDensityForExport().toStdString() ); - grpcData->set_fip_region( compSettings->fluidInPlaceRegion() ); -} + RimWellPath* wellPath = proj->wellPathByName( QString::fromStdString( wellPathName ) ); + if ( !wellPath ) + { + // Continue processing other wells instead of failing the entire request + continue; + } -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -void RiaGrpcWellPathService::copyCompdatToGrpc( const RigCompletionData& inputData, rips::SimulatorCompdatEntry* compDat ) -{ - compDat->set_comment( inputData.metaDataString().toStdString() ); - compDat->set_well_name( inputData.wellName().toStdString() ); + // Add completion data for this well + auto compdata = RicWellPathExportCompletionDataFeatureImpl::completionDataForWellPath( wellPath, eclipseCase ); + for ( const auto& cd : compdata ) + { + SimulatorCompdatEntry* grpcData = reply->add_compdat(); + RiaWellPathDataToGrpcConverter::copyCompdatToGrpc( cd, grpcData ); + } - // Convert to 1-based indexing - compDat->set_grid_i( inputData.completionDataGridCell().localCellIndexI() + 1 ); - compDat->set_grid_j( inputData.completionDataGridCell().localCellIndexJ() + 1 ); - compDat->set_upper_k( inputData.completionDataGridCell().localCellIndexK() + 1 ); - compDat->set_lower_k( inputData.completionDataGridCell().localCellIndexK() + 1 ); + // Add welspecs data for this well + auto ijPos = RicWellPathExportCompletionDataFeatureImpl::wellPathUpperGridIntersectionIJ( eclipseCase, wellPath ); + auto compSettings = wellPath->completionSettings(); - compDat->set_open_shut_flag( "OPEN" ); - if ( inputData.saturation() != inputData.defaultValue() ) - { - compDat->set_saturation( inputData.saturation() ); - } - if ( inputData.transmissibility() != inputData.defaultValue() ) - { - compDat->set_transmissibility( inputData.transmissibility() ); - } - if ( inputData.diameter() != inputData.defaultValue() ) - { - compDat->set_diameter( inputData.diameter() ); - } - if ( inputData.kh() != inputData.defaultValue() ) - { - compDat->set_kh( inputData.kh() ); - } - if ( inputData.skinFactor() != inputData.defaultValue() ) - { - compDat->set_skin_factor( inputData.skinFactor() ); - } - if ( inputData.dFactor() != inputData.defaultValue() ) - { - compDat->set_d_factor( inputData.dFactor() ); - } - compDat->set_direction( inputData.directionStringXYZ().toStdString() ); - if ( inputData.startMD().has_value() ) - { - compDat->set_start_md( inputData.startMD().value() ); - compDat->set_end_md( inputData.endMD().value() ); + SimulatorWelspecsEntry* grpcData = reply->add_welspecs(); + RiaWellPathDataToGrpcConverter::copyWelspecsToGrpc( compSettings, + grpcData, + eclipseCase, + ijPos.second.x(), + ijPos.second.y() ); + + // Add multisegment well data for this well + int timeStep = 0; + auto mswDataContainer = RicWellPathExportMswTableData::extractSingleWellMswData( eclipseCase, wellPath, timeStep ); + if ( mswDataContainer.has_value() ) + { + auto tables = mswDataContainer.value(); + + if ( tables.hasWelsegsData() ) + { + SimulatorWelsegsEntry* grpcSegData = reply->add_welsegs(); + RiaWellPathDataToGrpcConverter::copyWelsegsToGrpc( tables, grpcSegData ); + } + + RiaWellPathDataToGrpcConverter::copyCompsegsToGrpc( tables, reply ); + RiaWellPathDataToGrpcConverter::copyWsegvalvToGrpc( tables, reply ); + RiaWellPathDataToGrpcConverter::copyWsegaicdToGrpc( tables, reply ); + } } + + return grpc::Status::OK; } //-------------------------------------------------------------------------------------------------- @@ -165,7 +182,10 @@ std::vector RiaGrpcWellPathService::createCallbacks() return { new RiaGrpcUnaryCallback( this, &Self::GetCompletionData, - &Self::RequestGetCompletionData ) }; + &Self::RequestGetCompletionData ), + new RiaGrpcUnaryCallback( this, + &Self::GetCompletionDataUnified, + &Self::RequestGetCompletionDataUnified ) }; } static bool RiaGrpcWellPathService_init = RiaGrpcServiceFactory::instance()->registerCreator( diff --git a/GrpcInterface/RiaGrpcWellPathService.h b/GrpcInterface/RiaGrpcWellPathService.h index 4c86e8642f..a9aaff06aa 100644 --- a/GrpcInterface/RiaGrpcWellPathService.h +++ b/GrpcInterface/RiaGrpcWellPathService.h @@ -29,6 +29,7 @@ namespace rips class SimulationWellRequest; class SimulationWellStatus; class SimulatorWelspecsEntry; +class SimulatorWelsegsEntry; } // namespace rips class RiaGrpcCallbackInterface; @@ -36,6 +37,7 @@ class RiaGrpcCallbackInterface; class RimEclipseCase; class RimWellPathCompletionSettings; class RigCompletionData; +class RigMswTableData; //================================================================================================== // @@ -49,13 +51,9 @@ public: const ::rips::SimulatorTableRequest* request, ::rips::SimulatorTableData* response ) override; - std::vector createCallbacks() override; + grpc::Status GetCompletionDataUnified( ::grpc::ServerContext* context, + const ::rips::SimulatorTableUnifiedRequest* request, + ::rips::SimulatorTableData* response ) override; -private: - static void copyCompdatToGrpc( const RigCompletionData& inputData, rips::SimulatorCompdatEntry* compDat ); - static void copyWelspecsToGrpc( const RimWellPathCompletionSettings* compSettings, - rips::SimulatorWelspecsEntry* grpcData, - RimEclipseCase* eclipseCase, - int gridI, - int gridJ ); + std::vector createCallbacks() override; }; diff --git a/GrpcInterface/RiaWellPathDataToGrpcConverter.cpp b/GrpcInterface/RiaWellPathDataToGrpcConverter.cpp new file mode 100644 index 0000000000..445c722c03 --- /dev/null +++ b/GrpcInterface/RiaWellPathDataToGrpcConverter.cpp @@ -0,0 +1,314 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RiaWellPathDataToGrpcConverter.h" + +#include "CompletionsMsw/RigMswTableData.h" +#include "RigCompletionData.h" + +#include "RimEclipseCase.h" +#include "RimWellPathCompletionSettings.h" + +#include + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaWellPathDataToGrpcConverter::copyCompdatToGrpc( const RigCompletionData& inputData, + rips::SimulatorCompdatEntry* compDat ) +{ + compDat->set_comment( inputData.metaDataString().toStdString() ); + compDat->set_well_name( inputData.wellName().toStdString() ); + + // Convert to 1-based indexing + compDat->set_grid_i( inputData.completionDataGridCell().localCellIndexI() + 1 ); + compDat->set_grid_j( inputData.completionDataGridCell().localCellIndexJ() + 1 ); + compDat->set_upper_k( inputData.completionDataGridCell().localCellIndexK() + 1 ); + compDat->set_lower_k( inputData.completionDataGridCell().localCellIndexK() + 1 ); + + compDat->set_open_shut_flag( "OPEN" ); + if ( inputData.saturation() != inputData.defaultValue() ) + { + compDat->set_saturation( inputData.saturation() ); + } + if ( inputData.transmissibility() != inputData.defaultValue() ) + { + compDat->set_transmissibility( inputData.transmissibility() ); + } + if ( inputData.diameter() != inputData.defaultValue() ) + { + compDat->set_diameter( inputData.diameter() ); + } + if ( inputData.kh() != inputData.defaultValue() ) + { + compDat->set_kh( inputData.kh() ); + } + if ( inputData.skinFactor() != inputData.defaultValue() ) + { + compDat->set_skin_factor( inputData.skinFactor() ); + } + if ( inputData.dFactor() != inputData.defaultValue() ) + { + compDat->set_d_factor( inputData.dFactor() ); + } + compDat->set_direction( inputData.directionStringXYZ().toStdString() ); + if ( inputData.startMD().has_value() ) + { + compDat->set_start_md( inputData.startMD().value() ); + compDat->set_end_md( inputData.endMD().value() ); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaWellPathDataToGrpcConverter::copyWelspecsToGrpc( const RimWellPathCompletionSettings* compSettings, + rips::SimulatorWelspecsEntry* grpcData, + RimEclipseCase* eclipseCase, + int gridI, + int gridJ ) +{ + grpcData->set_well_name( compSettings->wellNameForExport().toStdString() ); + grpcData->set_group_name( compSettings->groupNameForExport().toStdString() ); + + // Convert to 1-based indexing + grpcData->set_grid_i( gridI + 1 ); + grpcData->set_grid_j( gridJ + 1 ); + + if ( compSettings->referenceDepth().has_value() ) + { + grpcData->set_bhp_depth( compSettings->referenceDepth().value() ); + } + grpcData->set_phase( compSettings->wellTypeNameForExport().toStdString() ); + if ( compSettings->drainageRadius().has_value() ) + { + grpcData->set_drainage_radius( compSettings->drainageRadius().value() ); + } + grpcData->set_inflow_equation( compSettings->gasInflowEquationForExport().toStdString() ); + grpcData->set_auto_shut_in( compSettings->automaticWellShutInForExport().toStdString() ); + grpcData->set_cross_flow( compSettings->allowWellCrossFlowForExport().toStdString() ); + grpcData->set_pvt_num( compSettings->wellBoreFluidPVT() ); + grpcData->set_hydrostatic_density_calc( compSettings->hydrostaticDensityForExport().toStdString() ); + grpcData->set_fip_region( compSettings->fluidInPlaceRegion() ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaWellPathDataToGrpcConverter::copyWelsegsToGrpc( const RigMswTableData& mswTableData, + rips::SimulatorWelsegsEntry* grpcData ) +{ + if ( !mswTableData.hasWelsegsData() ) return; + + auto* headerEntry = grpcData->mutable_header(); + auto welsegsHeader = mswTableData.welsegsHeader(); + headerEntry->set_well_name( welsegsHeader.well ); + headerEntry->set_top_depth( welsegsHeader.topDepth ); + headerEntry->set_top_length( welsegsHeader.topLength ); + if ( welsegsHeader.wellboreVolume.has_value() ) + { + headerEntry->set_wellbore_volume( welsegsHeader.wellboreVolume.value() ); + } + headerEntry->set_info_type( welsegsHeader.infoType ); + if ( welsegsHeader.pressureComponents.has_value() ) + { + headerEntry->set_pressure_omponents( welsegsHeader.pressureComponents.value() ); + } + if ( welsegsHeader.flowModel.has_value() ) + { + headerEntry->set_flow_model( welsegsHeader.flowModel.value() ); + } + + for ( const auto& welsegsRow : mswTableData.welsegsData() ) + { + auto* rowEntry = grpcData->add_row(); + rowEntry->set_segment_1( welsegsRow.segment1 ); + rowEntry->set_segment_2( welsegsRow.segment2 ); + rowEntry->set_branch( welsegsRow.branch ); + rowEntry->set_join_segment( welsegsRow.joinSegment ); + rowEntry->set_length( welsegsRow.length ); + rowEntry->set_depth( welsegsRow.depth ); + if ( welsegsRow.diameter.has_value() ) + { + rowEntry->set_diameter( welsegsRow.diameter.value() ); + } + if ( welsegsRow.roughness.has_value() ) + { + rowEntry->set_roughness( welsegsRow.roughness.value() ); + } + if ( !welsegsRow.description.empty() ) + { + rowEntry->set_description( welsegsRow.description ); + } + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaWellPathDataToGrpcConverter::copyCompsegsToGrpc( const RigMswTableData& mswTableData, + rips::SimulatorTableData* reply ) +{ + if ( !mswTableData.hasCompsegsData() ) return; + + for ( const auto& compsegsRow : mswTableData.compsegsData() ) + { + auto* grpcCompData = reply->add_compsegs(); + + // 1-based indexing for cell coordinates + grpcCompData->set_i( compsegsRow.i ); + grpcCompData->set_j( compsegsRow.j ); + grpcCompData->set_k( compsegsRow.k ); + + grpcCompData->set_branch( compsegsRow.branch ); + grpcCompData->set_distance_start( compsegsRow.distanceStart ); + grpcCompData->set_distance_end( compsegsRow.distanceEnd ); + + if ( !compsegsRow.gridName.empty() ) + { + grpcCompData->set_grid_name( compsegsRow.gridName ); + } + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaWellPathDataToGrpcConverter::copyWsegvalvToGrpc( const RigMswTableData& mswTableData, + rips::SimulatorTableData* reply ) +{ + if ( !mswTableData.hasWsegvalvData() ) return; + + for ( const auto& wsegvalvRow : mswTableData.wsegvalvData() ) + { + auto* grpcValvData = reply->add_wsegvalv(); + + grpcValvData->set_well_name( wsegvalvRow.well ); + grpcValvData->set_segment_number( wsegvalvRow.segmentNumber ); + grpcValvData->set_cv( wsegvalvRow.cv ); + grpcValvData->set_area( wsegvalvRow.area ); + + if ( wsegvalvRow.extraLength.has_value() ) + { + grpcValvData->set_extra_length( wsegvalvRow.extraLength.value() ); + } + if ( wsegvalvRow.pipeD.has_value() ) + { + grpcValvData->set_pipe_d( wsegvalvRow.pipeD.value() ); + } + if ( wsegvalvRow.roughness.has_value() ) + { + grpcValvData->set_roughness( wsegvalvRow.roughness.value() ); + } + if ( wsegvalvRow.pipeA.has_value() ) + { + grpcValvData->set_pipe_a( wsegvalvRow.pipeA.value() ); + } + if ( wsegvalvRow.status.has_value() ) + { + grpcValvData->set_status( wsegvalvRow.status.value() ); + } + if ( wsegvalvRow.maxA.has_value() ) + { + grpcValvData->set_max_a( wsegvalvRow.maxA.value() ); + } + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiaWellPathDataToGrpcConverter::copyWsegaicdToGrpc( const RigMswTableData& mswTableData, + rips::SimulatorTableData* reply ) +{ + if ( !mswTableData.hasWsegaicdData() ) return; + + for ( const auto& wsegaicdRow : mswTableData.wsegaicdData() ) + { + auto* grpcAicdData = reply->add_wsegaicd(); + + grpcAicdData->set_well_name( wsegaicdRow.well ); + grpcAicdData->set_segment_1( wsegaicdRow.segment1 ); + grpcAicdData->set_segment_2( wsegaicdRow.segment2 ); + grpcAicdData->set_strength( wsegaicdRow.strength ); + grpcAicdData->set_max_abs_rate( wsegaicdRow.maxAbsRate ); + grpcAicdData->set_flow_rate_exponent( wsegaicdRow.flowRateExponent ); + grpcAicdData->set_visc_exponent( wsegaicdRow.viscExponent ); + + if ( wsegaicdRow.length.has_value() ) + { + grpcAicdData->set_length( wsegaicdRow.length.value() ); + } + if ( wsegaicdRow.densityCali.has_value() ) + { + grpcAicdData->set_density_cali( wsegaicdRow.densityCali.value() ); + } + if ( wsegaicdRow.viscosityCali.has_value() ) + { + grpcAicdData->set_viscosity_cali( wsegaicdRow.viscosityCali.value() ); + } + if ( wsegaicdRow.criticalValue.has_value() ) + { + grpcAicdData->set_critical_value( wsegaicdRow.criticalValue.value() ); + } + if ( wsegaicdRow.widthTrans.has_value() ) + { + grpcAicdData->set_width_trans( wsegaicdRow.widthTrans.value() ); + } + if ( wsegaicdRow.maxViscRatio.has_value() ) + { + grpcAicdData->set_max_visc_ratio( wsegaicdRow.maxViscRatio.value() ); + } + if ( wsegaicdRow.methodScalingFactor.has_value() ) + { + grpcAicdData->set_method_scaling_factor( wsegaicdRow.methodScalingFactor.value() ); + } + if ( wsegaicdRow.status.has_value() ) + { + grpcAicdData->set_status( wsegaicdRow.status.value() ); + } + if ( wsegaicdRow.oilFlowFraction.has_value() ) + { + grpcAicdData->set_oil_flow_fraction( wsegaicdRow.oilFlowFraction.value() ); + } + if ( wsegaicdRow.waterFlowFraction.has_value() ) + { + grpcAicdData->set_water_flow_fraction( wsegaicdRow.waterFlowFraction.value() ); + } + if ( wsegaicdRow.gasFlowFraction.has_value() ) + { + grpcAicdData->set_gas_flow_fraction( wsegaicdRow.gasFlowFraction.value() ); + } + if ( wsegaicdRow.oilViscFraction.has_value() ) + { + grpcAicdData->set_oil_visc_fraction( wsegaicdRow.oilViscFraction.value() ); + } + if ( wsegaicdRow.waterViscFraction.has_value() ) + { + grpcAicdData->set_water_visc_fraction( wsegaicdRow.waterViscFraction.value() ); + } + if ( wsegaicdRow.gasViscFraction.has_value() ) + { + grpcAicdData->set_gas_visc_fraction( wsegaicdRow.gasViscFraction.value() ); + } + if ( !wsegaicdRow.description.empty() ) + { + grpcAicdData->set_description( wsegaicdRow.description ); + } + } +} diff --git a/GrpcInterface/RiaWellPathDataToGrpcConverter.h b/GrpcInterface/RiaWellPathDataToGrpcConverter.h new file mode 100644 index 0000000000..5c0e507c4f --- /dev/null +++ b/GrpcInterface/RiaWellPathDataToGrpcConverter.h @@ -0,0 +1,48 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "SimulatorTables.pb.h" + +class RigCompletionData; +class RimWellPathCompletionSettings; +class RimEclipseCase; +class RigMswTableData; + +//================================================================================================== +/// +/// Helper class for converting well path data to gRPC protobuf messages +/// +//================================================================================================== +class RiaWellPathDataToGrpcConverter +{ +public: + static void copyCompdatToGrpc( const RigCompletionData& inputData, rips::SimulatorCompdatEntry* compDat ); + + static void copyWelspecsToGrpc( const RimWellPathCompletionSettings* compSettings, + rips::SimulatorWelspecsEntry* grpcData, + RimEclipseCase* eclipseCase, + int gridI, + int gridJ ); + + static void copyWelsegsToGrpc( const RigMswTableData& mswTableData, rips::SimulatorWelsegsEntry* grpcData ); + static void copyCompsegsToGrpc( const RigMswTableData& mswTableData, rips::SimulatorTableData* reply ); + static void copyWsegvalvToGrpc( const RigMswTableData& mswTableData, rips::SimulatorTableData* reply ); + static void copyWsegaicdToGrpc( const RigMswTableData& mswTableData, rips::SimulatorTableData* reply ); +}; \ No newline at end of file