From 486e847cd7df497741328f02c83a215b4f757673 Mon Sep 17 00:00:00 2001 From: lvjunjie Date: Tue, 13 Oct 2026 10:49:43 +0800 Subject: [PATCH] =?UTF-8?q?feat(nmNum):=20=E5=AE=9E=E7=8E=B0=E6=95=8F?= =?UTF-8?q?=E6=84=9F=E6=80=A7=E5=88=86=E6=9E=90=E6=B1=82=E8=A7=A3=E5=89=AF?= =?UTF-8?q?=E6=9C=AC=E5=A4=8D=E7=94=A8=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 增加独立计算副本创建接口,绑定六个采样参数并校验输入。 - 支持按运行编号更新参数,恢复基准值并复用网格输入。 - 清空上次求解结果、错误与停止状态,避免影响原始模型。 --- .../nmCalculationDllPebiSolverTask.h | 27 +++ Include/nmNum/nmData/nmDataSensitive.h | 3 +- .../nmCalculationDllPebiSolverTask.cpp | 191 +++++++++++++++++- Src/nmNum/nmData/nmDataSensitive.cpp | 8 +- 4 files changed, 226 insertions(+), 3 deletions(-) diff --git a/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h b/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h index 7f911e13..9b6ba809 100644 --- a/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h +++ b/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h @@ -16,6 +16,7 @@ #include "pch.h" class nmDataAnalyzeManager; +class nmDataSensitive; class nmPebiResultSnapshot; class vtkDoubleArray; struct nmPebiSolverInputSnapshot; @@ -79,7 +80,33 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread { /** @brief 返回自动拟合目标井的半对数结果副本。 */ QVector > getAutoFitResultSemiLog() const; + /** + * @brief 从目标井值快照创建可重复使用的独立采样任务,不修改管理器或正式成果。 + * @param sampling 使用业务基准单位的采样配置。 + * @param runIndex 与 getRunValues() 对应的运行编号,从零开始。 + * @param errorMessage 创建失败的原因。 + * @return 调用方持有的任务;输入或基础网格无效时返回空指针。 + * @note 源任务需指定目标井且未启动;返回任务通过 run()/start() 求解, + * 完成后保存 getAutoFitResultPressure/LogLog/SemiLog() 返回的结果, + * 后续各组通过 updateSensitivityRun() 更新同一个任务。 + */ + nmCalculationDllPebiSolverTask* createSensitivityRun( + const nmDataSensitive& sampling, quint64 runIndex, + QString* errorMessage = nullptr) const; + + /** + * @brief 恢复冻结的基准参数,再填入下一组采样值并清空上次状态及曲线。 + * @note 仅用于 createSensitivityRun() 返回的任务。调用方必须串行调用; + * start() 后需 wait() 完成,直接 run() 时也需返回后才可更新。 + * 更新失败后输入无效,需再次成功更新后才能求解。 + */ + bool updateSensitivityRun(const nmDataSensitive& sampling, quint64 runIndex, + QString* errorMessage = nullptr); + private: + /** @brief 仅复制目标井求解的值快照,不登记或访问实时管理器。 */ + nmCalculationDllPebiSolverTask(const nmCalculationDllPebiSolverTask& source, + QObject* parent); /** @brief 执行当前唯一的 PEBI 求解模式。 */ bool execute(); /** @brief 成对释放构造时登记的 DataManager 后台使用权。 */ diff --git a/Include/nmNum/nmData/nmDataSensitive.h b/Include/nmNum/nmData/nmDataSensitive.h index 663a32ed..36458441 100644 --- a/Include/nmNum/nmData/nmDataSensitive.h +++ b/Include/nmNum/nmData/nmDataSensitive.h @@ -1,4 +1,4 @@ -#ifndef NMDATASENSITIVE_H +#ifndef NMDATASENSITIVE_H #define NMDATASENSITIVE_H #include @@ -116,6 +116,7 @@ public: // === 变量列表 === QList& getVariables(); + const QList& getVariables() const; void setVariables(const QList& vars); int getVariableCount() const; diff --git a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp index 5893bbe1..372c4cb1 100644 --- a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp @@ -1,6 +1,7 @@ #include "nmCalculationDllPebiSolverTask.h" #include "zxLogInstance.h" #include "nmDataAnalyzeManager.h" +#include "nmDataSensitive.h" #include "nmDataWellBase.h" #include "nmDataVerticalWell.h" #include "nmDataVerticalFracturedWell.h" @@ -26,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -46,10 +48,35 @@ #include /** - * @brief 一次 PEBI 求解任务独占的完整不可变输入。 + * @brief PEBI 求解任务独占的完整值输入,敏感性任务仅更新采样标量。 */ struct nmPebiSolverInputSnapshot { + // 只冻结会被采样覆盖的标量,不另存一份大型网格或完整模型。 + struct SensitivityBaseline + { + SensitivityBaseline() + : m_nTarget(-1), m_dSceneSkin(0.0), m_dSceneStorage(0.0), + m_dWellSkin(0.0), m_dWellStorage(0.0), m_dPressure(0.0), + m_dScenePermeability(0.0), m_dSceneThickness(0.0), + m_dScenePorosity(0.0), m_dReferencePermeability(0.0), + m_dReferenceThickness(0.0), m_dReferencePorosity(0.0) + { + } + int m_nTarget; ///< 非负值表示已初始化的独立敏感性任务。 + double m_dSceneSkin; + double m_dSceneStorage; + double m_dWellSkin; + double m_dWellStorage; + double m_dPressure; + double m_dScenePermeability; + double m_dSceneThickness; + double m_dScenePorosity; + double m_dReferencePermeability; + double m_dReferenceThickness; + double m_dReferencePorosity; + }; + nmPebiSolverInputSnapshot() : m_nSolverType(nmDataAnalyzeManager::PebiSolverOriginal), m_nOmpThreads(1), @@ -66,6 +93,7 @@ struct nmPebiSolverInputSnapshot nmPebiGridResult m_oGridResult; ///< 已有网格副本或后台新生成的局部网格。 /** @brief 在 DataManager 线程冻结的静态属性参考值、插值数据和授权路径。 */ nmPebiStaticPropertyCalculationInput m_oStaticPropertyInput; + SensitivityBaseline m_oSensitivityBaseline; ///< 首次复制时冻结,后续更新只读。 vtkSmartPointer m_pBaseGrid; ///< 完整成果使用的基础 VTK 网格。 vtkSmartPointer m_pSourceBaseGrid; ///< 主线程捕获引用,后台只读并深拷贝。 int m_nSolverType; ///< PEBI 求解器实现类型。 @@ -668,6 +696,167 @@ nmCalculationDllPebiSolverTask::~nmCalculationDllPebiSolverTask() m_pInputSnapshot = nullptr; } +nmCalculationDllPebiSolverTask::nmCalculationDllPebiSolverTask( + const nmCalculationDllPebiSolverTask& source, QObject* parent) + : QThread(parent), m_pDataManager(nullptr), + m_pInputSnapshot(new nmPebiSolverInputSnapshot(*source.m_pInputSnapshot)), + m_pManualCaptureState(nullptr), m_bManagerUseActive(false), + m_bInputSnapshotValid(source.m_bInputSnapshotValid), + m_nGridInputRevision(source.m_nGridInputRevision), + m_nResultInputRevision(source.m_nResultInputRevision), + m_lastRunSucceeded(false), m_nSolveTimeMs(-1), m_nPebiCount(-1), + m_sAutoFitTargetWellCode(source.m_sAutoFitTargetWellCode), + m_dPendingScalarMin(0.0), m_dPendingScalarMax(0.0), + m_bPendingFullResultReady(false), m_nCancelRequested(0), m_nWasCancelled(0) +{ + // 采样任务只持有自己的值快照;源任务和分析窗口可独立释放。 +} + +nmCalculationDllPebiSolverTask* nmCalculationDllPebiSolverTask::createSensitivityRun( + const nmDataSensitive& sampling, quint64 runIndex, QString* errorMessage) const +{ + // 从未运行的目标井快照复制输入,禁止采样过程回写原始模型。 + if(errorMessage) errorMessage->clear(); + if(isRunning() || !m_bInputSnapshotValid || !m_pInputSnapshot || + !m_pInputSnapshot->m_bAutoFitTargetOnly || + m_pInputSnapshot->m_oSensitivityBaseline.m_nTarget >= 0) { + if(errorMessage) *errorMessage = m_sFailureReason.isEmpty() + ? tr("Solver input is unavailable.") : m_sFailureReason; + return nullptr; + } + if(!m_pInputSnapshot->m_oGridResult.m_bSucceeded || + m_pInputSnapshot->m_bRequiresGridCalculation) { + if(errorMessage) *errorMessage = tr("Generate a valid base model before running sensitivity."); + return nullptr; + } + QScopedPointer task( + new nmCalculationDllPebiSolverTask(*this, nullptr)); + nmPebiGridInputSnapshot& grid = task->m_pInputSnapshot->m_oGridInput; + nmPebiStaticPropertyCalculationInput& properties = task->m_pInputSnapshot->m_oStaticPropertyInput; + int target = -1; + for(int i = 0; i < grid.m_vecSolverWellOrder.size() && i < grid.m_vecWellInputs.size(); ++i) { + if(grid.m_vecSolverWellOrder[i].m_sWellCode == m_sAutoFitTargetWellCode && + grid.m_vecWellInputs[i].m_bRealWell) { target = i; break; } + } + if(target < 0 || target >= int(grid.m_oScene.CS.C.size()) || + target >= int(grid.m_oScene.CS.S.size())) { + if(errorMessage) *errorMessage = tr("Solver input is unavailable."); + return nullptr; + } + // 首次创建时保存真实输入基准,后续切换采样参数也不会继承上一组的值。 + nmPebiSolverInputSnapshot::SensitivityBaseline& baseline = + task->m_pInputSnapshot->m_oSensitivityBaseline; + baseline.m_nTarget = target; + baseline.m_dSceneSkin = grid.m_oScene.CS.S[target]; + baseline.m_dSceneStorage = grid.m_oScene.CS.C[target]; + baseline.m_dWellSkin = grid.m_vecWellInputs[target].m_dSkin; + baseline.m_dWellStorage = grid.m_vecWellInputs[target].m_dWellboreStorage; + baseline.m_dPressure = grid.m_oScene.Base.Pi; + baseline.m_dScenePermeability = grid.m_oScene.Base.k_ref; + baseline.m_dSceneThickness = grid.m_oScene.Base.h_ref; + baseline.m_dScenePorosity = grid.m_oScene.Base.phi_ref; + baseline.m_dReferencePermeability = properties.m_dReferencePermeabilityMilliDarcy; + baseline.m_dReferenceThickness = properties.m_dReferenceThicknessMeters; + baseline.m_dReferencePorosity = properties.m_dReferencePorosity; + if(!task->updateSensitivityRun(sampling, runIndex, errorMessage)) return nullptr; + return task.take(); +} + +bool nmCalculationDllPebiSolverTask::updateSensitivityRun( + const nmDataSensitive& sampling, quint64 runIndex, QString* errorMessage) +{ + if(errorMessage) errorMessage->clear(); + // 更新必须发生在两次求解之间,不能用于原始任务或实时管理器输入。 + if(isRunning() || m_pDataManager != nullptr || !m_pInputSnapshot || + !m_pInputSnapshot->m_bAutoFitTargetOnly || + m_pInputSnapshot->m_oSensitivityBaseline.m_nTarget < 0) { + if(errorMessage) *errorMessage = tr("Sensitivity task is unavailable or still running."); + return false; + } + + // 即使本组配置无效,也不能将上次的成功状态或曲线当成本组结果。 + m_bInputSnapshotValid = false; + discardPendingFullResult(); + m_lastRunSucceeded = false; + m_sFailureReason.clear(); + m_nSolveTimeMs = -1; + m_nPebiCount = -1; + m_nCancelRequested.fetchAndStoreOrdered(0); + m_nWasCancelled.fetchAndStoreOrdered(0); + QVector values; + if(!sampling.usesBaseUnits() || !sampling.getRunValues(runIndex, values)) { + fail(tr("Invalid sensitivity sampling configuration.")); + if(errorMessage) *errorMessage = m_sFailureReason; + return false; + } + + // 在小型标量副本上恢复基准并校验,全部通过后一次写入,避免半更新输入。 + nmPebiSolverInputSnapshot::SensitivityBaseline parameters = + m_pInputSnapshot->m_oSensitivityBaseline; + nmPebiGridInputSnapshot& grid = m_pInputSnapshot->m_oGridInput; + nmPebiStaticPropertyCalculationInput& properties = m_pInputSnapshot->m_oStaticPropertyInput; + const QList& variables = sampling.getVariables(); + bool valid = values.size() == variables.size(); + for(int i = 0; valid && i < variables.size(); ++i) { + const nmDataSensitive::VariableSampling& variable = variables[i]; + if(!variable.m_enabled) continue; + const QString& name = variable.m_varName; + const double value = values[i]; + valid = isFiniteSolverNumber(value); + if(!valid) break; + // 井筒参数按目标井槽位写入,储层参数同步到场景和静态属性计算输入。 + if(name == "Skin") { + valid = value >= 0.0; + parameters.m_dSceneSkin = value; + parameters.m_dWellSkin = value; + } else if(name == "C") { + valid = value >= 0.0; + parameters.m_dSceneStorage = value; + parameters.m_dWellStorage = value; + } else if(name == "Pi") { + valid = value > 0.0; + parameters.m_dPressure = value; + } else if(name == "k" || name == "h" || name == QString(QChar(0x03c6))) { + valid = value > 0.0 && (name != QString(QChar(0x03c6)) || value <= 1.0); + const QString property = name == QString(QChar(0x03c6)) ? "phi" : name; + // 非均质插值会覆盖参考值,不能将这种运行误报为参数已变化。 + for(int field = 0; valid && field < properties.m_vecDataSets.size(); ++field) { + const nmPropertyInterpolationDataSet& dataSet = properties.m_vecDataSets[field]; + if(dataSet.useForCalculation && dataSet.property == property) valid = false; + } + if(name == "k") { + parameters.m_dReferencePermeability = value; + parameters.m_dScenePermeability = nmCalculationUtils::milliDarcyToDarcy(value); + } else if(name == "h") { + parameters.m_dReferenceThickness = value; + parameters.m_dSceneThickness = value; + } else { + parameters.m_dReferencePorosity = value; + parameters.m_dScenePorosity = value; + } + } else valid = false; + } + if(!valid) { + fail(tr("The sampled parameters are invalid or use a non-uniform field.")); + if(errorMessage) *errorMessage = m_sFailureReason; + return false; + } + const int target = parameters.m_nTarget; + grid.m_oScene.CS.S[target] = parameters.m_dSceneSkin; + grid.m_oScene.CS.C[target] = parameters.m_dSceneStorage; + grid.m_vecWellInputs[target].m_dSkin = parameters.m_dWellSkin; + grid.m_vecWellInputs[target].m_dWellboreStorage = parameters.m_dWellStorage; + grid.m_oScene.Base.Pi = parameters.m_dPressure; + grid.m_oScene.Base.k_ref = parameters.m_dScenePermeability; + grid.m_oScene.Base.h_ref = parameters.m_dSceneThickness; + grid.m_oScene.Base.phi_ref = parameters.m_dScenePorosity; + properties.m_dReferencePermeabilityMilliDarcy = parameters.m_dReferencePermeability; + properties.m_dReferenceThicknessMeters = parameters.m_dReferenceThickness; + properties.m_dReferencePorosity = parameters.m_dReferencePorosity; + m_bInputSnapshotValid = true; + return true; +} + void nmCalculationDllPebiSolverTask::run() { m_nSolveTimeMs = -1; diff --git a/Src/nmNum/nmData/nmDataSensitive.cpp b/Src/nmNum/nmData/nmDataSensitive.cpp index e8c3b21e..a4fb22b3 100644 --- a/Src/nmNum/nmData/nmDataSensitive.cpp +++ b/Src/nmNum/nmData/nmDataSensitive.cpp @@ -1,4 +1,4 @@ -#include "nmDataSensitive.h" +#include "nmDataSensitive.h" #include #include @@ -418,6 +418,12 @@ QList& nmDataSensitive::getVariables() return m_variables; } +const QList& nmDataSensitive::getVariables() const +{ + // 求解任务只读取采样配置,不取得可写参数列表。 + return m_variables; +} + void nmDataSensitive::setVariables(const QList& vars) { m_variables = vars;