diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm index b6dbcf60..13ec7e77 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 302d0278..28acd643 100644 --- a/Bin/Config/Lang/cn/nmNum_cn.ts +++ b/Bin/Config/Lang/cn/nmNum_cn.ts @@ -1276,14 +1276,6 @@ Reason: %1 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 进入联合形状调整时重建灵敏度 diff --git a/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp b/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp index 7ff0db33..2af9406a 100644 --- a/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp +++ b/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp @@ -496,25 +496,6 @@ 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 { @@ -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 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; @@ -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 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 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 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;