diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm
index 5408661a..b6dbcf60 100644
Binary files a/Bin/Config/Lang/cn/nmNum_cn.qm and b/Bin/Config/Lang/cn/nmNum_cn.qm differ
diff --git a/Bin/Config/Lang/cn/nmNum_cn.ts b/Bin/Config/Lang/cn/nmNum_cn.ts
index 947891b9..302d0278 100644
--- a/Bin/Config/Lang/cn/nmNum_cn.ts
+++ b/Bin/Config/Lang/cn/nmNum_cn.ts
@@ -673,8 +673,8 @@ Reason: %1
LM 采样加密:%1 / %2 个目标点;全目标点误差:%3
- LM sampling: layered target points (%1 / %2); acceptance uses all valid target points.
- LM 采样方式:目标点分层采样(%1 / %2 点),使用全部有效目标点验收。
+ LM sampling: layered target points (%1 / %2); total-stage acceptance uses all valid target points.
+ LM 采样方式:目标点分层采样(%1 / %2 点),整体阶段使用全部有效目标点验收。
LM sampling: fixed 80 points (original mode).
@@ -925,8 +925,8 @@ Reason: %1
=== LM 自动拟合 - 局部最优 ===
- Max iterations reached. Best error: %1, Iterations: %2
- 达到最大迭代次数。最佳误差:%1,迭代次数:%2
+ Total-stage budget reached. Best error: %1, Cumulative iterations: %2
+ 整体阶段预算已用尽。最佳误差:%1,累计迭代次数:%2
=== LM AUTOMATIC FITTING - MAX ITERATIONS ===
@@ -968,10 +968,6 @@ Reason: %1
=== Starting LM Main Loop ===
=== 开始 LM 主循环 ===
-
- LM starting point error: %1; evaluation budget: %2
- LM 起点误差:%1;最大评估次数:%2
-
No effective improvement for %1 consecutive steps; rebuilding sensitivity model for confirmation
连续 %1 次无有效改善,正在重建灵敏度模型进行确认
@@ -1065,8 +1061,8 @@ Reason: %1
检测到局部最优
- Maximum iterations reached
- 达到最大迭代次数
+ Total-stage iteration or evaluation budget reached
+ 整体阶段迭代或评估预算已用尽
Stopped by user request
@@ -1124,10 +1120,6 @@ Reason: %1
Permeability alignment: k=%1, height error=%2, shape error=%3, result=%4
渗透率对齐:k=%1,上下误差=%2,形状误差=%3,结果=%4
-
- LM stage 2: optimize pressure and derivative shape; stop after 3 ineffective steps.
- LM 阶段二:优先调整压力和压力导数形状,连续 3 步无明显改善后切换。
-
LM stage 3: original LM fitting; accept by total error only.
LM 阶段三:按原有 LM 拟合,仅依据整体误差接受调整。
@@ -1137,24 +1129,12 @@ Reason: %1
采用灵敏度试算点:整体误差=%1
- Shape stage ended: %1
- 形状阶段结束:%1
-
-
- 3 consecutive steps without effective shape improvement
- 连续 3 步形状没有明显改善
-
-
- reserve remaining iterations and evaluations for total fitting
- 为整体拟合保留剩余迭代和求解预算
-
-
- no valid shape sensitivity model
- 没有有效的形状灵敏度模型
+ Sensitivity probe accepted: early relative-slope matching error=%1
+ 灵敏度试算点已接受:前期相对斜率匹配误差=%1
- no feasible shape descent step
- 没有满足约束的形状下降步
+ Shape stage ended: %1
+ 形状阶段结束:%1
Permeability alignment ended: %1
@@ -1232,6 +1212,122 @@ Reason: %1
inaccurate model at minimum trust radius
最小信赖半径下仍连续预测失准
+
+ no remaining shape parameters
+ 没有其他勾选参数需要调整形状
+
+
+ Early adjustment: adjust wellbore storage, then skin, to match the early pressure-derivative slope difference of the target.
+ 前期预调整:先调整井储,再调整表皮,使模拟压力与导数的前期斜率差接近目标曲线。
+
+
+ Early adjustment: adjust skin to match the early pressure-derivative slope difference of the target.
+ 前期预调整:调整表皮,使模拟压力与导数的前期斜率差接近目标曲线。
+
+
+ Wellbore storage adjustment ended: %1; now adjusting skin.
+ 井储调整结束:%1;开始单独调整表皮。
+
+
+ Early wellbore adjustment ended: %1
+ 井储和表皮前期预调整结束:%1
+
+
+ Shape fitting continues with storage and skin fixed; a conditional wellbore recheck follows.
+ 继续调整形状,暂时固定井储和表皮;随后按条件进行一次井储表皮回检。
+
+
+ 2 consecutive steps without effective early improvement
+ 连续 2 步前期误差没有明显改善
+
+
+ no feasible early adjustment direction or parameter at bound
+ 没有明确可行的前期调整方向,或参数已到边界
+
+
+ wellbore storage relative-slope matching
+ 井储前期相对斜率匹配
+
+
+ skin relative-slope matching
+ 表皮前期相对斜率匹配
+
+
+ Wellbore storage trial: relative-slope matching error=%1, C=%2 -> %3
+ 井储试调:相对斜率匹配误差=%1,井储=%2 → %3
+
+
+ Wellbore recheck: early error=%1, total limit=%2, shape limit=%3.
+ 井储表皮回检:前期相对斜率匹配误差=%1,整体误差上限=%2,形状误差上限=%3。
+
+
+ early relative-slope error restored within tolerance
+ 前期相对斜率匹配误差已恢复到容差范围内
+
+
+ new sensitivity for wellbore recheck
+ 为井储表皮回检重建灵敏度
+
+
+ wellbore recheck completed during sensitivity evaluation
+ 井储表皮回检在灵敏度评价期间完成
+
+
+ total target reached during shape fitting
+ 形状调整期间已达到总误差目标
+
+
+ Half-length exploration accepted: L=%1, shape error=%2, total error=%3.
+ 裂缝半长探索结果已接受:半长=%1,形状误差=%2,整体误差=%3。
+
+
+ fresh sensitivity after half-length exploration
+ 裂缝半长探索后重建灵敏度
+
+
+ fresh sensitivity at joint shape entry
+ 进入联合形状调整时重建灵敏度
+
+
+ Adaptive fitting counts: %1 completed shape rounds, %2 total-stage iterations, %3 total evaluations.
+ 自适应拟合统计:已完成 %1 轮形状搜索,整体阶段迭代 %2 次,累计评估 %3 次。
+
+
+ LM stage 2: adaptive shape search; confirm stagnation after 2 complete rounds without significant improvement.
+ LM 阶段2:自适应形状搜索;连续两轮完整搜索无显著改善后确认停滞。
+
+
+ LM starting point error: %1; independent total-stage evaluation budget: %2
+ LM 起点误差:%1;整体阶段独立评估预算:%2
+
+
+ Shape search round %1: shape error=%2, improvement=%3, required=%4.
+ 形状搜索第 %1 轮:形状误差=%2,改善量=%3,所需改善量=%4。
+
+
+ Total-stage budget starts now: %1 iterations, %2 evaluations; pre-adjustment is counted separately.
+ 整体阶段预算开始计数:%1 次迭代、%2 次评估;预调整单独计数。
+
+
+ Unable to build a valid shape sensitivity model.
+ 无法建立有效的形状灵敏度模型。
+
+
+ confirm stagnation after 2 complete shape rounds
+ 完成两轮形状搜索后确认停滞
+
+
+ full sensitivity at total-stage entry
+ 进入整体阶段时建立完整灵敏度
+
+
+ no significant improvement in a complete round after fresh sensitivity confirmation
+ 重建灵敏度确认后,完整一轮搜索仍无显著改善
+
+
+ no valid early sensitivity model
+ 无有效的早期灵敏度模型
+
nmCalculationSolver
@@ -4499,6 +4595,10 @@ Supported types: Vertical, Vertical Fractured, and Horizontal Multi-Fractured We
Fitting Curve
拟合曲线
+
+ Pre-adjustment
+ 预调整中
+
nmWxChangeAnal
diff --git a/Include/nmNum/nmCalculation/nmCalculationAutoFitLM.h b/Include/nmNum/nmCalculation/nmCalculationAutoFitLM.h
index c700c7ab..579abe33 100644
--- a/Include/nmNum/nmCalculation/nmCalculationAutoFitLM.h
+++ b/Include/nmNum/nmCalculation/nmCalculationAutoFitLM.h
@@ -48,6 +48,13 @@ struct AutoFitObjectiveBreakdownLM {
bool registrationAmbiguous;
// 固定 log-time 网格上的双曲线斜率残差,独立于数值残差的采样层级。
QVector shapeResiduals;
+ // 前期数值残差为 81 点,平行程度残差为 73 个斜率区间,均含第一窗口权重。
+ // 平行误差比较模拟与目标各自的压力—导数斜率差,不要求模拟自身斜率差为零。
+ QVector earlyValueResiduals;
+ QVector earlyParallelResiduals;
+ double earlyValueLoss;
+ double earlyParallelLoss;
+ double earlyParallelBias;
double pressureVerticalBias;
double derivativeVerticalBias;
double shapeLoss;
@@ -70,6 +77,9 @@ struct AutoFitObjectiveBreakdownLM {
, horizontalLoss(std::numeric_limits::quiet_NaN())
, horizontalReliable(false)
, registrationAmbiguous(false)
+ , earlyValueLoss(std::numeric_limits::quiet_NaN())
+ , earlyParallelLoss(std::numeric_limits::quiet_NaN())
+ , earlyParallelBias(std::numeric_limits::quiet_NaN())
, pressureVerticalBias(0.0)
, derivativeVerticalBias(0.0)
, shapeLoss(std::numeric_limits::quiet_NaN())
diff --git a/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp b/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp
index b932eaa7..7ff0db33 100644
--- a/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp
+++ b/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp
@@ -316,14 +316,25 @@ static bool trustRegionResidualsValid(
for(int i = 0; i < breakdown.shapeResiduals.size(); ++i) {
if(!isFiniteNumber(breakdown.shapeResiduals[i])) return false;
}
+ if(breakdown.earlyValueResiduals.size() != 162 || breakdown.earlyParallelResiduals.size() != 146 ||
+ !isFiniteNumber(breakdown.earlyValueLoss) || !isFiniteNumber(breakdown.earlyParallelLoss) ||
+ !isFiniteNumber(breakdown.earlyParallelBias)) return false;
+ for(int i = 0; i < breakdown.earlyValueResiduals.size(); ++i) {
+ if(!isFiniteNumber(breakdown.earlyValueResiduals[i])) return false;
+ }
+ for(int i = 0; i < breakdown.earlyParallelResiduals.size(); ++i) {
+ if(!isFiniteNumber(breakdown.earlyParallelResiduals[i])) return false;
+ }
return true;
}
-// 两类残差在同一次真实评价中获得,联合缓存使阶段切换不必重算灵敏度。
+// 数值、形状和前期平行程度残差共用一次求解,联合缓存供各子阶段复用。
static QVector trustRegionFullResidual(const AutoFitObjectiveBreakdownLM& objective)
{
QVector residual = objective.residualVector;
residual += objective.shapeResiduals;
+ residual += objective.earlyValueResiduals;
+ residual += objective.earlyParallelResiduals;
return residual;
}
@@ -337,6 +348,70 @@ static double trustRegionSquaredNorm(const QVector& values)
return sum;
}
+// 第一窗口的形状能量与局部 Fisher 使用相同的中心时间和重叠权重,
+// 压力、导数同时参与;不重新插值或调用求解器。
+static double trustRegionEarlyShapeEnergy(const QVector& residual)
+{
+ const int pointCount = residual.size() / 2;
+ double energy = 0.0;
+ for(int row = 0; row < residual.size(); ++row) {
+ const double coordinate = (row % pointCount + 4.0) / 80.0;
+ energy += autoFitTimeWindowWeight(coordinate, 0) * residual[row] * residual[row];
+ }
+ return energy;
+}
+
+// 比较模拟与目标各自的压力—导数斜率差;误差为零表示两组曲线的相对走势一致。
+// 单独平移任一曲线不改变此指标;前期数值误差仅作诊断,不参与井储、表皮验收。
+static void populateEarlyWellboreMetrics(AutoFitObjectiveBreakdownLM* objective,
+ const QVector targetLogs[2], const QVector resultLogs[2], double logTimeSpan)
+{
+ const int count = 81;
+ QVector weights(count, 0.0);
+ double weightSum = 0.0;
+ for(int i = 0; i < count; ++i) {
+ weights[i] = autoFitTimeWindowWeight(i / 80.0, 0) * ((i == 0 || i == count - 1) ? 0.5 : 1.0);
+ weightSum += weights[i];
+ }
+ objective->earlyValueResiduals.fill(0.0, 2 * count);
+ for(int i = 0; i < count; ++i) {
+ const double weight = weights[i] / weightSum;
+ for(int component = 0; component < 2; ++component) {
+ const int row = component * count + i;
+ const double scale = qSqrt(0.5 * weight);
+ objective->earlyValueResiduals[row] = scale * (resultLogs[component][i] - targetLogs[component][i]);
+ }
+ }
+ objective->earlyValueLoss = qSqrt(trustRegionSquaredNorm(objective->earlyValueResiduals));
+
+ // 复用形状指标的 8 点跨度,窗口权重取斜率区间中心,减少相邻点噪声。
+ const int lag = 8;
+ const int slopeCount = count - lag;
+ const double logTimeStep = logTimeSpan * lag / (count - 1);
+ QVector parallelWeights(slopeCount, 0.0);
+ double parallelWeightSum = 0.0;
+ for(int i = 0; i < slopeCount; ++i) {
+ parallelWeights[i] = autoFitTimeWindowWeight((i + lag * 0.5) / (count - 1), 0);
+ parallelWeightSum += parallelWeights[i];
+ }
+ objective->earlyParallelResiduals.fill(0.0, 2 * slopeCount);
+ objective->earlyParallelBias = 0.0;
+ for(int i = 0; i < slopeCount; ++i) {
+ const double resultSlopeDifference = ((resultLogs[0][i + lag] - resultLogs[0][i]) -
+ (resultLogs[1][i + lag] - resultLogs[1][i])) / logTimeStep;
+ const double targetSlopeDifference = ((targetLogs[0][i + lag] - targetLogs[0][i]) -
+ (targetLogs[1][i + lag] - targetLogs[1][i])) / logTimeStep;
+ const double relativeSlopeError = resultSlopeDifference - targetSlopeDifference;
+ const double weight = parallelWeights[i] / parallelWeightSum;
+ objective->earlyParallelBias += weight * relativeSlopeError;
+ // 两半各占一半能量以兼容窗口 Fisher;保留相对目标的斜率差残差符号。
+ const double residual = qSqrt(0.5 * weight) * relativeSlopeError;
+ objective->earlyParallelResiduals[i] = residual;
+ objective->earlyParallelResiduals[slopeCount + i] = residual;
+ }
+ objective->earlyParallelLoss = qSqrt(trustRegionSquaredNorm(objective->earlyParallelResiduals));
+}
+
// 计算同维向量内积;维度不一致表示局部模型无效,返回零让调用方放弃修正。
static double trustRegionDotProduct(const QVector& left,
const QVector& right)
@@ -421,6 +496,25 @@ static bool solveTrustRegionLinearSystem(
return true;
}
+// 半长候选只由当前值和用户边界生成;投影后去重,不把某个半长写成目标值。
+static QVector trustRegionShapeLengthTrials(double value, double lower, double upper)
+{
+ QVector trials;
+ if(!isFiniteNumber(value) || !isFiniteNumber(lower) || !isFiniteNumber(upper) || upper <= lower)
+ return trials;
+ const double raw[] = {value * 2.0, value * 0.5, value * 2.0 < upper ? upper : lower};
+ for(int i = 0; i < 3; ++i) {
+ const double trial = qBound(lower, raw[i], upper);
+ const double tolerance = 1.0e-12 * qMax(1.0, qAbs(trial));
+ if(qAbs(trial - value) <= tolerance) continue;
+ bool duplicate = false;
+ for(int j = 0; j < trials.size(); ++j)
+ if(qAbs(trial - trials[j]) <= tolerance) duplicate = true;
+ if(!duplicate) trials.append(trial);
+ }
+ return trials;
+}
+
// Fisher 仅作为当前归一化坐标下的局部信息矩阵,不用于统计置信区间。
struct TrustRegionFisher
{
@@ -595,43 +689,76 @@ static bool buildTrustRegionFisherStep(
(*step)[selected[i]] = solution[i];
}
}
- double norm = qSqrt(trustRegionSquaredNorm(*step));
- if(!solved || !isFiniteNumber(norm) || norm < minimumStep) {
- step->fill(0.0, dimensions);
- for(int i = 0; i < count; ++i) {
- const int p = selected[i];
- double direction = -global.gradient[p];
- if((coordinates[p] <= minimumStep && direction < 0.0) ||
- (coordinates[p] >= 1.0 - minimumStep && direction > 0.0)) {
- direction = 0.0;
- }
- (*step)[p] = direction;
+ auto projectAndPredict = [&]() -> bool {
+ const double norm = qSqrt(trustRegionSquaredNorm(*step));
+ if(!isFiniteNumber(norm) || norm < minimumStep) return false;
+ if(norm > trustRadius) {
+ for(int p = 0; p < dimensions; ++p) (*step)[p] *= trustRadius / norm;
}
- norm = qSqrt(trustRegionSquaredNorm(*step));
- if(norm > minimumStep) {
- for(int p = 0; p < dimensions; ++p) {
- (*step)[p] *= trustRadius / norm;
- }
- norm = trustRadius;
+ for(int p = 0; p < dimensions; ++p) {
+ (*step)[p] = qBound(0.0, coordinates[p] + (*step)[p], 1.0) - coordinates[p];
}
- }
- if(norm > trustRadius) {
+ // 只比较投影后可执行步长的下降,阻尼项不属于真实拟合目标。
+ *predictedReduction = -trustRegionDotProduct(global.gradient, *step);
for(int p = 0; p < dimensions; ++p) {
- (*step)[p] *= trustRadius / norm;
+ for(int q = 0; q < dimensions; ++q) {
+ *predictedReduction -= 0.5 * (*step)[p] * global.matrix[p][q] * (*step)[q];
+ }
}
+ return isFiniteNumber(*predictedReduction) && *predictedReduction > 1.0e-14 &&
+ qSqrt(trustRegionSquaredNorm(*step)) >= minimumStep;
+ };
+ if(solved && projectAndPredict()) return true;
+
+ // 联合解可能被边界投影破坏;此时尝试可行梯度方向,不直接判为没有下降方向。
+ step->fill(0.0, dimensions);
+ for(int i = 0; i < count; ++i) {
+ const int p = selected[i];
+ double direction = -global.gradient[p];
+ if((coordinates[p] <= minimumStep && direction < 0.0) ||
+ (coordinates[p] >= 1.0 - minimumStep && direction > 0.0)) direction = 0.0;
+ (*step)[p] = direction;
}
+ const double norm = qSqrt(trustRegionSquaredNorm(*step));
+ if(!isFiniteNumber(norm) || norm < minimumStep) return false;
for(int p = 0; p < dimensions; ++p) {
- (*step)[p] = qBound(0.0, coordinates[p] + (*step)[p], 1.0) - coordinates[p];
+ (*step)[p] = qBound(0.0, coordinates[p] + (*step)[p] * trustRadius / norm, 1.0) - coordinates[p];
}
- // 只比较实际可执行步长的全局预测下降,阻尼项不属于真实拟合目标。
- *predictedReduction = -trustRegionDotProduct(global.gradient, *step);
+ const double linearReduction = -trustRegionDotProduct(global.gradient, *step);
+ double curvature = 0.0;
for(int p = 0; p < dimensions; ++p) {
for(int q = 0; q < dimensions; ++q) {
- *predictedReduction -= 0.5 * (*step)[p] * global.matrix[p][q] * (*step)[q];
+ curvature += (*step)[p] * global.matrix[p][q] * (*step)[q];
}
}
- return isFiniteNumber(*predictedReduction) && *predictedReduction > 1.0e-14 &&
- qSqrt(trustRegionSquaredNorm(*step)) >= minimumStep;
+ if(!isFiniteNumber(linearReduction) || !isFiniteNumber(curvature) || linearReduction <= 0.0) return false;
+ // 沿投影梯度最小化局部二次模型,只缩步,不突破信赖域或参数边界。
+ const double scale = curvature > 0.0 ? qMin(1.0, linearReduction / curvature) : 1.0;
+ for(int p = 0; p < dimensions; ++p) (*step)[p] *= scale;
+ return projectAndPredict();
+}
+
+// 放大已被真实结果验证可靠的联合方向,仍受最大半径和参数边界限制。
+static bool buildExpandedTrustRegionStep(const TrustRegionFisher& information,
+ const QVector& coordinates, const QVector& originalStep,
+ double maximumRadius, QVector* expandedStep, double* prediction)
+{
+ 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) && *prediction > 1.0e-14;
}
// 每次得到有效真实候选后,使用满足最新割线条件的秩一修正更新完整残差
@@ -1004,7 +1131,8 @@ void nmCalculationAutoFitLM::writeTraceHeader()
cols << "sampling_mode" << "sampling_stride" << "sampling_points"
<< "full_target_points" << "layer_objective"
- << "pressure_vertical_bias" << "derivative_vertical_bias";
+ << "pressure_vertical_bias" << "derivative_vertical_bias" << "first_window_shape_loss"
+ << "early_value_loss" << "early_parallel_loss" << "early_parallel_bias";
QTextStream out(&m_traceFile);
out << cols.join(",") << "\n";
}
@@ -1041,11 +1169,29 @@ void nmCalculationAutoFitLM::writeTraceMetaFile()
QTextStream out(&metaFile);
out << "{\n";
- out << " \"schema_version\": 10,\n";
- out << " \"strategy\": \"permeability_height_then_shape_then_original_lm\",\n";
+ out << " \"schema_version\": 20,\n";
+ out << " \"strategy\": \"permeability_height_then_shape_then_joint_lm\",\n";
+ out << " \"shape_priority\": \"storage_then_skin_then_shape_without_wellbore_then_optional_wellbore_recheck\",\n";
out << " \"shape_metric\": \"pressure_and_derivative_log_slopes_81_points_lag_8\",\n";
+ out << " \"early_parallel_metric\": \"pressure_derivative_log_slope_difference_vs_target_81_points_lag_8\",\n";
+ out << " \"early_wellbore_acceptance\": \"relative_slope_matching_decrease_only\",\n";
+ out << " \"early_wellbore_total_constraint\": false,\n";
out << " \"shape_stage_total_tolerance\": \"max(0.02, 25% of stage entry total)\",\n";
+ out << " \"shape_wellbore_parameters_frozen\": true,\n";
+ out << " \"shape_sensitivity_refresh\": \"full Jacobian at joint shape entry and after accepted length exploration\",\n";
+ out << " \"shape_length_exploration\": \"each complete shape round: after 3 ordinary candidates or before round completion; bounded 2x, 0.5x and far-bound trials with optional permeability correction\",\n";
+ out << " \"shape_round_extra_evaluation_limit\": 8,\n";
+ out << " \"shape_expansion_policy\": \"Dfc joint step up to 2x when actual rho exceeds 0.75; retain ordinary step on failure\",\n";
+ out << " \"stage2_budget\": \"adaptive, no fixed iteration or evaluation quota; full window/global/length 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 << " \"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 total and shape limits, each max(0.0001, 5%)\",\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 << " \"total_stage_shape_constraint\": false,\n";
+ out << " \"total_parameter_selection\": \"all_valid_free_columns\",\n";
+ out << " \"skin_difference_policy\": \"local_scale_in_all_stages\",\n";
+ out << " \"difference_failure_policy\": \"halve_before_opposite_direction\",\n";
out << " \"trace_type\": \"finite_difference_lm_trust_region\",\n";
out << " \"run_id\": " << jsonEscape(m_traceRunId) << ",\n";
out << " \"created_at\": "
@@ -1163,7 +1309,12 @@ void nmCalculationAutoFitLM::writeTraceRow(
<< (objectiveBreakdown ? QString::number(objectiveBreakdown->fullPointCount) : QString())
<< (objectiveBreakdown ? traceNumber(objectiveBreakdown->layerError) : QString())
<< (objectiveBreakdown ? traceNumber(objectiveBreakdown->pressureVerticalBias) : QString())
- << (objectiveBreakdown ? traceNumber(objectiveBreakdown->derivativeVerticalBias) : QString());
+ << (objectiveBreakdown ? traceNumber(objectiveBreakdown->derivativeVerticalBias) : QString())
+ << (objectiveBreakdown && objectiveBreakdown->valid
+ ? traceNumber(qSqrt(trustRegionEarlyShapeEnergy(objectiveBreakdown->shapeResiduals))) : QString())
+ << (objectiveBreakdown && objectiveBreakdown->valid ? traceNumber(objectiveBreakdown->earlyValueLoss) : QString())
+ << (objectiveBreakdown && objectiveBreakdown->valid ? traceNumber(objectiveBreakdown->earlyParallelLoss) : QString())
+ << (objectiveBreakdown && objectiveBreakdown->valid ? traceNumber(objectiveBreakdown->earlyParallelBias) : QString());
QTextStream out(&m_traceFile);
out << cols.join(",") << "\n";
m_traceFile.flush();
@@ -1628,7 +1779,7 @@ bool nmCalculationAutoFitLM::startAutoFitting()
emit logMessageGenerated(tr("=== LM AUTOMATIC FITTING - LOCAL OPTIMUM ==="));
} else if(finalReason == LM_MAX_ITERATIONS) {
success = true;
- message = QString(tr("Max iterations reached. Best error: %1, Iterations: %2"))
+ message = QString(tr("Total-stage budget reached. Best error: %1, Cumulative iterations: %2"))
.arg(m_globalBestFitness, 0, 'e', 4)
.arg(m_currentIteration + 1);
emit logMessageGenerated(tr("=== LM AUTOMATIC FITTING - MAX ITERATIONS ==="));
@@ -1658,7 +1809,6 @@ bool nmCalculationAutoFitLM::startAutoFitting()
}
emitRunSummary(success, finalReason);
- emit progressUpdated(m_maxIterations, m_globalBestFitness);
QApplication::processEvents();
msleep(200);
QApplication::processEvents();
@@ -1801,11 +1951,11 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
return LM_OPTIMIZATION_FAILED;
}
- // 真实求解次数比“外层迭代次数”更能反映耗时。预算至少允许完成一次全参数
- // 灵敏度和两次候选评价,同时避免连续重建 Jacobian 导致运行时间失控。
- const int maximumEvaluations = qMax(
- m_totalEvaluations + dimensions + 2,
- qMax(20, m_maxIterations * 3));
+ // 用户迭代设置只约束第三阶段;真实评价额度也在进入该阶段时独立起算。
+ // 前期调整按改善情况结束,不能提前消耗掉最终联合 LM 的迭代和求解机会。
+ const int totalEvaluationBudget = qMax(dimensions + 2, qMax(20, m_maxIterations * 3));
+ int maximumEvaluations = m_totalEvaluations + totalEvaluationBudget;
+ int totalIterations = 0;
// 下列步长均位于归一化内部坐标:0.04 表示参数范围的 4%,信赖半径
// 限制一次联合移动的二范数,相关性门槛用于排除响应近乎共线的参数。
const double sensitivityStep = 0.04;
@@ -1938,11 +2088,12 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
}
restoreEvaluationState(current);
+ emit progressUpdated(-1, current.fitness);
emit logMessageGenerated(tr("=== Starting LM Main Loop ==="));
emit logMessageGenerated(
- tr("LM starting point error: %1; evaluation budget: %2")
+ tr("LM starting point error: %1; independent total-stage evaluation budget: %2")
.arg(current.fitness, 0, 'e', 4)
- .arg(maximumEvaluations));
+ .arg(totalEvaluationBudget));
// 高度预调整只改变用户勾选的渗透率。ln(k) 的初始变化由有符号高度差
// 给出,真实求解后用割线估计修正;拒绝时缩步,不让其他参数补偿高度。
@@ -2010,18 +2161,57 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
if(m_shouldStop) return LM_USER_STOPPED;
- // 形状阶段不设达标阈值;连续三步无有效改善或耗用约三分之一预算即切换。
- // 允许整体误差适度回升,但上限固定在高度对齐后,禁止逐步放宽导致漂移。
- bool shapeStage = m_maxIterations >= 3 && current.fitness >= m_targetError;
- const double shapeValueLimit = current.fitness + qMax(0.02, 0.25 * current.fitness);
- const int shapeEvaluationDeadline = m_totalEvaluations + qMax(0,
- (maximumEvaluations - m_totalEvaluations - dimensions - 2) / 3);
- const int shapeIterationLimit = qMax(1, m_maxIterations / 3);
+ // 第二阶段不按迭代比例截断;普通形状按完整搜索轮次判断是否仍有显著改善。
+ // 井储、表皮保持冻结,总误差上限仍固定于普通形状阶段入口。
+ bool shapeStage = current.fitness >= m_targetError;
+ double shapeValueLimit = current.fitness + qMax(0.02, 0.25 * current.fitness);
+ bool jointShapeStarted = false;
+ double shapeRoundBaseline = current.breakdown.shapeLoss;
+ int shapeRoundNumber = 0;
+ int ineffectiveShapeRounds = 0;
+ bool shapeConfirmationRequested = false;
+ int shapeCandidateCount = 0;
+ const int fractureLengthColumn = m_enabledParamIndices.indexOf(6);
+ const int conductivityColumn = m_enabledParamIndices.indexOf(5);
+ // 每轮探索有限个候选,但不会因全流程累计次数截断半长与渗透率组合。
+ const int maximumShapeExplorationEvaluations = 8;
+ int shapeExplorationEvaluations = 0;
+ bool shapeExplorationDone = fractureLengthColumn < 0;
+ 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 recheckTotalLimit = 0.0;
+ double recheckShapeLimit = 0.0;
+ // 先井储、再表皮;回检沿用相同顺序和前期指标,不回到形状阶段反复循环。
+ bool earlyShapeStage = shapeStage && wellboreParameterCount > 0;
+ int earlyShapeParameter = storageColumn >= 0 ? 2 : 1;
+ auto parameterAllowedInStage = [&](int column) -> bool {
+ const int parameterIndex = m_enabledParamIndices[column];
+ // 候选和缓存灵敏度探针共用此限制,防止探针绕过形状阶段的冻结。
+ if(!shapeStage) return true;
+ if(earlyShapeStage) return parameterIndex == earlyShapeParameter;
+ return parameterIndex != 1 && parameterIndex != 2;
+ };
auto stageError = [&](const TrustRegionEvaluation& point) -> double {
+ if(earlyShapeStage) return point.breakdown.earlyParallelLoss;
return shapeStage ? point.breakdown.shapeLoss : point.fitness;
};
auto acceptable = [&](const TrustRegionEvaluation& point, const TrustRegionEvaluation& base) -> bool {
if(!point.valid) return false;
+ if(earlyShapeStage) {
+ // 初调保持原规则;回检还需保护整轮入口的总误差和形状,不能逐步放宽。
+ return point.breakdown.earlyParallelLoss < base.breakdown.earlyParallelLoss &&
+ (!wellboreRecheck || (point.fitness <= recheckTotalLimit &&
+ point.breakdown.shapeLoss <= recheckShapeLimit));
+ }
return shapeStage
? point.breakdown.shapeLoss < base.breakdown.shapeLoss && point.fitness <= shapeValueLimit
// 预调整结束后恢复原 LM:有效候选只按整体误差下降接受。
@@ -2033,8 +2223,13 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
restoreEvaluationState(current);
};
emit logMessageGenerated(shapeStage
- ? tr("LM stage 2: optimize pressure and derivative shape; stop after 3 ineffective steps.")
+ ? tr("LM stage 2: adaptive shape search; confirm stagnation after 2 complete rounds without significant improvement.")
: tr("LM stage 3: original LM fitting; accept by total error only."));
+ if(earlyShapeStage) {
+ emit logMessageGenerated(earlyShapeParameter == 2
+ ? tr("Early adjustment: adjust wellbore storage, then skin, to match the early pressure-derivative slope difference of the target.")
+ : tr("Early adjustment: adjust skin to match the early pressure-derivative slope difference of the target."));
+ }
// 有效改善始终相对“上一次有效改善后的误差”累计判断,避免一连串微小
// 下降每次都清零计数;累计达到门槛后才开始新的有效改善基准。
@@ -2085,21 +2280,18 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
};
emit logMessageGenerated(m_layeredSampling
- ? tr("LM sampling: layered target points (%1 / %2); acceptance uses all valid target points.")
+ ? tr("LM sampling: layered target points (%1 / %2); total-stage acceptance uses all valid target points.")
.arg(current.breakdown.residualVector.size() / 2).arg(current.breakdown.fullPointCount)
: tr("LM sampling: fixed 80 points (original mode)."));
- auto enterTotalStage = [&](const QString& reason) {
- // 保留当前曲线和完整 J,只重置阶段停滞状态、阻尼及信赖半径。
+ 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;
- shapeStage = false;
- effectiveImprovementBaseline = current.fitness;
consecutiveIneffectiveSteps = 0;
consecutiveRejectedSteps = 0;
consecutiveSolverFailures = 0;
@@ -2108,13 +2300,205 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
globalFallbackAttempted = false;
trustRadius = 0.12;
damping = 0.01;
+ };
+ auto exploreShapeLengths = [&]() -> bool {
+ if(shapeExplorationDone || !jointShapeStarted || earlyShapeStage || wellboreRecheck)
+ return false;
+ const TrustRegionEvaluation base = current;
+ TrustRegionEvaluation best;
+ const QVector trials = trustRegionShapeLengthTrials(base.parameters[fractureLengthColumn],
+ m_parameterLower[6], m_parameterUpper[6]);
+ const int explorationLimit = qMin(maximumShapeExplorationEvaluations, shapeExplorationEvaluations + 6);
+ auto canEvaluate = [&]() -> bool {
+ return shapeExplorationEvaluations < explorationLimit && processPauseAndStop();
+ };
+ auto keepBetter = [&](const TrustRegionEvaluation& point) {
+ if(acceptable(point, base) && (!best.valid || point.breakdown.shapeLoss < best.breakdown.shapeLoss ||
+ (point.breakdown.shapeLoss == best.breakdown.shapeLoss && point.fitness < best.fitness))) best = point;
+ };
+ for(int i = 0; i < trials.size() && canEvaluate(); ++i) {
+ TrustRegionEvaluation probe;
+ probe.parameters = base.parameters;
+ probe.parameters[fractureLengthColumn] = trials[i];
+ probe.coordinates = coordinatesFromParameters(probe.parameters);
+ ++shapeExplorationEvaluations;
+ probe.valid = evaluateTrustRegionPoint(probe.parameters, &probe.fitness,
+ &probe.breakdown, &probe.curve, &probe.elapsedMs);
+ writeTraceRow(m_currentIteration, fractureLengthColumn, "shape_length_probe", probe.parameters,
+ probe.fitness, probe.valid, probe.elapsedMs, probe.valid ? "probe_valid" : "solver_invalid",
+ probe.valid ? &probe.breakdown : nullptr);
+ if(probe.valid) {
+ keepBetter(probe);
+ // 半长点即使暂时超出总误差上限,仍允许一次渗透率补偿后验收组合。
+ // 沿用高度预调的有符号偏差与初始斜率 -1,远点修正必须真实求解验证。
+ if(permeabilityColumn >= 0 && probe.breakdown.verticalReliable &&
+ qAbs(probe.breakdown.verticalCommonBias) > 0.01 && m_parameterLower[0] > 0.0 &&
+ m_parameterUpper[0] > m_parameterLower[0] && canEvaluate()) {
+ TrustRegionEvaluation corrected;
+ corrected.parameters = probe.parameters;
+ const double logRange = qLn(m_parameterUpper[0]) - qLn(m_parameterLower[0]);
+ const double change = qBound(-0.30 * logRange, probe.breakdown.verticalCommonBias, 0.30 * logRange);
+ const double k = probe.parameters[permeabilityColumn];
+ corrected.parameters[permeabilityColumn] = qBound(m_parameterLower[0], k * qExp(change), m_parameterUpper[0]);
+ corrected.coordinates = coordinatesFromParameters(corrected.parameters);
+ if(qAbs(qLn(corrected.parameters[permeabilityColumn] / k)) > 1.0e-5) {
+ ++shapeExplorationEvaluations;
+ corrected.valid = evaluateTrustRegionPoint(corrected.parameters, &corrected.fitness,
+ &corrected.breakdown, &corrected.curve, &corrected.elapsedMs);
+ writeTraceRow(m_currentIteration, permeabilityColumn, "shape_length_height_probe", corrected.parameters,
+ corrected.fitness, corrected.valid, corrected.elapsedMs, corrected.valid ? "probe_valid" : "solver_invalid",
+ corrected.valid ? &corrected.breakdown : nullptr);
+ if(corrected.valid) keepBetter(corrected);
+ }
+ }
+ }
+ restoreEvaluationState(base);
+ }
+ restoreEvaluationState(base);
+ // 停止时不把未完成的探索记为完成;正常情况下至多六次调用即可覆盖本轮候选。
+ if(m_shouldStop) return false;
+ shapeExplorationDone = true;
+ if(!best.valid) return false;
+ acceptPoint(best);
+ // 不用跨越不同半长基点的割线修补旧模型;在最终选中的新点重新测完整 J。
+ jacobian.clear();
+ reuseSensitivityForNextStage();
+ rebuildRequested = true;
+ rebuildReason = QT_TR_NOOP("fresh sensitivity after half-length exploration");
+ effectiveImprovementBaseline = current.breakdown.shapeLoss;
+ writeTraceRow(m_currentIteration, fractureLengthColumn, "shape_exploration_accept", current.parameters,
+ current.fitness, true, 0, "length_permeability_combination_accepted", ¤t.breakdown);
+ emit logMessageGenerated(tr("Half-length exploration accepted: L=%1, shape error=%2, total error=%3.")
+ .arg(current.parameters[fractureLengthColumn], 0, 'g', 6)
+ .arg(current.breakdown.shapeLoss, 0, 'g', 6).arg(current.fitness, 0, 'g', 6));
+ return true;
+ };
+
+ auto enterTotalStage = [&](const QString& reason) {
+ const bool finishedRecheck = wellboreRecheck;
+ totalIterations = 0;
+ maximumEvaluations = m_totalEvaluations + totalEvaluationBudget;
+ shapeStage = false;
+ earlyShapeStage = false;
+ wellboreRecheck = false;
+ 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, "shape_to_total", ¤t.breakdown);
+ true, 0, finishedRecheck ? "wellbore_recheck_to_total" : "shape_to_total", ¤t.breakdown);
+ };
+
+ auto finishShapeStage = [&](const QString& reason) {
+ if(m_shouldStop) return;
+ // 只检查一次;用绝对加相对容差区分有意义的退化和接近零时的比例放大。
+ if(wellboreRecheckDone || wellboreParameterCount == 0 || !hasRemainingShapeParameters) {
+ enterTotalStage(reason);
+ return;
+ }
+ wellboreRecheckDone = true;
+ const double earlyTolerance = qMax(1.0e-4, 0.10 * earlyWellboreBaseline);
+ if(current.breakdown.earlyParallelLoss <= earlyWellboreBaseline + earlyTolerance) {
+ writeTraceRow(m_currentIteration, -1, "stage_switch", current.parameters, current.fitness,
+ true, 0, "wellbore_recheck_skipped_no_degradation", ¤t.breakdown);
+ enterTotalStage(reason);
+ return;
+ }
+ wellboreRecheck = true;
+ earlyShapeStage = true;
+ earlyShapeParameter = storageColumn >= 0 ? 2 : 1;
+ recheckTotalLimit = current.fitness + qMax(1.0e-4, 0.05 * current.fitness);
+ recheckShapeLimit = current.breakdown.shapeLoss + qMax(1.0e-4, 0.05 * current.breakdown.shapeLoss);
+ effectiveImprovementBaseline = current.breakdown.earlyParallelLoss;
+ // 其他参数已改变,回检入口重测两列,不能沿用初调时的响应方向。
+ jacobian.clear();
+ reuseSensitivityForNextStage();
+ rebuildRequested = true;
+ rebuildReason = QT_TR_NOOP("new sensitivity for wellbore recheck");
+ emit logMessageGenerated(tr("Wellbore recheck: early error=%1, total limit=%2, shape limit=%3.")
+ .arg(current.breakdown.earlyParallelLoss, 0, 'g', 6)
+ .arg(recheckTotalLimit, 0, 'g', 6).arg(recheckShapeLimit, 0, 'g', 6));
+ writeTraceRow(m_currentIteration, -1, "stage_switch", current.parameters, current.fitness,
+ true, 0, "shape_to_wellbore_recheck", ¤t.breakdown);
+ };
+
+ auto finishEarlyShapeStage = [&](const QString& reason) {
+ if(earlyShapeParameter == 2 && skinColumn >= 0) {
+ earlyShapeParameter = 1;
+ effectiveImprovementBaseline = current.breakdown.earlyParallelLoss;
+ reuseSensitivityForNextStage();
+ 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", ¤t.breakdown);
+ return;
+ }
+ 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;
+ // 井储、表皮可使总误差上升;联合形状阶段从当前工作点建立固定上限。
+ shapeValueLimit = current.fitness + qMax(0.02, 0.25 * current.fitness);
+ reuseSensitivityForNextStage();
+ emit logMessageGenerated(tr("Shape fitting continues with storage and skin fixed; a conditional wellbore recheck follows."));
+ writeTraceRow(m_currentIteration, -1, "stage_switch", current.parameters, current.fitness,
+ true, 0, "early_wellbore_to_shape", ¤t.breakdown);
+ };
+
+ auto completeShapeSearchRound = [&]() {
+ if(!shapeStage || earlyShapeStage || !globalFallbackAttempted) return;
+ // 即使所有局部方向都不可行,也先完成半长组合搜索。大步接受后需在新点重走窗口。
+ if(!shapeExplorationDone && exploreShapeLengths()) return;
+ if(m_shouldStop || !shapeExplorationDone) 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), ¤t.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;
+ }
+ // 下一轮重新覆盖窗口、全局和半长;单步的小改善不会把当前一轮无限延长。
+ shapeRoundBaseline = current.breakdown.shapeLoss;
+ attemptedWindows.fill(false);
+ globalFallbackAttempted = false;
+ shapeCandidateCount = 0;
+ shapeExplorationEvaluations = 0;
+ shapeExplorationDone = fractureLengthColumn < 0;
+ if(!improved && ineffectiveShapeRounds >= 2) {
+ jacobian.clear();
+ reuseSensitivityForNextStage();
+ rebuildRequested = true;
+ rebuildReason = QT_TR_NOOP("confirm stagnation after 2 complete shape rounds");
+ shapeConfirmationRequested = true;
+ }
};
auto registerEffectiveImprovement = [&](double fitness) -> bool {
+ if(shapeStage && !earlyShapeStage) return false;
const double requiredImprovement = qMax(
shapeStage ? 1.0e-4 : effectiveAbsoluteImprovement,
qAbs(effectiveImprovementBaseline) *
@@ -2136,9 +2520,15 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
// 无效尝试仍累计,但所有窗口及一次全局回退尝试完毕前,不触发停滞收敛。
// 完整一轮仍无改善时沿用原有重建确认,避免某个难处理窗口提前终止拟合。
auto recordIneffectiveStep = [&]() -> bool {
+ if(shapeStage && !earlyShapeStage) {
+ completeShapeSearchRound();
+ return false;
+ }
++consecutiveIneffectiveSteps;
- if(shapeStage) {
- if(consecutiveIneffectiveSteps >= maximumIneffectiveSteps) enterTotalStage(tr("3 consecutive steps without effective shape improvement"));
+ if(earlyShapeStage) {
+ // 一次拒绝只缩步,再给当前参数一次真实尝试;连续无改善才交给下一段。
+ if(consecutiveIneffectiveSteps >= 2)
+ finishEarlyShapeStage(tr("2 consecutive steps without effective early improvement"));
return false;
}
if(!globalFallbackAttempted) {
@@ -2172,14 +2562,16 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
.arg(effectiveRelativeImprovement * 100.0, 0, 'f', 2)
.arg(maximumIneffectiveSteps));
- if(current.fitness < m_targetError) {
+ if(!shapeStage && current.fitness < m_targetError) {
promoteSampling(true);
return samplingRefreshFailed ? LM_OPTIMIZATION_FAILED : LM_TARGET_ACHIEVED;
}
- // 在同一个真实工作点逐参数做单边差分。首选可用空间更大的方向;只有该方向
- // 求解失败时才补算反方向,因此初次建模通常每个参数只增加一次真实求解。
+ // 在同一个真实工作点逐参数做单边差分。失败先在原方向缩步,再反向尝试;
+ // 正常情况下每列仍只需一次真实求解,重试也计入总评价预算。
auto rebuildSensitivity = [&]() -> bool {
+ // 第二阶段差分不受第三阶段额度限制;每列仍只有有限的缩步和反向重试。
+ const int evaluationLimit = shapeStage ? (std::numeric_limits::max)() : maximumEvaluations;
const TrustRegionEvaluation base = current;
const QVector baseResidual = trustRegionFullResidual(base.breakdown);
const int residualCount = baseResidual.size();
@@ -2202,105 +2594,118 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
for(int column = 0;
column < dimensions &&
- m_totalEvaluations < maximumEvaluations &&
+ m_totalEvaluations < evaluationLimit &&
processPauseAndStop();
++column) {
- // 单边差分优先选择离边界空间更大的方向;首方向求解无效时才反向
- // 补算,因此正常情况下每个参数只消耗一次真实求解。
+ const int parameterIndex = m_enabledParamIndices[column];
+ if(earlyShapeStage && parameterIndex != 1 && parameterIndex != 2) continue;
+ const double lower = m_parameterLower[parameterIndex];
+ const double upper = m_parameterUpper[parameterIndex];
+ if(upper <= lower) continue;
+ // 表皮在所有阶段均按当前物理尺度扰动,避免整体阶段退回范围的 4%。
+ double localStep = finiteDifferenceStep;
+ if(parameterIndex == 1) {
+ localStep = qMin(localStep, 0.02 * qMax(0.1, qAbs(base.parameters[column])) / (upper - lower));
+ } else if(shapeStage && useTrustRegionLogScale(parameterIndex, lower, upper)) {
+ localStep = qMin(localStep, qLn(1.05) / (qLn(upper) - qLn(lower)));
+ }
double positiveRoom = 1.0 - base.coordinates[column];
double negativeRoom = base.coordinates[column];
double preferredSign = positiveRoom >= negativeRoom ? 1.0 : -1.0;
bool columnBuilt = false;
+ double probeStep = localStep;
for(int directionAttempt = 0;
directionAttempt < 2 &&
!columnBuilt &&
- m_totalEvaluations < maximumEvaluations;
+ m_totalEvaluations < evaluationLimit && processPauseAndStop();
++directionAttempt) {
double direction = directionAttempt == 0
? preferredSign : -preferredSign;
double availableRoom = direction > 0.0
? positiveRoom : negativeRoom;
- const int parameterIndex = m_enabledParamIndices[column];
- const double lower = m_parameterLower[parameterIndex];
- const double upper = m_parameterUpper[parameterIndex];
- double localStep = finiteDifferenceStep;
- if(shapeStage && upper > lower && parameterIndex == 1) {
- localStep = qMin(localStep, 0.02 * qMax(0.1, qAbs(base.parameters[column])) / (upper - lower));
- } else if(shapeStage && useTrustRegionLogScale(parameterIndex, lower, upper)) {
- localStep = qMin(localStep, qLn(1.05) / (qLn(upper) - qLn(lower)));
- }
- double deltaMagnitude = qMin(localStep, availableRoom);
- if(deltaMagnitude < minimumCoordinateStep) {
- continue;
- }
-
- TrustRegionEvaluation probe;
- probe.coordinates = base.coordinates;
- probe.coordinates[column] += direction * deltaMagnitude;
- probe.parameters = parametersFromCoordinates(probe.coordinates);
- probe.valid = evaluateTrustRegionPoint(
- probe.parameters,
- &probe.fitness,
- &probe.breakdown,
- &probe.curve,
- &probe.elapsedMs);
-
- QString decision = probe.valid
- ? "sensitivity_valid"
- : (directionAttempt == 0
- ? "sensitivity_retry_opposite"
- : "sensitivity_invalid");
- writeTraceRow(m_currentIteration,
- column,
- "trust_region_sensitivity",
- probe.parameters,
- probe.fitness,
- probe.valid,
- probe.elapsedMs,
- (shapeStage ? "shape_" : "total_") + decision,
- probe.valid ? &probe.breakdown : nullptr);
-
- if(!probe.valid) {
- restoreEvaluationState(base);
- continue;
- }
+ double deltaMagnitude = qMin(probeStep, availableRoom);
+ for(int shrinkAttempt = 0; shrinkAttempt < 3 && !columnBuilt &&
+ m_totalEvaluations < evaluationLimit && processPauseAndStop(); ++shrinkAttempt) {
+ if(deltaMagnitude < minimumCoordinateStep) {
+ break;
+ }
+ const bool canShrink = shrinkAttempt < 2 && deltaMagnitude * 0.5 >= minimumCoordinateStep;
+
+ TrustRegionEvaluation probe;
+ probe.coordinates = base.coordinates;
+ probe.coordinates[column] += direction * deltaMagnitude;
+ probe.parameters = parametersFromCoordinates(probe.coordinates);
+ probe.valid = evaluateTrustRegionPoint(
+ probe.parameters,
+ &probe.fitness,
+ &probe.breakdown,
+ &probe.curve,
+ &probe.elapsedMs);
+
+ QString decision = probe.valid
+ ? "sensitivity_valid"
+ : (canShrink ? "sensitivity_retry_smaller" : (directionAttempt == 0
+ ? "sensitivity_retry_opposite"
+ : "sensitivity_invalid"));
+ writeTraceRow(m_currentIteration,
+ column,
+ "trust_region_sensitivity",
+ probe.parameters,
+ probe.fitness,
+ probe.valid,
+ probe.elapsedMs,
+ (wellboreRecheck ? "wellbore_recheck_" : (shapeStage ? "shape_" : "total_")) + decision,
+ probe.valid ? &probe.breakdown : nullptr);
+
+ if(!probe.valid) {
+ restoreEvaluationState(base);
+ if(!canShrink) break;
+ deltaMagnitude *= 0.5;
+ probeStep = deltaMagnitude;
+ continue;
+ }
- double delta = probe.coordinates[column] -
- base.coordinates[column];
- if(qAbs(delta) < minimumCoordinateStep ||
- trustRegionFullResidual(probe.breakdown).size() != residualCount) {
- restoreEvaluationState(base);
- continue;
- }
+ double delta = probe.coordinates[column] -
+ base.coordinates[column];
+ if(qAbs(delta) < minimumCoordinateStep ||
+ trustRegionFullResidual(probe.breakdown).size() != residualCount) {
+ restoreEvaluationState(base);
+ break;
+ }
- // 第 column 列是固定残差向量相对内部参数坐标的有限差分:
- // J[:,column] = (r_probe-r_base)/delta。
- const QVector probeResidual = trustRegionFullResidual(probe.breakdown);
- for(int row = 0; row < residualCount; ++row) {
- jacobian[row][column] = (probeResidual[row] - baseResidual[row]) / delta;
- }
+ // 第 column 列是固定残差向量相对内部参数坐标的有限差分:
+ // J[:,column] = (r_probe-r_base)/delta。
+ const QVector probeResidual = trustRegionFullResidual(probe.breakdown);
+ for(int row = 0; row < residualCount; ++row) {
+ jacobian[row][column] = (probeResidual[row] - baseResidual[row]) / delta;
+ }
- jacobianColumnValid[column] = true;
- columnBuilt = true;
+ jacobianColumnValid[column] = true;
+ columnBuilt = true;
- if(acceptable(probe, base) &&
- (!bestProbe.valid || stageError(probe) < stageError(bestProbe))) {
- bestProbe = probe;
- bestProbeColumn = column;
- bestProbeDelta = delta;
+ // 其他参数仍建立灵敏度供后续复用,但不能绕过当前子阶段的选参限制。
+ if(parameterAllowedInStage(column) && acceptable(probe, base) &&
+ (!bestProbe.valid || stageError(probe) < stageError(bestProbe))) {
+ bestProbe = probe;
+ bestProbeColumn = column;
+ bestProbeDelta = delta;
+ }
+ restoreEvaluationState(base);
}
- restoreEvaluationState(base);
}
}
int validColumnCount = 0;
+ bool hasActiveSensitivity = false;
for(int i = 0; i < jacobianColumnValid.size(); ++i) {
if(jacobianColumnValid[i]) {
++validColumnCount;
+ if(parameterAllowedInStage(i)) hasActiveSensitivity = true;
}
}
- if(validColumnCount == 0 || m_shouldStop) {
+ // 冻结列成功不能掩盖所有可调形状列的求解失败;有效零梯度仍由停滞逻辑处理。
+ if(validColumnCount == 0 || (shapeStage && !earlyShapeStage && !hasActiveSensitivity) || m_shouldStop) {
restoreEvaluationState(base);
return false;
}
@@ -2323,11 +2728,14 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
current.fitness,
true,
0,
- shapeStage ? "shape_accepted_cached_probe" : "total_accepted_cached_probe",
+ wellboreRecheck ? "wellbore_recheck_accepted_cached_probe" :
+ (earlyShapeStage ? "early_shape_accepted_cached_probe" :
+ (shapeStage ? "shape_accepted_cached_probe" : "total_accepted_cached_probe")),
¤t.breakdown);
emit logMessageGenerated(
- tr("Sensitivity probe accepted: total error=%1")
- .arg(current.fitness, 0, 'e', 4));
+ (earlyShapeStage ? tr("Sensitivity probe accepted: early relative-slope matching error=%1")
+ : tr("Sensitivity probe accepted: total error=%1"))
+ .arg(earlyShapeStage ? current.breakdown.earlyParallelLoss : current.fitness, 0, 'e', 4));
} else {
restoreEvaluationState(current);
}
@@ -2350,9 +2758,8 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
int completedIterations = 0;
for(int iteration = 0;
- iteration < m_maxIterations &&
- m_totalEvaluations < maximumEvaluations &&
- !m_shouldStop;
+ !m_shouldStop && (shapeStage ||
+ (totalIterations < m_maxIterations && m_totalEvaluations < maximumEvaluations));
++iteration) {
m_currentIteration = iteration;
completedIterations = iteration + 1;
@@ -2360,19 +2767,31 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
if(!processPauseAndStop()) {
break;
}
- if(shapeStage && (iteration >= shapeIterationLimit ||
- m_totalEvaluations + (rebuildRequested ? dimensions : 0) >= shapeEvaluationDeadline)) {
- enterTotalStage(tr("reserve remaining iterations and evaluations for total fitting"));
+ if(wellboreRecheck && current.breakdown.earlyParallelLoss <=
+ earlyWellboreBaseline + qMax(1.0e-4, 0.10 * earlyWellboreBaseline))
+ enterTotalStage(tr("early relative-slope error restored within tolerance"));
+ if(shapeStage && !earlyShapeStage && !jointShapeStarted) {
+ jointShapeStarted = true;
+ shapeRoundBaseline = current.breakdown.shapeLoss;
+ // 初调已改变井储/表皮,普通形状必须在新的工作点测完整响应。
+ jacobian.clear();
+ reuseSensitivityForNextStage();
+ rebuildRequested = true;
+ rebuildReason = QT_TR_NOOP("fresh sensitivity at joint shape entry");
}
+ if(shapeStage && !earlyShapeStage && shapeCandidateCount >= 3)
+ exploreShapeLengths();
+ if(m_shouldStop) break;
+ if(!shapeStage && (totalIterations >= m_maxIterations || m_totalEvaluations >= maximumEvaluations)) break;
if(!shapeStage && m_layeredSampling && m_samplingStride > 1) {
const int remainingEvaluations = maximumEvaluations - m_totalEvaluations;
// 已准备重建时,提前计入本轮差分及候选的开销,避免紧接着因预算再加密。
const bool reserveFinalBudget = remainingEvaluations <= 2 * (dimensions + 1) ||
(rebuildRequested && remainingEvaluations <= 3 * (dimensions + 1));
const int layerDeadline = qMax(1, m_maxIterations * (m_samplingStride == 4 ? 1 : 2) / 3);
- const bool mergeRefinement = rebuildRequested && iteration < layerDeadline &&
- (iteration + 1 >= layerDeadline || reserveFinalBudget);
- if(reserveFinalBudget || iteration >= layerDeadline || mergeRefinement) {
+ const bool mergeRefinement = rebuildRequested && totalIterations < layerDeadline &&
+ (totalIterations + 1 >= layerDeadline || reserveFinalBudget);
+ if(reserveFinalBudget || totalIterations >= layerDeadline || mergeRefinement) {
const bool promoted = promoteSampling(reserveFinalBudget);
if(samplingRefreshFailed) break;
if(promoted && mergeRefinement) {
@@ -2384,14 +2803,19 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
emit logMessageGenerated(tr("Rebuilding sensitivity model: %1").arg(tr(rebuildReason)));
writeTraceRow(m_currentIteration, -1, "sensitivity_rebuild", current.parameters,
current.fitness, true, 0, rebuildReason, ¤t.breakdown);
- const bool confirmingStagnation =
- stagnationConfirmationRequested;
if(!rebuildSensitivity()) {
if(shapeStage && !m_shouldStop) {
- enterTotalStage(tr("no valid shape sensitivity model"));
- rebuildRequested = true;
- rebuildReason = QT_TR_NOOP("no valid sensitivity model");
- continue;
+ if(earlyShapeStage) {
+ finishEarlyShapeStage(tr("no valid early sensitivity model"));
+ rebuildRequested = true;
+ rebuildReason = QT_TR_NOOP("no valid sensitivity model");
+ continue;
+ }
+ // 真实差分全失败不能冒充停滞收敛;独立探索若找到有效新点则重试。
+ if(exploreShapeLengths()) continue;
+ m_lastError = tr("Unable to build a valid shape sensitivity model.");
+ stopReason = LM_OPTIMIZATION_FAILED;
+ break;
}
if(promoteSampling(false)) continue;
stopReason = m_shouldStop
@@ -2399,31 +2823,40 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
: LM_LOCAL_OPTIMUM;
break;
}
- if(current.fitness < m_targetError) {
+ if(shapeStage && !earlyShapeStage && current.fitness < m_targetError) {
+ finishShapeStage(tr("total target reached during shape fitting"));
+ --iteration;
+ continue;
+ }
+ if(!shapeStage && current.fitness < m_targetError) {
promoteSampling(true);
stopReason = LM_TARGET_ACHIEVED;
break;
}
- if(m_totalEvaluations >= maximumEvaluations) {
+ if(!shapeStage && m_totalEvaluations >= maximumEvaluations) {
stopReason = LM_MAX_ITERATIONS;
break;
}
- const bool rebuildEffective =
- registerEffectiveImprovement(stageError(current));
- if(confirmingStagnation && !rebuildEffective) {
- if(promoteSampling(false)) continue;
- emit logMessageGenerated(
- tr("Sensitivity rebuild produced no effective improvement; "
- "local convergence detected"));
- stopReason = LM_LOCAL_OPTIMUM;
- break;
+ if(wellboreRecheck && current.breakdown.earlyParallelLoss <=
+ earlyWellboreBaseline + qMax(1.0e-4, 0.10 * earlyWellboreBaseline)) {
+ enterTotalStage(tr("wellbore recheck completed during sensitivity evaluation"));
+ --iteration;
+ continue;
}
+ // 单列探针没有改善不代表联合步无效;重建后继续真实候选评价,再确认停滞。
+ registerEffectiveImprovement(stageError(current));
}
+ // 第三阶段独立计数;方向不可行也消耗一次局部尝试,不能无限缩步循环。
+ if(!shapeStage) ++totalIterations;
// 每轮从最新 J 和当前残差重算窗口 Fisher,包含有限差分与割线更新的变化。
- const QVector objectiveResidual = shapeStage
- ? current.breakdown.shapeResiduals : current.breakdown.residualVector;
- const int rowOffset = shapeStage ? current.breakdown.residualVector.size() : 0;
+ const QVector objectiveResidual = earlyShapeStage
+ ? current.breakdown.earlyParallelResiduals
+ : (shapeStage ? current.breakdown.shapeResiduals : current.breakdown.residualVector);
+ const int rowOffset = earlyShapeStage
+ ? current.breakdown.residualVector.size() + current.breakdown.shapeResiduals.size() +
+ current.breakdown.earlyValueResiduals.size()
+ : (shapeStage ? current.breakdown.residualVector.size() : 0);
const QVector > objectiveJacobian = jacobian.mid(rowOffset, objectiveResidual.size());
QVector objectiveCoordinates = current.breakdown.sampleCoordinates;
if(shapeStage) {
@@ -2437,15 +2870,37 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
const QVector information = buildTrustRegionFisher(
objectiveJacobian, objectiveResidual, jacobianColumnValid, objectiveCoordinates);
const TrustRegionFisher& global = information[kAutoFitTimeWindowCount];
+ // 前期残差已含第一窗口权重,使用全局和,不能再次乘局部窗口权重。
+ const TrustRegionFisher& stepInformation = global;
+ QVector stageColumnValid = jacobianColumnValid;
+ for(int column = 0; column < dimensions; ++column) {
+ if(!parameterAllowedInStage(column)) stageColumnValid[column] = false;
+ }
QVector selectedColumns;
QVector coordinateStep;
double predictedReduction = 0.0;
int selectedWindow = -1;
- // 最大能量窗口优先。某窗口无可行全局下降步时立即换下一个,不调用 DLL。
- // 全部窗口都处理不动后,再用全局 Fisher 作一次补充选参。
- while(selectedColumns.isEmpty()) {
- selectedWindow = nextTrustRegionWindow(
+ if(!shapeStage) {
+ // 整体阶段联合求解全部有效自由列,不按窗口得分、三参数上限或共线阈值删列。
+ // 边界列也参与耦合求解,是否向内移动由联合解及其边界投影决定。
+ for(int column = 0; column < dimensions; ++column) {
+ // 仅跳过完全没有总残差响应的列,保留弱敏感列及相互相关的列。
+ if(stageColumnValid[column] && global.matrix[column][column] > 0.0)
+ selectedColumns.append(column);
+ }
+ // 当前候选已使用全局信息,停滞判断无需再逐一尝试窗口子集。
+ attemptedWindows.fill(true);
+ globalFallbackAttempted = true;
+ if(selectedColumns.isEmpty() || !buildTrustRegionFisherStep(
+ global, selectedColumns, current.coordinates, damping,
+ trustRadius, minimumCoordinateStep, &coordinateStep, &predictedReduction)) {
+ selectedColumns.clear();
+ }
+ }
+ // 预调整阶段保留原窗口筛选及子集比较,整体阶段直接采用上面的联合步。
+ while(shapeStage && selectedColumns.isEmpty()) {
+ selectedWindow = earlyShapeStage ? 0 : nextTrustRegionWindow(
objectiveWindows, attemptedWindows);
if(selectedWindow >= 0) {
attemptedWindows[selectedWindow] = true;
@@ -2454,9 +2909,17 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
}
const TrustRegionFisher& local = selectedWindow >= 0
? information[selectedWindow] : global;
- const QVector proposed = selectTrustRegionFisherColumns(
- local, global, jacobianColumnValid,
- current.coordinates, minimumCoordinateStep);
+ QVector proposed;
+ if(earlyShapeStage) {
+ // 当前只开放井储或表皮;其余阶段仍使用原 Fisher 组合筛选。
+ for(int column = 0; column < dimensions; ++column) {
+ if(stageColumnValid[column]) proposed.append(column);
+ }
+ } else {
+ proposed = selectTrustRegionFisherColumns(
+ local, global, stageColumnValid,
+ current.coordinates, minimumCoordinateStep);
+ }
// 最多三个推荐参数,比较其全部非空子集(最多七组),避免首参数必选。
// 这里只做小矩阵运算,真正的候选评价每轮仍至多一次。
for(int mask = 1; mask < (1 << proposed.size()); ++mask) {
@@ -2468,15 +2931,12 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
}
QVector step;
double reduction = 0.0;
- if(!buildTrustRegionFisherStep(
- global, columns, current.coordinates,
- damping, trustRadius, minimumCoordinateStep,
- &step, &reduction)) {
- continue;
- }
- // 仅形状预调整限制整体偏离;第三阶段直接使用原 LM 步长,
- // 不预测形状上限,也不因形状变化缩短候选步长。
- if(shapeStage) {
+ // 井储、表皮均由相对斜率残差的实际灵敏度确定方向,不预设参数增减。
+ const bool stepBuilt = buildTrustRegionFisherStep(stepInformation, columns, current.coordinates,
+ damping, trustRadius, minimumCoordinateStep, &step, &reduction);
+ if(!stepBuilt) continue;
+ // 仅联合形状阶段预测总误差上限;井储、表皮步长不受总误差裁剪。
+ if(shapeStage && !earlyShapeStage) {
auto predictedGuard = [&](double scale) -> double {
double energy = 0.0;
for(int row = 0; row < current.breakdown.residualVector.size(); ++row) {
@@ -2499,10 +2959,10 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
const double stepScale = low;
for(int column = 0; column < dimensions; ++column) step[column] *= stepScale;
if(qSqrt(trustRegionSquaredNorm(step)) < minimumCoordinateStep) continue;
- reduction = -trustRegionDotProduct(global.gradient, step);
+ reduction = -trustRegionDotProduct(stepInformation.gradient, step);
for(int a = 0; a < dimensions; ++a)
for(int b = 0; b < dimensions; ++b)
- reduction -= 0.5 * step[a] * global.matrix[a][b] * step[b];
+ reduction -= 0.5 * step[a] * stepInformation.matrix[a][b] * step[b];
if(!isFiniteNumber(reduction) || reduction <= 1.0e-14) continue;
}
}
@@ -2515,14 +2975,24 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
predictedReduction = reduction;
}
}
- if(selectedWindow < 0) {
+ if(earlyShapeStage || selectedWindow < 0) {
break;
}
}
- // 只有窗口候选与全局回退均无方向,才收缩半径并进入原有重建/收敛处理。
+ // 当前阶段没有可行下降步时,收缩半径并进入原有重建/收敛处理。
if(selectedColumns.isEmpty()) {
- if(shapeStage) { enterTotalStage(tr("no feasible shape descent step")); continue; }
+ if(earlyShapeStage) {
+ finishEarlyShapeStage(tr("no feasible early adjustment direction or parameter at bound"));
+ // 尚未求解候选,原迭代留给其余参数,不额外占用迭代或求解预算。
+ --iteration;
+ continue;
+ }
+ if(shapeStage) {
+ completeShapeSearchRound();
+ --iteration;
+ continue;
+ }
if(trustRadius <= minimumTrustRadius * 1.01 &&
modelRebuiltAtMinimumRadius) {
if(promoteSampling(false)) continue;
@@ -2541,7 +3011,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
continue;
}
- const double stepNorm = qSqrt(trustRegionSquaredNorm(coordinateStep));
+ double stepNorm = qSqrt(trustRegionSquaredNorm(coordinateStep));
QVector candidateCoordinates = current.coordinates;
for(int column = 0; column < dimensions; ++column) {
candidateCoordinates[column] += coordinateStep[column];
@@ -2551,19 +3021,27 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
for(int i = 0; i < selectedColumns.size(); ++i) {
selectedParameterIndices << QString::number(m_enabledParamIndices[selectedColumns[i]]);
}
- const QString selectionName = QString(shapeStage ? "shape_" : "total_") + (selectedWindow >= 0
+ QString selectionName = (wellboreRecheck ? QString("recheck_") : QString()) + QString(earlyShapeStage
+ ? (earlyShapeParameter == 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) {
+ emit logMessageGenerated(tr("Wellbore storage trial: relative-slope matching error=%1, C=%2 -> %3")
+ .arg(current.breakdown.earlyParallelLoss, 0, 'g', 5)
+ .arg(current.parameters[storageColumn], 0, 'g', 6)
+ .arg(candidate.parameters[storageColumn], 0, 'g', 6));
+ }
candidate.valid = evaluateTrustRegionPoint(
candidate.parameters,
&candidate.fitness,
&candidate.breakdown,
&candidate.curve,
&candidate.elapsedMs);
+ if(shapeStage && !earlyShapeStage) ++shapeCandidateCount;
if(!candidate.valid) {
// 求解失败的候选不能改变 current。先完整恢复上一个已接受参数和
@@ -2599,6 +3077,49 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
continue;
}
+ // 普通联合步真实表现可靠时,给含导流能力的方向一次较大步试算。
+ // 先保存原步;扩展失败或不如原步时仍保留原候选,本轮额外调用共用八次上限。
+ if(shapeStage && !earlyShapeStage && shapeExplorationDone && conductivityColumn >= 0 &&
+ selectedColumns.contains(conductivityColumn) && acceptable(candidate, current) &&
+ shapeExplorationEvaluations < maximumShapeExplorationEvaluations && 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 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);
+ ++shapeExplorationEvaluations;
+ 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;
+ break;
+ }
consecutiveSolverFailures = 0;
// 有效候选即使最终被拒绝,也提供了一条真实割线,可用于修正下一轮
// 局部模型;是否成为新工作点由当前阶段的目标和约束共同决定。
@@ -2611,9 +3132,12 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
// reductionRatio 衡量局部线性模型的可信度:接近 1 表示预测准确;
// 值较小表示虽然可能下降,但模型低估了非线性,需要收紧下一步。
const double objectiveEnergy = 0.5 * trustRegionSquaredNorm(objectiveResidual);
- double actualReduction = objectiveEnergy - 0.5 * (
- trustRegionSquaredNorm(shapeStage ? candidate.breakdown.shapeResiduals : candidate.breakdown.residualVector));
- double reductionRatio = actualReduction / predictedReduction;
+ const QVector candidateResidual = earlyShapeStage
+ ? candidate.breakdown.earlyParallelResiduals
+ : (shapeStage ? candidate.breakdown.shapeResiduals : candidate.breakdown.residualVector);
+ const double candidateEnergy = 0.5 * trustRegionSquaredNorm(candidateResidual);
+ double actualReduction = objectiveEnergy - candidateEnergy;
+ double reductionRatio = predictedReduction > 1.0e-14 ? actualReduction / predictedReduction : 0.0;
// 使用同一采样层、同一阶段目标比较预测和实际改善。接近收敛时的微小
// 预测量交给停滞逻辑处理,避免比例数值波动反复触发昂贵的全参数重建。
const double predictionFloor = qMax(1.0e-14, objectiveEnergy * 1.0e-8);
@@ -2631,7 +3155,8 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
} else {
fullDataRejections = 0;
}
- QString componentName = shapeStage ? "shape" : "total";
+ QString componentName = earlyShapeStage
+ ? (earlyShapeParameter == 2 ? "storage_parallel" : "skin_parallel") : (shapeStage ? "shape" : "total");
if(accepted) {
// 当前阶段接受候选后同步发布参数、曲线和诊断。
@@ -2669,14 +3194,6 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
restoreEvaluationState(current);
}
- // 候选只要更优就继续作为 current 保存;是否足以解除停滞,则统一
- // 相对上一次有效改善基准判断。拒绝和微小改善都会累计无效次数。
- const bool effectiveImprovement =
- registerEffectiveImprovement(stageError(current));
- if(!effectiveImprovement && recordIneffectiveStep()) {
- stopReason = LM_LOCAL_OPTIMUM;
- }
-
writeTraceRow(m_currentIteration,
-1,
"trust_region_candidate",
@@ -2694,6 +3211,10 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
componentDisplayName = tr("vertical deviation");
} else if(componentName == "horizontal") {
componentDisplayName = tr("horizontal deviation");
+ } else if(componentName == "storage_parallel") {
+ componentDisplayName = tr("wellbore storage relative-slope matching");
+ } else if(componentName == "skin_parallel") {
+ componentDisplayName = tr("skin relative-slope matching");
} else if(componentName == "shape") {
componentDisplayName = tr("shape deviation");
} else if(componentName == "total") {
@@ -2705,9 +3226,13 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
.arg(iteration + 1)
.arg(componentDisplayName)
.arg(selectedColumns.size())
- .arg(candidate.fitness, 0, 'e', 4)
+ .arg(earlyShapeStage ? candidate.breakdown.earlyParallelLoss : candidate.fitness, 0, 'e', 4)
.arg(accepted ? tr("accepted") : tr("rejected")));
- emit progressUpdated(iteration + 1, m_globalBestFitness);
+ emit progressUpdated(shapeStage ? -1 : totalIterations, m_globalBestFitness);
+
+ // 先记录当前试调结果,再发布阶段切换,避免日志显示为未试调就结束。
+ const bool effectiveImprovement = registerEffectiveImprovement(stageError(current));
+ if(!effectiveImprovement && recordIneffectiveStep()) stopReason = LM_LOCAL_OPTIMUM;
if(stopReason == LM_LOCAL_OPTIMUM || fullDataRejections >= 2) {
if(promoteSampling(false)) {
@@ -2717,12 +3242,15 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
if(stopReason == LM_LOCAL_OPTIMUM || samplingRefreshFailed) break;
}
- if(current.fitness < m_targetError) {
+ if(shapeStage && !earlyShapeStage && current.fitness < m_targetError) {
+ finishShapeStage(tr("total target reached during shape fitting"));
+ }
+ if(!shapeStage && current.fitness < m_targetError) {
promoteSampling(true);
stopReason = LM_TARGET_ACHIEVED;
break;
}
- if(selectedWindow < 0 && trustRadius <= minimumTrustRadius * 1.01 &&
+ if(!shapeStage && selectedWindow < 0 && trustRadius <= minimumTrustRadius * 1.01 &&
consecutiveRejectedSteps >= 2 && consecutivePoorPredictions >= 2) {
if(modelRebuiltAtMinimumRadius) {
if(promoteSampling(false)) continue;
@@ -2738,6 +3266,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));
if(m_shouldStop) {
return LM_USER_STOPPED;
@@ -3581,6 +4111,7 @@ double nmCalculationAutoFitLM::calculateLogLogCurveError(
const int lag = 8;
const double span = qLn(overlapMaxX) - qLn(overlapMinX);
QVector residuals[2];
+ QVector targetLogs[2], resultLogs[2];
double biases[2] = {0.0, 0.0};
for(int component = 0; component < 2; ++component) {
for(int i = 0; i < count; ++i) {
@@ -3593,6 +4124,8 @@ double nmCalculationAutoFitLM::calculateLogLogCurveError(
time, &resultValue)) return false;
const double residual = resultValue - targetValue;
residuals[component].append(residual);
+ targetLogs[component].append(targetValue);
+ resultLogs[component].append(resultValue);
// 对数时间梯形权重等价于互补窗口加权求和,避免密集段主导高度。
biases[component] += residual * ((i == 0 || i == count - 1) ? 0.5 : 1.0)
/ (count - 1);
@@ -3614,6 +4147,7 @@ double nmCalculationAutoFitLM::calculateLogLogCurveError(
}
}
objective->shapeLoss = qSqrt(trustRegionSquaredNorm(objective->shapeResiduals));
+ populateEarlyWellboreMetrics(objective, targetLogs, resultLogs, span);
return isFiniteNumber(objective->shapeLoss);
};
@@ -4625,7 +5159,7 @@ QString nmCalculationAutoFitLM::getStopReasonDescription(StopReasonLM reason) co
return tr("Local optimum detected");
case LM_MAX_ITERATIONS:
- return tr("Maximum iterations reached");
+ return tr("Total-stage iteration or evaluation budget reached");
case LM_USER_STOPPED:
return tr("Stopped by user request");
diff --git a/Src/nmNum/nmSubWxs/nmWxAutomaticFittingStart.cpp b/Src/nmNum/nmSubWxs/nmWxAutomaticFittingStart.cpp
index 4a508097..9de73cd7 100644
--- a/Src/nmNum/nmSubWxs/nmWxAutomaticFittingStart.cpp
+++ b/Src/nmNum/nmSubWxs/nmWxAutomaticFittingStart.cpp
@@ -689,16 +689,19 @@ void nmWxAutomaticfittingStart::setPseudoPressureMode(bool enabled)
void nmWxAutomaticfittingStart::onFittingProgress(int iteration, double fitness)
{
- // 确保iteration在合理范围内
- int displayIteration = qMax(1, qMin(iteration, m_maxIterations));
-
- // 更新进度条
- progressBar->setValue(displayIteration);
- double progress = (double)displayIteration / m_maxIterations * 100;
- progressBar->setFormat(QString("%1%").arg(progress, 0, 'f', 1));
+ // LM 预调整没有固定总步数,单独显示阶段;整体阶段从局部迭代 0 开始计进度。
+ if (m_autoFitterLM && iteration < 0) {
+ progressBar->setValue(0);
+ progressBar->setFormat(tr("Pre-adjustment"));
+ currentIterationValue->setText(tr("Pre-adjustment"));
+ } else {
+ int displayIteration = qMax(m_autoFitterLM ? 0 : 1, qMin(iteration, m_maxIterations));
+ progressBar->setValue(displayIteration);
+ double progress = (double)displayIteration / m_maxIterations * 100;
+ progressBar->setFormat(QString("%1%").arg(progress, 0, 'f', 1));
+ currentIterationValue->setText(QString::number(displayIteration));
+ }
- // 更新参数显示
- currentIterationValue->setText(QString::number(displayIteration));
currentComfortValue->setText(formatScientific(fitness));
// 更新最佳适应度