evolvable 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.yardopts +2 -0
- data/CHANGELOG.md +24 -0
- data/Gemfile +2 -2
- data/Gemfile.lock +44 -25
- data/README.md +498 -190
- data/README_YARD.md +85 -166
- data/bin/console +10 -19
- data/docs/Evolvable/ClassMethods.html +1233 -0
- data/docs/Evolvable/Community/ClassMethods.html +708 -0
- data/docs/Evolvable/Community.html +1342 -0
- data/docs/Evolvable/CountGene.html +886 -0
- data/docs/Evolvable/EqualizeGoal.html +347 -0
- data/docs/Evolvable/Error.html +134 -0
- data/docs/Evolvable/Evaluation.html +773 -0
- data/docs/Evolvable/Evolution.html +616 -0
- data/docs/Evolvable/Gene/ClassMethods.html +413 -0
- data/docs/Evolvable/Gene.html +522 -0
- data/docs/Evolvable/GeneCluster/ClassMethods.html +431 -0
- data/docs/Evolvable/GeneCluster.html +280 -0
- data/docs/Evolvable/GeneCombination.html +515 -0
- data/docs/Evolvable/GeneSpace.html +619 -0
- data/docs/Evolvable/Genome.html +1070 -0
- data/docs/Evolvable/Goal.html +500 -0
- data/docs/Evolvable/MaximizeGoal.html +348 -0
- data/docs/Evolvable/MinimizeGoal.html +348 -0
- data/docs/Evolvable/Mutation.html +729 -0
- data/docs/Evolvable/PointCrossover.html +444 -0
- data/docs/Evolvable/Population.html +2826 -0
- data/docs/Evolvable/RigidCountGene.html +501 -0
- data/docs/Evolvable/Selection.html +594 -0
- data/docs/Evolvable/Serializer.html +293 -0
- data/docs/Evolvable/UniformCrossover.html +286 -0
- data/docs/Evolvable.html +1619 -0
- data/docs/_index.html +341 -0
- data/docs/class_list.html +54 -0
- data/docs/css/common.css +1 -0
- data/docs/css/full_list.css +58 -0
- data/docs/css/style.css +503 -0
- data/docs/file.README.html +750 -0
- data/docs/file_list.html +59 -0
- data/docs/frames.html +22 -0
- data/docs/index.html +750 -0
- data/docs/js/app.js +344 -0
- data/docs/js/full_list.js +242 -0
- data/docs/js/jquery.js +4 -0
- data/docs/method_list.html +1302 -0
- data/docs/top-level-namespace.html +110 -0
- data/evolvable.gemspec +6 -6
- data/examples/ascii_art.rb +5 -9
- data/examples/stickman.rb +25 -33
- data/{examples/hello_world.rb → exe/hello_evolvable_world} +46 -30
- data/lib/evolvable/community.rb +190 -0
- data/lib/evolvable/count_gene.rb +65 -0
- data/lib/evolvable/equalize_goal.rb +2 -9
- data/lib/evolvable/evaluation.rb +113 -14
- data/lib/evolvable/evolution.rb +38 -15
- data/lib/evolvable/gene.rb +124 -25
- data/lib/evolvable/gene_cluster.rb +106 -0
- data/lib/evolvable/gene_combination.rb +57 -16
- data/lib/evolvable/gene_space.rb +111 -0
- data/lib/evolvable/genome.rb +32 -4
- data/lib/evolvable/goal.rb +19 -24
- data/lib/evolvable/maximize_goal.rb +2 -9
- data/lib/evolvable/minimize_goal.rb +3 -9
- data/lib/evolvable/mutation.rb +87 -41
- data/lib/evolvable/point_crossover.rb +24 -4
- data/lib/evolvable/population.rb +258 -84
- data/lib/evolvable/rigid_count_gene.rb +36 -0
- data/lib/evolvable/selection.rb +68 -14
- data/lib/evolvable/serializer.rb +46 -0
- data/lib/evolvable/uniform_crossover.rb +30 -6
- data/lib/evolvable/version.rb +2 -1
- data/lib/evolvable.rb +268 -107
- metadata +57 -36
- data/examples/images/diagram.png +0 -0
- data/exe/hello +0 -16
- data/lib/evolvable/error/undefined_method.rb +0 -7
- data/lib/evolvable/search_space.rb +0 -181
data/README_YARD.md
CHANGED
@@ -1,11 +1,36 @@
|
|
1
|
-
# Evolvable
|
1
|
+
# Evolvable 🧬
|
2
2
|
|
3
|
-
[](https://badge.fury.io/rb/evolvable)
|
3
|
+
[](https://badge.fury.io/rb/evolvable)
|
4
4
|
|
5
|
-
|
5
|
+
**Code Version: {@string Evolvable::VERSION}**
|
6
6
|
|
7
|
-
|
7
|
+
Evolvable is a Ruby gem that brings genetic algorithms to Ruby objects through simple, flexible APIs. Define genes, implement fitness criteria, and let evolution discover optimal solutions through selection, combination, and mutation.
|
8
8
|
|
9
|
+
Perfect for optimization problems, creative content generation, machine learning, and simulating complex systems.
|
10
|
+
|
11
|
+
## Why Evolvable?
|
12
|
+
|
13
|
+
Evolvable is ideal when the solution space is too large or complex for brute-force methods. Instead of hardcoding solutions, you define constraints and let evolution discover optimal configurations over time.
|
14
|
+
|
15
|
+
**The Evolvable Approach:**
|
16
|
+
- Explore vast solution spaces efficiently without examining every possibility
|
17
|
+
- Discover novel solutions that might not be obvious to human designers
|
18
|
+
- Adapt to changing conditions through continuous evolution
|
19
|
+
- Balance diverse objectives with communities of different populations
|
20
|
+
- Integrate evolutionary concepts directly into your Ruby object model
|
21
|
+
- Generate creative content like music, art, and text, not just numerical optimization
|
22
|
+
|
23
|
+
Whether you're optimizing parameters, generating creative content, or simulating complex systems, Evolvable provides a natural, object-oriented approach to evolutionary algorithms.
|
24
|
+
|
25
|
+
**Creative Applications**
|
26
|
+
|
27
|
+
Evolvable treats creative, object-oriented representations as first-class citizens. The same API that optimizes numeric parameters can evolve music compositions, UI layouts, or game content with equal fluency. Examples include:
|
28
|
+
|
29
|
+
- **Generative art**: Evolve visual compositions based on aesthetic criteria
|
30
|
+
- **Music composition**: Create melodies, chord progressions, and rhythms
|
31
|
+
- **Game design**: Generate levels, characters, or game mechanics
|
32
|
+
- **Natural language**: Evolve text with specific tones, styles, or constraints
|
33
|
+
- **UI/UX design**: Discover intuitive layouts and color schemes
|
9
34
|
|
10
35
|
## Table of Contents
|
11
36
|
* [Installation](#installation)
|
@@ -18,220 +43,114 @@ Subscribe to the [Evolvable Newsletter](https://www.evolvable.site/newsletter) t
|
|
18
43
|
* [Selection](#selection)
|
19
44
|
* [Combination](#combination)
|
20
45
|
* [Mutation](#mutation)
|
21
|
-
* [
|
46
|
+
* [Gene Clusters](#gene-clusters)
|
47
|
+
* [Community](#community)
|
48
|
+
* [Serialization](#serialization)
|
49
|
+
* [Documentation]({@string Evolvable::DOC_URL})
|
22
50
|
|
23
51
|
|
24
52
|
## Installation
|
25
53
|
|
26
54
|
Add [gem "evolvable"](https://rubygems.org/gems/evolvable) to your Gemfile and run `bundle install` or install it yourself with: `gem install evolvable`
|
27
55
|
|
56
|
+
**Ruby Compatibility:** Evolvable officially supports Ruby 3.0 and higher.
|
57
|
+
|
28
58
|
## Getting Started
|
29
59
|
|
30
60
|
{@readme Evolvable}
|
31
61
|
|
32
|
-
|
33
|
-
|
34
|
-
### Hello World
|
35
|
-
|
36
|
-
Let's build the evolvable hello world program using the above steps. It'll evolve a population of arbitrary strings to be more like a given target string. After installing this gem, run `evolvable hello` at the command line to see it in action.
|
37
|
-
|
38
|
-
Below is example output from evolving a population of randomly initialized string objects to match "Hello World!", then "Hello Evolvable World".
|
39
|
-
|
40
|
-
```
|
41
|
-
❯ Enter a string to evolve: Hello World!
|
42
|
-
|
43
|
-
pp`W^jXG'_N`% Generation 0
|
44
|
-
H-OQXZ\a~{H* Generation 1 ...
|
45
|
-
HRv9X WorlNi Generation 50 ...
|
46
|
-
HRl6W World# Generation 100 ...
|
47
|
-
Hello World! Generation 165
|
48
|
-
|
49
|
-
❯ Enter a string to evolve: Hello Evolvable World
|
50
|
-
|
51
|
-
Helgo World!b+=1}3 Generation 165
|
52
|
-
Helgo Worlv!}:c(SoV Generation 166
|
53
|
-
Helgo WorlvsC`X(Joqs Generation 167
|
54
|
-
Helgo WorlvsC`X(So1RE Generation 168 ...
|
55
|
-
Hello Evolv#"l{ Wor*5 Generation 300 ...
|
56
|
-
Hello Evolvable World Generation 388
|
57
|
-
```
|
58
|
-
|
59
|
-
### Step 1
|
60
|
-
|
61
|
-
Let's begin by defining a `HelloWorld` class and have it **include the `Evolvable` module**.
|
62
|
-
|
63
|
-
```ruby
|
64
|
-
class HelloWorld
|
65
|
-
include Evolvable
|
66
|
-
end
|
67
|
-
```
|
68
|
-
|
69
|
-
### Step 2
|
70
|
-
|
71
|
-
Now we can **define the `.search_space`** class method with the types of [genes](#genes) that we want our our evolvable "hello world" instances to be able to have. We'll use `CharGene` instances to represent single characters within strings. So an instance with the string value of "Hello" would be composed of five `CharGene` instances.
|
72
|
-
|
73
|
-
```ruby
|
74
|
-
class HelloWorld
|
75
|
-
include Evolvable
|
76
|
-
|
77
|
-
def self.search_space
|
78
|
-
["CharGene", 1..40]
|
79
|
-
end
|
80
|
-
end
|
81
|
-
```
|
82
|
-
|
83
|
-
The [Search Space](#search-space) can be defined in a variety of ways. The above is shorthand that's useful for when there's only one type of gene. This method can also return an array of arrays or hash.
|
84
|
-
|
85
|
-
The `1..40` specifies the range of possible genes for a particular HelloWorld instance. Evolvable translates this range or integer value into a `Evolvable::CountGene` object.
|
86
|
-
|
87
|
-
By specifying a range, an `Evolvable::CountGene` instance can change the number of genes that are present in an evovlable instance. Count genes undergo evolutionary operations like any other gene. Their effects can be seen in the letter changes from Generation 165 to 168 in the above example output.
|
88
|
-
|
89
|
-
To finish step 2, we'll **define the gene class** that we referenced in the above `.search_space` method. Gene classes should include the `Evolvable::Gene` module.
|
90
|
-
|
91
|
-
```ruby
|
92
|
-
class CharGene
|
93
|
-
include Evolvable::Gene
|
94
|
-
|
95
|
-
def self.chars
|
96
|
-
@chars ||= 32.upto(126).map(&:chr)
|
97
|
-
end
|
98
|
-
|
99
|
-
def to_s
|
100
|
-
@to_s ||= self.class.chars.sample
|
101
|
-
end
|
102
|
-
end
|
103
|
-
```
|
104
|
-
|
105
|
-
It's important that, once accessed, the data for a particular gene never change. When the `#to_s` method first runs, Ruby's `||=` operator memoizes the result of randomly picking a char, enabling this method to sample a char only once per gene.
|
106
|
-
|
107
|
-
After defining the search space, we can now initialize `HelloWorld` instances with random genes, but to actually evolve them, we need to **define the `#value` instance method**. It provides the basis for comparing different evolvable instances.
|
108
|
-
|
109
|
-
### Step 3
|
110
|
-
|
111
|
-
In the next step, we'll set the goal value to 0, so that evolution favors evolvable HelloWorld instances with `#value` methods that return numbers closer to 0. That means we want instances that more closely match their targets to return scores nearer to 0. As an example, if our target is "hello world", an instance that returns "jello world" would have a value of 1 and "hello world" would have a value of 0.
|
112
|
-
|
113
|
-
For a working implementation, see the `#value` method in [examples/hello_world.rb](https://github.com/mattruzicka/evolvable/blob/main/examples/hello_world.rb)
|
114
|
-
|
115
|
-
### Step 4
|
116
|
-
|
117
|
-
Now it's time to **initialize a population with `.new_population`**. By default, evolvable populations seek to maximize numeric values. In this program, we always know the best possible value, so setting the goal to a concrete number makes sense. This is done by passing the evaluation params with equalize set to 0.
|
118
|
-
|
119
|
-
We'll also specify the number of instances in a population using the population's `size` parameter and change the mutation porbability from 0.03 (3%) to 0.6 (60%).
|
120
|
-
|
121
|
-
Experimentation has suggested that a large mutation probability tends to decrease the time it takes to evolve matches with short strings and has the opposite effect for long strings. This is demonstrated in the example output above by how many generations it took to go from "Hello World!" to "Hello Evolvable World". As an optimization, we could dynamically change the mutation probability using a population hook detailed below, but doing so will be left as an exercise for the reader. [Pull requests are welcome.](#contributing)
|
122
|
-
|
123
|
-
```ruby
|
124
|
-
population = HelloWorld.new_population(size: 100,
|
125
|
-
evaluation: { equalize: 0 },
|
126
|
-
mutation: { probability: 0.6 }
|
127
|
-
```
|
62
|
+
## Concepts
|
128
63
|
|
64
|
+
Evolvable is built on these core concepts:
|
65
|
+
- **Genes**: Ruby objects that represent traits or behaviors and are passed down during evolution.
|
66
|
+
- **Evolvables**: Your Ruby classes that include "Evolvable" and delegate to genes
|
67
|
+
- **Populations**: Groups of evolvables instances that evolve together
|
68
|
+
- **Evaluation**: Sorts evolvables by fitness
|
69
|
+
- **Evolution**: Selection → Combination → Mutation to generate new evolvables
|
70
|
+
- **Communities**: Encapsulate evolvable populations
|
129
71
|
|
130
|
-
|
72
|
+
The framework offers built-in implementations while allowing domain-specific customization through its extensible and swapable components.
|
131
73
|
|
132
|
-
|
74
|
+
## Genes
|
133
75
|
|
134
|
-
|
76
|
+
{@readme Evolvable::Gene}
|
135
77
|
|
136
|
-
|
137
|
-
2. `.before_evolution(population)`- {@readme Evolvable::ClassMethods#before_evolution}
|
138
|
-
3. `.after_evolution(population)` - {@readme Evolvable::ClassMethods#after_evolution}
|
78
|
+
[Gene Documentation]({@string Evolvable::DOC_URL}/Evolvable/Gene)
|
139
79
|
|
140
|
-
|
80
|
+
## Populations
|
141
81
|
|
142
|
-
|
143
|
-
class HelloWorld
|
144
|
-
include Evolvable
|
82
|
+
{@readme Evolvable::Population}
|
145
83
|
|
146
|
-
|
147
|
-
best_evolvable = population.best_evolvable
|
148
|
-
evolutions_count = population.evolutions_count
|
149
|
-
puts "#{best_evolvable} - Generation #{evolutions_count}"
|
150
|
-
end
|
84
|
+
[Population Documentation]({@string Evolvable::DOC_URL}/Evolvable/Population)
|
151
85
|
|
152
|
-
|
86
|
+
## Evaluation
|
153
87
|
|
154
|
-
|
155
|
-
@to_s ||= genes.join
|
156
|
-
end
|
88
|
+
{@readme Evolvable::Evaluation}
|
157
89
|
|
158
|
-
|
159
|
-
end
|
160
|
-
```
|
90
|
+
{@readme Evolvable::Goal}
|
161
91
|
|
162
|
-
|
92
|
+
Example goal implementation that prioritizes evolvables with fitness values within a specific range:
|
163
93
|
|
164
|
-
|
165
|
-
population.evolve
|
166
|
-
```
|
94
|
+
{@example Evolvable::Goal}
|
167
95
|
|
168
|
-
|
96
|
+
[Evaluation Documentation]({@string Evolvable::DOC_URL}/Evolvable/Evaluation)
|
169
97
|
|
170
|
-
|
98
|
+
[Goal Documentation]({@string Evolvable::DOC_URL}/Evolvable/Goal)
|
171
99
|
|
172
|
-
|
100
|
+
## Evolution
|
173
101
|
|
174
|
-
|
102
|
+
{@readme Evolvable::Evolution}
|
175
103
|
|
176
|
-
|
104
|
+
[Evolution Documentation]({@string Evolvable::DOC_URL}/Evolvable/Evolution)
|
177
105
|
|
178
|
-
|
106
|
+
## Selection
|
179
107
|
|
180
|
-
|
181
|
-
{@readme Evolvable::Gene}
|
108
|
+
{@readme Evolvable::Selection}
|
182
109
|
|
183
|
-
{@
|
110
|
+
[Selection Documentation]({@string Evolvable::DOC_URL}/Evolvable/Selection)
|
184
111
|
|
185
|
-
|
112
|
+
## Combination
|
186
113
|
|
187
|
-
|
188
|
-
{@readme Evolvable::Population}
|
114
|
+
{@readme Evolvable::GeneCombination}
|
189
115
|
|
190
|
-
{@
|
116
|
+
[Combination Documentation]({@string Evolvable::DOC_URL}/Evolvable/Combination)
|
191
117
|
|
192
|
-
|
118
|
+
**Point Crossover**
|
193
119
|
|
194
|
-
|
195
|
-
{@readme Evolvable::Evaluation}
|
120
|
+
{@readme Evolvable::PointCrossover}
|
196
121
|
|
197
|
-
{@
|
122
|
+
[PointCrossover Documentation]({@string Evolvable::DOC_URL}/Evolvable/PointCrossover)
|
198
123
|
|
199
|
-
|
124
|
+
**Uniform Crossover**
|
200
125
|
|
201
|
-
|
202
|
-
{@readme Evolvable::Evolution}
|
126
|
+
{@readme Evolvable::UniformCrossover}
|
203
127
|
|
204
|
-
[Documentation](
|
128
|
+
[UniformCrossover Documentation]({@string Evolvable::DOC_URL}/Evolvable/UniformCrossover)
|
205
129
|
|
206
|
-
##
|
207
|
-
{@readme Evolvable::Selection}
|
130
|
+
## Mutation
|
208
131
|
|
209
|
-
{@
|
132
|
+
{@readme Evolvable::Mutation}
|
210
133
|
|
134
|
+
[Mutation Documentation]({@string Evolvable::DOC_URL}/Evolvable/Mutation)
|
211
135
|
|
212
|
-
|
136
|
+
## Gene Clusters
|
213
137
|
|
214
|
-
|
215
|
-
{@readme Evolvable::GeneCombination}
|
138
|
+
{@readme Evolvable::GeneCluster}
|
216
139
|
|
217
|
-
[Documentation](
|
140
|
+
[GeneCluster Documentation]({@string Evolvable::DOC_URL}/Evolvable/GeneCluster)
|
218
141
|
|
219
|
-
##
|
220
|
-
{@readme Evolvable::Mutation}
|
142
|
+
## Community
|
221
143
|
|
222
|
-
{@
|
144
|
+
{@readme Evolvable::Community}
|
223
145
|
|
224
|
-
[Documentation](
|
146
|
+
[Community Documentation]({@string Evolvable::DOC_URL}/Evolvable/Community)
|
225
147
|
|
226
|
-
##
|
227
|
-
{@readme Evolvable::SearchSpace}
|
148
|
+
## Serialization
|
228
149
|
|
229
|
-
{@
|
150
|
+
{@readme Evolvable::Serializer}
|
230
151
|
|
231
|
-
[Documentation](
|
152
|
+
[Serializer Documentation]({@string Evolvable::DOC_URL}/Evolvable/Serializer)
|
232
153
|
|
233
154
|
## Contributing
|
234
155
|
|
235
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/mattruzicka/evolvable.
|
236
|
-
|
237
|
-
If you're interested in contributing, but don't know where to get started, message me on twitter at [@mattruzicka](https://twitter.com/mattruzicka).
|
156
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/mattruzicka/evolvable.
|
data/bin/console
CHANGED
@@ -2,26 +2,17 @@
|
|
2
2
|
|
3
3
|
require 'bundler/setup'
|
4
4
|
require 'evolvable'
|
5
|
-
require '
|
6
|
-
|
7
|
-
Dir['./examples/*.rb'].each { |f| require f }
|
5
|
+
require 'debug'
|
6
|
+
require 'irb'
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
# evaluation: { equalize: 0 },
|
12
|
-
# mutation: { probability: 0.6 })
|
13
|
-
# population.evolve
|
14
|
-
# HelloWorld.start_loop(population)
|
8
|
+
require './examples/stickman'
|
9
|
+
stickman_pop = Stickman.new_population(size: 5, mutation: { probability: 0.3 })
|
15
10
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
# population.evolve
|
11
|
+
require './examples/ascii_art'
|
12
|
+
require './examples/ascii_gene'
|
13
|
+
ascii_pop = AsciiArt.new_population(size: 8, mutation: { probability: 0.3, rate: 0.02 })
|
20
14
|
|
21
|
-
|
22
|
-
#
|
23
|
-
# mutation: { probability: 0.3, rate: 0.02 })
|
24
|
-
# ascii_art.evolve
|
15
|
+
# stickman_pop.evolve_forever
|
16
|
+
# ascii_pop.evolve_forever
|
25
17
|
|
26
|
-
|
27
|
-
IRB.start(__FILE__)
|
18
|
+
binding.irb
|