#include "nmPebiResultSnapshotBuilder.h" #include #include #include #include #include #include #if defined(_MSC_VER) #else #include #endif #include #include #include namespace { // Builder 只校验内存模型的完整性;持久化文件的数量上限和字节上限 // 由当前成果加载器在分配 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 isGaugeInputSha1(const QString& sSha1) { if(sSha1.size() != 40) { return false; } for(int nIndex = 0; nIndex < sSha1.size(); ++nIndex) { const QChar oChar = sSha1[nIndex]; if(!((oChar >= '0' && oChar <= '9') || (oChar >= 'a' && oChar <= 'f'))) { return false; } } return true; } bool isFiniteVector(const QVector& vecValues) { for(int nIndex = 0; nIndex < vecValues.size(); ++nIndex) { if(!isFiniteValue(vecValues[nIndex])) { return false; } } return true; } bool isStrictlyIncreasing(const QVector& vecValues) { for(int nIndex = 1; nIndex < vecValues.size(); ++nIndex) { if(vecValues[nIndex] <= vecValues[nIndex - 1]) { return false; } } return true; } bool isRequiredSeriesValid(const QVector& vecAxis, const QVector& vecValues) { return vecAxis.size() >= 2 && vecValues.size() == vecAxis.size(); } bool isOptionalSeriesValid(const QVector& vecAxis, const QVector& vecValues) { return vecValues.isEmpty() || (!vecAxis.isEmpty() && vecValues.size() == vecAxis.size()); } bool isCurveValid(const QVector >& 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 isWellFlowScheduleValid(const nmPebiResultWellSnapshot& oWell) { // 观察井不向求解器提供源汇项,结果快照中也不伪造流量制度。 if(oWell.m_eWellMode == NM_CaseWell_Observation) { return oWell.m_vecFlowDurations.isEmpty() && oWell.m_vecOilRates.isEmpty() && oWell.m_vecGasRates.isEmpty() && oWell.m_vecWaterRates.isEmpty() && oWell.m_eFlowSchedulePhase == PHASE_UNKNOWN && oWell.m_nFlowSectionIndex == 0 && !oWell.m_bUseOilRate && !oWell.m_bUseGasRate && !oWell.m_bUseWaterRate; } const int nSegmentCount = oWell.m_vecFlowDurations.size(); if(nSegmentCount <= 0 || oWell.m_vecOilRates.size() != nSegmentCount || oWell.m_vecGasRates.size() != nSegmentCount || oWell.m_vecWaterRates.size() != nSegmentCount || !isFiniteVector(oWell.m_vecFlowDurations) || !isFiniteVector(oWell.m_vecOilRates) || !isFiniteVector(oWell.m_vecGasRates) || !isFiniteVector(oWell.m_vecWaterRates) || oWell.m_nFlowSectionIndex < 1 || oWell.m_nFlowSectionIndex > nSegmentCount) { return false; } // 参考相必须是本次求解实际启用的相,历史流量图才能与对数曲线一致。 return (oWell.m_eFlowSchedulePhase == PHASE_Oil && oWell.m_bUseOilRate) || (oWell.m_eFlowSchedulePhase == PHASE_Gas && oWell.m_bUseGasRate) || (oWell.m_eFlowSchedulePhase == PHASE_Water && oWell.m_bUseWaterRate); } 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& 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& 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( 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& 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& 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); qSwap(oTarget.m_sGaugeInputSha1, oWell.m_sGaugeInputSha1); qSwap(oTarget.m_vecFlowDurations, oWell.m_vecFlowDurations); qSwap(oTarget.m_vecOilRates, oWell.m_vecOilRates); qSwap(oTarget.m_vecGasRates, oWell.m_vecGasRates); qSwap(oTarget.m_vecWaterRates, oWell.m_vecWaterRates); oTarget.m_eWellType = oWell.m_eWellType; oTarget.m_eWellCategory = oWell.m_eWellCategory; oTarget.m_eWellMode = oWell.m_eWellMode; oTarget.m_eFlowSchedulePhase = oWell.m_eFlowSchedulePhase; oTarget.m_nFlowSectionIndex = oWell.m_nFlowSectionIndex; oTarget.m_bUseOilRate = oWell.m_bUseOilRate; oTarget.m_bUseGasRate = oWell.m_bUseGasRate; oTarget.m_bUseWaterRate = oWell.m_bUseWaterRate; 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_eFlowSchedulePhase = PHASE_UNKNOWN; oWell.m_nFlowSectionIndex = 0; oWell.m_bUseOilRate = false; oWell.m_bUseGasRate = false; oWell.m_bUseWaterRate = false; 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 setWellIds; QSet 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() || !isGaugeInputSha1(oWell.m_sGaugeInputSha1) || 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); } if(!isWellFlowScheduleValid(oWell)) { return fail(QString("PEBI result well flow schedule is invalid: %1") .arg(oWell.m_sWellCode), pError); } setWellIds.insert(oWell.m_sWellInstanceId); setWellCodes.insert(oWell.m_sWellCode); } // 求解器槽位必须连续且覆盖全部真实井;手工裂缝槽位不允许伪装成井。 QSet 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 setDisplayWellIds; for(int nIndex = 0; nIndex < m_pCandidate->m_listDisplayWellInstanceIds.size(); ++nIndex) { const QString& sWellInstanceId = m_pCandidate->m_listDisplayWellInstanceIds[nIndex]; const nmPebiResultWellSnapshot* pDisplayWell = m_pCandidate->findWell(sWellInstanceId); if(!setWellIds.contains(sWellInstanceId) || pDisplayWell == NULL || pDisplayWell->m_eWellMode != NM_CaseWell_RateControlled || 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 nmPebiResultSnapshotBuilder::takeCandidate() { if(!m_bFinalized || m_bFailed || m_pCandidate.isNull() || !m_pCandidate->isComplete()) { return QSharedPointer(); } // take 后 Builder 不再持有可写别名,后续生命周期交给提交方管理。 QSharedPointer pCandidate = m_pCandidate; m_pCandidate.clear(); return pCandidate; } QString nmPebiResultSnapshotBuilder::getLastError() const { return m_sLastError; }