001/* An Optimizing Scheduler for the SDF domain 002 003 Copyright (c) 1998-2015 The Regents of the University of California. 004 All rights reserved. 005 Permission is hereby granted, without written agreement and without 006 license or royalty fees, to use, copy, modify, and distribute this 007 software and its documentation for any purpose, provided that the above 008 copyright notice and the following two paragraphs appear in all copies 009 of this software. 010 011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015 SUCH DAMAGE. 016 017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022 ENHANCEMENTS, OR MODIFICATIONS. 023 024 PT_COPYRIGHT_VERSION_2 025 COPYRIGHTENDKEY 026 027 */ 028 029package ptolemy.domains.sdf.optimize; 030 031import java.util.HashMap; 032import java.util.Iterator; 033import java.util.LinkedList; 034import java.util.List; 035import java.util.Map; 036 037import ptolemy.actor.Actor; 038import ptolemy.actor.CompositeActor; 039import ptolemy.actor.IOPort; 040import ptolemy.actor.Receiver; 041import ptolemy.actor.sched.Firing; 042import ptolemy.actor.sched.NotSchedulableException; 043import ptolemy.actor.sched.Schedule; 044import ptolemy.actor.util.DFUtilities; 045import ptolemy.data.BooleanToken; 046import ptolemy.data.IntToken; 047import ptolemy.data.Token; 048import ptolemy.domains.sdf.kernel.SDFDirector; 049import ptolemy.domains.sdf.kernel.SDFReceiver; 050import ptolemy.domains.sdf.kernel.SDFScheduler; 051import ptolemy.domains.sdf.optimize.OptimizingSDFDirector.OptimizationCriteria; 052import ptolemy.kernel.util.IllegalActionException; 053import ptolemy.kernel.util.InternalErrorException; 054import ptolemy.kernel.util.NameDuplicationException; 055import ptolemy.math.Fraction; 056 057/////////////////////////////////////////////////////////////////// 058////OptimizingSDFScheduler 059 060/** 061OptimizingSDFScheduler is the scheduler companion to the OptimizingSDFDirector 062It works with the synchronous dataflow (SDF) model of computation to find 063an optimized schedule according to a defined criterion. 064 065<h1>Class comments</h1> 066An OptimizingSDFScheduler is the class that determines an optimized static schedule. 067<p> 068See {@link ptolemy.domains.sdf.kernel.SDFScheduler} and 069{@link ptolemy.domains.sdf.optimize.OptimizingSDFDirector} for more information. 070</p> 071@see ptolemy.domains.sdf.kernel.SDFScheduler 072@see ptolemy.domains.sdf.optimize.OptimizingSDFDirector 073 074@author Marc Geilen 075@version $Id$ 076@since Ptolemy II 10.0 077@Pt.ProposedRating Red (mgeilen) 078@Pt.AcceptedRating Red () 079 */ 080 081public class OptimizingSDFScheduler extends SDFScheduler { 082 /** Construct an instance of an OptimizingSDFScheduler. Provide 083 * container and name as usual and an optimization criterion 084 * <i>crit</i>. 085 * @param container The container. 086 * @param name The name. 087 * @param crit optimization criterion 088 * @exception IllegalActionException If the attribute is not of an 089 * acceptable class for the container, or if the name contains a period. 090 * @exception NameDuplicationException If the name coincides with 091 * an attribute already in the container. 092 */ 093 public OptimizingSDFScheduler(OptimizingSDFDirector container, String name, 094 OptimizationCriteria crit) 095 throws IllegalActionException, NameDuplicationException { 096 super(container, name); 097 optimizationCriterion = crit; 098 } 099 100 /** 101 * The optimization criterion to use. 102 */ 103 public OptimizationCriteria optimizationCriterion; 104 105 /////////////////////////////////////////////////////////////////// 106 //// protected methods //// 107 108 /** Return the scheduling sequence. An exception will be thrown if the 109 * graph is not schedulable. This occurs in the following circumstances: 110 * <ul> 111 * <li>The graph is not a connected graph. 112 * <li>No integer solution exists for the balance equations. 113 * <li>The graph contains cycles without delays (deadlock). 114 * <li>Multiple output ports are connected to the same broadcast 115 * relation. (equivalent to a non-deterministic merge) 116 * <li>The vectorizationFactor parameter of the director does 117 * not contain a positive integer. 118 * </ul> 119 * 120 * @return A schedule of the deeply contained opaque entities 121 * in the firing order. 122 * @exception NotSchedulableException If the rates specified for 123 * the model imply that the model is not statically schedulable. 124 * @exception IllegalActionException If the rate parameters 125 * of the model are not correct, or the computed rates for 126 * external ports are not correct. 127 */ 128 @Override 129 protected Schedule _getSchedule() 130 throws NotSchedulableException, IllegalActionException { 131 SDFDirector director = (SDFDirector) getContainer(); 132 CompositeActor model = (CompositeActor) director.getContainer(); 133 134 _checkDynamicRateVariables(model, _rateVariables); 135 136 int vectorizationFactor = 1; 137 138 Token token = director.vectorizationFactor.getToken(); 139 vectorizationFactor = ((IntToken) token).intValue(); 140 141 if (vectorizationFactor < 1) { 142 throw new NotSchedulableException(this, 143 "The supplied vectorizationFactor must be " 144 + "a positive integer. The given value was: " 145 + vectorizationFactor); 146 } 147 148 CompositeActor container = (CompositeActor) director.getContainer(); 149 150 // A linked list containing all the actors. 151 List allActorList = container.deepEntityList(); 152 153 // externalRates maps from external 154 // ports to the number of tokens that that port 155 // will produce or consume in each firing. 156 // It gets populated with the fractional production ratios 157 // and is used in the end to set final rates on external ports. 158 // This map is initialized to zero. 159 // NOTE: This used to be a TreeMap using DFUtilities.NamedObjComparator(). 160 // However, that comparator is very slow. 161 // FIXME: Why not get this via the container of the receivers? 162 // or better yet, cache it in the receivers? 163 Map externalRates = new HashMap(); 164 165 // Initialize externalRates to zero. 166 for (Iterator ports = container.portList().iterator(); ports 167 .hasNext();) { 168 IOPort port = (IOPort) ports.next(); 169 externalRates.put(port, Fraction.ZERO); 170 } 171 172 // First solve the balance equations 173 Map entityToFiringsPerIteration = _solveBalanceEquations(container, 174 allActorList, externalRates); 175 176 // Multiply the number of firings for each actor by the 177 // vectorizationFactor. 178 _vectorizeFirings(vectorizationFactor, entityToFiringsPerIteration, 179 externalRates); 180 181 // Set the firing vector. 182 _firingVector = entityToFiringsPerIteration; 183 184 if (_debugging) { 185 _debug("Normalized Firing Counts:"); 186 _debug(entityToFiringsPerIteration.toString()); 187 } 188 189 // Schedule all the actors using the calculated firings. 190 Schedule result = _scheduleConnectedActors(externalRates, allActorList, 191 container); 192 193 // Set parameters on each actor that contain the number 194 // of firings in an iteration. 195 _saveFiringCounts(entityToFiringsPerIteration); 196 197 // Set the rate parameters of any external ports. 198 _saveContainerRates(externalRates); 199 200 // Set the schedule to be valid. 201 setValid(true); 202 _externalRates = externalRates; 203 return result; 204 } 205 206 /////////////////////////////////////////////////////////////////// 207 //// private methods //// 208 209 /** Create an optimal schedule for a set of actors. 210 * 211 * @param externalRates Map from external port to an Integer 212 * representing the number of tokens produced or consumed from 213 * that port during the course of an iteration. 214 * @param actorList The actors that need to be scheduled. 215 * @param container The container. 216 * @return An instance of the Schedule class, indicating the order 217 * in which actors should fire. 218 * @exception NotSchedulableException If the algorithm encounters an SDF 219 * graph that is not consistent with the firing vector, or detects an 220 * inconsistent internal state, or detects a graph that cannot be 221 * scheduled. 222 */ 223 private Schedule _scheduleConnectedActors(Map externalRates, List actorList, 224 CompositeActor container) throws NotSchedulableException { 225 226 // FIXME: contains a lot of duplicated code from same method 227 // in SDFScheduler. Would be good to factor out common code, 228 // but the original author did not want to touch SDFScheduler. 229 230 // A linked list containing all the actors that have no inputs. 231 LinkedList readyToScheduleActorList = new LinkedList(); 232 233 // optimizedSchedule holds the new schedule 234 Schedule optimizedSchedule; 235 236 // An association between each actor and the number of firings 237 // for that actor that remain to be simulated. 238 // NOTE: This used to be a TreeMap using DFUtilities.NamedObjComparator(). 239 // However, that comparator is very slow. 240 // Map firingsRemainingVector = new TreeMap( 241 // new DFUtilities.NamedObjComparator()); 242 243 // FindBugs: Useless object stored in variable firingsRemainingVector 244 //Map firingsRemainingVector = new HashMap(); 245 246 // Initialized the firingsRemainingVector to the current 247 // firing vector. 248 //firingsRemainingVector.putAll(_firingVector); 249 250 // A list of all that actors that we have not yet completely scheduled. 251 // FIXME: Is this list needed? 252 LinkedList unscheduledActorList = new LinkedList(); 253 unscheduledActorList.addAll(actorList); 254 255 try { 256 // clear the receivers of input ports of all actors in actorList 257 // as well as the external ports. This should cover all receivers 258 // we are dealing with 259 260 // clear all the receivers of input ports of actors in actorList 261 Iterator actorsIterator = actorList.iterator(); 262 while (actorsIterator.hasNext()) { 263 Actor actor = (Actor) actorsIterator.next(); 264 Iterator inputPorts = actor.inputPortList().iterator(); 265 while (inputPorts.hasNext()) { 266 IOPort inputPort = (IOPort) inputPorts.next(); 267 Receiver[][] receivers = inputPort.getReceivers(); 268 if (receivers != null) { 269 for (int m = 0; m < receivers.length; m++) { 270 for (int n = 0; n < receivers[m].length; n++) { 271 ((SDFReceiver) receivers[m][n])._waitingTokens = 0; 272 } 273 } 274 } 275 } 276 } 277 // clear all the receivers of the external output ports 278 Iterator externalOutputPorts = container.outputPortList() 279 .iterator(); 280 while (externalOutputPorts.hasNext()) { 281 IOPort outputPort = (IOPort) externalOutputPorts.next(); 282 Receiver[][] receivers = outputPort.getInsideReceivers(); 283 if (receivers != null) { 284 for (int m = 0; m < receivers.length; m++) { 285 for (int n = 0; n < receivers[m].length; n++) { 286 ((SDFReceiver) receivers[m][n])._waitingTokens = 0; 287 } 288 } 289 } 290 } 291 292 // Simulate production of initial tokens. 293 Iterator actors = actorList.iterator(); 294 295 while (actors.hasNext()) { 296 Actor actor = (Actor) actors.next(); 297 Iterator outputPorts = actor.outputPortList().iterator(); 298 299 while (outputPorts.hasNext()) { 300 IOPort outputPort = (IOPort) outputPorts.next(); 301 int count = DFUtilities.getTokenInitProduction(outputPort); 302 303 if (count > 0) { 304 _simulateTokensCreated(outputPort, count); 305 } 306 } 307 } 308 309 // Simulate a number of tokens initially present on each 310 // external input port. 311 for (Iterator inputPorts = container.inputPortList() 312 .iterator(); inputPorts.hasNext();) { 313 IOPort port = (IOPort) inputPorts.next(); 314 int count = ((Integer) externalRates.get(port)).intValue(); 315 316 if (count > 0) { 317 _simulateExternalInputs(port, count, actorList, 318 readyToScheduleActorList); 319 } 320 } 321 322 // Now the initial state is known and we can compute the optimal schedule 323 OptimalScheduleFinder finder = new OptimalScheduleFinder(this, 324 optimizationCriterion); 325 326 // Collect the repetition vector from _firingVector 327 // exclude the composite actor from the vector 328 HashMap repVec = new HashMap(); 329 Iterator rvi = _firingVector.keySet().iterator(); 330 while (rvi.hasNext()) { 331 Actor rva = (Actor) rvi.next(); 332 // do not include the composite 333 if (rva != container) { 334 repVec.put(rva, _firingVector.get(rva)); 335 } 336 } 337 // delegate the construction of the schedule to the OptimizedScheduleFinder 338 //optimizedSchedule = finder.makeSchedule(repVec); 339 optimizedSchedule = finder.makeScheduleGreedy(repVec); 340 341 // Iterate over the schedule once to fix the buffer sizes. 342 Iterator si = optimizedSchedule.iterator(); 343 while (si.hasNext()) { 344 Firing firing = (Firing) si.next(); 345 Actor firingActor = firing.getActor(); 346 347 // simulate firing according to firing 348 // simulate input consumption 349 _simulateInputConsumption(firingActor, 1); 350 // Get all its outputPorts 351 // and simulate the proper production of tokens. 352 for (Iterator outputPorts = firingActor.outputPortList() 353 .iterator(); outputPorts.hasNext();) { 354 IOPort outputPort = (IOPort) outputPorts.next(); 355 int count = DFUtilities.getTokenProductionRate(outputPort); 356 _simulateTokensCreated(outputPort, count); 357 } 358 } 359 360 } catch (IllegalActionException ex) { 361 // This could happen if we call getTokenConsumptionRate on a 362 // port that isn't a part of the actor. This probably means 363 // the graph is screwed up, or somebody else is mucking 364 // with it. 365 throw new InternalErrorException(this, ex, 366 "SDF Scheduler Failed internal consistency check."); 367 } 368 369 if (_debugging) { 370 _debug("Schedule is:"); 371 _debug(optimizedSchedule.toString()); 372 } 373 374 return optimizedSchedule; 375 } 376 377 /** Simulate the creation of tokens by the given output port when 378 * its actor fires. If any actors that receive tokens are then ready to 379 * fire, given that only actors in the actor list are being scheduled, then 380 * add those actors to the list of actors that are ready to schedule. 381 * @param outputPort The port that is creating the tokens. 382 * @param createdTokens The number of tokens to create. 383 */ 384 private void _simulateTokensCreated(IOPort outputPort, int createdTokens) 385 throws IllegalActionException { 386 // FIXME: Why are the actor lists lists rather than sets? 387 Receiver[][] receivers = outputPort.getRemoteReceivers(); 388 389 for (int channel = 0; channel < receivers.length; channel++) { 390 if (receivers[channel] == null) { 391 continue; 392 } 393 394 for (int copy = 0; copy < receivers[channel].length; copy++) { 395 if (!(receivers[channel][copy] instanceof SDFReceiver)) { 396 // NOTE: This should only occur if it is null. 397 assert receivers[channel][copy] == null; 398 continue; 399 } 400 401 SDFReceiver receiver = (SDFReceiver) receivers[channel][copy]; 402 // Increment the number of waiting tokens. 403 receiver._waitingTokens += createdTokens; 404 405 // Update the buffer size, if necessary. 406 boolean enforce = ((BooleanToken) constrainBufferSizes 407 .getToken()).booleanValue(); 408 409 if (enforce) { 410 int capacity = receiver.getCapacity(); 411 412 if (capacity == SDFReceiver.INFINITE_CAPACITY 413 || receiver._waitingTokens > capacity) { 414 receiver.setCapacity(receiver._waitingTokens); 415 } 416 } 417 } 418 } 419 } 420 421 /** 422 * Print a debug message. 423 * @param message message to display 424 */ 425 public void showDebug(String message) { 426 if (_debugging) { 427 _debug(message); 428 } 429 430 } 431 432}