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 014 015 016 017 018 019 020 021package com.net2plan.examples.ocnbook.offline; 022 023import cern.colt.matrix.tdouble.DoubleFactory1D; 024import cern.colt.matrix.tdouble.DoubleMatrix2D; 025import com.jom.DoubleMatrixND; 026import com.jom.OptimizationProblem; 027import com.net2plan.interfaces.networkDesign.*; 028import com.net2plan.utils.InputParameter; 029import com.net2plan.utils.Triple; 030 031import java.util.HashSet; 032import java.util.List; 033import java.util.Map; 034import java.util.Set; 035 036/** 037 * Solves several variants of multicast routing problems, with flow-link formulations 038 * @net2plan.description 039 * @net2plan.keywords Multicast, JOM, Flow-link formulation, Flow assignment (FA) 040 * @net2plan.ocnbooksections Section 4.6.2 041 * @net2plan.inputParameters 042 * @author Pablo Pavon-Marino 043 */ 044public class Offline_fa_xdeFormulationsMulticast implements IAlgorithm 045{ 046 private InputParameter linkCostType = new InputParameter ("linkCostType", "#select# hops km" , "Criteria to compute the multicast tree cost. Valid values: 'hops' (all links cost one) or 'km' (link cost is its length in km)"); 047 private InputParameter optimizationTarget = new InputParameter ("optimizationTarget", "#select# min-consumed-bandwidth min-av-num-hops minimax-link-utilization maximin-link-idle-capacity" , "Type of optimization target. Choose among minimize the total traffic in the links, minimize the average number of hops from ingress to different egress nodes, minimize the highest link utilization, maximize the lowest link idle capacity"); 048 private InputParameter maxCopyCapability = new InputParameter ("maxCopyCapability", (int) -1 , "Maximum number of copies of the traffic a node can make (this is the maximum number of output links in a node of the same multicast tree). A non-positive value means no limit"); 049 private InputParameter maxE2ELengthInKm = new InputParameter ("maxE2ELengthInKm", (double) -1 , "The path from an origin to any destination in any multicast tree cannot be longer than this. A non-positive number means this limit does not exist"); 050 private InputParameter maxE2ENumHops = new InputParameter ("maxE2ENumHops", (int) -1 , "The path from an origin to any destination in any multicast tree cannot have more than this number of hops. A non-positive number means this limit does not exist"); 051 private InputParameter maxE2EPropDelayInMs = new InputParameter ("maxE2EPropDelayInMs", (double) -1 , "The path from an origin to any destination in any multicast tree cannot have more than this propagation delay in miliseconds. A non-positive number means this limit does not exist"); 052 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"); 053 private InputParameter solverLibraryName = new InputParameter ("solverLibraryName", "" , "The solver library full or relative path, to be used by JOM. Leave blank to use JOM default."); 054 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)"); 055 056 @Override 057 public String executeAlgorithm(NetPlan netPlan, Map<String, String> algorithmParameters, Map<String, String> net2planParameters) 058 { 059 /* Initialize all InputParameter objects defined in this object (this uses Java reflection) */ 060 InputParameter.initializeAllInputParameterFieldsOfObject(this, algorithmParameters); 061 if (!linkCostType.getString().equalsIgnoreCase("km") && !linkCostType.getString().equalsIgnoreCase("hops")) 062 throw new Net2PlanException("Wrong linkCostType parameter"); 063 064 /* Initialize variables */ 065 final int E = netPlan.getNumberOfLinks(); 066 final int N = netPlan.getNumberOfNodes(); 067 final int MD = netPlan.getNumberOfMulticastDemands(); 068 if (E == 0 || MD == 0) throw new Net2PlanException("This algorithm requires a topology with links and a multicast demand set"); 069 070 /* Remove all multicast routed traffic. Any unicast routed traffic is kept */ 071 netPlan.removeAllMulticastTrees(); 072 073 /* Create the optimization problem object (JOM library) */ 074 OptimizationProblem op = new OptimizationProblem(); 075 076 /* Set some input parameters to the problem */ 077 op.setInputParameter("u_e", netPlan.getVectorLinkSpareCapacity(), "row"); /* for each link, its unused capacity (the one not used by any mulitcast trees) */ 078 op.setInputParameter("A_nd", netPlan.getMatrixNodeMulticastDemandIncidence()); /* 1 if node n is ingress in multicast demand d, -1 if n is egress */ 079 final DoubleMatrix2D Aout_nd = netPlan.getMatrixNodeMulticastDemandOutgoingIncidence(); 080 final DoubleMatrix2D Ain_nd = netPlan.getMatrixNodeMulticastDemandIncomingIncidence(); 081 op.setInputParameter("Aout_nd", Aout_nd); /* 1 if node n is ingress in multicast demand d */ 082 op.setInputParameter("Ain_nd", Ain_nd); /* -1 if node n is egress in multicast demand d */ 083 op.setInputParameter("A_ne", netPlan.getMatrixNodeLinkIncidence()); /* 1 in position (n,e) if link e starts in n, -1 if it ends in n, 0 otherwise */ 084 op.setInputParameter("Aout_ne", netPlan.getMatrixNodeLinkOutgoingIncidence()); /* 1 in position (n,e) if link e starts in n */ 085 op.setInputParameter("Ain_ne", netPlan.getMatrixNodeLinkIncomingIncidence()); /* 1 in position (n,e) if link e ends in n */ 086 op.setInputParameter("h_d", netPlan.getVectorMulticastDemandOfferedTraffic(), "row"); /* for each multicast demand, its offered traffic */ 087 op.setInputParameter("lengthInKm_e", netPlan.getVectorLinkLengthInKm(), "row"); /* for each link, its length in km */ 088 op.setInputParameter("propDelay_e", netPlan.getVectorLinkPropagationDelayInMiliseconds(), "row"); /* for each link, its length in km */ 089 op.setInputParameter("onesE", DoubleFactory1D.dense.make (E,1.0) , "row"); /* for each link, a one */ 090 op.setInputParameter("K", maxCopyCapability.getInt() <= 0? N : maxCopyCapability.getInt ()); /* the maximum number of output links a node can copy the input traffic of a multicast tree (<= 0 means no limitation) */ 091 092 /* Add decision variables for the multicast demands */ 093 op.addDecisionVariable("xx_de", true , new int [] {MD , E} , 0 , 1); // 1 if link e is used my the multicast tree of this demand 094 op.addDecisionVariable("xx_det", true , new int [] {MD , E , N} , 0 , 1); // 1 if link e is used my the multicast tree of this demand 095 096 /* Write the problem objective function and constraints specific to this objective function */ 097 if (optimizationTarget.getString ().equals ("min-consumed-bandwidth")) 098 { 099 op.setObjectiveFunction("minimize", "sum (h_d * xx_de)"); /* total traffic in the links */ 100 op.addConstraint("h_d * xx_de <= u_e"); /* the traffic in each link cannot exceed its capacity */ 101 } 102 else if (optimizationTarget.getString ().equals ("min-av-num-hops")) 103 { 104 op.setInputParameter ("EPSILON" , getMinimumNonZeroTrafficOrCapacityValue (netPlan) / 1000); 105 op.setObjectiveFunction("minimize", "sum(diag(h_d) * xx_det) + EPSILON * sum (h_d * xx_de)"); /* proportional to the number of hops each packet makes */ 106 op.addConstraint("h_d * xx_de <= u_e"); /* the traffic in each link cannot exceed its capacity */ 107 } 108 else if (optimizationTarget.getString ().equals ("minimax-link-utilization")) 109 { 110 op.setInputParameter ("EPSILON" , getMinimumNonZeroTrafficOrCapacityValue (netPlan) / 1000); 111 op.addDecisionVariable("rho", false, new int[] { 1, 1 }, 0, 1); /* worse case link utilization */ 112 op.setObjectiveFunction("minimize", "rho + EPSILON * sum (h_d * xx_de)"); // to avoid loops, we sum EPSILON by the traffic carried (EPSILON very small number) 113 op.addConstraint("h_d * xx_de <= rho * u_e"); /* the traffic in each link cannot exceed its capacity. sets rho as the worse case utilization */ 114 } 115 else if (optimizationTarget.getString ().equals ("maximin-link-idle-capacity")) 116 { 117 op.setInputParameter ("EPSILON" , getMinimumNonZeroTrafficOrCapacityValue (netPlan) / 1000); 118 op.addDecisionVariable("u", false, new int[] { 1, 1 }, 0, Double.MAX_VALUE); /* worse case link idle capacity */ 119 op.setObjectiveFunction("maximize", "u - EPSILON * sum (h_d * xx_de)"); // to avoid loops, we sum EPSILON by the traffic carried (EPSILON very small number) 120 op.addConstraint("h_d * xx_de <= -u + u_e"); /* the traffic in each link cannot exceed its capacity. sets u as the worse case idle capacity */ 121 } 122 else throw new Net2PlanException ("Unknown optimization target " + optimizationTarget.getString()); 123 124// DoubleMatrix3D I_eet = DoubleFactory3D.sparse.make (E,E,N); for (int t = 0; t < N ; t ++) I_eet.viewColumn (t).assign (DoubleFactory2D.sparse.identity(E)); 125// op.setInputParameter("I_eet", new DoubleMatrixND (I_eet)); 126 DoubleMatrixND I_eet = new DoubleMatrixND (new int [] {E,E,N} , "sparse"); for (int t = 0 ; t < N ; t ++) for (int e = 0 ; e < E ; e ++) I_eet.set (new int [] { e,e,t} , 1.0); 127 op.setInputParameter("I_eet", I_eet); 128 129 DoubleMatrixND A_ndt = new DoubleMatrixND (new int [] { N , MD , N } , "sparse"); 130 for (MulticastDemand d : netPlan.getMulticastDemands()) 131 for (Node t : d.getEgressNodes()) 132 { 133 A_ndt.set (new int [] { d.getIngressNode().getIndex() , d.getIndex () , t.getIndex () } , 1.0); 134 A_ndt.set (new int [] { t.getIndex () , d.getIndex () , t.getIndex () } , -1.0); 135 } 136 op.setInputParameter("A_ndt", A_ndt); 137 op.setInputParameter("onesN" , DoubleFactory1D.dense.make (N,1.0) , "row"); 138 139 /* Add constraints for the multicast demands */ 140 op.addConstraint ("Ain_ne * xx_de' >= Ain_nd"); // a destination node receives at least one input link 141 op.addConstraint ("Ain_ne * xx_de' <= 1 - Aout_nd"); // source nodes receive 0 links, destination nodes at most one (then just one) 142 op.addConstraint ("Aout_ne * xx_de' <= K * (Aout_nd + (Ain_ne * xx_de'))"); // at most K out links from ingress node and from intermediate nodes if they have one input link 143 op.addConstraint ("xx_det <= xx_de * I_eet"); // a link belongs to a path only if it is in the tree 144 op.addConstraint ("A_ne * permute(xx_det , [2 ; 1 ; 3]) == A_ndt"); // flow conservation constraint for each path in the tree 145 146 final boolean maxE2ELengthConstraintApplies = (maxE2ELengthInKm.getDouble() > 0) && (maxE2ELengthInKm.getDouble() < netPlan.getVectorLinkLengthInKm().zSum()); 147 final boolean maxE2ENumHopsConstraintApplies = (maxE2ENumHops.getInt() > 0) && (maxE2ENumHops.getInt() < E); 148 final boolean maxE2EPropDelayConstraintApplies = (maxE2EPropDelayInMs.getDouble() > 0) && (maxE2EPropDelayInMs.getDouble() < netPlan.getVectorLinkPropagationDelayInMiliseconds().zSum()); 149 if (maxE2ELengthConstraintApplies) op.addConstraint ("lengthInKm_e * permute(xx_det,[2;1;3]) <= " + maxE2ELengthInKm.getDouble()); // maximum length in km from ingress node to any demand egress node is limited 150 if (maxE2ENumHopsConstraintApplies) op.addConstraint ("sum(xx_det,2) <= " + maxE2ENumHops.getInt()); // maximum number of hops from ingress node to any demand egress node is limited 151 if (maxE2EPropDelayConstraintApplies) op.addConstraint ("propDelay_e * permute(xx_det,[2;1;3]) <= " + maxE2EPropDelayInMs.getDouble()); // maximum propagation delay in ms from ingress node to any demand egress node is limited 152 153 op.solve(solverName.getString (), "solverLibraryName", solverLibraryName.getString () , "maxSolverTimeInSeconds" , maxSolverTimeInSeconds.getDouble ()); 154 155 /* If no solution is found, quit */ 156 if (op.feasibleSolutionDoesNotExist()) throw new Net2PlanException("The problem has no feasible solution"); 157 if (!op.solutionIsFeasible()) throw new Net2PlanException("A feasible solution was not found"); 158 159 /* Save the solution found in the netPlan object */ 160 final DoubleMatrix2D xx_de = op.getPrimalSolution("xx_de").view2D(); 161 /* Check */ 162 final DoubleMatrixND xx_det = op.getPrimalSolution("xx_det"); 163 for (MulticastDemand d : netPlan.getMulticastDemands()) 164 for (Link e : netPlan.getLinks()) 165 for (Node n : netPlan.getNodes()) 166 if (xx_de.get(d.getIndex () , e.getIndex ()) == 0) 167 if (xx_det.get(new int [] {d.getIndex() , e.getIndex () , n.getIndex() } ) != 0) throw new RuntimeException ("Bad: x_de: " + (xx_de.get(d.getIndex () , e.getIndex ())) + ", xx_det: " + (xx_det.get(new int [] {d.getIndex() , e.getIndex () , n.getIndex() } )) ); 168 169 for (MulticastDemand d : netPlan.getMulticastDemands()) 170 { 171 Set<Link> linkSet = new HashSet<Link> (); for (int e = 0 ; e < E ; e ++) if (xx_de.get(d.getIndex() , e) != 0) { linkSet.add (netPlan.getLink (e)); } 172 netPlan.addMulticastTree(d , d.getOfferedTraffic() , d.getOfferedTraffic() , linkSet , null); 173 } 174 175 176 177 178 return "Ok!: The solution found is guaranteed to be optimal: " + op.solutionIsOptimal() + ", number of multicast trees created: " + netPlan.getNumberOfMulticastTrees(); 179 } 180 181 @Override 182 public String getDescription() 183 { 184 return "Given a network topology, the capacities in the links, and a set multicast traffic demands, this algorithm permits computing the optimum multicast routing of the traffic (that is, the set ofm multicast trees carrying the traffic of the multicast demand) solving flow-link formulations. Through a set of input parameters, the user can choose among different optimization targets and constraints."; 185 } 186 187 188 @Override 189 public List<Triple<String, String, String>> getParameters() 190 { 191 /* Returns the parameter information for all the InputParameter objects defined in this object (uses Java reflection) */ 192 return InputParameter.getInformationAllInputParameterFieldsOfObject(this); 193 } 194 195 private double getMinimumNonZeroTrafficOrCapacityValue (NetPlan netPlan) 196 { 197 double res = Double.MAX_VALUE; 198 for (Demand d : netPlan.getDemands ()) if (d.getOfferedTraffic() > 0) res = Math.min (res , d.getOfferedTraffic()); 199 for (Link e : netPlan.getLinks ()) if (e.getCapacity() > 0) res = Math.min (res , e.getCapacity()); 200 if (res == Double.MAX_VALUE) throw new Net2PlanException ("Too large offered traffics and link capacities"); 201 return res; 202 } 203}