feat(nmNum): 实现敏感性分析求解副本复用接口

- 增加独立计算副本创建接口,绑定六个采样参数并校验输入。
- 支持按运行编号更新参数,恢复基准值并复用网格输入。
- 清空上次求解结果、错误与停止状态,避免影响原始模型。
feature/Sensitivity-20261012
lvjunjie 1 week ago
parent 7083a47786
commit 486e847cd7

@ -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<QVector<double> > 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 后台使用权。 */

@ -1,4 +1,4 @@
#ifndef NMDATASENSITIVE_H
#ifndef NMDATASENSITIVE_H
#define NMDATASENSITIVE_H
#include <QObject>
@ -116,6 +116,7 @@ public:
// === 变量列表 ===
QList<VariableSampling>& getVariables();
const QList<VariableSampling>& getVariables() const;
void setVariables(const QList<VariableSampling>& vars);
int getVariableCount() const;

@ -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 <QDebug>
#include <QHash>
#include <QMutexLocker>
#include <QScopedPointer>
#include <QSet>
#include <QUuid>
#include <iostream>
@ -46,10 +48,35 @@
#include <vtkUnstructuredGrid.h>
/**
* @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<vtkUnstructuredGrid> m_pBaseGrid; ///< 完整成果使用的基础 VTK 网格。
vtkSmartPointer<vtkUnstructuredGrid> 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<nmCalculationDllPebiSolverTask> 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<double> 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<nmDataSensitive::VariableSampling>& 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;

@ -1,4 +1,4 @@
#include "nmDataSensitive.h"
#include "nmDataSensitive.h"
#include <cmath>
#include <limits>
@ -418,6 +418,12 @@ QList<nmDataSensitive::VariableSampling>& nmDataSensitive::getVariables()
return m_variables;
}
const QList<nmDataSensitive::VariableSampling>& nmDataSensitive::getVariables() const
{
// 求解任务只读取采样配置,不取得可写参数列表。
return m_variables;
}
void nmDataSensitive::setVariables(const QList<VariableSampling>& vars)
{
m_variables = vars;

Loading…
Cancel
Save