File indexing completed on 2024-05-12 15:28:10

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