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.DoubleFactory1D; 015import cern.colt.matrix.tdouble.DoubleMatrix1D; 016import com.net2plan.interfaces.networkDesign.IAlgorithm; 017import com.net2plan.interfaces.networkDesign.Link; 018import com.net2plan.interfaces.networkDesign.Net2PlanException; 019import com.net2plan.interfaces.networkDesign.NetPlan; 020import com.net2plan.libraries.IPUtils; 021import com.net2plan.utils.Constants.OrderingType; 022import com.net2plan.utils.Constants.RoutingType; 023import com.net2plan.utils.Constants.SearchType; 024import com.net2plan.utils.*; 025 026import java.io.File; 027import java.util.*; 028 029/** 030 * Searches for the OSPF link weights that minimize a measure of congestion, using an evolutionary algorithm (genetic algorithm) heuristic 031 * 032 * The time evolution of different metrics can be stored in output files, for later processing. 033 * As an example, see the <a href="../../../../../../graphGeneratorFiles/fig_sec12_9_ospfWeightEA.m">{@code fig_sec12_9_ospfWeightEA.m}</a> MATLAB file used for generating the graph/s of the case study in the 034 * <a href="http://eu.wiley.com/WileyCDA/WileyTitle/productCd-1119013356.html">book</a> using this algorithm. 035 * @net2plan.description 036 * @net2plan.keywords IP/OSPF, Flow assignment (FA), Evolutionary algorithm (EA) 037 * @net2plan.ocnbooksections Section 12.9 038 * @net2plan.inputParameters 039 * @author Pablo Pavon-Marino 040 */ 041public class Offline_fa_ospfWeightOptimization_EA implements IAlgorithm 042{ 043 private OSPFHeuristicUtils ospfEngine; 044 private NetPlan netPlan; 045 private List<DoubleMatrix1D> population; 046 private double[] costs; 047 048 private TimeTrace stat_objFunction; 049 private TimeTrace stat_avEntropy; 050 private int E; 051 052 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); 053 private InputParameter algorithm_randomSeed = new InputParameter ("algorithm_randomSeed", (long) 1 , "Seed of the random number generator"); 054 private InputParameter algorithm_outputFileNameRoot = new InputParameter ("algorithm_outputFileNameRoot", "ospfWeghtOptimization_ea" , "Root of the file name to be used in the output files. If blank, no output"); 055 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); 056 private InputParameter algorithm_maxExecutionTimeInSeconds = new InputParameter ("algorithm_maxExecutionTimeInSeconds", (double) 300 , "Algorithm maximum running time in seconds" , 0 , false , Double.MAX_VALUE , true); 057 private InputParameter ea_maxNumIterations = new InputParameter ("ea_maxNumIterations", (int) 100 , "Maximum number of iterations" , 1 , Integer.MAX_VALUE); 058 private InputParameter ea_populationSize = new InputParameter ("ea_populationSize", (int) 1000 , "Number of elements in the population" , 1 , Integer.MAX_VALUE); 059 private InputParameter ea_offSpringSize = new InputParameter ("ea_offSpringSize", (int) 200 , "Number of childs in the offspring every generation" , 1 , Integer.MAX_VALUE); 060 private InputParameter ea_fractionParentsChosenRandomly = new InputParameter ("ea_fractionParentsChosenRandomly", (double) 0.1 , "Fraction of the parents that are selected randomly for creating the offspring" , 0 , true , 1 , true); 061 062 @Override 063 public String executeAlgorithm(NetPlan netPlan, Map<String, String> algorithmParameters, Map<String, String> net2planParameters) 064 { 065 /* Initialize all InputParameter objects defined in this object (this uses Java reflection) */ 066 InputParameter.initializeAllInputParameterFieldsOfObject(this, algorithmParameters); 067 068 /* Basic checks */ 069 final int N = netPlan.getNumberOfNodes(); 070 this.E = netPlan.getNumberOfLinks(); 071 if (N == 0) throw new Net2PlanException("The input design must have nodes"); 072 netPlan.removeAllUnicastRoutingInformation(); 073 netPlan.setRoutingTypeAllDemands (RoutingType.HOP_BY_HOP_ROUTING); 074 075 /* Initialize some variables */ 076 if (ea_offSpringSize.getInt () > ea_populationSize.getInt () / 2.0) throw new Net2PlanException("The offspring size cannot exceed the population size divided by two"); 077 078 079 Random rng = new Random (algorithm_randomSeed.getLong()); 080 this.netPlan = netPlan; 081 this.E = netPlan.getNumberOfLinks(); 082 this.ospfEngine = new OSPFHeuristicUtils(netPlan, ospf_maxLinkWeight.getInt (), ospf_weightOfMaxUtilizationInObjectiveFunction.getDouble(), rng); 083 final long algorithmInitialtime = System.nanoTime(); 084 final long algorithmEndtime = algorithmInitialtime + (long) (algorithm_maxExecutionTimeInSeconds.getDouble() * 1E9); 085 086 this.stat_objFunction = new TimeTrace (); 087 this.stat_avEntropy = new TimeTrace(); 088 089 DoubleMatrix1D bestSol = null; 090 double bestObjFunction = Double.MAX_VALUE; 091 092 /* Generate the initial population */ 093 generateInitialSolutions(ea_populationSize.getInt ()); 094 095 /* Update the best solution found so far */ 096 final int bestSolutionId = DoubleUtils.maxIndexes(costs, SearchType.FIRST)[0]; 097 bestObjFunction = costs[bestSolutionId]; 098 bestSol = population.get(bestSolutionId).copy (); 099 System.out.println("Initial population. Best solution cost: " + bestObjFunction); 100 101 /* Evolve: one iteration per generation */ 102 int iterationCounter = 0; 103 while ((System.nanoTime() < algorithmEndtime) && (iterationCounter < ea_maxNumIterations.getInt ())) 104 { 105 LinkedList<Integer> parents = operator_parentSelection(ea_offSpringSize.getInt () , ea_fractionParentsChosenRandomly.getDouble () , rng); 106 List<DoubleMatrix1D> offspring = operator_crossover(parents , rng); 107 this.operator_mutate(offspring , rng , ospf_maxLinkWeight.getInt ()); 108 int indexBestSolution = this.operator_select(offspring); 109 if (costs[indexBestSolution] < bestObjFunction) 110 { 111 bestObjFunction = costs[indexBestSolution]; 112 bestSol = this.population.get(indexBestSolution).copy (); 113 } 114 115 //System.out.println("Iteration: " + iterationCounter + ". Best solution cost: " + bestObjFunction); 116 117 final double runningTimeSecs = (System.nanoTime() - algorithmInitialtime) * 1E-9; 118 stat_objFunction.add (runningTimeSecs , costs); 119 stat_avEntropy.add (runningTimeSecs , computeAverageEntropy ()); 120 iterationCounter++; 121 } 122 123 /* Save the best solution found into the netPlan object */ 124 IPUtils.setECMPForwardingRulesFromLinkWeights(netPlan, bestSol); 125 126 stat_objFunction.printToFile(new File (algorithm_outputFileNameRoot.getString () + "_cong.txt")); 127 stat_avEntropy.printToFile(new File (algorithm_outputFileNameRoot.getString () + "_entropy.txt")); 128 129 System.out.println("Ok! Best solution OF: " + bestObjFunction + ", number generations: " + iterationCounter); 130 return "Ok! Best solution OF: " + bestObjFunction + ", number generations: " + iterationCounter; 131 } 132 133 @Override 134 public String getDescription() 135 { 136 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 evolutionary algorithm (EA) or genetic algorithm heuristic approach."; 137 } 138 139 @Override 140 public List<Triple<String, String, String>> getParameters() 141 { 142 /* Returns the parameter information for all the InputParameter objects defined in this object (uses Java reflection) */ 143 return InputParameter.getInformationAllInputParameterFieldsOfObject(this); 144 } 145 146 private void generateInitialSolutions(int ea_populationSize) 147 { 148 population = new ArrayList<DoubleMatrix1D>(ea_populationSize); 149 costs = new double[ea_populationSize]; 150 151 for (int cont = 0; cont < ea_populationSize; cont++) 152 { 153 Pair<DoubleMatrix1D,Double> p = ospfEngine.getInitialSolution ("random"); 154 population.add(p.getFirst()); 155 costs[cont] = p.getSecond(); 156 } 157 } 158 159 private LinkedList<Integer> operator_parentSelection(int ea_offspringSize , double ea_fractionChosenRandomly , Random rng) 160 { 161 final int numParentsToChoose = 2 * ea_offspringSize; 162 final int numParentsChosenRandomly = (int) Math.floor(numParentsToChoose * ea_fractionChosenRandomly); 163 final int numParentsChosenFromCost = numParentsToChoose - numParentsChosenRandomly; 164 165 /* Initialize the list to be returned */ 166 LinkedList<Integer> chosenParents = new LinkedList<Integer>(); 167 168 int [] sortedPopulationIds = DoubleUtils.sortIndexes(costs, OrderingType.ASCENDING); 169 170 /* Choose the best solutions as parents: as many as numParentsChosenFromCost */ 171 for (int cont = 0; cont < numParentsChosenFromCost; cont++) 172 chosenParents.add(sortedPopulationIds [cont]); 173 174 /* The rest of the parents (numParentsChosenRandomly) are chosen randomly among all. Parents can be repeated */ 175 for (int cont = 0; cont < numParentsChosenRandomly; cont++) 176 chosenParents.add(rng.nextInt(this.population.size())); 177 178 return chosenParents; 179 } 180 181 private List<DoubleMatrix1D> operator_crossover(LinkedList<Integer> parents , Random rng) 182 { 183 /* The offspring to be returned */ 184 List<DoubleMatrix1D> offspring = new ArrayList<DoubleMatrix1D>(parents.size() / 2); 185 186 /* Shuffle randomly the parent selection. Then, couples are formed randomly */ 187 Collections.shuffle(parents); 188 189 /* Two consecutive parents are a couple */ 190 while (parents.size() >= 2) 191 { 192 final int firstParentId = parents.poll(); 193 final int secondParentId = parents.poll(); 194 195 final DoubleMatrix1D firstParent = population.get(firstParentId); 196 final DoubleMatrix1D secondParent = population.get(secondParentId); 197 198 /* The descendant chooses randomly for each link, the link weight from any of the two parents */ 199 final DoubleMatrix1D descendant = DoubleFactory1D.dense.make (E); 200 for (Link link : netPlan.getLinks ()) descendant.set(link.getIndex (), rng.nextBoolean() ? firstParent.get(link.getIndex ()) : secondParent.get(link.getIndex ())); 201 offspring.add(descendant); 202 } 203 204 return offspring; 205 } 206 207 private void operator_mutate(List<DoubleMatrix1D> offspring , Random rng , int maxLinkWeight) 208 { 209 for (DoubleMatrix1D solution : offspring) 210 { 211 final int e = rng.nextInt(netPlan.getNumberOfLinks()); 212 solution.set(e, 1.0 + rng.nextInt(maxLinkWeight)); 213 } 214 } 215 216 private int operator_select(List<DoubleMatrix1D> offspring) 217 { 218 /* Compute the costs of the offspring */ 219 final int ea_populationSize = costs.length; 220 double [] combinedCosts = Arrays.copyOf(costs, ea_populationSize + offspring.size()); 221 population.addAll(offspring); 222 int counter = ea_populationSize; 223 for (DoubleMatrix1D descendant : offspring) 224 combinedCosts[counter ++] = ospfEngine.computeObjectiveFunction(descendant).getFirst(); 225 226 int [] sortedPopulationIds = DoubleUtils.sortIndexes(combinedCosts, OrderingType.ASCENDING); 227 228 this.costs = new double [ea_populationSize]; 229 ArrayList<DoubleMatrix1D> newPopulation = new ArrayList<DoubleMatrix1D>(ea_populationSize); 230 for (int cont = 0 ; cont < ea_populationSize ; cont ++) 231 { 232 final int indexInCombinedOffspringAndPopulation = sortedPopulationIds[cont]; 233 costs [cont] = combinedCosts [indexInCombinedOffspringAndPopulation]; 234 newPopulation.add(population.get(indexInCombinedOffspringAndPopulation)); 235 } 236 this.population = newPopulation; 237 return 0; // return the best solution 238 } 239 240 241 private double computeAverageEntropy () 242 { 243 double freq_ew [][] = new double [E][ospf_maxLinkWeight.getInt ()]; // number of occurrencies 244 double sum_e [] = new double [E]; // sum of occurrencies per link 245 //private List<Map<Long, Double>> population; 246 for (DoubleMatrix1D sol : population) 247 { 248 for (Link e : netPlan.getLinks ()) 249 { 250 final int eIndex = e.getIndex (); 251 final int w = (int) sol.get(eIndex) - 1; 252 freq_ew [eIndex][w] ++; 253 sum_e [eIndex] ++; 254 } 255 } 256 final double convLogFactor = 1 / Math.log(2); 257 double avEntropy = 0; 258 for (int eIndex = 0 ; eIndex < E ; eIndex ++) 259 { 260 double entropy_e = 0; 261 for (int w = 0 ; w < ospf_maxLinkWeight.getInt () ; w ++) 262 { 263 if (sum_e [eIndex] > 0) { final double val = freq_ew [eIndex][w] / sum_e [eIndex]; entropy_e -= (val == 0)? 0 : val * Math.log(val) * convLogFactor; } 264 } 265 avEntropy += entropy_e; 266 } 267 return avEntropy / E; 268 } 269}