diff --git a/go.mod b/go.mod index fd11da7..3be8798 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( require ( git.sr.ht/~sbinet/gg v0.5.0 // indirect + github.com/Knetic/govaluate v3.0.0+incompatible // indirect github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b // indirect github.com/campoy/embedmd v1.0.0 // indirect github.com/ebitengine/purego v0.5.0 // indirect diff --git a/go.sum b/go.sum index 3acb0f1..90ff35c 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,8 @@ git.sr.ht/~sbinet/cmpimg v0.1.0/go.mod h1:FU12psLbF4TfNXkKH2ZZQ29crIqoiqTZmeQ7dk git.sr.ht/~sbinet/gg v0.5.0 h1:6V43j30HM623V329xA9Ntq+WJrMjDxRjuAB1LFWF5m8= git.sr.ht/~sbinet/gg v0.5.0/go.mod h1:G2C0eRESqlKhS7ErsNey6HHrqU1PwsnCQlekFi9Q2Oo= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg= +github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw= diff --git a/main.go b/main.go index e39985b..1e9fb1f 100644 --- a/main.go +++ b/main.go @@ -1,28 +1,57 @@ package main import ( + "fmt" "image" "log" + "math" "time" "github.com/hajimehoshi/ebiten/v2" ) -func f(x float64) float64 { return x*x + 5*x - 3 } -func df(x float64) float64 { return 2*x + 5 } +func f(x float64) float64 { return math.Cos(x*x + 3*x + 97) } // FUCTION CONTROL + +// TODO: use govaluate to get function as user input +// i can get string from user, parse it to function, but then what? + +func derivative(f func(float64) float64) func(float64) float64 { + return func(x float64) float64 { + dx := 0.00000000005 // dx -> 0 + return (f(x+dx) - f(x)) / dx //literally todays math lesson. + } +} +func GradientDescent(f func(float64) float64, x0, alpha float64) float64 { + x := x0 + var stop bool + for !stop { + x -= alpha * derivative(f)(x) + if derivative(f)(x) == 0 || math.Abs(derivative(f)(x)) < 0.00001 { + stop = true + } + } + return x +} func main() { ebiten.SetWindowSize(640, 480) ebiten.SetWindowTitle("Gradient descent") + fmt.Println(GradientDescent(f, -math.Pi/6, 0.01)) //CONTROL X0 AND ALPHA HERE FOR PRINTLN OUTPUT + img := make(chan *image.RGBA, 1) go func() { - p := Plot(-5, 0, 0.1, f) - x := 0.0 + p := Plot(-math.Pi, math.Pi/3, 0.01, f) //CONTROL RANGE AND STEP HERE + x := -math.Pi / 6 //CONTROL X0 HERE FOR GRAPHICS img <- p(x) - for i := 0; i < 50; i++ { + var stop bool + for !stop { time.Sleep(30 * time.Millisecond) - x -= df(x) * 0.1 + x -= 0.1 * derivative(f)(x) // CONTROL ALPHA HERE FOR GRAPHICS + // if you put alpha = 0.9, you wil get badly animated film about snake :) + if derivative(f)(x) == 0 || math.Abs(derivative(f)(x)) < 0.00001 { + stop = true + } img <- p(x) } }() diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..2ec80b5 --- /dev/null +++ b/main_test.go @@ -0,0 +1,62 @@ +package main + +import ( + "math" + "testing" +) + +func TestDerivative(t *testing.T) { + for _, tc := range []struct { + name string + f func(float64) float64 + x float64 + want float64 + }{ + {name: "constant", f: func(x float64) float64 { return 5 }, x: 0.0, want: 0.0}, + {name: "linear", f: func(x float64) float64 { return 2*x + 5 }, x: 0.0, want: 2.0}, + {name: "simple square", f: func(x float64) float64 { return x * x }, x: 2.0, want: 4.0}, + {name: "not so simple square", f: func(x float64) float64 { return x*x + 5*x - 3 }, x: 1.0, want: 7.0}, + {name: "hyperbola", f: func(x float64) float64 { return 1 / x }, x: 2.0, want: -0.25}, + {name: "diff.135 lpp. N18 a)", f: func(x float64) float64 { return 2*x*x - 3*x }, x: 3, want: 9}, + {name: "diff.135 lpp. N18 b)", f: func(x float64) float64 { return x*x - x + 2 }, x: 0, want: -1}, + {name: "diff.135 lpp. N18 c)", f: func(x float64) float64 { return 4 - 5*x - x*x }, x: 1, want: -7}, + {"diff.135 lpp. N18 d)", func(x float64) float64 { return -3 / x }, 3, 0.3333333}, //1/3 + {"diff.135 lpp. N18 e)", func(x float64) float64 { return 4 / x * x }, -2, 0}, + {"diff.135 lpp. N18 f)", func(x float64) float64 { return 4/x + 1 }, -2, -1}, + {"diff.135 lpp. N18 g)", func(x float64) float64 { return 6/2 - x }, -1, -1}, + {"diff.135 lpp. N18 h)", func(x float64) float64 { return 1 / math.Sqrt(x) }, 4, -0.062499}, + {"diff.135 lpp. N18 i)", func(x float64) float64 { return 2 * math.Sqrt(1-x) }, 0, -1}, + {"diff.135 lpp. N18 j)", func(x float64) float64 { return math.Sqrt(1 + 2*x) }, 4, 0.3333333}, + } { + t.Run("", func(t *testing.T) { + d := derivative(tc.f) + got := d(tc.x) + if math.Abs(got-tc.want) > 0.0001 { + t.Errorf("Expected derivative(%f) to be %f, but got %f", tc.x, tc.want, got) + } + }) + } +} + +func TestGradientDescend(t *testing.T) { + for _, tc := range []struct { + name string + f func(float64) float64 + x0 float64 + alpha float64 + want float64 + }{ + {name: "constant", f: func(x float64) float64 { return 5 }, x0: 0.0, alpha: 0.01, want: 0.0}, + {name: "simple square", f: func(x float64) float64 { return x * x }, x0: 2.0, alpha: 0.01, want: 0.0}, + {name: "not so simple square", f: func(x float64) float64 { return x*x + 5*x - 3 }, x0: 1.0, alpha: 0.01, want: -2.5}, + {name: "diff.135 lpp. N18 a)", f: func(x float64) float64 { return 2*x*x - 3*x }, x0: 3, alpha: 0.01, want: 0.75}, + {name: "diff.135 lpp. N18 b)", f: func(x float64) float64 { return x*x - x + 2 }, x0: 0, alpha: 0.01, want: 0.5}, + } { + t.Run("", func(t *testing.T) { + got := GradientDescent(tc.f, tc.x0, tc.alpha) + if math.Abs(got-tc.want) > 0.0001 { + t.Errorf("Expected GradientDescent(%f, %f) to be %f, but got %f", tc.x0, tc.alpha, tc.want, got) + } + }) + } +}