TALLER 3: EFICIENCIA DE UN ALGORITMO
Integrantes:
Ireidy Yurley Posada Bran
Nicolás Zapata Valdés
Yesica María Diosa Muñoz
Facultad de Ingeniería
Tecnológico de Antioquia
Medellín, Antioquia
Octubre 2023
Punto 1
¿Qué hacen los métodos head / tail /loc?
Head: se utiliza para mostrar las primeras filas de “nodos”
Tail: se utiliza para mostrar las ultimas filas de “nodos”
Loc: se utiliza para acceder a una fila especifica mediante un índice o etiqueta, en este caso
“nodos.loc[‘DL’]” devolverá la fila del “nodos” cuya etiqueta de índice es ‘DL’
los métodos head() y tail() se utilizan para mostrar las primeras y últimas filas de un
DataFrame, respectivamente, mientras que loc[] se utiliza para acceder a filas específicas
¿Qué hacen los métodos describe /corr?
¿Qué significa una correlación que se aproxima a: 0, -1, 1?
Describe: este método nos proporciona información estadística sobre las columnas
numéricas, tales como: recuento, media, desviación estándar, valor mínimo, cuartiles y
valor máximo
Corr: calcula la matriz de correlación entre las columnas numéricas. Los valores de
correlación cercanos a 0 indican poca o ninguna correlación, mientras que los valores
cercanos a 1 o -1 indican correlaciones positivas o negativas fuertes, respectivamente.
¿Qué diferencia hay entre el orden y el tamaño del grafo?
El orden de un grafo hace referencia al número de nodos, es una medida de cuantos
elementos (nodos) hay en el grafo
El tamaño de grafo, hace referencia al número de aristas que conectan los nodos.
Representa la cantidad de relación o conexiones entre los nodos
la diferencia entre el orden y el tamaño de un grafo es que el orden se refiere al número de
nodos en el grafo, mientras que el tamaño se refiere al número de aristas en el grafo
“Intentar al menos tres formas del método draw además de spring para la
distribución del grafo: circular / kamada_kawai / planar / random /
spectral / shell. Probar cambios en color revisando las diferentes
alternativas de la CSS matplotlib colors palette, tamaños de nodo y de
fuente para las etiquetas. ¿Qué configuración es la qué mejor funciona
para usted?”
Gráfico que nos dan por defecto: Gráfico usando “planar”
Gráfico usando “espectral” Gráfico usando “circualar”
Gráfico usando “shell” Gráfico usando “random”
La configuracion que mejor visibilidad nos proporciona para este ejercicio es la “planar”
con conexiones rojas ya que esta permite entender de manera más clara las distancias entre
los nodos.
¿Qué diferencia hay entre los 3 métodos trabajados?
all_shortest_paths: Este metodo permite encontrar todos los caminos más cortos entre los
nodos, este metodo es util cuando quieres conocer todas las posibles rutas optimas entre dos
puntos, pero pude tener mucho costo de memoria o tiempo si el grafo es muy grande.
“Además, este método no funciona si el grafo tiene ciclos de peso cero, ya que produciría
infinitos caminos de longitud ilimitada”
Este método encuentra todos los caminos más cortos posibles entre un par de nodos
fuente y destino.
No tiene en cuenta ningún atributo específico del grafo, como el peso de las aristas.
Es útil cuando deseas conocer todos los caminos posibles, pero no es eficiente si el
grafo es grande o complejo.
dijkstra_path: Este método utiliza el algoritmo de Dijkstra que se basa en ir explorando
los nodos más cercanos al nodo origen y actualiza las distancias mínimas hasta cada nodo,
es de los más efectos y garantiza encontrar el camino más corto siempre y cuando el grafo
no tenga aristas de peso negativo
El algoritmo de Dijkstra encuentra el camino más corto en función del peso de las
aristas (generalmente, la distancia o costo) desde un nodo fuente hasta un destino.
Es eficiente para grafos con pesos no negativos.
Se detiene cuando encuentra el camino más corto y no considera otros caminos.
Es útil cuando tienes un grafo con pesos y deseas encontrar la ruta más corta en
función de un atributo específico de las aristas.
astar_path: Este algoritmo es una variante del Dijkstra, te permite encontrar un solo camino
entre dos nodos usando el algoritmo “A” Este método es más rápido que el algoritmo de
Dijkstra si la función heurística es admisible, es decir, si nunca sobreestima la distancia
real. Sin embargo, este método requiere tener una función heurística adecuada para cada
problema y también falla si el grafo tiene aristas de peso negativo
El algoritmo A* es una variante del algoritmo de Dijkstra que encuentra el camino
más corto en función de una estimación de la distancia restante desde un nodo hasta
el destino (heurística).
Combina la información de la distancia recorrida hasta el momento y la estimación
de la distancia restante para elegir el camino más prometedor.
Es útil cuando tienes un grafo con pesos y deseas encontrar la ruta más corta en
función de una heurística que puede mejorar la eficiencia en ciertos casos.
¿Cómo funciona el método ver_ruta?
Es una función para imprimir una ruta entre dos nodos en un grafo, con la distancia total en
kilómetros y el costo total. La función toma como entrada una lista de nodos, que
representa la ruta. La función luego itera sobre la lista de nodos y imprime la información
de cada tramo de la ruta, incluyendo la distancia en kilómetros y el costo. Al final, la
función imprime la duración total de la ruta y el costo total.
El código también llama a la función ver_ruta() dos veces, una para la ruta seleccionada por
kilómetros y otra para la ruta seleccionada por costo. Esto permite comparar las dos rutas y
elegir la que mejor se adapte a las necesidades del usuario.
“Modifique el método plot_ruta para que el nodo origen y el nodo destino se pinten
de un color diferente a los nodos intermedios.”
Gráfica que nos dan por defecto:
Gráfico modificado:
numpy as np: Importa la biblioteca NumPy para trabajar con matrices
print(list(G.nodes())): Imprime la lista de nodos del grafo. Los nodos son los vértices del
grafo
print(list(G.edges())): Imprime la lista de aristas del grafo. Las aristas son las conexiones
entre los nodos
adj_matrix = nx.to_numpy_array(G, dtype=int): Utiliza la función nx.to_numpy_array
para convertir el grafo en una matriz de adyacencias.
Los métodos traveling_salesman_problem() y greedy_tsp() son métodos de Python para
resolver el problema del viajero.
El método traveling_salesman_problem() utiliza un algoritmo de búsqueda exhaustiva para
encontrar la ruta más corta. Este algoritmo comienza con un nodo aleatorio y luego explora
todas las posibles rutas desde ese nodo. La ruta más corta se devuelve como resultado.
El método greedy_tsp() utiliza un algoritmo voraz para encontrar una ruta aproximada. Este
algoritmo comienza con un nodo aleatorio y luego agrega el nodo más cercano que no haya
sido visitado a la ruta. El algoritmo continúa agregando nodos hasta que todos los nodos
hayan sido visitados. La ruta resultante puede no ser la ruta más corta, pero generalmente es
una buena aproximación.
Para los problemas del ejercicio, estos métodos se pueden utilizar para encontrar la ruta
más corta entre las ciudades. Por ejemplo, el método traveling_salesman_problem() se
puede utilizar para encontrar la ruta más corta entre las ciudades de Colombia. El método
greedy_tsp() se puede utilizar para encontrar una ruta aproximada entre las ciudades de
Colombia.
Punto 2
1. Recuperar los datos
Se ingresa a Colaboratorio de Google y se instala la librería Pyrosm.
Se ejecuta el código y se descarga la librería Pyrosm.
Se instalan paquetes correctamente. Al final se visualiza una lista de varios países,
incluyendo Colombia.
Se instala la librería.
Para la solución del taller, se seleccionó la ciudad de Bogotá. Al ejecutar el código se
visualizó la siguiente:
Reconociendo el data set fuente: ¿Qué variables tenemos disponible en un archivo PBF?
¿Qué información codifican: oneway, maxspeed, highway?
Respuestas:
¿Qué variables tenemos disponible en un archivo PBF?
La estructura de un archivo PBF puede variar dependiendo del tipo de dato que se esté
almacenando. Las variables disponibles pueden incluir:
1. Nodos: Representan puntos geográficos y pueden tener atributos como identificación
(ID), coordenadas geográficas, y etiquetas (pares clave-valor).
2. Caminos (Ways): Son secuencias ordenadas de nodos que forman una línea o un
polígono en el mapa. Pueden contener atributos similares a los nodos, así como
referencias a nodos que forman el camino.
3. Relaciones: Pueden representar relaciones entre elementos como nodos, caminos y
otros, y contienen referencias a esos elementos, así como etiquetas adicionales.
4. Etiquetas (Tags): Pares clave-valor asociados con nodos, caminos y relaciones para
describir características geográficas.
¿Qué información codifican: oneway, maxspeed, highway?
- oneway: Esta etiqueta indica si una carretera o calle es de sentido único o de doble
sentido. Puede tener valores como "yes" (sí) para indicar una dirección única, "no"
(no) para indicar doble sentido, o "reversible" para indicar que la dirección puede
cambiar en ciertos momentos.
- maxspeed: Esta etiqueta especifica la velocidad máxima permitida en una carretera o
calle.
- highway: Esta etiqueta describe el tipo de carretera o calle. Puede tener valores como
"residential" (residencial), "primary" (primaria), "secondary" (secundaria), "tertiary"
(terciaria), entre otros, para indicar la clasificación y función de la carretera.
Para poder crear un grafo necesitamos tener nodos y aristas. Contamos con un
GeoDataFrame de aristas, pero ¿dónde están los nodos? Con pyrosm se puede recuperar
fácilmente los nodos especificando nodes=True, al analizar las calles:
Al ejecutar el código se visualiza la siguiente vista:
Se ejecuta código cambiando los valores en el plano cartesiano. En este caso, se ingresaron
(-74.13, -74.12) en X y (4.6, 4.62) en Y para una vista más cercana de la ciudad.
Reconociendo los nuevos nodos: ¿Qué información contienen los nodos?
Respuesta: Al ejecutar la línea de código: nodes.head()se visualiza el siguiente recuadro:
En este, se puede visualizar la información de la longitud, latitud, etiquetas, tiempo, versión,
identificador y geometría (donde nos marca el punto entre la longitud y latitud).
Al ejecutar la línea de código: edges.iloc[:5,-4:] se visualiza el siguiente recuadro:
Podemos ver que las geometrías se almacenan ahora como LineString en lugar de
MultiLineString. Llegados a este punto, podemos solucionar el problema relacionado con el
hecho de tener algunas vías peatonales en nuestra red. Podemos hacerlo eliminando todas las
aristas de nuestro GeoDataFrame que tengan el valor de carretera en 'carril bici', 'carril
peatonal', 'peatón', 'camino', 'cruce':
Al ejecutar el código se visualiza lo siguiente:
Explique ¿qué hace el anterior fragmento de código?
Respuesta: Este código filtra y visualiza datos geoespaciales representados por bordes y
nodos en un grafo, centrándose en un área específica del mapa.
2. Modificar los datos:
Se realiza la comprobación de los recuentos de valores de la columna y también incluyamos
información sobre los valores NaN con el parámetro dropna ejecutando la línea de código:
Al ejecutar se visualiza lo siguiente:
Se reemplazando datos string con datos numéricos al ejecutar el código y los valores de
maxspeed se almacenan en formato entero dentro de un IntegerArray, y los valores None se
convirtieron en objetos pandas.
Ahora se crea una función que devuelva un valor numérico para las diferentes clases de
carreteras en base a los criterios del siguiente método:
Se ejecuta el siguiente código y se visualiza la siguiente información:
Explique ¿qué hace el anterior fragmento de código?
Respuesta:
El fragmento de código divide un DataFrame de carreteras en dos: uno con información de
límite de velocidad y otro sin información de límite de velocidad. Donde, para las filas sin
información de límite de velocidad aplica una función (road_class_to_kmph) para estimar los
límites de velocidad y actualiza esas filas en el DataFrame original. Finalmente, la última
línea imprime las primeras 5 filas de las actualizadas para inspeccionar el resultado.
Se recrear el GeoDataFrame edges combinando los dos subconjuntos anteriores ejecutando la
siguiente línea de código:
Como ahora todos nuestros edges tienen información sobre el límite de velocidad. También
podemos visualizarlos de la siguiente manera al ejecutar el código se visualiza lo siguiente:
Se ejecuta el código para calcular el tiempo de viaje en segundos:
3. Construir un grafo dirigido con pyrosm
Se crea un grafo NetworkX usando pyrosm con el comando:
¿Qué tipo de grafo es, explique?
Respuesta: Es un grafo dirigido y ponderado, ya que MultiDiGraph indica que es un grafo
dirigido y puede tener múltiples arcos entre los mismos nodos.
Se instala la librería para trazar el mapa interactivo:
Se ejecuta el código para trazar el mapa:
4. Enrutamiento con NetworkX
Se ejecuta el Código para recuperar las coordenadas de X y Y de el origen y destino
seleccionado en la ciudad de Bogotá. En este caso utilizó la siguiente información:
- Dirección de origen: Carrera 7 # 32-56.
- Dirección de destino: Plaza de Bolívar.
Al ejecutar el código, se generaron las siguientes coordenadas:
Encontrar los nodos más cercanos:
Se ejecuta el código para encontrar los nodos más cercanos del grafo para las dos
localizaciones seleccionadas:
Encontrar la ruta más rápida por distancia / tiempo:
Se ejecutan el siguiente código para encontrar el camino más corto y obtener el coste
acumulado del viaje entre los destinos seleccionados para 3 rutas:
Ruta 1:
orig_address = "Carrera 7 # 32-56, Bogotá, Colombia"
dest_address = "Plaza de Bolívar, Bogotá, Colombia"
Ruta 2:
orig_address = " Calle 93 # 13-71, Bogotá, Colombia"
dest_address = " Avenida Jiménez # 7-67, Bogotá, Colombia"
Ruta 3:
orig_address = "Carrera 15 # 77-05, Bogotá, Colombia"
dest_address = "Carrera 4 # 10-20, Bogotá, Colombia"
Punto 3
El código implementa un algoritmo genético que utiliza conceptos biológicos para adivinar
una contraseña mediante la evolución de secuencias de caracteres hasta encontrar una
solución que coincida con la contraseña objetivo.
La variable geneset en este contexto sirve como un conjunto de caracteres a partir del cual
se pueden formar genes o secuencias de genes. En un algoritmo genético, el conjunto de
genes representa el conjunto de posibles valores que pueden ser utilizados para formar las
soluciones candidatas. un conjunto de caracteres más amplio ofrece una mayor flexibilidad
y diversidad para generar secuencias de genes, lo que puede mejorar las posibilidades de
encontrar la contraseña objetivo en comparación con un conjunto de caracteres más
pequeño, que restringe significativamente las opciones de combinación y búsqueda.
la variable target representa la contraseña que el algoritmo genético intentará adivinar. Esta
variable contiene la secuencia de caracteres que el algoritmo intentará generar mediante la
evolución de cromosomas y comparando su similitud con esta cadena de caracteres
objetivo.
Así se comporta el código con algunas contraseñas, podemos observar el número de
iteraciones y el tiempo que demora para llegar a la contraseña:
¿Explique cómo funcionan las tres pruebas que se ejecutan sobre el algoritmo?
Resultado
Prueba Descripción Proceso Objetivo Esperado
Comprobar si el
algoritmo puede
adivinar una Adivinar con
Adivinar una Utiliza contraseña éxito la
contraseña guess_password(target) con específica contraseña
test_Prueba específica una contraseña predefinida conocida predefinida
Evaluar la
capacidad del Intentar
algoritmo para adivinar la
Adivinar una Genera una contraseña manejar contraseña
contraseña aleatoria usando el conjunto contraseñas aleatoria
test_Random aleatoria de genes disponibles desconocidas generada
Evaluar el Registrar el
rendimiento del tiempo de
Utiliza la clase Benchmark algoritmo en ejecución en
para ejecutar múltiples términos de diferentes
Pruebas de iteraciones del algoritmo y tiempo de iteraciones y
test_benchmark rendimiento medir el tiempo ejecución condicione
En este ejemplo no se utilizó el cruce, investigue y explique: ¿cómo se utiliza el cruce
en un algoritmo genético y cómo puede ayudar a mejorar la convergencia del
algoritmo?
El cruce, también conocido como crossover, es una etapa crucial en los algoritmos
genéticos que simula el proceso de reproducción biológica. En un algoritmo genético, el
cruce se utiliza para combinar información genética (representada por cromosomas o
individuos) de padres seleccionados y producir descendencia con nuevas características
genéticas. Este proceso contribuye significativamente a la mejora y la convergencia del
algoritmo por varias razones:
Exploración del espacio de búsqueda: Al combinar información genética de dos padres
diferentes, el cruce permite explorar y generar nuevas soluciones potenciales en el espacio
de búsqueda. La descendencia resultante puede contener combinaciones de características
beneficiosas presentes en ambos padres.
Intercambio de información genética: El cruce permite intercambiar información entre los
individuos. Esto ayuda a mantener la diversidad genética en la población, evitando que el
algoritmo se estanque en mínimos locales y facilitando la búsqueda de soluciones óptimas.
Recombinación y generación de variabilidad: Al mezclar partes de dos soluciones
parentales, el cruce crea diversidad en la descendencia. Esta variabilidad genética puede dar
lugar a nuevas soluciones que podrían ser más adecuadas o tener características más
deseables para resolver el problema.
Explotación y mejora gradual: A medida que el algoritmo avanza en las generaciones, el
cruce permite mantener y mejorar gradualmente las soluciones más prometedoras mediante
la combinación de información genética proveniente de las mejores soluciones encontradas
hasta el momento.