From 88499d557669842c0324813d43356ba59e81f30a Mon Sep 17 00:00:00 2001 From: Will Charczuk Date: Mon, 17 Apr 2017 10:27:24 -0700 Subject: [PATCH] updates --- linear_regression_series.go | 15 +- matrix/matrix.go | 319 ++++++++++++++++++++++++++++++++ matrix/util.go | 27 +++ polynomial_regression_series.go | 90 +++++++++ 4 files changed, 443 insertions(+), 8 deletions(-) create mode 100644 matrix/matrix.go create mode 100644 matrix/util.go create mode 100644 polynomial_regression_series.go diff --git a/linear_regression_series.go b/linear_regression_series.go index 142ea55..f1e3bc5 100644 --- a/linear_regression_series.go +++ b/linear_regression_series.go @@ -9,7 +9,7 @@ type LinearRegressionSeries struct { Style Style YAxis YAxisType - Window int + Limit int Offset int InnerSeries ValueProvider @@ -36,18 +36,18 @@ func (lrs LinearRegressionSeries) GetYAxis() YAxisType { // Len returns the number of elements in the series. func (lrs LinearRegressionSeries) Len() int { - return Math.MinInt(lrs.GetWindow(), lrs.InnerSeries.Len()-lrs.GetOffset()) + return Math.MinInt(lrs.GetLimit(), lrs.InnerSeries.Len()-lrs.GetOffset()) } -// GetWindow returns the window size. -func (lrs LinearRegressionSeries) GetWindow() int { - if lrs.Window == 0 { +// GetLimit returns the window size. +func (lrs LinearRegressionSeries) GetLimit() int { + if lrs.Limit == 0 { return lrs.InnerSeries.Len() } - return lrs.Window + return lrs.Limit } -// GetEndIndex returns the effective window end. +// GetEndIndex returns the effective limit end. func (lrs LinearRegressionSeries) GetEndIndex() int { offset := lrs.GetOffset() + lrs.Len() innerSeriesLastIndex := lrs.InnerSeries.Len() - 1 @@ -105,7 +105,6 @@ func (lrs *LinearRegressionSeries) computeCoefficients() { xvalues := NewRingBufferWithCapacity(lrs.Len()) for index := startIndex; index < endIndex; index++ { - x, _ := lrs.InnerSeries.GetValue(index) xvalues.Enqueue(x) } diff --git a/matrix/matrix.go b/matrix/matrix.go new file mode 100644 index 0000000..837402b --- /dev/null +++ b/matrix/matrix.go @@ -0,0 +1,319 @@ +package matrix + +import ( + "bytes" + "errors" + "fmt" + "math" +) + +const ( + // DefaultEpsilon represents the minimum precision for matrix math operations. + DefaultEpsilon = 0.000001 +) + +var ( + // ErrDimensionMismatch is a typical error. + ErrDimensionMismatch = errors.New("matrix is not square, cannot invert") +) + +// New returns a new matrix. +func New(rows, cols int) *Matrix { + return &Matrix{ + rows: rows, + cols: cols, + epsilon: DefaultEpsilon, + elements: make([]float64, rows*cols), + } +} + +// Identity returns the identity matrix of a given order. +func Identity(order int) *Matrix { + m := New(order, order) + for i := 0; i < order; i++ { + m.Set(i, i, 1) + } + return m +} + +// Zeros returns a matrix of a given size zeroed. +func Zeros(rows, cols int) *Matrix { + return New(rows, cols) +} + +// Ones returns an matrix of ones. +func Ones(rows, cols int) *Matrix { + ones := make([]float64, rows*cols) + for i := 0; i < (rows * cols); i++ { + ones[i] = 1 + } + + return &Matrix{ + rows: rows, + cols: cols, + epsilon: DefaultEpsilon, + elements: ones, + } +} + +// NewFromArrays creates a matrix from a jagged array set. +func NewFromArrays(a [][]float64) *Matrix { + rows := len(a) + if rows == 0 { + return nil + } + cols := len(a[0]) + m := New(rows, cols) + for row := 0; row < rows; row++ { + for col := 0; col < cols; col++ { + m.Set(row, col, a[row][col]) + } + } + return m +} + +// Vector is just an array of values. +type Vector []float64 + +// Matrix represents a 2d dense array of floats. +type Matrix struct { + epsilon float64 + elements []float64 + rows, cols int +} + +// Arrays returns the matrix as a two dimensional jagged array. +func (m *Matrix) Arrays() [][]float64 { + a := make([][]float64, m.rows, m.cols) + + for row := 0; row < m.rows; row++ { + for col := 0; col < m.cols; col++ { + a[row][col] = m.Get(row, col) + } + } + return a +} + +// Size returns the dimensions of the matrix. +func (m *Matrix) Size() (rows, cols int) { + rows = m.rows + cols = m.cols + return +} + +// IsSquare returns if the row count is equal to the column count. +func (m *Matrix) IsSquare() bool { + return m.rows == m.cols +} + +// IsSymmetric returns if the matrix is symmetric about its diagonal. +func (m *Matrix) IsSymmetric() bool { + if m.rows != m.cols { + return false + } + for i := 0; i < m.rows; i++ { + for j := 0; j < i; j++ { + if m.Get(i, j) != m.Get(j, i) { + return false + } + } + } + return true +} + +// Get returns the element at the given row, col. +func (m *Matrix) Get(row, col int) float64 { + index := (m.cols * row) + col + return m.elements[index] +} + +// Set sets a value. +func (m *Matrix) Set(row, col int, val float64) { + index := (m.cols * row) + col + m.elements[index] = val +} + +// Col returns a column of the matrix as a vector. +func (m *Matrix) Col(col int) Vector { + values := make([]float64, m.rows) + for row := 0; row < m.rows; row++ { + values[col] = m.Get(row, col) + } + return Vector(values) +} + +// Row returns a row of the matrix as a vector. +func (m *Matrix) Row(row int) Vector { + values := make([]float64, m.cols) + for col := 0; col < m.cols; col++ { + values[col] = m.Get(row, col) + } + return Vector(values) +} + +// Copy returns a duplicate of a given matrix. +func (m *Matrix) Copy() *Matrix { + m2 := New(m.rows, m.cols) + for row := 0; row < m.rows; row++ { + for col := 0; col < m.cols; col++ { + m2.Set(row, col, m.Get(row, col)) + } + } + return m2 +} + +// DiagonalVector returns a vector from the diagonal of a matrix. +func (m *Matrix) DiagonalVector() Vector { + rank := minInt(m.rows, m.cols) + values := make([]float64, rank) + + for index := 0; index < rank; index++ { + values[index] = m.Get(index, index) + } + return Vector(values) +} + +// Equals returns if a matrix equals another matrix. +func (m *Matrix) Equals(other *Matrix) bool { + if other == nil && m != nil { + return false + } else if other == nil { + return true + } + + if otherRows, otherCols := other.Size(); otherRows != m.rows || otherCols != m.cols { + return false + } + + for row := 0; row < m.rows; row++ { + for col := 0; col < m.cols; col++ { + if m.Get(row, col) != other.Get(row, col) { + return false + } + + } + } + return true +} + +// L returns the matrix with zeros below the diagonal. +func (m *Matrix) L() *Matrix { + m2 := New(m.rows, m.cols) + for row := 0; row < m.rows; row++ { + for col := row; col < m.cols; col++ { + m2.Set(row, col, m.Get(row, col)) + } + } + return m2 +} + +// U returns the matrix with zeros above the diagonal. +func (m *Matrix) U() *Matrix { + m2 := New(m.rows, m.cols) + for row := 0; row < m.rows; row++ { + for col := 0; col < row && col < m.cols; col++ { + m2.Set(row, col, m.Get(row, col)) + } + } + return m2 +} + +// Diagonal returns a matrix from the diagonal of a matrix. +func (m *Matrix) Diagonal() *Matrix { + rank := minInt(m.rows, m.cols) + m2 := New(rank, rank) + + for index := 0; index < rank; index++ { + m2.Set(index, index, m.Get(index, index)) + } + return m2 +} + +// String returns a string representation of the matrix. +func (m *Matrix) String() string { + buffer := bytes.NewBuffer(nil) + for row := 0; row < m.rows; row++ { + for col := 0; col < m.cols; col++ { + buffer.WriteString(fmt.Sprintf("%f", m.Get(row, col))) + buffer.WriteRune(' ') + } + buffer.WriteRune('\n') + } + return buffer.String() +} + +// Decompositions + +// LU returns the LU decomposition of a matrix. +func (m *Matrix) LU() (l, u, p *Matrix) { + return +} + +// QR performs the qr decomposition. +func (m *Matrix) QR() (q, r *Matrix) { + rows, cols := m.Size() + qr := m.Copy() + q = New(rows, cols) + r = New(rows, cols) + + var i, j, k int + var norm, s float64 + + for k = 0; k < cols; k++ { + norm = 0 + for i = k; i < rows; i++ { + norm = math.Hypot(norm, qr.Get(i, k)) + } + + if norm != 0 { + if qr.Get(k, k) < 0 { + norm = -norm + } + + for i = k; i < rows; i++ { + qr.Set(i, k, qr.Get(i, k)/norm) + } + qr.Set(k, k, qr.Get(k, k)+1.0) + + for j = k + 1; j < cols; j++ { + s = 0 + for i = k; i < rows; i++ { + s += qr.Get(i, k) * qr.Get(i, j) + } + s = -s / qr.Get(k, k) + for i = k; i < rows; i++ { + qr.Set(i, j, qr.Get(i, j)+s*qr.Get(i, k)) + + if i < j { + r.Set(i, j, qr.Get(i, j)) + } + } + + } + } + + r.Set(k, k, -norm) + + } + + //Q Matrix: + i, j, k = 0, 0, 0 + + for k = cols - 1; k >= 0; k-- { + q.Set(k, k, 1.0) + for j = k; j < cols; j++ { + if qr.Get(k, k) != 0 { + s = 0 + for i = k; i < rows; i++ { + s += qr.Get(i, k) * q.Get(i, j) + } + s = -s / qr.Get(k, k) + for i = k; i < rows; i++ { + q.Set(i, j, q.Get(i, j)+s*qr.Get(i, k)) + } + } + } + } + + return +} diff --git a/matrix/util.go b/matrix/util.go new file mode 100644 index 0000000..be851f2 --- /dev/null +++ b/matrix/util.go @@ -0,0 +1,27 @@ +package matrix + +import ( + "math" +) + +func minInt(values ...int) int { + min := math.MaxInt32 + + for x := 0; x < len(values); x++ { + if values[x] < min { + min = values[x] + } + } + return min +} + +func maxInt(values ...int) int { + max := math.MinInt32 + + for x := 0; x < len(values); x++ { + if values[x] > max { + max = values[x] + } + } + return max +} diff --git a/polynomial_regression_series.go b/polynomial_regression_series.go new file mode 100644 index 0000000..d01c4cc --- /dev/null +++ b/polynomial_regression_series.go @@ -0,0 +1,90 @@ +package chart + +import "fmt" + +// PolynomialRegressionSeries implements a polynomial regression over a given +// inner series. +type PolynomialRegressionSeries struct { + Name string + Style Style + YAxis YAxisType + + Limit int + Offset int + Order int + InnerSeries ValueProvider + + coeffs []float64 +} + +// GetName returns the name of the time series. +func (prs PolynomialRegressionSeries) GetName() string { + return prs.Name +} + +// GetStyle returns the line style. +func (prs PolynomialRegressionSeries) GetStyle() Style { + return prs.Style +} + +// GetYAxis returns which YAxis the series draws on. +func (prs PolynomialRegressionSeries) GetYAxis() YAxisType { + return prs.YAxis +} + +// Len returns the number of elements in the series. +func (prs PolynomialRegressionSeries) Len() int { + return Math.MinInt(prs.GetLimit(), prs.InnerSeries.Len()-prs.GetOffset()) +} + +// GetLimit returns the window size. +func (prs PolynomialRegressionSeries) GetLimit() int { + if prs.Limit == 0 { + return prs.InnerSeries.Len() + } + return prs.Limit +} + +// GetEndIndex returns the effective limit end. +func (prs PolynomialRegressionSeries) GetEndIndex() int { + offset := prs.GetOffset() + prs.Len() + innerSeriesLastIndex := prs.InnerSeries.Len() - 1 + return Math.MinInt(offset, innerSeriesLastIndex) +} + +// GetOffset returns the data offset. +func (prs PolynomialRegressionSeries) GetOffset() int { + if prs.Offset == 0 { + return 0 + } + return prs.Offset +} + +// Validate validates the series. +func (prs *PolynomialRegressionSeries) Validate() error { + if prs.InnerSeries == nil { + return fmt.Errorf("linear regression series requires InnerSeries to be set") + } + return nil +} + +// GetValue returns the series value for a given index. +func (prs *PolynomialRegressionSeries) GetValue(index int) (x, y float64) { + if prs.InnerSeries == nil || prs.InnerSeries.Len() == 0 { + return + } + return +} + +func (prs *PolynomialRegressionSeries) computeCoefficients() { + vandMatrix := make([][]float64, prs.Len(), prs.Order+1) + var xvalue float64 + for i := 0; i < prs.Len(); i++ { + _, xvalue = prs.InnerSeries.GetValue(i) + var mult float64 = 1.0 + for j := 0; j < prs.Order+1; j++ { + vandMatrix[i][j] = mult + mult = mult * xvalue + } + } +}