refactor(nmNum): 统一 LM 形状阶段联合搜索与扩步策略

- 移除每轮半长专项试探及配套渗透率补偿
- 保留局部窗口 Fisher 筛选,每轮全局步联合求解全部有效且有响应的非井储、表皮参数
- 将可靠方向扩步推广至所有形状调整参数,扩步失败或效果更差时保留原候选
- 保留井储表皮冻结、条件回检及完整轮次停滞确认
- 更新拟合轨迹策略元数据并清理失效中文翻译
feature/AutoFit-Optimize-20260914
lvjunjie 2 weeks ago
parent f0fbef51ad
commit 7e5255203e

Binary file not shown.

@ -1276,14 +1276,6 @@ Reason: %1</source>
<source>total target reached during shape fitting</source>
<translation>形状调整期间已达到总误差目标</translation>
</message>
<message>
<source>Half-length exploration accepted: L=%1, shape error=%2, total error=%3.</source>
<translation>裂缝半长探索结果已接受:半长=%1,形状误差=%2,整体误差=%3。</translation>
</message>
<message>
<source>fresh sensitivity after half-length exploration</source>
<translation>裂缝半长探索后重建灵敏度</translation>
</message>
<message>
<source>fresh sensitivity at joint shape entry</source>
<translation>进入联合形状调整时重建灵敏度</translation>

@ -496,25 +496,6 @@ static bool solveTrustRegionLinearSystem(
return true;
}
// 半长候选只由当前值和用户边界生成;投影后去重,不把某个半长写成目标值。
static QVector<double> trustRegionShapeLengthTrials(double value, double lower, double upper)
{
QVector<double> 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
{
@ -1169,7 +1150,7 @@ void nmCalculationAutoFitLM::writeTraceMetaFile()
QTextStream out(&metaFile);
out << "{\n";
out << " \"schema_version\": 20,\n";
out << " \"schema_version\": 21,\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";
@ -1178,11 +1159,10 @@ void nmCalculationAutoFitLM::writeTraceMetaFile()
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_sensitivity_refresh\": \"full Jacobian at joint shape entry and stagnation confirmation\",\n";
out << " \"shape_expansion_policy\": \"one expansion per reliable shape direction, up to 2x when actual rho exceeds 0.75; retain ordinary step on failure\",\n";
out << " \"shape_global_parameter_selection\": \"all_valid_non_wellbore_columns_with_positive_response\",\n";
out << " \"stage2_budget\": \"adaptive, no fixed iteration or evaluation quota; full window/global rounds, 2 ineffective rounds then fresh-J confirmation\",\n";
out << " \"shape_round_improvement_threshold\": \"max(0.0001, 1% of round entry shape loss)\",\n";
out << " \"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";
@ -2170,13 +2150,6 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
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;
@ -2301,79 +2274,6 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
trustRadius = 0.12;
damping = 0.01;
};
auto exploreShapeLengths = [&]() -> bool {
if(shapeExplorationDone || !jointShapeStarted || earlyShapeStage || wellboreRecheck)
return false;
const TrustRegionEvaluation base = current;
TrustRegionEvaluation best;
const QVector<double> 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", &current.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;
@ -2461,9 +2361,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
auto completeShapeSearchRound = [&]() {
if(!shapeStage || earlyShapeStage || !globalFallbackAttempted) return;
// 即使所有局部方向都不可行,也先完成半长组合搜索。大步接受后需在新点重走窗口。
if(!shapeExplorationDone && exploreShapeLengths()) return;
if(m_shouldStop || !shapeExplorationDone) return;
if(m_shouldStop) return;
const double required = qMax(1.0e-4, 0.01 * shapeRoundBaseline);
const double improvement = shapeRoundBaseline - current.breakdown.shapeLoss;
const bool improved = improvement >= required;
@ -2481,13 +2379,10 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
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();
@ -2779,8 +2674,6 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
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) {
@ -2811,8 +2704,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
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;
@ -2898,7 +2790,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
selectedColumns.clear();
}
}
// 预调整阶段保留原窗口筛选及子集比较,整体阶段直接采用上面的联合步。
// 形状阶段先覆盖局部窗口,再用全部有效自由列求一次全局联合步。
while(shapeStage && selectedColumns.isEmpty()) {
selectedWindow = earlyShapeStage ? 0 : nextTrustRegionWindow(
objectiveWindows, attemptedWindows);
@ -2911,22 +2803,30 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
? information[selectedWindow] : global;
QVector<int> proposed;
if(earlyShapeStage) {
// 当前只开放井储或表皮;其余阶段仍使用原 Fisher 组合筛选。
// 前期只开放当前的井储或表皮参数。
for(int column = 0; column < dimensions; ++column) {
if(stageColumnValid[column]) proposed.append(column);
}
} else if(selectedWindow < 0) {
// 全局步保留弱敏感及相关列,避免参数长期被窗口前三名或共线筛选排除。
for(int column = 0; column < dimensions; ++column) {
if(stageColumnValid[column] && global.matrix[column][column] > 0.0)
proposed.append(column);
}
} else {
proposed = selectTrustRegionFisherColumns(
local, global, stageColumnValid,
current.coordinates, minimumCoordinateStep);
}
// 最多三个推荐参数,比较其全部非空子集(最多七组),避免首参数必选。
// 这里只做小矩阵运算,真正的候选评价每轮仍至多一次。
for(int mask = 1; mask < (1 << proposed.size()); ++mask) {
// 局部窗口至多三列,比较非空子集;全局只求完整组合,不枚举全部参数子集。
const bool fullJointStep = !earlyShapeStage && selectedWindow < 0;
const int combinationCount = proposed.isEmpty() ? 0 : (fullJointStep ? 1 : (1 << proposed.size()) - 1);
for(int combination = 1; combination <= combinationCount; ++combination) {
QVector<int> columns;
for(int i = 0; i < proposed.size(); ++i) {
if(mask & (1 << i)) {
columns.append(proposed[i]);
if(fullJointStep) columns = proposed;
else {
for(int i = 0; i < proposed.size(); ++i) {
if(combination & (1 << i)) columns.append(proposed[i]);
}
}
QVector<double> step;
@ -3041,7 +2941,6 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
&candidate.breakdown,
&candidate.curve,
&candidate.elapsedMs);
if(shapeStage && !earlyShapeStage) ++shapeCandidateCount;
if(!candidate.valid) {
// 求解失败的候选不能改变 current。先完整恢复上一个已接受参数和
@ -3077,11 +2976,9 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
continue;
}
// 普通联合步真实表现可靠时,给含导流能力的方向一次较大步试算。
// 先保存原步;扩展失败或不如原步时仍保留原候选,本轮额外调用共用八次上限。
if(shapeStage && !earlyShapeStage && shapeExplorationDone && conductivityColumn >= 0 &&
selectedColumns.contains(conductivityColumn) && acceptable(candidate, current) &&
shapeExplorationEvaluations < maximumShapeExplorationEvaluations && processPauseAndStop()) {
// 普通形状步真实表现可靠时,沿同一联合方向最多扩步试算一次,不区分参数类型。
// 扩步仍须通过固定总误差上限且优于原步,否则保留原候选。
if(shapeStage && !earlyShapeStage && acceptable(candidate, current) && processPauseAndStop()) {
const double ordinaryEnergy = 0.5 * trustRegionSquaredNorm(objectiveResidual);
const double ordinaryReduction = ordinaryEnergy - 0.5 * trustRegionSquaredNorm(candidate.breakdown.shapeResiduals);
const double predictionFloor = qMax(1.0e-14, ordinaryEnergy * 1.0e-8);
@ -3096,7 +2993,6 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
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;

Loading…
Cancel
Save