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

0001 /*
0002     File                 : XYCorrelationCurve.cpp
0003     Project              : LabPlot
0004     Description          : A xy-curve defined by a correlation
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2018 Stefan Gerlach <stefan.gerlach@uni.kn>
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 /*!
0011   \class XYCorrelationCurve
0012   \brief A xy-curve defined by a correlation
0013 
0014   \ingroup worksheet
0015 */
0016 
0017 #include "XYCorrelationCurve.h"
0018 #include "XYCorrelationCurvePrivate.h"
0019 #include "backend/core/column/Column.h"
0020 #include "backend/lib/XmlStreamReader.h"
0021 #include "backend/lib/commandtemplates.h"
0022 #include "backend/lib/macros.h"
0023 
0024 #include <KLocalizedString>
0025 #include <QElapsedTimer>
0026 #include <QIcon>
0027 #include <QThreadPool>
0028 
0029 #include <gsl/gsl_math.h>
0030 
0031 XYCorrelationCurve::XYCorrelationCurve(const QString& name)
0032     : XYAnalysisCurve(name, new XYCorrelationCurvePrivate(this), AspectType::XYCorrelationCurve) {
0033 }
0034 
0035 XYCorrelationCurve::XYCorrelationCurve(const QString& name, XYCorrelationCurvePrivate* dd)
0036     : XYAnalysisCurve(name, dd, AspectType::XYCorrelationCurve) {
0037 }
0038 
0039 // no need to delete the d-pointer here - it inherits from QGraphicsItem
0040 // and is deleted during the cleanup in QGraphicsScene
0041 XYCorrelationCurve::~XYCorrelationCurve() = default;
0042 
0043 void XYCorrelationCurve::recalculate() {
0044     Q_D(XYCorrelationCurve);
0045     d->recalculate();
0046 }
0047 
0048 const XYAnalysisCurve::Result& XYCorrelationCurve::result() const {
0049     Q_D(const XYCorrelationCurve);
0050     return d->correlationResult;
0051 }
0052 
0053 /*!
0054     Returns an icon to be used in the project explorer.
0055 */
0056 QIcon XYCorrelationCurve::icon() const {
0057     //  return QIcon::fromTheme("labplot-xy-correlation-curve"); //not available yet
0058     return QIcon::fromTheme(QStringLiteral("labplot-xy-curve"));
0059 }
0060 
0061 // ##############################################################################
0062 // ##########################  getter methods  ##################################
0063 // ##############################################################################
0064 BASIC_SHARED_D_READER_IMPL(XYCorrelationCurve, XYCorrelationCurve::CorrelationData, correlationData, correlationData)
0065 
0066 const XYCorrelationCurve::CorrelationResult& XYCorrelationCurve::correlationResult() const {
0067     Q_D(const XYCorrelationCurve);
0068     return d->correlationResult;
0069 }
0070 
0071 // ##############################################################################
0072 // #################  setter methods and undo commands ##########################
0073 // ##############################################################################
0074 STD_SETTER_CMD_IMPL_F_S(XYCorrelationCurve, SetCorrelationData, XYCorrelationCurve::CorrelationData, correlationData, recalculate)
0075 void XYCorrelationCurve::setCorrelationData(const XYCorrelationCurve::CorrelationData& correlationData) {
0076     Q_D(XYCorrelationCurve);
0077     exec(new XYCorrelationCurveSetCorrelationDataCmd(d, correlationData, ki18n("%1: set options and perform the correlation")));
0078 }
0079 
0080 // ##############################################################################
0081 // ######################### Private implementation #############################
0082 // ##############################################################################
0083 XYCorrelationCurvePrivate::XYCorrelationCurvePrivate(XYCorrelationCurve* owner)
0084     : XYAnalysisCurvePrivate(owner)
0085     , q(owner) {
0086 }
0087 
0088 // no need to delete xColumn and yColumn, they are deleted
0089 // when the parent aspect is removed
0090 XYCorrelationCurvePrivate::~XYCorrelationCurvePrivate() = default;
0091 
0092 void XYCorrelationCurvePrivate::resetResults() {
0093     correlationResult = XYCorrelationCurve::CorrelationResult();
0094 }
0095 
0096 bool XYCorrelationCurvePrivate::preparationValid(const AbstractColumn* tmpXDataColumn, const AbstractColumn* tmpYDataColumn) {
0097     Q_UNUSED(tmpXDataColumn);
0098     return tmpYDataColumn != nullptr;
0099 }
0100 
0101 bool XYCorrelationCurvePrivate::recalculateSpecific(const AbstractColumn* tmpXDataColumn, const AbstractColumn* tmpYDataColumn) {
0102     DEBUG(Q_FUNC_INFO);
0103     QElapsedTimer timer;
0104     timer.start();
0105 
0106     // determine the data source columns
0107     const AbstractColumn* tmpY2DataColumn = nullptr;
0108     if (dataSourceType == XYAnalysisCurve::DataSourceType::Spreadsheet) {
0109         // spreadsheet columns as data source
0110         tmpY2DataColumn = y2DataColumn;
0111     } else {
0112         // curve columns as data source (autocorrelation)
0113         tmpY2DataColumn = dataSourceCurve->yColumn();
0114     }
0115 
0116     if (tmpY2DataColumn == nullptr) {
0117         return true;
0118     }
0119 
0120     // copy all valid data point for the correlation to temporary vectors
0121     QVector<double> xdataVector;
0122     QVector<double> ydataVector;
0123     QVector<double> y2dataVector;
0124 
0125     double xmin, xmax;
0126     if (tmpXDataColumn != nullptr && correlationData.autoRange) {
0127         xmin = tmpXDataColumn->minimum();
0128         xmax = tmpXDataColumn->maximum();
0129     } else {
0130         xmin = correlationData.xRange.first();
0131         xmax = correlationData.xRange.last();
0132     }
0133 
0134     // only copy those data where values are valid and in range
0135     if (tmpXDataColumn != nullptr) { // x-axis present (with possible range)
0136         for (int row = 0; row < tmpXDataColumn->rowCount(); ++row) {
0137             if (tmpXDataColumn->isValid(row) && !tmpXDataColumn->isMasked(row) && tmpYDataColumn->isValid(row) && !tmpYDataColumn->isMasked(row)) {
0138                 if (tmpXDataColumn->valueAt(row) >= xmin && tmpXDataColumn->valueAt(row) <= xmax) {
0139                     xdataVector.append(tmpXDataColumn->valueAt(row));
0140                     ydataVector.append(tmpYDataColumn->valueAt(row));
0141                 }
0142             }
0143         }
0144     } else { // no x-axis: take all valid values
0145         for (int row = 0; row < tmpYDataColumn->rowCount(); ++row)
0146             if (tmpYDataColumn->isValid(row) && !tmpYDataColumn->isMasked(row))
0147                 ydataVector.append(tmpYDataColumn->valueAt(row));
0148     }
0149 
0150     for (int row = 0; row < tmpY2DataColumn->rowCount(); ++row)
0151         if (tmpY2DataColumn->isValid(row) && !tmpY2DataColumn->isMasked(row))
0152             y2dataVector.append(tmpY2DataColumn->valueAt(row));
0153 
0154     const size_t n = (size_t)ydataVector.size(); // number of points for signal
0155     const size_t m = (size_t)y2dataVector.size(); // number of points for response
0156     if (n < 1 || m < 1) {
0157         correlationResult.available = true;
0158         correlationResult.valid = false;
0159         correlationResult.status = i18n("Not enough data points available.");
0160         return true;
0161     }
0162 
0163     double* xdata = xdataVector.data();
0164     double* ydata = ydataVector.data();
0165     double* y2data = y2dataVector.data();
0166 
0167     // correlation settings
0168     const double samplingInterval = correlationData.samplingInterval;
0169     const nsl_corr_type_type type = correlationData.type;
0170     const nsl_corr_norm_type norm = correlationData.normalize;
0171 
0172     DEBUG("signal_1 n = " << n << ", signal_2 n = " << m);
0173     DEBUG("sampling interval = " << samplingInterval);
0174     DEBUG("type = " << nsl_corr_type_name[type]);
0175     DEBUG("norm = " << nsl_corr_norm_name[norm]);
0176 
0177     ///////////////////////////////////////////////////////////
0178     size_t np = GSL_MAX(n, m);
0179     if (type == nsl_corr_type_linear)
0180         np = 2 * np - 1;
0181 
0182     double* out = (double*)malloc(np * sizeof(double));
0183     int status = nsl_corr_correlation(ydata, n, y2data, m, type, norm, out);
0184 
0185     xVector->resize((int)np);
0186     yVector->resize((int)np);
0187     // take given x-axis values or use index
0188     if (tmpXDataColumn != nullptr) {
0189         int size = GSL_MIN(xdataVector.size(), (int)np);
0190         memcpy(xVector->data(), xdata, size * sizeof(double));
0191         double sampleInterval = (xVector->data()[size - 1] - xVector->data()[0]) / (xdataVector.size() - 1);
0192         DEBUG("xdata size = " << xdataVector.size() << ", np = " << np << ", sample interval = " << sampleInterval);
0193         for (int i = size; i < (int)np; i++) // fill missing values
0194             xVector->data()[i] = xVector->data()[size - 1] + (i - size + 1) * sampleInterval;
0195     } else { // fill with index (starting with 0)
0196         if (type == nsl_corr_type_linear)
0197             for (size_t i = 0; i < np; i++)
0198                 xVector->data()[i] = (int)(i - np / 2) * samplingInterval;
0199         else
0200             for (size_t i = 0; i < np; i++)
0201                 xVector->data()[i] = (int)i * samplingInterval;
0202     }
0203 
0204     memcpy(yVector->data(), out, np * sizeof(double));
0205     free(out);
0206     ///////////////////////////////////////////////////////////
0207 
0208     // write the result
0209     correlationResult.available = true;
0210     correlationResult.valid = (status == 0);
0211     correlationResult.status = QString::number(status);
0212     correlationResult.elapsedTime = timer.elapsed();
0213 
0214     return true;
0215 }
0216 
0217 // ##############################################################################
0218 // ##################  Serialization/Deserialization  ###########################
0219 // ##############################################################################
0220 //! Save as XML
0221 void XYCorrelationCurve::save(QXmlStreamWriter* writer) const {
0222     Q_D(const XYCorrelationCurve);
0223 
0224     writer->writeStartElement(QStringLiteral("xyCorrelationCurve"));
0225 
0226     // write the base class
0227     XYAnalysisCurve::save(writer);
0228 
0229     // write xy-correlation-curve specific information
0230     //  correlation data
0231     writer->writeStartElement(QStringLiteral("correlationData"));
0232     writer->writeAttribute(QStringLiteral("samplingInterval"), QString::number(d->correlationData.samplingInterval));
0233     writer->writeAttribute(QStringLiteral("autoRange"), QString::number(d->correlationData.autoRange));
0234     writer->writeAttribute(QStringLiteral("xRangeMin"), QString::number(d->correlationData.xRange.first()));
0235     writer->writeAttribute(QStringLiteral("xRangeMax"), QString::number(d->correlationData.xRange.last()));
0236     writer->writeAttribute(QStringLiteral("type"), QString::number(d->correlationData.type));
0237     writer->writeAttribute(QStringLiteral("normalize"), QString::number(d->correlationData.normalize));
0238     writer->writeEndElement(); // correlationData
0239 
0240     // correlation results (generated columns)
0241     writer->writeStartElement(QStringLiteral("correlationResult"));
0242     writer->writeAttribute(QStringLiteral("available"), QString::number(d->correlationResult.available));
0243     writer->writeAttribute(QStringLiteral("valid"), QString::number(d->correlationResult.valid));
0244     writer->writeAttribute(QStringLiteral("status"), d->correlationResult.status);
0245     writer->writeAttribute(QStringLiteral("time"), QString::number(d->correlationResult.elapsedTime));
0246 
0247     // save calculated columns if available
0248     if (saveCalculations() && d->xColumn) {
0249         d->xColumn->save(writer);
0250         d->yColumn->save(writer);
0251     }
0252     writer->writeEndElement(); //"correlationResult"
0253 
0254     writer->writeEndElement(); //"xyCorrelationCurve"
0255 }
0256 
0257 //! Load from XML
0258 bool XYCorrelationCurve::load(XmlStreamReader* reader, bool preview) {
0259     Q_D(XYCorrelationCurve);
0260 
0261     QXmlStreamAttributes attribs;
0262     QString str;
0263 
0264     while (!reader->atEnd()) {
0265         reader->readNext();
0266         if (reader->isEndElement() && reader->name() == QLatin1String("xyCorrelationCurve"))
0267             break;
0268 
0269         if (!reader->isStartElement())
0270             continue;
0271 
0272         if (reader->name() == QLatin1String("xyAnalysisCurve")) {
0273             if (!XYAnalysisCurve::load(reader, preview))
0274                 return false;
0275         } else if (!preview && reader->name() == QLatin1String("correlationData")) {
0276             attribs = reader->attributes();
0277             READ_DOUBLE_VALUE("samplingInterval", correlationData.samplingInterval);
0278             READ_INT_VALUE("autoRange", correlationData.autoRange, bool);
0279             READ_DOUBLE_VALUE("xRangeMin", correlationData.xRange.first());
0280             READ_DOUBLE_VALUE("xRangeMax", correlationData.xRange.last());
0281             READ_INT_VALUE("type", correlationData.type, nsl_corr_type_type);
0282             READ_INT_VALUE("normalize", correlationData.normalize, nsl_corr_norm_type);
0283         } else if (!preview && reader->name() == QLatin1String("correlationResult")) {
0284             attribs = reader->attributes();
0285             READ_INT_VALUE("available", correlationResult.available, int);
0286             READ_INT_VALUE("valid", correlationResult.valid, int);
0287             READ_STRING_VALUE("status", correlationResult.status);
0288             READ_INT_VALUE("time", correlationResult.elapsedTime, int);
0289         } else if (!preview && reader->name() == QLatin1String("column")) {
0290             Column* column = new Column(QString(), AbstractColumn::ColumnMode::Double);
0291             if (!column->load(reader, preview)) {
0292                 delete column;
0293                 return false;
0294             }
0295             if (column->name() == QLatin1String("x"))
0296                 d->xColumn = column;
0297             else if (column->name() == QLatin1String("y"))
0298                 d->yColumn = column;
0299         } else { // unknown element
0300             reader->raiseUnknownElementWarning();
0301             if (!reader->skipToEndElement())
0302                 return false;
0303         }
0304     }
0305 
0306     if (preview)
0307         return true;
0308 
0309     // wait for data to be read before using the pointers
0310     QThreadPool::globalInstance()->waitForDone();
0311 
0312     if (d->xColumn && d->yColumn) {
0313         d->xColumn->setHidden(true);
0314         addChild(d->xColumn);
0315 
0316         d->yColumn->setHidden(true);
0317         addChild(d->yColumn);
0318 
0319         d->xVector = static_cast<QVector<double>*>(d->xColumn->data());
0320         d->yVector = static_cast<QVector<double>*>(d->yColumn->data());
0321 
0322         static_cast<XYCurvePrivate*>(d_ptr)->xColumn = d->xColumn;
0323         static_cast<XYCurvePrivate*>(d_ptr)->yColumn = d->yColumn;
0324 
0325         recalcLogicalPoints();
0326     }
0327 
0328     return true;
0329 }