diff --git a/ApplicationLibCode/ReservoirDataModel/SimulationFile/RigSimulationInputTool.cpp b/ApplicationLibCode/ReservoirDataModel/SimulationFile/RigSimulationInputTool.cpp index 1f7047fcb4..5383c1477a 100644 --- a/ApplicationLibCode/ReservoirDataModel/SimulationFile/RigSimulationInputTool.cpp +++ b/ApplicationLibCode/ReservoirDataModel/SimulationFile/RigSimulationInputTool.cpp @@ -1582,15 +1582,15 @@ RigSimulationInputTool::ConnectionKeySet RigSimulationInputTool::reversedSegment const auto& record = keyword.getRecord( r ); if ( record.size() < 6 ) continue; if ( !record.getItem( 0 ).hasValue( 0 ) || !record.getItem( 1 ).hasValue( 0 ) || !record.getItem( 2 ).hasValue( 0 ) ) continue; - if ( !record.getItem( 4 ).hasValue( 0 ) ) continue; const int i = record.getItem( 0 ).get( 0 ) - 1; const int j = record.getItem( 1 ).get( 0 ) - 1; const int k = record.getItem( 2 ).get( 0 ) - 1; if ( i < 0 || j < 0 || k < 0 ) continue; - const int branch = record.getItem( 3 ).hasValue( 0 ) ? record.getItem( 3 ).get( 0 ) : 0; - cellsPerBranch[branch].push_back( { record.getItem( 4 ).get( 0 ), caf::VecIjk0( i, j, k ) } ); + const int branch = record.getItem( 3 ).hasValue( 0 ) ? record.getItem( 3 ).get( 0 ) : 0; + const double startMd = record.getItem( 4 ).hasValue( 0 ) ? record.getItem( 4 ).get( 0 ) : 0.0; + cellsPerBranch[branch].push_back( { startMd, caf::VecIjk0( i, j, k ) } ); } for ( auto& [branch, cells] : cellsPerBranch ) @@ -1879,12 +1879,37 @@ std::expected, QString> RigSimulationInputTool::pro std::vector subcells = { { ( *transformResult )[dim], 1.0 } }; - const bool hasMdInterval = record.size() > 5 && record.getItem( 4 ).hasValue( 0 ) && record.getItem( 5 ).hasValue( 0 ); - const bool isCellRange = record.size() > 6 && record.getItem( 6 ).hasValue( 0 ); + using C = Opm::ParserKeywords::COMPSEGS; + + auto hasItemValue = [&record]( const std::string& itemName ) + { return record.hasItem( itemName ) && record.getItem( itemName ).hasValue( 0 ); }; + + // A cell range is given by END_IJK. DIRECTION alone (with an MD interval) describes a single cell. + const bool isCellRange = hasItemValue( C::END_IJK::itemName ); + const bool hasEndMd = hasItemValue( C::DISTANCE_END::itemName ); + + // A defaulted start MD is zero (only allowed for the first segment record) + const double startMd = hasItemValue( C::DISTANCE_START::itemName ) ? record.getItem( C::DISTANCE_START::itemName ).get( 0 ) : 0.0; + const double endMd = hasEndMd ? record.getItem( C::DISTANCE_END::itemName ).get( 0 ) : 0.0; + + // Whether the MD interval is divided between the sub-cells, otherwise the record is copied unchanged to each sub-cell + bool splitMdInterval = false; + if ( refinement.subcellCount( dim, sectorIndex ) > 1 ) { - if ( hasMdInterval && !isCellRange ) + if ( isCellRange ) { + RiaLogging::warning( + std::format( "COMPSEGS record for well {} at ({}, {}, {}) uses a cell range (END_IJK), which is not supported " + "for refined sector export. Only the center refined sub-cell is assigned to the segment.", + wellName, + origIjk.x() + 1, + origIjk.y() + 1, + origIjk.z() + 1 ) ); + } + else + { + // All refined sub-cells along the connection are completed by COMPDAT, so all must be assigned to a segment subcells = allSubcells( refinement, dim, sectorIndex ); // Assign the MD interval in the order the well travels through the sub-cells @@ -1892,15 +1917,17 @@ std::expected, QString> RigSimulationInputTool::pro { std::reverse( subcells.begin(), subcells.end() ); } - } - else - { - RiaLogging::warning( std::format( "COMPSEGS record for well {} at ({}, {}, {}) has no MD interval or uses a cell range. " - "Only the center refined sub-cell is assigned to the segment.", - wellName, - origIjk.x() + 1, - origIjk.y() + 1, - origIjk.z() + 1 ) ); + + splitMdInterval = hasEndMd; + if ( !hasEndMd ) + { + RiaLogging::warning( std::format( "COMPSEGS record for well {} at ({}, {}, {}) has no end MD. " + "The record is copied unchanged to each refined sub-cell.", + wellName, + origIjk.x() + 1, + origIjk.y() + 1, + origIjk.z() + 1 ) ); + } } } @@ -1920,11 +1947,10 @@ std::expected, QString> RigSimulationInputTool::pro const double endFraction = startFraction + subcell.fraction; for ( size_t i = 3; i < record.size(); ++i ) { - if ( subcells.size() > 1 && ( i == 4 || i == 5 ) ) + const auto& itemName = record.getItem( i ).name(); + if ( splitMdInterval && ( itemName == C::DISTANCE_START::itemName || itemName == C::DISTANCE_END::itemName ) ) { - const double startMd = record.getItem( 4 ).get( 0 ); - const double endMd = record.getItem( 5 ).get( 0 ); - const double fraction = ( i == 4 ) ? startFraction : endFraction; + const double fraction = ( itemName == C::DISTANCE_START::itemName ) ? startFraction : endFraction; items.push_back( RifOpmDeckTools::item( record.getItem( i ).name(), startMd + ( endMd - startMd ) * fraction ) ); } else diff --git a/ApplicationLibCode/ReservoirDataModel/SimulationFile/RigSimulationInputTool.h b/ApplicationLibCode/ReservoirDataModel/SimulationFile/RigSimulationInputTool.h index b66c45e40d..5bbcc1ded8 100644 --- a/ApplicationLibCode/ReservoirDataModel/SimulationFile/RigSimulationInputTool.h +++ b/ApplicationLibCode/ReservoirDataModel/SimulationFile/RigSimulationInputTool.h @@ -143,7 +143,8 @@ public: const ConnectionCellPropertiesProvider& cellProperties = {} ); // Segment records are split into one record per sub-cell along the connection penetration direction, - // with the MD interval divided by the sub-cell length fractions in the order the well travels through them + // with the MD interval divided by the sub-cell length fractions in the order the well travels through them. + // Without an end MD the record is copied unchanged to each sub-cell. Cell ranges (END_IJK) use the center sub-cell. static std::expected, QString> processCompsegsRecord( const Opm::DeckRecord& record, const std::string& wellName, bool isWellNameRecord, diff --git a/ApplicationLibCode/UnitTests/RigSimulationInputTool-Test.cpp b/ApplicationLibCode/UnitTests/RigSimulationInputTool-Test.cpp index 08fff24f8a..60255ed81d 100644 --- a/ApplicationLibCode/UnitTests/RigSimulationInputTool-Test.cpp +++ b/ApplicationLibCode/UnitTests/RigSimulationInputTool-Test.cpp @@ -3346,3 +3346,85 @@ TEST( RigSimulationInputTool, ReversedSegmentCells_ForwardTraversalNotReversed ) EXPECT_TRUE( reversedCells.empty() ); } + +//-------------------------------------------------------------------------------------------------- +/// DIRECTION without END_IJK is a single cell, so the MD interval is still split between the sub-cells +//-------------------------------------------------------------------------------------------------- +TEST( RigSimulationInputTool, ProcessCompsegsRecord_DirectionWithoutRangeIsSplit ) +{ + auto settings = createSectorSettings( cvf::Vec3st( 1, 1, 2 ) ); + + Opm::Parser parser; + auto deck = parser.parseString( "COMPSEGS\n 'W' /\n 3 4 2 1 100.0 120.0 'K' /\n/\n" ); + + const auto& compsegs = deck["COMPSEGS"].back(); + auto result = RigSimulationInputTool::processCompsegsRecord( compsegs.getRecord( 1 ), "W", false, settings, {} ); + ASSERT_TRUE( result.has_value() ) << result.error().toStdString(); + ASSERT_EQ( 2u, result->size() ); + EXPECT_EQ( 3, result->at( 0 ).getItem( 2 ).get( 0 ) ); + EXPECT_DOUBLE_EQ( 110.0, result->at( 0 ).getItem( 5 ).get( 0 ) ); + EXPECT_EQ( 4, result->at( 1 ).getItem( 2 ).get( 0 ) ); + EXPECT_DOUBLE_EQ( 110.0, result->at( 1 ).getItem( 4 ).get( 0 ) ); + EXPECT_EQ( "K", result->at( 1 ).getItem( 6 ).get( 0 ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// A defaulted start MD is zero +//-------------------------------------------------------------------------------------------------- +TEST( RigSimulationInputTool, ProcessCompsegsRecord_DefaultedStartMdIsZero ) +{ + auto settings = createSectorSettings( cvf::Vec3st( 1, 1, 2 ) ); + + Opm::Parser parser; + auto deck = parser.parseString( "COMPSEGS\n 'W' /\n 3 4 2 1 1* 20.0 /\n/\n" ); + + const auto& compsegs = deck["COMPSEGS"].back(); + auto result = RigSimulationInputTool::processCompsegsRecord( compsegs.getRecord( 1 ), "W", false, settings, {} ); + ASSERT_TRUE( result.has_value() ) << result.error().toStdString(); + ASSERT_EQ( 2u, result->size() ); + EXPECT_DOUBLE_EQ( 0.0, result->at( 0 ).getItem( 4 ).get( 0 ) ); + EXPECT_DOUBLE_EQ( 10.0, result->at( 0 ).getItem( 5 ).get( 0 ) ); + EXPECT_DOUBLE_EQ( 10.0, result->at( 1 ).getItem( 4 ).get( 0 ) ); + EXPECT_DOUBLE_EQ( 20.0, result->at( 1 ).getItem( 5 ).get( 0 ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// Without an end MD every sub-cell still gets a segment record, with the MD items unchanged +//-------------------------------------------------------------------------------------------------- +TEST( RigSimulationInputTool, ProcessCompsegsRecord_NoEndMdCopiedToAllSubcells ) +{ + auto settings = createSectorSettings( cvf::Vec3st( 1, 1, 2 ) ); + + Opm::Parser parser; + auto deck = parser.parseString( "COMPSEGS\n 'W' /\n 3 4 2 1 100.0 1* 'K' /\n/\n" ); + + const auto& compsegs = deck["COMPSEGS"].back(); + auto result = RigSimulationInputTool::processCompsegsRecord( compsegs.getRecord( 1 ), "W", false, settings, {} ); + ASSERT_TRUE( result.has_value() ) << result.error().toStdString(); + ASSERT_EQ( 2u, result->size() ); + for ( size_t s = 0; s < 2; ++s ) + { + EXPECT_EQ( 3 + static_cast( s ), result->at( s ).getItem( 2 ).get( 0 ) ); + EXPECT_DOUBLE_EQ( 100.0, result->at( s ).getItem( 4 ).get( 0 ) ); + EXPECT_FALSE( result->at( s ).getItem( 5 ).hasValue( 0 ) ); + } +} + +//-------------------------------------------------------------------------------------------------- +/// A cell range (END_IJK) is not split, only the center sub-cell is used +//-------------------------------------------------------------------------------------------------- +TEST( RigSimulationInputTool, ProcessCompsegsRecord_CellRangeUsesCenterSubcell ) +{ + auto settings = createSectorSettings( cvf::Vec3st( 1, 1, 2 ) ); + + Opm::Parser parser; + auto deck = parser.parseString( "COMPSEGS\n 'W' /\n 3 4 2 1 100.0 120.0 'K' 3 /\n/\n" ); + + const auto& compsegs = deck["COMPSEGS"].back(); + auto result = RigSimulationInputTool::processCompsegsRecord( compsegs.getRecord( 1 ), "W", false, settings, {} ); + ASSERT_TRUE( result.has_value() ) << result.error().toStdString(); + ASSERT_EQ( 1u, result->size() ); + EXPECT_EQ( 4, result->at( 0 ).getItem( 2 ).get( 0 ) ); + EXPECT_DOUBLE_EQ( 100.0, result->at( 0 ).getItem( 4 ).get( 0 ) ); + EXPECT_DOUBLE_EQ( 120.0, result->at( 0 ).getItem( 5 ).get( 0 ) ); +}