mirror of
https://github.com/OPM/ResInsight.git
synced 2026-08-27 05:37:21 -05:00
CompletionExport: Simplify findFishboneLateralsWellBoreParts and add unit tests
- Make findFishboneLateralsWellBoreParts public and return result by value - Move WellBorePartForTransCalc to its own header RicWellBorePartForTransCalc.h - Replace MSW tree traversal with direct RimFishbones lateral geometry iteration - Add setSubsOrientationMode() to RimFishbones for deterministic test angles - Add unit tests validating intersected cells and result values
This commit is contained in:
+41
-83
@@ -19,13 +19,8 @@
|
||||
#include "RicFishbonesTransmissibilityCalculationFeatureImp.h"
|
||||
|
||||
#include "RicExportCompletionDataSettingsUi.h"
|
||||
#include "RicMswBranch.h"
|
||||
#include "RicMswCompletions.h"
|
||||
#include "RicMswExportInfo.h"
|
||||
#include "RicMswSegment.h"
|
||||
#include "RicTransmissibilityCalculator.h"
|
||||
#include "RicWellPathExportCompletionDataFeatureImpl.h"
|
||||
#include "RicWellPathExportMswTableData.h"
|
||||
|
||||
#include "RigActiveCellInfo.h"
|
||||
#include "RigCompletionData.h"
|
||||
@@ -42,36 +37,6 @@
|
||||
#include "RimWellPathCompletions.h"
|
||||
|
||||
#include <cafPdmObject.h>
|
||||
#include <cafPdmPointer.h>
|
||||
|
||||
//==================================================================================================
|
||||
///
|
||||
//==================================================================================================
|
||||
struct WellBorePartForTransCalc
|
||||
{
|
||||
WellBorePartForTransCalc( cvf::Vec3d lengthsInCell, double wellRadius, double skinFactor, bool isMainBore, const QString& metaData )
|
||||
: lengthsInCell( lengthsInCell )
|
||||
, wellRadius( wellRadius )
|
||||
, skinFactor( skinFactor )
|
||||
, isMainBore( isMainBore )
|
||||
, metaData( metaData )
|
||||
, intersectionWithWellMeasuredDepth( HUGE_VAL )
|
||||
, lateralIndex( cvf::UNDEFINED_SIZE_T )
|
||||
{
|
||||
}
|
||||
|
||||
cvf::Vec3d lengthsInCell;
|
||||
double wellRadius;
|
||||
double skinFactor;
|
||||
QString metaData;
|
||||
bool isMainBore;
|
||||
|
||||
double intersectionWithWellMeasuredDepth;
|
||||
size_t lateralIndex;
|
||||
|
||||
void setSourcePdmObject( const caf::PdmObject* sourcePdmObj ) { sourcePdmObject = const_cast<caf::PdmObject*>( sourcePdmObj ); }
|
||||
caf::PdmPointer<caf::PdmObject> sourcePdmObject;
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
@@ -87,9 +52,7 @@ std::vector<RigCompletionData> RicFishbonesTransmissibilityCalculationFeatureImp
|
||||
return completionData;
|
||||
}
|
||||
|
||||
std::map<size_t, std::vector<WellBorePartForTransCalc>> wellBorePartsInCells; // wellBore = main bore or fishbone
|
||||
// lateral
|
||||
findFishboneLateralsWellBoreParts( wellBorePartsInCells, wellPath, settings.caseToApply() );
|
||||
auto wellBorePartsInCells = findFishboneLateralsWellBoreParts( wellPath, settings.caseToApply() );
|
||||
|
||||
const RigActiveCellInfo* activeCellInfo =
|
||||
settings.caseToApply->eclipseCaseData()->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL );
|
||||
@@ -187,60 +150,53 @@ std::vector<RigCompletionData> RicFishbonesTransmissibilityCalculationFeatureImp
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RicFishbonesTransmissibilityCalculationFeatureImp::findFishboneLateralsWellBoreParts(
|
||||
std::map<size_t, std::vector<WellBorePartForTransCalc>>& wellBorePartsInCells,
|
||||
const RimWellPath* wellPath,
|
||||
const RimEclipseCase* eclipseCase )
|
||||
std::map<size_t, std::vector<WellBorePartForTransCalc>>
|
||||
RicFishbonesTransmissibilityCalculationFeatureImp::findFishboneLateralsWellBoreParts( const RimWellPath* wellPath,
|
||||
const RimEclipseCase* eclipseCase )
|
||||
{
|
||||
if ( !wellPath || !wellPath->wellPathGeometry() || wellPath->wellPathGeometry()->measuredDepths().size() < 2 ) return;
|
||||
std::map<size_t, std::vector<WellBorePartForTransCalc>> wellBorePartsInCells;
|
||||
if ( !wellPath || !wellPath->wellPathGeometry() || wellPath->wellPathGeometry()->measuredDepths().size() < 2 )
|
||||
return wellBorePartsInCells;
|
||||
|
||||
// Generate data
|
||||
const RigEclipseCaseData* caseData = eclipseCase->eclipseCaseData();
|
||||
const RigEclipseCaseData* caseData = eclipseCase->eclipseCaseData();
|
||||
RiaDefines::EclipseUnitSystem unitSystem = caseData->unitsType();
|
||||
|
||||
auto mswParameters = wellPath->mswCompletionParameters();
|
||||
RiaDefines::EclipseUnitSystem unitSystem = caseData->unitsType();
|
||||
|
||||
RicMswExportInfo exportInfo( wellPath,
|
||||
unitSystem,
|
||||
wellPath->fishbonesCollection()->startMD(),
|
||||
mswParameters->lengthAndDepth().text(),
|
||||
mswParameters->pressureDrop().text() );
|
||||
|
||||
RicWellPathExportMswTableData::generateFishbonesMswExportInfoForWell( eclipseCase, wellPath, &exportInfo, exportInfo.mainBoreBranch() );
|
||||
|
||||
bool isMainBore = false;
|
||||
|
||||
for ( auto mainBoreSegment : exportInfo.mainBoreBranch()->segments() )
|
||||
// Build lateral WellBoreParts directly from RimFishbones — no MSW tree needed.
|
||||
for ( const RimFishbones* subs : wellPath->completions()->fishbonesCollection()->activeFishbonesSubs() )
|
||||
{
|
||||
for ( auto mainBoreCompletion : mainBoreSegment->completions() )
|
||||
const double holeDiameter = subs->holeDiameter( unitSystem );
|
||||
const double skinFactor = subs->skinFactor();
|
||||
|
||||
for ( const auto& [subIndex, lateralIndex] : subs->installedLateralIndices() )
|
||||
{
|
||||
for ( auto completionSegment : mainBoreCompletion->segments() )
|
||||
auto lateralCoordMDPairs = subs->coordsAndMDForLateral( subIndex, lateralIndex );
|
||||
if ( lateralCoordMDPairs.empty() ) continue;
|
||||
|
||||
std::vector<cvf::Vec3d> coords;
|
||||
std::vector<double> mds;
|
||||
for ( const auto& [coord, md] : lateralCoordMDPairs )
|
||||
{
|
||||
for ( auto completion : completionSegment->completions() )
|
||||
{
|
||||
for ( auto segment : completion->segments() )
|
||||
{
|
||||
for ( auto intersection : segment->intersections() )
|
||||
{
|
||||
double diameter = segment->holeDiameter();
|
||||
QString completionMetaData =
|
||||
( segment->label() +
|
||||
QString( ": Sub: %1 Lateral: %2" ).arg( segment->subIndex() + 1 ).arg( completion->index() + 1 ) );
|
||||
coords.push_back( coord );
|
||||
mds.push_back( md );
|
||||
}
|
||||
|
||||
WellBorePartForTransCalc wellBorePart = WellBorePartForTransCalc( intersection->lengthsInCell(),
|
||||
diameter / 2.0,
|
||||
segment->skinFactor(),
|
||||
isMainBore,
|
||||
completionMetaData );
|
||||
auto intersections = RigWellPathIntersectionTools::findCellIntersectionInfosAlongPath( caseData, wellPath->name(), coords, mds );
|
||||
|
||||
wellBorePart.intersectionWithWellMeasuredDepth = segment->endMD();
|
||||
wellBorePart.lateralIndex = completion->index();
|
||||
wellBorePart.setSourcePdmObject( segment->sourcePdmObject() );
|
||||
for ( const auto& cellIntInfo : intersections )
|
||||
{
|
||||
QString completionMetaData = QString( "Sub segment: Sub: %1 Lateral: %2" ).arg( subIndex + 1 ).arg( lateralIndex + 1 );
|
||||
|
||||
wellBorePartsInCells[intersection->globalCellIndex()].push_back( wellBorePart );
|
||||
}
|
||||
}
|
||||
}
|
||||
WellBorePartForTransCalc wellBorePart( cellIntInfo.intersectionLengthsInCellCS,
|
||||
holeDiameter / 2.0,
|
||||
skinFactor,
|
||||
false,
|
||||
completionMetaData );
|
||||
|
||||
wellBorePart.intersectionWithWellMeasuredDepth = cellIntInfo.endMD;
|
||||
wellBorePart.lateralIndex = lateralIndex;
|
||||
wellBorePart.setSourcePdmObject( subs );
|
||||
|
||||
wellBorePartsInCells[cellIntInfo.globCellIndex].push_back( wellBorePart );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -278,6 +234,8 @@ void RicFishbonesTransmissibilityCalculationFeatureImp::findFishboneLateralsWell
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return wellBorePartsInCells;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
+5
-6
@@ -18,6 +18,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "RicWellBorePartForTransCalc.h"
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
@@ -28,8 +30,6 @@ class RimFishbones;
|
||||
class RicExportCompletionDataSettingsUi;
|
||||
class RigEclipseCaseData;
|
||||
|
||||
struct WellBorePartForTransCalc;
|
||||
|
||||
//==================================================================================================
|
||||
///
|
||||
//==================================================================================================
|
||||
@@ -39,11 +39,10 @@ public:
|
||||
static std::vector<RigCompletionData>
|
||||
generateFishboneCompdatValuesUsingAdjustedCellVolume( const RimWellPath* wellPath, const RicExportCompletionDataSettingsUi& settings );
|
||||
|
||||
private:
|
||||
static void findFishboneLateralsWellBoreParts( std::map<size_t, std::vector<WellBorePartForTransCalc>>& wellBorePartsInCells,
|
||||
const RimWellPath* wellPath,
|
||||
const RimEclipseCase* eclipseCase );
|
||||
static std::map<size_t, std::vector<WellBorePartForTransCalc>> findFishboneLateralsWellBoreParts( const RimWellPath* wellPath,
|
||||
const RimEclipseCase* eclipseCase );
|
||||
|
||||
private:
|
||||
static void appendMainWellBoreParts( std::map<size_t, std::vector<WellBorePartForTransCalc>>& wellBorePartsInCells,
|
||||
const RimWellPath* wellPath,
|
||||
const RigEclipseCaseData* eclipseCaseData,
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (C) 2017 Statoil 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.
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "cvfVector3.h"
|
||||
|
||||
#include "cafPdmPointer.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace caf
|
||||
{
|
||||
class PdmObject;
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
///
|
||||
//==================================================================================================
|
||||
struct WellBorePartForTransCalc
|
||||
{
|
||||
WellBorePartForTransCalc( cvf::Vec3d lengthsInCell, double wellRadius, double skinFactor, bool isMainBore, const QString& metaData )
|
||||
: lengthsInCell( lengthsInCell )
|
||||
, wellRadius( wellRadius )
|
||||
, skinFactor( skinFactor )
|
||||
, isMainBore( isMainBore )
|
||||
, metaData( metaData )
|
||||
, intersectionWithWellMeasuredDepth( HUGE_VAL )
|
||||
, lateralIndex( cvf::UNDEFINED_SIZE_T )
|
||||
{
|
||||
}
|
||||
|
||||
cvf::Vec3d lengthsInCell;
|
||||
double wellRadius;
|
||||
double skinFactor;
|
||||
QString metaData;
|
||||
bool isMainBore;
|
||||
|
||||
double intersectionWithWellMeasuredDepth;
|
||||
size_t lateralIndex;
|
||||
|
||||
void setSourcePdmObject( const caf::PdmObject* sourcePdmObj ) { sourcePdmObject = const_cast<caf::PdmObject*>( sourcePdmObj ); }
|
||||
caf::PdmPointer<caf::PdmObject> sourcePdmObject;
|
||||
};
|
||||
@@ -188,6 +188,14 @@ void RimFishbones::setSystemParameters( int lateralsPerSub, double lateralLength
|
||||
m_icdCount = icdsPerSub;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
void RimFishbones::setSubsOrientationMode( RimFishbonesDefines::LateralsOrientationType orientationType )
|
||||
{
|
||||
m_subsOrientationMode = orientationType;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -62,6 +62,7 @@ public:
|
||||
void setValveLocations( const std::vector<double>& measuredDepths );
|
||||
|
||||
void setSystemParameters( int lateralsPerSub, double lateralLength, double holeDiameter, double buildAngle, int icdsPerSub );
|
||||
void setSubsOrientationMode( RimFishbonesDefines::LateralsOrientationType orientationType );
|
||||
|
||||
double measuredDepth( size_t subIndex ) const;
|
||||
double rotationAngle( size_t subIndex ) const;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "CompletionExportCommands/RicFishbonesTransmissibilityCalculationFeatureImp.h"
|
||||
#include "CompletionExportCommands/RicTransmissibilityCalculator.h"
|
||||
|
||||
#include "RifReaderMockModel.h"
|
||||
@@ -29,9 +30,16 @@
|
||||
#include "RigEclipseResultInfo.h"
|
||||
#include "RigMainGrid.h"
|
||||
|
||||
#include "Well/RigWellPath.h"
|
||||
|
||||
#include "RimEclipseCase.h"
|
||||
#include "RimEclipseResultCase.h"
|
||||
#include "RimFishbones.h"
|
||||
#include "RimFishbonesCollection.h"
|
||||
#include "RimOilField.h"
|
||||
#include "RimProject.h"
|
||||
#include "RimWellPath.h"
|
||||
#include "RimWellPathCollection.h"
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
/// Helper: Register a STATIC_NATIVE result with a uniform value for all cells
|
||||
@@ -219,3 +227,192 @@ TEST( RicTransmissibilityCalculator, CalculateCellMainDirection_DualPorosity_Use
|
||||
|
||||
delete eclipseCase;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RicFishbonesTransmissibilityCalculation, NullWellPath_ReturnsEmptyMap )
|
||||
{
|
||||
auto result = RicFishbonesTransmissibilityCalculationFeatureImp::findFishboneLateralsWellBoreParts( nullptr, nullptr );
|
||||
EXPECT_TRUE( result.empty() );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RicFishbonesTransmissibilityCalculation, WellPathWithNoGeometry_ReturnsEmptyMap )
|
||||
{
|
||||
RimWellPath wellPath;
|
||||
|
||||
auto result = RicFishbonesTransmissibilityCalculationFeatureImp::findFishboneLateralsWellBoreParts( &wellPath, nullptr );
|
||||
EXPECT_TRUE( result.empty() );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RicFishbonesTransmissibilityCalculation, WellPathWithSingleMeasuredDepth_ReturnsEmptyMap )
|
||||
{
|
||||
RimWellPath wellPath;
|
||||
|
||||
cvf::ref<RigWellPath> geometry = new RigWellPath( { cvf::Vec3d( 0, 0, -1000 ) }, { 1000.0 } );
|
||||
wellPath.setWellPathGeometry( geometry.p() );
|
||||
|
||||
auto result = RicFishbonesTransmissibilityCalculationFeatureImp::findFishboneLateralsWellBoreParts( &wellPath, nullptr );
|
||||
EXPECT_TRUE( result.empty() );
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RicFishbonesTransmissibilityCalculation, MainBoreWithMockGrid_ReturnsIntersectedCells )
|
||||
{
|
||||
// 2x2x2 mock grid, world coordinates (0,0,-100) to (100,100,0) — each cell is 50x50x50, z=0 is the top
|
||||
auto* eclipseCase = new RimEclipseResultCase;
|
||||
{
|
||||
cvf::ref<RigEclipseCaseData> caseData = new RigEclipseCaseData( eclipseCase );
|
||||
|
||||
cvf::ref<RifReaderMockModel> mockReader = new RifReaderMockModel;
|
||||
mockReader->setWorldCoordinates( cvf::Vec3d( 0, 0, -100 ), cvf::Vec3d( 100, 100, 0 ) );
|
||||
mockReader->setCellCounts( cvf::Vec3st( 2, 2, 2 ) );
|
||||
mockReader->enableWellData( false );
|
||||
mockReader->open( "", caseData.p() );
|
||||
caseData->mainGrid()->computeCachedData();
|
||||
|
||||
eclipseCase->setReservoirData( caseData.p() );
|
||||
|
||||
size_t cellCount = caseData->mainGrid()->totalCellCount();
|
||||
RigCaseCellResultsData* cellResults = caseData->results( RiaDefines::PorosityModelType::MATRIX_MODEL );
|
||||
addStaticResult( cellResults, "DX", cellCount, 50.0 );
|
||||
addStaticResult( cellResults, "DY", cellCount, 50.0 );
|
||||
addStaticResult( cellResults, "DZ", cellCount, 50.0 );
|
||||
addStaticResult( cellResults, "PERMX", cellCount, 100.0 );
|
||||
addStaticResult( cellResults, "PERMY", cellCount, 100.0 );
|
||||
addStaticResult( cellResults, "PERMZ", cellCount, 10.0 );
|
||||
addStaticResult( cellResults, "NTG", cellCount, 1.0 );
|
||||
}
|
||||
|
||||
auto* project = new RimProject;
|
||||
auto* wellPath = new RimWellPath;
|
||||
wellPath->setName( "TestWell" );
|
||||
wellPath->setUnitSystem( RiaDefines::EclipseUnitSystem::UNITS_METRIC );
|
||||
|
||||
// Vertical well at (25, 25) passing through z=0 (top of grid) at MD=10, then into the grid
|
||||
cvf::ref<RigWellPath> geometry = new RigWellPath( { cvf::Vec3d( 25, 25, 10 ), cvf::Vec3d( 25, 25, -110 ) }, { 0.0, 120.0 } );
|
||||
wellPath->setWellPathGeometry( geometry.p() );
|
||||
|
||||
project->oilFields[0]->wellPathCollection->addWellPath( wellPath );
|
||||
|
||||
// Add a fishbone sub at MD=10, where the well passes through z=0 — fishbonesCollection is checked by default
|
||||
auto* sub = new RimFishbones;
|
||||
wellPath->fishbonesCollection()->appendFishbonesSubs( sub );
|
||||
sub->setMeasuredDepthAndCount( 10.0, 10.0, 1 );
|
||||
sub->setSubsOrientationMode( RimFishbonesDefines::LateralsOrientationType::FIXED );
|
||||
|
||||
auto result = RicFishbonesTransmissibilityCalculationFeatureImp::findFishboneLateralsWellBoreParts( wellPath, eclipseCase );
|
||||
|
||||
EXPECT_FALSE( result.empty() );
|
||||
|
||||
bool hasMainBoreEntry = false;
|
||||
for ( const auto& [cellIndex, parts] : result )
|
||||
{
|
||||
for ( const auto& part : parts )
|
||||
{
|
||||
if ( part.isMainBore ) hasMainBoreEntry = true;
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE( hasMainBoreEntry );
|
||||
|
||||
delete eclipseCase;
|
||||
project->close();
|
||||
delete project;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
///
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
TEST( RicFishbonesTransmissibilityCalculation, MainBoreWithMockGrid_ValidatesResultValues )
|
||||
{
|
||||
// 2x2x2 mock grid, world coordinates (0,0,-100) to (100,100,0) — each cell is 50x50x50, z=0 is the top.
|
||||
// Cell layout (global index): k=0 is z=[-100,-50], k=1 is z=[-50,0].
|
||||
// Cells in k=1 (top layer): index 4=(i0,j0), 5=(i1,j0), 6=(i0,j1), 7=(i1,j1).
|
||||
auto* eclipseCase = new RimEclipseResultCase;
|
||||
{
|
||||
cvf::ref<RigEclipseCaseData> caseData = new RigEclipseCaseData( eclipseCase );
|
||||
|
||||
cvf::ref<RifReaderMockModel> mockReader = new RifReaderMockModel;
|
||||
mockReader->setWorldCoordinates( cvf::Vec3d( 0, 0, -100 ), cvf::Vec3d( 100, 100, 0 ) );
|
||||
mockReader->setCellCounts( cvf::Vec3st( 2, 2, 2 ) );
|
||||
mockReader->enableWellData( false );
|
||||
mockReader->open( "", caseData.p() );
|
||||
caseData->mainGrid()->computeCachedData();
|
||||
|
||||
eclipseCase->setReservoirData( caseData.p() );
|
||||
|
||||
size_t cellCount = caseData->mainGrid()->totalCellCount();
|
||||
RigCaseCellResultsData* cellResults = caseData->results( RiaDefines::PorosityModelType::MATRIX_MODEL );
|
||||
addStaticResult( cellResults, "DX", cellCount, 50.0 );
|
||||
addStaticResult( cellResults, "DY", cellCount, 50.0 );
|
||||
addStaticResult( cellResults, "DZ", cellCount, 50.0 );
|
||||
addStaticResult( cellResults, "PERMX", cellCount, 100.0 );
|
||||
addStaticResult( cellResults, "PERMY", cellCount, 100.0 );
|
||||
addStaticResult( cellResults, "PERMZ", cellCount, 10.0 );
|
||||
addStaticResult( cellResults, "NTG", cellCount, 1.0 );
|
||||
}
|
||||
|
||||
auto* project = new RimProject;
|
||||
auto* wellPath = new RimWellPath;
|
||||
wellPath->setName( "TestWell" );
|
||||
wellPath->setUnitSystem( RiaDefines::EclipseUnitSystem::UNITS_METRIC );
|
||||
|
||||
// Vertical well at (25,25): starts above grid at z=10 (MD=0), crosses z=0 at MD=10, ends at z=-110 (MD=120).
|
||||
cvf::ref<RigWellPath> geometry = new RigWellPath( { cvf::Vec3d( 25, 25, 10 ), cvf::Vec3d( 25, 25, -110 ) }, { 0.0, 120.0 } );
|
||||
wellPath->setWellPathGeometry( geometry.p() );
|
||||
|
||||
project->oilFields[0]->wellPathCollection->addWellPath( wellPath );
|
||||
|
||||
// Fishbone sub at MD=10 (z=0, top of grid). Default metric mainBoreDiameter=0.216 m → radius=0.108 m.
|
||||
auto* sub = new RimFishbones;
|
||||
wellPath->fishbonesCollection()->appendFishbonesSubs( sub );
|
||||
sub->setMeasuredDepthAndCount( 10.0, 10.0, 1 );
|
||||
sub->setSubsOrientationMode( RimFishbonesDefines::LateralsOrientationType::FIXED );
|
||||
|
||||
auto result = RicFishbonesTransmissibilityCalculationFeatureImp::findFishboneLateralsWellBoreParts( wellPath, eclipseCase );
|
||||
|
||||
// Expect exactly one cell intersected: the top-layer cell at (i=0, j=0, k=1), global index 4.
|
||||
ASSERT_EQ( 1u, result.size() );
|
||||
EXPECT_EQ( 4u, result.begin()->first );
|
||||
|
||||
const auto& parts = result.begin()->second;
|
||||
|
||||
// Find the single main bore part (laterals also land in this cell but have isMainBore=false).
|
||||
const WellBorePartForTransCalc* mainBorePart = nullptr;
|
||||
int mainBoreCount = 0;
|
||||
for ( const auto& part : parts )
|
||||
{
|
||||
if ( part.isMainBore )
|
||||
{
|
||||
mainBorePart = ∂
|
||||
mainBoreCount++;
|
||||
}
|
||||
}
|
||||
ASSERT_EQ( 1, mainBoreCount );
|
||||
ASSERT_NE( nullptr, mainBorePart );
|
||||
|
||||
EXPECT_DOUBLE_EQ( 0.0, mainBorePart->skinFactor );
|
||||
EXPECT_NEAR( 0.108, mainBorePart->wellRadius, 1e-6 );
|
||||
|
||||
// The well is vertical — only z-component of the intersection length should be non-zero.
|
||||
EXPECT_NEAR( 0.0, mainBorePart->lengthsInCell.x(), 1e-6 );
|
||||
EXPECT_NEAR( 0.0, mainBorePart->lengthsInCell.y(), 1e-6 );
|
||||
EXPECT_GT( mainBorePart->lengthsInCell.z(), 0.0 );
|
||||
|
||||
// Intersection begins where the well crosses z=0 (top of grid), which is MD=10.
|
||||
EXPECT_NEAR( 10.0, mainBorePart->intersectionWithWellMeasuredDepth, 1.0 );
|
||||
|
||||
EXPECT_TRUE( mainBorePart->metaData.contains( "main bore" ) );
|
||||
|
||||
delete eclipseCase;
|
||||
project->close();
|
||||
delete project;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user