Edge case fix + Astar Time mode

This commit is contained in:
Clement Lacau 2024-05-20 00:31:13 +02:00
parent 436d69db1b
commit 2ec3d4200f
7 changed files with 446 additions and 14 deletions

BIN
Synthese_LACAU_ALNET.docx Normal file

Binary file not shown.

View file

@ -1,13 +1,21 @@
package org.insa.graphs.algorithm.shortestpath; package org.insa.graphs.algorithm.shortestpath;
import org.insa.graphs.algorithm.AbstractInputData.Mode;
import org.insa.graphs.model.Node; import org.insa.graphs.model.Node;
public class AStarAlgorithm extends DijkstraAlgorithm { public class AStarAlgorithm extends DijkstraAlgorithm {
private Node destination; private Node destination;
@Override @Override
protected Label createLabel(Node node) { protected Label createLabel(Node node) {
return new LabelStar(node, destination); LabelStar retour;
if (data.getMode() == Mode.LENGTH) {
retour = new LabelStar(node, -1, destination);
}
else {
retour = new LabelStar(node, data.getGraph().getGraphInformation().getMaximumSpeed(), destination);
}
return retour;
} }
public AStarAlgorithm(ShortestPathData data) { public AStarAlgorithm(ShortestPathData data) {

View file

@ -74,15 +74,19 @@ public class DijkstraAlgorithm extends ShortestPathAlgorithm {
// we know its origin and destination // we know its origin and destination
for (Arc arc : x.getNode().getSuccessors()) { for (Arc arc : x.getNode().getSuccessors()) {
if (successor.getNode().equals(arc.getDestination())) { if (successor.getNode().equals(arc.getDestination())) {
// data.getcost(arc) returns a cost considering the mode chosen:
// TIME or LENGTH
// Similar to using getLength / getMinimumTravelTime
arc_cost = (float) data.getCost(arc); arc_cost = (float) data.getCost(arc);
} }
} }
final float possible_path_cost = x.getCost() + arc_cost; final float possible_path_cost = x.getCost() + arc_cost;
if (successor.getCost() > possible_path_cost) { if (successor.getCost() >= possible_path_cost) {
// Mise à jour du label // Mise à jour du label
successor.setPathCost(possible_path_cost); successor.setPathCost(possible_path_cost);
successor.setParentNode(x.getNode()); successor.setParentNode(x.getNode());
// Si le noeud n'a pas déjà était rajouté au tas, on le rajoute // Si le noeud n'a pas déjà été rajouté au tas, on le rajoute
// isReached permet de vérifier en complexité O(1) // isReached permet de vérifier en complexité O(1)
// C'est un léger coût en mémoire pour un gain en vitesse // C'est un léger coût en mémoire pour un gain en vitesse
if (successor.isReached()) { if (successor.isReached()) {
@ -92,7 +96,6 @@ public class DijkstraAlgorithm extends ShortestPathAlgorithm {
successor.markReached(); successor.markReached();
notifyNodeReached(successor.getNode()); notifyNodeReached(successor.getNode());
} }
tas.insert(successor); tas.insert(successor);
} }
} }
@ -112,7 +115,7 @@ public class DijkstraAlgorithm extends ShortestPathAlgorithm {
Label current_label = labels.get(data.getDestination().getId()); Label current_label = labels.get(data.getDestination().getId());
Label parent_label = current_label; Label parent_label = current_label;
while(current_label != null && current_label.getNode() != data.getOrigin()) while(current_label != null && current_label.getNode().getId() != data.getOrigin().getId())
{ {
// Find the label matching the parent node // Find the label matching the parent node
parent_label = labels.get(current_label.getParentNode().getId()); parent_label = labels.get(current_label.getParentNode().getId());

View file

@ -1,6 +1,7 @@
package org.insa.graphs.algorithm.shortestpath; package org.insa.graphs.algorithm.shortestpath;
import org.insa.graphs.model.Node; import org.insa.graphs.model.Node;
import org.insa.graphs.algorithm.AbstractInputData.Mode;
public class Label implements Comparable<Label> { public class Label implements Comparable<Label> {
Node node; Node node;
@ -30,6 +31,7 @@ public class Label implements Comparable<Label> {
public float getCost() { public float getCost() {
// function will be modified later // function will be modified later
return pathCost; return pathCost;
} }
@ -41,7 +43,5 @@ public class Label implements Comparable<Label> {
public int compareTo(Label other) { public int compareTo(Label other) {
final float difference = this.getTotalCost() - other.getTotalCost(); final float difference = this.getTotalCost() - other.getTotalCost();
return (int) Math.signum(difference); return (int) Math.signum(difference);
// Note that doesn't address the requested condition regarding equality :
// "En cas d'égalité, on considèrera en premier le sommet ayant le plus petit coût estimé à la destination."
} }
} }

View file

@ -2,17 +2,25 @@ package org.insa.graphs.algorithm.shortestpath;
import org.insa.graphs.model.Node; import org.insa.graphs.model.Node;
import org.insa.graphs.model.Point; import org.insa.graphs.model.Point;
import org.insa.graphs.algorithm.AbstractInputData.Mode;
import org.insa.graphs.model.RoadInformation;
public class LabelStar extends Label { public class LabelStar extends Label {
private float distanceToDestination; private float distanceToDestination;
private int MaximumSpeed;
public LabelStar(Node node, Node destination) { public LabelStar(Node node, int MaximumSpeed, Node destination) {
super(node); super(node);
this.MaximumSpeed = MaximumSpeed;
// precision was never an answer // precision was never an answer
distanceToDestination = (float) Point.distance(node.getPoint(), destination.getPoint()); if (this.MaximumSpeed < 0) {
distanceToDestination = (float) Point.distance(node.getPoint(), destination.getPoint());
}
else {
distanceToDestination = (float) Point.distance(node.getPoint(), destination.getPoint()) / (1000 * MaximumSpeed);
}
} }
@Override @Override
public float getTotalCost() { public float getTotalCost() {
return this.getCost() + distanceToDestination; return this.getCost() + distanceToDestination;

View file

@ -0,0 +1,416 @@
package org.insa.graphs.algorithm.shortestpath;
import static org.junit.Assert.*;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import org.insa.graphs.algorithm.ArcInspector;
import org.insa.graphs.algorithm.ArcInspectorFactory;
import org.insa.graphs.model.Graph;
import org.insa.graphs.model.Node;
import org.insa.graphs.model.Path;
import org.insa.graphs.model.io.BinaryGraphReader;
import org.insa.graphs.model.io.GraphReader;
import org.insa.graphs.model.io.PathReader;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runners.Parameterized.Parameter;
public class AStarAlgorithmTest {
public static GraphReader reader;
public static ArrayList<Graph> graph = new ArrayList<Graph>();
public static PathReader pathReader;
public static ArrayList<Path> path = new ArrayList<Path>();
@Parameter
// Liste de cartes utilisées
static ArrayList<String> Maps = new ArrayList<String>(Arrays.asList("../Maps/carre.mapgr",
"../Maps/insa.mapgr",
"../Maps/toulouse.mapgr",
"../Maps/midi-pyrenees.mapgr"));
@BeforeClass
/*
* Ajoute toute les cartes dans l'attribut graph.
* Ensuite, on testera des chemins dont qui sont disponibles dans custom_path.
* Ces chemins ont été tracés avec l'algo BellmanFord et sont donc considérés corrects.
* On ne crée pas les chemins dans un attribut, on va les "recréer" virtuellement à
* partir de leur origine/destination et en appliquant BellmanFord.
* Cela permet une meilleure adaptabilité du code.
*/
public static void init() {
try {
// Create the map
for (int j = 0 ; j < Maps.size() ; j++) {
final String mapName = Maps.get(j);
// Create a graph reader
final GraphReader reader = new BinaryGraphReader(
new DataInputStream(new BufferedInputStream(new FileInputStream(mapName))));
// Read the graph. X
graph.add(reader.read());
// free resources
reader.close();
}
}
catch (FileNotFoundException e) {
System.err.println("File not found: " + e.getMessage());
fail("File not found: " + e.getMessage());
}
catch (IOException e ) {
System.err.println("Error reading file: " + e.getMessage());
fail("Error reading file: " + e.getMessage());
}
}
/* Stratégie:
* Chemins courts testés par comparaison avec Bellman.
* Chemin longs testés par comparaison avec chemins déjà construits. Bellman trop long.
*/
@Test
/*
* Map: carre.mapgr
* Chemin: 19 --> 4
* Tous chemins permis
* Mode: LENGTH
* PATH UTILISE : ../Paths/custom_paths/short_path_carre.path
*/
public void chemin_court_CARRE_length() {
ArcInspector arcInspector = ArcInspectorFactory.getAllFilters().get(0);
Graph myGraph = graph.get(0);
Node origin = myGraph.get(19);
Node destination = myGraph.get(4);
ShortestPathData data = new ShortestPathData(myGraph, origin, destination, arcInspector);
AStarAlgorithm astar = new AStarAlgorithm(data);
ShortestPathSolution astar_path = astar.doRun();
BellmanFordAlgorithm bellman = new BellmanFordAlgorithm(data);
ShortestPathSolution bell_path = bellman.doRun();
assert(astar_path.getPath().isValid());
assert(astar_path.isFeasible());
assert(Math.abs(astar.getCostPath() - astar_path.getPath().getLength()) < 1.0);
assert(Math.abs(astar_path.getPath().getLength() - bell_path.getPath().getLength()) < 1.0);
}
@Test
/*
* Chemin long relativement à la carte carrée.
* Chemin: 15 --> 9
* Tous chemins permis
* PATH UTILISE : ../Paths/custom_paths/long_path_carre.path
*/
public void chemin_long_CARRE_length() {
ArcInspector arcInspector = ArcInspectorFactory.getAllFilters().get(0);
Graph myGraph = graph.get(0);
Node origin = myGraph.get(15);
Node destination = myGraph.get(9);
ShortestPathData data = new ShortestPathData(myGraph, origin, destination, arcInspector);
AStarAlgorithm astar = new AStarAlgorithm(data);
ShortestPathSolution astar_path = astar.doRun();
BellmanFordAlgorithm bellman = new BellmanFordAlgorithm(data);
ShortestPathSolution bell_path = bellman.doRun();
assert(astar_path.getPath().isValid());
assert(astar_path.isFeasible());
assert(Math.abs(astar.getCostPath() - astar_path.getPath().getLength()) < 1.0);
assert(Math.abs(astar_path.getPath().getLength() - bell_path.getPath().getLength()) < 1.0);
}
@Test
/*
* Chemin nul sur carte carrée.
* L'origine et la destination sont les mêmes (noeud 3).
* Tous chemins permis
*/
public void chemin_nul_CARRE() {
ArcInspector arcInspector = ArcInspectorFactory.getAllFilters().get(0);
Graph myGraph = graph.get(0);
Node origin = myGraph.get(3);
Node destination = myGraph.get(3);
ShortestPathData data = new ShortestPathData(myGraph, origin, destination, arcInspector);
AStarAlgorithm astar = new AStarAlgorithm(data);
ShortestPathSolution astar_path = astar.doRun();
assert(!astar_path.isFeasible());
assert(astar_path.getPath() == null);
}
@Test
/*
* Chemin inexistant sur la carte INSA.
* Origine: 224
* Destination: 814.
* Les 2 noeuds font partie de deux composantes non connexes.
* Tous chemins permis.
*/
public void chemin_inexistant_INSA() {
ArcInspector arcInspector = ArcInspectorFactory.getAllFilters().get(0);
Graph myGraph = graph.get(1);
Node origin = myGraph.get(224);
Node destination = myGraph.get(814);
ShortestPathData data = new ShortestPathData(myGraph, origin, destination, arcInspector);
AStarAlgorithm astar = new AStarAlgorithm(data);
ShortestPathSolution astar_path = astar.doRun();
assert(!astar_path.isFeasible());
assert(astar_path.getPath() == null);
}
@Test
/*
* Chemin court sur la carte de Toulouse.
* Origine : 8423
* Destination: 8435
* Tous chemins permis.
* PATH UTILISE : ../Paths/custom_paths/short_path_tls.path
*/
public void chemin_court_TLS() {
ArcInspector arcInspector = ArcInspectorFactory.getAllFilters().get(0);
Graph myGraph = graph.get(2);
Node origin = myGraph.get(8423);
Node destination = myGraph.get(8435);
ShortestPathData data = new ShortestPathData(myGraph, origin, destination, arcInspector);
AStarAlgorithm astar = new AStarAlgorithm(data);
ShortestPathSolution astar_path = astar.doRun();
BellmanFordAlgorithm bellman = new BellmanFordAlgorithm(data);
ShortestPathSolution bell_path = bellman.doRun();
assert(astar_path.getPath().isValid());
assert(astar_path.isFeasible());
assert(Math.abs(astar.getCostPath() - astar_path.getPath().getLength()) < 1.0);
assert(Math.abs(astar_path.getPath().getLength() - bell_path.getPath().getLength()) < 1.0);
}
@Test
/*
* Chemin long sur la carte de Toulouse.
* Même si Bellman est de plus long à faire long à faire, ce test prend moins
* de 3s, on estime que ce n'est pas trop et on va utiliser Bellman.
* Par contre, dans le test sur la région midi_pyrenees qui arrive après, on va
* être obligé de trouver une autre solution.
* Origine: 16644
* Destination: 39229
* Mode: LENGTH
* PATH UTILISE : ../Paths/custom_paths/long_path_tls.path
*/
public void chemin_long_TLS_length() {
ArcInspector arcInspector = ArcInspectorFactory.getAllFilters().get(0);
Graph myGraph = graph.get(2);
Node origin = myGraph.get(16644);
Node destination = myGraph.get(39229);
ShortestPathData data = new ShortestPathData(myGraph, origin, destination, arcInspector);
AStarAlgorithm astar = new AStarAlgorithm(data);
ShortestPathSolution astar_path = astar.doRun();
BellmanFordAlgorithm bellman = new BellmanFordAlgorithm(data);
ShortestPathSolution bell_path = bellman.doRun();
assert(astar_path.getPath().isValid());
assert(astar_path.isFeasible());
assert((Math.abs(astar.getCostPath() - astar_path.getPath().getLength()) < 1.0));
assert(Math.abs(astar_path.getPath().getLength() - bell_path.getPath().getLength()) < 10.0);
}
@Test
/*
* Chemin long sur la carte de Toulouse.
* Comme à cette étape Dijkstra a été testé, on va l'utiliser pour vérifier
* AStar. On considère que Dijkstra est correct et vérifié.
* Origine: 16644
* Destination: 39229
* Mode: TIME
* PATH UTILISE : ../Paths/custom_paths/long_path_tls.path
*/
public void chemin_long_TLS_time() {
ArcInspector arcInspector = ArcInspectorFactory.getAllFilters().get(2);
Graph myGraph = graph.get(2);
Node origin = myGraph.get(16644);
Node destination = myGraph.get(39229);
ShortestPathData data = new ShortestPathData(myGraph, origin, destination, arcInspector);
AStarAlgorithm astar = new AStarAlgorithm(data);
ShortestPathSolution astar_path = astar.doRun();
DijkstraAlgorithm dijkstra = new DijkstraAlgorithm(data);
ShortestPathSolution dijkstra_path = dijkstra.doRun();
assert(astar_path.getPath().isValid());
assert(astar_path.isFeasible());
assert((Math.abs(astar.getCostPath() - astar_path.getPath().getMinimumTravelTime()) < 1.0));
assert(Math.abs(astar.getCostPath() - dijkstra.getCostPath()) < 1.0 );
}
@Test
/*
* Test du mode à vélo facultatif.
* Nous prenons une origine sur l'autoroute et une destination en dehors.
* Ce chemin est donc censé être utilisable en vélo mais pas en voiture.
* Avec le filtre vélos, on obtient pas de chemin.
* Avec le filtre voitures on obtient un chemin.
* Origine: 19135
* Destination: 1980
* PATH UTILISE : ../Paths/custom_paths/path_cyclist.path
*/
public void chemin_velo_uniquement() {
// Filter: forBicyclesCustomT
ArcInspector arcInspector = ArcInspectorFactory.getAllFilters().get(4);
Graph myGraph = graph.get(2);
Node origin = myGraph.get(19135);
Node destination = myGraph.get(1980);
ShortestPathData data = new ShortestPathData(myGraph, origin, destination, arcInspector);
AStarAlgorithm astar_bicycle = new AStarAlgorithm(data);
ShortestPathSolution astar_path_bicycle = astar_bicycle.doRun();
BellmanFordAlgorithm bellman_bicycle = new BellmanFordAlgorithm(data);
ShortestPathSolution bell_path_bicycle = bellman_bicycle.doRun();
// Filter: forCarsL
ArcInspector new_Inspector = ArcInspectorFactory.getAllFilters().get(1);
data = new ShortestPathData(myGraph, origin, destination, new_Inspector);
AStarAlgorithm astar_car = new AStarAlgorithm(data);
ShortestPathSolution astar_path_car = astar_car.doRun();
assertEquals(astar_path_bicycle.getPath(), null);
assert(!astar_path_bicycle.isFeasible());
assertEquals(bell_path_bicycle.getPath(), null);
assert(!bell_path_bicycle.isFeasible());
assert(astar_path_car.getPath() != null);
assert(astar_path_car.isFeasible());
}
@Test
/*
* On veut vérifier le bon fonctionnement des modes TIME et LENGTH.
* Pour cela, on va obtenir deux chemins avec l'algo de astar et vérifier
* que:
* -le chemin TIME est plus rapide en durée que le chemin LENGTH
* -le chemin LENGTH est plus court en distance que le chemin LENGTH.
* On prend un grand chemin pour être sur d'avoir une différence :
* Origine: 16644
* Destination: 39229
* Mode: TIME/LENGTH
* PATH UTILISE : ../Paths/custom_paths/long_path_tls.path
*/
public void chemin_time_length_comparison() {
Graph myGraph = graph.get(2);
Node origin = myGraph.get(19135);
Node destination = myGraph.get(1980);
// Filter: forCarsL
ArcInspector arcInspector = ArcInspectorFactory.getAllFilters().get(1);
ShortestPathData data = new ShortestPathData(myGraph, origin, destination, arcInspector);
AStarAlgorithm astar_L = new AStarAlgorithm(data);
ShortestPathSolution astar_path_L = astar_L.doRun();
// Filter: forCarsT
ArcInspector new_Inspector = ArcInspectorFactory.getAllFilters().get(2);
data = new ShortestPathData(myGraph, origin, destination, new_Inspector);
AStarAlgorithm astar_T = new AStarAlgorithm(data);
ShortestPathSolution astar_path_T = astar_T.doRun();
assert(astar_path_L.getPath().isValid());
assert(astar_path_L.isFeasible());
assert(astar_path_T.getPath().isValid());
assert(astar_path_T.isFeasible());
assert((Math.abs(astar_L.getCostPath() - astar_path_L.getPath().getLength()) < 1.0));
assert((Math.abs(astar_T.getCostPath() - astar_path_T.getPath().getMinimumTravelTime()) < 1.0));
assert(astar_path_L.getPath().getLength() < astar_path_T.getPath().getLength());
assert(astar_path_L.getPath().getMinimumTravelTime() > astar_path_T.getPath().getMinimumTravelTime());
}
@Test
/*
* Cette fois-ci, Bellman est trop long à utiliser même une seule fois.
* On va donc utiliser quelques techniques pour se rassurer.
* -vérifier certains noeuds comme départ, destination, noeud pivot.
* -vérifier le cout avec une estimation gentille.
* -etc.
* Origin: 279654
* Destination: 481936
* Mode: LENGTH
* PATH UTILISE : ../Paths/custom_paths/long_chemin_midi_pyrenees.path
*/
public void chemin_long_Midi_pyrenees_length() {
ArcInspector arcInspector = ArcInspectorFactory.getAllFilters().get(0);
Graph myGraph = graph.get(3);
Node origin = myGraph.get(279654);
Node destination = myGraph.get(481936);
ShortestPathData data = new ShortestPathData(myGraph, origin, destination, arcInspector);
AStarAlgorithm astar = new AStarAlgorithm(data);
ShortestPathSolution astar_path = astar.doRun();
assert(astar_path.getPath().isValid());
assert(astar_path.isFeasible());
// On a des erreurs d'arrondi assez grande avec la distance, mais elles sont mineures
// relativement aux distance de 300000 ici.
assert((Math.abs(astar.getCostPath() - astar_path.getPath().getLength())) < 1000.0);
// Selon le chemin sélectionné on peut avoir une estimation de la longueur qu'on est censée avoir.
// Avec notre long chemin: entre 250 et 260 kilomètres.
assert(astar.getCostPath() > 250000 && astar.getCostPath() < 260000);
// On peut aussi supposer que le nombre d'arcs empruntés est très grand.
assert(astar_path.getPath().getArcs().size() > 1000);
}
@Test
/*
* Cette fois-ci, Bellman est trop long à utiliser même une seule fois.
* On va donc utiliser quelques techniques pour se rassurer.
* -vérifier certains noeuds comme départ, destination, noeud pivot.
* -vérifier le cout avec une estimation gentille.
* -etc.
* Origin: 279654
* Destination: 481936
* Mode: LENGTH
* PATH UTILISE : ../Paths/custom_paths/long_chemin_midi_pyrenees.path
*/
public void chemin_long_Midi_pyrenees_time() {
ArcInspector arcInspector = ArcInspectorFactory.getAllFilters().get(2);
Graph myGraph = graph.get(3);
Node origin = myGraph.get(279654);
Node destination = myGraph.get(481936);
ShortestPathData data = new ShortestPathData(myGraph, origin, destination, arcInspector);
AStarAlgorithm astar = new AStarAlgorithm(data);
ShortestPathSolution astar_path = astar.doRun();
assert(astar_path.getPath().isValid());
assert(astar_path.isFeasible());
// On a des erreurs d'arrondi assez grandes avec la distance
assert((Math.abs(astar.getCostPath() - astar_path.getPath().getMinimumTravelTime())) < 100.0);
// Selon le chemin sélectionné on peut avoir une estimation de la durée qu'on est censée avoir.
// Avec notre long chemin: entre 12000 et 13000 secondes.
assert(astar.getCostPath() > 12000 && astar.getCostPath() < 13000);
// On peut aussi supposer que le nombre d'arcs empruntés est très grand.
assert(astar_path.getPath().getArcs().size() > 1000);
}
}

View file

@ -24,7 +24,6 @@ import org.junit.runners.Parameterized.Parameter;
public class DijkstraAlgorithmTest { public class DijkstraAlgorithmTest {
// TODO finish this
public static GraphReader reader; public static GraphReader reader;
public static ArrayList<Graph> graph = new ArrayList<Graph>(); public static ArrayList<Graph> graph = new ArrayList<Graph>();
public static PathReader pathReader; public static PathReader pathReader;
@ -48,7 +47,6 @@ public class DijkstraAlgorithmTest {
*/ */
public static void init() { public static void init() {
try { try {
ArrayList<String> actual_path_list = new ArrayList<String>();
// Create the map // Create the map
for (int j = 0 ; j < Maps.size() ; j++) { for (int j = 0 ; j < Maps.size() ; j++) {
final String mapName = Maps.get(j); final String mapName = Maps.get(j);
@ -76,7 +74,6 @@ public class DijkstraAlgorithmTest {
* Chemins courts testés par comparaison avec Bellman. * Chemins courts testés par comparaison avec Bellman.
* Chemin longs testés par comparaison avec chemins déjà construits. Bellman trop long. * Chemin longs testés par comparaison avec chemins déjà construits. Bellman trop long.
*/ */
// TODO
@Test @Test
/* /*