From 07b000a81001ab6af707f0450fccefffe6137fce Mon Sep 17 00:00:00 2001 From: BubaVV Date: Thu, 25 Aug 2016 17:40:02 +0300 Subject: [PATCH] Add files via upload Update for Python 3 compatibility --- Consts.py | 537 +++++++++++++++ Crossovers.py | 810 +++++++++++++++++++++++ DBAdapters.py | 792 ++++++++++++++++++++++ FunctionSlot.py | 202 ++++++ G1DBinaryString.py | 173 +++++ G1DList.py | 167 +++++ G2DBinaryString.py | 197 ++++++ G2DList.py | 229 +++++++ GAllele.py | 286 ++++++++ GPopulation.py | 505 ++++++++++++++ GSimpleGA.py | 893 +++++++++++++++++++++++++ GTree.py | 730 ++++++++++++++++++++ GenomeBase.py | 609 +++++++++++++++++ Initializators.py | 275 ++++++++ Interaction.py | 87 +++ Migration.py | 342 ++++++++++ Mutators.py | 1164 ++++++++++++++++++++++++++++++++ Network.py | 445 +++++++++++++ Scaling.py | 133 ++++ Selectors.py | 183 +++++ Statistics.py | 106 +++ Util.py | 351 ++++++++++ __init__.py | 45 ++ pyevolve_ex10_g1dbinstr.py | 40 ++ pyevolve_ex11_allele.py | 72 ++ pyevolve_ex12_tsp.py | 132 ++++ pyevolve_ex13_sphere.py | 30 + pyevolve_ex14_ackley.py | 54 ++ pyevolve_ex15_rosenbrock.py | 52 ++ pyevolve_ex16_g2dbinstr.py | 37 ++ pyevolve_ex17_gtree.py | 44 ++ pyevolve_ex18_gp.py | 47 ++ pyevolve_ex19_gp.py | 94 +++ pyevolve_ex1_simple.py | 54 ++ pyevolve_ex20_gp_dotwrite.py | 56 ++ pyevolve_ex21_nqueens.py | 58 ++ pyevolve_ex22_monkey.py | 54 ++ pyevolve_ex2_realgauss.py | 42 ++ pyevolve_ex3_schaffer.py | 46 ++ pyevolve_ex4_sigmatrunc.py | 49 ++ pyevolve_ex5_callback.py | 45 ++ pyevolve_ex6_dbadapter.py | 47 ++ pyevolve_ex7_rastrigin.py | 40 ++ pyevolve_ex8_gauss_int.py | 44 ++ pyevolve_ex9_g2dlist.py | 43 ++ pyevolve_graph.py | 1214 +++++++++++++++++----------------- runtests.py | 10 +- setup.py | 7 +- test_crossovers.py | 394 +++++++++++ test_initializators.py | 42 ++ test_mutators.py | 179 +++++ test_simple_ga.py | 71 ++ test_util.py | 19 + 53 files changed, 11763 insertions(+), 614 deletions(-) create mode 100644 Consts.py create mode 100644 Crossovers.py create mode 100644 DBAdapters.py create mode 100644 FunctionSlot.py create mode 100644 G1DBinaryString.py create mode 100644 G1DList.py create mode 100644 G2DBinaryString.py create mode 100644 G2DList.py create mode 100644 GAllele.py create mode 100644 GPopulation.py create mode 100644 GSimpleGA.py create mode 100644 GTree.py create mode 100644 GenomeBase.py create mode 100644 Initializators.py create mode 100644 Interaction.py create mode 100644 Migration.py create mode 100644 Mutators.py create mode 100644 Network.py create mode 100644 Scaling.py create mode 100644 Selectors.py create mode 100644 Statistics.py create mode 100644 Util.py create mode 100644 __init__.py create mode 100644 pyevolve_ex10_g1dbinstr.py create mode 100644 pyevolve_ex11_allele.py create mode 100644 pyevolve_ex12_tsp.py create mode 100644 pyevolve_ex13_sphere.py create mode 100644 pyevolve_ex14_ackley.py create mode 100644 pyevolve_ex15_rosenbrock.py create mode 100644 pyevolve_ex16_g2dbinstr.py create mode 100644 pyevolve_ex17_gtree.py create mode 100644 pyevolve_ex18_gp.py create mode 100644 pyevolve_ex19_gp.py create mode 100644 pyevolve_ex1_simple.py create mode 100644 pyevolve_ex20_gp_dotwrite.py create mode 100644 pyevolve_ex21_nqueens.py create mode 100644 pyevolve_ex22_monkey.py create mode 100644 pyevolve_ex2_realgauss.py create mode 100644 pyevolve_ex3_schaffer.py create mode 100644 pyevolve_ex4_sigmatrunc.py create mode 100644 pyevolve_ex5_callback.py create mode 100644 pyevolve_ex6_dbadapter.py create mode 100644 pyevolve_ex7_rastrigin.py create mode 100644 pyevolve_ex8_gauss_int.py create mode 100644 pyevolve_ex9_g2dlist.py create mode 100644 test_crossovers.py create mode 100644 test_initializators.py create mode 100644 test_mutators.py create mode 100644 test_simple_ga.py create mode 100644 test_util.py diff --git a/Consts.py b/Consts.py new file mode 100644 index 0000000..254531a --- /dev/null +++ b/Consts.py @@ -0,0 +1,537 @@ +""" + +:mod:`Consts` -- constants module +============================================================================ + +Pyevolve have defaults in all genetic operators, settings and etc, this is an issue to helps the user in the API use and minimize the source code needed to make simple things. In the module :mod:`Consts`, you will find those defaults settings. You are encouraged to see the constants, but not to change directly on the module, there are methods for this. + +General constants +---------------------------------------------------------------------------- + +.. attribute:: CDefPythonRequire + + The mininum version required to run Pyevolve. + +.. attribute:: CDefLogFile + + The default log filename. + +.. attribute:: CDefLogLevel + + Default log level. + +.. attribute:: sortType + + Sort type, raw or scaled. + + Example: + >>> sort_type = Consts.sortType["raw"] + >>> sort_type = Consts.sortType["scaled"] + +.. attribute:: minimaxType + + The Min/Max type, maximize or minimize the evaluation function. + + Example: + >>> minmax = Consts.minimaxType["minimize"] + >>> minmax = Consts.minimaxType["maximize"] + +.. attribute:: CDefESCKey + + The ESC key ASCII code. Used to start Interactive Mode. + +.. attribute:: CDefRangeMin + + Minimum range. This constant is used as integer and real max/min. + +.. attribute:: CDefRangeMax + + Maximum range. This constant is used as integer and real max/min. + +.. attribute:: CDefBroadcastAddress + + The broadcast address for UDP, 255.255.255.255 + +.. attribute:: CDefImportList + + The import list and messages + +.. attribute:: nodeType + + The genetic programming node types, can be "TERMINAL":0 or "NONTERMINAL":1 + +.. attribute:: CDefGPGenomes + + The classes which are used in Genetic Programming, used to detected the + correct mode when starting the evolution + +Selection methods constants (:mod:`Selectors`) +---------------------------------------------------------------------------- + +.. attribute:: CDefTournamentPoolSize + + The default pool size for the Tournament Selector (:func:`Selectors.GTournamentSelector`). + +Scaling scheme constants (:mod:`Scaling`) +---------------------------------------------------------------------------- + +.. attribute:: CDefScaleLinearMultiplier + + The multiplier of the Linear (:func:`Scaling.LinearScaling`) scaling scheme. + +.. attribute:: CDefScaleSigmaTruncMultiplier + + The default Sigma Truncation (:func:`Scaling.SigmaTruncScaling`) scaling scheme. + +.. attribute:: CDefScalePowerLawFactor + + The default Power Law (:func:`Scaling.PowerLawScaling`) scaling scheme factor. + +.. attribute:: CDefScaleBoltzMinTemp + + The default mininum temperature of the (:func:`Scaling.BoltzmannScaling`) scaling scheme factor. + +.. attribute:: CDefScaleBoltzFactor + + The default Boltzmann Factor of (:func:`Scaling.BoltzmannScaling`) scaling scheme factor. + This is the factor that the temperature will be subtracted. + +.. attribute:: CDefScaleBoltzStart + + The default Boltzmann start temperature (:func:`Scaling.BoltzmannScaling`). + If you don't set the start temperature parameter, this will be the default initial + temperature for the Boltzmann scaling scheme. + +Population constants (:class:`GPopulation.GPopulation`) +---------------------------------------------------------------------------- + +.. attribute:: CDefPopSortType + + Default sort type parameter. + +.. attribute:: CDefPopMinimax + + Default min/max parameter. + +.. attribute:: CDefPopScale + + Default scaling scheme. + + +1D Binary String Defaults (:class:`G1DBinaryString.G1DBinaryString`) +---------------------------------------------------------------------------- + +.. attribute:: CDefG1DBinaryStringMutator + + The default mutator for the 1D Binary String (:class:`G1DBinaryString.G1DBinaryString`) chromosome. + +.. attribute:: CDefG1DBinaryStringCrossover + + The default crossover method for the 1D Binary String (:class:`G1DBinaryString.G1DBinaryString`) chromosome. + +.. attribute:: CDefG1DBinaryStringInit + + The default initializator for the 1D Binary String (:class:`G1DBinaryString.G1DBinaryString`) chromosome. + +.. attribute:: CDefG1DBinaryStringUniformProb + + The default uniform probability used for some uniform genetic operators for the 1D Binary String (:class:`G1DBinaryString.G1DBinaryString`) chromosome. + + +2D Binary String Defaults (:class:`G2DBinaryString.G2DBinaryString`) +---------------------------------------------------------------------------- + +.. attribute:: CDefG2DBinaryStringMutator + + The default mutator for the 2D Binary String (:class:`G2DBinaryString.G2DBinaryString`) chromosome. + +.. attribute:: CDefG2DBinaryStringCrossover + + The default crossover method for the 2D Binary String (:class:`G2DBinaryString.G2DBinaryString`) chromosome. + +.. attribute:: CDefG2DBinaryStringInit + + The default initializator for the 2D Binary String (:class:`G2DBinaryString.G2DBinaryString`) chromosome. + +.. attribute:: CDefG2DBinaryStringUniformProb + + The default uniform probability used for some uniform genetic operators for the 2D Binary String (:class:`G2DBinaryString.G2DBinaryString`) chromosome. + + +1D List chromosome constants (:class:`G1DList.G1DList`) +---------------------------------------------------------------------------- + +.. attribute:: CDefG1DListMutIntMU + + Default *mu* value of the 1D List Gaussian Integer Mutator (:func:`Mutators.G1DListMutatorIntegerGaussian`), the *mu* represents the mean of the distribution. + +.. attribute:: CDefG1DListMutIntSIGMA + + Default *sigma* value of the 1D List Gaussian Integer Mutator (:func:`Mutators.G1DListMutatorIntegerGaussian`), the *sigma* represents the standard deviation of the distribution. + +.. attribute:: CDefG1DListMutRealMU + + Default *mu* value of the 1D List Gaussian Real Mutator (:func:`Mutators.G1DListMutatorRealGaussian`), the *mu* represents the mean of the distribution. + +.. attribute:: CDefG1DListMutRealSIGMA + + Default *sigma* value of the 1D List Gaussian Real Mutator (:func:`Mutators.G1DListMutatorRealGaussian`), the *sigma* represents the mean of the distribution. + + +Tree chromosome constants (:class:`GTree.GTree`) +---------------------------------------------------------------------------- + +.. attribute:: CDefGTreeInit + + Default initializator of the tree chromosome. + +.. attribute:: CDefGGTreeMutator + + Default mutator of the tree chromosome. + +.. attribute:: CDefGTreeCrossover + + Default crossover of the tree chromosome. + + +2D List chromosome constants (:class:`G2DList.G2DList`) +---------------------------------------------------------------------------- + +.. attribute:: CDefG2DListMutRealMU + + Default *mu* value of the 2D List Gaussian Real Mutator (:func:`Mutators.G2DListMutatorRealGaussian`), the *mu* represents the mean of the distribution. + +.. attribute:: CDefG2DListMutRealSIGMA + + Default *sigma* value of the 2D List Gaussian Real Mutator (:func:`Mutators.G2DListMutatorRealGaussian`), the *sigma* represents the mean of the distribution. + +.. attribute:: CDefG2DListMutIntMU + + Default *mu* value of the 2D List Gaussian Integer Mutator (:func:`Mutators.G2DListMutatorIntegerGaussian`), the *mu* represents the mean of the distribution. + +.. attribute:: CDefG2DListMutIntSIGMA + + Default *sigma* value of the 2D List Gaussian Integer Mutator (:func:`Mutators.G2DListMutatorIntegerGaussian`), the *sigma* represents the mean of the distribution. + +.. attribute:: CDefG2DListMutator + + Default mutator for the 2D List chromosome. + +.. attribute:: CDefG2DListCrossover + + Default crossover method for the 2D List chromosome. + +.. attribute:: CDefG2DListInit + + Default initializator for the 2D List chromosome. + +.. attribute:: CDefG2DListCrossUniformProb + + Default uniform probability for the 2D List Uniform Crossover method (:func:`Crossovers.G2DListCrossoverUniform`). + + +GA Engine constants (:class:`GSimpleGA.GSimpleGA`) +---------------------------------------------------------------------------- + +.. attribute:: CDefGAGenerations + + Default number of generations. + +.. attribute:: CDefGAMutationRate + + Default mutation rate. + +.. attribute:: CDefGACrossoverRate + + Default crossover rate. + +.. attribute:: CDefGAPopulationSize + + Default population size. + +.. attribute:: CDefGASelector + + Default selector method. + +DB Adapters constants (:mod:`DBAdapters`) +---------------------------------------------------------------------------- +Constants for the DB Adapters + + +SQLite3 DB Adapter Constants (:class:`DBAdapters.DBSQLite`) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. attribute:: CDefSQLiteDBName + + Default database filename. + +.. attribute:: CDefSQLiteDBTable + + Default statistical table name. + +.. attribute:: CDefSQLiteDBTablePop + + Default population statistical table name. + +.. attribute:: CDefSQLiteStatsGenFreq + + Default generational frequency for dump statistics. + +.. attribute:: CDefSQLiteStatsCommitFreq + + Default commit frequency. + + +MySQL DB Adapter Constants (:class:`DBAdapters.DBMySQLAdapter`) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. attribute:: CDefMySQLDBName + + Default database name. + +.. attribute:: CDefMySQLDBTable + + Default statistical table name. + +.. attribute:: CDefMySQLDBTablePop + + Default population statistical table name. + +.. attribute:: CDefMySQLStatsGenFreq + + Default generational frequency for dump statistics. + +.. attribute:: CDefMySQLStatsCommitFreq + + Default commit frequency. + +.. attribute:: CDefMySQLDBHost + + Default MySQL connection host. + +.. attribute:: CDefMySQLDBPort + + Default MySQL connection TCP port. + + +URL Post DB Adapter Constants (:class:`DBAdapters.DBURLPost`) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. attribute:: CDefURLPostStatsGenFreq + + Default generational frequency for dump statistics. + + +CSV File DB Adapter Constants (:class:`DBAdapters.DBFileCSV`) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. attribute:: CDefCSVFileName + + The default CSV filename to dump statistics. + +.. attribute:: CDefCSVFileStatsGenFreq + + Default generational frequency for dump statistics. + + +XMP RPC DB Adapter Constants (:class:`DBAdapters.DBXMLRPC`) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. attribute:: CDefXMLRPCStatsGenFreq + + Default generational frequency for dump statistics. + +Migration Constants (:mod:`Migration`) +---------------------------------------------------------------------------- +.. attribute:: CDefGenMigrationRate + + The default generations supposed to migrate and receive individuals + +.. attribute:: CDefMigrationNIndividuals + + The default number of individuals that will migrate at the *CDefGenMigrationRate* + interval + +.. attribute:: CDefNetworkIndividual + + A migration code for network individual data + +.. attribute:: CDefNetworkInfo + + A migration code for network info data + +.. attribute:: CDefGenMigrationReplacement + + The default number of individuals to be replaced at the migration stage + + +""" +import logging + +from . import Scaling +from . import Selectors +from . import Initializators +from . import Mutators +from . import Crossovers +from .GTree import GTreeGP + +# Required python version 2.5+ +CDefPythonRequire = (2, 5) + +# Logging system +CDefLogFile = "pyevolve.log" +CDefLogLevel = logging.DEBUG + +# Types of sort +# - raw: uses the "score" attribute +# - scaled: uses the "fitness" attribute +sortType = { + "raw": 0, + "scaled": 1 +} + +# Optimization type +# - Minimize or Maximize the Evaluator Function +minimaxType = {"minimize": 0, + "maximize": 1 + } + +CDefESCKey = 27 + +CDefImportList = {"visual.graph": "you must install VPython !", + "csv": "csv module not found !", + "urllib": "urllib module not found !", + "sqlite3": "sqlite3 module not found, are you using Jython or IronPython ?", + "xmlrpclib": "xmlrpclib module not found !", + "MySQLdb": "MySQLdb module not found, you must install mysql-python !", + "pydot": "Pydot module not found, you must install Pydot to plot graphs !"} + +#################### +# Defaults section # +#################### + +# - Tournament selector +CDefTournamentPoolSize = 2 + +# - Scale methods defaults +CDefScaleLinearMultiplier = 1.2 +CDefScaleSigmaTruncMultiplier = 2.0 +CDefScalePowerLawFactor = 1.0005 +CDefScaleBoltzMinTemp = 1.0 +CDefScaleBoltzFactor = 0.05 +# 40 temp. = 500 generations +CDefScaleBoltzStart = 40.0 + +# - Population Defaults +CDefPopSortType = sortType["scaled"] +CDefPopMinimax = minimaxType["maximize"] +CDefPopScale = Scaling.LinearScaling + +# - GA Engine defaults +CDefGAGenerations = 100 +CDefGAMutationRate = 0.02 +CDefGACrossoverRate = 0.9 +CDefGAPopulationSize = 80 +CDefGASelector = Selectors.GRankSelector +CDefGAElitismReplacement = 1 + +# - This is general used by integer/real ranges defaults +CDefRangeMin = 0 +CDefRangeMax = 100 + +# - G1DBinaryString defaults +CDefG1DBinaryStringMutator = Mutators.G1DBinaryStringMutatorFlip +CDefG1DBinaryStringCrossover = Crossovers.G1DBinaryStringXSinglePoint +CDefG1DBinaryStringInit = Initializators.G1DBinaryStringInitializator +CDefG1DBinaryStringUniformProb = 0.5 + +# - G2DBinaryString defaults +CDefG2DBinaryStringMutator = Mutators.G2DBinaryStringMutatorFlip +CDefG2DBinaryStringCrossover = Crossovers.G2DBinaryStringXUniform +CDefG2DBinaryStringInit = Initializators.G2DBinaryStringInitializator +CDefG2DBinaryStringUniformProb = 0.5 + +# - GTree defaults +CDefGTreeInit = Initializators.GTreeInitializatorInteger +CDefGGTreeMutator = Mutators.GTreeMutatorIntegerRange +CDefGTreeCrossover = Crossovers.GTreeCrossoverSinglePointStrict + +# - GTreeGP defaults +CDefGTreeGPInit = Initializators.GTreeGPInitializator +CDefGGTreeGPMutator = Mutators.GTreeGPMutatorSubtree +CDefGTreeGPCrossover = Crossovers.GTreeGPCrossoverSinglePoint + +# - G1DList defaults +CDefG1DListMutIntMU = 2 +CDefG1DListMutIntSIGMA = 10 + +CDefG1DListMutRealMU = 0 +CDefG1DListMutRealSIGMA = 1 + +CDefG1DListMutator = Mutators.G1DListMutatorSwap +CDefG1DListCrossover = Crossovers.G1DListCrossoverSinglePoint +CDefG1DListInit = Initializators.G1DListInitializatorInteger +CDefG1DListCrossUniformProb = 0.5 + +# SBX Crossover defaults +# Crossover distribution index for SBX +# Larger Etac = similar to parents +# Smaller Etac = far away from parents +CDefG1DListSBXEtac = 10 +CDefG1DListSBXEPS = 1.0e-14 + +# - G2DList defaults +CDefG2DListMutIntMU = 2 +CDefG2DListMutIntSIGMA = 10 + +CDefG2DListMutRealMU = 0 +CDefG2DListMutRealSIGMA = 1 + +CDefG2DListMutator = Mutators.G2DListMutatorSwap +CDefG2DListCrossover = Crossovers.G2DListCrossoverUniform +CDefG2DListInit = Initializators.G2DListInitializatorInteger +CDefG2DListCrossUniformProb = 0.5 + +# Gaussian Gradient +CDefGaussianGradientMU = 1.0 +CDefGaussianGradientSIGMA = (1.0 / 3.0) # approx. +/- 3-sigma is +/- 10% + +# - DB Adapters SQLite defaults +CDefSQLiteDBName = "pyevolve.db" +CDefSQLiteDBTable = "statistics" +CDefSQLiteDBTablePop = "population" +CDefSQLiteStatsGenFreq = 1 +CDefSQLiteStatsCommitFreq = 300 + +# - DB Adapters MySQL defaults +CDefMySQLDBName = "pyevolve" +CDefMySQLDBTable = "statistics" +CDefMySQLDBTablePop = "population" +CDefMySQLDBHost = "localhost" +CDefMySQLDBPort = 3306 +CDefMySQLStatsGenFreq = 1 +CDefMySQLStatsCommitFreq = 300 + +# - DB Adapters URL Post defaults +CDefURLPostStatsGenFreq = 100 + +# - DB Adapters CSV File defaults +CDefCSVFileName = "pyevolve.csv" +CDefCSVFileStatsGenFreq = 1 + +# - DB Adapter XML RPC +CDefXMLRPCStatsGenFreq = 20 + +# Util Consts +CDefBroadcastAddress = "255.255.255.255" +nodeType = {"TERMINAL": 0, "NONTERMINAL": 1} + +CDefGPGenomes = [GTreeGP] + +# Migration Consts +CDefGenMigrationRate = 20 +CDefMigrationNIndividuals = 3 +CDefGenMigrationReplacement = 3 + +CDefNetworkIndividual = 1 +CDefNetworkInfo = 2 diff --git a/Crossovers.py b/Crossovers.py new file mode 100644 index 0000000..90c6748 --- /dev/null +++ b/Crossovers.py @@ -0,0 +1,810 @@ +""" + +:mod:`Crossovers` -- crossover methods module +===================================================================== + +In this module we have the genetic operators of crossover (or recombination) for each chromosome representation. + +""" +from future.builtins import range + +from random import randint as rand_randint, choice as rand_choice +from random import random as rand_random +import math +from . import Util + +############################# +## 1D Binary String ## +############################# + +def G1DBinaryStringXSinglePoint(genome, **args): + """ The crossover of 1D Binary String, Single Point + + .. warning:: You can't use this crossover method for binary strings with length of 1. + + """ + sister = None + brother = None + gMom = args["mom"] + gDad = args["dad"] + + if len(gMom) == 1: + Util.raiseException("The Binary String have one element, can't use the Single Point Crossover method !", TypeError) + + cut = rand_randint(1, len(gMom) - 1) + + if args["count"] >= 1: + sister = gMom.clone() + sister.resetStats() + sister[cut:] = gDad[cut:] + + if args["count"] == 2: + brother = gDad.clone() + brother.resetStats() + brother[cut:] = gMom[cut:] + + return (sister, brother) + +def G1DBinaryStringXTwoPoint(genome, **args): + """ The 1D Binary String crossover, Two Point + + .. warning:: You can't use this crossover method for binary strings with length of 1. + + """ + sister = None + brother = None + gMom = args["mom"] + gDad = args["dad"] + + if len(gMom) == 1: + Util.raiseException("The Binary String have one element, can't use the Two Point Crossover method !", TypeError) + + cuts = [rand_randint(1, len(gMom) - 1), rand_randint(1, len(gMom) - 1)] + + if cuts[0] > cuts[1]: + Util.listSwapElement(cuts, 0, 1) + + if args["count"] >= 1: + sister = gMom.clone() + sister.resetStats() + sister[cuts[0]:cuts[1]] = gDad[cuts[0]:cuts[1]] + + if args["count"] == 2: + brother = gDad.clone() + brother.resetStats() + brother[cuts[0]:cuts[1]] = gMom[cuts[0]:cuts[1]] + + return (sister, brother) + +def G1DBinaryStringXUniform(genome, **args): + """ The G1DList Uniform Crossover """ + from . import Consts + + sister = None + brother = None + gMom = args["mom"] + gDad = args["dad"] + + sister = gMom.clone() + brother = gDad.clone() + sister.resetStats() + brother.resetStats() + + for i in range(len(gMom)): + if Util.randomFlipCoin(Consts.CDefG1DBinaryStringUniformProb): + temp = sister[i] + sister[i] = brother[i] + brother[i] = temp + + return (sister, brother) + +#################### +## 1D List ## +#################### + +def G1DListCrossoverSinglePoint(genome, **args): + """ The crossover of G1DList, Single Point + + .. warning:: You can't use this crossover method for lists with just one element. + + """ + sister = None + brother = None + gMom = args["mom"] + gDad = args["dad"] + + if len(gMom) == 1: + Util.raiseException("The 1D List have one element, can't use the Single Point Crossover method !", TypeError) + + cut = rand_randint(1, len(gMom) - 1) + + if args["count"] >= 1: + sister = gMom.clone() + sister.resetStats() + sister[cut:] = gDad[cut:] + + if args["count"] == 2: + brother = gDad.clone() + brother.resetStats() + brother[cut:] = gMom[cut:] + + return (sister, brother) + +def G1DListCrossoverTwoPoint(genome, **args): + """ The G1DList crossover, Two Point + + .. warning:: You can't use this crossover method for lists with just one element. + + """ + sister = None + brother = None + gMom = args["mom"] + gDad = args["dad"] + + if len(gMom) == 1: + Util.raiseException("The 1D List have one element, can't use the Two Point Crossover method !", TypeError) + + cuts = [rand_randint(1, len(gMom) - 1), rand_randint(1, len(gMom) - 1)] + + if cuts[0] > cuts[1]: + Util.listSwapElement(cuts, 0, 1) + + if args["count"] >= 1: + sister = gMom.clone() + sister.resetStats() + sister[cuts[0]:cuts[1]] = gDad[cuts[0]:cuts[1]] + + if args["count"] == 2: + brother = gDad.clone() + brother.resetStats() + brother[cuts[0]:cuts[1]] = gMom[cuts[0]:cuts[1]] + + return (sister, brother) + +def G1DListCrossoverUniform(genome, **args): + """ The G1DList Uniform Crossover + + Each gene has a 50% chance of being swapped between mom and dad + + """ + from . import Consts + sister = None + brother = None + gMom = args["mom"] + gDad = args["dad"] + + sister = gMom.clone() + brother = gDad.clone() + sister.resetStats() + brother.resetStats() + + for i in range(len(gMom)): + if Util.randomFlipCoin(Consts.CDefG1DListCrossUniformProb): + temp = sister[i] + sister[i] = brother[i] + brother[i] = temp + + return (sister, brother) + +def G1DListCrossoverOX(genome, **args): + """ The OX Crossover for G1DList (order crossover) """ + sister = None + brother = None + gMom = args["mom"] + gDad = args["dad"] + listSize = len(gMom) + + c1, c2 = [rand_randint(1, len(gMom) - 1), rand_randint(1, len(gMom) - 1)] + + while c1 == c2: + c2 = rand_randint(1, len(gMom) - 1) + + if c1 > c2: + h = c1 + c1 = c2 + c2 = h + + if args["count"] >= 1: + sister = gMom.clone() + sister.resetStats() + P1 = [c for c in gMom[c2:] + gMom[:c2] if c not in gDad[c1:c2]] + sister.genomeList = P1[listSize - c2:] + gDad[c1:c2] + P1[:listSize - c2] + + if args["count"] == 2: + brother = gDad.clone() + brother.resetStats() + P2 = [c for c in gDad[c2:] + gDad[:c2] if c not in gMom[c1:c2]] + brother.genomeList = P2[listSize - c2:] + gMom[c1:c2] + P2[:listSize - c2] + + assert listSize == len(sister) + assert listSize == len(brother) + + return (sister, brother) + +def G1DListCrossoverEdge(genome, **args): + """ THe Edge Recombination crossover for G1DList (widely used for TSP problem) + + See more information in the `Edge Recombination Operator `_ + Wikipedia entry. + """ + gMom, sisterl = args["mom"], [] + gDad, brotherl = args["dad"], [] + + mom_edges, dad_edges, merge_edges = Util.G1DListGetEdgesComposite(gMom, gDad) + + for c, u in (sisterl, set(gMom)), (brotherl, set(gDad)): + curr = None + for i in range(len(gMom)): + curr = rand_choice(tuple(u)) if not curr else curr + c.append(curr) + u.remove(curr) + d = [v for v in merge_edges.get(curr, []) if v in u] + if d: + curr = rand_choice(d) + else: + s = [v for v in mom_edges.get(curr, []) if v in u] + s += [v for v in dad_edges.get(curr, []) if v in u] + curr = rand_choice(s) if s else None + + sister = gMom.clone() + brother = gDad.clone() + sister.resetStats() + brother.resetStats() + + sister.genomeList = sisterl + brother.genomeList = brotherl + + return (sister, brother) + +def G1DListCrossoverCutCrossfill(genome, **args): + """ The crossover of G1DList, Cut and crossfill, for permutations + """ + sister = None + brother = None + gMom = args["mom"] + gDad = args["dad"] + + if len(gMom) == 1: + Util.raiseException("The 1D List have one element, can't use the Single Point Crossover method !", TypeError) + + cut = rand_randint(1, len(gMom) - 1) + + if args["count"] >= 1: + sister = gMom.clone() + mother_part = gMom[0:cut] + sister.resetStats() + i = (len(sister) - cut) + x = 0 + for v in gDad: + if v in mother_part: + continue + if x >= i: + break + sister[cut + x] = v + x += 1 + + if args["count"] == 2: + brother = gDad.clone() + father_part = gDad[0:cut] + brother.resetStats() + i = (len(brother) - cut) + x = 0 + for v in gMom: + if v in father_part: + continue + if x >= i: + break + brother[cut + x] = v + x += 1 + + return (sister, brother) + +def G1DListCrossoverRealSBX(genome, **args): + """ Experimental SBX Implementation - Follows the implementation in NSGA-II (Deb, et.al) + + Some implementation `reference `_. + And another reference to the `Simulated Binary Crossover `_. + + .. warning:: This crossover method is Data Type Dependent, which means that + must be used for 1D genome of real values. + """ + from . import Consts + + EPS = Consts.CDefG1DListSBXEPS + # Crossover distribution index + eta_c = Consts.CDefG1DListSBXEtac + + gMom = args["mom"] + gDad = args["dad"] + + # Get the variable bounds ('gDad' could have been used; but I love Mom:-)) + lb = gMom.getParam("rangemin", Consts.CDefRangeMin) + ub = gMom.getParam("rangemax", Consts.CDefRangeMax) + + sister = gMom.clone() + brother = gDad.clone() + + sister.resetStats() + brother.resetStats() + + for i in range(0, len(gMom)): + if math.fabs(gMom[i] - gDad[i]) > EPS: + if gMom[i] > gDad[i]: + #swap + temp = gMom[i] + gMom[i] = gDad[i] + gDad[i] = temp + + #random number betwn. 0 & 1 + u = rand_random() + + beta = 1.0 + 2 * (gMom[i] - lb) / (1.0 * (gDad[i] - gMom[i])) + alpha = 2.0 - beta ** (-(eta_c + 1.0)) + + if u <= (1.0 / alpha): + beta_q = (u * alpha) ** (1.0 / ((eta_c + 1.0) * 1.0)) + else: + beta_q = (1.0 / (2.0 - u * alpha)) ** (1.0 / (1.0 * (eta_c + 1.0))) + + brother[i] = 0.5 * ((gMom[i] + gDad[i]) - beta_q * (gDad[i] - gMom[i])) + + beta = 1.0 + 2.0 * (ub - gDad[i]) / (1.0 * (gDad[i] - gMom[i])) + alpha = 2.0 - beta ** (-(eta_c + 1.0)) + + if u <= (1.0 / alpha): + beta_q = (u * alpha) ** (1.0 / ((eta_c + 1) * 1.0)) + else: + beta_q = (1.0 / (2.0 - u * alpha)) ** (1.0 / (1.0 * (eta_c + 1.0))) + + sister[i] = 0.5 * ((gMom[i] + gDad[i]) + beta_q * (gDad[i] - gMom[i])) + + if brother[i] > ub: + brother[i] = ub + if brother[i] < lb: + brother[i] = lb + + if sister[i] > ub: + sister[i] = ub + if sister[i] < lb: + sister[i] = lb + + if rand_random() > 0.5: + # Swap + temp = sister[i] + sister[i] = brother[i] + brother[i] = temp + else: + sister[i] = gMom[i] + brother[i] = gDad[i] + + return (sister, brother) + + +#################### +## 2D List ## +#################### + +def G2DListCrossoverUniform(genome, **args): + """ The G2DList Uniform Crossover """ + from . import Consts + sister = None + brother = None + gMom = args["mom"] + gDad = args["dad"] + + sister = gMom.clone() + brother = gDad.clone() + sister.resetStats() + brother.resetStats() + + h, w = gMom.getSize() + + for i in range(h): + for j in range(w): + if Util.randomFlipCoin(Consts.CDefG2DListCrossUniformProb): + temp = sister.getItem(i, j) + sister.setItem(i, j, brother.getItem(i, j)) + brother.setItem(i, j, temp) + + return (sister, brother) + + +def G2DListCrossoverSingleVPoint(genome, **args): + """ The crossover of G2DList, Single Vertical Point """ + sister = None + brother = None + gMom = args["mom"] + gDad = args["dad"] + + cut = rand_randint(1, gMom.getWidth() - 1) + + if args["count"] >= 1: + sister = gMom.clone() + sister.resetStats() + for i in range(sister.getHeight()): + sister[i][cut:] = gDad[i][cut:] + + if args["count"] == 2: + brother = gDad.clone() + brother.resetStats() + for i in range(brother.getHeight()): + brother[i][cut:] = gMom[i][cut:] + + return (sister, brother) + +def G2DListCrossoverSingleHPoint(genome, **args): + """ The crossover of G2DList, Single Horizontal Point """ + sister = None + brother = None + gMom = args["mom"] + gDad = args["dad"] + + cut = rand_randint(1, gMom.getHeight() - 1) + + if args["count"] >= 1: + sister = gMom.clone() + sister.resetStats() + for i in range(cut, sister.getHeight()): + sister[i][:] = gDad[i][:] + + if args["count"] == 2: + brother = gDad.clone() + brother.resetStats() + for i in range(brother.getHeight()): + brother[i][:] = gMom[i][:] + + return (sister, brother) + + +############################# +## 2D Binary String ## +############################# + + +def G2DBinaryStringXUniform(genome, **args): + """ The G2DBinaryString Uniform Crossover + + .. versionadded:: 0.6 + The *G2DBinaryStringXUniform* function + """ + from . import Consts + + sister = None + brother = None + gMom = args["mom"] + gDad = args["dad"] + + sister = gMom.clone() + brother = gDad.clone() + sister.resetStats() + brother.resetStats() + + h, w = gMom.getSize() + + for i in range(h): + for j in range(w): + if Util.randomFlipCoin(Consts.CDefG2DBinaryStringUniformProb): + temp = sister.getItem(i, j) + sister.setItem(i, j, brother.getItem(i, j)) + brother.setItem(i, j, temp) + + return (sister, brother) + + +def G2DBinaryStringXSingleVPoint(genome, **args): + """ The crossover of G2DBinaryString, Single Vertical Point + + .. versionadded:: 0.6 + The *G2DBinaryStringXSingleVPoint* function + """ + sister = None + brother = None + gMom = args["mom"] + gDad = args["dad"] + + cut = rand_randint(1, gMom.getWidth() - 1) + + if args["count"] >= 1: + sister = gMom.clone() + sister.resetStats() + for i in range(sister.getHeight()): + sister[i][cut:] = gDad[i][cut:] + + if args["count"] == 2: + brother = gDad.clone() + brother.resetStats() + for i in range(brother.getHeight()): + brother[i][cut:] = gMom[i][cut:] + + return (sister, brother) + +def G2DBinaryStringXSingleHPoint(genome, **args): + """ The crossover of G2DBinaryString, Single Horizontal Point + + .. versionadded:: 0.6 + The *G2DBinaryStringXSingleHPoint* function + + """ + sister = None + brother = None + gMom = args["mom"] + gDad = args["dad"] + + cut = rand_randint(1, gMom.getHeight() - 1) + + if args["count"] >= 1: + sister = gMom.clone() + sister.resetStats() + for i in range(cut, sister.getHeight()): + sister[i][:] = gDad[i][:] + + if args["count"] == 2: + brother = gDad.clone() + brother.resetStats() + for i in range(brother.getHeight()): + brother[i][:] = gMom[i][:] + + return (sister, brother) + +############################# +## Tree ## +############################# + + +def GTreeCrossoverSinglePoint(genome, **args): + """ The crossover for GTree, Single Point """ + sister = None + brother = None + gMom = args["mom"].clone() + gDad = args["dad"].clone() + + gMom.resetStats() + gDad.resetStats() + + node_mom_stack = [] + all_mom_nodes = [] + node_mom_tmp = None + + node_dad_stack = [] + all_dad_nodes = [] + node_dad_tmp = None + + node_mom_stack.append(gMom.getRoot()) + node_dad_stack.append(gDad.getRoot()) + + while (len(node_mom_stack) > 0) and (len(node_dad_stack) > 0): + node_mom_tmp = node_mom_stack.pop() + node_dad_tmp = node_dad_stack.pop() + + if node_mom_tmp != gMom.getRoot(): + all_mom_nodes.append(node_mom_tmp) + all_dad_nodes.append(node_dad_tmp) + + node_mom_stack.extend(node_mom_tmp.getChilds()) + node_dad_stack.extend(node_dad_tmp.getChilds()) + + if len(all_mom_nodes) == 0 or len(all_dad_nodes) == 0: + return (gMom, gDad) + + if len(all_dad_nodes) == 1: + nodeDad = all_dad_nodes[0] + else: + nodeDad = rand_choice(all_dad_nodes) + + if len(all_mom_nodes) == 1: + nodeMom = all_mom_nodes[0] + else: + nodeMom = rand_choice(all_mom_nodes) + + nodeMom_parent = nodeMom.getParent() + nodeDad_parent = nodeDad.getParent() + + # Sister + if args["count"] >= 1: + sister = gMom + nodeDad.setParent(nodeMom_parent) + nodeMom_parent.replaceChild(nodeMom, nodeDad) + sister.processNodes() + + # Brother + if args["count"] == 2: + brother = gDad + nodeMom.setParent(nodeDad_parent) + nodeDad_parent.replaceChild(nodeDad, nodeMom) + brother.processNodes() + + return (sister, brother) + +def GTreeCrossoverSinglePointStrict(genome, **args): + """ The crossover of Tree, Strict Single Point + + ..note:: This crossover method creates offspring with restriction of the + *max_depth* parameter. + + Accepts the *max_attempt* parameter, *max_depth* (required), and + the distr_leaft (>= 0.0 and <= 1.0), which represents the probability + of leaf selection when findin random nodes for crossover. + + """ + sister = None + brother = None + + gMom = args["mom"].clone() + gDad = args["dad"].clone() + + gMom.resetStats() + gDad.resetStats() + + max_depth = gMom.getParam("max_depth", None) + max_attempt = gMom.getParam("max_attempt", 10) + distr_leaf = gMom.getParam("distr_leaf", None) + + if max_depth is None: + Util.raiseException("You must specify the max_depth genome parameter !", ValueError) + + if max_depth < 0: + Util.raiseException("The max_depth must be >= 1, if you want to use GTreeCrossoverSinglePointStrict crossover !", ValueError) + + momRandom = None + dadRandom = None + + for i in range(max_attempt): + + if distr_leaf is None: + dadRandom = gDad.getRandomNode() + momRandom = gMom.getRandomNode() + else: + if Util.randomFlipCoin(distr_leaf): + momRandom = gMom.getRandomNode(1) + else: + momRandom = gMom.getRandomNode(2) + + if Util.randomFlipCoin(distr_leaf): + dadRandom = gDad.getRandomNode(1) + else: + dadRandom = gDad.getRandomNode(2) + + assert momRandom is not None + assert dadRandom is not None + + # Optimize here + mH = gMom.getNodeHeight(momRandom) + dH = gDad.getNodeHeight(dadRandom) + + mD = gMom.getNodeDepth(momRandom) + dD = gDad.getNodeDepth(dadRandom) + + # The depth of the crossover is greater than the max_depth + if (dD + mH <= max_depth) and (mD + dH <= max_depth): + break + + if i == (max_attempt - 1): + assert gMom.getHeight() <= max_depth + return (gMom, gDad) + else: + nodeMom, nodeDad = momRandom, dadRandom + + nodeMom_parent = nodeMom.getParent() + nodeDad_parent = nodeDad.getParent() + + # Sister + if args["count"] >= 1: + sister = gMom + nodeDad.setParent(nodeMom_parent) + + if nodeMom_parent is None: + sister.setRoot(nodeDad) + else: + nodeMom_parent.replaceChild(nodeMom, nodeDad) + sister.processNodes() + assert sister.getHeight() <= max_depth + + # Brother + if args["count"] == 2: + brother = gDad + nodeMom.setParent(nodeDad_parent) + + if nodeDad_parent is None: + brother.setRoot(nodeMom) + else: + nodeDad_parent.replaceChild(nodeDad, nodeMom) + brother.processNodes() + assert brother.getHeight() <= max_depth + + return (sister, brother) + +############################################################################# +################# GTreeGP Crossovers ###################################### +############################################################################# + +def GTreeGPCrossoverSinglePoint(genome, **args): + """ The crossover of the GTreeGP, Single Point for Genetic Programming + + ..note:: This crossover method creates offspring with restriction of the + *max_depth* parameter. + + Accepts the *max_attempt* parameter, *max_depth* (required). + """ + from . import Consts + + sister = None + brother = None + + gMom = args["mom"].clone() + gDad = args["dad"].clone() + + gMom.resetStats() + gDad.resetStats() + + max_depth = gMom.getParam("max_depth", None) + max_attempt = gMom.getParam("max_attempt", 15) + + if max_depth is None: + Util.raiseException("You must specify the max_depth genome parameter !", ValueError) + + if max_depth < 0: + Util.raiseException("The max_depth must be >= 1, if you want to use GTreeCrossoverSinglePointStrict crossover !", ValueError) + + momRandom = None + dadRandom = None + + for i in range(max_attempt): + + dadRandom = gDad.getRandomNode() + + if dadRandom.getType() == Consts.nodeType["TERMINAL"]: + momRandom = gMom.getRandomNode(1) + elif dadRandom.getType() == Consts.nodeType["NONTERMINAL"]: + momRandom = gMom.getRandomNode(2) + + mD = gMom.getNodeDepth(momRandom) + dD = gDad.getNodeDepth(dadRandom) + + # Two nodes are root + if mD == 0 and dD == 0: + continue + + mH = gMom.getNodeHeight(momRandom) + if dD + mH > max_depth: + continue + + dH = gDad.getNodeHeight(dadRandom) + if mD + dH > max_depth: + continue + + break + + if i == (max_attempt - 1): + assert gMom.getHeight() <= max_depth + return (gMom, gDad) + else: + nodeMom, nodeDad = momRandom, dadRandom + + nodeMom_parent = nodeMom.getParent() + nodeDad_parent = nodeDad.getParent() + + # Sister + if args["count"] >= 1: + sister = gMom + nodeDad.setParent(nodeMom_parent) + + if nodeMom_parent is None: + sister.setRoot(nodeDad) + else: + nodeMom_parent.replaceChild(nodeMom, nodeDad) + sister.processNodes() + assert sister.getHeight() <= max_depth + + # Brother + if args["count"] == 2: + brother = gDad + nodeMom.setParent(nodeDad_parent) + + if nodeDad_parent is None: + brother.setRoot(nodeMom) + else: + nodeDad_parent.replaceChild(nodeDad, nodeMom) + brother.processNodes() + assert brother.getHeight() <= max_depth + + return (sister, brother) diff --git a/DBAdapters.py b/DBAdapters.py new file mode 100644 index 0000000..d86f7d7 --- /dev/null +++ b/DBAdapters.py @@ -0,0 +1,792 @@ +""" +:mod:`DBAdapters` -- database adapters for statistics +===================================================================== + +.. warning:: the use the of a DB Adapter can reduce the performance of the + Genetic Algorithm. + +Pyevolve have a feature in which you can save the statistics of every +generation in a database, file or call an URL with the statistics as param. +You can use the database to plot evolution statistics graphs later. In this +module, you'll find the adapters above cited. + +.. seealso:: + + Method :meth:`GSimpleGA.GSimpleGA.setDBAdapter` + DB Adapters are set in the GSimpleGA Class. + +""" + +from future.builtins import range + +from pyevolve import __version__ +from . import Consts +from . import Util +from . import Statistics +import logging +import types +import datetime + + +class DBBaseAdapter(object): + """ DBBaseAdapter Class - The base class for all DB Adapters + + If you want to create your own DB Adapter, you must subclass this + class. + + :param frequency: the the generational dump frequency + + .. versionadded:: 0.6 + Added the :class:`DBBaseAdapter` class. + """ + def __init__(self, frequency, identify): + """ The class constructor """ + self.statsGenFreq = frequency + + if identify is None: + self.identify = datetime.datetime.strftime(datetime.datetime.now(), "%d/%m/%y-%H:%M") + else: + self.identify = identify + + def setIdentify(self, identify): + """ Sets the identify of the statistics + + :param identify: the id string + """ + if identify is None: + self.identify = datetime.datetime.strftime(datetime.datetime.now(), "%d/%m/%y-%H:%M") + else: + self.identify = identify + + def getIdentify(self): + """ Return the statistics identify + + :rtype: identify string + """ + return self.identify + + def getStatsGenFreq(self): + """ Returns the frequency of statistical dump + + :rtype: the generation interval of statistical dump + """ + return self.statsGenFreq + + def setStatsGenFreq(self, statsGenFreq): + """ Set the frequency of statistical dump + + :param statsGenFreq: the generation interval of statistical dump + """ + self.statsGenFreq = statsGenFreq + + def open(self, ga_engine): + """ This method is called one time to do the initialization of + the DB Adapter + + :param ga_engine: the GA Engine + """ + pass + + def commitAndClose(self): + """ This method is called at the end of the evolution, to closes the + DB Adapter and commit the changes """ + pass + + def insert(self, ga_engine): + """ Insert the stats + + :param ga_engine: the GA Engine + """ + Util.raiseException("This method is not implemented on the ABC", NotImplementedError) + +class DBFileCSV(DBBaseAdapter): + """ DBFileCSV Class - Adapter to dump statistics in CSV format + + Inheritance diagram for :class:`DBAdapters.DBFileCSV`: + + .. inheritance-diagram:: DBAdapters.DBFileCSV + + Example: + >>> adapter = DBFileCSV(filename="file.csv", identify="run_01", + frequency = 1, reset = True) + + :param filename: the CSV filename + :param identify: the identify of the run + :param frequency: the generational dump frequency + :param reset: if True, the file old data will be overwrite with the new + + .. versionadded:: 0.6 + Removed the stub methods and subclassed the :class:`DBBaseAdapter` class. + + """ + def __init__(self, filename=Consts.CDefCSVFileName, identify=None, + frequency=Consts.CDefCSVFileStatsGenFreq, reset=True): + """ The creator of DBFileCSV Class """ + + super(DBFileCSV, self).__init__(frequency, identify) + + self.csvmod = None + + self.filename = filename + self.csvWriter = None + self.fHandle = None + self.reset = reset + + def __repr__(self): + """ The string representation of adapter """ + ret = "DBFileCSV DB Adapter [File='%s', identify='%s']" % (self.filename, self.getIdentify()) + return ret + + def open(self, ga_engine): + """ Open the CSV file or creates a new file + + :param ga_engine: the GA Engine + + .. versionchanged:: 0.6 + The method now receives the *ga_engine* parameter. + """ + if self.csvmod is None: + logging.debug("Loading the csv module...") + self.csvmod = Util.importSpecial("csv") + + logging.debug("Opening the CSV file to dump statistics [%s]", self.filename) + open_mode = 'w' if self.reset else 'a' + self.fHandle = open(self.filename, open_mode) + self.csvWriter = self.csvmod.writer(self.fHandle, delimiter=';') + + def close(self): + """ Closes the CSV file handle """ + logging.debug("Closing the CSV file [%s]", self.filename) + if self.fHandle: + self.fHandle.close() + + def commitAndClose(self): + """ Commits and closes """ + self.close() + + def insert(self, ga_engine): + """ Inserts the stats into the CSV file + + :param ga_engine: the GA Engine + + .. versionchanged:: 0.6 + The method now receives the *ga_engine* parameter. + """ + stats = ga_engine.getStatistics() + generation = ga_engine.getCurrentGeneration() + line = [self.getIdentify(), generation] + line.extend(stats.asTuple()) + self.csvWriter.writerow(line) + +class DBURLPost(DBBaseAdapter): + """ DBURLPost Class - Adapter to call an URL with statistics + + Inheritance diagram for :class:`DBAdapters.DBURLPost`: + + .. inheritance-diagram:: DBAdapters.DBURLPost + + Example: + >>> dbadapter = DBURLPost(url="http://localhost/post.py", identify="test") + + The parameters that will be sent is all the statistics described in the :class:`Statistics.Statistics` + class, and the parameters: + + **generation** + The generation of the statistics + + **identify** + The id specified by user + + .. note:: see the :class:`Statistics.Statistics` documentation. + + :param url: the URL to be used + :param identify: the identify of the run + :param frequency: the generational dump frequency + :param post: if True, the POST method will be used, otherwise GET will be used. + + .. versionadded:: 0.6 + Removed the stub methods and subclassed the :class:`DBBaseAdapter` class. + """ + + def __init__(self, url, identify=None, + frequency=Consts.CDefURLPostStatsGenFreq, post=True): + """ The creator of the DBURLPost Class. """ + + super(DBURLPost, self).__init__(frequency, identify) + self.urllibmod = None + + self.url = url + self.post = post + + def __repr__(self): + """ The string representation of adapter """ + ret = "DBURLPost DB Adapter [URL='%s', identify='%s']" % (self.url, self.getIdentify()) + return ret + + def open(self, ga_engine): + """ Load the modules needed + + :param ga_engine: the GA Engine + + .. versionchanged:: 0.6 + The method now receives the *ga_engine* parameter. + """ + if self.urllibmod is None: + logging.debug("Loading urllib module...") + self.urllibmod = Util.importSpecial("urllib") + + def insert(self, ga_engine): + """ Sends the data to the URL using POST or GET + + :param ga_engine: the GA Engine + + .. versionchanged:: 0.6 + The method now receives the *ga_engine* parameter. + """ + logging.debug("Sending http request to %s.", self.url) + stats = ga_engine.getStatistics() + response = None + params = stats.internalDict.copy() + params["generation"] = ga_engine.getCurrentGeneration() + params["identify"] = self.getIdentify() + if self.post: # POST + response = self.urllibmod.urlopen(self.url, self.urllibmod.urlencode(params)) + else: # GET + response = self.urllibmod.urlopen(self.url + "?%s" % (self.urllibmod.urlencode(params))) + if response: + response.close() + +class DBSQLite(DBBaseAdapter): + """ DBSQLite Class - Adapter to dump data in SQLite3 database format + + Inheritance diagram for :class:`DBAdapters.DBSQLite`: + + .. inheritance-diagram:: DBAdapters.DBSQLite + + Example: + >>> dbadapter = DBSQLite(identify="test") + + When you run some GA for the first time, you need to create the database, for this, you + must use the *resetDB* parameter: + + >>> dbadapter = DBSQLite(identify="test", resetDB=True) + + This parameter will erase all the database tables and will create the new ones. + The *resetDB* parameter is different from the *resetIdentify* parameter, the *resetIdentify* + only erases the rows with the same "identify" name. + + :param dbname: the database filename + :param identify: the identify if the run + :param resetDB: if True, the database structure will be recreated + :param resetIdentify: if True, the identify with the same name will be overwrite with new data + :param frequency: the generational dump frequency + :param commit_freq: the commit frequency + """ + + def __init__(self, dbname=Consts.CDefSQLiteDBName, identify=None, resetDB=False, + resetIdentify=True, frequency=Consts.CDefSQLiteStatsGenFreq, + commit_freq=Consts.CDefSQLiteStatsCommitFreq): + """ The creator of the DBSQLite Class """ + + super(DBSQLite, self).__init__(frequency, identify) + + self.sqlite3mod = None + self.connection = None + self.resetDB = resetDB + self.resetIdentify = resetIdentify + self.dbName = dbname + self.typeDict = {float: "real"} + self.cursorPool = None + self.commitFreq = commit_freq + + def __repr__(self): + """ The string representation of adapter """ + ret = "DBSQLite DB Adapter [File='%s', identify='%s']" % (self.dbName, self.getIdentify()) + return ret + + def open(self, ga_engine): + """ Open the database connection + + :param ga_engine: the GA Engine + + .. versionchanged:: 0.6 + The method now receives the *ga_engine* parameter. + """ + if self.sqlite3mod is None: + logging.debug("Loading sqlite3 module...") + self.sqlite3mod = Util.importSpecial("sqlite3") + + logging.debug("Opening database, dbname=%s", self.dbName) + self.connection = self.sqlite3mod.connect(self.dbName) + + temp_stats = Statistics.Statistics() + + if self.resetDB: + self.resetStructure(Statistics.Statistics()) + + self.createStructure(temp_stats) + + if self.resetIdentify: + self.resetTableIdentify() + + def commitAndClose(self): + """ Commit changes on database and closes connection """ + self.commit() + self.close() + + def close(self): + """ Close the database connection """ + logging.debug("Closing database.") + if self.cursorPool: + self.cursorPool.close() + self.cursorPool = None + self.connection.close() + + def commit(self): + """ Commit changes to database """ + logging.debug("Commiting changes to database.") + self.connection.commit() + + def getCursor(self): + """ Return a cursor from the pool + + :rtype: the cursor + + """ + if not self.cursorPool: + logging.debug("Creating new cursor for database...") + self.cursorPool = self.connection.cursor() + return self.cursorPool + else: + return self.cursorPool + + def createStructure(self, stats): + """ Create table using the Statistics class structure + + :param stats: the statistics object + + """ + c = self.getCursor() + pstmt = "create table if not exists %s(identify text, generation integer, " % (Consts.CDefSQLiteDBTable) + for k, v in list(stats.items()): + pstmt += "%s %s, " % (k, self.typeDict[type(v)]) + pstmt = pstmt[:-2] + ")" + logging.debug("Creating table %s: %s.", Consts.CDefSQLiteDBTable, pstmt) + c.execute(pstmt) + + pstmt = """create table if not exists %s(identify text, generation integer, + individual integer, fitness real, raw real)""" % (Consts.CDefSQLiteDBTablePop) + logging.debug("Creating table %s: %s.", Consts.CDefSQLiteDBTablePop, pstmt) + c.execute(pstmt) + self.commit() + + def resetTableIdentify(self): + """ Delete all records on the table with the same Identify """ + c = self.getCursor() + stmt = "delete from %s where identify = ?" % (Consts.CDefSQLiteDBTable) + stmt2 = "delete from %s where identify = ?" % (Consts.CDefSQLiteDBTablePop) + + logging.debug("Erasing data from the tables with the identify = %s", self.getIdentify()) + try: + c.execute(stmt, (self.getIdentify(),)) + c.execute(stmt2, (self.getIdentify(),)) + except self.sqlite3mod.OperationalError as expt: + if str(expt).find("no such table") >= 0: + print("\n ## The DB Adapter can't find the tables ! Consider enable the parameter resetDB ! ##\n") + + self.commit() + + def resetStructure(self, stats): + """ Deletes de current structure and calls createStructure + + :param stats: the statistics object + + """ + logging.debug("Reseting structure, droping table and creating new empty table.") + c = self.getCursor() + c.execute("drop table if exists %s" % (Consts.CDefSQLiteDBTable,)) + c.execute("drop table if exists %s" % (Consts.CDefSQLiteDBTablePop,)) + self.commit() + self.createStructure(stats) + + def insert(self, ga_engine): + """ Inserts the statistics data to database + + :param ga_engine: the GA Engine + + .. versionchanged:: 0.6 + The method now receives the *ga_engine* parameter. + """ + stats = ga_engine.getStatistics() + population = ga_engine.getPopulation() + generation = ga_engine.getCurrentGeneration() + + c = self.getCursor() + pstmt = "insert into %s values (?, ?, " % (Consts.CDefSQLiteDBTable) + for i in range(len(stats)): + pstmt += "?, " + pstmt = pstmt[:-2] + ")" + c.execute(pstmt, (self.getIdentify(), generation) + stats.asTuple()) + + pstmt = "insert into %s values(?, ?, ?, ?, ?)" % (Consts.CDefSQLiteDBTablePop,) + tups = [] + for i in range(len(population)): + ind = population[i] + tups.append((self.getIdentify(), generation, i, ind.fitness, ind.score)) + + c.executemany(pstmt, tups) + if (generation % self.commitFreq == 0): + self.commit() + +class DBXMLRPC(DBBaseAdapter): + """ DBXMLRPC Class - Adapter to dump statistics to a XML Remote Procedure Call + + Inheritance diagram for :class:`DBAdapters.DBXMLRPC`: + + .. inheritance-diagram:: DBAdapters.DBXMLRPC + + Example: + >>> adapter = DBXMLRPC(url="http://localhost:8000/", identify="run_01", + frequency = 1) + + :param url: the URL of the XML RPC + :param identify: the identify of the run + :param frequency: the generational dump frequency + + + .. note:: The XML RPC Server must implement the *insert* method, wich receives + a python dictionary as argument. + + Example of an server in Python: :: + + import xmlrpclib + from SimpleXMLRPCServer import SimpleXMLRPCServer + + def insert(l): + print "Received statistics: %s" % l + + server = SimpleXMLRPCServer(("localhost", 8000), allow_none=True) + print "Listening on port 8000..." + server.register_function(insert, "insert") + server.serve_forever() + + .. versionadded:: 0.6 + The :class:`DBXMLRPC` class. + + """ + def __init__(self, url, identify=None, frequency=Consts.CDefXMLRPCStatsGenFreq): + """ The creator of DBXMLRPC Class """ + + super(DBXMLRPC, self).__init__(frequency, identify) + self.xmlrpclibmod = None + + self.url = url + self.proxy = None + + def __repr__(self): + """ The string representation of adapter """ + ret = "DBXMLRPC DB Adapter [URL='%s', identify='%s']" % (self.url, self.getIdentify()) + return ret + + def open(self, ga_engine): + """ Open the XML RPC Server proxy + + :param ga_engine: the GA Engine + + .. versionchanged:: 0.6 + The method now receives the *ga_engine* parameter. + """ + if self.xmlrpclibmod is None: + logging.debug("Loding the xmlrpclib module...") + self.xmlrpclibmod = Util.importSpecial("xmlrpclib") + + logging.debug("Opening the XML RPC Server Proxy on %s", self.url) + self.proxy = self.xmlrpclibmod.ServerProxy(self.url, allow_none=True) + + def insert(self, ga_engine): + """ Calls the XML RPC procedure + + :param ga_engine: the GA Engine + + .. versionchanged:: 0.6 + The method now receives the *ga_engine* parameter. + """ + stats = ga_engine.getStatistics() + generation = ga_engine.getCurrentGeneration() + di = stats.internalDict.copy() + di.update({"identify": self.getIdentify(), "generation": generation}) + self.proxy.insert(di) + +class DBVPythonGraph(DBBaseAdapter): + """ The DBVPythonGraph Class - A DB Adapter for real-time visualization using VPython + + Inheritance diagram for :class:`DBAdapters.DBVPythonGraph`: + + .. inheritance-diagram:: DBAdapters.DBVPythonGraph + + .. note:: to use this DB Adapter, you **must** install VPython first. + + Example: + >>> adapter = DBAdapters.DBVPythonGraph(identify="run_01", frequency = 1) + >>> ga_engine.setDBAdapter(adapter) + + :param identify: the identify of the run + :param genmax: use the generations as max value for x-axis, default False + :param frequency: the generational dump frequency + + .. versionadded:: 0.6 + The *DBVPythonGraph* class. + """ + + def __init__(self, identify=None, frequency=20, genmax=False): + super(DBVPythonGraph, self).__init__(frequency, identify) + self.genmax = genmax + self.vtkGraph = None + self.curveMin = None + self.curveMax = None + self.curveDev = None + self.curveAvg = None + + def makeDisplay(self, title_sec, x, y, ga_engine): + """ Used internally to create a new display for VPython. + + :param title_sec: the title of the window + :param x: the x position of the window + :param y: the y position of the window + :param ga_engine: the GA Engine + + :rtype: the window (the return of gdisplay call) + """ + title = "Pyevolve v.%s - %s - id [%s]" % (__version__, title_sec, self.identify) + if self.genmax: + disp = self.vtkGraph.gdisplay(title=title, xtitle='Generation', ytitle=title_sec, + xmax=ga_engine.getGenerations(), xmin=0., width=500, + height=250, x=x, y=y) + else: + disp = self.vtkGraph.gdisplay(title=title, xtitle='Generation', ytitle=title_sec, + xmin=0., width=500, height=250, x=x, y=y) + return disp + + def open(self, ga_engine): + """ Imports the VPython module and creates the four graph windows + + :param ga_engine: the GA Engine + """ + logging.debug("Loading visual.graph (VPython) module...") + if self.vtkGraph is None: + self.vtkGraph = Util.importSpecial("visual.graph").graph + + display_rawmin = self.makeDisplay("Raw Score (min)", 0, 0, ga_engine) + display_rawmax = self.makeDisplay("Raw Score (max)", 0, 250, ga_engine) + display_rawdev = self.makeDisplay("Raw Score (std. dev.)", 500, 0, ga_engine) + display_rawavg = self.makeDisplay("Raw Score (avg)", 500, 250, ga_engine) + + self.curveMin = self.vtkGraph.gcurve(color=self.vtkGraph.color.red, gdisplay=display_rawmin) + self.curveMax = self.vtkGraph.gcurve(color=self.vtkGraph.color.green, gdisplay=display_rawmax) + self.curveDev = self.vtkGraph.gcurve(color=self.vtkGraph.color.blue, gdisplay=display_rawdev) + self.curveAvg = self.vtkGraph.gcurve(color=self.vtkGraph.color.orange, gdisplay=display_rawavg) + + def insert(self, ga_engine): + """ Plot the current statistics to the graphs + + :param ga_engine: the GA Engine + """ + stats = ga_engine.getStatistics() + generation = ga_engine.getCurrentGeneration() + + self.curveMin.plot(pos=(generation, stats["rawMin"])) + self.curveMax.plot(pos=(generation, stats["rawMax"])) + self.curveDev.plot(pos=(generation, stats["rawDev"])) + self.curveAvg.plot(pos=(generation, stats["rawAve"])) + +class DBMySQLAdapter(DBBaseAdapter): + """ DBMySQLAdapter Class - Adapter to dump data in MySql database server + + Inheritance diagram for :class:`DBAdapters.DBMySQLAdapter`: + + .. inheritance-diagram:: DBAdapters.DBMySQLAdapter + + Example: + >>> dbadapter = DBMySQLAdapter("pyevolve_username", "password", identify="run1") + + or + + >>> dbadapter = DBMySQLAdapter(user="username", passwd="password", + ... host="mysqlserver.com.br", port=3306, db="pyevolve_db") + + When you run some GA for the first time, you need to create the database, for this, you + must use the *resetDB* parameter as True. + + This parameter will erase all the database tables and will create the new ones. + The *resetDB* parameter is different from the *resetIdentify* parameter, the *resetIdentify* + only erases the rows with the same "identify" name, and *resetDB* will drop and recreate + the tables. + + :param user: mysql username (must have permission to create, drop, insert, etc.. on tables + :param passwd: the user password on MySQL server + :param host: the hostname, default is "localhost" + :param port: the port, default is 3306 + :param db: the database name, default is "pyevolve" + :param identify: the identify if the run + :param resetDB: if True, the database structure will be recreated + :param resetIdentify: if True, the identify with the same name will be overwrite with new data + :param frequency: the generational dump frequency + :param commit_freq: the commit frequency + """ + + def __init__(self, user, passwd, host=Consts.CDefMySQLDBHost, port=Consts.CDefMySQLDBPort, + db=Consts.CDefMySQLDBName, identify=None, resetDB=False, resetIdentify=True, + frequency=Consts.CDefMySQLStatsGenFreq, commit_freq=Consts.CDefMySQLStatsCommitFreq): + """ The creator of the DBSQLite Class """ + + super(DBMySQLAdapter, self).__init__(frequency, identify) + + self.mysqldbmod = None + self.connection = None + self.resetDB = resetDB + self.resetIdentify = resetIdentify + self.db = db + self.host = host + self.port = port + self.user = user + self.passwd = passwd + self.typeDict = {float: "DOUBLE(14,6)"} + self.cursorPool = None + self.commitFreq = commit_freq + + def __repr__(self): + """ The string representation of adapter """ + ret = "DBMySQLAdapter DB Adapter [identify='%s', host='%s', username='%s', db='%s']" % (self.getIdentify(), + self.host, self.user, self.db) + return ret + + def open(self, ga_engine): + """ Open the database connection + + :param ga_engine: the GA Engine + + .. versionchanged:: 0.6 + The method now receives the *ga_engine* parameter. + """ + if self.mysqldbmod is None: + logging.debug("Loading MySQLdb module...") + self.mysqldbmod = Util.importSpecial("MySQLdb") + + logging.debug("Opening database, host=%s", self.host) + self.connection = self.mysqldbmod.connect(host=self.host, user=self.user, + passwd=self.passwd, db=self.db, + port=self.port) + temp_stats = Statistics.Statistics() + self.createStructure(temp_stats) + + if self.resetDB: + self.resetStructure(Statistics.Statistics()) + + if self.resetIdentify: + self.resetTableIdentify() + + def commitAndClose(self): + """ Commit changes on database and closes connection """ + self.commit() + self.close() + + def close(self): + """ Close the database connection """ + logging.debug("Closing database.") + if self.cursorPool: + self.cursorPool.close() + self.cursorPool = None + self.connection.close() + + def commit(self): + """ Commit changes to database """ + logging.debug("Commiting changes to database.") + self.connection.commit() + + def getCursor(self): + """ Return a cursor from the pool + + :rtype: the cursor + + """ + if not self.cursorPool: + logging.debug("Creating new cursor for database...") + self.cursorPool = self.connection.cursor() + return self.cursorPool + else: + return self.cursorPool + + def createStructure(self, stats): + """ Create table using the Statistics class structure + + :param stats: the statistics object + + """ + c = self.getCursor() + pstmt = "create table if not exists %s(identify VARCHAR(80), generation INTEGER, " % (Consts.CDefMySQLDBTable) + for k, v in list(stats.items()): + pstmt += "%s %s, " % (k, self.typeDict[type(v)]) + pstmt = pstmt[:-2] + ")" + logging.debug("Creating table %s: %s.", Consts.CDefSQLiteDBTable, pstmt) + c.execute(pstmt) + + pstmt = """create table if not exists %s(identify VARCHAR(80), generation INTEGER, + individual INTEGER, fitness DOUBLE(14,6), raw DOUBLE(14,6))""" % (Consts.CDefMySQLDBTablePop) + logging.debug("Creating table %s: %s.", Consts.CDefMySQLDBTablePop, pstmt) + c.execute(pstmt) + self.commit() + + def resetTableIdentify(self): + """ Delete all records on the table with the same Identify """ + c = self.getCursor() + stmt = "delete from %s where identify = '%s'" % (Consts.CDefMySQLDBTable, self.getIdentify()) + stmt2 = "delete from %s where identify = '%s'" % (Consts.CDefMySQLDBTablePop, self.getIdentify()) + + logging.debug("Erasing data from the tables with the identify = %s", self.getIdentify()) + c.execute(stmt) + c.execute(stmt2) + + self.commit() + + def resetStructure(self, stats): + """ Deletes de current structure and calls createStructure + + :param stats: the statistics object + + """ + logging.debug("Reseting structure, droping table and creating new empty table.") + c = self.getCursor() + c.execute("drop table if exists %s" % (Consts.CDefMySQLDBTable,)) + c.execute("drop table if exists %s" % (Consts.CDefMySQLDBTablePop,)) + self.commit() + self.createStructure(stats) + + def insert(self, ga_engine): + """ Inserts the statistics data to database + + :param ga_engine: the GA Engine + + .. versionchanged:: 0.6 + The method now receives the *ga_engine* parameter. + """ + stats = ga_engine.getStatistics() + population = ga_engine.getPopulation() + generation = ga_engine.getCurrentGeneration() + + c = self.getCursor() + pstmt = "insert into " + Consts.CDefMySQLDBTable + " values (%s, %s, " + for i in range(len(stats)): + pstmt += "%s, " + pstmt = pstmt[:-2] + ")" + c.execute(pstmt, (self.getIdentify(), generation) + stats.asTuple()) + + pstmt = "insert into " + Consts.CDefMySQLDBTablePop + " values(%s, %s, %s, %s, %s)" + + tups = [] + for i in range(len(population)): + ind = population[i] + tups.append((self.getIdentify(), generation, i, ind.fitness, ind.score)) + + c.executemany(pstmt, tups) + if (generation % self.commitFreq == 0): + self.commit() diff --git a/FunctionSlot.py b/FunctionSlot.py new file mode 100644 index 0000000..c4e5ec2 --- /dev/null +++ b/FunctionSlot.py @@ -0,0 +1,202 @@ +""" +:mod:`FunctionSlot` -- function slots module +================================================================== + +The *function slot* concept is large used by Pyevolve, the idea +is simple, each genetic operator or any operator, can be assigned +to a slot, by this way, we can add more than simple one operator, +we can have for example, two or more mutator operators at same time, +two or more evaluation functions, etc. In this :mod:`FunctionSlot` module, +you'll find the class :class:`FunctionSlot.FunctionSlot`, which is the slot class. + +""" + +from random import uniform as rand_uniform + +from . import Util +import collections + +class FunctionSlot(object): + """ FunctionSlot Class - The function slot + + Example: + >>> genome.evaluator.set(eval_func) + >>> genome.evaluator[0] + + >>> genome.evaluator + Slot [Evaluation Function] (Count: 1) + Name: eval_func + >>> genome.evaluator.clear() + >>> genome.evaluator + Slot [Evaluation Function] (Count: 0) + No function + + You can add weight to functions when using the `rand_apply` paramter: + >>> genome.evaluator.set(eval_main, 0.9) + >>> genome.evaluator.add(eval_sec, 0.3) + >>> genome.evaluator.setRandomApply() + + In the above example, the function *eval_main* will be called with 90% of + probability and the *eval_sec* will be called with 30% of probability. + + There are another way to add functions too: + >>> genome.evaluator += eval_func + + :param name: the slot name + :param rand_apply: if True, just one of the functions in the slot + will be applied, this function is randomly picked based + on the weight of the function added. + + """ + + def __init__(self, name="Anonymous Function", rand_apply=False): + """ The creator of the FunctionSlot Class """ + self.funcList = [] + self.funcWeights = [] + self.slotName = name + self.rand_apply = rand_apply + + def __typeCheck(self, func): + """ Used internally to check if a function passed to the + function slot is callable. Otherwise raises a TypeError exception. + + :param func: the function object + """ + if not isinstance(func, collections.Callable): + Util.raiseException("The function must be a method or function", TypeError) + + def __iadd__(self, func): + """ To add more functions using the += operator + + .. versionadded:: 0.6 + The __iadd__ method. + """ + self.__typeCheck(func) + self.funcList.append(func) + return self + + def __getitem__(self, index): + """ Used to retrieve some slot function index """ + return self.funcList[index] + + def __setitem__(self, index, value): + """ Used to set the index slot function """ + self.__typeCheck(value) + self.funcList[index] = value + + def __iter__(self): + """ Return the function list iterator """ + return iter(self.funcList) + + def __len__(self): + """ Return the number of functions on the slot + + .. versionadded:: 0.6 + The *__len__* method + """ + return len(self.funcList) + + def setRandomApply(self, flag=True): + """ Sets the random function application, in this mode, the + function will randomly choose one slot to apply + + :param flag: True or False + + """ + if not isinstance(flag, bool): + Util.raiseException("Random option must be True or False", TypeError) + + self.rand_apply = flag + + def clear(self): + """ Used to clear the functions in the slot """ + if len(self.funcList) > 0: + del self.funcList[:] + del self.funcWeights[:] + + def add(self, func, weight=0.5): + """ Used to add a function to the slot + + :param func: the function to be added in the slot + :param weight: used when you enable the *random apply*, it's the weight + of the function for the random selection + + .. versionadded:: 0.6 + The `weight` parameter. + + """ + self.__typeCheck(func) + self.funcList.append(func) + self.funcWeights.append(weight) + + def isEmpty(self): + """ Return true if the function slot is empy """ + return (len(self.funcList) == 0) + + def set(self, func, weight=0.5): + """ Used to clear all functions in the slot and add one + + :param func: the function to be added in the slot + :param weight: used when you enable the *random apply*, it's the weight + of the function for the random selection + + .. versionadded:: 0.6 + The `weight` parameter. + + .. note:: the method *set* of the function slot remove all previous + functions added to the slot. + """ + self.clear() + self.__typeCheck(func) + self.add(func, weight) + + def apply(self, index, obj, **args): + """ Apply the index function + + :param index: the index of the function + :param obj: this object is passes as parameter to the function + :param args: this args dictionary is passed to the function + + """ + if len(self.funcList) <= 0: + raise Exception("No function defined: " + self.slotName) + return self.funcList[index](obj, **args) + + def applyFunctions(self, obj=None, **args): + """ Generator to apply all function slots in obj + + :param obj: this object is passes as parameter to the function + :param args: this args dictionary is passed to the function + + """ + if len(self.funcList) <= 0: + Util.raiseException("No function defined: " + self.slotName) + + if not self.rand_apply: + for f in self.funcList: + yield f(obj, **args) + else: + v = rand_uniform(0, 1) + fobj = None + for func, weight in zip(self.funcList, self.funcWeights): + fobj = func + if v < weight: + break + v = v - weight + + yield fobj(obj, **args) + + def __repr__(self): + """ String representation of FunctionSlot """ + strRet = "Slot [%s] (Count: %d)\n" % (self.slotName, len(self.funcList)) + + if len(self.funcList) <= 0: + strRet += "\t\tNo function\n" + return strRet + + for f, w in zip(self.funcList, self.funcWeights): + strRet += "\t\tName: %s - Weight: %.2f\n" % (f.__name__, w) + if f.__doc__: + strRet += "\t\tDoc: " + f.__doc__ + "\n" + + return strRet diff --git a/G1DBinaryString.py b/G1DBinaryString.py new file mode 100644 index 0000000..7d9ec28 --- /dev/null +++ b/G1DBinaryString.py @@ -0,0 +1,173 @@ +""" +:mod:`G1DBinaryString` -- the classical binary string chromosome +===================================================================== + +This is the classical chromosome representation on GAs, it is the 1D +Binary String. This string looks like "00011101010". + + +Default Parameters +------------------------------------------------------------- + +*Initializator* + + :func:`Initializators.G1DBinaryStringInitializator` + + The Binatry String Initializator for G1DBinaryString + +*Mutator* + + :func:`Mutators.G1DBinaryStringMutatorFlip` + + The Flip Mutator for G1DBinaryString + +*Crossover* + + :func:`Crossovers.G1DBinaryStringXSinglePoint` + + The Single Point Crossover for G1DBinaryString + + +Class +------------------------------------------------------------- + + +""" + +from .GenomeBase import GenomeBase, G1DBase +from . import Consts +from . import Util + +class G1DBinaryString(G1DBase): + """ G1DBinaryString Class - The 1D Binary String chromosome + + Inheritance diagram for :class:`G1DBinaryString.G1DBinaryString`: + + .. inheritance-diagram:: G1DBinaryString.G1DBinaryString + + This chromosome class extends the :class:`GenomeBase.G1DBase` class. + + Example: + >>> genome = G1DBinaryString.G1DBinaryString(5) + + :param length: the 1D Binary String size + + """ + __slots__ = ["stringLength"] + + def __init__(self, length=10): + """ The initializator of G1DList representation """ + super(G1DBinaryString, self).__init__(length) + self.genomeList = [] + self.stringLength = length + self.initializator.set(Consts.CDefG1DBinaryStringInit) + self.mutator.set(Consts.CDefG1DBinaryStringMutator) + self.crossover.set(Consts.CDefG1DBinaryStringCrossover) + + def __setitem__(self, key, value): + """ Set the specified value for an gene of List + + >>> g = G1DBinaryString(5) + >>> for i in xrange(len(g)): + ... g.append(1) + >>> g[4] = 0 + >>> g[4] + 0 + + """ + if isinstance(value, int) and value not in (0,1): + Util.raiseException("The value must be zero (0) or one (1), used (%s)" % value, ValueError) + elif isinstance(value, list) and not set(value) <= set([0, 1]): + # if slice notation is used we check all passed values + vals = set(value) - set([0, 1]) + Util.raiseException("The value must be zero (0) or one (1), used (%s)" % vals, ValueError) + G1DBase.__setitem__(self, key, value) + + def __repr__(self): + """ Return a string representation of Genome """ + ret = GenomeBase.__repr__(self) + ret += "- G1DBinaryString\n" + ret += "\tString length:\t %s\n" % (self.getListSize(),) + ret += "\tString:\t\t %s\n\n" % (self.getBinary(),) + return ret + + def getDecimal(self): + """ Converts the binary string to decimal representation + + Example: + >>> g = G1DBinaryString(5) + >>> for i in xrange(len(g)): + ... g.append(0) + >>> g[3] = 1 + >>> g.getDecimal() + 2 + + :rtype: decimal value + + """ + return int(self.getBinary(), 2) + + def getBinary(self): + """ Returns the binary string representation + + Example: + >>> g = G1DBinaryString(2) + >>> g.append(0) + >>> g.append(1) + >>> g.getBinary() + '01' + + :rtype: the binary string + + """ + return "".join(map(str, self)) + + def append(self, value): + """ Appends an item to the list + + Example: + >>> g = G1DBinaryString(2) + >>> g.append(0) + + :param value: value to be added, 0 or 1 + + """ + if value not in [0, 1]: + Util.raiseException("The value must be 0 or 1", ValueError) + G1DBase.append(self, value) + + def copy(self, g): + """ Copy genome to 'g' + + Example: + >>> g1 = G1DBinaryString(2) + >>> g1.append(0) + >>> g1.append(1) + >>> g2 = G1DBinaryString(2) + >>> g1.copy(g2) + >>> g2[1] + 1 + + :param g: the destination genome + + """ + GenomeBase.copy(self, g) + G1DBase.copy(self, g) + + def clone(self): + """ Return a new instace copy of the genome + + Example: + >>> g = G1DBinaryString(5) + >>> for i in xrange(len(g)): + ... g.append(1) + >>> clone = g.clone() + >>> clone[0] + 1 + + :rtype: the G1DBinaryString instance clone + + """ + newcopy = G1DBinaryString(self.getListSize()) + self.copy(newcopy) + return newcopy diff --git a/G1DList.py b/G1DList.py new file mode 100644 index 0000000..e8645a6 --- /dev/null +++ b/G1DList.py @@ -0,0 +1,167 @@ +""" + +:mod:`G1DList` -- the 1D list chromosome +============================================================= + +This is the 1D List representation, this list can carry real +numbers or integers or any kind of object, by default, we have +genetic operators for integer and real lists, which can be found +on the respective modules. + +Default Parameters +------------------------------------------------------------- + +*Initializator* + + :func:`Initializators.G1DListInitializatorInteger` + + The Integer Initializator for G1DList + +*Mutator* + + :func:`Mutators.G1DListMutatorSwap` + + The Swap Mutator for G1DList + +*Crossover* + + :func:`Crossovers.G1DListCrossoverSinglePoint` + + The Single Point Crossover for G1DList + + +Class +------------------------------------------------------------- + +""" +from future.builtins import range + +from .GenomeBase import GenomeBase, G1DBase +from . import Consts + + +class G1DList(G1DBase): + """ G1DList Class - The 1D List chromosome representation + + Inheritance diagram for :class:`G1DList.G1DList`: + + .. inheritance-diagram:: G1DList.G1DList + + This chromosome class extends the :class:`GenomeBase.GenomeBase` class. + + **Examples** + + The instantiation + >>> g = G1DList(10) + + Compare + >>> genome2 = genome1.clone() + >>> genome2 == genome1 + True + + Multiply + >>> genome = population[0] + >>> genome + (...) + [1, 2, 3, 4] + >>> genome_result = genome * 2 + >>> genome_result + (...) + [2, 2, 6, 8] + + Add + >>> genome + (...) + [1, 2, 3, 4] + >>> genome_result = genome + 2 + (...) + [3, 4, 5, 6] + + Iteration + >>> for i in genome: + >>> print i + 1 + 2 + 3 + 4 + + Size, slice, get/set, append + >>> len(genome) + 4 + >>> genome + (...) + [1, 2, 3, 4] + >>> genome[0:1] + [1, 2] + >>> genome[1] = 666 + >>> genome + (...) + [1, 666, 3, 4] + >>> genome.append(99) + >>> genome + (...) + [1, 666, 3, 4, 99] + + :param size: the 1D list size + + """ + + def __init__(self, size=10, cloning=False): + """ The initializator of G1DList representation, + size parameter must be specified """ + super(G1DList, self).__init__(size) + if not cloning: + self.initializator.set(Consts.CDefG1DListInit) + self.mutator.set(Consts.CDefG1DListMutator) + self.crossover.set(Consts.CDefG1DListCrossover) + + def __mul__(self, other): + """ Multiply every element of G1DList by "other" """ + newObj = self.clone() + for i in range(len(newObj)): + newObj[i] *= other + return newObj + + def __add__(self, other): + """ Plus every element of G1DList by "other" """ + newObj = self.clone() + for i in range(len(newObj)): + newObj[i] += other + return newObj + + def __sub__(self, other): + """ Plus every element of G1DList by "other" """ + newObj = self.clone() + for i in range(len(newObj)): + newObj[i] -= other + return newObj + + def __repr__(self): + """ Return a string representation of Genome """ + ret = GenomeBase.__repr__(self) + ret += "- G1DList\n" + ret += "\tList size:\t %s\n" % (self.getListSize(),) + ret += "\tList:\t\t %s\n\n" % (self.genomeList,) + return ret + + def copy(self, g): + """ Copy genome to 'g' + + Example: + >>> genome_origin.copy(genome_destination) + + :param g: the destination G1DList instance + + """ + GenomeBase.copy(self, g) + G1DBase.copy(self, g) + + def clone(self): + """ Return a new instace copy of the genome + + :rtype: the G1DList clone instance + + """ + newcopy = G1DList(self.genomeSize, True) + self.copy(newcopy) + return newcopy diff --git a/G2DBinaryString.py b/G2DBinaryString.py new file mode 100644 index 0000000..e788985 --- /dev/null +++ b/G2DBinaryString.py @@ -0,0 +1,197 @@ +""" +:mod:`G2DBinaryString` -- the classical binary string chromosome +===================================================================== + +This representation is a 2D Binary String, the string looks like +this matrix: + +00101101010 +00100011010 +00101101010 +10100101000 + +Default Parameters +------------------------------------------------------------- + +*Initializator* + + :func:`Initializators.G2DBinaryStringInitializator` + + The Binatry String Initializator for G2DBinaryString + +*Mutator* + + :func:`Mutators.G2DBinaryStringMutatorFlip` + + The Flip Mutator for G2DBinaryString + +*Crossover* + + :func:`Crossovers.G2DBinaryStringXSinglePoint` + + The Single Point Crossover for G2DBinaryString + +.. versionadded:: 0.6 + Added the module :mod:`G2DBinaryString` + +Class +------------------------------------------------------------- +""" +from future.builtins import range + +from .GenomeBase import GenomeBase +from . import Consts +from . import Util + + +class G2DBinaryString(GenomeBase): + """ G3DBinaryString Class - The 2D Binary String chromosome + + Inheritance diagram for :class:`G2DBinaryString.G2DBinaryString`: + + .. inheritance-diagram:: G2DBinaryString.G2DBinaryString + + Example: + >>> genome = G2DBinaryString.G2DBinaryString(10, 12) + + + :param height: the number of rows + :param width: the number of columns + + """ + __slots__ = ["height", "width", "genomeString"] + + def __init__(self, height, width): + """ The initializator of G2DBinaryString representation, + height and width must be specified """ + super(G2DBinaryString, self).__init__() + self.height = height + self.width = width + + self.genomeString = [None] * height + for i in range(height): + self.genomeString[i] = [None] * width + + self.initializator.set(Consts.CDefG2DBinaryStringInit) + self.mutator.set(Consts.CDefG2DBinaryStringMutator) + self.crossover.set(Consts.CDefG2DBinaryStringCrossover) + + def __eq__(self, other): + """ Compares one chromosome with another """ + cond1 = (self.genomeString == other.genomeString) + cond2 = (self.height == other.height) + cond3 = (self.width == other.width) + return True if cond1 and cond2 and cond3 else False + + def getItem(self, x, y): + """ Return the specified gene of List + + Example: + >>> genome.getItem(3, 1) + 0 + + :param x: the x index, the column + :param y: the y index, the row + :rtype: the item at x,y position + + """ + return self.genomeString[x][y] + + def setItem(self, x, y, value): + """ Set the specified gene of List + + Example: + >>> genome.setItem(3, 1, 0) + + :param x: the x index, the column + :param y: the y index, the row + :param value: the value (integers 0 or 1) + + """ + if value not in [0, 1]: + Util.raiseException("The item value must be 0 or 1 in the G2DBinaryString chromosome", ValueError) + self.genomeString[x][y] = value + + def __getitem__(self, key): + """ Return the specified gene of List """ + return self.genomeString[key] + + def __iter__(self): + """ Iterator support to the list """ + return iter(self.genomeString) + + def getHeight(self): + """ Return the height (lines) of the List """ + return self.height + + def getWidth(self): + """ Return the width (lines) of the List """ + return self.width + + def getSize(self): + """ Returns a tuple (height, widht) + + Example: + >>> genome.getSize() + (3, 2) + + """ + return self.getHeight(), self.getWidth() + + def __repr__(self): + """ Return a string representation of Genome """ + ret = GenomeBase.__repr__(self) + ret += "- G2DBinaryString\n" + ret += "\tList size:\t %s\n" % (self.getSize(),) + ret += "\tList:\n" + for line in self.genomeString: + ret += "\t\t\t" + for item in line: + ret += "[%s] " % (item) + ret += "\n" + ret += "\n" + return ret + + def resumeString(self): + """ Returns a resumed string representation of the Genome + + """ + ret = "" + for line in self.genomeString: + for item in line: + ret += "[%s] " % (item) + ret += "\n" + return ret + + def clearString(self): + """ Remove all genes from Genome """ + del self.genomeString[:] + + self.genomeString = [None] * self.height + for i in range(self.height): + self.genomeString[i] = [None] * self.width + + def copy(self, g): + """ Copy genome to 'g' + + Example: + >>> genome_origin.copy(genome_destination) + + :param g: the destination G2DBinaryString instance + + """ + GenomeBase.copy(self, g) + g.height = self.height + g.width = self.width + for i in range(self.height): + g.genomeString[i] = self.genomeString[i][:] + + def clone(self): + """ Return a new instace copy of the genome + + :rtype: the G2DBinaryString clone instance + + """ + newcopy = G2DBinaryString(self.height, self.width) + self.copy(newcopy) + return newcopy diff --git a/G2DList.py b/G2DList.py new file mode 100644 index 0000000..4d893aa --- /dev/null +++ b/G2DList.py @@ -0,0 +1,229 @@ +""" +:mod:`G2DList` -- the 2D list chromosome +================================================================ + +This is the 2D List representation, this list can carry real numbers or +integers or any kind of object, by default, we have genetic operators +for integer and real lists, which can be found on the respective modules. +This chromosome class extends the :class:`GenomeBase.GenomeBase`. + +Default Parameters +------------------------------------------------------------- + +*Initializator* + + :func:`Initializators.G2DListInitializatorInteger` + + The Integer Initializator for G2DList + +*Mutator* + + :func:`Mutators.G2DListMutatorSwap` + + The Swap Mutator for G2DList + +*Crossover* + + :func:`Crossovers.G2DListCrossoverUniform` + + The Uniform Crossover for G2DList + + +Class +------------------------------------------------------------- + + +""" +from future.builtins import range + +from .GenomeBase import GenomeBase +from . import Consts + + +class G2DList(GenomeBase): + """ G2DList Class - The 2D List chromosome representation + + Inheritance diagram for :class:`G2DList.G2DList`: + + .. inheritance-diagram:: G2DList.G2DList + + **Examples** + + The instantiation + >>> genome = G2DList.G2DList(10, 10) + + Compare + >>> genome2 = genome1.clone() + >>> genome2 == genome1 + True + + Iteration + >>> for row in genome: + >>> print row + [1, 3, 4, 1] + [7, 5, 3, 4] + [9, 0, 1, 2] + + Size, slice, get/set, append + >>> len(genome) + 3 + >>> genome + (...) + [1, 3, 4, 1] + [7, 5, 3, 4] + [9, 0, 1, 2] + >>> genome[1][2] + 3 + >>> genome[1] = [666, 666, 666, 666] + >>> genome + (...) + [1, 3, 4, 1] + [666, 666, 666, 666] + [9, 0, 1, 2] + >>> genome[1][1] = 2 + (...) + + :param height: the number of rows + :param width: the number of columns + + """ + + __slots__ = ["height", "width", "genomeList"] + + def __init__(self, height, width, cloning=False): + """ The initializator of G2DList representation, + height and width must be specified """ + super(G2DList, self).__init__() + self.height = height + self.width = width + + self.genomeList = [None] * height + for i in range(height): + self.genomeList[i] = [None] * width + + if not cloning: + self.initializator.set(Consts.CDefG2DListInit) + self.mutator.set(Consts.CDefG2DListMutator) + self.crossover.set(Consts.CDefG2DListCrossover) + + def __eq__(self, other): + """ Compares one chromosome with another """ + cond1 = (self.genomeList == other.genomeList) + cond2 = (self.height == other.height) + cond3 = (self.width == other.width) + return True if cond1 and cond2 and cond3 else False + + def getItem(self, x, y): + """ Return the specified gene of List + + Example: + >>> genome.getItem(3, 1) + 666 + >>> genome[3][1] + + :param x: the x index, the column + :param y: the y index, the row + :rtype: the item at x,y position + + """ + return self.genomeList[x][y] + + def setItem(self, x, y, value): + """ Set the specified gene of List + + Example: + >>> genome.setItem(3, 1, 666) + >>> genome[3][1] = 666 + + :param x: the x index, the column + :param y: the y index, the row + :param value: the value + + """ + self.genomeList[x][y] = value + + def __getitem__(self, key): + """ Return the specified gene of List """ + return self.genomeList[key] + + def __iter__(self): + """ Iterator support to the list """ + return iter(self.genomeList) + + def getHeight(self): + """ Return the height (lines) of the List """ + return self.height + + def getWidth(self): + """ Return the width (lines) of the List """ + return self.width + + def getSize(self): + """ Returns a tuple (height, widht) + + Example: + >>> genome.getSize() + (3, 2) + + """ + return self.getHeight(), self.getWidth() + + def __repr__(self): + """ Return a string representation of Genome """ + ret = GenomeBase.__repr__(self) + ret += "- G2DList\n" + ret += "\tList size:\t %s\n" % (self.getSize(),) + ret += "\tList:\n" + for line in self.genomeList: + ret += "\t\t\t" + for item in line: + ret += "[%s] " % (item) + ret += "\n" + ret += "\n" + return ret + + def resumeString(self): + """ Returns a resumed string representation of the Genome + + .. versionadded:: 0.6 + The *resumeString* method. + """ + ret = "" + for line in self.genomeList: + for item in line: + ret += "[%s] " % item + ret += "\n" + return ret + + def clearList(self): + """ Remove all genes from Genome """ + del self.genomeList[:] + + self.genomeList = [None] * self.height + for i in range(self.height): + self.genomeList[i] = [None] * self.width + + def copy(self, g): + """ Copy genome to 'g' + + Example: + >>> genome_origin.copy(genome_destination) + + :param g: the destination G2DList instance + + """ + GenomeBase.copy(self, g) + g.height = self.height + g.width = self.width + for i in range(self.height): + g.genomeList[i] = self.genomeList[i][:] + + def clone(self): + """ Return a new instace copy of the genome + + :rtype: the G2DList clone instance + + """ + newcopy = G2DList(self.height, self.width, True) + self.copy(newcopy) + return newcopy diff --git a/GAllele.py b/GAllele.py new file mode 100644 index 0000000..aa03b12 --- /dev/null +++ b/GAllele.py @@ -0,0 +1,286 @@ +""" + +:mod:`GAllele` -- the genome alleles module +=========================================================== + +In this module, there are the :class:`GAllele.GAlleles` class (which is the +class that holds the allele types) and all the +allele types to use with the supported chromosomes. + +""" +from future.builtins import range + +import random +from . import Consts +from . import Util + +class GAlleles(object): + """ GAlleles Class - The set of alleles + + Example: + >>> alleles = GAlleles() + >>> choices = [1,2,3,4] + >>> lst = GAlleleList(choices) + >>> alleles.add(lst) + >>> alleles[0].getRandomAllele() in lst + True + + :param allele_list: the list of alleles + :param homogeneous: if True, all the alleles will be use only the first added + + """ + + def __init__(self, allele_list=None, homogeneous=False): + """ The constructor of GAlleles class """ + self.allele_list = [] + if allele_list is not None: + self.allele_list.extend(allele_list) + self.homogeneous = homogeneous + + def __iadd__(self, allele): + """ To add more alleles using the += operator + + .. versionadded:: 0.6 + The __iadd__ method. + """ + self.add(allele) + return self + + def add(self, allele): + """ Appends one allele to the alleles list + + :param allele: allele to be added + + """ + self.allele_list.append(allele) + + def __getslice__(self, a, b): + """ Returns the slice part of alleles list """ + return self.allele_list[a:b] + + def __getitem__(self, index): + """ Returns the index allele of the alleles list """ + if self.homogeneous: + return self.allele_list[0] + try: + val = self.allele_list[index] + except IndexError: + Util.raiseException( + """An error was occurred while finding allele for the %d position of chromosome. + You may consider use the 'homogeneous' parameter of the GAlleles class. + """ % (index,)) + return val + + def __setitem__(self, index, value): + """ Sets the index allele of the alleles list """ + if self.homogeneous: + self.allele_list[0] = value + self.allele_list[index] = value + + def __iter__(self): + """ Return the list iterator """ + if self.homogeneous: + oneList = [self.allele_list[0]] + return iter(oneList) + return iter(self.allele_list) + + def __len__(self): + """ Returns the length of the alleles list """ + if self.homogeneous: + return 1 + return len(self.allele_list) + + def __repr__(self): + """ Return a string representation of the allele """ + ret = "- GAlleles\n" + ret += "\tHomogeneous:\t %s\n" % (self.homogeneous,) + ret += "\tList size:\t %s\n" % (len(self),) + ret += "\tAlleles:\n\n" + if self.homogeneous: + ret += "Allele for position 0:\n" + ret += self.allele_list[0].__repr__() + else: + for i in range(len(self)): + ret += "Allele for position %d:\n" % (i,) + ret += self.allele_list[i].__repr__() + return ret + + +class GAlleleList(object): + """ GAlleleList Class - The list allele type + + Example: + >>> alleles = GAlleles() + >>> choices = [1,2,3,4] + >>> lst = GAlleleList(choices) + >>> alleles.add(lst) + >>> alleles[0].getRandomAllele() in lst + True + + """ + + def __init__(self, options=None): + """ The constructor of GAlleleList class """ + self.options = [] + if options is not None: + self.options.extend(options) + + def clear(self): + """ Removes all the allele options from the list """ + del self.options[:] + + def getRandomAllele(self): + """ Returns one random choice from the options list """ + return random.choice(self.options) + + def add(self, option): + """ Appends one option to the options list + + :param option: option to be added in the list + + """ + self.options.append(option) + + def __getslice__(self, a, b): + """ Returns the slice part of options """ + return self.options[a:b] + + def __getitem__(self, index): + """ Returns the index option from the options list """ + return self.options[index] + + def __setitem__(self, index, value): + """ Sets the index option of the list """ + self.options[index] = value + + def __iter__(self): + """ Return the list iterator """ + return iter(self.options) + + def __len__(self): + """ Returns the length of the options list """ + return len(self.options) + + def remove(self, option): + """ Removes the option from list + + :param option: remove the option from the list + + """ + self.options.remove(option) + + def __repr__(self): + """ Return a string representation of the allele """ + ret = "- GAlleleList\n" + ret += "\tList size:\t %s\n" % (len(self),) + ret += "\tAllele Options:\t %s\n\n" % (self.options,) + return ret + +class GAlleleRange(object): + """ GAlleleRange Class - The range allele type + + Example: + >>> ranges = GAlleleRange(0,100) + >>> ranges.getRandomAllele() >= 0 and ranges.getRandomAllele() <= 100 + True + + :param begin: the begin of the range + :param end: the end of the range + :param real: if True, the range will be of real values + + """ + + def __init__(self, begin=Consts.CDefRangeMin, + end=Consts.CDefRangeMax, real=False): + """ The constructor of GAlleleRange class """ + self.beginEnd = [(begin, end)] + self.real = real + self.minimum = None + self.maximum = None + self.__processMinMax() + + def __processMinMax(self): + """ Process the mininum and maximum of the Allele """ + self.minimum = min([x for x, y in self.beginEnd]) + self.maximum = max([y for x, y in self.beginEnd]) + + def add(self, begin, end): + """ Add a new range + + :param begin: the begin of range + :param end: the end of the range + + """ + if begin > end: + Util.raiseException('Wrong value, the end of the range (%s) is greater than the begin (%s) !' % (end, begin), ValueError) + self.beginEnd.append((begin, end)) + self.__processMinMax() + + def __getitem__(self, index): + return self.beginEnd[index] + + def __setitem__(self, index, value): + if value[0] > value[1]: + Util.raiseException('Wrong value, the end of the range is greater than the begin ! %s' % value, ValueError) + self.beginEnd[index] = value + self.__processMinMax() + + def __iter__(self): + return iter(self.beginEnd) + + def getMaximum(self): + """ Return the maximum of all the ranges + + :rtype: the maximum value + """ + return self.maximum + + def getMinimum(self): + """ Return the minimum of all the ranges + + :rtype: the minimum value + """ + return self.minimum + + def clear(self): + """ Removes all ranges """ + del self.beginEnd[:] + self.minimum = None + self.maximum = None + + def getRandomAllele(self): + """ Returns one random choice between the range """ + rand_func = random.uniform if self.real else random.randint + + if len(self.beginEnd) <= 1: + choice = 0 + else: + choice = random.randint(0, len(self.beginEnd) - 1) + return rand_func(self.beginEnd[choice][0], self.beginEnd[choice][1]) + + def setReal(self, flag=True): + """ Pass in True if the range is real or False if integer + + :param flag: True or False + + """ + self.real = flag + + def getReal(self): + """ Returns True if the range is real or False if it is integer """ + return self.real + + def __len__(self): + """ Returns the ranges in the allele """ + return len(self.beginEnd) + + def __repr__(self): + """ Return a string representation of the allele """ + ret = "- GAlleleRange\n" + ret += "\tReal:\t\t %s\n" % (self.real,) + ret += "\tRanges Count:\t %s\n" % (len(self),) + ret += "\tRange List:\n" + for beg, end in self.beginEnd: + ret += "\t\t\t Range from [%s] to [%s]\n" % (beg, end) + ret += "\n" + return ret diff --git a/GPopulation.py b/GPopulation.py new file mode 100644 index 0000000..a57d02b --- /dev/null +++ b/GPopulation.py @@ -0,0 +1,505 @@ +""" +:mod:`GPopulation` -- the population module +================================================================ + +This module contains the :class:`GPopulation.GPopulation` class, which is reponsible +to keep the population and the statistics. + +Default Parameters +------------------------------------------------------------- + +*Sort Type* + + >>> Consts.sortType["scaled"] + + The scaled sort type + +*Minimax* + + >>> Consts.minimaxType["maximize"] + + Maximize the evaluation function + +*Scale Method* + + :func:`Scaling.LinearScaling` + + The Linear Scaling scheme + +Class +------------------------------------------------------------- + + +""" + +from future.builtins import range +from functools import cmp_to_key + +from . import Consts +from . import Util +from .FunctionSlot import FunctionSlot +from .Statistics import Statistics +from math import sqrt as math_sqrt +import logging + +try: + from multiprocessing import cpu_count, Pool + CPU_COUNT = cpu_count() + MULTI_PROCESSING = True if CPU_COUNT > 1 else False + logging.debug("You have %d CPU cores, so the multiprocessing state is %s", CPU_COUNT, MULTI_PROCESSING) +except ImportError: + MULTI_PROCESSING = False + logging.debug("You don't have multiprocessing support for your Python version !") + + +def key_raw_score(individual): + """ A key function to return raw score + + :param individual: the individual instance + :rtype: the individual raw score + + .. note:: this function is used by the max()/min() python functions + + """ + return individual.score + +def key_fitness_score(individual): + """ A key function to return fitness score, used by max()/min() + + :param individual: the individual instance + :rtype: the individual fitness score + + .. note:: this function is used by the max()/min() python functions + + """ + return individual.fitness + + +def multiprocessing_eval(ind): + """ Internal used by the multiprocessing """ + ind.evaluate() + return ind.score + +def multiprocessing_eval_full(ind): + """ Internal used by the multiprocessing (full copy)""" + ind.evaluate() + return ind + + +class GPopulation(object): + """ GPopulation Class - The container for the population + + **Examples** + Get the population from the :class:`GSimpleGA.GSimpleGA` (GA Engine) instance + >>> pop = ga_engine.getPopulation() + + Get the best fitness individual + >>> bestIndividual = pop.bestFitness() + + Get the best raw individual + >>> bestIndividual = pop.bestRaw() + + Get the statistics from the :class:`Statistics.Statistics` instance + >>> stats = pop.getStatistics() + >>> print stats["rawMax"] + 10.4 + + Iterate, get/set individuals + >>> for ind in pop: + >>> print ind + (...) + + >>> for i in range(len(pop)): + >>> print pop[i] + (...) + + >>> pop[10] = newGenome + >>> pop[10].fitness + 12.5 + + :param genome: the :term:`Sample genome`, or a GPopulation object, when cloning. + + """ + + def __init__(self, genome): + """ The GPopulation Class creator """ + + if isinstance(genome, GPopulation): + self.oneSelfGenome = genome.oneSelfGenome + self.internalPop = [] + self.internalPopRaw = [] + self.popSize = genome.popSize + self.sortType = genome.sortType + self.sorted = False + self.minimax = genome.minimax + self.scaleMethod = genome.scaleMethod + self.allSlots = [self.scaleMethod] + + self.internalParams = genome.internalParams + self.multiProcessing = genome.multiProcessing + + self.statted = False + self.stats = Statistics() + return + + logging.debug("New population instance, %s class genomes.", genome.__class__.__name__) + self.oneSelfGenome = genome + self.internalPop = [] + self.internalPopRaw = [] + self.popSize = 0 + self.sortType = Consts.CDefPopSortType + self.sorted = False + self.minimax = Consts.CDefPopMinimax + self.scaleMethod = FunctionSlot("Scale Method") + self.scaleMethod.set(Consts.CDefPopScale) + self.allSlots = [self.scaleMethod] + + self.internalParams = {} + self.multiProcessing = (False, False, None) + + # Statistics + self.statted = False + self.stats = Statistics() + + def setMultiProcessing(self, flag=True, full_copy=False, max_processes=None): + """ Sets the flag to enable/disable the use of python multiprocessing module. + Use this option when you have more than one core on your CPU and when your + evaluation function is very slow. + The parameter "full_copy" defines where the individual data should be copied back + after the evaluation or not. This parameter is useful when you change the + individual in the evaluation function. + + :param flag: True (default) or False + :param full_copy: True or False (default) + :param max_processes: None (default) or an integer value + + .. warning:: Use this option only when your evaluation function is slow, se you + will get a good tradeoff between the process communication speed and the + parallel evaluation. + + .. versionadded:: 0.6 + The `setMultiProcessing` method. + + """ + self.multiProcessing = (flag, full_copy, max_processes) + + def setMinimax(self, minimax): + """ Sets the population minimax + + Example: + >>> pop.setMinimax(Consts.minimaxType["maximize"]) + + :param minimax: the minimax type + + """ + self.minimax = minimax + + def __repr__(self): + """ Returns the string representation of the population """ + ret = "- GPopulation\n" + ret += "\tPopulation Size:\t %d\n" % (self.popSize,) + ret += "\tSort Type:\t\t %s\n" % (list(Consts.sortType.keys())[list(Consts.sortType.values()).index(self.sortType)].capitalize(),) + ret += "\tMinimax Type:\t\t %s\n" % (list(Consts.minimaxType.keys())[list(Consts.minimaxType.values()).index(self.minimax)].capitalize(),) + for slot in self.allSlots: + ret += "\t" + slot.__repr__() + ret += "\n" + ret += self.stats.__repr__() + return ret + + def __len__(self): + """ Return the length of population """ + return len(self.internalPop) + + def __getitem__(self, key): + """ Returns the specified individual from population """ + return self.internalPop[key] + + def __iter__(self): + """ Returns the iterator of the population """ + return iter(self.internalPop) + + def __setitem__(self, key, value): + """ Set an individual of population """ + self.internalPop[key] = value + self.clearFlags() + + def clearFlags(self): + """ Clear the sorted and statted internal flags """ + self.sorted = False + self.statted = False + + def getStatistics(self): + """ Return a Statistics class for statistics + + :rtype: the :class:`Statistics.Statistics` instance + + """ + self.statistics() + return self.stats + + def statistics(self): + """ Do statistical analysis of population and set 'statted' to True """ + if self.statted: + return + logging.debug("Running statistical calculations") + raw_sum = 0 + + len_pop = len(self) + for ind in range(len_pop): + raw_sum += self[ind].score + + self.stats["rawMax"] = max(self, key=key_raw_score).score + self.stats["rawMin"] = min(self, key=key_raw_score).score + self.stats["rawAve"] = raw_sum / float(len_pop) + + tmpvar = 0.0 + for ind in range(len_pop): + s = self[ind].score - self.stats["rawAve"] + s *= s + tmpvar += s + + tmpvar /= float((len(self) - 1)) + try: + self.stats["rawDev"] = math_sqrt(tmpvar) + except: + self.stats["rawDev"] = 0.0 + + self.stats["rawVar"] = tmpvar + + self.statted = True + + def bestFitness(self, index=0): + """ Return the best scaled fitness individual of population + + :param index: the *index* best individual + :rtype: the individual + + """ + self.sort() + return self.internalPop[index] + + def worstFitness(self): + """ Return the worst scaled fitness individual of the population + + :rtype: the individual + + """ + self.sort() + return self.internalPop[-1] + + def bestRaw(self, index=0): + """ Return the best raw score individual of population + + :param index: the *index* best raw individual + :rtype: the individual + + .. versionadded:: 0.6 + The parameter `index`. + + """ + if self.sortType == Consts.sortType["raw"]: + return self.internalPop[index] + else: + self.sort() + return self.internalPopRaw[index] + + def worstRaw(self): + """ Return the worst raw score individual of population + + :rtype: the individual + + .. versionadded:: 0.6 + The parameter `index`. + + """ + if self.sortType == Consts.sortType["raw"]: + return self.internalPop[-1] + else: + self.sort() + return self.internalPopRaw[-1] + + def sort(self): + """ Sort the population """ + if self.sorted: + return + rev = (self.minimax == Consts.minimaxType["maximize"]) + + if self.sortType == Consts.sortType["raw"]: + # TODO update to proper python3 sorting + # https://docs.python.org/3.3/howto/sorting.html + self.internalPop.sort( + key=cmp_to_key(Util.cmp_individual_raw), + reverse=rev + ) + else: + self.scale() + self.internalPop.sort( + key=cmp_to_key(Util.cmp_individual_scaled), + reverse=rev + ) + self.internalPopRaw = self.internalPop[:] + self.internalPopRaw.sort( + key=cmp_to_key(Util.cmp_individual_raw), + reverse=rev + ) + + self.sorted = True + + def setPopulationSize(self, size): + """ Set the population size + + :param size: the population size + + """ + self.popSize = size + + def setSortType(self, sort_type): + """ Sets the sort type + + Example: + >>> pop.setSortType(Consts.sortType["scaled"]) + + :param sort_type: the Sort Type + + """ + self.sortType = sort_type + + def create(self, **args): + """ Clone the example genome to fill the population """ + self.minimax = args["minimax"] + self.internalPop = [self.oneSelfGenome.clone() for i in range(self.popSize)] + self.clearFlags() + + def __findIndividual(self, individual, end): + for i in range(end): + if individual.compare(self.internalPop[i]) == 0: + return True + + def initialize(self, **args): + """ Initialize all individuals of population, + this calls the initialize() of individuals """ + logging.debug("Initializing the population") + + if self.oneSelfGenome.getParam("full_diversity", True) and hasattr(self.oneSelfGenome, "compare"): + for i in range(len(self.internalPop)): + curr = self.internalPop[i] + curr.initialize(**args) + while self.__findIndividual(curr, i): + curr.initialize(**args) + else: + for gen in self.internalPop: + gen.initialize(**args) + self.clearFlags() + + def evaluate(self, **args): + """ Evaluate all individuals in population, calls the evaluate() method of individuals + + :param args: this params are passed to the evaluation function + + """ + # We have multiprocessing + if self.multiProcessing[0] and MULTI_PROCESSING: + logging.debug("Evaluating the population using the multiprocessing method") + proc_pool = Pool(processes=self.multiProcessing[2]) + + # Multiprocessing full_copy parameter + if self.multiProcessing[1]: + results = proc_pool.map(multiprocessing_eval_full, self.internalPop) + proc_pool.close() + proc_pool.join() + for i in range(len(self.internalPop)): + self.internalPop[i] = results[i] + else: + results = proc_pool.map(multiprocessing_eval, self.internalPop) + proc_pool.close() + proc_pool.join() + for individual, score in zip(self.internalPop, results): + individual.score = score + else: + for ind in self.internalPop: + ind.evaluate(**args) + + self.clearFlags() + + def scale(self, **args): + """ Scale the population using the scaling method + + :param args: this parameter is passed to the scale method + + """ + for it in self.scaleMethod.applyFunctions(self, **args): + pass + + fit_sum = 0 + for ind in range(len(self)): + fit_sum += self[ind].fitness + + self.stats["fitMax"] = max(self, key=key_fitness_score).fitness + self.stats["fitMin"] = min(self, key=key_fitness_score).fitness + self.stats["fitAve"] = fit_sum / float(len(self)) + + self.sorted = False + + def printStats(self): + """ Print statistics of the current population """ + message = "" + if self.sortType == Consts.sortType["scaled"]: + message = "Max/Min/Avg Fitness(Raw) [%(fitMax).2f(%(rawMax).2f)/%(fitMin).2f(%(rawMin).2f)/%(fitAve).2f(%(rawAve).2f)]" % self.stats + else: + message = "Max/Min/Avg Raw [%(rawMax).2f/%(rawMin).2f/%(rawAve).2f]" % self.stats + logging.info(message) + print(message) + return message + + def copy(self, pop): + """ Copy current population to 'pop' + + :param pop: the destination population + + .. warning:: this method do not copy the individuals, only the population logic + + """ + pop.popSize = self.popSize + pop.sortType = self.sortType + pop.minimax = self.minimax + pop.scaleMethod = self.scaleMethod + pop.internalParams = self.internalParams + pop.multiProcessing = self.multiProcessing + + def getParam(self, key, nvl=None): + """ Gets an internal parameter + + Example: + >>> population.getParam("tournamentPool") + 5 + + :param key: the key of param + :param nvl: if the key doesn't exist, the nvl will be returned + + """ + return self.internalParams.get(key, nvl) + + def setParams(self, **args): + """ Gets an internal parameter + + Example: + >>> population.setParams(tournamentPool=5) + + :param args: parameters to set + + .. versionadded:: 0.6 + The `setParams` method. + """ + self.internalParams.update(args) + + def clear(self): + """ Remove all individuals from population """ + del self.internalPop[:] + del self.internalPopRaw[:] + self.clearFlags() + + def clone(self): + """ Return a brand-new cloned population """ + newpop = GPopulation(self.oneSelfGenome) + self.copy(newpop) + return newpop diff --git a/GSimpleGA.py b/GSimpleGA.py new file mode 100644 index 0000000..f64c008 --- /dev/null +++ b/GSimpleGA.py @@ -0,0 +1,893 @@ +""" + +:mod:`GSimpleGA` -- the genetic algorithm by itself +===================================================================== + +This module contains the GA Engine, the GA Engine class is responsible +for all the evolutionary process. It contains the GA Engine related +funtions, like the Termination Criteria functions for convergence analysis, etc. + +Default Parameters +------------------------------------------------------------- + +*Number of Generations* + + Default is 100 generations + +*Mutation Rate* + + Default is 0.02, which represents 0.2% + +*Crossover Rate* + + Default is 0.9, which represents 90% + +*Elitism Replacement* + + Default is 1 individual + +*Population Size* + + Default is 80 individuals + +*Minimax* + + >>> Consts.minimaxType["maximize"] + + Maximize the evaluation function + +*DB Adapter* + + Default is **None** + +*Migration Adapter* + + Default is **None** + +*Interactive Mode* + + Default is **True** + +*Selector (Selection Method)* + + :func:`Selectors.GRankSelector` + + The Rank Selection method + +Class +------------------------------------------------------------- + +""" + +from future.builtins import range + +import random +import logging +from time import time +from sys import platform as sys_platform +from sys import stdout as sys_stdout +import code + +from .GPopulation import GPopulation +from .FunctionSlot import FunctionSlot +from .GenomeBase import GenomeBase +from .DBAdapters import DBBaseAdapter +from . import Consts +from . import Util +import pyevolve + +# Platform dependant code for the Interactive Mode +if sys_platform[:3] == "win": + import msvcrt + + +def RawScoreCriteria(ga_engine): + """ Terminate the evolution using the **bestrawscore** and **rounddecimal** + parameter obtained from the individual + + Example: + >>> genome.setParams(bestrawscore=0.00, rounddecimal=2) + (...) + >>> ga_engine.terminationCriteria.set(GSimpleGA.RawScoreCriteria) + + """ + ind = ga_engine.bestIndividual() + bestRawScore = ind.getParam("bestrawscore") + roundDecimal = ind.getParam("rounddecimal") + + if bestRawScore is None: + Util.raiseException("you must specify the bestrawscore parameter", ValueError) + + if ga_engine.getMinimax() == Consts.minimaxType["maximize"]: + if roundDecimal is not None: + return round(bestRawScore, roundDecimal) <= round(ind.score, roundDecimal) + else: + return bestRawScore <= ind.score + else: + if roundDecimal is not None: + return round(bestRawScore, roundDecimal) >= round(ind.score, roundDecimal) + else: + return bestRawScore >= ind.score + + +def ConvergenceCriteria(ga_engine): + """ Terminate the evolution when the population have converged + + Example: + >>> ga_engine.terminationCriteria.set(GSimpleGA.ConvergenceCriteria) + + """ + pop = ga_engine.getPopulation() + return pop[0] == pop[len(pop) - 1] + + +def RawStatsCriteria(ga_engine): + """ Terminate the evolution based on the raw stats + + Example: + >>> ga_engine.terminationCriteria.set(GSimpleGA.RawStatsCriteria) + + """ + stats = ga_engine.getStatistics() + if stats["rawMax"] == stats["rawMin"]: + if stats["rawAve"] == stats["rawMax"]: + return True + return False + + +def FitnessStatsCriteria(ga_engine): + """ Terminate the evoltion based on the fitness stats + + Example: + >>> ga_engine.terminationCriteria.set(GSimpleGA.FitnessStatsCriteria) + + + """ + stats = ga_engine.getStatistics() + if stats["fitMax"] == stats["fitMin"]: + if stats["fitAve"] == stats["fitMax"]: + return True + return False + + +class GSimpleGA(object): + """ GA Engine Class - The Genetic Algorithm Core + + Example: + >>> ga = GSimpleGA.GSimpleGA(genome) + >>> ga.selector.set(Selectors.GRouletteWheel) + >>> ga.setGenerations(120) + >>> ga.terminationCriteria.set(GSimpleGA.ConvergenceCriteria) + + :param genome: the :term:`Sample Genome` + :param interactiveMode: this flag enables the Interactive Mode, the default is True + :param seed: the random seed value + + .. note:: if you use the same random seed, all the runs of algorithm will be the same + + """ + + selector = None + """ This is the function slot for the selection method + if you want to change the default selector, you must do this: :: + + ga_engine.selector.set(Selectors.GRouletteWheel) """ + + stepCallback = None + """ This is the :term:`step callback function` slot, + if you want to set the function, you must do this: :: + + def your_func(ga_engine): + # Here you have access to the GA Engine + return False + + ga_engine.stepCallback.set(your_func) + + now *"your_func"* will be called every generation. + When this function returns True, the GA Engine will stop the evolution and show + a warning, if False, the evolution continues. + """ + + terminationCriteria = None + """ This is the termination criteria slot, if you want to set one + termination criteria, you must do this: :: + + ga_engine.terminationCriteria.set(GSimpleGA.ConvergenceCriteria) + + Now, when you run your GA, it will stop when the population converges. + + There are those termination criteria functions: :func:`GSimpleGA.RawScoreCriteria`, + :func:`GSimpleGA.ConvergenceCriteria`, :func:`GSimpleGA.RawStatsCriteria`, :func:`GSimpleGA.FitnessStatsCriteria` + + But you can create your own termination function, this function receives + one parameter which is the GA Engine, follows an example: :: + + def ConvergenceCriteria(ga_engine): + pop = ga_engine.getPopulation() + return pop[0] == pop[len(pop)-1] + + When this function returns True, the GA Engine will stop the evolution and show + a warning, if False, the evolution continues, this function is called every + generation. + """ + + def __init__(self, genome, seed=None, interactiveMode=True): + """ Initializator of GSimpleGA """ + if seed: + random.seed(seed) + + if not isinstance(interactiveMode, bool): + Util.raiseException("Interactive Mode option must be True or False", TypeError) + + if not isinstance(genome, GenomeBase): + Util.raiseException("The genome must be a GenomeBase subclass", TypeError) + + self.internalPop = GPopulation(genome) + self.nGenerations = Consts.CDefGAGenerations + self.pMutation = Consts.CDefGAMutationRate + self.pCrossover = Consts.CDefGACrossoverRate + self.nElitismReplacement = Consts.CDefGAElitismReplacement + self.setPopulationSize(Consts.CDefGAPopulationSize) + self.minimax = Consts.minimaxType["maximize"] + self.elitism = True + + # Adapters + self.dbAdapter = None + self.migrationAdapter = None + + self.time_init = None + self.max_time = None + self.interactiveMode = interactiveMode + self.interactiveGen = -1 + self.GPMode = False + + self.selector = FunctionSlot("Selector") + self.stepCallback = FunctionSlot("Generation Step Callback") + self.terminationCriteria = FunctionSlot("Termination Criteria") + self.selector.set(Consts.CDefGASelector) + self.allSlots = (self.selector, self.stepCallback, self.terminationCriteria) + + self.internalParams = {} + + self.currentGeneration = 0 + + # GP Testing + for classes in Consts.CDefGPGenomes: + if isinstance(self.internalPop.oneSelfGenome, classes): + self.setGPMode(True) + break + + logging.debug("A GA Engine was created, nGenerations=%d", self.nGenerations) + + def setGPMode(self, bool_value): + """ Sets the Genetic Programming mode of the GA Engine + + :param bool_value: True or False + """ + self.GPMode = bool_value + + def getGPMode(self): + """ Get the Genetic Programming mode of the GA Engine + + :rtype: True or False + """ + return self.GPMode + + def __call__(self, *args, **kwargs): + """ A method to implement a callable object + + Example: + >>> ga_engine(freq_stats=10) + + .. versionadded:: 0.6 + The callable method. + """ + if kwargs.get("freq_stats", None): + return self.evolve(kwargs.get("freq_stats")) + else: + return self.evolve() + + def setParams(self, **args): + """ Set the internal params + + Example: + >>> ga.setParams(gp_terminals=['x', 'y']) + + + :param args: params to save + + ..versionaddd:: 0.6 + Added the *setParams* method. + """ + self.internalParams.update(args) + + def getParam(self, key, nvl=None): + """ Gets an internal parameter + + Example: + >>> ga.getParam("gp_terminals") + ['x', 'y'] + + :param key: the key of param + :param nvl: if the key doesn't exist, the nvl will be returned + + ..versionaddd:: 0.6 + Added the *getParam* method. + """ + return self.internalParams.get(key, nvl) + + def setInteractiveGeneration(self, generation): + """ Sets the generation in which the GA must enter in the + Interactive Mode + + :param generation: the generation number, use "-1" to disable + + .. versionadded::0.6 + The *setInteractiveGeneration* method. + """ + if generation < -1: + Util.raiseException("Generation must be >= -1", ValueError) + self.interactiveGen = generation + + def getInteractiveGeneration(self): + """ returns the generation in which the GA must enter in the + Interactive Mode + + :rtype: the generation number or -1 if not set + + .. versionadded::0.6 + The *getInteractiveGeneration* method. + """ + return self.interactiveGen + + def setElitismReplacement(self, numreplace): + """ Set the number of best individuals to copy to the next generation on the elitism + + :param numreplace: the number of individuals + + .. versionadded:: 0.6 + The *setElitismReplacement* method. + + """ + if numreplace < 1: + Util.raiseException("Replacement number must be >= 1", ValueError) + self.nElitismReplacement = numreplace + + def setInteractiveMode(self, flag=True): + """ Enable/disable the interactive mode + + :param flag: True or False + + .. versionadded: 0.6 + The *setInteractiveMode* method. + + """ + if not isinstance(flag, bool): + Util.raiseException("Interactive Mode option must be True or False", TypeError) + self.interactiveMode = flag + + def __repr__(self): + """ The string representation of the GA Engine """ + minimax_type = list(Consts.minimaxType.keys())[ + list(Consts.minimaxType.values()).index(self.minimax) + ] + ret = "- GSimpleGA\n" + ret += "\tGP Mode:\t\t %s\n" % self.getGPMode() + ret += "\tPopulation Size:\t %d\n" % self.internalPop.popSize + ret += "\tGenerations:\t\t %d\n" % self.nGenerations + ret += "\tCurrent Generation:\t %d\n" % self.currentGeneration + ret += "\tMutation Rate:\t\t %.2f\n" % self.pMutation + ret += "\tCrossover Rate:\t\t %.2f\n" % self.pCrossover + ret += "\tMinimax Type:\t\t %s\n" % minimax_type.capitalize() + ret += "\tElitism:\t\t %s\n" % self.elitism + ret += "\tElitism Replacement:\t %d\n" % self.nElitismReplacement + ret += "\tDB Adapter:\t\t %s\n" % self.dbAdapter + for slot in self.allSlots: + ret += "\t" + slot.__repr__() + ret += "\n" + return ret + + def setMultiProcessing(self, flag=True, full_copy=False, max_processes=None): + """ Sets the flag to enable/disable the use of python multiprocessing module. + Use this option when you have more than one core on your CPU and when your + evaluation function is very slow. + + Pyevolve will automaticly check if your Python version has **multiprocessing** + support and if you have more than one single CPU core. If you don't have support + or have just only one core, Pyevolve will not use the **multiprocessing** + feature. + + Pyevolve uses the **multiprocessing** to execute the evaluation function over + the individuals, so the use of this feature will make sense if you have a + truly slow evaluation function (which is commom in GAs). + + The parameter "full_copy" defines where the individual data should be copied back + after the evaluation or not. This parameter is useful when you change the + individual in the evaluation function. + + :param flag: True (default) or False + :param full_copy: True or False (default) + :param max_processes: None (default) or an integer value + + .. warning:: Use this option only when your evaluation function is slow, so you'll + get a good tradeoff between the process communication speed and the + parallel evaluation. The use of the **multiprocessing** doesn't means + always a better performance. + + .. note:: To enable the multiprocessing option, you **MUST** add the *__main__* check + on your application, otherwise, it will result in errors. See more on the + `Python Docs `__ + site. + + .. versionadded:: 0.6 + The `setMultiProcessing` method. + + """ + if not isinstance(flag, bool): + Util.raiseException("Multiprocessing option must be True or False", TypeError) + + if not isinstance(full_copy, bool): + Util.raiseException("Multiprocessing 'full_copy' option must be True or False", TypeError) + + self.internalPop.setMultiProcessing(flag, full_copy, max_processes) + + def setMigrationAdapter(self, migration_adapter=None): + """ Sets the Migration Adapter + + .. versionadded:: 0.6 + The `setMigrationAdapter` method. + """ + + self.migrationAdapter = migration_adapter + if self.migrationAdapter is not None: + self.migrationAdapter.setGAEngine(self) + + def setDBAdapter(self, dbadapter=None): + """ Sets the DB Adapter of the GA Engine + + :param dbadapter: one of the :mod:`DBAdapters` classes instance + + .. warning:: the use the of a DB Adapter can reduce the speed performance of the + Genetic Algorithm. + """ + if (dbadapter is not None) and (not isinstance(dbadapter, DBBaseAdapter)): + Util.raiseException("The DB Adapter must be a DBBaseAdapter subclass", TypeError) + self.dbAdapter = dbadapter + + def setPopulationSize(self, size): + """ Sets the population size, calls setPopulationSize() of GPopulation + + :param size: the population size + + .. note:: the population size must be >= 2 + + """ + if size < 2: + Util.raiseException("population size must be >= 2", ValueError) + self.internalPop.setPopulationSize(size) + + def setSortType(self, sort_type): + """ Sets the sort type, Consts.sortType["raw"]/Consts.sortType["scaled"] + + Example: + >>> ga_engine.setSortType(Consts.sortType["scaled"]) + + :param sort_type: the Sort Type + + """ + if sort_type not in list(Consts.sortType.values()): + Util.raiseException("sort type must be a Consts.sortType type", TypeError) + self.internalPop.sortType = sort_type + + def setMutationRate(self, rate): + """ Sets the mutation rate, between 0.0 and 1.0 + + :param rate: the rate, between 0.0 and 1.0 + + """ + if (rate > 1.0) or (rate < 0.0): + Util.raiseException("Mutation rate must be >= 0.0 and <= 1.0", ValueError) + self.pMutation = rate + + def setCrossoverRate(self, rate): + """ Sets the crossover rate, between 0.0 and 1.0 + + :param rate: the rate, between 0.0 and 1.0 + + """ + if (rate > 1.0) or (rate < 0.0): + Util.raiseException("Crossover rate must be >= 0.0 and <= 1.0", ValueError) + self.pCrossover = rate + + def setGenerations(self, num_gens): + """ Sets the number of generations to evolve + + :param num_gens: the number of generations + + """ + if num_gens < 1: + Util.raiseException("Number of generations must be >= 1", ValueError) + self.nGenerations = num_gens + + def getGenerations(self): + """ Return the number of generations to evolve + + :rtype: the number of generations + + .. versionadded:: 0.6 + Added the *getGenerations* method + """ + return self.nGenerations + + def getMinimax(self): + """ Gets the minimize/maximize mode + + :rtype: the Consts.minimaxType type + + """ + return self.minimax + + def setMinimax(self, mtype): + """ Sets the minimize/maximize mode, use Consts.minimaxType + + :param mtype: the minimax mode, from Consts.minimaxType + + """ + if mtype not in list(Consts.minimaxType.values()): + Util.raiseException("Minimax must be maximize or minimize", TypeError) + self.minimax = mtype + + def getCurrentGeneration(self): + """ Gets the current generation + + :rtype: the current generation + + """ + return self.currentGeneration + + def setElitism(self, flag): + """ Sets the elitism option, True or False + + :param flag: True or False + + """ + if not isinstancetype(flag, bool): + Util.raiseException("Elitism option must be True or False", TypeError) + self.elitism = flag + + def getDBAdapter(self): + """ Gets the DB Adapter of the GA Engine + + :rtype: a instance from one of the :mod:`DBAdapters` classes + + """ + return self.dbAdapter + + def setMaxTime(self, seconds): + """ Sets the maximun evolve time of the GA Engine + + :param seconds: maximum time in seconds + """ + self.max_time = seconds + + def getMaxTime(self): + """ Get the maximun evolve time of the GA Engine + + :rtype: True or False + """ + return self.max_time + + def bestIndividual(self): + """ Returns the population best individual + + :rtype: the best individual + + """ + return self.internalPop.bestRaw() + + def worstIndividual(self): + """ Returns the population worst individual + + :rtype: the best individual + + """ + return self.internalPop.worstRaw() + + def __gp_catch_functions(self, prefix): + """ Internally used to catch functions with some specific prefix + as non-terminals of the GP core """ + import __main__ as mod_main + + function_set = {} + + main_dict = mod_main.__dict__ + for obj, addr in list(main_dict.items()): + if obj[0:len(prefix)] == prefix: + try: + op_len = addr.__code__.co_argcount + except: + continue + function_set[obj] = op_len + + if len(function_set) <= 0: + Util.raiseException("No function set found using function prefix '%s' !" % prefix, ValueError) + + self.setParams(gp_function_set=function_set) + + def initialize(self): + """ Initializes the GA Engine. Create and initialize population """ + self.internalPop.create(minimax=self.minimax) + self.internalPop.initialize(ga_engine=self) + logging.debug("The GA Engine was initialized !") + + def getPopulation(self): + """ Return the internal population of GA Engine + + :rtype: the population (:class:`GPopulation.GPopulation`) + + """ + return self.internalPop + + def getStatistics(self): + """ Gets the Statistics class instance of current generation + + :rtype: the statistics instance (:class:`Statistics.Statistics`) + + """ + return self.internalPop.getStatistics() + + def step(self): + """ Just do one step in evolution, one generation """ + newPop = GPopulation(self.internalPop) + logging.debug("Population was cloned.") + + size_iterate = len(self.internalPop) + + # Odd population size + if size_iterate % 2 != 0: + size_iterate -= 1 + + crossover_empty = self.select(popID=self.currentGeneration).crossover.isEmpty() + + for i in range(0, size_iterate, 2): + genomeMom = self.select(popID=self.currentGeneration) + genomeDad = self.select(popID=self.currentGeneration) + + if not crossover_empty and self.pCrossover >= 1.0: + for it in genomeMom.crossover.applyFunctions(mom=genomeMom, dad=genomeDad, count=2): + (sister, brother) = it + else: + if not crossover_empty and Util.randomFlipCoin(self.pCrossover): + for it in genomeMom.crossover.applyFunctions(mom=genomeMom, dad=genomeDad, count=2): + (sister, brother) = it + else: + sister = genomeMom.clone() + brother = genomeDad.clone() + + sister.mutate(pmut=self.pMutation, ga_engine=self) + brother.mutate(pmut=self.pMutation, ga_engine=self) + + newPop.internalPop.append(sister) + newPop.internalPop.append(brother) + + if len(self.internalPop) % 2 != 0: + genomeMom = self.select(popID=self.currentGeneration) + genomeDad = self.select(popID=self.currentGeneration) + + if Util.randomFlipCoin(self.pCrossover): + for it in genomeMom.crossover.applyFunctions(mom=genomeMom, dad=genomeDad, count=1): + (sister, brother) = it + else: + sister = random.choice([genomeMom, genomeDad]) + sister = sister.clone() + sister.mutate(pmut=self.pMutation, ga_engine=self) + + newPop.internalPop.append(sister) + + logging.debug("Evaluating the new created population.") + newPop.evaluate() + + if self.elitism: + logging.debug("Doing elitism.") + if self.getMinimax() == Consts.minimaxType["maximize"]: + for i in range(self.nElitismReplacement): + #re-evaluate before being sure this is the best + self.internalPop.bestRaw(i).evaluate() + if self.internalPop.bestRaw(i).score > newPop.bestRaw(i).score: + newPop[len(newPop) - 1 - i] = self.internalPop.bestRaw(i) + elif self.getMinimax() == Consts.minimaxType["minimize"]: + for i in range(self.nElitismReplacement): + #re-evaluate before being sure this is the best + self.internalPop.bestRaw(i).evaluate() + if self.internalPop.bestRaw(i).score < newPop.bestRaw(i).score: + newPop[len(newPop) - 1 - i] = self.internalPop.bestRaw(i) + + self.internalPop = newPop + self.internalPop.sort() + + logging.debug("The generation %d was finished.", self.currentGeneration) + + self.currentGeneration += 1 + + if self.max_time: + total_time = time() - self.time_init + if total_time > self.max_time: + return True + return self.currentGeneration == self.nGenerations + + def printStats(self): + """ Print generation statistics + + :rtype: the printed statistics as string + + .. versionchanged:: 0.6 + The return of *printStats* method. + """ + percent = self.currentGeneration * 100 / float(self.nGenerations) + message = "Gen. %d (%.2f%%):" % (self.currentGeneration, percent) + logging.info(message) + print(message, end=" ") + sys_stdout.flush() + self.internalPop.statistics() + stat_ret = self.internalPop.printStats() + return message + stat_ret + + def printTimeElapsed(self): + """ Shows the time elapsed since the begin of evolution """ + total_time = time() - self.time_init + print("Total time elapsed: %.3f seconds." % total_time) + return total_time + + def dumpStatsDB(self): + """ Dumps the current statistics to database adapter """ + logging.debug("Dumping stats to the DB Adapter") + self.internalPop.statistics() + self.dbAdapter.insert(self) + + def evolve(self, freq_stats=0): + """ Do all the generations until the termination criteria, accepts + the freq_stats (default is 0) to dump statistics at n-generation + + Example: + >>> ga_engine.evolve(freq_stats=10) + (...) + + :param freq_stats: if greater than 0, the statistics will be + printed every freq_stats generation. + :rtype: returns the best individual of the evolution + + .. versionadded:: 0.6 + the return of the best individual + + """ + + stopFlagCallback = False + stopFlagTerminationCriteria = False + + self.time_init = time() + + logging.debug("Starting the DB Adapter and the Migration Adapter if any") + if self.dbAdapter: + self.dbAdapter.open(self) + if self.migrationAdapter: + self.migrationAdapter.start() + + if self.getGPMode(): + gp_function_prefix = self.getParam("gp_function_prefix") + if gp_function_prefix is not None: + self.__gp_catch_functions(gp_function_prefix) + + self.initialize() + self.internalPop.evaluate() + self.internalPop.sort() + logging.debug("Starting loop over evolutionary algorithm.") + + try: + while True: + if self.migrationAdapter: + logging.debug("Migration adapter: exchange") + self.migrationAdapter.exchange() + self.internalPop.clearFlags() + self.internalPop.sort() + + if not self.stepCallback.isEmpty(): + for it in self.stepCallback.applyFunctions(self): + stopFlagCallback = it + + if not self.terminationCriteria.isEmpty(): + for it in self.terminationCriteria.applyFunctions(self): + stopFlagTerminationCriteria = it + + if freq_stats: + if (self.currentGeneration % freq_stats == 0) or (self.getCurrentGeneration() == 0): + self.printStats() + + if self.dbAdapter: + if self.currentGeneration % self.dbAdapter.getStatsGenFreq() == 0: + self.dumpStatsDB() + + if stopFlagTerminationCriteria: + logging.debug("Evolution stopped by the Termination Criteria !") + if freq_stats: + print("\n\tEvolution stopped by Termination Criteria function !\n") + break + + if stopFlagCallback: + logging.debug("Evolution stopped by Step Callback function !") + if freq_stats: + print("\n\tEvolution stopped by Step Callback function !\n") + break + + if self.interactiveMode: + if sys_platform[:3] == "win": + if msvcrt.kbhit(): + if ord(msvcrt.getch()) == Consts.CDefESCKey: + print("Loading modules for Interactive Mode...", end=" ") + logging.debug( + "Windows Interactive Mode key detected ! generation=%d", + self.getCurrentGeneration() + ) + from pyevolve import Interaction + print(" done !") + interact_banner = "## Pyevolve v.%s - Interactive Mode ##\n" \ + "Press CTRL-Z to quit interactive mode." % (pyevolve.__version__,) + session_locals = { + "ga_engine": self, + "population": self.getPopulation(), + "pyevolve": pyevolve, + "it": Interaction, + } + print() + code.interact(interact_banner, local=session_locals) + + is_interactive_generation = self.getInteractiveGeneration() == self.getCurrentGeneration() + if self.getInteractiveGeneration() >= 0 and is_interactive_generation: + print("Loading modules for Interactive Mode...", end=" ") + logging.debug( + "Manual Interactive Mode key detected ! generation=%d", + self.getCurrentGeneration() + ) + from pyevolve import Interaction + print(" done !") + interact_banner = "## Pyevolve v.%s - Interactive Mode ##" % (pyevolve.__version__,) + session_locals = { + "ga_engine": self, + "population": self.getPopulation(), + "pyevolve": pyevolve, + "it": Interaction + } + print() + code.interact(interact_banner, local=session_locals) + + if self.step(): + break + + except KeyboardInterrupt: + logging.debug("CTRL-C detected, finishing evolution.") + if freq_stats: + print("\n\tA break was detected, you have interrupted the evolution !\n") + + if freq_stats != 0: + self.printStats() + self.printTimeElapsed() + + if self.dbAdapter: + logging.debug("Closing the DB Adapter") + if not (self.currentGeneration % self.dbAdapter.getStatsGenFreq() == 0): + self.dumpStatsDB() + self.dbAdapter.commitAndClose() + + if self.migrationAdapter: + logging.debug("Closing the Migration Adapter") + self.migrationAdapter.stop() + + return self.bestIndividual() + + def select(self, **args): + """ Select one individual from population + + :param args: this parameters will be sent to the selector + + """ + for it in self.selector.applyFunctions(self.internalPop, **args): + return it diff --git a/GTree.py b/GTree.py new file mode 100644 index 0000000..1a48b6d --- /dev/null +++ b/GTree.py @@ -0,0 +1,730 @@ +""" + +:mod:`GTree` and GTreeGP -- the tree chromosomes +============================================================= + +This is the rooted tree representation, this chromosome representation +can carry any data-type. + +Default Parameters +------------------------------------------------------------- + +*Initializator* + + :func:`Initializators.GTreeInitializatorInteger` + + The Integer Initializator for GTree + +*Mutator* + + :func:`Mutators.GTreeMutatorIntegerRange` + + The Integer Range mutator for GTree + +*Crossover* + + :func:`Crossovers.GTreeCrossoverSinglePointStrict` + + The Strict Single Point crossover for GTree + +.. versionadded:: 0.6 + The *GTree* module. + +Classes +------------------------------------------------------------- +""" +from past.builtins import xrange + +import random +from .GenomeBase import GenomeBase, GTreeBase, GTreeNodeBase +from . import Util +from . import Consts + +try: + import pydot_ng as pydot + HAVE_PYDOT = True +except ImportError: + HAVE_PYDOT = False + +################################# +# GTree # +################################# + + +class GTree(GTreeBase): + """ The GTree class - The tree chromosome representation + + Inheritance diagram for :class:`GTree.GTree`: + + .. inheritance-diagram:: GTree.GTree + + :param root_node: the root node of the tree + """ + + def __init__(self, root_node=None): + from pyevolve import Consts + super(GTree, self).__init__(root_node) + self.initializator.set(Consts.CDefGTreeInit) + self.mutator.set(Consts.CDefGGTreeMutator) + self.crossover.set(Consts.CDefGTreeCrossover) + + def __repr__(self): + """ Return a string representation of Genome """ + ret = GenomeBase.__repr__(self) + ret += GTreeBase.__repr__(self) + return ret + + def copy(self, g): + """ Copy the contents to the destination g + + :param g: the GTree genome destination + """ + GenomeBase.copy(self, g) + GTreeBase.copy(self, g) + + def clone(self): + """ Return a new instance of the genome + + :rtype: new GTree instance + """ + newcopy = GTree() + self.copy(newcopy) + newcopy.processNodes(True) + return newcopy + + +class GTreeNode(GTreeNodeBase): + """ The GTreeNode class - The node representation + + Inheritance diagram for :class:`GTree.GTreeNode`: + + .. inheritance-diagram:: GTree.GTreeNode + + :param data: the root node of the tree + :param parent: the parent node, if root, this + must be *None* + """ + __slots__ = ["node_data"] + + def __init__(self, data, parent=None): + super(GTreeNode, self).__init__(parent) + self.node_data = data + + def __repr__(self): + str_repr = GTreeNodeBase.__repr__(self) + str_repr += " - [%s]" % self.node_data + return str_repr + + def setData(self, data): + """ Sets the data of the node + + :param data: the data of the node + """ + self.node_data = data + + def getData(self): + """ Return the data of the node + + :rtype: the data of the node + """ + return self.node_data + + def newNode(self, data): + """ Created a new child node + + :param data: the data of the new created node + """ + node = GTreeNode(data, self) + self.addChild(node) + return node + + def swapNodeData(self, node): + """ Swaps the node data with another node + + :param node: the node to do the data swap + """ + tmp_data = self.node_data + self.setData(node.getData()) + node.setData(tmp_data) + + def copy(self, g): + """ Copy the contents to the destination g + + :param g: the GTreeNode genome destination + """ + GTreeNodeBase.copy(self, g) + g.node_data = self.node_data + + def clone(self): + """ Return a new instance of the genome + + :rtype: new GTree instance + """ + newcopy = GTreeNode(None) + self.copy(newcopy) + return newcopy + +################################# +# Tree Utility Functions # +################################# + + +def buildGTreeGrow(depth, value_callback, max_siblings, max_depth): + """ Random generates a Tree structure using the value_callback + for data generation and the method "Grow" + + :param depth: the initial depth, zero + :param value_callback: the function which generates the random + values for nodes + :param max_siblings: the maximum number of sisters of a node + :param max_depth: the maximum depth of the tree + + :rtype: the root node of created tree + """ + + random_value = value_callback() + n = GTreeNode(random_value) + + if depth == max_depth: + return n + + for i in range(random.randint(0, abs(max_siblings))): + child = buildGTreeGrow(depth + 1, value_callback, max_siblings, max_depth) + child.setParent(n) + n.addChild(child) + return n + + +def buildGTreeFull(depth, value_callback, max_siblings, max_depth): + """ Random generates a Tree structure using the value_callback + for data generation and the method "Full" + + :param depth: the initial depth, zero + :param value_callback: the function which generates the random + values for nodes + :param max_siblings: the maximum number of sisters of a node + :param max_depth: the maximum depth of the tree + + :rtype: the root node of created tree + """ + + random_value = value_callback() + n = GTreeNode(random_value) + + if depth == max_depth: + return n + + if max_siblings < 0: + range_val = abs(max_siblings) + else: + range_val = random.randint(1, abs(max_siblings)) + + for i in range(range_val): + child = buildGTreeFull(depth + 1, value_callback, max_siblings, max_depth) + child.setParent(n) + n.addChild(child) + return n + +################################# +# GTree GP # +################################# + + +class GTreeNodeGP(GTreeNodeBase): + """ The GTreeNodeGP Class - The Genetic Programming Node representation + + Inheritance diagram for :class:`GTree.GTreeNodeGP`: + + .. inheritance-diagram:: GTree.GTreeNodeGP + + :param data: the node data + :param type: the node type + :param parent: the node parent + + """ + __slots__ = ["node_type", "node_data"] + + def __init__(self, data, node_type=0, parent=None): + super(GTreeNodeGP, self).__init__(parent) + self.node_type = node_type + self.node_data = data + + def __repr__(self): + str_repr = GTreeNodeBase.__repr__(self) + str_repr += " - [%s]" % self.node_data + return str_repr + + def compare(self, other): + """ Compare this node with other + + :param other: the other GTreeNodeGP + """ + if not isinstance(other, GTreeNodeGP): + Util.raiseException("The other node used to compare is not a GTreeNodeGP class", TypeError) + + if other.node_type == self.node_type: + if other.node_data == self.node_data: + return 0 + return -1 + + def setData(self, data): + """Sets the node internal data + + :param data: the internal data + """ + self.node_data = data + + def getData(self): + """Gets the node internal data + + :rtype: the internal data + """ + return self.node_data + + def setType(self, node_type): + """Sets the node type + + :param node_type: the node type is type of Consts.nodeType + """ + self.node_type = node_type + + def getType(self): + """Get the node type + + :rtype: the node type is type of Consts.nodeType + """ + return self.node_type + + def newNode(self, data): + """Creates a new node and adds this + node as children of current node + + :param data: the internal node data + """ + node = GTreeNodeGP(data, self) + self.addChild(node) + return node + + def swapNodeData(self, node): + """Swaps the node data and type with another node + + :param node: the node + """ + tmp_data = self.node_data + tmp_type = self.node_type + self.setData(node.getData()) + self.setType(node.getType()) + node.setData(tmp_data) + node.setType(tmp_type) + + def copy(self, g): + """ Copy the contents to the destination g + + :param g: the GTreeNodeGP genome destination + """ + GTreeNodeBase.copy(self, g) + g.node_data = self.node_data + g.node_type = self.node_type + + def clone(self): + """ Return a new copy of the node + + :rtype: the new GTreeNodeGP instance + """ + newcopy = GTreeNodeGP(None) + self.copy(newcopy) + return newcopy + + +class GTreeGP(GTreeBase): + """ The GTreeGP Class - The Genetic Programming Tree representation + + Inheritance diagram for :class:`GTree.GTreeGP`: + + .. inheritance-diagram:: GTree.GTreeGP + + :param root_node: the Root node of the GP Tree + """ + + def __init__(self, root_node=None, cloning=False): + from pyevolve import Consts + super(GTreeGP, self).__init__(root_node) + if not cloning: + self.initializator.set(Consts.CDefGTreeGPInit) + self.mutator.set(Consts.CDefGGTreeGPMutator) + self.crossover.set(Consts.CDefGTreeGPCrossover) + + def __repr__(self): + """ Return a string representation of Genome """ + ret = GenomeBase.__repr__(self) + ret += GTreeBase.__repr__(self) + ret += "\n- GTreeGP\n" + ret += "\tExpression: %s\n" % self.getPreOrderExpression() + return ret + + def writeDotImage(self, filename): + """ Writes a image representation of the individual + + :param filename: the output file image + """ + if not HAVE_PYDOT: + Util.raiseException("You must install Pydot to use this feature !") + + graph = pydot.Dot() + self.writeDotGraph(graph) + graph.write_jpeg(filename, prog='dot') + + def writeDotRaw(self, filename): + """ Writes the raw dot file (text-file used by dot/neato) with the + representation of the individual + + :param filename: the output file, ex: individual.dot + """ + if not HAVE_PYDOT: + Util.raiseException("You must install Pydot to use this feature !") + + graph = pydot.Dot(graph_type="digraph") + self.writeDotGraph(graph) + graph.write(filename, prog='dot', format="raw") + + def writeDotGraph(self, graph, startNode=0): + """ Write a graph to the pydot Graph instance + + :param graph: the pydot Graph instance + :param startNode: used to plot more than one individual + """ + from . import Consts + + if not HAVE_PYDOT: + print("You must install Pydot to use this feature !") + return + + count = startNode + node_stack = [] + nodes_dict = {} + import __main__ as main_module + + for i in range(len(self.nodes_list)): + newnode = pydot.Node(str(count), style="filled") + count += 1 + + if self.nodes_list[i].getType() == Consts.nodeType["TERMINAL"]: + newnode.set_color("lightblue2") + else: + newnode.set_color("goldenrod2") + + if self.nodes_list[i].getType() == Consts.nodeType["NONTERMINAL"]: + func = getattr(main_module, self.nodes_list[i].getData()) + + if hasattr(func, "shape"): + newnode.set_shape(func.shape) + + if hasattr(func, "representation"): + newnode.set_label(func.representation) + else: + newnode.set_label(self.nodes_list[i].getData()) + if hasattr(func, "color"): + newnode.set_color(func.color) + + else: + newnode.set_label(self.nodes_list[i].getData()) + + nodes_dict.update({self.nodes_list[i]: newnode}) + graph.add_node(newnode) + + node_stack.append(self.getRoot()) + while len(node_stack) > 0: + tmp = node_stack.pop() + + parent = tmp.getParent() + if parent is not None: + parent_node = nodes_dict[parent] + child_node = nodes_dict[tmp] + + newedge = pydot.Edge(parent_node, child_node) + graph.add_edge(newedge) + + rev_childs = tmp.getChilds()[:] + rev_childs.reverse() + node_stack.extend(rev_childs) + + return count + + def getSExpression(self, start_node=None): + """ Returns a tree-formated string (s-expression) of the tree. + + :rtype: a S-Expression representing the tree + """ + str_buff = "" + if start_node is None: + start_node = self.getRoot() + str_buff += "%s " % start_node.getData() + + is_leaf = start_node.isLeaf() + if not is_leaf: + str_buff += "( " + + for child_node in start_node.getChilds(): + str_buff += "%s " % child_node.getData() + str_buff += self.getSExpression(child_node) + + if not is_leaf: + str_buff += " )" + return str_buff + + def getPreOrderExpression(self, start_node=None): + """ Return the pre order expression string of the Tree, used + to python *eval*. + + :rtype: the expression string + """ + if start_node is None: + start_node = self.getRoot() + + str_buff = start_node.getData() + + if not start_node.isLeaf(): + all_childs = start_node.getChilds() + str_buff += "(" + self.getPreOrderExpression(all_childs[0]) + + for index in range(1, len(all_childs)): + child = all_childs[index] + str_buff += ", " + self.getPreOrderExpression(child) + str_buff += ")" + + return str_buff + + def getCompiledCode(self): + """ Get the compiled code for the Tree expression + After getting the compiled code object, you just need to evaluate it using + the :func:`eval` native Python method. + + :rtype: compiled python code + """ + expr = self.getPreOrderExpression() + return compile(expr, "", "eval") + + def copy(self, g): + """ Copy the contents to the destination g + + :param g: the GTreeGP genome destination + """ + GenomeBase.copy(self, g) + GTreeBase.copy(self, g) + + def clone(self): + """ Return a new instance of the genome + + :rtype: the new GTreeGP instance + """ + newcopy = GTreeGP(cloning=True) + self.copy(newcopy) + newcopy.processNodes(True) + return newcopy + + def compare(self, other): + """ This method will compare the currently tree with another one + + :param other: the other GTreeGP to compare + """ + if not isinstance(other, GTreeGP): + Util.raiseException("The other tree used to compare is not a GTreeGP class", TypeError) + + stack_self = [] + stack_other = [] + + stack_self.append(self.getRoot()) + stack_other.append(other.getRoot()) + + while len(stack_self) > 0: + + if (len(stack_self) <= 0) or (len(stack_other) <= 0): + return -1 + + tmp_self, tmp_other = stack_self.pop(), stack_other.pop() + if tmp_self.compare(tmp_other) != 0: + return -1 + + stack_self.extend(tmp_self.getChilds()) + stack_other.extend(tmp_other.getChilds()) + + return 0 + + @staticmethod + def writePopulationDot(ga_engine, filename, format="jpeg", start=0, end=0): + """ Writes to a graphical file using pydot, the population of trees + + Example: + >>> GTreeGP.writePopulationDot(ga_engine, "pop.jpg", "jpeg", 0, 10) + + This example will draw the first ten individuals of the population into + the file called "pop.jpg". + + :param ga_engine: the GA Engine + :param filename: the filename, ie. population.jpg + :param start: the start index of individuals + :param end: the end index of individuals + """ + if not HAVE_PYDOT: + Util.raiseException("You must install Pydot to use this feature !") + + pop = ga_engine.getPopulation() + graph = pydot.Dot(graph_type="digraph") + + if not isinstance(pop[0], GTreeGP): + Util.raiseException("The population must have individuals of the GTreeGP chromosome !") + + n = 0 + end_index = len(pop) if end == 0 else end + for i in range(start, end_index): + ind = pop[i] + subg = pydot.Cluster( + "cluster_%d" % i, + label="\"Ind. #%d - Score Raw/Fit.: %.4f/%.4f\"" % (i, ind.getRawScore(), ind.getFitnessScore()) + ) + n = ind.writeDotGraph(subg, n) + graph.add_subgraph(subg) + + graph.write(filename, prog='dot', format=format) + + @staticmethod + def writePopulationDotRaw(ga_engine, filename, start=0, end=0): + """ Writes to a raw dot file using pydot, the population of trees + + Example: + >>> GTreeGP.writePopulationDotRaw(ga_engine, "pop.dot", 0, 10) + + This example will draw the first ten individuals of the population into + the file called "pop.dot". + + :param ga_engine: the GA Engine + :param filename: the filename, ie. population.dot + :param start: the start index of individuals + :param end: the end index of individuals + """ + if not HAVE_PYDOT: + Util.raiseException("You must install Pydot to use this feature !") + + pop = ga_engine.getPopulation() + graph = pydot.Dot(graph_type="digraph") + + if not isinstance(pop[0], GTreeGP): + Util.raiseException("The population must have individuals of the GTreeGP chromosome !") + + n = 0 + end_index = len(pop) if end == 0 else end + for i in range(start, end_index): + ind = pop[i] + subg = pydot.Cluster( + "cluster_%d" % i, + label="\"Ind. #%d - Score Raw/Fit.: %.4f/%.4f\"" % (i, ind.getRawScore(), ind.getFitnessScore()) + ) + n = ind.writeDotGraph(subg, n) + graph.add_subgraph(subg) + + graph.write(filename, prog='dot', format="raw") + + +################################# +# Tree GP Utility Functions # +################################# + +def gpdec(**kwds): + """ This is a decorator to use with genetic programming non-terminals + + It currently accepts the attributes: shape, color and representation. + """ + def decorate(f): + for k in kwds: + setattr(f, k, kwds[k]) + return f + return decorate + + +def checkTerminal(terminal): + """ Do some check on the terminal, to evaluate ephemeral constants + + :param terminal: the terminal string + """ + if terminal.startswith("ephemeral:"): + splited = terminal.split(":") + ephemeral_constant = eval(splited[1]) + return str(ephemeral_constant) + else: + return terminal + + +def buildGTreeGPGrow(ga_engine, depth, max_depth): + """ Creates a new random GTreeGP root node with subtrees using + the "Grow" method. + + :param ga_engine: the GA Core + :param depth: the initial depth + :max_depth: the maximum depth of the tree + :rtype: the root node + """ + + gp_terminals = ga_engine.getParam("gp_terminals") + assert gp_terminals is not None + + gp_function_set = ga_engine.getParam("gp_function_set") + assert gp_function_set is not None + + if depth == max_depth: + random_terminal = checkTerminal(random.choice(gp_terminals)) + n = GTreeNodeGP(random_terminal, Consts.nodeType["TERMINAL"]) + return n + else: + # Do not generate degenerative trees + if depth == 0: + random_node = random.choice(list(gp_function_set.keys())) + else: + fchoice = random.choice([list(gp_function_set.keys()), gp_terminals]) + random_node = random.choice(fchoice) + + if random_node in gp_terminals: + n = GTreeNodeGP(checkTerminal(random_node), Consts.nodeType["TERMINAL"]) + else: + n = GTreeNodeGP(random_node, Consts.nodeType["NONTERMINAL"]) + + if n.getType() == Consts.nodeType["NONTERMINAL"]: + for i in range(gp_function_set[n.getData()]): + child = buildGTreeGPGrow(ga_engine, depth + 1, max_depth) + child.setParent(n) + n.addChild(child) + + return n + + +def buildGTreeGPFull(ga_engine, depth, max_depth): + """ Creates a new random GTreeGP root node with subtrees using + the "Full" method. + + :param ga_engine: the GA Core + :param depth: the initial depth + :max_depth: the maximum depth of the tree + :rtype: the root node + """ + from . import Consts + + gp_terminals = ga_engine.getParam("gp_terminals") + assert gp_terminals is not None + + gp_function_set = ga_engine.getParam("gp_function_set") + assert gp_function_set is not None + + if depth == max_depth: + random_terminal = checkTerminal(random.choice(gp_terminals)) + n = GTreeNodeGP(random_terminal, Consts.nodeType["TERMINAL"]) + return n + else: + random_oper = random.choice(list(gp_function_set.keys())) + n = GTreeNodeGP(random_oper, Consts.nodeType["NONTERMINAL"]) + + if n.getType() == Consts.nodeType["NONTERMINAL"]: + for i in range(gp_function_set[n.getData()]): + child = buildGTreeGPFull(ga_engine, depth + 1, max_depth) + child.setParent(n) + n.addChild(child) + + return n diff --git a/GenomeBase.py b/GenomeBase.py new file mode 100644 index 0000000..ac0cc05 --- /dev/null +++ b/GenomeBase.py @@ -0,0 +1,609 @@ +""" + +:mod:`GenomeBase` -- the genomes base module +================================================================ + +This module have the class which every representation extends, +if you are planning to create a new representation, you must +take a inside look into this module. + +""" +from future.builtins import range + +from random import choice as rand_choice +import inspect + +from .FunctionSlot import FunctionSlot +from . import Util + +class GenomeBase(object): + """ GenomeBase Class - The base of all chromosome representation """ + __slots__ = ["evaluator", "initializator", "mutator", "crossover", "internalParams", "score", "fitness"] + + def __init__(self): + """Genome Constructor""" + self.evaluator = FunctionSlot("Evaluator") + self.initializator = FunctionSlot("Initializator") + self.mutator = FunctionSlot("Mutator") + self.crossover = FunctionSlot("Crossover") + + self.internalParams = {} + self.score = 0.0 + self.fitness = 0.0 + + def getRawScore(self): + """ Get the Raw Score of the genome + + :rtype: genome raw score + + """ + return self.score + + def getFitnessScore(self): + """ Get the Fitness Score of the genome + + :rtype: genome fitness score + + """ + return self.fitness + + def __repr__(self): + """String representation of Genome""" + allSlots = [self.evaluator, self.initializator, self.mutator, + self.crossover] + + ret = "- GenomeBase\n" + ret += "\tScore:\t\t\t %.6f\n" % (self.score,) + ret += "\tFitness:\t\t %.6f\n\n" % (self.fitness,) + ret += "\tParams:\t\t %s\n\n" % (self.internalParams,) + + for slot in allSlots: + ret += "\t" + slot.__repr__() + ret += "\n" + + return ret + + def setParams(self, **args): + """ Set the internal params + + Example: + >>> genome.setParams(rangemin=0, rangemax=100, gauss_mu=0, gauss_sigma=1) + + .. note:: All the individuals of the population shares this parameters and uses + the same instance of this dict. + + :param args: this params will saved in every chromosome for genetic op. use + + """ + self.internalParams.update(args) + + def getParam(self, key, nvl=None): + """ Gets an internal parameter + + Example: + >>> genome.getParam("rangemax") + 100 + + .. note:: All the individuals of the population shares this parameters and uses + the same instance of this dict. + + :param key: the key of param + :param nvl: if the key doesn't exist, the nvl will be returned + + """ + return self.internalParams.get(key, nvl) + + def resetStats(self): + """ Clear score and fitness of genome """ + self.score = 0.0 + self.fitness = 0.0 + + def evaluate(self, **args): + """ Called to evaluate genome + + :param args: this parameters will be passes to the evaluator + + """ + self.resetStats() + for it in self.evaluator.applyFunctions(self, **args): + self.score += it + + def initialize(self, **args): + """ Called to initialize genome + + :param args: this parameters will be passed to the initializator + + """ + for it in self.initializator.applyFunctions(self, **args): + pass + + def mutate(self, **args): + """ Called to mutate the genome + + :param args: this parameters will be passed to the mutator + :rtype: the number of mutations returned by mutation operator + + """ + nmuts = 0 + for it in self.mutator.applyFunctions(self, **args): + nmuts += it + return nmuts + + def copy(self, g): + """ Copy the current GenomeBase to 'g' + + :param g: the destination genome + + .. note:: If you are planning to create a new chromosome representation, you + **must** implement this method on your class. + + """ + g.score = self.score + g.fitness = self.fitness + g.evaluator = self.evaluator + g.initializator = self.initializator + g.mutator = self.mutator + g.crossover = self.crossover + g.internalParams = self.internalParams + + def clone(self): + """ Clone this GenomeBase + + :rtype: the clone genome + + .. note:: If you are planning to create a new chromosome representation, you + **must** implement this method on your class. + """ + newcopy = GenomeBase() + self.copy(newcopy) + return newcopy + +class G1DBase(GenomeBase): + """ G1DBase Class - The base class for 1D chromosomes + + This chromosome class extends the :class:`GenomeBase` classes. + + :param size: the 1D list size + + .. versionadded:: 0.6 + Added the *G1DBase* class + """ + __slots__ = ["genomeSize", "genomeList"] + + def __init__(self, size): + super(G1DBase, self).__init__() + self.genomeSize = size + self.genomeList = [] + + def __iadd__(self, item): + """ To add more items using the += operator """ + self.genomeList.append(item) + return self + + def __eq__(self, other): + """ Compares one chromosome with another """ + cond1 = (self.genomeList == other.genomeList) + cond2 = (self.genomeSize == other.genomeSize) + return True if cond1 and cond2 else False + + def __contains__(self, value): + """ Used on: *value in genome* """ + return value in self.genomeList + + def __getslice__(self, a, b): + """ Return the sliced part of chromosome """ + return self.genomeList[a:b] + + def __setslice__(self, a, b, val): + """ Sets the slice part of chromosome """ + self.genomeList[a:b] = val + + def __getitem__(self, key): + """ Return the specified gene of List """ + return self.genomeList[key] + + def __setitem__(self, key, value): + """ Set the specified value for an gene of List """ + self.genomeList[key] = value + + def __iter__(self): + """ Iterator support to the list """ + return iter(self.genomeList) + + def __len__(self): + """ Return the size of the List """ + return len(self.genomeList) + + def getListSize(self): + """ Returns the list supposed size + + .. warning:: this is different from what the len(obj) returns + """ + return self.genomeSize + + def resumeString(self): + """ Returns a resumed string representation of the Genome """ + return str(self.genomeList) + + def append(self, value): + """ Appends an item to the end of the list + + Example: + >>> genome.append(44) + + :param value: value to be added + + """ + self.genomeList.append(value) + + def remove(self, value): + """ Removes an item from the list + + Example: + >>> genome.remove(44) + + :param value: value to be added + + """ + self.genomeList.remove(value) + + def clearList(self): + """ Remove all genes from Genome """ + del self.genomeList[:] + + def copy(self, g): + """ Copy genome to 'g' + + Example: + >>> genome_origin.copy(genome_destination) + + :param g: the destination instance + + """ + g.genomeSize = self.genomeSize + g.genomeList = self.genomeList[:] + + def getInternalList(self): + """ Returns the internal list of the genome + + ... note:: this method was created to solve performance issues + :rtype: the internal list + """ + return self.genomeList + + def setInternalList(self, lst): + """ Assigns a list to the internal list of the chromosome + + :param lst: the list to assign the internal list of the chromosome + """ + self.genomeList = lst + +class GTreeNodeBase(object): + """ GTreeNodeBase Class - The base class for the node tree genomes + + :param parent: the parent node of the node + :param childs: the childs of the node, must be a list of nodes + + .. versionadded:: 0.6 + Added the *GTreeNodeBase* class + """ + __slots__ = ["parent", "childs"] + + def __init__(self, parent, childs=None): + self.parent = parent + self.childs = [] + + if childs is not None: + if type(childs) != list: + Util.raiseException("Childs must be a list of nodes", TypeError) + typecheck_list = [x for x in childs if not isinstance(x, GTreeNodeBase)] + if len(typecheck_list) > 0: + Util.raiseException("Childs must be a list of nodes", TypeError) + self.childs += childs + + def isLeaf(self): + """ Return True if the node is a leaf + + :rtype: True or False + """ + return len(self.childs) == 0 + + def getChild(self, index): + """ Returns the index-child of the node + + :rtype: child node + """ + return self.childs[index] + + def getChilds(self): + """ Return the childs of the node + + .. warning :: use .getChilds()[:] if you'll change the list itself, like using childs.reverse(), + otherwise the original genome child order will be changed. + + :rtype: a list of nodes + """ + return self.childs + + def addChild(self, child): + """ Adds a child to the node + + :param child: the node to be added + """ + if type(child) == list: + self.childs.extend(child) + else: + if not isinstance(child, GTreeNodeBase): + Util.raiseException("The child must be a node", TypeError) + self.childs.append(child) + + def replaceChild(self, older, newer): + """ Replaces a child of the node + + :param older: the child to be replaces + :param newer: the new child which replaces the older + """ + index = self.childs.index(older) + self.childs[index] = newer + + def setParent(self, parent): + """ Sets the parent of the node + + :param parent: the parent node + """ + self.parent = parent + + def getParent(self): + """ Get the parent node of the node + + :rtype: the parent node + """ + return self.parent + + def __repr__(self): + str_repr = "GTreeNodeBase [Childs=%d]" % len(self) + return str_repr + + def __len__(self): + return len(self.childs) + + def copy(self, g): + """ Copy the current contents GTreeNodeBase to 'g' + + :param g: the destination node + + .. note:: If you are planning to create a new chromosome representation, you + **must** implement this method on your class. + """ + g.parent = self.parent + g.childs = self.childs[:] + + def clone(self): + """ Clone this GenomeBase + + :rtype: the clone genome + + .. note:: If you are planning to create a new chromosome representation, you + **must** implement this method on your class. + """ + newcopy = GTreeNodeBase(None) + self.copy(newcopy) + return newcopy + + +class GTreeBase(GenomeBase): + """ GTreeBase Class - The base class for the tree genomes + + This chromosome class extends the :class:`GenomeBase` classes. + + :param root_node: the root node of the tree + + .. versionadded:: 0.6 + Added the *GTreeBase* class + """ + __slots__ = ["root_node", "tree_height", "nodes_list", "nodes_leaf", "nodes_branch"] + + def __init__(self, root_node): + super(GTreeBase, self).__init__() + self.root_node = root_node + self.tree_height = None + self.nodes_list = None + + def processNodes(self, cloning=False): + """ Creates a *cache* on the tree, this method must be called + every time you change the shape of the tree. It updates the + internal nodes list and the internal nodes properties such as + depth and height. + """ + if self.root_node is None: + return + self.nodes_list = self.getAllNodes() + self.nodes_leaf = [n for n in self.nodes_list if n.isLeaf()] + self.nodes_branch = [n for n in self.nodes_list if n.isLeaf() is False] + + if not cloning: + self.tree_height = self.getNodeHeight(self.getRoot()) + + def getRoot(self): + """ Return the tree root node + + :rtype: the tree root node + """ + return self.root_node + + def setRoot(self, root): + """ Sets the root of the tree + + :param root: the tree root node + """ + if not isinstance(root, GTreeNodeBase): + Util.raiseException("The root must be a node", TypeError) + self.root_node = root + + def getNodeDepth(self, node): + """ Returns the depth of a node + + :rtype: the depth of the node, the depth of root node is 0 + """ + if node == self.getRoot(): + return 0 + else: + return 1 + self.getNodeDepth(node.getParent()) + + def getNodeHeight(self, node): + """ Returns the height of a node + + .. note:: If the node has no childs, the height will be 0. + + :rtype: the height of the node + """ + height = 0 + if len(node) <= 0: + return 0 + for child in node.getChilds(): + h_inner = self.getNodeHeight(child) + 1 + if h_inner > height: + height = h_inner + return height + + def getHeight(self): + """ Return the tree height + + :rtype: the tree height + """ + return self.tree_height + + def getNodesCount(self, start_node=None): + """ Return the number of the nodes on the tree + starting at the *start_node*, if *start_node* is None, + then the method will count all the tree nodes. + + :rtype: the number of nodes + """ + count = 1 + if start_node is None: + start_node = self.getRoot() + for i in start_node.getChilds(): + count += self.getNodesCount(i) + return count + + def getTraversalString(self, start_node=None, spc=0): + """ Returns a tree-formated string of the tree. This + method is used by the __repr__ method of the tree + + :rtype: a string representing the tree + """ + str_buff = "" + if start_node is None: + start_node = self.getRoot() + str_buff += "%s\n" % start_node + spaces = spc + 2 + for child_node in start_node.getChilds(): + str_buff += "%s%s\n" % (" " * spaces, child_node) + str_buff += self.getTraversalString(child_node, spaces) + return str_buff + + def traversal(self, callback, start_node=None): + """ Traversal the tree, this method will call the + user-defined callback function for each node on the tree + + :param callback: a function + :param start_node: the start node to begin the traversal + """ + if not inspect.isfunction(callback): + Util.raiseException("The callback for the tree traversal must be a function", TypeError) + + if start_node is None: + start_node = self.getRoot() + callback(start_node) + for child_node in start_node.getChilds(): + callback(child_node) + self.traversal(callback, child_node) + + def getRandomNode(self, node_type=0): + """ Returns a random node from the Tree + + :param node_type: 0 = Any, 1 = Leaf, 2 = Branch + :rtype: random node + """ + lists = (self.nodes_list, self.nodes_leaf, self.nodes_branch) + cho = lists[node_type] + if len(cho) <= 0: + return None + return rand_choice(cho) + + def getAllNodes(self): + """ Return a new list with all nodes + + :rtype: the list with all nodes + """ + node_stack = [] + all_nodes = [] + tmp = None + + node_stack.append(self.getRoot()) + while len(node_stack) > 0: + tmp = node_stack.pop() + all_nodes.append(tmp) + childs = tmp.getChilds() + node_stack.extend(childs) + + return all_nodes + + def __repr__(self): + str_buff = "- GTree\n" + str_buff += "\tHeight:\t\t\t%d\n" % self.getHeight() + str_buff += "\tNodes:\t\t\t%d\n" % self.getNodesCount() + str_buff += "\n" + self.getTraversalString() + + return str_buff + + def __len__(self): + return len(self.nodes_list) + + def __getitem__(self, index): + return self.nodes_list[index] + + def __iter__(self): + return iter(self.nodes_list) + + def copy(self, g, node=None, node_parent=None): + """ Copy the current contents GTreeBase to 'g' + + :param g: the destination GTreeBase tree + + .. note:: If you are planning to create a new chromosome representation, you + **must** implement this method on your class. + """ + if node is None: + g.tree_height = self.tree_height + node = self.root_node + + if node is None: + return None + + newnode = node.clone() + + if node_parent is None: + g.setRoot(newnode) + else: + newnode.setParent(node_parent) + node_parent.replaceChild(node, newnode) + + for ci in range(len(newnode)): + GTreeBase.copy(self, g, newnode.getChild(ci), newnode) + + return newnode + + def clone(self): + """ Clone this GenomeBase + + :rtype: the clone genome + + .. note:: If you are planning to create a new chromosome representation, you + **must** implement this method on your class. + """ + newcopy = GTreeBase(None) + self.copy(newcopy) + newcopy.processNodes() + return newcopy diff --git a/Initializators.py b/Initializators.py new file mode 100644 index 0000000..b5e4589 --- /dev/null +++ b/Initializators.py @@ -0,0 +1,275 @@ +""" + +:mod:`Initializators` -- initialization methods module +=================================================================== + +In this module we have the genetic operators of initialization for each +chromosome representation, the most part of initialization is done by +choosing random data. + +.. note:: In Pyevolve, the Initializator defines the data type that will + be used on the chromosome, for example, the :func:`G1DListInitializatorInteger` + will initialize the G1DList with Integers. + + +""" +from future.builtins import range + +from random import randint as rand_randint, uniform as rand_uniform, choice as rand_choice +from . import GTree +from . import Util + + +############################# +## 1D Binary String ## +############################# + +def G1DBinaryStringInitializator(genome, **args): + """ 1D Binary String initializator """ + genome.genomeList = [rand_choice((0, 1)) for _ in range(genome.getListSize())] + + +############################# +## 2D Binary String ## +############################# + +def G2DBinaryStringInitializator(genome, **args): + """ Integer initialization function of 2D Binary String + + .. versionadded:: 0.6 + The *G2DBinaryStringInitializator* function + """ + genome.clearString() + + for i in range(genome.getHeight()): + for j in range(genome.getWidth()): + random_gene = rand_choice((0, 1)) + genome.setItem(i, j, random_gene) + + +#################### +## 1D List ## +#################### + +def G1DListInitializatorAllele(genome, **args): + """ Allele initialization function of G1DList + + To use this initializator, you must specify the *allele* genome parameter with the + :class:`GAllele.GAlleles` instance. + + """ + + allele = genome.getParam("allele", None) + if allele is None: + Util.raiseException("to use the G1DListInitializatorAllele, you must specify the 'allele' parameter") + + genome.genomeList = [allele[i].getRandomAllele() for i in range(genome.getListSize())] + + +def G1DListInitializatorInteger(genome, **args): + """ Integer initialization function of G1DList + + This initializator accepts the *rangemin* and *rangemax* genome parameters. + + """ + range_min = genome.getParam("rangemin", 0) + range_max = genome.getParam("rangemax", 100) + + genome.genomeList = [rand_randint(range_min, range_max) for i in range(genome.getListSize())] + + +def G1DListInitializatorReal(genome, **args): + """ Real initialization function of G1DList + + This initializator accepts the *rangemin* and *rangemax* genome parameters. + + """ + range_min = genome.getParam("rangemin", 0) + range_max = genome.getParam("rangemax", 100) + + genome.genomeList = [rand_uniform(range_min, range_max) for i in range(genome.getListSize())] + + +#################### +## 2D List ## +#################### + +def G2DListInitializatorInteger(genome, **args): + """ Integer initialization function of G2DList + + This initializator accepts the *rangemin* and *rangemax* genome parameters. + + """ + genome.clearList() + + for i in range(genome.getHeight()): + for j in range(genome.getWidth()): + randomInteger = rand_randint(genome.getParam("rangemin", 0), + genome.getParam("rangemax", 100)) + genome.setItem(i, j, randomInteger) + + +def G2DListInitializatorReal(genome, **args): + """ Integer initialization function of G2DList + + This initializator accepts the *rangemin* and *rangemax* genome parameters. + + """ + genome.clearList() + + for i in range(genome.getHeight()): + for j in range(genome.getWidth()): + randomReal = rand_uniform(genome.getParam("rangemin", 0), + genome.getParam("rangemax", 100)) + genome.setItem(i, j, randomReal) + + +def G2DListInitializatorAllele(genome, **args): + """ Allele initialization function of G2DList + + To use this initializator, you must specify the *allele* genome parameter with the + :class:`GAllele.GAlleles` instance. + + .. warning:: the :class:`GAllele.GAlleles` instance must have the homogeneous flag enabled + + """ + + allele = genome.getParam("allele", None) + if allele is None: + Util.raiseException("to use the G2DListInitializatorAllele, you must specify the 'allele' parameter") + + if not allele.homogeneous: + Util.raiseException("to use the G2DListInitializatorAllele, the 'allele' must be homogeneous") + + genome.clearList() + + for i in range(genome.getHeight()): + for j in range(genome.getWidth()): + random_allele = allele[0].getRandomAllele() + genome.setItem(i, j, random_allele) + + +#################### +## Tree ## +#################### + +def GTreeInitializatorInteger(genome, **args): + """ Integer initialization function of GTree + + This initializator accepts the *rangemin* and *rangemax* genome parameters. + It accepts the following parameters too: + + *max_depth* + The max depth of the tree + + *max_siblings* + The number of maximum siblings of an node + + *method* + The method, accepts "grow", "full" or "ramped". + + .. versionadded:: 0.6 + The *GTreeInitializatorInteger* function. + """ + max_depth = genome.getParam("max_depth", 5) + max_siblings = genome.getParam("max_siblings", 2) + + range_min = genome.getParam("rangemin", 0) + range_max = genome.getParam("rangemax", 100) + + lambda_generator = lambda: rand_randint(range_min, range_max) + + method = genome.getParam("method", "grow") + + if method == "grow": + root = GTree.buildGTreeGrow(0, lambda_generator, max_siblings, max_depth) + elif method == "full": + root = GTree.buildGTreeFull(0, lambda_generator, max_siblings, max_depth) + elif method == "ramped": + if Util.randomFlipCoin(0.5): + root = GTree.buildGTreeGrow(0, lambda_generator, max_siblings, max_depth) + else: + root = GTree.buildGTreeFull(0, lambda_generator, max_siblings, max_depth) + else: + Util.raiseException("Unknown tree initialization method [%s] !" % method) + + genome.setRoot(root) + genome.processNodes() + assert genome.getHeight() <= max_depth + + +def GTreeInitializatorAllele(genome, **args): + """ Allele initialization function of GTree + + To use this initializator, you must specify the *allele* genome parameter with the + :class:`GAllele.GAlleles` instance. + + .. warning:: the :class:`GAllele.GAlleles` instance **must** have the homogeneous flag enabled + + .. versionadded:: 0.6 + The *GTreeInitializatorAllele* function. + """ + max_depth = genome.getParam("max_depth", 5) + max_siblings = genome.getParam("max_siblings", 2) + method = genome.getParam("method", "grow") + + allele = genome.getParam("allele", None) + if allele is None: + Util.raiseException("to use the GTreeInitializatorAllele, you must specify the 'allele' parameter") + + if not allele.homogeneous: + Util.raiseException("to use the GTreeInitializatorAllele, the 'allele' must be homogeneous") + + if method == "grow": + root = GTree.buildGTreeGrow(0, allele[0].getRandomAllele, max_siblings, max_depth) + elif method == "full": + root = GTree.buildGTreeFull(0, allele[0].getRandomAllele, max_siblings, max_depth) + elif method == "ramped": + if Util.randomFlipCoin(0.5): + root = GTree.buildGTreeGrow(0, allele[0].getRandomAllele, max_siblings, max_depth) + else: + root = GTree.buildGTreeFull(0, allele[0].getRandomAllele, max_siblings, max_depth) + else: + Util.raiseException("Unknown tree initialization method [%s] !" % method) + + genome.setRoot(root) + genome.processNodes() + assert genome.getHeight() <= max_depth + + +#################### +## Tree GP ## +#################### + +def GTreeGPInitializator(genome, **args): + """This initializator accepts the follow parameters: + + *max_depth* + The max depth of the tree + + *method* + The method, accepts "grow", "full" or "ramped" + + .. versionadded:: 0.6 + The *GTreeGPInitializator* function. + """ + + max_depth = genome.getParam("max_depth", 5) + method = genome.getParam("method", "grow") + ga_engine = args["ga_engine"] + + if method == "grow": + root = GTree.buildGTreeGPGrow(ga_engine, 0, max_depth) + elif method == "full": + root = GTree.buildGTreeGPFull(ga_engine, 0, max_depth) + elif method == "ramped": + if Util.randomFlipCoin(0.5): + root = GTree.buildGTreeGPFull(ga_engine, 0, max_depth) + else: + root = GTree.buildGTreeGPGrow(ga_engine, 0, max_depth) + else: + Util.raiseException("Unknown tree initialization method [%s] !" % method) + + genome.setRoot(root) + genome.processNodes() + assert genome.getHeight() <= max_depth diff --git a/Interaction.py b/Interaction.py new file mode 100644 index 0000000..646b300 --- /dev/null +++ b/Interaction.py @@ -0,0 +1,87 @@ +""" + +:mod:`Interaction` -- interaction module +========================================================================== + +In this module, you will find the funcionality for the :term:`Interactive mode`. +When you enter in the Interactive Mode, Pyevolve will automatic import this module +and exposes to you in the name space called "it". + +To use this mode, the parameter *interactiveMode* must be enabled in the +:class:`GSimpleGA.GSimpleGA`. + +You can use the manual method to enter in the Interactive Mode at specific +generation using the :meth:`GSimpleGA.GSimpleGA.setInteractiveGeneration` method. + +""" + +import logging + +try: + import pylab +except: + msg = "cannot import Matplotlib ! Plots will not be available !" + logging.debug(msg) + print(msg) + +try: + import numpy +except: + msg = "cannot import Numpy ! Some functions will not be available !" + logging.debug(msg) + print(msg) + +def getPopScores(population, fitness=False): + """ Returns a list of population scores + + Example: + >>> lst = Interaction.getPopScores(population) + + :param population: population object (:class:`GPopulation.GPopulation`) + :param fitness: if True, the fitness score will be used, otherwise, the raw. + :rtype: list of population scores + + """ + score_list = [] + for individual in population: + score_list.append(individual.fitness if fitness else individual.score) + return score_list + +def plotPopScore(population, fitness=False): + """ Plot the population score distribution + + Example: + >>> Interaction.plotPopScore(population) + + :param population: population object (:class:`GPopulation.GPopulation`) + :param fitness: if True, the fitness score will be used, otherwise, the raw. + :rtype: None + + """ + score_list = getPopScores(population, fitness) + pylab.plot(score_list, 'o') + pylab.title("Plot of population score distribution") + pylab.xlabel('Individual') + pylab.ylabel('Score') + pylab.grid(True) + pylab.show() + +def plotHistPopScore(population, fitness=False): + """ Population score distribution histogram + + Example: + >>> Interaction.plotHistPopScore(population) + + :param population: population object (:class:`GPopulation.GPopulation`) + :param fitness: if True, the fitness score will be used, otherwise, the raw. + :rtype: None + + """ + score_list = getPopScores(population, fitness) + n, bins, patches = pylab.hist(score_list, 50, facecolor='green', alpha=0.75, normed=1) + pylab.plot(bins, pylab.normpdf(bins, numpy.mean(score_list), numpy.std(score_list)), 'r--') + pylab.xlabel('Score') + pylab.ylabel('Frequency') + pylab.grid(True) + pylab.title("Plot of population score distribution") + pylab.show() diff --git a/Migration.py b/Migration.py new file mode 100644 index 0000000..a9e937d --- /dev/null +++ b/Migration.py @@ -0,0 +1,342 @@ +""" +:mod:`Migration` -- the migration schemes, distributed GA +===================================================================== + +This module contains all the migration schemes and the distributed +GA related functions. + +.. versionadded:: 0.6 + The :mod:`Migration` module. + +""" +from future.builtins import range + +from . import Util +from random import randint as rand_randint, choice as rand_choice +from . import Network +from . import Consts +from .FunctionSlot import FunctionSlot +import logging + +try: + from mpi4py import MPI + HAS_MPI4PY = True +except ImportError: + HAS_MPI4PY = False + +class MigrationScheme(object): + """ This is the base class for all migration schemes """ + + selector = None + """ This is the function slot for the selection method + if you want to change the default selector, you must do this: :: + + migration_scheme.selector.set(Selectors.GRouletteWheel) """ + + def __init__(self): + self.selector = FunctionSlot("Selector") + self.GAEngine = None + self.nMigrationRate = Consts.CDefGenMigrationRate + self.nIndividuals = Consts.CDefMigrationNIndividuals + self.nReplacement = Consts.CDefGenMigrationReplacement + self.networkCompression = 9 + + def isReady(self): + """ Returns true if is time to migrate """ + return True if self.GAEngine.getCurrentGeneration() % self.nMigrationRate == 0 else False + + def getCompressionLevel(self): + """ Get the zlib compression level of network data + + The values are in the interval described on the :func:`Network.pickleAndCompress` + """ + return self.networkCompression + + def setCompressionLevel(self, level): + """ Set the zlib compression level of network data + + The values are in the interval described on the :func:`Network.pickleAndCompress` + + :param level: the zlib compression level + """ + self.networkCompression = level + + def getNumReplacement(self): + """ Return the number of individuals that will be + replaced in the migration process """ + return self.nReplacement + + def setNumReplacement(self, num_individuals): + """ Return the number of individuals that will be + replaced in the migration process + + :param num_individuals: the number of individuals to be replaced + """ + self.nReplacement = num_individuals + + def getNumIndividuals(self): + """ Return the number of individuals that will migrate + + :rtype: the number of individuals to be replaced + """ + return self.nIndividuals + + def setNumIndividuals(self, num_individuals): + """ Set the number of individuals that will migrate + + :param num_individuals: the number of individuals + """ + self.nIndividuals = num_individuals + + def setMigrationRate(self, generations): + """ Sets the generation frequency supposed to migrate + and receive individuals. + + :param generations: the number of generations + """ + self.nMigrationRate = generations + + def getMigrationRate(self): + """ Return the the generation frequency supposed to migrate + and receive individuals + + :rtype: the number of generations + """ + return self.nMigrationRate + + def setGAEngine(self, ga_engine): + """ Sets the GA Engine handler """ + self.GAEngine = ga_engine + + def start(self): + """ Initializes the migration scheme """ + pass + + def stop(self): + """ Stops the migration engine """ + pass + + def select(self): + """ Picks an individual from population using specific selection method + + :rtype: an individual object + """ + if self.selector.isEmpty(): + return self.GAEngine.select(popID=self.GAEngine.currentGeneration) + else: + for it in self.selector.applyFunctions(self.GAEngine.internalPop, + popID=self.GAEngine.currentGeneration): + return it + + def selectPool(self, num_individuals): + """ Select num_individuals number of individuals and return a pool + + :param num_individuals: the number of individuals to select + :rtype: list with individuals + """ + pool = [self.select() for i in range(num_individuals)] + return pool + + def exchange(self): + """ Exchange individuals """ + pass + + +class WANMigration(MigrationScheme): + """ This is the Simple Migration class for distributed GA + + Example: + >>> mig = WANMigration("192.168.0.1", "10000", "group1") + + :param host: the source hostname + :param port: the source port number + :param group_name: the group name + """ + + selector = None + """ This is the function slot for the selection method + if you want to change the default selector, you must do this: :: + + migration_scheme.selector.set(Selectors.GRouletteWheel) """ + + def __init__(self, host, port, group_name): + super(WANMigration, self).__init__() + self.setMyself(host, port) + self.setGroupName(group_name) + self.topologyGraph = None + self.serverThread = Network.UDPThreadServer(host, port) + self.clientThread = Network.UDPThreadUnicastClient(self.myself[0], rand_randint(30000, 65534)) + + def setMyself(self, host, port): + """ Which interface you will use to send/receive data + + :param host: your hostname + :param port: your port + """ + self.myself = (host, port) + + def getGroupName(self): + """ Gets the group name + + .. note:: all islands of evolution which are supposed to exchange + individuals, must have the same group name. + """ + return self.groupName + + def setGroupName(self, name): + """ Sets the group name + + :param name: the group name + + .. note:: all islands of evolution which are supposed to exchange + individuals, must have the same group name. + """ + self.groupName = name + + def setTopology(self, graph): + """ Sets the topology of the migrations + + :param graph: the :class:`Util.Graph` instance + """ + self.topologyGraph = graph + + def start(self): + """ Start capture of packets and initialize the migration scheme """ + self.serverThread.start() + + if self.topologyGraph is None: + Util.raiseException("You must add a topology graph to the migration scheme !") + + targets = self.topologyGraph.getNeighbors(self.myself) + self.clientThread.setMultipleTargetHost(targets) + self.clientThread.start() + + def stop(self): + """ Stops the migration engine """ + self.serverThread.shutdown() + self.clientThread.shutdown() + server_timeout = self.serverThread.timeout + client_timeout = self.clientThread.timeout + + self.serverThread.join(server_timeout + 3) + self.clientThread.join(client_timeout + 3) + + if self.serverThread.isAlive(): + logging.warning("warning: server thread not joined !") + + if self.clientThread.isAlive(): + logging.warning("warning: client thread not joined !") + + def exchange(self): + """ This is the main method, is where the individuals + are exchanged """ + + if not self.isReady(): + return + + # Client section -------------------------------------- + # How many will migrate ? + pool = self.selectPool(self.getNumIndividuals()) + + for individual in pool: + # (code, group name, individual) + networkObject = (Consts.CDefNetworkIndividual, self.getGroupName(), individual) + networkData = Network.pickleAndCompress(networkObject, self.getCompressionLevel()) + # Send the individuals to the topology + self.clientThread.addData(networkData) + + # Server section -------------------------------------- + pool = [] + while self.serverThread.isReady(): + # (IP source, data) + networkData = self.serverThread.popPool() + networkObject = Network.unpickleAndDecompress(networkData[1]) + # (code, group name, individual) + pool.append(networkObject) + + # No individuals received + if len(pool) <= 0: + return + + population = self.GAEngine.getPopulation() + + for i in range(self.getNumReplacement()): + if len(pool) <= 0: + break + choice = rand_choice(pool) + pool.remove(choice) + + # replace the worst + population[len(population) - 1 - i] = choice[2] + + +class MPIMigration(MigrationScheme): + """ This is the MPIMigration """ + + def __init__(self): + # Delayed ImportError of mpi4py + if not HAS_MPI4PY: + raise ImportError("No module named mpi4py, you must install mpi4py to use MPIMIgration!") + + super(MPIMigration, self).__init__() + + self.comm = MPI.COMM_WORLD + self.pid = self.comm.rank + + if self.pid == 0: + self.source = self.comm.size - 1 + else: + self.source = self.comm.rank - 1 + + self.dest = (self.comm.rank + 1) % (self.comm.size) + + self.all_stars = None + + def isReady(self): + """ Returns true if is time to migrate """ + + if self.GAEngine.getCurrentGeneration() == 0: + return False + + if self.GAEngine.getCurrentGeneration() % self.nMigrationRate == 0: + return True + else: + return False + + def gather_bests(self): + ''' + Collect all the best individuals from the various populations. The + result is stored in process 0 + ''' + best_guy = self.select() + self.all_stars = self.comm.gather(sendobj=best_guy, root=0) + + def exchange(self): + """ This is the main method, is where the individuals + are exchanged """ + + if not self.isReady(): + return + + pool_to_send = self.selectPool(self.getNumIndividuals()) + pool_received = self.comm.sendrecv(sendobj=pool_to_send, + dest=self.dest, + sendtag=0, + recvobj=None, + source=self.source, + recvtag=0) + + population = self.GAEngine.getPopulation() + + pool = pool_received + for i in range(self.getNumReplacement()): + if len(pool) <= 0: + break + + choice = rand_choice(pool) + pool.remove(choice) + + # replace the worst + population[len(population) - 1 - i] = choice + + self.gather_bests() diff --git a/Mutators.py b/Mutators.py new file mode 100644 index 0000000..afa116d --- /dev/null +++ b/Mutators.py @@ -0,0 +1,1164 @@ +""" + +:mod:`Mutators` -- mutation methods module +===================================================================== + +In this module we have the genetic operators of mutation for each chromosome representation. + +""" +from future.builtins import range + +from . import Util +from random import randint as rand_randint, gauss as rand_gauss, uniform as rand_uniform +from random import choice as rand_choice +from . import GTree + +############################# +## 1D Binary String ## +############################# + +def G1DBinaryStringMutatorSwap(genome, **args): + """ The 1D Binary String Swap Mutator """ + + if args["pmut"] <= 0.0: + return 0 + stringLength = len(genome) + mutations = args["pmut"] * (stringLength) + + if mutations < 1.0: + mutations = 0 + for it in range(stringLength): + if Util.randomFlipCoin(args["pmut"]): + Util.listSwapElement(genome, it, rand_randint(0, stringLength - 1)) + mutations += 1 + + else: + for it in range(int(round(mutations))): + Util.listSwapElement(genome, rand_randint(0, stringLength - 1), + rand_randint(0, stringLength - 1)) + + return int(mutations) + +def G1DBinaryStringMutatorFlip(genome, **args): + """ The classical flip mutator for binary strings """ + if args["pmut"] <= 0.0: + return 0 + stringLength = len(genome) + mutations = args["pmut"] * (stringLength) + + if mutations < 1.0: + mutations = 0 + for it in range(stringLength): + if Util.randomFlipCoin(args["pmut"]): + if genome[it] == 0: + genome[it] = 1 + else: + genome[it] = 0 + mutations += 1 + + else: + for it in range(int(round(mutations))): + which = rand_randint(0, stringLength - 1) + if genome[which] == 0: + genome[which] = 1 + else: + genome[which] = 0 + + return int(mutations) + +#################### +## 1D List ## +#################### + +def G1DListMutatorSwap(genome, **args): + """ The mutator of G1DList, Swap Mutator + + .. note:: this mutator is :term:`Data Type Independent` + + """ + if args["pmut"] <= 0.0: + return 0 + listSize = len(genome) + mutations = args["pmut"] * listSize + + if mutations < 1.0: + mutations = 0 + for it in range(listSize): + if Util.randomFlipCoin(args["pmut"]): + Util.listSwapElement(genome, it, rand_randint(0, listSize - 1)) + mutations += 1 + else: + for it in range(int(round(mutations))): + Util.listSwapElement(genome, rand_randint(0, listSize - 1), rand_randint(0, listSize - 1)) + + return int(mutations) + +def G1DListMutatorSIM(genome, **args): + """ The mutator of G1DList, Simple Inversion Mutation + + .. note:: this mutator is :term:`Data Type Independent` + + """ + mutations = 0 + if args["pmut"] <= 0.0: + return 0 + + cuts = [rand_randint(0, len(genome)), rand_randint(0, len(genome))] + + if cuts[0] > cuts[1]: + Util.listSwapElement(cuts, 0, 1) + + if (cuts[1] - cuts[0]) <= 0: + cuts[1] = rand_randint(cuts[0], len(genome)) + + if Util.randomFlipCoin(args["pmut"]): + part = genome[cuts[0]:cuts[1]] + if len(part) == 0: + return 0 + part.reverse() + genome[cuts[0]:cuts[1]] = part + mutations += 1 + + return mutations + +def G1DListMutatorIntegerRange(genome, **args): + """ Simple integer range mutator for G1DList + + Accepts the *rangemin* and *rangemax* genome parameters, both optional. + + """ + from . import Consts + + if args["pmut"] <= 0.0: + return 0 + listSize = len(genome) + mutations = args["pmut"] * listSize + + if mutations < 1.0: + mutations = 0 + for it in range(listSize): + if Util.randomFlipCoin(args["pmut"]): + genome[it] = rand_randint(genome.getParam("rangemin", Consts.CDefRangeMin), + genome.getParam("rangemax", Consts.CDefRangeMax)) + mutations += 1 + + else: + for it in range(int(round(mutations))): + which_gene = rand_randint(0, listSize - 1) + genome[which_gene] = rand_randint(genome.getParam("rangemin", Consts.CDefRangeMin), + genome.getParam("rangemax", Consts.CDefRangeMax)) + + return int(mutations) + + +def G1DListMutatorRealRange(genome, **args): + """ Simple real range mutator for G1DList + + Accepts the *rangemin* and *rangemax* genome parameters, both optional. + + """ + from . import Consts + + if args["pmut"] <= 0.0: + return 0 + listSize = len(genome) + mutations = args["pmut"] * (listSize) + + if mutations < 1.0: + mutations = 0 + for it in range(listSize): + if Util.randomFlipCoin(args["pmut"]): + genome[it] = rand_uniform(genome.getParam("rangemin", Consts.CDefRangeMin), + genome.getParam("rangemax", Consts.CDefRangeMax)) + mutations += 1 + + else: + for it in range(int(round(mutations))): + which_gene = rand_randint(0, listSize - 1) + genome[which_gene] = rand_uniform(genome.getParam("rangemin", Consts.CDefRangeMin), + genome.getParam("rangemax", Consts.CDefRangeMax)) + + return int(mutations) + +def G1DListMutatorIntegerGaussianGradient(genome, **args): + """ A gaussian mutator for G1DList of Integers + + Accepts the *rangemin* and *rangemax* genome parameters, both optional. The + random distribution is set with mu=1.0 and std=0.0333 + + Same as IntegerGaussian, except that this uses relative gradient rather than + absolute gaussian. A value is randomly generated about gauss(mu=1, sigma=.0333) + and multiplied by the gene to drift it up or down (depending on what side of + 1 the random value falls on) and cast to integer + + """ + from . import Consts + + if args["pmut"] <= 0.0: + return 0 + listSize = len(genome) + mutations = args["pmut"] * (listSize) + + mu = Consts.CDefGaussianGradientMU + sigma = Consts.CDefGaussianGradientSIGMA + + if mutations < 1.0: + mutations = 0 + for it in range(listSize): + if Util.randomFlipCoin(args["pmut"]): + final_value = int(genome[it] * abs(rand_gauss(mu, sigma))) + + final_value = min(final_value, genome.getParam("rangemax", Consts.CDefRangeMax)) + final_value = max(final_value, genome.getParam("rangemin", Consts.CDefRangeMin)) + + genome[it] = final_value + mutations += 1 + else: + for it in range(int(round(mutations))): + which_gene = rand_randint(0, listSize - 1) + final_value = int(genome[which_gene] * abs(rand_gauss(mu, sigma))) + + final_value = min(final_value, genome.getParam("rangemax", Consts.CDefRangeMax)) + final_value = max(final_value, genome.getParam("rangemin", Consts.CDefRangeMin)) + + genome[which_gene] = final_value + + return int(mutations) + +def G1DListMutatorIntegerGaussian(genome, **args): + """ A gaussian mutator for G1DList of Integers + + Accepts the *rangemin* and *rangemax* genome parameters, both optional. Also + accepts the parameter *gauss_mu* and the *gauss_sigma* which respectively + represents the mean and the std. dev. of the random distribution. + + """ + from . import Consts + + if args["pmut"] <= 0.0: + return 0 + listSize = len(genome) + mutations = args["pmut"] * (listSize) + + mu = genome.getParam("gauss_mu") + sigma = genome.getParam("gauss_sigma") + + if mu is None: + mu = Consts.CDefG1DListMutIntMU + + if sigma is None: + sigma = Consts.CDefG1DListMutIntSIGMA + + if mutations < 1.0: + mutations = 0 + for it in range(listSize): + if Util.randomFlipCoin(args["pmut"]): + final_value = genome[it] + int(rand_gauss(mu, sigma)) + + final_value = min(final_value, genome.getParam("rangemax", Consts.CDefRangeMax)) + final_value = max(final_value, genome.getParam("rangemin", Consts.CDefRangeMin)) + + genome[it] = final_value + mutations += 1 + else: + for it in range(int(round(mutations))): + which_gene = rand_randint(0, listSize - 1) + final_value = genome[which_gene] + int(rand_gauss(mu, sigma)) + + final_value = min(final_value, genome.getParam("rangemax", Consts.CDefRangeMax)) + final_value = max(final_value, genome.getParam("rangemin", Consts.CDefRangeMin)) + + genome[which_gene] = final_value + + return int(mutations) + + +def G1DListMutatorRealGaussian(genome, **args): + """ The mutator of G1DList, Gaussian Mutator + + Accepts the *rangemin* and *rangemax* genome parameters, both optional. Also + accepts the parameter *gauss_mu* and the *gauss_sigma* which respectively + represents the mean and the std. dev. of the random distribution. + + """ + from . import Consts + + if args["pmut"] <= 0.0: + return 0 + listSize = len(genome) + mutations = args["pmut"] * (listSize) + + mu = genome.getParam("gauss_mu") + sigma = genome.getParam("gauss_sigma") + + if mu is None: + mu = Consts.CDefG1DListMutRealMU + + if sigma is None: + sigma = Consts.CDefG1DListMutRealSIGMA + + if mutations < 1.0: + mutations = 0 + for it in range(listSize): + if Util.randomFlipCoin(args["pmut"]): + final_value = genome[it] + rand_gauss(mu, sigma) + + final_value = min(final_value, genome.getParam("rangemax", Consts.CDefRangeMax)) + final_value = max(final_value, genome.getParam("rangemin", Consts.CDefRangeMin)) + + genome[it] = final_value + mutations += 1 + else: + for it in range(int(round(mutations))): + which_gene = rand_randint(0, listSize - 1) + final_value = genome[which_gene] + rand_gauss(mu, sigma) + + final_value = min(final_value, genome.getParam("rangemax", Consts.CDefRangeMax)) + final_value = max(final_value, genome.getParam("rangemin", Consts.CDefRangeMin)) + + genome[which_gene] = final_value + + return int(mutations) + +def G1DListMutatorRealGaussianGradient(genome, **args): + """ The mutator of G1DList, Gaussian Gradient Mutator + + Accepts the *rangemin* and *rangemax* genome parameters, both optional. The + random distribution is set with mu=1.0 and std=0.0333 + + The difference between this routine and the normal Gaussian Real is that the + other function generates a gaussian value and adds it to the value. If the + mu is 0, and the std is 1, a typical value could be 1.8 or -0.5. These small + values are fine if your range is 0-10, but if your range is much larger, like + 0-100,000, a relative gradient makes sense. + + This routine generates a gaussian value with mu=1.0 and std=0.0333 and then + the gene is multiplied by this value. This will cause the gene to drift + no matter how large it is. + + """ + from . import Consts + + if args["pmut"] <= 0.0: + return 0 + listSize = len(genome) + mutations = args["pmut"] * (listSize) + + mu = Consts.CDefGaussianGradientMU + sigma = Consts.CDefGaussianGradientSIGMA + + if mutations < 1.0: + mutations = 0 + for it in range(listSize): + if Util.randomFlipCoin(args["pmut"]): + final_value = genome[it] * abs(rand_gauss(mu, sigma)) + + final_value = min(final_value, genome.getParam("rangemax", Consts.CDefRangeMax)) + final_value = max(final_value, genome.getParam("rangemin", Consts.CDefRangeMin)) + + genome[it] = final_value + mutations += 1 + else: + for it in range(int(round(mutations))): + which_gene = rand_randint(0, listSize - 1) + final_value = genome[which_gene] * abs(rand_gauss(mu, sigma)) + + final_value = min(final_value, genome.getParam("rangemax", Consts.CDefRangeMax)) + final_value = max(final_value, genome.getParam("rangemin", Consts.CDefRangeMin)) + + genome[which_gene] = final_value + + return int(mutations) + +def G1DListMutatorIntegerBinary(genome, **args): + """ The mutator of G1DList, the binary mutator + + This mutator will random change the 0 and 1 elements of the 1D List. + + """ + if args["pmut"] <= 0.0: + return 0 + listSize = len(genome) + mutations = args["pmut"] * (listSize) + + if mutations < 1.0: + mutations = 0 + for it in range(listSize): + if Util.randomFlipCoin(args["pmut"]): + if genome[it] == 0: + genome[it] = 1 + elif genome[it] == 1: + genome[it] = 0 + + mutations += 1 + else: + for it in range(int(round(mutations))): + which_gene = rand_randint(0, listSize - 1) + if genome[which_gene] == 0: + genome[which_gene] = 1 + elif genome[which_gene] == 1: + genome[which_gene] = 0 + + return int(mutations) + +def G1DListMutatorAllele(genome, **args): + """ The mutator of G1DList, Allele Mutator + + To use this mutator, you must specify the *allele* genome parameter with the + :class:`GAllele.GAlleles` instance. + + """ + if args["pmut"] <= 0.0: + return 0 + listSize = len(genome) + mutations = args["pmut"] * listSize + + allele = genome.getParam("allele", None) + if allele is None: + Util.raiseException("to use the G1DListMutatorAllele, you must specify the 'allele' parameter", TypeError) + + if mutations < 1.0: + mutations = 0 + for it in range(listSize): + if Util.randomFlipCoin(args["pmut"]): + new_val = allele[it].getRandomAllele() + genome[it] = new_val + mutations += 1 + else: + for it in range(int(round(mutations))): + which_gene = rand_randint(0, listSize - 1) + new_val = allele[which_gene].getRandomAllele() + genome[which_gene] = new_val + + return int(mutations) + +def G1DListMutatorAlleleGaussian(genome, **arguments): + """An allele-based mutator based on G1DListMutatorRealGaussian. + + Accepts the parameter *gauss_mu* and the *gauss_sigma* which + respectively represents the mean and the std. dev. of the random + distribution. + """ + from . import Consts + + if arguments["pmut"] <= 0.0: + return 0 + listSize = len(genome) + mutations = arguments["pmut"] * listSize + + mu = genome.getParam("gauss_mu") + sigma = genome.getParam("gauss_sigma") + if mu is None: + mu = Consts.CDefG1DListMutRealMU + if sigma is None: + sigma = Consts.CDefG1DListMutRealSIGMA + + allele = genome.getParam("allele", None) + if allele is None: + Util.raiseException("to use this mutator, you must specify the 'allele' parameter", TypeError) + + if mutations < 1.0: + mutations = 0 + for it in range(listSize): + if Util.randomFlipCoin(arguments["pmut"]): + final_value = genome[it] + rand_gauss(mu, sigma) + assert len(allele[it].beginEnd) == 1, "only single ranges are supported" + rangemin, rangemax = allele[it].beginEnd[0] + final_value = min(final_value, rangemax) + final_value = max(final_value, rangemin) + genome[it] = final_value + mutations += 1 + else: + for it in range(int(round(mutations))): + which_gene = rand_randint(0, listSize - 1) + final_value = genome[which_gene] + rand_gauss(mu, sigma) + assert len(allele[which_gene].beginEnd) == 1, "only single ranges are supported" + rangemin, rangemax = allele[which_gene].beginEnd[0] + final_value = min(final_value, rangemax) + final_value = max(final_value, rangemin) + genome[which_gene] = final_value + return int(mutations) + + +#################### +## 2D List ## +#################### + +def G2DListMutatorSwap(genome, **args): + """ The mutator of G1DList, Swap Mutator + + .. note:: this mutator is :term:`Data Type Independent` + + """ + + if args["pmut"] <= 0.0: + return 0 + height, width = genome.getSize() + elements = height * width + + mutations = args["pmut"] * elements + + if mutations < 1.0: + mutations = 0 + for i in range(height): + for j in range(width): + if Util.randomFlipCoin(args["pmut"]): + index_b = (rand_randint(0, height - 1), rand_randint(0, width - 1)) + Util.list2DSwapElement(genome.genomeList, (i, j), index_b) + mutations += 1 + else: + for it in range(int(round(mutations))): + index_a = (rand_randint(0, height - 1), rand_randint(0, width - 1)) + index_b = (rand_randint(0, height - 1), rand_randint(0, width - 1)) + Util.list2DSwapElement(genome.genomeList, index_a, index_b) + + return int(mutations) + +def G2DListMutatorIntegerRange(genome, **args): + """ Simple integer range mutator for G2DList + + Accepts the *rangemin* and *rangemax* genome parameters, both optional. + + """ + from . import Consts + + if args["pmut"] <= 0.0: + return 0 + height, width = genome.getSize() + elements = height * width + + mutations = args["pmut"] * elements + + range_min = genome.getParam("rangemin", Consts.CDefRangeMin) + range_max = genome.getParam("rangemax", Consts.CDefRangeMax) + + if mutations < 1.0: + mutations = 0 + for i in range(genome.getHeight()): + for j in range(genome.getWidth()): + if Util.randomFlipCoin(args["pmut"]): + random_int = rand_randint(range_min, range_max) + genome.setItem(i, j, random_int) + mutations += 1 + + else: + for it in range(int(round(mutations))): + which_x = rand_randint(0, genome.getWidth() - 1) + which_y = rand_randint(0, genome.getHeight() - 1) + random_int = rand_randint(range_min, range_max) + genome.setItem(which_y, which_x, random_int) + + return int(mutations) + + +def G2DListMutatorIntegerGaussianGradient(genome, **args): + """ A gaussian mutator for G2DList of Integers + + Accepts the *rangemin* and *rangemax* genome parameters, both optional. + + This routine generates a gaussian value with mu=1.0 and std=0.0333 and then + the gene is multiplied by this value. This will cause the gene to drift + no matter how large it is. + + """ + from . import Consts + + if args["pmut"] <= 0.0: + return 0 + height, width = genome.getSize() + elements = height * width + + mutations = args["pmut"] * elements + + mu = Consts.CDefGaussianGradientMU + sigma = Consts.CDefGaussianGradientSIGMA + + if mutations < 1.0: + mutations = 0 + + for i in range(genome.getHeight()): + for j in range(genome.getWidth()): + if Util.randomFlipCoin(args["pmut"]): + final_value = int(genome[i][j] * abs(rand_gauss(mu, sigma))) + + final_value = min(final_value, genome.getParam("rangemax", Consts.CDefRangeMax)) + final_value = max(final_value, genome.getParam("rangemin", Consts.CDefRangeMin)) + + genome.setItem(i, j, final_value) + mutations += 1 + else: + + for it in range(int(round(mutations))): + which_x = rand_randint(0, genome.getWidth() - 1) + which_y = rand_randint(0, genome.getHeight() - 1) + + final_value = int(genome[which_y][which_x] * abs(rand_gauss(mu, sigma))) + + final_value = min(final_value, genome.getParam("rangemax", Consts.CDefRangeMax)) + final_value = max(final_value, genome.getParam("rangemin", Consts.CDefRangeMin)) + + genome.setItem(which_y, which_x, final_value) + + return int(mutations) + +def G2DListMutatorIntegerGaussian(genome, **args): + """ A gaussian mutator for G2DList of Integers + + Accepts the *rangemin* and *rangemax* genome parameters, both optional. Also + accepts the parameter *gauss_mu* and the *gauss_sigma* which respectively + represents the mean and the std. dev. of the random distribution. + + """ + from . import Consts + + if args["pmut"] <= 0.0: + return 0 + height, width = genome.getSize() + elements = height * width + + mutations = args["pmut"] * elements + + mu = genome.getParam("gauss_mu") + sigma = genome.getParam("gauss_sigma") + + if mu is None: + mu = Consts.CDefG2DListMutIntMU + + if sigma is None: + sigma = Consts.CDefG2DListMutIntSIGMA + + if mutations < 1.0: + mutations = 0 + + for i in range(genome.getHeight()): + for j in range(genome.getWidth()): + if Util.randomFlipCoin(args["pmut"]): + final_value = genome[i][j] + int(rand_gauss(mu, sigma)) + + final_value = min(final_value, genome.getParam("rangemax", Consts.CDefRangeMax)) + final_value = max(final_value, genome.getParam("rangemin", Consts.CDefRangeMin)) + + genome.setItem(i, j, final_value) + mutations += 1 + else: + + for it in range(int(round(mutations))): + which_x = rand_randint(0, genome.getWidth() - 1) + which_y = rand_randint(0, genome.getHeight() - 1) + + final_value = genome[which_y][which_x] + int(rand_gauss(mu, sigma)) + + final_value = min(final_value, genome.getParam("rangemax", Consts.CDefRangeMax)) + final_value = max(final_value, genome.getParam("rangemin", Consts.CDefRangeMin)) + + genome.setItem(which_y, which_x, final_value) + + return int(mutations) + + +def G2DListMutatorAllele(genome, **args): + """ The mutator of G2DList, Allele Mutator + + To use this mutator, you must specify the *allele* genome parameter with the + :class:`GAllele.GAlleles` instance. + + .. warning:: the :class:`GAllele.GAlleles` instance must have the homogeneous flag enabled + + """ + if args["pmut"] <= 0.0: + return 0 + listSize = genome.getHeight() * genome.getWidth() - 1 + mutations = args["pmut"] * (listSize + 1) + + allele = genome.getParam("allele", None) + if allele is None: + Util.raiseException("to use the G2DListMutatorAllele, you must specify the 'allele' parameter", TypeError) + + if not allele.homogeneous: + Util.raiseException("to use the G2DListMutatorAllele, the 'allele' must be homogeneous") + + if mutations < 1.0: + mutations = 0 + + for i in range(genome.getHeight()): + for j in range(genome.getWidth()): + if Util.randomFlipCoin(args["pmut"]): + new_val = allele[0].getRandomAllele() + genome.setItem(i, j, new_val) + mutations += 1 + else: + for it in range(int(round(mutations))): + which_x = rand_randint(0, genome.getHeight() - 1) + which_y = rand_randint(0, genome.getWidth() - 1) + + new_val = allele[0].getRandomAllele() + genome.setItem(which_x, which_y, new_val) + + return int(mutations) + + +def G2DListMutatorRealGaussian(genome, **args): + """ A gaussian mutator for G2DList of Real + + Accepts the *rangemin* and *rangemax* genome parameters, both optional. Also + accepts the parameter *gauss_mu* and the *gauss_sigma* which respectively + represents the mean and the std. dev. of the random distribution. + + """ + from . import Consts + + if args["pmut"] <= 0.0: + return 0 + height, width = genome.getSize() + elements = height * width + + mutations = args["pmut"] * elements + + mu = genome.getParam("gauss_mu") + sigma = genome.getParam("gauss_sigma") + + if mu is None: + mu = Consts.CDefG2DListMutRealMU + + if sigma is None: + sigma = Consts.CDefG2DListMutRealSIGMA + + if mutations < 1.0: + mutations = 0 + + for i in range(genome.getHeight()): + for j in range(genome.getWidth()): + if Util.randomFlipCoin(args["pmut"]): + final_value = genome[i][j] + rand_gauss(mu, sigma) + + final_value = min(final_value, genome.getParam("rangemax", Consts.CDefRangeMax)) + final_value = max(final_value, genome.getParam("rangemin", Consts.CDefRangeMin)) + + genome.setItem(i, j, final_value) + mutations += 1 + else: + + for it in range(int(round(mutations))): + which_x = rand_randint(0, genome.getWidth() - 1) + which_y = rand_randint(0, genome.getHeight() - 1) + + final_value = genome[which_y][which_x] + rand_gauss(mu, sigma) + + final_value = min(final_value, genome.getParam("rangemax", Consts.CDefRangeMax)) + final_value = max(final_value, genome.getParam("rangemin", Consts.CDefRangeMin)) + + genome.setItem(which_y, which_x, final_value) + + return int(mutations) + +def G2DListMutatorRealGaussianGradient(genome, **args): + """ A gaussian gradient mutator for G2DList of Real + + Accepts the *rangemin* and *rangemax* genome parameters, both optional. + + The difference is that this multiplies the gene by gauss(1.0, 0.0333), allowing + for a smooth gradient drift about the value. + + """ + from . import Consts + + if args["pmut"] <= 0.0: + return 0 + height, width = genome.getSize() + elements = height * width + + mutations = args["pmut"] * elements + + mu = Consts.CDefGaussianGradientMU + sigma = Consts.CDefGaussianGradientSIGMA + + if mutations < 1.0: + mutations = 0 + + for i in range(genome.getHeight()): + for j in range(genome.getWidth()): + if Util.randomFlipCoin(args["pmut"]): + final_value = genome[i][j] * abs(rand_gauss(mu, sigma)) + + final_value = min(final_value, genome.getParam("rangemax", Consts.CDefRangeMax)) + final_value = max(final_value, genome.getParam("rangemin", Consts.CDefRangeMin)) + + genome.setItem(i, j, final_value) + mutations += 1 + else: + + for it in range(int(round(mutations))): + which_x = rand_randint(0, genome.getWidth() - 1) + which_y = rand_randint(0, genome.getHeight() - 1) + + final_value = genome[which_y][which_x] * abs(rand_gauss(mu, sigma)) + + final_value = min(final_value, genome.getParam("rangemax", Consts.CDefRangeMax)) + final_value = max(final_value, genome.getParam("rangemin", Consts.CDefRangeMin)) + + genome.setItem(which_y, which_x, final_value) + + return int(mutations) + +############################# +## 2D Binary String ## +############################# + +def G2DBinaryStringMutatorSwap(genome, **args): + """ The mutator of G2DBinaryString, Swap Mutator + + .. versionadded:: 0.6 + The *G2DBinaryStringMutatorSwap* function + """ + + if args["pmut"] <= 0.0: + return 0 + height, width = genome.getSize() + elements = height * width + + mutations = args["pmut"] * elements + + if mutations < 1.0: + mutations = 0 + for i in range(height): + for j in range(width): + if Util.randomFlipCoin(args["pmut"]): + index_b = (rand_randint(0, height - 1), rand_randint(0, width - 1)) + Util.list2DSwapElement(genome.genomeString, (i, j), index_b) + mutations += 1 + else: + for it in range(int(round(mutations))): + index_a = (rand_randint(0, height - 1), rand_randint(0, width - 1)) + index_b = (rand_randint(0, height - 1), rand_randint(0, width - 1)) + Util.list2DSwapElement(genome.genomeString, index_a, index_b) + + return int(mutations) + + +def G2DBinaryStringMutatorFlip(genome, **args): + """ A flip mutator for G2DBinaryString + + .. versionadded:: 0.6 + The *G2DBinaryStringMutatorFlip* function + """ + if args["pmut"] <= 0.0: + return 0 + height, width = genome.getSize() + elements = height * width + + mutations = args["pmut"] * elements + + if mutations < 1.0: + mutations = 0 + + for i in range(genome.getHeight()): + for j in range(genome.getWidth()): + if Util.randomFlipCoin(args["pmut"]): + if genome[i][j] == 0: + genome.setItem(i, j, 1) + else: + genome.setItem(i, j, 0) + mutations += 1 + else: + + for it in range(int(round(mutations))): + which_x = rand_randint(0, genome.getWidth() - 1) + which_y = rand_randint(0, genome.getHeight() - 1) + + if genome[i][j] == 0: + genome.setItem(which_y, which_x, 1) + else: + genome.setItem(which_y, which_x, 0) + + return int(mutations) + +################# +## Tree ## +################# +def GTreeMutatorSwap(genome, **args): + """ The mutator of GTree, Swap Mutator + + .. versionadded:: 0.6 + The *GTreeMutatorSwap* function + """ + if args["pmut"] <= 0.0: + return 0 + elements = len(genome) + mutations = args["pmut"] * elements + + if mutations < 1.0: + mutations = 0 + for i in range(len(genome)): + if Util.randomFlipCoin(args["pmut"]): + mutations += 1 + nodeOne = genome.getRandomNode() + nodeTwo = genome.getRandomNode() + nodeOne.swapNodeData(nodeTwo) + else: + for it in range(int(round(mutations))): + nodeOne = genome.getRandomNode() + nodeTwo = genome.getRandomNode() + nodeOne.swapNodeData(nodeTwo) + + return int(mutations) + + +def GTreeMutatorIntegerRange(genome, **args): + """ The mutator of GTree, Integer Range Mutator + + Accepts the *rangemin* and *rangemax* genome parameters, both optional. + + .. versionadded:: 0.6 + The *GTreeMutatorIntegerRange* function + """ + from . import Consts + + if args["pmut"] <= 0.0: + return 0 + elements = len(genome) + mutations = args["pmut"] * elements + + range_min = genome.getParam("rangemin", Consts.CDefRangeMin) + range_max = genome.getParam("rangemax", Consts.CDefRangeMax) + + if mutations < 1.0: + mutations = 0 + for i in range(len(genome)): + if Util.randomFlipCoin(args["pmut"]): + mutations += 1 + rand_node = genome.getRandomNode() + random_int = rand_randint(range_min, range_max) + rand_node.setData(random_int) + + else: + for it in range(int(round(mutations))): + rand_node = genome.getRandomNode() + random_int = rand_randint(range_min, range_max) + rand_node.setData(random_int) + + return int(mutations) + + +def GTreeMutatorRealRange(genome, **args): + """ The mutator of GTree, Real Range Mutator + + Accepts the *rangemin* and *rangemax* genome parameters, both optional. + + .. versionadded:: 0.6 + The *GTreeMutatorRealRange* function + """ + from . import Consts + + if args["pmut"] <= 0.0: + return 0 + elements = len(genome) + mutations = args["pmut"] * elements + + range_min = genome.getParam("rangemin", Consts.CDefRangeMin) + range_max = genome.getParam("rangemax", Consts.CDefRangeMax) + + if mutations < 1.0: + mutations = 0 + for i in range(len(genome)): + if Util.randomFlipCoin(args["pmut"]): + mutations += 1 + rand_node = genome.getRandomNode() + random_real = rand_uniform(range_min, range_max) + rand_node.setData(random_real) + + else: + for it in range(int(round(mutations))): + rand_node = genome.getRandomNode() + random_real = rand_uniform(range_min, range_max) + rand_node.setData(random_real) + + return int(mutations) + + +def GTreeMutatorIntegerGaussian(genome, **args): + """ A gaussian mutator for GTree of Integers + + Accepts the *rangemin* and *rangemax* genome parameters, both optional. Also + accepts the parameter *gauss_mu* and the *gauss_sigma* which respectively + represents the mean and the std. dev. of the random distribution. + + """ + from . import Consts + + if args["pmut"] <= 0.0: + return 0 + elements = len(genome) + mutations = args["pmut"] * elements + + mu = genome.getParam("gauss_mu", Consts.CDefG1DListMutIntMU) + sigma = genome.getParam("gauss_sigma", Consts.CDefG1DListMutIntSIGMA) + + if mutations < 1.0: + mutations = 0 + for i in range(len(genome)): + if Util.randomFlipCoin(args["pmut"]): + mutations += 1 + rand_node = genome.getRandomNode() + final_value = rand_node.getData() + int(rand_gauss(mu, sigma)) + final_value = min(final_value, genome.getParam("rangemax", Consts.CDefRangeMax)) + final_value = max(final_value, genome.getParam("rangemin", Consts.CDefRangeMin)) + rand_node.setData(final_value) + else: + for it in range(int(round(mutations))): + rand_node = genome.getRandomNode() + final_value = rand_node.getData() + int(rand_gauss(mu, sigma)) + final_value = min(final_value, genome.getParam("rangemax", Consts.CDefRangeMax)) + final_value = max(final_value, genome.getParam("rangemin", Consts.CDefRangeMin)) + rand_node.setData(final_value) + + return int(mutations) + + +def GTreeMutatorRealGaussian(genome, **args): + """ A gaussian mutator for GTree of Real numbers + + Accepts the *rangemin* and *rangemax* genome parameters, both optional. Also + accepts the parameter *gauss_mu* and the *gauss_sigma* which respectively + represents the mean and the std. dev. of the random distribution. + + """ + if args["pmut"] <= 0.0: + return 0 + elements = len(genome) + mutations = args["pmut"] * elements + + mu = genome.getParam("gauss_mu", Consts.CDefG1DListMutRealMU) + sigma = genome.getParam("gauss_sigma", Consts.CDefG1DListMutRealSIGMA) + + if mutations < 1.0: + mutations = 0 + for i in range(len(genome)): + if Util.randomFlipCoin(args["pmut"]): + mutations += 1 + rand_node = genome.getRandomNode() + final_value = rand_node.getData() + rand_gauss(mu, sigma) + final_value = min(final_value, genome.getParam("rangemax", Consts.CDefRangeMax)) + final_value = max(final_value, genome.getParam("rangemin", Consts.CDefRangeMin)) + rand_node.setData(final_value) + else: + for it in range(int(round(mutations))): + rand_node = genome.getRandomNode() + final_value = rand_node.getData() + rand_gauss(mu, sigma) + final_value = min(final_value, genome.getParam("rangemax", Consts.CDefRangeMax)) + final_value = max(final_value, genome.getParam("rangemin", Consts.CDefRangeMin)) + rand_node.setData(final_value) + + return int(mutations) + +################### +## Tree GP ## +################### + +def GTreeGPMutatorOperation(genome, **args): + """ The mutator of GTreeGP, Operation Mutator + + .. versionadded:: 0.6 + The *GTreeGPMutatorOperation* function + """ + + if args["pmut"] <= 0.0: + return 0 + elements = len(genome) + mutations = args["pmut"] * elements + ga_engine = args["ga_engine"] + + gp_terminals = ga_engine.getParam("gp_terminals") + assert gp_terminals is not None + + gp_function_set = ga_engine.getParam("gp_function_set") + assert gp_function_set is not None + + if mutations < 1.0: + mutations = 0 + for i in range(len(genome)): + if Util.randomFlipCoin(args["pmut"]): + mutations += 1 + rand_node = genome.getRandomNode() + assert rand_node is not None + if rand_node.getType() == Consts.nodeType["TERMINAL"]: + term_operator = rand_choice(gp_terminals) + else: + op_len = gp_function_set[rand_node.getData()] + fun_candidates = [] + for o, l in list(gp_function_set.items()): + if l == op_len: + fun_candidates.append(o) + + if len(fun_candidates) <= 0: + continue + + term_operator = rand_choice(fun_candidates) + rand_node.setData(term_operator) + else: + for it in range(int(round(mutations))): + rand_node = genome.getRandomNode() + assert rand_node is not None + if rand_node.getType() == Consts.nodeType["TERMINAL"]: + term_operator = rand_choice(gp_terminals) + else: + op_len = gp_function_set[rand_node.getData()] + fun_candidates = [] + for o, l in list(gp_function_set.items()): + if l == op_len: + fun_candidates.append(o) + + if len(fun_candidates) <= 0: + continue + + term_operator = rand_choice(fun_candidates) + rand_node.setData(term_operator) + + return int(mutations) + + +def GTreeGPMutatorSubtree(genome, **args): + """ The mutator of GTreeGP, Subtree Mutator + + This mutator will recreate random subtree of the tree using the grow algorithm. + + .. versionadded:: 0.6 + The *GTreeGPMutatorSubtree* function + """ + + if args["pmut"] <= 0.0: + return 0 + ga_engine = args["ga_engine"] + max_depth = genome.getParam("max_depth", None) + mutations = 0 + + if max_depth is None: + Util.raiseException("You must specify the max_depth genome parameter !", ValueError) + + if max_depth < 0: + Util.raiseException("The max_depth must be >= 1, if you want to use GTreeGPMutatorSubtree crossover !", ValueError) + + branch_list = genome.nodes_branch + elements = len(branch_list) + + for i in range(elements): + + node = branch_list[i] + assert node is not None + + if Util.randomFlipCoin(args["pmut"]): + depth = genome.getNodeDepth(node) + mutations += 1 + + root_subtree = GTree.buildGTreeGPGrow(ga_engine, 0, max_depth - depth) + node_parent = node.getParent() + + if node_parent is None: + genome.setRoot(root_subtree) + genome.processNodes() + return mutations + else: + root_subtree.setParent(node_parent) + node_parent.replaceChild(node, root_subtree) + genome.processNodes() + + return int(mutations) diff --git a/Network.py b/Network.py new file mode 100644 index 0000000..92c2dae --- /dev/null +++ b/Network.py @@ -0,0 +1,445 @@ +""" + +:mod:`Network` -- network utility module +============================================================================ + +In this module you'll find all the network related implementation + +.. versionadded:: 0.6 + The *Network* module. + +""" + + +import threading +import socket +import time +import sys +from . import Util +import pickle + +try: + import zlib + ZLIB_SUPPORT = True +except ImportError: + ZLIB_SUPPORT = False + +from . import Consts +import logging + +def getMachineIP(): + """ Return all the IPs from current machine. + + Example: + >>> Util.getMachineIP() + ['200.12.124.181', '192.168.0.1'] + + :rtype: a python list with the string IPs + + """ + hostname = socket.gethostname() + addresses = socket.getaddrinfo(hostname, None) + ips = [x[4][0] for x in addresses] + return ips + +class UDPThreadBroadcastClient(threading.Thread): + """ The Broadcast UDP client thread class. + + This class is a thread to serve as Pyevolve client on the UDP + datagrams, it is used to send data over network lan/wan. + + Example: + >>> s = Network.UDPThreadClient('192.168.0.2', 1500, 666) + >>> s.setData("Test data") + >>> s.start() + >>> s.join() + + :param host: the hostname to bind the socket on sender (this is NOT the target host) + :param port: the sender port (this is NOT the target port) + :param target_port: the destination port target + + """ + def __init__(self, host, port, target_port): + super(UDPThreadBroadcastClient, self).__init__() + self.host = host + self.port = port + self.targetPort = target_port + self.data = None + self.sentBytes = None + self.sentBytesLock = threading.Lock() + + self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) + self.sock.bind((host, port)) + + def setData(self, data): + """ Set the data to send + + :param data: the data to send + + """ + self.data = data + + def getData(self): + """ Get the data to send + + :rtype: data to send + + """ + return self.data + + def close(self): + """ Close the internal socket """ + self.sock.close() + + def getSentBytes(self): + """ Returns the number of sent bytes. The use of this method makes sense + when you already have sent the data + + :rtype: sent bytes + + """ + sent = None + with self.sentBytesLock: + if self.sentBytes is None: + Util.raiseException('Bytes sent is None') + else: + sent = self.sentBytes + return sent + + def send(self): + """ Broadcasts the data """ + return self.sock.sendto(self.data, (Consts.CDefBroadcastAddress, self.targetPort)) + + def run(self): + """ Method called when you call *.start()* of the thread """ + if self.data is None: + Util.raiseException('You must set the data with setData method', ValueError) + + with self.sentBytesLock: + self.sentBytes = self.send() + self.close() + +class UDPThreadUnicastClient(threading.Thread): + """ The Unicast UDP client thread class. + + This class is a thread to serve as Pyevolve client on the UDP + datagrams, it is used to send data over network lan/wan. + + Example: + >>> s = Network.UDPThreadClient('192.168.0.2', 1500) + >>> s.setData("Test data") + >>> s.setTargetHost('192.168.0.50', 666) + >>> s.start() + >>> s.join() + + :param host: the hostname to bind the socket on sender (this is not the target host) + :param port: the sender port (this is not the target port) + :param pool_size: the size of send pool + :param timeout: the time interval to check if the client have data to send + + """ + def __init__(self, host, port, pool_size=10, timeout=0.5): + super(UDPThreadUnicastClient, self).__init__() + self.host = host + self.port = port + self.target = [] + self.sendPool = [] + self.poolSize = pool_size + self.sendPoolLock = threading.Lock() + self.timeout = timeout + + self.doshutdown = False + + self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.sock.bind((host, port)) + + def poolLength(self): + """ Returns the size of the pool + + :rtype: integer + + """ + with self.sendPoolLock: + ret = len(self.sendPool) + return ret + + def popPool(self): + """ Return the last data received on the pool + + :rtype: object + + """ + with self.sendPoolLock: + ret = self.sendPool.pop() + return ret + + def isReady(self): + """ Returns True when there is data on the pool or False when not + + :rtype: boolean + + """ + with self.sendPoolLock: + ret = True if len(self.sendPool) >= 1 else False + return ret + + def shutdown(self): + """ Shutdown the server thread, when called, this method will stop + the thread on the next socket timeout """ + self.doshutdown = True + + def addData(self, data): + """ Set the data to send + + :param data: the data to send + + """ + if self.poolLength() >= self.poolSize: + logging.warning('the send pool is full, consider increasing the pool size or decreasing the timeout !') + return + + with self.sendPoolLock: + self.sendPool.append(data) + + def setTargetHost(self, host, port): + """ Set the host/port of the target, the destination + + :param host: the target host + :param port: the target port + + .. note:: the host will be ignored when using broadcast mode + """ + del self.target[:] + self.target.append((host, port)) + + def setMultipleTargetHost(self, address_list): + """ Sets multiple host/port targets, the destinations + + :param address_list: a list with tuples (ip, port) + """ + del self.target[:] + self.target = address_list[:] + + def close(self): + """ Close the internal socket """ + self.sock.close() + + def send(self, data): + """ Send the data + + :param data: the data to send + :rtype: bytes sent to each destination + """ + bytes = -1 + for destination in self.target: + bytes = self.sock.sendto(data, destination) + return bytes + + def run(self): + """ Method called when you call *.start()* of the thread """ + if len(self.target) <= 0: + Util.raiseException('You must set the target(s) before send data', ValueError) + + while True: + if self.doshutdown: + break + + while self.isReady(): + data = self.popPool() + self.send(data) + + time.sleep(self.timeout) + + self.close() + +class UDPThreadServer(threading.Thread): + """ The UDP server thread class. + + This class is a thread to serve as Pyevolve server on the UDP + datagrams, it is used to receive data from network lan/wan. + + Example: + >>> s = UDPThreadServer("192.168.0.2", 666, 10) + >>> s.start() + >>> s.shutdown() + + :param host: the host to bind the server + :param port: the server port to bind + :param poolSize: the size of the server pool + :param timeout: the socket timeout + + .. note:: this thread implements a pool to keep the received data, + the *poolSize* parameter specifies how much individuals + we must keep on the pool until the *popPool* method + is called; when the pool is full, the sever will + discard the received individuals. + + """ + def __init__(self, host, port, poolSize=10, timeout=3): + super(UDPThreadServer, self).__init__() + self.recvPool = [] + self.recvPoolLock = threading.Lock() + self.bufferSize = 4096 + self.host = host + self.port = port + self.timeout = timeout + self.doshutdown = False + self.poolSize = poolSize + + self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.sock.bind((host, port)) + self.sock.settimeout(self.timeout) + + def shutdown(self): + """ Shutdown the server thread, when called, this method will stop + the thread on the next socket timeout """ + self.doshutdown = True + + def isReady(self): + """ Returns True when there is data on the pool or False when not + + :rtype: boolean + + """ + with self.recvPoolLock: + ret = True if len(self.recvPool) >= 1 else False + return ret + + def poolLength(self): + """ Returns the size of the pool + + :rtype: integer + + """ + with self.recvPoolLock: + ret = len(self.recvPool) + return ret + + def popPool(self): + """ Return the last data received on the pool + + :rtype: object + + """ + with self.recvPoolLock: + ret = self.recvPool.pop() + return ret + + def close(self): + """ Closes the internal socket """ + self.sock.close() + + def setBufferSize(self, size): + """ Sets the receive buffer size + + :param size: integer + + """ + self.bufferSize = size + + def getBufferSize(self): + """ Gets the current receive buffer size + + :rtype: integer + + """ + return self.bufferSize + + def getData(self): + """ Calls the socket *recvfrom* method and waits for the data, + when the data is received, the method will return a tuple + with the IP of the sender and the data received. When a timeout + exception occurs, the method return None. + + :rtype: tuple (sender ip, data) or None when timeout exception + + """ + try: + data, sender = self.sock.recvfrom(self.bufferSize) + except socket.timeout: + return None + return (sender[0], data) + + def run(self): + """ Called when the thread is started by the user. This method + is the main of the thread, when called, it will enter in loop + to wait data or shutdown when needed. + """ + while True: + # Get the data + data = self.getData() + # Shutdown called + if self.doshutdown: + break + # The pool is full + if self.poolLength() >= self.poolSize: + continue + # There is no data received + if data is None: + continue + # It's a packet from myself + if data[0] == self.host: + continue + with self.recvPoolLock: + self.recvPool.append(data) + + self.close() + +def pickleAndCompress(obj, level=9): + """ Pickles the object and compress the dumped string with zlib + + :param obj: the object to be pickled + :param level: the compression level, 9 is the best + and -1 is to not compress + + """ + pickled = pickle.dumps(obj) + if level < 0: + return pickled + else: + if not ZLIB_SUPPORT: + Util.raiseException('zlib not found !', ImportError) + pickled_zlib = zlib.compress(pickled, level) + return pickled_zlib + +def unpickleAndDecompress(obj_dump, decompress=True): + """ Decompress a zlib compressed string and unpickle the data + + :param obj: the object to be decompressend and unpickled + """ + if decompress: + if not ZLIB_SUPPORT: + Util.raiseException('zlib not found !', ImportError) + obj_decompress = zlib.decompress(obj_dump) + else: + obj_decompress = obj_dump + return pickle.loads(obj_decompress) + +if __name__ == "__main__": + arg = sys.argv[1] + myself = getMachineIP() + + if arg == "server": + s = UDPThreadServer(myself[0], 666) + s.start() + + while True: + print(".", end="") + time.sleep(10) + if s.isReady(): + item = s.popPool() + print(item) + time.sleep(4) + s.shutdown() + break + elif arg == "client": + print("Binding on %s..." % myself[0]) + s = UDPThreadUnicastClient(myself[0], 1500) + s.setData("dsfssdfsfddf") + s.setTargetHost(myself[0], 666) + s.start() + s.join() + print(s.getSentBytes()) + + print("end...") diff --git a/Scaling.py b/Scaling.py new file mode 100644 index 0000000..9ce6b1e --- /dev/null +++ b/Scaling.py @@ -0,0 +1,133 @@ +""" + +:mod:`Scaling` -- scaling schemes module +=========================================================== + +This module have the *scaling schemes* like Linear scaling, etc. + +""" +from future.builtins import range + +import math +import logging + +def LinearScaling(pop): + """ Linear Scaling scheme + + .. warning :: Linear Scaling is only for positive raw scores + + """ + from . import Consts, Util + logging.debug("Running linear scaling.") + pop.statistics() + c = Consts.CDefScaleLinearMultiplier + a = b = delta = 0.0 + + pop_rawAve = pop.stats["rawAve"] + pop_rawMax = pop.stats["rawMax"] + pop_rawMin = pop.stats["rawMin"] + + if pop_rawAve == pop_rawMax: + a = 1.0 + b = 0.0 + elif pop_rawMin > (c * pop_rawAve - pop_rawMax / c - 1.0): + delta = pop_rawMax - pop_rawAve + a = (c - 1.0) * pop_rawAve / delta + b = pop_rawAve * (pop_rawMax - (c * pop_rawAve)) / delta + else: + delta = pop_rawAve - pop_rawMin + a = pop_rawAve / delta + b = -pop_rawMin * pop_rawAve / delta + + for i in range(len(pop)): + f = pop[i].score + if f < 0.0: + Util.raiseException("Score %r is negative, linear scaling not supported !" % (f,), ValueError) + f = f * a + b + if f < 0: + f = 0.0 + pop[i].fitness = f + +def SigmaTruncScaling(pop): + """ Sigma Truncation scaling scheme, allows negative scores """ + from . import Consts + logging.debug("Running sigma truncation scaling.") + pop.statistics() + c = Consts.CDefScaleSigmaTruncMultiplier + pop_rawAve = pop.stats["rawAve"] + pop_rawDev = pop.stats["rawDev"] + for i in range(len(pop)): + f = pop[i].score - pop_rawAve + f += c * pop_rawDev + if f < 0: + f = 0.0 + pop[i].fitness = f + +def PowerLawScaling(pop): + """ Power Law scaling scheme + + .. warning :: Power Law Scaling is only for positive raw scores + + """ + from . import Consts + logging.debug("Running power law scaling.") + k = Consts.CDefScalePowerLawFactor + for i in range(len(pop)): + f = pop[i].score + if f < 0.0: + Util.raiseException("Score %r is negative, power law scaling not supported !" % (f,), ValueError) + f = math.pow(f, k) + pop[i].fitness = f + + +def BoltzmannScaling(pop): + """ Boltzmann scaling scheme. You can specify the **boltz_temperature** to the + population parameters, this parameter will set the start temperature. You + can specify the **boltz_factor** and the **boltz_min** parameters, the **boltz_factor** + is the value that the temperature will be subtracted and the **boltz_min** is the + mininum temperature of the scaling scheme. + + .. versionadded: 0.6 + The `BoltzmannScaling` function. + + """ + boltz_temperature = pop.getParam("boltz_temperature", Consts.CDefScaleBoltzStart) + boltz_factor = pop.getParam("boltz_factor", Consts.CDefScaleBoltzFactor) + boltz_min = pop.getParam("boltz_min", Consts.CDefScaleBoltzMinTemp) + + boltz_temperature -= boltz_factor + boltz_temperature = max(boltz_temperature, boltz_min) + pop.setParams(boltzTemperature=boltz_temperature) + + boltz_e = [] + avg = 0.0 + + for i in range(len(pop)): + val = math.exp(pop[i].score / boltz_temperature) + boltz_e.append(val) + avg += val + + avg /= len(pop) + + for i in range(len(pop)): + pop[i].fitness = boltz_e[i] / avg + +def ExponentialScaling(pop): + """ Exponential Scaling Scheme. The fitness will be the same as (e^score). + + .. versionadded: 0.6 + The `ExponentialScaling` function. + """ + for i in range(len(pop)): + score = pop[i].score + pop[i].fitness = math.exp(score) + +def SaturatedScaling(pop): + """ Saturated Scaling Scheme. The fitness will be the same as 1.0-(e^score) + + .. versionadded: 0.6 + The `SaturatedScaling` function. + """ + for i in range(len(pop)): + score = pop[i].score + pop[i].fitness = 1.0 - math.exp(score) diff --git a/Selectors.py b/Selectors.py new file mode 100644 index 0000000..0ecc8f8 --- /dev/null +++ b/Selectors.py @@ -0,0 +1,183 @@ +""" + +:mod:`Selectors` -- selection methods module +============================================================== + +This module have the *selection methods*, like roulette wheel, tournament, ranking, etc. + +""" +from future.builtins import range + +import random + +def GRankSelector(population, **args): + """ The Rank Selector - This selector will pick the best individual of + the population every time. + """ + from . import Consts + count = 0 + + if args["popID"] != GRankSelector.cachePopID: + if population.sortType == Consts.sortType["scaled"]: + best_fitness = population.bestFitness().fitness + for index in range(1, len(population.internalPop)): + if population[index].fitness == best_fitness: + count += 1 + else: + best_raw = population.bestRaw().score + for index in range(1, len(population.internalPop)): + if population[index].score == best_raw: + count += 1 + + GRankSelector.cachePopID = args["popID"] + GRankSelector.cacheCount = count + + else: + count = GRankSelector.cacheCount + + return population[random.randint(0, count)] + +GRankSelector.cachePopID = None +GRankSelector.cacheCount = None + +def GUniformSelector(population, **args): + """ The Uniform Selector """ + return population[random.randint(0, len(population) - 1)] + +def GTournamentSelector(population, **args): + """ The Tournament Selector + + It accepts the *tournamentPool* population parameter. + + .. note:: + the Tournament Selector uses the Roulette Wheel to + pick individuals for the pool + + .. versionchanged:: 0.6 + Changed the parameter `poolSize` to the `tournamentPool`, now the selector + gets the pool size from the population. + + """ + from . import Consts + choosen = None + should_minimize = population.minimax == Consts.minimaxType["minimize"] + minimax_operator = min if should_minimize else max + + poolSize = population.getParam("tournamentPool", Consts.CDefTournamentPoolSize) + tournament_pool = [GRouletteWheel(population, **args) for i in range(poolSize)] + + if population.sortType == Consts.sortType["scaled"]: + choosen = minimax_operator(tournament_pool, key=lambda ind: ind.fitness) + else: + choosen = minimax_operator(tournament_pool, key=lambda ind: ind.score) + + return choosen + +def GTournamentSelectorAlternative(population, **args): + """ The alternative Tournament Selector + + This Tournament Selector don't uses the Roulette Wheel + + It accepts the *tournamentPool* population parameter. + + .. versionadded: 0.6 + Added the GTournamentAlternative function. + + """ + from . import Consts + pool_size = population.getParam("tournamentPool", Consts.CDefTournamentPoolSize) + len_pop = len(population) + should_minimize = population.minimax == Consts.minimaxType["minimize"] + minimax_operator = min if should_minimize else max + tournament_pool = [population[random.randint(0, len_pop - 1)] for i in range(pool_size)] + + if population.sortType == Consts.sortType["scaled"]: + choosen = minimax_operator(tournament_pool, key=lambda ind: ind.fitness) + else: + choosen = minimax_operator(tournament_pool, key=lambda ind: ind.score) + + return choosen + +def GRouletteWheel(population, **args): + """ The Roulette Wheel selector """ + psum = None + if args["popID"] != GRouletteWheel.cachePopID: + GRouletteWheel.cachePopID = args["popID"] + psum = GRouletteWheel_PrepareWheel(population) + GRouletteWheel.cacheWheel = psum + else: + psum = GRouletteWheel.cacheWheel + + cutoff = random.random() + lower = 0 + upper = len(population) - 1 + while(upper >= lower): + i = lower + ((upper - lower) // 2) + if psum[i] > cutoff: + upper = i - 1 + else: + lower = i + 1 + + lower = min(len(population) - 1, lower) + lower = max(0, lower) + + return population.bestFitness(lower) + +GRouletteWheel.cachePopID = None +GRouletteWheel.cacheWheel = None + +def GRouletteWheel_PrepareWheel(population): + """ A preparation for Roulette Wheel selection """ + from . import Consts + + len_pop = len(population) + + psum = [i for i in range(len_pop)] + + population.statistics() + + if population.sortType == Consts.sortType["scaled"]: + pop_fitMax = population.stats["fitMax"] + pop_fitMin = population.stats["fitMin"] + + if pop_fitMax == pop_fitMin: + for index in range(len_pop): + psum[index] = (index + 1) / float(len_pop) + elif (pop_fitMax > 0 and pop_fitMin >= 0) or (pop_fitMax <= 0 and pop_fitMin < 0): + population.sort() + if population.minimax == Consts.minimaxType["maximize"]: + psum[0] = population[0].fitness + for i in range(1, len_pop): + psum[i] = population[i].fitness + psum[i - 1] + for i in range(len_pop): + psum[i] /= float(psum[len_pop - 1]) + else: + psum[0] = -population[0].fitness + pop_fitMax + pop_fitMin + for i in range(1, len_pop): + psum[i] = -population[i].fitness + pop_fitMax + pop_fitMin + psum[i - 1] + for i in range(len_pop): + psum[i] /= float(psum[len_pop - 1]) + else: + pop_rawMax = population.stats["rawMax"] + pop_rawMin = population.stats["rawMin"] + + if pop_rawMax == pop_rawMin: + for index in range(len_pop): + psum[index] = (index + 1) / float(len_pop) + + elif (pop_rawMax > 0 and pop_rawMin >= 0) or (pop_rawMax <= 0 and pop_rawMin < 0): + population.sort() + if population.minimax == Consts.minimaxType["maximize"]: + psum[0] = population[0].score + for i in range(1, len_pop): + psum[i] = population[i].score + psum[i - 1] + for i in range(len_pop): + psum[i] /= float(psum[len_pop - 1]) + else: + psum[0] = - population[0].score + pop_rawMax + pop_rawMin + for i in range(1, len_pop): + psum[i] = - population[i].score + pop_rawMax + pop_rawMin + psum[i - 1] + for i in range(len_pop): + psum[i] /= float(psum[len_pop - 1]) + + return psum diff --git a/Statistics.py b/Statistics.py new file mode 100644 index 0000000..0cf6111 --- /dev/null +++ b/Statistics.py @@ -0,0 +1,106 @@ +""" + +:mod:`Statistics` -- statistical structure module +========================================================================== + +This module have the class which is reponsible to keep statistics of each +generation. This class is used by the adapters and other statistics dump objects. + +""" + + +class Statistics(object): + """ Statistics Class - A class bean-like to store the statistics + + The statistics hold by this class are: + + **rawMax, rawMin, rawAve** + Maximum, minimum and average of raw scores + + **rawDev, rawVar** + Standard Deviation and Variance of raw scores + + **fitMax, fitMin, fitAve** + Maximum, mininum and average of fitness scores + + **rawTot, fitTot** + The total (sum) of raw scores and the fitness scores + + Example: + >>> stats = ga_engine.getStatistics() + >>> st["rawMax"] + 10.2 + """ + + def __init__(self): + """ The Statistics Class creator """ + + # 'fit' means 'fitness' + self.internalDict = { + "rawMax": 0.0, + "rawMin": 0.0, + "rawAve": 0.0, + "rawDev": 0.0, + "rawVar": 0.0, + "fitMax": 0.0, + "fitMin": 0.0, + "fitAve": 0.0 + } + + self.descriptions = { + "rawMax": "Maximum raw score", + "rawMin": "Minimum raw score", + "rawAve": "Average of raw scores", + "rawDev": "Standard deviation of raw scores", + "rawVar": "Raw scores variance", + "fitMax": "Maximum fitness", + "fitMin": "Minimum fitness", + "fitAve": "Fitness average", + } + + def __getitem__(self, key): + """ Return the specific statistic by key """ + return self.internalDict[key] + + def __setitem__(self, key, value): + """ Set the statistic """ + self.internalDict[key] = value + + def __len__(self): + """ Return the length of internal stats dictionary """ + return len(self.internalDict) + + def __repr__(self): + """ Return a string representation of the statistics """ + strBuff = "- Statistics\n" + for k, v in list(self.internalDict.items()): + strBuff += "\t%-45s = %.2f\n" % (self.descriptions.get(k, k), v) + return strBuff + + def asTuple(self): + """ Returns the stats as a python tuple """ + return tuple(self.internalDict.values()) + + def clear(self): + """ Set all statistics to zero """ + for k in list(self.internalDict.keys()): + self.internalDict[k] = 0 + + def items(self): + """ Return a tuple (name, value) for all stored statistics """ + return list(self.internalDict.items()) + + def clone(self): + """ Instantiate a new Statistic class with the same contents """ + clone_stat = Statistics() + self.copy(clone_stat) + return clone_stat + + def copy(self, obj): + """ Copy the values to the obj variable of the same class + + :param obj: the Statistics object destination + + """ + obj.internalDict = self.internalDict.copy() + obj.descriptions = self.descriptions.copy() diff --git a/Util.py b/Util.py new file mode 100644 index 0000000..c5e49b3 --- /dev/null +++ b/Util.py @@ -0,0 +1,351 @@ +""" + +:mod:`Util` -- utility module +============================================================================ + +This is the utility module, with some utility functions of general +use, like list item swap, random utilities and etc. + +""" +from future.builtins import range + +from random import random as rand_random +from math import sqrt as math_sqrt +import logging + + +def randomFlipCoin(p): + """Returns True with the *p* probability. If *p* is 1, the + function will always return True. If *p* is 0, the function will + return always False. + + Example: + >>> Util.randomFlipCoin(1.0) + True + + :param p: probability, between 0.0 and 1.0 + :rtype: True or False + + """ + if p == 1.0: + return True + if p == 0.0: + return False + + return rand_random() <= p + + +def listSwapElement(lst, indexa, indexb): + """ Swaps elements A and B in a list. + + Example: + >>> l = [1, 2, 3] + >>> Util.listSwapElement(l, 1, 2) + >>> l + [1, 3, 2] + + :param lst: the list + :param indexa: the swap element A + :param indexb: the swap element B + :rtype: None + + """ + lst[indexa], lst[indexb] = lst[indexb], lst[indexa] + + +def list2DSwapElement(lst, indexa, indexb): + """ Swaps elements A and B in a 2D list (matrix). + + Example: + >>> l = [ [1,2,3], [4,5,6] ] + >>> Util.list2DSwapElement(l, (0,1), (1,1) ) + >>> l + [[1, 5, 3], [4, 2, 6]] + + :param lst: the list + :param indexa: the swap element A + :param indexb: the swap element B + :rtype: None + + """ + temp = lst[indexa[0]][indexa[1]] + lst[indexa[0]][indexa[1]] = lst[indexb[0]][indexb[1]] + lst[indexb[0]][indexb[1]] = temp + + +def raiseException(message, expt=None): + """ Raise an exception and logs the message. + + Example: + >>> Util.raiseException('The value is not an integer', ValueError) + + :param message: the message of exception + :param expt: the exception class + :rtype: None + + """ + logging.critical(message) + if expt is None: + raise Exception(message) + else: + raise expt(message) + + +def cmp_individual_raw(a, b): + """ Compares two individual raw scores + + Example: + >>> GPopulation.cmp_individual_raw(a, b) + + :param a: the A individual instance + :param b: the B individual instance + :rtype: 0 if the two individuals raw score are the same, + -1 if the B individual raw score is greater than A and + 1 if the A individual raw score is greater than B. + + .. note:: this function is used to sorte the population individuals + + """ + if a.score < b.score: + return -1 + if a.score > b.score: + return 1 + return 0 + + +def cmp_individual_scaled(a, b): + """ Compares two individual fitness scores, used for sorting population + + Example: + >>> GPopulation.cmp_individual_scaled(a, b) + + :param a: the A individual instance + :param b: the B individual instance + :rtype: 0 if the two individuals fitness score are the same, + -1 if the B individual fitness score is greater than A and + 1 if the A individual fitness score is greater than B. + + .. note:: this function is used to sorte the population individuals + + """ + if a.fitness < b.fitness: + return -1 + if a.fitness > b.fitness: + return 1 + return 0 + + +def importSpecial(name): + """ This function will import the *name* module, if fails, + it will raise an ImportError exception and a message + + :param name: the module name + :rtype: the module object + + .. versionadded:: 0.6 + The *import_special* function + """ + from . import Consts + + try: + imp_mod = __import__(name) + except ImportError: + raiseException("Cannot import module %s: %s" % (name, Consts.CDefImportList[name]), expt=ImportError) + return imp_mod + + +class ErrorAccumulator(object): + """ An accumulator for the Root Mean Square Error (RMSE) and the + Mean Square Error (MSE) + """ + def __init__(self): + self.acc = 0.0 + self.acc_square = 0.0 + self.acc_len = 0 + + def reset(self): + """ Reset the accumulator """ + self.acc_square = 0.0 + self.acc = 0.0 + self.acc_len = 0 + + def append(self, target, evaluated): + """ Add value to the accumulator + + :param target: the target value + :param evaluated: the evaluated value + """ + self.acc_square += (target - evaluated) ** 2 + self.acc += abs(target - evaluated) + self.acc_len += 1 + + def __iadd__(self, value): + """ The same as append, but you must pass a tuple """ + self.append(*value) + return self + + def getMean(self): + """ Return the mean of the non-squared accumulator """ + return self.acc / self.acc_len + + def getSquared(self): + """ Returns the squared accumulator """ + return self.acc_square + + def getNonSquared(self): + """ Returns the non-squared accumulator """ + return self.acc + + def getAdjusted(self): + """ Returns the adjusted fitness + This fitness is calculated as 1 / (1 + standardized fitness) + """ + return 1.0 / (1.0 + self.acc) + + def getRMSE(self): + """ Return the root mean square error + + :rtype: float RMSE + """ + return math_sqrt(self.acc_square / float(self.acc_len)) + + def getMSE(self): + """ Return the mean square error + + :rtype: float MSE + """ + return self.acc_square / float(self.acc_len) + + +class Graph(object): + """ The Graph class + + Example: + >>> g = Graph() + >>> g.addEdge("a", "b") + >>> g.addEdge("b", "c") + >>> for node in g: + ... print node + a + b + c + + .. versionadded:: 0.6 + The *Graph* class. + """ + + def __init__(self): + """ The constructor """ + self.adjacent = {} + + def __iter__(self): + """ Returns an iterator to the all graph elements """ + return iter(self.adjacent) + + def addNode(self, node): + """ Add the node + + :param node: the node to add + """ + if node not in self.adjacent: + self.adjacent[node] = {} + + def __iadd__(self, node): + """ Add a node using the += operator """ + self.addNode(node) + return self + + def addEdge(self, a, b): + """ Add an edge between two nodes, if the nodes + doesn't exists, they will be created + + :param a: the first node + :param b: the second node + """ + if a not in self.adjacent: + self.adjacent[a] = {} + + if b not in self.adjacent: + self.adjacent[b] = {} + + self.adjacent[a][b] = True + self.adjacent[b][a] = True + + def getNodes(self): + """ Returns all the current nodes on the graph + + :rtype: the list of nodes + """ + return list(self.adjacent.keys()) + + def reset(self): + """ Deletes all nodes of the graph """ + self.adjacent.clear() + + def getNeighbors(self, node): + """ Returns the neighbors of the node + + :param node: the node + """ + return list(self.adjacent[node].keys()) + + def __getitem__(self, node): + """ Returns the adjacent nodes of the node """ + return list(self.adjacent[node].keys()) + + def __repr__(self): + ret = "- Graph\n" + ret += "\tNode list:\n" + for node in self: + ret += "\t\tNode [%s] = %s\n" % (node, self.getNeighbors(node)) + return ret + + +def G1DListGetEdgesComposite(mom, dad): + """ Get the edges and the merge between the edges of two G1DList individuals + + :param mom: the mom G1DList individual + :param dad: the dad G1DList individual + :rtype: a tuple (mom edges, dad edges, merge) + """ + mom_edges = G1DListGetEdges(mom) + dad_edges = G1DListGetEdges(dad) + return mom_edges, dad_edges, G1DListMergeEdges(mom_edges, dad_edges) + + +def G1DListGetEdges(individual): + """ Get the edges of a G1DList individual + + :param individual: the G1DList individual + :rtype: the edges dictionary + """ + edg = {} + ind_list = individual.getInternalList() + for i in range(len(ind_list)): + a, b = ind_list[i], ind_list[i - 1] + + if a not in edg: + edg[a] = [] + else: + edg[a].append(b) + + if b not in edg: + edg[b] = [] + else: + edg[b].append(a) + return edg + + +def G1DListMergeEdges(eda, edb): + """ Get the merge between the two individual edges + + :param eda: the edges of the first G1DList genome + :param edb: the edges of the second G1DList genome + :rtype: the merged dictionary + """ + edges = {} + for value, near in list(eda.items()): + for adj in near: + if (value in edb) and (adj in edb[value]): + edges.setdefault(value, []).append(adj) + return edges diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..4936e89 --- /dev/null +++ b/__init__.py @@ -0,0 +1,45 @@ +""" +:mod:`pyevolve` -- the main pyevolve namespace +================================================================ + +This is the main module of the pyevolve, every other module +is above this namespace, for example, to import :mod:`Mutators`: + + >>> from pyevolve import Mutators + + +""" +__all__ = ["Consts", "Crossovers", "DBAdapters", "FunctionSlot", + "G1DBinaryString", "G1DList", "G2DBinaryString", + "G2DList", "GAllele", "GenomeBase", "GPopulation", + "GSimpleGA", "GTree", "Initializators", + "Migration", "Mutators", "Network", "Scaling", "Selectors", + "Statistics", "Util"] + +__version__ = '0.6' +__author__ = 'Christian S. Perone' + +import pyevolve.Consts +import sys + +if sys.version_info[:2] < Consts.CDefPythonRequire: + raise Exception("Python 2.5+ required, the version %s was found on your system !" % (sys.version_info[:2],)) + +del sys + +def logEnable(filename=Consts.CDefLogFile, level=Consts.CDefLogLevel): + """ Enable the log system for pyevolve + + :param filename: the log filename + :param level: the debugging level + + Example: + >>> pyevolve.logEnable() + + """ + import logging + logging.basicConfig(level=level, + format='%(asctime)s [%(module)s:%(funcName)s:%(lineno)d] %(levelname)s %(message)s', + filename=filename, + filemode='w') + logging.info("Pyevolve v.%s, the log was enabled by user.", __version__) diff --git a/pyevolve_ex10_g1dbinstr.py b/pyevolve_ex10_g1dbinstr.py new file mode 100644 index 0000000..527fcc1 --- /dev/null +++ b/pyevolve_ex10_g1dbinstr.py @@ -0,0 +1,40 @@ +from pyevolve import G1DBinaryString +from pyevolve import GSimpleGA +from pyevolve import Selectors +from pyevolve import Mutators + +# This function is the evaluation function, we want +# to give high score to more zero'ed chromosomes +def eval_func(chromosome): + score = 0.0 + + # iterate over the chromosome + for value in chromosome: + if value == 0: + score += 0.1 + + return score + +def run_main(): + # Genome instance + genome = G1DBinaryString.G1DBinaryString(50) + + # The evaluator function (objective function) + genome.evaluator.set(eval_func) + genome.mutator.set(Mutators.G1DBinaryStringMutatorFlip) + + # Genetic Algorithm Instance + ga = GSimpleGA.GSimpleGA(genome) + ga.selector.set(Selectors.GTournamentSelector) + ga.setGenerations(70) + + # Do the evolution, with stats dump + # frequency of 10 generations + ga.evolve(freq_stats=20) + + # Best individual + print(ga.bestIndividual()) + +if __name__ == "__main__": + run_main() + diff --git a/pyevolve_ex11_allele.py b/pyevolve_ex11_allele.py new file mode 100644 index 0000000..d64c668 --- /dev/null +++ b/pyevolve_ex11_allele.py @@ -0,0 +1,72 @@ +from pyevolve import G1DList +from pyevolve import GSimpleGA +from pyevolve import Mutators +from pyevolve import Initializators +from pyevolve import GAllele + +# This function is the evaluation function, we want +# to give high score to more zero'ed chromosomes +def eval_func(chromosome): + score = 0.0 + + # iterate over the chromosome + for value in chromosome: + if value == 0: + score += 0.5 + + # Remember from the allele set defined above + # this value 'a' is possible at this position + if chromosome[18] == 'a': + score += 1.0 + + # Remember from the allele set defined above + # this value 'xxx' is possible at this position + if chromosome[12] == 'xxx': + score += 1.0 + + return score + +def run_main(): + # Genome instance + setOfAlleles = GAllele.GAlleles() + + # From 0 to 10 we can have only some + # defined ranges of integers + for i in range(11): + a = GAllele.GAlleleRange(0, i) + setOfAlleles.add(a) + + # From 11 to 19 we can have a set + # of elements + for i in range(11, 20): + # You can even add objects instead of strings or + # primitive values + a = GAllele.GAlleleList(['a','b', 'xxx', 666, 0]) + setOfAlleles.add(a) + + genome = G1DList.G1DList(20) + genome.setParams(allele=setOfAlleles) + + # The evaluator function (objective function) + genome.evaluator.set(eval_func) + + # This mutator and initializator will take care of + # initializing valid individuals based on the allele set + # that we have defined before + genome.mutator.set(Mutators.G1DListMutatorAllele) + genome.initializator.set(Initializators.G1DListInitializatorAllele) + + # Genetic Algorithm Instance + ga = GSimpleGA.GSimpleGA(genome) + ga.setGenerations(40) + + # Do the evolution, with stats dump + # frequency of 10 generations + ga.evolve(freq_stats=5) + + # Best individual + print(ga.bestIndividual()) + + +if __name__ == "__main__": + run_main() \ No newline at end of file diff --git a/pyevolve_ex12_tsp.py b/pyevolve_ex12_tsp.py new file mode 100644 index 0000000..3e08358 --- /dev/null +++ b/pyevolve_ex12_tsp.py @@ -0,0 +1,132 @@ +from pyevolve import G1DList, GAllele +from pyevolve import GSimpleGA +from pyevolve import Mutators +from pyevolve import Crossovers +from pyevolve import Consts + +import sys, random +random.seed(1024) +from math import sqrt + +print('Seems totally broken in Python 3, exit') +sys.exit() + +PIL_SUPPORT = None + +try: + from PIL import Image, ImageDraw, ImageFont + PIL_SUPPORT = True +except ImportError: + PIL_SUPPORT = False + + +cm = [] +coords = [] +CITIES = 100 +WIDTH = 1024 +HEIGHT = 768 +LAST_SCORE = -1 + +def cartesian_matrix(coords): + """ A distance matrix """ + matrix={} + for i,(x1,y1) in enumerate(coords): + for j,(x2,y2) in enumerate(coords): + dx, dy = x1-x2, y1-y2 + dist=sqrt(dx*dx + dy*dy) + matrix[i,j] = dist + return matrix + +def tour_length(matrix, tour): + """ Returns the total length of the tour """ + total = 0 + t = tour.getInternalList() + for i in range(CITIES): + j = (i+1)%CITIES + total += matrix[t[i], t[j]] + return total + +def write_tour_to_img(coords, tour, img_file): + """ The function to plot the graph """ + padding=20 + coords=[(x+padding,y+padding) for (x,y) in coords] + maxx,maxy=0,0 + for x,y in coords: + maxx, maxy = max(x,maxx), max(y,maxy) + maxx+=padding + maxy+=padding + img=Image.new("RGB",(int(maxx),int(maxy)),color=(255,255,255)) + font=ImageFont.load_default() + d=ImageDraw.Draw(img); + num_cities=len(tour) + for i in range(num_cities): + j=(i+1)%num_cities + city_i=tour[i] + city_j=tour[j] + x1,y1=coords[city_i] + x2,y2=coords[city_j] + d.line((int(x1),int(y1),int(x2),int(y2)),fill=(0,0,0)) + d.text((int(x1)+7,int(y1)-5),str(i),font=font,fill=(32,32,32)) + + for x,y in coords: + x,y=int(x),int(y) + d.ellipse((x-5,y-5,x+5,y+5),outline=(0,0,0),fill=(196,196,196)) + del d + img.save(img_file, "PNG") + print("The plot was saved into the %s file." % (img_file,)) + +def G1DListTSPInitializator(genome, **args): + """ The initializator for the TSP """ + lst = [i for i in range(genome.getListSize())] + random.shuffle(lst) + genome.setInternalList(lst) + +# This is to make a video of best individuals along the evolution +# Use mencoder to create a video with the file list list.txt +# mencoder mf://@list.txt -mf w=400:h=200:fps=3:type=png -ovc lavc +# -lavcopts vcodec=mpeg4:mbd=2:trell -oac copy -o output.avi +# +def evolve_callback(ga_engine): + global LAST_SCORE + if ga_engine.getCurrentGeneration() % 100 == 0: + best = ga_engine.bestIndividual() + if LAST_SCORE != best.getRawScore(): + write_tour_to_img( coords, best, "tspimg/tsp_result_%d.png" % ga_engine.getCurrentGeneration()) + LAST_SCORE = best.getRawScore() + return False + +def main_run(): + global cm, coords, WIDTH, HEIGHT + + coords = [(random.randint(0, WIDTH), random.randint(0, HEIGHT)) + for i in range(CITIES)] + cm = cartesian_matrix(coords) + genome = G1DList.G1DList(len(coords)) + + genome.evaluator.set(lambda chromosome: tour_length(cm, chromosome)) + genome.crossover.set(Crossovers.G1DListCrossoverEdge) + genome.initializator.set(G1DListTSPInitializator) + + # 3662.69 + ga = GSimpleGA.GSimpleGA(genome) + ga.setGenerations(200000) + ga.setMinimax(Consts.minimaxType["minimize"]) + ga.setCrossoverRate(1.0) + ga.setMutationRate(0.02) + ga.setPopulationSize(80) + + # This is to make a video + ga.stepCallback.set(evolve_callback) + # 21666.49 + + + ga.evolve(freq_stats=500) + best = ga.bestIndividual() + + if PIL_SUPPORT: + write_tour_to_img(coords, best, "tsp_result.png") + else: + print("No PIL detected, cannot plot the graph !") + +if __name__ == "__main__": + main_run() diff --git a/pyevolve_ex13_sphere.py b/pyevolve_ex13_sphere.py new file mode 100644 index 0000000..083b53b --- /dev/null +++ b/pyevolve_ex13_sphere.py @@ -0,0 +1,30 @@ +from pyevolve import G1DList +from pyevolve import Mutators, Initializators +from pyevolve import GSimpleGA, Consts + +# This is the Sphere Function +def sphere(xlist): + total = 0 + for i in xlist: + total += i**2 + return total + +def run_main(): + genome = G1DList.G1DList(140) + genome.setParams(rangemin=-5.12, rangemax=5.13) + genome.initializator.set(Initializators.G1DListInitializatorReal) + genome.mutator.set(Mutators.G1DListMutatorRealGaussian) + genome.evaluator.set(sphere) + + ga = GSimpleGA.GSimpleGA(genome, seed=666) + ga.setMinimax(Consts.minimaxType["minimize"]) + ga.setGenerations(1500) + ga.setMutationRate(0.01) + ga.evolve(freq_stats=500) + + best = ga.bestIndividual() + +if __name__ == "__main__": + run_main() + + diff --git a/pyevolve_ex14_ackley.py b/pyevolve_ex14_ackley.py new file mode 100644 index 0000000..109ee0c --- /dev/null +++ b/pyevolve_ex14_ackley.py @@ -0,0 +1,54 @@ +from pyevolve import G1DList, GSimpleGA, Selectors +from pyevolve import Initializators, Mutators, Consts, DBAdapters +import math + +# This is the Rastringin Function, a deception function +def ackley(xlist): + sum1 = 0 + score = 0 + n = len(xlist) + for i in range(n): + sum1 += xlist[i]*xlist[i] + t1 = math.exp(-0.2*(math.sqrt((1.0/5.0)*sum1))) + + sum1 = 0 + for i in range(n): + sum1 += math.cos(2.0*math.pi*xlist[i]); + t2 = math.exp((1.0/5.0)*sum1); + score = 20 + math.exp(1) - 20 * t1 - t2; + + return score + + +def run_main(): + # Genome instance + genome = G1DList.G1DList(5) + genome.setParams(rangemin=-8, rangemax=8, bestrawscore=0.00, rounddecimal=2) + genome.initializator.set(Initializators.G1DListInitializatorReal) + genome.mutator.set(Mutators.G1DListMutatorRealGaussian) + + # The evaluator function (objective function) + genome.evaluator.set(ackley) + + # Genetic Algorithm Instance + ga = GSimpleGA.GSimpleGA(genome) + ga.setMinimax(Consts.minimaxType["minimize"]) + ga.setGenerations(1000) + ga.setMutationRate(0.04) + ga.terminationCriteria.set(GSimpleGA.RawScoreCriteria) + + # Create DB Adapter and set as adapter + # sqlite_adapter = DBAdapters.DBSQLite(identify="ackley") + # ga.setDBAdapter(sqlite_adapter) + + # Do the evolution, with stats dump + # frequency of 10 generations + ga.evolve(freq_stats=50) + + # Best individual + best = ga.bestIndividual() + print("\nBest individual score: %.2f" % (best.getRawScore(),)) + print(best) + +if __name__ == "__main__": + run_main() diff --git a/pyevolve_ex15_rosenbrock.py b/pyevolve_ex15_rosenbrock.py new file mode 100644 index 0000000..c714368 --- /dev/null +++ b/pyevolve_ex15_rosenbrock.py @@ -0,0 +1,52 @@ +from pyevolve import G1DList, GSimpleGA, Selectors, Statistics +from pyevolve import Initializators, Mutators, Consts, DBAdapters + +# This is the Rosenbrock Function +def rosenbrock(xlist): + sum_var = 0 + for x in range(1, len(xlist)): + sum_var += 100.0 * (xlist[x] - xlist[x-1]**2)**2 + (1 - xlist[x-1])**2 + return sum_var + +def run_main(): + # Genome instance + genome = G1DList.G1DList(15) + genome.setParams(rangemin=-1, rangemax=1.1) + genome.initializator.set(Initializators.G1DListInitializatorReal) + genome.mutator.set(Mutators.G1DListMutatorRealRange) + + # The evaluator function (objective function) + genome.evaluator.set(rosenbrock) + + # Genetic Algorithm Instance + ga = GSimpleGA.GSimpleGA(genome) + ga.setMinimax(Consts.minimaxType["minimize"]) + ga.selector.set(Selectors.GRouletteWheel) + ga.setGenerations(4000) + ga.setCrossoverRate(0.9) + ga.setPopulationSize(100) + ga.setMutationRate(0.03) + + ga.evolve(freq_stats=500) + + # Best individual + best = ga.bestIndividual() + print("\nBest individual score: %.2f" % (best.score,)) + print(best) + + +if __name__ == "__main__": + run_main() + + + + + + + + + + + + + diff --git a/pyevolve_ex16_g2dbinstr.py b/pyevolve_ex16_g2dbinstr.py new file mode 100644 index 0000000..9a67c6d --- /dev/null +++ b/pyevolve_ex16_g2dbinstr.py @@ -0,0 +1,37 @@ +from pyevolve import G2DBinaryString +from pyevolve import GSimpleGA +from pyevolve import Selectors +from pyevolve import Crossovers +from pyevolve import Mutators + +# This function is the evaluation function, we want +# to give high score to more zero'ed chromosomes +def eval_func(chromosome): + score = 0.0 + + # iterate over the chromosome + for i in range(chromosome.getHeight()): + for j in range(chromosome.getWidth()): + # You can use the chromosome.getItem(i, j) + if chromosome[i][j]==0: + score += 0.1 + return score + +# Genome instance +genome = G2DBinaryString.G2DBinaryString(8, 5) + +# The evaluator function (objective function) +genome.evaluator.set(eval_func) +genome.crossover.set(Crossovers.G2DBinaryStringXSingleHPoint) +genome.mutator.set(Mutators.G2DBinaryStringMutatorSwap) + +# Genetic Algorithm Instance +ga = GSimpleGA.GSimpleGA(genome) +ga.setGenerations(200) + +# Do the evolution, with stats dump +# frequency of 10 generations +ga.evolve(freq_stats=10) + +# Best individual +print(ga.bestIndividual()) diff --git a/pyevolve_ex17_gtree.py b/pyevolve_ex17_gtree.py new file mode 100644 index 0000000..95d2218 --- /dev/null +++ b/pyevolve_ex17_gtree.py @@ -0,0 +1,44 @@ +from pyevolve import GSimpleGA +from pyevolve import GTree +from pyevolve import Crossovers +from pyevolve import Mutators +import time +import random + +def eval_func(chromosome): + score = 0.0 + # If you want to add score values based + # in the height of the Tree, the extra + # code is commented. + + #height = chromosome.getHeight() + + for node in chromosome: + score += (100 - node.getData())*0.1 + + #if height <= chromosome.getParam("max_depth"): + # score += (score*0.8) + + return score + +def run_main(): + genome = GTree.GTree() + root = GTree.GTreeNode(2) + genome.setRoot(root) + genome.processNodes() + + genome.setParams(max_depth=3, max_siblings=2, method="grow") + genome.evaluator.set(eval_func) + genome.crossover.set(Crossovers.GTreeCrossoverSinglePointStrict) + + ga = GSimpleGA.GSimpleGA(genome) + ga.setGenerations(100) + ga.setMutationRate(0.05) + + ga.evolve(freq_stats=10) + print(ga.bestIndividual()) + +if __name__ == "__main__": + run_main() + + diff --git a/pyevolve_ex18_gp.py b/pyevolve_ex18_gp.py new file mode 100644 index 0000000..4726290 --- /dev/null +++ b/pyevolve_ex18_gp.py @@ -0,0 +1,47 @@ +from pyevolve import Util +from pyevolve import GTree +from pyevolve import GSimpleGA +from pyevolve import Consts +import math + +rmse_accum = Util.ErrorAccumulator() + +def gp_add(a, b): return a+b +def gp_sub(a, b): return a-b +def gp_mul(a, b): return a*b +def gp_sqrt(a): return math.sqrt(abs(a)) + +def eval_func(chromosome): + global rmse_accum + rmse_accum.reset() + code_comp = chromosome.getCompiledCode() + + for a in range(0, 5): + for b in range(0, 5): + evaluated = eval(code_comp) + target = math.sqrt((a*a)+(b*b)) + rmse_accum += (target, evaluated) + + return rmse_accum.getRMSE() + +def main_run(): + genome = GTree.GTreeGP() + genome.setParams(max_depth=4, method="ramped") + genome.evaluator += eval_func + + ga = GSimpleGA.GSimpleGA(genome) + ga.setParams(gp_terminals = ['a', 'b'], + gp_function_prefix = "gp") + + ga.setMinimax(Consts.minimaxType["minimize"]) + ga.setGenerations(50) + ga.setCrossoverRate(1.0) + ga.setMutationRate(0.25) + ga.setPopulationSize(800) + + ga(freq_stats=10) + best = ga.bestIndividual() + print(best) + +if __name__ == "__main__": + main_run() diff --git a/pyevolve_ex19_gp.py b/pyevolve_ex19_gp.py new file mode 100644 index 0000000..ca5adcd --- /dev/null +++ b/pyevolve_ex19_gp.py @@ -0,0 +1,94 @@ +from pyevolve import GSimpleGA +from pyevolve import GTree +from pyevolve import Consts +from pyevolve import Selectors +from pyevolve import Mutators +from math import sqrt +import pydot_ng as pydot +import random + +def gp_add(a, b): + assert len(a)==len(b) + new_list = [x+y for x,y in zip(a,b)] + return new_list + +#def gp_sub(a, b): +# assert len(a)==len(b) +# new_list = [x-y for x,y in zip(a,b)] +# return new_list + +def prot_div(a, b): + if b==0: + return b + else: + return a/b + +#def gp_div(a,b): +# assert len(a)==len(b) +# new_list = [prot_div(x,float(y)) for x,y in zip(a,b)] +# return new_list + +def gp_mul(a,b): + assert len(a)==len(b) + new_list = [x*y for x,y in zip(a,b)] + return new_list + +def random_lists(size): + list_a = [random.randint(1,20) for i in range(size)] + list_b = [random.randint(1,20) for i in range(size)] + + return (list_a, list_b) + + +def eval_func(chromosome): + sz = 20 + code_comp = chromosome.getCompiledCode() + square_accum = 0.0 + + for j in range(sz): + a, b = random_lists(5) + target_list = gp_add(gp_mul(a,b),gp_mul(a,b)) + ret_list = eval(code_comp) + square_accum += (sum(target_list)-sum(ret_list))**2 + + RMSE = sqrt(square_accum / float(sz)) + score = (1.0 / (RMSE+1.0)) + return score + +def main_run(): + genome = GTree.GTreeGP() + root = GTree.GTreeNodeGP('a', Consts.nodeType["TERMINAL"]) + genome.setRoot(root) + + genome.setParams(max_depth=2, method="ramped") + genome.evaluator += eval_func + genome.mutator.set(Mutators.GTreeGPMutatorSubtree) + + ga = GSimpleGA.GSimpleGA(genome) + ga.setParams(gp_terminals = ['a', 'b'], + gp_function_prefix = "gp") + + ga.setMinimax(Consts.minimaxType["maximize"]) + ga.setGenerations(500) + ga.setCrossoverRate(1.0) + ga.setMutationRate(0.08) + ga.setPopulationSize(80) + + ga(freq_stats=1) + print(ga.bestIndividual()) + + graph = pydot.Dot() + ga.bestIndividual().writeDotGraph(graph) + graph.write_jpeg('tree.png', prog='dot') + +if __name__ == "__main__": + main_run() + #import hotshot, hotshot.stats + #prof = hotshot.Profile("ev.prof") + #prof.runcall(main_run) + #prof.close() + #stats = hotshot.stats.load("ev.prof") + #stats.strip_dirs() + #stats.sort_stats('time', 'calls') + #stats.print_stats(20) + diff --git a/pyevolve_ex1_simple.py b/pyevolve_ex1_simple.py new file mode 100644 index 0000000..0f80297 --- /dev/null +++ b/pyevolve_ex1_simple.py @@ -0,0 +1,54 @@ +from pyevolve import G1DList +from pyevolve import GSimpleGA +from pyevolve import Selectors +from pyevolve import Statistics +from pyevolve import DBAdapters + +# This function is the evaluation function, we want +# to give high score to more zero'ed chromosomes +def eval_func(genome): + score = 0.0 + + # iterate over the chromosome + # The same as "score = len(filter(lambda x: x==0, genome))" + for value in genome: + if value==0: + score += 1 + + return score + +def run_main(): + # Genome instance, 1D List of 50 elements + genome = G1DList.G1DList(50) + + # Sets the range max and min of the 1D List + genome.setParams(rangemin=0, rangemax=10) + + # The evaluator function (evaluation function) + genome.evaluator.set(eval_func) + + # Genetic Algorithm Instance + ga = GSimpleGA.GSimpleGA(genome) + + # Set the Roulette Wheel selector method, the number of generations and + # the termination criteria + ga.selector.set(Selectors.GRouletteWheel) + ga.setGenerations(500) + ga.terminationCriteria.set(GSimpleGA.ConvergenceCriteria) + + # Sets the DB Adapter, the resetDB flag will make the Adapter recreate + # the database and erase all data every run, you should use this flag + # just in the first time, after the pyevolve.db was created, you can + # omit it. + sqlite_adapter = DBAdapters.DBSQLite(identify="ex1", resetDB=True) + ga.setDBAdapter(sqlite_adapter) + + # Do the evolution, with stats dump + # frequency of 20 generations + ga.evolve(freq_stats=20) + + # Best individual + print(ga.bestIndividual()) + +if __name__ == "__main__": + run_main() diff --git a/pyevolve_ex20_gp_dotwrite.py b/pyevolve_ex20_gp_dotwrite.py new file mode 100644 index 0000000..ac8d92e --- /dev/null +++ b/pyevolve_ex20_gp_dotwrite.py @@ -0,0 +1,56 @@ +from pyevolve import * +import math + +rmse_accum = Util.ErrorAccumulator() + +def gp_add(a, b): return a+b +def gp_sub(a, b): return a-b +def gp_mul(a, b): return a*b +def gp_sqrt(a): return math.sqrt(abs(a)) + +def eval_func(chromosome): + global rmse_accum + rmse_accum.reset() + code_comp = chromosome.getCompiledCode() + + for a in range(0, 5): + for b in range(0, 5): + evaluated = eval(code_comp) + target = math.sqrt((a*a)+(b*b)) + rmse_accum += (target, evaluated) + return rmse_accum.getRMSE() + + +def step_callback(engine): + if engine.getCurrentGeneration() == 0: + GTree.GTreeGP.writePopulationDotRaw(engine, "pop.dot", 0, 40) + return False + + +def main_run(): + genome = GTree.GTreeGP() + genome.setParams(max_depth=6, method="ramped") + genome.evaluator += eval_func + genome.mutator.set(Mutators.GTreeGPMutatorSubtree) + + ga = GSimpleGA.GSimpleGA(genome, seed=666) + ga.stepCallback.set(step_callback) + ga.setParams(gp_terminals = ['a', 'b'], + gp_function_prefix = "gp") + + ga.setMinimax(Consts.minimaxType["minimize"]) + ga.setGenerations(2) + ga.setCrossoverRate(1.0) + ga.setMutationRate(0.08) + ga.setPopulationSize(100) + ga.setMultiProcessing(False) + + ga(freq_stats=5) + + #GTree.GTreeGP.writePopulationDotRaw(ga, "pop.dot", 0, 14) + + best = ga.bestIndividual() + + +if __name__ == "__main__": + main_run() diff --git a/pyevolve_ex21_nqueens.py b/pyevolve_ex21_nqueens.py new file mode 100644 index 0000000..e2fad78 --- /dev/null +++ b/pyevolve_ex21_nqueens.py @@ -0,0 +1,58 @@ +from pyevolve import G1DList +from pyevolve import Mutators, Crossovers +from pyevolve import Consts, GSimpleGA +from pyevolve import DBAdapters +from random import shuffle + +# The "n" in n-queens +BOARD_SIZE = 64 + +# The n-queens fitness function +def queens_eval(genome): + collisions = 0 + for i in range(0, BOARD_SIZE): + if i not in genome: return 0 + for i in range(0, BOARD_SIZE): + col = False + for j in range(0, BOARD_SIZE): + if (i != j) and (abs(i-j) == abs(genome[j]-genome[i])): + col = True + if col == True: collisions +=1 + return BOARD_SIZE-collisions + +def queens_init(genome, **args): + genome.genomeList = list(range(0, BOARD_SIZE)) + shuffle(genome.genomeList) + +def run_main(): + genome = G1DList.G1DList(BOARD_SIZE) + genome.setParams(bestrawscore=BOARD_SIZE, rounddecimal=2) + genome.initializator.set(queens_init) + genome.mutator.set(Mutators.G1DListMutatorSwap) + genome.crossover.set(Crossovers.G1DListCrossoverCutCrossfill) + genome.evaluator.set(queens_eval) + + ga = GSimpleGA.GSimpleGA(genome) + ga.terminationCriteria.set(GSimpleGA.RawScoreCriteria) + ga.setMinimax(Consts.minimaxType["maximize"]) + + ga.setPopulationSize(100) + ga.setGenerations(250) + ga.setMutationRate(0.02) + ga.setCrossoverRate(1.0) + + #sqlite_adapter = DBAdapters.DBSQLite(identify="queens") + #ga.setDBAdapter(sqlite_adapter) + + vpython_adapter = DBAdapters.DBVPythonGraph(identify="queens", frequency=1) + ga.setDBAdapter(vpython_adapter) + + ga.evolve(freq_stats=10) + + best = ga.bestIndividual() + print(best) + print("Best individual score: %.2f\n" % (best.getRawScore(),)) + +if __name__ == "__main__": + run_main() + diff --git a/pyevolve_ex22_monkey.py b/pyevolve_ex22_monkey.py new file mode 100644 index 0000000..e0232f6 --- /dev/null +++ b/pyevolve_ex22_monkey.py @@ -0,0 +1,54 @@ +#=============================================================================== +# Pyevolve version of the Infinite Monkey Theorem +# See: http://en.wikipedia.org/wiki/Infinite_monkey_theorem +# By Jelle Feringa +#=============================================================================== + +from pyevolve import G1DList +from pyevolve import GSimpleGA, Consts +from pyevolve import Selectors +from pyevolve import Initializators, Mutators, Crossovers +import math + +sentence = """ +'Just living is not enough,' said the butterfly, +'one must have sunshine, freedom, and a little flower.' +""" +numeric_sentence = list(map(ord, sentence)) + +def evolve_callback(ga_engine): + generation = ga_engine.getCurrentGeneration() + if generation%50==0: + indiv = ga_engine.bestIndividual() + print(''.join(map(chr,indiv))) + return False + +def run_main(): + genome = G1DList.G1DList(len(sentence)) + genome.setParams(rangemin=min(numeric_sentence), + rangemax=max(numeric_sentence), + bestrawscore=0.00, + gauss_mu=1, gauss_sigma=4) + + genome.initializator.set(Initializators.G1DListInitializatorInteger) + genome.mutator.set(Mutators.G1DListMutatorIntegerGaussian) + genome.evaluator.set(lambda genome: sum( + [abs(a-b) for a, b in zip(genome, numeric_sentence)] + )) + + ga = GSimpleGA.GSimpleGA(genome) + #ga.stepCallback.set(evolve_callback) + ga.setMinimax(Consts.minimaxType["minimize"]) + ga.terminationCriteria.set(GSimpleGA.RawScoreCriteria) + ga.setPopulationSize(60) + ga.setMutationRate(0.02) + ga.setCrossoverRate(0.9) + ga.setGenerations(5000) + ga.evolve(freq_stats=100) + + best = ga.bestIndividual() + print("Best individual score: %.2f" % (best.score,)) + print(''.join(map(chr, best))) + +if __name__ == "__main__": + run_main() diff --git a/pyevolve_ex2_realgauss.py b/pyevolve_ex2_realgauss.py new file mode 100644 index 0000000..f9e217a --- /dev/null +++ b/pyevolve_ex2_realgauss.py @@ -0,0 +1,42 @@ +from pyevolve import GSimpleGA +from pyevolve import G1DList +from pyevolve import Selectors +from pyevolve import Initializators, Mutators + +# Find negative element +def eval_func(genome): + score = 0.0 + + for element in genome: + if element < 0: score += 0.1 + + return score + +def run_main(): + # Genome instance + genome = G1DList.G1DList(20) + genome.setParams(rangemin=-6.0, rangemax=6.0) + + # Change the initializator to Real values + genome.initializator.set(Initializators.G1DListInitializatorReal) + + # Change the mutator to Gaussian Mutator + genome.mutator.set(Mutators.G1DListMutatorRealGaussian) + + # The evaluator function (objective function) + genome.evaluator.set(eval_func) + + # Genetic Algorithm Instance + ga = GSimpleGA.GSimpleGA(genome) + ga.selector.set(Selectors.GRouletteWheel) + ga.setGenerations(100) + + # Do the evolution + ga.evolve(freq_stats=10) + + # Best individual + print(ga.bestIndividual()) + +if __name__ == "__main__": + run_main() + diff --git a/pyevolve_ex3_schaffer.py b/pyevolve_ex3_schaffer.py new file mode 100644 index 0000000..d103057 --- /dev/null +++ b/pyevolve_ex3_schaffer.py @@ -0,0 +1,46 @@ +from pyevolve import G1DList, GSimpleGA, Selectors +from pyevolve import Initializators, Mutators, Consts +import math + +# This is the Schaffer F6 Function +# This function has been conceived by Schaffer, it's a +# multimodal function and it's hard for GAs due to the +# large number of local minima, the global minimum is +# at x=0,y=0 and there are many local minima around it +def schafferF6(genome): + t1 = math.sin(math.sqrt(genome[0]**2 + genome[1]**2)); + t2 = 1.0 + 0.001*(genome[0]**2 + genome[1]**2); + score = 0.5 + (t1*t1 - 0.5)/(t2*t2) + return score + +def run_main(): + # Genome instance + genome = G1DList.G1DList(2) + genome.setParams(rangemin=-100.0, rangemax=100.0, bestrawscore=0.0000, rounddecimal=4) + genome.initializator.set(Initializators.G1DListInitializatorReal) + genome.mutator.set(Mutators.G1DListMutatorRealGaussian) + + # The evaluator function (objective function) + genome.evaluator.set(schafferF6) + + # Genetic Algorithm Instance + ga = GSimpleGA.GSimpleGA(genome) + ga.selector.set(Selectors.GRouletteWheel) + + ga.setMinimax(Consts.minimaxType["minimize"]) + ga.setGenerations(8000) + ga.setMutationRate(0.05) + ga.setPopulationSize(100) + ga.terminationCriteria.set(GSimpleGA.RawScoreCriteria) + + # Do the evolution, with stats dump + # frequency of 10 generations + ga.evolve(freq_stats=250) + + # Best individual + best = ga.bestIndividual() + print(best) + print("Best individual score: %.2f" % best.getRawScore()) + +if __name__ == "__main__": + run_main() diff --git a/pyevolve_ex4_sigmatrunc.py b/pyevolve_ex4_sigmatrunc.py new file mode 100644 index 0000000..58adef9 --- /dev/null +++ b/pyevolve_ex4_sigmatrunc.py @@ -0,0 +1,49 @@ +from pyevolve import G1DList +from pyevolve import GSimpleGA +from pyevolve import Selectors +from pyevolve import Initializators, Mutators +from pyevolve import Scaling +from pyevolve import Consts +import math + +def eval_func(ind): + score = 0.0 + var_x = ind[0] + var_z = var_x**2+2*var_x+1*math.cos(var_x) + return var_z + +def run_main(): + # Genome instance + genome = G1DList.G1DList(1) + genome.setParams(rangemin=-60.0, rangemax=60.0) + + # Change the initializator to Real values + genome.initializator.set(Initializators.G1DListInitializatorReal) + + # Change the mutator to Gaussian Mutator + genome.mutator.set(Mutators.G1DListMutatorRealGaussian) + + # Removes the default crossover + genome.crossover.clear() + + # The evaluator function (objective function) + genome.evaluator.set(eval_func) + + # Genetic Algorithm Instance + ga = GSimpleGA.GSimpleGA(genome) + ga.setMinimax(Consts.minimaxType["minimize"]) + + pop = ga.getPopulation() + pop.scaleMethod.set(Scaling.SigmaTruncScaling) + + ga.selector.set(Selectors.GRouletteWheel) + ga.setGenerations(100) + + # Do the evolution + ga.evolve(10) + + # Best individual + print(ga.bestIndividual()) + +if __name__ == "__main__": + run_main() diff --git a/pyevolve_ex5_callback.py b/pyevolve_ex5_callback.py new file mode 100644 index 0000000..8f660a5 --- /dev/null +++ b/pyevolve_ex5_callback.py @@ -0,0 +1,45 @@ +from pyevolve import G1DList +from pyevolve import GSimpleGA +from pyevolve import Selectors + +# The step callback function, this function +# will be called every step (generation) of the GA evolution +def evolve_callback(ga_engine): + generation = ga_engine.getCurrentGeneration() + if generation % 100 == 0: + print("Current generation: %d" % (generation,)) + print(ga_engine.getStatistics()) + return False + +# This function is the evaluation function, we want +# to give high score to more zero'ed chromosomes +def eval_func(genome): + score = 0.0 + # iterate over the chromosome + for value in genome: + if value==0: score += 0.1 + return score + +def run_main(): + # Genome instance + genome = G1DList.G1DList(200) + genome.setParams(rangemin=0, rangemax=10) + + # The evaluator function (objective function) + genome.evaluator.set(eval_func) + + # Genetic Algorithm Instance + ga = GSimpleGA.GSimpleGA(genome) + ga.selector.set(Selectors.GRouletteWheel) + ga.setGenerations(800) + ga.stepCallback.set(evolve_callback) + + # Do the evolution + ga.evolve() + + # Best individual + print(ga.bestIndividual()) + +if __name__ == "__main__": + run_main() + diff --git a/pyevolve_ex6_dbadapter.py b/pyevolve_ex6_dbadapter.py new file mode 100644 index 0000000..2a6a454 --- /dev/null +++ b/pyevolve_ex6_dbadapter.py @@ -0,0 +1,47 @@ +from pyevolve import G1DList +from pyevolve import GSimpleGA +from pyevolve import Selectors +from pyevolve import DBAdapters +from pyevolve import Statistics + +# This function is the evaluation function, we want +# to give high score to more zero'ed chromosomes +def eval_func(chromosome): + score = 0.0 + + # iterate over the chromosome + for value in chromosome: + if value==0: + score += 0.5 + return score + +# Genome instance +genome = G1DList.G1DList(100) +genome.setParams(rangemin=0, rangemax=10) + +# The evaluator function (objective function) +genome.evaluator.set(eval_func) + +# Genetic Algorithm Instance +ga = GSimpleGA.GSimpleGA(genome, 666) +ga.setGenerations(80) +ga.setMutationRate(0.2) + +# Create DB Adapter and set as adapter +#sqlite_adapter = DBAdapters.DBSQLite(identify="ex6", resetDB=True) +#ga.setDBAdapter(sqlite_adapter) + +# Using CSV Adapter +#csvfile_adapter = DBAdapters.DBFileCSV() +#ga.setDBAdapter(csvfile_adapter) + +# Using the URL Post Adapter +# urlpost_adapter = DBAdapters.DBURLPost(url="http://whatismyip.oceanus.ro/server_variables.php", post=False) +# ga.setDBAdapter(urlpost_adapter) + +# Do the evolution, with stats dump +# frequency of 10 generations +ga.evolve(freq_stats=10) + +# Best individual +#print ga.bestIndividual() diff --git a/pyevolve_ex7_rastrigin.py b/pyevolve_ex7_rastrigin.py new file mode 100644 index 0000000..bc6d3cf --- /dev/null +++ b/pyevolve_ex7_rastrigin.py @@ -0,0 +1,40 @@ +from pyevolve import GSimpleGA +from pyevolve import G1DList +from pyevolve import Mutators, Initializators +from pyevolve import Selectors +from pyevolve import Consts +import math + +# This is the Rastrigin Function, a deception function +def rastrigin(genome): + n = len(genome) + total = 0 + for i in range(n): + total += genome[i]**2 - 10*math.cos(2*math.pi*genome[i]) + return (10*n) + total + +def run_main(): + # Genome instance + genome = G1DList.G1DList(20) + genome.setParams(rangemin=-5.2, rangemax=5.30, bestrawscore=0.00, rounddecimal=2) + genome.initializator.set(Initializators.G1DListInitializatorReal) + genome.mutator.set(Mutators.G1DListMutatorRealGaussian) + + genome.evaluator.set(rastrigin) + + # Genetic Algorithm Instance + ga = GSimpleGA.GSimpleGA(genome) + ga.terminationCriteria.set(GSimpleGA.RawScoreCriteria) + ga.setMinimax(Consts.minimaxType["minimize"]) + ga.setGenerations(3000) + ga.setCrossoverRate(0.8) + ga.setPopulationSize(100) + ga.setMutationRate(0.06) + + ga.evolve(freq_stats=50) + + best = ga.bestIndividual() + print(best) + +if __name__ == "__main__": + run_main() diff --git a/pyevolve_ex8_gauss_int.py b/pyevolve_ex8_gauss_int.py new file mode 100644 index 0000000..bbc48cb --- /dev/null +++ b/pyevolve_ex8_gauss_int.py @@ -0,0 +1,44 @@ +from pyevolve import G1DList +from pyevolve import GSimpleGA +from pyevolve import Selectors +from pyevolve import Mutators + +# This function is the evaluation function, we want +# to give high score to more zero'ed chromosomes +def eval_func(chromosome): + score = 0.0 + + # iterate over the chromosome + for value in chromosome: + if value==0: + score += 0.1 + return score + + +def run_main(): + # Genome instance + genome = G1DList.G1DList(40) + + # The gauss_mu and gauss_sigma is used to the Gaussian Mutator, but + # if you don't specify, the mutator will use the defaults + genome.setParams(rangemin=0, rangemax=10, gauss_mu=4, gauss_sigma=6) + genome.mutator.set(Mutators.G1DListMutatorIntegerGaussian) + + # The evaluator function (objective function) + genome.evaluator.set(eval_func) + + # Genetic Algorithm Instance + ga = GSimpleGA.GSimpleGA(genome) + #ga.selector.set(Selectors.GRouletteWheel) + ga.setGenerations(800) + + # Do the evolution, with stats dump + # frequency of 10 generations + ga.evolve(freq_stats=150) + + # Best individual + print(ga.bestIndividual()) + + +if __name__ == "__main__": + run_main() diff --git a/pyevolve_ex9_g2dlist.py b/pyevolve_ex9_g2dlist.py new file mode 100644 index 0000000..7378b84 --- /dev/null +++ b/pyevolve_ex9_g2dlist.py @@ -0,0 +1,43 @@ +from pyevolve import G2DList +from pyevolve import GSimpleGA +from pyevolve import Selectors +from pyevolve import Crossovers +from pyevolve import Mutators + +# This function is the evaluation function, we want +# to give high score to more zero'ed chromosomes +def eval_func(chromosome): + score = 0.0 + + # iterate over the chromosome + for i in range(chromosome.getHeight()): + for j in range(chromosome.getWidth()): + # You can use the chromosome.getItem(i, j) too + if chromosome[i][j]==0: + score += 0.1 + return score + +def run_main(): + # Genome instance + genome = G2DList.G2DList(8, 5) + genome.setParams(rangemin=0, rangemax=100) + + # The evaluator function (objective function) + genome.evaluator.set(eval_func) + genome.crossover.set(Crossovers.G2DListCrossoverSingleHPoint) + genome.mutator.set(Mutators.G2DListMutatorIntegerRange) + + # Genetic Algorithm Instance + ga = GSimpleGA.GSimpleGA(genome) + ga.setGenerations(800) + + # Do the evolution, with stats dump + # frequency of 10 generations + ga.evolve(freq_stats=100) + + # Best individual + print(ga.bestIndividual()) + + +if __name__ == "__main__": + run_main() diff --git a/pyevolve_graph.py b/pyevolve_graph.py index 7956331..0052137 100755 --- a/pyevolve_graph.py +++ b/pyevolve_graph.py @@ -1,607 +1,607 @@ -#!/usr/bin/python - -# This code is part of Pyevolve. -# It requires matplotlib v.0.98.5.0+ -from optparse import OptionParser -from optparse import OptionGroup - -def graph_pop_heatmap_raw(pop, minimize, colormap="jet", filesave=None): - pylab.imshow(pop, aspect="auto", interpolation="gaussian", cmap=matplotlib.cm.__dict__[colormap]) - pylab.title("Plot of pop. raw scores along the generations") - pylab.xlabel('Population') - pylab.ylabel('Generations') - pylab.grid(True) - pylab.colorbar() - - if filesave: - pylab.savefig(filesave) - print "Graph saved to %s file !" % (filesave,) - else: - pylab.show() - -def graph_pop_heatmap_fitness(pop, minimize, colormap="jet", filesave=None): - pylab.imshow(pop, aspect="equal", interpolation="gaussian", cmap=matplotlib.cm.__dict__[colormap]) - pylab.title("Plot of pop. fitness scores along the generations") - pylab.xlabel('Population') - pylab.ylabel('Generations') - pylab.grid(True) - pylab.colorbar() - - if filesave: - pylab.savefig(filesave) - print "Graph saved to %s file !" % (filesave,) - else: - pylab.show() - - -def graph_diff_raw(pop, minimize, filesave=None): - x = [] - - diff_raw_y = [] - diff_fit_y = [] - - for it in pop: - x.append(it["generation"]) - diff_raw_y.append(it["rawMax"] - it["rawMin"]) - diff_fit_y.append(it["fitMax"] - it["fitMin"]) - - pylab.figure() - pylab.subplot(211) - - pylab.plot(x, diff_raw_y, "g", label="Raw difference", linewidth=1.2) - pylab.fill_between(x, diff_raw_y, color="g", alpha=0.1) - - diff_raw_max= max(diff_raw_y) - gen_max_raw = x[diff_raw_y.index(diff_raw_max)] - - pylab.annotate("Maximum (%.2f)" % (diff_raw_max,), xy=(gen_max_raw, diff_raw_max), xycoords='data', - xytext=(-150, -20), textcoords='offset points', - arrowprops=dict(arrowstyle="->", - connectionstyle="arc"), - ) - - pylab.xlabel("Generation (#)") - pylab.ylabel("Raw difference") - pylab.title("Plot of evolution identified by '%s'" % (options.identify)) - - pylab.grid(True) - pylab.legend(prop=FontProperties(size="smaller")) - - pylab.subplot(212) - - pylab.plot(x, diff_fit_y, "b", label="Fitness difference", linewidth=1.2) - pylab.fill_between(x, diff_fit_y, color="b", alpha=0.1) - - - diff_fit_max= max(diff_fit_y) - gen_max_fit = x[diff_fit_y.index(diff_fit_max)] - - pylab.annotate("Maximum (%.2f)" % (diff_fit_max,), xy=(gen_max_fit, diff_fit_max), xycoords='data', - xytext=(-150, -20), textcoords='offset points', - arrowprops=dict(arrowstyle="->", - connectionstyle="arc"), - ) - - pylab.xlabel("Generation (#)") - pylab.ylabel("Fitness difference") - - pylab.grid(True) - pylab.legend(prop=FontProperties(size="smaller")) - - if filesave: - pylab.savefig(filesave) - print "Graph saved to %s file !" % (filesave,) - else: - pylab.show() - -def graph_maxmin_raw(pop, minimize, filesave=None): - x = [] - max_y = [] - min_y = [] - std_dev_y = [] - avg_y = [] - - for it in pop: - x.append(it["generation"]) - max_y.append(it["rawMax"]) - min_y.append(it["rawMin"]) - std_dev_y.append(it["rawDev"]) - avg_y.append(it["rawAve"]) - - pylab.figure() - - pylab.plot(x, max_y, "g", label="Max raw", linewidth=1.2) - pylab.plot(x, min_y, "r", label="Min raw", linewidth=1.2) - pylab.plot(x, avg_y, "b", label="Avg raw", linewidth=1.2) - pylab.plot(x, std_dev_y, "k", label="Std Dev raw", linewidth=1.2) - - pylab.fill_between(x, min_y, max_y, color="g", alpha=0.1, label="Diff max/min") - - if minimize: raw_max = min(min_y) - else: raw_max= max(max_y) - - if minimize: gen_max = x[min_y.index(raw_max)] - else: gen_max = x[max_y.index(raw_max)] - - min_std = min(std_dev_y) - gen_min_std = x[std_dev_y.index(min_std)] - - max_std = max(std_dev_y) - gen_max_std = x[std_dev_y.index(max_std)] - - if minimize: annot_label = "Minimum (%.2f)" % (raw_max,) - else: annot_label = "Maximum (%.2f)" % (raw_max,) - - - pylab.annotate(annot_label, xy=(gen_max, raw_max), xycoords='data', - xytext=(8, 15), textcoords='offset points', - arrowprops=dict(arrowstyle="->", - connectionstyle="arc"), - ) - - pylab.annotate("Min StdDev (%.2f)" % (min_std,), xy=(gen_min_std, min_std), xycoords='data', - xytext=(8, 15), textcoords='offset points', - arrowprops=dict(arrowstyle="->", - connectionstyle="arc"), - ) - - pylab.annotate("Max StdDev (%.2f)" % (max_std,), xy=(gen_max_std, max_std), xycoords='data', - xytext=(8, 15), textcoords='offset points', - arrowprops=dict(arrowstyle="->", - connectionstyle="arc"), - ) - - pylab.xlabel("Generation (#)") - pylab.ylabel("Raw score") - pylab.title("Plot of evolution identified by '%s' (raw scores)" % (options.identify)) - - pylab.grid(True) - pylab.legend(prop=FontProperties(size="smaller")) - - if filesave: - pylab.savefig(filesave) - print "Graph saved to %s file !" % (filesave,) - else: - pylab.show() - - -def graph_maxmin_fitness(pop, minimize, filesave=None): - x = [] - max_y = [] - min_y = [] - avg_y = [] - - for it in pop: - x.append(it["generation"]) - max_y.append(it["fitMax"]) - min_y.append(it["fitMin"]) - avg_y.append(it["fitAve"]) - - pylab.figure() - pylab.plot(x, max_y, "g", label="Max fitness") - pylab.plot(x, min_y, "r", label="Min fitness") - pylab.plot(x, avg_y, "b", label="Avg fitness") - - pylab.fill_between(x, min_y, max_y, color="g", alpha=0.1, label="Diff max/min") - - if minimize: raw_max = min(min_y) - else: raw_max = max(max_y) - - if minimize: gen_max = x[min_y.index(raw_max)] - else: gen_max = x[max_y.index(raw_max)] - - if minimize: annot_label = "Minimum (%.2f)" % (raw_max,) - else: annot_label = "Maximum (%.2f)" % (raw_max,) - - pylab.annotate(annot_label, xy=(gen_max, raw_max), xycoords='data', - xytext=(8, 15), textcoords='offset points', - arrowprops=dict(arrowstyle="->", - connectionstyle="arc"), - ) - - pylab.xlabel("Generation (#)") - pylab.ylabel("Fitness score") - pylab.title("Plot of evolution identified by '%s' (fitness scores)" % (options.identify)) - pylab.grid(True) - pylab.legend(prop=FontProperties(size="smaller")) - - if filesave: - pylab.savefig(filesave) - print "Graph saved to %s file !" % (filesave,) - else: - pylab.show() - -def graph_errorbars_raw(pop, minimize, filesave=None): - x = [] - y = [] - yerr_max = [] - yerr_min = [] - - for it in pop: - x.append(it["generation"]) - y.append(it["rawAve"]) - ymax = it["rawMax"] - it["rawAve"] - ymin = it["rawAve"] - it["rawMin"] - - yerr_max.append(ymax) - yerr_min.append(ymin) - - pylab.figure() - pylab.errorbar(x, y, [yerr_min, yerr_max], ecolor="g") - pylab.xlabel('Generation (#)') - pylab.ylabel('Raw score Min/Avg/Max') - pylab.title("Plot of evolution identified by '%s' (raw scores)" % (options.identify)) - pylab.grid(True) - - if filesave: - pylab.savefig(filesave) - print "Graph saved to %s file !" % (filesave,) - else: - pylab.show() - -def graph_errorbars_fitness(pop, minimize, filesave=None): - x = [] - y = [] - yerr_max = [] - yerr_min = [] - - for it in pop: - x.append(it["generation"]) - y.append(it["fitAve"]) - ymax = it["fitMax"] - it["fitAve"] - ymin = it["fitAve"] - it["fitMin"] - - yerr_max.append(ymax) - yerr_min.append(ymin) - - pylab.figure() - pylab.errorbar(x, y, [yerr_min, yerr_max], ecolor="g") - pylab.xlabel('Generation (#)') - pylab.ylabel('Fitness score Min/Avg/Max') - pylab.title("Plot of evolution identified by '%s' (fitness scores)" % (options.identify)) - - pylab.grid(True) - - if filesave: - pylab.savefig(filesave) - print "Graph saved to %s file !" % (filesave,) - else: - pylab.show() - -def graph_compare_raw(pop, minimize, id_list, filesave=None): - colors_list = ["g", "b", "r", "k", "m", "y"] - index = 0 - - pylab.figure() - - for it_out in pop: - x = [] - max_y = [] - min_y = [] - - for it in it_out: - x.append(it["generation"]) - max_y.append(it["rawMax"]) - min_y.append(it["rawMin"]) - - - if minimize: - pylab.plot(x, max_y, colors_list[index], linewidth=0.05) - pylab.plot(x, min_y, colors_list[index], label="Raw min (%s)" % (id_list[index],), linewidth=1.3) - else: - pylab.plot(x, max_y, colors_list[index], label="Raw max (%s)" % (id_list[index],), linewidth=1.3) - pylab.plot(x, min_y, colors_list[index], linewidth=0.05) - - pylab.fill_between(x, min_y, max_y, color=colors_list[index], alpha=0.06,) - - index += 1 - - pylab.xlabel("Generation (#)") - pylab.ylabel("Raw score") - pylab.title("Plot of evolution identified by '%s' (raw scores)" % ('many',)) - pylab.grid(True) - pylab.legend(prop=FontProperties(size="smaller")) - - if filesave: - pylab.savefig(filesave) - print "Graph saved to %s file !" % (filesave,) - else: - pylab.show() - -def graph_compare_fitness(pop, minimize, id_list, filesave=None): - colors_list = ["g", "b", "r", "k", "m", "y"] - index = 0 - - pylab.figure() - - for it_out in pop: - x = [] - max_y = [] - min_y = [] - - for it in it_out: - x.append(it["generation"]) - max_y.append(it["fitMax"]) - min_y.append(it["fitMin"]) - - if minimize: - pylab.plot(x, max_y, colors_list[index], linewidth=0.05) - pylab.plot(x, min_y, colors_list[index], label="Fitness min (%s)" % (id_list[index],), linewidth=1.3) - else: - pylab.plot(x, max_y, colors_list[index], label="Fitness max (%s)" % (id_list[index],), linewidth=1.3) - pylab.plot(x, min_y, colors_list[index], linewidth=0.05) - - pylab.fill_between(x, min_y, max_y, color=colors_list[index], alpha=0.06,) - - index += 1 - - pylab.xlabel("Generation (#)") - pylab.ylabel("Fitness score") - pylab.title("Plot of evolution identified by '%s' (fitness scores)" % ('many',)) - pylab.grid(True) - pylab.legend(prop=FontProperties(size="smaller")) - - if filesave: - pylab.savefig(filesave) - print "Graph saved to %s file !" % (filesave,) - else: - pylab.show() - - -if __name__ == "__main__": - from pyevolve import __version__ as pyevolve_version - from pyevolve import __author__ as pyevolve_author - - popGraph = False - - print "Pyevolve %s - Graph Plot Tool" % (pyevolve_version,) - print "By %s\n" % (pyevolve_author,) - parser = OptionParser() - - parser.add_option("-f", "--file", dest="dbfile", - help="Database file to read (default is 'pyevolve.db').", metavar="FILENAME", default="pyevolve.db") - - parser.add_option("-i", "--identify", dest="identify", - help="The identify of evolution.", metavar="IDENTIFY") - - parser.add_option("-o", "--outfile", dest="outfile", - help="""Write the graph image to a file (don't use extension, just the filename, default is png format, but you can change using --extension (-e) parameter).""", - metavar="OUTFILE") - - parser.add_option("-e", "--extension", dest="extension", - help="""Graph image file format. Supported options (formats) are: emf, eps, pdf, png, ps, raw, rgba, svg, svgz. Default is 'png'.""", - metavar="EXTENSION", default="png") - - parser.add_option("-g", "--genrange", dest="genrange", - help="""This is the generation range of the graph, ex: 1:30 (interval between 1 and 30).""", - metavar="GENRANGE") - - parser.add_option("-l", "--lindrange", dest="lindrange", - help="""This is the individual range of the graph, ex: 1:30 (individuals between 1 and 30), only applies to heatmaps.""", - metavar="LINDRANGE") - - parser.add_option("-c", "--colormap", dest="colormap", - help="""Sets the Color Map for the graph types 8 and 9. Some options are: summer, bone, gray, hot, jet, cooper, spectral. The default is 'jet'.""", - metavar="COLORMAP", default="jet") - - parser.add_option("-m", "--minimize", action="store_true", - help="Sets the 'Minimize' mode, default is the Maximize mode. This option makes sense if you are minimizing your evaluation function.", dest="minimize") - - group = OptionGroup(parser, "Graph types", "This is the supported graph types") - - group.add_option("-0", action="store_true", help="Write all graphs to files. Graph types: 1, 2, 3, 4 and 5.", dest="all_graphs") - - group.add_option("-1", action="store_true", help="Error bars graph (raw scores).", dest="errorbars_raw") - group.add_option("-2", action="store_true", help="Error bars graph (fitness scores).", dest="errorbars_fitness") - group.add_option("-3", action="store_true", help="Max/min/avg/std. dev. graph (raw scores).", dest="maxmin_raw") - group.add_option("-4", action="store_true", help="Max/min/avg graph (fitness scores).", dest="maxmin_fitness") - group.add_option("-5", action="store_true", help="Raw and Fitness min/max difference graph.", dest="diff_raw") - - group.add_option("-6", action="store_true", help="Compare best raw score of two or more evolutions (you must specify the identify comma-separed list with --identify (-i) parameter, like 'one, two, three'), the maximum is 6 items.", dest="compare_raw") - group.add_option("-7", action="store_true", help="Compare best fitness score of two or more evolutions (you must specify the identify comma-separed list with --identify (-i) parameter, like 'one, two, three'), the maximum is 6 items.", dest="compare_fitness") - - group.add_option("-8", action="store_true", help="Show a heat map of population raw score distribution between generations.", dest="pop_heatmap_raw") - group.add_option("-9", action="store_true", help="Show a heat map of population fitness score distribution between generations.", dest="pop_heatmap_fitness") - - - parser.add_option_group(group) - - (options, args) = parser.parse_args() - - if options.identify and (not options.errorbars_raw - and not options.errorbars_fitness - and not options.maxmin_raw - and not options.maxmin_fitness - and not options.diff_raw - and not options.all_graphs - and not options.compare_raw - and not options.pop_heatmap_raw - and not options.pop_heatmap_fitness - and not options.compare_fitness): - parser.error("You must choose one graph type !") - - if (not options.identify) or (not options.dbfile): - parser.print_help() - exit() - - print "Loading modules...." - - import os.path - if not os.path.exists(options.dbfile): - print "Database file '%s' not found !" % (options.dbfile, ) - exit() - - import pylab - from matplotlib.font_manager import FontProperties - import matplotlib.cm - import sqlite3 - import math - import os - - print "Loading database and creating graph..." - - identify_list = options.identify.split(",") - identify_list = map(str.strip, identify_list) - - pop = None - - if options.pop_heatmap_raw or options.pop_heatmap_fitness: - conn = sqlite3.connect(options.dbfile) - conn.row_factory = sqlite3.Row - c = conn.cursor() - - if options.genrange: - genrange = options.genrange.split(":") - ret = c.execute("select distinct generation from population where identify = ? and generation between ? and ?", (options.identify, genrange[0], genrange[1])) - else: - ret = c.execute("select distinct generation from population where identify = ?", (options.identify,)) - - generations = ret.fetchall() - if len(generations) <= 0: - print "No generation data found for the identify '%s' !" % (options.identify,) - exit() - - pop = [] - for gen in generations: - pop_tmp = [] - - if options.lindrange: - individual_range = options.lindrange.split(":") - ret = c.execute(""" - select * from population - where identify = ? - and generation = ? - and individual between ? and ? - """, (options.identify, gen[0], individual_range[0], individual_range[1])) - else: - ret = c.execute(""" - select * from population - where identify = ? - and generation = ? - """, (options.identify, gen[0])) - - ret_fetch = ret.fetchall() - for it in ret_fetch: - if options.pop_heatmap_raw: - pop_tmp.append(it["raw"]) - else: - pop_tmp.append(it["fitness"]) - pop.append(pop_tmp) - - ret.close() - conn.close() - - if len(pop) <= 0: - print "No statistic data found for the identify '%s' !" % (options.identify,) - exit() - - print "%d generations found !" % (len(pop),) - - popGraph = True - - - if len(identify_list) == 1 and not popGraph: - if options.compare_raw or options.compare_fitness: - parser.error("You can't use this graph type with only one identify !") - - conn = sqlite3.connect(options.dbfile) - conn.row_factory = sqlite3.Row - c = conn.cursor() - - if options.genrange: - genrange = options.genrange.split(":") - ret = c.execute("select * from statistics where identify = ? and generation between ? and ?", (options.identify, genrange[0], genrange[1])) - else: - ret = c.execute("select * from statistics where identify = ?", (options.identify,)) - - pop = ret.fetchall() - - ret.close() - conn.close() - - if len(pop) <= 0: - print "No statistic data found for the identify '%s' !" % (options.identify,) - exit() - - print "%d generations found !" % (len(pop),) - - elif len(identify_list) > 1 and not popGraph: - pop = [] - if (not options.compare_raw) and (not options.compare_fitness): - parser.error("You can't use many ids with this graph type !") - - conn = sqlite3.connect(options.dbfile) - conn.row_factory = sqlite3.Row - c = conn.cursor() - for item in identify_list: - if options.genrange: - genrange = options.genrange.split(":") - ret = c.execute("select * from statistics where identify = ? and generation between ? and ?", (item, genrange[0], genrange[1])) - else: - ret = c.execute("select * from statistics where identify = ?", (item,)) - fetchall = ret.fetchall() - if len(fetchall) > 0: - pop.append(fetchall) - - ret.close() - conn.close() - - if len(pop) <= 0: - print "No statistic data found for the identify list '%s' !" % (options.identify,) - exit() - - print "%d identify found !" % (len(pop),) - - if options.errorbars_raw: - if options.outfile: graph_errorbars_raw(pop, options.minimize, options.outfile + "." + options.extension) - else: graph_errorbars_raw(pop, options.minimize) - - if options.errorbars_fitness: - if options.outfile: graph_errorbars_fitness(pop, options.minimize, options.outfile + "." + options.extension) - else: graph_errorbars_fitness(pop, options.minimize) - - if options.maxmin_raw: - if options.outfile: graph_maxmin_raw(pop, options.minimize, options.outfile + "." + options.extension) - else: graph_maxmin_raw(pop, options.minimize) - - if options.maxmin_fitness: - if options.outfile: graph_maxmin_fitness(pop, options.minimize, options.outfile + "." + options.extension) - else: graph_maxmin_fitness(pop, options.minimize) - - if options.diff_raw: - if options.outfile: graph_diff_raw(pop, options.minimize, options.outfile + "." + options.extension) - else: graph_diff_raw(pop, options.minimize) - - if options.all_graphs: - all_graph_functions = [graph_errorbars_raw, graph_errorbars_fitness, graph_maxmin_raw, - graph_maxmin_fitness, graph_diff_raw] - if options.outfile: - parser.error("You can't specify one file to all graphs !") - - dirname = "graphs_" + options.identify - if not os.path.isdir(dirname): - os.mkdir(dirname) - - for graph in all_graph_functions: - filename = dirname + "/" - filename += options.identify + "_" + graph.__name__[6:] - filename += "." + options.extension - graph(pop, options.minimize, filename) - - print "\n\tDone ! The graphs was saved in the directory '%s'" % (dirname) - - if options.compare_raw: - if options.outfile: graph_compare_raw(pop, options.minimize, identify_list, options.outfile + "." + options.extension) - else: graph_compare_raw(pop, options.minimize, identify_list ) - - if options.compare_fitness: - if options.outfile: graph_compare_fitness(pop, options.minimize, identify_list, options.outfile + "." + options.extension) - else: graph_compare_fitness(pop, options.minimize, identify_list ) - - if options.pop_heatmap_raw: - if options.outfile: graph_pop_heatmap_raw(pop, options.minimize, options.colormap, options.outfile + "." + options.extension) - else: graph_pop_heatmap_raw(pop, options.minimize, options.colormap) - - if options.pop_heatmap_fitness: - if options.outfile: graph_pop_heatmap_fitness(pop, options.minimize, options.colormap, options.outfile + "." + options.extension) - else: graph_pop_heatmap_fitness(pop, options.minimize, options.colormap) +#!/usr/bin/python + +# This code is part of Pyevolve. +# It requires matplotlib v.0.98.5.0+ +from optparse import OptionParser +from optparse import OptionGroup + +def graph_pop_heatmap_raw(pop, minimize, colormap="jet", filesave=None): + pylab.imshow(pop, aspect="auto", interpolation="gaussian", cmap=matplotlib.cm.__dict__[colormap]) + pylab.title("Plot of pop. raw scores along the generations") + pylab.xlabel('Population') + pylab.ylabel('Generations') + pylab.grid(True) + pylab.colorbar() + + if filesave: + pylab.savefig(filesave) + print("Graph saved to %s file !" % (filesave,)) + else: + pylab.show() + +def graph_pop_heatmap_fitness(pop, minimize, colormap="jet", filesave=None): + pylab.imshow(pop, aspect="equal", interpolation="gaussian", cmap=matplotlib.cm.__dict__[colormap]) + pylab.title("Plot of pop. fitness scores along the generations") + pylab.xlabel('Population') + pylab.ylabel('Generations') + pylab.grid(True) + pylab.colorbar() + + if filesave: + pylab.savefig(filesave) + print("Graph saved to %s file !" % (filesave,)) + else: + pylab.show() + + +def graph_diff_raw(pop, minimize, filesave=None): + x = [] + + diff_raw_y = [] + diff_fit_y = [] + + for it in pop: + x.append(it["generation"]) + diff_raw_y.append(it["rawMax"] - it["rawMin"]) + diff_fit_y.append(it["fitMax"] - it["fitMin"]) + + pylab.figure() + pylab.subplot(211) + + pylab.plot(x, diff_raw_y, "g", label="Raw difference", linewidth=1.2) + pylab.fill_between(x, diff_raw_y, color="g", alpha=0.1) + + diff_raw_max= max(diff_raw_y) + gen_max_raw = x[diff_raw_y.index(diff_raw_max)] + + pylab.annotate("Maximum (%.2f)" % (diff_raw_max,), xy=(gen_max_raw, diff_raw_max), xycoords='data', + xytext=(-150, -20), textcoords='offset points', + arrowprops=dict(arrowstyle="->", + connectionstyle="arc"), + ) + + pylab.xlabel("Generation (#)") + pylab.ylabel("Raw difference") + pylab.title("Plot of evolution identified by '%s'" % (options.identify)) + + pylab.grid(True) + pylab.legend(prop=FontProperties(size="smaller")) + + pylab.subplot(212) + + pylab.plot(x, diff_fit_y, "b", label="Fitness difference", linewidth=1.2) + pylab.fill_between(x, diff_fit_y, color="b", alpha=0.1) + + + diff_fit_max= max(diff_fit_y) + gen_max_fit = x[diff_fit_y.index(diff_fit_max)] + + pylab.annotate("Maximum (%.2f)" % (diff_fit_max,), xy=(gen_max_fit, diff_fit_max), xycoords='data', + xytext=(-150, -20), textcoords='offset points', + arrowprops=dict(arrowstyle="->", + connectionstyle="arc"), + ) + + pylab.xlabel("Generation (#)") + pylab.ylabel("Fitness difference") + + pylab.grid(True) + pylab.legend(prop=FontProperties(size="smaller")) + + if filesave: + pylab.savefig(filesave) + print("Graph saved to %s file !" % (filesave,)) + else: + pylab.show() + +def graph_maxmin_raw(pop, minimize, filesave=None): + x = [] + max_y = [] + min_y = [] + std_dev_y = [] + avg_y = [] + + for it in pop: + x.append(it["generation"]) + max_y.append(it["rawMax"]) + min_y.append(it["rawMin"]) + std_dev_y.append(it["rawDev"]) + avg_y.append(it["rawAve"]) + + pylab.figure() + + pylab.plot(x, max_y, "g", label="Max raw", linewidth=1.2) + pylab.plot(x, min_y, "r", label="Min raw", linewidth=1.2) + pylab.plot(x, avg_y, "b", label="Avg raw", linewidth=1.2) + pylab.plot(x, std_dev_y, "k", label="Std Dev raw", linewidth=1.2) + + pylab.fill_between(x, min_y, max_y, color="g", alpha=0.1, label="Diff max/min") + + if minimize: raw_max = min(min_y) + else: raw_max= max(max_y) + + if minimize: gen_max = x[min_y.index(raw_max)] + else: gen_max = x[max_y.index(raw_max)] + + min_std = min(std_dev_y) + gen_min_std = x[std_dev_y.index(min_std)] + + max_std = max(std_dev_y) + gen_max_std = x[std_dev_y.index(max_std)] + + if minimize: annot_label = "Minimum (%.2f)" % (raw_max,) + else: annot_label = "Maximum (%.2f)" % (raw_max,) + + + pylab.annotate(annot_label, xy=(gen_max, raw_max), xycoords='data', + xytext=(8, 15), textcoords='offset points', + arrowprops=dict(arrowstyle="->", + connectionstyle="arc"), + ) + + pylab.annotate("Min StdDev (%.2f)" % (min_std,), xy=(gen_min_std, min_std), xycoords='data', + xytext=(8, 15), textcoords='offset points', + arrowprops=dict(arrowstyle="->", + connectionstyle="arc"), + ) + + pylab.annotate("Max StdDev (%.2f)" % (max_std,), xy=(gen_max_std, max_std), xycoords='data', + xytext=(8, 15), textcoords='offset points', + arrowprops=dict(arrowstyle="->", + connectionstyle="arc"), + ) + + pylab.xlabel("Generation (#)") + pylab.ylabel("Raw score") + pylab.title("Plot of evolution identified by '%s' (raw scores)" % (options.identify)) + + pylab.grid(True) + pylab.legend(prop=FontProperties(size="smaller")) + + if filesave: + pylab.savefig(filesave) + print("Graph saved to %s file !" % (filesave,)) + else: + pylab.show() + + +def graph_maxmin_fitness(pop, minimize, filesave=None): + x = [] + max_y = [] + min_y = [] + avg_y = [] + + for it in pop: + x.append(it["generation"]) + max_y.append(it["fitMax"]) + min_y.append(it["fitMin"]) + avg_y.append(it["fitAve"]) + + pylab.figure() + pylab.plot(x, max_y, "g", label="Max fitness") + pylab.plot(x, min_y, "r", label="Min fitness") + pylab.plot(x, avg_y, "b", label="Avg fitness") + + pylab.fill_between(x, min_y, max_y, color="g", alpha=0.1, label="Diff max/min") + + if minimize: raw_max = min(min_y) + else: raw_max = max(max_y) + + if minimize: gen_max = x[min_y.index(raw_max)] + else: gen_max = x[max_y.index(raw_max)] + + if minimize: annot_label = "Minimum (%.2f)" % (raw_max,) + else: annot_label = "Maximum (%.2f)" % (raw_max,) + + pylab.annotate(annot_label, xy=(gen_max, raw_max), xycoords='data', + xytext=(8, 15), textcoords='offset points', + arrowprops=dict(arrowstyle="->", + connectionstyle="arc"), + ) + + pylab.xlabel("Generation (#)") + pylab.ylabel("Fitness score") + pylab.title("Plot of evolution identified by '%s' (fitness scores)" % (options.identify)) + pylab.grid(True) + pylab.legend(prop=FontProperties(size="smaller")) + + if filesave: + pylab.savefig(filesave) + print("Graph saved to %s file !" % (filesave,)) + else: + pylab.show() + +def graph_errorbars_raw(pop, minimize, filesave=None): + x = [] + y = [] + yerr_max = [] + yerr_min = [] + + for it in pop: + x.append(it["generation"]) + y.append(it["rawAve"]) + ymax = it["rawMax"] - it["rawAve"] + ymin = it["rawAve"] - it["rawMin"] + + yerr_max.append(ymax) + yerr_min.append(ymin) + + pylab.figure() + pylab.errorbar(x, y, [yerr_min, yerr_max], ecolor="g") + pylab.xlabel('Generation (#)') + pylab.ylabel('Raw score Min/Avg/Max') + pylab.title("Plot of evolution identified by '%s' (raw scores)" % (options.identify)) + pylab.grid(True) + + if filesave: + pylab.savefig(filesave) + print("Graph saved to %s file !" % (filesave,)) + else: + pylab.show() + +def graph_errorbars_fitness(pop, minimize, filesave=None): + x = [] + y = [] + yerr_max = [] + yerr_min = [] + + for it in pop: + x.append(it["generation"]) + y.append(it["fitAve"]) + ymax = it["fitMax"] - it["fitAve"] + ymin = it["fitAve"] - it["fitMin"] + + yerr_max.append(ymax) + yerr_min.append(ymin) + + pylab.figure() + pylab.errorbar(x, y, [yerr_min, yerr_max], ecolor="g") + pylab.xlabel('Generation (#)') + pylab.ylabel('Fitness score Min/Avg/Max') + pylab.title("Plot of evolution identified by '%s' (fitness scores)" % (options.identify)) + + pylab.grid(True) + + if filesave: + pylab.savefig(filesave) + print("Graph saved to %s file !" % (filesave,)) + else: + pylab.show() + +def graph_compare_raw(pop, minimize, id_list, filesave=None): + colors_list = ["g", "b", "r", "k", "m", "y"] + index = 0 + + pylab.figure() + + for it_out in pop: + x = [] + max_y = [] + min_y = [] + + for it in it_out: + x.append(it["generation"]) + max_y.append(it["rawMax"]) + min_y.append(it["rawMin"]) + + + if minimize: + pylab.plot(x, max_y, colors_list[index], linewidth=0.05) + pylab.plot(x, min_y, colors_list[index], label="Raw min (%s)" % (id_list[index],), linewidth=1.3) + else: + pylab.plot(x, max_y, colors_list[index], label="Raw max (%s)" % (id_list[index],), linewidth=1.3) + pylab.plot(x, min_y, colors_list[index], linewidth=0.05) + + pylab.fill_between(x, min_y, max_y, color=colors_list[index], alpha=0.06,) + + index += 1 + + pylab.xlabel("Generation (#)") + pylab.ylabel("Raw score") + pylab.title("Plot of evolution identified by '%s' (raw scores)" % ('many',)) + pylab.grid(True) + pylab.legend(prop=FontProperties(size="smaller")) + + if filesave: + pylab.savefig(filesave) + print("Graph saved to %s file !" % (filesave,)) + else: + pylab.show() + +def graph_compare_fitness(pop, minimize, id_list, filesave=None): + colors_list = ["g", "b", "r", "k", "m", "y"] + index = 0 + + pylab.figure() + + for it_out in pop: + x = [] + max_y = [] + min_y = [] + + for it in it_out: + x.append(it["generation"]) + max_y.append(it["fitMax"]) + min_y.append(it["fitMin"]) + + if minimize: + pylab.plot(x, max_y, colors_list[index], linewidth=0.05) + pylab.plot(x, min_y, colors_list[index], label="Fitness min (%s)" % (id_list[index],), linewidth=1.3) + else: + pylab.plot(x, max_y, colors_list[index], label="Fitness max (%s)" % (id_list[index],), linewidth=1.3) + pylab.plot(x, min_y, colors_list[index], linewidth=0.05) + + pylab.fill_between(x, min_y, max_y, color=colors_list[index], alpha=0.06,) + + index += 1 + + pylab.xlabel("Generation (#)") + pylab.ylabel("Fitness score") + pylab.title("Plot of evolution identified by '%s' (fitness scores)" % ('many',)) + pylab.grid(True) + pylab.legend(prop=FontProperties(size="smaller")) + + if filesave: + pylab.savefig(filesave) + print("Graph saved to %s file !" % (filesave,)) + else: + pylab.show() + + +if __name__ == "__main__": + from pyevolve import __version__ as pyevolve_version + from pyevolve import __author__ as pyevolve_author + + popGraph = False + + print("Pyevolve %s - Graph Plot Tool" % (pyevolve_version,)) + print("By %s\n" % (pyevolve_author,)) + parser = OptionParser() + + parser.add_option("-f", "--file", dest="dbfile", + help="Database file to read (default is 'pyevolve.db').", metavar="FILENAME", default="pyevolve.db") + + parser.add_option("-i", "--identify", dest="identify", + help="The identify of evolution.", metavar="IDENTIFY") + + parser.add_option("-o", "--outfile", dest="outfile", + help="""Write the graph image to a file (don't use extension, just the filename, default is png format, but you can change using --extension (-e) parameter).""", + metavar="OUTFILE") + + parser.add_option("-e", "--extension", dest="extension", + help="""Graph image file format. Supported options (formats) are: emf, eps, pdf, png, ps, raw, rgba, svg, svgz. Default is 'png'.""", + metavar="EXTENSION", default="png") + + parser.add_option("-g", "--genrange", dest="genrange", + help="""This is the generation range of the graph, ex: 1:30 (interval between 1 and 30).""", + metavar="GENRANGE") + + parser.add_option("-l", "--lindrange", dest="lindrange", + help="""This is the individual range of the graph, ex: 1:30 (individuals between 1 and 30), only applies to heatmaps.""", + metavar="LINDRANGE") + + parser.add_option("-c", "--colormap", dest="colormap", + help="""Sets the Color Map for the graph types 8 and 9. Some options are: summer, bone, gray, hot, jet, cooper, spectral. The default is 'jet'.""", + metavar="COLORMAP", default="jet") + + parser.add_option("-m", "--minimize", action="store_true", + help="Sets the 'Minimize' mode, default is the Maximize mode. This option makes sense if you are minimizing your evaluation function.", dest="minimize") + + group = OptionGroup(parser, "Graph types", "This is the supported graph types") + + group.add_option("-0", action="store_true", help="Write all graphs to files. Graph types: 1, 2, 3, 4 and 5.", dest="all_graphs") + + group.add_option("-1", action="store_true", help="Error bars graph (raw scores).", dest="errorbars_raw") + group.add_option("-2", action="store_true", help="Error bars graph (fitness scores).", dest="errorbars_fitness") + group.add_option("-3", action="store_true", help="Max/min/avg/std. dev. graph (raw scores).", dest="maxmin_raw") + group.add_option("-4", action="store_true", help="Max/min/avg graph (fitness scores).", dest="maxmin_fitness") + group.add_option("-5", action="store_true", help="Raw and Fitness min/max difference graph.", dest="diff_raw") + + group.add_option("-6", action="store_true", help="Compare best raw score of two or more evolutions (you must specify the identify comma-separed list with --identify (-i) parameter, like 'one, two, three'), the maximum is 6 items.", dest="compare_raw") + group.add_option("-7", action="store_true", help="Compare best fitness score of two or more evolutions (you must specify the identify comma-separed list with --identify (-i) parameter, like 'one, two, three'), the maximum is 6 items.", dest="compare_fitness") + + group.add_option("-8", action="store_true", help="Show a heat map of population raw score distribution between generations.", dest="pop_heatmap_raw") + group.add_option("-9", action="store_true", help="Show a heat map of population fitness score distribution between generations.", dest="pop_heatmap_fitness") + + + parser.add_option_group(group) + + (options, args) = parser.parse_args() + + if options.identify and (not options.errorbars_raw + and not options.errorbars_fitness + and not options.maxmin_raw + and not options.maxmin_fitness + and not options.diff_raw + and not options.all_graphs + and not options.compare_raw + and not options.pop_heatmap_raw + and not options.pop_heatmap_fitness + and not options.compare_fitness): + parser.error("You must choose one graph type !") + + if (not options.identify) or (not options.dbfile): + parser.print_help() + exit() + + print("Loading modules....") + + import os.path + if not os.path.exists(options.dbfile): + print("Database file '%s' not found !" % (options.dbfile, )) + exit() + + import pylab + from matplotlib.font_manager import FontProperties + import matplotlib.cm + import sqlite3 + import math + import os + + print("Loading database and creating graph...") + + identify_list = options.identify.split(",") + identify_list = list(map(str.strip, identify_list)) + + pop = None + + if options.pop_heatmap_raw or options.pop_heatmap_fitness: + conn = sqlite3.connect(options.dbfile) + conn.row_factory = sqlite3.Row + c = conn.cursor() + + if options.genrange: + genrange = options.genrange.split(":") + ret = c.execute("select distinct generation from population where identify = ? and generation between ? and ?", (options.identify, genrange[0], genrange[1])) + else: + ret = c.execute("select distinct generation from population where identify = ?", (options.identify,)) + + generations = ret.fetchall() + if len(generations) <= 0: + print("No generation data found for the identify '%s' !" % (options.identify,)) + exit() + + pop = [] + for gen in generations: + pop_tmp = [] + + if options.lindrange: + individual_range = options.lindrange.split(":") + ret = c.execute(""" + select * from population + where identify = ? + and generation = ? + and individual between ? and ? + """, (options.identify, gen[0], individual_range[0], individual_range[1])) + else: + ret = c.execute(""" + select * from population + where identify = ? + and generation = ? + """, (options.identify, gen[0])) + + ret_fetch = ret.fetchall() + for it in ret_fetch: + if options.pop_heatmap_raw: + pop_tmp.append(it["raw"]) + else: + pop_tmp.append(it["fitness"]) + pop.append(pop_tmp) + + ret.close() + conn.close() + + if len(pop) <= 0: + print("No statistic data found for the identify '%s' !" % (options.identify,)) + exit() + + print("%d generations found !" % (len(pop),)) + + popGraph = True + + + if len(identify_list) == 1 and not popGraph: + if options.compare_raw or options.compare_fitness: + parser.error("You can't use this graph type with only one identify !") + + conn = sqlite3.connect(options.dbfile) + conn.row_factory = sqlite3.Row + c = conn.cursor() + + if options.genrange: + genrange = options.genrange.split(":") + ret = c.execute("select * from statistics where identify = ? and generation between ? and ?", (options.identify, genrange[0], genrange[1])) + else: + ret = c.execute("select * from statistics where identify = ?", (options.identify,)) + + pop = ret.fetchall() + + ret.close() + conn.close() + + if len(pop) <= 0: + print("No statistic data found for the identify '%s' !" % (options.identify,)) + exit() + + print("%d generations found !" % (len(pop),)) + + elif len(identify_list) > 1 and not popGraph: + pop = [] + if (not options.compare_raw) and (not options.compare_fitness): + parser.error("You can't use many ids with this graph type !") + + conn = sqlite3.connect(options.dbfile) + conn.row_factory = sqlite3.Row + c = conn.cursor() + for item in identify_list: + if options.genrange: + genrange = options.genrange.split(":") + ret = c.execute("select * from statistics where identify = ? and generation between ? and ?", (item, genrange[0], genrange[1])) + else: + ret = c.execute("select * from statistics where identify = ?", (item,)) + fetchall = ret.fetchall() + if len(fetchall) > 0: + pop.append(fetchall) + + ret.close() + conn.close() + + if len(pop) <= 0: + print("No statistic data found for the identify list '%s' !" % (options.identify,)) + exit() + + print("%d identify found !" % (len(pop),)) + + if options.errorbars_raw: + if options.outfile: graph_errorbars_raw(pop, options.minimize, options.outfile + "." + options.extension) + else: graph_errorbars_raw(pop, options.minimize) + + if options.errorbars_fitness: + if options.outfile: graph_errorbars_fitness(pop, options.minimize, options.outfile + "." + options.extension) + else: graph_errorbars_fitness(pop, options.minimize) + + if options.maxmin_raw: + if options.outfile: graph_maxmin_raw(pop, options.minimize, options.outfile + "." + options.extension) + else: graph_maxmin_raw(pop, options.minimize) + + if options.maxmin_fitness: + if options.outfile: graph_maxmin_fitness(pop, options.minimize, options.outfile + "." + options.extension) + else: graph_maxmin_fitness(pop, options.minimize) + + if options.diff_raw: + if options.outfile: graph_diff_raw(pop, options.minimize, options.outfile + "." + options.extension) + else: graph_diff_raw(pop, options.minimize) + + if options.all_graphs: + all_graph_functions = [graph_errorbars_raw, graph_errorbars_fitness, graph_maxmin_raw, + graph_maxmin_fitness, graph_diff_raw] + if options.outfile: + parser.error("You can't specify one file to all graphs !") + + dirname = "graphs_" + options.identify + if not os.path.isdir(dirname): + os.mkdir(dirname) + + for graph in all_graph_functions: + filename = dirname + "/" + filename += options.identify + "_" + graph.__name__[6:] + filename += "." + options.extension + graph(pop, options.minimize, filename) + + print("\n\tDone ! The graphs was saved in the directory '%s'" % (dirname)) + + if options.compare_raw: + if options.outfile: graph_compare_raw(pop, options.minimize, identify_list, options.outfile + "." + options.extension) + else: graph_compare_raw(pop, options.minimize, identify_list ) + + if options.compare_fitness: + if options.outfile: graph_compare_fitness(pop, options.minimize, identify_list, options.outfile + "." + options.extension) + else: graph_compare_fitness(pop, options.minimize, identify_list ) + + if options.pop_heatmap_raw: + if options.outfile: graph_pop_heatmap_raw(pop, options.minimize, options.colormap, options.outfile + "." + options.extension) + else: graph_pop_heatmap_raw(pop, options.minimize, options.colormap) + + if options.pop_heatmap_fitness: + if options.outfile: graph_pop_heatmap_fitness(pop, options.minimize, options.colormap, options.outfile + "." + options.extension) + else: graph_pop_heatmap_fitness(pop, options.minimize, options.colormap) diff --git a/runtests.py b/runtests.py index df41039..fd7f01f 100644 --- a/runtests.py +++ b/runtests.py @@ -1,5 +1,5 @@ -import nose - - -if __name__ == '__main__': - nose.main() +import nose + + +if __name__ == '__main__': + nose.main() diff --git a/setup.py b/setup.py index 1080014..4644971 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,7 @@ -from distutils.core import setup #changed to distutils.core for pypy comptibility +#from distutils.core import setup #changed to distutils.core for pypy comptibility +from setuptools import setup from pyevolve import __version__, __author__ +import sys setup( name = "Pyevolve", @@ -9,10 +11,11 @@ package_data = { 'pyevolve': ['*.txt'] }, + test_suite = 'tests', author = __author__, author_email = "christian.perone@gmail.com", description = "A complete, free and open-source evolutionary framework written in Python", license = "PSF", keywords = "genetic algorithm genetic programming algorithms framework library python ai evolutionary framework", - url = "http://pyevolve.sourceforge.net/" + url = "http://pyevolve.sourceforge.net/", ) diff --git a/test_crossovers.py b/test_crossovers.py new file mode 100644 index 0000000..e36b83a --- /dev/null +++ b/test_crossovers.py @@ -0,0 +1,394 @@ + + +from itertools import cycle +import unittest + +from mock import patch +from nose.tools import nottest + +from pyevolve import Crossovers +from pyevolve.G1DBinaryString import G1DBinaryString +from pyevolve.G1DList import G1DList +from pyevolve.G2DBinaryString import G2DBinaryString +from pyevolve.G2DList import G2DList +from pyevolve.GTree import GTree, GTreeNode + + +class CrossoverTestCase(unittest.TestCase): + def assertCrossoverResultsEqual( + self, + crossover, + expected_sister, + expected_brother, + crossover_extra_kwargs=None, + genome_attr_name='genomeList', # TODO refactor with Genome getter method + assertion_name='assertEqual' + ): + crossover_extra_kwargs = crossover_extra_kwargs or {} + kwargs = { + 'mom': self.mom, + 'dad': self.dad, + } + kwargs.update(crossover_extra_kwargs) + genome_value_getter = lambda g: getattr(g, genome_attr_name) if genome_attr_name else g + actual_sister, actual_brother = [genome_value_getter(g) if g else None for g in crossover(None, **kwargs)] + getattr(self, assertion_name)(actual_sister, expected_sister) + getattr(self, assertion_name)(actual_brother, expected_brother) + + +class G1DBinaryStringCrossoversTestCase(CrossoverTestCase): + def setUp(self): + self.mom = G1DBinaryString(3) + self.mom.append(1) + self.mom.append(0) + self.mom.append(0) + self.dad = G1DBinaryString(3) + self.dad.append(0) + self.dad.append(0) + self.dad.append(1) + + @patch('pyevolve.Crossovers.rand_randint') + def test_single_point(self, rand_mock): + rand_mock.return_value = 1 + self.assertCrossoverResultsEqual( + Crossovers.G1DBinaryStringXSinglePoint, + [1, 0, 1], + [0, 0, 0], + crossover_extra_kwargs={'count': 2} + ) + + self.assertCrossoverResultsEqual( + Crossovers.G1DBinaryStringXSinglePoint, + [1, 0, 1], + None, + crossover_extra_kwargs={'count': 1} + ) + + @patch('pyevolve.Crossovers.rand_randint') + def test_two_point(self, rand_mock): + rand_mock.return_value = 1 + self.assertCrossoverResultsEqual( + Crossovers.G1DBinaryStringXTwoPoint, + [1, 0, 0], + [0, 0, 1], + crossover_extra_kwargs={'count': 2} + ) + + self.assertCrossoverResultsEqual( + Crossovers.G1DBinaryStringXTwoPoint, + [1, 0, 0], + None, + crossover_extra_kwargs={'count': 1} + ) + + @patch('pyevolve.Util.randomFlipCoin') + def test_uniform(self, coin_flip_mock): + coin_flip_mock.return_value = [1, 1, 0] + self.assertCrossoverResultsEqual( + Crossovers.G1DBinaryStringXUniform, + [0, 0, 1], + [1, 0, 0], + ) + + +class G1DListCrossoversTestCase(CrossoverTestCase): + def setUp(self): + self.mom = G1DList(3) + self.mom.genomeList = [1, 2, 3] + self.dad = G1DList(3) + self.dad.genomeList = [4, 5, 6] + + @patch('pyevolve.Crossovers.rand_randint') + def test_single_point(self, rand_mock): + rand_mock.return_value = 1 + self.assertCrossoverResultsEqual( + Crossovers.G1DListCrossoverSinglePoint, + [1, 5, 6], + None, + crossover_extra_kwargs={'count': 1} + ) + self.assertCrossoverResultsEqual( + Crossovers.G1DListCrossoverSinglePoint, + [1, 5, 6], + [4, 2, 3], + crossover_extra_kwargs={'count': 2} + ) + + @patch('pyevolve.Crossovers.rand_randint') + def test_two_points(self, rand_mock): + rand_mock.return_value = 1 + self.assertCrossoverResultsEqual( + Crossovers.G1DListCrossoverTwoPoint, + [1, 2, 3], + None, + crossover_extra_kwargs={'count': 1} + ) + self.assertCrossoverResultsEqual( + Crossovers.G1DListCrossoverTwoPoint, + [1, 2, 3], + [4, 5, 6], + crossover_extra_kwargs={'count': 2} + ) + + @patch('pyevolve.Util.randomFlipCoin') + def test_uniform(self, coin_flip_mock): + coin_flip_mock.return_value = [1, 0, 0] + self.assertCrossoverResultsEqual( + Crossovers.G1DListCrossoverUniform, + [4, 5, 6], + [1, 2, 3], + ) + + @nottest # fails because of https://github.com/perone/Pyevolve/issues/26 + @patch('pyevolve.Crossovers.rand_randint') + def test_order_crossover(self, rand_mock): + rand_mock.side_effect = [1, 2] + self.assertCrossoverResultsEqual( + Crossovers.G1DListCrossoverOX, + [1, 2, 3], + None, + crossover_extra_kwargs={'count': 1} + ) + self.assertCrossoverResultsEqual( + Crossovers.G1DListCrossoverOX, + [1, 2, 3], + [4, 5, 6], + crossover_extra_kwargs={'count': 2} + ) + + @patch('pyevolve.Crossovers.rand_randint') + def test_crossfill_crossover(self, rand_mock): + rand_mock.return_value = 1 + self.assertCrossoverResultsEqual( + Crossovers.G1DListCrossoverCutCrossfill, + [1, 4, 5], + None, + crossover_extra_kwargs={'count': 1} + ) + self.assertCrossoverResultsEqual( + Crossovers.G1DListCrossoverCutCrossfill, + [1, 4, 5], + [4, 1, 2], + crossover_extra_kwargs={'count': 2} + ) + + @patch('pyevolve.Crossovers.rand_random') + def test_crossfill_crossover(self, rand_mock): + rand_mock.return_value = 0.6 + self.assertCrossoverResultsEqual( + Crossovers.G1DListCrossoverRealSBX, + [0.9696386870268516, 1.9692699516972016, 2.9692611909097177], + [4.030739398252697, 5.030739398252697, 6.030739398252697], + ) + + @patch('pyevolve.Crossovers.rand_randint') + def test_crossfill_cut_crossover(self, rand_mock): + rand_mock.return_value = 1 + self.assertCrossoverResultsEqual( + Crossovers.G1DListCrossoverCutCrossfill, + [1, 4, 5], + None, + crossover_extra_kwargs={'count': 1} + ) + self.assertCrossoverResultsEqual( + Crossovers.G1DListCrossoverCutCrossfill, + [1, 4, 5], + [4, 1, 2], + crossover_extra_kwargs={'count': 2} + ) + + @patch('pyevolve.Crossovers.rand_choice') + def test_edge_crossover(self, rand_mock): + rand_mock.side_effect = lambda u: u[0] + self.assertCrossoverResultsEqual( + Crossovers.G1DListCrossoverEdge, + [1, 2, 3], + [4, 5, 6], + ) + + +class G2DListCrossoversTestCase(CrossoverTestCase): + def setUp(self): + self.mom = G2DList(3, 3) + self.mom.genomeList = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + self.dad = G2DList(3, 3) + self.dad.genomeList = [[1, 4, 7], [2, 5, 8], [3, 6, 9]] + + @patch('pyevolve.Util.randomFlipCoin') + def test_uniform_crossover(self, coin_flip_mock): + coin_flip_mock.return_value = cycle([1, 0, 0]) + self.assertCrossoverResultsEqual( + Crossovers.G2DListCrossoverUniform, + [[1, 4, 7], [2, 5, 8], [3, 6, 9]], + [[1, 2, 3], [4, 5, 6], [7, 8, 9]], + ) + + @patch('pyevolve.Crossovers.rand_randint') + def test_svp_crossover(self, rand_mock): + rand_mock.return_value = 1 + self.assertCrossoverResultsEqual( + Crossovers.G2DListCrossoverSingleVPoint, + [[1, 4, 7], [4, 5, 8], [7, 6, 9]], + None, + crossover_extra_kwargs={'count': 1} + ) + self.assertCrossoverResultsEqual( + Crossovers.G2DListCrossoverSingleVPoint, + [[1, 4, 7], [4, 5, 8], [7, 6, 9]], + [[1, 2, 3], [2, 5, 6], [3, 8, 9]], + crossover_extra_kwargs={'count': 2} + ) + + @patch('pyevolve.Crossovers.rand_randint') + def test_shp_crossover(self, rand_mock): + rand_mock.return_value = 1 + self.assertCrossoverResultsEqual( + Crossovers.G2DListCrossoverSingleHPoint, + [[1, 2, 3], [2, 5, 8], [3, 6, 9]], + None, + crossover_extra_kwargs={'count': 1} + ) + self.assertCrossoverResultsEqual( + Crossovers.G2DListCrossoverSingleHPoint, + [[1, 2, 3], [2, 5, 8], [3, 6, 9]], + [[1, 2, 3], [4, 5, 6], [7, 8, 9]], + crossover_extra_kwargs={'count': 2} + ) + + +class G2DBinaryStringCrossoversTestCase(CrossoverTestCase): + def setUp(self): + self.mom = G2DBinaryString(3, 3) + self.mom.genomeString = [[0, 0, 0], [0, 0, 1], [0, 1, 0]] + self.dad = G2DBinaryString(3, 3) + self.dad.genomeString = [[0, 1, 1], [1, 0, 0], [1, 0, 1]] + + @patch('pyevolve.Util.randomFlipCoin') + def test_uniform_crossover(self, coin_flip_mock): + coin_flip_mock.return_value = cycle([1, 0, 0]) + self.assertCrossoverResultsEqual( + Crossovers.G2DBinaryStringXUniform, + [[0, 1, 1], [1, 0, 0], [1, 0, 1]], + [[0, 0, 0], [0, 0, 1], [0, 1, 0]], + genome_attr_name='genomeString' + ) + + @patch('pyevolve.Crossovers.rand_randint') + def test_svp_crossover(self, rand_mock): + rand_mock.return_value = 1 + self.assertCrossoverResultsEqual( + Crossovers.G2DBinaryStringXSingleVPoint, + [[0, 1, 1], [0, 0, 0], [0, 0, 1]], + None, + genome_attr_name='genomeString', + crossover_extra_kwargs={'count': 1} + ) + self.assertCrossoverResultsEqual( + Crossovers.G2DBinaryStringXSingleVPoint, + [[0, 1, 1], [0, 0, 0], [0, 0, 1]], + [[0, 0, 0], [1, 0, 1], [1, 1, 0]], + genome_attr_name='genomeString', + crossover_extra_kwargs={'count': 2} + ) + + @patch('pyevolve.Crossovers.rand_randint') + def test_shp_crossover(self, rand_mock): + rand_mock.return_value = 1 + self.assertCrossoverResultsEqual( + Crossovers.G2DBinaryStringXSingleHPoint, + [[0, 0, 0], [1, 0, 0], [1, 0, 1]], + None, + genome_attr_name='genomeString', + crossover_extra_kwargs={'count': 1} + ) + self.assertCrossoverResultsEqual( + Crossovers.G2DBinaryStringXSingleHPoint, + [[0, 0, 0], [1, 0, 0], [1, 0, 1]], + [[0, 0, 0], [0, 0, 1], [0, 1, 0]], + genome_attr_name='genomeString', + crossover_extra_kwargs={'count': 2} + ) + + +class GTreeCrossoversTestCase(CrossoverTestCase): + def setUp(self): + mom_root = GTreeNode(1) + mom_root.addChild(GTreeNode(2)) + mom_root.addChild(GTreeNode(3)) + self.mom = GTree(mom_root) + self.mom.setParams(max_depth=2) + dad_root = GTreeNode(4) + dad_root.addChild(GTreeNode(5)) + dad_root.addChild(GTreeNode(6)) + self.dad = GTree(dad_root) + self.dad.setParams(max_depth=2) + + def assetTreesEqual(self, tree1, tree2): + """ Compares values of simplest trees with root and leafes from root""" + self.assertFalse((tree1 is None and tree2 is not None) or (tree1 is not None and tree2 is None)) + if not(tree1 is None and tree2 is None): + root1, root2 = tree1.getRoot(), tree2.getRoot() + self.assertEqual(root1.node_data, root2.node_data) + root1_childs = set([l.node_data for l in root1.getChilds()]) + root2_childs = set([l.node_data for l in root2.getChilds()]) + print(root1_childs, root2_childs) + self.assertFalse((root1_childs and not root2_childs) or (not root1_childs and root2_childs)) + print(root1_childs, root2_childs) + self.assertFalse(root1_childs - root2_childs) + + @patch('pyevolve.Crossovers.rand_choice') + def test_single_point_crossover(self, rand_mock): + rand_mock.side_effect = lambda u: u[0] + + expected_root1 = GTreeNode(1) # TODO refactor after + expected_root1.addChild(GTreeNode(2)) + expected_root1.addChild(GTreeNode(6)) + expected_root2 = GTreeNode(4) + expected_root2.addChild(GTreeNode(3)) + expected_root2.addChild(GTreeNode(5)) + + self.assertCrossoverResultsEqual( + Crossovers.GTreeCrossoverSinglePoint, + GTree(expected_root1), + None, + genome_attr_name=None, + assertion_name='assetTreesEqual', + crossover_extra_kwargs={'count': 1} + ) + self.assertCrossoverResultsEqual( + Crossovers.GTreeCrossoverSinglePoint, + GTree(expected_root1), + GTree(expected_root2), + genome_attr_name=None, + assertion_name='assetTreesEqual', + crossover_extra_kwargs={'count': 2} + ) + + @nottest # patch GTreeBase.getRandomNode + @patch('pyevolve.Crossovers.rand_choice') + def test_strict_single_point_crossover(self, rand_mock): + rand_mock.side_effect = lambda u: u[0] + + expected_root1 = GTreeNode(6) # TODO refactor after + expected_root1.addChild(GTreeNode(2)) + expected_root1.addChild(GTreeNode(6)) + expected_root2 = GTreeNode(4) + expected_root2.addChild(GTreeNode(3)) + expected_root2.addChild(GTreeNode(5)) + + self.assertCrossoverResultsEqual( + Crossovers.GTreeCrossoverSinglePointStrict, + GTree(expected_root1), + None, + genome_attr_name=None, + assertion_name='assetTreesEqual', + crossover_extra_kwargs={'count': 1} + ) + self.assertCrossoverResultsEqual( + Crossovers.GTreeCrossoverSinglePointStrict, + GTree(expected_root1), + GTree(expected_root2), + genome_attr_name=None, + assertion_name='assetTreesEqual', + crossover_extra_kwargs={'count': 2} + ) diff --git a/test_initializators.py b/test_initializators.py new file mode 100644 index 0000000..94e5a86 --- /dev/null +++ b/test_initializators.py @@ -0,0 +1,42 @@ +import unittest + +from pyevolve.G1DBinaryString import G1DBinaryString +from pyevolve import Initializators +from pyevolve.G1DList import G1DList +from pyevolve.G2DList import G2DList +from pyevolve.GTree import GTree + + +class InitializatorsTestCase(unittest.TestCase): + def test_binary_string_initializator(self): + genome = G1DBinaryString(3) + Initializators.G1DBinaryStringInitializator(genome) + for gen in genome.genomeList: + self.assertTrue(gen in [0, 1]) + + def test_1d_list_real_initializator(self): + genome = G1DList(3) + Initializators.G1DListInitializatorReal(genome) + for gen in genome.genomeList: + self.assertTrue(type(gen) == float) + + def test_2d_list_integer_initializator(self): + genome = G2DList(3, 3) + Initializators.G2DListInitializatorInteger(genome) + for gen_row in genome.genomeList: + for gen in gen_row: + self.assertTrue(type(gen) == int) + + def test_2d_list_real_initializator(self): + genome = G2DList(3, 3) + Initializators.G2DListInitializatorReal(genome) + for gen_row in genome.genomeList: + for gen in gen_row: + self.assertTrue(type(gen) == float) + + def test_tree_integer_initializator(self): + genome = GTree() + genome.setParams(max_depth=3) + Initializators.GTreeInitializatorInteger(genome) + for gen in genome.getAllNodes(): + self.assertTrue(type(gen.getData()) == int) diff --git a/test_mutators.py b/test_mutators.py new file mode 100644 index 0000000..434ad85 --- /dev/null +++ b/test_mutators.py @@ -0,0 +1,179 @@ +import unittest + +from mock import patch + +from pyevolve.G1DBinaryString import G1DBinaryString +from pyevolve import Mutators, Consts +from pyevolve.G1DList import G1DList + + +class G1DBinaryStringMutatorsTestCase(unittest.TestCase): + def setUp(self): + self.genome = G1DBinaryString(3) + self.genome.append(1) + self.genome.append(0) + self.genome.append(0) + + @patch('pyevolve.Util.randomFlipCoin') + def test_swap_mutator_small_pmut(self, coin_flip_mock): + coin_flip_mock.return_value = 0 + expected_result = [1, 0, 0] + Mutators.G1DBinaryStringMutatorSwap(self.genome, pmut=0.1) + self.assertEqual(self.genome.genomeList, expected_result) + + @patch('pyevolve.Mutators.rand_randint') + def test_swap_mutator_large_pmut(self, rand_mock): + rand_mock.return_value = 0 + expected_result = [1, 0, 0] + Mutators.G1DBinaryStringMutatorSwap(self.genome, pmut=0.5) + self.assertEqual(self.genome.genomeList, expected_result) + + + @patch('pyevolve.Util.randomFlipCoin') + def test_flip_mutator_small_pmut(self, coin_flip_mock): + coin_flip_mock.return_value = 1 + expected_result = [0, 1, 1] + Mutators.G1DBinaryStringMutatorFlip(self.genome, pmut=0.1) + self.assertEqual(self.genome.genomeList, expected_result) + + @patch('pyevolve.Mutators.rand_randint') + def test_flip_mutator_large_pmut(self, rand_mock): + rand_mock.return_value = 0 + expected_result = [1, 0, 0] + Mutators.G1DBinaryStringMutatorFlip(self.genome, pmut=0.5) + self.assertEqual(self.genome.genomeList, expected_result) + + +class G1DListMutatorsTestCase(unittest.TestCase): + def setUp(self): + self.genome = G1DList(3) + self.genome.genomeList = [1, 2, 3] + + @patch('pyevolve.Util.randomFlipCoin') + @patch('pyevolve.Mutators.rand_randint') + def test_sim_mutator(self, rand_mock, coin_flip_mock): + rand_mock.side_effect = [0, 2] + coin_flip_mock.return_value = 1 + expected_result = [2, 1, 3] + Mutators.G1DListMutatorSIM(self.genome, pmut=0.5) + self.assertEqual(self.genome.genomeList, expected_result) + + @patch('pyevolve.Util.randomFlipCoin') + @patch('pyevolve.Mutators.rand_randint') + def test_range_mutator_small_pmut(self, rand_mock, coin_flip_mock): + coin_flip_mock.return_value = 1 + rand_mock.side_effect = [0, 2, 4] + expected_result = [0, 2, 4] + Mutators.G1DListMutatorIntegerRange(self.genome, pmut=0.1) + self.assertEqual(self.genome.genomeList, expected_result) + + @patch('pyevolve.Mutators.rand_randint') + def test_range_mutator_large_pmut(self, rand_mock): + rand_mock.return_value = 0 + expected_result = [0, 2, 3] + Mutators.G1DListMutatorIntegerRange(self.genome, pmut=0.5) + self.assertEqual(self.genome.genomeList, expected_result) + + @patch('pyevolve.Util.randomFlipCoin') + @patch('pyevolve.Mutators.rand_uniform') + def test_real_range_mutator_small_pmut(self, rand_mock, coin_flip_mock): + coin_flip_mock.return_value = 1 + rand_mock.side_effect = [0, 2, 4] + expected_result = [0, 2, 4] + Mutators.G1DListMutatorRealRange(self.genome, pmut=0.1) + self.assertEqual(self.genome.genomeList, expected_result) + + @patch('pyevolve.Mutators.rand_randint') + @patch('pyevolve.Mutators.rand_uniform') + def test_real_range_mutator_large_pmut(self, rand_uniform_mock, rand_mock): + rand_mock.return_value = 0 + rand_uniform_mock.return_value = Consts.CDefRangeMin + expected_result = [0, 2, 3] + Mutators.G1DListMutatorRealRange(self.genome, pmut=0.5) + self.assertEqual(self.genome.genomeList, expected_result) + + @patch('pyevolve.Util.randomFlipCoin') + @patch('pyevolve.Mutators.rand_gauss') + def test_integer_gauss_grad_mutator_small_pmut(self, rand_mock, coin_flip_mock): + coin_flip_mock.return_value = 1 + rand_mock.side_effect = [0, 2, 4] + expected_result = [0, 4, 12] + Mutators.G1DListMutatorIntegerGaussianGradient(self.genome, pmut=0.1) + self.assertEqual(self.genome.genomeList, expected_result) + + @patch('pyevolve.Mutators.rand_randint') + @patch('pyevolve.Mutators.rand_gauss') + def test_integer_gauss_grad_mutator_large_pmut(self, rand_gauss_mock, rand_mock): + rand_mock.return_value = 0 + rand_gauss_mock.return_value = Consts.CDefRangeMin + expected_result = [0, 2, 3] + Mutators.G1DListMutatorIntegerGaussianGradient(self.genome, pmut=0.5) + self.assertEqual(self.genome.genomeList, expected_result) + + @patch('pyevolve.Util.randomFlipCoin') + @patch('pyevolve.Mutators.rand_gauss') + def test_integer_gauss_mutator_small_pmut(self, rand_mock, coin_flip_mock): + coin_flip_mock.return_value = 1 + rand_mock.side_effect = [0, 2, 4] + expected_result = [1, 4, 7] + Mutators.G1DListMutatorIntegerGaussian(self.genome, pmut=0.1) + self.assertEqual(self.genome.genomeList, expected_result) + + @patch('pyevolve.Mutators.rand_randint') + @patch('pyevolve.Mutators.rand_gauss') + def test_integer_gauss_mutator_large_pmut(self, rand_gauss_mock, rand_mock): + rand_mock.return_value = 0 + rand_gauss_mock.return_value = Consts.CDefRangeMin + expected_result = [1, 2, 3] + Mutators.G1DListMutatorRealGaussian(self.genome, pmut=0.5) + self.assertEqual(self.genome.genomeList, expected_result) + + @patch('pyevolve.Util.randomFlipCoin') + @patch('pyevolve.Mutators.rand_gauss') + def test_real_gauss_mutator_small_pmut(self, rand_mock, coin_flip_mock): + coin_flip_mock.return_value = 1 + rand_mock.side_effect = [0, 2, 4] + expected_result = [1, 4, 7] + Mutators.G1DListMutatorRealGaussian(self.genome, pmut=0.1) + self.assertEqual(self.genome.genomeList, expected_result) + + @patch('pyevolve.Mutators.rand_randint') + @patch('pyevolve.Mutators.rand_gauss') + def test_real_gauss_mutator_large_pmut(self, rand_gauss_mock, rand_mock): + rand_mock.return_value = 0 + rand_gauss_mock.return_value = Consts.CDefRangeMin + expected_result = [1, 2, 3] + Mutators.G1DListMutatorRealGaussian(self.genome, pmut=0.5) + self.assertEqual(self.genome.genomeList, expected_result) + + @patch('pyevolve.Util.randomFlipCoin') + @patch('pyevolve.Mutators.rand_gauss') + def test_real_gauss_grad_mutator_small_pmut(self, rand_mock, coin_flip_mock): + coin_flip_mock.return_value = 1 + rand_mock.side_effect = [0, 2, 4] + expected_result = [0, 4, 12] + Mutators.G1DListMutatorRealGaussianGradient(self.genome, pmut=0.1) + self.assertEqual(self.genome.genomeList, expected_result) + + @patch('pyevolve.Mutators.rand_randint') + @patch('pyevolve.Mutators.rand_gauss') + def test_real_gauss_grad_mutator_large_pmut(self, rand_gauss_mock, rand_mock): + rand_mock.return_value = 0 + rand_gauss_mock.return_value = Consts.CDefRangeMin + expected_result = [0, 2, 3] + Mutators.G1DListMutatorRealGaussianGradient(self.genome, pmut=0.5) + self.assertEqual(self.genome.genomeList, expected_result) + + @patch('pyevolve.Util.randomFlipCoin') + def test_binary_mutator_small_pmut(self, coin_flip_mock): + coin_flip_mock.return_value = 1 + expected_result = [0, 2, 3] + Mutators.G1DListMutatorIntegerBinary(self.genome, pmut=0.1) + self.assertEqual(self.genome.genomeList, expected_result) + + @patch('pyevolve.Mutators.rand_randint') + def test_binary_mutator_large_pmut(self, rand_mock): + rand_mock.return_value = 0 + expected_result = [1, 2, 3] + Mutators.G1DListMutatorIntegerBinary(self.genome, pmut=0.5) + self.assertEqual(self.genome.genomeList, expected_result) diff --git a/test_simple_ga.py b/test_simple_ga.py new file mode 100644 index 0000000..4450fdc --- /dev/null +++ b/test_simple_ga.py @@ -0,0 +1,71 @@ +from unittest import TestCase + +from pyevolve import GSimpleGA, G1DList, Consts +from pyevolve.GTree import GTreeGP + + +class GSimpleGATestCase(TestCase): + def setUp(self): + self.genome = G1DList.G1DList(2) + self.genome.evaluator.set(lambda _: 0) + self.ga = GSimpleGA.GSimpleGA(self.genome) + + def test_works_fine(self): + self.ga.evolve(freq_stats=1) + self.assertTrue(self.ga.bestIndividual()) + + def test_works_fine_with_elitism(self): + self.ga.setElitismReplacement(2) + self.ga.evolve(freq_stats=1) + self.assertTrue(self.ga.bestIndividual()) + + def test_get_different_results_for_different_evaluators(self): + self.ga.evolve(freq_stats=1) + result1 = self.ga.bestIndividual() + self.genome.evaluator.set(lambda _: 100) + self.ga = GSimpleGA.GSimpleGA(self.genome) + self.ga.evolve(freq_stats=1) + result2 = self.ga.bestIndividual() + self.assertNotEqual(result1, result2) + + def test_fails_with_negative_evaluator(self): + self.genome.evaluator.set(lambda _: -1) + self.ga = GSimpleGA.GSimpleGA(self.genome) + self.assertRaises(ValueError, self.ga.evolve, {'freq_stats': 1}) + + def test_stem_call_replaces_internal_pop(self): + self.ga.initialize() + pop1 = self.ga.internalPop + self.ga.step() + pop2 = self.ga.internalPop + self.assertFalse(pop1 is pop2) + + def test_gp_mode_is_set_for_tree_genome(self): + ga = GSimpleGA.GSimpleGA(GTreeGP()) + self.assertTrue(ga.GPMode) + + def test_exception_on_wrong_multiprocessing_argument(self): + self.assertRaises(TypeError, self.ga.setMultiProcessing, {'flag': 'not_bool_argument'}) + self.assertRaises(TypeError, self.ga.setMultiProcessing, {'full_copy': 'not_bool_argument'}) + self.assertRaises(TypeError, self.ga.setMultiProcessing, {'flag': 'not_bool_argument', 'full_copy': True}) + self.assertRaises(TypeError, self.ga.setMultiProcessing, {'flag': True, 'full_copy': 'not_bool_argument'}) + + def test_exception_no_wrong_mutation_rate_size(self): + self.assertRaises(BaseException, self.ga.setMutationRate, [2]) + #self.assertRaises(ValueError, self.ga.setMutationRate, [2]) + + def test_repr(self): + ga = self.ga + ga_repr = ga.__repr__() + for param in [ + ga.getGPMode(), + ga.internalPop.popSize, + ga.nGenerations, + ga.currentGeneration, + ga.pMutation, + ga.pCrossover, + ga.elitism, + ga.nElitismReplacement, + ga.dbAdapter, + ]: + self.assertIn(str(param), ga_repr) diff --git a/test_util.py b/test_util.py new file mode 100644 index 0000000..a0625d1 --- /dev/null +++ b/test_util.py @@ -0,0 +1,19 @@ +from unittest import TestCase + +from pyevolve import Util + + +class UtilTestCase(TestCase): + def test_listSwapElement(self): + _list = [1, 2, 3] + Util.listSwapElement(_list, 0, 1) + self.assertEqual(_list, [2, 1, 3]) + + def test_randomFlipCoin_border_cases(self): + self.assertEqual(Util.randomFlipCoin(0.0), False) + self.assertEqual(Util.randomFlipCoin(1.0), True) + + def test_list2DSwapElement(self): + _list = [[1, 2, 3], [4, 5, 6]] + Util.list2DSwapElement(_list, (0, 1), (1, 1)) + self.assertEqual(_list, [[1, 5, 3], [4, 2, 6]]) \ No newline at end of file