├── .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 [](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 |  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 |  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 |
--------------------------------------------------------------------------------