From ed50f2c1d06ce52651043ae37052a94864dd9589 Mon Sep 17 00:00:00 2001 From: lvjunjie Date: Wed, 14 Oct 2026 11:57:54 +0800 Subject: [PATCH] =?UTF-8?q?fix(nmNum):=20=E4=BF=AE=E5=A4=8D=E5=88=86?= =?UTF-8?q?=E6=9E=90=E8=AE=A1=E7=AE=97=E5=9F=BA=E5=87=86=E4=B8=8E=E7=95=8C?= =?UTF-8?q?=E9=9D=A2=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 成功求解后在内存中保留完整输入,分析复制该输入并仅替换采样参数,避免使用未求解的编辑值。 - 主井、历史曲线、基准曲线及气井拟压力转换均使用同次求解的数据,失败或取消时保留旧基准。 - 未生成模型时禁用分析按钮,将 Results 窗口及表格背景统一为白色。 - 本次不增加输入快照的文件保存与加载。 --- .../nmCalculationDllPebiSolverTask.h | 6 +- Include/nmNum/nmData/nmPebiResultSnapshot.h | 8 ++ .../nmData/nmPebiResultSnapshotBuilder.h | 3 + Include/nmNum/nmSubWxs/nmWxNumericalDesign.h | 1 + .../nmCalculationDllPebiSolverTask.cpp | 84 ++++++++++++++++--- Src/nmNum/nmData/nmPebiResultSnapshot.cpp | 12 +++ .../nmData/nmPebiResultSnapshotBuilder.cpp | 11 +++ Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp | 16 ++++ Src/nmNum/nmSubWxs/nmWxSensitive.cpp | 69 ++++----------- 9 files changed, 146 insertions(+), 64 deletions(-) diff --git a/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h b/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h index 4cbc52f7..c5eb69bc 100644 --- a/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h +++ b/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h @@ -36,12 +36,14 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread { * @param sAutoFitTargetWellName 自动拟合目标井名;空值表示完整成果计算。 * @param parent Qt 父对象。 * @param bDeferManualSnapshot 手工求解是否由界面事件循环分批捕获输入。 + * @param bUseSolvedInput 敏感性分析只恢复最后成功模型的输入,不捕获当前编辑值。 */ nmCalculationDllPebiSolverTask(QString sPostprocessingDir, nmDataAnalyzeManager* pDataManager = nullptr, const QString& sAutoFitTargetWellName = QString(), QObject *parent = nullptr, - bool bDeferManualSnapshot = false); + bool bDeferManualSnapshot = false, + bool bUseSolvedInput = false); ~nmCalculationDllPebiSolverTask(); /** @brief QThread 入口,执行单次或敏感性批量求解并发送一次完成信号。 */ @@ -146,6 +148,8 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread { void discardPendingFullResult(); /** @brief 同步路径在任务创建线程一次性捕获全部求解输入。 */ bool captureInputSnapshot(const QString& sAutoFitTargetWellName); + /** @brief 从所属分析的最后成功结果恢复完整输入。 */ + bool restoreSolvedInput(); /** @brief 手工求解在线程内完成场景、缓存和 VTK 基础网格准备。 */ bool prepareManualInput(); /** @brief 无锁读取原子停止标记。 */ diff --git a/Include/nmNum/nmData/nmPebiResultSnapshot.h b/Include/nmNum/nmData/nmPebiResultSnapshot.h index a7f59b7c..f6eb2b65 100644 --- a/Include/nmNum/nmData/nmPebiResultSnapshot.h +++ b/Include/nmNum/nmData/nmPebiResultSnapshot.h @@ -19,6 +19,7 @@ class vtkUnstructuredGrid; class nmPebiResultSnapshotBuilder; class nmPebiResultSnapshotRenderAdapter; class nmPebiResultSnapshotSerializer; +struct nmPebiSolverInputSnapshot; /** @brief 一条求解器槽位在最后成功结果中的冻结描述。 */ struct NM_DATA_EXPORT nmPebiResultSolverSlot @@ -314,6 +315,10 @@ public: const nmPebiResultPvtParameters& getPvtParameters() const; /** @brief 返回求解时冻结的求解设置。 */ const nmPebiResultSolverSettings& getSolverSettings() const; + /** @brief 最后成功模型的只读输入,仅在本次运行中保留,不参与文件保存。 */ + const QSharedPointer& getSensitivityInput() const; + /** @brief 返回与完整输入对应的主井编码。 */ + const QString& getSensitivityWellCode() const; private: struct PressureFrame @@ -391,6 +396,9 @@ private: nmPebiResultPvtParameters m_oPvtParameters; /** @brief 求解时冻结的求解设置。 */ nmPebiResultSolverSettings m_oSolverSettings; + /** @brief 输入随成功结果一起发布,敏感性任务只使用其独立副本。 */ + QSharedPointer m_sensitivityInput; + QString m_sensitivityWellCode; // 只有 Builder 和快照序列化器可以在发布前填充私有数据。 friend class nmPebiResultSnapshotBuilder; diff --git a/Include/nmNum/nmData/nmPebiResultSnapshotBuilder.h b/Include/nmNum/nmData/nmPebiResultSnapshotBuilder.h index 66dcb8ae..989b33bd 100644 --- a/Include/nmNum/nmData/nmPebiResultSnapshotBuilder.h +++ b/Include/nmNum/nmData/nmPebiResultSnapshotBuilder.h @@ -58,6 +58,9 @@ public: bool setPvtParameters(const nmPebiResultPvtParameters& oParameters); /** @brief 设置求解时冻结的网格、时间步和求解器配置。 */ bool setSolverSettings(const nmPebiResultSolverSettings& oSettings); + /** @brief 将当次实际输入附在结果候选上,仅成功提交后成为分析基准。 */ + bool setSensitivityInput(const QSharedPointer& input, + const QString& wellCode); /** @brief 完成全部结构校验并封闭候选,失败后该 Builder 不可继续使用。 */ bool finalize(QString* pError = NULL); diff --git a/Include/nmNum/nmSubWxs/nmWxNumericalDesign.h b/Include/nmNum/nmSubWxs/nmWxNumericalDesign.h index 8663843a..8924cd7f 100644 --- a/Include/nmNum/nmSubWxs/nmWxNumericalDesign.h +++ b/Include/nmNum/nmSubWxs/nmWxNumericalDesign.h @@ -90,6 +90,7 @@ private slots: void onAutomaticFittingClicked(); void onForecastClicked(); void onSensitiveClicked(); + void updateSensitiveButtonEnabled(); // 现有和调整的槽函数 void onOptionsIconClicked(); // "包含其他井"按钮点击 diff --git a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp index 98d0c21b..c83a4503 100644 --- a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp @@ -92,6 +92,7 @@ struct nmPebiSolverInputSnapshot } nmPebiGridInputSnapshot m_oGridInput; ///< 网格、场景、井顺序和授权路径值快照。 + QString m_sPrimaryWellCode; ///< 当次成功模型对应的分析主井。 nmPebiGridResult m_oGridResult; ///< 已有网格副本或后台新生成的局部网格。 /** @brief 在 DataManager 线程冻结的静态属性参考值、插值数据和授权路径。 */ nmPebiStaticPropertyCalculationInput m_oStaticPropertyInput; @@ -210,6 +211,28 @@ bool isFiniteSolverNumber(double value) #endif } +bool savedPseudoPressure(const nmDataBinaryTools::NM_PEBI_SCENE& scene, + double pressure, double& result) +{ + // 气井分析使用求解时的拟压力表,避免当前编辑改写全局转换器配置。 + const std::vector& p = scene.PVT.pseudoPressureP; + const std::vector& ps = scene.PVT.pseudoPressurePs; + if(p.size() < 2 || p.size() != ps.size() || !isFiniteSolverNumber(pressure)) return false; + const bool ascending = p[1] > p[0]; + size_t first = 0, last = p.size(); + while(first < last) { + const size_t mid = first + (last - first) / 2; + if(ascending ? p[mid] < pressure : p[mid] > pressure) first = mid + 1; + else last = mid; + } + const size_t right = (std::max)(size_t(1), (std::min)(first, p.size() - 1)); + const double span = p[right] - p[right - 1]; + if(span == 0.0) return false; + // 与现有离线计算一致,区间内线性插值,超出范围沿端点外推。 + result = ps[right - 1] + (pressure - p[right - 1]) / span * (ps[right] - ps[right - 1]); + return isFiniteSolverNumber(result); +} + // 旧的双对数曲线 DLL 仍是单相接口,只在结果后处理时取输入捕获阶段 // 冻结的制度参考相。PEBI 主求解仍在场景中接收油、气、水三相全部流量。 const QVector* referenceRatesOf( @@ -382,6 +405,7 @@ bool captureResultParameters( nmPebiResultPvtParameters& oPvt = oSnapshot.m_oResultPvtParameters; + oSnapshot.m_sPrimaryWellCode = pDataManager->getPrimaryWellCode(); oPvt.m_dConstantBo = pReservoir->getBo().getValue().toDouble(); oPvt.m_dConstantMiuo = pReservoir->getMiuo().getValue().toDouble(); oPvt.m_dConstantBg = pReservoir->getBg().getValue().toDouble(); @@ -634,7 +658,8 @@ nmCalculationDllPebiSolverTask::nmCalculationDllPebiSolverTask( nmDataAnalyzeManager* pDataManager, const QString& sAutoFitTargetWellName, QObject *parent, - bool bDeferManualSnapshot): + bool bDeferManualSnapshot, + bool bUseSolvedInput): QThread(parent), m_sPostprocessingDir(sPostprocessingDir), m_pDataManager(pDataManager != nullptr @@ -665,7 +690,9 @@ nmCalculationDllPebiSolverTask::nmCalculationDllPebiSolverTask( m_bManagerUseActive = true; } - if(bDeferManualSnapshot && sAutoFitTargetWellName.isEmpty()) { + if(bUseSolvedInput) { + m_bInputSnapshotValid = restoreSolvedInput(); + } else if(bDeferManualSnapshot && sAutoFitTargetWellName.isEmpty()) { // 手工求解由界面事件循环分批冻结值输入;start() 必须等状态释放后再调用。 m_pManualCaptureState = new nmPebiManualCaptureState(); } else { @@ -916,12 +943,15 @@ nmCalculationDllPebiSolverTask* nmCalculationDllPebiSolverTask::createSensitivit task->m_pSensitivityResult->m_variables = frozenSampling.getVariables(); task->m_pSensitivityResult->m_sWellCode = task->m_sAutoFitTargetWellCode; if(m_pDataManager != nullptr) { - nmDataWellBase* well = m_pDataManager->findWellByCode(task->m_sAutoFitTargetWellCode); - if(well != nullptr) { - task->m_pSensitivityResult->m_sWellName = well->getWellName(); - task->m_pSensitivityResult->m_vecHistoryLogLog = well->getHistoryLogLog(); - // 复用原模型已有数值解作为对比基准,不增加一次求解或跟随后续结果变化。 - task->m_pSensitivityResult->m_vecBaseLogLog = well->getResultLogLog(); + const QSharedPointer solved = m_pDataManager->getPebiResultSnapshot(); + for(int i = 0; !solved.isNull() && i < solved->getWellCount(); ++i) { + const nmPebiResultWellSnapshot* well = solved->getWellAt(i); + if(well->m_sWellCode != task->m_sAutoFitTargetWellCode) continue; + // 历史和基准曲线必须与计算输入属于同一次求解。 + task->m_pSensitivityResult->m_sWellName = well->m_sWellName; + task->m_pSensitivityResult->m_vecHistoryLogLog = well->m_oCurves.m_vecHistoryLogLog; + task->m_pSensitivityResult->m_vecBaseLogLog = well->m_oCurves.m_vecResultLogLog; + break; } } return task.take(); @@ -1346,6 +1376,20 @@ void nmCalculationDllPebiSolverTask::releaseDataManagerUse() } } +bool nmCalculationDllPebiSolverTask::restoreSolvedInput() +{ + // 复制最后成功求解的输入,不再从当前编辑模型重新捕获。 + if(!m_pDataManager || QThread::currentThread() != m_pDataManager->thread()) return false; + const QSharedPointer 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( const QString& sAutoFitTargetWellName) { @@ -1869,11 +1913,13 @@ bool nmCalculationDllPebiSolverTask::buildPebiModeResult( // p1.pw 是求解器返回的原始井底压力, 后续仍按 MPa 保存到 vvecPressure. // 只有气单相变化 PVT 模型的双对数和半对数计算需要改用拟压力. const bool usePseudoPressure = (modelType == static_cast(SMT_Gas_VariablePvt)); - CalPseudoPressure calPseudoPressure = usePseudoPressure + const bool useSavedPseudoPressure = usePseudoPressure && + m_pInputSnapshot->m_oSensitivityBaseline.m_nTarget >= 0; + CalPseudoPressure calPseudoPressure = usePseudoPressure && !useSavedPseudoPressure ? getPseudoPressureConverter() : nullptr; - if(usePseudoPressure && calPseudoPressure == nullptr) { + if(usePseudoPressure && !useSavedPseudoPressure && calPseudoPressure == nullptr) { qWarning() << "Failed to load the gas pseudo-pressure converter."; return fail(tr("Failed to load the gas pseudo-pressure converter.")); } @@ -2011,8 +2057,11 @@ bool nmCalculationDllPebiSolverTask::buildPebiModeResult( double pressureForLog = p1.pw[wellIdx][i]; if(usePseudoPressure && (!isFiniteSolverNumber(pressureForLog) - // -1 与主界面调用一致, 表示使用 configPsAbouts() 配置的当前模式. - || !calPseudoPressure(pressureForLog, pressureForLog, -1) + // 敏感性任务只查求解时保存的表,不能读取已被编辑或其他窗口改写的全局配置。 + || !(useSavedPseudoPressure + ? savedPseudoPressure( + m_pInputSnapshot->m_oGridInput.m_oScene, pressureForLog, pressureForLog) + : calPseudoPressure(pressureForLog, pressureForLog, -1)) || !isFiniteSolverNumber(pressureForLog))) { qWarning() << "Failed to convert gas pressure to pseudo-pressure:" << p1.pw[wellIdx][i] @@ -2261,6 +2310,17 @@ bool nmCalculationDllPebiSolverTask::buildPebiResultSnapshotCandidate( try { nmPebiResultSnapshotBuilder oBuilder; + // 复用现有输入结构,在转移井制度和曲线前留一份只读副本,随成功结果发布。 + QSharedPointer 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, m_nResultInputRevision)) { return false; diff --git a/Src/nmNum/nmData/nmPebiResultSnapshot.cpp b/Src/nmNum/nmData/nmPebiResultSnapshot.cpp index e0f2292c..fa7d515c 100644 --- a/Src/nmNum/nmData/nmPebiResultSnapshot.cpp +++ b/Src/nmNum/nmData/nmPebiResultSnapshot.cpp @@ -12,6 +12,18 @@ #include +const QSharedPointer& nmPebiResultSnapshot::getSensitivityInput() const +{ + // 管理器仅持有只读输入,复制和采样由求解模块完成。 + return m_sensitivityInput; +} + +const QString& nmPebiResultSnapshot::getSensitivityWellCode() const +{ + // 界面定位求解时的井,不能因编辑主井而改变参数基准。 + return m_sensitivityWellCode; +} + namespace { bool deepCopyNamedArray(vtkFieldData* pSource, diff --git a/Src/nmNum/nmData/nmPebiResultSnapshotBuilder.cpp b/Src/nmNum/nmData/nmPebiResultSnapshotBuilder.cpp index 24812fd0..ced5726b 100644 --- a/Src/nmNum/nmData/nmPebiResultSnapshotBuilder.cpp +++ b/Src/nmNum/nmData/nmPebiResultSnapshotBuilder.cpp @@ -885,6 +885,17 @@ bool nmPebiResultSnapshotBuilder::setSolverSettings( return true; } +bool nmPebiResultSnapshotBuilder::setSensitivityInput( + const QSharedPointer& 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) { // 第一部分校验快照身份、输入版本和所有必填结果载荷。 diff --git a/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp b/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp index 27692410..d5479d00 100644 --- a/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp +++ b/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp @@ -807,6 +807,7 @@ void nmWxNumericalDesign::initButton() // 创建敏感性分析按钮 m_pSensitiveBtn = new QPushButton(tr("Sensitive"), this); m_pSensitiveBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + updateSensitiveButtonEnabled(); // 创建右下角按钮 m_pGenerateButton = new QPushButton(tr("Generate"), this); // "生成"按钮 m_pGenerateButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); // 固定尺寸 @@ -842,6 +843,8 @@ void nmWxNumericalDesign::setupConnections() this, SLOT(onIncludeOtherWellsToggled(bool))); // Map井增删或产量、压力目标变化后,立即更新多井选项是否可用。 if(m_pDataManager != nullptr) { + connect(m_pDataManager, SIGNAL(sigPebiResultSnapshotChanged()), + this, SLOT(updateSensitiveButtonEnabled()), Qt::UniqueConnection); connect(m_pDataManager, SIGNAL(dataChanged()), this, SLOT(onWellCollectionChanged()), Qt::UniqueConnection); // createWell() 会在井对象登记时先发送 dataChanged(),此时 WellCode、 @@ -1442,8 +1445,21 @@ void nmWxNumericalDesign::onForecastClicked() dlg->show(); } +void nmWxNumericalDesign::updateSensitiveButtonEnabled() +{ + // 只有本次运行中成功求解过才有完整输入,编辑参数不会改变已有分析基准。 + const QSharedPointer solved = m_pDataManager + ? m_pDataManager->getPebiResultSnapshot() : QSharedPointer(); + const bool available = !solved.isNull() && !solved->getSensitivityInput().isNull(); + m_pSensitiveBtn->setEnabled(available); + m_pSensitiveBtn->setToolTip(available ? QString() : tr("Generate the model to enable sensitivity analysis.")); +} + void nmWxNumericalDesign::onSensitiveClicked() { + // 没有模型时直接阻止进入,正常交互由按钮置灰表达,不再等生成分析时弹框。 + updateSensitiveButtonEnabled(); + if(!m_pSensitiveBtn->isEnabled()) return; nmWxSensitive* dlg = new nmWxSensitive(this, m_pDataManager); // 按数值生成按钮的现有路径,将任务请求转发给所属主窗口。 connect(dlg, SIGNAL(sigSensitivityTaskReady(nmWxSensitive*,nmCalculationDllPebiSolverTask*)), diff --git a/Src/nmNum/nmSubWxs/nmWxSensitive.cpp b/Src/nmNum/nmSubWxs/nmWxSensitive.cpp index 352f6f42..1c89e79e 100644 --- a/Src/nmNum/nmSubWxs/nmWxSensitive.cpp +++ b/Src/nmNum/nmSubWxs/nmWxSensitive.cpp @@ -7,8 +7,6 @@ #include "nmDataPerforation.h" #include "nmPebiResultSnapshot.h" #include "nmCalculationDllPebiSolverTask.h" -#include "iSubWndFitting.h" -#include "mGui/mGuiAnal/iAnalRun.h" #include "iUnitHelper.h" #include "iUnitGroup.h" #include "iSysParaHelper.h" @@ -62,8 +60,6 @@ #include #include -#pragma comment(lib, "mGuiAnal.lib") - #include #include #include @@ -656,11 +652,16 @@ void nmWxSensitive::refreshModelValues() ? m_pDataManager->getPebiResultSnapshot() : QSharedPointer(); const bool resetRanges = !snapshot.isNull() && snapshot->getSnapshotId() != m_sensitiveData.getModelSnapshotId(); - nmDataWellBase* well = m_pDataManager - ? m_pDataManager->findWellByCode(m_pDataManager->getPrimaryWellCode()) : nullptr; + 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; - const nmPebiResultWellSnapshot* solvedWell = !snapshot.isNull() && well - ? snapshot->findWell(well->getWellInstanceId()) : nullptr; + const nmPebiResultWellSnapshot* solvedWell = nullptr; + 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)); for (int n = 0; n < names.size(); ++n) { nmDataAttribute* attribute = nullptr; @@ -688,8 +689,10 @@ void nmWxSensitive::refreshModelValues() nmDataSensitive::VariableSampling& variable = m_sensitiveData.getVariable(index); // 在局部属性上替换数值,不能把未求解的编辑值带入 Model value 或默认范围。 nmDataAttribute solvedAttribute; - if (attribute && !snapshot.isNull()) { - solvedAttribute = *attribute; + 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; @@ -698,7 +701,6 @@ void nmWxSensitive::refreshModelValues() 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); - if (attribute) attribute = &solvedAttribute; } double physicalMin = 0.0, physicalMax = 0.0; if (!attribute || !samplingPhysicalRange(name, physicalMin, physicalMax)) { @@ -954,20 +956,6 @@ bool nmWxSensitive::saveDataFromUI() detailStack->setCurrentIndex(idx + 1); return false; } - if (!variable.getEnabled()) continue; - // 本期处理常量参数;属性场开启时常量会被插值覆盖,因此不能静默运行。 - if (m_pDataManager && (name == "k" || name == "h" || name == QString(QChar(0x03c6)))) { - const QString property = name == QString(QChar(0x03c6)) ? "phi" : name; - const QVector dataSets = m_pDataManager->getPropertyInterpolationDataSets(); - for (int field = 0; field < dataSets.size(); ++field) { - if (dataSets[field].useForCalculation && dataSets[field].property == property && - !dataSets[field].points.isEmpty()) { - QMessageBox::warning(this, tr("Sensitivity"), - tr("%1 uses a non-uniform field. Disable that field before sampling a constant value.").arg(name)); - return false; - } - } - } } m_sensitiveData = candidate; return true; @@ -1597,34 +1585,11 @@ void nmWxSensitive::onTreeItemChanged(QTreeWidgetItem* item, int column) void nmWxSensitive::onGenerateClicked() { 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::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; - if(!analRun || !analRun->configPsAbouts(true, fitting->getModelOption(), - false, fitting->getAllWxPtr())) { - QMessageBox::warning(this, tr("Sensitivity"), - tr("Gas pseudo-pressure data is unavailable or invalid.")); - return; - } - } - QString error; QScopedPointer task; try { - // 在管理器所属的界面线程捕获基准并复制一次,后台任务不持有管理器。 - nmCalculationDllPebiSolverTask source(QString(), m_pDataManager, well->getWellName()); + // 从最后成功结果恢复完整输入,当前编辑参数和全局拟压力配置不参与本次分析。 + nmCalculationDllPebiSolverTask source(QString(), m_pDataManager, QString(), nullptr, false, true); task.reset(source.createSensitivityBatch(m_sensitiveData, &error)); } catch(const std::bad_alloc&) { error = tr("Insufficient memory to prepare solver input."); @@ -1721,12 +1686,14 @@ void nmWxSensitive::onResultsClicked() if(!resultsButton->isEnabled()) return; emit sigSensitiveResultsRequested(); if(m_resultsDialog.isNull()) { - m_resultsDialog = new QDialog(this); + // 与参数窗口共用框架对话框样式,避免普通 QDialog 使用默认灰色背景。 + m_resultsDialog = new iDlgBase(this); m_resultsDialog->setAttribute(Qt::WA_DeleteOnClose); m_resultsDialog->setWindowTitle(tr("Sensitivity Results")); m_resultsDialog->resize(700, 380); QVBoxLayout* layout = new QVBoxLayout(m_resultsDialog.data()); QTableView* table = new QTableView(m_resultsDialog.data()); + table->setStyleSheet("QTableView { background-color: white; }"); m_resultsModel = new SensitivityResultsModel(m_resultsDialog.data()); m_resultsModel->setResults(m_pDataManager->getSensitiveData()->getResults()); table->setModel(m_resultsModel.data());