ResInsight/ApplicationLibCode/ProjectDataModel/Summary/RimSummaryCurvesData.cpp
Magne Sjaastad 473f1bebcd #10281 Make sure missing data is marked with NULL
If an ensemble do not have data for some realizations, Show Plot Data reported wrong data when resampling was activated. Display of data for No Resampling was correct.
2023-05-24 15:01:59 +02:00

497 lines
20 KiB
C++

/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2022 Equinor
//
// 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 "RimSummaryCurvesData.h"
#include "RiaSummaryCurveDefinition.h"
#include "RiaSummaryTools.h"
#include "RiaTimeHistoryCurveResampler.h"
#include "RimAsciiDataCurve.h"
#include "RimGridTimeHistoryCurve.h"
#include "RimSummaryCase.h"
#include "RimSummaryCaseCollection.h"
#include "RimSummaryCurve.h"
#include "RimSummaryPlot.h"
#include "cvfAssert.h"
#include "cvfMath.h"
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimSummaryCurvesData::populateTimeHistoryCurvesData( std::vector<RimGridTimeHistoryCurve*> curves, RimSummaryCurvesData* curvesData )
{
CVF_ASSERT( curvesData );
curvesData->clear();
for ( RimGridTimeHistoryCurve* curve : curves )
{
if ( !curve->isChecked() ) continue;
QString curveCaseName = curve->caseName();
CurveData curveData = { curve->curveExportDescription(), RifEclipseSummaryAddress(), curve->yValues() };
curvesData->addCurveData( curveCaseName, "", curve->timeStepValues(), curveData );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimSummaryCurvesData::populateAsciiDataCurvesData( std::vector<RimAsciiDataCurve*> curves, RimSummaryCurvesData* curvesData )
{
CVF_ASSERT( curvesData );
curvesData->clear();
for ( RimAsciiDataCurve* curve : curves )
{
if ( !curve->isChecked() ) continue;
CurveData curveData = { curve->curveExportDescription(), RifEclipseSummaryAddress(), curve->yValues() };
curvesData->addCurveDataNoSearch( "", "", curve->timeSteps(), { curveData } );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimSummaryCurvesData::RimSummaryCurvesData()
: resamplePeriod( RiaDefines::DateTimePeriod::NONE )
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimSummaryCurvesData::clear()
{
caseIds.clear();
timeSteps.clear();
allCurveData.clear();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimSummaryCurvesData::addCurveData( const QString& caseName,
const QString& ensembleName,
const std::vector<time_t>& curvetimeSteps,
const CurveData& curveData )
{
QString caseId = createCaseId( caseName, ensembleName );
size_t existingCaseIndex = findCaseIndexForCaseId( caseId, curvetimeSteps.size() );
if ( existingCaseIndex == cvf::UNDEFINED_SIZE_T )
{
caseIds.push_back( caseId );
timeSteps.push_back( curvetimeSteps );
allCurveData.push_back( { curveData } );
}
else
{
CVF_ASSERT( timeSteps[existingCaseIndex].size() == curveData.values.size() );
allCurveData[existingCaseIndex].push_back( curveData );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimSummaryCurvesData::addCurveDataNoSearch( const QString& caseName,
const QString& ensembleName,
const std::vector<time_t>& curvetimeSteps,
const std::vector<CurveData>& curveDataVector )
{
QString caseId = createCaseId( caseName, ensembleName );
caseIds.push_back( caseId );
timeSteps.push_back( curvetimeSteps );
allCurveData.push_back( curveDataVector );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimSummaryCurvesData::createTextForExport( const std::vector<RimSummaryCurve*>& curves,
const std::vector<RimAsciiDataCurve*>& asciiCurves,
const std::vector<RimGridTimeHistoryCurve*>& gridCurves,
RiaDefines::DateTimePeriod resamplingPeriod,
bool showTimeAsLongString )
{
QString out;
RimSummaryCurvesData summaryCurvesGridData;
RimSummaryCurvesData summaryCurvesObsData;
RimSummaryCurvesData::populateSummaryCurvesData( curves, SummaryCurveType::CURVE_TYPE_GRID, &summaryCurvesGridData );
RimSummaryCurvesData::populateSummaryCurvesData( curves, SummaryCurveType::CURVE_TYPE_OBSERVED, &summaryCurvesObsData );
RimSummaryCurvesData timeHistoryCurvesData;
RimSummaryCurvesData::populateTimeHistoryCurvesData( gridCurves, &timeHistoryCurvesData );
// Export observed data
RimSummaryCurvesData::appendToExportData( out, { summaryCurvesObsData }, showTimeAsLongString );
std::vector<RimSummaryCurvesData> exportData( 2 );
// Summary grid data for export
RimSummaryCurvesData::prepareCaseCurvesForExport( resamplingPeriod, ResampleAlgorithm::DATA_DECIDES, summaryCurvesGridData, &exportData[0] );
// Time history data for export
RimSummaryCurvesData::prepareCaseCurvesForExport( resamplingPeriod, ResampleAlgorithm::PERIOD_END, timeHistoryCurvesData, &exportData[1] );
// Export resampled summary and time history data
RimSummaryCurvesData::appendToExportData( out, exportData, showTimeAsLongString );
// Pasted observed data
{
RimSummaryCurvesData asciiCurvesData;
RimSummaryCurvesData::populateAsciiDataCurvesData( asciiCurves, &asciiCurvesData );
RimSummaryCurvesData::appendToExportData( out, { asciiCurvesData }, showTimeAsLongString );
}
return out;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimSummaryCurvesData::populateSummaryCurvesData( std::vector<RimSummaryCurve*> curves,
SummaryCurveType curveType,
RimSummaryCurvesData* curvesData )
{
CVF_ASSERT( curvesData );
curvesData->clear();
for ( RimSummaryCurve* curve : curves )
{
bool isObservedCurve = curve->summaryCaseY() ? curve->summaryCaseY()->isObservedData() : false;
if ( !curve->isChecked() ) continue;
if ( isObservedCurve && ( curveType != SummaryCurveType::CURVE_TYPE_OBSERVED ) ) continue;
if ( !isObservedCurve && ( curveType != SummaryCurveType::CURVE_TYPE_GRID ) ) continue;
if ( !curve->summaryCaseY() ) continue;
QString curveCaseName = curve->summaryCaseY()->displayCaseName();
QString ensembleName;
if ( curve->curveDefinitionY().ensemble() )
{
ensembleName = curve->curveDefinitionY().ensemble()->name();
}
CurveData curveData = { curve->curveExportDescription(), curve->summaryAddressY(), curve->valuesY() };
CurveData errorCurveData;
// Error data
auto errorValues = curve->errorValuesY();
bool hasErrorData = !errorValues.empty();
if ( hasErrorData )
{
errorCurveData.name = curve->curveExportDescription( curve->errorSummaryAddressY() );
errorCurveData.address = curve->errorSummaryAddressY();
errorCurveData.values = errorValues;
}
auto curveDataList = std::vector<CurveData>( { curveData } );
if ( hasErrorData ) curveDataList.push_back( errorCurveData );
if ( curve->summaryAddressY().isCalculated() )
{
// We have calculated data, and it we cannot assume identical time axis
curvesData->addCurveDataNoSearch( curveCaseName, ensembleName, curve->timeStepsY(), curveDataList );
}
else
{
for ( const auto& cd : curveDataList )
{
curvesData->addCurveData( curveCaseName, ensembleName, curve->timeStepsY(), cd );
}
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimSummaryCurvesData::prepareCaseCurvesForExport( RiaDefines::DateTimePeriod period,
ResampleAlgorithm algorithm,
const RimSummaryCurvesData& inputCurvesData,
RimSummaryCurvesData* resultCurvesData )
{
resultCurvesData->clear();
if ( period != RiaDefines::DateTimePeriod::NONE )
{
// Prepare result data
resultCurvesData->resamplePeriod = period;
for ( size_t i = 0; i < inputCurvesData.caseIds.size(); i++ )
{
// Shortcuts to input data
auto& caseId = inputCurvesData.caseIds[i];
auto& caseTimeSteps = inputCurvesData.timeSteps[i];
auto& caseCurveData = inputCurvesData.allCurveData[i];
// Prepare result data
for ( auto& curveDataItem : caseCurveData )
{
const auto [resampledTime, resampledValues] =
RiaSummaryTools::resampledValuesForPeriod( curveDataItem.address, caseTimeSteps, curveDataItem.values, period );
auto cd = curveDataItem;
cd.values = resampledValues;
resultCurvesData->addCurveData( caseId, "", resampledTime, cd );
}
}
}
else
{
*resultCurvesData = inputCurvesData;
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimSummaryCurvesData::appendToExportDataForCase( QString& out, const std::vector<time_t>& timeSteps, const std::vector<CurveData>& curveData )
{
for ( size_t j = 0; j < timeSteps.size(); j++ ) // time steps & data points
{
if ( j == 0 )
{
out += "Date and time";
for ( const auto& k : curveData ) // curves
{
out += "\t" + ( k.name );
}
}
out += "\n";
out += QDateTime::fromSecsSinceEpoch( timeSteps[j] ).toUTC().toString( "yyyy-MM-dd hh:mm:ss " );
for ( const auto& k : curveData ) // curves
{
QString valueText;
if ( j < k.values.size() )
{
valueText = QString::number( k.values[j], 'g', RimSummaryPlot::precision() );
}
out += "\t" + valueText.rightJustified( 13 );
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimSummaryCurvesData::appendToExportData( QString& out, const std::vector<RimSummaryCurvesData>& curvesData, bool showTimeAsLongString )
{
RimSummaryCurvesData data = RimSummaryCurvesData::concatCurvesData( curvesData );
if ( data.resamplePeriod != RiaDefines::DateTimePeriod::NONE )
{
time_t minTimeStep = std::numeric_limits<time_t>::max();
time_t maxTimeStep = 0;
for ( auto& timeSteps : data.timeSteps )
{
if ( !timeSteps.empty() )
{
if ( timeSteps.front() < minTimeStep ) minTimeStep = timeSteps.front();
if ( timeSteps.back() > maxTimeStep ) maxTimeStep = timeSteps.back();
}
}
auto allTimeSteps = RiaTimeHistoryCurveResampler::timeStepsFromTimeRange( data.resamplePeriod, minTimeStep, maxTimeStep );
out += "\n\n";
out += "Date and time";
for ( size_t i = 0; i < data.caseIds.size(); i++ )
{
for ( auto& j : data.allCurveData[i] )
{
out += "\t" + j.name;
}
}
out += "\n";
std::vector<size_t> currIndexes( data.caseIds.size() );
for ( auto& i : currIndexes )
i = 0;
for ( auto timeStep : allTimeSteps )
{
QDateTime timseStepUtc = QDateTime::fromSecsSinceEpoch( timeStep ).toUTC();
QString timeText;
if ( showTimeAsLongString )
{
timeText = timseStepUtc.toString( "yyyy-MM-dd hh:mm:ss " );
}
else
{
// Subtract one day to make sure the period is reported using the previous period as label
QDateTime oneDayEarlier = timseStepUtc.addDays( -1 );
QChar zeroChar( 48 );
switch ( data.resamplePeriod )
{
default:
// Fall through to NONE
case RiaDefines::DateTimePeriod::NONE:
timeText = timseStepUtc.toString( "yyyy-MM-dd hh:mm:ss " );
break;
case RiaDefines::DateTimePeriod::DAY:
timeText = oneDayEarlier.toString( "yyyy-MM-dd " );
break;
case RiaDefines::DateTimePeriod::WEEK:
{
timeText = oneDayEarlier.toString( "yyyy" );
int weekNumber = oneDayEarlier.date().weekNumber();
timeText += QString( "-W%1" ).arg( weekNumber, 2, 10, zeroChar );
break;
}
case RiaDefines::DateTimePeriod::MONTH:
timeText = oneDayEarlier.toString( "yyyy-MM" );
break;
case RiaDefines::DateTimePeriod::QUARTER:
{
int quarterNumber = oneDayEarlier.date().month() / 3;
timeText = oneDayEarlier.toString( "yyyy" );
timeText += QString( "-Q%1" ).arg( quarterNumber );
break;
}
case RiaDefines::DateTimePeriod::HALFYEAR:
{
int halfYearNumber = oneDayEarlier.date().month() / 6;
timeText = oneDayEarlier.toString( "yyyy" );
timeText += QString( "-H%1" ).arg( halfYearNumber );
break;
}
case RiaDefines::DateTimePeriod::YEAR:
timeText = oneDayEarlier.toString( "yyyy" );
break;
case RiaDefines::DateTimePeriod::DECADE:
timeText = oneDayEarlier.toString( "yyyy" );
break;
}
}
out += timeText;
for ( size_t i = 0; i < data.caseIds.size(); i++ ) // cases
{
// Check is time step exists in curr case
size_t& currIndex = currIndexes[i];
bool timeStepExists = currIndex < data.timeSteps[i].size() && timeStep == data.timeSteps[i][currIndex];
for ( auto& j : data.allCurveData[i] ) // vectors
{
QString valueText;
if ( timeStepExists )
{
valueText = QString::number( j.values[currIndex], 'g', RimSummaryPlot::precision() );
}
else
{
valueText = "NULL";
}
out += "\t" + valueText.rightJustified( 13 );
}
if ( timeStepExists && currIndex < data.timeSteps[i].size() ) currIndex++;
}
out += "\n";
}
}
else
{
for ( size_t i = 0; i < data.caseIds.size(); i++ )
{
out += "\n\n";
if ( !data.caseIds[i].isEmpty() )
{
out += "Case: " + data.caseIds[i];
out += "\n";
}
appendToExportDataForCase( out, data.timeSteps[i], data.allCurveData[i] );
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimSummaryCurvesData RimSummaryCurvesData::concatCurvesData( const std::vector<RimSummaryCurvesData>& curvesData )
{
CVF_ASSERT( !curvesData.empty() );
RiaDefines::DateTimePeriod period = curvesData.front().resamplePeriod;
RimSummaryCurvesData resultCurvesData;
resultCurvesData.resamplePeriod = period;
for ( auto curvesDataItem : curvesData )
{
if ( curvesDataItem.caseIds.empty() ) continue;
CVF_ASSERT( curvesDataItem.resamplePeriod == period );
resultCurvesData.caseIds.insert( resultCurvesData.caseIds.end(), curvesDataItem.caseIds.begin(), curvesDataItem.caseIds.end() );
resultCurvesData.timeSteps.insert( resultCurvesData.timeSteps.end(), curvesDataItem.timeSteps.begin(), curvesDataItem.timeSteps.end() );
resultCurvesData.allCurveData.insert( resultCurvesData.allCurveData.end(),
curvesDataItem.allCurveData.begin(),
curvesDataItem.allCurveData.end() );
}
return resultCurvesData;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
size_t RimSummaryCurvesData::findCaseIndexForCaseId( const QString& caseId, size_t timeStepCount )
{
size_t casePosInList = cvf::UNDEFINED_SIZE_T;
for ( size_t i = 0; i < caseIds.size(); i++ )
{
if ( caseId == caseIds[i] && timeSteps[i].size() == timeStepCount ) casePosInList = i;
}
return casePosInList;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimSummaryCurvesData::createCaseId( const QString& caseName, const QString& ensembleName )
{
QString caseId = caseName;
if ( !ensembleName.isEmpty() ) caseId += QString( " (%1)" ).arg( ensembleName );
return caseId;
}