mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-24 22:27:55 -05:00
Conductivity and width grids were built as separate lists; a missing width grid shortened one list and widthGrids[i] went out of range. Build the grids per definition so they stay paired. Crash stack (signature 642357244e71): [3] cvf::cref<RigFractureGrid>::cref(cvf::cref<RigFractureGrid> const&) at cvfObject.inl:410 [4] RigEnsembleFractureStatisticsCalculator::calculateProperty(...) at RigEnsembleFractureStatisticsCalculator.cpp:156 [5] RimStimPlanFractureTemplate::generatePropertiesTable() const at RimStimPlanFractureTemplate.cpp:791 [6] RimStimPlanFractureTemplate::loadDataAndUpdate() at RimStimPlanFractureTemplate.cpp:221 [7] RicMeshFractureTemplateHelper<RimStimPlanFractureTemplate>::createNewTemplatesFromFiles(...) at RicMeshFractureTemplateHelper.inl:149 [8] RicMeshFractureTemplateHelper<RimStimPlanFractureTemplate>::createNewTemplates(...) at RicMeshFractureTemplateHelper.inl:89 [9] RicNewStimPlanFractureTemplateFeature::createNewTemplates() at RicNewStimPlanFractureTemplateFeature.cpp:58 [10] RicNewStimPlanFractureTemplateFeature::onActionTriggered(bool) at RicNewStimPlanFractureTemplateFeature.cpp:78
471 lines
22 KiB
C++
471 lines
22 KiB
C++
/////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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 <http://www.gnu.org/licenses/gpl.html>
|
|
// 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 <limits>
|
|
|
|
namespace caf
|
|
{
|
|
template <>
|
|
void caf::AppEnum<RigEnsembleFractureStatisticsCalculator::PropertyType>::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<std::pair<double, double>> customBinRange,
|
|
RigHistogramCalculator::OutOfRangeHandling outOfRangeHandling )
|
|
{
|
|
std::vector<cvf::ref<RigStimPlanFractureDefinition>> fractureDefinitions = esf->readFractureDefinitions();
|
|
|
|
if ( esf->excludeZeroWidthFractures() )
|
|
{
|
|
fractureDefinitions = RigEnsembleFractureStatisticsCalculator::removeZeroWidthDefinitions( fractureDefinitions );
|
|
}
|
|
|
|
return createStatisticsData( fractureDefinitions, propertyType, numBins, binningMode, customBinRange, outOfRangeHandling );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
RigHistogramData
|
|
RigEnsembleFractureStatisticsCalculator::createStatisticsData( const std::vector<cvf::ref<RigStimPlanFractureDefinition>>& fractureDefinitions,
|
|
PropertyType propertyType,
|
|
int numBins,
|
|
RigHistogramCalculator::BinningMode binningMode,
|
|
std::optional<std::pair<double, double>> customBinRange,
|
|
RigHistogramCalculator::OutOfRangeHandling outOfRangeHandling )
|
|
{
|
|
std::vector<double> 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<size_t> 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<double>
|
|
RigEnsembleFractureStatisticsCalculator::calculateProperty( const std::vector<cvf::ref<RigStimPlanFractureDefinition>>& fractureDefinitions,
|
|
PropertyType propertyType )
|
|
{
|
|
std::vector<double> 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<double> RigEnsembleFractureStatisticsCalculator::calculateGridStatistics(
|
|
const std::vector<cvf::ref<RigStimPlanFractureDefinition>>& fractureDefinitions,
|
|
double( func )( cvf::cref<RigFractureGrid> ) )
|
|
{
|
|
std::vector<double> 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<cvf::cref<RigFractureGrid>> 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<RigFractureGrid> 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<RigFractureGrid> 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<RigFractureGrid> fractureGrid )
|
|
{
|
|
RiaWeightedMeanCalculator<double> 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<double> RigEnsembleFractureStatisticsCalculator::calculateAreaWeightedStatistics(
|
|
const std::vector<cvf::ref<RigStimPlanFractureDefinition>>& fractureDefinitions,
|
|
double( func )( cvf::cref<RigFractureGrid>, cvf::cref<RigFractureGrid>, RiaDefines::EclipseUnitSystem, const QString& ) )
|
|
{
|
|
std::vector<double> 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<cvf::ref<RigStimPlanFractureDefinition>> 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<RigFractureGrid> conductivityGrid,
|
|
cvf::cref<RigFractureGrid> widthGrid,
|
|
RiaDefines::EclipseUnitSystem widthUnitSystem,
|
|
const QString& widthUnit )
|
|
{
|
|
RiaWeightedMeanCalculator<double> calc;
|
|
const std::vector<RigFractureCell>& conductivityCells = conductivityGrid->fractureCells();
|
|
const std::vector<RigFractureCell>& 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<RigFractureGrid> conductivityGrid,
|
|
cvf::cref<RigFractureGrid> widthGrid,
|
|
RiaDefines::EclipseUnitSystem widthUnitSystem,
|
|
const QString& widthUnit )
|
|
{
|
|
RiaWeightedMeanCalculator<double> calc;
|
|
const std::vector<RigFractureCell>& conductivityCells = conductivityGrid->fractureCells();
|
|
const std::vector<RigFractureCell>& 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<RigFractureGrid> 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<double>
|
|
RigEnsembleFractureStatisticsCalculator::calculateFormationDip( const std::vector<cvf::ref<RigStimPlanFractureDefinition>>& fractureDefinitions )
|
|
{
|
|
std::vector<double> 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<cvf::ref<RigStimPlanFractureDefinition>> RigEnsembleFractureStatisticsCalculator::removeZeroWidthDefinitions(
|
|
const std::vector<cvf::ref<RigStimPlanFractureDefinition>>& fractureDefinitions )
|
|
{
|
|
std::vector<double> samples =
|
|
calculateAreaWeightedStatistics( fractureDefinitions, &RigEnsembleFractureStatisticsCalculator::calculateAreaWeightedWidth );
|
|
|
|
std::vector<cvf::ref<RigStimPlanFractureDefinition>> filteredFractureDefinitions;
|
|
|
|
int index = 0;
|
|
for ( double sample : samples )
|
|
{
|
|
if ( sample > 0.0 )
|
|
{
|
|
filteredFractureDefinitions.push_back( fractureDefinitions[index] );
|
|
}
|
|
index++;
|
|
}
|
|
|
|
return filteredFractureDefinitions;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
std::vector<RigEnsembleFractureStatisticsCalculator::PropertyType> RigEnsembleFractureStatisticsCalculator::propertyTypes()
|
|
{
|
|
std::vector<RigEnsembleFractureStatisticsCalculator::PropertyType> 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<caf::NumberFormatType, int> 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 );
|
|
}
|