diff --git a/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h b/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h index fac7291..bb25234 100644 --- a/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h +++ b/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -16,6 +17,7 @@ #include "pch.h" class nmDataAnalyzeManager; +class nmPebiResultSnapshot; class vtkDoubleArray; class vtkUnstructuredGrid; struct nmPebiSolverInputSnapshot; @@ -81,6 +83,8 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread { bool execute(); /** @brief 成对释放构造时登记的 DataManager 后台使用权。 */ void releaseDataManagerUse(); + /** @brief 丢弃正式结果候选及任务侧全部 VTK 可写别名,不影响旧快照。 */ + void discardPendingFullResult(); /** @brief 同步路径在任务创建线程一次性捕获全部求解输入。 */ bool captureInputSnapshot(const QString& sAutoFitTargetWellName); /** @brief 手工求解在线程内完成场景、缓存和 VTK 基础网格准备。 */ @@ -96,6 +100,8 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread { bool buildPebiModeResult(HX_NWTM_MODEL_OUTPUT& p1, int nModelType, const HX_NWTM_GRID_OUTPUT1& oGridOutput); + /** @brief 用任务局部输出构造并封闭完整结果候选,不访问实时数据对象。 */ + bool buildPebiResultSnapshotCandidate(); /** @brief 输出精简版 DLL 输入日志,用于定位求解异常。 */ void logHX_NWTM_MODEL_INPUT_Simplified(const HX_NWTM_MODEL_INPUT& p0); @@ -137,7 +143,11 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread { QVector > m_autoFitResultSemiLog; ///< 目标井半对数结果。 QVector m_vecPendingWellResults; ///< 完整求解的井结果快照。 QMap > m_mapPendingTimeSteps; ///< 场压力时间步快照。 - vtkSmartPointer m_pPendingResultGrid; ///< 本次结果绑定的基础网格副本。 + QMap m_mapPendingWellLocations; ///< 旧 UI 兼容层使用的井编码位置映射。 + /** @brief 发布前为求解基础网格,候选封闭后改为旧 UI 独占的结构网格。 */ + vtkSmartPointer m_pPendingResultGrid; + /** @brief 工作线程已经完整校验、等待主线程发布的可写候选引用。 */ + QSharedPointer m_pPendingResultSnapshot; double m_dPendingScalarMin; ///< 全部场压力的最小值。 double m_dPendingScalarMax; ///< 全部场压力的最大值。 bool m_bPendingFullResultReady; ///< 是否已有可由主线程一次提交的完整结果。 diff --git a/Include/nmNum/nmCalculation/nmCalculationPebiGrid.h b/Include/nmNum/nmCalculation/nmCalculationPebiGrid.h index 7425da8..f784c75 100644 --- a/Include/nmNum/nmCalculation/nmCalculationPebiGrid.h +++ b/Include/nmNum/nmCalculation/nmCalculationPebiGrid.h @@ -28,22 +28,39 @@ struct nmPebiWellInputSnapshot { nmPebiWellInputSnapshot() : m_nFlowSectionIndex(1), + m_dRadius(0.0), m_dWellboreStorage(0.0), m_dSkin(0.0), + m_dDfc(0.0), m_bRateControlled(false), - m_bRealWell(false) + m_bRealWell(false), + m_bHasPerforation(false), + m_bHasDfc(false), + m_bDisplayResultWell(false) { } - QString m_sWellCode; - QString m_sWellName; - QVector m_vecFlowPoints; - QPointF m_oLocation; - int m_nFlowSectionIndex; - double m_dWellboreStorage; - double m_dSkin; - bool m_bRateControlled; - bool m_bRealWell; + QString m_sWellCode; ///< 项目内稳定井编码。 + QString m_sWellName; ///< 捕获时井名称。 + QString m_sWellInstanceId; ///< 完整场结果使用的稳定井 UUID。 + QVector m_vecFlowPoints; ///< 求解使用的流量制度。 + /** @brief 求解时冻结的历史压力曲线。 */ + QVector > m_vecHistoryPressure; + /** @brief 求解时冻结的历史双对数曲线。 */ + QVector > m_vecHistoryLogLog; + /** @brief 求解时冻结的历史半对数曲线。 */ + QVector > m_vecHistorySemiLog; + QPointF m_oLocation; ///< 求解时井平面位置。 + int m_nFlowSectionIndex; ///< 当前流动段下标。 + double m_dRadius; ///< 求解时井筒半径。 + double m_dWellboreStorage; ///< 求解时井筒储集系数。 + double m_dSkin; ///< 求解时表皮系数。 + double m_dDfc; ///< 求解时裂缝导流能力。 + bool m_bRateControlled; ///< 是否提供源汇项。 + bool m_bRealWell; ///< 是否为真实井而非手工裂缝槽位。 + bool m_bHasPerforation; ///< 求解时是否存在射孔。 + bool m_bHasDfc; ///< 当前井型是否具有 Dfc 参数。 + bool m_bDisplayResultWell; ///< 是否进入结果井下拉框。 }; /** diff --git a/Include/nmNum/nmData/nmDataAnalyzeManager.h b/Include/nmNum/nmData/nmDataAnalyzeManager.h index bcfaad6..3504791 100644 --- a/Include/nmNum/nmData/nmDataAnalyzeManager.h +++ b/Include/nmNum/nmData/nmDataAnalyzeManager.h @@ -542,6 +542,17 @@ public: bool commitPebiResultSnapshot( QSharedPointer& pCandidate, QString* pError = NULL); + /** + * @brief 同时发布只读快照和第三阶段旧 UI 所需的场数据引用。 + * @note 全部校验完成前不修改旧结果;成功后清空候选及三个任务侧可写别名。 + */ + bool commitPebiResultSnapshot( + QSharedPointer& pCandidate, + QMap >& + mapLegacyTimeSteps, + vtkSmartPointer& pLegacyResultGrid, + QMap& mapLegacyWellLocations, + QString* pError = NULL); /** @brief 清除并行快照及其结果井查看状态。 */ void clearPebiResultSnapshot(); @@ -787,6 +798,8 @@ public: void sigPebiResultSnapshotChanged(); private: + /** @brief 按 UUID 给当前实时井统一绑定或解除最后结果快照弱引用。 */ + void rebindPebiResultSnapshotToWells(); /** @brief 创建本数据管理器的成果窗口;只用于读取本成果上下文,不拥有对象。 */ iSubWndFitting* m_pOwnerFitting; /** @brief 保护后台使用计数和析构等待条件。 */ diff --git a/Include/nmNum/nmData/nmDataWellBase.h b/Include/nmNum/nmData/nmDataWellBase.h index 84ade31..3a50890 100644 --- a/Include/nmNum/nmData/nmDataWellBase.h +++ b/Include/nmNum/nmData/nmDataWellBase.h @@ -6,11 +6,13 @@ #include "ZxDataObjectDbl.h" #include "nmDataPerforation.h" +#include //#include "nmDataReservoir.h" //#include "nmDataAnalyzeManager.h" class nmDataReservoir; class nmDataAnalyzeManager; +class nmPebiResultSnapshot; class NM_DATA_EXPORT nmDataWellBase : public nmDataBase { @@ -166,13 +168,19 @@ public: NM_WELL_MODEL getWellType() const; // 计算结果相关方法 + /** @brief 返回当前井 UUID 在最后结果快照中的压力曲线;未绑定时兼容旧成员。 */ QVector> getResultPressure(); + /** @brief 第二阶段兼容接口,后续旧结果成员清理前仍允许写入。 */ void setResultPressure(QVector> pressureData); + /** @brief 返回当前井 UUID 在最后结果快照中的双对数曲线;未绑定时兼容旧成员。 */ QVector> getResultLogLog(); + /** @brief 第二阶段兼容接口,后续旧结果成员清理前仍允许写入。 */ void setResultLogLog(QVector> loglogData); + /** @brief 返回当前井 UUID 在最后结果快照中的半对数曲线;未绑定时兼容旧成员。 */ QVector> getResultSemiLog(); + /** @brief 第二阶段兼容接口,后续旧结果成员清理前仍允许写入。 */ void setResultSemiLog(QVector> semiLogData); // 时间变表皮状态的getter和setter @@ -294,6 +302,9 @@ protected: QVector> m_vvecRstLogLog; // 计算结果双对数数据 QVector> m_vvecRstSemiLog; // 计算结果半对数数据 + /** @brief 最后结果快照的弱引用,不延长历史结果生命周期。 */ + QWeakPointer m_pPebiResultSnapshot; + // 射孔段集合 QVector m_vecPerforations; @@ -301,4 +312,12 @@ protected: double m_dLastWellLength; nmDataReservoir* m_pReservoir; + +private: + /** @brief 由数据管理器按井 UUID 统一绑定或解除最后结果快照。 */ + void bindPebiResultSnapshot( + const QWeakPointer& pSnapshot); + + /** @brief 只有所属数据管理器可以维护井与结果快照之间的弱关联。 */ + friend class nmDataAnalyzeManager; }; diff --git a/Include/nmNum/nmData/nmPebiResultSnapshotBuilder.h b/Include/nmNum/nmData/nmPebiResultSnapshotBuilder.h index 2e114db..6ea13c6 100644 --- a/Include/nmNum/nmData/nmPebiResultSnapshotBuilder.h +++ b/Include/nmNum/nmData/nmPebiResultSnapshotBuilder.h @@ -39,6 +39,8 @@ public: bool addSolverSlot(const nmPebiResultSolverSlot& oSlot); /** @brief 追加一口包含元数据和曲线的冻结结果井。 */ bool addWell(const nmPebiResultWellSnapshot& oWell); + /** @brief 转移一口井的字符串和曲线容器,避免提交阶段复制整套曲线。 */ + bool takeWell(nmPebiResultWellSnapshot& oWell); /** @brief 设置结果井下拉框使用的有序井 UUID 列表。 */ bool setDisplayWellInstanceIds(const QStringList& listWellInstanceIds); /** @brief 设置求解时冻结的储层参数。 */ diff --git a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp index 5ab0d50..a04e2b4 100644 --- a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp @@ -14,6 +14,7 @@ #include "nmDataFracture.h" #include "nmDataFault.h" #include "nmDataTimeStepSetting.h" +#include "nmPebiResultSnapshotBuilder.h" #include "nmCalculationPebiGrid.h" #include "nmCalculationUtils.h" @@ -24,11 +25,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -36,6 +39,7 @@ #include #include +#include #include /** @@ -50,7 +54,8 @@ struct nmPebiSolverInputSnapshot m_bRequiresGridCalculation(false), m_bGridResultNeedsCommit(false), m_bMayUseCachedGrid(false), - m_bAutoFitTargetOnly(false) + m_bAutoFitTargetOnly(false), + m_bResultMetadataCaptured(false) { } @@ -62,10 +67,17 @@ struct nmPebiSolverInputSnapshot int m_nSolverType; ///< PEBI 求解器实现类型。 int m_nOmpThreads; ///< CPU 加速求解线程数。 int m_nIluReuseSteps; ///< ILU 预条件复用步数。 + /** @brief 完整场结果使用的求解时储层值。 */ + nmPebiResultReservoirParameters m_oResultReservoirParameters; + /** @brief 完整场结果使用的常量值和全部 PVT 数组。 */ + nmPebiResultPvtParameters m_oResultPvtParameters; + /** @brief 完整场结果页面使用的求解与时间步设置。 */ + nmPebiResultSolverSettings m_oResultSolverSettings; bool m_bRequiresGridCalculation; ///< 后台是否需先用网格值快照计算一次网格。 bool m_bGridResultNeedsCommit; ///< 完整求解成功后是否需在主线程提交新网格。 bool m_bMayUseCachedGrid; ///< 捕获时是否存在同版本网格缓存。 bool m_bAutoFitTargetOnly; ///< true 时只构造目标井临时曲线,不提交成果。 + bool m_bResultMetadataCaptured; ///< 正式结果元数据是否已在主线程完整冻结。 }; /** @@ -164,6 +176,159 @@ bool isFiniteSolverNumber(double value) #endif } +bool isDisplayResultWell( + const nmDataNumericalAnalysisCase* pAnalysisCase, + const QString& sWellCode) +{ + if(pAnalysisCase == NULL || sWellCode.isEmpty()) { + return false; + } + return pAnalysisCase->getPrimaryWellCode() == sWellCode || + (pAnalysisCase->getIncludeOtherWells() && + pAnalysisCase->isIncludedWell(sWellCode)); +} + +bool captureResultWellMetadata( + nmDataWellBase* pWellData, + bool bDisplayResultWell, + nmPebiWellInputSnapshot& oWellInput) +{ + if(pWellData == NULL || !oWellInput.m_bRealWell || + pWellData->getWellCode() != oWellInput.m_sWellCode || + pWellData->getWellInstanceId().isEmpty() || + QUuid(pWellData->getWellInstanceId()).isNull()) { + return false; + } + + // 正式结果所需井值在主线程一次冻结;后台只读取本结构。 + oWellInput.m_sWellInstanceId = pWellData->getWellInstanceId(); + oWellInput.m_sWellName = pWellData->getWellName(); + oWellInput.m_vecHistoryPressure = pWellData->getHistoryPressure(); + oWellInput.m_vecHistoryLogLog = pWellData->getHistoryLogLog(); + oWellInput.m_vecHistorySemiLog = pWellData->getHistorySemiLog(); + oWellInput.m_dRadius = + pWellData->getRadius().getValue().toDouble(); + oWellInput.m_bHasPerforation = + pWellData->getPerforationCount() > 0; + oWellInput.m_bDisplayResultWell = bDisplayResultWell; + + nmDataVerticalFracturedWell* pVerticalFracturedWell = + dynamic_cast(pWellData); + nmDataHorizontalFracturedWell* pHorizontalFracturedWell = + dynamic_cast(pWellData); + oWellInput.m_bHasDfc = pVerticalFracturedWell != NULL || + pHorizontalFracturedWell != NULL; + if(pVerticalFracturedWell != NULL) { + oWellInput.m_dDfc = pVerticalFracturedWell->getDfc() + .getValue().toDouble(); + } else if(pHorizontalFracturedWell != NULL) { + oWellInput.m_dDfc = pHorizontalFracturedWell->getDfc() + .getValue().toDouble(); + } + return true; +} + +bool captureResultParameters( + nmDataAnalyzeManager* pDataManager, + nmPebiSolverInputSnapshot& oSnapshot) +{ + if(pDataManager == NULL || + QThread::currentThread() != pDataManager->thread()) { + return false; + } + + nmDataReservoir* pReservoir = pDataManager->getReservoirData(); + if(pReservoir == NULL) { + return false; + } + + nmPebiResultReservoirParameters& oReservoir = + oSnapshot.m_oResultReservoirParameters; + oReservoir.m_dInitialPressure = pReservoir->getInitialPressure() + .getValue().toDouble(); + oReservoir.m_dPermeability = pReservoir->getPermeability() + .getValue().toDouble(); + oReservoir.m_dThickness = pReservoir->getThickness() + .getValue().toDouble(); + oReservoir.m_dPorosity = pReservoir->getPorosity() + .getValue().toDouble(); + oReservoir.m_dTotalCompressibility = pReservoir->getCt() + .getValue().toDouble(); + oReservoir.m_dRockCompressibility = pReservoir->getCf() + .getValue().toDouble(); + oReservoir.m_dOilSaturation = pReservoir->getSoi() + .getValue().toDouble(); + oReservoir.m_dGasSaturation = pReservoir->getSgi() + .getValue().toDouble(); + oReservoir.m_dWaterSaturation = pReservoir->getSwi() + .getValue().toDouble(); + + nmPebiResultPvtParameters& oPvt = + oSnapshot.m_oResultPvtParameters; + oPvt.m_dConstantBo = pReservoir->getBo().getValue().toDouble(); + oPvt.m_dConstantMiuo = pReservoir->getMiuo().getValue().toDouble(); + oPvt.m_dConstantBg = pReservoir->getBg().getValue().toDouble(); + oPvt.m_dConstantMiug = pReservoir->getMiug().getValue().toDouble(); + oPvt.m_dConstantBw = pReservoir->getBw().getValue().toDouble(); + oPvt.m_dConstantMiuw = pReservoir->getMiuw().getValue().toDouble(); + + nmDataPvtParaForPebi* pPvt = pDataManager->getPebiPvtPara(); + if(pPvt != NULL) { + oPvt.m_bHasBubblePoint = true; + oPvt.m_dBubblePoint = pPvt->getPb().getValue().toDouble(); + oPvt.m_vecPressure = pPvt->getPressure(); + oPvt.m_vecRso = pPvt->getRso(); + oPvt.m_vecBo = pPvt->getBo(); + oPvt.m_vecCo = pPvt->getCo(); + oPvt.m_vecMiuo = pPvt->getMiuo(); + oPvt.m_vecRouo = pPvt->getRouo(); + oPvt.m_vecRv = pPvt->getRv(); + oPvt.m_vecBg = pPvt->getBg(); + oPvt.m_vecCg = pPvt->getCg(); + oPvt.m_vecMiug = pPvt->getMiug(); + oPvt.m_vecRoug = pPvt->getRoug(); + oPvt.m_vecZ = pPvt->getZ(); + oPvt.m_vecRsw = pPvt->getRsw(); + oPvt.m_vecBw = pPvt->getBw(); + oPvt.m_vecCw = pPvt->getCw(); + oPvt.m_vecMiuw = pPvt->getMiuw(); + oPvt.m_vecRouw = pPvt->getRouw(); + oPvt.m_vecV = pPvt->getV(); + oPvt.m_vecKkInitial = pPvt->getKKinitial(); + oPvt.m_vecCfCfInitial = pPvt->getCfCfinitial(); + oPvt.m_vecSo = pPvt->getSo(); + oPvt.m_vecKro = pPvt->getKro(); + oPvt.m_vecSg = pPvt->getSg(); + oPvt.m_vecKrg = pPvt->getKrg(); + oPvt.m_vecSw = pPvt->getSw(); + oPvt.m_vecKrw = pPvt->getKrw(); + } + + nmPebiResultSolverSettings& oSettings = + oSnapshot.m_oResultSolverSettings; + oSettings.m_nSolverModelType = + static_cast(pDataManager->getSolverModelType()); + oSettings.m_nPebiSolverType = oSnapshot.m_nSolverType; + oSettings.m_nOmpThreads = oSnapshot.m_nOmpThreads; + oSettings.m_nIluReuseSteps = oSnapshot.m_nIluReuseSteps; + oSettings.m_dGridControl = + oSnapshot.m_oGridInput.m_oGridInput.GridControl; + + nmDataTimeStepSetting* pTimeStep = pDataManager->getTimeStep(); + if(pTimeStep != NULL) { + oSettings.m_bHasTimeStepSettings = true; + oSettings.m_dTimeGrowthExponent = pTimeStep->getTimeGrowthExponent() + .getValue().toDouble(); + oSettings.m_dMinDeltaT = pTimeStep->getMinDeltaTAttribute() + .getValue().toDouble(); + oSettings.m_dMaxDeltaT = pTimeStep->getMaxDeltaTAttribute() + .getValue().toDouble(); + } + + oSnapshot.m_bResultMetadataCaptured = true; + return true; +} + // 将启用的数据组插值到全部网格单元中心,并覆盖对应的求解器属性数组. bool applyPropertyInterpolation( HX_NWTM_MODEL_INPUT& modelInput, @@ -421,7 +586,7 @@ nmCalculationDllPebiSolverTask::nmCalculationDllPebiSolverTask( m_pDataManager(pDataManager != nullptr ? pDataManager : nmDataAnalyzeManager::getCurrentInstance()), - m_pInputSnapshot(new nmPebiSolverInputSnapshot()), + m_pInputSnapshot(nullptr), m_pManualCaptureState(nullptr), m_bManagerUseActive(false), m_bInputSnapshotValid(false), @@ -436,19 +601,31 @@ nmCalculationDllPebiSolverTask::nmCalculationDllPebiSolverTask( m_nCancelRequested(0), m_nWasCancelled(0) { - // 第一步:构造后到 run() 结束前阻止所属成果提前释放 DataManager。 - if(m_pDataManager != nullptr) { - m_pDataManager->beginBackgroundUse(); - m_bManagerUseActive = true; - } + try { + m_pInputSnapshot = new nmPebiSolverInputSnapshot(); - if(bDeferManualSnapshot && sAutoFitTargetWellName.isEmpty()) { - // 手工求解由界面事件循环分批冻结值输入;start() 必须等状态释放后再调用。 - m_pManualCaptureState = new nmPebiManualCaptureState(); - } else { - // 自动拟合以及旧调用路径继续一次性同步捕获,保持原来的评价行为。 - m_bInputSnapshotValid = - captureInputSnapshot(sAutoFitTargetWellName); + // 第一步:构造后到 run() 结束前阻止所属成果提前释放 DataManager。 + if(m_pDataManager != nullptr) { + m_pDataManager->beginBackgroundUse(); + m_bManagerUseActive = true; + } + + if(bDeferManualSnapshot && sAutoFitTargetWellName.isEmpty()) { + // 手工求解由界面事件循环分批冻结值输入;start() 必须等状态释放后再调用。 + m_pManualCaptureState = new nmPebiManualCaptureState(); + } else { + // 自动拟合以及旧调用路径继续一次性同步捕获,保持原来的评价行为。 + m_bInputSnapshotValid = + captureInputSnapshot(sAutoFitTargetWellName); + } + } catch(const std::bad_alloc&) { + // 构造阶段内存不足时保留旧成果,任务保持无效并由既有完成流程报错。 + qWarning() << "Cannot allocate PEBI solver input snapshot."; + delete m_pManualCaptureState; + m_pManualCaptureState = nullptr; + delete m_pInputSnapshot; + m_pInputSnapshot = nullptr; + m_bInputSnapshotValid = false; } } @@ -470,11 +647,19 @@ void nmCalculationDllPebiSolverTask::run() { m_nSolveTimeMs = -1; m_nPebiCount = -1; - bool bSucceeded = this->execute(); + bool bSucceeded = false; + try { + bSucceeded = this->execute(); + } catch(const std::bad_alloc&) { + // VTK 部分分配接口没有返回值,统一在任务边界拒绝候选并保留旧快照。 + qWarning() << "Cannot allocate PEBI solver result snapshot."; + discardPendingFullResult(); + bSucceeded = false; + } if(isCancelRequested()) { // 停止只丢弃任务局部结果,主线程不会进入 commitResult()。 m_nWasCancelled.fetchAndStoreOrdered(1); - m_bPendingFullResultReady = false; + discardPendingFullResult(); bSucceeded = false; } m_lastRunSucceeded = bSucceeded; @@ -643,6 +828,14 @@ bool nmCalculationDllPebiSolverTask::captureManualInputStep( m_pInputSnapshot->m_oGridInput)) { return false; } + if(m_pInputSnapshot->m_oGridInput.m_vecWellInputs.isEmpty() || + !captureResultWellMetadata( + pWellData, + isDisplayResultWell(pAnalysisCase, + pWellData->getWellCode()), + m_pInputSnapshot->m_oGridInput.m_vecWellInputs.last())) { + return false; + } ++pState->m_nNextWellIndex; } @@ -682,6 +875,10 @@ bool nmCalculationDllPebiSolverTask::captureManualInputStep( m_pDataManager->getPebiOmpThreads(); m_pInputSnapshot->m_nIluReuseSteps = m_pDataManager->getPebiIluReuseSteps(); + if(!captureResultParameters(m_pDataManager, + *m_pInputSnapshot)) { + return false; + } pState->m_ePhase = nmPebiManualCaptureState::CapturePhase_GridCache; return true; @@ -793,6 +990,35 @@ bool nmCalculationDllPebiSolverTask::captureInputSnapshot( return false; } + // 自动拟合候选保持原来的轻量输入;只有最终完整场求解冻结正式结果元数据。 + if(!m_pInputSnapshot->m_bAutoFitTargetOnly) { + QHash mapWellsByCode; + const QVector vecWells = + m_pDataManager->getWellDataList(); + for(int nIndex = 0; nIndex < vecWells.size(); ++nIndex) { + nmDataWellBase* pWellData = vecWells[nIndex]; + if(pWellData != NULL && !pWellData->getWellCode().isEmpty()) { + mapWellsByCode.insert(pWellData->getWellCode(), pWellData); + } + } + for(int nIndex = 0; nIndex < vecWellOrder.size(); ++nIndex) { + const nmSolverWellRef& oWellRef = vecWellOrder[nIndex]; + if(oWellRef.m_eEntryKind == NM_SolverEntry_ManualFracture) { + continue; + } + nmDataWellBase* pWellData = + mapWellsByCode.value(oWellRef.m_sWellCode, NULL); + if(pWellData == NULL || + !captureResultWellMetadata( + pWellData, + isDisplayResultWell(pAnalysisCase, + oWellRef.m_sWellCode), + m_pInputSnapshot->m_oGridInput.m_vecWellInputs[nIndex])) { + return false; + } + } + } + // 第三步:复制属性插值及 DLL 求解配置。这些设置只影响模型求解结果, // 不应在工作线程中再次从 DataManager 查询。 m_pInputSnapshot->m_vecPropertyDataSets = @@ -803,6 +1029,10 @@ bool nmCalculationDllPebiSolverTask::captureInputSnapshot( m_pDataManager->getPebiOmpThreads(); m_pInputSnapshot->m_nIluReuseSteps = m_pDataManager->getPebiIluReuseSteps(); + if(!m_pInputSnapshot->m_bAutoFitTargetOnly && + !captureResultParameters(m_pDataManager, *m_pInputSnapshot)) { + return false; + } // 第四步:主线程仅检查缓存并捕获基础网格的智能指针。生成后的基础网格 // 不在原位修改,智能指针保证 DataManager 替换网格后旧对象仍存活;真正的 @@ -957,7 +1187,9 @@ bool nmCalculationDllPebiSolverTask::execPebiMode() m_bPendingFullResultReady = false; m_vecPendingWellResults.clear(); m_mapPendingTimeSteps.clear(); + m_mapPendingWellLocations.clear(); m_pPendingResultGrid = nullptr; + m_pPendingResultSnapshot.clear(); m_autoFitResultPressure.clear(); m_autoFitResultLogLog.clear(); m_autoFitResultSemiLog.clear(); @@ -1598,8 +1830,201 @@ bool nmCalculationDllPebiSolverTask::buildPebiModeResult( m_dPendingScalarMin = dMinP; m_dPendingScalarMax = dMaxP; - m_bPendingFullResultReady = true; - return true; + m_bPendingFullResultReady = buildPebiResultSnapshotCandidate(); + if(!m_bPendingFullResultReady) { + // 候选结构或内存校验失败时立即释放大结果,旧快照保持原状。 + discardPendingFullResult(); + } + return m_bPendingFullResultReady; +} + +void nmCalculationDllPebiSolverTask::discardPendingFullResult() +{ + m_pPendingResultSnapshot.clear(); + m_vecPendingWellResults.clear(); + m_mapPendingTimeSteps.clear(); + m_mapPendingWellLocations.clear(); + m_pPendingResultGrid = NULL; + m_bPendingFullResultReady = false; + + // 候选可能与输入基础网格共享 VTK 对象,丢弃或发布后必须清除任务侧别名。 + if(m_pInputSnapshot != NULL && + !m_pInputSnapshot->m_bAutoFitTargetOnly) { + m_pInputSnapshot->m_pBaseGrid = NULL; + m_pInputSnapshot->m_pSourceBaseGrid = NULL; + m_pInputSnapshot->m_oGridResult.m_pUnstructuredGrid = NULL; + } +} + +bool nmCalculationDllPebiSolverTask::buildPebiResultSnapshotCandidate() +{ + if(m_pInputSnapshot == NULL || + m_pInputSnapshot->m_bAutoFitTargetOnly || + !m_pInputSnapshot->m_bResultMetadataCaptured || + m_pPendingResultGrid == NULL || + m_mapPendingTimeSteps.isEmpty()) { + return false; + } + + try { + nmPebiResultSnapshotBuilder oBuilder; + if(!oBuilder.setInputRevisions(m_nGridInputRevision, + m_nResultInputRevision)) { + return false; + } + + // Builder 接管同一 VTK 对象的候选引用;任务暂留一份引用供第三阶段 + // 旧 UI 镜像发布,主线程提交后会立即清除全部任务侧别名。 + vtkSmartPointer pResultGrid = + m_pPendingResultGrid; + if(!oBuilder.takeResultGrid(pResultGrid)) { + return false; + } + + QMap >::const_iterator + oTimeIt = m_mapPendingTimeSteps.constBegin(); + for(; oTimeIt != m_mapPendingTimeSteps.constEnd(); ++oTimeIt) { + vtkSmartPointer pPressure = oTimeIt.value(); + if(!oBuilder.addPressureFrame(oTimeIt.key(), pPressure)) { + return false; + } + } + if(!oBuilder.setScalarRange(m_dPendingScalarMin, + m_dPendingScalarMax)) { + return false; + } + + QVector& vecSolverOrder = + m_pInputSnapshot->m_oGridInput.m_vecSolverWellOrder; + QVector& vecWellInputs = + m_pInputSnapshot->m_oGridInput.m_vecWellInputs; + if(vecSolverOrder.size() != vecWellInputs.size()) { + return false; + } + + QStringList listDisplayWellIds; + m_mapPendingWellLocations.clear(); + int nResultWellIndex = 0; + for(int nSlotIndex = 0; + nSlotIndex < vecSolverOrder.size(); + ++nSlotIndex) { + const nmSolverWellRef& oWellRef = vecSolverOrder[nSlotIndex]; + nmPebiWellInputSnapshot& oWellInput = + vecWellInputs[nSlotIndex]; + + nmPebiResultSolverSlot oSlot; + oSlot.m_nSolverIndex = nSlotIndex; + oSlot.m_eEntryKind = oWellRef.m_eEntryKind; + oSlot.m_sWellCode = oWellRef.m_sWellCode; + oSlot.m_eWellType = oWellRef.m_eWellType; + if(oWellRef.m_eEntryKind == NM_SolverEntry_Well) { + oSlot.m_sWellInstanceId = oWellInput.m_sWellInstanceId; + } + if(!oBuilder.addSolverSlot(oSlot)) { + return false; + } + + if(oWellRef.m_eEntryKind == + NM_SolverEntry_ManualFracture) { + continue; + } + if(nResultWellIndex >= m_vecPendingWellResults.size()) { + return false; + } + + nmPebiWellResultSnapshot& oWellResult = + m_vecPendingWellResults[nResultWellIndex++]; + if(!oWellInput.m_bRealWell || + oWellInput.m_sWellCode != oWellRef.m_sWellCode || + oWellResult.m_sWellCode != oWellRef.m_sWellCode) { + return false; + } + + nmPebiResultWellSnapshot oWell; + oWell.m_sWellInstanceId = oWellInput.m_sWellInstanceId; + oWell.m_sWellCode = oWellInput.m_sWellCode; + oWell.m_sWellName = oWellInput.m_sWellName; + oWell.m_eWellType = oWellRef.m_eWellType; + oWell.m_eWellMode = oWellInput.m_bRateControlled + ? NM_CaseWell_RateControlled + : NM_CaseWell_Observation; + oWell.m_oLocation = oWellInput.m_oLocation; + oWell.m_bHasPerforation = oWellInput.m_bHasPerforation; + oWell.m_bHasSkin = oWellInput.m_bHasPerforation; + oWell.m_bHasDfc = oWellInput.m_bHasDfc; + oWell.m_dRadius = oWellInput.m_dRadius; + oWell.m_dWellboreStorage = + oWellInput.m_dWellboreStorage; + oWell.m_dSkin = oWellInput.m_dSkin; + oWell.m_dDfc = oWellInput.m_dDfc; + + // 六组曲线逐项交换进入候选,清空任务输入和局部结果中的可写容器。 + qSwap(oWell.m_oCurves.m_vecHistoryPressure, + oWellInput.m_vecHistoryPressure); + qSwap(oWell.m_oCurves.m_vecHistoryLogLog, + oWellInput.m_vecHistoryLogLog); + qSwap(oWell.m_oCurves.m_vecHistorySemiLog, + oWellInput.m_vecHistorySemiLog); + qSwap(oWell.m_oCurves.m_vecResultPressure, + oWellResult.m_vecPressure); + qSwap(oWell.m_oCurves.m_vecResultLogLog, + oWellResult.m_vecLogLog); + qSwap(oWell.m_oCurves.m_vecResultSemiLog, + oWellResult.m_vecSemiLog); + + m_mapPendingWellLocations.insert(oWell.m_sWellCode, + oWell.m_oLocation); + if(oWellInput.m_bDisplayResultWell) { + listDisplayWellIds.append(oWell.m_sWellInstanceId); + } + if(!oBuilder.takeWell(oWell)) { + return false; + } + } + if(nResultWellIndex != m_vecPendingWellResults.size() || + !oBuilder.setDisplayWellInstanceIds(listDisplayWellIds) || + !oBuilder.setReservoirParameters( + m_pInputSnapshot->m_oResultReservoirParameters) || + !oBuilder.setPvtParameters( + m_pInputSnapshot->m_oResultPvtParameters) || + !oBuilder.setSolverSettings( + m_pInputSnapshot->m_oResultSolverSettings)) { + return false; + } + + QString sError; + if(!oBuilder.finalize(&sError)) { + qWarning() << "Cannot finalize PEBI result snapshot:" + << sError; + return false; + } + + // 第三阶段旧动画仍会在结果网格上切换活动压力标量,不能把快照 + // 内部基础网格直接交给它。CopyStructure 只建立独立网格壳并共享 + // 已验证为只读的点和单元拓扑,不复制大网格,也不会共享 CellData。 + vtkSmartPointer pLegacyResultGrid = + vtkSmartPointer::New(); + pLegacyResultGrid->CopyStructure(m_pPendingResultGrid); + pLegacyResultGrid->GetCellData()->Initialize(); + if(pLegacyResultGrid->GetNumberOfCells() != + m_pPendingResultGrid->GetNumberOfCells() || + pLegacyResultGrid->GetCellData()->GetScalars() != NULL) { + return false; + } + + m_pPendingResultSnapshot = oBuilder.takeCandidate(); + if(m_pPendingResultSnapshot.isNull()) { + return false; + } + + // 发布时 Manager 接收旧 UI 独占的网格壳;规范基础网格只留在快照中。 + m_pPendingResultGrid = pLegacyResultGrid; + return true; + } catch(const std::bad_alloc&) { + qWarning() << "Cannot allocate PEBI result snapshot candidate."; + m_pPendingResultSnapshot.clear(); + return false; + } } bool nmCalculationDllPebiSolverTask::commitResult( @@ -1611,9 +2036,11 @@ bool nmCalculationDllPebiSolverTask::commitResult( m_pInputSnapshot->m_bAutoFitTargetOnly || wasCancelled() || isCancelRequested() || !m_bPendingFullResultReady || + m_pPendingResultSnapshot.isNull() || pDataManager == nullptr || pDataManager != m_pDataManager || QThread::currentThread() != pDataManager->thread()) { + discardPendingFullResult(); return false; } @@ -1625,24 +2052,26 @@ bool nmCalculationDllPebiSolverTask::commitResult( m_mapPendingTimeSteps.isEmpty() || m_pPendingResultGrid == nullptr || m_pPendingResultGrid->GetNumberOfCells() <= 0) { + discardPendingFullResult(); return false; } - // 第二步:在改动旧成果前一次性解析全部目标井,并检查 WellCode 不重复。 - QVector vecTargetWells; - QSet setWellCodes; - vecTargetWells.reserve(m_vecPendingWellResults.size()); - for(int nIndex = 0; nIndex < m_vecPendingWellResults.size(); ++nIndex) { - const nmPebiWellResultSnapshot& oWellResult = - m_vecPendingWellResults[nIndex]; - nmDataWellBase* pWellData = - pDataManager->findWellByCode(oWellResult.m_sWellCode); - if(pWellData == nullptr || - setWellCodes.contains(oWellResult.m_sWellCode)) { + // 第二步:即使外部遗漏版本递增,同编码新井也不能接收旧任务结果。 + QSet setWellIds; + for(int nIndex = 0; + nIndex < m_pPendingResultSnapshot->getWellCount(); + ++nIndex) { + const nmPebiResultWellSnapshot* pWell = + m_pPendingResultSnapshot->getWellAt(nIndex); + nmDataWellBase* pLiveWell = pWell == NULL ? NULL : + pDataManager->findWellByCode(pWell->m_sWellCode); + if(pWell == NULL || pLiveWell == NULL || + pLiveWell->getWellInstanceId() != pWell->m_sWellInstanceId || + setWellIds.contains(pWell->m_sWellInstanceId)) { + discardPendingFullResult(); return false; } - setWellCodes.insert(oWellResult.m_sWellCode); - vecTargetWells.append(pWellData); + setWellIds.insert(pWell->m_sWellInstanceId); } // 第三步:任务后台生成了新网格时,先在当前主线程按同一输入版本提交。 @@ -1652,46 +2081,29 @@ bool nmCalculationDllPebiSolverTask::commitResult( pDataManager, m_pInputSnapshot->m_oGridInput, m_pInputSnapshot->m_oGridResult)) { + discardPendingFullResult(); return false; } } else if(!pAnalysisCase->isGridValid()) { + discardPendingFullResult(); return false; } - // 第四步:最后一次登记版本。登记失败时旧成果仍未被修改;登记成功后当前 - // 主线程事件不会被其他编辑操作插入,因此后续替换不存在可恢复失败分支。 - if(!pAnalysisCase->markResultsAvailableIfCurrent( - m_nGridInputRevision, m_nResultInputRevision)) { - return false; - } - - // 第五步:版本检查和对象解析全部通过后,在当前主线程事件内整体替换。 - // 这些 setter 不发事件也不包含可恢复失败分支,外部只能在本函数返回后看到新成果。 - pDataManager->clearWellLocations(); - for(int nIndex = 0; nIndex < m_vecPendingWellResults.size(); ++nIndex) { - const nmPebiWellResultSnapshot& oWellResult = - m_vecPendingWellResults[nIndex]; - nmDataWellBase* pWellData = vecTargetWells[nIndex]; - pWellData->setResultPressure(oWellResult.m_vecPressure); - pWellData->setResultLogLog(oWellResult.m_vecLogLog); - pWellData->setResultSemiLog(oWellResult.m_vecSemiLog); - pDataManager->addWellLocation(oWellResult.m_sWellCode, - oWellResult.m_oLocation); + // 第四步:Manager 在一次发布点校验并替换规范快照及旧 UI 兼容引用。 + QString sError; + const bool bCommitted = pDataManager->commitPebiResultSnapshot( + m_pPendingResultSnapshot, + m_mapPendingTimeSteps, + m_pPendingResultGrid, + m_mapPendingWellLocations, + &sError); + if(!bCommitted) { + qWarning() << "Cannot commit PEBI result snapshot:" << sError; } - pDataManager->clearTimeSteps(); - QMap >::const_iterator oTimeIt = - m_mapPendingTimeSteps.constBegin(); - for(; oTimeIt != m_mapPendingTimeSteps.constEnd(); ++oTimeIt) { - pDataManager->addTimeStep(oTimeIt.key(), oTimeIt.value()); - } - pDataManager->setScalarRangeP(m_dPendingScalarMin, - m_dPendingScalarMax); - pDataManager->setResultBaseGrid(m_pPendingResultGrid); - - // 第六步:清除待提交标志,防止重复完成信号再次覆盖后续结果。 - m_bPendingFullResultReady = false; - return true; + // 成功和失败都不允许任务继续持有候选可写别名;旧快照只由 Manager 决定。 + discardPendingFullResult(); + return bCommitted; } //bool nmCalculationDllPebiSolverTask::savePebiModeResult(HX_NWTM_MODEL_OUTPUT& p1) diff --git a/Src/nmNum/nmData/nmDataAnalyzeManager.cpp b/Src/nmNum/nmData/nmDataAnalyzeManager.cpp index 088c1e4..344c907 100644 --- a/Src/nmNum/nmData/nmDataAnalyzeManager.cpp +++ b/Src/nmNum/nmData/nmDataAnalyzeManager.cpp @@ -1676,6 +1676,8 @@ bool nmDataAnalyzeManager::replaceWellData(nmDataWellBase* pOldWell, // 第二步:先让注册表指向新对象,再销毁旧对象并统一通知观察者。 syncWellAttrs(); delete pOldWell; + // 同 UUID 井型替换后,新对象必须重新获得最后结果的弱引用。 + rebindPebiResultSnapshotToWells(); QStringList listParameters; for (int nIndex = 0; nIndex < WELL_PARA_DESC_COUNT; ++nIndex) @@ -3825,10 +3827,221 @@ bool nmDataAnalyzeManager::commitPebiResultSnapshot( m_pPebiResultSnapshot = pPublishedCandidate; m_sCurrentResultWellInstanceId = sNextWellInstanceId; pCandidate.clear(); + rebindPebiResultSnapshotToWells(); emit sigPebiResultSnapshotChanged(); return true; } +bool nmDataAnalyzeManager::commitPebiResultSnapshot( + QSharedPointer& pCandidate, + QMap >& mapLegacyTimeSteps, + vtkSmartPointer& pLegacyResultGrid, + QMap& mapLegacyWellLocations, + QString* pError) +{ + if(pError != NULL) + { + pError->clear(); + } + + QString sError; + if(QThread::currentThread() != thread()) + { + sError = "PEBI result snapshot must be committed on its owner thread."; + } + else if(pCandidate.isNull() || !pCandidate->isComplete()) + { + sError = "PEBI result snapshot candidate is incomplete."; + } + else if(pCandidate->getGridInputRevision() != + m_oNumericalAnalysisCase.getGridInputRevision() || + pCandidate->getResultInputRevision() != + m_oNumericalAnalysisCase.getResultInputRevision() || + !m_oNumericalAnalysisCase.isGridValid()) + { + sError = "PEBI result snapshot input revisions are stale."; + } + else if(pLegacyResultGrid == NULL || + pLegacyResultGrid->GetNumberOfCells() != + pCandidate->getResultCellCount() || + mapLegacyTimeSteps.size() != + pCandidate->getPressureFrameCount()) + { + sError = "PEBI legacy field result does not match the snapshot."; + } + + // 第三阶段仍需给旧场图保留只读约定下的 VTK 引用;发布前逐帧校验, + // 不能让旧 UI 镜像与规范快照指向不同尺寸或不同时间轴的数据。 + if(sError.isEmpty()) + { + int nFrameIndex = 0; + QMap >::const_iterator + oTimeIt = mapLegacyTimeSteps.constBegin(); + for(; oTimeIt != mapLegacyTimeSteps.constEnd(); + ++oTimeIt, ++nFrameIndex) + { + vtkDoubleArray* pPressure = oTimeIt.value(); + if(oTimeIt.key() != + pCandidate->getPressureTimeAt(nFrameIndex) || + pPressure == NULL || + pPressure->GetNumberOfComponents() != 1 || + pPressure->GetNumberOfTuples() != + pCandidate->getResultCellCount()) + { + sError = "PEBI legacy pressure frames are inconsistent."; + break; + } + } + } + + QSet setSnapshotWellCodes; + if(sError.isEmpty()) + { + for(int nIndex = 0; nIndex < pCandidate->getWellCount(); ++nIndex) + { + const nmPebiResultWellSnapshot* pWell = + pCandidate->getWellAt(nIndex); + nmDataWellBase* pLiveWell = pWell == NULL ? NULL : + findWellByCode(pWell->m_sWellCode); + if(pWell == NULL || pLiveWell == NULL || + pLiveWell->getWellInstanceId() != + pWell->m_sWellInstanceId || + setSnapshotWellCodes.contains(pWell->m_sWellCode) || + !mapLegacyWellLocations.contains(pWell->m_sWellCode) || + !qIsFinite(mapLegacyWellLocations.value( + pWell->m_sWellCode).x()) || + !qIsFinite(mapLegacyWellLocations.value( + pWell->m_sWellCode).y())) + { + sError = "PEBI result well identity or location is stale."; + break; + } + setSnapshotWellCodes.insert(pWell->m_sWellCode); + } + if(sError.isEmpty() && + (setSnapshotWellCodes.isEmpty() || + mapLegacyWellLocations.size() != + setSnapshotWellCodes.size())) + { + sError = "PEBI result well locations are incomplete."; + } + } + + // 当前网格登记的槽位也必须与候选完全相同,防止相同版本下的非法外部改写。 + if(sError.isEmpty()) + { + const QVector vecCurrentOrder = + m_oNumericalAnalysisCase.getSolverWellOrder(); + if(vecCurrentOrder.size() != pCandidate->getSolverSlotCount()) + { + sError = "PEBI solver slot order has changed."; + } + for(int nIndex = 0; + sError.isEmpty() && nIndex < vecCurrentOrder.size(); + ++nIndex) + { + const nmSolverWellRef& oCurrent = vecCurrentOrder[nIndex]; + const nmPebiResultSolverSlot* pSaved = + pCandidate->getSolverSlotAt(nIndex); + if(pSaved == NULL || + oCurrent.m_nSolverIndex != pSaved->m_nSolverIndex || + oCurrent.m_eEntryKind != pSaved->m_eEntryKind || + oCurrent.m_sWellCode != pSaved->m_sWellCode || + oCurrent.m_eWellType != pSaved->m_eWellType) + { + sError = "PEBI solver slot order has changed."; + } + } + } + + double aScalarRange[2] = { 0.0, 0.0 }; + if(sError.isEmpty() && !pCandidate->getScalarRange(aScalarRange)) + { + sError = "PEBI result scalar range is unavailable."; + } + + if(!sError.isEmpty()) + { + // 失败时只销毁新候选;任务局部 VTK 引用由任务随后释放,旧结果不变。 + pCandidate.clear(); + if(pError != NULL) + { + *pError = sError; + } + return false; + } + + QSharedPointer pPublishedCandidate( + pCandidate); + QString sNextWellInstanceId = m_sCurrentResultWellInstanceId; + if(!pPublishedCandidate->isDisplayWell(sNextWellInstanceId)) + { + const QStringList& listDisplayWellIds = + pPublishedCandidate->getDisplayWellInstanceIds(); + sNextWellInstanceId = listDisplayWellIds.isEmpty() + ? QString() : listDisplayWellIds.first(); + } + + // 所有可能失败的检查均已完成;先同步旧结果状态,再在同一主线程事件内 + // 替换规范快照和兼容载荷,观察者只在末尾收到一次完整结果通知。 + if(!m_oNumericalAnalysisCase.markResultsAvailableIfCurrent( + pCandidate->getGridInputRevision(), + pCandidate->getResultInputRevision())) + { + pCandidate.clear(); + if(pError != NULL) + { + *pError = "PEBI result snapshot became stale before publish."; + } + return false; + } + + m_pPebiResultSnapshot = pPublishedCandidate; + m_sCurrentResultWellInstanceId = sNextWellInstanceId; + m_mapTimeStepDataP.swap(mapLegacyTimeSteps); + mapLegacyTimeSteps.clear(); + m_mapWellLocations.swap(mapLegacyWellLocations); + mapLegacyWellLocations.clear(); + vtkSmartPointer pOldResultGrid = + m_pResultBaseGrid; + m_pResultBaseGrid = pLegacyResultGrid; + pLegacyResultGrid = pOldResultGrid; + pLegacyResultGrid = NULL; + m_dScalarRangeP[0] = aScalarRange[0]; + m_dScalarRangeP[1] = aScalarRange[1]; + pCandidate.clear(); + + rebindPebiResultSnapshotToWells(); + emit sigPebiResultSnapshotChanged(); + return true; +} + +void nmDataAnalyzeManager::rebindPebiResultSnapshotToWells() +{ + QWeakPointer pWeakSnapshot; + if(!m_pPebiResultSnapshot.isNull()) + { + pWeakSnapshot = QWeakPointer( + m_pPebiResultSnapshot); + } + + for(int nIndex = 0; nIndex < m_vWellData.size(); ++nIndex) + { + nmDataWellBase* pWell = m_vWellData[nIndex]; + if(pWell == NULL) + { + continue; + } + const bool bBelongsToSnapshot = + !m_pPebiResultSnapshot.isNull() && + m_pPebiResultSnapshot->findWell( + pWell->getWellInstanceId()) != NULL; + pWell->bindPebiResultSnapshot(bBelongsToSnapshot + ? pWeakSnapshot + : QWeakPointer()); + } +} + void nmDataAnalyzeManager::clearPebiResultSnapshot() { if(m_pPebiResultSnapshot.isNull() && @@ -3839,6 +4052,7 @@ void nmDataAnalyzeManager::clearPebiResultSnapshot() m_pPebiResultSnapshot.clear(); m_sCurrentResultWellInstanceId.clear(); + rebindPebiResultSnapshotToWells(); emit sigPebiResultSnapshotChanged(); } diff --git a/Src/nmNum/nmData/nmDataWellBase.cpp b/Src/nmNum/nmData/nmDataWellBase.cpp index dff95d8..cfb1dd5 100644 --- a/Src/nmNum/nmData/nmDataWellBase.cpp +++ b/Src/nmNum/nmData/nmDataWellBase.cpp @@ -1,6 +1,7 @@ #include "nmDataWellBase.h" #include "nmDataReservoir.h" #include "nmDataAnalyzeManager.h" +#include "nmPebiResultSnapshot.h" #include #include @@ -98,6 +99,8 @@ nmDataWellBase::~nmDataWellBase() nmDataWellBase& nmDataWellBase::operator=(const nmDataWellBase& other) { if(this != &other) { + // 工作副本不能继承 Manager 维护的结果绑定;提交替换后统一重绑。 + m_pPebiResultSnapshot.clear(); m_sWellInstanceId = other.m_sWellInstanceId; m_wellName = other.m_wellName; m_wellCode = other.m_wellCode; @@ -490,6 +493,8 @@ bool nmDataWellBase::restoreWellInstanceId( } m_sWellInstanceId = oUuid.toString().remove('{').remove('}'); + // UUID 改变后原弱引用不再具有关联意义,等待 Manager 重新绑定。 + m_pPebiResultSnapshot.clear(); return true; } @@ -777,6 +782,15 @@ void nmDataWellBase::setHistorySemiLog(QVector> semiLogData) { // 计算结果相关方法 QVector> nmDataWellBase::getResultPressure() { + QSharedPointer pSnapshot = + m_pPebiResultSnapshot.toStrongRef(); + if(!pSnapshot.isNull()) { + const nmPebiResultWellSnapshot* pWell = + pSnapshot->findWell(m_sWellInstanceId); + if(pWell != NULL) { + return pWell->m_oCurves.m_vecResultPressure; + } + } return m_vvecRstPressure; } @@ -787,6 +801,15 @@ void nmDataWellBase::setResultPressure(QVector> pressureData) QVector> nmDataWellBase::getResultLogLog() { + QSharedPointer pSnapshot = + m_pPebiResultSnapshot.toStrongRef(); + if(!pSnapshot.isNull()) { + const nmPebiResultWellSnapshot* pWell = + pSnapshot->findWell(m_sWellInstanceId); + if(pWell != NULL) { + return pWell->m_oCurves.m_vecResultLogLog; + } + } return m_vvecRstLogLog; } @@ -797,6 +820,15 @@ void nmDataWellBase::setResultLogLog(QVector> loglogData) QVector> nmDataWellBase::getResultSemiLog() { + QSharedPointer pSnapshot = + m_pPebiResultSnapshot.toStrongRef(); + if(!pSnapshot.isNull()) { + const nmPebiResultWellSnapshot* pWell = + pSnapshot->findWell(m_sWellInstanceId); + if(pWell != NULL) { + return pWell->m_oCurves.m_vecResultSemiLog; + } + } return m_vvecRstSemiLog; } @@ -805,6 +837,12 @@ void nmDataWellBase::setResultSemiLog(QVector> semiLogData) m_vvecRstSemiLog = semiLogData; } +void nmDataWellBase::bindPebiResultSnapshot( + const QWeakPointer& pSnapshot) +{ + m_pPebiResultSnapshot = pSnapshot; +} + // 射孔管理相关方法 void nmDataWellBase::addPerforation(nmDataPerforation* perforation) { diff --git a/Src/nmNum/nmData/nmPebiResultSnapshotBuilder.cpp b/Src/nmNum/nmData/nmPebiResultSnapshotBuilder.cpp index 97cb800..0b76c76 100644 --- a/Src/nmNum/nmData/nmPebiResultSnapshotBuilder.cpp +++ b/Src/nmNum/nmData/nmPebiResultSnapshotBuilder.cpp @@ -496,6 +496,67 @@ bool nmPebiResultSnapshotBuilder::addWell( 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) {