///////////////////////////////////////////////////////////////////////////////// // // 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 // 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 //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- 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 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( 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 RigStimPlanModelTools::findClosestFaultBarrier( RigEclipseCaseData* eclipseCaseData, const cvf::Vec3d& position, const cvf::Vec3d& directionToBarrier ) { std::vector intersections = RigStimPlanModelTools::generateBarrierIntersections( eclipseCaseData, position, directionToBarrier ); RiaLogging::info( QString( "Intersections: %1" ).arg( intersections.size() ) ); double shortestDistance = std::numeric_limits::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 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 intersections = generateBarrierIntersectionsBetweenPoints( eclipseCaseData, position, forwardPosition ); std::vector 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 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 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; }