perf(nmNum): 优化 LM 灵敏度重建时机与采样加密衔接

- 取消累计步长触发重建,连续两步实际改善不足预测的 25% 时刷新模型
- 将兜底刷新间隔调整为接受 10 步,避免微小预测量和单纯约束拒绝触发重建
- 合并临近的采样加密与灵敏度重建,提前考虑剩余求解预算
- 阶段切换时清除旧目标的预测失准计数,保留必要的失败恢复与停滞确认
- 补充重建原因日志、跟踪记录及中文翻译
feature/AutoFit-Optimize-20260914
lvjunjie 2 weeks ago
parent 57c3d613f6
commit cd8bd9bafe

Binary file not shown.

@ -1184,6 +1184,54 @@ Reason: %1</source>
<source>permeability reached its bound</source>
<translation>渗透率已到达范围边界</translation>
</message>
<message>
<source>Rebuilding sensitivity model: %1</source>
<translation>正在重建灵敏度模型:%1</translation>
</message>
<message>
<source>initial sensitivity model</source>
<translation>首次建立灵敏度模型</translation>
</message>
<message>
<source>sampling layer changed</source>
<translation>采样层级已改变</translation>
</message>
<message>
<source>no valid sensitivity model</source>
<translation>没有有效的灵敏度模型</translation>
</message>
<message>
<source>solver failure before stage switch</source>
<translation>阶段切换前发生求解失败</translation>
</message>
<message>
<source>10 accepted steps since last rebuild</source>
<translation>距上次重建已接受 10 步调整,进行兜底刷新</translation>
</message>
<message>
<source>confirm stagnation after ineffective steps</source>
<translation>连续无有效改善,重新确认停滞</translation>
</message>
<message>
<source>sampling refinement merged with pending rebuild</source>
<translation>提前加密采样,与本次重建合并处理</translation>
</message>
<message>
<source>no feasible descent step</source>
<translation>未找到可行的下降方向</translation>
</message>
<message>
<source>2 consecutive solver failures</source>
<translation>连续 2 次候选求解失败</translation>
</message>
<message>
<source>2 consecutive steps with actual improvement below 25% of prediction</source>
<translation>连续 2 步实际改善不足预测的 25%</translation>
</message>
<message>
<source>inaccurate model at minimum trust radius</source>
<translation>最小信赖半径下仍连续预测失准</translation>
</message>
</context>
<context>
<name>nmCalculationSolver</name>

@ -1041,7 +1041,7 @@ void nmCalculationAutoFitLM::writeTraceMetaFile()
QTextStream out(&metaFile);
out << "{\n";
out << " \"schema_version\": 9,\n";
out << " \"schema_version\": 10,\n";
out << " \"strategy\": \"permeability_height_then_shape_then_original_lm\",\n";
out << " \"shape_metric\": \"pressure_and_derivative_log_slopes_81_points_lag_8\",\n";
out << " \"shape_stage_total_tolerance\": \"max(0.02, 25% of stage entry total)\",\n";
@ -1819,15 +1819,17 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
const int maximumIneffectiveSteps = 3;
// damping 是 LM 阻尼;拒绝或预测失准时增大,真实下降与预测一致时减小。
// 两组累计量控制 Jacobian 重建,避免长期使用已偏离当前工作点的局部模型。
// 按连续预测失准刷新 Jacobian;接受步数只作兜底,不因累计移动距离强制重建。
double trustRadius = 0.12;
double damping = 1.0e-2;
int consecutiveRejectedSteps = 0;
int consecutiveSolverFailures = 0;
int acceptedSinceRebuild = 0;
int consecutiveIneffectiveSteps = 0;
double movementSinceRebuild = 0.0;
int consecutivePoorPredictions = 0;
bool rebuildRequested = true;
// 跟踪文件保留稳定英文原因,界面输出时再翻译,避免受本地编码影响。
const char* rebuildReason = QT_TR_NOOP("initial sensitivity model");
bool modelRebuiltAtMinimumRadius = false;
bool stagnationConfirmationRequested = false;
QVector<bool> attemptedWindows(kAutoFitTimeWindowCount, false);
@ -2061,12 +2063,13 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
jacobian.clear();
jacobianColumnValid.fill(false);
rebuildRequested = true;
rebuildReason = QT_TR_NOOP("sampling layer changed");
trustRadius = 0.12;
damping = 1.0e-2;
consecutiveRejectedSteps = 0;
consecutiveIneffectiveSteps = 0;
acceptedSinceRebuild = 0;
movementSinceRebuild = 0.0;
consecutivePoorPredictions = 0;
modelRebuiltAtMinimumRadius = false;
stagnationConfirmationRequested = false;
attemptedWindows.fill(false);
@ -2088,7 +2091,12 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
auto enterTotalStage = [&](const QString& reason) {
// 保留当前曲线和完整 J,只重置阶段停滞状态、阻尼及信赖半径。
rebuildRequested = jacobian.isEmpty() || consecutiveSolverFailures > 0;
rebuildRequested = jacobian.isEmpty() || consecutiveSolverFailures > 0 || acceptedSinceRebuild >= 10;
if(jacobian.isEmpty()) rebuildReason = QT_TR_NOOP("no valid sensitivity model");
else if(consecutiveSolverFailures > 0) rebuildReason = QT_TR_NOOP("solver failure before stage switch");
else if(acceptedSinceRebuild >= 10) rebuildReason = QT_TR_NOOP("10 accepted steps since last rebuild");
// 阶段目标已改变,不把形状目标下的预测失准累计到整体阶段。
consecutivePoorPredictions = 0;
modelRebuiltAtMinimumRadius = false;
shapeStage = false;
effectiveImprovementBaseline = current.fitness;
@ -2148,6 +2156,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
stagnationConfirmationRequested = true;
rebuildRequested = true;
rebuildReason = QT_TR_NOOP("confirm stagnation after ineffective steps");
emit logMessageGenerated(
tr("No effective improvement for %1 consecutive steps; "
"rebuilding sensitivity model for confirmation")
@ -2324,7 +2333,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
}
acceptedSinceRebuild = 0;
movementSinceRebuild = 0.0;
consecutivePoorPredictions = 0;
consecutiveRejectedSteps = 0;
rebuildRequested = false;
// 若重建过程中接受了试算点,当前模型已通过割线平移而不是在新点完整
@ -2356,18 +2365,34 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
enterTotalStage(tr("reserve remaining iterations and evaluations for total fitting"));
}
if(!shapeStage && m_layeredSampling && m_samplingStride > 1) {
const bool reserveFinalBudget = maximumEvaluations - m_totalEvaluations <= 2 * (dimensions + 1);
const int remainingEvaluations = maximumEvaluations - m_totalEvaluations;
// 已准备重建时,提前计入本轮差分及候选的开销,避免紧接着因预算再加密。
const bool reserveFinalBudget = remainingEvaluations <= 2 * (dimensions + 1) ||
(rebuildRequested && remainingEvaluations <= 3 * (dimensions + 1));
const int layerDeadline = qMax(1, m_maxIterations * (m_samplingStride == 4 ? 1 : 2) / 3);
if(reserveFinalBudget || iteration >= layerDeadline) {
promoteSampling(reserveFinalBudget);
const bool mergeRefinement = rebuildRequested && iteration < layerDeadline &&
(iteration + 1 >= layerDeadline || reserveFinalBudget);
if(reserveFinalBudget || iteration >= layerDeadline || mergeRefinement) {
const bool promoted = promoteSampling(reserveFinalBudget);
if(samplingRefreshFailed) break;
if(promoted && mergeRefinement) {
rebuildReason = QT_TR_NOOP("sampling refinement merged with pending rebuild");
}
}
}
if(rebuildRequested) {
emit logMessageGenerated(tr("Rebuilding sensitivity model: %1").arg(tr(rebuildReason)));
writeTraceRow(m_currentIteration, -1, "sensitivity_rebuild", current.parameters,
current.fitness, true, 0, rebuildReason, &current.breakdown);
const bool confirmingStagnation =
stagnationConfirmationRequested;
if(!rebuildSensitivity()) {
if(shapeStage && !m_shouldStop) { enterTotalStage(tr("no valid shape sensitivity model")); rebuildRequested = true; continue; }
if(shapeStage && !m_shouldStop) {
enterTotalStage(tr("no valid shape sensitivity model"));
rebuildRequested = true;
rebuildReason = QT_TR_NOOP("no valid sensitivity model");
continue;
}
if(promoteSampling(false)) continue;
stopReason = m_shouldStop
? LM_USER_STOPPED
@ -2507,6 +2532,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
trustRadius = qMax(minimumTrustRadius, trustRadius * 0.5);
damping = qMin(1.0e8, damping * 4.0);
rebuildRequested = true;
rebuildReason = QT_TR_NOOP("no feasible descent step");
if(recordIneffectiveStep()) {
if(promoteSampling(false)) continue;
stopReason = LM_LOCAL_OPTIMUM;
@ -2544,6 +2570,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
// 对应误差快照,再缩小信赖域;连续失败达到上限才终止整个拟合。
++consecutiveSolverFailures;
++consecutiveRejectedSteps;
consecutivePoorPredictions = 0;
trustRadius = qMax(minimumTrustRadius, trustRadius * 0.5);
damping = qMin(1.0e8, damping * 4.0);
writeTraceRow(m_currentIteration,
@ -2556,8 +2583,9 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
"solver_invalid_" + selectionName,
nullptr);
restoreEvaluationState(current);
if(consecutiveRejectedSteps >= 2) {
if(consecutiveSolverFailures >= 2) {
rebuildRequested = true;
rebuildReason = QT_TR_NOOP("2 consecutive solver failures");
}
if(consecutiveSolverFailures >= m_maxConsecutiveFailures) {
stopReason = LM_CONSECUTIVE_FAILURES;
@ -2582,9 +2610,20 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
coordinateStep);
// reductionRatio 衡量局部线性模型的可信度:接近 1 表示预测准确;
// 值较小表示虽然可能下降,但模型低估了非线性,需要收紧下一步。
double actualReduction = 0.5 * (trustRegionSquaredNorm(objectiveResidual) -
const double objectiveEnergy = 0.5 * trustRegionSquaredNorm(objectiveResidual);
double actualReduction = objectiveEnergy - 0.5 * (
trustRegionSquaredNorm(shapeStage ? candidate.breakdown.shapeResiduals : candidate.breakdown.residualVector));
double reductionRatio = actualReduction / predictedReduction;
// 使用同一采样层、同一阶段目标比较预测和实际改善。接近收敛时的微小
// 预测量交给停滞逻辑处理,避免比例数值波动反复触发昂贵的全参数重建。
const double predictionFloor = qMax(1.0e-14, objectiveEnergy * 1.0e-8);
const bool poorPrediction = predictedReduction > predictionFloor &&
(!isFiniteNumber(reductionRatio) || reductionRatio < 0.25);
consecutivePoorPredictions = poorPrediction ? consecutivePoorPredictions + 1 : 0;
if(consecutivePoorPredictions >= 2) {
rebuildRequested = true;
rebuildReason = QT_TR_NOOP("2 consecutive steps with actual improvement below 25% of prediction");
}
bool accepted = acceptable(candidate, current);
// 粗层认为下降而完整数据不认可时累计,连续两次就提前加密。
if(!shapeStage && m_layeredSampling && m_samplingStride > 1 && !accepted && actualReduction > 0.0) {
@ -2599,7 +2638,6 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
// 模型预测可靠时减小阻尼并可扩大半径,预测较差时保守收缩。
acceptPoint(candidate);
++acceptedSinceRebuild;
movementSinceRebuild += stepNorm;
consecutiveRejectedSteps = 0;
if(reductionRatio > 0.75) {
@ -2616,21 +2654,19 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
minimumTrustRadius, trustRadius * 0.75);
}
if(acceptedSinceRebuild >= 6 ||
movementSinceRebuild >= 0.30) {
if(acceptedSinceRebuild >= 10 && !rebuildRequested) {
rebuildRequested = true;
rebuildReason = QT_TR_NOOP("10 accepted steps since last rebuild");
}
modelRebuiltAtMinimumRadius = false;
} else {
// 拒绝时 candidate 只保留在 trace 中,DataManager 和内存状态都恢复
// 到 current。连续拒绝说明割线模型可能失真,因此请求重新试算灵敏度。
// 到 current。形状上限或全目标点验收也可能拒绝候选,不能仅凭拒绝
// 次数认定模型失准;重建由上面的预测质量判断,约束冲突先缩步。
++consecutiveRejectedSteps;
damping = qMin(1.0e8, damping * 4.0);
trustRadius = qMax(minimumTrustRadius, trustRadius * 0.5);
restoreEvaluationState(current);
if(consecutiveRejectedSteps >= 2) {
rebuildRequested = true;
}
}
// 候选只要更优就继续作为 current 保存;是否足以解除停滞,则统一
@ -2687,13 +2723,14 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
break;
}
if(selectedWindow < 0 && trustRadius <= minimumTrustRadius * 1.01 &&
consecutiveRejectedSteps >= 2) {
consecutiveRejectedSteps >= 2 && consecutivePoorPredictions >= 2) {
if(modelRebuiltAtMinimumRadius) {
if(promoteSampling(false)) continue;
stopReason = LM_LOCAL_OPTIMUM;
break;
}
rebuildRequested = true;
rebuildReason = QT_TR_NOOP("inaccurate model at minimum trust radius");
}
}

Loading…
Cancel
Save