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.
919 lines
28 KiB
C++
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);
|
|
}
|