ResInsight/ApplicationLibCode/ReservoirDataModel/RigStimPlanModelTools.cpp
Magne Sjaastad 952e766c2f
Update clang-format.yml (#10068)
* Update to clang-format-15
Removed two custom .clang-format files in subfolders of AppFwk

* Fixes by clang-format
2023-04-13 07:05:53 +02:00

290 lines
14 KiB
C++

/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2021- 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 "RigStimPlanModelTools.h"
#include "RiaLogging.h"
#include "RigCell.h"
#include "RigEclipseCaseData.h"
#include "RigFault.h"
#include "RigMainGrid.h"
#include "RigSimulationWellCoordsAndMD.h"
#include "RigWellLogExtractor.h"
#include "RigWellPathIntersectionTools.h"
#include "cvfBoundingBox.h"
#include "cvfGeometryTools.h"
#include "cvfMath.h"
#include "cvfPlane.h"
#include <QString>
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
cvf::Vec3d RigStimPlanModelTools::calculateTSTDirection( RigEclipseCaseData* eclipseCaseData,
const cvf::Vec3d& anchorPosition,
double boundingBoxHorizontal,
double boundingBoxVertical )
{
cvf::Vec3d defaultDirection = cvf::Vec3d( 0.0, 0.0, -1.0 );
if ( !eclipseCaseData ) return defaultDirection;
RigMainGrid* mainGrid = eclipseCaseData->mainGrid();
if ( !mainGrid ) return defaultDirection;
cvf::Vec3d boundingBoxSize( boundingBoxHorizontal, boundingBoxHorizontal, boundingBoxVertical );
// Find upper face of cells close to the anchor point
cvf::BoundingBox boundingBox( anchorPosition - boundingBoxSize, anchorPosition + boundingBoxSize );
std::vector<size_t> closeCells;
mainGrid->findIntersectingCells( boundingBox, &closeCells );
// The stratigraphic thickness is the averge of normals of the top face
cvf::Vec3d direction = cvf::Vec3d::ZERO;
int numContributingCells = 0;
for ( size_t globalCellIndex : closeCells )
{
const RigCell& cell = mainGrid->globalCellArray()[globalCellIndex];
if ( !cell.isInvalid() )
{
direction += cell.faceNormalWithAreaLength( cvf::StructGridInterface::NEG_K ).getNormalized();
numContributingCells++;
}
}
RiaLogging::info( QString( "TST contributing cells: %1/%2" ).arg( numContributingCells ).arg( closeCells.size() ) );
if ( numContributingCells == 0 )
{
// No valid close cells found: just point straight up
return defaultDirection;
}
direction = ( direction / static_cast<double>( numContributingCells ) ).getNormalized();
// A surface has normals in both directions: if the normal points upwards we flip it to
// make it point downwards. This necessary when finding the TST start and end points later.
if ( direction.z() > 0.0 )
{
direction *= -1.0;
}
return direction;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RigStimPlanModelTools::calculateFormationDip( const cvf::Vec3d& direction )
{
// Formation dip is inclination of a plane from horizontal.
return cvf::Math::toDegrees( cvf::GeometryTools::getAngle( direction, -cvf::Vec3d::Z_AXIS ) );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::tuple<const RigFault*, double, cvf::Vec3d, double> RigStimPlanModelTools::findClosestFaultBarrier( RigEclipseCaseData* eclipseCaseData,
const cvf::Vec3d& position,
const cvf::Vec3d& directionToBarrier )
{
std::vector<WellPathCellIntersectionInfo> intersections =
RigStimPlanModelTools::generateBarrierIntersections( eclipseCaseData, position, directionToBarrier );
RiaLogging::info( QString( "Intersections: %1" ).arg( intersections.size() ) );
double shortestDistance = std::numeric_limits<double>::max();
RigMainGrid* mainGrid = eclipseCaseData->mainGrid();
cvf::Vec3d barrierPosition;
double barrierDip = 0.0;
const RigFault* foundFault = nullptr;
for ( const WellPathCellIntersectionInfo& intersection : intersections )
{
// Find the closest cell face which is a fault
double distance = position.pointDistance( intersection.startPoint );
const RigFault* fault = mainGrid->findFaultFromCellIndexAndCellFace( intersection.globCellIndex, intersection.intersectedCellFaceIn );
if ( fault && distance < shortestDistance )
{
foundFault = fault;
shortestDistance = distance;
barrierPosition = intersection.startPoint;
const RigCell& cell = mainGrid->globalCellArray()[intersection.globCellIndex];
cvf::Vec3d faceNormal = cell.faceNormalWithAreaLength( intersection.intersectedCellFaceIn );
barrierDip = RigStimPlanModelTools::calculateFormationDip( faceNormal );
}
}
return std::make_tuple( foundFault, shortestDistance, barrierPosition, barrierDip );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<WellPathCellIntersectionInfo> RigStimPlanModelTools::generateBarrierIntersections( RigEclipseCaseData* eclipseCaseData,
const cvf::Vec3d& position,
const cvf::Vec3d& directionToBarrier )
{
double randoDistance = 10000.0;
cvf::Vec3d forwardPosition = position + ( directionToBarrier * randoDistance );
cvf::Vec3d backwardPosition = position + ( directionToBarrier * -randoDistance );
std::vector<WellPathCellIntersectionInfo> intersections =
generateBarrierIntersectionsBetweenPoints( eclipseCaseData, position, forwardPosition );
std::vector<WellPathCellIntersectionInfo> backwardIntersections =
generateBarrierIntersectionsBetweenPoints( eclipseCaseData, position, backwardPosition );
// Merge the intersections for the search for closest
intersections.insert( intersections.end(), backwardIntersections.begin(), backwardIntersections.end() );
return intersections;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<WellPathCellIntersectionInfo> RigStimPlanModelTools::generateBarrierIntersectionsBetweenPoints( RigEclipseCaseData* eclipseCaseData,
const cvf::Vec3d& startPosition,
const cvf::Vec3d& endPosition )
{
// Create a fake well path from the anchor point to
// a point far away in the direction barrier direction
std::vector<cvf::Vec3d> pathCoords;
pathCoords.push_back( startPosition );
pathCoords.push_back( endPosition );
RigSimulationWellCoordsAndMD helper( pathCoords );
return RigWellPathIntersectionTools::findCellIntersectionInfosAlongPath( eclipseCaseData,
"",
helper.wellPathPoints(),
helper.measuredDepths() );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RigStimPlanModelTools::findThicknessTargetPoints( RigEclipseCaseData* eclipseCaseData,
const cvf::Vec3d& position,
const cvf::Vec3d& direction,
double extractionDepthTop,
double extractionDepthBottom,
cvf::Vec3d& topPosition,
cvf::Vec3d& bottomPosition )
{
if ( !eclipseCaseData ) return false;
RiaLogging::info( QString( "Position: %1" ).arg( RigStimPlanModelTools::vecToString( position ) ) );
RiaLogging::info( QString( "Direction: %1" ).arg( RigStimPlanModelTools::vecToString( direction ) ) );
// Create a "fake" well path which from top to bottom of formation
// passing through the point and with the given direction
const cvf::BoundingBox& allCellsBoundingBox = eclipseCaseData->mainGrid()->boundingBox();
RiaLogging::info( QString( "All cells bounding box: %1 %2" )
.arg( RigStimPlanModelTools::vecToString( allCellsBoundingBox.min() ) )
.arg( RigStimPlanModelTools::vecToString( allCellsBoundingBox.max() ) ) );
cvf::BoundingBox geometryBoundingBox( allCellsBoundingBox );
// Use smaller depth bounding box for extraction if configured
if ( extractionDepthTop > 0.0 && extractionDepthBottom > 0.0 && extractionDepthTop < extractionDepthBottom )
{
cvf::Vec3d bbMin( allCellsBoundingBox.min().x(), allCellsBoundingBox.min().y(), -extractionDepthBottom );
cvf::Vec3d bbMax( allCellsBoundingBox.max().x(), allCellsBoundingBox.max().y(), -extractionDepthTop );
geometryBoundingBox = cvf::BoundingBox( bbMin, bbMax );
}
if ( !geometryBoundingBox.contains( position ) )
{
//
RiaLogging::error( "Anchor position is outside the grid bounding box. Unable to compute direction." );
return false;
}
// Create plane on top and bottom of formation
cvf::Plane topPlane;
topPlane.setFromPointAndNormal( geometryBoundingBox.max(), cvf::Vec3d::Z_AXIS );
cvf::Plane bottomPlane;
bottomPlane.setFromPointAndNormal( geometryBoundingBox.min(), cvf::Vec3d::Z_AXIS );
// Find and add point on top plane
cvf::Vec3d abovePlane = position + ( direction * -10000.0 );
if ( !topPlane.intersect( position, abovePlane, &topPosition ) )
{
RiaLogging::error( "Unable to compute top position of thickness direction vector." );
return false;
}
RiaLogging::info( QString( "Top: %1" ).arg( RigStimPlanModelTools::vecToString( topPosition ) ) );
// Find and add point on bottom plane
cvf::Vec3d belowPlane = position + ( direction * 10000.0 );
if ( !bottomPlane.intersect( position, belowPlane, &bottomPosition ) )
{
RiaLogging::error( "Unable to compute bottom position of thickness direction vector." );
return false;
}
RiaLogging::info( QString( "Bottom: %1" ).arg( RigStimPlanModelTools::vecToString( bottomPosition ) ) );
return true;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RigStimPlanModelTools::vecToString( const cvf::Vec3d& vec )
{
return QString( "[%1, %2, %3]" ).arg( vec.x() ).arg( vec.y() ).arg( vec.z() );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RigStimPlanModelTools::calculatePerforationLength( const cvf::Vec3d& direction, double perforationLength )
{
// Deviation from vertical. Since well path is tending downwards we compare with negative z.
double inclination = cvf::GeometryTools::getAngle( direction, -cvf::Vec3d::Z_AXIS );
// Keep inclination in 0-90 degrees range
if ( inclination > cvf::PI_D / 2.0 )
{
inclination = cvf::PI_D - inclination;
}
double correctedPerforationLength = perforationLength * std::cos( inclination );
RiaLogging::info( QString( "Perforation length correction: original length: %1 inclination: %2 corrected length: %3" )
.arg( perforationLength )
.arg( cvf::Math::toDegrees( inclination ) )
.arg( correctedPerforationLength ) );
// Handle well inclination close to 90 dgr to ensure visual perforation interval in StimPlan model plot
if ( std::fabs( cvf::Math::toDegrees( inclination ) - 90.0 ) < 0.1 )
{
double minimumPerforationInterval = 0.5;
return std::max( minimumPerforationInterval, correctedPerforationLength );
}
return correctedPerforationLength;
}