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