///////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2020 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 "RigEnsembleFractureStatisticsCalculator.h" #include "RiaDefines.h" #include "RiaEclipseUnitTools.h" #include "RiaWeightedMeanCalculator.h" #include "RigFractureCell.h" #include "RigFractureGrid.h" #include "RigHistogramData.h" #include "RigStatisticsMath.h" #include "RigStimPlanFractureDefinition.h" #include "RigTransmissibilityEquations.h" #include "RimEnsembleFractureStatistics.h" #include "RimStimPlanFractureTemplate.h" #include "cafAppEnum.h" #include "cvfObject.h" #include namespace caf { template <> void caf::AppEnum::setUp() { addItem( RigEnsembleFractureStatisticsCalculator::PropertyType::HEIGHT, "HEIGHT", "Height" ); addItem( RigEnsembleFractureStatisticsCalculator::PropertyType::AREA, "AREA", "Area" ); addItem( RigEnsembleFractureStatisticsCalculator::PropertyType::WIDTH, "WIDTH", "Width" ); addItem( RigEnsembleFractureStatisticsCalculator::PropertyType::XF, "XF", "Halflength (Xf)" ); addItem( RigEnsembleFractureStatisticsCalculator::PropertyType::KFWF, "KFWF", "Conductivity (KfWf)" ); addItem( RigEnsembleFractureStatisticsCalculator::PropertyType::PERMEABILITY, "PERMEABILITY", "Permeability" ); addItem( RigEnsembleFractureStatisticsCalculator::PropertyType::FORMATION_DIP, "FORMATION_DIP", "Formation Dip" ); setDefault( RigEnsembleFractureStatisticsCalculator::PropertyType::HEIGHT ); } }; // namespace caf //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RigHistogramData RigEnsembleFractureStatisticsCalculator::createStatisticsData( const RimEnsembleFractureStatistics* esf, PropertyType propertyType, int numBins, RigHistogramCalculator::BinningMode binningMode, std::optional> customBinRange, RigHistogramCalculator::OutOfRangeHandling outOfRangeHandling ) { std::vector> fractureDefinitions = esf->readFractureDefinitions(); if ( esf->excludeZeroWidthFractures() ) { fractureDefinitions = RigEnsembleFractureStatisticsCalculator::removeZeroWidthDefinitions( fractureDefinitions ); } return createStatisticsData( fractureDefinitions, propertyType, numBins, binningMode, customBinRange, outOfRangeHandling ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RigHistogramData RigEnsembleFractureStatisticsCalculator::createStatisticsData( const std::vector>& fractureDefinitions, PropertyType propertyType, int numBins, RigHistogramCalculator::BinningMode binningMode, std::optional> customBinRange, RigHistogramCalculator::OutOfRangeHandling outOfRangeHandling ) { std::vector samples = calculateProperty( fractureDefinitions, propertyType ); RigHistogramData histogramData; double sum; double range; double dev; RigStatisticsMath::calculateBasicStatistics( samples, &histogramData.min, &histogramData.max, &sum, &range, &histogramData.mean, &dev ); double p50; double mean; RigStatisticsMath::calculateStatisticsCurves( samples, &histogramData.p10, &p50, &histogramData.p90, &mean, RigStatisticsMath::PercentileStyle::SWITCHED ); double binMin = customBinRange ? customBinRange->first : histogramData.min; double binMax = customBinRange ? customBinRange->second : histogramData.max; if ( binningMode == RigHistogramCalculator::BinningMode::LOGARITHMIC && binMin <= 0.0 ) { // Use the smallest positive value as the logarithmic range minimum. HUGE_VAL when there are none. PosNegAccumulator posNegAccumulator; posNegAccumulator.addData( samples ); binMin = posNegAccumulator.pos; } if ( !samples.empty() && binMin <= binMax && RigStatisticsTools::isValidNumber( binMin ) && RigStatisticsTools::isValidNumber( binMax ) ) { std::vector histogram; RigHistogramCalculator histogramCalculator( binMin, binMax, numBins, &histogram, binningMode, outOfRangeHandling ); for ( auto s : samples ) histogramCalculator.addValue( s ); histogramData.histogram = histogram; // Let the plot draw bin edges that match the histogram histogramData.min = binMin; histogramData.max = binMax; } return histogramData; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RigEnsembleFractureStatisticsCalculator::calculateProperty( const std::vector>& fractureDefinitions, PropertyType propertyType ) { std::vector samples; if ( propertyType == PropertyType::HEIGHT ) { samples = calculateGridStatistics( fractureDefinitions, &RigEnsembleFractureStatisticsCalculator::calculateHeight ); } else if ( propertyType == PropertyType::AREA ) { samples = calculateGridStatistics( fractureDefinitions, &RigEnsembleFractureStatisticsCalculator::calculateArea ); } else if ( propertyType == PropertyType::WIDTH ) { samples = calculateAreaWeightedStatistics( fractureDefinitions, &RigEnsembleFractureStatisticsCalculator::calculateAreaWeightedWidth ); } else if ( propertyType == PropertyType::PERMEABILITY ) { samples = calculateAreaWeightedStatistics( fractureDefinitions, &RigEnsembleFractureStatisticsCalculator::calculateAreaWeightedPermeability ); } else if ( propertyType == PropertyType::XF ) { samples = calculateGridStatistics( fractureDefinitions, &RigEnsembleFractureStatisticsCalculator::calculateXf ); } else if ( propertyType == PropertyType::KFWF ) { samples = calculateGridStatistics( fractureDefinitions, &RigEnsembleFractureStatisticsCalculator::calculateKfWf ); } else if ( propertyType == PropertyType::FORMATION_DIP ) { samples = calculateFormationDip( fractureDefinitions ); } return samples; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RigEnsembleFractureStatisticsCalculator::calculateGridStatistics( const std::vector>& fractureDefinitions, double( func )( cvf::cref ) ) { std::vector samples; if ( fractureDefinitions.empty() ) return samples; if ( fractureDefinitions[0]->conductivityResultNames().isEmpty() ) return samples; // TODO: heuristic to find conductivity name? QString conductivityResultName = fractureDefinitions[0]->conductivityResultNames()[0]; std::vector> grids = RimEnsembleFractureStatistics::createFractureGrids( fractureDefinitions, RiaDefines::EclipseUnitSystem::UNITS_METRIC, conductivityResultName, RimEnsembleFractureStatistics::MeshAlignmentType::PERFORATION_DEPTH ); for ( auto grid : grids ) { double result = func( grid ); samples.push_back( result ); } return samples; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- double RigEnsembleFractureStatisticsCalculator::calculateHeight( cvf::cref fractureGrid ) { double longestRange = 0.0; for ( size_t i = 0; i < fractureGrid->iCellCount(); i++ ) { double currentAggregatedDistanceY = 0.0; for ( size_t j = 0; j < fractureGrid->jCellCount(); j++ ) { size_t idx = fractureGrid->getGlobalIndexFromIJ( i, j ); auto fractureCell = fractureGrid->cellFromIndex( idx ); double conductivityValue = fractureCell.getConductivityValue(); if ( conductivityValue > 0.0 ) { currentAggregatedDistanceY += fractureCell.cellSizeZ(); } else { longestRange = std::max( longestRange, currentAggregatedDistanceY ); currentAggregatedDistanceY = 0.0; } } longestRange = std::max( longestRange, currentAggregatedDistanceY ); } return longestRange; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- double RigEnsembleFractureStatisticsCalculator::calculateArea( cvf::cref fractureGrid ) { double sum = 0.0; for ( auto fractureCell : fractureGrid->fractureCells() ) { double value = fractureCell.getConductivityValue(); if ( !std::isinf( value ) && value > 0.0 ) { sum += fractureCell.area(); } } return sum; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- double RigEnsembleFractureStatisticsCalculator::calculateKfWf( cvf::cref fractureGrid ) { RiaWeightedMeanCalculator calc; for ( auto fractureCell : fractureGrid->fractureCells() ) { double value = fractureCell.getConductivityValue(); if ( !std::isinf( value ) && value > 0.0 ) { double area = fractureCell.area(); calc.addValueAndWeight( value, area ); } } return calc.weightedMean(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RigEnsembleFractureStatisticsCalculator::calculateAreaWeightedStatistics( const std::vector>& fractureDefinitions, double( func )( cvf::cref, cvf::cref, RiaDefines::EclipseUnitSystem, const QString& ) ) { std::vector samples; if ( fractureDefinitions.empty() ) return samples; if ( fractureDefinitions[0]->conductivityResultNames().isEmpty() ) return samples; // TODO: heuristic to find conductivity name? QString conductivityResultName = fractureDefinitions[0]->conductivityResultNames()[0]; auto [widthResultName, widthResultUnit] = RimStimPlanFractureTemplate::widthParameterNameAndUnit( fractureDefinitions[0] ); // Create the grids one definition at a time, as a definition can lack conductivity or width data. This keeps the two grids paired. for ( const auto& definition : fractureDefinitions ) { std::vector> singleDefinition = { definition }; auto grids = RimEnsembleFractureStatistics::createFractureGrids( singleDefinition, RiaDefines::EclipseUnitSystem::UNITS_METRIC, conductivityResultName, RimEnsembleFractureStatistics::MeshAlignmentType::PERFORATION_DEPTH ); auto widthGrids = RimEnsembleFractureStatistics::createFractureGrids( singleDefinition, RiaDefines::EclipseUnitSystem::UNITS_METRIC, widthResultName, RimEnsembleFractureStatistics::MeshAlignmentType::PERFORATION_DEPTH ); if ( grids.empty() || widthGrids.empty() ) continue; samples.push_back( func( grids.front(), widthGrids.front(), RiaDefines::EclipseUnitSystem::UNITS_METRIC, widthResultUnit ) ); } return samples; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- double RigEnsembleFractureStatisticsCalculator::calculateAreaWeightedWidth( cvf::cref conductivityGrid, cvf::cref widthGrid, RiaDefines::EclipseUnitSystem widthUnitSystem, const QString& widthUnit ) { RiaWeightedMeanCalculator calc; const std::vector& conductivityCells = conductivityGrid->fractureCells(); const std::vector& widthCells = widthGrid->fractureCells(); CAF_ASSERT( conductivityCells.size() == widthCells.size() ); for ( size_t i = 0; i < conductivityCells.size(); i++ ) { double value = conductivityCells[i].getConductivityValue(); if ( !std::isinf( value ) && value > 0.0 ) { double cellArea = conductivityCells[i].area(); // TODO: conductivity is misleading here double width = convertUnit( widthCells[i].getConductivityValue(), widthUnitSystem, widthUnit ); calc.addValueAndWeight( width, cellArea ); } } return calc.weightedMean(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- double RigEnsembleFractureStatisticsCalculator::calculateAreaWeightedPermeability( cvf::cref conductivityGrid, cvf::cref widthGrid, RiaDefines::EclipseUnitSystem widthUnitSystem, const QString& widthUnit ) { RiaWeightedMeanCalculator calc; const std::vector& conductivityCells = conductivityGrid->fractureCells(); const std::vector& widthCells = widthGrid->fractureCells(); CAF_ASSERT( conductivityCells.size() == widthCells.size() ); for ( size_t i = 0; i < conductivityCells.size(); i++ ) { double conductivity = conductivityCells[i].getConductivityValue(); if ( !std::isinf( conductivity ) && conductivity > 0.0 ) { double cellArea = conductivityCells[i].area(); // TODO: conductivity is misleading here double width = convertUnit( widthCells[i].getConductivityValue(), widthUnitSystem, widthUnit ); double permeability = RigTransmissibilityEquations::permeability( conductivity, width ); calc.addValueAndWeight( permeability, cellArea ); } } return calc.weightedMean(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- double RigEnsembleFractureStatisticsCalculator::calculateXf( cvf::cref fractureGrid ) { double height = calculateHeight( fractureGrid ); double area = calculateArea( fractureGrid ); if ( height > 0.0 ) { double length = area / height; double halfLength = length / 2.0; return halfLength; } return 0.0; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RigEnsembleFractureStatisticsCalculator::calculateFormationDip( const std::vector>& fractureDefinitions ) { std::vector formationDips; for ( auto fractureDefinition : fractureDefinitions ) { formationDips.push_back( fractureDefinition->formationDip() ); } return formationDips; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- double RigEnsembleFractureStatisticsCalculator::convertUnit( double value, RiaDefines::EclipseUnitSystem unitSystem, const QString& unitName ) { if ( unitSystem == RiaDefines::EclipseUnitSystem::UNITS_METRIC ) { return RiaEclipseUnitTools::convertToMeter( value, unitName ); } else if ( unitSystem == RiaDefines::EclipseUnitSystem::UNITS_FIELD ) { return RiaEclipseUnitTools::convertToFeet( value, unitName ); } return value; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector> RigEnsembleFractureStatisticsCalculator::removeZeroWidthDefinitions( const std::vector>& fractureDefinitions ) { std::vector samples = calculateAreaWeightedStatistics( fractureDefinitions, &RigEnsembleFractureStatisticsCalculator::calculateAreaWeightedWidth ); std::vector> filteredFractureDefinitions; int index = 0; for ( double sample : samples ) { if ( sample > 0.0 ) { filteredFractureDefinitions.push_back( fractureDefinitions[index] ); } index++; } return filteredFractureDefinitions; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RigEnsembleFractureStatisticsCalculator::propertyTypes() { std::vector types = { RigEnsembleFractureStatisticsCalculator::PropertyType::HEIGHT, RigEnsembleFractureStatisticsCalculator::PropertyType::XF, RigEnsembleFractureStatisticsCalculator::PropertyType::AREA, RigEnsembleFractureStatisticsCalculator::PropertyType::PERMEABILITY, RigEnsembleFractureStatisticsCalculator::PropertyType::WIDTH, RigEnsembleFractureStatisticsCalculator::PropertyType::KFWF, RigEnsembleFractureStatisticsCalculator::PropertyType::FORMATION_DIP, }; return types; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::pair RigEnsembleFractureStatisticsCalculator::numberFormatForProperty( PropertyType propertyType ) { if ( propertyType == PropertyType::WIDTH ) return std::make_pair( caf::NumberFormatType::FIXED, 4 ); else return std::make_pair( caf::NumberFormatType::FIXED, 1 ); }