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}