diff --git a/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h b/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h index 4c043119..4333ae2d 100644 --- a/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h +++ b/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h @@ -37,14 +37,12 @@ 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 bUseSolvedInput = false); + bool bDeferManualSnapshot = false); ~nmCalculationDllPebiSolverTask(); /** @brief QThread 入口,执行单次或敏感性批量求解并发送一次完成信号。 */ @@ -136,10 +134,6 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread { // 基准模型与采样模型共用压力误差口径,返回 -1 表示无有效误差。 static double calculatePressureRmse(const QVector >& measured, const QVector >& simulated); - // 从最后成功输入的裂缝端点读取半长,不访问当前编辑中的井对象。 - static bool getSolvedFractureHalfLength(const QSharedPointer& input, - const QString& wellCode, double& halfLength); - private: /** @brief 仅复制目标井求解的值快照,不登记或访问实时管理器。 */ nmCalculationDllPebiSolverTask(const nmCalculationDllPebiSolverTask& source, @@ -158,8 +152,6 @@ 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/nmDataAutomaticFitting.h b/Include/nmNum/nmData/nmDataAutomaticFitting.h index 8645dd62..e2d296bf 100644 --- a/Include/nmNum/nmData/nmDataAutomaticFitting.h +++ b/Include/nmNum/nmData/nmDataAutomaticFitting.h @@ -135,7 +135,6 @@ struct nmSensitivityFitSettings bool confirmed; nmDataAutomaticFitting parameters; QString targetWell; - QString snapshotId; QVector initialValues; }; diff --git a/Include/nmNum/nmData/nmDataSensitive.h b/Include/nmNum/nmData/nmDataSensitive.h index c38ad5ea..49e76021 100644 --- a/Include/nmNum/nmData/nmDataSensitive.h +++ b/Include/nmNum/nmData/nmDataSensitive.h @@ -122,7 +122,7 @@ public: BatchResult() : m_calcType(CALC_DETERMINISTIC), m_dFilterThreshold(-1.0), m_nHistogramVariable(-1), m_bCompleted(false), m_bStopped(false) {} CalculationType m_calcType; QList m_variables; // 求解时冻结的配置,独立于之后的参数编辑。 - QList m_fitParameters; // 名称、基准单位和求解快照中的模型值,仅存内存。 + QList m_fitParameters; // 名称、基准单位和本批分析开始时的模型值。 QString m_sWellCode; QString m_sWellName; QString m_sAxisXName; @@ -172,9 +172,6 @@ public: void setMonteCarloCount(int count); bool usesBaseUnits() const; void setUsesBaseUnits(bool enabled); - // 记录采样范围对应的求解快照,成功重新求解后再初始化范围。 - QString getModelSnapshotId() const; - void setModelSnapshotId(const QString& snapshotId); // 后台运行时由任务独占,线程结束后交给分析数据管理并由界面修改可见性。 QSharedPointer getResults() const; void setResults(const QSharedPointer& results); @@ -191,7 +188,6 @@ private: CalculationType m_calcType; quint64 m_totalModelCount; bool m_usesBaseUnits; - QString m_modelSnapshotId; QList m_variables; int m_monteCarloCount; QVector > m_monteCarloValues; diff --git a/Include/nmNum/nmData/nmPebiResultSnapshot.h b/Include/nmNum/nmData/nmPebiResultSnapshot.h index f6eb2b65..a7f59b7c 100644 --- a/Include/nmNum/nmData/nmPebiResultSnapshot.h +++ b/Include/nmNum/nmData/nmPebiResultSnapshot.h @@ -19,7 +19,6 @@ class vtkUnstructuredGrid; class nmPebiResultSnapshotBuilder; class nmPebiResultSnapshotRenderAdapter; class nmPebiResultSnapshotSerializer; -struct nmPebiSolverInputSnapshot; /** @brief 一条求解器槽位在最后成功结果中的冻结描述。 */ struct NM_DATA_EXPORT nmPebiResultSolverSlot @@ -315,10 +314,6 @@ public: const nmPebiResultPvtParameters& getPvtParameters() const; /** @brief 返回求解时冻结的求解设置。 */ const nmPebiResultSolverSettings& getSolverSettings() const; - /** @brief 最后成功模型的只读输入,仅在本次运行中保留,不参与文件保存。 */ - const QSharedPointer& getSensitivityInput() const; - /** @brief 返回与完整输入对应的主井编码。 */ - const QString& getSensitivityWellCode() const; private: struct PressureFrame @@ -396,9 +391,6 @@ 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 989b33bd..ab14db29 100644 --- a/Include/nmNum/nmData/nmPebiResultSnapshotBuilder.h +++ b/Include/nmNum/nmData/nmPebiResultSnapshotBuilder.h @@ -58,10 +58,6 @@ 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/nmWxAutomaticFitting.h b/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h index df206e94..3563d2ac 100644 --- a/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h +++ b/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h @@ -27,7 +27,6 @@ #include "nmWxAutomaticfittingStart.h" #include "nmSubWxs_global.h" -#include "nmPebiResultSnapshot.h" class NM_SUB_WXS_EXPORT nmWxAutomaticFitting : public iDlgBase { @@ -61,7 +60,6 @@ private: void setupButtons(); void setAutomaticFittingValue(); void loadSensitivitySettings(); - const nmPebiResultWellSnapshot* solvedWell(int index) const; void setParameterRowVisible(QTableWidget* table, int row, bool visible); void renumberVisibleParameterRows(QTableWidget* table); void updateParameterVisibility(QTableWidget* table, NM_SOLVER_MODEL_TYPE eType); @@ -105,7 +103,6 @@ private: // 数据成员 nmDataReservoir reservoirData; - QSharedPointer m_solvedSnapshot; nmDataAutomaticFitting automaticFittingData; nmSensitivityFitSettings* m_sensitivitySettings; QStringList m_sampledParameters; diff --git a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp index bc365544..05cb729d 100644 --- a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp @@ -96,7 +96,6 @@ struct nmPebiSolverInputSnapshot } nmPebiGridInputSnapshot m_oGridInput; ///< 网格、场景、井顺序和授权路径值快照。 - QString m_sPrimaryWellCode; ///< 当次成功模型对应的分析主井。 nmPebiGridResult m_oGridResult; ///< 已有网格副本或后台新生成的局部网格。 /** @brief 在 DataManager 线程冻结的静态属性参考值、插值数据和授权路径。 */ nmPebiStaticPropertyCalculationInput m_oStaticPropertyInput; @@ -255,10 +254,10 @@ double calculateSensitivityPressureRmse(const QVector >& measure ? 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) { - // 气井分析使用求解时的拟压力表,避免当前编辑改写全局转换器配置。 + // 使用本次任务捕获的拟压力表,后台插值不访问全局转换器。 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; @@ -449,7 +448,6 @@ 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(); @@ -702,8 +700,7 @@ nmCalculationDllPebiSolverTask::nmCalculationDllPebiSolverTask( nmDataAnalyzeManager* pDataManager, const QString& sAutoFitTargetWellName, QObject *parent, - bool bDeferManualSnapshot, - bool bUseSolvedInput): + bool bDeferManualSnapshot): QThread(parent), m_sPostprocessingDir(sPostprocessingDir), m_pDataManager(pDataManager != nullptr @@ -734,9 +731,7 @@ nmCalculationDllPebiSolverTask::nmCalculationDllPebiSolverTask( m_bManagerUseActive = true; } - if(bUseSolvedInput) { - m_bInputSnapshotValid = restoreSolvedInput(); - } else if(bDeferManualSnapshot && sAutoFitTargetWellName.isEmpty()) { + if(bDeferManualSnapshot && sAutoFitTargetWellName.isEmpty()) { // 手工求解由界面事件循环分批冻结值输入;start() 必须等状态释放后再调用。 m_pManualCaptureState = new nmPebiManualCaptureState(); } else { @@ -799,8 +794,8 @@ nmCalculationDllPebiSolverTask* nmCalculationDllPebiSolverTask::createSensitivit ? tr("Solver input is unavailable.") : m_sFailureReason; return nullptr; } - if(!m_pInputSnapshot->m_oGridResult.m_bSucceeded || - m_pInputSnapshot->m_bRequiresGridCalculation) { + 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; } @@ -992,9 +987,8 @@ nmCalculationDllPebiSolverTask* nmCalculationDllPebiSolverTask::createSensitivit task->m_pSensitivityResult->m_variables = frozenSampling.getVariables(); task->m_pSensitivityResult->m_sWellCode = task->m_sAutoFitTargetWellCode; if(m_pDataManager != nullptr) { - const QSharedPointer solved = m_pDataManager->getPebiResultSnapshot(); - // 拟合参数的蓝色基准点来自原模型,不使用设置窗口中可编辑的拟合初值。 - if(improveSettings && !solved.isNull()) { + // 拟合参数基准值读取当前模型,不使用设置窗口中可编辑的拟合初值。 + if(improveSettings) { const nmDataAutomaticFitting& fit = improveSettings->parameters; const bool selected[] = { fit.getPermeabilitySelected(), fit.getSkinSelected(), fit.getWellboreStorageSelected(), fit.getPorositySelected(), @@ -1002,28 +996,43 @@ nmCalculationDllPebiSolverTask* nmCalculationDllPebiSolverTask::createSensitivit const char* names[] = { "Permeability", "Skin", "Wellbore storage", "Porosity", "Fracture conductivity", "Fracture half length" }; const char* units[] = { "mD", "", "m^3/MPa", "", "mD.m", "m" }; - for(int i = 0; i < solved->getWellCount(); ++i) { - const nmPebiResultWellSnapshot* well = solved->getWellAt(i); - if(well->m_sWellName != improveSettings->targetWell) continue; - double halfLength = std::numeric_limits::quiet_NaN(); - if(selected[5]) getSolvedFractureHalfLength(solved->getSensitivityInput(), well->m_sWellCode, halfLength); + nmDataWellBase* well = m_pDataManager->findWellByName(improveSettings->targetWell); + if(well) { + // 启动前补齐拟合目标井的历史双对数,后台各组共用这份目标数据。 + QVector& wells = task->m_pInputSnapshot->m_oGridInput.m_vecWellInputs; + 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(well)) { + conductivity = fractured->getDfc().getValue().toDouble(); + halfLength = fractured->getFractureHalfLength().getValue().toDouble(); + } else if(nmDataHorizontalFracturedWell* fractured = dynamic_cast(well)) { + conductivity = fractured->getDfc().getValue().toDouble(); + halfLength = fractured->getFractureHalfLength().getValue().toDouble(); + } const double values[] = { m_pInputSnapshot->m_oStaticPropertyInput.m_dReferencePermeabilityMilliDarcy, - well->m_dSkin, well->m_dWellboreStorage, - m_pInputSnapshot->m_oStaticPropertyInput.m_dReferencePorosity, well->m_dDfc, halfLength }; - for(int j = 0; j < 6; ++j) if(selected[j]) + well->getPerforationCount() > 0 ? well->getPerforation(0)->getSkin().getValue().toDouble() : 0.0, + well->getWellboreStorage().getValue().toDouble(), + 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( QCoreApplication::translate("nmWxAutomaticFitting", names[j]), values[j], units[j])); - break; + } } } - 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; + nmDataWellBase* well = m_pDataManager->findWellByCode(task->m_sAutoFitTargetWellCode); + if(well) { + // 第三、四种方法按分析主井的实测压力统计误差,与拟合目标井分别取值。 + const int target = task->m_pInputSnapshot->m_oSensitivityBaseline.m_nTarget; + task->m_pInputSnapshot->m_oGridInput.m_vecWellInputs[target].m_vecHistoryPressure = well->getHistoryPressure(); + task->m_pSensitivityResult->m_sWellName = well->getWellName(); + task->m_pSensitivityResult->m_vecHistoryLogLog = well->getHistoryLogLog(); + // 对比曲线沿用已有求解结果,分析输入则取当前参数。 + task->m_pSensitivityResult->m_vecBaseLogLog = well->getResultLogLog(); } } return task.take(); @@ -1059,37 +1068,6 @@ double nmCalculationDllPebiSolverTask::calculatePressureRmse( return calculateSensitivityPressureRmse(measured, simulated, nullptr); } -bool nmCalculationDllPebiSolverTask::getSolvedFractureHalfLength( - const QSharedPointer& 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* 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& fittedParameters) { // 采样值已经写入本任务;候选只覆盖选中的拟合参数,不触碰管理器和正式结果。 @@ -1102,7 +1080,7 @@ bool nmCalculationDllPebiSolverTask::improveSensitivityRun(QVector& fitt if(grid.m_vecWellInputs[i].m_bRealWell && 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(), fit.getWellboreStorageSelected(), fit.getPorositySelected(), fit.getFractureConductivitySelected(), fit.getFractureHalfLengthSelected() }; @@ -1176,7 +1154,8 @@ bool nmCalculationDllPebiSolverTask::improveSensitivityRun(QVector& fitt } } 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(); calculateSingleRun(); return m_lastRunSucceeded ? m_autoFitResultLogLog : QVector >(); @@ -1244,7 +1223,7 @@ bool nmCalculationDllPebiSolverTask::executeSensitivityBatch() result.m_vecLogLog = m_autoFitResultLogLog; result.m_vecSemiLog = m_autoFitResultSemiLog; if(m_pSensitivityResult->m_calcType == nmDataSensitive::CALC_MONTE_CARLO) { - // 使用求解时冻结的目标井实测压力,不读取界面后来修改的数据。 + // 使用本批任务开始时读取的实测压力,计算过程中不访问界面数据。 const int target = m_pInputSnapshot->m_oSensitivityBaseline.m_nTarget; result.m_dPressureRmse = calculateSensitivityPressureRmse( 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 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) { @@ -1935,6 +1900,8 @@ bool nmCalculationDllPebiSolverTask::execPebiMode() return false; } + // 重开项目或当前几何改变时首次重建,后续采样复用本批网格。 + m_pInputSnapshot->m_bRequiresGridCalculation = false; if(bCreateUnstructuredGrid) { if(oGridResult.m_pUnstructuredGrid == nullptr || oGridResult.m_pUnstructuredGrid->GetNumberOfCells() <= 0) { @@ -2154,13 +2121,13 @@ bool nmCalculationDllPebiSolverTask::buildPebiModeResult( // p1.pw 是求解器返回的原始井底压力, 后续仍按 MPa 保存到 vvecPressure. // 只有气单相变化 PVT 模型的双对数和半对数计算需要改用拟压力. const bool usePseudoPressure = (modelType == static_cast(SMT_Gas_VariablePvt)); - const bool useSavedPseudoPressure = usePseudoPressure && + const bool useInputPseudoPressure = usePseudoPressure && m_pInputSnapshot->m_oSensitivityBaseline.m_nTarget >= 0; - CalPseudoPressure calPseudoPressure = usePseudoPressure && !useSavedPseudoPressure + CalPseudoPressure calPseudoPressure = usePseudoPressure && !useInputPseudoPressure ? getPseudoPressureConverter() : nullptr; - if(usePseudoPressure && !useSavedPseudoPressure && calPseudoPressure == nullptr) { + if(usePseudoPressure && !useInputPseudoPressure && calPseudoPressure == nullptr) { qWarning() << "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]; if(usePseudoPressure && (!isFiniteSolverNumber(pressureForLog) - // 敏感性任务只查求解时保存的表,不能读取已被编辑或其他窗口改写的全局配置。 - || !(useSavedPseudoPressure - ? savedPseudoPressure( + // 后台任务使用本次输入中的转换表,避免其他窗口改写全局配置。 + || !(useInputPseudoPressure + ? inputPseudoPressure( m_pInputSnapshot->m_oGridInput.m_oScene, pressureForLog, pressureForLog) : calPseudoPressure(pressureForLog, pressureForLog, -1)) || !isFiniteSolverNumber(pressureForLog))) { @@ -2551,17 +2518,6 @@ 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/nmDataAnalyzeManager.cpp b/Src/nmNum/nmData/nmDataAnalyzeManager.cpp index 13da0406..99cefc52 100644 --- a/Src/nmNum/nmData/nmDataAnalyzeManager.cpp +++ b/Src/nmNum/nmData/nmDataAnalyzeManager.cpp @@ -4937,7 +4937,7 @@ bool nmDataAnalyzeManager::commitPebiResultSnapshot( m_sCurrentResultWellInstanceId = sNextWellInstanceId; pCandidate.clear(); rebindPebiResultSnapshotToWells(); - // 新模型成功发布后丢弃旧敏感性结果,采样设置仍由窗口按新快照刷新。 + // 新模型成功发布后丢弃旧敏感性结果,采样参数由窗口读取当前模型。 if(m_pSensitiveData != nullptr && !m_pSensitiveData->getResults().isNull()) { m_pSensitiveData->setResults(QSharedPointer()); emit sigSensitiveResultsChanged(); diff --git a/Src/nmNum/nmData/nmDataSensitive.cpp b/Src/nmNum/nmData/nmDataSensitive.cpp index ac1e66af..9775f648 100644 --- a/Src/nmNum/nmData/nmDataSensitive.cpp +++ b/Src/nmNum/nmData/nmDataSensitive.cpp @@ -512,7 +512,6 @@ nmDataSensitive& nmDataSensitive::operator=(const nmDataSensitive& other) m_calcType = other.m_calcType; m_totalModelCount = other.m_totalModelCount; m_usesBaseUnits = other.m_usesBaseUnits; - m_modelSnapshotId = other.m_modelSnapshotId; m_variables = other.m_variables; m_monteCarloCount = other.m_monteCarloCount; m_monteCarloValues = other.m_monteCarloValues; @@ -532,8 +531,6 @@ rapidjson::Value nmDataSensitive::ToJsonValue( rapidjson::Value root(rapidjson::kObjectType); // 旧配置中的采样值是显示单位,新配置统一保存业务基准单位。 root.AddMember("UsesBaseUnits", m_usesBaseUnits, allocator); - // 随配置保存范围所属的快照,项目重开后仍能识别尚未刷新的范围。 - root.AddMember("ModelSnapshotId", rapidjson::Value(m_modelSnapshotId.toUtf8().constData(), allocator).Move(), allocator); // 顶部 Calculation type root.AddMember("CalculationType", @@ -560,8 +557,6 @@ rapidjson::Value nmDataSensitive::ToJsonValue( // 从 RapidJSON Value 反序列化数据到 nmDataSensitive void nmDataSensitive::FromJsonValue(const rapidjson::Value& jsonValue) { - // 旧项目没有快照标识,首次打开时按已有求解结果初始化范围。 - m_modelSnapshotId = sensitiveTextFromJson(jsonValue, "ModelSnapshotId"); // 旧项目没有 Results 时保持空结果。 m_results.clear(); if(jsonValue.HasMember("Results") && jsonValue["Results"].IsObject()) { @@ -747,19 +742,6 @@ bool nmDataSensitive::usesBaseUnits() const 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::getResults() const { // 曲线容器按共享引用交给界面,重复打开不复制整批结果。 diff --git a/Src/nmNum/nmData/nmPebiResultSnapshot.cpp b/Src/nmNum/nmData/nmPebiResultSnapshot.cpp index fa7d515c..e0f2292c 100644 --- a/Src/nmNum/nmData/nmPebiResultSnapshot.cpp +++ b/Src/nmNum/nmData/nmPebiResultSnapshot.cpp @@ -12,18 +12,6 @@ #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 ced5726b..24812fd0 100644 --- a/Src/nmNum/nmData/nmPebiResultSnapshotBuilder.cpp +++ b/Src/nmNum/nmData/nmPebiResultSnapshotBuilder.cpp @@ -885,17 +885,6 @@ 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/nmWxAutomaticFitting.cpp b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp index f9d923a6..471e16e7 100644 --- a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp +++ b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp @@ -1,7 +1,6 @@ #include "nmWxAutomaticFitting.h" #include "nmCalculationAutoFitPSO.h" #include "nmCalculationAutoFitLM.h" -#include "nmCalculationDllPebiSolverTask.h" #include "nmWxAutomaticfittingStart.h" #include "nmWxParameterProperty.h" #include "nmAttrRegistry.h" @@ -133,8 +132,16 @@ void nmWxAutomaticFitting::updateParameterVisibility(QTableWidget* table, NM_SOL // Dfc 只属于垂直压裂井和多段压裂水平井。普通井隐藏并取消勾选, // 防止切换目标井后不可见的裂缝参数仍进入拟合参数向量。 bool showFractureConductivity = false; - const nmPebiResultWellSnapshot* well = solvedWell(m_targetWellCombo->currentIndex()); - if(well) showFractureConductivity = well->m_bHasDfc; + nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance(); + 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, 6, showFractureConductivity); // 裂缝半长 @@ -423,12 +430,6 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent, nmSensitivityFitSett // 获取数据 nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance(); 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(); // 自动范围始终开启;用户在表格中修改上下限后,itemChanged 会临时切换为手工范围。 m_autoParameterRanges = true; @@ -446,9 +447,7 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent, nmSensitivityFitSett } // 每次打开都按当前模型参数生成范围,不沿用上次的上下限。 initializeSuggestedParameterRanges(); - // 新模型重新初始化范围,同一模型才恢复上次确认的拟合设置。 - if(m_sensitivitySettings && m_sensitivitySettings->confirmed && !m_solvedSnapshot.isNull() && - m_sensitivitySettings->snapshotId == m_solvedSnapshot->getSnapshotId()) loadSensitivitySettings(); + if(m_sensitivitySettings && m_sensitivitySettings->confirmed) loadSensitivitySettings(); m_updatingParameterRanges = false; DEBUG_UI("AutoFitting Constructor completed"); @@ -456,8 +455,10 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent, nmSensitivityFitSett 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; m_autoParameterRanges = false; // 敏感性设置恢复时也固定使用 LM。 @@ -478,7 +479,9 @@ void nmWxAutomaticFitting::loadSensitivitySettings() for(int i = 0; i < 6; ++i) { checks[i]->setChecked(selected[i] && checks[i]->isEnabled()); 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(); // 目标井先加入当前分析主井,再加入已勾选且具备流量、压力数据的包含井。 - // 井列表也取自求解快照,未求解的新井不作为旧模型的拟合目标。 - for(int i = 0; !m_solvedSnapshot.isNull() && i < m_solvedSnapshot->getWellCount(); ++i) { - const nmPebiResultWellSnapshot* well = m_solvedSnapshot->getWellAt(i); - if(well->m_sWellCode == m_solvedSnapshot->getSensitivityWellCode()) - m_targetWellCombo->insertItem(0, well->m_sWellName, well->m_sWellInstanceId); - else if(well->m_eWellMode == NM_CaseWell_RateControlled && !well->m_oCurves.m_vecHistoryPressure.isEmpty()) - m_targetWellCombo->addItem(well->m_sWellName, well->m_sWellInstanceId); - } + nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance(); + if(pManager != nullptr) { + const QString sPrimaryWellCode = pManager->getPrimaryWellCode(); + nmDataWellBase* pPrimaryWell = pManager->findWellByCode(sPrimaryWellCode); + if(pPrimaryWell != nullptr) { + m_targetWellCombo->addItem(pPrimaryWell->getWellName()); + } + + if(pManager->getIncludeOtherWells()) { + const QVector 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))); m_targetWellCombo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon); @@ -872,7 +890,6 @@ void nmWxAutomaticFitting::onAccept() // 仅提交给敏感性窗口;取消、关闭及普通拟合均不经过此分支。 m_sensitivitySettings->parameters = automaticFittingData; m_sensitivitySettings->targetWell = m_targetWellCombo->currentText(); - m_sensitivitySettings->snapshotId = m_solvedSnapshot->getSnapshotId(); m_sensitivitySettings->initialValues.clear(); const int rows[] = {0, 1, 2, 3, 5, 6}; for(int i = 0; i < 6; ++i) @@ -981,43 +998,49 @@ void nmWxAutomaticFitting::onReject() 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) { - // 切井也读取同一次成功模型,默认范围继续使用原有生成及物理限制逻辑。 - const nmPebiResultWellSnapshot* targetWell = solvedWell(index); + // 每次都读取模型实际保留的井参数,避免拟合结束后切井又恢复到打开窗口时的旧副本。 + nmDataAnalyzeManager* manager = nmDataAnalyzeManager::getCurrentInstance(); + if(!manager) { + return; + } + nmDataWellBase* targetWell = manager->findWellByName(m_targetWellCombo->itemText(index)); if(!targetWell) { return; } const bool wasUpdatingRanges = m_updatingParameterRanges; m_updatingParameterRanges = true; - updateParameterVisibility(m_parameterTable, - static_cast(m_solvedSnapshot->getSolverSettings().m_nSolverModelType)); + updateParameterVisibility(m_parameterTable, manager->getSolverModelType()); - if(targetWell->m_bHasSkin) { - const double skin = targetWell->m_dSkin; + nmDataPerforation* perforation = targetWell->getPerforation(0); + if(perforation) { + const double skin = perforation->getSkin().getValue().toDouble(); m_parameterTable->item(1, 3)->setText(QString::number(skin, 'g', 10)); if(m_autoParameterRanges) { 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)); if(m_autoParameterRanges) { updateRangeForParameter(2, storage); } - const double conductivity = targetWell->m_dDfc; + bool fracturedWell = false; + double conductivity = 0.0; double halfLength = 0.0; - if(targetWell->m_bHasDfc && nmCalculationDllPebiSolverTask::getSolvedFractureHalfLength( - m_solvedSnapshot->getSensitivityInput(), targetWell->m_sWellCode, halfLength)) { + if(nmDataVerticalFracturedWell* well = dynamic_cast(targetWell)) { + conductivity = well->getDfc().getValue().toDouble(); + halfLength = well->getFractureHalfLength().getValue().toDouble(); + fracturedWell = true; + } else if(nmDataHorizontalFracturedWell* well = dynamic_cast(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(6, 3)->setText(QString::number(halfLength, 'g', 10)); if(m_autoParameterRanges) { @@ -1390,12 +1413,8 @@ void nmWxAutomaticFitting::refreshParametersFromModel() return; } - // 拟合结束后重新取得最后成功快照,失败时仍沿用之前成功模型的初值。 + // 同步储层副本,避免下一次保存设置时把未拟合的储层属性覆盖成旧值。 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; m_updatingParameterRanges = true; m_parameterTable->item(0, 3)->setText(QString::number( @@ -1403,7 +1422,8 @@ void nmWxAutomaticFitting::refreshParametersFromModel() m_parameterTable->item(3, 3)->setText(QString::number( reservoirData.getPorosity().getValue().toDouble(), 'g', 10)); - // 先刷新快照初值,再统一重建范围,避免即时校验读到新旧值混合的表格。 + // 成功或失败都从当前目标井读取已落地的参数,不采用优化器尚未写回的最佳候选。 + // 先刷新全部初值,再统一重建范围,避免即时校验读到新旧值混合的表格。 m_autoParameterRanges = false; onWellSelected(m_targetWellCombo->currentIndex()); m_autoParameterRanges = true; diff --git a/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp b/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp index a1fc0abb..8912ce76 100644 --- a/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp +++ b/Src/nmNum/nmSubWxs/nmWxNumericalDesign.cpp @@ -1450,10 +1450,10 @@ void nmWxNumericalDesign::onForecastClicked() void nmWxNumericalDesign::updateSensitiveButtonEnabled() { - // 只有本次运行中成功求解过才有完整输入,编辑参数不会改变已有分析基准。 + // 已有成功模型即可分析或拟合,重开项目后同样可用。 const QSharedPointer solved = m_pDataManager ? m_pDataManager->getPebiResultSnapshot() : QSharedPointer(); - const bool available = !solved.isNull() && !solved->getSensitivityInput().isNull(); + const bool available = !solved.isNull(); m_pSensitiveBtn->setEnabled(available); m_pAutomaticfittingBtn->setEnabled(available); m_pSensitiveBtn->setToolTip(available ? QString() : tr("Generate the model to enable sensitivity analysis.")); diff --git a/Src/nmNum/nmSubWxs/nmWxSensitive.cpp b/Src/nmNum/nmSubWxs/nmWxSensitive.cpp index f57de299..a35565c1 100644 --- a/Src/nmNum/nmSubWxs/nmWxSensitive.cpp +++ b/Src/nmNum/nmSubWxs/nmWxSensitive.cpp @@ -6,7 +6,10 @@ #include "nmDataReservoir.h" #include "nmDataWellBase.h" #include "nmDataPerforation.h" -#include "nmPebiResultSnapshot.h" +#include "iSubWndFitting.h" +#include "mGui/mGuiAnal/iAnalRun.h" + +#pragma comment(lib, "mGuiAnal.lib") #include "nmCalculationDllPebiSolverTask.h" #include "iUnitHelper.h" #include "iUnitGroup.h" @@ -1387,21 +1390,11 @@ void nmWxSensitive::closeEvent(QCloseEvent* event) void nmWxSensitive::refreshModelValues() { - // 六个参数以最后成功求解的快照为准,编辑中的模型只提供属性类型和显示单位。 - const QSharedPointer snapshot = m_pDataManager - ? m_pDataManager->getPebiResultSnapshot() : QSharedPointer(); - 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; + // 每次打开或模型更新时读取当前参数,沿用已有单位和物理范围限制。 + nmDataWellBase* well = m_pDataManager + ? m_pDataManager->findWellByCode(m_pDataManager->getPrimaryWellCode()) : nullptr; nmDataReservoir* reservoir = m_pDataManager ? m_pDataManager->getReservoirData() : 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; - } - } + bool modelChanged = false; const QStringList names = QStringList() << "Skin" << "C" << "Pi" << "k" << "h" << QString(QChar(0x03c6)); for (int n = 0; n < names.size(); ++n) { nmDataAttribute* attribute = nullptr; @@ -1427,21 +1420,6 @@ void nmWxSensitive::refreshModelValues() index = m_sensitiveData.getVariableCount() - 1; } 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; if (!attribute || !samplingPhysicalRange(name, physicalMin, physicalMax)) { variable.setEnabled(false); @@ -1468,6 +1446,8 @@ void nmWxSensitive::refreshModelValues() for (int i = 0; migrated && i < values.size(); ++i) migrated = convertSamplingValue(name, values[i], oldUnit, baseUnit, values[i]); } + const bool resetRanges = fresh || !nearlyEqualSamplingValue(previousModel, attribute->getValue().toDouble()); + modelChanged = modelChanged || resetRanges; // 识别 Skin、Pi 的自动默认范围,按当前模型更新,保留用户自定义区间。 const bool previousDefault = variable.getMode() == nmDataSensitive::VariableSampling::MODE_AUTOMATIC && ((nearlyEqualSamplingValue(minimum, qMin(previousModel / 2.0, previousModel * 2.0)) && @@ -1480,7 +1460,7 @@ void nmWxSensitive::refreshModelValues() variable.getModelValue().getMaxValue().isValid() && nearlyEqualSamplingValue(minimum, variable.getModelValue().getMinValue().toDouble()) && nearlyEqualSamplingValue(maximum, variable.getModelValue().getMaxValue().toDouble()))); - // 新的正式求解结果重新建立范围,采样数量、模式及手动值保持原设置。 + // 当前模型值变化时重新建立范围,采样数量、模式及手动值保持原设置。 if (resetRanges || !migrated || ((name == "Skin" || name == "Pi") && previousDefault)) { const double model = attribute->getValue().toDouble(); minimum = qMin(model / 2.0, model * 2.0); @@ -1526,14 +1506,12 @@ void nmWxSensitive::refreshModelValues() if (!isSupportedSamplingVariable(m_sensitiveData.getVariable(i).getVarName())) m_sensitiveData.getVariable(i).setEnabled(false); m_sensitiveData.setUsesBaseUnits(true); - if (!snapshot.isNull()) m_sensitiveData.setModelSnapshotId(snapshot->getSnapshotId()); + if (modelChanged) m_sensitiveData.clearMonteCarloSamples(); } void nmWxSensitive::onModelResultChanged() { - // 查看井切换也会发送快照通知;只有新求解快照才刷新已打开窗口。 - const QSharedPointer snapshot = m_pDataManager->getPebiResultSnapshot(); - if (snapshot.isNull() || snapshot->getSnapshotId() == m_sensitiveData.getModelSnapshotId()) return; + // 通知后按当前模型刷新数值;未改变的参数保留用户范围。 m_loading = true; refreshModelValues(); loadDataToUI(true); @@ -2495,6 +2473,34 @@ 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; + // 第四种可选择其他井拟合,其历史曲线也要使用本次的拟压力配置。 + 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; QScopedPointer task; try { @@ -2506,11 +2512,11 @@ void nmWxSensitive::onGenerateClicked() return; } } - // 从最后成功结果恢复完整输入,当前编辑参数和全局拟压力配置不参与本次分析。 - nmCalculationDllPebiSolverTask source(QString(), m_pDataManager, QString(), nullptr, false, true); + // 启动时读取当前模型,后台各组计算只修改任务自己的参数副本。 + nmCalculationDllPebiSolverTask source(QString(), m_pDataManager, well->getWellName()); // 第四种直接交给后台任务逐组拟合,仍使用主窗口原有进度框。 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&) { error = tr("Insufficient memory to prepare solver input."); }