drp 0.0.6

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.
@@ -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