You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1966 lines
78 KiB
C++
1966 lines
78 KiB
C++
#include "nmPebiResultSnapshotSerializer.h"
|
|
|
|
#include "nmDataJsonTools.h"
|
|
#include "nmPebiCellMapping.h"
|
|
#include "nmPebiResultPropertyCatalog.h"
|
|
#include "nmPebiResultSnapshot.h"
|
|
#include "nmPebiResultSnapshotBuilder.h"
|
|
|
|
#include <QDir>
|
|
#include <QFile>
|
|
#include <QFileInfo>
|
|
#include <QMap>
|
|
#include <QSet>
|
|
#include <QUuid>
|
|
#include <QtGlobal>
|
|
|
|
#include <float.h>
|
|
#include <limits.h>
|
|
#include <math.h>
|
|
#include <new>
|
|
|
|
#include <vtkCellData.h>
|
|
#include <vtkDoubleArray.h>
|
|
#include <vtkUnstructuredGrid.h>
|
|
|
|
#include <rapidjson/document.h>
|
|
|
|
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<quint64>(0)) / nLeft)
|
|
{
|
|
return false;
|
|
}
|
|
nProduct = nLeft * nRight;
|
|
return true;
|
|
}
|
|
|
|
bool safeAdd(quint64 nLeft, quint64 nRight, quint64& nSum)
|
|
{
|
|
if(nRight > (~static_cast<quint64>(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<qint64>(qMin(
|
|
static_cast<quint64>(g_nStreamChunkBytes),
|
|
nBytes - nWritten));
|
|
if(oFile.write(pData + static_cast<size_t>(nWritten), nChunk) != nChunk)
|
|
{
|
|
return false;
|
|
}
|
|
nWritten += static_cast<quint64>(nChunk);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool readAll(QFile& oFile, char* pData, quint64 nBytes)
|
|
{
|
|
quint64 nRead = 0;
|
|
while(nRead < nBytes)
|
|
{
|
|
const qint64 nChunk = static_cast<qint64>(qMin(
|
|
static_cast<quint64>(g_nStreamChunkBytes),
|
|
nBytes - nRead));
|
|
if(oFile.read(pData + static_cast<size_t>(nRead), nChunk) != nChunk)
|
|
{
|
|
return false;
|
|
}
|
|
nRead += static_cast<quint64>(nChunk);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
template<typename T>
|
|
bool writePod(QFile& oFile, const T& oValue)
|
|
{
|
|
return writeAll(oFile, reinterpret_cast<const char*>(&oValue),
|
|
static_cast<quint64>(sizeof(T)));
|
|
}
|
|
|
|
template<typename T>
|
|
bool readPod(QFile& oFile, T& oValue)
|
|
{
|
|
return readAll(oFile, reinterpret_cast<char*>(&oValue),
|
|
static_cast<quint64>(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<rapidjson::SizeType>(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<int>(oParent[pName].GetStringLength()));
|
|
return true;
|
|
}
|
|
|
|
void addPropertyCatalog(
|
|
rapidjson::Value& oParent,
|
|
rapidjson::Document::AllocatorType& oAllocator)
|
|
{
|
|
rapidjson::Value oProperties(rapidjson::kArrayType);
|
|
const QList<nmPebiResultPropertyDescriptor> 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<nmPebiResultPropertyDescriptor> listDescriptors =
|
|
nmPebiResultPropertyCatalog::descriptors();
|
|
const rapidjson::Value& oProperties = oDocument["Properties"];
|
|
if(oProperties.Size() !=
|
|
static_cast<rapidjson::SizeType>(listDescriptors.size()))
|
|
{
|
|
return setError(pError, "PEBI snapshot property catalog is incomplete.");
|
|
}
|
|
QSet<QString> 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<int>(oJson["Path"].GetStringLength()));
|
|
oReference.m_nLength = oJson["Length"].GetUint64();
|
|
oReference.m_sSha1 = QString::fromLatin1(
|
|
oJson["Sha1"].GetString(),
|
|
static_cast<int>(oJson["Sha1"].GetStringLength()));
|
|
return oReference.isValid();
|
|
}
|
|
|
|
void addDoubleVector(rapidjson::Value& oParent,
|
|
const char* pName,
|
|
const QVector<double>& 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<double>& vecValues)
|
|
{
|
|
if(!oParent.IsObject() || !oParent.HasMember(pName) ||
|
|
!oParent[pName].IsArray() ||
|
|
oParent[pName].Size() > static_cast<rapidjson::SizeType>(10000000))
|
|
{
|
|
return false;
|
|
}
|
|
const rapidjson::Value& oArray = oParent[pName];
|
|
QVector<double> vecLoaded;
|
|
vecLoaded.reserve(static_cast<int>(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<QVector<double> >& vecCurve)
|
|
{
|
|
const quint32 nSeriesCount = static_cast<quint32>(vecCurve.size());
|
|
if(!writePod(oFile, nSeriesCount))
|
|
{
|
|
return false;
|
|
}
|
|
for(int nSeries = 0; nSeries < vecCurve.size(); ++nSeries)
|
|
{
|
|
const QVector<double>& vecValues = vecCurve[nSeries];
|
|
const quint64 nPointCount = static_cast<quint64>(vecValues.size());
|
|
quint64 nBytes = 0;
|
|
if(!safeMultiply(nPointCount, sizeof(double), nBytes) ||
|
|
!writePod(oFile, nPointCount) ||
|
|
(nBytes > 0 && !writeAll(oFile,
|
|
reinterpret_cast<const char*>(vecValues.constData()), nBytes)))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool readCurve(QFile& oFile,
|
|
QVector<QVector<double> >* pCurve,
|
|
quint64& nTotalPoints)
|
|
{
|
|
quint32 nSeriesCount = 0;
|
|
if(!readPod(oFile, nSeriesCount) || nSeriesCount > g_nMaximumCurveSeries)
|
|
{
|
|
return false;
|
|
}
|
|
QVector<QVector<double> > vecLoaded;
|
|
if(pCurve != NULL)
|
|
{
|
|
vecLoaded.resize(static_cast<int>(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<quint64>(oFile.bytesAvailable()) ||
|
|
nPointCount > static_cast<quint64>(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<int>(qMin(
|
|
nRemaining,
|
|
static_cast<quint64>(
|
|
g_nCurveValidationBufferValues)));
|
|
const quint64 nChunkBytes =
|
|
static_cast<quint64>(nChunk) * sizeof(double);
|
|
if(!readAll(oFile, reinterpret_cast<char*>(aBuffer),
|
|
nChunkBytes))
|
|
{
|
|
return false;
|
|
}
|
|
for(int nIndex = 0; nIndex < nChunk; ++nIndex)
|
|
{
|
|
if(!isFiniteValue(aBuffer[nIndex]))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
nRemaining -= static_cast<quint64>(nChunk);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QVector<double>& vecValues = vecLoaded[static_cast<int>(nSeries)];
|
|
vecValues.resize(static_cast<int>(nPointCount));
|
|
if(nBytes > 0 && !readAll(oFile,
|
|
reinterpret_cast<char*>(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<quint64>(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<quint64>(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<quint64>(1024), nRemaining);
|
|
const quint64 nChunkBytes =
|
|
nChunkValues * sizeof(double);
|
|
bOk = readAll(oFile,
|
|
reinterpret_cast<char*>(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<vtkDoubleArray> pPressure =
|
|
vtkSmartPointer<vtkDoubleArray>::New();
|
|
pPressure->SetName("p");
|
|
pPressure->SetNumberOfComponents(1);
|
|
pPressure->SetNumberOfTuples(static_cast<vtkIdType>(nCellCount));
|
|
double* pValues = pPressure->GetPointer(0);
|
|
bOk = pValues != NULL &&
|
|
pPressure->GetNumberOfTuples() ==
|
|
static_cast<vtkIdType>(nCellCount) &&
|
|
readAll(oFile, reinterpret_cast<char*>(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<double>* pValues)
|
|
{
|
|
quint64 nBytes = 0;
|
|
if(!safeMultiply(nValueCount, sizeof(double), nBytes))
|
|
{
|
|
return false;
|
|
}
|
|
if(pValues != NULL)
|
|
{
|
|
pValues->resize(static_cast<int>(nValueCount));
|
|
if(nBytes > 0 && !readAll(oFile,
|
|
reinterpret_cast<char*>(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<quint64>(1024), nRemaining);
|
|
const quint64 nChunkBytes = nChunkValues * sizeof(double);
|
|
if(!readAll(oFile, reinterpret_cast<char*>(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<quint64>(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<quint64>(INT_MAX) ||
|
|
nOriginalCellCount != nExpectedOriginalCellCount ||
|
|
!readPod(oFile, nPropertyCount) ||
|
|
nPropertyCount != g_nStaticPropertyCount)
|
|
{
|
|
return setError(pError,
|
|
"PEBI static property header is invalid.");
|
|
}
|
|
|
|
nmPebiStaticPropertyValues oLoadedValues;
|
|
QSet<QString> 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<int>(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<double>* 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<quint64>(oFile.pos()),
|
|
nValueBytes, nValueEnd) ||
|
|
nValueEnd > static_cast<quint64>(oInfo.size()) ||
|
|
!readStaticArrayValues(oFile, nElementCount, pTargetValues))
|
|
{
|
|
return setError(pError,
|
|
"PEBI static property values are invalid.");
|
|
}
|
|
setPropertyIds.insert(sPropertyId);
|
|
}
|
|
|
|
if(setPropertyIds.size() != static_cast<int>(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<int>(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<double>& vecValues,
|
|
quint64 nExpectedCount)
|
|
{
|
|
if(pPropertyId == NULL ||
|
|
static_cast<quint64>(vecValues.size()) != nExpectedCount)
|
|
{
|
|
return false;
|
|
}
|
|
const QByteArray baPropertyId(pPropertyId);
|
|
const quint32 nPropertyIdBytes =
|
|
static_cast<quint32>(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<const char*>(vecValues.constData()),
|
|
nValueBytes);
|
|
}
|
|
|
|
bool readWellCurves(const QString& sFilePath,
|
|
quint32 nExpectedWellCount,
|
|
QMap<QString, nmPebiResultWellCurves>* pCurves,
|
|
QSet<QString>* pWellIds,
|
|
QString* pError)
|
|
{
|
|
if(pWellIds != NULL)
|
|
{
|
|
pWellIds->clear();
|
|
}
|
|
const QFileInfo oInfo(sFilePath);
|
|
if(!oInfo.isFile() || oInfo.size() < 0 ||
|
|
static_cast<quint64>(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<QString, nmPebiResultWellCurves> mapLoaded;
|
|
QSet<QString> 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<quint32>(oFile.bytesAvailable());
|
|
QByteArray baUuid;
|
|
if(bOk)
|
|
{
|
|
baUuid.resize(static_cast<int>(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<QString>& setWellIds)
|
|
{
|
|
setWellIds.clear();
|
|
if(!oDocument.IsObject() || !oDocument.HasMember("Wells") ||
|
|
!oDocument["Wells"].IsArray() ||
|
|
oDocument["Wells"].Size() != nExpectedWellCount)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
QSet<QString> 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<int>(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<quint64>(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<quint64>(INT_MAX) ||
|
|
nOriginalCellCount < nCellCount ||
|
|
nFrameCount == 0 || nFrameCount > g_nMaximumFrameCount ||
|
|
nWellCount > g_nMaximumWellCount)
|
|
{
|
|
return setError(pError, "Snapshot.json declares invalid or duplicate payloads.");
|
|
}
|
|
QSet<QString> 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<int>(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<quint64>(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<quint64>(
|
|
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<quint64>(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<quint64>(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<quint64>(
|
|
oSnapshot.m_pResultGrid->GetNumberOfCells());
|
|
const quint32 nFrameCount = static_cast<quint32>(
|
|
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<const char*>(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<quint32>(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<quint32>(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<const nmPebiResultSnapshot>& 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<quint64>(pSnapshot->m_pResultGrid->GetNumberOfCells()),
|
|
oAllocator);
|
|
oDocument.AddMember("OriginalCellCount", static_cast<quint64>(
|
|
pSnapshot->m_oStaticProperties.
|
|
m_vecPermeabilityMilliDarcy.size()), oAllocator);
|
|
oDocument.AddMember("PressureFrameCount",
|
|
static_cast<unsigned>(pSnapshot->m_vecPressureFrames.size()),
|
|
oAllocator);
|
|
oDocument.AddMember("WellCount",
|
|
static_cast<unsigned>(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<int>(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<int>(oSlot.m_eWellType), oAllocator);
|
|
oJson.AddMember("WellCategory", static_cast<int>(
|
|
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<int>(oWell.m_eWellType), oAllocator);
|
|
oJson.AddMember("WellCategory", static_cast<int>(
|
|
oWell.m_eWellCategory), oAllocator);
|
|
oJson.AddMember("WellMode", static_cast<int>(oWell.m_eWellMode), oAllocator);
|
|
oJson.AddMember("GaugeInputSha1", toJsonString(
|
|
oWell.m_sGaugeInputSha1, oAllocator), oAllocator);
|
|
oJson.AddMember("FlowSchedulePhase", static_cast<int>(
|
|
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<QString> setJsonWellIds;
|
|
if(!readJsonWellIds(oDocument, nWellCount, setJsonWellIds))
|
|
{
|
|
return setError(pError,
|
|
"PEBI snapshot well directory is invalid.");
|
|
}
|
|
|
|
QString sGridPath;
|
|
QString sStaticPath;
|
|
QString sPressurePath;
|
|
QString sCurvesPath;
|
|
QSet<QString> 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<vtkUnstructuredGrid> pGrid;
|
|
QVector<vtkIdType> vecVtkToOriginal;
|
|
QVector<vtkIdType> vecOriginalToVtk;
|
|
if(!nmNumericalResultPersistence::readGrid(sGridPath, pGrid, pError) ||
|
|
pGrid == NULL ||
|
|
static_cast<quint64>(pGrid->GetNumberOfCells()) != nCellCount ||
|
|
nmPebiCellMapping::read(pGrid, &vecVtkToOriginal,
|
|
&vecOriginalToVtk, pError) !=
|
|
nmPebiCellMapping::MappingStatus_Valid ||
|
|
static_cast<quint64>(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<nmPebiResultSnapshot>& 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<vtkUnstructuredGrid> pGrid;
|
|
QMap<QString, nmPebiResultWellCurves> mapCurves;
|
|
nmPebiStaticPropertyValues oStaticProperties;
|
|
QVector<vtkIdType> vecVtkToOriginal;
|
|
QVector<vtkIdType> vecOriginalToVtk;
|
|
if(!nmNumericalResultPersistence::readGrid(sGridPath, pGrid, pError) ||
|
|
pGrid == NULL ||
|
|
static_cast<quint64>(pGrid->GetNumberOfCells()) != nCellCount ||
|
|
nmPebiCellMapping::read(pGrid, &vecVtkToOriginal,
|
|
&vecOriginalToVtk, pError) !=
|
|
nmPebiCellMapping::MappingStatus_Valid ||
|
|
static_cast<quint64>(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<NM_SOLVER_ENTRY_KIND>(nEntryKind);
|
|
oSlot.m_eWellType = static_cast<NM_WELL_MODEL>(nWellType);
|
|
oSlot.m_eWellCategory = static_cast<NM_WELL_CATEGORY>(
|
|
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<QString> 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<int>(
|
|
oJson["GaugeInputSha1"].GetStringLength()));
|
|
oWell.m_eWellType = static_cast<NM_WELL_MODEL>(nWellType);
|
|
oWell.m_eWellCategory = static_cast<NM_WELL_CATEGORY>(
|
|
nWellCategory);
|
|
oWell.m_eWellMode = static_cast<NM_CASE_WELL_MODE>(nWellMode);
|
|
oWell.m_eFlowSchedulePhase = static_cast<NM_PHASE_TYPE>(
|
|
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.");
|
|
}
|
|
}
|