Compare commits

...

2 Commits

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

Binary file not shown.

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

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

Loading…
Cancel
Save