001/******************************************************************************* 002 * Copyright (c) 2017 Pablo Pavon Marino and others. 003 * All rights reserved. This program and the accompanying materials 004 * are made available under the terms of the 2-clause BSD License 005 * which accompanies this distribution, and is available at 006 * https://opensource.org/licenses/BSD-2-Clause 007 * 008 * Contributors: 009 * Pablo Pavon Marino and others - initial API and implementation 010 *******************************************************************************/ 011 012 013 014package com.net2plan.examples.ocnbook.offline; 015 016import cern.colt.matrix.tdouble.DoubleFactory1D; 017import cern.colt.matrix.tdouble.DoubleFactory2D; 018import cern.colt.matrix.tdouble.DoubleMatrix1D; 019import cern.colt.matrix.tdouble.DoubleMatrix2D; 020import cern.jet.math.tdouble.DoubleFunctions; 021import cern.jet.math.tdouble.DoublePlusMultSecond; 022import com.jom.OptimizationProblem; 023import com.net2plan.interfaces.networkDesign.*; 024import com.net2plan.libraries.GraphUtils; 025import com.net2plan.utils.Constants.RoutingType; 026import com.net2plan.utils.InputParameter; 027import com.net2plan.utils.Quintuple; 028import com.net2plan.utils.TimeTrace; 029import com.net2plan.utils.Triple; 030 031import java.io.File; 032import java.util.List; 033import java.util.Map; 034 035/** 036 * Finds the routing and mocular capacities for a network that minimize the cost, using a dual decomposition approach 037 * 038 * The time evolution of different metrics can be stored in output files, for later processing. 039 * As an example, see the <a href="../../../../../../graphGeneratorFiles/fig_sec11_7_modularCapacitiesAndRouting_dualDecomp.m">{@code fig_sec11_7_modularCapacitiesAndRouting_dualDecomp.m}</a> MATLAB file used for generating the graph/s of the case study in the 040 * <a href="http://eu.wiley.com/WileyCDA/WileyTitle/productCd-1119013356.html">book</a> using this algorithm. 041 * @net2plan.description 042 * @net2plan.keywords Capacity assignment (CA), Flow assignment (FA), Modular capacities , Dual decomposition 043 * @net2plan.ocnbooksections Section 11.7 044 * @net2plan.inputParameters 045 * @author Pablo Pavon-Marino 046 */ 047public class Offline_cfa_modularCapacitiesAndRoutingDualDecomposition implements IAlgorithm 048{ 049 private double PRECISIONFACTOR; 050 private double numModulesUpperBound; 051 052 private int N , E; 053 054 private InputParameter moduleCapacity = new InputParameter ("moduleCapacity", 1.0 , "Capacity of one module" , 0 , false , Double.MAX_VALUE , true); 055 private InputParameter solverName = new InputParameter ("solverName", "#select# glpk ipopt xpress cplex", "The solver name to be used by JOM. GLPK and IPOPT are free, XPRESS and CPLEX commercial. GLPK, XPRESS and CPLEX solve linear problems w/w.o integer contraints. IPOPT is can solve nonlinear problems (if convex, returns global optimum), but cannot handle integer constraints"); 056 private InputParameter solverLibraryName = new InputParameter ("solverLibraryName", "" , "The solver library full or relative path, to be used by JOM. Leave blank to use JOM default."); 057 private InputParameter maxSolverTimeInSeconds = new InputParameter ("maxSolverTimeInSeconds", (double) -1 , "Maximum time granted to the solver to solve the problem. If this time expires, the solver returns the best solution found so far (if a feasible solution is found)"); 058 private InputParameter subgrad_gammaStep = new InputParameter ("subgrad_gammaStep", (double) 0.05 , "Gamma step in the algorithm iteration" , 0 , false , Double.MAX_VALUE , true); 059 private InputParameter subgrad_type = new InputParameter ("subgrad_type", "#select# 1-over-t constant 1-over-square-root-t", "Type of gradient algorithm. constant, 1-over-t, decreasing-square-t"); 060 private InputParameter simulation_outFileNameRoot = new InputParameter ("simulation_outFileNameRoot", "modularCapacitiesAndRoutingDualDecomp" , "Root of the file name to be used in the output files. If blank, no output"); 061 private InputParameter simulation_numIterations = new InputParameter ("simulation_numIterations", (int) 500 , "Number o iterations of the algorithm" , 1 , Integer.MAX_VALUE); 062 063 064 private DoubleMatrix2D tmColumnSumZero; 065 private DoubleMatrix2D A_ne; 066 067 private TimeTrace stat_bestObjFuncFeasible; 068 private TimeTrace stat_objFuncFeasible; 069 private TimeTrace stat_lowerBound; 070 private TimeTrace stat_bestLowerBound; 071 private TimeTrace stat_pie; 072 073 @Override 074 public String executeAlgorithm(NetPlan netPlan, Map<String, String> algorithmParameters, Map<String, String> net2planParameters) 075 { 076 /* Initialize all InputParameter objects defined in this object (this uses Java reflection) */ 077 InputParameter.initializeAllInputParameterFieldsOfObject(this, algorithmParameters); 078 079 if (netPlan.getNumberOfLayers() != 1) throw new Net2PlanException ("This algorithm works in single layer networks"); 080 081 /* Initialize some variables */ 082 this.N = netPlan.getNumberOfNodes(); 083 this.E = netPlan.getNumberOfLinks(); 084 this.PRECISIONFACTOR = Double.parseDouble(net2planParameters.get("precisionFactor")); 085 086 this.stat_objFuncFeasible = new TimeTrace (); 087 this.stat_bestObjFuncFeasible = new TimeTrace (); 088 this.stat_lowerBound = new TimeTrace (); 089 this.stat_bestLowerBound = new TimeTrace (); 090 this.stat_pie = new TimeTrace (); 091 092 netPlan.removeAllUnicastRoutingInformation(); 093 netPlan.setRoutingTypeAllDemands(RoutingType.HOP_BY_HOP_ROUTING); 094 095 /* The diagonal in the traffic matrix contains minus the amount of traffic destined to that node */ 096 double [][] trafficMatrix = netPlan.getMatrixNode2NodeOfferedTraffic().toArray(); 097 for (int n1 = 0 ; n1 < N ; n1 ++) for (int n2 = 0 ; n2 < N ; n2 ++) if (n1 == n2) continue; else trafficMatrix [n2][n2] -= trafficMatrix [n1][n2]; 098 this.tmColumnSumZero= DoubleFactory2D.dense.make(trafficMatrix); 099 100 System.out.println(netPlan.getMatrixNode2NodeOfferedTraffic()); 101 102 this.numModulesUpperBound = Math.ceil(netPlan.getVectorDemandOfferedTraffic().zSum() / moduleCapacity.getDouble()); 103 this.A_ne = netPlan.getMatrixNodeLinkIncidence(); 104 105 DoubleMatrix1D multipliers_eIP = DoubleFactory1D.dense.make(E, 1.0); 106 double highestDualCost = -Double.MAX_VALUE; 107 double lowestPrimalCost = Double.MAX_VALUE; 108 DoubleMatrix1D bestFeasible_ne = null; 109 DoubleMatrix2D bestRelaxedAndFeasible_xte = null; 110 for (int it = 1 ; it <= simulation_numIterations.getInt() ; it ++) 111 { 112 System.out.println("**** Iteration : " + it + ", mult: " + multipliers_eIP); 113 114 Quintuple<DoubleMatrix2D,DoubleMatrix1D,DoubleMatrix1D,Double,DoubleMatrix1D> q = solveSubproblems (multipliers_eIP); 115 final DoubleMatrix2D relaxedAndFeasible_xte = q.getFirst(); 116 final DoubleMatrix1D relaxed_ne = q.getSecond(); 117 final DoubleMatrix1D feasible_n_e = q.getThird(); 118 final double dualCost = q.getFourth(); 119 final DoubleMatrix1D gradient_e = q.getFifth (); 120 121 final double objFunc = feasible_n_e.zSum(); 122 123 highestDualCost = Math.max(highestDualCost , dualCost); 124 if (objFunc < lowestPrimalCost) 125 { 126 lowestPrimalCost = objFunc; 127 bestFeasible_ne = feasible_n_e.copy(); 128 bestRelaxedAndFeasible_xte = relaxedAndFeasible_xte.copy(); 129 } 130 131 if (highestDualCost > lowestPrimalCost + PRECISIONFACTOR) throw new RuntimeException ("Bad: highestDualCost: "+ highestDualCost + ", lowestPrimalCost: " + lowestPrimalCost); 132 133 System.out.println("* Feasible: Compute cost: " + objFunc); 134 135 this.stat_objFuncFeasible.add (it , new double [] { objFunc } ); 136 this.stat_bestObjFuncFeasible.add(it , new double [] { lowestPrimalCost } ); 137 this.stat_lowerBound.add(it , new double [] { dualCost } ); 138 this.stat_bestLowerBound.add(it , new double [] { highestDualCost } ); 139 this.stat_pie.add(it, multipliers_eIP.toArray() ); 140 141 double gamma = 0; 142 if (this.subgrad_type.getString ().equalsIgnoreCase("constant")) 143 gamma = this.subgrad_gammaStep.getDouble(); 144 else if (this.subgrad_type.getString ().equalsIgnoreCase("1-over-t")) 145 gamma = this.subgrad_gammaStep.getDouble() / it; 146 else if (this.subgrad_type.getString ().equalsIgnoreCase("1-over-square-root-t")) 147 gamma = this.subgrad_gammaStep.getDouble() / Math.sqrt(it); 148 else throw new Net2PlanException ("Unknown subgradient algorithm type"); 149 for (int e = 0 ; e < E ; e++) 150 multipliers_eIP.set(e , Math.max(0 , multipliers_eIP.get(e) + gamma * gradient_e.get(e) )); 151 } 152 153 saveNetPlan (netPlan , bestRelaxedAndFeasible_xte , bestFeasible_ne); 154 155 finish (netPlan); 156 157 return "Ok! Best solution. Cost: " + bestFeasible_ne.zSum(); 158 } 159 160 @Override 161 public String getDescription() 162 { 163 return "Given a network with a set of given nodes, and links, and a given known unicast offered traffic. This algorithm jointly computes (i) the routing of the traffic, and the capacity to assign to the links. The link capacities are constrained to be modular: an integer multiple of a link capacity quantum. Optimization target is minimizing the network cost, given by the number of capacity modules to install in the network. The problem in NP-hard. The algorithm implements a dual decomposition iterative, using a subgradient algorithm."; 164 } 165 166 @Override 167 public List<Triple<String, String, String>> getParameters() 168 { 169 /* Returns the parameter information for all the InputParameter objects defined in this object (uses Java reflection) */ 170 return InputParameter.getInformationAllInputParameterFieldsOfObject(this); 171 } 172 173 private Quintuple<DoubleMatrix2D,DoubleMatrix1D,DoubleMatrix1D,Double,DoubleMatrix1D> solveSubproblems (DoubleMatrix1D pi_e) 174 { 175 /* Upper layer */ 176 DoubleMatrix2D x_te = DoubleFactory2D.dense.make(N,E); 177 { 178 OptimizationProblem op = new OptimizationProblem(); 179 op.setInputParameter("pi_e", pi_e.toArray() , "column"); 180 op.setInputParameter("A_ne", A_ne.toArray()); 181 op.setInputParameter("TM", this.tmColumnSumZero.toArray()); 182 op.addDecisionVariable("x_te", false , new int[] { N , E }, 0, Double.MAX_VALUE); // dest-link formulation at IP layer 183 op.setObjectiveFunction("minimize", "sum (x_te * pi_e)"); 184 op.addConstraint("A_ne * (x_te') == TM"); // the flow-conservation constraints (NxN constraints) 185 /* Call the solver to solve the problem */ 186 op.solve(solverName.getString (), "solverLibraryName", solverLibraryName.getString () , "maxSolverTimeInSeconds" , maxSolverTimeInSeconds.getDouble ()); 187 188 /* If no solution is found, quit */ 189 if (op.feasibleSolutionDoesNotExist()) throw new Net2PlanException("The problem has no feasible solution"); 190 if (op.foundUnboundedSolution()) throw new Net2PlanException ("Found an unbounded solution"); 191 if (!op.solutionIsFeasible()) throw new Net2PlanException("A feasible solution was not found"); 192 x_te = op.getPrimalSolution("x_te").view2D(); 193 } 194 195 DoubleMatrix1D n_e = DoubleFactory1D.dense.make(E); 196 for (int e = 0 ; e < E ; e ++) 197 n_e.set(e , (1 - (moduleCapacity.getDouble() * pi_e.get(e)) >= 0)? 0 : numModulesUpperBound); 198 199 DoubleMatrix1D y_e = x_te.copy().zMult(DoubleFactory1D.dense.make(N, 1.0) , null , 1.0 , 0.0 , true); 200 DoubleMatrix1D gradient_e = y_e.copy().assign(n_e , DoublePlusMultSecond.plusDiv(-moduleCapacity.getDouble())); 201 202 /* Compute the dual cost */ 203 final double dualCost = x_te.copy().zMult(pi_e, null).zSum() - n_e.zDotProduct(pi_e) * moduleCapacity.getDouble(); 204 205 /* Compute a feasible solution from the relaxed x_te */ 206 DoubleMatrix1D feasible_n_e = y_e.copy().assign(DoubleFunctions.div(moduleCapacity.getDouble())).assign(DoubleFunctions.ceil); 207 208// System.out.println("carriedTraffic_e: " + carriedTraffic_e); 209// System.out.println("capacityLink_e: " + capacityLink_e); 210// System.out.println("barU_e: " + barU_e); 211 212 return Quintuple.of(x_te.copy () , n_e.copy() , feasible_n_e.copy() , dualCost , gradient_e); 213 } 214 215 private void finish (NetPlan np) 216 { 217 /* If no output file, return */ 218 if (simulation_outFileNameRoot.getString ().equals("")) return; 219 stat_objFuncFeasible.printToFile(new File (simulation_outFileNameRoot.getString () + "_objFunc.txt")); 220 stat_bestObjFuncFeasible.printToFile(new File (simulation_outFileNameRoot.getString () + "_bestObjFunc.txt")); 221 stat_lowerBound.printToFile(new File (simulation_outFileNameRoot.getString () + "_lowerBound.txt")); 222 stat_bestLowerBound.printToFile(new File (simulation_outFileNameRoot.getString () + "_bestLowerBound.txt")); 223 stat_pie.printToFile(new File (simulation_outFileNameRoot.getString () + "_pie.txt")); 224 225 226 double lowerBoundNumLps_1 = 0; 227 for (Node s : np.getNodes()) 228 { 229 double outTraffic = 0; for (Demand d : s.getOutgoingDemands()) outTraffic += d.getOfferedTraffic(); 230 lowerBoundNumLps_1 += Math.ceil(outTraffic / moduleCapacity.getDouble()); 231 } 232 double lowerBoundNumLps_2 = 0; 233 for (Node t : np.getNodes()) 234 { 235 double inTraffic = 0; for (Demand d : t.getIncomingDemands()) inTraffic += d.getOfferedTraffic(); 236 lowerBoundNumLps_2 += Math.ceil(inTraffic / moduleCapacity.getDouble()); 237 } 238 double lowerBoundNumLps_3 = 0; 239 for (Node t : np.getNodes()) 240 for (Node s : np.getNodes()) 241 { 242 if (s == t) continue; 243 final int numHopsSP = GraphUtils.getShortestPath(np.getNodes () , np.getLinks(), s, t, null).size(); 244 for (Demand d : np.getNodePairDemands(s,t,false)) lowerBoundNumLps_3 += numHopsSP * d.getOfferedTraffic(); 245 } 246 lowerBoundNumLps_3 = Math.ceil(lowerBoundNumLps_3/moduleCapacity.getDouble()); 247 248 TimeTrace.printToFile(new File (simulation_outFileNameRoot.getString () + "_heuristicLBCost.txt") , new double [] { lowerBoundNumLps_1 , lowerBoundNumLps_2 , lowerBoundNumLps_3 } ); 249 250 } 251 252 void saveNetPlan (NetPlan netPlan , DoubleMatrix2D x_te , DoubleMatrix1D n_e) 253 { 254 /* Set the routing at the IP layer */ 255 netPlan.setRoutingTypeAllDemands(RoutingType.HOP_BY_HOP_ROUTING); 256 netPlan.removeAllForwardingRules(); 257 netPlan.setRoutingFromDestinationLinkCarriedTraffic(x_te , true); 258 for (Link e : netPlan.getLinks()) 259 e.setCapacity(moduleCapacity.getDouble() * n_e.get(e.getIndex ())); 260 for (Demand d : netPlan.getDemandsBlocked()) 261 if (d.getBlockedTraffic() > PRECISIONFACTOR) throw new RuntimeException ("Bad"); 262 for (Link e : netPlan.getLinksOversubscribed()) 263 if (e.getOccupiedCapacity() - e.getCapacity() > PRECISIONFACTOR) throw new RuntimeException ("Bad"); 264 } 265 266}