Page 6 out of 24 total pages


3 Actor Libraries

Authors: Edward A. Lee
                Yuhong Xiong

3.1 Overview

Ptolemy II is about component-based design. Components are aggregated and interconnected to construct a model. One of the advantages of such an approach to design is that re-use of components becomes possible. In Ptolemy II, re-use potential is maximized through the use of polymorphism. Polymorphism is one of the key tenets of object-oriented design. It refers to the ability of a component to adapt in a controlled way to the type of data being supplied. For example, an addition operation is realized differently for vectors vs. scalars.

We call this classical form of polymorphism data polymorphism, because objects are polymorphic with respect to data types. A second form of polymorphism, introduced in Ptolemy II, is domain polymorphism, where a component adapts in a controlled way to the protocols that are used to exchange data between components. For example, an addition operation can accept input data delivered by any of a number of mechanisms, including discrete-events, rendezvous, or asynchronous message passing.

Ptolemy includes libraries of polymorphic actors using both kinds of polymorphism to maximize re-usability. Actors from these libraries can be used in a broad range of domains, where the domain provides the communication protocol between actors. In addition, most of these actors are data polymorphic, meaning that they can operate on a broad range of data types. In general, writing data and domain polymorphic actors is considerably more difficult than writing more specialized actors. This chapter discusses some of the issues.

3.2 Library Organization

Two key libraries of domain-polymorphic actors are provided by Ptolemy II. The actors with graphical user interface (GUI) functions are collected in the ptolemy.actor.gui package, and the rest are in ptolemy.actor.lib. Domain-specific actors are in ptolemy.domains.x.lib, where "x" is the domain name.

3.2.1 Actor.Lib

Figure 3.1 shows a UML static structure diagram for the ptolemy.actor.lib package (see appendix A of chapter 1 for an introduction to UML). All the classes in figure 3.1 extend TypedAtomicActor, except TimedActor and SequenceActor, which are interfaces. TypedAtomicActor is in the ptolemy.actor package, and is described in more detail in the Actor chapter. For our purposes here, it is sufficient to know that it provides a base class for actors with ports where the ports carry typed data (encapsulated in objects called tokens).

The grey boxes in figure 3.1 are not standard UML. They are used here to aggregate actors with common inheritance, or to refer to a set of actors (sources and sinks) that are enumerated later in this chapter.

None of the classes in figure 3.1 have any methods, except those inherited from the base classes, which are not shown. By convention, actors in Ptolemy II expose their ports and parameters as public members, and much of the functionality of an actor is accessed through the ports and parameters.

Many of the actors in this package are transformers, which extend the Transformer class. These actors read input data, modify it in some way, and produce output data. AddSubtract, MultiplyDivide, and Expression are also transformers, but they do not extend Transformer because they have somewhat unconventional port names (or in the case of Expression, no pre-defined ports at all).

The interfaces TimedActor and SequenceActor play a very important role in this library. They are empty interfaces (no methods), and hence are used only as markers. An actor that implements SequenceActor declares that its inputs are assumed to be sequences of distinct data values, and that the outputs it produces will be sequences of distinct data values. Thus, for example, the Average actor computes a running average of the input tokens. By contrast, Sine does not implement SequenceActor, because it does not care whether the input is a sequence. In particular, the order in which inputs are presented is irrelevant, and whether a particular input is presented more than once is irrelevant. Implementing the TimedActor interface declares that the current time in a model execution affects the behavior of the actor.

Some domains can only execute a subset of the actors in this library. In particular, some domains cannot handle actors that implement SequenceActor. For example, the CT domain (continuous time), which solves ordinary differential equations, may present data to actors that represents arbitrarily closely spaced samples of a continuous-time signal. Thus, the data presented to an actor cannot be viewed as a sequence, since the domain determines how closely spaced the samples are. For example, the Average actor would produce unpredictable results, since the spacing of samples is likely to be uneven over time. Thus, it is up to the director in the CT domain to reject actors that implement SequenceActor.

Currently, all domains can execute actors that implement TimedActor, because all directors provide a notion of current time. However, the results may not be what is expected. The SDF domain (synchronous dataflow), for example, does not advance current time. Thus, if it is the top-level domain, current time will always be zero, which is likely to lead to some confusion with timed actors.

3.2.2 Actor.GUI

Figure 3.2 shows a UML static structure diagram for the ptolemy.actor.gui package. These actors all have graphical user interface (GUI) functions. The TimedPlotter, for example, which was used in the previous chapter, displays a plot of its input data as a function of time. SequencePlotter, by contrast, ignores current time, and uses for the horizontal axis the position of an input token in a sequence of inputs. XYPlotter, by contrast, uses neither time nor the sequence number, and therefore implements neither TimedActor nor SequenceActor. All three are derived from Plotter, an abstract base class with a public member, plot, which implements the plot.

This package includes the Placeable interface, discussed in the previous chapter. Actors that implement this interface have graphical widgets that a user of the actor may wish to place on the screen. The setPanel() method is used to specify where to place the graphical element.

3.3 Data Polymorphism

A data polymorphic actor is one that can operate on any of a number of input data types. For example, AddSubtract can accept any type of input. Addition and subtraction are possible on any type of token because they are defined in the base class Token.

Figure 3.3 shows the methods defined in the base class Token. All data exchanged between actors in Ptolemy is wrapped in an instance of Token (or more precisely, in an instance of a class derived from Token). Notice that add() and subtract() are methods of this base class. This makes it easy to implement a data polymorphic adder.

The fire method of the AddSubtract actor is shown in figure 3.4. In this code, we first iterate through the channels of plus input. The first token read (by the get() method) is assigned to sum. Subsequently, the polymorphic add() method of that token is used to add additional tokens. The second iteration, over the channels at the minus input port, is slightly trickier. If no tokens were read from the plus input, then the variable sum is initialized by calling the polymorphic zero() method of the first token read at the minus port. The zero() method returns whatever a zero value is for the token in question.

Not all classes derived from Token override all its methods. For example, StringToken overrides add() but not subtract(). Adding strings means simply concatenating them, but it is hard to assign a reasonable meaning to subtraction. Thus, if AddSubtract is used on strings, then the minus port had better never receive tokens. It may be simply left disconnected, in which case minus.getWidth() returns zero. If the subtract() method of a StringToken is called, then a runtime exception will be thrown.

3.4 Domain Polymorphism

Most actors access their ports as shown in figure 3.4, using the hasToken(), get(), and broadcast() methods. Those methods are polymorphic, in that their exact behavior depends on the domain. For example, get() in the CSP domain causes a rendezvous to occur, which means that the calling thread is suspended until another thread sends data to the same port (using, for example, the broadcast() method on one of its ports). Correspondingly, a call to broadcast() causes the calling thread to suspend until some other thread calls a corresponding get(). In the PN domain, by contrast, broadcast() returns immediately (if there is room in the channel buffers), and only get() causes the calling thread to suspend.

Each domain has slightly different behavior associated with hasToken(), get(), broadcast() and other methods of ports. The actor, however, does not really care. The fire() method shown in figure 3.4 will work for any reasonable implementation of these methods. Thus, the AddSubtract actor is domain polymorphic.

Domains also have different behavior with respect to when the fire() method is invoked. In process-oriented domains, such as CSP and PN, a thread is created for each actor, and an infinite loop is created to repeatedly invoke the fire() method. Moreover, in these domains, hasToken() always returns true, since you can call get() on a port and it will not return until there is data available. In the DE domain, the fire() method is invoked only when there are new inputs that happen to be the oldest ones in the systems, and hasToken() returns true only if there is new data on the input port.

The simplest domain polymorphic actors are stateless, meaning that they store no data from one firing to the next. Such actors might also be called functional, since the output is a function of the input (only). For such actors, it is evident that when the fire() method is invoked is not important. The AddSubtract actor, for example, simply operates on whatever inputs are available. To understand how actors with state behave, we need to explain how actors are invoked.

3.4.1 Iterations

Invocation of actors in all domains follows a particular sequence. First, the initialize() method is invoked, exactly once. This method is invoked prior to type resolution, so the types of the ports may not have yet been determined. At the end of the execution, the wrapup() method is invoked, again exactly once (unless an exception occurs, in which case it may not be invoked at all).

An iteration of an actor is defined to be one invocation of prefire(), any number of invocations of fire(), and one invocation of postfire(). An execution is defined to be one invocation of initialize(), followed by any number of iterations, followed by one invocation of wrapup(). The methods initialize(), prefire(), fire(), postfire(), and wrapup() are called the action methods.

For more details, see the Actor Package chapter.

3.4.2 Domains with Fixed Point Semantics

The reason for allowing an iteration to consist of any number of invocations of the fire() method is that some domains have fixed-point semantics. This means that the tokens produced at the output converge during an iteration to a final, correct value. Early in an iteration the output values may be approximations, or may be absent. The semantics of the domain is that only the last outputs produced in the iteration are correct.

For example, directors in the CT domain, which models continuous-time systems, begin an iteration by estimating the time step that the iteration should take to get reasonable accuracy. They invoke the prefire() method of all actors, then the fire() method. They then query the actors to determine whether the results were sufficiently accurate. Domain-polymorphic actors are always content with the result, but some domain-specific actors may respond with "no, the time step was too big." The director then needs to recalculate the time step, and re-invoke the fire() methods of all the actors. Once all actors respond that the results are acceptable, the postfire() methods of all the actors are invoked.

The notion that the actors can specify whether the result is acceptable means that an iteration concludes when all actors are content (the solution has converged). That is, given the observed inputs, the output produced by each actor is correct. This is called a fixed point by analogy with the following mathematical equation:

(1) .

A solution x to this equation is called a fixed point of the function f. A candidate solution x is "accept able" to the function if when presented as an argument to the function, the result of the function is equal to x. In general, x may be a vector. The analogy, therefore, is that x is a vector of values of all signals at the conclusion of an iteration, and f is the collective effect of all the actors. The values in the vector x are acceptable if all actors find their current outputs consistent with their current inputs.

3.4.3 Actors with State

The fact that the fire() method may be invoked with approximate inputs, and may be permitted to produce approximate outputs has fairly profound implications on the design of domain-polymorphic actors. In particular, if the actor maintains state information, then it should not update that state until the postfire() method, after convergence has been reached.

It is instructive to examine the fire() and postfire() methods of the Average actor, shown in figure 3.5. This actor extends Transformer, which provides ports named input and output. It adds an additional port called reset. Its state is stored as private variables, _sum and _count. The _sum variable is the sum of all inputs it has seen. Clearly, it should sum only input values that are the final values of an iteration, not approximate or tentative values. The _count variable counts the number of iterations, which in general may be fewer than the number of invocations of the fire() method.

The general strategy here is to create two shadow variables, _latestSum and _latestCount, and update these in the fire() method. The postfire() method then simply copies the values of these variables into the permanent state variables, _sum and _count.

Examining the fire() method, we see that the first thing it does is to set _latestSum and _latestCount equal to the permanent state variables. Thus, if the fire() method has been previously invoked in this iteration, the results of that invocation are now discarded.

The fire() method then checks to see whether there is a reset input, and if there is, it sets the shadow variables accordingly, as if this were the first input being seen. It then obtains an input, and uses the polymorphic add() method of the input token to add the input to the shadow sum. Finally, it uses the polymorphic divide() method of the input token to divide by the total number of inputs seen since the last reset.

3.5 Descriptions of Libraries

Here we briefly describe the actors in the ptolemy.actor.lib and ptolemy.actor.gui libraries. This should be viewed as a summary only. Refer to the class documentation for a complete description of these actors. The summary is useful, however, because these actors are carefully designed to be extensively re-usable. Some general terms that may be useful in interpreting the descriptions are:

lub: Least upper bound, referring particularly to data types. For typical data polymorphic actors, the output data type is the lub of the input data types. This means that each input data type can be losslessly converted to the type of the output. In some cases, the output data type also depends on the type of parameters. See the Type System chapter for more detail.
multiport: A port that links to any number of channels. Ports described below are multiports only if they say so explicitly. Multiports can be left disconnected in all domains, in which case no inputs are read. Multiports resolve to a single data type, so all channels must have the same data type.

It is also useful to know some general patterns of behavior.

3.5.1 Functional Actors

The functional actors in the ptolemy.actor.lib are shown in figure 3.1. They are summarized here.

AbsoluteValue
Ports: input (DoubleToken), output (DoubleToken).
Produce an output token on each firing with a value that is equal to the absolute value of the input.
AddSubtract
Ports: plus (multiport, polymorphic), minus (multiport, polymorphic), output (lub(plus, minus)).
Add tokens on the plus input and subtract tokens on the minus input.
Maximum
Ports: input (multiport, DoubleToken), output (DoubleToken).
Produce an output token on each firing with a value that is the maximum of the input values.
Minimum
Ports: input (multiport, DoubleToken), output (DoubleToken).
Produce an output token on each firing with a value that is the minimum of the input values.
MultiplyDivide
Ports: multiply (multiport, polymorphic), divide (multiport, polymorphic), output (lub(multiply, divide)).
Multiply tokens on the multiply input and divide by tokens on the divide input.
Quantizer
Ports: input (DoubleToken), output (DoubleToken).
Parameters: levels (DoubleMatrixToken).
Produce an output token with the value in levels that is closest to the input value.
Scale
Ports: input (polymorphic), output (lub(input, gain)).
Parameters: gain (polymorphic).
Produce an output that is the product of the input and the gain.
Sine
Ports: input (DoubleToken), output (DoubleToken).
Parameters: amplitude (DoubleToken), omega (DoubleToken), phase (DoubleToken).
Produce an output token with value equal to amplitude × sin((omega × input) + phase). Note that this can be used to compute a cosine by setting phase to -PI/2.

3.5.2 Polymorphic Sources

Source actors are shown in figure 3.6. All of these actors have a trigger input, which is a multiport specifically so that it can be left disconnected in all domains (if disconnected, it has width zero). The trigger input can be used to force an output in domains where the firing of an actor is driven by input data. In DE, for example, it causes the current value of the source to be produced at the time stamp of the trigger input. In SDF and PN, if the trigger input is not connected, then the actor simply fires often enough to supply the destination actors with tokens.

Those sources that implement TimedActor have a parameter stopTime. When the current time of the model reaches this time, then postfire() returns false, requesting of the director that this actor not be invoked again. This can be used to generate a finite source signal. By default, this parameter has value 0.0, which indicates unbounded execution.

Some of these source actors use the fireAt() method of the director to request firing at particular times. Such actors will fire repeatedly even if there is no trigger input, even in domains (like DE) that fire actors only in response to input events. The fireAt() method schedules an event in the future to refire the actor.

Those sources that implement SequenceActor have a parameter firingCountLimit. When the number of iterations of the actor reaches this limit, then postfire() returns false, requesting of the director that this actor not be invoked again. This can be used to generate a finite source signal. By default, this parameter has value 0, which indicates unbounded execution.

Bernoulli
Ports: trigger (input multiport, Token), output (BooleanToken).
Parameters: trueProbability (DoubleToken), seed (LongToken).
Produce a random sequence of booleans (a source of coin flips).
Clock (implements TimedActor)
Ports: trigger (input multiport, Token), output (lub(elements of values)).
Parameters: offsets (DoubleMatrixToken), period (DoubleToken), values (MatrixToken), stopTime (DoubleToken).
Produce a piecewise-constant, periodic signal (or at minimum, a sequence of events corresponding to transitions in this signal). This actor uses fireAt() to schedule firings when time matches the transition times.
Const
Ports: trigger (input multiport, Token), output (type of value).
Parameters: value (polymorphic).
Produce a constant output with value given by value.
CurrentTime (implements TimedActor)
Ports: trigger (input multiport, Token), output (DoubleToken).
Parameters: stopTime (DoubleToken).
Produce an output token with value equal to the current time.
Gaussian
Ports: output (DoubleToken).
Parameters: mean (DoubleToken), standardDeviation (DoubleToken), seed (LongToken).
Produce a random sequence where each output is the outcome of a Gaussian random variable.
Poisson
Ports: trigger (input multiport, Token), output (lub(elements of values)).
Parameters: meanTime (DoubleToken), values (MatrixToken), stopTime (DoubleToken).
Produce a piecewise-constant signal where transitions occur according to a Poisson process (or at minimum, a sequence of events corresponding to transitions in this signal). This actor uses fireAt() to schedule firings at time intervals determined by independent and identically distributed exponential random variables with mean meanTime.
Pulse (implements SequenceActor)
Ports: trigger (input multiport, Token), output (lub(elements of values)).
Parameters: indexes (IntMatrixToken), values (MatrixToken), firingCountLimit (IntToken)
Produce a sequence of values at specified iterations.
Ramp (implements SequenceActor)
Ports: trigger (input multiport, Token), output (lub(init, step)).
Parameters: init (polymorphic), step (polymorphic), firingCountLimit (IntToken)
Produce a sequence that begins with the value given by init and is incremented by step after each iteration.

3.5.3 Polymorphic Sinks and Displays

The following actors are in the ptolemy.actor.lib package (see figure 3.7) if they have no graphical component, and in ptolemy.actor.gui (see figure 3.8) otherwise. Several of the plotters have a fillOnWrapup parameter, which has a boolean value. If the value is true (the default), then at the conclusion of the execution of the model, the axes of the plot will be adjusted to just fit the observed data.

FileWrite
Ports: input (multiport, Token).
Parameters: filename (StringToken).
Write the string representation of input tokens to the specified file or to standard out.
HistogramPlotter
Ports: input (multiport, DoubleToken).
Parameters: fillOnWrapup (BooleanToken).
Display a histogram of the data on each input channel.
Print
Ports: input (multiport, Token).
Display a string representation of the data on each input channel in a text area on the screen.
Recorder
Ports: input (multiport, Token).
Record the inputs for later querying. This actor is used extensively in the Ptolemy II test suites, and can also be used to collect data for display at the conclusion of an execution.
SequencePlotter
Ports: input (multiport, DoubleToken).
Parameters: fillOnWrapup (BooleanToken).
Display a plot of the data on each input channel vs. the iteration count for the actor.
TimedPlotter
Ports: input (multiport, DoubleToken).
Parameters: fillOnWrapup (BooleanToken).
Display a plot of the data on each input channel vs. the current time when the data is consumed.
XYPlotter
Ports: inputX (multiport, DoubleToken), inputY (multiport, DoubleToken).
Parameters: fillOnWrapup (BooleanToken).
Display a plot of the data on each inputX channel vs. the data on the corresponding inputY channel.

3.5.4 Expression Actor

One particularly powerful actor is the Expression actor, which can have any number of input ports, and produces an output that is an arbitrary expression involving the inputs. The expression just refers to the inputs by the name of the port. The expression language is described in the Data Package chapter. Notice that by default, there are no input ports. The user of this actor must create ports (instances of TypedIOPort) and add them to this actor by calling their setContainer() method. The expression can also refer to current time by the variable "time" and to the current iteration count by the variable "iteration."

Expression
Ports: output (polymorphic).
Parameters: expression (polymorphic).
On each firing, evaluate the expression parameter, whose value is set by an expression that may include references to any input ports that have been added to the actor.

3.5.5 Other Actors

Average (implements SequenceActor)
Ports: input (polymorphic), output (type of input), reset (multiport, BooleanToken).
Produce on each firing the average of all the inputs received since the last true on reset. The reset input may be left disconnected.
Commutator (implements SequenceActor)
Ports: input (multiport, polymorphic), output (type of input).
Interleave the data on the input channels into a single sequence on the output.
Distributor (implements SequenceActor)
Ports: input (polymorphic), output (multiport, type of input).
Distribute the data on the input sequence into a multiple sequences on the output channels.



Page 6 out of 24 total pages


ptII at eecs berkeley edu Copyright © 1998-1999, The Regents of the University of California. All rights reserved.