diff --git a/Bin/Config/Lang/cn/nmNum_cn.qm b/Bin/Config/Lang/cn/nmNum_cn.qm
index c234a7d0..ee2939dc 100644
Binary files a/Bin/Config/Lang/cn/nmNum_cn.qm and b/Bin/Config/Lang/cn/nmNum_cn.qm differ
diff --git a/Bin/Config/Lang/cn/nmNum_cn.ts b/Bin/Config/Lang/cn/nmNum_cn.ts
index 4aee9b73..45d2ab6c 100644
--- a/Bin/Config/Lang/cn/nmNum_cn.ts
+++ b/Bin/Config/Lang/cn/nmNum_cn.ts
@@ -677,22 +677,16 @@ Reason: %1
【初始参数】
- [Stage 1: Height alignment]
-Adjust permeability only; accept by height error.
- 【第一阶段:高度预调】
-只调整渗透率,按高度误差判断是否接受。
+ [Stage 1: Height alignment]
+ 【第一阶段:高度预调】
- [Stage 2: Joint LM - Shape]
-Adjust selected parameters; accept by shape error.
- 【第二阶段:联合 LM · 形状】
-调整勾选参数,按形状误差判断是否接受。
+ [Stage 2: Joint LM - Shape]
+ 【第二阶段:联合 LM · 形状】
- [Stage 2: Joint LM - Total error]
-Adjust selected parameters; accept by total error.
- 【第二阶段:联合 LM · 总误差】
-调整勾选参数,按总误差判断是否接受。
+ [Stage 2: Joint LM - Total error]
+ 【第二阶段:联合 LM · 总误差】
%1 | %2 | %3 | Improvement: %4%
@@ -759,12 +753,8 @@ No valid error was returned; keep the last accepted result and reduce the step.<
mD·m
- Trial %1 | %2 | Height error: %3 -> %4
- 试算 %1|%2|高度误差:%3 → %4
-
-
- Trial %1 | Solver failed; keep the last accepted result and reduce the step.
- 试算 %1|求解失败,保留上次已接受结果并缩小步长。
+ Height error: %1 -> %2
+ 高度误差:%1 → %2
Height error did not decrease; keep the last accepted result and reduce the step.
@@ -816,7 +806,7 @@ No valid error was returned; keep the last accepted result and reduce the step.<
Iteration or evaluation budget reached
- 达到本阶段迭代次数或试算次数上限
+ 达到总迭代上限或本段试算次数上限
Final total error did not improve; restoring the initial result.
@@ -1091,8 +1081,8 @@ No valid error was returned; keep the last accepted result and reduce the step.<
=== LM 自动拟合 - 局部最优 ===
- Total-stage budget reached. Best error: %1, Cumulative iterations: %2
- 整体阶段预算已用尽。最佳误差:%1,累计迭代次数:%2
+ Iteration or evaluation budget reached. Best error: %1, Cumulative iterations: %2
+ 达到总迭代上限或本段试算次数上限。最佳误差:%1,累计迭代次数:%2
=== LM AUTOMATIC FITTING - MAX ITERATIONS ===
@@ -4467,6 +4457,30 @@ Supported types: Vertical, Vertical Fractured, and Horizontal Multi-Fractured We
Gas pseudo-pressure data is unavailable or invalid.
气体拟压力数据不可用或无效。
+
+ Iteration limit:
+ 迭代上限:
+
+
+ Maximum iterations:
+ 最大迭代次数:
+
+
+ Height, shape and total-error adjustments share this iteration limit; one iteration may call the solver multiple times.
+ 高度、形状和总误差调整共用此迭代上限;一次迭代可能调用多次求解器。
+
+
+ Start fitting
+ 开始拟合
+
+
+ Target error:
+ 目标误差:
+
+
+ Fitting parameters
+ 拟合参数
+
nmWxAutomaticfitting
@@ -4562,8 +4576,24 @@ Supported types: Vertical, Vertical Fractured, and Horizontal Multi-Fractured We
nmWxAutomaticfittingStart
- Total time: %1 min %2 s
- 总耗时:%1 分 %2 秒
+ Iterations:
+ 迭代数:
+
+
+ Fitting process
+ 拟合过程
+
+
+ Total time: %1
+ 总耗时:%1
+
+
+ %1 s
+ %1 秒
+
+
+ %1 min %2 s
+ %1 分 %2 秒
Auto Fitting Progress
@@ -4757,6 +4787,86 @@ Supported types: Vertical, Vertical Fractured, and Horizontal Multi-Fractured We
Pre-adjustment
预调整中
+
+ %1 | Iteration %2 / limit %3
+ %1 · 迭代 %2 / 上限 %3
+
+
+ Adjustment log
+ 调整记录
+
+
+ Calculating initial error...
+ 正在计算初始误差……
+
+
+ Current parameters
+ 当前参数
+
+
+ Current value
+ 当前值
+
+
+ Fitting ended
+ 拟合结束
+
+
+ Fitting failed
+ 拟合失败
+
+
+ Generating final result...
+ 正在生成最终结果……
+
+
+ Height alignment
+ 高度预调
+
+
+ Shape adjustment
+ 形状调整
+
+
+ Total-error adjustment
+ 总误差调整
+
+
+ Collapse
+ 收起
+
+
+ Expand
+ 展开
+
+
+ Fitted
+ 拟合
+
+
+ Fitting in progress
+ 拟合中
+
+
+ Observed
+ 实测
+
+
+ Solver calls: %1 | Total time: %2
+ 求解器调用 %1 次 · 总耗时 %2
+
+
+ Target error:
+ 目标误差:
+
+
+ Total error: %1 -> %2
+ 总误差:%1 → %2
+
+
+ Current error: %1
+ 当前误差:%1
+
nmWxChangeAnal
diff --git a/Include/nmNum/nmCalculation/nmCalculationAutoFitLM.h b/Include/nmNum/nmCalculation/nmCalculationAutoFitLM.h
index d7ff03aa..06350bc9 100644
--- a/Include/nmNum/nmCalculation/nmCalculationAutoFitLM.h
+++ b/Include/nmNum/nmCalculation/nmCalculationAutoFitLM.h
@@ -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 > targetData,
diff --git a/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h b/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h
index 9482bdd4..4620b8f6 100644
--- a/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h
+++ b/Include/nmNum/nmSubWxs/nmWxAutomaticFitting.h
@@ -4,6 +4,7 @@
#include "iDlgBase.h"
#include
#include
+#include
#include
#include
#include
@@ -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; // 渗透率
diff --git a/Include/nmNum/nmSubWxs/nmWxAutomaticFittingStart.h b/Include/nmNum/nmSubWxs/nmWxAutomaticFittingStart.h
index 0697b6f1..e09b291f 100644
--- a/Include/nmNum/nmSubWxs/nmWxAutomaticFittingStart.h
+++ b/Include/nmNum/nmSubWxs/nmWxAutomaticFittingStart.h
@@ -7,13 +7,11 @@
#include
#include
#include
-#include
#include
#include
#include
#include
#include
-#include
#include
#include
#include
@@ -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 >& 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 parameterValues;
// 日志区域
QGroupBox* chartGroup;
nmAutomaticFittingCurveChart* curveChart;
QGroupBox* logGroup;
QTextEdit* logTextEdit;
+ QPushButton* logToggle;
+ QList 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;
diff --git a/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp b/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp
index 58fadf72..afcb16b1 100644
--- a/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp
+++ b/Src/nmNum/nmCalculation/nmCalculationAutoFitLM.cpp
@@ -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", ¤t.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 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) {
diff --git a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp
index 899254a4..3ffa3c27 100644
--- a/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp
+++ b/Src/nmNum/nmSubWxs/nmWxAutomaticFitting.cpp
@@ -15,6 +15,7 @@
#pragma comment(lib, "mGuiAnal.lib")
#include
+#include
#include
#include
#include
@@ -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(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>& 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()));
diff --git a/Src/nmNum/nmSubWxs/nmWxAutomaticFittingStart.cpp b/Src/nmNum/nmSubWxs/nmWxAutomaticFittingStart.cpp
index de0d367c..f220d974 100644
--- a/Src/nmNum/nmSubWxs/nmWxAutomaticFittingStart.cpp
+++ b/Src/nmNum/nmSubWxs/nmWxAutomaticFittingStart.cpp
@@ -1,9 +1,11 @@
#include "nmWxAutomaticfittingStart.h"
#include
+#include
#include
#include
#include
#include
+#include
#include
#include
#include
@@ -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(qFloor(m_logRange.top()));
int yEnd = static_cast(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(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() << 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() << 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 bestSolution = m_autoFitterPSO->getBestSolution();
- if (i < bestSolution.size()) {
- valueText = formatScientific(bestSolution[i]);
- }
- } else if (m_autoFitterLM) {
- QVector 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 values = m_autoFitterLM ? m_autoFitterLM->getBestSolution()
+ : (m_autoFitterPSO ? m_autoFitterPSO->getBestSolution() : QVector());
+ 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("%1").arg(value)));
+ resultSummary->show();
+}
+
void nmWxAutomaticfittingStart::addLogMessage(const QString& message)
{
// 获取滚动条
@@ -957,4 +1117,9 @@ void nmWxAutomaticfittingStart::onBestCurveUpdated(QVector > tar
}
curveChart->setBestLogLogData(bestData, iteration, fitness);
+ // 形状改善时总误差可能上升,参数值仍须同步到当前已接受曲线。
+ if(m_autoFitterLM) {
+ updateParameterValues();
+ updateCurrentError(fitness);
+ }
}