diff --git a/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp b/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp index 3c396d3f..c9f287d3 100644 --- a/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp @@ -632,30 +632,6 @@ static bool buildTrustRegionFisherStep( return projectAndPredict(); } -// 为每个可调参数独立求解 LM 步,按边界投影后的预测下降量选择一个参数。 -static bool buildBestSingleParameterStep(const TrustRegionFisher& information, - const QVector& available, const QVector& coordinates, - double damping, double trustRadius, double minimumStep, - QVector* selected, QVector* step, double* predictedReduction) -{ - selected->clear(); - step->fill(0.0, coordinates.size()); - *predictedReduction = 0.0; - for(int i = 0; i < available.size(); ++i) { - QVector singleColumn(1, available[i]); - QVector trialStep; - double trialPrediction = 0.0; - if(buildTrustRegionFisherStep(information, singleColumn, coordinates, - damping, trustRadius, minimumStep, &trialStep, &trialPrediction) && - trialPrediction > *predictedReduction) { - *selected = singleColumn; - *step = trialStep; - *predictedReduction = trialPrediction; - } - } - return !selected->isEmpty(); -} - // 每次得到有效真实候选后,使用满足最新割线条件的秩一修正更新完整残差 // Jacobian。这样模型吸收了刚得到的真实变化,又不必立即逐参数重新试算。 static void updateTrustRegionJacobian( @@ -1052,35 +1028,21 @@ void nmCalculationAutoFitLM::writeTraceMetaFile() QTextStream out(&metaFile); out << "{\n"; - out << " \"schema_version\": 50,\n"; - out << " \"strategy\": \"permeability_height_then_shape_then_joint_lm\",\n"; + out << " \"schema_version\": 52,\n"; + out << " \"strategy\": \"permeability_height_then_joint_lm_shape_then_total\",\n"; out << " \"height_acceptance\": \"reliable_vertical_loss_decrease; no_shape_or_total_loss_constraint\",\n"; - out << " \"shape_priority\": \"wellbore_parameter_sweep_then_non_wellbore_sweep_then_optional_wellbore_recheck\",\n"; out << " \"shape_metric\": \"pressure_and_derivative_log_slopes_shared_sampling_grid_lag_round_10_percent_min_1\",\n"; - out << " \"early_parallel_metric\": \"pressure_and_derivative_log_slopes_first_window_shared_sampling_grid_lag_round_10_percent_min_1\",\n"; - out << " \"early_gap_metric\": \"weighted_rms_log10_pressure_derivative_ratio_error_first_window_shared_sampling_grid\",\n"; - out << " \"early_gap_bias_metric\": \"weighted_mean_signed_log10_gap_simulation_minus_target_first_window\",\n"; - out << " \"early_direction_policy\": \"first_window_shape_LM_selects_unvisited_wellbore_parameter_and_initial_direction; direction_locked_until_parameter_finished; gap_diagnostics_only\",\n"; - out << " \"early_initial_step_policy\": \"same_as_later_shape_sweep; initial_LM_coordinate_radius_0.24; no_cached_probe_acceptance\",\n"; - out << " \"early_expansion_policy\": \"accepted_double_from_new_point; rejected_halve_from_unchanged_point; coordinate_step_cap_0.60; switch_after_3_consecutive_rejections\",\n"; - out << " \"early_wellbore_acceptance\": \"first_window_shape_decrease; no_gap_direction_constraint; wellbore_recheck_retains_full_shape_guard\",\n"; - out << " \"early_wellbore_total_constraint\": false,\n"; - out << " \"shape_stage_total_constraint\": false,\n"; - out << " \"shape_wellbore_parameters_frozen\": true,\n"; - out << " \"shape_sensitivity_refresh\": \"active_group_columns_once_at_each_sweep_entry; secant updates during sweep; full Jacobian rebuilt at total-stage entry\",\n"; - out << " \"shape_expansion_policy\": \"lock parameter and direction; after each strict shape decrease double next coordinate step from accepted point; otherwise retain point and halve step\",\n"; - out << " \"shape_radius_policy\": \"initial LM step capped at 0.24; subsequent coordinate steps capped at 0.60; no prediction-ratio gate\",\n"; - out << " \"shape_global_parameter_selection\": \"largest predicted reduction among unvisited parameters_in_current_group; finish current parameter before selecting next; each parameter visited once_per_sweep\",\n"; - out << " \"stage2_budget\": \"one sweep_per_parameter_group; switch_after_3_consecutive_rejections; bounds, minimum step and solver failure limit retained\",\n"; - out << " \"shape_step_improvement_threshold\": \"any strict shape loss decrease; no cumulative improvement threshold\",\n"; - out << " \"iteration_count_scope\": \"max_iterations applies to total-stage LM; trace iteration remains cumulative\",\n"; - out << " \"wellbore_recheck_trigger\": \"early loss exceeds initial early-stage exit by max(0.0001, 10%)\",\n"; - out << " \"wellbore_recheck_acceptance\": \"early decrease with fixed entry shape limit max(0.0001, 5%); no_total_loss_constraint\",\n"; - out << " \"wellbore_recheck_budget\": \"once_when_early_shape_degrades; single_parameter_sweep_with_3_consecutive_rejections; stop_when_baseline_restored; independent_of_total_stage_budget\",\n"; + out << " \"shape_parameter_selection\": \"all_valid_free_columns_joint_LM; no_single_parameter_sweep_or_wellbore_recheck\",\n"; + out << " \"shape_acceptance\": \"strict_full_shape_loss_decrease; no_total_loss_constraint\",\n"; + out << " \"lm_step_policy\": \"same_joint_LM_damping_trust_radius_secant_updates_and_sensitivity_rebuilds_in_both_phases\",\n"; + out << " \"lm_phase_switch\": \"shape_target_reached_or_confirmed_stagnation_or_phase_budget_exhausted_then_total; user_stop_and_consecutive_solver_failure_abort\",\n"; + out << " \"lm_phase_budget\": \"each_phase_has_max_iterations_and_max(dimensions+2,20,3*max_iterations)_evaluations; independent_of_height_prealignment\",\n"; + out << " \"lm_effective_improvement\": \"max(0.00001,0.002*baseline_stage_error); 3_ineffective_steps_trigger_stagnation_confirmation\",\n"; + out << " \"iteration_count_scope\": \"max_iterations_applies_independently_to_each_LM_phase; trace_iteration_is_cumulative\",\n"; out << " \"total_stage_shape_constraint\": false,\n"; out << " \"total_parameter_selection\": \"all_valid_free_columns\",\n"; - out << " \"skin_difference_policy\": \"local_scale_in_all_stages\",\n"; - out << " \"stage2_difference_policy\": \"coordinate_step_cap_0.10_with_full_trust_radius; log_parameter_ratio_cap_1.50; skin_local_scale_fraction_0.10; total_stage_unchanged\",\n"; + out << " \"skin_difference_policy\": \"local_scale_fraction_0.02_in_both_LM_phases\",\n"; + out << " \"difference_policy\": \"coordinate_step_cap_0.04_with_half_trust_radius_in_both_LM_phases\",\n"; out << " \"difference_failure_policy\": \"halve_before_opposite_direction\",\n"; out << " \"trace_type\": \"finite_difference_lm_trust_region\",\n"; out << " \"run_id\": " << jsonEscape(m_traceRunId) << ",\n"; @@ -1818,10 +1780,11 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() return LM_OPTIMIZATION_FAILED; } - // 用户迭代设置只约束第三阶段;真实评价额度也在进入该阶段时独立起算。 - // 前期调整按改善情况结束,不能提前消耗掉最终联合 LM 的迭代和求解机会。 - const int totalEvaluationBudget = qMax(dimensions + 2, qMax(20, m_maxIterations * 3)); - int maximumEvaluations = m_totalEvaluations + totalEvaluationBudget; + // 形状与总误差共用原联合 LM,每段独立使用原迭代和求解额度。 + const int phaseEvaluationBudget = qMax(dimensions + 2, qMax(20, m_maxIterations * 3)); + int maximumEvaluations = 0; + int phaseIterations = 0; + int shapeIterations = 0; int totalIterations = 0; // 下列步长均位于归一化内部坐标:0.04 表示参数范围的 4%,信赖半径 // 限制一次联合移动的二范数,相关性门槛用于排除响应近乎共线的参数。 @@ -1829,9 +1792,6 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() const double minimumCoordinateStep = 1.0e-5; const double minimumTrustRadius = 2.0e-3; const double maximumTrustRadius = 0.30; - // 第二阶段两组逐参数调整共用较大步幅;第三阶段继续使用原来的半径和预测验收规则。 - const double initialShapeTrustRadius = 0.24; - const double maximumShapeTrustRadius = 0.60; // 误差下降至少达到绝对 1e-5 且相对当前有效基准 0.2% 才算有效改善。 // 更小的下降仍保留为最佳解,但不能反复清除停滞状态、延长拟合时间。 const double effectiveRelativeImprovement = 2.0e-3; @@ -1856,7 +1816,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() StopReasonLM stopReason = LM_MAX_ITERATIONS; // jacobian 的行对应固定采样的残差,列对应用户勾选的参数。 - // Fisher 按当前阶段取数值或形状残差行;冻结列不参与形状差分,整体阶段重建全部列。 + // Fisher 按当前目标取数值或形状残差行,两段均使用所有勾选参数。 QVector > jacobian; QVector jacobianColumnValid(dimensions, false); @@ -1960,9 +1920,9 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() emit progressUpdated(-1, current.fitness); emit logMessageGenerated(tr("=== Starting LM Main Loop ===")); emit logMessageGenerated( - tr("LM starting point error: %1; independent total-stage evaluation budget: %2") + tr("LM starting point error: %1; evaluation budget per LM phase: %2") .arg(current.fitness, 0, 'e', 4) - .arg(totalEvaluationBudget)); + .arg(phaseEvaluationBudget)); // 高度预调整只改变用户勾选的渗透率。ln(k) 的初始变化由有符号高度差 // 给出,真实求解后用割线估计修正;拒绝时缩步,不让其他参数补偿高度。 @@ -2027,211 +1987,25 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() if(m_shouldStop) return LM_USER_STOPPED; - // 第二阶段先调井筒参数、再调其他参数;各组共用单参数扫描,每个参数只访问一次。 - // 普通形状调整冻结井储、表皮,只按形状验收;总误差达标只在第三阶段判断。 + // 原逐参数第二阶段已移除;同一个联合 LM 先匹配整体形状,再匹配总误差。 bool shapeStage = true; - bool singleShapeStarted = false; - int shapeStepCount = 0; - QVector shapeParameterFinished(dimensions, false); - int activeShapeColumn = -1; - double shapeCoordinateStep = 0.0; - int consecutiveShapeRejections = 0; - const int maximumShapeRejections = 3; - const int storageColumn = m_enabledParamIndices.indexOf(2); - const int skinColumn = m_enabledParamIndices.indexOf(1); - bool hasRemainingShapeParameters = false; - for(int column = 0; column < dimensions; ++column) { - if(m_enabledParamIndices[column] != 1 && m_enabledParamIndices[column] != 2) - hasRemainingShapeParameters = true; - } - const int wellboreParameterCount = (storageColumn >= 0 ? 1 : 0) + (skinColumn >= 0 ? 1 : 0); - bool wellboreRecheck = false; - bool wellboreRecheckDone = false; - double earlyWellboreBaseline = current.breakdown.earlyParallelLoss; - double recheckShapeLimit = 0.0; - // 井储、表皮在组内按预测下降量选序;回检沿用第一窗口指标和相同扫描规则。 - bool earlyShapeStage = shapeStage && wellboreParameterCount > 0; - trustRadius = initialShapeTrustRadius; - auto parameterAllowedInStage = [&](int column) -> bool { - const int parameterIndex = m_enabledParamIndices[column]; - // 各子阶段只开放对应参数组,实际候选始终只改变选中的一列。 - if(!shapeStage) return true; - if(earlyShapeStage) return parameterIndex == 1 || parameterIndex == 2; - return parameterIndex != 1 && parameterIndex != 2; - }; auto stageError = [&](const TrustRegionEvaluation& point) -> double { - if(earlyShapeStage) return point.breakdown.earlyParallelLoss; return shapeStage ? point.breakdown.shapeLoss : point.fitness; }; auto acceptable = [&](const TrustRegionEvaluation& point, const TrustRegionEvaluation& base) -> bool { - if(!point.valid) return false; - if(earlyShapeStage) { - // 第一窗口形状决定是否改善;补调保留原有的整体形状保护。 - return point.breakdown.earlyParallelLoss < base.breakdown.earlyParallelLoss && - (!wellboreRecheck || point.breakdown.shapeLoss <= recheckShapeLimit); - } - return shapeStage - ? point.breakdown.shapeLoss < base.breakdown.shapeLoss - // 预调整结束后恢复原 LM:有效候选只按整体误差下降接受。 - : point.fitness < base.fitness; + return point.valid && stageError(point) < stageError(base); }; auto acceptPoint = [&](const TrustRegionEvaluation& point) { - // 只发布通过当前阶段目标验收的真实候选。 + // 参数、曲线和诊断同步发布,两个目标仅改变候选验收指标。 current = point; publishAcceptedPoint(current); restoreEvaluationState(current); }; - emit logMessageGenerated(shapeStage - ? tr("LM stage 2: adjust each shape parameter once; double the step after improvement, halve it after rejection, and switch after 3 consecutive rejections.") - : tr("LM stage 3: original LM fitting; accept by total error only.")); - if(earlyShapeStage) { - emit logMessageGenerated(tr("Early adjustment: select storage or skin by predicted first-window shape reduction; halve the step after rejection and switch after 3 consecutive rejections.")); - } - - // 第三阶段有效改善相对上一次有效基准累计判断;第二阶段按逐参数拒绝次数结束。 double effectiveImprovementBaseline = stageError(current); emit logMessageGenerated(tr("LM sampling: %1 points (%2 intervals per log-time decade).") .arg(current.breakdown.residualVector.size() / 2).arg(kAutoFitIntervalsPerDecade)); - auto reuseSensitivityForNextStage = [&]() { - // 更换目标时复用完整 J,清除旧目标下的拒绝和预测失准计数。 - 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; - consecutiveIneffectiveSteps = 0; - consecutiveRejectedSteps = 0; - consecutiveSolverFailures = 0; - stagnationConfirmationRequested = false; - globalFallbackAttempted = false; - trustRadius = shapeStage ? initialShapeTrustRadius : 0.12; - damping = 0.01; - }; - auto enterTotalStage = [&](const QString& reason) { - const bool finishedRecheck = wellboreRecheck; - totalIterations = 0; - maximumEvaluations = m_totalEvaluations + totalEvaluationBudget; - shapeStage = false; - earlyShapeStage = false; - wellboreRecheck = false; - activeShapeColumn = -1; - shapeCoordinateStep = 0.0; - consecutiveShapeRejections = 0; - effectiveImprovementBaseline = current.fitness; - reuseSensitivityForNextStage(); - // 第二阶段只测各子阶段需要的列;所有出口都在当前点重建完整 J,不能把缺列模型带入整体 LM。 - jacobian.clear(); - rebuildRequested = true; - rebuildReason = QT_TR_NOOP("full sensitivity at total-stage entry"); - emit progressUpdated(0, current.fitness); - emit logMessageGenerated(tr("Total-stage budget starts now: %1 iterations, %2 evaluations; pre-adjustment is counted separately.") - .arg(m_maxIterations).arg(totalEvaluationBudget)); - emit logMessageGenerated(tr("Shape stage ended: %1").arg(reason)); - emit logMessageGenerated(tr("LM stage 3: original LM fitting; accept by total error only.")); - writeTraceRow(m_currentIteration, -1, "stage_switch", current.parameters, current.fitness, - true, 0, finishedRecheck ? "wellbore_recheck_to_total" : "shape_to_total", ¤t.breakdown); - }; - - auto finishShapeStage = [&](const QString& reason) { - if(m_shouldStop) return; - // 只检查一次;用绝对加相对容差区分有意义的退化和接近零时的比例放大。 - if(wellboreRecheckDone || wellboreParameterCount == 0 || !hasRemainingShapeParameters) { - enterTotalStage(reason); - return; - } - wellboreRecheckDone = true; - const double earlyTolerance = qMax(1.0e-4, 0.10 * earlyWellboreBaseline); - if(current.breakdown.earlyParallelLoss <= earlyWellboreBaseline + earlyTolerance) { - writeTraceRow(m_currentIteration, -1, "stage_switch", current.parameters, current.fitness, - true, 0, "wellbore_recheck_skipped_no_degradation", ¤t.breakdown); - enterTotalStage(reason); - return; - } - wellboreRecheck = true; - earlyShapeStage = true; - // 回检只约束形状:允许总误差上升,但保护其他参数已调好的整体形状。 - recheckShapeLimit = current.breakdown.shapeLoss + qMax(1.0e-4, 0.05 * current.breakdown.shapeLoss); - // 回检重新开放井储和表皮,清除上一组留下的活动步长及完成标记。 - if(storageColumn >= 0) shapeParameterFinished[storageColumn] = false; - if(skinColumn >= 0) shapeParameterFinished[skinColumn] = false; - activeShapeColumn = -1; - shapeCoordinateStep = 0.0; - consecutiveShapeRejections = 0; - effectiveImprovementBaseline = current.breakdown.earlyParallelLoss; - // 其他参数已改变,回检入口重测第一窗口形状灵敏度,由 LM 重新确定方向和步幅。 - jacobian.clear(); - reuseSensitivityForNextStage(); - rebuildRequested = true; - rebuildReason = QT_TR_NOOP("new sensitivity for wellbore recheck"); - emit logMessageGenerated(tr("Wellbore recheck: early shape error=%1, shape limit=%2.") - .arg(current.breakdown.earlyParallelLoss, 0, 'g', 6) - .arg(recheckShapeLimit, 0, 'g', 6)); - writeTraceRow(m_currentIteration, -1, "stage_switch", current.parameters, current.fitness, - true, 0, "shape_to_wellbore_recheck", ¤t.breakdown); - }; - - auto finishEarlyShapeStage = [&](const QString& reason) { - if(m_shouldStop) return; - // 井筒参数整组完成后才切换目标,不能把旧组的活动参数或步长带入下一组。 - activeShapeColumn = -1; - shapeCoordinateStep = 0.0; - consecutiveShapeRejections = 0; - if(wellboreRecheck) { - enterTotalStage(reason); - return; - } - earlyShapeStage = false; - earlyWellboreBaseline = current.breakdown.earlyParallelLoss; - emit logMessageGenerated(tr("Early wellbore adjustment ended: %1").arg(reason)); - if(!hasRemainingShapeParameters) { - enterTotalStage(tr("no remaining shape parameters")); - return; - } - effectiveImprovementBaseline = current.breakdown.shapeLoss; - reuseSensitivityForNextStage(); - emit logMessageGenerated(tr("Shape fitting continues with storage and skin fixed; a conditional wellbore recheck follows.")); - writeTraceRow(m_currentIteration, -1, "stage_switch", current.parameters, current.fitness, - true, 0, "early_wellbore_to_shape", ¤t.breakdown); - }; - - auto finishShapeParameter = [&](const QString& reason) { - // 已完成的参数不再参与后续选优,保证所有可调参数只扫一遍。 - if(activeShapeColumn >= 0) { - shapeParameterFinished[activeShapeColumn] = true; - writeTraceRow(m_currentIteration, activeShapeColumn, "shape_parameter_end", - current.parameters, current.fitness, true, 0, reason, ¤t.breakdown); - } - activeShapeColumn = -1; - shapeCoordinateStep = 0.0; - consecutiveShapeRejections = 0; - for(int column = 0; column < dimensions; ++column) { - if(parameterAllowedInStage(column) && !shapeParameterFinished[column]) return; - } - if(earlyShapeStage) finishEarlyShapeStage(tr("all wellbore parameters visited once")); - else finishShapeStage(tr("all shape parameters visited once")); - }; - auto completeShapeSearchStep = [&](bool accepted) { - if(!shapeStage || m_shouldStop) return; - // 两组参数共用扩缩步规则:改善后放大,拒绝后从已接受点缩步重试。 - ++shapeStepCount; - consecutiveShapeRejections = accepted ? 0 : consecutiveShapeRejections + 1; - shapeCoordinateStep = qBound(-maximumShapeTrustRadius, - shapeCoordinateStep * (accepted ? 2.0 : 0.5), maximumShapeTrustRadius); - writeTraceRow(m_currentIteration, activeShapeColumn, "shape_step_end", current.parameters, - current.fitness, true, 0, - QString("%1_step_%2_rejections_%3_next_delta_%4") - .arg(accepted ? "improved" : "rejected").arg(shapeStepCount) - .arg(consecutiveShapeRejections).arg(shapeCoordinateStep, 0, 'g', 12), ¤t.breakdown); - if(consecutiveShapeRejections >= maximumShapeRejections) - finishShapeParameter("3_consecutive_non_improving_trials"); - else if(qAbs(shapeCoordinateStep) < minimumCoordinateStep) - finishShapeParameter("minimum_coordinate_step"); - }; - auto registerEffectiveImprovement = [&](double fitness) -> bool { - if(shapeStage) return false; const double requiredImprovement = qMax( effectiveAbsoluteImprovement, qAbs(effectiveImprovementBaseline) * effectiveRelativeImprovement); @@ -2248,12 +2022,8 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() return true; }; - // 第二阶段求解失败也进入当前参数的拒绝处理,第三阶段保留原 LM 停滞确认。 + // 两个目标均沿用原 LM 的有效改善门槛及重建后停滞确认。 auto recordIneffectiveStep = [&]() -> bool { - if(shapeStage) { - completeShapeSearchStep(false); - return false; - } ++consecutiveIneffectiveSteps; if(!globalFallbackAttempted) { return false; @@ -2279,23 +2049,17 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() }; emit logMessageGenerated( - tr("Total-stage effective improvement threshold: max(%1, %2% of baseline error); " + tr("LM effective improvement threshold: max(%1, %2% of baseline error); " "%3 consecutive ineffective steps trigger convergence confirmation") .arg(effectiveAbsoluteImprovement, 0, 'e', 2) .arg(effectiveRelativeImprovement * 100.0, 0, 'f', 2) .arg(maximumIneffectiveSteps)); - if(!shapeStage && current.fitness < m_targetError) { - return LM_TARGET_ACHIEVED; - } - // 在同一个真实工作点逐参数做单边差分。失败先在原方向缩步,再反向尝试; // 正常情况下每列仍只需一次真实求解,重试也计入总评价预算。 auto rebuildSensitivity = [&]() -> bool { - // 第二阶段差分不受第三阶段额度限制;每列仍只有有限的缩步和反向重试。 - const int evaluationLimit = shapeStage ? (std::numeric_limits::max)() : maximumEvaluations; - const int sensitivityColumnCount = shapeStage - ? (earlyShapeStage ? wellboreParameterCount : dimensions - wellboreParameterCount) : dimensions; + // 形状段也受原 LM 求解额度约束,差分范围及重试规则与总误差段相同。 + const int evaluationLimit = maximumEvaluations; const TrustRegionEvaluation base = current; const QVector baseResidual = trustRegionFullResidual(base.breakdown); const int residualCount = baseResidual.size(); @@ -2310,11 +2074,9 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() TrustRegionEvaluation bestProbe; int bestProbeColumn = -1; double bestProbeDelta = 0.0; - // 第二阶段允许内部坐标范围的 10% 及完整信赖半径,避免较大比例试探被旧上限截小。 - // 第三阶段仍为 4% 及半个信赖半径;均保留 0.5% 下限以减少数值噪声影响。 + // 两段共用原 LM 的局部差分尺度,避免目标切换同时改变灵敏度算法。 const double finiteDifferenceStep = qMin( - shapeStage ? 0.10 : sensitivityStep, - qMax(5.0e-3, trustRadius * (shapeStage ? 1.0 : 0.5))); + sensitivityStep, qMax(5.0e-3, trustRadius * 0.5)); for(int column = 0; column < dimensions && @@ -2322,19 +2084,14 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() processPauseAndStop(); ++column) { const int parameterIndex = m_enabledParamIndices[column]; - // 每组入口只测该组开放的参数;回检和整体阶段会重新差分。 - if(shapeStage && !parameterAllowedInStage(column)) continue; + const double lower = m_parameterLower[parameterIndex]; const double upper = m_parameterUpper[parameterIndex]; if(upper <= lower) continue; - // 第二阶段扩大形状试探范围;第三阶段仍沿用原来的局部差分尺度。 - // 表皮包含零和负值,按物理尺度扰动;正值参数按对数比例限制幅度。 + // 表皮包含零和负值,沿用原 LM 按局部物理尺度扰动的方式。 double localStep = finiteDifferenceStep; if(parameterIndex == 1) { - const double skinStepFraction = shapeStage ? 0.10 : 0.02; - localStep = qMin(localStep, skinStepFraction * qMax(0.1, qAbs(base.parameters[column])) / (upper - lower)); - } else if(shapeStage && useTrustRegionLogScale(parameterIndex, lower, upper)) { - localStep = qMin(localStep, qLn(1.50) / (qLn(upper) - qLn(lower))); + localStep = qMin(localStep, 0.02 * qMax(0.1, qAbs(base.parameters[column])) / (upper - lower)); } double positiveRoom = 1.0 - base.coordinates[column]; double negativeRoom = base.coordinates[column]; @@ -2382,7 +2139,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() probe.fitness, probe.valid, probe.elapsedMs, - (wellboreRecheck ? "wellbore_recheck_" : (shapeStage ? "shape_" : "total_")) + decision, + (shapeStage ? "shape_" : "total_") + decision, probe.valid ? &probe.breakdown : nullptr); if(!probe.valid) { @@ -2411,9 +2168,8 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() jacobianColumnValid[column] = true; columnBuilt = true; - // 第二阶段探针只用于灵敏度;只有第三阶段保留直接接受缓存探针的规则。 - if(!shapeStage && - parameterAllowedInStage(column) && acceptable(probe, base) && + // 沿用原 LM 的缓存探针验收,仅按当前段的目标选择更优探针。 + if(acceptable(probe, base) && (!bestProbe.valid || stageError(probe) < stageError(bestProbe))) { bestProbe = probe; bestProbeColumn = column; @@ -2425,20 +2181,18 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() } int validColumnCount = 0; - bool hasActiveSensitivity = false; for(int i = 0; i < jacobianColumnValid.size(); ++i) { if(jacobianColumnValid[i]) { ++validColumnCount; - if(parameterAllowedInStage(i)) hasActiveSensitivity = true; } } - // 冻结列成功不能掩盖所有可调形状列的求解失败;有效零梯度交给逐参数选步处理。 - if(validColumnCount == 0 || (shapeStage && !hasActiveSensitivity) || m_shouldStop) { + // 两段都要求至少一列有效灵敏度,零梯度仍交给原 LM 停滞判断。 + if(validColumnCount == 0 || m_shouldStop) { restoreEvaluationState(base); return false; } - // 只有整体阶段可接受更优缓存探针,第二阶段两组参数都由单参数 LM 选步。 + // 两段均保留原 LM 接受更优缓存探针的行为。 // 所有列先基于同一个 base 建完,再用已知割线平移 Jacobian,避免边算边移动基点。 if(bestProbe.valid && bestProbeColumn >= 0) { QVector acceptedStep(dimensions, 0.0); @@ -2456,10 +2210,10 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() current.fitness, true, 0, - "total_accepted_cached_probe", + shapeStage ? "shape_accepted_cached_probe" : "total_accepted_cached_probe", ¤t.breakdown); emit logMessageGenerated( - tr("Sensitivity probe accepted: total error=%1") + tr("Sensitivity probe accepted: current objective error=%1") .arg(stageError(current), 0, 'e', 4)); } else { restoreEvaluationState(current); @@ -2477,297 +2231,228 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() emit logMessageGenerated( tr("Sensitivity model rebuilt: %1/%2 parameter columns valid") .arg(validColumnCount) - .arg(sensitivityColumnCount)); + .arg(dimensions)); return true; }; int completedIterations = 0; - for(int iteration = 0; - !m_shouldStop && (shapeStage || - (totalIterations < m_maxIterations && m_totalEvaluations < maximumEvaluations)); - ++iteration) { - m_currentIteration = iteration; - completedIterations = iteration + 1; - - if(!processPauseAndStop()) { - break; - } - if(wellboreRecheck && current.breakdown.earlyParallelLoss <= - earlyWellboreBaseline + qMax(1.0e-4, 0.10 * earlyWellboreBaseline)) - enterTotalStage(tr("early shape error restored within tolerance")); - if(shapeStage && !earlyShapeStage && !singleShapeStarted) { - singleShapeStarted = true; - // 初调已改变井储/表皮,普通形状必须在新的工作点重测其余可调参数的响应。 - jacobian.clear(); - reuseSensitivityForNextStage(); - rebuildRequested = true; - rebuildReason = QT_TR_NOOP("fresh sensitivity at single-parameter shape entry"); - } - if(m_shouldStop) break; - if(!shapeStage && (totalIterations >= m_maxIterations || m_totalEvaluations >= maximumEvaluations)) break; - 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); - if(!rebuildSensitivity()) { - if(shapeStage && !m_shouldStop) { - if(earlyShapeStage) { - finishEarlyShapeStage(tr("no valid early sensitivity model")); - rebuildRequested = true; - rebuildReason = QT_TR_NOOP("no valid sensitivity model"); - continue; - } - // 真实差分全失败不能冒充停滞收敛。 - m_lastError = tr("Unable to build a valid shape sensitivity model."); - stopReason = LM_OPTIMIZATION_FAILED; - break; - } - stopReason = m_shouldStop - ? LM_USER_STOPPED - : LM_LOCAL_OPTIMUM; - break; - } - if(!shapeStage && current.fitness < m_targetError) { + for(int phase = 0; phase < 2 && !m_shouldStop; ++phase) { + shapeStage = phase == 0; + phaseIterations = 0; + maximumEvaluations = m_totalEvaluations + phaseEvaluationBudget; + stopReason = LM_MAX_ITERATIONS; + effectiveImprovementBaseline = stageError(current); + trustRadius = 0.12; + damping = 0.01; + consecutiveRejectedSteps = 0; + consecutiveSolverFailures = 0; + acceptedSinceRebuild = 0; + consecutiveIneffectiveSteps = 0; + consecutivePoorPredictions = 0; + modelRebuiltAtMinimumRadius = false; + stagnationConfirmationRequested = false; + globalFallbackAttempted = false; + jacobian.clear(); + rebuildRequested = true; + rebuildReason = shapeStage ? QT_TR_NOOP("full sensitivity at shape LM entry") + : 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.") + .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); + + for(int iteration = completedIterations; + !m_shouldStop && phaseIterations < m_maxIterations && m_totalEvaluations < maximumEvaluations; + ++iteration) { + m_currentIteration = iteration; + completedIterations = iteration + 1; + if(!processPauseAndStop()) break; + if(stageError(current) < m_targetError) { stopReason = LM_TARGET_ACHIEVED; break; } - if(!shapeStage && m_totalEvaluations >= maximumEvaluations) { - stopReason = LM_MAX_ITERATIONS; - break; - } - // 差分探针不等于 LM 候选;重建后继续按当前阶段求步并真实评价,再确认停滞。 - registerEffectiveImprovement(stageError(current)); - } - - // 第三阶段独立计数;方向不可行也消耗一次局部尝试,不能无限缩步循环。 - if(!shapeStage) ++totalIterations; - // 每次从最新 J 和当前阶段残差重算全局信息,供单参数或联合 LM 求步。 - const QVector objectiveResidual = earlyShapeStage - ? current.breakdown.earlyParallelResiduals - : (shapeStage ? current.breakdown.shapeResiduals : current.breakdown.residualVector); - const int rowOffset = earlyShapeStage - ? current.breakdown.residualVector.size() + current.breakdown.shapeResiduals.size() + - current.breakdown.earlyValueResiduals.size() - : (shapeStage ? current.breakdown.residualVector.size() : 0); - const QVector > objectiveJacobian = jacobian.mid(rowOffset, objectiveResidual.size()); - QVector objectiveCoordinates; - if(shapeStage) { - // Fisher 窗口与形状残差使用同一组斜率区间中心。 - const int pointCount = current.breakdown.residualVector.size() / 2; - const int lag = autoFitShapeLag(pointCount); - for(int i = 0; i < pointCount - lag; ++i) - objectiveCoordinates.append((i + lag * 0.5) / (pointCount - 1)); - } - const QVector information = buildTrustRegionFisher( - objectiveJacobian, objectiveResidual, jacobianColumnValid, objectiveCoordinates); - // 前期残差已含第一窗口权重,使用全局和,不能再次乘局部窗口权重。 - const TrustRegionFisher& global = information[kAutoFitTimeWindowCount]; - QVector stageColumnValid = jacobianColumnValid; - for(int column = 0; column < dimensions; ++column) { - if(!parameterAllowedInStage(column)) stageColumnValid[column] = false; - } - QVector selectedColumns; - QVector coordinateStep; - double predictedReduction = 0.0; - const int selectedWindow = earlyShapeStage ? 0 : -1; - - // 第二阶段从当前组有效自由列中选一个,整体 LM 仍联合求解。 - // 保留弱敏感、相关及边界列供预测比较;窗口只保留作误差诊断。 - for(int column = 0; column < dimensions; ++column) { - if(stageColumnValid[column] && global.matrix[column][column] > 0.0) - selectedColumns.append(column); - } - if(shapeStage) { - // 首步由灵敏度选出尚未调过的参数;后续锁定方向,直接使用扩缩后的步长。 - if(activeShapeColumn < 0) { - QVector availableColumns; - for(int i = 0; i < selectedColumns.size(); ++i) { - if(!shapeParameterFinished[selectedColumns[i]]) availableColumns.append(selectedColumns[i]); + 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); + if(!rebuildSensitivity()) { + stopReason = m_shouldStop + ? LM_USER_STOPPED + : LM_LOCAL_OPTIMUM; + break; } - buildBestSingleParameterStep(global, availableColumns, current.coordinates, - 0.01, initialShapeTrustRadius, minimumCoordinateStep, - &selectedColumns, &coordinateStep, &predictedReduction); - if(!selectedColumns.isEmpty()) { - activeShapeColumn = selectedColumns[0]; - shapeCoordinateStep = coordinateStep[activeShapeColumn]; - consecutiveShapeRejections = 0; - writeTraceRow(m_currentIteration, activeShapeColumn, "shape_parameter_start", - current.parameters, current.fitness, true, 0, - "largest_predicted_reduction_unvisited", ¤t.breakdown); - } else { - // 剩余参数均无可行预测下降步时逐项记录,不能重复选择已完成参数。 - const bool finishingEarlyShape = earlyShapeStage; - for(int column = 0; column < dimensions; ++column) { - if(!parameterAllowedInStage(column) || shapeParameterFinished[column]) continue; - activeShapeColumn = column; - finishShapeParameter(jacobianColumnValid[column] - ? "no_feasible_descent_step" : "invalid_sensitivity"); - if(!shapeStage || earlyShapeStage != finishingEarlyShape) break; - } - --iteration; - continue; + if(stageError(current) < m_targetError) { + stopReason = LM_TARGET_ACHIEVED; + break; } - } else { - selectedColumns.clear(); - selectedColumns.append(activeShapeColumn); - coordinateStep.fill(0.0, dimensions); - shapeCoordinateStep = qBound(0.0, - current.coordinates[activeShapeColumn] + shapeCoordinateStep, 1.0) - - current.coordinates[activeShapeColumn]; - if(qAbs(shapeCoordinateStep) < minimumCoordinateStep) { - finishShapeParameter("parameter_bound_or_minimum_step"); - --iteration; - continue; + if(m_totalEvaluations >= maximumEvaluations) { + stopReason = LM_MAX_ITERATIONS; + break; } - coordinateStep[activeShapeColumn] = shapeCoordinateStep; - predictedReduction = -global.gradient[activeShapeColumn] * shapeCoordinateStep - - 0.5 * global.matrix[activeShapeColumn][activeShapeColumn] - * shapeCoordinateStep * shapeCoordinateStep; + // 差分探针不等于 LM 候选;重建后继续按当前阶段求步并真实评价,再确认停滞。 + registerEffectiveImprovement(stageError(current)); + } + + // 两段分别计数;无可行方向也消耗一次尝试,避免无限缩步重建。 + ++phaseIterations; + const QVector objectiveResidual = shapeStage + ? current.breakdown.shapeResiduals : current.breakdown.residualVector; + const int rowOffset = shapeStage ? current.breakdown.residualVector.size() : 0; + const QVector > objectiveJacobian = jacobian.mid(rowOffset, objectiveResidual.size()); + QVector objectiveCoordinates; + if(shapeStage) { + // Fisher 窗口与形状残差使用同一组斜率区间中心。 + const int pointCount = current.breakdown.residualVector.size() / 2; + const int lag = autoFitShapeLag(pointCount); + for(int i = 0; i < pointCount - lag; ++i) + objectiveCoordinates.append((i + lag * 0.5) / (pointCount - 1)); + } + const QVector information = buildTrustRegionFisher( + objectiveJacobian, objectiveResidual, jacobianColumnValid, objectiveCoordinates); + // 两段都使用当前目标的全局信息,不按时间窗口另行选参。 + const TrustRegionFisher& global = information[kAutoFitTimeWindowCount]; + QVector selectedColumns; + QVector coordinateStep; + double predictedReduction = 0.0; + + // 两段都联合调整全部有效自由参数,只切换残差及其对应的 Jacobian 行。 + for(int column = 0; column < dimensions; ++column) { + if(jacobianColumnValid[column] && global.matrix[column][column] > 0.0) + selectedColumns.append(column); } - } else { - // 第三阶段保持全部有效自由参数的联合 LM 调整。 globalFallbackAttempted = true; if(selectedColumns.isEmpty() || !buildTrustRegionFisherStep( global, selectedColumns, current.coordinates, damping, trustRadius, minimumCoordinateStep, &coordinateStep, &predictedReduction)) { selectedColumns.clear(); } - } - // 当前阶段没有可行下降步时,收缩半径并进入原有重建/收敛处理。 - if(selectedColumns.isEmpty()) { - if(trustRadius <= minimumTrustRadius * 1.01 && - modelRebuiltAtMinimumRadius) { - stopReason = LM_LOCAL_OPTIMUM; - break; + // 当前阶段没有可行下降步时,收缩半径并进入原有重建/收敛处理。 + if(selectedColumns.isEmpty()) { + if(trustRadius <= minimumTrustRadius * 1.01 && + modelRebuiltAtMinimumRadius) { + stopReason = LM_LOCAL_OPTIMUM; + break; + } + 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()) { + stopReason = LM_LOCAL_OPTIMUM; + break; + } + continue; } - 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()) { - stopReason = LM_LOCAL_OPTIMUM; - break; + + double stepNorm = qSqrt(trustRegionSquaredNorm(coordinateStep)); + QVector candidateCoordinates = current.coordinates; + for(int column = 0; column < dimensions; ++column) { + candidateCoordinates[column] += coordinateStep[column]; } - continue; - } + // 窗口和参数索引仅写入已有跟踪文件,不向拟合日志窗口增加分段信息。 + QStringList selectedParameterIndices; + for(int i = 0; i < selectedColumns.size(); ++i) { + selectedParameterIndices << QString::number(m_enabledParamIndices[selectedColumns[i]]); + } + QString selectionName = QString(shapeStage ? "shape_global_params_" : "total_global_params_") + + selectedParameterIndices.join("_"); - double stepNorm = qSqrt(trustRegionSquaredNorm(coordinateStep)); - QVector candidateCoordinates = current.coordinates; - for(int column = 0; column < dimensions; ++column) { - candidateCoordinates[column] += coordinateStep[column]; - } - // 窗口和参数索引仅写入已有跟踪文件,不向拟合日志窗口增加分段信息。 - QStringList selectedParameterIndices; - for(int i = 0; i < selectedColumns.size(); ++i) { - selectedParameterIndices << QString::number(m_enabledParamIndices[selectedColumns[i]]); - } - QString selectionName = (wellboreRecheck ? QString("recheck_") : QString()) + QString(earlyShapeStage - ? (m_enabledParamIndices[selectedColumns[0]] == 2 ? "storage_parallel_" : "skin_parallel_") : (shapeStage ? "shape_" : "total_")) + (selectedWindow >= 0 - ? QString("window_%1").arg(selectedWindow + 1) : QString("global")) + - "_params_" + selectedParameterIndices.join("_"); - - TrustRegionEvaluation candidate; - candidate.coordinates = candidateCoordinates; - candidate.parameters = parametersFromCoordinates(candidate.coordinates); - if(earlyShapeStage && m_enabledParamIndices[selectedColumns[0]] == 2) { - emit logMessageGenerated(tr("Wellbore storage trial: early shape error=%1, C=%2 -> %3") - .arg(current.breakdown.earlyParallelLoss, 0, 'g', 5) - .arg(current.parameters[storageColumn], 0, 'g', 6) - .arg(candidate.parameters[storageColumn], 0, 'g', 6)); - } - candidate.valid = evaluateTrustRegionPoint( - candidate.parameters, - &candidate.fitness, - &candidate.breakdown, - &candidate.curve, - &candidate.elapsedMs); + TrustRegionEvaluation candidate; + candidate.coordinates = candidateCoordinates; + candidate.parameters = parametersFromCoordinates(candidate.coordinates); + candidate.valid = evaluateTrustRegionPoint( + candidate.parameters, + &candidate.fitness, + &candidate.breakdown, + &candidate.curve, + &candidate.elapsedMs); - if(m_shouldStop) { - restoreEvaluationState(current); - stopReason = LM_USER_STOPPED; - break; - } + if(m_shouldStop) { + restoreEvaluationState(current); + stopReason = LM_USER_STOPPED; + break; + } - if(!candidate.valid) { - // 求解失败的候选不能改变 current。先完整恢复上一个已接受参数和 - // 对应误差快照,再缩小信赖域;连续失败达到上限才终止整个拟合。 - ++consecutiveSolverFailures; - ++consecutiveRejectedSteps; - consecutivePoorPredictions = 0; - trustRadius = qMax(minimumTrustRadius, trustRadius * 0.5); - damping = qMin(1.0e8, damping * 4.0); - writeTraceRow(m_currentIteration, - -1, - "trust_region_candidate", - candidate.parameters, - candidate.fitness, - false, - candidate.elapsedMs, - "solver_invalid_" + selectionName, - nullptr); - restoreEvaluationState(current); - if(consecutiveSolverFailures >= 2 && !shapeStage) { - rebuildRequested = true; - rebuildReason = QT_TR_NOOP("2 consecutive solver failures"); + if(!candidate.valid) { + // 求解失败的候选不能改变 current。先完整恢复上一个已接受参数和 + // 对应误差快照,再缩小信赖域;连续失败达到上限才终止整个拟合。 + ++consecutiveSolverFailures; + ++consecutiveRejectedSteps; + consecutivePoorPredictions = 0; + trustRadius = qMax(minimumTrustRadius, trustRadius * 0.5); + damping = qMin(1.0e8, damping * 4.0); + writeTraceRow(m_currentIteration, + -1, + "trust_region_candidate", + candidate.parameters, + candidate.fitness, + false, + candidate.elapsedMs, + "solver_invalid_" + selectionName, + nullptr); + restoreEvaluationState(current); + if(consecutiveSolverFailures >= 2) { + rebuildRequested = true; + rebuildReason = QT_TR_NOOP("2 consecutive solver failures"); + } + if(consecutiveSolverFailures >= m_maxConsecutiveFailures) { + stopReason = LM_CONSECUTIVE_FAILURES; + break; + } + if(recordIneffectiveStep()) { + stopReason = LM_LOCAL_OPTIMUM; + break; + } + continue; } - if(consecutiveSolverFailures >= m_maxConsecutiveFailures) { - stopReason = LM_CONSECUTIVE_FAILURES; + + if(m_shouldStop) { + restoreEvaluationState(current); + stopReason = LM_USER_STOPPED; break; } - if(recordIneffectiveStep()) { - stopReason = LM_LOCAL_OPTIMUM; - break; + consecutiveSolverFailures = 0; + // 有效候选即使最终被拒绝,也提供了一条真实割线,可用于修正下一轮 + // 局部模型;是否成为新工作点由当前阶段的目标和约束共同决定。 + const AutoFitObjectiveBreakdownLM oldBreakdown = current.breakdown; + updateTrustRegionJacobian( + &jacobian, + trustRegionFullResidual(oldBreakdown), + trustRegionFullResidual(candidate.breakdown), + coordinateStep); + // reductionRatio 衡量局部线性模型的可信度:接近 1 表示预测准确; + // 值较小表示虽然可能下降,但模型低估了非线性,需要收紧下一步。 + const double objectiveEnergy = 0.5 * trustRegionSquaredNorm(objectiveResidual); + const QVector candidateResidual = shapeStage + ? candidate.breakdown.shapeResiduals : candidate.breakdown.residualVector; + const double candidateEnergy = 0.5 * trustRegionSquaredNorm(candidateResidual); + double actualReduction = objectiveEnergy - candidateEnergy; + double reductionRatio = predictedReduction > 1.0e-14 ? actualReduction / predictedReduction : 0.0; + // 使用固定采样和同一阶段目标比较预测与实际改善。接近收敛时的微小 + // 预测量交给停滞逻辑处理,避免比例数值波动反复触发昂贵的全参数重建。 + 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"); } - continue; - } + bool accepted = acceptable(candidate, current); - if(m_shouldStop) { - restoreEvaluationState(current); - stopReason = LM_USER_STOPPED; - break; - } - consecutiveSolverFailures = 0; - // 有效候选即使最终被拒绝,也提供了一条真实割线,可用于修正下一轮 - // 局部模型;是否成为新工作点由当前阶段的目标和约束共同决定。 - const AutoFitObjectiveBreakdownLM oldBreakdown = current.breakdown; - updateTrustRegionJacobian( - &jacobian, - trustRegionFullResidual(oldBreakdown), - trustRegionFullResidual(candidate.breakdown), - coordinateStep); - // reductionRatio 衡量局部线性模型的可信度:接近 1 表示预测准确; - // 值较小表示虽然可能下降,但模型低估了非线性,需要收紧下一步。 - const double objectiveEnergy = 0.5 * trustRegionSquaredNorm(objectiveResidual); - const QVector candidateResidual = earlyShapeStage - ? candidate.breakdown.earlyParallelResiduals - : (shapeStage ? candidate.breakdown.shapeResiduals : candidate.breakdown.residualVector); - const double candidateEnergy = 0.5 * trustRegionSquaredNorm(candidateResidual); - double actualReduction = objectiveEnergy - candidateEnergy; - double reductionRatio = predictedReduction > 1.0e-14 ? actualReduction / predictedReduction : 0.0; - // 使用固定采样和同一阶段目标比较预测与实际改善。接近收敛时的微小 - // 预测量交给停滞逻辑处理,避免比例数值波动反复触发昂贵的全参数重建。 - 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 && !shapeStage) { - rebuildRequested = true; - rebuildReason = QT_TR_NOOP("2 consecutive steps with actual improvement below 25% of prediction"); - } - bool accepted = acceptable(candidate, current); - QString componentName = earlyShapeStage - ? (m_enabledParamIndices[selectedColumns[0]] == 2 ? "storage_parallel" : "skin_parallel") : (shapeStage ? "shape" : "total"); - - if(accepted) { - // 当前阶段接受候选后同步发布参数、曲线和诊断。 - // 模型预测可靠时减小阻尼并可扩大半径,预测较差时保守收缩。 - acceptPoint(candidate); - ++acceptedSinceRebuild; - consecutiveRejectedSteps = 0; - - // 第三阶段保留原 LM 控制;第二阶段两组都在步末直接扩缩实际步长。 - if(!shapeStage) { + if(accepted) { + // 当前阶段接受候选后同步发布参数、曲线和诊断。 + // 模型预测可靠时减小阻尼并可扩大半径,预测较差时保守收缩。 + acceptPoint(candidate); + ++acceptedSinceRebuild; + consecutiveRejectedSteps = 0; + + // 两段共用原 LM 阻尼与信赖半径更新,预测比不替代目标误差验收。 if(reductionRatio > 0.75) { damping = qMax(1.0e-8, damping * 0.5); if(stepNorm >= trustRadius * 0.8) { @@ -2781,81 +2466,71 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() trustRadius = qMax( minimumTrustRadius, trustRadius * 0.75); } - } - if(acceptedSinceRebuild >= 10 && !rebuildRequested && !shapeStage) { - rebuildRequested = true; - rebuildReason = QT_TR_NOOP("10 accepted steps since last rebuild"); + if(acceptedSinceRebuild >= 10 && !rebuildRequested) { + rebuildRequested = true; + rebuildReason = QT_TR_NOOP("10 accepted steps since last rebuild"); + } + modelRebuiltAtMinimumRadius = false; + } else { + // 拒绝时参数恢复到 current;沿用原 LM 的阻尼和半径收缩规则, + // 模型是否重建仍由预测质量判断,不因更换目标而改变。 + ++consecutiveRejectedSteps; + damping = qMin(1.0e8, damping * 4.0); + trustRadius = qMax(minimumTrustRadius, trustRadius * 0.5); + restoreEvaluationState(current); } - modelRebuiltAtMinimumRadius = false; - } else { - // 拒绝时 candidate 只保留在 trace 中,DataManager 和内存状态都恢复 - // 到 current。形状上限或全目标点验收也可能拒绝候选,不能仅凭拒绝 - // 次数认定模型失准;重建由上面的预测质量判断,约束冲突先缩步。 - ++consecutiveRejectedSteps; - damping = qMin(1.0e8, damping * 4.0); - trustRadius = qMax(minimumTrustRadius, trustRadius * 0.5); - restoreEvaluationState(current); - } - writeTraceRow(m_currentIteration, - -1, - "trust_region_candidate", - candidate.parameters, - candidate.fitness, - true, - candidate.elapsedMs, - accepted - ? "accepted_" + selectionName - : "rejected_" + selectionName, - &candidate.breakdown); - - QString componentDisplayName = componentName; - if(componentName == "vertical") { - componentDisplayName = tr("vertical deviation"); - } else if(componentName == "horizontal") { - componentDisplayName = tr("horizontal deviation"); - } else if(componentName == "storage_parallel") { - componentDisplayName = tr("wellbore storage early shape"); - } else if(componentName == "skin_parallel") { - componentDisplayName = tr("skin early shape"); - } else if(componentName == "shape") { - componentDisplayName = tr("shape deviation"); - } else if(componentName == "total") { - componentDisplayName = tr("total error"); - } + writeTraceRow(m_currentIteration, + -1, + "trust_region_candidate", + candidate.parameters, + candidate.fitness, + true, + candidate.elapsedMs, + accepted + ? "accepted_" + selectionName + : "rejected_" + selectionName, + &candidate.breakdown); - 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"))); - emit progressUpdated(shapeStage ? -1 : totalIterations, m_globalBestFitness); - - // 先记录当前试调结果,再发布阶段切换,避免日志显示为未试调就结束。 - if(shapeStage) { - completeShapeSearchStep(accepted); - continue; - } - const bool effectiveImprovement = registerEffectiveImprovement(stageError(current)); - if(!effectiveImprovement && recordIneffectiveStep()) stopReason = LM_LOCAL_OPTIMUM; + 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"))); + emit progressUpdated(phaseIterations, m_globalBestFitness); - if(stopReason == LM_LOCAL_OPTIMUM) break; + const bool effectiveImprovement = registerEffectiveImprovement(stageError(current)); + if(!effectiveImprovement && recordIneffectiveStep()) stopReason = LM_LOCAL_OPTIMUM; - if(!shapeStage && current.fitness < m_targetError) { - stopReason = LM_TARGET_ACHIEVED; - break; - } - if(!shapeStage && selectedWindow < 0 && trustRadius <= minimumTrustRadius * 1.01 && - consecutiveRejectedSteps >= 2 && consecutivePoorPredictions >= 2) { - if(modelRebuiltAtMinimumRadius) { - stopReason = LM_LOCAL_OPTIMUM; + if(stopReason == LM_LOCAL_OPTIMUM) break; + + if(stageError(current) < m_targetError) { + stopReason = LM_TARGET_ACHIEVED; break; } - rebuildRequested = true; - rebuildReason = QT_TR_NOOP("inaccurate model at minimum trust radius"); + if(trustRadius <= minimumTrustRadius * 1.01 && + consecutiveRejectedSteps >= 2 && consecutivePoorPredictions >= 2) { + if(modelRebuiltAtMinimumRadius) { + stopReason = LM_LOCAL_OPTIMUM; + break; + } + rebuildRequested = true; + rebuildReason = QT_TR_NOOP("inaccurate model at minimum trust radius"); + } + } + if(shapeStage) shapeIterations = phaseIterations; + else totalIterations = phaseIterations; + if(m_shouldStop || stopReason == LM_CONSECUTIVE_FAILURES || stopReason == LM_OPTIMIZATION_FAILED) + break; + // 形状达标、确认停滞或用完额度后,保留当前参数进入总误差段。 + if(shapeStage) { + emit logMessageGenerated(tr("Shape LM 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); } } @@ -2863,8 +2538,8 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting() m_currentIteration = completedIterations - 1; } restoreEvaluationState(current); - emit logMessageGenerated(tr("Adaptive fitting counts: %1 shape attempts, %2 total-stage iterations, %3 total evaluations.") - .arg(shapeStepCount).arg(totalIterations).arg(m_totalEvaluations)); + emit logMessageGenerated(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) { return LM_USER_STOPPED;