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.onlineSim;
012
013
014import cern.colt.matrix.tdouble.DoubleFactory1D;
015import cern.colt.matrix.tdouble.DoubleFactory2D;
016import cern.colt.matrix.tdouble.DoubleMatrix1D;
017import cern.colt.matrix.tdouble.DoubleMatrix2D;
018import com.net2plan.examples.ocnbook.offline.Offline_ca_wirelessPersistenceProbability;
019import com.net2plan.interfaces.networkDesign.Link;
020import com.net2plan.interfaces.networkDesign.Net2PlanException;
021import com.net2plan.interfaces.networkDesign.NetPlan;
022import com.net2plan.interfaces.networkDesign.Node;
023import com.net2plan.interfaces.simulation.IEventProcessor;
024import com.net2plan.interfaces.simulation.SimEvent;
025import com.net2plan.libraries.NetworkPerformanceMetrics;
026import com.net2plan.libraries.WirelessUtils;
027import com.net2plan.utils.GradientProjectionUtils;
028import com.net2plan.utils.InputParameter;
029import com.net2plan.utils.TimeTrace;
030import com.net2plan.utils.Triple;
031
032import java.io.File;
033import java.io.FileOutputStream;
034import java.io.PrintStream;
035import java.util.*;
036import java.util.Map.Entry;
037
038/** 
039 * This module implements a distributed primal-gradient based algorithm for adjusting the link persistence probabilities in a wireless network with a ALOHA-type random-access based MAC, to maximize the network utility enforcing a fair allocation of the resources.
040 *
041 * Ths event processor is adapted to permit observing the algorithm performances under user-defined conditions, 
042 * including asynchronous distributed executions, where signaling can be affected by losses and/or delays, and/or measurement errors. 
043 * The time evolution of different metrics can be stored in output files, for later processing. 
044 * As an example, see the <a href="../../../../../../graphGeneratorFiles/fig_sec9_5_persitenceProbabilityAdjustmentPrimal.m">{@code fig_sec9_5_persitenceProbabilityAdjustmentPrimal.m}</a> MATLAB file used for generating the graph/s of the case study in the 
045 * <a href="http://eu.wiley.com/WileyCDA/WileyTitle/productCd-1119013356.html">book</a> using this algorithm.
046 * 
047 * To simulate a network with this module, use the {@code Online_evGen_doNothing} generator.
048 * 
049 * @net2plan.keywords Random-access MAC, Wireless, Distributed algorithm, Primal gradient algorithm, Capacity assignment (CA)
050 * @net2plan.ocnbooksections Section 9.5
051 * @net2plan.inputParameters 
052 * @author Pablo Pavon-Marino
053 */
054@SuppressWarnings("unchecked")
055public class Online_evProc_persistenceProbAdjustmentPrimal extends IEventProcessor
056{
057        private static PrintStream getNulFile () { try { return new PrintStream (new FileOutputStream ("NUL") , false); } catch (Exception e) {e.printStackTrace(); throw new RuntimeException ("Not NUL file"); }   } 
058        private PrintStream log = getNulFile (); //System.err;//new PrintStream (new FileOutputStream ("NUL") , true);
059
060        private Random rng;
061        
062        private static final int SIGNALING_WAKEUPTOSENDMESSAGE = 300;
063        private static final int SIGNALING_RECEIVEDMESSAGE = 301;
064        private static final int UPDATE_WAKEUPTOUPDATE = 302;
065
066        private InputParameter signaling_isSynchronous = new InputParameter ("signaling_isSynchronous", false , "true if all the distributed agents involved wake up synchronously to send the signaling messages");
067        private InputParameter signaling_averageInterMessageTime = new InputParameter ("signaling_averageInterMessageTime", 1.0 , "Average time between two signaling messages sent by an agent" , 0 , false , Double.MAX_VALUE , true);
068        private InputParameter signaling_maxFluctuationInterMessageTime = new InputParameter ("signaling_maxFluctuationInterMessageTime", 0.5 , "Max fluctuation in time between two signaling messages sent by an agent" , 0 , true , Double.MAX_VALUE , true);
069        private InputParameter signaling_averageDelay = new InputParameter ("signaling_averageDelay", 0.0 , "Average time between signaling message transmission by an agent and its reception by other or others" , 0 , true , Double.MAX_VALUE , true);
070        private InputParameter signaling_maxFluctuationInDelay = new InputParameter ("signaling_maxFluctuationInDelay", 0.0 , "Max fluctuation in time in the signaling delay, in absolute time values. The signaling delays are sampled from a uniform distribution within the given interval" , 0 , true , Double.MAX_VALUE , true);
071        private InputParameter signaling_signalingLossProbability = new InputParameter ("signaling_signalingLossProbability", 0.05 , "Probability that a signaling message transmitted is lost (not received by other or others involved agents)" , 0 , true , Double.MAX_VALUE , true);
072        private InputParameter update_isSynchronous = new InputParameter ("update_isSynchronous", false , "true if all the distributed agents involved wake up synchronousely to update its state");
073        private InputParameter update_averageInterUpdateTime = new InputParameter ("update_averageInterUpdateTime", 1.0 , "Average time between two updates of an agent" , 0 , false , Double.MAX_VALUE , true);
074        private InputParameter update_maxFluctuationInterUpdateTime = new InputParameter ("update_maxFluctuationInterUpdateTime", 0.5 , "Max fluctuation in time in the update interval of an agent, in absolute time values. The update intervals are sampled from a uniform distribution within the given interval" , 0 , true , Double.MAX_VALUE , true);
075
076        private InputParameter gradient_maxGradientAbsoluteNoise = new InputParameter ("gradient_maxGradientAbsoluteNoise", 0.0 , "Max value of the added noise to the gradient coordinate in absolute values" , 0 , true , Double.MAX_VALUE , true);
077        private InputParameter gradient_gammaStep = new InputParameter ("gradient_gammaStep", 0.00001 , "Gamma step in the gradient algorithm" , 0 , false , Double.MAX_VALUE , true);
078        private InputParameter gradient_maxGradientCoordinateChange = new InputParameter ("gradient_maxGradientCoordinateChange", 0.1 , "Maximum change in an iteration of a gradient coordinate" , 0 , false , Double.MAX_VALUE , true);
079        private InputParameter simulation_randomSeed = new InputParameter ("simulation_randomSeed", (long) 1 , "Seed of the random number generator");
080        private InputParameter simulation_outFileNameRoot = new InputParameter ("simulation_outFileNameRoot", "persistenceProbAdjustmentPrimal" , "Root of the file name to be used in the output files. If blank, no output");
081        private InputParameter simulation_maxNumberOfUpdateIntervals = new InputParameter ("simulation_maxNumberOfUpdateIntervals", 100.0 , "Maximum number of update intervals in average per agent" , 0 , false , Double.MAX_VALUE , true);
082
083        private InputParameter control_maxUeRelativeNoise = new InputParameter ("control_maxUeRelativeNoise", 0 , "Maximum relative fluctuation in link capacity estimation, that reflects into gradient noise" , 0 , true , Double.MAX_VALUE , true);
084        private InputParameter control_simultaneousTxAndRxPossible = new InputParameter ("control_simultaneousTxAndRxPossible", false , "If false, a node cannot transmit and receive simultaneously. If true, it can. This affects the interference map calculation");
085        private InputParameter control_linkNominalCapacity = new InputParameter ("control_linkNominalCapacity", 1.0 , "Nominal rate of the links. If non positive, nominal rates are rates of the initial design");
086        private InputParameter control_minLinkPersistenceProb = new InputParameter ("control_minLinkPersistenceProb", 0.03 , "Minimum persistence probability of a link" , 0 , true , 1 , true);
087        private InputParameter control_maxNodePersistenceProb = new InputParameter ("control_maxNodePersistenceProb", 0.99 , "Maximum persistence probability of a node (summing the persistence probabilities of its outoingl links)" , 0 , true , 1 , true);
088        private InputParameter control_fairnessFactor = new InputParameter ("control_fairnessFactor", 2.0 , "Fairness factor in utility function of link capacities (>= 1 to be a convex program)" , 0 , true , Double.MAX_VALUE , true);
089
090        private NetPlan currentNetPlan , copyInitialNetPlan;
091        private int E , N;
092                
093        private DoubleMatrix1D control_linkNominalCapacities;
094        private DoubleMatrix1D control_p_e;
095        private DoubleMatrix1D control_q_n;
096        private DoubleMatrix2D control_mostUpdatedQn2ValueNodeKnowByNode1_n1n2; // each node is assigned a map with known p_n values of one hop neighbors and two hop neighbors
097        private DoubleMatrix2D control_mostUpdatedUeValueNodeKnowByNode_ne; // each node is assigned a map with known s_e values of one hop neighbors and two hop neighbors
098
099        private Map<Link,Set<Node>> control_nodesInterfTo_e;
100        private Map<Node,Set<Link>> control_linksInterfFrom_n;
101        private Map<Node,int []> control_outLinksIndexes;
102        
103        private TimeTrace stat_traceOf_p_e;
104        private TimeTrace stat_traceOf_u_e;
105        private TimeTrace stat_traceOf_objFunction;
106
107        
108        @Override
109        public String getDescription()
110        {
111                return "This module implements a distributed primal-gradient based algorithm for adjusting the link persistence probabilities in a wireless network with a ALOHA-type random-access based MAC, to maximize the network utility enforcing a fair allocation of the resources.";
112        }
113
114        @Override
115        public List<Triple<String, String, String>> getParameters()
116        {
117                /* Returns the parameter information for all the InputParameter objects defined in this object (uses Java reflection) */
118                return InputParameter.getInformationAllInputParameterFieldsOfObject(this);
119        }
120
121        @Override
122        public void initialize(NetPlan currentNetPlan, Map<String, String> algorithmParameters, Map<String, String> simulationParameters, Map<String, String> net2planParameters)
123        {
124                /* Initialize all InputParameter objects defined in this object (this uses Java reflection) */
125                InputParameter.initializeAllInputParameterFieldsOfObject(this, algorithmParameters);
126
127                this.currentNetPlan = currentNetPlan;
128                this.copyInitialNetPlan = currentNetPlan.copy ();
129                if (currentNetPlan.getNumberOfLayers() != 1) throw new Net2PlanException ("This algorithm works in single layer networks");
130                this.N = currentNetPlan.getNumberOfNodes ();
131                this.E = currentNetPlan.getNumberOfLinks ();
132
133                if (control_linkNominalCapacity.getDouble() > 0) currentNetPlan.setVectorLinkCapacity(DoubleFactory1D.dense.make (E , control_linkNominalCapacity.getDouble()));
134                control_linkNominalCapacities = currentNetPlan.getVectorLinkCapacity();
135                
136                this.rng = new Random (simulation_randomSeed.getLong());
137
138                /* Check all nodes have output links */
139                for (Node n : currentNetPlan.getNodes()) if (n.getOutgoingLinks().isEmpty()) throw new Net2PlanException ("All nodes should have output links");
140                for (Node n : currentNetPlan.getNodes()) if (control_minLinkPersistenceProb.getDouble() * n.getOutgoingLinks().size () > control_maxNodePersistenceProb.getDouble()) throw new Net2PlanException("Minimum persistence per link is too high or maximum persistence per node too small: the problem has no feasible solutions");
141
142                Triple<DoubleMatrix2D, Map<Link,Set<Node>> , Map<Node,Set<Link>>> interfMap = WirelessUtils.computeInterferenceMap(currentNetPlan.getNodes () , currentNetPlan.getLinks () , control_simultaneousTxAndRxPossible.getBoolean());
143                this.control_nodesInterfTo_e = interfMap.getSecond ();
144                this.control_linksInterfFrom_n = interfMap.getThird();
145                
146                this.control_outLinksIndexes = new HashMap<Node,int []> ();
147                for (Node n : currentNetPlan.getNodes ()) 
148                { 
149                        int [] indexes = new int [n.getOutgoingLinks().size ()]; 
150                        int counter = 0; for (Link e : n.getOutgoingLinks()) indexes [counter ++] = e.getIndex (); 
151                        control_outLinksIndexes.put (n , indexes);
152                }
153                
154                
155                /* Initialize the persistence probabilities: p_e to the minimum */
156                this.control_q_n = DoubleFactory1D.dense.make (N);
157                this.control_p_e = DoubleFactory1D.dense.make (E , control_minLinkPersistenceProb.getDouble());
158                for (Node n : currentNetPlan.getNodes ()) 
159                        this.control_q_n.set(n.getIndex () , control_minLinkPersistenceProb.getDouble() *  n.getOutgoingLinks().size ());
160                control_mostUpdatedUeValueNodeKnowByNode_ne = DoubleFactory2D.dense.make (N,E);
161                control_mostUpdatedQn2ValueNodeKnowByNode1_n1n2 = DoubleFactory2D.dense.make (N,N);
162
163                /* Initialize the most updated signaled values known of q_n: one and two hop neighbors */
164                for (Node n1 : currentNetPlan.getNodes())
165                { 
166                        for (Node nNeighborOneHop : n1.getOutNeighbors())
167                        {
168                                control_mostUpdatedQn2ValueNodeKnowByNode1_n1n2.set (n1.getIndex () , nNeighborOneHop.getIndex () , control_q_n.get (nNeighborOneHop.getIndex ()));
169                                for (Node nNeighborTwoHops : nNeighborOneHop.getOutNeighbors())
170                                        if (nNeighborTwoHops != n1) 
171                                                control_mostUpdatedQn2ValueNodeKnowByNode1_n1n2.set (n1.getIndex () , nNeighborTwoHops.getIndex () , control_q_n.get (nNeighborTwoHops.getIndex ()));
172                        }
173                }
174                
175                /* Set link capacities to its actual values */
176                for (Link e : this.currentNetPlan.getLinks ()) 
177                {
178                        /* compute link capacity */
179                        double u_e = this.control_linkNominalCapacities.get(e.getIndex ()) * this.control_p_e.get(e.getIndex ());
180                        for (Node n : this.control_nodesInterfTo_e.get(e))
181                                u_e *= Math.max(0 , 1 - this.control_q_n.get(n.getIndex ()));
182                        e.setAttribute("p_e" , "" + control_p_e.get(e.getIndex ())); 
183                        e.setCapacity(u_e); 
184                }               
185
186                /* Set the most updated values known in each node, from the true u_e values */
187                DoubleMatrix1D noisyUeEstimation_e = DoubleFactory1D.dense.make (E);
188                for (Link e : currentNetPlan.getLinks())
189                        noisyUeEstimation_e.set(e.getIndex (), e.getCapacity() * ( 1 + control_maxUeRelativeNoise.getDouble()*2*(rng.nextDouble()-0.5)));
190                for (Node n : currentNetPlan.getNodes())
191                { 
192                        for (Link e : this.control_linksInterfFrom_n.get(n)) control_mostUpdatedUeValueNodeKnowByNode_ne.set (n.getIndex () , e.getIndex () , noisyUeEstimation_e.get(e.getIndex ()));
193                        for (Link e : n.getOutgoingLinks()) control_mostUpdatedUeValueNodeKnowByNode_ne.set (n.getIndex () , e.getIndex () , noisyUeEstimation_e.get(e.getIndex ()));
194                        for (Link e : n.getIncomingLinks()) control_mostUpdatedUeValueNodeKnowByNode_ne.set (n.getIndex () , e.getIndex () , noisyUeEstimation_e.get(e.getIndex ()));
195                }
196                
197                /* Initially all nodes receive a "wake up to transmit" event, aligned at time zero or y asynchr => randomly chosen */
198                for (Node n : currentNetPlan.getNodes())
199                {
200                        final double signalingTime = (signaling_isSynchronous.getBoolean())? signaling_averageInterMessageTime.getDouble() : Math.max(0 , signaling_averageInterMessageTime.getDouble() + signaling_maxFluctuationInterMessageTime.getDouble() * (rng.nextDouble() - 0.5));
201                        this.scheduleEvent(new SimEvent (signalingTime , SimEvent.DestinationModule.EVENT_PROCESSOR , SIGNALING_WAKEUPTOSENDMESSAGE , n));
202                        final double updateTime = (update_isSynchronous.getBoolean())? update_averageInterUpdateTime.getDouble() : Math.max(0 , update_averageInterUpdateTime.getDouble() + update_maxFluctuationInterUpdateTime.getDouble() * (rng.nextDouble() - 0.5));
203                        this.scheduleEvent(new SimEvent (updateTime , SimEvent.DestinationModule.EVENT_PROCESSOR , UPDATE_WAKEUPTOUPDATE , n));
204                }
205
206                /* Intialize the traces */
207                this.stat_traceOf_p_e = new TimeTrace ();
208                this.stat_traceOf_u_e = new TimeTrace ();
209                this.stat_traceOf_objFunction = new TimeTrace ();
210                this.stat_traceOf_p_e.add(0.0, control_p_e.copy());
211                this.stat_traceOf_u_e.add(0.0, currentNetPlan.getVectorLinkCapacity());
212                this.stat_traceOf_objFunction.add(0.0 , NetworkPerformanceMetrics.alphaUtility(currentNetPlan.getVectorLinkCapacity() , control_fairnessFactor.getDouble()));
213        }
214
215        @Override
216        public void processEvent(NetPlan currentNetPlan, SimEvent event)
217        {
218                final double t = event.getEventTime();
219                switch (event.getEventType())
220                {
221                case SIGNALING_RECEIVEDMESSAGE: // A node broadcasts signaling info to its 1 hop neighbors
222                {
223                        final Triple<Node,Map<Node,Double>,Map<Link,Double>> signalInfo = (Triple<Node,Map<Node,Double>,Map<Link,Double>>) event.getEventObject();
224                        final Node nMe = signalInfo.getFirst();
225                        final Map<Node,Double> receivedInfo_qn = signalInfo.getSecond();
226                        final Map<Link,Double> receivedInfo_ue = signalInfo.getThird();
227                        /* Take the p_n values signaled except my own p_n (I prefer my own estimation of q_n, than something estimated by others) */
228                        for (Entry<Node,Double> nodeInfo : receivedInfo_qn.entrySet())
229                                if (nodeInfo.getKey() != nMe) control_mostUpdatedQn2ValueNodeKnowByNode1_n1n2.set (nMe.getIndex () , nodeInfo.getKey().getIndex () , nodeInfo.getValue());
230                        /* Take the u_e values signaled */
231                        for (Entry<Link,Double> linkInfo : receivedInfo_ue.entrySet())
232                                control_mostUpdatedUeValueNodeKnowByNode_ne.set (nMe.getIndex () , linkInfo.getKey().getIndex () , linkInfo.getValue());
233                        break;
234                }
235                
236                case SIGNALING_WAKEUPTOSENDMESSAGE: // A node broadcasts signaling info to its 1 hop neighbors
237                {
238                        final Node nMe = (Node) event.getEventObject();
239                        final DoubleMatrix1D infoIKnow_qn = control_mostUpdatedQn2ValueNodeKnowByNode1_n1n2.viewRow (nMe.getIndex ());
240                        infoIKnow_qn.set (nMe.getIndex () , control_q_n.get (nMe.getIndex ()));
241
242                        /* Create the info I will signal */
243                        Map<Node,Double> infoToSignal_qn = new HashMap<Node,Double> ();
244                        infoToSignal_qn.put(nMe, this.control_q_n.get (nMe.getIndex ()));
245                        for (Node inNeighbor : nMe.getInNeighbors()) 
246                                infoToSignal_qn.put(inNeighbor, infoIKnow_qn.get(inNeighbor.getIndex ()));
247                        Map<Link,Double> infoToSignal_ue = new HashMap<Link,Double> ();
248                        for (Link inLink : nMe.getIncomingLinks()) 
249                                infoToSignal_ue.put(inLink, inLink.getCapacity() * ( 1 + 2*this.control_maxUeRelativeNoise.getDouble()*(rng.nextDouble() - 0.5)  ) );
250
251                        /* Send the events of the signaling information messages to the neighbors */
252                        if (rng.nextDouble() >= this.signaling_signalingLossProbability.getDouble()) // the signaling may be lost => lost to all nodes
253                                for (Node outNeighbor : nMe.getOutNeighbors())
254                                {
255                                        final double signalingReceptionTime = t + Math.max(0 , signaling_averageDelay.getDouble() + signaling_maxFluctuationInDelay.getDouble() * (rng.nextDouble() - 0.5));
256                                        this.scheduleEvent(new SimEvent (signalingReceptionTime , SimEvent.DestinationModule.EVENT_PROCESSOR , SIGNALING_RECEIVEDMESSAGE , Triple.of(outNeighbor, infoToSignal_qn , infoToSignal_ue)));
257                                }
258                        
259                        /* Re-schedule when to wake up again */
260                        final double signalingTime = signaling_isSynchronous.getBoolean()? t + signaling_averageInterMessageTime.getDouble() : Math.max(t , t + signaling_averageInterMessageTime.getDouble() + signaling_maxFluctuationInterMessageTime.getDouble() * (rng.nextDouble() - 0.5));
261                        this.scheduleEvent(new SimEvent (signalingTime , SimEvent.DestinationModule.EVENT_PROCESSOR , SIGNALING_WAKEUPTOSENDMESSAGE , nMe));
262                        break;
263                }
264
265                case UPDATE_WAKEUPTOUPDATE: // a node updates its p_n, p_e values, using most updated info available
266                {
267                        final Node nMe = (Node) event.getEventObject(); 
268                        
269                        final DoubleMatrix1D infoIKnow_qn = control_mostUpdatedQn2ValueNodeKnowByNode1_n1n2.viewRow (nMe.getIndex ());
270                        infoIKnow_qn.set (nMe.getIndex () , control_q_n.get (nMe.getIndex ()));
271                        DoubleMatrix1D infoIKnow_ue = control_mostUpdatedUeValueNodeKnowByNode_ne.viewRow (nMe.getIndex ());
272                        DoubleMatrix1D newMacValues_e = DoubleFactory1D.dense.make (E);
273                        for (int eIndex : control_outLinksIndexes.get (nMe))
274                        {
275                                final Link e = currentNetPlan.getLink (eIndex);
276                                final double current_pe = this.control_p_e.get(eIndex);
277                                final double gradientThisLink = computeGradient (e , this.control_fairnessFactor.getDouble() , current_pe , infoIKnow_qn , infoIKnow_ue) + 2*gradient_maxGradientAbsoluteNoise.getDouble()*(rng.nextDouble()-0.5);
278                                newMacValues_e.set(eIndex, current_pe + gradient_gammaStep.getDouble() * gradientThisLink);
279                        }
280                        GradientProjectionUtils.euclideanProjection_sumInequality(newMacValues_e , control_outLinksIndexes.get (nMe) , control_minLinkPersistenceProb.getDouble() , control_maxNodePersistenceProb.getDouble());
281                        
282                        if (gradient_maxGradientCoordinateChange.getDouble() > 0)
283                                GradientProjectionUtils.scaleDown_maxAbsoluteCoordinateChange (control_p_e , newMacValues_e , control_outLinksIndexes.get (nMe) ,  gradient_maxGradientCoordinateChange.getDouble());
284                        for (int eIndex : control_outLinksIndexes.get (nMe)) this.control_p_e.set (eIndex , newMacValues_e.get (eIndex));
285                        
286                        /* update p_n value applied by me */
287                        double thisNode_pn = 0; for (Link e : nMe.getOutgoingLinks()) thisNode_pn += this.control_p_e.get(e.getIndex ());
288                        if ((thisNode_pn <= 0) || (thisNode_pn > 1 + 1E-3)) throw new RuntimeException ("Bad");
289                        this.control_q_n.set(nMe.getIndex (), thisNode_pn);
290                        
291                        /* Send event next recomputing time */
292                        final double updateTime = update_isSynchronous.getBoolean()? t + update_averageInterUpdateTime.getDouble() : Math.max(t , t + update_averageInterUpdateTime.getDouble() + update_maxFluctuationInterUpdateTime.getDouble() * (rng.nextDouble() - 0.5));
293                        this.scheduleEvent(new SimEvent (updateTime , SimEvent.DestinationModule.EVENT_PROCESSOR , UPDATE_WAKEUPTOUPDATE,  nMe));
294
295                        /* Set link capacities to its actual values, update n2p object */
296                        for (Link e : currentNetPlan.getLinks ())
297                        { 
298                                /* compute link capacity */
299                                double u_e = this.control_linkNominalCapacities.get(e.getIndex ()) * this.control_p_e.get(e.getIndex ());
300                                for (Node n : this.control_nodesInterfTo_e.get(e))
301                                        u_e *= Math.max(0 , 1 - this.control_q_n.get(n.getIndex ()));
302                                e.setAttribute("p_e" , "" + control_p_e.get(e.getIndex ())); 
303                                e.setCapacity(u_e); 
304                        }               
305                        for (Node n : currentNetPlan.getNodes ()) { n.setAttribute("q_n" , "" + control_q_n.get(n.getIndex ())); }              
306
307                        checkCapacitiesNetPlan ();
308                        
309                        this.stat_traceOf_p_e.add(t, control_p_e.copy ());
310                        this.stat_traceOf_u_e.add(t, this.currentNetPlan.getVectorLinkCapacity());
311                        this.stat_traceOf_objFunction.add(t , NetworkPerformanceMetrics.alphaUtility(currentNetPlan.getVectorLinkCapacity() , control_fairnessFactor.getDouble()));
312
313                        if (t > this.simulation_maxNumberOfUpdateIntervals.getDouble() * this.update_averageInterUpdateTime.getDouble()) { this.endSimulation (); }
314                        
315                        break;
316                }
317                        
318
319                default: throw new RuntimeException ("Unexpected received event");
320                }
321                
322                
323        }
324
325        public String finish (StringBuilder st , double simTime)
326        {
327                if (simulation_outFileNameRoot.getString().equals("")) return null;
328                stat_traceOf_u_e.printToFile(new File (simulation_outFileNameRoot.getString() + "_ue.txt"));
329                stat_traceOf_objFunction.printToFile(new File (simulation_outFileNameRoot.getString() + "_objFunc.txt"));
330                stat_traceOf_p_e.printToFile(new File (simulation_outFileNameRoot.getString() + "_pe.txt"));
331                /* compute optimum solution */
332                Map<String,String> param = new HashMap<String,String> ();
333                param.put("alphaFairnessFactor", "" + this.control_fairnessFactor.getDouble());
334                param.put("minLinkPersistenceProb", "" + this.control_minLinkPersistenceProb.getDouble());
335                param.put("maxNodePersistenceProb", "" + this.control_maxNodePersistenceProb.getDouble());
336                param.put("linkNominalCapacity", "" + this.control_linkNominalCapacity.getDouble());
337                param.put("simultaneousTxAndRxPossible", "" + this.control_simultaneousTxAndRxPossible.getBoolean());
338                param.put("solverName", "ipopt");
339                param.put("solverLibraryName", "");
340                param.put("maxSolverTimeInSeconds", "-1");
341//              private InputParameter solverName = new InputParameter ("solverName", "#select# ipopt", "The solver name to be used by JOM. ");
342//              private InputParameter solverLibraryName = new InputParameter ("solverLibraryName", Configuration.getOption("glpkSolverLibraryName") , "The solver library full or relative path, to be used by JOM. Leave blank to use JOM default.");
343//              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)");
344                new Offline_ca_wirelessPersistenceProbability ().executeAlgorithm(copyInitialNetPlan , param , null);
345                final double optimumNetUtilityJOM = NetworkPerformanceMetrics.alphaUtility(copyInitialNetPlan.getVectorLinkCapacity() , control_fairnessFactor.getDouble());
346                DoubleMatrix1D optimum_ue = copyInitialNetPlan.getVectorLinkCapacity();
347                DoubleMatrix1D optimum_pe = DoubleFactory1D.dense.make (E);
348                for (Link e : copyInitialNetPlan.getLinks ()) optimum_pe.set (e.getIndex () , Double.parseDouble (e.getAttribute("p_e")));
349                TimeTrace.printToFile(new File (simulation_outFileNameRoot.getString() + "_jom_ue.txt"), optimum_ue);
350                TimeTrace.printToFile(new File (simulation_outFileNameRoot.getString() + "_jom_pe.txt"), optimum_pe);
351                TimeTrace.printToFile(new File (simulation_outFileNameRoot.getString() + "_jom_objFunc.txt"), optimumNetUtilityJOM);
352                return null;
353        }
354        
355        private double computeGradient (Link thisLink , double fairnessFactor , double thisLink_pe , DoubleMatrix1D infoIKnow_p_n , DoubleMatrix1D infoIKnow_ue)
356        {
357                final Node a_e = thisLink.getOriginNode();
358                if (thisLink_pe == 0) throw new RuntimeException ("Link " + thisLink + " has zero p_e");
359                double gradient = Math.pow(infoIKnow_ue.get(thisLink.getIndex ()) , 1 - fairnessFactor) / thisLink_pe; 
360                for (Link e : this.control_linksInterfFrom_n.get(a_e))
361                {
362                        if (e.getOriginNode() == thisLink.getOriginNode()) throw new RuntimeException ("Bad");
363                        gradient -= Math.pow(infoIKnow_ue.get(e.getIndex ()),1-fairnessFactor) / (1 - infoIKnow_p_n.get(e.getOriginNode().getIndex ()));
364                }
365                if (Double.isNaN(gradient)) throw new RuntimeException ("Link " + thisLink + " gradient is Nan");
366                if (Double.isInfinite(gradient)) throw new RuntimeException ("Link " + thisLink + " gradient is inifinte");
367                return gradient;
368        }
369
370        private void checkCapacitiesNetPlan ()
371        {
372                double [] p_e = new double [E]; 
373                for (Link e : currentNetPlan.getLinks ()) { p_e [e.getIndex ()] = Double.parseDouble (e.getAttribute("p_e")); if ((p_e [e.getIndex ()] < control_minLinkPersistenceProb.getDouble() - 1E-3) ||(p_e [e.getIndex ()] > 1E-3 + control_maxNodePersistenceProb.getDouble())) throw new RuntimeException ("Bad"); }
374                double [] q_n = new double [N]; 
375                for (Node n : currentNetPlan.getNodes ()) 
376                {
377                        for (Link e : n.getOutgoingLinks()) q_n [n.getIndex ()] += p_e [e.getIndex ()];
378                        if (Math.abs (q_n [n.getIndex ()] - Double.parseDouble (n.getAttribute("q_n"))) > 1E-3) throw new RuntimeException ("Bad");
379                        if (q_n [n.getIndex ()] > control_maxNodePersistenceProb.getDouble() + 1E-3) throw new RuntimeException ("Bad");
380                }
381                for (Link e : currentNetPlan.getLinks ())
382                {
383                        final double u_e = e.getCapacity();
384                        double supposedCapacity = control_linkNominalCapacities.get(e.getIndex ()) * control_p_e.get (e.getIndex ());
385                        for (Node n : control_nodesInterfTo_e.get(e)) supposedCapacity *= 1 - q_n [n.getIndex ()];
386                        if (Math.abs (u_e - supposedCapacity) > 1e-3) throw new RuntimeException ("Bad");
387                }
388        }
389        
390}