#9671 summary calculations for summary items

* Summary Calculation: use shorter name in title
* Sort summary addresses to group calculated results last.
* Summary Calculation: add option to distribute calculation to other items.
* Add support for summary calculation for RimGridSummaryCase.
* Use short name for calculations in plot titles.
* Update ensembles in Data Sources when calculation is added.
* Summary Calculation: allow drag-and-drop of ensembles
* Summary Plot: fix axis range aggregation for calculated ensemble addresses.

---------

Co-authored-by: Kristian Bendiksen <kristian.bendiksen@gmail.com>
This commit is contained in:
Magne Sjaastad
2023-02-26 08:08:06 +01:00
parent 62038c0fc9
commit 2bf3a511fe
51 changed files with 990 additions and 601 deletions

View File

@@ -18,16 +18,22 @@
#include "RimSummaryCalculation.h"
#include "expressionparser/ExpressionParser.h"
#include "RifEclipseSummaryAddress.h"
#include "RiaCurveMerger.h"
#include "RiaLogging.h"
#include "RiaSummaryCurveDefinition.h"
#include "RiaSummaryTools.h"
#include "RifSummaryReaderInterface.h"
#include "RimDataSourceSteppingTools.h"
#include "RimProject.h"
#include "RimSummaryAddress.h"
#include "RimSummaryCalculationCollection.h"
#include "RimSummaryCalculationVariable.h"
#include "RimSummaryCase.h"
#include "RimSummaryCaseCollection.h"
#include "RimSummaryCaseMainCollection.h"
#include "RimSummaryCurve.h"
#include "RimSummaryMultiPlot.h"
#include "RimSummaryMultiPlotCollection.h"
@@ -39,6 +45,8 @@
#include "cafPdmUiTableViewEditor.h"
#include "cafPdmUiTextEditor.h"
#include "expressionparser/ExpressionParser.h"
#include <algorithm>
CAF_PDM_SOURCE_INIT( RimSummaryCalculation, "RimSummaryCalculation" );
@@ -49,6 +57,11 @@ CAF_PDM_SOURCE_INIT( RimSummaryCalculation, "RimSummaryCalculation" );
RimSummaryCalculation::RimSummaryCalculation()
{
CAF_PDM_InitObject( "RimSummaryCalculation", ":/octave.png", "Calculation", "" );
CAF_PDM_InitField( &m_distributeToOtherItems,
"DistributeToOtherItems",
true,
"Distribute to other items (wells, groups, ..)" );
}
//--------------------------------------------------------------------------------------------------
@@ -62,12 +75,54 @@ RimSummaryCalculationVariable* RimSummaryCalculation::createVariable()
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RimSummaryCalculation::calculate()
std::optional<std::vector<SummaryCalculationVariable>> RimSummaryCalculation::getVariables() const
{
QString leftHandSideVariableName = RimSummaryCalculation::findLeftHandSide( m_expression );
std::vector<SummaryCalculationVariable> variables;
RiaTimeHistoryCurveMerger timeHistoryCurveMerger;
for ( size_t i = 0; i < m_variables.size(); i++ )
{
RimSummaryCalculationVariable* v = dynamic_cast<RimSummaryCalculationVariable*>( m_variables[i] );
CAF_ASSERT( v != nullptr );
if ( !v->summaryCase() )
{
return {};
}
if ( !v->summaryAddress() )
{
return {};
}
if ( v->summaryAddress()->address().id() == id() )
{
return {};
}
if ( v->summaryAddress()->address().isCalculated() )
{
std::set<int> calcIds;
if ( detectCyclicCalculation( v->summaryAddress()->address().id(), calcIds ) )
{
return {};
}
}
SummaryCalculationVariable variable;
variable.name = v->name();
variable.summaryCase = v->summaryCase();
variable.summaryAddress = v->summaryAddress()->address();
variables.push_back( variable );
}
return variables;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RimSummaryCalculation::checkVariables() const
{
for ( size_t i = 0; i < m_variables.size(); i++ )
{
RimSummaryCalculationVariable* v = dynamic_cast<RimSummaryCalculationVariable*>( m_variables[i] );
@@ -78,7 +133,6 @@ bool RimSummaryCalculation::calculate()
RiaLogging::errorInMessageBox( nullptr,
"Expression Parser",
QString( "No summary case defined for variable : %1" ).arg( v->name() ) );
return false;
}
@@ -87,11 +141,180 @@ bool RimSummaryCalculation::calculate()
RiaLogging::errorInMessageBox( nullptr,
"Expression Parser",
QString( "No summary address defined for variable : %1" ).arg( v->name() ) );
return false;
}
RiaSummaryCurveDefinition curveDef( v->summaryCase(), v->summaryAddress()->address(), false );
if ( v->summaryAddress()->address().id() == id() )
{
RiaLogging::errorInMessageBox( nullptr,
"Expression Parser",
QString( "Recursive calculation detected for variable : %1" ).arg( v->name() ) );
return false;
}
if ( v->summaryAddress()->address().isCalculated() )
{
std::set<int> calcIds;
if ( detectCyclicCalculation( v->summaryAddress()->address().id(), calcIds ) )
{
RiaLogging::errorInMessageBox( nullptr,
"Expression Parser",
QString( "Cyclic calculation detected for variable : %1" ).arg( v->name() ) );
return false;
}
}
}
return true;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RimSummaryCalculation::detectCyclicCalculation( int id, std::set<int>& ids ) const
{
if ( ids.count( id ) > 0 )
return true;
else
{
ids.insert( id );
// Get calculation for the referenced id
RimSummaryCalculationCollection* calcColl = RimProject::current()->calculationCollection();
auto calc = dynamic_cast<RimSummaryCalculation*>( calcColl->findCalculationById( id ) );
// Check if any of the variables references already seen calculations
auto vars = calc->variables();
for ( size_t i = 0; i < vars->size(); i++ )
{
auto variable = dynamic_cast<RimSummaryCalculationVariable*>( vars->at( i ) );
auto addr = variable->summaryAddress()->address();
if ( addr.id() != -1 && detectCyclicCalculation( addr.id(), ids ) ) return true;
}
return false;
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RimSummaryCalculation::calculate()
{
auto isOk = checkVariables();
if ( !isOk ) return false;
// Not much to do for calculate: values and timesteps are generate when needed later.
m_isDirty = false;
return true;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimSummaryCalculation::substituteVariables( std::vector<SummaryCalculationVariable>& vars,
const RifEclipseSummaryAddress& address )
{
auto firstVariable = vars.front();
auto category = firstVariable.summaryAddress.category();
QVariant oldValue;
QVariant newValue;
bool isHandledBySteppingTools = false;
if ( category == RifEclipseSummaryAddress::SUMMARY_WELL )
{
oldValue = QString::fromStdString( firstVariable.summaryAddress.wellName() );
newValue = QString::fromStdString( address.wellName() );
isHandledBySteppingTools = true;
}
else if ( category == RifEclipseSummaryAddress::SUMMARY_REGION )
{
oldValue = firstVariable.summaryAddress.regionNumber();
newValue = address.regionNumber();
isHandledBySteppingTools = true;
}
else if ( category == RifEclipseSummaryAddress::SUMMARY_GROUP )
{
oldValue = QString::fromStdString( firstVariable.summaryAddress.groupName() );
newValue = QString::fromStdString( address.groupName() );
isHandledBySteppingTools = true;
}
else if ( category == RifEclipseSummaryAddress::SUMMARY_AQUIFER )
{
oldValue = firstVariable.summaryAddress.aquiferNumber();
newValue = address.aquiferNumber();
isHandledBySteppingTools = true;
}
else if ( category == RifEclipseSummaryAddress::SUMMARY_WELL_COMPLETION ||
category == RifEclipseSummaryAddress::SUMMARY_BLOCK )
{
oldValue = QString::fromStdString( firstVariable.summaryAddress.blockAsString() );
newValue = QString::fromStdString( address.blockAsString() );
isHandledBySteppingTools = true;
}
else if ( category == RifEclipseSummaryAddress::SUMMARY_MISC || category == RifEclipseSummaryAddress::SUMMARY_FIELD )
{
// No need to do anything for these types
return;
}
else if ( category == RifEclipseSummaryAddress::SUMMARY_REGION_2_REGION )
{
oldValue = QString::fromStdString( firstVariable.summaryAddress.formatUiTextRegionToRegion() );
newValue = QString::fromStdString( address.formatUiTextRegionToRegion() );
isHandledBySteppingTools = true;
}
else
{
RiaLogging::error( QString( "Unhandled subst for category: %1" ).arg( address.uiText().c_str() ) );
}
if ( isHandledBySteppingTools )
{
for ( auto& v : vars )
{
if ( v.summaryAddress.category() == address.category() )
{
RimDataSourceSteppingTools::updateAddressIfMatching( oldValue, newValue, address.category(), &v.summaryAddress );
}
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::optional<std::pair<std::vector<double>, std::vector<time_t>>>
RimSummaryCalculation::calculateWithSubstitutions( RimSummaryCase* summaryCase, const RifEclipseSummaryAddress& addr )
{
auto variables = getVariables();
if ( !variables ) return {};
auto vars = variables.value();
substituteVariables( vars, addr );
return calculateResult( m_expression, vars, summaryCase );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::optional<std::pair<std::vector<double>, std::vector<time_t>>>
RimSummaryCalculation::calculateResult( const QString& expression,
const std::vector<SummaryCalculationVariable>& variables,
RimSummaryCase* summaryCase )
{
QString leftHandSideVariableName = RimSummaryCalculation::findLeftHandSide( expression );
RiaTimeHistoryCurveMerger timeHistoryCurveMerger;
for ( size_t i = 0; i < variables.size(); i++ )
{
SummaryCalculationVariable v = variables[i];
RiaSummaryCurveDefinition curveDef( summaryCase, v.summaryAddress, false );
std::vector<double> curveValues;
RiaSummaryCurveDefinition::resultValues( curveDef, &curveValues );
@@ -102,17 +325,21 @@ bool RimSummaryCalculation::calculate()
{
timeHistoryCurveMerger.addCurveData( curveTimeSteps, curveValues );
}
else
{
// One variable is missing: not possible to complete the calculation.
// Can happen when stepping and substituting variables.
return {};
}
}
timeHistoryCurveMerger.computeInterpolatedValues();
ExpressionParser parser;
for ( size_t i = 0; i < m_variables.size(); i++ )
for ( size_t i = 0; i < variables.size(); i++ )
{
RimSummaryCalculationVariable* v = dynamic_cast<RimSummaryCalculationVariable*>( m_variables[i] );
CAF_ASSERT( v != nullptr );
parser.assignVector( v->name(), timeHistoryCurveMerger.interpolatedYValuesForAllXValues( i ) );
SummaryCalculationVariable v = variables[i];
parser.assignVector( v.name, timeHistoryCurveMerger.interpolatedYValuesForAllXValues( i ) );
}
std::vector<double> resultValues;
@@ -121,13 +348,10 @@ bool RimSummaryCalculation::calculate()
parser.assignVector( leftHandSideVariableName, resultValues );
QString errorText;
bool evaluatedOk = parser.expandIfStatementsAndEvaluate( m_expression, &errorText );
bool evaluatedOk = parser.expandIfStatementsAndEvaluate( expression, &errorText );
if ( evaluatedOk )
{
m_timesteps.v().clear();
m_calculatedValues.v().clear();
if ( timeHistoryCurveMerger.validIntervalsForAllXValues().size() > 0 )
{
size_t firstValidTimeStep = timeHistoryCurveMerger.validIntervalsForAllXValues().front().first;
@@ -141,12 +365,9 @@ bool RimSummaryCalculation::calculate()
std::vector<double> validValues( resultValues.begin() + firstValidTimeStep,
resultValues.begin() + lastValidTimeStep );
m_timesteps = validTimeSteps;
m_calculatedValues = validValues;
return std::make_pair( validValues, validTimeSteps );
}
}
m_isDirty = false;
}
else
{
@@ -156,7 +377,7 @@ bool RimSummaryCalculation::calculate()
RiaLogging::errorInMessageBox( nullptr, "Expression Parser", s );
}
return evaluatedOk;
return {};
}
//--------------------------------------------------------------------------------------------------
@@ -168,6 +389,23 @@ void RimSummaryCalculation::updateDependentObjects()
this->firstAncestorOrThisOfTypeAsserted( calcColl );
calcColl->rebuildCaseMetaData();
// Refresh data sources tree.
// TODO: refresh too much: would be enough to only refresh calculated resutls.
RimSummaryCaseMainCollection* summaryCaseCollection = RiaSummaryTools::summaryCaseMainCollection();
auto summaryCases = summaryCaseCollection->allSummaryCases();
for ( RimSummaryCase* summaryCase : summaryCases )
{
summaryCase->createSummaryReaderInterface();
summaryCase->createRftReaderInterface();
summaryCase->refreshMetaData();
}
auto summaryCaseCollections = summaryCaseCollection->summaryCaseCollections();
for ( RimSummaryCaseCollection* summaryCaseCollection : summaryCaseCollections )
{
summaryCaseCollection->refreshMetaData();
}
RimSummaryMultiPlotCollection* summaryPlotCollection = RiaSummaryTools::summaryMultiPlotCollection();
for ( auto multiPlot : summaryPlotCollection->multiPlots() )
{
@@ -177,7 +415,7 @@ void RimSummaryCalculation::updateDependentObjects()
for ( RimSummaryCurve* sumCurve : sumPlot->summaryCurves() )
{
if ( sumCurve->summaryAddressY().category() == RifEclipseSummaryAddress::SUMMARY_CALCULATED )
if ( sumCurve->summaryAddressY().isCalculated() )
{
sumCurve->updateConnectedEditors();
@@ -199,3 +437,216 @@ void RimSummaryCalculation::updateDependentObjects()
void RimSummaryCalculation::removeDependentObjects()
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RimSummaryCalculationAddress> RimSummaryCalculation::allAddressesForSummaryCase( RimSummaryCase* summaryCase ) const
{
auto variables = getVariables();
if ( variables && !variables.value().empty() )
{
// The first variable is the substituable one. Use its category to
// provide all available addresses.
auto firstVariable = variables.value().front();
if ( m_distributeToOtherItems )
{
auto allResultAddresses = summaryCase->summaryReader()->allResultAddresses();
return allAddressesForCategory( firstVariable.summaryAddress.category(), allResultAddresses );
}
else
{
// Generate the result only for the first variable
return { RimSummaryCalculationAddress( singleAddressesForCategory( firstVariable.summaryAddress ) ) };
}
}
return {};
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RimSummaryCalculationAddress>
RimSummaryCalculation::allAddressesForCategory( RifEclipseSummaryAddress::SummaryVarCategory category,
const std::set<RifEclipseSummaryAddress>& allResultAddresses ) const
{
std::vector<RimSummaryCalculationAddress> addresses;
std::string name = shortName().toStdString();
if ( category == RifEclipseSummaryAddress::SUMMARY_FIELD )
{
addresses.push_back( RimSummaryCalculationAddress( RifEclipseSummaryAddress::fieldAddress( name, m_id ) ) );
}
else if ( category == RifEclipseSummaryAddress::SUMMARY_AQUIFER )
{
std::set<int> uniqueNumbers;
std::for_each( allResultAddresses.begin(), allResultAddresses.end(), [&]( const auto& addr ) {
uniqueNumbers.insert( addr.aquiferNumber() );
} );
for ( auto num : uniqueNumbers )
{
addresses.push_back(
RimSummaryCalculationAddress( RifEclipseSummaryAddress::aquiferAddress( name, num, m_id ) ) );
}
}
else if ( category == RifEclipseSummaryAddress::SUMMARY_MISC )
{
addresses.push_back( RimSummaryCalculationAddress( RifEclipseSummaryAddress::miscAddress( name, m_id ) ) );
}
else if ( category == RifEclipseSummaryAddress::SUMMARY_NETWORK )
{
addresses.push_back( RimSummaryCalculationAddress( RifEclipseSummaryAddress::networkAddress( name, m_id ) ) );
}
else if ( category == RifEclipseSummaryAddress::SUMMARY_WELL )
{
std::set<std::string> uniqueWellNames;
std::for_each( allResultAddresses.begin(), allResultAddresses.end(), [&]( const auto& addr ) {
uniqueWellNames.insert( addr.wellName() );
} );
for ( auto wellName : uniqueWellNames )
{
addresses.push_back(
RimSummaryCalculationAddress( RifEclipseSummaryAddress::wellAddress( name, wellName, m_id ) ) );
}
}
else if ( category == RifEclipseSummaryAddress::SUMMARY_GROUP )
{
std::set<std::string> uniqueGroupNames;
std::for_each( allResultAddresses.begin(), allResultAddresses.end(), [&]( const auto& addr ) {
uniqueGroupNames.insert( addr.groupName() );
} );
for ( auto groupName : uniqueGroupNames )
{
addresses.push_back(
RimSummaryCalculationAddress( RifEclipseSummaryAddress::groupAddress( name, groupName, m_id ) ) );
}
}
else if ( category == RifEclipseSummaryAddress::SUMMARY_REGION )
{
std::set<int> uniqueRegionNumbers;
std::for_each( allResultAddresses.begin(), allResultAddresses.end(), [&]( const auto& addr ) {
uniqueRegionNumbers.insert( addr.regionNumber() );
} );
for ( auto regionNumber : uniqueRegionNumbers )
{
addresses.push_back(
RimSummaryCalculationAddress( RifEclipseSummaryAddress::regionAddress( name, regionNumber, m_id ) ) );
}
}
else if ( category == RifEclipseSummaryAddress::SUMMARY_REGION_2_REGION )
{
std::set<std::pair<int, int>> uniqueRegionNumbers;
std::for_each( allResultAddresses.begin(), allResultAddresses.end(), [&]( const auto& addr ) {
uniqueRegionNumbers.insert( std::make_pair( addr.regionNumber(), addr.regionNumber2() ) );
} );
for ( auto regionNumber : uniqueRegionNumbers )
{
auto [r1, r2] = regionNumber;
addresses.push_back(
RimSummaryCalculationAddress( RifEclipseSummaryAddress::regionToRegionAddress( name, r1, r2, m_id ) ) );
}
}
return addresses;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimSummaryCalculationAddress RimSummaryCalculation::singleAddressesForCategory( const RifEclipseSummaryAddress& address ) const
{
std::string name = shortName().toStdString();
RifEclipseSummaryAddress::SummaryVarCategory category = address.category();
if ( category == RifEclipseSummaryAddress::SUMMARY_FIELD )
{
return RifEclipseSummaryAddress::fieldAddress( name, m_id );
}
else if ( category == RifEclipseSummaryAddress::SUMMARY_AQUIFER )
{
return RifEclipseSummaryAddress::aquiferAddress( name, address.aquiferNumber(), m_id );
}
else if ( category == RifEclipseSummaryAddress::SUMMARY_MISC )
{
return RifEclipseSummaryAddress::miscAddress( name, m_id );
}
else if ( category == RifEclipseSummaryAddress::SUMMARY_NETWORK )
{
return RifEclipseSummaryAddress::networkAddress( name, m_id );
}
else if ( category == RifEclipseSummaryAddress::SUMMARY_WELL )
{
return RifEclipseSummaryAddress::wellAddress( name, address.wellName(), m_id );
}
else if ( category == RifEclipseSummaryAddress::SUMMARY_GROUP )
{
return RifEclipseSummaryAddress::groupAddress( name, address.groupName(), m_id );
}
else if ( category == RifEclipseSummaryAddress::SUMMARY_REGION )
{
return RifEclipseSummaryAddress::regionAddress( name, address.regionNumber(), m_id );
}
else if ( category == RifEclipseSummaryAddress::SUMMARY_REGION_2_REGION )
{
return RifEclipseSummaryAddress::regionToRegionAddress( name, address.regionNumber(), address.regionNumber2(), m_id );
}
return RifEclipseSummaryAddress();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<double> RimSummaryCalculation::values( RimSummaryCase* summaryCase, const RimSummaryCalculationAddress& address )
{
CAF_ASSERT( summaryCase );
const auto& result = calculateWithSubstitutions( summaryCase, address.address() );
if ( result )
{
auto [validValues, validTimeSteps] = result.value();
return validValues;
}
return {};
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<time_t> RimSummaryCalculation::timeSteps( RimSummaryCase* summaryCase,
const RimSummaryCalculationAddress& address )
{
CAF_ASSERT( summaryCase );
const auto& result = calculateWithSubstitutions( summaryCase, address.address() );
if ( result )
{
auto [validValues, validTimeSteps] = result.value();
return validTimeSteps;
}
return {};
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimSummaryCalculation::buildCalculationName() const
{
QString name = "Default Calculation Name";
if ( !m_expression.v().isEmpty() )
{
name = m_expression;
}
return name;
}