///////////////////////////////////////////////////////////////////////////////// // // 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 // for more details. // ///////////////////////////////////////////////////////////////////////////////// #include "RimGridCalculation.h" #include "RiaDefines.h" #include "RiaLogging.h" #include "RiaPorosityModel.h" #include "RigActiveCellInfo.h" #include "RimEclipseCase.h" #include "RimEclipseCellColors.h" #include "RimEclipseView.h" #include "RimGridCalculationVariable.h" #include "RimProject.h" #include "RimReloadCaseTools.h" #include "RimTools.h" #include "RigCaseCellResultsData.h" #include "RigEclipseCaseData.h" #include "RigEclipseResultAddress.h" #include "RigGridManager.h" #include "RigMainGrid.h" #include "RigResultAccessor.h" #include "RigResultAccessorFactory.h" #include "expressionparser/ExpressionParser.h" CAF_PDM_SOURCE_INIT( RimGridCalculation, "RimGridCalculation" ); namespace caf { template <> void caf::AppEnum::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 ); } }; // 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_defaultPropertyVariableIndex, "DefaultPropertyVariableName", 0, "Property Variable Name" ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimGridCalculationVariable* RimGridCalculation::createVariable() { auto variable = new RimGridCalculationVariable; variable->eclipseResultChanged.connect( this, &RimGridCalculation::onVariableUpdated ); return variable; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool RimGridCalculation::calculate() { QString leftHandSideVariableName = RimGridCalculation::findLeftHandSide( m_expression ); RimEclipseCase* eclipseCase = outputEclipseCase(); if ( !eclipseCase ) { RiaLogging::errorInMessageBox( nullptr, "Grid Property Calculator", QString( "No case found for calculation : %1" ).arg( leftHandSideVariableName ) ); return false; } auto [isOk, errorMessage] = validateVariables(); if ( !isOk ) { RiaLogging::errorInMessageBox( nullptr, "Grid Property Calculator", errorMessage ); return false; } for ( auto variableCase : inputCases() ) { if ( !eclipseCase->isGridSizeEqualTo( variableCase ) ) { QString msg = "Detected IJK mismatch between input cases and destination case. All grid " "cases must have identical IJK sizes."; RiaLogging::errorInMessageBox( nullptr, "Grid Property Calculator", msg ); return false; } } auto porosityModel = RiaDefines::PorosityModelType::MATRIX_MODEL; RigEclipseResultAddress resAddr( RiaDefines::ResultCatType::GENERATED, leftHandSideVariableName ); if ( !eclipseCase->results( porosityModel )->ensureKnownResultLoaded( resAddr ) ) { bool needsToBeStored = false; eclipseCase->results( porosityModel )->createResultEntry( resAddr, needsToBeStored ); } eclipseCase->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 ), eclipseCase->results( porosityModel )->maxTimeStepCount() ); std::vector>* scalarResultFrames = eclipseCase->results( porosityModel )->modifiableCellScalarResultTimesteps( resAddr ); scalarResultFrames->resize( timeStepCount ); for ( size_t tsId = 0; tsId < timeStepCount; tsId++ ) { std::vector> values; for ( size_t i = 0; i < m_variables.size(); i++ ) { RimGridCalculationVariable* v = dynamic_cast( m_variables[i] ); CAF_ASSERT( v != nullptr ); values.push_back( getInputVectorForVariable( v, tsId, porosityModel, outputEclipseCase() ) ); } ExpressionParser parser; for ( size_t i = 0; i < m_variables.size(); i++ ) { RimGridCalculationVariable* v = dynamic_cast( m_variables[i] ); CAF_ASSERT( v != nullptr ); parser.assignVector( v->name(), values[i] ); } std::vector resultValues; resultValues.resize( values[0].size() ); parser.assignVector( leftHandSideVariableName, resultValues ); QString errorText; bool evaluatedOk = parser.expandIfStatementsAndEvaluate( m_expression, &errorText ); if ( evaluatedOk ) { if ( m_cellFilterView() ) { filterResults( m_cellFilterView(), values, m_defaultValueType(), m_defaultValue(), resultValues, porosityModel, outputEclipseCase() ); } scalarResultFrames->at( tsId ) = resultValues; m_isDirty = false; } else { QString s = "The following error message was received from the parser library : \n\n"; s += errorText; RiaLogging::errorInMessageBox( nullptr, "Grid Property Calculator", s ); return false; } } eclipseCase->updateResultAddressCollection(); return true; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimEclipseCase* RimGridCalculation::outputEclipseCase() const { return m_destinationCase; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimGridCalculation::inputCases() const { std::vector cases; for ( const auto& variable : m_variables ) { auto* v = dynamic_cast( 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 ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimGridCalculation::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) { RimUserDefinedCalculation::defineUiOrdering( uiConfigName, uiOrdering ); uiOrdering.add( &m_destinationCase ); 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_defaultPropertyVariableIndex ); else if ( m_defaultValueType() == RimGridCalculation::DefaultValueType::USER_DEFINED ) filterGroup->add( &m_defaultValue ); } uiOrdering.skipRemainingFields(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QList RimGridCalculation::calculateValueOptions( const caf::PdmFieldHandle* fieldNeedingOptions ) { QList options; if ( fieldNeedingOptions == &m_cellFilterView ) { options.push_back( caf::PdmOptionItemInfo( "Disabled", nullptr ) ); std::vector views; RimProject::current()->allViews( views ); RimEclipseCase* firstEclipseCase = nullptr; if ( !inputCases().empty() ) firstEclipseCase = inputCases().front(); if ( firstEclipseCase ) { for ( auto* view : views ) { auto eclipseView = dynamic_cast( 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 ) { if ( inputCases().empty() ) { RimTools::eclipseCaseOptionItems( &options ); } else { RimEclipseCase* firstInputCase = inputCases()[0]; RimProject* proj = RimProject::current(); if ( proj ) { std::vector cases; proj->allCases( cases ); for ( RimCase* c : cases ) { auto* eclipseCase = dynamic_cast( c ); if ( !eclipseCase ) continue; if ( !firstInputCase->isGridSizeEqualTo( eclipseCase ) ) continue; options.push_back( caf::PdmOptionItemInfo( c->caseUserDescription(), c, false, c->uiIconProvider() ) ); } } } options.push_front( caf::PdmOptionItemInfo( "None", nullptr ) ); } else if ( fieldNeedingOptions == &m_defaultPropertyVariableIndex ) { for ( int i = 0; i < static_cast( m_variables.size() ); i++ ) { auto v = dynamic_cast( m_variables[i] ); QString optionText = v->name(); options.push_back( caf::PdmOptionItemInfo( optionText, i ) ); } } return options; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimGridCalculation::initAfterRead() { for ( auto& variable : m_variables ) { auto gridVar = dynamic_cast( variable.p() ); if ( gridVar ) { gridVar->eclipseResultChanged.connect( this, &RimGridCalculation::onVariableUpdated ); if ( m_destinationCase == nullptr ) m_destinationCase = gridVar->eclipseCase(); } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimGridCalculation::onVariableUpdated( const SignalEmitter* emitter ) { if ( m_destinationCase == nullptr ) { auto variable = dynamic_cast( emitter ); if ( variable && variable->eclipseCase() ) { m_destinationCase = variable->eclipseCase(); updateConnectedEditors(); } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RigEclipseResultAddress RimGridCalculation::outputAddress() const { QString leftHandSideVariableName = RimGridCalculation::findLeftHandSide( m_expression ); RigEclipseResultAddress resAddr( RiaDefines::ResultCatType::GENERATED, leftHandSideVariableName ); return resAddr; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::vector RimGridCalculation::getInputVectorForVariable( RimGridCalculationVariable* v, size_t tsId, RiaDefines::PorosityModelType porosityModel, RimEclipseCase* outputEclipseCase ) const { int timeStep = v->timeStep(); auto resultCategoryType = v->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; } RigEclipseResultAddress resAddr( resultCategoryType, v->resultVariable() ); auto mainGrid = v->eclipseCase()->mainGrid(); size_t maxGridCount = mainGrid->gridCount(); auto activeCellInfo = outputEclipseCase->eclipseCaseData()->activeCellInfo( porosityModel ); size_t cellCount = activeCellInfo->reservoirActiveCellCount(); std::vector inputValues( cellCount ); for ( size_t gridIdx = 0; gridIdx < maxGridCount; ++gridIdx ) { auto grid = mainGrid->gridByIndex( gridIdx ); cvf::ref sourceResultAccessor = RigResultAccessorFactory::createFromResultAddress( v->eclipseCase()->eclipseCaseData(), gridIdx, porosityModel, timeStepToUse, resAddr ); #pragma omp parallel for for ( int localGridCellIdx = 0; localGridCellIdx < static_cast( grid->cellCount() ); localGridCellIdx++ ) { const size_t reservoirCellIndex = grid->reservoirCellIndex( localGridCellIdx ); if ( activeCellInfo->isActive( reservoirCellIndex ) ) { size_t cellResultIndex = activeCellInfo->cellResultIndex( reservoirCellIndex ); inputValues[cellResultIndex] = sourceResultAccessor->cellScalar( localGridCellIdx ); } } } return inputValues; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimGridCalculation::replaceFilteredValuesWithVector( const std::vector& inputValues, cvf::ref visibility, std::vector& resultValues, RiaDefines::PorosityModelType porosityModel, RimEclipseCase* outputEclipseCase ) { auto activeCellInfo = outputEclipseCase->eclipseCaseData()->activeCellInfo( porosityModel ); int numCells = static_cast( visibility->size() ); #pragma omp parallel for for ( int i = 0; i < numCells; i++ ) { if ( !visibility->val( i ) && activeCellInfo->isActive( i ) ) { size_t cellResultIndex = activeCellInfo->cellResultIndex( i ); resultValues[cellResultIndex] = inputValues[cellResultIndex]; } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimGridCalculation::replaceFilteredValuesWithDefaultValue( double defaultValue, cvf::ref visibility, std::vector& resultValues, RiaDefines::PorosityModelType porosityModel, RimEclipseCase* outputEclipseCase ) { auto activeCellInfo = outputEclipseCase->eclipseCaseData()->activeCellInfo( porosityModel ); int numCells = static_cast( visibility->size() ); #pragma omp parallel for for ( int i = 0; i < numCells; i++ ) { if ( !visibility->val( i ) && activeCellInfo->isActive( i ) ) { size_t cellResultIndex = activeCellInfo->cellResultIndex( i ); resultValues[cellResultIndex] = defaultValue; } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimGridCalculation::filterResults( RimGridView* cellFilterView, const std::vector>& values, RimGridCalculation::DefaultValueType defaultValueType, double defaultValue, std::vector& resultValues, RiaDefines::PorosityModelType porosityModel, RimEclipseCase* outputEclipseCase ) const { auto visibility = cellFilterView->currentTotalCellVisibility(); if ( defaultValueType == RimGridCalculation::DefaultValueType::FROM_PROPERTY ) { if ( m_defaultPropertyVariableIndex < static_cast( values.size() ) ) replaceFilteredValuesWithVector( values[m_defaultPropertyVariableIndex], visibility, resultValues, porosityModel, outputEclipseCase ); else { QString errorMessage = "Invalid input data for default result property, no data assigned to non-visible cells."; RiaLogging::errorInMessageBox( nullptr, "Grid Property Calculator", errorMessage ); } } else { double valueToUse = defaultValue; if ( defaultValueType == RimGridCalculation::DefaultValueType::POSITIVE_INFINITY ) valueToUse = HUGE_VAL; replaceFilteredValuesWithDefaultValue( valueToUse, visibility, resultValues, porosityModel, outputEclipseCase ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimGridCalculation::updateDependentObjects() { RimEclipseCase* eclipseCase = outputEclipseCase(); 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 ); RimEclipseCase* eclipseCase = outputEclipseCase(); 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 ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::pair RimGridCalculation::validateVariables() { auto porosityModel = RiaDefines::PorosityModelType::MATRIX_MODEL; for ( size_t i = 0; i < m_variables.size(); i++ ) { RimGridCalculationVariable* v = dynamic_cast( 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& 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(); } } }