File indexing completed on 2025-01-26 03:34:21
0001 /* 0002 File : XYInterpolationCurve.cpp 0003 Project : LabPlot 0004 Description : A xy-curve defined by an interpolation 0005 -------------------------------------------------------------------- 0006 SPDX-FileCopyrightText: 2016-2021 Stefan Gerlach <stefan.gerlach@uni.kn> 0007 SPDX-FileCopyrightText: 2016-2023 Alexander Semke <alexander.semke@web.de> 0008 SPDX-License-Identifier: GPL-2.0-or-later 0009 */ 0010 0011 /*! 0012 \class XYInterpolationCurve 0013 \brief A xy-curve defined by an interpolation 0014 0015 \ingroup worksheet 0016 */ 0017 0018 #include "XYInterpolationCurve.h" 0019 #include "CartesianCoordinateSystem.h" 0020 #include "XYInterpolationCurvePrivate.h" 0021 #include "backend/core/column/Column.h" 0022 #include "backend/gsl/errors.h" 0023 #include "backend/lib/XmlStreamReader.h" 0024 #include "backend/lib/commandtemplates.h" 0025 #include "backend/lib/macros.h" 0026 0027 extern "C" { 0028 #include "backend/nsl/nsl_diff.h" 0029 #include "backend/nsl/nsl_int.h" 0030 } 0031 #include <gsl/gsl_interp.h> 0032 #include <gsl/gsl_spline.h> 0033 0034 #include <QElapsedTimer> 0035 #include <QIcon> 0036 #include <QThreadPool> 0037 0038 XYInterpolationCurve::XYInterpolationCurve(const QString& name) 0039 : XYAnalysisCurve(name, new XYInterpolationCurvePrivate(this), AspectType::XYInterpolationCurve) { 0040 } 0041 0042 XYInterpolationCurve::XYInterpolationCurve(const QString& name, XYInterpolationCurvePrivate* dd) 0043 : XYAnalysisCurve(name, dd, AspectType::XYInterpolationCurve) { 0044 } 0045 0046 // no need to delete the d-pointer here - it inherits from QGraphicsItem 0047 // and is deleted during the cleanup in QGraphicsScene 0048 XYInterpolationCurve::~XYInterpolationCurve() = default; 0049 0050 void XYInterpolationCurve::recalculate() { 0051 Q_D(XYInterpolationCurve); 0052 d->recalculate(); 0053 } 0054 0055 /*! 0056 Returns an icon to be used in the project explorer. 0057 */ 0058 QIcon XYInterpolationCurve::icon() const { 0059 return QIcon::fromTheme(QStringLiteral("labplot-xy-interpolation-curve")); 0060 } 0061 0062 // ############################################################################## 0063 // ########################## getter methods ################################## 0064 // ############################################################################## 0065 BASIC_SHARED_D_READER_IMPL(XYInterpolationCurve, XYInterpolationCurve::InterpolationData, interpolationData, interpolationData) 0066 0067 const XYAnalysisCurve::Result& XYInterpolationCurve::result() const { 0068 Q_D(const XYInterpolationCurve); 0069 return d->interpolationResult; 0070 } 0071 0072 // ############################################################################## 0073 // ################# setter methods and undo commands ########################## 0074 // ############################################################################## 0075 STD_SETTER_CMD_IMPL_F_S(XYInterpolationCurve, SetInterpolationData, XYInterpolationCurve::InterpolationData, interpolationData, recalculate) 0076 void XYInterpolationCurve::setInterpolationData(const XYInterpolationCurve::InterpolationData& interpolationData) { 0077 Q_D(XYInterpolationCurve); 0078 exec(new XYInterpolationCurveSetInterpolationDataCmd(d, interpolationData, ki18n("%1: set options and perform the interpolation"))); 0079 } 0080 0081 // ############################################################################## 0082 // ######################### Private implementation ############################# 0083 // ############################################################################## 0084 XYInterpolationCurvePrivate::XYInterpolationCurvePrivate(XYInterpolationCurve* owner) 0085 : XYAnalysisCurvePrivate(owner) 0086 , q(owner) { 0087 } 0088 0089 // no need to delete xColumn and yColumn, they are deleted 0090 // when the parent aspect is removed 0091 XYInterpolationCurvePrivate::~XYInterpolationCurvePrivate() = default; 0092 0093 void XYInterpolationCurvePrivate::resetResults() { 0094 interpolationResult = XYInterpolationCurve::InterpolationResult(); 0095 } 0096 0097 bool XYInterpolationCurvePrivate::recalculateSpecific(const AbstractColumn* tmpXDataColumn, const AbstractColumn* tmpYDataColumn) { 0098 QElapsedTimer timer; 0099 timer.start(); 0100 0101 // check column sizes 0102 if (tmpXDataColumn->rowCount() != tmpYDataColumn->rowCount()) { 0103 interpolationResult.available = true; 0104 interpolationResult.valid = false; 0105 interpolationResult.status = i18n("Number of x and y data points must be equal."); 0106 return true; 0107 } 0108 0109 // copy all valid data point for the interpolation to temporary vectors 0110 QVector<double> xdataVector; 0111 QVector<double> ydataVector; 0112 0113 double xmin, xmax; 0114 if (interpolationData.autoRange) { // all points 0115 xmin = tmpXDataColumn->minimum(); 0116 xmax = tmpXDataColumn->maximum(); 0117 } else { 0118 xmin = interpolationData.xRange.first(); 0119 xmax = interpolationData.xRange.last(); 0120 } 0121 0122 XYAnalysisCurve::copyData(xdataVector, ydataVector, tmpXDataColumn, tmpYDataColumn, xmin, xmax); 0123 0124 // only use range of valid data points 0125 const double validXMin = *std::min_element(xdataVector.constBegin(), xdataVector.constEnd()); 0126 const double validXMax = *std::max_element(xdataVector.constBegin(), xdataVector.constEnd()); 0127 if (interpolationData.autoRange) { 0128 xmin = validXMin; 0129 xmax = validXMax; 0130 } else { 0131 xmin = std::max(xmin, validXMin); 0132 xmax = std::min(xmax, validXMax); 0133 } 0134 DEBUG(Q_FUNC_INFO << ", x range = " << xmin << " .. " << xmax) 0135 0136 // number of data points to interpolate 0137 const size_t n = (size_t)xdataVector.size(); 0138 if (n < 2) { 0139 interpolationResult.available = true; 0140 interpolationResult.valid = false; 0141 interpolationResult.status = i18n("Not enough data points available."); 0142 return true; 0143 } 0144 0145 double* xdata = xdataVector.data(); 0146 double* ydata = ydataVector.data(); 0147 0148 for (unsigned int i = 1; i < n; i++) { 0149 if (xdata[i - 1] >= xdata[i]) { 0150 DEBUG("ERROR: x data not strictly increasing: x_{i-1} >= x_i @ i = " << i << ": " << xdata[i - 1] << " >= " << xdata[i]) 0151 interpolationResult.status = i18n("interpolation failed since x data is not strictly monotonic increasing!"); 0152 interpolationResult.available = true; 0153 return false; 0154 } 0155 } 0156 0157 // interpolation settings 0158 const nsl_interp_type type = interpolationData.type; 0159 const nsl_interp_pch_variant variant = interpolationData.variant; 0160 const double tension = interpolationData.tension; 0161 const double continuity = interpolationData.continuity; 0162 const double bias = interpolationData.bias; 0163 const nsl_interp_evaluate evaluate = interpolationData.evaluate; 0164 const size_t npoints = interpolationData.npoints; 0165 0166 DEBUG(Q_FUNC_INFO << ", type = " << nsl_interp_type_name[type]); 0167 DEBUG(Q_FUNC_INFO << ", cubic Hermite variant: " << nsl_interp_pch_variant_name[variant] << " (" << tension << continuity << bias << ")"); 0168 DEBUG(Q_FUNC_INFO << ", evaluate: " << nsl_interp_evaluate_name[evaluate]); 0169 DEBUG(Q_FUNC_INFO << ", npoints = " << npoints); 0170 DEBUG(Q_FUNC_INFO << ", data points = " << n); 0171 0172 /////////////////////////////////////////////////////////// 0173 int status = 0; 0174 0175 gsl_set_error_handler_off(); 0176 gsl_interp_accel* acc = gsl_interp_accel_alloc(); 0177 gsl_spline* spline = nullptr; 0178 switch (type) { 0179 case nsl_interp_type_linear: 0180 spline = gsl_spline_alloc(gsl_interp_linear, n); 0181 status = gsl_spline_init(spline, xdata, ydata, n); 0182 break; 0183 case nsl_interp_type_polynomial: 0184 spline = gsl_spline_alloc(gsl_interp_polynomial, n); 0185 status = gsl_spline_init(spline, xdata, ydata, n); 0186 break; 0187 case nsl_interp_type_cspline: 0188 spline = gsl_spline_alloc(gsl_interp_cspline, n); 0189 status = gsl_spline_init(spline, xdata, ydata, n); 0190 break; 0191 case nsl_interp_type_cspline_periodic: 0192 spline = gsl_spline_alloc(gsl_interp_cspline_periodic, n); 0193 status = gsl_spline_init(spline, xdata, ydata, n); 0194 break; 0195 case nsl_interp_type_akima: 0196 spline = gsl_spline_alloc(gsl_interp_akima, n); 0197 status = gsl_spline_init(spline, xdata, ydata, n); 0198 break; 0199 case nsl_interp_type_akima_periodic: 0200 spline = gsl_spline_alloc(gsl_interp_akima_periodic, n); 0201 status = gsl_spline_init(spline, xdata, ydata, n); 0202 break; 0203 case nsl_interp_type_steffen: 0204 #if GSL_MAJOR_VERSION >= 2 0205 spline = gsl_spline_alloc(gsl_interp_steffen, n); 0206 status = gsl_spline_init(spline, xdata, ydata, n); 0207 #endif 0208 break; 0209 case nsl_interp_type_cosine: 0210 case nsl_interp_type_pch: 0211 case nsl_interp_type_rational: 0212 case nsl_interp_type_exponential: 0213 break; 0214 } 0215 0216 xVector->resize((int)npoints); 0217 yVector->resize((int)npoints); 0218 for (unsigned int i = 0; i < npoints; i++) { 0219 size_t a = 0, b = n - 1; 0220 0221 double x = xmin + i * (xmax - xmin) / (npoints - 1); 0222 (*xVector)[(int)i] = x; 0223 0224 // make sure the value for x determined above is within the ranges to avoid subtle issues 0225 // related to the representation of float numbers 0226 if (i == 0 && x < xmin) { 0227 x = xmin; 0228 (*xVector)[(int)i] = xmin; 0229 } else if (i == npoints - 1 && x > xmax) { 0230 x = xmax; 0231 (*xVector)[(int)i] = x; 0232 } 0233 0234 // find index a,b for interval [x[a],x[b]] around x[i] using bisection 0235 if (type == nsl_interp_type_cosine || type == nsl_interp_type_exponential || type == nsl_interp_type_pch) { 0236 while (b - a > 1) { 0237 unsigned int j = floor((a + b) / 2.); 0238 if (xdata[j] > x) 0239 b = j; 0240 else 0241 a = j; 0242 } 0243 } 0244 0245 // evaluate interpolation 0246 double t; 0247 switch (type) { 0248 case nsl_interp_type_linear: 0249 case nsl_interp_type_polynomial: 0250 case nsl_interp_type_cspline: 0251 case nsl_interp_type_cspline_periodic: 0252 case nsl_interp_type_akima: 0253 case nsl_interp_type_akima_periodic: 0254 case nsl_interp_type_steffen: 0255 switch (evaluate) { 0256 case nsl_interp_evaluate_function: 0257 (*yVector)[(int)i] = gsl_spline_eval(spline, x, acc); 0258 break; 0259 case nsl_interp_evaluate_derivative: 0260 (*yVector)[(int)i] = gsl_spline_eval_deriv(spline, x, acc); 0261 break; 0262 case nsl_interp_evaluate_second_derivative: 0263 (*yVector)[(int)i] = gsl_spline_eval_deriv2(spline, x, acc); 0264 break; 0265 case nsl_interp_evaluate_integral: 0266 (*yVector)[(int)i] = gsl_spline_eval_integ(spline, xmin, x, acc); 0267 break; 0268 } 0269 break; 0270 case nsl_interp_type_cosine: 0271 t = (x - xdata[a]) / (xdata[b] - xdata[a]); 0272 t = (1. - cos(M_PI * t)) / 2.; 0273 (*yVector)[(int)i] = ydata[a] + t * (ydata[b] - ydata[a]); 0274 break; 0275 case nsl_interp_type_exponential: 0276 t = (x - xdata[a]) / (xdata[b] - xdata[a]); 0277 (*yVector)[(int)i] = ydata[a] * pow(ydata[b] / ydata[a], t); 0278 break; 0279 case nsl_interp_type_pch: { 0280 t = (x - xdata[a]) / (xdata[b] - xdata[a]); 0281 double t2 = t * t, t3 = t2 * t; 0282 double h1 = 2. * t3 - 3. * t2 + 1, h2 = -2. * t3 + 3. * t2, h3 = t3 - 2 * t2 + t, h4 = t3 - t2; 0283 double m1 = 0., m2 = 0.; 0284 switch (variant) { 0285 case nsl_interp_pch_variant_finite_difference: 0286 if (a == 0) 0287 m1 = (ydata[b] - ydata[a]) / (xdata[b] - xdata[a]); 0288 else 0289 m1 = ((ydata[b] - ydata[a]) / (xdata[b] - xdata[a]) + (ydata[a] - ydata[a - 1]) / (xdata[a] - xdata[a - 1])) / 2.; 0290 if (b == n - 1) 0291 m2 = (ydata[b] - ydata[a]) / (xdata[b] - xdata[a]); 0292 else 0293 m2 = ((ydata[b + 1] - ydata[b]) / (xdata[b + 1] - xdata[b]) + (ydata[b] - ydata[a]) / (xdata[b] - xdata[a])) / 2.; 0294 0295 break; 0296 case nsl_interp_pch_variant_catmull_rom: 0297 if (a == 0) 0298 m1 = (ydata[b] - ydata[a]) / (xdata[b] - xdata[a]); 0299 else 0300 m1 = (ydata[b] - ydata[a - 1]) / (xdata[b] - xdata[a - 1]); 0301 if (b == n - 1) 0302 m2 = (ydata[b] - ydata[a]) / (xdata[b] - xdata[a]); 0303 else 0304 m2 = (ydata[b + 1] - ydata[a]) / (xdata[b + 1] - xdata[a]); 0305 0306 break; 0307 case nsl_interp_pch_variant_cardinal: 0308 if (a == 0) 0309 m1 = (ydata[b] - ydata[a]) / (xdata[b] - xdata[a]); 0310 else 0311 m1 = (ydata[b] - ydata[a - 1]) / (xdata[b] - xdata[a - 1]); 0312 m1 *= (1. - tension); 0313 if (b == n - 1) 0314 m2 = (ydata[b] - ydata[a]) / (xdata[b] - xdata[a]); 0315 else 0316 m2 = (ydata[b + 1] - ydata[a]) / (xdata[b + 1] - xdata[a]); 0317 m2 *= (1. - tension); 0318 0319 break; 0320 case nsl_interp_pch_variant_kochanek_bartels: 0321 if (a == 0) 0322 m1 = (1. + continuity) * (1. - bias) * (ydata[b] - ydata[a]) / (xdata[b] - xdata[a]); 0323 else 0324 m1 = ((1. - continuity) * (1. + bias) * (ydata[a] - ydata[a - 1]) / (xdata[a] - xdata[a - 1]) 0325 + (1. + continuity) * (1. - bias) * (ydata[b] - ydata[a]) / (xdata[b] - xdata[a])) 0326 / 2.; 0327 m1 *= (1. - tension); 0328 if (b == n - 1) 0329 m2 = (1. + continuity) * (1. + bias) * (ydata[b] - ydata[a]) / (xdata[b] - xdata[a]); 0330 else 0331 m2 = ((1. + continuity) * (1. + bias) * (ydata[b] - ydata[a]) / (xdata[b] - xdata[a]) 0332 + (1. - continuity) * (1. - bias) * (ydata[b + 1] - ydata[b]) / (xdata[b + 1] - xdata[b])) 0333 / 2.; 0334 m2 *= (1. - tension); 0335 0336 break; 0337 } 0338 0339 // Hermite polynomial 0340 (*yVector)[(int)i] = ydata[a] * h1 + ydata[b] * h2 + (xdata[b] - xdata[a]) * (m1 * h3 + m2 * h4); 0341 } break; 0342 case nsl_interp_type_rational: { 0343 double v, dv; 0344 nsl_interp_ratint(xdata, ydata, (int)n, x, &v, &dv); 0345 (*yVector)[(int)i] = v; 0346 // TODO: use error dv 0347 break; 0348 } 0349 } 0350 } 0351 0352 // calculate "evaluate" option for own types 0353 if (type == nsl_interp_type_cosine || type == nsl_interp_type_exponential || type == nsl_interp_type_pch || type == nsl_interp_type_rational) { 0354 switch (evaluate) { 0355 case nsl_interp_evaluate_function: 0356 break; 0357 case nsl_interp_evaluate_derivative: 0358 nsl_diff_first_deriv_second_order(xVector->data(), yVector->data(), npoints); 0359 break; 0360 case nsl_interp_evaluate_second_derivative: 0361 nsl_diff_second_deriv_second_order(xVector->data(), yVector->data(), npoints); 0362 break; 0363 case nsl_interp_evaluate_integral: 0364 nsl_int_trapezoid(xVector->data(), yVector->data(), npoints, 0); 0365 break; 0366 } 0367 } 0368 0369 // check values 0370 for (int i = 0; i < (int)npoints; i++) { 0371 if ((*yVector)[i] > std::numeric_limits<double>::max()) 0372 (*yVector)[i] = std::numeric_limits<double>::max(); 0373 else if ((*yVector)[i] < std::numeric_limits<double>::lowest()) 0374 (*yVector)[i] = std::numeric_limits<double>::lowest(); 0375 } 0376 0377 gsl_spline_free(spline); 0378 gsl_interp_accel_free(acc); 0379 0380 /////////////////////////////////////////////////////////// 0381 0382 // write the result 0383 interpolationResult.available = true; 0384 interpolationResult.valid = (status == GSL_SUCCESS); 0385 interpolationResult.status = gslErrorToString(status); 0386 interpolationResult.elapsedTime = timer.elapsed(); 0387 0388 return true; 0389 } 0390 0391 // ############################################################################## 0392 // ################## Serialization/Deserialization ########################### 0393 // ############################################################################## 0394 //! Save as XML 0395 void XYInterpolationCurve::save(QXmlStreamWriter* writer) const { 0396 Q_D(const XYInterpolationCurve); 0397 0398 writer->writeStartElement(QStringLiteral("xyInterpolationCurve")); 0399 0400 // write the base class 0401 XYAnalysisCurve::save(writer); 0402 0403 // write xy-interpolation-curve specific information 0404 // interpolation data 0405 writer->writeStartElement(QStringLiteral("interpolationData")); 0406 writer->writeAttribute(QStringLiteral("autoRange"), QString::number(d->interpolationData.autoRange)); 0407 writer->writeAttribute(QStringLiteral("xRangeMin"), QString::number(d->interpolationData.xRange.first())); 0408 writer->writeAttribute(QStringLiteral("xRangeMax"), QString::number(d->interpolationData.xRange.last())); 0409 writer->writeAttribute(QStringLiteral("type"), QString::number(d->interpolationData.type)); 0410 writer->writeAttribute(QStringLiteral("variant"), QString::number(d->interpolationData.variant)); 0411 writer->writeAttribute(QStringLiteral("tension"), QString::number(d->interpolationData.tension)); 0412 writer->writeAttribute(QStringLiteral("continuity"), QString::number(d->interpolationData.continuity)); 0413 writer->writeAttribute(QStringLiteral("bias"), QString::number(d->interpolationData.bias)); 0414 writer->writeAttribute(QStringLiteral("npoints"), QString::number(d->interpolationData.npoints)); 0415 writer->writeAttribute(QStringLiteral("pointsMode"), QString::number(static_cast<int>(d->interpolationData.pointsMode))); 0416 writer->writeAttribute(QStringLiteral("evaluate"), QString::number(d->interpolationData.evaluate)); 0417 writer->writeEndElement(); // interpolationData 0418 0419 // interpolation results (generated columns) 0420 writer->writeStartElement(QStringLiteral("interpolationResult")); 0421 writer->writeAttribute(QStringLiteral("available"), QString::number(d->interpolationResult.available)); 0422 writer->writeAttribute(QStringLiteral("valid"), QString::number(d->interpolationResult.valid)); 0423 writer->writeAttribute(QStringLiteral("status"), d->interpolationResult.status); 0424 writer->writeAttribute(QStringLiteral("time"), QString::number(d->interpolationResult.elapsedTime)); 0425 0426 // save calculated columns if available 0427 if (saveCalculations() && d->xColumn) { 0428 d->xColumn->save(writer); 0429 d->yColumn->save(writer); 0430 } 0431 writer->writeEndElement(); //"interpolationResult" 0432 0433 writer->writeEndElement(); //"xyInterpolationCurve" 0434 } 0435 0436 //! Load from XML 0437 bool XYInterpolationCurve::load(XmlStreamReader* reader, bool preview) { 0438 Q_D(XYInterpolationCurve); 0439 0440 QXmlStreamAttributes attribs; 0441 QString str; 0442 0443 while (!reader->atEnd()) { 0444 reader->readNext(); 0445 if (reader->isEndElement() && reader->name() == QLatin1String("xyInterpolationCurve")) 0446 break; 0447 0448 if (!reader->isStartElement()) 0449 continue; 0450 0451 if (reader->name() == QLatin1String("xyAnalysisCurve")) { 0452 if (!XYAnalysisCurve::load(reader, preview)) 0453 return false; 0454 } else if (!preview && reader->name() == QLatin1String("interpolationData")) { 0455 attribs = reader->attributes(); 0456 READ_INT_VALUE("autoRange", interpolationData.autoRange, bool); 0457 READ_DOUBLE_VALUE("xRangeMin", interpolationData.xRange.first()); 0458 READ_DOUBLE_VALUE("xRangeMax", interpolationData.xRange.last()); 0459 READ_INT_VALUE("type", interpolationData.type, nsl_interp_type); 0460 READ_INT_VALUE("variant", interpolationData.variant, nsl_interp_pch_variant); 0461 READ_DOUBLE_VALUE("tension", interpolationData.tension); 0462 READ_DOUBLE_VALUE("continuity", interpolationData.continuity); 0463 READ_DOUBLE_VALUE("bias", interpolationData.bias); 0464 READ_INT_VALUE("npoints", interpolationData.npoints, size_t); 0465 READ_INT_VALUE("pointsMode", interpolationData.pointsMode, XYInterpolationCurve::PointsMode); 0466 READ_INT_VALUE("evaluate", interpolationData.evaluate, nsl_interp_evaluate); 0467 } else if (!preview && reader->name() == QLatin1String("interpolationResult")) { 0468 attribs = reader->attributes(); 0469 READ_INT_VALUE("available", interpolationResult.available, int); 0470 READ_INT_VALUE("valid", interpolationResult.valid, int); 0471 READ_STRING_VALUE("status", interpolationResult.status); 0472 READ_INT_VALUE("time", interpolationResult.elapsedTime, int); 0473 } else if (reader->name() == QLatin1String("column")) { 0474 Column* column = new Column(QString(), AbstractColumn::ColumnMode::Double); 0475 if (!column->load(reader, preview)) { 0476 delete column; 0477 return false; 0478 } 0479 if (column->name() == QLatin1String("x")) 0480 d->xColumn = column; 0481 else if (column->name() == QLatin1String("y")) 0482 d->yColumn = column; 0483 } else { // unknown element 0484 reader->raiseUnknownElementWarning(); 0485 if (!reader->skipToEndElement()) 0486 return false; 0487 } 0488 } 0489 0490 if (preview) 0491 return true; 0492 0493 // wait for data to be read before using the pointers 0494 QThreadPool::globalInstance()->waitForDone(); 0495 0496 if (d->xColumn && d->yColumn) { 0497 d->xColumn->setHidden(true); 0498 addChild(d->xColumn); 0499 0500 d->yColumn->setHidden(true); 0501 addChild(d->yColumn); 0502 0503 d->xVector = static_cast<QVector<double>*>(d->xColumn->data()); 0504 d->yVector = static_cast<QVector<double>*>(d->yColumn->data()); 0505 0506 static_cast<XYCurvePrivate*>(d_ptr)->xColumn = d->xColumn; 0507 static_cast<XYCurvePrivate*>(d_ptr)->yColumn = d->yColumn; 0508 0509 recalcLogicalPoints(); 0510 } 0511 0512 return true; 0513 }