GA Optimization ------------ In this demo, we will be optimizing the traps' positions to minimize the time it takes for a mosquito to get caught. This is done with the `DEAP package `_, as it allows much flexibility and implementation speedups. The Workflow ~~~~~~~~~~~~~~~~~~~~~~ The way `MGSurvE `_ and `DEAP `_ communicate to each other is through the traps' positions and the fitness function. Our landscape object contains the information we need to calculate the migration and trapping metrics on our environment, and our optimizer should be able to modify the traps' locations to test which positions are the best ones given a cost function. For this to happen, we will create a copy of our landscape object (as it will be modified in place), which will be constantly updated through the traps' positions by the `DEAP framework `_: .. image:: ../../img/MGSurvEDiagSingleSex.jpg Landscape and Traps ~~~~~~~~~~~~~~~~~~~~~~ We are going to use a "donut" landscape as a testbed, so we define our pointset as: .. code-block:: python ptsNum = 100 radii = (75, 100) xy = srv.ptsDonut(ptsNum, radii).T points = pd.DataFrame({'x': xy[0], 'y': xy[1], 't': [0]*xy.shape[1]}) mKer = {'params': [.075, 1.0e-10, math.inf], 'zeroInflation': .75} And, as we are going to optimize our traps locations, we can define them all at coordinates :code:`(0,0)`, and for this example we are assuming all the traps are the same type (:code:`t=0`) and that they are all movable (:code:`f=0`): .. code-block:: python nullTraps = [0, 0, 0, 0] traps = pd.DataFrame({ 'x': nullTraps, 'y': nullTraps, 't': nullTraps, 'f': nullTraps }) tKer = {0: {'kernel': srv.exponentialDecay, 'params': {'A': .5, 'b': .1}}} With our landscape object being setup as: .. code-block:: python lnd = srv.Landscape( points, kernelParams=mKer, traps=traps, trapsKernels=tKer ) bbox = lnd.getBoundingBox() For now, our landscape looks like this: .. image:: ../../img/demo_GA1.jpg Genetic Algorithm ~~~~~~~~~~~~~~~~~~~~~~ To get started with setting up the GA, we define the population size, generations (:code:`GENS`), mating (:code:`MAT`), mutation (:code:`MUT`) and selection (:code:`SEL`) parameters: .. code-block:: python (GENS, VERBOSE) = (200, True) POP_SIZE = int(10*(lnd.trapsNumber*1.25)) MAT = {'mate': .3, 'cxpb': 0.5} MUT = {'mean': 0, 'sd': min([i[1]-i[0] for i in bbox])/5, 'mutpb': .5, 'ipb': .5} SEL = {'tSize': 3} Next, as defined by the `DEAP docs `_, we register all the functions and operations that we are going to use in our optimization cycle. For this version, we'll be using a pretty "vanilla" GA with cxBlend, gaussian mutation, and tournament selection. .. code-block:: python toolbox = base.Toolbox() creator.create("FitnessMin", base.Fitness, weights=(-1.0, )) # Population creation ----------------------------------------------------- creator.create( "Individual", list, fitness=creator.FitnessMin ) toolbox.register( "initChromosome", srv.initChromosome, trapsCoords=lndGA.trapsCoords, fixedTrapsMask=trpMsk, coordsRange=bbox ) toolbox.register( "individualCreator", tools.initIterate, creator.Individual, toolbox.initChromosome ) toolbox.register( "populationCreator", tools.initRepeat, list, toolbox.individualCreator ) # Mutation and Crossover -------------------------------------------------- toolbox.register( "mate", tools.cxBlend, alpha=MAT['mate'] ) toolbox.register( "mutate", tools.mutGaussian, mu=MUT['mean'], sigma=MUT['sd'], indpb=MUT['ipb'] ) # Select and evaluate ----------------------------------------------------- toolbox.register( "select", tools.selTournament, tournsize=SEL['tSize'] ) toolbox.register( "evaluate", srv.calcFitness, landscape=lndGA, optimFunction=srv.getDaysTillTrapped, optimFunctionArgs={'outer': np.mean, 'inner': np.max} ) It is important to note that we provide custom implementations for the :code:`initChromosome`, :code:`cxBlend`, and :code:`mutateChromosome`; to allow immovable traps to be laid in the landscape, but we will stick to `DEAP's' `_ implementations for this first exercise. We now register summary statistics for our algorithm: .. code-block:: python pop = toolbox.populationCreator(n=POP_SIZE) hof = tools.HallOfFame(1) stats = tools.Statistics(lambda ind: ind.fitness.values) stats.register("min", np.min) stats.register("avg", np.mean) stats.register("max", np.max) stats.register("traps", lambda fitnessValues: pop[fitnessValues.index(min(fitnessValues))]) stats.register("best", lambda fitnessValues: fitnessValues.index(min(fitnessValues))) Where the statistics go as follow (more stats can be added as needed): * :code:`min`: Traps' population minimum fitness (best in generation). * :code:`avg`: Traps' population average fitness. * :code:`max`: Traps' population maximum fitness (worst in generation). * :code:`traps`: Best traps positions in the current generation. * :code:`best`: Best fitness within populations. * :code:`hof`: Best chromosome across generations. Now, we run our optimization cycle: .. code-block:: python (pop, logbook) = algorithms.eaSimple( pop, toolbox, cxpb=MAT['cxpb'], mutpb=MUT['mutpb'], ngen=GENS, stats=stats, halloffame=hof, verbose=VERBOSE ) This will take some time depending on the number of generations and the size of the landscape/traps (check out our `benchmarks <./benchmarks.html>`_ for more info) but once it's done running, we can get our resulting optimized positions. Summary and Plotting ~~~~~~~~~~~~~~~~~~~~~~ Having the results of the GA in our hands, we can get our best chromosome (stored in the :code:`hof` object) and re-shape it so that it is structured as traps locations: .. code-block:: python bestChromosome = hof[0] bestPositions = np.reshape(bestChromosome, (-1, 2)) With these traps locations, we can update our landscape and get the stats for the GA logbook object in a dataframe form: .. code-block:: python lnd.updateTrapsCoords(bestTraps) dta = pd.DataFrame(logbook) We can now plot our landscape with optimized traps positions: .. code-block:: python (fig, ax) = plt.subplots(1, 1, figsize=(15, 15), sharey=False) lnd.plotSites(fig, ax, size=100) lnd.plotMigrationNetwork(fig, ax, alphaMin=.6, lineWidth=25) lnd.plotTraps(fig, ax) srv.plotClean(fig, ax, frame=False, bbox=bbox) srv.plotFitness(fig, ax, min(dta['min'])) fig.savefig( path.join(OUT_PTH, '{}_TRP.png'.format(ID)), facecolor='w', bbox_inches='tight', pad_inches=0, dpi=300 ) .. image:: ../../img/demo_GA.jpg With the generations (x axis) versus fitness (y axis) plot: .. code-block:: python (fig, ax) = plt.subplots(figsize=(15, 15)) (fig, ax) = srv.plotGAEvolution(fig, ax, dta) pthSave = path.join( OUT_PTH, '{}_GAP'.format(ID) ) .. image:: ../../img/demo_GAT.jpg The code used for this tutorial can be found `in this link `_, with the simplified version available `here `_.