Learn-to-Compress / headers / polynomial_regression_internals.hpp
polynomial_regression_internals.hpp
Raw
#ifndef _POLYNOMIAL_REGRESSION_ABC_INTERNALS_H
#define _POLYNOMIAL_REGRESSION_ABC_INTERNALS_H  __POLYNOMIAL_REGRESSION_ABC_INTERNALS_H

/**
 * PURPOSE:
 *
 *  Polynomial Regression aims to fit a non-linear relationship to a set of
 *  points. It approximates this by solving a series of linear equations using
 *  a least-squares approach.
 *
 *  We can model the expected value y as an nth degree polynomial, yielding
 *  the general polynomial regression model:
 *
 *  y = a0 + a1 * x + a2 * x^2 + ... + an * x^n
 *
 * LICENSE:
 *
 * MIT License
 *
 * Copyright (c) 2020 Chris Engelsma, Audrius Meskauskas
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * @author Chris Engelsma (initial version, all algorithm)
 * @author Audrius Meskauskas (later changes starting from August 19, 2020)
 */

#include <vector>
#include <cstdlib>
#include <stdexcept>
#include <cmath>
#include <cassert>
#include <array>
#include <string.h>

namespace andviane {

// Build the matrix of X values raised in degree.
  template<int till_degree, typename PRECISION, typename ITERATOR_X>
  static void build_x_matrix(ITERATOR_X x_iter, size_t N, std::array<std::vector<PRECISION>, till_degree> &x_raised) {
    for (int degree = 0; degree < till_degree; degree++) {
      switch (degree) {
        case 0:
          x_raised[degree] = std::vector<PRECISION>(N, 1); // x^0
          break;
        case 1:
          x_raised[degree] = std::vector<PRECISION>(N);   // x^1
          for (int ix = 0; ix < N; ix++) {
            x_raised[degree][ix] = (PRECISION) *x_iter;
            ++x_iter;
          }
          break;
        default:
          x_raised[degree] = std::vector<PRECISION>(N);   // x^degree
          for (int ix = 0; ix < N; ix++)
            x_raised[degree][ix] = x_raised[degree - 1][ix] * x_raised[1][ix];
          break;
      }
    }
  }

// Build the fixed (enumerating) matrix of X values raised in degree.
  template<int till_degree, typename PRECISION>
  static void build_x_matrix(size_t N, std::array<std::vector<PRECISION>, till_degree> &x_raised) {
    for (int degree = 0; degree < till_degree; degree++) {
      switch (degree) {
        case 0:
          x_raised[degree] = std::vector<PRECISION>(N, 1); // x^0
          break;
        case 1:
          x_raised[degree] = std::vector<PRECISION>(N);   // x^1
          for (int ix = 0; ix < N; ix++) {
            x_raised[degree][ix] = (PRECISION) ix;
          }
          break;
        default:
          x_raised[degree] = std::vector<PRECISION>(N);   // x^degree
          for (int ix = 0; ix < N; ix++)
            x_raised[degree][ix] = x_raised[degree - 1][ix] * x_raised[1][ix];
          break;
      }
    }
  }

// Main algorithm of polynomial regression
  template<int n, typename TYPE=double, typename PRECISION=TYPE, typename ITERATOR_Y>
  Polynomial <n, TYPE, PRECISION> polynomial_regression_iter(const std::array<std::vector<PRECISION>, 2 * n + 1> &x_raised,
                                                        ITERATOR_Y y_iter,
                                                        bool compute_residual, size_t N) {
    constexpr int np1 = n + 1;
    constexpr int np2 = n + 2;
    constexpr int tnp1 = 2 * n + 1;

    // X = vector that stores values of sigma(xi^2n)
    PRECISION X[tnp1];
    for (int i = 0; i < tnp1; ++i) {
      X[i] = 0;
      for (int j = 0; j < N; ++j)
        X[i] += x_raised[i][j];
    }

    // a = vector to store final coefficients.
    Polynomial<n, TYPE, PRECISION> a(N);

    // B = normal augmented matrix that stores the equations.
    PRECISION B[np1][np2];
    memset(&B, 0, sizeof(B));

    for (int i = 0; i <= n; ++i)
      for (int j = 0; j <= n; ++j)
        B[i][j] = X[i + j];

    // Y = vector to store values of sigma(xi^n * yi)
    PRECISION Y[np1];
    for (int i = 0; i < np1; ++i) {
      Y[i] = 0;
      ITERATOR_Y y_iter_iter = y_iter;
      for (int j = 0; j < N; ++j) {
        Y[i] += x_raised[i][j] * (*y_iter_iter++);
      }
    }

    // Load values of Y as last column of B
    for (int i = 0; i <= n; ++i)
      B[i][np1] = Y[i];

    // Pivotisation of the B matrix.
    for (int i = 0; i < np1; ++i)
      for (int k = i + 1; k < np1; ++k)
        if (B[i][i] < B[k][i])
          for (int j = 0; j <= np1; ++j) {
            std::swap(B[i][j], B[k][j]);
          }

    // Performs the Gaussian elimination.
    // (1) Make all elements below the pivot equals to zero
    //     or eliminate the variable.
    for (int i = 0; i < n; ++i)
      for (int k = i + 1; k < np1; ++k) {
        PRECISION t = B[k][i] / B[i][i];
        for (int j = 0; j <= np1; ++j)
          B[k][j] -= t * B[i][j];         // (1)
      }

    // Back substitution.
    // (1) Set the variable as the rhs of last equation
    // (2) Subtract all lhs values except the target coefficient.
    // (3) Divide rhs by coefficient of variable being calculated.
    for (int i = n; i >= 0; --i) {
      a[i] = B[i][np1];                 // (1)
      for (int j = 0; j < np1; ++j)
        if (j != i)
          a[i] -= B[i][j] * a[j];       // (2)
      a[i] /= B[i][i];                  // (3)
    }

    if (compute_residual) {
      PRECISION r = 0;
      ITERATOR_Y y_iter_iter = y_iter;
      for (int i = 0; i < N; i++) {
        PRECISION x = x_raised[1][i];
        PRECISION diff = a(x) - (*y_iter_iter++);
        r = r + diff * diff;
      }
      a.residual(r);
    }
    return a;
  }
}
#endif