drp 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,154 @@
1
+
2
+ =begin
3
+
4
+ DRP, Genetic Programming + Grammatical Evolution = Directed Ruby Programming
5
+ Copyright (C) 2006, Christophe McKeon
6
+
7
+ This program is free software; you can redistribute it and/or
8
+ modify it under the terms of the GNU General Public License
9
+ as published by the Free Software Foundation; either version 2
10
+ of the License, or (at your option) any later version.
11
+
12
+ This program is distributed in the hope that it will be useful,
13
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ GNU General Public License for more details.
16
+
17
+ You should have received a copy of the GNU General Public License
18
+ along with this program; if not, write to the Free Softwar Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
+
20
+ =end
21
+
22
+ module DRP
23
+
24
+ module InstanceMethods
25
+
26
+ # this is called when all rule methods are exhausted
27
+ # during the selection process. by default returns nil.
28
+ # you can override this in your extended class,
29
+ # but as a regular method
30
+ # not a rule method to have non-nil value returned, but
31
+ # be sure to accept an array of *args or have the arity
32
+ # correct to handle all your rule methods.
33
+ def default_rule_method *args; end
34
+
35
+ =begin
36
+ # EXTRANEOUS, CAN JUST BE DONE IN :initialize METHOD
37
+ # this is called automatically for you after
38
+ # your objects are initialized but before any codons
39
+ # are used to set weights depths etc..
40
+ # it does nothing by default
41
+ def init_codons; end
42
+ =end
43
+
44
+ # you should reimplement this method in your
45
+ # extended class, that is unless you want the
46
+ # default behaviour of an endless random stream.
47
+ # this is included mostly for quick testing purposes
48
+ def next_codon
49
+ rand
50
+ end
51
+
52
+ # this is what weights and max_depths use to get codons.
53
+ # it defaults to just using next_codon. override it in
54
+ # your extended class to have them use a separate codon stream
55
+ def next_meta_codon
56
+ next_codon
57
+ end
58
+
59
+ # how deep is the current rule method's recursion
60
+ def depth
61
+ @__drp__depth__stack.last
62
+ end
63
+
64
+ # what is the maximum depth attainable by the current rule method.
65
+ # do not confuse this with the class method setter
66
+ def max_depth
67
+ @__drp__rule__method__stack.last.max_depth
68
+ end
69
+
70
+ # don't know if these two are really worth implementing
71
+ # how many time has the current rule method executed
72
+ # including this execution
73
+ #def count
74
+ # @__drp__count__stack.last
75
+ #end
76
+ # how many times has the current rule (all rule methods)
77
+ # executed including this execution
78
+ # def rule_count
79
+ # end
80
+
81
+ # uses next_codon to output to somewhere within the range
82
+ # specified using specified function, unless a block is given
83
+ # in which case it counts the formal parameters to the block,
84
+ # and yields appropriate number of codons using next_codon
85
+ # you may not pass both a block and a range, only one or the other
86
+ def map rng = nil, function = :linear, &b # :yields: next_codon ...
87
+ if block_given?
88
+ if rng
89
+ raise ArgumentError, "both block and #{rng} passed to map", caller
90
+ end
91
+ arity = b.arity
92
+ case arity
93
+ # these are here, and also ordered, for efficiencies sake
94
+ when 1
95
+ yield next_codon
96
+ when 2
97
+ yield next_codon, next_codon
98
+ when 0, -1
99
+ raise ArgumentError, 'block given to map must have 1 or more arguments', caller
100
+ else
101
+ yield *Array.new(arity) { next_codon }
102
+ end
103
+ else
104
+ Utils::map rng, next_codon, function
105
+ end
106
+ end
107
+
108
+ private
109
+
110
+ def __drp__choose__method useable_methods
111
+
112
+ weights = useable_methods.collect do |meth|
113
+ meth.weight
114
+ end
115
+ scale_by = weights.inject(0) do |weight, prev|
116
+ prev + weight
117
+ end
118
+
119
+ weights = if scale_by == 0
120
+ sz = weights.size
121
+ weight = 1.0/sz
122
+ prev_weight = 0
123
+ Array.new(sz) do
124
+ prev_weight = weight + prev_weight
125
+ end
126
+ else
127
+ prev_weight = 0
128
+ weights.collect do |weight|
129
+ prev_weight = weight / scale_by + prev_weight
130
+ end
131
+ end
132
+
133
+ index, codon = -1, next_codon
134
+ weights.detect do |weight|
135
+ index += 1;
136
+ codon < weight
137
+ end
138
+
139
+ useable_methods[index]
140
+
141
+ end
142
+
143
+ def __drp__call__method meth, args
144
+ @__drp__depth__stack.push(meth.depth + 1)
145
+ @__drp__rule__method__stack.push meth
146
+ res = meth.call *args
147
+ @__drp__rule__method__stack.pop
148
+ @__drp__depth__stack.pop
149
+ res
150
+ end
151
+
152
+ end
153
+
154
+ end # module DRP
@@ -0,0 +1,178 @@
1
+
2
+ =begin
3
+
4
+ DRP, Genetic Programming + Grammatical Evolution = Directed Ruby Programming
5
+ Copyright (C) 2006, Christophe McKeon
6
+
7
+ This program is free software; you can redistribute it and/or
8
+ modify it under the terms of the GNU General Public License
9
+ as published by the Free Software Foundation; either version 2
10
+ of the License, or (at your option) any later version.
11
+
12
+ This program is distributed in the hope that it will be useful,
13
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ GNU General Public License for more details.
16
+
17
+ You should have received a copy of the GNU General Public License
18
+ along with this program; if not, write to the Free Softwar Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
+
20
+ =end
21
+
22
+ module DRP
23
+ module SearchAlgorithms
24
+ module PSO
25
+
26
+ VERY_LARGE_NUMBER = 2**29
27
+
28
+ class AbstractParticleSwarmOptimizer
29
+
30
+ attr_reader :global_best_error, :global_best_vector
31
+
32
+ def initialize swarm_size, vector_size
33
+ @swarm_size, @vector_size = swarm_size, vector_size
34
+ end
35
+
36
+ private
37
+
38
+ # must be called by subclass initialize methods
39
+ def init_particles particle_class
40
+ @particles = Array.new(@swarm_size) { particle_class.new(self,@vector_size) }
41
+ set_as_global_best @particles[rand(@swarm_size)]
42
+ end
43
+
44
+ def set_as_global_best particle
45
+ @global_best_vector = particle.vector.dup
46
+ end
47
+
48
+ end
49
+
50
+ class InteractiveParticle
51
+
52
+ attr_accessor :vector
53
+
54
+ def initialize pso, vector_size
55
+ @pso, @vector_size = pso, vector_size
56
+ init_vector
57
+ end
58
+
59
+ def set_as_best
60
+ @best_vector = @vector.dup
61
+ end
62
+ def set_as_global_best
63
+ @pso.set_as_global_best self
64
+ end
65
+
66
+ def roam
67
+ gbest = @pso.global_best_vector
68
+ @vector_size.times do |i|
69
+ c = @vector[i]
70
+ pbest = @best_vector[i]
71
+ @vector[i] = c + 2 * rand * pbest - c + 2 * rand * gbest[i] - c
72
+ end
73
+ end
74
+
75
+ def init_vector
76
+ @vector = Array.new(@vector_size) { rand }
77
+ set_as_best
78
+ end
79
+
80
+ =begin
81
+ def save name
82
+
83
+ end
84
+ def load name
85
+ if @thawed
86
+ # ...
87
+ end
88
+ end
89
+
90
+ def freeze; @thawed = false end
91
+ def thaw; @thawed = true end
92
+ def frozen? !@thawed end
93
+ =end
94
+ end
95
+
96
+ class InteractiveParticleSwarmOptimizer < AbstractParticleSwarmOptimizer
97
+
98
+ attr_reader :particles
99
+
100
+ def initialize swarm_size, vector_size, rebirth = 0.0
101
+ super
102
+ init_particles InteractiveParticle
103
+ end
104
+ def each
105
+ @particles.each do |p|
106
+ yield p
107
+ end
108
+ end
109
+ def roam_all
110
+ each { |p| p.roam }
111
+ end
112
+ def reinit_all
113
+ each { |p| p.init_vector }
114
+ end
115
+
116
+ end
117
+
118
+ class Particle < InteractiveParticle
119
+ def initialize pso, vector_size
120
+ @best_error = VERY_LARGE_NUMBER
121
+ super
122
+ end
123
+ def optimize error
124
+ if error < @best_error
125
+ @best_error = error
126
+ set_as_best
127
+ end
128
+ end
129
+ end
130
+
131
+ class ParticleSwarmOptimizer < AbstractParticleSwarmOptimizer
132
+
133
+ def initialize swarm_size, vector_size, rebirth = 0.0
134
+ @global_best_error = VERY_LARGE_NUMBER
135
+ @num_reborn = (swarm_size * rebirth).to_i
136
+ @rebirth_index = 0
137
+ super swarm_size, vector_size
138
+ init_particles Particle
139
+ end
140
+
141
+ def each
142
+ rebirth
143
+ best_this_time = VERY_LARGE_NUMBER
144
+ @particles.each do |p|
145
+ v = p.vector
146
+ error = yield v
147
+ if error < @global_best_error
148
+ @global_best_vector = v.dup
149
+ @global_best_error = error
150
+ end
151
+ if error < best_this_time
152
+ best_this_time = error
153
+ end
154
+ p.optimize error
155
+ p.roam
156
+ end
157
+ #puts best_this_time
158
+ end
159
+
160
+ private
161
+
162
+ # my non-standard addition to pso algorithm
163
+ # the idea being brutal removal from local optima.
164
+ # cycles through particles to give the reborn
165
+ # a chance to roam a bit before being reborn yet again.
166
+ def rebirth
167
+ @num_reborn.times do
168
+ @rebirth_index = 0 if @rebirth_index == @swarm_size
169
+ @particles[@rebirth_index].init_vector
170
+ @rebirth_index += 1
171
+ end
172
+ end
173
+
174
+ end
175
+
176
+ end # module PSO
177
+ end # module SearchAlgoritms
178
+ end # module DRP
@@ -0,0 +1,324 @@
1
+
2
+ =begin
3
+
4
+ DRP, Genetic Programming + Grammatical Evolution = Directed Ruby Programming
5
+ Copyright (C) 2006, Christophe McKeon
6
+
7
+ This program is free software; you can redistribute it and/or
8
+ modify it under the terms of the GNU General Public License
9
+ as published by the Free Software Foundation; either version 2
10
+ of the License, or (at your option) any later version.
11
+
12
+ This program is distributed in the hope that it will be useful,
13
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ GNU General Public License for more details.
16
+
17
+ You should have received a copy of the GNU General Public License
18
+ along with this program; if not, write to the Free Softwar Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
+
20
+ =end
21
+
22
+ module DRP
23
+
24
+ class RuleMethod
25
+
26
+ attr_reader :depth, :max_depth
27
+
28
+ def initialize drp_instance, method_name, weight_factory, max_depth
29
+ @method = drp_instance.method method_name
30
+ @max_depth = max_depth.value drp_instance
31
+ @weight = weight_factory.call self, drp_instance
32
+ @depth = 0
33
+ end
34
+
35
+ def expressed?
36
+ @depth < @max_depth
37
+ end
38
+
39
+ def call *args
40
+ @depth += 1
41
+ result = @method.call *args
42
+ @depth -= 1
43
+ result
44
+ end
45
+
46
+ def weight
47
+ @weight.value
48
+ end
49
+
50
+ end
51
+
52
+ module RuleEngine
53
+
54
+ def new *a, &b
55
+ super.__drp__init
56
+ end
57
+
58
+ private
59
+
60
+ def self.extend_object klass
61
+ DRPNameClashError.test(klass) if DEFAULT[:test_for_extend_name_clashes]
62
+ klass.class_eval do
63
+ include InstanceMethods
64
+ end
65
+ super
66
+ end
67
+
68
+ def method_added name
69
+ # explicit boolean test because can also be :finished
70
+ if @__drp__defining_rules == true
71
+
72
+ rule = @__drp__rules[name] ||= []
73
+
74
+ # to stop recursion of method_added due to alias which calls it
75
+ @__drp__defining_rules = false
76
+ class_eval "private :#{name}; alias __drp__#{name}__#{rule.size} #{name}"
77
+ @__drp__defining_rules = true
78
+
79
+ # note this comes after class_eval cuz of ref to rule.size there
80
+ rule << [@__drp__weights.last, @__drp__max__depths.last]
81
+
82
+ end
83
+ end
84
+
85
+ # TODO find way of making rdoc document these even if they are private
86
+ public
87
+
88
+ def begin_rules
89
+ # if it is true or :finished, see end_rules & method_added
90
+ if @__drp__defining_rules
91
+ raise DRPError, 'begin rules may only be called once'
92
+ else
93
+ @__drp__rules = {}
94
+ @__drp__defining_rules = true
95
+ @__drp__weights = []
96
+ @__drp__max__depths = []
97
+ # NB max_depth should come first here in case user changes
98
+ # the default to some weight which needs to know the max_depth
99
+ max_depth DEFAULT[:max_depth]
100
+ weight DEFAULT[:weight]
101
+ end
102
+ end
103
+
104
+ # NB the two methods defined via define_method in end_rules
105
+ # will be instance methods in instances of classes which
106
+ # are extended by this module. they are here because they
107
+ # are closures referencing the 'name' and 'all_methods'
108
+ # variables respectively.
109
+ # other non-closure methods are in module InstanceMethods
110
+
111
+ def end_rules
112
+
113
+ @__drp__defining_rules = :finished
114
+
115
+ all_methods = {}
116
+
117
+ @__drp__rules.each do |name, weights_and_max_depths|
118
+
119
+ methods = []
120
+
121
+ weights_and_max_depths.each_with_index do |w_md, i|
122
+ methods.push ["__drp__#{name}__#{i}"] + w_md
123
+ end
124
+
125
+ all_methods[name] = methods
126
+
127
+ define_method name do |*args|
128
+
129
+ useable_methods = @__drp__rule__methods[name].select do |meth|
130
+ meth.expressed?
131
+ end
132
+
133
+ case useable_methods.size
134
+
135
+ when 0
136
+ default_rule_method *args
137
+
138
+ when 1
139
+ __drp__call__method(useable_methods.first, args)
140
+
141
+ else
142
+ __drp__call__method(__drp__choose__method(useable_methods), args)
143
+
144
+ end
145
+
146
+ end # define_method name do
147
+
148
+
149
+ end # @__drp__rules.each do
150
+
151
+ define_method :__drp__init do
152
+
153
+ @__drp__rule__methods = {}
154
+ @__drp__depth__stack = []
155
+ @__drp__rule__method__stack = []
156
+
157
+ all_methods.each do |name, arg_array|
158
+ @__drp__rule__methods[name] = arg_array.collect do |args|
159
+ RuleMethod.new self, *args
160
+ end
161
+ end
162
+
163
+ self
164
+
165
+ end
166
+
167
+ end # end_rules
168
+
169
+ =begin rdoc
170
+
171
+ sets the maximum obtainable depth of the rule methods which follow it
172
+ in the listing. max_depths are set for each instance of the class you
173
+ extended with DRP::RuleEngine upon initialization (or when you call init_drp),
174
+ once and only once, and remains set to that static value. max depths are always
175
+ integer values.
176
+
177
+ it may be used in any of the following four ways:
178
+
179
+ *scalar*, all following rule_methods are set to given value
180
+
181
+ max_depth 1
182
+
183
+ *range*, each following rule_method is initialized to some integer
184
+ from range.start to range.end inclusive using a linear mapping
185
+ of a codon gotten via the method next_meta_codon.
186
+
187
+ max_depth 1..2
188
+
189
+ same as the previous but uses the function given, at present
190
+ the only function implemented is i_linear (integer linear),
191
+ which is the default action when no function is given, so at
192
+ present this is useless.
193
+
194
+ max_depth 1..2, :function
195
+
196
+ *block*, each following rule_method is initialized to some integer
197
+ given by your block. the formal parameters to your supplied
198
+ block are counted, and the appropriate number of codons harvested using
199
+ InstanceMethods#next_meta_codon are passed to the block.
200
+
201
+ max_depth { |next_meta_codon, ...| ... }
202
+
203
+ see example_2.rb
204
+
205
+ =end
206
+
207
+ def max_depth *args, &block
208
+
209
+ sz = args.size
210
+ are_args = sz > 0
211
+ md = nil
212
+
213
+ if block_given?
214
+ if are_args
215
+ raise ArgumentError, 'max_depth called with both arguments and a block', caller
216
+ else
217
+ md = MaxDepths::ProcMaxDepth.new(block)
218
+ end
219
+ else
220
+ case sz
221
+ when 1
222
+ arg = args[0]
223
+ case arg
224
+ when Numeric
225
+ md = MaxDepths::StaticMaxDepth.new arg.to_i
226
+ when Range
227
+ md = MaxDepths::MappedMaxDepth.new arg
228
+ else
229
+ raise ArgumentError, 'bad argument to max_depth', caller
230
+ end
231
+ when 2
232
+ arg1, arg2 = args
233
+ if (arg1.kind_of? Range) && (arg2.kind_of? Symbol)
234
+ md = MaxDepths::MappedMaxDepth.new arg1, arg2
235
+ else
236
+ raise ArgumentError, 'bad argument to max_depth', caller
237
+ end
238
+ else
239
+ raise ArgumentError, "too many (#{sz}) args passed to max_depth", caller
240
+ end # case sz
241
+ end # if block_given
242
+
243
+ @__drp__max__depths << md
244
+
245
+ end # def max_depth
246
+
247
+ =begin rdoc
248
+
249
+ sets the weight of the rule methods which follow it
250
+ in the listing, and may be used in any of the following four ways:
251
+
252
+ *scalar*, all following rule_methods are set to given value
253
+
254
+ max_depth 1
255
+
256
+ *range*, each following rule_method is initialized to some integer
257
+ from range.start to range.end inclusive using a linear mapping
258
+ of a codon gotten via the method next_meta_codon when your
259
+ extended object is initialized via drp_init.
260
+
261
+ max_depth 1..2
262
+
263
+ same as the previous but uses the function given, at present
264
+ the only function implemented is i_linear (integer linear),
265
+ which is the default action when no function is given, so at
266
+ present this is useless.
267
+
268
+ max_depth 1..2, :function
269
+
270
+ *block*, each following rule_method is initialized to some integer
271
+ given by your block upon a call to drp_init, i.e. when you initialize
272
+ your extended object instance. the formal parameters to your supplied
273
+ block are counted, and the appropriate number of codons harvested using
274
+ InstanceMethods#next_meta_codon are passed to the block.
275
+
276
+ max_depth { |next_meta_codon, ...| ... }
277
+
278
+ =end
279
+
280
+
281
+ def weight *args, &block
282
+ bg, are_args = block_given?, args.size > 0
283
+ if bg && are_args
284
+ raise ArgumentError, 'weight called with both arguments and a block', caller
285
+ elsif bg
286
+ @__drp__weights << Weights::ProcStaticWeight.factory(block)
287
+ elsif are_args
288
+ @__drp__weights << Weights::StaticWeight.factory(args)
289
+ else
290
+ raise ArgumentError, 'weight called with neither args nor block', caller
291
+ end
292
+ end
293
+
294
+ =begin NOT REALLY USEFUL
295
+ def weight_dyn *args, &block
296
+ bg, are_args = block_given?, args.size > 0
297
+ if bg && are_args
298
+ __drp__error "weight_dyn called with both args and block"
299
+ elsif bg
300
+ @__drp__weights << Weights::ProcDynamicWeight.factory(block)
301
+ elsif are_args
302
+ @__drp__weights << Weights::DynamicWeight.factory(args)
303
+ else
304
+ raise ArgumentError, 'weight_dyn called with neither args nor block', caller
305
+ end
306
+ end
307
+ =end
308
+
309
+ def weight_fcd *args, &block
310
+ bg, are_args = block_given?, args.size > 0
311
+ if bg && are_args
312
+ __drp__error "weight_fcd called with both args and block"
313
+ elsif bg
314
+ @__drp__weights << Weights::ProcWeightFromCurrentDepth.factory(block)
315
+ elsif are_args
316
+ @__drp__weights << Weights::WeightFromCurrentDepth.factory(args)
317
+ else
318
+ raise ArgumentError, 'weight_fcd called with neither args nor block', caller
319
+ end
320
+ end
321
+
322
+ end # class RuleEngine
323
+
324
+ end # module DRP