refactor(nmNum): 将井储和表皮调整统一为单参数 LM 策略

- 按第一时间窗口形状误差的预计下降量选择参数、方向和初始步长
- 改善后步长翻倍,拒绝后减半,连续拒绝三次后切换参数
- 移除间距定向及初始形状变差时的扩步试探逻辑
- 同步拟合策略记录和中文日志资源
feature/AutoFit-Optimize-20260914
lvjunjie 4 days ago
parent 8d807413c4
commit a8053b23a0

Binary file not shown.

@ -1217,16 +1217,12 @@ Reason: %1</source>
<translation>没有其他勾选参数需要调整形状</translation>
</message>
<message>
<source>Early adjustment: adjust wellbore storage, then skin, to match the early pressure-derivative slope difference of the target.</source>
<translation>前期预调整:先调整井储,再调整表皮,使模拟压力与导数的前期斜率差接近目标曲线。</translation>
<source>Early adjustment: select storage or skin by predicted first-window shape reduction; halve the step after rejection and switch after 3 consecutive rejections.</source>
<translation>前期预调整:按第一时间窗口形状误差的预计下降量选择井储或表皮;拒绝后步长减半,连续拒绝三次换参数。</translation>
</message>
<message>
<source>Early adjustment: adjust skin to match the early pressure-derivative slope difference of the target.</source>
<translation>前期预调整:调整表皮,使模拟压力与导数的前期斜率差接近目标曲线。</translation>
</message>
<message>
<source>Wellbore storage adjustment ended: %1; now adjusting skin.</source>
<translation>井储调整结束:%1;开始单独调整表皮。</translation>
<source>all wellbore parameters visited once</source>
<translation>已勾选的井储、表皮参数均已完成一轮调整</translation>
</message>
<message>
<source>Early wellbore adjustment ended: %1</source>

@ -381,13 +381,6 @@ static double trustRegionEarlyGapBias(const AutoFitObjectiveBreakdownLM& objecti
return bias / qLn(10.0);
}
// 按用户指定的形态规则定向:初始模拟间距大则增大井储/表皮,小则减小。
static double trustRegionEarlyGapDirection(double initialGapBias)
{
if(!isFiniteNumber(initialGapBias) || qAbs(initialGapBias) <= 1.0e-12) return 0.0;
return initialGapBias > 0.0 ? 1.0 : -1.0;
}
// 第一窗口的形状能量与局部 Fisher 使用相同的中心时间和重叠权重,
// 压力、导数同时参与;不重新插值或调用求解器。
static double trustRegionEarlyShapeEnergy(const QVector<double>& residual)
@ -695,50 +688,6 @@ static bool buildBestSingleParameterStep(const TrustRegionFisher& information,
return !selected->isEmpty();
}
// 前期每次只调一个参数:间距锁定符号,形状 LM 提供幅度和后续验收依据。
static bool buildEarlyGapGuidedStep(const TrustRegionFisher& shapeInformation,
const QVector<int>& selected, const QVector<double>& coordinates, double direction,
double damping, double trustRadius, double minimumStep,
QVector<double>* step, double* predictedReduction)
{
if(selected.size() != 1 || direction == 0.0) return false;
const int column = selected[0];
TrustRegionFisher guided = shapeInformation;
guided.gradient[column] = -direction * qAbs(shapeInformation.gradient[column]);
if(!buildTrustRegionFisherStep(guided, selected, coordinates, damping,
trustRadius, minimumStep, step, predictedReduction)) return false;
// 改方向后的预测必须用原形状模型重算,不能把上坡伪装成预测下降。
const double delta = (*step)[column];
*predictedReduction = -shapeInformation.gradient[column] * delta -
0.5 * shapeInformation.matrix[column][column] * delta * delta;
return isFiniteNumber(*predictedReduction);
}
// 放大当前方向,仍受传入的最大半径和参数边界限制。
// 前期初始探路可跨过预测上坡区,最终仍按真实形状验收。
static bool buildExpandedTrustRegionStep(const TrustRegionFisher& information,
const QVector<double>& coordinates, const QVector<double>& originalStep,
double maximumRadius, QVector<double>* expandedStep, double* prediction,
bool requirePredictedDescent = true)
{
const double norm = qSqrt(trustRegionSquaredNorm(originalStep));
if(norm <= 1.0e-12) return false;
const double scale = qMin(2.0, maximumRadius / norm);
if(scale <= 1.01) return false;
expandedStep->resize(originalStep.size());
double difference = 0.0;
for(int i = 0; i < originalStep.size(); ++i) {
(*expandedStep)[i] = qBound(0.0, coordinates[i] + scale * originalStep[i], 1.0) - coordinates[i];
difference += qAbs((*expandedStep)[i] - originalStep[i]);
}
if(difference <= 1.0e-10) return false;
*prediction = -trustRegionDotProduct(information.gradient, *expandedStep);
for(int i = 0; i < expandedStep->size(); ++i)
for(int j = 0; j < expandedStep->size(); ++j)
*prediction -= 0.5 * (*expandedStep)[i] * information.matrix[i][j] * (*expandedStep)[j];
return isFiniteNumber(*prediction) && (!requirePredictedDescent || *prediction > 1.0e-14);
}
// 每次得到有效真实候选后,使用满足最新割线条件的秩一修正更新完整残差
// Jacobian。这样模型吸收了刚得到的真实变化,又不必立即逐参数重新试算。
static void updateTrustRegionJacobian(
@ -1147,31 +1096,31 @@ void nmCalculationAutoFitLM::writeTraceMetaFile()
QTextStream out(&metaFile);
out << "{\n";
out << " \"schema_version\": 39,\n";
out << " \"schema_version\": 47,\n";
out << " \"strategy\": \"permeability_height_then_shape_then_joint_lm\",\n";
out << " \"height_acceptance\": \"reliable_vertical_loss_decrease; no_shape_or_total_loss_constraint\",\n";
out << " \"shape_priority\": \"storage_then_skin_then_shape_without_wellbore_then_optional_wellbore_recheck\",\n";
out << " \"shape_priority\": \"wellbore_parameter_sweep_then_non_wellbore_sweep_then_optional_wellbore_recheck\",\n";
out << " \"shape_metric\": \"pressure_and_derivative_log_slopes_81_points_lag_8\",\n";
out << " \"early_parallel_metric\": \"pressure_and_derivative_log_slopes_first_window_81_points_lag_8\",\n";
out << " \"early_gap_metric\": \"weighted_rms_log10_pressure_derivative_ratio_error_first_window_81_points\",\n";
out << " \"early_gap_bias_metric\": \"weighted_mean_signed_log10_gap_simulation_minus_target_first_window\",\n";
out << " \"early_direction_policy\": \"initial_gap_bias_positive_increase_negative_decrease; direction_shared_and_locked_for_storage_and_skin; recompute_only_at_wellbore_recheck_entry\",\n";
out << " \"early_initial_step_policy\": \"after_gap_direction_selection; storage_ratio_cap_1.05; skin_local_scale_fraction_0.02; no_cached_probe_acceptance_before_first_shape_improvement\",\n";
out << " \"early_initial_expansion_policy\": \"before_first_accepted_shape_improvement; if_shape_worsens_double_coordinate_step_from_unchanged_base_up_to_parameter_bound; retain_base_on_failure\",\n";
out << " \"early_wellbore_acceptance\": \"locked_gap_direction_and_early_shape_decrease\",\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\": \"non_wellbore_columns_once_at_sweep_entry; secant updates during sweep; full Jacobian rebuilt at total-stage entry\",\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 non-wellbore parameters; finish current parameter before selecting next; each parameter visited once\",\n";
out << " \"stage2_budget\": \"one parameter sweep; each parameter stops after 3 consecutive non-improving trials, a bound, or minimum step; existing solver failure limit retained\",\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; each wellbore parameter stops after 2 ineffective steps, independent of total-stage budget\",\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 << " \"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";
@ -1949,7 +1898,7 @@ 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% 才算有效改善。
@ -2147,7 +2096,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
if(m_shouldStop) return LM_USER_STOPPED;
// 第二阶段先测灵敏度,再逐个调完非井筒参数;每个参数只访问一次。
// 第二阶段先调井筒参数、再调其他参数;各组共用单参数扫描,每个参数只访问一次。
// 普通形状调整冻结井储、表皮,只按形状验收;总误差达标只在第三阶段判断。
bool shapeStage = true;
bool singleShapeStarted = false;
@ -2169,31 +2118,14 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
bool wellboreRecheckDone = false;
double earlyWellboreBaseline = current.breakdown.earlyParallelLoss;
double recheckShapeLimit = 0.0;
// 先井储、再表皮;回检沿用相同顺序和前期指标,不回到形状阶段反复循环。
// 井储、表皮在组内按预测下降量选序;回检沿用第一窗口指标和相同扫描规则。
bool earlyShapeStage = shapeStage && wellboreParameterCount > 0;
int earlyShapeParameter = storageColumn >= 0 ? 2 : 1;
double initialEarlyGapBias = trustRegionEarlyGapBias(current.breakdown);
double earlyGapDirection = trustRegionEarlyGapDirection(initialEarlyGapBias);
bool earlyShapeHasImproved = false;
auto announceEarlyGapDirection = [&]() {
// 两个参数共用本轮入口的方向;日志保留初始偏差,避免误读为探针响应方向。
const QString directionName = earlyGapDirection > 0.0 ? "increase" :
(earlyGapDirection < 0.0 ? "decrease" : "matched");
writeTraceRow(m_currentIteration, m_enabledParamIndices.indexOf(earlyShapeParameter),
"early_gap_direction", current.parameters, current.fitness, true, 0,
QString("%1_initial_gap_bias_%2").arg(directionName).arg(initialEarlyGapBias, 0, 'g', 12),
&current.breakdown);
emit logMessageGenerated(tr("Early gap guidance: %1, initial signed gap=%2, direction=%3; fixed for storage and skin.")
.arg(earlyShapeParameter == 2 ? tr("wellbore storage") : tr("skin"))
.arg(initialEarlyGapBias, 0, 'g', 6)
.arg(earlyGapDirection > 0.0 ? tr("increase") :
(earlyGapDirection < 0.0 ? tr("decrease") : tr("matched"))));
};
trustRadius = initialShapeTrustRadius;
auto parameterAllowedInStage = [&](int column) -> bool {
const int parameterIndex = m_enabledParamIndices[column];
// 候选和缓存灵敏度探针共用此限制,防止探针绕过形状阶段的冻结。
// 各子阶段只开放对应参数组,实际候选始终只改变选中的一列。
if(!shapeStage) return true;
if(earlyShapeStage) return parameterIndex == earlyShapeParameter;
if(earlyShapeStage) return parameterIndex == 1 || parameterIndex == 2;
return parameterIndex != 1 && parameterIndex != 2;
};
auto stageError = [&](const TrustRegionEvaluation& point) -> double {
@ -2203,10 +2135,8 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
auto acceptable = [&](const TrustRegionEvaluation& point, const TrustRegionEvaluation& base) -> bool {
if(!point.valid) return false;
if(earlyShapeStage) {
// 缓存探针也只能沿间距确定的方向接受;停止仍看原来的前期形状改善。
const int column = m_enabledParamIndices.indexOf(earlyShapeParameter);
return earlyGapDirection * (point.coordinates[column] - base.coordinates[column]) > 0.0 &&
point.breakdown.earlyParallelLoss < base.breakdown.earlyParallelLoss &&
// 第一窗口形状决定是否改善;补调保留原有的整体形状保护。
return point.breakdown.earlyParallelLoss < base.breakdown.earlyParallelLoss &&
(!wellboreRecheck || point.breakdown.shapeLoss <= recheckShapeLimit);
}
return shapeStage
@ -2215,8 +2145,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
: point.fitness < base.fitness;
};
auto acceptPoint = [&](const TrustRegionEvaluation& point) {
// 缓存探针和正式候选共用此标记;首次接受形状改善后,不再启用初始扩步探路。
if(earlyShapeStage) earlyShapeHasImproved = true;
// 只发布通过当前阶段目标验收的真实候选。
current = point;
publishAcceptedPoint(current);
restoreEvaluationState(current);
@ -2225,14 +2154,10 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
? 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(earlyShapeParameter == 2
? tr("Early adjustment: storage then skin; pressure-derivative gap selects direction, early shape error controls acceptance and stopping.")
: tr("Early adjustment: skin; pressure-derivative gap selects direction, early shape error controls acceptance and stopping."));
announceEarlyGapDirection();
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);
int fullDataRejections = 0;
bool samplingRefreshFailed = false;
@ -2296,7 +2221,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
consecutiveSolverFailures = 0;
stagnationConfirmationRequested = false;
globalFallbackAttempted = false;
trustRadius = shapeStage && !earlyShapeStage ? initialShapeTrustRadius : 0.12;
trustRadius = shapeStage ? initialShapeTrustRadius : 0.12;
damping = 0.01;
};
auto enterTotalStage = [&](const QString& reason) {
@ -2306,6 +2231,9 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
shapeStage = false;
earlyShapeStage = false;
wellboreRecheck = false;
activeShapeColumn = -1;
shapeCoordinateStep = 0.0;
consecutiveShapeRejections = 0;
effectiveImprovementBaseline = current.fitness;
reuseSensitivityForNextStage();
// 第二阶段只测各子阶段需要的列;所有出口都在当前点重建完整 J,不能把缺列模型带入整体 LM。
@ -2338,14 +2266,16 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
}
wellboreRecheck = true;
earlyShapeStage = true;
earlyShapeParameter = storageColumn >= 0 ? 2 : 1;
initialEarlyGapBias = trustRegionEarlyGapBias(current.breakdown);
earlyGapDirection = trustRegionEarlyGapDirection(initialEarlyGapBias);
earlyShapeHasImproved = false;
// 回检只约束形状:允许总误差上升,但保护其他参数已调好的整体形状。
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;
@ -2355,24 +2285,14 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
.arg(recheckShapeLimit, 0, 'g', 6));
writeTraceRow(m_currentIteration, -1, "stage_switch", current.parameters, current.fitness,
true, 0, "shape_to_wellbore_recheck", &current.breakdown);
announceEarlyGapDirection();
};
auto finishEarlyShapeStage = [&](const QString& reason) {
if(earlyShapeParameter == 2 && skinColumn >= 0) {
earlyShapeParameter = 1;
earlyShapeHasImproved = false;
effectiveImprovementBaseline = current.breakdown.earlyParallelLoss;
reuseSensitivityForNextStage();
// 井储已变,重测表皮的形状灵敏度和步幅,方向仍保持本轮入口的判断。
rebuildRequested = true;
rebuildReason = QT_TR_NOOP("refresh shape sensitivity at skin adjustment entry");
emit logMessageGenerated(tr("Wellbore storage adjustment ended: %1; now adjusting skin.").arg(reason));
writeTraceRow(m_currentIteration, -1, "stage_switch", current.parameters, current.fitness,
true, 0, wellboreRecheck ? "recheck_storage_to_skin" : "storage_to_skin", &current.breakdown);
announceEarlyGapDirection();
return;
}
if(m_shouldStop) return;
// 井筒参数整组完成后才切换目标,不能把旧组的活动参数或步长带入下一组。
activeShapeColumn = -1;
shapeCoordinateStep = 0.0;
consecutiveShapeRejections = 0;
if(wellboreRecheck) {
enterTotalStage(reason);
return;
@ -2404,11 +2324,12 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
for(int column = 0; column < dimensions; ++column) {
if(parameterAllowedInStage(column) && !shapeParameterFinished[column]) return;
}
finishShapeStage(tr("all shape parameters visited once"));
if(earlyShapeStage) finishEarlyShapeStage(tr("all wellbore parameters visited once"));
else finishShapeStage(tr("all shape parameters visited once"));
};
auto completeShapeSearchStep = [&](bool accepted) {
if(!shapeStage || earlyShapeStage || m_shouldStop) return;
// 只按本次真实下降控制实际步长;不再使用累计 10% 门槛提前结束整轮。
if(!shapeStage || m_shouldStop) return;
// 两组参数共用扩缩步规则:改善后放大,拒绝后从已接受点缩步重试。
++shapeStepCount;
consecutiveShapeRejections = accepted ? 0 : consecutiveShapeRejections + 1;
shapeCoordinateStep = qBound(-maximumShapeTrustRadius,
@ -2425,11 +2346,10 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
};
auto registerEffectiveImprovement = [&](double fitness) -> bool {
if(shapeStage && !earlyShapeStage) return false;
if(shapeStage) return false;
const double requiredImprovement = qMax(
shapeStage ? 1.0e-4 : effectiveAbsoluteImprovement,
qAbs(effectiveImprovementBaseline) *
(shapeStage ? 0.01 : effectiveRelativeImprovement));
effectiveAbsoluteImprovement,
qAbs(effectiveImprovementBaseline) * effectiveRelativeImprovement);
const double improvement = effectiveImprovementBaseline - fitness;
if(improvement < requiredImprovement) {
return false;
@ -2443,20 +2363,13 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
return true;
};
// 普通形状的失败试算缩步并累计当前参数的拒绝次数;
// 前期井储、表皮与整体 LM 沿用各自的无效次数处理。
// 第二阶段求解失败也进入当前参数的拒绝处理,第三阶段保留原 LM 停滞确认。
auto recordIneffectiveStep = [&]() -> bool {
if(shapeStage && !earlyShapeStage) {
if(shapeStage) {
completeShapeSearchStep(false);
return false;
}
++consecutiveIneffectiveSteps;
if(earlyShapeStage) {
// 一次拒绝只缩步,再给当前参数一次真实尝试;连续无改善才交给下一段。
if(consecutiveIneffectiveSteps >= 2)
finishEarlyShapeStage(tr("2 consecutive steps without effective early improvement"));
return false;
}
if(!globalFallbackAttempted) {
return false;
}
@ -2525,9 +2438,8 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
processPauseAndStop();
++column) {
const int parameterIndex = m_enabledParamIndices[column];
if(earlyShapeStage && parameterIndex != 1 && parameterIndex != 2) continue;
// 普通形状阶段跳过冻结的井储、表皮,避免无用正演;回检和整体阶段会重新差分。
if(shapeStage && !earlyShapeStage && !parameterAllowedInStage(column)) continue;
// 每组入口只测该组开放的参数;回检和整体阶段会重新差分。
if(shapeStage && !parameterAllowedInStage(column)) continue;
const double lower = m_parameterLower[parameterIndex];
const double upper = m_parameterUpper[parameterIndex];
if(upper <= lower) continue;
@ -2615,11 +2527,8 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
jacobianColumnValid[column] = true;
columnBuilt = true;
// 后半段形状探针仅用于灵敏度,正式调整必须经过单参数 LM 预测选优。
// 前期同时测井储和表皮,但缓存探针不能绕过当前子阶段的选参限制。
// 前期首次正式调整必须从小步开始,测形状灵敏度的大探针不能提前成为工作点。
if((!shapeStage || earlyShapeStage) &&
(!earlyShapeStage || earlyShapeHasImproved) &&
// 第二阶段探针只用于灵敏度;只有第三阶段保留直接接受缓存探针的规则。
if(!shapeStage &&
parameterAllowedInStage(column) && acceptable(probe, base) &&
(!bestProbe.valid || stageError(probe) < stageError(bestProbe))) {
bestProbe = probe;
@ -2640,12 +2549,12 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
}
}
// 冻结列成功不能掩盖所有可调形状列的求解失败;有效零梯度交给逐参数选步处理。
if(validColumnCount == 0 || (shapeStage && !earlyShapeStage && !hasActiveSensitivity) || m_shouldStop) {
if(validColumnCount == 0 || (shapeStage && !hasActiveSensitivity) || m_shouldStop) {
restoreEvaluationState(base);
return false;
}
// 前期井储/表皮和整体阶段仍可接受更优缓存探针;后半段形状只由 LM 选步。
// 只有整体阶段可接受更优缓存探针,第二阶段两组参数都由单参数 LM 选步。
// 所有列先基于同一个 base 建完,再用已知割线平移 Jacobian,避免边算边移动基点。
if(bestProbe.valid && bestProbeColumn >= 0) {
QVector<double> acceptedStep(dimensions, 0.0);
@ -2663,14 +2572,10 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
current.fitness,
true,
0,
wellboreRecheck ? "wellbore_recheck_accepted_cached_probe" :
(earlyShapeStage ? "early_shape_accepted_cached_probe" :
(shapeStage ? "shape_accepted_cached_probe" : "total_accepted_cached_probe")),
"total_accepted_cached_probe",
&current.breakdown);
emit logMessageGenerated(
(earlyShapeStage ? tr("Sensitivity probe accepted: early shape error=%1")
: (shapeStage ? tr("Sensitivity probe accepted: shape error=%1")
: tr("Sensitivity probe accepted: total error=%1")))
tr("Sensitivity probe accepted: total error=%1")
.arg(stageError(current), 0, 'e', 4));
} else {
restoreEvaluationState(current);
@ -2764,12 +2669,6 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
stopReason = LM_MAX_ITERATIONS;
break;
}
if(wellboreRecheck && current.breakdown.earlyParallelLoss <=
earlyWellboreBaseline + qMax(1.0e-4, 0.10 * earlyWellboreBaseline)) {
enterTotalStage(tr("wellbore recheck completed during sensitivity evaluation"));
--iteration;
continue;
}
// 差分探针不等于 LM 候选;重建后继续按当前阶段求步并真实评价,再确认停滞。
registerEffectiveImprovement(stageError(current));
}
@ -2792,9 +2691,8 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
}
const QVector<TrustRegionFisher> information = buildTrustRegionFisher(
objectiveJacobian, objectiveResidual, jacobianColumnValid, objectiveCoordinates);
const TrustRegionFisher& global = information[kAutoFitTimeWindowCount];
// 前期残差已含第一窗口权重,使用全局和,不能再次乘局部窗口权重。
const TrustRegionFisher& stepInformation = global;
const TrustRegionFisher& global = information[kAutoFitTimeWindowCount];
QVector<bool> stageColumnValid = jacobianColumnValid;
for(int column = 0; column < dimensions; ++column) {
if(!parameterAllowedInStage(column)) stageColumnValid[column] = false;
@ -2804,32 +2702,13 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
double predictedReduction = 0.0;
const int selectedWindow = earlyShapeStage ? 0 : -1;
// 前期只开放当前井储或表皮;后半段形状从有效自由列中选一个,整体 LM 仍联合求解。
// 第二阶段从当前组有效自由列中选一个,整体 LM 仍联合求解。
// 保留弱敏感、相关及边界列供预测比较;窗口只保留作误差诊断。
for(int column = 0; column < dimensions; ++column) {
if(stageColumnValid[column] && (earlyShapeStage || global.matrix[column][column] > 0.0))
if(stageColumnValid[column] && global.matrix[column][column] > 0.0)
selectedColumns.append(column);
}
if(earlyShapeStage) {
double earlyStepRadius = trustRadius;
if(!earlyShapeHasImproved && selectedColumns.size() == 1) {
// 首次调整沿用小步起调,形状变差时由后面的同向扩步循环处理。
const int column = selectedColumns[0];
const int parameterIndex = m_enabledParamIndices[column];
const double lower = m_parameterLower[parameterIndex];
const double upper = m_parameterUpper[parameterIndex];
const double smallStep = useTrustRegionLogScale(parameterIndex, lower, upper)
? qLn(1.05) / (qLn(upper) - qLn(lower))
: (parameterIndex == 1 ? 0.02 * qMax(0.1, qAbs(current.parameters[column]))
: 0.05 * qMax(1.0e-8, qAbs(current.parameters[column]))) / (upper - lower);
earlyStepRadius = qMin(trustRadius, smallStep);
}
if(selectedColumns.isEmpty() || !buildEarlyGapGuidedStep(
stepInformation, selectedColumns, current.coordinates, earlyGapDirection,
damping, earlyStepRadius, minimumCoordinateStep, &coordinateStep, &predictedReduction)) {
selectedColumns.clear();
}
} else if(shapeStage) {
if(shapeStage) {
// 首步由灵敏度选出尚未调过的参数;后续锁定方向,直接使用扩缩后的步长。
if(activeShapeColumn < 0) {
QVector<int> availableColumns;
@ -2848,12 +2727,13 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
"largest_predicted_reduction_unvisited", &current.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) break;
if(!shapeStage || earlyShapeStage != finishingEarlyShape) break;
}
--iteration;
continue;
@ -2887,12 +2767,6 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
// 当前阶段没有可行下降步时,收缩半径并进入原有重建/收敛处理。
if(selectedColumns.isEmpty()) {
if(earlyShapeStage) {
finishEarlyShapeStage(tr("no feasible early adjustment direction or parameter at bound"));
// 尚未求解候选,原迭代留给其余参数,不额外占用迭代或求解预算。
--iteration;
continue;
}
if(trustRadius <= minimumTrustRadius * 1.01 &&
modelRebuiltAtMinimumRadius) {
if(promoteSampling(false)) continue;
@ -2922,14 +2796,14 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
selectedParameterIndices << QString::number(m_enabledParamIndices[selectedColumns[i]]);
}
QString selectionName = (wellboreRecheck ? QString("recheck_") : QString()) + QString(earlyShapeStage
? (earlyShapeParameter == 2 ? "storage_parallel_" : "skin_parallel_") : (shapeStage ? "shape_" : "total_")) + (selectedWindow >= 0
? (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 && earlyShapeParameter == 2) {
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)
@ -2942,43 +2816,6 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
&candidate.curve,
&candidate.elapsedMs);
// 初始形状变差时先沿锁定方向跨大步探路,不立即缩步或累计无效次数。
// 始终以未移动的 current 为基点;每次翻倍并裁到边界,差解不发布、不接受。
bool earlyInitialExpanded = false;
bool earlyInitialExpansionAtBound = false;
while(earlyShapeStage && !earlyShapeHasImproved && candidate.valid &&
candidate.breakdown.earlyParallelLoss > current.breakdown.earlyParallelLoss &&
processPauseAndStop()) {
QVector<double> expandedStep;
double expandedPrediction = 0.0;
if(!buildExpandedTrustRegionStep(stepInformation, current.coordinates, coordinateStep,
1.0, &expandedStep, &expandedPrediction, false)) {
earlyInitialExpansionAtBound = true;
break;
}
writeTraceRow(m_currentIteration, selectedColumns[0], "early_initial_expansion",
candidate.parameters, candidate.fitness, true, candidate.elapsedMs,
"shape_worse_expand_same_direction", &candidate.breakdown);
restoreEvaluationState(current);
TrustRegionEvaluation expanded;
expanded.coordinates = current.coordinates;
for(int column = 0; column < dimensions; ++column)
expanded.coordinates[column] += expandedStep[column];
expanded.parameters = parametersFromCoordinates(expanded.coordinates);
const int column = selectedColumns[0];
emit logMessageGenerated(tr("Early shape initially worsened: %1, trial=%2 -> %3; expanding in the same direction.")
.arg(earlyShapeParameter == 2 ? tr("wellbore storage") : tr("skin"))
.arg(candidate.parameters[column], 0, 'g', 6)
.arg(expanded.parameters[column], 0, 'g', 6));
expanded.valid = evaluateTrustRegionPoint(expanded.parameters, &expanded.fitness,
&expanded.breakdown, &expanded.curve, &expanded.elapsedMs);
candidate = expanded;
coordinateStep = expandedStep;
predictedReduction = expandedPrediction;
stepNorm = qSqrt(trustRegionSquaredNorm(coordinateStep));
earlyInitialExpanded = true;
}
if(earlyInitialExpanded) selectionName += "_initial_expanded";
if(m_shouldStop) {
restoreEvaluationState(current);
stopReason = LM_USER_STOPPED;
@ -3003,7 +2840,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
"solver_invalid_" + selectionName,
nullptr);
restoreEvaluationState(current);
if(consecutiveSolverFailures >= 2 && (!shapeStage || earlyShapeStage)) {
if(consecutiveSolverFailures >= 2 && !shapeStage) {
rebuildRequested = true;
rebuildReason = QT_TR_NOOP("2 consecutive solver failures");
}
@ -3048,7 +2885,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
const bool poorPrediction = predictedReduction > predictionFloor &&
(!isFiniteNumber(reductionRatio) || reductionRatio < 0.25);
consecutivePoorPredictions = poorPrediction ? consecutivePoorPredictions + 1 : 0;
if(consecutivePoorPredictions >= 2 && (!shapeStage || earlyShapeStage)) {
if(consecutivePoorPredictions >= 2 && !shapeStage) {
rebuildRequested = true;
rebuildReason = QT_TR_NOOP("2 consecutive steps with actual improvement below 25% of prediction");
}
@ -3060,7 +2897,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
fullDataRejections = 0;
}
QString componentName = earlyShapeStage
? (earlyShapeParameter == 2 ? "storage_parallel" : "skin_parallel") : (shapeStage ? "shape" : "total");
? (m_enabledParamIndices[selectedColumns[0]] == 2 ? "storage_parallel" : "skin_parallel") : (shapeStage ? "shape" : "total");
if(accepted) {
// 当前阶段接受候选后同步发布参数、曲线和诊断。
@ -3069,8 +2906,8 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
++acceptedSinceRebuild;
consecutiveRejectedSteps = 0;
// 井筒调整及第三阶段保留原 LM 控制;逐参数粗调在步末直接扩缩实际步长。
if(!shapeStage || earlyShapeStage) {
// 第三阶段保留原 LM 控制;第二阶段两组都在步末直接扩缩实际步长。
if(!shapeStage) {
if(reductionRatio > 0.75) {
damping = qMax(1.0e-8, damping * 0.5);
if(stepNorm >= trustRadius * 0.8) {
@ -3086,7 +2923,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
}
}
if(acceptedSinceRebuild >= 10 && !rebuildRequested && (!shapeStage || earlyShapeStage)) {
if(acceptedSinceRebuild >= 10 && !rebuildRequested && !shapeStage) {
rebuildRequested = true;
rebuildReason = QT_TR_NOOP("10 accepted steps since last rebuild");
}
@ -3138,11 +2975,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
emit progressUpdated(shapeStage ? -1 : totalIterations, m_globalBestFitness);
// 先记录当前试调结果,再发布阶段切换,避免日志显示为未试调就结束。
if(earlyInitialExpansionAtBound && !accepted) {
finishEarlyShapeStage(tr("initial same-direction expansion reached the parameter bound without early shape improvement"));
continue;
}
if(shapeStage && !earlyShapeStage) {
if(shapeStage) {
completeShapeSearchStep(accepted);
continue;
}

Loading…
Cancel
Save