7
"github.com/RB-PRO/ssa/pkg/oss"
8
"github.com/mjibson/go-dsp/fft"
9
"github.com/pkg/errors"
10
"gonum.org/v1/gonum/blas"
11
"gonum.org/v1/gonum/floats"
12
"gonum.org/v1/gonum/lapack/lapack64"
13
"gonum.org/v1/gonum/mat"
16
// SavGolFilter implements Savitzky-Golay filter (https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.savgol_filter.html)
17
// based on: https://github.com/scipy/scipy/blob/v1.3.0rc1/scipy/signal/_savitzky_golay.py#L227
18
func SavGolFilter(x []float64, window_length int, polyorder int, deriv int /*=0*/, delta float64 /*=1.0*/) []float64 {
19
// computing filter coefficients
20
// the outputs of this step seem to be numerically same as the Python code ones
21
coeffs := SavGolCoeffs(window_length, polyorder, deriv, delta, true)
22
// convolving the original signal with the filter coefficients
23
// note: the outputs of this step are not completely numerically same as the Python code ones (because the latter uses different convolution function)
25
if len(x) < window_length {
29
convolutionOutput := Convolve1d(x, coeffs)
31
result := make([]float64, 0)
32
for i := 0; i < len(convolutionOutput); i++ {
33
result = append(result, real(convolutionOutput[i]))
38
func Convolve1d(x []float64, coeffs []float64) []complex128 {
39
xFFt := fft.FFTReal(x)
40
coefftsFFt := fft.FFTReal(coeffs)
42
if len(xFFt) != len(coefftsFFt) {
43
for i := len(coefftsFFt); i < len(xFFt); i++ {
44
coefftsFFt = append(coefftsFFt, 0+0i)
47
matrixProduct := make([]complex128, 0)
48
for i := 0; i < len(xFFt); i++ {
49
matrixProduct = append(matrixProduct, xFFt[i]*coefftsFFt[i])
51
return fft.IFFT(matrixProduct)
54
// Computes Savitzky-Golay filter coefficients.
55
func SavGolCoeffs(window_length int, polyorder int, deriv int, delta float64, useInConv bool) []float64 {
56
if polyorder >= window_length {
57
panic("polyorder must be less than window_length.")
59
fmt.Println("window_length", window_length)
60
if window_length%2 == 0 {
61
panic("window_length must be odd.")
63
pos := window_length / 2
65
panic("pos must be nonnegative.")
68
// Form the design matrix `A`. The columns of `A` are powers of the integers
69
// from -pos to window_length - pos - 1. The powers (i.e. rows) range
70
// from 0 to polyorder.
71
aRowTemplate := Arange(-pos, window_length-pos)
73
// Reverse so that result can be used in a convolution.
74
floats.Reverse(aRowTemplate)
76
a := oss.MakeMatrix(polyorder+1, len(aRowTemplate), func(i, j int) float64 {
77
return math.Pow(aRowTemplate[j], float64(i))
80
// `b` determines which order derivative is returned.
81
// The coefficient assigned to b[deriv] scales the result to take into
82
// account the order of the derivative and the sample spacing.
83
b := oss.MakeMatrix(polyorder+1, 1, func(i, j int) float64 {
87
return float64(factorial(deriv)) / math.Pow(delta, float64(deriv))
90
// finding the least-squares solution of A*x = b
92
if _, cols := coeff.Dims(); cols != 1 {
93
panic(errors.Errorf("SHOULD NOT HAPPEN: LstSq result contains %d columns instead of 1", cols))
95
return coeff.RawMatrix().Data
98
// LstSq computes least-squares solution to equation A*x = b, i.e. computes a vector x such that the 2-norm “|b - A x|“ is minimized.
99
func LstSq(a, b *mat.Dense) *mat.Dense {
100
// m is a number of columns in `a`, n is a number of rows in `a`
102
if m == 0 || n == 0 {
103
panic("zero-sized problem is not supported (confuses LAPACK)")
106
// nhrs (why is it called so?) is a number of rows in `b`
109
panic(errors.Errorf("shape mismatch: a and b should have the same number of rows: %d != %d", m, m2))
112
// LAPACK uses `b` as an output parameter as well - and therefore wants it to be resized from (m, nhrs) to (max(m,n), nhrs)
113
// here we copy `b` anyway (even if it doesn't need to be resized) - to avoid overwriting the user-supplied `b`
114
b = oss.MakeMatrix(max2(m, n), nhrs, func(i, j int) float64 {
121
// LAPACK function for computing least-squares solutions to linear equations
122
gels := func(work []float64, lwork int) bool {
123
return lapack64.Gels(blas.NoTrans, a.RawMatrix(), b.RawMatrix(), work, lwork)
126
// retrieving the size of work space needed (this is how LAPACK interfaces are designed:
127
// if we call the function with lwork=-1, it returns the work size needed in work[0])
128
work := make([]float64, 1)
130
lwork := int(math.Ceil(work[0]))
132
// solving the equation itself
133
result := gels(make([]float64, lwork), lwork)
135
panic(errors.Errorf("gels: computation didn't converge: A='%+v', b='%+v'", a, b))
138
// dgels writes a solution into b
142
// Arange implements `np.arange` - i.e. returns a list of integers (start, ..., stop - 1) in the form of []float64
143
func Arange(start int, stop int) []float64 {
144
return Linspace(float64(start), float64(stop-1), stop-start)
147
// Zeroes returns an array of zeroes of specified size.
148
// It's encouraged to use it instead of just make() in case the further code relies on the fact that the array contains zeroes.
149
func Zeroes(size int) []float64 {
150
return make([]float64, size)
153
// Ones return an array of ones of specified size.
154
func Ones(size int) []float64 {
155
result := make([]float64, size)
156
for i := range result {
162
// Linspace implements `np.linspace` - i.e. splits the interval [start, end] into `num - 1` equal intervals and returns `num` split points.
163
func Linspace(start, end float64, num int) []float64 {
165
panic(errors.Errorf("number of samples, %d, must be non-negative.", num))
167
result := make([]float64, num)
168
step := (end - start) / float64(num-1)
169
for i := range result {
170
result[i] = start + float64(i)*step
175
// maximum of two integers
176
func max2(a, b int) int {
184
func factorial(n int) int {
186
for i := 1; i <= n; i++ {
194
func savitzky_goley(y []float64, f, k int) []float64 {
195
// Функция, реализующая сглаживание с помощью фильтра Савицкого-Голея.
196
// f - окно сглаживания, желательно, чтобы оно было нечётным числом.
197
// k - степень полинома, оно должно быть меньше чем f
199
x := make([]int, len(y))
204
f = int(math.Floor(float64(f)))
208
//v := dinamicMatrix_float64(f, k+1)
209
var v mat.Dense = *(mat.NewDense(f, k+1, nil))
211
t := make([]int, hf*2+1)
216
for i := 0; i < f; i++ {
217
for j := 0; j <= k; j++ {
218
v.Set(i, j, math.Pow(float64(t[i]), float64(j)))
224
ymid := filt(q, hf, y)
226
ybegin := yBegin(q, hf, f, y)
227
yend := yEnd(q, hf, f, y)
228
for i := 0; i < len(ybegin); i++ {
232
// fmt.Println(len(ymid)-len(yend)-1, len(ymid), "len(yend)", len(yend))
234
for i := len(ymid) - len(yend); i < len(ymid); i++ {
235
//fmt.Println(i - (len(ymid) - len(yend)))
236
//fmt.Println(ybegin[i-(len(ymid)-len(yend))])
237
ymid[i] = ybegin[i-(len(ymid)-len(yend))]
243
func yBegin(q mat.Dense, hf, f int, y []float64) []float64 {
244
//ybegin = q(1:hf,:)*q'*y(1:f);
247
sliseQ := q.Slice(0, hf, 0, col)
249
matr.Mul(sliseQ, q.T())
251
var matVecDense mat.VecDense
252
matVecDense.MulVec(mat.Matrix(&matr), mat.Vector(mat.NewVecDense(f, y[:f])))
254
return oss.VecDense_in_float64(matVecDense)
256
func yEnd(q mat.Dense, hf, f int, y []float64) []float64 {
257
//yend = q((hf+2):end,:)*q'*y(n-f+1:n);
260
sliseQ := q.Slice(hf+1, row, 0, col)
262
matr.Mul(sliseQ, q.T())
264
var matVecDense mat.VecDense
265
matVecDense.MulVec(mat.Matrix(&matr), mat.Vector(mat.NewVecDense(f, y[len(y)-f:])))
266
return oss.VecDense_in_float64(matVecDense)
269
func filt(q mat.Dense, hf int, x []float64) []float64 {
272
elem := q.RowView(hf + 1) // q(hf+1,:)'
273
var matr mat.Matrix = mat.Matrix(&q) // q
274
b.MulVec(matr, elem) // q*q(hf+1,:)'
276
y := make([]float64, len(x))
277
y[0] = b.AtVec(0) * x[0]
279
var y_tmp, y_b1, y_j float64
281
for i := 1; i < len(x); i++ {
283
y_b1 = b.AtVec(1) * x[i]
285
for j := 1; j < b.Len(); j++ {
287
y_j = b.AtVec(j) * x[i-j+1]
292
y_j = b.AtVec(j) * x[i-j+1]
305
// QR Decomposition - Некорректный вывод
306
func QRDec(a mat.Dense) (mat.Dense, mat.Dense) {
309
var r mat.Dense = *mat.NewDense(col, col, nil)
310
var matVecDense mat.VecDense
311
for i := 0; i < col; i++ {
312
for j := 0; j < i; j++ {
313
r.Set(i, j, oss.MulVecToVec(q.ColView(j), q.ColView(i)))
314
matVecDense.ScaleVec(r.At(j, i), q.ColView(j))
315
matVecDense.SubVec(q.ColView(i), &matVecDense)
316
q.SetCol(i, oss.VecDense_in_float64(matVecDense))
318
matVecDense = *mat.VecDenseCopyOf(q.ColView(i))
319
r.Set(i, i, matVecDense.Norm(2.0))
320
//r.Set(i, i, p_norm(q.ColView(i), 2.0))
322
if r.At(i, i) == 0.0 {
325
matVecDense.ScaleVec(1/r.At(i, i), q.ColView(i))
326
q.SetCol(i, oss.VecDense_in_float64(matVecDense))
331
func p_norm(arr mat.Vector, p float64) float64 {
332
//func p_norm(arr []float64, p float64) float64 {
333
// The general definition for the p-norm of a vector v that has N elements is
334
// If p = 1, then the resulting 1-norm is the sum of the absolute values of the vector elements.
335
// If p = 2, then the resulting 2-norm gives the vector magnitude or Euclidean length of the vector.
336
// If p = Inf, then v = max(arr)
337
// If p = -Inf, then v = min(arr)
340
for ind := 0; ind < arr.Len(); ind++ {
341
//for _, value := range arr {
342
sum += arr.AtVec(ind)
345
for ind := 0; ind < arr.Len(); ind++ {
346
//for _, value := range arr {
347
sum += math.Pow(arr.AtVec(ind), 2.0)
351
for ind := 0; ind < arr.Len(); ind++ {
352
//for _, value := range arr {
353
sum += math.Pow(arr.AtVec(ind), p)
355
sum = math.Pow(sum, 1/p)
360
func multipleArray(a, b []float64) (float64, error) {
361
if len(a) != len(b) {
362
return 0.0, errors.New("Length vector is different")
365
for i := 0; i < len(a); i++ {
371
// Вычитание вектора из вектора
372
func subVectors(a, b []float64) ([]float64, error) {
373
if len(a) != len(b) {
374
return nil, errors.New("Length vector is different")
376
for i := 0; i < len(a); i++ {
382
// Умножение вектора на константу
383
func multipleConstArray(a []float64, multipleConstant float64) []float64 {
384
for i := 0; i < len(a); i++ {
385
a[i] *= multipleConstant
390
// Умножение вектора на константу
391
func divisionConstArray(a []float64, multipleConstant float64) []float64 {
392
for i := 0; i < len(a); i++ {
393
a[i] /= multipleConstant
398
// Создать матрицу с размерами row, col. int
399
func dinamicMatrix(row, col int) [][]int {
400
matrix := make([][]int, row)
401
for ind := range matrix {
402
matrix[ind] = make([]int, col)
407
// Создать матрицу с размерами row, col. float64
408
func dinamicMatrix_float64(row, col int) [][]float64 {
409
matrix := make([][]float64, row)
410
for ind := range matrix {
411
matrix[ind] = make([]float64, col)
416
// Минимальное число из двух
417
func min2(a, b int) int {