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 *******************************************************************************/ 011package com.net2plan.examples.ocnbook.offline; 012 013 014import cern.colt.matrix.tdouble.DoubleMatrix1D; 015import com.net2plan.interfaces.networkDesign.IAlgorithm; 016import com.net2plan.interfaces.networkDesign.Link; 017import com.net2plan.interfaces.networkDesign.Net2PlanException; 018import com.net2plan.interfaces.networkDesign.NetPlan; 019import com.net2plan.libraries.IPUtils; 020import com.net2plan.utils.Constants.OrderingType; 021import com.net2plan.utils.Constants.RoutingType; 022import com.net2plan.utils.*; 023 024import java.io.File; 025import java.util.*; 026 027/** 028 * Searches for the OSPF link weights that minimize a measure of congestion, using an ant-colony optimization (ACO) heuristic. 029 * 030 * The time evolution of different metrics can be stored in output files, for later processing. 031 * As an example, see the <a href="../../../../../../graphGeneratorFiles/fig_sec12_8_ospfWeightACO.m">{@code fig_sec12_8_ospfWeightACO.m}</a> MATLAB file used for generating the graph/s of the case study in the 032 * <a href="http://eu.wiley.com/WileyCDA/WileyTitle/productCd-1119013356.html">book</a> using this algorithm. 033 * @net2plan.description 034 * @net2plan.keywords IP/OSPF, Flow assignment (FA), Ant Colony Optimization (ACO) 035 * @net2plan.ocnbooksections Section 12.8 036 * @net2plan.inputParameters 037 * @author Pablo Pavon-Marino 038 */ 039public class Offline_fa_ospfWeightOptimization_ACO implements IAlgorithm 040{ 041 private TimeTrace stat_objFunction; 042 private TimeTrace stat_pheromone_ew; 043 private OSPFHeuristicUtils ospfEngine; 044 045 private InputParameter aco_initializationType = new InputParameter ("aco_initializationType", "#select# random" , "The type of initialization of the OSPF link weights"); 046 private InputParameter ospf_maxLinkWeight = new InputParameter ("ospf_maxLinkWeight", (int) 16 , "OSPF link weights are constrained to be integers between 1 and this parameter" , 1 , Integer.MAX_VALUE); 047 private InputParameter aco_differenceInWeightToBeNeighbors = new InputParameter ("aco_differenceInWeightToBeNeighbors", (int) 1 , "Two solutions where all the links have the same weight, but one link where the weight differs in the quantity given by this parameter, are considered neighbors"); 048 private InputParameter algorithm_randomSeed = new InputParameter ("algorithm_randomSeed", (long) 1 , "Seed of the random number generator"); 049 private InputParameter algorithm_outputFileNameRoot = new InputParameter ("algorithm_outputFileNameRoot", "ospfWeghtOptimization_aco" , "Root of the file name to be used in the output files. If blank, no output"); 050 private InputParameter ospf_weightOfMaxUtilizationInObjectiveFunction = new InputParameter ("ospf_weightOfMaxUtilizationInObjectiveFunction", (double) 0.9 , "Objective function is this factor multiplied by maximum link utilization, plus 1 minus this factor by average link utilization" , 0 , true , 1 , true); 051 private InputParameter algorithm_maxExecutionTimeInSeconds = new InputParameter ("algorithm_maxExecutionTimeInSeconds", (double) 5 , "Algorithm maximum running time in seconds" , 0 , false , Double.MAX_VALUE , true); 052 private InputParameter aco_maxNumIterations = new InputParameter ("aco_maxNumIterations", (int) 100 , "Maximum number of iterations" , 1 , Integer.MAX_VALUE); 053 private InputParameter aco_pheromoneEvaporationRate = new InputParameter ("aco_pheromoneEvaporationRate", (double) 0.5 , "Evaporation factor of the pheromones in the ACO model" , 0 , true , 1 , true); 054 private InputParameter aco_factorImportanceOfPheromones = new InputParameter ("aco_factorImportanceOfPheromones", (double) 1 , "Importance of pheromones vs greedy step for the ants" , 0 , true , 1 , true); 055 private InputParameter aco_numAnts = new InputParameter ("aco_numAnts", (int) 10 , "Number of ants in the system" , 1 , Integer.MAX_VALUE); 056 private InputParameter aco_fractionOfEliteAntsContributingToPheromones = new InputParameter ("aco_fractionOfEliteAntsContributingToPheromones", (double) 0.5 , "Only the best fraction of the ants (round up) contributes to pheromone reinforcement" , 0 , true , 1 , true); 057 058 059 @Override 060 public String executeAlgorithm(NetPlan netPlan, Map<String, String> algorithmParameters, Map<String, String> net2planParameters) 061 { 062 /* Initialize all InputParameter objects defined in this object (this uses Java reflection) */ 063 InputParameter.initializeAllInputParameterFieldsOfObject(this, algorithmParameters); 064 065 /* Basic checks */ 066 final int N = netPlan.getNumberOfNodes(); 067 final int E = netPlan.getNumberOfLinks(); 068 if (N == 0) throw new Net2PlanException("The input design must have nodes"); 069 netPlan.removeAllUnicastRoutingInformation(); 070 netPlan.setRoutingTypeAllDemands (RoutingType.HOP_BY_HOP_ROUTING); 071 072 Random rng = new Random (algorithm_randomSeed.getLong()); 073 this.ospfEngine = new OSPFHeuristicUtils(netPlan, ospf_maxLinkWeight.getInt (), ospf_weightOfMaxUtilizationInObjectiveFunction.getDouble(), rng); 074 final long algorithmInitialtime = System.nanoTime(); 075 final long algorithmEndtime = algorithmInitialtime + (long) (algorithm_maxExecutionTimeInSeconds.getDouble() * 1E9); 076 077 this.stat_objFunction = new TimeTrace (); 078 this.stat_pheromone_ew = new TimeTrace (); 079 DoubleMatrix1D bestSol = null; 080 double bestObjFunction = Double.MAX_VALUE; 081 082 /* Initialize all pheromones to zero */ 083 double [][] pheromones_ew = new double [E][ospf_maxLinkWeight.getInt ()]; for (int e = 0 ; e < E ; e ++) Arrays.fill (pheromones_ew [e] , 1.0); 084 085 int iterationCounter = 0; 086 while ((System.nanoTime() < algorithmEndtime) && (iterationCounter < aco_maxNumIterations.getInt ())) 087 { 088 iterationCounter ++; 089 090 /* To store each ant solution and objective function */ 091 double [] objFunc_a = new double [aco_numAnts.getInt ()]; 092 ArrayList<DoubleMatrix1D> sol_a = new ArrayList<DoubleMatrix1D> (aco_numAnts.getInt ()); 093 for (int contAnt = 0 ; contAnt < aco_numAnts.getInt () ; contAnt ++) 094 { 095 Pair<DoubleMatrix1D,Double> p = ospfEngine.getInitialSolution (aco_initializationType.getString ()); 096 DoubleMatrix1D currentSol = p.getFirst(); 097 098 /* Create a randomly shuffled sequence of link ids */ 099 List<Link> shuffledLinks = new ArrayList<Link> (netPlan.getLinks()); 100 Collections.shuffle(shuffledLinks , rng); 101 102 /* The ant movements */ 103 for (Link e : shuffledLinks) 104 antWeightChoice (e , currentSol , ospf_maxLinkWeight.getInt () , aco_differenceInWeightToBeNeighbors.getInt () , pheromones_ew [e.getIndex ()] , aco_factorImportanceOfPheromones.getDouble() , rng); 105 106 double objFunc = ospfEngine.computeObjectiveFunction(currentSol).getFirst(); 107 objFunc_a [contAnt] = objFunc; 108 sol_a.add(currentSol); 109 110 /* Update the incumbent solution */ 111 if (objFunc < bestObjFunction) { bestObjFunction = objFunc; bestSol = currentSol.copy (); } 112 113 } 114 115 /* Truncate it in case we end the algorithm ahead of time */ 116 objFunc_a = Arrays.copyOf(objFunc_a, sol_a.size()); 117 118 /* Increase the pheromones */ 119 int [] orderedAntIndexes = DoubleUtils.sortIndexes(objFunc_a, OrderingType.ASCENDING); 120 final int numEliteAnts = (int) Math.ceil(sol_a.size() * aco_fractionOfEliteAntsContributingToPheromones.getDouble()); 121 for (int cont = 0 ; cont < numEliteAnts ; cont ++) 122 { 123 final int a = orderedAntIndexes [cont]; 124 final double objFuncAnt = objFunc_a [a]; 125 for (Link e : netPlan.getLinks()) 126 { 127 final int linkWeightAnt = (int) sol_a.get(a).get(e.getIndex ()); 128 pheromones_ew[e.getIndex ()][linkWeightAnt-1] += 1.0 / objFuncAnt; 129 } 130 } 131 132 /* Evaporate pheromones */ 133 for (Link e : netPlan.getLinks()) 134 { 135 double [] pheromone_w = pheromones_ew [e.getIndex ()]; 136 for (int w = 0; w < pheromone_w.length ; w ++) pheromone_w[w] *= 1 - aco_pheromoneEvaporationRate.getDouble(); 137 } 138 139 /* Update the stats */ 140 //this.stat_objFunction.add(iterationCounter , Arrays.copyOf (objFunc_a , numAnts)); 141 this.stat_objFunction.add(iterationCounter , copyOf (objFunc_a , sol_a.size() , aco_numAnts.getInt ())); 142 this.stat_pheromone_ew.add (iterationCounter , copyOf(pheromones_ew)); 143 } 144 145 final String baseFileName = algorithm_outputFileNameRoot.getString () + "_a" + aco_numAnts.getInt () + "_rho" + ((int) (aco_pheromoneEvaporationRate.getDouble()*100)) + "_g" + ((int) (100*aco_factorImportanceOfPheromones.getDouble())); 146 stat_objFunction.printToFile(new File (baseFileName + "_objFunc.txt")); 147 stat_pheromone_ew.printToFile(new File (baseFileName + "_pheromones_ew.txt")); 148 149 IPUtils.setECMPForwardingRulesFromLinkWeights(netPlan, bestSol); 150 151 System.out.println("Ok! Best solution OF: " + bestObjFunction); 152 return "Ok! Best OF: " + bestObjFunction; 153 } 154 155 @Override 156 public String getDescription() 157 { 158 return "Given a set of nodes, links and offered traffic, this algorithm assumes that the nodes are IP routers running the OSPF protocol (applying ECMP rules) for routing it. The algorithm searches for the set of link weights that optimize the routing. In particular, the target is minimizing a congestion metric computed as a function of both the worst-case link utilization and the average link utilization. The algorithm is based on applying an ant-colony optimization (ACO) heuristic approach."; 159 } 160 161 @Override 162 public List<Triple<String, String, String>> getParameters() 163 { 164 /* Returns the parameter information for all the InputParameter objects defined in this object (uses Java reflection) */ 165 return InputParameter.getInformationAllInputParameterFieldsOfObject(this); 166 } 167 168 private void antWeightChoice (Link e , DoubleMatrix1D currentSol , int maxLinkWeight , int differenceInWeightToBeNeighbors , double [] pheromone_w , double factorImportanceOfPheromones , Random rng) 169 { 170 /* Sum the probabilities coming from the pheromones */ 171 double [] accumProbabilities = new double [pheromone_w.length]; 172 double accumProbabilitySum = 0; 173 for (int w = 0 ; w < accumProbabilities.length ; w ++) { accumProbabilities [w] = pheromone_w [w] * factorImportanceOfPheromones; accumProbabilitySum += accumProbabilities [w]; } 174 175 if (factorImportanceOfPheromones != 1) 176 { 177 Pair<double [],int []> pair = ospfEngine.computeSolutionsVaryingLinkWeight (e , currentSol , null , differenceInWeightToBeNeighbors); 178 final double [] objFunctions = pair.getFirst(); 179 final int [] weights = pair.getSecond(); 180 181 for (int cont = 0 ; cont < objFunctions.length ; cont ++) 182 { 183 final int w = weights [cont]; 184 final double greedyObjFunction = objFunctions [cont]; 185 final double importanceToSum = (1/greedyObjFunction) * (1-factorImportanceOfPheromones); 186 accumProbabilities [w-1] += importanceToSum; 187 accumProbabilitySum += importanceToSum; 188 } 189 } 190 currentSol.set(e.getIndex (), 1.0 + (double) sampleUniformDistribution (accumProbabilities , accumProbabilitySum , rng)); 191 return ; 192 } 193 194 /* Receives a vector with values proportional to the probabilities p[s] of each option s. Returns the index of the sample chosen. 195 * Each sample s has a probability to be chosen proportional to p[s] */ 196 private int sampleUniformDistribution (double [] p , double totalSumProbabilities , Random r) 197 { 198 final double comp = r.nextDouble() * totalSumProbabilities; 199 double accumValue = 0; 200 201 double checkTotalSum = 0; for (int cont = 0 ; cont < p.length ; cont ++) checkTotalSum += p [cont]; 202 if (Math.abs(checkTotalSum - totalSumProbabilities) > 1E-3) throw new RuntimeException ("Bad"); 203 for (int cont = 0 ; cont < p.length ; cont ++) 204 { accumValue += p [cont]; if (accumValue >= comp) return cont; } 205 return p.length-1; 206 } 207 208 private double [][] copyOf (double [][] x) 209 { 210 double [][] res = new double [x.length][]; for (int c1 = 0 ; c1 < x.length ; c1 ++) res [c1] = Arrays.copyOf(x[c1], x[c1].length); return res; 211 } 212 private double [] copyOf (double [] x , int numValuesCopy , int finalSize) 213 { 214 double [] res = new double [finalSize]; for (int c1 = 0 ; c1 < numValuesCopy ; c1 ++) res [c1] = x [c1]; return res; 215 } 216}