Compare commits

...

2 Commits

Author SHA1 Message Date
lvjunjie 452123b06a refactor(nmNum): 将 LM 形状阶段改为逐参数单轮调整 2 weeks ago
lvjunjie 50f149970c refactor(nmNum): 简化 LM 形状阶段联合调整与停滞确认
- 移除时间窗口选参、三参数上限及子集枚举,每步联合调整全部有效非井储表皮参数
- 连续两次改善不足后刷新灵敏度,仅做一次全局联合确认,累计有效改善门槛设为 10%
- 放宽形状扩步条件,取消实际与预测改善比值限制,保留预测下降、步长和边界约束
- 确认步求解失败时有限重试,避免将失败误判为收敛
- 同步拟合轨迹元数据、尝试次数日志及中文翻译
2 weeks ago

Binary file not shown.

@ -1277,24 +1277,24 @@ Reason: %1</source>
<translation>接受灵敏度探针:形状误差=%1</translation>
</message>
<message>
<source>fresh sensitivity at joint shape entry</source>
<translation>进入联合形状调整时重建灵敏度</translation>
<source>fresh sensitivity at single-parameter shape entry</source>
<translation>进入单参数形状调整时重建灵敏度</translation>
</message>
<message>
<source>Adaptive fitting counts: %1 completed shape rounds, %2 total-stage iterations, %3 total evaluations.</source>
<translation>自适应拟合统计:已完成 %1 轮形状搜索,整体阶段迭代 %2 次,累计评估 %3 次。</translation>
<source>Adaptive fitting counts: %1 shape attempts, %2 total-stage iterations, %3 total evaluations.</source>
<translation>自适应拟合统计:形状尝试 %1 次,整体阶段迭代 %2 次,累计评估 %3 次。</translation>
</message>
<message>
<source>LM stage 2: adaptive shape search; confirm stagnation after 2 complete rounds without significant improvement.</source>
<translation>LM 阶段2:自适应形状搜索;连续两轮完整搜索无显著改善后确认停滞。</translation>
<source>LM stage 2: adjust each shape parameter once; double the step after improvement, halve it after rejection, and switch after 3 consecutive rejections.</source>
<translation>LM 第二阶段:逐个调整形状参数,每个参数只调一轮;改善后步长翻倍,拒绝后减半,连续拒绝三次后换下一个参数。</translation>
</message>
<message>
<source>LM starting point error: %1; independent total-stage evaluation budget: %2</source>
<translation>LM 起点误差:%1;整体阶段独立评估预算:%2</translation>
</message>
<message>
<source>Shape search round %1: shape error=%2, improvement=%3, required=%4.</source>
<translation>形状搜索第 %1 轮:形状误差=%2,改善量=%3,所需改善量=%4。</translation>
<source>Shape search step %1: shape error=%2, accumulated improvement=%3, required=%4.</source>
<translation>形状尝试 %1:形状误差=%2,累计改善=%3,有效改善门槛=%4。</translation>
</message>
<message>
<source>Total-stage budget starts now: %1 iterations, %2 evaluations; pre-adjustment is counted separately.</source>
@ -1305,16 +1305,16 @@ Reason: %1</source>
<translation>无法建立有效的形状灵敏度模型。</translation>
</message>
<message>
<source>confirm stagnation after 2 complete shape rounds</source>
<translation>完成两轮形状搜索后确认停滞</translation>
<source>confirm shape stagnation after 2 ineffective steps</source>
<translation>连续两次形状改善不足,刷新灵敏度确认停滞</translation>
</message>
<message>
<source>full sensitivity at total-stage entry</source>
<translation>进入整体阶段时建立完整灵敏度</translation>
</message>
<message>
<source>no significant improvement in a complete round after fresh sensitivity confirmation</source>
<translation>重建灵敏度确认后,完整一轮搜索仍无显著改善</translation>
<source>all shape parameters visited once</source>
<translation>所有形状参数均已完成一轮调整</translation>
</message>
<message>
<source>no valid early sensitivity model</source>

@ -594,95 +594,6 @@ static QVector<TrustRegionFisher> buildTrustRegionFisher(
return information;
}
static int nextTrustRegionWindow(
const QVector<AutoFitTimeWindowLM>& windows,
const QVector<bool>& attempted)
{
// 每次都按当前误差能量重新排序;本轮无效的窗口暂时跳过,-1 表示全局回退。
int selected = -1;
double largestEnergy = 0.0;
for(int k = 0; k < windows.size(); ++k) {
if(!attempted[k] && isFiniteNumber(windows[k].energy) &&
windows[k].energy > largestEnergy) {
largestEnergy = windows[k].energy;
selected = k;
}
}
return selected;
}
static QVector<int> selectTrustRegionFisherColumns(
const TrustRegionFisher& local,
const TrustRegionFisher& global,
const QVector<bool>& columnValid,
const QVector<double>& coordinates,
double minimumStep)
{
// g^2/F 衡量修正当前残差的潜力,避免仅按影响强度选中无助于降误差的参数。
const int dimensions = columnValid.size();
double maximumDiagonal = 0.0;
for(int p = 0; p < dimensions; ++p) {
if(columnValid[p] && isFiniteNumber(local.matrix[p][p])) {
maximumDiagonal = qMax(maximumDiagonal, local.matrix[p][p]);
}
}
const double diagonalFloor = qMax(1.0e-30, maximumDiagonal * 1.0e-12);
QVector<double> scores(dimensions, 0.0);
double maximumScore = 0.0;
for(int p = 0; p < dimensions; ++p) {
if(!columnValid[p] || local.matrix[p][p] <= diagonalFloor ||
!isFiniteNumber(local.matrix[p][p]) ||
!isFiniteNumber(local.gradient[p]) ||
!isFiniteNumber(global.gradient[p])) {
continue;
}
// 全局下降方向指向边界外时暂不选该参数,避免它挤占可移动参数的位置。
if((coordinates[p] <= minimumStep && global.gradient[p] > 0.0) ||
(coordinates[p] >= 1.0 - minimumStep && global.gradient[p] < 0.0)) {
continue;
}
double scaledGradient = local.gradient[p] /
qSqrt(local.matrix[p][p] + diagonalFloor);
double score = scaledGradient * scaledGradient;
if(isFiniteNumber(score)) {
scores[p] = score;
maximumScore = qMax(maximumScore, score);
}
}
QVector<int> selected;
for(int selection = 0; selection < qMin(3, dimensions); ++selection) {
int best = -1;
double bestScore = maximumScore * 1.0e-12;
for(int p = 0; p < dimensions; ++p) {
if(selected.contains(p) || scores[p] <= bestScore) {
continue;
}
// 沿用现有全局 0.995 共线限制,不因单个窗口内响应相似而冻结参数。
bool correlated = false;
for(int i = 0; i < selected.size(); ++i) {
int q = selected[i];
double denominator = qSqrt(global.matrix[p][p]) *
qSqrt(global.matrix[q][q]);
if(denominator > 0.0 &&
qAbs(global.matrix[p][q]) / denominator > 0.995) {
correlated = true;
break;
}
}
if(!correlated) {
best = p;
bestScore = scores[p];
}
}
if(best < 0) {
break;
}
selected.append(best);
}
return selected;
}
static bool buildTrustRegionFisherStep(
const TrustRegionFisher& global,
const QVector<int>& selected,
@ -760,6 +671,30 @@ static bool buildTrustRegionFisherStep(
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();
}
// 前期每次只调一个参数:间距锁定符号,形状 LM 提供幅度和后续验收依据。
static bool buildEarlyGapGuidedStep(const TrustRegionFisher& shapeInformation,
const QVector<int>& selected, const QVector<double>& coordinates, double direction,
@ -780,7 +715,7 @@ static bool buildEarlyGapGuidedStep(const TrustRegionFisher& shapeInformation,
}
// 放大当前方向,仍受传入的最大半径和参数边界限制。
// 普通形状步要求预测下降;前期初始探路可跨过预测上坡区,最终仍按真实形状验收。
// 前期初始探路可跨过预测上坡区,最终仍按真实形状验收。
static bool buildExpandedTrustRegionStep(const TrustRegionFisher& information,
const QVector<double>& coordinates, const QVector<double>& originalStep,
double maximumRadius, QVector<double>* expandedStep, double* prediction,
@ -1212,7 +1147,7 @@ void nmCalculationAutoFitLM::writeTraceMetaFile()
QTextStream out(&metaFile);
out << "{\n";
out << " \"schema_version\": 31,\n";
out << " \"schema_version\": 39,\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";
@ -1227,11 +1162,12 @@ void nmCalculationAutoFitLM::writeTraceMetaFile()
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_only at joint shape entry and stagnation confirmation; full Jacobian rebuilt at total-stage entry\",\n";
out << " \"shape_expansion_policy\": \"one expansion per reliable shape direction, up to 2x when actual rho exceeds 0.75; retain ordinary step on failure\",\n";
out << " \"shape_global_parameter_selection\": \"all_valid_non_wellbore_columns_with_positive_response\",\n";
out << " \"stage2_budget\": \"adaptive, no fixed iteration or evaluation quota; full window/global rounds, 2 ineffective rounds then fresh-J confirmation\",\n";
out << " \"shape_round_improvement_threshold\": \"max(0.0001, 1% of round entry shape loss)\",\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_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_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";
@ -2013,6 +1949,9 @@ 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;
@ -2033,7 +1972,6 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
const char* rebuildReason = QT_TR_NOOP("initial sensitivity model");
bool modelRebuiltAtMinimumRadius = false;
bool stagnationConfirmationRequested = false;
QVector<bool> attemptedWindows(kAutoFitTimeWindowCount, false);
bool globalFallbackAttempted = false;
StopReasonLM stopReason = LM_MAX_ITERATIONS;
@ -2210,14 +2148,16 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
if(m_shouldStop) return LM_USER_STOPPED;
// 第二阶段不按迭代比例截断;普通形状按完整搜索轮次判断是否仍有显著改善。
// 第二阶段先测灵敏度,再逐个调完非井筒参数;每个参数只访问一次。
// 普通形状调整冻结井储、表皮,只按形状验收;总误差达标只在第三阶段判断。
bool shapeStage = true;
bool jointShapeStarted = false;
double shapeRoundBaseline = current.breakdown.shapeLoss;
int shapeRoundNumber = 0;
int ineffectiveShapeRounds = 0;
bool shapeConfirmationRequested = false;
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;
@ -2283,7 +2223,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
restoreEvaluationState(current);
};
emit logMessageGenerated(shapeStage
? tr("LM stage 2: adaptive shape search; confirm stagnation after 2 complete rounds without significant improvement.")
? 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
@ -2328,7 +2268,6 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
consecutivePoorPredictions = 0;
modelRebuiltAtMinimumRadius = false;
stagnationConfirmationRequested = false;
attemptedWindows.fill(false);
globalFallbackAttempted = false;
fullDataRejections = 0;
effectiveImprovementBaseline = stageError(current);
@ -2357,9 +2296,8 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
consecutiveRejectedSteps = 0;
consecutiveSolverFailures = 0;
stagnationConfirmationRequested = false;
attemptedWindows.fill(false);
globalFallbackAttempted = false;
trustRadius = 0.12;
trustRadius = shapeStage && !earlyShapeStage ? initialShapeTrustRadius : 0.12;
damping = 0.01;
};
auto enterTotalStage = [&](const QString& reason) {
@ -2454,37 +2392,37 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
true, 0, "early_wellbore_to_shape", &current.breakdown);
};
auto completeShapeSearchRound = [&]() {
if(!shapeStage || earlyShapeStage || !globalFallbackAttempted) return;
if(m_shouldStop) return;
const double required = qMax(1.0e-4, 0.01 * shapeRoundBaseline);
const double improvement = shapeRoundBaseline - current.breakdown.shapeLoss;
const bool improved = improvement >= required;
++shapeRoundNumber;
if(improved) {
ineffectiveShapeRounds = 0;
shapeConfirmationRequested = false;
} else ++ineffectiveShapeRounds;
writeTraceRow(m_currentIteration, -1, "shape_round_end", current.parameters, current.fitness,
true, 0, QString(improved ? "improved_round_%1" : "ineffective_round_%1").arg(shapeRoundNumber), &current.breakdown);
emit logMessageGenerated(tr("Shape search round %1: shape error=%2, improvement=%3, required=%4.")
.arg(shapeRoundNumber).arg(current.breakdown.shapeLoss, 0, 'g', 6)
.arg(improvement, 0, 'g', 6).arg(required, 0, 'g', 6));
if(!improved && shapeConfirmationRequested) {
finishShapeStage(tr("no significant improvement in a complete round after fresh sensitivity confirmation"));
return;
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);
}
// 下一轮重新覆盖窗口和全部有效参数的全局步;单步小改善不重置本轮进度。
shapeRoundBaseline = current.breakdown.shapeLoss;
attemptedWindows.fill(false);
globalFallbackAttempted = false;
if(!improved && ineffectiveShapeRounds >= 2) {
jacobian.clear();
reuseSensitivityForNextStage();
rebuildRequested = true;
rebuildReason = QT_TR_NOOP("confirm stagnation after 2 complete shape rounds");
shapeConfirmationRequested = true;
activeShapeColumn = -1;
shapeCoordinateStep = 0.0;
consecutiveShapeRejections = 0;
for(int column = 0; column < dimensions; ++column) {
if(parameterAllowedInStage(column) && !shapeParameterFinished[column]) return;
}
finishShapeStage(tr("all shape parameters visited once"));
};
auto completeShapeSearchStep = [&](bool accepted) {
if(!shapeStage || earlyShapeStage || m_shouldStop) return;
// 只按本次真实下降控制实际步长;不再使用累计 10% 门槛提前结束整轮。
++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 {
@ -2498,8 +2436,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
return false;
}
// 有效改善后重新开放全部窗口,并按新工作点的能量排序。
attemptedWindows.fill(false);
// 有效改善后更新累计基准,并清除停滞计数。
globalFallbackAttempted = false;
effectiveImprovementBaseline = fitness;
consecutiveIneffectiveSteps = 0;
@ -2507,11 +2444,11 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
return true;
};
// 无效尝试仍累计,但所有窗口及一次全局回退尝试完毕前,不触发停滞收敛。
// 完整一轮仍无改善时沿用原有重建确认,避免某个难处理窗口提前终止拟合。
// 普通形状的失败试算缩步并累计当前参数的拒绝次数;
// 前期井储、表皮与整体 LM 沿用各自的无效次数处理。
auto recordIneffectiveStep = [&]() -> bool {
if(shapeStage && !earlyShapeStage) {
completeShapeSearchRound();
completeShapeSearchStep(false);
return false;
}
++consecutiveIneffectiveSteps;
@ -2524,7 +2461,6 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
if(!globalFallbackAttempted) {
return false;
}
attemptedWindows.fill(false);
globalFallbackAttempted = false;
if(consecutiveIneffectiveSteps < maximumIneffectiveSteps) {
return false;
@ -2680,9 +2616,11 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
jacobianColumnValid[column] = true;
columnBuilt = true;
// 后半段形状探针仅用于灵敏度,正式调整必须经过单参数 LM 预测选优。
// 前期同时测井储和表皮,但缓存探针不能绕过当前子阶段的选参限制。
// 前期首次正式调整必须从小步开始,测形状灵敏度的大探针不能提前成为工作点。
if((!earlyShapeStage || earlyShapeHasImproved) &&
if((!shapeStage || earlyShapeStage) &&
(!earlyShapeStage || earlyShapeHasImproved) &&
parameterAllowedInStage(column) && acceptable(probe, base) &&
(!bestProbe.valid || stageError(probe) < stageError(bestProbe))) {
bestProbe = probe;
@ -2702,14 +2640,14 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
if(parameterAllowedInStage(i)) hasActiveSensitivity = true;
}
}
// 冻结列成功不能掩盖所有可调形状列的求解失败;有效零梯度仍由停滞逻辑处理。
// 冻结列成功不能掩盖所有可调形状列的求解失败;有效零梯度交给逐参数选步处理。
if(validColumnCount == 0 || (shapeStage && !earlyShapeStage && !hasActiveSensitivity) || m_shouldStop) {
restoreEvaluationState(base);
return false;
}
// 灵敏度试算本身若找到更优真实解也应保留。所有列先基于同一个 base
// 建完,再用该已知割线把 Jacobian 平移到新工作点,避免边算边移动基点。
// 前期井储/表皮和整体阶段仍可接受更优缓存探针;后半段形状只由 LM 选步。
// 所有列先基于同一个 base 建完,再用已知割线平移 Jacobian,避免边算边移动基点。
if(bestProbe.valid && bestProbeColumn >= 0) {
QVector<double> acceptedStep(dimensions, 0.0);
acceptedStep[bestProbeColumn] = bestProbeDelta;
@ -2769,14 +2707,13 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
if(wellboreRecheck && current.breakdown.earlyParallelLoss <=
earlyWellboreBaseline + qMax(1.0e-4, 0.10 * earlyWellboreBaseline))
enterTotalStage(tr("early shape error restored within tolerance"));
if(shapeStage && !earlyShapeStage && !jointShapeStarted) {
jointShapeStarted = true;
shapeRoundBaseline = current.breakdown.shapeLoss;
if(shapeStage && !earlyShapeStage && !singleShapeStarted) {
singleShapeStarted = true;
// 初调已改变井储/表皮,普通形状必须在新的工作点重测其余可调参数的响应。
jacobian.clear();
reuseSensitivityForNextStage();
rebuildRequested = true;
rebuildReason = QT_TR_NOOP("fresh sensitivity at joint shape entry");
rebuildReason = QT_TR_NOOP("fresh sensitivity at single-parameter shape entry");
}
if(m_shouldStop) break;
if(!shapeStage && (totalIterations >= m_maxIterations || m_totalEvaluations >= maximumEvaluations)) break;
@ -2834,13 +2771,13 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
--iteration;
continue;
}
// 单列探针没有改善不代表联合步无效;重建后继续真实候选评价,再确认停滞。
// 差分探针不等于 LM 候选;重建后继续按当前阶段求步并真实评价,再确认停滞。
registerEffectiveImprovement(stageError(current));
}
// 第三阶段独立计数;方向不可行也消耗一次局部尝试,不能无限缩步循环。
if(!shapeStage) ++totalIterations;
// 每轮从最新 J 和当前残差重算窗口 Fisher,包含有限差分与割线更新的变化。
// 每次从最新 J 和当前阶段残差重算全局信息,供单参数或联合 LM 求步。
const QVector<double> objectiveResidual = earlyShapeStage
? current.breakdown.earlyParallelResiduals
: (shapeStage ? current.breakdown.shapeResiduals : current.breakdown.residualVector);
@ -2854,10 +2791,6 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
objectiveCoordinates.clear();
for(int i = 0; i < 73; ++i) objectiveCoordinates.append((i + 4.0) / 80.0);
}
const QVector<AutoFitTimeWindowLM> objectiveWindows = shapeStage
? calculateAutoFitTimeWindows(objectiveResidual, m_comparisonTimeMin, m_comparisonTimeMax,
objectiveCoordinates, autoFitLogTimeWeights(objectiveCoordinates))
: current.breakdown.timeWindows;
const QVector<TrustRegionFisher> information = buildTrustRegionFisher(
objectiveJacobian, objectiveResidual, jacobianColumnValid, objectiveCoordinates);
const TrustRegionFisher& global = information[kAutoFitTimeWindowCount];
@ -2870,98 +2803,86 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
QVector<int> selectedColumns;
QVector<double> coordinateStep;
double predictedReduction = 0.0;
int selectedWindow = -1;
if(!shapeStage) {
// 整体阶段联合求解全部有效自由列,不按窗口得分、三参数上限或共线阈值删列。
// 边界列也参与耦合求解,是否向内移动由联合解及其边界投影决定。
for(int column = 0; column < dimensions; ++column) {
// 仅跳过完全没有总残差响应的列,保留弱敏感列及相互相关的列。
if(stageColumnValid[column] && global.matrix[column][column] > 0.0)
selectedColumns.append(column);
const int selectedWindow = earlyShapeStage ? 0 : -1;
// 前期只开放当前井储或表皮;后半段形状从有效自由列中选一个,整体 LM 仍联合求解。
// 保留弱敏感、相关及边界列供预测比较;窗口只保留作误差诊断。
for(int column = 0; column < dimensions; ++column) {
if(stageColumnValid[column] && (earlyShapeStage || 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);
}
// 当前候选已使用全局信息,停滞判断无需再逐一尝试窗口子集。
attemptedWindows.fill(true);
globalFallbackAttempted = true;
if(selectedColumns.isEmpty() || !buildTrustRegionFisherStep(
global, selectedColumns, current.coordinates, damping,
trustRadius, minimumCoordinateStep, &coordinateStep, &predictedReduction)) {
if(selectedColumns.isEmpty() || !buildEarlyGapGuidedStep(
stepInformation, selectedColumns, current.coordinates, earlyGapDirection,
damping, earlyStepRadius, minimumCoordinateStep, &coordinateStep, &predictedReduction)) {
selectedColumns.clear();
}
}
// 形状阶段先覆盖局部窗口,再用全部有效自由列求一次全局联合步。
while(shapeStage && selectedColumns.isEmpty()) {
selectedWindow = earlyShapeStage ? 0 : nextTrustRegionWindow(
objectiveWindows, attemptedWindows);
if(selectedWindow >= 0) {
attemptedWindows[selectedWindow] = true;
} else {
globalFallbackAttempted = true;
}
const TrustRegionFisher& local = selectedWindow >= 0
? information[selectedWindow] : global;
QVector<int> proposed;
if(earlyShapeStage) {
// 前期只开放当前的井储或表皮参数。
for(int column = 0; column < dimensions; ++column) {
if(stageColumnValid[column]) proposed.append(column);
} else if(shapeStage) {
// 首步由灵敏度选出尚未调过的参数;后续锁定方向,直接使用扩缩后的步长。
if(activeShapeColumn < 0) {
QVector<int> availableColumns;
for(int i = 0; i < selectedColumns.size(); ++i) {
if(!shapeParameterFinished[selectedColumns[i]]) availableColumns.append(selectedColumns[i]);
}
} else if(selectedWindow < 0) {
// 全局步保留弱敏感及相关列,避免参数长期被窗口前三名或共线筛选排除。
for(int column = 0; column < dimensions; ++column) {
if(stageColumnValid[column] && global.matrix[column][column] > 0.0)
proposed.append(column);
}
} else {
proposed = selectTrustRegionFisherColumns(
local, global, stageColumnValid,
current.coordinates, minimumCoordinateStep);
}
// 局部窗口至多三列,比较非空子集;全局只求完整组合,不枚举全部参数子集。
const bool fullJointStep = !earlyShapeStage && selectedWindow < 0;
const int combinationCount = proposed.isEmpty() ? 0 : (fullJointStep ? 1 : (1 << proposed.size()) - 1);
for(int combination = 1; combination <= combinationCount; ++combination) {
QVector<int> columns;
if(fullJointStep) columns = proposed;
else {
for(int i = 0; i < proposed.size(); ++i) {
if(combination & (1 << i)) columns.append(proposed[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 {
// 剩余参数均无可行预测下降步时逐项记录,不能重复选择已完成参数。
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;
}
--iteration;
continue;
}
QVector<double> step;
double reduction = 0.0;
double earlyStepRadius = trustRadius;
if(earlyShapeStage && !earlyShapeHasImproved && columns.size() == 1) {
// 确定方向后先小步:井储最多 1.05 倍,表皮为局部尺度的 2%。
// 首次形状变差由后面的扩步循环处理,首次接受后恢复正常 LM 幅度。
const int column = columns[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);
}
// 井储、表皮沿间距确定的方向调整;其他阶段保持原来的 LM 求步。
const bool stepBuilt = earlyShapeStage
? buildEarlyGapGuidedStep(stepInformation, columns, current.coordinates, earlyGapDirection,
damping, earlyStepRadius, minimumCoordinateStep, &step, &reduction)
: buildTrustRegionFisherStep(stepInformation, columns, current.coordinates,
damping, trustRadius, minimumCoordinateStep, &step, &reduction);
if(!stepBuilt) continue;
const double tolerance = 1.0e-12 * qMax(predictedReduction, reduction);
if(selectedColumns.isEmpty() || reduction > predictedReduction + tolerance ||
(qAbs(reduction - predictedReduction) <= tolerance &&
columns.size() < selectedColumns.size())) {
selectedColumns = columns;
coordinateStep = step;
predictedReduction = reduction;
} 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;
}
if(earlyShapeStage || selectedWindow < 0) {
break;
} else {
// 第三阶段保持全部有效自由参数的联合 LM 调整。
globalFallbackAttempted = true;
if(selectedColumns.isEmpty() || !buildTrustRegionFisherStep(
global, selectedColumns, current.coordinates, damping,
trustRadius, minimumCoordinateStep, &coordinateStep, &predictedReduction)) {
selectedColumns.clear();
}
}
@ -2973,11 +2894,6 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
--iteration;
continue;
}
if(shapeStage) {
completeShapeSearchRound();
--iteration;
continue;
}
if(trustRadius <= minimumTrustRadius * 1.01 &&
modelRebuiltAtMinimumRadius) {
if(promoteSampling(false)) continue;
@ -3088,7 +3004,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
"solver_invalid_" + selectionName,
nullptr);
restoreEvaluationState(current);
if(consecutiveSolverFailures >= 2) {
if(consecutiveSolverFailures >= 2 && (!shapeStage || earlyShapeStage)) {
rebuildRequested = true;
rebuildReason = QT_TR_NOOP("2 consecutive solver failures");
}
@ -3104,41 +3020,6 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
continue;
}
// 普通形状步真实表现可靠时,沿同一联合方向最多扩步试算一次,不区分参数类型。
// 扩步只按整体形状验收,优于原步才替换,否则保留原候选。
if(shapeStage && !earlyShapeStage && acceptable(candidate, current) && processPauseAndStop()) {
const double ordinaryEnergy = 0.5 * trustRegionSquaredNorm(objectiveResidual);
const double ordinaryReduction = ordinaryEnergy - 0.5 * trustRegionSquaredNorm(candidate.breakdown.shapeResiduals);
const double predictionFloor = qMax(1.0e-14, ordinaryEnergy * 1.0e-8);
QVector<double> expandedStep;
double expandedPrediction = 0.0;
if(predictedReduction > predictionFloor && ordinaryReduction / predictedReduction > 0.75 &&
buildExpandedTrustRegionStep(stepInformation, current.coordinates, coordinateStep,
maximumTrustRadius, &expandedStep, &expandedPrediction)) {
writeTraceRow(m_currentIteration, -1, "shape_step_base", candidate.parameters,
candidate.fitness, true, candidate.elapsedMs, "eligible_for_expansion", &candidate.breakdown);
TrustRegionEvaluation expanded;
expanded.coordinates = current.coordinates;
for(int i = 0; i < dimensions; ++i) expanded.coordinates[i] += expandedStep[i];
expanded.parameters = parametersFromCoordinates(expanded.coordinates);
expanded.valid = evaluateTrustRegionPoint(expanded.parameters, &expanded.fitness,
&expanded.breakdown, &expanded.curve, &expanded.elapsedMs);
const bool useExpanded = acceptable(expanded, current) && expanded.breakdown.shapeLoss < candidate.breakdown.shapeLoss;
writeTraceRow(m_currentIteration, -1, "shape_step_expanded", expanded.parameters,
expanded.fitness, expanded.valid, expanded.elapsedMs, useExpanded ? "selected_expanded" : "retain_ordinary",
expanded.valid ? &expanded.breakdown : nullptr);
if(useExpanded) {
candidate = expanded;
coordinateStep = expandedStep;
predictedReduction = expandedPrediction;
stepNorm = qSqrt(trustRegionSquaredNorm(coordinateStep));
selectionName += "_expanded";
}
// 两次真实调用已分别记录,下方候选行只记录最终决策,避免重复统计耗时。
candidate.elapsedMs = -1;
restoreEvaluationState(current);
}
}
if(m_shouldStop) {
restoreEvaluationState(current);
stopReason = LM_USER_STOPPED;
@ -3168,7 +3049,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
const bool poorPrediction = predictedReduction > predictionFloor &&
(!isFiniteNumber(reductionRatio) || reductionRatio < 0.25);
consecutivePoorPredictions = poorPrediction ? consecutivePoorPredictions + 1 : 0;
if(consecutivePoorPredictions >= 2) {
if(consecutivePoorPredictions >= 2 && (!shapeStage || earlyShapeStage)) {
rebuildRequested = true;
rebuildReason = QT_TR_NOOP("2 consecutive steps with actual improvement below 25% of prediction");
}
@ -3189,21 +3070,24 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
++acceptedSinceRebuild;
consecutiveRejectedSteps = 0;
if(reductionRatio > 0.75) {
damping = qMax(1.0e-8, damping * 0.5);
if(stepNorm >= trustRadius * 0.8) {
trustRadius = qMin(
maximumTrustRadius, trustRadius * 1.6);
// 井筒调整及第三阶段保留原 LM 控制;逐参数粗调在步末直接扩缩实际步长。
if(!shapeStage || earlyShapeStage) {
if(reductionRatio > 0.75) {
damping = qMax(1.0e-8, damping * 0.5);
if(stepNorm >= trustRadius * 0.8) {
trustRadius = qMin(
maximumTrustRadius, trustRadius * 1.6);
}
} else if(reductionRatio > 0.25) {
damping = qMax(1.0e-8, damping * 0.8);
} else {
damping = qMin(1.0e8, damping * 2.0);
trustRadius = qMax(
minimumTrustRadius, trustRadius * 0.75);
}
} else if(reductionRatio > 0.25) {
damping = qMax(1.0e-8, damping * 0.8);
} else {
damping = qMin(1.0e8, damping * 2.0);
trustRadius = qMax(
minimumTrustRadius, trustRadius * 0.75);
}
if(acceptedSinceRebuild >= 10 && !rebuildRequested) {
if(acceptedSinceRebuild >= 10 && !rebuildRequested && (!shapeStage || earlyShapeStage)) {
rebuildRequested = true;
rebuildReason = QT_TR_NOOP("10 accepted steps since last rebuild");
}
@ -3259,6 +3143,10 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
finishEarlyShapeStage(tr("initial same-direction expansion reached the parameter bound without early shape improvement"));
continue;
}
if(shapeStage && !earlyShapeStage) {
completeShapeSearchStep(accepted);
continue;
}
const bool effectiveImprovement = registerEffectiveImprovement(stageError(current));
if(!effectiveImprovement && recordIneffectiveStep()) stopReason = LM_LOCAL_OPTIMUM;
@ -3291,8 +3179,8 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
m_currentIteration = completedIterations - 1;
}
restoreEvaluationState(current);
emit logMessageGenerated(tr("Adaptive fitting counts: %1 completed shape rounds, %2 total-stage iterations, %3 total evaluations.")
.arg(shapeRoundNumber).arg(totalIterations).arg(m_totalEvaluations));
emit logMessageGenerated(tr("Adaptive fitting counts: %1 shape attempts, %2 total-stage iterations, %3 total evaluations.")
.arg(shapeStepCount).arg(totalIterations).arg(m_totalEvaluations));
if(m_shouldStop) {
return LM_USER_STOPPED;

Loading…
Cancel
Save