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
014
015
016 
017
018
019
020
021package com.net2plan.examples.ocnbook.offline;
022
023import cern.colt.matrix.tdouble.DoubleFactory1D;
024import cern.colt.matrix.tdouble.DoubleMatrix2D;
025import com.jom.DoubleMatrixND;
026import com.jom.OptimizationProblem;
027import com.net2plan.interfaces.networkDesign.*;
028import com.net2plan.utils.InputParameter;
029import com.net2plan.utils.Triple;
030
031import java.util.HashSet;
032import java.util.List;
033import java.util.Map;
034import java.util.Set;
035
036/**
037 * Solves several variants of multicast routing problems, with flow-link formulations
038 * @net2plan.description
039 * @net2plan.keywords Multicast, JOM, Flow-link formulation, Flow assignment (FA)
040 * @net2plan.ocnbooksections Section 4.6.2
041 * @net2plan.inputParameters 
042 * @author Pablo Pavon-Marino
043 */
044public class Offline_fa_xdeFormulationsMulticast implements IAlgorithm
045{
046        private InputParameter linkCostType = new InputParameter ("linkCostType", "#select# hops km" , "Criteria to compute the multicast tree cost. Valid values: 'hops' (all links cost one) or 'km' (link cost is its length in km)");
047        private InputParameter optimizationTarget = new InputParameter ("optimizationTarget", "#select# min-consumed-bandwidth min-av-num-hops minimax-link-utilization maximin-link-idle-capacity" , "Type of optimization target. Choose among minimize the total traffic in the links, minimize the average number of hops from ingress to different egress nodes, minimize the highest link utilization, maximize the lowest link idle capacity");
048        private InputParameter maxCopyCapability = new InputParameter ("maxCopyCapability", (int) -1 , "Maximum number of copies of the traffic a node can make (this is the maximum number of output links in a node of the same multicast tree). A non-positive value means no limit");
049        private InputParameter maxE2ELengthInKm = new InputParameter ("maxE2ELengthInKm", (double) -1 , "The path from an origin to any destination in any multicast tree cannot be longer than this. A non-positive number means this limit does not exist");
050        private InputParameter maxE2ENumHops = new InputParameter ("maxE2ENumHops", (int) -1 , "The path from an origin to any destination in any multicast tree cannot have more than this number of hops. A non-positive number means this limit does not exist");
051        private InputParameter maxE2EPropDelayInMs = new InputParameter ("maxE2EPropDelayInMs", (double) -1 , "The path from an origin to any destination in any multicast tree cannot have more than this propagation delay in miliseconds. A non-positive number means this limit does not exist");
052        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");
053        private InputParameter solverLibraryName = new InputParameter ("solverLibraryName", "" , "The solver library full or relative path, to be used by JOM. Leave blank to use JOM default.");
054        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)");
055        
056        @Override
057        public String executeAlgorithm(NetPlan netPlan, Map<String, String> algorithmParameters, Map<String, String> net2planParameters)
058        {
059                /* Initialize all InputParameter objects defined in this object (this uses Java reflection) */
060                InputParameter.initializeAllInputParameterFieldsOfObject(this, algorithmParameters);
061                if (!linkCostType.getString().equalsIgnoreCase("km") && !linkCostType.getString().equalsIgnoreCase("hops"))
062                        throw new Net2PlanException("Wrong linkCostType parameter");
063                
064                /* Initialize variables */
065                final int E = netPlan.getNumberOfLinks();
066                final int N = netPlan.getNumberOfNodes();
067                final int MD = netPlan.getNumberOfMulticastDemands();
068                if (E == 0 || MD == 0) throw new Net2PlanException("This algorithm requires a topology with links and a multicast demand set");
069
070                /* Remove all multicast routed traffic. Any unicast routed traffic is kept */
071                netPlan.removeAllMulticastTrees();
072
073                /* Create the optimization problem object (JOM library) */
074                OptimizationProblem op = new OptimizationProblem();
075
076                /* Set some input parameters to the problem */
077                op.setInputParameter("u_e", netPlan.getVectorLinkSpareCapacity(), "row"); /* for each link, its unused capacity (the one not used by any mulitcast trees) */
078                op.setInputParameter("A_nd", netPlan.getMatrixNodeMulticastDemandIncidence()); /* 1 if node n is ingress in multicast demand d, -1 if n is egress */ 
079                final DoubleMatrix2D Aout_nd = netPlan.getMatrixNodeMulticastDemandOutgoingIncidence();
080                final DoubleMatrix2D Ain_nd = netPlan.getMatrixNodeMulticastDemandIncomingIncidence();
081                op.setInputParameter("Aout_nd", Aout_nd); /* 1 if node n is ingress in multicast demand d */ 
082                op.setInputParameter("Ain_nd", Ain_nd); /* -1 if node n is egress in multicast demand d */ 
083                op.setInputParameter("A_ne", netPlan.getMatrixNodeLinkIncidence()); /* 1 in position (n,e) if link e starts in n, -1 if it ends in n, 0 otherwise */
084                op.setInputParameter("Aout_ne", netPlan.getMatrixNodeLinkOutgoingIncidence()); /* 1 in position (n,e) if link e starts in n */
085                op.setInputParameter("Ain_ne", netPlan.getMatrixNodeLinkIncomingIncidence()); /* 1 in position (n,e) if link e ends in n */
086                op.setInputParameter("h_d", netPlan.getVectorMulticastDemandOfferedTraffic(), "row"); /* for each multicast demand, its offered traffic */
087                op.setInputParameter("lengthInKm_e", netPlan.getVectorLinkLengthInKm(), "row"); /* for each link, its length in km */
088                op.setInputParameter("propDelay_e", netPlan.getVectorLinkPropagationDelayInMiliseconds(), "row"); /* for each link, its length in km */
089                op.setInputParameter("onesE", DoubleFactory1D.dense.make (E,1.0) , "row"); /* for each link, a one */
090                op.setInputParameter("K", maxCopyCapability.getInt() <= 0? N : maxCopyCapability.getInt ()); /* the maximum number of output links a node can copy the input traffic of a multicast tree (<= 0 means no limitation) */
091                
092                /* Add decision variables for the multicast demands */
093                op.addDecisionVariable("xx_de", true , new int [] {MD , E} , 0 , 1); // 1 if link e is used my the multicast tree of this demand
094                op.addDecisionVariable("xx_det", true , new int [] {MD , E , N} , 0 , 1); // 1 if link e is used my the multicast tree of this demand
095
096                /* Write the problem objective function and constraints specific to this objective function */
097                if (optimizationTarget.getString ().equals ("min-consumed-bandwidth")) 
098                {
099                        op.setObjectiveFunction("minimize", "sum (h_d * xx_de)"); /* total traffic in the links */
100                        op.addConstraint("h_d * xx_de <= u_e"); /* the traffic in each link cannot exceed its capacity */
101                }
102                else if (optimizationTarget.getString ().equals ("min-av-num-hops")) 
103                {
104                        op.setInputParameter ("EPSILON" , getMinimumNonZeroTrafficOrCapacityValue (netPlan) / 1000);
105                        op.setObjectiveFunction("minimize", "sum(diag(h_d) * xx_det) + EPSILON * sum (h_d * xx_de)"); /* proportional to the number of hops each packet makes */
106                        op.addConstraint("h_d * xx_de <= u_e"); /* the traffic in each link cannot exceed its capacity */
107                }
108                else if (optimizationTarget.getString ().equals ("minimax-link-utilization")) 
109                {
110                        op.setInputParameter ("EPSILON" , getMinimumNonZeroTrafficOrCapacityValue (netPlan) / 1000);
111                        op.addDecisionVariable("rho", false, new int[] { 1, 1 }, 0, 1); /* worse case link utilization */
112                        op.setObjectiveFunction("minimize", "rho + EPSILON * sum (h_d * xx_de)"); // to avoid loops, we sum EPSILON by the traffic carried (EPSILON very small number)
113                        op.addConstraint("h_d * xx_de <= rho * u_e"); /* the traffic in each link cannot exceed its capacity. sets rho as the worse case utilization */
114                }
115                else if (optimizationTarget.getString ().equals ("maximin-link-idle-capacity"))
116                {
117                        op.setInputParameter ("EPSILON" , getMinimumNonZeroTrafficOrCapacityValue (netPlan) / 1000);
118                        op.addDecisionVariable("u", false, new int[] { 1, 1 }, 0, Double.MAX_VALUE); /* worse case link idle capacity */
119                        op.setObjectiveFunction("maximize", "u - EPSILON * sum (h_d * xx_de)"); // to avoid loops, we sum EPSILON by the traffic carried (EPSILON very small number)
120                        op.addConstraint("h_d * xx_de <= -u + u_e"); /* the traffic in each link cannot exceed its capacity. sets u as the worse case idle capacity */
121                }
122                else throw new Net2PlanException ("Unknown optimization target " + optimizationTarget.getString());
123
124//              DoubleMatrix3D I_eet = DoubleFactory3D.sparse.make (E,E,N); for (int t = 0; t < N ; t ++) I_eet.viewColumn (t).assign (DoubleFactory2D.sparse.identity(E));
125//              op.setInputParameter("I_eet", new DoubleMatrixND (I_eet));
126                DoubleMatrixND I_eet = new DoubleMatrixND (new int [] {E,E,N} , "sparse"); for (int t = 0 ; t < N ; t ++) for (int e = 0 ; e < E ; e ++) I_eet.set (new int [] { e,e,t} , 1.0);
127                op.setInputParameter("I_eet", I_eet);
128                
129                DoubleMatrixND A_ndt = new DoubleMatrixND (new int [] { N , MD , N }  , "sparse"); 
130                for (MulticastDemand d : netPlan.getMulticastDemands())
131                        for (Node t : d.getEgressNodes())
132                        {
133                                A_ndt.set (new int [] { d.getIngressNode().getIndex() , d.getIndex ()  , t.getIndex () } , 1.0);
134                                A_ndt.set (new int [] { t.getIndex () , d.getIndex () , t.getIndex () } , -1.0);
135                        }
136                op.setInputParameter("A_ndt", A_ndt);
137                op.setInputParameter("onesN" , DoubleFactory1D.dense.make (N,1.0)  , "row");
138                
139                /* Add constraints for the multicast demands */
140                op.addConstraint ("Ain_ne * xx_de' >= Ain_nd"); // a destination node receives at least one input link
141                op.addConstraint ("Ain_ne * xx_de' <= 1 - Aout_nd"); // source nodes receive 0 links, destination nodes at most one (then just one)
142                op.addConstraint ("Aout_ne * xx_de' <= K * (Aout_nd + (Ain_ne * xx_de'))"); // at most K out links from ingress node and from intermediate nodes if they have one input link
143                op.addConstraint ("xx_det  <= xx_de * I_eet"); // a link belongs to a path only if it is in the tree
144                op.addConstraint ("A_ne * permute(xx_det , [2 ; 1 ; 3]) == A_ndt"); // flow conservation constraint for each path in the tree
145
146                final boolean maxE2ELengthConstraintApplies = (maxE2ELengthInKm.getDouble() > 0) && (maxE2ELengthInKm.getDouble() < netPlan.getVectorLinkLengthInKm().zSum());
147                final boolean maxE2ENumHopsConstraintApplies = (maxE2ENumHops.getInt() > 0) && (maxE2ENumHops.getInt() < E);
148                final boolean maxE2EPropDelayConstraintApplies = (maxE2EPropDelayInMs.getDouble() > 0) && (maxE2EPropDelayInMs.getDouble() < netPlan.getVectorLinkPropagationDelayInMiliseconds().zSum());
149                if (maxE2ELengthConstraintApplies) op.addConstraint ("lengthInKm_e * permute(xx_det,[2;1;3]) <= " + maxE2ELengthInKm.getDouble()); // maximum length in km from ingress node to any demand egress node is limited
150                if (maxE2ENumHopsConstraintApplies) op.addConstraint ("sum(xx_det,2) <= " + maxE2ENumHops.getInt()); // maximum number of hops from ingress node to any demand egress node is limited
151                if (maxE2EPropDelayConstraintApplies) op.addConstraint ("propDelay_e * permute(xx_det,[2;1;3])  <= " + maxE2EPropDelayInMs.getDouble()); // maximum propagation delay in ms from ingress node to any demand egress node is limited
152
153                op.solve(solverName.getString (), "solverLibraryName", solverLibraryName.getString () , "maxSolverTimeInSeconds" , maxSolverTimeInSeconds.getDouble ());
154
155                /* If no solution is found, quit */
156                if (op.feasibleSolutionDoesNotExist()) throw new Net2PlanException("The problem has no feasible solution");
157                if (!op.solutionIsFeasible()) throw new Net2PlanException("A feasible solution was not found");
158                
159                /* Save the solution found in the netPlan object */
160                final DoubleMatrix2D xx_de = op.getPrimalSolution("xx_de").view2D();
161                /* Check */
162                final DoubleMatrixND xx_det = op.getPrimalSolution("xx_det");
163                for (MulticastDemand d : netPlan.getMulticastDemands())
164                        for (Link e : netPlan.getLinks())
165                                for (Node n : netPlan.getNodes())
166                                        if (xx_de.get(d.getIndex () , e.getIndex ()) == 0)
167                                                if (xx_det.get(new int [] {d.getIndex() , e.getIndex () , n.getIndex() } ) != 0) throw new RuntimeException ("Bad: x_de: " + (xx_de.get(d.getIndex () , e.getIndex ())) + ", xx_det: " + (xx_det.get(new int [] {d.getIndex() , e.getIndex () , n.getIndex() } )) );
168                
169                for (MulticastDemand d : netPlan.getMulticastDemands())
170                {
171                        Set<Link> linkSet = new HashSet<Link> (); for (int e = 0 ; e < E ; e ++) if (xx_de.get(d.getIndex() , e) != 0) { linkSet.add (netPlan.getLink (e)); }
172                        netPlan.addMulticastTree(d , d.getOfferedTraffic() , d.getOfferedTraffic() , linkSet , null);
173                }
174                
175
176                
177                
178                return "Ok!: The solution found is guaranteed to be optimal: " + op.solutionIsOptimal() + ", number of multicast trees created: " + netPlan.getNumberOfMulticastTrees();
179        }
180
181        @Override
182        public String getDescription()
183        {
184                return "Given a network topology, the capacities in the links, and a set multicast traffic demands, this algorithm permits computing the optimum multicast routing of the traffic (that is, the set ofm multicast trees carrying the traffic of the multicast demand) solving flow-link formulations. Through a set of input parameters, the user can choose among different optimization targets and constraints.";
185        }
186
187        
188        @Override
189        public List<Triple<String, String, String>> getParameters()
190        {
191                /* Returns the parameter information for all the InputParameter objects defined in this object (uses Java reflection) */
192                return InputParameter.getInformationAllInputParameterFieldsOfObject(this);
193        }
194        
195        private double getMinimumNonZeroTrafficOrCapacityValue (NetPlan netPlan)
196        {
197                double res = Double.MAX_VALUE;
198                for (Demand d : netPlan.getDemands ()) if (d.getOfferedTraffic() > 0) res = Math.min (res , d.getOfferedTraffic());
199                for (Link e : netPlan.getLinks ()) if (e.getCapacity() > 0) res = Math.min (res , e.getCapacity());
200                if (res == Double.MAX_VALUE) throw new Net2PlanException ("Too large offered traffics and link capacities");
201                return res;
202        }
203}