You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nmWTAI-Platform/Src/nmNum/nmSubWxs/nmWxAutomaticFittingStart.cpp

919 lines
28 KiB
C++

#include "nmWxAutomaticfittingStart.h"
#include <QtGui/QApplication>
#include <QtCore/QEvent>
#include <QtGui/QMouseEvent>
#include <QtGui/QFont>
#include <QScrollBar>
#include <QList>
#include <QPainter>
#include <QPaintEvent>
#include <QPainterPath>
#include <QtCore/qmath.h>
#include <cmath>
#ifdef Q_OS_WIN
#include <float.h>
#endif
static const double kTargetCrossHalfSize = 1.0;
static const double kTargetCircleRadius = 1.0;
static bool nmAutoFitChartIsFinite(double value)
{
#ifdef Q_OS_WIN
return _finite(value) != 0;
#else
return std::isfinite(value);
#endif
}
nmAutomaticFittingCurveChart::nmAutomaticFittingCurveChart(QWidget* parent)
: QWidget(parent)
, m_logRange(0.0, 0.0, 1.0, 1.0)
, m_bestIteration(0)
, m_bestFitness(0.0)
{
setMinimumHeight(220);
setMinimumWidth(420);
}
void nmAutomaticFittingCurveChart::setTargetLogLogData(const QVector<QVector<double> >& data)
{
m_targetPressure = extractCurve(data, 1);
m_targetDerivative = extractCurve(data, 2);
rebuildRange();
update();
}
void nmAutomaticFittingCurveChart::setBestLogLogData(const QVector<QVector<double> >& data, int iteration, double fitness)
{
m_bestPressure = extractCurve(data, 1);
m_bestDerivative = extractCurve(data, 2);
m_bestIteration = iteration;
m_bestFitness = fitness;
rebuildRange();
update();
}
QVector<QPointF> nmAutomaticFittingCurveChart::extractCurve(const QVector<QVector<double> >& data, int yIndex) const
{
QVector<QPointF> points;
if(data.size() <= yIndex || data[0].isEmpty() || data[yIndex].isEmpty()) {
return points;
}
int count = qMin(data[0].size(), data[yIndex].size());
points.reserve(count);
for(int i = 0; i < count; ++i) {
double x = data[0][i];
double y = data[yIndex][i];
if(nmAutoFitChartIsFinite(x) && nmAutoFitChartIsFinite(y) && x > 0.0 && y > 0.0) {
points.append(QPointF(x, y));
}
}
return points;
}
void nmAutomaticFittingCurveChart::rebuildRange()
{
bool hasPoint = false;
double minX = 0.0;
double maxX = 0.0;
double minY = 0.0;
double maxY = 0.0;
QVector<QVector<QPointF> > allCurves;
allCurves << m_targetPressure << m_targetDerivative << m_bestPressure << m_bestDerivative;
for(int c = 0; c < allCurves.size(); ++c) {
const QVector<QPointF>& points = allCurves[c];
for(int i = 0; i < points.size(); ++i) {
double lx = qLn(points[i].x()) / qLn(10.0);
double ly = qLn(points[i].y()) / qLn(10.0);
if(!hasPoint) {
minX = maxX = lx;
minY = maxY = ly;
hasPoint = true;
} else {
minX = qMin(minX, lx);
maxX = qMax(maxX, lx);
minY = qMin(minY, ly);
maxY = qMax(maxY, ly);
}
}
}
if(!hasPoint) {
m_logRange = QRectF(0.0, 0.0, 1.0, 1.0);
return;
}
if(qAbs(maxX - minX) < 1e-8) {
minX -= 0.5;
maxX += 0.5;
}
if(qAbs(maxY - minY) < 1e-8) {
minY -= 0.5;
maxY += 0.5;
}
double padX = (maxX - minX) * 0.06;
double padY = (maxY - minY) * 0.10;
m_logRange = QRectF(minX - padX, minY - padY, (maxX - minX) + 2.0 * padX, (maxY - minY) + 2.0 * padY);
}
QPointF nmAutomaticFittingCurveChart::dataToScreen(const QPointF& point) const
{
double lx = qLn(point.x()) / qLn(10.0);
double ly = qLn(point.y()) / qLn(10.0);
double xRatio = (lx - m_logRange.left()) / qMax(1e-12, m_logRange.width());
double yRatio = (ly - m_logRange.top()) / qMax(1e-12, m_logRange.height());
return QPointF(m_chartRect.left() + xRatio * m_chartRect.width(),
m_chartRect.bottom() - yRatio * m_chartRect.height());
}
QString nmAutomaticFittingCurveChart::formatPowerTickLabel(int exponent) const
{
if(exponent == 0) {
return "1";
}
if(exponent == 1) {
return "10";
}
QString expStr = QString::number(exponent);
expStr.replace("-", QString(QChar(0x207B))); // ⁻
expStr.replace("0", QString(QChar(0x2070))); // ⁰
expStr.replace("1", QString(QChar(0x00B9))); // ¹
expStr.replace("2", QString(QChar(0x00B2))); // ²
expStr.replace("3", QString(QChar(0x00B3))); // ³
expStr.replace("4", QString(QChar(0x2074))); // ⁴
expStr.replace("5", QString(QChar(0x2075))); // ⁵
expStr.replace("6", QString(QChar(0x2076))); // ⁶
expStr.replace("7", QString(QChar(0x2077))); // ⁷
expStr.replace("8", QString(QChar(0x2078))); // ⁸
expStr.replace("9", QString(QChar(0x2079))); // ⁹
return QString("10") + expStr;
}
void nmAutomaticFittingCurveChart::paintEvent(QPaintEvent* event)
{
Q_UNUSED(event);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.fillRect(rect(), QColor(240, 240, 240));
const int marginLeft = 62;
const int marginTop = 18;
const int marginRight = 16;
const int marginBottom = 42;
m_chartRect = QRectF(marginLeft, marginTop,
qMax(10, width() - marginLeft - marginRight),
qMax(10, height() - marginTop - marginBottom));
painter.fillRect(m_chartRect, QColor(252, 252, 252));
drawGrid(painter);
drawAxes(painter);
drawCurveLine(painter, m_bestPressure, QColor(250, 74, 74));
drawCurveLine(painter, m_bestDerivative, QColor(71, 252, 71));
drawCrossMarkers(painter, m_targetPressure, QColor(71, 252, 71));
drawCircleMarkers(painter, m_targetDerivative, QColor(250, 74, 74));
}
void nmAutomaticFittingCurveChart::drawGrid(QPainter& painter)
{
painter.save();
int xStart = static_cast<int>(qFloor(m_logRange.left()));
int xEnd = static_cast<int>(qCeil(m_logRange.right()));
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));
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);
if(value <= m_logRange.left() || value >= m_logRange.right()) {
continue;
}
double ratio = (value - m_logRange.left()) / qMax(1e-12, m_logRange.width());
double sx = m_chartRect.left() + ratio * m_chartRect.width();
painter.drawLine(QPointF(sx, m_chartRect.top()), QPointF(sx, m_chartRect.bottom()));
}
}
for(int decade = yStart; decade <= yEnd; ++decade) {
for(int multiplier = 2; multiplier <= 9; ++multiplier) {
double value = decade + qLn(static_cast<double>(multiplier)) / qLn(10.0);
if(value <= m_logRange.top() || value >= m_logRange.bottom()) {
continue;
}
double ratio = (value - m_logRange.top()) / qMax(1e-12, m_logRange.height());
double sy = m_chartRect.bottom() - ratio * m_chartRect.height();
painter.drawLine(QPointF(m_chartRect.left(), sy), QPointF(m_chartRect.right(), sy));
}
}
painter.setPen(QPen(QColor(214, 214, 214), 1, Qt::SolidLine));
for(int x = xStart; x <= xEnd; ++x) {
if(x < m_logRange.left() || x > m_logRange.right()) {
continue;
}
double ratio = (x - m_logRange.left()) / qMax(1e-12, m_logRange.width());
double sx = m_chartRect.left() + ratio * m_chartRect.width();
painter.drawLine(QPointF(sx, m_chartRect.top()), QPointF(sx, m_chartRect.bottom()));
}
for(int y = yStart; y <= yEnd; ++y) {
if(y < m_logRange.top() || y > m_logRange.bottom()) {
continue;
}
double ratio = (y - m_logRange.top()) / qMax(1e-12, m_logRange.height());
double sy = m_chartRect.bottom() - ratio * m_chartRect.height();
painter.drawLine(QPointF(m_chartRect.left(), sy), QPointF(m_chartRect.right(), sy));
}
painter.restore();
}
void nmAutomaticFittingCurveChart::drawAxes(QPainter& painter)
{
painter.save();
painter.setPen(QPen(Qt::black, 1));
painter.drawRect(m_chartRect);
QFontMetrics fm(painter.font());
int xStart = static_cast<int>(qFloor(m_logRange.left()));
int xEnd = static_cast<int>(qCeil(m_logRange.right()));
int yStart = static_cast<int>(qFloor(m_logRange.top()));
int yEnd = static_cast<int>(qCeil(m_logRange.bottom()));
for(int x = xStart; x <= xEnd; ++x) {
if(x < m_logRange.left() || x > m_logRange.right()) {
continue;
}
double ratio = (x - m_logRange.left()) / qMax(1e-12, m_logRange.width());
double sx = m_chartRect.left() + ratio * m_chartRect.width();
QString label = formatPowerTickLabel(x);
painter.drawLine(QPointF(sx, m_chartRect.bottom()), QPointF(sx, m_chartRect.bottom() + 5));
painter.drawText(QPointF(sx - fm.width(label) / 2.0, m_chartRect.bottom() + 22), label);
}
for(int y = yStart; y <= yEnd; ++y) {
if(y < m_logRange.top() || y > m_logRange.bottom()) {
continue;
}
double ratio = (y - m_logRange.top()) / qMax(1e-12, m_logRange.height());
double sy = m_chartRect.bottom() - ratio * m_chartRect.height();
QString label = formatPowerTickLabel(y);
painter.drawLine(QPointF(m_chartRect.left() - 5, sy), QPointF(m_chartRect.left(), sy));
painter.drawText(QPointF(m_chartRect.left() - fm.width(label) - 9, sy + fm.height() / 3.0), label);
}
QString xTitle = tr("Time (hr)");
painter.drawText(QPointF(m_chartRect.center().x() - fm.width(xTitle) / 2.0, height() - 8), xTitle);
painter.save();
painter.translate(13, m_chartRect.center().y() + 46);
painter.rotate(-90);
painter.drawText(QPointF(0, 0), tr("Pressure (MPa)"));
painter.restore();
painter.restore();
}
void nmAutomaticFittingCurveChart::drawCurveLine(QPainter& painter, const QVector<QPointF>& points, const QColor& color)
{
if(points.size() < 2) {
return;
}
painter.save();
painter.setClipRect(m_chartRect.adjusted(-1, -1, 1, 1));
painter.setPen(QPen(color, 2));
QPainterPath path;
QPointF first = dataToScreen(points[0]);
path.moveTo(first);
for(int i = 1; i < points.size(); ++i) {
path.lineTo(dataToScreen(points[i]));
}
painter.drawPath(path);
painter.restore();
}
void nmAutomaticFittingCurveChart::drawCrossMarkers(QPainter& painter, const QVector<QPointF>& points, const QColor& color)
{
if(points.isEmpty()) {
return;
}
painter.save();
painter.setClipRect(m_chartRect.adjusted(-1, -1, 1, 1));
painter.setPen(QPen(color, 1));
int step = qMax(1, points.size() / 220);
const double size = kTargetCrossHalfSize;
for(int i = 0; i < points.size(); i += step) {
QPointF p = dataToScreen(points[i]);
painter.drawLine(QPointF(p.x() - size, p.y() - size), QPointF(p.x() + size, p.y() + size));
painter.drawLine(QPointF(p.x() - size, p.y() + size), QPointF(p.x() + size, p.y() - size));
}
painter.restore();
}
void nmAutomaticFittingCurveChart::drawCircleMarkers(QPainter& painter, const QVector<QPointF>& points, const QColor& color)
{
if(points.isEmpty()) {
return;
}
painter.save();
painter.setClipRect(m_chartRect.adjusted(-1, -1, 1, 1));
painter.setPen(QPen(color, 1));
painter.setBrush(Qt::NoBrush);
int step = qMax(1, points.size() / 220);
for(int i = 0; i < points.size(); i += step) {
painter.drawEllipse(dataToScreen(points[i]), kTargetCircleRadius, kTargetCircleRadius);
}
painter.restore();
}
nmWxAutomaticfittingStart::nmWxAutomaticfittingStart(QWidget *parent)
: QDialog(parent)
, rightSplitter(nullptr)
, chartGroup(nullptr)
, curveChart(nullptr)
, m_autoFitterPSO(nullptr)
, m_autoFitterGA(nullptr)
, m_algorithmType(FITTING_ALGORITHM_PSO)
, m_maxIterations(100)
, m_targetError(0.001)
, m_wellName("")
, m_isFinished(false)
, m_bestFitnessEver(1e10)
, m_startTime()
{
setupUI();
// 设置窗口属性
setWindowTitle(tr("Auto Fitting Progress"));
setMinimumSize(1000, 650);
setModal(false);
}
nmWxAutomaticfittingStart::~nmWxAutomaticfittingStart()
{
}
void nmWxAutomaticfittingStart::setupUI()
{
// 创建主要布局
mainLayout = new QHBoxLayout();
leftLayout = new QVBoxLayout();
rightLayout = new QVBoxLayout();
// 设置左右布局
setupParameterArea();
setupTableArea();
setupChartArea();
setupLogArea();
setupControlArea();
// 左侧布局
leftLayout->addWidget(parameterGroup);
leftLayout->addStretch();
leftLayout->addWidget(tableGroup);
// 右侧布局
rightSplitter = new QSplitter(Qt::Vertical, this);
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);
// 控制区域布局
QHBoxLayout *controlLayout = new QHBoxLayout();
controlLayout->addWidget(progressBar);
controlLayout->addWidget(stopButton);
QVBoxLayout *overallLayout = new QVBoxLayout();
overallLayout->addLayout(mainLayout);
overallLayout->addLayout(controlLayout);
setLayout(overallLayout);
}
void nmWxAutomaticfittingStart::setupParameterArea()
{
parameterGroup = new QGroupBox(tr("Parameter Display"), this);
parameterGroup->setFixedWidth(280);
QGridLayout *paramLayout = new QGridLayout(parameterGroup);
// 创建标签
algorithmTypeLabel = new QLabel(tr("Algorithm:"));
currentIterationLabel = new QLabel(tr("Current Iteration:"));
maxIterationLabel = new QLabel(tr("Max Iteration:"));
currentComfortLabel = new QLabel(tr("Current Error:"));
bestComfortLabel = new QLabel(tr("Best Error:"));
targetPrecisionLabel = new QLabel(tr("Target Precision:"));
targetWellLabel = new QLabel(tr("Target Well:"));
// 创建数值显示标签
algorithmTypeValue = new QLabel("PSO");
currentIterationValue = new QLabel("0");
maxIterationValue = new QLabel("100");
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);
parameterGroup->setLayout(paramLayout);
}
void nmWxAutomaticfittingStart::setupTableArea()
{
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);
}
void nmWxAutomaticfittingStart::setupChartArea()
{
chartGroup = new QGroupBox(tr("Fitting Curve"), this);
QVBoxLayout* chartLayout = new QVBoxLayout(chartGroup);
curveChart = new nmAutomaticFittingCurveChart(chartGroup);
curveChart->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
chartLayout->setMargin(6);
chartLayout->addWidget(curveChart);
chartGroup->setLayout(chartLayout);
chartGroup->setMinimumHeight(240);
}
void nmWxAutomaticfittingStart::setupLogArea()
{
logGroup = new QGroupBox(tr("Log Window"), this);
QVBoxLayout *logLayout = new QVBoxLayout(logGroup);
logTextEdit = new QTextEdit(this);
logTextEdit->setReadOnly(true);
logTextEdit->setFont(QFont("Consolas", 9));
// 添加初始日志
addLogMessage(tr("System initialization completed"));
addLogMessage(tr("Waiting for fitting to start"));
logLayout->addWidget(logTextEdit);
logGroup->setLayout(logLayout);
}
void nmWxAutomaticfittingStart::setupControlArea()
{
// 进度条
progressBar = new QProgressBar(this);
progressBar->setRange(0, 100);
progressBar->setValue(0);
progressBar->setTextVisible(true);
// 设置进度条为绿色
progressBar->setStyleSheet(
"QProgressBar { border: 2px solid grey; border-radius: 5px; text-align: center; }"
"QProgressBar::chunk { background-color: #4CAF50; }"
);
// 停止按钮
stopButton = new QPushButton(tr("Stop"), this);
stopButton->setFixedSize(80, 30);
stopButton->setEnabled(false);
// 连接信号槽
connect(stopButton, SIGNAL(clicked()), this, SLOT(onStopButtonClicked()));
}
void nmWxAutomaticfittingStart::setAutoFitter(nmCalculationAutoFitPSO* autoFitter)
{
m_autoFitterPSO = autoFitter;
m_autoFitterGA = nullptr; // 清空GA实例
m_algorithmType = FITTING_ALGORITHM_PSO;
// 更新算法类型显示
algorithmTypeValue->setText("PSO");
algorithmTypeValue->setStyleSheet("QLabel { color: blue; font-weight: bold; }");
if (m_autoFitterPSO) {
connect(m_autoFitterPSO, SIGNAL(progressUpdated(int, double)),
this, SLOT(onFittingProgress(int, double)));//更新进度条、当前迭代数、当前误差
connect(m_autoFitterPSO, SIGNAL(fittingFinished(bool, QString)),
this, SLOT(onFittingFinished(bool, QString)));//显示结束状态
connect(m_autoFitterPSO, SIGNAL(logMessageGenerated(QString)),
this, SLOT(onLogMessageReceived(QString)));//把 PSO 内部日志显示到窗口
connect(m_autoFitterPSO, SIGNAL(bestCurveUpdated(QVector<QVector<double> >,QVector<QVector<double> >,int,double)),
this, SLOT(onBestCurveUpdated(QVector<QVector<double> >,QVector<QVector<double> >,int,double)));//把目标曲线和当前最优模拟曲线画出来
// 启用停止按钮
stopButton->setEnabled(true);
addLogMessage(tr("PSO auto fitting started"));
}
}
void nmWxAutomaticfittingStart::setAutoFitterGA(nmCalculationAutoFitGA* autoFitter)
{
m_autoFitterGA = autoFitter;
m_autoFitterPSO = nullptr; // 清空PSO实例
m_algorithmType = FITTING_ALGORITHM_GA;
// 更新算法类型显示
algorithmTypeValue->setText("GA");
algorithmTypeValue->setStyleSheet("QLabel { color: red; font-weight: bold; }");
if (m_autoFitterGA) {
connect(m_autoFitterGA, SIGNAL(progressUpdated(int, double)),
this, SLOT(onFittingProgress(int, double)));
connect(m_autoFitterGA, SIGNAL(fittingFinished(bool, QString)),
this, SLOT(onFittingFinished(bool, QString)));
connect(m_autoFitterGA, SIGNAL(logMessageGenerated(QString)),
this, SLOT(onLogMessageReceived(QString)));
// 启用停止按钮
stopButton->setEnabled(true);
addLogMessage(tr("GA auto fitting started"));
}
}
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));
targetWellValue->setText(wellName);
progressBar->setRange(0, maxIterations);
QString algorithmName = (m_algorithmType == FITTING_ALGORITHM_PSO) ? "PSO" : "GA";
addLogMessage(tr("%1 fitting parameters set: MaxIterations=%2, TargetAccuracy=%3, TargetWell=%4")
.arg(algorithmName).arg(maxIterations).arg(formatScientific(targetError)).arg(wellName));
}
void nmWxAutomaticfittingStart::markFittingStarted()
{
m_startTime = QDateTime::currentDateTime();
QString algorithmName = (m_algorithmType == FITTING_ALGORITHM_PSO) ? "PSO" : "GA";
QString timestamp = m_startTime.toString("yyyy-MM-dd hh:mm:ss");
addLogMessage(tr("=== %1 Fitting Session Started at %2 ===")
.arg(algorithmName)
.arg(timestamp));
}
void nmWxAutomaticfittingStart::setSelectedParameters(const QStringList& parameterNames)
{
m_selectedParameters = parameterNames;
// 立即更新参数表格
updateParameterTable();
QString algorithmName = (m_algorithmType == FITTING_ALGORITHM_PSO) ? "PSO" : "GA";
addLogMessage(tr("%1 selected parameters: %2").arg(algorithmName).arg(parameterNames.join(", ")));
}
void nmWxAutomaticfittingStart::setTargetLogLogData(const QVector<QVector<double> >& targetData)
{
if(curveChart) {
curveChart->setTargetLogLogData(targetData);
}
}
void nmWxAutomaticfittingStart::onFittingProgress(int iteration, double fitness)
{
// 确保iteration在合理范围内
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));
currentComfortValue->setText(formatScientific(fitness));
// 更新最佳适应度
if (fitness < m_bestFitnessEver) {
m_bestFitnessEver = fitness;
bestComfortValue->setText(formatScientific(fitness));
bestComfortValue->setStyleSheet("QLabel { color: green; font-weight: bold; }");
addLogMessage(tr("Better solution found: %1").arg(formatScientific(fitness)));
// 更新参数表格
updateParameterTable();
} else {
bestComfortValue->setStyleSheet("QLabel { color: black; }");
}
}
void nmWxAutomaticfittingStart::onFittingFinished(bool success, const QString& message)
{
m_isFinished = true;
QString algorithmName = (m_algorithmType == FITTING_ALGORITHM_PSO) ? "PSO" : "GA";
// 更新状态
if (success) {
progressBar->setValue(m_maxIterations);
// 区分正常完成和用户停止
if(message.contains("stopped by user", Qt::CaseInsensitive)) {
addLogMessage(tr("%1 fitting stopped by user: %2").arg(algorithmName).arg(message));
// 更新进度条显示停止状态
progressBar->setFormat(tr("Stopped: 100%"));
progressBar->setStyleSheet(
"QProgressBar { border: 2px solid grey; border-radius: 5px; text-align: center; }"
"QProgressBar::chunk { background-color: #FF9800; }" // 橙色表示停止
);
} else if(message.contains("target achieved", Qt::CaseInsensitive)) {
addLogMessage(tr("%1 fitting completed successfully - target achieved: %2").arg(algorithmName).arg(message));
// 更新进度条显示成功状态
progressBar->setFormat(tr("Target Achieved: 100%"));
progressBar->setStyleSheet(
"QProgressBar { border: 2px solid grey; border-radius: 5px; text-align: center; }"
"QProgressBar::chunk { background-color: #4CAF50; }" // 绿色表示成功
);
} else {
addLogMessage(tr("%1 fitting completed successfully: %2").arg(algorithmName).arg(message));
// 更新进度条显示完成状态
progressBar->setFormat(tr("Completed: 100%"));
progressBar->setStyleSheet(
"QProgressBar { border: 2px solid grey; border-radius: 5px; text-align: center; }"
"QProgressBar::chunk { background-color: #2196F3; }" // 蓝色表示完成
);
}
} else {
// 真正的失败情况
addLogMessage(tr("%1 fitting failed: %2").arg(algorithmName).arg(message));
// 更新进度条显示失败状态
int currentProgress = progressBar->value();
progressBar->setFormat(tr("Failed (%1%)").arg((double)currentProgress / m_maxIterations * 100, 0, 'f', 1));
progressBar->setStyleSheet(
"QProgressBar { border: 2px solid grey; border-radius: 5px; text-align: center; }"
"QProgressBar::chunk { background-color: #F44336; }" // 红色表示失败
);
}
// 禁用停止按钮
stopButton->setEnabled(true);
stopButton->setText(tr("Close"));
// 连接关闭功能
disconnect(stopButton, SIGNAL(clicked()), this, SLOT(onStopButtonClicked()));
connect(stopButton, SIGNAL(clicked()), this, SLOT(close()));
// 最终更新参数表格
updateParameterTable();
// 添加完成时间戳
QDateTime endTime = QDateTime::currentDateTime();
QString timestamp = endTime.toString("yyyy-MM-dd hh:mm:ss");
addLogMessage(tr("=== %1 Fitting Session Ended at %2 ===")
.arg(algorithmName).arg(timestamp));
// 如果有开始时间,则计算总耗时
if (m_startTime.isValid()) {
qint64 ms = m_startTime.msecsTo(endTime);
double seconds = ms / 1000.0;
int minutes = static_cast<int>(seconds / 60.0);
double remainSec = seconds - minutes * 60.0;
// 同时给总秒数和分秒
addLogMessage(tr("Total fitting time: %1 s (%2 min %3 s)")
.arg(seconds, 0, 'f', 2)
.arg(minutes)
.arg(remainSec, 0, 'f', 2));
}
}
void nmWxAutomaticfittingStart::onStopButtonClicked()
{
bool isRunning = false;
if (m_algorithmType == FITTING_ALGORITHM_PSO && m_autoFitterPSO) {
isRunning = m_autoFitterPSO->isRunning();
} else if (m_algorithmType == FITTING_ALGORITHM_GA && m_autoFitterGA) {
isRunning = m_autoFitterGA->isRunning();
}
if (isRunning) {
QString algorithmName = (m_algorithmType == FITTING_ALGORITHM_PSO) ? "PSO" : "GA";
int ret = QMessageBox::question(this, tr("Confirm Stop"),
tr("Are you sure you want to stop the %1 fitting process?").arg(algorithmName),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No);
if (ret == QMessageBox::Yes) {
if (m_algorithmType == FITTING_ALGORITHM_PSO && m_autoFitterPSO) {
m_autoFitterPSO->stopFitting();
} else if (m_algorithmType == FITTING_ALGORITHM_GA && m_autoFitterGA) {
m_autoFitterGA->stopFitting();
}
addLogMessage(tr("User requested to stop %1 fitting").arg(algorithmName));
}
}
}
void nmWxAutomaticfittingStart::updateParameterTable()
{
// 设置表格行数
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_algorithmType == FITTING_ALGORITHM_PSO && m_autoFitterPSO) {
QVector<double> bestSolution = m_autoFitterPSO->getBestSolution();
if (i < bestSolution.size()) {
valueText = formatScientific(bestSolution[i]);
}
} else if (m_algorithmType == FITTING_ALGORITHM_GA && m_autoFitterGA) {
QVector<double> bestSolution = m_autoFitterGA->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);
}
}
void nmWxAutomaticfittingStart::addLogMessage(const QString& message)
{
// 获取滚动条
QScrollBar* scrollBar = logTextEdit->verticalScrollBar();
// 记录当前是否在底部
bool wasAtBottom = (scrollBar->value() == scrollBar->maximum());
// 添加消息
logTextEdit->append(message);
// 只有当用户之前在底部时才自动滚动到底部
if (wasAtBottom) {
scrollBar->setValue(scrollBar->maximum());
}
}
QString nmWxAutomaticfittingStart::formatScientific(double value)
{
return QString::number(value, 'e', 3);
}
void nmWxAutomaticfittingStart::closeEvent(QCloseEvent *event)
{
bool isRunning = false;
QString algorithmName;
if (m_algorithmType == FITTING_ALGORITHM_PSO && m_autoFitterPSO) {
isRunning = m_autoFitterPSO->isRunning();
algorithmName = "PSO";
} else if (m_algorithmType == FITTING_ALGORITHM_GA && m_autoFitterGA) {
isRunning = m_autoFitterGA->isRunning();
algorithmName = "GA";
}
if (isRunning && !m_isFinished) {
int ret = QMessageBox::question(this, tr("Confirm Close"),
tr("%1 fitting is running. Do you want to stop it and close the window?").arg(algorithmName),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::No);
if (ret == QMessageBox::Yes) {
if (m_algorithmType == FITTING_ALGORITHM_PSO && m_autoFitterPSO) {
m_autoFitterPSO->stopFitting();
} else if (m_algorithmType == FITTING_ALGORITHM_GA && m_autoFitterGA) {
m_autoFitterGA->stopFitting();
}
event->accept();
} else {
event->ignore();
}
} else {
event->accept();
}
}
void nmWxAutomaticfittingStart::onLogMessageReceived(const QString& message)
{
addLogMessage(message);
}
void nmWxAutomaticfittingStart::onBestCurveUpdated(QVector<QVector<double> > targetData,
QVector<QVector<double> > bestData,
int iteration,
double fitness)
{
if(!curveChart) {
return;
}
if(!targetData.isEmpty()) {
curveChart->setTargetLogLogData(targetData);
}
curveChart->setBestLogLogData(bestData, iteration, fitness);
}