示例#1
0
def variable_onepoint(p_0, p_1):
    """
    Given two individuals, create two children using one-point crossover and
    return them. A different point is selected on each genome for crossover
    to occur. Note that this allows for genomes to grow or shrink in
    size. Crossover points are selected within the used portion of the
    genome by default (i.e. crossover does not occur in the tail of the
    individual).
    
    :param p_0: Parent 0
    :param p_1: Parent 1
    :return: A list of crossed-over individuals.
    """

    # Get the chromosomes.
    genome_0, genome_1 = p_0.genome, p_1.genome

    # Uniformly generate crossover points.
    max_p_0, max_p_1 = get_max_genome_index(p_0, p_1)

    # Select unique points on each genome for crossover to occur.
    pt_0, pt_1 = randint(1, max_p_0), randint(1, max_p_1)

    # Make new chromosomes by crossover: these slices perform copies.
    if random() < params['CROSSOVER_PROBABILITY']:
        c_0 = genome_0[:pt_0] + genome_1[pt_1:]
        c_1 = genome_1[:pt_1] + genome_0[pt_0:]
    else:
        c_0, c_1 = genome_0[:], genome_1[:]

    # Put the new chromosomes into new individuals.
    ind_0 = individual.Individual(c_0, None)
    ind_1 = individual.Individual(c_1, None)

    return [ind_0, ind_1]
示例#2
0
def fixed_twopoint(p_0, p_1):
    """
    Given two individuals, create two children using two-point crossover and
    return them. The same points are selected on both genomes for crossover
    to occur. Crossover points are selected within the used portion of the
    genome by default (i.e. crossover does not occur in the tail of the
    individual).

    :param p_0: Parent 0
    :param p_1: Parent 1
    :return: A list of crossed-over individuals.
    """

    genome_0, genome_1 = p_0.genome, p_1.genome

    # Uniformly generate crossover points.
    max_p_0, max_p_1 = get_max_genome_index(p_0, p_1)

    # Select the same points on both genomes for crossover to occur.
    a, b = randint(1, max_p_0), randint(1, max_p_1)
    pt_0, pt_1 = min([a, b]), max([a, b])

    # Make new chromosomes by crossover: these slices perform copies.
    if random() < params['CROSSOVER_PROBABILITY']:
        c_0 = genome_0[:pt_0] + genome_1[pt_0:pt_1] + genome_0[pt_1:]
        c_1 = genome_1[:pt_0] + genome_0[pt_0:pt_1] + genome_1[pt_1:]
    else:
        c_0, c_1 = genome_0[:], genome_1[:]

    # Put the new chromosomes into new individuals.
    ind_0 = individual.Individual(c_0, None)
    ind_1 = individual.Individual(c_1, None)

    return [ind_0, ind_1]
示例#3
0
def LTGE_crossover(p_0, p_1):
    """Crossover in the LTGE representation."""

    # crossover and repair.
    # the LTGE crossover produces one child, and is symmetric (ie
    # xover(p0, p1) is not different from xover(p1, p0)), but since it's
    # stochastic we can just run it twice to get two individuals
    # expected to be different.
    g_0, ph_0 = latent_tree_repair(
        latent_tree_crossover(p_0.genome, p_1.genome), params['BNF_GRAMMAR'],
        params['MAX_TREE_DEPTH'])
    g_1, ph_1 = latent_tree_repair(
        latent_tree_crossover(p_0.genome, p_1.genome), params['BNF_GRAMMAR'],
        params['MAX_TREE_DEPTH'])

    # wrap up in Individuals and fix up various Individual attributes
    ind_0 = individual.Individual(g_0, None, False)
    ind_1 = individual.Individual(g_1, None, False)

    ind_0.phenotype = ph_0
    ind_1.phenotype = ph_1

    # number of nodes is the number of decisions in the genome
    ind_0.nodes = ind_0.used_codons = len(g_0)
    ind_1.nodes = ind_1.used_codons = len(g_1)

    # each key is the length of a path from root
    ind_0.depth = max(len(k) for k in g_0)
    ind_1.depth = max(len(k) for k in g_1)

    # in LTGE there are no invalid individuals
    ind_0.invalid = False
    ind_1.invalid = False

    return [ind_0, ind_1]
def int_flip_per_ind(ind):
    """
    Mutate the genome of an individual by randomly choosing a new int with
    probability p_mut. Works per-individual. Mutation is performed over the
    entire length of the genome by default, but the flag within_used is
    provided to limit mutation to only the effective length of the genome.

    :param ind: An individual to be mutated.
    :return: A mutated individual.
    """

    # Set effective genome length over which mutation will be performed.
    eff_length = get_effective_length(ind)

    if not eff_length:
        # Linear mutation cannot be performed on this individual.
        return ind

    for _ in range(params['MUTATION_EVENTS']):
        idx = randint(0, eff_length - 1)
        ind.genome[idx] = randint(0, params['CODON_SIZE'])

    # Re-build a new individual with the newly mutated genetic information.
    new_ind = individual.Individual(ind.genome, None)

    return new_ind
示例#5
0
def check_ind_from_parser(ind, target):
    """
    Checks the mapping of an individual generated by the GE parser against
    the specified target string to ensure the GE individual is correct.

    :param ind: An instance of the representation.individaul.Individual class.
    :param target: A target string against which to match the phenotype of
    the individual.
    :return: Nothing.
    """

    # Re-map individual using genome mapper to check everything is ok.
    new_ind = individual.Individual(ind.genome, None)

    # Check phenotypes are the same.
    if new_ind.phenotype != ind.phenotype:
        s = "utilities.representation.check_methods.check_ind_from_parser\n" \
            "Error: Solution phenotype doesn't match genome mapping.\n" \
            "       Solution phenotype:  \t %s\n" \
            "       Solution from genome:\t %s\n" \
            "       Derived genome:      \t %s" % \
            (ind.phenotype, new_ind.phenotype, ind.genome)
        raise Exception(s)

    # Check the phenotype matches the target string.
    elif ind.phenotype != target:
        s = "utilities.representation.check_methods.check_ind_from_parser\n" \
            "Error: Solution phenotype doesn't match target.\n" \
            "       Target:   \t %s\n" \
            "       Solution: \t %s" % (target, ind.phenotype)
        raise Exception(s)

    else:
        # Check the tree matches the phenotype.
        check_genome_mapping(ind)
示例#6
0
def LTGE_initialisation(size):
    """Initialise a population in the LTGE representation."""

    pop = []
    for _ in range(size):

        # Random genotype
        g, ph = latent_tree_random_ind(params['BNF_GRAMMAR'],
                                       params['MAX_TREE_DEPTH'])

        # wrap up in an Individual and fix up various Individual attributes
        ind = individual.Individual(g, None, False)

        ind.phenotype = ph

        # number of nodes is the number of decisions in the genome
        ind.nodes = ind.used_codons = len(g)

        # each key is the length of a path from root
        ind.depth = max(len(k) for k in g)

        # in LTGE there are no invalid individuals
        ind.invalid = False

        pop.append(ind)
    return pop
def subtree(ind):
    """
    Mutate the individual by replacing a randomly selected subtree with a
    new randomly generated subtree. Guaranteed one event per individual, unless
    params['MUTATION_EVENTS'] is specified as a higher number.

    :param ind: An individual to be mutated.
    :return: A mutated individual.
    """
    def subtree_mutate(ind_tree):
        """
        Creates a list of all nodes and picks one node at random to mutate.
        Because we have a list of all nodes, we can (but currently don't)
        choose what kind of nodes to mutate on. Handy.

        :param ind_tree: The full tree of an individual.
        :return: The full mutated tree and the associated genome.
        """

        # Find the list of nodes we can mutate from.
        targets = ind_tree.get_target_nodes(
            [], target=params['BNF_GRAMMAR'].non_terminals)

        # Pick a node.
        new_tree = choice(targets)

        # Set the depth limits for the new subtree.
        if params['MAX_TREE_DEPTH']:
            # Set the limit to the tree depth.
            max_depth = params['MAX_TREE_DEPTH'] - new_tree.depth

        else:
            # There is no limit to tree depth.
            max_depth = None

        # Mutate a new subtree.
        generate_tree(new_tree, [], [], "random", 0, 0, 0, max_depth)

        return ind_tree

    if ind.invalid:
        # The individual is invalid.
        tail = []

    else:
        # Save the tail of the genome.
        tail = ind.genome[ind.used_codons:]

    # Allows for multiple mutation events should that be desired.
    for i in range(params['MUTATION_EVENTS']):
        ind.tree = subtree_mutate(ind.tree)

    # Re-build a new individual with the newly mutated genetic information.
    ind = individual.Individual(None, ind.tree)

    # Add in the previous tail.
    ind.genome = ind.genome + tail

    return ind
示例#8
0
def uniform_genome(size):
    """
    Create a population of individuals by sampling genomes uniformly.

    :param size: The size of the required population.
    :return: A full population composed of randomly generated individuals.
    """

    return [individual.Individual(sample_genome(), None) for _ in range(size)]
示例#9
0
def check_snippets_for_solution():
    """
    Check the snippets repository to see if we have built up the correct
    solution yet.

    :return: An individual representing the correct solution if it exists,
    otherwise None.
    """

    # Initialise None biggest snippet
    biggest_snippet = [0, None]

    for snippet in sorted(trackers.snippets.keys()):
        # Check each snippet to find the largest one.

        # Find length of snippet
        index = get_num_from_str(snippet)
        length = index[1] - index[0]

        if length > biggest_snippet[0]:
            # We have a new biggest snippet.
            biggest_snippet = [length, snippet]

    # Get the phenotype of the largest snippet
    largest_snippet = get_output(trackers.snippets[biggest_snippet[1]])

    if largest_snippet != params['REVERSE_MAPPING_TARGET']:
        # The solution doesn't match the target string.

        # Get the location of the phenotype of the largest snippet on the
        # target string.
        largest_indexes = get_num_from_str(biggest_snippet[1])

        # Generate whitespace to position the phenotype accordingly.
        spaces = "".join([" " for _ in range(largest_indexes[0] - 1)])

        s = "operators.subtree_parse.check_snippets_for_solution\n" \
            "Error: Solution doesn't match the target string.\n" \
            "       Target:   \t %s\n" \
            "       Solution: \t %s %s\n" \
            "       Check grammar file `%s` to ensure the grammar is capable" \
            " of producing the exact target string." % \
            (params['REVERSE_MAPPING_TARGET'], spaces, largest_snippet,
             params['GRAMMAR_FILE'])
        raise Exception(s)

    if largest_snippet == params['REVERSE_MAPPING_TARGET']:
        # We have a perfect match

        # Generate individual that represents the perfect solution.
        ind = individual.Individual(None,
                                    trackers.snippets[biggest_snippet[1]])

        # Return ind.
        return ind
示例#10
0
def check_genome_mapping(ind):
    """
    Re-maps individual to ensure genome is correct, i.e. that it maps to the
    correct phenotype and individual.
    
    :param ind: An instance of the representation.individual.Individual class.
    :return: Nothing.
    """

    # Re-map individual using fast genome mapper to check everything is ok
    new_ind = individual.Individual(ind.genome, None)

    # Get attributes of both individuals.
    attributes_0 = vars(ind)
    attributes_1 = vars(new_ind)

    if params['GENOME_OPERATIONS']:
        # If this parameter is set then the new individual will have no tree.
        attributes_0['tree'] = None

    else:
        if attributes_0['tree'] != attributes_1['tree']:
            s = "utilities.representation.check_methods.check_ind.\n" \
                "Error: Individual trees do not match."
            raise Exception(s)

    # Check that all attributes match across both individuals.
    for a_0 in sorted(attributes_0.keys()):
        for a_1 in sorted(attributes_1.keys()):
            if a_0 == a_1 and attributes_0[a_0] != attributes_1[a_1] and not \
                    (type(attributes_0[a_0]) is float and
                     type(attributes_1[a_1]) is float and
                     np.isnan(attributes_0[a_0]) and
                     np.isnan(attributes_1[a_1])):

                s = "utilities.representation.check_methods." \
                    "check_genome_mapping\n" \
                    "Error: Individual attributes do not match genome-" \
                    "encoded attributes.\n" \
                    "       Original attribute:\n" \
                    "           %s :\t %s\n" \
                    "       Encoded attribute:\n" \
                    "           %s :\t %s" % \
                    (a_0, attributes_0[a_0], a_1, attributes_1[a_1])
                raise Exception(s)
def int_flip_per_codon(ind):
    """
    Mutate the genome of an individual by randomly choosing a new int with
    probability p_mut. Works per-codon. Mutation is performed over the
    effective length (i.e. within used codons, not tails) by default;
    within_used=False switches this off.

    :param ind: An individual to be mutated.
    :return: A mutated individual.
    """

    # Set effective genome length over which mutation will be performed.
    eff_length = get_effective_length(ind)

    if not eff_length:
        # Linear mutation cannot be performed on this individual.
        return ind

    # Set mutation probability. Default is 1 over the length of the genome.
    if params['MUTATION_PROBABILITY'] and params['MUTATION_EVENTS'] == 1:
        p_mut = params['MUTATION_PROBABILITY']
    elif params['MUTATION_PROBABILITY'] and params['MUTATION_EVENTS'] > 1:
        s = "operators.mutation.int_flip_per_codon\n" \
            "Error: mutually exclusive parameters for 'MUTATION_PROBABILITY'" \
            "and 'MUTATION_EVENTS' have been explicitly set.\n" \
            "       Only one of these parameters can be used at a time with" \
            "int_flip_per_codon mutation."
        raise Exception(s)
    else:
        # Default mutation events per individual is 1. Raising this number
        # will influence the mutation probability for each codon.
        p_mut = params['MUTATION_EVENTS'] / eff_length

    # Mutation probability works per-codon over the portion of the
    # genome as defined by the within_used flag.
    for i in range(eff_length):
        if random() < p_mut:
            ind.genome[i] = randint(0, params['CODON_SIZE'])

    # Re-build a new individual with the newly mutated genetic information.
    new_ind = individual.Individual(ind.genome, None)

    return new_ind
def LTGE_mutation(ind):
    """Mutation in the LTGE representation."""

    # mutate and repair.
    g, ph = latent_tree_repair(latent_tree_mutate(ind.genome),
                               params['BNF_GRAMMAR'], params['MAX_TREE_DEPTH'])

    # wrap up in an Individual and fix up various Individual attributes
    ind = individual.Individual(g, None, False)

    ind.phenotype = ph

    # number of nodes is the number of decisions in the genome
    ind.nodes = ind.used_codons = len(g)

    # each key is the length of a path from root
    ind.depth = max(len(k) for k in g)

    # in LTGE there are no invalid individuals
    ind.invalid = False

    return ind
示例#13
0
def generate_ind_tree(max_depth, method):
    """
    Generate an individual using a given subtree initialisation method.

    :param max_depth: The maximum depth for the initialised subtree.
    :param method: The method of subtree initialisation required.
    :return: A fully built individual.
    """

    # Initialise an instance of the tree class
    ind_tree = Tree(str(params['BNF_GRAMMAR'].start_rule["symbol"]), None)

    # Generate a tree
    genome, output, nodes, _, depth = generate_tree(ind_tree, [], [], method,
                                                    0, 0, 0, max_depth)

    # Get remaining individual information
    phenotype, invalid, used_cod = "".join(output), False, len(genome)

    if params['BNF_GRAMMAR'].python_mode:
        # Grammar contains python code

        phenotype = python_filter(phenotype)

    # Initialise individual
    ind = individual.Individual(genome, ind_tree, map_ind=False)

    # Set individual parameters
    ind.phenotype, ind.nodes = phenotype, nodes
    ind.depth, ind.used_codons, ind.invalid = depth, used_cod, invalid

    # Generate random tail for genome.
    ind.genome = genome + [
        randint(0, params['CODON_SIZE'])
        for _ in range(int(ind.used_codons / 2))
    ]

    return ind
示例#14
0
def subtree(p_0, p_1):
    """
    Given two individuals, create two children using subtree crossover and
    return them. Candidate subtrees are selected based on matching
    non-terminal nodes rather than matching terminal nodes.
    
    :param p_0: Parent 0.
    :param p_1: Parent 1.
    :return: A list of crossed-over individuals.
    """
    def do_crossover(tree0, tree1, shared_nodes):
        """
        Given two instances of the representation.tree.Tree class (
        derivation trees of individuals) and a list of intersecting
        non-terminal nodes across both trees, performs subtree crossover on
        these trees.
        
        :param tree0: The derivation tree of individual 0.
        :param tree1: The derivation tree of individual 1.
        :param shared_nodes: The sorted list of all non-terminal nodes that are
        in both derivation trees.
        :return: The new derivation trees after subtree crossover has been
        performed.
        """

        # Randomly choose a non-terminal from the set of permissible
        # intersecting non-terminals.
        crossover_choice = choice(shared_nodes)

        # Find all nodes in both trees that match the chosen crossover node.
        nodes_0 = tree0.get_target_nodes([], target=[crossover_choice])
        nodes_1 = tree1.get_target_nodes([], target=[crossover_choice])

        # Randomly pick a node.
        t0, t1 = choice(nodes_0), choice(nodes_1)

        # Check the parents of both chosen subtrees.
        p0 = t0.parent
        p1 = t1.parent

        if not p0 and not p1:
            # Crossover is between the entire tree of both tree0 and tree1.

            return t1, t0

        elif not p0:
            # Only t0 is the entire of tree0.
            tree0 = t1

            # Swap over the subtrees between parents.
            i1 = p1.children.index(t1)
            p1.children[i1] = t0

            # Set the parents of the crossed-over subtrees as their new
            # parents. Since the entire tree of t1 is now a whole
            # individual, it has no parent.
            t0.parent = p1
            t1.parent = None

        elif not p1:
            # Only t1 is the entire of tree1.
            tree1 = t0

            # Swap over the subtrees between parents.
            i0 = p0.children.index(t0)
            p0.children[i0] = t1

            # Set the parents of the crossed-over subtrees as their new
            # parents. Since the entire tree of t0 is now a whole
            # individual, it has no parent.
            t1.parent = p0
            t0.parent = None

        else:
            # The crossover node for both trees is not the entire tree.

            # For the parent nodes of the original subtrees, get the indexes
            # of the original subtrees.
            i0 = p0.children.index(t0)
            i1 = p1.children.index(t1)

            # Swap over the subtrees between parents.
            p0.children[i0] = t1
            p1.children[i1] = t0

            # Set the parents of the crossed-over subtrees as their new
            # parents.
            t1.parent = p0
            t0.parent = p1

        return tree0, tree1

    def intersect(l0, l1):
        """
        Returns the intersection of two sets of labels of nodes of
        derivation trees. Only returns matching non-terminal nodes across
        both derivation trees.
        
        :param l0: The labels of all nodes of tree 0.
        :param l1: The labels of all nodes of tree 1.
        :return: The sorted list of all non-terminal nodes that are in both
        derivation trees.
        """

        # Find all intersecting elements of both sets l0 and l1.
        shared_nodes = l0.intersection(l1)

        # Find only the non-terminals present in the intersecting set of
        # labels.
        shared_nodes = [
            i for i in shared_nodes if i in params['BNF_GRAMMAR'].non_terminals
        ]

        return sorted(shared_nodes)

    if random() > params['CROSSOVER_PROBABILITY']:
        # Crossover is not to be performed, return entire individuals.
        ind0 = p_1
        ind1 = p_0

    else:
        # Crossover is to be performed.

        if p_0.invalid:
            # The individual is invalid.
            tail_0 = []

        else:
            # Save tail of each genome.
            tail_0 = p_0.genome[p_0.used_codons:]

        if p_1.invalid:
            # The individual is invalid.
            tail_1 = []

        else:
            # Save tail of each genome.
            tail_1 = p_1.genome[p_1.used_codons:]

        # Get the set of labels of non terminals for each tree.
        labels1 = p_0.tree.get_node_labels(set())
        labels2 = p_1.tree.get_node_labels(set())

        # Find overlapping non-terminals across both trees.
        shared_nodes = intersect(labels1, labels2)

        if len(shared_nodes) != 0:
            # There are overlapping NTs, cross over parts of trees.
            ret_tree0, ret_tree1 = do_crossover(p_0.tree, p_1.tree,
                                                shared_nodes)

        else:
            # There are no overlapping NTs, cross over entire trees.
            ret_tree0, ret_tree1 = p_1.tree, p_0.tree

        # Initialise new individuals using the new trees.
        ind0 = individual.Individual(None, ret_tree0)
        ind1 = individual.Individual(None, ret_tree1)

        # Preserve tails.
        ind0.genome = ind0.genome + tail_0
        ind1.genome = ind1.genome + tail_1

    return [ind0, ind1]
示例#15
0
def PI_grow(size):
    """
    Create a population of size using Position Independent Grow and return.

    :param size: The size of the required population.
    :return: A full population of individuals.
    """

    # Calculate the range of depths to ramp individuals from.
    depths = range(params['BNF_GRAMMAR'].min_ramp + 1,
                   params['MAX_INIT_TREE_DEPTH'] + 1)
    population = []

    if size < 2:
        # If the population size is too small, can't use PI Grow
        # initialisation.
        print("Error: population size too small for PI Grow initialisation.")
        print("Returning randomly built trees.")
        return [
            individual.Individual(sample_genome(), None) for _ in range(size)
        ]

    elif not depths:
        # If we have no depths to ramp from, then params['MAX_INIT_DEPTH'] is
        # set too low for the specified grammar.
        s = "operators.initialisation.PI_grow\n" \
            "Error: Maximum initialisation depth too low for specified " \
            "grammar."
        raise Exception(s)

    else:
        if size < len(depths):
            # The population size is too small to fully cover all ramping
            # depths. Only ramp to the number of depths we can reach.
            depths = depths[:int(size)]

        # Calculate how many individuals are to be generated by each
        # initialisation method.
        times = int(floor(size / len(depths)))
        remainder = int(size - (times * len(depths)))

        # Iterate over depths.
        for depth in depths:
            # Iterate over number of required individuals per depth.
            for i in range(times):

                # Generate individual using "Grow"
                ind = generate_PI_ind_tree(depth)

                # Append individual to population
                population.append(ind)

        if remainder:
            # The full "size" individuals were not generated. The population
            #  will be completed with individuals of random depths.
            depths = list(depths)
            shuffle(depths)

        for i in range(remainder):
            depth = depths.pop()

            # Generate individual using "Grow"
            ind = generate_PI_ind_tree(depth)

            # Append individual to population
            population.append(ind)

        return population