lsq_solver.dart 5.24 KB
Newer Older
1 2 3 4
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6 7
import "dart:math" as math;
import "dart:typed_data";

8 9 10
class _Vector {
  _Vector(int size)
  : _offset = 0, _length = size, _elements = new Float64List(size);
11

12 13
  _Vector.fromValues(List<double> values)
  : _offset = 0, _length = values.length, _elements = values;
14

15 16
  _Vector.fromVOL(List<double> values, int offset, int length)
  : _offset = offset, _length = length, _elements = values;
17

Ian Hickson's avatar
Ian Hickson committed
18 19
  final int _offset;

20
  int get length => _length;
Ian Hickson's avatar
Ian Hickson committed
21 22 23
  final int _length;

  final List<double> _elements;
24

Ian Hickson's avatar
Ian Hickson committed
25 26 27 28
  double operator [](int i) => _elements[i + _offset];
  void operator []=(int i, double value) {
    _elements[i + _offset] = value;
  }
29

Ian Hickson's avatar
Ian Hickson committed
30
  double operator *(_Vector a) {
31
    double result = 0.0;
Ian Hickson's avatar
Ian Hickson committed
32
    for (int i = 0; i < _length; i += 1)
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
      result += this[i] * a[i];
    return result;
  }

  double norm() => math.sqrt(this * this);

  String toString() {
    String result = "";
    for (int i = 0; i < _length; i++) {
      if (i > 0)
        result += ", ";
        result += this[i].toString();
    }
    return result;
  }
}

50 51
class _Matrix {
  _Matrix(int rows, int cols)
52
  : _rows = rows,
53 54
    _columns = cols,
    _elements = new Float64List(rows * cols);
55

Ian Hickson's avatar
Ian Hickson committed
56 57 58 59
  final int _rows;
  final int _columns;
  final List<double> _elements;

60
  double get(int row, int col) => _elements[row * _columns + col];
61
  void set(int row, int col, double value) {
62
    _elements[row * _columns + col] = value;
63 64
  }

65 66 67 68 69
  _Vector getRow(int row) => new _Vector.fromVOL(
    _elements,
    row * _columns,
    _columns
  );
70 71 72 73 74 75

  String toString() {
    String result = "";
    for (int i = 0; i < _rows; i++) {
      if (i > 0)
        result += "; ";
76
      for (int j = 0; j < _columns; j++) {
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
        if (j > 0)
          result += ", ";
        result += get(i, j).toString();
      }
    }
    return result;
  }
}

class PolynomialFit {
  PolynomialFit(int degree) : coefficients = new Float64List(degree + 1);

  final List<double> coefficients;
  double confidence;
}

class LeastSquaresSolver {
  LeastSquaresSolver(this.x, this.y, this.w) {
    assert(x.length == y.length);
    assert(y.length == w.length);
  }

  final List<double> x;
  final List<double> y;
  final List<double> w;

  PolynomialFit solve(int degree) {
Florian Loitsch's avatar
Florian Loitsch committed
104
    if (degree > x.length) // Not enough data to fit a curve.
105 106 107 108
      return null;

    PolynomialFit result = new PolynomialFit(degree);

Florian Loitsch's avatar
Florian Loitsch committed
109
    // Shorthands for the purpose of notation equivalence to original C++ code.
110 111 112 113
    final int m = x.length;
    final int n = degree + 1;

    // Expand the X vector to a matrix A, pre-multiplied by the weights.
114
    _Matrix a = new _Matrix(n, m);
Ian Hickson's avatar
Ian Hickson committed
115
    for (int h = 0; h < m; h += 1) {
116
      a.set(0, h, w[h]);
Ian Hickson's avatar
Ian Hickson committed
117
      for (int i = 1; i < n; i += 1)
118 119 120 121 122 123
        a.set(i, h, a.get(i - 1, h) * x[h]);
    }

    // Apply the Gram-Schmidt process to A to obtain its QR decomposition.

    // Orthonormal basis, column-major ordVectorer.
124
    _Matrix q = new _Matrix(n, m);
125
    // Upper triangular matrix, row-major order.
126
    _Matrix r = new _Matrix(n, n);
Ian Hickson's avatar
Ian Hickson committed
127 128
    for (int j = 0; j < n; j += 1) {
      for (int h = 0; h < m; h += 1)
129
        q.set(j, h, a.get(j, h));
Ian Hickson's avatar
Ian Hickson committed
130
      for (int i = 0; i < j; i += 1) {
131
        double dot = q.getRow(j) * q.getRow(i);
Ian Hickson's avatar
Ian Hickson committed
132
        for (int h = 0; h < m; h += 1)
133 134 135 136 137
          q.set(j, h, q.get(j, h) - dot * q.get(i, h));
      }

      double norm = q.getRow(j).norm();
      if (norm < 0.000001) {
Florian Loitsch's avatar
Florian Loitsch committed
138
        // Vectors are linearly dependent or zero so no solution.
139 140 141
        return null;
      }

142
      double inverseNorm = 1.0 / norm;
Ian Hickson's avatar
Ian Hickson committed
143
      for (int h = 0; h < m; h += 1)
144
        q.set(j, h, q.get(j, h) * inverseNorm);
Ian Hickson's avatar
Ian Hickson committed
145
      for (int i = 0; i < n; i += 1)
146
        r.set(j, i, i < j ? 0.0 : q.getRow(j) * a.getRow(i));
147 148 149 150
    }

    // Solve R B = Qt W Y to find B.  This is easy because R is upper triangular.
    // We just work from bottom-right to top-left calculating B's coefficients.
151
    _Vector wy = new _Vector(m);
Ian Hickson's avatar
Ian Hickson committed
152
    for (int h = 0; h < m; h += 1)
153
      wy[h] = y[h] * w[h];
Ian Hickson's avatar
Ian Hickson committed
154
    for (int i = n - 1; i >= 0; i -= 1) {
155
      result.coefficients[i] = q.getRow(i) * wy;
Ian Hickson's avatar
Ian Hickson committed
156
      for (int j = n - 1; j > i; j -= 1)
157 158
        result.coefficients[i] -= r.get(i, j) * result.coefficients[j];
      result.coefficients[i] /= r.get(i, i);
159 160
    }

161
    // Calculate the coefficient of determination (confidence) as:
Ian Hickson's avatar
Ian Hickson committed
162 163
    //   1 - (sumSquaredError / sumSquaredTotal)
    // ...where sumSquaredError is the residual sum of squares (variance of the
164 165 166
    // error), and sumSquaredTotal is the total sum of squares (variance of the
    // data) where each has been weighted.
    double yMean = 0.0;
Ian Hickson's avatar
Ian Hickson committed
167
    for (int h = 0; h < m; h += 1)
168 169
      yMean += y[h];
    yMean /= m;
170

171 172
    double sumSquaredError = 0.0;
    double sumSquaredTotal = 0.0;
Ian Hickson's avatar
Ian Hickson committed
173
    for (int h = 0; h < m; h += 1) {
174
      double term = 1.0;
Ian Hickson's avatar
Ian Hickson committed
175 176
      double err = y[h] - result.coefficients[0];
      for (int i = 1; i < n; i += 1) {
177
        term *= x[h];
178
        err -= term * result.coefficients[i];
179
      }
180
      sumSquaredError += w[h] * w[h] * err * err;
Ian Hickson's avatar
Ian Hickson committed
181
      final double v = y[h] - yMean;
182
      sumSquaredTotal += w[h] * w[h] * v * v;
183 184
    }

Ian Hickson's avatar
Ian Hickson committed
185 186
    result.confidence = sumSquaredTotal <= 0.000001 ? 1.0 :
                          1.0 - (sumSquaredError / sumSquaredTotal);
187 188 189 190 191

    return result;
  }

}