From 1237b44a4c443385edd210e1d86034f4625588f5 Mon Sep 17 00:00:00 2001 From: lvjunjie Date: Wed, 30 Sep 2026 17:09:52 +0800 Subject: [PATCH] =?UTF-8?q?refactor(nmNum):=20=E4=BC=98=E5=8C=96=20LM=20?= =?UTF-8?q?=E6=8B=9F=E5=90=88=E6=97=A5=E5=BF=97=E5=B9=B6=E5=B1=95=E7=A4=BA?= =?UTF-8?q?=E8=AF=AF=E5=B7=AE=E6=94=B9=E5=96=84=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Bin/Config/Lang/cn/nmNum_cn.qm | Bin 147326 -> 152000 bytes Bin/Config/Lang/cn/nmNum_cn.ts | 170 ++++++++++++ .../nmCalculation/nmCalculationAutoFitLM.h | 9 + .../nmCalculationDllPebiSolverTask.h | 2 + .../nmCalculation/nmCalculationAutoFitLM.cpp | 244 +++++++++++------- .../nmCalculationDllPebiSolverTask.cpp | 2 + .../nmSubWxs/nmWxAutomaticFittingStart.cpp | 36 +-- 7 files changed, 351 insertions(+), 112 deletions(-) diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm index 4c6dfbfc5c98350eb79752b0b99b6d96f343fb36..c234a7d0a9056a13814602450e07215e20761d6d 100644 GIT binary patch delta 16934 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 IRUZ?oG*{cWDVbPZUyjncIP1IMwC?<^di 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 diff --git a/Bin/Config/Lang/cn/nmNum_cn.ts b/Bin/Config/Lang/cn/nmNum_cn.ts index ad1f6241..4aee9b73 100644 --- a/Bin/Config/Lang/cn/nmNum_cn.ts +++ b/Bin/Config/Lang/cn/nmNum_cn.ts @@ -664,6 +664,172 @@ Reason: %1 nmCalculationAutoFitLM + + [Fitting completed] + 【拟合完成】 + + + [Fitting ended] + 【拟合结束】 + + + [Initial parameters] + 【初始参数】 + + + [Stage 1: Height alignment] +Adjust permeability only; accept by height error. + 【第一阶段:高度预调】 +只调整渗透率,按高度误差判断是否接受。 + + + [Stage 2: Joint LM - Shape] +Adjust selected parameters; accept by shape error. + 【第二阶段:联合 LM · 形状】 +调整勾选参数,按形状误差判断是否接受。 + + + [Stage 2: Joint LM - Total error] +Adjust selected parameters; accept by total error. + 【第二阶段:联合 LM · 总误差】 +调整勾选参数,按总误差判断是否接受。 + + + %1 | %2 | %3 | Improvement: %4% + %1|%2|%3|改善:%4% + + + %1 | Solver failed +No valid error was returned; keep the last accepted result and reduce the step. + %1|求解失败 +未返回有效误差,保留上次已接受结果并缩小步长。 + + + + Objective error did not decrease; keep the last accepted result and reduce the step. + + 目标误差未减小,保留上次已接受结果并缩小步长。 + + + ========== LM fitting started ========== + ========== LM 拟合开始 ========== + + + Target well: %1 + 目标井:%1 + + + Target error: %1 + 目标误差:%1 + + + Initial total error: %1 + 初始总误差:%1 + + + Permeability + 渗透率 + + + Skin + 表皮系数 + + + Wellbore storage + 井筒储集系数 + + + Porosity + 孔隙度 + + + Fracture conductivity + 裂缝导流能力 + + + Fracture half length + 裂缝半长 + + + m^3/MPa + m³/MPa + + + mD.m + mD·m + + + Trial %1 | %2 | Height error: %3 -> %4 + 试算 %1|%2|高度误差:%3 → %4 + + + Trial %1 | Solver failed; keep the last accepted result and reduce the step. + 试算 %1|求解失败,保留上次已接受结果并缩小步长。 + + + Height error did not decrease; keep the last accepted result and reduce the step. + 高度误差未减小,保留上次已接受结果并缩小步长。 + + + Pressure and derivative height directions conflict; keep the last accepted result and reduce the step. + 压力与导数的高度偏移方向冲突,保留上次已接受结果并缩小步长。 + + + Height alignment ended: %1 + 高度预调结束:%1 + + + Height alignment skipped: permeability is not selected. + 跳过高度预调:未勾选渗透率。 + + + Calculating sensitivity... + 正在计算灵敏度…… + + + Sensitivity trial + 灵敏度试算 + + + Iteration %1 + 迭代 %1 + + + Shape error: %1 -> %2 + 形状误差:%1 → %2 + + + Total error: %1 -> %2 + 总误差:%1 → %2 + + + Shape phase ended: %1 + 形状部分结束:%1 + + + Total-error phase ended: %1 + 总误差部分结束:%1 + + + Iteration %1 | No feasible descent step; keep the last accepted result. + 迭代 %1|没有可行的下降步,保留上次已接受结果。 + + + Iteration or evaluation budget reached + 达到本阶段迭代次数或试算次数上限 + + + Final total error did not improve; restoring the initial result. + 最终结果无效或总误差变大,恢复初始结果。 + + + Initial solution failed; trying the parameter-range midpoint. + 初始求解失败,将尝试参数范围中点。 + + + Solver calls: %1 + 共调用求解器:%1 次 + Failed to refresh the sampling layer from the current curve. 无法根据当前曲线刷新采样层级。 @@ -4395,6 +4561,10 @@ Supported types: Vertical, Vertical Fractured, and Horizontal Multi-Fractured We nmWxAutomaticfittingStart + + Total time: %1 min %2 s + 总耗时:%1 分 %2 秒 + Auto Fitting Progress 自动拟合进程 diff --git a/Include/nmNum/nmCalculation/nmCalculationAutoFitLM.h b/Include/nmNum/nmCalculation/nmCalculationAutoFitLM.h index e88920fd..d7ff03aa 100644 --- a/Include/nmNum/nmCalculation/nmCalculationAutoFitLM.h +++ b/Include/nmNum/nmCalculation/nmCalculationAutoFitLM.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "nmCalculation_global.h" @@ -112,6 +113,7 @@ public: bool isRunning() const; int getCurrentIteration() const; int getTotalEvaluations() const; + int getSolverCallCount() const; void resetOptimizer(); void setTargetWellName(const QString& wellName); @@ -126,6 +128,10 @@ signals: void finalizingStarted(); void logMessageGenerated(const QString& message); +private slots: + // 由求解线程直接通知,仅累加原子计数,不操作界面。 + void recordSolverCall(); + private: bool initializeTemporaryDirectory(); void cleanupTemporaryDirectory(); @@ -172,6 +178,8 @@ private: const AutoFitObjectiveBreakdownLM* objectiveBreakdown = nullptr); QVector buildTraceParameterVector(const QVector& selectedParameters) const; void emitRunSummary(bool success, StopReasonLM finalReason); + void emitTrialLog(const QString& title, const AutoFitObjectiveBreakdownLM& before, + const AutoFitObjectiveBreakdownLM& after, bool valid, bool accepted, bool shapeStage); bool validateParameters(const QVector& parameters) const; bool validateLogLogData(const QVector >& logLogData) const; @@ -212,6 +220,7 @@ private: int m_maxIterations; double m_targetError; int m_totalEvaluations; + QAtomicInt m_solverCallCount; int m_successfulEvaluations; volatile int m_evaluationInProgress; diff --git a/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h b/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h index 309d7e9b..38bae72d 100644 --- a/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h +++ b/Include/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.h @@ -157,6 +157,8 @@ class NMCALCULATION_EXPORT nmCalculationDllPebiSolverTask : public QThread { //void slotTaskUpdateProgress(); signals: + /** @brief 即将实际调用数值求解器,供 LM 统计调用次数。 */ + void solverInvoked(); void sig_calculateDone(bool isSuccessed); /** @brief 报告当前真实阶段;nTotal<=0 表示 DLL 等不可测阶段。 */ void sigStageChanged(QString sStage, int nCurrent, int nTotal); diff --git a/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp b/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp index 2ca16e59..58fadf72 100644 --- a/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp @@ -695,6 +695,7 @@ nmCalculationAutoFitLM::nmCalculationAutoFitLM(QObject* parent) , m_maxIterations(100) , m_targetError(0.001) , m_totalEvaluations(0) + , m_solverCallCount(0) , m_successfulEvaluations(0) , m_evaluationInProgress(0) , m_consecutiveFailures(0) @@ -791,15 +792,15 @@ void nmCalculationAutoFitLM::setTargetLogLogData(const QVector > void nmCalculationAutoFitLM::stopFitting() { if(m_isFinalizing) { - emit logMessageGenerated( + DEBUG_OUT( tr("The final full-field calculation is already running")); return; } // 用户点击停止时只设置请求标志,让 LM 主循环和求解器等待逻辑自然退出。 if(m_isRunning) { - emit logMessageGenerated(tr("=== User Stop Request Received ===")); - emit logMessageGenerated(tr("Gracefully stopping LM automatic fitting...")); + DEBUG_OUT(tr("=== User Stop Request Received ===")); + DEBUG_OUT(tr("Gracefully stopping LM automatic fitting...")); m_shouldStop = true; // 此槽由求解等待循环派发,必须立即返回,外层循环才能转发取消请求。 @@ -807,9 +808,9 @@ void nmCalculationAutoFitLM::stopFitting() emit logMessageGenerated(tr("Waiting for current solver evaluation to stop...")); } - emit logMessageGenerated(tr("LM automatic fitting stop request processed")); + DEBUG_OUT(tr("LM automatic fitting stop request processed")); } else { - emit logMessageGenerated(tr("Stop request received but optimization is not running")); + DEBUG_OUT(tr("Stop request received but optimization is not running")); } } @@ -827,10 +828,22 @@ int nmCalculationAutoFitLM::getCurrentIteration() const int nmCalculationAutoFitLM::getTotalEvaluations() const { - // 返回本轮拟合实际调用真实求解器的总次数,供完成日志展示。 + // 返回评价次数,用于迭代预算;真实求解器调用次数另行统计。 return m_totalEvaluations; } +int nmCalculationAutoFitLM::getSolverCallCount() const +{ + // 包含失败调用、重试和最终完整求解,不包含未进入求解器的评价。 + return m_solverCallCount; +} + +void nmCalculationAutoFitLM::recordSolverCall() +{ + // 信号来自求解线程,使用原子累加避免跨线程读写竞争。 + m_solverCallCount.fetchAndAddOrdered(1); +} + QVector nmCalculationAutoFitLM::getBestSolution() const { // 返回紧凑的“启用参数向量”,顺序与 m_enabledParamIndices 一致。 @@ -873,6 +886,7 @@ void nmCalculationAutoFitLM::resetOptimizer() m_comparisonTimeMax = 0.0; m_currentIteration = 0; m_totalEvaluations = 0; + m_solverCallCount = 0; m_successfulEvaluations = 0; m_lastError.clear(); m_initialValues.clear(); @@ -931,9 +945,9 @@ void nmCalculationAutoFitLM::initializeTraceFile() writeTraceHeader(); writeTraceMetaFile(); - emit logMessageGenerated(tr("LM fitting trace: %1").arg(m_traceFilePath)); + DEBUG_OUT(tr("LM fitting trace: %1").arg(m_traceFilePath)); if(!m_traceMetaFilePath.isEmpty()) { - emit logMessageGenerated( + DEBUG_OUT( tr("LM fitting trace metadata: %1").arg(m_traceMetaFilePath)); } } @@ -1172,26 +1186,48 @@ void nmCalculationAutoFitLM::writeTraceRow( m_traceFile.flush(); } +// 窗口统一显示普通小数;失败评价不展示惩罚值或非有限数。 +static QString lmLogError(double value) +{ + return isFiniteNumber(value) && value < 1.0e9 + ? QString::number(value, 'f', 6) : QString("--"); +} + void nmCalculationAutoFitLM::emitRunSummary(bool success, StopReasonLM finalReason) { - // 汇总仅描述 LM 迭代和真实求解器评价。 - emit logMessageGenerated(tr("=== LM Run Summary ===")); - emit logMessageGenerated(tr("Stop reason: %1").arg(getStopReasonDescription(finalReason))); - emit logMessageGenerated( - tr("Result: %1, final error=%2, iterations=%3, evaluations=%4 (successful=%5, failed=%6)") - .arg(success ? tr("SUCCESS") : tr("FAILED")) - .arg(m_globalBestFitness, 0, 'e', 4) - .arg(m_currentIteration + 1) - .arg(m_totalEvaluations) - .arg(m_successfulEvaluations) - .arg(m_totalEvaluations - m_successfulEvaluations)); - - if(!m_traceFilePath.isEmpty()) { - emit logMessageGenerated(tr("Artifacts: trace=%1").arg(m_traceFilePath)); - } - if(!m_traceMetaFilePath.isEmpty()) { - emit logMessageGenerated(tr("Artifacts: trace_meta=%1").arg(m_traceMetaFilePath)); + // 最终只汇总总误差,耗时由进程窗口在收到结束信号后追加。 + emit logMessageGenerated(success ? tr("[Fitting completed]") : tr("[Fitting ended]")); + emit logMessageGenerated(tr("Stop reason: %1").arg(success + ? getStopReasonDescription(finalReason) + : (m_lastError.isEmpty() ? getStopReasonDescription(finalReason) : m_lastError))); + emit logMessageGenerated(tr("Total error: %1 -> %2") + .arg(lmLogError(m_userInitialFitness)).arg(lmLogError(m_globalBestFitness))); + emit logMessageGenerated(tr("Solver calls: %1").arg(getSolverCallCount())); +} + +void nmCalculationAutoFitLM::emitTrialLog(const QString& title, + const AutoFitObjectiveBreakdownLM& before, const AutoFitObjectiveBreakdownLM& after, + bool valid, bool accepted, bool shapeStage) +{ + // 只显示当前阶段目标;改善率相对上次已接受的误差计算,变差时为负值。 + if(!valid) { + emit logMessageGenerated(tr("%1 | Solver failed\nNo valid error was returned; keep the last accepted result and reduce the step.").arg(title)); + return; } + const QString status = accepted ? tr("accepted") : tr("rejected"); + const double beforeError = shapeStage ? before.shapeLoss : before.total; + const double afterError = shapeStage ? after.shapeLoss : after.total; + const QString objective = (shapeStage ? tr("Shape error: %1 -> %2") + : tr("Total error: %1 -> %2")) + .arg(lmLogError(beforeError)).arg(lmLogError(afterError)); + // 起点为零时相对改善率无定义,避免除零。 + const QString improvement = beforeError > 0.0 + ? QString::number((beforeError - afterError) / beforeError * 100.0, 'f', 2) + : QString("--"); + QString message = tr("%1 | %2 | %3 | Improvement: %4%") + .arg(title).arg(status).arg(objective).arg(improvement); + if(!accepted) message += tr("\n Objective error did not decrease; keep the last accepted result and reduce the step."); + emit logMessageGenerated(message); } QVector nmCalculationAutoFitLM::buildTraceParameterVector(const QVector& selectedParameters) const @@ -1353,9 +1389,9 @@ bool nmCalculationAutoFitLM::startAutoFitting() return false; } - emit logMessageGenerated(tr("Algorithm: LM")); + DEBUG_OUT(tr("Algorithm: LM")); const int enabledParams = getEnabledParameterCount(); - emit logMessageGenerated(tr("Enabled parameters count: %1").arg(enabledParams)); + DEBUG_OUT(tr("Enabled parameters count: %1").arg(enabledParams)); if(enabledParams == 0) { m_lastError = tr("No parameters enabled for optimization"); @@ -1382,7 +1418,7 @@ bool nmCalculationAutoFitLM::startAutoFitting() return false; } - emit logMessageGenerated( + DEBUG_OUT( tr("Target data validation passed (%1 data points)") .arg(m_targetLogLogData[0].size())); @@ -1403,21 +1439,29 @@ bool nmCalculationAutoFitLM::startAutoFitting() m_consecutiveFailures = 0; m_initialValues = savedInitialValues; initializeTraceFile(); + // 开头只展示一次初始参数和总误差,不展示形状误差。 + emit logMessageGenerated(tr("========== LM fitting started ==========")); + emit logMessageGenerated(tr("Target well: %1").arg(m_targetWellName)); + emit logMessageGenerated(tr("Target error: %1").arg(lmLogError(m_targetError))); // 先用真实求解器评价用户当前模型,供最终精英保护使用。 if(!savedInitialValues.isEmpty()) { m_userInitialSolution = savedInitialValues; - emit logMessageGenerated(tr("=== Evaluating Initial Solution (Elite Protection) ===")); + DEBUG_OUT(tr("=== Evaluating Initial Solution (Elite Protection) ===")); - QString paramStr = tr("Initial parameters: "); + const char* names[] = { QT_TR_NOOP("Permeability"), QT_TR_NOOP("Skin"), + QT_TR_NOOP("Wellbore storage"), QT_TR_NOOP("Porosity"), "", + QT_TR_NOOP("Fracture conductivity"), QT_TR_NOOP("Fracture half length") }; + const char* units[] = { "mD", "", "m^3/MPa", "", "", "mD.m", "m" }; + emit logMessageGenerated(tr("[Initial parameters]")); for(int i = 0; i < m_userInitialSolution.size(); ++i) { - paramStr += QString("[%1]=%2 ") - .arg(i).arg(m_userInitialSolution[i], 0, 'f', 6); + const int index = m_enabledParamIndices[i]; + emit logMessageGenerated(QString("%1: %2 %3").arg(tr(names[index])) + .arg(m_userInitialSolution[i], 0, 'g', 10).arg(tr(units[index]))); } - emit logMessageGenerated(paramStr); try { - emit logMessageGenerated( + DEBUG_OUT( tr("Starting initial solution evaluation...")); QTime initialEvalTimer; initialEvalTimer.start(); @@ -1434,16 +1478,15 @@ bool nmCalculationAutoFitLM::startAutoFitting() m_globalBestLogLogData = m_userInitialLogLogData; m_userInitialObjectiveBreakdown = m_lastObjectiveBreakdown; m_globalBestObjectiveBreakdown = m_userInitialObjectiveBreakdown; - emit logMessageGenerated(tr("Initial solution evaluation successful")); - emit logMessageGenerated( - tr("Initial Error: %1").arg(m_userInitialFitness, 0, 'e', 4)); + DEBUG_OUT(tr("Initial solution evaluation successful")); + emit logMessageGenerated(tr("Initial total error: %1").arg(lmLogError(m_userInitialFitness))); emit bestCurveUpdated(m_targetLogLogData, m_globalBestLogLogData, 0, m_globalBestFitness); } else { m_hasValidUserSolution = false; - emit logMessageGenerated(tr("Initial solution evaluation failed")); + if(!m_shouldStop) emit logMessageGenerated(tr("Initial solution failed; trying the parameter-range midpoint.")); } writeTraceRow(-1, @@ -1458,7 +1501,7 @@ bool nmCalculationAutoFitLM::startAutoFitting() ? &m_userInitialObjectiveBreakdown : nullptr); } catch(...) { m_hasValidUserSolution = false; - emit logMessageGenerated(tr("Exception during initial solution evaluation")); + if(!m_shouldStop) emit logMessageGenerated(tr("Initial solution failed; trying the parameter-range midpoint.")); } m_initialValues = savedInitialValues; @@ -1491,6 +1534,7 @@ bool nmCalculationAutoFitLM::startAutoFitting() cleanupTemporaryDirectory(); m_isFinalizing = false; m_isRunning = false; + emitRunSummary(false, LM_OPTIMIZATION_FAILED); emit fittingFinished(false, m_lastError); return false; } catch(...) { @@ -1500,6 +1544,7 @@ bool nmCalculationAutoFitLM::startAutoFitting() cleanupTemporaryDirectory(); m_isFinalizing = false; m_isRunning = false; + emitRunSummary(false, LM_OPTIMIZATION_FAILED); emit fittingFinished(false, m_lastError); return false; } @@ -1513,7 +1558,7 @@ bool nmCalculationAutoFitLM::startAutoFitting() // Stop 只结束优化迭代;从这里开始必须用当前最优参数生成并发布正式快照。 m_isFinalizing = true; emit finalizingStarted(); - emit logMessageGenerated(tr("Applying optimized parameters to model...")); + DEBUG_OUT(tr("Applying optimized parameters to model...")); applyParametersToDataManager(m_globalBestPosition); // 裂缝参数会改变网格输入;标记失效后,最终求解任务会基于新快照重建网格。 @@ -1529,28 +1574,28 @@ bool nmCalculationAutoFitLM::startAutoFitting() dataManager->invalidatePebiGrid(); } - emit logMessageGenerated( + DEBUG_OUT( tr("Running final full-field calculation with optimized parameters...")); finalFullSolverExecuted = true; finalFullSolverSucceeded = runFinalFullSolver(); if(finalFullSolverSucceeded) { - emit logMessageGenerated( + DEBUG_OUT( tr("Final full-field calculation completed successfully")); } else { m_lastError = tr("Optimized parameters were found, but the final full-field calculation failed"); - emit logMessageGenerated( + DEBUG_OUT( tr("ERROR: Final full-field calculation failed")); } saveOptimizationResult(); - emit logMessageGenerated(tr("=== Optimization Results ===")); - emit logMessageGenerated( + DEBUG_OUT(tr("=== Optimization Results ===")); + DEBUG_OUT( tr("Final error: %1").arg(m_globalBestFitness, 0, 'e', 4)); - emit logMessageGenerated( + DEBUG_OUT( tr("Total iterations: %1").arg(m_currentIteration + 1)); - emit logMessageGenerated( + DEBUG_OUT( tr("Total evaluations: %1 (successful: %2)") .arg(m_totalEvaluations).arg(m_successfulEvaluations)); @@ -1559,24 +1604,24 @@ bool nmCalculationAutoFitLM::startAutoFitting() finalParams += QString("[%1]=%2 ") .arg(i).arg(m_globalBestPosition[i], 0, 'f', 6); } - emit logMessageGenerated(finalParams); + DEBUG_OUT(finalParams); if(finalFullSolverExecuted && finalFullSolverSucceeded) { - emit logMessageGenerated( + DEBUG_OUT( tr("Parameters and full-field results applied successfully to data manager")); } else if(!finalFullSolverExecuted) { - emit logMessageGenerated( + DEBUG_OUT( tr("Optimized parameters applied to data manager")); } } catch(const std::exception& e) { finalFullSolverSucceeded = false; m_lastError = tr("Failed to apply final parameters: %1").arg(e.what()); - emit logMessageGenerated( + DEBUG_OUT( tr("ERROR: Failed to apply final parameters: %1").arg(e.what())); } catch(...) { finalFullSolverSucceeded = false; m_lastError = tr("Failed to apply final parameters due to unknown error"); - emit logMessageGenerated( + DEBUG_OUT( tr("ERROR: Unknown error applying final parameters")); } } @@ -1591,45 +1636,45 @@ bool nmCalculationAutoFitLM::startAutoFitting() message = QString(tr("Target achieved. Best error: %1, Iterations: %2")) .arg(m_globalBestFitness, 0, 'e', 4) .arg(m_currentIteration + 1); - emit logMessageGenerated(tr("=== LM AUTOMATIC FITTING SUCCESSFUL ===")); + DEBUG_OUT(tr("=== LM AUTOMATIC FITTING SUCCESSFUL ===")); } else if(finalReason == LM_TRUE_CONVERGENCE) { success = true; message = QString( tr("Automatic fitting converged to a stable solution. Best error: %1, Iterations: %2")) .arg(m_globalBestFitness, 0, 'e', 4) .arg(m_currentIteration + 1); - emit logMessageGenerated(tr("=== LM AUTOMATIC FITTING CONVERGED ===")); + DEBUG_OUT(tr("=== LM AUTOMATIC FITTING CONVERGED ===")); } else if(finalReason == LM_LOCAL_OPTIMUM) { success = true; message = QString( tr("Automatic fitting reached a local optimum. Best error: %1, Iterations: %2")) .arg(m_globalBestFitness, 0, 'e', 4) .arg(m_currentIteration + 1); - emit logMessageGenerated(tr("=== LM AUTOMATIC FITTING - LOCAL OPTIMUM ===")); + DEBUG_OUT(tr("=== LM AUTOMATIC FITTING - LOCAL OPTIMUM ===")); } else if(finalReason == LM_MAX_ITERATIONS) { success = true; message = QString(tr("Total-stage budget reached. Best error: %1, Cumulative iterations: %2")) .arg(m_globalBestFitness, 0, 'e', 4) .arg(m_currentIteration + 1); - emit logMessageGenerated(tr("=== LM AUTOMATIC FITTING - MAX ITERATIONS ===")); + DEBUG_OUT(tr("=== LM AUTOMATIC FITTING - MAX ITERATIONS ===")); } else if(finalReason == LM_USER_STOPPED) { success = true; message = QString(tr("Stopped by user. Best error: %1, Iterations: %2")) .arg(m_globalBestFitness, 0, 'e', 4) .arg(m_currentIteration + 1); - emit logMessageGenerated(tr("=== LM AUTOMATIC FITTING STOPPED BY USER ===")); + DEBUG_OUT(tr("=== LM AUTOMATIC FITTING STOPPED BY USER ===")); } else if(finalReason == LM_CONSECUTIVE_FAILURES) { message = QString( tr("Automatic fitting failed due to consecutive failures. Best error: %1, Iterations: %2")) .arg(m_globalBestFitness, 0, 'e', 4) .arg(m_currentIteration + 1); - emit logMessageGenerated(tr("=== LM AUTOMATIC FITTING FAILED ===")); + DEBUG_OUT(tr("=== LM AUTOMATIC FITTING FAILED ===")); } else { message = QString( tr("Automatic fitting ended unexpectedly. Best error: %1, Iterations: %2")) .arg(m_globalBestFitness, 0, 'e', 4) .arg(m_currentIteration + 1); - emit logMessageGenerated(tr("=== LM AUTOMATIC FITTING - UNKNOWN END ===")); + DEBUG_OUT(tr("=== LM AUTOMATIC FITTING - UNKNOWN END ===")); } if(!finalFullSolverSucceeded) { @@ -1918,8 +1963,8 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() restoreEvaluationState(current); emit progressUpdated(-1, current.fitness); - emit logMessageGenerated(tr("=== Starting LM Main Loop ===")); - emit logMessageGenerated( + DEBUG_OUT(tr("=== Starting LM Main Loop ===")); + DEBUG_OUT( tr("LM starting point error: %1; evaluation budget per LM phase: %2") .arg(current.fitness, 0, 'e', 4) .arg(phaseEvaluationBudget)); @@ -1931,8 +1976,9 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() int heightStagnation = 0; double heightScale = 1.0; double heightSlope = -1.0; + int heightTrials = 0; if(permeabilityColumn >= 0) { - emit logMessageGenerated(tr("LM stage 1: align curve height using permeability only; no fixed evaluation limit, effective improvement threshold 10%.")); + emit logMessageGenerated(tr("[Stage 1: Height alignment]\nAdjust permeability only; accept by height error.")); // 不限制试算次数,由高度对齐、改善停滞及可行步长决定何时结束。 while(processPauseAndStop()) { const double bias = current.breakdown.verticalCommonBias; @@ -1941,6 +1987,8 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() if(k <= 0.0 || m_parameterLower[0] <= 0.0 || m_parameterUpper[0] <= m_parameterLower[0]) break; const double logRange = qLn(m_parameterUpper[0]) - qLn(m_parameterLower[0]); const double change = qBound(-0.30 * logRange, -bias / heightSlope * heightScale, 0.30 * logRange); + const AutoFitObjectiveBreakdownLM previous = current.breakdown; + ++heightTrials; TrustRegionEvaluation candidate; candidate.parameters = current.parameters; candidate.parameters[permeabilityColumn] = qBound(m_parameterLower[0], k * qExp(change), m_parameterUpper[0]); @@ -1970,10 +2018,16 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() heightBaseline = current.breakdown.verticalLoss; heightStagnation = 0; } else ++heightStagnation; - emit logMessageGenerated(tr("Permeability alignment: k=%1, height error=%2, shape error=%3, result=%4") - .arg(candidate.parameters[permeabilityColumn], 0, 'g', 6) - .arg(candidate.breakdown.verticalLoss, 0, 'e', 4).arg(candidate.breakdown.shapeLoss, 0, 'e', 4) - .arg(accepted ? tr("accepted") : tr("rejected"))); + if(candidate.valid) { + emit logMessageGenerated(tr("Trial %1 | %2 | Height error: %3 -> %4") + .arg(heightTrials).arg(accepted ? tr("accepted") : tr("rejected")) + .arg(lmLogError(previous.verticalLoss)).arg(lmLogError(candidate.breakdown.verticalLoss))); + if(!accepted) emit logMessageGenerated(candidate.breakdown.verticalReliable + ? tr("Height error did not decrease; keep the last accepted result and reduce the step.") + : tr("Pressure and derivative height directions conflict; keep the last accepted result and reduce the step.")); + } else { + emit logMessageGenerated(tr("Trial %1 | Solver failed; keep the last accepted result and reduce the step.").arg(heightTrials)); + } } QString heightReason = tr("no feasible permeability step or step too small"); if(m_shouldStop) heightReason = tr("stopped by user"); @@ -1982,9 +2036,10 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() else if(heightStagnation >= 2) heightReason = tr("2 consecutive steps without effective height improvement"); else if(current.parameters[permeabilityColumn] <= m_parameterLower[0] || current.parameters[permeabilityColumn] >= m_parameterUpper[0]) heightReason = tr("permeability reached its bound"); - emit logMessageGenerated(tr("Permeability alignment ended: %1").arg(heightReason)); + emit logMessageGenerated(tr("Height alignment ended: %1").arg(heightReason)); } + if(permeabilityColumn < 0) emit logMessageGenerated(tr("Height alignment skipped: permeability is not selected.")); if(m_shouldStop) return LM_USER_STOPPED; // 原逐参数第二阶段已移除;同一个联合 LM 先匹配整体形状,再匹配总误差。 @@ -2002,7 +2057,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() restoreEvaluationState(current); }; double effectiveImprovementBaseline = stageError(current); - emit logMessageGenerated(tr("LM sampling: %1 points (%2 intervals per log-time decade).") + DEBUG_OUT(tr("LM sampling: %1 points (%2 intervals per log-time decade).") .arg(current.breakdown.residualVector.size() / 2).arg(kAutoFitIntervalsPerDecade)); auto registerEffectiveImprovement = [&](double fitness) -> bool { @@ -2048,7 +2103,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() return false; }; - emit logMessageGenerated( + DEBUG_OUT( tr("LM effective improvement threshold: max(%1, %2% of baseline error); " "%3 consecutive ineffective steps trigger convergence confirmation") .arg(effectiveAbsoluteImprovement, 0, 'e', 2) @@ -2212,9 +2267,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() 0, shapeStage ? "shape_accepted_cached_probe" : "total_accepted_cached_probe", ¤t.breakdown); - emit logMessageGenerated( - tr("Sensitivity probe accepted: current objective error=%1") - .arg(stageError(current), 0, 'e', 4)); + emitTrialLog(tr("Sensitivity trial"), base.breakdown, current.breakdown, true, true, shapeStage); } else { restoreEvaluationState(current); } @@ -2258,9 +2311,9 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() : QT_TR_NOOP("full sensitivity at total-stage entry"); emit progressUpdated(0, current.fitness); emit logMessageGenerated(shapeStage - ? tr("Joint LM shape phase: adjust all selected parameters by full shape error.") - : tr("Joint LM total phase: adjust all selected parameters by total error.")); - emit logMessageGenerated(tr("LM phase budget: %1 iterations, %2 evaluations.") + ? tr("[Stage 2: Joint LM - Shape]\nAdjust selected parameters; accept by shape error.") + : tr("[Stage 2: Joint LM - Total error]\nAdjust selected parameters; accept by total error.")); + DEBUG_OUT(tr("LM phase budget: %1 iterations, %2 evaluations.") .arg(m_maxIterations).arg(phaseEvaluationBudget)); writeTraceRow(completedIterations, -1, "stage_switch", current.parameters, current.fitness, true, 0, shapeStage ? "height_to_shape_lm" : "shape_to_total", ¤t.breakdown); @@ -2276,7 +2329,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() break; } if(rebuildRequested) { - emit logMessageGenerated(tr("Rebuilding sensitivity model: %1").arg(tr(rebuildReason))); + emit logMessageGenerated(tr("Calculating sensitivity...")); writeTraceRow(m_currentIteration, -1, "sensitivity_rebuild", current.parameters, current.fitness, true, 0, rebuildReason, ¤t.breakdown); if(!rebuildSensitivity()) { @@ -2333,6 +2386,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() // 当前阶段没有可行下降步时,收缩半径并进入原有重建/收敛处理。 if(selectedColumns.isEmpty()) { + emit logMessageGenerated(tr("Iteration %1 | No feasible descent step; keep the last accepted result.").arg(phaseIterations)); if(trustRadius <= minimumTrustRadius * 1.01 && modelRebuiltAtMinimumRadius) { stopReason = LM_LOCAL_OPTIMUM; @@ -2379,6 +2433,8 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() } if(!candidate.valid) { + emitTrialLog(tr("Iteration %1").arg(phaseIterations), current.breakdown, candidate.breakdown, + false, false, shapeStage); // 求解失败的候选不能改变 current。先完整恢复上一个已接受参数和 // 对应误差快照,再缩小信赖域;连续失败达到上限才终止整个拟合。 ++consecutiveSolverFailures; @@ -2493,14 +2549,8 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() : "rejected_" + selectionName, &candidate.breakdown); - QString componentDisplayName = shapeStage ? tr("shape deviation") : tr("total error"); - emit logMessageGenerated( - tr("Iteration %1: focus=%2, parameters=%3, error=%4, result=%5") - .arg(iteration + 1) - .arg(componentDisplayName) - .arg(selectedColumns.size()) - .arg(stageError(candidate), 0, 'e', 4) - .arg(accepted ? tr("accepted") : tr("rejected"))); + emitTrialLog(tr("Iteration %1").arg(phaseIterations), oldBreakdown, candidate.breakdown, + true, accepted, shapeStage); emit progressUpdated(phaseIterations, m_globalBestFitness); const bool effectiveImprovement = registerEffectiveImprovement(stageError(current)); @@ -2528,9 +2578,11 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() break; // 形状达标、确认停滞或用完额度后,保留当前参数进入总误差段。 if(shapeStage) { - emit logMessageGenerated(tr("Shape LM phase ended: %1").arg(getStopReasonDescription(stopReason))); + emit logMessageGenerated(tr("Shape phase ended: %1").arg(getStopReasonDescription(stopReason))); writeTraceRow(m_currentIteration, -1, "lm_phase_end", current.parameters, current.fitness, true, 0, QString("shape_stop_reason_%1").arg(static_cast(stopReason)), ¤t.breakdown); + } else { + emit logMessageGenerated(tr("Total-error phase ended: %1").arg(getStopReasonDescription(stopReason))); } } @@ -2538,7 +2590,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() m_currentIteration = completedIterations - 1; } restoreEvaluationState(current); - emit logMessageGenerated(tr("Adaptive fitting counts: %1 shape LM iterations, %2 total LM iterations, %3 total evaluations.") + DEBUG_OUT(tr("Adaptive fitting counts: %1 shape LM iterations, %2 total LM iterations, %3 total evaluations.") .arg(shapeIterations).arg(totalIterations).arg(m_totalEvaluations)); if(m_shouldStop) { @@ -3001,17 +3053,17 @@ void nmCalculationAutoFitLM::validateAndProtectFinalResult() // 最终精英保护只阻止无效结果或真正变差的结果。任何真实误差下降都应保留, // 不能再用固定百分比门槛把已经找到的更优解恢复成初始值。 if(!m_hasValidUserSolution) { - emit logMessageGenerated(tr("No initial solution for elite protection")); + DEBUG_OUT(tr("No initial solution for elite protection")); return; } - emit logMessageGenerated(tr("=== Final Result Validation (Elite Protection) ===")); + DEBUG_OUT(tr("=== Final Result Validation (Elite Protection) ===")); // 使用已有的评估结果 double finalFitness = m_globalBestFitness; double initialFitness = m_userInitialFitness; - emit logMessageGenerated(tr("Comparing results: Initial=%1, Final=%2") + DEBUG_OUT(tr("Comparing results: Initial=%1, Final=%2") .arg(initialFitness, 0, 'e', 4).arg(finalFitness, 0, 'e', 4)); bool finalValid = isFiniteNumber(finalFitness) && @@ -3024,15 +3076,15 @@ void nmCalculationAutoFitLM::validateAndProtectFinalResult() double improvement = initialFitness - finalFitness; double relativeImprovement = improvement / qMax(1.0e-10, qAbs(initialFitness)); - emit logMessageGenerated(tr("Improvement: %1 (%2%)") + DEBUG_OUT(tr("Improvement: %1 (%2%)") .arg(improvement, 0, 'e', 4) .arg(relativeImprovement * 100, 0, 'f', 2)); } if(!finalValid || finalFitness > initialFitness) { - emit logMessageGenerated( + DEBUG_OUT( tr("Elite protection triggered: final result is invalid or worse than initial")); - emit logMessageGenerated(tr("Restoring initial solution as final result")); + emit logMessageGenerated(tr("Final total error did not improve; restoring the initial result.")); m_globalBestFitness = initialFitness; m_globalBestPosition = m_userInitialSolution; @@ -3040,9 +3092,9 @@ void nmCalculationAutoFitLM::validateAndProtectFinalResult() m_globalBestObjectiveBreakdown = m_userInitialObjectiveBreakdown; emit bestCurveUpdated(m_targetLogLogData, m_globalBestLogLogData, m_currentIteration + 1, m_globalBestFitness); - emit logMessageGenerated(tr("Initial solution restored successfully")); + DEBUG_OUT(tr("Initial solution restored successfully")); } else { - emit logMessageGenerated( + DEBUG_OUT( tr("Final result validated - solution is not worse than initial")); } } @@ -4129,6 +4181,7 @@ QVector> nmCalculationAutoFitLM::runSolverDll() DEBUG_OUT("Starting DLL solver execution..."); // 异步执行 DLL 任务。循环等待期间持续 processEvents,保证界面不会完全卡死。 + connect(dllTask, SIGNAL(solverInvoked()), this, SLOT(recordSolverCall()), Qt::DirectConnection); dllTask->start(); // 等待完成。最大等待 1 小时;用户停止使用任务已有的协作取消接口。 @@ -4296,6 +4349,7 @@ bool nmCalculationAutoFitLM::runFinalFullSolver() ++m_evaluationInProgress; nmCalculationDllPebiSolverTask dllTask(m_tempDirectory, dataManager); + connect(&dllTask, SIGNAL(solverInvoked()), this, SLOT(recordSolverCall()), Qt::DirectConnection); dllTask.start(); const int maxWait = 3600000; @@ -4347,7 +4401,7 @@ QString nmCalculationAutoFitLM::getStopReasonDescription(StopReasonLM reason) co return tr("Local optimum detected"); case LM_MAX_ITERATIONS: - return tr("Total-stage iteration or evaluation budget reached"); + return tr("Iteration or evaluation budget reached"); case LM_USER_STOPPED: return tr("Stopped by user request"); diff --git a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp index 3220d85b..5feeafb7 100644 --- a/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationDllPebiSolverTask.cpp @@ -1382,6 +1382,8 @@ bool nmCalculationDllPebiSolverTask::execPebiMode() } reportStage(tr("Solving model..."), 0, 0); + // 只统计进入数值求解器的调用,网格准备失败和缓存复用不计入。 + emit solverInvoked(); pfnModel(oModelOutput, oModelInput, m_pInputSnapshot->m_oGridInput.m_sLicensePath diff --git a/Src/nmNum/nmSubWxs/nmWxAutomaticFittingStart.cpp b/Src/nmNum/nmSubWxs/nmWxAutomaticFittingStart.cpp index 9de73cd7..de0d367c 100644 --- a/Src/nmNum/nmSubWxs/nmWxAutomaticFittingStart.cpp +++ b/Src/nmNum/nmSubWxs/nmWxAutomaticFittingStart.cpp @@ -612,6 +612,8 @@ void nmWxAutomaticfittingStart::setAutoFitter(nmCalculationAutoFitLM* autoFitter m_autoFitterPSO = nullptr; m_autoFitterLM = autoFitter; m_algorithmName = "LM"; + // LM 日志由优化器统一组织,清除通用初始化提示。 + logTextEdit->clear(); algorithmTypeValue->setText(m_algorithmName); algorithmTypeValue->setStyleSheet("QLabel { color: blue; font-weight: bold; }"); @@ -629,7 +631,6 @@ void nmWxAutomaticfittingStart::setAutoFitter(nmCalculationAutoFitLM* autoFitter this, SLOT(onBestCurveUpdated(QVector >,QVector >,int,double))); stopButton->setEnabled(true); - addLogMessage(tr("%1 auto fitting started").arg(m_algorithmName)); } } @@ -646,7 +647,7 @@ void nmWxAutomaticfittingStart::setFittingParameters(int maxIterations, double t progressBar->setRange(0, maxIterations); const QString algorithmName = m_algorithmName; - addLogMessage(tr("%1 fitting parameters set: MaxIterations=%2, TargetAccuracy=%3, TargetWell=%4") + if(!m_autoFitterLM) addLogMessage(tr("%1 fitting parameters set: MaxIterations=%2, TargetAccuracy=%3, TargetWell=%4") .arg(algorithmName).arg(maxIterations).arg(formatScientific(targetError)).arg(wellName)); } @@ -657,7 +658,7 @@ void nmWxAutomaticfittingStart::markFittingStarted() const QString algorithmName = m_algorithmName; QString timestamp = m_startTime.toString("yyyy-MM-dd hh:mm:ss"); - addLogMessage(tr("=== %1 Fitting Session Started at %2 ===") + if(!m_autoFitterLM) addLogMessage(tr("=== %1 Fitting Session Started at %2 ===") .arg(algorithmName) .arg(timestamp)); } @@ -670,7 +671,7 @@ void nmWxAutomaticfittingStart::setSelectedParameters(const QStringList& paramet updateParameterTable(); const QString algorithmName = m_algorithmName; - addLogMessage(tr("%1 selected parameters: %2").arg(algorithmName).arg(parameterNames.join(", "))); + if(!m_autoFitterLM) addLogMessage(tr("%1 selected parameters: %2").arg(algorithmName).arg(parameterNames.join(", "))); } void nmWxAutomaticfittingStart::setTargetLogLogData(const QVector >& targetData) @@ -710,7 +711,7 @@ void nmWxAutomaticfittingStart::onFittingProgress(int iteration, double fitness) bestComfortValue->setText(formatScientific(fitness)); bestComfortValue->setStyleSheet("QLabel { color: green; font-weight: bold; }"); - addLogMessage(tr("Better solution found: %1").arg(formatScientific(fitness))); + if(!m_autoFitterLM) addLogMessage(tr("Better solution found: %1").arg(formatScientific(fitness))); // 更新参数表格 updateParameterTable(); @@ -732,7 +733,7 @@ void nmWxAutomaticfittingStart::onFittingFinished(bool success, const QString& m // 区分正常完成和用户停止 if(message.contains("stopped by user", Qt::CaseInsensitive)) { - addLogMessage(tr("%1 fitting stopped by user: %2").arg(algorithmName).arg(message)); + if(!m_autoFitterLM) addLogMessage(tr("%1 fitting stopped by user: %2").arg(algorithmName).arg(message)); // 更新进度条显示停止状态 progressBar->setFormat(tr("Stopped: 100%")); @@ -741,7 +742,7 @@ void nmWxAutomaticfittingStart::onFittingFinished(bool success, const QString& m "QProgressBar::chunk { background-color: #FF9800; }" // 橙色表示停止 ); } else if(message.contains("target achieved", Qt::CaseInsensitive)) { - addLogMessage(tr("%1 fitting completed successfully - target achieved: %2").arg(algorithmName).arg(message)); + if(!m_autoFitterLM) addLogMessage(tr("%1 fitting completed successfully - target achieved: %2").arg(algorithmName).arg(message)); // 更新进度条显示成功状态 progressBar->setFormat(tr("Target Achieved: 100%")); @@ -750,7 +751,7 @@ void nmWxAutomaticfittingStart::onFittingFinished(bool success, const QString& m "QProgressBar::chunk { background-color: #4CAF50; }" // 绿色表示成功 ); } else { - addLogMessage(tr("%1 fitting completed successfully: %2").arg(algorithmName).arg(message)); + if(!m_autoFitterLM) addLogMessage(tr("%1 fitting completed successfully: %2").arg(algorithmName).arg(message)); // 更新进度条显示完成状态 progressBar->setFormat(tr("Completed: 100%")); @@ -761,7 +762,7 @@ void nmWxAutomaticfittingStart::onFittingFinished(bool success, const QString& m } } else { // 真正的失败情况 - addLogMessage(tr("%1 fitting failed: %2").arg(algorithmName).arg(message)); + if(!m_autoFitterLM) addLogMessage(tr("%1 fitting failed: %2").arg(algorithmName).arg(message)); // 更新进度条显示失败状态 int currentProgress = progressBar->value(); @@ -786,7 +787,7 @@ void nmWxAutomaticfittingStart::onFittingFinished(bool success, const QString& m // 添加完成时间戳 QDateTime endTime = QDateTime::currentDateTime(); QString timestamp = endTime.toString("yyyy-MM-dd hh:mm:ss"); - addLogMessage(tr("=== %1 Fitting Session Ended at %2 ===") + if(!m_autoFitterLM) addLogMessage(tr("=== %1 Fitting Session Ended at %2 ===") .arg(algorithmName).arg(timestamp)); // 如果有开始时间,则计算总耗时 @@ -800,12 +801,13 @@ void nmWxAutomaticfittingStart::onFittingFinished(bool success, const QString& m ? m_autoFitterLM->getTotalEvaluations() : (m_autoFitterPSO ? m_autoFitterPSO->getTotalEvaluations() : 0); - // 同时显示总秒数、分秒和本轮真实求解器调用次数。 - addLogMessage(tr("Total fitting time: %1 s (%2 min %3 s), total solver calls: %4") - .arg(seconds, 0, 'f', 2) - .arg(minutes) - .arg(remainSec, 0, 'f', 2) - .arg(totalEvaluations)); + // LM 的调用次数由优化器汇总,这里只在结束时显示一次总耗时。 + if(m_autoFitterLM) { + addLogMessage(tr("Total time: %1 min %2 s").arg(minutes).arg(remainSec, 0, 'f', 1)); + } else { + addLogMessage(tr("Total fitting time: %1 s (%2 min %3 s), total solver calls: %4") + .arg(seconds, 0, 'f', 2).arg(minutes).arg(remainSec, 0, 'f', 2).arg(totalEvaluations)); + } } } @@ -840,7 +842,7 @@ void nmWxAutomaticfittingStart::onStopButtonClicked() } else if (m_autoFitterLM) { m_autoFitterLM->stopFitting(); } - addLogMessage(tr("User requested to stop %1 fitting").arg(algorithmName)); + if(!m_autoFitterLM) addLogMessage(tr("User requested to stop %1 fitting").arg(algorithmName)); } } }