refactor(nmNum): 优化 LM 拟合日志并展示误差改善率

feature/AutoFit-Optimize-20260914
lvjunjie 3 weeks ago
parent ed9cc46ac5
commit 1237b44a4c

Binary file not shown.

@ -664,6 +664,172 @@ Reason: %1</source>
</context>
<context>
<name>nmCalculationAutoFitLM</name>
<message>
<source>[Fitting completed]</source>
<translation>【拟合完成】</translation>
</message>
<message>
<source>[Fitting ended]</source>
<translation>【拟合结束】</translation>
</message>
<message>
<source>[Initial parameters]</source>
<translation>【初始参数】</translation>
</message>
<message>
<source>[Stage 1: Height alignment]
Adjust permeability only; accept by height error.</source>
<translation>【第一阶段:高度预调】
只调整渗透率,按高度误差判断是否接受。</translation>
</message>
<message>
<source>[Stage 2: Joint LM - Shape]
Adjust selected parameters; accept by shape error.</source>
<translation>【第二阶段:联合 LM · 形状】
调整勾选参数,按形状误差判断是否接受。</translation>
</message>
<message>
<source>[Stage 2: Joint LM - Total error]
Adjust selected parameters; accept by total error.</source>
<translation>【第二阶段:联合 LM · 总误差】
调整勾选参数,按总误差判断是否接受。</translation>
</message>
<message>
<source>%1 | %2 | %3 | Improvement: %4%</source>
<translation>%1|%2|%3|改善:%4%</translation>
</message>
<message>
<source>%1 | Solver failed
No valid error was returned; keep the last accepted result and reduce the step.</source>
<translation>%1|求解失败
未返回有效误差,保留上次已接受结果并缩小步长。</translation>
</message>
<message>
<source>
Objective error did not decrease; keep the last accepted result and reduce the step.</source>
<translation>
目标误差未减小,保留上次已接受结果并缩小步长。</translation>
</message>
<message>
<source>========== LM fitting started ==========</source>
<translation>========== LM 拟合开始 ==========</translation>
</message>
<message>
<source>Target well: %1</source>
<translation>目标井:%1</translation>
</message>
<message>
<source>Target error: %1</source>
<translation>目标误差:%1</translation>
</message>
<message>
<source>Initial total error: %1</source>
<translation>初始总误差:%1</translation>
</message>
<message>
<source>Permeability</source>
<translation>渗透率</translation>
</message>
<message>
<source>Skin</source>
<translation>表皮系数</translation>
</message>
<message>
<source>Wellbore storage</source>
<translation>井筒储集系数</translation>
</message>
<message>
<source>Porosity</source>
<translation>孔隙度</translation>
</message>
<message>
<source>Fracture conductivity</source>
<translation>裂缝导流能力</translation>
</message>
<message>
<source>Fracture half length</source>
<translation>裂缝半长</translation>
</message>
<message>
<source>m^3/MPa</source>
<translation>m³/MPa</translation>
</message>
<message>
<source>mD.m</source>
<translation>mD·m</translation>
</message>
<message>
<source>Trial %1 | %2 | Height error: %3 -&gt; %4</source>
<translation>试算 %1|%2|高度误差:%3 → %4</translation>
</message>
<message>
<source>Trial %1 | Solver failed; keep the last accepted result and reduce the step.</source>
<translation>试算 %1|求解失败,保留上次已接受结果并缩小步长。</translation>
</message>
<message>
<source>Height error did not decrease; keep the last accepted result and reduce the step.</source>
<translation>高度误差未减小,保留上次已接受结果并缩小步长。</translation>
</message>
<message>
<source>Pressure and derivative height directions conflict; keep the last accepted result and reduce the step.</source>
<translation>压力与导数的高度偏移方向冲突,保留上次已接受结果并缩小步长。</translation>
</message>
<message>
<source>Height alignment ended: %1</source>
<translation>高度预调结束:%1</translation>
</message>
<message>
<source>Height alignment skipped: permeability is not selected.</source>
<translation>跳过高度预调:未勾选渗透率。</translation>
</message>
<message>
<source>Calculating sensitivity...</source>
<translation>正在计算灵敏度……</translation>
</message>
<message>
<source>Sensitivity trial</source>
<translation>灵敏度试算</translation>
</message>
<message>
<source>Iteration %1</source>
<translation>迭代 %1</translation>
</message>
<message>
<source>Shape error: %1 -&gt; %2</source>
<translation>形状误差:%1 → %2</translation>
</message>
<message>
<source>Total error: %1 -&gt; %2</source>
<translation>总误差:%1 → %2</translation>
</message>
<message>
<source>Shape phase ended: %1</source>
<translation>形状部分结束:%1</translation>
</message>
<message>
<source>Total-error phase ended: %1</source>
<translation>总误差部分结束:%1</translation>
</message>
<message>
<source>Iteration %1 | No feasible descent step; keep the last accepted result.</source>
<translation>迭代 %1|没有可行的下降步,保留上次已接受结果。</translation>
</message>
<message>
<source>Iteration or evaluation budget reached</source>
<translation>达到本阶段迭代次数或试算次数上限</translation>
</message>
<message>
<source>Final total error did not improve; restoring the initial result.</source>
<translation>最终结果无效或总误差变大,恢复初始结果。</translation>
</message>
<message>
<source>Initial solution failed; trying the parameter-range midpoint.</source>
<translation>初始求解失败,将尝试参数范围中点。</translation>
</message>
<message>
<source>Solver calls: %1</source>
<translation>共调用求解器:%1 次</translation>
</message>
<message>
<source>Failed to refresh the sampling layer from the current curve.</source>
<translation>无法根据当前曲线刷新采样层级。</translation>
@ -4395,6 +4561,10 @@ Supported types: Vertical, Vertical Fractured, and Horizontal Multi-Fractured We
</context>
<context>
<name>nmWxAutomaticfittingStart</name>
<message>
<source>Total time: %1 min %2 s</source>
<translation>总耗时:%1 分 %2 秒</translation>
</message>
<message>
<source>Auto Fitting Progress</source>
<translation>自动拟合进程</translation>

@ -7,6 +7,7 @@
#include <QString>
#include <QStringList>
#include <QFile>
#include <QAtomicInt>
#include <limits>
#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<double> buildTraceParameterVector(const QVector<double>& 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<double>& parameters) const;
bool validateLogLogData(const QVector<QVector<double> >& 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;

@ -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);

@ -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<QVector<double> >
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<double> 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<double> nmCalculationAutoFitLM::buildTraceParameterVector(const QVector<double>& 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",
&current.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", &current.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, &current.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<int>(stopReason)), &current.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<QVector<double>> 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");

@ -1382,6 +1382,8 @@ bool nmCalculationDllPebiSolverTask::execPebiMode()
}
reportStage(tr("Solving model..."), 0, 0);
// 只统计进入数值求解器的调用,网格准备失败和缓存复用不计入。
emit solverInvoked();
pfnModel(oModelOutput,
oModelInput,
m_pInputSnapshot->m_oGridInput.m_sLicensePath

@ -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<double> >,QVector<QVector<double> >,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<QVector<double> >& 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));
}
}
}

Loading…
Cancel
Save