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.DoubleMatrix1D; 017import com.net2plan.interfaces.networkDesign.IAlgorithm; 018import com.net2plan.interfaces.networkDesign.Link; 019import com.net2plan.interfaces.networkDesign.Net2PlanException; 020import com.net2plan.interfaces.networkDesign.NetPlan; 021import com.net2plan.libraries.IPUtils; 022import com.net2plan.utils.Constants.OrderingType; 023import com.net2plan.utils.Constants.RoutingType; 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 a tabu search 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_5_ospfWeightTabuSearch.m">{@code fig_sec12_5_ospfWeightTabuSearch.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), Tabu search (TS) 037 * @net2plan.ocnbooksections Section 12.5 038 * @net2plan.inputParameters 039 * @author Pablo Pavon-Marino 040 */ 041public class Offline_fa_ospfWeightOptimization_tabuSearch implements IAlgorithm 042{ 043 private int numIterations; 044 private int numIterationsNonImprovingBestSolutionSinceLastRandomization; 045 private TimeTrace stat_objFunction; 046 private OSPFHeuristicUtils ospfEngine; 047 private double [][] numberOccurrencies_ew; 048 049 private InputParameter ts_initializationType = new InputParameter ("ts_initializationType", "#select# random" , "The type of initialization of the OSPF link weights"); 050 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); 051 private InputParameter algorithm_randomSeed = new InputParameter ("algorithm_randomSeed", (long) 1 , "Seed of the random number generator"); 052 private InputParameter algorithm_outputFileNameRoot = new InputParameter ("algorithm_outputFileNameRoot", "ospfWeghtOptimization_tabu" , "Root of the file name to be used in the output files. If blank, no output"); 053 private InputParameter ts_differenceInWeightToBeNeighbors = new InputParameter ("ts_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"); 054 private InputParameter ts_tabuListSizeAsFractionOfLinks = new InputParameter ("ts_tabuListSizeAsFractionOfLinks", (double) 0.5 , "Size of the tabe list, given as a fraction of the total number of links" , 0 , true , 1 , true); 055 private InputParameter algorithm_maxExecutionTimeInSeconds = new InputParameter ("algorithm_maxExecutionTimeInSeconds", (double) 60 , "Algorithm maximum running time in seconds" , 0 , false , Double.MAX_VALUE , true); 056 private InputParameter ts_maxNumIterations = new InputParameter ("ts_maxNumIterations", (int) 50000 , "Maximum number of iterations" , 1 , Integer.MAX_VALUE); 057 private InputParameter ts_maxNumIterationsNonImprovingIncumbentSolution = new InputParameter ("ts_maxNumIterationsNonImprovingIncumbentSolution", (int) 15 , "Num iterations non improving the incumbent solution, to restart the search in a randomized solution" , 1 , Integer.MAX_VALUE); 058 private InputParameter ts_aspirationCriterion = new InputParameter ("ts_aspirationCriterion", true , "Apply aspiration criterion in tabu search"); 059 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); 060 061 @Override 062 public String executeAlgorithm(NetPlan netPlan, Map<String, String> algorithmParameters, Map<String, String> net2planParameters) 063 { 064 /* Initialize all InputParameter objects defined in this object (this uses Java reflection) */ 065 InputParameter.initializeAllInputParameterFieldsOfObject(this, algorithmParameters); 066 067 /* Basic checks */ 068 final int N = netPlan.getNumberOfNodes(); 069 final int E = netPlan.getNumberOfLinks(); 070 if (N == 0) throw new Net2PlanException("The input design must have nodes"); 071 netPlan.removeAllUnicastRoutingInformation(); 072 netPlan.setRoutingTypeAllDemands (RoutingType.HOP_BY_HOP_ROUTING); 073 074 /* Initialize some variables */ 075 final long algorithmInitialtime = System.nanoTime(); 076 final long algorithmEndtime = algorithmInitialtime + (long) (algorithm_maxExecutionTimeInSeconds.getDouble() * 1E9); 077 final int tabuListSize = (int) Math.ceil(E * ts_tabuListSizeAsFractionOfLinks.getDouble()); 078 if (tabuListSize >= E) throw new Net2PlanException ("The tabu list size is larger or equal than the number of links: all jumps would be tabu"); 079 080 Random rng = new Random (algorithm_randomSeed.getLong()); 081 this.ospfEngine = new OSPFHeuristicUtils(netPlan, ospf_maxLinkWeight.getInt (), ospf_weightOfMaxUtilizationInObjectiveFunction.getDouble(), rng); 082 this.numberOccurrencies_ew = new double [E][ospf_maxLinkWeight.getInt ()]; 083 084 /* Tabu list info. Because of aspiration criterion, a link can appear more than once in the tabu list */ 085 Map<Link,Integer> numEntriesTabuList_e = new HashMap<Link,Integer> (); for (Link e : netPlan.getLinks()) numEntriesTabuList_e.put(e, 0); 086 LinkedList<Link> tabuList_e = new LinkedList<Link> (); 087 088 Pair<DoubleMatrix1D,Double> p = ospfEngine.getInitialSolution (ts_initializationType.getString()); 089 DoubleMatrix1D currentSol = p.getFirst(); 090 double currentObjFunction = p.getSecond(); 091 092 DoubleMatrix1D bestSol = currentSol.copy (); 093 double bestObjFunction = currentObjFunction; 094 double bestObjFunctionSinceLastRandomization = currentObjFunction; 095 096 this.numIterations= 0; 097 this.numIterationsNonImprovingBestSolutionSinceLastRandomization = 0; 098 this.stat_objFunction = new TimeTrace (); 099 100 stat_objFunction.add(0.0, currentObjFunction); 101 102 /* update the long-term memory*/ 103 for (int eIndex = 0 ; eIndex < E ; eIndex ++) this.numberOccurrencies_ew [eIndex][(int) (currentSol.get(eIndex)) - 1] ++; 104 105 //System.out.println("Initial objFunction: " + bestObjFunction + ", tabu list tenure: " + tabuListSize); 106 107 while ((System.nanoTime() < algorithmEndtime) && (numIterations < ts_maxNumIterations.getInt ())) 108 { 109 this.numIterations ++; 110 Link bestNeighborLink = null; 111 int bestNeighborWeight = -1; 112 double bestNeighborObjFunction = Double.MAX_VALUE; 113 114 /* Neighbors differing in one link */ 115 for (Link e1 : netPlan.getLinks()) 116 { 117 final boolean isTabu = (numEntriesTabuList_e.get(e1) > 0); 118 if (!ts_aspirationCriterion.getBoolean() && isTabu) continue; 119 120 final int currentWeight1 = (int) currentSol.get(e1.getIndex ()); 121 for (int w1 = 1 ; w1 <= ospf_maxLinkWeight.getInt () ; w1 ++) 122 { 123 if (w1 == currentWeight1) continue; 124 if (Math.abs(w1 - currentWeight1) > ts_differenceInWeightToBeNeighbors.getInt ()) continue; 125 currentSol.set(e1.getIndex (), (double) w1); 126 final double neighborObjFunction = ospfEngine.computeObjectiveFunction(currentSol).getFirst(); 127 currentSol.set(e1.getIndex (), (double) currentWeight1); 128 129 /* Update best neighbor if (i) not tabu and improves best neighbor, (ii) tabu, but aspiration criterion is active and improves incumbent solution */ 130 if ( (!isTabu && (neighborObjFunction < bestNeighborObjFunction)) || (isTabu && ts_aspirationCriterion.getBoolean() && (neighborObjFunction < bestObjFunction)) ) 131 { 132 // if (isTabu) System.out.println ("Aspiration criterion applied"); 133 bestNeighborLink = e1; bestNeighborWeight = w1; bestNeighborObjFunction = neighborObjFunction; 134 } 135 } 136 } 137 138 if (bestNeighborLink == null) { throw new RuntimeException ("This should not happen: it means a too large tabu list"); } 139 140 /* Jump to the neighbor solution */ 141 currentSol.set(bestNeighborLink.getIndex (), (double) bestNeighborWeight); currentObjFunction = bestNeighborObjFunction; 142 143 /* update tabu list */ 144 if (tabuList_e.size() == tabuListSize) { Link eToRemove = tabuList_e.removeFirst(); numEntriesTabuList_e.put(eToRemove, numEntriesTabuList_e.get(eToRemove) - 1); if (numEntriesTabuList_e.get(eToRemove) < 0) throw new RuntimeException ("Bad"); } 145 tabuList_e.addLast(bestNeighborLink); numEntriesTabuList_e.put(bestNeighborLink , numEntriesTabuList_e.get(bestNeighborLink) + 1); 146 147 /* update the incumbent solution */ 148 if (currentObjFunction < bestObjFunctionSinceLastRandomization) 149 { 150 this.numIterationsNonImprovingBestSolutionSinceLastRandomization = 0; 151 bestObjFunctionSinceLastRandomization = currentObjFunction; 152 //System.out.println("Improving best objFunction since last randomization: " + currentObjFunction); 153 if (currentObjFunction < bestObjFunction) { bestObjFunction = currentObjFunction; bestSol = currentSol.copy (); /* System.out.println("Improving best objFunction: " + bestObjFunction); */ } 154 } 155 else 156 this.numIterationsNonImprovingBestSolutionSinceLastRandomization ++; 157 158 /* update the long-term memory*/ 159 for (int eIndex = 0 ; eIndex < E ; eIndex ++) this.numberOccurrencies_ew [eIndex][(int) (currentSol.get(eIndex)) - 1] ++; 160 161 /* Check if too many iterations without improving the incumbent solution */ 162 if (numIterationsNonImprovingBestSolutionSinceLastRandomization > ts_maxNumIterationsNonImprovingIncumbentSolution.getInt ()) 163 { 164 // System.out.println("Randomization!!"); 165 166 /* Initialize tabu list */ 167 tabuList_e.clear(); for (Link e : netPlan.getLinks()) numEntriesTabuList_e.put(e, 0); 168 169 /* Make a diversification jump: incumbent solution, then each link with prob 0.5 randomizes its weight */ 170 currentSol = bestSol.copy(); 171 for (Link e : netPlan.getLinks()) 172 { 173 if (rng.nextBoolean()) continue; 174 final int [] leastUtilizedWeightsMinus1 = DoubleUtils.sortIndexes(this.numberOccurrencies_ew [e.getIndex ()], OrderingType.ASCENDING); 175 final int randomlyChosenWeight = leastUtilizedWeightsMinus1 [rng.nextInt(ospf_maxLinkWeight.getInt ()/2)] + 1; 176 currentSol.set(e.getIndex (), (double) randomlyChosenWeight); 177 } 178 currentObjFunction = ospfEngine.computeObjectiveFunction(currentSol).getFirst(); 179 bestObjFunctionSinceLastRandomization = currentObjFunction; 180 this.numIterationsNonImprovingBestSolutionSinceLastRandomization = 0; 181 } 182 183 final double currentTimeInSecs = (System.nanoTime() - algorithmInitialtime) * 1e-9; 184 stat_objFunction.add(currentTimeInSecs , currentObjFunction); 185 } 186 187 IPUtils.setECMPForwardingRulesFromLinkWeights(netPlan, bestSol); 188 189 final String completeFileName = algorithm_outputFileNameRoot.getString () + "_0" + ((int) (10*ts_tabuListSizeAsFractionOfLinks.getDouble())) + ((ts_aspirationCriterion.getBoolean())? "_asp" : "_noasp"); 190 stat_objFunction.printToFile(new File (completeFileName + "_cong.txt")); 191 192 System.out.println("Ok! ObjFunction: " + bestObjFunction + ", num it: " + numIterations + ", Best solution: " + bestSol); 193 return "Ok! ObjFunction: " + bestObjFunction + ", num it: " + numIterations; 194 } 195 196 @Override 197 public String getDescription() 198 { 199 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 a tabu search heuristic approach."; 200 } 201 202 @Override 203 public List<Triple<String, String, String>> getParameters() 204 { 205 /* Returns the parameter information for all the InputParameter objects defined in this object (uses Java reflection) */ 206 return InputParameter.getInformationAllInputParameterFieldsOfObject(this); 207 } 208 209}