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}