tree.rb

Path: lib/charlie/tree/tree.rb
Last Update: Sat Jan 26 01:08:38 +0100 2008

Tree genotype, crossover, mutation etc.

Methods

Included Modules

GPTreeHelper GPTreeHelper

Constants

TreeReplaceMutator = TreeReplaceMutator()
TreePruneMutator = TreeReplaceMutator(0)
TreeNumTerminalMutator = TreeNumTerminalMutator()
TreeEvalMutator = TreeEvalMutator()

Public Class methods

[Source]

    # File lib/charlie/tree/tree.rb, line 83
83:     def initialize
84:       self.genes = generate_random_tree(init_depth,init_type)
85:     end

Public Instance methods

Replaces a random subtree by the result of its evaluation. value_hash is passed to eval_tree.

[Source]

     # File lib/charlie/tree/tree.rb, line 279
279: def TreeEvalMutator(value_hash=Hash.new{0})
280:  Module.new{
281:   self.name = "TreeEvalMutator(#{value_hash.inspect})"
282:   define_method(:mutate!) {
283:     st = random_subtree
284:     st.replace [:term,eval_tree(st,value_hash)]
285:     self
286:   }
287:  }
288: end

Tree genotype, for genetic programming etc.

  • Pass arrays of terminals/unary operators/binary operators to this function to generate a class.
  • terminals can be procs (eval‘d on initialization), symbols (replaced by values in calls to eval_genes) or anything else (not changed, so make sure all operators are defined for these)
  • This needs more options. Depth of initial trees, etc. Also needs a better mutator.

[Source]

     # File lib/charlie/tree/tree.rb, line 73
 73: def TreeGenotype(terminals, unary_ops, binary_ops, init_depth = 3, init_type = :half)
 74:   unary_ops  = nil if unary_ops.empty?
 75:   Class.new(Genotype) {
 76: 
 77:     define_method(:unary_ops)  {unary_ops }
 78:     define_method(:binary_ops) {binary_ops}
 79:     define_method(:terminals)  {terminals }
 80:     define_method(:init_depth) {init_depth}
 81:     define_method(:init_type)  {init_type }
 82: 
 83:     def initialize
 84:       self.genes = generate_random_tree(init_depth,init_type)
 85:     end
 86: 
 87:     def genes=(g)
 88:       class << g # ensures a genes.dup call is a deep copy
 89:         def dup
 90:           GPTreeHelper.dup_tree(self)
 91:         end
 92:       end
 93:       @genes = g
 94:     end
 95: 
 96:     def size
 97:       tree_size(@genes)
 98:     end
 99: 
100:     def depth
101:       tree_depth(@genes)
102:     end
103: 
104: 
105:     # Generates a random tree.
106:     # * type = grow uses generate_random_tree_grow
107:     # * type = full uses generate_random_tree_full
108:     # * type = half uses one of them, with 50% probability each.
109:     def generate_random_tree(depth, type=:half)
110:       if type==:full || ( type == :half && rand < 0.5 )
111:         generate_random_tree_full(depth)
112:       else
113:         generate_random_tree_grow(depth,true)
114:       end
115:     end
116: 
117:     # Generates a random tree of a certain maximum depth.
118:     # * <tt>no_term=true</tt> makes sure the root node is a function.
119:     def generate_random_tree_grow(depth,no_term=nil)
120:       if depth.zero? || (rand(3).zero?  && !no_term)
121:         e = terminals.at_rand
122:         [:term, e.is_a?(Proc) ? e.call : e]
123:       else
124:         if unary_ops.nil? || rand(2).zero?
125:           [binary_ops.at_rand,generate_random_tree_grow(depth-1),generate_random_tree_grow(depth-1)]
126:         else
127:           [unary_ops.at_rand,generate_random_tree_grow(depth-1)]
128:         end
129:        end
130:     end
131: 
132:     # Generates a random tree, with all terminals at 'depth'.
133:     def generate_random_tree_full(depth)
134:       if depth.zero?
135:         e = terminals.at_rand
136:         [:term, e.is_a?(Proc) ? e.call : e]
137:       else
138:         if unary_ops.nil? || rand(2).zero?
139:           [binary_ops.at_rand,generate_random_tree_full(depth-1),generate_random_tree_full(depth-1)]
140:         else
141:           [unary_ops.at_rand,generate_random_tree_full(depth-1)]
142:         end
143:        end
144:     end
145: 
146:     def eval_genes(terminals_value_hash = {})
147:       eval_tree(@genes,terminals_value_hash)
148:     end
149: 
150:     def to_s
151:       @genes.inspect
152:     end
153: 
154:     use PCross(0.7,TreeCrossover), PMutate(0.5,TreeReplaceMutator) # TODO: test what options are best -- benchmark show that these are ok for simple settings.
155: 
156:     # make helper functions available at both class and instance level
157:     include GPTreeHelper
158:     class << self
159:       include GPTreeHelper
160:     end
161:   }
162: end

mutates a random numeric terminal using a point mutator (cf. ListMutator) or a block (e.g. {|x| x-rand+0.5}

[Source]

     # File lib/charlie/tree/tree.rb, line 255
255: def TreeNumTerminalMutator(mutate=:uniform[0.1], &b)
256:  if block_given?
257:    mutate_proc = b
258:  else
259:   mut_name, *mut_arg = mutate
260:   mut_fn = PointMutators[mut_name]
261:   mutate_proc = proc{|x| mut_fn.call(x,*mut_arg) }
262:  end
263: 
264:  Module.new{
265:   self.name = "TreeNumTerminalMutator(#{mutate.inspect})"
266:   define_method(:mutate!) {
267:     numterms = all_terminals.select{|x| x[1].is_a? Numeric }
268:     unless numterms.empty?
269:       random_term = numterms.at_rand
270:       random_term[1] = mutate_proc.call(random_term[1])
271:     end
272:     self
273:   }
274:  }
275: end

TreeReplaceMutator replaces a randomly chosen subtree with a new, randomly generated, subtree.

  • depth and type are arguments for TreeGenotype#generate_random_tree
  • depth == [1,2,3,..] or depth==(1..3) uses one of the elements in the range for the depth.
  • depth == :same to use the depth of the replaced subtree.
  • depth == :same[min,max] for depth of the replaced subtree plus a random offset between min and max.

[Source]

     # File lib/charlie/tree/tree.rb, line 183
183: def TreeReplaceMutator(depth=2,type=:half)
184:   Module.new{
185:      self.name = "TreeReplaceMutator(#{depth.inspect},#{type.inspect})"
186:     if depth.is_a? Numeric
187:       define_method(:mutate!) {
188:         random_subtree.replace generate_random_tree(depth,type)
189:         self
190:       }
191:     elsif depth==:same || (depth.is_a?(Array) && depth[0]==:same)
192:       s, dd_min, dd_max = *depth
193:       possible_deltas = (dd_min||0..dd_max||0).to_a
194:       define_method(:mutate!) {
195:         st = random_subtree
196:         st.replace generate_random_tree([tree_depth(st) + possible_deltas.at_rand,0].max, type)
197:         self
198:       }
199:     elsif depth.respond_to?(:to_a)
200:       possible_depths = depth.to_a
201:       define_method(:mutate!) {
202:         random_subtree.replace generate_random_tree(possible_depths.at_rand,type)
203:         self
204:       }
205:     else
206:       raise ArgumentError, "invalid option for depth"
207:     end
208:   }
209: end

[Source]

     # File lib/charlie/tree/tree.rb, line 100
100:     def depth
101:       tree_depth(@genes)
102:     end

[Source]

     # File lib/charlie/tree/tree.rb, line 146
146:     def eval_genes(terminals_value_hash = {})
147:       eval_tree(@genes,terminals_value_hash)
148:     end

Generates a random tree.

[Source]

     # File lib/charlie/tree/tree.rb, line 109
109:     def generate_random_tree(depth, type=:half)
110:       if type==:full || ( type == :half && rand < 0.5 )
111:         generate_random_tree_full(depth)
112:       else
113:         generate_random_tree_grow(depth,true)
114:       end
115:     end

Generates a random tree, with all terminals at ‘depth’.

[Source]

     # File lib/charlie/tree/tree.rb, line 133
133:     def generate_random_tree_full(depth)
134:       if depth.zero?
135:         e = terminals.at_rand
136:         [:term, e.is_a?(Proc) ? e.call : e]
137:       else
138:         if unary_ops.nil? || rand(2).zero?
139:           [binary_ops.at_rand,generate_random_tree_full(depth-1),generate_random_tree_full(depth-1)]
140:         else
141:           [unary_ops.at_rand,generate_random_tree_full(depth-1)]
142:         end
143:        end
144:     end

Generates a random tree of a certain maximum depth.

  • no_term=true makes sure the root node is a function.

[Source]

     # File lib/charlie/tree/tree.rb, line 119
119:     def generate_random_tree_grow(depth,no_term=nil)
120:       if depth.zero? || (rand(3).zero?  && !no_term)
121:         e = terminals.at_rand
122:         [:term, e.is_a?(Proc) ? e.call : e]
123:       else
124:         if unary_ops.nil? || rand(2).zero?
125:           [binary_ops.at_rand,generate_random_tree_grow(depth-1),generate_random_tree_grow(depth-1)]
126:         else
127:           [unary_ops.at_rand,generate_random_tree_grow(depth-1)]
128:         end
129:        end
130:     end

[Source]

    # File lib/charlie/tree/tree.rb, line 87
87:     def genes=(g)
88:       class << g # ensures a genes.dup call is a deep copy
89:         def dup
90:           GPTreeHelper.dup_tree(self)
91:         end
92:       end
93:       @genes = g
94:     end

[Source]

    # File lib/charlie/tree/tree.rb, line 96
96:     def size
97:       tree_size(@genes)
98:     end

[Source]

     # File lib/charlie/tree/tree.rb, line 150
150:     def to_s
151:       @genes.inspect
152:     end

[Validate]