001/* Director for the synchronous dataflow model of computation.
002
003 Copyright (c) 1997-2015 The Regents of the University of California.
004 All rights reserved.
005 Permission is hereby granted, without written agreement and without
006 license or royalty fees, to use, copy, modify, and distribute this
007 software and its documentation for any purpose, provided that the above
008 copyright notice and the following two paragraphs appear in all copies
009 of this software.
010
011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023 PT_COPYRIGHT_VERSION_2
024 COPYRIGHTENDKEY
025
026 */
027package ptolemy.domains.sdf.kernel;
028
029import java.util.Iterator;
030
031import ptolemy.actor.Actor;
032import ptolemy.actor.CompositeActor;
033import ptolemy.actor.IOPort;
034import ptolemy.actor.NoTokenException;
035import ptolemy.actor.Receiver;
036import ptolemy.actor.TypedCompositeActor;
037import ptolemy.actor.parameters.ParameterPort;
038import ptolemy.actor.sched.NotSchedulableException;
039import ptolemy.actor.sched.Schedule;
040import ptolemy.actor.sched.StaticSchedulingDirector;
041import ptolemy.actor.util.DFUtilities;
042import ptolemy.actor.util.PeriodicDirector;
043import ptolemy.actor.util.PeriodicDirectorHelper;
044import ptolemy.actor.util.Time;
045import ptolemy.data.BooleanToken;
046import ptolemy.data.DoubleToken;
047import ptolemy.data.IntToken;
048import ptolemy.data.Token;
049import ptolemy.data.expr.Parameter;
050import ptolemy.data.type.BaseType;
051import ptolemy.kernel.CompositeEntity;
052import ptolemy.kernel.util.Attribute;
053import ptolemy.kernel.util.IllegalActionException;
054import ptolemy.kernel.util.InternalErrorException;
055import ptolemy.kernel.util.NameDuplicationException;
056import ptolemy.kernel.util.Settable;
057import ptolemy.kernel.util.Workspace;
058
059///////////////////////////////////////////////////////////////////
060//// SDFDirector
061
062/**
063 Director for the synchronous dataflow (SDF) model of computation.
064
065 <h1>SDF overview</h1>
066 The Synchronous Dataflow(SDF) domain supports the efficient
067 execution of Dataflow graphs that
068 lack control structures.   Dataflow graphs that contain control structures
069 should be executed using the Process Networks(PN) domain instead.
070 SDF allows efficient execution, with very little overhead at runtime.  It
071 requires that the rates on the ports of all actors be known before hand.
072 SDF also requires that the rates on the ports not change during
073 execution.  In addition, in some cases (namely systems with feedback) delays,
074 which are represented by initial tokens on relations must be explicitly
075 noted.  SDF uses this rate and delay information to determine
076 the execution sequence of the actors before execution begins.
077 <h2>Schedule Properties</h2>
078 <ul>
079 <li>The number of tokens accumulated on every relation is bounded, given
080 an infinite number of executions of the schedule.</li>
081 <li>Deadlock will never occur, given an infinite number of executions of
082 the schedule.</li>
083 </ul>
084 <h1>Class comments</h1>
085 An SDFDirector is the class that controls execution of actors under the
086 SDF domain.  By default, actor scheduling is handled by the SDFScheduler
087 class.  Furthermore, the newReceiver method creates Receivers of type
088 SDFReceiver, which extends QueueReceiver to support optimized gets
089 and puts of arrays of tokens.
090 <p>
091 Actors are assumed to consume and produce exactly one token per channel on
092 each firing.  Actors that do not follow this convention should set
093 the appropriate parameters on input and output ports to declare the number
094 of tokens they produce or consume.  See the
095 {@link ptolemy.domains.sdf.kernel.SDFScheduler} for more information.
096 The {@link ptolemy.domains.sdf.lib.SampleDelay} actor is usually used
097 in a model to specify the delay across a relation.
098 </p><p>
099 The <i>allowDisconnectedGraphs</i> parameter of this director determines
100 whether disconnected graphs are permitted.
101 A model may have two or more graphs of actors that
102 are not connected.  The schedule can jump from one graph to
103 another among the disconnected graphs. There is nothing to
104 force the scheduler to finish executing all actors on one
105 graph before firing actors on another graph. However, the
106 order of execution within an graph should be correct.
107 Usually, disconnected graphs in an SDF model indicates an
108 error.
109 The default value of the allowDisconnectedGraphs parameter is a
110 BooleanToken with the value false.
111 </p><p>
112 The <i>iterations</i> parameter of this director corresponds to a
113 limit on the number of times the director will fire its hierarchy
114 before it returns false in postfire.  If this number is not greater
115 than zero, then no limit is set and postfire will always return true.
116 The default value of the iterations parameter is an IntToken with value one.
117 </p><p>
118 If any actor's postfire() method returns false during an iteration,
119 then at the conclusion of the iteration, this director's postfire() method
120 will return false. This will normally result in termination of the execution.
121 The reasoning for this behavior is that the model cannot continue executing
122 without the participation of all actors, and if any actor returns false
123 in postfire(), then it is indicating that it wishes to not continue executing.
124 </p><p>
125 The <i>vectorizationFactor</i> parameter of this director sets the number
126 of times that the basic schedule is executed during each firing of this
127 director.  This might allow the director to execute the model more efficiently,
128 by combining multiple firings of each actor.  The default value of the
129 vectorizationFactor parameter is an IntToken with value one.
130 </p><p>
131 The SDF director has a <i>period</i> parameter which specifies the
132 amount of model time that elapses per iteration. If the value of
133 <i>period</i> is 0.0 (the default), then it has no effect, and
134 this director never increments time nor calls fireAt() on the
135 enclosing director. If the period is greater than 0.0, then
136 if this director is at the top level, it increments
137 time by this amount in each invocation of postfire().
138 If it is not at the top level, then it calls
139 fireAt(currentTime + period) in postfire().
140 </p><p>
141 This behavior gives an interesting use of SDF within DE:
142 You can "kick start" an SDF submodel with a single
143 event, and then if the director of that SDF submodel
144 has a period greater than 0.0, then it will continue to fire
145 periodically with the specified period.
146 </p><p>
147 If <i>period</i> is greater than 0.0 and the parameter
148 <i>synchronizeToRealTime</i> is set to <code>true</code>,
149 then the prefire() method stalls until the real time elapsed
150 since the model started matches the period multiplied by
151 the iteration count.
152 This ensures that the director does not get ahead of real time. However,
153 of course, this does not ensure that the director keeps up with real time.
154 </p>
155 @see ptolemy.domains.sdf.kernel.SDFScheduler
156 @see ptolemy.domains.sdf.kernel.SDFReceiver
157
158 @author Steve Neuendorffer, Contributor: Christopher Brooks
159 @version $Id$
160 @since Ptolemy II 0.2
161 @Pt.ProposedRating Yellow (cxh)
162 @Pt.AcceptedRating Yellow (cxh)
163 */
164public class SDFDirector extends StaticSchedulingDirector
165        implements PeriodicDirector {
166    /** Construct a director in the default workspace with an empty string
167     *  as its name. The director is added to the list of objects in
168     *  the workspace. Increment the version number of the workspace.
169     *
170     *  The SDFDirector will have a default scheduler of type SDFScheduler.
171     *  @exception IllegalActionException If the name has a period in it, or
172     *   the director is not compatible with the specified container.
173     *  @exception NameDuplicationException If the container already contains
174     *   an entity with the specified name.
175     */
176    public SDFDirector()
177            throws IllegalActionException, NameDuplicationException {
178        super();
179        _init();
180    }
181
182    /** Construct a director in the  workspace with an empty name.
183     *  The director is added to the list of objects in the workspace.
184     *  Increment the version number of the workspace.
185     *  The SDFDirector will have a default scheduler of type SDFScheduler.
186     *
187     *  @param workspace The workspace for this object.
188     *  @exception IllegalActionException If the name has a period in it, or
189     *   the director is not compatible with the specified container.
190     *  @exception NameDuplicationException If the container already contains
191     *   an entity with the specified name.
192     */
193    public SDFDirector(Workspace workspace)
194            throws IllegalActionException, NameDuplicationException {
195        super(workspace);
196        _init();
197    }
198
199    /** Construct a director in the given container with the given name.
200     *  The container argument must not be null, or a
201     *  NullPointerException will be thrown.
202     *  If the name argument is null, then the name is set to the
203     *  empty string. Increment the version number of the workspace.
204     *   The SDFDirector will have a default scheduler of type
205     *   SDFScheduler.
206     *
207     *  @param container Container of the director.
208     *  @param name Name of this director.
209     *  @exception IllegalActionException If the director is not compatible
210     *   with the specified container.  May be thrown in a derived class.
211     *  @exception NameDuplicationException If the container is not a
212     *   CompositeActor and the name collides with an entity in the container.
213     */
214    public SDFDirector(CompositeEntity container, String name)
215            throws IllegalActionException, NameDuplicationException {
216        super(container, name);
217        _init();
218    }
219
220    ///////////////////////////////////////////////////////////////////
221    ////                         public variables                  ////
222
223    ///////////////////////////////////////////////////////////////////
224    ////                         parameters                        ////
225
226    /** A parameter representing whether disconnected graphs are
227     *  permitted.  A model may have two or more graphs of actors that
228     *  are not connected.  The schedule can jump from one graph to
229     *  another among the disconnected graphs. There is nothing to
230     *  force the scheduler to finish executing all actors on one
231     *  graph before firing actors on another graph. However, the
232     *  order of execution within an graph should be correct.
233     *  Usually, disconnected graphs in an SDF model indicates an
234     *  error.  The default value is a BooleanToken with the value
235     *  false.
236     */
237    public Parameter allowDisconnectedGraphs;
238
239    /** A parameter representing whether dynamic rate changes are
240     *  permitted.  An SDF model may constructed such that the values
241     *  of rate parameters are modified during the execution of the
242     *  system.  If this parameter is true, then such models are
243     *  valid and this class dynamically computes a new schedule at
244     *  runtime.  If this parameter is false, then the SDF domain
245     *  performs a static check to disallow such models.  Note that in
246     *  order to generate code from an SDF model, this parameter must
247     *  be set to false.  This is a boolean with default
248     *  value false.
249     */
250    public Parameter allowRateChanges;
251
252    /** If true, then buffer sizes are fixed according to the schedule,
253     *  and attempts to write to the buffer that cause the buffer to
254     *  exceed the schedule size result in an exception. This method
255     *  works by setting the capacity of the receivers if the value is
256     *  true. This parameter is a boolean that defaults to true.
257     */
258    public Parameter constrainBufferSizes;
259
260    /** A Parameter representing the number of times that postfire may be
261     *  called before it returns false.  If the value is less than or
262     *  equal to zero, then the execution will never return false in postfire,
263     *  and thus the execution can continue forever. Note that the amount
264     *  of data processed by the SDF model is a function of both this
265     *  parameter and the value of parameter <i>vectorizationFactor</i>, since
266     *  <i>vectorizationFactor</i> can influence the choice of schedule.
267     *
268     *  <p>If the number of iterations is -1, which is the value of
269     *  the AUTO choice in the UI, then if the container of the
270     *  director is the the top level then one iteration will occur
271     *  before postfire() returns false.</p>
272     *
273     *  <p>If the number of iterations is -1 and and the container of
274     *  the director is <b>not</b> at the top level then postfire()
275     *  will always return true and execution will continue
276     *  forever.</p>
277     *
278     *  The default value is an IntToken with the value AUTO, which
279     *  is -1.  The UI has a second choice: UNBOUNDED, which is 0.
280     */
281    public Parameter iterations;
282
283    /** The time period of each iteration.  This parameter has type double
284     *  and default value 0.0, which means that this director does not
285     *  increment model time and does not request firings by calling
286     *  fireAt() on any enclosing director.  If the value is set to
287     *  something greater than 0.0, then if this director is at the
288     *  top level, it will increment model time by the specified
289     *  amount in its postfire() method. If it is not at the top
290     *  level, then it will call fireAt() on the enclosing executive
291     *  director with the argument being the current time plus the
292     *  specified period.
293     */
294    public Parameter period;
295
296    /** Specify whether the execution should synchronize to the
297     *  real time. This parameter has type boolean and defaults
298     *  to false. If set to true, then this director stalls in the
299     *  prefire() method until the elapsed real real time matches
300     *  the product of the <i>period</i> parameter value and the
301     *  iteration count. If the <i>period</i> parameter has value
302     *  0.0 (the default), then changing this parameter to true
303     *  has no effect.
304     */
305    public Parameter synchronizeToRealTime;
306
307    /** A Parameter representing the requested vectorization factor.
308     *  The director will attempt to construct a schedule where each
309     *  actor fires <i>vectorizationFactor</i> times more often than
310     *  it would in a minimal schedule.  This can allow actor executions
311     *  to be grouped together, resulting in faster execution.  This is
312     *  more likely to be possible in graphs without tight feedback.
313     *  This parameter must be a positive integer.
314     *  The default value is an IntToken with the value one.
315     */
316    public Parameter vectorizationFactor;
317
318    /** The value used to signify special behavior for the
319     *  iterations parameter.
320     */
321    public static final IntToken AUTO_INTTOKEN = new IntToken(-1);
322
323    /** The name of the AUTO iterations parameter choice: "AUTO". */
324    public static final String AUTO_NAME = "AUTO";
325
326    /** The UNBOUNDED iterations choice is equivalent to IntToken.ZERO. */
327    public static final IntToken UNBOUNDED_INTTOKEN = IntToken.ZERO;
328
329    /** The name of the UNBOUNDED iterations parameter choice: "UNBOUNDED". */
330    public static final String UNBOUNDED_NAME = "UNBOUNDED";
331
332    /** The name of the iterations parameter: "iterations". */
333    public static final String ITERATIONS_NAME = "iterations";
334
335    ///////////////////////////////////////////////////////////////////
336    ////                         public methods                    ////
337
338    /** React to a change in an attribute. If the changed attribute
339     *  matches a parameter of the director, then the corresponding
340     *  local copy of the parameter value will be updated.
341     *  @param attribute The changed parameter.
342     *  @exception IllegalActionException If the parameter set is not valid.
343     */
344    @Override
345    public void attributeChanged(Attribute attribute)
346            throws IllegalActionException {
347        // NOTE: Invalidate the schedules only if the values of these
348        // parameters have changed.
349        if (attribute == allowDisconnectedGraphs) {
350            Token token = allowDisconnectedGraphs.getToken();
351            boolean newValue = ((BooleanToken) token).booleanValue();
352            if (newValue != _allowDisconnectedGraphs) {
353                _allowDisconnectedGraphs = newValue;
354                invalidateSchedule();
355            }
356        } else if (attribute == vectorizationFactor) {
357            Token token = vectorizationFactor.getToken();
358            int newValue = ((IntToken) token).intValue();
359            if (newValue != _vectorizationFactor) {
360                _vectorizationFactor = newValue;
361                invalidateSchedule();
362            }
363        }
364
365        super.attributeChanged(attribute);
366    }
367
368    /** Clone the object into the specified workspace. The new object is
369     *  <i>not</i> added to the directory of that workspace (you must do this
370     *  yourself if you want it there).
371     *  @param workspace The workspace for the cloned object.
372     *  @exception CloneNotSupportedException Not thrown in this base class
373     *  @return The new Attribute.
374     */
375    @Override
376    public Object clone(Workspace workspace) throws CloneNotSupportedException {
377        SDFDirector newObject = (SDFDirector) super.clone(workspace);
378
379        // Subclasses may set this to null and handle this themselves.
380        try {
381            newObject._periodicDirectorHelper = new PeriodicDirectorHelper(
382                    newObject);
383        } catch (IllegalActionException e) {
384            throw new CloneNotSupportedException(
385                    "Failed to create PeriodicDirectorHelper.");
386        }
387
388        return newObject;
389    }
390
391    /**  Create the SDF schedule for this director.
392     */
393    @Override
394    public void createSchedule() throws IllegalActionException {
395        BaseSDFScheduler scheduler = (BaseSDFScheduler) getScheduler();
396
397        if (scheduler == null) {
398            throw new IllegalActionException("Attempted to initialize "
399                    + "SDF system with no scheduler");
400        }
401
402        // force the schedule to be computed.
403        if (_debugging) {
404            _debug("### Schedule:");
405        }
406
407        try {
408            Schedule schedule = scheduler.getSchedule();
409            if (_debugging) {
410                _debug(schedule.toString());
411                _debug("### End schedule");
412            }
413        } catch (NotSchedulableException ex) {
414            // Capt. Robbins suggested that we show which actors are connected
415            // or disconnected at the top, rather than burying it.
416            throw ex;
417        } catch (Exception ex) {
418            throw new IllegalActionException(this, ex,
419                    "Failed to compute schedule:");
420        }
421
422        // Declare the dependencies of rate parameters of external
423        // ports.  Note that this must occur after scheduling, since
424        // rate parameters are assumed to exist.
425        scheduler.declareRateDependency();
426    }
427
428    /** Return the number of iterations.
429     *
430     *  <p>The number of iterations returned depends on the value of
431     *  the <i>iterations</i> parameter and whether the container
432     *  of the director is at the top level.
433     *  See the {@link #iterations} documentation for details.</p>
434     *
435     *  <p>Code that uses SDFDirector should call getIterations()
436     *  instead of directly referring to the value of the
437     *  <i>iterations</i> parameter.</p>
438     *
439     *  @return the number of iterations
440     *  @exception IllegalActionException If thrown while getting the
441     *  value of the iterations parameter.
442     */
443    public int getIterations() throws IllegalActionException {
444        // See "SDF director iterations parameter default of 0 is unfriendly"
445        // http://bugzilla.ecoinformatics.org/show_bug.cgi?id=5546
446        IntToken token = ((IntToken) iterations.getToken());
447        int iterationsValue = 0;
448        if (token != null) {
449            iterationsValue = token.intValue();
450        }
451        if (iterationsValue > 0) {
452            return iterationsValue;
453        }
454        // The director should call isEmbedded()
455        // instead of seeing whether the container's container is null.
456        // The reason for this is RunCompositeActor, where the container's
457        // container is not null, but you still want the model to behave
458        // as if it were at the top level...
459        if (!isEmbedded()) {
460            // The container of this director is at the toplevel
461            if (iterations.getToken().equals(AUTO_INTTOKEN)) {
462                return 1;
463            }
464        }
465        return 0;
466    }
467
468    /** Return the time value of the next iteration.
469     *  If this director is at the top level, then the returned value
470     *  is the current time plus the period. Otherwise, this method
471     *  delegates to the executive director.
472     *  @return The time of the next iteration.
473     *  @exception IllegalActionException If time objects cannot be created.
474     */
475    @Override
476    public Time getModelNextIterationTime() throws IllegalActionException {
477        if (!_isTopLevel()) {
478            return super.getModelNextIterationTime();
479        }
480        try {
481            double periodValue = periodValue();
482
483            if (periodValue > 0.0) {
484                return getModelTime().add(periodValue);
485            } else {
486                return getModelTime();
487            }
488        } catch (IllegalActionException exception) {
489            // This should have been caught by now.
490            throw new InternalErrorException(exception);
491        }
492    }
493
494    /** Call super.fire() and reset the _prefire flag.
495     *  @exception IllegalActionException Thrown by super class.
496     */
497    @Override
498    public void fire() throws IllegalActionException {
499        _prefire = false;
500        super.fire();
501    }
502
503    /** Request a firing of the given actor at the given absolute
504     *  time, and return the time at which the specified will be
505     *  fired. If the <i>period</i> is 0.0 and there is no enclosing
506     *  director, then this method returns the current time. If
507     *  the period is 0.0 and there is an enclosing director, then
508     *  this method delegates to the enclosing director, returning
509     *  whatever it returns. If the <i>period</i> is not 0.0, then
510     *  this method checks to see whether the
511     *  requested time is equal to the current time plus an integer
512     *  multiple of the period. If so, it returns the requested time.
513     *  If not, it returns current time plus the period.
514     *  @param actor The actor scheduled to be fired.
515     *  @param time The requested time.
516     *  @param microstep The microstep (ignored by this director).
517     *  @exception IllegalActionException If the operation is not
518     *    permissible (e.g. the given time is in the past).
519     *  @return Either the requested time or the current time plus the
520     *  period.
521     */
522    @Override
523    public Time fireAt(Actor actor, Time time, int microstep)
524            throws IllegalActionException {
525        if (_periodicDirectorHelper != null) {
526            return _periodicDirectorHelper.fireAt(actor, time);
527        }
528        return super.fireAt(actor, time);
529    }
530
531    /** Initialize the actors associated with this director and then
532     *  set the iteration count to zero.  The order in which the
533     *  actors are initialized is arbitrary.  In addition, if actors
534     *  connected directly to output ports have initial production,
535     *  then copy that initial production to the outside of the
536     *  composite actor.
537     *  @exception IllegalActionException If the initialize() method of
538     *  one of the associated actors throws it, or if there is no
539     *  scheduler.
540     */
541    @Override
542    public void initialize() throws IllegalActionException {
543
544        super.initialize();
545        _iterationCount = 0;
546
547        if (_periodicDirectorHelper != null) {
548            _periodicDirectorHelper.initialize();
549        }
550
551        CompositeActor container = (CompositeActor) getContainer();
552
553        for (Iterator ports = container.outputPortList().iterator(); ports
554                .hasNext();) {
555            IOPort port = (IOPort) ports.next();
556
557            // Create external initial production.
558            int rate = DFUtilities.getTokenInitProduction(port);
559
560            for (int i = 0; i < port.getWidthInside(); i++) {
561                try {
562                    for (int k = 0; k < rate; k++) {
563                        if (port.hasTokenInside(i)) {
564                            Token t = port.getInside(i);
565
566                            if (_debugging) {
567                                _debug(getName(), "transferring output from "
568                                        + port.getName());
569                            }
570
571                            port.send(i, t);
572                        } else {
573                            throw new IllegalActionException(this, port,
574                                    "Port should produce " + rate
575                                            + " tokens, but there were only "
576                                            + k + " tokens available.");
577                        }
578                    }
579                } catch (NoTokenException ex) {
580                    // this shouldn't happen.
581                    throw new InternalErrorException(this, ex, null);
582                }
583            }
584        }
585    }
586
587    /** Return a new receiver consistent with the SDF domain.
588     *  @return A new SDFReceiver.
589     */
590    @Override
591    public Receiver newReceiver() {
592        return new SDFReceiver();
593    }
594
595    /** Return the value of the period as a double.
596     *  @return The value of the period as a double.
597     *  @exception IllegalActionException If the period parameter
598     *   cannot be evaluated
599     */
600    @Override
601    public double periodValue() throws IllegalActionException {
602        return ((DoubleToken) period.getToken()).doubleValue();
603    }
604
605    /** Check the input ports of the container composite actor (if there
606     *  are any) to see whether they have enough tokens, and return true
607     *  if they do.  If there are no input ports, then also return true.
608     *  Otherwise, return false.  Note that this does not call prefire()
609     *  on the contained actors.
610     *  <p>
611     *  This method also implements the functionality of
612     *  <i>synchronizeToRealTime</i> by waiting for real time
613     *  to elapse if the parameter value is true.
614     *  @exception IllegalActionException If port methods throw it.
615     *  @return true If all of the input ports of the container of this
616     *  director have enough tokens.
617     */
618    @Override
619    public boolean prefire() throws IllegalActionException {
620        // Set current time based on the enclosing model.
621
622        // If prefire returns true and prefire is called again
623        // without calling fire in between,
624        // which can happen when resourceScheduling is enabled,
625        // then return true again. Otherwise check prefire
626        // conditions.
627        if (_aspectsPresent && _prefire) {
628            return true;
629        }
630        _prefire = super.prefire();
631
632        if (!_prefire) {
633            return false;
634        }
635
636        double periodValue = periodValue();
637        boolean synchronizeValue = ((BooleanToken) synchronizeToRealTime
638                .getToken()).booleanValue();
639
640        if (periodValue > 0.0 && synchronizeValue) {
641            int depth = 0;
642            try {
643                synchronized (this) {
644                    while (true) {
645                        long elapsedTime = elapsedTimeSinceStart();
646
647                        // NOTE: We assume that the elapsed time can be
648                        // safely cast to a double.  This means that
649                        // the SDF domain has an upper limit on running
650                        // time of Double.MAX_VALUE milliseconds.
651                        double elapsedTimeInSeconds = elapsedTime / 1000.0;
652                        double currentTime = getModelTime().getDoubleValue();
653
654                        if (currentTime <= elapsedTimeInSeconds) {
655                            break;
656                        }
657
658                        long timeToWait = (long) ((currentTime
659                                - elapsedTimeInSeconds) * 1000.0);
660
661                        if (_debugging) {
662                            _debug("Waiting for real time to pass: "
663                                    + timeToWait);
664                        }
665
666                        try {
667                            // NOTE: The built-in Java wait() method
668                            // does not release the
669                            // locks on the workspace, which would block
670                            // UI interactions and may cause deadlocks.
671                            // SOLUTION: explicitly release read permissions.
672                            if (timeToWait > 0) {
673                                // Bug fix from J. S. Senecal:
674                                //
675                                //  The problem was that sometimes, the
676                                //  method Object.wait(timeout) was called
677                                //  with timeout = 0. According to java
678                                //  documentation:
679                                //
680                                // " If timeout is zero, however, then
681                                // real time is not taken into
682                                // consideration and the thread simply
683                                // waits until notified."
684                                depth = _workspace.releaseReadPermission();
685                                wait(timeToWait);
686                            }
687                        } catch (InterruptedException ex) {
688                            // Continue executing.
689                        }
690                    }
691                }
692            } finally {
693                if (depth > 0) {
694                    _workspace.reacquireReadPermission(depth);
695                }
696            }
697        }
698
699        // Refuse to fire if the period is greater than zero and the current
700        // time is not a multiple of the period.
701        if (_periodicDirectorHelper != null
702                && !_periodicDirectorHelper.prefire()) {
703            if (_debugging) {
704                _debug("Current time is not a multiple of the period or the microstep is 0. Returning false.\n"
705                        + "Current time: " + getModelTime() + "  Period: "
706                        + periodValue);
707            }
708            return false;
709        }
710
711        // Check to see whether the input ports have enough data.
712        TypedCompositeActor container = (TypedCompositeActor) getContainer();
713        Iterator inputPorts = container.inputPortList().iterator();
714        while (inputPorts.hasNext()) {
715            IOPort inputPort = (IOPort) inputPorts.next();
716
717            // NOTE: If the port is a ParameterPort, then we should not
718            // insist on there being an input.
719            if (inputPort instanceof ParameterPort) {
720                continue;
721            }
722
723            int threshold = DFUtilities.getTokenConsumptionRate(inputPort);
724
725            if (_debugging) {
726                _debug("checking input " + inputPort.getFullName());
727                _debug("Threshold = " + threshold);
728            }
729
730            for (int channel = 0; channel < inputPort.getWidth(); channel++) {
731                if (threshold > 0 && !inputPort.hasToken(channel, threshold)) {
732                    if (_debugging) {
733                        _debug("Port " + inputPort.getFullName()
734                                + " does not have enough tokens: " + threshold
735                                + " Prefire returns false.");
736                    }
737
738                    return false;
739                }
740            }
741        }
742
743        if (_debugging) {
744            _debug("Director prefire returns true.");
745        }
746
747        return true;
748    }
749
750    /** Preinitialize the actors associated with this director and
751     *  compute the schedule.  The schedule is computed during
752     *  preinitialization so that hierarchical opaque composite actors
753     *  can be scheduled properly, since the act of computing the
754     *  schedule sets the rate parameters of the external ports.  In
755     *  addition, performing scheduling during preinitialization
756     *  enables it to be present during code generation.  The order in
757     *  which the actors are preinitialized is arbitrary.
758     *  @exception IllegalActionException If the preinitialize() method of
759     *  one of the associated actors throws it.
760     */
761    @Override
762    public void preinitialize() throws IllegalActionException {
763        super.preinitialize();
764        createSchedule();
765    }
766
767    /** Return false if the system has finished executing, either by
768     *  reaching the iteration limit, or having an actor in the system return
769     *  false in postfire.  Increment the number of iterations.
770     *  If the "iterations" parameter is greater than zero, then
771     *  see if the limit has been reached.  If so, return false.
772     *  Otherwise return true if all of the fired actors since the last
773     *  call to prefire returned true.
774     *  If the <i>period</i> parameter is greater than 0.0, then
775     *  if this director is at the top level, then increment time
776     *  by the specified period, and otherwise request a refiring
777     *  at the current time plus the period.
778     *  @return True if the Director wants to be fired again in the
779     *  future.
780     *  @exception IllegalActionException If the iterations parameter
781     *  does not contain a legal value.
782     */
783    @Override
784    public boolean postfire() throws IllegalActionException {
785        int iterationsValue = getIterations();
786        _iterationCount++;
787
788        if (iterationsValue > 0 && _iterationCount >= iterationsValue) {
789            _iterationCount = 0;
790            if (_debugging) {
791                _debug("Reached specified number of iterations: "
792                        + iterationsValue);
793            }
794            return false;
795        }
796
797        boolean result = super.postfire();
798        if (_periodicDirectorHelper != null) {
799            _periodicDirectorHelper.postfire();
800        }
801        return result;
802    }
803
804    /** Return an array of suggested ModalModel directors  to use with
805     *  SDFDirector. The default director is HDFFSMDirector, which supports
806     *  multirate actors and only allows state transitions on each iteration.
807     *  This is the most safe director to use with SDF models.
808     *  MultirateFSMDirector supports multirate actors and allows state
809     *  transitions on each firing of the modal model. MultirateFSMDirector
810     *  can be used with SDF if rate signatures for all the states in the
811     *  modal model are same. If rate signatures change during an iteration,
812     *  the SDFDirector will throw an exception.
813     *  FSMDirector can be used with SDFDirector only when rate signatures
814     *  for modal model are all 1.
815     *  @return An array of suggested directors to be used with ModalModel.
816     *  @see ptolemy.actor.Director#suggestedModalModelDirectors()
817     */
818    @Override
819    public String[] suggestedModalModelDirectors() {
820        return new String[] { "ptolemy.domains.modal.kernel.FSMDirector",
821                "ptolemy.domains.modal.kernel.MultirateFSMDirector",
822                "ptolemy.domains.hdf.kernel.HDFFSMDirector" };
823    }
824
825    /** Return true to indicate that a ModalModel under control
826     *  of this director supports multirate firing.
827     *  @return True indicating a ModalModel under control of this director
828     *  supports multirate firing.
829     */
830    @Override
831    public boolean supportMultirateFiring() {
832        return true;
833    }
834
835    /** Override the base class method to transfer enough tokens to
836     *  complete an internal iteration.  If there are not enough tokens,
837     *  then throw an exception.  If the port is not connected on the
838     *  inside, or has a narrower width on the inside than on the outside,
839     *  then consume exactly one token from the corresponding outside
840     *  channels and discard it.  Thus, a port connected on the outside
841     *  but not on the inside can be used as a trigger for an SDF
842     *  composite actor.
843     *
844     *  @param port The port to transfer tokens from.
845     *  @return True if data are transferred.
846     *  @exception IllegalActionException If the port is not an opaque
847     *  input port, or if there are not enough input tokens available.
848     */
849    @Override
850    public boolean transferInputs(IOPort port) throws IllegalActionException {
851        if (!port.isInput() || !port.isOpaque()) {
852            throw new IllegalActionException(this, port,
853                    "Attempted to transferInputs on a port is not an opaque"
854                            + "input port.");
855        }
856
857        // The number of tokens depends on the schedule, so make sure
858        // the schedule is valid.
859        getScheduler().getSchedule();
860
861        int rate = DFUtilities.getTokenConsumptionRate(port);
862        boolean wasTransferred = false;
863
864        for (int i = 0; i < port.getWidth(); i++) {
865            try {
866                if (i < port.getWidthInside()) {
867                    for (int k = 0; k < rate; k++) {
868                        if (port.hasToken(i)) {
869                            Token t = port.get(i);
870
871                            if (_debugging) {
872                                _debug(getName(), "transferring input from "
873                                        + port.getName());
874                            }
875
876                            port.sendInside(i, t);
877                            wasTransferred = true;
878                        } else {
879                            throw new IllegalActionException(this, port,
880                                    "Port should consume " + rate
881                                            + " tokens, but there were only "
882                                            + k + " tokens available.");
883                        }
884                    }
885                } else if (port.isKnown(i)) {
886                    // No inside connection to transfer tokens to.
887                    // Tolerate an unknown input, but if it is known, then
888                    // transfer the input token if there is one.
889                    // In this case, consume one input token if there is one.
890                    if (_debugging) {
891                        _debug(getName(),
892                                "Dropping single input from " + port.getName());
893                    }
894
895                    if (port.hasToken(i)) {
896                        port.get(i);
897                    }
898                }
899            } catch (NoTokenException ex) {
900                // this shouldn't happen.
901                throw new InternalErrorException(this, ex, null);
902            }
903        }
904
905        return wasTransferred;
906    }
907
908    /** Override the base class method to transfer enough tokens to
909     *  fulfill the output production rate.
910     *  This behavior is required to handle the case of non-homogeneous
911     *  opaque composite actors. The port argument must be an opaque
912     *  output port. If any channel of the output port has no data, then
913     *  that channel is ignored.
914     *
915     *  @param port The port to transfer tokens from.
916     *  @return True if data are transferred.
917     *  @exception IllegalActionException If the port is not an opaque
918     *  output port.
919     */
920    @Override
921    public boolean transferOutputs(IOPort port) throws IllegalActionException {
922        if (_debugging) {
923            _debug("Calling transferOutputs on port: " + port.getFullName());
924        }
925
926        if (!port.isOutput() || !port.isOpaque()) {
927            throw new IllegalActionException(this, port,
928                    "Attempted to transferOutputs on a port that "
929                            + "is not an opaque output port.");
930        }
931
932        int rate = DFUtilities.getTokenProductionRate(port);
933        boolean wasTransferred = false;
934
935        for (int i = 0; i < port.getWidthInside(); i++) {
936            try {
937                for (int k = 0; k < rate; k++) {
938                    if (port.hasTokenInside(i)) {
939                        Token t = port.getInside(i);
940
941                        if (_debugging) {
942                            _debug(getName(), "transferring output from "
943                                    + port.getName());
944                        }
945
946                        port.send(i, t);
947                        wasTransferred = true;
948                    } else {
949                        throw new IllegalActionException(this, port,
950                                "Port should produce " + rate
951                                        + " tokens, but there were only " + k
952                                        + " tokens available.");
953                    }
954                }
955            } catch (NoTokenException ex) {
956                // this shouldn't happen.
957                throw new InternalErrorException(this, ex, null);
958            }
959        }
960
961        return wasTransferred;
962    }
963
964    ///////////////////////////////////////////////////////////////////
965    ////                         protected variables               ////
966
967    /** The iteration count. */
968    protected int _iterationCount = 0;
969
970    /** Helper class supporting the <i>period</i> parameter. */
971    protected PeriodicDirectorHelper _periodicDirectorHelper;
972
973    ///////////////////////////////////////////////////////////////////
974    ////                         private methods                   ////
975
976    /** Initialize the object.   In this case, we give the SDFDirector a
977     *  default scheduler of the class SDFScheduler, an iterations
978     *  parameter and a vectorizationFactor parameter.
979     */
980    private void _init()
981            throws IllegalActionException, NameDuplicationException {
982
983        // AUTO and UNBOUNDED are used to set the value of iterations,
984        // see the getIterations() method.
985
986        Parameter AUTO = new Parameter(this, AUTO_NAME);
987        AUTO.setToken(AUTO_INTTOKEN);
988        AUTO.setVisibility(Settable.EXPERT);
989        AUTO.setPersistent(false);
990
991        Parameter UNBOUNDED = new Parameter(this, UNBOUNDED_NAME);
992        UNBOUNDED.setToken(UNBOUNDED_INTTOKEN);
993        UNBOUNDED.setVisibility(Settable.EXPERT);
994        UNBOUNDED.setPersistent(false);
995
996        iterations = new Parameter(this, ITERATIONS_NAME);
997        iterations.setTypeEquals(BaseType.INT);
998        iterations.addChoice(AUTO_NAME);
999        iterations.addChoice(UNBOUNDED_NAME);
1000        iterations.setExpression(AUTO_NAME);
1001
1002        vectorizationFactor = new Parameter(this, "vectorizationFactor");
1003        vectorizationFactor.setTypeEquals(BaseType.INT);
1004        vectorizationFactor.setExpression("1");
1005
1006        allowDisconnectedGraphs = new Parameter(this,
1007                "allowDisconnectedGraphs");
1008        allowDisconnectedGraphs.setTypeEquals(BaseType.BOOLEAN);
1009        allowDisconnectedGraphs.setExpression("false");
1010
1011        allowRateChanges = new Parameter(this, "allowRateChanges");
1012        allowRateChanges.setTypeEquals(BaseType.BOOLEAN);
1013        allowRateChanges.setExpression("false");
1014
1015        constrainBufferSizes = new Parameter(this, "constrainBufferSizes");
1016        constrainBufferSizes.setTypeEquals(BaseType.BOOLEAN);
1017        constrainBufferSizes.setExpression("true");
1018
1019        period = new Parameter(this, "period", new DoubleToken(1.0));
1020        period.setTypeEquals(BaseType.DOUBLE);
1021        period.setExpression("0.0");
1022
1023        synchronizeToRealTime = new Parameter(this, "synchronizeToRealTime");
1024        synchronizeToRealTime.setExpression("false");
1025        synchronizeToRealTime.setTypeEquals(BaseType.BOOLEAN);
1026
1027        startTime.moveToLast();
1028        stopTime.moveToLast();
1029
1030        SDFScheduler scheduler = new SDFScheduler(this,
1031                uniqueName("Scheduler"));
1032        scheduler.constrainBufferSizes.setExpression("constrainBufferSizes");
1033        setScheduler(scheduler);
1034
1035        // Subclasses may set this to null and handle this themselves.
1036        _periodicDirectorHelper = new PeriodicDirectorHelper(this);
1037    }
1038
1039    ///////////////////////////////////////////////////////////////////
1040    ////                package friendly variables                 ////
1041
1042    /** Cache of the value of allowDisconnectedGraphs. */
1043    boolean _allowDisconnectedGraphs = false;
1044
1045    ///////////////////////////////////////////////////////////////////
1046    ////                         private variables                 ////
1047
1048    /** Cache of the most recent value of vectorizationFactor. */
1049    private int _vectorizationFactor = 1;
1050
1051}