├── .github └── workflows │ └── checks.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── arbitrer.go ├── arbitrer_test.go ├── codecov.yml ├── crosser.go ├── evaluater.go ├── evolution.go ├── examplegnetic_test.go ├── exampleswarm_test.go ├── genetic.go ├── genetic_test.go ├── go.mod ├── individual.go ├── individual_test.go ├── mock_test.go ├── mutater.go ├── pool.go ├── poolSync.go ├── pool_test.go ├── population.go ├── populationSync.go ├── population_test.go ├── positioner.go ├── selecter.go ├── selecter_test.go ├── swarm.go ├── swarm_test.go └── test.sh /.github/workflows/checks.yml: -------------------------------------------------------------------------------- 1 | name: checks 2 | on: 3 | push: 4 | branches: 5 | - 'master' 6 | pull_request_target: 7 | branches: 8 | - 'master' 9 | jobs: 10 | 11 | checks: 12 | name: checks 13 | runs-on: ubuntu-latest 14 | container: 15 | image: khezen/goci 16 | env: 17 | WORKDIR: /go/src/github.com/khezen/evoli 18 | steps: 19 | 20 | - name: checkout 21 | run: | 22 | set -e 23 | mkdir -p $WORKDIR 24 | git clone -n --depth=1 --no-single-branch https://khezen:${{ secrets.GITHUB_TOKEN }}@github.com/khezen/evoli $WORKDIR 25 | cd $WORKDIR 26 | git fetch --depth=1 origin $GITHUB_SHA 27 | git checkout FETCH_HEAD 28 | 29 | - name: linter 30 | if: github.event_name == 'pull_request' 31 | working-directory: ${{ env.WORKDIR }} 32 | run: | 33 | LINTER=$LINTER$( { golangci-lint run --modules-download-mode vendor || true; } 2>&1 ); 34 | if [ ${#LINTER} -eq 0 ]; then 35 | GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} cmtpr "**Linter:** :heavy_check_mark:" 36 | else 37 | LINTER_FMT=$( printf "%s\n" "**Linter:** :x:" "\`\`\`sh" "$LINTER" "\`\`\`" ) 38 | GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} cmtpr "$LINTER_FMT" 39 | fi 40 | 41 | - name: unit tests 42 | working-directory: ${{ env.WORKDIR }} 43 | run: | 44 | echo "" > coverage.txt 45 | sh test.sh 46 | 47 | - name: code coverage 48 | if: github.ref == 'refs/heads/master' 49 | working-directory: ${{ env.WORKDIR }} 50 | run: curl -s https://codecov.io/bash | bash 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | go.sum 6 | 7 | # Folders 8 | _obj 9 | _test 10 | 11 | # Architecture specific extensions/prefixes 12 | *.[568vq] 13 | [568vq].out 14 | 15 | *.cgo1.go 16 | *.cgo2.c 17 | _cgo_defun.c 18 | _cgo_gotypes.go 19 | _cgo_export.* 20 | 21 | _testmain.go 22 | 23 | *.exe 24 | *.test 25 | *.prof 26 | 27 | # workspace settings 28 | .vscode/ 29 | 30 | # Output of the go coverage tool, specifically when used with LiteIDE 31 | *.out 32 | 33 | # external packages folder 34 | vendor/ 35 | 36 | coverage.* -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at . All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Guillaume Simonneau 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # *evoli* 2 | 3 | [![GoDoc](https://img.shields.io/badge/go-documentation-blue.svg)](https://godoc.org/github.com/khezen/evoli) 4 | [![Build Status](https://github.com/khezen/evoli/workflows/build/badge.svg?branch=master)](https://github.com/khezen/evoli/actions?query=workflow%3Abuild) [![codecov](https://img.shields.io/codecov/c/github/khezen/evoli/master.svg)](https://codecov.io/gh/khezen/evoli) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/khezen/evoli)](https://goreportcard.com/report/github.com/khezen/evoli) 6 | 7 | Genetic Algorithm and Particle Swarm Optimization written in Go 8 | 9 | ## Example 10 | 11 | ### Problem 12 | 13 | Given `f(x,y) = cos(x^2 * y^2) * 1/(x^2 * y^2 + 1)` 14 | 15 | Find `(x,y)` such as `f(x,y)` reaches its maximum 16 | 17 | Answer `f(0,0) = 1` 18 | 19 | ### Particle Swarm Optimization 20 | 21 | ```golang 22 | package main 23 | 24 | import ( 25 | "fmt" 26 | "math" 27 | "math/rand" 28 | 29 | "github.com/khezen/evoli" 30 | ) 31 | 32 | // 3d cosine that gets smaller as you move away from 0,0 33 | func f(x, y float64) float64 { 34 | d := x*x + y*y 35 | return math.Cos(d) * (1 / (d/10 + 1)) 36 | } 37 | 38 | type FIndividual struct { 39 | v []float64 40 | x []float64 41 | fitness float64 42 | } 43 | 44 | func (i *FIndividual) Equal(other evoli.Individual) bool { 45 | return i == other 46 | } 47 | 48 | func (i *FIndividual) Fitness() float64 { 49 | return i.fitness 50 | } 51 | 52 | func (i *FIndividual) SetFitness(newFitness float64) { 53 | i.fitness = newFitness 54 | } 55 | 56 | type FPositioner struct { 57 | } 58 | 59 | func (p *FPositioner) Position(indiv, pBest, gBest evoli.Individual, c1, c2 float64) (evoli.Individual, error) { 60 | fIndiv, ok1 := indiv.(*FIndividual) 61 | fPBest, ok2 := pBest.(*FIndividual) 62 | fGBest, ok3 := gBest.(*FIndividual) 63 | if !ok1 || !ok2 || !ok3 { 64 | return nil, fmt.Errorf("invalid individual type") 65 | } 66 | newIndiv := FIndividual{ 67 | v: make([]float64, len(fIndiv.v)), 68 | x: make([]float64, len(fIndiv.v)), 69 | } 70 | w := 0.9 71 | for d := range fIndiv.v { 72 | rp := rand.Float64() 73 | rg := rand.Float64() 74 | newIndiv.v[d] = w*fIndiv.v[d] + 75 | c1*rp*(fPBest.x[d]-fIndiv.x[d]) + 76 | c2*rg*(fGBest.x[d]-fIndiv.x[d]) 77 | 78 | newIndiv.x[d] = fIndiv.x[d] + newIndiv.v[d] 79 | } 80 | return &newIndiv, nil 81 | } 82 | 83 | type FEvaluater struct { 84 | } 85 | 86 | func (e *FEvaluater) Evaluate(indiv evoli.Individual) (Fitness float64, err error) { 87 | fIndiv, ok := indiv.(*FIndividual) 88 | if !ok { 89 | return 0, fmt.Errorf("invalid individual type") 90 | } 91 | return f(fIndiv.x[0], fIndiv.x[1]), nil 92 | } 93 | 94 | func main() { 95 | pop := evoli.NewPopulation(50) 96 | for i := 0; i < pop.Cap(); i++ { 97 | x := rand.Float64()*20 - 10 98 | y := rand.Float64()*20 - 10 99 | vx := rand.Float64()*20 - 10 100 | vy := rand.Float64()*20 - 10 101 | pop.Add(&FIndividual{ 102 | x: []float64{x, y}, 103 | v: []float64{vx, vy}, 104 | }) 105 | } 106 | positioner := &FPositioner{} 107 | evaluator := &FEvaluater{} 108 | 109 | sw := evoli.NewSwarm(pop, positioner, .2, .2, evaluator) 110 | 111 | for i := 0; i < 100; i++ { 112 | err := sw.Next() 113 | if err != nil { 114 | panic(err) 115 | } 116 | } 117 | 118 | // evaluate the latest population 119 | for _, v := range sw.Population().Slice() { 120 | f, err := evaluator.Evaluate(v) 121 | if err != nil { 122 | panic(err) 123 | } 124 | v.SetFitness(f) 125 | } 126 | 127 | fmt.Printf("Max Value: %.2f\n", sw.Alpha().Fitness()) 128 | } 129 | ``` 130 | 131 | ```bash 132 | Max Value: 1.00 133 | ``` 134 | 135 | ### Gentic Algorithm 136 | 137 | ```golang 138 | package main 139 | 140 | import ( 141 | "fmt" 142 | "math" 143 | "math/rand" 144 | 145 | "github.com/khezen/evoli" 146 | ) 147 | 148 | // 3d cosine that gets smaller as you move away from 0,0 149 | func h(x, y float64) float64 { 150 | d := x*x + y*y 151 | return math.Cos(d) * (1 / (d/10 + 1)) 152 | } 153 | 154 | type HIndividual struct { 155 | v []float64 156 | x []float64 157 | fitness float64 158 | } 159 | 160 | func (i *HIndividual) Equal(other evoli.Individual) bool { 161 | return i == other 162 | } 163 | 164 | func (i *HIndividual) Fitness() float64 { 165 | return i.fitness 166 | } 167 | 168 | func (i *HIndividual) SetFitness(newFitness float64) { 169 | i.fitness = newFitness 170 | } 171 | 172 | type HMutater struct { 173 | } 174 | 175 | func (m *HMutater) Mutate(indiv evoli.Individual, mutationProbability float64) (evoli.Individual, error) { 176 | hIndiv1, ok := indiv.(*HIndividual) 177 | if !ok { 178 | return nil, fmt.Errorf("invalid individual type") 179 | } 180 | x := hIndiv1.x[0] 181 | y := hIndiv1.x[1] 182 | vx := hIndiv1.v[0] 183 | vy := hIndiv1.v[1] 184 | if mutationProbability > rand.Float64() { 185 | x = rand.Float64()*20 - 10 186 | } 187 | if mutationProbability > rand.Float64() { 188 | y = rand.Float64()*20 - 10 189 | } 190 | if mutationProbability > rand.Float64() { 191 | vx = rand.Float64()*20 - 10 192 | } 193 | if mutationProbability > rand.Float64() { 194 | vy = rand.Float64()*20 - 10 195 | } 196 | return &HIndividual{ 197 | x: []float64{x, y}, 198 | v: []float64{vx, vy}, 199 | }, nil 200 | } 201 | 202 | type HCrosser struct { 203 | } 204 | 205 | func (h *HCrosser) Cross(indiv1, indiv2 evoli.Individual) (evoli.Individual, evoli.Individual, error) { 206 | hIndiv1, ok1 := indiv1.(*HIndividual) 207 | hIndiv2, ok2 := indiv2.(*HIndividual) 208 | if !ok1 || !ok2 { 209 | return nil, nil, fmt.Errorf("invalid individual type") 210 | } 211 | return &HIndividual{ 212 | x: []float64{hIndiv1.x[0], hIndiv2.x[1]}, 213 | v: []float64{hIndiv1.v[0], hIndiv2.v[1]}, 214 | }, &HIndividual{ 215 | x: []float64{hIndiv2.x[0], hIndiv1.x[1]}, 216 | v: []float64{hIndiv2.v[0], hIndiv1.v[1]}, 217 | }, nil 218 | } 219 | 220 | type HEvaluater struct { 221 | } 222 | 223 | func (e *HEvaluater) Evaluate(indiv evoli.Individual) (Fitness float64, err error) { 224 | fIndiv, ok := indiv.(*HIndividual) 225 | if !ok { 226 | return 0, fmt.Errorf("invalid individual type") 227 | } 228 | return h(fIndiv.x[0], fIndiv.x[1]), nil 229 | } 230 | 231 | func main() { 232 | pop := evoli.NewPopulation(50) 233 | for i := 0; i < pop.Cap(); i++ { 234 | x := rand.Float64()*20 - 10 235 | y := rand.Float64()*20 - 10 236 | vx := rand.Float64()*20 - 10 237 | vy := rand.Float64()*20 - 10 238 | pop.Add(&HIndividual{ 239 | x: []float64{x, y}, 240 | v: []float64{vx, vy}, 241 | }) 242 | } 243 | crosser := &HCrosser{} 244 | mutater := &HMutater{} 245 | evaluator := &HEvaluater{} 246 | mutationProbability := .20 247 | selecter := evoli.NewTruncationSelecter() 248 | survivorSize := 30 249 | 250 | ga := evoli.NewGenetic(pop, selecter, survivorSize, crosser, mutater, mutationProbability, evaluator) 251 | 252 | for i := 0; i < 100; i++ { 253 | err := ga.Next() 254 | if err != nil { 255 | panic(err) 256 | } 257 | } 258 | 259 | // evaluate the latest population 260 | for _, v := range ga.Population().Slice() { 261 | f, err := evaluator.Evaluate(v) 262 | if err != nil { 263 | panic(err) 264 | } 265 | v.SetFitness(f) 266 | } 267 | 268 | fmt.Printf("Max Value: %.2f\n", ga.Alpha().Fitness()) 269 | } 270 | ``` 271 | 272 | ```bash 273 | Max Value: 1.00 274 | ``` 275 | 276 | 277 | ## Issues 278 | 279 | If you have any problems or questions, please ask for help through a [GitHub issue](https://github.com/khezen/evoli/issues). 280 | 281 | ## Contributions 282 | 283 | Help is always welcome! For example, documentation (like the text you are reading now) can always use improvement. There's always code that can be improved. If you ever see something you think should be fixed, you should own it. If you have no idea what to start on, you can browse the issues labeled with [help wanted](https://github.com/khezen/evoli/labels/help%20wanted). 284 | 285 | As a potential contributor, your changes and ideas are welcome at any hour of the day or night, weekdays, weekends, and holidays. Please do not ever hesitate to ask a question or send a pull request. 286 | 287 | [Code of conduct](https://github.com/khezen/evoli/blob/master/CODE_OF_CONDUCT.md). 288 | -------------------------------------------------------------------------------- /arbitrer.go: -------------------------------------------------------------------------------- 1 | package evoli 2 | 3 | // ErrArbitration - must provide one or more individuals 4 | var ErrArbitration = "ErrArbitration - must provide one or more individuals" 5 | 6 | // Arbitrer - elect the winner between multiple participants 7 | type Arbitrer interface { 8 | Abritrate(participants ...Individual) (winner Individual, loosers []Individual) 9 | } 10 | 11 | func checkArbitrersParams(participants []Individual) { 12 | if len(participants) < 1 { 13 | panic(ErrArbitration) 14 | } 15 | } 16 | 17 | type selecterBasedArbitrer struct { 18 | Selecter 19 | } 20 | 21 | func (a selecterBasedArbitrer) Abritrate(participants ...Individual) (winner Individual, loosers []Individual) { 22 | checkArbitrersParams(participants) 23 | pop := NewPopulation(len(participants)) 24 | pop.Add(participants...) 25 | selected, deads, _ := a.Select(pop, 1) 26 | var deadsSlice []Individual 27 | if deads != nil { 28 | deadsSlice = deads.Slice() 29 | } 30 | defer selected.Close() 31 | return selected.Get(0), deadsSlice 32 | } 33 | 34 | // NewProportionalToFitnessArbitrer - based on fitness value 35 | func NewProportionalToFitnessArbitrer() Arbitrer { 36 | return selecterBasedArbitrer{NewProportionalToFitnessSelecter()} 37 | } 38 | 39 | // NewProportionalToRankArbitrer - based on rank 40 | func NewProportionalToRankArbitrer() Arbitrer { 41 | return selecterBasedArbitrer{NewProportionalToRankSelecter()} 42 | } 43 | 44 | // NewStochasticUniversalSamplingArbitrer - based on fitness value 45 | func NewStochasticUniversalSamplingArbitrer() Arbitrer { 46 | return selecterBasedArbitrer{NewStochasticUniversalSamplingSelecter()} 47 | } 48 | 49 | // NewTournamentArbitrer - High Fitness increase chances to come out vcitorious from a duel 50 | func NewTournamentArbitrer(p float64) Arbitrer { 51 | return selecterBasedArbitrer{NewTournamentSelecter(p)} 52 | } 53 | 54 | // NewTruncationArbitrer - take the highest fitness 55 | func NewTruncationArbitrer() Arbitrer { 56 | return selecterBasedArbitrer{NewTruncationSelecter()} 57 | } 58 | 59 | // NewRandomArbitrer - choose randomly 60 | func NewRandomArbitrer() Arbitrer { 61 | return selecterBasedArbitrer{NewRandomSelecter()} 62 | } 63 | -------------------------------------------------------------------------------- /arbitrer_test.go: -------------------------------------------------------------------------------- 1 | package evoli 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestArbitrerTruncation(t *testing.T) { 8 | testArbitrer(t, NewTruncationArbitrer()) 9 | } 10 | 11 | func TestArbitrerTournament(t *testing.T) { 12 | testArbitrer(t, NewTournamentArbitrer(1)) 13 | } 14 | 15 | func TestArbitrerRandom(t *testing.T) { 16 | testArbitrer(t, NewRandomArbitrer()) 17 | } 18 | 19 | func TestArbitrerProportionalToRank(t *testing.T) { 20 | testArbitrer(t, NewProportionalToRankArbitrer()) 21 | } 22 | 23 | func TestArbitrerProportionalToFitness(t *testing.T) { 24 | testArbitrer(t, NewProportionalToFitnessArbitrer()) 25 | } 26 | 27 | func testArbitrer(t *testing.T, a Arbitrer) { 28 | i1, i2, i3, i4, i5, i6 := NewIndividual(1), NewIndividual(2), NewIndividual(-3), NewIndividual(4), NewIndividual(5), NewIndividual(-6) 29 | cases := []struct { 30 | in []Individual 31 | }{ 32 | {[]Individual{i1, i2, i3, i4, i5, i6}}, 33 | {[]Individual{i1, i1, i1, i1, i1, i1, i1, i1, i1, i1, i1, i1, i1}}, 34 | {[]Individual{i1, i2, i4, i5}}, 35 | {[]Individual{i1}}, 36 | {[]Individual{i3, i6}}, 37 | } 38 | for _, c := range cases { 39 | indiv, _ := a.Abritrate(c.in...) 40 | ok := false 41 | for _, inIndiv := range c.in { 42 | if indiv == inIndiv { 43 | ok = true 44 | break 45 | } 46 | } 47 | if !ok { 48 | t.Error("oups") 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | notify: 3 | require_ci_to_pass: yes 4 | 5 | coverage: 6 | precision: 2 7 | round: down 8 | range: "70...100" 9 | 10 | status: 11 | project: yes 12 | patch: yes 13 | changes: no 14 | 15 | parsers: 16 | gcov: 17 | branch_detection: 18 | conditional: yes 19 | loop: yes 20 | method: no 21 | macro: no 22 | -------------------------------------------------------------------------------- /crosser.go: -------------------------------------------------------------------------------- 1 | package evoli 2 | 3 | // Crosser produces a new individual from two individuals. This operators provides convergence to a population. 4 | type Crosser interface { 5 | Cross(parent1, parent2 Individual) (child1, child2 Individual, err error) 6 | } 7 | -------------------------------------------------------------------------------- /evaluater.go: -------------------------------------------------------------------------------- 1 | package evoli 2 | 3 | // Evaluater calculates individual Fitness 4 | type Evaluater interface { 5 | Evaluate(Individual) (Fitness float64, err error) 6 | } 7 | -------------------------------------------------------------------------------- /evolution.go: -------------------------------------------------------------------------------- 1 | package evoli 2 | 3 | // Evolution - problem solving algorithm exploring the range of solutions 4 | type Evolution interface { 5 | Population() Population 6 | SetPopulation(Population) 7 | Next() error 8 | Evaluater() Evaluater 9 | Alpha() Individual 10 | } 11 | 12 | type evolution struct { 13 | pop Population 14 | evaluater Evaluater 15 | } 16 | 17 | func newEvolution(pop Population, eval Evaluater) evolution { 18 | return evolution{pop, eval} 19 | } 20 | func (e *evolution) Population() Population { 21 | return e.pop 22 | } 23 | 24 | func (e *evolution) SetPopulation(pop Population) { 25 | e.pop = pop 26 | } 27 | 28 | func (e *evolution) Alpha() Individual { 29 | return e.pop.Max() 30 | } 31 | 32 | func (e *evolution) Evaluater() Evaluater { 33 | return e.evaluater 34 | } 35 | -------------------------------------------------------------------------------- /examplegnetic_test.go: -------------------------------------------------------------------------------- 1 | package evoli_test 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "math/rand" 7 | 8 | "github.com/khezen/evoli" 9 | ) 10 | 11 | // 3d cosine that gets smaller as you move away from 0,0 12 | func h(x, y float64) float64 { 13 | d := x*x + y*y 14 | return math.Cos(d) * (1 / (d/10 + 1)) 15 | } 16 | 17 | type HIndividual struct { 18 | v []float64 19 | x []float64 20 | fitness float64 21 | } 22 | 23 | func (i *HIndividual) Equal(other evoli.Individual) bool { 24 | return i == other 25 | } 26 | 27 | func (i *HIndividual) Fitness() float64 { 28 | return i.fitness 29 | } 30 | 31 | func (i *HIndividual) SetFitness(newFitness float64) { 32 | i.fitness = newFitness 33 | } 34 | 35 | type HMutater struct { 36 | } 37 | 38 | func (m HMutater) Mutate(indiv evoli.Individual, p float64) (evoli.Individual, error) { 39 | if rand.Float64() <= p { 40 | x := rand.Float64()*20 - 10 41 | y := rand.Float64()*20 - 10 42 | vx := rand.Float64()*20 - 10 43 | vy := rand.Float64()*20 - 10 44 | return &HIndividual{ 45 | x: []float64{x, y}, 46 | v: []float64{vx, vy}, 47 | }, nil 48 | } else { 49 | return indiv, nil 50 | } 51 | 52 | } 53 | 54 | type HCrosser struct { 55 | } 56 | 57 | func (h HCrosser) Cross(parent1, parent2 evoli.Individual) (child1, child2 evoli.Individual, err error) { 58 | fIndiv1, _ := parent1.(*HIndividual) 59 | fIndiv2, _ := parent2.(*HIndividual) 60 | w := 0.1 + 0.8*rand.Float64() 61 | return &HIndividual{ 62 | x: []float64{w*fIndiv1.x[0] + (1-w)*fIndiv2.x[0], w*fIndiv1.x[1] + (1-w)*fIndiv2.x[1]}, 63 | v: []float64{w*fIndiv1.v[0] + (1-w)*fIndiv2.v[0], w*fIndiv1.v[1] + (1-w)*fIndiv2.v[1]}, 64 | }, &HIndividual{ 65 | x: []float64{(1-w)*fIndiv1.x[0] + w*fIndiv2.x[0], (1-w)*fIndiv1.x[1] + w*fIndiv2.x[1]}, 66 | v: []float64{(1-w)*fIndiv1.v[0] + w*fIndiv2.v[0], (1-w)*fIndiv1.v[1] + w*fIndiv2.v[1]}, 67 | }, nil 68 | } 69 | 70 | type HEvaluater struct { 71 | } 72 | 73 | func (e HEvaluater) Evaluate(indiv evoli.Individual) (Fitness float64, err error) { 74 | fIndiv, ok := indiv.(*HIndividual) 75 | if !ok { 76 | return 0, fmt.Errorf("invalid individual type") 77 | } 78 | return h(fIndiv.x[0], fIndiv.x[1]), nil 79 | } 80 | 81 | func ExampleNewGenetic() { 82 | 83 | pop := evoli.NewPopulation(50) 84 | for i := 0; i < pop.Cap(); i++ { 85 | x := rand.Float64()*20 - 10 86 | y := rand.Float64()*20 - 10 87 | vx := rand.Float64()*20 - 10 88 | vy := rand.Float64()*20 - 10 89 | pop.Add(&HIndividual{ 90 | x: []float64{x, y}, 91 | v: []float64{vx, vy}, 92 | }) 93 | } 94 | crosser := HCrosser{} 95 | mutater := HMutater{} 96 | evaluator := HEvaluater{} 97 | mutationProbability := .02 98 | selecter := evoli.NewTruncationSelecter() 99 | survivorSize := 30 100 | 101 | ga := evoli.NewGenetic(pop, selecter, survivorSize, crosser, mutater, mutationProbability, evaluator) 102 | 103 | for i := 0; i < 100; i++ { 104 | err := ga.Next() 105 | if err != nil { 106 | panic(err) 107 | } 108 | } 109 | 110 | // evaluate the latest population 111 | for _, v := range ga.Population().Slice() { 112 | f, err := evaluator.Evaluate(v) 113 | if err != nil { 114 | panic(err) 115 | } 116 | v.SetFitness(f) 117 | } 118 | 119 | fmt.Printf("Max Value: %.2f\n", ga.Alpha().Fitness()) 120 | 121 | // Output: Max Value: 1.00 122 | } 123 | -------------------------------------------------------------------------------- /exampleswarm_test.go: -------------------------------------------------------------------------------- 1 | package evoli_test 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "math/rand" 7 | 8 | "github.com/khezen/evoli" 9 | ) 10 | 11 | // 3d cosine that gets smaller as you move away from 0,0 12 | func f(x, y float64) float64 { 13 | d := x*x + y*y 14 | return math.Cos(d) * (1 / (d/10 + 1)) 15 | } 16 | 17 | type FIndividual struct { 18 | v []float64 19 | x []float64 20 | fitness float64 21 | } 22 | 23 | func (i *FIndividual) Equal(other evoli.Individual) bool { 24 | return i == other 25 | } 26 | 27 | func (i *FIndividual) Fitness() float64 { 28 | return i.fitness 29 | } 30 | 31 | func (i *FIndividual) SetFitness(newFitness float64) { 32 | i.fitness = newFitness 33 | } 34 | 35 | type FPositioner struct { 36 | } 37 | 38 | func (p *FPositioner) Position(indiv, pBest, gBest evoli.Individual, c1, c2 float64) (evoli.Individual, error) { 39 | fIndiv, ok1 := indiv.(*FIndividual) 40 | fPBest, ok2 := pBest.(*FIndividual) 41 | fGBest, ok3 := gBest.(*FIndividual) 42 | if !ok1 || !ok2 || !ok3 { 43 | return nil, fmt.Errorf("invalid individual type") 44 | } 45 | newIndiv := FIndividual{ 46 | v: make([]float64, len(fIndiv.v)), 47 | x: make([]float64, len(fIndiv.v)), 48 | } 49 | w := 0.9 50 | for d := range fIndiv.v { 51 | rp := rand.Float64() 52 | rg := rand.Float64() 53 | newIndiv.v[d] = w*fIndiv.v[d] + 54 | c1*rp*(fPBest.x[d]-fIndiv.x[d]) + 55 | c2*rg*(fGBest.x[d]-fIndiv.x[d]) 56 | 57 | newIndiv.x[d] = fIndiv.x[d] + newIndiv.v[d] 58 | } 59 | return &newIndiv, nil 60 | } 61 | 62 | type FEvaluater struct { 63 | } 64 | 65 | func (e *FEvaluater) Evaluate(indiv evoli.Individual) (Fitness float64, err error) { 66 | fIndiv, ok := indiv.(*FIndividual) 67 | if !ok { 68 | return 0, fmt.Errorf("invalid individual type") 69 | } 70 | return f(fIndiv.x[0], fIndiv.x[1]), nil 71 | } 72 | 73 | func ExampleNewSwarm() { 74 | 75 | pop := evoli.NewPopulation(50) 76 | for i := 0; i < pop.Cap(); i++ { 77 | x := rand.Float64()*20 - 10 78 | y := rand.Float64()*20 - 10 79 | vx := rand.Float64()*20 - 10 80 | vy := rand.Float64()*20 - 10 81 | pop.Add(&FIndividual{ 82 | x: []float64{x, y}, 83 | v: []float64{vx, vy}, 84 | }) 85 | } 86 | positioner := &FPositioner{} 87 | evaluator := &FEvaluater{} 88 | 89 | sw := evoli.NewSwarm(pop, positioner, .2, .2, evaluator) 90 | 91 | for i := 0; i < 100; i++ { 92 | err := sw.Next() 93 | if err != nil { 94 | panic(err) 95 | } 96 | } 97 | 98 | // evaluate the latest population 99 | for _, v := range sw.Population().Slice() { 100 | f, err := evaluator.Evaluate(v) 101 | if err != nil { 102 | panic(err) 103 | } 104 | v.SetFitness(f) 105 | } 106 | 107 | fmt.Printf("Max Value: %.2f\n", sw.Alpha().Fitness()) 108 | 109 | // Output: Max Value: 1.00 110 | } 111 | -------------------------------------------------------------------------------- /genetic.go: -------------------------------------------------------------------------------- 1 | package evoli 2 | 3 | import ( 4 | "errors" 5 | "math/rand" 6 | "sync" 7 | ) 8 | 9 | // genetic is a genetic algorithm implementation 10 | type genetic struct { 11 | evolution 12 | selecter Selecter 13 | SurvivorSize int 14 | crosser Crosser 15 | mutater Mutater 16 | MutationProbability float64 17 | } 18 | 19 | var ( 20 | // ErrSurvivorSize - survivorSize < 1 21 | ErrSurvivorSize = errors.New("ErrSurvivorSize - survivorSize must be >= 1") 22 | // ErrMutationProb - 0<= mutationProbability <= 1 23 | ErrMutationProb = errors.New("ErrMutationProb - mutation probability must be 0 <= mutationProbability <= 1") 24 | ) 25 | 26 | // NewGenetic - constructor for Genetic Algorithm 27 | func NewGenetic(pop Population, s Selecter, survivorSize int, c Crosser, m Mutater, mutationProbability float64, e Evaluater) Evolution { 28 | if survivorSize < 1 { 29 | panic(ErrSurvivorSize) 30 | } 31 | if mutationProbability < 0 || mutationProbability > 1 { 32 | panic(ErrMutationProb) 33 | } 34 | return &genetic{newEvolution(pop, e), s, survivorSize, c, m, mutationProbability} 35 | } 36 | 37 | // Next takes a population and produce a the new generation of this population 38 | func (g *genetic) Next() error { 39 | err := g.evaluation(g.pop) 40 | if err != nil { 41 | return err 42 | } 43 | survivors, deads, err := g.selecter.Select(g.pop, g.SurvivorSize) 44 | if err != nil { 45 | return err 46 | } 47 | if deads != nil { 48 | deads.Close() 49 | } 50 | offsprings, err := g.crossovers(survivors) 51 | if err != nil { 52 | return err 53 | } 54 | offsprings, err = g.mutations(offsprings) 55 | if err != nil { 56 | return err 57 | } 58 | survivors.Add(offsprings.Slice()...) 59 | g.pop = survivors 60 | return nil 61 | } 62 | 63 | func (g *genetic) evaluation(pop Population) error { 64 | var ( 65 | length = pop.Len() 66 | wg = sync.WaitGroup{} 67 | bubbledErr error 68 | ) 69 | for i := 0; i < length; i++ { 70 | wg.Add(1) 71 | go func(i int) { 72 | defer wg.Done() 73 | individual := pop.Get(i) 74 | fitness, err := g.evaluater.Evaluate(individual) 75 | if err != nil { 76 | bubbledErr = err 77 | return 78 | } 79 | individual.SetFitness(fitness) 80 | }(i) 81 | } 82 | wg.Wait() 83 | if bubbledErr != nil { 84 | return bubbledErr 85 | } 86 | return nil 87 | } 88 | 89 | func (g *genetic) crossovers(pop Population) (Population, error) { 90 | var ( 91 | capacity = pop.Cap() - pop.Len() 92 | offsprings = NewPopulation(capacity) 93 | mut sync.Mutex 94 | wg = sync.WaitGroup{} 95 | bubbledErr error 96 | ) 97 | for index := 0; index < capacity; index += 2 { 98 | wg.Add(1) 99 | go func(index int) { 100 | defer wg.Done() 101 | var i, j = rand.Intn(pop.Len()), rand.Intn(pop.Len()) 102 | if i == j { 103 | switch i { 104 | case pop.Len() - 1: 105 | j = i - 1 106 | default: 107 | j = i + 1 108 | } 109 | } 110 | indiv1, indiv2 := pop.Get(i), pop.Get(j) 111 | child1, child2, err := g.crosser.Cross(indiv1, indiv2) 112 | if err != nil { 113 | bubbledErr = err 114 | return 115 | } 116 | mut.Lock() 117 | offsprings.Add(child1) 118 | if index+1 < capacity { 119 | offsprings.Add(child2) 120 | } 121 | mut.Unlock() 122 | }(index) 123 | } 124 | wg.Wait() 125 | if bubbledErr != nil { 126 | return nil, bubbledErr 127 | } 128 | return offsprings, nil 129 | } 130 | 131 | func (g *genetic) mutations(pop Population) (Population, error) { 132 | var ( 133 | wg = sync.WaitGroup{} 134 | bubbledErr error 135 | ) 136 | for i := 0; i < pop.Len(); i++ { 137 | wg.Add(1) 138 | go func(i int) { 139 | defer wg.Done() 140 | indiv := pop.Get(i) 141 | mutant, err := g.mutater.Mutate(indiv, g.MutationProbability) 142 | if err != nil { 143 | bubbledErr = err 144 | return 145 | } 146 | pop.Replace(i, mutant) 147 | }(i) 148 | } 149 | wg.Wait() 150 | if bubbledErr != nil { 151 | return nil, bubbledErr 152 | } 153 | return pop, nil 154 | } 155 | 156 | type geneticSync struct { 157 | genetic 158 | sync.RWMutex 159 | } 160 | 161 | // NewGeneticSync - constructor for Genetic Algorithm (sync impl) 162 | func NewGeneticSync(pop Population, s Selecter, survivorSize int, c Crosser, m Mutater, mutationProbability float64, e Evaluater) Evolution { 163 | return &geneticSync{*NewGenetic(pop, s, survivorSize, c, m, mutationProbability, e).(*genetic), sync.RWMutex{}} 164 | } 165 | 166 | func (s *geneticSync) Next() error { 167 | s.Lock() 168 | defer s.Unlock() 169 | return s.genetic.Next() 170 | } 171 | 172 | func (s *geneticSync) Population() Population { 173 | s.RLock() 174 | defer s.RUnlock() 175 | return s.genetic.Population() 176 | } 177 | 178 | func (s *geneticSync) SetPopulation(pop Population) { 179 | s.Lock() 180 | defer s.Unlock() 181 | s.genetic.SetPopulation(pop) 182 | } 183 | 184 | func (s *geneticSync) Alpha() Individual { 185 | s.RLock() 186 | defer s.RUnlock() 187 | return s.genetic.Alpha() 188 | } 189 | -------------------------------------------------------------------------------- /genetic_test.go: -------------------------------------------------------------------------------- 1 | package evoli 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // To be completed 8 | func TestNewGenetic(t *testing.T) { 9 | cases := []struct { 10 | s Selecter 11 | survivorSize int 12 | c Crosser 13 | m Mutater 14 | mutaionProb float64 15 | e Evaluater 16 | }{ 17 | {NewTruncationSelecter(), 10, crosserMock{}, mutaterMock{}, 0.01, evaluaterMock{}}, 18 | } 19 | for _, c := range cases { 20 | i1 := NewIndividual(1) 21 | i2 := NewIndividual(2) 22 | popInit := NewPopulation(2) 23 | newPop := NewPopulation(1) 24 | popInit.Add(i1, i2) 25 | gen := NewGenetic(popInit, c.s, c.survivorSize, c.c, c.m, c.mutaionProb, c.e) 26 | pop := gen.Population() 27 | if pop != popInit { 28 | t.Errorf("expected %v got %v", popInit, pop) 29 | } 30 | alpha := gen.Alpha() 31 | if alpha != i2 { 32 | t.Errorf("expected %v got %v", i2, alpha) 33 | } 34 | gen.SetPopulation(newPop) 35 | pop = gen.Population() 36 | if pop != newPop { 37 | t.Errorf("expected %v got %v", newPop, pop) 38 | } 39 | gen = NewGeneticSync(popInit, c.s, c.survivorSize, c.c, c.m, c.mutaionProb, c.e) 40 | pop = gen.Population() 41 | if pop != popInit { 42 | t.Errorf("expected %v got %v", popInit, pop) 43 | } 44 | alpha = gen.Alpha() 45 | if alpha != i2 { 46 | t.Errorf("expected %v got %v", i2, alpha) 47 | } 48 | gen.SetPopulation(newPop) 49 | pop = gen.Population() 50 | if pop != newPop { 51 | t.Errorf("expected %v got %v", newPop, pop) 52 | } 53 | } 54 | } 55 | 56 | // To be completed 57 | func TestGeneticNext(t *testing.T) { 58 | i1, i2, i3, i4, i5, i6 := NewIndividual(1), NewIndividual(-2), NewIndividual(3), NewIndividual(4), NewIndividual(5), NewIndividual(6) 59 | pop1 := population{i1, i2, i3, i4, i5, i6} 60 | pop2 := population{i1, i2, i3, i4, i5, i6} 61 | cases := []struct { 62 | genetic Evolution 63 | }{ 64 | {NewGenetic(&pop1, NewTruncationSelecter(), 5, crosserMock{}, mutaterMock{}, 1, evaluaterMock{})}, 65 | {NewGeneticSync(&pop2, NewTruncationSelecter(), 5, crosserMock{}, mutaterMock{}, 1, evaluaterMock{})}, 66 | } 67 | for _, c := range cases { 68 | err := c.genetic.Next() 69 | if err != nil { 70 | t.Error(err) 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/khezen/evoli 2 | 3 | go 1.14 4 | -------------------------------------------------------------------------------- /individual.go: -------------------------------------------------------------------------------- 1 | package evoli 2 | 3 | import "sync" 4 | 5 | // Individual refers to a potential solution to the problem we want to solve 6 | type Individual interface { 7 | Fitness() float64 8 | SetFitness(float64) 9 | Equal(Individual) bool 10 | } 11 | 12 | // individual https://en.wikipedia.org/wiki/individual 13 | type individual struct { 14 | fitness float64 15 | best Individual 16 | } 17 | 18 | // NewIndividual is the constructor for individuals 19 | func NewIndividual(fitness float64) Individual { 20 | indiv := individual{ 21 | fitness, 22 | nil, 23 | } 24 | return &indiv 25 | } 26 | 27 | // Fitness returns the strength of a individual regarding to its environement. Higher is stronger. 28 | func (indiv *individual) Fitness() float64 { 29 | return indiv.fitness 30 | } 31 | 32 | // SetFitness set the strength of a individual regarding to its environement. Higher is stronger. 33 | func (indiv *individual) SetFitness(fitness float64) { 34 | indiv.fitness = fitness 35 | } 36 | 37 | // Equal return true if indiv is equal to toBeCompared 38 | func (indiv *individual) Equal(toBeCompared Individual) bool { 39 | return indiv == toBeCompared 40 | } 41 | 42 | // individual https://en.wikipedia.org/wiki/individual 43 | type individualSync struct { 44 | individual 45 | fitMut sync.RWMutex 46 | bestMut sync.RWMutex 47 | } 48 | 49 | // NewIndividualSync is the constructor for threadsafe individuals 50 | func NewIndividualSync(fitness float64) Individual { 51 | indiv := individual{ 52 | fitness, 53 | nil, 54 | } 55 | return &individualSync{ 56 | indiv, 57 | sync.RWMutex{}, 58 | sync.RWMutex{}, 59 | } 60 | } 61 | 62 | // Fitness returns the strength of a individual regarding to its environement. Higher is stronger. 63 | func (indiv *individualSync) Fitness() float64 { 64 | indiv.fitMut.RLock() 65 | defer indiv.fitMut.RUnlock() 66 | return indiv.individual.Fitness() 67 | } 68 | 69 | // SetFitness set the strength of a individual regarding to its environement. Higher is stronger. 70 | func (indiv *individualSync) SetFitness(Fitness float64) { 71 | indiv.fitMut.Lock() 72 | defer indiv.fitMut.Unlock() 73 | indiv.individual.SetFitness(Fitness) 74 | } 75 | -------------------------------------------------------------------------------- /individual_test.go: -------------------------------------------------------------------------------- 1 | package evoli 2 | 3 | import "testing" 4 | 5 | func TestNewIndividual(t *testing.T) { 6 | cases := []struct { 7 | indiv Individual 8 | expected float64 9 | }{ 10 | {NewIndividual(0.7), 0.7}, 11 | {NewIndividual(54.0), 54.0}, 12 | {NewIndividual(0), 0}, 13 | {NewIndividualSync(0.7), 0.7}, 14 | {NewIndividualSync(54.0), 54.0}, 15 | {NewIndividualSync(0), 0}, 16 | } 17 | for _, c := range cases { 18 | got := c.indiv.Fitness() 19 | if got != c.expected { 20 | t.Errorf("expected %f got %f", c.expected, got) 21 | } 22 | } 23 | } 24 | 25 | func TestFitness(t *testing.T) { 26 | cases := []struct { 27 | indiv Individual 28 | in, expected float64 29 | }{ 30 | {NewIndividual(0), 0.7, 0.7}, 31 | {NewIndividual(0), 54.0, 54.0}, 32 | {NewIndividual(10), 0, 0}, 33 | {NewIndividualSync(0), 0.7, 0.7}, 34 | {NewIndividualSync(0), 54.0, 54.0}, 35 | {NewIndividualSync(10), 0, 0}, 36 | } 37 | for _, c := range cases { 38 | c.indiv.SetFitness(c.in) 39 | got := c.indiv.Fitness() 40 | if got != c.expected { 41 | t.Errorf("indiv.SetFitness(%f); indiv.Fitness() == %f, expected %f", c.in, got, c.expected) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /mock_test.go: -------------------------------------------------------------------------------- 1 | package evoli 2 | 3 | import "math/rand" 4 | 5 | type crosserMock struct { 6 | } 7 | 8 | func (c crosserMock) Cross(parent1, parent2 Individual) (child1, child2 Individual, err error) { 9 | w := 0.1 + 0.8*rand.Float64() 10 | return NewIndividual(w*parent1.Fitness() + (1-w)*parent2.Fitness()), 11 | NewIndividual((1-w)*parent1.Fitness() + w*parent2.Fitness()), 12 | nil 13 | } 14 | 15 | type evaluaterMock struct { 16 | } 17 | 18 | func (e evaluaterMock) Evaluate(individual Individual) (Fitness float64, err error) { 19 | return individual.Fitness(), nil 20 | } 21 | 22 | type mutaterMock struct { 23 | } 24 | 25 | func (m mutaterMock) Mutate(individual Individual, p float64) (Individual, error) { 26 | return individual, nil 27 | } 28 | 29 | type positionerMock struct { 30 | } 31 | 32 | func (p positionerMock) Position(indiv, pBest, gBest Individual, c1, c2 float64) (Individual, error) { 33 | return NewIndividual((indiv.Fitness() + pBest.Fitness() + gBest.Fitness()) / 3), nil 34 | } 35 | -------------------------------------------------------------------------------- /mutater.go: -------------------------------------------------------------------------------- 1 | package evoli 2 | 3 | // Mutater randomly modify a individual. This operator maintain diversity in a population. 4 | type Mutater interface { 5 | Mutate(indiv Individual, p float64) (Individual, error) 6 | } 7 | -------------------------------------------------------------------------------- /pool.go: -------------------------------------------------------------------------------- 1 | package evoli 2 | 3 | import ( 4 | "math/rand" 5 | "sync" 6 | ) 7 | 8 | var ( 9 | // ErrPoolEvaluater - all evolutions of a pool must share the same evaluater operator 10 | ErrPoolEvaluater = "ErrPoolEvaluater - all evolution of a pool must share the same evaluater operator" 11 | ) 12 | 13 | // Pool - solve one problem with different algorithms(with different pros/cons) 14 | // It also make sense to have a pool running multiple instances of the same algorithm asynchronously with smaller populations. 15 | type Pool interface { 16 | Add(Evolution) 17 | Delete(Evolution) 18 | Has(Evolution) bool 19 | Alpha() Individual 20 | Individuals() []Individual 21 | Populations() []Population 22 | Evolutions() []Evolution 23 | Shuffle() 24 | Next() error 25 | NextAsync() error 26 | } 27 | 28 | type pool struct { 29 | evaluater Evaluater 30 | evolutions []Evolution 31 | } 32 | 33 | // NewPool - creates a Pool 34 | func NewPool(length int) Pool { 35 | return &pool{nil, make([]Evolution, 0, length)} 36 | } 37 | 38 | func (p *pool) Add(e Evolution) { 39 | switch p.evaluater { 40 | case nil: 41 | p.evaluater = e.Evaluater() 42 | case e.Evaluater(): 43 | break 44 | default: 45 | panic(ErrPoolEvaluater) 46 | } 47 | p.evolutions = append(p.evolutions, e) 48 | } 49 | 50 | func (p *pool) Delete(e Evolution) { 51 | length := len(p.evolutions) 52 | for i := range p.evolutions { 53 | if p.evolutions[i] == e { 54 | p.evolutions[i] = p.evolutions[length-1] 55 | p.evolutions[length-1] = nil 56 | p.evolutions = p.evolutions[:length-1] 57 | break 58 | } 59 | } 60 | if len(p.evolutions) == 0 { 61 | p.evaluater = nil 62 | } 63 | } 64 | 65 | func (p *pool) Has(e Evolution) bool { 66 | for i := range p.evolutions { 67 | if p.evolutions[i] == e { 68 | return true 69 | } 70 | } 71 | return false 72 | } 73 | 74 | func (p *pool) Evolutions() []Evolution { 75 | return p.evolutions 76 | } 77 | 78 | func (p *pool) Populations() []Population { 79 | populations := make([]Population, 0, len(p.evolutions)) 80 | for _, e := range p.evolutions { 81 | populations = append(populations, e.Population()) 82 | } 83 | return populations 84 | } 85 | 86 | func (p *pool) Individuals() []Individual { 87 | individualsLen := 0 88 | for _, e := range p.evolutions { 89 | individualsLen += e.Population().Len() 90 | } 91 | individuals := make([]Individual, 0, individualsLen) 92 | for _, e := range p.evolutions { 93 | individuals = append(individuals, e.Population().Slice()...) 94 | } 95 | return individuals 96 | } 97 | 98 | func (p *pool) Alpha() Individual { 99 | var alpha Individual 100 | for _, e := range p.evolutions { 101 | outsider := e.Alpha() 102 | if alpha == nil || alpha.Fitness() < outsider.Fitness() { 103 | alpha = outsider 104 | } 105 | } 106 | return alpha 107 | } 108 | 109 | func (p *pool) Shuffle() { 110 | tmpPopulations := make([]Population, 0, len(p.evolutions)) 111 | nextPopulations := make([]Population, 0, len(p.evolutions)) 112 | for _, e := range p.evolutions { 113 | pop := e.Population() 114 | capacity := pop.Cap() 115 | newPop := pop.New(capacity) 116 | tmpPopulations = append(tmpPopulations, newPop) 117 | } 118 | individuals := p.Individuals() 119 | for _, indiv := range individuals { 120 | tmpPopulationsLen := len(tmpPopulations) 121 | i := rand.Intn(tmpPopulationsLen) 122 | tmpPopulations[i].Add(indiv) 123 | if tmpPopulations[i].Len() == tmpPopulations[i].Cap() { 124 | nextPopulations = append(nextPopulations, tmpPopulations[i]) 125 | tmpPopulations[i] = tmpPopulations[tmpPopulationsLen-1] 126 | tmpPopulations[tmpPopulationsLen-1] = nil 127 | tmpPopulations = tmpPopulations[:tmpPopulationsLen-1] 128 | } 129 | } 130 | for i := range p.evolutions { 131 | p.evolutions[i].SetPopulation(nextPopulations[i]) 132 | } 133 | } 134 | 135 | func (p *pool) Next() error { 136 | for _, e := range p.evolutions { 137 | err := e.Next() 138 | if err != nil { 139 | return err 140 | } 141 | } 142 | return nil 143 | } 144 | 145 | func (p *pool) NextAsync() error { 146 | evolutionsLen := len(p.evolutions) 147 | wg := sync.WaitGroup{} 148 | wg.Add(evolutionsLen) 149 | var bubbledErr error 150 | for _, e := range p.evolutions { 151 | go func(e Evolution) { 152 | err := e.Next() 153 | if err != nil { 154 | bubbledErr = err 155 | } 156 | wg.Done() 157 | }(e) 158 | } 159 | wg.Wait() 160 | return bubbledErr 161 | } 162 | -------------------------------------------------------------------------------- /poolSync.go: -------------------------------------------------------------------------------- 1 | package evoli 2 | 3 | import "sync" 4 | 5 | type poolSync struct { 6 | pool 7 | sync.RWMutex 8 | } 9 | 10 | // NewPoolSync creates a sync implementation of Pool 11 | func NewPoolSync(length int) Pool { 12 | return &poolSync{ 13 | *NewPool(length).(*pool), 14 | sync.RWMutex{}, 15 | } 16 | } 17 | 18 | func (p *poolSync) Add(e Evolution) { 19 | p.Lock() 20 | defer p.Unlock() 21 | p.pool.Add(e) 22 | } 23 | 24 | func (p *poolSync) Delete(e Evolution) { 25 | p.Lock() 26 | defer p.Unlock() 27 | p.pool.Delete(e) 28 | } 29 | 30 | func (p *poolSync) Has(e Evolution) bool { 31 | p.RLock() 32 | defer p.RUnlock() 33 | return p.pool.Has(e) 34 | } 35 | 36 | func (p *poolSync) Evolutions() []Evolution { 37 | p.RLock() 38 | defer p.RUnlock() 39 | return p.pool.Evolutions() 40 | } 41 | 42 | func (p *poolSync) Populations() []Population { 43 | p.RLock() 44 | defer p.RUnlock() 45 | return p.pool.Populations() 46 | } 47 | 48 | func (p *poolSync) Individuals() []Individual { 49 | p.RLock() 50 | defer p.RUnlock() 51 | return p.pool.Individuals() 52 | } 53 | 54 | func (p *poolSync) Alpha() Individual { 55 | p.RLock() 56 | defer p.RUnlock() 57 | return p.pool.Alpha() 58 | } 59 | 60 | func (p *poolSync) Shuffle() { 61 | p.Lock() 62 | defer p.Unlock() 63 | p.pool.Shuffle() 64 | } 65 | 66 | func (p *poolSync) Next() error { 67 | p.Lock() 68 | defer p.Unlock() 69 | return p.pool.Next() 70 | } 71 | 72 | func (p *poolSync) NextAsync() error { 73 | p.Lock() 74 | defer p.Unlock() 75 | return p.pool.NextAsync() 76 | } 77 | -------------------------------------------------------------------------------- /pool_test.go: -------------------------------------------------------------------------------- 1 | package evoli 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | ) 7 | 8 | func TestPoolCRUD(t *testing.T) { 9 | gen := NewGenetic(NewPopulationSync(10), NewTruncationSelecter(), 5, crosserMock{}, mutaterMock{}, 1, evaluaterMock{}) 10 | cases := []struct { 11 | pool Pool 12 | e Evolution 13 | }{ 14 | {NewPool(1), gen}, 15 | {NewPoolSync(1), gen}, 16 | } 17 | for _, c := range cases { 18 | c.pool.Add(c.e) 19 | has := c.pool.Has(c.e) 20 | if !has { 21 | t.Error("expected true, got false") 22 | } 23 | c.pool.Delete(c.e) 24 | has = c.pool.Has(c.e) 25 | if has { 26 | t.Errorf("expected false, got true") 27 | } 28 | } 29 | } 30 | 31 | func TestAlpha(t *testing.T) { 32 | i1, i2, i3, i4, i5, i6 := NewIndividual(0.2), NewIndividual(0.7), NewIndividual(1), NewIndividual(0), NewIndividual(100), NewIndividual(42) 33 | pop1, pop2 := &population{i1, i2, i3}, &population{i4, i5, i6} 34 | gen1, gen2 := NewGenetic(pop1, NewTruncationSelecter(), 5, crosserMock{}, mutaterMock{}, 1, evaluaterMock{}), NewGenetic(pop2, NewTruncationSelecter(), 5, crosserMock{}, mutaterMock{}, 1, evaluaterMock{}) 35 | cases := []struct { 36 | pool Pool 37 | populations []Evolution 38 | alpha Individual 39 | }{ 40 | {NewPool(2), []Evolution{gen1, gen2}, i5}, 41 | {NewPoolSync(2), []Evolution{gen1, gen2}, i5}, 42 | } 43 | for _, c := range cases { 44 | for _, evolution := range c.populations { 45 | c.pool.Add(evolution) 46 | } 47 | alpha := c.pool.Alpha() 48 | if alpha != c.alpha { 49 | t.Errorf("expected %v got %v", c.alpha, alpha) 50 | } 51 | } 52 | } 53 | 54 | func TestCollections(t *testing.T) { 55 | i1, i2, i3, i4, i5, i6 := NewIndividual(0.2), NewIndividual(0.7), NewIndividual(1), NewIndividual(0), NewIndividual(100), NewIndividual(42) 56 | pop1, pop2 := &population{i1, i2, i3}, &population{i4, i5, i6} 57 | gen1, gen2 := NewGenetic(pop1, NewTruncationSelecter(), 5, crosserMock{}, mutaterMock{}, 1, evaluaterMock{}), NewGenetic(pop2, NewTruncationSelecter(), 5, crosserMock{}, mutaterMock{}, 1, evaluaterMock{}) 58 | cases := []struct { 59 | pool Pool 60 | evolutions []Evolution 61 | }{ 62 | {NewPool(2), []Evolution{gen1, gen2}}, 63 | {NewPoolSync(2), []Evolution{gen1, gen2}}, 64 | } 65 | for _, c := range cases { 66 | for _, evolution := range c.evolutions { 67 | c.pool.Add(evolution) 68 | } 69 | evolutions := c.pool.Evolutions() 70 | if len(evolutions) != len(c.evolutions) { 71 | t.Errorf("expected %v, got %v", len(c.evolutions), len(evolutions)) 72 | } 73 | for _, e := range evolutions { 74 | found := false 75 | for _, ce := range c.evolutions { 76 | if e == ce { 77 | found = true 78 | break 79 | } 80 | } 81 | if !found { 82 | t.Error("unexpected result") 83 | } 84 | } 85 | 86 | populations := c.pool.Populations() 87 | if len(populations) != len(c.evolutions) { 88 | t.Errorf("expected %v, got %v", len(c.evolutions), len(populations)) 89 | } 90 | for _, pop := range populations { 91 | found := false 92 | for _, e := range c.evolutions { 93 | if e.Population() == pop { 94 | found = true 95 | break 96 | } 97 | } 98 | if !found { 99 | t.Error("unexpected result") 100 | } 101 | } 102 | individuals := c.pool.Individuals() 103 | for _, indiv := range individuals { 104 | has := false 105 | for _, e := range c.evolutions { 106 | pop := e.Population() 107 | has = pop.Has(indiv) 108 | if has { 109 | break 110 | } 111 | } 112 | if !has { 113 | t.Error("indiv not found") 114 | } 115 | } 116 | } 117 | } 118 | 119 | func TestShuffle(t *testing.T) { 120 | i1, i2, i3, i4, i5, i6 := NewIndividual(0.2), NewIndividual(0.7), NewIndividual(1), NewIndividual(0), NewIndividual(100), NewIndividual(42) 121 | i7, i8, i9, i10, i11, i12 := NewIndividual(0.2), NewIndividual(0.7), NewIndividual(1), NewIndividual(0), NewIndividual(100), NewIndividual(42) 122 | pop1, pop2 := &population{i1, i2, i3, i4, i5, i6}, &population{i7, i8, i9, i10, i11, i12} 123 | cases := []struct { 124 | pool Pool 125 | populations []Population 126 | }{ 127 | {NewPool(2), []Population{pop1, pop2}}, 128 | {NewPoolSync(2), []Population{pop1, pop2}}, 129 | } 130 | for _, c := range cases { 131 | for _, cpop := range c.populations { 132 | pop := cpop.New(cpop.Cap()) 133 | pop.Add(cpop.Slice()...) 134 | gen := NewGenetic(pop, NewTruncationSelecter(), 5, crosserMock{}, mutaterMock{}, 1, evaluaterMock{}) 135 | c.pool.Add(gen) 136 | } 137 | c.pool.Shuffle() 138 | populations := c.pool.Populations() 139 | for _, pop := range populations { 140 | if pop.Len() != 6 { 141 | t.Errorf("expected %v got %v", 6, pop.Len()) 142 | } 143 | individuals := pop.Slice() 144 | for _, cpop := range c.populations { 145 | different := false 146 | cindividuals := cpop.Slice() 147 | for _, indiv := range individuals { 148 | has := false 149 | for _, cindiv := range cindividuals { 150 | if indiv == cindiv { 151 | has = true 152 | break 153 | } 154 | } 155 | if !has { 156 | different = true 157 | break 158 | } 159 | } 160 | if !different { 161 | t.Error("shuffle produced the same populations") 162 | } 163 | } 164 | } 165 | } 166 | } 167 | 168 | func TestPoolNext(t *testing.T) { 169 | i1, i2, i3, i4, i5, i6 := NewIndividualSync(0.2), NewIndividualSync(0.7), NewIndividualSync(1), NewIndividualSync(0), NewIndividualSync(100), NewIndividualSync(42) 170 | i7, i8, i9, i10, i11, i12 := NewIndividualSync(0.2), NewIndividualSync(0.7), NewIndividualSync(1), NewIndividualSync(0), NewIndividualSync(100), NewIndividualSync(42) 171 | pop1, pop2 := &populationSync{population{i1, i2, i3, i4, i5, i6}, sync.RWMutex{}}, &populationSync{population{i7, i8, i9, i10, i11, i12}, sync.RWMutex{}} 172 | cases := []struct { 173 | pool Pool 174 | populations []Population 175 | }{ 176 | {NewPool(2), []Population{pop1, pop2}}, 177 | {NewPoolSync(2), []Population{pop1, pop2}}, 178 | } 179 | for _, c := range cases { 180 | for _, cpop := range c.populations { 181 | pop := cpop.New(cpop.Cap()) 182 | pop.Add(cpop.Slice()...) 183 | gen := NewGenetic(pop, NewTruncationSelecter(), 2, crosserMock{}, mutaterMock{}, 0.05, evaluaterMock{}) 184 | c.pool.Add(gen) 185 | } 186 | err := c.pool.Next() 187 | if err != nil { 188 | t.Error(err) 189 | } 190 | populations := c.pool.Populations() 191 | for _, pop := range populations { 192 | if pop.Len() != 6 { 193 | t.Errorf("expected %v got %v", 6, pop.Len()) 194 | } 195 | individuals := pop.Slice() 196 | for _, cpop := range c.populations { 197 | different := false 198 | cindividuals := cpop.Slice() 199 | for _, indiv := range individuals { 200 | has := false 201 | for _, cindiv := range cindividuals { 202 | if indiv == cindiv { 203 | has = true 204 | break 205 | } 206 | } 207 | if !has { 208 | different = true 209 | break 210 | } 211 | } 212 | if !different { 213 | t.Error("Next produced the same populations") 214 | } 215 | } 216 | } 217 | } 218 | } 219 | 220 | func TestPoolNextAsync(t *testing.T) { 221 | i1, i2, i3, i4, i5, i6 := NewIndividualSync(0.2), NewIndividualSync(0.7), NewIndividualSync(1), NewIndividualSync(0), NewIndividualSync(100), NewIndividualSync(42) 222 | i7, i8, i9, i10, i11, i12 := NewIndividualSync(0.2), NewIndividualSync(0.7), NewIndividualSync(1), NewIndividualSync(0), NewIndividualSync(100), NewIndividualSync(42) 223 | pop1, pop2 := &populationSync{population{i1, i2, i3, i4, i5, i6}, sync.RWMutex{}}, &populationSync{population{i7, i8, i9, i10, i11, i12}, sync.RWMutex{}} 224 | cases := []struct { 225 | pool Pool 226 | populations []Population 227 | }{ 228 | {NewPool(2), []Population{pop1, pop2}}, 229 | {NewPoolSync(2), []Population{pop1, pop2}}, 230 | } 231 | for _, c := range cases { 232 | for _, cpop := range c.populations { 233 | pop := cpop.New(cpop.Cap()) 234 | pop.Add(cpop.Slice()...) 235 | gen := NewGenetic(pop, NewTruncationSelecter(), 2, crosserMock{}, mutaterMock{}, 0.05, evaluaterMock{}) 236 | c.pool.Add(gen) 237 | } 238 | err := c.pool.NextAsync() 239 | if err != nil { 240 | t.Error(err) 241 | } 242 | populations := c.pool.Populations() 243 | for _, pop := range populations { 244 | if pop.Len() != 6 { 245 | t.Errorf("expected %v got %v", 6, pop.Len()) 246 | } 247 | individuals := pop.Slice() 248 | for _, cpop := range c.populations { 249 | different := false 250 | cindividuals := cpop.Slice() 251 | for _, indiv := range individuals { 252 | has := false 253 | for _, cindiv := range cindividuals { 254 | if indiv == cindiv { 255 | has = true 256 | break 257 | } 258 | } 259 | if !has { 260 | different = true 261 | break 262 | } 263 | } 264 | if !different { 265 | t.Error("Next produced the same populations") 266 | } 267 | } 268 | 269 | } 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /population.go: -------------------------------------------------------------------------------- 1 | package evoli 2 | 3 | import ( 4 | "errors" 5 | "sort" 6 | ) 7 | 8 | // Population contains the current set of solutions seen as Individual 9 | type Population interface { 10 | sort.Interface 11 | Sort() 12 | Cap() int 13 | SetCap(int) 14 | Add(...Individual) 15 | Get(int) Individual 16 | RemoveAt(int) 17 | Remove(...Individual) 18 | Replace(int, Individual) 19 | Min() Individual 20 | Max() Individual 21 | Has(...Individual) bool 22 | IndexOf(Individual) (int, error) 23 | Each(func(item Individual) bool) 24 | Slice() []Individual 25 | New(cap int) Population 26 | Close() 27 | } 28 | 29 | var ( 30 | // ErrCapacity - 31 | ErrCapacity = errors.New("ErrCapacity - capacity must be >= 1") 32 | // ErrIndexOutOfBounds - 33 | ErrIndexOutOfBounds = errors.New("ErrIndexOutOfBounds") 34 | // ErrNotFound - 35 | ErrNotFound = errors.New("ErrNotFound") 36 | ) 37 | 38 | // population is a set of individuals in population genetics. 39 | type population []Individual 40 | 41 | // NewPopulation is population constructor 42 | func NewPopulation(capacity int) Population { 43 | if capacity < 1 { 44 | panic(ErrCapacity) 45 | } 46 | pop := population(make([]Individual, 0, capacity)) 47 | return &pop 48 | } 49 | 50 | // Len returns the current livings count of a population 51 | func (pop *population) Len() int { 52 | return len(*pop) 53 | } 54 | 55 | // Less reports whether the element with 56 | // index i should sort before the element with index j. 57 | func (pop *population) Less(i, j int) bool { 58 | indivi := pop.Get(i) 59 | indivj := pop.Get(j) 60 | return indivi.Fitness() >= indivj.Fitness() 61 | } 62 | 63 | // Swap swaps the elements with indexes i and j. 64 | func (pop *population) Swap(i, j int) { 65 | if i != j && i >= 0 && j >= 0 && i < pop.Len() && j < pop.Len() { 66 | tmp := (*pop)[i] 67 | (*pop)[i] = (*pop)[j] 68 | (*pop)[j] = tmp 69 | } 70 | } 71 | 72 | // Sort sort the population 73 | func (pop *population) Sort() { 74 | sort.Sort(pop) 75 | } 76 | 77 | // Cap returns the population capacity 78 | func (pop *population) Cap() int { 79 | return cap(*pop) 80 | } 81 | 82 | // SetCap set the resize the population capacity 83 | func (pop *population) SetCap(newCap int) { 84 | if newCap < 0 { 85 | panic(ErrCapacity) 86 | } 87 | currentCap := pop.Cap() 88 | if newCap != currentCap { 89 | tmp := *pop 90 | switch { 91 | case newCap < currentCap: 92 | tmp = (*pop)[0:newCap] 93 | *pop = make([]Individual, newCap) 94 | case newCap > currentCap: 95 | *pop = make([]Individual, pop.Len(), newCap) 96 | } 97 | copy(tmp, *pop) 98 | } 99 | } 100 | 101 | // Add adds an individual to a population. If the populagtion has already reached its capacity, capacity is incremented. 102 | func (pop *population) Add(indiv ...Individual) { 103 | *pop = append(*pop, indiv...) 104 | } 105 | 106 | // Get returns the individual at index i 107 | func (pop *population) Get(i int) Individual { 108 | err := pop.checkIndex(i) 109 | if err != nil { 110 | panic(err) 111 | } 112 | return (*pop)[i] 113 | } 114 | 115 | // RemoveAt removesthe individual at index i without preserving order 116 | func (pop *population) RemoveAt(i int) { 117 | err := pop.checkIndex(i) 118 | if err != nil { 119 | panic(err) 120 | } 121 | popLen := pop.Len() 122 | (*pop)[i] = (*pop)[popLen-1] 123 | (*pop)[popLen-1] = nil 124 | *pop = (*pop)[:popLen-1] 125 | } 126 | 127 | // Remove removes all given individuals without preserving order 128 | func (pop *population) Remove(individuals ...Individual) { 129 | for _, indiv := range individuals { 130 | i, err := pop.IndexOf(indiv) 131 | if err == nil { 132 | pop.RemoveAt(i) 133 | } 134 | } 135 | } 136 | 137 | // Replace replaces and returns the individual at index i by the substitute 138 | func (pop *population) Replace(i int, substitute Individual) { 139 | err := pop.checkIndex(i) 140 | if err != nil { 141 | panic(err) 142 | } 143 | (*pop)[i] = substitute 144 | } 145 | 146 | // Min returns the least Resilent individual 147 | func (pop *population) Min() Individual { 148 | return pop.extremum(false) 149 | } 150 | 151 | // Max returns the most Resilent individual 152 | func (pop *population) Max() Individual { 153 | return pop.extremum(true) 154 | } 155 | 156 | func (pop *population) extremum(greaterThan bool) Individual { 157 | extremum := pop.Get(0) 158 | length := pop.Len() 159 | for i := 1; i < length; i++ { 160 | indiv := pop.Get(i) 161 | if (greaterThan && indiv.Fitness() > extremum.Fitness()) || (!greaterThan && indiv.Fitness() < extremum.Fitness()) { 162 | extremum = indiv 163 | } 164 | } 165 | return extremum 166 | } 167 | 168 | // Has return true if the specified individual is in the population 169 | func (pop *population) Has(individuals ...Individual) bool { 170 | has := true 171 | for _, indiv := range individuals { 172 | _, err := pop.IndexOf(indiv) 173 | has = has && err == nil 174 | } 175 | return has 176 | } 177 | 178 | // IndexOf returns the inde of the specified individual if it exists 179 | func (pop *population) IndexOf(indiv Individual) (int, error) { 180 | for i, current := range *pop { 181 | if current.Equal(indiv) { 182 | return i, nil 183 | } 184 | } 185 | return -1, ErrNotFound 186 | } 187 | 188 | // Each traverse the population and execute given callback on each individual. Stops if the callbak return false. 189 | func (pop *population) Each(f func(indiv Individual) bool) { 190 | for _, individual := range *pop { 191 | resume := f(individual) 192 | if !resume { 193 | break 194 | } 195 | } 196 | } 197 | 198 | // Slice returns the population as []Individual 199 | func (pop *population) Slice() []Individual { 200 | return *pop 201 | } 202 | 203 | func (pop *population) New(cap int) Population { 204 | return NewPopulation(cap) 205 | } 206 | 207 | func (pop *population) checkIndex(i int) error { 208 | if i < 0 || i >= pop.Len() { 209 | return ErrIndexOutOfBounds 210 | } 211 | return nil 212 | } 213 | 214 | func (pop *population) Close() { 215 | *pop = (*pop)[:0] 216 | *pop = nil 217 | } 218 | -------------------------------------------------------------------------------- /populationSync.go: -------------------------------------------------------------------------------- 1 | package evoli 2 | 3 | import "sync" 4 | 5 | type populationSync struct { 6 | population 7 | sync.RWMutex 8 | } 9 | 10 | // NewPopulationSync creates a threadsafe population 11 | func NewPopulationSync(capacity int) Population { 12 | pop := NewPopulation(capacity) 13 | return &populationSync{ 14 | *pop.(*population), 15 | sync.RWMutex{}, 16 | } 17 | } 18 | 19 | // Len returns the current livings count of a population 20 | func (p *populationSync) Len() int { 21 | p.RLock() 22 | defer p.RUnlock() 23 | return p.population.Len() 24 | } 25 | 26 | // Less reports whether the element with 27 | // index i should sort before the element with index j. 28 | func (p *populationSync) Less(i, j int) bool { 29 | p.RLock() 30 | defer p.RUnlock() 31 | return p.population.Less(i, j) 32 | } 33 | 34 | // Swap swaps the elements with indexes i and j. 35 | func (p *populationSync) Swap(i, j int) { 36 | p.Lock() 37 | defer p.Unlock() 38 | p.population.Swap(i, j) 39 | } 40 | 41 | // Sort sort the population 42 | func (p *populationSync) Sort() { 43 | p.Lock() 44 | defer p.Unlock() 45 | p.population.Sort() 46 | } 47 | 48 | // SetCap set the resize the population capacity 49 | func (p *populationSync) SetCap(newCap int) { 50 | p.Lock() 51 | defer p.Unlock() 52 | p.population.SetCap(newCap) 53 | } 54 | 55 | // Add adds an individual to a population. If the populagtion has already reached its capacity, capacity is incremented. 56 | func (p *populationSync) Add(indiv ...Individual) { 57 | p.Lock() 58 | defer p.Unlock() 59 | p.population.Add(indiv...) 60 | } 61 | 62 | // Get returns the individual at index i 63 | func (p *populationSync) Get(i int) Individual { 64 | p.RLock() 65 | defer p.RUnlock() 66 | return p.population.Get(i) 67 | } 68 | 69 | // RemoveAt removes and returns the individual at index i 70 | func (p *populationSync) RemoveAt(i int) { 71 | p.Lock() 72 | defer p.Unlock() 73 | p.population.RemoveAt(i) 74 | } 75 | 76 | // Remove removes all given individuals 77 | func (p *populationSync) Remove(individuals ...Individual) { 78 | p.Lock() 79 | defer p.Unlock() 80 | p.population.Remove(individuals...) 81 | } 82 | 83 | // Replace replaces and returns the individual at index i by the substitute 84 | func (p *populationSync) Replace(i int, substitute Individual) { 85 | p.Lock() 86 | defer p.Unlock() 87 | p.population.Replace(i, substitute) 88 | } 89 | 90 | // Min returns the least Resilent individual 91 | func (p *populationSync) Min() Individual { 92 | p.RLock() 93 | defer p.RUnlock() 94 | return p.population.Min() 95 | } 96 | 97 | // Max returns the most Resilent individual 98 | func (p *populationSync) Max() Individual { 99 | p.RLock() 100 | defer p.RUnlock() 101 | return p.population.Max() 102 | } 103 | 104 | // Has return true if the specified individual is in the population 105 | func (p *populationSync) Has(individuals ...Individual) bool { 106 | has := true 107 | for _, indiv := range individuals { 108 | _, err := p.IndexOf(indiv) 109 | has = has && err == nil 110 | } 111 | return has 112 | } 113 | 114 | // IndexOf returns the inde of the specified individual if it exists 115 | func (p *populationSync) IndexOf(indiv Individual) (int, error) { 116 | p.RLock() 117 | defer p.RUnlock() 118 | return p.population.IndexOf(indiv) 119 | } 120 | 121 | // Each traverse the population and execute given callback on each individual. Stops if the callbak return false. 122 | func (p *populationSync) Each(f func(indiv Individual) bool) { 123 | p.RLock() 124 | defer p.RUnlock() 125 | p.population.Each(f) 126 | } 127 | 128 | // Slice returns the population as []Individual 129 | func (p *populationSync) Slice() []Individual { 130 | p.RLock() 131 | defer p.RUnlock() 132 | return p.population.Slice() 133 | } 134 | 135 | func (p *populationSync) New(cap int) Population { 136 | return NewPopulationSync(cap) 137 | } 138 | 139 | func (p *populationSync) Close() { 140 | p.Lock() 141 | defer p.Unlock() 142 | p.population.Close() 143 | } 144 | -------------------------------------------------------------------------------- /population_test.go: -------------------------------------------------------------------------------- 1 | package evoli 2 | 3 | import ( 4 | "sync" 5 | "testing" 6 | ) 7 | 8 | func TestNewPopulation(t *testing.T) { 9 | cases := []struct { 10 | in, expected int 11 | }{ 12 | {1, 1}, 13 | {7, 7}, 14 | } 15 | for _, c := range cases { 16 | var got Population 17 | got = NewPopulation(c.in) 18 | if got.Cap() != c.expected { 19 | t.Errorf("expected %v", c.expected) 20 | } 21 | got = got.New(c.in) 22 | if got.Cap() != c.expected { 23 | t.Errorf("expected %v", c.expected) 24 | } 25 | } 26 | } 27 | 28 | func TestNewPopulationSync(t *testing.T) { 29 | cases := []struct { 30 | in, expected int 31 | }{ 32 | {1, 1}, 33 | {7, 7}, 34 | } 35 | for _, c := range cases { 36 | got := NewPopulationSync(c.in) 37 | if got.Cap() != c.expected { 38 | t.Errorf("expected %v", c.expected) 39 | } 40 | } 41 | } 42 | 43 | func TestSort(t *testing.T) { 44 | i1, i2, i3 := NewIndividual(0.2), NewIndividual(0.7), NewIndividual(1) 45 | cases := []struct { 46 | in, expected Population 47 | }{ 48 | {&population{i1, i2, i3}, &population{i3, i2, i1}}, 49 | {&population{i1, i3, i2}, &population{i3, i2, i1}}, 50 | {&population{i3, i2, i1}, &population{i3, i2, i1}}, 51 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, &populationSync{population{i3, i2, i1}, sync.RWMutex{}}}, 52 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, &populationSync{population{i3, i2, i1}, sync.RWMutex{}}}, 53 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, &populationSync{population{i3, i2, i1}, sync.RWMutex{}}}, 54 | } 55 | for _, c := range cases { 56 | c.in.Sort() 57 | for i := 0; i < c.in.Len(); i++ { 58 | exp := c.expected.Get(i) 59 | in := c.in.Get(i) 60 | if in != exp { 61 | t.Errorf(".Sort() => %v; expected = %v", c.in, c.expected) 62 | break 63 | } 64 | } 65 | } 66 | } 67 | 68 | func TestCap(t *testing.T) { 69 | cases := []struct { 70 | in Population 71 | expected int 72 | }{ 73 | {NewPopulation(7), 7}, 74 | {NewPopulationSync(7), 7}, 75 | } 76 | for _, c := range cases { 77 | got := c.in.Cap() 78 | if got != c.expected { 79 | t.Errorf("%v.Cap() => %v; expected = %v", c.in, got, c.expected) 80 | } 81 | } 82 | } 83 | 84 | func TestSetCap(t *testing.T) { 85 | cases := []struct { 86 | pop Population 87 | in, expected int 88 | }{ 89 | {NewPopulation(2), 1, 1}, 90 | {NewPopulation(2), 7, 7}, 91 | {NewPopulationSync(2), 1, 1}, 92 | {NewPopulationSync(2), 7, 7}, 93 | } 94 | for _, c := range cases { 95 | c.pop.SetCap(c.in) 96 | if c.pop.Cap() != c.expected { 97 | t.Errorf("expected %v", c.expected) 98 | } 99 | } 100 | } 101 | 102 | func TestAdd(t *testing.T) { 103 | i1, i2, i3 := NewIndividual(0.2), NewIndividual(0.7), NewIndividual(1) 104 | cases := []struct { 105 | in Population 106 | indiv Individual 107 | expected Population 108 | }{ 109 | {&population{i1, i2}, i3, &population{i1, i2, i3}}, 110 | {&population{}, i2, &population{i2}}, 111 | {&population{i3, i2}, i1, &population{i3, i2, i1}}, 112 | {&population{i3, i2}, nil, &population{i3, i2, nil}}, 113 | {&populationSync{population{i1, i2}, sync.RWMutex{}}, i3, &populationSync{population{i1, i2, i3}, sync.RWMutex{}}}, 114 | {&populationSync{population{}, sync.RWMutex{}}, i2, &populationSync{population{i2}, sync.RWMutex{}}}, 115 | {&populationSync{population{i3, i2}, sync.RWMutex{}}, i1, &populationSync{population{i3, i2, i1}, sync.RWMutex{}}}, 116 | {&populationSync{population{i1, i2}, sync.RWMutex{}}, nil, &populationSync{population{i1, i2, nil}, sync.RWMutex{}}}, 117 | } 118 | for _, c := range cases { 119 | c.in.Add(c.indiv) 120 | for i := 0; i < c.in.Len(); i++ { 121 | in := c.in.Get(i) 122 | exp := c.expected.Get(i) 123 | if in != exp { 124 | t.Errorf(".Add(%v) => %v; expected = %v", c.indiv, c.in, c.expected) 125 | break 126 | } 127 | } 128 | } 129 | } 130 | 131 | func TestGet(t *testing.T) { 132 | i1, i2, i3 := NewIndividual(0.2), NewIndividual(0.7), NewIndividual(1) 133 | cases := []struct { 134 | pop Population 135 | }{ 136 | {&population{i2, i1, i3}}, 137 | {&populationSync{population{i2, i1, i3}, sync.RWMutex{}}}, 138 | } 139 | for _, c := range cases { 140 | indiv := c.pop.Get(1) 141 | if indiv != i1 { 142 | t.Errorf(".Get(%v) => %v; expected = %v", 1, indiv, i1) 143 | } 144 | } 145 | } 146 | 147 | func TestRemove(t *testing.T) { 148 | i1, i2, i3 := NewIndividual(0.2), NewIndividual(0.7), NewIndividual(1) 149 | cases := []struct { 150 | pop Population 151 | toBeRemoved Individual 152 | expected []Individual 153 | }{ 154 | {&population{i1, i2, i3}, i2, []Individual{i1, i3}}, 155 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, i2, []Individual{i1, i3}}, 156 | } 157 | for _, c := range cases { 158 | c.pop.Remove(c.toBeRemoved) 159 | for _, indiv := range c.expected { 160 | if !c.pop.Has(indiv) { 161 | t.Errorf("unexpected") 162 | } 163 | if c.pop.Len() != len(c.expected) { 164 | t.Errorf("unexpected") 165 | } 166 | } 167 | } 168 | } 169 | 170 | func TestRemoveAt(t *testing.T) { 171 | i1, i2, i3 := NewIndividual(0.2), NewIndividual(0.7), NewIndividual(1) 172 | cases := []struct { 173 | pop Population 174 | indexToBeRemoved int 175 | expected []Individual 176 | }{ 177 | {&population{i1, i2, i3}, 1, []Individual{i1, i3}}, 178 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, 1, []Individual{i1, i3}}, 179 | } 180 | for _, c := range cases { 181 | c.pop.RemoveAt(c.indexToBeRemoved) 182 | for _, indiv := range c.expected { 183 | if !c.pop.Has(indiv) { 184 | t.Errorf("unexpected") 185 | } 186 | if c.pop.Len() != len(c.expected) { 187 | t.Errorf("unexpected") 188 | } 189 | } 190 | } 191 | } 192 | 193 | func TestReplace(t *testing.T) { 194 | i1, i2, i3, i4 := NewIndividual(0.2), NewIndividual(0.7), NewIndividual(1), NewIndividual(10) 195 | cases := []struct { 196 | pop Population 197 | index int 198 | indiv Individual 199 | }{ 200 | {&population{i2, i1, i3}, 1, i4}, 201 | {&populationSync{population{i2, i1, i3}, sync.RWMutex{}}, 1, i4}, 202 | } 203 | for _, c := range cases { 204 | c.pop.Replace(c.index, c.indiv) 205 | if c.pop.Len() != 3 { 206 | t.Errorf(".Replace(%v, %v); pop.Len() => %v; expected = %v", c.index, c.indiv, c.pop.Len(), 3) 207 | } 208 | } 209 | } 210 | 211 | func TestMax(t *testing.T) { 212 | i1, i2, i3 := NewIndividual(0.2), NewIndividual(0.7), NewIndividual(1) 213 | var pop Population 214 | pop = &population{i2, i1, i3} 215 | max := pop.Max() 216 | if max != i3 { 217 | t.Errorf("%v.Max() returned %v instead of %v", pop, max, i3) 218 | } 219 | pop = &populationSync{population{i2, i1, i3}, sync.RWMutex{}} 220 | max = pop.Max() 221 | if max != i3 { 222 | t.Errorf("%v.Max() returned %v instead of %v", pop, max, i3) 223 | } 224 | } 225 | 226 | func TestMin(t *testing.T) { 227 | i1, i2, i3 := NewIndividual(0.2), NewIndividual(0.7), NewIndividual(1) 228 | var pop Population 229 | pop = &population{i2, i1, i3} 230 | min := pop.Min() 231 | if min != i1 { 232 | t.Errorf("%v.Min() returned %v instead of %v", pop, min, i1) 233 | } 234 | pop = &populationSync{population{i2, i1, i3}, sync.RWMutex{}} 235 | min = pop.Min() 236 | if min != i1 { 237 | t.Errorf("%v.Min() returned %v instead of %v", pop, min, i1) 238 | } 239 | } 240 | 241 | func TestLen(t *testing.T) { 242 | i1, i2, i3 := NewIndividual(0.2), NewIndividual(0.7), NewIndividual(1) 243 | cases := []struct { 244 | in Population 245 | cap int 246 | expected int 247 | }{ 248 | {&population{i1, i2, i3}, 3, 3}, 249 | {&population{i1, i3, i2}, 4, 3}, 250 | {&population{i3, i1}, 12, 2}, 251 | {&population{i3, i2, i1}, 2, 2}, 252 | {&population{}, 2, 0}, 253 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, 3, 3}, 254 | {&populationSync{population{i1, i3, i2}, sync.RWMutex{}}, 4, 3}, 255 | {&populationSync{population{i3, i1}, sync.RWMutex{}}, 12, 2}, 256 | {&populationSync{population{i3, i2, i1}, sync.RWMutex{}}, 2, 2}, 257 | {&populationSync{population{}, sync.RWMutex{}}, 2, 0}, 258 | } 259 | for _, c := range cases { 260 | c.in.SetCap(c.cap) 261 | length := c.in.Len() 262 | if length != c.expected { 263 | t.Errorf("%v.Len() returned %v instead of %v", c.in, length, c.expected) 264 | } 265 | } 266 | } 267 | 268 | func TestLess(t *testing.T) { 269 | i1, i2, i3 := NewIndividual(0.2), NewIndividual(0.7), NewIndividual(1) 270 | cases := []struct { 271 | in Population 272 | i int 273 | j int 274 | expected bool 275 | }{ 276 | {&population{i1, i2, i3}, 0, 0, true}, 277 | {&population{i1, i2, i3}, 0, 1, false}, 278 | {&population{i1, i2, i3}, 0, 2, false}, 279 | {&population{i1, i2, i3}, 1, 0, true}, 280 | {&population{i1, i2, i3}, 1, 1, true}, 281 | {&population{i1, i2, i3}, 1, 2, false}, 282 | {&population{i1, i2, i3}, 2, 0, true}, 283 | {&population{i1, i2, i3}, 2, 1, true}, 284 | {&population{i1, i2, i3}, 2, 2, true}, 285 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, 0, 0, true}, 286 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, 0, 1, false}, 287 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, 0, 2, false}, 288 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, 1, 0, true}, 289 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, 1, 1, true}, 290 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, 1, 2, false}, 291 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, 2, 0, true}, 292 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, 2, 1, true}, 293 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, 2, 2, true}, 294 | } 295 | for _, c := range cases { 296 | less := c.in.Less(c.i, c.j) 297 | if less != c.expected { 298 | t.Errorf("%v.Less(%v, %v) returned %v instead of %v", c.in, c.i, c.j, less, c.expected) 299 | } 300 | } 301 | } 302 | 303 | func TestSwap(t *testing.T) { 304 | i1, i2, i3 := NewIndividual(0.2), NewIndividual(0.7), NewIndividual(1) 305 | cases := []struct { 306 | in Population 307 | i int 308 | j int 309 | expected Population 310 | }{ 311 | {&population{i1, i2, i3}, 0, 0, &population{i1, i2, i3}}, 312 | {&population{i1, i2, i3}, 0, 1, &population{i2, i1, i3}}, 313 | {&population{i1, i2, i3}, 0, 2, &population{i3, i2, i1}}, 314 | {&population{i1, i2, i3}, 1, 0, &population{i2, i1, i3}}, 315 | {&population{i1, i2, i3}, 1, 1, &population{i1, i2, i3}}, 316 | {&population{i1, i2, i3}, 1, 2, &population{i1, i3, i2}}, 317 | {&population{i1, i2, i3}, 2, 0, &population{i3, i2, i1}}, 318 | {&population{i1, i2, i3}, 2, 1, &population{i1, i3, i2}}, 319 | {&population{i1, i2, i3}, 2, 2, &population{i1, i2, i3}}, 320 | {&population{i1, i2, i3}, -1, 0, &population{i1, i2, i3}}, 321 | {&population{i1, i2, i3}, 0, -1, &population{i1, i2, i3}}, 322 | {&population{i1, i2, i3}, -1, -1, &population{i1, i2, i3}}, 323 | {&population{i1, i2, i3}, 1000, 0, &population{i1, i2, i3}}, 324 | {&population{i1, i2, i3}, 0, 1000, &population{i1, i2, i3}}, 325 | {&population{i1, i2, i3}, 1000, 1000, &population{i1, i2, i3}}, 326 | {&population{i1, i2, i3}, -1, 1000, &population{i1, i2, i3}}, 327 | {&population{i1, i2, i3}, 1000, -1, &population{i1, i2, i3}}, 328 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, 0, 0, &populationSync{population{i1, i2, i3}, sync.RWMutex{}}}, 329 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, 0, 1, &populationSync{population{i2, i1, i3}, sync.RWMutex{}}}, 330 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, 0, 2, &populationSync{population{i3, i2, i1}, sync.RWMutex{}}}, 331 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, 1, 0, &populationSync{population{i2, i1, i3}, sync.RWMutex{}}}, 332 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, 1, 1, &populationSync{population{i1, i2, i3}, sync.RWMutex{}}}, 333 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, 1, 2, &populationSync{population{i1, i3, i2}, sync.RWMutex{}}}, 334 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, 2, 0, &populationSync{population{i3, i2, i1}, sync.RWMutex{}}}, 335 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, 2, 1, &populationSync{population{i1, i3, i2}, sync.RWMutex{}}}, 336 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, 2, 2, &populationSync{population{i1, i2, i3}, sync.RWMutex{}}}, 337 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, -1, 0, &populationSync{population{i1, i2, i3}, sync.RWMutex{}}}, 338 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, 0, -1, &populationSync{population{i1, i2, i3}, sync.RWMutex{}}}, 339 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, -1, -1, &populationSync{population{i1, i2, i3}, sync.RWMutex{}}}, 340 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, 1000, 0, &populationSync{population{i1, i2, i3}, sync.RWMutex{}}}, 341 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, 0, 1000, &populationSync{population{i1, i2, i3}, sync.RWMutex{}}}, 342 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, 1000, 1000, &populationSync{population{i1, i2, i3}, sync.RWMutex{}}}, 343 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, -1, 1000, &populationSync{population{i1, i2, i3}, sync.RWMutex{}}}, 344 | {&populationSync{population{i1, i2, i3}, sync.RWMutex{}}, 1000, -1, &populationSync{population{i1, i2, i3}, sync.RWMutex{}}}, 345 | } 346 | for _, c := range cases { 347 | c.in.Swap(c.i, c.j) 348 | for i := 0; i < c.in.Len(); i++ { 349 | indiv := c.in.Get(i) 350 | expected := c.expected.Get(i) 351 | if indiv != expected { 352 | t.Errorf("%v.Swap(%v, %v) resulted in %v instead of %v", c.in, c.i, c.j, c.in, c.expected) 353 | break 354 | } 355 | } 356 | } 357 | } 358 | 359 | func TestContains(t *testing.T) { 360 | i1, i2, i3 := NewIndividual(0.2), NewIndividual(0.7), NewIndividual(1) 361 | cases := []struct { 362 | in Population 363 | indiv Individual 364 | expected bool 365 | }{ 366 | {&population{i1, i2}, i1, true}, 367 | {&population{i1, i2}, i3, false}, 368 | {&populationSync{population{i1, i2}, sync.RWMutex{}}, i1, true}, 369 | {&populationSync{population{i1, i2}, sync.RWMutex{}}, i3, false}, 370 | } 371 | for _, c := range cases { 372 | contains := c.in.Has(c.indiv) 373 | if contains != c.expected { 374 | t.Errorf("%v.Contains(%v) returned %v instead of %v", c.in, c.indiv, contains, c.expected) 375 | } 376 | } 377 | } 378 | 379 | func TestIndexOf(t *testing.T) { 380 | i1, i2, i3 := NewIndividual(0.2), NewIndividual(0.7), NewIndividual(1) 381 | cases := []struct { 382 | in Population 383 | indiv Individual 384 | expected int 385 | }{ 386 | {&population{i1, i2}, i1, 0}, 387 | {&population{i1, i2}, i2, 1}, 388 | {&population{i1, i2}, i3, -1}, 389 | {&populationSync{population{i1, i2}, sync.RWMutex{}}, i1, 0}, 390 | {&populationSync{population{i1, i2}, sync.RWMutex{}}, i2, 1}, 391 | {&populationSync{population{i1, i2}, sync.RWMutex{}}, i3, -1}, 392 | } 393 | for _, c := range cases { 394 | index, _ := c.in.IndexOf(c.indiv) 395 | if index != c.expected { 396 | t.Errorf("%v.Contains(%v) returned %v instead of %v", c.in, c.indiv, index, c.expected) 397 | } 398 | } 399 | pop := population{i2, i1, i3} 400 | _, err := pop.IndexOf(nil) 401 | if err == nil { 402 | panic(err) 403 | } 404 | } 405 | 406 | func TestEach(t *testing.T) { 407 | i1, i2, i3, i4, i5, i6 := NewIndividual(1), NewIndividual(2), NewIndividual(3), NewIndividual(4), NewIndividual(5), NewIndividual(6) 408 | cases := []struct { 409 | pop Population 410 | individuals []Individual 411 | }{ 412 | {NewPopulation(4), []Individual{i1, i5, i6, i4}}, 413 | {NewPopulation(5), []Individual{i1, i3, i5, i2, i6}}, 414 | {NewPopulationSync(4), []Individual{i1, i5, i6, i4}}, 415 | {NewPopulationSync(5), []Individual{i1, i3, i5, i2, i6}}, 416 | } 417 | for _, c := range cases { 418 | c.pop.Add(c.individuals...) 419 | c.pop.Each(func(indiv Individual) bool { 420 | has := false 421 | for _, current := range c.individuals { 422 | if current == indiv { 423 | has = true 424 | break 425 | } 426 | } 427 | if !has { 428 | t.Errorf("Each traversal %v which is not found in %v", indiv, c.individuals) 429 | } 430 | return true 431 | }) 432 | c.pop.Each(func(indiv Individual) bool { 433 | return false 434 | }) 435 | } 436 | } 437 | 438 | func TestSlice(t *testing.T) { 439 | i1, i2, i3 := NewIndividual(0.2), NewIndividual(0.7), NewIndividual(1) 440 | cases := []struct { 441 | pop Population 442 | slice []Individual 443 | }{ 444 | {NewPopulation(3), []Individual{i1, i2, i3}}, 445 | {NewPopulationSync(3), []Individual{i1, i2, i3}}, 446 | } 447 | for _, c := range cases { 448 | c.pop.Add(c.slice...) 449 | slice := c.pop.Slice() 450 | for i := range c.slice { 451 | indiv := slice[i] 452 | if indiv != c.slice[i] { 453 | t.Errorf("expected %v, got %v", c.slice[i], indiv) 454 | } 455 | } 456 | } 457 | } 458 | -------------------------------------------------------------------------------- /positioner.go: -------------------------------------------------------------------------------- 1 | package evoli 2 | 3 | // Positioner produces a new individual from 4 | // its previous version(indiv), 5 | // its best version since it exists(pBest), 6 | // the best of the poopulation(gBest) or local neighborhood, 7 | // and the learning coefficients(c1,c2). typical values are c1=c2=2 8 | type Positioner interface { 9 | Position(indiv, pBest, gBest Individual, c1, c2 float64) (Individual, error) 10 | } 11 | -------------------------------------------------------------------------------- /selecter.go: -------------------------------------------------------------------------------- 1 | package evoli 2 | 3 | import ( 4 | "math/rand" 5 | ) 6 | 7 | // Selecter is the selecter operator interface 8 | type Selecter interface { 9 | Select(pop Population, survivorsSize int) (survivors Population, deads Population, err error) 10 | } 11 | 12 | func checkSelectParams(survivorsSize int) { 13 | if survivorsSize < 1 { 14 | panic(ErrSurvivorSize) 15 | } 16 | } 17 | 18 | type proportionalToFitnessSelecter struct{} 19 | 20 | func (s proportionalToFitnessSelecter) Select(pop Population, survivorsSize int) (survivors, deads Population, err error) { 21 | checkSelectParams(survivorsSize) 22 | if survivorsSize >= pop.Len() { 23 | return pop, nil, nil 24 | } 25 | var ( 26 | leftovers Population 27 | newPop = pop.New(pop.Cap()) 28 | minIndiv, maxIndiv Individual 29 | min, max float64 30 | pivot float64 31 | score float64 32 | benchmark float64 33 | penaltyStep float64 34 | penalty float64 35 | ) 36 | for newPop.Len() < survivorsSize { 37 | leftovers = pop.New(pop.Len()) 38 | minIndiv, maxIndiv = pop.Min(), pop.Max() 39 | min, max = minIndiv.Fitness(), maxIndiv.Fitness() 40 | if min < 0 { 41 | pivot += -min 42 | } else { 43 | pivot += min 44 | } 45 | benchmark = max + pivot 46 | penalty = 0 47 | penaltyStep = benchmark / float64(pop.Len()) 48 | for i := 0; i < pop.Len(); i++ { 49 | indiv := pop.Get(i) 50 | if newPop.Len() >= survivorsSize { 51 | leftovers.Add(indiv) 52 | continue 53 | } 54 | score = (indiv.Fitness() + pivot - penalty) / benchmark 55 | if rand.Float64() <= 0.1+0.8*score { 56 | newPop.Add(indiv) 57 | penalty += penaltyStep 58 | } else { 59 | leftovers.Add(indiv) 60 | } 61 | } 62 | pop.Close() 63 | pop = leftovers 64 | } 65 | return newPop, pop, nil 66 | } 67 | 68 | // NewProportionalToFitnessSelecter is the constructor for selecter based on fitness value 69 | func NewProportionalToFitnessSelecter() Selecter { 70 | return proportionalToFitnessSelecter{} 71 | } 72 | 73 | type stochasticUniversalSampling struct{} 74 | 75 | func (s stochasticUniversalSampling) Select(pop Population, survivorsSize int) (survivors, deads Population, err error) { 76 | checkSelectParams(survivorsSize) 77 | if survivorsSize >= pop.Len() { 78 | return pop, nil, nil 79 | } 80 | survivors = pop.New(pop.Cap()) 81 | deads = pop.New(pop.Cap() - survivorsSize) 82 | var ( 83 | minIndiv = pop.Min() 84 | minFit = minIndiv.Fitness() 85 | offset float64 86 | totalFit float64 87 | ) 88 | if minFit < 0 { 89 | offset = -minFit 90 | } 91 | pop.Each(func(indiv Individual) bool { 92 | totalFit += indiv.Fitness() + offset 93 | return true 94 | }) 95 | step := totalFit / float64(survivorsSize) 96 | start := rand.Float64() * step 97 | milestones := make([]float64, 0, survivorsSize) 98 | for i := 0; i < survivorsSize; i++ { 99 | milestones = append(milestones, start+float64(i)*step) 100 | } 101 | var ( 102 | fitSum float64 103 | indiv Individual 104 | isSelected bool 105 | i int 106 | ) 107 | pop.Sort() 108 | for _, milestone := range milestones { 109 | isSelected = false 110 | for !isSelected { 111 | indiv = pop.Get(i) 112 | fitSum += indiv.Fitness() + offset 113 | isSelected = fitSum >= milestone 114 | if isSelected { 115 | survivors.Add(indiv) 116 | } else { 117 | deads.Add(indiv) 118 | } 119 | i++ 120 | } 121 | } 122 | pop.Close() 123 | return survivors, deads, nil 124 | } 125 | 126 | // NewStochasticUniversalSamplingSelecter is the constructor for selecter based on fitness value 127 | func NewStochasticUniversalSamplingSelecter() Selecter { 128 | return stochasticUniversalSampling{} 129 | } 130 | 131 | type tournamentSelecter struct { 132 | p float64 133 | } 134 | 135 | func (s tournamentSelecter) Select(pop Population, survivorsSize int) (survivors, deads Population, err error) { 136 | checkSelectParams(survivorsSize) 137 | if survivorsSize >= pop.Len() { 138 | return pop, nil, nil 139 | } 140 | survivors = pop.New(pop.Cap()) 141 | var leftovers Population 142 | var popLen int 143 | pop.Sort() 144 | for survivors.Len() < survivorsSize { 145 | popLen = pop.Len() 146 | leftovers = pop.New(pop.Cap() - survivorsSize + survivors.Len()) 147 | for i := 0; i < popLen; i++ { 148 | indiv := pop.Get(i) 149 | if survivors.Len() >= survivorsSize { 150 | leftovers.Add(indiv) 151 | continue 152 | } 153 | draw := rand.Float64() 154 | if draw <= s.p { 155 | survivors.Add(indiv) 156 | } else { 157 | leftovers.Add(indiv) 158 | } 159 | } 160 | pop.Close() 161 | pop = leftovers 162 | } 163 | return survivors, pop, nil 164 | } 165 | 166 | // NewTournamentSelecter is the constructor for tournament selecter. High rank increase chances to be selected 167 | func NewTournamentSelecter(p float64) Selecter { 168 | return tournamentSelecter{p} 169 | } 170 | 171 | type truncationSelecter struct{} 172 | 173 | func (s truncationSelecter) Select(pop Population, survivorsSize int) (survivors, deads Population, err error) { 174 | checkSelectParams(survivorsSize) 175 | if pop.Len() <= survivorsSize { 176 | return pop, nil, nil 177 | } 178 | pop.Sort() 179 | survivors = pop.New(pop.Cap()) 180 | deads = pop.New(pop.Cap() - survivorsSize) 181 | survivors.Add(pop.Slice()[:survivorsSize]...) 182 | deads.Add(pop.Slice()[survivorsSize:]...) 183 | pop.Close() 184 | return survivors, deads, nil 185 | } 186 | 187 | // NewTruncationSelecter is the constructor for truncation selecter 188 | func NewTruncationSelecter() Selecter { 189 | return truncationSelecter{} 190 | } 191 | 192 | type randomSelecter struct{} 193 | 194 | func (s randomSelecter) Select(pop Population, survivorsSize int) (survivors, deads Population, err error) { 195 | checkSelectParams(survivorsSize) 196 | if pop.Len() <= survivorsSize { 197 | return pop, nil, nil 198 | } 199 | var ( 200 | deathCount = pop.Cap() - survivorsSize 201 | count int 202 | deadIndex int 203 | ) 204 | survivors = pop.New(pop.Cap()) 205 | deads = pop.New(deathCount) 206 | survivors.Add(pop.Slice()...) 207 | for count = 0; count < deathCount; count++ { 208 | deadIndex = rand.Intn(survivors.Len() - 1) 209 | deads.Add(survivors.Get(deadIndex)) 210 | survivors.RemoveAt(deadIndex) 211 | } 212 | pop.Close() 213 | return survivors, deads, nil 214 | } 215 | 216 | // NewRandomSelecter is the constructor for random selecter 217 | func NewRandomSelecter() Selecter { 218 | return randomSelecter{} 219 | } 220 | 221 | type proportionalToRankSelecter struct{} 222 | 223 | func (s proportionalToRankSelecter) Select(pop Population, survivorsSize int) (survivors, deads Population, err error) { 224 | checkSelectParams(survivorsSize) 225 | if survivorsSize >= pop.Len() { 226 | return pop, nil, nil 227 | } 228 | var ( 229 | leftovers Population 230 | newPop = pop.New(pop.Cap()) 231 | benchmark float64 232 | score float64 233 | popLen int 234 | n float64 235 | penalty int 236 | ) 237 | pop.Sort() 238 | for newPop.Len() < survivorsSize { 239 | popLen = pop.Len() 240 | leftovers = pop.New(popLen) 241 | penalty = 0 242 | benchmark = float64(popLen*(popLen+1)) / 2 243 | for i := 0; i < popLen; i++ { 244 | indiv := pop.Get(i) 245 | if newPop.Len() >= survivorsSize { 246 | leftovers.Add(indiv) 247 | continue 248 | } 249 | n = float64(popLen - i - penalty) 250 | score = n * (n + 1) / 2 251 | if rand.Float64() <= 0.25+0.5*score/benchmark { 252 | newPop.Add(indiv) 253 | penalty++ 254 | } else { 255 | leftovers.Add(indiv) 256 | } 257 | } 258 | pop.Close() 259 | pop = leftovers 260 | } 261 | return newPop, pop, nil 262 | } 263 | 264 | // NewProportionalToRankSelecter is the constructor for selecter based on ranking across the population 265 | func NewProportionalToRankSelecter() Selecter { 266 | return proportionalToRankSelecter{} 267 | } 268 | -------------------------------------------------------------------------------- /selecter_test.go: -------------------------------------------------------------------------------- 1 | package evoli 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestSelecterTruncation(t *testing.T) { 8 | testSelecter(t, NewTruncationSelecter()) 9 | } 10 | 11 | func TestSelecterTournament(t *testing.T) { 12 | testSelecter(t, NewTournamentSelecter(1)) 13 | } 14 | 15 | func TestSelecterRandom(t *testing.T) { 16 | testSelecter(t, NewRandomSelecter()) 17 | } 18 | 19 | func TestSelecterProportionalToRank(t *testing.T) { 20 | testSelecter(t, NewProportionalToRankSelecter()) 21 | } 22 | 23 | func TestSelecterProportionalToFitness(t *testing.T) { 24 | testSelecter(t, NewProportionalToFitnessSelecter()) 25 | } 26 | 27 | func testSelecter(t *testing.T, s Selecter) { 28 | i1, i2, i3, i4, i5, i6 := NewIndividual(1), NewIndividual(2), NewIndividual(-3), NewIndividual(4), NewIndividual(5), NewIndividual(-6) 29 | cases := []struct { 30 | in population 31 | survivalSize int 32 | expectedLen int 33 | expectedCap int 34 | }{ 35 | {population{i1, i2, i3, i4, i5, i6}, 3, 3, 6}, 36 | {population{i1, i1, i1, i1, i1, i1, i1, i1, i1, i1, i1, i1, i1}, 1, 1, 13}, 37 | {population{i1, i2, i4, i5}, 2, 2, 4}, 38 | {population{i1}, 3, 1, 1}, 39 | {population{i3, i6}, 1, 1, 2}, 40 | } 41 | for _, c := range cases { 42 | newPop, _, _ := s.Select(&c.in, c.survivalSize) 43 | length, capacity := newPop.Len(), newPop.Cap() 44 | if length != c.expectedLen { 45 | t.Errorf("s.Select(%v, %v) returned %v which has a length of %v instead of %v", c.in, c.survivalSize, newPop, length, c.expectedLen) 46 | } 47 | if capacity != c.expectedCap { 48 | t.Errorf("s.Select(%v, %v) returned %v which has a capacity of %v instead of %v", c.in, c.survivalSize, newPop, capacity, c.expectedCap) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /swarm.go: -------------------------------------------------------------------------------- 1 | package evoli 2 | 3 | import "sync" 4 | 5 | var ( 6 | // ErrLearningCoef - c1 & c2 must be > 0 7 | ErrLearningCoef = "ErrLearningCoef - c1 & c2 must be > 0" 8 | ) 9 | 10 | type swarm struct { 11 | evolution 12 | bests map[Individual]Individual 13 | positioner Positioner 14 | c1, c2 float64 15 | } 16 | 17 | // NewSwarm - constructor for particles swarm optimization algorithm 18 | // typical value for learning coef is c1 = c2 = 2 19 | // the bigger are the coefficients the faster the population converge 20 | func NewSwarm(pop Population, positioner Positioner, c1, c2 float64, evaluater Evaluater) Evolution { 21 | if c1 <= 0 || c2 <= 0 { 22 | panic(ErrLearningCoef) 23 | } 24 | return &swarm{newEvolution(pop, evaluater), make(map[Individual]Individual), positioner, c1, c2} 25 | } 26 | 27 | func (s *swarm) Next() error { 28 | newPop, err := s.evaluation(s.pop) 29 | if err != nil { 30 | return err 31 | } 32 | newPop, err = s.positioning(newPop) 33 | if err != nil { 34 | return err 35 | } 36 | s.pop = newPop 37 | return nil 38 | } 39 | 40 | func (s *swarm) evaluation(pop Population) (Population, error) { 41 | length := pop.Len() 42 | for i := 0; i < length; i++ { 43 | individual := pop.Get(i) 44 | fitness, err := s.evaluater.Evaluate(individual) 45 | if err != nil { 46 | return pop, err 47 | } 48 | best, exists := s.bests[individual] 49 | if !exists || fitness > best.Fitness() { 50 | s.bests[individual] = individual 51 | } 52 | individual.SetFitness(fitness) 53 | } 54 | return pop, nil 55 | } 56 | 57 | func (s *swarm) positioning(pop Population) (Population, error) { 58 | newPop := NewPopulation(pop.Cap()) 59 | gBest := pop.Max() 60 | individuals := pop.Slice() 61 | for _, indiv := range individuals { 62 | pBest := s.bests[indiv] 63 | newIndiv, err := s.positioner.Position(indiv, pBest, gBest, s.c1, s.c2) 64 | if err != nil { 65 | return nil, err 66 | } 67 | delete(s.bests, indiv) 68 | s.bests[newIndiv] = pBest 69 | newPop.Add(newIndiv) 70 | } 71 | return newPop, nil 72 | } 73 | 74 | func (s *swarm) SetPopulation(pop Population) { 75 | s.bests = make(map[Individual]Individual) 76 | s.evolution.SetPopulation(pop) 77 | } 78 | 79 | type swarmSync struct { 80 | swarm 81 | sync.RWMutex 82 | } 83 | 84 | // NewSwarmSync - constructor for particles swarm optimization algorithm (sync impl) 85 | // typical value for learning coef is c1 = c2 = 2 86 | // the bigger are the coefficients the faster the population converge 87 | func NewSwarmSync(pop Population, positioner Positioner, c1, c2 float64, evaluater Evaluater) Evolution { 88 | return &swarmSync{*NewSwarm(pop, positioner, c1, c2, evaluater).(*swarm), sync.RWMutex{}} 89 | } 90 | 91 | func (s *swarmSync) Next() error { 92 | s.Lock() 93 | defer s.Unlock() 94 | return s.swarm.Next() 95 | } 96 | 97 | func (s *swarmSync) Population() Population { 98 | s.RLock() 99 | defer s.RUnlock() 100 | return s.swarm.Population() 101 | } 102 | 103 | func (s *swarmSync) SetPopulation(pop Population) { 104 | s.Lock() 105 | defer s.Unlock() 106 | s.swarm.SetPopulation(pop) 107 | } 108 | 109 | func (s *swarmSync) Alpha() Individual { 110 | s.RLock() 111 | defer s.RUnlock() 112 | return s.swarm.Alpha() 113 | } 114 | -------------------------------------------------------------------------------- /swarm_test.go: -------------------------------------------------------------------------------- 1 | package evoli 2 | 3 | import "testing" 4 | 5 | // To be completed 6 | func TestNewSwarm(t *testing.T) { 7 | errorCases := []struct { 8 | p Positioner 9 | c1, c2 float64 10 | e Evaluater 11 | }{ 12 | {positionerMock{}, 2, 2, evaluaterMock{}}, 13 | } 14 | for _, c := range errorCases { 15 | i1 := NewIndividual(1) 16 | i2 := NewIndividual(2) 17 | popInit := NewPopulation(2) 18 | newPop := NewPopulation(1) 19 | popInit.Add(i1, i2) 20 | sw := NewSwarm(popInit, c.p, c.c1, c.c2, c.e) 21 | pop := sw.Population() 22 | if pop != popInit { 23 | t.Errorf("expected %v got %v", popInit, pop) 24 | } 25 | alpha := sw.Alpha() 26 | if alpha != i2 { 27 | t.Errorf("expected %v got %v", i2, alpha) 28 | } 29 | sw.SetPopulation(newPop) 30 | pop = sw.Population() 31 | if pop != newPop { 32 | t.Errorf("expected %v got %v", newPop, pop) 33 | } 34 | sw = NewSwarmSync(popInit, c.p, c.c1, c.c2, c.e) 35 | pop = sw.Population() 36 | if pop != popInit { 37 | t.Errorf("expected %v got %v", popInit, pop) 38 | } 39 | alpha = sw.Alpha() 40 | if alpha != i2 { 41 | t.Errorf("expected %v got %v", i2, alpha) 42 | } 43 | sw.SetPopulation(newPop) 44 | pop = sw.Population() 45 | if pop != newPop { 46 | t.Errorf("expected %v got %v", newPop, pop) 47 | } 48 | } 49 | } 50 | 51 | // To be completed 52 | func TestSwarmNext(t *testing.T) { 53 | i1, i2, i3, i4, i5, i6 := NewIndividual(1), NewIndividual(-2), NewIndividual(3), NewIndividual(4), NewIndividual(5), NewIndividual(6) 54 | pop := population{i1, i2, i3, i4, i5, i6} 55 | cpy := NewPopulation(pop.Cap()) 56 | cpy.Add(pop...) 57 | cases := []struct { 58 | swarm Evolution 59 | }{ 60 | {NewSwarm(&pop, positionerMock{}, 2, 2, evaluaterMock{})}, 61 | {NewSwarmSync(&pop, positionerMock{}, 2, 2, evaluaterMock{})}, 62 | } 63 | for _, c := range cases { 64 | _ = c.swarm.Next() 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | unit_tests(){ 4 | local ret 5 | ret=0 6 | for d in $(go list ./... | grep -v vendor); do 7 | COV=$(go test -race -coverprofile=profile.out -covermode=atomic $d | sed 's/.*\[no test files\]/0/g' | sed 's/.*coverage//g' | sed 's/[^0-9.]*//g' | sed 's/\.[0-9.]*//g') 8 | if [ -f profile.out ]; then 9 | cat profile.out >> coverage.txt; 10 | rm profile.out; 11 | fi 12 | if test $COV -lt 75; then 13 | echo expecting test coverage greater than 75 %, got insufficient $COV % for package $d; 14 | ret=1 15 | fi 16 | done 17 | return $ret 18 | } 19 | 20 | set -e 21 | unit_tests --------------------------------------------------------------------------------