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.jom.OptimizationProblem; 019import com.net2plan.interfaces.networkDesign.*; 020import com.net2plan.interfaces.simulation.IEventProcessor; 021import com.net2plan.interfaces.simulation.SimEvent; 022import com.net2plan.utils.Constants.RoutingType; 023import com.net2plan.utils.*; 024 025import java.io.File; 026import java.io.FileOutputStream; 027import java.io.PrintStream; 028import java.util.List; 029import java.util.Map; 030import java.util.Random; 031 032/** 033 * This module implements a distributed dual-gradient based algorithm, for iteratively adapting the network routing. 034 * 035 * Ths event processor is adapted to permit observing the algorithm performances under user-defined conditions, 036 * including asynchronous distributed executions, where signaling can be affected by losses and/or delays, and/or measurement errors. 037 * The time evolution of different metrics can be stored in output files, for later processing. 038 * As an example, see the <a href="../../../../../../graphGeneratorFiles/fig_sec10_2_adaptiveRoutingDual.m">{@code fig_sec10_2_adaptiveRoutingDual.m}</a> MATLAB file used for generating the graph/s of the case study in the 039 * <a href="http://eu.wiley.com/WileyCDA/WileyTitle/productCd-1119013356.html">book</a> using this algorithm. 040 * 041 * To simulate a network with this module, use the {@code Online_evGen_doNothing} generator. 042 * 043 * @net2plan.keywords Flow assignment (FA), Distributed algorithm, Dual gradient algorithm 044 * @net2plan.ocnbooksections Section 10.2 045 * @net2plan.inputParameters 046 * @author Pablo Pavon-Marino 047 */ 048@SuppressWarnings("unchecked") 049public class Online_evProc_adaptiveRoutingDual extends IEventProcessor 050{ 051 private static PrintStream getNulFile () { try { return new PrintStream (new FileOutputStream ("NUL") , false); } catch (Exception e) {e.printStackTrace(); throw new RuntimeException ("Not NUL file"); } } 052 private PrintStream log = getNulFile (); //System.err;//new PrintStream (new FileOutputStream ("NUL") , true); 053 054 private InputParameter signaling_isSynchronous = new InputParameter ("signaling_isSynchronous", false , "true if all the distributed agents involved wake up synchronously to send the signaling messages"); 055 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); 056 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); 057 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); 058 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); 059 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); 060 private InputParameter update_isSynchronous = new InputParameter ("update_isSynchronous", false , "true if all the distributed agents involved wake up synchronousely to update its state"); 061 private InputParameter update_averageInterUpdateTime = new InputParameter ("update_averageInterUpdateTime", 1.0 , "Average time between two updates of an agent" , 0 , false , Double.MAX_VALUE , true); 062 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); 063 private InputParameter simulation_maxNumberOfUpdateIntervals = new InputParameter ("simulation_maxNumberOfUpdateIntervals", 500.0 , "Maximum number of update intervals in average per agent" , 0 , false , Double.MAX_VALUE , true); 064 private InputParameter simulation_randomSeed = new InputParameter ("simulation_randomSeed", (long) 1 , "Seed of the random number generator"); 065 private InputParameter simulation_outFileNameRoot = new InputParameter ("simulation_outFileNameRoot", "adaptiveRoutingDual" , "Root of the file name to be used in the output files. If blank, no output"); 066 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); 067 private InputParameter gradient_gammaStep = new InputParameter ("gradient_gammaStep", 0.001 , "Gamma step in the gradient algorithm" , 0 , false , Double.MAX_VALUE , true); 068 private InputParameter gradient_heavyBallBetaParameter = new InputParameter ("gradient_heavyBallBetaParameter", 0.0 , "Beta parameter of heavy ball, between 0 and 1. Value 0 means no heavy ball" , 0 , true , 1.0 , true); 069 private InputParameter gradient_maxGradientCoordinateChange = new InputParameter ("gradient_maxGradientCoordinateChange", 1.0e10 , "Maximum change in an iteration of a gradient coordinate" , 0 , false , Double.MAX_VALUE , true); 070 private InputParameter gradient_regularizationEpsilon = new InputParameter ("gradient_regularizationEpsilon", 1.0e-3 , "Regularization factor in the objective function term to make it strictly convex" , 0 , true , Double.MAX_VALUE , true); 071 072 private InputParameter control_maxNumberOfPathsPerDemand = new InputParameter ("control_maxNumberOfPathsPerDemand", (int) 5 , "Number of acceptable paths per demand. Take the k-shortest paths in number of hops" , 1 , Integer.MAX_VALUE); 073 private InputParameter control_initialLinkWeights = new InputParameter ("control_initialLinkWeights", 0.5 , "Initial value of the link weights" , 0 , true , Double.MAX_VALUE , true); 074 075 private Random rng; 076 private int E , R , N , D; 077 078 private static final int SIGNALING_WAKEUPTOSENDMESSAGE = 300; 079 private static final int SIGNALING_RECEIVEDMESSAGE = 301; 080 private static final int UPDATE_WAKEUPTOUPDATE = 302; 081 082 private NetPlan currentNetPlan; 083 private DoubleMatrix1D routing_price_e; 084 085 private DoubleMatrix2D routing_mostUpdatedLinkPriceKnownByNode_ne; 086 private DoubleMatrix1D routing_previousLinkWeight_e; 087 private int [][] control_routeIndexes_d; 088 089 private TimeTrace stat_traceOf_xp; 090 private TimeTrace stat_traceOf_pie; 091 private TimeTrace stat_traceOf_ye; 092 private TimeTrace stat_traceOf_objFunction; 093 094 @Override 095 public String getDescription() 096 { 097 return "This module implements a distributed dual-gradient based algorithm, for iteratively adapting the network routing."; 098 } 099 100 @Override 101 public List<Triple<String, String, String>> getParameters() 102 { 103 /* Returns the parameter information for all the InputParameter objects defined in this object (uses Java reflection) */ 104 return InputParameter.getInformationAllInputParameterFieldsOfObject(this); 105 } 106 107 @Override 108 public void initialize(NetPlan currentNetPlan, Map<String, String> algorithmParameters, Map<String, String> simulationParameters, Map<String, String> net2planParameters) 109 { 110 /* Initialize all InputParameter objects defined in this object (this uses Java reflection) */ 111 InputParameter.initializeAllInputParameterFieldsOfObject(this, algorithmParameters); 112 113 if (currentNetPlan.getNumberOfLayers() != 1) throw new Net2PlanException ("This algorithm works in single layer networks"); 114 115 this.rng = new Random (simulation_randomSeed.getLong()); 116 this.currentNetPlan = currentNetPlan; 117 this.E = currentNetPlan.getNumberOfLinks(); 118 this.N = currentNetPlan.getNumberOfNodes(); 119 this.D = currentNetPlan.getNumberOfDemands(); 120 121 /* Check all nodes have output links */ 122 for (Node n : currentNetPlan.getNodes()) if (n.getOutgoingLinks().isEmpty()) throw new Net2PlanException ("All nodes should have output links"); 123 124 /* Set the initial prices in the links to all zeros */ 125 this.routing_price_e = DoubleFactory1D.dense.make (E , control_initialLinkWeights.getDouble()); 126 this.routing_previousLinkWeight_e = routing_price_e.copy (); 127 for (Link e : currentNetPlan.getLinks ()) e.setAttribute("pi_e" , "" + routing_price_e.get (e.getIndex ())); 128 129 /* Initialize the information each demand knows of the prices of all the links */ 130 this.routing_mostUpdatedLinkPriceKnownByNode_ne = DoubleFactory2D.dense.make (N,E,control_initialLinkWeights.getDouble()); 131 132 /* Sets the initial routing, according to the prices, with all the traffic balanced equally in all the paths of the demand */ 133 currentNetPlan.removeAllUnicastRoutingInformation(); 134 currentNetPlan.setRoutingTypeAllDemands(RoutingType.SOURCE_ROUTING); 135 this.currentNetPlan.addRoutesFromCandidatePathList(currentNetPlan.computeUnicastCandidatePathList(null , control_maxNumberOfPathsPerDemand.getInt(), -1, -1, -1, -1, -1, -1 , null)); 136 137 this.R = currentNetPlan.getNumberOfRoutes (); 138 this.control_routeIndexes_d = new int [D][]; 139 for (Demand d : this.currentNetPlan.getDemands()) 140 { 141 control_routeIndexes_d [d.getIndex ()] = new int [d.getRoutes ().size ()]; 142 int counter_r = 0; for (Route r : d.getRoutes ()) control_routeIndexes_d [d.getIndex ()][counter_r ++] = r.getIndex (); 143 } 144 145 146 for (Demand d : this.currentNetPlan.getDemands()) 147 updateDemandRoutingFromWeightsKnown (d); 148 149 /* Initially all nodes receive a "wake up to transmit" event, aligned at time zero or y asynchr => randomly chosen */ 150 for (Link e : currentNetPlan.getLinks()) 151 { 152 final double signalingTime = (signaling_isSynchronous.getBoolean())? signaling_averageInterMessageTime.getDouble() : Math.max(0 , signaling_averageInterMessageTime.getDouble() + signaling_maxFluctuationInterMessageTime.getDouble() * (rng.nextDouble() - 0.5)); 153 this.scheduleEvent(new SimEvent (signalingTime , SimEvent.DestinationModule.EVENT_PROCESSOR , SIGNALING_WAKEUPTOSENDMESSAGE , e)); 154 } 155 for (Node n : currentNetPlan.getNodes()) 156 { 157 final double updateTime = (update_isSynchronous.getBoolean())? update_averageInterUpdateTime.getDouble() : Math.max(0 , update_averageInterUpdateTime.getDouble() + update_maxFluctuationInterUpdateTime.getDouble() * (rng.nextDouble() - 0.5)); 158 this.scheduleEvent(new SimEvent (updateTime , SimEvent.DestinationModule.EVENT_PROCESSOR , UPDATE_WAKEUPTOUPDATE , n)); 159 } 160 161 /* Intialize the traces */ 162 this.stat_traceOf_xp = new TimeTrace (); 163 this.stat_traceOf_pie = new TimeTrace (); 164 this.stat_traceOf_ye = new TimeTrace (); 165 this.stat_traceOf_objFunction = new TimeTrace (); 166 this.stat_traceOf_xp.add(0.0, this.currentNetPlan.getVectorRouteCarriedTraffic()); 167 this.stat_traceOf_pie.add(0.0, this.routing_price_e.copy ()); 168 this.stat_traceOf_ye.add(0.0, this.currentNetPlan.getVectorLinkCarriedTraffic()); 169 this.stat_traceOf_objFunction.add(0.0, computeObjectiveFucntionFromNetPlan()); 170 171 } 172 173 @Override 174 public void processEvent(NetPlan currentNetPlan, SimEvent event) 175 { 176 final double t = event.getEventTime(); 177 switch (event.getEventType()) 178 { 179 case SIGNALING_RECEIVEDMESSAGE: // A node receives a price information from a link 180 { 181 final Triple<Node,Link,Double> signalInfo = (Triple<Node,Link,Double>) event.getEventObject(); 182 final Node nMe = signalInfo.getFirst(); 183 final Link e = signalInfo.getSecond(); 184 final double link_price_e = signalInfo.getThird(); 185 this.routing_mostUpdatedLinkPriceKnownByNode_ne.set(nMe.getIndex () , e.getIndex () , link_price_e); 186 break; 187 } 188 189 case SIGNALING_WAKEUPTOSENDMESSAGE: // A link updates its price (gradient), and signals it to all the nodes 190 { 191 final Link eMe = (Link) event.getEventObject(); 192 193 /* Update the gradient iteration */ 194 final double previous_pi_e = this.routing_previousLinkWeight_e.get(eMe.getIndex ()); 195 final double old_pi_e = this.routing_price_e.get(eMe.getIndex ()); 196 final double gradient_e = eMe.getCarriedTraffic() - eMe.getCapacity() + 2*gradient_maxGradientAbsoluteNoise.getDouble()*(this.rng.nextDouble()-0.5); 197 final double new_pi_e_notProjected = old_pi_e + this.gradient_gammaStep.getDouble() * gradient_e + this.gradient_heavyBallBetaParameter.getDouble() * (old_pi_e - previous_pi_e); 198 double new_pi_e_projected = Math.max(0, new_pi_e_notProjected); 199 if (gradient_maxGradientCoordinateChange.getDouble() > 0) 200 new_pi_e_projected = GradientProjectionUtils.scaleDown_maxAbsoluteCoordinateChange (old_pi_e , new_pi_e_projected , gradient_maxGradientCoordinateChange.getDouble()); 201 202 this.routing_previousLinkWeight_e.set(eMe.getIndex (), old_pi_e); 203 this.routing_price_e.set(eMe.getIndex () , new_pi_e_projected); 204 eMe.setAttribute("pi_e" , "" + routing_price_e.get (eMe.getIndex ())); 205 206 207 /* Send the events of the signaling information messages to all the nodes */ 208 if (rng.nextDouble() >= this.signaling_signalingLossProbability.getDouble()) // the signaling may be lost => lost to all nodes 209 for (Node n : currentNetPlan.getNodes ()) 210 { 211 final double signalingReceptionTime = t + Math.max(0 , signaling_averageDelay.getDouble() + signaling_maxFluctuationInDelay.getDouble() * (rng.nextDouble() - 0.5)); 212 this.scheduleEvent(new SimEvent (signalingReceptionTime , SimEvent.DestinationModule.EVENT_PROCESSOR , SIGNALING_RECEIVEDMESSAGE , Triple.of(n , eMe , routing_price_e.get(eMe.getIndex ())))); 213 } 214 215 /* Re-schedule when to wake up again */ 216 final double signalingTime = signaling_isSynchronous.getBoolean()? t + signaling_averageInterMessageTime.getDouble() : Math.max(t , t + signaling_averageInterMessageTime.getDouble() + signaling_maxFluctuationInterMessageTime.getDouble() * (rng.nextDouble() - 0.5)); 217 this.scheduleEvent(new SimEvent (signalingTime , SimEvent.DestinationModule.EVENT_PROCESSOR , SIGNALING_WAKEUPTOSENDMESSAGE , eMe)); 218 break; 219 220 } 221 222 case UPDATE_WAKEUPTOUPDATE: // a node updates its source routing using the most updated information it has 223 { 224 final Node nMe = (Node) event.getEventObject(); 225 for (Demand d : nMe.getOutgoingDemands()) 226 updateDemandRoutingFromWeightsKnown (d); 227 228 final double updateTime = update_isSynchronous.getBoolean()? t + update_averageInterUpdateTime.getDouble() : Math.max(t , t + update_averageInterUpdateTime.getDouble() + update_maxFluctuationInterUpdateTime.getDouble() * (rng.nextDouble() - 0.5)); 229 this.scheduleEvent(new SimEvent (updateTime , SimEvent.DestinationModule.EVENT_PROCESSOR , UPDATE_WAKEUPTOUPDATE, nMe)); 230 231 this.stat_traceOf_xp.add(t, this.currentNetPlan.getVectorRouteCarriedTraffic()); 232 this.stat_traceOf_pie.add(t, this.routing_price_e.copy ()); 233 this.stat_traceOf_ye.add(t, this.currentNetPlan.getVectorLinkCarriedTraffic()); 234 this.stat_traceOf_objFunction.add(t, computeObjectiveFucntionFromNetPlan()); 235 236 if (t > this.simulation_maxNumberOfUpdateIntervals.getDouble() * this.update_averageInterUpdateTime.getDouble()) { this.endSimulation (); } 237 238 break; 239 } 240 241 242 default: throw new RuntimeException ("Unexpected received event"); 243 } 244 245 246 } 247 248 public String finish (StringBuilder st , double simTime) 249 { 250 if (simulation_outFileNameRoot.getString().equals("")) return null; 251 stat_traceOf_xp.printToFile(new File (simulation_outFileNameRoot.getString() + "_xp.txt")); 252 stat_traceOf_pie.printToFile(new File (simulation_outFileNameRoot.getString() + "_pie.txt")); 253 stat_traceOf_ye.printToFile(new File (simulation_outFileNameRoot.getString() + "_ye.txt")); 254 stat_traceOf_objFunction.printToFile(new File (simulation_outFileNameRoot.getString() + "_objFunc.txt")); 255 Quadruple<DoubleMatrix1D,DoubleMatrix1D,DoubleMatrix1D,Double> pair = computeOptimumSolution (true); 256 DoubleMatrix1D x_p_opt = pair.getFirst(); 257 DoubleMatrix1D pi_e = pair.getSecond(); 258 DoubleMatrix1D y_e = pair.getThird(); 259 double optCost = pair.getFourth(); 260 TimeTrace.printToFile(new File (simulation_outFileNameRoot.getString() + "_jomReg_objFunc.txt"), optCost); 261 TimeTrace.printToFile(new File (simulation_outFileNameRoot.getString() + "_jomReg_xp.txt"), x_p_opt); 262 TimeTrace.printToFile(new File (simulation_outFileNameRoot.getString() + "_jomReg_pie.txt"), pi_e); 263 TimeTrace.printToFile(new File (simulation_outFileNameRoot.getString() + "_jomReg_ye.txt"), y_e); 264 return null; 265 } 266 267 private double computeObjectiveFucntionFromNetPlan () 268 { 269 double objFunc = 0; 270 for (Route r : this.currentNetPlan.getRoutes()) 271 { 272 final double x_r = r.getCarriedTraffic(); 273 objFunc += x_r * r.getNumberOfHops() + this.gradient_regularizationEpsilon.getDouble() * Math.pow(x_r, 2); 274 } 275 return objFunc; 276 } 277 278 private Quadruple<DoubleMatrix1D,DoubleMatrix1D,DoubleMatrix1D,Double> computeOptimumSolution (boolean isRegularized) 279 { 280 /* Modify the map so that it is the pojection where all elements sum h_d, and are non-negative */ 281 final int P = this.currentNetPlan.getNumberOfRoutes(); 282 final int E = this.currentNetPlan.getNumberOfLinks(); 283 final DoubleMatrix1D h_d = this.currentNetPlan.getVectorDemandOfferedTraffic(); 284 final DoubleMatrix1D u_e = this.currentNetPlan.getVectorLinkCapacity(); 285 final DoubleMatrix1D h_p = this.currentNetPlan.getVectorRouteOfferedTrafficOfAssociatedDemand(); 286 287 OptimizationProblem op = new OptimizationProblem(); 288 op.addDecisionVariable("x_p", false , new int[] { 1 , P }, DoubleUtils.zeros(P) , h_p.toArray()); 289 op.addDecisionVariable("y_e", false , new int[] { 1 , E }, DoubleUtils.zeros(E) , u_e.toArray()); 290 op.setInputParameter("h_d", h_d , "row"); 291 op.setInputParameter("u_e", u_e , "row"); 292 op.setInputParameter("epsilon", this.gradient_regularizationEpsilon.getDouble()); 293 294 /* Set some input parameters */ 295 if (!isRegularized) 296 op.setObjectiveFunction("minimize", "sum (y_e)"); 297 else 298 op.setObjectiveFunction("minimize", "sum (y_e) + epsilon * x_p * x_p'"); 299 300 /* VECTORIAL FORM OF THE CONSTRAINTS */ 301 op.setInputParameter("A_dp", currentNetPlan.getMatrixDemand2RouteAssignment()); 302 op.setInputParameter("A_ep", currentNetPlan.getMatrixLink2RouteAssignment()); 303 op.addConstraint("A_dp * x_p' == h_d'"); // for each demand, the 100% of the traffic is carried (summing the associated paths) 304 op.addConstraint("A_ep * x_p' == y_e'"); // set the traffic in each link equal to y_e 305 306 /* Call the solver to solve the problem */ 307 op.solve("ipopt"); 308 309 /* If an optimal solution was not found, quit */ 310 if (!op.solutionIsOptimal ()) throw new RuntimeException ("An optimal solution was not found"); 311 312 /* Retrieve the optimum solutions. Convert the bps into Erlangs */ 313 final DoubleMatrix1D x_p = op.getPrimalSolution("x_p").view1D(); 314 final DoubleMatrix1D y_e = op.getPrimalSolution("y_e").view1D(); 315 final DoubleMatrix1D pi_e = op.getMultiplierOfUpperBoundConstraintToPrimalVariables("y_e").view1D(); 316 317 /* Check the solution */ 318 for (Link e : this.currentNetPlan.getLinks()) 319 { 320 double yThisLink = 0; for (Route r : e.getTraversingRoutes()) yThisLink += x_p.get(r.getIndex ()); 321 if (yThisLink > e.getCapacity() + 1E-3) throw new RuntimeException ("Bad"); 322 } 323 for (Demand d : this.currentNetPlan.getDemands()) 324 { 325 double trafDemand = 0; for (Route r : d.getRoutes()) trafDemand += x_p.get(r.getIndex ()); 326 if (Math.abs(trafDemand - d.getOfferedTraffic()) > 1E-3) throw new RuntimeException ("Bad"); 327 } 328 329 /* Compute optimum cost */ 330 double objFunc = 0; for (Route r : this.currentNetPlan.getRoutes()) 331 { 332 final double x_r = x_p.get(r.getIndex ()); 333 objFunc += x_r * r.getNumberOfHops(); 334 if (isRegularized) objFunc += this.gradient_regularizationEpsilon.getDouble() * Math.pow(x_r, 2); 335 } 336 return Quadruple.of(x_p,pi_e,y_e,objFunc); 337 338 } 339 340 341 private void updateDemandRoutingFromWeightsKnown (Demand d) 342 { 343 final Node a_d = d.getIngressNode(); 344 final double h_d = d.getOfferedTraffic(); 345 final DoubleMatrix1D weightsKnown_e = this.routing_mostUpdatedLinkPriceKnownByNode_ne.viewRow(a_d.getIndex ()); 346 DoubleMatrix1D a_k = DoubleFactory1D.dense.make (R); 347 for (Route r : d.getRoutes()) 348 { 349 double val = 0; 350 for (Link e : r.getSeqLinks()) val += weightsKnown_e.get(e.getIndex()) + 1; 351 a_k.set(r.getIndex (), val); 352 } 353 354 DoubleMatrix1D x_r = GradientProjectionUtils.regularizedProjection_sumEquality (a_k , control_routeIndexes_d [d.getIndex ()] , null , h_d , this.gradient_regularizationEpsilon.getDouble()); 355 for (Route r : d.getRoutes ()) 356 r.setCarriedTraffic(x_r.get(r.getIndex ()) , x_r.get(r.getIndex ())); 357 } 358 359}