refactor(numerical): 优化自动拟合界面与 LM 迭代控制

- 调整窗口布局、参数展示和拟合日志
- 默认勾选可用参数,移除 PSO 加速选项
- LM 共用最大迭代次数,改善门槛改为 max(0.01, 基准误差×2%)
feature/AutoFit-Optimize-20260914
lvjunjie 2 weeks ago
parent 1237b44a4c
commit 43a70c9888

Binary file not shown.

@ -677,22 +677,16 @@ Reason: %1</source>
<translation>【初始参数】</translation>
</message>
<message>
<source>[Stage 1: Height alignment]
Adjust permeability only; accept by height error.</source>
<translation>【第一阶段:高度预调】
只调整渗透率,按高度误差判断是否接受。</translation>
<source>[Stage 1: Height alignment]</source>
<translation>【第一阶段:高度预调】</translation>
</message>
<message>
<source>[Stage 2: Joint LM - Shape]
Adjust selected parameters; accept by shape error.</source>
<translation>【第二阶段:联合 LM · 形状】
调整勾选参数,按形状误差判断是否接受。</translation>
<source>[Stage 2: Joint LM - Shape]</source>
<translation>【第二阶段:联合 LM · 形状】</translation>
</message>
<message>
<source>[Stage 2: Joint LM - Total error]
Adjust selected parameters; accept by total error.</source>
<translation>【第二阶段:联合 LM · 总误差】
调整勾选参数,按总误差判断是否接受。</translation>
<source>[Stage 2: Joint LM - Total error]</source>
<translation>【第二阶段:联合 LM · 总误差】</translation>
</message>
<message>
<source>%1 | %2 | %3 | Improvement: %4%</source>
@ -759,12 +753,8 @@ No valid error was returned; keep the last accepted result and reduce the step.<
<translation>mD·m</translation>
</message>
<message>
<source>Trial %1 | %2 | Height error: %3 -&gt; %4</source>
<translation>试算 %1|%2|高度误差:%3 → %4</translation>
</message>
<message>
<source>Trial %1 | Solver failed; keep the last accepted result and reduce the step.</source>
<translation>试算 %1|求解失败,保留上次已接受结果并缩小步长。</translation>
<source>Height error: %1 -&gt; %2</source>
<translation>高度误差:%1 → %2</translation>
</message>
<message>
<source>Height error did not decrease; keep the last accepted result and reduce the step.</source>
@ -816,7 +806,7 @@ No valid error was returned; keep the last accepted result and reduce the step.<
</message>
<message>
<source>Iteration or evaluation budget reached</source>
<translation>达到本阶段迭代次数或试算次数上限</translation>
<translation>达到总迭代上限或本段试算次数上限</translation>
</message>
<message>
<source>Final total error did not improve; restoring the initial result.</source>
@ -1091,8 +1081,8 @@ No valid error was returned; keep the last accepted result and reduce the step.<
<translation>=== LM 自动拟合 - 局部最优 ===</translation>
</message>
<message>
<source>Total-stage budget reached. Best error: %1, Cumulative iterations: %2</source>
<translation>整体阶段预算已用尽。最佳误差:%1,累计迭代次数:%2</translation>
<source>Iteration or evaluation budget reached. Best error: %1, Cumulative iterations: %2</source>
<translation>达到总迭代上限或本段试算次数上限。最佳误差:%1,累计迭代次数:%2</translation>
</message>
<message>
<source>=== LM AUTOMATIC FITTING - MAX ITERATIONS ===</source>
@ -4467,6 +4457,30 @@ Supported types: Vertical, Vertical Fractured, and Horizontal Multi-Fractured We
<source>Gas pseudo-pressure data is unavailable or invalid.</source>
<translation>气体拟压力数据不可用或无效。</translation>
</message>
<message>
<source>Iteration limit:</source>
<translation>迭代上限:</translation>
</message>
<message>
<source>Maximum iterations:</source>
<translation>最大迭代次数:</translation>
</message>
<message>
<source>Height, shape and total-error adjustments share this iteration limit; one iteration may call the solver multiple times.</source>
<translation>高度、形状和总误差调整共用此迭代上限;一次迭代可能调用多次求解器。</translation>
</message>
<message>
<source>Start fitting</source>
<translation>开始拟合</translation>
</message>
<message>
<source>Target error:</source>
<translation>目标误差:</translation>
</message>
<message>
<source>Fitting parameters</source>
<translation>拟合参数</translation>
</message>
</context>
<context>
<name>nmWxAutomaticfitting</name>
@ -4562,8 +4576,24 @@ Supported types: Vertical, Vertical Fractured, and Horizontal Multi-Fractured We
<context>
<name>nmWxAutomaticfittingStart</name>
<message>
<source>Total time: %1 min %2 s</source>
<translation>总耗时:%1 分 %2 秒</translation>
<source>Iterations:</source>
<translation>迭代数:</translation>
</message>
<message>
<source>Fitting process</source>
<translation>拟合过程</translation>
</message>
<message>
<source>Total time: %1</source>
<translation>总耗时:%1</translation>
</message>
<message>
<source>%1 s</source>
<translation>%1 秒</translation>
</message>
<message>
<source>%1 min %2 s</source>
<translation>%1 分 %2 秒</translation>
</message>
<message>
<source>Auto Fitting Progress</source>
@ -4757,6 +4787,86 @@ Supported types: Vertical, Vertical Fractured, and Horizontal Multi-Fractured We
<source>Pre-adjustment</source>
<translation>预调整中</translation>
</message>
<message>
<source>%1 | Iteration %2 / limit %3</source>
<translation>%1 · 迭代 %2 / 上限 %3</translation>
</message>
<message>
<source>Adjustment log</source>
<translation>调整记录</translation>
</message>
<message>
<source>Calculating initial error...</source>
<translation>正在计算初始误差……</translation>
</message>
<message>
<source>Current parameters</source>
<translation>当前参数</translation>
</message>
<message>
<source>Current value</source>
<translation>当前值</translation>
</message>
<message>
<source>Fitting ended</source>
<translation>拟合结束</translation>
</message>
<message>
<source>Fitting failed</source>
<translation>拟合失败</translation>
</message>
<message>
<source>Generating final result...</source>
<translation>正在生成最终结果……</translation>
</message>
<message>
<source>Height alignment</source>
<translation>高度预调</translation>
</message>
<message>
<source>Shape adjustment</source>
<translation>形状调整</translation>
</message>
<message>
<source>Total-error adjustment</source>
<translation>总误差调整</translation>
</message>
<message>
<source>Collapse</source>
<translation>收起</translation>
</message>
<message>
<source>Expand</source>
<translation>展开</translation>
</message>
<message>
<source>Fitted</source>
<translation>拟合</translation>
</message>
<message>
<source>Fitting in progress</source>
<translation>拟合中</translation>
</message>
<message>
<source>Observed</source>
<translation>实测</translation>
</message>
<message>
<source>Solver calls: %1 | Total time: %2</source>
<translation>求解器调用 %1 次 · 总耗时 %2</translation>
</message>
<message>
<source>Target error:</source>
<translation>目标误差:</translation>
</message>
<message>
<source>Total error: %1 -&gt; %2</source>
<translation>总误差:%1 → %2</translation>
</message>
<message>
<source>Current error: %1</source>
<translation>当前误差:%1</translation>
</message>
</context>
<context>
<name>nmWxChangeAnal</name>

@ -118,6 +118,8 @@ public:
void setTargetWellName(const QString& wellName);
signals:
// 阶段仅用于界面显示:0 初始求解,1 高度,2 形状,3 总误差。
void fittingStageChanged(int stage);
void progressUpdated(int iteration, double bestFitness);
void fittingFinished(bool success, const QString& message);
void bestCurveUpdated(QVector<QVector<double> > targetData,

@ -4,6 +4,7 @@
#include "iDlgBase.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QTableWidget>
#include <QCheckBox>
#include <QLineEdit>
@ -33,6 +34,7 @@ class NM_SUB_WXS_EXPORT nmWxAutomaticFitting : public iDlgBase
public:
nmWxAutomaticFitting(QWidget *parent = 0);
~nmWxAutomaticFitting();
virtual QSize sizeHint() const;
private slots:
void onCellSelectionChanged(int currentRow, int currentColumn, int previousRow, int previousColumn);
@ -73,16 +75,15 @@ private:
private:
// UI组件
QVBoxLayout* m_mainLayout;
QVBoxLayout* m_controlLayout;
QGridLayout* m_controlLayout;
QTableWidget* m_parameterTable;
// 控制面板组件
QLineEdit* m_iterationEdit;
QLabel* m_iterationLabel;
QLineEdit* m_errorLimitEdit;
QComboBox* m_targetWellCombo;
QComboBox* m_algorithmCombo;
QLabel* m_surrogateLabel;
QComboBox* m_surrogateCombo;
// 参数复选框
QCheckBox* m_kCheckBox; // 渗透率

@ -7,13 +7,11 @@
#include <QGridLayout>
#include <QGroupBox>
#include <QLabel>
#include <QTableWidget>
#include <QTextEdit>
#include <QProgressBar>
#include <QPushButton>
#include <QMessageBox>
#include <QCloseEvent>
#include <QHeaderView>
#include <QFont>
#include <QTextCursor>
#include <QDateTime>
@ -75,6 +73,7 @@ class nmWxAutomaticfittingStart : public iDlgBase
public:
explicit nmWxAutomaticfittingStart(QWidget *parent = 0);
~nmWxAutomaticfittingStart();
virtual QSize sizeHint() const;
// 算法接口
void setAutoFitter(nmCalculationAutoFitPSO* autoFitter);
@ -83,7 +82,7 @@ public:
// 通用设置接口
void setFittingParameters(int maxIterations, double targetError, const QString& wellName);
void setSelectedParameters(const QStringList& parameterNames);
void setSelectedParameters(const QStringList& parameterNames, const QStringList& parameterUnits);
void setTargetLogLogData(const QVector<QVector<double> >& targetData);
void setPseudoPressureMode(bool enabled);
@ -92,6 +91,8 @@ public:
public slots:
void onFittingProgress(int iteration, double fitness);
void onLMStageChanged(int stage);
void onToggleLog();
void onFittingFinished(bool success, const QString& message);
void onFinalizingStarted();
void onStopButtonClicked();
@ -107,24 +108,22 @@ protected:
private:
void setupUI();
void setupParameterArea();
void setupTableArea();
void setupParameterStrip();
void setupChartArea();
void setupLogArea();
void setupControlArea();
void updateParameterTable();
void updateAlgorithmDisplay();
void updateParameterValues();
void updateCurrentError(double fitness);
void addLogMessage(const QString& message);
QString formatScientific(double value);
// UI组件
QHBoxLayout* mainLayout;
QVBoxLayout* leftLayout;
QVBoxLayout* rightLayout;
QSplitter* rightSplitter;
// 参数显示区域
QGroupBox* parameterGroup;
QLabel* currentIterationLabel;
QLabel* maxIterationLabel;
QLabel* currentComfortLabel;
QLabel* bestComfortLabel;
QLabel* targetPrecisionLabel;
@ -132,25 +131,31 @@ private:
QLabel* algorithmTypeLabel;
QLabel* currentIterationValue;
QLabel* maxIterationValue;
QLabel* currentComfortValue;
QLabel* bestComfortValue;
QLabel* targetPrecisionValue;
QLabel* targetWellValue;
QLabel* algorithmTypeValue;
// 参数表格区域
QGroupBox* tableGroup;
QTableWidget* tableWidget;
// 曲线和日志下方的整行参数,名称和单位在上、数值在下,用竖线分隔。
QWidget* parameterStrip;
QGridLayout* parameterLayout;
QVector<QLabel*> parameterValues;
// 日志区域
QGroupBox* chartGroup;
nmAutomaticFittingCurveChart* curveChart;
QGroupBox* logGroup;
QTextEdit* logTextEdit;
QPushButton* logToggle;
QList<int> expandedLogSizes;
// 控制区域
QProgressBar* progressBar;
QLabel* stageStatus;
QLabel* stateBadge;
QLabel* resultSummary;
QLabel* runSummary;
QPushButton* stopButton;
// 算法实例
@ -162,7 +167,6 @@ private:
int m_maxIterations;
double m_targetError;
QString m_wellName;
QStringList m_selectedParameters;
bool m_isFinished;
bool m_isFinalizing;
double m_bestFitnessEver;

@ -688,7 +688,7 @@ nmCalculationAutoFitLM::nmCalculationAutoFitLM(QObject* parent)
, m_isRunning(false)
, m_shouldStop(false)
, m_isFinalizing(false)
, m_currentIteration(0)
, m_currentIteration(-1)
, m_globalBestFitness(1e10)
, m_comparisonTimeMin(0.0)
, m_comparisonTimeMax(0.0)
@ -884,7 +884,7 @@ void nmCalculationAutoFitLM::resetOptimizer()
m_userInitialObjectiveBreakdown = AutoFitObjectiveBreakdownLM();
m_comparisonTimeMin = 0.0;
m_comparisonTimeMax = 0.0;
m_currentIteration = 0;
m_currentIteration = -1; // 尚未执行迭代;对外日志使用索引加一。
m_totalEvaluations = 0;
m_solverCallCount = 0;
m_successfulEvaluations = 0;
@ -1042,17 +1042,17 @@ void nmCalculationAutoFitLM::writeTraceMetaFile()
QTextStream out(&metaFile);
out << "{\n";
out << " \"schema_version\": 52,\n";
out << " \"schema_version\": 53,\n";
out << " \"strategy\": \"permeability_height_then_joint_lm_shape_then_total\",\n";
out << " \"height_acceptance\": \"reliable_vertical_loss_decrease; no_shape_or_total_loss_constraint\",\n";
out << " \"shape_metric\": \"pressure_and_derivative_log_slopes_shared_sampling_grid_lag_round_10_percent_min_1\",\n";
out << " \"shape_parameter_selection\": \"all_valid_free_columns_joint_LM; no_single_parameter_sweep_or_wellbore_recheck\",\n";
out << " \"shape_acceptance\": \"strict_full_shape_loss_decrease; no_total_loss_constraint\",\n";
out << " \"lm_step_policy\": \"same_joint_LM_damping_trust_radius_secant_updates_and_sensitivity_rebuilds_in_both_phases\",\n";
out << " \"lm_phase_switch\": \"shape_target_reached_or_confirmed_stagnation_or_phase_budget_exhausted_then_total; user_stop_and_consecutive_solver_failure_abort\",\n";
out << " \"lm_phase_budget\": \"each_phase_has_max_iterations_and_max(dimensions+2,20,3*max_iterations)_evaluations; independent_of_height_prealignment\",\n";
out << " \"lm_effective_improvement\": \"max(0.00001,0.002*baseline_stage_error); 3_ineffective_steps_trigger_stagnation_confirmation\",\n";
out << " \"iteration_count_scope\": \"max_iterations_applies_independently_to_each_LM_phase; trace_iteration_is_cumulative\",\n";
out << " \"lm_phase_switch\": \"shape_target_reached_or_confirmed_stagnation_or_phase_evaluation_budget_exhausted_then_total_if_iterations_remain; shared_iteration_limit_or_user_stop_or_consecutive_solver_failure_abort\",\n";
out << " \"lm_phase_budget\": \"shared_max_iterations_including_height; each_LM_phase_retains_max(dimensions+2,20,3*max_iterations)_evaluations\",\n";
out << " \"lm_effective_improvement\": \"max(0.01,0.02*baseline_stage_error); 3_ineffective_steps_trigger_stagnation_confirmation\",\n";
out << " \"iteration_count_scope\": \"height_shape_and_total_share_max_iterations; rejected_and_failed_candidates_count; sensitivity_evaluations_are_not_iterations; trace_iteration_is_zero_based_cumulative\",\n";
out << " \"total_stage_shape_constraint\": false,\n";
out << " \"total_parameter_selection\": \"all_valid_free_columns\",\n";
out << " \"skin_difference_policy\": \"local_scale_fraction_0.02_in_both_LM_phases\",\n";
@ -1193,6 +1193,14 @@ static QString lmLogError(double value)
? QString::number(value, 'f', 6) : QString("--");
}
// 改善率统一相对上次已接受误差计算;起点为零时不作除法。
static QString lmLogImprovement(double beforeError, double afterError)
{
return beforeError > 0.0
? QString::number((beforeError - afterError) / beforeError * 100.0, 'f', 2)
: QString("--");
}
void nmCalculationAutoFitLM::emitRunSummary(bool success, StopReasonLM finalReason)
{
// 最终只汇总总误差,耗时由进程窗口在收到结束信号后追加。
@ -1220,12 +1228,8 @@ void nmCalculationAutoFitLM::emitTrialLog(const QString& title,
const QString objective = (shapeStage ? tr("Shape error: %1 -> %2")
: tr("Total error: %1 -> %2"))
.arg(lmLogError(beforeError)).arg(lmLogError(afterError));
// 起点为零时相对改善率无定义,避免除零。
const QString improvement = beforeError > 0.0
? QString::number((beforeError - afterError) / beforeError * 100.0, 'f', 2)
: QString("--");
QString message = tr("%1 | %2 | %3 | Improvement: %4%")
.arg(title).arg(status).arg(objective).arg(improvement);
.arg(title).arg(status).arg(objective).arg(lmLogImprovement(beforeError, afterError));
if(!accepted) message += tr("\n Objective error did not decrease; keep the last accepted result and reduce the step.");
emit logMessageGenerated(message);
}
@ -1435,7 +1439,7 @@ bool nmCalculationAutoFitLM::startAutoFitting()
m_isRunning = true;
m_shouldStop = false;
m_isFinalizing = false;
m_currentIteration = 0;
m_currentIteration = -1;
m_consecutiveFailures = 0;
m_initialValues = savedInitialValues;
initializeTraceFile();
@ -1443,6 +1447,7 @@ bool nmCalculationAutoFitLM::startAutoFitting()
emit logMessageGenerated(tr("========== LM fitting started =========="));
emit logMessageGenerated(tr("Target well: %1").arg(m_targetWellName));
emit logMessageGenerated(tr("Target error: %1").arg(lmLogError(m_targetError)));
emit fittingStageChanged(0);
// 先用真实求解器评价用户当前模型,供最终精英保护使用。
if(!savedInitialValues.isEmpty()) {
@ -1653,7 +1658,7 @@ bool nmCalculationAutoFitLM::startAutoFitting()
DEBUG_OUT(tr("=== LM AUTOMATIC FITTING - LOCAL OPTIMUM ==="));
} else if(finalReason == LM_MAX_ITERATIONS) {
success = true;
message = QString(tr("Total-stage budget reached. Best error: %1, Cumulative iterations: %2"))
message = QString(tr("Iteration or evaluation budget reached. Best error: %1, Cumulative iterations: %2"))
.arg(m_globalBestFitness, 0, 'e', 4)
.arg(m_currentIteration + 1);
DEBUG_OUT(tr("=== LM AUTOMATIC FITTING - MAX ITERATIONS ==="));
@ -1825,22 +1830,23 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
return LM_OPTIMIZATION_FAILED;
}
// 形状与总误差共用原联合 LM,每段独立使用原迭代和求解额度。
// 高度、形状、总误差共用迭代上限;灵敏度求解仍使用原来的各段评价额度。
const int phaseEvaluationBudget = qMax(dimensions + 2, qMax(20, m_maxIterations * 3));
int maximumEvaluations = 0;
int phaseIterations = 0;
int shapeIterations = 0;
int totalIterations = 0;
int completedIterations = 0;
// 下列步长均位于归一化内部坐标:0.04 表示参数范围的 4%,信赖半径
// 限制一次联合移动的二范数,相关性门槛用于排除响应近乎共线的参数。
const double sensitivityStep = 0.04;
const double minimumCoordinateStep = 1.0e-5;
const double minimumTrustRadius = 2.0e-3;
const double maximumTrustRadius = 0.30;
// 误差下降至少达到绝对 1e-5 且相对当前有效基准 0.2% 才算有效改善。
// 误差下降至少达到绝对 0.01 且相对当前有效基准 2% 才算有效改善。
// 更小的下降仍保留为最佳解,但不能反复清除停滞状态、延长拟合时间。
const double effectiveRelativeImprovement = 2.0e-3;
const double effectiveAbsoluteImprovement = 1.0e-5;
const double effectiveRelativeImprovement = 2.0e-2;
const double effectiveAbsoluteImprovement = 1.0e-2;
const int maximumIneffectiveSteps = 3;
// damping 是 LM 阻尼;拒绝或预测失准时增大,真实下降与预测一致时减小。
@ -1962,7 +1968,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
}
restoreEvaluationState(current);
emit progressUpdated(-1, current.fitness);
emit progressUpdated(0, current.fitness);
DEBUG_OUT(tr("=== Starting LM Main Loop ==="));
DEBUG_OUT(
tr("LM starting point error: %1; evaluation budget per LM phase: %2")
@ -1978,9 +1984,10 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
double heightSlope = -1.0;
int heightTrials = 0;
if(permeabilityColumn >= 0) {
emit logMessageGenerated(tr("[Stage 1: Height alignment]\nAdjust permeability only; accept by height error."));
// 不限制试算次数,由高度对齐、改善停滞及可行步长决定何时结束。
while(processPauseAndStop()) {
emit fittingStageChanged(1);
emit logMessageGenerated(tr("[Stage 1: Height alignment]"));
// 高度调整也占用总迭代额度,仍保留原来的对齐、停滞和可行步长判断。
while(completedIterations < m_maxIterations && processPauseAndStop()) {
const double bias = current.breakdown.verticalCommonBias;
if(!current.breakdown.verticalReliable || qAbs(bias) <= 0.01 || heightStagnation >= 2) break;
const double k = current.parameters[permeabilityColumn];
@ -1988,13 +1995,15 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
const double logRange = qLn(m_parameterUpper[0]) - qLn(m_parameterLower[0]);
const double change = qBound(-0.30 * logRange, -bias / heightSlope * heightScale, 0.30 * logRange);
const AutoFitObjectiveBreakdownLM previous = current.breakdown;
++heightTrials;
TrustRegionEvaluation candidate;
candidate.parameters = current.parameters;
candidate.parameters[permeabilityColumn] = qBound(m_parameterLower[0], k * qExp(change), m_parameterUpper[0]);
candidate.coordinates = coordinatesFromParameters(candidate.parameters);
const double actualStep = qLn(candidate.parameters[permeabilityColumn] / k);
if(qAbs(actualStep) < 1.0e-5) break;
m_currentIteration = completedIterations++;
++heightTrials;
emit progressUpdated(completedIterations, current.fitness);
candidate.valid = evaluateTrustRegionPoint(candidate.parameters, &candidate.fitness,
&candidate.breakdown, &candidate.curve, &candidate.elapsedMs);
// 第一阶段只按上下偏移改善验收,不限制形状;仍防止两条曲线一高一低互相抵消。
@ -2004,7 +2013,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
const double slope = (candidate.breakdown.verticalCommonBias - bias) / actualStep;
if(slope < -0.05 && isFiniteNumber(slope)) heightSlope = slope;
}
writeTraceRow(-1, 0, "permeability_height", candidate.parameters, candidate.fitness,
writeTraceRow(m_currentIteration, 0, "permeability_height", candidate.parameters, candidate.fitness,
candidate.valid, candidate.elapsedMs, accepted ? "accepted_height" : "rejected_height",
candidate.valid ? &candidate.breakdown : nullptr);
if(accepted) {
@ -2019,18 +2028,24 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
heightStagnation = 0;
} else ++heightStagnation;
if(candidate.valid) {
emit logMessageGenerated(tr("Trial %1 | %2 | Height error: %3 -> %4")
.arg(heightTrials).arg(accepted ? tr("accepted") : tr("rejected"))
.arg(lmLogError(previous.verticalLoss)).arg(lmLogError(candidate.breakdown.verticalLoss)));
const QString objective = tr("Height error: %1 -> %2")
.arg(lmLogError(previous.verticalLoss)).arg(lmLogError(candidate.breakdown.verticalLoss));
emit logMessageGenerated(tr("%1 | %2 | %3 | Improvement: %4%")
.arg(tr("Iteration %1").arg(completedIterations))
.arg(accepted ? tr("accepted") : tr("rejected")).arg(objective)
.arg(lmLogImprovement(previous.verticalLoss, candidate.breakdown.verticalLoss)));
if(!accepted) emit logMessageGenerated(candidate.breakdown.verticalReliable
? tr("Height error did not decrease; keep the last accepted result and reduce the step.")
: tr("Pressure and derivative height directions conflict; keep the last accepted result and reduce the step."));
} else {
emit logMessageGenerated(tr("Trial %1 | Solver failed; keep the last accepted result and reduce the step.").arg(heightTrials));
emit logMessageGenerated(tr("%1 | Solver failed\nNo valid error was returned; keep the last accepted result and reduce the step.")
.arg(tr("Iteration %1").arg(completedIterations)));
}
emit progressUpdated(completedIterations, current.fitness);
}
QString heightReason = tr("no feasible permeability step or step too small");
if(m_shouldStop) heightReason = tr("stopped by user");
else if(completedIterations >= m_maxIterations) heightReason = getStopReasonDescription(LM_MAX_ITERATIONS);
else if(!current.breakdown.verticalReliable) heightReason = tr("pressure and derivative height directions conflict");
else if(current.breakdown.verticalLoss <= 0.01) heightReason = tr("curve height is approximately aligned");
else if(heightStagnation >= 2) heightReason = tr("2 consecutive steps without effective height improvement");
@ -2041,6 +2056,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
if(permeabilityColumn < 0) emit logMessageGenerated(tr("Height alignment skipped: permeability is not selected."));
if(m_shouldStop) return LM_USER_STOPPED;
if(completedIterations >= m_maxIterations) return LM_MAX_ITERATIONS;
// 原逐参数第二阶段已移除;同一个联合 LM 先匹配整体形状,再匹配总误差。
bool shapeStage = true;
@ -2288,8 +2304,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
return true;
};
int completedIterations = 0;
for(int phase = 0; phase < 2 && !m_shouldStop; ++phase) {
for(int phase = 0; phase < 2 && !m_shouldStop && completedIterations < m_maxIterations; ++phase) {
shapeStage = phase == 0;
phaseIterations = 0;
maximumEvaluations = m_totalEvaluations + phaseEvaluationBudget;
@ -2309,20 +2324,18 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
rebuildRequested = true;
rebuildReason = shapeStage ? QT_TR_NOOP("full sensitivity at shape LM entry")
: QT_TR_NOOP("full sensitivity at total-stage entry");
emit progressUpdated(0, current.fitness);
emit fittingStageChanged(shapeStage ? 2 : 3);
emit progressUpdated(completedIterations, current.fitness);
emit logMessageGenerated(shapeStage
? tr("[Stage 2: Joint LM - Shape]\nAdjust selected parameters; accept by shape error.")
: tr("[Stage 2: Joint LM - Total error]\nAdjust selected parameters; accept by total error."));
DEBUG_OUT(tr("LM phase budget: %1 iterations, %2 evaluations.")
.arg(m_maxIterations).arg(phaseEvaluationBudget));
? tr("[Stage 2: Joint LM - Shape]")
: tr("[Stage 2: Joint LM - Total error]"));
DEBUG_OUT(tr("LM phase budget: %1 remaining shared iterations, %2 evaluations.")
.arg(m_maxIterations - completedIterations).arg(phaseEvaluationBudget));
writeTraceRow(completedIterations, -1, "stage_switch", current.parameters, current.fitness,
true, 0, shapeStage ? "height_to_shape_lm" : "shape_to_total", &current.breakdown);
for(int iteration = completedIterations;
!m_shouldStop && phaseIterations < m_maxIterations && m_totalEvaluations < maximumEvaluations;
++iteration) {
m_currentIteration = iteration;
completedIterations = iteration + 1;
while(!m_shouldStop && completedIterations < m_maxIterations && m_totalEvaluations < maximumEvaluations) {
m_currentIteration = completedIterations;
if(!processPauseAndStop()) break;
if(stageError(current) < m_targetError) {
stopReason = LM_TARGET_ACHIEVED;
@ -2350,8 +2363,10 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
registerEffectiveImprovement(stageError(current));
}
// 两段分别计数;无可行方向也消耗一次尝试,避免无限缩步重建。
// 全程累计迭代;无可行方向、拒绝和求解失败也占用一次额度。
++phaseIterations;
++completedIterations;
emit progressUpdated(completedIterations, current.fitness);
const QVector<double> objectiveResidual = shapeStage
? current.breakdown.shapeResiduals : current.breakdown.residualVector;
const int rowOffset = shapeStage ? current.breakdown.residualVector.size() : 0;
@ -2386,7 +2401,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
// 当前阶段没有可行下降步时,收缩半径并进入原有重建/收敛处理。
if(selectedColumns.isEmpty()) {
emit logMessageGenerated(tr("Iteration %1 | No feasible descent step; keep the last accepted result.").arg(phaseIterations));
emit logMessageGenerated(tr("Iteration %1 | No feasible descent step; keep the last accepted result.").arg(completedIterations));
if(trustRadius <= minimumTrustRadius * 1.01 &&
modelRebuiltAtMinimumRadius) {
stopReason = LM_LOCAL_OPTIMUM;
@ -2433,7 +2448,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
}
if(!candidate.valid) {
emitTrialLog(tr("Iteration %1").arg(phaseIterations), current.breakdown, candidate.breakdown,
emitTrialLog(tr("Iteration %1").arg(completedIterations), current.breakdown, candidate.breakdown,
false, false, shapeStage);
// 求解失败的候选不能改变 current。先完整恢复上一个已接受参数和
// 对应误差快照,再缩小信赖域;连续失败达到上限才终止整个拟合。
@ -2549,9 +2564,9 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
: "rejected_" + selectionName,
&candidate.breakdown);
emitTrialLog(tr("Iteration %1").arg(phaseIterations), oldBreakdown, candidate.breakdown,
emitTrialLog(tr("Iteration %1").arg(completedIterations), oldBreakdown, candidate.breakdown,
true, accepted, shapeStage);
emit progressUpdated(phaseIterations, m_globalBestFitness);
emit progressUpdated(completedIterations, m_globalBestFitness);
const bool effectiveImprovement = registerEffectiveImprovement(stageError(current));
if(!effectiveImprovement && recordIneffectiveStep()) stopReason = LM_LOCAL_OPTIMUM;
@ -2576,7 +2591,7 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
else totalIterations = phaseIterations;
if(m_shouldStop || stopReason == LM_CONSECUTIVE_FAILURES || stopReason == LM_OPTIMIZATION_FAILED)
break;
// 形状达标、确认停滞或用完额度后,保留当前参数进入总误差段。
// 形状部分结束后仅在总迭代额度有剩余时进入总误差部分。
if(shapeStage) {
emit logMessageGenerated(tr("Shape phase ended: %1").arg(getStopReasonDescription(stopReason)));
writeTraceRow(m_currentIteration, -1, "lm_phase_end", current.parameters, current.fitness,
@ -2586,12 +2601,11 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
}
}
if(completedIterations > 0) {
m_currentIteration = completedIterations - 1;
}
// 灵敏度建模或初始误差已达标时可能没有候选迭代,不多计一次。
m_currentIteration = completedIterations - 1;
restoreEvaluationState(current);
DEBUG_OUT(tr("Adaptive fitting counts: %1 shape LM iterations, %2 total LM iterations, %3 total evaluations.")
.arg(shapeIterations).arg(totalIterations).arg(m_totalEvaluations));
DEBUG_OUT(tr("Adaptive fitting counts: %1 height iterations, %2 shape LM iterations, %3 total LM iterations, %4 total evaluations.")
.arg(heightTrials).arg(shapeIterations).arg(totalIterations).arg(m_totalEvaluations));
if(m_shouldStop) {
return LM_USER_STOPPED;
@ -2599,6 +2613,9 @@ StopReasonLM nmCalculationAutoFitLM::runTrustRegionFitting()
if(!shapeStage && current.fitness < m_targetError) {
return LM_TARGET_ACHIEVED;
}
if(completedIterations >= m_maxIterations && stopReason != LM_CONSECUTIVE_FAILURES && stopReason != LM_OPTIMIZATION_FAILED) {
return LM_MAX_ITERATIONS;
}
if(stopReason == LM_CONSECUTIVE_FAILURES ||
stopReason == LM_LOCAL_OPTIMUM ||
stopReason == LM_OPTIMIZATION_FAILED) {

@ -15,6 +15,7 @@
#pragma comment(lib, "mGuiAnal.lib")
#include <QApplication>
#include <QDesktopWidget>
#include <QHeaderView>
#include <cmath>
#include <float.h>
@ -66,13 +67,14 @@ bool nmAutoFitReadPhysicalRange(const char* parameterName,
}
// 设置某一行参数是否显示。隐藏时同步取消勾选并禁用,避免隐藏参数参与自动拟合。
// 隐藏参数取消勾选并禁用;新显示的参数默认勾选,保留已显示参数的手工选择。
void nmWxAutomaticFitting::setParameterRowVisible(QTableWidget* table, int row, bool visible)
{
if(!table || row < 0 || row >= table->rowCount()) {
return;
}
const bool wasHidden = table->isRowHidden(row);
table->setRowHidden(row, !visible);
QCheckBox* checkBox = qobject_cast<QCheckBox*>(table->cellWidget(row, 1));
@ -80,6 +82,8 @@ void nmWxAutomaticFitting::setParameterRowVisible(QTableWidget* table, int row,
checkBox->setEnabled(visible);
if(!visible) {
checkBox->setChecked(false);
} else if(wasHidden) {
checkBox->setChecked(true);
}
}
}
@ -115,7 +119,8 @@ void nmWxAutomaticFitting::updateParameterVisibility(QTableWidget* table, NM_SOL
return;
}
for(int row = 0; row < table->rowCount(); ++row) {
// 裂缝参数直接按井型设置,避免先显示再隐藏造成选择状态重置。
for(int row = 0; row < 4; ++row) {
setParameterRowVisible(table, row, true);
}
@ -426,7 +431,8 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent)
setupUI();
setWindowTitle(tr("Automatic fitting"));
setModal(true);
resize(800, 480);
// 初始尺寸随所在屏幕变化,最小尺寸交给实际控件布局决定。
resize(sizeHint());
if(m_targetWellCombo->count() > 0) {
onWellSelected(0); // 默认选中第一口井
@ -438,6 +444,15 @@ nmWxAutomaticFitting::nmWxAutomaticFitting(QWidget *parent)
DEBUG_UI("AutoFitting Constructor completed");
}
QSize nmWxAutomaticFitting::sizeHint() const
{
// 沿用项目的可用屏幕尺寸计算方式,优先取父窗口所在屏幕,支持多显示器。
QDesktopWidget* desktop = QApplication::desktop();
if(desktop == nullptr) return iDlgBase::sizeHint();
const QRect available = desktop->availableGeometry(parentWidget() ? parentWidget()->window() : this);
return QSize(available.width() * 45 / 100, available.height() * 55 / 100);
}
nmWxAutomaticFitting::~nmWxAutomaticFitting()
{
DEBUG_UI(QString("AutoFitting Destructor: this=0x%1").arg((quintptr)this, 0, 16));
@ -461,35 +476,56 @@ nmWxAutomaticFitting::~nmWxAutomaticFitting()
void nmWxAutomaticFitting::setupUI()
{
// 用浅底色区分白色设置区和参数区,避免所有内容铺在同一平面。
setObjectName("autoFittingSettings");
setFont(QFont("Microsoft YaHei", 9));
setStyleSheet(
"#autoFittingSettings { background: #f3f5f8; color: #273449; }"
"#autoFittingSettings QFrame#fittingControls, #autoFittingSettings QFrame#fittingParameters { background: #ffffff; border: 1px solid #dce2ea; border-radius: 7px; }"
"#autoFittingSettings QLabel { color: #667487; background: transparent; border: none; }"
"#autoFittingSettings QLabel#parameterHeading { color: #273449; font-weight: bold; }"
"#autoFittingSettings QLineEdit, #autoFittingSettings QComboBox { background: #ffffff; color: #273449; border: 1px solid #cfd7e2; border-radius: 4px; padding: 5px 8px; min-height: 20px; selection-background-color: #dceafb; selection-color: #273449; }"
"#autoFittingSettings QLineEdit:focus, #autoFittingSettings QComboBox:focus { border-color: #3978c6; }"
"#autoFittingSettings QComboBox::drop-down { border: none; width: 24px; }"
"#autoFittingSettings QComboBox QAbstractItemView { background: #ffffff; color: #273449; selection-background-color: #eaf1fb; selection-color: #273449; }"
"#autoFittingSettings QTableWidget { border: none; background: #ffffff; color: #273449; selection-background-color: #eaf1fb; selection-color: #273449; }"
"#autoFittingSettings QHeaderView::section { background: #f5f7fa; color: #667487; border: none; border-bottom: 1px solid #e5eaf1; padding: 7px; }"
"#autoFittingSettings QTableWidget::item { border-bottom: 1px solid #eef1f5; padding: 5px 8px; }"
"#autoFittingSettings QCheckBox { color: #273449; background: transparent; padding-left: 10px; spacing: 8px; }"
"#autoFittingSettings QPushButton { background: #ffffff; color: #46556b; border: 1px solid #cfd7e2; border-radius: 4px; padding: 6px 18px; min-width: 64px; min-height: 20px; }"
"#autoFittingSettings QPushButton:hover { background: #edf3fc; border-color: #9bb7db; }"
"#autoFittingSettings QPushButton:pressed { background: #dce8f8; }"
"#autoFittingSettings QPushButton#startFitting { background: #3978c6; color: #ffffff; border-color: #3978c6; }"
"#autoFittingSettings QPushButton#startFitting:hover { background: #306bb5; }"
"#autoFittingSettings QPushButton#startFitting:pressed { background: #285b9c; }");
m_mainLayout = new QVBoxLayout(this);
// 创建主要内容的水平布局
QHBoxLayout* contentLayout = new QHBoxLayout();
// 添加左侧间距
contentLayout->addSpacing(20);
setupControlPanel(); // 左侧控制面板
contentLayout->addLayout(m_controlLayout);
// 添加控制面板和表格之间的间距
contentLayout->addSpacing(30);
setupParameterTable(); // 右侧参数表格
contentLayout->addWidget(m_parameterTable, 1); // 设置拉伸因子为1,使表格可以扩展
// 添加右侧间距
contentLayout->addSpacing(20);
m_mainLayout->addLayout(contentLayout);
setupButtons(); // 底部按钮
m_mainLayout->setContentsMargins(16, 16, 16, 14);
m_mainLayout->setSpacing(12);
setupControlPanel();
QFrame* controlsPanel = new QFrame(this);
controlsPanel->setObjectName("fittingControls");
m_controlLayout->setContentsMargins(16, 14, 16, 14);
controlsPanel->setLayout(m_controlLayout);
m_mainLayout->addWidget(controlsPanel);
setupParameterTable();
QFrame* tablePanel = new QFrame(this);
tablePanel->setObjectName("fittingParameters");
QVBoxLayout* tableLayout = new QVBoxLayout(tablePanel);
tableLayout->setContentsMargins(12, 12, 12, 8);
tableLayout->setSpacing(10);
QLabel* tableHeading = new QLabel(tr("Fitting parameters"), tablePanel);
tableHeading->setObjectName("parameterHeading");
tableLayout->addWidget(tableHeading);
tableLayout->addWidget(m_parameterTable, 1);
m_mainLayout->addWidget(tablePanel, 1);
setupButtons();
setLayout(m_mainLayout);
}
void nmWxAutomaticFitting::setupParameterTable()
{
// 创建表格
// 每次打开时默认勾选全部参数,再按井型隐藏不可用参数。
m_parameterTable = new QTableWidget(7, 6, this);
// 设置表头
@ -499,7 +535,10 @@ void nmWxAutomaticFitting::setupParameterTable()
// 设置表格属性
m_parameterTable->setSelectionBehavior(QAbstractItemView::SelectItems);
m_parameterTable->setAlternatingRowColors(true);
m_parameterTable->setAlternatingRowColors(false);
m_parameterTable->setShowGrid(false);
m_parameterTable->verticalHeader()->setDefaultSectionSize(36);
m_parameterTable->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter);
m_parameterTable->verticalHeader()->setVisible(false);
// 设置列宽
@ -513,8 +552,11 @@ void nmWxAutomaticFitting::setupParameterTable()
// 设置固定列的宽度
m_parameterTable->setColumnWidth(0, 40); // 序号列宽度
m_parameterTable->setColumnWidth(1, 120); // 参数列宽度
m_parameterTable->setColumnWidth(1, 180); // 参数名称与复选框保留足够空间
m_parameterTable->setColumnWidth(5, 80); // 单位列宽度
// 只改变视觉顺序,保留数据读写使用的原列索引。
m_parameterTable->setColumnHidden(0, true);
header->moveSection(header->visualIndex(3), header->visualIndex(2));
// 设置表格的大小策略
m_parameterTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
@ -522,7 +564,7 @@ void nmWxAutomaticFitting::setupParameterTable()
// 渗透率 (Permeability)
m_parameterTable->setItem(0, 0, new QTableWidgetItem("1"));
m_kCheckBox = new QCheckBox(tr("Permeability"));
m_kCheckBox->setChecked(automaticFittingData.getPermeabilitySelected());
m_kCheckBox->setChecked(true);
m_parameterTable->setCellWidget(0, 1, m_kCheckBox);
m_parameterTable->setItem(0, 2, new QTableWidgetItem(QString::number(automaticFittingData.getPermeabilityMin().getValue().toDouble())));
m_parameterTable->setItem(0, 3, new QTableWidgetItem(QString::number(reservoirData.getPermeability().getValue().toDouble())));
@ -532,7 +574,7 @@ void nmWxAutomaticFitting::setupParameterTable()
// 表皮系数 (Skin)
m_parameterTable->setItem(1, 0, new QTableWidgetItem("2"));
m_sCheckBox = new QCheckBox(tr("Skin"));
m_sCheckBox->setChecked(automaticFittingData.getSkinSelected());
m_sCheckBox->setChecked(true);
m_parameterTable->setCellWidget(1, 1, m_sCheckBox);
m_parameterTable->setItem(1, 2, new QTableWidgetItem(QString::number(automaticFittingData.getSkinMin().getValue().toDouble())));
m_parameterTable->setItem(1, 3, new QTableWidgetItem());
@ -542,7 +584,7 @@ void nmWxAutomaticFitting::setupParameterTable()
// 井筒储集系数 (Wellbore storage)
m_parameterTable->setItem(2, 0, new QTableWidgetItem("3"));
m_cCheckBox = new QCheckBox(tr("Wellbore storage"));
m_cCheckBox->setChecked(automaticFittingData.getWellboreStorageSelected());
m_cCheckBox->setChecked(true);
m_parameterTable->setCellWidget(2, 1, m_cCheckBox);
m_parameterTable->setItem(2, 2, new QTableWidgetItem(QString::number(automaticFittingData.getWellboreStorageMin().getValue().toDouble())));
m_parameterTable->setItem(2, 3, new QTableWidgetItem());
@ -552,7 +594,7 @@ void nmWxAutomaticFitting::setupParameterTable()
// 孔隙度 (Porosity)
m_parameterTable->setItem(3, 0, new QTableWidgetItem("4"));
m_phiCheckBox = new QCheckBox(tr("Porosity"));
m_phiCheckBox->setChecked(automaticFittingData.getPorositySelected());
m_phiCheckBox->setChecked(true);
m_parameterTable->setCellWidget(3, 1, m_phiCheckBox);
m_parameterTable->setItem(3, 2, new QTableWidgetItem(QString::number(automaticFittingData.getPorosityMin().getValue().toDouble())));
m_parameterTable->setItem(3, 3, new QTableWidgetItem(QString::number(reservoirData.getPorosity().getValue().toDouble())));
@ -565,7 +607,7 @@ void nmWxAutomaticFitting::setupParameterTable()
// 从当前目标井读取,其他裂缝几何参数保持固定,不进入自动拟合。
m_parameterTable->setItem(5, 0, new QTableWidgetItem("6"));
m_dfcCheckBox = new QCheckBox(tr("Fracture conductivity"));
m_dfcCheckBox->setChecked(automaticFittingData.getFractureConductivitySelected());
m_dfcCheckBox->setChecked(true);
m_parameterTable->setCellWidget(5, 1, m_dfcCheckBox);
m_parameterTable->setItem(5, 2, new QTableWidgetItem(QString::number(automaticFittingData.getFractureConductivityMin().getValue().toDouble())));
m_parameterTable->setItem(5, 3, new QTableWidgetItem());
@ -575,7 +617,7 @@ void nmWxAutomaticFitting::setupParameterTable()
// 裂缝半长。该行与 Dfc 一样只对压裂井显示,初值从当前目标井读取。
m_parameterTable->setItem(6, 0, new QTableWidgetItem("7"));
m_fractureHalfLengthCheckBox = new QCheckBox(tr("Fracture half length"));
m_fractureHalfLengthCheckBox->setChecked(automaticFittingData.getFractureHalfLengthSelected());
m_fractureHalfLengthCheckBox->setChecked(true);
m_parameterTable->setCellWidget(6, 1, m_fractureHalfLengthCheckBox);
m_parameterTable->setItem(6, 2, new QTableWidgetItem(QString::number(automaticFittingData.getFractureHalfLengthMin().getValue().toDouble())));
m_parameterTable->setItem(6, 3, new QTableWidgetItem());
@ -622,11 +664,10 @@ void nmWxAutomaticFitting::setupParameterTable()
void nmWxAutomaticFitting::setupControlPanel()
{
m_controlLayout = new QVBoxLayout();
m_controlLayout->setSizeConstraint(QLayout::SetFixedSize);
// 上方弹簧
m_controlLayout->addStretch(1);
// 四列设置:目标井、算法、目标误差、迭代上限。
m_controlLayout = new QGridLayout();
m_controlLayout->setHorizontalSpacing(16);
m_controlLayout->setVerticalSpacing(6);
// 算法选择
QLabel* algorithmLabel = new QLabel(tr("Algorithm:"));
@ -634,30 +675,16 @@ void nmWxAutomaticFitting::setupControlPanel()
m_algorithmCombo->addItem(tr("PSO (Particle Swarm)"));
m_algorithmCombo->addItem(tr("LM"));
m_algorithmCombo->setCurrentIndex(0);
m_algorithmCombo->setMaximumWidth(160);
m_algorithmCombo->setMinimumWidth(160);
m_surrogateLabel = new QLabel(tr("PSO acceleration:"));
m_surrogateCombo = new QComboBox();
m_surrogateCombo->addItem(tr("Off"));
m_surrogateCombo->addItem(tr("On"));
m_surrogateCombo->setCurrentIndex(automaticFittingData.getSurrogateScreeningEnabled() ? 1 : 0);
m_surrogateCombo->setMaximumWidth(160);
m_surrogateCombo->setMinimumWidth(160);
connect(m_algorithmCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onAlgorithmChanged(int)));
onAlgorithmChanged(m_algorithmCombo->currentIndex());
// 迭代次数
QLabel* iterationLabel = new QLabel(tr("Number of iterations:"));
m_iterationLabel = new QLabel();
m_iterationEdit = new QLineEdit(QString::number(automaticFittingData.getIterationCount().getValue().toInt()));
m_iterationEdit->setMaximumWidth(100);
m_iterationEdit->setMinimumWidth(100);
// 误差上限
QLabel* errorLabel = new QLabel(tr("Error tolerance:"));
QLabel* errorLabel = new QLabel(tr("Target error:"));
m_errorLimitEdit = new QLineEdit(QString::number(automaticFittingData.getErrorTolerance().getValue().toDouble()));
m_errorLimitEdit->setMaximumWidth(100);
m_errorLimitEdit->setMinimumWidth(100);
// 目标井选择
QLabel* wellLabel = new QLabel(tr("Target Well:"));
@ -689,39 +716,26 @@ void nmWxAutomaticFitting::setupControlPanel()
}
connect(m_targetWellCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onWellSelected(int)));
m_targetWellCombo->setMaximumWidth(100);
m_targetWellCombo->setMinimumWidth(100);
// 添加到垂直布局
m_controlLayout->addWidget(algorithmLabel);
m_controlLayout->addWidget(m_algorithmCombo);
m_controlLayout->addSpacing(15);
m_controlLayout->addWidget(m_surrogateLabel);
m_controlLayout->addWidget(m_surrogateCombo);
m_controlLayout->addSpacing(15);
m_controlLayout->addWidget(iterationLabel);
m_controlLayout->addWidget(m_iterationEdit);
m_controlLayout->addSpacing(15);
m_controlLayout->addWidget(errorLabel);
m_controlLayout->addWidget(m_errorLimitEdit);
m_controlLayout->addSpacing(15);
m_controlLayout->addWidget(wellLabel);
m_controlLayout->addWidget(m_targetWellCombo);
// 下方弹簧
m_controlLayout->addStretch(1);
m_targetWellCombo->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon);
m_targetWellCombo->setMinimumContentsLength(10);
m_controlLayout->addWidget(wellLabel, 0, 0);
m_controlLayout->addWidget(m_targetWellCombo, 1, 0);
m_controlLayout->addWidget(algorithmLabel, 0, 1);
m_controlLayout->addWidget(m_algorithmCombo, 1, 1);
m_controlLayout->addWidget(errorLabel, 0, 2);
m_controlLayout->addWidget(m_errorLimitEdit, 1, 2);
m_controlLayout->addWidget(m_iterationLabel, 0, 3);
m_controlLayout->addWidget(m_iterationEdit, 1, 3);
for(int column = 0; column < 4; ++column) m_controlLayout->setColumnStretch(column, 1);
onAlgorithmChanged(m_algorithmCombo->currentIndex());
}
void nmWxAutomaticFitting::onAlgorithmChanged(int index)
{
// LM 不使用 PSO 加速;只切换可用状态,保留切回 PSO 时的原选择。
// 两种算法统一显示最大迭代次数,提示仍说明 LM 共用次数的含义。
const bool usePSO = (index == 0);
m_surrogateLabel->setEnabled(usePSO);
m_surrogateCombo->setEnabled(usePSO);
m_iterationLabel->setText(tr("Maximum iterations:"));
m_iterationEdit->setToolTip(usePSO ? QString() : tr("Height, shape and total-error adjustments share this iteration limit; one iteration may call the solver multiple times."));
}
void nmWxAutomaticFitting::setupButtons()
@ -729,7 +743,9 @@ void nmWxAutomaticFitting::setupButtons()
QHBoxLayout* buttonLayout = new QHBoxLayout();
m_reverseBtn = new QPushButton(tr("Reverse Selection"));
m_okBtn = new QPushButton(tr("OK"));
m_okBtn = new QPushButton(tr("Start fitting"));
m_okBtn->setObjectName("startFitting");
m_okBtn->setDefault(true);
m_cancelBtn = new QPushButton(tr("Cancel"));
// 设置按钮大小
@ -737,10 +753,10 @@ void nmWxAutomaticFitting::setupButtons()
m_okBtn->setMinimumWidth(80);
m_cancelBtn->setMinimumWidth(80);
buttonLayout->addStretch();
buttonLayout->addWidget(m_reverseBtn);
buttonLayout->addWidget(m_okBtn);
buttonLayout->addStretch();
buttonLayout->addWidget(m_cancelBtn);
buttonLayout->addWidget(m_okBtn);
m_mainLayout->addLayout(buttonLayout);
@ -989,7 +1005,8 @@ void nmWxAutomaticFitting::setAutomaticFittingValue()
automaticFittingData.setPorositySelected(m_phiCheckBox->isChecked());
automaticFittingData.setFractureConductivitySelected(m_dfcCheckBox->isChecked());
automaticFittingData.setFractureHalfLengthSelected(m_fractureHalfLengthCheckBox->isChecked());
automaticFittingData.setSurrogateScreeningEnabled(m_surrogateCombo && m_surrogateCombo->currentIndex() == 1);
// 界面不再提供 PSO 加速选项,同时关闭旧配置中可能保留的加速开关。
automaticFittingData.setSurrogateScreeningEnabled(false);
// 保存渗透率的最小值和最大值
automaticFittingData.getPermeabilityMin().setValue(m_parameterTable->item(0, 2)->text().toDouble());
@ -1163,7 +1180,15 @@ void nmWxAutomaticFitting::startAutoFitting(const QVector<QVector<double>>& targ
double targetError = m_errorLimitEdit->text().toDouble();
QString wellName = m_targetWellCombo->currentText();
m_progressMonitor->setFittingParameters(maxIterations, targetError, wellName);
m_progressMonitor->setSelectedParameters(selectedParams);
// 单位和勾选参数保持同一顺序,供横排参数区显示。
QStringList selectedUnits;
if(m_kCheckBox->isChecked()) selectedUnits << tr("mD");
if(m_sCheckBox->isChecked()) selectedUnits << QString();
if(m_cCheckBox->isChecked()) selectedUnits << tr("m^3/MPa");
if(m_phiCheckBox->isChecked()) selectedUnits << QString();
if(m_dfcCheckBox->isChecked()) selectedUnits << tr("md.m");
if(m_fractureHalfLengthCheckBox->isChecked()) selectedUnits << tr("m");
m_progressMonitor->setSelectedParameters(selectedParams, selectedUnits);
m_progressMonitor->show();
QTimer::singleShot(100, this, SLOT(runAutoFitting()));

@ -1,9 +1,11 @@
#include "nmWxAutomaticfittingStart.h"
#include <QtGui/QApplication>
#include <QDesktopWidget>
#include <QtCore/QEvent>
#include <QtGui/QMouseEvent>
#include <QtGui/QFont>
#include <QScrollBar>
#include <QTextDocument>
#include <QList>
#include <QPainter>
#include <QPaintEvent>
@ -33,7 +35,8 @@ nmAutomaticFittingCurveChart::nmAutomaticFittingCurveChart(QWidget* parent)
, m_bestFitness(0.0)
, m_pseudoPressureMode(false)
{
setMinimumHeight(220);
// 保留绘图空间,同时允许小屏幕压缩曲线区域的高度。
setMinimumHeight(160);
setMinimumWidth(420);
}
@ -181,7 +184,8 @@ void nmAutomaticFittingCurveChart::paintEvent(QPaintEvent* event)
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.fillRect(rect(), QColor(240, 240, 240));
// 保持全宽白色绘图区,曲线和实测标记沿用原有红绿配色。
painter.fillRect(rect(), Qt::white);
const int marginLeft = 62;
const int marginTop = 18;
@ -191,7 +195,7 @@ void nmAutomaticFittingCurveChart::paintEvent(QPaintEvent* event)
qMax(10, width() - marginLeft - marginRight),
qMax(10, height() - marginTop - marginBottom));
painter.fillRect(m_chartRect, QColor(252, 252, 252));
painter.fillRect(m_chartRect, Qt::white);
drawGrid(painter);
drawAxes(painter);
drawCurveLine(painter, m_bestPressure, QColor(250, 74, 74));
@ -210,7 +214,7 @@ void nmAutomaticFittingCurveChart::drawGrid(QPainter& painter)
int yStart = static_cast<int>(qFloor(m_logRange.top()));
int yEnd = static_cast<int>(qCeil(m_logRange.bottom()));
painter.setPen(QPen(QColor(236, 236, 236), 1, Qt::SolidLine));
painter.setPen(QPen(QColor(247, 248, 250), 1, Qt::SolidLine));
for(int decade = xStart; decade <= xEnd; ++decade) {
for(int multiplier = 2; multiplier <= 9; ++multiplier) {
double value = decade + qLn(static_cast<double>(multiplier)) / qLn(10.0);
@ -237,7 +241,7 @@ void nmAutomaticFittingCurveChart::drawGrid(QPainter& painter)
}
}
painter.setPen(QPen(QColor(214, 214, 214), 1, Qt::SolidLine));
painter.setPen(QPen(QColor(228, 232, 238), 1, Qt::SolidLine));
for(int x = xStart; x <= xEnd; ++x) {
if(x < m_logRange.left() || x > m_logRange.right()) {
continue;
@ -397,167 +401,237 @@ nmWxAutomaticfittingStart::nmWxAutomaticfittingStart(QWidget *parent)
// 设置窗口属性
setWindowTitle(tr("Auto Fitting Progress"));
setMinimumSize(1000, 650);
// 初始尺寸随所在屏幕变化,最小尺寸交给实际控件布局决定。
resize(sizeHint());
setModal(false);
}
QSize nmWxAutomaticfittingStart::sizeHint() const
{
// 加宽进程窗口,为曲线和完整日志行留出空间,同时保留屏幕边缘。
QDesktopWidget* desktop = QApplication::desktop();
if(desktop == nullptr) return iDlgBase::sizeHint();
const QRect available = desktop->availableGeometry(parentWidget() ? parentWidget()->window() : this);
return QSize(available.width() * 75 / 100, available.height() * 65 / 100);
}
nmWxAutomaticfittingStart::~nmWxAutomaticfittingStart()
{
}
void nmWxAutomaticfittingStart::setupUI()
{
// 创建主要布局
mainLayout = new QHBoxLayout();
leftLayout = new QVBoxLayout();
rightLayout = new QVBoxLayout();
// 设置左右布局
// 曲线和日志左右并排,参数横排占满下方整行,底部使用一行汇总。
setObjectName("autoFittingProgress");
setFont(QFont("Microsoft YaHei", 9));
setStyleSheet(
"#autoFittingProgress { background: #ffffff; color: #233247; }"
"#autoFittingProgress QGroupBox { background: #ffffff; color: #233247; border: none; margin-top: 0; padding-top: 0; font-weight: bold; }"
"#autoFittingProgress QLabel { color: #66768a; background: transparent; font-weight: normal; }"
"#autoFittingProgress QGroupBox#fittingSummary { padding-top: 0; }"
"#autoFittingProgress QGroupBox#fittingSummary QLabel { font-size: 14px; color: #46556b; }"
"#autoFittingProgress QWidget#parameterStrip { background: #ffffff; border-top: 1px solid #e3e8ef; }"
"#autoFittingProgress QWidget#parameterSeparator { background: #e3e8ef; }"
"#autoFittingProgress QLabel#parameterName { font-size: 12px; }"
"#autoFittingProgress QLabel#parameterValue { color: #233247; font-size: 18px; font-weight: 500; }"
"#autoFittingProgress QGroupBox#fittingSummary QLabel#summaryValue { color: #233247; }"
"#autoFittingProgress QGroupBox#fittingSummary QLabel#stateBadge { background: #edf3fc; color: #366baf; border-radius: 5px; padding: 4px 10px; font-size: 12px; }"
"#autoFittingProgress QGroupBox#fittingLog { padding-top: 0; }"
"#autoFittingProgress QLabel#sectionHeading { color: #233247; font-size: 13px; font-weight: bold; }"
"#autoFittingProgress QWidget#fittingFooter { background: #ffffff; border-top: 1px solid #e3e8ef; }"
"#autoFittingProgress QLabel#resultSummary { color: #66768a; font-size: 14px; }"
"#autoFittingProgress QLabel#stageStatus { color: #66768a; font-size: 12px; }"
"#autoFittingProgress QLabel#runSummary { color: #46556b; font-size: 14px; }"
"#autoFittingProgress QTextEdit { border: none; background: #ffffff; color: #233247; font-size: 13px; font-weight: normal; selection-background-color: #eaf1fb; selection-color: #233247; }"
"#autoFittingProgress QPushButton { background: #ffffff; color: #46556b; border: 1px solid #cfd7e2; border-radius: 5px; padding: 8px 22px; min-width: 64px; min-height: 20px; }"
"#autoFittingProgress QPushButton:hover { background: #edf3fc; border-color: #9bb7db; }"
"#autoFittingProgress QPushButton:pressed { background: #dce8f8; }"
"#autoFittingProgress QPushButton:disabled { background: #f0f2f5; color: #98a2b1; border-color: #e0e5ec; }"
"#autoFittingProgress QPushButton#logToggle { border: none; background: transparent; color: #738091; min-width: 0; padding: 3px 8px; }"
"#autoFittingProgress QPushButton#logToggle:hover { color: #396fc0; }"
"#autoFittingProgress QSplitter::handle { background: #ffffff; }");
// 控件复用原有数据更新逻辑,仅重新组织所属区域。
setupParameterArea();
setupTableArea();
setupParameterStrip();
setupChartArea();
setupLogArea();
setupControlArea();
// 左侧布局
leftLayout->addWidget(parameterGroup);
leftLayout->addStretch();
leftLayout->addWidget(tableGroup);
// 右侧布局
rightSplitter = new QSplitter(Qt::Vertical, this);
setupLogArea();
// 日志只占右侧较窄区域,两侧底边对齐,分隔条允许调整宽度。
rightSplitter = new QSplitter(Qt::Horizontal, this);
rightSplitter->setHandleWidth(4);
rightSplitter->addWidget(chartGroup);
rightSplitter->addWidget(logGroup);
rightSplitter->setStretchFactor(0, 3);
rightSplitter->setStretchFactor(1, 2);
rightSplitter->setSizes(QList<int>() << 320 << 260);
rightLayout->addWidget(rightSplitter);
// 主布局
mainLayout->addLayout(leftLayout, 1);
mainLayout->addLayout(rightLayout, 2);
rightSplitter->setStretchFactor(0, 1);
rightSplitter->setStretchFactor(1, 1);
rightSplitter->setChildrenCollapsible(false);
rightSplitter->setSizes(QList<int>() << 650 << 350);
// 控制区域布局
QHBoxLayout *controlLayout = new QHBoxLayout();
controlLayout->addWidget(progressBar);
QWidget* footer = new QWidget(this);
footer->setObjectName("fittingFooter");
footer->setAttribute(Qt::WA_StyledBackground, true);
QHBoxLayout *controlLayout = new QHBoxLayout(footer);
controlLayout->setContentsMargins(22, 14, 22, 14);
controlLayout->setSpacing(24);
controlLayout->addWidget(resultSummary);
controlLayout->addWidget(runSummary);
controlLayout->addStretch();
controlLayout->addWidget(progressBar, 1);
controlLayout->addWidget(stopButton);
QVBoxLayout *overallLayout = new QVBoxLayout();
overallLayout->addLayout(mainLayout);
overallLayout->addLayout(controlLayout);
overallLayout->setContentsMargins(0, 0, 0, 0);
overallLayout->setSpacing(0);
overallLayout->addWidget(parameterGroup);
overallLayout->addWidget(rightSplitter, 1);
overallLayout->addWidget(parameterStrip);
overallLayout->addWidget(footer);
setLayout(overallLayout);
}
void nmWxAutomaticfittingStart::setupParameterArea()
{
parameterGroup = new QGroupBox(tr("Parameter Display"), this);
parameterGroup->setFixedWidth(280);
parameterGroup = new QGroupBox(this);
parameterGroup->setObjectName("fittingSummary");
QGridLayout *paramLayout = new QGridLayout(parameterGroup);
paramLayout->setContentsMargins(22, 19, 22, 17);
paramLayout->setHorizontalSpacing(12);
paramLayout->setVerticalSpacing(8);
// 创建标签
algorithmTypeLabel = new QLabel(tr("Algorithm:"));
currentIterationLabel = new QLabel(tr("Current Iteration:"));
maxIterationLabel = new QLabel(tr("Max Iteration:"));
currentIterationLabel = new QLabel(tr("Iterations:"));
currentComfortLabel = new QLabel(tr("Current Error:"));
bestComfortLabel = new QLabel(tr("Best Error:"));
targetPrecisionLabel = new QLabel(tr("Target Precision:"));
targetPrecisionLabel = new QLabel(tr("Target error:"));
targetWellLabel = new QLabel(tr("Target Well:"));
// 创建数值显示标签
algorithmTypeValue = new QLabel("PSO");
currentIterationValue = new QLabel("0");
maxIterationValue = new QLabel("100");
stateBadge = new QLabel(tr("Waiting for fitting to start"));
stateBadge->setObjectName("stateBadge");
currentIterationValue = new QLabel("0 / 100");
currentIterationValue->setObjectName("summaryValue");
currentComfortValue = new QLabel("N/A");
bestComfortValue = new QLabel("N/A");
targetPrecisionValue = new QLabel("0.001");
targetWellValue = new QLabel("N/A");
// 添加到布局
paramLayout->addWidget(algorithmTypeLabel, 0, 0);
paramLayout->addWidget(algorithmTypeValue, 0, 1);
paramLayout->addWidget(currentIterationLabel, 1, 0);
paramLayout->addWidget(currentIterationValue, 1, 1);
paramLayout->addWidget(maxIterationLabel, 2, 0);
paramLayout->addWidget(maxIterationValue, 2, 1);
paramLayout->addWidget(currentComfortLabel, 3, 0);
paramLayout->addWidget(currentComfortValue, 3, 1);
paramLayout->addWidget(bestComfortLabel, 4, 0);
paramLayout->addWidget(bestComfortValue, 4, 1);
paramLayout->addWidget(targetPrecisionLabel, 5, 0);
paramLayout->addWidget(targetPrecisionValue, 5, 1);
paramLayout->addWidget(targetWellLabel, 6, 0);
paramLayout->addWidget(targetWellValue, 6, 1);
algorithmTypeValue->setObjectName("summaryValue");
targetWellValue->setObjectName("summaryValue");
targetPrecisionValue->setObjectName("summaryValue");
// 顶部统一显示累计迭代数和上限;PSO 的额外误差信息放在第二行。
paramLayout->addWidget(targetWellLabel, 0, 0);
paramLayout->addWidget(targetWellValue, 0, 1);
paramLayout->addWidget(algorithmTypeLabel, 0, 2);
paramLayout->addWidget(algorithmTypeValue, 0, 3);
paramLayout->addWidget(targetPrecisionLabel, 0, 4);
paramLayout->addWidget(targetPrecisionValue, 0, 5);
paramLayout->addWidget(currentIterationLabel, 0, 6);
paramLayout->addWidget(currentIterationValue, 0, 7);
paramLayout->addWidget(stateBadge, 0, 9, Qt::AlignRight | Qt::AlignVCenter);
paramLayout->addWidget(currentComfortLabel, 1, 0);
paramLayout->addWidget(currentComfortValue, 1, 1);
paramLayout->addWidget(bestComfortLabel, 1, 2);
paramLayout->addWidget(bestComfortValue, 1, 3);
paramLayout->setColumnStretch(8, 1);
targetWellValue->setWordWrap(true);
parameterGroup->setLayout(paramLayout);
}
void nmWxAutomaticfittingStart::setupTableArea()
void nmWxAutomaticfittingStart::setupParameterStrip()
{
tableGroup = new QGroupBox(tr("Parameter Table"), this);
tableGroup->setFixedWidth(300);
QVBoxLayout *tableLayout = new QVBoxLayout(tableGroup);
tableWidget = new QTableWidget(0, 2, this);
tableWidget->setHorizontalHeaderLabels(QStringList() << tr("Parameter Name") << tr("Optimal Value"));
// 设置表格属性
tableWidget->setAlternatingRowColors(true);
tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
tableWidget->verticalHeader()->setVisible(false);
tableWidget->setMinimumHeight(310);
// 设置列宽
QHeaderView* header = tableWidget->horizontalHeader();
header->setResizeMode(0, QHeaderView::ResizeToContents);
header->setResizeMode(1, QHeaderView::Stretch);
// 设置表格填充整个区域
tableWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
// 隐藏滚动条
tableWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
tableLayout->addWidget(tableWidget);
tableGroup->setLayout(tableLayout);
// 参数独占曲线和日志下方的整行,横向均分空间,不保留尾部空白。
parameterStrip = new QWidget(this);
parameterStrip->setObjectName("parameterStrip");
parameterStrip->setAttribute(Qt::WA_StyledBackground, true);
parameterStrip->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
// 标题与参数共用网格,竖线跨越两行,上下留白均为 4 像素。
parameterLayout = new QGridLayout(parameterStrip);
parameterLayout->setContentsMargins(22, 4, 22, 4);
parameterLayout->setHorizontalSpacing(0);
parameterLayout->setVerticalSpacing(2);
}
void nmWxAutomaticfittingStart::setupChartArea()
{
chartGroup = new QGroupBox(tr("Fitting Curve"), this);
// 曲线上方仅保留标题。
chartGroup = new QGroupBox(this);
QVBoxLayout* chartLayout = new QVBoxLayout(chartGroup);
QLabel* heading = new QLabel(tr("Fitting Curve"), chartGroup);
heading->setObjectName("sectionHeading");
chartLayout->addWidget(heading);
curveChart = new nmAutomaticFittingCurveChart(chartGroup);
curveChart->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
chartLayout->setMargin(6);
chartLayout->setContentsMargins(22, 17, 22, 12);
chartLayout->addWidget(curveChart);
chartGroup->setLayout(chartLayout);
chartGroup->setMinimumHeight(240);
chartGroup->setMinimumHeight(200);
}
void nmWxAutomaticfittingStart::setupLogArea()
{
logGroup = new QGroupBox(tr("Log Window"), this);
// 日志标题始终可见,正文收起时仍继续接收日志。
logGroup = new QGroupBox(this);
logGroup->setObjectName("fittingLog");
QVBoxLayout *logLayout = new QVBoxLayout(logGroup);
logLayout->setContentsMargins(18, 13, 22, 16);
logLayout->setSpacing(8);
logLayout->setAlignment(Qt::AlignTop);
QHBoxLayout* logHeader = new QHBoxLayout();
QLabel* heading = new QLabel(tr("Fitting process"), logGroup);
heading->setObjectName("sectionHeading");
logToggle = new QPushButton(tr("Collapse"), logGroup);
logToggle->setObjectName("logToggle");
logToggle->setAutoDefault(false);
logHeader->addWidget(heading);
logHeader->addStretch();
logHeader->addWidget(logToggle);
logLayout->addLayout(logHeader);
// 阶段状态放到标题下方,避免挤占窄日志区的标题和按钮。
logLayout->addWidget(stageStatus);
connect(logToggle, SIGNAL(clicked()), this, SLOT(onToggleLog()));
logTextEdit = new QTextEdit(this);
logTextEdit->setReadOnly(true);
logTextEdit->setFont(QFont("Consolas", 9));
// 小屏幕允许缩短可见日志区域,完整内容仍可通过滚动查看。
logTextEdit->setMinimumHeight(48);
logTextEdit->setMinimumWidth(180);
logTextEdit->setFont(font());
// 正文与标题左对齐,沿用窗口白底,不额外绘制日志底色。
logTextEdit->document()->setDocumentMargin(0);
// 添加初始日志
addLogMessage(tr("System initialization completed"));
addLogMessage(tr("Waiting for fitting to start"));
logLayout->addWidget(logTextEdit);
logLayout->addWidget(logTextEdit, 1);
logGroup->setLayout(logLayout);
}
void nmWxAutomaticfittingStart::setupControlArea()
{
// 运行状态放在拟合过程标题下方;底部汇总保持单行。
stageStatus = new QLabel(tr("Waiting for fitting to start"), this);
stageStatus->setObjectName("stageStatus");
stageStatus->setWordWrap(true);
stageStatus->hide();
resultSummary = new QLabel(this);
resultSummary->setObjectName("resultSummary");
resultSummary->setTextFormat(Qt::RichText);
resultSummary->setWordWrap(false);
resultSummary->hide();
runSummary = new QLabel(this);
runSummary->setObjectName("runSummary");
runSummary->setWordWrap(false);
runSummary->hide();
// 进度条
progressBar = new QProgressBar(this);
progressBar->setRange(0, 100);
@ -571,7 +645,6 @@ void nmWxAutomaticfittingStart::setupControlArea()
// 停止按钮
stopButton = new QPushButton(tr("Stop"), this);
stopButton->setFixedSize(80, 30);
stopButton->setEnabled(false);
// 连接信号槽
@ -583,10 +656,10 @@ void nmWxAutomaticfittingStart::setAutoFitter(nmCalculationAutoFitPSO* autoFitte
m_autoFitterPSO = autoFitter;
m_autoFitterLM = nullptr;
m_algorithmName = "PSO";
updateAlgorithmDisplay();
// 更新算法类型显示
algorithmTypeValue->setText("PSO");
algorithmTypeValue->setStyleSheet("QLabel { color: blue; font-weight: bold; }");
if (m_autoFitterPSO) {
connect(m_autoFitterPSO, SIGNAL(progressUpdated(int, double)),
@ -612,13 +685,15 @@ void nmWxAutomaticfittingStart::setAutoFitter(nmCalculationAutoFitLM* autoFitter
m_autoFitterPSO = nullptr;
m_autoFitterLM = autoFitter;
m_algorithmName = "LM";
updateAlgorithmDisplay();
// LM 日志由优化器统一组织,清除通用初始化提示。
logTextEdit->clear();
algorithmTypeValue->setText(m_algorithmName);
algorithmTypeValue->setStyleSheet("QLabel { color: blue; font-weight: bold; }");
if (m_autoFitterLM) {
connect(m_autoFitterLM, SIGNAL(fittingStageChanged(int)),
this, SLOT(onLMStageChanged(int)));
connect(m_autoFitterLM, SIGNAL(progressUpdated(int, double)),
this, SLOT(onFittingProgress(int, double)));
connect(m_autoFitterLM, SIGNAL(fittingFinished(bool, QString)),
@ -634,14 +709,48 @@ void nmWxAutomaticfittingStart::setAutoFitter(nmCalculationAutoFitLM* autoFitter
}
}
void nmWxAutomaticfittingStart::updateAlgorithmDisplay()
{
// 迭代数始终在顶部显示,PSO 保留原有额外误差和进度条。
const bool lm = m_autoFitterLM != nullptr;
QLabel* psoLabels[] = { currentComfortLabel, currentComfortValue,
bestComfortLabel, bestComfortValue };
for(int i = 0; i < 4; ++i) psoLabels[i]->setVisible(!lm);
progressBar->setVisible(!lm);
stageStatus->setVisible(lm);
resultSummary->setVisible(lm);
if(lm) updateCurrentError(m_autoFitterLM->getBestFitness());
if(lm) onLMStageChanged(0);
}
void nmWxAutomaticfittingStart::onLMStageChanged(int stage)
{
// 仅更新拟合过程区域的运行状态,不在曲线上方增加阶段栏。
const QString stages[] = { tr("Calculating initial error..."), tr("Height alignment"),
tr("Shape adjustment"), tr("Total-error adjustment") };
stageStatus->setText(stages[stage]);
}
void nmWxAutomaticfittingStart::onToggleLog()
{
// 收起正文时把横向空间让给曲线,保留标题和按钮以便重新展开。
const bool collapse = !logTextEdit->isHidden();
if(collapse) expandedLogSizes = rightSplitter->sizes();
logTextEdit->setVisible(!collapse);
logToggle->setText(collapse ? tr("Expand") : tr("Collapse"));
logGroup->layout()->activate();
logGroup->setMaximumWidth(collapse ? logGroup->minimumSizeHint().width() : QWIDGETSIZE_MAX);
if(!collapse && !expandedLogSizes.isEmpty()) rightSplitter->setSizes(expandedLogSizes);
}
void nmWxAutomaticfittingStart::setFittingParameters(int maxIterations, double targetError, const QString& wellName)
{
m_maxIterations = maxIterations;
m_targetError = targetError;
m_wellName = wellName;
maxIterationValue->setText(QString::number(maxIterations));
targetPrecisionValue->setText(formatScientific(targetError));
currentIterationValue->setText(QString("0 / %1").arg(maxIterations));
targetPrecisionValue->setText(QString::number(targetError, 'g', 6));
targetWellValue->setText(wellName);
progressBar->setRange(0, maxIterations);
@ -654,6 +763,7 @@ void nmWxAutomaticfittingStart::setFittingParameters(int maxIterations, double t
void nmWxAutomaticfittingStart::markFittingStarted()
{
m_startTime = QDateTime::currentDateTime();
stateBadge->setText(tr("Fitting in progress"));
const QString algorithmName = m_algorithmName;
QString timestamp = m_startTime.toString("yyyy-MM-dd hh:mm:ss");
@ -663,12 +773,56 @@ void nmWxAutomaticfittingStart::markFittingStarted()
.arg(timestamp));
}
void nmWxAutomaticfittingStart::setSelectedParameters(const QStringList& parameterNames)
void nmWxAutomaticfittingStart::setSelectedParameters(const QStringList& parameterNames,
const QStringList& parameterUnits)
{
m_selectedParameters = parameterNames;
// 仅在选择变化时重建标签,拟合期间只更新数值,避免布局反复抖动。
QLayoutItem* item = nullptr;
while((item = parameterLayout->takeAt(0)) != nullptr) {
delete item->widget();
delete item;
}
parameterValues.clear();
for(int column = 0; column < parameterLayout->columnCount(); ++column) {
parameterLayout->setColumnStretch(column, 0);
}
QLabel* heading = new QLabel(tr("Current parameters"), parameterStrip);
heading->setObjectName("sectionHeading");
parameterLayout->addWidget(heading, 0, 0);
for(int i = 0; i < parameterNames.size(); ++i) {
// 名称和单位在上、数值在下,相邻参数用细竖线分隔。
QWidget* cell = new QWidget(parameterStrip);
cell->setAttribute(Qt::WA_StyledBackground, true);
QVBoxLayout* cellLayout = new QVBoxLayout(cell);
cellLayout->setContentsMargins(12, 0, 12, 0);
cellLayout->setSpacing(2);
const QString unit = parameterUnits.value(i);
QLabel* name = new QLabel(parameterNames[i] + (unit.isEmpty() ? QString() : " / " + unit), cell);
name->setObjectName("parameterName");
name->setWordWrap(true);
name->setAlignment(Qt::AlignCenter);
QLabel* value = new QLabel("--", cell);
value->setObjectName("parameterValue");
value->setAlignment(Qt::AlignCenter);
value->setTextInteractionFlags(Qt::TextSelectableByMouse);
cellLayout->addWidget(name, 1);
cellLayout->addWidget(value);
parameterLayout->addWidget(cell, 1, i * 2);
parameterLayout->setColumnStretch(i * 2, 1);
if(i > 0) {
// 分隔线独立于参数单元格,避免标题导致上下间距不一致。
QWidget* separator = new QWidget(parameterStrip);
separator->setObjectName("parameterSeparator");
separator->setAttribute(Qt::WA_StyledBackground, true);
separator->setFixedWidth(1);
parameterLayout->addWidget(separator, 0, i * 2 - 1, 2, 1);
}
parameterValues.append(value);
}
parameterStrip->setVisible(!parameterNames.isEmpty());
// 立即更新参数表格
updateParameterTable();
// 立即显示当前已接受的参数值。
updateParameterValues();
const QString algorithmName = m_algorithmName;
if(!m_autoFitterLM) addLogMessage(tr("%1 selected parameters: %2").arg(algorithmName).arg(parameterNames.join(", ")));
@ -690,17 +844,17 @@ void nmWxAutomaticfittingStart::setPseudoPressureMode(bool enabled)
void nmWxAutomaticfittingStart::onFittingProgress(int iteration, double fitness)
{
// LM 预调整没有固定总步数,单独显示阶段;整体阶段从局部迭代 0 开始计进度。
if (m_autoFitterLM && iteration < 0) {
progressBar->setValue(0);
progressBar->setFormat(tr("Pre-adjustment"));
currentIterationValue->setText(tr("Pre-adjustment"));
// LM 各阶段显示累计迭代次数;达到上限之前仍可能提前收敛,不显示完成百分比。
currentIterationValue->setText(QString("%1 / %2").arg(qMax(0, iteration)).arg(m_maxIterations));
if(m_autoFitterLM) {
updateCurrentError(fitness);
return;
} else {
int displayIteration = qMax(m_autoFitterLM ? 0 : 1, qMin(iteration, m_maxIterations));
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));
currentIterationValue->setText(QString::number(displayIteration));
currentIterationValue->setText(QString("%1 / %2").arg(displayIteration).arg(m_maxIterations));
}
currentComfortValue->setText(formatScientific(fitness));
@ -713,8 +867,8 @@ void nmWxAutomaticfittingStart::onFittingProgress(int iteration, double fitness)
if(!m_autoFitterLM) addLogMessage(tr("Better solution found: %1").arg(formatScientific(fitness)));
// 更新参数表格
updateParameterTable();
// 更新参数值。
updateParameterValues();
} else {
bestComfortValue->setStyleSheet("QLabel { color: black; }");
}
@ -724,11 +878,20 @@ void nmWxAutomaticfittingStart::onFittingFinished(bool success, const QString& m
{
m_isFinished = true;
m_isFinalizing = false;
stateBadge->setText(success ? tr("Fitting ended") : tr("Fitting failed"));
stateBadge->setStyleSheet(success
? "QLabel { color: #28765f; background: #edf7f2; }"
: "QLabel { color: #a04438; background: #fff0ed; }");
const QString algorithmName = m_algorithmName;
// 更新状态
if (success) {
if(m_autoFitterLM) {
stageStatus->setText(success ? tr("Fitting ended") : tr("Fitting failed"));
// 完成区直接读取优化器数值,失败评价显示占位符,不暴露惩罚值。
updateCurrentError(m_autoFitterLM->getBestFitness());
stageStatus->hide();
} else if (success) {
progressBar->setValue(m_maxIterations);
// 区分正常完成和用户停止
@ -781,8 +944,8 @@ void nmWxAutomaticfittingStart::onFittingFinished(bool success, const QString& m
disconnect(stopButton, SIGNAL(clicked()), this, SLOT(onStopButtonClicked()));
connect(stopButton, SIGNAL(clicked()), this, SLOT(close()));
// 最终更新参数表格
updateParameterTable();
// 同步最终参数值。
updateParameterValues();
// 添加完成时间戳
QDateTime endTime = QDateTime::currentDateTime();
@ -803,7 +966,13 @@ void nmWxAutomaticfittingStart::onFittingFinished(bool success, const QString& m
// LM 的调用次数由优化器汇总,这里只在结束时显示一次总耗时。
if(m_autoFitterLM) {
addLogMessage(tr("Total time: %1 min %2 s").arg(minutes).arg(remainSec, 0, 'f', 1));
// 不足一分钟只显示秒,窗口汇总和完成日志共用同一耗时文本。
const QString elapsed = minutes == 0 ? tr("%1 s").arg(seconds, 0, 'f', 1)
: tr("%1 min %2 s").arg(minutes).arg(remainSec, 0, 'f', 1);
addLogMessage(tr("Total time: %1").arg(elapsed));
runSummary->setText(tr("Solver calls: %1 | Total time: %2")
.arg(m_autoFitterLM->getSolverCallCount()).arg(elapsed));
runSummary->show();
} else {
addLogMessage(tr("Total fitting time: %1 s (%2 min %3 s), total solver calls: %4")
.arg(seconds, 0, 'f', 2).arg(minutes).arg(remainSec, 0, 'f', 2).arg(totalEvaluations));
@ -816,6 +985,8 @@ void nmWxAutomaticfittingStart::onFinalizingStarted()
{
// 最终完整求解负责生成正式快照,此阶段不能再次停止或关闭进度窗口。
m_isFinalizing = true;
stateBadge->setText(tr("Finalizing..."));
if(m_autoFitterLM) stageStatus->setText(tr("Generating final result..."));
stopButton->setEnabled(false);
stopButton->setText(tr("Finalizing..."));
addLogMessage(tr("Generating the final result with the current best parameters..."));
@ -837,6 +1008,8 @@ void nmWxAutomaticfittingStart::onStopButtonClicked()
// 先锁定按钮,等优化循环退出后由 finalizingStarted() 切换到最终求解状态。
stopButton->setEnabled(false);
stopButton->setText(tr("Stopping..."));
stateBadge->setText(tr("Stopping..."));
if(m_autoFitterLM) stageStatus->setText(tr("Stopping..."));
if (m_autoFitterPSO) {
m_autoFitterPSO->stopFitting();
} else if (m_autoFitterLM) {
@ -847,40 +1020,27 @@ void nmWxAutomaticfittingStart::onStopButtonClicked()
}
}
void nmWxAutomaticfittingStart::updateParameterTable()
void nmWxAutomaticfittingStart::updateParameterValues()
{
// 设置表格行数
tableWidget->setRowCount(m_selectedParameters.size());
// 更新表格内容
for (int i = 0; i < m_selectedParameters.size(); ++i) {
// 参数名
QTableWidgetItem* nameItem = new QTableWidgetItem(m_selectedParameters.at(i));
nameItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
tableWidget->setItem(i, 0, nameItem);
// 最优参数值
QString valueText = "N/A";
if (m_autoFitterPSO) {
QVector<double> bestSolution = m_autoFitterPSO->getBestSolution();
if (i < bestSolution.size()) {
valueText = formatScientific(bestSolution[i]);
}
} else if (m_autoFitterLM) {
QVector<double> bestSolution = m_autoFitterLM->getBestSolution();
if (i < bestSolution.size()) {
valueText = formatScientific(bestSolution[i]);
}
}
QTableWidgetItem* valueItem = new QTableWidgetItem(valueText);
valueItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
valueItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
tableWidget->setItem(i, 1, valueItem);
// 使用当前已接受参数,普通数值优先显示十进制,极小数仍保留有效位。
const QVector<double> values = m_autoFitterLM ? m_autoFitterLM->getBestSolution()
: (m_autoFitterPSO ? m_autoFitterPSO->getBestSolution() : QVector<double>());
for(int i = 0; i < parameterValues.size(); ++i) {
parameterValues[i]->setText(i < values.size() ? QString::number(values[i], 'g', 6) : "--");
parameterValues[i]->setToolTip(i < values.size() ? QString::number(values[i], 'g', 15) : QString());
}
}
void nmWxAutomaticfittingStart::updateCurrentError(double fitness)
{
// 仅展示当前已接受参数对应的总误差,未获得有效结果时显示占位符。
const QString value = nmAutoFitChartIsFinite(fitness) && fitness >= 0.0 && fitness < 1.0e9
? QString::number(fitness, 'f', 6) : "--";
resultSummary->setText(tr("Current error: %1")
.arg(QString("<span style=\"color:#233247; font-weight:500;\">%1</span>").arg(value)));
resultSummary->show();
}
void nmWxAutomaticfittingStart::addLogMessage(const QString& message)
{
// 获取滚动条
@ -957,4 +1117,9 @@ void nmWxAutomaticfittingStart::onBestCurveUpdated(QVector<QVector<double> > tar
}
curveChart->setBestLogLogData(bestData, iteration, fitness);
// 形状改善时总误差可能上升,参数值仍须同步到当前已接受曲线。
if(m_autoFitterLM) {
updateParameterValues();
updateCurrentError(fitness);
}
}

Loading…
Cancel
Save