From f0fbef51ad4995f77bb92a9d453abf31fcf14585 Mon Sep 17 00:00:00 2001 From: lvjunjie Date: Fri, 18 Sep 2026 16:57:05 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=20LM=20=E5=88=86?= =?UTF-8?q?=E9=98=B6=E6=AE=B5=E6=8B=9F=E5=90=88=E4=B8=8E=E8=87=AA=E9=80=82?= =?UTF-8?q?=E5=BA=94=E5=BD=A2=E7=8A=B6=E6=90=9C=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 根据目标压力与导数的相对斜率差,依次调整井储和表皮 - 形状阶段冻结井储与表皮,前期走势退化时回检并保护整体误差与形状 - 增加每轮半长探索与渗透率补偿,对可靠的导流能力联合方向尝试扩步 - 取消形状阶段固定配额,按完整搜索轮次和灵敏度刷新确认停滞,独立分配整体 LM 预算 - 整体阶段联合优化全部有效自由参数,完善局部差分、边界回退和失败重试 - 同步预调整进度显示、拟合轨迹诊断及中文翻译 --- Bin/Config/Lang/cn/nmNum_cn.qm | Bin 143515 -> 147547 bytes Bin/Config/Lang/cn/nmNum_cn.ts | 160 ++- .../nmCalculation/nmCalculationAutoFitLM.h | 10 + .../nmCalculation/nmCalculationAutoFitLM.cpp | 938 ++++++++++++++---- .../nmSubWxs/nmWxAutomaticFittingStart.cpp | 21 +- 5 files changed, 888 insertions(+), 241 deletions(-) diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm index 5408661a05b73dd27fc1d2ae8321f279271634af..b6dbcf60ea14d35f257b5c0003b959306c276013 100644 GIT binary patch delta 17420 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 delta 14097 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 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)); // 更新最佳适应度