def test_tree_crossover2(): np.random.seed(42) pset = pg.PrimitiveSet() pset.addFunction(op.add, 2) pset.addFunction(op.sub, 2) pset.addFunction(op.mul, 2) pset.addFunction(protected_div, 2) num_constants = 10 for i in range(num_constants): pset.addTerminal(np.random.randint(-5, 5)) pset.addVariable("x") t1 = np.array([3, 9, 1, 1, 5, 7, 6, 0, 0, 0]) t2 = np.array([6, 0, 0, 0, 0, 0, 0, 0, 0, 0]) i1 = pg.TreeIndividual(tree=t1, nodes=7) i2 = pg.TreeIndividual(tree=t2, nodes=1) o1, o2 = pg.tree_crossover(i1, i2, pset=pset) o1_str = pg.interpreter(pset, o1.genotype) o2_str = pg.interpreter(pset, o2.genotype) assert o1.depth == 3 assert o1.nodes == 5 assert np.array_equal(o1.genotype, np.array([3, 9, 1, 6, 6, 0, 0, 0, 0, 0])) assert o1_str == 'mul(4, add(-2, -2))' assert o2.depth == 2 assert o2.nodes == 3 assert np.array_equal(o2.genotype, np.array([1, 5, 7, 0, 0, 0, 0, 0, 0, 0])) assert o2_str == 'add(1, 2)'
def make_tree_population(size, pset, init_max_depth, max_depth, initial_type=None, init_method=grow_tree): ''' Make Tree Population Args: size (int): number of individuals in the Population pset (PrimitiveSet): set of primitives to build a random tree init_max_depth (int): initial max tree depth max_depth (int): max tree depth that translates into max array size initial_type (type): when using types, this constraints the initial primitive ot be of this type init_method (function): function that generates random trees (grow_tree, full_tree) Returns: array of tree based individuals initialized according to given method, with or without types ''' pop = make_empty_population(size) for i in range(size): pop.individuals[i] = pg.TreeIndividual() pop.individuals[i].genotype = init_method(pset, init_max_depth, max_depth, initial_type=initial_type) depth, nodes = pg.count_tree_internals(pset, pop.individuals[i].genotype) pop.individuals[i].depth = depth pop.individuals[i].nodes = nodes return pop
def test_make_individual_tree(): ind = pg.TreeIndividual() assert type(ind) is pg.TreeIndividual assert ind.fitness is None assert ind.genotype is None assert ind.run_eval is True assert ind.depth is None assert ind.nodes is None
def tree_crossover(parent1, parent2, pset=None): ''' Tree Crossover Args: parent1 (TreeIndividual): first parent parent2 (TreeIndividual): second parent pset (PrimitiveSet): the set primitives allowed to be used Returns: offsprings from the two parents ''' def arraycopy(src, src_pos, dest, dest_pos, length): dest[dest_pos:dest_pos + length] = src[src_pos:src_pos + length] def get_primitive_type(primitive): if primitive in pset.ephemeral_constants: _, p_type = pset.ephemeral_constants[primitive] elif primitive in pset.terminals: _, p_type = pset.terminals[primitive] elif primitive in pset.variables: _, p_type = pset.variables[primitive] elif primitive in pset.functions: _, _, p_type = pset.functions[primitive] else: p_type = None raise AttributeError( 'This is a typed primitive set so types are required!') return p_type[0] # create offsprings offspring1 = pg.TreeIndividual( tree=np.zeros(parent1.genotype.size, dtype=parent1.genotype.dtype)) offspring2 = pg.TreeIndividual( tree=np.zeros(parent2.genotype.size, dtype=parent2.genotype.dtype)) # define tree cut points for subtree swap start1 = np.random.randint(parent1.nodes) end1 = pg.transverse_tree(pset, parent1.genotype, start1) # if typed set, start2 must be of the same type as start1 if pset.typed: p1_primitive = parent1.genotype[start1] p1_type = get_primitive_type(p1_primitive) valid_gene_pos = [] pos = 0 while pos < parent2.nodes: if p1_type == get_primitive_type(parent2.genotype[pos]): valid_gene_pos.append(pos) pos += 1 if valid_gene_pos == []: return parent1, parent2 # there is not valid point in the other, return original parents else: valid_pos = np.array(valid_gene_pos) start2 = valid_pos[np.random.randint(valid_pos.size)] else: start2 = np.random.randint(parent2.nodes) end2 = pg.transverse_tree(pset, parent2.genotype, start2) # define length of offspring trees len1 = start1 + (end2 - start2) + (parent1.nodes - end1) len2 = start2 + (end1 - start1) + (parent2.nodes - end2) if len1 > parent1.genotype.size: # passes max depth, return parent offspring1 = parent1 else: # produce offpsring 1 arraycopy(parent1.genotype, 0, offspring1.genotype, 0, start1) num_elements = (end2 - start2) arraycopy(parent2.genotype, start2, offspring1.genotype, start1, num_elements) num_elements = (parent1.nodes - end1) arraycopy(parent1.genotype, end1, offspring1.genotype, start1 + (end2 - start2), num_elements) offspring1.depth, offspring1.nodes = pg.count_tree_internals( pset, offspring1.genotype) if len2 > parent2.genotype.size: # passes max depth, return parent offspring2 = parent2 else: # produce offspring 2 arraycopy(parent2.genotype, 0, offspring2.genotype, 0, start2) num_elements = (end1 - start1) arraycopy(parent1.genotype, start1, offspring2.genotype, start2, num_elements) num_elements = (parent2.nodes - end2) arraycopy(parent2.genotype, end2, offspring2.genotype, start2 + (end1 - start1), num_elements) offspring2.depth, offspring2.nodes = pg.count_tree_internals( pset, offspring2.genotype) return offspring1, offspring2
def subtree_mutation(parent, pset=None, **kargs): ''' SubTree Mutation Args: parent (TreeIndividual): the individual to be mutated pset (PrimitiveSet): the set primitives allowed to be used Returns: mutated individual ''' def arraycopy(src, src_pos, dest, dest_pos, length): dest[dest_pos:dest_pos + length] = src[src_pos:src_pos + length] def get_primitive_type(primitive): if primitive in pset.ephemeral_constants: _, p_type = pset.ephemeral_constants[primitive] elif primitive in pset.terminals: _, p_type = pset.terminals[primitive] elif primitive in pset.variables: _, p_type = pset.variables[primitive] elif primitive in pset.functions: _, _, p_type = pset.functions[primitive] else: p_type = None raise AttributeError( 'This is a typed primitive set so types are required!') return p_type[0] offspring = pg.TreeIndividual( tree=np.zeros(parent.genotype.size, dtype=parent.genotype.dtype)) # define tree cut points for subtree replacement start1 = np.random.randint(parent.nodes) end1 = pg.transverse_tree(pset, parent.genotype, start1) # if typed subtree must return the appropriate type if pset.typed: primitive = parent.genotype[start1] parent_type = get_primitive_type(primitive) else: parent_type = None # TODO: the default values to set the size of the generated tree must be revised # and a proper mechanism to set these values on a per-problem case must be available # if typed set, start2 must be of the same type as start1 try: subtree = pg.grow_tree(pset, parent.depth - 1, parent.depth, initial_type=parent_type) except: e = sys.exc_info()[0] print('%s' % e) print('parent.depth: %s' % parent.depth) start2 = 0 end2 = pg.transverse_tree(pset, subtree, start2) len1 = start1 + (end2 - start2) + (parent.nodes - end1) # produce offpsring 1 arraycopy(parent.genotype, 0, offspring.genotype, 0, start1) num_elements = (end2 - start2) arraycopy(subtree, start2, offspring.genotype, start1, num_elements) num_elements = (parent.nodes - end1) arraycopy(parent.genotype, end1, offspring.genotype, start1 + (end2 - start2), num_elements) # update tree metrics offspring.depth, offspring.nodes = pg.count_tree_internals( pset, offspring.genotype) if offspring.nodes <= parent.genotype.size: return offspring else: return parent
def tree_point_mutation(i1, pset=None, gene_rate=None, **kargs): ''' Tree Point Mutation Args: i1 (TreeIndividual): the individual to be mutated pset (PrimitiveSet): the set primitives allowed to be used Returns: mutated individual ''' if pset.ephemeral_cache: terminals_keys = [] for k in list(pset.terminals.keys()): if k in pset.ephemeral_cache: pass else: terminals_keys.append(k) terminals_idx = np.concatenate([ np.array(terminals_keys), np.array(list(pset.ephemeral_constants.keys())) ]) else: terminals_idx = np.array(list(pset.terminals.keys())) variables_idx = np.array(list(pset.variables.keys())) all_terminals_idx = np.concatenate([terminals_idx, variables_idx]) new_genotype = np.copy(i1.genotype) i = 0 while i < new_genotype.size and new_genotype[i] != 0: if np.random.uniform() < gene_rate: primitive = new_genotype[i] # replace terminal/variable with another one if primitive in pset.terminals or primitive in pset.variables or primitive in pset.ephemeral_constants: if pset.typed: _, term_types = pset.terminals[ primitive] if primitive in pset.terminals else (None, None) _, ephm_types = pset.ephemeral_constants[ primitive] if primitive in pset.ephemeral_constants else ( None, None) _, vars_types = pset.variables[ primitive] if primitive in pset.variables else (None, None) if term_types is not None: typed_terminals = pset.terminals_types[term_types[0]] for e in pset.ephemeral_cache: typed_terminals.remove(e) else: typed_terminals = [] if ephm_types is not None: typed_ephemerals = pset.terminals_types[ephm_types[0]] else: typed_ephemerals = [] if vars_types is not None: typed_variables = pset.variables_types[vars_types[0]] else: typed_variables = [] valid_terminals = np.concatenate([ np.array(typed_terminals, dtype=int), np.array(typed_variables, dtype=int), np.array(typed_ephemerals, dtype=int) ]) new_idx = valid_terminals[np.random.randint( valid_terminals.size)] else: new_idx = all_terminals_idx[np.random.randint( all_terminals_idx.size)] if new_idx in pset.ephemeral_cache: new_idx = pset.addEphemeralConstant(new_idx) new_genotype[i] = new_idx # replace function with another one of the same arity elif primitive in pset.functions: # only functions of the same arity are valid to be used _, arity, types = pset.functions[primitive] if pset.typed: arity_functions = set(pset.arity_cache[arity]) # only cache by return type typed_functions = set(pset.functions_types[types[0]]) # compute intersection of arity and typed functions # and if result is not null, check if all arguments are equivalent # otherwise, does not mutate final_candidates = [] for fn_key in set.intersection(arity_functions, typed_functions): _, _, candidate_types = pset.functions[fn_key] if candidate_types == types: final_candidates.append(fn_key) if final_candidates != []: valid_functions_idx = np.array(final_candidates) new_genotype[i] = valid_functions_idx[ np.random.randint(valid_functions_idx.size)] else: valid_functions_idx = np.array(pset.arity_cache[arity]) new_genotype[i] = valid_functions_idx[np.random.randint( valid_functions_idx.size)] i += 1 new_individual = pg.TreeIndividual(tree=new_genotype, depth=i1.depth, nodes=i1.nodes) return new_individual