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.DoubleFactory1D;
017import cern.colt.matrix.tdouble.DoubleFactory2D;
018import cern.colt.matrix.tdouble.DoubleMatrix1D;
019import cern.colt.matrix.tdouble.DoubleMatrix2D;
020import cern.jet.math.tdouble.DoubleFunctions;
021import cern.jet.math.tdouble.DoublePlusMultSecond;
022import com.jom.OptimizationProblem;
023import com.net2plan.interfaces.networkDesign.*;
024import com.net2plan.libraries.GraphUtils;
025import com.net2plan.utils.Constants.RoutingType;
026import com.net2plan.utils.InputParameter;
027import com.net2plan.utils.Quintuple;
028import com.net2plan.utils.TimeTrace;
029import com.net2plan.utils.Triple;
030
031import java.io.File;
032import java.util.List;
033import java.util.Map;
034
035/**
036 * Finds the routing and mocular capacities for a network that minimize the cost, using a dual decomposition approach
037 * 
038 * The time evolution of different metrics can be stored in output files, for later processing. 
039 * As an example, see the <a href="../../../../../../graphGeneratorFiles/fig_sec11_7_modularCapacitiesAndRouting_dualDecomp.m">{@code fig_sec11_7_modularCapacitiesAndRouting_dualDecomp.m}</a> MATLAB file used for generating the graph/s of the case study in the 
040 * <a href="http://eu.wiley.com/WileyCDA/WileyTitle/productCd-1119013356.html">book</a> using this algorithm.
041 * @net2plan.description
042 * @net2plan.keywords Capacity assignment (CA), Flow assignment (FA), Modular capacities , Dual decomposition
043 * @net2plan.ocnbooksections Section 11.7
044 * @net2plan.inputParameters 
045 * @author Pablo Pavon-Marino
046 */
047public class Offline_cfa_modularCapacitiesAndRoutingDualDecomposition implements IAlgorithm
048{
049        private double PRECISIONFACTOR;
050        private double numModulesUpperBound;
051        
052        private int N , E;
053        
054        private InputParameter moduleCapacity = new InputParameter ("moduleCapacity", 1.0 , "Capacity of one module" , 0 , false , Double.MAX_VALUE , true);
055        private InputParameter solverName = new InputParameter ("solverName", "#select# glpk ipopt xpress cplex", "The solver name to be used by JOM. GLPK and IPOPT are free, XPRESS and CPLEX commercial. GLPK, XPRESS and CPLEX solve linear problems w/w.o integer contraints. IPOPT is can solve nonlinear problems (if convex, returns global optimum), but cannot handle integer constraints");
056        private InputParameter solverLibraryName = new InputParameter ("solverLibraryName", "" , "The solver library full or relative path, to be used by JOM. Leave blank to use JOM default.");
057        private InputParameter maxSolverTimeInSeconds = new InputParameter ("maxSolverTimeInSeconds", (double) -1 , "Maximum time granted to the solver to solve the problem. If this time expires, the solver returns the best solution found so far (if a feasible solution is found)");
058        private InputParameter subgrad_gammaStep = new InputParameter ("subgrad_gammaStep", (double) 0.05 , "Gamma step in the algorithm iteration" , 0 , false , Double.MAX_VALUE , true);
059        private InputParameter subgrad_type = new InputParameter ("subgrad_type", "#select# 1-over-t constant 1-over-square-root-t", "Type of gradient algorithm.  constant, 1-over-t, decreasing-square-t");   
060        private InputParameter simulation_outFileNameRoot = new InputParameter ("simulation_outFileNameRoot", "modularCapacitiesAndRoutingDualDecomp" , "Root of the file name to be used in the output files. If blank, no output");
061        private InputParameter simulation_numIterations = new InputParameter ("simulation_numIterations", (int) 500 , "Number o iterations of the algorithm" , 1 , Integer.MAX_VALUE);
062
063
064        private DoubleMatrix2D tmColumnSumZero;
065        private DoubleMatrix2D A_ne;
066
067        private TimeTrace stat_bestObjFuncFeasible;
068        private TimeTrace stat_objFuncFeasible;
069        private TimeTrace stat_lowerBound;
070        private TimeTrace stat_bestLowerBound;
071        private TimeTrace stat_pie;
072
073        @Override
074        public String executeAlgorithm(NetPlan netPlan, Map<String, String> algorithmParameters, Map<String, String> net2planParameters)
075        {
076                /* Initialize all InputParameter objects defined in this object (this uses Java reflection) */
077                InputParameter.initializeAllInputParameterFieldsOfObject(this, algorithmParameters);
078
079                if (netPlan.getNumberOfLayers() != 1) throw new Net2PlanException ("This algorithm works in single layer networks");
080
081                /* Initialize some variables */
082                this.N = netPlan.getNumberOfNodes();
083                this.E = netPlan.getNumberOfLinks();
084                this.PRECISIONFACTOR = Double.parseDouble(net2planParameters.get("precisionFactor"));
085                
086                this.stat_objFuncFeasible = new TimeTrace ();
087                this.stat_bestObjFuncFeasible = new TimeTrace ();
088                this.stat_lowerBound = new TimeTrace ();
089                this.stat_bestLowerBound = new TimeTrace ();
090                this.stat_pie = new TimeTrace ();
091
092                netPlan.removeAllUnicastRoutingInformation();
093                netPlan.setRoutingTypeAllDemands(RoutingType.HOP_BY_HOP_ROUTING);
094                
095            /* The diagonal in the traffic matrix contains minus the amount of traffic destined to that node */
096                double [][] trafficMatrix = netPlan.getMatrixNode2NodeOfferedTraffic().toArray();
097                for (int n1 = 0 ; n1 < N ; n1 ++) for (int n2 = 0 ; n2 < N ; n2 ++) if (n1 == n2) continue; else trafficMatrix [n2][n2] -= trafficMatrix [n1][n2];
098                this.tmColumnSumZero= DoubleFactory2D.dense.make(trafficMatrix);
099
100                System.out.println(netPlan.getMatrixNode2NodeOfferedTraffic());
101                
102                this.numModulesUpperBound = Math.ceil(netPlan.getVectorDemandOfferedTraffic().zSum() / moduleCapacity.getDouble());
103                this.A_ne = netPlan.getMatrixNodeLinkIncidence();
104                
105                DoubleMatrix1D multipliers_eIP = DoubleFactory1D.dense.make(E, 1.0);
106    double highestDualCost = -Double.MAX_VALUE;
107    double lowestPrimalCost = Double.MAX_VALUE;
108    DoubleMatrix1D bestFeasible_ne = null;
109    DoubleMatrix2D bestRelaxedAndFeasible_xte = null;
110                for (int it = 1 ; it <= simulation_numIterations.getInt() ; it ++)
111                {
112                        System.out.println("**** Iteration : " + it + ", mult: " + multipliers_eIP);
113
114                        Quintuple<DoubleMatrix2D,DoubleMatrix1D,DoubleMatrix1D,Double,DoubleMatrix1D> q = solveSubproblems (multipliers_eIP);
115                        final DoubleMatrix2D relaxedAndFeasible_xte = q.getFirst();
116                        final DoubleMatrix1D relaxed_ne = q.getSecond();
117                        final DoubleMatrix1D feasible_n_e = q.getThird();
118                        final double dualCost = q.getFourth();
119                        final DoubleMatrix1D gradient_e = q.getFifth ();
120                        
121                        final double objFunc = feasible_n_e.zSum();
122
123                        highestDualCost = Math.max(highestDualCost , dualCost);
124                        if (objFunc < lowestPrimalCost)
125                        {
126                                lowestPrimalCost = objFunc;
127                                bestFeasible_ne = feasible_n_e.copy();
128                                bestRelaxedAndFeasible_xte = relaxedAndFeasible_xte.copy();
129                        }
130
131                        if (highestDualCost > lowestPrimalCost + PRECISIONFACTOR) throw new RuntimeException ("Bad: highestDualCost: "+  highestDualCost + ", lowestPrimalCost: " + lowestPrimalCost); 
132                        
133                        System.out.println("* Feasible: Compute cost: " + objFunc);
134
135                        this.stat_objFuncFeasible.add (it , new double [] { objFunc } );
136                        this.stat_bestObjFuncFeasible.add(it ,  new double [] { lowestPrimalCost } );
137                        this.stat_lowerBound.add(it , new double [] { dualCost } );
138                        this.stat_bestLowerBound.add(it , new double [] { highestDualCost } );
139                        this.stat_pie.add(it, multipliers_eIP.toArray() );
140
141                        double gamma = 0; 
142                        if (this.subgrad_type.getString ().equalsIgnoreCase("constant"))
143                                gamma = this.subgrad_gammaStep.getDouble();
144                        else if (this.subgrad_type.getString ().equalsIgnoreCase("1-over-t"))
145                                gamma = this.subgrad_gammaStep.getDouble() / it;
146                        else if (this.subgrad_type.getString ().equalsIgnoreCase("1-over-square-root-t"))
147                                gamma = this.subgrad_gammaStep.getDouble() / Math.sqrt(it);
148                        else throw new Net2PlanException ("Unknown subgradient algorithm type");
149                        for (int e = 0 ; e < E ; e++)
150                                multipliers_eIP.set(e , Math.max(0 ,  multipliers_eIP.get(e) + gamma * gradient_e.get(e) ));
151                }
152                
153                saveNetPlan (netPlan , bestRelaxedAndFeasible_xte , bestFeasible_ne);           
154                
155                finish (netPlan);
156
157                return "Ok! Best solution. Cost: " + bestFeasible_ne.zSum();
158        }
159
160        @Override
161        public String getDescription()
162        {
163                return "Given a network with a set of given nodes, and links, and a given known unicast offered traffic. This algorithm jointly computes (i) the routing of the traffic, and the capacity to assign to the links. The link capacities are constrained to be modular: an integer multiple of a link capacity quantum. Optimization target is minimizing the network cost, given by the number of capacity modules to install in the network. The problem in NP-hard. The algorithm implements a dual decomposition iterative, using a subgradient algorithm.";
164        }
165
166        @Override
167        public List<Triple<String, String, String>> getParameters()
168        {
169                /* Returns the parameter information for all the InputParameter objects defined in this object (uses Java reflection) */
170                return InputParameter.getInformationAllInputParameterFieldsOfObject(this);
171        }
172        
173        private Quintuple<DoubleMatrix2D,DoubleMatrix1D,DoubleMatrix1D,Double,DoubleMatrix1D> solveSubproblems (DoubleMatrix1D pi_e)
174        {
175                /* Upper layer */
176                DoubleMatrix2D x_te = DoubleFactory2D.dense.make(N,E);
177                {
178                        OptimizationProblem op = new OptimizationProblem();
179                        op.setInputParameter("pi_e", pi_e.toArray() , "column");
180            op.setInputParameter("A_ne", A_ne.toArray());
181                        op.setInputParameter("TM", this.tmColumnSumZero.toArray());
182      op.addDecisionVariable("x_te", false , new int[] { N , E }, 0, Double.MAX_VALUE); // dest-link formulation at IP layer
183                        op.setObjectiveFunction("minimize", "sum (x_te * pi_e)");
184                        op.addConstraint("A_ne * (x_te') == TM"); // the flow-conservation constraints (NxN constraints)
185                        /* Call the solver to solve the problem */
186                        op.solve(solverName.getString (), "solverLibraryName", solverLibraryName.getString () ,  "maxSolverTimeInSeconds" , maxSolverTimeInSeconds.getDouble ());
187
188                        /* If no solution is found, quit */
189                        if (op.feasibleSolutionDoesNotExist()) throw new Net2PlanException("The problem has no feasible solution");
190                        if (op.foundUnboundedSolution()) throw new Net2PlanException ("Found an unbounded solution");
191                        if (!op.solutionIsFeasible()) throw new Net2PlanException("A feasible solution was not found");
192                        x_te = op.getPrimalSolution("x_te").view2D();
193                }
194                
195                DoubleMatrix1D n_e = DoubleFactory1D.dense.make(E);
196                for (int e = 0 ; e < E ; e ++)
197                        n_e.set(e , (1 - (moduleCapacity.getDouble() * pi_e.get(e)) >= 0)? 0 : numModulesUpperBound);
198                
199                DoubleMatrix1D y_e = x_te.copy().zMult(DoubleFactory1D.dense.make(N, 1.0) , null , 1.0 , 0.0 , true);
200                DoubleMatrix1D gradient_e = y_e.copy().assign(n_e , DoublePlusMultSecond.plusDiv(-moduleCapacity.getDouble()));
201
202                /* Compute the dual cost */
203                final double dualCost = x_te.copy().zMult(pi_e, null).zSum() - n_e.zDotProduct(pi_e) * moduleCapacity.getDouble();
204                
205                /* Compute a feasible solution from the relaxed x_te */
206                DoubleMatrix1D feasible_n_e = y_e.copy().assign(DoubleFunctions.div(moduleCapacity.getDouble())).assign(DoubleFunctions.ceil);
207
208//              System.out.println("carriedTraffic_e: " + carriedTraffic_e);
209//              System.out.println("capacityLink_e: " + capacityLink_e);
210//              System.out.println("barU_e: " + barU_e);
211                
212                return Quintuple.of(x_te.copy () ,  n_e.copy() ,  feasible_n_e.copy() ,  dualCost ,  gradient_e);
213        }
214        
215        private void finish (NetPlan np)
216        {
217                /* If no output file, return */
218                if (simulation_outFileNameRoot.getString ().equals("")) return;
219                stat_objFuncFeasible.printToFile(new File (simulation_outFileNameRoot.getString () + "_objFunc.txt"));
220                stat_bestObjFuncFeasible.printToFile(new File (simulation_outFileNameRoot.getString () + "_bestObjFunc.txt"));
221                stat_lowerBound.printToFile(new File (simulation_outFileNameRoot.getString () + "_lowerBound.txt"));
222                stat_bestLowerBound.printToFile(new File (simulation_outFileNameRoot.getString () + "_bestLowerBound.txt"));
223                stat_pie.printToFile(new File (simulation_outFileNameRoot.getString () + "_pie.txt"));
224                
225                
226                double lowerBoundNumLps_1 = 0;
227                for (Node s : np.getNodes())
228                {
229                        double outTraffic = 0; for (Demand d : s.getOutgoingDemands()) outTraffic += d.getOfferedTraffic();
230                        lowerBoundNumLps_1 += Math.ceil(outTraffic / moduleCapacity.getDouble());
231                }
232                double lowerBoundNumLps_2 = 0;
233                for (Node t : np.getNodes())
234                {
235                        double inTraffic = 0; for (Demand d : t.getIncomingDemands()) inTraffic += d.getOfferedTraffic();
236                        lowerBoundNumLps_2 += Math.ceil(inTraffic / moduleCapacity.getDouble());
237                }
238                double lowerBoundNumLps_3 = 0;
239                for (Node t : np.getNodes())
240                        for (Node s : np.getNodes())
241                        {
242                                if (s == t) continue;
243                                final int numHopsSP = GraphUtils.getShortestPath(np.getNodes () , np.getLinks(), s, t, null).size();
244                                for (Demand d : np.getNodePairDemands(s,t,false)) lowerBoundNumLps_3 += numHopsSP * d.getOfferedTraffic();
245                        }
246                lowerBoundNumLps_3 = Math.ceil(lowerBoundNumLps_3/moduleCapacity.getDouble());
247
248                TimeTrace.printToFile(new File (simulation_outFileNameRoot.getString () + "_heuristicLBCost.txt") , new double [] { lowerBoundNumLps_1 , lowerBoundNumLps_2 , lowerBoundNumLps_3  }   );
249                
250        }
251        
252        void saveNetPlan (NetPlan netPlan , DoubleMatrix2D x_te , DoubleMatrix1D n_e)
253        {
254                /* Set the routing at the IP layer */
255                netPlan.setRoutingTypeAllDemands(RoutingType.HOP_BY_HOP_ROUTING);
256                netPlan.removeAllForwardingRules();
257                netPlan.setRoutingFromDestinationLinkCarriedTraffic(x_te , true);
258                for (Link e : netPlan.getLinks())
259                        e.setCapacity(moduleCapacity.getDouble() * n_e.get(e.getIndex ()));
260                for (Demand d : netPlan.getDemandsBlocked())
261                        if (d.getBlockedTraffic() > PRECISIONFACTOR) throw new RuntimeException ("Bad");
262                for (Link e : netPlan.getLinksOversubscribed())
263                        if (e.getOccupiedCapacity() - e.getCapacity() > PRECISIONFACTOR) throw new RuntimeException ("Bad");
264        }
265        
266}