├── Ejercicios
├── Optimizacion de rotor
│ ├── optrot
│ │ ├── __init__.py
│ │ └── rotor.py
│ └── Optimizacion con algoritmo genetico.ipynb
├── Hormiguero
│ ├── ants
│ │ ├── __init__.py
│ │ └── _ants.py
│ └── Hormiguero.ipynb
├── Laberinto
│ └── laberinto
│ │ ├── __init__.py
│ │ ├── algen.py
│ │ └── laberinto.py
├── El vecindario racista
│ ├── vecindario
│ │ ├── __init__.py
│ │ └── _vecindario.py
│ └── El vecindario racista.ipynb
├── Equilibrios de Nash
│ └── nash.py
└── El problema de los matrimonios estables
│ └── matrimonios_estables.ipynb
├── imagenes
├── Knowmore.jpg
├── hormigas.png
├── john_holland.jpg
├── imm_cross_tour.gif
├── turingpattern.jpg
├── aeropython_logo.png
├── genome_animation.gif
└── points_animation.gif
├── README.md
├── .gitignore
├── LICENSE
├── Extras.ipynb
├── codigo
└── algen.py
└── Teoria II - Sistemas complejos.ipynb
/Ejercicios/Optimizacion de rotor/optrot/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Ejercicios/Hormiguero/ants/__init__.py:
--------------------------------------------------------------------------------
1 | from._ants import *
2 |
--------------------------------------------------------------------------------
/Ejercicios/Laberinto/laberinto/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | #from._laberinto import *
3 |
--------------------------------------------------------------------------------
/Ejercicios/El vecindario racista/vecindario/__init__.py:
--------------------------------------------------------------------------------
1 | from._vecindario import *
2 |
--------------------------------------------------------------------------------
/imagenes/Knowmore.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AeroPython/Taller-PyConEs-2015/master/imagenes/Knowmore.jpg
--------------------------------------------------------------------------------
/imagenes/hormigas.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AeroPython/Taller-PyConEs-2015/master/imagenes/hormigas.png
--------------------------------------------------------------------------------
/imagenes/john_holland.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AeroPython/Taller-PyConEs-2015/master/imagenes/john_holland.jpg
--------------------------------------------------------------------------------
/imagenes/imm_cross_tour.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AeroPython/Taller-PyConEs-2015/master/imagenes/imm_cross_tour.gif
--------------------------------------------------------------------------------
/imagenes/turingpattern.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AeroPython/Taller-PyConEs-2015/master/imagenes/turingpattern.jpg
--------------------------------------------------------------------------------
/imagenes/aeropython_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AeroPython/Taller-PyConEs-2015/master/imagenes/aeropython_logo.png
--------------------------------------------------------------------------------
/imagenes/genome_animation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AeroPython/Taller-PyConEs-2015/master/imagenes/genome_animation.gif
--------------------------------------------------------------------------------
/imagenes/points_animation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AeroPython/Taller-PyConEs-2015/master/imagenes/points_animation.gif
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Taller-PyConEs-2015
2 | Taller de la PyConEs 2015: Simplifica tu vida con sistemas complejos y algoritmos genéticos
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 |
5 | # C extensions
6 | *.so
7 |
8 | # Distribution / packaging
9 | .Python
10 | env/
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | *.egg-info/
23 | .installed.cfg
24 | *.egg
25 | .ipynb_checkpoints/
26 |
27 | # PyInstaller
28 | # Usually these files are written by a python script from a template
29 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
30 | *.manifest
31 | *.spec
32 |
33 | # Installer logs
34 | pip-log.txt
35 | pip-delete-this-directory.txt
36 |
37 | # Unit test / coverage reports
38 | htmlcov/
39 | .tox/
40 | .coverage
41 | .coverage.*
42 | .cache
43 | nosetests.xml
44 | coverage.xml
45 | *,cover
46 |
47 | # Translations
48 | *.mo
49 | *.pot
50 |
51 | # Django stuff:
52 | *.log
53 |
54 | # Sphinx documentation
55 | docs/_build/
56 |
57 | # PyBuilder
58 | target/
59 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 AeroPython
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 |
23 |
--------------------------------------------------------------------------------
/Extras.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | ""
8 | ]
9 | },
10 | {
11 | "cell_type": "markdown",
12 | "metadata": {},
13 | "source": [
14 | "Esperamos que te haya gustado el taller!\n",
15 | "\n",
16 | "Si te interesa, puedes mirar qué mas cosas tenemos en la página de github de Aeropython https://github.com/AeroPython, o en nuestras páginas personales de Github: https://github.com/AunSiro https://github.com/cdorado\n",
17 | "\n",
18 | "Puedes contactarnos por Twitter y linkedin:\n",
19 | "\n",
20 | "https://twitter.com/AeroPython https://twitter.com/bocatadelechuga https://twitter.com/Aun_Siro\n",
21 | "\n",
22 | "https://es.linkedin.com/in/siro-moreno-martín-1bab18b7 https://es.linkedin.com/in/carlosdoradocardenas"
23 | ]
24 | },
25 | {
26 | "cell_type": "markdown",
27 | "metadata": {},
28 | "source": [
29 | "#¿Desea saber más?"
30 | ]
31 | },
32 | {
33 | "cell_type": "markdown",
34 | "metadata": {},
35 | "source": [
36 | ""
37 | ]
38 | },
39 | {
40 | "cell_type": "markdown",
41 | "metadata": {},
42 | "source": [
43 | "Existe un paquete muy completo para optimización con algoritmos genéticos en Python: DEAP\n",
44 | "\n",
45 | "https://github.com/DEAP/deap\n",
46 | "\n",
47 | "Es demasiado completo para la introducción que pretendíamos abordar en este taller, pero si te ha gustado, ¡Te recomendamos que le eches un ojo!"
48 | ]
49 | },
50 | {
51 | "cell_type": "markdown",
52 | "metadata": {},
53 | "source": [
54 | "Gran parte de los ejercicios están inspirados en los materiales de Evolife por el profesor **Jean-Louis Dessalles** de Paris Telecom. Puedes consultar este proyecto aquí: http://evolife.dessalles.fr/"
55 | ]
56 | },
57 | {
58 | "cell_type": "markdown",
59 | "metadata": {
60 | "collapsed": true
61 | },
62 | "source": [
63 | "En internet hay gran cantidad de proyectos e información sobre estos temas. Aquí tienes un ejemplo sobre una simulación de colonia de hormigas http://www.nightlab.ch/antsim.php , y un par de vídeos sobre algoritmos genéticos:\n",
64 | "\n",
65 | "https://www.youtube.com/watch?v=Gl3EjiVlz_4\n",
66 | "\n",
67 | "https://www.youtube.com/watch?v=pgaEE27nsQw\n",
68 | "\n",
69 | "https://www.youtube.com/watch?v=fyVr7gdGEPE\n",
70 | "\n",
71 | "https://www.youtube.com/watch?v=HgWQ-gPIvt4"
72 | ]
73 | },
74 | {
75 | "cell_type": "code",
76 | "execution_count": null,
77 | "metadata": {
78 | "collapsed": true
79 | },
80 | "outputs": [],
81 | "source": []
82 | }
83 | ],
84 | "metadata": {
85 | "kernelspec": {
86 | "display_name": "Python 3",
87 | "language": "python",
88 | "name": "python3"
89 | },
90 | "language_info": {
91 | "codemirror_mode": {
92 | "name": "ipython",
93 | "version": 3
94 | },
95 | "file_extension": ".py",
96 | "mimetype": "text/x-python",
97 | "name": "python",
98 | "nbconvert_exporter": "python",
99 | "pygments_lexer": "ipython3",
100 | "version": "3.4.3"
101 | }
102 | },
103 | "nbformat": 4,
104 | "nbformat_minor": 0
105 | }
106 |
--------------------------------------------------------------------------------
/Ejercicios/Equilibrios de Nash/nash.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | from numpy.lib.stride_tricks import as_strided
3 | import random as random
4 | import matplotlib.pyplot as plt
5 | from matplotlib.patches import Circle
6 |
7 | def create_world (lines = 10, columns = 0, proportion = 0.5):
8 | """
9 | This function creates a random world consisting of a matrix of individuals.
10 | There are two types of individuals, in a certain proportion
11 | """
12 |
13 | if columns == 0:
14 | columns = lines
15 |
16 | len_world = lines * columns
17 | world = round(proportion * len_world) * [1]
18 | world += (len_world - len(world)) * [0]
19 | random.shuffle(world)
20 |
21 | return np.array(world).reshape(lines, columns)
22 |
23 |
24 | def plot_world (world):
25 | '''
26 | This function takes a numpy 2D array with string elements
27 | and scatters it with the colours corresponding to these strings
28 | '''
29 |
30 | #This sets the colours for the plot
31 | cmap = plt.cm.jet
32 | world = world*160 + 90
33 |
34 |
35 | fig = plt.figure(figsize = (10,10))
36 | ax = fig.add_subplot(111, aspect='equal')
37 |
38 | for ii in range (world.shape[0]):
39 |
40 | x = range(world.shape[1])
41 | y = world.shape[1] * [ii]
42 | colours = world [ii]
43 |
44 | for (x, y, c) in zip(x, y, colours):
45 | ax.add_artist(Circle(xy=(x, y), radius=0.45, color=cmap(c)))
46 |
47 | plt.axis('off')
48 | ax.set_xlim(-1, world.shape[1])
49 | ax.set_ylim(-1, world.shape[0])
50 |
51 |
52 |
53 | def sliding_window(arr, window_size):
54 | """
55 | #by pv on stack overflow
56 | Construct a sliding window view of the array
57 | """
58 | arr = np.asarray(arr)
59 | window_size = int(window_size)
60 | if arr.ndim != 2:
61 | raise ValueError("need 2-D input")
62 | if not (window_size > 0):
63 | raise ValueError("need a positive window size")
64 | shape = (arr.shape[0] - window_size + 1,
65 | arr.shape[1] - window_size + 1,
66 | window_size, window_size)
67 | if shape[0] <= 0:
68 | shape = (1, shape[1], arr.shape[0], shape[3])
69 | if shape[1] <= 0:
70 | shape = (shape[0], 1, shape[2], arr.shape[1])
71 | strides = (arr.shape[1]*arr.itemsize, arr.itemsize,
72 | arr.shape[1]*arr.itemsize, arr.itemsize)
73 | return as_strided(arr, shape=shape, strides=strides)
74 |
75 | def neighbourhood(arr, i, j, d):
76 | """
77 | #by pv on stack overflow
78 | Return d-th neighbors of cell (i, j)
79 | """
80 |
81 | w = sliding_window(arr, 2*d+1)
82 |
83 | ix = np.clip(i - d, 0, w.shape[0]-1)
84 | jx = np.clip(j - d, 0, w.shape[1]-1)
85 |
86 | i0 = max(0, i - d - ix)
87 | j0 = max(0, j - d - jx)
88 | i1 = w.shape[2] - max(0, d - i + ix)
89 | j1 = w.shape[3] - max(0, d - j + jx)
90 |
91 | return w[ix, jx][i0:i1,j0:j1].ravel()
92 |
93 | def weighted_choice_sub(weights):
94 | """
95 | weights = [0.9, 0.05, 0.05]
96 | N = 100000
97 | lista = [weighted_choice_sub(weights) for ii in range(N)]
98 | print( lista.count(0)/N, lista.count(1)/N, lista.count(2)/N)
99 | """
100 | rnd = random.random() * sum(weights)
101 | for i, w in enumerate(weights):
102 | rnd -= w
103 | if rnd < 0:
104 | return i
--------------------------------------------------------------------------------
/codigo/algen.py:
--------------------------------------------------------------------------------
1 | import random as random
2 |
3 |
4 |
5 | #This function was taken from Eli Bendersky's website
6 | #It returns an index of a list called "weights",
7 | #where the content of each element in "weights" is the probability of this index to be returned.
8 | #For this function to be as fast as possible we need to pass it a list of weights in descending order.
9 | def weighted_choice_sub(weights):
10 | rnd = random.random() * sum(weights)
11 | for i, w in enumerate(weights):
12 | rnd -= w
13 | if rnd < 0:
14 | return i
15 |
16 |
17 |
18 |
19 | generate_random_binary_list = lambda n: [random.randint(0,1) for b in range(1,n+1)]
20 |
21 |
22 |
23 |
24 | class Individual (object):
25 |
26 | def __init__(self, genome):
27 |
28 | self.genome = genome
29 | self.traits = {}
30 | self.performances = {}
31 | self.fitness = 0
32 |
33 |
34 |
35 | def generate_genome (dict_genes):
36 |
37 | #We first calculate the total number of bits that the genome must contain
38 | number_of_bits = sum([dict_genes[trait] for trait in dict_genes])
39 |
40 | #And we return a random genome of this length
41 | return generate_random_binary_list(number_of_bits)
42 |
43 |
44 |
45 | def calculate_traits (individual, dict_genes):
46 | #This function must decipher the genome and return the traits of the individual.
47 | #Normally, the genome contains binary numerical values for the different traits.
48 |
49 | dict_traits = {}
50 | index = 0
51 |
52 | for trait in dict_genes:
53 | dict_traits[trait] = int(''.join(str(bit) for bit in individual.genome[index : index+dict_genes[trait]]), 2)
54 | index += dict_genes[trait]
55 |
56 | individual.traits = dict_traits
57 |
58 |
59 |
60 |
61 | def immigration (society, target_population,
62 | calculate_performances, calculate_fitness,
63 | dict_genes, Object = Individual, *args):
64 |
65 | while len(society) < target_population:
66 |
67 | new_individual = Object (generate_genome (dict_genes), args)
68 | calculate_traits (new_individual, dict_genes)
69 | calculate_performances (new_individual)
70 | calculate_fitness (new_individual)
71 |
72 | society.append (new_individual)
73 |
74 |
75 |
76 |
77 |
78 | def crossover (society, reproduction_rate, mutation_rate,
79 | calculate_performances, calculate_fitness,
80 | dict_genes, Object = Individual, *args):
81 |
82 | #First we create a list with the fitness values of every individual in the society
83 | fitness_list = [individual.fitness for individual in society]
84 |
85 | #We sort the individuals in the society in descending order of fitness.
86 | society_sorted = [x for (y, x) in sorted(zip(fitness_list, society), key=lambda x: x[0], reverse=True)]
87 |
88 | #We then create a list of relative probabilities in descending order,
89 | #so that the fittest individual in the society has N times more chances to reproduce than the least fit,
90 | #where N is the number of individuals in the society.
91 | probability = [i for i in reversed(range(1,len(society_sorted)+1))]
92 |
93 | #We create a list of weights with the probabilities of non-mutation and mutation
94 | mutation = [1 - mutation_rate, mutation_rate]
95 |
96 | #For every new individual to be created through reproduction:
97 | for i in range (int(len(society) * reproduction_rate)):
98 |
99 | #We select two parents randomly, using the list of probabilities in "probability".
100 | father, mother = society_sorted[weighted_choice_sub(probability)], society_sorted[weighted_choice_sub(probability)]
101 |
102 | #We randomly select two cutting points for the genome.
103 | a, b = random.randrange(0, len(father.genome)), random.randrange(0, len(father.genome))
104 |
105 | #And we create the genome of the child putting together the genome slices of the parents in the cutting points.
106 | child_genome = father.genome[0:min(a,b)]+mother.genome[min(a,b):max(a,b)]+father.genome[max(a,b):]
107 |
108 | #For every bit in the not-yet-born child, we generate a list containing
109 | #1's in the positions where the genome must mutate (i.e. the bit must switch its value)
110 | #and 0's in the positions where the genome must stay the same.
111 | n = [weighted_choice_sub(mutation) for ii in range(len(child_genome))]
112 |
113 | #This line switches the bits of the genome of the child that must mutate.
114 | mutant_child_genome = [abs(n[i] - child_genome[i]) for i in range(len(child_genome))]
115 |
116 | #We finally append the newborn individual to the society
117 | newborn = Object(mutant_child_genome, args)
118 | calculate_traits (newborn, dict_genes)
119 | calculate_performances (newborn)
120 | calculate_fitness (newborn)
121 | society.append(newborn)
122 |
123 |
124 |
125 | def tournament(society, target_population):
126 |
127 | while len(society) > target_population:
128 |
129 | #index1, index2 = random.randrange(0, len(society)), random.randrange(0, len(society))
130 |
131 | #if society[index1].fitness > society[index2].fitness:
132 | # society.pop(index2)
133 | #else:
134 | # society.pop(index1)
135 |
136 | fitness_list = [individual.fitness for individual in society]
137 | society.pop(fitness_list.index(min(fitness_list)))
--------------------------------------------------------------------------------
/Ejercicios/Laberinto/laberinto/algen.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """Ejercicio del Laberinto
5 |
6 | Taller de la PyConEs 2015: Simplifica tu vida con sistemas complejos y algoritmos genéticos
7 |
8 | Este script contiene las funciones y clases necesarias para el algoritmo genético del
9 | ejercicio del laberinto.
10 |
11 |
12 | Este script usa arrays de numpy, aunque no debería ser difícil para alguien con experiencia
13 | sustituírlos por otras estructuras si es necesario.
14 |
15 | También usa la librería Matplotlib en las funciones que dibujan resultados."""
16 |
17 |
18 |
19 | import random as random
20 |
21 |
22 |
23 | #This function was taken from Eli Bendersky's website
24 | #It returns an index of a list called "weights",
25 | #where the content of each element in "weights" is the probability of this index to be returned.
26 | #For this function to be as fast as possible we need to pass it a list of weights in descending order.
27 | def weighted_choice_sub(weights):
28 | rnd = random.random() * sum(weights)
29 | for i, w in enumerate(weights):
30 | rnd -= w
31 | if rnd < 0:
32 | return i
33 |
34 |
35 |
36 |
37 | generate_random_binary_list = lambda n: [random.randint(0,1) for b in range(1,n+1)]
38 |
39 |
40 |
41 |
42 | class Individual (object):
43 |
44 | def __init__(self, genome):
45 |
46 | self.genome = genome
47 | self.traits = {}
48 | self.performances = {}
49 | self.fitness = 0
50 |
51 |
52 |
53 | def generate_genome (dict_genes):
54 |
55 | #We first calculate the total number of bits that the genome must contain
56 | number_of_bits = sum([dict_genes[trait] for trait in dict_genes])
57 |
58 | #And we return a random genome of this length
59 | return generate_random_binary_list(number_of_bits)
60 |
61 |
62 |
63 | def calculate_traits (individual, dict_genes):
64 | #This function must decipher the genome and return the traits of the individual.
65 | #Normally, the genome contains binary numerical values for the different traits.
66 |
67 | dict_traits = {}
68 | index = 0
69 |
70 | for trait in dict_genes:
71 | dict_traits[trait] = int(''.join(str(bit) for bit in individual.genome[index : index+dict_genes[trait]]), 2)
72 | index += dict_genes[trait]
73 |
74 | individual.traits = dict_traits
75 |
76 |
77 |
78 |
79 | def immigration (society, target_population,
80 | calculate_performances, calculate_fitness,
81 | dict_genes, Object = Individual, *args):
82 |
83 | while len(society) < target_population:
84 |
85 | new_individual = Object (generate_genome (dict_genes), args)
86 | calculate_traits (new_individual, dict_genes)
87 | calculate_performances (new_individual)
88 | calculate_fitness (new_individual)
89 |
90 | society.append (new_individual)
91 |
92 |
93 |
94 |
95 |
96 | def crossover (society, reproduction_rate, mutation_rate,
97 | calculate_performances, calculate_fitness,
98 | dict_genes, Object = Individual, *args):
99 |
100 | #First we create a list with the fitness values of every individual in the society
101 | fitness_list = [individual.fitness for individual in society]
102 |
103 | #We sort the individuals in the society in descending order of fitness.
104 | society_sorted = [x for (y, x) in sorted(zip(fitness_list, society), key=lambda x: x[0], reverse=True)]
105 |
106 | #We then create a list of relative probabilities in descending order,
107 | #so that the fittest individual in the society has N times more chances to reproduce than the least fit,
108 | #where N is the number of individuals in the society.
109 | probability = [i for i in reversed(range(1,len(society_sorted)+1))]
110 |
111 | #We create a list of weights with the probabilities of non-mutation and mutation
112 | mutation = [1 - mutation_rate, mutation_rate]
113 |
114 | #For every new individual to be created through reproduction:
115 | for i in range (int(len(society) * reproduction_rate)):
116 |
117 | #We select two parents randomly, using the list of probabilities in "probability".
118 | father, mother = society_sorted[weighted_choice_sub(probability)], society_sorted[weighted_choice_sub(probability)]
119 |
120 | #We randomly select two cutting points for the genome.
121 | a, b = random.randrange(0, len(father.genome)), random.randrange(0, len(father.genome))
122 |
123 | #And we create the genome of the child putting together the genome slices of the parents in the cutting points.
124 | child_genome = father.genome[0:min(a,b)]+mother.genome[min(a,b):max(a,b)]+father.genome[max(a,b):]
125 |
126 | #For every bit in the not-yet-born child, we generate a list containing
127 | #1's in the positions where the genome must mutate (i.e. the bit must switch its value)
128 | #and 0's in the positions where the genome must stay the same.
129 | n = [weighted_choice_sub(mutation) for ii in range(len(child_genome))]
130 |
131 | #This line switches the bits of the genome of the child that must mutate.
132 | mutant_child_genome = [abs(n[i] - child_genome[i]) for i in range(len(child_genome))]
133 |
134 | #We finally append the newborn individual to the society
135 | newborn = Object(mutant_child_genome, args)
136 | calculate_traits (newborn, dict_genes)
137 | calculate_performances (newborn)
138 | calculate_fitness (newborn)
139 | society.append(newborn)
140 |
141 |
142 |
143 | def tournament(society, target_population):
144 |
145 | while len(society) > target_population:
146 |
147 | #index1, index2 = random.randrange(0, len(society)), random.randrange(0, len(society))
148 |
149 | #if society[index1].fitness > society[index2].fitness:
150 | # society.pop(index2)
151 | #else:
152 | # society.pop(index1)
153 |
154 | fitness_list = [individual.fitness for individual in society]
155 | society.pop(fitness_list.index(min(fitness_list)))
--------------------------------------------------------------------------------
/Teoria II - Sistemas complejos.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | ""
8 | ]
9 | },
10 | {
11 | "cell_type": "markdown",
12 | "metadata": {},
13 | "source": [
14 | "#Simplifica tu vida con sistemas complejos y algoritmos genéticos"
15 | ]
16 | },
17 | {
18 | "cell_type": "markdown",
19 | "metadata": {},
20 | "source": [
21 | "##Parte 2 - Sistemas Complejos"
22 | ]
23 | },
24 | {
25 | "cell_type": "markdown",
26 | "metadata": {},
27 | "source": [
28 | "##¿Qué es un sistema complejo?"
29 | ]
30 | },
31 | {
32 | "cell_type": "markdown",
33 | "metadata": {},
34 | "source": [
35 | "Por el momento, no existe una definición formal exacta de qué es un sistema complejo. Sin embargo, de forma general, se puede aceptar que los sitemas complejos tienen una serie de propiedades que los identifican. De manera resumida, podemos decir que los más característicos son:\n",
36 | "\n",
37 | "- Están compuestos por múltiples unidades simples\n",
38 | "- Estas unidades se relacionan entre sí\n",
39 | "- Estas relaciones producen comportamientos emergentes que no se deducen fácilmente del análisis de los agentes sencillos por separado.\n",
40 | "\n",
41 | "Ejemplo: una colonia de hormigas, el cerebro, un animal, la biosfera, una célula... También es ralativamente común que las unidades simples de un sistema complejo sean sistemas complejos a su vez.\n",
42 | "\n",
43 | "Una de los aspectos más interesantes de estos sistemas son las citadas **propiedades emergentes**: Son características, patrones, variables o comportamientos del sistema que no se deducen de manera fácil a partir del estudio de cada agente del sistema. Por ejemplo, la inteligencia no se deduce fácilmente del estudio independiente de cada neurona. La estructura compleja y eficiente de un termitero no se puede calcular sólo analizando qué hace una termita aislada."
44 | ]
45 | },
46 | {
47 | "cell_type": "markdown",
48 | "metadata": {},
49 | "source": [
50 | "###¿Por qué son interesantes en ingeniería los sistemas complejos?"
51 | ]
52 | },
53 | {
54 | "cell_type": "markdown",
55 | "metadata": {},
56 | "source": [
57 | "Los sistemas complejos pueden ser realmente interesantes en muchos campos de la ingeniería:\n",
58 | "\n",
59 | "- Pueden llegar a ser muy robustos: un hormiguero que pierda a 2/3 de sus miembros puede sobrevivir, pero imagina intentar usar un ordenador o un vehículo con 2/3 de sus piezas rotas...\n",
60 | "- A veces pueden autoregularse a partir de reglas simples.\n",
61 | "- Algunos son fácilmente escalables: un enjambre de robots colaborando en una tarea, si han sido diseñados con este enfoque en mente, pueden mejorar su rendimiento añadiendo más robots iguales sin necesidad de más cambios.\n",
62 | "- Pueden usarse para resolver problemas complicados: Problema del viajante, distribución de paquetes en telecomunicaciones, líneas de transporte que se adaptan al tráfico en tiempo real..."
63 | ]
64 | },
65 | {
66 | "cell_type": "markdown",
67 | "metadata": {},
68 | "source": [
69 | "##Ejemplo : Algoritmo de Colonia de Hormigas (ACO) para el problema del viajante"
70 | ]
71 | },
72 | {
73 | "cell_type": "markdown",
74 | "metadata": {},
75 | "source": [
76 | "Imaginemos una distribución de ciudades en un mapa. Somos un vendedor que quiere visitarlas todas, sólo una vez cada una, gastando el menor combustible posible. Este es el conocido Problema del Viajante, que se puede resolver fácilmente con un sistema complejo. Es importante tener en cuenta que aunque este método obtiene resultados muy buenos rápidamente, no necesariamente encontrará siempre la solución óptima.\n",
77 | "\n",
78 | "Este problema clásico es muy conocido, porque bajo su enunciado simple se esconde una enorme complejidad: \n",
79 | "\n",
80 | "Para 10 ciudades hay 181.440 rutas diferentes, pero para 30 ciudades hay más de 4·10^31 rutas posibles. Un ordenador que calcule **un millón de rutas por segundo** necesitaría **10^18 años** para resolverlo. Dicho de otra forma, si se hubiera comenzado a calcular al comienzo de la creación del universo (hace unos 13.400 millones de años) sólo habría calculado el 0,0000001% a día de hoy.\n",
81 | "\n",
82 | "Suena interesante, ¿verdad? ¡Veamos cómo modelarlo y resolverlo con Hormigas!"
83 | ]
84 | },
85 | {
86 | "cell_type": "markdown",
87 | "metadata": {},
88 | "source": [
89 | ""
90 | ]
91 | },
92 | {
93 | "cell_type": "markdown",
94 | "metadata": {
95 | "collapsed": true
96 | },
97 | "source": [
98 | "El algoritmo se basa en varias generaciones sucesivas de hormigas que recorren el mapa viajando de ciudad en ciudad, eligiendo su siguiente ciudad de manera aletoria hasta que las han recorrido todas. En cada etapa del viaje, las hormigas eligen moverse de una ciudad a otra teniendo en cuenta las siguientes reglas:\n",
99 | "\n",
100 | "1. Debe visitar cada ciudad exactamente una vez, excepto la inicial en la que estará dos veces (salida y llegada final);\n",
101 | "2. Una ciudad distante tiene menor posibilidad de ser elegida (Visibilidad);\n",
102 | "3. Cuanto más intenso es el rastro de feromonas de una arista entre dos ciudades, mayor es la probabilidad de que esa arista sea elegida;\n",
103 | "4. Después de haber completado su recorrido, la hormiga deposita feromonas en todas las aristas visitadas, mayor cantidad cuanto más pequeña es la distancia total recorrida;\n",
104 | "5. Después de cada generación, algunas feromonas son evaporadas.\n"
105 | ]
106 | },
107 | {
108 | "cell_type": "code",
109 | "execution_count": null,
110 | "metadata": {
111 | "collapsed": true
112 | },
113 | "outputs": [],
114 | "source": [
115 | "%matplotlib inline\n",
116 | "from Ejercicios.Hormiguero import ants"
117 | ]
118 | },
119 | {
120 | "cell_type": "code",
121 | "execution_count": null,
122 | "metadata": {
123 | "collapsed": false
124 | },
125 | "outputs": [],
126 | "source": [
127 | "mapa1 = ants.Mapa()\n",
128 | "mapa1.draw_distances()"
129 | ]
130 | },
131 | {
132 | "cell_type": "code",
133 | "execution_count": null,
134 | "metadata": {
135 | "collapsed": false,
136 | "scrolled": true
137 | },
138 | "outputs": [],
139 | "source": [
140 | "mapa1.swarm_create(100)\n",
141 | "print('generación:', end=' ')\n",
142 | "for i in range(100):\n",
143 | " print(i, end='·')\n",
144 | " mapa1.swarm_generation()\n",
145 | "mapa1.draw_best_path()"
146 | ]
147 | },
148 | {
149 | "cell_type": "markdown",
150 | "metadata": {},
151 | "source": [
152 | "Después, durante la explicación de los ejercicios propuestos, veremos este caso con más detalle."
153 | ]
154 | },
155 | {
156 | "cell_type": "code",
157 | "execution_count": null,
158 | "metadata": {
159 | "collapsed": true
160 | },
161 | "outputs": [],
162 | "source": []
163 | },
164 | {
165 | "cell_type": "code",
166 | "execution_count": null,
167 | "metadata": {
168 | "collapsed": true
169 | },
170 | "outputs": [],
171 | "source": []
172 | },
173 | {
174 | "cell_type": "markdown",
175 | "metadata": {},
176 | "source": [
177 | "Siro Moreno, Aeropython, 19 de Noviembre de 2015\n"
178 | ]
179 | },
180 | {
181 | "cell_type": "code",
182 | "execution_count": null,
183 | "metadata": {
184 | "collapsed": true
185 | },
186 | "outputs": [],
187 | "source": []
188 | }
189 | ],
190 | "metadata": {
191 | "kernelspec": {
192 | "display_name": "Python 3",
193 | "language": "python",
194 | "name": "python3"
195 | },
196 | "language_info": {
197 | "codemirror_mode": {
198 | "name": "ipython",
199 | "version": 3
200 | },
201 | "file_extension": ".py",
202 | "mimetype": "text/x-python",
203 | "name": "python",
204 | "nbconvert_exporter": "python",
205 | "pygments_lexer": "ipython3",
206 | "version": "3.4.3"
207 | }
208 | },
209 | "nbformat": 4,
210 | "nbformat_minor": 0
211 | }
212 |
--------------------------------------------------------------------------------
/Ejercicios/Optimizacion de rotor/optrot/rotor.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """Ejercicio del Algoritmo de optimización ingenieril
5 |
6 | Taller de la PyConEs 2015: Simplifica tu vida con sistemas complejos y algoritmos genéticos
7 |
8 | Este script contiene las funciones necesarias para calcular un rotor o hélice.
9 |
10 |
11 | Este script usa arrays de numpy, aunque no debería ser difícil para alguien con experiencia
12 | sustituírlos por otras estructuras si es necesario.
13 |
14 | """
15 |
16 |
17 | import numpy as np
18 |
19 | def airf_aerodata(c, v, alpha, x, rho):
20 | '''Devuelve los coeficientes de sustentación y resistencia.
21 | c = cuerda
22 | v = velocidad
23 | alpha = ángulo de ataque'''
24 |
25 | #Calcular reynolds
26 | factor_Re = 0.5 + 8 *(1.5 / (1 + c * v * rho) )
27 | factor_Re_2 = 1 / (1.2 - factor_Re)
28 | #calcular cl
29 | alpha_rad = alpha * np.pi/180
30 | s_fun = 0.5 - np.arctan((alpha - 20) * 0.4)/np.pi
31 | s_fun_2 = 0.5 - np.arctan((-alpha - 20) * 0.4)/np.pi
32 | cl_base = (alpha_rad + 0.) * 2 * np.pi
33 | cl = cl_base * s_fun * s_fun_2 * (0.7 + 0.3 * np.sqrt(x))
34 | cd_base = 0.1 * cl_base**2 + 0.01 * factor_Re
35 | cd_base_2 = 0.55 #+ 0.01 * factor_Re
36 | cd = 0.4 * cd_base * s_fun * s_fun_2 + cd_base_2 * (1 - s_fun) + cd_base_2 * (1 - s_fun_2)
37 | # print(factor_Re, factor_Re_2)
38 |
39 | return cl, cd#, factor_Re
40 |
41 |
42 | def airf_slope (alpha, v, c, x, rho = 0.0208):
43 | '''
44 | Calcula la pendiente de la curva de sustentación.
45 | Se alimenta con 4 arrays de dimensión N:
46 | - c, cuerda del perfil en metros
47 | - v, velocidad del en metros
48 | - alpha, ángulo de ataque del perfil en RADIANES
49 | - x, posición del perfil (0 = raíz, 0 = punta)
50 |
51 | Devuelve en un vector de dimensión N las pendientes de la curva de sustentación para cada perfil.
52 |
53 | '''
54 | alpha_0 = alpha *180 / np.pi #La función que calcula cl usa grados
55 |
56 | cl = airf_aerodata(c, v, alpha_0, x, rho = rho)[0]
57 |
58 | #Esta parte es por si hay algún alpha = 0, para evitar el error de matemáticas.
59 | for ii in range(alpha.shape[0]):
60 | if abs(alpha[ii]) < 0.00000001:
61 | alpha[ii] = 0.00000001
62 |
63 | slope = cl / alpha
64 |
65 | return slope
66 |
67 | def chord(x, law = 0.05):
68 | '''
69 | Devuelve un array de longitudes de cuerda.
70 | Entradas:
71 | -x : array de posiciones (0 = raíz, 1 = punta)
72 | -law : describe la forma de la pala, y puede ser:
73 | - un número float: la cuerda es constante, con ese valor en metros
74 | - una lista de la forma ['l', x1, x2 ]:
75 | La cuerda es lineal con la posición, y mide x1 m en la raíz y x2 m en la punta
76 | '''
77 | c_0 = np.array([1])
78 | if type(x) == np.ndarray:
79 | c_0 = np.ones_like(x)
80 |
81 |
82 |
83 |
84 | if type(law) == float :
85 | #print('law is float')
86 | c = c_0 * law
87 | elif type(law) == list :
88 | if law[0] == 'l':
89 | c = law[1] + (law[2] - law[1]) * x
90 | else:
91 | c = 'ERROR '
92 | print('Law not recogniced: ', type(law))
93 | print(5 * c)
94 | else:
95 | c = 'ERROR '
96 | print('Law not recogniced: ', type(law))
97 | print(5 * c)
98 | return c
99 |
100 |
101 | def torsion(x, law = 'c', p = 10):
102 |
103 | '''
104 | Devuelve un array de torsiones.
105 | Entradas:
106 | -x : array de posiciones (0 = raíz, 1 = punta)
107 | -p : parámetro
108 | -law : describe la forma de la ley de torsiones, y puede ser:
109 | - 'c': distribución de torsión constante = p
110 | - 'l': distribución de torsión lineal, con p[0] en la raíz y p[1] en la punta
111 | - 'h': distribución de torsión hiperbólica, con torsión p en la punta
112 | '''
113 |
114 | c_0 = np.array([1])
115 | if type(x) == np.ndarray:
116 | c_0 = np.ones_like(x)
117 |
118 | if law == 'c' :
119 | t = c_0 * p
120 | elif law == 'h' :
121 |
122 | t0 = (p / x)
123 | t = (t0 * (0.5 * (np.sign(90 - t0) + 1)) +
124 | 90 * (0.5 * (np.sign(t0 - 90) + 1)) )
125 | elif law == 'l':
126 | t = p[0] + (p[1] - p[0]) * x
127 | else:
128 | t = 'ERROR'
129 | return t
130 |
131 |
132 | def integrar(x, y):
133 | step = x[1:] - x[:-1]
134 | y0 = (y[:-1] + y[1:])/2
135 | return np.sum(step * y0)
136 |
137 | def rotor_adim(omega, vz, R, b,
138 | x_min = 0.05, n = 500,
139 | theta0 = 0, tors_param = ['c', 10],
140 | chord_params = 0.05,
141 | rho = 0.0208):
142 | '''Calcula un rotor teniendo en cuenta perdidas de punta de pala'''
143 |
144 | x = np.linspace(x_min, 1, n)
145 | dx = x[1] - x[0]
146 | r = x * R
147 | dr = dx * R
148 | vr = omega * r
149 | c = chord(x, chord_params)
150 | sigma = b * c / (np.pi * R)
151 |
152 |
153 | theta = theta0 + torsion(x, tors_param[0], tors_param[1]) * np.pi / 180
154 | a0 = 2 * np.pi
155 | a = a0
156 | vza = vz / (omega * R)
157 |
158 | #teoría elemento de pala + tubo de corriente:
159 |
160 |
161 |
162 |
163 | for ii in range(5):
164 | ao = a * sigma / 2
165 | via = 0.5 * ( - (vza + ao/4) + np.sqrt((vza + ao/4)**2 + ao *(x * theta - vza)))
166 | vi = omega * R * via
167 | ut = vr
168 | up = -(vz * np.ones_like(x) + vi)
169 | ur = np.sqrt(ut **2 + up ** 2)
170 | fi = np.arctan(up / ut)
171 | alpha = theta + fi
172 | a_1 = a
173 | a = airf_slope( alpha, ur, c, x, rho = rho)
174 | #print(np.mean(a), np.mean(a/a0), np.mean(a/a_1))
175 |
176 |
177 | cl, cd = airf_aerodata(c, ur, alpha * 180 / np.pi, x, rho = rho)#[0:2]
178 |
179 |
180 | dct = ao * (theta - (vza + via)/x) * x**2
181 | dcpi = - fi * x * dct
182 | dcp0 = sigma * cd * x**3 / 2
183 |
184 | ct = integrar(x, dct)
185 | cpi = integrar(x, dcpi)
186 | cp0 = integrar(x, dcp0)
187 | cp = cpi + cp0
188 |
189 |
190 | #Calculamos el factor B para representar las pérdidas en Punta de Pala:
191 |
192 | B = 1 - np.sqrt(abs(2 * ct)) / b
193 | #print('B = ', B)
194 |
195 | dct = np.where(x < B, dct, 0)
196 | dcpi = np.where(x < B, dcpi, 0)
197 |
198 | ct = integrar(x, dct)
199 | cpi = integrar(x, dcpi)
200 | cp0 = integrar(x, dcp0)
201 | cp = cpi + cp0
202 |
203 | return (ct, cp, vza)
204 |
205 | def densidad(h):
206 | t = 288.15 - 0.0065 * h
207 | rho = 1.225 * (t/288.15) ** (9.8 / (287 * 0.0065) - 1)
208 | return rho
209 |
210 | def calcular_rotor(omega, vz, R, b,
211 | h = 0,
212 | theta0 = 0.174, tors_param = ['h', 14],
213 | chord_params = 0.05):
214 | '''
215 | Calcula las propiedades de una hélice.
216 |
217 | Argumentos obligatorios:
218 |
219 | - omega: velocidad de giro de la hélice, en rad/s
220 | - vz: velocidad de avance, en m/s
221 | - R : radio de la hélice
222 | - b : número de palas
223 |
224 | Argumentos opcionales:
225 |
226 | - h : altitud de vuelo, en metros sobre el nivel del mar
227 | - theta0 : ángulo de paso colectivo
228 | - tors_param : parámetros de torsión de la hélice:
229 | formato: [ley, p]
230 | p: Parámetro: número o lista
231 | Ley:describe la forma de la ley de torsiones, y puede ser:
232 | - 'c': distribución de torsión constante = p
233 | - 'l': distribución de torsión lineal, con p[0] en la raíz y p[1] en la punta
234 | - 'h': distribución de torsión hiperbólica, con torsión p en la punta
235 | - chord_params : parámetros de distribución de cuerda de la hélice.
236 | Describe la forma de la pala, y puede ser:
237 | - un número float: la cuerda es constante, con ese valor en metros
238 | - una lista de la forma ['l', x1, x2 ]:
239 | La cuerda es lineal con la posición, y mide x1 m en la raíz y x2 m en la punta
240 | Devuelve:
241 |
242 | - T : tracción de la hélice, en Newtons
243 | - P : potencia de la hélice, en Watios
244 | - efic : eficiencia o rendimiento de la hélice (a v=0 es 0 por definición)
245 | - mach_punta : número de mach de las puntas de la hélice
246 | '''
247 | x_min = 0.05
248 | n = 100
249 | temp = 288.15 - 0.0065 * h
250 | v_son = np.sqrt(1.4 * 8.314 * temp / 0.029)
251 | rho = densidad(h)
252 | sup = np.pi * R**2
253 | ct, cp, vza = rotor_adim(omega, vz, R, b, x_min, n, theta0, tors_param, chord_params, rho)
254 | T = max(0, ct * rho * sup * omega**2 * R**2)
255 | P = max(0, cp * rho * sup * omega**3 * R**3)
256 | if cp > 0:
257 | efic = T * vz / P
258 | else:
259 | efic = 0
260 | v_punta = np.sqrt(vz**2 + (omega * R)**2)
261 | mach_punta = v_punta / v_son
262 |
263 | return T, P, efic, mach_punta
264 |
--------------------------------------------------------------------------------
/Ejercicios/El problema de los matrimonios estables/matrimonios_estables.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | ""
8 | ]
9 | },
10 | {
11 | "cell_type": "markdown",
12 | "metadata": {},
13 | "source": [
14 | "#El problema de los matrimonios estables"
15 | ]
16 | },
17 | {
18 | "cell_type": "markdown",
19 | "metadata": {},
20 | "source": [
21 | ">*Dados un cierto número de hombres y mujeres heterosexuales, organízalos por parejas de tal manera que su matrimonio sea estable. Cada persona ha ordenado a las personas del sexo opuesto según su preferencia. Los matrimonios se consideran estables si no es posible encontrar dos personas del sexo opuesto que se atraigan entre sí más que lo que les atraen sus respectivas parejas.*"
22 | ]
23 | },
24 | {
25 | "cell_type": "markdown",
26 | "metadata": {},
27 | "source": [
28 | "Este problema siempre tiene solución (¡a veces puede tener varias!) y existe un algoritmo, diseñado por David Gale and Lloyd Shapley en 1962, en el que las parejas se ordenan a sí mismas, como un sistema complejo. Mira en qué consiste el algoritmo en este vídeo:"
29 | ]
30 | },
31 | {
32 | "cell_type": "code",
33 | "execution_count": null,
34 | "metadata": {
35 | "collapsed": true
36 | },
37 | "outputs": [],
38 | "source": [
39 | "from IPython.display import HTML\n",
40 | "HTML('')"
41 | ]
42 | },
43 | {
44 | "cell_type": "markdown",
45 | "metadata": {},
46 | "source": [
47 | "Nota: Este ejercicio sigue la nomenclatura y la estructura clásicas de este problema para que resulte intuitivo y fácil de seguir, no pretende ser un modelo real de comportamiento. Desde la organización de la PyConEs y nosotros mismos, queremos fomentar y apoyar la diversidad y la tolerancia en todas las facetas de la sociedad, y respetamos por igual todas las identidades de género y sexualidad."
48 | ]
49 | },
50 | {
51 | "cell_type": "code",
52 | "execution_count": 1,
53 | "metadata": {
54 | "collapsed": true
55 | },
56 | "outputs": [],
57 | "source": [
58 | "import numpy as np\n",
59 | "import random as random"
60 | ]
61 | },
62 | {
63 | "cell_type": "code",
64 | "execution_count": 2,
65 | "metadata": {
66 | "collapsed": false
67 | },
68 | "outputs": [],
69 | "source": [
70 | "class Woman (object):\n",
71 | " ''' Este es el elemento estático, quien recibe propuestas y elige al mejor'''\n",
72 | " def __init__(self, name):\n",
73 | " \n",
74 | " self.name = name\n",
75 | " self.preferences = {}\n",
76 | " self.preferences_inv = {}\n",
77 | " self.boyfriend = []\n",
78 | " self.candidates = []\n",
79 | " \n",
80 | " def engage(self, man):\n",
81 | " self.boyfriend = man\n",
82 | " man.girlfriend = self\n",
83 | " \n",
84 | " def breakup(self, man):\n",
85 | " self.boyfriend = []\n",
86 | " man.girlfriend = []\n",
87 | " \n",
88 | " \n",
89 | "class Man (object):\n",
90 | " '''Este es el elemento dinámico, que busca a su mejores opciones y se propone'''\n",
91 | " \n",
92 | " def __init__(self, name):\n",
93 | " \n",
94 | " self.name = name\n",
95 | " self.preferences = {}\n",
96 | " self.preferences_inv = {}\n",
97 | " self.girlfriend = []\n",
98 | " self.number_of_proposals = 1\n",
99 | " \n",
100 | " def propose(self, woman):\n",
101 | " woman.candidates += [self]\n",
102 | " self.number_of_proposals += 1"
103 | ]
104 | },
105 | {
106 | "cell_type": "markdown",
107 | "metadata": {},
108 | "source": [
109 | "Ahora creamos nuestra población, y la repartimos en dos listas."
110 | ]
111 | },
112 | {
113 | "cell_type": "code",
114 | "execution_count": 3,
115 | "metadata": {
116 | "collapsed": true
117 | },
118 | "outputs": [],
119 | "source": [
120 | "magdalena, elena, ana, julia, marta = Woman('Magdalena'), Woman('Elena'), Woman('Ana'), Woman('Julia'), Woman('Marta')\n",
121 | "carlos, siro, manuel, antonio, javier = Man('Carlos'), Man('Siro'), Man('Manuel'), Man('Antonio'), Man('Javier')\n",
122 | "\n",
123 | "women = [magdalena, elena, ana, julia, marta]\n",
124 | "men =[carlos, siro, manuel, antonio, javier]"
125 | ]
126 | },
127 | {
128 | "cell_type": "code",
129 | "execution_count": 4,
130 | "metadata": {
131 | "collapsed": false
132 | },
133 | "outputs": [],
134 | "source": [
135 | "for woman in women:\n",
136 | " #Generamos una lista de preferencias de manera aleatoria\n",
137 | " preferences = [ii for ii in range(1, len(men)+1)]\n",
138 | " random.shuffle(preferences)\n",
139 | " \n",
140 | " #Estas preferencias se almacenan como dos diccionarios\n",
141 | " for index in range(len(men)):\n",
142 | " woman.preferences[preferences[index]] = men[index]\n",
143 | " \n",
144 | " for index in range(1, len(men)+1):\n",
145 | " woman.preferences_inv[woman.preferences.get(index).name] = index\n",
146 | " \n",
147 | "for man in men:\n",
148 | " preferences = [ii for ii in range(1, len(women)+1)]\n",
149 | " random.shuffle(preferences)\n",
150 | " \n",
151 | " for index in range(len(women)):\n",
152 | " man.preferences[preferences[index]] = women[index]\n",
153 | " \n",
154 | " for index in range(1, len(men)+1):\n",
155 | " man.preferences_inv[man.preferences.get(index).name] = index"
156 | ]
157 | },
158 | {
159 | "cell_type": "code",
160 | "execution_count": 5,
161 | "metadata": {
162 | "collapsed": false
163 | },
164 | "outputs": [],
165 | "source": [
166 | "def noche_de_fiesta(man, women):\n",
167 | " \n",
168 | " for woman in women:\n",
169 | " woman.candidates=[]\n",
170 | " \n",
171 | " for man in men:\n",
172 | " if man.girlfriend == []:\n",
173 | " man.propose(man.preferences[man.number_of_proposals])\n",
174 | " \n",
175 | " for woman in women:\n",
176 | " \n",
177 | " if woman.boyfriend == []:\n",
178 | " for ii in range(1, len(men)+1):\n",
179 | " if woman.preferences[ii] in woman.candidates:\n",
180 | " woman.engage(woman.preferences[ii])\n",
181 | " break\n",
182 | " \n",
183 | " elif any (woman.preferences_inv[man.name]>woman.preferences_inv[woman.boyfriend.name] for man in woman.candidates):\n",
184 | " woman.breakup(woman.boyfriend)\n",
185 | " for ii in range(1, len(men)+1):\n",
186 | " if woman.preferences[ii] in woman.candidates:\n",
187 | " woman.engage(woman.preferences[ii])\n",
188 | " break "
189 | ]
190 | },
191 | {
192 | "cell_type": "code",
193 | "execution_count": null,
194 | "metadata": {
195 | "collapsed": false
196 | },
197 | "outputs": [],
198 | "source": [
199 | "for dia in range(1, len(men)+2):\n",
200 | " print('Noche ' + str(dia))\n",
201 | " print('-------')\n",
202 | " noche_de_fiesta(men, women)\n",
203 | " for woman in women:\n",
204 | " print(woman.name)\n",
205 | " if woman.candidates != []:\n",
206 | " print(' Candidatos: ', [candidate.name for candidate in woman.candidates])\n",
207 | " if woman.boyfriend != []:\n",
208 | " print(' Novio: ', woman.boyfriend.name)\n",
209 | " print()\n",
210 | " print()"
211 | ]
212 | },
213 | {
214 | "cell_type": "markdown",
215 | "metadata": {},
216 | "source": [
217 | "Este ejercicio parece un poco extraño, ¿no?\n",
218 | "\n",
219 | "Pero... ¿y si te dijera que es un algoritmo muy utilizado diariamente por instituciones de todo el mundo?"
220 | ]
221 | },
222 | {
223 | "cell_type": "markdown",
224 | "metadata": {},
225 | "source": [
226 | "##Te toca trabajar"
227 | ]
228 | },
229 | {
230 | "cell_type": "markdown",
231 | "metadata": {},
232 | "source": [
233 | "Este algoritmo es muy usado para asignación de plazas en oposiciones, y se usa en varios países para repartir a los candidatos en las diferentes plazas según sus resultados y sus preferencias. Modelémoslo!\n",
234 | "\n",
235 | "Crea unas clases nuevas llamadas 'Candidato' y 'Destino' basadas en 'Man' y 'Woman'. Simplemente con esto, ya tenemos un reparto de plazas muy adecuado, pero podemos mejorar nuestro modelo. Te sugiero que intentes los siguientes cambios:\n",
236 | "\n",
237 | "- Los candidatos generan una propiedad aleatoria llamada 'Nota' al ser creados, que los destinos usan para decidir sus preferencias, en vez de un orden aleatorio\n",
238 | "- Los destino tienen una capacidad de varios puestos, e incluso...\n",
239 | "- Cada destino tiene una cantidad diferente de puestos, que se define al crearlo.\n",
240 | "\n",
241 | "Recuerda que es probable que necesites modificar las funciones anteriores (o crear otras nuevas basadas en ellas, si quieres conservar las originales sin tocar como referencia).\n",
242 | "\n",
243 | "Para crear la población, puede que te resulte útil el método append() en el interior de un bucle."
244 | ]
245 | },
246 | {
247 | "cell_type": "code",
248 | "execution_count": null,
249 | "metadata": {
250 | "collapsed": true
251 | },
252 | "outputs": [],
253 | "source": []
254 | },
255 | {
256 | "cell_type": "code",
257 | "execution_count": null,
258 | "metadata": {
259 | "collapsed": true
260 | },
261 | "outputs": [],
262 | "source": []
263 | },
264 | {
265 | "cell_type": "markdown",
266 | "metadata": {},
267 | "source": [
268 | "Carlos Dorado, Aeropython, 20 de Noviembre de 2015"
269 | ]
270 | },
271 | {
272 | "cell_type": "code",
273 | "execution_count": null,
274 | "metadata": {
275 | "collapsed": true
276 | },
277 | "outputs": [],
278 | "source": []
279 | }
280 | ],
281 | "metadata": {
282 | "kernelspec": {
283 | "display_name": "Python 3",
284 | "language": "python",
285 | "name": "python3"
286 | },
287 | "language_info": {
288 | "codemirror_mode": {
289 | "name": "ipython",
290 | "version": 3
291 | },
292 | "file_extension": ".py",
293 | "mimetype": "text/x-python",
294 | "name": "python",
295 | "nbconvert_exporter": "python",
296 | "pygments_lexer": "ipython3",
297 | "version": "3.4.3"
298 | }
299 | },
300 | "nbformat": 4,
301 | "nbformat_minor": 0
302 | }
303 |
--------------------------------------------------------------------------------
/Ejercicios/Hormiguero/ants/_ants.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """Ejercicio del Algoritmo de Colonia de Hormigas
5 |
6 | Taller de la PyConEs 2015: Simplifica tu vida con sistemas complejos y algoritmos genéticos
7 |
8 | Este script contiene las funciones y clases necesarias para ejecutar una simulación del
9 | Algoritmo de colonia de hormigas para optimizar el problema del viajante.
10 |
11 |
12 | Este script usa arrays de numpy, aunque no debería ser difícil para alguien con experiencia
13 | sustituírlos por otras estructuras si es necesario.
14 |
15 | También usa la librería Matplotlib en las funciones que dibujan resultados."""
16 |
17 |
18 | import numpy as np
19 | import matplotlib.pyplot as plt
20 |
21 | #Primero vamos a definir los dos tipos de objeto que necesitamos:
22 | class Mapa:
23 | '''Este objeto contiene el mapa de las ciudades que se van a visitar,
24 | con todos los datos necesarios (como las feromonas y distancias entre ciudades)
25 | y las hormigas que lo recorren'''
26 | def __init__ (self, n_ciud = 10, mapsize = 10):
27 | self.n_ciud = n_ciud
28 | self.mapsize = mapsize
29 | self.ciudades = mapsize * np.random.rand(n_ciud, 2)
30 | self.distances = np.zeros([n_ciud, n_ciud])
31 | for i in range(n_ciud):
32 | for j in range(i+1, n_ciud):
33 | vector = self.ciudades[i,:] - self.ciudades[j,:]
34 | modulo = np.linalg.norm(vector)
35 | self.distances[i,j] = modulo
36 | self.distances[j,i] = modulo
37 | self.feromap = np.zeros_like(self.distances)
38 | self.feromultiplier = 1
39 | self.conjunto_analisis = []
40 | self.pathdata = []
41 | self.bestpath = [0,1]
42 | self.bestpathlenght = n_ciud * mapsize
43 |
44 | def show_distances_matrix(self):
45 | '''Muestra la matriz de distancias en código de colores'''
46 | plt.matshow(self.distances)
47 | plt.title('Matriz de distancias entre ciudades')
48 |
49 | def show_feromones_matrix(self):
50 | '''Muestra la matriz de feromonas en código de colores'''
51 | plt.matshow(self.feromap)
52 | plt.title('Matriz de feromonas entre ciudades')
53 |
54 | def draw_distances(self):
55 | '''Dibuja un mapa con las ciudades, unidas entre sí por líneas que son más gruesas
56 | cuanto más cercanas son. Es una manera gráfica de comprobar la matriz de distancias.'''
57 | plt.figure(None, figsize=(8,8))
58 | plt.scatter(self.ciudades[:,0], self.ciudades[:,1], s = 100, c = '#5599FF',zorder=2)
59 | for i in range(self.n_ciud):
60 | for j in range(i+1, self.n_ciud):
61 | path = np.zeros([2,2])
62 | path[0,:] = self.ciudades[i,:]
63 | path[1,:] = self.ciudades[j,:]
64 | dist = self.distances[i,j]
65 | thickness = 7 - dist * (7 / self.mapsize)
66 | plt.plot(path[:,0], path[:,1],'#88AA22', linewidth=thickness,zorder=1)
67 | plt.title('Mapa de ciudades con sus distancias')
68 |
69 | def draw_feromones(self, rescale_lines = True):
70 | '''Dibuja un mapa con las ciudades, unidas entre sí por líneas que son más gruesas
71 | cuanto más feromonas contiene la ruta que las une. Es una manera gráfica
72 | de comprobar la matriz de feromonas.'''
73 | plt.figure(None, figsize=(8,8))
74 | plt.scatter(self.ciudades[:,0], self.ciudades[:,1], s = 100, c = '#5599FF',zorder=2)
75 | if rescale_lines:
76 | maxfer = np.max(self.feromap)
77 | for i in range(self.n_ciud):
78 | for j in range(i+1, self.n_ciud):
79 | path = np.zeros([2,2])
80 | path[0,:] = self.ciudades[i,:]
81 | path[1,:] = self.ciudades[j,:]
82 | if rescale_lines:
83 | fer = self.feromap[i,j]
84 | if maxfer > 0:
85 | fer *= 7/maxfer
86 |
87 | else:
88 | fer = self.feromap[i,j]
89 |
90 | plt.plot(path[:,0], path[:,1],'#DD2222', linewidth=fer,zorder=1)
91 | plt.title('Mapa de ciudades con sus rastros de feromonas')
92 |
93 | def draw_best_path(self):
94 | '''Dibuja un mapa con las ciudades, unidas entre sí por la mejor ruta encontrada hasta el momento.'''
95 | plt.figure(None, figsize=(8,8))
96 | plt.scatter(self.ciudades[:,0], self.ciudades[:,1], s = 100, c = '#5599FF',zorder=2)
97 | ruta = self.ciudades[[self.bestpath]]
98 | plt.plot(ruta[:,0], ruta[:,1],'#2222AA', linewidth=8,zorder=1)
99 | plt.title('Mapa de ciudades con mejor ruta encontrada')
100 |
101 | def draw_results(self, relative_scale = False):
102 | '''Dibuja la longitud máxima, mínima y media de los caminos que siguen las hormigas,
103 | y la longitud mínima que el algoritmo ha encontrado'''
104 | plt.figure(None, figsize=(8,5))
105 | patharray = np.array(self.pathdata)
106 | for i in range(3):
107 | plt.plot(patharray[:,i])
108 | longx = len(patharray[:,0])
109 | plt.plot([0, longx], [self.bestpathlenght, self.bestpathlenght])
110 | plt.title('Longitud máxima, mínima, media y mejor camino encontrado')
111 | if not relative_scale : plt.ylim(0)
112 |
113 | def draw_best_results(self, relative_scale = False):
114 | '''Dibuja la longitud mínima de los caminos que siguen las hormigas,
115 | para todas las veces que el algoritmo se ha ejecutado,
116 | y la longitud mínima que el algoritmo ha encontrado'''
117 | plt.figure(None, figsize=(8,5))
118 | longx = 0
119 | for i in range(len(self.conjunto_analisis)):
120 | patharray = np.array(self.conjunto_analisis[i])
121 | plt.plot(patharray[:,1])
122 | longx = max(longx, len(patharray[:,0]))
123 |
124 | patharray = np.array(self.pathdata)
125 | longx = max(longx, len(patharray[:,0]))
126 | plt.plot(patharray[:,1])
127 |
128 | plt.plot([0, longx], [self.bestpathlenght, self.bestpathlenght])
129 | plt.title('Longitud mínima para cada ejecución y mejor camino encontrado')
130 | if not relative_scale : plt.ylim(0)
131 |
132 | def swarm_create(self, n_ant = 10):
133 | '''Crea una población de hormigas en el mapa'''
134 | self.lista_hormigas = []
135 | for i in range(n_ant):
136 | nueva = Hormiga(self)
137 | self.lista_hormigas.append(nueva)
138 | del(nueva)
139 |
140 | def swarm_show(self):
141 | '''Dibuja un mapa con las ciudades y las hormigas.
142 | Es una manera gráfica de comprobar dónde se encuentran.'''
143 | plt.figure(None, figsize=(8,8))
144 | plt.scatter(self.ciudades[:,0], self.ciudades[:,1], s = 100, c = '#5599FF')
145 | ant_pos = np.zeros([len(self.lista_hormigas), 2])
146 | for i in range(len(self.lista_hormigas)):
147 | hormiga = self.lista_hormigas[i]
148 | city = hormiga.position
149 | exact_position = self.ciudades[city,:]
150 | aprox_position = exact_position + 0.03 *self.mapsize * (np.random.rand(2) - 0.5)
151 | #print(exact_position)
152 | #print(aprox_position)
153 | ant_pos[i,:] = aprox_position
154 | plt.scatter(ant_pos[:,0], ant_pos[:,1], s = 5, c = 'k')
155 | plt.title('Mapa de ciudades y hormigas')
156 |
157 | def feromone_reset(self):
158 | '''Devuelve a 0 el mapa de feromonas para repetir el análisis
159 | de un mapa dado.
160 | Los datos alcanzados hasta ahora, se guardarán para posterior consulta.'''
161 | self.feromap = np.zeros_like(self.distances)
162 | self.conjunto_analisis.append(self.pathdata)
163 | self.pathdata = []
164 |
165 | def feromone_fine_tune(self):
166 | '''Permite controlar en detalle la cantidad de feromonas que se evaporan cada turno.
167 | Un factor mayor aumenta la cantidad de feromonas, y viceversa.
168 | Por defecto, el factor = 1.'''
169 | ok = False
170 | while not ok:
171 | x = input('introduzca un valor, p.ej. 1: ')
172 | try:
173 | x = float(x)
174 | ok = True
175 | except:
176 | print('Valor incorrecto')
177 |
178 | print('Valor cambiado')
179 | self.feromultiplier = x
180 |
181 | def swarm_delete(self):
182 | '''Elimina a todas las hormigas del mapa'''
183 | del(self.lista_hormigas)
184 | self.lista_hormigas = []
185 |
186 | def swarm_generation(self):
187 | '''Realiza una generación completa de hormigas:
188 | 1. Las mueve paso a paso hasta completar la ruta
189 | 2. Analiza los resultados
190 | 3. Deposita las feromonas
191 | 4. Elimina las hormigas viejas y crea una nueva población
192 | 5. Evapora las feromonas'''
193 | n_ant = len(self.lista_hormigas)
194 | self.pathlens = []
195 | for i in range(self.n_ciud - 1):
196 | for hormiga in self.lista_hormigas:
197 | hormiga.journey_step()
198 | for hormiga in self.lista_hormigas:
199 | hormiga.back_home()
200 | length = hormiga.calc_route_length()
201 | self.pathlens.append(length)
202 | if length < self.bestpathlenght :
203 | self.bestpathlenght = length
204 | self.bestpath = hormiga.route
205 | hormiga.feromone_spray()
206 |
207 | maxpath = max(self.pathlens)
208 | minpath = min(self.pathlens)
209 | meanpath = np.mean(self.pathlens)
210 | self.pathdata.append([maxpath, minpath, meanpath])
211 | self.swarm_delete()
212 | self.swarm_create(n_ant)
213 | self.feromap /= (3 / self.feromultiplier)
214 | self.feromap -=0.5
215 | self.feromap = np.where(self.feromap>0, self.feromap, np.zeros_like(self.feromap))
216 |
217 |
218 | class Hormiga:
219 | '''Estas hormigas recorren el mapa acumulando información
220 | sobre la longitud del viaje'''
221 | def __init__(self, mapa):
222 | self.mapa = mapa
223 | self.city_list = list(range(mapa.n_ciud))
224 | self.start = self.city_list.pop(np.random.randint(mapa.n_ciud))
225 | self.position = self.start
226 | self.route = [self.start,]
227 | self.distance_weight = 0.2
228 |
229 | def journey_step(self):
230 | '''Avanza a la siguiente ciudad'''
231 | all_dist = self.mapa.distances[self.position, :]
232 | posible_dist = all_dist[self.city_list]
233 | all_ferom = self.mapa.feromap[self.position, :]
234 | posible_ferom = all_ferom[self.city_list]
235 | probabilities = (self.distance_weight / (0.1 + posible_dist) +
236 | (1 - self.distance_weight) * (0.1 + posible_ferom))
237 | probabilities = probabilities / np.sum(probabilities)
238 | indexes = np.arange(len(self.city_list))
239 | new_city_index = np.random.choice(indexes, 1, p = probabilities)
240 | self.position = self.city_list.pop(new_city_index)
241 | self.route.append(self.position)
242 |
243 | def back_home(self):
244 | '''Devuelve a la hormiga a su ciudad inicial tras recorrer todo el mapa'''
245 | self.position = self.start
246 | self.route.append(self.start)
247 |
248 | def calc_route_length(self):
249 | '''Calcula la longitud de la ruta de la hormiga'''
250 | self.route_length = 0
251 | for i in range(1, len(self.route)):
252 | city1 = self.route[i-1]
253 | city2 = self.route[i]
254 | dist = self.mapa.distances[city1, city2]
255 | self.route_length += dist
256 | return self.route_length
257 |
258 | def feromone_spray(self):
259 | '''Deposita sobre la ruta recorrida una cantidad de feromonas
260 | que depende de la longitud del viaje'''
261 | feromone_amount = (2 * self.mapa.n_ciud * self.mapa.mapsize)/self.route_length**2
262 | for i in range(1, len(self.route)):
263 | city1 = self.route[i-1]
264 | city2 = self.route[i]
265 | self.mapa.feromap[city1, city2] += feromone_amount
266 | self.mapa.feromap[city2, city1] += feromone_amount
267 |
268 | #Sólo necesitamos estos objetos para ejecutar nuestras simulaciones
269 |
270 |
271 |
272 |
273 | #Ejemplo:
274 | if __name__ == '__main__':
275 |
276 | map1 = ants.Mapa(8)
277 |
278 |
279 |
--------------------------------------------------------------------------------
/Ejercicios/Laberinto/laberinto/laberinto.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """Ejercicio del Algoritmo de Colonia de Hormigas
5 |
6 | Taller de la PyConEs 2015: Simplifica tu vida con sistemas complejos y algoritmos genéticos
7 |
8 | Este script contiene las funciones y clases necesarias para el ejercicio del laberinto.
9 |
10 |
11 | Este script usa arrays de numpy, aunque no debería ser difícil para alguien con experiencia
12 | sustituírlos por otras estructuras si es necesario.
13 |
14 | También usa la librería Matplotlib en las funciones que dibujan resultados."""
15 |
16 |
17 | import numpy as np
18 | import matplotlib.pyplot as plt
19 | try:
20 | import laberinto.algen as ag
21 | except:
22 | try:
23 | import Ejercicios.Laberinto.laberinto.algen as ag
24 | except:
25 | print('Problema al cargar el módulo')
26 |
27 | #Primero vamos a definir los dos tipos de objeto que necesitamos:
28 | class Map():
29 | def __init__(self, max_steps = 50, veneno = 0) :
30 |
31 | self.max_steps = max_steps
32 | self.veneno = veneno
33 | self.list_caminos = []
34 | self.history = []
35 | self.bestpath = None
36 | self.bestscore = -10E8
37 | self.dict_genes = {}
38 | for i in range (10):
39 | for j in range (10):
40 | self.dict_genes[i,j] = 2
41 | #------ -- Matrices del Mapa: ---------
42 | #Esta matriz describe las fronteras de cada casilla con cada número en binario:
43 | #primer dígito binario: frontera superior
44 | #segundo dígito binario: frontera derecha
45 | #tercer dígito binario: frontera inferior
46 | #cuarto dígito binario: frontera izquierda
47 | #Para cada dígito: 1 = frontera cerrada, 0 = frontera abierta
48 | self.grid = np.array([
49 | [9, 3, 9, 5, 5, 5, 5, 5, 5, 3 ],
50 | [10, 12, 6, 9, 1, 5, 5, 3, 9, 6 ],
51 | [10, 9, 3, 10, 10, 9, 3, 10, 10, 11],
52 | [10, 10, 10, 10, 10, 12, 6, 10, 8, 2 ],
53 | [8, 2, 8, 6, 12, 5, 5, 6, 10, 12],
54 | [10, 10, 12, 1, 5, 5, 1, 3, 12, 3 ],
55 | [10, 12, 3, 10, 9, 5, 6, 12, 3, 10],
56 | [8, 5, 6, 10, 10, 9, 3, 11, 10, 10],
57 | [10, 9, 3, 10, 12, 6, 12, 2, 10, 10],
58 | [12, 6, 12, 4, 5, 5, 5, 6, 12, 6 ]
59 | ])
60 |
61 | #Esta matriz simplemente es un damero que sirve para pintarlo bonito
62 | self.back = np.zeros([10,10])
63 | self.back[::2, ::2] = 1
64 | self.back[1::2, 1::2] = 1
65 | #En esta matriz guardaremos feromonas:
66 | self.feromap = np.zeros((10,10))
67 |
68 | def draw_tablero(self):
69 | '''Dibuja el laberinto'''
70 | plt.figure(1, figsize=(10,10))
71 | plt.matshow(self.back, fignum= 1, cmap=plt.cm.Oranges, alpha = 0.4)
72 | #plt.contourf(xx, yy, back, np.linspace(-1, 2, 3), cmap=plt.cm.Blues)
73 | plt.xlim(-1,10)
74 | plt.ylim(-1,10)
75 | x = list(range(10))
76 | y = x
77 | for i in x:
78 | for j in y:
79 | if self.grid[j,i] & 1 :
80 | xx = i + np.array([-0.5, 0.5])
81 | yy = j + np.array([-0.5,-0.5])
82 | plt.plot(xx,yy, 'k', linewidth=3)
83 | if self.grid[j,i] & 2 :
84 | xx = i + np.array([ 0.5, 0.5])
85 | yy = j + np.array([-0.5, 0.5])
86 | plt.plot(xx,yy, 'k', linewidth=3)
87 | if self.grid[j,i] & 4 :
88 | xx = i + np.array([-0.5, 0.5])
89 | yy = j + np.array([ 0.5, 0.5])
90 | plt.plot(xx,yy, 'k', linewidth=3)
91 | if self.grid[j,i] & 8 :
92 | xx = i + np.array([-0.5,-0.5])
93 | yy = j + np.array([-0.5, 0.5])
94 | plt.plot(xx,yy, 'k', linewidth=3)
95 | plt.gca().invert_yaxis()
96 | def create_camino(self):
97 | '''Crea un nuevo camino aleatorio'''
98 | self.list_caminos.append(Camino(False, [self]))
99 |
100 | def statistics(self):
101 | '''Analiza los valores de la puntuación de la población'''
102 | scores = []
103 | for j in self.list_caminos :
104 | scores.append(j.fitness)
105 | if j.fitness > self.bestscore:
106 | self.bestscore = j.fitness
107 | self.bestpath = j
108 | self.history.append([min(scores), sum(scores)/len(scores), max(scores)])
109 |
110 | def draw_history(self):
111 | '''Dibuja las gráficas de evolución de la puntuación'''
112 | plt.figure(None, figsize=(10, 8))
113 | history = np.array(self.history)
114 | for i in range(3):
115 | plt.plot(history[:, i])
116 | plt.title('Puntuación máxima, media y mínima para cada generación')
117 |
118 | def draw_best(self):
119 | '''Dibuja el mejor camino encontrado.
120 | Es necesario pintar el tablero por separado'''
121 | self.bestpath.draw_path(alpha = 0.5, c = 'b', w = 4)
122 |
123 | def draw_poison(self):
124 | '''Dibuja las toxinas o feromonas del mapa.
125 | Es necesario pintar el tablero por separado'''
126 | if self.veneno != 0:
127 | maxpoison = np.max(self.feromap)
128 | for i in range(10):
129 | for j in range(10):
130 | poison = 0.8 * self.feromap[j,i] / maxpoison
131 | plt.plot(i , j, 'o', color = 'g', alpha = poison, markersize=40)
132 |
133 | def reload_poison(self):
134 | '''Actualiza las feromonas y el valor de la aptitud de las soluciones'''
135 | self.bestpath = None
136 | self.bestscore = -10E8
137 | self.feromap /=2
138 | for i in self.list_caminos:
139 | i.deploy_poison()
140 | for i in self.list_caminos:
141 | calculate_fitness(i)
142 |
143 |
144 | class Camino():
145 | '''Este objeto contiene una disposición dada de direcciones sobre el mapa,
146 | con la que se puede construir un camino'''
147 | def __init__(self, genome = False, opciones = False):
148 | self.poison = 0
149 | if not opciones:
150 | self.mapa = None
151 | else:
152 | self.mapa = opciones[0]
153 | self.dict_genes = {}
154 | for i in range (10):
155 | for j in range (10):
156 | self.dict_genes[i,j] = 2
157 |
158 | if not genome:
159 | self.genome = np.random.randint(0,2,200)
160 | else:
161 | self.genome = genome
162 |
163 |
164 | def draw_directions(self):
165 | '''Dibuja el tablero y a continuación, dibuja sobre él
166 | el mapa de direcciones'''
167 | self.mapa.draw_tablero()
168 | x = list(range(10))
169 | y = x
170 | for i in x:
171 | for j in y:
172 |
173 | if self.directions[j ,i] == 0:
174 | plt.arrow(i, j + 0.4, 0, -0.6, head_width=0.1, head_length=0.2, fc='b', ec='b')
175 | if self.directions[j ,i] == 1:
176 | plt.arrow(i - 0.4, j, 0.6, 0, head_width=0.1, head_length=0.2, fc='b', ec='b')
177 | if self.directions[j ,i] == 2:
178 | plt.arrow(i, j - 0.4, 0, 0.6, head_width=0.1, head_length=0.2, fc='b', ec='b')
179 | if self.directions[j ,i] == 3:
180 | plt.arrow(i + 0.4, j, -0.6, 0, head_width=0.1, head_length=0.2, fc='b', ec='b')
181 |
182 | #-- Funciones para calcular el camino
183 |
184 | def move(self, row, col, direction):
185 | '''Intenta moverse a la siguiente casilla'''
186 | grid = self.mapa.grid
187 | d = 2 ** direction
188 |
189 | if not grid[row, col] & d:
190 |
191 | if direction == 0:
192 | return row -1, col
193 | elif direction == 1:
194 | return row , col+1
195 | elif direction == 2:
196 | return row +1, col
197 | elif direction == 3:
198 | return row , col-1
199 | else:
200 | return None
201 |
202 | def step(self, row, col, direction, path):
203 | '''Intenta moverse a la siguiente casilla, si no lo consigue
204 | (porque choca con una pared), intenta moverse en otra dirección.
205 | Si la segunda vez tampoco lo consigue, se queda quieto.
206 |
207 | Devuelve información sobre si ha chocado o si ha vuelto a la casilla en la que estaba
208 | en el paso anterior'''
209 | wall = False
210 | u_turn = False
211 | newpos = self.move(row, col, direction)
212 | if newpos == None:
213 | wall = True
214 | new_d = np.random.randint(0,4)
215 | newpos = self.move(row, col, new_d)
216 |
217 | if newpos != None and 0<= col <=9:
218 | row,col = newpos
219 | if len(path) >=2 and [row, col] == path[-2]:
220 | u_turn = True
221 | return row, col, wall, u_turn
222 |
223 | def get_path(self):
224 | '''Calcula el camino a partir del mapa de direcciones'''
225 |
226 | max_steps = self.mapa.max_steps
227 | path = [[4,0]]
228 | wall_count = 0
229 | u_turn_count = 0
230 | for nstep in range(max_steps):
231 | #print('step:', nstep, end=' ')
232 | row, col = path[nstep]
233 | row, col, wall, u_turn = self.step(row, col, self.directions[row, col], path)
234 | wall_count += wall
235 | u_turn_count += u_turn
236 | path.append([row, col])
237 | if [row,col] == [4, 10]:
238 | break
239 |
240 | self.path, self.wall_count, self.u_turn_count = np.array(path), wall_count, u_turn_count
241 | def deploy_poison(self):
242 | '''Deposita feromonas negativas en las casillas que ha visitado'''
243 | if self.mapa.veneno != 0 :
244 | for i in range(self.path.shape[0]):
245 | row = self.path[i, 0]
246 | col = self.path[i, 1]
247 | if col < 10:
248 | self.poison += self.mapa.feromap[row,col]
249 | self.mapa.feromap[row,col] += 0.1 * self.mapa.veneno
250 |
251 | def draw_path(self, alpha = 0.5, c = 'r', w = 8):
252 | '''Dibuja su camino sobre el mapa.
253 | Es necesario pintar el tablero por separado'''
254 | plt.plot(self.path[:,1], self.path[:,0], c, linewidth = w, alpha = alpha)
255 |
256 |
257 |
258 | def calculate_performances(individual):
259 | '''Calcula las performances de un individuo:
260 | En este caso, el camino a partir del mapa de direcciones.
261 | En este paso también se depositan las feromonas'''
262 |
263 | individual.directions = np.zeros([10,10], dtype=np.int)
264 | for i in range (10):
265 | for j in range (10):
266 | individual.directions[i,j] = individual.traits[(i,j)]
267 | individual.get_path()
268 | individual.deploy_poison()
269 |
270 | def calculate_fitness(individual):
271 | '''Calcula la aptitud de un individuo'''
272 | path, wall_count, u_turn_count = individual.path, individual.wall_count, individual.u_turn_count
273 | poison = individual.poison
274 | max_steps = individual.mapa.max_steps
275 | endx = path[-1,1]
276 | victory = max_steps + 1 - len(path) # >0 si ha llegado al final, mayor cuanto más corto sea el camino
277 | individual.fitness = endx * 4 - 2 * wall_count - 3 * u_turn_count - 0.03 * poison + victory * 5
278 |
279 | def avanzar(mapa, n = 100,
280 | max_pop = 100, min_pop = 10,
281 | reproduction_rate = 8, mutation_rate = 0.05):
282 | '''Efectua una cantidad n de generaciones '''
283 | for i in range(n):
284 | print(i+1, end='·')
285 | ag.immigration(mapa.list_caminos, max_pop,
286 | calculate_performances, calculate_fitness,
287 | mapa.dict_genes, Camino, mapa)
288 | ag.tournament(mapa.list_caminos, min_pop)
289 | mapa.reload_poison()
290 | ag.crossover(mapa.list_caminos, reproduction_rate, mutation_rate,
291 | calculate_performances, calculate_fitness,
292 | mapa.dict_genes, Camino, mapa)
293 | mapa.statistics()
294 |
295 | def draw_all(mapa):
296 | '''Dibuja el mapa con todas las soluciones de la generación actual,
297 | las feromonas del mapa, y gráficas de evolución.'''
298 | mapa.bestpath.draw_directions()
299 | mapa.draw_poison()
300 | for x in mapa.list_caminos:
301 | x.draw_path(0.1)
302 |
303 | mapa.draw_best()
304 | mapa.draw_history()
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
--------------------------------------------------------------------------------
/Ejercicios/Hormiguero/Hormiguero.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | ""
8 | ]
9 | },
10 | {
11 | "cell_type": "markdown",
12 | "metadata": {},
13 | "source": [
14 | "#Algoritmo de Optimización por Colonia de Hormigas (ACO)"
15 | ]
16 | },
17 | {
18 | "cell_type": "markdown",
19 | "metadata": {},
20 | "source": [
21 | "Como vimos en la parte de teoría, el problema del viajante es un problema clásico:\n",
22 | "\n",
23 | "Imaginemos una distribución de ciudades en un mapa. Somos un vendedor que quiere visitarlas todas, sólo una vez cada una, gastando el menor combustible posible. \n",
24 | "\n",
25 | "El algoritmo se basa en varias generaciones sucesivas de hormigas que recorren el mapa viajando de ciudad en ciudad, eligiendo su siguiente ciudad de manera aletoria hasta que las han recorrido todas. En cada etapa del viaje, las hormigas eligen moverse de una ciudad a otra teniendo en cuenta las siguientes reglas:\n",
26 | "\n",
27 | "1. Debe visitar cada ciudad exactamente una vez, excepto la inicial en la que estará dos veces (salida y llegada final);\n",
28 | "2. Una ciudad distante tiene menor posibilidad de ser elegida (Visibilidad);\n",
29 | "3. Cuanto más intenso es el rastro de feromonas de una arista entre dos ciudades, mayor es la probabilidad de que esa arista sea elegida;\n",
30 | "4. Después de haber completado su recorrido, la hormiga deposita feromonas en todas las aristas visitadas, mayor cantidad cuanto más pequeña es la distancia total recorrida;\n",
31 | "5. Después de cada generación, algunas feromonas son evaporadas.\n",
32 | "\n"
33 | ]
34 | },
35 | {
36 | "cell_type": "code",
37 | "execution_count": null,
38 | "metadata": {
39 | "collapsed": false
40 | },
41 | "outputs": [],
42 | "source": [
43 | "#Comencemos importando los paquetes necesarios:\n",
44 | "\n",
45 | "%matplotlib inline \n",
46 | "import numpy as np # Usaremos arrays\n",
47 | "import matplotlib.pyplot as plt # Para pintar resultados\n",
48 | "import ants as ants # Aquí están los objetos del algoritmo"
49 | ]
50 | },
51 | {
52 | "cell_type": "markdown",
53 | "metadata": {},
54 | "source": [
55 | "Lo primero que vamos a hacer es crear un mapa que contenga nuestras ciudades. Como primer argumento le pasamos el número de ciudades, y como segundo, el tamaño del mapa. Al ser creado, el mapa generará automáticamente las ciudades en posiciones aleatorias."
56 | ]
57 | },
58 | {
59 | "cell_type": "code",
60 | "execution_count": null,
61 | "metadata": {
62 | "collapsed": false
63 | },
64 | "outputs": [],
65 | "source": [
66 | "map1 = ants.Mapa(10)"
67 | ]
68 | },
69 | {
70 | "cell_type": "markdown",
71 | "metadata": {},
72 | "source": [
73 | "Podemos ver el mapa con las ciudades unidas por líneas cuyo grosor depende de la distancia entre ellas:"
74 | ]
75 | },
76 | {
77 | "cell_type": "code",
78 | "execution_count": null,
79 | "metadata": {
80 | "collapsed": false
81 | },
82 | "outputs": [],
83 | "source": [
84 | "map1.draw_distances()"
85 | ]
86 | },
87 | {
88 | "cell_type": "markdown",
89 | "metadata": {},
90 | "source": [
91 | "A continuación, lo siguiente que tenemos que hacer es crear un enjambre de hormigas. Con esta función, lo podremos hacer fácilmente."
92 | ]
93 | },
94 | {
95 | "cell_type": "code",
96 | "execution_count": null,
97 | "metadata": {
98 | "collapsed": false
99 | },
100 | "outputs": [],
101 | "source": [
102 | "map1.swarm_create(100) # Creamos un enjambre de 100 hormigas"
103 | ]
104 | },
105 | {
106 | "cell_type": "markdown",
107 | "metadata": {},
108 | "source": [
109 | "Si queremos ver dónde se encuentran nuestras hormigas en un momento dado, podemos hacerlo:"
110 | ]
111 | },
112 | {
113 | "cell_type": "code",
114 | "execution_count": null,
115 | "metadata": {
116 | "collapsed": false
117 | },
118 | "outputs": [],
119 | "source": [
120 | "map1.swarm_show()"
121 | ]
122 | },
123 | {
124 | "cell_type": "markdown",
125 | "metadata": {},
126 | "source": [
127 | "Podemos comprobar fácilmente que la matriz de distancias es simétrica, y que la matriz de feromonas aún está vacía:"
128 | ]
129 | },
130 | {
131 | "cell_type": "code",
132 | "execution_count": null,
133 | "metadata": {
134 | "collapsed": false
135 | },
136 | "outputs": [],
137 | "source": [
138 | "map1.show_distances_matrix()\n",
139 | "map1.show_feromones_matrix()"
140 | ]
141 | },
142 | {
143 | "cell_type": "markdown",
144 | "metadata": {},
145 | "source": [
146 | "Empecemos a mover a nuestras hormigas!\n",
147 | "\n",
148 | "Para que la primera generación de hormigas recorra el mapa llamaremos a la función swarm_generation():"
149 | ]
150 | },
151 | {
152 | "cell_type": "code",
153 | "execution_count": null,
154 | "metadata": {
155 | "collapsed": true
156 | },
157 | "outputs": [],
158 | "source": [
159 | "map1.swarm_generation()"
160 | ]
161 | },
162 | {
163 | "cell_type": "markdown",
164 | "metadata": {},
165 | "source": [
166 | "Veamos cómo ha cambiado las feromonas!"
167 | ]
168 | },
169 | {
170 | "cell_type": "code",
171 | "execution_count": null,
172 | "metadata": {
173 | "collapsed": false
174 | },
175 | "outputs": [],
176 | "source": [
177 | "map1.show_feromones_matrix()\n",
178 | "map1.draw_feromones()"
179 | ]
180 | },
181 | {
182 | "cell_type": "markdown",
183 | "metadata": {},
184 | "source": [
185 | "Para poder encontrar una buena ruta, necesitaremos que pasen unas cuantras generaciones más... Pongamos que 50"
186 | ]
187 | },
188 | {
189 | "cell_type": "code",
190 | "execution_count": null,
191 | "metadata": {
192 | "collapsed": false
193 | },
194 | "outputs": [],
195 | "source": [
196 | "for i in range(50):\n",
197 | " print(i, end = '·')\n",
198 | " map1.swarm_generation()\n",
199 | "map1.show_feromones_matrix()\n",
200 | "map1.draw_feromones()"
201 | ]
202 | },
203 | {
204 | "cell_type": "markdown",
205 | "metadata": {},
206 | "source": [
207 | "Parece que las hormigas ya tienen claros sus caminos favoritos!\n",
208 | "\n",
209 | "Veamos qué pinta tiene el mejor camino que han encontrado:"
210 | ]
211 | },
212 | {
213 | "cell_type": "code",
214 | "execution_count": null,
215 | "metadata": {
216 | "collapsed": false
217 | },
218 | "outputs": [],
219 | "source": [
220 | "map1.draw_best_path()"
221 | ]
222 | },
223 | {
224 | "cell_type": "markdown",
225 | "metadata": {},
226 | "source": [
227 | "Podemos borrar las feromonas y empezar de nuevo el algoritmo, para ver si siempre llegan a la misma solución. No te preocupes, al algoritmo no borrará la mejor ruta encontrada."
228 | ]
229 | },
230 | {
231 | "cell_type": "code",
232 | "execution_count": null,
233 | "metadata": {
234 | "collapsed": false,
235 | "scrolled": false
236 | },
237 | "outputs": [],
238 | "source": [
239 | "for j in range(3):\n",
240 | " map1.feromone_reset()\n",
241 | " print()\n",
242 | " print('Ejecución', j+1, ', generación: ')\n",
243 | " for i in range(50):\n",
244 | " print(i+1, end = '·')\n",
245 | " map1.swarm_generation()\n",
246 | " map1.draw_feromones()"
247 | ]
248 | },
249 | {
250 | "cell_type": "markdown",
251 | "metadata": {},
252 | "source": [
253 | "El algoritmo podría haber encontrado una ruta mejor, comprobémoslo:"
254 | ]
255 | },
256 | {
257 | "cell_type": "code",
258 | "execution_count": null,
259 | "metadata": {
260 | "collapsed": false
261 | },
262 | "outputs": [],
263 | "source": [
264 | "map1.draw_best_path()"
265 | ]
266 | },
267 | {
268 | "cell_type": "markdown",
269 | "metadata": {},
270 | "source": [
271 | "Podemos observar cómo han ido variando las longitudes máxima, mínima y media de los caminos de las hormigas en cada generación, y compararlas con la del mejor camino encontrado:"
272 | ]
273 | },
274 | {
275 | "cell_type": "code",
276 | "execution_count": null,
277 | "metadata": {
278 | "collapsed": false,
279 | "scrolled": true
280 | },
281 | "outputs": [],
282 | "source": [
283 | "map1.draw_results()"
284 | ]
285 | },
286 | {
287 | "cell_type": "markdown",
288 | "metadata": {},
289 | "source": [
290 | "También podemos dibujar las longitudes mínimas de cada ejecución del algoritmo:"
291 | ]
292 | },
293 | {
294 | "cell_type": "code",
295 | "execution_count": null,
296 | "metadata": {
297 | "collapsed": false
298 | },
299 | "outputs": [],
300 | "source": [
301 | "map1.draw_best_results()"
302 | ]
303 | },
304 | {
305 | "cell_type": "markdown",
306 | "metadata": {},
307 | "source": [
308 | "##Ajuste fino de las Feromonas"
309 | ]
310 | },
311 | {
312 | "cell_type": "markdown",
313 | "metadata": {},
314 | "source": [
315 | "Supongamos ahora que queremos optimizar una ruta entre 40 ciudades:"
316 | ]
317 | },
318 | {
319 | "cell_type": "code",
320 | "execution_count": null,
321 | "metadata": {
322 | "collapsed": false
323 | },
324 | "outputs": [],
325 | "source": [
326 | "map2 = ants.Mapa(40)\n",
327 | "map2.swarm_create(200)\n",
328 | "map2.swarm_generation()\n",
329 | "map2.show_feromones_matrix()\n",
330 | "map2.draw_feromones()"
331 | ]
332 | },
333 | {
334 | "cell_type": "markdown",
335 | "metadata": {},
336 | "source": [
337 | "¿Qué pasa? ¡No hay feromonas!\n",
338 | "\n",
339 | "La solución es muy simple: las hormigas \"standard\" están adaptadas a mapas más pequeños, y no dejan tras de sí suficiente feromona como para que no se evapore toda. \n",
340 | "\n",
341 | "Para solucionarlo, podemos modificar a nuestras hormigas para este entorno:"
342 | ]
343 | },
344 | {
345 | "cell_type": "code",
346 | "execution_count": null,
347 | "metadata": {
348 | "collapsed": false
349 | },
350 | "outputs": [],
351 | "source": [
352 | "#Con un valor de 5 es suficiente\n",
353 | "map2.feromone_fine_tune()"
354 | ]
355 | },
356 | {
357 | "cell_type": "code",
358 | "execution_count": null,
359 | "metadata": {
360 | "collapsed": false
361 | },
362 | "outputs": [],
363 | "source": [
364 | "map2.swarm_generation()\n",
365 | "map2.show_feromones_matrix()\n",
366 | "map2.draw_feromones()"
367 | ]
368 | },
369 | {
370 | "cell_type": "code",
371 | "execution_count": null,
372 | "metadata": {
373 | "collapsed": false
374 | },
375 | "outputs": [],
376 | "source": [
377 | "for i in range(25):\n",
378 | " print(i, end = '·')\n",
379 | " map2.swarm_generation()\n",
380 | "map2.show_feromones_matrix()\n",
381 | "map2.draw_feromones()\n",
382 | "map2.draw_best_path()\n",
383 | "map2.draw_results()"
384 | ]
385 | },
386 | {
387 | "cell_type": "markdown",
388 | "metadata": {
389 | "collapsed": true
390 | },
391 | "source": [
392 | "## Te toca trabajar"
393 | ]
394 | },
395 | {
396 | "cell_type": "markdown",
397 | "metadata": {
398 | "collapsed": true
399 | },
400 | "source": [
401 | "Ahora que ya hemos visto cómo funciona este algoritmo, probemos a cambiarlo. ¿Cómo funcionaría si no todas las ciudades estuvieran conectadas entre sí? Intenta cambiar el algoritmo para modelar este caso. Probablemente la manera más fácil sea modificar la matriz de distancias, hinchando el valor de algunas distancias por encima de su valor real para penalizar ciertas rutas. ¿Qué pasaría si la matriz se quedara asimétrica?\n",
402 | "\n",
403 | "Una vez que lo hayas conseguido, te propongo el problema de enrutación de paquetes: En vez de recorrer todo el mapa, intenta ir de un nodo dado a otro. Puedes añadir feromonas atractivas en ciertos nodos para representar que son nodos de la red que están sobredimensionados, o crear feromonas negativas y esparcirlos por otros nodos para representar enlaces que están sobrecargados y es mejor evitar. Recuerda que las feromonas se disipan un poco cada generación, para efectos duraderos deberas añadirlas también cada turno."
404 | ]
405 | },
406 | {
407 | "cell_type": "code",
408 | "execution_count": null,
409 | "metadata": {
410 | "collapsed": true
411 | },
412 | "outputs": [],
413 | "source": []
414 | },
415 | {
416 | "cell_type": "code",
417 | "execution_count": null,
418 | "metadata": {
419 | "collapsed": true
420 | },
421 | "outputs": [],
422 | "source": []
423 | },
424 | {
425 | "cell_type": "code",
426 | "execution_count": null,
427 | "metadata": {
428 | "collapsed": true
429 | },
430 | "outputs": [],
431 | "source": []
432 | },
433 | {
434 | "cell_type": "markdown",
435 | "metadata": {},
436 | "source": [
437 | "Siro Moreno, Aeropython, 8 de Noviembre de 2015\n"
438 | ]
439 | },
440 | {
441 | "cell_type": "code",
442 | "execution_count": null,
443 | "metadata": {
444 | "collapsed": true
445 | },
446 | "outputs": [],
447 | "source": []
448 | }
449 | ],
450 | "metadata": {
451 | "kernelspec": {
452 | "display_name": "Python 3",
453 | "language": "python",
454 | "name": "python3"
455 | },
456 | "language_info": {
457 | "codemirror_mode": {
458 | "name": "ipython",
459 | "version": 3
460 | },
461 | "file_extension": ".py",
462 | "mimetype": "text/x-python",
463 | "name": "python",
464 | "nbconvert_exporter": "python",
465 | "pygments_lexer": "ipython3",
466 | "version": "3.4.3"
467 | }
468 | },
469 | "nbformat": 4,
470 | "nbformat_minor": 0
471 | }
472 |
--------------------------------------------------------------------------------
/Ejercicios/El vecindario racista/El vecindario racista.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | ""
8 | ]
9 | },
10 | {
11 | "cell_type": "markdown",
12 | "metadata": {},
13 | "source": [
14 | "#El vecindario racista: el modelo de segregación de Schelling "
15 | ]
16 | },
17 | {
18 | "cell_type": "markdown",
19 | "metadata": {
20 | "collapsed": true
21 | },
22 | "source": [
23 | "La segregación racial es un problema en muchas partes del mundo desde hace mucho tiempo. A pesar de que ciertos colectivos han realizado un gran esfuerzo por solucionarlo, muchos países continúan segregados por razones étnicas, de credo, sexo, riqueza, etc. ¿Por qué es un problema tan complicado de resolver?\n",
24 | "\n",
25 | "En 1971, el economista americano Thomas Schelling creó un modelo basado en agentes que podría ayudar a explicar por qué la segregación es un problema tan complicado de combatir. Su modelo de segregación mostraba que individuos o \"agentes\" que no eran especialmente rigurosos respecto a su entorno tendían aún así a segregarse con el tiempo. A pesar de que el modelo es especialmente simple, permite una interesante perspectiva sobre cómo los individuos pueden tender a segregarse, a pesar de no tener un especial deseo por hacerlo.\n",
26 | "\n",
27 | "(Traducción de http://nifty.stanford.edu/2014/mccown-schelling-model-segregation/ )\n",
28 | "\n",
29 | "(Enlace al paper original de Schelling para Harvard: http://www.stat.berkeley.edu/~aldous/157/Papers/Schelling_Seg_Models.pdf )"
30 | ]
31 | },
32 | {
33 | "cell_type": "markdown",
34 | "metadata": {},
35 | "source": [
36 | "##Planteamiento"
37 | ]
38 | },
39 | {
40 | "cell_type": "markdown",
41 | "metadata": {},
42 | "source": [
43 | "Para este ejercicio usaremos numpy, matplotlib y el código que está en la carpeta vecindario."
44 | ]
45 | },
46 | {
47 | "cell_type": "code",
48 | "execution_count": 1,
49 | "metadata": {
50 | "collapsed": true
51 | },
52 | "outputs": [],
53 | "source": [
54 | "%matplotlib inline\n",
55 | "import numpy as np\n",
56 | "import matplotlib.pyplot as plt\n",
57 | "import vecindario as vc"
58 | ]
59 | },
60 | {
61 | "cell_type": "markdown",
62 | "metadata": {
63 | "collapsed": true
64 | },
65 | "source": [
66 | "Supongamos que tenemos un vecindario. Este vecindario es una matriz o casillero, en el que cada vecino puede ocupar una casilla."
67 | ]
68 | },
69 | {
70 | "cell_type": "code",
71 | "execution_count": null,
72 | "metadata": {
73 | "collapsed": false
74 | },
75 | "outputs": [],
76 | "source": [
77 | "mundo, colores = vc.crear_mundo()\n",
78 | "vc.vecin_print(mundo, colores, 10, 0)"
79 | ]
80 | },
81 | {
82 | "cell_type": "markdown",
83 | "metadata": {},
84 | "source": [
85 | "Aquí podemos ver un pequeño vecindario, con vecinos azules y rojos. También hay espacios libres que no están ocupados por nadie.\n",
86 | "\n",
87 | "Cada vecino se ve afectado por las 8 casillas que tiene a su alrededor. En principio, no le molesta la presencia de vecinos de un color diferente, pero si la proporción de vecinos de su mismo color es de sólo 1/3 o menos, se sentirá incomodado y deseará marcharse.\n",
88 | "\n",
89 | "En el gráfico superior, los vecinos incomodados tienen unas esquinas grises, mientras que los vecinos confortables no.\n",
90 | "\n",
91 | "Estos vecinos que se sienten incómodos se mudarán en cuanto puedan. Para representar esto, repasaremos la lista de vecinos, detectando a los que quieren mudarse, y cambiarán de sitio a una casilla nueva aleatoria que esté vacía.\n",
92 | "\n",
93 | "Esto representa un 'step'."
94 | ]
95 | },
96 | {
97 | "cell_type": "code",
98 | "execution_count": null,
99 | "metadata": {
100 | "collapsed": false
101 | },
102 | "outputs": [],
103 | "source": [
104 | "n = vc.step_mudanza(mundo, colores, 1, 10)"
105 | ]
106 | },
107 | {
108 | "cell_type": "markdown",
109 | "metadata": {},
110 | "source": [
111 | "Podemos comprobar que aunque los individuos no prefieren ningún tipo de segregación, y su única condición es que al menos 1/3 de sus vecinos sean del mismo color que ellos, al cabo de unos pocos steps la segregación en grupos homogéneos ha aparecido como propiedad emergente."
112 | ]
113 | },
114 | {
115 | "cell_type": "code",
116 | "execution_count": null,
117 | "metadata": {
118 | "collapsed": false
119 | },
120 | "outputs": [],
121 | "source": [
122 | "n = vc.step_multiple(mundo, colores, n, 10)"
123 | ]
124 | },
125 | {
126 | "cell_type": "markdown",
127 | "metadata": {},
128 | "source": [
129 | "Cómo podemos evitar esta situación?\n",
130 | "\n",
131 | "Existe una sencilla manera de evitarlo: que los individuos activamente trabajen para evitar la segregación. La manera de implementar esto es muy sencilla: basta con que también se sientan incómodos si más del 90% de sus vecinos son iguales a ellos para obtener cambios sustanciales en la segregación del grupo."
132 | ]
133 | },
134 | {
135 | "cell_type": "code",
136 | "execution_count": null,
137 | "metadata": {
138 | "collapsed": false
139 | },
140 | "outputs": [],
141 | "source": [
142 | "mundo, colores = vc.crear_mundo(intom = 90)\n",
143 | "n = vc.step_multiple(mundo, colores, 0, 10, numsteps=100)"
144 | ]
145 | },
146 | {
147 | "cell_type": "markdown",
148 | "metadata": {},
149 | "source": [
150 | "##Extendiendo el algoritmo"
151 | ]
152 | },
153 | {
154 | "cell_type": "markdown",
155 | "metadata": {
156 | "collapsed": true
157 | },
158 | "source": [
159 | "Ahora que ya hemos visto cómo trabaja este modelo, vamos a jugar un poco con sus parámetros. ¿Te atreves a predecir cómo influirán estos números en un vecindario de tres colores?"
160 | ]
161 | },
162 | {
163 | "cell_type": "code",
164 | "execution_count": null,
165 | "metadata": {
166 | "collapsed": true
167 | },
168 | "outputs": [],
169 | "source": [
170 | "dim = 50 #Tamaño del lado de la matriz\n",
171 | "vacios = 10 #Porcentaje de huecos\n",
172 | "colors = 3 #Número de colores\n",
173 | "prop = [0.33, 0.33]\n",
174 | "n = 0\n",
175 | "intolerance = 33 #Porcentaje mínimo de vecinos iguales\n",
176 | "intom = 100 #Porcentaje máximo de vecinos iguales\n",
177 | "rad = 1 # Radio en el que el color de los vecinos se comprueba \n",
178 | "historia_felicidad = [] # Aquí vamos a almacenar la felicidad del grupo en cada step\n",
179 | "historia_segregacion = []# Y aquí, el valor de la segregación."
180 | ]
181 | },
182 | {
183 | "cell_type": "code",
184 | "execution_count": null,
185 | "metadata": {
186 | "collapsed": false
187 | },
188 | "outputs": [],
189 | "source": [
190 | "#Veamos cómo comienza la simulación, con los vecinos repartidos aleatoriamente.\n",
191 | "par, datacolor = vc.crear_mundo(dim, colors= colors, prop= prop, vacios = vacios,\n",
192 | " intolerance=intolerance, intom= intom, rad = rad,\n",
193 | " h_fel = historia_felicidad, h_seg = historia_segregacion)\n",
194 | "\n",
195 | "vc.vecin_print(par, datacolor, dim, n)"
196 | ]
197 | },
198 | {
199 | "cell_type": "code",
200 | "execution_count": null,
201 | "metadata": {
202 | "collapsed": false
203 | },
204 | "outputs": [],
205 | "source": [
206 | "#Qué pasará dentro de 50 steps?\n",
207 | "n = vc.step_multiple(par, datacolor, n, dim,\n",
208 | " historia_felicidad, historia_segregacion, 50)"
209 | ]
210 | },
211 | {
212 | "cell_type": "markdown",
213 | "metadata": {},
214 | "source": [
215 | "¡Sorpresa! ¿Esperabas esto?\n",
216 | "\n",
217 | "También podemos observar cómo han ido evolucionando:"
218 | ]
219 | },
220 | {
221 | "cell_type": "code",
222 | "execution_count": null,
223 | "metadata": {
224 | "collapsed": false
225 | },
226 | "outputs": [],
227 | "source": [
228 | "vc.evolucion(historia_felicidad, historia_segregacion)"
229 | ]
230 | },
231 | {
232 | "cell_type": "markdown",
233 | "metadata": {},
234 | "source": [
235 | "¿Qué crees que pasará en un vecindario igual pero más concienciado?"
236 | ]
237 | },
238 | {
239 | "cell_type": "code",
240 | "execution_count": null,
241 | "metadata": {
242 | "collapsed": true
243 | },
244 | "outputs": [],
245 | "source": [
246 | "intom = 90 #Porcentaje máximo de vecinos iguales\n",
247 | "n = 0 #Vamos a comenzar de 0\n",
248 | "historia_felicidad = [] # Aquí vamos a almacenar la felicidad del grupo en cada step\n",
249 | "historia_segregacion = []# Y aquí, el valor de la segregación."
250 | ]
251 | },
252 | {
253 | "cell_type": "code",
254 | "execution_count": null,
255 | "metadata": {
256 | "collapsed": false
257 | },
258 | "outputs": [],
259 | "source": [
260 | "#Veamos cómo comienza la simulación, con los vecinos repartidos aleatoriamente.\n",
261 | "par, datacolor = vc.crear_mundo(dim, colors= colors, prop= prop, vacios = vacios,\n",
262 | " intolerance=intolerance, intom= intom, rad = rad,\n",
263 | " h_fel = historia_felicidad, h_seg = historia_segregacion)\n",
264 | "\n",
265 | "vc.vecin_print(par, datacolor, dim, n)"
266 | ]
267 | },
268 | {
269 | "cell_type": "code",
270 | "execution_count": null,
271 | "metadata": {
272 | "collapsed": false
273 | },
274 | "outputs": [],
275 | "source": [
276 | "#Qué pasará dentro de 50 steps?\n",
277 | "n = vc.step_multiple(par, datacolor, n, dim,\n",
278 | " historia_felicidad, historia_segregacion, 50)"
279 | ]
280 | },
281 | {
282 | "cell_type": "code",
283 | "execution_count": null,
284 | "metadata": {
285 | "collapsed": false
286 | },
287 | "outputs": [],
288 | "source": [
289 | "vc.evolucion(historia_felicidad, historia_segregacion)"
290 | ]
291 | },
292 | {
293 | "cell_type": "markdown",
294 | "metadata": {},
295 | "source": [
296 | "Al ser un requisito más restrictivo ahora, comprobamos que el porcentaje de vecinos satisfechos aumenta más lentamente, pero sin embargo, el impacto en la segregación es muy notable.\n",
297 | "\n",
298 | "Ahora queda a tu criterio experimentar con los valores. ¡Investiga qué patrones forman las diferentes combinaciones de parámetros!"
299 | ]
300 | },
301 | {
302 | "cell_type": "markdown",
303 | "metadata": {},
304 | "source": [
305 | "##Más sobre patrones emergentes: los Patrones de Turing"
306 | ]
307 | },
308 | {
309 | "cell_type": "markdown",
310 | "metadata": {},
311 | "source": [
312 | ""
313 | ]
314 | },
315 | {
316 | "cell_type": "markdown",
317 | "metadata": {
318 | "collapsed": true
319 | },
320 | "source": [
321 | "Si te ha gustado cómo se pueden formar patrones como propiedad emergente de un sistema complejo, probablemente te interese investigar sobre los Patrones de Turing. Este tema también está relacionado con el anterior a traves de la importancia de la tolerancia y el respeto en la sociedad, ya que fue el último trabajo que Turing publicó antes de \"suicidarse\" tras ser condenado a castración química por su homosexualidad.\n",
322 | "\n",
323 | "En sus últimos años de vida, el gran matemático Alan Turing planteó la cuestión de la teoría de la morfogénesis, es decir, cómo un ser vivo cuyas células tienen todas el mismo código genético es capaz de desarrollar una forma compleja, como extremidades, dedos, etc.\n",
324 | "\n",
325 | "En el caso concreto de los patrones del color de la piel, estudió la posibilidad de que diferentes concentraciones de elementos químicos que reaccionan entre sí según fórmulas sencillas pudieran producirlos, y los resultados fueron sorprendentes.\n",
326 | "\n",
327 | "Puedes encontrar una introducción al tema en estos artículos cortos:\n",
328 | "\n",
329 | "http://francis.naukas.com/2010/09/24/alan-turing-el-genio-matematico-que-creo-la-teoria-de-la-morfogenesis-poco-antes-de-suicidarse/\n",
330 | "\n",
331 | "http://nadaesgratis.es/anxo-sanchez/turing-y-sus-patrones-el-pionero-de-la-biologia-matematica\n",
332 | "\n",
333 | "Imagen extraída de: http://francis.naukas.com/2009/08/08/generacion-de-patrones-espaciotemporales-en-nuevos-tipos-de-reacciones-quimicas/"
334 | ]
335 | },
336 | {
337 | "cell_type": "markdown",
338 | "metadata": {
339 | "collapsed": true
340 | },
341 | "source": [
342 | "##Te toca trabajar"
343 | ]
344 | },
345 | {
346 | "cell_type": "markdown",
347 | "metadata": {
348 | "collapsed": true
349 | },
350 | "source": [
351 | "Hemos visto cómo funciona la formación de patrones cuando todos los individuos tienen las mismas características, pero... ¿Cómo se formarían si no las tuvieran?\n",
352 | "\n",
353 | "Ve al código de la carpeta 'vecindario' y modifícalo de manera que cuando se crea la población, los individuos reciban una diferencia aletoria de magnitud controlada en sus niveles de intolerancia. ¡Observa cómo afecta este parámetro en la población!\n",
354 | "\n",
355 | "Estas funciones pueden serte de ayuda:\n",
356 | "\n",
357 | " np.random.rand() \n",
358 | " np.random.randn()"
359 | ]
360 | },
361 | {
362 | "cell_type": "code",
363 | "execution_count": null,
364 | "metadata": {
365 | "collapsed": true
366 | },
367 | "outputs": [],
368 | "source": []
369 | },
370 | {
371 | "cell_type": "markdown",
372 | "metadata": {},
373 | "source": [
374 | "Siro Moreno, Aeropython, 28 de Octubre de 2015"
375 | ]
376 | },
377 | {
378 | "cell_type": "markdown",
379 | "metadata": {
380 | "collapsed": true
381 | },
382 | "source": [
383 | "Basado en el ejercicio 'Segregation' del paquete Evolife del profesor Jean Louis Dessalles"
384 | ]
385 | },
386 | {
387 | "cell_type": "code",
388 | "execution_count": null,
389 | "metadata": {
390 | "collapsed": true
391 | },
392 | "outputs": [],
393 | "source": []
394 | }
395 | ],
396 | "metadata": {
397 | "kernelspec": {
398 | "display_name": "Python 3",
399 | "language": "python",
400 | "name": "python3"
401 | },
402 | "language_info": {
403 | "codemirror_mode": {
404 | "name": "ipython",
405 | "version": 3
406 | },
407 | "file_extension": ".py",
408 | "mimetype": "text/x-python",
409 | "name": "python",
410 | "nbconvert_exporter": "python",
411 | "pygments_lexer": "ipython3",
412 | "version": "3.4.3"
413 | }
414 | },
415 | "nbformat": 4,
416 | "nbformat_minor": 0
417 | }
418 |
--------------------------------------------------------------------------------
/Ejercicios/El vecindario racista/vecindario/_vecindario.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | """Ejercicio del Vecindario Racista
5 |
6 | Taller de la PyConEs 2015: Simplifica tu vida con sistemas complejos y algoritmos genéticos
7 |
8 | Este script contiene las funciones y clases necesarias para ejecutar una simulación del
9 | modelo de emergencia de segregación propuesto por Schelling.
10 |
11 | http://www.stat.berkeley.edu/~aldous/157/Papers/Schelling_Seg_Models.pdf
12 |
13 | Este script usa arrays de numpy, aunque no debería ser difícil para alguien con experiencia
14 | sustituírlos por otras estructuras si es necesario.
15 |
16 | También usa la librería Matplotlib en las funciones que dibujan resultados."""
17 |
18 |
19 | import numpy as np
20 | import matplotlib.pyplot as plt
21 |
22 | #Primero vamos a definir los dos tipos de objeto que necesitamos:
23 |
24 | class Parcela:
25 | '''Este objeto representa la finca o mundo en el que viven los vecinos
26 |
27 | Controla todas las variables globales y contiene la lista de vecinos'''
28 | def __init__(self, dim1, intolerance = 55, intomax = 100, dim2 = 0, r = 1):
29 | '''Esta función se ejecuta al crear el objeto:
30 | Asignamos a las variables locales una serie de valores que debemos pasar
31 | como argumento a la hora de crear el mundo:
32 | -Su anchura
33 | -La intolerancia de sus ciudadanos(por defecto, 55)
34 | -Su intolerancia máxima(proporción de vecinos del mismo color máximos para que no quiera mudarse)
35 | -Su altura (por defecto, igual que la anchura)
36 | -El radio de análisis de confort'''
37 | self.dim1 = dim1
38 | if dim2 == 0: #Si la altura es 0 (por defecto), la hacemos igual a la anchura
39 | self.dim2 = dim1
40 | else:
41 | self.dim2 = dim2
42 | self.r = r
43 | self.intolerance = intolerance
44 | self.intomax = intomax
45 | self.tot_pos = self.dim1 * self.dim2 #Número total de casillas
46 | self.libres = list(range(self.tot_pos))#En este momento, todas las casillas están libres
47 | self.matrix = np.zeros((self.dim2, self.dim1), dtype=np.int8)#Esta matriz representa al vecindario.
48 | #Esta matriz empieza llena de ceros (aún no hay nade viviendo), pero conforme vayamos
49 | #añadiendo vecinos, sus casillas se llenarán con el número que representa al color del ocupante.
50 | self.matrix_ampli = np.zeros((self.dim2 + 2 * r, self.dim1 + 2 * self.r), dtype=np.int8)
51 | #Esta matriz es igual que la anterior, pero con un reborde vacío alrededor que luego nos será útil al
52 | #al calcular los vecinos de las casillas que están en los bordes.
53 | self.listavecinos = []
54 | #En esta lista, que aún está vacía, iremos almacenando a los vecinos que vayamos creando. Así,
55 | #luego los podremos ir llamando fácilmente.
56 | def coord(self, num, dim1):
57 | '''Devuelve las coordenadas del elemento enésimo de la matriz'''
58 | fila = num//dim1
59 | columna = num - fila * dim1
60 | return[fila, columna]
61 | def asignar(self):
62 | '''Escoge una casilla aleatoria libre y devuelve su número. Luego, la borra de
63 | la lista de casillas libres.'''
64 | n = np.random.randint(0, len(self.libres))
65 | return self.libres.pop(n)
66 | def liberar(self, f, c):
67 | '''Libera una casilla ocupada, borrándola de las matrices y añadiéndola a la lista
68 | de casillas libres'''
69 | n = f * self.dim1 + c #Calcula el número de la casilla a partir de las coordenadas
70 | self.libres.append(n)
71 | self.matrix[f, c] = 0
72 | self.matrix_ampli[f + 1 , c + 1 ] = 0
73 | def nuevo(self, color):
74 | '''Crea un nuevo vecino y le asigna una casilla libre aleatoria'''
75 | n = self.asignar() #Escoge una casilla libre aleatoria
76 | coordenadas = self.coord(n, self.dim1)#Calcula sus coordenadas
77 | self.matrix[coordenadas[0], coordenadas[1]] = color# Asigna a las matrices el color del nuevo vecino
78 | self.matrix_ampli[coordenadas[0] + self.r , coordenadas[1] + self.r ] = color
79 | self.listavecinos.append(Vecino(color, self, coordenadas,
80 | self.intolerance, self.intomax))#Añade el nuevo a la lista de vecinos
81 | def clear(self):
82 | '''Borra a todos los vecinos y deja todo limpio y en blanco'''
83 | self.libres = list(range(self.tot_pos))
84 | self.matrix = np.zeros((self.dim2, self.dim1), dtype=np.int8)
85 | self.matrix_ampli = np.zeros((self.dim2 + 2*self.r, self.dim1 + 2*self.r), dtype=np.int8)
86 | self.listavecinos = []
87 | def entorno(self, f, c, color):
88 | '''Dada una casilla y un color, analiza el entorno de la casilla y
89 | devuelve la proporción de casillas ocupadas de ese color'''
90 | #El parámetro r de análisis se pasa de manera implícita dentro de self
91 | r = self.r
92 | vecinos_ocupados = -1 #Empiezan a -1 porque el bucle contará a la propia casilla
93 | vecinos_iguales = -1
94 | for xx in range(1 + 2 * r):
95 | for yy in range(1 + 2 * r):
96 | f_a = f + xx
97 | c_a = c + yy
98 | color_a = self.matrix_ampli.item(f_a, c_a)#Usamos la matriz ampliada para poder analizar fácilmente
99 | # las casillas que están en los bordes sin complicar la notación
100 | if color_a == 0 :
101 | pass
102 | elif color_a == color :
103 | vecinos_ocupados += 1
104 | vecinos_iguales += 1
105 | else:
106 | vecinos_ocupados += 1
107 | if vecinos_ocupados == 0 :
108 | return 1.
109 | else:
110 | return vecinos_iguales / vecinos_ocupados
111 |
112 | class Vecino:
113 | '''
114 | Este objeto representa a cada vecino del vecindario, es decir, cada circulito.
115 |
116 | Este objeto está pensado para ser llamado sólo desde el objeto "Parcela".
117 | '''
118 | def __init__(self, color, parcela, coords, intolerance = 55, intomax = 100):
119 | '''Esta función se ejecuta al crear el objeto:
120 | Asignamos a las variables locales una serie de valores que debemos pasar
121 | como argumento a la hora de crear cada ciudadano:
122 | -Su color
123 | -Su intolerancia(proporción de vecinos del mismo color mínimos para que no quiera mudarse)
124 | -Su intolerancia máxima(proporción de vecinos del mismo color máximos para que no quiera mudarse)
125 | -Desde qué parcela se le ha invocado (para luego poder usarla desde él)
126 | -Sus coordenadas'''
127 | self.color = color
128 | self.intolerance = intolerance
129 | self.intomax = intomax
130 | self.parcela = parcela
131 | self.coords = coords
132 | def mudanza(self):
133 | '''Esta función sirve para mudar el vecino desde su posición a otro sitio'''
134 | n = self.parcela.asignar() #Primero se le asigna un sitio libre de la parcela
135 | r = self.parcela.r
136 | coordenadas = self.parcela.coord(n, self.parcela.dim1)# Se calculan las coord del nuevo sitio
137 | self.parcela.matrix[coordenadas[0], coordenadas[1]] = self.color# Se cambia el color del nuevo sitio en la matriz
138 | self.parcela.matrix_ampli[coordenadas[0] + r , coordenadas[1] + r ] = self.color#Y en la matriz ampliada
139 | self.parcela.liberar(self.coords[0], self.coords[1])# Se avisa a la parcela de que el sitio viejo está libre
140 | self.coords = coordenadas# Se cambian las coordenadas que el vecino tiene guardadas a las nuevas
141 | def satisfecho(self):
142 | '''Esta función sirve para saver si el vecino está satisfecho con su entorno'''
143 | vecin_prop = self.parcela.entorno(self.coords[0], self.coords[1], self.color)#Primero, en
144 | #la parcela se calcula qué porcentaje de sus vecinos son del mismo color que él
145 | suficientes_iguales = vecin_prop * 100 >= self.intolerance # Se compara con la intolerancia,
146 | #si la proporción es mayor que su intol, está a gusto (True), si no, (False)
147 | no_demasiados_iguales = vecin_prop * 100 <= self.intomax # También se compara con la intol máxima,
148 | #si la proporción de gente igual alrededor es damasiado alta, tampoco está a gusto
149 |
150 | return suficientes_iguales and no_demasiados_iguales, vecin_prop
151 |
152 |
153 | #Sólo con estos objetos ya podemos ejecutar nuestras simulaciones, pero
154 | #nos será mucho más fácil si definimos unas cuantas funciones útiles:
155 |
156 | def crear_mundo(tam = 10, colors = 2, vacios = 20, intolerance = 30, intom = 100,
157 | prop = [0.5], rad = 1, h_fel = [], h_seg = []):
158 | '''Crea un nuevo espacio de análisis, con una matriz cuadrada de base.
159 | Variables:
160 | -tam: número de casillas del lado de la matriz.
161 | -colors: número de colores.
162 | -vacios: porcentaje de plazas vacías que quedarán en la matriz.
163 | -intolerance: porcentaje mínimo de vecinos adyacentes de su mismo color que admite
164 | un vecino antes de cambiar su posición.
165 | -intom: porcentaje máximo de vecinos adyacentes del mismo color que admite antes de mudarse.
166 | -prop: Proporción de cada color, es una lista con tantos elementos como colores menos uno.
167 | -rad: radio de análisis cuando se observan los vecinos de alguien.
168 | -h_fel: Aquí se irán guardando los valores de felicidad.
169 | -h_seg: Aquí, los de segregación.'''
170 |
171 | par = Parcela(tam, intolerance = intolerance, intomax = intom, r=rad) #Se crea una parcela nueva
172 | size = par.tot_pos
173 |
174 | llenos = (size * (100 - vacios))//100 #Se calculan cuantas casillas deben tener ocupantes.
175 | asignados = 0
176 |
177 |
178 | #A continuación, iremos añadiendo vecinos en la proporción deseada
179 | for color in range(1, colors):
180 | asignados_al_final = asignados + int(np.round(llenos * prop[color-1]))
181 | for i in range(asignados, asignados_al_final):
182 | par.nuevo(color)
183 | asignados = asignados_al_final
184 | for i in range(asignados, llenos + 1):
185 | par.nuevo(colors)
186 |
187 | #Lo último que hacemos es calcular el nivel de satisfacción y de segregación que
188 | #tiene esta distribución inicial
189 | tot_satis = 0
190 | tot_seg = 0
191 | for i in par.listavecinos :
192 | satis, prop_vec = i.satisfecho()
193 | tot_seg += prop_vec
194 | if satis:
195 | tot_satis +=1
196 |
197 |
198 | satisfaccion = 100 * tot_satis/len(par.listavecinos) #Es el porcentaje de la población que está a gusto
199 | segregacion = 100 * tot_seg/len(par.listavecinos) #Es la media del porcentaje de vecinos iguales a uno mismo.
200 | h_fel.append(np.round(satisfaccion,2))
201 | h_seg.append(np.round(segregacion,2))
202 |
203 | return par, (llenos, colors, prop)
204 |
205 | def vecin_print(par, datacolor, dim, n):
206 | '''Esta función sirve para dibujar la situación actual del vecindario.
207 | Necesita:
208 | -par: el objeto parcela del vecindario.
209 | -datacolor: una lista que contiene variables que explican cómo representar los datos.
210 | Esta lista es el segundo argumento de salida de la función "crear_mundo".
211 | -dim: el ancho de la matriz del mundo.
212 | -n: el número de la iteración'''
213 |
214 |
215 |
216 | colordict = {} #Aquí vamos a ir guardando las coordenadas de cada vecino agrupados por colores
217 | llenos, colors, prop = datacolor #desempaquetamos los datos de datacolor
218 | colornames = ['b', 'r', 'g', '0.4', 'c', 'k'] #Estos colores son los de los tipos de vecinos
219 |
220 | #Ahora, un bucle similar al que creó los vecinos los distribuirá por colores
221 | #para después poder pintarlos
222 | asignados = 0
223 | for color in range(1, colors):
224 | colorpop = []
225 | asignados_al_final = asignados + int(np.round(llenos * prop[color-1]))
226 | for i in range(asignados, asignados_al_final):
227 | colorpop.append(par.listavecinos[i].coords)
228 | asignados = asignados_al_final
229 | colordict[color-1] = np.array(colorpop)
230 | colorpop = []
231 | for i in range(asignados, llenos + 1):
232 | colorpop.append(par.listavecinos[i].coords)
233 | colordict[colors-1] = np.array(colorpop)
234 |
235 |
236 |
237 |
238 | plt.figure(None, figsize = (10,10))
239 | plt.title('Vecindario: n =' + str(n))
240 | tam_point = 105722 * dim ** -1.78 #El tamaño de los círculos se ajusta al tamaño de la matriz
241 |
242 | # Al representar el estado, también vamos a mostrar el nivel de satisfacción
243 | # y segregación, así que los calculamos.
244 | tot_satis = 0
245 | tot_seg = 0
246 | for i in par.listavecinos :
247 | satis, prop_vec = i.satisfecho()
248 | tot_seg += prop_vec
249 | if satis:
250 | tot_satis +=1
251 | else:
252 | plt.scatter(i.coords[0], i.coords[1], c = '0.6',marker = 's', s = tam_point, linewidths=0)
253 |
254 | satisfaccion = 100 * tot_satis/len(par.listavecinos)
255 | segregacion = 100 * tot_seg/len(par.listavecinos)
256 | nota = 'Satisfacción de la población: ' + str(np.round(satisfaccion, 2)) + '%'
257 | nota2 = 'Segregación de la población: ' + str(np.round(segregacion, 2)) + '%'
258 |
259 | #Lo pintamos todo:
260 | for i in range(colors):
261 | plt.scatter(colordict[i][:,0], colordict[i][:,1], c = colornames[i], s = tam_point, linewidths=0)
262 |
263 | plt.xlim(-1, dim)
264 | plt.ylim(-1, dim)
265 |
266 | plt.text(0, -1 - dim/20, nota, fontsize=12)
267 | plt.text(0, -1 - 2*dim/20, nota2,fontsize=12)
268 |
269 |
270 | def step_mudanza(par, datacolor, n, dim, h_fel = None, h_seg = None):
271 | '''Esta función recorre toda la lista de vecinos y los muda si no están satisfechos.
272 | después, guarda los valores conseguidos de felicidad y segregación, y por último
273 | representa todo gráficamente'''
274 |
275 |
276 | if not h_fel: h_fel = []
277 | if not h_seg: h_seg = []
278 |
279 |
280 | #Mudanza
281 | for i in par.listavecinos :
282 | if not i.satisfecho()[0]:
283 | i.mudanza()
284 |
285 | n += 1
286 | #Representación gráfica
287 | colordict = {}
288 | llenos, colors, prop = datacolor
289 | colornames = ['b', 'r', 'g', '0.4', 'c', 'k']
290 |
291 | asignados = 0
292 | for color in range(1, colors):
293 | colorpop = []
294 | asignados_al_final = asignados + int(np.round(llenos * prop[color-1]))
295 | for i in range(asignados, asignados_al_final):
296 | colorpop.append(par.listavecinos[i].coords)
297 | asignados = asignados_al_final
298 | colordict[color-1] = np.array(colorpop)
299 | colorpop = []
300 | for i in range(asignados, llenos + 1):
301 | colorpop.append(par.listavecinos[i].coords)
302 | colordict[colors-1] = np.array(colorpop)
303 |
304 | plt.figure(None, figsize = (10,10))
305 | tam_point = 105722 * dim ** -1.78
306 |
307 | tot_satis = 0
308 | tot_seg = 0
309 | for i in par.listavecinos :
310 | satis, prop_vec = i.satisfecho()
311 | tot_seg += prop_vec
312 | if satis:
313 | tot_satis +=1
314 | else:
315 | plt.scatter(i.coords[0], i.coords[1], c = '0.6',marker = 's', s = tam_point, linewidths=0)
316 |
317 | satisfaccion = 100 * tot_satis/len(par.listavecinos)
318 | segregacion = 100 * tot_seg/len(par.listavecinos)
319 | h_fel.append(np.round(satisfaccion,2))
320 | h_seg.append(np.round(segregacion,2))
321 | nota = 'Satisfacción de la población: ' + str(np.round(satisfaccion, 2)) + '%'
322 | nota2 = 'Segregación de la población: ' + str(np.round(segregacion, 2)) + '%'
323 | plt.title('Vecindario: n =' + str(n))
324 |
325 | for i in range(colors):
326 | plt.scatter(colordict[i][:,0], colordict[i][:,1], c = colornames[i], s = tam_point, linewidths=0)
327 | plt.xlim(-1, dim )
328 | plt.ylim(-1, dim )
329 | plt.text(0, -1 - dim/20, nota, fontsize=12)
330 | plt.text(0, -1 - 2*dim/20, nota2,fontsize=12)
331 | return n
332 |
333 |
334 | def _step_mudanza_ciego(par, numcolor, n, dim, h_fel = None, h_seg = None):
335 | '''Esta función recorre la lista de vecinos y los muda si no están satisfechos,
336 | pero no los representa gráficamente.'''
337 |
338 |
339 | if not h_fel: h_fel = []
340 | if not h_seg: h_seg = []
341 |
342 |
343 | for i in par.listavecinos :
344 | if not i.satisfecho()[0]:
345 | i.mudanza()
346 | tot_satis = 0
347 | tot_seg = 0
348 | for i in par.listavecinos :
349 | satis, prop_vec = i.satisfecho()
350 | tot_seg += prop_vec
351 | if satis:
352 | tot_satis +=1
353 |
354 | satisfaccion = 100 * tot_satis/len(par.listavecinos)
355 | segregacion = 100 * tot_seg/len(par.listavecinos)
356 | h_fel.append(np.round(satisfaccion,2))
357 | h_seg.append(np.round(segregacion,2))
358 | return n + 1
359 |
360 |
361 | def step_multiple(par, datacolor, n, dim, h_fel = None, h_seg = None, numsteps = 20):
362 | '''Recorre la lista "numstep" veces, mudando a los vecinos infelices, y finalmente
363 | representa gráficamente el resultado'''
364 |
365 | if not h_fel: h_fel = []
366 | if not h_seg: h_seg = []
367 |
368 | print('Calculando steps, total', numsteps, ':', end=' ')
369 | for i in range(numsteps-1):
370 | n = _step_mudanza_ciego(par, datacolor, n, dim, h_fel, h_seg)
371 | print(i + 1 , end='·')
372 |
373 | print(numsteps)
374 | n = step_mudanza(par, datacolor, n, dim, h_fel, h_seg)
375 | return n
376 |
377 | def evolucion(h_fel, h_seg):
378 | '''Representa gráficamente la evolución de los valores
379 | de felicidad y segregación a lo largo de las iteraciones'''
380 | plt.figure(1, figsize= (8,5))
381 | plt.title('Evolución de la felicidad del vecindario')
382 | plt.xlabel('Steps')
383 | plt.ylabel('Felicidad (%)')
384 | plt.plot(h_fel)
385 | plt.grid()
386 |
387 | plt.figure(2, figsize= (8,5))
388 | plt.title('Evolución de la segregación del vecindario')
389 | plt.xlabel('Steps')
390 | plt.ylabel('Segregación (%)')
391 | plt.plot(h_seg)
392 | plt.grid()
393 |
394 |
395 | #Ejemplo:
396 | if __name__ == '__main__':
397 |
398 |
399 |
400 | dim = 20
401 | vacios = 10
402 | colors = 2
403 | prop = [0.5]
404 | n = 0
405 | intolerance = 35
406 | intom = 100
407 | rad = 1
408 | historia_felicidad = []
409 | historia_segregacion = []
410 |
411 | par, datacolor = crear_mundo(dim, colors= colors, prop= prop, vacios = vacios,
412 | intolerance=intolerance, intom= intom, rad = rad,
413 | h_fel = historia_felicidad, h_seg = historia_segregacion)
414 |
415 | vecin_print(par, datacolor, dim, n)
416 | plt.show()
417 |
418 | n = step_mudanza(par, datacolor, n, dim,
419 | historia_felicidad, historia_segregacion)
420 | plt.show()
421 | n = step_multiple(par, datacolor, n, dim,
422 | historia_felicidad, historia_segregacion, 15)
423 | plt.show()
424 | evolucion(historia_felicidad, historia_segregacion)
425 | plt.show()
426 |
427 |
--------------------------------------------------------------------------------
/Ejercicios/Optimizacion de rotor/Optimizacion con algoritmo genetico.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | ""
8 | ]
9 | },
10 | {
11 | "cell_type": "markdown",
12 | "metadata": {},
13 | "source": [
14 | "#Ejercicio: Algoritmo genético para optimizar un rotor o hélice, paso a paso"
15 | ]
16 | },
17 | {
18 | "cell_type": "markdown",
19 | "metadata": {},
20 | "source": [
21 | "##El problema"
22 | ]
23 | },
24 | {
25 | "cell_type": "markdown",
26 | "metadata": {},
27 | "source": [
28 | "A menudo, en ingeniería, cuando nos enfrentamos a un problema, no podemos resolver directamente o despejar la solución como en los problemas sencillos típicos de matemáticas o física clásica. Una manera muy típica en la que nos encontraremos los problemas es en la forma de simulación: tenemos una serie de parámetros y un modelo, y podemos simularlo para obtener sus características, pero sin tener ninguna fórmula explícita que relacione parámetros y resultados y que nos permita obtener una función inversa.\n",
29 | "\n",
30 | "En este ejercicio, nos plantearemos un problema de ese tipo: tenemos una función que calcula las propiedades de una hélice en función de una serie de parámetros, pero no conocemos los cálculos que hace internamente. Para nosotros, es una caja negra.\n",
31 | "\n",
32 | "Para optimizar, iremos recuperando las funciones del algoritmo genético que se vieron en la parte de teoría."
33 | ]
34 | },
35 | {
36 | "cell_type": "code",
37 | "execution_count": 1,
38 | "metadata": {
39 | "collapsed": true
40 | },
41 | "outputs": [],
42 | "source": [
43 | "%matplotlib inline\n",
44 | "import numpy as np # Trabajaremos con arrays\n",
45 | "import matplotlib.pyplot as plt # Y vamos a pintar gráficos\n",
46 | "from optrot.rotor import calcular_rotor # Esta función es la que vamos a usar para calcular el rotor\n",
47 | "import random as random # Necesitaremos números aleatorios"
48 | ]
49 | },
50 | {
51 | "cell_type": "markdown",
52 | "metadata": {},
53 | "source": [
54 | "Empecemos echando un ojo a la función del rotor, para ver qué vamos a necesitar y con qué parámetros vamos a trabajar."
55 | ]
56 | },
57 | {
58 | "cell_type": "code",
59 | "execution_count": 5,
60 | "metadata": {
61 | "collapsed": false
62 | },
63 | "outputs": [
64 | {
65 | "name": "stdout",
66 | "output_type": "stream",
67 | "text": [
68 | "Help on function calcular_rotor in module optrot.rotor:\n",
69 | "\n",
70 | "calcular_rotor(omega, vz, R, b, h=0, theta0=0.174, tors_param=['h', 14], chord_params=0.05)\n",
71 | " Calcula las propiedades de una hélice. \n",
72 | " \n",
73 | " Argumentos obligatorios:\n",
74 | " \n",
75 | " - omega: velocidad de giro de la hélice, en rad/s\n",
76 | " - vz: velocidad de avance, en m/s\n",
77 | " - R : radio de la hélice\n",
78 | " - b : número de palas\n",
79 | " \n",
80 | " Argumentos opcionales:\n",
81 | " \n",
82 | " - h : altitud de vuelo, en metros sobre el nivel del mar\n",
83 | " - theta0 : ángulo de paso colectivo\n",
84 | " - tors_param : parámetros de torsión de la hélice:\n",
85 | " formato: [ley, p]\n",
86 | " p: Parámetro: número o lista\n",
87 | " Ley:describe la forma de la ley de torsiones, y puede ser:\n",
88 | " - 'c': distribución de torsión constante = p\n",
89 | " - 'l': distribución de torsión lineal, con p[0] en la raíz y p[1] en la punta\n",
90 | " - 'h': distribución de torsión hiperbólica, con torsión p en la punta\n",
91 | " - chord_params : parámetros de distribución de cuerda de la hélice.\n",
92 | " Describe la forma de la pala, y puede ser:\n",
93 | " - un número float: la cuerda es constante, con ese valor en metros\n",
94 | " - una lista de la forma ['l', x1, x2 ]:\n",
95 | " La cuerda es lineal con la posición, y mide x1 m en la raíz y x2 m en la punta \n",
96 | " Devuelve:\n",
97 | " \n",
98 | " - T : tracción de la hélice, en Newtons\n",
99 | " - P : potencia de la hélice, en Watios\n",
100 | " - efic : eficiencia o rendimiento de la hélice (a v=0 es 0 por definición)\n",
101 | " - mach_punta : número de mach de las puntas de la hélice\n",
102 | "\n"
103 | ]
104 | }
105 | ],
106 | "source": [
107 | "help(calcular_rotor)"
108 | ]
109 | },
110 | {
111 | "cell_type": "markdown",
112 | "metadata": {},
113 | "source": [
114 | "Podemos trazar unas cuantas curvas para observar qué pinta va a tener lo que saquemos. Por ejemplo, cómo cambian las características de la hélice dependiendo de la velocidad de vuelo, para una hélice de ejemplo que gira a uyna velocidad dada."
115 | ]
116 | },
117 | {
118 | "cell_type": "code",
119 | "execution_count": 11,
120 | "metadata": {
121 | "collapsed": true
122 | },
123 | "outputs": [],
124 | "source": [
125 | "vel = np.linspace(0, 30, 100)\n",
126 | "efic = np.zeros_like(vel)\n",
127 | "T = np.zeros_like(vel)\n",
128 | "P = np.zeros_like(vel)\n",
129 | "mach = np.zeros_like(vel)\n",
130 | "for i in range(len(vel)):\n",
131 | " T[i], P[i], efic[i], mach[i] = calcular_rotor(130, vel[i], 0.5, 3)"
132 | ]
133 | },
134 | {
135 | "cell_type": "code",
136 | "execution_count": 12,
137 | "metadata": {
138 | "collapsed": false,
139 | "scrolled": true
140 | },
141 | "outputs": [
142 | {
143 | "data": {
144 | "text/plain": [
145 | ""
146 | ]
147 | },
148 | "execution_count": 12,
149 | "metadata": {},
150 | "output_type": "execute_result"
151 | },
152 | {
153 | "data": {
154 | "image/png": "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\n",
155 | "text/plain": [
156 | ""
157 | ]
158 | },
159 | "metadata": {},
160 | "output_type": "display_data"
161 | }
162 | ],
163 | "source": [
164 | "plt.plot(vel, T)\n",
165 | "plt.title('Tracción de la hélice')"
166 | ]
167 | },
168 | {
169 | "cell_type": "code",
170 | "execution_count": 13,
171 | "metadata": {
172 | "collapsed": false
173 | },
174 | "outputs": [
175 | {
176 | "data": {
177 | "text/plain": [
178 | ""
179 | ]
180 | },
181 | "execution_count": 13,
182 | "metadata": {},
183 | "output_type": "execute_result"
184 | },
185 | {
186 | "data": {
187 | "image/png": "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\n",
188 | "text/plain": [
189 | ""
190 | ]
191 | },
192 | "metadata": {},
193 | "output_type": "display_data"
194 | }
195 | ],
196 | "source": [
197 | "plt.plot(vel, P)\n",
198 | "plt.title('Potencia consumida')"
199 | ]
200 | },
201 | {
202 | "cell_type": "code",
203 | "execution_count": 14,
204 | "metadata": {
205 | "collapsed": false
206 | },
207 | "outputs": [
208 | {
209 | "data": {
210 | "text/plain": [
211 | ""
212 | ]
213 | },
214 | "execution_count": 14,
215 | "metadata": {},
216 | "output_type": "execute_result"
217 | },
218 | {
219 | "data": {
220 | "image/png": "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\nJeLcPAsAAAAASUVORK5CYII=\n",
221 | "text/plain": [
222 | ""
223 | ]
224 | },
225 | "metadata": {},
226 | "output_type": "display_data"
227 | }
228 | ],
229 | "source": [
230 | "plt.plot(vel, efic)\n",
231 | "plt.title('Eficiencia de la hélice')"
232 | ]
233 | },
234 | {
235 | "cell_type": "code",
236 | "execution_count": 15,
237 | "metadata": {
238 | "collapsed": false
239 | },
240 | "outputs": [
241 | {
242 | "data": {
243 | "text/plain": [
244 | ""
245 | ]
246 | },
247 | "execution_count": 15,
248 | "metadata": {},
249 | "output_type": "execute_result"
250 | },
251 | {
252 | "data": {
253 | "image/png": "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\nYII=\n",
254 | "text/plain": [
255 | ""
256 | ]
257 | },
258 | "metadata": {},
259 | "output_type": "display_data"
260 | }
261 | ],
262 | "source": [
263 | "plt.plot(vel, mach)\n",
264 | "plt.title('Mach en la punta de las palas')"
265 | ]
266 | },
267 | {
268 | "cell_type": "markdown",
269 | "metadata": {},
270 | "source": [
271 | "##Definiendo el genoma"
272 | ]
273 | },
274 | {
275 | "cell_type": "markdown",
276 | "metadata": {},
277 | "source": [
278 | "Definamos un individuo genérico: Cada individuo será un posible diseño del rotor, con unas características determinadas."
279 | ]
280 | },
281 | {
282 | "cell_type": "code",
283 | "execution_count": 7,
284 | "metadata": {
285 | "collapsed": true
286 | },
287 | "outputs": [],
288 | "source": [
289 | "class Individual (object):\n",
290 | " \n",
291 | " def __init__(self, genome):\n",
292 | " \n",
293 | " self.genome = genome \n",
294 | " self.traits = {}\n",
295 | " self.performances = {}\n",
296 | " self.fitness = 0"
297 | ]
298 | },
299 | {
300 | "cell_type": "markdown",
301 | "metadata": {},
302 | "source": [
303 | "Nuestro rotor depende de varios parámetros, pero en general, buscaremos optimizar el valor de unos, mateniendo un valor controlado de otros. Por ejemplo, la velocidad de avance y la altitud normalmente las impondremos, ya que querremos optimizar para una velocidad y altura de vuelos dadas.\n",
304 | "\n",
305 | "En nuestro algoritmo, usaremos como genoma los parámetros de optimización, y las variables circunstanciales las controlaremos a mano.\n",
306 | "\n",
307 | "***Sugerencia*** (esta es una manera de organizar las variables, aunque puedes escoger otras)\n",
308 | "\n",
309 | "Parámetros de optimización: \n",
310 | "\n",
311 | "- omega (velocidad de rotación) (Entre 0 y 200 radianes/segundo)\n",
312 | "- R (radio de la hélice) (Entre 0.1 y 2 metros)\n",
313 | "- b (número de palas) (Entre 2 y 5 palas) \n",
314 | "- theta0 (ángulo de paso colectivo) (Entre -0.26 y 0.26 radianes)(*se corresponde a -15 y 15 grados*)\n",
315 | "- p (parámetro de torsión) (Entre -5 y 20 grados)\n",
316 | "- cuerda (anchura de la pala) (Entre 0.01 y 0.2 metros)\n",
317 | "\n",
318 | "Parámetros circunstanciales:\n",
319 | "\n",
320 | "- vz (velocidad de vuelo)\n",
321 | "- h (altura de vuelo)\n",
322 | "\n",
323 | "Variables que se van a mantener\n",
324 | "\n",
325 | "- ley de torsión (hiperbólica)\n",
326 | "- formato de chord params: un solo número, para que la anchura sea constante a lo largo de la pala\n"
327 | ]
328 | },
329 | {
330 | "cell_type": "code",
331 | "execution_count": 8,
332 | "metadata": {
333 | "collapsed": false
334 | },
335 | "outputs": [
336 | {
337 | "data": {
338 | "text/plain": [
339 | "0.2617993877991494"
340 | ]
341 | },
342 | "execution_count": 8,
343 | "metadata": {},
344 | "output_type": "execute_result"
345 | }
346 | ],
347 | "source": [
348 | "15 * np.pi / 180\n"
349 | ]
350 | },
351 | {
352 | "cell_type": "markdown",
353 | "metadata": {},
354 | "source": [
355 | "A continuación crearemos un diccionario de genes. En él iremos almacenando los nombres de los parámetros y la cantidad de bits que usaremos para definirlos. Cuantos más bits, más resolución\n",
356 | "\n",
357 | "Ej: 1 bit : 2 valores, 2 bit : 4 valores, 10 bit : 1024 valores"
358 | ]
359 | },
360 | {
361 | "cell_type": "code",
362 | "execution_count": 6,
363 | "metadata": {
364 | "collapsed": true
365 | },
366 | "outputs": [],
367 | "source": [
368 | "#Completa este diccionario con las variables que hayas elegido y los bits que usarás\n",
369 | "\n",
370 | "dict_genes = {\n",
371 | " 'omega' : 10,\n",
372 | " 'R': 10,\n",
373 | " 'b': 2\n",
374 | "}"
375 | ]
376 | },
377 | {
378 | "cell_type": "markdown",
379 | "metadata": {},
380 | "source": [
381 | "Ahora, crearemos una función que rellene estos genomas con datos aleatorios:"
382 | ]
383 | },
384 | {
385 | "cell_type": "code",
386 | "execution_count": null,
387 | "metadata": {
388 | "collapsed": true
389 | },
390 | "outputs": [],
391 | "source": [
392 | "def generate_genome (dict_genes):\n",
393 | " \n",
394 | " #Calculamos el número total de bits con un bucle que recorra el diccionario\n",
395 | " \n",
396 | " n_bits = ?\n",
397 | " \n",
398 | " #Generamos un array aletorio de 1 y 0 de esa longitud con numpy\n",
399 | " genome = np.random.randint(0, 2, nbits)\n",
400 | " \n",
401 | " #Transformamos el array en una lista antes de devolverlo\n",
402 | " return list(genome)"
403 | ]
404 | },
405 | {
406 | "cell_type": "code",
407 | "execution_count": null,
408 | "metadata": {
409 | "collapsed": true
410 | },
411 | "outputs": [],
412 | "source": [
413 | "# Podemos probar a usar nuestra función, para ver qué pinta tiene el ADN de un rotor:\n",
414 | "generate_genome(dict_genes)"
415 | ]
416 | },
417 | {
418 | "cell_type": "markdown",
419 | "metadata": {},
420 | "source": [
421 | "##Trabajando con el individuo"
422 | ]
423 | },
424 | {
425 | "cell_type": "markdown",
426 | "metadata": {},
427 | "source": [
428 | "Ahora necesitamos una función que transforme esos genes a valores con sentido. Cada gen es un número binario cuyo valor estará entre 0 y 2 ^ n, siendo n el número de bits que hayamos escogido. Estas variables traducidas las guardaremos en otro diccionario, ya con su valor. Estos genes no están volando por ahí sueltos, sino que estarán guardados en el interior del individuo al que pertenezcan, por lo que la función deberá estar preparada para extraerlos del individuo, y guardar los resultados a su vez en el interior del individuo.\n",
429 | "\n"
430 | ]
431 | },
432 | {
433 | "cell_type": "code",
434 | "execution_count": null,
435 | "metadata": {
436 | "collapsed": true
437 | },
438 | "outputs": [],
439 | "source": [
440 | "def calculate_traits (individual, dict_genes):\n",
441 | " \n",
442 | " \n",
443 | " genome = individual.genome \n",
444 | " \n",
445 | " integer_temporal_list = []\n",
446 | " \n",
447 | " for gen in dict_genes: #Recorremos el diccionario de genes para ir traduciendo del binario\n",
448 | " \n",
449 | " ??? #Buscamos los bits que se corresponden al bit en cuestión\n",
450 | " \n",
451 | " ??? #Pasamos de lista binaria a número entero\n",
452 | " \n",
453 | " integer_temporal_list.append(??) #Añadimos el entero a la lista\n",
454 | " \n",
455 | " # Transformamos cada entero en una variable con sentido físico:\n",
456 | " # Por ejemplo, si el entero de la variable Omega está entre 0 y 1023 (10bits), \n",
457 | " # pero la variable Omega real estará entre 0 y 200 radianes por segundo:\n",
458 | " omega = integer_temporal_list[0] * 200 / 1023\n",
459 | " \n",
460 | " #del mismo modo, para R:\n",
461 | " R = 0.1 + integer_temporal_list[1] * 1.9 / 1023 #Obtendremos un radio entre 0.1 y 2 metros\n",
462 | " \n",
463 | " #El número de palas debe ser un entero, hay que tener cuidado:\n",
464 | " b = integer_temporal_list[2] + 2 #(entre 2 y 5 palas)\n",
465 | " \n",
466 | " #Continúa con el resto de variables que hayas elegido!\n",
467 | " \n",
468 | " dict_traits = { #Aquí iremos guardando los traits, o parámetros \n",
469 | " 'omega' : omega,\n",
470 | " 'R': R\n",
471 | " } \n",
472 | " individual.traits = dict_traits #Por último, guardamos los traits en el individuo"
473 | ]
474 | },
475 | {
476 | "cell_type": "markdown",
477 | "metadata": {},
478 | "source": [
479 | "El siguiente paso es usar estos traits(parámetros) para calcular las performances (características o desempeños) del motor. Aquí es donde entra el modelo del motor propiamente dicho. "
480 | ]
481 | },
482 | {
483 | "cell_type": "code",
484 | "execution_count": null,
485 | "metadata": {
486 | "collapsed": true
487 | },
488 | "outputs": [],
489 | "source": [
490 | "def calculate_performances (individual):\n",
491 | " dict_traits = individual.traits\n",
492 | " \n",
493 | " #Nuestras circunstancias las podemos imponer aquí, o irlas pasando como argumento a la función\n",
494 | " h = 2000 #Altitud de vuelo en metros\n",
495 | " vz = 70 #velocidad de avance en m/s, unos 250 km/h\n",
496 | " \n",
497 | " #Extraemos los traits del diccionario:\n",
498 | " omega = dict_traits['omega']\n",
499 | " R = dict_traits['R']\n",
500 | " #... etc\n",
501 | " \n",
502 | " T, P, efic, mach_punta = calcular_rotor(omega, vz, R, b, h...) #Introduce las variables que uses de parámetro.\n",
503 | " # Consulta la ayuda para asegurarte de que usas el \n",
504 | " # formato correcto!\n",
505 | " dict_perfo = { \n",
506 | " 'T' : T, #Tracción de la hélice\n",
507 | " 'P' : P, #Potencia consumida por la hélice\n",
508 | " 'efic': efic, #Eficiencia propulsiva de la hélice\n",
509 | " 'mach_punta': mach_punta #Mach en la punta de las palas\n",
510 | " }\n",
511 | " \n",
512 | " individual.performances = dict_perfo"
513 | ]
514 | },
515 | {
516 | "cell_type": "markdown",
517 | "metadata": {},
518 | "source": [
519 | "Comprobemos si todo funciona!"
520 | ]
521 | },
522 | {
523 | "cell_type": "code",
524 | "execution_count": null,
525 | "metadata": {
526 | "collapsed": false
527 | },
528 | "outputs": [],
529 | "source": [
530 | "individuo = Individual(generate_genome(dict_genes))\n",
531 | "calculate_traits(individuo, dict_genes)\n",
532 | "calculate_performances(individuo)\n",
533 | "\n",
534 | "print(individuo.traits)\n",
535 | "print(individuo.performances)"
536 | ]
537 | },
538 | {
539 | "cell_type": "markdown",
540 | "metadata": {},
541 | "source": [
542 | "El último paso que tenemos que realizar sobre el individuo es uno de los más críticos: Transformar las performances en un valor único (fitness) que con exprese cómo de bueno es con respecto al objetivo de optimización. La función de fitness puede ser función de parámetros(traits) y performances, dependiendo de qué queramos optimizar.\n",
543 | "\n",
544 | "Por ejemplo, si buscáramos que tuviera la tracción máxima sin preocuparnos de nada más, el valor de fitnes sería simplemente igual al de T:\n",
545 | "\n",
546 | " fitness = T\n",
547 | "\n",
548 | "Si queremos imponer restricciones, por ejemplo, que la potencia sea menor a 1000 watios, se pueden añadir sentencias del tipo:\n",
549 | "\n",
550 | " if P > 1000:\n",
551 | " fitness -= 1000\n",
552 | " \n",
553 | "Se puede hacer depender la fitness de varios parámetros de manera ponderada:\n",
554 | "\n",
555 | " fitness = parámetro_importante * 10 + parámetro_poco_importante * 0.5\n",
556 | " \n",
557 | "También se pueden combinar diferentes funciones no lineales:\n",
558 | "\n",
559 | " fitness = parámetro_1 * parámetro_2 - parámetro_3 **2 * log(parámetro_4)\n",
560 | "\n",
561 | "Ahora te toca ser creativo! Elige con qué objetivo quieres optimizar la hélice!\n",
562 | "\n",
563 | "Sugerencias de posibles objetivos de optimización:\n",
564 | "\n",
565 | "- Mínimo radio posible, manteniendo una tracción mínima de 30 Newtons\n",
566 | "- Mínima potencia posible, máxima eficiencia, y mínimo radio posible en menor medida, manteniendo una tracción mínima de 40 Newtons y un mach en la punta de las palas de como mucho 0.7\n",
567 | "- Mínima potencia posible y máxima eficiencia cuando vuela a 70 m/s, tracción mayor a 50 Newtons en el despegue (vz = 0), mínimo peso posible (calculado a partir del radio, número y anchura de las palas) (Puede que tengas que reescribir la función y el diccionario de performances!)"
568 | ]
569 | },
570 | {
571 | "cell_type": "code",
572 | "execution_count": null,
573 | "metadata": {
574 | "collapsed": true
575 | },
576 | "outputs": [],
577 | "source": [
578 | "def calculate_fitness (individual):\n",
579 | " \n",
580 | " dict_traits = individuo.traits\n",
581 | " dict_performances = individuo.performances\n",
582 | " \n",
583 | " fitness = ????? #Be Creative!\n",
584 | " \n",
585 | " \n",
586 | " individual.fitness = fitness"
587 | ]
588 | },
589 | {
590 | "cell_type": "markdown",
591 | "metadata": {},
592 | "source": [
593 | "Ya tenemos terminado todo lo que necesitamos a nivel de individuo!"
594 | ]
595 | },
596 | {
597 | "cell_type": "markdown",
598 | "metadata": {},
599 | "source": [
600 | "## Que comiencen los Juegos!"
601 | ]
602 | },
603 | {
604 | "cell_type": "markdown",
605 | "metadata": {},
606 | "source": [
607 | "Es hora de trabajar a nivel de algoritmo, y para ello, lo primero es crear una sociedad compuesta de individuos aleatorios. Definamos una función para ello."
608 | ]
609 | },
610 | {
611 | "cell_type": "code",
612 | "execution_count": null,
613 | "metadata": {
614 | "collapsed": true
615 | },
616 | "outputs": [],
617 | "source": [
618 | "def immigration (society, target_population, dict_genes):\n",
619 | " \n",
620 | " while len(society) < target_population:\n",
621 | " \n",
622 | " new_individual = Individual (generate_genome (dict_genes)) # Generamos un individuo aleatorio\n",
623 | " calculate_traits (new_individual, dict_genes) # Calculamos sus traits\n",
624 | " calculate_performances (new_individual) # Calculamos sus performances\n",
625 | " calculate_fitness (new_individual) # Calculamos su fitness\n",
626 | " \n",
627 | " society.append (new_individual) # Nuestro nuevo ciudadano está listo para unirse al grupo! "
628 | ]
629 | },
630 | {
631 | "cell_type": "markdown",
632 | "metadata": {},
633 | "source": [
634 | "Ahora podemos crear nuestra sociedad:"
635 | ]
636 | },
637 | {
638 | "cell_type": "code",
639 | "execution_count": null,
640 | "metadata": {
641 | "collapsed": true
642 | },
643 | "outputs": [],
644 | "source": [
645 | "society = []\n",
646 | "immigration (society, 12, dict_genes) #12 por ejemplo, pueden ser los que sean\n",
647 | "\n",
648 | "\n",
649 | "#Veamos qué pinta tienen los genes de la población\n",
650 | "plt.matshow([individual.genome for individual in society], cmap=plt.cm.gray)"
651 | ]
652 | },
653 | {
654 | "cell_type": "markdown",
655 | "metadata": {},
656 | "source": [
657 | "Ya tenemos nuestra pequeña sociedad, aumentémosla un poco más mezclando entre sí a los ciudadanos con mejores fitness! Vamos a extender nuestra población mezclando los genomas de otros individuos. Los individuos con mejor fitness es más probable que se reproduzcan. Además, en los nuevos individuos produciremos ligeras mutaciones aleatorias."
658 | ]
659 | },
660 | {
661 | "cell_type": "code",
662 | "execution_count": null,
663 | "metadata": {
664 | "collapsed": true
665 | },
666 | "outputs": [],
667 | "source": [
668 | "#This function was taken from Eli Bendersky's website\n",
669 | "#It returns an index of a list called \"weights\", \n",
670 | "#where the content of each element in \"weights\" is the probability of this index to be returned.\n",
671 | "#For this function to be as fast as possible we need to pass it a list of weights in descending order.\n",
672 | "def weighted_choice_sub(weights):\n",
673 | " \n",
674 | " rnd = random.random() * sum(weights)\n",
675 | " for i, w in enumerate(weights):\n",
676 | " rnd -= w\n",
677 | " if rnd < 0:\n",
678 | " return i\n",
679 | "\n",
680 | "def crossover (society, reproduction_rate, mutation_rate):\n",
681 | " \n",
682 | " #First we create a list with the fitness values of every individual in the society\n",
683 | " fitness_list = [individual.fitness for individual in society]\n",
684 | " \n",
685 | " #We sort the individuals in the society in descending order of fitness. \n",
686 | " society_sorted = [x for (y, x) in sorted(zip(fitness_list, society), key=lambda x: x[0], reverse=True)] \n",
687 | " \n",
688 | " #We then create a list of relative probabilities in descending order, \n",
689 | " #so that the fittest individual in the society has N times more chances to reproduce than the least fit,\n",
690 | " #where N is the number of individuals in the society.\n",
691 | " probability = [i for i in reversed(range(1,len(society_sorted)+1))]\n",
692 | " \n",
693 | " #We create a list of weights with the probabilities of non-mutation and mutation\n",
694 | " mutation = [1 - mutation_rate, mutation_rate] \n",
695 | " \n",
696 | " #For every new individual to be created through reproduction:\n",
697 | " for i in range (int(len(society) * reproduction_rate)):\n",
698 | " \n",
699 | " #We select two parents randomly, using the list of probabilities in \"probability\".\n",
700 | " father, mother = society_sorted[weighted_choice_sub(probability)], society_sorted[weighted_choice_sub(probability)]\n",
701 | " \n",
702 | " #We randomly select two cutting points for the genome.\n",
703 | " a, b = random.randrange(0, len(father.genome)), random.randrange(0, len(father.genome))\n",
704 | " \n",
705 | " #And we create the genome of the child putting together the genome slices of the parents in the cutting points.\n",
706 | " child_genome = father.genome[0:min(a,b)]+mother.genome[min(a,b):max(a,b)]+father.genome[max(a,b):]\n",
707 | " \n",
708 | " #For every bit in the not-yet-born child, we generate a list containing \n",
709 | " #1's in the positions where the genome must mutate (i.e. the bit must switch its value)\n",
710 | " #and 0's in the positions where the genome must stay the same.\n",
711 | " n = [weighted_choice_sub(mutation) for ii in range(len(child_genome))]\n",
712 | " \n",
713 | " #This line switches the bits of the genome of the child that must mutate.\n",
714 | " mutant_child_genome = [abs(n[i] - child_genome[i]) for i in range(len(child_genome))]\n",
715 | " \n",
716 | " #We finally append the newborn individual to the society\n",
717 | " newborn = Individual(mutant_child_genome)\n",
718 | " calculate_traits (newborn, dict_genes)\n",
719 | " calculate_performances (newborn)\n",
720 | " calculate_fitness (newborn)\n",
721 | " society.append(newborn) "
722 | ]
723 | },
724 | {
725 | "cell_type": "markdown",
726 | "metadata": {},
727 | "source": [
728 | "Ahora que tenemos una sociedad extensa, es el momento de que actúe la selección \"natural\": Eliminaremos de la sociedad a los individuos con peor fitness hasta llegar a una población objetivo."
729 | ]
730 | },
731 | {
732 | "cell_type": "code",
733 | "execution_count": null,
734 | "metadata": {
735 | "collapsed": true
736 | },
737 | "outputs": [],
738 | "source": [
739 | "def tournament(society, target_population):\n",
740 | " \n",
741 | " while len(society) > target_population:\n",
742 | " \n",
743 | " fitness_list = [individual.fitness for individual in society]\n",
744 | " society.pop(fitness_list.index(min(fitness_list)))"
745 | ]
746 | },
747 | {
748 | "cell_type": "markdown",
749 | "metadata": {},
750 | "source": [
751 | "Ya tenemos nuestro algoritmo prácticamente terminado!"
752 | ]
753 | },
754 | {
755 | "cell_type": "code",
756 | "execution_count": null,
757 | "metadata": {
758 | "collapsed": true
759 | },
760 | "outputs": [],
761 | "source": [
762 | "society = []\n",
763 | "fitness_max = []\n",
764 | "\n",
765 | "for generation in range(30):\n",
766 | " \n",
767 | " immigration (society, 100, dict_genes) #Añade individuos aleatorios a la sociedad hasta tener 100\n",
768 | " fitness_max += [max([individual.fitness for individual in society])]\n",
769 | " \n",
770 | " tournament (society, 15) #Los hace competir hasta que quedan 15\n",
771 | " crossover(society, 5, 0.05) #Los ganadores se reproducen hasta tener 75\n",
772 | " \n",
773 | " \n",
774 | "plt.plot(fitness_max)\n",
775 | "plt.title('Evolución del valor de fitness')\n",
776 | "\n",
777 | "tournament (society, 1) #Buscamos el mejor de todos\n",
778 | "winner = society[0]\n",
779 | "\n",
780 | "print(winner.traits) #Comprobamos sus características\n",
781 | "print(winner.performances)\n"
782 | ]
783 | },
784 | {
785 | "cell_type": "code",
786 | "execution_count": null,
787 | "metadata": {
788 | "collapsed": true
789 | },
790 | "outputs": [],
791 | "source": []
792 | },
793 | {
794 | "cell_type": "code",
795 | "execution_count": null,
796 | "metadata": {
797 | "collapsed": true
798 | },
799 | "outputs": [],
800 | "source": []
801 | },
802 | {
803 | "cell_type": "markdown",
804 | "metadata": {},
805 | "source": [
806 | "Siro Moreno y Carlos Dorado, Aeropython, 20 de Noviembre de 2015"
807 | ]
808 | },
809 | {
810 | "cell_type": "code",
811 | "execution_count": null,
812 | "metadata": {
813 | "collapsed": true
814 | },
815 | "outputs": [],
816 | "source": []
817 | }
818 | ],
819 | "metadata": {
820 | "kernelspec": {
821 | "display_name": "Python 3",
822 | "language": "python",
823 | "name": "python3"
824 | },
825 | "language_info": {
826 | "codemirror_mode": {
827 | "name": "ipython",
828 | "version": 3
829 | },
830 | "file_extension": ".py",
831 | "mimetype": "text/x-python",
832 | "name": "python",
833 | "nbconvert_exporter": "python",
834 | "pygments_lexer": "ipython3",
835 | "version": "3.4.3"
836 | }
837 | },
838 | "nbformat": 4,
839 | "nbformat_minor": 0
840 | }
841 |
--------------------------------------------------------------------------------