/
matrix.go
180 lines (163 loc) · 6.01 KB
/
matrix.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
package matrix
import (
"fmt"
"math/rand"
"gonum.org/v1/gonum/mat"
"gonum.org/v1/gonum/stat"
)
// ColsMax returns a slice of max values of first cols number of matrix columns
// It returns error if passed in matrix is nil, has zero size or requested number
// of columns exceeds the number of columns in the matrix passed in as parameter.
func ColsMax(cols int, m *mat.Dense) ([]float64, error) {
return withValidDim("cols", cols, m, mat.Max)
}
// ColsMin returns a slice of min values of first cols number of matrix columns
// It returns error if passed in matrix is nil, has zero size or requested number
// of columns exceeds the number of columns in the matrix passed in as parameter.
func ColsMin(cols int, m *mat.Dense) ([]float64, error) {
return withValidDim("cols", cols, m, mat.Min)
}
// ColsMean returns a slice of mean values of first cols matrix columns
// It returns error if passed in matrix is nil or has zero size or requested number
// of columns exceeds the number of columns in matrix m.
func ColsMean(cols int, m *mat.Dense) ([]float64, error) {
return withValidDim("cols", cols, m, mean)
}
// ColsStdev returns a slice of standard deviations of first cols matrix columns
// It returns error if passed in matrix is nil or has zero size or requested number
// of columns exceeds the number of columns in matrix m.
func ColsStdev(cols int, m *mat.Dense) ([]float64, error) {
return withValidDim("cols", cols, m, stdev)
}
// RowsMax returns a slice of max values of first rows matrix rows.
// It returns error if passed in matrix is nil or has zero size or requested number
// of rows exceeds the number of rows in matrix m.
func RowsMax(rows int, m *mat.Dense) ([]float64, error) {
return withValidDim("rows", rows, m, mat.Max)
}
// RowsMin returns a slice of min values of first rows matrix rows.
// It returns error if passed in matrix is nil or has zero size or requested number
// of rows exceeds the number of rows in matrix m.
func RowsMin(rows int, m *mat.Dense) ([]float64, error) {
return withValidDim("rows", rows, m, mat.Min)
}
// MakeRandom creates a new matrix with provided number of rows and columns
// which is initialized to random numbers uniformly distributed in interval [min, max].
// MakeRandom fails if non-positive matrix dimensions are requested.
func MakeRandom(rows, cols int, min, max float64) (*mat.Dense, error) {
return withValidDims(rows, cols, func() (*mat.Dense, error) {
// set random seed
rand.Seed(55)
// allocate data slice
randVals := make([]float64, rows*cols)
for i := range randVals {
// we need value between 0 and 1.0
randVals[i] = rand.Float64()*(max-min) + min
}
return mat.NewDense(rows, cols, randVals), nil
})
}
// MakeConstant returns a matrix of rows x cols whose each element is set to val.
// MakeConstant fails if invalid matrix dimensions are requested.
func MakeConstant(rows, cols int, val float64) (*mat.Dense, error) {
return withValidDims(rows, cols, func() (*mat.Dense, error) {
// allocate zero matrix and set every element to val
constMx := mat.NewDense(rows, cols, nil)
for i := 0; i < rows; i++ {
for j := 0; j < cols; j++ {
constMx.Set(i, j, val)
}
}
return constMx, nil
})
}
// AddConst adds a constant value to every element of matrix
// It modifies the matrix m passed in as a paramter.
// AddConstant fails with error if empty matrix is supplied
func AddConst(val float64, m *mat.Dense) (*mat.Dense, error) {
if m == nil {
return nil, fmt.Errorf("invalid matrix supplied: %v", m)
}
rows, cols := m.Dims()
return withValidDims(rows, cols, func() (*mat.Dense, error) {
// allocate zero matrix and set every element to val
for i := 0; i < rows; i++ {
for j := 0; j < cols; j++ {
m.Set(i, j, m.At(i, j)+val)
}
}
return m, nil
})
}
// viewFunc defines matrix dimension view function
type viewFunc func(int) mat.Vector
// dimFn applies function fn to first count matrix rows or columns.
// dim can be either set to rows or cols.
// dimFn collects the results into a slice and returns it
func dimFn(dim string, count int, m *mat.Dense, fn func(mat.Matrix) float64) []float64 {
res := make([]float64, count)
var viewFn viewFunc
switch dim {
case "rows":
viewFn = m.RowView
case "cols":
viewFn = m.ColView
}
for i := 0; i < count; i++ {
res[i] = fn(viewFn(i))
}
return res
}
// withValidDim executes function fn on first count of matrix columns or rows.
// It collects the results of each calculation and returns it in a slice.
// It returns error if either matrix m is nil, has zero size or requested number of
// particular dimension is larger than the matrix m dimensions.
func withValidDim(dim string, count int, m *mat.Dense,
fn func(mat.Matrix) float64) ([]float64, error) {
// matrix can't be nil
if m == nil {
return nil, fmt.Errorf("invalid matrix supplied: %v", m)
}
rows, cols := m.Dims()
switch dim {
case "rows":
if rows == 0 {
return nil, fmt.Errorf("invalid number of rows supplied: %v", m)
}
if count > rows {
return nil, fmt.Errorf("row count exceeds matrix rows: %d", count)
}
case "cols":
if cols == 0 {
return nil, fmt.Errorf("invalid number of columns supplied: %v", m)
}
if count > cols {
return nil, fmt.Errorf("column count exceeds matrix columns: %d", count)
}
}
return dimFn(dim, count, m, fn), nil
}
// withValidDims validates if the rows and cols are valid matrix dimensions
// It returns error if either rows or cols are invalid i.e. non-positive integers
func withValidDims(rows, cols int, fn func() (*mat.Dense, error)) (*mat.Dense, error) {
// can not create matrix with negative dimensions
if rows <= 0 {
return nil, fmt.Errorf("invalid number of rows: %d", rows)
}
if cols <= 0 {
return nil, fmt.Errorf("invalid number of columns: %d", cols)
}
return fn()
}
// returns a mean valur for a given matrix
func mean(m mat.Matrix) float64 {
r, c := m.Dims()
return mat.Sum(m) / (float64(r) * float64(c))
}
// returns a mean valur for a given matrix
func stdev(m mat.Matrix) float64 {
r, _ := m.Dims()
col := make([]float64, r)
mat.Col(col, 0, m)
return stat.StdDev(col, nil)
}