|
|
#include "nmPebiResultSnapshotBuilder.h"
|
|
|
|
|
|
#include <QSet>
|
|
|
#include <QUuid>
|
|
|
#include <QtGlobal>
|
|
|
|
|
|
#include <new>
|
|
|
#include <float.h>
|
|
|
#include <math.h>
|
|
|
|
|
|
#if defined(_MSC_VER)
|
|
|
#else
|
|
|
#include <cmath>
|
|
|
#endif
|
|
|
|
|
|
#include <vtkCellData.h>
|
|
|
#include <vtkDoubleArray.h>
|
|
|
#include <vtkUnstructuredGrid.h>
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
// Builder 只校验内存模型的完整性;持久化文件的数量上限和字节上限
|
|
|
// 由 v3 加载器在分配 VTK/Qt 容器之前单独检查。
|
|
|
bool isFiniteValue(double dValue)
|
|
|
{
|
|
|
#if defined(_MSC_VER)
|
|
|
return _finite(dValue) != 0;
|
|
|
#else
|
|
|
return std::isfinite(dValue);
|
|
|
#endif
|
|
|
}
|
|
|
|
|
|
bool isValidInstanceId(const QString& sInstanceId)
|
|
|
{
|
|
|
return !sInstanceId.isEmpty() && !QUuid(sInstanceId).isNull();
|
|
|
}
|
|
|
|
|
|
bool isFiniteVector(const QVector<double>& vecValues)
|
|
|
{
|
|
|
for(int nIndex = 0; nIndex < vecValues.size(); ++nIndex)
|
|
|
{
|
|
|
if(!isFiniteValue(vecValues[nIndex]))
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool isStrictlyIncreasing(const QVector<double>& vecValues)
|
|
|
{
|
|
|
for(int nIndex = 1; nIndex < vecValues.size(); ++nIndex)
|
|
|
{
|
|
|
if(vecValues[nIndex] <= vecValues[nIndex - 1])
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool isRequiredSeriesValid(const QVector<double>& vecAxis,
|
|
|
const QVector<double>& vecValues)
|
|
|
{
|
|
|
return vecAxis.size() >= 2 &&
|
|
|
vecValues.size() == vecAxis.size();
|
|
|
}
|
|
|
|
|
|
bool isOptionalSeriesValid(const QVector<double>& vecAxis,
|
|
|
const QVector<double>& vecValues)
|
|
|
{
|
|
|
return vecValues.isEmpty() ||
|
|
|
(!vecAxis.isEmpty() && vecValues.size() == vecAxis.size());
|
|
|
}
|
|
|
|
|
|
bool isCurveValid(const QVector<QVector<double> >& vecCurve,
|
|
|
int nMinimumSeries,
|
|
|
bool bRequired)
|
|
|
{
|
|
|
if(vecCurve.isEmpty())
|
|
|
{
|
|
|
return !bRequired;
|
|
|
}
|
|
|
if(vecCurve.size() < nMinimumSeries)
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
const int nPointCount = vecCurve[0].size();
|
|
|
for(int nSeries = 0; nSeries < vecCurve.size(); ++nSeries)
|
|
|
{
|
|
|
if(vecCurve[nSeries].size() != nPointCount ||
|
|
|
!isFiniteVector(vecCurve[nSeries]))
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
// 观察井没有产量制度时,双对数和半对数按固定列数保存空列。
|
|
|
// 必填曲线仍必须包含数据,选填曲线允许“列结构存在但点数为零”。
|
|
|
return nPointCount > 0 || !bRequired;
|
|
|
}
|
|
|
|
|
|
bool areWellCurvesValid(const nmPebiResultWellCurves& oCurves)
|
|
|
{
|
|
|
return isCurveValid(oCurves.m_vecHistoryPressure, 2, false) &&
|
|
|
isCurveValid(oCurves.m_vecHistoryLogLog, 3, false) &&
|
|
|
isCurveValid(oCurves.m_vecHistorySemiLog, 2, false) &&
|
|
|
isCurveValid(oCurves.m_vecResultPressure, 2, true) &&
|
|
|
isCurveValid(oCurves.m_vecResultLogLog, 3, false) &&
|
|
|
isCurveValid(oCurves.m_vecResultSemiLog, 2, false);
|
|
|
}
|
|
|
|
|
|
bool areReservoirParametersFinite(
|
|
|
const nmPebiResultReservoirParameters& oParameters)
|
|
|
{
|
|
|
return isFiniteValue(oParameters.m_dInitialPressure) &&
|
|
|
isFiniteValue(oParameters.m_dPermeability) &&
|
|
|
isFiniteValue(oParameters.m_dThickness) &&
|
|
|
isFiniteValue(oParameters.m_dPorosity) &&
|
|
|
isFiniteValue(oParameters.m_dTotalCompressibility) &&
|
|
|
isFiniteValue(oParameters.m_dRockCompressibility) &&
|
|
|
isFiniteValue(oParameters.m_dOilSaturation) &&
|
|
|
isFiniteValue(oParameters.m_dGasSaturation) &&
|
|
|
isFiniteValue(oParameters.m_dWaterSaturation);
|
|
|
}
|
|
|
|
|
|
bool arePvtParametersFinite(const nmPebiResultPvtParameters& oParameters)
|
|
|
{
|
|
|
if((oParameters.m_bHasBubblePoint &&
|
|
|
!isFiniteValue(oParameters.m_dBubblePoint)) ||
|
|
|
!isFiniteValue(oParameters.m_dConstantBo) ||
|
|
|
!isFiniteValue(oParameters.m_dConstantMiuo) ||
|
|
|
!isFiniteValue(oParameters.m_dConstantBg) ||
|
|
|
!isFiniteValue(oParameters.m_dConstantMiug) ||
|
|
|
!isFiniteValue(oParameters.m_dConstantBw) ||
|
|
|
!isFiniteValue(oParameters.m_dConstantMiuw))
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
return isFiniteVector(oParameters.m_vecPressure) &&
|
|
|
isFiniteVector(oParameters.m_vecRso) &&
|
|
|
isFiniteVector(oParameters.m_vecBo) &&
|
|
|
isFiniteVector(oParameters.m_vecCo) &&
|
|
|
isFiniteVector(oParameters.m_vecMiuo) &&
|
|
|
isFiniteVector(oParameters.m_vecRouo) &&
|
|
|
isFiniteVector(oParameters.m_vecRv) &&
|
|
|
isFiniteVector(oParameters.m_vecBg) &&
|
|
|
isFiniteVector(oParameters.m_vecCg) &&
|
|
|
isFiniteVector(oParameters.m_vecMiug) &&
|
|
|
isFiniteVector(oParameters.m_vecRoug) &&
|
|
|
isFiniteVector(oParameters.m_vecZ) &&
|
|
|
isFiniteVector(oParameters.m_vecRsw) &&
|
|
|
isFiniteVector(oParameters.m_vecBw) &&
|
|
|
isFiniteVector(oParameters.m_vecCw) &&
|
|
|
isFiniteVector(oParameters.m_vecMiuw) &&
|
|
|
isFiniteVector(oParameters.m_vecRouw) &&
|
|
|
isFiniteVector(oParameters.m_vecV) &&
|
|
|
isFiniteVector(oParameters.m_vecKkInitial) &&
|
|
|
isFiniteVector(oParameters.m_vecCfCfInitial) &&
|
|
|
isFiniteVector(oParameters.m_vecSo) &&
|
|
|
isFiniteVector(oParameters.m_vecKro) &&
|
|
|
isFiniteVector(oParameters.m_vecSg) &&
|
|
|
isFiniteVector(oParameters.m_vecKrg) &&
|
|
|
isFiniteVector(oParameters.m_vecSw) &&
|
|
|
isFiniteVector(oParameters.m_vecKrw);
|
|
|
}
|
|
|
|
|
|
bool areOptionalPressureSeriesValid(
|
|
|
const nmPebiResultPvtParameters& oParameters)
|
|
|
{
|
|
|
const QVector<double>& vecPressure = oParameters.m_vecPressure;
|
|
|
return isOptionalSeriesValid(vecPressure, oParameters.m_vecRso) &&
|
|
|
isOptionalSeriesValid(vecPressure, oParameters.m_vecBo) &&
|
|
|
isOptionalSeriesValid(vecPressure, oParameters.m_vecCo) &&
|
|
|
isOptionalSeriesValid(vecPressure, oParameters.m_vecMiuo) &&
|
|
|
isOptionalSeriesValid(vecPressure, oParameters.m_vecRouo) &&
|
|
|
isOptionalSeriesValid(vecPressure, oParameters.m_vecRv) &&
|
|
|
isOptionalSeriesValid(vecPressure, oParameters.m_vecBg) &&
|
|
|
isOptionalSeriesValid(vecPressure, oParameters.m_vecCg) &&
|
|
|
isOptionalSeriesValid(vecPressure, oParameters.m_vecMiug) &&
|
|
|
isOptionalSeriesValid(vecPressure, oParameters.m_vecRoug) &&
|
|
|
isOptionalSeriesValid(vecPressure, oParameters.m_vecZ) &&
|
|
|
isOptionalSeriesValid(vecPressure, oParameters.m_vecRsw) &&
|
|
|
isOptionalSeriesValid(vecPressure, oParameters.m_vecBw) &&
|
|
|
isOptionalSeriesValid(vecPressure, oParameters.m_vecCw) &&
|
|
|
isOptionalSeriesValid(vecPressure, oParameters.m_vecMiuw) &&
|
|
|
isOptionalSeriesValid(vecPressure, oParameters.m_vecRouw) &&
|
|
|
isOptionalSeriesValid(vecPressure, oParameters.m_vecV) &&
|
|
|
isOptionalSeriesValid(vecPressure, oParameters.m_vecKkInitial) &&
|
|
|
isOptionalSeriesValid(vecPressure,
|
|
|
oParameters.m_vecCfCfInitial);
|
|
|
}
|
|
|
|
|
|
bool areOptionalSaturationSeriesValid(
|
|
|
const nmPebiResultPvtParameters& oParameters,
|
|
|
int nSolverModelType)
|
|
|
{
|
|
|
if(!oParameters.m_vecSo.isEmpty() &&
|
|
|
!isStrictlyIncreasing(oParameters.m_vecSo))
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
if(!oParameters.m_vecSg.isEmpty() &&
|
|
|
!isStrictlyIncreasing(oParameters.m_vecSg))
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
if(!oParameters.m_vecSw.isEmpty() &&
|
|
|
!isStrictlyIncreasing(oParameters.m_vecSw))
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if(oParameters.m_vecSo.isEmpty() != oParameters.m_vecKro.isEmpty() ||
|
|
|
(!oParameters.m_vecSo.isEmpty() &&
|
|
|
oParameters.m_vecSo.size() != oParameters.m_vecKro.size()) ||
|
|
|
oParameters.m_vecSg.isEmpty() != oParameters.m_vecKrg.isEmpty() ||
|
|
|
(!oParameters.m_vecSg.isEmpty() &&
|
|
|
oParameters.m_vecSg.size() != oParameters.m_vecKrg.size()))
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 当前油水两相求解器以 So 为横坐标,同时保存 Kro 和 Krw。
|
|
|
if(nSolverModelType == 8)
|
|
|
{
|
|
|
return isRequiredSeriesValid(oParameters.m_vecSo,
|
|
|
oParameters.m_vecKro) &&
|
|
|
isRequiredSeriesValid(oParameters.m_vecSo,
|
|
|
oParameters.m_vecKrw);
|
|
|
}
|
|
|
|
|
|
if(oParameters.m_vecKrw.isEmpty())
|
|
|
{
|
|
|
return oParameters.m_vecSw.isEmpty();
|
|
|
}
|
|
|
if(!oParameters.m_vecSw.isEmpty() &&
|
|
|
oParameters.m_vecKrw.size() != oParameters.m_vecSw.size())
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
if(!oParameters.m_vecSo.isEmpty() &&
|
|
|
oParameters.m_vecKrw.size() != oParameters.m_vecSo.size())
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
return !oParameters.m_vecSw.isEmpty() ||
|
|
|
!oParameters.m_vecSo.isEmpty();
|
|
|
}
|
|
|
|
|
|
bool arePvtParametersStructurallyValid(
|
|
|
const nmPebiResultPvtParameters& oParameters,
|
|
|
int nSolverModelType)
|
|
|
{
|
|
|
const QVector<double>& vecPressure = oParameters.m_vecPressure;
|
|
|
if((!vecPressure.isEmpty() && !isStrictlyIncreasing(vecPressure)) ||
|
|
|
!areOptionalPressureSeriesValid(oParameters) ||
|
|
|
!areOptionalSaturationSeriesValid(oParameters, nSolverModelType))
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
switch(nSolverModelType)
|
|
|
{
|
|
|
case 1: // 常量油 PVT
|
|
|
case 3: // 常量水 PVT
|
|
|
return true;
|
|
|
case 2: // 变化油 PVT
|
|
|
return isRequiredSeriesValid(vecPressure, oParameters.m_vecBo) &&
|
|
|
isRequiredSeriesValid(vecPressure, oParameters.m_vecCo) &&
|
|
|
isRequiredSeriesValid(vecPressure, oParameters.m_vecMiuo);
|
|
|
case 4: // 变化水 PVT
|
|
|
return isRequiredSeriesValid(vecPressure, oParameters.m_vecBw) &&
|
|
|
isRequiredSeriesValid(vecPressure, oParameters.m_vecCw) &&
|
|
|
isRequiredSeriesValid(vecPressure, oParameters.m_vecMiuw);
|
|
|
case 5: // 变化气 PVT
|
|
|
case 6: // 气拟压力模型
|
|
|
return isRequiredSeriesValid(vecPressure, oParameters.m_vecBg) &&
|
|
|
isRequiredSeriesValid(vecPressure, oParameters.m_vecCg) &&
|
|
|
isRequiredSeriesValid(vecPressure, oParameters.m_vecMiug);
|
|
|
case 8: // 油水两相
|
|
|
return isRequiredSeriesValid(vecPressure, oParameters.m_vecBo) &&
|
|
|
isRequiredSeriesValid(vecPressure, oParameters.m_vecMiuo) &&
|
|
|
isRequiredSeriesValid(vecPressure, oParameters.m_vecBw) &&
|
|
|
isRequiredSeriesValid(vecPressure, oParameters.m_vecMiuw);
|
|
|
default:
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
bool areSolverSettingsValid(const nmPebiResultSolverSettings& oSettings)
|
|
|
{
|
|
|
const bool bSupportedModel =
|
|
|
oSettings.m_nSolverModelType == 1 ||
|
|
|
oSettings.m_nSolverModelType == 2 ||
|
|
|
oSettings.m_nSolverModelType == 3 ||
|
|
|
oSettings.m_nSolverModelType == 4 ||
|
|
|
oSettings.m_nSolverModelType == 5 ||
|
|
|
oSettings.m_nSolverModelType == 6 ||
|
|
|
oSettings.m_nSolverModelType == 8;
|
|
|
const bool bSupportedSolver =
|
|
|
oSettings.m_nPebiSolverType == 1 ||
|
|
|
oSettings.m_nPebiSolverType == 2;
|
|
|
if(!bSupportedModel || !bSupportedSolver ||
|
|
|
oSettings.m_nOmpThreads <= 0 ||
|
|
|
oSettings.m_nIluReuseSteps <= 0 ||
|
|
|
!isFiniteValue(oSettings.m_dGridControl) ||
|
|
|
oSettings.m_dGridControl <= 0.0)
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
return !oSettings.m_bHasTimeStepSettings ||
|
|
|
(isFiniteValue(oSettings.m_dTimeGrowthExponent) &&
|
|
|
isFiniteValue(oSettings.m_dMinDeltaT) &&
|
|
|
isFiniteValue(oSettings.m_dMaxDeltaT) &&
|
|
|
oSettings.m_dTimeGrowthExponent > 0.0 &&
|
|
|
oSettings.m_dMinDeltaT > 0.0 &&
|
|
|
oSettings.m_dMaxDeltaT >= oSettings.m_dMinDeltaT);
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
nmPebiResultSnapshotBuilder::nmPebiResultSnapshotBuilder()
|
|
|
: m_bFinalized(false),
|
|
|
m_bFailed(false),
|
|
|
m_bHasScalarRange(false),
|
|
|
m_bHasReservoirParameters(false),
|
|
|
m_bHasPvtParameters(false),
|
|
|
m_bHasSolverSettings(false)
|
|
|
{
|
|
|
try
|
|
|
{
|
|
|
m_pCandidate = QSharedPointer<nmPebiResultSnapshot>(
|
|
|
new nmPebiResultSnapshot());
|
|
|
}
|
|
|
catch(const std::bad_alloc&)
|
|
|
{
|
|
|
m_bFailed = true;
|
|
|
m_sLastError = "Cannot allocate PEBI result snapshot.";
|
|
|
}
|
|
|
}
|
|
|
|
|
|
nmPebiResultSnapshotBuilder::~nmPebiResultSnapshotBuilder()
|
|
|
{
|
|
|
m_pCandidate.clear();
|
|
|
}
|
|
|
|
|
|
bool nmPebiResultSnapshotBuilder::canWrite()
|
|
|
{
|
|
|
if(m_pCandidate.isNull() || m_bFinalized || m_bFailed)
|
|
|
{
|
|
|
if(m_sLastError.isEmpty())
|
|
|
{
|
|
|
m_sLastError = "PEBI result snapshot builder is not writable.";
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmPebiResultSnapshotBuilder::fail(
|
|
|
const QString& sError,
|
|
|
QString* pError)
|
|
|
{
|
|
|
m_bFailed = true;
|
|
|
m_sLastError = sError;
|
|
|
if(pError != NULL)
|
|
|
{
|
|
|
*pError = sError;
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
bool nmPebiResultSnapshotBuilder::setSnapshotId(
|
|
|
const QString& sSnapshotId)
|
|
|
{
|
|
|
if(!canWrite() || !isValidInstanceId(sSnapshotId))
|
|
|
{
|
|
|
return fail("Invalid PEBI result snapshot identity.");
|
|
|
}
|
|
|
|
|
|
m_pCandidate->m_sSnapshotId = QUuid(sSnapshotId).toString()
|
|
|
.remove('{').remove('}');
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmPebiResultSnapshotBuilder::setInputRevisions(
|
|
|
quint64 nGridInputRevision,
|
|
|
quint64 nResultInputRevision)
|
|
|
{
|
|
|
if(!canWrite() || nGridInputRevision == 0 || nResultInputRevision == 0)
|
|
|
{
|
|
|
return fail("Invalid PEBI result input revisions.");
|
|
|
}
|
|
|
m_pCandidate->m_nGridInputRevision = nGridInputRevision;
|
|
|
m_pCandidate->m_nResultInputRevision = nResultInputRevision;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmPebiResultSnapshotBuilder::takeResultGrid(
|
|
|
vtkSmartPointer<vtkUnstructuredGrid>& pResultGrid)
|
|
|
{
|
|
|
if(!canWrite() || pResultGrid == NULL ||
|
|
|
pResultGrid->GetNumberOfCells() <= 0)
|
|
|
{
|
|
|
return fail("Invalid PEBI result grid.");
|
|
|
}
|
|
|
// 通过交换所有权避免大网格 DeepCopy;成功后调用方不能再修改源网格。
|
|
|
m_pCandidate->m_pResultGrid = pResultGrid;
|
|
|
pResultGrid = NULL;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmPebiResultSnapshotBuilder::addPressureFrame(
|
|
|
double dTime,
|
|
|
vtkSmartPointer<vtkDoubleArray>& pPressure)
|
|
|
{
|
|
|
if(!canWrite() || !isFiniteValue(dTime) || pPressure == NULL ||
|
|
|
pPressure->GetNumberOfComponents() != 1 ||
|
|
|
pPressure->GetNumberOfTuples() <= 0)
|
|
|
{
|
|
|
return fail("Invalid PEBI pressure frame.");
|
|
|
}
|
|
|
if(!m_pCandidate->m_vecPressureFrames.isEmpty() &&
|
|
|
dTime <= m_pCandidate->m_vecPressureFrames.last().m_dTime)
|
|
|
{
|
|
|
return fail("PEBI pressure frame times must be strictly increasing.");
|
|
|
}
|
|
|
|
|
|
try
|
|
|
{
|
|
|
// 压力帧同样直接转交,发布路径不会产生第二份完整时间步数据。
|
|
|
nmPebiResultSnapshot::PressureFrame oFrame;
|
|
|
oFrame.m_dTime = dTime;
|
|
|
pPressure->SetName("p");
|
|
|
oFrame.m_pPressure = pPressure;
|
|
|
m_pCandidate->m_vecPressureFrames.append(oFrame);
|
|
|
pPressure = NULL;
|
|
|
}
|
|
|
catch(const std::bad_alloc&)
|
|
|
{
|
|
|
return fail("Cannot allocate PEBI pressure frame list.");
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmPebiResultSnapshotBuilder::setScalarRange(
|
|
|
double dMinimum,
|
|
|
double dMaximum)
|
|
|
{
|
|
|
if(!canWrite() || !isFiniteValue(dMinimum) ||
|
|
|
!isFiniteValue(dMaximum) ||
|
|
|
dMinimum > dMaximum)
|
|
|
{
|
|
|
return fail("Invalid PEBI scalar range.");
|
|
|
}
|
|
|
m_pCandidate->m_dScalarMin = dMinimum;
|
|
|
m_pCandidate->m_dScalarMax = dMaximum;
|
|
|
m_bHasScalarRange = true;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmPebiResultSnapshotBuilder::addSolverSlot(
|
|
|
const nmPebiResultSolverSlot& oSlot)
|
|
|
{
|
|
|
if(!canWrite())
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
try
|
|
|
{
|
|
|
m_pCandidate->m_vecSolverSlots.append(oSlot);
|
|
|
}
|
|
|
catch(const std::bad_alloc&)
|
|
|
{
|
|
|
return fail("Cannot allocate PEBI solver slot list.");
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmPebiResultSnapshotBuilder::addWell(
|
|
|
const nmPebiResultWellSnapshot& oWell)
|
|
|
{
|
|
|
if(!canWrite())
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
try
|
|
|
{
|
|
|
m_pCandidate->m_vecWells.append(oWell);
|
|
|
}
|
|
|
catch(const std::bad_alloc&)
|
|
|
{
|
|
|
return fail("Cannot allocate PEBI result well list.");
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmPebiResultSnapshotBuilder::takeWell(
|
|
|
nmPebiResultWellSnapshot& oWell)
|
|
|
{
|
|
|
if(!canWrite())
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
try
|
|
|
{
|
|
|
m_pCandidate->m_vecWells.append(nmPebiResultWellSnapshot());
|
|
|
}
|
|
|
catch(const std::bad_alloc&)
|
|
|
{
|
|
|
return fail("Cannot allocate PEBI result well list.");
|
|
|
}
|
|
|
|
|
|
// Qt 4.8 没有移动语义,先追加空元素再交换隐式共享容器,
|
|
|
// 避免最终提交时重新复制六组可能很大的井曲线。
|
|
|
nmPebiResultWellSnapshot& oTarget =
|
|
|
m_pCandidate->m_vecWells.last();
|
|
|
qSwap(oTarget.m_sWellInstanceId, oWell.m_sWellInstanceId);
|
|
|
qSwap(oTarget.m_sWellCode, oWell.m_sWellCode);
|
|
|
qSwap(oTarget.m_sWellName, oWell.m_sWellName);
|
|
|
oTarget.m_eWellType = oWell.m_eWellType;
|
|
|
oTarget.m_eWellCategory = oWell.m_eWellCategory;
|
|
|
oTarget.m_eWellMode = oWell.m_eWellMode;
|
|
|
oTarget.m_oLocation = oWell.m_oLocation;
|
|
|
oTarget.m_bHasPerforation = oWell.m_bHasPerforation;
|
|
|
oTarget.m_bHasSkin = oWell.m_bHasSkin;
|
|
|
oTarget.m_bHasDfc = oWell.m_bHasDfc;
|
|
|
oTarget.m_dRadius = oWell.m_dRadius;
|
|
|
oTarget.m_dWellboreStorage = oWell.m_dWellboreStorage;
|
|
|
oTarget.m_dSkin = oWell.m_dSkin;
|
|
|
oTarget.m_dDfc = oWell.m_dDfc;
|
|
|
qSwap(oTarget.m_oCurves.m_vecHistoryPressure,
|
|
|
oWell.m_oCurves.m_vecHistoryPressure);
|
|
|
qSwap(oTarget.m_oCurves.m_vecHistoryLogLog,
|
|
|
oWell.m_oCurves.m_vecHistoryLogLog);
|
|
|
qSwap(oTarget.m_oCurves.m_vecHistorySemiLog,
|
|
|
oWell.m_oCurves.m_vecHistorySemiLog);
|
|
|
qSwap(oTarget.m_oCurves.m_vecResultPressure,
|
|
|
oWell.m_oCurves.m_vecResultPressure);
|
|
|
qSwap(oTarget.m_oCurves.m_vecResultLogLog,
|
|
|
oWell.m_oCurves.m_vecResultLogLog);
|
|
|
qSwap(oTarget.m_oCurves.m_vecResultSemiLog,
|
|
|
oWell.m_oCurves.m_vecResultSemiLog);
|
|
|
|
|
|
// 清除剩余值,保证调用方不再保留候选井的可写语义。
|
|
|
oWell.m_eWellType = Unknow_Well;
|
|
|
oWell.m_eWellCategory = NM_WellCategory_Unknown;
|
|
|
oWell.m_eWellMode = NM_CaseWell_Observation;
|
|
|
oWell.m_oLocation = QPointF();
|
|
|
oWell.m_bHasPerforation = false;
|
|
|
oWell.m_bHasSkin = false;
|
|
|
oWell.m_bHasDfc = false;
|
|
|
oWell.m_dRadius = 0.0;
|
|
|
oWell.m_dWellboreStorage = 0.0;
|
|
|
oWell.m_dSkin = 0.0;
|
|
|
oWell.m_dDfc = 0.0;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmPebiResultSnapshotBuilder::setDisplayWellInstanceIds(
|
|
|
const QStringList& listWellInstanceIds)
|
|
|
{
|
|
|
if(!canWrite())
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
try
|
|
|
{
|
|
|
m_pCandidate->m_listDisplayWellInstanceIds = listWellInstanceIds;
|
|
|
}
|
|
|
catch(const std::bad_alloc&)
|
|
|
{
|
|
|
return fail("Cannot allocate PEBI display well list.");
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmPebiResultSnapshotBuilder::setReservoirParameters(
|
|
|
const nmPebiResultReservoirParameters& oParameters)
|
|
|
{
|
|
|
if(!canWrite())
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
m_pCandidate->m_oReservoirParameters = oParameters;
|
|
|
m_bHasReservoirParameters = true;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmPebiResultSnapshotBuilder::setPvtParameters(
|
|
|
const nmPebiResultPvtParameters& oParameters)
|
|
|
{
|
|
|
if(!canWrite())
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
try
|
|
|
{
|
|
|
m_pCandidate->m_oPvtParameters = oParameters;
|
|
|
}
|
|
|
catch(const std::bad_alloc&)
|
|
|
{
|
|
|
return fail("Cannot allocate PEBI PVT snapshot.");
|
|
|
}
|
|
|
m_bHasPvtParameters = true;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmPebiResultSnapshotBuilder::setSolverSettings(
|
|
|
const nmPebiResultSolverSettings& oSettings)
|
|
|
{
|
|
|
if(!canWrite())
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
m_pCandidate->m_oSolverSettings = oSettings;
|
|
|
m_bHasSolverSettings = true;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmPebiResultSnapshotBuilder::validate(QString* pError)
|
|
|
{
|
|
|
// 第一部分校验快照身份、输入版本和所有必填结果载荷。
|
|
|
if(!isValidInstanceId(m_pCandidate->m_sSnapshotId))
|
|
|
{
|
|
|
return fail("PEBI result snapshot identity is invalid.", pError);
|
|
|
}
|
|
|
if(m_pCandidate->m_nGridInputRevision == 0 ||
|
|
|
m_pCandidate->m_nResultInputRevision == 0)
|
|
|
{
|
|
|
return fail("PEBI result revisions are missing.", pError);
|
|
|
}
|
|
|
if(m_pCandidate->m_pResultGrid == NULL ||
|
|
|
m_pCandidate->m_pResultGrid->GetNumberOfCells() <= 0)
|
|
|
{
|
|
|
return fail("PEBI result grid is missing.", pError);
|
|
|
}
|
|
|
if(m_pCandidate->m_vecPressureFrames.isEmpty())
|
|
|
{
|
|
|
return fail("PEBI pressure frames are missing.", pError);
|
|
|
}
|
|
|
if(!m_bHasScalarRange || !m_bHasReservoirParameters ||
|
|
|
!m_bHasPvtParameters || !m_bHasSolverSettings)
|
|
|
{
|
|
|
return fail("PEBI result parameters are incomplete.", pError);
|
|
|
}
|
|
|
if(!areReservoirParametersFinite(
|
|
|
m_pCandidate->m_oReservoirParameters) ||
|
|
|
!areSolverSettingsValid(m_pCandidate->m_oSolverSettings) ||
|
|
|
!arePvtParametersFinite(m_pCandidate->m_oPvtParameters) ||
|
|
|
!arePvtParametersStructurallyValid(
|
|
|
m_pCandidate->m_oPvtParameters,
|
|
|
m_pCandidate->m_oSolverSettings.m_nSolverModelType))
|
|
|
{
|
|
|
return fail("PEBI result parameters are invalid.", pError);
|
|
|
}
|
|
|
|
|
|
// 每帧必须与基础网格一一对应;同时从实际压力重新计算标量范围,
|
|
|
// 防止保存或求解端传入与场数据不一致的显示范围。
|
|
|
const vtkIdType nCellCount =
|
|
|
m_pCandidate->m_pResultGrid->GetNumberOfCells();
|
|
|
double dPreviousTime = 0.0;
|
|
|
double dActualMinimum = DBL_MAX;
|
|
|
double dActualMaximum = -DBL_MAX;
|
|
|
for(int nIndex = 0;
|
|
|
nIndex < m_pCandidate->m_vecPressureFrames.size(); ++nIndex)
|
|
|
{
|
|
|
const nmPebiResultSnapshot::PressureFrame& oFrame =
|
|
|
m_pCandidate->m_vecPressureFrames[nIndex];
|
|
|
if(!isFiniteValue(oFrame.m_dTime) ||
|
|
|
(nIndex > 0 && oFrame.m_dTime <= dPreviousTime) ||
|
|
|
oFrame.m_pPressure == NULL ||
|
|
|
oFrame.m_pPressure->GetNumberOfComponents() != 1 ||
|
|
|
oFrame.m_pPressure->GetNumberOfTuples() != nCellCount)
|
|
|
{
|
|
|
return fail("PEBI pressure frame dimensions are invalid.", pError);
|
|
|
}
|
|
|
for(vtkIdType nCellIndex = 0; nCellIndex < nCellCount;
|
|
|
++nCellIndex)
|
|
|
{
|
|
|
const double dValue = oFrame.m_pPressure->GetValue(nCellIndex);
|
|
|
if(!isFiniteValue(dValue))
|
|
|
{
|
|
|
return fail("PEBI pressure frame contains invalid values.",
|
|
|
pError);
|
|
|
}
|
|
|
if(dValue < dActualMinimum)
|
|
|
{
|
|
|
dActualMinimum = dValue;
|
|
|
}
|
|
|
if(dValue > dActualMaximum)
|
|
|
{
|
|
|
dActualMaximum = dValue;
|
|
|
}
|
|
|
}
|
|
|
dPreviousTime = oFrame.m_dTime;
|
|
|
}
|
|
|
|
|
|
const double dRangeScale = qMax(1.0,
|
|
|
qMax(fabs(dActualMinimum), fabs(dActualMaximum)));
|
|
|
const double dRangeTolerance = dRangeScale * 1.0e-10;
|
|
|
if(fabs(m_pCandidate->m_dScalarMin - dActualMinimum) >
|
|
|
dRangeTolerance ||
|
|
|
fabs(m_pCandidate->m_dScalarMax - dActualMaximum) >
|
|
|
dRangeTolerance)
|
|
|
{
|
|
|
return fail("PEBI scalar range does not match pressure frames.",
|
|
|
pError);
|
|
|
}
|
|
|
|
|
|
// UUID 是结果关联主键,井编码只作为求解时冻结元数据参与一致性校验。
|
|
|
QSet<QString> setWellIds;
|
|
|
QSet<QString> setWellCodes;
|
|
|
for(int nIndex = 0; nIndex < m_pCandidate->m_vecWells.size(); ++nIndex)
|
|
|
{
|
|
|
const nmPebiResultWellSnapshot& oWell =
|
|
|
m_pCandidate->m_vecWells[nIndex];
|
|
|
if(!isValidInstanceId(oWell.m_sWellInstanceId) ||
|
|
|
oWell.m_sWellCode.isEmpty() || oWell.m_sWellName.isEmpty() ||
|
|
|
oWell.m_eWellType == Unknow_Well ||
|
|
|
!nmIsValidWellCategory(oWell.m_eWellCategory) ||
|
|
|
(oWell.m_eWellMode != NM_CaseWell_RateControlled &&
|
|
|
oWell.m_eWellMode != NM_CaseWell_Observation) ||
|
|
|
!isFiniteValue(oWell.m_oLocation.x()) ||
|
|
|
!isFiniteValue(oWell.m_oLocation.y()) ||
|
|
|
!isFiniteValue(oWell.m_dRadius) ||
|
|
|
!isFiniteValue(oWell.m_dWellboreStorage) ||
|
|
|
(oWell.m_bHasSkin && !isFiniteValue(oWell.m_dSkin)) ||
|
|
|
(oWell.m_bHasDfc && !isFiniteValue(oWell.m_dDfc)) ||
|
|
|
setWellIds.contains(oWell.m_sWellInstanceId) ||
|
|
|
setWellCodes.contains(oWell.m_sWellCode))
|
|
|
{
|
|
|
return fail(QString("PEBI result well metadata are invalid: %1")
|
|
|
.arg(oWell.m_sWellCode), pError);
|
|
|
}
|
|
|
if(!areWellCurvesValid(oWell.m_oCurves))
|
|
|
{
|
|
|
return fail(QString("PEBI result well curves are invalid: %1")
|
|
|
.arg(oWell.m_sWellCode), pError);
|
|
|
}
|
|
|
setWellIds.insert(oWell.m_sWellInstanceId);
|
|
|
setWellCodes.insert(oWell.m_sWellCode);
|
|
|
}
|
|
|
|
|
|
// 求解器槽位必须连续且覆盖全部真实井;手工裂缝槽位不允许伪装成井。
|
|
|
QSet<QString> setSlotWellIds;
|
|
|
int nRealWellSlotCount = 0;
|
|
|
for(int nIndex = 0; nIndex < m_pCandidate->m_vecSolverSlots.size();
|
|
|
++nIndex)
|
|
|
{
|
|
|
const nmPebiResultSolverSlot& oSlot =
|
|
|
m_pCandidate->m_vecSolverSlots[nIndex];
|
|
|
if(oSlot.m_nSolverIndex != nIndex ||
|
|
|
(oSlot.m_eEntryKind != NM_SolverEntry_Well &&
|
|
|
oSlot.m_eEntryKind != NM_SolverEntry_ManualFracture))
|
|
|
{
|
|
|
return fail("PEBI solver slot order is invalid.", pError);
|
|
|
}
|
|
|
if(oSlot.m_eEntryKind == NM_SolverEntry_ManualFracture)
|
|
|
{
|
|
|
if(!oSlot.m_sWellInstanceId.isEmpty() ||
|
|
|
oSlot.m_eWellCategory != NM_WellCategory_Unknown)
|
|
|
{
|
|
|
return fail("Manual fracture slot has a well identity.", pError);
|
|
|
}
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
const nmPebiResultWellSnapshot* pWell =
|
|
|
m_pCandidate->findWell(oSlot.m_sWellInstanceId);
|
|
|
if(pWell == NULL || pWell->m_sWellCode != oSlot.m_sWellCode ||
|
|
|
pWell->m_eWellType != oSlot.m_eWellType ||
|
|
|
pWell->m_eWellCategory != oSlot.m_eWellCategory ||
|
|
|
!nmIsValidWellCategory(oSlot.m_eWellCategory) ||
|
|
|
setSlotWellIds.contains(oSlot.m_sWellInstanceId))
|
|
|
{
|
|
|
return fail("PEBI solver slot well mapping is invalid.", pError);
|
|
|
}
|
|
|
setSlotWellIds.insert(oSlot.m_sWellInstanceId);
|
|
|
++nRealWellSlotCount;
|
|
|
}
|
|
|
if(nRealWellSlotCount != m_pCandidate->m_vecWells.size())
|
|
|
{
|
|
|
return fail("PEBI result well count does not match solver slots.", pError);
|
|
|
}
|
|
|
|
|
|
// 可显示列表是完整井集合的有序子集,允许为空以支持仅显示场图。
|
|
|
QSet<QString> setDisplayWellIds;
|
|
|
for(int nIndex = 0;
|
|
|
nIndex < m_pCandidate->m_listDisplayWellInstanceIds.size(); ++nIndex)
|
|
|
{
|
|
|
const QString& sWellInstanceId =
|
|
|
m_pCandidate->m_listDisplayWellInstanceIds[nIndex];
|
|
|
if(!setWellIds.contains(sWellInstanceId) ||
|
|
|
setDisplayWellIds.contains(sWellInstanceId))
|
|
|
{
|
|
|
return fail("PEBI display well list is invalid.", pError);
|
|
|
}
|
|
|
setDisplayWellIds.insert(sWellInstanceId);
|
|
|
}
|
|
|
|
|
|
// 发布前基础网格必须与压力帧解耦,渲染网格只能按帧受控绑定。
|
|
|
m_pCandidate->m_pResultGrid->GetCellData()->SetScalars(NULL);
|
|
|
m_pCandidate->m_pResultGrid->GetCellData()->RemoveArray("p");
|
|
|
if(m_pCandidate->m_pResultGrid->GetCellData()->GetScalars() != NULL ||
|
|
|
m_pCandidate->m_pResultGrid->GetCellData()->GetArray("p") != NULL)
|
|
|
{
|
|
|
return fail("PEBI result grid still owns pressure scalars.", pError);
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool nmPebiResultSnapshotBuilder::finalize(QString* pError)
|
|
|
{
|
|
|
if(!canWrite())
|
|
|
{
|
|
|
if(pError != NULL)
|
|
|
{
|
|
|
*pError = m_sLastError;
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
try
|
|
|
{
|
|
|
if(!validate(pError))
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
// 只有全部校验通过后才设置完整标志,Manager 不接受其他候选。
|
|
|
m_pCandidate->m_bComplete = true;
|
|
|
m_bFinalized = true;
|
|
|
return true;
|
|
|
}
|
|
|
catch(const std::bad_alloc&)
|
|
|
{
|
|
|
return fail("Cannot finalize PEBI result snapshot.", pError);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
QSharedPointer<nmPebiResultSnapshot>
|
|
|
nmPebiResultSnapshotBuilder::takeCandidate()
|
|
|
{
|
|
|
if(!m_bFinalized || m_bFailed || m_pCandidate.isNull() ||
|
|
|
!m_pCandidate->isComplete())
|
|
|
{
|
|
|
return QSharedPointer<nmPebiResultSnapshot>();
|
|
|
}
|
|
|
|
|
|
// take 后 Builder 不再持有可写别名,后续生命周期交给提交方管理。
|
|
|
QSharedPointer<nmPebiResultSnapshot> pCandidate = m_pCandidate;
|
|
|
m_pCandidate.clear();
|
|
|
return pCandidate;
|
|
|
}
|
|
|
|
|
|
QString nmPebiResultSnapshotBuilder::getLastError() const
|
|
|
{
|
|
|
return m_sLastError;
|
|
|
}
|