Sector export: Assign all refined sub-cells to segments in COMPSEGS

Only END_IJK denotes a cell range, DIRECTION alone is a single cell and the MD
interval is split. A defaulted start MD is zero. Without an end MD the record is
copied to each sub-cell so all refined COMPDAT connections are attached to a segment.
This commit is contained in:
Kristian Bendiksen committed 2026-10-21 14:42:53 +02:00
1 parent 2a8cd7856b
commit 66c2cd47d3
3 files changed
+129 -20

No files matched your search

@@ -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<int>( 0 ) - 1;
const int j = record.getItem( 1 ).get<int>( 0 ) - 1;
const int k = record.getItem( 2 ).get<int>( 0 ) - 1;
if ( i < 0 || j < 0 || k < 0 ) continue;
const int branch = record.getItem( 3 ).hasValue( 0 ) ? record.getItem( 3 ).get<int>( 0 ) : 0;
cellsPerBranch[branch].push_back( { record.getItem( 4 ).get<double>( 0 ), caf::VecIjk0( i, j, k ) } );
const int branch = record.getItem( 3 ).hasValue( 0 ) ? record.getItem( 3 ).get<int>( 0 ) : 0;
const double startMd = record.getItem( 4 ).hasValue( 0 ) ? record.getItem( 4 ).get<double>( 0 ) : 0.0;
cellsPerBranch[branch].push_back( { startMd, caf::VecIjk0( i, j, k ) } );
}
for ( auto& [branch, cells] : cellsPerBranch )
@@ -1879,12 +1879,37 @@ std::expected<std::vector<Opm::DeckRecord>, QString> RigSimulationInputTool::pro
std::vector<RefinedSubcell> 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<double>( 0 ) : 0.0;
const double endMd = hasEndMd ? record.getItem( C::DISTANCE_END::itemName ).get<double>( 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<std::vector<Opm::DeckRecord>, 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<std::vector<Opm::DeckRecord>, 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<double>( 0 );
const double endMd = record.getItem( 5 ).get<double>( 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
@@ -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<std::vector<Opm::DeckRecord>, QString> processCompsegsRecord( const Opm::DeckRecord& record,
const std::string& wellName,
bool isWellNameRecord,
@@ -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<int>( 0 ) );
EXPECT_DOUBLE_EQ( 110.0, result->at( 0 ).getItem( 5 ).get<double>( 0 ) );
EXPECT_EQ( 4, result->at( 1 ).getItem( 2 ).get<int>( 0 ) );
EXPECT_DOUBLE_EQ( 110.0, result->at( 1 ).getItem( 4 ).get<double>( 0 ) );
EXPECT_EQ( "K", result->at( 1 ).getItem( 6 ).get<std::string>( 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<double>( 0 ) );
EXPECT_DOUBLE_EQ( 10.0, result->at( 0 ).getItem( 5 ).get<double>( 0 ) );
EXPECT_DOUBLE_EQ( 10.0, result->at( 1 ).getItem( 4 ).get<double>( 0 ) );
EXPECT_DOUBLE_EQ( 20.0, result->at( 1 ).getItem( 5 ).get<double>( 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<int>( s ), result->at( s ).getItem( 2 ).get<int>( 0 ) );
EXPECT_DOUBLE_EQ( 100.0, result->at( s ).getItem( 4 ).get<double>( 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<int>( 0 ) );
EXPECT_DOUBLE_EQ( 100.0, result->at( 0 ).getItem( 4 ).get<double>( 0 ) );
EXPECT_DOUBLE_EQ( 120.0, result->at( 0 ).getItem( 5 ).get<double>( 0 ) );
}