diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm
index e279b9ee..5408661a 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 99e585c7..947891b9 100644
--- a/Bin/Config/Lang/cn/nmNum_cn.ts
+++ b/Bin/Config/Lang/cn/nmNum_cn.ts
@@ -1184,6 +1184,54 @@ Reason: %1
permeability reached its bound
渗透率已到达范围边界
+
+ Rebuilding sensitivity model: %1
+ 正在重建灵敏度模型:%1
+
+
+ initial sensitivity model
+ 首次建立灵敏度模型
+
+
+ sampling layer changed
+ 采样层级已改变
+
+
+ no valid sensitivity model
+ 没有有效的灵敏度模型
+
+
+ solver failure before stage switch
+ 阶段切换前发生求解失败
+
+
+ 10 accepted steps since last rebuild
+ 距上次重建已接受 10 步调整,进行兜底刷新
+
+
+ confirm stagnation after ineffective steps
+ 连续无有效改善,重新确认停滞
+
+
+ sampling refinement merged with pending rebuild
+ 提前加密采样,与本次重建合并处理
+
+
+ no feasible descent step
+ 未找到可行的下降方向
+
+
+ 2 consecutive solver failures
+ 连续 2 次候选求解失败
+
+
+ 2 consecutive steps with actual improvement below 25% of prediction
+ 连续 2 步实际改善不足预测的 25%
+
+
+ inaccurate model at minimum trust radius
+ 最小信赖半径下仍连续预测失准
+
nmCalculationSolver
diff --git a/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp b/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp
index 68f683d9..b932eaa7 100644
--- a/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp
+++ b/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp
@@ -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 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, ¤t.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");
}
}