001/* A Scheduler for the SDF domain 002 003 Copyright (c) 1998-2018 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 */ 028package ptolemy.domains.sdf.kernel; 029 030import java.util.HashMap; 031import java.util.HashSet; 032import java.util.Iterator; 033import java.util.LinkedList; 034import java.util.List; 035import java.util.Map; 036import java.util.Set; 037 038import ptolemy.actor.Actor; 039import ptolemy.actor.CompositeActor; 040import ptolemy.actor.Director; 041import ptolemy.actor.IOPort; 042import ptolemy.actor.IntermediateReceiver; 043import ptolemy.actor.Receiver; 044import ptolemy.actor.parameters.ParameterPort; 045import ptolemy.actor.parameters.PortParameter; 046import ptolemy.actor.sched.Firing; 047import ptolemy.actor.sched.NotSchedulableException; 048import ptolemy.actor.sched.Schedule; 049import ptolemy.actor.sched.StaticSchedulingDirector; 050import ptolemy.actor.util.ConstVariableModelAnalysis; 051import ptolemy.actor.util.DFUtilities; 052import ptolemy.data.BooleanToken; 053import ptolemy.data.IntToken; 054import ptolemy.data.Token; 055import ptolemy.data.expr.Parameter; 056import ptolemy.data.expr.Variable; 057import ptolemy.data.type.BaseType; 058import ptolemy.kernel.ComponentEntity; 059import ptolemy.kernel.Entity; 060import ptolemy.kernel.Port; 061import ptolemy.kernel.util.IllegalActionException; 062import ptolemy.kernel.util.InternalErrorException; 063import ptolemy.kernel.util.KernelException; 064import ptolemy.kernel.util.NameDuplicationException; 065import ptolemy.kernel.util.NamedObj; 066import ptolemy.kernel.util.Settable; 067import ptolemy.kernel.util.ValueListener; 068import ptolemy.kernel.util.Workspace; 069import ptolemy.math.Fraction; 070 071/////////////////////////////////////////////////////////////////// 072//// SDFScheduler 073 074/** 075 076 A scheduler that implements basic scheduling of SDF graphs. This 077 class calculates the SDF schedule in two phases. First, the balance 078 equations for the rates between actors are solved to determine the 079 <i>firing vector</i> (also known as the repetitions vector). The 080 firing vector is the least integer solution such that the number of 081 tokens created on each channel of each relation is equal to the number 082 of tokens consumed. In some cases, no solution exists. Such graphs 083 are not executable under SDF. 084 <p> 085 Then the actors are ordered such that each actor only fires when the 086 scheduler has determined that enough tokens will be present on its 087 input ports to allow it to fire. In cases where the dataflow graph is 088 cyclic, a valid firing vector exists, but no actor can fire, since 089 they all depend on the output of another actor. This situation is 090 known as <i>deadlock</i>. Deadlock must be prevented in SDF by manually 091 inserting delay actors, which represent initial tokens on each 092 relation. Such delay actors are responsible for creating tokens 093 during initialization that will prevent deadlock. These actors 094 set the <i>tokenInitProduction</i> parameter of their output ports 095 to represent the number of 096 tokens they will create during initialization. The SDFScheduler uses 097 this parameter to break the dependency in a cyclic 098 graph. 099 <p> 100 In addition, an input port may initially have available input tokens. 101 This is indicated by a <i>tokenInitConsumption</i> parameter on the 102 input port. 103 <p> 104 Note that this scheduler only ensures that the number of firings is 105 minimal. Most notably, it does not attempt to minimize the size of 106 the buffers that are associated with each relation. The resulting 107 schedule is a linear schedule (as opposed to a looped schedule) and is 108 not suitable for multiprocessing environments. 109 <p> 110 Any actors may be 111 scheduled by this scheduler, which will, by default, assume 112 homogeneous behavior for each actor. (i.e. each output port produces 113 one token for each firing, and each input port consumes one token on 114 each firing, and no tokens are created during initialization.) If 115 this is not the case then parameters named <i>tokenConsumptionRate</i>, 116 <i>tokenProductionRate</i>, <i>tokenInitProduction</i>, and 117 <i>tokenInitConsumption</i> must be set. 118 The SDFIOPort class provides easier access to these parameters. 119 <p> 120 Note that reconstructing the schedule is expensive, so the schedule is 121 locally cached for as long as possible, and mutations under SDF should 122 be avoided. 123 <p> 124 Note that this scheduler supports actors with 0-rate ports as long as 125 the graph is not equivalent to a disconnected graph. This scheduler 126 is somewhat conservative in this respect. 127 <p>Disconnected graphs are supported if the SDF Director parameter 128 <i>allowDisconnectedGraphs</i> is true. 129 130 @see ptolemy.actor.sched.Scheduler 131 @see ptolemy.domains.sdf.lib.SampleDelay 132 133 @author Stephen Neuendorffer and Brian Vogel 134 @version $Id$ 135 @since Ptolemy II 0.2 136 @Pt.ProposedRating Green (neuendor) 137 @Pt.AcceptedRating Green (neuendor) 138 */ 139public class SDFScheduler extends BaseSDFScheduler implements ValueListener { 140 /** Construct a scheduler with no container(director) 141 * in the default workspace, the name of the scheduler is 142 * "Scheduler". 143 */ 144 public SDFScheduler() { 145 super(); 146 _init(); 147 } 148 149 /** Construct a scheduler in the given workspace with the name 150 * "Scheduler". 151 * If the workspace argument is null, use the default workspace. 152 * The scheduler is added to the list of objects in the workspace. 153 * Increment the version number of the workspace. 154 * 155 * @param workspace Object for synchronization and version tracking. 156 */ 157 public SDFScheduler(Workspace workspace) { 158 super(workspace); 159 _init(); 160 } 161 162 /** Construct a scheduler in the given container with the given name. 163 * The container argument must not be null, or a 164 * NullPointerException will be thrown. This attribute will use the 165 * workspace of the container for synchronization and version counts. 166 * If the name argument is null, then the name is set to the empty string. 167 * Increment the version of the workspace. 168 * @param container The container. 169 * @param name The name of this attribute. 170 * @exception IllegalActionException If the attribute is not of an 171 * acceptable class for the container, or if the name contains a period. 172 * @exception NameDuplicationException If the name coincides with 173 * an attribute already in the container. 174 */ 175 public SDFScheduler(Director container, String name) 176 throws IllegalActionException, NameDuplicationException { 177 super(container, name); 178 _init(); 179 } 180 181 /////////////////////////////////////////////////////////////////// 182 //// parameters //// 183 184 /** If true, then buffer sizes are fixed according to the schedule, 185 * and attempts to write to the buffer that cause the buffer to 186 * exceed the schedule size result in an exception. This method 187 * works by setting the capacity of the receivers if the value is 188 * true. This parameter is a boolean that defaults to true. 189 */ 190 public Parameter constrainBufferSizes; 191 192 /////////////////////////////////////////////////////////////////// 193 //// public methods //// 194 195 /** Clone the scheduler into the specified workspace. The new object is 196 * <i>not</i> added to the directory of that workspace (you must do this 197 * yourself if you want it there). 198 * The result is a new scheduler with no container, and no valid schedule. 199 * @param workspace The workspace for the cloned object. 200 * @exception CloneNotSupportedException If one of the attributes 201 * cannot be cloned. 202 * @return The new Scheduler. 203 */ 204 @Override 205 public Object clone(Workspace workspace) throws CloneNotSupportedException { 206 SDFScheduler newObject = (SDFScheduler) super.clone(workspace); 207 newObject._firingVector = new HashMap(); 208 newObject._externalRates = new HashMap(); 209 newObject._rateVariables = new LinkedList(); 210 return newObject; 211 } 212 213 /** Declare the rate dependency on any external ports of the model. 214 * SDF directors should invoke this method once during preinitialize. 215 */ 216 @Override 217 public void declareRateDependency() throws IllegalActionException { 218 ConstVariableModelAnalysis analysis = ConstVariableModelAnalysis 219 .getAnalysis(this); 220 SDFDirector director = (SDFDirector) getContainer(); 221 CompositeActor model = (CompositeActor) director.getContainer(); 222 223 for (Iterator ports = model.portList().iterator(); ports.hasNext();) { 224 IOPort port = (IOPort) ports.next(); 225 226 if (!(port instanceof ParameterPort)) { 227 if (port.isInput()) { 228 _declareDependency(analysis, port, "tokenConsumptionRate", 229 _rateVariables); 230 _declareDependency(analysis, port, "tokenInitConsumption", 231 _rateVariables); 232 } 233 234 if (port.isOutput()) { 235 _declareDependency(analysis, port, "tokenProductionRate", 236 _rateVariables); 237 _declareDependency(analysis, port, "tokenInitProduction", 238 _rateVariables); 239 } 240 } 241 } 242 } 243 244 /** Get the external port rates. 245 * @return a Map from external ports to the number of tokens that 246 * that port will produce or consume in each firing. 247 */ 248 public Map getExternalRates() { 249 return _externalRates; 250 } 251 252 /** Create the schedule. Return the number of times that the given 253 * entity will fire in a single iteration of the system. 254 * @param entity The entity that is being fired. 255 * @return The number of times that the given entity will fire 256 * @exception IllegalActionException If thrown by getSchedule(). 257 258 */ 259 public int getFiringCount(Entity entity) throws IllegalActionException { 260 getSchedule(); 261 return _getFiringCount(entity); 262 } 263 264 /** React to the fact that the specified Settable has changed by 265 * invalidating the schedule. 266 * @param settable The object that has changed value. 267 */ 268 @Override 269 public void valueChanged(Settable settable) { 270 setValid(false); 271 } 272 273 /////////////////////////////////////////////////////////////////// 274 //// protected methods //// 275 276 /** Populate the given set with the dynamic rate variables in the model. 277 * @param model The model. 278 * @param rateVariables A list of rate variables. Each element 279 * is a Variable. 280 * @exception IllegalActionException If throw while looking for dynamic 281 * rate parameters. 282 */ 283 protected void _checkDynamicRateVariables(CompositeActor model, 284 List rateVariables) throws IllegalActionException { 285 // Check for rate parameters which are dynamic. 286 ConstVariableModelAnalysis analysis = ConstVariableModelAnalysis 287 .getAnalysis(getContainer()); 288 289 // Save the rate variables that we already listen to. 290 LinkedList oldList = new LinkedList(); 291 oldList.addAll(rateVariables); 292 293 LinkedList newList = new LinkedList(); 294 for (Iterator entities = model.deepEntityList().iterator(); entities 295 .hasNext();) { 296 Entity entity = (Entity) entities.next(); 297 298 for (Iterator ports = entity.portList().iterator(); ports 299 .hasNext();) { 300 Port port = (Port) ports.next(); 301 Set set = analysis.getNotConstVariables(port); 302 Variable variable; 303 variable = DFUtilities.getRateVariable(port, 304 "tokenInitProduction"); 305 _listenToRateVariable(variable, rateVariables); 306 newList.add(variable); 307 308 if (set.contains(variable)) { 309 _assertDynamicRateVariable(model, variable, rateVariables, 310 analysis); 311 } 312 313 variable = DFUtilities.getRateVariable(port, 314 "tokenInitConsumption"); 315 _listenToRateVariable(variable, rateVariables); 316 newList.add(variable); 317 318 if (set.contains(variable)) { 319 _assertDynamicRateVariable(model, variable, rateVariables, 320 analysis); 321 } 322 323 variable = DFUtilities.getRateVariable(port, 324 "tokenConsumptionRate"); 325 _listenToRateVariable(variable, rateVariables); 326 newList.add(variable); 327 328 if (set.contains(variable)) { 329 _assertDynamicRateVariable(model, variable, rateVariables, 330 analysis); 331 } 332 333 variable = DFUtilities.getRateVariable(port, 334 "tokenProductionRate"); 335 _listenToRateVariable(variable, rateVariables); 336 newList.add(variable); 337 338 if (set.contains(variable)) { 339 _assertDynamicRateVariable(model, variable, rateVariables, 340 analysis); 341 } 342 } 343 } 344 345 // Don't listen to old rate variables anymore. 346 oldList.removeAll(newList); 347 for (Iterator oldRateVariables = oldList.iterator(); oldRateVariables 348 .hasNext();) { 349 Variable variable = (Variable) oldRateVariables.next(); 350 if (_debugging) { 351 _debug("No longer listening to rate variable " + variable); 352 } 353 variable.removeValueListener(this); 354 rateVariables.remove(variable); 355 } 356 } 357 358 /** Determine the number of times the given actor can fire, based on 359 * the number of tokens that are present on its inputs. 360 * @param currentActor The actor. 361 * @return The number of times the actor can fire. 362 * @exception IllegalActionException If the rate parameters are invalid. 363 */ 364 protected int _computeMaximumFirings(Actor currentActor) 365 throws IllegalActionException { 366 int result = Integer.MAX_VALUE; 367 368 // Update the number of tokens waiting on the actor's input ports. 369 Iterator inputPorts = currentActor.inputPortList().iterator(); 370 371 while (inputPorts.hasNext()) { 372 IOPort inputPort = (IOPort) inputPorts.next(); 373 int tokenRate = DFUtilities.getTokenConsumptionRate(inputPort); 374 375 // Ignore zero rate ports.. they don't limit the number of times 376 // we can fire their actors. 377 if (tokenRate == 0) { 378 continue; 379 } 380 381 Receiver[][] receivers = inputPort.getReceivers(); 382 383 for (int channel = 0; channel < receivers.length; channel++) { 384 if (receivers[channel] == null) { 385 continue; 386 } 387 388 for (int copy = 0; copy < receivers[channel].length; copy++) { 389 if (!(receivers[channel][copy] instanceof SDFReceiver)) { 390 // This should only occur if it is null. 391 continue; 392 } 393 394 SDFReceiver receiver = (SDFReceiver) receivers[channel][copy]; 395 396 int firings = receiver._waitingTokens / tokenRate; 397 398 // Keep track of whether or not this actor can fire again immediately. 399 if (firings < result) { 400 result = firings; 401 } 402 } 403 } 404 } 405 406 return result; 407 } 408 409 /** Count the number of input ports in the given actor that must be 410 * fulfilled before the actor can fire. Ports that are connected 411 * to actors that we are not scheduling right now are assumed to 412 * be fulfilled. Ports that have more tokens waiting on each of 413 * their channels than their input consumption rate are also 414 * already fulfilled. All other ports are considered to be 415 * unfulfilled. 416 * @param actor The actor. 417 * @param actorList The list of actors that we are scheduling. 418 * @param resetCapacity If true, then reset the capacity of each 419 * receiver to infinite capacity (do this during initialization). 420 * @return The number of unfulfilled input ports of the given actor. 421 * @exception IllegalActionException If any called method throws it. 422 */ 423 @SuppressWarnings("unused") 424 protected int _countUnfulfilledInputs(Actor actor, List actorList, 425 boolean resetCapacity) throws IllegalActionException { 426 if (_debugging && VERBOSE) { 427 _debug("Counting unfulfilled inputs for " + actor.getFullName()); 428 } 429 430 int count = 0; 431 432 Iterator inputPorts = actor.inputPortList().iterator(); 433 434 while (inputPorts.hasNext()) { 435 IOPort inputPort = (IOPort) inputPorts.next(); 436 437 if (_debugging && VERBOSE) { 438 _debug("Checking input " + inputPort.getFullName()); 439 } 440 441 int threshold = DFUtilities.getTokenConsumptionRate(inputPort); 442 443 if (_debugging && VERBOSE) { 444 _debug("Threshold = " + threshold); 445 } 446 447 Receiver[][] receivers = inputPort.getReceivers(); 448 boolean isFulfilled = true; 449 450 for (int channel = 0; channel < receivers.length; channel++) { 451 if (receivers[channel] == null) { 452 continue; 453 } 454 455 for (int copy = 0; copy < receivers[channel].length; copy++) { 456 if (!(receivers[channel][copy] instanceof SDFReceiver)) { 457 // This should only occur if it is null. 458 continue; 459 } 460 461 SDFReceiver receiver = (SDFReceiver) receivers[channel][copy]; 462 463 if (resetCapacity) { 464 receiver.setCapacity(SDFReceiver.INFINITE_CAPACITY); 465 } 466 467 if (receiver._waitingTokens < threshold) { 468 isFulfilled = false; 469 470 if (!resetCapacity) { 471 break; 472 } 473 } 474 } 475 476 if (!isFulfilled) { 477 // No point in continuing. 478 break; 479 } 480 } 481 482 if (!isFulfilled) { 483 count++; 484 } 485 } 486 487 return count; 488 } 489 490 /** Return the number of firings associated with the given entity. The 491 * number of firings is stored in the _firingVector map, indexed 492 * by the entity. 493 * @param entity One of the actors we are scheduling. 494 * @return The number of firings. 495 */ 496 protected int _getFiringCount(Entity entity) { 497 return ((Integer) _firingVector.get(entity)).intValue(); 498 } 499 500 /** Return the scheduling sequence. An exception will be thrown if the 501 * graph is not schedulable. This occurs in the following circumstances: 502 * <ul> 503 * <li>The graph is not a connected graph. 504 * <li>No integer solution exists for the balance equations. 505 * <li>The graph contains cycles without delays (deadlock). 506 * <li>Multiple output ports are connected to the same broadcast 507 * relation. (equivalent to a non-deterministic merge) 508 * <li>The vectorizationFactor parameter of the director does 509 * not contain a positive integer. 510 * </ul> 511 * 512 * @return A schedule of the deeply contained opaque entities 513 * in the firing order. 514 * @exception NotSchedulableException If the rates specified for 515 * the model imply that the model is not statically schedulable. 516 * @exception IllegalActionException If the rate parameters 517 * of the model are not correct, or the computed rates for 518 * external ports are not correct. 519 */ 520 @Override 521 @SuppressWarnings("unused") 522 protected Schedule _getSchedule() 523 throws NotSchedulableException, IllegalActionException { 524 SDFDirector director = (SDFDirector) getContainer(); 525 CompositeActor model = (CompositeActor) director.getContainer(); 526 527 _checkDynamicRateVariables(model, _rateVariables); 528 529 int vectorizationFactor = 1; 530 531 Token token = director.vectorizationFactor.getToken(); 532 vectorizationFactor = ((IntToken) token).intValue(); 533 534 if (vectorizationFactor < 1) { 535 throw new NotSchedulableException(this, 536 "The supplied vectorizationFactor must be " 537 + "a positive integer. The given value was: " 538 + vectorizationFactor); 539 } 540 541 CompositeActor container = (CompositeActor) director.getContainer(); 542 543 // A linked list containing all the actors. 544 List allActorList = container.deepEntityList(); 545 546 // externalRates maps from external 547 // ports to the number of tokens that that port 548 // will produce or consume in each firing. 549 // It gets populated with the fractional production ratios 550 // and is used in the end to set final rates on external ports. 551 // This map is initialized to zero. 552 // NOTE: This used to be a TreeMap using DFUtilities.NamedObjComparator(). 553 // However, that comparator is very slow. 554 // FIXME: Why not get this via the container of the receivers? 555 // or better yet, cache it in the receivers? 556 Map externalRates = new HashMap(); 557 558 // Initialize externalRates to zero. 559 for (Iterator ports = container.portList().iterator(); ports 560 .hasNext();) { 561 IOPort port = (IOPort) ports.next(); 562 externalRates.put(port, Fraction.ZERO); 563 } 564 565 // First solve the balance equations 566 Map entityToFiringsPerIteration = _solveBalanceEquations(container, 567 allActorList, externalRates); 568 569 if (_debugging && VERBOSE) { 570 _debug("Firing Ratios: " + entityToFiringsPerIteration.toString()); 571 } 572 573 // Multiply the number of firings for each actor by the 574 // vectorizationFactor. 575 _vectorizeFirings(vectorizationFactor, entityToFiringsPerIteration, 576 externalRates); 577 578 // Set the firing vector. 579 _firingVector = entityToFiringsPerIteration; 580 581 if (_debugging) { 582 _debug("Normalized Firing Counts:"); 583 _debug(entityToFiringsPerIteration.toString()); 584 } 585 586 // Schedule all the actors using the calculated firings. 587 Schedule result = _scheduleConnectedActors(externalRates, allActorList, 588 container); 589 590 if (_debugging && VERBOSE) { 591 _debug("Firing Vector:"); 592 _debug(entityToFiringsPerIteration.toString()); 593 } 594 595 // Set parameters on each actor that contain the number 596 // of firings in an iteration. 597 _saveFiringCounts(entityToFiringsPerIteration); 598 599 // Set the rate parameters of any external ports. 600 _saveContainerRates(externalRates); 601 602 // Set the schedule to be valid. 603 setValid(true); 604 _externalRates = externalRates; 605 return result; 606 } 607 608 /** Solve the balance equations for the list of connected Actors. 609 * For each actor, determine the ratio that determines the rate at 610 * which it should fire relative to the other actors for the graph to 611 * be live and operate within bounded memory. Normalize this ratio 612 * into integer, which is the minimum number of firings of the actor 613 * to satisfy the balance equations. 614 * 615 * @param container The container that is being scheduled. 616 * @param actorList The actors that we are interested in. 617 * @param externalRates A map from external ports of container to 618 * the fractional rates of that port. This starts out initialized with 619 * Fraction.ZERO and will be populated during this method. 620 * @return A map from each actor to its fractional 621 * firing. 622 * @exception NotSchedulableException If the graph is not consistent 623 * under the synchronous dataflow model, or if the graph is not connected. 624 * @exception IllegalActionException If any called method throws it. 625 */ 626 protected Map _solveBalanceEquations(CompositeActor container, 627 List actorList, Map externalRates) 628 throws NotSchedulableException, IllegalActionException { 629 // The map that we will return. 630 // This will be populated with the fraction firing ratios for 631 // each actor. 632 // NOTE: This used to be a TreeMap using DFUtilities.NamedObjComparator(). 633 // However, that comparator is very slow. 634 // Map entityToFiringsPerIteration = new TreeMap( 635 // new DFUtilities.NamedObjComparator()); 636 Map entityToFiringsPerIteration = new HashMap(); 637 638 if (actorList.size() == 0) { 639 640 _checkDirectInputOutputConnection(container, externalRates); 641 // If we've been given 642 // no actors to do anything with, return an empty Map. 643 return entityToFiringsPerIteration; 644 } 645 646 // The pool of actors that have their firingsPerIteration set, 647 // but have not had their ports explored yet. 648 LinkedList pendingActors = new LinkedList(); 649 650 // Set of actors that belong to the same cluster. 651 Set clusteredActors = new HashSet(); 652 653 // Set of external ports that are conneted to 654 // actors of the same cluster. 655 Set clusteredExternalPorts = new HashSet(); 656 657 // The pool of Actors that have not been touched 658 // yet. (i.e. all their firingsPerIteration are still set to 659 // Fraction equal to -1/1) 660 LinkedList remainingActors = new LinkedList(); 661 662 // Initialize remainingActors to contain all the actors we were given. 663 remainingActors.addAll(actorList); 664 665 // Initialize entityToFiringsPerIteration for each actor to -1. 666 for (Iterator actors = remainingActors.iterator(); actors.hasNext();) { 667 ComponentEntity entity = (ComponentEntity) actors.next(); 668 entityToFiringsPerIteration.put(entity, _minusOne); 669 } 670 671 StaticSchedulingDirector director = (StaticSchedulingDirector) getContainer(); 672 673 boolean allowDisconnectedGraphs = false; 674 675 if (director instanceof SDFDirector) { 676 allowDisconnectedGraphs = ((SDFDirector) director)._allowDisconnectedGraphs; 677 } 678 679 // Ned Stoffel's change to support disconnected graphs: 680 // Finally, the schedule can jump from one island to 681 // another among the disconnected graphs. There is nothing 682 // to force the scheduler to finish executing all actors 683 // on one island before firing actors on another 684 // island. However, the order of execution within an 685 // island should be correct. 686 while (!remainingActors.isEmpty()) { 687 clusteredActors.clear(); 688 clusteredExternalPorts.clear(); 689 690 ComponentEntity actor = _pickZeroRatePortActor(remainingActors); 691 692 if (actor == null) { 693 actor = (ComponentEntity) remainingActors.removeFirst(); 694 } else { 695 remainingActors.remove(actor); 696 } 697 698 clusteredActors.add(actor); 699 700 entityToFiringsPerIteration.put(actor, new Fraction(1)); 701 pendingActors.addLast(actor); 702 703 while (!pendingActors.isEmpty()) { 704 Actor currentActor = (Actor) pendingActors.removeFirst(); 705 Iterator actorPorts = ((ComponentEntity) currentActor) 706 .portList().iterator(); 707 708 while (actorPorts.hasNext()) { 709 IOPort currentPort = (IOPort) actorPorts.next(); 710 _propagatePort(container, currentPort, 711 entityToFiringsPerIteration, externalRates, 712 remainingActors, pendingActors, clusteredActors, 713 clusteredExternalPorts); 714 } 715 } 716 717 // Now we have _clusteredActors, which contains actors in 718 // one cluster (they are connected). Find the LCM of their 719 // denominator and normalize their firings. This means firings 720 // of actors are only normalized within their cluster. 721 int lcm = 1; 722 723 for (Iterator actors = clusteredActors.iterator(); actors 724 .hasNext();) { 725 Actor currentActor = (Actor) actors.next(); 726 Fraction fraction = (Fraction) entityToFiringsPerIteration 727 .get(currentActor); 728 int denominator = fraction.getDenominator(); 729 lcm = Fraction.lcm(lcm, denominator); 730 } 731 732 // Got the normalizing factor. 733 Fraction lcmFraction = new Fraction(lcm); 734 735 for (Iterator actors = clusteredActors.iterator(); actors 736 .hasNext();) { 737 Actor currentActor = (Actor) actors.next(); 738 Fraction repetitions = ((Fraction) entityToFiringsPerIteration 739 .get(currentActor)).multiply(lcmFraction); 740 741 if (repetitions.getDenominator() != 1) { 742 throw new InternalErrorException( 743 "Failed to properly perform" 744 + " fraction normalization."); 745 } 746 747 entityToFiringsPerIteration.put(currentActor, repetitions); 748 } 749 750 for (Iterator externalPorts = clusteredExternalPorts 751 .iterator(); externalPorts.hasNext();) { 752 IOPort port = (IOPort) externalPorts.next(); 753 Fraction rate = ((Fraction) externalRates.get(port)) 754 .multiply(lcmFraction); 755 756 if (rate.getDenominator() != 1) { 757 throw new InternalErrorException( 758 "Failed to properly perform" 759 + " fraction normalization."); 760 } 761 762 externalRates.put(port, rate); 763 } 764 765 clusteredActors.clear(); 766 clusteredExternalPorts.clear(); 767 768 if (!allowDisconnectedGraphs) { 769 break; 770 } 771 } 772 773 _checkDirectInputOutputConnection(container, externalRates); 774 775 if (!remainingActors.isEmpty()) { 776 // If there are any actors left that we didn't get to, then 777 // this is not a connected graph, and we throw an exception. 778 StringBuffer messageBuffer = new StringBuffer( 779 "SDF scheduler found disconnected actors! " 780 + "Usually, disconnected actors in an SDF model " 781 + "indicates an error. If this is not an error, try " 782 + "setting the SDFDirector parameter " 783 + "allowDisconnectedGraphs to true."); 784 785 // Look through all the unreached actors. If any of them are 786 // in transparent composite actors that contain PortParameters, 787 // print a message. 788 // See http://bugzilla.ecoinformatics.org/show_bug.cgi?id=4086 789 790 // We only print messages about the first 99 PortParameters. 791 int count = 0; 792 StringBuffer portParameterMessageBuffer = new StringBuffer(); 793 Set portParametersFound = new HashSet(); 794 Set containersSeen = new HashSet(); 795 for (Iterator actors = actorList.iterator(); actors.hasNext() 796 && count < 100; count++) { 797 NamedObj actor = (NamedObj) actors.next(); 798 NamedObj actorContainer = actor.getContainer(); 799 if (actorContainer instanceof CompositeActor 800 && !((CompositeActor) actorContainer).isOpaque() 801 && !containersSeen.contains(actorContainer)) { 802 containersSeen.add(actorContainer); 803 List portParameters = actorContainer 804 .attributeList(PortParameter.class); 805 for (Object portParameter : portParameters) { 806 if (!portParametersFound.contains(portParameter)) { 807 portParametersFound.add(portParameter); 808 portParameterMessageBuffer 809 .append(((PortParameter) portParameter) 810 .getFullName() + " "); 811 if (count > 100) { 812 break; 813 } 814 } 815 } 816 } 817 } 818 if (portParameterMessageBuffer.length() > 0) { 819 messageBuffer 820 .append("Note that some of the unreached actors are in " 821 + "transparent composite actors that have PortParameters. " 822 + "A transparent composite actor is composite actor that has " 823 + "no local director. Transparent composite actors and " 824 + "PortParameters are not compatible, the workaround is to " 825 + "insert a director or remove the PortParameter. " 826 + "\nThe PortParameters:\n" 827 + portParameterMessageBuffer.toString()); 828 if (count >= 99) { 829 messageBuffer.append("..."); 830 } 831 } 832 833 messageBuffer.append("\nUnreached Actors:\n"); 834 count = 0; 835 for (Iterator unreachedActors = remainingActors 836 .iterator(); unreachedActors.hasNext() 837 && count < 100; count++) { 838 NamedObj unreachedActor = (NamedObj) unreachedActors.next(); 839 messageBuffer.append(unreachedActor.getFullName() + " "); 840 } 841 842 if (count >= 99) { 843 messageBuffer.append("..."); 844 } 845 messageBuffer.append("\nReached Actors:\n"); 846 847 List reachedActorList = new LinkedList(); 848 reachedActorList.addAll(actorList); 849 reachedActorList.removeAll(remainingActors); 850 851 count = 0; 852 for (Iterator actors = reachedActorList.iterator(); actors.hasNext() 853 && count < 100; count++) { 854 Entity entity = (Entity) actors.next(); 855 messageBuffer.append(entity.getFullName() + " "); 856 } 857 858 if (count >= 99) { 859 messageBuffer.append("..."); 860 } 861 throw new NotSchedulableException(this, messageBuffer.toString()); 862 } 863 864 return entityToFiringsPerIteration; 865 } 866 867 /** Multiply all of the repetition rates 868 * by the given vectorizationFactor. This factor 869 * is normally the integer value of the vectorizationFactor 870 * parameter of the director. Also multiply the production and 871 * consumption rates of the external ports of the model by the 872 * same amount. Also, convert the two maps in the arguments to 873 * contain Integers rather than Fractions. 874 * @param vectorizationFactor An integer scaling factor to multiply 875 * the firing vector by. 876 * @param entityToFiringsPerIteration Map representing the firing vector. 877 * @param externalRates Map representing production rates of 878 * external ports. 879 */ 880 @SuppressWarnings("unused") 881 protected void _vectorizeFirings(int vectorizationFactor, 882 Map entityToFiringsPerIteration, Map externalRates) { 883 // Note: after we have called the _solveBalanceEquations(), 884 // all the fractual firings and external rates have been 885 // normalized to integers, but we still represent them 886 // as fractions. After the _vectorizeFirings(), they 887 // are saved as integers. 888 if (_debugging && VERBOSE) { 889 _debug("Multiplying firings by vectorizationFactor = " 890 + vectorizationFactor); 891 } 892 893 Fraction lcmFraction = new Fraction(vectorizationFactor); 894 895 // Use entrySet here for performance reasons. 896 for (Iterator actorMapEntries = entityToFiringsPerIteration.entrySet() 897 .iterator(); actorMapEntries.hasNext();) { 898 Map.Entry actors = (Map.Entry) actorMapEntries.next(); 899 Fraction repetitions = (Fraction) actors.getValue(); 900 repetitions = repetitions.multiply(lcmFraction); 901 902 // FIXME: Doing the conversion to Integer here is bizarre, 903 // since they are integers coming in. 904 actors.setValue(Integer.valueOf(repetitions.getNumerator())); 905 } 906 907 // Go through the ports and normalize the external production 908 // and consumption rates by the same factor. 909 // Use entrySet here for performance reasons. 910 for (Iterator portMapEntries = externalRates.entrySet() 911 .iterator(); portMapEntries.hasNext();) { 912 Map.Entry ports = (Map.Entry) portMapEntries.next(); 913 Fraction rate = (Fraction) ports.getValue(); 914 rate = rate.multiply(lcmFraction); 915 916 // FIXME: Doing the conversion to Integer here is bizarre, 917 // since they are integers coming in. 918 ports.setValue(Integer.valueOf(rate.getNumerator())); 919 } 920 } 921 922 /////////////////////////////////////////////////////////////////// 923 //// private methods //// 924 private void _assertDynamicRateVariable(CompositeActor model, 925 Variable variable, List rateVariables, 926 ConstVariableModelAnalysis analysis) throws IllegalActionException { 927 boolean allowRateChanges = ((BooleanToken) ((SDFDirector) getContainer()).allowRateChanges 928 .getToken()).booleanValue(); 929 930 if (!allowRateChanges) { 931 throw new IllegalActionException(variable, 932 "The SDF rate parameter may change." 933 + " This is not allowed in SDF models " 934 + "that will be run through the code " 935 + "generator. If you don't care about " 936 + "code generation, then you might " 937 + "consider setting the allowRateChanges " 938 + "parameter of the SDF director to false."); 939 } 940 941 /* FIXME: This is bogus. It rules out perfectly legitimate models. 942 Entity changeContext = analysis.getChangeContext(variable); 943 944 if (!((changeContext == model) || changeContext.deepContains(model))) { 945 throw new IllegalActionException(variable, 946 "The SDF rate parameter changes during " 947 + "execution of the schedule!"); 948 } 949 */ 950 } 951 952 /** Update the The external rates of those directly connected input 953 * and output ports to be 1. So a direct connection will transfer 954 * one token in each execution of the schedule. 955 * 956 * @param container The container that is being scheduled. 957 * @param externalRates A map from external ports of container to 958 * the fractional rates of that port. The external rates of 959 * those directly connected input and output ports will be updated 960 * to be 1 during this method. 961 */ 962 private void _checkDirectInputOutputConnection(CompositeActor container, 963 Map externalRates) { 964 965 Iterator inputPorts = container.inputPortList().iterator(); 966 while (inputPorts.hasNext()) { 967 IOPort inputPort = (IOPort) inputPorts.next(); 968 Fraction rate = (Fraction) externalRates.get(inputPort); 969 if (rate.equals(Fraction.ZERO)) { 970 971 // Check to make sure that if this port is an external 972 // input port, then it does not drive the same relation as some 973 // other output port or some other external input port. 974 // This results in a non-deterministic merge and is illegal. 975 976 Iterator connectedPorts = inputPort.deepInsidePortList() 977 .iterator(); 978 979 // Make sure any connected output ports are connected on 980 // the inside. 981 while (connectedPorts.hasNext()) { 982 IOPort connectedPort = (IOPort) connectedPorts.next(); 983 984 // connectPort might be connected on the inside to the 985 // currentPort, which is legal. The container argument 986 // is always the container of the director, so any port 987 // that has that container must be connected on the inside. 988 if (connectedPort.isOutput() 989 && connectedPort.getContainer() != container) { 990 throw new NotSchedulableException(inputPort, 991 connectedPort, 992 "External input port drive the same relation " 993 + "as an output port. " 994 + "This is not legal in SDF."); 995 } else if (connectedPort.isInput() 996 && connectedPort.getContainer() == container) { 997 throw new NotSchedulableException(inputPort, 998 connectedPort, 999 "External input port drives the same relation " 1000 + "as another external input port. " 1001 + "This is not legal in SDF."); 1002 } 1003 } 1004 1005 boolean isDirectionConnection = true; 1006 List insideSinkPorts = inputPort.insideSinkPortList(); 1007 1008 // A dangling port has zero rate. 1009 if (insideSinkPorts.isEmpty()) { 1010 isDirectionConnection = false; 1011 } else { 1012 // If the zero external port rate is due to the rate 1013 // propagation from a contained actor (i.e., connected to the 1014 // zero rate port of the actor), then the zero external rate 1015 // must be preserved. 1016 Iterator sinkPorts = insideSinkPorts.iterator(); 1017 while (sinkPorts.hasNext()) { 1018 IOPort sinkPort = (IOPort) sinkPorts.next(); 1019 if (sinkPort.getContainer() != container) { 1020 isDirectionConnection = false; 1021 break; 1022 } 1023 } 1024 } 1025 1026 if (isDirectionConnection) { 1027 externalRates.put(inputPort, new Fraction(1)); 1028 Iterator sinkPorts = insideSinkPorts.iterator(); 1029 while (sinkPorts.hasNext()) { 1030 IOPort sinkPort = (IOPort) sinkPorts.next(); 1031 externalRates.put(sinkPort, new Fraction(1)); 1032 } 1033 } 1034 } 1035 } 1036 } 1037 1038 /** Create the parameter constrainBufferSizes and set its default 1039 * value and type constraints. 1040 */ 1041 private void _init() { 1042 try { 1043 constrainBufferSizes = new Parameter(this, "constrainBufferSizes"); 1044 constrainBufferSizes.setTypeEquals(BaseType.BOOLEAN); 1045 constrainBufferSizes.setExpression("true"); 1046 } catch (KernelException e) { 1047 throw new InternalErrorException(e); 1048 } 1049 } 1050 1051 /** Add this scheduler as a value listener to the given variable 1052 * and add the variable to the given list. If the list already 1053 * includes the variable, or the variable is null, then do 1054 * nothing. 1055 * @param variable A variable, which is a rate variable that this scheduler 1056 * uses for scheduling. 1057 * @param rateVariables A list of rate variables. 1058 */ 1059 private void _listenToRateVariable(Variable variable, List rateVariables) { 1060 // The schedule depends on the rate parameter. 1061 if (variable != null && !rateVariables.contains(variable)) { 1062 if (_debugging) { 1063 _debug("Listening to rate variable " + variable); 1064 } 1065 1066 variable.addValueListener(this); 1067 rateVariables.add(variable); 1068 } 1069 } 1070 1071 /** Search the given list of actors for one that contains at least 1072 * one port that has zero rate. 1073 * 1074 * @param actorList The list of all of the actors to search. 1075 * @return An actor that contains at least one zero rate port, or null 1076 * if no actor has a zero rate port. 1077 */ 1078 private ComponentEntity _pickZeroRatePortActor(List actorList) 1079 throws IllegalActionException { 1080 for (Iterator actors = actorList.iterator(); actors.hasNext();) { 1081 ComponentEntity actor = (ComponentEntity) actors.next(); 1082 1083 // Check if this actor has any ports with rate of zero. 1084 for (Iterator ports = actor.portList().iterator(); ports 1085 .hasNext();) { 1086 IOPort port = (IOPort) ports.next(); 1087 1088 if (DFUtilities.getRate(port) == 0) { 1089 return actor; 1090 } 1091 } 1092 } 1093 1094 return null; 1095 } 1096 1097 /** Propagate the number of fractional firings decided for this actor 1098 * through the specified port. Compute the fractional 1099 * firing ratio for each actor that is connected to the given port. 1100 * If we have not previously computed the ratio for an 1101 * actor, then store the value in the given map of firing ratios and move 1102 * the actor from the remainingActors list to the pendingActors list. 1103 * If the value has been previously computed and is not the same, 1104 * then the model is not schedulable and an exception will be thrown. 1105 * Note that ports directly contained by the given container are 1106 * handled slightly differently from other ports. Most importantly, 1107 * their rates are propagated to ports they are connected to on the 1108 * inside, as opposed to ports they are connected to on the outside. 1109 * 1110 * @param container The actor that is being scheduled. 1111 * @param currentPort The port that we are propagating from. 1112 * @param entityToFiringsPerIteration The current Map of 1113 * fractional firing ratios for each actor. This map will be 1114 * updated if the ratio for any actor has not been previously 1115 * computed. 1116 * @param externalRates A map from external ports of container to 1117 * the fractional rates of that port. This will be updated 1118 * during this method. 1119 * @param remainingActors The set of actors that have not had their 1120 * fractional firing set. This will be updated during this method. 1121 * @param pendingActors The set of actors that have had their rate 1122 * set, but have not been propagated onwards. This will be updated 1123 * during this method. 1124 * @param clusteredActors The set of actors that are within one 1125 * cluster, i.e., they are connected. 1126 * @param clusteredExternalPorts The set of external ports that 1127 * are connected with the same cluster of actors. 1128 * 1129 * @exception NotSchedulableException If the model is not 1130 * schedulable. 1131 * @exception IllegalActionException If the expression for a 1132 * rate parameter is not valid. 1133 */ 1134 @SuppressWarnings("unused") 1135 private void _propagatePort(CompositeActor container, IOPort currentPort, 1136 Map entityToFiringsPerIteration, Map externalRates, 1137 LinkedList remainingActors, LinkedList pendingActors, 1138 Set clusteredActors, Set clusteredExternalPorts) 1139 throws NotSchedulableException, IllegalActionException { 1140 ComponentEntity currentActor = (ComponentEntity) currentPort 1141 .getContainer(); 1142 1143 // First check to make sure that this port is not connected to 1144 // any other output ports on the outside. 1145 // This results in a non-deterministic merge and is illegal. 1146 // Do not do this test for output ports where we are propagating 1147 // inwards instead of outwards. 1148 if (currentPort.isOutput() && currentPort.getContainer() != container) { 1149 Iterator connectedPorts = currentPort.deepConnectedPortList() 1150 .iterator(); 1151 1152 // Make sure any connected output ports are connected on 1153 // the inside. 1154 while (connectedPorts.hasNext()) { 1155 IOPort connectedPort = (IOPort) connectedPorts.next(); 1156 1157 // connectedPort might be connected on the inside to the 1158 // currentPort, which is legal. The container argument 1159 // is always the container of the director, so any port 1160 // that has that container must be connected on the inside. 1161 if (connectedPort.isOutput() 1162 && connectedPort.getContainer() != container) { 1163 throw new NotSchedulableException(currentPort, 1164 connectedPort, 1165 "Output ports drive the same relation. " 1166 + "This is not legal in SDF."); 1167 } else if (connectedPort.isInput() 1168 && connectedPort.getContainer() == container) { 1169 throw new NotSchedulableException(currentPort, 1170 connectedPort, 1171 "Output port drives the same relation " 1172 + "as the external input port. " 1173 + "This is not legal in SDF."); 1174 } 1175 } 1176 } 1177 1178 // Next check to make sure that if this port is an external 1179 // input port, then it does not drive the same relation as some 1180 // other output port or some other external input port. 1181 // This results in a non-deterministic merge and is illegal. 1182 if (currentPort.isInput() && currentPort.getContainer() == container) { 1183 Iterator connectedPorts = currentPort.deepInsidePortList() 1184 .iterator(); 1185 1186 // Make sure any connected output ports are connected on 1187 // the inside. 1188 while (connectedPorts.hasNext()) { 1189 IOPort connectedPort = (IOPort) connectedPorts.next(); 1190 1191 // connectPort might be connected on the inside to the 1192 // currentPort, which is legal. The container argument 1193 // is always the container of the director, so any port 1194 // that has that container must be connected on the inside. 1195 if (connectedPort.isOutput() 1196 && connectedPort.getContainer() != container) { 1197 throw new NotSchedulableException(currentPort, 1198 connectedPort, 1199 "External input port drive the same relation " 1200 + "as an output port. " 1201 + "This is not legal in SDF."); 1202 } else if (connectedPort.isInput() 1203 && connectedPort.getContainer() == container) { 1204 throw new NotSchedulableException(currentPort, 1205 connectedPort, 1206 "External input port drives the same relation " 1207 + "as another external input port. " 1208 + "This is not legal in SDF."); 1209 } 1210 } 1211 } 1212 1213 Director director = (Director) getContainer(); 1214 CompositeActor model = (CompositeActor) director.getContainer(); 1215 1216 // Get the rate of this port. 1217 int currentRate; 1218 1219 if (currentActor == model) { 1220 currentRate = 1; 1221 } else { 1222 currentRate = DFUtilities.getRate(currentPort); 1223 } 1224 1225 // Port rates of less than zero are not valid. 1226 if (currentRate < 0) { 1227 throw new NotSchedulableException(currentPort, 1228 "Rate cannot be less than zero. It was: " + currentRate); 1229 } 1230 1231 // Propagate to anything that this port is connected to. For 1232 // external ports, this is anything that is connected on the 1233 // inside. For ports of actors that are being scheduled, this is 1234 // anything that is connected on the outside. 1235 Iterator connectedPorts; 1236 1237 if (currentPort.getContainer() == container) { 1238 // Find all the ports that are deeply connected to 1239 // current port on the inside. 1240 1241 if (_debugging && VERBOSE) { 1242 // Move this inside and avoid FindBugs Dead Local Store 1243 connectedPorts = currentPort.deepInsidePortList().iterator(); 1244 _debug("deepInsidePortList of " + currentPort); 1245 1246 while (connectedPorts.hasNext()) { 1247 _debug(connectedPorts.next().toString()); 1248 } 1249 } 1250 1251 connectedPorts = currentPort.deepInsidePortList().iterator(); 1252 } else { 1253 connectedPorts = currentPort.deepConnectedPortList().iterator(); 1254 } 1255 1256 // For every port we are connected to. 1257 while (connectedPorts.hasNext()) { 1258 IOPort connectedPort = (IOPort) connectedPorts.next(); 1259 1260 ComponentEntity connectedActor = (ComponentEntity) connectedPort 1261 .getContainer(); 1262 1263 if (_debugging && VERBOSE) { 1264 _debug("Propagating " + currentPort + " to " 1265 + connectedActor.getName()); 1266 } 1267 1268 int connectedRate; 1269 1270 if (connectedActor == model) { 1271 connectedRate = 1; 1272 } else { 1273 connectedRate = DFUtilities.getRate(connectedPort); 1274 } 1275 1276 // currentFiring is the firing ratio that we've already 1277 // calculated for currentActor 1278 Fraction currentFiring = (Fraction) entityToFiringsPerIteration 1279 .get(currentActor); 1280 1281 // Compute the firing ratio that we think connected actor 1282 // should have, based on its connection to currentActor 1283 Fraction desiredFiring; 1284 1285 // HDF actors might have zero rates... 1286 if (currentRate == 0 && connectedRate > 0) { 1287 // The current port of the current actor has a rate 1288 // of 0, and the current connected port of the 1289 // connected actor has a positive integer rate. 1290 // therefore, we must set the firing count of 1291 // the connected actor to 0 so that it will 1292 // not appear in the final static schedule. 1293 desiredFiring = Fraction.ZERO; 1294 } else if (currentRate > 0 && connectedRate == 0) { 1295 // The current port of the current actor has a 1296 // positive integer rate, and the current 1297 // connected port of the connected actor has 1298 // rate of 0. therefore, we set the firing 1299 // count of the current actor to 0 so that 1300 // it will not appear in the final static schedule. 1301 currentFiring = Fraction.ZERO; 1302 1303 // Update the entry in the firing table. 1304 entityToFiringsPerIteration.put(currentActor, currentFiring); 1305 1306 // Set the firing count of the connected actor to 1307 // be 1. 1308 desiredFiring = new Fraction(1); 1309 } else if (currentRate == 0 && connectedRate == 0) { 1310 // Give the connected actor the same rate as the 1311 // current actor. 1312 desiredFiring = currentFiring; 1313 } else { 1314 // Both the rates are non zero, so we can just do the 1315 // regular actor propagation. 1316 desiredFiring = currentFiring 1317 .multiply(new Fraction(currentRate, connectedRate)); 1318 } 1319 1320 // Now, compare the firing ratio that was computed before 1321 // with what we just determined. 1322 // This should be either 1323 // the firing that we computed previously, or null 1324 // if the port is an external port, or _minusOne if 1325 // we have not computed the firing ratio for this actor yet. 1326 Fraction presentFiring = (Fraction) entityToFiringsPerIteration 1327 .get(connectedActor); 1328 1329 if (_debugging && VERBOSE) { 1330 _debug("presentFiring of connectedActor " + connectedActor 1331 + " = " + presentFiring); 1332 } 1333 1334 // if (presentFiring == null) { 1335 // Make sure to check for presentFiring == null here so that 1336 // we avoid a NullPointerException if the model is ill formed. 1337 // I had problems here with a bug in Publisher.clone() and 1338 // Subscriber.clone() where presentFiring was null. 1339 // Getting a NullPointerException is bad, we should check 1340 // for null and try to give a better message. 1341 if (connectedActor == model || presentFiring == null) { 1342 // We've gotten out to an external port. 1343 // Temporarily create the entry in the firing table. 1344 // This is possibly rather fragile. 1345 entityToFiringsPerIteration.put(connectedActor, desiredFiring); 1346 1347 // Compute the external rate for this port. 1348 Fraction rate = currentFiring 1349 .multiply(new Fraction(currentRate, 1)); 1350 Fraction previousRate = (Fraction) externalRates 1351 .get(connectedPort); 1352 1353 if (previousRate == null) { 1354 // This can happen if we somehow have a link to a port 1355 // within a class definition. 1356 // Give better error message than null pointer exception. 1357 throw new InternalErrorException( 1358 "Invalid connection found between ports " 1359 + currentPort.getFullName() + " and " 1360 + connectedPort.getFullName() 1361 + ". The rate of the " 1362 + connectedPort.getFullName() 1363 + " was not found in the map from external ports of the container" 1364 + " to the fractional rates of that port, or is null. " 1365 + " Perhaps there is a link to a port within a class " 1366 + "definition? The container of " 1367 + currentPort.getFullName() 1368 + (((Entity) currentPort.getContainer()) 1369 .isWithinClassDefinition() ? " is" 1370 : " is not") 1371 + " within an actor oriented class definition. " 1372 + "The container of " 1373 + connectedPort.getFullName() 1374 + (((Entity) connectedPort.getContainer()) 1375 .isWithinClassDefinition() ? " is" 1376 : " is not") 1377 + " within an actor oriented class definition."); 1378 1379 } 1380 1381 //if (previousRate.equals(Fraction.ZERO)) { 1382 if (!clusteredExternalPorts.contains(connectedPort)) { 1383 clusteredExternalPorts.add(connectedPort); 1384 externalRates.put(connectedPort, rate); 1385 1386 _propagatePort(container, connectedPort, 1387 entityToFiringsPerIteration, externalRates, 1388 remainingActors, pendingActors, clusteredActors, 1389 clusteredExternalPorts); 1390 } else if (!rate.equals(previousRate)) { 1391 // The rates don't match. 1392 throw new NotSchedulableException(this, 1393 "No solution " 1394 + "exists for the balance equations.\n" 1395 + "Graph is not " 1396 + "consistent under the SDF domain " 1397 + "detected on external port " 1398 + connectedPort.getFullName()); 1399 } 1400 1401 // _propagatePort(container, connectedPort, 1402 // entityToFiringsPerIteration, externalRates, 1403 // remainingActors, pendingActors, clusteredActors, 1404 // clusteredExternalPorts); 1405 // entityToFiringsPerIteration.remove(connectedActor); 1406 } else if (presentFiring.equals(_minusOne)) { 1407 // So we are propagating here for the first time. 1408 // Create the entry in the firing table. 1409 entityToFiringsPerIteration.put(connectedActor, desiredFiring); 1410 1411 // Remove them from remainingActors. 1412 remainingActors.remove(connectedActor); 1413 clusteredActors.add(connectedActor); 1414 1415 // and add them to the pendingActors. 1416 pendingActors.addLast(connectedActor); 1417 } else if (!presentFiring.equals(desiredFiring)) { 1418 // So we've already propagated here, but the 1419 // firingsPerIteration don't match. 1420 throw new NotSchedulableException(this, 1421 "No solution " + "exists for the balance equations.\n" 1422 + "Graph is not " 1423 + "consistent under the SDF domain " 1424 + "detected on external port " 1425 + connectedPort.getFullName()); 1426 } 1427 1428 if (_debugging && VERBOSE) { 1429 _debug("New Firing: "); 1430 _debug(entityToFiringsPerIteration.toString()); 1431 } 1432 } 1433 } 1434 1435 /** Create a schedule for a set of actors. Given a valid 1436 * firing vector, simulate the scheduling of the actors until the 1437 * end of one synchronous dataflow iteration. 1438 * Each actor will appear in the schedule exactly the number of times that 1439 * minimally solves the balance equations and in an order where each 1440 * actor has sufficient tokens on its inputs to fire. Note that no 1441 * claim is made that this is an optimal solution in any other sense. 1442 * 1443 * @param externalRates Map from external port to an Integer 1444 * representing the number of tokens produced or consumed from 1445 * that port during the course of an iteration. 1446 * @param actorList The actors that need to be scheduled. 1447 * @param container The container. 1448 * @return An instance of the Schedule class, indicating the order 1449 * in which actors should fire. 1450 * @exception NotSchedulableException If the algorithm encounters an SDF 1451 * graph that is not consistent with the firing vector, or detects an 1452 * inconsistent internal state, or detects a graph that cannot be 1453 * scheduled. 1454 */ 1455 @SuppressWarnings("unused") 1456 private Schedule _scheduleConnectedActors(Map externalRates, List actorList, 1457 CompositeActor container) throws NotSchedulableException { 1458 // A linked list containing all the actors that have no inputs. 1459 LinkedList readyToScheduleActorList = new LinkedList(); 1460 1461 Schedule newSchedule = new Schedule(); 1462 1463 // An association between each actor and the number of firings 1464 // for that actor that remain to be simulated. 1465 // NOTE: This used to be a TreeMap using DFUtilities.NamedObjComparator(). 1466 // However, that comparator is very slow. 1467 // Map firingsRemainingVector = new TreeMap( 1468 // new DFUtilities.NamedObjComparator()); 1469 Map firingsRemainingVector = new HashMap(); 1470 1471 // Initialized the firingsRemainingVector to the current 1472 // firing vector. 1473 firingsRemainingVector.putAll(_firingVector); 1474 1475 // A list of all that actors that we have not yet completely scheduled. 1476 // FIXME: Is this list needed? 1477 LinkedList unscheduledActorList = new LinkedList(); 1478 unscheduledActorList.addAll(actorList); 1479 1480 try { 1481 1482 // Initializing waitingTokens at all the input ports of actors and 1483 // output ports of the model to zero is not necessary because 1484 // SDFReceiver.clear() does it. 1485 1486 // The above statement seems incorrect to me. The only place where 1487 // SDFReceiver.clear() is called is during initialization. 1488 // For models that need to recompute the schedule during the 1489 // execution, _waitingTokens is not cleared to zero, because 1490 // initialize() is called only once at the beginning of the 1491 // execution. Plus, in code generation, initialize() is never 1492 // called. so I'm adding code to clear the _waitingTokes to zero 1493 // here: 1494 // --Gang Zhou 1495 Iterator actorsIterator = actorList.iterator(); 1496 while (actorsIterator.hasNext()) { 1497 Actor actor = (Actor) actorsIterator.next(); 1498 Iterator inputPorts = actor.inputPortList().iterator(); 1499 while (inputPorts.hasNext()) { 1500 IOPort inputPort = (IOPort) inputPorts.next(); 1501 Receiver[][] receivers = inputPort.getReceivers(); 1502 if (receivers != null) { 1503 for (int m = 0; m < receivers.length; m++) { 1504 for (int n = 0; n < receivers[m].length; n++) { 1505 Receiver r = receivers[m][n]; 1506 while (r instanceof IntermediateReceiver) { 1507 r = ((IntermediateReceiver) r)._receiver; 1508 } 1509 ((SDFReceiver) r)._waitingTokens = 0; 1510 } 1511 } 1512 } 1513 } 1514 } 1515 Iterator externalOutputPorts = container.outputPortList() 1516 .iterator(); 1517 while (externalOutputPorts.hasNext()) { 1518 IOPort outputPort = (IOPort) externalOutputPorts.next(); 1519 Receiver[][] receivers = outputPort.getInsideReceivers(); 1520 if (receivers != null) { 1521 for (int m = 0; m < receivers.length; m++) { 1522 for (int n = 0; n < receivers[m].length; n++) { 1523 Receiver r = receivers[m][n]; 1524 while (r instanceof IntermediateReceiver) { 1525 r = ((IntermediateReceiver) r)._receiver; 1526 } 1527 ((SDFReceiver) r)._waitingTokens = 0; 1528 } 1529 } 1530 } 1531 } 1532 1533 // Simulate the creation of initialization tokens (delays). 1534 // Fill readyToScheduleActorList with all the actors that have 1535 // no unfulfilled input ports, and are thus ready to fire. 1536 // This includes actors with no input ports and those 1537 // whose input ports have consumption rates of zero. 1538 Iterator actors = actorList.iterator(); 1539 1540 while (actors.hasNext()) { 1541 Actor actor = (Actor) actors.next(); 1542 int firingsRemaining = ((Integer) firingsRemainingVector 1543 .get(actor)).intValue(); 1544 1545 if (firingsRemaining == 0) { 1546 unscheduledActorList.remove(actor); 1547 continue; 1548 } 1549 1550 int inputCount = _countUnfulfilledInputs(actor, actorList, 1551 true); 1552 1553 if (inputCount == 0) { 1554 readyToScheduleActorList.addFirst(actor); 1555 } 1556 1557 if (_debugging && VERBOSE) { 1558 _debug("Actor " + ((ComponentEntity) actor).getName() 1559 + " has " + inputCount + " unfulfilledInputs."); 1560 } 1561 } 1562 1563 // Simulate production of initial tokens. 1564 actors = actorList.iterator(); 1565 1566 while (actors.hasNext()) { 1567 Actor actor = (Actor) actors.next(); 1568 Iterator outputPorts = actor.outputPortList().iterator(); 1569 1570 while (outputPorts.hasNext()) { 1571 IOPort outputPort = (IOPort) outputPorts.next(); 1572 int count = DFUtilities.getTokenInitProduction(outputPort); 1573 1574 if (_debugging && VERBOSE) { 1575 _debug("Simulating " + count 1576 + " initial tokens created on " + outputPort); 1577 } 1578 1579 if (count > 0) { 1580 _simulateTokensCreated(outputPort, count, actorList, 1581 readyToScheduleActorList); 1582 } 1583 } 1584 1585 // Also simulate initially available tokens on input ports. 1586 Iterator inputPorts = actor.inputPortList().iterator(); 1587 1588 while (inputPorts.hasNext()) { 1589 IOPort inputPort = (IOPort) inputPorts.next(); 1590 int count = DFUtilities.getTokenInitConsumption(inputPort); 1591 1592 // If the port is an input port that sends initial tokens 1593 // to the inside, then those tokens are represented by 1594 // an init _production_ parameter. This needs to be taken 1595 // into account too. 1596 count += DFUtilities.getTokenInitProduction(inputPort); 1597 1598 if (_debugging && VERBOSE) { 1599 _debug("Simulating " + count 1600 + " initial tokens on input " + inputPort); 1601 } 1602 1603 if (count > 0) { 1604 _simulateInitialTokens(inputPort, count, actorList, 1605 readyToScheduleActorList); 1606 } 1607 } 1608 } 1609 1610 // Simulate a number of tokens initially present on each 1611 // external input port. 1612 for (Iterator inputPorts = container.inputPortList() 1613 .iterator(); inputPorts.hasNext();) { 1614 IOPort port = (IOPort) inputPorts.next(); 1615 int count = ((Integer) externalRates.get(port)).intValue(); 1616 1617 // The input port may have initial tokens (SubcriberPort supports this). 1618 count += DFUtilities.getTokenInitProduction(port); 1619 1620 if (count > 0) { 1621 _simulateExternalInputs(port, count, actorList, 1622 readyToScheduleActorList); 1623 } 1624 } 1625 1626 // Simulate a number of tokens initially present on each 1627 // external output port. An output port may have initial 1628 // tokens on its inside receiver ready to be transported 1629 // to the outside upon initialization. E.g., a publisher 1630 // port with initial tokens. 1631 for (Iterator outputPorts = container.outputPortList() 1632 .iterator(); outputPorts.hasNext();) { 1633 IOPort port = (IOPort) outputPorts.next(); 1634 int count = DFUtilities.getTokenInitProduction(port); 1635 if (count > 0) { 1636 _simulateInitialOutputTokens(port, count); 1637 } 1638 } 1639 1640 // While we have actors left, pick one that is ready and fire it. 1641 while (readyToScheduleActorList.size() > 0) { 1642 if (_debugging && VERBOSE) { 1643 _debug("Actors that can be scheduled:"); 1644 1645 for (Iterator readyActors = readyToScheduleActorList 1646 .iterator(); readyActors.hasNext();) { 1647 Entity readyActor = (Entity) readyActors.next(); 1648 _debug(readyActor.getFullName()); 1649 } 1650 1651 _debug("Actors with firings left:"); 1652 1653 for (Iterator remainingActors = unscheduledActorList 1654 .iterator(); remainingActors.hasNext();) { 1655 Entity remainingActor = (Entity) remainingActors.next(); 1656 _debug(remainingActor.getFullName()); 1657 } 1658 } 1659 1660 // Pick an actor that is ready to fire. 1661 Actor currentActor = (Actor) readyToScheduleActorList 1662 .getFirst(); 1663 1664 // Remove it from the list of actors we are waiting to fire. 1665 while (readyToScheduleActorList.remove(currentActor)) { 1666 } 1667 1668 // Determine the number of times currentActor can fire. 1669 int numberOfFirings = _computeMaximumFirings(currentActor); 1670 1671 // We should never schedule something more than the number 1672 // of times expected by the balance equations. This might 1673 // happen because we assume an infinite number of tokens 1674 // are waiting on external ports. 1675 int firingsRemaining = ((Integer) firingsRemainingVector 1676 .get(currentActor)).intValue(); 1677 1678 if (numberOfFirings > firingsRemaining) { 1679 numberOfFirings = firingsRemaining; 1680 } 1681 1682 if (_debugging && VERBOSE) { 1683 _debug("Scheduling actor " + currentActor.getName() + " " 1684 + numberOfFirings + " times."); 1685 } 1686 1687 // Update the firingsRemainingVector for this actor. 1688 firingsRemaining -= numberOfFirings; 1689 firingsRemainingVector.put(currentActor, 1690 Integer.valueOf(firingsRemaining)); 1691 1692 if (_debugging && VERBOSE) { 1693 _debug(currentActor.getName() + " should fire " 1694 + firingsRemaining + " more times."); 1695 } 1696 1697 // Simulate the tokens that are consumed by the actors 1698 // input ports. 1699 _simulateInputConsumption(currentActor, numberOfFirings); 1700 1701 // Add it to the schedule numberOfFirings times. 1702 Firing firing = new Firing(); 1703 firing.setActor(currentActor); 1704 firing.setIterationCount(numberOfFirings); 1705 newSchedule.add(firing); 1706 1707 // Get all its outputPorts 1708 // and simulate the proper production of tokens. 1709 for (Iterator outputPorts = currentActor.outputPortList() 1710 .iterator(); outputPorts.hasNext();) { 1711 IOPort outputPort = (IOPort) outputPorts.next(); 1712 1713 int count = DFUtilities.getTokenProductionRate(outputPort); 1714 1715 _simulateTokensCreated(outputPort, count * numberOfFirings, 1716 unscheduledActorList, readyToScheduleActorList); 1717 } 1718 1719 // Figure out what to do with the actor, now that it has been 1720 // scheduled. 1721 if (firingsRemaining < 0) { 1722 // If we screwed up somewhere, and fired this more 1723 // times than we thought we should have 1724 // then throw an exception. 1725 // This should never happen. 1726 throw new InternalErrorException("Balance Equation " 1727 + "solution does not agree with " 1728 + "scheduling algorithm!"); 1729 } 1730 1731 if (firingsRemaining == 0) { 1732 // If we've fired this actor all the 1733 // times that it should, then 1734 // we get rid of it entirely. 1735 if (_debugging && VERBOSE) { 1736 _debug("Actor = " + currentActor + " is done firing."); 1737 } 1738 1739 // Remove the actor from the unscheduledActorList 1740 // since we don't need to fire it any more. 1741 while (unscheduledActorList.remove(currentActor)) { 1742 ; 1743 } 1744 1745 if (_debugging && VERBOSE) { 1746 _debug("Remaining actors:"); 1747 1748 for (Iterator readyActors = readyToScheduleActorList 1749 .iterator(); readyActors.hasNext();) { 1750 Entity entity = (Entity) readyActors.next(); 1751 _debug(entity.getFullName()); 1752 } 1753 } 1754 } else { 1755 // Otherwise the actor still has firings left. 1756 // Count the number of unfulfilled inputs. 1757 int inputCount = _countUnfulfilledInputs(currentActor, 1758 unscheduledActorList, false); 1759 1760 // We've already removed currentActor from 1761 // readyToSchedule actors, and presumably 1762 // fired it until it can be fired no more. 1763 // This check is here for robustness... 1764 // if the actor can still be scheduled 1765 // i.e. all its inputs are satisfied, and it 1766 // appears in the unscheduled actors list 1767 // then put it on the readyToScheduleActorList. 1768 if (inputCount <= 0 1769 && unscheduledActorList.contains(currentActor)) { 1770 readyToScheduleActorList.addFirst(currentActor); 1771 } 1772 } 1773 } 1774 } catch (IllegalActionException ex) { 1775 // This could happen if we call getTokenConsumptionRate on a 1776 // port that isn't a part of the actor. This probably means 1777 // the graph is screwed up, or somebody else is mucking 1778 // with it. 1779 throw new InternalErrorException(this, ex, 1780 "SDF Scheduler Failed internal consistency check."); 1781 } 1782 1783 // If there are any actors left when we're done, then report the 1784 // error. 1785 if (unscheduledActorList.size() > 0) { 1786 StringBuffer message = new StringBuffer( 1787 "Actors remain that cannot be scheduled!\n" 1788 + "\nThere are several possible reasons:\n" 1789 + "* SDF Graphs with feedback loops should have an actor " 1790 + "with a delay in the loop, such as a SampleDelay.\n" 1791 + "* The SDF director has an \"allowDisconnectedGraphs\"" 1792 + "parameter, which, when true, permits disconnected " 1793 + "SDF graphs.\n" 1794 + "* The token consumption rate and production rates might " 1795 + "be mismatched.\nUsually, actors produce one token or consume " 1796 + "one token on a port. To produce or consume multiple tokens " 1797 + "per firing, add a \"tokenConsumptionRate\" or " 1798 + "\"tokenProductionRate\" parameter to the appropriate port.\n" 1799 + "Unscheduled actors:\n"); 1800 1801 // Only display the first 100 connected or disconnected actors. 1802 int count = 0; 1803 for (Iterator actors = unscheduledActorList.iterator(); actors 1804 .hasNext() && count < 100; count++) { 1805 Entity entity = (Entity) actors.next(); 1806 message.append(entity.getFullName() + " "); 1807 } 1808 1809 if (count >= 99) { 1810 message.append("..."); 1811 } 1812 message.append("\nScheduled actors:\n"); 1813 List scheduledActorList = new LinkedList(); 1814 scheduledActorList.addAll(actorList); 1815 scheduledActorList.removeAll(unscheduledActorList); 1816 1817 count = 0; 1818 1819 for (Iterator actors = scheduledActorList.iterator(); actors 1820 .hasNext() && count < 100; count++) { 1821 Entity entity = (Entity) actors.next(); 1822 message.append(entity.getFullName() + " "); 1823 } 1824 1825 if (count >= 99) { 1826 message.append("..."); 1827 } 1828 throw new NotSchedulableException(this, message.toString()); 1829 } 1830 1831 if (_debugging) { 1832 _debug("Schedule is:"); 1833 _debug(newSchedule.toString()); 1834 } 1835 1836 return newSchedule; 1837 } 1838 1839 /** Simulate the consumption of tokens from the given external input 1840 * port. This assumes the input ports have the number of tokens given 1841 * by their rate. 1842 * @param port The external input port. 1843 * @param count The number of tokens assumed to be on that port. 1844 * @param actorList The list of actors. 1845 * @param readyToScheduleActorList The list of actors that are ready 1846 * to be scheduled. This will be updated if any actors that receive 1847 * tokens from outputPort are now ready to fire. 1848 * @exception IllegalActionException If thrown while reading a token, 1849 * setting the capacity of a receiver or counting unfulfilled input.s 1850 */ 1851 @SuppressWarnings("unused") 1852 protected void _simulateExternalInputs(IOPort port, int count, 1853 List actorList, LinkedList readyToScheduleActorList) 1854 throws IllegalActionException { 1855 Receiver[][] receivers = port.deepGetReceivers(); 1856 1857 if (_debugging && VERBOSE) { 1858 _debug("Simulating external input tokens from " 1859 + port.getFullName()); 1860 _debug("number of inside channels = " + receivers.length); 1861 } 1862 1863 for (int channel = 0; channel < receivers.length; channel++) { 1864 if (receivers[channel] == null) { 1865 continue; 1866 } 1867 1868 for (int copy = 0; copy < receivers[channel].length; copy++) { 1869 if (!(receivers[channel][copy] instanceof SDFReceiver)) { 1870 // This should only occur if it is null. 1871 continue; 1872 } 1873 1874 SDFReceiver receiver = (SDFReceiver) receivers[channel][copy]; 1875 IOPort connectedPort = receivers[channel][copy].getContainer(); 1876 ComponentEntity connectedActor = (ComponentEntity) connectedPort 1877 .getContainer(); 1878 1879 // If the connected port has an initial tokens for consumption, 1880 // those need to be added to the count. 1881 count += DFUtilities.getTokenInitConsumption(connectedPort); 1882 1883 receiver._waitingTokens = count; 1884 1885 // Update the buffer size, if necessary. 1886 boolean enforce = ((BooleanToken) constrainBufferSizes 1887 .getToken()).booleanValue(); 1888 1889 if (enforce) { 1890 int capacity = receiver.getCapacity(); 1891 1892 if (capacity == SDFReceiver.INFINITE_CAPACITY 1893 || receiver._waitingTokens > capacity) { 1894 receiver.setCapacity(count); 1895 } 1896 } 1897 1898 // Determine whether the connectedActor can now be scheduled. 1899 // Only proceed if the connected actor is something we are 1900 // scheduling. The most notable time when this will not be 1901 // true is when a connection is made to the inside of an opaque port. 1902 if (actorList.contains(connectedActor)) { 1903 int inputCount = _countUnfulfilledInputs( 1904 (Actor) connectedActor, actorList, false); 1905 int firingsRemaining = _getFiringCount(connectedActor); 1906 1907 // If so, then add it to the proper list. 1908 // Note that the actor may appear more than once. 1909 // This is OK, since we remove all of the appearances from 1910 // the list when the actor is actually scheduled. 1911 if (inputCount < 1 && firingsRemaining > 0) { 1912 // Ned Stoffel suggested changing this from 1913 // addLast() to addFirst() so as to minimize 1914 // the number of tokens in transit. "This leads 1915 // to a markedly more serial schedule, as can 1916 // be demonstrated by animating the simulations" 1917 readyToScheduleActorList.addFirst(connectedActor); 1918 } 1919 } 1920 } 1921 } 1922 } 1923 1924 /** Simulate the consumption of tokens by the actor during execution of 1925 * the given number of firings. Also determine whether enough tokens 1926 * still remain at the inputs of the actor for it to fire again immediately. 1927 * @param currentActor The actor to be fired. 1928 * @param firingCount The number of firings. 1929 * @return true If the actor can fire again. 1930 * @exception IllegalActionException If the rate parameters are invalid. 1931 */ 1932 protected boolean _simulateInputConsumption(Actor currentActor, 1933 int firingCount) throws IllegalActionException { 1934 boolean stillReadyToSchedule = true; 1935 1936 // Update the number of tokens waiting on the actor's input ports. 1937 Iterator inputPorts = currentActor.inputPortList().iterator(); 1938 1939 while (inputPorts.hasNext()) { 1940 IOPort inputPort = (IOPort) inputPorts.next(); 1941 int tokenRate = DFUtilities.getTokenConsumptionRate(inputPort); 1942 1943 Receiver[][] receivers = inputPort.getReceivers(); 1944 1945 for (int channel = 0; channel < receivers.length; channel++) { 1946 if (receivers[channel] == null) { 1947 continue; 1948 } 1949 1950 for (int copy = 0; copy < receivers[channel].length; copy++) { 1951 if (!(receivers[channel][copy] instanceof SDFReceiver)) { 1952 // This should only occur if it is null. 1953 continue; 1954 } 1955 1956 SDFReceiver receiver = (SDFReceiver) receivers[channel][copy]; 1957 receiver._waitingTokens -= tokenRate * firingCount; 1958 1959 if (receiver._waitingTokens < tokenRate) { 1960 stillReadyToSchedule = false; 1961 } 1962 } 1963 } 1964 } 1965 1966 return stillReadyToSchedule; 1967 } 1968 1969 /** Simulate the creation of tokens by the given output port when 1970 * its actor fires. If any actors that receive tokens are then ready to 1971 * fire, given that only actors in the actor list are being scheduled, then 1972 * add those actors to the list of actors that are ready to schedule. 1973 * @param outputPort The port that is creating the tokens. 1974 * @param createdTokens The number of tokens to create. 1975 * @param actorList The list of actors that are being scheduled. 1976 * @param readyToScheduleActorList The list of actors that are ready 1977 * to be scheduled. This will be updated if any actors that receive 1978 * tokens from outputPort are now ready to fire. 1979 */ 1980 @SuppressWarnings("unused") 1981 private void _simulateTokensCreated(IOPort outputPort, int createdTokens, 1982 List actorList, LinkedList readyToScheduleActorList) 1983 throws IllegalActionException { 1984 // FIXME: Why are the actor lists lists rather than sets? 1985 Receiver[][] receivers = outputPort.getRemoteReceivers(); 1986 1987 if (_debugging && VERBOSE) { 1988 _debug("Creating " + createdTokens + " tokens on " 1989 + outputPort.getFullName()); 1990 _debug("source channels = " + receivers.length); 1991 _debug("width = " + outputPort.getWidth()); 1992 } 1993 1994 for (int channel = 0; channel < receivers.length; channel++) { 1995 if (receivers[channel] == null) { 1996 continue; 1997 } 1998 1999 for (int copy = 0; copy < receivers[channel].length; copy++) { 2000 if (!(receivers[channel][copy] instanceof SDFReceiver)) { 2001 // NOTE: This should only occur if it is null. 2002 continue; 2003 } 2004 2005 SDFReceiver receiver = (SDFReceiver) receivers[channel][copy]; 2006 IOPort connectedPort = receivers[channel][copy].getContainer(); 2007 ComponentEntity connectedActor = (ComponentEntity) connectedPort 2008 .getContainer(); 2009 2010 // Increment the number of waiting tokens. 2011 receiver._waitingTokens += createdTokens; 2012 2013 // Update the buffer size, if necessary. 2014 boolean enforce = ((BooleanToken) constrainBufferSizes 2015 .getToken()).booleanValue(); 2016 2017 if (enforce) { 2018 int capacity = receiver.getCapacity(); 2019 2020 if (capacity == SDFReceiver.INFINITE_CAPACITY 2021 || receiver._waitingTokens > capacity) { 2022 receiver.setCapacity(receiver._waitingTokens); 2023 } 2024 } 2025 2026 // Only proceed if the connected actor is 2027 // something we are scheduling. 2028 // The most notable time when this will not be 2029 // true is when a connection is made to the 2030 // inside of an opaque port. 2031 if (actorList.contains(connectedActor)) { 2032 // Check and see whether the connectedActor 2033 // can be scheduled. 2034 int inputCount = _countUnfulfilledInputs( 2035 (Actor) connectedActor, actorList, false); 2036 int firingsRemaining = _getFiringCount(connectedActor); 2037 2038 // If so, then add it to the proper list. 2039 // Note that the actor may appear more than once. 2040 // This is OK, since we remove all of the appearances from 2041 // the list when the actor is actually scheduled. 2042 if (inputCount < 1 && firingsRemaining > 0) { 2043 // Ned Stoffel suggested changing this from 2044 // addLast() to addFirst() so as to minimize 2045 // the number of tokens in transit. "This leads 2046 // to a markedly more serial schedule, as can 2047 // be demonstrated by animating the simulations" 2048 readyToScheduleActorList.addFirst(connectedActor); 2049 } 2050 } 2051 } 2052 } 2053 } 2054 2055 /** Simulate the availability of initial tokens on the given input port when 2056 * its actor first fires. If its actors becomes ready to fire with these 2057 * initial tokens, given that only actors in the actor list are being scheduled, then 2058 * add its actors to the list of actors that are ready to schedule. 2059 * @param inputPort The port that will have initial tokens. 2060 * @param initialTokens The number of initial tokens. 2061 * @param actorList The list of actors that are being scheduled. 2062 * @param readyToScheduleActorList The list of actors that are ready 2063 * to be scheduled. This will be updated if the actor of this input 2064 * port becomes ready to fire. 2065 */ 2066 @SuppressWarnings("unused") 2067 private void _simulateInitialTokens(IOPort inputPort, int initialTokens, 2068 List actorList, LinkedList readyToScheduleActorList) 2069 throws IllegalActionException { 2070 Receiver[][] receivers = inputPort.getReceivers(); 2071 2072 if (_debugging && VERBOSE) { 2073 _debug("Initializing with " + initialTokens + " tokens on " 2074 + inputPort.getFullName()); 2075 _debug("input channels = " + receivers.length); 2076 _debug("width = " + inputPort.getWidth()); 2077 } 2078 2079 for (int channel = 0; channel < receivers.length; channel++) { 2080 if (receivers[channel] == null) { 2081 continue; 2082 } 2083 2084 for (int copy = 0; copy < receivers[channel].length; copy++) { 2085 if (!(receivers[channel][copy] instanceof SDFReceiver)) { 2086 // NOTE: This should only occur if it is null. 2087 continue; 2088 } 2089 2090 SDFReceiver receiver = (SDFReceiver) receivers[channel][copy]; 2091 IOPort itsPort = receivers[channel][copy].getContainer(); 2092 ComponentEntity itsActor = (ComponentEntity) itsPort 2093 .getContainer(); 2094 2095 // Increment the number of waiting tokens. 2096 receiver._waitingTokens += initialTokens; 2097 2098 // Update the buffer size, if necessary. 2099 boolean enforce = ((BooleanToken) constrainBufferSizes 2100 .getToken()).booleanValue(); 2101 2102 if (enforce) { 2103 int capacity = receiver.getCapacity(); 2104 2105 if (capacity == SDFReceiver.INFINITE_CAPACITY 2106 || receiver._waitingTokens > capacity) { 2107 receiver.setCapacity(receiver._waitingTokens); 2108 } 2109 } 2110 2111 // Only proceed if the connected actor is 2112 // something we are scheduling. 2113 // The most notable time when this will not be 2114 // true is when a connection is made to the 2115 // inside of an opaque port. 2116 if (actorList.contains(itsActor)) { 2117 // Check and see whether the connectedActor 2118 // can be scheduled. 2119 int inputCount = _countUnfulfilledInputs((Actor) itsActor, 2120 actorList, false); 2121 int firingsRemaining = _getFiringCount(itsActor); 2122 2123 // If so, then add it to the proper list. 2124 // Note that the actor may appear more than once. 2125 // This is OK, since we remove all of the appearances from 2126 // the list when the actor is actually scheduled. 2127 if (inputCount < 1 && firingsRemaining > 0) { 2128 // Ned Stoffel suggested changing this from 2129 // addLast() to addFirst() so as to minimize 2130 // the number of tokens in transit. "This leads 2131 // to a markedly more serial schedule, as can 2132 // be demonstrated by animating the simulations" 2133 readyToScheduleActorList.addFirst(itsActor); 2134 } 2135 } 2136 } 2137 } 2138 } 2139 2140 /** Simulate the availability of initial tokens on the inside 2141 * of the given output port. These initial tokens have no 2142 * effect on the schedule at this level of the hierarchy, 2143 * but they do affect the capacity of the inside receivers 2144 * on such ports. 2145 */ 2146 @SuppressWarnings("unused") 2147 private void _simulateInitialOutputTokens(IOPort outputPort, 2148 int initialTokens) throws IllegalActionException { 2149 Receiver[][] receivers = outputPort.getInsideReceivers(); 2150 2151 if (_debugging && VERBOSE) { 2152 _debug("Initializing with " + initialTokens 2153 + " tokens on the inside of " + outputPort.getFullName()); 2154 _debug(" input channels = " + receivers.length); 2155 _debug(" width = " + outputPort.getWidthInside()); 2156 } 2157 2158 for (int channel = 0; channel < receivers.length; channel++) { 2159 if (receivers[channel] == null) { 2160 continue; 2161 } 2162 2163 for (int copy = 0; copy < receivers[channel].length; copy++) { 2164 if (!(receivers[channel][copy] instanceof SDFReceiver)) { 2165 // NOTE: This should only occur if it is null. 2166 continue; 2167 } 2168 2169 SDFReceiver receiver = (SDFReceiver) receivers[channel][copy]; 2170 2171 // Increment the number of waiting tokens. 2172 receiver._waitingTokens += initialTokens; 2173 2174 // Update the buffer size, if necessary. 2175 boolean enforce = ((BooleanToken) constrainBufferSizes 2176 .getToken()).booleanValue(); 2177 2178 if (enforce) { 2179 int capacity = receiver.getCapacity(); 2180 2181 if (capacity == SDFReceiver.INFINITE_CAPACITY 2182 || receiver._waitingTokens > capacity) { 2183 receiver.setCapacity(receiver._waitingTokens); 2184 } 2185 } 2186 } 2187 } 2188 } 2189 2190 /////////////////////////////////////////////////////////////////// 2191 //// private variables //// 2192 2193 /** A map from actors to an integer representing the 2194 * number of times the actor will fire. 2195 */ 2196 protected Map _firingVector = new HashMap(); 2197 2198 /** A fraction equal to -1. Used in several places to indicate an 2199 * actor for which we have not determined the number of times it will 2200 * fire. 2201 */ 2202 private Fraction _minusOne = new Fraction(-1); 2203 2204 /** Mmaps from external 2205 * ports to the number of tokens that that port 2206 * will produce or consume in each firing. 2207 * It gets populated with the fractional production ratios 2208 * and is used in the end to set final rates on external ports. 2209 */ 2210 protected Map _externalRates = new HashMap(); 2211 2212 // NOTE: This used to be a TreeMap using DFUtilities.NamedObjComparator(). 2213 // However, that comparator is very slow. 2214 2215 //private Set _clusteredActors = new HashSet(); 2216 //private Set _clusteredExternalPorts = new HashSet(); 2217 2218 /** The list of rate variables that this scheduler is listening to 2219 * for rate changes. 2220 */ 2221 protected List _rateVariables = new LinkedList(); 2222}