File indexing completed on 2025-07-13 03:32:51

0001 /*
0002     File                 : MatrixFunctionDialog.cpp
0003     Project              : LabPlot
0004     Description          : Dialog for generating matrix values from a mathematical function
0005     ------------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2015-2019 Alexander Semke <alexander.semke@web.de>
0007     SPDX-FileCopyrightText: 2016 Stefan Gerlach <stefan.gerlach@uni.kn>
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include "MatrixFunctionDialog.h"
0012 #include "backend/core/Settings.h"
0013 #include "backend/gsl/ExpressionParser.h"
0014 #include "backend/lib/macros.h"
0015 #include "backend/matrix/Matrix.h"
0016 #include "kdefrontend/widgets/ConstantsWidget.h"
0017 #include "kdefrontend/widgets/FunctionsWidget.h"
0018 
0019 #include <KLocalizedString>
0020 #include <KWindowConfig>
0021 
0022 #include <QDialogButtonBox>
0023 #include <QMenu>
0024 #include <QPushButton>
0025 #include <QThreadPool>
0026 #include <QWidgetAction>
0027 #include <QWindow>
0028 #ifndef NDEBUG
0029 #include <QElapsedTimer>
0030 #endif
0031 
0032 #include "backend/gsl/parser.h"
0033 #include <cmath>
0034 
0035 /*!
0036     \class MatrixFunctionDialog
0037     \brief Dialog for generating matrix values from a mathematical function.
0038 
0039     \ingroup kdefrontend
0040  */
0041 MatrixFunctionDialog::MatrixFunctionDialog(Matrix* m, QWidget* parent)
0042     : QDialog(parent)
0043     , m_matrix(m) {
0044     Q_ASSERT(m_matrix);
0045     setWindowTitle(i18nc("@title:window", "Function Values"));
0046 
0047     ui.setupUi(this);
0048     setAttribute(Qt::WA_DeleteOnClose);
0049     ui.tbConstants->setIcon(QIcon::fromTheme(QStringLiteral("labplot-format-text-symbol")));
0050     ui.tbFunctions->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-font")));
0051 
0052     QStringList vars;
0053     vars << QStringLiteral("x") << QStringLiteral("y");
0054     ui.teEquation->setVariables(vars);
0055     ui.teEquation->setFocus();
0056     ui.teEquation->setMaximumHeight(QLineEdit().sizeHint().height() * 2);
0057 
0058     const auto numberLocale = QLocale();
0059     QString info = QStringLiteral("[") + numberLocale.toString(m_matrix->xStart()) + QStringLiteral(", ") + numberLocale.toString(m_matrix->xEnd())
0060         + QStringLiteral("], ") + i18np("%1 value", "%1 values", m_matrix->columnCount());
0061     ui.lXInfo->setText(info);
0062     info = QStringLiteral("[") + numberLocale.toString(m_matrix->yStart()) + QStringLiteral(", ") + numberLocale.toString(m_matrix->yEnd())
0063         + QStringLiteral("], ") + i18np("%1 value", "%1 values", m_matrix->rowCount());
0064     ui.lYInfo->setText(info);
0065 
0066     ui.teEquation->setPlainText(m_matrix->formula());
0067     auto* btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
0068 
0069     ui.gridLayout_2->addWidget(btnBox, 3, 0, 1, 3);
0070     m_okButton = btnBox->button(QDialogButtonBox::Ok);
0071 
0072     connect(btnBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &MatrixFunctionDialog::close);
0073     connect(btnBox, &QDialogButtonBox::accepted, this, &MatrixFunctionDialog::accept);
0074     connect(btnBox, &QDialogButtonBox::rejected, this, &MatrixFunctionDialog::reject);
0075 
0076     m_okButton->setText(i18n("&Generate"));
0077     m_okButton->setToolTip(i18n("Generate function values"));
0078 
0079     connect(ui.teEquation, &ExpressionTextEdit::expressionChanged, this, &MatrixFunctionDialog::checkValues);
0080     connect(ui.tbConstants, &QToolButton::clicked, this, &MatrixFunctionDialog::showConstants);
0081     connect(ui.tbFunctions, &QToolButton::clicked, this, &MatrixFunctionDialog::showFunctions);
0082     connect(m_okButton, &QPushButton::clicked, this, &MatrixFunctionDialog::generate);
0083 
0084     // restore saved settings if available
0085     create(); // ensure there's a window created
0086     KConfigGroup conf = Settings::group(QStringLiteral("MatrixFunctionDialog"));
0087     if (conf.exists()) {
0088         KWindowConfig::restoreWindowSize(windowHandle(), conf);
0089         resize(windowHandle()->size()); // workaround for QTBUG-40584
0090     } else
0091         resize(QSize(300, 0).expandedTo(minimumSize()));
0092 }
0093 
0094 MatrixFunctionDialog::~MatrixFunctionDialog() {
0095     KConfigGroup conf = Settings::group(QStringLiteral("MatrixFunctionDialog"));
0096     KWindowConfig::saveWindowSize(windowHandle(), conf);
0097 }
0098 
0099 void MatrixFunctionDialog::checkValues() {
0100     if (!ui.teEquation->isValid()) {
0101         m_okButton->setEnabled(false);
0102         return;
0103     }
0104 
0105     m_okButton->setEnabled(true);
0106 }
0107 
0108 void MatrixFunctionDialog::showConstants() {
0109     QMenu menu;
0110     ConstantsWidget constants(&menu);
0111     connect(&constants, &ConstantsWidget::constantSelected, this, &MatrixFunctionDialog::insertConstant);
0112     connect(&constants, &ConstantsWidget::constantSelected, &menu, &QMenu::close);
0113     connect(&constants, &ConstantsWidget::canceled, &menu, &QMenu::close);
0114 
0115     auto* widgetAction = new QWidgetAction(this);
0116     widgetAction->setDefaultWidget(&constants);
0117     menu.addAction(widgetAction);
0118 
0119     QPoint pos(-menu.sizeHint().width() + ui.tbConstants->width(), -menu.sizeHint().height());
0120     menu.exec(ui.tbConstants->mapToGlobal(pos));
0121 }
0122 
0123 void MatrixFunctionDialog::showFunctions() {
0124     QMenu menu;
0125     FunctionsWidget functions(&menu);
0126     connect(&functions, &FunctionsWidget::functionSelected, this, &MatrixFunctionDialog::insertFunction);
0127     connect(&functions, &FunctionsWidget::functionSelected, &menu, &QMenu::close);
0128     connect(&functions, &FunctionsWidget::canceled, &menu, &QMenu::close);
0129 
0130     auto* widgetAction = new QWidgetAction(this);
0131     widgetAction->setDefaultWidget(&functions);
0132     menu.addAction(widgetAction);
0133 
0134     QPoint pos(-menu.sizeHint().width() + ui.tbFunctions->width(), -menu.sizeHint().height());
0135     menu.exec(ui.tbFunctions->mapToGlobal(pos));
0136 }
0137 
0138 void MatrixFunctionDialog::insertFunction(const QString& functionName) const {
0139     ui.teEquation->insertPlainText(functionName + ExpressionParser::functionArgumentString(functionName, XYEquationCurve::EquationType::Cartesian));
0140 }
0141 
0142 void MatrixFunctionDialog::insertConstant(const QString& constantsName) const {
0143     ui.teEquation->insertPlainText(constantsName);
0144 }
0145 
0146 /* task class for parallel fill (not used) */
0147 class GenerateValueTask : public QRunnable {
0148 public:
0149     GenerateValueTask(int startCol, int endCol, QVector<QVector<double>>& matrixData, double xStart, double yStart, double xStep, double yStep, char* func)
0150         : m_startCol(startCol)
0151         , m_endCol(endCol)
0152         , m_matrixData(matrixData)
0153         , m_xStart(xStart)
0154         , m_yStart(yStart)
0155         , m_xStep(xStep)
0156         , m_yStep(yStep)
0157         , m_func(func) {
0158     }
0159 
0160     void run() override {
0161         const int rows = m_matrixData[m_startCol].size();
0162         double x = m_xStart;
0163         double y = m_yStart;
0164         DEBUG("FILL col" << m_startCol << "-" << m_endCol << " x/y =" << x << '/' << y << " steps =" << m_xStep << '/' << m_yStep << " rows =" << rows)
0165 
0166         parser_var vars[] = {{"x", x}, {"y", y}};
0167         for (int col = m_startCol; col < m_endCol; ++col) {
0168             vars[0].value = x;
0169             for (int row = 0; row < rows; ++row) {
0170                 vars[1].value = y;
0171                 double z = parse_with_vars(m_func, vars, 2, qPrintable(QLocale().name()));
0172                 // DEBUG(" z =" << z);
0173                 m_matrixData[col][row] = z;
0174                 y += m_yStep;
0175             }
0176 
0177             y = m_yStart;
0178             x += m_xStep;
0179         }
0180     }
0181 
0182 private:
0183     int m_startCol;
0184     int m_endCol;
0185     QVector<QVector<double>>& m_matrixData;
0186     double m_xStart;
0187     double m_yStart;
0188     double m_xStep;
0189     double m_yStep;
0190     char* m_func;
0191 };
0192 
0193 void MatrixFunctionDialog::generate() {
0194     WAIT_CURSOR;
0195 
0196     m_matrix->beginMacro(i18n("%1: fill matrix with function values", m_matrix->name()));
0197 
0198     // TODO: data types
0199     auto* new_data = static_cast<QVector<QVector<double>>*>(m_matrix->data());
0200 
0201     // check if rows or cols == 1
0202     double diff = m_matrix->xEnd() - m_matrix->xStart();
0203     double xStep = 0.0;
0204     if (m_matrix->columnCount() > 1)
0205         xStep = diff / double(m_matrix->columnCount() - 1);
0206 
0207     diff = m_matrix->yEnd() - m_matrix->yStart();
0208     double yStep = 0.0;
0209     if (m_matrix->rowCount() > 1)
0210         yStep = diff / double(m_matrix->rowCount() - 1);
0211 
0212 #ifndef NDEBUG
0213     QElapsedTimer timer;
0214     timer.start();
0215 #endif
0216 
0217     // TODO: too slow because every parser thread needs an own symbol_table
0218     //  idea: use pool->maxThreadCount() symbol tables and reuse them?
0219     /*  double yStart = m_matrix->yStart();
0220         const int cols = m_matrix->columnCount();
0221         QThreadPool* pool = QThreadPool::globalInstance();
0222         int range = ceil(double(cols)/pool->maxThreadCount());
0223         DEBUG("Starting" << pool->maxThreadCount() << "threads. cols =" << cols << ": range =" << range);
0224 
0225         for (int i = 0; i < pool->maxThreadCount(); ++i) {
0226             const int start = i*range;
0227             int end = (i+1)*range;
0228             if (end > cols) end = cols;
0229             qDebug() << "start/end: " << start << end;
0230             const double xStart = m_matrix->xStart() + xStep*start;
0231             GenerateValueTask* task = new GenerateValueTask(start, end, new_data, xStart, yStart, xStep, yStep,
0232                                         qPrintable(ui.teEquation->toPlainText()));
0233             task->setAutoDelete(false);
0234             pool->start(task);
0235         }
0236         pool->waitForDone();
0237     */
0238     double x = m_matrix->xStart();
0239     double y = m_matrix->yStart();
0240     parser_var vars[] = {{"x", x}, {"y", y}};
0241     for (int col = 0; col < m_matrix->columnCount(); ++col) {
0242         vars[0].value = x;
0243         for (int row = 0; row < m_matrix->rowCount(); ++row) {
0244             vars[1].value = y;
0245             (*new_data)[col][row] = parse_with_vars(qPrintable(ui.teEquation->toPlainText()), vars, 2, qPrintable(QLocale().name()));
0246             y += yStep;
0247         }
0248         y = m_matrix->yStart();
0249         x += xStep;
0250     }
0251 
0252     // Timing
0253 #ifndef NDEBUG
0254     DEBUG("elapsed time =" << timer.elapsed() << "ms")
0255 #endif
0256 
0257     m_matrix->setFormula(ui.teEquation->toPlainText());
0258     m_matrix->setData(new_data);
0259 
0260     m_matrix->endMacro();
0261     RESET_CURSOR;
0262 }