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.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -0
  3. data/CHANGELOG.md +24 -0
  4. data/Gemfile +2 -2
  5. data/Gemfile.lock +44 -25
  6. data/README.md +498 -190
  7. data/README_YARD.md +85 -166
  8. data/bin/console +10 -19
  9. data/docs/Evolvable/ClassMethods.html +1233 -0
  10. data/docs/Evolvable/Community/ClassMethods.html +708 -0
  11. data/docs/Evolvable/Community.html +1342 -0
  12. data/docs/Evolvable/CountGene.html +886 -0
  13. data/docs/Evolvable/EqualizeGoal.html +347 -0
  14. data/docs/Evolvable/Error.html +134 -0
  15. data/docs/Evolvable/Evaluation.html +773 -0
  16. data/docs/Evolvable/Evolution.html +616 -0
  17. data/docs/Evolvable/Gene/ClassMethods.html +413 -0
  18. data/docs/Evolvable/Gene.html +522 -0
  19. data/docs/Evolvable/GeneCluster/ClassMethods.html +431 -0
  20. data/docs/Evolvable/GeneCluster.html +280 -0
  21. data/docs/Evolvable/GeneCombination.html +515 -0
  22. data/docs/Evolvable/GeneSpace.html +619 -0
  23. data/docs/Evolvable/Genome.html +1070 -0
  24. data/docs/Evolvable/Goal.html +500 -0
  25. data/docs/Evolvable/MaximizeGoal.html +348 -0
  26. data/docs/Evolvable/MinimizeGoal.html +348 -0
  27. data/docs/Evolvable/Mutation.html +729 -0
  28. data/docs/Evolvable/PointCrossover.html +444 -0
  29. data/docs/Evolvable/Population.html +2826 -0
  30. data/docs/Evolvable/RigidCountGene.html +501 -0
  31. data/docs/Evolvable/Selection.html +594 -0
  32. data/docs/Evolvable/Serializer.html +293 -0
  33. data/docs/Evolvable/UniformCrossover.html +286 -0
  34. data/docs/Evolvable.html +1619 -0
  35. data/docs/_index.html +341 -0
  36. data/docs/class_list.html +54 -0
  37. data/docs/css/common.css +1 -0
  38. data/docs/css/full_list.css +58 -0
  39. data/docs/css/style.css +503 -0
  40. data/docs/file.README.html +750 -0
  41. data/docs/file_list.html +59 -0
  42. data/docs/frames.html +22 -0
  43. data/docs/index.html +750 -0
  44. data/docs/js/app.js +344 -0
  45. data/docs/js/full_list.js +242 -0
  46. data/docs/js/jquery.js +4 -0
  47. data/docs/method_list.html +1302 -0
  48. data/docs/top-level-namespace.html +110 -0
  49. data/evolvable.gemspec +6 -6
  50. data/examples/ascii_art.rb +5 -9
  51. data/examples/stickman.rb +25 -33
  52. data/{examples/hello_world.rb → exe/hello_evolvable_world} +46 -30
  53. data/lib/evolvable/community.rb +190 -0
  54. data/lib/evolvable/count_gene.rb +65 -0
  55. data/lib/evolvable/equalize_goal.rb +2 -9
  56. data/lib/evolvable/evaluation.rb +113 -14
  57. data/lib/evolvable/evolution.rb +38 -15
  58. data/lib/evolvable/gene.rb +124 -25
  59. data/lib/evolvable/gene_cluster.rb +106 -0
  60. data/lib/evolvable/gene_combination.rb +57 -16
  61. data/lib/evolvable/gene_space.rb +111 -0
  62. data/lib/evolvable/genome.rb +32 -4
  63. data/lib/evolvable/goal.rb +19 -24
  64. data/lib/evolvable/maximize_goal.rb +2 -9
  65. data/lib/evolvable/minimize_goal.rb +3 -9
  66. data/lib/evolvable/mutation.rb +87 -41
  67. data/lib/evolvable/point_crossover.rb +24 -4
  68. data/lib/evolvable/population.rb +258 -84
  69. data/lib/evolvable/rigid_count_gene.rb +36 -0
  70. data/lib/evolvable/selection.rb +68 -14
  71. data/lib/evolvable/serializer.rb +46 -0
  72. data/lib/evolvable/uniform_crossover.rb +30 -6
  73. data/lib/evolvable/version.rb +2 -1
  74. data/lib/evolvable.rb +268 -107
  75. metadata +57 -36
  76. data/examples/images/diagram.png +0 -0
  77. data/exe/hello +0 -16
  78. data/lib/evolvable/error/undefined_method.rb +0 -7
  79. data/lib/evolvable/search_space.rb +0 -181
data/README_YARD.md CHANGED
@@ -1,11 +1,36 @@
1
- # Evolvable 🦎
1
+ # Evolvable 🧬
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/evolvable.svg)](https://badge.fury.io/rb/evolvable) [![Maintainability](https://api.codeclimate.com/v1/badges/7faf84a6d467718b33c0/maintainability)](https://codeclimate.com/github/mattruzicka/evolvable/maintainability)
3
+ [![Gem Version](https://badge.fury.io/rb/evolvable.svg)](https://badge.fury.io/rb/evolvable)
4
4
 
5
- An evolutionary framework for writing programs that use operations such as selection, crossover, and mutation. Explore ideas generatively in any domain, discover novel solutions to complex problems, and build intuitions about intelligence, complexity, and the natural world.
5
+ **Code Version: {@string Evolvable::VERSION}**
6
6
 
7
- Subscribe to the [Evolvable Newsletter](https://www.evolvable.site/newsletter) to slowly learn more, or keep reading this contextualization of the [full documentation](https://rubydoc.info/github/mattruzicka/evolvable).
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
- * [Search Space](#search-space)
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
- To demonstrate these steps, we'll look at the [Hello World](#) example program.
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
- At this point, everything should work when we run `population.evolve`, but it'll look like nothing is happening. The next section will allow us to gain instight by hooking into the evolutionary process.
72
+ The framework offers built-in implementations while allowing domain-specific customization through its extensible and swapable components.
131
73
 
132
- ### Evolvable Population Hooks
74
+ ## Genes
133
75
 
134
- The following class methods can be implemented on your Evolvable class, e.g. HelloWorld, to hook into the Population#evolve lifecycle. This is useful for logging evolutionary progress, optimizing parameters, and creating interactions with and between evolvable instances.
76
+ {@readme Evolvable::Gene}
135
77
 
136
- 1. `.before_evaluation(population)` - {@readme Evolvable::ClassMethods#before_evaluation}
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
- Let's define `.before_evolution` to print the best value for each generation. We'll also define `HelloWorld#to_s`, which implicitly delegates to `CharGene#to_s` during the string interpolation that happens.
80
+ ## Populations
141
81
 
142
- ```ruby
143
- class HelloWorld
144
- include Evolvable
82
+ {@readme Evolvable::Population}
145
83
 
146
- def self.before_evolution(population)
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
- def to_s
155
- @to_s ||= genes.join
156
- end
88
+ {@readme Evolvable::Evaluation}
157
89
 
158
- # ...
159
- end
160
- ```
90
+ {@readme Evolvable::Goal}
161
91
 
162
- Finally we can **evolve the population with the `Evolvable::Population#evolve` instance method**.
92
+ Example goal implementation that prioritizes evolvables with fitness values within a specific range:
163
93
 
164
- ```ruby
165
- population.evolve
166
- ```
94
+ {@example Evolvable::Goal}
167
95
 
168
- **You now know the fundamental steps to building evolvable programs of endless complexity in any domain!** 🐸 The exact implementation for the command line demo can be found in [exe/hello](https://github.com/mattruzicka/evolvable/blob/main/exe/hello) and [examples/hello_world.rb](https://github.com/mattruzicka/evolvable/blob/main/examples/hello_world.rb).
96
+ [Evaluation Documentation]({@string Evolvable::DOC_URL}/Evolvable/Evaluation)
169
97
 
170
- ## Concepts
98
+ [Goal Documentation]({@string Evolvable::DOC_URL}/Evolvable/Goal)
171
99
 
172
- [Populations](#populations) are composed of evolvables which are composed of genes. Evolvables orchestrate behaviors by delegating to gene objects. Collections of genes are organized into genomes and constitute the [search space](#search-space). [Evaluation](#evaluation) and [evolution](#evolution) objects are used to evolve populations. By default, evolution is composed of [selection](#selection), [combination](#combination), and [mutation](#mutation).
100
+ ## Evolution
173
101
 
174
- The following concept map depicts how genes flow through populations.
102
+ {@readme Evolvable::Evolution}
175
103
 
176
- ![Concept Map](https://github.com/mattruzicka/evolvable/raw/main/examples/images/diagram.png)
104
+ [Evolution Documentation]({@string Evolvable::DOC_URL}/Evolvable/Evolution)
177
105
 
178
- Evolvable is designed with extensibility in mind. Evolvable objects such as [evaluation](#evaluation), [evolution](#evolution), [selection](#selection), [combination](#combination), and [mutation](#mutation) can be extended and swapped, potentially in ways that alter the above graph.
106
+ ## Selection
179
107
 
180
- ## Genes
181
- {@readme Evolvable::Gene}
108
+ {@readme Evolvable::Selection}
182
109
 
183
- {@example Evolvable::Gene}
110
+ [Selection Documentation]({@string Evolvable::DOC_URL}/Evolvable/Selection)
184
111
 
185
- [Documentation](https://rubydoc.info/github/mattruzicka/evolvable/Evolvable/Gene)
112
+ ## Combination
186
113
 
187
- ## Populations
188
- {@readme Evolvable::Population}
114
+ {@readme Evolvable::GeneCombination}
189
115
 
190
- {@example Evolvable::Population}
116
+ [Combination Documentation]({@string Evolvable::DOC_URL}/Evolvable/Combination)
191
117
 
192
- [Documentation](https://rubydoc.info/github/mattruzicka/evolvable/Evolvable/Population)
118
+ **Point Crossover**
193
119
 
194
- ## Evaluation
195
- {@readme Evolvable::Evaluation}
120
+ {@readme Evolvable::PointCrossover}
196
121
 
197
- {@example Evolvable::Evaluation}
122
+ [PointCrossover Documentation]({@string Evolvable::DOC_URL}/Evolvable/PointCrossover)
198
123
 
199
- [Documentation](https://rubydoc.info/github/mattruzicka/evolvable/Evolvable/Evaluation)
124
+ **Uniform Crossover**
200
125
 
201
- ## Evolution
202
- {@readme Evolvable::Evolution}
126
+ {@readme Evolvable::UniformCrossover}
203
127
 
204
- [Documentation](https://rubydoc.info/github/mattruzicka/evolvable/Evolvable/Evolution)
128
+ [UniformCrossover Documentation]({@string Evolvable::DOC_URL}/Evolvable/UniformCrossover)
205
129
 
206
- ## Selection
207
- {@readme Evolvable::Selection}
130
+ ## Mutation
208
131
 
209
- {@example Evolvable::Selection}
132
+ {@readme Evolvable::Mutation}
210
133
 
134
+ [Mutation Documentation]({@string Evolvable::DOC_URL}/Evolvable/Mutation)
211
135
 
212
- [Documentation](https://rubydoc.info/github/mattruzicka/evolvable/Evolvable/Selection)
136
+ ## Gene Clusters
213
137
 
214
- ## Combination
215
- {@readme Evolvable::GeneCombination}
138
+ {@readme Evolvable::GeneCluster}
216
139
 
217
- [Documentation](https://rubydoc.info/github/mattruzicka/evolvable/Evolvable/Combination)
140
+ [GeneCluster Documentation]({@string Evolvable::DOC_URL}/Evolvable/GeneCluster)
218
141
 
219
- ## Mutation
220
- {@readme Evolvable::Mutation}
142
+ ## Community
221
143
 
222
- {@example Evolvable::Mutation}
144
+ {@readme Evolvable::Community}
223
145
 
224
- [Documentation](https://rubydoc.info/github/mattruzicka/evolvable/Evolvable/Mutation)
146
+ [Community Documentation]({@string Evolvable::DOC_URL}/Evolvable/Community)
225
147
 
226
- ## Search Space
227
- {@readme Evolvable::SearchSpace}
148
+ ## Serialization
228
149
 
229
- {@example Evolvable::SearchSpace}
150
+ {@readme Evolvable::Serializer}
230
151
 
231
- [Documentation](https://rubydoc.info/github/mattruzicka/evolvable/Evolvable/SearchSpace)
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 'byebug'
6
-
7
- Dir['./examples/*.rb'].each { |f| require f }
5
+ require 'debug'
6
+ require 'irb'
8
7
 
9
- ## HelloWorld
10
- # population = HelloWorld.new_population(size: 100,
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
- ## Stickman
17
- # population = Stickman.new_population(size: 5,
18
- # mutation: { probability: 0.3 })
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
- ## AsciiArt
22
- # ascii_art = AsciiArt.new_population(size: 8,
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
- require 'irb'
27
- IRB.start(__FILE__)
18
+ binding.irb