File indexing completed on 2025-01-26 03:34:21

0001 /*
0002     File                 : XYSmoothCurve.cpp
0003     Project              : LabPlot
0004     Description          : A xy-curve defined by a smooth
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2016 Stefan Gerlach <stefan.gerlach@uni.kn>
0007     SPDX-FileCopyrightText: 2017 Alexander Semke <alexander.semke@web.de>
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 /*!
0012   \class XYSmoothCurve
0013   \brief A xy-curve defined by a smooth
0014 
0015   \ingroup worksheet
0016 */
0017 
0018 #include "XYSmoothCurve.h"
0019 #include "XYSmoothCurvePrivate.h"
0020 #include "backend/core/column/Column.h"
0021 #include "backend/lib/XmlStreamReader.h"
0022 #include "backend/lib/commandtemplates.h"
0023 #include "backend/lib/macros.h"
0024 
0025 #include <KLocalizedString>
0026 
0027 #include <QElapsedTimer>
0028 #include <QIcon>
0029 #include <QThreadPool>
0030 
0031 extern "C" {
0032 #include "backend/nsl/nsl_sf_kernel.h"
0033 }
0034 #include "backend/nsl/nsl_stats.h"
0035 #include <gsl/gsl_math.h> // gsl_pow_*
0036 
0037 XYSmoothCurve::XYSmoothCurve(const QString& name)
0038     : XYAnalysisCurve(name, new XYSmoothCurvePrivate(this), AspectType::XYSmoothCurve) {
0039 }
0040 
0041 XYSmoothCurve::XYSmoothCurve(const QString& name, XYSmoothCurvePrivate* dd)
0042     : XYAnalysisCurve(name, dd, AspectType::XYSmoothCurve) {
0043 }
0044 
0045 // no need to delete the d-pointer here - it inherits from QGraphicsItem
0046 // and is deleted during the cleanup in QGraphicsScene
0047 XYSmoothCurve::~XYSmoothCurve() = default;
0048 
0049 void XYSmoothCurve::recalculate() {
0050     Q_D(XYSmoothCurve);
0051     d->recalculate();
0052 }
0053 
0054 /*!
0055     Returns an icon to be used in the project explorer.
0056 */
0057 QIcon XYSmoothCurve::icon() const {
0058     return QIcon::fromTheme(QStringLiteral("labplot-xy-smoothing-curve"));
0059 }
0060 
0061 // ##############################################################################
0062 // ##########################  getter methods  ##################################
0063 // ##############################################################################
0064 const AbstractColumn* XYSmoothCurve::roughsColumn() const {
0065     Q_D(const XYSmoothCurve);
0066     return d->roughColumn;
0067 }
0068 
0069 BASIC_SHARED_D_READER_IMPL(XYSmoothCurve, XYSmoothCurve::SmoothData, smoothData, smoothData)
0070 
0071 const XYAnalysisCurve::Result& XYSmoothCurve::result() const {
0072     Q_D(const XYSmoothCurve);
0073     return d->smoothResult;
0074 }
0075 
0076 // ##############################################################################
0077 // #################  setter methods and undo commands ##########################
0078 // ##############################################################################
0079 STD_SETTER_CMD_IMPL_F_S(XYSmoothCurve, SetSmoothData, XYSmoothCurve::SmoothData, smoothData, recalculate)
0080 void XYSmoothCurve::setSmoothData(const XYSmoothCurve::SmoothData& smoothData) {
0081     Q_D(XYSmoothCurve);
0082     exec(new XYSmoothCurveSetSmoothDataCmd(d, smoothData, ki18n("%1: set options and perform the smooth")));
0083 }
0084 
0085 // ##############################################################################
0086 // ######################### Private implementation #############################
0087 // ##############################################################################
0088 XYSmoothCurvePrivate::XYSmoothCurvePrivate(XYSmoothCurve* owner)
0089     : XYAnalysisCurvePrivate(owner)
0090     , q(owner) {
0091 }
0092 
0093 // no need to delete xColumn and yColumn, they are deleted
0094 // when the parent aspect is removed
0095 XYSmoothCurvePrivate::~XYSmoothCurvePrivate() = default;
0096 
0097 void XYSmoothCurvePrivate::resetResults() {
0098     smoothResult = XYAnalysisCurve::Result();
0099 }
0100 
0101 bool XYSmoothCurvePrivate::recalculateSpecific(const AbstractColumn* tmpXDataColumn, const AbstractColumn* tmpYDataColumn) {
0102     DEBUG(Q_FUNC_INFO)
0103     QElapsedTimer timer;
0104     timer.start();
0105 
0106     if (roughVector)
0107         roughVector->clear();
0108 
0109     if (!roughColumn) {
0110         roughColumn = new Column(QStringLiteral("rough"), AbstractColumn::ColumnMode::Double);
0111         roughColumn->setFixed(true); // visible in the project explorer but cannot be modified (renamed, deleted, etc.)
0112         roughVector = static_cast<QVector<double>*>(roughColumn->data());
0113         q->addChild(roughColumn);
0114     }
0115 
0116     // check column sizes
0117     if (tmpXDataColumn->rowCount() != tmpYDataColumn->rowCount()) {
0118         smoothResult.available = true;
0119         smoothResult.valid = false;
0120         smoothResult.status = i18n("Number of x and y data points must be equal.");
0121         return true;
0122     }
0123 
0124     // copy all valid data point for the smooth to temporary vectors
0125     QVector<double> xdataVector;
0126     QVector<double> ydataVector;
0127 
0128     double xmin;
0129     double xmax;
0130     if (smoothData.autoRange) {
0131         xmin = tmpXDataColumn->minimum();
0132         xmax = tmpXDataColumn->maximum();
0133     } else {
0134         xmin = smoothData.xRange.first();
0135         xmax = smoothData.xRange.last();
0136     }
0137 
0138     XYAnalysisCurve::copyData(xdataVector, ydataVector, tmpXDataColumn, tmpYDataColumn, xmin, xmax);
0139 
0140     // number of data points to smooth
0141     const size_t n = (size_t)xdataVector.size();
0142     if (n < 2) {
0143         smoothResult.available = true;
0144         smoothResult.valid = false;
0145         smoothResult.status = i18n("Not enough data points available.");
0146         return true;
0147     }
0148 
0149     double* xdata = xdataVector.data();
0150     double* ydata = ydataVector.data();
0151 
0152     double* ydataOriginal = new double[n];
0153     memcpy(ydataOriginal, ydata, n * sizeof(double));
0154 
0155     // smooth settings
0156     const nsl_smooth_type type = smoothData.type;
0157     const size_t points = smoothData.points;
0158     const nsl_smooth_weight_type weight = smoothData.weight;
0159     const double percentile = smoothData.percentile;
0160     const int order = smoothData.order;
0161     const nsl_smooth_pad_mode padMode = smoothData.mode;
0162     const double lvalue = smoothData.lvalue;
0163     const double rvalue = smoothData.rvalue;
0164 
0165     DEBUG(" smooth type:" << nsl_smooth_type_name[type]);
0166     DEBUG(" points = " << points);
0167     DEBUG(" weight: " << nsl_smooth_weight_type_name[weight]);
0168     DEBUG(" percentile = " << percentile);
0169     DEBUG(" order = " << order);
0170     DEBUG(" pad mode =  " << nsl_smooth_pad_mode_name[padMode]);
0171     DEBUG(" const. values = " << lvalue << ' ' << rvalue);
0172 
0173     ///////////////////////////////////////////////////////////
0174     int status = 0;
0175     gsl_set_error_handler_off();
0176 
0177     switch (type) {
0178     case nsl_smooth_type_moving_average:
0179         status = nsl_smooth_moving_average(ydata, n, points, weight, padMode);
0180         break;
0181     case nsl_smooth_type_moving_average_lagged:
0182         status = nsl_smooth_moving_average_lagged(ydata, n, points, weight, padMode);
0183         break;
0184     case nsl_smooth_type_percentile:
0185         status = nsl_smooth_percentile(ydata, n, points, percentile, padMode);
0186         break;
0187     case nsl_smooth_type_savitzky_golay:
0188         if (padMode == nsl_smooth_pad_constant)
0189             nsl_smooth_pad_constant_set(lvalue, rvalue);
0190         status = nsl_smooth_savgol(ydata, n, points, order, padMode);
0191         break;
0192     }
0193 
0194     xVector->resize((int)n);
0195     yVector->resize((int)n);
0196     memcpy(xVector->data(), xdata, n * sizeof(double));
0197     memcpy(yVector->data(), ydata, n * sizeof(double));
0198     ///////////////////////////////////////////////////////////
0199 
0200     // write the result
0201     smoothResult.available = true;
0202     smoothResult.valid = (status == 0);
0203     smoothResult.status = QString::number(status);
0204     smoothResult.elapsedTime = timer.elapsed();
0205 
0206     // fill rough vector
0207     if (roughVector) {
0208         roughVector->resize((int)n);
0209         for (int i = 0; i < (int)n; ++i)
0210             roughVector->data()[i] = ydataOriginal[i] - ydata[i];
0211         roughColumn->setChanged();
0212     }
0213 
0214     delete[] ydataOriginal;
0215 
0216     return true;
0217 }
0218 
0219 // ##############################################################################
0220 // ##################  Serialization/Deserialization  ###########################
0221 // ##############################################################################
0222 //! Save as XML
0223 void XYSmoothCurve::save(QXmlStreamWriter* writer) const {
0224     Q_D(const XYSmoothCurve);
0225 
0226     writer->writeStartElement(QStringLiteral("xySmoothCurve"));
0227 
0228     // write the base class
0229     XYAnalysisCurve::save(writer);
0230 
0231     // write xy-smooth-curve specific information
0232     //  smooth data
0233     writer->writeStartElement(QStringLiteral("smoothData"));
0234     writer->writeAttribute(QStringLiteral("autoRange"), QString::number(d->smoothData.autoRange));
0235     writer->writeAttribute(QStringLiteral("xRangeMin"), QString::number(d->smoothData.xRange.first()));
0236     writer->writeAttribute(QStringLiteral("xRangeMax"), QString::number(d->smoothData.xRange.last()));
0237     writer->writeAttribute(QStringLiteral("type"), QString::number(d->smoothData.type));
0238     writer->writeAttribute(QStringLiteral("points"), QString::number(d->smoothData.points));
0239     writer->writeAttribute(QStringLiteral("weight"), QString::number(d->smoothData.weight));
0240     writer->writeAttribute(QStringLiteral("percentile"), QString::number(d->smoothData.percentile));
0241     writer->writeAttribute(QStringLiteral("order"), QString::number(d->smoothData.order));
0242     writer->writeAttribute(QStringLiteral("mode"), QString::number(d->smoothData.mode));
0243     writer->writeAttribute(QStringLiteral("lvalue"), QString::number(d->smoothData.lvalue));
0244     writer->writeAttribute(QStringLiteral("rvalue"), QString::number(d->smoothData.rvalue));
0245     writer->writeEndElement(); // smoothData
0246 
0247     // smooth results (generated columns)
0248     writer->writeStartElement(QStringLiteral("smoothResult"));
0249     writer->writeAttribute(QStringLiteral("available"), QString::number(d->smoothResult.available));
0250     writer->writeAttribute(QStringLiteral("valid"), QString::number(d->smoothResult.valid));
0251     writer->writeAttribute(QStringLiteral("status"), d->smoothResult.status);
0252     writer->writeAttribute(QStringLiteral("time"), QString::number(d->smoothResult.elapsedTime));
0253 
0254     // save calculated columns if available
0255     if (saveCalculations() && d->xColumn) {
0256         d->xColumn->save(writer);
0257         d->yColumn->save(writer);
0258     }
0259 
0260     if (d->roughColumn)
0261         d->roughColumn->save(writer);
0262 
0263     writer->writeEndElement(); //"smoothResult"
0264 
0265     writer->writeEndElement(); //"xySmoothCurve"
0266 }
0267 
0268 //! Load from XML
0269 bool XYSmoothCurve::load(XmlStreamReader* reader, bool preview) {
0270     Q_D(XYSmoothCurve);
0271 
0272     QXmlStreamAttributes attribs;
0273     QString str;
0274 
0275     while (!reader->atEnd()) {
0276         reader->readNext();
0277         if (reader->isEndElement() && reader->name() == QLatin1String("xySmoothCurve"))
0278             break;
0279 
0280         if (!reader->isStartElement())
0281             continue;
0282 
0283         if (reader->name() == QLatin1String("xyAnalysisCurve")) {
0284             if (!XYAnalysisCurve::load(reader, preview))
0285                 return false;
0286         } else if (!preview && reader->name() == QLatin1String("smoothData")) {
0287             attribs = reader->attributes();
0288             READ_INT_VALUE("autoRange", smoothData.autoRange, bool);
0289             READ_DOUBLE_VALUE("xRangeMin", smoothData.xRange.first());
0290             READ_DOUBLE_VALUE("xRangeMax", smoothData.xRange.last());
0291             READ_INT_VALUE("type", smoothData.type, nsl_smooth_type);
0292             READ_INT_VALUE("points", smoothData.points, size_t);
0293             READ_INT_VALUE("weight", smoothData.weight, nsl_smooth_weight_type);
0294             READ_DOUBLE_VALUE("percentile", smoothData.percentile);
0295             READ_INT_VALUE("order", smoothData.order, int);
0296             READ_INT_VALUE("mode", smoothData.mode, nsl_smooth_pad_mode);
0297             READ_DOUBLE_VALUE("lvalue", smoothData.lvalue);
0298             READ_DOUBLE_VALUE("rvalue", smoothData.rvalue);
0299         } else if (!preview && reader->name() == QLatin1String("smoothResult")) {
0300             attribs = reader->attributes();
0301             READ_INT_VALUE("available", smoothResult.available, int);
0302             READ_INT_VALUE("valid", smoothResult.valid, int);
0303             READ_STRING_VALUE("status", smoothResult.status);
0304             READ_INT_VALUE("time", smoothResult.elapsedTime, int);
0305         } else if (!preview && reader->name() == QLatin1String("column")) {
0306             Column* column = new Column(QString(), AbstractColumn::ColumnMode::Double);
0307             if (!column->load(reader, preview)) {
0308                 delete column;
0309                 return false;
0310             }
0311             if (column->name() == QLatin1String("x"))
0312                 d->xColumn = column;
0313             else if (column->name() == QLatin1String("y"))
0314                 d->yColumn = column;
0315             else
0316                 d->roughColumn = column;
0317         } else { // unknown element
0318             reader->raiseUnknownElementWarning();
0319             if (!reader->skipToEndElement())
0320                 return false;
0321         }
0322     }
0323 
0324     if (preview)
0325         return true;
0326 
0327     // wait for data to be read before using the pointers
0328     QThreadPool::globalInstance()->waitForDone();
0329 
0330     if (d->xColumn && d->yColumn) {
0331         d->xColumn->setHidden(true);
0332         addChild(d->xColumn);
0333 
0334         d->yColumn->setHidden(true);
0335         addChild(d->yColumn);
0336 
0337         d->xVector = static_cast<QVector<double>*>(d->xColumn->data());
0338         d->yVector = static_cast<QVector<double>*>(d->yColumn->data());
0339 
0340         static_cast<XYCurvePrivate*>(d_ptr)->xColumn = d->xColumn;
0341         static_cast<XYCurvePrivate*>(d_ptr)->yColumn = d->yColumn;
0342 
0343         recalcLogicalPoints();
0344     }
0345 
0346     if (d->roughColumn) {
0347         addChild(d->roughColumn);
0348         d->roughVector = static_cast<QVector<double>*>(d->roughColumn->data());
0349     }
0350 
0351     return true;
0352 }