class TestFitnessTruncationRanking(unittest.TestCase):
    def setUp(self):
        self.fitness_list = FitnessList(MAX)
        self.fitness_list.extend([[1.5, 0], [2.5, 1], [12.0, 2]])

    def test_classinit_(self):

        fit = FitnessTruncationRanking(self.fitness_list, 0.5)
        self.assertAlmostEqual(0.5, fit._trunc_rate)

    def test_set_trunc_rate(self):

        fit = FitnessTruncationRanking(self.fitness_list, 0.5)
        self.assertRaises(ValueError, fit.set_trunc_rate, 5)
        self.assertRaises(ValueError, fit.set_trunc_rate, 5.0)
        self.assertRaises(ValueError, fit.set_trunc_rate, -5)
        self.assertRaises(ValueError, fit.set_trunc_rate, -5.0)
        self.assertRaises(ValueError, fit.set_trunc_rate, 1.0)

        fit.set_trunc_rate(0.4)
        self.assertAlmostEqual(0.4, fit._trunc_rate)

    def test_calc_prob(self):
        fit = FitnessTruncationRanking(self.fitness_list, 0.5)
        self.assertAlmostEqual(0.125, fit._calc_prob(10, 2))

    def test_select(self):

        fit = FitnessTruncationRanking(self.fitness_list, 0.5)
        count = len([i for i in fit.select()])
        self.assertEqual(3, count)
class TestReplacement(unittest.TestCase):
    def setUp(self):
        self.fitness_list = FitnessList(MAX)
        self.fitness_list.extend([[1.5, 0], [2.5, 1], [12.0, 2]])
        self.repl = Replacement(self.fitness_list)

    def test_classinit_(self):
        self.assertEqual(0, self.repl._replacement_count)
class TestReplacementTournament(unittest.TestCase):
    def setUp(self):
        self.fitness_list = FitnessList(MAX)
        self.fitness_list.extend([[1.5, 0], [2.5, 1], [12.0, 2]])

        self.repl = ReplacementTournament(self.fitness_list, tournament_size=1)

    def test_classinit_(self):

        self.assertEqual(1, self.repl._tournament_size)
    def test_classinit_(self):

        fitness_list = FitnessList(MAX)
        fitness_list.extend([[1.5, 0], [2.5, 1], [12.0, 2]])

        fit = FitnessTournament(fitness_list)
        self.assertEqual(2, fit._tournament_size)

        fit = FitnessTournament(fitness_list, tournament_size=3)
        self.assertEqual(3, fit._tournament_size)

        self.assertRaises(ValueError, FitnessTournament, fitness_list, tournament_size=4)
    def test_set_fitness_type(self):
        """
        This function tests setting a fitness type.  It also tests the option
        of setting a target value at the same time.

        """

        self.ges.set_fitness_type(CENTER)
        fitl = FitnessList(CENTER)
        self.assertEqual(fitl.get_fitness_type(),
                            self.ges.fitness_list.get_fitness_type())

        fitl = FitnessList(MIN)
        self.ges.set_fitness_type(MIN)
        self.assertEqual(fitl.get_fitness_type(),
                            self.ges.fitness_list.get_fitness_type())

        fitl = FitnessList(MAX)
        self.ges.set_fitness_type(MAX)
        self.assertEqual(fitl.get_fitness_type(),
                            self.ges.fitness_list.get_fitness_type())

        self.assertRaises(ValueError, self.ges.set_fitness_type, "wrong")

        #   Include target value
        fitl = FitnessList(CENTER, 1.0)
        self.ges.set_fitness_type(CENTER, 1.0)
        self.assertEqual(fitl.get_fitness_type(),
                            self.ges.fitness_list.get_fitness_type())
        self.assertEqual(1.0, self.ges.fitness_list._target_value)

        self.assertRaises(ValueError,
                        self.ges.set_fitness_type, CENTER, "test")
class TestFitnessLinearRanking(unittest.TestCase):
    def setUp(self):
        self.fitness_list = FitnessList(MAX)
        self.fitness_list.extend([[1.5, 0], [2.5, 1], [12.0, 2]])

    def test_classinit_(self):

        fit = FitnessLinearRanking(self.fitness_list, 0.6)
        self.assertAlmostEqual(0.6, fit._worstfactor)

    def test_set_worstfactor(self):
        fit = FitnessLinearRanking(self.fitness_list, 0.6)
        self.assertAlmostEqual(0.6, fit._worstfactor)
        fit.set_worstfactor(0.7)
        self.assertAlmostEqual(0.7, fit._worstfactor)

        self.assertRaises(ValueError, fit.set_worstfactor, -0.1)
        self.assertRaises(ValueError, fit.set_worstfactor, 2.1)

    def test_select(self):

        fit = FitnessLinearRanking(self.fitness_list, 0.6)
        count = len([i for i in fit.select()])
        self.assertEqual(3, count)

    def test_linear_ranking(self):
        pass
        fit = FitnessLinearRanking(self.fitness_list, 0.6)

        prob_list = fit._linear_ranking(3, 0.6)
        self.assertAlmostEqual(0.199999999, prob_list[0])
        self.assertAlmostEqual(0.333333333, prob_list[1])
        self.assertAlmostEqual(0.466666666, prob_list[2])

        self.assertLessEqual(prob_list[0], prob_list[1])
        self.assertLessEqual(prob_list[1], prob_list[2])

        self.assertAlmostEqual(1.0, sum(prob_list))

        prob_list = fit._linear_ranking(3, 0.2)
        self.assertAlmostEqual(0.06666667, prob_list[0])
        self.assertAlmostEqual(0.33333333, prob_list[1])
        self.assertAlmostEqual(0.6, prob_list[2])

        self.assertLessEqual(prob_list[0], prob_list[1])
        self.assertLessEqual(prob_list[1], prob_list[2])

        self.assertAlmostEqual(1.0, sum(prob_list))
    def setUp(self):
        self.fitness_list = FitnessList(MAX)

        self.fitness_list.append([3.0, 0])
        self.fitness_list.append([2.0, 1])
        self.fitness_list.append([5.0, 2])
        self.fitness_list.append([4.0, 3])
        self.fitness_list.append([1.0, 4])
class TestFitnessElites(unittest.TestCase):
    def setUp(self):
        self.fitness_list = FitnessList(MAX)
        self.fitness_list.extend([[1.5, 0], [2.5, 1], [12.0, 2]])

    def test_classinit_(self):
        self.assertRaises(ValueError, FitnessElites, self.fitness_list, rate=1.1)
        self.assertRaises(ValueError, FitnessElites, self.fitness_list, rate=-1.1)
        self.assertRaises(ValueError, FitnessElites, self.fitness_list, rate=0.0)

        fit = FitnessElites(self.fitness_list, rate=0.1)
        self.assertAlmostEqual(0.1, fit._rate)
        self.assertAlmostEqual(MIN, fit._selection_type)

    def test_set_rate(self):
        fit = FitnessElites(self.fitness_list, rate=0.1)
        self.assertAlmostEqual(0.1, fit._rate)

    def test_select(self):
        fit = FitnessElites(self.fitness_list, rate=0.3333)
        count = len([i for i in fit.select()])
        self.assertEqual(1, count)
class TestReplacementDeleteWorst(unittest.TestCase):
    def setUp(self):
        self.fitness_list = FitnessList(MAX)
        self.fitness_list.extend([[1.5, 0], [2.5, 1], [12.0, 2]])

        self.repl = ReplacementDeleteWorst(self.fitness_list, replacement_count=1)

    def test_classinit_(self):

        self.assertEqual(1, self.repl._replacement_count)
        self.assertEqual(MIN, self.repl._selection_type)

    def test_set_replacement_count(self):
        self.repl.set_replacement_count(2)
        self.assertEqual(2, self.repl._replacement_count)

        self.repl.set_replacement_count(3)
        self.assertEqual(3, self.repl._replacement_count)

        self.assertRaises(ValueError, self.repl.set_replacement_count, 4)

    def test_select(self):

        #   attempting to maximize remove worst 3
        self.repl.set_replacement_count(3)
        values = [i for i in self.repl.select()]

        self.assertLessEqual(self.repl._fitness_list[values[0]][0], self.repl._fitness_list[values[1]][0])
        self.assertLessEqual(self.repl._fitness_list[values[1]][0], self.repl._fitness_list[values[2]][0])

        #   attempting to minimize remove worst 3
        self.fitness_list = FitnessList(MIN)
        values = [i for i in self.repl.select()]

        self.assertGreaterEqual(self.repl._fitness_list[values[0]][0], self.repl._fitness_list[values[1]][0])
        self.assertGreaterEqual(self.repl._fitness_list[values[1]][0], self.repl._fitness_list[values[2]][0])
    def test_select(self):

        #   attempting to maximize remove worst 3
        self.repl.set_replacement_count(3)
        values = [i for i in self.repl.select()]

        self.assertLessEqual(self.repl._fitness_list[values[0]][0], self.repl._fitness_list[values[1]][0])
        self.assertLessEqual(self.repl._fitness_list[values[1]][0], self.repl._fitness_list[values[2]][0])

        #   attempting to minimize remove worst 3
        self.fitness_list = FitnessList(MIN)
        values = [i for i in self.repl.select()]

        self.assertGreaterEqual(self.repl._fitness_list[values[0]][0], self.repl._fitness_list[values[1]][0])
        self.assertGreaterEqual(self.repl._fitness_list[values[1]][0], self.repl._fitness_list[values[2]][0])
    def test_set_fitness_list(self):

        #   Do MAX
        fitness = FitnessList(MAX)
        fit = Fitness(fitness)
        fit._fitness_list = None
        self.assertEqual(fit._fitness_list, None)
        fit.set_fitness_list(fitness)
        self.assertEqual(fit._fitness_list, fitness)

        #   Do CENTER selection_list converted to distance from target
        fitness = FitnessList(CENTER, 0.15)
        fitness.append([0.5, 0])
        fitness.append([0.25, 1])
        fitness.append([2.5, 2])
        fit = Fitness(fitness)
        fit._fitness_list = None
        self.assertEqual(fit._fitness_list, None)
        fit.set_fitness_list(fitness)
        self.assertEqual(fit._selection_list, [0.35, 0.1, 2.35])
    def __init__(self):
        """
        Because there are a number of parameters to specify, there are no
        specific variables that are initialized within __init__.

        There is a formidable number of default variables specified in this
        function however.

        """

        #   Parameters for changing generations
        self.stopping_criteria = {
                STOPPING_MAX_GEN: None,
                STOPPING_FITNESS_LANDSCAPE: None}
        self._crossover_rate = DEFAULT_CROSSOVER_RATE
        self._children_per_crossover = DEFAULT_CHILDEREN_PER_CROSSOVER
        self._mutation_type = DEFAULT_MUTATION_TYPE
        self._mutation_rate = DEFAULT_MUTATION_RATE
        self._max_fitness_rate = DEFAULT_MAX_FITNESS_RATE

        #   Parameters for phenotype creation
        self._wrap = DEFAULT_WRAP
        self._extend_genotype = DEFAULT_EXTEND_GENOTYPE
        self._start_gene_length = DEFAULT_START_GENE_LENGTH
        self._max_gene_length = DEFAULT_MAX_PROGRAM_LENGTH
        self._max_program_length = DEFAULT_MAX_PROGRAM_LENGTH

        #   Parameters for overall process
        self._generation = 0
        self.fitness_list = FitnessList(CENTER)
        self._fitness_fail = DEFAULT_FITNESS_FAIL
        self._maintain_history = DEFAULT_MAINTAIN_HISTORY
        self._timeouts = DEFAULT_TIMEOUTS

        #   Parameters used during runtime
        self.current_g = None
        self._fitness_selections = []
        self._replacement_selections = []

        self.bnf = {}
        self._population_size = 0
        self.population = []

        self._history = []
class GrammaticalEvolution(object):
    """
    This class comprises the overall process of generating genotypes,
    expressing the genes as programs using grammer and evalating the fitness of
    those members.
    """

    def __init__(self):
        """
        Because there are a number of parameters to specify, there are no
        specific variables that are initialized within __init__.

        There is a formidable number of default variables specified in this
        function however.

        """

        #   Parameters for changing generations
        self.stopping_criteria = {
                STOPPING_MAX_GEN: None,
                STOPPING_FITNESS_LANDSCAPE: None}
        self._crossover_rate = DEFAULT_CROSSOVER_RATE
        self._children_per_crossover = DEFAULT_CHILDEREN_PER_CROSSOVER
        self._mutation_type = DEFAULT_MUTATION_TYPE
        self._mutation_rate = DEFAULT_MUTATION_RATE
        self._max_fitness_rate = DEFAULT_MAX_FITNESS_RATE

        #   Parameters for phenotype creation
        self._wrap = DEFAULT_WRAP
        self._extend_genotype = DEFAULT_EXTEND_GENOTYPE
        self._start_gene_length = DEFAULT_START_GENE_LENGTH
        self._max_gene_length = DEFAULT_MAX_PROGRAM_LENGTH
        self._max_program_length = DEFAULT_MAX_PROGRAM_LENGTH

        #   Parameters for overall process
        self._generation = 0
        self.fitness_list = FitnessList(CENTER)
        self._fitness_fail = DEFAULT_FITNESS_FAIL
        self._maintain_history = DEFAULT_MAINTAIN_HISTORY
        self._timeouts = DEFAULT_TIMEOUTS

        #   Parameters used during runtime
        self.current_g = None
        self._fitness_selections = []
        self._replacement_selections = []

        self.bnf = {}
        self._population_size = 0
        self.population = []

        self._history = []

    def set_population_size(self, size):
        """
        This function sets the total number of genotypes in the population.
        This program uses a fixed size population.

        """

        size = long(size)
        if isinstance(size, long) and size > 0:
            self._population_size = size
            i = len(self.fitness_list)
            while i < size:
                self.fitness_list.append([0.0, i])
                i += 1
        else:
            raise ValueError("""
                population size, %s, must be a long above 0""" % (size))

    def get_population_size(self):
        """
        This function returns total number of genotypes in the
        population.

        """

        return self._population_size

    def set_genotype_length(self, start_gene_length,
                                    max_gene_length=None):
        """
        This function sets the initial size of the binary genotype.  An
        optional max_gene_length can be entered as well.  This permits the
        genotype to grow during the mapping process of the genotype to a
        program.  The lengths are the length of the decimal genotypes, which
        are therefor 8 times longer the binary genotypes created.

        """

        if max_gene_length is None:
            max_gene_length = start_gene_length

        start_gene_length = long(start_gene_length)
        max_gene_length = long(max_gene_length)
        if not isinstance(start_gene_length, long):
            raise ValueError("start_gene_length, %s, must be a long" % (
                                                    start_gene_length))
        if start_gene_length < 0:
            raise ValueError("start_gene_length, %s, must be above 0" % (
                                                    start_gene_length))
        if not isinstance(max_gene_length, long):
            raise ValueError("max_gene_length, %s, must be a long" % (
                                                    max_gene_length))
        if max_gene_length < 0:
            raise ValueError("max_gene_length, %s, must be above 0" % (
                                                    max_gene_length))
        if start_gene_length > max_gene_length:
            raise ValueError("""max_gene_length, %s, cannot be smaller
                than start_gene_length%s""" % (
                    max_gene_length, start_gene_length))

        self._start_gene_length = start_gene_length
        self._max_gene_length = max_gene_length

    def get_genotype_length(self):
        """
        This function returns a tuple with the length the initial genotype and
        the maximum genotype length permitted.

        """

        return (self._start_gene_length, self._max_gene_length)

    def set_extend_genotype(self, true_false):
        """
        This function sets whether the genotype is extended during the gene
        mapping process.

        """

        if isinstance(true_false, bool):
            self._extend_genotype = true_false
        else:
            raise ValueError("Extend genotype must be True or False")

    def get_extend_genotype(self):
        """
        This function returns whether the genotype is extended during the gene
        mapping process.

        """

        return self._extend_genotype

    def set_wrap(self, true_false):
        """
        This function sets whether the genotype is wrapped during the gene
        mapping process.  Wrapping would occur in the iterative process of
        getting the next codon is the basis for the variable selection process.
        If wrapped, when all the codons in the genotype are exhausted, the
        position marker is wrapped around to the first codon in the sequence
        and goes on.

        """

        if isinstance(true_false, bool):
            self._wrap = true_false
        else:
            raise ValueError("Wrap must be True or False")

    def get_wrap(self):
        """
        This function returns whether the genotype is wrapped during the gene
        mapping process.  Wrapping would occur in the iterative process of
        getting the next codon is the basis for the variable selection process.
        If wrapped, when all the codons in the genotype are exhausted, the
        position marker is wrapped around to the first codon in the sequence
        and goes on.

        """

        return self._wrap

    def set_bnf(self, bnf):
        """
        This function parses up a bnf and builds a dictionary. The incoming
        format is designed to follow a format of:  <key> ::= value1 | value2
        \n. The following lines can also hold additional values to accommodate
        longer choices.

        In addition, a set of statements are marked with a key
        starting with "<S".  These are treated differently in that spaces are
        not automatically stripped from the front.  This enables python
        oriented white space to be honored.

        """

        def strip_spaces(key, values):
            """
            This removes white space unless it is a statement.
            """
            if key.startswith(STATEMENT_FORMAT):
                values = [value.rstrip()
                    for value in values.split('|') if value]
            else:
                values = [value.strip()
                    for value in values.split('|') if value]

            return values

        bnf_dict = {}
        for item in bnf.split('\n'):
            if item.find('::=') >= 0:
                key, values = item.split('::=')
                key = key.strip()
                bnf_dict[key] = strip_spaces(key, values)
            elif item:
                values = bnf_dict[key]
                values.extend(strip_spaces(key, item))
                if key.startswith(STATEMENT_FORMAT):
                    #   Convert statements back to string
                    values = ['\n'.join(values)]
                bnf_dict[key] = values
            else:
                #   blank line
                pass
        self.bnf = bnf_dict

    def get_bnf(self):
        """
        This function returns the Backus Naur form of variables that are used
        to map the genotypes to the generated programs.

        """

        return self.bnf

    def set_maintain_history(self, true_false):
        """
        This function sets a flag to maintain a history of fitness_lists.

        """
        if isinstance(true_false, bool):
            self._maintain_history = true_false
        else:
            raise ValueError("Maintain history must be True or False")

    def get_maintain_history(self):
        """
        This function returns a flag indicating whether the fitness list is
        retained for each generation.

        """

        return self._maintain_history

    def set_max_program_length(self, max_program_length):
        """
        This function sets the maximum length that a program can attain before
        the genotype is declared a failure.

        """

        errmsg1 = """The maximum program length, %s must be an long value
                    """ % (max_program_length)
        errmsg2 = """The maximum program length, %s must be greater than 0
                    """ % (max_program_length)
        max_program_length = long(max_program_length)
        if not isinstance(max_program_length, long):
            raise ValueError(errmsg1)
        if max_program_length < 0:
            raise ValueError(errmsg2)

        self._max_program_length = max_program_length

    def get_max_program_length(self):
        """
        This function gets the maximum length that a program can attain before
        the genotype is declared a failure.

        """

        return self._max_program_length

    def set_fitness_fail(self, fitness_fail):
        """
        This function sets the fitness fail value that will be applied to
        fitness functions that are deemed failure.  Failure would be programs
        that fail due to overflows, or programs that grow to greater than
        maximum program length, syntax failures, or other reasons.

        """

        errmsg = """The fitness_fail, %s must be a float value
                    """ % (fitness_fail)
        #   coerce if possible
        fitness_fail = float(fitness_fail)
        if not isinstance(fitness_fail, float):
            raise ValueError(errmsg)

        self._fitness_fail = fitness_fail

    def get_fitness_fail(self):
        """
        This function returns the value of fitness if the program is a failure.

        """

        return self._fitness_fail

    def set_mutation_type(self, mutation_type):
        """
        This function sets the mutation type.  The choices are s(ingle),
        m(ultiple).  If the choice is "s", then the mutation rate is applied
        as a choice of whether to alter 1 bit on a gene or not.  If the choice
        is "m", then the process applies the rate as the probability that a bit
        will be changed as it walks the gene.  In short, "s", means that if the
        gene is mutated, it will take place once.  Otherwise, the gene could be
        mutated multiple times.

        """

        errmsg = "The mutation type must be either '%s' or '%s'." % (
                                                    MUT_TYPE_S, MUT_TYPE_M)
        if mutation_type not in [MUT_TYPE_M, MUT_TYPE_S]:
            raise ValueError(errmsg)

        self._mutation_type = mutation_type

    def get_mutation_type(self):
        """
        This function returns the mutation type.  See set_mutation_type for a
        more complete explanation.

        """

        return self._mutation_type

    def set_mutation_rate(self, mutation_rate):
        """
        This function sets the mutation rate that will be applied to members
        selected into the fitness pool and to newly generated children.  Note
        that the mutation rate should be vastly different depending upon the
        mutation type that you have selected.  If the mutation type is 's',
        then the rate is the probability that the genotype will be mutated.  If
        the mutation type is 'm', then the rate is the probability that the any
        given bit in the genotype will be altered.  Because of that, the
        mutation rate should be significantly lower than the rate used with a
        mutation type of 's'.

        """

        errmsg = """The mutation rate, %s must be a float value
                    from 0.0 to 1.0""" % (mutation_rate)
        if not isinstance(mutation_rate, float):
            raise ValueError(errmsg)
        if not (0.0 <= mutation_rate <= 1.0):
            raise ValueError(errmsg)

        self._mutation_rate = mutation_rate

    def get_mutation_rate(self):
        """
        This function gets the mutation rate that will be applied to members
        selected into the fitness pool and to newly generated children.  Note
        that the mutation rate should be vastly different depending upon the
        mutation type that you have selected.  If the mutation type is 's',
        then the rate is the probability that the genotype will be mutated.  If
        the mutation type is 'm', then the rate is the probability that the any
        given bit in the genotype will be altered.  Because of that, the
        mutation rate should be significantly lower than the rate used with a
        mutation type of 's'.

        """

        return self._mutation_rate

    def set_crossover_rate(self, crossover_rate):
        """
        This function sets the probablity that will be
        applied to members selected into the fitness pool.

        """

        errmsg = """The crossover rate, %s must be a float value
                    from 0.0 to 1.0""" % (crossover_rate)
        if not isinstance(crossover_rate, float):
            raise ValueError(errmsg)
        if not (0.0 <= crossover_rate <= 1.0):
            raise ValueError(errmsg)

        self._crossover_rate = crossover_rate

    def get_crossover_rate(self):
        """
        This function gets the probablity that will be applied to members
        selected into the fitness pool.

        """

        return self._crossover_rate

    def set_children_per_crossover(self, children_per_crossover):
        """
        This function sets the number of children that will generated from two
        parents.  The choice is one or two.

        """

        if children_per_crossover not in [1, 2]:
            raise ValueError(
                "The children per crossovermust be either 1 or 2.")
        self._children_per_crossover = children_per_crossover

    def get_children_per_crossover(self):
        """
        This function gets the number of children that will generated from two
        parents.

        """

        return self._children_per_crossover

    def set_max_generations(self, generations):
        """
        This function sets the maximum number of generations that will be run.

        """

        if isinstance(generations, int) and generations >= 0:
            self.stopping_criteria[STOPPING_MAX_GEN] = generations
        else:
            raise ValueError("""
                generations, %s, must be an int 0 or greater""" % (
                                                                generations))

    def get_max_generations(self):
        """
        This function gets the maximum number of generations that will be run.

        """

        return self.stopping_criteria[STOPPING_MAX_GEN]

    def set_fitness_type(self, fitness_type, target_value=0.0):
        """
        This function sets whether the objective is to achieve as large a
        fitness value possible, small, or hit a target_value.  Therefor the
        choices are 'max', 'min', or 'center'.  If center is used, then a
        target value should be entered as well.  For example, suppose that you
        wanted to hit a target somewhere near zero.  Setting the target_value
        at .001 would cause the process to complete if a fitness value achieved
        and absolute value of .001 or less.

        """

        self.fitness_list.set_fitness_type(fitness_type)
        self.fitness_list.set_target_value(target_value)

    def get_fitness_type(self):
        """
        This function gets whether the objective is to achieve as large a
        fitness value possible, small, or hit a target_value.  Therefor the
        choices are 'max', 'min', or 'center'.  If center is used, then a
        target value should be entered as well.  For example, suppose that you
        wanted to hit a target somewhere near zero.  Setting the target_value
        at .001 would cause the process to complete if a fitness value achieved
        .001 or less.

        """

        return self.fitness_list.get_fitness_type()

    def set_max_fitness_rate(self, max_fitness_rate):
        """
        This function sets a maximum for the number of genotypes that can be
        put in the fitness pool.  Since some fitness selection approaches can
        have a varying number selected, and since multiple selection approaches
        can be applied consequentially, there needs to be an ultimate limit on
        the total number.  The max fitness rate must be a value greater than
        zero and less than 1.0.

        """

        errmsg = """The max fitness rate, %s must be a float value
                    from 0.0 to 1.0""" % (max_fitness_rate)
        if not isinstance(max_fitness_rate, float):
            raise ValueError(errmsg)
        if not (0.0 <= max_fitness_rate <= 1.0):
            raise ValueError(errmsg)

        self._max_fitness_rate = max_fitness_rate

    def get_max_fitness_rate(self):
        """
        This function gets a maximum for the number of genotypes that can be
        in the fitness pool.  Since some fitness selection approaches can have
        a varying number selected, and since multiple selection approaches can
        be applied consequentially, there needs to be an ultimate limit on the
        total number.  The max fitness rate must be a value greater than zero
        and less than 1.0.

        """

        return self._max_fitness_rate

    def set_fitness_selections(self, *params):
        """
        This function loads the fitness selections that are to be used to
        determine genotypes worthy of continuing to the next generation.  There
        can be multiple selections, such as elites and tournaments.  See the
        section Fitness Selection for further information.

        """

        for fitness_selection in params:
            if isinstance(fitness_selection, Fitness):
                self._fitness_selections.append(fitness_selection)
            else:
                raise ValueError("Invalid fitness selection")

    def set_replacement_selections(self, *params):
        """
        This function loads the replacement selections that are used to
        determine genotypes are to be replaced.  Basically, it is the grim
        reaper. Multiple replacement types can be loaded to meet the criteria.
        The number replaced is governed by the fitness selection functions to
        ensure that the population number stays constant.

        """

        for replacement_selection in params:
            if isinstance(replacement_selection, Replacement):
                self._replacement_selections.append(replacement_selection)
            else:
                raise ValueError("Invalid replacement selection")

    def get_fitness_history(self, statistic='best_value'):
        """
        This funcion returns a list of values that represent historical values
        from the fitness history.  While there is a default value of
        'best_value', other values are 'mean', 'min_value', 'max_value',
        'worst_value', 'min_member', 'max_member', 'best_member', and
        'worst_member'. The order is from oldest to newest.

        """

        hist_list = []
        for fitness_list in self._history:
            hist_list.append(fitness_list.__getattribute__(statistic)())
        return hist_list

    def get_best_member(self):
        """
        This function returns the member that it is most fit according to the
        fitness list.  Accordingly, it is only functional after at least one
        generation has been completed.

        """

        return self.population[self.fitness_list.best_member()]

    def get_worst_member(self):
        """
        This function returns the member that it is least fit according to the
        fitness list.  Accordingly, it is only functional after at least one
        generation has been completed.

        """

        return self.population[self.fitness_list.worst_member()]

    def set_timeouts(self, preprogram, program):
        """
        This function sets the number of seconds that the program waits until
        declaring that the process is a runaway and cuts it off.  During the
        mapping process against the preprogram, due to random chance a function
        can be calling another function, which calls another, until the process
        becomes so convoluted that the resulting program will be completely
        useless. While the total length of a program can be guide to its
        uselessnes as well, this is another way to handle it. Since variables
        can also be generated during the running of the program there is a
        second variable for the running program. Clearly, the second value must
        be in harmony with the nature of the program that you are actually
        running. Otherwise, you will be cutting of your program prematurely.
        Note that the second timeout is checked only if the running program
        requests an additional variable.  Otherwise, it will not be triggered.

        """

        if isinstance(preprogram, int) and preprogram >= 0:
            self._timeouts[0] = preprogram
        else:
            raise ValueError("""
                timeout, %s, must be an int 0 or above""" % (preprogram))

        if isinstance(program, int) and program >= 0:
            self._timeouts[1] = program
        else:
            raise ValueError("""
                timeout, %s, must be an int 0 or above""" % (program))

    def get_timeouts(self):
        """
        This function returns the number of seconds that must elapse before
        the mapping process cuts off the process and declares that the genotype
        is a failure.  It returns a tuple for the number of seconds for the
        preprogram and the program itself.

        """

        return self._timeouts

    def _compute_fitness(self):
        """
        This function runs the process of computing fitness functions for each
        genotype and calculating the fitness function.

        """

        for gene in self.population:
            starttime = datetime.now()
            gene._generation = self._generation
            logging.debug("Starting member G %s: %s at %s" % (
                self._generation, gene.member_no,
                starttime.strftime('%m/%d/%y %H:%M')))

            #print "Starting member G %s: %s at %s" % (
                #self._generation, gene.member_no,
                #starttime.strftime('%m/%d/%y %H:%M'))
            gene.starttime = starttime
            self.current_g = gene
            gene.compute_fitness()

            logging.debug("fitness=%s" % (gene.get_fitness()))
            self.fitness_list[gene.member_no][0] = gene.get_fitness()

    def run(self, starting_generation=0):
        """
        Once the parameters have all been set governing the course of the
        evolutionary processing, this function starts the process running.  It
        will continue until it the completion criteria have been set.

        """

        logging.info("started run")
        self._generation = starting_generation
        while True:
            self._compute_fitness()
            if self._maintain_history:
                self._history.append(deepcopy(self.fitness_list))

            if self._continue_processing():
                self._perform_endcycle()

                logging.info("Finished generation: %s Max generations: %s" % (
                            self._generation,
                            self.get_max_generations()))
                logging.info(' '.join(
                            ["best_value: %s" % (
                                self.fitness_list.best_value()),
                            "median: %s" % (self.fitness_list.median()),
                            "mean: %s" % (self.fitness_list.mean())]))
                #temp -- remove this
                gene = self.population[self.fitness_list.best_member()]
                program = gene.get_program()
                logging.info(program)

                #logging.debug("stddev= %s" % self.fitness_list.stddev())
                self._generation += 1
            else:
                break

        logging.info(
            "completed run: generations: %s, best member:%s fitness: %s" % (
                    self._generation,
                    self.fitness_list.best_member(),
                    self.fitness_list.best_value()))

        return self.fitness_list.best_member()

    def create_genotypes(self):
        """
        This function creates a genotype using the input parameters for each
        member of the population, and transfers operating parameters to the
        genotype for running the fitness functions.

        """

        member_no = 0
        while member_no < self._population_size:
            gene = Genotype(self._start_gene_length,
                        self._max_gene_length,
                        member_no)
            #   a local copy is made because variables
            #   can be saved within the local_bnf
            gene.local_bnf = deepcopy(self.bnf)
            gene.local_bnf['<member_no>'] = [gene.member_no]
            gene._max_program_length = self._max_program_length
            gene._fitness = self._fitness_fail
            gene._fitness_fail = self._fitness_fail
            gene._extend_genotype = self._extend_genotype
            gene._timeouts = self._timeouts
            gene._wrap = self._wrap
            self.population.append(gene)
            member_no += 1

    def _perform_endcycle(self):
        """
        This function runs after each member of the population has computed
        their fitness function.  Then, the fitness selection objects will
        evaluate those members according to their respective criteria and
        develop a pool of members that will potentially survive to the next
        generation. Crossovers will take place from that pool and each member
        will be subject to the possibility of mutatuting.  Finally, a
        replacement process will find which members should be replaced. The
        fitness pool will then replace those members.

        """

        fitness_pool = self._evaluate_fitness()
        child_list = self._perform_crossovers(fitness_pool)

        fitness_pool.extend(child_list)
        self._perform_mutations(fitness_pool)
        self._perform_replacements(fitness_pool)

    def _evaluate_fitness(self):
        """
        This function evaluates the fitness of the members in the light of the
        fitness criteria functions.  It returns a list of members that will be
        used for crossovers and mutations.

        """

        flist = []
        total = int(round(
            self._max_fitness_rate * float(self._population_size)))
        count = 0
        for fsel in self._fitness_selections:
            fsel.set_fitness_list(self.fitness_list)
            for i in fsel.select():
                flist.append(i)
                count += 1
                if count == total:
                    #   Done
                    break

        flist1 = []
        for member_no in flist:
            flist1.append(deepcopy(self.population[member_no]))

        return flist1

    def _perform_crossovers(self, flist):
        """
        This function accepts a list of genotypes that are to be crossed.  The
        list is processed two at a time, and a child list holding the offspring
        is returned.  The _children_per_crossover indicator governs whether two
        children are produced or one.

        """

        child_list = []
        length = len(flist)
        if length % 2 == 1:
            length -= 1
        if length >= 2:
            for i in xrange(0, length, 2):
                parent1 = flist[i]
                parent2 = flist[i + 1]

                child1, child2 = self._crossover(parent1, parent2)
                if self._children_per_crossover == 2:
                    child_list.append(child1)
                    child_list.append(child2)
                else:
                    child_list.append(child1)

        return child_list

    def _crossover(self, parent1, parent2):
        """
        This function accepts two parents, randomly selects which is parent1
        and which is parent2.  Then, executes the crossover, and returns two
        children.

        """

        if not isinstance(parent1, Genotype):
            raise ValueError("Parent1 is not a genotype")
        if not isinstance(parent2, Genotype):
            raise ValueError("Parent2 is not a genotype")

        if randint(0, 1):
            child1 = deepcopy(parent1)
            child2 = deepcopy(parent2)
        else:
            child1 = deepcopy(parent2)
            child2 = deepcopy(parent1)

        child1_binary = child1.binary_gene
        child2_binary = child2.binary_gene

        minlength = min(len(child1_binary), len(child2_binary))
        crosspoint = randint(2, minlength - 2)

        child1_binary, child2_binary = self._crossover_function(
            child1.binary_gene, child2.binary_gene, crosspoint)

        child1.set_binary_gene(child1_binary)
        child1.generate_decimal_gene()
        child2.set_binary_gene(child2_binary)
        child2.generate_decimal_gene()

        return (child1, child2)

    @staticmethod
    def _crossover_function(child1_binary, child2_binary, crosspoint):
        """
        This function performs the actual crossover of material at a random
        point.

        I gratefully acknowlege Franco from Argentina ([email protected]) for
        the fix to my previous version of this code.

        """

        child1_binary, child2_binary = child1_binary[0:crosspoint] + \
                                    child2_binary[crosspoint:], \
                                    child2_binary[0:crosspoint] + \
                                    child1_binary[crosspoint:]

        return (child1_binary, child2_binary)

    def _perform_mutations(self, mlist):
        """
        This functions accepts a list of genotypes that are subject to
        mutation.  Each genotype is then put at risk for mutation and may or
        may not be mutated.

        """

        for gene in mlist:
            gene.mutate(self._mutation_rate, self._mutation_type)

    def _perform_replacements(self, fitness_pool):
        """
        This function accepts a list of members that will replace lesser
        performers.  The replacement process then applies the fitness pool to
        the population.

        """

        position = 0
        for rsel in self._replacement_selections:
            rsel.set_fitness_list(self.fitness_list)

            for replaced_no in rsel.select():
                replaced_g = self.population[replaced_no]
                if position < len(fitness_pool):
                    new_g = fitness_pool[position]
                    new_g.member_no = replaced_g.member_no
                    new_g._generation = self._generation + 1

                    #   update local bnf
                    new_g.local_bnf['<member_no>'] = [new_g.member_no]

                    self.population[new_g.member_no] = new_g
                    position += 1
                else:
                    break

    def _continue_processing(self):
        """
        This function, using the criteria for ending the evolutionary process
        after each generation, returns a flag of whether to continue or not.

        """

        status = True
        fitl = self.fitness_list

        #   check max generations first
        if self.stopping_criteria[STOPPING_MAX_GEN] is not None:
            if self.stopping_criteria[STOPPING_MAX_GEN] <= self._generation:
                logging.info("stopping processing due to max generation")
                return False

        #   check target value
        if fitl.get_target_value() is not None:
            if fitl.get_fitness_type() == MAX:
                if fitl.best_value() >= fitl.get_target_value():
                    logging.info(' '.join([
                    "stopping processing due to",
                    "best value, %s, better than target value, %s" % (
                    fitl.best_value(), fitl.get_target_value())]))
                    return False
            elif fitl.get_fitness_type() == MIN:
                if fitl.best_value() <= fitl.get_target_value():
                    logging.info(' '.join([
                    "stopping processing due to",
                    "best value, %s, better than target value, %s" % (
                    fitl.best_value(), fitl.get_target_value())]))
                    return False
            elif fitl.get_fitness_type() == CENTER:
                if fitl.best_value() <= fitl.get_target_value():
                    logging.info(' '.join([
                    "stopping processing due to",
                    "best value, %s, better than target value, %s" % (
                    fitl.best_value(), fitl.get_target_value())]))
                    return False

        #   Finally check if there is a stopping function
        if self.stopping_criteria[STOPPING_FITNESS_LANDSCAPE] is not None:
            status = self.stopping_criteria[STOPPING_FITNESS_LANDSCAPE](
                                                        self.fitness_list)
            logging.info(' '.join([
                                "stopping processing due to",
                                "fitness landscape being reached."]))

        return status
    def test_set_fitness_type(self):
        """
        This function tests setting a fitness type.  It also tests the option
        of setting a target value at the same time.

        """

        self.ges.set_fitness_type(CENTER)
        fitl = FitnessList(CENTER)
        self.assertEqual(fitl.get_fitness_type(),
                            self.ges.fitness_list.get_fitness_type())

        fitl = FitnessList(MIN)
        self.ges.set_fitness_type(MIN)
        self.assertEqual(fitl.get_fitness_type(),
                            self.ges.fitness_list.get_fitness_type())

        fitl = FitnessList(MAX)
        self.ges.set_fitness_type(MAX)
        self.assertEqual(fitl.get_fitness_type(),
                            self.ges.fitness_list.get_fitness_type())

        self.assertRaises(ValueError, self.ges.set_fitness_type, "wrong")

        #   Include target value
        fitl = FitnessList(CENTER, 1.0)
        self.ges.set_fitness_type(CENTER, 1.0)
        self.assertEqual(fitl.get_fitness_type(),
                            self.ges.fitness_list.get_fitness_type())
        self.assertEqual(1.0, self.ges.fitness_list._target_value)

        self.assertRaises(ValueError,
                        self.ges.set_fitness_type, CENTER, "test")
Beispiel #15
0
 def setUp(self):
     #   Check truncation
     self.fitness = FitnessList(MAX)
     self.fitness.extend([[1.5, 0], [2.5, 1], [12.0, 2]])
Beispiel #16
0
 def setUp(self):
     self.fitness_list = FitnessList(MAX)
     self.fitness_list.extend([[1.5, 0], [2.5, 1], [12.0, 2]])
Beispiel #17
0
    def test_scale_list(self):

        #
        #   fitness type MAX, test select type MAX
        #
        fitness = FitnessList(MAX)
        fitness.extend([[.5, 0], [.25, 1], [2.5, 2]])

        fit = Fitness(fitness)
        fit.set_selection_type(MAX)
        fit._scale_list()

        #   not inverted
        self.assertAlmostEqual(.5, fit._selection_list[0])
        self.assertAlmostEqual(.25, fit._selection_list[1])
        self.assertAlmostEqual(2.5, fit._selection_list[2])

        #
        #   fitness type MAX, test select type MIN
        #
        fit.set_fitness_list(fitness)
        fit.set_selection_type(MIN)
        fit._scale_list()

        #   inverted
        self.assertAlmostEqual(2.0, fit._selection_list[0])
        self.assertAlmostEqual(4.0, fit._selection_list[1])
        self.assertAlmostEqual(0.4, fit._selection_list[2])

        #
        #   fitness type MIN, test select type MAX
        #
        fitness.set_fitness_type(MIN)
        fit.set_fitness_list(fitness)
        fit.set_selection_type(MAX)
        fit._scale_list()

        #   inverted
        self.assertAlmostEqual(2.0, fit._selection_list[0])
        self.assertAlmostEqual(4.0, fit._selection_list[1])
        self.assertAlmostEqual(0.4, fit._selection_list[2])

        #
        #   fitness type MIN, test select type MIN
        #
        fit.set_fitness_list(fitness)
        fit.set_selection_type(MIN)
        fit._scale_list()

        #   not inverted
        self.assertAlmostEqual(.5, fit._selection_list[0])
        self.assertAlmostEqual(.25, fit._selection_list[1])
        self.assertAlmostEqual(2.5, fit._selection_list[2])

        #
        #   fitness type CENTER, test select type MAX
        #
        fitness.set_fitness_type(CENTER)
        fitness.set_target_value(.75)
        fit.set_fitness_list(fitness)
        fit.set_selection_type(MAX)
        fit._scale_list()

        #   inverted
        self.assertAlmostEqual(4.0, fit._selection_list[0])
        self.assertAlmostEqual(2.0, fit._selection_list[1])
        self.assertAlmostEqual(0.5714285714, fit._selection_list[2])

        #
        #   fitness type CENTER, test select type MIN
        #
        fit.set_fitness_list(fitness)
        fit.set_selection_type(MIN)
        fit._scale_list()

        #   not inverted
        self.assertAlmostEqual(.25, fit._selection_list[0])
        self.assertAlmostEqual(.5, fit._selection_list[1])
        self.assertAlmostEqual(1.75, fit._selection_list[2])
Beispiel #18
0
class TestFitnessProportionate(unittest.TestCase):
    def setUp(self):
        #   Check truncation
        self.fitness = FitnessList(MAX)
        self.fitness.extend([[1.5, 0], [2.5, 1], [12.0, 2]])

    def test_class_init_(self):
        #   is the scaling type set
        #   does it check the number range
        fit = FitnessProportionate(self.fitness, SCALING_LINEAR)
        self.assertNotEqual(None, fit._scaling_type)

        #   add some negative numbers
        self.fitness.extend([[-.5, 3], [-.25, 4], [2.5, 2]])
        self.assertRaises(ValueError, FitnessProportionate, self.fitness,
                          SCALING_LINEAR)

    def test_set_scaling_type(self):
        fit = FitnessProportionate(self.fitness, SCALING_LINEAR)
        self.assertEqual(SCALING_LINEAR, fit._scaling_type)

    def test_check_minmax(self):
        self.fitness.extend([[.5, 3], [.25, 1], [-2.5, 2]])
        self.assertRaises(ValueError, FitnessProportionate, self.fitness,
                          SCALING_LINEAR)

    def test_select(self):
        fit = FitnessProportionate(self.fitness, SCALING_LINEAR)

        self.assertEqual(3, len([i for i in fit.select()]))

    def test_apply_prop_scaling(self):

        #   This scales the list according to the scaling type
        param = None
        #   Check linear
        fit = FitnessProportionate(self.fitness, SCALING_LINEAR)
        scaling_list = fit._apply_prop_scaling(param)
        self.assertAlmostEqual(1.0, sum(scaling_list))

        self.assertAlmostEqual(0.09375, scaling_list[0])
        self.assertAlmostEqual(0.15625, scaling_list[1])
        self.assertAlmostEqual(0.75, scaling_list[2])

        #   Check exponential
        fit = FitnessProportionate(self.fitness, SCALING_EXPONENTIAL)
        scaling_list = fit._apply_prop_scaling(param)
        self.assertAlmostEqual(1.0, sum(scaling_list))

        self.assertAlmostEqual(0.014754098360655738, scaling_list[0])
        self.assertAlmostEqual(0.040983606557377046, scaling_list[1])
        self.assertAlmostEqual(0.94426229508196724, scaling_list[2])

        fit = FitnessProportionate(self.fitness, SCALING_EXPONENTIAL)
        scaling_list = fit._apply_prop_scaling(param=1.5)
        self.assertAlmostEqual(1.0, sum(scaling_list))

        self.assertAlmostEqual(0.038791152234464166, scaling_list[0])
        self.assertAlmostEqual(0.083465270324597968, scaling_list[1])
        self.assertAlmostEqual(0.87774357744093778, scaling_list[2])

        #   Check log
        fit = FitnessProportionate(self.fitness, SCALING_LOG)
        scaling_list = fit._apply_prop_scaling(param)
        self.assertAlmostEqual(1.0, sum(scaling_list))

        self.assertAlmostEqual(0.10651459360996007, scaling_list[0])
        self.assertAlmostEqual(0.24070711137026513, scaling_list[1])
        self.assertAlmostEqual(0.6527782950197748, scaling_list[2])

        #   Check truncation
        fit = FitnessProportionate(self.fitness, SCALING_TRUNC)
        scaling_list = fit._apply_prop_scaling(param=2.0)
        self.assertAlmostEqual(1.0, sum(scaling_list))

        self.assertAlmostEqual(0.0, scaling_list[0])
        self.assertAlmostEqual(0.17241379310344829, scaling_list[1])
        self.assertAlmostEqual(0.82758620689655171, scaling_list[2])
class TestFitnessList(unittest.TestCase):
    """
    This class tests the base class of fitness.
    """

    def setUp(self):
        self.fitness_list = FitnessList(MAX)

        self.fitness_list.append([3.0, 0])
        self.fitness_list.append([2.0, 1])
        self.fitness_list.append([5.0, 2])
        self.fitness_list.append([4.0, 3])
        self.fitness_list.append([1.0, 4])

    def test_class_init_(self):

        fitness_list = FitnessList(MAX)

        #   Is it a list?
        self.assertEqual(True, isinstance(fitness_list, list))

        #   Does the fitness type get set?
        self.assertEqual(MAX, fitness_list._fitness_type)

        #   Does the target_value get set?
        self.assertEqual(0.0, fitness_list._target_value)

        fitness_list = FitnessList(MAX, 0.5)
        self.assertAlmostEqual(0.5, fitness_list._target_value)

    def test_set_fitness_type(self):

        self.fitness_list.set_fitness_type(MIN)
        self.assertEqual(MIN, self.fitness_list._fitness_type)

    def test_get_fitness_type(self):

        self.assertEqual(MAX, self.fitness_list.get_fitness_type())

        self.fitness_list._fitness_type = MIN
        self.assertEqual(MIN, self.fitness_list.get_fitness_type())

    def test_set_target_value(self):

        self.fitness_list.set_target_value(0.3)
        self.assertEqual(0.3, self.fitness_list._target_value)

    def test_get_target_value(self):
        self.fitness_list._target_value = 0.45
        self.assertAlmostEqual(0.45, self.fitness_list.get_target_value())

    def test_min_value(self):
        self.assertEqual(1.0, self.fitness_list.min_value())

    def test_max_value(self):
        self.assertEqual(5.0, self.fitness_list.max_value())

    def test_best_value(self):
        self.assertEqual(5.0, self.fitness_list.best_value())

        self.fitness_list.set_fitness_type(MIN)
        self.assertEqual(1.0, self.fitness_list.best_value())

        self.fitness_list.set_fitness_type(CENTER)
        self.fitness_list.set_target_value(3.0)
        self.assertEqual(3.0, self.fitness_list.best_value())

    def test_worst_value(self):
        self.assertEqual(1.0, self.fitness_list.worst_value())

        self.fitness_list.set_fitness_type(MIN)
        self.assertEqual(5.0, self.fitness_list.worst_value())

        self.fitness_list.set_fitness_type(CENTER)
        self.fitness_list.set_target_value(3.0)
        self.assertEqual(1.0, self.fitness_list.worst_value())

    def test_min_member(self):
        self.assertEqual(4, self.fitness_list.min_member())

    def test_max_member(self):
        self.assertEqual(2, self.fitness_list.max_member())

    def test_best_member(self):
        #   max
        self.assertEqual(2, self.fitness_list.best_member())

        #   min
        self.fitness_list.set_fitness_type(MIN)
        self.assertEqual(4, self.fitness_list.best_member())

        #   center
        self.fitness_list.set_fitness_type(CENTER)
        self.assertEqual(4, self.fitness_list.best_member())

        #   check absolute value on center
        self.fitness_list.append([-0.5, 5])
        self.assertEqual(5, self.fitness_list.best_member())

    def test_worst_member(self):
        #   max
        self.assertEqual(4, self.fitness_list.worst_member())

        #   min
        self.fitness_list.set_fitness_type(MIN)
        self.assertEqual(2, self.fitness_list.worst_member())

        #   center
        self.fitness_list.set_fitness_type(CENTER)
        self.assertEqual(2, self.fitness_list.worst_member())

        #   check absolute value on center
        self.fitness_list.append([-5.0, 5])
        self.assertEqual(5, self.fitness_list.worst_member())

    def test_mean(self):

        mean = (3.0 + 2.0 + 5.0 + 4.0 + 1.0) / 5
        self.assertAlmostEqual(mean, self.fitness_list.mean())

    def test_median(self):

        self.assertAlmostEqual(3.0, self.fitness_list.median())

    def test_stddev(self):
        self.assertAlmostEqual(1.5811388301, self.fitness_list.stddev())

    def test_sorted(self):

        #   max
        sorted_list = self.fitness_list.sorted()
        self.assertAlmostEqual(5.0, sorted_list[0][0])
        self.assertEqual(2, sorted_list[0][1])
        self.assertAlmostEqual(4.0, sorted_list[1][0])
        self.assertEqual(3, sorted_list[1][1])
        self.assertAlmostEqual(3.0, sorted_list[2][0])
        self.assertEqual(0, sorted_list[2][1])
        self.assertAlmostEqual(2.0, sorted_list[3][0])
        self.assertEqual(1, sorted_list[3][1])
        self.assertAlmostEqual(1.0, sorted_list[4][0])
        self.assertEqual(4, sorted_list[4][1])

        #   min
        self.fitness_list.set_fitness_type(MIN)
        sorted_list = self.fitness_list.sorted()

        self.assertAlmostEqual(1.0, sorted_list[0][0])
        self.assertEqual(4, sorted_list[0][1])
        self.assertAlmostEqual(2.0, sorted_list[1][0])
        self.assertEqual(1, sorted_list[1][1])
        self.assertAlmostEqual(3.0, sorted_list[2][0])
        self.assertEqual(0, sorted_list[2][1])
        self.assertAlmostEqual(4.0, sorted_list[3][0])
        self.assertEqual(3, sorted_list[3][1])
        self.assertAlmostEqual(5.0, sorted_list[4][0])
        self.assertEqual(2, sorted_list[4][1])

        #   center
        self.fitness_list.set_fitness_type(CENTER)
        self.fitness_list.append([-0.5, 5])
        sorted_list = self.fitness_list.sorted()

        self.assertAlmostEqual(-0.5, sorted_list[0][0])
        self.assertEqual(5, sorted_list[0][1])
        self.assertAlmostEqual(1.0, sorted_list[1][0])
        self.assertEqual(4, sorted_list[1][1])
        self.assertAlmostEqual(2.0, sorted_list[2][0])
        self.assertEqual(1, sorted_list[2][1])
        self.assertAlmostEqual(3.0, sorted_list[3][0])
        self.assertEqual(0, sorted_list[3][1])
        self.assertAlmostEqual(4.0, sorted_list[4][0])
        self.assertEqual(3, sorted_list[4][1])
        self.assertAlmostEqual(5.0, sorted_list[5][0])
        self.assertEqual(2, sorted_list[5][1])
Beispiel #20
0
class TestFitnessList(unittest.TestCase):
    """
    This class tests the base class of fitness.
    """
    def setUp(self):
        self.fitness_list = FitnessList(MAX)

        self.fitness_list.append([3.0, 0])
        self.fitness_list.append([2.0, 1])
        self.fitness_list.append([5.0, 2])
        self.fitness_list.append([4.0, 3])
        self.fitness_list.append([1.0, 4])

    def test_class_init_(self):

        fitness_list = FitnessList(MAX)

        #   Is it a list?
        self.assertEqual(True, isinstance(fitness_list, list))

        #   Does the fitness type get set?
        self.assertEqual(MAX, fitness_list._fitness_type)

        #   Does the target_value get set?
        self.assertEqual(0.0, fitness_list._target_value)

        fitness_list = FitnessList(MAX, .5)
        self.assertAlmostEqual(0.5, fitness_list._target_value)

    def test_set_fitness_type(self):

        self.fitness_list.set_fitness_type(MIN)
        self.assertEqual(MIN, self.fitness_list._fitness_type)

    def test_get_fitness_type(self):

        self.assertEqual(MAX, self.fitness_list.get_fitness_type())

        self.fitness_list._fitness_type = MIN
        self.assertEqual(MIN, self.fitness_list.get_fitness_type())

    def test_set_target_value(self):

        self.fitness_list.set_target_value(0.3)
        self.assertEqual(0.3, self.fitness_list._target_value)

    def test_get_target_value(self):
        self.fitness_list._target_value = .45
        self.assertAlmostEqual(.45, self.fitness_list.get_target_value())

    def test_min_value(self):
        self.assertEqual(1.0, self.fitness_list.min_value())

    def test_max_value(self):
        self.assertEqual(5.0, self.fitness_list.max_value())

    def test_best_value(self):
        self.assertEqual(5.0, self.fitness_list.best_value())

        self.fitness_list.set_fitness_type(MIN)
        self.assertEqual(1.0, self.fitness_list.best_value())

        self.fitness_list.set_fitness_type(CENTER)
        self.fitness_list.set_target_value(3.0)
        self.assertEqual(3.0, self.fitness_list.best_value())

    def test_worst_value(self):
        self.assertEqual(1.0, self.fitness_list.worst_value())

        self.fitness_list.set_fitness_type(MIN)
        self.assertEqual(5.0, self.fitness_list.worst_value())

        self.fitness_list.set_fitness_type(CENTER)
        self.fitness_list.set_target_value(3.0)
        self.assertEqual(1.0, self.fitness_list.worst_value())

    def test_min_member(self):
        self.assertEqual(4, self.fitness_list.min_member())

    def test_max_member(self):
        self.assertEqual(2, self.fitness_list.max_member())

    def test_best_member(self):
        #   max
        self.assertEqual(2, self.fitness_list.best_member())

        #   min
        self.fitness_list.set_fitness_type(MIN)
        self.assertEqual(4, self.fitness_list.best_member())

        #   center
        self.fitness_list.set_fitness_type(CENTER)
        self.assertEqual(4, self.fitness_list.best_member())

        #   check absolute value on center
        self.fitness_list.append([-.5, 5])
        self.assertEqual(5, self.fitness_list.best_member())

    def test_worst_member(self):
        #   max
        self.assertEqual(4, self.fitness_list.worst_member())

        #   min
        self.fitness_list.set_fitness_type(MIN)
        self.assertEqual(2, self.fitness_list.worst_member())

        #   center
        self.fitness_list.set_fitness_type(CENTER)
        self.assertEqual(2, self.fitness_list.worst_member())

        #   check absolute value on center
        self.fitness_list.append([-5.0, 5])
        self.assertEqual(5, self.fitness_list.worst_member())

    def test_mean(self):

        mean = (3.0 + 2.0 + 5.0 + 4.0 + 1.0) / 5
        self.assertAlmostEqual(mean, self.fitness_list.mean())

    def test_median(self):

        self.assertAlmostEqual(3.0, self.fitness_list.median())

    def test_stddev(self):
        self.assertAlmostEqual(1.5811388301, self.fitness_list.stddev())

    def test_sorted(self):

        #   max
        sorted_list = self.fitness_list.sorted()
        self.assertAlmostEqual(5.0, sorted_list[0][0])
        self.assertEqual(2, sorted_list[0][1])
        self.assertAlmostEqual(4.0, sorted_list[1][0])
        self.assertEqual(3, sorted_list[1][1])
        self.assertAlmostEqual(3.0, sorted_list[2][0])
        self.assertEqual(0, sorted_list[2][1])
        self.assertAlmostEqual(2.0, sorted_list[3][0])
        self.assertEqual(1, sorted_list[3][1])
        self.assertAlmostEqual(1.0, sorted_list[4][0])
        self.assertEqual(4, sorted_list[4][1])

        #   min
        self.fitness_list.set_fitness_type(MIN)
        sorted_list = self.fitness_list.sorted()

        self.assertAlmostEqual(1.0, sorted_list[0][0])
        self.assertEqual(4, sorted_list[0][1])
        self.assertAlmostEqual(2.0, sorted_list[1][0])
        self.assertEqual(1, sorted_list[1][1])
        self.assertAlmostEqual(3.0, sorted_list[2][0])
        self.assertEqual(0, sorted_list[2][1])
        self.assertAlmostEqual(4.0, sorted_list[3][0])
        self.assertEqual(3, sorted_list[3][1])
        self.assertAlmostEqual(5.0, sorted_list[4][0])
        self.assertEqual(2, sorted_list[4][1])

        #   center
        self.fitness_list.set_fitness_type(CENTER)
        self.fitness_list.append([-0.5, 5])
        sorted_list = self.fitness_list.sorted()

        self.assertAlmostEqual(-0.5, sorted_list[0][0])
        self.assertEqual(5, sorted_list[0][1])
        self.assertAlmostEqual(1.0, sorted_list[1][0])
        self.assertEqual(4, sorted_list[1][1])
        self.assertAlmostEqual(2.0, sorted_list[2][0])
        self.assertEqual(1, sorted_list[2][1])
        self.assertAlmostEqual(3.0, sorted_list[3][0])
        self.assertEqual(0, sorted_list[3][1])
        self.assertAlmostEqual(4.0, sorted_list[4][0])
        self.assertEqual(3, sorted_list[4][1])
        self.assertAlmostEqual(5.0, sorted_list[5][0])
        self.assertEqual(2, sorted_list[5][1])
    def test_scale_list(self):

        #
        #   fitness type MAX, test select type MAX
        #
        fitness = FitnessList(MAX)
        fitness.extend([[0.5, 0], [0.25, 1], [2.5, 2]])

        fit = Fitness(fitness)
        fit.set_selection_type(MAX)
        fit._scale_list()

        #   not inverted
        self.assertAlmostEqual(0.5, fit._selection_list[0])
        self.assertAlmostEqual(0.25, fit._selection_list[1])
        self.assertAlmostEqual(2.5, fit._selection_list[2])

        #
        #   fitness type MAX, test select type MIN
        #
        fit.set_fitness_list(fitness)
        fit.set_selection_type(MIN)
        fit._scale_list()

        #   inverted
        self.assertAlmostEqual(2.0, fit._selection_list[0])
        self.assertAlmostEqual(4.0, fit._selection_list[1])
        self.assertAlmostEqual(0.4, fit._selection_list[2])

        #
        #   fitness type MIN, test select type MAX
        #
        fitness.set_fitness_type(MIN)
        fit.set_fitness_list(fitness)
        fit.set_selection_type(MAX)
        fit._scale_list()

        #   inverted
        self.assertAlmostEqual(2.0, fit._selection_list[0])
        self.assertAlmostEqual(4.0, fit._selection_list[1])
        self.assertAlmostEqual(0.4, fit._selection_list[2])

        #
        #   fitness type MIN, test select type MIN
        #
        fit.set_fitness_list(fitness)
        fit.set_selection_type(MIN)
        fit._scale_list()

        #   not inverted
        self.assertAlmostEqual(0.5, fit._selection_list[0])
        self.assertAlmostEqual(0.25, fit._selection_list[1])
        self.assertAlmostEqual(2.5, fit._selection_list[2])

        #
        #   fitness type CENTER, test select type MAX
        #
        fitness.set_fitness_type(CENTER)
        fitness.set_target_value(0.75)
        fit.set_fitness_list(fitness)
        fit.set_selection_type(MAX)
        fit._scale_list()

        #   inverted
        self.assertAlmostEqual(4.0, fit._selection_list[0])
        self.assertAlmostEqual(2.0, fit._selection_list[1])
        self.assertAlmostEqual(0.5714285714, fit._selection_list[2])

        #
        #   fitness type CENTER, test select type MIN
        #
        fit.set_fitness_list(fitness)
        fit.set_selection_type(MIN)
        fit._scale_list()

        #   not inverted
        self.assertAlmostEqual(0.25, fit._selection_list[0])
        self.assertAlmostEqual(0.5, fit._selection_list[1])
        self.assertAlmostEqual(1.75, fit._selection_list[2])
    def setUp(self):
        self.fitness_list = FitnessList(MAX)
        self.fitness_list.extend([[1.5, 0], [2.5, 1], [12.0, 2]])

        self.repl = ReplacementTournament(self.fitness_list, tournament_size=1)
class TestFitnessProportionate(unittest.TestCase):
    def setUp(self):
        #   Check truncation
        self.fitness = FitnessList(MAX)
        self.fitness.extend([[1.5, 0], [2.5, 1], [12.0, 2]])

    def test_class_init_(self):
        #   is the scaling type set
        #   does it check the number range
        fit = FitnessProportionate(self.fitness, SCALING_LINEAR)
        self.assertNotEqual(None, fit._scaling_type)

        #   add some negative numbers
        self.fitness.extend([[-0.5, 3], [-0.25, 4], [2.5, 2]])
        self.assertRaises(ValueError, FitnessProportionate, self.fitness, SCALING_LINEAR)

    def test_set_scaling_type(self):
        fit = FitnessProportionate(self.fitness, SCALING_LINEAR)
        self.assertEqual(SCALING_LINEAR, fit._scaling_type)

    def test_check_minmax(self):
        self.fitness.extend([[0.5, 3], [0.25, 1], [-2.5, 2]])
        self.assertRaises(ValueError, FitnessProportionate, self.fitness, SCALING_LINEAR)

    def test_select(self):
        fit = FitnessProportionate(self.fitness, SCALING_LINEAR)

        self.assertEqual(3, len([i for i in fit.select()]))

    def test_apply_prop_scaling(self):

        #   This scales the list according to the scaling type
        param = None
        #   Check linear
        fit = FitnessProportionate(self.fitness, SCALING_LINEAR)
        scaling_list = fit._apply_prop_scaling(param)
        self.assertAlmostEqual(1.0, sum(scaling_list))

        self.assertAlmostEqual(0.09375, scaling_list[0])
        self.assertAlmostEqual(0.15625, scaling_list[1])
        self.assertAlmostEqual(0.75, scaling_list[2])

        #   Check exponential
        fit = FitnessProportionate(self.fitness, SCALING_EXPONENTIAL)
        scaling_list = fit._apply_prop_scaling(param)
        self.assertAlmostEqual(1.0, sum(scaling_list))

        self.assertAlmostEqual(0.014754098360655738, scaling_list[0])
        self.assertAlmostEqual(0.040983606557377046, scaling_list[1])
        self.assertAlmostEqual(0.94426229508196724, scaling_list[2])

        fit = FitnessProportionate(self.fitness, SCALING_EXPONENTIAL)
        scaling_list = fit._apply_prop_scaling(param=1.5)
        self.assertAlmostEqual(1.0, sum(scaling_list))

        self.assertAlmostEqual(0.038791152234464166, scaling_list[0])
        self.assertAlmostEqual(0.083465270324597968, scaling_list[1])
        self.assertAlmostEqual(0.87774357744093778, scaling_list[2])

        #   Check log
        fit = FitnessProportionate(self.fitness, SCALING_LOG)
        scaling_list = fit._apply_prop_scaling(param)
        self.assertAlmostEqual(1.0, sum(scaling_list))

        self.assertAlmostEqual(0.10651459360996007, scaling_list[0])
        self.assertAlmostEqual(0.24070711137026513, scaling_list[1])
        self.assertAlmostEqual(0.6527782950197748, scaling_list[2])

        #   Check truncation
        fit = FitnessProportionate(self.fitness, SCALING_TRUNC)
        scaling_list = fit._apply_prop_scaling(param=2.0)
        self.assertAlmostEqual(1.0, sum(scaling_list))

        self.assertAlmostEqual(0.0, scaling_list[0])
        self.assertAlmostEqual(0.17241379310344829, scaling_list[1])
        self.assertAlmostEqual(0.82758620689655171, scaling_list[2])
    def setUp(self):
        self.fitness_list = FitnessList(MAX)
        self.fitness_list.extend([[1.5, 0], [2.5, 1], [12.0, 2]])

        self.repl = ReplacementDeleteWorst(self.fitness_list, replacement_count=1)
 def setUp(self):
     #   Check truncation
     self.fitness = FitnessList(MAX)
     self.fitness.extend([[1.5, 0], [2.5, 1], [12.0, 2]])
Beispiel #26
0
class GrammaticalEvolution(object):
    """
    This class comprises the overall process of generating genotypes,
    expressing the genes as programs using grammer and evalating the fitness of
    those members.
    """
    def __init__(self):
        """
        Because there are a number of parameters to specify, there are no
        specific variables that are initialized within __init__.

        There is a formidable number of default variables specified in this
        function however.

        """

        #   Parameters for changing generations
        self.stopping_criteria = {
            STOPPING_MAX_GEN: None,
            STOPPING_FITNESS_LANDSCAPE: None
        }
        self._crossover_rate = DEFAULT_CROSSOVER_RATE
        self._children_per_crossover = DEFAULT_CHILDEREN_PER_CROSSOVER
        self._mutation_type = DEFAULT_MUTATION_TYPE
        self._mutation_rate = DEFAULT_MUTATION_RATE
        self._max_fitness_rate = DEFAULT_MAX_FITNESS_RATE

        #   Parameters for phenotype creation
        self._wrap = DEFAULT_WRAP
        self._extend_genotype = DEFAULT_EXTEND_GENOTYPE
        self._start_gene_length = DEFAULT_START_GENE_LENGTH
        self._max_gene_length = DEFAULT_MAX_PROGRAM_LENGTH
        self._max_program_length = DEFAULT_MAX_PROGRAM_LENGTH

        #   Parameters for overall process
        self._generation = 0
        self.fitness_list = FitnessList(CENTER)
        self._fitness_fail = DEFAULT_FITNESS_FAIL
        self._maintain_history = DEFAULT_MAINTAIN_HISTORY
        self._timeouts = DEFAULT_TIMEOUTS

        #   Parameters used during runtime
        self.current_g = None
        self._fitness_selections = []
        self._replacement_selections = []

        self.bnf = {}
        self._population_size = 0
        self.population = []

        self._history = []

    def set_population_size(self, size):
        """
        This function sets the total number of genotypes in the population.
        This program uses a fixed size population.

        """

        size = int(size)
        if isinstance(size, int) and size > 0:
            self._population_size = size
            i = len(self.fitness_list)
            while i < size:
                self.fitness_list.append([0.0, i])
                i += 1
        else:
            raise ValueError("""
                population size, %s, must be a long above 0""" % (size))

    def get_population_size(self):
        """
        This function returns total number of genotypes in the
        population.

        """

        return self._population_size

    def set_genotype_length(self, start_gene_length, max_gene_length=None):
        """
        This function sets the initial size of the binary genotype.  An
        optional max_gene_length can be entered as well.  This permits the
        genotype to grow during the mapping process of the genotype to a
        program.  The lengths are the length of the decimal genotypes, which
        are therefor 8 times longer the binary genotypes created.

        """

        if max_gene_length is None:
            max_gene_length = start_gene_length

        start_gene_length = int(start_gene_length)
        max_gene_length = int(max_gene_length)
        if not isinstance(start_gene_length, int):
            raise ValueError("start_gene_length, %s, must be a long" %
                             (start_gene_length))
        if start_gene_length < 0:
            raise ValueError("start_gene_length, %s, must be above 0" %
                             (start_gene_length))
        if not isinstance(max_gene_length, int):
            raise ValueError("max_gene_length, %s, must be a long" %
                             (max_gene_length))
        if max_gene_length < 0:
            raise ValueError("max_gene_length, %s, must be above 0" %
                             (max_gene_length))
        if start_gene_length > max_gene_length:
            raise ValueError("""max_gene_length, %s, cannot be smaller
                than start_gene_length%s""" %
                             (max_gene_length, start_gene_length))

        self._start_gene_length = start_gene_length
        self._max_gene_length = max_gene_length

    def get_genotype_length(self):
        """
        This function returns a tuple with the length the initial genotype and
        the maximum genotype length permitted.

        """

        return (self._start_gene_length, self._max_gene_length)

    def set_extend_genotype(self, true_false):
        """
        This function sets whether the genotype is extended during the gene
        mapping process.

        """

        if isinstance(true_false, bool):
            self._extend_genotype = true_false
        else:
            raise ValueError("Extend genotype must be True or False")

    def get_extend_genotype(self):
        """
        This function returns whether the genotype is extended during the gene
        mapping process.

        """

        return self._extend_genotype

    def set_wrap(self, true_false):
        """
        This function sets whether the genotype is wrapped during the gene
        mapping process.  Wrapping would occur in the iterative process of
        getting the next codon is the basis for the variable selection process.
        If wrapped, when all the codons in the genotype are exhausted, the
        position marker is wrapped around to the first codon in the sequence
        and goes on.

        """

        if isinstance(true_false, bool):
            self._wrap = true_false
        else:
            raise ValueError("Wrap must be True or False")

    def get_wrap(self):
        """
        This function returns whether the genotype is wrapped during the gene
        mapping process.  Wrapping would occur in the iterative process of
        getting the next codon is the basis for the variable selection process.
        If wrapped, when all the codons in the genotype are exhausted, the
        position marker is wrapped around to the first codon in the sequence
        and goes on.

        """

        return self._wrap

    def set_bnf(self, bnf):
        """
        This function parses up a bnf and builds a dictionary. The incoming
        format is designed to follow a format of:  <key> ::= value1 | value2
        \n. The following lines can also hold additional values to accommodate
        longer choices.

        In addition, a set of statements are marked with a key
        starting with "<S".  These are treated differently in that spaces are
        not automatically stripped from the front.  This enables python
        oriented white space to be honored.

        """
        def strip_spaces(key, values):
            """
            This removes white space unless it is a statement.
            """
            if key.startswith(STATEMENT_FORMAT):
                values = [
                    value.rstrip() for value in values.split('|') if value
                ]
            else:
                values = [
                    value.strip() for value in values.split('|') if value
                ]

            return values

        bnf_dict = {}
        for item in bnf.split('\n'):
            if item.find('::=') >= 0:
                key, values = item.split('::=')
                key = key.strip()
                bnf_dict[key] = strip_spaces(key, values)
            elif item:
                values = bnf_dict[key]
                values.extend(strip_spaces(key, item))
                if key.startswith(STATEMENT_FORMAT):
                    #   Convert statements back to string
                    values = ['\n'.join(values)]
                bnf_dict[key] = values
            else:
                #   blank line
                pass
        self.bnf = bnf_dict

    def get_bnf(self):
        """
        This function returns the Backus Naur form of variables that are used
        to map the genotypes to the generated programs.

        """

        return self.bnf

    def set_maintain_history(self, true_false):
        """
        This function sets a flag to maintain a history of fitness_lists.

        """
        if isinstance(true_false, bool):
            self._maintain_history = true_false
        else:
            raise ValueError("Maintain history must be True or False")

    def get_maintain_history(self):
        """
        This function returns a flag indicating whether the fitness list is
        retained for each generation.

        """

        return self._maintain_history

    def set_max_program_length(self, max_program_length):
        """
        This function sets the maximum length that a program can attain before
        the genotype is declared a failure.

        """

        errmsg1 = """The maximum program length, %s must be an long value
                    """ % (max_program_length)
        errmsg2 = """The maximum program length, %s must be greater than 0
                    """ % (max_program_length)
        max_program_length = int(max_program_length)
        if not isinstance(max_program_length, int):
            raise ValueError(errmsg1)
        if max_program_length < 0:
            raise ValueError(errmsg2)

        self._max_program_length = max_program_length

    def get_max_program_length(self):
        """
        This function gets the maximum length that a program can attain before
        the genotype is declared a failure.

        """

        return self._max_program_length

    def set_fitness_fail(self, fitness_fail):
        """
        This function sets the fitness fail value that will be applied to
        fitness functions that are deemed failure.  Failure would be programs
        that fail due to overflows, or programs that grow to greater than
        maximum program length, syntax failures, or other reasons.

        """

        errmsg = """The fitness_fail, %s must be a float value
                    """ % (fitness_fail)
        #   coerce if possible
        fitness_fail = float(fitness_fail)
        if not isinstance(fitness_fail, float):
            raise ValueError(errmsg)

        self._fitness_fail = fitness_fail

    def get_fitness_fail(self):
        """
        This function returns the value of fitness if the program is a failure.

        """

        return self._fitness_fail

    def set_mutation_type(self, mutation_type):
        """
        This function sets the mutation type.  The choices are s(ingle),
        m(ultiple).  If the choice is "s", then the mutation rate is applied
        as a choice of whether to alter 1 bit on a gene or not.  If the choice
        is "m", then the process applies the rate as the probability that a bit
        will be changed as it walks the gene.  In short, "s", means that if the
        gene is mutated, it will take place once.  Otherwise, the gene could be
        mutated multiple times.

        """

        errmsg = "The mutation type must be either '%s' or '%s'." % (
            MUT_TYPE_S, MUT_TYPE_M)
        if mutation_type not in [MUT_TYPE_M, MUT_TYPE_S]:
            raise ValueError(errmsg)

        self._mutation_type = mutation_type

    def get_mutation_type(self):
        """
        This function returns the mutation type.  See set_mutation_type for a
        more complete explanation.

        """

        return self._mutation_type

    def set_mutation_rate(self, mutation_rate):
        """
        This function sets the mutation rate that will be applied to members
        selected into the fitness pool and to newly generated children.  Note
        that the mutation rate should be vastly different depending upon the
        mutation type that you have selected.  If the mutation type is 's',
        then the rate is the probability that the genotype will be mutated.  If
        the mutation type is 'm', then the rate is the probability that the any
        given bit in the genotype will be altered.  Because of that, the
        mutation rate should be significantly lower than the rate used with a
        mutation type of 's'.

        """

        errmsg = """The mutation rate, %s must be a float value
                    from 0.0 to 1.0""" % (mutation_rate)
        if not isinstance(mutation_rate, float):
            raise ValueError(errmsg)
        if not (0.0 <= mutation_rate <= 1.0):
            raise ValueError(errmsg)

        self._mutation_rate = mutation_rate

    def get_mutation_rate(self):
        """
        This function gets the mutation rate that will be applied to members
        selected into the fitness pool and to newly generated children.  Note
        that the mutation rate should be vastly different depending upon the
        mutation type that you have selected.  If the mutation type is 's',
        then the rate is the probability that the genotype will be mutated.  If
        the mutation type is 'm', then the rate is the probability that the any
        given bit in the genotype will be altered.  Because of that, the
        mutation rate should be significantly lower than the rate used with a
        mutation type of 's'.

        """

        return self._mutation_rate

    def set_crossover_rate(self, crossover_rate):
        """
        This function sets the probablity that will be
        applied to members selected into the fitness pool.

        """

        errmsg = """The crossover rate, %s must be a float value
                    from 0.0 to 1.0""" % (crossover_rate)
        if not isinstance(crossover_rate, float):
            raise ValueError(errmsg)
        if not (0.0 <= crossover_rate <= 1.0):
            raise ValueError(errmsg)

        self._crossover_rate = crossover_rate

    def get_crossover_rate(self):
        """
        This function gets the probablity that will be applied to members
        selected into the fitness pool.

        """

        return self._crossover_rate

    def set_children_per_crossover(self, children_per_crossover):
        """
        This function sets the number of children that will generated from two
        parents.  The choice is one or two.

        """

        if children_per_crossover not in [1, 2]:
            raise ValueError(
                "The children per crossovermust be either 1 or 2.")
        self._children_per_crossover = children_per_crossover

    def get_children_per_crossover(self):
        """
        This function gets the number of children that will generated from two
        parents.

        """

        return self._children_per_crossover

    def set_max_generations(self, generations):
        """
        This function sets the maximum number of generations that will be run.

        """

        if isinstance(generations, int) and generations >= 0:
            self.stopping_criteria[STOPPING_MAX_GEN] = generations
        else:
            raise ValueError("""
                generations, %s, must be an int 0 or greater""" %
                             (generations))

    def get_max_generations(self):
        """
        This function gets the maximum number of generations that will be run.

        """

        return self.stopping_criteria[STOPPING_MAX_GEN]

    def set_fitness_type(self, fitness_type, target_value=0.0):
        """
        This function sets whether the objective is to achieve as large a
        fitness value possible, small, or hit a target_value.  Therefor the
        choices are 'max', 'min', or 'center'.  If center is used, then a
        target value should be entered as well.  For example, suppose that you
        wanted to hit a target somewhere near zero.  Setting the target_value
        at .001 would cause the process to complete if a fitness value achieved
        and absolute value of .001 or less.

        """

        self.fitness_list.set_fitness_type(fitness_type)
        self.fitness_list.set_target_value(target_value)

    def get_fitness_type(self):
        """
        This function gets whether the objective is to achieve as large a
        fitness value possible, small, or hit a target_value.  Therefor the
        choices are 'max', 'min', or 'center'.  If center is used, then a
        target value should be entered as well.  For example, suppose that you
        wanted to hit a target somewhere near zero.  Setting the target_value
        at .001 would cause the process to complete if a fitness value achieved
        .001 or less.

        """

        return self.fitness_list.get_fitness_type()

    def set_max_fitness_rate(self, max_fitness_rate):
        """
        This function sets a maximum for the number of genotypes that can be
        put in the fitness pool.  Since some fitness selection approaches can
        have a varying number selected, and since multiple selection approaches
        can be applied consequentially, there needs to be an ultimate limit on
        the total number.  The max fitness rate must be a value greater than
        zero and less than 1.0.

        """

        errmsg = """The max fitness rate, %s must be a float value
                    from 0.0 to 1.0""" % (max_fitness_rate)
        if not isinstance(max_fitness_rate, float):
            raise ValueError(errmsg)
        if not (0.0 <= max_fitness_rate <= 1.0):
            raise ValueError(errmsg)

        self._max_fitness_rate = max_fitness_rate

    def get_max_fitness_rate(self):
        """
        This function gets a maximum for the number of genotypes that can be
        in the fitness pool.  Since some fitness selection approaches can have
        a varying number selected, and since multiple selection approaches can
        be applied consequentially, there needs to be an ultimate limit on the
        total number.  The max fitness rate must be a value greater than zero
        and less than 1.0.

        """

        return self._max_fitness_rate

    def set_fitness_selections(self, *params):
        """
        This function loads the fitness selections that are to be used to
        determine genotypes worthy of continuing to the next generation.  There
        can be multiple selections, such as elites and tournaments.  See the
        section Fitness Selection for further information.

        """

        for fitness_selection in params:
            if isinstance(fitness_selection, Fitness):
                self._fitness_selections.append(fitness_selection)
            else:
                raise ValueError("Invalid fitness selection")

    def set_replacement_selections(self, *params):
        """
        This function loads the replacement selections that are used to
        determine genotypes are to be replaced.  Basically, it is the grim
        reaper. Multiple replacement types can be loaded to meet the criteria.
        The number replaced is governed by the fitness selection functions to
        ensure that the population number stays constant.

        """

        for replacement_selection in params:
            if isinstance(replacement_selection, Replacement):
                self._replacement_selections.append(replacement_selection)
            else:
                raise ValueError("Invalid replacement selection")

    def get_fitness_history(self, statistic='best_value'):
        """
        This funcion returns a list of values that represent historical values
        from the fitness history.  While there is a default value of
        'best_value', other values are 'mean', 'min_value', 'max_value',
        'worst_value', 'min_member', 'max_member', 'best_member', and
        'worst_member'. The order is from oldest to newest.

        """

        hist_list = []
        for fitness_list in self._history:
            hist_list.append(fitness_list.__getattribute__(statistic)())
        return hist_list

    def get_best_member(self):
        """
        This function returns the member that it is most fit according to the
        fitness list.  Accordingly, it is only functional after at least one
        generation has been completed.

        """

        return self.population[self.fitness_list.best_member()]

    def get_worst_member(self):
        """
        This function returns the member that it is least fit according to the
        fitness list.  Accordingly, it is only functional after at least one
        generation has been completed.

        """

        return self.population[self.fitness_list.worst_member()]

    def set_timeouts(self, preprogram, program):
        """
        This function sets the number of seconds that the program waits until
        declaring that the process is a runaway and cuts it off.  During the
        mapping process against the preprogram, due to random chance a function
        can be calling another function, which calls another, until the process
        becomes so convoluted that the resulting program will be completely
        useless. While the total length of a program can be guide to its
        uselessnes as well, this is another way to handle it. Since variables
        can also be generated during the running of the program there is a
        second variable for the running program. Clearly, the second value must
        be in harmony with the nature of the program that you are actually
        running. Otherwise, you will be cutting of your program prematurely.
        Note that the second timeout is checked only if the running program
        requests an additional variable.  Otherwise, it will not be triggered.

        """

        if isinstance(preprogram, int) and preprogram >= 0:
            self._timeouts[0] = preprogram
        else:
            raise ValueError("""
                timeout, %s, must be an int 0 or above""" % (preprogram))

        if isinstance(program, int) and program >= 0:
            self._timeouts[1] = program
        else:
            raise ValueError("""
                timeout, %s, must be an int 0 or above""" % (program))

    def get_timeouts(self):
        """
        This function returns the number of seconds that must elapse before
        the mapping process cuts off the process and declares that the genotype
        is a failure.  It returns a tuple for the number of seconds for the
        preprogram and the program itself.

        """

        return self._timeouts

    def _compute_fitness(self):
        """
        This function runs the process of computing fitness functions for each
        genotype and calculating the fitness function.

        """

        for gene in self.population:
            starttime = datetime.now()
            gene._generation = self._generation
            logging.debug("Starting member G %s: %s at %s" %
                          (self._generation, gene.member_no,
                           starttime.strftime('%m/%d/%y %H:%M')))

            #print "Starting member G %s: %s at %s" % (
            #self._generation, gene.member_no,
            #starttime.strftime('%m/%d/%y %H:%M'))
            gene.starttime = starttime
            self.current_g = gene
            gene.compute_fitness()

            logging.debug("fitness=%s" % (gene.get_fitness()))
            self.fitness_list[gene.member_no][0] = gene.get_fitness()

    def run(self, starting_generation=0):
        """
        Once the parameters have all been set governing the course of the
        evolutionary processing, this function starts the process running.  It
        will continue until it the completion criteria have been set.

        """

        logging.info("started run")
        self._generation = starting_generation
        while True:
            self._compute_fitness()
            if self._maintain_history:
                self._history.append(deepcopy(self.fitness_list))

            if self._continue_processing():
                self._perform_endcycle()

                logging.info("Finished generation: %s Max generations: %s" %
                             (self._generation, self.get_max_generations()))
                logging.info(' '.join([
                    "best_value: %s" % (self.fitness_list.best_value()),
                    "median: %s" % (self.fitness_list.median()),
                    "mean: %s" % (self.fitness_list.mean())
                ]))
                #temp -- remove this
                gene = self.population[self.fitness_list.best_member()]
                program = gene.get_program()
                logging.info(program)

                #logging.debug("stddev= %s" % self.fitness_list.stddev())
                self._generation += 1
            else:
                break

        logging.info(
            "completed run: generations: %s, best member:%s fitness: %s" %
            (self._generation, self.fitness_list.best_member(),
             self.fitness_list.best_value()))

        return self.fitness_list.best_member()

    def create_genotypes(self):
        """
        This function creates a genotype using the input parameters for each
        member of the population, and transfers operating parameters to the
        genotype for running the fitness functions.

        """

        member_no = 0
        while member_no < self._population_size:
            gene = Genotype(self._start_gene_length, self._max_gene_length,
                            member_no)
            #   a local copy is made because variables
            #   can be saved within the local_bnf
            gene.local_bnf = deepcopy(self.bnf)
            gene.local_bnf['<member_no>'] = [gene.member_no]
            gene._max_program_length = self._max_program_length
            gene._fitness = self._fitness_fail
            gene._fitness_fail = self._fitness_fail
            gene._extend_genotype = self._extend_genotype
            gene._timeouts = self._timeouts
            gene._wrap = self._wrap
            self.population.append(gene)
            member_no += 1

    def _perform_endcycle(self):
        """
        This function runs after each member of the population has computed
        their fitness function.  Then, the fitness selection objects will
        evaluate those members according to their respective criteria and
        develop a pool of members that will potentially survive to the next
        generation. Crossovers will take place from that pool and each member
        will be subject to the possibility of mutatuting.  Finally, a
        replacement process will find which members should be replaced. The
        fitness pool will then replace those members.

        """

        fitness_pool = self._evaluate_fitness()
        child_list = self._perform_crossovers(fitness_pool)

        fitness_pool.extend(child_list)
        self._perform_mutations(fitness_pool)
        self._perform_replacements(fitness_pool)

    def _evaluate_fitness(self):
        """
        This function evaluates the fitness of the members in the light of the
        fitness criteria functions.  It returns a list of members that will be
        used for crossovers and mutations.

        """

        flist = []
        total = int(
            round(self._max_fitness_rate * float(self._population_size)))
        count = 0
        for fsel in self._fitness_selections:
            fsel.set_fitness_list(self.fitness_list)
            for i in fsel.select():
                flist.append(i)
                count += 1
                if count == total:
                    #   Done
                    break

        flist1 = []
        for member_no in flist:
            flist1.append(deepcopy(self.population[member_no]))

        return flist1

    def _perform_crossovers(self, flist):
        """
        This function accepts a list of genotypes that are to be crossed.  The
        list is processed two at a time, and a child list holding the offspring
        is returned.  The _children_per_crossover indicator governs whether two
        children are produced or one.

        """

        child_list = []
        length = len(flist)
        if length % 2 == 1:
            length -= 1
        if length >= 2:
            for i in range(0, length, 2):
                parent1 = flist[i]
                parent2 = flist[i + 1]

                child1, child2 = self._crossover(parent1, parent2)
                if self._children_per_crossover == 2:
                    child_list.append(child1)
                    child_list.append(child2)
                else:
                    child_list.append(child1)

        return child_list

    def _crossover(self, parent1, parent2):
        """
        This function accepts two parents, randomly selects which is parent1
        and which is parent2.  Then, executes the crossover, and returns two
        children.

        """

        if not isinstance(parent1, Genotype):
            raise ValueError("Parent1 is not a genotype")
        if not isinstance(parent2, Genotype):
            raise ValueError("Parent2 is not a genotype")

        if randint(0, 1):
            child1 = deepcopy(parent1)
            child2 = deepcopy(parent2)
        else:
            child1 = deepcopy(parent2)
            child2 = deepcopy(parent1)

        child1_binary = child1.binary_gene
        child2_binary = child2.binary_gene

        minlength = min(len(child1_binary), len(child2_binary))
        crosspoint = randint(2, minlength - 2)

        child1_binary, child2_binary = self._crossover_function(
            child1.binary_gene, child2.binary_gene, crosspoint)

        child1.set_binary_gene(child1_binary)
        child1.generate_decimal_gene()
        child2.set_binary_gene(child2_binary)
        child2.generate_decimal_gene()

        return (child1, child2)

    @staticmethod
    def _crossover_function(child1_binary, child2_binary, crosspoint):
        """
        This function performs the actual crossover of material at a random
        point.

        I gratefully acknowlege Franco from Argentina ([email protected]) for
        the fix to my previous version of this code.

        """

        child1_binary, child2_binary = child1_binary[0:crosspoint] + \
                                    child2_binary[crosspoint:], \
                                    child2_binary[0:crosspoint] + \
                                    child1_binary[crosspoint:]

        return (child1_binary, child2_binary)

    def _perform_mutations(self, mlist):
        """
        This functions accepts a list of genotypes that are subject to
        mutation.  Each genotype is then put at risk for mutation and may or
        may not be mutated.

        """

        for gene in mlist:
            gene.mutate(self._mutation_rate, self._mutation_type)

    def _perform_replacements(self, fitness_pool):
        """
        This function accepts a list of members that will replace lesser
        performers.  The replacement process then applies the fitness pool to
        the population.

        """

        position = 0
        for rsel in self._replacement_selections:
            rsel.set_fitness_list(self.fitness_list)

            for replaced_no in rsel.select():
                replaced_g = self.population[replaced_no]
                if position < len(fitness_pool):
                    new_g = fitness_pool[position]
                    new_g.member_no = replaced_g.member_no
                    new_g._generation = self._generation + 1

                    #   update local bnf
                    new_g.local_bnf['<member_no>'] = [new_g.member_no]

                    self.population[new_g.member_no] = new_g
                    position += 1
                else:
                    break

    def _continue_processing(self):
        """
        This function, using the criteria for ending the evolutionary process
        after each generation, returns a flag of whether to continue or not.

        """

        status = True
        fitl = self.fitness_list

        #   check max generations first
        if self.stopping_criteria[STOPPING_MAX_GEN] is not None:
            if self.stopping_criteria[STOPPING_MAX_GEN] <= self._generation:
                logging.info("stopping processing due to max generation")
                return False

        #   check target value
        if fitl.get_target_value() is not None:
            if fitl.get_fitness_type() == MAX:
                if fitl.best_value() >= fitl.get_target_value():
                    logging.info(' '.join([
                        "stopping processing due to",
                        "best value, %s, better than target value, %s" %
                        (fitl.best_value(), fitl.get_target_value())
                    ]))
                    return False
            elif fitl.get_fitness_type() == MIN:
                if fitl.best_value() <= fitl.get_target_value():
                    logging.info(' '.join([
                        "stopping processing due to",
                        "best value, %s, better than target value, %s" %
                        (fitl.best_value(), fitl.get_target_value())
                    ]))
                    return False
            elif fitl.get_fitness_type() == CENTER:
                if fitl.best_value() <= fitl.get_target_value():
                    logging.info(' '.join([
                        "stopping processing due to",
                        "best value, %s, better than target value, %s" %
                        (fitl.best_value(), fitl.get_target_value())
                    ]))
                    return False

        #   Finally check if there is a stopping function
        if self.stopping_criteria[STOPPING_FITNESS_LANDSCAPE] is not None:
            status = self.stopping_criteria[STOPPING_FITNESS_LANDSCAPE](
                self.fitness_list)
            logging.info(' '.join([
                "stopping processing due to",
                "fitness landscape being reached."
            ]))

        return status
 def setUp(self):
     self.fitness_list = FitnessList(MAX)
     self.fitness_list.extend([[1.5, 0], [2.5, 1], [12.0, 2]])