///////////////////////////////////////////////////////////////////////////////// // // 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 "RiaNumberFormat.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 ) { std::vector> fractureDefinitions = esf->readFractureDefinitions(); if ( esf->excludeZeroWidthFractures() ) { fractureDefinitions = RigEnsembleFractureStatisticsCalculator::removeZeroWidthDefinitions( fractureDefinitions ); } return createStatisticsData( fractureDefinitions, propertyType, numBins ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RigHistogramData RigEnsembleFractureStatisticsCalculator::createStatisticsData( const std::vector>& fractureDefinitions, PropertyType propertyType, int numBins ) { 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 ); std::vector histogram; RigHistogramCalculator histogramCalculator( histogramData.min, histogramData.max, numBins, &histogram ); for ( auto s : samples ) histogramCalculator.addValue( s ); histogramData.histogram = histogram; 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; // 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; // 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 ); auto [widthResultName, widthResultUnit] = RimStimPlanFractureTemplate::widthParameterNameAndUnit( fractureDefinitions[0] ); std::vector> widthGrids = RimEnsembleFractureStatistics::createFractureGrids( fractureDefinitions, RiaDefines::EclipseUnitSystem::UNITS_METRIC, widthResultName, RimEnsembleFractureStatistics::MeshAlignmentType::PERFORATION_DEPTH ); CAF_ASSERT( grids.size() == widthGrids.size() ); for ( size_t i = 0; i < grids.size(); i++ ) { double result = func( grids[i], widthGrids[i], RiaDefines::EclipseUnitSystem::UNITS_METRIC, widthResultUnit ); samples.push_back( result ); } 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( RiaNumberFormat::NumberFormatType::FIXED, 4 ); else return std::make_pair( RiaNumberFormat::NumberFormatType::FIXED, 1 ); }