Grafos dinámicos incrementales
Agregar un arco aleatorio
Función solo para agregar un nuevo arco al grafo dado un nodo fuente y un nodo destino
def insert_edge(self, source, target, weight=1): self.clean_vars() self.source = np.append(self.source, source) self.target = np.append(self.target, target) self.weight = np.append(self.weight, weight) self.last_edge_updated = np.array([source, target, weight]) self.last_edge_action = "ADD" self.sort_sources() return { "last_edge_updated": self.last_edge_updated, "last_edge_action": self.last_edge_action }
Busca todos los pares de nodos que no tengan arco y elige dos aleatorios
def insert_random_edge(self, weights=[1, 2, 3, 4, 5, 6, 7, 8, 9]): count_max = 100 flag = 0 while True: source = np.random.choice(self.nodes, 1)[0] index_for_target = np.invert(np.logical_or(np.in1d(self.nodes, self.target[source == self.source]), self.nodes == source)) choisen = self.nodes[index_for_target] if choisen.size != 0: target = np.random.choice(choisen, 1)[0] break flag = flag + 1 if flag >= count_max: return -2
Agregar arco peor caso
Función para encontrar el total de nodos descedentes o ascendetes de un nodo dado. Si to_find
es sources entonces encuetra los descendentes, si es targets entonces encuentra los nodos ascendentes
def find_total(self, node, to_find="sources"): sources = 0 vis = [False for i in self.nodes] Q = deque([node]) vis[node] = True while len(Q) > 0: x = Q.popleft() list = self.source[self.target == x] if to_find == 'sources' else self.target[self.source == x] for z in list: if not vis[z]: vis[z] = True Q.append(z) sources += 1 return sources
Encontramos los nodos que poseen peor escenario. Para esto calculamos el total de nodos descendientes y ascendentes de todos los nodos, buscamos entre los pares que no posee un arco entre sí y elegimos aquel que su producto entre nodos descendes y ascendentes sea mayor.
def find_source_target_worst_scenary_incremental_edge(self): node_sources = {} node_targets = {} for node in self.nodes: node_sources[node] = self.find_total(node, "sources") node_targets[node] = self.find_total(node, "targets") max_path_long = 0 node_source = 0 node_target = 0 for x in self.nodes: for y in self.nodes: if x == y: continue if self.get_weight(x, y) != np.inf: continue total_path_long = node_sources[x] * node_targets[y] if total_path_long > max_path_long: max_path_long = total_path_long node_source = x node_target = y return { "source": node_source, "target": node_target }
Función para correrlo:
def insert_worst_edge(self, weight=1): self.clean_vars() result_found = self.find_source_target_worst_scenary_incremental_edge() source = result_found['source'] target = result_found['target'] return self.insert_edge(source, target, weight)
Disminuir el peso de un arco aleatorio
Recibe dos nodos que poseean un arco entre sí y le establece un peso. Por defecto es 1.
def decrease_weight(self, source, target, weight=1): self.clean_vars() if weight > self.get_weight(source, target): return -2 self.weight[np.logical_and(self.source == source, self.target == target)] = weight if not self.directed: self.weight[np.logical_and(self.source == target, self.target == source)] = weight self.last_edge_updated = np.array([source, target, weight]) self.last_edge_action = "DECREASE" self.sort_sources() return { "last_edge_updated": self.last_edge_updated, "last_edge_action": self.last_edge_action }
Busca entre dos nodos con arco y disminuye el peso. Lo establece en 0. Si el peso anterior es 0, lo hace -1
def decrease_random_weight(self, weight=1): count_max = 100 flag = 0 while True: source = np.random.choice(self.nodes, 1)[0] index_for_target = np.logical_or(np.in1d(self.nodes, self.target[source == self.source]), self.nodes == source) choisen = self.nodes[index_for_target] if choisen.size != 0: target = np.random.choice(choisen, 1)[0] weight_current = self.get_weight(source, target) if weight_current > 1: weight = 0 if weight_current > 0 else -1 break flag = flag + 1 if flag >= count_max: return -2 return self.edge_update(source, target, weight=weight)
Disminuir el peso de un arco en el peor escenario
Usamos la misma función de find_total. Encontramos dos nodos el cual su producto entre nodos descendentes y ascendentes, es el mayor.
def find_edge_worst_scenary_update_edge(self): node_sources = {} node_targets = {} for node in self.nodes: node_sources[node] = self.find_total(node, "sources") node_targets[node] = self.find_total(node, "targets") max_path_long = 0 node_source = 0 node_target = 0 for x in self.nodes: for y in self.nodes: if x == y: continue if self.get_weight(x, y) == np.inf: continue total_path_long = node_sources[x] + node_targets[y] if total_path_long > max_path_long: max_path_long = total_path_long node_source = x node_target = y return { "source": node_source, "target": node_target }
Luego de encontrar esos nodos, se disminuye su peso
def decrease_worst_weight(self): self.clean_vars() result_found = self.find_edge_worst_scenary_update_edge() source = result_found['source'] target = result_found['target'] weight_current = self.get_weight(source, target) weight_new = 0 if weight_current > 0 else -1 return self.decrease_weight(source, target, weight=weight_new)
Correr Algoritmo
Una clase que tiene como objetivo correr todos los algoritmos diseñados. Se arman en una lista y se corre con run_algorithm
cada algoritmo
class Algorithm(): def __init__(self, values): self.attempt = 30 self.graph = Graph.import_values(values) def list(self): return { 'static': ['dijkstra-apsp', 'floyd-warshall'], 'incremental': [ 'abm', 'rr-bfs-truncated', 'even-gazit', 'quinca', 'forest' ] } def run_algorithm(self, algorithm, dist=[[]]): if algorithm == 'dijkstra-apsp': return self.run_algorithm_dijkstra_apsp() if algorithm == 'floyd-warshall': return self.run_algorithm_floyd_warshall() if algorithm == 'rr-bfs-truncated': return self.run_algorithm_rr_bfs_truncated(dist) if algorithm == 'even-gazit': return self.run_algorithm_eg(dist) if algorithm == 'quinca': return self.run_algorithm_quinca(dist) if algorithm == 'abm': return self.run_algorithm_abm(dist) if algorithm == 'forest': return self.run_algorithm_forest(dist) def run_algorithm_floyd_warshall(self): t = time() Floyd_Warshall(self.graph) time_seconds = (time() - t) * 1000 return self.export_algorithm(dist, time_seconds) def run_algori ...
Correr grafo dinámicos y algoritmos
Antes de correr el laboratorio debes tener unas funciones predeterminadas necesarias:
Preparar carpetas de resultados
Está función genera las carpetas necesarias para guardas los resultados. Sea para exportar el grafo o exportar los resultados y gráficas.
Crea sub carpetas dentro divididos por el tipo de dinamismo del grafo y más adentro por el tipo de grafo en relación con su densidad.
def get_folder(folder, type_incremental, num_nodes, probability_edges=0): dirType = folder + "/" + type_incremental + "/" if not os.path.exists(dirType): os.mkdir(dirType) dirNode = dirType + str(num_nodes) + "/" if not os.path.exists(dirNode): os.mkdir(dirNode) if probability_edges == 0: return dirNode dirEdge = dirNode + str(probability_edges) + "/" if not os.path.exists(dirEdge): os.mkdir(dirEdge) return dirEdge
Exportar matrix
Para exportar la matrix de resultados (matris de distancia) de un grafo. Listo para un archivo
def export_matrix(matrix_result): matrix_to_export = [] for row in matrix_result: matrix_to_export.append([ str(a) if a == np.inf else str(int(a)) for a in row ]) return matrix_to_export
Laboratorio Parte 1
Prepara los valores de entrada, estos valores definen cómo será el laboratorio (tipo de grafo, si guarda los resultados, etc)
def run( type_incremental, num_nodes, probability_edges, num_try=30, attempt=10, saveFileExec=True, labExec=True, printer=True ): ## Define params labExec = bool(labExec) saveFileExec = bool(saveFileExec) num_nodes = int(num_nodes) num_try = int(num_try) attempt = int(attempt) probability_edges = float(probability_edges) results = [] dirGraphs = get_folder("synthetics", type_incremental, num_nodes, probability_edges) dirResults = get_folder("results", type_incremental, num_nodes, probability_edges) if printer: print("=============================================") print("Num nodes: ", num_nodes) print("Probability Edge: ", probability_edges) print("Graph count: ", num_try) print("Try algorithms: ", num_try) print("Exporting graphs: ", saveFileExec) print("Exec algorithms: ", labExec) print("\n")
Laboratorio Parte 2
Aquí se genera el grafo por cada iteración. Se ejecuta el dinamismo del grafo y luego se corre en la clase de Algoritmo. Los resultados se guardan en la variable results
.
### Define lab for exec for i in range(num_try): print("Loading graph [" + str(num_nodes) + ", " + str(probability_edges) + "]", i, " of ", num_try) graph = Graph.creategraph(num_nodes, probability_edges) dist_before = Dijkstra_apsp(graph) if type_incremental == "decrease_worst_edge": graph.decrease_worst_weight() elif type_incremental == "insert_worst_edge": graph.insert_worst_edge() elif type_incremental == "decrease_edge": graph.decrease_random_weight() else: graph.insert_random_edge(weights=[1]) calculate = Algorithm(graph.export_values()) calculate.attempt = attempt ### Save graph if saveFileExec: graph_values = { "graph": graph.export_values(), "dist": export_matrix(dist_before) } filename = dirGraphs + "g_" + str(i) + ".json" with open(filename, 'w') as outfile: json.dump(graph_values, outfile) ### Exec lab and save on results[] if labExec: for algorithm_name in calculate.list()['incremental']: times = calculate.run_algorithm(algorithm_name, dist_before) for time in times: results.append({ "algorithm": algorithm_name, "time": time, "nodes": len(calculate.graph.nodes), "edges": len(calculate.graph.source), "density": probability_edges, "type": type_incremental })
Laboratorio Parte 3
Se exportan los resultados a un archivo csv y se devuelve el nombre del archivo
### Set Data Frame df = pd.DataFrame(results) filename = dirResults + "result.csv" if labExec: if printer: print("To export: ", filename) df.to_csv (filename, index=False, header=True) print("END") return { "dataframe": df, "filename": filename }
Graficar resultados
Quizás laa parte más difícil. Antes del núcleo de este módulo hablemos de unas funciones que necesitamos:
Colores por algoritmo
Devuelve los colores para cada algoritmo
def return_colors(algorithms=[]): colors = { 'rr-bfs-truncated': "#0000FF", 'even-gazit': "#808000", 'quinca': "#FFFF00", 'abm': "#FF0000", 'forest': "#008080" } colors_return = [] if algorithms: for al in algorithms: colors_return.append(colors[al]) else: for c in colors: colors_return.append(colors[c]) return colors
Indexar una lista
Ordena una lista dado un orden de indices
def sortListByIndexing(data, sorted): newD = [] for iS in sorted: newD.append(data[iS]) return newD
Calcular límites para partir la gráfica
Algunos datos quedan muy alejados del resto, para eso partimos la gráfica para poder apreciar mejor. Se calcula el dato más alto entre todos y luego el más alto entre todos las demás barras. Se utiliza para partir la gráfica.
def calcLimit(totalTimes): timesByAlgoritmhs=[] for ii in range(len(totalTimes[0])): timesByAlgoritmhs.append([]) for jj in range(len(totalTimes)): for ii in range(len(totalTimes[jj])): timesByAlgoritmhs[ii].append(totalTimes[jj][ii]) max = 0 maxOtherLine = 0 min = np.inf indexMax = -1 for ii in range(len(timesByAlgoritmhs)): #print(timesByAlgoritmhs[ii]) for value in timesByAlgoritmhs[ii]: if value > max: max = value indexMax = ii maxOtherLineRead = [] count = 0 minDefined = False while True: for ii in range(len(timesByAlgoritmhs)): if ii == indexMax: if not minDefined: for value in timesByAlgoritmhs[ii]: if value < min: min = value continue for value in timesByAlgoritmhs[ii]: if value > maxOtherLine and value not in maxOtherLineRead: maxOtherLine = value if maxOtherLine > min: count = count + 1 if count == 5: break min = maxOtherLine minDefined = True maxOtherLineRead.append(maxOtherLine) maxOtherLine = 0 else: break return [maxOtherLine, min, max]
Algoritmos más rápidos
Otra manera de visualizar mejor las gráficas es deshaciendo los algoritmos que demoran demasiando, dejando solo unos más rápido. Tener en cuenta que el algoritmo abm
siempre tiene que aparecer.
def algorithms_faster(data, fasterNum=3): algoritms_selected = [] while True: dd = data.groupby('algorithm')['time'].mean().to_frame().sort_values(by=['time']) algoritms_selected = dd.index.tolist()[:fasterNum] if 'abm' in algoritms_selected: break fasterNum += 1 return algoritms_selected
Graficar resultados Parte 1
Se definen todos valores, como el modo de importar una gráfica, entre otras.
def draw_plot_seaborn_bar( data, num_nodes=100, type_incremental='insert_edge', densityGroup=True, densityTypes = 0 ): width = 0.85 colors_g = sns.xkcd_palette(['windows blue', 'amber', 'greyish', 'faded green', 'dusty purple']) dir = get_folder("figs", type_incremental, num_nodes) groupF = 'density' if densityGroup else 'algorithms' densityF = 'small' if densityTypes == 0 else 'large' densityValues = list(dict.fromkeys(data['density'].values.tolist())) if densityTypes == 0: densityValues = [i for i in densityValues if i < 0.08] else: densityValues = [i for i in densityValues if i > 0.08] if densityValues: data = data.loc[data['density'].isin(densityValues)] filename = dir + "fig_" + type_incremental + "_" + str(num_nodes) + "_" + groupF + "_" + densityF dataToPrint = None
Graficar resultados Parte 2
Grafica el primer tipo de gráfica.
if densityGroup: ### Print FIG Times vs Density, group Algorithms) faster_al = algorithms_faster(data, fasterNum=4) data_filter = data.loc[data['algorithm'].isin(faster_al)] dataToPrint = data_filter ax = sns.barplot(y='time', x='density', data=data_filter, hue="algorithm", capsize=.2, palette=return_colors()) ax.set(xlabel='Edge Density', ylabel='Time (Miliseconds)')
Graficar resultados Parte 3
La siguiente parte es la que divide la gráfica para datos demasiado elevados.
else: ### Print FIG Times vs Algorithms (Group Density) fig, ax = plt.subplots() data_gg = data.groupby('algorithm')['time'].mean().to_frame() densitySorted = sorted(densityValues) totalLabels = [] totalTimes = [] totalDensity =[] totalColor = [] valuesABM = [] dataToPrint = data for index_d in range(len(densityValues)): xLabels = data_gg.index.to_list() density = densityValues[index_d] indexForColor = densitySorted.index(densityValues[index_d]) color = colors_g[indexForColor] data_filtered = data.loc[data['density'] == density] data_filtered_g = data_filtered.groupby('algorithm')['time'].mean().to_frame() listed = data_filtered_g['time'].to_list() #listed, xLabels = [ list(tuple) for tuple in zip(*sorted(zip(listed, xLabels), reverse=True))] totalDensity.append(density) totalLabels.append(xLabels) totalTimes.append(listed) totalColor.append(color) for jj in range(len(xLabels)): if xLabels[jj] == 'abm': valuesABM.append(listed[jj]) ABMsorted = sorted(range(len(valuesABM)), key=lambda k: valuesABM[k]) ABMsorted.reverse() limit = calcLimit(totalTimes) ### Start to Draw fig, (ax1,ax2) = plt.subplots(2,1,sharex=True, figsize=(5,6)) ax1.spines['bottom'].set_visible(False) ax1.tick_params(axis='x',which='both',bottom=False) ax2.spines['top'].set_visible(False) ax2.set_ylim(0, limit[0]) ax1.set_ylim(limit[1],limit[2]) for i in ABMsorted: density = round(totalDensity[i], 2) xLabels = totalLabels[i] listed = totalTimes[i] color = totalColor[i] bars1 = ax1.bar(xLabels, listed, width, label=str(density), color=color) bars2 = ax2.bar(xLabels, listed, width, label=str(density), color=color) for tick in ax2.get_xticklabels(): tick.set_rotation(0) d = .015 kwargs = dict(transform=ax1.transAxes, color='k', clip_on=False) ax1.plot((-d, +d), (-d, +d), **kwargs) ax1.plot((1 - d, 1 + d), (-d, +d), **kwargs) kwargs.update(transform=ax2.transAxes) ax2.plot((-d, +d), (1 - d, 1 + d), **kwargs) ax2.plot((1 - d, 1 + d), (1 - d, 1 + d), **kwargs) for b1, b2 in zip(bars1, bars2): posx = b2.get_x() + b2.get_width()/2. if b2.get_height() > limit[0]: ax2.plot((posx-3*d, posx+3*d), (1 - d, 1 + d), color='k', clip_on=False, transform=ax2.get_xaxis_transform()) if b1.get_height() > limit[1]: ax1.plot((posx-3*d, posx+3*d), (- d, + d), color='k', clip_on=False, transform=ax1.get_xaxis_transform()) ax1.set_ylabel('Times (Miliseconds)') ax1.legend() plt.xticks(rotation=45)
Graficar resultados Parte 4
Última parte, guarda la gráfica en un archivo y los resultados en un csv.
if True: if dataToPrint is not None: dataToPrint.to_csv (filename + '.csv', index=False, header=True) plt.tight_layout() plt.savefig(filename + '.png', dpi=150) plt.cla() plt.clf()
Graficar Resultados por dos tipos
Se expone las dos funciones para representar un modo o el otro.
def draw_all_plot(data, num_nodes, type_incremental, densityTypes = 0): data_filter = data.loc[ (data['nodes'] == num_nodes) & (data['type'] == type_incremental)] data_filter_one_calc = data_filter.groupby('algorithm')['time'].mean().to_frame() data_filter_one_calc['algorithm'] = data_filter_one_calc.index.values #PLOT draw_plot_seaborn_bar( data_filter, num_nodes=num_nodes, type_incremental=type_incremental, densityGroup=False, densityTypes=densityTypes ) # Draw Group Density def draw_group_plot(data, num_nodes, type_incremental, densityTypes=0): data_filter = data.loc[ (data['nodes'] == num_nodes) & (data['type'] == type_incremental)] data_filter_one_calc = data_filter.groupby('algorithm')['time'].mean().to_frame() data_filter_one_calc['algorithm'] = data_filter_one_calc.index.values draw_plot_seaborn_bar( data_filter, num_nodes=num_nodes, type_incremental=type_incremental, densityGroup=True, densityTypes=densityTypes )
Módulo Principal
Aquí sucede toda la magia, donde se unen todos los módulos anteriores y se establecen los parámetros a seguir
from generate_and_result import run from export_fig import draw_all_plot, draw_group_plot import pandas as pd def read_files(files=[]): datas = pd.DataFrame() for filename in files: data_import = pd.read_csv(filename) datas = pd.concat([datas, data_import]) return datas def exec( num_graphs_random=30, try_exec_each_graph=10 ): types = [ 'insert_edge', 'decrease_edge', 'insert_worst_edge', 'decrease_worst_edge' ] num_nodes = [ 100, 1000, 10000 ] probability_edges = [ 0.1, 0.2, 0.3, 0.4, 0.5, 0.01, 0.02, 0.03, 0.04, 0.05 ] files = [] for type in types: for num_node in num_nodes: for pp in probability_edges: # Dataframe result = run( type, num_node, pp, num_try = num_graphs_random, attempt = try_exec_each_graph, saveFileExec = True, labExec=True ) files.append(result['filename']) df = read_files(files) for type in types: for num_node in num_nodes: draw_all_plot(df, num_node, type, densityTypes=0) draw_group_plot(df, num_node, type, densityTypes=0) draw_all_plot(df, num_node, type, densityTypes=1) draw_group_plot(df, num_node, type, densityTypes=1) exec()
Comentarios