├── .gitignore ├── differentiate ├── backward.go ├── forward.go ├── central.go ├── central_test.go ├── forward_test.go └── backward_test.go ├── fit ├── fit.go ├── linear │ ├── linear.go │ └── linear_test.go ├── exponential │ ├── exponential.go │ └── exponential_test.go └── poly │ ├── poly.go │ └── poly_test.go ├── root ├── bisection.go ├── newton.go ├── newton_test.go └── bisection_test.go ├── integrate ├── trapezoid.go ├── simpson.go ├── simpson_test.go └── trapezoid_test.go ├── interpolate ├── base.go ├── interpolate.go ├── lagrange │ ├── lagrange.go │ └── lagrange_test.go └── linear │ ├── linear.go │ └── linear_test.go ├── coordinate_pair.go ├── .travis.yml ├── vector.go ├── vector_test.go ├── README.md ├── matrix.go └── matrix_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.out -------------------------------------------------------------------------------- /differentiate/backward.go: -------------------------------------------------------------------------------- 1 | package differentiate 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func Backward(f func(float64) float64, val, h float64) (float64, error) { 8 | if h <= 0 { 9 | return 0, fmt.Errorf("Step size has to be greater than 0") 10 | } 11 | return (f(val) - f(val-h)) / h, nil 12 | } 13 | -------------------------------------------------------------------------------- /differentiate/forward.go: -------------------------------------------------------------------------------- 1 | package differentiate 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func Forward(f func(float64) float64, val, h float64) (float64, error) { 8 | if h <= 0 { 9 | return 0, fmt.Errorf("Step size has to be greater than 0") 10 | } 11 | return (f(val+h) - f(val)) / h, nil 12 | } 13 | -------------------------------------------------------------------------------- /differentiate/central.go: -------------------------------------------------------------------------------- 1 | package differentiate 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func Central(f func(float64) float64, val, h float64) (float64, error) { 8 | if h <= 0 { 9 | return 0, fmt.Errorf("Step size has to be greater than 0") 10 | } 11 | return (f(val+h) - f(val-h)) / (2 * h), nil 12 | } 13 | -------------------------------------------------------------------------------- /fit/fit.go: -------------------------------------------------------------------------------- 1 | package fit 2 | 3 | import ( 4 | "github.com/DzananGanic/numericalgo" 5 | ) 6 | 7 | type predictor interface { 8 | Predict(float64) float64 9 | } 10 | 11 | // PredictMulti accepts the slice of float64, and returns the predicted values for the passed slice values, and the error 12 | func PredictMulti(p predictor, vals numericalgo.Vector) numericalgo.Vector { 13 | var r []float64 14 | for _, val := range vals { 15 | est := p.Predict(val) 16 | r = append(r, est) 17 | } 18 | return r 19 | } 20 | -------------------------------------------------------------------------------- /root/bisection.go: -------------------------------------------------------------------------------- 1 | package root 2 | 3 | import ( 4 | "math" 5 | ) 6 | 7 | // Bisection receives three parameters. First parameter is the function we want to find root of. Second one is the tolerance. Third and fourth parameters are left and right bounds of the function. 8 | func Bisection(f func(float64) float64, eps, l, r float64) float64 { 9 | 10 | mid := (l + r) / 2 11 | 12 | if math.Abs(f(mid)) < eps { 13 | return mid 14 | } else if (f(l) < 0) == (f(mid) < 0) { 15 | return Bisection(f, eps, mid, r) 16 | } else if (f(r) < 0) == (f(mid) < 0) { 17 | return Bisection(f, eps, l, mid) 18 | } 19 | 20 | return math.Inf(1) 21 | } 22 | -------------------------------------------------------------------------------- /root/newton.go: -------------------------------------------------------------------------------- 1 | package root 2 | 3 | import "github.com/DzananGanic/numericalgo/differentiate" 4 | 5 | // Newton receives three parameters. First parameter is the function we want to find root of. Second one is the initial guess (reasonably close to the true root). Third one is the number of iterations for the newton method. The Newton function returns the result as a float64, and the error. 6 | func Newton(f func(float64) float64, x0 float64, iter int) (float64, error) { 7 | for i := 0; i < iter; i++ { 8 | d, err := differentiate.Central(f, x0, 0.01) 9 | if err != nil { 10 | return 0, err 11 | } 12 | x0 = x0 - f(x0)/d 13 | } 14 | 15 | return x0, nil 16 | } 17 | -------------------------------------------------------------------------------- /integrate/trapezoid.go: -------------------------------------------------------------------------------- 1 | package integrate 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/DzananGanic/numericalgo" 7 | ) 8 | 9 | // Trapezoid is a function which accepts function, left, right bounds and n number of subdivisions. It returns the integration 10 | // value of the function in the given bounds using trapezoidal rule. 11 | func Trapezoid(f func(float64) float64, l, r float64, n int) (float64, error) { 12 | 13 | var eval numericalgo.Vector 14 | var x float64 15 | 16 | if n == 0 { 17 | return 0, fmt.Errorf("Number of subdivisions n cannot be 0") 18 | } 19 | 20 | h := (r - l) / float64(n) 21 | 22 | for i := 0; i <= n; i++ { 23 | x = l + h*float64(i) 24 | eval = append(eval, f(x)) 25 | } 26 | 27 | return h * ((eval[0]+eval[n])/2 + eval[1:n].Sum()), nil 28 | } 29 | -------------------------------------------------------------------------------- /interpolate/base.go: -------------------------------------------------------------------------------- 1 | package interpolate 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/DzananGanic/numericalgo" 7 | ) 8 | 9 | // Base type provides the base functionality for any interpolation type 10 | type Base struct { 11 | XYPairs []numericalgo.CoordinatePair 12 | X []float64 13 | Y []float64 14 | } 15 | 16 | // Fit receives two float64 slices - for the x and y coordinates where x[i] and y[i] represent a coordinate pair in a grid. 17 | // It returns the error if the X and Y sizes do not match. 18 | func (b *Base) Fit(x, y []float64) error { 19 | if len(x) != len(y) { 20 | return fmt.Errorf("X and Y sizes do not match") 21 | } 22 | b.X = x 23 | b.Y = y 24 | b.XYPairs = numericalgo.SlicesToCoordinatePairs(x, y) 25 | numericalgo.SortCoordinatePairs(b.XYPairs) 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /coordinate_pair.go: -------------------------------------------------------------------------------- 1 | package numericalgo 2 | 3 | import ( 4 | "sort" 5 | ) 6 | 7 | // CoordinatePair is a struct type with two properties, X and Y coordinates 8 | type CoordinatePair struct { 9 | X float64 10 | Y float64 11 | } 12 | 13 | // SortCoordinatePairs is a function which receives a slice of CoordinatePairs, and sorts it in ascending order. 14 | func SortCoordinatePairs(cp []CoordinatePair) { 15 | sort.Slice(cp, func(i, j int) bool { 16 | return cp[i].X < cp[j].X 17 | }) 18 | } 19 | 20 | // SlicesToCoordinatePairs is a function which receives two slices of floats (x and y), turns them into a slice of CoordinatePairs, and returns the result. 21 | func SlicesToCoordinatePairs(x, y []float64) []CoordinatePair { 22 | cp := make([]CoordinatePair, len(x)) 23 | for i := 0; i < len(x); i++ { 24 | cp = append(cp, CoordinatePair{X: x[i], Y: y[i]}) 25 | } 26 | return cp 27 | } 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | go: 4 | - tip 5 | before_install: 6 | - go get github.com/mattn/goveralls 7 | - go get golang.org/x/tools/cmd/cover 8 | script: 9 | - $HOME/gopath/bin/goveralls -service=travis-ci 10 | env: 11 | global: 12 | secure: SQktZZ8B+6JAgAJS9svGEbHhFLFaw6SrV7l0YG4ezmXwVTrB7gCMcbzxy5oG3/ufCxBSvKAigGQh5/vRlhXiZYSYQ4YPOJz8qWR6yWITJf9S3oY4JAFr9sR65XJ/7QxtfvBdGE+vZ4Aayax+/l7TtlQ9qRU4bAVrY5nI7ECxIkWl/FaxQxlNmWaIzKwPI8uoOq7sKxusAkPZQgIDaqnubhv7K0jQ1CL/aQiZE5azW2Fw6WJTroWCU4vooikVrGymM73OkxJH78t2JPZSYMyQb0FWy+hVhUVz4di2mKK+iNgk/31Zeh8Ar4/ODIKtD8EZ2XAyY0H+it3kihDxbZP5WCI2iEQtUCm3vqYgG1O6US9sS2DZPTKYGWzDbBhK6mkFh2IyXKUs3IA0v9n3CDANX0+TWrJVXuL31cZU2wMpirY26O33qnOlneC0HryrWxUMAoszaEG/U17QOIuYxPEgXCjYzhvqjXOOJ6OCmqzQC6o7pE9EOIIuLv6vbRjI1OuwIdYuKz5XnZmUMt05eS965ea1+/9MpAwma80d+dsv7NmQrcgb2iYd1vWcmBA1YtOXeO+asdgDS5/3dtqx0qMY+I25vRzgFch1s1q4ym3L39LD7TxzJPHuqVnSJVNcbMxmGplstjljHuhxTJtQSnjcA3K3DehLjY611CWqMWpll4w= -------------------------------------------------------------------------------- /root/newton_test.go: -------------------------------------------------------------------------------- 1 | package root_test 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | 7 | "github.com/DzananGanic/numericalgo/root" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestNewton(t *testing.T) { 12 | 13 | cases := map[string]struct { 14 | f func(x float64) float64 15 | iter int 16 | initialGuess float64 17 | expectedValue float64 18 | expectedError error 19 | }{ 20 | "basic root finding": { 21 | f: func(x float64) float64 { 22 | return math.Pow(x, 3) - 2*math.Pow(x, 2) + 5 23 | }, 24 | iter: 3, 25 | initialGuess: -1, 26 | expectedValue: -1.2419, 27 | expectedError: nil, 28 | }, 29 | } 30 | 31 | for name, c := range cases { 32 | t.Run(name, func(t *testing.T) { 33 | result, err := root.Newton(c.f, c.initialGuess, c.iter) 34 | assert.InEpsilon(t, result, c.expectedValue, 1e-4) 35 | assert.Equal(t, err, c.expectedError) 36 | }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /integrate/simpson.go: -------------------------------------------------------------------------------- 1 | package integrate 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/DzananGanic/numericalgo" 7 | ) 8 | 9 | // Simpson is a function which accepts function, left, right bounds and n number of subdivisions. It returns the integration 10 | // value of the function in the given bounds using simpson rule. 11 | func Simpson(f func(float64) float64, l, r float64, n int) (float64, error) { 12 | 13 | var eval, evalOdd, evalEven numericalgo.Vector 14 | var x float64 15 | 16 | if n == 0 { 17 | return 0, fmt.Errorf("Number of subdivisions n cannot be 0") 18 | } 19 | 20 | h := (r - l) / float64(n) 21 | 22 | for i := 0; i <= n; i++ { 23 | x = l + h*float64(i) 24 | eval = append(eval, f(x)) 25 | } 26 | 27 | for i := 1; i < n; i++ { 28 | if i%2 == 0 { 29 | evalEven = append(evalEven, eval[i]) 30 | } else { 31 | evalOdd = append(evalOdd, eval[i]) 32 | } 33 | } 34 | 35 | return h / 3 * (eval[0] + eval[n] + 4*evalOdd.Sum() + 2*evalEven.Sum()), nil 36 | } 37 | -------------------------------------------------------------------------------- /interpolate/interpolate.go: -------------------------------------------------------------------------------- 1 | package interpolate 2 | 3 | type interpolator interface { 4 | Interpolate(float64) float64 5 | } 6 | 7 | type validator interface { 8 | Validate(float64) error 9 | } 10 | 11 | type validateInterpolator interface { 12 | interpolator 13 | validator 14 | } 15 | 16 | // WithMulti accepts the slice of float64, and returns the interpolated values for the passed slice values, and the error 17 | func WithMulti(vi validateInterpolator, vals []float64) ([]float64, error) { 18 | var r []float64 19 | for _, val := range vals { 20 | est, err := WithSingle(vi, val) 21 | if err != nil { 22 | return r, err 23 | } 24 | r = append(r, est) 25 | } 26 | return r, nil 27 | } 28 | 29 | // WithSingle accepts the single float64 value, and returns the interpolated value for it, and the error 30 | func WithSingle(vi validateInterpolator, val float64) (float64, error) { 31 | var est float64 32 | 33 | err := vi.Validate(val) 34 | if err != nil { 35 | return est, err 36 | } 37 | 38 | est = vi.Interpolate(val) 39 | return est, nil 40 | } 41 | -------------------------------------------------------------------------------- /root/bisection_test.go: -------------------------------------------------------------------------------- 1 | package root_test 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | 7 | "github.com/DzananGanic/numericalgo/root" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestBisection(t *testing.T) { 12 | 13 | cases := map[string]struct { 14 | f func(x float64) float64 15 | eps float64 16 | l float64 17 | r float64 18 | expectedValue float64 19 | expectedError error 20 | }{ 21 | "basic bisection": { 22 | f: func(x float64) float64 { 23 | return math.Pow(x, 2) 24 | }, 25 | eps: 0.01, 26 | l: -1, 27 | r: 1, 28 | expectedValue: 0, 29 | expectedError: nil, 30 | }, 31 | "bisecting more complex function": { 32 | f: func(x float64) float64 { 33 | return math.Pow(x, 3) - 2*math.Pow(x, 2) + 5 34 | }, 35 | eps: 0.01, 36 | l: -1.5, 37 | r: -1, 38 | expectedValue: -1.2421875, 39 | }, 40 | } 41 | 42 | for name, c := range cases { 43 | t.Run(name, func(t *testing.T) { 44 | result := root.Bisection(c.f, c.eps, c.l, c.r) 45 | assert.Equal(t, result, c.expectedValue) 46 | }) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /interpolate/lagrange/lagrange.go: -------------------------------------------------------------------------------- 1 | package lagrange 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/DzananGanic/numericalgo/interpolate" 7 | ) 8 | 9 | // Lagrange provides the basic functionality for lagrange interpolation. 10 | // Given X and Y float64 slices, it can estimate the value of the function at the desired point. 11 | type Lagrange struct { 12 | interpolate.Base 13 | } 14 | 15 | // New returns the new Lagrange object. 16 | func New() *Lagrange { 17 | lg := &Lagrange{} 18 | return lg 19 | } 20 | 21 | func (lg *Lagrange) Interpolate(val float64) float64 { 22 | var est float64 23 | 24 | for i := 0; i < len(lg.X); i++ { 25 | prod := lg.Y[i] 26 | for j := 0; j < len(lg.X); j++ { 27 | if i != j { 28 | prod = prod * (val - lg.X[j]) / (lg.X[i] - lg.X[j]) 29 | } 30 | } 31 | est += prod 32 | } 33 | 34 | return est 35 | } 36 | 37 | func (lg *Lagrange) Validate(val float64) error { 38 | 39 | for i := 0; i < len(lg.X); i++ { 40 | for j := 0; j < len(lg.X); j++ { 41 | if i != j { 42 | if lg.X[i]-lg.X[j] == 0 { 43 | return fmt.Errorf("There are at least 2 same X values. This will result in division by zero in Lagrange interpolation") 44 | } 45 | } 46 | } 47 | } 48 | 49 | if val < lg.XYPairs[0].X { 50 | return fmt.Errorf("Value to interpolate is too small and not in range") 51 | } 52 | 53 | if val > lg.XYPairs[len(lg.XYPairs)-1].X { 54 | return fmt.Errorf("Value to interpolate is too large and not in range") 55 | } 56 | 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /interpolate/linear/linear.go: -------------------------------------------------------------------------------- 1 | package linear 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/DzananGanic/numericalgo/interpolate" 7 | ) 8 | 9 | // Linear provides the basic functionality for linear interpolation. 10 | // Given X and Y float64 slices, it can estimate the value of the function at the desired point. 11 | type Linear struct { 12 | interpolate.Base 13 | } 14 | 15 | // New returns the new Linear object 16 | func New() *Linear { 17 | li := &Linear{} 18 | return li 19 | } 20 | 21 | func (li *Linear) Interpolate(val float64) float64 { 22 | var est float64 23 | 24 | l, r := li.findNearestNeighbors(val, 0, len(li.XYPairs)-1) 25 | 26 | lX := li.XYPairs[l].X 27 | rX := li.XYPairs[r].X 28 | lY := li.XYPairs[l].Y 29 | rY := li.XYPairs[r].Y 30 | 31 | est = lY + (rY-lY)/(rX-lX)*(val-lX) 32 | return est 33 | } 34 | 35 | func (li *Linear) Validate(val float64) error { 36 | 37 | if val < li.XYPairs[0].X { 38 | return fmt.Errorf("Value to interpolate is too small and not in range") 39 | } 40 | 41 | if val > li.XYPairs[len(li.XYPairs)-1].X { 42 | return fmt.Errorf("Value to interpolate is too large and not in range") 43 | } 44 | 45 | return nil 46 | } 47 | 48 | func (li *Linear) findNearestNeighbors(val float64, l, r int) (int, int) { 49 | middle := (l + r) / 2 50 | if (val >= li.XYPairs[middle-1].X) && (val <= li.XYPairs[middle].X) { 51 | return middle - 1, middle 52 | } else if val < li.XYPairs[middle-1].X { 53 | return li.findNearestNeighbors(val, l, middle-2) 54 | } 55 | return li.findNearestNeighbors(val, middle+1, r) 56 | } 57 | -------------------------------------------------------------------------------- /fit/linear/linear.go: -------------------------------------------------------------------------------- 1 | package linear 2 | 3 | import ( 4 | "github.com/DzananGanic/numericalgo" 5 | ) 6 | 7 | // Linear type fits two vectors x and y, finds the appropriate coefficients and predicts the value such that y=p+qx is the best approximation of the given data in a sense of the least square error. 8 | type Linear struct { 9 | x numericalgo.Vector 10 | y numericalgo.Vector 11 | Coeff numericalgo.Vector 12 | } 13 | 14 | // New returns the pointer to the new Linear type 15 | func New() *Linear { 16 | lf := &Linear{} 17 | return lf 18 | } 19 | 20 | // Fit function in Linear type receives two vectors, finds and stores the coefficients in the coeff property, and returns the error if something went wrong. Coefficients are calculated based on the y=p+q*x formula. 21 | func (l *Linear) Fit(x numericalgo.Vector, y numericalgo.Vector) error { 22 | xMatrix := numericalgo.Matrix{x} 23 | yMatrix := numericalgo.Matrix{y} 24 | 25 | xT, err := xMatrix.Transpose() 26 | 27 | if err != nil { 28 | return err 29 | } 30 | 31 | ones := make(numericalgo.Vector, x.Dim()) 32 | for i := range ones { 33 | ones[i] = 1 34 | } 35 | 36 | X, err := xT.InsertCol(0, ones) 37 | 38 | if err != nil { 39 | return err 40 | } 41 | 42 | Y, err := yMatrix.Transpose() 43 | 44 | if err != nil { 45 | return err 46 | } 47 | 48 | coeff, err := X.LeftDivide(Y) 49 | 50 | if err != nil { 51 | return err 52 | } 53 | 54 | l.Coeff, err = coeff.Col(0) 55 | 56 | if err != nil { 57 | return err 58 | } 59 | 60 | return nil 61 | } 62 | 63 | // Predict function in Linear type accepts value to be predicted, and returns the predicted value based on the y=p+q*x formula. 64 | func (l *Linear) Predict(val float64) float64 { 65 | c := l.Coeff 66 | return c[0] + c[1]*val 67 | } 68 | -------------------------------------------------------------------------------- /differentiate/central_test.go: -------------------------------------------------------------------------------- 1 | package differentiate_test 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "testing" 7 | 8 | "github.com/DzananGanic/numericalgo/differentiate" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestCentral(t *testing.T) { 13 | 14 | cases := map[string]struct { 15 | f func(x float64) float64 16 | val float64 17 | h float64 18 | expectedValue float64 19 | expectedError error 20 | }{ 21 | "central difference with 0.1 step size": { 22 | f: func(x float64) float64 { 23 | return math.Cos(math.Pow(x, 2) - 2) 24 | }, 25 | val: 1, 26 | h: 0.1, 27 | expectedValue: 1.6609, 28 | expectedError: nil, 29 | }, 30 | "central difference with 0.01 step size": { 31 | f: func(x float64) float64 { 32 | return math.Cos(math.Pow(x, 2) - 2) 33 | }, 34 | val: 1, 35 | h: 0.01, 36 | expectedValue: 1.6827, 37 | expectedError: nil, 38 | }, 39 | "central difference with 0.001 step size": { 40 | f: func(x float64) float64 { 41 | return math.Cos(math.Pow(x, 2) - 2) 42 | }, 43 | val: 1, 44 | h: 0.001, 45 | expectedValue: 1.6829, 46 | expectedError: nil, 47 | }, 48 | "central difference with wrong step size": { 49 | f: func(x float64) float64 { 50 | return math.Cos(math.Pow(x, 2) - 2) 51 | }, 52 | val: 1, 53 | h: -2, 54 | expectedValue: 0, 55 | expectedError: fmt.Errorf("Step size has to be greater than 0"), 56 | }, 57 | } 58 | 59 | for name, c := range cases { 60 | t.Run(name, func(t *testing.T) { 61 | result, err := differentiate.Central(c.f, c.val, c.h) 62 | if result != 0 { 63 | assert.InEpsilon(t, result, c.expectedValue, 1e-4) 64 | } else { 65 | assert.Equal(t, result, c.expectedValue) 66 | } 67 | assert.Equal(t, err, c.expectedError) 68 | }) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /differentiate/forward_test.go: -------------------------------------------------------------------------------- 1 | package differentiate_test 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "testing" 7 | 8 | "github.com/DzananGanic/numericalgo/differentiate" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestForward(t *testing.T) { 13 | 14 | cases := map[string]struct { 15 | f func(x float64) float64 16 | val float64 17 | h float64 18 | expectedValue float64 19 | expectedError error 20 | }{ 21 | "forward difference with 0.1 step size": { 22 | f: func(x float64) float64 { 23 | return math.Cos(math.Pow(x, 2) - 2) 24 | }, 25 | val: 1, 26 | h: 0.1, 27 | expectedValue: 1.6354, 28 | expectedError: nil, 29 | }, 30 | "forward difference with 0.01 step size": { 31 | f: func(x float64) float64 { 32 | return math.Cos(math.Pow(x, 2) - 2) 33 | }, 34 | val: 1, 35 | h: 0.01, 36 | expectedValue: 1.6803, 37 | expectedError: nil, 38 | }, 39 | "forward difference with 0.001 step size": { 40 | f: func(x float64) float64 { 41 | return math.Cos(math.Pow(x, 2) - 2) 42 | }, 43 | val: 1, 44 | h: 0.001, 45 | expectedValue: 1.6827, 46 | expectedError: nil, 47 | }, 48 | "forward difference with wrong step size": { 49 | f: func(x float64) float64 { 50 | return math.Cos(math.Pow(x, 2) - 2) 51 | }, 52 | val: 1, 53 | h: -2, 54 | expectedValue: 0, 55 | expectedError: fmt.Errorf("Step size has to be greater than 0"), 56 | }, 57 | } 58 | 59 | for name, c := range cases { 60 | t.Run(name, func(t *testing.T) { 61 | result, err := differentiate.Forward(c.f, c.val, c.h) 62 | if result != 0 { 63 | assert.InEpsilon(t, result, c.expectedValue, 1e-4) 64 | } else { 65 | assert.Equal(t, result, c.expectedValue) 66 | } 67 | assert.Equal(t, err, c.expectedError) 68 | }) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /differentiate/backward_test.go: -------------------------------------------------------------------------------- 1 | package differentiate_test 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "testing" 7 | 8 | "github.com/DzananGanic/numericalgo/differentiate" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestBackward(t *testing.T) { 13 | 14 | cases := map[string]struct { 15 | f func(x float64) float64 16 | val float64 17 | h float64 18 | expectedValue float64 19 | expectedError error 20 | }{ 21 | "backward difference with 0.1 step size": { 22 | f: func(x float64) float64 { 23 | return math.Cos(math.Pow(x, 2) - 2) 24 | }, 25 | val: 1, 26 | h: 0.1, 27 | expectedValue: 1.6864, 28 | expectedError: nil, 29 | }, 30 | "backward difference with 0.01 step size": { 31 | f: func(x float64) float64 { 32 | return math.Cos(math.Pow(x, 2) - 2) 33 | }, 34 | val: 1, 35 | h: 0.01, 36 | expectedValue: 1.6851, 37 | expectedError: nil, 38 | }, 39 | "backward difference with 0.001 step size": { 40 | f: func(x float64) float64 { 41 | return math.Cos(math.Pow(x, 2) - 2) 42 | }, 43 | val: 1, 44 | h: 0.001, 45 | expectedValue: 1.6832, 46 | expectedError: nil, 47 | }, 48 | "backward difference with wrong step size": { 49 | f: func(x float64) float64 { 50 | return math.Cos(math.Pow(x, 2) - 2) 51 | }, 52 | val: 1, 53 | h: -2, 54 | expectedValue: 0, 55 | expectedError: fmt.Errorf("Step size has to be greater than 0"), 56 | }, 57 | } 58 | 59 | for name, c := range cases { 60 | t.Run(name, func(t *testing.T) { 61 | result, err := differentiate.Backward(c.f, c.val, c.h) 62 | if result != 0 { 63 | assert.InEpsilon(t, result, c.expectedValue, 1e-4) 64 | } else { 65 | assert.Equal(t, result, c.expectedValue) 66 | } 67 | assert.Equal(t, err, c.expectedError) 68 | }) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /fit/exponential/exponential.go: -------------------------------------------------------------------------------- 1 | package exponential 2 | 3 | import ( 4 | "github.com/DzananGanic/numericalgo" 5 | ) 6 | 7 | // Exponential type fits two vectors x and y, finds the appropriate coefficients and predicts the value such that y=p*e^(q*x) is the best approximation of the given data in a sense of the least square error. 8 | type Exponential struct { 9 | x numericalgo.Vector 10 | y numericalgo.Vector 11 | Coeff numericalgo.Vector 12 | } 13 | 14 | // New returns the pointer to the new Exponential type 15 | func New() *Exponential { 16 | ef := &Exponential{} 17 | return ef 18 | } 19 | 20 | // Fit function in Exponential type receives two vectors, finds and stores the coefficients in the coeff property, and returns the error if something went wrong. Coefficients are calculated based on the y=p*e^(q*x) formula. 21 | func (e *Exponential) Fit(x numericalgo.Vector, y numericalgo.Vector) error { 22 | xMatrix := numericalgo.Matrix{x} 23 | yMatrix := numericalgo.Matrix{y} 24 | 25 | xLogMatrix := xMatrix.Log() 26 | yLogMatrix := yMatrix.Log() 27 | 28 | xLogT, err := xLogMatrix.Transpose() 29 | if err != nil { 30 | return err 31 | } 32 | 33 | yLogT, err := yLogMatrix.Transpose() 34 | if err != nil { 35 | return err 36 | } 37 | 38 | ones := make(numericalgo.Vector, x.Dim()) 39 | for i := range ones { 40 | ones[i] = 1 41 | } 42 | 43 | X, err := xLogT.InsertCol(0, ones) 44 | 45 | if err != nil { 46 | return err 47 | } 48 | 49 | coeff, err := X.LeftDivide(yLogT) 50 | 51 | if err != nil { 52 | return err 53 | } 54 | 55 | expCoeff := coeff.Exp() 56 | 57 | e.Coeff, err = expCoeff.Col(0) 58 | 59 | if err != nil { 60 | return err 61 | } 62 | 63 | return nil 64 | } 65 | 66 | // Predict function in Exponential type accepts value to be predicted, and returns the predicted value based on the y=p*e^(q*x) formula. 67 | func (e *Exponential) Predict(val float64) float64 { 68 | c := e.Coeff 69 | return c[1]*val + c[0] 70 | } 71 | -------------------------------------------------------------------------------- /fit/poly/poly.go: -------------------------------------------------------------------------------- 1 | package poly 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/DzananGanic/numericalgo" 7 | ) 8 | 9 | // Poly type fits two vectors x and y, finds the appropriate coefficients and predicts the value such that y=p1+p2*x+p3*x^2+...+p(n+1)*x^n is the best approximation of the given data in a sense of the least square error. 10 | type Poly struct { 11 | x numericalgo.Vector 12 | y numericalgo.Vector 13 | Coeff numericalgo.Vector 14 | } 15 | 16 | // New returns the pointer to the new Poly type 17 | func New() *Poly { 18 | pf := &Poly{} 19 | return pf 20 | } 21 | 22 | // Fit function in Poly type receives two vectors, finds and stores the coefficients in the coeff property, and returns the error if something went wrong. Coefficients are calculated based on the y=p1+p2*x+p3*x^2+...+p(n+1)*x^n formula. 23 | func (p *Poly) Fit(x numericalgo.Vector, y numericalgo.Vector, n int) error { 24 | xMatrix := numericalgo.Matrix{x} 25 | yMatrix := numericalgo.Matrix{y} 26 | 27 | xT, err := xMatrix.Transpose() 28 | 29 | if err != nil { 30 | return err 31 | } 32 | 33 | ones := make(numericalgo.Vector, x.Dim()) 34 | for i := range ones { 35 | ones[i] = 1 36 | } 37 | 38 | X, err := xT.InsertCol(0, ones) 39 | 40 | if err != nil { 41 | return err 42 | } 43 | 44 | for i := 2; i <= n; i++ { 45 | X, err = X.InsertCol(i, x.Power(float64(i))) 46 | } 47 | 48 | Y, err := yMatrix.Transpose() 49 | 50 | if err != nil { 51 | return err 52 | } 53 | 54 | coeff, err := X.LeftDivide(Y) 55 | 56 | if err != nil { 57 | return err 58 | } 59 | 60 | p.Coeff, err = coeff.Col(0) 61 | 62 | if err != nil { 63 | return err 64 | } 65 | 66 | return nil 67 | } 68 | 69 | // Predict function in Poly type accepts value to be predicted, and returns the predicted value based on the y=p1+p2*x+p3*x^2+...+p(n+1)*x^n formula. 70 | func (p *Poly) Predict(val float64) float64 { 71 | var result float64 72 | c := p.Coeff 73 | for i := 0; i < len(c); i++ { 74 | result += c[i] * math.Pow(val, float64(i)) 75 | } 76 | return result 77 | } 78 | -------------------------------------------------------------------------------- /integrate/simpson_test.go: -------------------------------------------------------------------------------- 1 | package integrate_test 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "testing" 7 | 8 | "github.com/DzananGanic/numericalgo/integrate" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestSimpson(t *testing.T) { 13 | 14 | cases := map[string]struct { 15 | f func(x float64) float64 16 | l float64 17 | r float64 18 | n int 19 | expectedValue float64 20 | expectedError error 21 | }{ 22 | "f(x) = x^4 with n = 20": { 23 | f: func(x float64) float64 { 24 | return math.Pow(x, 4) 25 | }, 26 | l: 1, 27 | r: 3, 28 | n: 20, 29 | expectedValue: 48.40002, 30 | expectedError: nil, 31 | }, 32 | "f(x) = x^4 with n = 50": { 33 | f: func(x float64) float64 { 34 | return math.Pow(x, 4) 35 | }, 36 | l: 1, 37 | r: 3, 38 | n: 50, 39 | expectedValue: 48.4000006, 40 | expectedError: nil, 41 | }, 42 | "f(x) = x^4 with n = 100": { 43 | f: func(x float64) float64 { 44 | return math.Pow(x, 4) 45 | }, 46 | l: 1, 47 | r: 3, 48 | n: 100, 49 | expectedValue: 48.40000, 50 | expectedError: nil, 51 | }, 52 | "f(x) = x^4 with n = 500": { 53 | f: func(x float64) float64 { 54 | return math.Pow(x, 4) 55 | }, 56 | l: 1, 57 | r: 3, 58 | n: 100, 59 | expectedValue: 48.40000000, 60 | expectedError: nil, 61 | }, 62 | "f(x) = 1/x with n = 20": { 63 | f: func(x float64) float64 { 64 | return 1 / x 65 | }, 66 | l: 2, 67 | r: 5, 68 | n: 20, 69 | expectedValue: 0.916291, 70 | expectedError: nil, 71 | }, 72 | "f(x) = sin(x) with n = 20": { 73 | f: func(x float64) float64 { 74 | return math.Sin(x) 75 | }, 76 | l: 0, 77 | r: math.Pi / 2, 78 | n: 20, 79 | expectedValue: 1.000000, 80 | expectedError: nil, 81 | }, 82 | "f(x) = sin(x) with n = 0": { 83 | f: func(x float64) float64 { 84 | return math.Sin(x) 85 | }, 86 | l: 0, 87 | r: math.Pi / 2, 88 | n: 0, 89 | expectedValue: 0, 90 | expectedError: fmt.Errorf("Number of subdivisions n cannot be 0"), 91 | }, 92 | } 93 | 94 | for name, c := range cases { 95 | t.Run(name, func(t *testing.T) { 96 | result, err := integrate.Simpson(c.f, c.l, c.r, c.n) 97 | if result != 0 { 98 | assert.InEpsilon(t, result, c.expectedValue, 1e-4) 99 | } else { 100 | assert.Equal(t, result, c.expectedValue) 101 | } 102 | assert.Equal(t, err, c.expectedError) 103 | }) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /integrate/trapezoid_test.go: -------------------------------------------------------------------------------- 1 | package integrate_test 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "testing" 7 | 8 | "github.com/DzananGanic/numericalgo/integrate" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestTrapezoid(t *testing.T) { 13 | 14 | cases := map[string]struct { 15 | f func(x float64) float64 16 | l float64 17 | r float64 18 | n int 19 | expectedValue float64 20 | expectedError error 21 | }{ 22 | "f(x) = x^4 with n = 20": { 23 | f: func(x float64) float64 { 24 | return math.Pow(x, 4) 25 | }, 26 | l: 1, 27 | r: 3, 28 | n: 20, 29 | expectedValue: 48.48666, 30 | expectedError: nil, 31 | }, 32 | "f(x) = x^4 with n = 50": { 33 | f: func(x float64) float64 { 34 | return math.Pow(x, 4) 35 | }, 36 | l: 1, 37 | r: 3, 38 | n: 50, 39 | expectedValue: 48.41386, 40 | expectedError: nil, 41 | }, 42 | "f(x) = x^4 with n = 100": { 43 | f: func(x float64) float64 { 44 | return math.Pow(x, 4) 45 | }, 46 | l: 1, 47 | r: 3, 48 | n: 100, 49 | expectedValue: 48.400138, 50 | expectedError: nil, 51 | }, 52 | "f(x) = x^4 with n = 500": { 53 | f: func(x float64) float64 { 54 | return math.Pow(x, 4) 55 | }, 56 | l: 1, 57 | r: 3, 58 | n: 100, 59 | expectedValue: 48.400138, 60 | expectedError: nil, 61 | }, 62 | "f(x) = 1/x with n = 20": { 63 | f: func(x float64) float64 { 64 | return 1 / x 65 | }, 66 | l: 2, 67 | r: 5, 68 | n: 20, 69 | expectedValue: 0.91668422, 70 | expectedError: nil, 71 | }, 72 | "f(x) = sin(x) with n = 20": { 73 | f: func(x float64) float64 { 74 | return math.Sin(x) 75 | }, 76 | l: 0, 77 | r: math.Pi / 2, 78 | n: 20, 79 | expectedValue: 0.9994859, 80 | expectedError: nil, 81 | }, 82 | "f(x) = sin(x) with n = 0": { 83 | f: func(x float64) float64 { 84 | return math.Sin(x) 85 | }, 86 | l: 0, 87 | r: math.Pi / 2, 88 | n: 0, 89 | expectedValue: 0, 90 | expectedError: fmt.Errorf("Number of subdivisions n cannot be 0"), 91 | }, 92 | } 93 | 94 | for name, c := range cases { 95 | t.Run(name, func(t *testing.T) { 96 | result, err := integrate.Trapezoid(c.f, c.l, c.r, c.n) 97 | if result != 0 { 98 | assert.InEpsilon(t, result, c.expectedValue, 1e-4) 99 | } else { 100 | assert.Equal(t, result, c.expectedValue) 101 | } 102 | assert.Equal(t, err, c.expectedError) 103 | }) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /fit/exponential/exponential_test.go: -------------------------------------------------------------------------------- 1 | package exponential_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/DzananGanic/numericalgo" 7 | "github.com/DzananGanic/numericalgo/fit" 8 | "github.com/DzananGanic/numericalgo/fit/exponential" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestFitExponentialFit(t *testing.T) { 13 | cases := map[string]struct { 14 | x numericalgo.Vector 15 | y numericalgo.Vector 16 | p float64 17 | q float64 18 | expectedError error 19 | }{ 20 | "basic exponential fit": { 21 | x: numericalgo.Vector{0.3, 0.8, 1.2, 1.7, 2.4, 3.1, 3.8, 4.5, 5.1, 5.8, 6.5}, 22 | y: numericalgo.Vector{8.61, 7.94, 7.55, 6.85, 6.11, 5.17, 4.19, 3.41, 2.63, 1.77, 0.89}, 23 | p: 6.9758664327105, 24 | q: 0.5471581141119718, 25 | expectedError: nil, 26 | }, 27 | } 28 | 29 | for name, c := range cases { 30 | t.Run(name, func(t *testing.T) { 31 | ef := exponential.New() 32 | err := ef.Fit(c.x, c.y) 33 | coef := ef.Coeff 34 | assert.InEpsilon(t, coef[0], c.p, 1e-10) 35 | assert.InEpsilon(t, coef[1], c.q, 1e-10) 36 | assert.Equal(t, c.expectedError, err) 37 | }) 38 | } 39 | } 40 | 41 | func TestPredictExponentialFit(t *testing.T) { 42 | 43 | cases := map[string]struct { 44 | x numericalgo.Vector 45 | y numericalgo.Vector 46 | valueToPredict float64 47 | expectedResult float64 48 | expectedError error 49 | }{ 50 | "basic predict exponential fit": { 51 | x: numericalgo.Vector{0.3, 0.8, 1.2, 1.7, 2.4, 3.1, 3.8, 4.5, 5.1, 5.8, 6.5}, 52 | y: numericalgo.Vector{8.61, 7.94, 7.55, 6.85, 6.11, 5.17, 4.19, 3.41, 2.63, 1.77, 0.89}, 53 | valueToPredict: 1.2, 54 | expectedResult: 7.632456, 55 | expectedError: nil, 56 | }, 57 | } 58 | 59 | for name, c := range cases { 60 | t.Run(name, func(t *testing.T) { 61 | ef := exponential.New() 62 | err := ef.Fit(c.x, c.y) 63 | result := ef.Predict(c.valueToPredict) 64 | assert.InEpsilon(t, c.expectedResult, result, 1e-4) 65 | assert.Equal(t, c.expectedError, err) 66 | }) 67 | } 68 | } 69 | 70 | func TestPredictMultiExponentialFit(t *testing.T) { 71 | cases := map[string]struct { 72 | x numericalgo.Vector 73 | y numericalgo.Vector 74 | valuesToPredict numericalgo.Vector 75 | expectedResult numericalgo.Vector 76 | expectedError error 77 | }{ 78 | "basic multi exponential fit prediction": { 79 | x: numericalgo.Vector{0.3, 0.8, 1.2, 1.7, 2.4, 3.1, 3.8, 4.5, 5.1, 5.8, 6.5}, 80 | y: numericalgo.Vector{8.61, 7.94, 7.55, 6.85, 6.11, 5.17, 4.19, 3.41, 2.63, 1.77, 0.89}, 81 | valuesToPredict: numericalgo.Vector{1.9, 7.0, 8.0}, 82 | expectedResult: numericalgo.Vector{8.01546, 10.8059732, 11.353131}, 83 | expectedError: nil, 84 | }, 85 | } 86 | 87 | for name, c := range cases { 88 | t.Run(name, func(t *testing.T) { 89 | ef := exponential.New() 90 | err := ef.Fit(c.x, c.y) 91 | result := fit.PredictMulti(ef, c.valuesToPredict) 92 | assert.InEpsilonSlice(t, c.expectedResult, result, 1e-4) 93 | assert.Equal(t, c.expectedError, err) 94 | }) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /fit/poly/poly_test.go: -------------------------------------------------------------------------------- 1 | package poly_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/DzananGanic/numericalgo/fit" 7 | 8 | "github.com/DzananGanic/numericalgo" 9 | "github.com/DzananGanic/numericalgo/fit/poly" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestFitPolyFit(t *testing.T) { 14 | cases := map[string]struct { 15 | x numericalgo.Vector 16 | y numericalgo.Vector 17 | n int 18 | coef numericalgo.Vector 19 | expectedError error 20 | }{ 21 | "basic poly fit": { 22 | x: numericalgo.Vector{0.0, 1.0, 2.0, 3.0, 4.0, 5.0}, 23 | y: numericalgo.Vector{0.0, 0.8, 0.9, 0.1, -0.8, -1.0}, 24 | n: 3, 25 | coef: numericalgo.Vector{-0.0396825, 1.693121, -0.8134920, 0.0870370}, 26 | expectedError: nil, 27 | }, 28 | } 29 | 30 | for name, c := range cases { 31 | t.Run(name, func(t *testing.T) { 32 | pf := poly.New() 33 | err := pf.Fit(c.x, c.y, c.n) 34 | r := pf.Coeff.IsSimilar(c.coef, 1e-4) 35 | assert.Equal(t, true, r) 36 | assert.Equal(t, c.expectedError, err) 37 | }) 38 | } 39 | } 40 | 41 | func TestPredictPolyFit(t *testing.T) { 42 | cases := map[string]struct { 43 | x numericalgo.Vector 44 | y numericalgo.Vector 45 | n int 46 | valueToPredict float64 47 | expectedResult float64 48 | expectedError error 49 | }{ 50 | "basic poly fit prediction": { 51 | x: numericalgo.Vector{0.0, 1.0, 2.0, 3.0, 4.0, 5.0}, 52 | y: numericalgo.Vector{0.0, 0.8, 0.9, 0.1, -0.8, -1.0}, 53 | n: 3, 54 | valueToPredict: 2.0, 55 | expectedResult: 0.7888888888889196, 56 | expectedError: nil, 57 | }, 58 | "second poly fit prediction": { 59 | x: numericalgo.Vector{0.0, 1.0, 2.0, 3.0, 4.0, 5.0}, 60 | y: numericalgo.Vector{0.0, 0.8, 0.9, 0.1, -0.8, -1.0}, 61 | n: 3, 62 | valueToPredict: 2.5, 63 | expectedResult: 0.4687499999999935, 64 | expectedError: nil, 65 | }, 66 | } 67 | 68 | for name, c := range cases { 69 | t.Run(name, func(t *testing.T) { 70 | pf := poly.New() 71 | err := pf.Fit(c.x, c.y, c.n) 72 | result := pf.Predict(c.valueToPredict) 73 | assert.InEpsilon(t, c.expectedResult, result, 1e-2) 74 | assert.Equal(t, c.expectedError, err) 75 | }) 76 | } 77 | } 78 | 79 | func TestPredictMultiPolyFit(t *testing.T) { 80 | cases := map[string]struct { 81 | x numericalgo.Vector 82 | y numericalgo.Vector 83 | n int 84 | valuesToPredict numericalgo.Vector 85 | expectedResult numericalgo.Vector 86 | expectedError error 87 | }{ 88 | "basic multi poly fit prediction": { 89 | x: numericalgo.Vector{0.0, 1.0, 2.0, 3.0, 4.0, 5.0}, 90 | y: numericalgo.Vector{0.0, 0.8, 0.9, 0.1, -0.8, -1.0}, 91 | n: 3, 92 | valuesToPredict: numericalgo.Vector{2.0, 6.0, 7.0, 8.0}, 93 | expectedResult: numericalgo.Vector{0.788888888, -0.366666, 1.8047619, 6.00476190}, 94 | expectedError: nil, 95 | }, 96 | } 97 | 98 | for name, c := range cases { 99 | t.Run(name, func(t *testing.T) { 100 | pf := poly.New() 101 | err := pf.Fit(c.x, c.y, c.n) 102 | result := fit.PredictMulti(pf, c.valuesToPredict) 103 | assert.InEpsilonSlice(t, c.expectedResult, result, 1e-4) 104 | assert.Equal(t, c.expectedError, err) 105 | }) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /fit/linear/linear_test.go: -------------------------------------------------------------------------------- 1 | package linear_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/DzananGanic/numericalgo" 7 | "github.com/DzananGanic/numericalgo/fit" 8 | "github.com/DzananGanic/numericalgo/fit/linear" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestFitLinearFit(t *testing.T) { 13 | cases := map[string]struct { 14 | x numericalgo.Vector 15 | y numericalgo.Vector 16 | p float64 17 | q float64 18 | expectedError error 19 | }{ 20 | "basic linear fit": { 21 | x: numericalgo.Vector{0.3, 0.8, 1.2, 1.7, 2.4, 3.1, 3.8, 4.5, 5.1, 5.8, 6.5}, 22 | y: numericalgo.Vector{8.61, 7.94, 7.55, 6.85, 6.11, 5.17, 4.19, 3.41, 2.63, 1.77, 0.89}, 23 | p: -1.246552501126634, 24 | q: 8.99987709451432, 25 | expectedError: nil, 26 | }, 27 | } 28 | 29 | for name, c := range cases { 30 | t.Run(name, func(t *testing.T) { 31 | lf := linear.New() 32 | err := lf.Fit(c.x, c.y) 33 | coef := lf.Coeff 34 | assert.InEpsilon(t, coef[1], c.p, 1e-10) 35 | assert.InEpsilon(t, coef[0], c.q, 1e-10) 36 | assert.Equal(t, c.expectedError, err) 37 | }) 38 | } 39 | } 40 | 41 | func TestPredictLinearFit(t *testing.T) { 42 | 43 | cases := map[string]struct { 44 | x numericalgo.Vector 45 | y numericalgo.Vector 46 | valueToPredict float64 47 | expectedResult float64 48 | expectedError error 49 | }{ 50 | "basic linear fit prediction": { 51 | x: numericalgo.Vector{0.3, 0.8, 1.2, 1.7, 2.4, 3.1, 3.8, 4.5, 5.1, 5.8, 6.5}, 52 | y: numericalgo.Vector{8.61, 7.94, 7.55, 6.85, 6.11, 5.17, 4.19, 3.41, 2.63, 1.77, 0.89}, 53 | valueToPredict: 1.9, 54 | expectedResult: 6.631427342373716, 55 | expectedError: nil, 56 | }, 57 | "second linear fit prediction": { 58 | x: numericalgo.Vector{1.3, 2.1, 3.7, 4.2}, 59 | y: numericalgo.Vector{2.2, 5.8, 10.2, 11.8}, 60 | valueToPredict: 1.9, 61 | expectedResult: 3.19383*1.9 - 1.52256, 62 | expectedError: nil, 63 | }, 64 | } 65 | 66 | for name, c := range cases { 67 | t.Run(name, func(t *testing.T) { 68 | lf := linear.New() 69 | err := lf.Fit(c.x, c.y) 70 | result := lf.Predict(c.valueToPredict) 71 | assert.InEpsilon(t, c.expectedResult, result, 1e-4) 72 | assert.Equal(t, c.expectedError, err) 73 | }) 74 | } 75 | } 76 | 77 | func TestPredictMultiLinearFit(t *testing.T) { 78 | cases := map[string]struct { 79 | x numericalgo.Vector 80 | y numericalgo.Vector 81 | valuesToPredict numericalgo.Vector 82 | expectedResult numericalgo.Vector 83 | expectedError error 84 | }{ 85 | "basic multi linear fit prediction": { 86 | x: numericalgo.Vector{0.3, 0.8, 1.2, 1.7, 2.4, 3.1, 3.8, 4.5, 5.1, 5.8, 6.5}, 87 | y: numericalgo.Vector{8.61, 7.94, 7.55, 6.85, 6.11, 5.17, 4.19, 3.41, 2.63, 1.77, 0.89}, 88 | valuesToPredict: numericalgo.Vector{1.9, 7.0, 8.0}, 89 | expectedResult: numericalgo.Vector{6.631427, 0.27400958, -0.972542}, 90 | expectedError: nil, 91 | }, 92 | } 93 | 94 | for name, c := range cases { 95 | t.Run(name, func(t *testing.T) { 96 | lf := linear.New() 97 | err := lf.Fit(c.x, c.y) 98 | result := fit.PredictMulti(lf, c.valuesToPredict) 99 | assert.InEpsilonSlice(t, c.expectedResult, result, 1e-4) 100 | assert.Equal(t, c.expectedError, err) 101 | }) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /vector.go: -------------------------------------------------------------------------------- 1 | package numericalgo 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | ) 7 | 8 | // Vector is the []float64 which has custom methods needed for vector operations. 9 | type Vector []float64 10 | 11 | // Dim returns the dimension of the vector. 12 | func (v Vector) Dim() int { 13 | return len(v) 14 | } 15 | 16 | // AreDimsEqual receives another vector as a parameter. Method returns true if the dimensions of the vectors are equal. 17 | func (v Vector) AreDimsEqual(v2 Vector) bool { 18 | return v.Dim() == v2.Dim() 19 | } 20 | 21 | // IsSimilar receives another vector and tolerance as a parameter. It checks whether the two vectors are similar within the provided tolerance. 22 | func (v Vector) IsSimilar(v2 Vector, tol float64) bool { 23 | 24 | if !v.AreDimsEqual(v2) { 25 | return false 26 | } 27 | 28 | for i := range v { 29 | if math.Abs(v[i]-v2[i]) > tol { 30 | return false 31 | } 32 | } 33 | 34 | return true 35 | } 36 | 37 | // Sum returns the sum of all elements in the vector 38 | func (v Vector) Sum() float64 { 39 | var sum float64 40 | for _, val := range v { 41 | sum += val 42 | } 43 | return sum 44 | } 45 | 46 | // Power receives a float as a parameter. It returns the vector whose elements are x^n. 47 | func (v Vector) Power(n float64) Vector { 48 | 49 | var result Vector 50 | 51 | for _, val := range v { 52 | result = append(result, math.Pow(val, n)) 53 | //v[i] = math.Pow(val, n) 54 | } 55 | 56 | return result 57 | } 58 | 59 | // Add receives another vector as a parameter. It adds the two vectors and returns the result vector and the error (if there is any). 60 | func (v Vector) Add(v2 Vector) (Vector, error) { 61 | var r Vector 62 | 63 | if !v.AreDimsEqual(v2) { 64 | return r, fmt.Errorf("Dimensions must match") 65 | } 66 | 67 | for index := range v { 68 | r = append(r, v[index]+v2[index]) 69 | } 70 | 71 | return r, nil 72 | } 73 | 74 | // Subtract receives another vector as a parameter. It subtracts the two vectors and returns the result vector and an error (if there is any). 75 | func (v Vector) Subtract(v2 Vector) (Vector, error) { 76 | var r Vector 77 | 78 | if !v.AreDimsEqual(v2) { 79 | return r, fmt.Errorf("Dimensions must match") 80 | } 81 | 82 | for index := range v { 83 | r = append(r, v[index]-v2[index]) 84 | } 85 | 86 | return r, nil 87 | } 88 | 89 | // Dot receives another vector as a parameter. It calculates the dot product between the two vectors and returns the float result and an error (if there is any). 90 | func (v Vector) Dot(v2 Vector) (float64, error) { 91 | var r float64 92 | 93 | if !v.AreDimsEqual(v2) { 94 | return r, fmt.Errorf("Dimensions must match") 95 | } 96 | 97 | for index := range v { 98 | r += v[index] * v2[index] 99 | } 100 | 101 | return r, nil 102 | } 103 | 104 | // MultiplyByScalar receives a scalar as a parameter. It multiplies all the elements of the vector with provided scalar and returns the result vector. 105 | func (v Vector) MultiplyByScalar(s float64) Vector { 106 | var r Vector 107 | 108 | for index := range v { 109 | r = append(r, v[index]*s) 110 | } 111 | 112 | return r 113 | } 114 | 115 | // DivideByScalar receives a scalar as a parameter. It divides all the elements of the vector by provided scalar and returns the result vector. 116 | func (v Vector) DivideByScalar(s float64) (Vector, error) { 117 | var r Vector 118 | 119 | if s == 0 { 120 | return r, fmt.Errorf("Cannot divide by zero") 121 | } 122 | 123 | for index := range v { 124 | r = append(r, v[index]/s) 125 | } 126 | 127 | return r, nil 128 | } 129 | -------------------------------------------------------------------------------- /interpolate/linear/linear_test.go: -------------------------------------------------------------------------------- 1 | package linear_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "fmt" 7 | 8 | "github.com/DzananGanic/numericalgo/interpolate" 9 | "github.com/DzananGanic/numericalgo/interpolate/linear" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestLinearCanFit(t *testing.T) { 14 | cases := map[string]struct { 15 | x []float64 16 | y []float64 17 | expectedError error 18 | }{ 19 | "basic linear fit": { 20 | x: []float64{1.3, 1.8, 2.5, 3.1, 3.8, 4.4, 4.9, 5.5, 6.2}, 21 | y: []float64{3.37, 4.45, 4.81, 3.96, 3.31, 2.72, 3.02, 3.43, 4.07}, 22 | expectedError: nil, 23 | }, 24 | "wrong x and y size": { 25 | x: []float64{1.3, 1.8, 2.5, 3.1, 3.8, 4.4, 4.9, 5.5, 4.07}, 26 | y: []float64{3.37, 4.45, 4.81, 3.96, 3.31}, 27 | expectedError: fmt.Errorf("X and Y sizes do not match"), 28 | }, 29 | } 30 | 31 | for name, c := range cases { 32 | t.Run(name, func(t *testing.T) { 33 | li := linear.New() 34 | err := li.Fit(c.x, c.y) 35 | assert.Equal(t, c.expectedError, err) 36 | }) 37 | } 38 | } 39 | 40 | func TestLinearCanInterpolateSingleValue(t *testing.T) { 41 | cases := map[string]struct { 42 | x []float64 43 | y []float64 44 | valueToInterpolate float64 45 | expectedEstimate float64 46 | expectedError error 47 | }{ 48 | "basic linear single-valued interpolation": { 49 | x: []float64{1.3, 1.8, 2.5, 3.1, 3.8, 4.4, 4.9, 5.5, 6.2}, 50 | y: []float64{3.37, 4.45, 4.81, 3.96, 3.31, 2.72, 3.02, 3.43, 4.07}, 51 | valueToInterpolate: 5.1, 52 | expectedEstimate: 3.1566666666666663, 53 | expectedError: nil, 54 | }, 55 | "testing binary search for nearest neighbor - case where the interpolation value should be between indexes 0 and 1": { 56 | x: []float64{1.3, 1.8, 2.5, 3.1, 3.8, 4.4, 4.9, 5.5, 6.2}, 57 | y: []float64{3.37, 4.45, 4.81, 3.96, 3.31, 2.72, 3.02, 3.43, 4.07}, 58 | valueToInterpolate: 1.5, 59 | expectedEstimate: 3.802, 60 | expectedError: nil, 61 | }, 62 | "testing binary search for nearest neighbor - case where the interpolation value should be between last two indexes": { 63 | x: []float64{1.3, 1.8, 2.5, 3.1, 3.8, 4.4, 4.9, 5.5, 6.2}, 64 | y: []float64{3.37, 4.45, 4.81, 3.96, 3.31, 2.72, 3.02, 3.43, 4.07}, 65 | valueToInterpolate: 5.8, 66 | expectedEstimate: 3.704285714285714, 67 | expectedError: nil, 68 | }, 69 | "unsorted x and y test": { 70 | x: []float64{1.8, 4.9, 2.5, 1.3, 4.4, 3.1, 3.8, 5.5, 6.2}, 71 | y: []float64{4.45, 3.02, 4.81, 3.37, 2.72, 3.96, 3.31, 3.43, 4.07}, 72 | valueToInterpolate: 2.2, 73 | expectedEstimate: 4.655714285714286, 74 | expectedError: nil, 75 | }, 76 | "big value to interpolate test": { 77 | x: []float64{1.8, 4.9, 2.5, 1.3, 4.4, 3.1, 3.8, 5.5, 6.2}, 78 | y: []float64{4.45, 3.02, 4.81, 3.37, 2.72, 3.96, 3.31, 3.43, 4.07}, 79 | valueToInterpolate: 1000, 80 | expectedEstimate: 0, 81 | expectedError: fmt.Errorf("Value to interpolate is too large and not in range"), 82 | }, 83 | "too small value to interpolate test": { 84 | x: []float64{1.8, 4.9, 2.5, 1.3, 4.4, 3.1, 3.8, 5.5, 6.2}, 85 | y: []float64{4.45, 3.02, 4.81, 3.37, 2.72, 3.96, 3.31, 3.43, 4.07}, 86 | valueToInterpolate: -20, 87 | expectedEstimate: 0, 88 | expectedError: fmt.Errorf("Value to interpolate is too small and not in range"), 89 | }, 90 | } 91 | 92 | for name, c := range cases { 93 | t.Run(name, func(t *testing.T) { 94 | li := linear.New() 95 | li.Fit(c.x, c.y) 96 | estimate, err := interpolate.WithSingle(li, c.valueToInterpolate) 97 | assert.Equal(t, c.expectedEstimate, estimate) 98 | assert.Equal(t, c.expectedError, err) 99 | }) 100 | } 101 | } 102 | 103 | func TestLinearCanInterpolateMultipleValues(t *testing.T) { 104 | cases := map[string]struct { 105 | x []float64 106 | y []float64 107 | valuesToInterpolate []float64 108 | expectedEstimates []float64 109 | expectedError error 110 | }{ 111 | "basic linear multiple-value interpolation": { 112 | x: []float64{1.3, 1.8, 2.5, 3.1, 3.8, 4.4, 4.9, 5.5, 6.2}, 113 | y: []float64{3.37, 4.45, 4.81, 3.96, 3.31, 2.72, 3.02, 3.43, 4.07}, 114 | valuesToInterpolate: []float64{2.2, 5.1, 1.5}, 115 | expectedEstimates: []float64{4.655714285714286, 3.1566666666666663, 3.802}, 116 | expectedError: nil, 117 | }, 118 | } 119 | 120 | for name, c := range cases { 121 | t.Run(name, func(t *testing.T) { 122 | li := linear.New() 123 | li.Fit(c.x, c.y) 124 | estimates, err := interpolate.WithMulti(li, c.valuesToInterpolate) 125 | assert.Equal(t, c.expectedEstimates, estimates) 126 | assert.Equal(t, c.expectedError, err) 127 | }) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /interpolate/lagrange/lagrange_test.go: -------------------------------------------------------------------------------- 1 | package lagrange_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/DzananGanic/numericalgo/interpolate" 8 | "github.com/DzananGanic/numericalgo/interpolate/lagrange" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestLagrangeCanFit(t *testing.T) { 13 | cases := map[string]struct { 14 | x []float64 15 | y []float64 16 | expectedError error 17 | }{ 18 | "basic lagrange fit": { 19 | x: []float64{1.3, 1.8, 2.5, 3.1, 3.8, 4.4, 4.9, 5.5, 6.2}, 20 | y: []float64{3.37, 4.45, 4.81, 3.96, 3.31, 2.72, 3.02, 3.43, 4.07}, 21 | expectedError: nil, 22 | }, 23 | "wrong x and y sizes lagrange fit": { 24 | x: []float64{1.3, 1.8, 2.5, 3.1, 3.8, 4.4, 4.9, 5.5, 4.07}, 25 | y: []float64{3.37, 4.45, 4.81, 3.96, 3.31}, 26 | expectedError: fmt.Errorf("X and Y sizes do not match"), 27 | }, 28 | } 29 | 30 | for name, c := range cases { 31 | t.Run(name, func(t *testing.T) { 32 | lgi := lagrange.New() 33 | //lgi := interpolation.NewLagrange() 34 | err := lgi.Fit(c.x, c.y) 35 | assert.Equal(t, c.expectedError, err) 36 | }) 37 | } 38 | } 39 | 40 | func TestLagrangeCanInterpolateSingleValue(t *testing.T) { 41 | cases := map[string]struct { 42 | x []float64 43 | y []float64 44 | valueToInterpolate float64 45 | expectedEstimate float64 46 | expectedError error 47 | }{ 48 | "basic lagrange single-value interpolation": { 49 | x: []float64{1.3, 1.8, 2.5, 3.1, 3.8, 4.4, 4.9, 5.5, 6.2}, 50 | y: []float64{3.37, 4.45, 4.81, 3.96, 3.31, 2.72, 3.02, 3.43, 4.07}, 51 | valueToInterpolate: 5.1, 52 | expectedEstimate: 3.3068917458526563, 53 | expectedError: nil, 54 | }, 55 | "testing binary search for nearest neighbor - case where the interpolation value should be between indexes 0 and 1": { 56 | x: []float64{1.3, 1.8, 2.5, 3.1, 3.8, 4.4, 4.9, 5.5, 6.2}, 57 | y: []float64{3.37, 4.45, 4.81, 3.96, 3.31, 2.72, 3.02, 3.43, 4.07}, 58 | valueToInterpolate: 1.5, 59 | expectedEstimate: 3.224674773993458, 60 | expectedError: nil, 61 | }, 62 | "testing binary search for nearest neighbor - case where the interpolation value should be between last two indexes": { 63 | x: []float64{1.3, 1.8, 2.5, 3.1, 3.8, 4.4, 4.9, 5.5, 6.2}, 64 | y: []float64{3.37, 4.45, 4.81, 3.96, 3.31, 2.72, 3.02, 3.43, 4.07}, 65 | valueToInterpolate: 5.8, 66 | expectedEstimate: 2.785117811403902, 67 | expectedError: nil, 68 | }, 69 | "unsorted x and y test": { 70 | x: []float64{1.8, 4.9, 2.5, 1.3, 4.4, 3.1, 3.8, 5.5, 6.2}, 71 | y: []float64{4.45, 3.02, 4.81, 3.37, 2.72, 3.96, 3.31, 3.43, 4.07}, 72 | valueToInterpolate: 2.2, 73 | expectedEstimate: 5.134038366257703, 74 | expectedError: nil, 75 | }, 76 | "big value to interpolate test": { 77 | x: []float64{1.8, 4.9, 2.5, 1.3, 4.4, 3.1, 3.8, 5.5, 6.2}, 78 | y: []float64{4.45, 3.02, 4.81, 3.37, 2.72, 3.96, 3.31, 3.43, 4.07}, 79 | valueToInterpolate: 1000, 80 | expectedEstimate: 0, 81 | expectedError: fmt.Errorf("Value to interpolate is too large and not in range"), 82 | }, 83 | "too small value to interpolate test": { 84 | x: []float64{1.8, 4.9, 2.5, 1.3, 4.4, 3.1, 3.8, 5.5, 6.2}, 85 | y: []float64{4.45, 3.02, 4.81, 3.37, 2.72, 3.96, 3.31, 3.43, 4.07}, 86 | valueToInterpolate: -20, 87 | expectedEstimate: 0, 88 | expectedError: fmt.Errorf("Value to interpolate is too small and not in range"), 89 | }, 90 | "same x values error": { 91 | x: []float64{1.8, 1.8, 1.8, 1.3, 4.4, 3.1, 3.8, 5.5, 6.2}, 92 | y: []float64{4.45, 3.02, 4.81, 3.37, 2.72, 3.96, 3.31, 3.43, 4.07}, 93 | valueToInterpolate: -20, 94 | expectedEstimate: 0, 95 | expectedError: fmt.Errorf("There are at least 2 same X values. This will result in division by zero in Lagrange interpolation"), 96 | }, 97 | } 98 | 99 | for name, c := range cases { 100 | t.Run(name, func(t *testing.T) { 101 | lg := lagrange.New() 102 | lg.Fit(c.x, c.y) 103 | estimate, err := interpolate.WithSingle(lg, c.valueToInterpolate) 104 | assert.Equal(t, c.expectedEstimate, estimate) 105 | assert.Equal(t, c.expectedError, err) 106 | }) 107 | } 108 | } 109 | 110 | func TestLagrangeCanInterpolateMultipleValues(t *testing.T) { 111 | cases := map[string]struct { 112 | x []float64 113 | y []float64 114 | valuesToInterpolate []float64 115 | expectedEstimates []float64 116 | expectedError error 117 | }{ 118 | "basic lagrange multiple-value interpolation": { 119 | x: []float64{1.3, 1.8, 2.5, 3.1, 3.8, 4.4, 4.9, 5.5, 6.2}, 120 | y: []float64{3.37, 4.45, 4.81, 3.96, 3.31, 2.72, 3.02, 3.43, 4.07}, 121 | valuesToInterpolate: []float64{2.2, 5.1, 1.5}, 122 | expectedEstimates: []float64{5.134038366257704, 3.3068917458526563, 3.224674773993458}, 123 | expectedError: nil, 124 | }, 125 | } 126 | 127 | for name, c := range cases { 128 | t.Run(name, func(t *testing.T) { 129 | lg := lagrange.New() 130 | lg.Fit(c.x, c.y) 131 | estimates, err := interpolate.WithMulti(lg, c.valuesToInterpolate) 132 | assert.Equal(t, c.expectedEstimates, estimates) 133 | assert.Equal(t, c.expectedError, err) 134 | }) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /vector_test.go: -------------------------------------------------------------------------------- 1 | package numericalgo_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/DzananGanic/numericalgo" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestVectorDim(t *testing.T) { 12 | cases := map[string]struct { 13 | vector numericalgo.Vector 14 | expectedResult int 15 | }{ 16 | "basic test": { 17 | vector: numericalgo.Vector{1, 2, 3}, 18 | expectedResult: 3, 19 | }, 20 | "empty vector": { 21 | vector: numericalgo.Vector{}, 22 | expectedResult: 0, 23 | }, 24 | "single element vector": { 25 | vector: numericalgo.Vector{1}, 26 | expectedResult: 1, 27 | }, 28 | } 29 | 30 | for name, c := range cases { 31 | t.Run(name, func(t *testing.T) { 32 | assert.Equal(t, c.expectedResult, c.vector.Dim()) 33 | }) 34 | } 35 | 36 | } 37 | 38 | func TestVectorAreDimsEqual(t *testing.T) { 39 | cases := map[string]struct { 40 | vector1 numericalgo.Vector 41 | vector2 numericalgo.Vector 42 | expectedResult bool 43 | }{ 44 | "basic equality test": { 45 | vector1: numericalgo.Vector{1, 2, 3}, 46 | vector2: numericalgo.Vector{3, 1, 0}, 47 | expectedResult: true, 48 | }, 49 | "different dimensions": { 50 | vector1: numericalgo.Vector{1, 2}, 51 | vector2: numericalgo.Vector{1, 2, 3}, 52 | expectedResult: false, 53 | }, 54 | "testing with empty vector": { 55 | vector1: numericalgo.Vector{1}, 56 | vector2: numericalgo.Vector{}, 57 | expectedResult: false, 58 | }, 59 | } 60 | 61 | for name, c := range cases { 62 | t.Run(name, func(t *testing.T) { 63 | assert.Equal(t, c.expectedResult, c.vector1.AreDimsEqual(c.vector2)) 64 | }) 65 | } 66 | } 67 | 68 | func TestAddVectors(t *testing.T) { 69 | cases := map[string]struct { 70 | vector1 numericalgo.Vector 71 | vector2 numericalgo.Vector 72 | expectedResult numericalgo.Vector 73 | expectedError error 74 | }{ 75 | "add vectors basic test": { 76 | vector1: numericalgo.Vector{1, 2, 3}, 77 | vector2: numericalgo.Vector{3, 1, 0}, 78 | expectedResult: numericalgo.Vector{4, 3, 3}, 79 | expectedError: nil, 80 | }, 81 | "add with wrong dimensions": { 82 | vector1: numericalgo.Vector{1, 2}, 83 | vector2: numericalgo.Vector{1, 2, 3}, 84 | expectedResult: nil, 85 | expectedError: fmt.Errorf("Dimensions must match"), 86 | }, 87 | } 88 | 89 | for name, c := range cases { 90 | t.Run(name, func(t *testing.T) { 91 | result, err := c.vector1.Add(c.vector2) 92 | 93 | assert.Equal(t, c.expectedResult, result) 94 | assert.Equal(t, c.expectedError, err) 95 | }) 96 | } 97 | } 98 | 99 | func TestSubtractVectors(t *testing.T) { 100 | cases := map[string]struct { 101 | vector1 numericalgo.Vector 102 | vector2 numericalgo.Vector 103 | expectedResult numericalgo.Vector 104 | expectedError error 105 | }{ 106 | "subtract vectors basic test": { 107 | vector1: numericalgo.Vector{1, 2, 3}, 108 | vector2: numericalgo.Vector{3, 1, 0}, 109 | expectedResult: numericalgo.Vector{-2, 1, 3}, 110 | expectedError: nil, 111 | }, 112 | "subtract with wrong dimensions": { 113 | vector1: numericalgo.Vector{1, 2}, 114 | vector2: numericalgo.Vector{1, 2, 3}, 115 | expectedResult: nil, 116 | expectedError: fmt.Errorf("Dimensions must match"), 117 | }, 118 | } 119 | 120 | for name, c := range cases { 121 | t.Run(name, func(t *testing.T) { 122 | result, err := c.vector1.Subtract(c.vector2) 123 | 124 | assert.Equal(t, c.expectedResult, result) 125 | assert.Equal(t, c.expectedError, err) 126 | }) 127 | } 128 | } 129 | 130 | func TestVectorDotProduct(t *testing.T) { 131 | cases := map[string]struct { 132 | vector1 numericalgo.Vector 133 | vector2 numericalgo.Vector 134 | expectedResult float64 135 | expectedError error 136 | }{ 137 | "basic dot product test": { 138 | vector1: numericalgo.Vector{1, 2, 3}, 139 | vector2: numericalgo.Vector{4, 5, 6}, 140 | expectedResult: 32, 141 | expectedError: nil, 142 | }, 143 | "dot product with wrong dimensions": { 144 | vector1: numericalgo.Vector{1, 2}, 145 | vector2: numericalgo.Vector{1, 2, 3}, 146 | expectedResult: 0, 147 | expectedError: fmt.Errorf("Dimensions must match"), 148 | }, 149 | } 150 | 151 | for name, c := range cases { 152 | t.Run(name, func(t *testing.T) { 153 | result, err := c.vector1.Dot(c.vector2) 154 | 155 | assert.Equal(t, c.expectedResult, result) 156 | assert.Equal(t, c.expectedError, err) 157 | }) 158 | } 159 | } 160 | 161 | func TestMultiplyVectorByScalar(t *testing.T) { 162 | cases := map[string]struct { 163 | vector numericalgo.Vector 164 | scalar float64 165 | expectedResult numericalgo.Vector 166 | }{ 167 | "basic multiply vector by scalar": { 168 | vector: numericalgo.Vector{1, 2, 3}, 169 | scalar: 5, 170 | expectedResult: numericalgo.Vector{5, 10, 15}, 171 | }, 172 | "multiply vector by 0": { 173 | vector: numericalgo.Vector{1, 2}, 174 | scalar: 0, 175 | expectedResult: numericalgo.Vector{0, 0}, 176 | }, 177 | } 178 | 179 | for name, c := range cases { 180 | t.Run(name, func(t *testing.T) { 181 | result := c.vector.MultiplyByScalar(c.scalar) 182 | assert.Equal(t, c.expectedResult, result) 183 | }) 184 | } 185 | 186 | } 187 | 188 | func TestDivideVectorByScalar(t *testing.T) { 189 | cases := map[string]struct { 190 | vector numericalgo.Vector 191 | scalar float64 192 | expectedResult numericalgo.Vector 193 | expectedError error 194 | }{ 195 | "basic vector division by scalar": { 196 | vector: numericalgo.Vector{1, 2, 3}, 197 | scalar: 5.0, 198 | expectedResult: numericalgo.Vector{0.2, 0.4, 0.6}, 199 | expectedError: nil, 200 | }, 201 | "vector division by 0": { 202 | vector: numericalgo.Vector{1, 2}, 203 | scalar: 0, 204 | expectedResult: nil, 205 | expectedError: fmt.Errorf("Cannot divide by zero"), 206 | }, 207 | } 208 | 209 | for name, c := range cases { 210 | t.Run(name, func(t *testing.T) { 211 | result, err := c.vector.DivideByScalar(c.scalar) 212 | assert.Equal(t, c.expectedResult, result) 213 | assert.Equal(t, c.expectedError, err) 214 | }) 215 | } 216 | } 217 | 218 | func TestVectorPower(t *testing.T) { 219 | cases := map[string]struct { 220 | vector numericalgo.Vector 221 | power float64 222 | expectedResult numericalgo.Vector 223 | }{ 224 | "vector elements squared": { 225 | vector: numericalgo.Vector{1, 2, 3}, 226 | power: 2, 227 | expectedResult: numericalgo.Vector{1, 4, 9}, 228 | }, 229 | } 230 | 231 | for name, c := range cases { 232 | t.Run(name, func(t *testing.T) { 233 | result := c.vector.Power(c.power) 234 | assert.Equal(t, c.expectedResult, result) 235 | }) 236 | } 237 | } 238 | 239 | func TestVectorIsSimilar(t *testing.T) { 240 | cases := map[string]struct { 241 | vector1 numericalgo.Vector 242 | vector2 numericalgo.Vector 243 | tolerance float64 244 | expectedResult bool 245 | }{ 246 | "test non-similar vectors": { 247 | vector1: numericalgo.Vector{1.2, 2.5}, 248 | vector2: numericalgo.Vector{1, 2}, 249 | tolerance: 0.01, 250 | expectedResult: false, 251 | }, 252 | "test similar vectors": { 253 | vector1: numericalgo.Vector{1.000000001, 2.0000000001}, 254 | vector2: numericalgo.Vector{1, 2}, 255 | tolerance: 0.1, 256 | expectedResult: true, 257 | }, 258 | } 259 | 260 | for name, c := range cases { 261 | t.Run(name, func(t *testing.T) { 262 | result := c.vector1.IsSimilar(c.vector2, c.tolerance) 263 | assert.Equal(t, c.expectedResult, result) 264 | }) 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # numericalgo 2 | 3 | [![Build Status](https://travis-ci.org/DzananGanic/numericalgo.svg?branch=master)](https://travis-ci.org/DzananGanic/numericalgo) 4 | [![Coverage Status](https://coveralls.io/repos/github/DzananGanic/numericalgo/badge.svg?branch=master)](https://coveralls.io/github/DzananGanic/numericalgo?branch=master) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/DzananGanic/numericalgo)](https://goreportcard.com/report/github.com/DzananGanic/numericalgo) 6 | 7 | numericalgo is a set of numerical methods implemented in Golang. The idea was to implement everything from scratch - not just the methods, but the custom types as well (matrices, vectors etc.) 8 | 9 | ## Installation 10 | numericalgo does not use any third party libraries. For getting it to run on your machine, you just run standard go get: 11 | ```go 12 | go get github.com/DzananGanic/numericalgo 13 | ``` 14 | 15 | ## Currently implemented methods: 16 | 17 | - [Interpolation:](https://github.com/DzananGanic/numericalgo/tree/master/interpolate) ( [Usage](https://github.com/DzananGanic/numericalgo#interpolation) ) 18 | - [Linear](https://github.com/DzananGanic/numericalgo/tree/master/interpolate/linear) 19 | - [Lagrange](https://github.com/DzananGanic/numericalgo/tree/master/interpolate/lagrange) 20 | - [Regressions (fits)](https://github.com/DzananGanic/numericalgo/tree/master/fit) ( [Usage](https://github.com/DzananGanic/numericalgo#fit) ) 21 | - [Linear](https://github.com/DzananGanic/numericalgo/tree/master/fit/linear) 22 | - [Polynomial](https://github.com/DzananGanic/numericalgo/tree/master/fit/poly) 23 | - [Exponential](https://github.com/DzananGanic/numericalgo/tree/master/fit/exponential) 24 | - [Root finding:](https://github.com/DzananGanic/numericalgo/tree/master/root) ( [Usage](https://github.com/DzananGanic/numericalgo#root-finding) ) 25 | - [Bisection](https://github.com/DzananGanic/numericalgo/tree/master/root) 26 | - [Newton's method](https://github.com/DzananGanic/numericalgo/tree/master/root) 27 | - [Numerical Differentiation](https://github.com/DzananGanic/numericalgo/tree/master/differentiate) ( [Usage](https://github.com/DzananGanic/numericalgo#differentiate) ) 28 | - [Backward difference formula](https://github.com/DzananGanic/numericalgo/tree/master/differentiate) 29 | - [Forward difference formula](https://github.com/DzananGanic/numericalgo/tree/master/differentiate) 30 | - [Central difference formula](https://github.com/DzananGanic/numericalgo/tree/master/differentiate) 31 | - [Numerical Integration](https://github.com/DzananGanic/numericalgo/tree/master/integrate) ( [Usage](https://github.com/DzananGanic/numericalgo#integrate) ) 32 | - [Trapezoidal rule integration](https://github.com/DzananGanic/numericalgo/tree/master/integrate) 33 | - [Simpson’s rule integration](https://github.com/DzananGanic/numericalgo/tree/master/integrate) 34 | 35 | With numericalgo, it is also possible to solve linear equations and work with matrices and vectors, as those types are provided. 36 | 37 | ## Usage 38 | Below are the examples of usage (at least one method from each category). Methods that are not explained are used in the same way as explained methods. 39 | 40 | ### Interpolation 41 | 42 | #### Single value interpolation 43 | For the single value interpolation, we need to define slices for x and y, which will be converted to coordinate pairs and sorted. We fit the slices into our interpolation type, and call interpolate.WithSingle function which receives the two parameters - first parameter is any type which conforms to validateInterpolator interface (thus implementing Interpolate and Validate methods), and the second parameter is the float value we want to interpolate. It returns the interpolated value, and the error (if it exists). 44 | 45 | ```go 46 | x := []float64{1.3, 1.8, 2.5, 3.1, 3.8, 4.4, 4.9, 5.5, 6.2} 47 | y := []float64{3.37, 4.45, 4.81, 3.96, 3.31, 2.72, 3.02, 3.43, 4.07} 48 | valToInterp := 5.1 49 | 50 | li := linear.New() 51 | li.Fit(x, y) 52 | 53 | estimate, err := interpolate.WithSingle(li, valToInterp) 54 | // (3.1566666666666663, nil) 55 | ``` 56 | 57 | #### Multiple values interpolation 58 | With multiple value interpolation, instead of declaring a single value, we define a slice of values we want to interpolate. Instead of calling interpolate.WithSingle, we call interpolate.WithMulti. Other details are the same. 59 | 60 | ```go 61 | x := []float64{1.3, 1.8, 2.5, 3.1, 3.8, 4.4, 4.9, 5.5, 6.2} 62 | y := []float64{3.37, 4.45, 4.81, 3.96, 3.31, 2.72, 3.02, 3.43, 4.07} 63 | valsToInterp := []float64{2.2, 5.1, 1.5} 64 | 65 | li := linear.New() 66 | li.Fit(x, y) 67 | 68 | estimate, err := interpolate.WithMulti(li, valToInterp) 69 | ([]float64{4.655714285714286, 3.1566666666666663, 3.802}, nil) 70 | ``` 71 | 72 | ### Fit 73 | 74 | #### Single value prediction 75 | For the single value prediction, we define two vectors - x and y. We fit the vectors, and call Predict method on defined type while passing the value to predict parameter. 76 | 77 | ```go 78 | x := numericalgo.Vector{0.3, 0.8, 1.2, 1.7, 2.4, 3.1, 3.8, 4.5, 5.1, 5.8, 6.5} 79 | y := numericalgo.Vector{8.61, 7.94, 7.55, 6.85, 6.11, 5.17, 4.19, 3.41, 2.63, 1.77, 0.89} 80 | valToPred := 1.9 81 | 82 | lf := linear.New() 83 | err := lf.Fit(x, y) 84 | result := lf.Predict(valToPred) 85 | ``` 86 | 87 | #### Multiple values prediction 88 | If we want to predict multiple values, instead of calling Predict method on defined type, we call fit.PredictMulti helper method, while passing our fit type (which conforms to predictor interface) as the first parameter, and the vector of values we want to predict. 89 | 90 | ```go 91 | x := numericalgo.Vector{0.3, 0.8, 1.2, 1.7, 2.4, 3.1, 3.8, 4.5, 5.1, 5.8, 6.5} 92 | y := numericalgo.Vector{8.61, 7.94, 7.55, 6.85, 6.11, 5.17, 4.19, 3.41, 2.63, 1.77, 0.89} 93 | valsToPred := numericalgo.Vector{6.63, 7.21} 94 | 95 | lf := linear.New() 96 | err := lf.Fit(x, y) 97 | result := fit.PredictMulti(lf, valsToPred) 98 | ``` 99 | 100 | ### Root finding 101 | #### Bisection 102 | When performing root finding, we define f(x) function with the signature func (float64) float64, eps as the tolerance, and left and right bounds of the function. Then we simply call the Bisection function from the root package while passing those parameters. 103 | 104 | ```go 105 | f := func(x float64) float64 { 106 | return math.Pow(x, 2) 107 | } 108 | eps := 0.01 109 | l := -1 110 | r := 1 111 | 112 | result := root.Bisection(f, eps, l, r) 113 | // 0 114 | ``` 115 | #### Newton's method 116 | Newton's method usage differs from the bisection. Newton function from differentiate package receives the function with the same signature as with bisection, but here second parameter is the initial guess (which is reasonably close to the true root) and the third parameter is the number of iterations for the Newton method. 117 | 118 | ```go 119 | f := func(x float64) float64 { 120 | return math.Pow(x, 3) - 2*math.Pow(x, 2) + 5 121 | } 122 | initialGuess := -1 123 | iter := 3 124 | 125 | result := root.Newton(f, initialGuess, iter) 126 | // -1.2419 127 | ``` 128 | 129 | ### Differentiate 130 | When differentiating, we need to define f(x) function with the signature func (float64) float64, value and the step size (the smaller the step size - the better the precision). Then we call the Central, Backward or Forward function from the differentiate package 131 | 132 | ```go 133 | f := func(x float64) float64 { 134 | return math.Cos(math.Pow(x,2) - 2) 135 | } 136 | val := 1 137 | h := 0.1 138 | 139 | result, err := differentiate.Central(f, val, h) 140 | // (1.6609, nil) 141 | ``` 142 | 143 | 144 | ### Integrate 145 | We need to define the f(x) function with the signature func (float64) float64. 'l' is our left bound and 'r' is our right bound for the integration. 'n' is the number of subdivisions (the higher the number, the more precise our result will be). Then we just call integrate.Trapezoid function and pass the defined values. It is the same thing with Simpsons rule (we just call integrate.Simpson) 146 | 147 | ```go 148 | f := func(x float64) float64 { 149 | return math.Sin(x) 150 | } 151 | l := 0 152 | r := math.Pi / 2 153 | n := 20 154 | 155 | result, err := integrate.Trapezoid(f, l, r, n) 156 | // (0.999, nil) 157 | ``` 158 | -------------------------------------------------------------------------------- /matrix.go: -------------------------------------------------------------------------------- 1 | package numericalgo 2 | 3 | import "fmt" 4 | import "math" 5 | 6 | // Matrix type is the slice of Vectors, with custom methods needed for matrix operations. 7 | type Matrix []Vector 8 | 9 | // Dim returns the dimensions of the matrix in the form (rows, columns). 10 | func (m Matrix) Dim() (int, int) { 11 | if m.isNil() { 12 | return 0, 0 13 | } 14 | return len(m), len(m[0]) 15 | } 16 | 17 | // Invert returns the inverted matrix by using Gauss-Jordan elimination 18 | func (m Matrix) Invert() (Matrix, error) { 19 | 20 | if !m.isSquare() { 21 | return nil, fmt.Errorf("Cannot invert non-square Matrix") 22 | } 23 | 24 | var rows, _ = m.Dim() 25 | 26 | vec := make(Vector, rows) 27 | 28 | // 1. Reduction to identity form 29 | for currentRow := 1; currentRow <= rows; currentRow++ { 30 | 31 | // Pivoting 32 | p := currentRow 33 | for i := currentRow + 1; i <= rows; i++ { 34 | if math.Abs(m[i-1][currentRow-1]) > math.Abs(m[p-1][currentRow-1]) { 35 | p = i 36 | } 37 | } 38 | 39 | // If there exists no element a(k,i) different from zero, matrix is singular and has none or more than one solution 40 | if math.Abs(m[p-1][currentRow-1]) < 1e-10 { 41 | return nil, fmt.Errorf("Matrix is singular") 42 | } 43 | 44 | // If we find pivot which is the largest a(i, currentRow), we swap the rows 45 | if p != currentRow { 46 | tmp := m[currentRow-1] 47 | m[currentRow-1] = m[p-1] 48 | m[p-1] = tmp 49 | } 50 | 51 | vec[currentRow-1] = float64(p) 52 | 53 | mi := m[currentRow-1][currentRow-1] 54 | m[currentRow-1][currentRow-1] = 1.0 55 | 56 | // Dividing by mi 57 | div, err := m[currentRow-1].DivideByScalar(mi) 58 | 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | m[currentRow-1] = div 64 | 65 | for i := 1; i <= rows; i++ { 66 | if i != currentRow { 67 | mi = m[i-1][currentRow-1] 68 | m[i-1][currentRow-1] = 0.0 69 | for j := 1; j <= rows; j++ { 70 | m[i-1][j-1] -= mi * m[currentRow-1][j-1] 71 | } 72 | } 73 | } 74 | } 75 | 76 | // Reverse swapping 77 | for j := rows; j >= 1; j-- { 78 | p := vec[j-1] 79 | if p != float64(j) { 80 | for i := 1; i <= rows; i++ { 81 | tmp := m[i-1][int64(p)-1] 82 | m[i-1][int64(p)-1] = m[i-1][j-1] 83 | m[i-1][j-1] = tmp 84 | } 85 | } 86 | } 87 | return m, nil 88 | } 89 | 90 | // Log applies natural logarithm to all the elements of the matrix, and returns the resulting matrix. 91 | func (m Matrix) Log() Matrix { 92 | row, col := m.Dim() 93 | result := make(Matrix, row) 94 | for i := range m { 95 | result[i] = make(Vector, col) 96 | for j := range m[i] { 97 | result[i][j] = math.Log(m[i][j]) 98 | } 99 | } 100 | return result 101 | } 102 | 103 | // Exp applies e^x to all the elements of the matrix, and returns the resulting matrix. 104 | func (m Matrix) Exp() Matrix { 105 | row, col := m.Dim() 106 | result := make(Matrix, row) 107 | for i := range m { 108 | result[i] = make(Vector, col) 109 | for j := range m[i] { 110 | result[i][j] = math.Exp(m[i][j]) 111 | } 112 | } 113 | return result 114 | } 115 | 116 | // LeftDivide receives another matrix as a parameter. The method solves the symbolic system of linear equations in matrix form, A*X = B for X. It returns the results in matrix form and error (if there is any). 117 | func (m Matrix) LeftDivide(m2 Matrix) (Matrix, error) { 118 | var r Matrix 119 | mT, err := m.Transpose() 120 | 121 | if err != nil { 122 | return r, err 123 | } 124 | 125 | mtm, err := mT.MultiplyBy(m) 126 | 127 | if err != nil { 128 | return r, err 129 | } 130 | 131 | pInv, err := mtm.Invert() 132 | if err != nil { 133 | return r, err 134 | } 135 | 136 | pmt, err := pInv.MultiplyBy(mT) 137 | 138 | if err != nil { 139 | return r, err 140 | } 141 | 142 | r, err = pmt.MultiplyBy(m2) 143 | 144 | if err != nil { 145 | return r, err 146 | } 147 | 148 | return r, nil 149 | } 150 | 151 | func (m Matrix) sumAbs() float64 { 152 | var sum float64 153 | rows, cols := m.Dim() 154 | for i := 0; i < rows; i++ { 155 | for j := 0; j < cols; j++ { 156 | sum += math.Abs(m[i][j]) 157 | } 158 | } 159 | return sum 160 | } 161 | 162 | func (m Matrix) isSquare() bool { 163 | rows, cols := m.Dim() 164 | return rows == cols 165 | } 166 | 167 | // MultiplyBy receives another matrix as a parameter. It multiplies the matrices and returns the resulting matrix and error. 168 | func (m Matrix) MultiplyBy(m2 Matrix) (Matrix, error) { 169 | var r Matrix 170 | 171 | _, cols1 := m.Dim() 172 | rows2, _ := m2.Dim() 173 | 174 | if cols1 != rows2 { 175 | return r, fmt.Errorf("The number of columns of the 1st matrix must equal the number of rows of the 2nd matrix") 176 | } 177 | 178 | for currentRowIndex := range m { 179 | r = append(r, Vector{}) 180 | for currentColumnIndex := range m2[0] { 181 | m2Col, err := m2.Col(currentColumnIndex) 182 | 183 | if err != nil { 184 | return r, err 185 | } 186 | 187 | dot, err := m[currentRowIndex].Dot(m2Col) 188 | if err != nil { 189 | return r, err 190 | } 191 | 192 | r[currentRowIndex] = append(r[currentRowIndex], dot) 193 | } 194 | } 195 | 196 | return r, nil 197 | } 198 | 199 | // InsertCol receives the index and the vector. It adds the provided vector as a column at index k, and returns the resulting matrix and the error (if there is any). 200 | func (m Matrix) InsertCol(k int, c Vector) (Matrix, error) { 201 | var r Matrix 202 | 203 | if k < 0 { 204 | return r, fmt.Errorf("Index cannot be less than 0") 205 | } else if _, width := m.Dim(); k > width { 206 | return r, fmt.Errorf("Index cannot be greater than number of columns + 1") 207 | } else if len(c) != len(m) { 208 | return r, fmt.Errorf("Column dimensions must match") 209 | } 210 | 211 | for i := 0; i < len(m); i++ { 212 | row := m[i] 213 | expRow := append(row[:k], append(Vector{c[i]}, row[k:]...)...) 214 | r = append(r, expRow) 215 | } 216 | 217 | return r, nil 218 | } 219 | 220 | // Row receives the index as a parameter. It returns the vector row at provided index and the error (if there is any). 221 | func (m Matrix) Row(i int) (Vector, error) { 222 | if i < 0 { 223 | return nil, fmt.Errorf("Index cannot be negative") 224 | } else if i > len(m) { 225 | return nil, fmt.Errorf("Index cannot be greater than the length") 226 | } 227 | return m[i], nil 228 | } 229 | 230 | // Col receives the index as a parameter. It returns the vector column at provided index and the error (if there is any). 231 | func (m Matrix) Col(i int) (Vector, error) { 232 | var r Vector 233 | 234 | if i < 0 { 235 | return nil, fmt.Errorf("Index cannot be negative") 236 | } else if i > len(m[0]) { 237 | return nil, fmt.Errorf("Index cannot be greater than the length") 238 | } 239 | 240 | for row := range m { 241 | r = append(r, m[row][i]) 242 | } 243 | 244 | return r, nil 245 | } 246 | 247 | // Transpose returns the transposed matrix and the error. 248 | func (m Matrix) Transpose() (Matrix, error) { 249 | var t Matrix 250 | 251 | for columnIndex := range m[0] { 252 | column, err := m.Col(columnIndex) 253 | if err != nil { 254 | return t, err 255 | } 256 | t = append(t, column) 257 | } 258 | 259 | return t, nil 260 | } 261 | 262 | // IsSimilar receives another matrix and tolerance as the parameters. It checks whether the two matrices are similar within the provided tolerance. 263 | func (m Matrix) IsSimilar(m2 Matrix, tol float64) bool { 264 | 265 | if m.IsEqual(m2) { 266 | return true 267 | } 268 | 269 | if !m.areDimsEqual(m2) { 270 | return false 271 | } 272 | 273 | for col := range m { 274 | for row := range m[col] { 275 | if math.Abs(m[col][row]-m2[col][row]) > tol { 276 | return false 277 | } 278 | } 279 | } 280 | 281 | return true 282 | } 283 | 284 | // IsEqual receives another matrix as a parameter. It returns true if the values of the two matrices are equal, and false otherwise. 285 | func (m Matrix) IsEqual(m2 Matrix) bool { 286 | if m == nil && m2 == nil { 287 | return true 288 | } else if m == nil || m2 == nil { 289 | return false 290 | } else if !m.areDimsEqual(m2) { 291 | return false 292 | } 293 | 294 | for row := range m { 295 | for col := range m[row] { 296 | if m[row][col] != m2[row][col] { 297 | return false 298 | } 299 | } 300 | } 301 | return true 302 | } 303 | 304 | // Add receives another matrix as a parameter. It adds the two matrices and returns the result matrix and the error (if there is any). 305 | func (m Matrix) Add(m2 Matrix) (Matrix, error) { 306 | rows, cols := m.Dim() 307 | var r = make(Matrix, rows, cols) 308 | if ok, err := m.canPerformOperationsWith(m2); !ok { 309 | return nil, err 310 | } 311 | 312 | for row := range m { 313 | for col := range m[row] { 314 | r[row] = append(r[row], m[row][col]+m2[row][col]) 315 | } 316 | } 317 | 318 | return r, nil 319 | } 320 | 321 | // Subtract receives another matrix as a parameter. It subtracts the two matrices and returns the result matrix and the error (if there is any). 322 | func (m Matrix) Subtract(m2 Matrix) (Matrix, error) { 323 | rows, cols := m.Dim() 324 | var r = make(Matrix, rows, cols) 325 | if ok, err := m.canPerformOperationsWith(m2); !ok { 326 | return nil, err 327 | } 328 | 329 | for row := range m { 330 | for col := range m[row] { 331 | r[row] = append(r[row], m[row][col]-m2[row][col]) 332 | } 333 | } 334 | 335 | return r, nil 336 | } 337 | 338 | func (m Matrix) areDimsEqual(m2 Matrix) bool { 339 | mRows, mCols := m.Dim() 340 | m2Rows, m2Cols := m2.Dim() 341 | 342 | if mRows != m2Rows || mCols != m2Cols { 343 | return false 344 | } 345 | return true 346 | } 347 | 348 | func (m Matrix) isNil() bool { 349 | if m == nil { 350 | return true 351 | } 352 | return false 353 | } 354 | 355 | func (m Matrix) canPerformOperationsWith(m2 Matrix) (bool, error) { 356 | if m == nil || m2 == nil { 357 | return false, fmt.Errorf("Matrices cannot be nil") 358 | } else if !m.areDimsEqual(m2) { 359 | return false, fmt.Errorf("Matrix dimensions must match") 360 | } 361 | return true, nil 362 | } 363 | 364 | // OTHER CONVENIENT METHODS THAT CAN BE IMPLEMENTED: 365 | // TODO: AddRowAt 366 | // TODO: RemoveRowAt 367 | // TODO: RemoveColumnAt 368 | // TODO: Determinant 369 | // TODO: Check for consistent dimensions 370 | // TODO: IsSingular 371 | -------------------------------------------------------------------------------- /matrix_test.go: -------------------------------------------------------------------------------- 1 | package numericalgo_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/DzananGanic/numericalgo" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestCompareMatrices(t *testing.T) { 12 | 13 | cases := map[string]struct { 14 | matrix1 numericalgo.Matrix 15 | matrix2 numericalgo.Matrix 16 | isEqual bool 17 | }{ 18 | "basic matrix comparison": { 19 | matrix1: numericalgo.Matrix{ 20 | {1, 2}, 21 | {3, 4}, 22 | }, 23 | matrix2: numericalgo.Matrix{ 24 | {1, 2}, 25 | {3, 4}, 26 | }, 27 | isEqual: true, 28 | }, 29 | "wrong dimensions matrix comparison": { 30 | matrix1: numericalgo.Matrix{ 31 | {1, 2}, 32 | }, 33 | matrix2: numericalgo.Matrix{ 34 | {1, 2}, 35 | {3, 4}, 36 | }, 37 | isEqual: false, 38 | }, 39 | "different matrices comparison": { 40 | matrix1: numericalgo.Matrix{ 41 | {1, 2}, 42 | {3, 5}, 43 | }, 44 | matrix2: numericalgo.Matrix{ 45 | {1, 2}, 46 | {3, 4}, 47 | }, 48 | isEqual: false, 49 | }, 50 | "comparing matrix to nil": { 51 | matrix1: numericalgo.Matrix{ 52 | {1, 2}, 53 | {3, 5}, 54 | }, 55 | matrix2: nil, 56 | isEqual: false, 57 | }, 58 | } 59 | 60 | for name, c := range cases { 61 | t.Run(name, func(t *testing.T) { 62 | isEqual := c.matrix1.IsEqual(c.matrix2) 63 | assert.Equal(t, c.isEqual, isEqual) 64 | }) 65 | } 66 | } 67 | 68 | func TestMatrixInsertCol(t *testing.T) { 69 | cases := map[string]struct { 70 | matrix numericalgo.Matrix 71 | column numericalgo.Vector 72 | index int 73 | expectedResult numericalgo.Matrix 74 | expectedError error 75 | }{ 76 | "adding column at 0th index": { 77 | matrix: numericalgo.Matrix{ 78 | {1, 2}, 79 | {3, 4}, 80 | }, 81 | column: numericalgo.Vector{1, 1}, 82 | index: 0, 83 | expectedResult: numericalgo.Matrix{ 84 | {1, 1, 2}, 85 | {1, 3, 4}, 86 | }, 87 | expectedError: nil, 88 | }, 89 | "adding column at 1st index": { 90 | matrix: numericalgo.Matrix{ 91 | {1, 2}, 92 | {3, 4}, 93 | }, 94 | column: numericalgo.Vector{1, 1}, 95 | index: 1, 96 | expectedResult: numericalgo.Matrix{ 97 | {1, 1, 2}, 98 | {3, 1, 4}, 99 | }, 100 | expectedError: nil, 101 | }, 102 | "adding column at 2nd index": { 103 | matrix: numericalgo.Matrix{ 104 | {1, 2}, 105 | {3, 4}, 106 | }, 107 | column: numericalgo.Vector{1, 1}, 108 | index: 2, 109 | expectedResult: numericalgo.Matrix{ 110 | {1, 2, 1}, 111 | {3, 4, 1}, 112 | }, 113 | expectedError: nil, 114 | }, 115 | "adding column with wrong dimensions": { 116 | matrix: numericalgo.Matrix{ 117 | {1, 2}, 118 | {3, 4}, 119 | }, 120 | column: numericalgo.Vector{1, 1, 4}, 121 | index: 0, 122 | expectedResult: nil, 123 | expectedError: fmt.Errorf("Column dimensions must match"), 124 | }, 125 | "adding column at incorrect index": { 126 | matrix: numericalgo.Matrix{ 127 | {1, 2}, 128 | {3, 4}, 129 | }, 130 | column: numericalgo.Vector{1, 1}, 131 | index: -1, 132 | expectedResult: nil, 133 | expectedError: fmt.Errorf("Index cannot be less than 0"), 134 | }, 135 | "adding column at index which is too large": { 136 | matrix: numericalgo.Matrix{ 137 | {1, 2}, 138 | {3, 4}, 139 | }, 140 | column: numericalgo.Vector{1, 1}, 141 | index: 3, 142 | expectedResult: nil, 143 | expectedError: fmt.Errorf("Index cannot be greater than number of columns + 1"), 144 | }, 145 | } 146 | 147 | for name, c := range cases { 148 | t.Run(name, func(t *testing.T) { 149 | result, err := c.matrix.InsertCol(c.index, c.column) 150 | assert.Equal(t, result, c.expectedResult) 151 | assert.Equal(t, err, c.expectedError) 152 | }) 153 | } 154 | } 155 | 156 | func TestAddMatrices(t *testing.T) { 157 | cases := map[string]struct { 158 | matrix1 numericalgo.Matrix 159 | matrix2 numericalgo.Matrix 160 | expectedResult numericalgo.Matrix 161 | expectedError error 162 | }{ 163 | "basic matrix addition": { 164 | matrix1: numericalgo.Matrix{ 165 | {1, 2}, 166 | {3, 4}, 167 | }, 168 | matrix2: numericalgo.Matrix{ 169 | {4, 3}, 170 | {2, 1}, 171 | }, 172 | expectedResult: numericalgo.Matrix{ 173 | {5, 5}, 174 | {5, 5}, 175 | }, 176 | expectedError: nil, 177 | }, 178 | // Wrong dimensions 179 | // { 180 | // matrix1: numericalgo.Matrix{ 181 | // {1, 2}, 182 | // {3, 4}, 183 | // }, 184 | // matrix2: numericalgo.Matrix{ 185 | // {4, 3}, 186 | // }, 187 | // result: nil, 188 | // expectedError: fmt.Errorf("Matrix dimensions must match"), 189 | // }, 190 | // Adding two nils 191 | "adding two nils": { 192 | matrix1: nil, 193 | matrix2: nil, 194 | expectedResult: nil, 195 | expectedError: fmt.Errorf("Matrices cannot be nil"), 196 | }, 197 | } 198 | 199 | for name, c := range cases { 200 | t.Run(name, func(t *testing.T) { 201 | additionResult, err := c.matrix1.Add(c.matrix2) 202 | assert.Equal(t, additionResult, c.expectedResult) 203 | assert.Equal(t, err, c.expectedError) 204 | }) 205 | } 206 | } 207 | 208 | func TestSubtractMatrices(t *testing.T) { 209 | cases := map[string]struct { 210 | matrix1 numericalgo.Matrix 211 | matrix2 numericalgo.Matrix 212 | expectedResult numericalgo.Matrix 213 | expectedError error 214 | }{ 215 | "basic matrix subtraction": { 216 | matrix1: numericalgo.Matrix{ 217 | {10, 5}, 218 | {3, 1}, 219 | }, 220 | matrix2: numericalgo.Matrix{ 221 | {1, 1}, 222 | {1, 1}, 223 | }, 224 | expectedResult: numericalgo.Matrix{ 225 | {9, 4}, 226 | {2, 0}, 227 | }, 228 | expectedError: nil, 229 | }, 230 | "matrix subtraction with negative result": { 231 | matrix1: numericalgo.Matrix{ 232 | {3, 2}, 233 | {3, 1}, 234 | }, 235 | matrix2: numericalgo.Matrix{ 236 | {4, 3}, 237 | {4, 2}, 238 | }, 239 | expectedResult: numericalgo.Matrix{ 240 | {-1, -1}, 241 | {-1, -1}, 242 | }, 243 | expectedError: nil, 244 | }, 245 | "matrix subtraction with wrong dimensions": { 246 | matrix1: numericalgo.Matrix{ 247 | {1, 2}, 248 | {3, 4}, 249 | }, 250 | matrix2: numericalgo.Matrix{ 251 | {4, 3}, 252 | }, 253 | expectedResult: nil, 254 | expectedError: fmt.Errorf("Matrix dimensions must match"), 255 | }, 256 | "matrix subtraction with two nils": { 257 | matrix1: nil, 258 | matrix2: nil, 259 | expectedResult: nil, 260 | expectedError: fmt.Errorf("Matrices cannot be nil"), 261 | }, 262 | } 263 | 264 | for name, c := range cases { 265 | t.Run(name, func(t *testing.T) { 266 | additionResult, err := c.matrix1.Subtract(c.matrix2) 267 | assert.Equal(t, additionResult, c.expectedResult) 268 | assert.Equal(t, err, c.expectedError) 269 | }) 270 | } 271 | } 272 | 273 | func TestCol(t *testing.T) { 274 | cases := map[string]struct { 275 | matrix numericalgo.Matrix 276 | i int 277 | expectedResult numericalgo.Vector 278 | expectedError error 279 | }{ 280 | "getting column at index 1": { 281 | matrix: numericalgo.Matrix{ 282 | {1, 2, 3}, 283 | {4, 5, 6}, 284 | }, 285 | i: 1, 286 | expectedResult: numericalgo.Vector{2, 5}, 287 | expectedError: nil, 288 | }, 289 | "getting column at wrong index": { 290 | matrix: numericalgo.Matrix{ 291 | {1, 2, 3}, 292 | {4, 5, 6}, 293 | }, 294 | i: -5, 295 | expectedResult: nil, 296 | expectedError: fmt.Errorf("Index cannot be negative"), 297 | }, 298 | "getting column at index which is too large": { 299 | matrix: numericalgo.Matrix{ 300 | {1, 2, 3}, 301 | {4, 5, 6}, 302 | }, 303 | i: 5, 304 | expectedResult: nil, 305 | expectedError: fmt.Errorf("Index cannot be greater than the length"), 306 | }, 307 | } 308 | 309 | for name, c := range cases { 310 | t.Run(name, func(t *testing.T) { 311 | column, err := c.matrix.Col(c.i) 312 | assert.Equal(t, column, c.expectedResult) 313 | assert.Equal(t, err, c.expectedError) 314 | }) 315 | } 316 | } 317 | 318 | func TestRow(t *testing.T) { 319 | cases := map[string]struct { 320 | matrix numericalgo.Matrix 321 | expectedResult numericalgo.Vector 322 | i int 323 | expectedError error 324 | }{ 325 | "getting the row at index 1": { 326 | matrix: numericalgo.Matrix{ 327 | {1, 2, 3}, 328 | {4, 5, 6}, 329 | }, 330 | i: 1, 331 | expectedResult: numericalgo.Vector{4, 5, 6}, 332 | expectedError: nil, 333 | }, 334 | "getting the row at wrong index": { 335 | matrix: numericalgo.Matrix{ 336 | {1, 2, 3}, 337 | {4, 5, 6}, 338 | }, 339 | i: -5, 340 | expectedResult: nil, 341 | expectedError: fmt.Errorf("Index cannot be negative"), 342 | }, 343 | "getting the row at index which is too large": { 344 | matrix: numericalgo.Matrix{ 345 | {1, 2, 3}, 346 | {4, 5, 6}, 347 | }, 348 | i: 5, 349 | expectedResult: nil, 350 | expectedError: fmt.Errorf("Index cannot be greater than the length"), 351 | }, 352 | } 353 | 354 | for name, c := range cases { 355 | t.Run(name, func(t *testing.T) { 356 | column, err := c.matrix.Row(c.i) 357 | assert.Equal(t, column, c.expectedResult) 358 | assert.Equal(t, err, c.expectedError) 359 | }) 360 | } 361 | 362 | } 363 | 364 | func TestTransposeMatrix(t *testing.T) { 365 | cases := map[string]struct { 366 | matrix numericalgo.Matrix 367 | expectedResult numericalgo.Matrix 368 | expectedError error 369 | }{ 370 | "basic matrix transpose": { 371 | matrix: numericalgo.Matrix{ 372 | {1, 2, 3}, 373 | {4, 5, 6}, 374 | }, 375 | expectedResult: numericalgo.Matrix{ 376 | {1, 4}, 377 | {2, 5}, 378 | {3, 6}, 379 | }, 380 | expectedError: nil, 381 | }, 382 | "second basic matrix transpose": { 383 | matrix: numericalgo.Matrix{ 384 | {1, 4}, 385 | {2, 5}, 386 | {3, 6}, 387 | }, 388 | expectedResult: numericalgo.Matrix{ 389 | {1, 2, 3}, 390 | {4, 5, 6}, 391 | }, 392 | expectedError: nil, 393 | }, 394 | "transposing one-dimensional matrix": { 395 | matrix: numericalgo.Matrix{ 396 | {1, 4}, 397 | }, 398 | expectedResult: numericalgo.Matrix{ 399 | {1}, 400 | {4}, 401 | }, 402 | expectedError: nil, 403 | }, 404 | // Inconsistent dimensions 405 | // { 406 | // matrix: numericalgo.Matrix{ 407 | // {1, 4}, 408 | // {2}, 409 | // }, 410 | // expectedResult: nil, 411 | // expectedError: fmt.Errorf("Inconsistent dimensions"), 412 | // }, 413 | } 414 | 415 | for name, c := range cases { 416 | t.Run(name, func(t *testing.T) { 417 | transposed, err := c.matrix.Transpose() 418 | assert.Equal(t, transposed, c.expectedResult) 419 | assert.Equal(t, err, c.expectedError) 420 | }) 421 | } 422 | 423 | } 424 | 425 | func TestMatrixMultiplication(t *testing.T) { 426 | cases := map[string]struct { 427 | matrix1 numericalgo.Matrix 428 | matrix2 numericalgo.Matrix 429 | expectedResult numericalgo.Matrix 430 | expectedError error 431 | }{ 432 | "basic matrix multiplication": { 433 | matrix1: numericalgo.Matrix{ 434 | {1, 2, 3}, 435 | {4, 5, 6}, 436 | }, 437 | matrix2: numericalgo.Matrix{ 438 | {1, 1}, 439 | {2, 3}, 440 | {5, 2}, 441 | }, 442 | expectedResult: numericalgo.Matrix{ 443 | {20, 13}, 444 | {44, 31}, 445 | }, 446 | expectedError: nil, 447 | }, 448 | "second matrix multiplication": { 449 | matrix1: numericalgo.Matrix{ 450 | {1, 2, 3}, 451 | {4, 5, 6}, 452 | }, 453 | matrix2: numericalgo.Matrix{ 454 | {1, 4}, 455 | {2, 5}, 456 | {3, 6}, 457 | }, 458 | expectedResult: numericalgo.Matrix{ 459 | {14, 32}, 460 | {32, 77}, 461 | }, 462 | expectedError: nil, 463 | }, 464 | "multiplying matrices with wrong dimensions": { 465 | matrix1: numericalgo.Matrix{ 466 | {1, 2, 3}, 467 | {4, 5, 6}, 468 | }, 469 | matrix2: numericalgo.Matrix{ 470 | {1, 4}, 471 | {2, 5}, 472 | }, 473 | expectedResult: nil, 474 | expectedError: fmt.Errorf("The number of columns of the 1st matrix must equal the number of rows of the 2nd matrix"), 475 | }, 476 | "multiplying matrix with identity matrix": { 477 | matrix1: numericalgo.Matrix{ 478 | {1, 2, 3}, 479 | {4, 5, 6}, 480 | {7, 8, 9}, 481 | }, 482 | matrix2: numericalgo.Matrix{ 483 | {1, 0, 0}, 484 | {0, 1, 0}, 485 | {0, 0, 1}, 486 | }, 487 | expectedResult: numericalgo.Matrix{ 488 | {1, 2, 3}, 489 | {4, 5, 6}, 490 | {7, 8, 9}, 491 | }, 492 | expectedError: nil, 493 | }, 494 | "multiplying matrices with different but correct dimensions": { 495 | matrix1: numericalgo.Matrix{ 496 | {1, 2, 3}, 497 | {4, 5, 6}, 498 | }, 499 | matrix2: numericalgo.Matrix{ 500 | {1, 2, 3}, 501 | {1, 2, 3}, 502 | {1, 2, 3}, 503 | }, 504 | expectedResult: numericalgo.Matrix{ 505 | {6, 12, 18}, 506 | {15, 30, 45}, 507 | }, 508 | expectedError: nil, 509 | }, 510 | "multiplying 1D matrix with 2D one": { 511 | matrix1: numericalgo.Matrix{ 512 | {3, 4, 2}, 513 | }, 514 | matrix2: numericalgo.Matrix{ 515 | {13, 9, 7, 15}, 516 | {8, 7, 4, 6}, 517 | {6, 4, 0, 3}, 518 | }, 519 | expectedResult: numericalgo.Matrix{ 520 | {83, 63, 37, 75}, 521 | }, 522 | expectedError: nil, 523 | }, 524 | } 525 | 526 | for name, c := range cases { 527 | t.Run(name, func(t *testing.T) { 528 | multiplied, err := c.matrix1.MultiplyBy(c.matrix2) 529 | assert.Equal(t, multiplied, c.expectedResult) 530 | assert.Equal(t, err, c.expectedError) 531 | }) 532 | } 533 | } 534 | 535 | func TestMatrixLeftDivide(t *testing.T) { 536 | cases := map[string]struct { 537 | matrix1 numericalgo.Matrix 538 | matrix2 numericalgo.Matrix 539 | expectedResult numericalgo.Matrix 540 | expectedError error 541 | }{ 542 | "basic matrix left divide": { 543 | matrix1: numericalgo.Matrix{ 544 | {2}, 545 | {4}, 546 | }, 547 | matrix2: numericalgo.Matrix{ 548 | {4}, 549 | {4}, 550 | }, 551 | expectedResult: numericalgo.Matrix{ 552 | {1.2}, 553 | }, 554 | expectedError: nil, 555 | }, 556 | "second matrix left divide": { 557 | matrix1: numericalgo.Matrix{ 558 | {1, 2}, 559 | {2, 2}, 560 | }, 561 | matrix2: numericalgo.Matrix{ 562 | {3, 2}, 563 | {1, 1}, 564 | }, 565 | expectedResult: numericalgo.Matrix{ 566 | {-2, -1}, 567 | {2.5, 1.5}, 568 | }, 569 | expectedError: nil, 570 | }, 571 | "left divide with wrong dimensions": { 572 | matrix1: numericalgo.Matrix{ 573 | {1, 2}, 574 | {2, 2}, 575 | }, 576 | matrix2: numericalgo.Matrix{ 577 | {3, 2}, 578 | }, 579 | expectedResult: nil, 580 | expectedError: fmt.Errorf("The number of columns of the 1st matrix must equal the number of rows of the 2nd matrix"), 581 | }, 582 | "left divide - singular matrix": { 583 | matrix1: numericalgo.Matrix{ 584 | {1, 2, 3}, 585 | {4, 5, 6}, 586 | }, 587 | matrix2: numericalgo.Matrix{ 588 | {1, 1}, 589 | {1, 1}, 590 | {1, 1}, 591 | }, 592 | expectedResult: nil, 593 | expectedError: fmt.Errorf("Matrix is singular"), 594 | }, 595 | "left divide with ones column": { 596 | matrix1: numericalgo.Matrix{ 597 | {1, 1.3}, 598 | {1, 2.1}, 599 | {1, 3.7}, 600 | {1, 4.2}, 601 | }, 602 | matrix2: numericalgo.Matrix{ 603 | {2.2}, 604 | {5.8}, 605 | {10.2}, 606 | {11.8}, 607 | }, 608 | expectedResult: numericalgo.Matrix{ 609 | {-1.5225601452564645}, 610 | {3.1938266000907847}, 611 | }, 612 | expectedError: nil, 613 | }, 614 | "second left divide with ones column": { 615 | matrix1: numericalgo.Matrix{ 616 | {1, 0.3}, 617 | {1, 0.8}, 618 | {1, 1.2}, 619 | {1, 1.7}, 620 | {1, 2.4}, 621 | {1, 3.1}, 622 | {1, 3.8}, 623 | {1, 4.5}, 624 | {1, 5.1}, 625 | {1, 5.8}, 626 | {1, 6.5}, 627 | }, 628 | matrix2: numericalgo.Matrix{ 629 | {8.61}, 630 | {7.94}, 631 | {7.55}, 632 | {6.85}, 633 | {6.11}, 634 | {5.17}, 635 | {4.19}, 636 | {3.41}, 637 | {2.63}, 638 | {1.77}, 639 | {0.89}, 640 | }, 641 | expectedResult: numericalgo.Matrix{ 642 | {8.99987709451432}, 643 | {-1.246552501126634}, 644 | }, 645 | expectedError: nil, 646 | }, 647 | } 648 | 649 | for name, c := range cases { 650 | t.Run(name, func(t *testing.T) { 651 | leftDivided, err := c.matrix1.LeftDivide(c.matrix2) 652 | 653 | isSimilar := leftDivided.IsSimilar(c.expectedResult, 1e-4) 654 | assert.Equal(t, true, isSimilar) 655 | assert.Equal(t, err, c.expectedError) 656 | }) 657 | } 658 | } 659 | 660 | func TestMatrixInverse(t *testing.T) { 661 | cases := map[string]struct { 662 | matrix numericalgo.Matrix 663 | expectedResult numericalgo.Matrix 664 | expectedError error 665 | }{ 666 | "simple matrix inverse": { 667 | matrix: numericalgo.Matrix{ 668 | {4, 7}, 669 | {2, 6}, 670 | }, 671 | expectedResult: numericalgo.Matrix{ 672 | {0.6, -0.7}, 673 | {-0.2, 0.4}, 674 | }, 675 | expectedError: nil, 676 | }, 677 | "inverting non-square matrix": { 678 | matrix: numericalgo.Matrix{ 679 | {4, 7}, 680 | }, 681 | expectedResult: nil, 682 | expectedError: fmt.Errorf("Cannot invert non-square Matrix"), 683 | }, 684 | "inverting singular matrix": { 685 | matrix: numericalgo.Matrix{ 686 | {2, 4}, 687 | {6, 12}, 688 | }, 689 | expectedResult: nil, 690 | expectedError: fmt.Errorf("Matrix is singular"), 691 | }, 692 | "second simple matrix inverse": { 693 | matrix: numericalgo.Matrix{ 694 | {3, 0, 2}, 695 | {2, 0, -2}, 696 | {0, 1, 1}, 697 | }, 698 | expectedResult: numericalgo.Matrix{ 699 | {0.2, 0.2, 0}, 700 | {-0.2, 0.3, 1}, 701 | {0.2, -0.3, 0}, 702 | }, 703 | expectedError: nil, 704 | }, 705 | } 706 | 707 | for name, c := range cases { 708 | t.Run(name, func(t *testing.T) { 709 | inverted, err := c.matrix.Invert() 710 | isSimilar := inverted.IsSimilar(c.expectedResult, 1e-10) 711 | assert.Equal(t, true, isSimilar) 712 | assert.Equal(t, err, c.expectedError) 713 | }) 714 | } 715 | } 716 | func TestMatrixIsSimilar(t *testing.T) { 717 | cases := map[string]struct { 718 | matrix1 numericalgo.Matrix 719 | matrix2 numericalgo.Matrix 720 | expectedResult bool 721 | }{ 722 | "basic matrix similarity test": { 723 | matrix1: numericalgo.Matrix{ 724 | {1, 2}, 725 | {3, 4}, 726 | }, 727 | matrix2: numericalgo.Matrix{ 728 | {1.000000001, 2.0000000001}, 729 | {3, 4}, 730 | }, 731 | expectedResult: true, 732 | }, 733 | "matrix similarity with wrong dimensions": { 734 | matrix1: numericalgo.Matrix{ 735 | {1, 2}, 736 | }, 737 | matrix2: numericalgo.Matrix{ 738 | {1, 2}, 739 | {3, 4}, 740 | }, 741 | expectedResult: false, 742 | }, 743 | "matrix similarity with non-similar matrices": { 744 | matrix1: numericalgo.Matrix{ 745 | {1.2, 2.5}, 746 | {3.2, 5.4}, 747 | }, 748 | matrix2: numericalgo.Matrix{ 749 | {1, 2}, 750 | {3, 4}, 751 | }, 752 | expectedResult: false, 753 | }, 754 | "passing nil as a second matrix": { 755 | matrix1: numericalgo.Matrix{ 756 | {1, 2}, 757 | {3, 5}, 758 | }, 759 | matrix2: nil, 760 | expectedResult: false, 761 | }, 762 | } 763 | 764 | for name, c := range cases { 765 | t.Run(name, func(t *testing.T) { 766 | isSimilar := c.matrix1.IsSimilar(c.matrix2, 0.1) 767 | assert.Equal(t, c.expectedResult, isSimilar) 768 | }) 769 | } 770 | } 771 | --------------------------------------------------------------------------------