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}