diff --git a/src/main/java/edu/repetita/io/RepetitaWriter.java b/src/main/java/edu/repetita/io/RepetitaWriter.java index e7caf76..1e0fa01 100644 --- a/src/main/java/edu/repetita/io/RepetitaWriter.java +++ b/src/main/java/edu/repetita/io/RepetitaWriter.java @@ -5,12 +5,17 @@ public class RepetitaWriter { private static String outputFilename = null; + private static String outpathsFilename = null; private static int verbose = 0; public static void setOutputFilename (String filename){ outputFilename = filename; } + public static void setOutpathsFilename (String filename){ + outpathsFilename = filename; + } + public static void setVerbose(int verboseLevel) { verbose = verboseLevel; } @@ -60,6 +65,12 @@ private static void writeToFile (String content, FileOutputStream outFile){ } } + public static void writeToPathFile (String paths) { + if (outpathsFilename != null) { + writeToFile(paths, outpathsFilename); + } + } + public static String formatAsListOneColumn(List listOfStrings){ StringBuilder formatted = new StringBuilder(); for(String s: listOfStrings){ @@ -100,4 +111,5 @@ public static String formatAsDocumentation(String content) { } return commented.toString(); } + } diff --git a/src/main/java/edu/repetita/main/Main.java b/src/main/java/edu/repetita/main/Main.java index f0d19af..d7c4855 100644 --- a/src/main/java/edu/repetita/main/Main.java +++ b/src/main/java/edu/repetita/main/Main.java @@ -23,8 +23,9 @@ public class Main { /* Private print methods */ private static String getUsage(){ return "Typical usage: repetita " + - "-graph topology_file -demands demands_filename -solver algorithm_id -scenario scenario_id " + - "-t max_execution_time\n"; + "-graph topology_file -demands demands_filename -demandchanges list_demands_filename " + + "-solver algorithm_id -scenario scenario_id -t max_execution_time -outpaths path_filename " + + "-out output_filename -verbose debugging_level\n"; } private static String getUsageOptions(){ @@ -32,7 +33,7 @@ private static String getUsageOptions(){ ArrayList descriptions = new ArrayList<>(); options.addAll(Arrays.asList("h","doc","graph","demands","demandchanges","solver", - "scenario","t","out","verbose")); + "scenario","t","outpaths","out","verbose")); descriptions.addAll(Arrays.asList( "only prints this help message", @@ -43,7 +44,8 @@ private static String getUsageOptions(){ "identifier of the algorithm to run, to be chosen among " + storage.getSolverIDs().toString(), "identifier of the scenario to simulate, to be chosen among " + storage.getScenarioIDs().toString(), "maximum time in seconds allowed to the solver", - "name of the file collecting all the output (stdout by default)", + "name of the file collecting information of paths", + "name of the file collecting all the information (standard output by default)", "level of debugging (default 0, only results reported)" )); @@ -163,6 +165,10 @@ public static void main(String[] args) throws Exception { timeLimit = Double.parseDouble(args[++i]); break; + case "-outpaths": + RepetitaWriter.setOutpathsFilename(args[++i]); + break; + case "-out": RepetitaWriter.setOutputFilename(args[++i]); break; diff --git a/src/main/java/edu/repetita/paths/ShortestPaths.java b/src/main/java/edu/repetita/paths/ShortestPaths.java index 5e22557..17f38cf 100644 --- a/src/main/java/edu/repetita/paths/ShortestPaths.java +++ b/src/main/java/edu/repetita/paths/ShortestPaths.java @@ -2,6 +2,10 @@ import edu.repetita.core.Topology; import edu.repetita.utils.datastructures.ArrayHeapInt; +import javafx.util.Pair; +import org.apache.commons.collections15.map.HashedMap; + +import java.util.*; /** * Computes shortest paths for all destinations of the topology given in the constructor. @@ -22,280 +26,304 @@ * @author Steven Gay aashcrahin@gmail.com */ final public class ShortestPaths { - final private int infiniteDistance; - - final Topology topology; - final private int nNodes; - final private int nEdges; - - // representation of the shortest path DAGs - final public int[][][] successorNodes; - final public int[][][] successorEdges; - final public int[][] nSuccessors; - - final public int[][][] predecessorNodes; - final public int[][][] predecessorEdges; - final public int[][] nPredecessors; - - // Distance matrix and heap are used in Dijkstra's algorithm - final public int[][] distance; - final private ArrayHeapInt heap; - private boolean graphIsDisconnected = false; - private boolean graphIsDisconnectedFilled = false; - - // Some facilities to generate topological ordering of shortest path DAGs from source to destination - final private int[] toVisitStack; - final private boolean[] visited; - final private boolean[] visiting; - final private int[] degree; - - /** Filled on demand by makeTopologicalOrdering methods */ - final public int[] topologicalOrdering; - - /** - * Creates a ShortestPaths instance, that will use {@code topology} internally - * - * @param topology the topology on which shortest paths are to be computed - */ - public ShortestPaths(Topology topology) { - this.topology = topology; - this.infiniteDistance = Topology.INFINITE_DISTANCE; - - nNodes = topology.nNodes; // copy for performance - nEdges = topology.nEdges; - - // Shortest path DAGs, per destination - // dest, node => array of successors of node in DAG of dest - successorNodes = new int[nNodes][nNodes][]; - for (int dest = 0; dest < nNodes; dest++) - for (int node = 0; node < nNodes; node++) - successorNodes[dest][node] = new int[topology.outEdges[node].length]; - - successorEdges = new int[nNodes][nNodes][]; - for (int dest = 0; dest < nNodes; dest++) - for (int node = 0; node < nNodes; node++) - successorEdges[dest][node] = new int[topology.outEdges[node].length]; - - nSuccessors = new int[nNodes][nNodes]; - - // dest, node => array of predecessors of node in DAG of dest - predecessorNodes = new int[nNodes][nNodes][]; - for (int dest = 0; dest < nNodes; dest++) - for (int node = 0; node < nNodes; node++) - predecessorNodes[dest][node] = new int[topology.inEdges[node].length]; - - predecessorEdges = new int[nNodes][nNodes][]; - for (int dest = 0; dest < nNodes; dest++) - for (int node = 0; node < nNodes; node++) - predecessorEdges[dest][node] = new int[topology.inEdges[node].length]; - - nPredecessors = new int[nNodes][nNodes]; - - distance = new int[nNodes][nNodes]; - - heap = new ArrayHeapInt(nNodes); - - computeShortestPaths(); - - toVisitStack = new int[nEdges + nNodes]; - visited = new boolean[nNodes]; - visiting = new boolean[nNodes]; - topologicalOrdering = new int[nNodes]; - degree = new int[nNodes]; - } - - - private void fillGraphIsDisconnected() { - graphIsDisconnectedFilled = true; - graphIsDisconnected = false; - for (int src = 0; src < nNodes; src++) { - for (int dest = 0; dest < nNodes; dest++) { - if (distance[src][dest] == infiniteDistance) graphIsDisconnected = true; - } + final private int infiniteDistance; + + final Topology topology; + final private int nNodes; + final private int nEdges; + + // representation of the shortest path DAGs + final public int[][][] successorNodes; + final public int[][][] successorEdges; + final public int[][] nSuccessors; + + final public int[][][] predecessorNodes; + final public int[][][] predecessorEdges; + final public int[][] nPredecessors; + + // Distance matrix and heap are used in Dijkstra's algorithm + final public int[][] distance; + final private ArrayHeapInt heap; + private boolean graphIsDisconnected = false; + private boolean graphIsDisconnectedFilled = false; + + // Some facilities to generate topological ordering of shortest path DAGs from source to destination + final private int[] toVisitStack; + final private boolean[] visited; + final private boolean[] visiting; + final private int[] degree; + + /** + * Filled on demand by makeTopologicalOrdering methods + */ + final public int[] topologicalOrdering; + + /** + * Creates a ShortestPaths instance, that will use {@code topology} internally + * + * @param topology the topology on which shortest paths are to be computed + */ + public ShortestPaths(Topology topology) { + this.topology = topology; + this.infiniteDistance = Topology.INFINITE_DISTANCE; + + nNodes = topology.nNodes; // copy for performance + nEdges = topology.nEdges; + + // Shortest path DAGs, per destination + // dest, node => array of successors of node in DAG of dest + successorNodes = new int[nNodes][nNodes][]; + for (int dest = 0; dest < nNodes; dest++) + for (int node = 0; node < nNodes; node++) + successorNodes[dest][node] = new int[topology.outEdges[node].length]; + + successorEdges = new int[nNodes][nNodes][]; + for (int dest = 0; dest < nNodes; dest++) + for (int node = 0; node < nNodes; node++) + successorEdges[dest][node] = new int[topology.outEdges[node].length]; + + nSuccessors = new int[nNodes][nNodes]; + + // dest, node => array of predecessors of node in DAG of dest + predecessorNodes = new int[nNodes][nNodes][]; + for (int dest = 0; dest < nNodes; dest++) + for (int node = 0; node < nNodes; node++) { + predecessorNodes[dest][node] = new int[topology.inEdges[node].length]; + for (int index = 0; index < predecessorNodes[dest][node].length; index++) { + predecessorNodes[dest][node][index] = -1; + } + } + + predecessorEdges = new int[nNodes][nNodes][]; + for (int dest = 0; dest < nNodes; dest++) + for (int node = 0; node < nNodes; node++) + predecessorEdges[dest][node] = new int[topology.inEdges[node].length]; + + nPredecessors = new int[nNodes][nNodes]; + + distance = new int[nNodes][nNodes]; + + heap = new ArrayHeapInt(nNodes); + + computeShortestPaths(); + + toVisitStack = new int[nEdges + nNodes]; + visited = new boolean[nNodes]; + visiting = new boolean[nNodes]; + topologicalOrdering = new int[nNodes]; + degree = new int[nNodes]; } - } - - /** - * Checks and returns whether graph is connected. - * - * @return true iff the graph was not strongly connected when computeShortestPaths() was last called - */ - public boolean isGraphDisconnected() { - if (!graphIsDisconnectedFilled) fillGraphIsDisconnected(); - return graphIsDisconnected; - } - - /** - * Computes shortest path DAGs to all destinations, puts the resulting DAGs in predecessor/successor - */ - public void computeShortestPaths() { - // O(V E log V), main part of the cost - for (int dest = 0; dest < nNodes; dest++) computeShortestPathsTo(dest); - - // Use successors to compute DAGs as predecessors - // set all nPredecessors to 0, O(V^2) - for (int dest = 0; dest < nNodes; dest++) - for (int node = 0; node < nNodes; node++) - nPredecessors[dest][node] = 0; - - // browse successor structure: when b successor of a in DAG dest, add a as predecessor of b in DAG dest - // O(V E) - for (int dest = 0; dest < nNodes; dest++) { - for (int nodeA = 0; nodeA < nNodes; nodeA++) { - int[] succNodes = successorNodes[dest][nodeA]; - int[] succEdges = successorEdges[dest][nodeA]; - - for (int pSucc = nSuccessors[dest][nodeA] - 1; pSucc >= 0; pSucc--) { - int nodeB = succNodes[pSucc]; - int edge = succEdges[pSucc]; - int index = nPredecessors[dest][nodeB]; - predecessorNodes[dest][nodeB][index] = nodeB; - predecessorEdges[dest][nodeB][index] = edge; - nPredecessors[dest][nodeB]++; + + + private void fillGraphIsDisconnected() { + graphIsDisconnectedFilled = true; + graphIsDisconnected = false; + for (int src = 0; src < nNodes; src++) { + for (int dest = 0; dest < nNodes; dest++) { + if (distance[src][dest] == infiniteDistance) graphIsDisconnected = true; + } } - } } - - graphIsDisconnectedFilled = false; - } - - // compute shortest paths to one destination - private void computeShortestPathsTo(int dest) { - int[] weights = topology.edgeWeight; // shortcut access - - // Reset structures, heap is already empty from last run - for (int i = 0; i < nNodes; i++) { - distance[i][dest] = infiniteDistance; - nSuccessors[dest][i] = 0; + + /** + * Checks and returns whether graph is connected. + * + * @return true iff the graph was not strongly connected when computeShortestPaths() was last called + */ + public boolean isGraphDisconnected() { + if (!graphIsDisconnectedFilled) fillGraphIsDisconnected(); + return graphIsDisconnected; } - // Initialize with first event - distance[dest][dest] = 0; - heap.enqueue(0, dest); - - // Run Dijkstra's algorithm for single destination shortest path - while (!heap.isEmpty()) { - // visit next (closest) node - int node = heap.dequeue(); - int nodeDistance = distance[node][dest]; - int[] inEdges = topology.inEdges[node]; - - // for every predecessor src, shorten distance[src][dest] if possible and add in heap - for (int i = inEdges.length - 1; i >= 0; i--) { - int edge = inEdges[i]; - int src = topology.edgeSrc[edge]; - - // distance to predecessor by going through node - int edgeWeight = weights[edge]; - if (edgeWeight == infiniteDistance) continue; // edge is not here - int newDist = nodeDistance + edgeWeight; - - // update structures if this edge is on a shortest path - int comp = newDist - distance[src][dest]; - if (comp < 0) { - // path shorter than any other seen, update distance, the set of successor now has exactly one element - if (heap.inHeap(src)) heap.decreaseKey(newDist, src); - else heap.enqueue(newDist, src); - - distance[src][dest] = newDist; - successorEdges[dest][src][0] = edge; - successorNodes[dest][src][0] = node; - nSuccessors[dest][src] = 1; - } - else if (comp == 0) { - // another path with the same shortest distance, just add to successors - int k = nSuccessors[dest][src]; - successorEdges[dest][src][k] = edge; - successorNodes[dest][src][k] = node; - nSuccessors[dest][src]++; + /** + * Computes shortest path DAGs to all destinations, puts the resulting DAGs in predecessor/successor + */ + public void computeShortestPaths() { + // O(V E log V), main part of the cost + for (int dest = 0; dest < nNodes; dest++) computeShortestPathsTo(dest); + + // Use successors to compute DAGs as predecessors + // set all nPredecessors to 0, O(V^2) + for (int dest = 0; dest < nNodes; dest++) + for (int node = 0; node < nNodes; node++) + nPredecessors[dest][node] = 0; + + // browse successor structure: when b successor of a in DAG dest, add a as predecessor of b in DAG dest + // O(V E) + for (int dest = 0; dest < nNodes; dest++) { + for (int nodeA = 0; nodeA < nNodes; nodeA++) { + int[] succNodes = successorNodes[dest][nodeA]; + int[] succEdges = successorEdges[dest][nodeA]; + + for (int pSucc = nSuccessors[dest][nodeA] - 1; pSucc >= 0; pSucc--) { + int nodeB = succNodes[pSucc]; + int edge = succEdges[pSucc]; + int index = nPredecessors[dest][nodeB]; + predecessorNodes[dest][nodeB][index] = nodeB; + predecessorEdges[dest][nodeB][index] = edge; + nPredecessors[dest][nodeB]++; + } + } + } + + graphIsDisconnectedFilled = false; + } + + // compute shortest paths to one destination + private void computeShortestPathsTo(int dest) { + int[] weights = topology.edgeWeight; // shortcut access + + // Reset structures, heap is already empty from last run + for (int i = 0; i < nNodes; i++) { + distance[i][dest] = infiniteDistance; + nSuccessors[dest][i] = 0; + } + + // Initialize with first event + distance[dest][dest] = 0; + heap.enqueue(0, dest); + + // Run Dijkstra's algorithm for single destination shortest path + while (!heap.isEmpty()) { + // visit next (closest) node + int node = heap.dequeue(); + int nodeDistance = distance[node][dest]; + int[] inEdges = topology.inEdges[node]; + + // for every predecessor src, shorten distance[src][dest] if possible and add in heap + for (int i = inEdges.length - 1; i >= 0; i--) { + int edge = inEdges[i]; + int src = topology.edgeSrc[edge]; + + // distance to predecessor by going through node + int edgeWeight = weights[edge]; + if (edgeWeight == infiniteDistance) continue; // edge is not here + int newDist = nodeDistance + edgeWeight; + + // update structures if this edge is on a shortest path + int comp = newDist - distance[src][dest]; + if (comp < 0) { + // path shorter than any other seen, update distance, the set of successor now has exactly one element + if (heap.inHeap(src)) heap.decreaseKey(newDist, src); + else heap.enqueue(newDist, src); + + distance[src][dest] = newDist; + successorEdges[dest][src][0] = edge; + successorNodes[dest][src][0] = node; + nSuccessors[dest][src] = 1; + } else if (comp == 0) { + // another path with the same shortest distance, just add to successors + int k = nSuccessors[dest][src]; + successorEdges[dest][src][k] = edge; + successorNodes[dest][src][k] = node; + nSuccessors[dest][src]++; + } + } } - } } - } - - - /** - * Makes a topological ordering of the nodes in DAG of the destination. - * The ordering is put in the topologicalOrdering field, with dest as first entry of topologicalOrdering. - * Returns the number of nodes in the ordering, which is always nNodes in this case. - * - * @param dest the destination to which compute shortest paths - * @return the number of nodes explored, will always be nNodes - */ - // Uses a predecessor-counting technique: when a node has 0 remaining predecessors, - // put it at the front of ordering, then decrease the counts of its successors. - // Doing so on a DAG visits all nodes and yields a topological ordering. - public int makeTopologicalOrdering(int dest) { - int nToVisit = 0; - - // copy node degrees in DAG of dest; remember which nodes have degree 0 - for (int node = 0; node < nNodes; node++) { - degree[node] = nPredecessors[dest][node]; - if (degree[node] == 0) toVisitStack[nToVisit++] = node; + + + /** + * Makes a topological ordering of the nodes in DAG of the destination. + * The ordering is put in the topologicalOrdering field, with dest as first entry of topologicalOrdering. + * Returns the number of nodes in the ordering, which is always nNodes in this case. + * + * @param dest the destination to which compute shortest paths + * @return the number of nodes explored, will always be nNodes + */ + // Uses a predecessor-counting technique: when a node has 0 remaining predecessors, + // put it at the front of ordering, then decrease the counts of its successors. + // Doing so on a DAG visits all nodes and yields a topological ordering. + public int makeTopologicalOrdering(int dest) { + int nToVisit = 0; + + // copy node degrees in DAG of dest; remember which nodes have degree 0 + for (int node = 0; node < nNodes; node++) { + degree[node] = nPredecessors[dest][node]; + if (degree[node] == 0) toVisitStack[nToVisit++] = node; + } + + // visit node and remove edges from node to neighbors; if it was the last incoming edge of a node, add node to visit stack + int nOrder = nNodes; + while (nToVisit > 0) { + // visit top of stack + int node = toVisitStack[--nToVisit]; + topologicalOrdering[--nOrder] = node; + + // remove edges + for (int pSucc = nSuccessors[dest][node] - 1; pSucc >= 0; pSucc--) { + int succ = successorNodes[dest][node][pSucc]; + degree[succ]--; + if (degree[succ] == 0) toVisitStack[nToVisit++] = succ; + } + } + + assert nOrder == 0 : "nOrder should be 0, or graph is disconnected"; + return nNodes; } - - // visit node and remove edges from node to neighbors; if it was the last incoming edge of a node, add node to visit stack - int nOrder = nNodes; - while (nToVisit > 0) { - // visit top of stack - int node = toVisitStack[--nToVisit]; - topologicalOrdering[--nOrder] = node; - - // remove edges - for (int pSucc = nSuccessors[dest][node] - 1; pSucc >= 0; pSucc--) { - int succ = successorNodes[dest][node][pSucc]; - degree[succ]--; - if (degree[succ] == 0) toVisitStack[nToVisit++] = succ; - } + + + /** + * Makes a topological ordering of the nodes in all shortest paths from source to destination. + * The resulting ordering is in the topologicalOrdering field, with source as its first entry. + * Returns the number of nodes k in the sub-DAG, thus topologicalOrdering's (k-1)-th entry is destination. + * + * @param source the root of the sub-DAG + * @param destination the only leaf of the sub-DAG + * @return the number of nodes in the sub-DAG + */ + // Uses a DFS-based algorithm + public int makeTopologicalOrdering(int source, int destination) { + int[][] successors = successorNodes[destination]; + int[] nSuccessorNodes = nSuccessors[destination]; + + toVisitStack[0] = source; + int pStack = 1; + int pOrdering = 0; + + while (pStack > 0) { + int node = toVisitStack[--pStack]; + + if (visiting[node]) { + visiting[node] = false; + visited[node] = true; + topologicalOrdering[pOrdering++] = node; + } else if (!visited[node]) { + visiting[node] = true; + + pStack++; // push node back // toVisitStack[pStack++] = node; + + for (int pSucc = nSuccessorNodes[node] - 1; pSucc >= 0; pSucc--) { + int succ = successors[node][pSucc]; // push + toVisitStack[pStack++] = succ; + } + } + } + + // clear visited + int nOrdering = pOrdering; + while (pOrdering > 0) visited[topologicalOrdering[--pOrdering]] = false; + + return nOrdering; } - - assert nOrder == 0 : "nOrder should be 0, or graph is disconnected"; - return nNodes; - } - - - /** - * Makes a topological ordering of the nodes in all shortest paths from source to destination. - * The resulting ordering is in the topologicalOrdering field, with source as its first entry. - * Returns the number of nodes k in the sub-DAG, thus topologicalOrdering's (k-1)-th entry is destination. - * - * @param source the root of the sub-DAG - * @param destination the only leaf of the sub-DAG - * @return the number of nodes in the sub-DAG - */ - // Uses a DFS-based algorithm - public int makeTopologicalOrdering(int source, int destination) { - int[][] successors = successorNodes[destination]; - int[] nSuccessorNodes = nSuccessors[destination]; - - toVisitStack[0] = source; - int pStack = 1; - int pOrdering = 0; - - while (pStack > 0) { - int node = toVisitStack[--pStack]; - - if (visiting[node]) { - visiting[node] = false; - visited[node] = true; - topologicalOrdering[pOrdering++] = node; - } - else if (!visited[node]) { - visiting[node] = true; - - pStack++; // push node back // toVisitStack[pStack++] = node; - - for (int pSucc = nSuccessorNodes[node] - 1; pSucc >= 0; pSucc--) { - int succ = successors[node][pSucc]; // push - toVisitStack[pStack++] = succ; + + public String getNextHops() { + StringBuilder sb = new StringBuilder(); + + for (int dest = 0; dest < successorNodes.length; dest++) { + sb.append("\nDestination " + topology.nodeLabel[dest]); + for (int src = 0; src < successorNodes[dest].length; src++) { + Set nextHopSet = new HashSet(); + for (int index = 0; index < nSuccessors[dest][src]; index++) { + nextHopSet.add(topology.nodeLabel[successorNodes[dest][src][index]]); + } + + sb.append("\nnode: " + topology.nodeLabel[src] + ", next hops: " + nextHopSet.toString()); + } + sb.append("\n"); } - } + + return sb.toString(); } - - // clear visited - int nOrdering = pOrdering; - while (pOrdering > 0) visited[topologicalOrdering[--pOrdering]] = false; - - return nOrdering; - } } + diff --git a/src/main/java/edu/repetita/scenarios/SingleSolverRun.java b/src/main/java/edu/repetita/scenarios/SingleSolverRun.java index 83ced53..ca2c9ed 100644 --- a/src/main/java/edu/repetita/scenarios/SingleSolverRun.java +++ b/src/main/java/edu/repetita/scenarios/SingleSolverRun.java @@ -2,6 +2,8 @@ import edu.repetita.core.Scenario; import edu.repetita.analyses.Analysis; +import edu.repetita.io.RepetitaWriter; +import edu.repetita.simulators.FlowSimulator; /* * Basic scenario: we run the solvers for a set amount of time, @@ -37,5 +39,8 @@ public void run(long timeMillis) { // print results this.print(analyzer.compare(preOpt,postOpt)); this.print("Optimization time (in seconds): " + optTime / 1000000000.0); + + // save on paths file (if asked by the user) + RepetitaWriter.writeToPathFile(FlowSimulator.getInstance().getNextHops()); } } diff --git a/src/main/java/edu/repetita/simulators/FlowSimulator.java b/src/main/java/edu/repetita/simulators/FlowSimulator.java index 3b03ae9..a2d0933 100644 --- a/src/main/java/edu/repetita/simulators/FlowSimulator.java +++ b/src/main/java/edu/repetita/simulators/FlowSimulator.java @@ -27,6 +27,7 @@ public static FlowSimulator getInstance(){ private List simulators = new ArrayList<>(); private Setting setting; public double[] flow; + public StringBuffer nextHops; // getter methods public Setting getSetting(){ return this.setting; } @@ -43,16 +44,25 @@ public void setSimulators(List simulators) { */ public void setup(Setting setting){ this.setting = setting; + this.computeFlows(); } /** - * Internally computes traffic distribution for the setting provided in input through - * the setup method. - * Such traffic distribution must be stored in the double[] flow variable. + * Returns the next-hops for each source-destination pair. + */ + public String getNextHops(){ + return this.nextHops.toString(); + } + + /** + * Internally computes and stores in private variables traffic distribution and next-hops + * for the setting provided in input through the setup method. */ public void computeFlows(){ Set simulatedDemands = new HashSet<>(); this.flow = new double[this.setting.getTopology().nEdges]; + this.nextHops = new StringBuffer(); + int priority = 1; // simulate the different parts of the configuration using the specialized simulators one by one for (SpecializedFlowSimulator sim: this.simulators){ @@ -65,6 +75,10 @@ public void computeFlows(){ for(int e = 0; e < this.flow.length; e++){ this.flow[e] += sim.getFlow()[e]; } + + // update next-hop string + this.nextHops.append("\n***Next hops priority " + priority + " (" + sim.name() + " paths)***\n" + sim.getNextHops()); + priority++; } } diff --git a/src/main/java/edu/repetita/simulators/specialized/ECMPFlowSimulator.java b/src/main/java/edu/repetita/simulators/specialized/ECMPFlowSimulator.java index 44b9400..341478d 100644 --- a/src/main/java/edu/repetita/simulators/specialized/ECMPFlowSimulator.java +++ b/src/main/java/edu/repetita/simulators/specialized/ECMPFlowSimulator.java @@ -52,6 +52,7 @@ public Collection computeFlows() { // computing paths ShortestPaths sp = new ShortestPaths(topology); sp.computeShortestPaths(); + this.nextHops = sp.getNextHops(); // simulate flows per destination: for every dest, add passing flows on edges to the flow variable for (int dest = 0; dest < nNodes; dest++) { @@ -60,11 +61,11 @@ public Collection computeFlows() { toRoute[node] = traffic[node][dest]; } - // push flow by topological order from farthest to closest using all shortest paths + // push flow by topological order from the farthest to the closest using all shortest paths int[] ordering = sp.topologicalOrdering; int nOrdering = sp.makeTopologicalOrdering(dest); - // visit nodes in far -> dest order, which is reverse of ordering. + // visit nodes in the far -> dest order while (--nOrdering >= 0) { int node = ordering[nOrdering]; diff --git a/src/main/java/edu/repetita/simulators/specialized/ExplicitPathFlowSimulator.java b/src/main/java/edu/repetita/simulators/specialized/ExplicitPathFlowSimulator.java index b33f06b..b9c879a 100644 --- a/src/main/java/edu/repetita/simulators/specialized/ExplicitPathFlowSimulator.java +++ b/src/main/java/edu/repetita/simulators/specialized/ExplicitPathFlowSimulator.java @@ -32,6 +32,7 @@ public String getDescription() { @Override public Collection computeFlows() { List simulatedDemands = new ArrayList<>(); + StringBuffer currNextHops = new StringBuffer(""); // extract information from setting Topology topology = this.setting.getTopology(); @@ -46,6 +47,7 @@ public Collection computeFlows() { ExplicitPaths paths = this.setting.getExplicitPaths(); if (paths == null){ RepetitaWriter.appendToOutput("No explicit paths set!",2); + this.nextHops = ""; return simulatedDemands; } @@ -60,14 +62,19 @@ public Collection computeFlows() { continue; } + currNextHops.append("\nDestination " + topology.nodeLabel[demands.dest[demand]] + "\n"); + double amount = demands.amount[demand]; for (int edge : pathEdges) { this.flow[edge] += amount; + currNextHops.append("node: " + topology.nodeLabel[topology.edgeSrc[edge]] + ", next hops: [" + topology.nodeLabel[topology.edgeDest[edge]] + "]\n"); } simulatedDemands.add(demands.label[demand]); } + this.nextHops = currNextHops.toString(); + return simulatedDemands; } } diff --git a/src/main/java/edu/repetita/simulators/specialized/SegmentRoutingFlowSimulator.java b/src/main/java/edu/repetita/simulators/specialized/SegmentRoutingFlowSimulator.java index 7a2313d..6fadb24 100644 --- a/src/main/java/edu/repetita/simulators/specialized/SegmentRoutingFlowSimulator.java +++ b/src/main/java/edu/repetita/simulators/specialized/SegmentRoutingFlowSimulator.java @@ -32,6 +32,7 @@ public Collection computeFlows() { Topology topology = this.setting.getTopology(); Demands demands = this.setting.getDemands(); int nNodes = topology.nNodes; + StringBuffer currNextHops = new StringBuffer(); Set simulatedDemands = new HashSet<>(); @@ -46,8 +47,8 @@ public Collection computeFlows() { } } + // compute the new traffic matrix, splitting demand so as to match SR paths if(paths != null) { - // compute traffic matrix splitting demand so to match SR paths for (int demand = 0; demand < demands.nDemands; demand++) { // if the demand has to be ignored, do nothing if(this.demandsToIgnore.contains(demands.label[demand])){ @@ -56,12 +57,17 @@ public Collection computeFlows() { // if there is an SR path, split the demand in sub-demands double amount = demands.amount[demand]; + currNextHops.append("\nDestination " + topology.nodeLabel[demands.dest[demand]] + "\nsequence of middlepoints: "); if (paths.getPath(demand) != null) { int positions = paths.getPathLength(demand) - 1; for (int position = 0; position < positions; position++) { int subSrc = paths.getPathElement(demand, position); int subDest = paths.getPathElement(demand, position + 1); traffic[subSrc][subDest] += amount; + currNextHops.append(topology.nodeLabel[subSrc] + " -> "); + if (position == positions - 1){ + currNextHops.append(topology.nodeLabel[demands.dest[demand]] + "\n"); + } } simulatedDemands.add(demands.label[demand]); } @@ -71,6 +77,9 @@ public Collection computeFlows() { // compute ECMP paths for the traffic matrix reflecting SR paths this.flow = ecmp.computeTrafficDistribution(topology, traffic); + // store the current SR paths + this.nextHops = currNextHops.toString(); + return simulatedDemands; } diff --git a/src/main/java/edu/repetita/simulators/specialized/SpecializedFlowSimulator.java b/src/main/java/edu/repetita/simulators/specialized/SpecializedFlowSimulator.java index 51ff35a..7fe5d39 100644 --- a/src/main/java/edu/repetita/simulators/specialized/SpecializedFlowSimulator.java +++ b/src/main/java/edu/repetita/simulators/specialized/SpecializedFlowSimulator.java @@ -2,15 +2,18 @@ import edu.repetita.core.Setting; import edu.repetita.core.Topology; +import javafx.util.Pair; import java.util.Collection; import java.util.HashSet; +import java.util.Map; import java.util.Set; public abstract class SpecializedFlowSimulator { Setting setting; double[] flow; Set demandsToIgnore = new HashSet<>(); + String nextHops; /** * Must return the name of the specific flow simulator. @@ -26,6 +29,7 @@ public abstract class SpecializedFlowSimulator { * Must compute traffic distribution for the setting provided in input through * the setup method. * Such traffic distribution must be stored in the double[] flow variable. + * Also, next hops computed here are stored in the nextHops variable. * * @return a collection of identifiers for the demands whose traffic distribution has been simulated */ @@ -66,4 +70,8 @@ public Topology getTopology(){ */ void resetEdgeLoad(double[] load) { for (int edge = 0; edge < load.length; edge++) load[edge] = 0; } + /** + * Returns a StringBuffer description of the computed paths + */ + public String getNextHops() {return this.nextHops; } } diff --git a/src/main/scala/be/ac/ucl/ingi/rls/TrafficMatrixGenerator.scala b/src/main/scala/be/ac/ucl/ingi/rls/TrafficMatrixGenerator.scala index d6f162c..97cfd53 100644 --- a/src/main/scala/be/ac/ucl/ingi/rls/TrafficMatrixGenerator.scala +++ b/src/main/scala/be/ac/ucl/ingi/rls/TrafficMatrixGenerator.scala @@ -73,6 +73,7 @@ object TrafficMatrixGenerator extends App { generate_matrix_gravity(topologyData) val outFileName = fileName + f".$i%04d" + ".demands" + println(outFileName) val outFile = new PrintWriter(outFileName) outFile.println("DEMANDS") diff --git a/src/tests/tests/java/edu/repetita/MainTest.java b/src/tests/tests/java/edu/repetita/MainTest.java index 17e4f22..3fcaf50 100644 --- a/src/tests/tests/java/edu/repetita/MainTest.java +++ b/src/tests/tests/java/edu/repetita/MainTest.java @@ -11,6 +11,7 @@ import java.io.File; import java.io.PrintStream; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.security.Permission; import java.util.*; @@ -152,6 +153,29 @@ public void testMain_noErrorsNoVerbose_runDefoWithFractionalTime () throws Excep Main.main(this.getArgs()); } + @Test + public void testMain_noErrorsNoVerboseOutpaths_runDefoWithFractionalTime () throws Exception { + String outpathsFilename = "defo-paths.txt"; + + this.customArgs.put("-solver","defoCP"); + this.customArgs.put("-t","0.5"); + this.customArgs.put("-outpaths",outpathsFilename); + this.setArgs(this.customArgs); + + Main.main(this.getArgs()); + + Path outfile = Paths.get(outpathsFilename); + String content = new String(Files.readAllBytes(outfile)); + Files.delete(outfile); + System.out.println(content); + Pattern regex = Pattern.compile(".*Next hops priority 1 \\(explicit paths\\)\\*\\*\\*\n\n\\*\\*\\*Next hops priority 2.*", Pattern.DOTALL); + Matcher regexMatcher = regex.matcher(content); + assert regexMatcher.find(); + Pattern regex1 = Pattern.compile(".*Next hops priority 2 \\(sr paths\\)\\*\\*\\*\n\nDestination.*", Pattern.DOTALL); + Matcher regexMatcher1 = regex1.matcher(content); + assert regexMatcher1.find(); + } + @Test public void testMain_noErrorsNoVerbose_runMIPWeightOptimizer () throws Exception { this.customArgs.put("-solver","MIPWeightOptimizer"); @@ -237,4 +261,23 @@ public void testMain_noErrors_runAnotherExternalSolver () throws Exception { } } + @Test + public void testMain_noErrorsOutpaths_runAnotherExternalSolver () throws Exception { + String outpathsFilename = "explicit-paths.txt"; + + this.customArgs.put("-solver","randomExplicitPaths"); + this.customArgs.put("-outpaths",outpathsFilename); + this.setArgs(this.customArgs); + + Main.main(this.getArgs()); + + Path outfile = Paths.get(outpathsFilename); + String content = new String(Files.readAllBytes(outfile)); + Files.delete(outfile); + System.out.println(content); + Pattern regex = Pattern.compile(".*Next hops priority 1 \\(explicit paths\\)\\*\\*\\*\n\nDestination.*", Pattern.DOTALL); + Matcher regexMatcher = regex.matcher(content); + assert regexMatcher.find(); + } + } diff --git a/src/tests/tests/java/edu/repetita/simulators/FlowSimulatorTest.java b/src/tests/tests/java/edu/repetita/simulators/FlowSimulatorTest.java index 63fe938..49e72df 100644 --- a/src/tests/tests/java/edu/repetita/simulators/FlowSimulatorTest.java +++ b/src/tests/tests/java/edu/repetita/simulators/FlowSimulatorTest.java @@ -37,6 +37,8 @@ public void computeFlows_consistentWithEcmpFlowSimulator_withNoExplicitPathAndNo ecmp.computeFlows(); System.out.println(FlowSimulator.getMaxUtilization(ecmp.getFlow(),setting)); assert this.simulator.getMaxUtilization() == FlowSimulator.getMaxUtilization(ecmp.getFlow(),setting); + + System.out.println(FlowSimulator.getInstance().nextHops); } @Test @@ -92,6 +94,14 @@ else if (e == square.getEdgeId("ac") || e == square.getEdgeId("cd") || e == squa assert trafficOnCurrEdge == 0.0; } } + + // check subset of printed paths (explicit and igp paths for destination b) + System.out.println(this.simulator.getNextHops()); + String[] pathDescriptionFragments = this.simulator.getNextHops().split("Destination b\n"); + String explicitPathsForB = pathDescriptionFragments[1].split("\n\n")[0]; + assert explicitPathsForB.equals("node: c, next hops: [a]\nnode: a, next hops: [b]"); + String igpPathsForB = pathDescriptionFragments[2].split("\n\n")[0]; + assert igpPathsForB.startsWith("node: a, next hops: [b]\nnode: b, next hops: []\nnode: c, next hops: [a, d]\nnode: d, next hops: [b]"); } } diff --git a/src/tests/tests/java/edu/repetita/simulators/SegmentRoutingFlowSimulatorTest.java b/src/tests/tests/java/edu/repetita/simulators/SegmentRoutingFlowSimulatorTest.java index 3699572..fa6af97 100644 --- a/src/tests/tests/java/edu/repetita/simulators/SegmentRoutingFlowSimulatorTest.java +++ b/src/tests/tests/java/edu/repetita/simulators/SegmentRoutingFlowSimulatorTest.java @@ -122,5 +122,11 @@ public void computeFlows_expectedLinkUtilization_withKnownTopologyTwoDemandsTwoS assert flow[e] == 0.0; } } + + // check that the paths printed are as expected + System.out.println(this.simulator.getNextHops()); + String[] pathDescriptions = this.simulator.getNextHops().split("\nDestination "); + assert (pathDescriptions[1].replace("\nsequence of middlepoints","").trim()).equals("d: c -> a -> b -> d"); + assert (pathDescriptions[2].replace("\nsequence of middlepoints","").trim()).equals("b: a -> c -> d -> b"); } }