///////////////////////////////////////////////////////////////////////////////// // // 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 "RimEnsembleFractureStatistics.h" #include "RiaDefines.h" #include "RiaInterpolationTools.h" #include "RiaLogging.h" #include "RiaPreferences.h" #include "RiaWeightedGeometricMeanCalculator.h" #include "RiaWeightedHarmonicMeanCalculator.h" #include "RigFractureGrid.h" #include "RigSlice2D.h" #include "RigStatisticsMath.h" #include "RigStimPlanFractureDefinition.h" #include "RifCsvDataTableFormatter.h" #include "RifEnsembleFractureStatisticsExporter.h" #include "RifStimPlanXmlReader.h" #include "FractureCommands/RicNewStimPlanFractureTemplateFeature.h" #include "cafAppEnum.h" #include "cafPdmUiTextEditor.h" #include "cafPdmUiToolButtonEditor.h" #include "cafPdmUiTreeSelectionEditor.h" #include #include #include namespace caf { template <> void caf::AppEnum::setUp() { addItem( RimEnsembleFractureStatistics::StatisticsType::MEAN, "MEAN", "Mean" ); addItem( RimEnsembleFractureStatistics::StatisticsType::MIN, "MIN", "Minimum" ); addItem( RimEnsembleFractureStatistics::StatisticsType::MAX, "MAX", "Maximum" ); addItem( RimEnsembleFractureStatistics::StatisticsType::P10, "P10", "P10" ); addItem( RimEnsembleFractureStatistics::StatisticsType::P50, "P50", "P50" ); addItem( RimEnsembleFractureStatistics::StatisticsType::P90, "P90", "P90" ); addItem( RimEnsembleFractureStatistics::StatisticsType::OCCURRENCE, "OCCURRENCE", "Occurrence" ); setDefault( RimEnsembleFractureStatistics::StatisticsType::MEAN ); } template <> void caf::AppEnum::setUp() { addItem( RimEnsembleFractureStatistics::MeshType::ADAPTIVE, "ADAPTIVE", "Adaptive" ); addItem( RimEnsembleFractureStatistics::MeshType::UNIFORM, "UNIFORM", "Uniform" ); addItem( RimEnsembleFractureStatistics::MeshType::NAIVE, "NAIVE", "Naive" ); setDefault( RimEnsembleFractureStatistics::MeshType::ADAPTIVE ); } template <> void caf::AppEnum::setUp() { addItem( RimEnsembleFractureStatistics::MeanType::HARMONIC, "HARMONIC", "Harmonic" ); addItem( RimEnsembleFractureStatistics::MeanType::ARITHMETIC, "ARITHMETIC", "Artihmetic" ); addItem( RimEnsembleFractureStatistics::MeanType::GEOMETRIC, "GEOMETRIC", "Geometric" ); addItem( RimEnsembleFractureStatistics::MeanType::MINIMUM, "MINIMUM", "Minimum" ); setDefault( RimEnsembleFractureStatistics::MeanType::HARMONIC ); } template <> void caf::AppEnum::setUp() { addItem( RimEnsembleFractureStatistics::AdaptiveSamplingSizeType::AVERAGE, "AVERAGE", "Average" ); addItem( RimEnsembleFractureStatistics::AdaptiveSamplingSizeType::MINIMUM, "MINIMUM", "Minimum" ); addItem( RimEnsembleFractureStatistics::AdaptiveSamplingSizeType::MAXIMUM, "MAXIMUM", "Maximum" ); addItem( RimEnsembleFractureStatistics::AdaptiveSamplingSizeType::USER_DEFINED, "USER_DEFINED", "User-Defined" ); setDefault( RimEnsembleFractureStatistics::AdaptiveSamplingSizeType::AVERAGE ); } } // namespace caf CAF_PDM_SOURCE_INIT( RimEnsembleFractureStatistics, "EnsembleFractureStatistics" ); //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimEnsembleFractureStatistics::RimEnsembleFractureStatistics() { CAF_PDM_InitObject( "Ensemble Fracture Statistics", ":/FractureTemplate16x16.png", "", "" ); CAF_PDM_InitFieldNoDefault( &m_filePaths, "FilePaths", "", "", "", "" ); CAF_PDM_InitFieldNoDefault( &m_filePathsTable, "FilePathsTable", "File Paths Table", "", "", "" ); m_filePathsTable.uiCapability()->setUiEditorTypeName( caf::PdmUiTextEditor::uiEditorTypeName() ); m_filePathsTable.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::HIDDEN ); m_filePathsTable.uiCapability()->setUiReadOnly( true ); m_filePathsTable.xmlCapability()->disableIO(); CAF_PDM_InitFieldNoDefault( &m_meshType, "MeshType", "Mesh Type", "", "", "" ); // Uniform sampling CAF_PDM_InitField( &m_numSamplesX, "NumberOfSamplesX", 100, "X", "", "", "" ); CAF_PDM_InitField( &m_numSamplesY, "NumberOfSamplesY", 200, "Y", "", "", "" ); // Adaptive sampling CAF_PDM_InitFieldNoDefault( &m_adaptiveMeanType, "AdaptiveMeanType", "Mean Type", "", "", "" ); CAF_PDM_InitFieldNoDefault( &m_adaptiveSamplingSizeType, "AdaptiveSamplingSizeType", "Sampling Type", "", "", "" ); CAF_PDM_InitField( &m_adaptiveNumSamplesY, "AdaptiveNumSamplesY", 30, "Number of Samples Y", "", "", "" ); std::vector> defaultStatisticsTypes = { caf::AppEnum( RimEnsembleFractureStatistics::StatisticsType::MEAN ) }; CAF_PDM_InitField( &m_selectedStatisticsType, "SelectedStatisticsType", defaultStatisticsTypes, "Statistics Type", "", "", "" ); m_selectedStatisticsType.uiCapability()->setUiEditorTypeName( caf::PdmUiTreeSelectionEditor::uiEditorTypeName() ); m_selectedStatisticsType.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::TOP ); CAF_PDM_InitFieldNoDefault( &m_computeStatistics, "ComputeStatistics", "Compute Templates", "", "", "" ); m_computeStatistics.uiCapability()->setUiEditorTypeName( caf::PdmUiToolButtonEditor::uiEditorTypeName() ); m_computeStatistics.uiCapability()->setUiLabelPosition( caf::PdmUiItemInfo::HIDDEN ); setDeletable( true ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimEnsembleFractureStatistics::~RimEnsembleFractureStatistics() { } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimEnsembleFractureStatistics::addFilePath( const QString& filePath ) { m_filePaths.v().push_back( filePath ); m_filePathsTable = generateFilePathsTable(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimEnsembleFractureStatistics::generateFilePathsTable() { QString body; for ( auto prop : m_filePaths.v() ) { body.append( prop.path() + "
" ); } return body; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QList RimEnsembleFractureStatistics::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions, bool* useOptionsOnly ) { QList options; if ( fieldNeedingOptions == &m_selectedStatisticsType ) { for ( size_t i = 0; i < caf::AppEnum::size(); ++i ) { caf::AppEnum t = caf::AppEnum::fromIndex( i ); t.uiText(); options.push_back( caf::PdmOptionItemInfo( t.uiText(), t.value() ) ); } } return options; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimEnsembleFractureStatistics::defineEditorAttribute( const caf::PdmFieldHandle* field, QString uiConfigName, caf::PdmUiEditorAttribute* attribute ) { if ( field == &m_filePathsTable ) { auto myAttr = dynamic_cast( attribute ); if ( myAttr ) { myAttr->wrapMode = caf::PdmUiTextEditorAttribute::NoWrap; myAttr->textMode = caf::PdmUiTextEditorAttribute::HTML; } } else if ( field == &m_selectedStatisticsType ) { caf::PdmUiTreeSelectionEditorAttribute* attrib = dynamic_cast( attribute ); if ( attrib ) { attrib->showTextFilter = false; attrib->showToggleAllCheckbox = false; attrib->singleSelectionMode = false; } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimEnsembleFractureStatistics::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) { if ( changedField == &m_computeStatistics ) { m_computeStatistics = false; std::vector filePaths = computeStatistics(); RicNewStimPlanFractureTemplateFeature::createNewTemplatesFromFiles( filePaths ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimEnsembleFractureStatistics::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) { uiOrdering.add( nameField() ); uiOrdering.add( &m_filePathsTable ); uiOrdering.add( &m_meshType ); uiOrdering.add( &m_numSamplesX ); uiOrdering.add( &m_numSamplesY ); bool isUniformMesh = m_meshType() == MeshType::UNIFORM; m_numSamplesX.uiCapability()->setUiHidden( !isUniformMesh ); m_numSamplesY.uiCapability()->setUiHidden( !isUniformMesh ); uiOrdering.add( &m_adaptiveMeanType ); uiOrdering.add( &m_adaptiveSamplingSizeType ); uiOrdering.add( &m_adaptiveNumSamplesY ); bool isAdaptiveMesh = m_meshType() == MeshType::ADAPTIVE; m_adaptiveMeanType.uiCapability()->setUiHidden( !isAdaptiveMesh ); m_adaptiveSamplingSizeType.uiCapability()->setUiHidden( !isAdaptiveMesh ); bool adaptiveSamplesUserDefined = m_adaptiveSamplingSizeType() == AdaptiveSamplingSizeType::USER_DEFINED; m_adaptiveNumSamplesY.uiCapability()->setUiHidden( !isAdaptiveMesh || !adaptiveSamplesUserDefined ); uiOrdering.add( &m_selectedStatisticsType ); uiOrdering.add( &m_computeStatistics ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimEnsembleFractureStatistics::loadAndUpdateData() { m_filePathsTable = generateFilePathsTable(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimEnsembleFractureStatistics::computeStatistics() { auto unitSystem = RiaDefines::EclipseUnitSystem::UNITS_METRIC; std::vector> stimPlanFractureDefinitions = readFractureDefinitions( m_filePaths.v(), unitSystem ); std::set> availableResults = findAllResultNames( stimPlanFractureDefinitions ); std::map, std::shared_ptr> statisticsGridsAll; auto selectedStatistics = m_selectedStatisticsType.value(); // TODO: take from an incoming xml? double timeStep = 1.0; double referenceDepth = 0.0; for ( auto definition : stimPlanFractureDefinitions ) { referenceDepth += computeDepthOfWellPathAtFracture( definition ); } referenceDepth /= stimPlanFractureDefinitions.size(); std::vector gridXs; std::vector gridYs; auto [minX, maxX, minY, maxY] = findSamplingIntervals( stimPlanFractureDefinitions, gridXs, gridYs ); RiaLogging::info( QString( "Ensemble Fracture Size: X = [%1, %2] Y = [%3, %4]" ).arg( minX ).arg( maxX ).arg( minY ).arg( maxY ) ); for ( auto result : availableResults ) { RiaLogging::info( QString( "Creating statistics for result: %1" ).arg( result.first ) ); std::vector> fractureGrids = createFractureGrids( stimPlanFractureDefinitions, unitSystem, result.first ); std::vector> samples( gridXs.size() * gridYs.size() ); sampleAllGrids( fractureGrids, gridXs, gridYs, samples ); std::map> statisticsGrids; generateStatisticsGrids( samples, gridXs.size(), gridYs.size(), statisticsGrids, selectedStatistics ); for ( auto [statType, slice] : statisticsGrids ) { auto key = std::make_pair( statType, result.first ); statisticsGridsAll[key] = slice; } } std::vector xmlFilePaths; // Save images in snapshot catalog relative to project directory RiaApplication* app = RiaApplication::instance(); QString outputDirectoryPath = app->createAbsolutePathFromProjectRelativePath( "fracturestats" ); QDir outputDirectory( outputDirectoryPath ); if ( !outputDirectory.exists() ) { outputDirectory.mkpath( outputDirectoryPath ); } for ( auto t : selectedStatistics ) { QString text = t.text(); // Get the all the properties for this statistics type std::vector> statisticsSlices; std::vector> properties; for ( auto result : availableResults ) { properties.push_back( result ); std::shared_ptr slice = statisticsGridsAll[std::make_pair( t.value(), result.first )]; statisticsSlices.push_back( slice ); QString csvFilePath = outputDirectoryPath + "/" + text + "-" + result.first + ".csv"; writeStatisticsToCsv( csvFilePath, *slice ); } QString xmlFilePath = outputDirectoryPath + "/" + name() + "-" + text + ".xml"; // TODO: add offset for grid ys std::vector gridYsWithOffset; for ( double depth : gridYs ) gridYsWithOffset.push_back( referenceDepth - depth ); RiaLogging::info( QString( "Writing fracture group statistics to: %1" ).arg( xmlFilePath ) ); RifEnsembleFractureStatisticsExporter::writeAsStimPlanXml( statisticsSlices, properties, xmlFilePath, gridXs, gridYsWithOffset, timeStep, unitSystem ); xmlFilePaths.push_back( xmlFilePath ); } return xmlFilePaths; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector> RimEnsembleFractureStatistics::readFractureDefinitions( const std::vector& filePaths, RiaDefines::EclipseUnitSystem unitSystem ) const { double conductivityScaleFactor = 1.0; std::vector> results; for ( auto filePath : m_filePaths.v() ) { RiaLogging::info( QString( "Loading file: %1" ).arg( filePath.path() ) ); QString errorMessage; cvf::ref stimPlanFractureDefinitionData = RifStimPlanXmlReader::readStimPlanXMLFile( filePath.path(), conductivityScaleFactor, RifStimPlanXmlReader::MirrorMode::MIRROR_AUTO, unitSystem, &errorMessage ); if ( !errorMessage.isEmpty() ) { RiaLogging::error( QString( "Error when reading file: '%1'" ).arg( errorMessage ) ); } if ( stimPlanFractureDefinitionData.notNull() ) { results.push_back( stimPlanFractureDefinitionData ); } } return results; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::set> RimEnsembleFractureStatistics::findAllResultNames( const std::vector>& stimPlanFractureDefinitions ) { std::set> resultNames; for ( auto stimPlanFractureDefinitionData : stimPlanFractureDefinitions ) { for ( auto propertyNameWithUnit : stimPlanFractureDefinitionData->getStimPlanPropertyNamesUnits() ) { resultNames.insert( propertyNameWithUnit ); } } return resultNames; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector> RimEnsembleFractureStatistics::createFractureGrids( const std::vector>& stimPlanFractureDefinitions, RiaDefines::EclipseUnitSystem unitSystem, const QString& resultNameOnFile ) { // Defaults to avoid scaling double halfLengthScaleFactor = 1.0; double heightScaleFactor = 1.0; std::vector> fractureGrids; for ( auto stimPlanFractureDefinitionData : stimPlanFractureDefinitions ) { double wellPathDepthAtFracture = computeDepthOfWellPathAtFracture( stimPlanFractureDefinitionData ); // Always use last time steps std::vector timeSteps = stimPlanFractureDefinitionData->timeSteps(); int activeTimeStepIndex = timeSteps.size() - 1; cvf::cref fractureGrid = stimPlanFractureDefinitionData->createFractureGrid( resultNameOnFile, activeTimeStepIndex, halfLengthScaleFactor, heightScaleFactor, wellPathDepthAtFracture, unitSystem ); if ( fractureGrid.notNull() ) { fractureGrids.push_back( fractureGrid ); } } return fractureGrids; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::tuple RimEnsembleFractureStatistics::findSamplingIntervals( const std::vector>& stimPlanFractureDefinitions, std::vector& gridXs, std::vector& gridYs ) const { // Find min and max extent of all the grids auto [minX, maxX, minY, maxY] = findMaxGridExtents( stimPlanFractureDefinitions ); if ( m_meshType() == MeshType::UNIFORM ) { generateUniformMesh( minX, maxX, minY, maxY, gridXs, gridYs ); } else if ( m_meshType() == MeshType::NAIVE ) { generateNaiveMesh( minX, maxX, minY, maxY, stimPlanFractureDefinitions, gridXs, gridYs ); } else if ( m_meshType() == MeshType::ADAPTIVE ) { generateAdaptiveMesh( minX, maxX, minY, maxY, stimPlanFractureDefinitions, gridXs, gridYs ); } return std::make_tuple( minX, maxX, minY, maxY ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::tuple RimEnsembleFractureStatistics::findMaxGridExtents( const std::vector>& stimPlanFractureDefinitions ) { double minX = std::numeric_limits::max(); double maxX = -std::numeric_limits::max(); double minY = std::numeric_limits::max(); double maxY = -std::numeric_limits::max(); for ( auto def : stimPlanFractureDefinitions ) { minX = std::min( minX, def->xs().front() ); maxX = std::max( maxX, def->xs().back() ); double offset = computeDepthOfWellPathAtFracture( def ); minY = std::min( minY, offset + def->ys().back() ); maxY = std::max( maxY, offset + def->ys().front() ); } return std::make_tuple( minX, maxX, minY, maxY ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimEnsembleFractureStatistics::generateUniformMesh( double minX, double maxX, double minY, double maxY, std::vector& gridXs, std::vector& gridYs ) const { int numSamplesX = m_numSamplesX(); double sampleDistanceX = linearSampling( minX, maxX, numSamplesX, gridXs ); int numSamplesY = m_numSamplesY(); double sampleDistanceY = linearSampling( minY, maxY, numSamplesY, gridYs ); RiaLogging::info( QString( "Uniform Mesh. Output size: %1x%2. Sampling Distance X = %3 Sampling Distance Y = %4" ) .arg( numSamplesX ) .arg( numSamplesY ) .arg( sampleDistanceX ) .arg( sampleDistanceY ) ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimEnsembleFractureStatistics::generateNaiveMesh( double minX, double maxX, double minY, double maxY, const std::vector>& stimPlanFractureDefinitions, std::vector& gridXs, std::vector& gridYs ) const { // Find max number of cells in x direction int maxNx = 0; for ( auto def : stimPlanFractureDefinitions ) { maxNx = std::max( maxNx, static_cast( def->xs().size() ) ); } // Do linear sampling in x drection linearSampling( minX, maxX, maxNx, gridXs ); std::vector depths; for ( auto def : stimPlanFractureDefinitions ) { double offset = computeDepthOfWellPathAtFracture( def ); for ( double y : def->ys() ) { depths.push_back( offset + y ); } } std::sort( depths.begin(), depths.end() ); gridYs = depths; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimEnsembleFractureStatistics::generateAdaptiveMesh( double minX, double maxX, double minY, double maxY, const std::vector>& stimPlanFractureDefinitions, std::vector& gridXs, std::vector& gridYs ) const { // Find max number of cells in x direction int maxNx = 0; for ( auto def : stimPlanFractureDefinitions ) { maxNx = std::max( maxNx, static_cast( def->xs().size() ) ); } // Do linear sampling in x drection linearSampling( minX, maxX, maxNx, gridXs ); std::vector layers; generateAllLayers( stimPlanFractureDefinitions, layers ); const int targetNumLayers = getTargetNumberOfLayers( stimPlanFractureDefinitions ); // Group the layers into linearly spaced bins double totalDepth = maxY - minY; double binSize = totalDepth / targetNumLayers; RiaLogging::info( QString( "Adaptive mesh. Number of layers: %1. Layer thickness: %2" ).arg( targetNumLayers ).arg( binSize ) ); int nTotalMatches = 0; std::vector baseDepth; baseDepth.push_back( minY ); std::vector means; double sumMeans = 0.0; // Calculate for ( int layerNo = 0; layerNo < targetNumLayers; layerNo++ ) { double binTopDepth = minY + layerNo * binSize; double binBottomDepth = minY + ( layerNo + 1 ) * binSize; baseDepth.push_back( binBottomDepth ); RiaWeightedHarmonicMeanCalculator harmonicMeanCalculator; RiaWeightedGeometricMeanCalculator geometricMeanCalculator; double sum = 0.0; double nMatches = 0; double minThickness = std::numeric_limits::max(); for ( Layer layer : layers ) { // TODO: top and bottom is mixed up here (again!) if ( layer.centerDepth() > binTopDepth && layer.centerDepth() <= binBottomDepth ) { harmonicMeanCalculator.addValueAndWeight( layer.thickness(), 1.0 ); geometricMeanCalculator.addValueAndWeight( layer.thickness(), 1.0 ); minThickness = std::min( minThickness, layer.thickness() ); sum += layer.thickness(); nMatches++; nTotalMatches++; } } double arithmeticMean = 0.0; if ( nMatches > 0 ) arithmeticMean = sum / nMatches; double harmonicMean = harmonicMeanCalculator.weightedMean(); double geometricMean = geometricMeanCalculator.weightedMean(); RiaLogging::info( QString( "Binning layer #%1: [%2 - %3] n=%4 means: A=%5 H=%6 G=%7" ) .arg( layerNo ) .arg( binTopDepth ) .arg( binBottomDepth ) .arg( nMatches ) .arg( arithmeticMean ) .arg( harmonicMean ) .arg( geometricMean ) ); double mean = std::numeric_limits::infinity(); if ( m_adaptiveMeanType() == MeanType::HARMONIC ) { mean = harmonicMean; } else if ( m_adaptiveMeanType() == MeanType::ARITHMETIC ) { mean = arithmeticMean; } else if ( m_adaptiveMeanType() == MeanType::GEOMETRIC ) { mean = geometricMean; } else { CAF_ASSERT( m_adaptiveMeanType() == MeanType::MINIMUM ); mean = minThickness; } means.push_back( mean ); sumMeans += mean; } RiaLogging::info( QString( "Total matches: %1 (expected: %2)" ).arg( nTotalMatches ).arg( layers.size() ) ); double totalThickness = totalDepth; std::vector scaledMeans; double sumScaledMean = 0.0; for ( double mean : means ) { double scaledMean = binSize / mean; scaledMeans.push_back( scaledMean ); sumScaledMean += scaledMean; } RiaLogging::info( QString( "Total thickness: %1 Sum scaled mean: %2" ).arg( totalThickness ).arg( sumScaledMean ) ); std::vector AM; AM.push_back( 0.0 ); double sumAI = 0.0; for ( int layerNo = 0; layerNo < targetNumLayers; layerNo++ ) { double AI = scaledMeans[layerNo] * targetNumLayers / sumScaledMean; sumAI += AI; AM.push_back( sumAI ); } auto findSmallerIndex = []( double value, const std::vector& vec ) { for ( size_t i = 0; i < vec.size(); i++ ) { if ( vec[i] > value ) return i - 1; } return vec.size(); }; double prevDepth = baseDepth[0]; for ( int layerNo = 0; layerNo < targetNumLayers; layerNo++ ) { double ap = layerNo; int az = findSmallerIndex( ap, AM ); CAF_ASSERT( az >= 0 ); CAF_ASSERT( az < static_cast( AM.size() ) ); int azNext = az + 1; double ba = AM[az]; double bb = AM[azNext]; double be = bb - ba; double bc = baseDepth[az]; double bd = baseDepth[azNext]; double bf = bd - bc; double offset = ( ap - ba ) * bf / be; double as = bc + offset; double av = as - prevDepth; double ay = as - av / 2.0; RiaLogging::info( QString( "Res[%1] = %2 az=%3 ba=%4 bb=%5 bc=%6 av=%7 as=%8 offset=%9 azNext=%10" ) .arg( layerNo ) .arg( ay ) .arg( az ) .arg( ba ) .arg( bb ) .arg( bc ) .arg( av ) .arg( as ) .arg( offset ) .arg( azNext ) ); gridYs.push_back( as ); prevDepth = as; } gridYs.push_back( maxY ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimEnsembleFractureStatistics::generateAllLayers( const std::vector>& stimPlanFractureDefinitions, std::vector& layers ) { for ( auto def : stimPlanFractureDefinitions ) { double offset = computeDepthOfWellPathAtFracture( def ); bool isFirst = true; double topDepth = 0.0; for ( double y : def->ys() ) { double depth = offset + y; if ( !isFirst ) { double bottomDepth = depth; Layer layer( topDepth, bottomDepth ); layers.push_back( layer ); } isFirst = false; topDepth = depth; } } std::sort( layers.begin(), layers.end(), []( const Layer& a, const Layer& b ) { return a.centerDepth() > b.centerDepth(); } ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- int RimEnsembleFractureStatistics::getTargetNumberOfLayers( const std::vector>& stimPlanFractureDefinitions ) const { if ( m_adaptiveSamplingSizeType() == AdaptiveSamplingSizeType::USER_DEFINED ) return m_adaptiveNumSamplesY(); int maxNy = 0; int minNy = std::numeric_limits::max(); int sum = 0; for ( auto def : stimPlanFractureDefinitions ) { int ny = static_cast( def->ys().size() ); maxNy = std::max( maxNy, ny ); minNy = std::min( minNy, ny ); sum += ny; } if ( m_adaptiveSamplingSizeType() == AdaptiveSamplingSizeType::MAXIMUM ) return maxNy; else if ( m_adaptiveSamplingSizeType() == AdaptiveSamplingSizeType::MINIMUM ) return minNy; else { CAF_ASSERT( m_adaptiveSamplingSizeType() == AdaptiveSamplingSizeType::AVERAGE ); return static_cast( std::ceil( static_cast( sum ) / stimPlanFractureDefinitions.size() ) ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- double RimEnsembleFractureStatistics::linearSampling( double minValue, double maxValue, int numSamples, std::vector& samples ) { 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; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool RimEnsembleFractureStatistics::isCoordinateInsideFractureCell( double x, double y, const RigFractureCell& cell ) { const cvf::Vec3d& minPoint = cell.getPolygon()[0]; const cvf::Vec3d& maxPoint = cell.getPolygon()[2]; // TODO: Investigate strange ordering for y coords. return ( x > minPoint.x() && x <= maxPoint.x() && y <= minPoint.y() && y > maxPoint.y() ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- double RimEnsembleFractureStatistics::computeDepthOfWellPathAtFracture( cvf::ref stimPlanFractureDefinitionData ) { double firstTvd = stimPlanFractureDefinitionData->topPerfTvd(); double lastTvd = stimPlanFractureDefinitionData->bottomPerfTvd(); if ( firstTvd != HUGE_VAL && lastTvd != HUGE_VAL ) { return ( firstTvd + lastTvd ) / 2; } else { firstTvd = stimPlanFractureDefinitionData->minDepth(); lastTvd = stimPlanFractureDefinitionData->maxDepth(); return ( firstTvd + lastTvd ) / 2; } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimEnsembleFractureStatistics::sampleAllGrids( const std::vector>& fractureGrids, const std::vector& samplesX, const std::vector& samplesY, std::vector>& samples ) { for ( size_t y = 0; y < samplesY.size(); y++ ) { for ( size_t x = 0; x < samplesX.size(); x++ ) { double posX = samplesX[x]; double posY = samplesY[y]; for ( auto fractureGrid : fractureGrids ) { for ( auto fractureCell : fractureGrid->fractureCells() ) { if ( isCoordinateInsideFractureCell( posX, posY, fractureCell ) ) { size_t idx = y * samplesX.size() + x; samples[idx].push_back( fractureCell.getConductivityValue() ); break; } } } } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool RimEnsembleFractureStatistics::writeStatisticsToCsv( const QString& filePath, const RigSlice2D& samples ) { QFile data( filePath ); if ( !data.open( QFile::WriteOnly | QFile::Truncate ) ) { return false; } QTextStream stream( &data ); QString fieldSeparator = RiaPreferences::current()->csvTextExportFieldSeparator; RifCsvDataTableFormatter formatter( stream, fieldSeparator ); std::vector header; for ( size_t y = 0; y < samples.ny(); y++ ) header.push_back( RifTextDataTableColumn( "", RifTextDataTableDoubleFormat::RIF_FLOAT ) ); formatter.header( header ); for ( size_t y = 0; y < samples.ny(); y++ ) { for ( size_t x = 0; x < samples.nx(); x++ ) { formatter.add( samples.getValue( x, y ) ); } formatter.rowCompleted(); } formatter.tableCompleted(); return true; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimEnsembleFractureStatistics::generateStatisticsGrids( const std::vector>& samples, size_t numSamplesX, size_t numSamplesY, std::map>& statisticsGrids, const std::vector>& statisticsTypes ) { for ( auto t : statisticsTypes ) { std::shared_ptr grid = std::make_shared( numSamplesX, numSamplesY ); statisticsGrids[t.value()] = grid; } auto isCalculationEnabled = []( StatisticsType t, auto statisticsTypes ) { return std::find( statisticsTypes.begin(), statisticsTypes.end(), t ) != statisticsTypes.end(); }; bool calculateMin = isCalculationEnabled( StatisticsType::MIN, statisticsTypes ); bool calculateMax = isCalculationEnabled( StatisticsType::MAX, statisticsTypes ); bool calculateMean = isCalculationEnabled( StatisticsType::MEAN, statisticsTypes ); bool calculateP10 = isCalculationEnabled( StatisticsType::P10, statisticsTypes ); bool calculateP50 = isCalculationEnabled( StatisticsType::P50, statisticsTypes ); bool calculateP90 = isCalculationEnabled( StatisticsType::P90, statisticsTypes ); bool calculateOccurrence = isCalculationEnabled( StatisticsType::OCCURRENCE, statisticsTypes ); for ( size_t y = 0; y < numSamplesY; y++ ) { for ( size_t x = 0; x < numSamplesX; x++ ) { size_t idx = y * numSamplesX + x; if ( calculateMin || calculateMax || calculateMean ) { double min; double max; double sum; double range; double mean; double dev; RigStatisticsMath::calculateBasicStatistics( samples[idx], &min, &max, &sum, &range, &mean, &dev ); if ( calculateMean ) statisticsGrids[RimEnsembleFractureStatistics::StatisticsType::MEAN]->setValue( x, y, mean ); if ( calculateMin ) statisticsGrids[RimEnsembleFractureStatistics::StatisticsType::MIN]->setValue( x, y, min ); if ( calculateMax ) statisticsGrids[RimEnsembleFractureStatistics::StatisticsType::MAX]->setValue( x, y, max ); } if ( calculateP10 || calculateP50 || calculateP90 ) { double p10; double p50; double p90; double mean; RigStatisticsMath::calculateStatisticsCurves( samples[idx], &p10, &p50, &p90, &mean ); if ( calculateP10 ) statisticsGrids[RimEnsembleFractureStatistics::StatisticsType::P10]->setValue( x, y, p10 ); if ( calculateP50 ) statisticsGrids[RimEnsembleFractureStatistics::StatisticsType::P50]->setValue( x, y, p50 ); if ( calculateP90 ) statisticsGrids[RimEnsembleFractureStatistics::StatisticsType::P90]->setValue( x, y, p90 ); } if ( calculateOccurrence ) { statisticsGrids[RimEnsembleFractureStatistics::StatisticsType::OCCURRENCE]->setValue( x, y, samples[idx].size() ); } } } }