diff --git a/math/neville.go b/math/neville.go new file mode 100644 index 000000000..d8b582f79 --- /dev/null +++ b/math/neville.go @@ -0,0 +1,39 @@ +// Package math provides common mathematical algorithms and functions. +package math + +import "errors" + +// Neville implements Neville's algorithm for polynomial interpolation. +// It computes the value of the unique polynomial of degree at most n that +// passes through n+1 given points (xCoords, yCoords) at a specific value `target`. +// For more details, see: https://en.wikipedia.org/wiki/Neville%27s_algorithm +// +// Author: [Mitrajit Ghorui](https://github.com/KeyKyrios) +func Neville(xCoords, yCoords []float64, target float64) (float64, error) { + if len(xCoords) != len(yCoords) { + return 0, errors.New("x and y coordinate slices must have the same length") + } + if len(xCoords) == 0 { + return 0, errors.New("input slices cannot be empty") + } + + seenX := make(map[float64]bool) + for _, x := range xCoords { + if seenX[x] { + return 0, errors.New("input x-coordinates must be unique") + } + seenX[x] = true + } + + n := len(xCoords) + p := make([]float64, n) + copy(p, yCoords) + + for k := 1; k < n; k++ { + for i := 0; i < n-k; i++ { + p[i] = ((target-xCoords[i+k])*p[i] + (xCoords[i]-target)*p[i+1]) / (xCoords[i] - xCoords[i+k]) + } + } + + return p[0], nil +} \ No newline at end of file diff --git a/math/neville_test.go b/math/neville_test.go new file mode 100644 index 000000000..3fdbc91b1 --- /dev/null +++ b/math/neville_test.go @@ -0,0 +1,86 @@ + +package math + +import ( + "math" + "testing" +) + +func TestNeville(t *testing.T) { + testCases := []struct { + name string + x []float64 + y []float64 + target float64 + expected float64 + hasError bool + }{ + { + name: "Linear Interpolation", + x: []float64{0, 2}, + y: []float64{1, 5}, + target: 1, + expected: 3.0, + hasError: false, + }, + { + name: "Quadratic Interpolation", + x: []float64{0, 1, 3}, + y: []float64{0, 1, 9}, + target: 2, + expected: 4.0, + hasError: false, + }, + { + name: "Negative Numbers Interpolation", + x: []float64{-1, 0, 2}, + y: []float64{4, 1, 1}, + target: 1, + expected: 0.0, + hasError: false, + }, + { + name: "Mismatched Lengths", + x: []float64{1, 2}, + y: []float64{1}, + target: 1.5, + expected: 0, + hasError: true, + }, + { + name: "Empty Slices", + x: []float64{}, + y: []float64{}, + target: 1, + expected: 0, + hasError: true, + }, + { + name: "Duplicate X-Coordinates", + x: []float64{1, 2, 1}, + y: []float64{5, 8, 3}, + target: 1.5, + expected: 0, + hasError: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result, err := Neville(tc.x, tc.y, tc.target) + + if tc.hasError { + if err == nil { + t.Errorf("Expected an error but got none") + } + } else { + if err != nil { + t.Errorf("Expected no error but got: %v", err) + } + if math.Abs(result-tc.expected) > 1e-9 { + t.Errorf("Expected %f, but got %f", tc.expected, result) + } + } + }) + } +} \ No newline at end of file