diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm index 4c6dfbfc..c234a7d0 100644 Binary files a/Bin/Config/Lang/cn/nmNum_cn.qm and b/Bin/Config/Lang/cn/nmNum_cn.qm differ 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)); } } }