mirror of
https://github.com/OPM/ResInsight.git
synced 2026-10-24 22:27:55 -05:00
The visibility mask for 'filter by view' was taken from the view's visible cells, which are limited to the active cells of the view's case. When applying the calculation to additional cases with different ACTNUM, cells inside the filter geometry that are inactive in the view's case were excluded from the aggregation for cases where they are active, giving too low sums. Build the mask from the cell filter geometry (RANGE_FILTERED + RANGE_FILTERED_INACTIVE) instead, so each calculation case contributes its own active cells inside the filters. Property filters in the view no longer affect the calculation. The result filtering now uses the same mask as the input filtering, also when the visibility filter is provided by the caller.
1313 lines
55 KiB
C++
1313 lines
55 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 "RimGridCalculation.h"
|
|
|
|
#include "RiaDefines.h"
|
|
#include "RiaGuiApplication.h"
|
|
#include "RiaLogging.h"
|
|
#include "RiaPorosityModel.h"
|
|
#include "RiaQStringFormatter.h"
|
|
#include "RiaRegressionTestRunner.h"
|
|
#include "RiuMessageDialog.h"
|
|
|
|
#include "RigActiveCellInfo.h"
|
|
#include "RigCaseCellResultsData.h"
|
|
#include "RigEclipseCaseData.h"
|
|
#include "RigEclipseResultAddress.h"
|
|
#include "RigGridManager.h"
|
|
#include "RigMainGrid.h"
|
|
#include "RigResultAccessor.h"
|
|
#include "RigResultAccessorFactory.h"
|
|
#include "RigStatisticsMath.h"
|
|
#include "RigStatisticsTools.h"
|
|
|
|
#include "RimCaseCollection.h"
|
|
#include "RimEclipseCase.h"
|
|
#include "RimEclipseCaseCollection.h"
|
|
#include "RimEclipseCaseEnsemble.h"
|
|
#include "RimEclipseCaseTools.h"
|
|
#include "RimEclipseCellColors.h"
|
|
#include "RimEclipseResultAddress.h"
|
|
#include "RimEclipseStatisticsCase.h"
|
|
#include "RimEclipseView.h"
|
|
#include "RimGridCalculationCollection.h"
|
|
#include "RimGridCalculationVariable.h"
|
|
#include "RimIdenticalGridCaseGroup.h"
|
|
#include "RimOilField.h"
|
|
#include "RimProject.h"
|
|
#include "RimReloadCaseTools.h"
|
|
#include "RimResultSelectionUi.h"
|
|
#include "RimTools.h"
|
|
|
|
#include "expressionparser/ExpressionParser.h"
|
|
|
|
#include "cafPdmUiPropertyViewDialog.h"
|
|
#include "cafPdmUiTreeSelectionEditor.h"
|
|
|
|
#include <QCheckBox>
|
|
#include <QMessageBox>
|
|
|
|
CAF_PDM_SOURCE_INIT( RimGridCalculation, "RimGridCalculation" );
|
|
|
|
namespace caf
|
|
{
|
|
template <>
|
|
void caf::AppEnum<RimGridCalculation::DefaultValueType>::setUp()
|
|
{
|
|
addItem( RimGridCalculation::DefaultValueType::POSITIVE_INFINITY, "POSITIVE_INFINITY", "Infinity" );
|
|
addItem( RimGridCalculation::DefaultValueType::FROM_PROPERTY, "FROM_PROPERTY", "Property Value" );
|
|
addItem( RimGridCalculation::DefaultValueType::USER_DEFINED, "USER_DEFINED", "User Defined Custom Value" );
|
|
setDefault( RimGridCalculation::DefaultValueType::POSITIVE_INFINITY );
|
|
}
|
|
template <>
|
|
void caf::AppEnum<RimGridCalculation::AdditionalCasesType>::setUp()
|
|
{
|
|
addItem( RimGridCalculation::AdditionalCasesType::NONE, "NONE", "None" );
|
|
addItem( RimGridCalculation::AdditionalCasesType::GRID_CASE_GROUP, "GRID_CASE_GROUP", "Case Group" );
|
|
addItem( RimGridCalculation::AdditionalCasesType::ENSEMBLE, "ENSEMBLE", "Ensemble" );
|
|
addItem( RimGridCalculation::AdditionalCasesType::ALL_CASES, "ALL_CASES", "All Cases" );
|
|
setDefault( RimGridCalculation::AdditionalCasesType::NONE );
|
|
}
|
|
}; // namespace caf
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
RimGridCalculation::RimGridCalculation()
|
|
{
|
|
CAF_PDM_InitObject( "RimGridCalculation", ":/octave.png", "Calculation", "" );
|
|
CAF_PDM_InitFieldNoDefault( &m_cellFilterView, "VisibleCellView", "Filter by 3d View Visibility" );
|
|
CAF_PDM_InitFieldNoDefault( &m_defaultValueType, "DefaultValueType", "Non-visible Cell Value" );
|
|
CAF_PDM_InitField( &m_defaultValue, "DefaultValue", 0.0, "Custom Value" );
|
|
CAF_PDM_InitFieldNoDefault( &m_destinationCase, "DestinationCase", "Destination Case" );
|
|
|
|
CAF_PDM_InitField( &m_applyToAllCases_OBSOLETE, "AllDestinationCase", false, "Apply to All Cases" );
|
|
m_applyToAllCases_OBSOLETE.xmlCapability()->setIOWritable( false );
|
|
|
|
CAF_PDM_InitFieldNoDefault( &m_additionalCasesType, "AdditionalCasesType", "Apply To Additional Cases" );
|
|
CAF_PDM_InitFieldNoDefault( &m_additionalCaseGroup, "AdditionalCaseGroup", "Case Group" );
|
|
CAF_PDM_InitFieldNoDefault( &m_additionalEnsemble, "AdditionalEnsemble", "Ensemble" );
|
|
|
|
CAF_PDM_InitFieldNoDefault( &m_nonVisibleResultAddress, "NonVisibleResultAddress", "" );
|
|
m_nonVisibleResultAddress = new RimEclipseResultAddress;
|
|
|
|
CAF_PDM_InitFieldNoDefault( &m_nonVisibleResultText, "NonVisibleResultText", "" );
|
|
m_nonVisibleResultText.registerGetMethod( this, &RimGridCalculation::nonVisibleResultAddressText );
|
|
m_nonVisibleResultText.uiCapability()->setUiReadOnly( true );
|
|
|
|
CAF_PDM_InitFieldNoDefault( &m_selectedTimeSteps, "SelectedTimeSteps", "Time Step Selection" );
|
|
m_selectedTimeSteps.uiCapability()->setUiEditorTypeName( caf::PdmUiTreeSelectionEditor::uiEditorTypeName() );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RimGridCalculation::preCalculate() const
|
|
{
|
|
if ( !RiaRegressionTestRunner::instance()->isRunningRegressionTests() && RiaGuiApplication::isRunning() &&
|
|
m_additionalCasesType() != RimGridCalculation::AdditionalCasesType::NONE )
|
|
{
|
|
const QString cacheKey = "GridCalculatorMessage";
|
|
|
|
auto cacheValue = RiaApplication::instance()->cacheDataObject( cacheKey );
|
|
if ( cacheValue.isValid() && cacheValue.canConvert<bool>() )
|
|
{
|
|
return cacheValue.toBool();
|
|
}
|
|
|
|
QCheckBox* cb = new QCheckBox( "Don't show this question again" );
|
|
|
|
QMessageBox msgbox;
|
|
msgbox.setWindowTitle( "Grid Property Calculator" );
|
|
msgbox.setText( "Calculation will be executed on all grid model cases.\nDo you want to continue?\n" );
|
|
msgbox.setIcon( QMessageBox::Icon::Question );
|
|
msgbox.addButton( QMessageBox::Yes );
|
|
msgbox.addButton( QMessageBox::No );
|
|
msgbox.setDefaultButton( QMessageBox::Yes );
|
|
msgbox.setCheckBox( cb );
|
|
|
|
auto reply = msgbox.exec();
|
|
|
|
bool returnValue = ( reply == QMessageBox::Yes );
|
|
|
|
if ( cb->isChecked() && returnValue )
|
|
{
|
|
RiaApplication::instance()->setCacheDataObject( cacheKey, returnValue );
|
|
}
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
RimGridCalculationVariable* RimGridCalculation::createVariable()
|
|
{
|
|
auto variable = new RimGridCalculationVariable;
|
|
|
|
variable->eclipseResultChanged.connect( this, &RimGridCalculation::onVariableUpdated );
|
|
|
|
return variable;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RimGridCalculation::calculate()
|
|
{
|
|
// Equal grid size is required if there is more than one grid case in the expression. If a cell filter view is active, the visibility is
|
|
// based on one view and reused for all other grid models, and requires equal grid size.
|
|
bool checkIfGridSizeIsEqual = ( !allSourceCasesAreEqualToDestinationCase() || m_cellFilterView != nullptr ) &&
|
|
m_additionalCasesType != AdditionalCasesType::ENSEMBLE;
|
|
|
|
// Grid dimensions are required to validate the calculation, and a source case is not necessarily opened. This is the case for a
|
|
// statistics case with no computed statistics.
|
|
for ( auto inputCase : inputCases() )
|
|
{
|
|
if ( inputCase && !inputCase->eclipseCaseData() ) inputCase->ensureReservoirCaseIsOpen();
|
|
}
|
|
|
|
for ( auto calculationCase : outputEclipseCases() )
|
|
{
|
|
if ( !calculationCase ) continue;
|
|
|
|
if ( !calculationCase->eclipseCaseData() )
|
|
{
|
|
if ( m_additionalCasesType == AdditionalCasesType::ENSEMBLE )
|
|
{
|
|
calculationCase->ensureReservoirCaseIsOpen();
|
|
}
|
|
else
|
|
{
|
|
QString msg = QString( "No data available for case %1, aborting calculation" ).arg( calculationCase->caseUserDescription() );
|
|
RiuMessageDialog::showError( nullptr, "Grid Property Calculator", msg );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ( !calculationCase->eclipseCaseData()->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL ) )
|
|
{
|
|
QString msg =
|
|
QString( "No active cell data available for case %1, aborting calculation" ).arg( calculationCase->caseUserDescription() );
|
|
RiuMessageDialog::showError( nullptr, "Grid Property Calculator", msg );
|
|
return false;
|
|
}
|
|
|
|
if ( checkIfGridSizeIsEqual )
|
|
{
|
|
for ( auto inputCase : inputCases() )
|
|
{
|
|
if ( !calculationCase->isGridSizeEqualTo( inputCase ) )
|
|
{
|
|
QString msg = "Detected IJK mismatch between input cases and destination case. All grid "
|
|
"cases must have identical IJK sizes.";
|
|
RiuMessageDialog::showError( nullptr, "Grid Property Calculator", msg );
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( m_defaultValueType() == DefaultValueType::FROM_PROPERTY )
|
|
{
|
|
bool isDataSourceAvailable = false;
|
|
|
|
if ( m_nonVisibleResultAddress->eclipseCase() )
|
|
{
|
|
auto data = m_nonVisibleResultAddress->eclipseCase()->results( RiaDefines::PorosityModelType::MATRIX_MODEL );
|
|
if ( data && data->hasResultEntry(
|
|
RigEclipseResultAddress( m_nonVisibleResultAddress->resultType(), m_nonVisibleResultAddress->resultName() ) ) )
|
|
{
|
|
isDataSourceAvailable = true;
|
|
}
|
|
}
|
|
|
|
if ( !isDataSourceAvailable )
|
|
{
|
|
QString msg = "No data available for result defined in 'Non-visible Cell Value'";
|
|
|
|
RiuMessageDialog::showError( nullptr, "Grid Property Calculator", msg );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
cvf::ref<cvf::UByteArray> inputValueVisibilityFilter;
|
|
if ( m_cellFilterView() )
|
|
{
|
|
if ( auto eclipseView = dynamic_cast<RimEclipseView*>( m_cellFilterView() ) )
|
|
{
|
|
// Use the cell filter geometry, independent of the active cells in the view's case. Cells inside the
|
|
// filters that are inactive in the view's case can be active in other calculation cases, and must be
|
|
// included when the calculation is applied to additional cases.
|
|
inputValueVisibilityFilter = new cvf::UByteArray;
|
|
eclipseView->calculateCellVisibility( inputValueVisibilityFilter.p(),
|
|
{ RANGE_FILTERED, RANGE_FILTERED_INACTIVE },
|
|
eclipseView->currentTimeStep() );
|
|
}
|
|
else
|
|
{
|
|
inputValueVisibilityFilter = m_cellFilterView()->currentTotalCellVisibility();
|
|
}
|
|
}
|
|
|
|
std::optional<std::vector<size_t>> timeSteps = std::nullopt;
|
|
|
|
if ( !m_selectedTimeSteps().empty() )
|
|
{
|
|
std::vector<size_t> tmp;
|
|
for ( auto t : m_selectedTimeSteps() )
|
|
{
|
|
tmp.push_back( static_cast<size_t>( t ) );
|
|
}
|
|
|
|
timeSteps = tmp;
|
|
}
|
|
|
|
bool evaluateDependentCalculations = true;
|
|
return calculateForCases( outputEclipseCases(), inputValueVisibilityFilter.p(), timeSteps, evaluateDependentCalculations );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
std::vector<RimEclipseCase*> RimGridCalculation::outputEclipseCases() const
|
|
{
|
|
if ( m_additionalCasesType() == RimGridCalculation::AdditionalCasesType::ALL_CASES )
|
|
{
|
|
// Find all Eclipse cases suitable for grid calculations. This includes all single grid cases and source cases in a grid case group.
|
|
// Exclude the statistics cases, as it is not possible to use them in a grid calculations.
|
|
return RimEclipseCaseTools::nativeEclipseGridCases();
|
|
}
|
|
|
|
if ( m_additionalCasesType() == RimGridCalculation::AdditionalCasesType::GRID_CASE_GROUP )
|
|
{
|
|
if ( m_additionalCaseGroup() && m_additionalCaseGroup()->caseCollection() )
|
|
return m_additionalCaseGroup()->caseCollection()->reservoirs.childrenByType();
|
|
}
|
|
|
|
if ( m_additionalCasesType() == RimGridCalculation::AdditionalCasesType::ENSEMBLE )
|
|
{
|
|
if ( m_additionalEnsemble() ) return m_additionalEnsemble()->cases();
|
|
}
|
|
|
|
return { m_destinationCase };
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
std::vector<RimEclipseCase*> RimGridCalculation::inputCases() const
|
|
{
|
|
std::vector<RimEclipseCase*> cases;
|
|
|
|
for ( const auto& variable : m_variables )
|
|
{
|
|
auto* v = dynamic_cast<RimGridCalculationVariable*>( variable.p() );
|
|
|
|
if ( v->eclipseCase() ) cases.push_back( v->eclipseCase() );
|
|
}
|
|
|
|
return cases;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
RimGridCalculation::DefaultValueConfig RimGridCalculation::defaultValueConfiguration() const
|
|
{
|
|
if ( m_defaultValueType() == RimGridCalculation::DefaultValueType::USER_DEFINED )
|
|
return std::make_pair( m_defaultValueType(), m_defaultValue() );
|
|
|
|
return std::make_pair( m_defaultValueType(), HUGE_VAL );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
QString RimGridCalculation::nonVisibleResultAddressText() const
|
|
{
|
|
QString txt;
|
|
|
|
if ( m_nonVisibleResultAddress->eclipseCase() ) txt += m_nonVisibleResultAddress->eclipseCase()->caseUserDescription() + " : ";
|
|
|
|
txt += m_nonVisibleResultAddress->resultName();
|
|
|
|
return txt;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RimGridCalculation::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering )
|
|
{
|
|
RimUserDefinedCalculation::defineUiOrdering( uiConfigName, uiOrdering );
|
|
|
|
uiOrdering.add( &m_destinationCase );
|
|
|
|
uiOrdering.add( &m_additionalCasesType );
|
|
uiOrdering.add( &m_additionalCaseGroup );
|
|
m_additionalCaseGroup.uiCapability()->setUiHidden( m_additionalCasesType() != RimGridCalculation::AdditionalCasesType::GRID_CASE_GROUP );
|
|
|
|
uiOrdering.add( &m_additionalEnsemble );
|
|
m_additionalEnsemble.uiCapability()->setUiHidden( m_additionalCasesType() != RimGridCalculation::AdditionalCasesType::ENSEMBLE );
|
|
|
|
caf::PdmUiGroup* filterGroup = uiOrdering.addNewGroup( "Cell Filter" );
|
|
filterGroup->setCollapsedByDefault();
|
|
filterGroup->add( &m_cellFilterView );
|
|
|
|
if ( m_cellFilterView() != nullptr )
|
|
{
|
|
filterGroup->add( &m_defaultValueType );
|
|
|
|
if ( m_defaultValueType() == RimGridCalculation::DefaultValueType::FROM_PROPERTY )
|
|
{
|
|
filterGroup->add( &m_nonVisibleResultText );
|
|
filterGroup->addNewButton( "Edit", [this]() { onEditNonVisibleResultAddressButtonPressed(); }, { .newRow = false } );
|
|
}
|
|
else if ( m_defaultValueType() == RimGridCalculation::DefaultValueType::USER_DEFINED )
|
|
filterGroup->add( &m_defaultValue );
|
|
}
|
|
|
|
caf::PdmUiGroup* timeStepGroup = uiOrdering.addNewGroup( "Time Step Filter" );
|
|
timeStepGroup->setCollapsedByDefault();
|
|
timeStepGroup->add( &m_selectedTimeSteps );
|
|
|
|
uiOrdering.skipRemainingFields();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
QList<caf::PdmOptionItemInfo> RimGridCalculation::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions )
|
|
{
|
|
QList<caf::PdmOptionItemInfo> options;
|
|
|
|
if ( fieldNeedingOptions == &m_cellFilterView )
|
|
{
|
|
options.push_back( caf::PdmOptionItemInfo( "Disabled", nullptr ) );
|
|
|
|
RimEclipseCase* firstEclipseCase = nullptr;
|
|
if ( !inputCases().empty() )
|
|
{
|
|
firstEclipseCase = inputCases().front();
|
|
}
|
|
else
|
|
{
|
|
// If no input cases are defined, use the destination case to determine the grid size. This will enable use of expressions
|
|
// with no input cases like "calculation := 1.0"
|
|
firstEclipseCase = m_destinationCase();
|
|
}
|
|
|
|
if ( firstEclipseCase )
|
|
{
|
|
std::vector<Rim3dView*> views = RimProject::current()->allViews();
|
|
for ( auto* view : views )
|
|
{
|
|
auto eclipseView = dynamic_cast<RimEclipseView*>( view );
|
|
if ( !eclipseView ) continue;
|
|
if ( !firstEclipseCase->isGridSizeEqualTo( eclipseView->eclipseCase() ) ) continue;
|
|
|
|
options.push_back( caf::PdmOptionItemInfo( view->autoName(), view, false, view->uiIconProvider() ) );
|
|
}
|
|
}
|
|
}
|
|
else if ( fieldNeedingOptions == &m_destinationCase )
|
|
{
|
|
RimEclipseCase* firstInputCase = nullptr;
|
|
|
|
if ( !inputCases().empty() )
|
|
{
|
|
firstInputCase = inputCases()[0];
|
|
}
|
|
|
|
for ( auto eclipseCase : RimEclipseCaseTools::nativeEclipseGridCases() )
|
|
{
|
|
if ( !eclipseCase ) continue;
|
|
if ( firstInputCase && !firstInputCase->isGridSizeEqualTo( eclipseCase ) ) continue;
|
|
|
|
options.push_back( caf::PdmOptionItemInfo( eclipseCase->caseUserDescription(), eclipseCase, false, eclipseCase->uiIconProvider() ) );
|
|
}
|
|
|
|
options.push_front( caf::PdmOptionItemInfo( "None", nullptr ) );
|
|
}
|
|
else if ( &m_selectedTimeSteps == fieldNeedingOptions )
|
|
{
|
|
RimEclipseCase* firstEclipseCase = nullptr;
|
|
if ( !inputCases().empty() ) firstEclipseCase = inputCases().front();
|
|
|
|
if ( firstEclipseCase )
|
|
{
|
|
const auto timeStepStrings = firstEclipseCase->timeStepStrings();
|
|
|
|
int index = 0;
|
|
for ( const auto& text : timeStepStrings )
|
|
{
|
|
options.push_back( caf::PdmOptionItemInfo( text, index++ ) );
|
|
}
|
|
}
|
|
}
|
|
else if ( &m_additionalCaseGroup == fieldNeedingOptions )
|
|
{
|
|
options.push_back( caf::PdmOptionItemInfo( "None", nullptr ) );
|
|
|
|
RimProject* proj = RimProject::current();
|
|
if ( proj->activeOilField() && proj->activeOilField()->analysisModels() )
|
|
{
|
|
auto analysisModels = proj->activeOilField()->analysisModels();
|
|
for ( RimIdenticalGridCaseGroup* cg : analysisModels->caseGroups() )
|
|
{
|
|
options.push_back( caf::PdmOptionItemInfo( cg->name(), cg, false, cg->uiIconProvider() ) );
|
|
}
|
|
}
|
|
}
|
|
else if ( &m_additionalEnsemble == fieldNeedingOptions )
|
|
{
|
|
options.push_back( caf::PdmOptionItemInfo( "None", nullptr ) );
|
|
|
|
RimProject* proj = RimProject::current();
|
|
if ( proj->activeOilField() && proj->activeOilField()->analysisModels() )
|
|
{
|
|
auto analysisModels = proj->activeOilField()->analysisModels();
|
|
for ( RimEclipseCaseEnsemble* e : analysisModels->caseEnsembles() )
|
|
{
|
|
options.push_back( caf::PdmOptionItemInfo( e->name(), e, false, e->uiIconProvider() ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
return options;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RimGridCalculation::initAfterRead()
|
|
{
|
|
for ( auto& variable : m_variables )
|
|
{
|
|
auto gridVar = dynamic_cast<RimGridCalculationVariable*>( variable.p() );
|
|
if ( gridVar )
|
|
{
|
|
gridVar->eclipseResultChanged.connect( this, &RimGridCalculation::onVariableUpdated );
|
|
|
|
if ( m_destinationCase == nullptr ) m_destinationCase = gridVar->eclipseCase();
|
|
}
|
|
}
|
|
|
|
if ( m_applyToAllCases_OBSOLETE ) m_additionalCasesType = RimGridCalculation::AdditionalCasesType::ALL_CASES;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RimGridCalculation::onEditNonVisibleResultAddressButtonPressed()
|
|
{
|
|
auto eclipseCase = m_nonVisibleResultAddress->eclipseCase();
|
|
if ( !eclipseCase ) eclipseCase = m_destinationCase;
|
|
|
|
RimResultSelectionUi selectionUi;
|
|
selectionUi.setEclipseResultAddress( eclipseCase, m_nonVisibleResultAddress->resultType(), m_nonVisibleResultAddress->resultName() );
|
|
|
|
caf::PdmUiPropertyViewDialog propertyDialog( nullptr, &selectionUi, "Select Result", "" );
|
|
if ( propertyDialog.exec() == QDialog::Accepted )
|
|
{
|
|
m_nonVisibleResultAddress->setEclipseCase( selectionUi.eclipseCase() );
|
|
m_nonVisibleResultAddress->setResultType( selectionUi.resultType() );
|
|
m_nonVisibleResultAddress->setResultName( selectionUi.resultVariable() );
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RimGridCalculation::onVariableUpdated( const SignalEmitter* emitter )
|
|
{
|
|
if ( auto variable = dynamic_cast<const RimGridCalculationVariable*>( emitter ) )
|
|
{
|
|
if ( auto variableCase = variable->eclipseCase() )
|
|
{
|
|
// A statistics case can be used as source, but never as destination. A calculated result stored in a statistics case is
|
|
// interpreted as computed statistics, and will block recomputation of the statistics.
|
|
const bool canBeDestinationCase = dynamic_cast<RimEclipseStatisticsCase*>( variableCase ) == nullptr;
|
|
|
|
if ( canBeDestinationCase && ( !m_destinationCase || !m_destinationCase->isGridSizeEqualTo( variableCase ) ) )
|
|
{
|
|
m_destinationCase = variableCase;
|
|
}
|
|
}
|
|
}
|
|
|
|
updateConnectedEditors();
|
|
|
|
variableUpdated.send();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RimGridCalculation::allSourceCasesAreEqualToDestinationCase() const
|
|
{
|
|
if ( m_destinationCase() == nullptr ) return false;
|
|
|
|
for ( const auto& variable : m_variables )
|
|
{
|
|
auto gridVar = dynamic_cast<RimGridCalculationVariable*>( variable.p() );
|
|
if ( gridVar )
|
|
{
|
|
if ( gridVar->eclipseCase() != m_destinationCase() ) return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
RigEclipseResultAddress RimGridCalculation::outputAddress() const
|
|
{
|
|
QString leftHandSideVariableName = RimGridCalculation::findLeftHandSide( m_expression );
|
|
RigEclipseResultAddress resAddr( RiaDefines::ResultCatType::GENERATED, leftHandSideVariableName );
|
|
return resAddr;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
std::vector<double> RimGridCalculation::getActiveCellValuesForVariable( RimGridCalculationVariable* variable,
|
|
size_t tsId,
|
|
RiaDefines::PorosityModelType porosityModel,
|
|
RimEclipseCase* sourceCase,
|
|
RimEclipseCase* destinationCase ) const
|
|
{
|
|
if ( !sourceCase || !destinationCase ) return {};
|
|
|
|
int timeStep = variable->timeStep();
|
|
auto resultCategoryType = variable->resultCategoryType();
|
|
|
|
// General case is to use the data from the given time step
|
|
size_t timeStepToUse = tsId;
|
|
|
|
if ( resultCategoryType == RiaDefines::ResultCatType::STATIC_NATIVE )
|
|
{
|
|
// Use the first time step for static data for all time steps
|
|
timeStepToUse = 0;
|
|
}
|
|
else if ( timeStep != RimGridCalculationVariable::allTimeStepsValue() )
|
|
{
|
|
// Use data from a specific time step for this variable for all result time steps
|
|
timeStepToUse = timeStep;
|
|
}
|
|
|
|
return getActiveCellValues( variable->resultVariable(), resultCategoryType, timeStepToUse, porosityModel, sourceCase, destinationCase );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
/// Return values for active cells, both for dynamic and static results. Use the active cell info from the destination case, and read data
|
|
/// from the source case.
|
|
//--------------------------------------------------------------------------------------------------
|
|
std::vector<double> RimGridCalculation::getActiveCellValues( const QString& resultName,
|
|
const RiaDefines::ResultCatType resultCategoryType,
|
|
size_t tsId,
|
|
RiaDefines::PorosityModelType porosityModel,
|
|
RimEclipseCase* sourceCase,
|
|
RimEclipseCase* destinationCase ) const
|
|
{
|
|
if ( !sourceCase || !destinationCase ) return {};
|
|
|
|
// A statistics case is not necessarily opened when used as source for a calculation
|
|
if ( !sourceCase->eclipseCaseData() && !sourceCase->ensureReservoirCaseIsOpen() ) return {};
|
|
|
|
size_t timeStepToUse = tsId;
|
|
if ( resultCategoryType == RiaDefines::ResultCatType::STATIC_NATIVE )
|
|
{
|
|
// Use the first time step for static data for all time steps
|
|
timeStepToUse = 0;
|
|
}
|
|
|
|
RigEclipseResultAddress resAddr( resultCategoryType, resultName );
|
|
|
|
auto eclipseCaseData = sourceCase->eclipseCaseData();
|
|
if ( !eclipseCaseData ) return {};
|
|
|
|
auto rigCaseCellResultsData = eclipseCaseData->results( porosityModel );
|
|
if ( rigCaseCellResultsData->findOrLoadKnownScalarResultForTimeStep( resAddr, timeStepToUse ) == cvf::UNDEFINED_SIZE_T ) return {};
|
|
|
|
// Active cell info must always be retrieved from the destination case, as the returned vector must be of the same size as
|
|
// number of active cells in the destination case. Active cells can be different between source and destination case.
|
|
auto activeCellInfoDestination = destinationCase->eclipseCaseData()->activeCellInfo( porosityModel );
|
|
if ( !activeCellInfoDestination ) return {};
|
|
|
|
auto activeReservoirCells = activeCellInfoDestination->activeReservoirCellIndices();
|
|
if ( activeReservoirCells.empty() ) return {};
|
|
|
|
std::vector<double> values( activeCellInfoDestination->activeReservoirCellIndices().size() );
|
|
|
|
size_t gridIndex = 0;
|
|
auto resultAccessor =
|
|
RigResultAccessorFactory::createFromResultAddress( eclipseCaseData, gridIndex, porosityModel, timeStepToUse, resAddr );
|
|
if ( resultAccessor.isNull() ) return {};
|
|
|
|
#pragma omp parallel for
|
|
for ( int i = 0; i < static_cast<int>( activeReservoirCells.size() ); i++ )
|
|
{
|
|
values[i] = resultAccessor->cellScalarGlobIdx( activeReservoirCells[i].value() );
|
|
}
|
|
|
|
if ( m_releaseMemoryAfterDataIsExtracted )
|
|
{
|
|
auto categoriesToExclude = { RiaDefines::ResultCatType::GENERATED };
|
|
|
|
sourceCase->results( RiaDefines::PorosityModelType::MATRIX_MODEL )->freeAllocatedResultsData( categoriesToExclude, timeStepToUse );
|
|
sourceCase->results( RiaDefines::PorosityModelType::FRACTURE_MODEL )->freeAllocatedResultsData( categoriesToExclude, timeStepToUse );
|
|
}
|
|
|
|
return values;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RimGridCalculation::replaceFilteredValuesWithVector( const std::vector<double>& inputValues,
|
|
cvf::ref<cvf::UByteArray> visibility,
|
|
std::vector<double>& resultValues,
|
|
RigActiveCellInfo* activeCellInfo )
|
|
|
|
{
|
|
auto activeReservoirCellIndices = activeCellInfo->activeReservoirCellIndices();
|
|
int numActiveCells = static_cast<int>( activeReservoirCellIndices.size() );
|
|
|
|
CAF_ASSERT( numActiveCells == (int)resultValues.size() );
|
|
CAF_ASSERT( numActiveCells == (int)inputValues.size() );
|
|
|
|
#pragma omp parallel for
|
|
for ( int i = 0; i < numActiveCells; i++ )
|
|
{
|
|
const auto reservoirCellIndex = activeReservoirCellIndices[i];
|
|
if ( !visibility->val( reservoirCellIndex.value() ) )
|
|
{
|
|
resultValues[i] = inputValues[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
/// Replace values for non-visible cells with the given default value. Returns the number of replaced
|
|
/// values.
|
|
//--------------------------------------------------------------------------------------------------
|
|
size_t RimGridCalculation::replaceFilteredValuesWithDefaultValue( double defaultValue,
|
|
cvf::ref<cvf::UByteArray> visibility,
|
|
std::vector<double>& resultValues,
|
|
RigActiveCellInfo* activeCellInfo )
|
|
|
|
{
|
|
auto activeReservoirCellIndices = activeCellInfo->activeReservoirCellIndices();
|
|
int numActiveCells = static_cast<int>( activeReservoirCellIndices.size() );
|
|
|
|
CAF_ASSERT( numActiveCells == (int)resultValues.size() );
|
|
|
|
size_t replacedCount = 0;
|
|
|
|
#pragma omp parallel for reduction( + : replacedCount )
|
|
for ( int i = 0; i < numActiveCells; i++ )
|
|
{
|
|
const auto reservoirCellIndex = activeReservoirCellIndices[i];
|
|
if ( !visibility->val( reservoirCellIndex.value() ) )
|
|
{
|
|
resultValues[i] = defaultValue;
|
|
replacedCount++;
|
|
}
|
|
}
|
|
|
|
return replacedCount;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
/// Replace undefined values (infinity and NaN) with the given default value. Returns the number of
|
|
/// replaced values.
|
|
//--------------------------------------------------------------------------------------------------
|
|
size_t RimGridCalculation::replaceInvalidValuesWithDefaultValue( double defaultValue, std::vector<double>& values )
|
|
{
|
|
size_t replacedCount = 0;
|
|
|
|
#pragma omp parallel for reduction( + : replacedCount )
|
|
for ( int i = 0; i < static_cast<int>( values.size() ); i++ )
|
|
{
|
|
if ( RigStatisticsTools::isInvalidNumber( values[i] ) )
|
|
{
|
|
values[i] = defaultValue;
|
|
replacedCount++;
|
|
}
|
|
}
|
|
|
|
return replacedCount;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RimGridCalculation::filterResults( cvf::UByteArray* visibility,
|
|
const std::vector<std::vector<double>>& values,
|
|
size_t timeStep,
|
|
RimGridCalculation::DefaultValueType defaultValueType,
|
|
double defaultValue,
|
|
std::vector<double>& resultValues,
|
|
RiaDefines::PorosityModelType porosityModel,
|
|
RimEclipseCase* outputEclipseCase ) const
|
|
|
|
{
|
|
auto activeCellInfo = outputEclipseCase->eclipseCaseData()->activeCellInfo( porosityModel );
|
|
|
|
if ( defaultValueType == RimGridCalculation::DefaultValueType::FROM_PROPERTY )
|
|
{
|
|
auto nonVisibleValues = getActiveCellValues( m_nonVisibleResultAddress->resultName(),
|
|
m_nonVisibleResultAddress->resultType(),
|
|
timeStep,
|
|
porosityModel,
|
|
outputEclipseCase,
|
|
outputEclipseCase );
|
|
|
|
if ( !nonVisibleValues.empty() )
|
|
{
|
|
replaceFilteredValuesWithVector( nonVisibleValues, visibility, resultValues, activeCellInfo );
|
|
}
|
|
else
|
|
{
|
|
QString errorMessage =
|
|
"Grid Property Calculator: Invalid input data for default result property, no data assigned to non-visible cells.";
|
|
RiaLogging::error( errorMessage.toStdString() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
double valueToUse = defaultValue;
|
|
if ( defaultValueType == RimGridCalculation::DefaultValueType::POSITIVE_INFINITY ) valueToUse = HUGE_VAL;
|
|
|
|
replaceFilteredValuesWithDefaultValue( valueToUse, visibility, resultValues, activeCellInfo );
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RimGridCalculation::updateDependentObjects()
|
|
{
|
|
for ( RimEclipseCase* eclipseCase : outputEclipseCases() )
|
|
{
|
|
if ( eclipseCase )
|
|
{
|
|
RimReloadCaseTools::updateAll3dViews( eclipseCase );
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RimGridCalculation::removeDependentObjects()
|
|
{
|
|
QString leftHandSideVariableName = RimGridCalculation::findLeftHandSide( m_expression );
|
|
|
|
auto porosityModel = RiaDefines::PorosityModelType::MATRIX_MODEL;
|
|
|
|
RigEclipseResultAddress resAddr( RiaDefines::ResultCatType::GENERATED, leftHandSideVariableName );
|
|
|
|
for ( RimEclipseCase* eclipseCase : outputEclipseCases() )
|
|
{
|
|
if ( eclipseCase )
|
|
{
|
|
// Select "None" result if the result that is being removed were displayed in a view.
|
|
for ( auto v : eclipseCase->reservoirViews() )
|
|
{
|
|
if ( v->cellResult()->resultType() == resAddr.resultCatType() && v->cellResult()->resultVariable() == resAddr.resultName() )
|
|
{
|
|
v->cellResult()->setResultType( RiaDefines::ResultCatType::GENERATED );
|
|
v->cellResult()->setResultVariable( "None" );
|
|
}
|
|
}
|
|
|
|
eclipseCase->results( porosityModel )->clearScalarResult( resAddr );
|
|
eclipseCase->results( porosityModel )->setRemovedTagOnGeneratedResult( resAddr );
|
|
|
|
RimReloadCaseTools::updateAll3dViews( eclipseCase );
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
bool RimGridCalculation::calculateForCases( const std::vector<RimEclipseCase*>& calculationCases,
|
|
cvf::UByteArray* inputValueVisibilityFilter,
|
|
std::optional<std::vector<size_t>> timeSteps,
|
|
bool evaluateDependentCalculations )
|
|
{
|
|
if ( calculationCases.empty() ) return true;
|
|
|
|
if ( evaluateDependentCalculations ) findAndEvaluateDependentCalculations( calculationCases, inputValueVisibilityFilter, timeSteps );
|
|
|
|
QString leftHandSideVariableName = RimGridCalculation::findLeftHandSide( m_expression );
|
|
|
|
auto [isOk, errorMessage] = validateVariables();
|
|
if ( !isOk )
|
|
{
|
|
RiuMessageDialog::showError( nullptr, "Grid Property Calculator", errorMessage );
|
|
return false;
|
|
}
|
|
|
|
const bool isMultipleCasesPresent = calculationCases.size() > 1;
|
|
const bool hasAggregationExpression = m_expression().contains( "sum" ) || m_expression().contains( "avg" ) ||
|
|
m_expression().contains( "min" ) || m_expression().contains( "max" ) ||
|
|
m_expression().contains( "count" );
|
|
|
|
// If multiple cases are present, release memory after data is extracted to avoid memory issues.
|
|
m_releaseMemoryAfterDataIsExtracted = isMultipleCasesPresent;
|
|
|
|
if ( isMultipleCasesPresent )
|
|
{
|
|
QString txt = "Starting calculation '" + description() + "' for " + QString::number( calculationCases.size() ) + " cases.";
|
|
RiaLogging::info( txt.toStdString() );
|
|
|
|
if ( hasAggregationExpression )
|
|
{
|
|
RiaLogging::info( std::format( " Detected aggregated value in expression '{}'.", m_expression() ) );
|
|
RiaLogging::info( " Aggregated value per realization is displayed in one column per time step." );
|
|
}
|
|
}
|
|
|
|
std::vector<std::vector<double>> allAggregatedValues;
|
|
|
|
bool anyErrorsDetected = false;
|
|
for ( RimEclipseCase* calculationCase : calculationCases )
|
|
{
|
|
if ( !calculationCase )
|
|
{
|
|
RiuMessageDialog::showError( nullptr,
|
|
"Grid Property Calculator",
|
|
QString( "No case found for calculation : %1" ).arg( leftHandSideVariableName ) );
|
|
anyErrorsDetected = true;
|
|
continue;
|
|
}
|
|
|
|
auto porosityModel = RiaDefines::PorosityModelType::MATRIX_MODEL;
|
|
|
|
RigEclipseResultAddress resAddr( RiaDefines::ResultCatType::GENERATED, leftHandSideVariableName );
|
|
|
|
if ( !calculationCase->results( porosityModel )->ensureKnownResultLoaded( resAddr ) )
|
|
{
|
|
bool needsToBeStored = false;
|
|
calculationCase->results( porosityModel )->createResultEntry( resAddr, needsToBeStored );
|
|
}
|
|
|
|
calculationCase->results( porosityModel )->clearScalarResult( resAddr );
|
|
|
|
// If an input grid is present, max time step count is zero. Make sure the time step count for the calculation is
|
|
// always 1 or more.
|
|
const size_t timeStepCount = std::max( size_t( 1 ), calculationCase->results( porosityModel )->maxTimeStepCount() );
|
|
|
|
if ( calculationCase == calculationCases.front() && hasAggregationExpression )
|
|
{
|
|
// Print time header
|
|
|
|
auto timeStepStrings = calculationCase->timeStepStrings();
|
|
|
|
QString timeHeader;
|
|
for ( size_t tsId = 0; tsId < timeStepCount; tsId++ )
|
|
{
|
|
// Skip time steps that are not in the list of time steps to calculate
|
|
if ( timeSteps && std::find( timeSteps->begin(), timeSteps->end(), tsId ) == timeSteps->end() ) continue;
|
|
|
|
timeHeader += "\t";
|
|
auto index = static_cast<int>( tsId );
|
|
|
|
if ( index < timeStepStrings.size() )
|
|
{
|
|
timeHeader += timeStepStrings.at( static_cast<int>( tsId ) );
|
|
}
|
|
else
|
|
timeHeader += "Undefined";
|
|
}
|
|
RiaLogging::info( timeHeader.toStdString() );
|
|
}
|
|
|
|
std::vector<std::vector<double>>* scalarResultFrames =
|
|
calculationCase->results( porosityModel )->modifiableCellScalarResultTimesteps( resAddr );
|
|
scalarResultFrames->resize( timeStepCount );
|
|
|
|
std::vector<double> aggregatedValuesOneTimeStep;
|
|
std::vector<size_t> invalidValueCountPerVariable( m_variables.size(), 0 );
|
|
|
|
for ( size_t tsId = 0; tsId < timeStepCount; tsId++ )
|
|
{
|
|
// Skip time steps that are not in the list of time steps to calculate
|
|
if ( timeSteps && std::find( timeSteps->begin(), timeSteps->end(), tsId ) == timeSteps->end() ) continue;
|
|
|
|
std::vector<std::vector<double>> dataForAllVariables;
|
|
for ( size_t i = 0; i < m_variables.size(); i++ )
|
|
{
|
|
RimGridCalculationVariable* v = dynamic_cast<RimGridCalculationVariable*>( m_variables[i] );
|
|
CAF_ASSERT( v != nullptr );
|
|
|
|
bool useDataFromSourceCase = ( v->eclipseCase() == m_destinationCase ) ||
|
|
m_additionalCasesType == AdditionalCasesType::ENSEMBLE;
|
|
auto sourceCase = useDataFromSourceCase ? calculationCase : v->eclipseCase();
|
|
|
|
auto dataForVariable = getActiveCellValuesForVariable( v, tsId, porosityModel, sourceCase, calculationCase );
|
|
if ( dataForVariable.empty() )
|
|
{
|
|
RiaLogging::error( std::format( " No data found for variable '{}'.", v->name() ) );
|
|
}
|
|
else if ( hasAggregationExpression )
|
|
{
|
|
const double defaultValue = 0.0;
|
|
size_t nonVisibleCount = 0;
|
|
if ( inputValueVisibilityFilter )
|
|
{
|
|
auto activeCellInfo = calculationCase->eclipseCaseData()->activeCellInfo( porosityModel );
|
|
nonVisibleCount =
|
|
replaceFilteredValuesWithDefaultValue( defaultValue, inputValueVisibilityFilter, dataForVariable, activeCellInfo );
|
|
}
|
|
|
|
// Aggregation functions include all values in the vector. Replace undefined values with the
|
|
// default value to avoid contaminating aggregated values like sum() with infinity. Undefined
|
|
// values are ignored by other statistics computations, like the histogram in the 3d view.
|
|
const size_t invalidCount = replaceInvalidValuesWithDefaultValue( defaultValue, dataForVariable );
|
|
invalidValueCountPerVariable[i] += invalidCount;
|
|
|
|
RiaLogging::debug( std::format( " Case '{}', time step {}: variable '{}': {} of {} values used in aggregation "
|
|
"({} non-visible and {} undefined values replaced with 0.0)",
|
|
calculationCase->caseUserDescription().toStdString(),
|
|
tsId,
|
|
v->name().toStdString(),
|
|
dataForVariable.size() - nonVisibleCount - invalidCount,
|
|
dataForVariable.size(),
|
|
nonVisibleCount,
|
|
invalidCount ) );
|
|
}
|
|
|
|
dataForAllVariables.push_back( dataForVariable );
|
|
}
|
|
|
|
ExpressionParser parser;
|
|
for ( size_t i = 0; i < m_variables.size(); i++ )
|
|
{
|
|
RimGridCalculationVariable* v = dynamic_cast<RimGridCalculationVariable*>( m_variables[i] );
|
|
CAF_ASSERT( v != nullptr );
|
|
parser.assignVector( v->name(), dataForAllVariables[i] );
|
|
}
|
|
|
|
std::vector<double> resultValues;
|
|
if ( calculationCase && calculationCase->eclipseCaseData() )
|
|
{
|
|
// Find number of active cells in the destination case.
|
|
auto activeCellInfoDestination = calculationCase->eclipseCaseData()->activeCellInfo( porosityModel );
|
|
if ( activeCellInfoDestination )
|
|
{
|
|
resultValues.resize( activeCellInfoDestination->reservoirActiveCellCount() );
|
|
}
|
|
}
|
|
parser.assignVector( leftHandSideVariableName, resultValues );
|
|
|
|
auto evaluateResult = parser.expandIfStatementsAndEvaluate( m_expression );
|
|
|
|
if ( evaluateResult )
|
|
{
|
|
if ( hasAggregationExpression )
|
|
{
|
|
auto it =
|
|
std::find_if( resultValues.begin(), resultValues.end(), []( double v ) { return ( !std::isnan( v ) && v != 0.0 ); } );
|
|
if ( it != resultValues.end() )
|
|
{
|
|
aggregatedValuesOneTimeStep.push_back( *it );
|
|
}
|
|
else
|
|
{
|
|
aggregatedValuesOneTimeStep.push_back( 0.0 );
|
|
}
|
|
}
|
|
|
|
if ( inputValueVisibilityFilter && !resultValues.empty() )
|
|
{
|
|
filterResults( inputValueVisibilityFilter,
|
|
dataForAllVariables,
|
|
tsId,
|
|
m_defaultValueType(),
|
|
m_defaultValue(),
|
|
resultValues,
|
|
porosityModel,
|
|
calculationCase );
|
|
}
|
|
|
|
scalarResultFrames->at( tsId ) = resultValues;
|
|
|
|
m_isDirty = false;
|
|
}
|
|
else
|
|
{
|
|
QString s = "The following error message was received from the parser library : \n\n";
|
|
s += evaluateResult.error();
|
|
|
|
RiuMessageDialog::showError( nullptr, "Grid Property Calculator", s );
|
|
return false;
|
|
}
|
|
|
|
calculationCase->updateResultAddressCollection();
|
|
}
|
|
|
|
for ( size_t i = 0; i < m_variables.size(); i++ )
|
|
{
|
|
if ( invalidValueCountPerVariable[i] > 0 )
|
|
{
|
|
RiaLogging::warning( std::format( " Variable '{}': {} undefined input values were replaced with 0.0 for case '{}'. The "
|
|
"input result may be missing data for some cells or time steps.",
|
|
m_variables[i]->name().toStdString(),
|
|
invalidValueCountPerVariable[i],
|
|
calculationCase->caseUserDescription().toStdString() ) );
|
|
}
|
|
}
|
|
|
|
if ( hasAggregationExpression )
|
|
{
|
|
QString txt = " " + calculationCase->caseUserDescription();
|
|
|
|
for ( auto v : aggregatedValuesOneTimeStep )
|
|
{
|
|
txt += "\t" + QString::number( v );
|
|
}
|
|
|
|
allAggregatedValues.push_back( aggregatedValuesOneTimeStep );
|
|
|
|
RiaLogging::info( txt.toStdString() );
|
|
}
|
|
}
|
|
|
|
if ( isMultipleCasesPresent )
|
|
{
|
|
auto [anyError, statisticsText] = createStatisticsText( allAggregatedValues );
|
|
if ( anyError )
|
|
{
|
|
anyErrorsDetected = true;
|
|
}
|
|
else if ( !statisticsText.empty() )
|
|
{
|
|
RiaLogging::info( " Statistics" );
|
|
for ( const auto& txt : statisticsText )
|
|
{
|
|
RiaLogging::info( txt.toStdString() );
|
|
}
|
|
}
|
|
|
|
QString txt = "Completed calculation '" + description() + "' for " + QString::number( calculationCases.size() ) + " cases.";
|
|
RiaLogging::info( txt.toStdString() );
|
|
}
|
|
|
|
return !anyErrorsDetected;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RimGridCalculation::findAndEvaluateDependentCalculations( const std::vector<RimEclipseCase*>& calculationCases,
|
|
cvf::UByteArray* inputValueVisibilityFilter,
|
|
std::optional<std::vector<size_t>> timeSteps )
|
|
{
|
|
auto proj = RimProject::current();
|
|
auto calcColl = proj->gridCalculationCollection();
|
|
|
|
auto dependentCalculations = calcColl->dependentCalculations( this );
|
|
for ( auto dependentCalc : dependentCalculations )
|
|
{
|
|
if ( dependentCalc == this ) continue;
|
|
|
|
// Propagate the settings for this calculation to the dependent calculation. This will allow changes on top level
|
|
// calculation to be propagated to dependent calculations automatically. Do not trigger
|
|
// findAndEvaluateDependentCalculations() recursively, as all dependent calculations are traversed in this function.
|
|
|
|
bool evaluateDependentCalculations = false;
|
|
dependentCalc->calculateForCases( calculationCases, inputValueVisibilityFilter, timeSteps, evaluateDependentCalculations );
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RimGridCalculation::assignEclipseCaseForNullPointers( RimEclipseCase* eclipseCase )
|
|
{
|
|
if ( m_destinationCase() == nullptr )
|
|
{
|
|
m_destinationCase = eclipseCase;
|
|
}
|
|
|
|
for ( auto v : m_variables )
|
|
{
|
|
if ( auto gridVar = dynamic_cast<RimGridCalculationVariable*>( v.p() ) )
|
|
{
|
|
if ( gridVar->eclipseCase() == nullptr )
|
|
{
|
|
gridVar->setEclipseCase( eclipseCase );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
std::pair<bool, QString> RimGridCalculation::validateVariables()
|
|
{
|
|
auto porosityModel = RiaDefines::PorosityModelType::MATRIX_MODEL;
|
|
|
|
for ( size_t i = 0; i < m_variables.size(); i++ )
|
|
{
|
|
RimGridCalculationVariable* v = dynamic_cast<RimGridCalculationVariable*>( m_variables[i] );
|
|
CAF_ASSERT( v != nullptr );
|
|
|
|
if ( !v->eclipseCase() )
|
|
{
|
|
QString errorMessage = QString( "No case defined for variable : %1" ).arg( v->name() );
|
|
return std::make_pair( false, errorMessage );
|
|
}
|
|
|
|
if ( v->resultVariable().isEmpty() )
|
|
{
|
|
QString errorMessage = QString( "No result variable defined for variable : %1" ).arg( v->name() );
|
|
return std::make_pair( false, errorMessage );
|
|
}
|
|
|
|
auto resultCategoryType = v->resultCategoryType();
|
|
RigEclipseResultAddress resAddr( resultCategoryType, v->resultVariable() );
|
|
if ( !v->eclipseCase()->results( porosityModel )->ensureKnownResultLoaded( resAddr ) )
|
|
{
|
|
QString errorMessage = QString( "Unable to load result for variable : %1" ).arg( v->name() );
|
|
return std::make_pair( false, errorMessage );
|
|
}
|
|
}
|
|
|
|
return std::make_pair( true, "" );
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
void RimGridCalculation::onChildrenUpdated( caf::PdmChildArrayFieldHandle* childArray, std::vector<caf::PdmObjectHandle*>& updatedObjects )
|
|
{
|
|
if ( childArray == &m_variables )
|
|
{
|
|
// Update the editors of all the variables if a variable changes.
|
|
// This makes the read-only state of the filter parameters consistent:
|
|
// only one filter is allowed at a time.
|
|
for ( auto v : m_variables )
|
|
{
|
|
v->updateConnectedEditors();
|
|
}
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
///
|
|
//--------------------------------------------------------------------------------------------------
|
|
std::pair<bool, QStringList> RimGridCalculation::createStatisticsText( const std::vector<std::vector<double>>& values )
|
|
{
|
|
bool anyErrorsDetected = false;
|
|
QStringList statisticsText;
|
|
|
|
if ( values.empty() ) return { anyErrorsDetected, statisticsText };
|
|
|
|
size_t size = values.front().size();
|
|
for ( const auto& aggregatedVector : values )
|
|
{
|
|
if ( aggregatedVector.size() != size )
|
|
{
|
|
QString msg = "Detected mismatch in number of time steps for aggregated values. All grid "
|
|
"cases must have identical number of time steps.";
|
|
RiaLogging::error( msg.toStdString() );
|
|
anyErrorsDetected = true;
|
|
return { anyErrorsDetected, statisticsText };
|
|
}
|
|
}
|
|
|
|
std::vector<double> minValues;
|
|
std::vector<double> p90Values;
|
|
std::vector<double> p50Values;
|
|
std::vector<double> avgValues;
|
|
std::vector<double> p10Values;
|
|
std::vector<double> maxValues;
|
|
|
|
for ( size_t timeIdx = 0; timeIdx < values.front().size(); timeIdx++ )
|
|
{
|
|
std::vector<double> valuesForTimeStep;
|
|
|
|
for ( auto v : values )
|
|
{
|
|
valuesForTimeStep.push_back( v[timeIdx] );
|
|
}
|
|
|
|
// Required to have minimum 8 values to calculate statistics
|
|
if ( valuesForTimeStep.size() < 8 ) continue;
|
|
|
|
double p90 = std::numeric_limits<double>::infinity();
|
|
double p50 = std::numeric_limits<double>::infinity();
|
|
double avg = std::numeric_limits<double>::infinity();
|
|
double p10 = std::numeric_limits<double>::infinity();
|
|
|
|
auto [min, max] = std::minmax_element( valuesForTimeStep.begin(), valuesForTimeStep.end() );
|
|
|
|
RigStatisticsMath::calculateStatisticsCurves( valuesForTimeStep, &p10, &p50, &p90, &avg, RigStatisticsMath::PercentileStyle::SWITCHED );
|
|
|
|
minValues.push_back( *min );
|
|
p90Values.push_back( p90 );
|
|
p50Values.push_back( p50 );
|
|
avgValues.push_back( avg );
|
|
p10Values.push_back( p10 );
|
|
maxValues.push_back( *max );
|
|
}
|
|
|
|
if ( !minValues.empty() )
|
|
{
|
|
QString minTxt = " Minimum";
|
|
QString p90Txt = " P90 ";
|
|
QString p50Txt = " P50 ";
|
|
QString avgTxt = " Mean ";
|
|
QString p10Txt = " P10 ";
|
|
QString maxTxt = " Maximum";
|
|
|
|
for ( size_t timeIdx = 0; timeIdx < p10Values.size(); timeIdx++ )
|
|
{
|
|
minTxt += "\t" + QString::number( minValues[timeIdx] );
|
|
p90Txt += "\t" + QString::number( p90Values[timeIdx] );
|
|
p50Txt += "\t" + QString::number( p50Values[timeIdx] );
|
|
avgTxt += "\t" + QString::number( avgValues[timeIdx] );
|
|
p10Txt += "\t" + QString::number( p10Values[timeIdx] );
|
|
maxTxt += "\t" + QString::number( maxValues[timeIdx] );
|
|
}
|
|
|
|
statisticsText.push_back( minTxt );
|
|
statisticsText.push_back( p90Txt );
|
|
statisticsText.push_back( p50Txt );
|
|
statisticsText.push_back( avgTxt );
|
|
statisticsText.push_back( p10Txt );
|
|
statisticsText.push_back( maxTxt );
|
|
}
|
|
|
|
return { anyErrorsDetected, statisticsText };
|
|
}
|