|
GeneticAlgorithm
0.5 (beta)
A python framework for rapid GA prototyping
|
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