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 }