001/* A Scheduler infrastructure for the SDF domain
002
003 Copyright (c) 2003-2014 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.Iterator;
031import java.util.List;
032import java.util.Map;
033
034import ptolemy.actor.CompositeActor;
035import ptolemy.actor.Director;
036import ptolemy.actor.IOPort;
037import ptolemy.actor.sched.NotSchedulableException;
038import ptolemy.actor.sched.Scheduler;
039import ptolemy.actor.util.ConstVariableModelAnalysis;
040import ptolemy.actor.util.DFUtilities;
041import ptolemy.actor.util.DependencyDeclaration;
042import ptolemy.data.expr.Variable;
043import ptolemy.kernel.Entity;
044import ptolemy.kernel.Port;
045import ptolemy.kernel.Relation;
046import ptolemy.kernel.util.ChangeRequest;
047import ptolemy.kernel.util.IllegalActionException;
048import ptolemy.kernel.util.InternalErrorException;
049import ptolemy.kernel.util.KernelException;
050import ptolemy.kernel.util.NameDuplicationException;
051import ptolemy.kernel.util.Workspace;
052
053///////////////////////////////////////////////////////////////////
054//// BaseSDFScheduler
055
056/**
057 This class factors code out of the SDF domain, for use in different
058 schedulers, so that they can be implemented in a consistent fashion.
059
060 @author Stephen Neuendorffer, Shuvra S. Bhattacharyya
061 @version $Id$
062 @since Ptolemy II 4.0
063 @Pt.ProposedRating Red (neuendor)
064 @Pt.AcceptedRating Red (neuendor)
065 */
066public abstract class BaseSDFScheduler extends Scheduler {
067    /** Construct a scheduler with no container(director)
068     *  in the default workspace, the name of the scheduler is
069     *  "Scheduler".
070     */
071    public BaseSDFScheduler() {
072        super();
073    }
074
075    /** Construct a scheduler in the given workspace with the name
076     *  "Scheduler".
077     *  If the workspace argument is null, use the default workspace.
078     *  The scheduler is added to the list of objects in the workspace.
079     *  Increment the version number of the workspace.
080     *
081     *  @param workspace Object for synchronization and version tracking.
082     */
083    public BaseSDFScheduler(Workspace workspace) {
084        super(workspace);
085    }
086
087    /** Construct a scheduler in the given container with the given name.
088     *  The container argument must not be null, or a
089     *  NullPointerException will be thrown.  This attribute will use the
090     *  workspace of the container for synchronization and version counts.
091     *  If the name argument is null, then the name is set to the empty string.
092     *  Increment the version of the workspace.
093     *  @param container The container.
094     *  @param name The name of this attribute.
095     *  @exception IllegalActionException If the attribute is not of an
096     *   acceptable class for the container, or if the name contains a period.
097     *  @exception NameDuplicationException If the name coincides with
098     *   an attribute already in the container.
099     */
100    public BaseSDFScheduler(Director container, String name)
101            throws IllegalActionException, NameDuplicationException {
102        super(container, name);
103    }
104
105    ///////////////////////////////////////////////////////////////////
106    ////                         public methods                    ////
107
108    /** Declare the rate dependency on any external ports of the model.
109     *  SDF directors should invoke this method once during preinitialize.
110     *  @exception IllegalActionException If there is a problem setting
111     *  the rate dependency on an external port.
112     */
113    public abstract void declareRateDependency() throws IllegalActionException;
114
115    ///////////////////////////////////////////////////////////////////
116    ////                         protected methods                 ////
117
118    /** Add a DependencyDeclaration (with the name
119     *  "_SDFRateDependencyDeclaration") to the variable with the given
120     *  name in the given port that declares the variable is dependent
121     *  on the given list of variables.  If a dependency declaration
122     *  with that name already exists, then simply set its dependents
123     *  list to the given list.
124     *  @param analysis The ConstVariableModelAnalysis
125     *  @param port The port that gets the DependencyDeclaration.
126     *  @param name The name of the DependencyDeclaration.
127     *  @param dependents The dependents.
128     *  @exception IllegalActionException If there is a problem setting
129     *  the rate dependency on a port
130     */
131    @SuppressWarnings("unused")
132    protected void _declareDependency(ConstVariableModelAnalysis analysis,
133            Port port, String name, List dependents)
134            throws IllegalActionException {
135        if (_debugging && VERBOSE) {
136            _debug("declaring dependency for rate variable " + name
137                    + " in port " + port.getFullName());
138        }
139
140        Variable variable = DFUtilities.getRateVariable(port, name);
141        DependencyDeclaration declaration = (DependencyDeclaration) variable
142                .getAttribute("_SDFRateDependencyDeclaration",
143                        DependencyDeclaration.class);
144
145        if (declaration == null) {
146            try {
147                declaration = new DependencyDeclaration(variable,
148                        "_SDFRateDependencyDeclaration");
149            } catch (NameDuplicationException ex) {
150                // We used to ignore this, but FindBugs would complain
151                // that declaration could still be null.
152                throw new InternalErrorException("Failed to construct "
153                        + "_SDFRateDependencyDeclaration");
154            }
155        }
156
157        declaration.setDependents(dependents);
158        analysis.addDependencyDeclaration(declaration);
159    }
160
161    /** Create and set a parameter in each relation according
162     *  to the buffer sizes calculated for this system.
163     *  @param minimumBufferSizes A map from relation
164     *  to the minimum possible buffer size of that relation.
165     */
166    protected void _saveBufferSizes(final Map minimumBufferSizes) {
167        Director director = (Director) getContainer();
168        final CompositeActor container = (CompositeActor) director
169                .getContainer();
170
171        // FIXME: These buffer sizes should be properties of input ports,
172        // not properties of relations.
173        ChangeRequest request = new ChangeRequest(this, "Record buffer sizes") {
174            @Override
175            protected void _execute() throws KernelException {
176                Iterator relations = container.relationList().iterator();
177
178                while (relations.hasNext()) {
179                    Relation relation = (Relation) relations.next();
180                    Object bufferSizeObject = minimumBufferSizes.get(relation);
181
182                    if (bufferSizeObject instanceof Integer) {
183                        int bufferSize = ((Integer) bufferSizeObject)
184                                .intValue();
185                        DFUtilities.setOrCreate(relation, "bufferSize",
186                                bufferSize);
187
188                        if (_debugging) {
189                            _debug("Adding bufferSize parameter to "
190                                    + relation.getName() + " with value "
191                                    + bufferSize);
192                        }
193                    } else if (bufferSizeObject instanceof String) {
194                        String bufferSizeExpression = (String) bufferSizeObject;
195                        DFUtilities.setOrCreate(relation, "bufferSize",
196                                "\"" + bufferSizeExpression + "\"");
197
198                        if (_debugging) {
199                            _debug("Adding bufferSize parameter to "
200                                    + relation.getName() + " with expression "
201                                    + bufferSizeExpression);
202                        }
203                    } else if (bufferSizeObject == null) {
204                    } else {
205                        throw new InternalErrorException("Invalid value found "
206                                + "in buffer size map.\nValue is of type "
207                                + bufferSizeObject.getClass().getName()
208                                + ".\nIt should be of type Integer or String.\n");
209                    }
210                }
211            }
212        };
213
214        // Indicate that the change is non-persistent, so that
215        // the UI doesn't prompt to save.
216        request.setPersistent(false);
217        container.requestChange(request);
218    }
219
220    /** Push the rates calculated for this system up to the contained Actor,
221     *  but only if the ports do not have a set rates.
222     *  This allows the container to be properly scheduled if it is
223     *  in a hierarchical system and the outside system is SDF.
224     *  @param externalRates A map from external port to the rate of that
225     *   port.
226     *  @exception IllegalActionException If any called method throws it.
227     *  @exception NotSchedulableException If an external port is both
228     *   an input and an output, or neither an input or an output, or
229     *   connected on the inside to ports that have different
230     *   tokenInitProduction.
231     */
232    @SuppressWarnings("unused")
233    protected void _saveContainerRates(Map externalRates)
234            throws NotSchedulableException, IllegalActionException {
235        Director director = (Director) getContainer();
236        CompositeActor container = (CompositeActor) director.getContainer();
237        Iterator ports = container.portList().iterator();
238
239        while (ports.hasNext()) {
240            IOPort port = (IOPort) ports.next();
241
242            if (_debugging && VERBOSE) {
243                _debug("External Port " + port.getName());
244            }
245
246            Integer rate = (Integer) externalRates.get(port);
247
248            if (port.isInput() && port.isOutput()) {
249                throw new NotSchedulableException(port,
250                        "External port is both an input and an output, "
251                                + "which is not allowed in SDF.");
252            } else if (port.isInput()) {
253                DFUtilities.setIfNotDefined(port, "tokenConsumptionRate",
254                        rate.intValue());
255
256                if (_debugging && VERBOSE) {
257                    _debug("Setting tokenConsumptionRate to "
258                            + rate.intValue());
259                }
260
261                // External ports do not any initial consumption tokens
262                // that are caused by the inside model, so we set this
263                // parameter to zero.
264                DFUtilities.setIfNotDefined(port, "tokenInitConsumption", 0);
265
266                if (_debugging && VERBOSE) {
267                    _debug("Setting tokenInitConsumption to 0.");
268                }
269            } else if (port.isOutput()) {
270                DFUtilities.setIfNotDefined(port, "tokenProductionRate",
271                        rate.intValue());
272
273                if (_debugging && VERBOSE) {
274                    _debug("Setting tokenProductionRate to " + rate.intValue());
275                }
276
277                // Infer init production.
278                // Note that this is a very simple type of inference...
279                // However, in general, we don't want to try to
280                // flatten this model...
281                Iterator connectedPorts = port.insideSourcePortList()
282                        .iterator();
283                IOPort foundOutputPort = null;
284                int inferredRate = 0;
285
286                while (connectedPorts.hasNext()) {
287                    IOPort connectedPort = (IOPort) connectedPorts.next();
288
289                    int newRate;
290
291                    if (connectedPort.isOutput()) {
292                        newRate = DFUtilities
293                                .getTokenInitProduction(connectedPort);
294                    } else {
295                        newRate = 0;
296                    }
297
298                    // If we've already set the rate, then check that the
299                    // rate for any other internal port is correct.
300                    if (foundOutputPort != null && newRate != inferredRate) {
301                        throw new NotSchedulableException(port,
302                                "External output port " + port
303                                        + " is connected on the inside to ports "
304                                        + "with different initial production: "
305                                        + foundOutputPort + " and "
306                                        + connectedPort);
307                    }
308
309                    foundOutputPort = connectedPort;
310                    inferredRate = newRate;
311                }
312
313                // If this output port has had its tokenInitConsumption
314                // parameter set to something other than zero, the this
315                // means that it will receive a token on the inside from
316                // some port that does initial production, such as PublisherPort.
317                // These initial tokens become initial _production_ for this
318                // port.
319                int initConsumption = DFUtilities.getTokenInitConsumption(port);
320                inferredRate += initConsumption;
321
322                DFUtilities.setIfNotDefined(port, "tokenInitProduction",
323                        inferredRate);
324
325                if (_debugging && VERBOSE) {
326                    _debug("Setting tokenInitProduction to " + inferredRate);
327                }
328            } else {
329                throw new NotSchedulableException(port,
330                        "External port is neither an input and an output, "
331                                + "which is not allowed in SDF.");
332            }
333        }
334    }
335
336    /** Create and set a parameter in each actor according
337     *  to the number of times it will fire in one execution of the schedule.
338     *  @param entityToFiringsPerIteration A map from actor to firing count.
339     */
340    protected void _saveFiringCounts(final Map entityToFiringsPerIteration) {
341        Director director = (Director) getContainer();
342        final CompositeActor container = (CompositeActor) director
343                .getContainer();
344
345        ChangeRequest request = new ChangeRequest(this,
346                "Record firings per iteration") {
347            @Override
348            protected void _execute() throws KernelException {
349                Iterator entities = entityToFiringsPerIteration.keySet()
350                        .iterator();
351
352                while (entities.hasNext()) {
353                    Entity entity = (Entity) entities.next();
354                    int firingCount = ((Integer) entityToFiringsPerIteration
355                            .get(entity)).intValue();
356                    DFUtilities.setOrCreate(entity, "firingsPerIteration",
357                            firingCount);
358
359                    if (_debugging) {
360                        _debug("Adding firingsPerIteration parameter to "
361                                + entity.getName() + " with value "
362                                + firingCount);
363                    }
364                }
365            }
366        };
367
368        // Indicate that the change is non-persistent, so that
369        // the UI doesn't prompt to save.
370        request.setPersistent(false);
371        container.requestChange(request);
372    }
373
374    ///////////////////////////////////////////////////////////////////
375    ////                         protected variables               ////
376
377    /** If true, then print verbose messages.  By default, this variable
378     *  is set to false.  To enable verbose messages, edit the source file
379     *  and recompile.
380     */
381    protected static final boolean VERBOSE = false;
382}