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 }