├── .vscode ├── launch.json └── tasks.json ├── ast ├── string.go ├── weight.go ├── eval.go ├── node.go ├── new.go ├── reduce.go ├── combine.go ├── mutate.go └── operators.go ├── program_test.go ├── engine └── context.go ├── domain ├── sort.go └── cases.go ├── program.go └── readme.md /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch", 6 | "type": "go", 7 | "request": "launch", 8 | "mode": "debug", 9 | "remotePath": "", 10 | "port": 2345, 11 | "host": "127.0.0.1", 12 | "program": "${workspaceRoot}", 13 | "env": {}, 14 | "args": [] 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /ast/string.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import "strconv" 4 | 5 | func (node *BinaryNode) String() string { 6 | return node.operator.String(node.left, node.right) 7 | } 8 | 9 | func (node *LiteralNode) String() string { 10 | return strconv.FormatFloat(node.value, 'f', -1, 64) 11 | } 12 | 13 | func (node *VariableNode) String() string { 14 | return node.variable 15 | } 16 | -------------------------------------------------------------------------------- /ast/weight.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | //Eval evaluates the result of the binary operation 4 | func (node *BinaryNode) Weight() int { 5 | return 1 + node.left.Weight() + node.right.Weight() 6 | } 7 | 8 | //Eval evaluates the value of the literal node 9 | func (node *LiteralNode) Weight() int { 10 | return 1 11 | } 12 | 13 | //Eval evaluates the value of the variable using a engine.Context 14 | func (node *VariableNode) Weight() int { 15 | return 1 16 | } 17 | -------------------------------------------------------------------------------- /program_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/rogeralsing/go-genetic-math/ast" 7 | "github.com/rogeralsing/go-genetic-math/engine" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestBar(t *testing.T) { 12 | context := engine.NewContext() 13 | context.SetVariable("hej", 123) 14 | add := ast.Add(ast.Literal(2), ast.Literal(3)) 15 | res := add.Eval(context) 16 | println(res) 17 | assert.Equal(t, 5.0, res, "should be 5") 18 | } 19 | -------------------------------------------------------------------------------- /engine/context.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | type Context struct { 4 | variables map[string]float64 5 | } 6 | 7 | var EmptyContext *Context = nil 8 | 9 | func NewContext() *Context { 10 | return &Context{ 11 | variables: map[string]float64{}, 12 | } 13 | } 14 | 15 | func (node *Context) GetVariable(variable string) float64 { 16 | return node.variables[variable] 17 | } 18 | 19 | func (node *Context) SetVariable(variable string, value float64) { 20 | node.variables[variable] = value 21 | } 22 | -------------------------------------------------------------------------------- /ast/eval.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import "github.com/rogeralsing/go-genetic-math/engine" 4 | 5 | //Eval evaluates the result of the binary operation 6 | func (node *BinaryNode) Eval(context *engine.Context) float64 { 7 | return node.operator.Apply(node.left, node.right, context) 8 | } 9 | 10 | //Eval evaluates the value of the literal node 11 | func (node *LiteralNode) Eval(context *engine.Context) float64 { 12 | return node.value 13 | } 14 | 15 | //Eval evaluates the value of the variable using a engine.Context 16 | func (node *VariableNode) Eval(context *engine.Context) float64 { 17 | return context.GetVariable(node.variable) 18 | } 19 | -------------------------------------------------------------------------------- /domain/sort.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import ( 4 | "github.com/rogeralsing/go-genetic-math/ast" 5 | ) 6 | 7 | type NodeFitness struct { 8 | Node ast.Node 9 | Fitness float64 10 | } 11 | 12 | type byFitnessAndWeight []NodeFitness 13 | 14 | func (a byFitnessAndWeight) Len() int { 15 | return len(a) 16 | } 17 | func (a byFitnessAndWeight) Swap(i, j int) { 18 | a[i], a[j] = a[j], a[i] 19 | } 20 | func (a byFitnessAndWeight) Less(i, j int) bool { 21 | if a[i].Fitness < a[j].Fitness { 22 | return true 23 | } 24 | if a[i].Fitness == a[j].Fitness { 25 | return a[i].Node.Weight() < a[j].Node.Weight() 26 | } 27 | return false 28 | } 29 | -------------------------------------------------------------------------------- /ast/node.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import "github.com/rogeralsing/go-genetic-math/engine" 4 | 5 | //Node represents an abstract AST node and the behaviors available on it 6 | type Node interface { 7 | Eval(*engine.Context) float64 8 | String() string 9 | Mutate() Node 10 | Reduce() Node 11 | Combine(Node) Node 12 | Weight() int 13 | Extract() Node 14 | } 15 | 16 | //LiteralNode represents a literal value, e.g. 123.456 17 | type LiteralNode struct { 18 | value float64 19 | } 20 | 21 | //BinaryNode represents a binary operation, e.g. a + b 22 | type BinaryNode struct { 23 | left Node 24 | right Node 25 | operator binaryOperator 26 | } 27 | 28 | //VariableNode represents a variable, e.g. X 29 | type VariableNode struct { 30 | variable string 31 | } 32 | -------------------------------------------------------------------------------- /ast/new.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | //Add adds the evaluated result of two nodes 4 | func Add(left Node, right Node) Node { 5 | return Binary(left, right, OpAdd) 6 | } 7 | 8 | func Sub(left Node, right Node) Node { 9 | return Binary(left, right, OpSub) 10 | } 11 | 12 | func Div(left Node, right Node) Node { 13 | return Binary(left, right, OpDiv) 14 | } 15 | 16 | func Mul(left Node, right Node) Node { 17 | return Binary(left, right, OpMul) 18 | } 19 | 20 | func Binary(left Node, right Node, operator binaryOperator) Node { 21 | return &BinaryNode{left: left, right: right, operator: operator} 22 | } 23 | 24 | func Literal(value float64) Node { 25 | return &LiteralNode{value: value} 26 | } 27 | 28 | func Var(name string) Node { 29 | return &VariableNode{variable: name} 30 | } 31 | -------------------------------------------------------------------------------- /ast/reduce.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "github.com/rogeralsing/go-genetic-math/engine" 5 | ) 6 | 7 | func isLiteralZero(node Node) bool { 8 | switch t := node.(type) { 9 | default: 10 | return false 11 | case *LiteralNode: 12 | return t.value == 0 13 | } 14 | } 15 | 16 | func isLiteral(node Node) bool { 17 | switch node.(type) { 18 | default: 19 | return false 20 | case *LiteralNode: 21 | return true 22 | } 23 | } 24 | 25 | func (node *BinaryNode) Reduce() Node { 26 | left := node.left.Reduce() 27 | right := node.right.Reduce() 28 | 29 | if isLiteral(left) && isLiteral(right) { 30 | constant := node.operator.Apply(left, right, engine.EmptyContext) 31 | return Literal(constant) 32 | } 33 | 34 | return node.operator.Reduce(left, right) 35 | } 36 | 37 | func (node *LiteralNode) Reduce() Node { 38 | return node 39 | } 40 | 41 | func (node *VariableNode) Reduce() Node { 42 | return node 43 | } 44 | -------------------------------------------------------------------------------- /ast/combine.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | func (node *BinaryNode) Combine(other Node) Node { 4 | if hit(3) { 5 | return Binary(node.left.Combine(other), node.right, node.operator) 6 | } 7 | if hit(3) { 8 | return Binary(node.left, node.right.Combine(other), node.operator) 9 | } 10 | 11 | return other.Extract() 12 | } 13 | 14 | func (node *LiteralNode) Combine(other Node) Node { 15 | if hit(2) { 16 | return other.Extract() 17 | } 18 | return node 19 | } 20 | 21 | func (node *VariableNode) Combine(other Node) Node { 22 | if hit(2) { 23 | return other.Extract() 24 | } 25 | return node 26 | } 27 | 28 | func (node *BinaryNode) Extract() Node { 29 | if hit(3) { 30 | return node.left.Extract() 31 | } 32 | if hit(3) { 33 | return node.right.Extract() 34 | } 35 | 36 | return node 37 | } 38 | 39 | func (node *LiteralNode) Extract() Node { 40 | return node 41 | } 42 | 43 | func (node *VariableNode) Extract() Node { 44 | return node 45 | } 46 | -------------------------------------------------------------------------------- /program.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/rogeralsing/go-genetic-math/domain" 5 | "runtime" 6 | ) 7 | 8 | func main() { 9 | runtime.GOMAXPROCS(20) 10 | // define inputs and expected output 11 | // problem := domain.DefineProblem(domain.Case(5*2+2, domain.Input("x", 5), domain.Input("y", 2), domain.Input("z", 2)), 12 | // domain.Case(10*2+2, domain.Input("x", 10), domain.Input("y", 2), domain.Input("z", 2)), 13 | // domain.Case(10*3+2, domain.Input("x", 10), domain.Input("y", 3), domain.Input("z", 2)), 14 | // domain.Case(1000*3+5, domain.Input("x", 1000), domain.Input("y", 3), domain.Input("z", 5)), 15 | // domain.Case(333*333+5, domain.Input("x", 333), domain.Input("y", 333), domain.Input("z", 5))) 16 | 17 | //This one is from an old FB spam 18 | // 19 | // If we assume that: 20 | // 2 + 3 = 10 21 | // 7 + 2 = 63 22 | // 6 + 5 = 66 23 | // 8 + 4 = 96 24 | 25 | // How much is? 26 | // 9 + 7 = ???? 27 | 28 | problem := domain.DefineProblem(domain.Case(10,domain.Input("x",2),domain.Input("y",3)), 29 | domain.Case(63,domain.Input("x",7),domain.Input("y",2)), 30 | domain.Case(66,domain.Input("x",6),domain.Input("y",5)), 31 | domain.Case(96,domain.Input("x",8),domain.Input("y",4))) 32 | 33 | // some binary logic 34 | 35 | // problem := domain.DefineProblem(domain.Case(5&2+2, domain.Input("x", 5), domain.Input("y", 2), domain.Input("z", 2)), 36 | // domain.Case(10&2+2, domain.Input("x", 10), domain.Input("y", 2), domain.Input("z", 2)), 37 | // domain.Case(10&3+2, domain.Input("x", 10), domain.Input("y", 3), domain.Input("z", 2)), 38 | // domain.Case(1000&3+5, domain.Input("x", 1000), domain.Input("y", 3), domain.Input("z", 5)), 39 | // domain.Case(333&333+5, domain.Input("x", 333), domain.Input("y", 333), domain.Input("z", 5))) 40 | 41 | //try to find a formula that matches the above description 42 | problem.Solve() 43 | } 44 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | This is my attempt at doing genetic programming in Go. 2 | The application can breed math formulas in order to solve problems based on given input and expected output. 3 | 4 | 5 | ``` 6 | λ go run program.go 7 | 2016/04/15 19:46:04 Case 0 8 | Var x = 2 9 | Var y = 3 10 | Expected 10 11 | Case 1 12 | Var x = 7 13 | Var y = 2 14 | Expected 63 15 | Case 2 16 | Var x = 6 17 | Var y = 5 18 | Expected 66 19 | Case 3 20 | Var x = 8 21 | Var y = 4 22 | Expected 96 23 | 24 | 2016/04/15 19:46:04 231 1 25 | 2016/04/15 19:46:04 227.04056911668317 1.9898577208292023 26 | 2016/04/15 19:46:04 225.06925986002796 2.4826850349930085 27 | 2016/04/15 19:46:04 211.3481214428312 5.9129696392922 28 | 2016/04/15 19:46:04 205.1839151688682 7.454021207782951 29 | 2016/04/15 19:46:04 191.57396194260832 11.713019028695836 30 | 2016/04/15 19:46:04 186.97642273912768 14.011788630436158 31 | 2016/04/15 19:46:04 165.40717223638566 (1.7696822679686193*14.011788630436158) 32 | 2016/04/15 19:46:04 111.02357726087232 (y*14.011788630436158) 33 | 2016/04/15 19:46:04 111 (y*14) 34 | 2016/04/15 19:46:04 38.02409870676257 (x*11.574871243823225) 35 | 2016/04/15 19:46:04 34 (x*11) 36 | 2016/04/15 19:46:04 33.99999999999999 ((x*11)+-1.5434753795242313) 37 | 2016/04/15 19:46:04 32.93131579583263 ((x*11.213736840833471)+-1.5434753795242313) 38 | 2016/04/15 19:46:04 32.931315795832624 (((x*11.213736840833471)+-1.5434753795242313)-0.606984099938135) 39 | 2016/04/15 19:46:04 32 ((int)((x*11.213736840833471)+-1.5434753795242313)&(int)-3.363353483702479) 40 | 2016/04/15 19:46:04 31 ((int)((x*11.213736840833471)+((int)-1.5434753795242313&(int)-0.4758898825219138))&(int)-3.363353483702479) 41 | 2016/04/15 19:46:04 27 ((int)((x*11.213736840833471)+((int)-1.5434753795242313&(int)-0.4758898825219138))&(int)-5.165992000820678) 42 | 2016/04/15 19:46:04 25 ((int)((x*11.213736840833471)+-0.4758898825219138)&(int)-5.165992000820678) 43 | 2016/04/15 19:46:04 24 ((int)((x*11.213736840833471)+-1.5053658005476873)&(int)-5.165992000820678) 44 | 2016/04/15 19:46:04 23 ((int)((x*11.213736840833471)+(0.3487038194498285+-1.5053658005476873))&(int)-6.056595237779577) 45 | 2016/04/15 19:46:04 17 ((int)((x*(y+x))+(0.3487038194498285+-1.5053658005476873))&(int)-6.056595237779577) 46 | 2016/04/15 19:46:04 5 ((int)(x*(y+x))&(int)-6.056595237779577) 47 | 2016/04/15 19:46:04 0 (x*(y+x)) 48 | 2016/04/15 19:46:04 Solved (x*(y+x)) 49 | 2016/04/15 19:46:04 Generations 13773 50 | 2016/04/15 19:46:04 Time to find solution 60.042ms 51 | ``` -------------------------------------------------------------------------------- /ast/mutate.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import "math/rand" 4 | 5 | const ( 6 | rate1 = 50 7 | rate2 = 100 8 | rate3 = 200 9 | ) 10 | 11 | var letterRunes = []rune("xyz") 12 | 13 | func randomLetter() string { 14 | return string(letterRunes[rand.Intn(len(letterRunes))]) 15 | } 16 | 17 | func randomOperator() binaryOperator { 18 | return operators[rand.Intn(len(operators))] 19 | } 20 | 21 | func hit(max int32) bool { 22 | return rand.Int31n(max) == 1 23 | } 24 | 25 | func randomLiteralNode() Node { 26 | return Literal(rand.NormFloat64()) 27 | } 28 | 29 | func randomBinaryNode() Node { 30 | return Binary(randomNode(), randomNode(), randomOperator()) 31 | } 32 | 33 | func randomVariableNode() Node { 34 | return Var(randomLetter()) 35 | } 36 | 37 | func randomSplit(node Node) Node { 38 | if hit(2) { 39 | split := Binary(randomNode(), node, randomOperator()) 40 | return split 41 | } 42 | split := Binary(node, randomNode(), randomOperator()) 43 | return split 44 | } 45 | 46 | //Prio 47 | //VariableNode 48 | //BinaryNode 49 | //LiteralNode 50 | // 51 | //this is because we most likely want a formula based on variables, and not a lot of magic constants 52 | func randomNode() Node { 53 | if hit(50000) { 54 | return randomLiteralNode() 55 | } 56 | if hit(10) { 57 | return randomBinaryNode() 58 | } 59 | return randomVariableNode() 60 | } 61 | 62 | func randomRemove(node *BinaryNode) Node { 63 | 64 | if hit(2) { 65 | return node.left 66 | } 67 | return node.right 68 | } 69 | 70 | func mutateAny(node Node) Node { 71 | if hit(rate3) { 72 | return randomNode() 73 | } 74 | if hit(rate3) { 75 | return randomSplit(node) 76 | } 77 | 78 | return node 79 | } 80 | 81 | //Mutate the given node 82 | func (node *VariableNode) Mutate() Node { 83 | if hit(rate2) { 84 | return Var(randomLetter()) 85 | } 86 | return mutateAny(node) 87 | } 88 | 89 | //Mutate the given node 90 | func (node *LiteralNode) Mutate() Node { 91 | //mutate by offset 92 | if hit(rate1) { 93 | return Literal(node.value - (rand.Float64()-0.5)*10) 94 | } 95 | if hit(rate1) { 96 | //hard mutation 97 | return randomLiteralNode() 98 | } 99 | if hit(rate1) { 100 | //hard mutation to integer 101 | return Literal(float64(int(node.value))) 102 | } 103 | 104 | return mutateAny(node) 105 | } 106 | 107 | //Mutate the given node 108 | func (node *BinaryNode) Mutate() Node { 109 | //remove left or right 110 | if hit(rate1) { 111 | return randomRemove(node) 112 | } 113 | //mutate children 114 | left := node.left.Mutate() 115 | right := node.right.Mutate() 116 | 117 | operator := node.operator 118 | if hit(rate1) { 119 | operator = randomOperator() 120 | } 121 | 122 | mutated := Binary(left, right, operator) 123 | 124 | return mutateAny(mutated) 125 | } 126 | -------------------------------------------------------------------------------- /ast/operators.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import "github.com/rogeralsing/go-genetic-math/engine" 4 | import "math" 5 | import "fmt" 6 | 7 | type binaryOperator interface { 8 | Apply(left Node, right Node, context *engine.Context) float64 9 | String(left Node, right Node) string 10 | Reduce(left Node, right Node) Node 11 | } 12 | 13 | type OpAddValue struct{} 14 | type OpSubValue struct{} 15 | type OpDivValue struct{} 16 | type OpMulValue struct{} 17 | type OpModValue struct{} 18 | type OpOrValue struct{} 19 | type OpAndValue struct{} 20 | 21 | var OpAdd = OpAddValue{} 22 | var OpSub = OpSubValue{} 23 | var OpDiv = OpDivValue{} 24 | var OpMul = OpMulValue{} 25 | var OpMod = OpModValue{} 26 | var OpOr = OpOrValue{} 27 | var OpAnd = OpAndValue{} 28 | 29 | var operators = [...]binaryOperator{ 30 | OpAdd, 31 | OpSub, 32 | OpMul, 33 | OpDiv, 34 | OpMod, 35 | OpOr, 36 | OpAnd, 37 | // OpXor, 38 | } 39 | 40 | func (OpAddValue) Apply(left Node, right Node, context *engine.Context) float64 { 41 | return left.Eval(context) + right.Eval(context) 42 | } 43 | func (OpSubValue) Apply(left Node, right Node, context *engine.Context) float64 { 44 | return left.Eval(context) - right.Eval(context) 45 | } 46 | func (OpDivValue) Apply(left Node, right Node, context *engine.Context) float64 { 47 | leftValue := left.Eval(context) 48 | rightValue := right.Eval(context) 49 | if rightValue == 0 { 50 | return 0 51 | } 52 | return leftValue / rightValue 53 | } 54 | func (OpMulValue) Apply(left Node, right Node, context *engine.Context) float64 { 55 | return left.Eval(context) * right.Eval(context) 56 | } 57 | func (OpModValue) Apply(left Node, right Node, context *engine.Context) float64 { 58 | leftValue := left.Eval(context) 59 | rightValue := right.Eval(context) 60 | if rightValue <= 0 { 61 | return 0 62 | } 63 | return math.Mod(leftValue, rightValue) 64 | } 65 | func (OpOrValue) Apply(left Node, right Node, context *engine.Context) float64 { 66 | return float64(int(left.Eval(context)) | int(right.Eval(context))) 67 | } 68 | func (OpAndValue) Apply(left Node, right Node, context *engine.Context) float64 { 69 | return float64(int(left.Eval(context)) & int(right.Eval(context))) 70 | } 71 | 72 | func (OpAddValue) String(left Node, right Node) string { 73 | return fmt.Sprintf("(%v+%v)", left, right) 74 | } 75 | func (OpSubValue) String(left Node, right Node) string { 76 | return fmt.Sprintf("(%v-%v)", left, right) 77 | } 78 | func (OpDivValue) String(left Node, right Node) string { 79 | return fmt.Sprintf("(%v/%v)", left, right) 80 | } 81 | func (OpMulValue) String(left Node, right Node) string { 82 | return fmt.Sprintf("(%v*%v)", left, right) 83 | } 84 | func (OpModValue) String(left Node, right Node) string { 85 | return fmt.Sprintf("mod(%v,%v)", left, right) 86 | } 87 | func (OpOrValue) String(left Node, right Node) string { 88 | return fmt.Sprintf("((int)%v|(int)%v)", left, right) 89 | } 90 | func (OpAndValue) String(left Node, right Node) string { 91 | return fmt.Sprintf("((int)%v&(int)%v)", left, right) 92 | } 93 | 94 | func (operator OpAddValue) Reduce(left Node, right Node) Node { 95 | 96 | //if left is 0, we can reduce this to only the right node 97 | if isLiteralZero(left) { 98 | return right 99 | } 100 | 101 | //if right is 0, we can reduce this to only the left node 102 | if isLiteralZero(right) { 103 | return left 104 | } 105 | 106 | return Binary(left, right, operator) 107 | } 108 | func (operator OpSubValue) Reduce(left Node, right Node) Node { 109 | //if left is 0, we can reduce this to only the right node 110 | if isLiteralZero(left) { 111 | return right 112 | } 113 | 114 | //if right is 0, we can reduce this to only the left node 115 | if isLiteralZero(right) { 116 | return left 117 | } 118 | 119 | return Binary(left, right, operator) 120 | } 121 | func (operator OpDivValue) Reduce(left Node, right Node) Node { 122 | return Binary(left, right, operator) 123 | } 124 | func (operator OpMulValue) Reduce(left Node, right Node) Node { 125 | 126 | //anything multiplied by 0 is 0, reduce to constant 127 | if isLiteralZero(left) || isLiteralZero(right) { 128 | return Literal(0) 129 | } 130 | return Binary(left, right, operator) 131 | } 132 | func (operator OpModValue) Reduce(left Node, right Node) Node { 133 | return Binary(left, right, operator) 134 | } 135 | func (operator OpOrValue) Reduce(left Node, right Node) Node { 136 | return Binary(left, right, operator) 137 | } 138 | func (operator OpAndValue) Reduce(left Node, right Node) Node { 139 | return Binary(left, right, operator) 140 | } 141 | -------------------------------------------------------------------------------- /domain/cases.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | "math" 8 | "math/rand" 9 | "sort" 10 | "time" 11 | 12 | "github.com/rogeralsing/go-genetic-math/ast" 13 | "github.com/rogeralsing/go-genetic-math/engine" 14 | ) 15 | 16 | type InputValue struct { 17 | Variable string 18 | Value float64 19 | } 20 | 21 | func Input(variable string, value float64) InputValue { 22 | return InputValue{Variable: variable, Value: value} 23 | } 24 | 25 | type CaseValue struct { 26 | inputs []InputValue 27 | result float64 28 | } 29 | 30 | func Case(result float64, inputs ...InputValue) CaseValue { 31 | return CaseValue{ 32 | result: result, 33 | inputs: inputs, 34 | } 35 | } 36 | 37 | type CasesValue struct { 38 | cases []CaseValue 39 | } 40 | 41 | func DefineProblem(cases ...CaseValue) CasesValue { 42 | return CasesValue{cases: cases} 43 | } 44 | 45 | func (cases CasesValue) String() string { 46 | var buffer bytes.Buffer 47 | for x, c := range cases.cases { 48 | buffer.WriteString(fmt.Sprintf("Case %v\n", x)) 49 | for _, i := range c.inputs { 50 | buffer.WriteString(fmt.Sprintf("\tVar %s = %v \n", i.Variable, i.Value)) 51 | } 52 | buffer.WriteString(fmt.Sprintf("\tExpected %v \n", c.result)) 53 | } 54 | return fmt.Sprint(buffer.String()) 55 | } 56 | 57 | func (cases CasesValue) Fitness(node ast.Node) float64 { 58 | context := engine.NewContext() 59 | total := 0.0 60 | for _, c := range cases.cases { 61 | for _, i := range c.inputs { 62 | context.SetVariable(i.Variable, i.Value) 63 | } 64 | res := node.Eval(context) 65 | diff := math.Abs(c.result - res) 66 | total += diff 67 | } 68 | 69 | return total 70 | } 71 | 72 | func calculateFitness(nodes []ast.Node, cases CasesValue) []NodeFitness { 73 | var fitnessNodes = make([]NodeFitness, len(nodes)) 74 | for i := 0; i < len(nodes); i++ { 75 | node := nodes[i] 76 | fitnessNodes[i].Node = node 77 | fitnessNodes[i].Fitness = cases.Fitness(node) 78 | } 79 | sort.Sort(byFitnessAndWeight(fitnessNodes)) 80 | return fitnessNodes 81 | } 82 | 83 | func (cases CasesValue) Solve() ast.Node { 84 | 85 | start := time.Now() 86 | log.Println(cases) 87 | results := make(chan ast.Node, 1) 88 | cancel := make(chan struct{}, 1) 89 | defer close(results) 90 | defer close(cancel) 91 | 92 | for i := 0; i < 10 ; i++ { 93 | go solve(cases, results, cancel) 94 | } 95 | 96 | solution := <-results 97 | log.Printf("Solved %v", solution) 98 | elapsed := time.Since(start) 99 | log.Printf("Time to find solution %s", elapsed) 100 | return solution 101 | } 102 | 103 | func solve(cases CasesValue, results chan<- ast.Node, cancel <-chan struct{}) { 104 | populationSize := 10 105 | generaton := 0 106 | optimizations := 1 107 | var population = make([]ast.Node, populationSize) 108 | 109 | //initialize with dummy data 110 | for i := 0; i < populationSize; i++ { 111 | population[i] = ast.Literal(1).Mutate() 112 | } 113 | 114 | bestFitness := math.MaxFloat64 115 | for { 116 | 117 | select { 118 | case <-cancel: 119 | log.Println("Quitting") 120 | return 121 | default: 122 | } 123 | 124 | //create a child per parent 125 | for i := 0; i < populationSize; i++ { 126 | child := population[i].Mutate() 127 | population = append(population, child) 128 | } 129 | 130 | //create children by genetic crossover 131 | for i := 0; i < populationSize; i++ { 132 | mother := population[rand.Intn(len(population))] 133 | father := population[rand.Intn(len(population))] 134 | child := mother.Combine(father) 135 | population = append(population, child) 136 | } 137 | 138 | //sort all organisms by fitness 139 | sorted := calculateFitness(population, cases) 140 | best := sorted[0] 141 | 142 | if generaton%1000 == 0 { 143 | log.Printf("Generation %v \t %v %v", generaton, best.Fitness, best.Node) 144 | } 145 | 146 | //if we got a better fitness now, print it 147 | if best.Fitness < bestFitness { 148 | bestFitness = best.Fitness 149 | log.Printf("Generation %v \t %v %v", generaton, best.Fitness, best.Node) 150 | } 151 | 152 | if best.Fitness == 0 { 153 | optimizations-- 154 | } 155 | //did we find a solution? if so return it 156 | if best.Fitness == 0 && optimizations == 0 { 157 | solution := best.Node.Reduce() 158 | results <- solution 159 | return 160 | } 161 | 162 | population = make([]ast.Node, populationSize) 163 | for i := 0; i < populationSize; i++ { 164 | population[i] = sorted[i].Node 165 | } 166 | generaton++ 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // Available variables which can be used inside of strings. 2 | // ${workspaceRoot}: the root folder of the team 3 | // ${file}: the current opened file 4 | // ${fileBasename}: the current opened file's basename 5 | // ${fileDirname}: the current opened file's dirname 6 | // ${fileExtname}: the current opened file's extension 7 | // ${cwd}: the current working directory of the spawned process 8 | 9 | // A task runner that calls the Typescript compiler (tsc) and 10 | // Compiles a HelloWorld.ts program 11 | { 12 | "version": "0.1.0", 13 | 14 | // The command is tsc. Assumes that tsc has been installed using npm install -g typescript 15 | "command": "go", 16 | 17 | // The command is a shell script 18 | "isShellCommand": true, 19 | 20 | // Show the output window only if unrecognized errors occur. 21 | "showOutput": "silent", 22 | 23 | // args is the HelloWorld program to compile. 24 | "args": ["run","Program.go"] 25 | } 26 | 27 | // A task runner that calls the Typescript compiler (tsc) and 28 | // compiles based on a tsconfig.json file that is present in 29 | // the root of the folder open in VSCode 30 | /* 31 | { 32 | "version": "0.1.0", 33 | 34 | // The command is tsc. Assumes that tsc has been installed using npm install -g typescript 35 | "command": "tsc", 36 | 37 | // The command is a shell script 38 | "isShellCommand": true, 39 | 40 | // Show the output window only if unrecognized errors occur. 41 | "showOutput": "silent", 42 | 43 | // Tell the tsc compiler to use the tsconfig.json from the open folder. 44 | "args": ["-p", "."], 45 | 46 | // use the standard tsc problem matcher to find compile problems 47 | // in the output. 48 | "problemMatcher": "$tsc" 49 | } 50 | */ 51 | 52 | // A task runner configuration for gulp. Gulp provides a less task 53 | // which compiles less to css. 54 | /* 55 | { 56 | "version": "0.1.0", 57 | "command": "gulp", 58 | "isShellCommand": true, 59 | "tasks": [ 60 | { 61 | "taskName": "less", 62 | // Make this the default build command. 63 | "isBuildCommand": true, 64 | // Show the output window only if unrecognized errors occur. 65 | "showOutput": "silent", 66 | // Use the standard less compilation problem matcher. 67 | "problemMatcher": "$lessCompile" 68 | } 69 | ] 70 | } 71 | */ 72 | 73 | // Uncomment the following section to use jake to build a workspace 74 | // cloned from https://github.com/Microsoft/TypeScript.git 75 | /* 76 | { 77 | "version": "0.1.0", 78 | // Task runner is jake 79 | "command": "jake", 80 | // Need to be executed in shell / cmd 81 | "isShellCommand": true, 82 | "showOutput": "silent", 83 | "tasks": [ 84 | { 85 | // TS build command is local. 86 | "taskName": "local", 87 | // Make this the default build command. 88 | "isBuildCommand": true, 89 | // Show the output window only if unrecognized errors occur. 90 | "showOutput": "silent", 91 | // Use the redefined Typescript output problem matcher. 92 | "problemMatcher": [ 93 | "$tsc" 94 | ] 95 | } 96 | ] 97 | } 98 | */ 99 | 100 | // Uncomment the section below to use msbuild and generate problems 101 | // for csc, cpp, tsc and vb. The configuration assumes that msbuild 102 | // is available on the path and a solution file exists in the 103 | // workspace folder root. 104 | /* 105 | { 106 | "version": "0.1.0", 107 | "command": "msbuild", 108 | "args": [ 109 | // Ask msbuild to generate full paths for file names. 110 | "/property:GenerateFullPaths=true" 111 | ], 112 | "taskSelector": "/t:", 113 | "showOutput": "silent", 114 | "tasks": [ 115 | { 116 | "taskName": "build", 117 | // Show the output window only if unrecognized errors occur. 118 | "showOutput": "silent", 119 | // Use the standard MS compiler pattern to detect errors, warnings 120 | // and infos in the output. 121 | "problemMatcher": "$msCompile" 122 | } 123 | ] 124 | } 125 | */ 126 | 127 | // Uncomment the following section to use msbuild which compiles Typescript 128 | // and less files. 129 | /* 130 | { 131 | "version": "0.1.0", 132 | "command": "msbuild", 133 | "args": [ 134 | // Ask msbuild to generate full paths for file names. 135 | "/property:GenerateFullPaths=true" 136 | ], 137 | "taskSelector": "/t:", 138 | "showOutput": "silent", 139 | "tasks": [ 140 | { 141 | "taskName": "build", 142 | // Show the output window only if unrecognized errors occur. 143 | "showOutput": "silent", 144 | // Use the standard MS compiler pattern to detect errors, warnings 145 | // and infos in the output. 146 | "problemMatcher": [ 147 | "$msCompile", 148 | "$lessCompile" 149 | ] 150 | } 151 | ] 152 | } 153 | */ 154 | // A task runner example that defines a problemMatcher inline instead of using 155 | // a predefined one. 156 | /* 157 | { 158 | "version": "0.1.0", 159 | "command": "tsc", 160 | "isShellCommand": true, 161 | "args": ["HelloWorld.ts"], 162 | "showOutput": "silent", 163 | "problemMatcher": { 164 | // The problem is owned by the typescript language service. Ensure that the problems 165 | // are merged with problems produced by Visual Studio's language service. 166 | "owner": "typescript", 167 | // The file name for reported problems is relative to the current working directory. 168 | "fileLocation": ["relative", "${cwd}"], 169 | // The actual pattern to match problems in the output. 170 | "pattern": { 171 | // The regular expression. Matches HelloWorld.ts(2,10): error TS2339: Property 'logg' does not exist on type 'Console'. 172 | "regexp": "^([^\\s].*)\\((\\d+|\\d+,\\d+|\\d+,\\d+,\\d+,\\d+)\\):\\s+(error|warning|info)\\s+(TS\\d+)\\s*:\\s*(.*)$", 173 | // The match group that denotes the file containing the problem. 174 | "file": 1, 175 | // The match group that denotes the problem location. 176 | "location": 2, 177 | // The match group that denotes the problem's severity. Can be omitted. 178 | "severity": 3, 179 | // The match group that denotes the problem code. Can be omitted. 180 | "code": 4, 181 | // The match group that denotes the problem's message. 182 | "message": 5 183 | } 184 | } 185 | } 186 | */ --------------------------------------------------------------------------------