rubyneat 0.3.5.alpha.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.directory +4 -0
- data/.gitignore.orig +20 -0
- data/.idea/.name +1 -0
- data/.idea/.rakeTasks +7 -0
- data/.idea/dictionaries/trader.xml +3 -0
- data/.idea/encodings.xml +5 -0
- data/.idea/misc.xml +5 -0
- data/.idea/modules.xml +9 -0
- data/.idea/rubyneat.iml +197 -0
- data/.idea/runConfigurations/invpend_neat.xml +26 -0
- data/.idea/runConfigurations/sigdebug_neat.xml +24 -0
- data/.idea/runConfigurations/xor_neat.xml +26 -0
- data/.idea/runConfigurations/xordebug_neat.xml +24 -0
- data/.idea/runConfigurations/xorsin_neat.xml +24 -0
- data/.idea/scopes/scope_settings.xml +5 -0
- data/.idea/vcs.xml +7 -0
- data/.idea/workspace.xml +1124 -0
- data/.semver +5 -0
- data/.yardoc/checksums +11 -0
- data/.yardoc/object_types +0 -0
- data/.yardoc/objects/root.dat +0 -0
- data/.yardoc/proxy_types +0 -0
- data/Gemfile +32 -0
- data/Gemfile.lock +135 -0
- data/Gemfile.lock.orig +147 -0
- data/Guardfile +8 -0
- data/Rakefile +61 -0
- data/bin/neat +83 -0
- data/config/application.rb +5 -0
- data/doc/ControllerPoint.html +125 -0
- data/doc/CuteA.html +286 -0
- data/doc/CuteB.html +297 -0
- data/doc/DSL.html +883 -0
- data/doc/NEAT/BasicNeuronTypes/BiasNeuron.html +518 -0
- data/doc/NEAT/BasicNeuronTypes/CosineNeuron.html +274 -0
- data/doc/NEAT/BasicNeuronTypes/InputNeuron.html +366 -0
- data/doc/NEAT/BasicNeuronTypes/SigmoidNeuron.html +275 -0
- data/doc/NEAT/BasicNeuronTypes/SineNeuron.html +274 -0
- data/doc/NEAT/BasicNeuronTypes/TanhNeuron.html +274 -0
- data/doc/NEAT/BasicNeuronTypes.html +136 -0
- data/doc/NEAT/Controller/NeatSettings.html +3985 -0
- data/doc/NEAT/Controller.html +2490 -0
- data/doc/NEAT/Critter/Genotype/Gene.html +979 -0
- data/doc/NEAT/Critter/Genotype.html +1601 -0
- data/doc/NEAT/Critter/Phenotype.html +603 -0
- data/doc/NEAT/Critter.html +1037 -0
- data/doc/NEAT/DSL.html +1255 -0
- data/doc/NEAT/Evaluator.html +420 -0
- data/doc/NEAT/Evolver/CritterOp.html +551 -0
- data/doc/NEAT/Evolver.html +602 -0
- data/doc/NEAT/Expressor.html +327 -0
- data/doc/NEAT/Graph/DependencyResolver.html +478 -0
- data/doc/NEAT/Graph/GraphException.html +123 -0
- data/doc/NEAT/Graph.html +402 -0
- data/doc/NEAT/NeatException.html +123 -0
- data/doc/NEAT/NeatOb.html +567 -0
- data/doc/NEAT/Neuron.html +1067 -0
- data/doc/NEAT/Operator.html +162 -0
- data/doc/NEAT/Population.html +1961 -0
- data/doc/NEAT/Trait.html +169 -0
- data/doc/NEAT.html +588 -0
- data/doc/_index.html +373 -0
- data/doc/class_list.html +54 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +57 -0
- data/doc/css/style.css +339 -0
- data/doc/file_list.html +53 -0
- data/doc/frames.html +26 -0
- data/doc/index.html +373 -0
- data/doc/js/app.js +219 -0
- data/doc/js/full_list.js +178 -0
- data/doc/js/jquery.js +4 -0
- data/doc/method_list.html +1415 -0
- data/doc/top-level-namespace.html +164 -0
- data/foo/foo_aquarium_example.rb +38 -0
- data/foo/foo_gosu.rb +99 -0
- data/foo/foo_rubygoo.rb +104 -0
- data/foo/foo_sdl.rb +34 -0
- data/foo/icon.png +0 -0
- data/lib/rubyneat/critter.rb +374 -0
- data/lib/rubyneat/default_neat.rb +10 -0
- data/lib/rubyneat/dsl.rb +130 -0
- data/lib/rubyneat/evaluator.rb +51 -0
- data/lib/rubyneat/evolver.rb +315 -0
- data/lib/rubyneat/expressor.rb +110 -0
- data/lib/rubyneat/graph.rb +95 -0
- data/lib/rubyneat/neuron.rb +152 -0
- data/lib/rubyneat/population.rb +227 -0
- data/lib/rubyneat/rubyneat.rb +429 -0
- data/lib/rubyneat.rb +8 -0
- data/neater/invpend_neat.rb +150 -0
- data/neater/rnlib/inverted_pendulum.rb +380 -0
- data/neater/rnlib/xor.rb +10 -0
- data/neater/sigdebug_neat.rb +136 -0
- data/neater/xor_neat.rb +137 -0
- data/neater/xoranalog_neat.rb +138 -0
- data/neater/xorsin_neat.rb +143 -0
- data/projectFilesBackup/.idea/rubyneat.iml +180 -0
- data/public/.directory +4 -0
- data/public/background.png +0 -0
- data/public/background.xcf +0 -0
- data/public/cart.png +0 -0
- data/public/cart.xcf +0 -0
- data/public/metalpoles_molton_ball_l.jpg +0 -0
- data/public/old_background.png +0 -0
- data/public/pointer.png +0 -0
- data/public/pointer.xcf +0 -0
- data/public/pole.kra +0 -0
- data/public/pole.png +0 -0
- data/public/pole.xcf +0 -0
- data/public/wheel-of-year-stone-DD-131-WOYS.jpg +0 -0
- data/public/wheel.png +0 -0
- data/public/wheel.xcf +0 -0
- data/public/wood-planks-texture.jpg +0 -0
- data/rdoc/ControllerPoint.html +116 -0
- data/rdoc/CuteA.html +177 -0
- data/rdoc/CuteB.html +178 -0
- data/rdoc/DSLSetup.html +177 -0
- data/rdoc/GameTestWindow.html +242 -0
- data/rdoc/GameWindow.html +292 -0
- data/rdoc/Gemfile.html +215 -0
- data/rdoc/Gemfile_lock.html +327 -0
- data/rdoc/GraphTest.html +210 -0
- data/rdoc/Guardfile.html +198 -0
- data/rdoc/InvertedPendulum/Cart.html +668 -0
- data/rdoc/InvertedPendulum/DSL.html +259 -0
- data/rdoc/InvertedPendulum/InvPendWindow.html +402 -0
- data/rdoc/InvertedPendulum.html +198 -0
- data/rdoc/Logger.html +98 -0
- data/rdoc/NEAT/BasicNeuronTypes/BiasNeuron.html +265 -0
- data/rdoc/NEAT/BasicNeuronTypes/CosineNeuron.html +162 -0
- data/rdoc/NEAT/BasicNeuronTypes/InputNeuron.html +206 -0
- data/rdoc/NEAT/BasicNeuronTypes/SigmoidNeuron.html +162 -0
- data/rdoc/NEAT/BasicNeuronTypes/SineNeuron.html +162 -0
- data/rdoc/NEAT/BasicNeuronTypes/TanhNeuron.html +161 -0
- data/rdoc/NEAT/BasicNeuronTypes.html +107 -0
- data/rdoc/NEAT/Controller/NeatSettings.html +880 -0
- data/rdoc/NEAT/Controller.html +729 -0
- data/rdoc/NEAT/Critter/Genotype/Gene.html +457 -0
- data/rdoc/NEAT/Critter/Genotype.html +735 -0
- data/rdoc/NEAT/Critter/Phenotype.html +330 -0
- data/rdoc/NEAT/Critter.html +489 -0
- data/rdoc/NEAT/DSL.html +729 -0
- data/rdoc/NEAT/Evaluator.html +256 -0
- data/rdoc/NEAT/Evolver/CritterOp.html +349 -0
- data/rdoc/NEAT/Evolver.html +891 -0
- data/rdoc/NEAT/Expressor.html +402 -0
- data/rdoc/NEAT/Graph/DependencyResolver.html +291 -0
- data/rdoc/NEAT/Graph/GraphException.html +105 -0
- data/rdoc/NEAT/Graph.html +263 -0
- data/rdoc/NEAT/NeatException.html +105 -0
- data/rdoc/NEAT/NeatOb.html +325 -0
- data/rdoc/NEAT/Neuron.html +481 -0
- data/rdoc/NEAT/Operator.html +109 -0
- data/rdoc/NEAT/Population.html +935 -0
- data/rdoc/NEAT/Trait.html +117 -0
- data/rdoc/NEAT.html +422 -0
- data/rdoc/Object.html +384 -0
- data/rdoc/Phi.html +98 -0
- data/rdoc/Player.html +383 -0
- data/rdoc/Rakefile.html +254 -0
- data/rdoc/RubyNEAT/Application.html +105 -0
- data/rdoc/RubyNEAT.html +98 -0
- data/rdoc/SDL/Event2.html +98 -0
- data/rdoc/SDL.html +98 -0
- data/rdoc/Vector.html +195 -0
- data/rdoc/created.rid +125 -0
- data/rdoc/doc/ControllerPoint_html.html +299 -0
- data/rdoc/doc/CuteA_html.html +438 -0
- data/rdoc/doc/CuteB_html.html +436 -0
- data/rdoc/doc/DSL_html.html +992 -0
- data/rdoc/doc/NEAT/BasicNeuronTypes/BiasNeuron_html.html +617 -0
- data/rdoc/doc/NEAT/BasicNeuronTypes/CosineNeuron_html.html +413 -0
- data/rdoc/doc/NEAT/BasicNeuronTypes/InputNeuron_html.html +498 -0
- data/rdoc/doc/NEAT/BasicNeuronTypes/SigmoidNeuron_html.html +413 -0
- data/rdoc/doc/NEAT/BasicNeuronTypes/SineNeuron_html.html +413 -0
- data/rdoc/doc/NEAT/BasicNeuronTypes/TanhNeuron_html.html +412 -0
- data/rdoc/doc/NEAT/BasicNeuronTypes_html.html +310 -0
- data/rdoc/doc/NEAT/Controller/NeatSettings_html.html +3324 -0
- data/rdoc/doc/NEAT/Controller_html.html +2212 -0
- data/rdoc/doc/NEAT/Critter/Genotype/Gene_html.html +997 -0
- data/rdoc/doc/NEAT/Critter/Genotype_html.html +1556 -0
- data/rdoc/doc/NEAT/Critter/Phenotype_html.html +687 -0
- data/rdoc/doc/NEAT/Critter_html.html +1037 -0
- data/rdoc/doc/NEAT/DSL_html.html +1349 -0
- data/rdoc/doc/NEAT/Evaluator_html.html +556 -0
- data/rdoc/doc/NEAT/Evolver/CritterOp_html.html +690 -0
- data/rdoc/doc/NEAT/Evolver_html.html +677 -0
- data/rdoc/doc/NEAT/Expressor_html.html +468 -0
- data/rdoc/doc/NEAT/Graph/DependencyResolver_html.html +598 -0
- data/rdoc/doc/NEAT/Graph/GraphException_html.html +299 -0
- data/rdoc/doc/NEAT/Graph_html.html +527 -0
- data/rdoc/doc/NEAT/NeatException_html.html +299 -0
- data/rdoc/doc/NEAT/NeatOb_html.html +671 -0
- data/rdoc/doc/NEAT/Neuron_html.html +1095 -0
- data/rdoc/doc/NEAT/Operator_html.html +337 -0
- data/rdoc/doc/NEAT/Population_html.html +1795 -0
- data/rdoc/doc/NEAT/Trait_html.html +344 -0
- data/rdoc/doc/NEAT_html.html +736 -0
- data/rdoc/doc/_index_html.html +559 -0
- data/rdoc/doc/class_list_html.html +369 -0
- data/rdoc/doc/css/common_css.html +188 -0
- data/rdoc/doc/css/full_list_css.html +243 -0
- data/rdoc/doc/css/style_css.html +530 -0
- data/rdoc/doc/file_list_html.html +240 -0
- data/rdoc/doc/frames_html.html +217 -0
- data/rdoc/doc/index_html.html +559 -0
- data/rdoc/doc/js/app_js.html +423 -0
- data/rdoc/doc/js/full_list_js.html +372 -0
- data/rdoc/doc/js/jquery_js.html +1536 -0
- data/rdoc/doc/method_list_html.html +1375 -0
- data/rdoc/doc/top-level-namespace_html.html +317 -0
- data/rdoc/fonts/Lato-Light.ttf +0 -0
- data/rdoc/fonts/Lato-LightItalic.ttf +0 -0
- data/rdoc/fonts/Lato-Regular.ttf +0 -0
- data/rdoc/fonts/Lato-RegularItalic.ttf +0 -0
- data/rdoc/fonts/SourceCodePro-Bold.ttf +0 -0
- data/rdoc/fonts/SourceCodePro-Regular.ttf +0 -0
- data/rdoc/fonts.css +167 -0
- data/rdoc/images/add.png +0 -0
- data/rdoc/images/arrow_up.png +0 -0
- data/rdoc/images/brick.png +0 -0
- data/rdoc/images/brick_link.png +0 -0
- data/rdoc/images/bug.png +0 -0
- data/rdoc/images/bullet_black.png +0 -0
- data/rdoc/images/bullet_toggle_minus.png +0 -0
- data/rdoc/images/bullet_toggle_plus.png +0 -0
- data/rdoc/images/date.png +0 -0
- data/rdoc/images/delete.png +0 -0
- data/rdoc/images/find.png +0 -0
- data/rdoc/images/loadingAnimation.gif +0 -0
- data/rdoc/images/macFFBgHack.png +0 -0
- data/rdoc/images/package.png +0 -0
- data/rdoc/images/page_green.png +0 -0
- data/rdoc/images/page_white_text.png +0 -0
- data/rdoc/images/page_white_width.png +0 -0
- data/rdoc/images/plugin.png +0 -0
- data/rdoc/images/ruby.png +0 -0
- data/rdoc/images/tag_blue.png +0 -0
- data/rdoc/images/tag_green.png +0 -0
- data/rdoc/images/transparent.png +0 -0
- data/rdoc/images/wrench.png +0 -0
- data/rdoc/images/wrench_orange.png +0 -0
- data/rdoc/images/zoom.png +0 -0
- data/rdoc/index.html +282 -0
- data/rdoc/js/darkfish.js +140 -0
- data/rdoc/js/jquery.js +18 -0
- data/rdoc/js/navigation.js +142 -0
- data/rdoc/js/search.js +109 -0
- data/rdoc/js/search_index.js +1 -0
- data/rdoc/js/searcher.js +228 -0
- data/rdoc/rdoc.css +580 -0
- data/rdoc/rubyneat_gemspec.html +387 -0
- data/rdoc/table_of_contents.html +2502 -0
- data/rdoc/xordebug_log.html +170598 -0
- data/rdoc/xorsin_log.html +22569 -0
- data/rubyneat.gemspec +347 -0
- data/rubyneat.gemspec.orig +375 -0
- data/spec/lib/rubyneat/rubyneat_spec.rb +132 -0
- metadata +555 -0
@@ -0,0 +1,374 @@
|
|
1
|
+
require 'rubyneat'
|
2
|
+
|
3
|
+
=begin rdoc
|
4
|
+
= Critter
|
5
|
+
=end
|
6
|
+
module NEAT
|
7
|
+
|
8
|
+
#= Critters for NEAT
|
9
|
+
# The Critter class comprises a Genotype and a Phenotype.
|
10
|
+
# The Genotype comprises Genes and Neurons.
|
11
|
+
class Critter < NeatOb
|
12
|
+
attr_reader :population
|
13
|
+
attr_accessor :genotype, :phenotype
|
14
|
+
|
15
|
+
# Ratings assigned by Evaluator
|
16
|
+
attr_accessor :fitness, :novelty
|
17
|
+
|
18
|
+
# Critter construction. We construct the genotype.
|
19
|
+
# The phenotype will be constructed by the Expressor operator.
|
20
|
+
def initialize(pop, mating = false, &block)
|
21
|
+
super pop.controller
|
22
|
+
@population = pop
|
23
|
+
@genotype = Genotype.new(self, mating)
|
24
|
+
block.(self) unless block.nil?
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get the Critter ready for the Expressor to
|
28
|
+
# express the geneotype.
|
29
|
+
def ready_for_expression!
|
30
|
+
@genotype.wire!
|
31
|
+
@phenotype = NEAT::Critter::Phenotype[self]
|
32
|
+
@phenotype
|
33
|
+
end
|
34
|
+
|
35
|
+
# Exoress this critter using the Expressor plugin.
|
36
|
+
def express!
|
37
|
+
@controller.expressor.express! self
|
38
|
+
end
|
39
|
+
|
40
|
+
# This initializes neurons in preparation for recurrence.
|
41
|
+
# Note that the Critter should already have expressed its
|
42
|
+
# genotype before this is called.
|
43
|
+
def initialize_neurons!
|
44
|
+
@phenotype.initialize_neurons
|
45
|
+
end
|
46
|
+
|
47
|
+
# A single evaluation step. Evaluate and
|
48
|
+
# generate fitness, novelty, etc.
|
49
|
+
# Returns the result.
|
50
|
+
def evaluate!
|
51
|
+
@controller.evaluator.evaluate! self
|
52
|
+
end
|
53
|
+
|
54
|
+
#= Genotype part of the Critter
|
55
|
+
# List of connections, basically.
|
56
|
+
#
|
57
|
+
# Also, basic phentypic expression (which may be overriden by
|
58
|
+
# the expressor)
|
59
|
+
#
|
60
|
+
#= Notes
|
61
|
+
# Currently, all lists of neurons and genes are Hashes. The
|
62
|
+
# neurons are indexed by their own names, and the genes
|
63
|
+
# are indexed by their innovation numbers.
|
64
|
+
#
|
65
|
+
class Genotype < NeatOb
|
66
|
+
# Critter to which we belong
|
67
|
+
attr_accessor :critter
|
68
|
+
|
69
|
+
# Genes keyed by innovation numbers
|
70
|
+
attr_accessor :genes
|
71
|
+
|
72
|
+
# List of neurons hashed by name
|
73
|
+
attr_accessor :neurons
|
74
|
+
|
75
|
+
# Instantiations of neural inputs and outputs
|
76
|
+
attr_reader :neural_inputs, :neural_outputs
|
77
|
+
|
78
|
+
# This will be set to true if there are dangling neurons.
|
79
|
+
attr_accessor :dangling_neurons
|
80
|
+
alias :dangling_neurons? :dangling_neurons
|
81
|
+
|
82
|
+
# Map neurons to the genes that marks them as output
|
83
|
+
# { oneu_name => [ gene_1, gene_2,... gene_n], ...}
|
84
|
+
# Just take the in_neuron name and the weight to do
|
85
|
+
# the call to that neuron function with the appropriate weights
|
86
|
+
attr_reader :neural_gene_map
|
87
|
+
|
88
|
+
def initialize(critter, mating = false, &block)
|
89
|
+
super critter.controller
|
90
|
+
@critter = critter
|
91
|
+
|
92
|
+
# Initialize basic structures
|
93
|
+
@genes = nil
|
94
|
+
@neural_inputs = Hash[@critter.population.input_neurons.map { |sym, ineu|
|
95
|
+
[sym, ineu.new(@controller, sym)]
|
96
|
+
}]
|
97
|
+
|
98
|
+
@neural_outputs = Hash[@critter.population.output_neurons.map { |sym, ineu|
|
99
|
+
[sym, ineu.new(@controller, sym)]
|
100
|
+
}]
|
101
|
+
@neurons = @neural_inputs.clone # this must be a shallow clone!
|
102
|
+
@neurons.merge! @neural_outputs
|
103
|
+
|
104
|
+
@controller.evolver.gen_initial_genes!(self) unless mating
|
105
|
+
block.(self) unless block.nil?
|
106
|
+
end
|
107
|
+
|
108
|
+
# We add genes given here to the genome.
|
109
|
+
# An array of genes is returned from the block
|
110
|
+
# and we simply add them in.
|
111
|
+
# @param [boolean] clean
|
112
|
+
# @param [Proc] block
|
113
|
+
def neucleate(clean: true, &block)
|
114
|
+
genes = Hash[block.(self).map { |g|
|
115
|
+
g.genotype = self
|
116
|
+
[g.innovation, g] }]
|
117
|
+
if clean
|
118
|
+
@genes = genes
|
119
|
+
else
|
120
|
+
@genes.merge! genes
|
121
|
+
end
|
122
|
+
nuke_redundancies!
|
123
|
+
end
|
124
|
+
|
125
|
+
# Remove any redundancies in the genome,
|
126
|
+
# any genes refering to the same two neurons.
|
127
|
+
# Simply choose one and delete the rest.
|
128
|
+
# TODO: implement nuke_redundancies!
|
129
|
+
def nuke_redundancies!
|
130
|
+
log.warn 'nuke_redundancies! NIY'
|
131
|
+
end
|
132
|
+
|
133
|
+
# Make the neurons forget their wiring.
|
134
|
+
def forget!
|
135
|
+
@neurons.each { |name, neu| neu.clear_graph }
|
136
|
+
@neural_gene_map = Hash.new {|h, k| h[k] = [] }
|
137
|
+
end
|
138
|
+
|
139
|
+
# Wire up the neurons based on the genes.
|
140
|
+
def wire!
|
141
|
+
forget!
|
142
|
+
@genes.each do |innov, gene|
|
143
|
+
if gene.enabled?
|
144
|
+
raise NeatException.new "Can't find #{gene.out_neuron}" if @neurons[gene.out_neuron].nil?
|
145
|
+
@neurons[gene.out_neuron] << @neurons[gene.in_neuron]
|
146
|
+
@neural_gene_map[gene.out_neuron] << gene unless gene.in_neuron.nil?
|
147
|
+
end
|
148
|
+
end unless @genes.nil?
|
149
|
+
if @genes.nil?
|
150
|
+
$log.error 'Genes Not Present'
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Add new neurons to the fold
|
155
|
+
def add_neurons(*neus)
|
156
|
+
neus.each do |neu|
|
157
|
+
@neurons[neu.name] = neu
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Genes added here MUST correspond to pre-existing neurons.
|
162
|
+
# Be sure to do add_neurons first!!!!
|
163
|
+
def add_genes(*genes)
|
164
|
+
genes.each do |gene|
|
165
|
+
raise NeatException.new "Neuron #{gene.in_neuron} missing" unless @neurons.member? gene.in_neuron
|
166
|
+
raise NeatException.new "Neuron #{gene.out_neuron} missing" unless @neurons.member? gene.out_neuron
|
167
|
+
@genes[gene.innovation] = gene
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# We take the neural hashes (presumably from other neurons), and innervate them.
|
172
|
+
# We do this in distinctions based on the neuron's names.
|
173
|
+
# FIXME We need to randomly select a neuron in the case of clashes.
|
174
|
+
# @param [Hash] hneus -- hashes of neurons to innervate
|
175
|
+
def innervate!(*hneus)
|
176
|
+
hneus.each do |neus|
|
177
|
+
@neurons.merge! neus.dclone
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Go through the list of neurons and drop
|
182
|
+
# any neurons not referenced by the genes.
|
183
|
+
#
|
184
|
+
# Then go through the genes and drop any that
|
185
|
+
# are dangling (i.e. no matching neurons)
|
186
|
+
#
|
187
|
+
# Then make sure that @neural_inputs and @neural_outputs reference the actual
|
188
|
+
# instance neurons in @neurons
|
189
|
+
#
|
190
|
+
# TODO add this circularity check to prune!
|
191
|
+
def prune!
|
192
|
+
# Take care of dangling neurons
|
193
|
+
neunames = @genes.values.map{|g| [g.in_neuron, g.out_neuron]}.flatten.to_set
|
194
|
+
@neurons = Hash[@neurons.values.reject do |n|
|
195
|
+
not neunames.member? n.name
|
196
|
+
end.map do |n|
|
197
|
+
[n.name, n]
|
198
|
+
end]
|
199
|
+
|
200
|
+
# Take care of dangling genes
|
201
|
+
@genes = Hash[@genes.values.reject do |gene|
|
202
|
+
not (@neurons.member?(gene.in_neuron) and @neurons.member?(gene.out_neuron))
|
203
|
+
end.map do |gene|
|
204
|
+
[gene.name, gene]
|
205
|
+
end]
|
206
|
+
|
207
|
+
# Make sure @neural_inputs and @neural_outputs are consistent
|
208
|
+
@neural_inputs = Hash[@neural_inputs.values.map{|n| [n.name, @neurons[n.name]]}]
|
209
|
+
@neural_outputs = Hash[@neural_outputs.values.map{|n| [n.name, @neurons[n.name]]}]
|
210
|
+
end
|
211
|
+
|
212
|
+
# Calculate the cost of this genotype.
|
213
|
+
def fitness_cost
|
214
|
+
p = @controller.parms
|
215
|
+
p.fitness_cost_per_neuron * @neurons.size + p.fitness_cost_per_gene * @genes.size
|
216
|
+
end
|
217
|
+
|
218
|
+
def dump_s
|
219
|
+
to_s + "\ngenes:\n" + @genes.map{|k, gene|
|
220
|
+
gene.to_s}.join("\n") + "\nneurons:\n" + @neurons.map{|k, neu|
|
221
|
+
neu.to_s}.join("\n")
|
222
|
+
end
|
223
|
+
|
224
|
+
#= Gene Specification
|
225
|
+
# The Gene specifices a singlular input and
|
226
|
+
# output neuron, which represents a connection
|
227
|
+
# between them, along with the weight of that
|
228
|
+
# connection, which may be positive, negative, or zero.
|
229
|
+
#
|
230
|
+
# There is also the enabled flag
|
231
|
+
class Gene < NeatOb
|
232
|
+
# parent genotype
|
233
|
+
attr_accessor :genotype
|
234
|
+
|
235
|
+
# innovation number
|
236
|
+
attr_accessor :innovation
|
237
|
+
|
238
|
+
# input neuron's name (where our output goes)
|
239
|
+
# ouptut neuron's name (neuron to be queried)
|
240
|
+
attr_accessor :in_neuron, :out_neuron
|
241
|
+
|
242
|
+
# weight of the connection
|
243
|
+
attr_accessor :weight
|
244
|
+
# Is this gene enabled?
|
245
|
+
attr_accessor :enabled
|
246
|
+
|
247
|
+
def initialize(genotype, &block)
|
248
|
+
super genotype.controller
|
249
|
+
@genotype = genotype
|
250
|
+
@enabled = true
|
251
|
+
@innovation = NEAT::new_innovation
|
252
|
+
@in_neuron = @out_neuron = nil
|
253
|
+
block.(self) unless block.nil?
|
254
|
+
end
|
255
|
+
|
256
|
+
def enabled? ; @enabled ; end
|
257
|
+
def disabled? ; not enabled? ; end
|
258
|
+
|
259
|
+
# Create a new Gene and set it up fully.
|
260
|
+
## genotype -- genotype
|
261
|
+
## input -- name of input neuron connection
|
262
|
+
## output -- name of output neuron connection
|
263
|
+
## weight -- weight to give neuron (optional)
|
264
|
+
## innov -- innovation number of gene (optional)
|
265
|
+
def self.[](genotype, input, output, weight = 0.0, innov = nil)
|
266
|
+
g = Gene.new genotype
|
267
|
+
g.in_neuron = (input.kind_of? Symbol) ? input : input.name
|
268
|
+
g.out_neuron = (output.kind_of? Symbol) ? output : output.name
|
269
|
+
g.weight = weight
|
270
|
+
g.innovation = innov unless innov.nil?
|
271
|
+
return g
|
272
|
+
end
|
273
|
+
|
274
|
+
def to_s
|
275
|
+
super + "[i%s,w%s,%s]" % [@innovation, @weight, self.enabled?]
|
276
|
+
end
|
277
|
+
alias_method :dump_s, :to_s
|
278
|
+
|
279
|
+
end
|
280
|
+
|
281
|
+
end
|
282
|
+
|
283
|
+
#= Phenotype part of the Critter
|
284
|
+
# This is created by Evolver.
|
285
|
+
class Phenotype < NeatOb
|
286
|
+
include Math
|
287
|
+
|
288
|
+
# Critter to which we belong
|
289
|
+
attr_accessor :critter
|
290
|
+
|
291
|
+
# Expressed code as a string (that was instance_eval()ed)
|
292
|
+
attr_accessor :code
|
293
|
+
|
294
|
+
def self.[](critter)
|
295
|
+
ph = Phenotype.new critter.controller
|
296
|
+
ph.critter = critter
|
297
|
+
ph.code = "## Phenotype Code %s for critter %s\n" % [ph.name, critter.name]
|
298
|
+
return ph
|
299
|
+
end
|
300
|
+
|
301
|
+
# Take what is in code and express that!
|
302
|
+
def express!
|
303
|
+
instance_eval @code
|
304
|
+
self
|
305
|
+
end
|
306
|
+
|
307
|
+
# This function is re-written by Expressor -- with parameters and all.
|
308
|
+
# It returns a "response" in the form of a response hash.
|
309
|
+
# TODO This *is* network activation, so we should rename this at a later date...
|
310
|
+
def stimulate
|
311
|
+
nil
|
312
|
+
end
|
313
|
+
|
314
|
+
# This gives us a complete
|
315
|
+
def to_s
|
316
|
+
"## %s\n%s" % [super, @code]
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
# Compare ourselves against another critter for
|
321
|
+
# compability.
|
322
|
+
#
|
323
|
+
# The function to be used here is:
|
324
|
+
## distance = c1*E + c2*D + c3*W
|
325
|
+
#
|
326
|
+
# Where:
|
327
|
+
## E, D - The number of excess and disjoint genes repesctively.
|
328
|
+
## N - The number of genes in the largest genome.
|
329
|
+
## W - The sum of absolute weight differences.
|
330
|
+
#
|
331
|
+
# This is a variation of the formulation suggested by the Stanley
|
332
|
+
# paper, which normalizes the E and D terms by N.
|
333
|
+
def compare(oc)
|
334
|
+
c1 = @controller.parms.excess_coefficient
|
335
|
+
c2 = @controller.parms.disjoint_coefficient
|
336
|
+
c3 = @controller.parms.weight_coefficient
|
337
|
+
e = excess(oc)
|
338
|
+
d = disjoint(oc)
|
339
|
+
w = weight_diff(oc)
|
340
|
+
return c1 * e + c2 * d + c3 * w
|
341
|
+
end
|
342
|
+
|
343
|
+
# Critter print
|
344
|
+
def dump_s
|
345
|
+
to_s + @genotype.dump_s + "\n" + @phenotype.to_s + "\n"
|
346
|
+
end
|
347
|
+
|
348
|
+
private
|
349
|
+
# Return a count of excesses.
|
350
|
+
def excess(oc)
|
351
|
+
(@genotype.genes.size - oc.genotype.genes.size).abs
|
352
|
+
end
|
353
|
+
|
354
|
+
# Return the count of disjoint genes
|
355
|
+
def disjoint(oc)
|
356
|
+
a = @genotype.genes.keys
|
357
|
+
b = oc.genotype.genes.keys
|
358
|
+
(a - b).size + (b - a).size - excess(oc)
|
359
|
+
end
|
360
|
+
|
361
|
+
#
|
362
|
+
def weight_diff(oc)
|
363
|
+
ag = @genotype.genes
|
364
|
+
bg = oc.genotype.genes
|
365
|
+
matches = ag.keys & bg.keys
|
366
|
+
unless matches.empty?
|
367
|
+
matches.map{|i| (ag[i].weight - bg[i].weight).abs}.reduce{|w, ws| w + ws} / matches.size
|
368
|
+
else
|
369
|
+
0
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
end
|
374
|
+
end
|
data/lib/rubyneat/dsl.rb
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'rubyneat/rubyneat'
|
2
|
+
|
3
|
+
=begin rdoc
|
4
|
+
= RubyNEAT DSL
|
5
|
+
DSL is a domain-specific language for RubyNEAT to allow you to configure the NEAT engine
|
6
|
+
for various evolutionary projects.
|
7
|
+
=end
|
8
|
+
module NEAT
|
9
|
+
module DSL
|
10
|
+
include NEAT
|
11
|
+
include NEAT::BasicNeuronTypes
|
12
|
+
include Math
|
13
|
+
|
14
|
+
# DSL -- Define defines the parameters to the controller.
|
15
|
+
def define(name = NEAT.random_name_generator, &block)
|
16
|
+
[
|
17
|
+
:inputs,
|
18
|
+
:outputs,
|
19
|
+
:hidden # we really don't care about mapping hidden neurons, but we'll ignore them later.
|
20
|
+
].each do |iometh|
|
21
|
+
instance_eval %Q[
|
22
|
+
def #{iometh}(nodes = nil, &block)
|
23
|
+
neui = unless nodes.nil?
|
24
|
+
nodes
|
25
|
+
else
|
26
|
+
block.()
|
27
|
+
end
|
28
|
+
NEAT::controller.neural_#{iometh} = if neui.kind_of? Hash
|
29
|
+
neui
|
30
|
+
else
|
31
|
+
Hash[neui.map{|n| [NEAT::random_name_generator, n]}]
|
32
|
+
end
|
33
|
+
end]
|
34
|
+
end
|
35
|
+
block.(NEAT::controller)
|
36
|
+
end
|
37
|
+
|
38
|
+
# DSL -- Run evolution
|
39
|
+
def evolve(&block)
|
40
|
+
# Query function is called with the sequence (time evolution) number,
|
41
|
+
# and returns an array or hash of parameters that will be given
|
42
|
+
# to the input nodes. In the case of hash, the keys in the hash
|
43
|
+
# shall correspond to the names given to the input neurons.
|
44
|
+
def query(&block)
|
45
|
+
NEAT::controller.query_func = block
|
46
|
+
end
|
47
|
+
|
48
|
+
def recurrence(&block)
|
49
|
+
NEAT::controller.recurrence_func = block
|
50
|
+
end
|
51
|
+
|
52
|
+
# fitness function calls the block with 2 vectors or two hashes, input and output
|
53
|
+
# vectors of the critter being evaluated for fitness, as well as a sequence
|
54
|
+
# number that can be used to index what the actual output should be.
|
55
|
+
# |vin, vout, seq|
|
56
|
+
def fitness(&block)
|
57
|
+
NEAT::controller.fitness_func = block
|
58
|
+
end
|
59
|
+
|
60
|
+
# Fitness ordering -- given 2 fitness numbers,
|
61
|
+
# use the <=> to compare them (or the equivalent, following
|
62
|
+
# the +1, 0, -1 that is in the sense of <=>)
|
63
|
+
def compare(&block)
|
64
|
+
NEAT::controller.compare_func = block
|
65
|
+
end
|
66
|
+
|
67
|
+
# Calculation to add the cost to the fitness, resulting in a fitness
|
68
|
+
# that incorporates the cost for sorting purposes.
|
69
|
+
def cost(&block)
|
70
|
+
NEAT::controller.cost_func = block
|
71
|
+
end
|
72
|
+
|
73
|
+
# Stop the progression once the fitness criteria is reached
|
74
|
+
# for the most fit critter
|
75
|
+
def stop_on_fitness(&block)
|
76
|
+
NEAT::controller.stop_on_fit_func = block
|
77
|
+
end
|
78
|
+
|
79
|
+
# Helper function to
|
80
|
+
# Condition boolean vectors to be +1 if true, -1 if false (0 if sigmoid)
|
81
|
+
def condition_boolean_vector(vec, sig = :tanh)
|
82
|
+
vec.map{|b| b ? 1 : ((sig == :sigmoid) ? 0 : -1)}
|
83
|
+
end
|
84
|
+
|
85
|
+
# Helper function to
|
86
|
+
# Uncondition boolean vectors to be +1 if true, -1 if false
|
87
|
+
# FIXME we need a better discrimination function
|
88
|
+
def uncondition_boolean_vector(vec, sig = :tanh)
|
89
|
+
vec.map{|o| o > ((sig == :sigmoid) ? 0.5 : 0) ? true : false}
|
90
|
+
end
|
91
|
+
|
92
|
+
# Helper function to do a simple fitness calculation
|
93
|
+
# on the basis of the sum of the square of the diffences
|
94
|
+
# of the element in the two vectors.
|
95
|
+
def simple_fitness_error(v1, v2)
|
96
|
+
sqrt v1.zip(v2).map{|a, b| (a - b) ** 2.0}.reduce{|m, c| m + c}
|
97
|
+
end
|
98
|
+
|
99
|
+
block.(NEAT::controller)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Report on evaluations
|
103
|
+
def report(&block)
|
104
|
+
NEAT::controller.report_hook = block
|
105
|
+
end
|
106
|
+
|
107
|
+
# Run the engine. The block is called on each generation.
|
108
|
+
def run_engine(&block)
|
109
|
+
NEAT::controller.end_run_func = block
|
110
|
+
NEAT::controller.run
|
111
|
+
end
|
112
|
+
|
113
|
+
# This is used to handle the details of our DSL.
|
114
|
+
def method_missing(m, *args, &block)
|
115
|
+
# we want to catch parameters settings here.
|
116
|
+
if NEAT::controller.parms.respond_to? (assignment = (m.to_s + '=').to_sym)
|
117
|
+
raise NeatException.new("Missing value(s) to %s" % m) if args.empty?
|
118
|
+
val = (args.size == 1) ? args[0] : args
|
119
|
+
$log.debug { "Caught method %s with parameter of %s" % [assignment, val] }
|
120
|
+
NEAT::controller.parms.send(assignment, val)
|
121
|
+
else
|
122
|
+
super
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# FIXME: This needs to better specified for cases in which there may be multiple
|
129
|
+
# Controllers.
|
130
|
+
require 'rubyneat/default_neat'
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rubyneat/rubyneat'
|
2
|
+
|
3
|
+
module NEAT
|
4
|
+
#= Evaluator evaluates phenotype of critter for fitness, novelty, etc.
|
5
|
+
# We can have a chain of these evaluators whose outputs are summed, etc.
|
6
|
+
class Evaluator < Operator
|
7
|
+
|
8
|
+
# This is call prior to any sequence evaluation. Here,
|
9
|
+
# we clean up persistent tracking information, etc.
|
10
|
+
def ready_for_evaluation(pop)
|
11
|
+
@crit_hist = {}
|
12
|
+
pop.initialize_for_recurrence!
|
13
|
+
end
|
14
|
+
|
15
|
+
# Evaluate one step of a sequence of evaluations.
|
16
|
+
# For time series and realtime ongoing evaluations,
|
17
|
+
# @controller.seq_num governs where in the sequence
|
18
|
+
# everything is.
|
19
|
+
#
|
20
|
+
# Returns [vin, vout], where vin is the input vector,
|
21
|
+
# and vout in the output vector from the critter.
|
22
|
+
# FIXME: this should not really have to deal with an error.
|
23
|
+
# FIXME: the error should be handled upstream from here.
|
24
|
+
def evaluate!(critter)
|
25
|
+
vin = @controller.query_func.(@controller.seq_num)
|
26
|
+
@crit_hist[critter] = {} unless @crit_hist.member? critter
|
27
|
+
begin
|
28
|
+
vout = critter.phenotype.stimulate *vin, &@controller.recurrence_func
|
29
|
+
log.debug "Critter #{critter.name}: vin=#{vin}. vout=#{vout}"
|
30
|
+
@crit_hist[critter][@controller.seq_num] = [vin, vout]
|
31
|
+
rescue Exception => e
|
32
|
+
log.error "Exception #{e} on code:\n#{critter.phenotype.code}"
|
33
|
+
@crit_hist[critter][@controller.seq_num] = [vin, :error]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Analyze the evaluation and compute a fitness for the given critter.
|
38
|
+
# Note that if cost_func is set, we call that to integrate the cost to
|
39
|
+
# the fitness average fitness calculated for the fitness vector.
|
40
|
+
def analyze_for_fitness!(critter)
|
41
|
+
fitvec = @crit_hist[critter].map{|seq, vio| @controller.fitness_func.(vio[0], vio[1], seq) }
|
42
|
+
# Average the fitness vector to get a scalar fitness.
|
43
|
+
critter.fitness = unless @controller.cost_func.nil?
|
44
|
+
@controller.cost_func.(fitvec, critter.genotype.fitness_cost)
|
45
|
+
else
|
46
|
+
fitvec.reduce {|a,r| a+r} / fitvec.size.to_f + critter.genotype.fitness_cost
|
47
|
+
end
|
48
|
+
log.debug "Fitness Vector: #{fitvec}, fitness of #{critter.fitness} assigned to #{critter}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|