package org.example;
import org.example.entity.BellmanFordResult;
import org.example.entity.PathResult;
import org.graphstream.graph.Edge;
import org.graphstream.graph.Graph;
import org.graphstream.graph.Node;
import org.graphstream.graph.implementations.SingleGraph;
import org.graphstream.stream.file.FileSource;
import org.graphstream.stream.file.FileSourceFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Cette classe fournit des méthodes utilitaires pour manipuler des graphes représentant
* des villes et calculer les plus courts chemins pour un vélo électrique.
*/
public class GraphManipulation {
/**
* Reconstruit le chemin le plus court jusqu'à un nœud de destination
* à partir des résultats de Bellman-Ford.
*
* @param bellmanFordResult Résultat de l'algorithme Bellman-Ford contenant les coûts et prédécesseurs.
* @param endNode Le nœud de destination du chemin à reconstruire.
* @return Un objet PathResult contenant la liste des nœuds du chemin (dans l'ordre source → destination)
* et le coût total pour atteindre endNode.
*/
public static PathResult getPath(BellmanFordResult bellmanFordResult, Node endNode){
Map<Node, Node> pred = bellmanFordResult.pred();
List<Node> chemin = new ArrayList<>();
Node current = endNode;
while (current != null) {
chemin.add(current);
current = pred.get(current);
}
return new PathResult(chemin.reversed(),bellmanFordResult.mapCost().get(endNode));
}
/**
* Marque un chemin dans le graphe pour l'affichage.
* Les nœuds et les arêtes du chemin seront mis en évidence.
*
* @param chemin Liste des nœuds représentant le chemin (de la source à la destination).
*/
public static void markPath( List<Node> chemin) {
for (int i = 0; i < chemin.size(); i++) {
Node node = chemin.get(i);
if (i < chemin.size() - 1) {
Node nextNode = chemin.get(i + 1);
Edge edge = node.getEdgeBetween(nextNode);
if (edge != null) {
edge.setAttribute("ui.class", "inPath");
}
}
}
}
/**
* Calcule le coût en électricité pour parcourir une arête d'un graphe représentant
* une ville pour un vélo électrique.
* @param edge L'arête dont on souhaite calculer le coût.
* @return Le coût en Wh pour parcourir cette arête. Les descentes donnent un coût négatif.
*/
public static Float getCostOfTheArete(Edge edge) {
Integer aHauteur = edge.getSourceNode().getAttribute("hauteur",Integer.class);
Integer bHauteur = edge.getTargetNode().getAttribute("hauteur",Integer.class);
return (aHauteur<=bHauteur)? (5*(bHauteur-aHauteur)+2f) : ((bHauteur-aHauteur)/2f);
}
/**
* Charge un graphe depuis un fichier .dgs et retourne l'objet Graph correspondant.
*
* @param path Chemin vers le fichier .dgs.
* @return L'objet Graph chargé depuis le fichier.
*/
public static Graph getTheFileFromPath(String path) {
Graph graph = new SingleGraph("LoadedGraph");
try {
FileSource fs = FileSourceFactory.sourceFor(path);
if (fs == null) {
throw new IOException("Format de fichier non reconnu : " + path);
}
fs.addSink(graph);
fs.readAll(path);
fs.removeSink(graph);
} catch (Exception e) {
System.err.println("Erreur lors du chargement du graphe : " + e.getMessage());
return graph;
}
return graph;
}
/**
* Implémente l'algorithme de Bellman-Ford pour calculer le plus court chemin
* depuis un nœud source dans un graphe orienté avec des coûts qui peuvent être négatifs.
*
* @param graph Le graphe orienté sur lequel calculer les plus courts chemins.
* @param startNode Le nœud source depuis lequel on calcule les plus courts chemins.
* @return Un objet BellmanFordResult contenant les maps des coûts et des prédécesseurs.
* 1. mapCost : les coûts minimaux pour atteindre chaque nœud depuis startNode.
* 2. pred : les prédécesseurs de chaque nœud pour reconstruire le plus court chemin.
*/
public static BellmanFordResult BellmanFord(Graph graph, Node startNode){
Map<Node,Float> mapCost = new HashMap<>();
Map<Node, Node> pred = new HashMap<>();
graph.forEach( node -> {
float value = node == startNode ? 0f: Float.POSITIVE_INFINITY;
mapCost.put(node, value);
pred.put(node, null);
});
AtomicBoolean updated = new AtomicBoolean(true);
for (int k = 1; k < graph.getNodeCount() - 1 && updated.get(); k++) {
updated.set(false);
graph.edges().forEach(edge ->{
float edgeCost = edge.hasAttribute("weight")
? ((Number) edge.getAttribute("weight")).floatValue()
: getCostOfTheArete(edge);
Node sourceNode =edge.getSourceNode();//u
Node targetNode =edge.getTargetNode();//v
if (mapCost.get(sourceNode) +edgeCost<mapCost.get(targetNode)) {
mapCost.put(targetNode,mapCost.get(sourceNode) +edgeCost);
pred.put(targetNode, sourceNode);
updated.set(true);
}
});
}
return new BellmanFordResult(mapCost,pred);
}
}
package org.example;
import org.example.entity.BellmanFordResult;
import org.example.entity.PathResult;
import org.graphstream.graph.Graph;
import org.graphstream.graph.Node;
import static org.example.GraphManipulation.*;
public class Main {
public static void main(String[] args) {
System.setProperty("org.graphstream.ui", "swing");
Graph graph = getTheFileFromPath("src/main/resources/graphe_lehavre.dgs");
if(graph != null){
Node startNode = graph.getNode("33317746");
Node endNode = graph.getNode("144477138");
BellmanFordResult result = BellmanFord(graph, startNode);
PathResult pathResult = getPath(result,endNode);
markPath(pathResult.path());
System.out.println("Cout du plus court chemin : " +pathResult.cost());
System.out.println("chemin trouvé : " + pathResult.path());
graph.setAttribute("ui.stylesheet", "url(file://src/main/resources/style.css)");
graph.display(false);
}
}
}
\ No newline at end of file
package org.example.entity;
import org.graphstream.graph.Node;
import java.util.Map;
public record BellmanFordResult ( Map<Node, Float> mapCost,Map<Node, Node> pred){}
package org.example.entity;
import org.graphstream.graph.Node;
import java.util.List;
public record PathResult(List<Node> path, Float cost) { }
Ce diff est replié.
Ce diff est replié.
Ce diff est replié.
Ce diff est replié.
Ce diff est replié.
Ce diff est replié.
Ce diff est replié.
Ce diff est replié.
node{
size:0px;
}
edge{
arrow-size : 0px, 0px;
}
edge.inPath{
size:4;
fill-color: red;
}
package org.example;
import org.example.entity.BellmanFordResult;
import org.example.entity.PathResult;
import org.graphstream.algorithm.BellmanFord;
import org.graphstream.graph.Graph;
import org.graphstream.graph.Node;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.*;
import static org.example.GraphManipulation.*;
import static org.example.Utils.getRandomNode;
import static org.example.Utils.saveDat;
/**
* Classe de test pour comparer les performances de l'algorithme Bellman-Ford
* implémenté manuellement avec celui de la bibliothèque GraphStream.
*/
public class MainTest {
private static final int DEFAULT_TESTS_PER_GRAPHCOMPARATE= 5;
private static final int DEFAULT_TESTS_PER_GRAPH_MY_BELLMAN = 25;
private static final int DEFAULT_TIMEOUT_SECONDS = 90;
private static final String GNUPLOTPATH = "gnuplot/dat/";
private static final String GRAPHPATH = "src/main/resources/";
public static void main(String[] args) {
System.setProperty("org.graphstream.ui", "swing");
File[] dgsFiles = new File(GRAPHPATH).listFiles((dir, name) -> name.endsWith(".dgs"));
if(dgsFiles !=null) {
getAllInfoGraph(dgsFiles);
setUpComparatorOnlyMyAlgorithme(dgsFiles);
setUpComparatorBetween(dgsFiles);
}
}
/**
* Affiche les informations de base (nombre de nœuds et d'arêtes) pour chaque graphe.
* @param dgsFiles Tableau de fichiers .dgs représentant des graphes.
*/
public static void getAllInfoGraph(File[] dgsFiles) {
for (File file : dgsFiles) {
Graph graphFile = getTheFileFromPath(file.getPath());
System.out.println(file.getName() + " : n Node(" +graphFile.getNodeCount() +") - n Edge(" +graphFile.getEdgeCount()+")");
}
}
/**
* Effectue plusieurs tests du Bellman-Ford maison sur chaque graphe,
* avec des nœuds aléatoires, et sauvegarde les résultats dans des fichiers.
* @param dgsFiles Tableau de fichiers .dgs représentant des graphes.
*/
public static void setUpComparatorOnlyMyAlgorithme(File[] dgsFiles) {
for (File file : dgsFiles) {
System.out.println(file.getName());
System.out.println("==============================");
List<List<Double>> result = new ArrayList<>(Collections.emptyList());
Graph graphFile = getTheFileFromPath(file.getPath());
for (int i = 0; i < DEFAULT_TESTS_PER_GRAPH_MY_BELLMAN; i++) {
result.add(
BellmanComparatorBetween(graphFile, getRandomNode(graphFile), getRandomNode(graphFile), false));
}
String filename = String.format(GNUPLOTPATH + "2_myBellman/" + (file.getName().split("\\.")[0].split("_")[1]) + ".dat");
saveDat(filename, result);
}
}
/**
* Effectue la comparaison entre l'algorithme maison et GraphStream pour chaque graphe.
* Les arêtes sont préalablement pondérées pour l'équité des tests.
* @param dgsFiles Tableau de fichiers .dgs représentant des graphes.
*/
public static void setUpComparatorBetween(File[] dgsFiles) {
Arrays.sort(dgsFiles, (a, b) -> a.getName().equals("graphe_lehavre.dgs") ? -1
: b.getName().equals("graphe_lehavre.dgs") ? 1 : a.getName().compareTo(b.getName()));// juste pour tester leHavre en premier car plus rapide
assert dgsFiles != null;
for (File file : dgsFiles) {
System.out.println(file.getName());
System.out.println("==============================");
List<List<Double>> result = new ArrayList<>(Collections.emptyList());
Graph graphFile = getTheFileFromPath(file.getPath());
// Ajout du poids avant de calculer le temps pour équiter entre algorithme
graphFile.edges().forEach(edge -> {
double cost = getCostOfTheArete(edge);
edge.setAttribute("weight", cost);
});
for (int i = 0; i < DEFAULT_TESTS_PER_GRAPHCOMPARATE; i++) {
result.add(
BellmanComparatorBetween(graphFile, getRandomNode(graphFile), getRandomNode(graphFile), true));
}
String filename = String.format(GNUPLOTPATH + "3_compare/" + (file.getName().split("\\.")[0].split("_")[1]) + ".dat");
saveDat(filename, result);
}
}
/**
* Compare les temps d'exécution du Bellman-Ford maison et de GraphStream pour un même couple de nœuds.
* Les résultats sont renvoyés sous forme de liste de durées en millisecondes.
*
* @param graph Le graphe sur lequel effectuer les calculs.
* @param startNode Le nœud de départ.
* @param endNode Le nœud d'arrivée.
* @param withGraphStream Si vrai, exécute également l'algorithme GraphStream.
* @return Liste des durées (en ms) pour chaque algorithme exécuté.
*/
public static ArrayList<Double> BellmanComparatorBetween(Graph graph, Node startNode, Node endNode,
Boolean withGraphStream) {
ArrayList<Double> results = new ArrayList<>();
System.out.println("BellmanFord : Node(" + startNode.getId() + ") -> Node(" + endNode.getId() + ")");
if (graph != null) {
ExecutorService executor = Executors.newSingleThreadExecutor(); // Pour le timeOut
try {
if (withGraphStream) {
// Calcule le temps de BellmanFord de GRAPHSTREAM
Future<Double> futureGS = executor.submit(() -> {
long startGS = System.nanoTime();
BellmanFord bf = new BellmanFord();
bf.init(graph);
bf.setSource(startNode.getId());
bf.compute();
long endGS = System.nanoTime();
double cost = bf.getShortestPathValue(endNode);
System.out.println("GraphStream Bellman-Ford = " + (endGS - startGS) / 1_000_000.0 + " ms");
results.add((endGS - startGS) / 1_000_000.0);
return cost;
});
try {
futureGS.get(DEFAULT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
} catch (TimeoutException e) {
System.out.println("⚠ GraphStream Bellman-Ford > : timeout résultat fixé à "
+ DEFAULT_TIMEOUT_SECONDS + "s");
results.add((double) DEFAULT_TIMEOUT_SECONDS * 1_000);
}
}
// Calcule le temps de BellmanFord du Mien
Future<Float> futureMine = executor.submit(() -> {
long startMine = System.nanoTime();
BellmanFordResult result = BellmanFord(graph, startNode);
PathResult pathResult = getPath(result, endNode);
long endMine = System.nanoTime();
System.out.println("Mon Bellman-Ford = " + (endMine - startMine) / 1_000_000.0 + " ms");
results.add((endMine - startMine) / 1_000_000.0);
return pathResult.cost();
});
try {
futureMine.get(DEFAULT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
} catch (TimeoutException e) {
System.out
.println("⚠ Mon Bellman-Ford > : timeout résultat fixé à " + DEFAULT_TIMEOUT_SECONDS + "s");
results.add((double) DEFAULT_TIMEOUT_SECONDS * 1_000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
return results;
}
}
package org.example;
import org.graphstream.graph.Graph;
import org.graphstream.graph.Node;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
public class Utils {
/**
* Sauvegarde un histogramme dans un fichier au format texte.
* Chaque ligne contient soit un seul élément, soit deux éléments séparés par un espace.
*
* @param filename Nom du fichier dans lequel écrire l'histogramme.
* @param histogram Liste de listes de durées (ou valeurs) à enregistrer.
*/
public static void saveDat(String filename, List<List<Double>> histogram) {
try (FileWriter writer = new FileWriter(filename)) {
for (List<Double> durations : histogram) {
if (durations.size() == 1) {
writer.write(durations.get(0) + "\n");
} else if (durations.size() >= 2) {
writer.write(durations.get(0) + " " + durations.get(1) + "\n");
}
}
System.out.println("Histogramme enregistré dans " + filename);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Sélectionne un nœud aléatoire dans un graphe donné.
*
* @param graph Le graphe dans lequel choisir un nœud.
* @return Un nœud choisi aléatoirement parmi tous les nœuds du graphe.
*/
public static Node getRandomNode(Graph graph){
return graph.getNode(ThreadLocalRandom.current().nextInt(0, graph.getNodeCount()));
}
}