-
Notifications
You must be signed in to change notification settings - Fork 4
/
mathlab.go
143 lines (124 loc) · 2.98 KB
/
mathlab.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
package main
import (
"fmt"
"math"
"sort"
)
// see https://github.com/cpmech/gosl/tree/main/utl
func LinSpace(start, stop float64, num int) (res []float64) {
if num <= 0 {
return []float64{}
}
if num == 1 {
return []float64{start}
}
step := (stop - start) / float64(num-1)
res = make([]float64, num)
res[0] = start
for i := 1; i < num; i++ {
res[i] = start + float64(i)*step
}
res[num-1] = stop
return
}
func CumSum(s []float64) (dst []float64) {
if len(s) == 0 {
return []float64{}
}
dst = make([]float64, len(s))
dst[0] = s[0]
for i, v := range s[1:] {
dst[i+1] = dst[i] + v
}
return
}
func Diff(p []Point) (n []Point) {
if len(p) == 0 {
return []Point{}
}
n = make([]Point, len(p)-1)
for i, v := range p[1:] {
n[i] = Point{v.X - p[i].X, v.Y - p[i].Y}
}
return
}
// See https://www.npmjs.com/package/interp1
// Finds the index of range in which a query value is
// included in a sorted array with binary search.
func binaryFindIndex(xs []float64, xq float64) float64 {
// Special case of only one element in array.
if len(xs) == 1 && xs[0] == xq {
return 0.0
}
// Determine bounds.
lower := 0
upper := len(xs) - 1
// Find index of range.
for lower < upper {
// Determine test range.
mid := math.Floor(float64(lower+upper) / 2.0)
prev := xs[int(mid)]
next := xs[int(mid)+1]
if xq < prev {
// Query value is below range.
upper = int(mid)
} else if xq > next {
// Query value is above range.
lower = int(mid) + 1
} else {
// Query value is in range.
return mid + (xq-prev)/(next-prev)
}
}
// Range not found.
return -1.0
}
// Interpolates a value linear.
func interpolate(vs []float64, index float64) float64 {
prev := math.Floor(index)
next := math.Ceil(index)
lambda := index - prev
return (1-lambda)*vs[int(prev)] + lambda*vs[int(next)]
}
type zip struct {
X, V float64
}
// Interpolates values linearly in one dimension.
func interp1(x, v, xqs []float64) ([]float64, error) {
if len(x) != len(v) {
return nil, fmt.Errorf("Arrays of sample points xs and corresponding values vs have to have equal length.: %d vs. %d\n", len(x), len(v))
}
// sort x and v together
p := make([]zip, len(x))
for i := range x {
p[i] = zip{x[i], v[i]}
}
sort.Slice(p, func(i, j int) bool {
return p[i].X < p[j].X
})
// check for double x values
for i, v := range p[1:] {
if p[i].X == v.X {
return nil, fmt.Errorf("Two sample points have equal value %f. This is not allowed.", v.X)
}
}
// unzip everything
sortedX := make([]float64, len(p))
sortedV := make([]float64, len(p))
for i, v := range p {
sortedX[i] = v.X
sortedV[i] = v.V
}
// interpolate
r := make([]float64, len(xqs))
for i, xq := range xqs {
// Determine index of range of query value.
index := binaryFindIndex(sortedX, xq)
// Check if value lies in interpolation range.
if index == -1.0 {
return nil, fmt.Errorf("Query value %f lies outside of range. Extrapolation is not supported.", xq)
}
r[i] = interpolate(sortedV, index)
}
return r, nil
}