#include "nmPebiResultSnapshotSerializer.h" #include "nmDataJsonTools.h" #include "nmPebiCellMapping.h" #include "nmPebiResultPropertyCatalog.h" #include "nmPebiResultSnapshot.h" #include "nmPebiResultSnapshotBuilder.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { const char g_aPressureMagic[8] = { 'N', 'M', 'P', 'R', 'E', 'S', '3', 0 }; const char g_aCurvesMagic[8] = { 'N', 'M', 'C', 'U', 'R', 'V', '3', 0 }; const char g_aStaticMagic[8] = { 'N', 'M', 'S', 'T', 'A', 'T', '5', 0 }; const quint32 g_nBinaryFormatVersion = 1; const int g_nSnapshotFormatVersion = 5; const quint32 g_nEndianMarker = 0x01020304u; const quint64 g_nMaximumCellCount = 50000000ull; const quint32 g_nMaximumFrameCount = 10000u; const quint32 g_nMaximumWellCount = 10000u; const quint32 g_nMaximumCurveSeries = 64u; const quint64 g_nMaximumCurvePoints = 50000000ull; const quint32 g_nMaximumUuidBytes = 128u; const quint32 g_nMaximumPropertyIdBytes = 64u; const quint32 g_nStaticPropertyCount = 3u; const quint64 g_nMaximumSnapshotJsonBytes = 64ull * 1024ull * 1024ull; const qint64 g_nStreamChunkBytes = 4 * 1024 * 1024; const int g_nCurveValidationBufferValues = 4096; bool setError(QString* pError, const QString& sError) { if(pError != NULL) { *pError = sError; } return false; } bool isFiniteValue(double dValue) { #if defined(_MSC_VER) return _finite(dValue) != 0; #else return qIsFinite(dValue); #endif } bool safeMultiply(quint64 nLeft, quint64 nRight, quint64& nProduct) { if(nLeft != 0 && nRight > (~static_cast(0)) / nLeft) { return false; } nProduct = nLeft * nRight; return true; } bool safeAdd(quint64 nLeft, quint64 nRight, quint64& nSum) { if(nRight > (~static_cast(0)) - nLeft) { return false; } nSum = nLeft + nRight; return true; } bool writeAll(QFile& oFile, const char* pData, quint64 nBytes) { quint64 nWritten = 0; while(nWritten < nBytes) { const qint64 nChunk = static_cast(qMin( static_cast(g_nStreamChunkBytes), nBytes - nWritten)); if(oFile.write(pData + static_cast(nWritten), nChunk) != nChunk) { return false; } nWritten += static_cast(nChunk); } return true; } bool readAll(QFile& oFile, char* pData, quint64 nBytes) { quint64 nRead = 0; while(nRead < nBytes) { const qint64 nChunk = static_cast(qMin( static_cast(g_nStreamChunkBytes), nBytes - nRead)); if(oFile.read(pData + static_cast(nRead), nChunk) != nChunk) { return false; } nRead += static_cast(nChunk); } return true; } template bool writePod(QFile& oFile, const T& oValue) { return writeAll(oFile, reinterpret_cast(&oValue), static_cast(sizeof(T))); } template bool readPod(QFile& oFile, T& oValue) { return readAll(oFile, reinterpret_cast(&oValue), static_cast(sizeof(T))); } bool hasMagic(const char* pActual, const char* pExpected) { for(int nIndex = 0; nIndex < 8; ++nIndex) { if(pActual[nIndex] != pExpected[nIndex]) { return false; } } return true; } rapidjson::Value toJsonString(const QString& sValue, rapidjson::Document::AllocatorType& oAllocator) { const QByteArray baValue = sValue.toUtf8(); return rapidjson::Value(baValue.constData(), static_cast(baValue.size()), oAllocator); } QString propertyTimeTypeText(NM_PEBI_RESULT_PROPERTY_TIME_TYPE eType) { return eType == NM_PebiResultProperty_Dynamic ? QString::fromLatin1("dynamic") : QString::fromLatin1("static"); } QString propertySourceText(NM_PEBI_RESULT_PROPERTY_SOURCE eSource) { return eSource == NM_PebiResultPropertySource_SolverOutput ? QString::fromLatin1("solver-output") : QString::fromLatin1("static-calculation"); } QString propertyAssociationText( NM_PEBI_RESULT_PROPERTY_ASSOCIATION eAssociation) { Q_UNUSED(eAssociation); return QString::fromLatin1("cell-data"); } QString propertyRangePolicyText( NM_PEBI_RESULT_PROPERTY_RANGE_POLICY ePolicy) { return ePolicy == NM_PebiResultPropertyRange_AllFrames ? QString::fromLatin1("all-frames") : QString::fromLatin1("current-array"); } bool readRequiredString(const rapidjson::Value& oParent, const char* pName, QString& sValue) { if(!oParent.IsObject() || !oParent.HasMember(pName) || !oParent[pName].IsString()) { return false; } sValue = QString::fromUtf8(oParent[pName].GetString(), static_cast(oParent[pName].GetStringLength())); return true; } void addPropertyCatalog( rapidjson::Value& oParent, rapidjson::Document::AllocatorType& oAllocator) { rapidjson::Value oProperties(rapidjson::kArrayType); const QList listDescriptors = nmPebiResultPropertyCatalog::descriptors(); for(int nIndex = 0; nIndex < listDescriptors.size(); ++nIndex) { const nmPebiResultPropertyDescriptor& oDescriptor = listDescriptors[nIndex]; rapidjson::Value oProperty(rapidjson::kObjectType); oProperty.AddMember("Id", toJsonString( oDescriptor.m_sPropertyId, oAllocator), oAllocator); oProperty.AddMember("DisplayNameSourceText", toJsonString( oDescriptor.m_sDisplayNameSourceText, oAllocator), oAllocator); oProperty.AddMember("BaseUnit", toJsonString( oDescriptor.m_sBaseUnit, oAllocator), oAllocator); oProperty.AddMember("DefaultDisplayUnit", toJsonString( oDescriptor.m_sDefaultDisplayUnit, oAllocator), oAllocator); oProperty.AddMember("TimeType", toJsonString( propertyTimeTypeText(oDescriptor.m_eTimeType), oAllocator), oAllocator); oProperty.AddMember("Source", toJsonString( propertySourceText(oDescriptor.m_eSource), oAllocator), oAllocator); oProperty.AddMember("Association", toJsonString( propertyAssociationText(oDescriptor.m_eAssociation), oAllocator), oAllocator); oProperty.AddMember("RangePolicy", toJsonString( propertyRangePolicyText(oDescriptor.m_eRangePolicy), oAllocator), oAllocator); oProperties.PushBack(oProperty, oAllocator); } oParent.AddMember("Properties", oProperties, oAllocator); } bool validatePropertyCatalog(const rapidjson::Value& oDocument, QString* pError) { if(!oDocument.IsObject() || !oDocument.HasMember("Properties") || !oDocument["Properties"].IsArray()) { return setError(pError, "PEBI snapshot property catalog is missing."); } const QList listDescriptors = nmPebiResultPropertyCatalog::descriptors(); const rapidjson::Value& oProperties = oDocument["Properties"]; if(oProperties.Size() != static_cast(listDescriptors.size())) { return setError(pError, "PEBI snapshot property catalog is incomplete."); } QSet setPropertyIds; for(rapidjson::SizeType nIndex = 0; nIndex < oProperties.Size(); ++nIndex) { const rapidjson::Value& oProperty = oProperties[nIndex]; QString sPropertyId; QString sDisplayName; QString sBaseUnit; QString sDisplayUnit; QString sTimeType; QString sSource; QString sAssociation; QString sRangePolicy; if(!readRequiredString(oProperty, "Id", sPropertyId) || !readRequiredString(oProperty, "DisplayNameSourceText", sDisplayName) || !readRequiredString(oProperty, "BaseUnit", sBaseUnit) || !readRequiredString(oProperty, "DefaultDisplayUnit", sDisplayUnit) || !readRequiredString(oProperty, "TimeType", sTimeType) || !readRequiredString(oProperty, "Source", sSource) || !readRequiredString(oProperty, "Association", sAssociation) || !readRequiredString(oProperty, "RangePolicy", sRangePolicy) || setPropertyIds.contains(sPropertyId)) { return setError(pError, "PEBI snapshot property descriptor is invalid."); } nmPebiResultPropertyDescriptor oExpected; if(!nmPebiResultPropertyCatalog::findDescriptor( sPropertyId, oExpected) || sDisplayName != oExpected.m_sDisplayNameSourceText || sBaseUnit != oExpected.m_sBaseUnit || sDisplayUnit != oExpected.m_sDefaultDisplayUnit || sTimeType != propertyTimeTypeText(oExpected.m_eTimeType) || sSource != propertySourceText(oExpected.m_eSource) || sAssociation != propertyAssociationText( oExpected.m_eAssociation) || sRangePolicy != propertyRangePolicyText( oExpected.m_eRangePolicy)) { return setError(pError, "PEBI snapshot property catalog does not match V5."); } setPropertyIds.insert(sPropertyId); } return setPropertyIds.size() == listDescriptors.size(); } void addFileReference(rapidjson::Value& oParent, const char* pName, const nmNumericalFileReference& oReference, rapidjson::Document::AllocatorType& oAllocator) { rapidjson::Value oJson(rapidjson::kObjectType); oJson.AddMember("Path", toJsonString(oReference.m_sRelativePath, oAllocator), oAllocator); oJson.AddMember("Length", oReference.m_nLength, oAllocator); oJson.AddMember("Sha1", toJsonString(oReference.m_sSha1, oAllocator), oAllocator); oParent.AddMember(rapidjson::Value(pName, oAllocator).Move(), oJson, oAllocator); } bool readFileReference(const rapidjson::Value& oParent, const char* pName, nmNumericalFileReference& oReference) { if(!oParent.IsObject() || !oParent.HasMember(pName) || !oParent[pName].IsObject()) { return false; } const rapidjson::Value& oJson = oParent[pName]; if(!oJson.HasMember("Path") || !oJson["Path"].IsString() || !oJson.HasMember("Length") || !oJson["Length"].IsUint64() || !oJson.HasMember("Sha1") || !oJson["Sha1"].IsString() || oJson["Sha1"].GetStringLength() != 40u) { return false; } oReference.m_sRelativePath = QString::fromUtf8( oJson["Path"].GetString(), static_cast(oJson["Path"].GetStringLength())); oReference.m_nLength = oJson["Length"].GetUint64(); oReference.m_sSha1 = QString::fromLatin1( oJson["Sha1"].GetString(), static_cast(oJson["Sha1"].GetStringLength())); return oReference.isValid(); } void addDoubleVector(rapidjson::Value& oParent, const char* pName, const QVector& vecValues, rapidjson::Document::AllocatorType& oAllocator) { rapidjson::Value oArray(rapidjson::kArrayType); for(int nIndex = 0; nIndex < vecValues.size(); ++nIndex) { oArray.PushBack(vecValues[nIndex], oAllocator); } oParent.AddMember(rapidjson::Value(pName, oAllocator).Move(), oArray, oAllocator); } bool readDoubleVector(const rapidjson::Value& oParent, const char* pName, QVector& vecValues) { if(!oParent.IsObject() || !oParent.HasMember(pName) || !oParent[pName].IsArray() || oParent[pName].Size() > static_cast(10000000)) { return false; } const rapidjson::Value& oArray = oParent[pName]; QVector vecLoaded; vecLoaded.reserve(static_cast(oArray.Size())); for(rapidjson::SizeType nIndex = 0; nIndex < oArray.Size(); ++nIndex) { if(!oArray[nIndex].IsNumber() || !isFiniteValue(oArray[nIndex].GetDouble())) { return false; } vecLoaded.append(oArray[nIndex].GetDouble()); } vecValues.swap(vecLoaded); return true; } bool writeCurve(QFile& oFile, const QVector >& vecCurve) { const quint32 nSeriesCount = static_cast(vecCurve.size()); if(!writePod(oFile, nSeriesCount)) { return false; } for(int nSeries = 0; nSeries < vecCurve.size(); ++nSeries) { const QVector& vecValues = vecCurve[nSeries]; const quint64 nPointCount = static_cast(vecValues.size()); quint64 nBytes = 0; if(!safeMultiply(nPointCount, sizeof(double), nBytes) || !writePod(oFile, nPointCount) || (nBytes > 0 && !writeAll(oFile, reinterpret_cast(vecValues.constData()), nBytes))) { return false; } } return true; } bool readCurve(QFile& oFile, QVector >* pCurve, quint64& nTotalPoints) { quint32 nSeriesCount = 0; if(!readPod(oFile, nSeriesCount) || nSeriesCount > g_nMaximumCurveSeries) { return false; } QVector > vecLoaded; if(pCurve != NULL) { vecLoaded.resize(static_cast(nSeriesCount)); } for(quint32 nSeries = 0; nSeries < nSeriesCount; ++nSeries) { quint64 nPointCount = 0; quint64 nBytes = 0; if(!readPod(oFile, nPointCount) || nPointCount > g_nMaximumCurvePoints || nTotalPoints > g_nMaximumCurvePoints - nPointCount || !safeMultiply(nPointCount, sizeof(double), nBytes) || nBytes > static_cast(oFile.bytesAvailable()) || nPointCount > static_cast(INT_MAX)) { return false; } nTotalPoints += nPointCount; if(pCurve == NULL) { // validate() 不构造完整曲线,但仍必须真实读取并检查所有数值。 // 固定大小缓冲避免损坏文件利用声明长度造成额外大块分配。 double aBuffer[g_nCurveValidationBufferValues]; quint64 nRemaining = nPointCount; while(nRemaining > 0) { const int nChunk = static_cast(qMin( nRemaining, static_cast( g_nCurveValidationBufferValues))); const quint64 nChunkBytes = static_cast(nChunk) * sizeof(double); if(!readAll(oFile, reinterpret_cast(aBuffer), nChunkBytes)) { return false; } for(int nIndex = 0; nIndex < nChunk; ++nIndex) { if(!isFiniteValue(aBuffer[nIndex])) { return false; } } nRemaining -= static_cast(nChunk); } } else { QVector& vecValues = vecLoaded[static_cast(nSeries)]; vecValues.resize(static_cast(nPointCount)); if(nBytes > 0 && !readAll(oFile, reinterpret_cast(vecValues.data()), nBytes)) { return false; } for(int nIndex = 0; nIndex < vecValues.size(); ++nIndex) { if(!isFiniteValue(vecValues[nIndex])) { return false; } } } } if(pCurve != NULL) { pCurve->swap(vecLoaded); } return true; } bool readPressureFrames(const QString& sFilePath, quint64 nExpectedCellCount, quint32 nExpectedFrameCount, nmPebiResultSnapshotBuilder* pBuilder, QString* pError, double* pActualMinimum = NULL, double* pActualMaximum = NULL) { const QFileInfo oInfo(sFilePath); if(!oInfo.isFile() || oInfo.size() < 0 || static_cast(oInfo.size()) > nmNumericalResultPersistence::maximumBinaryPayloadBytes()) { return setError(pError, "PEBI pressure payload is missing or too large."); } QFile oFile(sFilePath); if(!oFile.open(QIODevice::ReadOnly)) { return setError(pError, "Cannot open PEBI pressure frame file."); } char aMagic[8] = { 0 }; quint32 nVersion = 0; quint32 nEndian = 0; quint64 nCellCount = 0; quint32 nFrameCount = 0; quint64 nFrameBytes = 0; quint64 nPerFrameBytes = 0; quint64 nPayloadBytes = 0; quint64 nExpectedBytes = 0; const quint64 nHeaderBytes = 8 + sizeof(quint32) + sizeof(quint32) + sizeof(quint64) + sizeof(quint32); bool bOk = readAll(oFile, aMagic, 8) && hasMagic(aMagic, g_aPressureMagic) && readPod(oFile, nVersion) && nVersion == g_nBinaryFormatVersion && readPod(oFile, nEndian) && nEndian == g_nEndianMarker && readPod(oFile, nCellCount) && nCellCount > 0 && nCellCount <= g_nMaximumCellCount && readPod(oFile, nFrameCount) && nFrameCount > 0 && nFrameCount <= g_nMaximumFrameCount && nCellCount == nExpectedCellCount && nFrameCount == nExpectedFrameCount && safeMultiply(nCellCount, sizeof(double), nFrameBytes) && safeAdd(sizeof(double), nFrameBytes, nPerFrameBytes) && safeMultiply(nPerFrameBytes, nFrameCount, nPayloadBytes) && safeAdd(nHeaderBytes, nPayloadBytes, nExpectedBytes) && nExpectedBytes == static_cast(oInfo.size()); double dPreviousTime = 0.0; double dActualMinimum = DBL_MAX; double dActualMaximum = -DBL_MAX; for(quint32 nFrame = 0; bOk && nFrame < nFrameCount; ++nFrame) { double dTime = 0.0; bOk = readPod(oFile, dTime) && isFiniteValue(dTime) && (nFrame == 0 || dTime > dPreviousTime); if(!bOk) { break; } if(pBuilder == NULL) { // 发布前验证必须真正读取压力值,不能通过 seek 跳过损坏或非有限数据。 double aBuffer[1024]; quint64 nRemaining = nCellCount; while(bOk && nRemaining > 0) { const quint64 nChunkValues = qMin( static_cast(1024), nRemaining); const quint64 nChunkBytes = nChunkValues * sizeof(double); bOk = readAll(oFile, reinterpret_cast(aBuffer), nChunkBytes); for(quint64 nCell = 0; bOk && nCell < nChunkValues; ++nCell) { bOk = isFiniteValue(aBuffer[nCell]); dActualMinimum = qMin(dActualMinimum, aBuffer[nCell]); dActualMaximum = qMax(dActualMaximum, aBuffer[nCell]); } nRemaining -= nChunkValues; } } else { vtkSmartPointer pPressure = vtkSmartPointer::New(); pPressure->SetName("p"); pPressure->SetNumberOfComponents(1); pPressure->SetNumberOfTuples(static_cast(nCellCount)); double* pValues = pPressure->GetPointer(0); bOk = pValues != NULL && pPressure->GetNumberOfTuples() == static_cast(nCellCount) && readAll(oFile, reinterpret_cast(pValues), nFrameBytes); for(quint64 nCell = 0; bOk && nCell < nCellCount; ++nCell) { bOk = isFiniteValue(pValues[nCell]); dActualMinimum = qMin(dActualMinimum, pValues[nCell]); dActualMaximum = qMax(dActualMaximum, pValues[nCell]); } bOk = bOk && pBuilder->addPressureFrame(dTime, pPressure) && pPressure == NULL; } dPreviousTime = dTime; } bOk = bOk && oFile.atEnd(); oFile.close(); if(!bOk || dActualMinimum == DBL_MAX || dActualMaximum == -DBL_MAX) { return setError(pError, "PEBI pressure frame file is damaged."); } if(pActualMinimum != NULL) { *pActualMinimum = dActualMinimum; } if(pActualMaximum != NULL) { *pActualMaximum = dActualMaximum; } return true; } bool readStaticArrayValues(QFile& oFile, quint64 nValueCount, QVector* pValues) { quint64 nBytes = 0; if(!safeMultiply(nValueCount, sizeof(double), nBytes)) { return false; } if(pValues != NULL) { pValues->resize(static_cast(nValueCount)); if(nBytes > 0 && !readAll(oFile, reinterpret_cast(pValues->data()), nBytes)) { return false; } for(int nIndex = 0; nIndex < pValues->size(); ++nIndex) { if(!isFiniteValue((*pValues)[nIndex])) { return false; } } return true; } double aBuffer[1024]; quint64 nRemaining = nValueCount; while(nRemaining > 0) { const quint64 nChunkValues = qMin( static_cast(1024), nRemaining); const quint64 nChunkBytes = nChunkValues * sizeof(double); if(!readAll(oFile, reinterpret_cast(aBuffer), nChunkBytes)) { return false; } for(quint64 nIndex = 0; nIndex < nChunkValues; ++nIndex) { if(!isFiniteValue(aBuffer[nIndex])) { return false; } } nRemaining -= nChunkValues; } return true; } bool readStaticProperties( const QString& sFilePath, quint64 nExpectedOriginalCellCount, nmPebiStaticPropertyValues* pValues, QString* pError) { if(pValues != NULL) { pValues->clear(); } const QFileInfo oInfo(sFilePath); if(!oInfo.isFile() || oInfo.size() <= 0 || static_cast(oInfo.size()) > nmNumericalResultPersistence::maximumBinaryPayloadBytes()) { return setError(pError, "PEBI static property payload is missing or too large."); } QFile oFile(sFilePath); if(!oFile.open(QIODevice::ReadOnly)) { return setError(pError, "Cannot open PEBI static property file."); } char aMagic[8] = { 0 }; quint32 nVersion = 0; quint32 nEndian = 0; quint64 nOriginalCellCount = 0; quint32 nPropertyCount = 0; if(!readAll(oFile, aMagic, 8) || !hasMagic(aMagic, g_aStaticMagic) || !readPod(oFile, nVersion) || nVersion != g_nBinaryFormatVersion || !readPod(oFile, nEndian) || nEndian != g_nEndianMarker || !readPod(oFile, nOriginalCellCount) || nOriginalCellCount == 0 || nOriginalCellCount > g_nMaximumCellCount || nOriginalCellCount > static_cast(INT_MAX) || nOriginalCellCount != nExpectedOriginalCellCount || !readPod(oFile, nPropertyCount) || nPropertyCount != g_nStaticPropertyCount) { return setError(pError, "PEBI static property header is invalid."); } nmPebiStaticPropertyValues oLoadedValues; QSet setPropertyIds; for(quint32 nPropertyIndex = 0; nPropertyIndex < nPropertyCount; ++nPropertyIndex) { quint32 nPropertyIdBytes = 0; if(!readPod(oFile, nPropertyIdBytes) || nPropertyIdBytes == 0 || nPropertyIdBytes > g_nMaximumPropertyIdBytes) { return setError(pError, "PEBI static property ID is invalid."); } QByteArray baPropertyId; baPropertyId.resize(static_cast(nPropertyIdBytes)); if(!readAll(oFile, baPropertyId.data(), nPropertyIdBytes)) { return setError(pError, "PEBI static property ID is incomplete."); } const QString sPropertyId = QString::fromLatin1( baPropertyId.constData(), baPropertyId.size()); quint64 nElementCount = 0; if(setPropertyIds.contains(sPropertyId) || !readPod(oFile, nElementCount) || nElementCount != nOriginalCellCount) { return setError(pError, "PEBI static property dimensions are invalid."); } QVector* pTargetValues = NULL; if(sPropertyId == QString::fromLatin1("permeability")) { pTargetValues = pValues == NULL ? NULL : &oLoadedValues.m_vecPermeabilityMilliDarcy; } else if(sPropertyId == QString::fromLatin1("porosity")) { pTargetValues = pValues == NULL ? NULL : &oLoadedValues.m_vecPorosity; } else if(sPropertyId == QString::fromLatin1("thickness")) { pTargetValues = pValues == NULL ? NULL : &oLoadedValues.m_vecThicknessMeters; } else { return setError(pError, "PEBI static property file contains an unknown ID."); } quint64 nValueBytes = 0; quint64 nValueEnd = 0; if(!safeMultiply(nElementCount, sizeof(double), nValueBytes) || oFile.pos() < 0 || !safeAdd(static_cast(oFile.pos()), nValueBytes, nValueEnd) || nValueEnd > static_cast(oInfo.size()) || !readStaticArrayValues(oFile, nElementCount, pTargetValues)) { return setError(pError, "PEBI static property values are invalid."); } setPropertyIds.insert(sPropertyId); } if(setPropertyIds.size() != static_cast(g_nStaticPropertyCount) || !setPropertyIds.contains(QString::fromLatin1("permeability")) || !setPropertyIds.contains(QString::fromLatin1("porosity")) || !setPropertyIds.contains(QString::fromLatin1("thickness")) || oFile.pos() != oInfo.size() || (pValues != NULL && !oLoadedValues.isValidForCellCount( static_cast(nOriginalCellCount)))) { return setError(pError, "PEBI static property payload is inconsistent."); } if(pValues != NULL) { qSwap(pValues->m_vecPermeabilityMilliDarcy, oLoadedValues.m_vecPermeabilityMilliDarcy); qSwap(pValues->m_vecPorosity, oLoadedValues.m_vecPorosity); qSwap(pValues->m_vecThicknessMeters, oLoadedValues.m_vecThicknessMeters); } return true; } bool writeStaticPropertyArray(QFile& oFile, const char* pPropertyId, const QVector& vecValues, quint64 nExpectedCount) { if(pPropertyId == NULL || static_cast(vecValues.size()) != nExpectedCount) { return false; } const QByteArray baPropertyId(pPropertyId); const quint32 nPropertyIdBytes = static_cast(baPropertyId.size()); quint64 nValueBytes = 0; if(nPropertyIdBytes == 0 || nPropertyIdBytes > g_nMaximumPropertyIdBytes || !safeMultiply(nExpectedCount, sizeof(double), nValueBytes)) { return false; } for(int nIndex = 0; nIndex < vecValues.size(); ++nIndex) { if(!isFiniteValue(vecValues[nIndex])) { return false; } } return writePod(oFile, nPropertyIdBytes) && writeAll(oFile, baPropertyId.constData(), nPropertyIdBytes) && writePod(oFile, nExpectedCount) && writeAll(oFile, reinterpret_cast(vecValues.constData()), nValueBytes); } bool readWellCurves(const QString& sFilePath, quint32 nExpectedWellCount, QMap* pCurves, QSet* pWellIds, QString* pError) { if(pWellIds != NULL) { pWellIds->clear(); } const QFileInfo oInfo(sFilePath); if(!oInfo.isFile() || oInfo.size() < 0 || static_cast(oInfo.size()) > nmNumericalResultPersistence::maximumBinaryPayloadBytes()) { return setError(pError, "PEBI well curve payload is missing or too large."); } QFile oFile(sFilePath); if(!oFile.open(QIODevice::ReadOnly)) { return setError(pError, "Cannot open PEBI well curve file."); } char aMagic[8] = { 0 }; quint32 nVersion = 0; quint32 nEndian = 0; quint32 nWellCount = 0; QMap mapLoaded; QSet setWellIds; quint64 nTotalPoints = 0; bool bOk = readAll(oFile, aMagic, 8) && hasMagic(aMagic, g_aCurvesMagic) && readPod(oFile, nVersion) && nVersion == g_nBinaryFormatVersion && readPod(oFile, nEndian) && nEndian == g_nEndianMarker && readPod(oFile, nWellCount) && nWellCount <= g_nMaximumWellCount && nWellCount == nExpectedWellCount; for(quint32 nWell = 0; bOk && nWell < nWellCount; ++nWell) { quint32 nUuidBytes = 0; bOk = readPod(oFile, nUuidBytes) && nUuidBytes > 0 && nUuidBytes <= g_nMaximumUuidBytes && nUuidBytes <= static_cast(oFile.bytesAvailable()); QByteArray baUuid; if(bOk) { baUuid.resize(static_cast(nUuidBytes)); bOk = readAll(oFile, baUuid.data(), nUuidBytes); } const QString sUuid = QString::fromUtf8( baUuid.constData(), baUuid.size()); bOk = bOk && !sUuid.isEmpty() && !QUuid(sUuid).isNull() && !setWellIds.contains(sUuid); nmPebiResultWellCurves oCurves; bOk = bOk && readCurve(oFile, pCurves == NULL ? NULL : &oCurves.m_vecHistoryPressure, nTotalPoints) && readCurve(oFile, pCurves == NULL ? NULL : &oCurves.m_vecHistoryLogLog, nTotalPoints) && readCurve(oFile, pCurves == NULL ? NULL : &oCurves.m_vecHistorySemiLog, nTotalPoints) && readCurve(oFile, pCurves == NULL ? NULL : &oCurves.m_vecResultPressure, nTotalPoints) && readCurve(oFile, pCurves == NULL ? NULL : &oCurves.m_vecResultLogLog, nTotalPoints) && readCurve(oFile, pCurves == NULL ? NULL : &oCurves.m_vecResultSemiLog, nTotalPoints); if(bOk) { setWellIds.insert(sUuid); if(pCurves != NULL) { mapLoaded.insert(sUuid, oCurves); } } } bOk = bOk && oFile.atEnd() && setWellIds.size() == nExpectedWellCount; oFile.close(); if(!bOk) { return setError(pError, "PEBI well curve file is damaged."); } if(pCurves != NULL) { pCurves->swap(mapLoaded); } if(pWellIds != NULL) { *pWellIds = setWellIds; } return true; } bool readRequiredInt(const rapidjson::Value& oParent, const char* pName, int& nValue) { if(!oParent.IsObject() || !oParent.HasMember(pName) || !oParent[pName].IsInt()) { return false; } nValue = oParent[pName].GetInt(); return true; } bool readRequiredDouble(const rapidjson::Value& oParent, const char* pName, double& dValue) { if(!oParent.IsObject() || !oParent.HasMember(pName) || !oParent[pName].IsNumber() || !isFiniteValue(oParent[pName].GetDouble())) { return false; } dValue = oParent[pName].GetDouble(); return true; } bool readRequiredBool(const rapidjson::Value& oParent, const char* pName, bool& bValue) { if(!oParent.IsObject() || !oParent.HasMember(pName) || !oParent[pName].IsBool()) { return false; } bValue = oParent[pName].GetBool(); return true; } bool readJsonWellIds(const rapidjson::Document& oDocument, quint32 nExpectedWellCount, QSet& setWellIds) { setWellIds.clear(); if(!oDocument.IsObject() || !oDocument.HasMember("Wells") || !oDocument["Wells"].IsArray() || oDocument["Wells"].Size() != nExpectedWellCount) { return false; } QSet setLoaded; const rapidjson::Value& oWells = oDocument["Wells"]; for(rapidjson::SizeType nIndex = 0; nIndex < oWells.Size(); ++nIndex) { const rapidjson::Value& oWell = oWells[nIndex]; if(!oWell.IsObject() || !oWell.HasMember("WellInstanceId") || !oWell["WellInstanceId"].IsString()) { return false; } const rapidjson::Value& oId = oWell["WellInstanceId"]; const QString sWellId = QString::fromUtf8( oId.GetString(), static_cast(oId.GetStringLength())); if(sWellId.isEmpty() || QUuid(sWellId).isNull() || setLoaded.contains(sWellId)) { return false; } setLoaded.insert(sWellId); } setWellIds = setLoaded; return true; } bool parseSnapshotJson( const QString& sSnapshotJsonPath, rapidjson::Document& oDocument, nmNumericalFileReference& oGridReference, nmNumericalFileReference& oStaticReference, nmNumericalFileReference& oPressureReference, nmNumericalFileReference& oCurvesReference, quint64& nCellCount, quint64& nOriginalCellCount, quint32& nFrameCount, quint32& nWellCount, QString* pError) { const QFileInfo oInfo(sSnapshotJsonPath); if(!oInfo.isFile() || oInfo.size() <= 0 || static_cast(oInfo.size()) > g_nMaximumSnapshotJsonBytes || !nmDataJsonTools::ReadDomFromFile(sSnapshotJsonPath, oDocument)) { return setError(pError, "Snapshot.json is missing, too large or invalid."); } if(!oDocument.IsObject() || !oDocument.HasMember("SnapshotFormatVersion") || !oDocument["SnapshotFormatVersion"].IsInt()) { return setError(pError, "Snapshot.json format version is missing or invalid."); } const int nSnapshotVersion = oDocument["SnapshotFormatVersion"].GetInt(); if(nSnapshotVersion != g_nSnapshotFormatVersion) { return setError(pError, QString::fromLatin1( "Unsupported snapshot format version %1.") .arg(nSnapshotVersion)); } if(!oDocument.HasMember("OriginalCellCount") || !oDocument["OriginalCellCount"].IsUint64() || !oDocument.HasMember("ResultCellCount") || !oDocument["ResultCellCount"].IsUint64() || !oDocument.HasMember("PressureFrameCount") || !oDocument["PressureFrameCount"].IsUint() || !oDocument.HasMember("WellCount") || !oDocument["WellCount"].IsUint() || !readFileReference(oDocument, "ResultGrid", oGridReference) || !readFileReference(oDocument, "StaticProperties", oStaticReference) || !readFileReference(oDocument, "PressureFrames", oPressureReference) || !readFileReference(oDocument, "WellCurves", oCurvesReference)) { return setError(pError, "Snapshot.json header or file references are invalid."); } if(!validatePropertyCatalog(oDocument, pError)) { return false; } nCellCount = oDocument["ResultCellCount"].GetUint64(); nOriginalCellCount = oDocument["OriginalCellCount"].GetUint64(); nFrameCount = oDocument["PressureFrameCount"].GetUint(); nWellCount = oDocument["WellCount"].GetUint(); if(nCellCount == 0 || nCellCount > g_nMaximumCellCount || nOriginalCellCount == 0 || nOriginalCellCount > g_nMaximumCellCount || nOriginalCellCount > static_cast(INT_MAX) || nOriginalCellCount < nCellCount || nFrameCount == 0 || nFrameCount > g_nMaximumFrameCount || nWellCount > g_nMaximumWellCount) { return setError(pError, "Snapshot.json declares invalid or duplicate payloads."); } QSet setPayloadPaths; setPayloadPaths.insert(oGridReference.m_sRelativePath.toLower()); setPayloadPaths.insert(oStaticReference.m_sRelativePath.toLower()); setPayloadPaths.insert(oPressureReference.m_sRelativePath.toLower()); setPayloadPaths.insert(oCurvesReference.m_sRelativePath.toLower()); if(setPayloadPaths.size() != 4) { return setError(pError, "Snapshot.json declares invalid or duplicate payloads."); } return true; } } bool nmPebiResultSnapshotSerializer::writeStaticProperties( const nmPebiResultSnapshot& oSnapshot, const QString& sFilePath, QString* pError) { const int nOriginalCellCount = oSnapshot.m_oStaticProperties.m_vecPermeabilityMilliDarcy.size(); if(nOriginalCellCount <= 0 || nOriginalCellCount > static_cast(g_nMaximumCellCount) || !oSnapshot.m_oStaticProperties.isValidForCellCount( nOriginalCellCount)) { return setError(pError, "Cannot save invalid PEBI static properties."); } const char* aPropertyIds[3] = { "permeability", "porosity", "thickness" }; quint64 nValueBytes = 0; quint64 nExpectedBytes = 8 + sizeof(quint32) + sizeof(quint32) + sizeof(quint64) + sizeof(quint32); if(!safeMultiply(static_cast(nOriginalCellCount), sizeof(double), nValueBytes)) { return setError(pError, "PEBI static property payload size overflows."); } for(int nIndex = 0; nIndex < 3; ++nIndex) { quint64 nArrayBytes = 0; quint64 nNextBytes = 0; const quint64 nIdBytes = static_cast( QByteArray(aPropertyIds[nIndex]).size()); if(!safeAdd(sizeof(quint32), nIdBytes, nArrayBytes) || !safeAdd(nArrayBytes, sizeof(quint64), nArrayBytes) || !safeAdd(nArrayBytes, nValueBytes, nArrayBytes) || !safeAdd(nExpectedBytes, nArrayBytes, nNextBytes)) { return setError(pError, "PEBI static property payload size overflows."); } nExpectedBytes = nNextBytes; } if(nExpectedBytes > nmNumericalResultPersistence::maximumBinaryPayloadBytes()) { return setError(pError, "PEBI static property payload is too large."); } QFile oFile(sFilePath); if(!oFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { return setError(pError, "Cannot create PEBI static property file."); } const quint64 nCount = static_cast(nOriginalCellCount); bool bOk = writeAll(oFile, g_aStaticMagic, 8) && writePod(oFile, g_nBinaryFormatVersion) && writePod(oFile, g_nEndianMarker) && writePod(oFile, nCount) && writePod(oFile, g_nStaticPropertyCount) && writeStaticPropertyArray(oFile, aPropertyIds[0], oSnapshot.m_oStaticProperties. m_vecPermeabilityMilliDarcy, nCount) && writeStaticPropertyArray(oFile, aPropertyIds[1], oSnapshot.m_oStaticProperties.m_vecPorosity, nCount) && writeStaticPropertyArray(oFile, aPropertyIds[2], oSnapshot.m_oStaticProperties.m_vecThicknessMeters, nCount) && oFile.flush() && static_cast(oFile.size()) == nExpectedBytes; oFile.close(); if(!bOk) { QFile::remove(sFilePath); return setError(pError, "Cannot write complete PEBI static property file."); } return true; } bool nmPebiResultSnapshotSerializer::writePressureFrames( const nmPebiResultSnapshot& oSnapshot, const QString& sFilePath, QString* pError) { QFile oFile(sFilePath); if(!oFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { return setError(pError, "Cannot create PEBI pressure frame file."); } const quint64 nCellCount = static_cast( oSnapshot.m_pResultGrid->GetNumberOfCells()); const quint32 nFrameCount = static_cast( oSnapshot.m_vecPressureFrames.size()); bool bOk = writeAll(oFile, g_aPressureMagic, 8) && writePod(oFile, g_nBinaryFormatVersion) && writePod(oFile, g_nEndianMarker) && writePod(oFile, nCellCount) && writePod(oFile, nFrameCount); quint64 nFrameBytes = 0; bOk = bOk && safeMultiply(nCellCount, sizeof(double), nFrameBytes); for(int nIndex = 0; bOk && nIndex < oSnapshot.m_vecPressureFrames.size(); ++nIndex) { const nmPebiResultSnapshot::PressureFrame& oFrame = oSnapshot.m_vecPressureFrames[nIndex]; double* pValues = oFrame.m_pPressure == NULL ? NULL : oFrame.m_pPressure->GetPointer(0); bOk = pValues != NULL && writePod(oFile, oFrame.m_dTime) && writeAll(oFile, reinterpret_cast(pValues), nFrameBytes); } bOk = bOk && oFile.flush(); oFile.close(); if(!bOk) { QFile::remove(sFilePath); return setError(pError, "Cannot write complete PEBI pressure frame file."); } return true; } bool nmPebiResultSnapshotSerializer::writeWellCurves( const nmPebiResultSnapshot& oSnapshot, const QString& sFilePath, QString* pError) { QFile oFile(sFilePath); if(!oFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { return setError(pError, "Cannot create PEBI well curve file."); } const quint32 nWellCount = static_cast(oSnapshot.m_vecWells.size()); bool bOk = writeAll(oFile, g_aCurvesMagic, 8) && writePod(oFile, g_nBinaryFormatVersion) && writePod(oFile, g_nEndianMarker) && writePod(oFile, nWellCount); for(int nIndex = 0; bOk && nIndex < oSnapshot.m_vecWells.size(); ++nIndex) { const nmPebiResultWellSnapshot& oWell = oSnapshot.m_vecWells[nIndex]; const QByteArray baUuid = oWell.m_sWellInstanceId.toUtf8(); const quint32 nUuidBytes = static_cast(baUuid.size()); bOk = nUuidBytes > 0 && nUuidBytes <= g_nMaximumUuidBytes && writePod(oFile, nUuidBytes) && writeAll(oFile, baUuid.constData(), nUuidBytes) && writeCurve(oFile, oWell.m_oCurves.m_vecHistoryPressure) && writeCurve(oFile, oWell.m_oCurves.m_vecHistoryLogLog) && writeCurve(oFile, oWell.m_oCurves.m_vecHistorySemiLog) && writeCurve(oFile, oWell.m_oCurves.m_vecResultPressure) && writeCurve(oFile, oWell.m_oCurves.m_vecResultLogLog) && writeCurve(oFile, oWell.m_oCurves.m_vecResultSemiLog); } bOk = bOk && oFile.flush(); oFile.close(); if(!bOk) { QFile::remove(sFilePath); return setError(pError, "Cannot write complete PEBI well curve file."); } return true; } bool nmPebiResultSnapshotSerializer::save( const QSharedPointer& pSnapshot, const QString& sWindowDirectory, nmNumericalFileReference& oSnapshotJsonReference, QString* pError) { if(pError != NULL) { pError->clear(); } if(pSnapshot.isNull() || !pSnapshot->m_bComplete || pSnapshot->m_pResultGrid == NULL) { return setError(pError, "Cannot save an incomplete PEBI result snapshot."); } try { const QString sSnapshotDirectory = QDir(sWindowDirectory).filePath("Snapshot"); if(!QDir().mkpath(sSnapshotDirectory)) { return setError(pError, "Cannot create PEBI snapshot directory."); } const QString sGridPath = QDir(sWindowDirectory).filePath( "Snapshot/ResultGrid.vtu"); const QString sStaticPath = QDir(sWindowDirectory).filePath( "Snapshot/StaticProperties.bin"); const QString sPressurePath = QDir(sWindowDirectory).filePath( "Snapshot/PressureFrames.bin"); const QString sCurvesPath = QDir(sWindowDirectory).filePath( "Snapshot/WellCurves.bin"); const QString sJsonPath = QDir(sWindowDirectory).filePath( "Snapshot/Snapshot.json"); nmNumericalFileReference oGridReference; nmNumericalFileReference oStaticReference; nmNumericalFileReference oPressureReference; nmNumericalFileReference oCurvesReference; if(!nmNumericalResultPersistence::writeGrid( pSnapshot->m_pResultGrid, sGridPath, pError) || !nmNumericalResultPersistence::buildFileReference( sGridPath, "Snapshot/ResultGrid.vtu", oGridReference) || !writeStaticProperties(*pSnapshot, sStaticPath, pError) || !nmNumericalResultPersistence::buildFileReference( sStaticPath, "Snapshot/StaticProperties.bin", oStaticReference) || !writePressureFrames(*pSnapshot, sPressurePath, pError) || !nmNumericalResultPersistence::buildFileReference( sPressurePath, "Snapshot/PressureFrames.bin", oPressureReference) || !writeWellCurves(*pSnapshot, sCurvesPath, pError) || !nmNumericalResultPersistence::buildFileReference( sCurvesPath, "Snapshot/WellCurves.bin", oCurvesReference)) { return false; } rapidjson::Document oDocument; oDocument.SetObject(); rapidjson::Document::AllocatorType& oAllocator = oDocument.GetAllocator(); // V5 冻结原始单元顺序的静态属性及稳定属性目录,不兼容旧快照。 oDocument.AddMember("SnapshotFormatVersion", g_nSnapshotFormatVersion, oAllocator); oDocument.AddMember("SnapshotId", toJsonString(pSnapshot->m_sSnapshotId, oAllocator), oAllocator); oDocument.AddMember("GridInputRevision", pSnapshot->m_nGridInputRevision, oAllocator); oDocument.AddMember("ResultInputRevision", pSnapshot->m_nResultInputRevision, oAllocator); oDocument.AddMember("ResultCellCount", static_cast(pSnapshot->m_pResultGrid->GetNumberOfCells()), oAllocator); oDocument.AddMember("OriginalCellCount", static_cast( pSnapshot->m_oStaticProperties. m_vecPermeabilityMilliDarcy.size()), oAllocator); oDocument.AddMember("PressureFrameCount", static_cast(pSnapshot->m_vecPressureFrames.size()), oAllocator); oDocument.AddMember("WellCount", static_cast(pSnapshot->m_vecWells.size()), oAllocator); oDocument.AddMember("ScalarMinimum", pSnapshot->m_dScalarMin, oAllocator); oDocument.AddMember("ScalarMaximum", pSnapshot->m_dScalarMax, oAllocator); addFileReference(oDocument, "ResultGrid", oGridReference, oAllocator); addFileReference(oDocument, "StaticProperties", oStaticReference, oAllocator); addFileReference(oDocument, "PressureFrames", oPressureReference, oAllocator); addFileReference(oDocument, "WellCurves", oCurvesReference, oAllocator); addPropertyCatalog(oDocument, oAllocator); rapidjson::Value oSlots(rapidjson::kArrayType); for(int nIndex = 0; nIndex < pSnapshot->m_vecSolverSlots.size(); ++nIndex) { const nmPebiResultSolverSlot& oSlot = pSnapshot->m_vecSolverSlots[nIndex]; rapidjson::Value oJson(rapidjson::kObjectType); oJson.AddMember("SolverIndex", oSlot.m_nSolverIndex, oAllocator); oJson.AddMember("EntryKind", static_cast(oSlot.m_eEntryKind), oAllocator); oJson.AddMember("WellInstanceId", toJsonString(oSlot.m_sWellInstanceId, oAllocator), oAllocator); oJson.AddMember("WellCode", toJsonString(oSlot.m_sWellCode, oAllocator), oAllocator); oJson.AddMember("WellType", static_cast(oSlot.m_eWellType), oAllocator); oJson.AddMember("WellCategory", static_cast( oSlot.m_eWellCategory), oAllocator); oSlots.PushBack(oJson, oAllocator); } oDocument.AddMember("SolverSlots", oSlots, oAllocator); rapidjson::Value oWells(rapidjson::kArrayType); for(int nIndex = 0; nIndex < pSnapshot->m_vecWells.size(); ++nIndex) { const nmPebiResultWellSnapshot& oWell = pSnapshot->m_vecWells[nIndex]; rapidjson::Value oJson(rapidjson::kObjectType); oJson.AddMember("WellInstanceId", toJsonString( oWell.m_sWellInstanceId, oAllocator), oAllocator); oJson.AddMember("WellCode", toJsonString( oWell.m_sWellCode, oAllocator), oAllocator); oJson.AddMember("WellName", toJsonString( oWell.m_sWellName, oAllocator), oAllocator); oJson.AddMember("WellType", static_cast(oWell.m_eWellType), oAllocator); oJson.AddMember("WellCategory", static_cast( oWell.m_eWellCategory), oAllocator); oJson.AddMember("WellMode", static_cast(oWell.m_eWellMode), oAllocator); oJson.AddMember("GaugeInputSha1", toJsonString( oWell.m_sGaugeInputSha1, oAllocator), oAllocator); oJson.AddMember("FlowSchedulePhase", static_cast( oWell.m_eFlowSchedulePhase), oAllocator); oJson.AddMember("FlowSectionIndex", oWell.m_nFlowSectionIndex, oAllocator); oJson.AddMember("UseOilRate", oWell.m_bUseOilRate, oAllocator); oJson.AddMember("UseGasRate", oWell.m_bUseGasRate, oAllocator); oJson.AddMember("UseWaterRate", oWell.m_bUseWaterRate, oAllocator); addDoubleVector(oJson, "FlowDurations", oWell.m_vecFlowDurations, oAllocator); addDoubleVector(oJson, "OilRates", oWell.m_vecOilRates, oAllocator); addDoubleVector(oJson, "GasRates", oWell.m_vecGasRates, oAllocator); addDoubleVector(oJson, "WaterRates", oWell.m_vecWaterRates, oAllocator); oJson.AddMember("X", oWell.m_oLocation.x(), oAllocator); oJson.AddMember("Y", oWell.m_oLocation.y(), oAllocator); oJson.AddMember("HasPerforation", oWell.m_bHasPerforation, oAllocator); oJson.AddMember("HasSkin", oWell.m_bHasSkin, oAllocator); oJson.AddMember("HasDfc", oWell.m_bHasDfc, oAllocator); oJson.AddMember("Radius", oWell.m_dRadius, oAllocator); oJson.AddMember("WellboreStorage", oWell.m_dWellboreStorage, oAllocator); oJson.AddMember("Skin", oWell.m_dSkin, oAllocator); oJson.AddMember("Dfc", oWell.m_dDfc, oAllocator); oWells.PushBack(oJson, oAllocator); } oDocument.AddMember("Wells", oWells, oAllocator); rapidjson::Value oDisplayWells(rapidjson::kArrayType); for(int nIndex = 0; nIndex < pSnapshot->m_listDisplayWellInstanceIds.size(); ++nIndex) { oDisplayWells.PushBack(toJsonString( pSnapshot->m_listDisplayWellInstanceIds[nIndex], oAllocator), oAllocator); } oDocument.AddMember("DisplayWellInstanceIds", oDisplayWells, oAllocator); const nmPebiResultReservoirParameters& oReservoir = pSnapshot->m_oReservoirParameters; rapidjson::Value oReservoirJson(rapidjson::kObjectType); oReservoirJson.AddMember("Pi", oReservoir.m_dInitialPressure, oAllocator); oReservoirJson.AddMember("K", oReservoir.m_dPermeability, oAllocator); oReservoirJson.AddMember("h", oReservoir.m_dThickness, oAllocator); oReservoirJson.AddMember("phi", oReservoir.m_dPorosity, oAllocator); oReservoirJson.AddMember("Cti", oReservoir.m_dTotalCompressibility, oAllocator); oReservoirJson.AddMember("Cf", oReservoir.m_dRockCompressibility, oAllocator); oReservoirJson.AddMember("Soi", oReservoir.m_dOilSaturation, oAllocator); oReservoirJson.AddMember("Sgi", oReservoir.m_dGasSaturation, oAllocator); oReservoirJson.AddMember("Swi", oReservoir.m_dWaterSaturation, oAllocator); oDocument.AddMember("Reservoir", oReservoirJson, oAllocator); const nmPebiResultPvtParameters& oPvt = pSnapshot->m_oPvtParameters; rapidjson::Value oPvtJson(rapidjson::kObjectType); oPvtJson.AddMember("HasBubblePoint", oPvt.m_bHasBubblePoint, oAllocator); oPvtJson.AddMember("BubblePoint", oPvt.m_dBubblePoint, oAllocator); oPvtJson.AddMember("ConstantBo", oPvt.m_dConstantBo, oAllocator); oPvtJson.AddMember("ConstantMiuo", oPvt.m_dConstantMiuo, oAllocator); oPvtJson.AddMember("ConstantBg", oPvt.m_dConstantBg, oAllocator); oPvtJson.AddMember("ConstantMiug", oPvt.m_dConstantMiug, oAllocator); oPvtJson.AddMember("ConstantBw", oPvt.m_dConstantBw, oAllocator); oPvtJson.AddMember("ConstantMiuw", oPvt.m_dConstantMiuw, oAllocator); #define NM_ADD_PVT_VECTOR(Name) addDoubleVector(oPvtJson, #Name, oPvt.m_vec##Name, oAllocator) NM_ADD_PVT_VECTOR(Pressure); NM_ADD_PVT_VECTOR(Rso); NM_ADD_PVT_VECTOR(Bo); NM_ADD_PVT_VECTOR(Co); NM_ADD_PVT_VECTOR(Miuo); NM_ADD_PVT_VECTOR(Rouo); NM_ADD_PVT_VECTOR(Rv); NM_ADD_PVT_VECTOR(Bg); NM_ADD_PVT_VECTOR(Cg); NM_ADD_PVT_VECTOR(Miug); NM_ADD_PVT_VECTOR(Roug); NM_ADD_PVT_VECTOR(Z); NM_ADD_PVT_VECTOR(Rsw); NM_ADD_PVT_VECTOR(Bw); NM_ADD_PVT_VECTOR(Cw); NM_ADD_PVT_VECTOR(Miuw); NM_ADD_PVT_VECTOR(Rouw); NM_ADD_PVT_VECTOR(V); NM_ADD_PVT_VECTOR(KkInitial); NM_ADD_PVT_VECTOR(CfCfInitial); NM_ADD_PVT_VECTOR(So); NM_ADD_PVT_VECTOR(Kro); NM_ADD_PVT_VECTOR(Sg); NM_ADD_PVT_VECTOR(Krg); NM_ADD_PVT_VECTOR(Sw); NM_ADD_PVT_VECTOR(Krw); #undef NM_ADD_PVT_VECTOR oDocument.AddMember("Pvt", oPvtJson, oAllocator); const nmPebiResultSolverSettings& oSettings = pSnapshot->m_oSolverSettings; rapidjson::Value oSettingsJson(rapidjson::kObjectType); oSettingsJson.AddMember("SolverModelType", oSettings.m_nSolverModelType, oAllocator); oSettingsJson.AddMember("PebiSolverType", oSettings.m_nPebiSolverType, oAllocator); oSettingsJson.AddMember("OmpThreads", oSettings.m_nOmpThreads, oAllocator); oSettingsJson.AddMember("IluReuseSteps", oSettings.m_nIluReuseSteps, oAllocator); oSettingsJson.AddMember("GridControl", oSettings.m_dGridControl, oAllocator); oSettingsJson.AddMember("HasTimeStepSettings", oSettings.m_bHasTimeStepSettings, oAllocator); oSettingsJson.AddMember("TimeGrowthExponent", oSettings.m_dTimeGrowthExponent, oAllocator); oSettingsJson.AddMember("MinDeltaT", oSettings.m_dMinDeltaT, oAllocator); oSettingsJson.AddMember("MaxDeltaT", oSettings.m_dMaxDeltaT, oAllocator); oDocument.AddMember("SolverSettings", oSettingsJson, oAllocator); if(!nmDataJsonTools::WriteDomToFile(oDocument, sJsonPath) || !nmNumericalResultPersistence::buildFileReference( sJsonPath, "Snapshot/Snapshot.json", oSnapshotJsonReference) || !validate(sWindowDirectory, oSnapshotJsonReference, pError)) { return false; } return true; } catch(const std::bad_alloc&) { return setError(pError, "Not enough memory to save PEBI result snapshot."); } } bool nmPebiResultSnapshotSerializer::validate( const QString& sWindowDirectory, const nmNumericalFileReference& oSnapshotJsonReference, QString* pError) { if(pError != NULL) { pError->clear(); } QString sJsonPath; if(!nmNumericalResultPersistence::validateFileReference( sWindowDirectory, oSnapshotJsonReference, &sJsonPath, pError)) { return false; } rapidjson::Document oDocument; nmNumericalFileReference oGridReference; nmNumericalFileReference oStaticReference; nmNumericalFileReference oPressureReference; nmNumericalFileReference oCurvesReference; quint64 nCellCount = 0; quint64 nOriginalCellCount = 0; quint32 nFrameCount = 0; quint32 nWellCount = 0; if(!parseSnapshotJson(sJsonPath, oDocument, oGridReference, oStaticReference, oPressureReference, oCurvesReference, nCellCount, nOriginalCellCount, nFrameCount, nWellCount, pError)) { return false; } QSet setJsonWellIds; if(!readJsonWellIds(oDocument, nWellCount, setJsonWellIds)) { return setError(pError, "PEBI snapshot well directory is invalid."); } QString sGridPath; QString sStaticPath; QString sPressurePath; QString sCurvesPath; QSet setCurveWellIds; double dActualPressureMinimum = 0.0; double dActualPressureMaximum = 0.0; if(!nmNumericalResultPersistence::validateFileReference( sWindowDirectory, oGridReference, &sGridPath, pError) || !nmNumericalResultPersistence::validateFileReference( sWindowDirectory, oStaticReference, &sStaticPath, pError) || !nmNumericalResultPersistence::validateFileReference( sWindowDirectory, oPressureReference, &sPressurePath, pError) || !nmNumericalResultPersistence::validateFileReference( sWindowDirectory, oCurvesReference, &sCurvesPath, pError) || !readStaticProperties(sStaticPath, nOriginalCellCount, NULL, pError) || !readPressureFrames(sPressurePath, nCellCount, nFrameCount, NULL, pError, &dActualPressureMinimum, &dActualPressureMaximum) || !readWellCurves(sCurvesPath, nWellCount, NULL, &setCurveWellIds, pError)) { return false; } if(setCurveWellIds != setJsonWellIds) { return setError(pError, "PEBI snapshot well curve mapping is inconsistent."); } double dDeclaredPressureMinimum = 0.0; double dDeclaredPressureMaximum = 0.0; const double dRangeScale = qMax(1.0, qMax( fabs(dActualPressureMinimum), fabs(dActualPressureMaximum))); const double dRangeTolerance = dRangeScale * 1.0e-10; if(!readRequiredDouble(oDocument, "ScalarMinimum", dDeclaredPressureMinimum) || !readRequiredDouble(oDocument, "ScalarMaximum", dDeclaredPressureMaximum) || fabs(dDeclaredPressureMinimum - dActualPressureMinimum) > dRangeTolerance || fabs(dDeclaredPressureMaximum - dActualPressureMaximum) > dRangeTolerance) { return setError(pError, "PEBI snapshot pressure range is inconsistent."); } vtkSmartPointer pGrid; QVector vecVtkToOriginal; QVector vecOriginalToVtk; if(!nmNumericalResultPersistence::readGrid(sGridPath, pGrid, pError) || pGrid == NULL || static_cast(pGrid->GetNumberOfCells()) != nCellCount || nmPebiCellMapping::read(pGrid, &vecVtkToOriginal, &vecOriginalToVtk, pError) != nmPebiCellMapping::MappingStatus_Valid || static_cast(vecOriginalToVtk.size()) != nOriginalCellCount) { return setError(pError, pError != NULL && !pError->isEmpty() ? *pError : "PEBI snapshot grid mapping is invalid."); } return true; } bool nmPebiResultSnapshotSerializer::getPayloadRelativePaths( const QString& sWindowDirectory, const nmNumericalFileReference& oSnapshotJsonReference, QStringList& listRelativePaths, QString* pError) { listRelativePaths.clear(); QString sJsonPath; if(!nmNumericalResultPersistence::validateFileReference( sWindowDirectory, oSnapshotJsonReference, &sJsonPath, pError)) { return false; } rapidjson::Document oDocument; nmNumericalFileReference oGridReference; nmNumericalFileReference oStaticReference; nmNumericalFileReference oPressureReference; nmNumericalFileReference oCurvesReference; quint64 nCellCount = 0; quint64 nOriginalCellCount = 0; quint32 nFrameCount = 0; quint32 nWellCount = 0; if(!parseSnapshotJson(sJsonPath, oDocument, oGridReference, oStaticReference, oPressureReference, oCurvesReference, nCellCount, nOriginalCellCount, nFrameCount, nWellCount, pError)) { return false; } listRelativePaths << oGridReference.m_sRelativePath << oStaticReference.m_sRelativePath << oPressureReference.m_sRelativePath << oCurvesReference.m_sRelativePath; return true; } bool nmPebiResultSnapshotSerializer::load( const QString& sWindowDirectory, const nmNumericalFileReference& oSnapshotJsonReference, QSharedPointer& pCandidate, QString* pError) { pCandidate.clear(); if(pError != NULL) { pError->clear(); } try { QString sJsonPath; if(!nmNumericalResultPersistence::validateFileReference( sWindowDirectory, oSnapshotJsonReference, &sJsonPath, pError)) { return false; } rapidjson::Document oDocument; nmNumericalFileReference oGridReference; nmNumericalFileReference oStaticReference; nmNumericalFileReference oPressureReference; nmNumericalFileReference oCurvesReference; quint64 nCellCount = 0; quint64 nOriginalCellCount = 0; quint32 nFrameCount = 0; quint32 nWellCount = 0; if(!parseSnapshotJson(sJsonPath, oDocument, oGridReference, oStaticReference, oPressureReference, oCurvesReference, nCellCount, nOriginalCellCount, nFrameCount, nWellCount, pError)) { return false; } QString sGridPath; QString sStaticPath; QString sPressurePath; QString sCurvesPath; if(!nmNumericalResultPersistence::validateFileReference( sWindowDirectory, oGridReference, &sGridPath, pError) || !nmNumericalResultPersistence::validateFileReference( sWindowDirectory, oStaticReference, &sStaticPath, pError) || !nmNumericalResultPersistence::validateFileReference( sWindowDirectory, oPressureReference, &sPressurePath, pError) || !nmNumericalResultPersistence::validateFileReference( sWindowDirectory, oCurvesReference, &sCurvesPath, pError)) { return false; } vtkSmartPointer pGrid; QMap mapCurves; nmPebiStaticPropertyValues oStaticProperties; QVector vecVtkToOriginal; QVector vecOriginalToVtk; if(!nmNumericalResultPersistence::readGrid(sGridPath, pGrid, pError) || pGrid == NULL || static_cast(pGrid->GetNumberOfCells()) != nCellCount || nmPebiCellMapping::read(pGrid, &vecVtkToOriginal, &vecOriginalToVtk, pError) != nmPebiCellMapping::MappingStatus_Valid || static_cast(vecOriginalToVtk.size()) != nOriginalCellCount || !readStaticProperties(sStaticPath, nOriginalCellCount, &oStaticProperties, pError) || !readWellCurves(sCurvesPath, nWellCount, &mapCurves, NULL, pError)) { return setError(pError, pError != NULL && !pError->isEmpty() ? *pError : "PEBI snapshot grid or curves are invalid."); } nmPebiResultSnapshotBuilder oBuilder; if(!oDocument.HasMember("SnapshotId") || !oDocument["SnapshotId"].IsString() || !oDocument.HasMember("GridInputRevision") || !oDocument["GridInputRevision"].IsUint64() || !oDocument.HasMember("ResultInputRevision") || !oDocument["ResultInputRevision"].IsUint64() || !oBuilder.setSnapshotId(QString::fromUtf8( oDocument["SnapshotId"].GetString())) || !oBuilder.setInputRevisions( oDocument["GridInputRevision"].GetUint64(), oDocument["ResultInputRevision"].GetUint64()) || !oBuilder.takeResultGrid(pGrid) || pGrid != NULL || !oBuilder.takeStaticProperties(oStaticProperties) || !oStaticProperties.m_vecPermeabilityMilliDarcy.isEmpty() || !oStaticProperties.m_vecPorosity.isEmpty() || !oStaticProperties.m_vecThicknessMeters.isEmpty() || !readPressureFrames(sPressurePath, nCellCount, nFrameCount, &oBuilder, pError)) { return setError(pError, pError != NULL && !pError->isEmpty() ? *pError : "PEBI snapshot identity or pressure frames are invalid."); } double dScalarMinimum = 0.0; double dScalarMaximum = 0.0; if(!readRequiredDouble(oDocument, "ScalarMinimum", dScalarMinimum) || !readRequiredDouble(oDocument, "ScalarMaximum", dScalarMaximum) || !oBuilder.setScalarRange(dScalarMinimum, dScalarMaximum) || !oDocument.HasMember("SolverSlots") || !oDocument["SolverSlots"].IsArray() || oDocument["SolverSlots"].Size() > g_nMaximumWellCount + 100000u) { return setError(pError, "PEBI snapshot scalar range or solver slots are invalid."); } const rapidjson::Value& oSlots = oDocument["SolverSlots"]; for(rapidjson::SizeType nIndex = 0; nIndex < oSlots.Size(); ++nIndex) { const rapidjson::Value& oJson = oSlots[nIndex]; nmPebiResultSolverSlot oSlot; int nEntryKind = 0; int nWellType = 0; int nWellCategory = 0; if(!readRequiredInt(oJson, "SolverIndex", oSlot.m_nSolverIndex) || !readRequiredInt(oJson, "EntryKind", nEntryKind) || !readRequiredInt(oJson, "WellType", nWellType) || !readRequiredInt(oJson, "WellCategory", nWellCategory) || !oJson.HasMember("WellInstanceId") || !oJson["WellInstanceId"].IsString() || !oJson.HasMember("WellCode") || !oJson["WellCode"].IsString()) { return setError(pError, "PEBI snapshot solver slot is invalid."); } oSlot.m_eEntryKind = static_cast(nEntryKind); oSlot.m_eWellType = static_cast(nWellType); oSlot.m_eWellCategory = static_cast( nWellCategory); oSlot.m_sWellInstanceId = QString::fromUtf8( oJson["WellInstanceId"].GetString()); oSlot.m_sWellCode = QString::fromUtf8(oJson["WellCode"].GetString()); if(!oBuilder.addSolverSlot(oSlot)) { return setError(pError, "Cannot restore PEBI snapshot solver slot."); } } if(!oDocument.HasMember("Wells") || !oDocument["Wells"].IsArray() || oDocument["Wells"].Size() != nWellCount) { return setError(pError, "PEBI snapshot well directory is invalid."); } const rapidjson::Value& oWells = oDocument["Wells"]; QSet setJsonWellIds; for(rapidjson::SizeType nIndex = 0; nIndex < oWells.Size(); ++nIndex) { const rapidjson::Value& oJson = oWells[nIndex]; nmPebiResultWellSnapshot oWell; int nWellType = 0; int nWellCategory = 0; int nWellMode = 0; int nFlowSchedulePhase = 0; if(!oJson.IsObject() || !oJson.HasMember("WellInstanceId") || !oJson["WellInstanceId"].IsString() || !oJson.HasMember("WellCode") || !oJson["WellCode"].IsString() || !oJson.HasMember("WellName") || !oJson["WellName"].IsString() || !oJson.HasMember("GaugeInputSha1") || !oJson["GaugeInputSha1"].IsString() || oJson["GaugeInputSha1"].GetStringLength() != 40u || !readRequiredInt(oJson, "WellType", nWellType) || !readRequiredInt(oJson, "WellCategory", nWellCategory) || !readRequiredInt(oJson, "WellMode", nWellMode) || !readRequiredInt(oJson, "FlowSchedulePhase", nFlowSchedulePhase) || !readRequiredInt(oJson, "FlowSectionIndex", oWell.m_nFlowSectionIndex) || !readRequiredBool(oJson, "UseOilRate", oWell.m_bUseOilRate) || !readRequiredBool(oJson, "UseGasRate", oWell.m_bUseGasRate) || !readRequiredBool(oJson, "UseWaterRate", oWell.m_bUseWaterRate) || !readDoubleVector(oJson, "FlowDurations", oWell.m_vecFlowDurations) || !readDoubleVector(oJson, "OilRates", oWell.m_vecOilRates) || !readDoubleVector(oJson, "GasRates", oWell.m_vecGasRates) || !readDoubleVector(oJson, "WaterRates", oWell.m_vecWaterRates) || !readRequiredDouble(oJson, "X", oWell.m_oLocation.rx()) || !readRequiredDouble(oJson, "Y", oWell.m_oLocation.ry()) || !readRequiredBool(oJson, "HasPerforation", oWell.m_bHasPerforation) || !readRequiredBool(oJson, "HasSkin", oWell.m_bHasSkin) || !readRequiredBool(oJson, "HasDfc", oWell.m_bHasDfc) || !readRequiredDouble(oJson, "Radius", oWell.m_dRadius) || !readRequiredDouble(oJson, "WellboreStorage", oWell.m_dWellboreStorage) || !readRequiredDouble(oJson, "Skin", oWell.m_dSkin) || !readRequiredDouble(oJson, "Dfc", oWell.m_dDfc)) { return setError(pError, "PEBI snapshot well metadata are invalid."); } oWell.m_sWellInstanceId = QString::fromUtf8( oJson["WellInstanceId"].GetString()); oWell.m_sWellCode = QString::fromUtf8(oJson["WellCode"].GetString()); oWell.m_sWellName = QString::fromUtf8(oJson["WellName"].GetString()); oWell.m_sGaugeInputSha1 = QString::fromLatin1( oJson["GaugeInputSha1"].GetString(), static_cast( oJson["GaugeInputSha1"].GetStringLength())); oWell.m_eWellType = static_cast(nWellType); oWell.m_eWellCategory = static_cast( nWellCategory); oWell.m_eWellMode = static_cast(nWellMode); oWell.m_eFlowSchedulePhase = static_cast( nFlowSchedulePhase); if(setJsonWellIds.contains(oWell.m_sWellInstanceId) || !mapCurves.contains(oWell.m_sWellInstanceId)) { return setError(pError, "PEBI snapshot well curve mapping is invalid."); } setJsonWellIds.insert(oWell.m_sWellInstanceId); oWell.m_oCurves = mapCurves.take(oWell.m_sWellInstanceId); if(!oBuilder.takeWell(oWell)) { return setError(pError, "Cannot restore PEBI snapshot well."); } } if(!mapCurves.isEmpty()) { return setError(pError, "PEBI well curve file contains unreferenced wells."); } if(!oDocument.HasMember("DisplayWellInstanceIds") || !oDocument["DisplayWellInstanceIds"].IsArray()) { return setError(pError, "PEBI display well list is missing."); } QStringList listDisplayWellIds; const rapidjson::Value& oDisplay = oDocument["DisplayWellInstanceIds"]; for(rapidjson::SizeType nIndex = 0; nIndex < oDisplay.Size(); ++nIndex) { if(!oDisplay[nIndex].IsString()) { return setError(pError, "PEBI display well list is invalid."); } listDisplayWellIds.append(QString::fromUtf8( oDisplay[nIndex].GetString())); } if(!oBuilder.setDisplayWellInstanceIds(listDisplayWellIds)) { return setError(pError, "Cannot restore PEBI display well list."); } if(!oDocument.HasMember("Reservoir") || !oDocument["Reservoir"].IsObject()) { return setError(pError, "PEBI reservoir snapshot is missing."); } const rapidjson::Value& oReservoirJson = oDocument["Reservoir"]; nmPebiResultReservoirParameters oReservoir; if(!readRequiredDouble(oReservoirJson, "Pi", oReservoir.m_dInitialPressure) || !readRequiredDouble(oReservoirJson, "K", oReservoir.m_dPermeability) || !readRequiredDouble(oReservoirJson, "h", oReservoir.m_dThickness) || !readRequiredDouble(oReservoirJson, "phi", oReservoir.m_dPorosity) || !readRequiredDouble(oReservoirJson, "Cti", oReservoir.m_dTotalCompressibility) || !readRequiredDouble(oReservoirJson, "Cf", oReservoir.m_dRockCompressibility) || !readRequiredDouble(oReservoirJson, "Soi", oReservoir.m_dOilSaturation) || !readRequiredDouble(oReservoirJson, "Sgi", oReservoir.m_dGasSaturation) || !readRequiredDouble(oReservoirJson, "Swi", oReservoir.m_dWaterSaturation) || !oBuilder.setReservoirParameters(oReservoir)) { return setError(pError, "PEBI reservoir snapshot is invalid."); } if(!oDocument.HasMember("Pvt") || !oDocument["Pvt"].IsObject()) { return setError(pError, "PEBI PVT snapshot is missing."); } const rapidjson::Value& oPvtJson = oDocument["Pvt"]; nmPebiResultPvtParameters oPvt; if(!readRequiredBool(oPvtJson, "HasBubblePoint", oPvt.m_bHasBubblePoint) || !readRequiredDouble(oPvtJson, "BubblePoint", oPvt.m_dBubblePoint) || !readRequiredDouble(oPvtJson, "ConstantBo", oPvt.m_dConstantBo) || !readRequiredDouble(oPvtJson, "ConstantMiuo", oPvt.m_dConstantMiuo) || !readRequiredDouble(oPvtJson, "ConstantBg", oPvt.m_dConstantBg) || !readRequiredDouble(oPvtJson, "ConstantMiug", oPvt.m_dConstantMiug) || !readRequiredDouble(oPvtJson, "ConstantBw", oPvt.m_dConstantBw) || !readRequiredDouble(oPvtJson, "ConstantMiuw", oPvt.m_dConstantMiuw)) { return setError(pError, "PEBI PVT constants are invalid."); } #define NM_READ_PVT_VECTOR(Name) readDoubleVector(oPvtJson, #Name, oPvt.m_vec##Name) if(!NM_READ_PVT_VECTOR(Pressure) || !NM_READ_PVT_VECTOR(Rso) || !NM_READ_PVT_VECTOR(Bo) || !NM_READ_PVT_VECTOR(Co) || !NM_READ_PVT_VECTOR(Miuo) || !NM_READ_PVT_VECTOR(Rouo) || !NM_READ_PVT_VECTOR(Rv) || !NM_READ_PVT_VECTOR(Bg) || !NM_READ_PVT_VECTOR(Cg) || !NM_READ_PVT_VECTOR(Miug) || !NM_READ_PVT_VECTOR(Roug) || !NM_READ_PVT_VECTOR(Z) || !NM_READ_PVT_VECTOR(Rsw) || !NM_READ_PVT_VECTOR(Bw) || !NM_READ_PVT_VECTOR(Cw) || !NM_READ_PVT_VECTOR(Miuw) || !NM_READ_PVT_VECTOR(Rouw) || !NM_READ_PVT_VECTOR(V) || !NM_READ_PVT_VECTOR(KkInitial) || !NM_READ_PVT_VECTOR(CfCfInitial) || !NM_READ_PVT_VECTOR(So) || !NM_READ_PVT_VECTOR(Kro) || !NM_READ_PVT_VECTOR(Sg) || !NM_READ_PVT_VECTOR(Krg) || !NM_READ_PVT_VECTOR(Sw) || !NM_READ_PVT_VECTOR(Krw) || !oBuilder.setPvtParameters(oPvt)) { #undef NM_READ_PVT_VECTOR return setError(pError, "PEBI PVT arrays are invalid."); } #undef NM_READ_PVT_VECTOR if(!oDocument.HasMember("SolverSettings") || !oDocument["SolverSettings"].IsObject()) { return setError(pError, "PEBI solver settings are missing."); } const rapidjson::Value& oSettingsJson = oDocument["SolverSettings"]; nmPebiResultSolverSettings oSettings; if(!readRequiredInt(oSettingsJson, "SolverModelType", oSettings.m_nSolverModelType) || !readRequiredInt(oSettingsJson, "PebiSolverType", oSettings.m_nPebiSolverType) || !readRequiredInt(oSettingsJson, "OmpThreads", oSettings.m_nOmpThreads) || !readRequiredInt(oSettingsJson, "IluReuseSteps", oSettings.m_nIluReuseSteps) || !readRequiredDouble(oSettingsJson, "GridControl", oSettings.m_dGridControl) || !readRequiredBool(oSettingsJson, "HasTimeStepSettings", oSettings.m_bHasTimeStepSettings) || !readRequiredDouble(oSettingsJson, "TimeGrowthExponent", oSettings.m_dTimeGrowthExponent) || !readRequiredDouble(oSettingsJson, "MinDeltaT", oSettings.m_dMinDeltaT) || !readRequiredDouble(oSettingsJson, "MaxDeltaT", oSettings.m_dMaxDeltaT) || !oBuilder.setSolverSettings(oSettings)) { return setError(pError, "PEBI solver settings are invalid."); } QString sBuilderError; if(!oBuilder.finalize(&sBuilderError)) { return setError(pError, sBuilderError); } pCandidate = oBuilder.takeCandidate(); if(pCandidate.isNull()) { return setError(pError, "Cannot take the loaded PEBI snapshot candidate."); } return true; } catch(const std::bad_alloc&) { pCandidate.clear(); return setError(pError, "Not enough memory to load PEBI result snapshot."); } }