Skip to content
Extraits de code Groupes Projets

Comparer les révisions

Les modifications sont affichées comme si la révision source était fusionnée avec la révision cible. En savoir plus sur la comparaison des révisions.

Source

Sélectionner le projet cible
No results found

Cible

Sélectionner le projet cible
  • leblancc/pygroupj4
1 résultat
Afficher les modifications
Validations sur la source (22)
# This file is a template, and might need editing before it works on your project.
# To contribute improvements to CI/CD templates, please follow the Development guide at:
# https://docs.gitlab.com/ee/development/cicd/templates.html
# This specific template is located at:
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Getting-Started.gitlab-ci.yml
# This is a sample GitLab CI/CD configuration file that should run without any modifications.
# It demonstrates a basic 3 stage CI/CD pipeline. Instead of real tests or scripts,
# it uses echo commands to simulate the pipeline execution.
#
# A pipeline is composed of independent jobs that run scripts, grouped into stages.
# Stages run in sequential order, but jobs within stages run in parallel.
#
# For more information, see: https://docs.gitlab.com/ee/ci/yaml/index.html#stages
image: registry.forge.uclouvain.be/inl/containers/runner-python
stages: # List of stages for jobs, and their order of execution
- test
variables:
GOUTPUT: graph_test.bin
ROUTPUT: out_test.bin
test-job: # This job runs in the test stage.
stage: test
script:
- echo "Running unit tests..."
- python3 create_graph.py --ntf sample_ntf/small.ntf --output $GOUTPUT
- python3 sp.py -f $ROUTPUT $GOUTPUT
- python3 verify_output.py $ROUTPUT
only:
- main
artifacts:
paths:
- $GOUTPUT
- $ROUTPUT
# Python
Python code for the shortest-path project.
\ No newline at end of file
Fichier ajouté
import random
import argparse
import warnings
import os
def create_random_graph(args):
nb_nodes = args.nodes
nb_links = args.links
random.seed(args.seed)
nodes = dict()
for _ in range(nb_links):
node_a = random.randint(0, nb_nodes - 1)
node_b = random.randint(0, nb_nodes - 1)
while node_b == node_a:
node_b = random.randint(0, nb_nodes - 1)
c_ab = random.randint(1, 10)
nodes.setdefault(node_a, list()).append((node_b, c_ab))
nodes.setdefault(node_b, list())
return nodes, nb_links
def ntf_parse(args):
with open(args.ntf) as fd:
data = fd.read().split("\n")
mapping = dict()
nodes = dict()
for line in data:
tab = line.split(" ")
node_a = tab[0]
node_b = tab[1]
node_a = mapping.setdefault(node_a, len(mapping))
node_b = mapping.setdefault(node_b, len(mapping))
c_ab = int(tab[2])
nodes.setdefault(node_a, list()).append((node_b, c_ab))
nodes.setdefault(node_b, list())
return nodes, len(data)
def to_header_file(nodes, nb_links, output):
# Check that the extension is correct.
_, file_extension = os.path.splitext(output)
if file_extension != ".h":
warnings.warn(
f'The extension of the output file is not ".h": {output}')
s = ""
# Create the array containing the links.
for node_id, neighs in nodes.items():
for (neigh_id, cost) in neighs:
s += f"\t{{{node_id}, {neigh_id}, {cost}}},\n"
s = s[:-2] + "\n"
with open(output, "w+") as fd:
content = f'#include <stdint.h>\n\n#define NB_NODES {len(nodes)}\n#define NB_LINKS {nb_links}\n\nint64_t links[NB_LINKS][3] = {{\n{s}}};'
fd.write(content)
def to_binary_file(nodes, nb_links, output):
nb_nodes = len(nodes)
with open(output, "wb+") as fd:
fd.write(nb_nodes.to_bytes(4, "big"))
fd.write(nb_links.to_bytes(4, "big"))
print(nodes)
for node in nodes:
for j, cost in nodes[node]:
print(node, j, cost)
fd.write(node.to_bytes(4, "big"))
fd.write(j.to_bytes(4, "big"))
fd.write(cost.to_bytes(4, "big", signed=True))
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-t", "--ntf", type=str, default=None,
help="Parse an NTF file instead of creating a graph")
parser.add_argument("-o", "--output", type=str,
help="Output file", default="graph.bin")
parser.add_argument("-n", "--nodes", type=int,
help="Number of nodes. Unused if '--ntf'", default=5)
parser.add_argument("-l", "--links", type=int,
help="Number of links. Unused if '--ntf'", default=10)
parser.add_argument("-c", "--c-header", action="store_true",
help="Writes the graph as a C header file (.h) instead of a binary file")
parser.add_argument("-s", "--seed", type=int, help="Seed for random generation of the graph", default=42)
args = parser.parse_args()
if args.ntf:
graph, nb_links = ntf_parse(args)
else:
graph, nb_links = create_random_graph(args)
if args.c_header:
to_header_file(graph, nb_links, args.output)
else:
to_binary_file(graph, nb_links, args.output)
graph {
0 [label=0]
2 [label=2]
0 -- 2 [label=4 dir=forward]
0 [label=0]
4 [label=4]
0 -- 4 [label=7 dir=forward]
0 [label=0]
1 [label=1]
0 -- 1 [label=4 dir=forward]
2 [label=2]
0 [label=0]
2 -- 0 [label=3 dir=forward]
1 [label=1]
0 [label=0]
1 -- 0 [label=9 dir=forward]
1 [label=1]
4 [label=4]
1 -- 4 [label=7 dir=forward]
1 [label=1]
3 [label=3]
1 -- 3 [label=10 dir=forward]
1 [label=1]
2 [label=2]
1 -- 2 [label=2 dir=forward]
4 [label=4]
0 [label=0]
4 -- 0 [label=9 dir=forward]
3 [label=3]
2 [label=2]
3 -- 2 [label=5 dir=forward]
}
Fichier ajouté
#include <stdint.h>
#define NB_NODES 5
#define NB_LINKS 10
int32_t LINKS[NB_LINKS][3] = {
{0, 1, 3},
{0, 2, 2},
{1, 2, 1},
{1, 3, -2},
{2, 3, 4},
{2, 4, 1},
{3, 4, 2},
{0, 3, 1},
{1, 4, 2},
{2, 0, -1}
};
graph.png

33,6 ko

import time
from sp import bellman_ford
def main():
# Read graph and global information from text file
n, nb_nodes, links_size, s, min_cost, max_cost, graphs = read_graphs_from_file("random_graphs.txt")
execution_time = run_random_test(n, nb_nodes, links_size, s, graphs)
print(f"Do you want to add the test results to a text file? (y/n) ", end='')
user_input = input().strip().lower()
if user_input == 'y':
write_to_file(n, nb_nodes, links_size, s, min_cost, max_cost, execution_time)
print("The results have been added to the 'results.txt' file.")
def write_to_file(n, nb_nodes, links_size, s, min_cost, max_cost, execution_time):
file_name = "results.txt"
try:
file_exists = False
with open(file_name, 'r'):
file_exists = True
except FileNotFoundError:
pass
with open(file_name, 'a') as file:
if not file_exists:
file.write("Number of iterations, Number of nodes, Links size, Source node, Minimum cost, Maximum cost, Time for BF, Time for SPFA_SLF\n")
file.write(f"{n} {nb_nodes} {links_size} {s} {min_cost} {max_cost} {execution_time:.8f}\n")
def read_graphs_from_file(file_path):
graphs = []
with open(file_path, 'r') as file:
lines = file.readlines()
# Read the global information from the first line
n, nb_nodes, links_size, s, min_cost, max_cost = map(int, lines[0].strip().split(' '))
# Initialize an empty graph
graph = []
for line in lines[1:]:
line = line.strip()
if line == "=====":
# End of the current graph, append it to the list of graphs and start a new one
graphs.append(graph)
graph = []
else:
# Read the edge information and add it to the current graph
edge = list(map(int, line.split(' ')))
graph.append(edge)
return n, nb_nodes, links_size, s, min_cost, max_cost, graphs
def run_random_test(n, nb_nodes, links_size, s, graphs):
total_time = 0
for graph in graphs:
start_time = time.perf_counter()
bellman_ford(graph, s, nb_nodes, False)
end_time = time.perf_counter()
total_time += end_time - start_time
execution_time = total_time / n
print(f"Average execution time: {execution_time:.8f} seconds")
return execution_time
if __name__ == "__main__":
main()
\ No newline at end of file
Ce diff est replié.
Fichier ajouté
[ZoneTransfer]
ZoneId=3
HostUrl=about:internet
Number of iterations, Number of nodes, Links size, Source node, Minimum cost, Maximum cost, Time for BF, Time for SPFA_SLF
10000 5 10 0 -4 4 0.00003108
10000 5 10 0 -4 4 0.00001678
10000 5 10 0 -4 4 0.00001427
10000 5 10 0 -4 4 0.00001363
10000 25 50 0 -10 10 0.00030640
10000 25 50 0 -10 10 0.00026869
10000 25 50 0 -10 10 0.00029570
10000 25 50 0 -10 10 0.00028216
10000 25 50 0 -10 10 0.00027970
10000 25 50 0 -10 10 0.00035282
10000 25 50 0 -10 10 0.00030537
10000 25 50 0 -10 10 0.00027629
1000 50 350 0 -25 25 0.00518876
1000 50 350 0 -25 25 0.00490556
1000 50 350 0 -25 25 0.00489480
1000 50 350 0 -25 25 0.00485835
100 750 1200 0 -50 50 0.23045785
100 750 1200 0 -50 50 0.20430856
100 750 1200 0 -50 50 0.19876927
10 2500 50000 0 -1500 1500 43.78349648
#Plus de négatif que de positif
1000 50 120 0 -50 5 0.00147268
1000 50 120 0 -50 5 0.00153322
1000 50 120 0 -50 5 0.00162718
1000 50 120 0 -50 5 0.00163333
1000 50 350 0 -50 5 0.00529458
1000 50 350 0 -50 5 0.00524702
1000 50 350 0 -50 5 0.00482742
1000 50 350 0 -50 5 0.00497017
200 350 2000 0 -500 5 0.21386693
200 350 2000 0 -500 5 0.21833981
#Plus de négatif que de positif
200 350 2000 0 -50 150 0.19522310
200 350 2000 0 -50 150 0.20343573
0 1 1
1 0 4
0 2 -1
2 1 9
\ No newline at end of file
import math
import argparse
import sys
# # Données de graph.h directement intégrées dans le code
# NB_NODES = 5
# NB_LINKS = 7
# LINKS = [
# [2,0,1],
# [0,1,2],
# [0,3,3],
# [0,4,1],
# [1,2,-1],
# [3,1,-2],
# [3,4,5]
# ]
def get_file_infos(links):
"""
Récupère les informations basiques du graphe : le nombre de noeuds et de liens.
"""
nb_nodes = max(max(link[:2]) for link in links) + 1
nb_edges = len(links)
return nb_nodes, nb_edges
def bellman_ford(links, s, nb_nodes, verbose):
"""
Exécute l'algorithme de Bellman-Ford avec comme noeud source `s`.
Retourne un tableau de distances pour atteindre chaque noeud depuis `s`,
ainsi que le chemin pour atteindre ces noeuds (tableau de précédence).
Si le noeud est isolé ou qu'un cycle négatif est trouvé, retourne une distance
infinie pour chaque autre noeud, et une distance de 0 pour `s`.
"""
# Initialisation.
dist = [math.inf] * nb_nodes
dist[s] = 0 # Le noeud source est à distance 0 de lui-meme.
path = [-1] * nb_nodes
# Iteration de Bellman-Ford.
for _ in range(nb_nodes-1):
for j in range(len(links)):
node_from, node_to, cost = links[j][0], links[j][1], links[j][2]
if (dist[node_from] != math.inf and dist[node_to] > dist[node_from] + cost):
dist[node_to] = dist[node_from] + cost
path[node_to] = node_from
# Detection de cycle negatif.
for j in range(len(links)):
node_from, node_to, cost = links[j][0], links[j][1], links[j][2]
if (dist[node_from] != math.inf and dist[node_to] > dist[node_from] + cost):
if verbose:
print("Cycle négatif détecté")
dist = [math.inf] * nb_nodes
dist[s] = 0
path = [-1] * nb_nodes
return dist, path
def get_path(dest, path, source):
"""
Retourne une liste contenant le chemin de `source` vers `dest`
en utilisant le tableau de précédence `path`.
"""
r = [dest]
i = dest
while i != source:
r.insert(0, path[i])
i = path[i]
return r
def get_max(dist, s):
"""
Retourne l'indice du noeud dont il existe un chemin de `s` vers ce noeud
et le cout de ce chemin est le plus élevé parmis tous les noeuds ayant un chemin
depuis `s`.
"""
max_cost = -math.inf
max_node = s
for node_idx in range(len(dist)):
if node_idx != s and dist[node_idx] != math.inf and dist[node_idx] >= max_cost:
max_cost = dist[node_idx]
max_node = node_idx
if max_cost == -math.inf:
if dist[s] != math.inf and dist[s] >= max_cost:
max_cost = dist[s]
return max_cost, max_node
def read_graph():
"""
Récupère le graphe directement à partir des données définies dans le code.
"""
global LINKS, NB_NODES
nb_nodes, nb_edges = get_file_infos(LINKS)
return LINKS, nb_nodes
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="LEPL1503 - Algorithme de plus court chemin")
parser.add_argument(
"input_file", help="chemin vers le fichier d'instance representant le graphe a traiter.")
parser.add_argument("-f", help="chemin vers le fichier qui contiendra le resultat de programme, au format specifie dans l'enonce. Defaut : stdout.",
type=argparse.FileType("wb"), default=sys.stdout)
parser.add_argument(
"-v", help="autorise les messages de debug. Si ce n'est pas active, aucun message de ce type ne peut etre affiche, excepte les messages d'erreur en cas d'echec. Defaut : False.", action="store_true")
args = parser.parse_args()
verbose = args.v
output_fd = args.f
nb_nodes = None
nb_edges = None
if verbose:
# Exemple de message que vous pouvez ecrire si le mode verbose est actif.
print(args, file=sys.stderr)
graph, nb_nodes = read_graph()
if output_fd == sys.stdout or output_fd == sys.stderr:
print("Nombre de noeuds: " + str(nb_nodes))
else:
output_fd.write(nb_nodes.to_bytes(4, "big"))
for source in range(nb_nodes):
dist, path = bellman_ford(graph, source, verbose)
# Ces messages ne sont pas des messages de debug.
# Ils peuvent donc etre affiches (uniquement si la sortie choisie est stdout ou stderr)
# meme si le mode verbose n'est pas actif.
for source in range(1):
dist, path = bellman_ford(graph, source, verbose)
for i in range(nb_nodes):
print(f"Distance to node {i} : {dist[i]}")
print(f"Path to node {i} : {path[i]}")
import argparse
import struct
OKGREEN = '\033[92m'
FAIL = '\033[91m'
def verify_output(file):
with open(file, "rb") as fd:
# First 4 bytes should be the number of nodes.
data = fd.read(4)
nb_nodes, = struct.unpack(">l", data)
# The file should contain exactly nb_nodes entries.
for _ in range(nb_nodes):
# An entry is 4 + 4 + 8 + 4 + len(path) * 4 bytes.
data = fd.read(20)
# Index of the node, distance value, path length (number of hops).
source_idx, destination_idx, _cost, path_len = struct.unpack(">llql", data)
# The node index lies within the limits.
assert source_idx >= 0 and source_idx < nb_nodes, FAIL + f"The source idx does not have the correct format: {source_idx}"
assert destination_idx >= 0 and destination_idx < nb_nodes, FAIL + f"The destination idx does not have the correct format: {destination_idx}"
# The path len can be nul if there is no path.
# The shortest path cannot contain loops.
assert path_len >= 1 and path_len <= nb_nodes, FAIL + f"The path length is too long or nul: {path_len}"
if path_len > 0:
for i in range(path_len):
data = fd.read(4)
hop_idx, = struct.unpack(">l", data)
# Same... the node index lies within the limits.
assert hop_idx >= 0 and hop_idx < nb_nodes, FAIL + f"A hop of the path does not have the correct format {hop_idx}"
# The first node should be the source.
if i == 0:
assert hop_idx == source_idx, FAIL + f"The first node of the path is not the source: {hop_idx} (and source is {source_idx})"
# The last node should be the destination.
if i == path_len - 1:
assert hop_idx == destination_idx, FAIL + f"The first node of the path is not the destination: {hop_idx} (and destination is {destination_idx})"
# The file does not contain anymore bytes
assert fd.read() == b"", FAIL + "The file is not empty"
print(OKGREEN + "The file has the correct format!\nThis does not mean that it solves the shortest path problem, but at least it contains readable information...")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"file", type=str, help="Binary file generated by the project to inspect")
args = parser.parse_args()
verify_output(args.file)
import graphviz
import struct
import argparse
import os
def read_graph(filename):
with open(filename, "rb") as fd:
data = fd.read(8)
_, nb_links = struct.unpack(">ll", data)
graph = dict()
for _ in range(nb_links):
data = fd.read(12)
node_1, node_2, cost_12 = struct.unpack(">lll", data)
graph.setdefault(node_1, list()).append((node_2, cost_12))
return graph
def plot_graph(graph, output_filepath):
file, file_extension = os.path.splitext(output_filepath)
g = graphviz.Graph(format=file_extension[1:])
for node in graph:
for nei_id, cost in graph[node]:
g.node(f"{node}", label=f"{node}")
g.node(f"{nei_id}", label=f"{nei_id}")
g.edge(f"{node}", f"{nei_id}", label=f"{cost}", dir="forward")
g.render(file)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"file", type=str, help="Input file (binary) representing the graph")
parser.add_argument("save", type=str,
help="Save the graph visualization in the indicated path")
args = parser.parse_args()
graph = read_graph(args.file)
plot_graph(graph, args.save)