added LICENSE
 
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ https://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ https://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
changed README.md
 
@@ -4,9 +4,14 @@
4
4
5
5
[![Build Status](https://travis-ci.org/seanmor5/genex.svg?branch=master)](https://travis-ci.org/seanmor5/genex)
6
6
[![Coverage Status](https://coveralls.io/repos/github/seanmor5/genex/badge.svg?branch=master)](https://coveralls.io/github/seanmor5/genex?branch=master)
7
+ [![Hex Version](https://img.shields.io/hexpm/v/genex)](https://hex.pm/packages/genex/0.1.4)
7
8
8
9
This library is inspired by Python's [DEAP](https://github.com/deap/deap).
9
10
11
+ ## Documentation
12
+
13
+ Documentation is available at [https://hexdocs.pm/genex/introduction-overview.html](https://hexdocs.pm/genex/introduction-overview.html)
14
+
10
15
## Installation
11
16
12
17
The package can be installed by adding `genex` to your list of dependencies in `mix.exs`.
 
@@ -21,11 +26,7 @@ end
21
26
22
27
## Usage
23
28
24
- Genex works by making transformations on a `Population`.
25
-
26
- The simplest way to use Genex is by including it in one of your modules with default parameters.
27
-
28
- It requires you to implement 3 functions: `encoding/0`, `fitness_function/1`, `terminate?/1`.
29
+ Genex requires an implementation module with 3 functions: `encoding/0`, `fitness_function/1`, and `terminate?/1`.
29
30
30
31
```elixir
31
32
defmodule OneMax do
 
@@ -41,28 +42,42 @@ defmodule OneMax do
41
42
end
42
43
```
43
44
44
- Your algorithm can then be run by calling the `run/0` method Genex provides.
45
+ Now, run `iex -S mix`.
45
46
46
- ## Visualization
47
-
48
- Genex currently offers text visualization of populations. To view a summary of the solution your algorithm produced simply do:
49
- ```elixir
50
- soln = OneMax.run()
51
- Genex.Visualizers.Text.display_summary(soln)
47
+ Then:
48
+ ```
49
+ iex> OneMax.run()
52
50
```
53
51
54
- ## Genealogy
52
+ ## Features
55
53
56
- Genex comes with an implementation of a Genealogy tree using an Erlang digraph. The tree is available in the `history` field of the `Population` struct. As of this version of Genex, there are no pre-packaged genealogy visualizers. You'll have to find a third-party solution.
54
+ Genex strives to be as simple and customizable as possible. Along with the ability to customize EVERY step of your Genetic algorithm, Genex comes with the following features:
57
55
58
- ## Configuration
56
+ - 6 Selection Operators
57
+ - 7 Crossover Operators
58
+ - 6 Mutation Operators
59
+ - Customizable Population Statistics
60
+ - Customizable Benchmarking of Algorithms
61
+ - Exportable Genealogy Tree
62
+ - Flexible Encoding of Chromosomes
63
+ - Simple Text Visualizations
59
64
60
- Genex can be configured like so:
61
- ```elixir
62
- def MyGA do
63
- use Genex, crossover_type: :two_point, crossover_rate: 0.5
64
- ...
65
- end
65
+ ## Benchmarks
66
+
67
+ To run benchmarks, clone this repo. In the `genex` directory run:
68
+
69
+ ```
70
+ mix run bench/benchmarks.exs
66
71
```
67
72
68
- Please see the [documentation](https://hexdocs.pm/genex/tutorials-getting-started.html#content) for a full list of configuration options.
73
+ You can also run the individual benchmarks available in the `bench/` directory. This will take some time!
74
+
75
+ ## Contributing
76
+
77
+ If you have any problems with Genex, please open an issue! If you have a fix for an issue, submit a pull request.
78
+
79
+ ## Roadmap
80
+
81
+ The next phase of this library will involve extensive performance improvements. Most of the algorithms involve processing very large lists. This is an ideal job for a NIF.
82
+
83
+ If anybody has any experience writing NIFs or writing algorithms for processing large lists, [email me](mailto:[email protected]) to get involved!
\ No newline at end of file
changed changelog.md
 
@@ -1,5 +1,18 @@
1
+ # v0.1.2
2
+ - Bug fixes
3
+ - Added population statistics
4
+ - Crossover rate and mutation rate implemented as functions
5
+
1
6
# v0.1.4
2
7
- Bug fixes
3
8
- 3.51x Performance Improvement in single_point crossover
4
9
- 2.35x Performance Improvement in two_point crossover
5
10
- Addition of `benchmark/0` function to benchmark your algorithm
11
+
12
+ # v0.2.0
13
+ - Changed to `libgraph` for Genealogy tree.
14
+ - Added ability to export graph as DOT file.
15
+ - Removed `track_history?` flag.
16
+ - Added 17 Tests, Plus Doctests
17
+ - Fixed Variance bug
18
+ - Added methods WITHOUT random variables to verify validity of operator algorithms.
\ No newline at end of file
changed hex_metadata.config
 
@@ -11,7 +11,7 @@
11
11
<<"lib/genex/operators/selection.ex">>,
12
12
<<"lib/genex/operators/mutation.ex">>,<<"lib/genex/visualizers">>,
13
13
<<"lib/genex/visualizers/text.ex">>,<<"lib/genex.ex">>,<<".formatter.exs">>,
14
- <<"mix.exs">>,<<"README.md">>,<<"changelog.md">>]}.
14
+ <<"mix.exs">>,<<"README.md">>,<<"LICENSE">>,<<"changelog.md">>]}.
15
15
{<<"licenses">>,[<<"Apache-2.0">>]}.
16
16
{<<"links">>,[{<<"GitHub">>,<<"https://www.github.com/seanmor5/genex">>}]}.
17
17
{<<"name">>,<<"genex">>}.
 
@@ -25,5 +25,10 @@
25
25
{<<"name">>,<<"benchee">>},
26
26
{<<"optional">>,false},
27
27
{<<"repository">>,<<"hexpm">>},
28
- {<<"requirement">>,<<"~> 1.0">>}]]}.
29
- {<<"version">>,<<"0.1.4">>}.
28
+ {<<"requirement">>,<<"~> 1.0">>}],
29
+ [{<<"app">>,<<"libgraph">>},
30
+ {<<"name">>,<<"libgraph">>},
31
+ {<<"optional">>,false},
32
+ {<<"repository">>,<<"hexpm">>},
33
+ {<<"requirement">>,<<"~> 0.13.0">>}]]}.
34
+ {<<"version">>,<<"0.2.0">>}.
changed lib/genex.ex
 
@@ -14,18 +14,20 @@ defmodule Genex do
14
14
Genex is a simple library for creating Genetic Algorithms in Elixir. A Genetic Algorithm is a search-based optimization technique based on the principles of Genetics and Natural Selection.
15
15
16
16
The basic life-cycle of a Genetic Algorithm is as follows:
17
- 1) Initialize the Population
18
- 2) Loop until goal is reached
19
- a) Select Parents
20
- b) Perform Crossover
21
- c) Mutate some of population
22
- d) Select Survivors
23
17
24
- Genex follows a structure similar to the one above and offers callbacks corresponding to each stage in the genetic algorithm to allow for full customization.
18
+ 1) Initialize the Population
19
+ 2) Loop until goal is reached
20
+ a) Select Parents
21
+ b) Perform Crossover
22
+ c) Mutate some of population
23
+ d) Select Survivors
25
24
26
- # Implementation
25
+ Genex uses a structure similar to the one above, using callbacks to give you full control over your genetic algorithm.
26
+
27
+ ## Implementation
27
28
28
29
Genex requires an implementation module:
30
+
29
31
```
30
32
defmodule OneMax do
31
33
use Genex
 
@@ -39,9 +41,10 @@ defmodule Genex do
39
41
def terminate?(population), do: population.max_fitness == 15
40
42
end
41
43
```
44
+
42
45
Genex requires 3 function definitions: `encoding/0`, `fitness_function/1`, and `terminate?/1`. Let's take a closer look at each of these:
43
46
44
- ## Encoding
47
+ ### Encoding
45
48
```
46
49
def encoding do
47
50
for _ <- 1..15, do: Enum.random(0..1)
 
@@ -50,7 +53,7 @@ defmodule Genex do
50
53
51
54
`encoding/0` defines your encoding of the chromosome's genes for your use-case. In the example above, we define a Binary Gene Set of length 15. Genex uses this function to generate an initial population of Chromosomes matching your encoding.
52
55
53
- ## Fitness Function
56
+ ### Fitness Function
54
57
```
55
58
def fitness_function(chromosome) do
56
59
Enum.sum(chromosome.genes)
 
@@ -59,7 +62,7 @@ defmodule Genex do
59
62
60
63
`fitness_function/1` defines how the algorithm evaluates the fitness of a chromosome. It takes in a chromosome struct and returns a number. Fitness is how your algorithm determines which chromosomes should be persisted to the next generation as well as which chromosomes should be selected to crossover. In this case, we want to maximize 1's in our set of genes, so we define fitness as the sum of genes.
61
64
62
- ## Termination Criteria
65
+ ### Termination Criteria
63
66
```
64
67
def terminate?(population) do
65
68
population.max_fitness == 15
 
@@ -68,7 +71,7 @@ defmodule Genex do
68
71
69
72
`terminate?/1` defines the termination criteria for your algorithm. This tells Genex when your algorithm should stop running. In this case we use a Max Fitness; however, you can also tell the algorithm to stop after a certain number of generations.
70
73
71
- # Running
74
+ ## Running
72
75
73
76
Once you have defined an implementation module. Utilize the `run/0` function to run the algorithm. The function will return the solution population for analysis. You can display a summary of the solution with the: `Genex.Visualizers.Text.display_summary/1` function.
74
77
 
@@ -77,39 +80,13 @@ defmodule Genex do
77
80
Genex.Visualizers.Text.display_summary(soln)
78
81
```
79
82
80
- # Configuration
83
+ ## Configuration
81
84
82
- Genex offers a number of settings to adjust the algorithm to your liking. You can adjust: strategies, rates, and more. Below is a comprehensive list of settings and options.
85
+ Genex offers a number of settings to adjust the algorithm to your liking. You can adjust: strategies, rates, and more. See the [configuration](https://hexdocs.pm/genex/introduction-configuration.html) guide for a full list.
83
86
84
- ## Strategies
85
- - `:crossover_type`- `:single_point`, `:two_point`, `:uniform`, `:blend`
86
- - `:mutation_type`- `:scramble`, `:invert`, `:bit_flip`
87
- - `:parent_selection`- `:natural`, `:random`, `:worst`
88
- - `:survivor_selection`- `:natural`, `:random`, `:worst`
87
+ ## Customization
89
88
90
- ## Rates
91
- - `:crossover_rate`- between 0 and 1
92
- - `:mutation_rate`- between 0 and 1
93
-
94
- ## Population
95
- - `:population_size`- `integer` greater than 0
96
-
97
- ## Unique to some Strategies
98
- - `:uniform_crossover_rate`- between 0 and 1 (Uniform Crossover)
99
- - `:alpha`- between 0 and 1 (Blend Crossover)
100
-
101
- # Customization
102
-
103
- You can customize every step of your genetic algorithm utilizing some of the many callbacks Genex provides. A list of callbacks is provided below.
104
-
105
- - `seed/0` - Seed the Population.
106
- - `evaluate/1` - Evaluate the entire Population.
107
- - `cycle/1` - The Genetic Algorithm Cycle.
108
- - `select_parents/1` - Select Parents for Crossover.
109
- - `crossover/1` - Perform Crossover.
110
- - `mutate/1` - Perform Mutation.
111
- - `select_survivors/1` - Select a number of chromosomes to survive.
112
- - `advance/1` - Advance to the next generation.
89
+ You can customize every step of your genetic algorithm utilizing one or more of the many callbacks Genex provides. See the [customization](https://hexdocs.pm/genex/introduction-customization.html) guide for more.
113
90
"""
114
91
115
92
@doc """
 
@@ -148,43 +125,40 @@ defmodule Genex do
148
125
@callback mutation_rate(Population.t()) :: number()
149
126
150
127
@doc """
151
- Radiation level is affects the "aggressiveness" of mutations.
128
+ Radiation level (aggressiveness) of mutation as a function of the population.
152
129
"""
153
130
@callback radiation(Population.t()) :: number()
154
131
155
132
@doc """
156
- Selects a number of individuals for crossover.
157
- The number of individuals selected depends on the crossover rate. This phase populates the `parent` field of the population struct with a `List` of tuples. Each tuple is a pair of parents to crossover.
133
+ Selects a number of individuals for crossover, depending on the `crossover_rate`.
158
134
"""
159
- @callback select_parents(population :: Population.t()) :: {:ok, Population.t()} | {:error, any()}
135
+ @callback select_parents(population :: Population.t()) ::
136
+ {:ok, Population.t()} | {:error, any()}
160
137
161
138
@doc """
162
139
Crossover a number of individuals to create a new population.
163
- The number of individuals depends on the crossover rate. This phase populates the `children` field of the populaton struct with a `List` of `Chromosomes`.
164
140
"""
165
141
@callback crossover(population :: Population.t()) :: {:ok, Population.t()} | {:error, any()}
166
142
167
143
@doc """
168
- Mutate a number of individuals to add novelty to the population.
169
- The number of individuals depends on the mutation rate. This phase populates the `mutant` field of the population struct with a `List` of `Chromosomes`.
144
+ Mutate a number of individuals, depending on the `mutation_rate`.
170
145
"""
171
146
@callback mutate(population :: Population.t()) :: {:ok, Population.t()} | {:error, any()}
172
147
173
148
@doc """
174
- Select a number of individuals to survive to the next generation.
175
- The number of individuals depends on the survival rate. This phase populates the `survivors` field of the population struct with a `List` of `Chromosomes`.
149
+ Select a number of individuals to survive to the next generation, depending on the `crossover_rate`.s
176
150
"""
177
- @callback select_survivors(population :: Population.t()) :: {:ok, Population.t()} | {:error, any()}
151
+ @callback select_survivors(population :: Population.t()) ::
152
+ {:ok, Population.t()} | {:error, any()}
178
153
179
154
@doc """
180
155
Tests the population for some termination criteria.
181
156
"""
182
- @callback terminate?(population :: Population.t) :: boolean()
157
+ @callback terminate?(population :: Population.t()) :: boolean()
183
158
184
159
defmacro __using__(opts \\ []) do
185
160
# Population
186
161
population_size = Keyword.get(opts, :population_size, 100)
187
- track_history? = Keyword.get(opts, :track_history?, false)
188
162
189
163
# Strategies
190
164
parent_selection_type = Keyword.get(opts, :parent_selection, :natural)
 
@@ -205,10 +179,10 @@ defmodule Genex do
205
179
206
180
alias Genex.Chromosome
207
181
alias Genex.Population
182
+ alias Genex.Support.Statistics
208
183
209
184
# Population Characteristics
210
185
@population_size unquote(population_size)
211
- @track_history? unquote(track_history?)
212
186
213
187
# Strategies
214
188
@crossover_type unquote(crossover_type)
 
@@ -273,13 +247,18 @@ defmodule Genex do
273
247
@spec seed :: {:ok, Chromosome.t()}
274
248
def seed do
275
249
history = Genealogy.init()
250
+
276
251
chromosomes =
277
252
for n <- 1..@population_size do
278
253
g = encoding()
279
254
c = %Chromosome{genes: g, size: length(g)}
280
- if @track_history?, do: Genealogy.update(history, c)
281
255
c
282
256
end
257
+
258
+ history =
259
+ history
260
+ |> Genealogy.add_generation(chromosomes)
261
+
283
262
pop = %Population{chromosomes: chromosomes, size: @population_size, history: history}
284
263
{:ok, pop}
285
264
end
 
@@ -299,8 +278,16 @@ defmodule Genex do
299
278
chromosomes =
300
279
population.chromosomes
301
280
|> Enum.map(fn c -> %Chromosome{c | fitness: fitness_function(c)} end)
281
+
302
282
strongest = Enum.max_by(chromosomes, &Chromosome.get_fitness/1)
303
- pop = %Population{population | chromosomes: chromosomes, strongest: strongest, max_fitness: strongest.fitness}
283
+
284
+ pop = %Population{
285
+ population
286
+ | chromosomes: chromosomes,
287
+ strongest: strongest,
288
+ max_fitness: strongest.fitness
289
+ }
290
+
304
291
{:ok, pop}
305
292
end
306
293
 
@@ -347,6 +334,7 @@ defmodule Genex do
347
334
{:ok, population}
348
335
else
349
336
Text.display_summary(population)
337
+
350
338
with {:ok, population} <- select_parents(population),
351
339
{:ok, population} <- crossover(population),
352
340
{:ok, population} <- mutate(population),
 
@@ -354,7 +342,7 @@ defmodule Genex do
354
342
{:ok, population} <- advance(population),
355
343
{:ok, population} <- evaluate(population),
356
344
{:ok, population} <- do_statistics(population) do
357
- cycle(population)
345
+ cycle(population)
358
346
else
359
347
{:error, reason} -> raise reason
360
348
end
 
@@ -374,13 +362,33 @@ defmodule Genex do
374
362
@spec select_parents(Population.t()) :: {:ok, Population.t()} | {:error, String.t()}
375
363
def select_parents(population) do
376
364
case @parent_selection_type do
377
- :natural -> do_parent_selection(population, crossover_rate(population), &Selection.natural/2, [])
378
- :worst -> do_parent_selection(population, crossover_rate(population), &Selection.worst/2, [])
379
- :random -> do_parent_selection(population, crossover_rate(population), &Selection.random/2, [])
380
- :roulette -> do_parent_selection(population, crossover_rate(population), &Selection.roulette/2, [])
381
- :tournament -> do_parent_selection(population, crossover_rate(population), &Selection.tournament/3, [@tournsize])
382
- :stochastic -> do_parent_selection(population, crossover_rate(population), &Selection.stochastic_universal_sampling/2, [])
383
- _ -> {:error, "Invalid Selection Type"}
365
+ :natural ->
366
+ do_parent_selection(population, crossover_rate(population), &Selection.natural/2, [])
367
+
368
+ :worst ->
369
+ do_parent_selection(population, crossover_rate(population), &Selection.worst/2, [])
370
+
371
+ :random ->
372
+ do_parent_selection(population, crossover_rate(population), &Selection.random/2, [])
373
+
374
+ :roulette ->
375
+ do_parent_selection(population, crossover_rate(population), &Selection.roulette/2, [])
376
+
377
+ :tournament ->
378
+ do_parent_selection(population, crossover_rate(population), &Selection.tournament/3, [
379
+ @tournsize
380
+ ])
381
+
382
+ :stochastic ->
383
+ do_parent_selection(
384
+ population,
385
+ crossover_rate(population),
386
+ &Selection.stochastic_universal_sampling/2,
387
+ []
388
+ )
389
+
390
+ _ ->
391
+ {:error, "Invalid Selection Type"}
384
392
end
385
393
end
386
394
 
@@ -397,14 +405,14 @@ defmodule Genex do
397
405
@spec crossover(Population.t()) :: {:ok, Population.t()} | {:error, String.t()}
398
406
def crossover(population) do
399
407
case @crossover_type do
400
- :single_point -> do_crossover(population, &Crossover.single_point/2, [])
401
- :two_point -> do_crossover(population, &Crossover.two_point/2, [])
402
- :uniform -> do_crossover(population, &Crossover.uniform/3, [@uniform_crossover_rate])
403
- :blend -> do_crossover(population, &Crossover.blend/3, [@alpha])
404
- :simulated_binary -> do_crossover(population, &Crossover.simulated_binary/3, [@eta])
408
+ :single_point -> do_crossover(population, &Crossover.single_point/2, [])
409
+ :two_point -> do_crossover(population, &Crossover.two_point/2, [])
410
+ :uniform -> do_crossover(population, &Crossover.uniform/3, [@uniform_crossover_rate])
411
+ :blend -> do_crossover(population, &Crossover.blend/3, [@alpha])
412
+ :simulated_binary -> do_crossover(population, &Crossover.simulated_binary/3, [@eta])
405
413
:messy_single_point -> do_crossover(population, &Crossover.messy_single_point/2, [])
406
- :davis_order -> do_crossover(population, &Crossover.davis_order/2, [])
407
- _ -> {:error, "Invalid Crossover Type."}
414
+ :davis_order -> do_crossover(population, &Crossover.davis_order/2, [])
415
+ _ -> {:error, "Invalid Crossover Type."}
408
416
end
409
417
end
410
418
 
@@ -421,14 +429,38 @@ defmodule Genex do
421
429
@spec mutate(Population.t()) :: {:ok, Population.t()} | {:error, String.t()}
422
430
def mutate(population) do
423
431
case @mutation_type do
424
- :bit_flip -> do_mutation(population, &Mutation.bit_flip/2, [radiation(population)])
425
- :scramble -> do_mutation(population, &Mutation.scramble/2, [radiation(population)])
426
- :invert -> do_mutation(population, &Mutation.invert/2, [radiation(population)])
427
- :uniform_integer -> do_mutation(population, &Mutation.uniform_integer/4, [radiation(population), @min, @max])
428
- :gaussian -> do_mutation(population, &Mutation.gaussian/2, [radiation(population)])
429
- :polynomial_bounded -> do_mutation(population, &Mutation.polynomial_bounded/5, [radiation(population), @eta, @min, @max])
430
- :none -> {:ok, population}
431
- _ -> {:error, "Invalid Mutation Type."}
432
+ :bit_flip ->
433
+ do_mutation(population, &Mutation.bit_flip/2, [radiation(population)])
434
+
435
+ :scramble ->
436
+ do_mutation(population, &Mutation.scramble/2, [radiation(population)])
437
+
438
+ :invert ->
439
+ do_mutation(population, &Mutation.invert/2, [radiation(population)])
440
+
441
+ :uniform_integer ->
442
+ do_mutation(population, &Mutation.uniform_integer/4, [
443
+ radiation(population),
444
+ @min,
445
+ @max
446
+ ])
447
+
448
+ :gaussian ->
449
+ do_mutation(population, &Mutation.gaussian/2, [radiation(population)])
450
+
451
+ :polynomial_bounded ->
452
+ do_mutation(population, &Mutation.polynomial_bounded/5, [
453
+ radiation(population),
454
+ @eta,
455
+ @min,
456
+ @max
457
+ ])
458
+
459
+ :none ->
460
+ {:ok, population}
461
+
462
+ _ ->
463
+ {:error, "Invalid Mutation Type."}
432
464
end
433
465
end
434
466
 
@@ -445,13 +477,26 @@ defmodule Genex do
445
477
@spec select_survivors(Population.t()) :: {:ok, Population.t()} | {:error, String.t()}
446
478
def select_survivors(population) do
447
479
case @survivor_selection_type do
448
- :natural -> do_survivor_selection(population, &Selection.natural/2, [])
449
- :worst -> do_survivor_selection(population, &Selection.worst/2, [])
450
- :random -> do_survivor_selection(population, &Selection.random/2, [])
451
- :roulette -> do_survivor_selection(population, &Selection.roulette/2, [])
452
- :tournament -> do_survivor_selection(population, &Selection.tournament/3, [@tournsize])
453
- :stochastic -> do_survivor_selection(population, &Selection.stochastic_universal_sampling/2, [])
454
- _ -> {:error, "Invalid Selection Type"}
480
+ :natural ->
481
+ do_survivor_selection(population, &Selection.natural/2, [])
482
+
483
+ :worst ->
484
+ do_survivor_selection(population, &Selection.worst/2, [])
485
+
486
+ :random ->
487
+ do_survivor_selection(population, &Selection.random/2, [])
488
+
489
+ :roulette ->
490
+ do_survivor_selection(population, &Selection.roulette/2, [])
491
+
492
+ :tournament ->
493
+ do_survivor_selection(population, &Selection.tournament/3, [@tournsize])
494
+
495
+ :stochastic ->
496
+ do_survivor_selection(population, &Selection.stochastic_universal_sampling/2, [])
497
+
498
+ _ ->
499
+ {:error, "Invalid Selection Type"}
455
500
end
456
501
end
457
502
 
@@ -467,13 +512,25 @@ defmodule Genex do
467
512
"""
468
513
@spec advance(Population.t()) :: {:ok, Population.t()}
469
514
def advance(population) do
470
- generation = population.generation+1
515
+ generation = population.generation + 1
516
+
471
517
survivors =
472
518
population.survivors
473
- |> Enum.map(fn c -> %Chromosome{c | age: c.age+1} end)
519
+ |> Enum.map(fn c -> %Chromosome{c | age: c.age + 1} end)
520
+
474
521
chromosomes = survivors ++ population.children
475
522
size = length(chromosomes)
476
- pop = %Population{population | size: size, chromosomes: chromosomes, generation: generation, children: nil, parents: nil, survivors: nil}
523
+
524
+ pop = %Population{
525
+ population
526
+ | size: size,
527
+ chromosomes: chromosomes,
528
+ generation: generation,
529
+ children: nil,
530
+ parents: nil,
531
+ survivors: nil
532
+ }
533
+
477
534
{:ok, pop}
478
535
end
479
536
 
@@ -487,11 +544,12 @@ defmodule Genex do
487
544
@spec run :: Population.t()
488
545
def run do
489
546
Text.init()
547
+
490
548
with {:ok, population} <- seed(),
491
549
{:ok, population} <- evaluate(population),
492
550
{:ok, population} <- cycle(population) do
493
- soln = Population.sort(population)
494
- soln
551
+ soln = Population.sort(population)
552
+ soln
495
553
else
496
554
{:error, reason} -> raise reason
497
555
end
 
@@ -515,6 +573,7 @@ defmodule Genex do
515
573
{:ok, survivor_pop} = select_survivors(mutated_pop)
516
574
genes = encoding()
517
575
c = %Chromosome{genes: genes, size: length(genes)}
576
+
518
577
Benchee.run(%{
519
578
"encoding/0" => fn -> encoding() end,
520
579
"seed/0" => fn -> seed() end,
 
@@ -526,57 +585,66 @@ defmodule Genex do
526
585
"mutate/1" => fn -> mutate(child_pop) end,
527
586
"select_survivors/1" => fn -> select_survivors(mutated_pop) end
528
587
})
588
+
529
589
:ok
530
590
end
531
591
532
592
defp do_crossover(population, f, args) do
533
593
parents = population.parents
534
- children =
594
+
595
+ {children, history} =
535
596
parents
536
- |> Enum.map(
537
- fn {p1, p2} ->
597
+ |> Enum.reduce(
598
+ {[], population.history},
599
+ fn {p1, p2}, {chd, his} ->
538
600
{c1, c2} = apply(f, [p1, p2] ++ args)
539
- if @track_history? do
540
- Genealogy.update(population.history, c1, p1, p2)
541
- Genealogy.update(population.history, c2, p1, p2)
542
- end
543
- [c1, c2]
601
+
602
+ newHis =
603
+ his
604
+ |> Genealogy.update(c1, p1, p2)
605
+ |> Genealogy.update(c1, p1, p2)
606
+
607
+ {[c1 | [c2 | chd]], newHis}
544
608
end
545
609
)
546
- |> List.flatten
547
- pop = %Population{population | children: children}
610
+
611
+ pop = %Population{population | children: children, history: history}
548
612
{:ok, pop}
549
613
end
550
614
551
615
defp do_mutation(population, f, args) do
552
616
u = mutation_rate(population)
617
+
553
618
chromosomes =
554
619
population.chromosomes
555
- |> Enum.map(
556
- fn c ->
557
- if :rand.uniform() < u do
558
- apply(f, [c] ++ args)
559
- else
560
- c
561
- end
620
+ |> Enum.map(fn c ->
621
+ if :rand.uniform() < u do
622
+ apply(f, [c] ++ args)
623
+ else
624
+ c
562
625
end
563
- )
564
- pop = %Population{population | chromosomes: chromosomes}
565
- {:ok, pop}
626
+ end)
627
+
628
+ pop = %Population{population | chromosomes: chromosomes}
629
+ {:ok, pop}
566
630
end
567
631
568
- defp do_parent_selection(population, rate, f, args) when is_float(rate) and rate >= 0.0 and rate <= 1.0 do
632
+ defp do_parent_selection(population, rate, f, args)
633
+ when is_float(rate) and rate >= 0.0 and rate <= 1.0 do
569
634
chromosomes = population.chromosomes
570
635
n = floor(rate * length(chromosomes))
636
+
571
637
parents =
572
638
f
573
639
|> apply([chromosomes, n] ++ args)
574
640
|> Enum.chunk_every(2, 2, :discard)
575
641
|> Enum.map(fn f -> List.to_tuple(f) end)
642
+
576
643
pop = %Population{population | parents: parents}
577
644
{:ok, pop}
578
645
end
579
- defp do_parent_selection(_, _, _, _), do: raise "Invalid crossover rate!"
646
+
647
+ defp do_parent_selection(_, _, _, _), do: raise("Invalid crossover rate!")
580
648
581
649
defp do_survivor_selection(population, f, args) do
582
650
chromosomes = population.chromosomes
 
@@ -589,33 +657,31 @@ defmodule Genex do
589
657
defp do_statistics(population) do
590
658
stats =
591
659
statistics()
592
- |> Enum.map(
593
- fn {k, v} ->
594
- val =
595
- population.chromosomes
596
- |> Enum.map(fn c -> c.fitness end)
597
- |> v.()
598
- {:"#{k}", val}
599
- end
600
- )
660
+ |> Enum.map(fn {k, v} ->
661
+ val =
662
+ population.chromosomes
663
+ |> Enum.map(fn c -> c.fitness end)
664
+ |> v.()
665
+
666
+ {:"#{k}", val}
667
+ end)
668
+
601
669
pop = %Population{population | statistics: stats}
602
670
{:ok, pop}
603
671
end
604
672
605
- defoverridable [
606
- select_parents: 1,
607
- crossover: 1,
608
- mutate: 1,
609
- select_survivors: 1,
610
- seed: 0,
611
- evaluate: 1,
612
- advance: 1,
613
- cycle: 1,
614
- statistics: 0
615
- ]
673
+ defoverridable select_parents: 1,
674
+ crossover: 1,
675
+ mutate: 1,
676
+ select_survivors: 1,
677
+ seed: 0,
678
+ evaluate: 1,
679
+ advance: 1,
680
+ cycle: 1,
681
+ statistics: 0,
682
+ benchmark: 0
616
683
end
617
684
end
618
685
619
686
defguard valid_rate?(rate) when is_float(rate) and rate >= 0.0 and rate <= 1.0
620
-
621
687
end
changed lib/genex/chromosome.ex
 
@@ -1,5 +1,6 @@
1
1
defmodule Genex.Chromosome do
2
2
alias __MODULE__, as: Chromosome
3
+
3
4
@moduledoc """
4
5
A collection of genes.
5
6
 
@@ -7,11 +8,11 @@ defmodule Genex.Chromosome do
7
8
"""
8
9
9
10
@type t :: %Chromosome{
10
- genes: Enum.t,
11
- fitness: number(),
12
- size: integer(),
13
- age: integer()
14
- }
11
+ genes: Enum.t(),
12
+ fitness: number(),
13
+ size: integer(),
14
+ age: integer()
15
+ }
15
16
16
17
@enforce_keys [:genes]
17
18
defstruct [:genes, fitness: 0, size: 0, age: 0]
changed lib/genex/operators/crossover.ex
 
@@ -1,5 +1,6 @@
1
1
defmodule Genex.Operators.Crossover do
2
2
alias Genex.Chromosome
3
+
3
4
@moduledoc """
4
5
Implementation of several popular crossover methods.
5
6
 
@@ -13,55 +14,150 @@ defmodule Genex.Operators.Crossover do
13
14
"""
14
15
15
16
@doc """
16
- Performs single point crossover.
17
+ Performs single point crossover at a random point.
17
18
18
- This will swap a slice of genes from each chromosome, producing 2 new chromosomes.
19
+ This will swap a random slice of genes from each chromosome, producing 2 new chromosomes.
19
20
20
- Returns `%Chromosome{}`.
21
+ Returns `{%Chromosome{}, %Chromosome{}}`.
21
22
22
23
# Parameters
23
- - `p1`: Parent One.
24
- - `p2`: Parent Two.
24
+ - `p1`: Parent one.
25
+ - `p2`: Parent two.
25
26
"""
26
27
@spec single_point(Chromosome.t(), Chromosome.t()) :: {Chromosome.t(), Chromosome.t()}
27
28
def single_point(p1, p2) do
28
29
chromosome_length = p1.size
29
- point = floor(chromosome_length * :rand.uniform())
30
- {g1, g2} = Enum.split(p1.genes, point)
31
- {g3, g4} = Enum.split(p2.genes, point)
32
- {c1, c2} = {g1 ++ g4, g3 ++ g2}
33
- {%Chromosome{genes: c1, size: p1.size}, %Chromosome{genes: c2, size: p1.size}}
30
+ point = :rand.uniform(chromosome_length)
31
+ single_point(p1, p2, point)
34
32
end
35
33
36
34
@doc """
37
- Performs two-point crossover.
35
+ Performs single point crossover at a known point.
38
36
39
- This will swap multiple slices of genes from each chromosome, producing 2 new chromosomes.
37
+ This will swap a known slice of genes from each chromosome, producing 2 new chromosomes.
38
+
39
+ Returns `{%Chromosome{}, %Chromosome{}}`.
40
+
41
+ # Parameters
42
+ - `p1`: Parent one.
43
+ - `p2`: Parent two.
44
+ - `point`: Slice to swap.
45
+
46
+ # Examples
47
+
48
+ iex> c1 = %#{Chromosome}{genes: [1, 2, 3, 4, 5], size: 5}
49
+ iex> c2 = %#{Chromosome}{genes: [6, 7, 8, 9, 10], size: 5}
50
+ iex> single_point(c1, c2, 2)
51
+ {%#{Chromosome}{genes: [1, 2, 8, 9, 10], size: 5}, %#{Chromosome}{genes: [6, 7, 3, 4, 5], size: 5}}
52
+
53
+ iex> c1 = %#{Chromosome}{genes: [1, 2, 3, 4, 5], size: 5}
54
+ iex> c2 = %#{Chromosome}{genes: [6, 7, 8, 9, 10], size: 5}
55
+ iex> single_point(c1, c2, 10)
56
+ {%#{Chromosome}{genes: [1, 2, 3, 4, 5], size: 5}, %#{Chromosome}{genes: [6, 7, 8, 9, 10], size: 5}}
57
+
58
+ iex> c1 = %#{Chromosome}{genes: [1, 2, 3, 4, 5], size: 5}
59
+ iex> c2 = %#{Chromosome}{genes: [6, 7, 8, 9, 10], size: 5}
60
+ iex> single_point(c1, c2, 0)
61
+ {%#{Chromosome}{genes: [1, 2, 3, 4, 5], size: 5}, %#{Chromosome}{genes: [6, 7, 8, 9, 10], size: 5}}
62
+
63
+ iex> c1 = %#{Chromosome}{genes: [1, 2, 3, 4, 5], size: 5}
64
+ iex> c2 = %#{Chromosome}{genes: [6, 7, 8, 9, 10], size: 5}
65
+ iex> single_point(c1, c2, -5)
66
+ {%#{Chromosome}{genes: [1, 2, 3, 4, 5], size: 5}, %#{Chromosome}{genes: [6, 7, 8, 9, 10], size: 5}}
67
+ """
68
+ @spec single_point(Chromosome.t(), Chromosome.t()) :: {Chromosome.t(), Chromosome.t()}
69
+ def single_point(p1, p2, point) when point <= 0,
70
+ do: {%Chromosome{genes: p1.genes, size: p1.size}, %Chromosome{genes: p2.genes, size: p2.size}}
71
+
72
+ def single_point(p1, p2, point) do
73
+ {g1, g2} = Enum.split(p1.genes, point)
74
+ {g3, g4} = Enum.split(p2.genes, point)
75
+ {c1, c2} = {g1 ++ g4, g3 ++ g2}
76
+ {%Chromosome{genes: c1, size: length(c1)}, %Chromosome{genes: c2, size: length(c2)}}
77
+ end
78
+
79
+ @doc """
80
+ Performs two-point crossover at a random point.
81
+
82
+ This will swap two random slices of genes from each chromosome, producing 2 new chromosomes.
40
83
41
84
Returns `%Chromosome{}`.
42
85
43
86
# Parameters
44
- - `p1`: Parent One.
45
- - `p2`: Parent Two.
87
+ - `p1`: Parent one.
88
+ - `p2`: Parent two.
46
89
"""
47
- @spec two_point(Chromosome.t(), Chromosome.t()) :: Chromosome.t()
90
+ @spec two_point(Chromosome.t(), Chromosome.t()) :: {Chromosome.t(), Chromosome.t()}
48
91
def two_point(p1, p2) do
49
92
chromosome_length = p1.size
50
- a = :rand.uniform(chromosome_length-1)
51
- b = :rand.uniform(chromosome_length-2)
52
- point1 = if b >= a do a else b end
53
- point2 = if b >= a do b+1 else a end
93
+ a = :rand.uniform(chromosome_length - 1)
94
+ b = :rand.uniform(chromosome_length - 2)
95
+
96
+ point1 =
97
+ if b >= a do
98
+ a
99
+ else
100
+ b
101
+ end
102
+
103
+ point2 =
104
+ if b >= a do
105
+ b + 1
106
+ else
107
+ a
108
+ end
109
+
54
110
# Split
55
- {slice1, rem1} = Enum.split(p1.genes, point1)
56
- {slice2, rem2} = Enum.split(p2.genes, point1)
57
- {slice3, rem3} = Enum.split(rem1, point2-point1)
58
- {slice4, rem4} = Enum.split(rem2, point2-point1)
59
- {c1, c2} =
60
- {
61
- slice1 ++ slice4 ++ rem3,
62
- slice2 ++ slice3 ++ rem4
63
- }
64
- {%Chromosome{genes: c1, size: p1.size}, %Chromosome{genes: c2, size: p1.size}}
111
+ two_point(p1, p2, point1, point2)
112
+ end
113
+
114
+ @doc """
115
+ Performs two-point crossover at a known point.
116
+
117
+ This will swap a known slice of genes from each chromosome, producing 2 new chromosomes.
118
+
119
+ Returns `{%Chromosome{}, %Chromosome{}}`.
120
+
121
+ # Parameters
122
+ - `p1`: Parent one.
123
+ - `p2`: Parent two.
124
+ - `first`: First Split Point.
125
+ - `second`: Second Split Point.
126
+
127
+ # Examples
128
+
129
+ iex> c1 = %#{Chromosome}{genes: [1, 2, 3, 4, 5], size: 5}
130
+ iex> c2 = %#{Chromosome}{genes: [6, 7, 8, 9, 10], size: 5}
131
+ iex> two_point(c1, c2, 1, 3)
132
+ {%#{Chromosome}{genes: [1, 7, 8, 4, 5], size: 5}, %#{Chromosome}{genes: [6, 2, 3, 9, 10], size: 5}}
133
+
134
+ iex> c1 = %#{Chromosome}{genes: [1, 2, 3, 4, 5], size: 5}
135
+ iex> c2 = %#{Chromosome}{genes: [6, 7, 8, 9, 10], size: 5}
136
+ iex> two_point(c1, c2, 10, 3)
137
+ {%#{Chromosome}{genes: [1, 2, 3, 4, 5], size: 5}, %#{Chromosome}{genes: [6, 7, 8, 9, 10], size: 5}}
138
+
139
+ iex> c1 = %#{Chromosome}{genes: [1, 2, 3, 4, 5], size: 5}
140
+ iex> c2 = %#{Chromosome}{genes: [6, 7, 8, 9, 10], size: 5}
141
+ iex> two_point(c1, c2, -3, 3)
142
+ {%#{Chromosome}{genes: [1, 2, 3, 4, 5], size: 5}, %#{Chromosome}{genes: [6, 7, 8, 9, 10], size: 5}}
143
+ """
144
+ @spec two_point(Chromosome.t(), Chromosome.t(), integer(), integer()) ::
145
+ {Chromosome.t(), Chromosome.t()}
146
+ def two_point(p1, p2, first, second) when first < 0 or second < 0,
147
+ do: {%Chromosome{genes: p1.genes, size: p1.size}, %Chromosome{genes: p2.genes, size: p2.size}}
148
+
149
+ def two_point(p1, p2, first, second) do
150
+ {slice1, rem1} = Enum.split(p1.genes, first)
151
+ {slice2, rem2} = Enum.split(p2.genes, first)
152
+ {slice3, rem3} = Enum.split(rem1, second - first)
153
+ {slice4, rem4} = Enum.split(rem2, second - first)
154
+
155
+ {c1, c2} = {
156
+ slice1 ++ slice4 ++ rem3,
157
+ slice2 ++ slice3 ++ rem4
158
+ }
159
+
160
+ {%Chromosome{genes: c1, size: length(c1)}, %Chromosome{genes: c2, size: length(c2)}}
65
161
end
66
162
67
163
@doc """
 
@@ -72,8 +168,8 @@ defmodule Genex.Operators.Crossover do
72
168
Returns `Chromosome`.
73
169
74
170
# Parameters
75
- - `p1`: Parent One.
76
- - `p2`: Parent Two.
171
+ - `p1`: Parent one.
172
+ - `p2`: Parent two.
77
173
- `rate`: `Float` between 0 and 1 representing rates to swap genes.
78
174
"""
79
175
@spec uniform(Chromosome.t(), Chromosome.t(), float()) :: {Chromosome.t(), Chromosome.t()}
 
@@ -81,109 +177,161 @@ defmodule Genex.Operators.Crossover do
81
177
{c1, c2} =
82
178
p1.genes
83
179
|> Enum.zip(p2.genes)
84
- |> Enum.map(fn {x, y} -> if :rand.uniform < rate do {x, y} else {y, x} end end)
85
- |> Enum.unzip
180
+ |> Enum.map(fn {x, y} ->
181
+ if :rand.uniform() < rate do
182
+ {x, y}
183
+ else
184
+ {y, x}
185
+ end
186
+ end)
187
+ |> Enum.unzip()
188
+
86
189
{%Chromosome{genes: c1, size: p1.size}, %Chromosome{genes: c2, size: p1.size}}
87
190
end
88
191
89
192
@doc """
90
193
Performs a blend crossover.
91
194
92
- This will blend genes according to some alpha between 0 and 1. If alpha=.5, the resulting chromosomes will be identical to one another.
195
+ This will blend genes according to some alpha between 0 and 1.
93
196
94
- Returns `Chromosome`.
197
+ Returns `{%Chromosome{}, %Chromosome{}}`.
95
198
96
199
# Parameters
97
200
- `p1`: Parent one.
98
201
- `p2`: Parent two.
99
202
- `alpha`: `Float` between 0 and 1 representing percentage of each parent to blend into children.
100
203
"""
101
- @spec blend(Chromosome.t(), Chromosome.t(), float()) :: Chromosome.t()
204
+ @spec blend(Chromosome.t(), Chromosome.t(), float()) :: {Chromosome.t(), Chromosome.t()}
102
205
def blend(p1, p2, alpha) do
103
206
{c1, c2} =
104
207
p1.genes
105
208
|> Enum.zip(p2.genes)
106
- |> Enum.map(
107
- fn {x, y} ->
108
- gamma = (1 + 2 * alpha) * :rand.uniform() - alpha
109
- {
110
- (1 - gamma) * x + gamma * y,
111
- gamma * x + (1 - gamma) * y
112
- }
113
- end
114
- )
115
- |> Enum.unzip
209
+ |> Enum.map(fn {x, y} ->
210
+ gamma = (1 + 2 * alpha) * :rand.uniform() - alpha
211
+
212
+ {
213
+ (1 - gamma) * x + gamma * y,
214
+ gamma * x + (1 - gamma) * y
215
+ }
216
+ end)
217
+ |> Enum.unzip()
218
+
116
219
{%Chromosome{genes: c1, size: p1.size}, %Chromosome{genes: c2, size: p1.size}}
117
220
end
118
221
119
222
@doc """
120
223
Performs a simulated binary crossover.
121
224
122
- Returns `Chromosome`.
225
+ Returns `{%Chromosome{}, %Chromosome{}}`.
123
226
124
227
# Parameters
125
228
- `p1`: Parent one.
126
229
- `p2`: Parent two.
127
230
- `eta`: `Float`
128
231
"""
129
- @spec simulated_binary(Chromosome.t(), Chromosome.t(), number()) :: Chromosome.t()
232
+ @spec simulated_binary(Chromosome.t(), Chromosome.t(), number()) ::
233
+ {Chromosome.t(), Chromosome.t()}
130
234
def simulated_binary(p1, p2, eta) do
131
235
{c1, c2} =
132
236
p1.genes
133
237
|> Enum.zip(p2.genes)
134
- |> Enum.map(
135
- fn {x, y} ->
136
- rand = :rand.uniform()
137
- beta = if rand <= 0.5 do 2 * rand else 1/(2*(1-rand)) end
138
- beta = :math.pow(beta, (1/eta+1))
139
- {
140
- 0.5 * (((1 + beta) * x) + ((1 - beta) * y)),
141
- 0.5 * (((1 - beta) * x) + ((1 + beta) * y))
142
- }
238
+ |> Enum.map(fn {x, y} ->
239
+ rand = :rand.uniform()
240
+
241
+ beta =
242
+ if rand <= 0.5 do
243
+ 2 * rand
244
+ else
245
+ 1 / (2 * (1 - rand))
143
246
end
144
- )
145
- |> Enum.unzip
247
+
248
+ beta = :math.pow(beta, 1 / eta + 1)
249
+
250
+ {
251
+ 0.5 * ((1 + beta) * x + (1 - beta) * y),
252
+ 0.5 * ((1 - beta) * x + (1 + beta) * y)
253
+ }
254
+ end)
255
+ |> Enum.unzip()
256
+
146
257
{%Chromosome{genes: c1, size: p1.size}, %Chromosome{genes: c2, size: p1.size}}
147
258
end
148
259
149
260
@doc """
150
- Performs a messy single point crossover.
261
+ Performs a messy single point crossover at random points.
151
262
152
263
This crossover disregards the length of the chromosome and will often arbitrarily increase or decrease it's size.
153
264
154
- Returns `Chromosome`.
265
+ Returns `{%Chromosome{}, %Chromosome{}}`.
155
266
156
267
# Parameters
157
268
- `p1`: Parent one.
158
269
- `p2`: Parent two.
159
270
"""
160
- @spec messy_single_point(Chromosome.t(), Chromosome.t()) :: Chromosome.t()
271
+ @spec messy_single_point(Chromosome.t(), Chromosome.t()) :: {Chromosome.t(), Chromosome.t()}
161
272
def messy_single_point(p1, p2) do
162
273
chromosome_length = length(p1.genes)
163
- point = if chromosome_length == 0 do 0 else :rand.uniform(chromosome_length) end
164
- {c1, c2} = {
165
- Enum.slice(p1.genes, 0..point)
166
- ++ Enum.slice(p2.genes, 0..point),
167
- Enum.slice(p1.genes, point..chromosome_length-1)
168
- ++ Enum.slice(p1.genes, point..chromosome_length-1)
169
- }
274
+ point1 = if chromosome_length == 0, do: 0, else: :rand.uniform(chromosome_length)
275
+ point2 = if chromosome_length == 0, do: 0, else: :rand.uniform(chromosome_length)
276
+ messy_single_point(p1, p2, point1, point2)
277
+ end
278
+
279
+ @doc """
280
+ Performs a messy single point crossover at known points.
281
+
282
+ This crossover disregards the length of the chromosome and will often arbitrarily increase or decrease it's size.
283
+
284
+ Returns `{%Chromosome{}, %Chromosome{}}`.
285
+
286
+ # Parameters
287
+ - `p1`: Parent one.
288
+ - `p2`: Parent two.
289
+ - `point1`: `p1` split point.
290
+ - `point2`: `p2` split point.
291
+
292
+ # Examples
293
+
294
+ iex> c1 = %#{Chromosome}{genes: [1, 2, 3, 4, 5], size: 5}
295
+ iex> c2 = %#{Chromosome}{genes: [6, 7, 8, 9, 10], size: 5}
296
+ iex> messy_single_point(c1, c2, 2, 4)
297
+ {%#{Chromosome}{genes: [1, 2, 10], size: 3}, %#{Chromosome}{genes: [6, 7, 8, 9, 3, 4, 5], size: 7}}
298
+
299
+ iex> c1 = %#{Chromosome}{genes: [1, 2, 3, 4, 5], size: 5}
300
+ iex> c2 = %#{Chromosome}{genes: [6, 7, 8, 9, 10], size: 5}
301
+ iex> messy_single_point(c1, c2, 0, 10)
302
+ {%#{Chromosome}{genes: [], size: 0}, %#{Chromosome}{genes: [6, 7, 8, 9, 10, 1, 2, 3, 4, 5], size: 10}}
303
+
304
+ iex> c1 = %#{Chromosome}{genes: [1, 2, 3, 4, 5], size: 5}
305
+ iex> c2 = %#{Chromosome}{genes: [6, 7, 8, 9, 10], size: 5}
306
+ iex> messy_single_point(c1, c2, -2, 3)
307
+ {%#{Chromosome}{genes: [1, 2, 3, 4, 5], size: 5}, %#{Chromosome}{genes: [6, 7, 8, 9, 10], size: 5}}
308
+ """
309
+ @spec messy_single_point(Chromosome.t(), Chromosome.t(), integer(), integer()) ::
310
+ {Chromosome.t(), Chromosome.t()}
311
+ def messy_single_point(p1, p2, point1, point2) when point1 < 0 or point2 < 0,
312
+ do: {%Chromosome{genes: p1.genes, size: 5}, %Chromosome{genes: p2.genes, size: 5}}
313
+
314
+ def messy_single_point(p1, p2, point1, point2) do
315
+ {g1, g2} = Enum.split(p1.genes, point1)
316
+ {g3, g4} = Enum.split(p2.genes, point2)
317
+ {c1, c2} = {g1 ++ g4, g3 ++ g2}
170
318
{%Chromosome{genes: c1, size: length(c1)}, %Chromosome{genes: c2, size: length(c2)}}
171
319
end
172
320
173
321
@doc """
174
- Performs Davis Order crossover.
322
+ Performs Davis Order crossover of a random slice.
175
323
176
- Returns `Chromosome`.
324
+ Returns `{%Chromosome{}, %Chromosome{}}`.
177
325
178
326
# Parameters
179
327
- `p1`: Parent one.
180
328
- `p2`: Parent two.
181
329
"""
182
- @spec davis_order(Chromosome.t(), Chromosome.t()) :: Chromosome.t()
330
+ @spec davis_order(Chromosome.t(), Chromosome.t()) :: {Chromosome.t(), Chromosome.t()}
183
331
def davis_order(p1, p2) do
184
- {a, b} = {:rand.uniform(p1.size-1), :rand.uniform(p1.size-2)}
332
+ {a, b} = {:rand.uniform(p1.size - 1), :rand.uniform(p1.size - 2)}
185
333
point1 = if b >= a, do: a, else: b
186
- point2 = if b >= a, do: b+1, else: a
334
+ point2 = if b >= a, do: b + 1, else: a
187
335
slice1 = Enum.slice(p1.genes, point1, point2)
188
336
slice2 = Enum.slice(p2.genes, point1, point2)
189
337
rem1 = Enum.filter(p1.genes, fn x -> x not in slice1 end)
changed lib/genex/operators/mutation.ex
 
@@ -2,6 +2,7 @@ defmodule Genex.Operators.Mutation do
2
2
use Bitwise
3
3
alias Genex.Chromosome
4
4
import Genex, only: [valid_rate?: 1]
5
+
5
6
@moduledoc """
6
7
Implementation of several population mutation methods.
7
8
 
@@ -24,54 +25,70 @@ defmodule Genex.Operators.Mutation do
24
25
def bit_flip(chromosome, radiation) when valid_rate?(radiation) do
25
26
genes =
26
27
chromosome.genes
27
- |> Enum.map(
28
- fn x ->
29
- if :rand.uniform > radiation do
30
- 1 ^^^ x
31
- else
32
- x
33
- end
28
+ |> Enum.map(fn x ->
29
+ if :rand.uniform() > radiation do
30
+ 1 ^^^ x
31
+ else
32
+ x
34
33
end
35
- )
34
+ end)
35
+
36
36
%Chromosome{chromosome | genes: genes}
37
37
end
38
- def bit_flip(_, _), do: raise "Invalid radiation level!"
38
+
39
+ def bit_flip(_, _), do: raise(ArgumentError, message: "Invalid radiation level!")
39
40
40
41
@doc """
41
42
Perform a scramble mutation.
42
43
43
- This mutation shuffles the genes of the Chromosome.
44
+ This mutation shuffles the genes of the Chromosome between 2 random points.
44
45
45
- Returns `Chromosome`.
46
+ Returns `%Chromosome{}`.
46
47
47
48
# Parameters
48
- - `chromosome`- `Chromosome` to mutate.
49
+ - `chromosome`: `Chromosome` to mutate.
50
+ - `radiation`: Aggressiveness of the mutation.
49
51
"""
50
52
@spec scramble(Chromosome.t(), float()) :: Chromosome.t()
51
53
def scramble(chromosome, radiation) when valid_rate?(radiation) do
52
- p = floor(chromosome.size * radiation)
53
- genes =
54
- if p == chromosome.size-1 do
55
- chromosome.genes
56
- |> Enum.shuffle()
57
- else
58
- {head, tail} =
59
- chromosome.genes
60
- |> Enum.split(p)
61
- head
62
- |> Enum.shuffle()
63
- |> Kernel.++(tail)
64
- end
65
- %Chromosome{chromosome | genes: genes}
54
+ chromosome_length = chromosome.size
55
+ num_to_scramble = floor(radiation * chromosome_length)
56
+ point = if chromosome_length == 0, do: 0, else: :rand.uniform(chromosome_length)
57
+ scramble(chromosome, point, point + num_to_scramble)
66
58
end
67
- def scramble(_, _), do: raise "Invalid radiation level!"
59
+
60
+ def scramble(_, _), do: raise(ArgumentError, message: "Invalid radiation level!")
68
61
69
62
@doc """
70
- Perform inversion mutation.
63
+ Performs a scramble mutation of a known slice.
71
64
72
- This mutation reverses (inverts) the genes of the Chromosome.
65
+ This mutation shuffles the genes of a Chromosome between 2 known points.
73
66
74
- Returns `Chromosome`.
67
+ # Parameters
68
+ - `chromosome`: `Chromosome` to mutate.
69
+ - `start`: First scramble point.
70
+ - `finish`: Second scramble point.
71
+
72
+ # Examples
73
+
74
+ iex> c1 = %#{Chromosome}{genes: [1, 2, 3, 4, 5], size: 5}
75
+ iex> scramble(c1, 10, 4)
76
+ %#{Chromosome}{genes: [1, 2, 3, 4, 5], size: 5}
77
+ """
78
+ @spec scramble(Chromosome.t(), integer(), integer()) :: Chromosome.t()
79
+ def scramble(chromosome, start, finish) do
80
+ {head, slice} = Enum.split(chromosome.genes, start)
81
+ {slice, tail} = Enum.split(slice, finish - start)
82
+ genes = head ++ Enum.shuffle(slice) ++ tail
83
+ %Chromosome{chromosome | genes: genes}
84
+ end
85
+
86
+ @doc """
87
+ Perform inversion mutation on a random slice.
88
+
89
+ This mutation reverses (inverts) a random slice of genes of the Chromosome.
90
+
91
+ Returns `%Chromosome{}`.
75
92
76
93
# Parameters
77
94
- `chromosome`- `Chromosome` to mutate.
 
@@ -79,22 +96,47 @@ defmodule Genex.Operators.Mutation do
79
96
"""
80
97
@spec invert(Chromosome.t(), float()) :: Chromosome.t()
81
98
def invert(chromosome, radiation) when valid_rate?(radiation) do
82
- p = floor(chromosome.size * radiation)
83
- genes =
84
- if p == chromosome.size-1 do
85
- chromosome.genes
86
- |> Enum.reverse()
87
- else
88
- {head, tail} =
89
- chromosome.genes
90
- |> Enum.split(p)
91
- head
92
- |> Enum.reverse()
93
- |> Kernel.++(tail)
94
- end
99
+ chromosome_length = chromosome.size
100
+ point = if chromosome_length == 0, do: 0, else: :rand.uniform(chromosome_length)
101
+ num_to_invert = floor(radiation * chromosome_length)
102
+ invert(chromosome, point, point + num_to_invert)
103
+ end
104
+
105
+ def invert(_, _), do: raise(ArgumentError, message: "Invalid radiation level!")
106
+
107
+ @doc """
108
+ Perform inversion mutation on a known slice.
109
+
110
+ This mutation reverses (inverts) a known slice of genes of the Chromosome.
111
+
112
+ Returns `%Chromosome{}`.
113
+
114
+ # Parameters
115
+ - `chromosome`: `Chromosome` to mutate.
116
+ - `start`: Start of slice.
117
+ - `finish`: End of slice.
118
+
119
+ # Examples
120
+
121
+ iex> c1 = %#{Chromosome}{genes: [1, 2, 3, 4, 5], size: 5}
122
+ iex> invert(c1, 2, 4)
123
+ %#{Chromosome}{genes: [1, 2, 4, 3, 5], size: 5}
124
+
125
+ iex> c1 = %#{Chromosome}{genes: [1, 2, 3, 4, 5], size: 5}
126
+ iex> invert(c1, 10, 3)
127
+ %#{Chromosome}{genes: [1, 2, 3, 4, 5], size: 5}
128
+
129
+ iex> c1 = %#{Chromosome}{genes: [1, 2, 3, 4, 5], size: 5}
130
+ iex> invert(c1, 0, 10)
131
+ %#{Chromosome}{genes: [5, 4, 3, 2, 1], size: 5}
132
+ """
133
+ @spec invert(Chromosome.t(), integer(), integer()) :: Chromosome.t()
134
+ def invert(chromosome, start, finish) do
135
+ {head, slice} = Enum.split(chromosome.genes, start)
136
+ {slice, tail} = Enum.split(slice, finish - start)
137
+ genes = head ++ Enum.reverse(slice) ++ tail
95
138
%Chromosome{chromosome | genes: genes}
96
139
end
97
- def invert(_, _), do: raise "Invalid radiation level!"
98
140
99
141
@doc """
100
142
Performs uniform integer mutation.
 
@@ -113,18 +155,18 @@ defmodule Genex.Operators.Mutation do
113
155
def uniform_integer(chromosome, radiation, min, max) when valid_rate?(radiation) do
114
156
genes =
115
157
chromosome.genes
116
- |> Enum.map(
117
- fn x ->
118
- if :rand.uniform() < radiation do
119
- Enum.random(min..max)
120
- else
121
- x
122
- end
123
- end
124
- )
158
+ |> Enum.map(fn x ->
159
+ if :rand.uniform() < radiation do
160
+ Enum.random(min..max)
161
+ else
162
+ x
163
+ end
164
+ end)
165
+
125
166
%Chromosome{chromosome | genes: genes}
126
167
end
127
- def uniform_integer(_, _, _, _), do: raise "Invalid radiation level!"
168
+
169
+ def uniform_integer(_, _, _, _), do: raise(ArgumentError, message: "Invalid radiation level!")
128
170
129
171
@doc """
130
172
Performs a gaussian mutation.
 
@@ -140,25 +182,28 @@ defmodule Genex.Operators.Mutation do
140
182
@spec gaussian(Chromosome.t(), float()) :: Chromosome.t()
141
183
def gaussian(chromosome, radiation) when valid_rate?(radiation) do
142
184
mu = Enum.sum(chromosome.genes) / length(chromosome.genes)
185
+
143
186
sigma =
144
187
chromosome.genes
145
188
|> Enum.map(fn x -> (mu - x) * (mu - x) end)
146
189
|> Enum.sum()
147
190
|> Kernel./(length(chromosome.genes))
191
+
148
192
genes =
149
193
chromosome.genes
150
- |> Enum.map(
151
- fn x ->
152
- if :rand.uniform() < radiation do
153
- :rand.normal(mu, sigma)
154
- else
155
- x
156
- end
157
- end
158
- )
194
+ |> Enum.map(fn x ->
195
+ if :rand.uniform() < radiation do
196
+ :rand.normal(mu, sigma)
197
+ else
198
+ x
199
+ end
200
+ end)
201
+
159
202
%Chromosome{chromosome | genes: genes}
160
203
end
161
204
205
+ def gaussian(_, _), do: raise(ArgumentError, "Invalid radiation level!")
206
+
162
207
@doc """
163
208
Performs Polynomial Bounded mutation.
164
209
 
@@ -176,30 +221,32 @@ defmodule Genex.Operators.Mutation do
176
221
def polynomial_bounded(chromosome, radiation, eta, low, high) when valid_rate?(radiation) do
177
222
genes =
178
223
chromosome.genes
179
- |> Enum.map(
180
- fn x ->
181
- delta_1 = (x - low) / (high - low)
182
- delta_2 = (high - x) / (high - low)
183
- rand = :rand.uniform()
184
- mut_pow = 1.0 / (eta + 1)
224
+ |> Enum.map(fn x ->
225
+ delta_1 = (x - low) / (high - low)
226
+ delta_2 = (high - x) / (high - low)
227
+ rand = :rand.uniform()
228
+ mut_pow = 1.0 / (eta + 1)
185
229
186
- delta_q =
187
- if rand < radiation do
188
- a = 1.0 - delta_1
189
- b = 2.0 * rand + (1.0 - 2.0 * rand) * :math.pow(a, eta+1)
190
- c = :math.pow(b, mut_pow - 1)
191
- c
192
- else
193
- a = 1.0 - delta_2
194
- b = 2.0 * rand + (1.0 - 2.0 * rand) * :math.pow(a, eta+1)
195
- c = 1.0 - :math.pow(b, mut_pow)
196
- c
197
- end
198
- y = x + delta_q * (high - low)
199
- min(max(y, low), high)
230
+ delta_q =
231
+ if rand < radiation do
232
+ a = 1.0 - delta_1
233
+ b = 2.0 * rand + (1.0 - 2.0 * rand) * :math.pow(a, eta + 1)
234
+ c = :math.pow(b, mut_pow - 1)
235
+ c
236
+ else
237
+ a = 1.0 - delta_2
238
+ b = 2.0 * rand + (1.0 - 2.0 * rand) * :math.pow(a, eta + 1)
239
+ c = 1.0 - :math.pow(b, mut_pow)
240
+ c
200
241
end
201
- )
242
+
243
+ y = x + delta_q * (high - low)
244
+ min(max(y, low), high)
245
+ end)
246
+
202
247
%Chromosome{chromosome | genes: genes}
203
248
end
204
- def polynomial_bounded(_, _, _, _, _), do: raise "Invalid radiation level!"
249
+
250
+ def polynomial_bounded(_, _, _, _, _),
251
+ do: raise(ArgumentError, message: "Invalid radiation level!")
205
252
end
changed lib/genex/operators/selection.ex
 
@@ -38,7 +38,7 @@ defmodule Genex.Operators.Selection do
38
38
@spec worst(Enum.t(), integer()) :: Enum.t()
39
39
def worst(chromosomes, n) do
40
40
chromosomes
41
- |> Enum.reverse
41
+ |> Enum.reverse()
42
42
|> Enum.take(n)
43
43
end
44
44
 
@@ -73,14 +73,12 @@ defmodule Genex.Operators.Selection do
73
73
"""
74
74
@spec tournament(Enum.t(), integer(), integer()) :: Enum.t()
75
75
def tournament(chromosomes, n, tournsize) do
76
- 0..n-1
77
- |> Enum.map(
78
- fn _ ->
79
- chromosomes
80
- |> Enum.take_random(tournsize)
81
- |> Enum.max_by(&Genex.Chromosome.get_fitness/1)
82
- end
83
- )
76
+ 0..(n - 1)
77
+ |> Enum.map(fn _ ->
78
+ chromosomes
79
+ |> Enum.take_random(tournsize)
80
+ |> Enum.max_by(&Genex.Chromosome.get_fitness/1)
81
+ end)
84
82
end
85
83
86
84
@doc """
 
@@ -98,23 +96,24 @@ defmodule Genex.Operators.Selection do
98
96
def roulette(chromosomes, n) do
99
97
sum_fitness =
100
98
chromosomes
101
- |> Enum.reduce(0, fn x, acc -> acc+x.fitness end)
102
- 0..n-1
103
- |> Enum.map(
104
- fn _ ->
105
- u = :rand.uniform() * sum_fitness
106
- chromosomes
107
- |> Enum.reduce_while(0,
108
- fn x, sum ->
109
- if x.fitness+sum > u do
110
- {:halt, x}
111
- else
112
- {:cont, x.fitness+sum}
113
- end
114
- end
115
- )
99
+ |> Enum.reduce(0, fn x, acc -> acc + x.fitness end)
100
+
101
+ 0..(n - 1)
102
+ |> Enum.map(fn _ ->
103
+ u = :rand.uniform() * sum_fitness
104
+
105
+ chromosomes
106
+ |> Enum.reduce_while(
107
+ 0,
108
+ fn x, sum ->
109
+ if x.fitness + sum > u do
110
+ {:halt, x}
111
+ else
112
+ {:cont, x.fitness + sum}
113
+ end
116
114
end
117
115
)
116
+ end)
118
117
end
119
118
120
119
@doc """
 
@@ -132,24 +131,25 @@ defmodule Genex.Operators.Selection do
132
131
def stochastic_universal_sampling(chromosomes, n) do
133
132
sum_fitness =
134
133
chromosomes
135
- |> Enum.reduce(0, fn x, acc -> acc+x.fitness end)
134
+ |> Enum.reduce(0, fn x, acc -> acc + x.fitness end)
135
+
136
136
p = sum_fitness / n
137
137
start = p * :rand.uniform()
138
- pointers = for i <- 0..n-1, do: start + i*p
138
+ pointers = for i <- 0..(n - 1), do: start + i * p
139
+
139
140
pointers
140
- |> Enum.map(
141
- fn x ->
142
- chromosomes
143
- |> Enum.reduce_while(0,
144
- fn y, sum ->
145
- if y.fitness+sum >= x do
146
- {:halt, y}
147
- else
148
- {:cont, y.fitness+sum}
149
- end
150
- end
151
- )
141
+ |> Enum.map(fn x ->
142
+ chromosomes
143
+ |> Enum.reduce_while(
144
+ 0,
145
+ fn y, sum ->
146
+ if y.fitness + sum >= x do
147
+ {:halt, y}
148
+ else
149
+ {:cont, y.fitness + sum}
150
+ end
152
151
end
153
152
)
153
+ end)
154
154
end
155
155
end
changed lib/genex/population.ex
 
@@ -1,5 +1,6 @@
1
1
defmodule Genex.Population do
2
2
alias Genex.Chromosome
3
+
3
4
@moduledoc """
4
5
A collection of `Chromosomes`.
5
6
 
@@ -9,17 +10,17 @@ defmodule Genex.Population do
9
10
"""
10
11
11
12
@type t :: %__MODULE__{
12
- chromosomes: Enum.t(),
13
- generation: integer(),
14
- max_fitness: number(),
15
- parents: Enum.t() | nil,
16
- survivors: Enum.t() | nil,
17
- children: Enum.t() | nil,
18
- size: integer(),
19
- strongest: Chromosome.t(),
20
- history: :digraph.graph(),
21
- statistics: Keyword.t()
22
- }
13
+ chromosomes: Enum.t(),
14
+ generation: integer(),
15
+ max_fitness: number(),
16
+ parents: Enum.t() | nil,
17
+ survivors: Enum.t() | nil,
18
+ children: Enum.t() | nil,
19
+ size: integer(),
20
+ strongest: Chromosome.t(),
21
+ history: :digraph.graph(),
22
+ statistics: Keyword.t()
23
+ }
23
24
24
25
@enforce_keys [:chromosomes]
25
26
defstruct [
 
@@ -43,6 +44,7 @@ defmodule Genex.Population do
43
44
population.chromosomes
44
45
|> Enum.sort_by(&Chromosome.get_fitness/1)
45
46
|> Enum.reverse()
47
+
46
48
%__MODULE__{population | chromosomes: sorted_chromosomes, strongest: hd(sorted_chromosomes)}
47
49
end
48
50
end
changed lib/genex/support/genealogy.ex
 
@@ -10,7 +10,7 @@ defmodule Genex.Support.Genealogy do
10
10
11
11
Returns `Graph`.
12
12
"""
13
- def init, do: :digraph.new()
13
+ def init, do: Graph.new(type: :directed)
14
14
15
15
@doc """
16
16
Updates a Genealogy Tree with just a vertex.
 
@@ -22,25 +22,46 @@ defmodule Genex.Support.Genealogy do
22
22
- `chromosome` - Chromosome to add to Genealogy.
23
23
"""
24
24
def update(genealogy, chromosome) do
25
- :digraph.add_vertex(genealogy, chromosome)
26
25
genealogy
26
+ |> Graph.add_vertex(chromosome)
27
27
end
28
28
29
29
@doc """
30
- Updates a Genealogy Tree with a vertex and it's corresponding parents.
30
+ Updates a Genealogy Tree with a vertex and it's parents.
31
31
32
32
Returns `Graph`.
33
33
34
34
# Parameters
35
35
- `genealogy` - Reference to a Genealogy Tree.
36
- - `chromosome` - Chromosome to add to genealogy.
37
- - `parent_a` - Parent Chromosome.
38
- - `parent_b` - Parent Chromosome.
36
+ - `child` - Chromosome to add.
37
+ - `parent_a` - Child's parent.
38
+ - `parent_b` - Child's parent.
39
39
"""
40
- def update(genealogy, chromosome, parent_a, parent_b) do
41
- :digraph.add_vertex(genealogy, chromosome)
42
- :digraph.add_edge(genealogy, parent_a, chromosome)
43
- :digraph.add_edge(genealogy, parent_b, chromosome)
40
+ def update(genealogy, child, parent_a, parent_b) do
44
41
genealogy
42
+ |> Graph.add_vertex(child)
43
+ |> Graph.add_edge(parent_a, child)
44
+ |> Graph.add_edge(parent_b, child)
45
45
end
46
- end
\ No newline at end of file
46
+
47
+ @doc """
48
+ Add a generation of `Chromosomes` to Genealogy Tree.
49
+
50
+ Returns `Graph`.
51
+
52
+ # Parameters
53
+ - `genealogy` - Reference to a Genealogy tree.
54
+ - `chromosome` - Chromosome to add.
55
+ """
56
+ def add_generation(genealogy, chromosomes) do
57
+ Graph.add_vertices(genealogy, chromosomes)
58
+ end
59
+
60
+ @doc """
61
+ Exports the genealogy tree.
62
+ """
63
+ def export(genealogy) do
64
+ genealogy
65
+ |> Graph.Serializers.DOT.serialize()
66
+ end
67
+ end
changed lib/genex/support/statistics.ex
 
@@ -3,22 +3,129 @@ defmodule Genex.Support.Statistics do
3
3
Provides basic summary statistics for use in the Population struct.
4
4
"""
5
5
6
+ @doc """
7
+ Takes the mean of `list`.
8
+
9
+ Returns `Number`.
10
+
11
+ # Parameters
12
+ - `list`: List of values.
13
+
14
+ # Examples
15
+
16
+ iex> list = [1, 2, 3, 4, 5]
17
+ iex> mean(list)
18
+ 3.0
19
+
20
+ iex> list = []
21
+ iex> mean(list)
22
+ 0
23
+ """
24
+ def mean([]), do: 0
25
+
6
26
def mean(list) do
7
27
list
8
28
|> Enum.sum()
9
29
|> Kernel./(length(list))
10
30
end
11
31
32
+ @doc """
33
+ Takes the variance of `list`.
34
+
35
+ Returns `Number`.
36
+
37
+ # Parameters
38
+ - `list`: List of values.
39
+
40
+ # Examples
41
+
42
+ iex> list = [0, 1, 1, 1]
43
+ iex> variance(list)
44
+ 0.25
45
+
46
+ iex> list = []
47
+ iex> variance(list)
48
+ 0
49
+ """
50
+ def variance([]), do: 0
51
+
12
52
def variance(list) do
13
- list_mean = mean(list)
14
- list |> Enum.map(fn x -> (list_mean - x) * (list_mean - x) end) |> mean
53
+ avg = mean(list)
54
+ size = length(list)
55
+
56
+ list
57
+ |> Enum.reduce(
58
+ 0,
59
+ fn x, total ->
60
+ total + :math.pow(x - avg, 2)
61
+ end
62
+ )
63
+ |> Kernel./(size - 1)
15
64
end
16
65
66
+ @doc """
67
+ Takes the standard deviation of `list`.
68
+
69
+ Returns `Number`.
70
+
71
+ # Parameters
72
+ - `list`: List of values.
73
+
74
+ # Examples
75
+
76
+ iex> list = [0, 1, 1, 1]
77
+ iex> stdev(list)
78
+ 0.5
79
+
80
+ iex> list = []
81
+ iex> stdev(list)
82
+ 0
83
+ """
84
+ def stdev([]), do: 0
85
+
17
86
def stdev(list) do
18
87
:math.sqrt(variance(list))
19
88
end
20
89
90
+ @doc """
91
+ Takes the min of `list`.
92
+
93
+ Returns `Number`.
94
+
95
+ # Parameters
96
+ - `list`: List of values.
97
+
98
+ # Examples
99
+
100
+ iex> list = [1, 2, 3, 4, 5]
101
+ iex> min(list)
102
+ 1
103
+
104
+ iex> list = []
105
+ iex> min(list)
106
+ nil
107
+ """
108
+ def min([]), do: nil
21
109
def min(list), do: Enum.min(list)
22
110
111
+ @doc """
112
+ Takes the max of `list`.
113
+
114
+ Returns `Number`.
115
+
116
+ # Parameters
117
+ - `list`: List of values.
118
+
119
+ # Examples
120
+
121
+ iex> list = [1, 2, 3, 4, 5]
122
+ iex> max(list)
123
+ 5
124
+
125
+ iex> list = []
126
+ iex> max(list)
127
+ nil
128
+ """
129
+ def max([]), do: nil
23
130
def max(list), do: Enum.max(list)
24
- end
\ No newline at end of file
131
+ end
changed lib/genex/visualizers/text.ex
 
@@ -9,10 +9,12 @@ defmodule Genex.Visualizers.Text do
9
9
def init do
10
10
title = "Genetic Algorithm"
11
11
header = ["Generation", "Size", "Max Fitness", "Strongest"]
12
+
12
13
rows = [
13
14
[0, 0, 0, ""],
14
15
[0, 0, 0, ""]
15
16
]
17
+
16
18
IO.write(TableRex.quick_render!(rows, header, title))
17
19
end
18
20
 
@@ -22,8 +24,9 @@ defmodule Genex.Visualizers.Text do
22
24
def display_summary(population) do
23
25
title = "Genetic Algorithm"
24
26
header = ["Generation", "Size", "Max Fitness", "Strongest"]
27
+
25
28
rows = [
26
- [population.generation, population.size, population.max_fitness, population.strongest],
29
+ [population.generation, population.size, population.max_fitness, population.strongest]
27
30
]
28
31
29
32
IO.ANSI.cursor_up(7)
changed mix.exs
 
@@ -1,7 +1,7 @@
1
1
defmodule Genex.MixProject do
2
2
use Mix.Project
3
3
4
- @version "0.1.4"
4
+ @version "0.2.0"
5
5
@url "https://www.github.com/seanmor5/genex"
6
6
@maintainers ["Sean Moriarity"]
7
7
 
@@ -27,7 +27,7 @@ defmodule Genex.MixProject do
27
27
"coveralls.detail": :test,
28
28
"coveralls.post": :test,
29
29
"coveralls.html": :test
30
- ],
30
+ ]
31
31
]
32
32
end
33
33
 
@@ -74,6 +74,12 @@ defmodule Genex.MixProject do
74
74
],
75
75
"guides/support/benchmarking.md": [
76
76
filename: "support-benchmarking"
77
+ ],
78
+ "guides/support/genealogy.md": [
79
+ filename: "support-genealogy"
80
+ ],
81
+ "guides/operators/crossover.md": [
82
+ filename: "operators-crossover"
77
83
]
78
84
]
79
85
end
 
@@ -82,7 +88,8 @@ defmodule Genex.MixProject do
82
88
[
83
89
Introduction: Path.wildcard("guides/introduction/*.md"),
84
90
Support: Path.wildcard("guides/support/*.md"),
85
- Tutorials: Path.wildcard("guides/tutorials/*.md")
91
+ Tutorials: Path.wildcard("guides/tutorials/*.md"),
92
+ Operators: Path.wildcard("guides/operators/*.md")
86
93
]
87
94
end
88
95
 
@@ -93,11 +100,9 @@ defmodule Genex.MixProject do
93
100
Genex.Operators.Mutation,
94
101
Genex.Operators.Selection
95
102
],
96
-
97
103
Support: [
98
104
Genex.Support.Genealogy
99
105
],
100
-
101
106
Structures: [
102
107
Genex.Population,
103
108
Genex.Chromosome
 
@@ -111,7 +116,8 @@ defmodule Genex.MixProject do
111
116
{:ex_doc, "~> 0.21", only: :dev, runtime: false},
112
117
{:excoveralls, "~> 0.10", only: :test},
113
118
{:table_rex, "~> 2.0.0"},
114
- {:benchee, "~> 1.0"}
119
+ {:benchee, "~> 1.0"},
120
+ {:libgraph, "~> 0.13.0"}
115
121
]
116
122
end