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}