diff --git a/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h b/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h index 0aa6fb0..337f78a 100644 --- a/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h +++ b/Include/nmNum/nmCalculation/nmCalculationAutoFitPSO.h @@ -164,6 +164,8 @@ private: QVector > runSolver(); QVector> runSolverDll(); QVector> runSolverExe(); + /** @brief 使用最终最优参数执行完整求解并提交正式结果快照。 */ + bool runFinalFullSolver(); // ===== 数据处理 ===== QVector interpolateData(const QVector& source, diff --git a/Include/nmNum/nmSubWnd/nmSubWndMain.h b/Include/nmNum/nmSubWnd/nmSubWndMain.h index 305868b..11e1cd5 100644 --- a/Include/nmNum/nmSubWnd/nmSubWndMain.h +++ b/Include/nmNum/nmSubWnd/nmSubWndMain.h @@ -121,6 +121,10 @@ class NM_SUB_WND_EXPORT nmSubWndMain : public iSubWndBaseFit { // 根据分析数据绘制对应的地图信息 void updateMapByDataManager(nmDataAnalyzeManager* pDataManager); + /** @brief 绑定当前数值面板所属的数据管理器和成果窗口。 */ + void bindPebiResultRefreshContext(nmDataAnalyzeManager* pDataManager, + iSubWndFitting* pSubWndFitting); + signals: void sigFreshRnStates(QStringList list); void sigShowProgressAbout(bool); @@ -159,6 +163,8 @@ class NM_SUB_WND_EXPORT nmSubWndMain : public iSubWndBaseFit { /** @brief 当前分析的建网提交结束后,恢复待启动或待捕获的求解流程。 */ void onPebiGridGenerationFinished(quint64 nGridInputRevision, bool bSucceeded); + /** @brief 正式结果快照发布后刷新当前分析的曲线和结果页面。 */ + void onPebiResultSnapshotChanged(); /** @brief 按快照井 UUID 切换结果曲线,不改变实时井或参数编辑井。 */ void onWellSelected(const QString& sWellInstanceId); @@ -313,6 +319,12 @@ private: int m_nPendingSolverTimeMs; ///< 待显示的求解耗时。 bool m_bSolverStartPending; ///< 进度框已显示,输入正分批捕获或任务等待启动。 bool m_bSolverCancelRequested; ///< 已向当前任务发送停止请求,忽略后续阶段信号。 + /** @brief 数值面板固定绑定的数据管理器,自动拟合刷新不依赖当前活动页签。 */ + QPointer m_pResultRefreshDataManager; + /** @brief 数值面板固定绑定的成果窗口。 */ + QPointer m_pResultRefreshFitting; + QString m_sObservedPebiResultSnapshotId; ///< 最近已经交给界面处理的快照 UUID。 + bool m_bRefreshingPebiResultSnapshot; ///< 防止结果井校正信号造成刷新重入。 #ifdef QT_DEBUG // 示例:演示增加Dock并且追加至程序主窗口 diff --git a/Include/nmNum/nmSubWnd/nmSubWndUtils.h b/Include/nmNum/nmSubWnd/nmSubWndUtils.h index 98281c4..fc43283 100644 --- a/Include/nmNum/nmSubWnd/nmSubWndUtils.h +++ b/Include/nmNum/nmSubWnd/nmSubWndUtils.h @@ -167,6 +167,7 @@ public: // 统一装配新建、加载分析共用的数值设计与参数属性Dock控件 static void setupNumericalDockWidgets(nmDataAnalyzeManager* pDataManager, nmSubWndMain* pSubWndMain, + iSubWndFitting* pSubWndFitting, iDockBaseWx* pWxDockNm1, iDockBaseWx* pWxDockNm2); diff --git a/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp b/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp index 0e0c36b..c08c2f5 100644 --- a/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationAutoFitPSO.cpp @@ -3213,12 +3213,26 @@ bool nmCalculationAutoFitPSO::startAutoFitting() } m_isRunning = false; + bool finalFullSolverSucceeded = false; // 应用最终参数 if(!m_globalBestPosition.isEmpty()) { try { emit logMessageGenerated(tr("Applying optimized parameters to model...")); applyParametersToDataManager(m_globalBestPosition); + + // 候选粒子只返回目标井曲线;最优参数确定后必须再生成并提交完整结果快照。 + emit logMessageGenerated( + tr("Running final full-field calculation with optimized parameters...")); + finalFullSolverSucceeded = runFinalFullSolver(); + if(finalFullSolverSucceeded) { + emit logMessageGenerated( + tr("Final full-field calculation completed successfully")); + } else { + m_lastError = tr("Optimized parameters were found, but the final full-field calculation failed"); + emit logMessageGenerated( + tr("ERROR: Final full-field calculation failed")); + } saveOptimizationResult(); // 输出最终优化结果 @@ -3237,7 +3251,13 @@ bool nmCalculationAutoFitPSO::startAutoFitting() emit logMessageGenerated(finalParams); - emit logMessageGenerated(tr("Parameters applied successfully to data manager")); + if(finalFullSolverSucceeded) { + emit logMessageGenerated( + tr("Parameters and full-field results applied successfully to data manager")); + } else { + emit logMessageGenerated( + tr("Optimized parameters applied to data manager")); + } } catch(const std::exception& e) { emit logMessageGenerated(tr("ERROR: Failed to apply final parameters: %1").arg(e.what())); m_lastError = QString("Failed to apply final parameters: %1").arg(e.what()); @@ -3289,6 +3309,14 @@ bool nmCalculationAutoFitPSO::startAutoFitting() emit logMessageGenerated(tr("=== PSO OPTIMIZATION - UNKNOWN END ===")); } + // 只有正式快照已经提交,界面收到的成功信号才代表本次拟合结果完整可用。 + if(success && !finalFullSolverSucceeded) { + success = false; + message = m_lastError.isEmpty() + ? tr("The final full-field calculation failed") + : m_lastError; + } + emitRunSummary(success, finalReason); // 先发送最终进度更新,确保进度条达到100% @@ -5199,6 +5227,51 @@ QVector> nmCalculationAutoFitPSO::runSolverDll() return result; } +bool nmCalculationAutoFitPSO::runFinalFullSolver() +{ + // 不设置目标井名,任务按普通完整模式构建全部井、网格和压力场结果。 + nmDataAnalyzeManager* pDataManager = + nmDataAnalyzeManager::getCurrentInstance(); + if(pDataManager == nullptr || m_evaluationInProgress > 0) { + DEBUG_OUT("Cannot start final full-field solver"); + return false; + } + + ++m_evaluationInProgress; + nmCalculationDllPebiSolverTask dllTask(m_tempDirectory, pDataManager); + dllTask.start(); + + const int maxWait = 3600000; + const int checkInterval = 50; + QTime waitTimer; + waitTimer.start(); + + while(waitTimer.elapsed() < maxWait) { + if(dllTask.wait(checkInterval) || !dllTask.isRunning()) { + break; + } + + // 最终求解期间继续处理绘制事件,避免进度窗口看起来失去响应。 + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, + checkInterval); + } + + if(dllTask.isRunning()) { + DEBUG_OUT("Final full-field solver timeout, terminating task"); + dllTask.terminate(); + dllTask.wait(2000); + --m_evaluationInProgress; + return false; + } + + dllTask.wait(); + // 后台只生成候选快照,必须回到 DataManager 所属线程一次性提交。 + const bool succeeded = + dllTask.wasSuccessful() && dllTask.commitResult(pDataManager); + --m_evaluationInProgress; + return succeeded; +} + //QVector> nmCalculationAutoFitPSO::runSolverExe() //{ // DEBUG_OUT("SOLVER EXE START"); diff --git a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp index 6798e82..ebeec67 100644 --- a/Src/nmNum/nmSubWnd/nmSubWndMain.cpp +++ b/Src/nmNum/nmSubWnd/nmSubWndMain.cpp @@ -145,6 +145,10 @@ nmSubWndMain::nmSubWndMain(QWidget *parent, QString sExt) : m_nPendingSolverTimeMs = -1; m_bSolverStartPending = false; m_bSolverCancelRequested = false; + m_pResultRefreshDataManager = nullptr; + m_pResultRefreshFitting = nullptr; + m_sObservedPebiResultSnapshotId.clear(); + m_bRefreshingPebiResultSnapshot = false; m_pPlotToolBar = nullptr; } @@ -221,6 +225,66 @@ nmSubWndMain::~nmSubWndMain() }*/ } +void nmSubWndMain::bindPebiResultRefreshContext( + nmDataAnalyzeManager* pDataManager, + iSubWndFitting* pSubWndFitting) +{ + // 自动拟合完成时始终刷新创建该数值面板的分析窗口,避免页签切换后取到其他分析。 + m_pResultRefreshDataManager = pDataManager; + m_pResultRefreshFitting = pSubWndFitting; + QSharedPointer pSnapshot = + pDataManager != nullptr + ? pDataManager->getPebiResultSnapshot() + : QSharedPointer(); + m_sObservedPebiResultSnapshotId = pSnapshot.isNull() + ? QString() : pSnapshot->getSnapshotId(); +} + +void nmSubWndMain::onPebiResultSnapshotChanged() +{ + nmDataAnalyzeManager* pDataManager = m_pResultRefreshDataManager.data(); + iSubWndFitting* pSubWndFitting = m_pResultRefreshFitting.data(); + if(pDataManager == nullptr || pSubWndFitting == nullptr || + sender() != pDataManager || m_bRefreshingPebiResultSnapshot) { + return; + } + + QSharedPointer pSnapshot = + pDataManager->getPebiResultSnapshot(); + if(pSnapshot.isNull()) { + m_sObservedPebiResultSnapshotId.clear(); + return; + } + + const QString sSnapshotId = pSnapshot->getSnapshotId(); + if(sSnapshotId == m_sObservedPebiResultSnapshotId) { + // 同一快照内切换结果井仍由 onWellSelected() 处理,不重复刷新全部结果页面。 + return; + } + + // 手工 Generate 有自己的完成和提示流程;这里只接管自动拟合提交的新快照。 + if(m_pSolverTask != nullptr || m_bSolverStartPending || + m_bSolverResultCompletionPending) { + m_sObservedPebiResultSnapshotId = sSnapshotId; + return; + } + + m_bRefreshingPebiResultSnapshot = true; + QPointer pSavedSolverDataManager = + m_pSolverDataManager; + QPointer pSavedSolverFitting = m_pSolverFitting; + m_pSolverDataManager = pDataManager; + m_pSolverFitting = pSubWndFitting; + const bool bRefreshed = mergeAnaResultToFitting(); + schedulePendingResultWidgets(); + m_pSolverDataManager = pSavedSolverDataManager; + m_pSolverFitting = pSavedSolverFitting; + m_bRefreshingPebiResultSnapshot = false; + if(bRefreshed) { + m_sObservedPebiResultSnapshotId = sSnapshotId; + } +} + void nmSubWndMain::configWnd(QString sPrevInfo) { iSubWndBaseFit::configWnd(sPrevInfo); diff --git a/Src/nmNum/nmSubWnd/nmSubWndUtils.cpp b/Src/nmNum/nmSubWnd/nmSubWndUtils.cpp index a04ce78..6878c93 100644 --- a/Src/nmNum/nmSubWnd/nmSubWndUtils.cpp +++ b/Src/nmNum/nmSubWnd/nmSubWndUtils.cpp @@ -409,11 +409,13 @@ void nmSubWndUtils::connectWellListSignals(nmDataAnalyzeManager* pDataManager, void nmSubWndUtils::setupNumericalDockWidgets(nmDataAnalyzeManager* pDataManager, nmSubWndMain* pSubWndMain, + iSubWndFitting* pSubWndFitting, iDockBaseWx* pWxDockNm1, iDockBaseWx* pWxDockNm2) { Q_ASSERT(pDataManager != nullptr); Q_ASSERT(pSubWndMain != nullptr); + Q_ASSERT(pSubWndFitting != nullptr); Q_ASSERT(pWxDockNm1 != nullptr); Q_ASSERT(pWxDockNm2 != nullptr); @@ -447,6 +449,12 @@ void nmSubWndUtils::setupNumericalDockWidgets(nmDataAnalyzeManager* pDataManager connect(pAnalWx, SIGNAL(sigResultWellChanged(QString)), pSubWndMain, SLOT(onWellSelected(QString))); + // 手工求解和自动拟合都发布同一种正式快照,统一复用主窗口结果刷新逻辑。 + pSubWndMain->bindPebiResultRefreshContext(pDataManager, pSubWndFitting); + connect(pDataManager, SIGNAL(sigPebiResultSnapshotChanged()), + pSubWndMain, SLOT(onPebiResultSnapshotChanged()), + Qt::UniqueConnection); + connectWellListSignals(pDataManager, pParaWx); pWxDockNm1->setWidget(pAnalWx); @@ -677,7 +685,7 @@ bool nmSubWndUtils::fillNmDockWxs(iSubWnd* pSubWnd) ZxMainWindow* pMainWnd = pSubWndF->getMainWindow(); // 设置当前窗口到Map pSubWndMain->setMainWindow(pMainWnd); - setupNumericalDockWidgets(pDataManager, pSubWndMain, + setupNumericalDockWidgets(pDataManager, pSubWndMain, pSubWndF, pWxDockNm1, pWxDockNm2); return true; //your own codes @@ -1067,7 +1075,7 @@ bool nmSubWndUtils::loadRsts(iSubWnd* pSubWnd, \ nmSubWndMain* pSubWndMain = new nmSubWndMain(NULL, ""); ZxMainWindow* pMainWnd1 = const_cast(pMainWnd); pSubWndMain->setMainWindow(pMainWnd1); - setupNumericalDockWidgets(pDataManager, pSubWndMain, + setupNumericalDockWidgets(pDataManager, pSubWndMain, pSubWndF, pWxDockNm1, pWxDockNm2); // v3 场图只以独立快照是否存在为准,不再依赖旧时间步 Map。