#14804 MSW export: Segmentation controls per interval and around perforations

Add fixed, min and max segment length per segment interval (fixed is exclusive with min/max, min and max can be combined), connect COMPSEGS rows to the nearest segment node, and add collection options for a single segment before the first and after the last perforation.
This commit is contained in:
Magne Sjaastad committed 2026-10-12 19:12:08 +02:00
1 parent 2f7de9469f
commit d8f346e9ab
22 files changed
+1866 -149

No files matched your search

@@ -44,7 +44,7 @@ TEST( RicMswBranchBuilder, FindOutlet_EmptyMap )
//--------------------------------------------------------------------------------------------------
TEST( RicMswBranchBuilder, FindOutlet_MDInFirstCell )
{
std::vector<CellSegmentEntry> map = { { 100.0, 200.0, 5 }, { 200.0, 300.0, 6 }, { 300.0, 400.0, 7 } };
std::vector<CellSegmentEntry> map = { { 100.0, 200.0, 5, 150.0 }, { 200.0, 300.0, 6, 250.0 }, { 300.0, 400.0, 7, 350.0 } };
EXPECT_EQ( 5, findOutletSegmentForMD( map, 150.0 ) );
}
@@ -53,7 +53,7 @@ TEST( RicMswBranchBuilder, FindOutlet_MDInFirstCell )
//--------------------------------------------------------------------------------------------------
TEST( RicMswBranchBuilder, FindOutlet_MDInMiddleCell )
{
std::vector<CellSegmentEntry> map = { { 100.0, 200.0, 5 }, { 200.0, 300.0, 6 }, { 300.0, 400.0, 7 } };
std::vector<CellSegmentEntry> map = { { 100.0, 200.0, 5, 150.0 }, { 200.0, 300.0, 6, 250.0 }, { 300.0, 400.0, 7, 350.0 } };
EXPECT_EQ( 6, findOutletSegmentForMD( map, 250.0 ) );
}
@@ -62,7 +62,7 @@ TEST( RicMswBranchBuilder, FindOutlet_MDInMiddleCell )
//--------------------------------------------------------------------------------------------------
TEST( RicMswBranchBuilder, FindOutlet_MDInLastCell )
{
std::vector<CellSegmentEntry> map = { { 100.0, 200.0, 5 }, { 200.0, 300.0, 6 }, { 300.0, 400.0, 7 } };
std::vector<CellSegmentEntry> map = { { 100.0, 200.0, 5, 150.0 }, { 200.0, 300.0, 6, 250.0 }, { 300.0, 400.0, 7, 350.0 } };
EXPECT_EQ( 7, findOutletSegmentForMD( map, 350.0 ) );
}
@@ -74,7 +74,7 @@ TEST( RicMswBranchBuilder, FindOutlet_MDInLastCell )
//--------------------------------------------------------------------------------------------------
TEST( RicMswBranchBuilder, FindOutlet_MDAtCellStart )
{
std::vector<CellSegmentEntry> map = { { 100.0, 200.0, 5 }, { 200.0, 300.0, 6 } };
std::vector<CellSegmentEntry> map = { { 100.0, 200.0, 5, 150.0 }, { 200.0, 300.0, 6, 250.0 } };
EXPECT_EQ( 5, findOutletSegmentForMD( map, 100.0 ) ); // shallower than midpoint 150 → first seg
EXPECT_EQ( 5, findOutletSegmentForMD( map, 200.0 ) ); // midpoint 150 ≤ 200 < midpoint 250 → seg 5
}
@@ -85,7 +85,7 @@ TEST( RicMswBranchBuilder, FindOutlet_MDAtCellStart )
//--------------------------------------------------------------------------------------------------
TEST( RicMswBranchBuilder, FindOutlet_MDAtCellEnd_ExclusiveBoundary )
{
std::vector<CellSegmentEntry> map = { { 100.0, 200.0, 5 }, { 200.0, 300.0, 6 } };
std::vector<CellSegmentEntry> map = { { 100.0, 200.0, 5, 150.0 }, { 200.0, 300.0, 6, 250.0 } };
EXPECT_EQ( 5, findOutletSegmentForMD( map, 200.0 ) );
}
@@ -95,7 +95,7 @@ TEST( RicMswBranchBuilder, FindOutlet_MDAtCellEnd_ExclusiveBoundary )
//--------------------------------------------------------------------------------------------------
TEST( RicMswBranchBuilder, FindOutlet_MDBelowAllCells )
{
std::vector<CellSegmentEntry> map = { { 100.0, 200.0, 5 }, { 200.0, 300.0, 6 } };
std::vector<CellSegmentEntry> map = { { 100.0, 200.0, 5, 150.0 }, { 200.0, 300.0, 6, 250.0 } };
EXPECT_EQ( 5, findOutletSegmentForMD( map, 50.0 ) ); // shallower than all midpoints → first seg
}
@@ -104,7 +104,7 @@ TEST( RicMswBranchBuilder, FindOutlet_MDBelowAllCells )
//--------------------------------------------------------------------------------------------------
TEST( RicMswBranchBuilder, FindOutlet_MDBeyondAllCells )
{
std::vector<CellSegmentEntry> map = { { 100.0, 200.0, 5 }, { 200.0, 300.0, 6 }, { 300.0, 400.0, 7 } };
std::vector<CellSegmentEntry> map = { { 100.0, 200.0, 5, 150.0 }, { 200.0, 300.0, 6, 250.0 }, { 300.0, 400.0, 7, 350.0 } };
EXPECT_EQ( 7, findOutletSegmentForMD( map, 999.0 ) );
}
@@ -113,12 +113,224 @@ TEST( RicMswBranchBuilder, FindOutlet_MDBeyondAllCells )
//--------------------------------------------------------------------------------------------------
TEST( RicMswBranchBuilder, FindOutlet_SingleCell )
{
std::vector<CellSegmentEntry> map = { { 0.0, 100.0, 3 } };
std::vector<CellSegmentEntry> map = { { 0.0, 100.0, 3, 50.0 } };
EXPECT_EQ( 3, findOutletSegmentForMD( map, 50.0 ) );
EXPECT_EQ( 3, findOutletSegmentForMD( map, 0.0 ) );
EXPECT_EQ( 3, findOutletSegmentForMD( map, 200.0 ) ); // beyond → fallback = 3
}
//==================================================================================================
// Segmentation helpers
//==================================================================================================
namespace
{
//--------------------------------------------------------------------------------------------------
/// Cells [0,10] [10,14] [14,30] [30,33] [33,60] from the heel at MD 0, centres 5, 12, 22, 31.5, 46.5
//--------------------------------------------------------------------------------------------------
const std::vector<std::pair<double, double>> exampleCells = { { 0.0, 10.0 }, { 10.0, 14.0 }, { 14.0, 30.0 }, { 30.0, 33.0 }, { 33.0, 60.0 } };
std::vector<double> centres( const std::vector<std::pair<double, double>>& pieces )
{
std::vector<double> result;
for ( const auto& [start, end] : pieces )
{
result.push_back( 0.5 * ( start + end ) );
}
return result;
}
void expectNodes( const std::vector<double>& expected, const std::vector<double>& actual )
{
ASSERT_EQ( expected.size(), actual.size() );
for ( size_t i = 0; i < expected.size(); ++i )
{
EXPECT_NEAR( expected[i], actual[i], 1.0e-9 ) << "node " << i;
}
}
} // namespace
//==================================================================================================
// Segment node placement tests
//==================================================================================================
using RicMswTableDataTools::SegmentationInterval;
TEST( RicMswBranchBuilder, PlaceSegmentNodes_NoRuleKeepsCellCentres )
{
const auto candidates = centres( exampleCells );
expectNodes( candidates, RicMswTableDataTools::placeSegmentNodes( 0.0, candidates, {} ) );
expectNodes( candidates, RicMswTableDataTools::placeSegmentNodes( 0.0, candidates, { { 0.0, 60.0, std::nullopt, std::nullopt } } ) );
}
TEST( RicMswBranchBuilder, PlaceSegmentNodes_MinLength )
{
const auto nodes = RicMswTableDataTools::placeSegmentNodes( 0.0, centres( exampleCells ), { { 0.0, 60.0, 12.0, std::nullopt } } );
expectNodes( { 12.0, 31.5, 46.5 }, nodes );
}
TEST( RicMswBranchBuilder, PlaceSegmentNodes_MaxLength )
{
const auto nodes = RicMswTableDataTools::placeSegmentNodes( 0.0, centres( exampleCells ), { { 0.0, 60.0, std::nullopt, 10.0 } } );
expectNodes( { 5.0, 12.0, 22.0, 31.5, 39.0, 46.5 }, nodes );
}
TEST( RicMswBranchBuilder, PlaceSegmentNodes_MaxLengthInsertsSeveralNodes )
{
const auto nodes = RicMswTableDataTools::placeSegmentNodes( 0.0, { 5.0, 35.0 }, { { 0.0, 60.0, std::nullopt, 10.0 } } );
expectNodes( { 5.0, 15.0, 25.0, 35.0 }, nodes );
}
TEST( RicMswBranchBuilder, PlaceSegmentNodes_MinLengthLargerThanIntervalKeepsOneNode )
{
const auto nodes = RicMswTableDataTools::placeSegmentNodes( 0.0, centres( exampleCells ), { { 0.0, 60.0, 100.0, std::nullopt } } );
expectNodes( { 46.5 }, nodes );
}
TEST( RicMswBranchBuilder, PlaceSegmentNodes_IntervalStartingMidCell )
{
// The cell [14,30] is split at the interval start 20
const std::vector<std::pair<double, double>> pieces =
{ { 0.0, 10.0 }, { 10.0, 14.0 }, { 14.0, 20.0 }, { 20.0, 30.0 }, { 30.0, 33.0 }, { 33.0, 60.0 } };
const auto nodes = RicMswTableDataTools::placeSegmentNodes( 0.0, centres( pieces ), { { 20.0, 60.0, 12.0, std::nullopt } } );
// The spacing inside the interval is measured from the last node before it, 17
expectNodes( { 5.0, 12.0, 17.0, 31.5, 46.5 }, nodes );
}
TEST( RicMswBranchBuilder, PlaceSegmentNodes_BackToBackIntervals )
{
const std::vector<SegmentationInterval> intervals = { { 0.0, 14.0, 12.0, std::nullopt }, { 14.0, 60.0, std::nullopt, 10.0 } };
const auto nodes = RicMswTableDataTools::placeSegmentNodes( 0.0, centres( exampleCells ), intervals );
expectNodes( { 12.0, 22.0, 31.5, 39.0, 46.5 }, nodes );
}
TEST( RicMswBranchBuilder, PlaceSegmentNodes_MeasuredFromOutletNode )
{
// The outlet node at 100 is the heel of the branch, the first candidate is too close to it
const auto nodes = RicMswTableDataTools::placeSegmentNodes( 100.0, { 105.0, 115.0, 125.0 }, { { 100.0, 130.0, 12.0, std::nullopt } } );
expectNodes( { 115.0 }, nodes );
}
TEST( RicMswBranchBuilder, PlaceSegmentNodes_MinAndMaxLengthCombined )
{
const auto nodes = RicMswTableDataTools::placeSegmentNodes( 0.0, centres( exampleCells ), { { 0.0, 60.0, 12.0, 15.0 } } );
expectNodes( { 12.0, 21.75, 31.5, 46.5 }, nodes );
}
TEST( RicMswBranchBuilder, PlaceSegmentNodes_FixedLength )
{
// Segments [0,20], [20,40], [40,60] with nodes at their centres
expectNodes( { 10.0, 30.0, 50.0 },
RicMswTableDataTools::placeSegmentNodes( 0.0, centres( exampleCells ), { { 0.0, 60.0, std::nullopt, std::nullopt, 20.0 } } ) );
// Cell centres outside the interval are kept, the fixed segments start at the interval start
expectNodes( { 5.0, 12.0, 30.0, 50.0 },
RicMswTableDataTools::placeSegmentNodes( 0.0, centres( exampleCells ), { { 20.0, 60.0, std::nullopt, std::nullopt, 20.0 } } ) );
// The last segment is cut at the end of the branch
expectNodes( { 10.0, 30.0, 42.5 },
RicMswTableDataTools::placeSegmentNodes( 0.0, centres( exampleCells ), { { 0.0, 50.0, std::nullopt, std::nullopt, 20.0 } }, 45.0 ) );
}
TEST( RicMswBranchBuilder, CollapseNodesOutsidePerforations )
{
const std::vector<double> nodes = { 5.0, 12.0, 22.0, 31.5, 46.5, 55.0 };
expectNodes( { 10.0, 22.0, 31.5, 46.5, 55.0 },
RicMswTableDataTools::collapseNodesOutsidePerforations( nodes, 0.0, 60.0, 20.0, 40.0, true, false ) );
expectNodes( { 5.0, 12.0, 22.0, 31.5, 50.0 },
RicMswTableDataTools::collapseNodesOutsidePerforations( nodes, 0.0, 60.0, 20.0, 40.0, false, true ) );
expectNodes( nodes, RicMswTableDataTools::collapseNodesOutsidePerforations( nodes, 0.0, 60.0, 20.0, 40.0, false, false ) );
// A single node outside the perforations is unchanged
expectNodes( { 5.0, 22.0, 31.5 },
RicMswTableDataTools::collapseNodesOutsidePerforations( { 5.0, 22.0, 31.5 }, 0.0, 60.0, 20.0, 40.0, true, true ) );
}
//==================================================================================================
// Upstream node tests
//==================================================================================================
TEST( RicMswBranchBuilder, UpstreamNodeIndex )
{
const std::vector<double> nodes = { 12.0, 31.5, 46.5 };
EXPECT_FALSE( RicMswTableDataTools::upstreamNodeIndex( {}, 10.0 ).has_value() );
// Upstream of all nodes, the most upstream node is used
EXPECT_EQ( std::optional<size_t>( 0 ), RicMswTableDataTools::upstreamNodeIndex( nodes, 5.0 ) );
// Cell 3 in the example, centre 22, is closer to 31.5 but 12 is the nearest node upstream
EXPECT_EQ( std::optional<size_t>( 0 ), RicMswTableDataTools::upstreamNodeIndex( nodes, 22.0 ) );
EXPECT_EQ( std::optional<size_t>( 1 ), RicMswTableDataTools::upstreamNodeIndex( nodes, 46.0 ) );
EXPECT_EQ( std::optional<size_t>( 2 ), RicMswTableDataTools::upstreamNodeIndex( nodes, 100.0 ) );
}
TEST( RicMswBranchBuilder, UpstreamNodeIndex_NodeAtMDIsUsed )
{
EXPECT_EQ( std::optional<size_t>( 1 ), RicMswTableDataTools::upstreamNodeIndex( { 10.0, 20.0 }, 20.0 ) );
EXPECT_EQ( std::optional<size_t>( 0 ), RicMswTableDataTools::upstreamNodeIndex( { 10.0, 20.0 }, 19.999 ) );
}
//==================================================================================================
// Nearest node tests
//==================================================================================================
TEST( RicMswBranchBuilder, NearestNodeIndex )
{
const std::vector<double> nodes = { 12.0, 31.5, 46.5 };
EXPECT_FALSE( RicMswTableDataTools::nearestNodeIndex( {}, 10.0 ).has_value() );
EXPECT_EQ( std::optional<size_t>( 0 ), RicMswTableDataTools::nearestNodeIndex( nodes, 5.0 ) );
// Cell 3 in the example, centre 22, is 9.5 from 31.5 and 10 from 12
EXPECT_EQ( std::optional<size_t>( 1 ), RicMswTableDataTools::nearestNodeIndex( nodes, 22.0 ) );
EXPECT_EQ( std::optional<size_t>( 2 ), RicMswTableDataTools::nearestNodeIndex( nodes, 100.0 ) );
}
TEST( RicMswBranchBuilder, NearestNodeIndex_TieGoesToFirstNode )
{
EXPECT_EQ( std::optional<size_t>( 0 ), RicMswTableDataTools::nearestNodeIndex( { 10.0, 20.0 }, 15.0 ) );
}
//==================================================================================================
// Segment span tests
//==================================================================================================
namespace
{
void expectSpans( const std::vector<NodeSpan>& expected, const std::vector<NodeSpan>& actual )
{
ASSERT_EQ( expected.size(), actual.size() );
for ( size_t i = 0; i < expected.size(); ++i )
{
EXPECT_NEAR( expected[i].startMD, actual[i].startMD, 1.0e-9 ) << "span " << i;
EXPECT_NEAR( expected[i].endMD, actual[i].endMD, 1.0e-9 ) << "span " << i;
EXPECT_EQ( expected[i].nodeIndex, actual[i].nodeIndex ) << "span " << i;
}
}
} // namespace
TEST( RicMswBranchBuilder, SegmentSpans_NoRuleOneSpanPerCell )
{
expectSpans( { { 0.0, 10.0, 0 }, { 10.0, 14.0, 1 }, { 14.0, 30.0, 2 }, { 30.0, 33.0, 3 }, { 33.0, 60.0, 4 } },
segmentSpans( exampleCells, centres( exampleCells ) ) );
}
TEST( RicMswBranchBuilder, SegmentSpans_MinLengthSpansSeveralCells )
{
expectSpans( { { 0.0, 31.5, 0 }, { 31.5, 46.5, 1 }, { 46.5, 60.0, 2 } }, segmentSpans( exampleCells, { 12.0, 31.5, 46.5 } ) );
}
TEST( RicMswBranchBuilder, SegmentSpans_MaxLengthSplitsCellsAtNodes )
{
expectSpans( { { 0.0, 12.0, 0 }, { 12.0, 22.0, 1 }, { 22.0, 31.5, 2 }, { 31.5, 39.0, 3 }, { 39.0, 46.5, 4 }, { 46.5, 60.0, 5 } },
segmentSpans( exampleCells, { 5.0, 12.0, 22.0, 31.5, 39.0, 46.5 } ) );
}
//==================================================================================================
// toMswCellIntersection tests
//==================================================================================================