fix(nmNum): 改为读取当前模型参数

- 移除分析和拟合对旧输入快照的依赖,按当前参数初始化范围。
- 补齐历史曲线和压力数据,修复拟合及误差统计。
- 支持重开项目后分析,刷新气井拟合目标的历史曲线。
feature/Sensitivity-20261012
lvjunjie 4 days ago
parent f9f1997970
commit 7acdfa06e4

@ -37,14 +37,12 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread {
* @param sAutoFitTargetWellName 自动拟合目标井名;空值表示完整成果计算。 * @param sAutoFitTargetWellName 自动拟合目标井名;空值表示完整成果计算。
* @param parent Qt 父对象。 * @param parent Qt 父对象。
* @param bDeferManualSnapshot 手工求解是否由界面事件循环分批捕获输入。 * @param bDeferManualSnapshot 手工求解是否由界面事件循环分批捕获输入。
* @param bUseSolvedInput 敏感性分析只恢复最后成功模型的输入,不捕获当前编辑值。
*/ */
nmCalculationDllPebiSolverTask(QString sPostprocessingDir, nmCalculationDllPebiSolverTask(QString sPostprocessingDir,
nmDataAnalyzeManager* pDataManager = nullptr, nmDataAnalyzeManager* pDataManager = nullptr,
const QString& sAutoFitTargetWellName = QString(), const QString& sAutoFitTargetWellName = QString(),
QObject *parent = nullptr, QObject *parent = nullptr,
bool bDeferManualSnapshot = false, bool bDeferManualSnapshot = false);
bool bUseSolvedInput = false);
~nmCalculationDllPebiSolverTask(); ~nmCalculationDllPebiSolverTask();
/** @brief QThread 入口,执行单次或敏感性批量求解并发送一次完成信号。 */ /** @brief QThread 入口,执行单次或敏感性批量求解并发送一次完成信号。 */
@ -136,10 +134,6 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread {
// 基准模型与采样模型共用压力误差口径,返回 -1 表示无有效误差。 // 基准模型与采样模型共用压力误差口径,返回 -1 表示无有效误差。
static double calculatePressureRmse(const QVector<QVector<double> >& measured, static double calculatePressureRmse(const QVector<QVector<double> >& measured,
const QVector<QVector<double> >& simulated); const QVector<QVector<double> >& simulated);
// 从最后成功输入的裂缝端点读取半长,不访问当前编辑中的井对象。
static bool getSolvedFractureHalfLength(const QSharedPointer<const nmPebiSolverInputSnapshot>& input,
const QString& wellCode, double& halfLength);
private: private:
/** @brief 仅复制目标井求解的值快照,不登记或访问实时管理器。 */ /** @brief 仅复制目标井求解的值快照,不登记或访问实时管理器。 */
nmCalculationDllPebiSolverTask(const nmCalculationDllPebiSolverTask& source, nmCalculationDllPebiSolverTask(const nmCalculationDllPebiSolverTask& source,
@ -158,8 +152,6 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread {
void discardPendingFullResult(); void discardPendingFullResult();
/** @brief 同步路径在任务创建线程一次性捕获全部求解输入。 */ /** @brief 同步路径在任务创建线程一次性捕获全部求解输入。 */
bool captureInputSnapshot(const QString& sAutoFitTargetWellName); bool captureInputSnapshot(const QString& sAutoFitTargetWellName);
/** @brief 从所属分析的最后成功结果恢复完整输入。 */
bool restoreSolvedInput();
/** @brief 手工求解在线程内完成场景、缓存和 VTK 基础网格准备。 */ /** @brief 手工求解在线程内完成场景、缓存和 VTK 基础网格准备。 */
bool prepareManualInput(); bool prepareManualInput();
/** @brief 无锁读取原子停止标记。 */ /** @brief 无锁读取原子停止标记。 */

@ -135,7 +135,6 @@ struct nmSensitivityFitSettings
bool confirmed; bool confirmed;
nmDataAutomaticFitting parameters; nmDataAutomaticFitting parameters;
QString targetWell; QString targetWell;
QString snapshotId;
QVector<double> initialValues; QVector<double> initialValues;
}; };

@ -122,7 +122,7 @@ public:
BatchResult() : m_calcType(CALC_DETERMINISTIC), m_dFilterThreshold(-1.0), m_nHistogramVariable(-1), m_bCompleted(false), m_bStopped(false) {} BatchResult() : m_calcType(CALC_DETERMINISTIC), m_dFilterThreshold(-1.0), m_nHistogramVariable(-1), m_bCompleted(false), m_bStopped(false) {}
CalculationType m_calcType; CalculationType m_calcType;
QList<VariableSampling> m_variables; // 求解时冻结的配置,独立于之后的参数编辑。 QList<VariableSampling> m_variables; // 求解时冻结的配置,独立于之后的参数编辑。
QList<nmDataAttribute> m_fitParameters; // 名称、基准单位和求解快照中的模型值,仅存内存。 QList<nmDataAttribute> m_fitParameters; // 名称、基准单位和本批分析开始时的模型值。
QString m_sWellCode; QString m_sWellCode;
QString m_sWellName; QString m_sWellName;
QString m_sAxisXName; QString m_sAxisXName;
@ -172,9 +172,6 @@ public:
void setMonteCarloCount(int count); void setMonteCarloCount(int count);
bool usesBaseUnits() const; bool usesBaseUnits() const;
void setUsesBaseUnits(bool enabled); void setUsesBaseUnits(bool enabled);
// 记录采样范围对应的求解快照,成功重新求解后再初始化范围。
QString getModelSnapshotId() const;
void setModelSnapshotId(const QString& snapshotId);
// 后台运行时由任务独占,线程结束后交给分析数据管理并由界面修改可见性。 // 后台运行时由任务独占,线程结束后交给分析数据管理并由界面修改可见性。
QSharedPointer<BatchResult> getResults() const; QSharedPointer<BatchResult> getResults() const;
void setResults(const QSharedPointer<BatchResult>& results); void setResults(const QSharedPointer<BatchResult>& results);
@ -191,7 +188,6 @@ private:
CalculationType m_calcType; CalculationType m_calcType;
quint64 m_totalModelCount; quint64 m_totalModelCount;
bool m_usesBaseUnits; bool m_usesBaseUnits;
QString m_modelSnapshotId;
QList<VariableSampling> m_variables; QList<VariableSampling> m_variables;
int m_monteCarloCount; int m_monteCarloCount;
QVector<QVector<double> > m_monteCarloValues; QVector<QVector<double> > m_monteCarloValues;

@ -19,7 +19,6 @@ class vtkUnstructuredGrid;
class nmPebiResultSnapshotBuilder; class nmPebiResultSnapshotBuilder;
class nmPebiResultSnapshotRenderAdapter; class nmPebiResultSnapshotRenderAdapter;
class nmPebiResultSnapshotSerializer; class nmPebiResultSnapshotSerializer;
struct nmPebiSolverInputSnapshot;
/** @brief 一条求解器槽位在最后成功结果中的冻结描述。 */ /** @brief 一条求解器槽位在最后成功结果中的冻结描述。 */
struct NM_DATA_EXPORT nmPebiResultSolverSlot struct NM_DATA_EXPORT nmPebiResultSolverSlot
@ -315,10 +314,6 @@ public:
const nmPebiResultPvtParameters& getPvtParameters() const; const nmPebiResultPvtParameters& getPvtParameters() const;
/** @brief 返回求解时冻结的求解设置。 */ /** @brief 返回求解时冻结的求解设置。 */
const nmPebiResultSolverSettings& getSolverSettings() const; const nmPebiResultSolverSettings& getSolverSettings() const;
/** @brief 最后成功模型的只读输入,仅在本次运行中保留,不参与文件保存。 */
const QSharedPointer<const nmPebiSolverInputSnapshot>& getSensitivityInput() const;
/** @brief 返回与完整输入对应的主井编码。 */
const QString& getSensitivityWellCode() const;
private: private:
struct PressureFrame struct PressureFrame
@ -396,9 +391,6 @@ private:
nmPebiResultPvtParameters m_oPvtParameters; nmPebiResultPvtParameters m_oPvtParameters;
/** @brief 求解时冻结的求解设置。 */ /** @brief 求解时冻结的求解设置。 */
nmPebiResultSolverSettings m_oSolverSettings; nmPebiResultSolverSettings m_oSolverSettings;
/** @brief 输入随成功结果一起发布,敏感性任务只使用其独立副本。 */
QSharedPointer<const nmPebiSolverInputSnapshot> m_sensitivityInput;
QString m_sensitivityWellCode;
// 只有 Builder 和快照序列化器可以在发布前填充私有数据。 // 只有 Builder 和快照序列化器可以在发布前填充私有数据。
friend class nmPebiResultSnapshotBuilder; friend class nmPebiResultSnapshotBuilder;

@ -58,10 +58,6 @@ public:
bool setPvtParameters(const nmPebiResultPvtParameters& oParameters); bool setPvtParameters(const nmPebiResultPvtParameters& oParameters);
/** @brief 设置求解时冻结的网格、时间步和求解器配置。 */ /** @brief 设置求解时冻结的网格、时间步和求解器配置。 */
bool setSolverSettings(const nmPebiResultSolverSettings& oSettings); bool setSolverSettings(const nmPebiResultSolverSettings& oSettings);
/** @brief 将当次实际输入附在结果候选上,仅成功提交后成为分析基准。 */
bool setSensitivityInput(const QSharedPointer<const nmPebiSolverInputSnapshot>& input,
const QString& wellCode);
/** @brief 完成全部结构校验并封闭候选,失败后该 Builder 不可继续使用。 */ /** @brief 完成全部结构校验并封闭候选,失败后该 Builder 不可继续使用。 */
bool finalize(QString* pError = NULL); bool finalize(QString* pError = NULL);
/** /**

@ -27,7 +27,6 @@
#include "nmWxAutomaticfittingStart.h" #include "nmWxAutomaticfittingStart.h"
#include "nmSubWxs_global.h" #include "nmSubWxs_global.h"
#include "nmPebiResultSnapshot.h"
class NM_SUB_WXS_EXPORT nmWxAutomaticFitting : public iDlgBase class NM_SUB_WXS_EXPORT nmWxAutomaticFitting : public iDlgBase
{ {
@ -61,7 +60,6 @@ private:
void setupButtons(); void setupButtons();
void setAutomaticFittingValue(); void setAutomaticFittingValue();
void loadSensitivitySettings(); void loadSensitivitySettings();
const nmPebiResultWellSnapshot* solvedWell(int index) const;
void setParameterRowVisible(QTableWidget* table, int row, bool visible); void setParameterRowVisible(QTableWidget* table, int row, bool visible);
void renumberVisibleParameterRows(QTableWidget* table); void renumberVisibleParameterRows(QTableWidget* table);
void updateParameterVisibility(QTableWidget* table, NM_SOLVER_MODEL_TYPE eType); void updateParameterVisibility(QTableWidget* table, NM_SOLVER_MODEL_TYPE eType);
@ -105,7 +103,6 @@ private:
// 数据成员 // 数据成员
nmDataReservoir reservoirData; nmDataReservoir reservoirData;
QSharedPointer<const nmPebiResultSnapshot> m_solvedSnapshot;
nmDataAutomaticFitting automaticFittingData; nmDataAutomaticFitting automaticFittingData;
nmSensitivityFitSettings* m_sensitivitySettings; nmSensitivityFitSettings* m_sensitivitySettings;
QStringList m_sampledParameters; QStringList m_sampledParameters;

@ -96,7 +96,6 @@ struct nmPebiSolverInputSnapshot
} }
nmPebiGridInputSnapshot m_oGridInput; ///< 网格、场景、井顺序和授权路径值快照。 nmPebiGridInputSnapshot m_oGridInput; ///< 网格、场景、井顺序和授权路径值快照。
QString m_sPrimaryWellCode; ///< 当次成功模型对应的分析主井。
nmPebiGridResult m_oGridResult; ///< 已有网格副本或后台新生成的局部网格。 nmPebiGridResult m_oGridResult; ///< 已有网格副本或后台新生成的局部网格。
/** @brief 在 DataManager 线程冻结的静态属性参考值、插值数据和授权路径。 */ /** @brief 在 DataManager 线程冻结的静态属性参考值、插值数据和授权路径。 */
nmPebiStaticPropertyCalculationInput m_oStaticPropertyInput; nmPebiStaticPropertyCalculationInput m_oStaticPropertyInput;
@ -255,10 +254,10 @@ double calculateSensitivityPressureRmse(const QVector<QVector<double> >& measure
? std::sqrt(squaredError / count) : -1.0; ? std::sqrt(squaredError / count) : -1.0;
} }
bool savedPseudoPressure(const nmDataBinaryTools::NM_PEBI_SCENE& scene, bool inputPseudoPressure(const nmDataBinaryTools::NM_PEBI_SCENE& scene,
double pressure, double& result) double pressure, double& result)
{ {
// 气井分析使用求解时的拟压力表,避免当前编辑改写全局转换器配置。 // 使用本次任务捕获的拟压力表,后台插值不访问全局转换器。
const std::vector<double>& p = scene.PVT.pseudoPressureP; const std::vector<double>& p = scene.PVT.pseudoPressureP;
const std::vector<double>& ps = scene.PVT.pseudoPressurePs; const std::vector<double>& ps = scene.PVT.pseudoPressurePs;
if(p.size() < 2 || p.size() != ps.size() || !isFiniteSolverNumber(pressure)) return false; if(p.size() < 2 || p.size() != ps.size() || !isFiniteSolverNumber(pressure)) return false;
@ -449,7 +448,6 @@ bool captureResultParameters(
nmPebiResultPvtParameters& oPvt = nmPebiResultPvtParameters& oPvt =
oSnapshot.m_oResultPvtParameters; oSnapshot.m_oResultPvtParameters;
oSnapshot.m_sPrimaryWellCode = pDataManager->getPrimaryWellCode();
oPvt.m_dConstantBo = pReservoir->getBo().getValue().toDouble(); oPvt.m_dConstantBo = pReservoir->getBo().getValue().toDouble();
oPvt.m_dConstantMiuo = pReservoir->getMiuo().getValue().toDouble(); oPvt.m_dConstantMiuo = pReservoir->getMiuo().getValue().toDouble();
oPvt.m_dConstantBg = pReservoir->getBg().getValue().toDouble(); oPvt.m_dConstantBg = pReservoir->getBg().getValue().toDouble();
@ -702,8 +700,7 @@ nmCalculationDllPebiSolverTask::nmCalculationDllPebiSolverTask(
nmDataAnalyzeManager* pDataManager, nmDataAnalyzeManager* pDataManager,
const QString& sAutoFitTargetWellName, const QString& sAutoFitTargetWellName,
QObject *parent, QObject *parent,
bool bDeferManualSnapshot, bool bDeferManualSnapshot):
bool bUseSolvedInput):
QThread(parent), QThread(parent),
m_sPostprocessingDir(sPostprocessingDir), m_sPostprocessingDir(sPostprocessingDir),
m_pDataManager(pDataManager != nullptr m_pDataManager(pDataManager != nullptr
@ -734,9 +731,7 @@ nmCalculationDllPebiSolverTask::nmCalculationDllPebiSolverTask(
m_bManagerUseActive = true; m_bManagerUseActive = true;
} }
if(bUseSolvedInput) { if(bDeferManualSnapshot && sAutoFitTargetWellName.isEmpty()) {
m_bInputSnapshotValid = restoreSolvedInput();
} else if(bDeferManualSnapshot && sAutoFitTargetWellName.isEmpty()) {
// 手工求解由界面事件循环分批冻结值输入;start() 必须等状态释放后再调用。 // 手工求解由界面事件循环分批冻结值输入;start() 必须等状态释放后再调用。
m_pManualCaptureState = new nmPebiManualCaptureState(); m_pManualCaptureState = new nmPebiManualCaptureState();
} else { } else {
@ -799,8 +794,8 @@ nmCalculationDllPebiSolverTask* nmCalculationDllPebiSolverTask::createSensitivit
? tr("Solver input is unavailable.") : m_sFailureReason; ? tr("Solver input is unavailable.") : m_sFailureReason;
return nullptr; return nullptr;
} }
if(!m_pInputSnapshot->m_oGridResult.m_bSucceeded || if(!m_pInputSnapshot->m_oGridResult.m_bSucceeded &&
m_pInputSnapshot->m_bRequiresGridCalculation) { !m_pInputSnapshot->m_bRequiresGridCalculation) {
if(errorMessage) *errorMessage = tr("Generate a valid base model before running sensitivity."); if(errorMessage) *errorMessage = tr("Generate a valid base model before running sensitivity.");
return nullptr; return nullptr;
} }
@ -992,9 +987,8 @@ nmCalculationDllPebiSolverTask* nmCalculationDllPebiSolverTask::createSensitivit
task->m_pSensitivityResult->m_variables = frozenSampling.getVariables(); task->m_pSensitivityResult->m_variables = frozenSampling.getVariables();
task->m_pSensitivityResult->m_sWellCode = task->m_sAutoFitTargetWellCode; task->m_pSensitivityResult->m_sWellCode = task->m_sAutoFitTargetWellCode;
if(m_pDataManager != nullptr) { if(m_pDataManager != nullptr) {
const QSharedPointer<const nmPebiResultSnapshot> solved = m_pDataManager->getPebiResultSnapshot(); // 拟合参数基准值读取当前模型,不使用设置窗口中可编辑的拟合初值。
// 拟合参数的蓝色基准点来自原模型,不使用设置窗口中可编辑的拟合初值。 if(improveSettings) {
if(improveSettings && !solved.isNull()) {
const nmDataAutomaticFitting& fit = improveSettings->parameters; const nmDataAutomaticFitting& fit = improveSettings->parameters;
const bool selected[] = { fit.getPermeabilitySelected(), fit.getSkinSelected(), const bool selected[] = { fit.getPermeabilitySelected(), fit.getSkinSelected(),
fit.getWellboreStorageSelected(), fit.getPorositySelected(), fit.getWellboreStorageSelected(), fit.getPorositySelected(),
@ -1002,28 +996,43 @@ nmCalculationDllPebiSolverTask* nmCalculationDllPebiSolverTask::createSensitivit
const char* names[] = { "Permeability", "Skin", "Wellbore storage", "Porosity", const char* names[] = { "Permeability", "Skin", "Wellbore storage", "Porosity",
"Fracture conductivity", "Fracture half length" }; "Fracture conductivity", "Fracture half length" };
const char* units[] = { "mD", "", "m^3/MPa", "", "mD.m", "m" }; const char* units[] = { "mD", "", "m^3/MPa", "", "mD.m", "m" };
for(int i = 0; i < solved->getWellCount(); ++i) { nmDataWellBase* well = m_pDataManager->findWellByName(improveSettings->targetWell);
const nmPebiResultWellSnapshot* well = solved->getWellAt(i); if(well) {
if(well->m_sWellName != improveSettings->targetWell) continue; // 启动前补齐拟合目标井的历史双对数,后台各组共用这份目标数据。
double halfLength = std::numeric_limits<double>::quiet_NaN(); QVector<nmPebiWellInputSnapshot>& wells = task->m_pInputSnapshot->m_oGridInput.m_vecWellInputs;
if(selected[5]) getSolvedFractureHalfLength(solved->getSensitivityInput(), well->m_sWellCode, halfLength); for(int i = 0; i < wells.size(); ++i) {
if(wells[i].m_bRealWell && wells[i].m_sWellCode == well->getWellCode()) {
wells[i].m_vecHistoryLogLog = well->getHistoryLogLog();
break;
}
}
double conductivity = 0.0, halfLength = 0.0;
if(nmDataVerticalFracturedWell* fractured = dynamic_cast<nmDataVerticalFracturedWell*>(well)) {
conductivity = fractured->getDfc().getValue().toDouble();
halfLength = fractured->getFractureHalfLength().getValue().toDouble();
} else if(nmDataHorizontalFracturedWell* fractured = dynamic_cast<nmDataHorizontalFracturedWell*>(well)) {
conductivity = fractured->getDfc().getValue().toDouble();
halfLength = fractured->getFractureHalfLength().getValue().toDouble();
}
const double values[] = { m_pInputSnapshot->m_oStaticPropertyInput.m_dReferencePermeabilityMilliDarcy, const double values[] = { m_pInputSnapshot->m_oStaticPropertyInput.m_dReferencePermeabilityMilliDarcy,
well->m_dSkin, well->m_dWellboreStorage, well->getPerforationCount() > 0 ? well->getPerforation(0)->getSkin().getValue().toDouble() : 0.0,
m_pInputSnapshot->m_oStaticPropertyInput.m_dReferencePorosity, well->m_dDfc, halfLength }; well->getWellboreStorage().getValue().toDouble(),
for(int j = 0; j < 6; ++j) if(selected[j]) m_pInputSnapshot->m_oStaticPropertyInput.m_dReferencePorosity, conductivity, halfLength };
for(int j = 0; j < 6; ++j) if(selected[j]) {
task->m_pSensitivityResult->m_fitParameters.append(nmDataAttribute( task->m_pSensitivityResult->m_fitParameters.append(nmDataAttribute(
QCoreApplication::translate("nmWxAutomaticFitting", names[j]), values[j], units[j])); QCoreApplication::translate("nmWxAutomaticFitting", names[j]), values[j], units[j]));
break; }
} }
} }
for(int i = 0; !solved.isNull() && i < solved->getWellCount(); ++i) { nmDataWellBase* well = m_pDataManager->findWellByCode(task->m_sAutoFitTargetWellCode);
const nmPebiResultWellSnapshot* well = solved->getWellAt(i); if(well) {
if(well->m_sWellCode != task->m_sAutoFitTargetWellCode) continue; // 第三、四种方法按分析主井的实测压力统计误差,与拟合目标井分别取值。
// 历史和基准曲线必须与计算输入属于同一次求解。 const int target = task->m_pInputSnapshot->m_oSensitivityBaseline.m_nTarget;
task->m_pSensitivityResult->m_sWellName = well->m_sWellName; task->m_pInputSnapshot->m_oGridInput.m_vecWellInputs[target].m_vecHistoryPressure = well->getHistoryPressure();
task->m_pSensitivityResult->m_vecHistoryLogLog = well->m_oCurves.m_vecHistoryLogLog; task->m_pSensitivityResult->m_sWellName = well->getWellName();
task->m_pSensitivityResult->m_vecBaseLogLog = well->m_oCurves.m_vecResultLogLog; task->m_pSensitivityResult->m_vecHistoryLogLog = well->getHistoryLogLog();
break; // 对比曲线沿用已有求解结果,分析输入则取当前参数。
task->m_pSensitivityResult->m_vecBaseLogLog = well->getResultLogLog();
} }
} }
return task.take(); return task.take();
@ -1059,37 +1068,6 @@ double nmCalculationDllPebiSolverTask::calculatePressureRmse(
return calculateSensitivityPressureRmse(measured, simulated, nullptr); return calculateSensitivityPressureRmse(measured, simulated, nullptr);
} }
bool nmCalculationDllPebiSolverTask::getSolvedFractureHalfLength(
const QSharedPointer<const nmPebiSolverInputSnapshot>& input,
const QString& wellCode, double& halfLength)
{
// 各类井的几何数组与同类型槽位顺序一致,水平压裂井的各条裂缝共用半长。
if(input.isNull()) return false;
const nmPebiGridInputSnapshot& grid = input->m_oGridInput;
size_t vertical = 0, horizontal = 0;
for(int i = 0; i < grid.m_vecSolverWellOrder.size(); ++i) {
const nmSolverWellRef& well = grid.m_vecSolverWellOrder[i];
const std::vector<double>* fracture = nullptr;
if(well.m_eWellType == Vertical_Fractured_Well) {
if(vertical < grid.m_oGridInput.FractureVerticalWell.size())
fracture = &grid.m_oGridInput.FractureVerticalWell[vertical];
++vertical;
} else if(well.m_eWellType == Horizontal_Fractured_Well) {
if(horizontal < grid.m_oGridInput.MultistageFracturedHorizontalWell.size() &&
!grid.m_oGridInput.MultistageFracturedHorizontalWell[horizontal].empty())
fracture = &grid.m_oGridInput.MultistageFracturedHorizontalWell[horizontal].front();
++horizontal;
}
if(well.m_sWellCode != wellCode) continue;
if(!fracture || fracture->size() < 4) return false;
const double dx = (*fracture)[2] - (*fracture)[0];
const double dy = (*fracture)[3] - (*fracture)[1];
halfLength = std::sqrt(dx * dx + dy * dy) * 0.5;
return qIsFinite(halfLength) && halfLength > 0.0;
}
return false;
}
bool nmCalculationDllPebiSolverTask::improveSensitivityRun(QVector<double>& fittedParameters) bool nmCalculationDllPebiSolverTask::improveSensitivityRun(QVector<double>& fittedParameters)
{ {
// 采样值已经写入本任务;候选只覆盖选中的拟合参数,不触碰管理器和正式结果。 // 采样值已经写入本任务;候选只覆盖选中的拟合参数,不触碰管理器和正式结果。
@ -1102,7 +1080,7 @@ bool nmCalculationDllPebiSolverTask::improveSensitivityRun(QVector<double>& fitt
if(grid.m_vecWellInputs[i].m_bRealWell && if(grid.m_vecWellInputs[i].m_bRealWell &&
grid.m_vecWellInputs[i].m_sWellName == settings.targetWell) { target = i; break; } grid.m_vecWellInputs[i].m_sWellName == settings.targetWell) { target = i; break; }
} }
if(target < 0) return fail(tr("Target well is unavailable in the solved model.")); if(target < 0) return fail(tr("The target well is unavailable."));
const bool selected[] = { fit.getPermeabilitySelected(), fit.getSkinSelected(), const bool selected[] = { fit.getPermeabilitySelected(), fit.getSkinSelected(),
fit.getWellboreStorageSelected(), fit.getPorositySelected(), fit.getWellboreStorageSelected(), fit.getPorositySelected(),
fit.getFractureConductivitySelected(), fit.getFractureHalfLengthSelected() }; fit.getFractureConductivitySelected(), fit.getFractureHalfLengthSelected() };
@ -1176,7 +1154,8 @@ bool nmCalculationDllPebiSolverTask::improveSensitivityRun(QVector<double>& fitt
} }
} }
if(selected[4]) grid.m_vecWellInputs[target].m_dDfc = values[4]; if(selected[4]) grid.m_vecWellInputs[target].m_dDfc = values[4];
m_pInputSnapshot->m_bRequiresGridCalculation = !fractures.empty(); m_pInputSnapshot->m_bRequiresGridCalculation =
m_pInputSnapshot->m_bRequiresGridCalculation || !fractures.empty();
m_sFailureReason.clear(); m_sFailureReason.clear();
calculateSingleRun(); calculateSingleRun();
return m_lastRunSucceeded ? m_autoFitResultLogLog : QVector<QVector<double> >(); return m_lastRunSucceeded ? m_autoFitResultLogLog : QVector<QVector<double> >();
@ -1244,7 +1223,7 @@ bool nmCalculationDllPebiSolverTask::executeSensitivityBatch()
result.m_vecLogLog = m_autoFitResultLogLog; result.m_vecLogLog = m_autoFitResultLogLog;
result.m_vecSemiLog = m_autoFitResultSemiLog; result.m_vecSemiLog = m_autoFitResultSemiLog;
if(m_pSensitivityResult->m_calcType == nmDataSensitive::CALC_MONTE_CARLO) { if(m_pSensitivityResult->m_calcType == nmDataSensitive::CALC_MONTE_CARLO) {
// 使用求解时冻结的目标井实测压力,不读取界面后来修改的数据。 // 使用本批任务开始时读取的实测压力,计算过程中不访问界面数据。
const int target = m_pInputSnapshot->m_oSensitivityBaseline.m_nTarget; const int target = m_pInputSnapshot->m_oSensitivityBaseline.m_nTarget;
result.m_dPressureRmse = calculateSensitivityPressureRmse( result.m_dPressureRmse = calculateSensitivityPressureRmse(
m_pInputSnapshot->m_oGridInput.m_vecWellInputs[target].m_vecHistoryPressure, m_pInputSnapshot->m_oGridInput.m_vecWellInputs[target].m_vecHistoryPressure,
@ -1617,20 +1596,6 @@ void nmCalculationDllPebiSolverTask::releaseDataManagerUse()
} }
} }
bool nmCalculationDllPebiSolverTask::restoreSolvedInput()
{
// 复制最后成功求解的输入,不再从当前编辑模型重新捕获。
if(!m_pDataManager || QThread::currentThread() != m_pDataManager->thread()) return false;
const QSharedPointer<const nmPebiResultSnapshot> solved = m_pDataManager->getPebiResultSnapshot();
if(solved.isNull() || solved->getSensitivityInput().isNull())
return fail(tr("Regenerate the model before running sensitivity."));
*m_pInputSnapshot = *solved->getSensitivityInput();
m_nGridInputRevision = solved->getGridInputRevision();
m_nResultInputRevision = solved->getResultInputRevision();
m_sAutoFitTargetWellCode = m_pInputSnapshot->m_sPrimaryWellCode;
return true;
}
bool nmCalculationDllPebiSolverTask::captureInputSnapshot( bool nmCalculationDllPebiSolverTask::captureInputSnapshot(
const QString& sAutoFitTargetWellName) const QString& sAutoFitTargetWellName)
{ {
@ -1935,6 +1900,8 @@ bool nmCalculationDllPebiSolverTask::execPebiMode()
return false; return false;
} }
// 重开项目或当前几何改变时首次重建,后续采样复用本批网格。
m_pInputSnapshot->m_bRequiresGridCalculation = false;
if(bCreateUnstructuredGrid) { if(bCreateUnstructuredGrid) {
if(oGridResult.m_pUnstructuredGrid == nullptr || if(oGridResult.m_pUnstructuredGrid == nullptr ||
oGridResult.m_pUnstructuredGrid->GetNumberOfCells() <= 0) { oGridResult.m_pUnstructuredGrid->GetNumberOfCells() <= 0) {
@ -2154,13 +2121,13 @@ bool nmCalculationDllPebiSolverTask::buildPebiModeResult(
// p1.pw 是求解器返回的原始井底压力, 后续仍按 MPa 保存到 vvecPressure. // p1.pw 是求解器返回的原始井底压力, 后续仍按 MPa 保存到 vvecPressure.
// 只有气单相变化 PVT 模型的双对数和半对数计算需要改用拟压力. // 只有气单相变化 PVT 模型的双对数和半对数计算需要改用拟压力.
const bool usePseudoPressure = (modelType == static_cast<int>(SMT_Gas_VariablePvt)); const bool usePseudoPressure = (modelType == static_cast<int>(SMT_Gas_VariablePvt));
const bool useSavedPseudoPressure = usePseudoPressure && const bool useInputPseudoPressure = usePseudoPressure &&
m_pInputSnapshot->m_oSensitivityBaseline.m_nTarget >= 0; m_pInputSnapshot->m_oSensitivityBaseline.m_nTarget >= 0;
CalPseudoPressure calPseudoPressure = usePseudoPressure && !useSavedPseudoPressure CalPseudoPressure calPseudoPressure = usePseudoPressure && !useInputPseudoPressure
? getPseudoPressureConverter() ? getPseudoPressureConverter()
: nullptr; : nullptr;
if(usePseudoPressure && !useSavedPseudoPressure && calPseudoPressure == nullptr) { if(usePseudoPressure && !useInputPseudoPressure && calPseudoPressure == nullptr) {
qWarning() << "Failed to load the gas pseudo-pressure converter."; qWarning() << "Failed to load the gas pseudo-pressure converter.";
return fail(tr("Failed to load the gas pseudo-pressure converter.")); return fail(tr("Failed to load the gas pseudo-pressure converter."));
} }
@ -2298,9 +2265,9 @@ bool nmCalculationDllPebiSolverTask::buildPebiModeResult(
double pressureForLog = p1.pw[wellIdx][i]; double pressureForLog = p1.pw[wellIdx][i];
if(usePseudoPressure if(usePseudoPressure
&& (!isFiniteSolverNumber(pressureForLog) && (!isFiniteSolverNumber(pressureForLog)
// 敏感性任务只查求解时保存的表,不能读取已被编辑或其他窗口改写的全局配置。 // 后台任务使用本次输入中的转换表,避免其他窗口改写全局配置。
|| !(useSavedPseudoPressure || !(useInputPseudoPressure
? savedPseudoPressure( ? inputPseudoPressure(
m_pInputSnapshot->m_oGridInput.m_oScene, pressureForLog, pressureForLog) m_pInputSnapshot->m_oGridInput.m_oScene, pressureForLog, pressureForLog)
: calPseudoPressure(pressureForLog, pressureForLog, -1)) : calPseudoPressure(pressureForLog, pressureForLog, -1))
|| !isFiniteSolverNumber(pressureForLog))) { || !isFiniteSolverNumber(pressureForLog))) {
@ -2551,17 +2518,6 @@ bool nmCalculationDllPebiSolverTask::buildPebiResultSnapshotCandidate(
try { try {
nmPebiResultSnapshotBuilder oBuilder; nmPebiResultSnapshotBuilder oBuilder;
// 复用现有输入结构,在转移井制度和曲线前留一份只读副本,随成功结果发布。
QSharedPointer<nmPebiSolverInputSnapshot> sensitivityInput(
new nmPebiSolverInputSnapshot(*m_pInputSnapshot));
sensitivityInput->m_bAutoFitTargetOnly = true;
sensitivityInput->m_bRequiresGridCalculation = false;
sensitivityInput->m_bGridResultNeedsCommit = false;
// 分析只需要 DLL 网格值,不保留正式结果的 VTK 可写引用。
sensitivityInput->m_pBaseGrid = NULL;
sensitivityInput->m_pSourceBaseGrid = NULL;
sensitivityInput->m_oGridResult.m_pUnstructuredGrid = NULL;
if(!oBuilder.setSensitivityInput(sensitivityInput, m_pInputSnapshot->m_sPrimaryWellCode)) return false;
if(!oBuilder.setInputRevisions(m_nGridInputRevision, if(!oBuilder.setInputRevisions(m_nGridInputRevision,
m_nResultInputRevision)) { m_nResultInputRevision)) {
return false; return false;

@ -4937,7 +4937,7 @@ bool nmDataAnalyzeManager::commitPebiResultSnapshot(
m_sCurrentResultWellInstanceId = sNextWellInstanceId; m_sCurrentResultWellInstanceId = sNextWellInstanceId;
pCandidate.clear(); pCandidate.clear();
rebindPebiResultSnapshotToWells(); rebindPebiResultSnapshotToWells();
// 新模型成功发布后丢弃旧敏感性结果,采样设置仍由窗口按新快照刷新。 // 新模型成功发布后丢弃旧敏感性结果,采样参数由窗口读取当前模型。
if(m_pSensitiveData != nullptr && !m_pSensitiveData->getResults().isNull()) { if(m_pSensitiveData != nullptr && !m_pSensitiveData->getResults().isNull()) {
m_pSensitiveData->setResults(QSharedPointer<nmDataSensitive::BatchResult>()); m_pSensitiveData->setResults(QSharedPointer<nmDataSensitive::BatchResult>());
emit sigSensitiveResultsChanged(); emit sigSensitiveResultsChanged();

@ -512,7 +512,6 @@ nmDataSensitive& nmDataSensitive::operator=(const nmDataSensitive& other)
m_calcType = other.m_calcType; m_calcType = other.m_calcType;
m_totalModelCount = other.m_totalModelCount; m_totalModelCount = other.m_totalModelCount;
m_usesBaseUnits = other.m_usesBaseUnits; m_usesBaseUnits = other.m_usesBaseUnits;
m_modelSnapshotId = other.m_modelSnapshotId;
m_variables = other.m_variables; m_variables = other.m_variables;
m_monteCarloCount = other.m_monteCarloCount; m_monteCarloCount = other.m_monteCarloCount;
m_monteCarloValues = other.m_monteCarloValues; m_monteCarloValues = other.m_monteCarloValues;
@ -532,8 +531,6 @@ rapidjson::Value nmDataSensitive::ToJsonValue(
rapidjson::Value root(rapidjson::kObjectType); rapidjson::Value root(rapidjson::kObjectType);
// 旧配置中的采样值是显示单位,新配置统一保存业务基准单位。 // 旧配置中的采样值是显示单位,新配置统一保存业务基准单位。
root.AddMember("UsesBaseUnits", m_usesBaseUnits, allocator); root.AddMember("UsesBaseUnits", m_usesBaseUnits, allocator);
// 随配置保存范围所属的快照,项目重开后仍能识别尚未刷新的范围。
root.AddMember("ModelSnapshotId", rapidjson::Value(m_modelSnapshotId.toUtf8().constData(), allocator).Move(), allocator);
// 顶部 Calculation type // 顶部 Calculation type
root.AddMember("CalculationType", root.AddMember("CalculationType",
@ -560,8 +557,6 @@ rapidjson::Value nmDataSensitive::ToJsonValue(
// 从 RapidJSON Value 反序列化数据到 nmDataSensitive // 从 RapidJSON Value 反序列化数据到 nmDataSensitive
void nmDataSensitive::FromJsonValue(const rapidjson::Value& jsonValue) void nmDataSensitive::FromJsonValue(const rapidjson::Value& jsonValue)
{ {
// 旧项目没有快照标识,首次打开时按已有求解结果初始化范围。
m_modelSnapshotId = sensitiveTextFromJson(jsonValue, "ModelSnapshotId");
// 旧项目没有 Results 时保持空结果。 // 旧项目没有 Results 时保持空结果。
m_results.clear(); m_results.clear();
if(jsonValue.HasMember("Results") && jsonValue["Results"].IsObject()) { if(jsonValue.HasMember("Results") && jsonValue["Results"].IsObject()) {
@ -747,19 +742,6 @@ bool nmDataSensitive::usesBaseUnits() const
return m_usesBaseUnits; return m_usesBaseUnits;
} }
QString nmDataSensitive::getModelSnapshotId() const
{
// 范围版本独立于敏感性批次结果。
return m_modelSnapshotId;
}
void nmDataSensitive::setModelSnapshotId(const QString& snapshotId)
{
// 仅在同步模型与范围后登记,编辑采样值不改变此标识。
if(snapshotId != m_modelSnapshotId) clearMonteCarloSamples();
m_modelSnapshotId = snapshotId;
}
QSharedPointer<nmDataSensitive::BatchResult> nmDataSensitive::getResults() const QSharedPointer<nmDataSensitive::BatchResult> nmDataSensitive::getResults() const
{ {
// 曲线容器按共享引用交给界面,重复打开不复制整批结果。 // 曲线容器按共享引用交给界面,重复打开不复制整批结果。

@ -12,18 +12,6 @@
#include <QUuid> #include <QUuid>
const QSharedPointer<const nmPebiSolverInputSnapshot>& nmPebiResultSnapshot::getSensitivityInput() const
{
// 管理器仅持有只读输入,复制和采样由求解模块完成。
return m_sensitivityInput;
}
const QString& nmPebiResultSnapshot::getSensitivityWellCode() const
{
// 界面定位求解时的井,不能因编辑主井而改变参数基准。
return m_sensitivityWellCode;
}
namespace { namespace {
bool deepCopyNamedArray(vtkFieldData* pSource, bool deepCopyNamedArray(vtkFieldData* pSource,

@ -885,17 +885,6 @@ bool nmPebiResultSnapshotBuilder::setSolverSettings(
return true; return true;
} }
bool nmPebiResultSnapshotBuilder::setSensitivityInput(
const QSharedPointer<const nmPebiSolverInputSnapshot>& input, const QString& wellCode)
{
// 与结果候选一起发布,求解失败或取消不会替换上次输入。
if(!canWrite()) return false;
if(input.isNull() || wellCode.isEmpty()) return fail("PEBI sensitivity input is incomplete.");
m_pCandidate->m_sensitivityInput = input;
m_pCandidate->m_sensitivityWellCode = wellCode;
return true;
}
bool nmPebiResultSnapshotBuilder::validate(QString* pError) bool nmPebiResultSnapshotBuilder::validate(QString* pError)
{ {
// 第一部分校验快照身份、输入版本和所有必填结果载荷。 // 第一部分校验快照身份、输入版本和所有必填结果载荷。

@ -1,7 +1,6 @@
#include "nmWxAutomaticFitting.h" #include "nmWxAutomaticFitting.h"
#include "nmCalculationAutoFitPSO.h" #include "nmCalculationAutoFitPSO.h"
#include "nmCalculationAutoFitLM.h" #include "nmCalculationAutoFitLM.h"
#include "nmCalculationDllPebiSolverTask.h"
#include "nmWxAutomaticfittingStart.h" #include "nmWxAutomaticfittingStart.h"
#include "nmWxParameterProperty.h" #include "nmWxParameterProperty.h"
#include "nmAttrRegistry.h" #include "nmAttrRegistry.h"
@ -133,8 +132,16 @@ void nmWxAutomaticFitting::updateParameterVisibility(QTableWidget* table, NM_SOL
// Dfc 只属于垂直压裂井和多段压裂水平井。普通井隐藏并取消勾选, // Dfc 只属于垂直压裂井和多段压裂水平井。普通井隐藏并取消勾选,
// 防止切换目标井后不可见的裂缝参数仍进入拟合参数向量。 // 防止切换目标井后不可见的裂缝参数仍进入拟合参数向量。
bool showFractureConductivity = false; bool showFractureConductivity = false;
const nmPebiResultWellSnapshot* well = solvedWell(m_targetWellCombo->currentIndex()); nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance();
if(well) showFractureConductivity = well->m_bHasDfc; if(manager && m_targetWellCombo) {
nmDataWellBase* targetWell = manager->findWellByName(m_targetWellCombo->currentText());
if(targetWell) {
NM_WELL_MODEL wellType = targetWell->getWellType();
showFractureConductivity =
wellType == NM_WELL_MODEL::Vertical_Fractured_Well ||
wellType == NM_WELL_MODEL::Horizontal_Fractured_Well;
}
}
setParameterRowVisible(table, 5, showFractureConductivity); // Dfc setParameterRowVisible(table, 5, showFractureConductivity); // Dfc
setParameterRowVisible(table, 6, showFractureConductivity); // 裂缝半长 setParameterRowVisible(table, 6, showFractureConductivity); // 裂缝半长
@ -423,12 +430,6 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent, nmSensitivityFitSett
// 获取数据 // 获取数据
nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance(); nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance();
reservoirData = pManager->getReservoirDataCopy(); reservoirData = pManager->getReservoirDataCopy();
// 初值来自最后成功模型;当前储层副本只保留原有属性及单位。
m_solvedSnapshot = pManager->getPebiResultSnapshot();
if(!m_solvedSnapshot.isNull()) {
reservoirData.getPermeability().setValue(m_solvedSnapshot->getReservoirParameters().m_dPermeability);
reservoirData.getPorosity().setValue(m_solvedSnapshot->getReservoirParameters().m_dPorosity);
}
automaticFittingData = pManager->getAutomaticFittingDataCopy(); automaticFittingData = pManager->getAutomaticFittingDataCopy();
// 自动范围始终开启;用户在表格中修改上下限后,itemChanged 会临时切换为手工范围。 // 自动范围始终开启;用户在表格中修改上下限后,itemChanged 会临时切换为手工范围。
m_autoParameterRanges = true; m_autoParameterRanges = true;
@ -446,9 +447,7 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent, nmSensitivityFitSett
} }
// 每次打开都按当前模型参数生成范围,不沿用上次的上下限。 // 每次打开都按当前模型参数生成范围,不沿用上次的上下限。
initializeSuggestedParameterRanges(); initializeSuggestedParameterRanges();
// 新模型重新初始化范围,同一模型才恢复上次确认的拟合设置。 if(m_sensitivitySettings && m_sensitivitySettings->confirmed) loadSensitivitySettings();
if(m_sensitivitySettings && m_sensitivitySettings->confirmed && !m_solvedSnapshot.isNull() &&
m_sensitivitySettings->snapshotId == m_solvedSnapshot->getSnapshotId()) loadSensitivitySettings();
m_updatingParameterRanges = false; m_updatingParameterRanges = false;
DEBUG_UI("AutoFitting Constructor completed"); DEBUG_UI("AutoFitting Constructor completed");
@ -456,8 +455,10 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent, nmSensitivityFitSett
void nmWxAutomaticFitting::loadSensitivitySettings() void nmWxAutomaticFitting::loadSensitivitySettings()
{ {
// 同一次模型的拟合设置仍恢复用户确认的范围与初值。 // 恢复已确认的勾选和拟合设置,初值仍读取当前模型。
m_targetWellCombo->setCurrentIndex(m_targetWellCombo->findText(m_sensitivitySettings->targetWell)); const int targetIndex = m_targetWellCombo->findText(m_sensitivitySettings->targetWell);
if(targetIndex < 0) return;
m_targetWellCombo->setCurrentIndex(targetIndex);
automaticFittingData = m_sensitivitySettings->parameters; automaticFittingData = m_sensitivitySettings->parameters;
m_autoParameterRanges = false; m_autoParameterRanges = false;
// 敏感性设置恢复时也固定使用 LM。 // 敏感性设置恢复时也固定使用 LM。
@ -478,7 +479,9 @@ void nmWxAutomaticFitting::loadSensitivitySettings()
for(int i = 0; i < 6; ++i) { for(int i = 0; i < 6; ++i) {
checks[i]->setChecked(selected[i] && checks[i]->isEnabled()); checks[i]->setChecked(selected[i] && checks[i]->isEnabled());
setParameterRange(rows[i], lower[i]->getValue().toDouble(), upper[i]->getValue().toDouble()); setParameterRange(rows[i], lower[i]->getValue().toDouble(), upper[i]->getValue().toDouble());
m_parameterTable->item(rows[i], 3)->setText(QString::number(m_sensitivitySettings->initialValues[i], 'g', 10)); const double currentValue = m_parameterTable->item(rows[i], 3)->text().toDouble();
if(currentValue < lower[i]->getValue().toDouble() || currentValue > upper[i]->getValue().toDouble())
updateRangeForParameter(rows[i], currentValue);
} }
} }
@ -731,14 +734,29 @@ void nmWxAutomaticFitting::setupControlPanel()
m_targetWellCombo = new QComboBox(); m_targetWellCombo = new QComboBox();
// 目标井先加入当前分析主井,再加入已勾选且具备流量、压力数据的包含井。 // 目标井先加入当前分析主井,再加入已勾选且具备流量、压力数据的包含井。
// 井列表也取自求解快照,未求解的新井不作为旧模型的拟合目标。 nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance();
for(int i = 0; !m_solvedSnapshot.isNull() && i < m_solvedSnapshot->getWellCount(); ++i) { if(pManager != nullptr) {
const nmPebiResultWellSnapshot* well = m_solvedSnapshot->getWellAt(i); const QString sPrimaryWellCode = pManager->getPrimaryWellCode();
if(well->m_sWellCode == m_solvedSnapshot->getSensitivityWellCode()) nmDataWellBase* pPrimaryWell = pManager->findWellByCode(sPrimaryWellCode);
m_targetWellCombo->insertItem(0, well->m_sWellName, well->m_sWellInstanceId); if(pPrimaryWell != nullptr) {
else if(well->m_eWellMode == NM_CaseWell_RateControlled && !well->m_oCurves.m_vecHistoryPressure.isEmpty()) m_targetWellCombo->addItem(pPrimaryWell->getWellName());
m_targetWellCombo->addItem(well->m_sWellName, well->m_sWellInstanceId); }
}
if(pManager->getIncludeOtherWells()) {
const QVector<nmCalculationWellRef> vecIncludedWells =
pManager->getIncludedCalculationWells();
for(int nIndex = 0; nIndex < vecIncludedWells.size(); ++nIndex) {
const QString& sWellCode = vecIncludedWells[nIndex].m_sWellCode;
nmDataWellBase* pWellData = pManager->findWellByCode(sWellCode);
if(pWellData == nullptr || sWellCode == sPrimaryWellCode ||
pWellData->getFlowSegmentCount() <= 0 ||
pWellData->getPressurePoints().size() < 2) {
continue;
}
m_targetWellCombo->addItem(pWellData->getWellName());
}
}
}
connect(m_targetWellCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onWellSelected(int))); connect(m_targetWellCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onWellSelected(int)));
m_targetWellCombo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); m_targetWellCombo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon);
@ -872,7 +890,6 @@ void nmWxAutomaticFitting::onAccept()
// 仅提交给敏感性窗口;取消、关闭及普通拟合均不经过此分支。 // 仅提交给敏感性窗口;取消、关闭及普通拟合均不经过此分支。
m_sensitivitySettings->parameters = automaticFittingData; m_sensitivitySettings->parameters = automaticFittingData;
m_sensitivitySettings->targetWell = m_targetWellCombo->currentText(); m_sensitivitySettings->targetWell = m_targetWellCombo->currentText();
m_sensitivitySettings->snapshotId = m_solvedSnapshot->getSnapshotId();
m_sensitivitySettings->initialValues.clear(); m_sensitivitySettings->initialValues.clear();
const int rows[] = {0, 1, 2, 3, 5, 6}; const int rows[] = {0, 1, 2, 3, 5, 6};
for(int i = 0; i < 6; ++i) for(int i = 0; i < 6; ++i)
@ -981,43 +998,49 @@ void nmWxAutomaticFitting::onReject()
reject(); reject();
} }
const nmPebiResultWellSnapshot* nmWxAutomaticFitting::solvedWell(int index) const
{
// 用快照中的稳定标识定位井,不依赖后来编辑的井名称或参数。
return m_solvedSnapshot.isNull() ? nullptr :
m_solvedSnapshot->findWell(m_targetWellCombo->itemData(index).toString());
}
void nmWxAutomaticFitting::onWellSelected(int index) void nmWxAutomaticFitting::onWellSelected(int index)
{ {
// 切井也读取同一次成功模型,默认范围继续使用原有生成及物理限制逻辑。 // 每次都读取模型实际保留的井参数,避免拟合结束后切井又恢复到打开窗口时的旧副本。
const nmPebiResultWellSnapshot* targetWell = solvedWell(index); nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance();
if(!manager) {
return;
}
nmDataWellBase* targetWell = manager->findWellByName(m_targetWellCombo->itemText(index));
if(!targetWell) { if(!targetWell) {
return; return;
} }
const bool wasUpdatingRanges = m_updatingParameterRanges; const bool wasUpdatingRanges = m_updatingParameterRanges;
m_updatingParameterRanges = true; m_updatingParameterRanges = true;
updateParameterVisibility(m_parameterTable, updateParameterVisibility(m_parameterTable, manager->getSolverModelType());
static_cast<NM_SOLVER_MODEL_TYPE>(m_solvedSnapshot->getSolverSettings().m_nSolverModelType));
if(targetWell->m_bHasSkin) { nmDataPerforation* perforation = targetWell->getPerforation(0);
const double skin = targetWell->m_dSkin; if(perforation) {
const double skin = perforation->getSkin().getValue().toDouble();
m_parameterTable->item(1, 3)->setText(QString::number(skin, 'g', 10)); m_parameterTable->item(1, 3)->setText(QString::number(skin, 'g', 10));
if(m_autoParameterRanges) { if(m_autoParameterRanges) {
updateRangeForParameter(1, skin); updateRangeForParameter(1, skin);
} }
} }
const double storage = targetWell->m_dWellboreStorage; const double storage = targetWell->getWellboreStorage().getValue().toDouble();
m_parameterTable->item(2, 3)->setText(QString::number(storage, 'g', 10)); m_parameterTable->item(2, 3)->setText(QString::number(storage, 'g', 10));
if(m_autoParameterRanges) { if(m_autoParameterRanges) {
updateRangeForParameter(2, storage); updateRangeForParameter(2, storage);
} }
const double conductivity = targetWell->m_dDfc; bool fracturedWell = false;
double conductivity = 0.0;
double halfLength = 0.0; double halfLength = 0.0;
if(targetWell->m_bHasDfc && nmCalculationDllPebiSolverTask::getSolvedFractureHalfLength( if(nmDataVerticalFracturedWell* well = dynamic_cast<nmDataVerticalFracturedWell*>(targetWell)) {
m_solvedSnapshot->getSensitivityInput(), targetWell->m_sWellCode, halfLength)) { conductivity = well->getDfc().getValue().toDouble();
halfLength = well->getFractureHalfLength().getValue().toDouble();
fracturedWell = true;
} else if(nmDataHorizontalFracturedWell* well = dynamic_cast<nmDataHorizontalFracturedWell*>(targetWell)) {
conductivity = well->getDfc().getValue().toDouble();
halfLength = well->getFractureHalfLength().getValue().toDouble();
fracturedWell = true;
}
if(fracturedWell) {
m_parameterTable->item(5, 3)->setText(QString::number(conductivity, 'g', 10)); m_parameterTable->item(5, 3)->setText(QString::number(conductivity, 'g', 10));
m_parameterTable->item(6, 3)->setText(QString::number(halfLength, 'g', 10)); m_parameterTable->item(6, 3)->setText(QString::number(halfLength, 'g', 10));
if(m_autoParameterRanges) { if(m_autoParameterRanges) {
@ -1390,12 +1413,8 @@ void nmWxAutomaticFitting::refreshParametersFromModel()
return; return;
} }
// 拟合结束后重新取得最后成功快照,失败时仍沿用之前成功模型的初值。 // 同步储层副本,避免下一次保存设置时把未拟合的储层属性覆盖成旧值。
reservoirData = manager->getReservoirDataCopy(); reservoirData = manager->getReservoirDataCopy();
m_solvedSnapshot = manager->getPebiResultSnapshot();
if(m_solvedSnapshot.isNull()) return;
reservoirData.getPermeability().setValue(m_solvedSnapshot->getReservoirParameters().m_dPermeability);
reservoirData.getPorosity().setValue(m_solvedSnapshot->getReservoirParameters().m_dPorosity);
const bool wasUpdatingRanges = m_updatingParameterRanges; const bool wasUpdatingRanges = m_updatingParameterRanges;
m_updatingParameterRanges = true; m_updatingParameterRanges = true;
m_parameterTable->item(0, 3)->setText(QString::number( m_parameterTable->item(0, 3)->setText(QString::number(
@ -1403,7 +1422,8 @@ void nmWxAutomaticFitting::refreshParametersFromModel()
m_parameterTable->item(3, 3)->setText(QString::number( m_parameterTable->item(3, 3)->setText(QString::number(
reservoirData.getPorosity().getValue().toDouble(), 'g', 10)); reservoirData.getPorosity().getValue().toDouble(), 'g', 10));
// 先刷新快照初值,再统一重建范围,避免即时校验读到新旧值混合的表格。 // 成功或失败都从当前目标井读取已落地的参数,不采用优化器尚未写回的最佳候选。
// 先刷新全部初值,再统一重建范围,避免即时校验读到新旧值混合的表格。
m_autoParameterRanges = false; m_autoParameterRanges = false;
onWellSelected(m_targetWellCombo->currentIndex()); onWellSelected(m_targetWellCombo->currentIndex());
m_autoParameterRanges = true; m_autoParameterRanges = true;

@ -1450,10 +1450,10 @@ void nmWxNumericalDesign::onForecastClicked()
void nmWxNumericalDesign::updateSensitiveButtonEnabled() void nmWxNumericalDesign::updateSensitiveButtonEnabled()
{ {
// 只有本次运行中成功求解过才有完整输入,编辑参数不会改变已有分析基准。 // 已有成功模型即可分析或拟合,重开项目后同样可用。
const QSharedPointer<const nmPebiResultSnapshot> solved = m_pDataManager const QSharedPointer<const nmPebiResultSnapshot> solved = m_pDataManager
? m_pDataManager->getPebiResultSnapshot() : QSharedPointer<const nmPebiResultSnapshot>(); ? m_pDataManager->getPebiResultSnapshot() : QSharedPointer<const nmPebiResultSnapshot>();
const bool available = !solved.isNull() && !solved->getSensitivityInput().isNull(); const bool available = !solved.isNull();
m_pSensitiveBtn->setEnabled(available); m_pSensitiveBtn->setEnabled(available);
m_pAutomaticfittingBtn->setEnabled(available); m_pAutomaticfittingBtn->setEnabled(available);
m_pSensitiveBtn->setToolTip(available ? QString() : tr("Generate the model to enable sensitivity analysis.")); m_pSensitiveBtn->setToolTip(available ? QString() : tr("Generate the model to enable sensitivity analysis."));

@ -6,7 +6,10 @@
#include "nmDataReservoir.h" #include "nmDataReservoir.h"
#include "nmDataWellBase.h" #include "nmDataWellBase.h"
#include "nmDataPerforation.h" #include "nmDataPerforation.h"
#include "nmPebiResultSnapshot.h" #include "iSubWndFitting.h"
#include "mGui/mGuiAnal/iAnalRun.h"
#pragma comment(lib, "mGuiAnal.lib")
#include "nmCalculationDllPebiSolverTask.h" #include "nmCalculationDllPebiSolverTask.h"
#include "iUnitHelper.h" #include "iUnitHelper.h"
#include "iUnitGroup.h" #include "iUnitGroup.h"
@ -1387,21 +1390,11 @@ void nmWxSensitive::closeEvent(QCloseEvent* event)
void nmWxSensitive::refreshModelValues() void nmWxSensitive::refreshModelValues()
{ {
// 六个参数以最后成功求解的快照为准,编辑中的模型只提供属性类型和显示单位。 // 每次打开或模型更新时读取当前参数,沿用已有单位和物理范围限制。
const QSharedPointer<const nmPebiResultSnapshot> snapshot = m_pDataManager nmDataWellBase* well = m_pDataManager
? m_pDataManager->getPebiResultSnapshot() : QSharedPointer<const nmPebiResultSnapshot>(); ? m_pDataManager->findWellByCode(m_pDataManager->getPrimaryWellCode()) : nullptr;
const bool resetRanges = !snapshot.isNull() &&
snapshot->getSnapshotId() != m_sensitiveData.getModelSnapshotId();
const QString wellCode = !snapshot.isNull() ? snapshot->getSensitivityWellCode() : QString();
nmDataWellBase* well = m_pDataManager ? m_pDataManager->findWellByCode(wellCode) : nullptr;
nmDataReservoir* reservoir = m_pDataManager ? m_pDataManager->getReservoirData() : nullptr; nmDataReservoir* reservoir = m_pDataManager ? m_pDataManager->getReservoirData() : nullptr;
const nmPebiResultWellSnapshot* solvedWell = nullptr; bool modelChanged = false;
for(int i = 0; !snapshot.isNull() && i < snapshot->getWellCount(); ++i) {
if(snapshot->getWellAt(i)->m_sWellCode == wellCode) {
solvedWell = snapshot->getWellAt(i);
break;
}
}
const QStringList names = QStringList() << "Skin" << "C" << "Pi" << "k" << "h" << QString(QChar(0x03c6)); const QStringList names = QStringList() << "Skin" << "C" << "Pi" << "k" << "h" << QString(QChar(0x03c6));
for (int n = 0; n < names.size(); ++n) { for (int n = 0; n < names.size(); ++n) {
nmDataAttribute* attribute = nullptr; nmDataAttribute* attribute = nullptr;
@ -1427,21 +1420,6 @@ void nmWxSensitive::refreshModelValues()
index = m_sensitiveData.getVariableCount() - 1; index = m_sensitiveData.getVariableCount() - 1;
} }
nmDataSensitive::VariableSampling& variable = m_sensitiveData.getVariable(index); nmDataSensitive::VariableSampling& variable = m_sensitiveData.getVariable(index);
// 在局部属性上替换数值,不能把未求解的编辑值带入 Model value 或默认范围。
nmDataAttribute solvedAttribute;
if (!snapshot.isNull()) {
if (attribute) solvedAttribute = *attribute;
else solvedAttribute.setUnit(samplingBaseUnit(name));
attribute = &solvedAttribute;
const nmPebiResultReservoirParameters& solvedReservoir = snapshot->getReservoirParameters();
if (name == "Skin" || name == "C") {
if (!solvedWell || (name == "Skin" && !solvedWell->m_bHasSkin)) attribute = nullptr;
else solvedAttribute.setValue(name == "Skin" ? solvedWell->m_dSkin : solvedWell->m_dWellboreStorage);
} else if (name == "Pi") solvedAttribute.setValue(solvedReservoir.m_dInitialPressure);
else if (name == "k") solvedAttribute.setValue(solvedReservoir.m_dPermeability);
else if (name == "h") solvedAttribute.setValue(solvedReservoir.m_dThickness);
else if (name == QString(QChar(0x03c6))) solvedAttribute.setValue(solvedReservoir.m_dPorosity);
}
double physicalMin = 0.0, physicalMax = 0.0; double physicalMin = 0.0, physicalMax = 0.0;
if (!attribute || !samplingPhysicalRange(name, physicalMin, physicalMax)) { if (!attribute || !samplingPhysicalRange(name, physicalMin, physicalMax)) {
variable.setEnabled(false); variable.setEnabled(false);
@ -1468,6 +1446,8 @@ void nmWxSensitive::refreshModelValues()
for (int i = 0; migrated && i < values.size(); ++i) for (int i = 0; migrated && i < values.size(); ++i)
migrated = convertSamplingValue(name, values[i], oldUnit, baseUnit, values[i]); migrated = convertSamplingValue(name, values[i], oldUnit, baseUnit, values[i]);
} }
const bool resetRanges = fresh || !nearlyEqualSamplingValue(previousModel, attribute->getValue().toDouble());
modelChanged = modelChanged || resetRanges;
// 识别 Skin、Pi 的自动默认范围,按当前模型更新,保留用户自定义区间。 // 识别 Skin、Pi 的自动默认范围,按当前模型更新,保留用户自定义区间。
const bool previousDefault = variable.getMode() == nmDataSensitive::VariableSampling::MODE_AUTOMATIC && const bool previousDefault = variable.getMode() == nmDataSensitive::VariableSampling::MODE_AUTOMATIC &&
((nearlyEqualSamplingValue(minimum, qMin(previousModel / 2.0, previousModel * 2.0)) && ((nearlyEqualSamplingValue(minimum, qMin(previousModel / 2.0, previousModel * 2.0)) &&
@ -1480,7 +1460,7 @@ void nmWxSensitive::refreshModelValues()
variable.getModelValue().getMaxValue().isValid() && variable.getModelValue().getMaxValue().isValid() &&
nearlyEqualSamplingValue(minimum, variable.getModelValue().getMinValue().toDouble()) && nearlyEqualSamplingValue(minimum, variable.getModelValue().getMinValue().toDouble()) &&
nearlyEqualSamplingValue(maximum, variable.getModelValue().getMaxValue().toDouble()))); nearlyEqualSamplingValue(maximum, variable.getModelValue().getMaxValue().toDouble())));
// 新的正式求解结果重新建立范围,采样数量、模式及手动值保持原设置。 // 当前模型值变化时重新建立范围,采样数量、模式及手动值保持原设置。
if (resetRanges || !migrated || ((name == "Skin" || name == "Pi") && previousDefault)) { if (resetRanges || !migrated || ((name == "Skin" || name == "Pi") && previousDefault)) {
const double model = attribute->getValue().toDouble(); const double model = attribute->getValue().toDouble();
minimum = qMin(model / 2.0, model * 2.0); minimum = qMin(model / 2.0, model * 2.0);
@ -1526,14 +1506,12 @@ void nmWxSensitive::refreshModelValues()
if (!isSupportedSamplingVariable(m_sensitiveData.getVariable(i).getVarName())) if (!isSupportedSamplingVariable(m_sensitiveData.getVariable(i).getVarName()))
m_sensitiveData.getVariable(i).setEnabled(false); m_sensitiveData.getVariable(i).setEnabled(false);
m_sensitiveData.setUsesBaseUnits(true); m_sensitiveData.setUsesBaseUnits(true);
if (!snapshot.isNull()) m_sensitiveData.setModelSnapshotId(snapshot->getSnapshotId()); if (modelChanged) m_sensitiveData.clearMonteCarloSamples();
} }
void nmWxSensitive::onModelResultChanged() void nmWxSensitive::onModelResultChanged()
{ {
// 查看井切换也会发送快照通知;只有新求解快照才刷新已打开窗口。 // 通知后按当前模型刷新数值;未改变的参数保留用户范围。
const QSharedPointer<const nmPebiResultSnapshot> snapshot = m_pDataManager->getPebiResultSnapshot();
if (snapshot.isNull() || snapshot->getSnapshotId() == m_sensitiveData.getModelSnapshotId()) return;
m_loading = true; m_loading = true;
refreshModelValues(); refreshModelValues();
loadDataToUI(true); loadDataToUI(true);
@ -2495,6 +2473,34 @@ void nmWxSensitive::onTreeItemChanged(QTreeWidgetItem* item, int column)
void nmWxSensitive::onGenerateClicked() void nmWxSensitive::onGenerateClicked()
{ {
if(m_calculationRunning || !m_pDataManager || !saveDataFromUI()) return; if(m_calculationRunning || !m_pDataManager || !saveDataFromUI()) return;
nmDataWellBase* well = m_pDataManager->findWellByCode(m_pDataManager->getPrimaryWellCode());
if(!well || well->getWellName().isEmpty()) {
QMessageBox::warning(this, tr("Sensitivity"), tr("The target well is unavailable."));
return;
}
// 气井转换器使用本窗口所属分析的配置,不能随当前活动窗口切换。
if(m_pDataManager->getSolverModelType() == SMT_Gas_VariablePvt) {
iSubWndFitting* fitting = nullptr;
QMap<iSubWndFitting*, nmDataAnalyzeManager*>::const_iterator it =
nmDataAnalyzeManager::s_mapDataAnalManager.constBegin();
for(; it != nmDataAnalyzeManager::s_mapDataAnalManager.constEnd(); ++it) {
if(it.value() == m_pDataManager) { fitting = it.key(); break; }
}
iAnalRun* analRun = fitting ? fitting->getAnalRun() : nullptr;
// 第四种可选择其他井拟合,其历史曲线也要使用本次的拟压力配置。
nmDataWellBase* fitWell = (calculationTypeCombo->currentIndex() == 3) && !m_improveSettings.isNull()
? m_pDataManager->findWellByName(m_improveSettings->targetWell) : nullptr;
if(!analRun || !analRun->configPsAbouts(true, fitting->getModelOption(),
false, fitting->getAllWxPtr()) ||
!m_pDataManager->updateWellHistoryData(well) ||
(fitWell && fitWell != well && !m_pDataManager->updateWellHistoryData(fitWell))) {
QMessageBox::warning(this, tr("Sensitivity"),
tr("Gas pseudo-pressure data is unavailable or invalid."));
return;
}
}
QString error; QString error;
QScopedPointer<nmCalculationDllPebiSolverTask> task; QScopedPointer<nmCalculationDllPebiSolverTask> task;
try { try {
@ -2506,11 +2512,11 @@ void nmWxSensitive::onGenerateClicked()
return; return;
} }
} }
// 从最后成功结果恢复完整输入,当前编辑参数和全局拟压力配置不参与本次分析。 // 启动时读取当前模型,后台各组计算只修改任务自己的参数副本。
nmCalculationDllPebiSolverTask source(QString(), m_pDataManager, QString(), nullptr, false, true); nmCalculationDllPebiSolverTask source(QString(), m_pDataManager, well->getWellName());
// 第四种直接交给后台任务逐组拟合,仍使用主窗口原有进度框。 // 第四种直接交给后台任务逐组拟合,仍使用主窗口原有进度框。
task.reset(source.createSensitivityBatch(m_sensitiveData, &error, task.reset(source.createSensitivityBatch(m_sensitiveData, &error,
calculationTypeCombo->currentIndex() == 3 ? m_improveSettings.data() : nullptr)); (calculationTypeCombo->currentIndex() == 3) ? m_improveSettings.data() : nullptr));
} catch(const std::bad_alloc&) { } catch(const std::bad_alloc&) {
error = tr("Insufficient memory to prepare solver input."); error = tr("Insufficient memory to prepare solver input.");
} }

Loading…
Cancel
Save