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.
1189 lines
48 KiB
C++
1189 lines
48 KiB
C++
#include "nmPebiResultSnapshotSerializer.h"
|
|
|
|
#include "nmDataJsonTools.h"
|
|
#include "nmPebiResultSnapshot.h"
|
|
#include "nmPebiResultSnapshotBuilder.h"
|
|
|
|
#include <QDir>
|
|
#include <QFile>
|
|
#include <QFileInfo>
|
|
#include <QMap>
|
|
#include <QSet>
|
|
#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 quint32 g_nBinaryFormatVersion = 1;
|
|
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 quint64 g_nMaximumSnapshotJsonBytes = 64ull * 1024ull * 1024ull;
|
|
const qint64 g_nStreamChunkBytes = 4 * 1024 * 1024;
|
|
|
|
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);
|
|
}
|
|
|
|
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())
|
|
{
|
|
return false;
|
|
}
|
|
oReference.m_sRelativePath = QString::fromUtf8(oJson["Path"].GetString());
|
|
oReference.m_nLength = oJson["Length"].GetUint64();
|
|
oReference.m_sSha1 = QString::fromLatin1(oJson["Sha1"].GetString());
|
|
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)
|
|
{
|
|
if(!oFile.seek(oFile.pos() + static_cast<qint64>(nBytes)))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
if(pCurve != NULL)
|
|
{
|
|
pCurve->swap(vecLoaded);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool readPressureFrames(const QString& sFilePath,
|
|
quint64 nExpectedCellCount,
|
|
quint32 nExpectedFrameCount,
|
|
nmPebiResultSnapshotBuilder* pBuilder,
|
|
QString* pError)
|
|
{
|
|
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;
|
|
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)
|
|
{
|
|
bOk = oFile.seek(oFile.pos() + static_cast<qint64>(nFrameBytes));
|
|
}
|
|
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]);
|
|
}
|
|
bOk = bOk && pBuilder->addPressureFrame(dTime, pPressure) &&
|
|
pPressure == NULL;
|
|
}
|
|
dPreviousTime = dTime;
|
|
}
|
|
bOk = bOk && oFile.atEnd();
|
|
oFile.close();
|
|
return bOk ? true : setError(pError, "PEBI pressure frame file is damaged.");
|
|
}
|
|
|
|
bool readWellCurves(const QString& sFilePath,
|
|
quint32 nExpectedWellCount,
|
|
QMap<QString, nmPebiResultWellCurves>* pCurves,
|
|
QString* pError)
|
|
{
|
|
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);
|
|
bOk = bOk && !sUuid.isEmpty() && !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);
|
|
}
|
|
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 parseSnapshotJson(
|
|
const QString& sSnapshotJsonPath,
|
|
rapidjson::Document& oDocument,
|
|
nmNumericalFileReference& oGridReference,
|
|
nmNumericalFileReference& oPressureReference,
|
|
nmNumericalFileReference& oCurvesReference,
|
|
quint64& nCellCount,
|
|
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() ||
|
|
oDocument["SnapshotFormatVersion"].GetInt() != 2 ||
|
|
!oDocument.HasMember("ResultCellCount") ||
|
|
!oDocument["ResultCellCount"].IsUint64() ||
|
|
!oDocument.HasMember("PressureFrameCount") ||
|
|
!oDocument["PressureFrameCount"].IsUint() ||
|
|
!oDocument.HasMember("WellCount") ||
|
|
!oDocument["WellCount"].IsUint() ||
|
|
!readFileReference(oDocument, "ResultGrid", oGridReference) ||
|
|
!readFileReference(oDocument, "PressureFrames", oPressureReference) ||
|
|
!readFileReference(oDocument, "WellCurves", oCurvesReference))
|
|
{
|
|
return setError(pError, "Snapshot.json header or file references are invalid.");
|
|
}
|
|
nCellCount = oDocument["ResultCellCount"].GetUint64();
|
|
nFrameCount = oDocument["PressureFrameCount"].GetUint();
|
|
nWellCount = oDocument["WellCount"].GetUint();
|
|
if(nCellCount == 0 || nCellCount > g_nMaximumCellCount ||
|
|
nFrameCount == 0 || nFrameCount > g_nMaximumFrameCount ||
|
|
nWellCount > g_nMaximumWellCount ||
|
|
oGridReference.m_sRelativePath == oPressureReference.m_sRelativePath ||
|
|
oGridReference.m_sRelativePath == oCurvesReference.m_sRelativePath ||
|
|
oPressureReference.m_sRelativePath == oCurvesReference.m_sRelativePath)
|
|
{
|
|
return setError(pError, "Snapshot.json declares invalid or duplicate payloads.");
|
|
}
|
|
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 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 oPressureReference;
|
|
nmNumericalFileReference oCurvesReference;
|
|
if(!nmNumericalResultPersistence::writeGrid(
|
|
pSnapshot->m_pResultGrid, sGridPath, pError) ||
|
|
!nmNumericalResultPersistence::buildFileReference(
|
|
sGridPath, "Snapshot/ResultGrid.vtu", oGridReference) ||
|
|
!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();
|
|
// v2 增加井别元数据;压力帧和井曲线二进制格式保持不变。
|
|
oDocument.AddMember("SnapshotFormatVersion", 2, 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("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, "PressureFrames", oPressureReference, oAllocator);
|
|
addFileReference(oDocument, "WellCurves", oCurvesReference, 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("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 oPressureReference;
|
|
nmNumericalFileReference oCurvesReference;
|
|
quint64 nCellCount = 0;
|
|
quint32 nFrameCount = 0;
|
|
quint32 nWellCount = 0;
|
|
if(!parseSnapshotJson(sJsonPath, oDocument, oGridReference,
|
|
oPressureReference, oCurvesReference, nCellCount,
|
|
nFrameCount, nWellCount, pError))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
QString sGridPath;
|
|
QString sPressurePath;
|
|
QString sCurvesPath;
|
|
if(!nmNumericalResultPersistence::validateFileReference(
|
|
sWindowDirectory, oGridReference, &sGridPath, pError) ||
|
|
!nmNumericalResultPersistence::validateFileReference(
|
|
sWindowDirectory, oPressureReference, &sPressurePath, pError) ||
|
|
!nmNumericalResultPersistence::validateFileReference(
|
|
sWindowDirectory, oCurvesReference, &sCurvesPath, pError) ||
|
|
!readPressureFrames(sPressurePath, nCellCount, nFrameCount,
|
|
NULL, pError) ||
|
|
!readWellCurves(sCurvesPath, nWellCount, NULL, pError))
|
|
{
|
|
return false;
|
|
}
|
|
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 oPressureReference;
|
|
nmNumericalFileReference oCurvesReference;
|
|
quint64 nCellCount = 0;
|
|
quint32 nFrameCount = 0;
|
|
quint32 nWellCount = 0;
|
|
if(!parseSnapshotJson(sJsonPath, oDocument, oGridReference,
|
|
oPressureReference, oCurvesReference, nCellCount,
|
|
nFrameCount, nWellCount, pError))
|
|
{
|
|
return false;
|
|
}
|
|
QString sGridPath;
|
|
QString sPressurePath;
|
|
QString sCurvesPath;
|
|
if(!nmNumericalResultPersistence::validateFileReference(
|
|
sWindowDirectory, oGridReference, &sGridPath, pError) ||
|
|
!nmNumericalResultPersistence::validateFileReference(
|
|
sWindowDirectory, oPressureReference, &sPressurePath, pError) ||
|
|
!nmNumericalResultPersistence::validateFileReference(
|
|
sWindowDirectory, oCurvesReference, &sCurvesPath, pError))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
vtkSmartPointer<vtkUnstructuredGrid> pGrid;
|
|
QMap<QString, nmPebiResultWellCurves> mapCurves;
|
|
if(!nmNumericalResultPersistence::readGrid(sGridPath, pGrid, pError) ||
|
|
static_cast<quint64>(pGrid->GetNumberOfCells()) != nCellCount ||
|
|
!readWellCurves(sCurvesPath, nWellCount, &mapCurves, 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 ||
|
|
!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;
|
|
if(!oJson.IsObject() ||
|
|
!oJson.HasMember("WellInstanceId") ||
|
|
!oJson["WellInstanceId"].IsString() ||
|
|
!oJson.HasMember("WellCode") || !oJson["WellCode"].IsString() ||
|
|
!oJson.HasMember("WellName") || !oJson["WellName"].IsString() ||
|
|
!readRequiredInt(oJson, "WellType", nWellType) ||
|
|
!readRequiredInt(oJson, "WellCategory", nWellCategory) ||
|
|
!readRequiredInt(oJson, "WellMode", nWellMode) ||
|
|
!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_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);
|
|
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.");
|
|
}
|
|
}
|