From 1f02b8201e2a200bbdf3847964b5e01678a973b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Wed, 30 May 2018 12:20:10 +0200 Subject: [PATCH] [apps/regression] Fix regression --- apps/regression/app.cpp | 2 +- apps/regression/calculation_controller.cpp | 2 +- apps/regression/graph_controller.cpp | 9 +- apps/regression/graph_view.cpp | 11 +- apps/regression/graph_view.h | 1 + apps/regression/store.cpp | 201 ++++++++++++--------- apps/regression/store.h | 49 ++--- 7 files changed, 156 insertions(+), 119 deletions(-) diff --git a/apps/regression/app.cpp b/apps/regression/app.cpp index d1b3f9ade..ecd8180a3 100644 --- a/apps/regression/app.cpp +++ b/apps/regression/app.cpp @@ -32,7 +32,7 @@ App * App::Snapshot::unpack(Container * container) { } void App::Snapshot::reset() { - m_store.deleteAllPairsOfAllSeries(); + m_store.deleteAllPairs(); m_store.setDefault(); m_modelVersion = 0; m_rangeVersion = 0; diff --git a/apps/regression/calculation_controller.cpp b/apps/regression/calculation_controller.cpp index 00a7efa23..065d0e8fc 100644 --- a/apps/regression/calculation_controller.cpp +++ b/apps/regression/calculation_controller.cpp @@ -163,7 +163,7 @@ void CalculationController::willDisplayCellAtLocation(HighlightCell * cell, int } if (i == 1 && j > k_totalNumberOfDoubleBufferRows) { assert(j != 9); - CalculPointer calculationMethods[k_totalNumberOfRows-k_totalNumberOfDoubleBufferRows] = {&Store::numberOfPairs, &Store::covariance, + CalculPointer calculationMethods[k_totalNumberOfRows-k_totalNumberOfDoubleBufferRows] = {&Store::doubleCastedNumberOfPairsOfSeries, &Store::covariance, &Store::columnProductSum, nullptr, &Store::slope, &Store::yIntercept, &Store::correlationCoefficient, &Store::squaredCorrelationCoefficient}; double calculation = (m_store->*calculationMethods[j-k_totalNumberOfDoubleBufferRows-1])(); EvenOddBufferTextCell * myCell = (EvenOddBufferTextCell *)cell; diff --git a/apps/regression/graph_controller.cpp b/apps/regression/graph_controller.cpp index b664a02b2..7abbd9416 100644 --- a/apps/regression/graph_controller.cpp +++ b/apps/regression/graph_controller.cpp @@ -26,10 +26,15 @@ ViewController * GraphController::initialisationParameterController() { } bool GraphController::isEmpty() const { - if (m_store->numberOfPairs() < 2 || std::isinf(m_store->slope()) || std::isnan(m_store->slope())) { + if (m_store->isEmpty()) { return true; } - return false; + for (int series = 0; series < FloatPairStore::k_numberOfSeries; series++) { + if (!m_store->seriesIsEmpty(series) && !std::isinf(m_store->slope(series)) && (std::isnan(m_store->slope(series)))) { + return false; + } + } + return true; } I18n::Message GraphController::emptyMessage() { diff --git a/apps/regression/graph_view.cpp b/apps/regression/graph_view.cpp index af696bbee..ae70ba10d 100644 --- a/apps/regression/graph_view.cpp +++ b/apps/regression/graph_view.cpp @@ -9,7 +9,8 @@ GraphView::GraphView(Store * store, CurveViewCursor * cursor, BannerView * banne CurveView(store, cursor, bannerView, cursorView), m_store(store), m_xLabels{}, - m_yLabels{} + m_yLabels{}, + m_series(0) //TODO { } @@ -20,17 +21,17 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { drawAxes(ctx, rect, Axis::Vertical); drawLabels(ctx, rect, Axis::Horizontal, true); drawLabels(ctx, rect, Axis::Vertical, true); - float regressionParameters[2] = {(float)m_store->slope(), (float)m_store->yIntercept()}; + float regressionParameters[2] = {(float)m_store->slope(m_series), (float)m_store->yIntercept(m_series)}; drawCurve(ctx, rect, [](float abscissa, void * model, void * context) { float * params = (float *)model; return params[0]*abscissa+params[1]; }, regressionParameters, nullptr, Palette::YellowDark); for (int index = 0; index < m_store->numberOfPairs(); index++) { - drawDot(ctx, rect, m_store->get(0,index), m_store->get(1,index), Palette::Red); + drawDot(ctx, rect, m_store->get(m_series, 0,index), m_store->get(m_series, 1,index), Palette::Red); } - drawDot(ctx, rect, m_store->meanOfColumn(0), m_store->meanOfColumn(1), Palette::Palette::YellowDark, true); - drawDot(ctx, rect, m_store->meanOfColumn(0), m_store->meanOfColumn(1), KDColorWhite); + drawDot(ctx, rect, m_store->meanOfColumn(m_series, 0), m_store->meanOfColumn(m_series, 1), Palette::Palette::YellowDark, true); + drawDot(ctx, rect, m_store->meanOfColumn(m_series, 0), m_store->meanOfColumn(m_series, 1), KDColorWhite); } char * GraphView::label(Axis axis, int index) const { diff --git a/apps/regression/graph_view.h b/apps/regression/graph_view.h index de60b5e91..76eebff34 100644 --- a/apps/regression/graph_view.h +++ b/apps/regression/graph_view.h @@ -17,6 +17,7 @@ private: Store * m_store; char m_xLabels[k_maxNumberOfXLabels][Poincare::PrintFloat::bufferSizeForFloatsWithPrecision(Constant::ShortNumberOfSignificantDigits)]; char m_yLabels[k_maxNumberOfYLabels][Poincare::PrintFloat::bufferSizeForFloatsWithPrecision(Constant::ShortNumberOfSignificantDigits)]; + int m_series; }; } diff --git a/apps/regression/store.cpp b/apps/regression/store.cpp index 8e9de77d3..7cfb5adbe 100644 --- a/apps/regression/store.cpp +++ b/apps/regression/store.cpp @@ -8,6 +8,9 @@ using namespace Shared; namespace Regression { +static inline float max(float x, float y) { return (x>y ? x : y); } +static inline float min(float x, float y) { return (x= 0) == (direction > 0))) { - // Handle edge case: if 2 dots have the same abscissa but different ordinates - if (nextX != m_data[series][0][index] || ((nextY - m_data[series][1][index] >= 0) == (direction > 0))) { - nextX = m_data[series][0][index]; - nextY = m_data[series][1][index]; - selectedDot = index; + for (int series = 0; series < k_numberOfSeries; series ++) { + if (!seriesIsEmpty(series)) { + for (int index = 0; index < numberOfPairsOfSeries(series); index++) { + if ((m_xMin <= m_data[series][0][index] && m_data[series][0][index] <= m_xMax) && + (std::fabs(m_data[series][0][index] - x) < std::fabs(nextX - x)) && + ((m_data[series][1][index] - yValueForXValue(series, m_data[series][0][index]) >= 0) == (direction > 0))) { + // Handle edge case: if 2 dots have the same abscissa but different ordinates + if (nextX != m_data[series][0][index] || ((nextY - m_data[series][1][index] >= 0) == (direction > 0))) { + nextX = m_data[series][0][index]; + nextY = m_data[series][1][index]; + selectedDot = index; + } + } } } - } - // Compare with the mean dot - double meanX = meanOfColumn(series, 0); - double meanY = meanOfColumn(series, 1); - if (m_xMin <= meanX && meanX <= m_xMax && - (std::fabs(meanX - x) < std::fabs(nextX - x)) && - ((meanY - yValueForXValue(meanX) >= 0) == (direction > 0))) { - if (nextX != meanX || ((nextY - meanY >= 0) == (direction > 0))) { - selectedDot = m_numberOfPairs[series]; + // Compare with the mean dot + double meanX = meanOfColumn(series, 0); + double meanY = meanOfColumn(series, 1); + if (m_xMin <= meanX && meanX <= m_xMax && + (std::fabs(meanX - x) < std::fabs(nextX - x)) && + ((meanY - yValueForXValue(series, meanX) >= 0) == (direction > 0))) { + if (nextX != meanX || ((nextY - meanY >= 0) == (direction > 0))) { + selectedDot = numberOfPairsOfSeries(series); + } } + } return selectedDot; } -int Store::nextDot(int direction, int dot) { +int Store::nextDot(int series, int direction, int dot) { float nextX = INFINITY; int selectedDot = -1; double meanX = meanOfColumn(series, 0); float x = meanX; - if (dot >= 0 && dot < m_numberOfPairs[series]) { - x = get(0, dot); + if (dot >= 0 && dot < numberOfPairsOfSeries(series)) { + x = get(series, 0, dot); } /* We have to scan the Store in opposite ways for the 2 directions to ensure to * select all dots (even with equal abscissa) */ if (direction > 0) { - for (int index = 0; index < m_numberOfPairs[series]; index++) { + for (int index = 0; index < numberOfPairsOfSeries(series); index++) { /* The conditions to test are in this order: * - the next dot is the closest one in abscissa to x * - the next dot is not the same as the selected one * - the next dot is at the right of the selected one */ - if (std::fabs(m_date[series][0][index] - x) < std::fabs(nextX - x) && + if (std::fabs(m_data[series][0][index] - x) < std::fabs(nextX - x) && (index != dot) && - (m_date[series][0][index] >= x)) { + (m_data[series][0][index] >= x)) { // Handle edge case: 2 dots have same abscissa - if (m_date[series][0][index] != x || (index > dot)) { - nextX = m_date[series][0][index]; + if (m_data[series][0][index] != x || (index > dot)) { + nextX = m_data[series][0][index]; selectedDot = index; } } } // Compare with the mean dot if (std::fabs(meanX - x) < std::fabs(nextX - x) && - (m_numberOfPairs[series] != dot) && + (numberOfPairsOfSeries(series) != dot) && (meanX >= x)) { if (meanX != x || (x > dot)) { - selectedDot = m_numberOfPairs[series]; + selectedDot = numberOfPairsOfSeries(series); } } } else { // Compare with the mean dot if (std::fabs(meanX - x) < std::fabs(nextX - x) && - (m_numberOfPairs[series] != dot) && + (numberOfPairsOfSeries(series) != dot) && (meanX <= x)) { - if (meanX != x || (m_numberOfPairs[series] < dot)) { + if (meanX != x || (numberOfPairsOfSeries(series) < dot)) { nextX = meanX; - selectedDot = m_numberOfPairs[series]; + selectedDot = numberOfPairsOfSeries(series); } } - for (int index = m_numberOfPairs[series]-1; index >= 0; index--) { - if (std::fabs(m_date[series][0][index] - x) < std::fabs(nextX - x) && + for (int index = numberOfPairsOfSeries(series)-1; index >= 0; index--) { + if (std::fabs(m_data[series][0][index] - x) < std::fabs(nextX - x) && (index != dot) && - (m_date[series][0][index] <= x)) { + (m_data[series][0][index] <= x)) { // Handle edge case: 2 dots have same abscissa - if (m_date[series][0][index] != x || (index < dot)) { - nextX = m_date[series][0][index]; + if (m_data[series][0][index] != x || (index < dot)) { + nextX = m_data[series][0][index]; selectedDot = index; } } @@ -111,115 +119,132 @@ int Store::nextDot(int direction, int dot) { /* Window */ -void Store::setDefault(int series) { - float min = minValueOfColumn(series, 0); - float max = maxValueOfColumn(series, 0); - float range = max - min; - setXMin(min - k_displayLeftMarginRatio*range); - setXMax(max + k_displayRightMarginRatio*range); +void Store::setDefault() { + float minX = FLT_MAX; + float maxX = -FLT_MAX; + for (int series = 0; series < k_numberOfSeries; series ++) { + if (!seriesIsEmpty(series)) { + minX = min(minX, minValueOfColumn(series, 0)); + maxX = max(maxX, maxValueOfColumn(series, 0)); + } + } + float range = maxX - minX; + setXMin(minX - k_displayLeftMarginRatio*range); + setXMax(maxX + k_displayRightMarginRatio*range); setYAuto(true); } +bool Store::isEmpty() const { + for (int i = 0; i < k_numberOfSeries; i ++) { + if (!seriesIsEmpty(i)) { + return false; + } + } + return true; +} + +bool Store::seriesIsEmpty(int series) const { + return numberOfPairsOfSeries(series) < 2; +} + /* Calculations */ -float Store::maxValueOfColumn(int series, int i) { - float max = -FLT_MAX; - for (int k = 0; k < m_numberOfPairs[series]; k++) { - if (m_data[series][i][k] > max) { - max = m_data[series][i][k]; - } - } - return max; +double Store::doubleCastedNumberOfPairsOfSeries(int series) const { + return FloatPairStore::numberOfPairsOfSeries(series); } -float Store::minValueOfColumn(int series, int i) { - float min = FLT_MAX; - for (int k = 0; k < m_numberOfPairs[series]; k++) { - if (m_data[series][i][k] < min) { - min = m_data[series][i][k]; - } +float Store::maxValueOfColumn(int series, int i) const { + float maxColumn = -FLT_MAX; + for (int k = 0; k < numberOfPairsOfSeries(series); k++) { + maxColumn = max(maxColumn, m_data[series][i][k]); } - return min; + return maxColumn; } -double Store::squaredValueSumOfColumn(int series, int i) { +float Store::minValueOfColumn(int series, int i) const { + float minColumn = FLT_MAX; + for (int k = 0; k < numberOfPairsOfSeries(series); k++) { + minColumn = min(minColumn, m_data[series][i][k]); + } + return minColumn; +} + +double Store::squaredValueSumOfColumn(int series, int i) const { double result = 0; - for (int k = 0; k < m_numberOfPairs[series]; k++) { + for (int k = 0; k < numberOfPairsOfSeries(series); k++) { result += m_data[series][i][k]*m_data[series][i][k]; } return result; } -double Store::columnProductSum(int series) { +double Store::columnProductSum(int series) const { double result = 0; - for (int k = 0; k < m_numberOfPairs[series]; k++) { - result += m_date[series][0][k]*m_date[series][1][k]; + for (int k = 0; k < numberOfPairsOfSeries(series); k++) { + result += m_data[series][0][k]*m_data[series][1][k]; } return result; } -double Store::meanOfColumn(int series, int i) { - return m_numberOfPairs[series] == 0 ? 0 : sumOfColumn(series, i)/m_numberOfPairs[series]; +double Store::meanOfColumn(int series, int i) const { + return numberOfPairsOfSeries(series) == 0 ? 0 : sumOfColumn(series, i)/numberOfPairsOfSeries(series); } -double Store::varianceOfColumn(int series, int i) { +double Store::varianceOfColumn(int series, int i) const { double mean = meanOfColumn(series, i); - return squaredValueSumOfColumn(series, i)/m_numberOfPairs[series] - mean*mean; + return squaredValueSumOfColumn(series, i)/numberOfPairsOfSeries(series) - mean*mean; } -double Store::standardDeviationOfColumn(int series, int i) { +double Store::standardDeviationOfColumn(int series, int i) const { return std::sqrt(varianceOfColumn(series, i)); } -double Store::covariance(int series) { - return columnProductSum(series)/m_numberOfPairs[series] - meanOfColumn(series, 0)*meanOfColumn(series, 1); +double Store::covariance(int series) const { + return columnProductSum(series)/numberOfPairsOfSeries(series) - meanOfColumn(series, 0)*meanOfColumn(series, 1); } -double Store::slope(int series) { +double Store::slope(int series) const { return covariance(series)/varianceOfColumn(series, 0); } -double Store::yIntercept(int series) { +double Store::yIntercept(int series) const { return meanOfColumn(series, 1) - slope(series)*meanOfColumn(series, 0); } -double Store::yValueForXValue(int series, double x) { +double Store::yValueForXValue(int series, double x) const { return slope(series)*x+yIntercept(series); } -double Store::xValueForYValue(int series, double y) { +double Store::xValueForYValue(int series, double y) const { return std::fabs(slope(series)) < DBL_EPSILON ? NAN : (y - yIntercept(series))/slope(series); } -double Store::correlationCoefficient(int series) { +double Store::correlationCoefficient(int series) const { double sd0 = standardDeviationOfColumn(series, 0); double sd1 = standardDeviationOfColumn(series, 1); return (sd0 == 0.0 || sd1 == 0.0) ? 1.0 : covariance(series)/(sd0*sd1); } -double Store::squaredCorrelationCoefficient(int series) { +double Store::squaredCorrelationCoefficient(int series) const { double cov = covariance(series); double v0 = varianceOfColumn(series, 0); double v1 = varianceOfColumn(series, 1); return (v0 == 0.0 || v1 == 0.0) ? 1.0 : cov*cov/(v0*v1); } -InteractiveCurveViewRangeDelegate::Range Store::computeYRange(int series, InteractiveCurveViewRange * interactiveCurveViewRange) { - float min = FLT_MAX; - float max = -FLT_MAX; - for (int k = 0; k < m_numberOfPairs[series]; k++) { - if (m_xMin <= m_date[series][0][k] && m_date[series][0][k] <= m_xMax) { - if (m_date[series][1][k] < min) { - min = m_date[series][1][k]; - } - if (m_date[series][1][k] > max) { - max = m_date[series][1][k]; +InteractiveCurveViewRangeDelegate::Range Store::computeYRange(InteractiveCurveViewRange * interactiveCurveViewRange) { + float minY = FLT_MAX; + float maxY = -FLT_MAX; + for (int series = 0; series < k_numberOfSeries; series++) { + for (int k = 0; k < numberOfPairsOfSeries(series); k++) { + if (m_xMin <= m_data[series][0][k] && m_data[series][0][k] <= m_xMax) { + minY = min(minY, m_data[series][1][k]); + maxY = max(maxY, m_data[series][1][k]); } } } InteractiveCurveViewRangeDelegate::Range range; - range.min = min; - range.max = max; + range.min = minY; + range.max = maxY; return range; } diff --git a/apps/regression/store.h b/apps/regression/store.h index 59dd10fdc..25a5b41e9 100644 --- a/apps/regression/store.h +++ b/apps/regression/store.h @@ -9,43 +9,48 @@ namespace Regression { class Store : public Shared::InteractiveCurveViewRange, public Shared::FloatPairStore, public Shared::InteractiveCurveViewRangeDelegate { public: Store(); + // Dots - /* Return the closest dot to x above the regression curve if direction > 0, - * below otherwise*/ - int closestVerticalDot(int series, int direction, float x); - /* Return the closest dot to dot given on the right if direction > 0, - * on the left otherwise*/ + /* Return the closest dot to abscissa x above the regression curve if + * direction > 0, below otherwise */ + int closestVerticalDot(int direction, float x); + /* Return the closest dot to given dot, on the right if direction > 0, + * on the left otherwise */ int nextDot(int series, int direction, int dot); // Window void setDefault() override; + // Series + bool isEmpty() const; + bool seriesIsEmpty(int series) const; + // Calculation - double numberOfPairs(int series) const { return m_numberOfPairs; } - double squaredValueSumOfColumn(int series, int i); - double columnProductSum(int series); - double meanOfColumn(int series, int i); - double varianceOfColumn(int series, int i); - double standardDeviationOfColumn(int series, int i); - double covariance(int series); - double slope(int series); - double yIntercept(int series); - double yValueForXValue(int series, double x); - double xValueForYValue(int series, double y); - double correlationCoefficient(int series); - double squaredCorrelationCoefficient(int series); + double doubleCastedNumberOfPairsOfSeries(int series) const; + double squaredValueSumOfColumn(int series, int i) const; + double columnProductSum(int series) const; + double meanOfColumn(int series, int i) const; + double varianceOfColumn(int series, int i) const; + double standardDeviationOfColumn(int series, int i) const; + double covariance(int series) const; + double slope(int series) const; + double yIntercept(int series) const; + double yValueForXValue(int series, double x) const; + double xValueForYValue(int series, double y) const; + double correlationCoefficient(int series) const; + double squaredCorrelationCoefficient(int series) const; private: constexpr static float k_displayTopMarginRatio = 0.12f; constexpr static float k_displayRightMarginRatio = 0.05f; constexpr static float k_displayBottomMarginRatio = 0.5f; constexpr static float k_displayLeftMarginRatio = 0.05f; - InteractiveCurveViewRangeDelegate::Range computeYRange(int series, InteractiveCurveViewRange * interactiveCurveViewRange) override; + InteractiveCurveViewRangeDelegate::Range computeYRange(InteractiveCurveViewRange * interactiveCurveViewRange) override; float addMargin(float x, float range, bool isMin) override; - float maxValueOfColumn(int series, int i); - float minValueOfColumn(int series, int i); + float maxValueOfColumn(int series, int i) const; + float minValueOfColumn(int series, int i) const; }; -typedef double (Store::*ArgCalculPointer)(int); +typedef double (Store::*ArgCalculPointer)(int, int) const; typedef double (Store::*CalculPointer)(); typedef void (Store::*RangeMethodPointer)();