mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-25 06:38:13 -05:00
#14646 MSW: Report a grid cell only once across COMPSEGS branches
A grid cell has a single COMPDAT connection and must be connected to exactly one segment, but the geometry-based export emitted one COMPSEGS row per branch intersecting the cell. Restore the well-global bookkeeping the removed tree path had: tag each branch with its origin and let the branch carrying most flow claim a shared cell, perforations before fishbones before fractures.
This commit is contained in:
1 parent
377751eb68
commit
d085f6ce74
4 files changed
+126
-10
No files matched your search
+3
-3
@@ -848,7 +848,7 @@ std::vector<RigMswBranch> buildFractureBranches( RimEclipseCase*
|
||||
seg.description = fracture->name().toStdString();
|
||||
seg.intersections = std::move( compsegs );
|
||||
|
||||
result.push_back( RigMswBranch{ fracBranch, std::nullopt, { std::move( seg ) } } );
|
||||
result.push_back( RigMswBranch{ fracBranch, std::nullopt, { std::move( seg ) }, RigMswBranchSource::Fracture } );
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -993,7 +993,7 @@ std::vector<RigMswBranch> buildFishbonesBranches( const RimEclipseCase*
|
||||
|
||||
fishbonesContext.icdSegments.push_back( { icdSegNum, std::move( icdGlobalCellIndices ) } );
|
||||
|
||||
result.push_back( RigMswBranch{ icdBranch, std::nullopt, { std::move( icdSeg ) } } );
|
||||
result.push_back( RigMswBranch{ icdBranch, std::nullopt, { std::move( icdSeg ) }, RigMswBranchSource::Fishbones } );
|
||||
|
||||
// Lateral sub-segments
|
||||
for ( size_t lateralIndex : lateralIndices )
|
||||
@@ -1099,7 +1099,7 @@ std::vector<RigMswBranch> buildFishbonesBranches( const RimEclipseCase*
|
||||
|
||||
fishbonesContext.laterals.push_back( std::move( lateralContext ) );
|
||||
|
||||
result.push_back( RigMswBranch{ latBranch, std::nullopt, std::move( latSegs ) } );
|
||||
result.push_back( RigMswBranch{ latBranch, std::nullopt, std::move( latSegs ), RigMswBranchSource::Fishbones } );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+37
-5
@@ -41,6 +41,9 @@
|
||||
#include "cafAssert.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <set>
|
||||
#include <tuple>
|
||||
|
||||
namespace RicWellPathExportMswGeometryPath
|
||||
{
|
||||
@@ -504,7 +507,13 @@ RigMswTableData collectTableData( const RigMswWellExportData& exportData, RiaDef
|
||||
tableData.setWelsegsHeader( exportData.header );
|
||||
|
||||
// Collected while the segments are visited, and added to the table data ordered by branch number.
|
||||
std::vector<CompsegsRow> compsegsRows;
|
||||
// The branch source is kept alongside the row to resolve cells claimed by several branches.
|
||||
struct CollectedCompsegsRow
|
||||
{
|
||||
CompsegsRow row;
|
||||
RigMswBranchSource source;
|
||||
};
|
||||
std::vector<CollectedCompsegsRow> compsegsRows;
|
||||
|
||||
auto emitSegment = [&]( const RigMswBranch& branch, const RigMswSegment& seg )
|
||||
{
|
||||
@@ -533,7 +542,7 @@ RigMswTableData collectTableData( const RigMswWellExportData& exportData, RiaDef
|
||||
compRow.distanceStart = inter.distanceStart;
|
||||
compRow.distanceEnd = inter.distanceEnd;
|
||||
compRow.gridName = inter.gridName;
|
||||
compsegsRows.push_back( compRow );
|
||||
compsegsRows.push_back( { compRow, branch.source } );
|
||||
}
|
||||
|
||||
// WSEGVALV row
|
||||
@@ -554,14 +563,37 @@ RigMswTableData collectTableData( const RigMswWellExportData& exportData, RiaDef
|
||||
tableData.addMswBranch( branch );
|
||||
}
|
||||
|
||||
// A grid cell has a single COMPDAT connection and must be connected to exactly one segment. When
|
||||
// several branches intersect the same cell, the branch carrying most flow claims it: perforations
|
||||
// first, then fishbones, then fractures. Visit the rows in that order and drop the later claims.
|
||||
std::vector<size_t> claimOrder( compsegsRows.size() );
|
||||
std::iota( claimOrder.begin(), claimOrder.end(), size_t( 0 ) );
|
||||
std::stable_sort( claimOrder.begin(),
|
||||
claimOrder.end(),
|
||||
[&]( size_t lhs, size_t rhs ) { return compsegsRows[lhs].source < compsegsRows[rhs].source; } );
|
||||
|
||||
std::set<std::tuple<std::string, size_t, size_t, size_t>> connectedCells;
|
||||
std::vector<bool> isCellOwner( compsegsRows.size(), false );
|
||||
for ( size_t index : claimOrder )
|
||||
{
|
||||
const auto& row = compsegsRows[index].row;
|
||||
isCellOwner[index] = connectedCells.emplace( row.gridName, row.i, row.j, row.k ).second;
|
||||
}
|
||||
|
||||
// COMPSEGS is ordered by branch number. WELSEGS keeps the order the branches are listed in, where
|
||||
// the completion branches follow the lateral they are connected to and thus are not sorted by
|
||||
// branch number. The sort is stable, so the rows of a branch keep their order along the well path.
|
||||
std::stable_sort( compsegsRows.begin(),
|
||||
compsegsRows.end(),
|
||||
std::vector<CompsegsRow> ownedRows;
|
||||
for ( size_t index = 0; index < compsegsRows.size(); ++index )
|
||||
{
|
||||
if ( isCellOwner[index] ) ownedRows.push_back( compsegsRows[index].row );
|
||||
}
|
||||
|
||||
std::stable_sort( ownedRows.begin(),
|
||||
ownedRows.end(),
|
||||
[]( const CompsegsRow& lhs, const CompsegsRow& rhs ) { return lhs.branch < rhs.branch; } );
|
||||
|
||||
for ( const auto& compsegsRow : compsegsRows )
|
||||
for ( const auto& compsegsRow : ownedRows )
|
||||
{
|
||||
tableData.addCompsegsRow( compsegsRow );
|
||||
}
|
||||
|
||||
@@ -68,6 +68,19 @@ struct RigMswSegment
|
||||
};
|
||||
|
||||
|
||||
//==================================================================================================
|
||||
/// Origin of a branch, used to decide which branch connects a grid cell when several branches
|
||||
/// intersect the same cell. The enum is ordered by priority, so the branch carrying most flow
|
||||
/// claims the cell first.
|
||||
//==================================================================================================
|
||||
enum class RigMswBranchSource
|
||||
{
|
||||
Perforation, // Main bore and perforation valve branches
|
||||
Fishbones,
|
||||
Fracture
|
||||
};
|
||||
|
||||
|
||||
//==================================================================================================
|
||||
/// One branch in the MSW export.
|
||||
/// All segments share the same IBRANCH number, which is stored here rather than per-segment.
|
||||
@@ -78,6 +91,8 @@ struct RigMswBranch
|
||||
int branchNumber; // IBRANCH for all segments in this branch
|
||||
std::optional<RigMswSegment> tieInValve; // Optional ICV at the tie-in point (laterals only)
|
||||
std::vector<RigMswSegment> segments; // Segments of this branch
|
||||
|
||||
RigMswBranchSource source = RigMswBranchSource::Perforation; // Decides COMPSEGS cell ownership
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -62,9 +62,19 @@ RigMswSegment makeSegment( int segNum, int outletSegNum, double length = 10.0, d
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Build a minimal RigMswBranch with the given branch number and segments.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
RigMswBranch makeBranch( int branchNum, std::vector<RigMswSegment> segs )
|
||||
RigMswBranch makeBranch( int branchNum, std::vector<RigMswSegment> segs, RigMswBranchSource source = RigMswBranchSource::Perforation )
|
||||
{
|
||||
return RigMswBranch{ branchNum, std::nullopt, std::move( segs ) };
|
||||
return RigMswBranch{ branchNum, std::nullopt, std::move( segs ), source };
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Build a branch with a single segment intersecting the given main grid cell.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
RigMswBranch makeBranchIntersectingCell( int branchNum, int segNum, size_t i, size_t j, size_t k, RigMswBranchSource source )
|
||||
{
|
||||
RigMswSegment seg = makeSegment( segNum, 1 );
|
||||
seg.intersections = { RigMswCellIntersection{ i, j, k, 100.0, 110.0, "" } };
|
||||
return makeBranch( branchNum, { seg }, source );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -431,6 +441,65 @@ TEST( RicWellPathExportMswGeometryPath, HeaderFieldsPropagated )
|
||||
EXPECT_EQ( RiaDefines::EclipseUnitSystem::UNITS_FIELD, result.unitSystem() );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// A grid cell has a single COMPDAT connection and must be connected to one segment only. When two
|
||||
/// branches of the same kind intersect the cell, the branch listed first keeps it.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RicWellPathExportMswGeometryPath, CompsegsCellIsConnectedOnlyOnce )
|
||||
{
|
||||
RigMswWellExportData exportData;
|
||||
exportData.header = makeHeader();
|
||||
|
||||
exportData.branches = { makeBranchIntersectingCell( 2, 10, 3, 5, 7, RigMswBranchSource::Fracture ),
|
||||
makeBranchIntersectingCell( 3, 11, 3, 5, 7, RigMswBranchSource::Fracture ) };
|
||||
|
||||
auto result = RicWellPathExportMswGeometryPath::collectTableData( exportData, RiaDefines::EclipseUnitSystem::UNITS_METRIC );
|
||||
|
||||
ASSERT_EQ( 2u, result.welsegsData().size() );
|
||||
ASSERT_EQ( 1u, result.compsegsData().size() );
|
||||
EXPECT_EQ( 2, result.compsegsData()[0].branch );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// The branch carrying most flow claims a shared cell: perforations before fishbones before
|
||||
/// fractures, regardless of the order the branches are listed in.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RicWellPathExportMswGeometryPath, CompsegsCellIsClaimedByTheBranchWithMostFlow )
|
||||
{
|
||||
RigMswWellExportData exportData;
|
||||
exportData.header = makeHeader();
|
||||
|
||||
exportData.branches = { makeBranchIntersectingCell( 4, 12, 3, 5, 7, RigMswBranchSource::Fracture ),
|
||||
makeBranchIntersectingCell( 3, 11, 3, 5, 7, RigMswBranchSource::Fishbones ),
|
||||
makeBranchIntersectingCell( 2, 10, 3, 5, 7, RigMswBranchSource::Perforation ) };
|
||||
|
||||
auto result = RicWellPathExportMswGeometryPath::collectTableData( exportData, RiaDefines::EclipseUnitSystem::UNITS_METRIC );
|
||||
|
||||
ASSERT_EQ( 1u, result.compsegsData().size() );
|
||||
EXPECT_EQ( 2, result.compsegsData()[0].branch );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// The same IJK in the main grid and in an LGR are different cells, and both are connected.
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RicWellPathExportMswGeometryPath, CompsegsSeparatesMainGridAndLgrCells )
|
||||
{
|
||||
RigMswWellExportData exportData;
|
||||
exportData.header = makeHeader();
|
||||
|
||||
RigMswSegment lgrSeg = makeSegment( 11, 1 );
|
||||
lgrSeg.intersections = { RigMswCellIntersection{ 3, 5, 7, 100.0, 110.0, "LGR_NEAR_WELL" } };
|
||||
|
||||
exportData.branches = { makeBranchIntersectingCell( 2, 10, 3, 5, 7, RigMswBranchSource::Fracture ),
|
||||
makeBranch( 3, { lgrSeg }, RigMswBranchSource::Fracture ) };
|
||||
|
||||
auto result = RicWellPathExportMswGeometryPath::collectTableData( exportData, RiaDefines::EclipseUnitSystem::UNITS_METRIC );
|
||||
|
||||
ASSERT_EQ( 2u, result.compsegsData().size() );
|
||||
EXPECT_TRUE( result.compsegsData()[0].isMainGrid() );
|
||||
EXPECT_EQ( "LGR_NEAR_WELL", result.compsegsData()[1].gridName );
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Branch numbering, using the multiple_laterals test project.
|
||||
//
|
||||
|
||||
Reference in new issue
Block a user