Compare commits

..

2 Commits

Author SHA1 Message Date
lvjunjie e3d49941bc fix(nmNum): 每次打开拟合窗口时重建参数范围
- 每次打开自动拟合窗口时,按当前模型参数重新生成上下限
- 移除沿用和校正历史范围的分支,复用现有范围计算规则
- 通过编译语法检查和差异检查
3 days ago
lvjunjie 529655f205 refactor(nmNum): 将拟合流程改为高度预调和双目标联合 LM
- 保留渗透率高度预调,移除逐参数形状扫描及井储、表皮复查
- 联合 LM 先优化整体形状误差,再优化总误差,共用步长、阻尼和停滞规则
- 形状达标、确认停滞或达到额度后切换目标,两段独立计算迭代和求解额度
- 更新拟合日志和策略元数据,通过编译语法检查及联合 LM 行为验证
3 days ago

@ -62,7 +62,6 @@ private:
void updateRangeForParameter(int parameterIndex, double centerValue); void updateRangeForParameter(int parameterIndex, double centerValue);
void setParameterRange(int parameterIndex, double minValue, double maxValue); void setParameterRange(int parameterIndex, double minValue, double maxValue);
bool getPhysicalParameterRange(int parameterIndex, double& minValue, double& maxValue); bool getPhysicalParameterRange(int parameterIndex, double& minValue, double& maxValue);
void normalizeSavedParameterRanges();
bool validateParameterTable(QString& errorMessage, int parameterIndex = -1); bool validateParameterTable(QString& errorMessage, int parameterIndex = -1);
void startAutoFitting(const QVector<QVector<double>>& targetData, const QStringList& selectedParams, const QString& targetWellName); void startAutoFitting(const QVector<QVector<double>>& targetData, const QStringList& selectedParams, const QString& targetWellName);

@ -632,30 +632,6 @@ static bool buildTrustRegionFisherStep(
return projectAndPredict(); return projectAndPredict();
} }
// 为每个可调参数独立求解 LM 步,按边界投影后的预测下降量选择一个参数。
static bool buildBestSingleParameterStep(const TrustRegionFisher& information,
const QVector<int>& available, const QVector<double>& coordinates,
double damping, double trustRadius, double minimumStep,
QVector<int>* selected, QVector<double>* step, double* predictedReduction)
{
selected->clear();
step->fill(0.0, coordinates.size());
*predictedReduction = 0.0;
for(int i = 0; i < available.size(); ++i) {
QVector<int> singleColumn(1, available[i]);
QVector<double> 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。这样模型吸收了刚得到的真实变化,又不必立即逐参数重新试算。 // Jacobian。这样模型吸收了刚得到的真实变化,又不必立即逐参数重新试算。
static void updateTrustRegionJacobian( static void updateTrustRegionJacobian(
@ -1052,35 +1028,21 @@ void nmCalculationAutoFitLM::writeTraceMetaFile()
QTextStream out(&metaFile); QTextStream out(&metaFile);
out << "{\n"; out << "{\n";
out << " \"schema_version\": 50,\n"; out << " \"schema_version\": 52,\n";
out << " \"strategy\": \"permeability_height_then_shape_then_joint_lm\",\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 << " \"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 << " \"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 << " \"shape_parameter_selection\": \"all_valid_free_columns_joint_LM; no_single_parameter_sweep_or_wellbore_recheck\",\n";
out << " \"early_gap_metric\": \"weighted_rms_log10_pressure_derivative_ratio_error_first_window_shared_sampling_grid\",\n"; out << " \"shape_acceptance\": \"strict_full_shape_loss_decrease; no_total_loss_constraint\",\n";
out << " \"early_gap_bias_metric\": \"weighted_mean_signed_log10_gap_simulation_minus_target_first_window\",\n"; out << " \"lm_step_policy\": \"same_joint_LM_damping_trust_radius_secant_updates_and_sensitivity_rebuilds_in_both_phases\",\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 << " \"lm_phase_switch\": \"shape_target_reached_or_confirmed_stagnation_or_phase_budget_exhausted_then_total; user_stop_and_consecutive_solver_failure_abort\",\n";
out << " \"early_initial_step_policy\": \"same_as_later_shape_sweep; initial_LM_coordinate_radius_0.24; no_cached_probe_acceptance\",\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 << " \"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 << " \"lm_effective_improvement\": \"max(0.00001,0.002*baseline_stage_error); 3_ineffective_steps_trigger_stagnation_confirmation\",\n";
out << " \"early_wellbore_acceptance\": \"first_window_shape_decrease; no_gap_direction_constraint; wellbore_recheck_retains_full_shape_guard\",\n"; out << " \"iteration_count_scope\": \"max_iterations_applies_independently_to_each_LM_phase; trace_iteration_is_cumulative\",\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 << " \"total_stage_shape_constraint\": false,\n"; out << " \"total_stage_shape_constraint\": false,\n";
out << " \"total_parameter_selection\": \"all_valid_free_columns\",\n"; out << " \"total_parameter_selection\": \"all_valid_free_columns\",\n";
out << " \"skin_difference_policy\": \"local_scale_in_all_stages\",\n"; out << " \"skin_difference_policy\": \"local_scale_fraction_0.02_in_both_LM_phases\",\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 << " \"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 << " \"difference_failure_policy\": \"halve_before_opposite_direction\",\n";
out << " \"trace_type\": \"finite_difference_lm_trust_region\",\n"; out << " \"trace_type\": \"finite_difference_lm_trust_region\",\n";
out << " \"run_id\": " << jsonEscape(m_traceRunId) << ",\n"; out << " \"run_id\": " << jsonEscape(m_traceRunId) << ",\n";
@ -1818,10 +1780,11 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
return LM_OPTIMIZATION_FAILED; return LM_OPTIMIZATION_FAILED;
} }
// 用户迭代设置只约束第三阶段;真实评价额度也在进入该阶段时独立起算。 // 形状与总误差共用原联合 LM,每段独立使用原迭代和求解额度。
// 前期调整按改善情况结束,不能提前消耗掉最终联合 LM 的迭代和求解机会。 const int phaseEvaluationBudget = qMax(dimensions + 2, qMax(20, m_maxIterations * 3));
const int totalEvaluationBudget = qMax(dimensions + 2, qMax(20, m_maxIterations * 3)); int maximumEvaluations = 0;
int maximumEvaluations = m_totalEvaluations + totalEvaluationBudget; int phaseIterations = 0;
int shapeIterations = 0;
int totalIterations = 0; int totalIterations = 0;
// 下列步长均位于归一化内部坐标:0.04 表示参数范围的 4%,信赖半径 // 下列步长均位于归一化内部坐标:0.04 表示参数范围的 4%,信赖半径
// 限制一次联合移动的二范数,相关性门槛用于排除响应近乎共线的参数。 // 限制一次联合移动的二范数,相关性门槛用于排除响应近乎共线的参数。
@ -1829,9 +1792,6 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
const double minimumCoordinateStep = 1.0e-5; const double minimumCoordinateStep = 1.0e-5;
const double minimumTrustRadius = 2.0e-3; const double minimumTrustRadius = 2.0e-3;
const double maximumTrustRadius = 0.30; const double maximumTrustRadius = 0.30;
// 第二阶段两组逐参数调整共用较大步幅;第三阶段继续使用原来的半径和预测验收规则。
const double initialShapeTrustRadius = 0.24;
const double maximumShapeTrustRadius = 0.60;
// 误差下降至少达到绝对 1e-5 且相对当前有效基准 0.2% 才算有效改善。 // 误差下降至少达到绝对 1e-5 且相对当前有效基准 0.2% 才算有效改善。
// 更小的下降仍保留为最佳解,但不能反复清除停滞状态、延长拟合时间。 // 更小的下降仍保留为最佳解,但不能反复清除停滞状态、延长拟合时间。
const double effectiveRelativeImprovement = 2.0e-3; const double effectiveRelativeImprovement = 2.0e-3;
@ -1856,7 +1816,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
StopReasonLM stopReason = LM_MAX_ITERATIONS; StopReasonLM stopReason = LM_MAX_ITERATIONS;
// jacobian 的行对应固定采样的残差,列对应用户勾选的参数。 // jacobian 的行对应固定采样的残差,列对应用户勾选的参数。
// Fisher 按当前阶段取数值或形状残差行;冻结列不参与形状差分,整体阶段重建全部列。 // Fisher 按当前目标取数值或形状残差行,两段均使用所有勾选参数。
QVector<QVector<double> > jacobian; QVector<QVector<double> > jacobian;
QVector<bool> jacobianColumnValid(dimensions, false); QVector<bool> jacobianColumnValid(dimensions, false);
@ -1960,9 +1920,9 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
emit progressUpdated(-1, current.fitness); emit progressUpdated(-1, current.fitness);
emit logMessageGenerated(tr("=== Starting LM Main Loop ===")); emit logMessageGenerated(tr("=== Starting LM Main Loop ==="));
emit logMessageGenerated( 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(current.fitness, 0, 'e', 4)
.arg(totalEvaluationBudget)); .arg(phaseEvaluationBudget));
// 高度预调整只改变用户勾选的渗透率。ln(k) 的初始变化由有符号高度差 // 高度预调整只改变用户勾选的渗透率。ln(k) 的初始变化由有符号高度差
// 给出,真实求解后用割线估计修正;拒绝时缩步,不让其他参数补偿高度。 // 给出,真实求解后用割线估计修正;拒绝时缩步,不让其他参数补偿高度。
@ -2027,211 +1987,25 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
if(m_shouldStop) return LM_USER_STOPPED; if(m_shouldStop) return LM_USER_STOPPED;
// 第二阶段先调井筒参数、再调其他参数;各组共用单参数扫描,每个参数只访问一次。 // 原逐参数第二阶段已移除;同一个联合 LM 先匹配整体形状,再匹配总误差。
// 普通形状调整冻结井储、表皮,只按形状验收;总误差达标只在第三阶段判断。
bool shapeStage = true; bool shapeStage = true;
bool singleShapeStarted = false;
int shapeStepCount = 0;
QVector<bool> 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 { auto stageError = [&](const TrustRegionEvaluation& point) -> double {
if(earlyShapeStage) return point.breakdown.earlyParallelLoss;
return shapeStage ? point.breakdown.shapeLoss : point.fitness; return shapeStage ? point.breakdown.shapeLoss : point.fitness;
}; };
auto acceptable = [&](const TrustRegionEvaluation& point, const TrustRegionEvaluation& base) -> bool { auto acceptable = [&](const TrustRegionEvaluation& point, const TrustRegionEvaluation& base) -> bool {
if(!point.valid) return false; return point.valid && stageError(point) < stageError(base);
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;
}; };
auto acceptPoint = [&](const TrustRegionEvaluation& point) { auto acceptPoint = [&](const TrustRegionEvaluation& point) {
// 只发布通过当前阶段目标验收的真实候选。 // 参数、曲线和诊断同步发布,两个目标仅改变候选验收指标。
current = point; current = point;
publishAcceptedPoint(current); publishAcceptedPoint(current);
restoreEvaluationState(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); double effectiveImprovementBaseline = stageError(current);
emit logMessageGenerated(tr("LM sampling: %1 points (%2 intervals per log-time decade).") emit logMessageGenerated(tr("LM sampling: %1 points (%2 intervals per log-time decade).")
.arg(current.breakdown.residualVector.size() / 2).arg(kAutoFitIntervalsPerDecade)); .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", &current.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", &current.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", &current.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", &current.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, &current.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), &current.breakdown);
if(consecutiveShapeRejections >= maximumShapeRejections)
finishShapeParameter("3_consecutive_non_improving_trials");
else if(qAbs(shapeCoordinateStep) < minimumCoordinateStep)
finishShapeParameter("minimum_coordinate_step");
};
auto registerEffectiveImprovement = [&](double fitness) -> bool { auto registerEffectiveImprovement = [&](double fitness) -> bool {
if(shapeStage) return false;
const double requiredImprovement = qMax( const double requiredImprovement = qMax(
effectiveAbsoluteImprovement, effectiveAbsoluteImprovement,
qAbs(effectiveImprovementBaseline) * effectiveRelativeImprovement); qAbs(effectiveImprovementBaseline) * effectiveRelativeImprovement);
@ -2248,12 +2022,8 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
return true; return true;
}; };
// 第二阶段求解失败也进入当前参数的拒绝处理,第三阶段保留原 LM 停滞确认。 // 两个目标均沿用原 LM 的有效改善门槛及重建后停滞确认。
auto recordIneffectiveStep = [&]() -> bool { auto recordIneffectiveStep = [&]() -> bool {
if(shapeStage) {
completeShapeSearchStep(false);
return false;
}
++consecutiveIneffectiveSteps; ++consecutiveIneffectiveSteps;
if(!globalFallbackAttempted) { if(!globalFallbackAttempted) {
return false; return false;
@ -2279,23 +2049,17 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
}; };
emit logMessageGenerated( 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") "%3 consecutive ineffective steps trigger convergence confirmation")
.arg(effectiveAbsoluteImprovement, 0, 'e', 2) .arg(effectiveAbsoluteImprovement, 0, 'e', 2)
.arg(effectiveRelativeImprovement * 100.0, 0, 'f', 2) .arg(effectiveRelativeImprovement * 100.0, 0, 'f', 2)
.arg(maximumIneffectiveSteps)); .arg(maximumIneffectiveSteps));
if(!shapeStage && current.fitness < m_targetError) {
return LM_TARGET_ACHIEVED;
}
// 在同一个真实工作点逐参数做单边差分。失败先在原方向缩步,再反向尝试; // 在同一个真实工作点逐参数做单边差分。失败先在原方向缩步,再反向尝试;
// 正常情况下每列仍只需一次真实求解,重试也计入总评价预算。 // 正常情况下每列仍只需一次真实求解,重试也计入总评价预算。
auto rebuildSensitivity = [&]() -> bool { auto rebuildSensitivity = [&]() -> bool {
// 第二阶段差分不受第三阶段额度限制;每列仍只有有限的缩步和反向重试。 // 形状段也受原 LM 求解额度约束,差分范围及重试规则与总误差段相同。
const int evaluationLimit = shapeStage ? (std::numeric_limits<int>::max)() : maximumEvaluations; const int evaluationLimit = maximumEvaluations;
const int sensitivityColumnCount = shapeStage
? (earlyShapeStage ? wellboreParameterCount : dimensions - wellboreParameterCount) : dimensions;
const TrustRegionEvaluation base = current; const TrustRegionEvaluation base = current;
const QVector<double> baseResidual = trustRegionFullResidual(base.breakdown); const QVector<double> baseResidual = trustRegionFullResidual(base.breakdown);
const int residualCount = baseResidual.size(); const int residualCount = baseResidual.size();
@ -2310,11 +2074,9 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
TrustRegionEvaluation bestProbe; TrustRegionEvaluation bestProbe;
int bestProbeColumn = -1; int bestProbeColumn = -1;
double bestProbeDelta = 0.0; double bestProbeDelta = 0.0;
// 第二阶段允许内部坐标范围的 10% 及完整信赖半径,避免较大比例试探被旧上限截小。 // 两段共用原 LM 的局部差分尺度,避免目标切换同时改变灵敏度算法。
// 第三阶段仍为 4% 及半个信赖半径;均保留 0.5% 下限以减少数值噪声影响。
const double finiteDifferenceStep = qMin( const double finiteDifferenceStep = qMin(
shapeStage ? 0.10 : sensitivityStep, sensitivityStep, qMax(5.0e-3, trustRadius * 0.5));
qMax(5.0e-3, trustRadius * (shapeStage ? 1.0 : 0.5)));
for(int column = 0; for(int column = 0;
column < dimensions && column < dimensions &&
@ -2322,19 +2084,14 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
processPauseAndStop(); processPauseAndStop();
++column) { ++column) {
const int parameterIndex = m_enabledParamIndices[column]; const int parameterIndex = m_enabledParamIndices[column];
// 每组入口只测该组开放的参数;回检和整体阶段会重新差分。
if(shapeStage && !parameterAllowedInStage(column)) continue;
const double lower = m_parameterLower[parameterIndex]; const double lower = m_parameterLower[parameterIndex];
const double upper = m_parameterUpper[parameterIndex]; const double upper = m_parameterUpper[parameterIndex];
if(upper <= lower) continue; if(upper <= lower) continue;
// 第二阶段扩大形状试探范围;第三阶段仍沿用原来的局部差分尺度。 // 表皮包含零和负值,沿用原 LM 按局部物理尺度扰动的方式。
// 表皮包含零和负值,按物理尺度扰动;正值参数按对数比例限制幅度。
double localStep = finiteDifferenceStep; double localStep = finiteDifferenceStep;
if(parameterIndex == 1) { if(parameterIndex == 1) {
const double skinStepFraction = shapeStage ? 0.10 : 0.02; localStep = qMin(localStep, 0.02 * qMax(0.1, qAbs(base.parameters[column])) / (upper - lower));
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)));
} }
double positiveRoom = 1.0 - base.coordinates[column]; double positiveRoom = 1.0 - base.coordinates[column];
double negativeRoom = base.coordinates[column]; double negativeRoom = base.coordinates[column];
@ -2382,7 +2139,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
probe.fitness, probe.fitness,
probe.valid, probe.valid,
probe.elapsedMs, probe.elapsedMs,
(wellboreRecheck ? "wellbore_recheck_" : (shapeStage ? "shape_" : "total_")) + decision, (shapeStage ? "shape_" : "total_") + decision,
probe.valid ? &probe.breakdown : nullptr); probe.valid ? &probe.breakdown : nullptr);
if(!probe.valid) { if(!probe.valid) {
@ -2411,9 +2168,8 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
jacobianColumnValid[column] = true; jacobianColumnValid[column] = true;
columnBuilt = true; columnBuilt = true;
// 第二阶段探针只用于灵敏度;只有第三阶段保留直接接受缓存探针的规则。 // 沿用原 LM 的缓存探针验收,仅按当前段的目标选择更优探针。
if(!shapeStage && if(acceptable(probe, base) &&
parameterAllowedInStage(column) && acceptable(probe, base) &&
(!bestProbe.valid || stageError(probe) < stageError(bestProbe))) { (!bestProbe.valid || stageError(probe) < stageError(bestProbe))) {
bestProbe = probe; bestProbe = probe;
bestProbeColumn = column; bestProbeColumn = column;
@ -2425,20 +2181,18 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
} }
int validColumnCount = 0; int validColumnCount = 0;
bool hasActiveSensitivity = false;
for(int i = 0; i < jacobianColumnValid.size(); ++i) { for(int i = 0; i < jacobianColumnValid.size(); ++i) {
if(jacobianColumnValid[i]) { if(jacobianColumnValid[i]) {
++validColumnCount; ++validColumnCount;
if(parameterAllowedInStage(i)) hasActiveSensitivity = true;
} }
} }
// 冻结列成功不能掩盖所有可调形状列的求解失败;有效零梯度交给逐参数选步处理。 // 两段都要求至少一列有效灵敏度,零梯度仍交给原 LM 停滞判断。
if(validColumnCount == 0 || (shapeStage && !hasActiveSensitivity) || m_shouldStop) { if(validColumnCount == 0 || m_shouldStop) {
restoreEvaluationState(base); restoreEvaluationState(base);
return false; return false;
} }
// 只有整体阶段可接受更优缓存探针,第二阶段两组参数都由单参数 LM 选步。 // 两段均保留原 LM 接受更优缓存探针的行为。
// 所有列先基于同一个 base 建完,再用已知割线平移 Jacobian,避免边算边移动基点。 // 所有列先基于同一个 base 建完,再用已知割线平移 Jacobian,避免边算边移动基点。
if(bestProbe.valid && bestProbeColumn >= 0) { if(bestProbe.valid && bestProbeColumn >= 0) {
QVector<double> acceptedStep(dimensions, 0.0); QVector<double> acceptedStep(dimensions, 0.0);
@ -2456,10 +2210,10 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
current.fitness, current.fitness,
true, true,
0, 0,
"total_accepted_cached_probe", shapeStage ? "shape_accepted_cached_probe" : "total_accepted_cached_probe",
&current.breakdown); &current.breakdown);
emit logMessageGenerated( emit logMessageGenerated(
tr("Sensitivity probe accepted: total error=%1") tr("Sensitivity probe accepted: current objective error=%1")
.arg(stageError(current), 0, 'e', 4)); .arg(stageError(current), 0, 'e', 4));
} else { } else {
restoreEvaluationState(current); restoreEvaluationState(current);
@ -2477,61 +2231,65 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
emit logMessageGenerated( emit logMessageGenerated(
tr("Sensitivity model rebuilt: %1/%2 parameter columns valid") tr("Sensitivity model rebuilt: %1/%2 parameter columns valid")
.arg(validColumnCount) .arg(validColumnCount)
.arg(sensitivityColumnCount)); .arg(dimensions));
return true; return true;
}; };
int completedIterations = 0; int completedIterations = 0;
for(int iteration = 0; for(int phase = 0; phase < 2 && !m_shouldStop; ++phase) {
!m_shouldStop && (shapeStage || shapeStage = phase == 0;
(totalIterations < m_maxIterations && m_totalEvaluations < maximumEvaluations)); 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", &current.breakdown);
for(int iteration = completedIterations;
!m_shouldStop && phaseIterations < m_maxIterations && m_totalEvaluations < maximumEvaluations;
++iteration) { ++iteration) {
m_currentIteration = iteration; m_currentIteration = iteration;
completedIterations = iteration + 1; completedIterations = iteration + 1;
if(!processPauseAndStop()) break;
if(!processPauseAndStop()) { if(stageError(current) < m_targetError) {
stopReason = LM_TARGET_ACHIEVED;
break; 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) { if(rebuildRequested) {
emit logMessageGenerated(tr("Rebuilding sensitivity model: %1").arg(tr(rebuildReason))); emit logMessageGenerated(tr("Rebuilding sensitivity model: %1").arg(tr(rebuildReason)));
writeTraceRow(m_currentIteration, -1, "sensitivity_rebuild", current.parameters, writeTraceRow(m_currentIteration, -1, "sensitivity_rebuild", current.parameters,
current.fitness, true, 0, rebuildReason, &current.breakdown); current.fitness, true, 0, rebuildReason, &current.breakdown);
if(!rebuildSensitivity()) { 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 stopReason = m_shouldStop
? LM_USER_STOPPED ? LM_USER_STOPPED
: LM_LOCAL_OPTIMUM; : LM_LOCAL_OPTIMUM;
break; break;
} }
if(!shapeStage && current.fitness < m_targetError) { if(stageError(current) < m_targetError) {
stopReason = LM_TARGET_ACHIEVED; stopReason = LM_TARGET_ACHIEVED;
break; break;
} }
if(!shapeStage && m_totalEvaluations >= maximumEvaluations) { if(m_totalEvaluations >= maximumEvaluations) {
stopReason = LM_MAX_ITERATIONS; stopReason = LM_MAX_ITERATIONS;
break; break;
} }
@ -2539,16 +2297,11 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
registerEffectiveImprovement(stageError(current)); registerEffectiveImprovement(stageError(current));
} }
// 第三阶段独立计数;方向不可行也消耗一次局部尝试,不能无限缩步循环。 // 两段分别计数;无可行方向也消耗一次尝试,避免无限缩步重建。
if(!shapeStage) ++totalIterations; ++phaseIterations;
// 每次从最新 J 和当前阶段残差重算全局信息,供单参数或联合 LM 求步。 const QVector<double> objectiveResidual = shapeStage
const QVector<double> objectiveResidual = earlyShapeStage ? current.breakdown.shapeResiduals : current.breakdown.residualVector;
? current.breakdown.earlyParallelResiduals const int rowOffset = shapeStage ? current.breakdown.residualVector.size() : 0;
: (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<QVector<double> > objectiveJacobian = jacobian.mid(rowOffset, objectiveResidual.size()); const QVector<QVector<double> > objectiveJacobian = jacobian.mid(rowOffset, objectiveResidual.size());
QVector<double> objectiveCoordinates; QVector<double> objectiveCoordinates;
if(shapeStage) { if(shapeStage) {
@ -2560,79 +2313,23 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
} }
const QVector<TrustRegionFisher> information = buildTrustRegionFisher( const QVector<TrustRegionFisher> information = buildTrustRegionFisher(
objectiveJacobian, objectiveResidual, jacobianColumnValid, objectiveCoordinates); objectiveJacobian, objectiveResidual, jacobianColumnValid, objectiveCoordinates);
// 前期残差已含第一窗口权重,使用全局和,不能再次乘局部窗口权重。 // 两段都使用当前目标的全局信息,不按时间窗口另行选参。
const TrustRegionFisher& global = information[kAutoFitTimeWindowCount]; const TrustRegionFisher& global = information[kAutoFitTimeWindowCount];
QVector<bool> stageColumnValid = jacobianColumnValid;
for(int column = 0; column < dimensions; ++column) {
if(!parameterAllowedInStage(column)) stageColumnValid[column] = false;
}
QVector<int> selectedColumns; QVector<int> selectedColumns;
QVector<double> coordinateStep; QVector<double> coordinateStep;
double predictedReduction = 0.0; double predictedReduction = 0.0;
const int selectedWindow = earlyShapeStage ? 0 : -1;
// 第二阶段从当前组有效自由列中选一个,整体 LM 仍联合求解。 // 两段都联合调整全部有效自由参数,只切换残差及其对应的 Jacobian 行。
// 保留弱敏感、相关及边界列供预测比较;窗口只保留作误差诊断。
for(int column = 0; column < dimensions; ++column) { for(int column = 0; column < dimensions; ++column) {
if(stageColumnValid[column] && global.matrix[column][column] > 0.0) if(jacobianColumnValid[column] && global.matrix[column][column] > 0.0)
selectedColumns.append(column); selectedColumns.append(column);
} }
if(shapeStage) {
// 首步由灵敏度选出尚未调过的参数;后续锁定方向,直接使用扩缩后的步长。
if(activeShapeColumn < 0) {
QVector<int> availableColumns;
for(int i = 0; i < selectedColumns.size(); ++i) {
if(!shapeParameterFinished[selectedColumns[i]]) availableColumns.append(selectedColumns[i]);
}
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", &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 != finishingEarlyShape) break;
}
--iteration;
continue;
}
} 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;
}
coordinateStep[activeShapeColumn] = shapeCoordinateStep;
predictedReduction = -global.gradient[activeShapeColumn] * shapeCoordinateStep
- 0.5 * global.matrix[activeShapeColumn][activeShapeColumn]
* shapeCoordinateStep * shapeCoordinateStep;
}
} else {
// 第三阶段保持全部有效自由参数的联合 LM 调整。
globalFallbackAttempted = true; globalFallbackAttempted = true;
if(selectedColumns.isEmpty() || !buildTrustRegionFisherStep( if(selectedColumns.isEmpty() || !buildTrustRegionFisherStep(
global, selectedColumns, current.coordinates, damping, global, selectedColumns, current.coordinates, damping,
trustRadius, minimumCoordinateStep, &coordinateStep, &predictedReduction)) { trustRadius, minimumCoordinateStep, &coordinateStep, &predictedReduction)) {
selectedColumns.clear(); selectedColumns.clear();
} }
}
// 当前阶段没有可行下降步时,收缩半径并进入原有重建/收敛处理。 // 当前阶段没有可行下降步时,收缩半径并进入原有重建/收敛处理。
if(selectedColumns.isEmpty()) { if(selectedColumns.isEmpty()) {
@ -2662,20 +2359,12 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
for(int i = 0; i < selectedColumns.size(); ++i) { for(int i = 0; i < selectedColumns.size(); ++i) {
selectedParameterIndices << QString::number(m_enabledParamIndices[selectedColumns[i]]); selectedParameterIndices << QString::number(m_enabledParamIndices[selectedColumns[i]]);
} }
QString selectionName = (wellboreRecheck ? QString("recheck_") : QString()) + QString(earlyShapeStage QString selectionName = QString(shapeStage ? "shape_global_params_" : "total_global_params_") +
? (m_enabledParamIndices[selectedColumns[0]] == 2 ? "storage_parallel_" : "skin_parallel_") : (shapeStage ? "shape_" : "total_")) + (selectedWindow >= 0 selectedParameterIndices.join("_");
? QString("window_%1").arg(selectedWindow + 1) : QString("global")) +
"_params_" + selectedParameterIndices.join("_");
TrustRegionEvaluation candidate; TrustRegionEvaluation candidate;
candidate.coordinates = candidateCoordinates; candidate.coordinates = candidateCoordinates;
candidate.parameters = parametersFromCoordinates(candidate.coordinates); 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.valid = evaluateTrustRegionPoint(
candidate.parameters, candidate.parameters,
&candidate.fitness, &candidate.fitness,
@ -2707,7 +2396,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
"solver_invalid_" + selectionName, "solver_invalid_" + selectionName,
nullptr); nullptr);
restoreEvaluationState(current); restoreEvaluationState(current);
if(consecutiveSolverFailures >= 2 && !shapeStage) { if(consecutiveSolverFailures >= 2) {
rebuildRequested = true; rebuildRequested = true;
rebuildReason = QT_TR_NOOP("2 consecutive solver failures"); rebuildReason = QT_TR_NOOP("2 consecutive solver failures");
} }
@ -2739,9 +2428,8 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
// reductionRatio 衡量局部线性模型的可信度:接近 1 表示预测准确; // reductionRatio 衡量局部线性模型的可信度:接近 1 表示预测准确;
// 值较小表示虽然可能下降,但模型低估了非线性,需要收紧下一步。 // 值较小表示虽然可能下降,但模型低估了非线性,需要收紧下一步。
const double objectiveEnergy = 0.5 * trustRegionSquaredNorm(objectiveResidual); const double objectiveEnergy = 0.5 * trustRegionSquaredNorm(objectiveResidual);
const QVector<double> candidateResidual = earlyShapeStage const QVector<double> candidateResidual = shapeStage
? candidate.breakdown.earlyParallelResiduals ? candidate.breakdown.shapeResiduals : candidate.breakdown.residualVector;
: (shapeStage ? candidate.breakdown.shapeResiduals : candidate.breakdown.residualVector);
const double candidateEnergy = 0.5 * trustRegionSquaredNorm(candidateResidual); const double candidateEnergy = 0.5 * trustRegionSquaredNorm(candidateResidual);
double actualReduction = objectiveEnergy - candidateEnergy; double actualReduction = objectiveEnergy - candidateEnergy;
double reductionRatio = predictedReduction > 1.0e-14 ? actualReduction / predictedReduction : 0.0; double reductionRatio = predictedReduction > 1.0e-14 ? actualReduction / predictedReduction : 0.0;
@ -2751,13 +2439,11 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
const bool poorPrediction = predictedReduction > predictionFloor && const bool poorPrediction = predictedReduction > predictionFloor &&
(!isFiniteNumber(reductionRatio) || reductionRatio < 0.25); (!isFiniteNumber(reductionRatio) || reductionRatio < 0.25);
consecutivePoorPredictions = poorPrediction ? consecutivePoorPredictions + 1 : 0; consecutivePoorPredictions = poorPrediction ? consecutivePoorPredictions + 1 : 0;
if(consecutivePoorPredictions >= 2 && !shapeStage) { if(consecutivePoorPredictions >= 2) {
rebuildRequested = true; rebuildRequested = true;
rebuildReason = QT_TR_NOOP("2 consecutive steps with actual improvement below 25% of prediction"); rebuildReason = QT_TR_NOOP("2 consecutive steps with actual improvement below 25% of prediction");
} }
bool accepted = acceptable(candidate, current); bool accepted = acceptable(candidate, current);
QString componentName = earlyShapeStage
? (m_enabledParamIndices[selectedColumns[0]] == 2 ? "storage_parallel" : "skin_parallel") : (shapeStage ? "shape" : "total");
if(accepted) { if(accepted) {
// 当前阶段接受候选后同步发布参数、曲线和诊断。 // 当前阶段接受候选后同步发布参数、曲线和诊断。
@ -2766,8 +2452,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
++acceptedSinceRebuild; ++acceptedSinceRebuild;
consecutiveRejectedSteps = 0; consecutiveRejectedSteps = 0;
// 第三阶段保留原 LM 控制;第二阶段两组都在步末直接扩缩实际步长。 // 两段共用原 LM 阻尼与信赖半径更新,预测比不替代目标误差验收。
if(!shapeStage) {
if(reductionRatio > 0.75) { if(reductionRatio > 0.75) {
damping = qMax(1.0e-8, damping * 0.5); damping = qMax(1.0e-8, damping * 0.5);
if(stepNorm >= trustRadius * 0.8) { if(stepNorm >= trustRadius * 0.8) {
@ -2781,17 +2466,15 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
trustRadius = qMax( trustRadius = qMax(
minimumTrustRadius, trustRadius * 0.75); minimumTrustRadius, trustRadius * 0.75);
} }
}
if(acceptedSinceRebuild >= 10 && !rebuildRequested && !shapeStage) { if(acceptedSinceRebuild >= 10 && !rebuildRequested) {
rebuildRequested = true; rebuildRequested = true;
rebuildReason = QT_TR_NOOP("10 accepted steps since last rebuild"); rebuildReason = QT_TR_NOOP("10 accepted steps since last rebuild");
} }
modelRebuiltAtMinimumRadius = false; modelRebuiltAtMinimumRadius = false;
} else { } else {
// 拒绝时 candidate 只保留在 trace 中,DataManager 和内存状态都恢复 // 拒绝时参数恢复到 current;沿用原 LM 的阻尼和半径收缩规则,
// 到 current。形状上限或全目标点验收也可能拒绝候选,不能仅凭拒绝 // 模型是否重建仍由预测质量判断,不因更换目标而改变。
// 次数认定模型失准;重建由上面的预测质量判断,约束冲突先缩步。
++consecutiveRejectedSteps; ++consecutiveRejectedSteps;
damping = qMin(1.0e8, damping * 4.0); damping = qMin(1.0e8, damping * 4.0);
trustRadius = qMax(minimumTrustRadius, trustRadius * 0.5); trustRadius = qMax(minimumTrustRadius, trustRadius * 0.5);
@ -2810,21 +2493,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
: "rejected_" + selectionName, : "rejected_" + selectionName,
&candidate.breakdown); &candidate.breakdown);
QString componentDisplayName = componentName; QString componentDisplayName = shapeStage ? tr("shape deviation") : tr("total error");
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");
}
emit logMessageGenerated( emit logMessageGenerated(
tr("Iteration %1: focus=%2, parameters=%3, error=%4, result=%5") tr("Iteration %1: focus=%2, parameters=%3, error=%4, result=%5")
.arg(iteration + 1) .arg(iteration + 1)
@ -2832,23 +2501,18 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
.arg(selectedColumns.size()) .arg(selectedColumns.size())
.arg(stageError(candidate), 0, 'e', 4) .arg(stageError(candidate), 0, 'e', 4)
.arg(accepted ? tr("accepted") : tr("rejected"))); .arg(accepted ? tr("accepted") : tr("rejected")));
emit progressUpdated(shapeStage ? -1 : totalIterations, m_globalBestFitness); emit progressUpdated(phaseIterations, m_globalBestFitness);
// 先记录当前试调结果,再发布阶段切换,避免日志显示为未试调就结束。
if(shapeStage) {
completeShapeSearchStep(accepted);
continue;
}
const bool effectiveImprovement = registerEffectiveImprovement(stageError(current)); const bool effectiveImprovement = registerEffectiveImprovement(stageError(current));
if(!effectiveImprovement && recordIneffectiveStep()) stopReason = LM_LOCAL_OPTIMUM; if(!effectiveImprovement && recordIneffectiveStep()) stopReason = LM_LOCAL_OPTIMUM;
if(stopReason == LM_LOCAL_OPTIMUM) break; if(stopReason == LM_LOCAL_OPTIMUM) break;
if(!shapeStage && current.fitness < m_targetError) { if(stageError(current) < m_targetError) {
stopReason = LM_TARGET_ACHIEVED; stopReason = LM_TARGET_ACHIEVED;
break; break;
} }
if(!shapeStage && selectedWindow < 0 && trustRadius <= minimumTrustRadius * 1.01 && if(trustRadius <= minimumTrustRadius * 1.01 &&
consecutiveRejectedSteps >= 2 && consecutivePoorPredictions >= 2) { consecutiveRejectedSteps >= 2 && consecutivePoorPredictions >= 2) {
if(modelRebuiltAtMinimumRadius) { if(modelRebuiltAtMinimumRadius) {
stopReason = LM_LOCAL_OPTIMUM; stopReason = LM_LOCAL_OPTIMUM;
@ -2858,13 +2522,24 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
rebuildReason = QT_TR_NOOP("inaccurate model at minimum trust radius"); 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<int>(stopReason)), &current.breakdown);
}
}
if(completedIterations > 0) { if(completedIterations > 0) {
m_currentIteration = completedIterations - 1; m_currentIteration = completedIterations - 1;
} }
restoreEvaluationState(current); restoreEvaluationState(current);
emit logMessageGenerated(tr("Adaptive fitting counts: %1 shape attempts, %2 total-stage iterations, %3 total evaluations.") emit logMessageGenerated(tr("Adaptive fitting counts: %1 shape LM iterations, %2 total LM iterations, %3 total evaluations.")
.arg(shapeStepCount).arg(totalIterations).arg(m_totalEvaluations)); .arg(shapeIterations).arg(totalIterations).arg(m_totalEvaluations));
if(m_shouldStop) { if(m_shouldStop) {
return LM_USER_STOPPED; return LM_USER_STOPPED;

@ -239,7 +239,7 @@ void nmWxAutomaticFitting::setParameterRange(int parameterIndex,
} }
// 根据当前初值生成建议搜索范围,并始终截断在拟合边界内。skin 下界为 0, // 根据当前初值生成建议搜索范围,并始终截断在拟合边界内。skin 下界为 0,
// 上界按初值加固定宽度,其余正值参数使用倍率范围;首次加载和拟合完成后复用。 // 上界按初值加固定宽度,其余正值参数使用倍率范围;每次打开窗口和拟合完成后复用。
void nmWxAutomaticFitting::updateRangeForParameter(int parameterIndex, void nmWxAutomaticFitting::updateRangeForParameter(int parameterIndex,
double centerValue) double centerValue)
{ {
@ -306,7 +306,7 @@ void nmWxAutomaticFitting::updateRangeForParameter(int parameterIndex,
} }
} }
// 按当前表格中的初值为所有参数建立建议范围,首次加载和拟合完成后共用。 // 按当前表格中的初值为所有参数建立建议范围,每次打开窗口和拟合完成后共用。
void nmWxAutomaticFitting::initializeSuggestedParameterRanges() void nmWxAutomaticFitting::initializeSuggestedParameterRanges()
{ {
if(!m_parameterTable) { if(!m_parameterTable) {
@ -333,57 +333,6 @@ void nmWxAutomaticFitting::initializeSuggestedParameterRanges()
} }
} }
// 校正已保存的范围:保留物理边界内的用户区间,无交集时按当前初值生成兜底区间。
void nmWxAutomaticFitting::normalizeSavedParameterRanges()
{
if(!m_parameterTable) {
return;
}
for(int parameterIndex = 0; parameterIndex < 7; ++parameterIndex) {
QTableWidgetItem* minItem = m_parameterTable->item(parameterIndex, 2);
QTableWidgetItem* maxItem = m_parameterTable->item(parameterIndex, 4);
QTableWidgetItem* initialItem = m_parameterTable->item(parameterIndex, 3);
if(!minItem || !maxItem || !initialItem) {
continue;
}
double physicalMin = 0.0;
double physicalMax = 0.0;
if(!getPhysicalParameterRange(parameterIndex, physicalMin, physicalMax)) {
continue;
}
bool savedMinOk = false;
bool savedMaxOk = false;
const double savedMin = minItem->text().toDouble(&savedMinOk);
const double savedMax = maxItem->text().toDouble(&savedMaxOk);
// 旧项目没有 Dfc 字段时,nmDataAttribute 会表现为 0~0。Dfc=0 在求解器中
// 表示无限导流,不能作为连续拟合区间,因此按当前压裂井初值重新建范围。
const bool savedRangeValid = savedMinOk && savedMaxOk
&& nmAutoFitUiIsFinite(savedMin) && nmAutoFitUiIsFinite(savedMax)
&& savedMax >= savedMin
&& !(parameterIndex == 5 && savedMax <= 1.0e-10);
if(savedRangeValid && physicalMax >= physicalMin) {
const double clippedMin = qMax(savedMin, physicalMin);
const double clippedMax = qMin(savedMax, physicalMax);
if(clippedMax >= clippedMin) {
setParameterRange(parameterIndex, clippedMin, clippedMax);
continue;
}
}
// 已保存范围无效或与物理边界无交集时,按数据对象初值重新生成。
bool initialOk = false;
const double initialValue = initialItem->text().toDouble(&initialOk);
if(initialOk && nmAutoFitUiIsFinite(initialValue)) {
updateRangeForParameter(parameterIndex, initialValue);
} else if(physicalMax >= physicalMin) {
setParameterRange(parameterIndex, physicalMin, physicalMax);
}
}
}
// 校验当前表格中的参数范围;parameterIndex 为 -1 时检查所有可见参数行。 // 校验当前表格中的参数范围;parameterIndex 为 -1 时检查所有可见参数行。
bool nmWxAutomaticFitting::validateParameterTable(QString& errorMessage, int parameterIndex) bool nmWxAutomaticFitting::validateParameterTable(QString& errorMessage, int parameterIndex)
{ {
@ -493,7 +442,6 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent)
nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance(); nmDataAnalyzeManager* pManager = nmDataAnalyzeManager::getCurrentInstance();
reservoirData = pManager->getReservoirDataCopy(); reservoirData = pManager->getReservoirDataCopy();
automaticFittingData = pManager->getAutomaticFittingDataCopy(); automaticFittingData = pManager->getAutomaticFittingDataCopy();
const bool hasSavedFittingData = pManager && pManager->getAutomaticFittingData() != nullptr;
// 自动范围始终开启;用户在表格中修改上下限后,itemChanged 会临时切换为手工范围。 // 自动范围始终开启;用户在表格中修改上下限后,itemChanged 会临时切换为手工范围。
m_autoParameterRanges = true; m_autoParameterRanges = true;
@ -505,11 +453,8 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent)
if(m_targetWellCombo->count() > 0) { if(m_targetWellCombo->count() > 0) {
onWellSelected(0); // 默认选中第一口井 onWellSelected(0); // 默认选中第一口井
} }
if(!hasSavedFittingData) { // 每次打开都按当前模型参数生成范围,不沿用上次的上下限。
initializeSuggestedParameterRanges(); initializeSuggestedParameterRanges();
} else {
normalizeSavedParameterRanges();
}
m_updatingParameterRanges = false; m_updatingParameterRanges = false;
DEBUG_UI("AutoFitting Constructor completed"); DEBUG_UI("AutoFitting Constructor completed");

Loading…
Cancel
Save