package ptolemy.cg.kernel.generic.program.procedural.java.modular;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import ptolemy.actor.Actor;
import ptolemy.actor.CompositeActor;
import ptolemy.actor.IOPort;
import ptolemy.actor.parameters.PortParameter;
import ptolemy.actor.sched.NotSchedulableException;
import ptolemy.actor.util.DFUtilities;
import ptolemy.domains.sdf.kernel.SDFScheduler;
import ptolemy.kernel.ComponentEntity;
import ptolemy.kernel.Entity;
import ptolemy.kernel.util.IllegalActionException;
import ptolemy.kernel.util.InternalErrorException;
import ptolemy.kernel.util.NamedObj;
import ptolemy.math.Fraction;
/** An SDF scheduler for modular code generation.
* @author Dai Bui
* @version $Id$
* @since Ptolemy II 7.1
* @Pt.ProposedRating Red (jiazou)
* @Pt.AcceptedRating Red (jiazou)
*/
public class SDFModularScheduler extends SDFScheduler {
///////////////////////////////////////////////////////////////////
//// public methods ////
/**
* Get the firing vector for a given container.
* @param container The container that is being scheduled.
* @param vectorizationFactor The vectorization factor
* @return A map from each actor to its fractional
* firing.
* @throws IllegalActionException
*/
public Map getFiringVector(CompositeActor container, int vectorizationFactor)
throws IllegalActionException {
if (vectorizationFactor < 1) {
throw new NotSchedulableException(this,
"The supplied vectorizationFactor must be "
+ "a positive integer. The given value was: "
+ vectorizationFactor);
}
// A linked list containing all the actors.
List allActorList = container.deepEntityList();
// externalRates maps from external
// ports to the number of tokens that that port
// will produce or consume in each firing.
// It gets populated with the fractional production ratios
// and is used in the end to set final rates on external ports.
// This map is initialized to zero.
// NOTE: This used to be a TreeMap using DFUtilities.NamedObjComparator().
// However, that comparator is very slow.
// FIXME: Why not get this via the container of the receivers?
// or better yet, cache it in the receivers?
Map externalRates = new HashMap();
// Initialize externalRates to zero.
for (Iterator ports = container.portList().iterator(); ports.hasNext();) {
IOPort port = (IOPort) ports.next();
externalRates.put(port, Fraction.ZERO);
}
// First solve the balance equations
Map entityToFiringsPerIteration = _solveBalanceEquations(container,
allActorList, externalRates);
if (_debugging && VERBOSE) {
_debug("Firing Ratios: " + entityToFiringsPerIteration.toString());
}
// Multiply the number of firings for each actor by the
// vectorizationFactor.
_vectorizeFirings(vectorizationFactor, entityToFiringsPerIteration,
externalRates);
// Set the firing vector.
return entityToFiringsPerIteration;
}
///////////////////////////////////////////////////////////////////
//// protected methods ////
/** Solve the balance equations for the list of connected Actors.
* For each actor, determine the ratio that determines the rate at
* which it should fire relative to the other actors for the graph to
* be live and operate within bounded memory. Normalize this ratio
* into integer, which is the minimum number of firings of the actor
* to satisfy the balance equations.
*
* @param container The container that is being scheduled.
* @param actorList The actors that we are interested in.
* @param externalRates A map from external ports of container to
* the fractional rates of that port. This starts out initialized with
* Fraction.ZERO and will be populated during this method.
* @return A map from each actor to its fractional
* firing.
* @exception NotSchedulableException If the graph is not consistent
* under the synchronous dataflow model, or if the graph is not connected.
* @exception IllegalActionException If any called method throws it.
*/
protected Map _solveBalanceEquations(CompositeActor container,
List actorList, Map externalRates) throws NotSchedulableException,
IllegalActionException {
// The map that we will return.
// This will be populated with the fraction firing ratios for
// each actor.
// NOTE: This used to be a TreeMap using DFUtilities.NamedObjComparator().
// However, that comparator is very slow.
// Map entityToFiringsPerIteration = new TreeMap(
// new DFUtilities.NamedObjComparator());
Map entityToFiringsPerIteration = new HashMap();
if (actorList.size() == 0) {
_checkDirectInputOutputConnection(container, externalRates);
// If we've been given
// no actors to do anything with, return an empty Map.
return entityToFiringsPerIteration;
}
// The pool of actors that have their firingsPerIteration set,
// but have not had their ports explored yet.
LinkedList pendingActors = new LinkedList();
// Set of actors that belong to the same cluster.
Set clusteredActors = new HashSet();
// Set of external ports that are conneted to
// actors of the same cluster.
Set clusteredExternalPorts = new HashSet();
// The pool of Actors that have not been touched
// yet. (i.e. all their firingsPerIteration are still set to
// Fraction equal to -1/1)
LinkedList remainingActors = new LinkedList();
// Initialize remainingActors to contain all the actors we were given.
remainingActors.addAll(actorList);
// Initialize entityToFiringsPerIteration for each actor to -1.
for (Iterator actors = remainingActors.iterator(); actors.hasNext();) {
ComponentEntity entity = (ComponentEntity) actors.next();
entityToFiringsPerIteration.put(entity, _minusOne);
}
// Ned Stoffel's change to support disconnected graphs:
// Finally, the schedule can jump from one island to
// another among the disconnected graphs. There is nothing
// to force the scheduler to finish executing all actors
// on one island before firing actors on another
// island. However, the order of execution within an
// island should be correct.
while (!remainingActors.isEmpty()) {
clusteredActors.clear();
clusteredExternalPorts.clear();
ComponentEntity actor = _pickZeroRatePortActor(remainingActors);
if (actor == null) {
actor = (ComponentEntity) remainingActors.removeFirst();
} else {
remainingActors.remove(actor);
}
clusteredActors.add(actor);
entityToFiringsPerIteration.put(actor, new Fraction(1));
pendingActors.addLast(actor);
while (!pendingActors.isEmpty()) {
Actor currentActor = (Actor) pendingActors.removeFirst();
Iterator actorPorts = ((ComponentEntity) currentActor)
.portList().iterator();
while (actorPorts.hasNext()) {
IOPort currentPort = (IOPort) actorPorts.next();
_propagatePort(container, currentPort,
entityToFiringsPerIteration, externalRates,
remainingActors, pendingActors, clusteredActors,
clusteredExternalPorts);
}
}
// Now we have _clusteredActors, which contains actors in
// one cluster (they are connected). Find the LCM of their
// denominator and normalize their firings. This means firings
// of actors are only normalized within their cluster.
int lcm = 1;
for (Iterator actors = clusteredActors.iterator(); actors.hasNext();) {
Actor currentActor = (Actor) actors.next();
Fraction fraction = (Fraction) entityToFiringsPerIteration
.get(currentActor);
int denominator = fraction.getDenominator();
lcm = Fraction.lcm(lcm, denominator);
}
// Got the normalizing factor.
Fraction lcmFraction = new Fraction(lcm);
for (Iterator actors = clusteredActors.iterator(); actors.hasNext();) {
Actor currentActor = (Actor) actors.next();
Fraction repetitions = ((Fraction) entityToFiringsPerIteration
.get(currentActor)).multiply(lcmFraction);
if (repetitions.getDenominator() != 1) {
throw new InternalErrorException(
"Failed to properly perform"
+ " fraction normalization.");
}
entityToFiringsPerIteration.put(currentActor, repetitions);
}
for (Iterator externalPorts = clusteredExternalPorts.iterator(); externalPorts
.hasNext();) {
IOPort port = (IOPort) externalPorts.next();
Fraction rate = ((Fraction) externalRates.get(port))
.multiply(lcmFraction);
if (rate.getDenominator() != 1) {
throw new InternalErrorException(
"Failed to properly perform"
+ " fraction normalization.");
}
externalRates.put(port, rate);
}
clusteredActors.clear();
clusteredExternalPorts.clear();
}
_checkDirectInputOutputConnection(container, externalRates);
if (!remainingActors.isEmpty()) {
// If there are any actors left that we didn't get to, then
// this is not a connected graph, and we throw an exception.
StringBuffer messageBuffer = new StringBuffer(
"SDF scheduler found disconnected actors! "
+ "Usually, disconnected actors in an SDF model "
+ "indicates an error. If this is not an error, try "
+ "setting the SDFDirector parameter "
+ "allowDisconnectedGraphs to true.");
// Look through all the unreached actors. If any of them are
// in transparent composite actors that contain PortParameters,
// print a message.
// See http://bugzilla.ecoinformatics.org/show_bug.cgi?id=4086
// We only print messages about the first 99 PortParameters.
int count = 0;
StringBuffer portParameterMessageBuffer = new StringBuffer();
Set portParametersFound = new HashSet();
Set containersSeen = new HashSet();
for (Iterator actors = actorList.iterator(); actors.hasNext()
&& count < 100; count++) {
NamedObj actor = (NamedObj) (actors.next());
NamedObj actorContainer = actor.getContainer();
if (actorContainer instanceof CompositeActor
&& !((CompositeActor) actorContainer).isOpaque()
&& !containersSeen.contains(actorContainer)) {
containersSeen.add(actorContainer);
List portParameters = actorContainer
.attributeList(PortParameter.class);
for (Object portParameter : portParameters) {
if (!portParametersFound.contains(portParameter)) {
portParametersFound.add(portParameter);
portParameterMessageBuffer
.append(((PortParameter) portParameter)
.getFullName()
+ " ");
if (count > 100) {
break;
}
}
}
}
}
if (portParameterMessageBuffer.length() > 0) {
messageBuffer
.append("Note that some of the unreached actors are in "
+ "transparent composite actors that have PortParameters. "
+ "A transparent composite actor is composite actor that has "
+ "no local director. Transparent composite actors and "
+ "PortParameters are not compatible, the workaround is to "
+ "insert a director or remove the PortParameter. "
+ "\nThe PortParameters:\n"
+ portParameterMessageBuffer.toString());
if (count >= 99) {
messageBuffer.append("...");
}
}
messageBuffer.append("\nUnreached Actors:\n");
count = 0;
for (Iterator unreachedActors = remainingActors.iterator(); unreachedActors
.hasNext()
&& count < 100; count++) {
NamedObj unreachedActor = (NamedObj) (unreachedActors.next());
messageBuffer.append(unreachedActor.getFullName() + " ");
}
if (count >= 99) {
messageBuffer.append("...");
}
messageBuffer.append("\nReached Actors:\n");
List reachedActorList = new LinkedList();
reachedActorList.addAll(actorList);
reachedActorList.removeAll(remainingActors);
count = 0;
for (Iterator actors = reachedActorList.iterator(); actors
.hasNext()
&& count < 100; count++) {
Entity entity = (Entity) actors.next();
messageBuffer.append(entity.getFullName() + " ");
}
if (count >= 99) {
messageBuffer.append("...");
}
throw new NotSchedulableException(messageBuffer.toString());
}
return entityToFiringsPerIteration;
}
///////////////////////////////////////////////////////////////////
//// private methods ////
/** Update the The external rates of those directly connected input
* and output ports to be 1. So a direct connection will transfer
* one token in each execution of the schedule.
*
* @param container The container that is being scheduled.
* @param externalRates A map from external ports of container to
* the fractional rates of that port. The external rates of
* those directly connected input and output ports will be updated
* to be 1 during this method.
*/
private void _checkDirectInputOutputConnection(CompositeActor container,
Map externalRates) {
Iterator inputPorts = container.inputPortList().iterator();
while (inputPorts.hasNext()) {
IOPort inputPort = (IOPort) inputPorts.next();
Fraction rate = (Fraction) externalRates.get(inputPort);
if (rate.equals(Fraction.ZERO)) {
// Check to make sure that if this port is an external
// input port, then it does not drive the same relation as some
// other output port or some other external input port.
// This results in a non-deterministic merge and is illegal.
Iterator connectedPorts = inputPort.deepInsidePortList()
.iterator();
// Make sure any connected output ports are connected on
// the inside.
while (connectedPorts.hasNext()) {
IOPort connectedPort = (IOPort) connectedPorts.next();
// connectPort might be connected on the inside to the
// currentPort, which is legal. The container argument
// is always the container of the director, so any port
// that has that container must be connected on the inside.
if (connectedPort.isOutput()
&& (connectedPort.getContainer() != container)) {
throw new NotSchedulableException(inputPort,
connectedPort,
"External input port drive the same relation "
+ "as an output port. "
+ "This is not legal in SDF.");
} else if (connectedPort.isInput()
&& (connectedPort.getContainer() == container)) {
throw new NotSchedulableException(inputPort,
connectedPort,
"External input port drives the same relation "
+ "as another external input port. "
+ "This is not legal in SDF.");
}
}
boolean isDirectionConnection = true;
List insideSinkPorts = inputPort.insideSinkPortList();
// A dangling port has zero rate.
if (insideSinkPorts.isEmpty()) {
isDirectionConnection = false;
} else {
// If the zero external port rate is due to the rate
// propagation from a contained actor (i.e., connected to the
// zero rate port of the actor), then the zero external rate
// must be preserved.
Iterator sinkPorts = insideSinkPorts.iterator();
while (sinkPorts.hasNext()) {
IOPort sinkPort = (IOPort) sinkPorts.next();
if (sinkPort.getContainer() != container) {
isDirectionConnection = false;
break;
}
}
}
if (isDirectionConnection) {
externalRates.put(inputPort, new Fraction(1));
Iterator sinkPorts = insideSinkPorts.iterator();
while (sinkPorts.hasNext()) {
IOPort sinkPort = (IOPort) sinkPorts.next();
externalRates.put(sinkPort, new Fraction(1));
}
}
}
}
}
/** Search the given list of actors for one that contains at least
* one port that has zero rate.
*
* @param actorList The list of all of the actors to search.
* @return An actor that contains at least one zero rate port, or null
* if no actor has a zero rate port.
*/
private ComponentEntity _pickZeroRatePortActor(List actorList)
throws IllegalActionException {
for (Iterator actors = actorList.iterator(); actors.hasNext();) {
ComponentEntity actor = (ComponentEntity) actors.next();
// Check if this actor has any ports with rate of zero.
for (Iterator ports = actor.portList().iterator(); ports.hasNext();) {
IOPort port = (IOPort) ports.next();
if (DFUtilities.getRate(port) == 0) {
return actor;
}
}
}
return null;
}
/** Propagate the number of fractional firings decided for this actor
* through the specified port. Compute the fractional
* firing ratio for each actor that is connected to the given port.
* If we have not previously computed the ratio for an
* actor, then store the value in the given map of firing ratios and move
* the actor from the remainingActors list to the pendingActors list.
* If the value has been previously computed and is not the same,
* then the model is not schedulable and an exception will be thrown.
* Note that ports directly contained by the given container are
* handled slightly differently from other ports. Most importantly,
* their rates are propagated to ports they are connected to on the
* inside, as opposed to ports they are connected to on the outside.
*
* @param container The actor that is being scheduled.
* @param currentPort The port that we are propagating from.
* @param entityToFiringsPerIteration The current Map of
* fractional firing ratios for each actor. This map will be
* updated if the ratio for any actor has not been previously
* computed.
* @param externalRates A map from external ports of container to
* the fractional rates of that port. This will be updated
* during this method.
* @param remainingActors The set of actors that have not had their
* fractional firing set. This will be updated during this method.
* @param pendingActors The set of actors that have had their rate
* set, but have not been propagated onwards. This will be updated
* during this method.
* @param clusteredActors The set of actors that are within one
* cluster, i.e., they are connected.
* @param clusteredExternalPorts The set of external ports that
* are connected with the same cluster of actors.
*
* @exception NotSchedulableException If the model is not
* schedulable.
* @exception IllegalActionException If the expression for a
* rate parameter is not valid.
*/
private void _propagatePort(CompositeActor container, IOPort currentPort,
Map entityToFiringsPerIteration, Map externalRates,
LinkedList remainingActors, LinkedList pendingActors,
Set clusteredActors, Set clusteredExternalPorts)
throws NotSchedulableException, IllegalActionException {
ComponentEntity currentActor = (ComponentEntity) currentPort
.getContainer();
// First check to make sure that this port is not connected to
// any other output ports on the outside.
// This results in a non-deterministic merge and is illegal.
// Do not do this test for output ports where we are propagating
// inwards instead of outwards.
if (currentPort.isOutput() && (currentPort.getContainer() != container)) {
Iterator connectedPorts = currentPort.deepConnectedPortList()
.iterator();
// Make sure any connected output ports are connected on
// the inside.
while (connectedPorts.hasNext()) {
IOPort connectedPort = (IOPort) connectedPorts.next();
// connectedPort might be connected on the inside to the
// currentPort, which is legal. The container argument
// is always the container of the director, so any port
// that has that container must be connected on the inside.
if (connectedPort.isOutput()
&& (connectedPort.getContainer() != container)) {
throw new NotSchedulableException(currentPort,
connectedPort,
"Output ports drive the same relation. "
+ "This is not legal in SDF.");
} else if (connectedPort.isInput()
&& (connectedPort.getContainer() == container)) {
throw new NotSchedulableException(currentPort,
connectedPort,
"Output port drives the same relation "
+ "as the external input port. "
+ "This is not legal in SDF.");
}
}
}
// Next check to make sure that if this port is an external
// input port, then it does not drive the same relation as some
// other output port or some other external input port.
// This results in a non-deterministic merge and is illegal.
if (currentPort.isInput() && (currentPort.getContainer() == container)) {
Iterator connectedPorts = currentPort.deepInsidePortList()
.iterator();
// Make sure any connected output ports are connected on
// the inside.
while (connectedPorts.hasNext()) {
IOPort connectedPort = (IOPort) connectedPorts.next();
// connectPort might be connected on the inside to the
// currentPort, which is legal. The container argument
// is always the container of the director, so any port
// that has that container must be connected on the inside.
if (connectedPort.isOutput()
&& (connectedPort.getContainer() != container)) {
throw new NotSchedulableException(currentPort,
connectedPort,
"External input port drive the same relation "
+ "as an output port. "
+ "This is not legal in SDF.");
} else if (connectedPort.isInput()
&& (connectedPort.getContainer() == container)) {
throw new NotSchedulableException(currentPort,
connectedPort,
"External input port drives the same relation "
+ "as another external input port. "
+ "This is not legal in SDF.");
}
}
}
// Director director = (Director) getContainer();
// CompositeActor model = (CompositeActor) director.getContainer();
CompositeActor model = container; //FIXME
// Get the rate of this port.
int currentRate;
if (currentActor == model) {
currentRate = 1;
} else {
currentRate = DFUtilities.getRate(currentPort);
}
// Port rates of less than zero are not valid.
if (currentRate < 0) {
throw new NotSchedulableException(currentPort,
"Rate cannot be less than zero. It was: " + currentRate);
}
// Propagate to anything that this port is connected to. For
// external ports, this is anything that is connected on the
// inside. For ports of actors that are being scheduled, this is
// anything that is connected on the outside.
Iterator connectedPorts;
if (currentPort.getContainer() == container) {
// Find all the ports that are deeply connected to
// current port on the inside.
if (_debugging && VERBOSE) {
// Move this inside and avoid FindBugs Dead Local Store
connectedPorts = currentPort.deepInsidePortList().iterator();
_debug("deepInsidePortList of " + currentPort);
while (connectedPorts.hasNext()) {
_debug(connectedPorts.next().toString());
}
}
connectedPorts = currentPort.deepInsidePortList().iterator();
} else {
connectedPorts = currentPort.deepConnectedPortList().iterator();
}
// For every port we are connected to.
while (connectedPorts.hasNext()) {
IOPort connectedPort = (IOPort) connectedPorts.next();
ComponentEntity connectedActor = (ComponentEntity) connectedPort
.getContainer();
if (_debugging && VERBOSE) {
_debug("Propagating " + currentPort + " to "
+ connectedActor.getName());
}
int connectedRate;
if (connectedActor == model) {
connectedRate = 1;
} else {
connectedRate = DFUtilities.getRate(connectedPort);
}
// currentFiring is the firing ratio that we've already
// calculated for currentActor
Fraction currentFiring = (Fraction) entityToFiringsPerIteration
.get(currentActor);
// Compute the firing ratio that we think connected actor
// should have, based on its connection to currentActor
Fraction desiredFiring;
// HDF actors might have zero rates...
if ((currentRate == 0) && (connectedRate > 0)) {
// The current port of the current actor has a rate
// of 0, and the current connected port of the
// connected actor has a positive integer rate.
// therefore, we must set the firing count of
// the connected actor to 0 so that it will
// not appear in the final static schedule.
desiredFiring = Fraction.ZERO;
} else if ((currentRate > 0) && (connectedRate == 0)) {
// The current port of the current actor has a
// positive integer rate, and the current
// connected port of the connected actor has
// rate of 0. therefore, we set the firing
// count of the current actor to 0 so that
// it will not appear in the final static schedule.
currentFiring = Fraction.ZERO;
// Update the entry in the firing table.
entityToFiringsPerIteration.put(currentActor, currentFiring);
// Set the firing count of the connected actor to
// be 1.
desiredFiring = new Fraction(1);
} else if ((currentRate == 0) && (connectedRate == 0)) {
// Give the connected actor the same rate as the
// current actor.
desiredFiring = currentFiring;
} else {
// Both the rates are non zero, so we can just do the
// regular actor propagation.
desiredFiring = currentFiring.multiply(new Fraction(
currentRate, connectedRate));
}
// Now, compare the firing ratio that was computed before
// with what we just determined.
// This should be either
// the firing that we computed previously, or null
// if the port is an external port, or _minusOne if
// we have not computed the firing ratio for this actor yet.
Fraction presentFiring = (Fraction) entityToFiringsPerIteration
.get(connectedActor);
if (_debugging && VERBOSE) {
_debug("presentFiring of connectedActor " + connectedActor
+ " = " + presentFiring);
}
// if (presentFiring == null) {
// Make sure to check for presentFiring == null here so that
// we avoid a NullPointerException if the model is ill formed.
// I had problems here with a bug in Publisher.clone() and
// Subscriber.clone() where presentFiring was null.
// Getting a NullPointerException is bad, we should check
// for null and try to give a better message.
if (connectedActor == model || presentFiring == null) {
// We've gotten out to an external port.
// Temporarily create the entry in the firing table.
// This is possibly rather fragile.
entityToFiringsPerIteration.put(connectedActor, desiredFiring);
// Compute the external rate for this port.
Fraction rate = currentFiring.multiply(new Fraction(
currentRate, 1));
Fraction previousRate = (Fraction) externalRates
.get(connectedPort);
if (previousRate == null) {
// This can happen if we somehow have a link to a port
// within a class definition.
// Give better error message than null pointer exception.
throw new InternalErrorException(
"Invalid connection found between ports: "
+ currentPort.getFullName() + " and "
+ connectedPort.getFullName());
}
//if (previousRate.equals(Fraction.ZERO)) {
if (!clusteredExternalPorts.contains(connectedPort)) {
clusteredExternalPorts.add(connectedPort);
externalRates.put(connectedPort, rate);
_propagatePort(container, connectedPort,
entityToFiringsPerIteration, externalRates,
remainingActors, pendingActors, clusteredActors,
clusteredExternalPorts);
} else if (!rate.equals(previousRate)) {
// The rates don't match.
throw new NotSchedulableException("No solution "
+ "exists for the balance equations.\n"
+ "Graph is not "
+ "consistent under the SDF domain "
+ "detected on external port "
+ connectedPort.getFullName());
}
// _propagatePort(container, connectedPort,
// entityToFiringsPerIteration, externalRates,
// remainingActors, pendingActors, clusteredActors,
// clusteredExternalPorts);
// entityToFiringsPerIteration.remove(connectedActor);
} else if (presentFiring.equals(_minusOne)) {
// So we are propagating here for the first time.
// Create the entry in the firing table.
entityToFiringsPerIteration.put(connectedActor, desiredFiring);
// Remove them from remainingActors.
remainingActors.remove(connectedActor);
clusteredActors.add(connectedActor);
// and add them to the pendingActors.
pendingActors.addLast(connectedActor);
} else if (!presentFiring.equals(desiredFiring)) {
// So we've already propagated here, but the
// firingsPerIteration don't match.
throw new NotSchedulableException(this, "No solution "
+ "exists for the balance equations.\n"
+ "Graph is not " + "consistent under the SDF domain "
+ "detected on external port "
+ connectedPort.getFullName());
}
if (_debugging && VERBOSE) {
_debug("New Firing: ");
_debug(entityToFiringsPerIteration.toString());
}
}
}
///////////////////////////////////////////////////////////////////
//// private variables ////
/** A fraction equal to -1. Used in several places to indicate an
* actor for which we have not determined the number of times it will
* fire.
*/
private Fraction _minusOne = new Fraction(-1);
}