diff --git a/src/main/org/insa/algo/AlgorithmFactory.java b/src/main/org/insa/algo/AlgorithmFactory.java new file mode 100644 index 0000000..00fe9a7 --- /dev/null +++ b/src/main/org/insa/algo/AlgorithmFactory.java @@ -0,0 +1,128 @@ +package org.insa.algo; + +import java.lang.reflect.Constructor; +import java.util.IdentityHashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import org.insa.algo.shortestpath.AStarAlgorithm; +import org.insa.algo.shortestpath.BellmanFordAlgorithm; +import org.insa.algo.shortestpath.DijkstraAlgorithm; +import org.insa.algo.shortestpath.ShortestPathAlgorithm; +import org.insa.algo.weakconnectivity.WeaklyConnectedComponentsAlgorithm; + +/** + * Factory class used to register and retrieve algorithms based on their common + * ancestor and name. + * + */ +public class AlgorithmFactory { + + // Map between algorithm names and class. + private final static Map>, Map>>> ALGORITHMS = new IdentityHashMap<>(); + + static { + // Register weakly-connected components algorithm: + registerAlgorithm(WeaklyConnectedComponentsAlgorithm.class, "WCC basic", + WeaklyConnectedComponentsAlgorithm.class); + + // Register shortest path algorithm: + registerAlgorithm(ShortestPathAlgorithm.class, "Bellman-Ford", BellmanFordAlgorithm.class); + registerAlgorithm(ShortestPathAlgorithm.class, "Dijkstra", DijkstraAlgorithm.class); + registerAlgorithm(ShortestPathAlgorithm.class, "A*", AStarAlgorithm.class); + + // Register your algorithms here: + // registerAlgorithm(CarPoolingAlgorithm.class, "My Awesome Algorithm", + // MyCarPoolingAlgorithm.class); + } + + /** + * Register the given algorithm class with the given name as a child class of + * the given base algorithm. + * + * @param baseAlgorithm Base algorithm class that corresponds to the newly + * registered algorithm class (e.g., generic algorithm class for the + * problem). + * @param name Name for the registered algorithm class. + * @param algoClass Algorithm class to register. + */ + public static void registerAlgorithm(Class> baseAlgorithm, + String name, Class> algoClass) { + if (!ALGORITHMS.containsKey(baseAlgorithm)) { + ALGORITHMS.put(baseAlgorithm, new LinkedHashMap<>()); + } + ALGORITHMS.get(baseAlgorithm).put(name, algoClass); + } + + /** + * Create an instance of the given algorithm class using the given input data. + * Assuming algorithm correspond to a class "Algorithm", this function returns + * an object equivalent to `new Algorithm(data)`. + * + * @param algorithm Class of the algorithm to create. + * @param data Input data for the algorithm. + * + * @return A new instance of the given algorithm class using the given data. + * + * @throws Exception if something wrong happens when constructing the object, + * i.e. the given input data does not correspond to the given algorithm + * and/or no constructor that takes a single parameter of type + * (data.getClass()) exists. + */ + public static AbstractAlgorithm createAlgorithm( + Class> algorithm, AbstractInputData data) + throws Exception { + // Retrieve the set of constructors for the given algorithm class. + Constructor[] constructors = algorithm.getDeclaredConstructors(); + + // Within this set, find the constructor that can be called with "data" (only). + AbstractAlgorithm constructed = null; + for (Constructor c: constructors) { + Class[] params = c.getParameterTypes(); + if (params.length == 1 && params[0].isAssignableFrom(data.getClass())) { + c.setAccessible(true); + constructed = (AbstractAlgorithm) c.newInstance(new Object[]{ data }); + break; + } + } + return constructed; + } + + /** + * Return the algorithm class corresponding to the given base algorithm class + * and name. The algorithm must have been previously registered using + * registerAlgorithm. + * + * @param baseAlgorithm Base algorithm class for the algorithm to retrieve. + * @param name Name of the algorithm to retrieve. + * + * @return Class corresponding to the given name. + * + * @see #registerAlgorithm + */ + public static Class> getAlgorithmClass( + Class> baseAlgorithm, String name) { + return ALGORITHMS.get(baseAlgorithm).get(name); + } + + /** + * Return the list of names corresponding to the registered algorithm classes + * for the given base algorithm class. + * + * @param baseAlgorithm Base algorithm class for the algorithm class names to + * retrieve. + * + * @return Names of the currently registered algorithms. + * + * @see #registerAlgorithm + */ + public static Set getAlgorithmNames( + Class> baseAlgorithm) { + if (!ALGORITHMS.containsKey(baseAlgorithm)) { + return new TreeSet<>(); + } + return ALGORITHMS.get(baseAlgorithm).keySet(); + } +} diff --git a/src/main/org/insa/algo/shortestpath/AStarAlgorithm.java b/src/main/org/insa/algo/shortestpath/AStarAlgorithm.java new file mode 100644 index 0000000..4b12c24 --- /dev/null +++ b/src/main/org/insa/algo/shortestpath/AStarAlgorithm.java @@ -0,0 +1,9 @@ +package org.insa.algo.shortestpath; + +public class AStarAlgorithm extends DijkstraAlgorithm { + + public AStarAlgorithm(ShortestPathData data) { + super(data); + } + +} diff --git a/src/main/org/insa/algo/shortestpath/BellmanFordAlgorithm.java b/src/main/org/insa/algo/shortestpath/BellmanFordAlgorithm.java new file mode 100644 index 0000000..e423b9e --- /dev/null +++ b/src/main/org/insa/algo/shortestpath/BellmanFordAlgorithm.java @@ -0,0 +1,102 @@ +package org.insa.algo.shortestpath; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; + +import org.insa.algo.AbstractInputData; +import org.insa.algo.AbstractSolution.Status; +import org.insa.graph.Arc; +import org.insa.graph.Graph; +import org.insa.graph.Node; +import org.insa.graph.Path; + +public class BellmanFordAlgorithm extends ShortestPathAlgorithm { + + public BellmanFordAlgorithm(ShortestPathData data) { + super(data); + } + + @Override + protected ShortestPathSolution doRun() { + + // Retrieve the graph. + ShortestPathData data = getInputData(); + Graph graph = data.getGraph(); + + final int nbNodes = graph.getNodes().size(); + + // Initialize array of distances. + double[] distances = new double[nbNodes]; + Arrays.fill(distances, Double.POSITIVE_INFINITY); + distances[data.getOrigin().getId()] = 0; + + // Notify observers about the first event (origin processed). + notifyOriginProcessed(data.getOrigin()); + + // Initialize array of predecessors. + Arc[] predecessorArcs = new Arc[nbNodes]; + + // Actual algorithm, we will assume the graph does not contain negative cycle... + boolean found = false; + for (int i = 0; !found && i < nbNodes; ++i) { + found = true; + for (Node node: graph.getNodes()) { + for (Arc arc: node.getSuccessors()) { + + // Small test to check allowed roads... + if (!data.isAllowed(arc)) { + continue; + } + + // Retrieve weight of the arc. + double w = data.getMode() == AbstractInputData.Mode.LENGTH ? arc.getLength() + : arc.getMinimumTravelTime(); + + double oldDistance = distances[arc.getDestination().getId()]; + double newDistance = distances[node.getId()] + w; + + if (Double.isInfinite(oldDistance) && Double.isFinite(newDistance)) { + notifyNodeReached(arc.getDestination()); + } + + // Check if new distances would be better, if so update... + if (newDistance < oldDistance) { + found = false; + distances[arc.getDestination().getId()] = distances[node.getId()] + w; + predecessorArcs[arc.getDestination().getId()] = arc; + } + } + } + } + + ShortestPathSolution solution = null; + + // Destination has no predecessor, the solution is infeasible... + if (predecessorArcs[data.getDestination().getId()] == null) { + solution = new ShortestPathSolution(data, Status.INFEASIBLE); + } + else { + + // The destination has been found, notify the observers. + notifyDestinationReached(data.getDestination()); + + // Create the path from the array of predecessors... + ArrayList arcs = new ArrayList<>(); + Arc arc = predecessorArcs[data.getDestination().getId()]; + while (arc != null) { + arcs.add(arc); + arc = predecessorArcs[arc.getOrigin().getId()]; + } + + // Reverse the path... + Collections.reverse(arcs); + + // Create the final solution. + solution = new ShortestPathSolution(data, Status.OPTIMAL, new Path(graph, arcs)); + } + + return solution; + } + +} diff --git a/src/main/org/insa/algo/shortestpath/DijkstraAlgorithm.java b/src/main/org/insa/algo/shortestpath/DijkstraAlgorithm.java new file mode 100644 index 0000000..4f84150 --- /dev/null +++ b/src/main/org/insa/algo/shortestpath/DijkstraAlgorithm.java @@ -0,0 +1,17 @@ +package org.insa.algo.shortestpath; + +public class DijkstraAlgorithm extends ShortestPathAlgorithm { + + public DijkstraAlgorithm(ShortestPathData data) { + super(data); + } + + @Override + protected ShortestPathSolution doRun() { + ShortestPathData data = getInputData(); + ShortestPathSolution solution = null; + // TODO: + return solution; + } + +} diff --git a/src/main/org/insa/algo/shortestpath/ShortestPathAlgorithm.java b/src/main/org/insa/algo/shortestpath/ShortestPathAlgorithm.java new file mode 100644 index 0000000..6890a0e --- /dev/null +++ b/src/main/org/insa/algo/shortestpath/ShortestPathAlgorithm.java @@ -0,0 +1,69 @@ +package org.insa.algo.shortestpath; + +import org.insa.algo.AbstractAlgorithm; +import org.insa.graph.Node; + +public abstract class ShortestPathAlgorithm extends AbstractAlgorithm { + + protected ShortestPathAlgorithm(ShortestPathData data) { + super(data); + } + + @Override + public ShortestPathSolution run() { + return (ShortestPathSolution) super.run(); + } + + @Override + protected abstract ShortestPathSolution doRun(); + + @Override + public ShortestPathData getInputData() { + return (ShortestPathData) super.getInputData(); + } + + /** + * Notify all observers that the origin has been processed. + * + * @param node Origin. + */ + public void notifyOriginProcessed(Node node) { + for (ShortestPathObserver obs: getObservers()) { + obs.notifyOriginProcessed(node); + } + } + + /** + * Notify all observers that a node has been reached for the first time. + * + * @param node Node that has been reached. + */ + public void notifyNodeReached(Node node) { + for (ShortestPathObserver obs: getObservers()) { + obs.notifyNodeReached(node); + } + } + + /** + * Notify all observers that a node has been marked, i.e. its final value has + * been set. + * + * @param node Node that has been marked. + */ + public void notifyNodeMarked(Node node) { + for (ShortestPathObserver obs: getObservers()) { + obs.notifyNodeMarked(node); + } + } + + /** + * Notify all observers that the destination has been reached. + * + * @param node Destination. + */ + public void notifyDestinationReached(Node node) { + for (ShortestPathObserver obs: getObservers()) { + obs.notifyDestinationReached(node); + } + } +} diff --git a/src/main/org/insa/algo/shortestpath/ShortestPathData.java b/src/main/org/insa/algo/shortestpath/ShortestPathData.java new file mode 100644 index 0000000..09f69a1 --- /dev/null +++ b/src/main/org/insa/algo/shortestpath/ShortestPathData.java @@ -0,0 +1,63 @@ +package org.insa.algo.shortestpath; + +import org.insa.algo.AbstractInputData; +import org.insa.graph.Graph; +import org.insa.graph.Node; + +public class ShortestPathData extends AbstractInputData { + + // Origin and destination nodes. + private final Node origin, destination; + + /** + * Construct a new instance of ShortestPathData with the given parameters and + * for which all arcs are allowed. + * + * @param graph Graph in which the path should be looked for. + * @param origin Origin node of the path. + * @param destination Destination node of the path. + * @param mode Cost mode for the path. + */ + public ShortestPathData(Graph graph, Node origin, Node destination, Mode mode) { + super(graph, mode); + this.origin = origin; + this.destination = destination; + } + + /** + * Construct a new instance of ShortestPathInputData with the given parameters. + * + * @param graph Graph in which the path should be looked for. + * @param origin Origin node of the path. + * @param destination Destination node of the path. + * @param mode Cost mode for the path. + * @param arcFilter Filter for arcs (used to allow only a specific set of arcs + * in the graph to be used). + */ + public ShortestPathData(Graph graph, Node origin, Node destination, Mode mode, + AbstractInputData.ArcFilter arcFilter) { + super(graph, mode, arcFilter); + this.origin = origin; + this.destination = destination; + } + + /** + * @return Origin node for the path. + */ + public Node getOrigin() { + return origin; + } + + /** + * @return Destination node for the path. + */ + public Node getDestination() { + return destination; + } + + @Override + public String toString() { + return "Shortest-path from #" + origin.getId() + " to #" + destination.getId() + " [" + + getMode().toString().toLowerCase() + "]"; + } +} diff --git a/src/main/org/insa/algo/shortestpath/ShortestPathGraphicObserver.java b/src/main/org/insa/algo/shortestpath/ShortestPathGraphicObserver.java new file mode 100644 index 0000000..8079910 --- /dev/null +++ b/src/main/org/insa/algo/shortestpath/ShortestPathGraphicObserver.java @@ -0,0 +1,41 @@ +package org.insa.algo.shortestpath; + +import java.awt.Color; + +import org.insa.graph.Node; +import org.insa.graphics.drawing.Drawing; +import org.insa.graphics.drawing.overlays.PointSetOverlay; + +public class ShortestPathGraphicObserver implements ShortestPathObserver { + + // Drawing and Graph drawing + protected Drawing drawing; + protected PointSetOverlay psOverlay1, psOverlay2; + + public ShortestPathGraphicObserver(Drawing drawing) { + this.drawing = drawing; + psOverlay1 = drawing.createPointSetOverlay(1, Color.CYAN); + psOverlay2 = drawing.createPointSetOverlay(1, Color.BLUE); + } + + @Override + public void notifyOriginProcessed(Node node) { + // drawing.drawMarker(node.getPoint(), Color.RED); + } + + @Override + public void notifyNodeReached(Node node) { + psOverlay1.addPoint(node.getPoint()); + } + + @Override + public void notifyNodeMarked(Node node) { + psOverlay2.addPoint(node.getPoint()); + } + + @Override + public void notifyDestinationReached(Node node) { + // drawing.drawMarker(node.getPoint(), Color.RED); + } + +} diff --git a/src/main/org/insa/algo/shortestpath/ShortestPathObserver.java b/src/main/org/insa/algo/shortestpath/ShortestPathObserver.java new file mode 100644 index 0000000..8d9c8e0 --- /dev/null +++ b/src/main/org/insa/algo/shortestpath/ShortestPathObserver.java @@ -0,0 +1,37 @@ +package org.insa.algo.shortestpath; + +import org.insa.graph.Node; + +public interface ShortestPathObserver { + + /** + * Notify the observer that the origin has been processed. + * + * @param node Origin. + */ + public void notifyOriginProcessed(Node node); + + /** + * Notify the observer that a node has been reached for the first + * time. + * + * @param node Node that has been reached. + */ + public void notifyNodeReached(Node node); + + /** + * Notify the observer that a node has been marked, i.e. its final + * value has been set. + * + * @param node Node that has been marked. + */ + public void notifyNodeMarked(Node node); + + /** + * Notify the observer that the destination has been reached. + * + * @param node Destination. + */ + public void notifyDestinationReached(Node node); + +} diff --git a/src/main/org/insa/algo/shortestpath/ShortestPathSolution.java b/src/main/org/insa/algo/shortestpath/ShortestPathSolution.java new file mode 100644 index 0000000..39ff5d9 --- /dev/null +++ b/src/main/org/insa/algo/shortestpath/ShortestPathSolution.java @@ -0,0 +1,76 @@ +package org.insa.algo.shortestpath; + +import org.insa.algo.AbstractInputData; +import org.insa.algo.AbstractSolution; +import org.insa.graph.Path; + +public class ShortestPathSolution extends AbstractSolution { + + // Optimal solution. + private Path path; + + /** + * {@inheritDoc} + */ + public ShortestPathSolution(ShortestPathData data) { + super(data); + } + + /** + * Create a new infeasible shortest-path solution for the given input and + * status. + * + * @param data Original input data for this solution. + * @param status Status of the solution (UNKNOWN / INFEASIBLE). + */ + public ShortestPathSolution(ShortestPathData data, Status status) { + super(data, status); + } + + /** + * Create a new shortest-path solution. + * + * @param data Original input data for this solution. + * @param status Status of the solution (FEASIBLE / OPTIMAL). + * @param path Path corresponding to the solution. + */ + public ShortestPathSolution(ShortestPathData data, Status status, Path path) { + super(data, status); + this.path = path; + } + + @Override + public ShortestPathData getInputData() { + return (ShortestPathData) super.getInputData(); + } + + /** + * @return The path of this solution, if any. + */ + public Path getPath() { + return path; + } + + @Override + public String toString() { + String info = null; + if (!isFeasible()) { + info = String.format("No path found from node #%d to node #%d", + getInputData().getOrigin().getId(), getInputData().getDestination().getId()); + } + else { + info = String.format("Found a path from node #%d to node #%d", + getInputData().getOrigin().getId(), getInputData().getDestination().getId()); + if (getInputData().getMode() == AbstractInputData.Mode.LENGTH) { + info = String.format("%s, %.4f kilometers", info, (getPath().getLength() / 1000.0)); + } + else { + info = String.format("%s, %.4f minutes", info, + (getPath().getMinimumTravelTime() / 60.0)); + } + } + info += " in " + getSolvingTime().getSeconds() + " seconds."; + return info; + } + +} diff --git a/src/main/org/insa/algo/shortestpath/ShortestPathTextObserver.java b/src/main/org/insa/algo/shortestpath/ShortestPathTextObserver.java new file mode 100644 index 0000000..d2a08fd --- /dev/null +++ b/src/main/org/insa/algo/shortestpath/ShortestPathTextObserver.java @@ -0,0 +1,37 @@ +package org.insa.algo.shortestpath; + +import java.io.PrintStream; + +import org.insa.graph.Node; + +public class ShortestPathTextObserver implements ShortestPathObserver { + + private final PrintStream stream; + + public ShortestPathTextObserver(PrintStream stream) { + this.stream = stream; + } + + @Override + public void notifyOriginProcessed(Node node) { + // TODO Auto-generated method stub + + } + + @Override + public void notifyNodeReached(Node node) { + stream.println("Node " + node.getId() + " reached."); + } + + @Override + public void notifyNodeMarked(Node node) { + stream.println("Node " + node.getId() + " marked."); + } + + @Override + public void notifyDestinationReached(Node node) { + // TODO Auto-generated method stub + + } + +} diff --git a/src/main/org/insa/graph/Path.java b/src/main/org/insa/graph/Path.java new file mode 100644 index 0000000..5d8c452 --- /dev/null +++ b/src/main/org/insa/graph/Path.java @@ -0,0 +1,224 @@ +package org.insa.graph; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Class representing a path between nodes in a graph. + * + * A path is represented as a list of {@link Arc} and not a list of {@link Node} + * due to the multigraph nature of the considered graphs. + * + */ +public class Path { + + /** + * Create a new path that goes through the given list of nodes (in order), + * choosing the fastest route if multiple are available. + * + * @param graph Graph containing the nodes in the list. + * @param nodes List of nodes to build the path. + * + * @return A path that goes through the given list of nodes. + * + * @throws IllegalArgumentException If the list of nodes is not valid, i.e. two + * consecutive nodes in the list are not connected in the graph. + * + * @deprecated Need to be implement. + */ + public static Path createFastestPathFromNodes(Graph graph, List nodes) + throws IllegalArgumentException { + List arcs = new ArrayList(); + // TODO: + return new Path(graph, arcs); + } + + /** + * Create a new path that goes through the given list of nodes (in order), + * choosing the shortest route if multiple are available. + * + * @param graph Graph containing the nodes in the list. + * @param nodes List of nodes to build the path. + * + * @return A path that goes through the given list of nodes. + * + * @throws IllegalArgumentException If the list of nodes is not valid, i.e. two + * consecutive nodes in the list are not connected in the graph. + * + * @deprecated Need to be implement. + */ + public static Path createShortestPathFromNodes(Graph graph, List nodes) + throws IllegalArgumentException { + List arcs = new ArrayList(); + // TODO: + return new Path(graph, arcs); + } + + /** + * Concatenate the given paths. + * + * @param paths Array of paths to concatenate. + * + * @return Concatenated path. + * + * @throws IllegalArgumentException if the paths cannot be concatenated (IDs of + * map do not match, or the end of a path is not the beginning of the + * next). + */ + public static Path concatenate(Path... paths) throws IllegalArgumentException { + if (paths.length == 0) { + throw new IllegalArgumentException("Cannot concatenate an empty list of paths."); + } + final String mapId = paths[0].getGraph().getMapId(); + for (int i = 1; i < paths.length; ++i) { + if (!paths[i].getGraph().getMapId().equals(mapId)) { + throw new IllegalArgumentException( + "Cannot concatenate paths from different graphs."); + } + } + ArrayList arcs = new ArrayList<>(); + for (Path path: paths) { + arcs.addAll(path.getArcs()); + } + Path path = new Path(paths[0].getGraph(), arcs); + if (!path.isValid()) { + throw new IllegalArgumentException( + "Cannot concatenate paths that do not form a single path."); + } + return path; + } + + // Graph containing this path. + private final Graph graph; + + // Origin of the path + private final Node origin; + + // List of arcs in this path. + private final List arcs; + + /** + * Create an empty path corresponding to the given graph. + * + * @param graph Graph containing the path. + */ + public Path(Graph graph) { + this.graph = graph; + this.origin = null; + this.arcs = new ArrayList<>(); + } + + /** + * Create a new path containing a single node. + * + * @param graph Graph containing the path. + * @param node Single node of the path. + */ + public Path(Graph graph, Node node) { + this.graph = graph; + this.origin = node; + this.arcs = new ArrayList<>(); + } + + /** + * Create a new path with the given list of arcs. + * + * @param graph Graph containing the path. + * @param arcs Arcs to construct the path. + */ + public Path(Graph graph, List arcs) { + this.graph = graph; + this.arcs = arcs; + this.origin = arcs.size() > 0 ? arcs.get(0).getOrigin() : null; + } + + /** + * @return Graph containing the path. + */ + public Graph getGraph() { + return graph; + } + + /** + * @return First node of the path. + */ + public Node getOrigin() { + return origin; + } + + /** + * @return Last node of the path. + */ + public Node getDestination() { + return arcs.get(arcs.size() - 1).getDestination(); + } + + /** + * @return List of arcs in the path. + */ + public List getArcs() { + return Collections.unmodifiableList(arcs); + } + + /** + * Check if this path is empty (it does not contain any node). + * + * @return true if this path is empty, false otherwise. + */ + public boolean isEmpty() { + return arcs.isEmpty(); + } + + /** + * Check if this path is valid. + * + * A path is valid if it is empty, contains a single node (without arcs) or if + * the first arc has for origin the origin of the path and, for two consecutive + * arcs, the destination of the first one is the origin of the second one. + * + * @return true if the path is valid, false otherwise. + * + * @deprecated Need to be implement. + */ + public boolean isValid() { + // TODO: + return false; + } + + /** + * @return Total length of the path. + * + * @deprecated Need to be implement. + */ + public float getLength() { + // TODO: + return 0; + } + + /** + * Compute the time required to travel this path if moving at the given speed. + * + * @param speed Speed to compute the travel time. + * + * @return Time (in seconds) required to travel this path at the given speed (in + * kilometers-per-hour). + * + * @deprecated Need to be implement. + */ + public double getTravelTime(double speed) { + // TODO: + return 0; + } + + /** + * @return Minimum travel time of the in seconds (assuming maximum speed). + * + * @deprecated Need to be implement. + */ + public double getMinimumTravelTime() { + // TODO: + return 0; + } + +}