GeneticAlgorithm  0.5 (beta)
A python framework for rapid GA prototyping
src/examples/KnapsackDemo.py
Go to the documentation of this file.
00001 import math
00002 from GeneticAlgorithm import *
00003 ## @page KnapsackPage Solving the Knapsack problem
00004 #  
00005 #  @section KnapsackIntroduction Introduction
00006 #  This page describes how to use the GeneticAlgorithm framework to solve the knapsack problem.
00007 #  The knapsack0-1 problem can be defined as follows:
00008 #   \f{eqnarray*}{
00009 #        \max \limits_{\textbf{s}} z =& \textbf{c}^{\textrm{T}}\textbf{s} \\
00010 #        \textrm{subject to} & \\
00011 #                 & \textbf{v}^{\textrm{T}}\textbf{s} \leq V_{\textrm{max}}\\
00012 #   \f}
00013 #  Where
00014 #  \f$ \textbf{c} \f$ is the cost vector. Element \f$ c_{i} \f$ is the gain from taking object \f$i\f$.
00015 #  \f$ \textbf{v} \f$ is the volume vector. Element \f$ v_{i} \f$ is the volume consumed when taking object \f$i\f$.
00016 #  \f$ \textbf{s} \f$ is the solution. Each decision variable \f$ s_{i}\f$ is binary, and takes the value 1 if the object \f$i\f$ is selected to be part of the solution and 0 otherwise.
00017 #  \f$ V_{\textrm{max}} \f$ is the maximum volume allowed for a solution. Every valid solution must comply with the restriction \f$ V_{\textrm{max}} \leq \textbf{c}^{\textrm{T}}\textbf{s} \f$. The parameter \f$ \lambda \f$ is a penalization factor, used to penalize all the unfeasible individuals.
00018 #
00019 #  The objective function of the problem has been modified according to the lagrangian relaxation technique. Furthermore, to make the \f$ \lambda \f$ selection parameter easier, the penalty term has been implemented in a non-linear way, that penalizes only the non-feasible.   
00020 #
00021 #  @section KnapsackEvaluation Knapsack: Deriving the BaseEvaluationOperator to implement the knapsack objective function
00022 #  
00023 #  The GeneticAlgorithm framework provides a set of GeneticAlgorithm that can be used to easily implement an evaluation function. The GeneticAlgorithm.BaseEvaluationOperator provides the evaluateIndividual method, that can be overloaded to evaluate every individual in the population. All GeneticAlgorithm EvaluationOperators should contain all the non-genotype parameters required to evaluate an individual internally. The __init__ function should be overloaded to receive the problem instance parameters and save them internally. See tha Knapsack class documentation for detailed information on these metods.
00024 #
00025 #  @subsection KnapsackInit The initialization function
00026 #  This snippet of code describes the initialization function. Original commentaries are included, because they describe the class members and relate them to the ecuation shown above.
00027 #  Notice that the initialization function receives and stores all the instance-related constants as object members. This is the expected behavior of any derived Evaluation Operator.
00028 #  @code
00029 #    def __init__(self, maxVolume=0, objectVolumes=[], volumeLambda=0.0, objectCosts=[], **kwargs):
00030 #        super(Knapsack, self).__init__(**kwargs)
00031 #        ## @member maxVolume The maximum volume allowed for feasible solutions \f$ V_{\textrm{max}} \f$
00032 #        self.maxVolume = maxVolume
00033 #        ## @member objectVolumes The vector of object volumes \f$ \textbf{v} \f$
00034 #        self.objectVolumes = objectVolumes
00035 #        ## @member objectCosts The vector of object costs \f$ \textbf{c} \f$
00036 #        self.objectCosts = objectCosts
00037 #        ## @member volumeLambda The penalization factor \f$ \lambda \f$
00038 #        self.volumeLambda = volumeLambda
00039 ## @endcode
00040 #
00041 #  @subsection KnapsackEval The evaluateIndividual function
00042 #
00043 #  The main objective of all derived EvaluationOperators is to implement this function. The GeneticAlgorithm Scheduler calls this function for each newly generated individuals, every iteration. The programmer is responsible for filling in the individual.fitness member with the result of its evaluation, as exemplified below:
00044 #
00045 #  @code
00046 #    def evaluateIndividual(self, individual):
00047 #        # Get the vector s
00048 #        segmentsValues = [segment.data for segment in individual.genotype.segments]
00049 #        # Compute the volume used by the solution
00050 #        usedVolume = reduce(lambda x, y: x+y, map(lambda v, s:v*s, self.objectVolumes, segmentsValues))
00051 #        # Compute the solution cost
00052 #        solutionCost = reduce(lambda x, y: x+y, map(lambda v, s:v*s, self.objectCosts, segmentsValues))
00053 #        # The difference between the maximum allowed volume and the solution volume is the residual volume
00054 #        residualVolume = self.maxVolume - usedVolume
00055 #        # If the residual volume is less than 0, the solution is not penalized. The solution is penalized with a cost of lambda*residualVolume otherwise
00056 #        lambdaPenalty = 0 if residualVolume > 0 else self.volumeLambda*residualVolume
00057 #        # The individual fitness is the penalty plus the objective
00058 #        individual.fitness = solutionCost + lambdaPenalty
00059 #  @endcode
00060 
00061  
00062 ## @class Knapsack
00063 #  @brief An evaluation operator that computes the relaxed version of the objective function used to solve a knapsack instance.
00064 #
00065 #  This class implements the following evaluation function:
00066 #   \f{eqnarray*}{
00067 #        \max \limits_{\textbf{s}} z =& \textbf{c}^{\textrm{T}}\textbf{s} + f_{\lambda}(\textbf{v}, \textbf{s}) \\
00068 #        f_{\lambda}(\textbf{v}, \textbf{s}) =&
00069 #               \left\{
00070 #                 \begin{array}{rl}
00071 #                   \lambda (V_{\textrm{max}} - \textbf{c}^{\textrm{T}}\textbf{s}) & \textrm{ if }  \textbf{v}^{\textrm{T}}\textbf{s} \leq V_{\textrm{max}}\\
00072 #                   0 &  \textrm{otherwise}
00073 #                 \end{array}
00074 #               \right. \\
00075 #   \f}
00076 #
00077 #  The parameter \f$ \textbf{c} \f$ is the cost vector. Element \f$ c_{i} \f$ is the gain from taking object \f$i\f$.
00078 #  The parameter \f$ \textbf{v} \f$ is the volume vector. Element \f$ v_{i} \f$ is the volume consumed when taking object \f$i\f$.
00079 #  The parameter \f$ \textbf{s} \f$ is the solution. Each decision variable \f$ s_{i}\f$ is binary, and takes the value 1 if the object \f$i\f$ is selected to be part of the solution and 0 otherwise.
00080 #  The parameter \f$ V_{\textrm{max}} \f$ is the maximum volume allowed for a solution. Every valid solution must comply with the restriction \f$ V_{\textrm{max}} \leq \textbf{c}^{\textrm{T}}\textbf{s} \f$. The parameter \f$ \lambda \f$ is a penalization factor, used to penalize all the unfeasible individuals.
00081 class Knapsack(EvaluationOperators.BaseEvaluationOperator):
00082     ## @fn __init__(self, maxVolume=0, objectVolumes=[], volumeLambda=0.0, objectCosts=[], **kwargs)
00083     #  @brief Initialize an evaluation operator to implement the evaluation function as described above
00084     #  @param maxVolume The maximum volume allowed for feasible solutions \f$ V_{\textrm{max}} \f$
00085     #  @param objectVolumes The vector of object volumes \f$ \textbf{v} \f$
00086     #  @param objectCosts The vector of object costs \f$ \textbf{c} \f$
00087     #  @param volumeLambda The penalization factor \f$ \lambda \f$
00088     def __init__(self, maxVolume=0, objectVolumes=[], volumeLambda=0.0, objectCosts=[], **kwargs):
00089         super(Knapsack, self).__init__(**kwargs)
00090         ## @member maxVolume The maximum volume allowed for feasible solutions \f$ V_{\textrm{max}} \f$
00091         self.maxVolume = maxVolume
00092         ## @member objectVolumes The vector of object volumes \f$ \textbf{v} \f$
00093         self.objectVolumes = objectVolumes
00094         ## @member objectCosts The vector of object costs \f$ \textbf{c} \f$
00095         self.objectCosts = objectCosts
00096         ## @member volumeLambda The penalization factor \f$ \lambda \f$
00097         self.volumeLambda = volumeLambda
00098 
00099     ## @fn evaluateIndividual(self, individual)
00100     #  @brief Evaluate an individual according to the function described above
00101     #  @param individual The list [segment.data for segment in individual.genotype.segments] contains the solution vector \f$ \textbf{s} \f$
00102     def evaluateIndividual(self, individual):
00103         # Get the vector s
00104         segmentsValues = [segment.data for segment in individual.genotype.segments]
00105         # Compute the volume used by the solution
00106         usedVolume = reduce(lambda x, y: x+y, map(lambda v, s:v*s, self.objectVolumes, segmentsValues))
00107         # Compute the solution cost
00108         solutionCost = reduce(lambda x, y: x+y, map(lambda v, s:v*s, self.objectCosts, segmentsValues))
00109         # The difference between the maximum allowed volume and the solution volume is the residual volume
00110         residualVolume = self.maxVolume - usedVolume
00111         # If the residual volume is less than 0, the solution is not penalized. The solution is penalized with a cost of lambda*residualVolume otherwise
00112         lambdaPenalty = 0 if residualVolume > 0 else self.volumeLambda*residualVolume
00113         # The individual fitness is the penalty plus the objective
00114         individual.fitness = solutionCost + lambdaPenalty
00115 
00116 ## @page KnapsackPage Solving the Knapsack problem
00117 #  @section KnapsackGA Running a GeneticAlgorithm using the Knapsack EvaluationOperator
00118 #
00119 #  The knapsack evaluation operator is used in conjunction with the rest of the GeneticAlgorithm framework. This section demonstrates how to implement a script to generate a random knapsack instance and solve it.
00120 #
00121 
00122 if __name__=='__main__':
00123 ## @page KnapsackPage Solving the Knapsack problem
00124 #  @subsection Generating a random instance
00125 #  This section demonstrates how to generate a random Knapsack instance. The python random package is used for this purpose, as demonstrated below:
00126 #
00127 #  @code 
00128 #    import random
00129 #    random.seed(0)
00130 #    
00131 #    # Kanpsack instance parameters
00132 #    nObjects =  12
00133 #    
00134 #    objectVolumes = [random.randrange(1, 20) for i in xrange(nObjects)]
00135 #    objectCosts = [random.randrange(10, 20) for i in xrange(nObjects)]
00136 #    maxVolume = reduce(lambda x, y: x+y, objectVolumes) / 2
00137 #    volumeLambda = maxVolume*10
00138 #  @endcode    
00139 #
00140 #  The object volumes and costs are generated randomly, while the maxVolume is kept at half the volume of all objects, to make sure that the instance is non-trivial and not every object fits in the optimal solution. The penalty constant volumeLambda is chosen to be proportional to the maximumVolume; This guarantees that non-feasible individuals have lower evaluations than the feasible individuals. 
00141 #
00142 #  @subsection KnapsackConfig Configuring the genetic algoritm
00143 #
00144 #  The Knapsack evaluation function is designed to maximize, so we create a flag that indicates that, and use it to initialize the selection and logging operators.
00145 #  @code 
00146 #    maximize = True
00147 #  @endcode
00148 #
00149 #  The number of generations to run, the populationSize and the generationSize are chosen to be proportional to the size of the problem. These numbers are not guaranteed to produce the best result for a particular instance, but have been found experimentally to produce good results when compared to non-probabilistic search algorithms
00150 # 
00151 #  @code
00152 #    nGenerations = nObjects*5
00153 #    popSize = nObjects*10
00154 #    genSize = int(math.floor( popSize/10 ))
00155 #  @endcode 
00156 #
00157 #  The variable nGenerations will be used to control how many iterations to run using a GAScheduler. The variable popSize will be used during the population construction, and determines how many individuals are contained simultaneously in a population. The variable genSize will be used to initialize the population as well, and controls how many individuals are replaced with each iteration. As a thumbrule, all the parameters that need to be shared between operators, should be a part of the population object. At this point, every required parameter is defined, and we can proceed to initialize GeneticAlgorithm objects.
00158 #
00159 #  First, a prototype chromosome is created. This is used to initialize all the individuals in the population uniformly.
00160 #
00161 #  @code
00162 #    ch = Core.Genotype(segments=[GenotypeLibrary.BinaryChromosomeSegment(nBits=1) for i in range(nObjects)])
00163 #  @endcode
00164 #
00165 #  The main data container on a genetic algorithm is the population object. This contains a list of individuals, and all the parameters that an operator needs to determine how to transform the population, such as mutation probability, crossover probability, the maximization target and how many individuals to replace per iteration (genSize).
00166 #  @code
00167 #    p  = Core.Population(schema=ch, popSize=popSize, genSize=genSize, maximize=maximize, mutation_probability=0.01)
00168 #  @endcode
00169 #
00170 #  The Scheduler object contains a population object, as well as a list of genetic operators that are applied iteratively to the population. Here we instantiate some useful operators:
00171 #  @code
00172 #    ga = Core.Scheduler(name='Demo',\
00173 #                        population=p,\
00174 #                        operators=[Knapsack(maxVolume, objectVolumes, volumeLambda, objectCosts),\
00175 #                                   LoggingOperators.LogGenerations(iterationFrequency=1),\
00176 #                                   PlottingOperators.PlotBestLogger(iterationFrequency=1, maximize=maximize),\
00177 #                                   SelectionOperators.KTournament(),\
00178 #                                   SelectionOperators.SelectLethals(),\
00179 #                                   Core.Crossover(),\
00180 #                                   Core.Mutate()])  
00181 #  @endcode
00182 #
00183 #  The evaluartion operator is the first one, because individuals need to be evaluated before logging, selection, crossover or mutation can occur. The first GeneticOperator object in the list is an instance of our Knapsack class; Note that every variable that characterizes an instance is passed to the initialization function of the object, so that it can reference them whenever it is required.
00184 #  The logging operators come next. The LogGenerations operator simply stores a copy of a population and all its individuals every time that an iterationFrequency iterations have elapsed. This example stores every generation of the run.
00185 #  The PlotBestLogger operator is instantiated next. This operator can be instantiated multiple times, and used to keep track of several properties of an individual. This is the reason why the maximization flag has to be passed to this operator. This class samples the fitness member by default every time that an iterationFrequency iterations have elapsed.
00186 #  The traditional genetic operators are instantiated next. The KTournament operator implements a 2-tournament selection scheme by default; It sets the matingPool member of the population object to contain a list of indices, which identify the parent individuals used to produce the iteration offspring. The SelectLethals operator selects the worse genSize individuals and marks them for removal with every iteration. The crossover operator generates new individuals by combining the genotypes of the parents in the mating pool, using a one-point crossover algorithm. Finally, the mutation operator may select a random bit from the new offspring and flip its value, to introduce variability to the population.
00187 #
00188 #  @code
00189 #    ga.runGA(nGenerations)
00190 #  @endcode
00191 #
00192 #  The last block calls the runGA function of the newly created scheduler. This causes the GeneticAlgorithm to run for nGenerations. Every GeneticOperator is an object that supports the basic initialize(), iterate() and finalize() functions. As their name suggests, the Scheduler calls the initialization function of every operator once at the beginning of the run. The iterate function is called nGenerations times, using the genetic operators in a round-robin order. The finalize function of every object is called once after the last iteration, this step is usually implemented by logging operators to print the ga run logged variables to a file or screen.
00193     import random
00194     random.seed(0)
00195     
00196     # Kanpsack instance parameters
00197     nObjects =  12
00198     
00199     objectVolumes = [random.randrange(1, 20) for i in xrange(nObjects)]
00200     objectCosts = [random.randrange(10, 20) for i in xrange(nObjects)]
00201     maxVolume = reduce(lambda x, y: x+y, objectVolumes) / 2
00202     volumeLambda = maxVolume*10
00203     
00204     # GA parameters
00205     maximize = True
00206     nGenerations = nObjects*5
00207     popSize = nObjects*10
00208     genSize = int(math.floor( popSize/10 ))
00209         
00210     # Genetic algorithm
00211     ch = Core.Genotype(segments=[GenotypeLibrary.BinaryChromosomeSegment(nBits=1) for i in range(nObjects)])
00212     p  = Core.Population(schema=ch, popSize=popSize, genSize=genSize, maximize=maximize, mutation_probability=0.01)
00213     ga = Core.Scheduler(name='Demo',\
00214                         population=p,\
00215                         operators=[Knapsack(maxVolume, objectVolumes, volumeLambda, objectCosts),\
00216                                    LoggingOperators.LogGenerations(iterationFrequency=1),\
00217                                    PlottingOperators.PlotBestLogger(iterationFrequency=1, maximize=maximize),\
00218                                    SelectionOperators.KTournament(),\
00219                                    SelectionOperators.SelectLethals(),\
00220                                    Core.Crossover(),\
00221                                    Core.Mutate()])    
00222     ga.runGA(nGenerations)
00223     
00224     print 'Object Volumes', objectVolumes
00225     print 'Object Costs', objectCosts
00226     print 'Max Volume', maxVolume
00227     print 'Volume Lambda', volumeLambda
 All Classes Namespaces Files Functions Variables