///////////////////////////////////////////////////////////////////////////////// // // 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 // 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 curves, RimSummaryCurvesData* curvesData ) { CVF_ASSERT( curvesData ); curvesData->clear(); for ( RimGridTimeHistoryCurve* curve : curves ) { if ( !curve->isCurveVisible() ) continue; QString curveCaseName = curve->caseName(); CurveData curveData = { curve->curveExportDescription(), RifEclipseSummaryAddress(), curve->yValues() }; curvesData->addCurveData( curveCaseName, "", curve->timeStepValues(), curveData ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimSummaryCurvesData::populateAsciiDataCurvesData( std::vector curves, RimSummaryCurvesData* curvesData ) { CVF_ASSERT( curvesData ); curvesData->clear(); for ( RimAsciiDataCurve* curve : curves ) { if ( !curve->isCurveVisible() ) 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& 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& curvetimeSteps, const std::vector& curveDataVector ) { QString caseId = createCaseId( caseName, ensembleName ); caseIds.push_back( caseId ); timeSteps.push_back( curvetimeSteps ); allCurveData.push_back( curveDataVector ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimSummaryCurvesData::createTextForExport( const std::vector& curves, const std::vector& asciiCurves, const std::vector& 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 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 curves, SummaryCurveType curveType, RimSummaryCurvesData* curvesData ) { CVF_ASSERT( curvesData ); curvesData->clear(); for ( RimSummaryCurve* curve : curves ) { bool isObservedCurve = curve->summaryCaseY() ? curve->summaryCaseY()->isObservedData() : false; if ( !curve->isCurveVisible() ) 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 } ); if ( hasErrorData ) curveDataList.push_back( errorCurveData ); if ( curve->summaryAddressY().category() == RifEclipseSummaryAddress::SUMMARY_CALCULATED ) { // 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 ) { RiaTimeHistoryCurveResampler resampler; 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 ) { resampler.setCurveData( curveDataItem.values, caseTimeSteps ); if ( RiaSummaryTools::hasAccumulatedData( curveDataItem.address ) || algorithm == ResampleAlgorithm::PERIOD_END ) { resampler.resampleAndComputePeriodEndValues( period ); } else { resampler.resampleAndComputeWeightedMeanValues( period ); } auto cd = curveDataItem; cd.values = resampler.resampledValues(); resultCurvesData->addCurveData( caseId, "", resampler.resampledTimeSteps(), cd ); } } } else { *resultCurvesData = inputCurvesData; } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimSummaryCurvesData::appendToExportDataForCase( QString& out, const std::vector& timeSteps, const std::vector& 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& curvesData, bool showTimeAsLongString ) { RimSummaryCurvesData data = RimSummaryCurvesData::concatCurvesData( curvesData ); if ( data.resamplePeriod != RiaDefines::DateTimePeriod::NONE ) { time_t minTimeStep = std::numeric_limits::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 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& 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; }