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.
nmWTAI-Platform/Src/nmNum/nmData/nmPebiResultSnapshotBuilder...

858 lines
28 KiB
C++

#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_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_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 ||
(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())
{
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 ||
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;
}