2026-03-25 07:40:10 +01:00
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
//
|
|
|
|
|
// Copyright (C) 2026 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 <http://www.gnu.org/licenses/gpl.html>
|
|
|
|
|
// for more details.
|
|
|
|
|
//
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
|
#include "RicWellPathExportMswGeometryPath.h"
|
|
|
|
|
|
|
|
|
|
#include "RicMswBranchBuilder.h"
|
|
|
|
|
#include "RicMswTableDataTools.h"
|
|
|
|
|
#include "RicWellPathExportMswTableData.h"
|
|
|
|
|
|
|
|
|
|
#include "CompletionsMsw/RigMswTableData.h"
|
|
|
|
|
|
|
|
|
|
#include "RigActiveCellInfo.h"
|
|
|
|
|
#include "RigEclipseCaseData.h"
|
|
|
|
|
#include "Well/RigWellPath.h"
|
|
|
|
|
|
|
|
|
|
#include "RimEclipseCase.h"
|
|
|
|
|
#include "RimPerforationCollection.h"
|
|
|
|
|
#include "RimPerforationInterval.h"
|
2026-09-04 15:23:32 +02:00
|
|
|
#include "RimSegmentCollection.h"
|
2026-03-25 07:40:10 +01:00
|
|
|
#include "RimValveCollection.h"
|
|
|
|
|
#include "RimWellPath.h"
|
|
|
|
|
#include "RimWellPathCompletionSettings.h"
|
|
|
|
|
#include "RimWellPathTieIn.h"
|
|
|
|
|
#include "RimWellPathValve.h"
|
|
|
|
|
|
2026-08-06 21:18:33 +02:00
|
|
|
#include "cafAssert.h"
|
|
|
|
|
|
2026-03-25 07:40:10 +01:00
|
|
|
#include <algorithm>
|
2026-08-28 21:53:25 +02:00
|
|
|
#include <numeric>
|
|
|
|
|
#include <set>
|
|
|
|
|
#include <tuple>
|
2026-03-25 07:40:10 +01:00
|
|
|
|
|
|
|
|
namespace RicWellPathExportMswGeometryPath
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
using CompletionType = RicWellPathExportMswTableData::CompletionType;
|
|
|
|
|
|
2026-08-08 17:17:49 +02:00
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
|
|
|
/// Number of well path laterals in the tie-in tree, including the given well path itself.
|
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
|
|
|
static int lateralCount( const RimWellPath* wellPath )
|
|
|
|
|
{
|
|
|
|
|
int count = 1;
|
|
|
|
|
for ( const auto* childWellPath : RicWellPathExportMswTableData::wellPathsWithTieIn( wellPath ) )
|
|
|
|
|
{
|
|
|
|
|
count += lateralCount( childWellPath );
|
|
|
|
|
}
|
|
|
|
|
return count;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 07:40:10 +01:00
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
|
|
|
/// Recursively build all WELSEGS/COMPSEGS/valve segments for one lateral (child well path)
|
|
|
|
|
/// and any of its own child laterals.
|
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
2026-08-06 14:14:43 +02:00
|
|
|
std::vector<RigMswBranch> buildLateralBranches( RimEclipseCase* eclipseCase,
|
|
|
|
|
const RimWellPath* wellPath,
|
|
|
|
|
const RigMainGrid* mainGrid,
|
|
|
|
|
int outletSegNum,
|
|
|
|
|
CompletionType completionType,
|
|
|
|
|
const std::optional<QDateTime>& exportDate,
|
|
|
|
|
int& segmentNumber,
|
2026-08-08 17:17:49 +02:00
|
|
|
int& lateralBranchNumber,
|
|
|
|
|
int& completionBranchNumber,
|
2026-08-06 14:14:43 +02:00
|
|
|
RiaDefines::EclipseUnitSystem unitSystem,
|
|
|
|
|
RicMswBranchBuilder::FishbonesExportContext& fishbonesContext )
|
2026-03-25 07:40:10 +01:00
|
|
|
{
|
|
|
|
|
std::vector<RigMswBranch> result;
|
2026-09-04 15:23:32 +02:00
|
|
|
auto mswParameters = wellPath->segmentCollection();
|
2026-03-25 07:40:10 +01:00
|
|
|
if ( !mswParameters ) return result;
|
|
|
|
|
|
2026-10-16 14:47:35 +02:00
|
|
|
const std::string infoType = wellPath->completionSettings()->lengthAndDepth().text().toStdString();
|
2026-09-11 14:26:31 +02:00
|
|
|
const std::vector<std::pair<double, double>> segmentIntervals = mswParameters->getSegmentIntervals();
|
|
|
|
|
const double tieInMD = wellPath->wellPathTieIn()->tieInMeasuredDepth();
|
|
|
|
|
const double tieInTVD = -wellPath->wellPathGeometry()->interpolatedPointAlongWellPath( tieInMD ).z();
|
|
|
|
|
const std::string wellNameForExport = wellPath->completionSettings()->wellNameForExport().toStdString();
|
2026-03-25 07:40:10 +01:00
|
|
|
|
2026-08-08 17:17:49 +02:00
|
|
|
const int lateralBranchNum = ++lateralBranchNumber;
|
2026-03-25 07:40:10 +01:00
|
|
|
int childOutletSeg = outletSegNum;
|
|
|
|
|
|
|
|
|
|
// Optional ICV valve at the tie-in point — stored on the branch, not as a separate branch
|
|
|
|
|
std::optional<RigMswSegment> tieInValve;
|
|
|
|
|
const RimWellPathValve* outletValve = wellPath->wellPathTieIn()->outletValve();
|
|
|
|
|
if ( outletValve && outletValve->isChecked() )
|
|
|
|
|
{
|
|
|
|
|
const bool isActive = !exportDate.has_value() || outletValve->isActiveOnDate( *exportDate );
|
|
|
|
|
if ( isActive )
|
|
|
|
|
{
|
|
|
|
|
const double valveMD = wellPath->wellPathTieIn()->branchValveMeasuredDepth();
|
|
|
|
|
const double offset = ( valveMD == tieInMD ) ? RicMswTableDataTools::valveSegmentLength : 0.0;
|
|
|
|
|
const double valveEndMD = valveMD + offset;
|
|
|
|
|
const double valveStartTVD = RicMswTableDataTools::tvdFromMeasuredDepth( wellPath, valveMD );
|
|
|
|
|
const double valveEndTVD = RicMswTableDataTools::tvdFromMeasuredDepth( wellPath, valveEndMD );
|
|
|
|
|
|
|
|
|
|
double length = 0.0;
|
|
|
|
|
double depth = 0.0;
|
|
|
|
|
if ( infoType == "INC" )
|
|
|
|
|
{
|
|
|
|
|
length = valveEndMD - valveMD;
|
|
|
|
|
depth = valveEndTVD - valveStartTVD;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
length = valveEndMD;
|
|
|
|
|
depth = valveEndTVD;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const int valveSegNum = segmentNumber++;
|
|
|
|
|
|
|
|
|
|
RigMswSegment valveSeg;
|
|
|
|
|
valveSeg.segmentNumber = valveSegNum;
|
|
|
|
|
valveSeg.outletSegmentNumber = outletSegNum;
|
|
|
|
|
valveSeg.length = length;
|
|
|
|
|
valveSeg.depth = depth;
|
|
|
|
|
valveSeg.diameter = mswParameters->linerDiameter( unitSystem );
|
|
|
|
|
valveSeg.roughness = mswParameters->roughnessFactor( unitSystem );
|
|
|
|
|
valveSeg.sourceWellName = wellPath->name().toStdString();
|
|
|
|
|
valveSeg.description = QString( "%1 valve for %2" ).arg( outletValve->componentLabel() ).arg( wellPath->name() ).toStdString();
|
|
|
|
|
|
|
|
|
|
WsegvalvRow wv;
|
|
|
|
|
wv.well = wellNameForExport;
|
|
|
|
|
wv.segmentNumber = valveSegNum;
|
|
|
|
|
wv.cv = outletValve->flowCoefficient();
|
|
|
|
|
wv.area = outletValve->area( unitSystem );
|
|
|
|
|
wv.status = outletValve->isOpen() ? "OPEN" : "SHUT";
|
|
|
|
|
wv.description = valveSeg.description;
|
|
|
|
|
valveSeg.wsegvalvData = wv;
|
|
|
|
|
|
|
|
|
|
tieInValve = std::move( valveSeg );
|
|
|
|
|
childOutletSeg = valveSegNum;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Child main-bore segments
|
|
|
|
|
auto cellIntersections = RicWellPathExportMswTableData::generateCellSegments( eclipseCase, wellPath );
|
|
|
|
|
auto filteredIntersections =
|
|
|
|
|
RicWellPathExportMswTableData::filterIntersections( cellIntersections, tieInMD, wellPath->wellPathGeometry(), eclipseCase );
|
|
|
|
|
|
|
|
|
|
const bool includePerforations = ( completionType & CompletionType::PERFORATIONS ) == CompletionType::PERFORATIONS;
|
|
|
|
|
const std::vector<const RimPerforationInterval*> perforationIntervals =
|
|
|
|
|
includePerforations ? wellPath->perforationIntervalCollection()->activePerforations() : std::vector<const RimPerforationInterval*>();
|
|
|
|
|
|
|
|
|
|
std::set<const RimPerforationInterval*> valvedIntervals;
|
|
|
|
|
for ( const auto* perf : perforationIntervals )
|
|
|
|
|
{
|
|
|
|
|
if ( exportDate.has_value() && !perf->isActiveOnDate( exportDate.value() ) ) continue;
|
|
|
|
|
for ( const auto* valve : perf->descendantsIncludingThisOfType<RimWellPathValve>() )
|
|
|
|
|
{
|
|
|
|
|
if ( !valve->isChecked() ) continue;
|
|
|
|
|
if ( exportDate.has_value() && !valve->isActiveOnDate( exportDate.value() ) ) continue;
|
|
|
|
|
const auto t = valve->componentType();
|
|
|
|
|
if ( t == RiaDefines::WellPathComponentType::ICD || t == RiaDefines::WellPathComponentType::ICV ||
|
|
|
|
|
t == RiaDefines::WellPathComponentType::AICD || t == RiaDefines::WellPathComponentType::SICD )
|
|
|
|
|
{
|
|
|
|
|
valvedIntervals.insert( perf );
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<RicMswBranchBuilder::CellSegmentEntry> childCellSegMap;
|
|
|
|
|
|
|
|
|
|
const RigActiveCellInfo* activeCellInfo = eclipseCase->eclipseCaseData()->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL );
|
|
|
|
|
|
2026-06-18 13:58:45 +02:00
|
|
|
auto lateralBranch = RicMswBranchBuilder::buildMainBoreBranch( wellPath,
|
|
|
|
|
filteredIntersections,
|
|
|
|
|
mainGrid,
|
|
|
|
|
eclipseCase,
|
|
|
|
|
perforationIntervals,
|
|
|
|
|
valvedIntervals,
|
|
|
|
|
infoType,
|
|
|
|
|
tieInMD,
|
|
|
|
|
tieInTVD,
|
|
|
|
|
lateralBranchNum,
|
|
|
|
|
segmentNumber,
|
|
|
|
|
childOutletSeg,
|
2026-09-11 14:26:31 +02:00
|
|
|
segmentIntervals,
|
2026-10-09 14:36:40 +02:00
|
|
|
RicMswTableDataTools::segmentationSettings( mswParameters, exportDate ),
|
2026-06-18 13:58:45 +02:00
|
|
|
exportDate,
|
|
|
|
|
unitSystem,
|
|
|
|
|
&childCellSegMap,
|
|
|
|
|
activeCellInfo );
|
2026-08-06 15:32:04 +02:00
|
|
|
// Identify the segments of this lateral by name in the exported comment line
|
|
|
|
|
if ( !lateralBranch.segments.empty() )
|
|
|
|
|
{
|
|
|
|
|
lateralBranch.segments.front().description = QString( "Segments on lateral %1" ).arg( wellPath->name() ).toStdString();
|
|
|
|
|
}
|
2026-03-25 07:40:10 +01:00
|
|
|
lateralBranch.tieInValve = std::move( tieInValve );
|
|
|
|
|
|
|
|
|
|
// Standalone valves (from valveCollection, not inside perforation intervals)
|
|
|
|
|
for ( const auto* valve : wellPath->valveCollection()->activeValves() )
|
|
|
|
|
{
|
|
|
|
|
if ( exportDate.has_value() && !valve->isActiveOnDate( *exportDate ) ) continue;
|
|
|
|
|
|
|
|
|
|
const int segNum = RicMswBranchBuilder::findOutletSegmentForMD( childCellSegMap, valve->startMD() );
|
|
|
|
|
for ( auto& seg : lateralBranch.segments )
|
|
|
|
|
{
|
|
|
|
|
if ( seg.segmentNumber == segNum )
|
|
|
|
|
{
|
|
|
|
|
WsegvalvRow wv;
|
|
|
|
|
wv.well = wellNameForExport;
|
|
|
|
|
wv.segmentNumber = segNum;
|
|
|
|
|
wv.cv = valve->flowCoefficient();
|
|
|
|
|
wv.area = valve->area( unitSystem );
|
|
|
|
|
wv.status = valve->isOpen() ? "OPEN" : "SHUT";
|
|
|
|
|
wv.description = valve->name().toStdString();
|
|
|
|
|
seg.wsegvalvData = wv;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result.push_back( std::move( lateralBranch ) );
|
|
|
|
|
|
2026-06-18 13:58:45 +02:00
|
|
|
auto valveBranches = RicMswBranchBuilder::buildValveBranches( wellPath,
|
|
|
|
|
filteredIntersections,
|
|
|
|
|
mainGrid,
|
|
|
|
|
eclipseCase,
|
|
|
|
|
perforationIntervals,
|
|
|
|
|
childCellSegMap,
|
|
|
|
|
infoType,
|
|
|
|
|
wellNameForExport,
|
|
|
|
|
segmentNumber,
|
2026-08-08 17:17:49 +02:00
|
|
|
completionBranchNumber,
|
2026-09-11 14:26:31 +02:00
|
|
|
segmentIntervals,
|
2026-06-18 13:58:45 +02:00
|
|
|
exportDate,
|
|
|
|
|
unitSystem );
|
2026-03-25 07:40:10 +01:00
|
|
|
result.insert( result.end(), std::make_move_iterator( valveBranches.begin() ), std::make_move_iterator( valveBranches.end() ) );
|
|
|
|
|
|
|
|
|
|
if ( ( completionType & CompletionType::FRACTURES ) == CompletionType::FRACTURES )
|
|
|
|
|
{
|
2026-08-08 17:17:49 +02:00
|
|
|
auto fracBranches = RicMswBranchBuilder::buildFractureBranches( eclipseCase,
|
|
|
|
|
wellPath,
|
|
|
|
|
mainGrid,
|
|
|
|
|
childCellSegMap,
|
|
|
|
|
infoType,
|
|
|
|
|
segmentNumber,
|
|
|
|
|
completionBranchNumber );
|
2026-03-25 07:40:10 +01:00
|
|
|
result.insert( result.end(), std::make_move_iterator( fracBranches.begin() ), std::make_move_iterator( fracBranches.end() ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( ( completionType & CompletionType::FISHBONES ) == CompletionType::FISHBONES )
|
|
|
|
|
{
|
2026-06-18 13:58:45 +02:00
|
|
|
auto fishBranches = RicMswBranchBuilder::buildFishbonesBranches( eclipseCase,
|
|
|
|
|
wellPath,
|
|
|
|
|
mainGrid,
|
|
|
|
|
filteredIntersections,
|
|
|
|
|
childCellSegMap,
|
|
|
|
|
infoType,
|
|
|
|
|
wellNameForExport,
|
|
|
|
|
segmentNumber,
|
2026-08-08 17:17:49 +02:00
|
|
|
completionBranchNumber,
|
2026-09-11 14:26:31 +02:00
|
|
|
segmentIntervals,
|
2026-08-06 14:06:48 +02:00
|
|
|
unitSystem,
|
2026-08-06 14:14:43 +02:00
|
|
|
fishbonesContext );
|
2026-03-25 07:40:10 +01:00
|
|
|
result.insert( result.end(), std::make_move_iterator( fishBranches.begin() ), std::make_move_iterator( fishBranches.end() ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Recurse into grandchildren
|
|
|
|
|
for ( auto* grandchild : RicWellPathExportMswTableData::wellPathsWithTieIn( wellPath ) )
|
|
|
|
|
{
|
|
|
|
|
const int grandchildOutlet =
|
|
|
|
|
RicMswBranchBuilder::findOutletSegmentForMD( childCellSegMap, grandchild->wellPathTieIn()->branchValveMeasuredDepth() );
|
2026-08-06 14:06:48 +02:00
|
|
|
auto grandchildBranches = buildLateralBranches( eclipseCase,
|
|
|
|
|
grandchild,
|
|
|
|
|
mainGrid,
|
|
|
|
|
grandchildOutlet,
|
|
|
|
|
completionType,
|
|
|
|
|
exportDate,
|
|
|
|
|
segmentNumber,
|
2026-08-08 17:17:49 +02:00
|
|
|
lateralBranchNumber,
|
|
|
|
|
completionBranchNumber,
|
2026-08-06 14:06:48 +02:00
|
|
|
unitSystem,
|
2026-08-06 14:14:43 +02:00
|
|
|
fishbonesContext );
|
2026-03-25 07:40:10 +01:00
|
|
|
result.insert( result.end(), std::make_move_iterator( grandchildBranches.begin() ), std::make_move_iterator( grandchildBranches.end() ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
2026-06-18 13:54:23 +02:00
|
|
|
/// Build MSW export data directly from well-path geometry and Rim completion objects.
|
2026-03-25 07:40:10 +01:00
|
|
|
///
|
2026-06-18 13:55:44 +02:00
|
|
|
/// Handles main-bore segments, perforation and valve completions (ICD/AICD/SICD/ICV),
|
|
|
|
|
/// fractures, fishbones laterals, and tie-in child laterals.
|
2026-03-25 07:40:10 +01:00
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
|
|
|
RigMswWellExportData buildMswWellExportData( RimEclipseCase* eclipseCase,
|
|
|
|
|
const RimWellPath* wellPath,
|
|
|
|
|
const std::vector<std::pair<double, double>>& customSegmentIntervals,
|
|
|
|
|
CompletionType completionType,
|
|
|
|
|
const std::optional<QDateTime>& exportDate )
|
|
|
|
|
{
|
2026-09-04 15:23:32 +02:00
|
|
|
auto mswParameters = wellPath->segmentCollection();
|
2026-08-06 21:18:33 +02:00
|
|
|
CAF_ASSERT( mswParameters );
|
2026-03-25 07:40:10 +01:00
|
|
|
|
|
|
|
|
const RiaDefines::EclipseUnitSystem unitSystem = eclipseCase->eclipseCaseData()->unitsType();
|
|
|
|
|
const RigMainGrid* mainGrid = eclipseCase->mainGrid();
|
2026-10-16 14:47:35 +02:00
|
|
|
const std::string infoType = wellPath->completionSettings()->lengthAndDepth().text().toStdString();
|
2026-03-25 07:40:10 +01:00
|
|
|
|
|
|
|
|
auto cellIntersections = RicWellPathExportMswTableData::generateCellSegments( eclipseCase, wellPath );
|
|
|
|
|
double initialMD = RicWellPathExportMswTableData::computeIntitialMeasuredDepth( eclipseCase, wellPath, mswParameters, cellIntersections );
|
|
|
|
|
double initialTVD = -wellPath->wellPathGeometry()->interpolatedPointAlongWellPath( initialMD ).z();
|
|
|
|
|
|
|
|
|
|
auto filteredIntersections =
|
|
|
|
|
RicWellPathExportMswTableData::filterIntersections( cellIntersections, initialMD, wellPath->wellPathGeometry(), eclipseCase );
|
|
|
|
|
|
|
|
|
|
WelsegsHeader header;
|
|
|
|
|
header.well = wellPath->completionSettings()->wellNameForExport().toStdString();
|
|
|
|
|
header.topLength = initialMD;
|
|
|
|
|
header.topDepth = initialTVD;
|
|
|
|
|
header.infoType = infoType;
|
2026-10-16 14:47:35 +02:00
|
|
|
header.pressureComponents = wellPath->completionSettings()->pressureDrop().text().toStdString();
|
2026-03-25 07:40:10 +01:00
|
|
|
|
|
|
|
|
const bool includePerforations = ( completionType & CompletionType::PERFORATIONS ) == CompletionType::PERFORATIONS;
|
|
|
|
|
const std::vector<const RimPerforationInterval*> perforationIntervals =
|
|
|
|
|
includePerforations ? wellPath->perforationIntervalCollection()->activePerforations() : std::vector<const RimPerforationInterval*>();
|
|
|
|
|
|
|
|
|
|
// Determine which perforation intervals have at least one active, checked valve.
|
|
|
|
|
// Main bore COMPSEGS are skipped for these; the valve builder will emit them instead.
|
|
|
|
|
std::set<const RimPerforationInterval*> valvedIntervals;
|
|
|
|
|
for ( const auto* perf : perforationIntervals )
|
|
|
|
|
{
|
|
|
|
|
if ( exportDate.has_value() && !perf->isActiveOnDate( exportDate.value() ) ) continue;
|
|
|
|
|
for ( const auto* valve : perf->descendantsIncludingThisOfType<RimWellPathValve>() )
|
|
|
|
|
{
|
|
|
|
|
if ( !valve->isChecked() ) continue;
|
|
|
|
|
if ( exportDate.has_value() && !valve->isActiveOnDate( exportDate.value() ) ) continue;
|
|
|
|
|
const auto t = valve->componentType();
|
|
|
|
|
if ( t == RiaDefines::WellPathComponentType::ICD || t == RiaDefines::WellPathComponentType::ICV ||
|
|
|
|
|
t == RiaDefines::WellPathComponentType::AICD || t == RiaDefines::WellPathComponentType::SICD )
|
|
|
|
|
{
|
|
|
|
|
valvedIntervals.insert( perf );
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-08 17:17:49 +02:00
|
|
|
int segmentNumber = 2; // Segment 1 is the implicit well heel.
|
|
|
|
|
|
|
|
|
|
// The well path laterals are given their branch number first, so that the main bore and the
|
|
|
|
|
// laterals occupy the lowest branch numbers. The completion branches (valves, fractures and
|
|
|
|
|
// fishbones) are numbered after all laterals, but are still listed immediately after the
|
|
|
|
|
// lateral they are connected to.
|
|
|
|
|
int lateralBranchNumber = 1; // Main bore is branch 1, incremented for each lateral.
|
|
|
|
|
int completionBranchNumber = lateralCount( wellPath ); // Incremented for each completion branch.
|
|
|
|
|
|
2026-03-25 07:40:10 +01:00
|
|
|
std::vector<RicMswBranchBuilder::CellSegmentEntry> cellSegMap;
|
|
|
|
|
|
2026-08-06 14:06:48 +02:00
|
|
|
// Collected across the main bore and all tie-in laterals, applied once the branches are assembled.
|
2026-08-06 14:14:43 +02:00
|
|
|
RicMswBranchBuilder::FishbonesExportContext fishbonesContext;
|
2026-08-06 14:06:48 +02:00
|
|
|
|
2026-03-25 07:40:10 +01:00
|
|
|
const RigActiveCellInfo* activeCellInfo = eclipseCase->eclipseCaseData()->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL );
|
|
|
|
|
|
2026-06-18 13:58:45 +02:00
|
|
|
auto mainBoreBranch = RicMswBranchBuilder::buildMainBoreBranch( wellPath,
|
|
|
|
|
filteredIntersections,
|
|
|
|
|
mainGrid,
|
|
|
|
|
eclipseCase,
|
|
|
|
|
perforationIntervals,
|
|
|
|
|
valvedIntervals,
|
|
|
|
|
infoType,
|
|
|
|
|
initialMD,
|
|
|
|
|
initialTVD,
|
2026-08-08 17:17:49 +02:00
|
|
|
lateralBranchNumber,
|
2026-06-18 13:58:45 +02:00
|
|
|
segmentNumber,
|
|
|
|
|
1, // outlet = heel (segment 1)
|
|
|
|
|
customSegmentIntervals,
|
2026-10-09 14:36:40 +02:00
|
|
|
RicMswTableDataTools::segmentationSettings( mswParameters, exportDate ),
|
2026-06-18 13:58:45 +02:00
|
|
|
exportDate,
|
|
|
|
|
unitSystem,
|
|
|
|
|
&cellSegMap,
|
|
|
|
|
activeCellInfo );
|
2026-03-25 07:40:10 +01:00
|
|
|
|
|
|
|
|
const std::string wellNameForExport = wellPath->completionSettings()->wellNameForExport().toStdString();
|
|
|
|
|
|
|
|
|
|
// Standalone valves (from valveCollection, not inside perforation intervals)
|
|
|
|
|
for ( const auto* valve : wellPath->valveCollection()->activeValves() )
|
|
|
|
|
{
|
|
|
|
|
if ( exportDate.has_value() && !valve->isActiveOnDate( *exportDate ) ) continue;
|
|
|
|
|
|
|
|
|
|
const int segNum = RicMswBranchBuilder::findOutletSegmentForMD( cellSegMap, valve->startMD() );
|
|
|
|
|
for ( auto& seg : mainBoreBranch.segments )
|
|
|
|
|
{
|
|
|
|
|
if ( seg.segmentNumber == segNum )
|
|
|
|
|
{
|
|
|
|
|
WsegvalvRow wv;
|
|
|
|
|
wv.well = wellNameForExport;
|
|
|
|
|
wv.segmentNumber = segNum;
|
|
|
|
|
wv.cv = valve->flowCoefficient();
|
|
|
|
|
wv.area = valve->area( unitSystem );
|
|
|
|
|
wv.status = valve->isOpen() ? "OPEN" : "SHUT";
|
|
|
|
|
wv.description = valve->name().toStdString();
|
|
|
|
|
seg.wsegvalvData = wv;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-18 13:58:45 +02:00
|
|
|
auto valveBranches = RicMswBranchBuilder::buildValveBranches( wellPath,
|
|
|
|
|
filteredIntersections,
|
|
|
|
|
mainGrid,
|
|
|
|
|
eclipseCase,
|
|
|
|
|
perforationIntervals,
|
|
|
|
|
cellSegMap,
|
|
|
|
|
infoType,
|
|
|
|
|
wellNameForExport,
|
|
|
|
|
segmentNumber,
|
2026-08-08 17:17:49 +02:00
|
|
|
completionBranchNumber,
|
2026-06-18 13:58:45 +02:00
|
|
|
customSegmentIntervals,
|
|
|
|
|
exportDate,
|
|
|
|
|
unitSystem );
|
2026-03-25 07:40:10 +01:00
|
|
|
|
|
|
|
|
const bool includeFractures = ( completionType & CompletionType::FRACTURES ) == CompletionType::FRACTURES;
|
|
|
|
|
std::vector<RigMswBranch> fractureBranches;
|
|
|
|
|
if ( includeFractures )
|
|
|
|
|
{
|
|
|
|
|
fractureBranches =
|
2026-08-08 17:17:49 +02:00
|
|
|
RicMswBranchBuilder::buildFractureBranches( eclipseCase, wellPath, mainGrid, cellSegMap, infoType, segmentNumber, completionBranchNumber );
|
2026-03-25 07:40:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const bool includeFishbones = ( completionType & CompletionType::FISHBONES ) == CompletionType::FISHBONES;
|
|
|
|
|
std::vector<RigMswBranch> fishbonesBranches;
|
|
|
|
|
if ( includeFishbones )
|
|
|
|
|
{
|
2026-06-18 13:58:45 +02:00
|
|
|
fishbonesBranches = RicMswBranchBuilder::buildFishbonesBranches( eclipseCase,
|
|
|
|
|
wellPath,
|
|
|
|
|
mainGrid,
|
|
|
|
|
filteredIntersections,
|
|
|
|
|
cellSegMap,
|
|
|
|
|
infoType,
|
|
|
|
|
wellNameForExport,
|
|
|
|
|
segmentNumber,
|
2026-08-08 17:17:49 +02:00
|
|
|
completionBranchNumber,
|
2026-08-06 15:19:33 +02:00
|
|
|
customSegmentIntervals,
|
2026-08-06 14:06:48 +02:00
|
|
|
unitSystem,
|
2026-08-06 14:14:43 +02:00
|
|
|
fishbonesContext );
|
2026-03-25 07:40:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Tie-in child laterals (recursive)
|
|
|
|
|
std::vector<RigMswBranch> lateralBranches;
|
|
|
|
|
for ( auto* childWellPath : RicWellPathExportMswTableData::wellPathsWithTieIn( wellPath ) )
|
|
|
|
|
{
|
|
|
|
|
const int childOutlet =
|
|
|
|
|
RicMswBranchBuilder::findOutletSegmentForMD( cellSegMap, childWellPath->wellPathTieIn()->branchValveMeasuredDepth() );
|
2026-08-06 14:06:48 +02:00
|
|
|
auto childBranches = buildLateralBranches( eclipseCase,
|
|
|
|
|
childWellPath,
|
|
|
|
|
mainGrid,
|
|
|
|
|
childOutlet,
|
|
|
|
|
completionType,
|
|
|
|
|
exportDate,
|
|
|
|
|
segmentNumber,
|
2026-08-08 17:17:49 +02:00
|
|
|
lateralBranchNumber,
|
|
|
|
|
completionBranchNumber,
|
2026-08-06 14:06:48 +02:00
|
|
|
unitSystem,
|
2026-08-06 14:14:43 +02:00
|
|
|
fishbonesContext );
|
2026-03-25 07:40:10 +01:00
|
|
|
lateralBranches.insert( lateralBranches.end(),
|
|
|
|
|
std::make_move_iterator( childBranches.begin() ),
|
|
|
|
|
std::make_move_iterator( childBranches.end() ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RigMswWellExportData result;
|
|
|
|
|
result.header = header;
|
|
|
|
|
result.branches.push_back( std::move( mainBoreBranch ) );
|
|
|
|
|
result.branches.insert( result.branches.end(),
|
|
|
|
|
std::make_move_iterator( valveBranches.begin() ),
|
|
|
|
|
std::make_move_iterator( valveBranches.end() ) );
|
|
|
|
|
result.branches.insert( result.branches.end(),
|
|
|
|
|
std::make_move_iterator( fractureBranches.begin() ),
|
|
|
|
|
std::make_move_iterator( fractureBranches.end() ) );
|
|
|
|
|
result.branches.insert( result.branches.end(),
|
|
|
|
|
std::make_move_iterator( fishbonesBranches.begin() ),
|
|
|
|
|
std::make_move_iterator( fishbonesBranches.end() ) );
|
|
|
|
|
result.branches.insert( result.branches.end(),
|
|
|
|
|
std::make_move_iterator( lateralBranches.begin() ),
|
|
|
|
|
std::make_move_iterator( lateralBranches.end() ) );
|
2026-08-06 14:06:48 +02:00
|
|
|
|
2026-08-06 14:14:43 +02:00
|
|
|
RicMswBranchBuilder::applyEffectiveDiameters( fishbonesContext, result );
|
|
|
|
|
RicMswBranchBuilder::applyIcdAreaPerCell( fishbonesContext, result );
|
2026-08-06 14:06:48 +02:00
|
|
|
|
2026-03-25 07:40:10 +01:00
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
2026-06-18 13:54:23 +02:00
|
|
|
/// Populate RigMswTableData from a pre-built RigMswWellExportData by simple iteration over
|
|
|
|
|
/// RigMswSegment objects.
|
2026-03-25 07:40:10 +01:00
|
|
|
//--------------------------------------------------------------------------------------------------
|
2026-10-09 14:36:40 +02:00
|
|
|
RigMswTableData collectTableData( const RigMswWellExportData& exportData, RiaDefines::EclipseUnitSystem unitSystem, bool exportSegmentNumbers )
|
2026-03-25 07:40:10 +01:00
|
|
|
{
|
|
|
|
|
RigMswTableData tableData( exportData.header.well, unitSystem );
|
|
|
|
|
tableData.setWelsegsHeader( exportData.header );
|
|
|
|
|
|
2026-08-08 17:24:27 +02:00
|
|
|
// Collected while the segments are visited, and added to the table data ordered by branch number.
|
2026-08-28 21:53:25 +02:00
|
|
|
// 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;
|
2026-08-08 17:24:27 +02:00
|
|
|
|
2026-03-25 07:40:10 +01:00
|
|
|
auto emitSegment = [&]( const RigMswBranch& branch, const RigMswSegment& seg )
|
|
|
|
|
{
|
|
|
|
|
// WELSEGS row
|
|
|
|
|
WelsegsRow welsegsRow;
|
|
|
|
|
welsegsRow.segment1 = seg.segmentNumber;
|
|
|
|
|
welsegsRow.segment2 = seg.segmentNumber;
|
|
|
|
|
welsegsRow.branch = branch.branchNumber;
|
|
|
|
|
welsegsRow.joinSegment = seg.outletSegmentNumber;
|
|
|
|
|
welsegsRow.length = seg.length;
|
|
|
|
|
welsegsRow.depth = seg.depth;
|
|
|
|
|
welsegsRow.diameter = seg.diameter;
|
|
|
|
|
welsegsRow.roughness = seg.roughness;
|
|
|
|
|
welsegsRow.description = seg.description;
|
|
|
|
|
welsegsRow.sourceWellName = seg.sourceWellName;
|
|
|
|
|
tableData.addWelsegsRow( welsegsRow );
|
|
|
|
|
|
|
|
|
|
// COMPSEGS rows
|
|
|
|
|
for ( const auto& inter : seg.intersections )
|
|
|
|
|
{
|
|
|
|
|
CompsegsRow compRow;
|
|
|
|
|
compRow.i = inter.i;
|
|
|
|
|
compRow.j = inter.j;
|
|
|
|
|
compRow.k = inter.k;
|
|
|
|
|
compRow.branch = branch.branchNumber;
|
|
|
|
|
compRow.distanceStart = inter.distanceStart;
|
|
|
|
|
compRow.distanceEnd = inter.distanceEnd;
|
|
|
|
|
compRow.gridName = inter.gridName;
|
2026-10-09 14:36:40 +02:00
|
|
|
if ( exportSegmentNumbers ) compRow.segmentNumber = seg.segmentNumber;
|
2026-08-28 21:53:25 +02:00
|
|
|
compsegsRows.push_back( { compRow, branch.source } );
|
2026-03-25 07:40:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WSEGVALV row
|
|
|
|
|
if ( seg.wsegvalvData ) tableData.addWsegvalvRow( *seg.wsegvalvData );
|
|
|
|
|
|
|
|
|
|
// WSEGAICD row
|
|
|
|
|
if ( seg.wsegaicdData ) tableData.addWsegaicdRow( *seg.wsegaicdData );
|
|
|
|
|
|
|
|
|
|
// WSEGSICD row
|
|
|
|
|
if ( seg.wsegsicdData ) tableData.addWsegsicdRow( *seg.wsegsicdData );
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for ( const auto& branch : exportData.branches )
|
|
|
|
|
{
|
|
|
|
|
if ( branch.tieInValve ) emitSegment( branch, *branch.tieInValve );
|
|
|
|
|
for ( const auto& seg : branch.segments )
|
|
|
|
|
emitSegment( branch, seg );
|
|
|
|
|
tableData.addMswBranch( branch );
|
|
|
|
|
}
|
2026-08-08 17:24:27 +02:00
|
|
|
|
2026-08-28 21:53:25 +02:00
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-08 17:24:27 +02:00
|
|
|
// 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.
|
2026-08-28 21:53:25 +02:00
|
|
|
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(),
|
2026-08-08 17:24:27 +02:00
|
|
|
[]( const CompsegsRow& lhs, const CompsegsRow& rhs ) { return lhs.branch < rhs.branch; } );
|
|
|
|
|
|
2026-08-28 21:53:25 +02:00
|
|
|
for ( const auto& compsegsRow : ownedRows )
|
2026-08-08 17:24:27 +02:00
|
|
|
{
|
|
|
|
|
tableData.addCompsegsRow( compsegsRow );
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 07:40:10 +01:00
|
|
|
return tableData;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace RicWellPathExportMswGeometryPath
|