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"); } }