ResInsight/ApplicationLibCode/ReservoirDataModel/RigThermalFractureResultUtil.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

617 lines
26 KiB
C++

/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2022 - 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 "RigThermalFractureResultUtil.h"
#include "RiaEclipseUnitTools.h"
#include "RiaLogging.h"
#include "RiaWeightedMeanCalculator.h"
#include "RigCellGeometryTools.h"
#include "RigConvexHull.h"
#include "RigFractureGrid.h"
#include "RigStatisticsMath.h"
#include "RigThermalFractureDefinition.h"
#include "cafAssert.h"
#include "cvfBoundingBox.h"
#include "cvfGeometryTools.h"
#include <cmath>
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RigThermalFractureResultUtil::RigThermalFractureResultUtil()
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RigThermalFractureResultUtil::~RigThermalFractureResultUtil()
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<std::vector<double>>
RigThermalFractureResultUtil::getDataAtTimeIndex( std::shared_ptr<const RigThermalFractureDefinition> fractureDefinition,
const QString& resultName,
const QString& unitName,
size_t timeStepIndex )
{
std::vector<std::vector<double>> vec;
int propertyIndex = fractureDefinition->getPropertyIndex( resultName );
if ( propertyIndex < 0 ) return vec;
std::vector<cvf::Vec3d> relativePos = getRelativeCoordinates( fractureDefinition, timeStepIndex );
// Create bounding box
cvf::BoundingBox boundingBox;
for ( auto p : relativePos )
boundingBox.add( p );
boundingBox.expand( 1.0 );
// Generate a uniform mesh (center points)
auto [xCoords, depthCoords] = generateUniformMesh( boundingBox, NUM_SAMPLES_X, NUM_SAMPLES_Y );
// Fill with invalid value
for ( int i = 0; i < NUM_SAMPLES_Y; i++ )
{
std::vector<double> junk( NUM_SAMPLES_X, std::numeric_limits<double>::infinity() );
vec.push_back( junk );
}
// Find the boundary of the fracture (i.e. convex hull around the points)
std::vector<cvf::Vec3d> fractureBoundary = RigConvexHull::compute2d( relativePos );
fractureBoundary.push_back( fractureBoundary.front() );
for ( int i = 0; i < static_cast<int>( xCoords.size() ) - 1; i++ )
{
for ( int j = 0; j < static_cast<int>( depthCoords.size() ) - 1; j++ )
{
cvf::BoundingBox bb;
bb.add( cvf::Vec3d( xCoords[i], depthCoords[j], 0.0 ) );
bb.add( cvf::Vec3d( xCoords[i + 1], depthCoords[j], 0.0 ) );
bb.add( cvf::Vec3d( xCoords[i + 1], depthCoords[j + 1], 0.0 ) );
bb.add( cvf::Vec3d( xCoords[i], depthCoords[j + 1], 0.0 ) );
if ( RigCellGeometryTools::pointInsidePolygon2D( bb.center(), fractureBoundary ) )
{
double value = interpolateProperty( bb.center(), relativePos, fractureDefinition, propertyIndex, timeStepIndex );
vec[j][i] = value;
}
}
}
return vec;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigThermalFractureResultUtil::createFractureTriangleGeometry( std::shared_ptr<const RigThermalFractureDefinition> fractureDefinition,
int activeTimeStepIndex,
double xScaleFactor,
double yScaleFactor,
double wellPathIntersectionAtFractureDepth,
std::vector<cvf::Vec3f>* vertices,
std::vector<cvf::uint>* triangleIndices )
{
// Convert to coordinates relative to center node
std::vector<cvf::Vec3d> points = getRelativeCoordinates( fractureDefinition, activeTimeStepIndex );
// Create bounding box
cvf::BoundingBox boundingBox;
for ( auto p : points )
boundingBox.add( p );
boundingBox.expand( 1.0 );
// Generate a uniform mesh
auto [xCoords, depthCoords] = generateUniformMesh( boundingBox, NUM_SAMPLES_X, NUM_SAMPLES_Y );
// Code adapted from RigStimPlanFractureDefinition
std::vector<double> adjustedYs = depthCoords;
cvf::uint lenXcoords = static_cast<cvf::uint>( xCoords.size() );
for ( cvf::uint k = 0; k < adjustedYs.size(); k++ )
{
for ( cvf::uint i = 0; i < lenXcoords; i++ )
{
cvf::Vec3f node = cvf::Vec3f( xCoords[i], adjustedYs[k], 0 );
vertices->push_back( node );
if ( i < lenXcoords - 1 && k < adjustedYs.size() - 1 )
{
double THRESHOLD_VALUE = 1e-5;
if ( xCoords[i] < THRESHOLD_VALUE )
{
// Upper triangle
triangleIndices->push_back( i + k * lenXcoords );
triangleIndices->push_back( ( i + 1 ) + k * lenXcoords );
triangleIndices->push_back( ( i + 1 ) + ( k + 1 ) * lenXcoords );
// Lower triangle
triangleIndices->push_back( i + k * lenXcoords );
triangleIndices->push_back( ( i + 1 ) + ( k + 1 ) * lenXcoords );
triangleIndices->push_back( ( i ) + ( k + 1 ) * lenXcoords );
}
else
{
// Upper triangle
triangleIndices->push_back( i + k * lenXcoords );
triangleIndices->push_back( ( i + 1 ) + k * lenXcoords );
triangleIndices->push_back( ( i ) + ( k + 1 ) * lenXcoords );
// Lower triangle
triangleIndices->push_back( ( i + 1 ) + k * lenXcoords );
triangleIndices->push_back( ( i + 1 ) + ( k + 1 ) * lenXcoords );
triangleIndices->push_back( ( i ) + ( k + 1 ) * lenXcoords );
}
}
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<double> RigThermalFractureResultUtil::fractureGridResults( std::shared_ptr<const RigThermalFractureDefinition> fractureDefinition,
const QString& resultName,
const QString& unitName,
size_t timeStepIndex )
{
std::vector<double> fractureGridResults;
const std::vector<std::vector<double>>& resultValuesAtTimeStep =
getDataAtTimeIndex( fractureDefinition, resultName, unitName, timeStepIndex );
for ( int i = 0; i < static_cast<int>( NUM_SAMPLES_X ) - 2; i++ )
{
for ( int j = 0; j < static_cast<int>( NUM_SAMPLES_Y ) - 2; j++ )
{
if ( j + 1 < static_cast<int>( resultValuesAtTimeStep.size() ) && i + 1 < static_cast<int>( resultValuesAtTimeStep[j + 1].size() ) )
{
fractureGridResults.push_back( resultValuesAtTimeStep[j + 1][i + 1] );
}
else
{
fractureGridResults.push_back( HUGE_VAL );
}
}
}
return fractureGridResults;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
cvf::cref<RigFractureGrid>
RigThermalFractureResultUtil::createFractureGrid( std::shared_ptr<const RigThermalFractureDefinition> fractureDefinition,
const QString& resultName,
int activeTimeStepIndex,
double xScaleFactor,
double yScaleFactor,
double wellPathIntersectionAtFractureDepth,
RiaDefines::EclipseUnitSystem requiredUnitSet )
{
// Convert to coordinates relative to center node
std::vector<cvf::Vec3d> points = getRelativeCoordinates( fractureDefinition, activeTimeStepIndex );
QString conductivityUnitTextOnFile;
std::vector<std::pair<QString, QString>> propertyNamesUnitsOnFile = fractureDefinition->getPropertyNamesUnits();
for ( auto properyNameUnit : propertyNamesUnitsOnFile )
{
if ( resultName == properyNameUnit.first )
{
conductivityUnitTextOnFile = properyNameUnit.second;
}
}
CAF_ASSERT( !conductivityUnitTextOnFile.isEmpty() );
if ( conductivityUnitTextOnFile.isEmpty() )
{
RiaLogging::error( "Did not find unit for conductivity on file" );
return nullptr;
}
std::vector<std::vector<double>> conductivityValues =
getDataAtTimeIndex( fractureDefinition, resultName, conductivityUnitTextOnFile, activeTimeStepIndex );
if ( conductivityValues.empty() )
{
RiaLogging::error( QString( "No conductivity values found for result: %1" ).arg( resultName ) );
return nullptr;
}
// Check that the data is in the required unit system.
// Convert if not the case.
if ( requiredUnitSet != fractureDefinition->unitSystem() )
{
// Convert to the conductivity unit system used by the fracture template
// The conductivity value is used in the computations of transmissibility when exporting COMPDAT, and has unit
// md-m or md-ft This unit must match the unit used to represent coordinates of the grid used for export
for ( auto& yValues : conductivityValues )
{
for ( auto& xVal : yValues )
{
if ( requiredUnitSet == RiaDefines::EclipseUnitSystem::UNITS_FIELD )
{
xVal = RiaEclipseUnitTools::convertToFeet( xVal, conductivityUnitTextOnFile, false );
}
else if ( requiredUnitSet == RiaDefines::EclipseUnitSystem::UNITS_METRIC )
{
xVal = RiaEclipseUnitTools::convertToMeter( xVal, conductivityUnitTextOnFile, false );
}
}
}
}
// Create bounding box
cvf::BoundingBox boundingBox;
for ( auto p : points )
boundingBox.add( p );
boundingBox.expand( 1.0 );
// Generate a uniform mesh
auto [Xs, Ys] = generateUniformMesh( boundingBox, NUM_SAMPLES_X, NUM_SAMPLES_Y );
double centerZ = fractureDefinition->centerPosition().z();
double offset = wellPathIntersectionAtFractureDepth - centerZ;
std::vector<double> adjustedYs = adjustedYCoordsAroundWellPathPosition( Ys, offset );
std::vector<double> scaledXs = scaleVector( Xs, xScaleFactor );
std::vector<double> scaledYs = scaleVector( adjustedYs, yScaleFactor );
std::vector<double> xCoordsAtNodes = scaledXs;
std::vector<double> yCoordsAtNodes = scaledYs;
// Find center points
std::vector<double> xCoords;
for ( int i = 0; i < static_cast<int>( xCoordsAtNodes.size() ) - 1; i++ )
xCoords.push_back( ( xCoordsAtNodes[i] + xCoordsAtNodes[i + 1] ) / 2 );
std::vector<double> depthCoords;
for ( int i = 0; i < static_cast<int>( yCoordsAtNodes.size() ) - 1; i++ )
depthCoords.push_back( ( yCoordsAtNodes[i] + yCoordsAtNodes[i + 1] ) / 2 );
std::pair<size_t, size_t> wellCenterStimPlanCellIJ = std::make_pair( 0, 0 );
std::vector<RigFractureCell> stimPlanCells;
for ( int i = 0; i < static_cast<int>( xCoords.size() ) - 1; i++ )
{
for ( int j = 0; j < static_cast<int>( depthCoords.size() ) - 1; j++ )
{
std::vector<cvf::Vec3d> cellPolygon;
cellPolygon.push_back( cvf::Vec3d( xCoords[i], depthCoords[j + 1], 0.0 ) );
cellPolygon.push_back( cvf::Vec3d( xCoords[i + 1], depthCoords[j + 1], 0.0 ) );
cellPolygon.push_back( cvf::Vec3d( xCoords[i + 1], depthCoords[j], 0.0 ) );
cellPolygon.push_back( cvf::Vec3d( xCoords[i], depthCoords[j], 0.0 ) );
RigFractureCell stimPlanCell( cellPolygon, i, j );
if ( !conductivityValues.empty() ) // Assuming vector to be of correct length, or no values
{
stimPlanCell.setConductivityValue( conductivityValues[j + 1][i + 1] );
}
else
{
stimPlanCell.setConductivityValue( cvf::UNDEFINED_DOUBLE );
}
// The well path is intersecting the fracture at origo in the fracture coordinate system
// Find the Stimplan cell where the well path is intersecting
if ( cellPolygon[0].x() <= 0.0 && cellPolygon[1].x() >= 0.0 )
{
if ( cellPolygon[1].y() >= 0.0 && cellPolygon[2].y() <= 0.0 )
{
wellCenterStimPlanCellIJ = std::make_pair( stimPlanCell.getI(), stimPlanCell.getJ() );
}
}
stimPlanCells.push_back( stimPlanCell );
}
}
cvf::ref<RigFractureGrid> fractureGrid = new RigFractureGrid;
fractureGrid->setFractureCells( stimPlanCells );
fractureGrid->setWellCenterFractureCellIJ( wellCenterStimPlanCellIJ );
fractureGrid->setICellCount( NUM_SAMPLES_X - 2 );
fractureGrid->setJCellCount( NUM_SAMPLES_Y - 2 );
fractureGrid->ensureCellSearchTreeIsBuilt();
return cvf::cref<RigFractureGrid>( fractureGrid.p() );
}
//--------------------------------------------------------------------------------------------------
/// TODO: adapted from RimEnsembleFractureStatistics. Maybe extract?
//--------------------------------------------------------------------------------------------------
std::pair<std::vector<double>, std::vector<double>>
RigThermalFractureResultUtil::generateUniformMesh( const cvf::BoundingBox& bb, int NUM_SAMPLES_X, int NUM_SAMPLES_Y )
{
CAF_ASSERT( NUM_SAMPLES_X > 0 );
CAF_ASSERT( NUM_SAMPLES_Y > 0 );
double minX = bb.min().x();
double maxX = bb.max().x();
double minY = bb.min().y();
double maxY = bb.max().y();
std::vector<double> gridXs;
linearSampling( minX, maxX, NUM_SAMPLES_X, gridXs );
std::vector<double> gridYs;
linearSampling( minY, maxY, NUM_SAMPLES_Y, gridYs );
return std::make_pair( gridXs, gridYs );
}
//--------------------------------------------------------------------------------------------------
/// TODO: duplicated from RimEnsembleFractureStatistics. Extract to util.
//--------------------------------------------------------------------------------------------------
double RigThermalFractureResultUtil::linearSampling( double minValue, double maxValue, int numSamples, std::vector<double>& samples )
{
CAF_ASSERT( numSamples > 0 );
double sampleDistance = ( maxValue - minValue ) / numSamples;
for ( int s = 0; s < numSamples; s++ )
{
double pos = minValue + s * sampleDistance + sampleDistance * 0.5;
samples.push_back( pos );
}
return sampleDistance;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<double> RigThermalFractureResultUtil::scaleVector( const std::vector<double>& xs, double scaleFactor )
{
std::vector<double> scaledXs;
// Scale using 0 as scaling anchor
for ( double x : xs )
{
if ( scaleFactor != 1.0 ) x *= scaleFactor;
scaledXs.push_back( x );
}
return scaledXs;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<double> RigThermalFractureResultUtil::adjustedYCoordsAroundWellPathPosition( const std::vector<double>& ys, double offset )
{
std::vector<double> adjusted;
for ( auto p : ys )
adjusted.push_back( p + offset );
return adjusted;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RigThermalFractureResultUtil::appendDataToResultStatistics( std::shared_ptr<const RigThermalFractureDefinition> fractureDefinition,
const QString& resultName,
const QString& unit,
MinMaxAccumulator& minMaxAccumulator,
PosNegAccumulator& posNegAccumulator )
{
int propertyIndex = fractureDefinition->getPropertyIndex( resultName );
if ( propertyIndex < 0 ) return;
int maxTs = static_cast<int>( fractureDefinition->timeSteps().size() );
for ( int ts = 0; ts < maxTs; ts++ )
{
for ( int nodeIndex = 0; nodeIndex < static_cast<int>( fractureDefinition->numNodes() ); nodeIndex++ )
{
double value = fractureDefinition->getPropertyValue( propertyIndex, nodeIndex, ts );
minMaxAccumulator.addValue( value );
posNegAccumulator.addValue( value );
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<cvf::Vec3d>
RigThermalFractureResultUtil::getRelativeCoordinates( std::shared_ptr<const RigThermalFractureDefinition> fractureDefinition,
size_t timeStepIndex )
{
std::vector<cvf::Vec3d> relativePos = fractureDefinition->relativeCoordinates( static_cast<int>( timeStepIndex ) );
CAF_ASSERT( relativePos.size() == fractureDefinition->numNodes() );
for ( auto& r : relativePos )
{
r.z() *= -1.0;
}
// Store the depths of the points
std::vector<double> depths;
for ( auto& r : relativePos )
{
depths.push_back( r.z() );
}
cvf::Vec3d p0 = relativePos[0];
cvf::Vec3d p1 = relativePos[1];
cvf::Vec3d p2 = relativePos[2];
cvf::Plane plane;
plane.setFromPoints( p0, p1, p2 );
cvf::Vec3d planeNormal = plane.normal().getNormalized();
auto rotMat = cvf::GeometryTools::rotationMatrixBetweenVectors( planeNormal, ( cvf::Vec3d::Z_AXIS ).getNormalized() );
for ( auto& r : relativePos )
{
r.transformVector( rotMat );
r.z() = 0.0;
}
auto findPointsWithMostSimilarDepth = []( const std::vector<cvf::Vec3d>& points, const std::vector<double>& depths )
{
double minDiff = std::numeric_limits<double>::max();
cvf::Vec3d e1 = cvf::Vec3d::UNDEFINED;
cvf::Vec3d e2 = cvf::Vec3d::UNDEFINED;
for ( size_t i1 = 0; i1 < points.size(); i1++ )
{
for ( size_t i2 = 0; i2 < points.size(); i2++ )
{
if ( i1 != i2 )
{
double diff = std::fabs( depths[i1] - depths[i2] );
if ( diff < minDiff )
{
minDiff = diff;
e1 = points[i1];
e2 = points[i2];
}
}
}
}
return std::make_pair( e1, e2 );
};
// Find the rotation that aligns the data so that depth (z coord) is the most similar.
auto [e1, e2] = findPointsWithMostSimilarDepth( relativePos, depths );
cvf::Vec3d direction = e1 - e2;
cvf::Vec3d directionNormal = direction.getNormalized();
// Make sure normal is pointing down
if ( e1.x() < e2.x() ) directionNormal *= -1.0;
auto rotMat2 = cvf::GeometryTools::rotationMatrixBetweenVectors( directionNormal, cvf::Vec3d::X_AXIS );
for ( auto& r : relativePos )
{
r.transformVector( rotMat2 );
}
return relativePos;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::pair<cvf::Vec3d, cvf::Vec3d>
RigThermalFractureResultUtil::computePositionAndRotation( std::shared_ptr<const RigThermalFractureDefinition> fractureDefinition,
size_t timeStepIndex )
{
std::vector<cvf::Vec3d> relativePos = fractureDefinition->relativeCoordinates( static_cast<int>( timeStepIndex ) );
CAF_ASSERT( relativePos.size() == fractureDefinition->numNodes() );
cvf::Plane plane;
cvf::Vec3d p0 = relativePos[0];
cvf::Vec3d p1 = relativePos[1];
cvf::Vec3d p2 = relativePos[2];
p0.z() *= -1.0;
p1.z() *= -1.0;
p2.z() *= -1.0;
plane.setFromPoints( p0, p1, p2 );
cvf::Vec3d planeNormal = plane.normal().getNormalized();
RiaLogging::info( QString( "Plane normal: [%1 %2 %3]" ).arg( planeNormal.x() ).arg( planeNormal.y() ).arg( planeNormal.z() ) );
cvf::Plane xyPlane;
xyPlane.setFromPointAndNormal( cvf::Vec3d::ZERO, cvf::Vec3d::Z_AXIS );
cvf::Vec3d inPlane;
if ( !xyPlane.projectVector( planeNormal, &inPlane ) ) RiaLogging::info( "Failed to project vector" );
double azimuth = cvf::Math::toDegrees( cvf::GeometryTools::getAngle( inPlane, cvf::Vec3d::Y_AXIS ) ) + 90.0;
double dip = cvf::Math::toDegrees( cvf::GeometryTools::getAngle( inPlane, cvf::Vec3d::Z_AXIS ) ) - 90.0;
double tilt = cvf::Math::toDegrees( cvf::GeometryTools::getAngle( planeNormal, cvf::Vec3d::Z_AXIS ) ) - 90.0;
RiaLogging::info( QString( "Dip: %1" ).arg( dip ) );
RiaLogging::info( QString( "Tilt: %1" ).arg( tilt ) );
RiaLogging::info( QString( "Azimuth: %1" ).arg( azimuth ) );
cvf::Vec3d rotation( azimuth, dip, tilt );
return std::make_pair( fractureDefinition->centerPosition(), rotation );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
double RigThermalFractureResultUtil::interpolateProperty( const cvf::Vec3d& position,
const std::vector<cvf::Vec3d>& points,
std::shared_ptr<const RigThermalFractureDefinition> fractureDefinition,
int propertyIndex,
size_t timeStepIndex )
{
// Compute the distance to the other points
std::vector<std::pair<double, int>> distances;
int index = 0;
for ( auto p : points )
{
double distance = position.pointDistance( p );
distances.push_back( std::make_pair( distance, index ) );
index++;
}
// Sort by distance
std::sort( distances.begin(), distances.end() );
// Create inverse-distance-weighthed mean of first few points
size_t numPoints = 3;
RiaWeightedMeanCalculator<double> calc;
for ( size_t i = 0; i < numPoints; i++ )
{
auto [distance, nodeIndex] = distances[i];
double value = fractureDefinition->getPropertyValue( propertyIndex, nodeIndex, static_cast<int>( timeStepIndex ) );
calc.addValueAndWeight( value, std::pow( 1.0 / distance, 2.0 ) );
}
return calc.weightedMean();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::pair<double, double> RigThermalFractureResultUtil::minMaxDepth( std::shared_ptr<const RigThermalFractureDefinition> fractureDefinition,
int activeTimeStepIndex )
{
auto getBoundingBox = []( const std::vector<cvf::Vec3d>& coords )
{
cvf::BoundingBox bb;
for ( auto c : coords )
bb.add( c );
return bb;
};
auto relativeCoords = getRelativeCoordinates( fractureDefinition, activeTimeStepIndex );
auto bb = getBoundingBox( relativeCoords );
double centerZ = fractureDefinition->centerPosition().z();
// Y is depth in fracture coordinate system.
return std::make_pair( centerZ + bb.min().y(), centerZ + bb.max().y() );
}