diff --git a/Include/nmNum/nmCalculation/nmCalculationAutoFitLM.h b/Include/nmNum/nmCalculation/nmCalculationAutoFitLM.h index e4b011f4..c67b4406 100644 --- a/Include/nmNum/nmCalculation/nmCalculationAutoFitLM.h +++ b/Include/nmNum/nmCalculation/nmCalculationAutoFitLM.h @@ -11,6 +11,9 @@ #include #include "nmCalculation_global.h" +#include + +struct nmSensitivityFitSettings; // 双对数曲线误差分解。预调整使用纵向偏差,形状阶段使用双曲线斜率残差, // 总误差部分只使用 total 接受候选;两个 LM 目标均联合调整有效参数。 @@ -78,6 +81,10 @@ public: void setTargetLogLogData(const QVector >& targetData); bool startAutoFitting(); + // 在样本副本上复用拟合算法,评价和停止由敏感性任务负责,不写回当前模型。 + bool startSensitivityFitting(const nmSensitivityFitSettings& settings, + const std::function >(const QVector&)>& evaluate, + const std::function& cancelled, bool verticalWell); void stopFitting(); QVector getBestSolution() const; double getBestFitness() const; @@ -162,6 +169,10 @@ private: const QVector >& result); private: + const nmSensitivityFitSettings* m_sensitivitySettings; + std::function >(const QVector&)> m_sensitivityEvaluate; + std::function m_sensitivityCancelled; + bool m_sensitivityVerticalWell; bool m_isRunning; bool m_shouldStop; bool m_isFinalizing; diff --git a/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h b/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h index 61091194..1826f4c1 100644 --- a/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h +++ b/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h @@ -19,6 +19,7 @@ class nmDataAnalyzeManager; class nmPebiResultSnapshot; class vtkDoubleArray; struct nmPebiSolverInputSnapshot; +struct nmSensitivityFitSettings; struct nmPebiManualCaptureState; struct nmPebiStaticPropertyValues; // DLL 类型只用于私有函数的引用参数,界面模块无需包含求解器内部头文件。 @@ -122,9 +123,10 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread { std::vector& results, QString* errorMessage = nullptr) const; - /** @brief 在源输入捕获线程创建独立批量任务,随后由 start() 在后台执行。 */ + /** @brief 创建后台分析任务;传入拟合设置时对每组样本执行 LM 拟合。 */ nmCalculationDllPebiSolverTask* createSensitivityBatch( - const nmDataSensitive& sampling, QString* errorMessage = nullptr) const; + const nmDataSensitive& sampling, QString* errorMessage = nullptr, + const nmSensitivityFitSettings* improveSettings = nullptr) const; /** @brief 线程结束后读取临时批量结果,引用在任务析构或重新运行后失效。 */ const std::vector& getSensitivityResults() const; /** @brief 仅在启动前或线程结束后获取批次数据,供主线程保存及显示。 */ @@ -146,6 +148,8 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread { void calculateSingleRun(); /** @brief 复用当前独立副本串行计算,保留进度、停止状态及每组失败原因。 */ bool executeSensitivityBatch(); + /** @brief 固定本组采样参数,在独立输入上拟合并生成最终曲线。 */ + bool improveSensitivityRun(); /** @brief 执行当前唯一的 PEBI 求解模式。 */ bool execute(); /** @brief 成对释放构造时登记的 DataManager 后台使用权。 */ @@ -222,6 +226,7 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread { bool m_bPendingFullResultReady; ///< 是否已有可由主线程一次提交的完整结果。 QAtomicInt m_nCancelRequested; ///< 跨线程协作式停止标记,1 表示请求停止。 QAtomicInt m_nWasCancelled; ///< run() 已按停止请求丢弃本次局部结果。 + QSharedPointer m_pSensitivityFitSettings; ///< 第四种批量拟合的确认设置。 QSharedPointer m_pSensitivitySampling; ///< 批量启动前冻结的配置。 QSharedPointer m_pSensitivityResult; ///< 后台独占的批次数据。 QString m_sSensitivityFailureReason; ///< 整次批量无法继续执行的原因。 diff --git a/Include/nmNum/nmData/nmDataAutomaticFitting.h b/Include/nmNum/nmData/nmDataAutomaticFitting.h index 6b7cb2e8..8645dd62 100644 --- a/Include/nmNum/nmData/nmDataAutomaticFitting.h +++ b/Include/nmNum/nmData/nmDataAutomaticFitting.h @@ -4,6 +4,7 @@ #include "nmData_global.h" #include "nmDataBase.h" #include "nmDataAttribute.h" +#include class NM_DATA_EXPORT nmDataAutomaticFitting : public nmDataBase { @@ -127,4 +128,15 @@ private: bool m_fractureHalfLengthSelected; // 是否选择裂缝半长进行拟合 }; +// 敏感性窗口持有的临时拟合设置,确认后才替换,不写入当前模型。 +struct nmSensitivityFitSettings +{ + nmSensitivityFitSettings() : confirmed(false) {} + bool confirmed; + nmDataAutomaticFitting parameters; + QString targetWell; + QString snapshotId; + QVector initialValues; +}; + #endif // NMDATAAUTOMATICFITTING_H diff --git a/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h b/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h index f8edf144..df206e94 100644 --- a/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h +++ b/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h @@ -29,17 +29,6 @@ #include "nmSubWxs_global.h" #include "nmPebiResultSnapshot.h" -// 敏感性窗口持有的临时拟合设置,确认后才替换,不写入当前模型。 -struct nmSensitivityFitSettings -{ - nmSensitivityFitSettings() : confirmed(false) {} - bool confirmed; - nmDataAutomaticFitting parameters; - QString targetWell; - QString snapshotId; - QVector initialValues; -}; - class NM_SUB_WXS_EXPORT nmWxAutomaticFitting : public iDlgBase { Q_OBJECT diff --git a/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp b/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp index 4629cb79..f97b106f 100644 --- a/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp @@ -495,6 +495,8 @@ static QStringList traceParameterNames() nmCalculationAutoFitLM::nmCalculationAutoFitLM(QObject* parent) : QObject(parent) + , m_sensitivitySettings(nullptr) + , m_sensitivityVerticalWell(false) , m_isRunning(false) , m_shouldStop(false) , m_isFinalizing(false) @@ -1079,8 +1081,10 @@ bool nmCalculationAutoFitLM::loadAllConfigFromDataManager() void nmCalculationAutoFitLM::loadOptimizationConfig() { // LM 只读取迭代次数和目标误差,其他数值控制保留在现有算法实现中。 - nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); - nmDataAutomaticFitting fittingData = dataManager->getAutomaticFittingDataCopy(); + // 敏感性运行只读取确认过的设置,普通拟合保留原来的配置来源。 + nmDataAutomaticFitting fittingData = m_sensitivitySettings + ? m_sensitivitySettings->parameters + : nmDataAnalyzeManager::getCurrentInstance()->getAutomaticFittingDataCopy(); m_maxIterations = fittingData.getIterationCount().getValue().toInt(); m_targetError = fittingData.getErrorTolerance().getValue().toDouble(); @@ -1096,8 +1100,10 @@ void nmCalculationAutoFitLM::loadParameterBounds() // - m_parameterSelected[6]:完整参数体系中每个参数是否参与拟合; // - m_parameterLower/Upper[6]:完整参数体系的搜索上下界; // - m_enabledParamIndices:将勾选参数向量映射回完整参数索引。 - nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); - nmDataAutomaticFitting fittingData = dataManager->getAutomaticFittingDataCopy(); + // 敏感性运行只读取确认过的设置,普通拟合保留原来的配置来源。 + nmDataAutomaticFitting fittingData = m_sensitivitySettings + ? m_sensitivitySettings->parameters + : nmDataAnalyzeManager::getCurrentInstance()->getAutomaticFittingDataCopy(); m_parameterSelected.resize(6); m_parameterSelected[0] = fittingData.getPermeabilitySelected(); @@ -1141,6 +1147,24 @@ void nmCalculationAutoFitLM::loadParameterBounds() } // ==================== 自动拟合核心方法 ==================== +bool nmCalculationAutoFitLM::startSensitivityFitting( + const nmSensitivityFitSettings& settings, + const std::function >(const QVector&)>& evaluate, + const std::function& cancelled, bool verticalWell) +{ + // 同步复用原算法;返回前释放回调,避免持有任务局部对象。 + m_sensitivitySettings = &settings; + m_sensitivityEvaluate = evaluate; + m_sensitivityCancelled = cancelled; + m_sensitivityVerticalWell = verticalWell; + m_targetWellName = settings.targetWell; + const bool succeeded = startAutoFitting(); + m_sensitivitySettings = nullptr; + m_sensitivityEvaluate = std::function >(const QVector&)>(); + m_sensitivityCancelled = std::function(); + return succeeded; +} + bool nmCalculationAutoFitLM::startAutoFitting() { // 总入口只负责准备数据、调用有限差分 + LM/信赖域,并写回最终结果。 @@ -1195,7 +1219,7 @@ bool nmCalculationAutoFitLM::startAutoFitting() resetOptimizer(); // 输入校验通过后,为本次拟合创建新的临时计算目录。 cleanupTemporaryDirectory(); - if(!initializeTemporaryDirectory()) { + if(!m_sensitivitySettings && !initializeTemporaryDirectory()) { m_lastError = tr("Cannot create the automatic fitting temporary directory"); emit logMessageGenerated(tr("ERROR: %1").arg(m_lastError)); return false; @@ -1205,7 +1229,7 @@ bool nmCalculationAutoFitLM::startAutoFitting() m_isFinalizing = false; m_currentIteration = -1; m_initialValues = savedInitialValues; - initializeTraceFile(); + if(!m_sensitivitySettings) initializeTraceFile(); // 开头只展示一次初始参数和总误差,不展示形状误差。 emit logMessageGenerated(tr("========== LM fitting started ==========")); emit logMessageGenerated(tr("Target well: %1").arg(m_targetWellName)); @@ -1317,7 +1341,14 @@ bool nmCalculationAutoFitLM::startAutoFitting() return false; } - // 只有最终完整求解实际执行并提交快照后才能置为成功。 + // 敏感性任务自己用最优参数收尾,不能进入普通拟合的模型回写流程。 + if(m_sensitivitySettings) { + m_isRunning = false; + return !m_shouldStop && !m_sensitivityCancelled() && + !m_globalBestPosition.isEmpty() && m_globalBestFitness < 1.0e9; + } + + // 普通拟合只有完整求解并提交快照后才算成功。 bool finalFullSolverSucceeded = false; bool finalFullSolverExecuted = false; @@ -1454,6 +1485,17 @@ bool nmCalculationAutoFitLM::startAutoFitting() void nmCalculationAutoFitLM::extractUserInitialValues() { + // 按勾选顺序提取快照初值,并沿用窗口确认的上下限。 + if(m_sensitivitySettings) { + m_initialValues.clear(); + for(int i = 0; i < m_enabledParamIndices.size(); ++i) { + const int index = m_enabledParamIndices[i]; + m_initialValues.append(qBound(m_parameterLower[index], + m_sensitivitySettings->initialValues[index], m_parameterUpper[index])); + } + return; + } + // 从当前项目模型读取用户已有初始参数。 // 只提取用户勾选的参数,并按 m_enabledParamIndices 的顺序写入 m_initialValues。 // 这些值用于初始解真实评价、LM 起点和最终精英保护。 @@ -1574,9 +1616,10 @@ bool nmCalculationAutoFitLM::evaluateTrustRegionPoint( StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() { // 本次拟合开始时按目标井的实际井型确定流程,普通直井跳过形状调整。 - nmDataAnalyzeManager* dataManager = nmDataAnalyzeManager::getCurrentInstance(); + nmDataAnalyzeManager* dataManager = m_sensitivitySettings ? nullptr : nmDataAnalyzeManager::getCurrentInstance(); nmDataWellBase* targetWell = dataManager ? dataManager->findWellByName(m_targetWellName) : nullptr; - const bool skipShapeStage = targetWell && targetWell->getWellType() == NM_WELL_MODEL::Vertical_Well; + const bool skipShapeStage = m_sensitivitySettings ? m_sensitivityVerticalWell + : targetWell && targetWell->getWellType() == NM_WELL_MODEL::Vertical_Well; const int dimensions = getEnabledParameterCount(); if(dimensions <= 0 || m_enabledParamIndices.size() != dimensions) { m_lastError = tr("No valid parameters are available for trust-region fitting"); @@ -1654,7 +1697,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() auto restoreEvaluationState = [&](const TrustRegionEvaluation& evaluation) { // evaluateFitness() 会把试算参数写入 DataManager。无论候选是否接受, // 下一次计算前都恢复到唯一的已接受工作点,防止失败试算污染后续求解。 - applyParametersToDataManager(evaluation.parameters); + if(!m_sensitivitySettings) applyParametersToDataManager(evaluation.parameters); m_lastObjectiveBreakdown = evaluation.breakdown; m_lastEvaluatedLogLogData = evaluation.curve; }; @@ -1673,6 +1716,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() }; auto processPauseAndStop = [&]() -> bool { + if(m_sensitivitySettings) m_shouldStop = m_sensitivityCancelled(); QApplication::processEvents(); return !m_shouldStop; }; @@ -2371,6 +2415,18 @@ double nmCalculationAutoFitLM::evaluateFitness(const QVector& parameters m_lastEvaluatedLogLogData.clear(); m_lastObjectiveBreakdown = AutoFitObjectiveBreakdownLM(); + // 候选求解在冻结输入副本上执行,误差口径仍使用本算法现有实现。 + if(m_sensitivitySettings) { + m_shouldStop = m_sensitivityCancelled(); + if(m_shouldStop || !validateParameters(parameters)) return 1.0e10; + const QVector > curve = m_sensitivityEvaluate(parameters); + m_shouldStop = m_sensitivityCancelled(); + if(m_shouldStop || !validateLogLogData(curve)) return 1.0e10; + const double fitness = calculateLogLogCurveError(m_targetLogLogData, curve); + if(fitness < 1.0e9) m_lastEvaluatedLogLogData = curve; + return fitness; + } + try { DEBUG_OUT(QString("%1: Call #%2 - Starting evaluation with %3 parameters") .arg(funcName).arg(callCount).arg(parameters.size())); diff --git a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp index 2d4b3b25..d8455bab 100644 --- a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp @@ -22,6 +22,8 @@ #include "nmCalculationPebiGrid.h" #include "nmCalculationUtils.h" +#include "nmCalculationAutoFitLM.h" +#include "nmDataAutomaticFitting.h" #include "nmDataAnalyzeManager.h" #include "nmDataPvtParaForPebi.h" @@ -50,7 +52,7 @@ #include /** - * @brief PEBI 求解任务独占的完整值输入,敏感性任务仅更新采样标量。 + * @brief PEBI 求解任务独占的完整值输入,敏感性采样和拟合只修改此副本。 */ struct nmPebiSolverInputSnapshot { @@ -948,7 +950,8 @@ bool nmCalculationDllPebiSolverTask::calculateSensitivityBatch( } nmCalculationDllPebiSolverTask* nmCalculationDllPebiSolverTask::createSensitivityBatch( - const nmDataSensitive& sampling, QString* errorMessage) const + const nmDataSensitive& sampling, QString* errorMessage, + const nmSensitivityFitSettings* improveSettings) const { if(errorMessage) errorMessage->clear(); try { @@ -975,6 +978,9 @@ nmCalculationDllPebiSolverTask* nmCalculationDllPebiSolverTask::createSensitivit if(errorMessage) *errorMessage = creationError; return nullptr; } + // 第四种的拟合配置按值冻结,整批样本共用确认过的初值和范围。 + if(improveSettings) task->m_pSensitivityFitSettings = + QSharedPointer(new nmSensitivityFitSettings(*improveSettings)); task->m_pSensitivitySampling = QSharedPointer( new nmDataSensitive(frozenSampling)); // 批次结果单独持有冻结配置与历史曲线,关闭参数窗口后仍可保存和显示。 @@ -1059,6 +1065,116 @@ bool nmCalculationDllPebiSolverTask::getSolvedFractureHalfLength( return false; } +bool nmCalculationDllPebiSolverTask::improveSensitivityRun() +{ + // 采样值已经写入本任务;候选只覆盖选中的拟合参数,不触碰管理器和正式结果。 + const nmSensitivityFitSettings& settings = *m_pSensitivityFitSettings; + const nmDataAutomaticFitting& fit = settings.parameters; + nmPebiGridInputSnapshot& grid = m_pInputSnapshot->m_oGridInput; + nmPebiStaticPropertyCalculationInput& properties = m_pInputSnapshot->m_oStaticPropertyInput; + int target = -1; + for(int i = 0; i < grid.m_vecWellInputs.size(); ++i) { + 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.")); + const bool selected[] = { fit.getPermeabilitySelected(), fit.getSkinSelected(), + fit.getWellboreStorageSelected(), fit.getPorositySelected(), + fit.getFractureConductivitySelected(), fit.getFractureHalfLengthSelected() }; + for(int i = 0; i < properties.m_vecDataSets.size(); ++i) { + const nmPropertyInterpolationDataSet& field = properties.m_vecDataSets[i]; + if(field.useForCalculation && ((selected[0] && field.property == "k") || + (selected[3] && field.property == "phi"))) + return fail(tr("The sampled parameters are invalid or use a non-uniform field.")); + } + + // 井型数组与求解槽位顺序一致,保留原端点以免反复缩放造成几何累积偏差。 + std::vector*> fractures; + std::vector > originalFractures; + if(selected[4] || selected[5]) { + size_t vertical = 0, horizontal = 0; + for(int i = 0; i <= target; ++i) { + const int type = grid.m_vecSolverWellOrder[i].m_eWellType; + if(type == Vertical_Fractured_Well) { + if(i == target) fractures.push_back(&grid.m_oGridInput.FractureVerticalWell[vertical]); + ++vertical; + } else if(type == Horizontal_Fractured_Well) { + if(i == target) { + std::vector >& well = + grid.m_oGridInput.MultistageFracturedHorizontalWell[horizontal]; + for(size_t j = 0; j < well.size(); ++j) fractures.push_back(&well[j]); + } + ++horizontal; + } + } + if(fractures.empty()) return fail(tr("Fracture parameters are unavailable for the target well.")); + for(size_t i = 0; i < fractures.size(); ++i) originalFractures.push_back(*fractures[i]); + } + + const QString resultWellCode = m_sAutoFitTargetWellCode; + m_sAutoFitTargetWellCode = grid.m_vecWellInputs[target].m_sWellCode; + auto evaluate = [&](const QVector& parameters) -> QVector > { + if(isCancelRequested()) return QVector >(); + QVector values = settings.initialValues; + int index = 0; + for(int i = 0; i < 6; ++i) if(selected[i]) values[i] = parameters[index++]; + if(selected[0]) { + grid.m_oScene.Base.k_ref = nmCalculationUtils::milliDarcyToDarcy(values[0]); + properties.m_dReferencePermeabilityMilliDarcy = values[0]; + } + if(selected[1]) { + grid.m_oScene.CS.S[target] = values[1]; + grid.m_vecWellInputs[target].m_dSkin = values[1]; + } + if(selected[2]) { + grid.m_oScene.CS.C[target] = values[2]; + grid.m_vecWellInputs[target].m_dWellboreStorage = values[2]; + } + if(selected[3]) { + grid.m_oScene.Base.phi_ref = values[3]; + properties.m_dReferencePorosity = values[3]; + } + for(size_t i = 0; i < fractures.size(); ++i) { + std::vector& fracture = *fractures[i]; + fracture = originalFractures[i]; + if(selected[4]) fracture[5] = values[4]; + if(selected[5]) { + const double dx = (fracture[2] - fracture[0]) * 0.5; + const double dy = (fracture[3] - fracture[1]) * 0.5; + const double centerX = (fracture[0] + fracture[2]) * 0.5; + const double centerY = (fracture[1] + fracture[3]) * 0.5; + const double scale = values[5] / std::sqrt(dx * dx + dy * dy); + fracture[0] = centerX - dx * scale; + fracture[1] = centerY - dy * scale; + fracture[2] = centerX + dx * scale; + fracture[3] = centerY + dy * scale; + } + } + if(selected[4]) grid.m_vecWellInputs[target].m_dDfc = values[4]; + m_pInputSnapshot->m_bRequiresGridCalculation = !fractures.empty(); + m_sFailureReason.clear(); + calculateSingleRun(); + return m_lastRunSucceeded ? m_autoFitResultLogLog : QVector >(); + }; + auto cancelled = [&]() -> bool { return isCancelRequested(); }; + const bool verticalWell = grid.m_vecSolverWellOrder[target].m_eWellType == Vertical_Well; + // 敏感性改善仅使用 LM,直接复用其拟合流程。 + nmCalculationAutoFitLM fitter; + fitter.setTargetLogLogData(grid.m_vecWellInputs[target].m_vecHistoryLogLog); + const bool succeeded = fitter.startSensitivityFitting(settings, evaluate, cancelled, verticalWell); + const QVector best = fitter.getBestSolution(); + const QString error = fitter.getLastError(); + // 算法最后评价的候选不一定最优;恢复分析井后用最优参数求解并保留三条曲线。 + m_sAutoFitTargetWellCode = resultWellCode; + if(isCancelRequested()) return false; + if(!succeeded) { + m_sFailureReason.clear(); + return fail(error.isEmpty() ? tr("No valid fitting result was obtained.") : error); + } + evaluate(best); + return m_lastRunSucceeded; +} + bool nmCalculationDllPebiSolverTask::executeSensitivityBatch() { m_pSensitivityResult->m_runs.clear(); @@ -1069,9 +1185,15 @@ bool nmCalculationDllPebiSolverTask::executeSensitivityBatch() progressTimer.start(); emit sigSensitivityProgress(0, count, 0); try { + // 拟合还会改动其他井的参数、裂缝几何及网格,整批只保留一份初始输入供逐组恢复。 + QScopedPointer fitBaseline; + if(!m_pSensitivityFitSettings.isNull()) + fitBaseline.reset(new nmPebiSolverInputSnapshot(*m_pInputSnapshot)); for(quint64 runIndex = 0; runIndex < count; ++runIndex) { if(isCancelRequested()) return false; + // 先还原输入,再由 updateSensitivityRun 填入本组采样值;停止标记不随输入重置。 + if(!fitBaseline.isNull() && runIndex > 0) *m_pInputSnapshot = *fitBaseline; SensitivityRunResult result; result.m_nRunIndex = runIndex; // 确定性组合和 Monte-Carlo 样本均按预览顺序读取,后台不重新采样。 @@ -1084,8 +1206,12 @@ bool nmCalculationDllPebiSolverTask::executeSensitivityBatch() &result.m_sFailureReason); } if(inputReady) { - calculateSingleRun(); - result.m_bSucceeded = m_lastRunSucceeded; + if(m_pSensitivityFitSettings.isNull()) { + calculateSingleRun(); + result.m_bSucceeded = m_lastRunSucceeded; + } else { + result.m_bSucceeded = improveSensitivityRun(); + } if(result.m_bSucceeded) { result.m_vecPressure = m_autoFitResultPressure; result.m_vecLogLog = m_autoFitResultLogLog; diff --git a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp index c496d75a..f9d923a6 100644 --- a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp +++ b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp @@ -460,7 +460,8 @@ void nmWxAutomaticFitting::loadSensitivitySettings() m_targetWellCombo->setCurrentIndex(m_targetWellCombo->findText(m_sensitivitySettings->targetWell)); automaticFittingData = m_sensitivitySettings->parameters; m_autoParameterRanges = false; - m_algorithmCombo->setCurrentIndex(automaticFittingData.getAlgorithmName() == "LM" ? 1 : 0); + // 敏感性设置恢复时也固定使用 LM。 + m_algorithmCombo->setCurrentIndex(1); m_iterationEdit->setText(QString::number(automaticFittingData.getIterationCount().getValue().toInt())); m_errorLimitEdit->setText(QString::number(automaticFittingData.getErrorTolerance().getValue().toDouble(), 'g', 10)); const int rows[] = {0, 1, 2, 3, 5, 6}; @@ -711,7 +712,9 @@ void nmWxAutomaticFitting::setupControlPanel() m_algorithmCombo = new QComboBox(); m_algorithmCombo->addItem(tr("PSO (Particle Swarm)")); m_algorithmCombo->addItem(tr("LM")); - m_algorithmCombo->setCurrentIndex(0); + // 从敏感性分析打开时仅使用 LM,普通自动拟合仍可选择算法。 + m_algorithmCombo->setCurrentIndex(m_sensitivitySettings ? 1 : 0); + m_algorithmCombo->setEnabled(!m_sensitivitySettings); connect(m_algorithmCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onAlgorithmChanged(int))); @@ -1066,7 +1069,7 @@ void nmWxAutomaticFitting::setAutomaticFittingValue() automaticFittingData.getErrorTolerance().setValue(m_errorLimitEdit->text().toDouble()); // 敏感性只收集设置,初值由调用方保存,不写回井和储层参数。 if(m_sensitivitySettings) { - automaticFittingData.setAlgorithmName(m_algorithmCombo->currentIndex() == 1 ? "LM" : "PSO"); + automaticFittingData.setAlgorithmName("LM"); return; } diff --git a/Src/nmNum/nmSubWxs/nmWxSensitive.cpp b/Src/nmNum/nmSubWxs/nmWxSensitive.cpp index 40b47c57..a6793402 100644 --- a/Src/nmNum/nmSubWxs/nmWxSensitive.cpp +++ b/Src/nmNum/nmSubWxs/nmWxSensitive.cpp @@ -2330,12 +2330,6 @@ void nmWxSensitive::onTreeItemChanged(QTreeWidgetItem* item, int column) void nmWxSensitive::onGenerateClicked() { - // 第四种的批量拟合尚未接入,不能误用普通 Monte-Carlo 生成结果。 - if(calculationTypeCombo->currentIndex() == 3) { - QMessageBox::information(this, tr("Sensitivity"), - tr("Monte-Carlo + Improve calculation is not implemented yet.")); - return; - } if(m_calculationRunning || !m_pDataManager || !saveDataFromUI()) return; QString error; QScopedPointer task; @@ -2350,7 +2344,9 @@ void nmWxSensitive::onGenerateClicked() } // 从最后成功结果恢复完整输入,当前编辑参数和全局拟压力配置不参与本次分析。 nmCalculationDllPebiSolverTask source(QString(), m_pDataManager, QString(), nullptr, false, true); - task.reset(source.createSensitivityBatch(m_sensitiveData, &error)); + // 第四种直接交给后台任务逐组拟合,仍使用主窗口原有进度框。 + task.reset(source.createSensitivityBatch(m_sensitiveData, &error, + calculationTypeCombo->currentIndex() == 3 ? m_improveSettings.data() : nullptr)); } catch(const std::bad_alloc&) { error = tr("Insufficient memory to prepare solver input."); }