From 7c7b58ba2d2f56c41934274ea3e869ca7586c832 Mon Sep 17 00:00:00 2001 From: Mathieu Jadin Date: Mon, 11 Mar 2019 16:50:48 +0100 Subject: [PATCH] Add extra options specific to a solver '--extra ' option is introduced. This gives the path to a json file containing a dictionary. This dictionary sets values for options that are specific to a particular solver. For instance, one extra option is introduced for defoCP and SRLS. This argument controls the maximum number of intermediate segments that these solvers can use. This fixes Issue #7 --- build.sbt | 2 + src/main/java/edu/repetita/core/Setting.java | 31 +++++++-- src/main/java/edu/repetita/core/Solver.java | 7 ++ src/main/java/edu/repetita/main/Main.java | 67 ++++++++++++++++--- .../edu/repetita/main/RepetitaStorage.java | 10 +-- .../scenarios/DemandChangeReoptimization.java | 2 +- .../java/edu/repetita/settings/SRSetting.java | 55 +++++++++++++++ .../java/edu/repetita/solvers/SRSolver.java | 7 ++ .../java/edu/repetita/solvers/sr/DefoCP.java | 12 ++-- .../repetita/solvers/sr/MIPTwoSRNoSplit.java | 5 ++ .../java/edu/repetita/solvers/sr/SRLS.java | 4 +- .../be/ac/ucl/ingi/defo/core/CoreSolver.scala | 3 +- .../ac/ucl/ingi/defo/core/DEFOInstance.scala | 8 ++- .../be/ac/ucl/ingi/rls/LoadOptimizer.scala | 4 +- .../be/ac/ucl/ingi/rls/state/PathState.scala | 4 +- 15 files changed, 185 insertions(+), 36 deletions(-) create mode 100644 src/main/java/edu/repetita/settings/SRSetting.java diff --git a/build.sbt b/build.sbt index aae5138..7794748 100644 --- a/build.sbt +++ b/build.sbt @@ -14,6 +14,8 @@ lazy val root = (project in file(".")). libraryDependencies += "net.sf.jung" % "jung-visualization" % "2.1.1", libraryDependencies += "net.sf.jung" % "jung-graph-impl" % "2.1.1", libraryDependencies += "org.scala-lang.modules" %% "scala-xml" % "1.0.4", + libraryDependencies += "com.fasterxml.jackson.core" % "jackson-core" % "2.9.8", + libraryDependencies += "com.fasterxml.jackson.core" % "jackson-databind" % "2.9.8", javaOptions in run += "-Xmx4G" ) diff --git a/src/main/java/edu/repetita/core/Setting.java b/src/main/java/edu/repetita/core/Setting.java index 49537f1..83fdb21 100644 --- a/src/main/java/edu/repetita/core/Setting.java +++ b/src/main/java/edu/repetita/core/Setting.java @@ -1,8 +1,7 @@ package edu.repetita.core; import java.io.IOException; -import java.util.LinkedList; -import java.util.List; +import java.util.*; import edu.repetita.io.RepetitaParser; import edu.repetita.paths.ExplicitPaths; @@ -42,7 +41,7 @@ public void setDemandsFilename(String demandsFilename) { public String getTopologyFilename() { return this.topologyFilename; } - + public Topology getTopology() { return this.topology; } @@ -50,11 +49,11 @@ public Topology getTopology() { public void setTopology(Topology topology) { this.topology = topology; } - + public String getDemandsFilename() { return this.demandsFilename; } - + public Demands getDemands() { return this.demands; } public void setDemands(Demands newDemands) { @@ -113,13 +112,33 @@ public ExplicitPaths getExplicitPaths() { return this.config.getExplicitPaths(); } + /** + * Fills the args map with each extra argument name as key and its description as value + */ + public void help(HashMap args) { } + + protected void setExtra(String key, Object value) throws IllegalArgumentException { + throw new IllegalArgumentException("This solver does not take option '" + key + "'"); + } + + public void setExtras(Map extras) throws IllegalArgumentException { + for (String key : extras.keySet()) { + Object value = extras.get(key); + setExtra(key, value); + } + } + public Setting clone(){ Setting copy = new Setting(); + this.init(copy); + return copy; + } + + protected void init(Setting copy) { copy.setTopology(this.topology.clone()); copy.setDemands(this.demands); copy.setDemandChanges(this.demandChanges); copy.setNumberReoptimizations(this.numberReoptimizations); copy.setRoutingConfiguration(this.getRoutingConfiguration().clone()); - return copy; } } diff --git a/src/main/java/edu/repetita/core/Solver.java b/src/main/java/edu/repetita/core/Solver.java index 70cabad..0557598 100644 --- a/src/main/java/edu/repetita/core/Solver.java +++ b/src/main/java/edu/repetita/core/Solver.java @@ -52,4 +52,11 @@ public Solver(){ * @return the time needed by the last call to solve(), in nanoseconds */ public abstract long solveTime(Setting setting); + + /** + * @return a Setting object to store and parse all the specific arguments of this solver + */ + public Setting getSetting() { + return new Setting(); + } } diff --git a/src/main/java/edu/repetita/main/Main.java b/src/main/java/edu/repetita/main/Main.java index d7c4855..de5bd2f 100644 --- a/src/main/java/edu/repetita/main/Main.java +++ b/src/main/java/edu/repetita/main/Main.java @@ -1,5 +1,7 @@ package edu.repetita.main; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import edu.repetita.core.Scenario; import edu.repetita.core.Setting; import edu.repetita.core.Solver; @@ -11,9 +13,11 @@ import edu.repetita.scenarios.ScenarioFactory; import edu.repetita.solvers.SolverFactory; -import java.net.URISyntaxException; +import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.Map; public class Main { @@ -33,7 +37,7 @@ private static String getUsageOptions(){ ArrayList descriptions = new ArrayList<>(); options.addAll(Arrays.asList("h","doc","graph","demands","demandchanges","solver", - "scenario","t","outpaths","out","verbose")); + "scenario","t","outpaths","out","verbose","extra")); descriptions.addAll(Arrays.asList( "only prints this help message", @@ -46,12 +50,36 @@ private static String getUsageOptions(){ "maximum time in seconds allowed to the solver", "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)" + "level of debugging (default 0, only results reported)", + "A path to a json file containing a JSON dictionary of extra options." + + " These options are specific to a solver." )); return "All options:\n" + RepetitaWriter.formatAsListTwoColumns(options, descriptions, " -"); } + private static String getUsageExtraOptions() { + StringBuilder sb = new StringBuilder(); + + for (String solverID: storage.getSolverIDs()) { + Setting setting = storage.getSolver(solverID).getSetting(); + HashMap extras = new HashMap<>(); + setting.help(extras); + + if (extras.size() > 0) { + ArrayList options = new ArrayList<>(extras.keySet()); + ArrayList descriptions = new ArrayList<>(); + for (String option: options) { + descriptions.add(extras.get(option)); + } + sb.append("\n").append(solverID).append(" extra options:\n") + .append(RepetitaWriter.formatAsListTwoColumns(options, descriptions, " ")); + } + } + + return sb.toString(); + } + private static void printHelp(String additional) { if (additional != null && !additional.equals("")) { @@ -60,6 +88,7 @@ private static void printHelp(String additional) { System.out.println(getUsage()); System.out.println(getUsageOptions()); + System.out.println(getUsageExtraOptions()); System.exit(1); } @@ -123,6 +152,10 @@ public static void main(String[] args) throws Exception { String solverChoice = "tabuLS"; String scenarioChoice = "SingleSolverRun"; + HashMap extras = new HashMap<>(); + + // create storage + storage = RepetitaStorage.getInstance(); // parse command line arguments int i = 0; @@ -178,15 +211,21 @@ public static void main(String[] args) throws Exception { RepetitaWriter.setVerbose(verboseLevel); break; - default: + case "-extra": + try { + ObjectMapper mapper = new ObjectMapper(); + extras = mapper.readValue(new File(args[++i]), new TypeReference>(){}); + } catch (IOException e) { + printHelp("IOException when reading extra options: " + e.getMessage()); + } + break; + + default: printHelp("Unknown option " + args[i]); - } + } i++; } - // create storage (after having set the verbose level) - storage = RepetitaStorage.getInstance(); - // check that the strictly necessary information has been provided in input (after having creating the storage) if (args.length < 1 || help) printHelp(""); if (graphFilename == null) printHelp("Needs an input topology file"); @@ -196,12 +235,18 @@ public static void main(String[] args) throws Exception { if (! storage.getSolverIDs().contains(solverChoice)) printHelp("Unknown solver: " + solverChoice); if (! storage.getScenarioIDs().contains(scenarioChoice)) printHelp("Unknown scenario: " + scenarioChoice); - // run an experiment according to command line parameters - Setting setting = storage.newSetting(graphFilename, demandsFilename, demandChangesFilenames); - Solver solver = storage.getSolver(solverChoice); solver.setVerbose(verboseLevel); + // run an experiment according to command line parameters + Setting setting = null; + try { + setting = storage.newSetting(solver, graphFilename, demandsFilename, + demandChangesFilenames, extras); + } catch (IllegalArgumentException e) { + printHelp(e.getMessage()); + } + Scenario scenario = storage.newScenario(scenarioChoice, setting, solver); scenario.run((long) timeLimit * 1000); } diff --git a/src/main/java/edu/repetita/main/RepetitaStorage.java b/src/main/java/edu/repetita/main/RepetitaStorage.java index fb27fc6..68c2e02 100644 --- a/src/main/java/edu/repetita/main/RepetitaStorage.java +++ b/src/main/java/edu/repetita/main/RepetitaStorage.java @@ -56,16 +56,18 @@ public Scenario newScenario(String scenarioName, Setting setting, Solver solver) return scenario; } - public Setting newSetting(String graphFilename, String demandsFilename){ - Setting setting = new Setting(); + public Setting newSetting(Solver solver, String graphFilename, String demandsFilename){ + Setting setting = solver.getSetting(); setting.setTopologyFilename(graphFilename); setting.setDemandsFilename(demandsFilename); return setting; } - public Setting newSetting(String graphFilename, String demandsFilename, List otherDemandsFilenames){ - Setting setting = this.newSetting(graphFilename,demandsFilename); + public Setting newSetting(Solver solver, String graphFilename, String demandsFilename, + List otherDemandsFilenames, Map extras){ + Setting setting = this.newSetting(solver, graphFilename,demandsFilename); setting.setDemandChangesFilenames(otherDemandsFilenames); + setting.setExtras(extras); return setting; } diff --git a/src/main/java/edu/repetita/scenarios/DemandChangeReoptimization.java b/src/main/java/edu/repetita/scenarios/DemandChangeReoptimization.java index 6f3085b..0babdf0 100644 --- a/src/main/java/edu/repetita/scenarios/DemandChangeReoptimization.java +++ b/src/main/java/edu/repetita/scenarios/DemandChangeReoptimization.java @@ -37,7 +37,7 @@ public void run(long timeMillis) { // create a new (minimalistic) setting and analyze it Long timeBeforeChange = System.nanoTime(); - Setting currentSetting = new Setting(); + Setting currentSetting = this.setting.clone(); currentSetting.setTopology(topology); currentSetting.setDemands(currentDemands); currentSetting.setRoutingConfiguration(lastConfig); diff --git a/src/main/java/edu/repetita/settings/SRSetting.java b/src/main/java/edu/repetita/settings/SRSetting.java new file mode 100644 index 0000000..30b4e5f --- /dev/null +++ b/src/main/java/edu/repetita/settings/SRSetting.java @@ -0,0 +1,55 @@ +package edu.repetita.settings; + +import edu.repetita.core.Setting; + +import java.util.*; + +public class SRSetting extends Setting { + + private int maxSeg = 1; + + public int getMaxSeg() { + return maxSeg; + } + + public void setMaxSeg(int maxSeg) { + this.maxSeg = maxSeg; + } + + @Override + public void help(HashMap args) { + args.put("maxseg", "The maximum number of intermediate segments (i.e., source and destination nodes are not counted)"); + } + + @Override + protected void setExtra(String key, Object value) throws IllegalArgumentException { + switch (key) { + case "maxseg": + if (!(value instanceof Integer)) { + throw new IllegalArgumentException("The maximum number of intermediate segments should be an integer."); + } + int maxSeg = (Integer) value; + if (maxSeg < 0) { + throw new IllegalArgumentException("'" + value + "' is not a valid number of segment." + + " It should be positive."); + } + this.maxSeg = maxSeg; + break; + default: + super.setExtra(key, value); + } + } + + @Override + public Setting clone() { + Setting copy = new SRSetting(); + this.init(copy); + return copy; + } + + @Override + protected void init(Setting copy) { + super.init(copy); + ((SRSetting) copy).setMaxSeg(maxSeg); + } +} diff --git a/src/main/java/edu/repetita/solvers/SRSolver.java b/src/main/java/edu/repetita/solvers/SRSolver.java index ab29c7e..8af8dbd 100644 --- a/src/main/java/edu/repetita/solvers/SRSolver.java +++ b/src/main/java/edu/repetita/solvers/SRSolver.java @@ -1,6 +1,8 @@ package edu.repetita.solvers; +import edu.repetita.core.Setting; import edu.repetita.core.Solver; +import edu.repetita.settings.SRSetting; /** * Abstract class implementing methods common to all segment-routing based solvers. @@ -12,4 +14,9 @@ public abstract class SRSolver extends Solver { public double getTargetObjectiveValue() { return this.targetObjectiveValue; } public void setTargetObjectiveValue(double value) { this.targetObjectiveValue = value; } + + @Override + public Setting getSetting() { + return new SRSetting(); + } } diff --git a/src/main/java/edu/repetita/solvers/sr/DefoCP.java b/src/main/java/edu/repetita/solvers/sr/DefoCP.java index 214b539..d0def89 100644 --- a/src/main/java/edu/repetita/solvers/sr/DefoCP.java +++ b/src/main/java/edu/repetita/solvers/sr/DefoCP.java @@ -9,6 +9,7 @@ import edu.repetita.core.Setting; import edu.repetita.core.Topology; import edu.repetita.paths.SRPaths; +import edu.repetita.settings.SRSetting; import edu.repetita.solvers.SRSolver; import java.io.PrintWriter; @@ -41,10 +42,11 @@ public long solveTime(Setting setting) { @Override public void solve(Setting setting, long timeMillis) { + SRSetting srSetting = (SRSetting) setting; // extract information from setting - Topology topology = setting.getTopology(); + Topology topology = srSetting.getTopology(); int nEdges = topology.nEdges; - Demands demands = setting.getDemands(); + Demands demands = srSetting.getDemands(); // translate in scala data structures be.ac.ucl.ingi.defo.core.Topology defoTopology = be.ac.ucl.ingi.defo.core.Topology.apply(topology.edgeSrc, topology.edgeDest); @@ -57,7 +59,7 @@ public void solve(Setting setting, long timeMillis) { DEFOConstraint[][] demandConstraints = new DEFOConstraint[demands.nDemands][0]; DEFOConstraint[] topologyConstraints = new DEFOConstraint[0]; - DEFOInstance instance = new DEFOInstance(defoTopology, topology.edgeWeight, demandTraffic, demands.source, demands.dest, demandConstraints, topologyConstraints, edgeCapacities, topology.edgeLatency); + DEFOInstance instance = new DEFOInstance(defoTopology, topology.edgeWeight, demandTraffic, demands.source, demands.dest, demandConstraints, topologyConstraints, edgeCapacities, topology.edgeLatency, srSetting.getMaxSeg()); DEFOptimizer optimizer = new DEFOptimizer(instance, this.verbose > 0, scala.Option.apply((PrintWriter) null)); TimeUnit timeLimit = new TimeUnit((int) timeMillis, timeMillis + "ms"); @@ -72,11 +74,11 @@ public void solve(Setting setting, long timeMillis) { // write results back int[][] bestPaths = optimizer.core().bestPaths(); - SRPaths paths = new SRPaths(setting.getDemands(),setting.getTopology().nNodes); + SRPaths paths = new SRPaths(srSetting.getDemands(),srSetting.getTopology().nNodes); for (int demand = 0; demand < demands.nDemands; demand++) { paths.setPath(demand, bestPaths[demand]); } - setting.setSRPaths(paths); + srSetting.setSRPaths(paths); } } diff --git a/src/main/java/edu/repetita/solvers/sr/MIPTwoSRNoSplit.java b/src/main/java/edu/repetita/solvers/sr/MIPTwoSRNoSplit.java index 326cd64..a4466a1 100644 --- a/src/main/java/edu/repetita/solvers/sr/MIPTwoSRNoSplit.java +++ b/src/main/java/edu/repetita/solvers/sr/MIPTwoSRNoSplit.java @@ -278,4 +278,9 @@ public void computeSegments(Setting setting, long timeMillis) { } } + @Override + public Setting getSetting() { + return new Setting(); + } + } diff --git a/src/main/java/edu/repetita/solvers/sr/SRLS.java b/src/main/java/edu/repetita/solvers/sr/SRLS.java index f171002..107b3a4 100644 --- a/src/main/java/edu/repetita/solvers/sr/SRLS.java +++ b/src/main/java/edu/repetita/solvers/sr/SRLS.java @@ -4,6 +4,7 @@ import edu.repetita.core.Setting; import edu.repetita.core.Topology; import edu.repetita.paths.SRPaths; +import edu.repetita.settings.SRSetting; import edu.repetita.solvers.SRSolver; import be.ac.ucl.ingi.rls.LoadOptimizer; import be.ac.ucl.ingi.rls.io.DemandsData; @@ -71,6 +72,7 @@ public long solveTime(Setting setting) { private void initializeScalaVariables(Setting setting){ Topology topology = setting.getTopology(); Demands demands = setting.getDemands(); + SRSetting srSetting = (SRSetting) setting; rlsTopology = TopologyData.apply( topology.nodeLabel, @@ -83,6 +85,6 @@ private void initializeScalaVariables(Setting setting){ ); rlsDemands = DemandsData.apply(demands.label, demands.source, demands.dest, demands.amount); - loadOptimizer = new LoadOptimizer(rlsTopology, rlsDemands, this.verbose > 0); + loadOptimizer = new LoadOptimizer(rlsTopology, rlsDemands, srSetting.getMaxSeg(), this.verbose > 0); } } diff --git a/src/main/scala/be/ac/ucl/ingi/defo/core/CoreSolver.scala b/src/main/scala/be/ac/ucl/ingi/defo/core/CoreSolver.scala index 86cc1d1..7da2937 100755 --- a/src/main/scala/be/ac/ucl/ingi/defo/core/CoreSolver.scala +++ b/src/main/scala/be/ac/ucl/ingi/defo/core/CoreSolver.scala @@ -41,6 +41,7 @@ class CoreSolver(instance: DEFOInstance, verbose: Boolean, statsFile: Option[Pri private[this] val latencies: Array[Int] = instance.latencies private[this] val nEdges = topology.nEdges private[this] val nNodes = topology.nNodes + private[this] val maxSeg = instance.maxSeg // Max number of intermediate segments (i.e., source and destination node not included) // Preprocessed data private[this] val ecmpStruct = ECMPStructure(topology, weights, latencies) @@ -250,7 +251,7 @@ class CoreSolver(instance: DEFOInstance, verbose: Boolean, statsFile: Option[Pri val demand = demandsId(i) // Path length - constraints.append(paths(i).length <= 4) + constraints.append(paths(i).length <= 2 + maxSeg) // Reach constraints.append(new CanReach(paths(i), reachStruct)) // The graph of links is acyclic diff --git a/src/main/scala/be/ac/ucl/ingi/defo/core/DEFOInstance.scala b/src/main/scala/be/ac/ucl/ingi/defo/core/DEFOInstance.scala index 0062e86..4a98c0b 100755 --- a/src/main/scala/be/ac/ucl/ingi/defo/core/DEFOInstance.scala +++ b/src/main/scala/be/ac/ucl/ingi/defo/core/DEFOInstance.scala @@ -13,10 +13,11 @@ class DEFOInstance( val demandConstraints: Array[Array[DEFOConstraint]], val topologyConstraints: Array[DEFOConstraint], val capacities: Array[Int], - val latencies: Array[Int]) + val latencies: Array[Int], + val maxSeg: Int = 2) object DEFOInstance { - def apply(topologyData: TopologyData, demandsData: DemandsData): DEFOInstance = { + def apply(topologyData: TopologyData, demandsData: DemandsData, maxSeg: Int): DEFOInstance = { val topology = Topology(topologyData.edgeSrcs, topologyData.edgeDests) new DEFOInstance( topology, @@ -27,7 +28,8 @@ object DEFOInstance { Array.fill(demandsData.demandTraffics.length)(Array.empty), Array.empty, topologyData.edgeCapacities, - topologyData.edgeLatencies + topologyData.edgeLatencies, + maxSeg ) } } diff --git a/src/main/scala/be/ac/ucl/ingi/rls/LoadOptimizer.scala b/src/main/scala/be/ac/ucl/ingi/rls/LoadOptimizer.scala index 4fb6936..1338824 100644 --- a/src/main/scala/be/ac/ucl/ingi/rls/LoadOptimizer.scala +++ b/src/main/scala/be/ac/ucl/ingi/rls/LoadOptimizer.scala @@ -19,7 +19,7 @@ import be.ac.ucl.ingi.rls.metaheuristic.WeightedDemands * LoadOptimizer tries to find paths so that the max maxLinkLoad of edges is minimal. */ -class LoadOptimizer(topologyData: TopologyData, decisionDemands: DemandsData)(debug: Boolean) { +class LoadOptimizer(topologyData: TopologyData, decisionDemands: DemandsData, maxSeg: Int)(debug: Boolean) { private val topology = Topology(topologyData) /* @@ -53,7 +53,7 @@ class LoadOptimizer(topologyData: TopologyData, decisionDemands: DemandsData)(de // val delays = new DelayDataImpl(topology.nNodes, shortestPaths, topologyData) // initialize network state - private val pathState = new PathState(decisionDemands) + private val pathState = new PathState(decisionDemands, maxSeg) // val delayState = new DelayState(nDemands, delays, pathState) // val flowState = new FlowStateRecomputeDAG(nNodes, nEdges, shortestPaths, pathState, decisionDemands) diff --git a/src/main/scala/be/ac/ucl/ingi/rls/state/PathState.scala b/src/main/scala/be/ac/ucl/ingi/rls/state/PathState.scala index 82d1db2..6e1a962 100644 --- a/src/main/scala/be/ac/ucl/ingi/rls/state/PathState.scala +++ b/src/main/scala/be/ac/ucl/ingi/rls/state/PathState.scala @@ -8,11 +8,11 @@ import be.ac.ucl.ingi.rls.io.DemandsData * A structure to store the current paths (detour sequences) in the network. */ -class PathState(demands: DemandsData) +class PathState(demands: DemandsData, maxSeg: Int) extends TrialState { val nDemands = demands.nDemands - val maxDetourSize = 6 + val maxDetourSize = 2 + maxSeg def source(demand: Demand): Node = demands.demandSrcs(demand) def destination(demand: Demand): Node = demands.demandDests(demand)