├── .gitignore ├── LICENSE ├── README.md ├── fmga ├── LICENSE ├── README.md └── fmga │ └── function_maximize.py ├── fmga_neuro.py ├── fmga_plots.py ├── fmga_spiral_linear.png ├── fmga_spiral_neuro.png ├── fmga_test.py ├── functionplot.png ├── meanstats.png └── vcga └── vcga └── vertex_cover.py /.gitignore: -------------------------------------------------------------------------------- 1 | # core files 2 | fmga/* 3 | vcga/* 4 | 5 | !fmga/fmga 6 | fmga/fmga/* 7 | 8 | !vcga/vcga 9 | vcga/vcga/* 10 | 11 | !fmga/fmga/function_maximize.py 12 | !fmga/README.md 13 | !fmga/LICENSE 14 | 15 | !vcga/vcga/vertex_cover.py 16 | !vcga/README.md 17 | 18 | # all the generated files 19 | .idea 20 | __pycache__ 21 | 22 | # just the test 23 | function_maximize_test.py 24 | multiprocessing_test.py -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Ameya Daigavane 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GeneticAlgorithmsRepo [![codebeat badge](https://codebeat.co/badges/ccb58ecb-0bd0-45ba-aa71-56a6758d9cfe)](https://codebeat.co/projects/github-com-ameya98-geneticalgorithmsrepo-master) 2 | An assortment of genetic algorithms - all written from scratch, for Python 3.5. 3 | ### Objective Function Maximization 4 | 5 | ![](functionplot.png) 6 | 7 | **fmga** is a genetic algorithms package for arbitrary function maximization. 8 | fmga is available on PyPI - latest version 2.7.0 - and now supports multiprocessing, a **fmga.minimize()** method and an easier interface to unpack arguments with **fmga.unpack()**! 9 | ```bash 10 | pip install fmga 11 | ``` 12 | Then, use with: 13 | ```python 14 | import fmga 15 | fmga.maximize(f, population_size=200, iterations=40, multiprocessing=True) 16 | ``` 17 | The objective function doesn't have to be differentiable, or even continuous in the specified domain! 18 | The population of multi-dimensional points undergoes random mutations - and is selected through elitism and raking selection with selection weights 19 | inversely proportional to fitness and diversity ranks. 20 | 21 | **fmga_plots.py** contains relevant code on obtaining surface plots of the function (if 2-dimensional), initial population and final population graphs, as well as line plots of mean population fitness 22 | and L1 diversity through the iterations. 23 | Completely customizable - licensed under the MIT License. 24 | 25 | ![](fmga_spiral_neuro.png) 26 | A neural network trained by **fmga** by code in **fmga_neuro.py**, inspired by [this](http://cs231n.github.io/neural-networks-case-study/) exercise. 27 | 28 | See the README in the **fmga** subdirectory for more details. 29 | 30 | ### Minimum Vertex Cover for Graphs 31 | **vertex_cover.py** gives a genetic algorithm heuristic to the well-known NP-Complete Minimum Vertex Cover problem - given a graph, find a subset of its vertices such that every edge has an endpoint in this subset. 32 | This is an implementation of the OBIG heuristic with an indirect binary decision diagram encoding for the chromosomes, which is described in this paper by Isaac K. Evans, [here](https://pdfs.semanticscholar.org/4309/66ae3423f07738748f6cd5cef4f108ca87ea.pdf). 33 | A [networkx](https://networkx.github.io/) graph is used as an input - and a population of vertex covers mutates and breeds to find an minimum vertex cover. 34 | As above, elitism and breeding with selection weights inversely proportional to fitness and diversity ranks are used to generate the new population. 35 | The fitness of each vertex cover is defined as: 36 |

37 | 39 | -------------------------------------------------------------------------------- /fmga/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2018] [Ameya Daigavane] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /fmga/README.md: -------------------------------------------------------------------------------- 1 | ## fmga 2 | **fmga** (**f**unction **m**aximization through **g**enetic **a**lgorithms) is a package that takes a genetic algorithm approach to maximization problem of non-convex objective functions in multiple dimensions. 3 | 4 | The objective function doesn't have to be differentiable, or even continuous in the specified domain! 5 | The idea is to sample an evolving population of points converging to the function maximum over many iterations. 6 | 7 | The population of n-dimensional points undergoes random mutations - and is selected through elitism and ranking selection with selection weights inversely proportional to fitness and diversity ranks. 8 | 9 | **fmga** now supports multiprocessing through **[pathos](https://github.com/uqfoundation/pathos)** too! 10 | 11 | ### Installation 12 | Install with pip: 13 | ```bash 14 | pip install fmga 15 | ``` 16 | Import within the python script with: 17 | ```python 18 | import fmga 19 | ``` 20 | 21 | ### Execution 22 | Given a function on multiple variables, say: 23 | ```python 24 | def f(x, y, z): 25 | return x - math.sin(y) * z 26 | ``` 27 | Pass this function as the *objective_function* argument to the .maximize() method (lambdas work too!). 28 | 29 | ```python 30 | best_point = fmga.maximize(f, population_size=60, dimensions=3) 31 | ``` 32 | 33 | The **maximize()** method creates a **Population** of **Point** objects, calls the **.converge()** method on the Population object, and finally, 34 | returns a **Point** object representing the n-dimensional point with best fitness through the **.best_estimate()** method. 35 | 36 | ```python 37 | print(best_point, best_point.fitness) 38 | ``` 39 | By default, the *multiprocessing* argument defaults to False, so to enable multiprocessing, set this argument to True, and pass the number of processes to be spawned as the *processes* argument. 40 | ```python 41 | best_point = fmga.maximize(f, multiprocessing=True, processes=4) 42 | ``` 43 | Note that, when multiprocessing is enabled on Windows systems, you must put a guard over the entry point of your script. 44 | See [here](https://docs.python.org/2/library/multiprocessing.html#windows) for a how-to. 45 | 46 | fmga also supports a variable number of dimensions to optimise over, passed as the *dimensions* argument, which defaults to the number of arguments of the objective function passed. 47 | 48 | If you wish to interact with the **Population** object directly, you can. 49 | Both of the following work: 50 | ```python 51 | population = fmga.Population(f, population_size=60, dimensions=3) 52 | population = fmga.Population(population_size=60, objective_function=f, dimensions=3) 53 | ``` 54 | If you wish to define custom boundaries, create a list of tuples, for each dimension. Default boundaries are (0, 100). 55 | (This is different than in versions 1.x) 56 | ```python 57 | boundaries = [(0, 2.5), (0, 10)] 58 | ``` 59 | and pass this as the *boundaries* argument to the **Population** constructor or the **maximise()** method: 60 | ```python 61 | population = fmga.Population(f, population_size=60, boundaries=boundaries) 62 | best_point = fmga.maximize(f, population_size=60, boundaries=boundaries) 63 | ``` 64 | Note that the default range for missing dimensions is (0, 100). 65 | The population can be set to breed and iterate by using the **.converge()** method. 66 | ```python 67 | population.converge(iterations=20) 68 | ``` 69 | To perform only one iteration of breeding and mutating, do: 70 | ```python 71 | population.iterate() 72 | ``` 73 | Access population mean fitness and mean L1 diversity stats through the _.mean_fitness_ and _.mean_diversity_ attributes: 74 | ```python 75 | print(population.mean_fitness, population.mean_diversity) 76 | ``` 77 | 78 | The **.best_estimate()** method returns the point closest to the function point of maxima in the population, as a **Point** object. 79 | ```python 80 | best_point = population.best_estimate() 81 | ``` 82 | Every **Point** object has the __coordinates__ attribute, a numpy array signifying the coordinates of point. 83 | To find the value of the function at this point, use the __fitness__ attribute. 84 | ```python 85 | print(best_point.coordinates) 86 | print(best_point.fitness) 87 | ``` 88 | 89 | ## Population Class Methods 90 | The Population constructor takes the following arguments, in order: 91 | **objective_function** The function to maximize! 92 | **dimensions** (default = number of arguments of objective_function) The dimensionality of the points and the number of variables to maximize over. 93 | 94 | From versions 2.8.0 and onward, the PopulationParameters class handles the parameters below. 95 | The interface is the same as previous versions, however, so you can pass these arguments to the Population constructor as before. 96 | 97 | **population_size** (default = 60) Number of points in the population. 98 | **boundaries** (default = (0, 100) for every dimension) Must be an iterable of tuples. The tuple indicates the domain where the points are spread along that dimension. 99 | **elite_fraction** (default = 0.1) Fraction of the population's points to be kept as elite during breeding. Must be between 0 and 1, inclusive. 100 | **mutation_probability** (default = 0.05) How likely is is for a single point to mutate - this probability is the same for all points in the population. 101 | Must be between 0 and 1, inclusive. 102 | **mutation_range** (default = 5) The range of the mutation when it does occur. Note that the point will never mutate out of the domain defined! 103 | **multiprocessing** (default = False) Whether multiprocessing is enabled 104 | **processes** (default = multiprocessing.cpu_count()) Number of processes to spawn if multiprocessing is enabled. 105 | 106 | The **maximize()** method takes all of the above, an **iterations** argument, 107 | defaulting to 15, signifying the number of iterations that the underlying population undergoes, as well as a **verbose** argument (default = 0, was 2 for versions <= 2.4.0) denoting how much console output to be displayed after each iteration (Must take values 0, 1 or 2 with 2 representing the most output, and 0 representing none.) 108 | 109 | The **converge()** and **iterate()** methods also take the **iterations** and **verbose** arguments. 110 | 111 | The **minimize()** method is a wrapper over the **maximize()** method - replacing the objective function by its negative, and maximizing this new objective function. 112 | 113 | The **unpack()** method accepts two arguments, a tuple of values and a list of shapes. If the length of the list of shapes is greater than one, it returns a list of numpy arrays of shape according to the list, by reshaping the tuple in-order. 114 | Otherwise it returns just a numpy array of the passed shape, formed by reshaping the tuple. 115 | This is useful when working with a large number of arguments! Example: 116 | ```python 117 | def f(*args): 118 | x, y, z = fmga.unpack(args, (1, (2, 2), 4)) 119 | 120 | # x.shape == (1,) 121 | # y.shape == (2, 2) 122 | # z.shape == (4,) 123 | 124 | return x - y[0][0] + z[2] 125 | ``` 126 | 127 | ## Dependencies 128 | * numpy 129 | * pathos (>= 0.2.2.1) -------------------------------------------------------------------------------- /fmga/fmga/function_maximize.py: -------------------------------------------------------------------------------- 1 | ''' 2 | fmga 3 | Genetic Algorithms - Objective Function Maximization 4 | Author: Ameya Daigavane 5 | Date: 15th April, 2018 6 | ''' 7 | 8 | # External library dependencies 9 | from random import randint, uniform 10 | import numpy as np 11 | import pathos.multiprocessing as mp 12 | 13 | 14 | # Weighted choice function - each choice has a corresponding weight 15 | def weighted_choice(choices, weights): 16 | normalized_weights = np.array([weight for weight in weights]) / np.sum(weights) 17 | threshold = uniform(0, 1) 18 | total = 1 19 | 20 | for index, normalized_weight in enumerate(normalized_weights): 21 | total -= normalized_weight 22 | if total < threshold: 23 | return choices[index] 24 | 25 | 26 | # Point class and method definitions 27 | class Point: 28 | 29 | # Create random n-dimensional point within boundaries 30 | def __init__(self, associated_population=None, dimensions=2): 31 | 32 | if associated_population is None: 33 | self.associated_population = None 34 | self.boundaries = [(0, 100) for _ in range(dimensions)] 35 | self.mutation_range = 5 36 | else: 37 | self.associated_population = associated_population 38 | self.boundaries = associated_population.parameters.boundaries 39 | self.mutation_range = associated_population.parameters.mutation_range 40 | 41 | # Initialize coordinates uniformly random in range for each dimension 42 | self.coordinates = np.array([uniform(self.boundaries[dimension][0], self.boundaries[dimension][1]) for dimension in range(dimensions)]) 43 | 44 | self.index = -1 45 | self.fitness = 0.0 46 | self.diversity = 0.0 47 | self.fitness_rank = -1 48 | self.diversity_rank = -1 49 | 50 | # Fitness score - objective function evaluated at the point 51 | def evaluate_fitness(self, eval_function=None): 52 | try: 53 | self.fitness = eval_function(*self.coordinates) 54 | return self.fitness 55 | except TypeError: 56 | print("function passed is invalid.") 57 | raise 58 | 59 | # Mutation operator 60 | def mutate(self): 61 | # Choose an index at random 62 | index = randint(0, np.size(self.coordinates) - 1) 63 | self.coordinates[index] += uniform(-self.mutation_range, self.mutation_range) 64 | 65 | # Ensure the point doesn't mutate out of range! 66 | self.coordinates[index] = min(self.boundaries[index][0], self.coordinates[index]) 67 | self.coordinates[index] = max(self.boundaries[index][1], self.coordinates[index]) 68 | 69 | def __repr__(self): 70 | return repr(self.coordinates) 71 | 72 | 73 | # Parameter object for the Population parameters 74 | class PopulationParameters: 75 | 76 | def __init__(self, dimensions, **kwargs): 77 | self.num_dimensions = dimensions 78 | self.population_size = kwargs.get('population_size', 60) 79 | self.boundaries = kwargs.get('boundaries') 80 | self.elite_fraction = kwargs.get('elite_fraction', 0.1) 81 | self.mutation_probability = kwargs.get('mutation_probability', 0.1) 82 | self.mutation_range = kwargs.get('mutation_range', 0.1) 83 | 84 | # Data-validation for parameters 85 | if self.elite_fraction > 1.0 or self.elite_fraction < 0.0: 86 | raise ValueError("Parameter 'elite_fraction' must be in range [0,1].") 87 | 88 | if self.mutation_probability > 1.0 or self.mutation_probability < 0.0: 89 | raise ValueError("Parameter 'mutation_probability' must be in range [0,1].") 90 | 91 | # Assign default boundaries if nothing passed 92 | if self.boundaries is None: 93 | self.boundaries = [(0, 100)] * self.num_dimensions 94 | else: 95 | try: 96 | # Default boundaries for missing parameters 97 | for dimension in range(len(self.boundaries), self.num_dimensions): 98 | self.boundaries.append((0, 100)) 99 | 100 | # Validate passed boundaries 101 | for dimension in range(len(self.boundaries)): 102 | if float(self.boundaries[dimension][0]) > float(self.boundaries[dimension][1]): 103 | raise ValueError("Incorrect value for boundary - min greater than max for range.") 104 | 105 | except TypeError: 106 | raise TypeError("Boundaries not passed correctly.") 107 | 108 | 109 | # Population class and method definition 110 | class Population: 111 | def __init__(self, objective_function=None, dimensions=None, **kwargs): 112 | # No dimensions argument passed 113 | if dimensions is None: 114 | try: 115 | # Use the objective function's number of arguments as dimensions 116 | dimensions = objective_function.__code__.co_argcount 117 | except TypeError: 118 | raise TypeError("Invalid function passed.") 119 | 120 | # Construct PopulationParameters object 121 | self.parameters = PopulationParameters(dimensions=dimensions, **kwargs) 122 | 123 | self.objective_function = objective_function 124 | self.elite_population_size = int(self.parameters.elite_fraction * self.parameters.population_size) 125 | self.evaluated_fitness_ranks = False 126 | self.evaluated_diversity_ranks = False 127 | self.mean_fitness = 0 128 | self.mean_diversity = 0 129 | self.mean_coordinates = np.zeros((self.parameters.num_dimensions, 1)) 130 | self.num_iterations = 1 131 | 132 | # Multiprocessing defaults 133 | self.multiprocessing = kwargs.get('multiprocessing', False) 134 | self.processes = kwargs.get('processes') 135 | 136 | # Create points as Point objects 137 | self.points = [] 138 | for pointnumber in range(self.parameters.population_size): 139 | point = Point(associated_population=self, dimensions=self.parameters.num_dimensions) 140 | self.points.append(point) 141 | self.points[pointnumber].index = pointnumber 142 | 143 | # If multiprocessing is enabled, create pool of processes. 144 | if self.multiprocessing: 145 | if self.processes is None: 146 | self.pool = mp.ProcessingPool() 147 | else: 148 | self.pool = mp.ProcessingPool(ncpus=self.processes) 149 | 150 | fitnesses = self.pool.map(lambda coordinates, func: func(*coordinates), [point.coordinates for point in self.points], [self.objective_function] * self.size) 151 | 152 | # Assign fitnesses to each point 153 | for index, point in enumerate(self.points): 154 | point.fitness = fitnesses[index] 155 | else: 156 | for point in self.points: 157 | point.evaluate_fitness(self.objective_function) 158 | 159 | # Evaluate fitness and diversity ranks 160 | self.__evaluate_fitness_ranks() 161 | self.__evaluate_diversity_ranks() 162 | 163 | # Evaluate the fitness rank of each point in the population 164 | def __evaluate_fitness_ranks(self): 165 | if not self.evaluated_fitness_ranks: 166 | self.mean_fitness = np.sum(point.fitness for point in self.points) / self.parameters.population_size 167 | 168 | # Sort according to decreasing fitness and assign fitness ranks 169 | self.points.sort(key=lambda point: point.fitness, reverse=True) 170 | for rank_number in range(self.parameters.population_size): 171 | self.points[rank_number].fitness_rank = rank_number 172 | 173 | self.evaluated_fitness_ranks = True 174 | 175 | # Evaluate the diversity rank of each point in the population 176 | def __evaluate_diversity_ranks(self): 177 | if not self.evaluated_diversity_ranks: 178 | # Find mean coordinates 179 | self.mean_coordinates = np.sum(point.coordinates for point in self.points) / self.parameters.population_size 180 | 181 | # Evaluate individual diversity as L1 deviation from mean coordinates 182 | for point in self.points: 183 | point.diversity = np.sum(np.abs(point.coordinates - self.mean_coordinates)) 184 | 185 | # Population diversity is mean of individual point diversities 186 | self.mean_diversity = np.sum(point.diversity for point in self.points) / self.parameters.population_size 187 | 188 | # Sort according to decreasing diversity and assign diversity ranks 189 | self.points.sort(key=lambda point: point.diversity, reverse=True) 190 | for rank_number in range(self.parameters.population_size): 191 | self.points[rank_number].diversity_rank = rank_number 192 | 193 | self.evaluated_diversity_ranks = True 194 | 195 | # Generate the new population by breeding points 196 | def __breed(self): 197 | # Sort according to fitness rank 198 | self.points.sort(key=lambda point: point.fitness_rank) 199 | 200 | # Push all the really good points first (according to fitness) 201 | newpopulation = [] 202 | for pointnumber in range(self.elite_population_size): 203 | newpopulation.append(self.points[pointnumber]) 204 | 205 | # Assign weights to being selected for breeding 206 | weights = [1 / (1 + point.fitness_rank + point.diversity_rank) for point in self.points] 207 | 208 | # Randomly select for the rest and breed 209 | while len(newpopulation) < self.parameters.population_size: 210 | parent1 = weighted_choice(list(range(self.parameters.population_size)), weights) 211 | parent2 = weighted_choice(list(range(self.parameters.population_size)), weights) 212 | 213 | # Don't breed with yourself, dude! 214 | while parent1 == parent2: 215 | parent1 = weighted_choice(list(range(self.parameters.population_size)), weights) 216 | parent2 = weighted_choice(list(range(self.parameters.population_size)), weights) 217 | 218 | # Breed now 219 | child1, child2 = crossover(self.points[parent1], self.points[parent2]) 220 | 221 | # Add the children 222 | newpopulation.append(child1) 223 | if len(newpopulation) < self.parameters.population_size: 224 | newpopulation.append(child2) 225 | 226 | # Re-assign to the new population 227 | self.points = newpopulation 228 | 229 | # Evaluate fitnesses of new population points 230 | if self.multiprocessing: 231 | # Reuse pool of processes 232 | fitnesses = self.pool.map(lambda coordinates, func: func(*coordinates), [point.coordinates for point in self.points], [self.objective_function] * self.parameters.population_size) 233 | 234 | # Assign fitnesses to each point 235 | for index, point in enumerate(self.points): 236 | point.fitness = fitnesses[index] 237 | else: 238 | for point in self.points: 239 | point.evaluate_fitness(self.objective_function) 240 | 241 | self.evaluated_fitness_ranks = False 242 | self.evaluated_diversity_ranks = False 243 | 244 | # Mutate population randomly 245 | def __mutate(self): 246 | for point in self.points: 247 | mutate_probability = uniform(0, 1) 248 | if mutate_probability < self.parameters.mutation_probability: 249 | point.mutate() 250 | point.evaluate_fitness(self.objective_function) 251 | 252 | self.evaluated_fitness_ranks = False 253 | self.evaluated_diversity_ranks = False 254 | 255 | # Perform one iteration of breeding and mutation 256 | def iterate(self, verbose=0): 257 | # Breed 258 | self.__breed() 259 | 260 | # Mutate 261 | self.__mutate() 262 | 263 | # Find the new population's fitness and diversity ranks. 264 | self.__evaluate_fitness_ranks() 265 | self.__evaluate_diversity_ranks() 266 | 267 | # Print the population stats, if enabled 268 | if verbose == 1: 269 | print("Iteration", self.num_iterations, "complete.") 270 | elif verbose == 2: 271 | print("Iteration", self.num_iterations, "complete, with statistics:") 272 | print("Mean fitness =", self.mean_fitness) 273 | print("Mean L1 diversity =", self.mean_diversity) 274 | print() 275 | 276 | self.num_iterations += 1 277 | 278 | # Perform iterations sequentially 279 | def converge(self, iterations=15, verbose=0): 280 | for iteration in range(1, iterations + 1): 281 | self.iterate(verbose=verbose) 282 | 283 | # The point with best fitness is the estimate of point of maxima 284 | def best_estimate(self): 285 | best_point_fitness = float("-inf") 286 | best_point = None 287 | for point in self.points: 288 | if point.fitness > best_point_fitness: 289 | best_point_fitness = point.fitness 290 | best_point = point 291 | 292 | return best_point 293 | 294 | 295 | # Crossover (breed) 2 points by swapping coordinates 296 | def crossover(point1, point2): 297 | if point1.associated_population != point2.associated_population: 298 | raise ValueError("Points are from different populations.") 299 | 300 | child1 = Point(associated_population=point1.associated_population, dimensions=np.size(point1.coordinates)) 301 | child2 = Point(associated_population=point2.associated_population, dimensions=np.size(point2.coordinates)) 302 | 303 | splitpoint = randint(1, np.size(point1.coordinates)) 304 | 305 | child1.coordinates = np.concatenate([point1.coordinates[:splitpoint], point2.coordinates[splitpoint:]]) 306 | child2.coordinates = np.concatenate([point2.coordinates[:splitpoint], point1.coordinates[splitpoint:]]) 307 | 308 | return child1, child2 309 | 310 | 311 | # Wrapper to build a population and converge to function maxima, returning the best point as a Point object 312 | def maximize(objective_function=None, dimensions=None, iterations=15, verbose=0, **kwargs): 313 | 314 | population = Population(objective_function=objective_function, dimensions=dimensions, **kwargs) 315 | population.converge(iterations=iterations, verbose=verbose) 316 | 317 | return population.best_estimate() 318 | 319 | 320 | # Wrapper to build a population and converge to function minima, returning the best point as a Point object 321 | def minimize(objective_function=None, dimensions=None, iterations=15, verbose=0, **kwargs): 322 | 323 | # Negative of the objective function 324 | def objective_function_neg(*args): 325 | return -objective_function(*args) 326 | 327 | # Minimize the function by maximizing the negative of the function. 328 | best_point = maximize(objective_function=objective_function_neg, dimensions=dimensions, 329 | iterations=iterations, verbose=verbose, **kwargs) 330 | 331 | best_point.evaluate_fitness(objective_function) 332 | 333 | return best_point 334 | 335 | 336 | # Helper to unpack arguments with shapes as given 337 | def unpack(args, shapes): 338 | try: 339 | # Convert passed arguments to a numpy array 340 | np_args = np.array(args) 341 | index = 0 342 | unpacked_args = [] 343 | 344 | # Step through the passed arguments and reshape them one-by-one 345 | for shape in shapes: 346 | currprod = 1 347 | try: 348 | for val in shape: 349 | currprod *= val 350 | except TypeError: 351 | currprod *= shape 352 | finally: 353 | unpacked_args.append(np_args[index: index + currprod].reshape(shape)) 354 | index += currprod 355 | 356 | if len(shapes) > 1: 357 | return unpacked_args 358 | else: 359 | return unpacked_args[0] 360 | 361 | except (TypeError, IndexError): 362 | raise 363 | 364 | -------------------------------------------------------------------------------- /fmga_neuro.py: -------------------------------------------------------------------------------- 1 | ''' 2 | fmga to train a neural network to classify a spiral dataset 3 | Author: Ameya Daigavane 4 | Reference: http://cs231n.github.io/neural-networks-case-study/ 5 | Most of the code below is from the reference above. 6 | ''' 7 | 8 | import fmga 9 | import numpy as np 10 | import matplotlib.pyplot as plt 11 | 12 | if __name__ == '__main__': 13 | 14 | N = 100 # number of points per class 15 | D = 2 # dimensionality 16 | K = 3 # number of classes 17 | X = np.zeros((N*K,D)) # data matrix (each row = single example) 18 | y = np.zeros(N*K, dtype='uint8') # class labels 19 | reg = 1e-5 # regularization lambda 20 | h = 30 # size of hidden layer 21 | num_examples = X.shape[0] 22 | 23 | for j in range(K): 24 | ix = range(N*j,N*(j+1)) 25 | r = np.linspace(0.0,1,N) # radius 26 | t = np.linspace(j*4,(j+1)*4,N) + np.random.randn(N)*0.2 # theta 27 | X[ix] = np.c_[r*np.sin(t), r*np.cos(t)] 28 | y[ix] = j 29 | # lets visualize the data: 30 | plt.scatter(X[:, 0], X[:, 1], c=y, s=40, cmap=plt.cm.Spectral) 31 | # plt.show() 32 | 33 | def cross_entropy_loss(*args): 34 | # unpack as numpy arrays 35 | W, b, W2, b2 = fmga.unpack(args, [(D, h), (1, h), (h, K), (1, K)]) 36 | 37 | # hidden layer with ReLU activation 38 | hidden_layer = np.maximum(0, np.dot(X, W) + b) # note, ReLU activation 39 | 40 | # scores! 41 | scores = np.dot(hidden_layer, W2) + b2 42 | 43 | # get unnormalized probabilities 44 | exp_scores = np.exp(scores) 45 | 46 | # normalize them for each example 47 | probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True) 48 | 49 | # for the correct classes 50 | correct_logprobs = -np.log(probs[range(num_examples), y]) 51 | 52 | data_loss = np.sum(correct_logprobs) / num_examples 53 | reg_loss = 0.5 * reg * np.sum(W * W) + 0.5 * reg * np.sum(W2 * W2) 54 | 55 | return -(data_loss + reg_loss) 56 | 57 | bounds = [(-5, 5) for _ in range(D*h + h + h*K + K)] 58 | best_params = fmga.maximize(cross_entropy_loss, dimensions=(D*h + h + h*K + K), population_size=500, iterations=20, 59 | boundaries=bounds, mutation_range=1, mutation_probability=0.1, elite_fraction=0.15, verbose=2) 60 | 61 | # the loss on the best weights 62 | print(best_params.fitness) 63 | 64 | # unpack! 65 | W, b, W2, b2 = fmga.unpack(best_params.coordinates, [(D, h), (1, h), (h, K), (1, K)]) 66 | 67 | hidden_layer = np.maximum(0, np.dot(X, W) + b) 68 | scores = np.dot(hidden_layer, W2) + b2 69 | predicted_class = np.argmax(scores, axis=1) 70 | print(np.mean(predicted_class == y)) 71 | 72 | # plot the resulting classifier 73 | h = 0.02 74 | x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1 75 | y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1 76 | xx, yy = np.meshgrid(np.arange(x_min, x_max, h), 77 | np.arange(y_min, y_max, h)) 78 | Z = np.dot(np.maximum(0, np.dot(np.c_[xx.ravel(), yy.ravel()], W) + b), W2) + b2 79 | Z = np.argmax(Z, axis=1) 80 | Z = Z.reshape(xx.shape) 81 | fig = plt.figure() 82 | plt.contourf(xx, yy, Z, cmap=plt.cm.Spectral, alpha=0.8) 83 | plt.scatter(X[:, 0], X[:, 1], c=y, s=40, cmap=plt.cm.Spectral) 84 | plt.xlim(xx.min(), xx.max()) 85 | plt.ylim(yy.min(), yy.max()) 86 | plt.show() 87 | # fig.savefig('fmga_spiral_neuro.png') -------------------------------------------------------------------------------- /fmga_plots.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Testing the fmga package 3 | Author: Ameya Daigavane 4 | Date: 15th April, 2018 5 | ''' 6 | 7 | # libraries for the genetic algorithm 8 | from fmga import Population 9 | from math import sin, pi, exp 10 | 11 | # libraries for the plots 12 | from mpl_toolkits.mplot3d import Axes3D 13 | import matplotlib.pyplot as plt 14 | from matplotlib import cm 15 | from matplotlib.ticker import LinearLocator, FormatStrFormatter 16 | import numpy as np 17 | 18 | # objective function to maximise 19 | def f(x, y): 20 | return (10 * sin((5 * pi * x)/(2 * 100)) ** 2) * (10 * sin((5 * pi * y)/(2 * 100)) ** 2) * exp(-(x + y)/100) 21 | 22 | 23 | # surface plot of objective function - comment out if not interested 24 | fig = plt.figure() 25 | axes = fig.gca(projection='3d') 26 | 27 | x_sample = np.arange(101) 28 | y_sample = np.arange(101) 29 | x_sample, y_sample = np.meshgrid(x_sample, y_sample) 30 | z_sample = [[f(x_sample[i][j], y_sample[i][j]) for i in range(101)] for j in range(101)] 31 | 32 | surface = axes.plot_surface(x_sample, y_sample, z_sample, cmap=cm.coolwarm, linewidth=0, antialiased=False) 33 | 34 | axes.set_zlim(-50, 50) 35 | axes.zaxis.set_major_locator(LinearLocator(10)) 36 | axes.zaxis.set_major_formatter(FormatStrFormatter('%.02f')) 37 | 38 | # color bar to map values to colours 39 | fig.colorbar(surface, shrink=0.5, aspect=5) 40 | plt.title("Objective Function Surface Plot") 41 | plt.show() 42 | 43 | 44 | # actual algorithm code starts here # 45 | # initial population 46 | population = Population(population_size=60, objective_function=f) 47 | 48 | # for plotting 49 | plot_fitness = [population.mean_fitness] 50 | plot_diversity = [population.mean_diversity] 51 | population_x_vals = [point.coordinates[0] for point in population.points] 52 | population_y_vals = [point.coordinates[1] for point in population.points] 53 | 54 | plt.scatter(population_x_vals, population_y_vals) 55 | plt.title("Initial Population") 56 | plt.xlim((0, 100)) 57 | plt.ylim((0, 100)) 58 | plt.show() 59 | 60 | # print the initial stats 61 | print("Initial Population") 62 | print("Mean fitness =", population.mean_fitness) 63 | print("Mean L1 diversity =", population.mean_diversity) 64 | print() 65 | 66 | # breed and mutate this num_iterations times 67 | for iteration in range(1, 16): 68 | 69 | # perform one iteration 70 | population.iterate() 71 | 72 | # add to the plot after every iteration 73 | plot_fitness.append(population.mean_fitness) 74 | plot_diversity.append(population.mean_diversity) 75 | 76 | # print the updated stats 77 | print("Iteration", iteration) 78 | print("Mean fitness =", population.mean_fitness) 79 | print("Mean L1 diversity =", population.mean_diversity) 80 | print() 81 | 82 | # point with best fitness is the estimate of point of maxima 83 | best_point = population.best_estimate() 84 | 85 | print("Function Maximum Estimate =", best_point.fitness) 86 | print("Function Maximum Position Estimate =", "(" + str(best_point.coordinates[0]) + ", " + str(best_point.coordinates[1]) + ")") 87 | 88 | # plotting again 89 | # plot final population points 90 | population_x_vals = [point.coordinates[0] for point in population.points] 91 | population_y_vals = [point.coordinates[1] for point in population.points] 92 | 93 | plt.scatter(population_x_vals, population_y_vals, color='r') 94 | plt.title("Final Population") 95 | plt.xlim((0, 100)) 96 | plt.ylim((0, 100)) 97 | plt.show() 98 | 99 | # plot population stats 100 | plt.subplot(2, 1, 1) 101 | plt.title("Mean Population Stats") 102 | 103 | plt.plot(range(16), plot_fitness, 'b--',) 104 | plt.ylabel('Fitness') 105 | 106 | plt.subplot(2, 1, 2) 107 | plt.plot(range(16), plot_diversity, 'r--') 108 | plt.ylabel('L1 diversity') 109 | 110 | plt.xlabel("Iteration number") 111 | plt.show() 112 | -------------------------------------------------------------------------------- /fmga_spiral_linear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameya98/GeneticAlgorithmsRepo/381e9770a0c001baebaaab45f4dba209ab7ca01a/fmga_spiral_linear.png -------------------------------------------------------------------------------- /fmga_spiral_neuro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameya98/GeneticAlgorithmsRepo/381e9770a0c001baebaaab45f4dba209ab7ca01a/fmga_spiral_neuro.png -------------------------------------------------------------------------------- /fmga_test.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Testing the fmga package 3 | Author: Ameya Daigavane 4 | ''' 5 | 6 | from fmga import maximize 7 | import math 8 | 9 | 10 | if __name__ == '__main__': 11 | 12 | def f(x, y, z): 13 | return -z * math.sin(x + y) + z * math.sin(x - y) 14 | 15 | bounds = [(-math.pi, math.pi), (-math.pi, math.pi), (-10, 10)] 16 | best_point = maximize(f, dimensions=3, mutation_probability=0.1, population_size=100, multiprocessing=True, iterations=10, boundaries=bounds) 17 | print(best_point.coordinates, best_point.fitness) 18 | 19 | -------------------------------------------------------------------------------- /functionplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameya98/GeneticAlgorithmsRepo/381e9770a0c001baebaaab45f4dba209ab7ca01a/functionplot.png -------------------------------------------------------------------------------- /meanstats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameya98/GeneticAlgorithmsRepo/381e9770a0c001baebaaab45f4dba209ab7ca01a/meanstats.png -------------------------------------------------------------------------------- /vcga/vcga/vertex_cover.py: -------------------------------------------------------------------------------- 1 | # Genetic Algorithms - Vertex Cover # 2 | # Author: Ameya Daigavane # 3 | # Date: 26th April, 2018 # 4 | # An implementation of the genetic algorithm given in 'Evolutionary Algorithms for Vertex Cover' by Isaac K. Evans # 5 | 6 | import networkx as nx 7 | import numpy as np 8 | from random import randint, uniform, choice, shuffle 9 | import matplotlib.pyplot as plt 10 | from networkx.algorithms import approximation 11 | 12 | # global configuration settings 13 | # note: keep population_size and elite_population_size of same parity 14 | population_size = 30 15 | elite_population_size = 8 16 | mutation_probability = 0.04 17 | num_iterations = 5 18 | 19 | # create graph G - a networkx undirected graph 20 | # G = nx.gnp_random_graph(num_vertices, 0.2) 21 | # G = nx.karate_club_graph() 22 | G = nx.gnm_random_graph(1000, 500) 23 | print(len(G.nodes), len(G.edges)) 24 | 25 | 26 | # a weighted choice function 27 | def weighted_choice(choices, weights): 28 | normalized_weights = np.array([weight for weight in weights]) / np.sum(weights) 29 | threshold = uniform(0, 1) 30 | total = 1 31 | for index, normalized_weight in enumerate(normalized_weights): 32 | total -= normalized_weight 33 | if total < threshold: 34 | return choices[index] 35 | 36 | 37 | # Vertex Cover class definition 38 | class VertexCover: 39 | def __init__(self, associated_population=None): 40 | # population to which this point belongs 41 | self.associated_population = associated_population 42 | 43 | # randomly create chromosome 44 | self.chromosomes = [randint(0, 1) for _ in range(len(self.associated_population.graph.nodes()))] 45 | 46 | # initialize 47 | self.vertexarray = np.array([False for _ in range(len(self.associated_population.graph.nodes()))]) 48 | self.chromosomenumber = 0 49 | self.vertexlist = np.array([]) 50 | 51 | # required when considering the entire population 52 | self.index = -1 53 | self.fitness = 0.0 54 | self.diversity = 0.0 55 | self.fitness_rank = -1 56 | self.diversity_rank = -1 57 | self.evaluated_fitness = False 58 | 59 | def evaluate_fitness(self): 60 | if not self.evaluated_fitness: 61 | original_graph = self.associated_population.graph.copy() 62 | 63 | self.vertexarray = np.array([False for _ in range(len(original_graph.nodes()))]) 64 | self.chromosomenumber = 0 65 | 66 | while len(original_graph.edges) > 0: 67 | # shuffle the list of vertices 68 | node_list = list(original_graph.nodes) 69 | shuffle(node_list) 70 | 71 | # remove all degree-1 vertices one-by-one 72 | degree_one_vertex_found = False 73 | for vertex in node_list: 74 | if original_graph.degree[vertex] == 1: 75 | degree_one_vertex_found = True 76 | 77 | neighbors = list(original_graph.neighbors(vertex)) 78 | adjvertex = neighbors[0] 79 | 80 | # select the adjacent vertex 81 | self.vertexarray[adjvertex] = True 82 | 83 | # remove vertex along with neighbours from graph 84 | removed_subgraph = neighbors 85 | removed_subgraph.append(vertex) 86 | original_graph.remove_nodes_from(removed_subgraph) 87 | 88 | break 89 | 90 | # no more degree-1 vertices left 91 | if not degree_one_vertex_found: 92 | # randomly choose one of the remaining vertices 93 | vertex = choice(list(original_graph.nodes)) 94 | if original_graph.degree[vertex] >= 2: 95 | # make a choice depending on the chromosome bit 96 | if self.chromosomes[self.chromosomenumber] == 0: 97 | # add all neighbours to vertex cover 98 | for othervertex in original_graph.neighbors(vertex): 99 | self.vertexarray[othervertex] = True 100 | 101 | # remove vertex along with neighbours from graph 102 | removed_subgraph = list(original_graph.neighbors(vertex)) 103 | removed_subgraph.append(vertex) 104 | original_graph.remove_nodes_from(removed_subgraph) 105 | 106 | elif self.chromosomes[self.chromosomenumber] == 1: 107 | # add only this vertex to the vertex cover 108 | self.vertexarray[vertex] = True 109 | 110 | # remove only this vertex from the graph 111 | original_graph.remove_node(vertex) 112 | 113 | # go to the next chromosome to be checked 114 | self.chromosomenumber = self.chromosomenumber + 1 115 | continue 116 | 117 | # put all true elements in a numpy array - representing the actual vertex cover 118 | self.vertexlist = np.where(self.vertexarray == True)[0] 119 | self.fitness = len(self.associated_population.graph.nodes()) / (1 + len(self.vertexlist)) 120 | self.evaluated_fitness = True 121 | 122 | return self.fitness 123 | 124 | # mutate the chromosome at a random index 125 | def mutate(self): 126 | if self.chromosomenumber > 0: 127 | index = randint(0, self.chromosomenumber) 128 | else: 129 | index = 0 130 | 131 | if self.chromosomes[index] == 0: 132 | self.chromosomes[index] = 1 133 | elif self.chromosomes[index] == 1: 134 | self.chromosomes[index] = 0 135 | 136 | self.evaluated_fitness = False 137 | self.evaluate_fitness() 138 | 139 | def __len__(self): 140 | return len(self.vertexlist) 141 | 142 | def __iter__(self): 143 | return iter(self.vertexlist) 144 | 145 | 146 | # class for the 'population' of vertex covers 147 | class Population: 148 | def __init__(self, G, size): 149 | self.vertexcovers = [] 150 | self.size = size 151 | self.graph = G.copy() 152 | 153 | for vertex_cover_number in range(self.size): 154 | vertex_cover = VertexCover(self) 155 | vertex_cover.evaluate_fitness() 156 | 157 | self.vertexcovers.append(vertex_cover) 158 | self.vertexcovers[vertex_cover_number].index = vertex_cover_number 159 | 160 | self.evaluated_fitness_ranks = False 161 | self.evaluated_diversity_ranks = False 162 | self.mean_fitness = 0 163 | self.mean_diversity = 0 164 | self.mean_vertex_cover_size = 0 165 | self.average_vertices = np.zeros((len(self.graph.nodes()), 1)) 166 | 167 | # evaluate fitness ranks for each vertex cover 168 | def evaluate_fitness_ranks(self): 169 | if not self.evaluated_fitness_ranks: 170 | for vertex_cover in self.vertexcovers: 171 | vertex_cover.fitness = vertex_cover.evaluate_fitness() 172 | self.mean_fitness += vertex_cover.fitness 173 | self.mean_vertex_cover_size += len(vertex_cover) 174 | 175 | self.mean_fitness /= self.size 176 | self.mean_vertex_cover_size /= self.size 177 | self.vertexcovers.sort(key=lambda vertex_cover: vertex_cover.fitness, reverse=True) 178 | 179 | for rank_number in range(self.size): 180 | self.vertexcovers[rank_number].fitness_rank = rank_number 181 | 182 | self.evaluated_fitness_ranks = True 183 | 184 | # evaluate diversity rank of each point in population 185 | def evaluate_diversity_ranks(self): 186 | if not self.evaluated_diversity_ranks: 187 | # find the average occurrence of every vertex in the population 188 | for vertex_cover in self.vertexcovers: 189 | self.average_vertices[vertex_cover.vertexlist] += 1 190 | 191 | self.average_vertices /= self.size 192 | 193 | for vertex_cover in self.vertexcovers: 194 | vertex_cover.diversity = np.sum(np.abs(vertex_cover.vertexlist - self.average_vertices))/self.size 195 | self.mean_diversity += vertex_cover.diversity 196 | 197 | self.mean_diversity /= self.size 198 | self.vertexcovers.sort(key=lambda vertex_cover: vertex_cover.diversity, reverse=True) 199 | 200 | for rank_number in range(self.size): 201 | self.vertexcovers[rank_number].diversity_rank = rank_number 202 | 203 | self.evaluated_diversity_ranks = True 204 | 205 | # generate the new population by breeding vertex covers 206 | def breed(self): 207 | # sort according to fitness_rank 208 | self.vertexcovers.sort(key=lambda vertex_cover: vertex_cover.fitness_rank) 209 | 210 | # push all the really good ('elite') vertex covers first 211 | newpopulation = [] 212 | for index in range(elite_population_size): 213 | newpopulation.append(self.vertexcovers[index]) 214 | 215 | # assign weights to being selected for breeding 216 | weights = [1 / (1 + vertex_cover.fitness_rank + vertex_cover.diversity_rank) for vertex_cover in self.vertexcovers] 217 | 218 | # randomly select for the rest and breed 219 | while len(newpopulation) < population_size: 220 | parent1 = weighted_choice(list(range(population_size)), weights) 221 | parent2 = weighted_choice(list(range(population_size)), weights) 222 | 223 | # don't breed with yourself, dude! 224 | while parent1 == parent2: 225 | parent1 = weighted_choice(list(range(population_size)), weights) 226 | parent2 = weighted_choice(list(range(population_size)), weights) 227 | 228 | # breed now 229 | child1, child2 = crossover(self.vertexcovers[parent1], self.vertexcovers[parent2]) 230 | 231 | # add the children 232 | newpopulation.append(child1) 233 | newpopulation.append(child2) 234 | 235 | # assign the new population 236 | self.vertexcovers = newpopulation 237 | 238 | self.evaluated_fitness_ranks = False 239 | self.evaluated_diversity_ranks = False 240 | 241 | # mutate population randomly 242 | def mutate(self): 243 | for vertex_cover in self.vertexcovers: 244 | test_probability = uniform(0, 1) 245 | if test_probability < mutation_probability: 246 | vertex_cover.mutate() 247 | vertex_cover.evaluate_fitness() 248 | 249 | self.evaluated_fitness_ranks = False 250 | self.evaluated_diversity_ranks = False 251 | 252 | 253 | # crossover between two vertex cover chromosomes 254 | def crossover(parent1, parent2): 255 | if parent1.associated_population != parent2.associated_population: 256 | raise ValueError("Vertex covers belong to different populations.") 257 | 258 | child1 = VertexCover(parent1.associated_population) 259 | child2 = VertexCover(parent2.associated_population) 260 | 261 | # find the point to split and rejoin the chromosomes 262 | # note that chromosome number + 1 is the actual length of the chromosome in each vertex cover encoding 263 | split_point = randint(0, min(parent1.chromosomenumber, parent2.chromosomenumber)) 264 | 265 | 266 | # actual splitting and recombining 267 | child1.chromosomes = parent1.chromosomes[:split_point] + parent2.chromosomes[split_point:] 268 | child2.chromosomes = parent2.chromosomes[:split_point] + parent1.chromosomes[split_point:] 269 | 270 | # evaluate fitnesses 271 | child1.evaluate_fitness() 272 | child2.evaluate_fitness() 273 | 274 | return child1, child2 275 | 276 | 277 | # just to check feasibility of vertex covers 278 | def is_valid_vertex_cover(vertex_cover): 279 | valid_vertex_cover = True 280 | for edge in G.edges: 281 | if (edge[0] not in vertex_cover) and (edge[1] not in vertex_cover): 282 | print("Invalid!", edge[0], "-", edge[1]) 283 | valid_vertex_cover = False 284 | break 285 | 286 | if valid_vertex_cover: 287 | print("Valid!") 288 | 289 | 290 | # main logic begins here # 291 | # initialise vertex cover population 292 | population = Population(G, population_size) 293 | population.evaluate_fitness_ranks() 294 | population.evaluate_diversity_ranks() 295 | 296 | # for plotting 297 | plot_fitness = [population.mean_fitness] 298 | plot_diversity = [population.mean_diversity] 299 | 300 | # print the chromosome numbers 301 | # for vertex_cover in population.vertexcovers: 302 | # print(vertex_cover.chromosomenumber) 303 | 304 | # print the initial stats 305 | print("Initial Population") 306 | print("Mean fitness =", population.mean_fitness) 307 | print("Mean L1 diversity =", population.mean_diversity) 308 | print("Mean VC size =", population.mean_vertex_cover_size) 309 | print() 310 | 311 | # breed and mutate this population num_iterations times 312 | for iteration in range(1, num_iterations + 1): 313 | 314 | # DEBUG - check if all valid vertex covers 315 | # for vertex_cover in population.vertexcovers: 316 | # is_valid_vertex_cover(vertex_cover) 317 | 318 | population.breed() 319 | population.mutate() 320 | 321 | # find the new ranks 322 | population.evaluate_fitness_ranks() 323 | population.evaluate_diversity_ranks() 324 | 325 | # add to the plot 326 | plot_fitness.append(population.mean_fitness) 327 | plot_diversity.append(population.mean_diversity) 328 | 329 | # print the updated stats 330 | print("Iteration", iteration) 331 | print("Mean fitness =", population.mean_fitness) 332 | print("Mean L1 diversity =", population.mean_diversity) 333 | print("Mean VC size =", population.mean_vertex_cover_size) 334 | print() 335 | 336 | # vertex cover with best fitness is our output 337 | best_vertex_cover = None 338 | best_fitness = 0 339 | for vertex_cover in population.vertexcovers: 340 | if vertex_cover.fitness > best_fitness: 341 | best_vertex_cover = vertex_cover 342 | 343 | print("Best Vertex Cover Size =", len(best_vertex_cover)) 344 | print("Best Vertex Cover = ", best_vertex_cover.vertexlist) 345 | 346 | print("networkx approximation =", len(approximation.min_weighted_vertex_cover(G))) 347 | 348 | # just to check 349 | # is_valid_vertex_cover(best_vertex_cover) 350 | 351 | # plotting again 352 | # plot population stats 353 | plt.subplot(2, 1, 1) 354 | plt.title("Mean Population Stats") 355 | 356 | plt.plot(range(num_iterations + 1), plot_fitness, 'b--',) 357 | plt.ylabel('Fitness') 358 | 359 | plt.subplot(2, 1, 2) 360 | plt.plot(range(num_iterations + 1), plot_diversity, 'r--') 361 | plt.ylabel('L1 diversity') 362 | 363 | plt.xlabel("Iteration number") 364 | plt.show() 365 | --------------------------------------------------------------------------------