Ptolemy II Heterogeneous Concurrent Modeling and D PDF
Ptolemy II Heterogeneous Concurrent Modeling and D PDF
Sponsored by
Defense Advanced Research Projects Agency
DARPA Order No. El 17/87
The views and conclusions contained in this document are those of the authors and should not be
interpreted as necessarily representing the official policies, either expressed or implied, of the
Defense Advanced Research Projects Agency or the U.S. Government.
Copyright ©1998-2001
The Regents of the University of California
All rights reserved
20010706 106
This report has been reviewed by the Air Force Research Laboratory, Information
Directorate, Public Affairs Office (IFOIPA) and is releasable to the National Technical
Information Service (NTIS). At NTIS it will be releasable to the general public,
including foreign nations.
APPROVED:
JS P. HANNA
Project Engineer
If your address has changed or if you wish to be removed from the Air Force Research
Laboratory Rome Research Site mailing list, or if the addressee is no longer employed by
your organization, please notify AFRL/IFTC, 26 Electronic Pky, Rome, NY 13441-4514.
This will assist us in maintaining a current mailing list.
Do not return copies of this report unless contractual obligations or notices on a specific
document require that it be returned.
PTOLEMY II, HETEROGENEOUS CONCURRENT
MODELING AND DESIGN IN JAVA
Public reporting burden lor this collection of information is estimated to average 1 hour par response, including the time for reviewing instructions, searching existing data sources, gathering and mainteininfl the data needed, and completing and reviewing
the collection of information. Send comments regarding this burden estimate or eny other aspect of this collection of information, including suggestions for reducing this burden, to Weshington Headquarters Services, Directorate for Information
Operations and Reports, 1215 Jefferson Davis Highway, Suite 1204, Arlington, VA 22202-4302, and to the Office of Management and Budget, Paperwork Reduction Project (0704-01881, Washington, DC 20503
1. AGENCY USE ONLY (Leave blank) 2. REPORT DATE 3. REPORT TYPE AND DATES COVERED
MAY 2001 Final Nov 96 - Aug 00
4. TITLE AND SUBTITLE 5. FUNDING NUMBERS
PTOLEMY II, HETEROGENEOUS CONCURRENT MODELING AND DESIGN F - F3O602-97-C-0282
IN JAVA PE- 63739E
PR- El 17
6. AUTHOR(S)
TA- 00
Edward A. Lee, John Davis II, Christopher Hylands, Bart Kienhuis, Jie Liu, Xiaojun WU-32
Liu, Lukito Muliadi, Steve Neuendorffer, Jeff Tsay, Brian Vogel, and Yuhong Xiong
17. SECURITY CLASSIFICATION 18. SECURITY CLASSIFICATION 19. SECURITY CLASSIFICATION 20. LIMITATION OF
OF REPORT OF THIS PAGE OF ABSTRACT ABSTRACT
UNCLASSIFIED UNCLASSIFIED UNCLASSIFIED UL
Standard Form 298 (Rev. 2-891 (EG)
Prescribed by ANSI Std. 239.18
Designed using Perform Pro, WHSIDI0R, Dct 94
■
Table of Contents
1 Introduction
1.1 Modeling and Design 1
1.2 Architecture Design 3
1.3 Models of Computation 4
1.4 Choosing Models of Computation 8
1.5 Visual Syntaxes 8
1.6 Ptolemy II 9
Appendix A 17
Appendix B 21/22
2 Using Vergil
2.1 Introduction 23
2.2 Quick Start 24
2.3 Data Types and the Type System 25
2.4 Hierarchy 27
2.5 Broadcast Relations 28
2.6 SDF and Multirate Systems 29
2.7 Using the Plotter 30
3 MoML
3.1 Introduction 33
3.2 MoML Principles 25
3.3 Specification of a Model 37
3.4 Incremental Parsing 54
3.5 Parsing MoML 58
3.6 Exporting MoML 60
3.7 Special Attributes 61
3.8 Acknowledgements 61
Appendix C 62
4 Custom Applets
4.1 Introduction 67
4.2 HTML Files Containing Applets 67
Appendix D 81
5 Actor Libraries
5.1 Overview 89
5.2 Library Organization 89
5.3 Data Polymorphism 91
5.4 Domain Polymorphism
i
5.5 Descriptions of Libraries 95
6 Designing Actors
6.1 Overview 109
6.2 Anatomy of an Actor 110
6.3 Action Methods 118
6.4 Time 123
6.5 Code Format 124
7 The Kernel
7.1 Abstract Syntax 131/132
7.2 Non-Hierarchical Topologies 133/134
7.3 Support Classes 137
7.4 Clustered Graphs 139
7.5 Opaque Composite Entities 146
7.6 Concurrency 147
7.7 Mutations 150
7.8 Exceptions 153
8 Actor Package
8.1 Concurrent Computation 155
8.2 Message Passing 156
8.3 Execution 165
9 Data Package
9.1 Introduction 175
9.2 Data Encapsulation 175
9.3 Polymorphism 177
9.4 Variables and Parameters 180
9.5 Expressions 184
9.6 Fixed Point Data Type 189
Appendix E 193
10 Graph Package
10.1 Introduction 197
10.2 Classes and Interfaces in the Graph Package 198
10.3 Example Use 201
11 Type System
11.1 Introduction 205
11.2 Formulation 207
11.3 Structured Types 210
11.4 Implementation 211
11.5 Examples 216
ii
11.6 Actors Constructing Tokens with Structured Types 217
Appendix F 219
12 Plot Package
12.1 Overview 221
12.2 Using plots 222
12.3 Class Structure 227
12.4 PlotML File Format 232
12.5 Old Textual File Format 240
12.6 Compatibility 243/244
12.7 Limitations '243/244
13 Vergil
13.1 Introduction 245
13.2 Infrastructure 246
13.3 Architecture 247
13.4 Common Operations 251
13.5 Ptolemy Model Visualization 254
14 CT Domain
14.1 Introduction 260/261
14.2 Solving ODEs numerically 265
14.3 CT Actors 269
14.4 CT Directors 271
14.5 Interacting with Other Domains 273
14.6 CT Domain Demos 274
14.7 Implementation 279
Appendix G 286
15 DE Domain
15.1 Introduction 287
15.2 Overview of The Software Architecture 290
15.3 The DE Actor Library 292
15.4 Mutations 292
15.5 Writing DE Actors 295
15.6 Composing DE with Other Domains 301
16 SDF Domain
16.1 Purpose of the Domain 305
16.2 Using SDF 305
16.3 Properties of the SDF domain 308
16.4 Software Architecture 311
16.5 Actors 315/316
iii
17 CSP Domains
17.1 Introduction 317
17.2 CSP Communication Semantics 318
17.3 Example CSP Applications 321
17.4 Building CSP Applications 325
17.5 The CSP Software Architecture 327
17.6 Technical Details 332
18 DDE Domain
18.1 Introduction 339
18.2 DDE Semantics 339
18.3 Example DDE Applications 343
18.4 Building DDE Applications 344
18.5 The DDE Software Architecture 345
18.6 Technical Details 349/350
19 PN Domain
19.1 Introduction 351
19.2 Process Network Semantics 352
19.3 The PN Software Architecture 354
19.4 Technical Details 359
References 362
Glossary 367
BIndex 370
iv
List of Figures
V
Figure 4.5 An improved applet that properly reports errors in model
Construction 71
Figure 4.6 A modified applet that places the resulting plot in the
Browser window itself, as shown in figure 4.7 72
Figure 4.7 Applet with embedded plot as displayed in Netscape
Navigator 72
Figure 4.8 Code that adds execution time controls to the applet 73
Figure 4.9 Browser view of the applet in figure 4.8 74
Figure 4.10 Code that adds a parameter control to the applet 75
Figure 4.11 Browser view of the applet in figure 4.10 76
Figure 4.12 An applet that extends that in figure 4.10 by configuring the
Plotter 77
Figure 4.13 View of the applet in figure 4.12, as displayed by Sun's
Appletviewer. 78
Figure 4.14 An HTML segment that modifies that of figure 4.2 to use
Jar files 79
Figure 4.15 View of the inspection paradox applet described in the
Appendix 82
Figure 5.1 Organization of actors in the ptolemy.actor.lib package 90
Figure 5.2 Organization of actors in the ptolemy.actor.gui package 92
Figure 5.3 The Token class defines a polymorphic interface that
Includes basic arithmetic operations 93
Figure 5.4 The fire() mthod of the AddSubtract shows the use of
Polymorphic add() and subtract() methods in the Token
Class (see figure 5.3) 93
Figure 5.5 The fire() and postfire() mehtods of the Average actor show
How state is updated only in postfire() 96
Figure 5.6 Source actors in the ptolemy.actor.lib package 100
Figure 5.7 Sink actors in the ptolemy.actor.lib package 102
Figure 5.8 Display actors in the ptolemy.actor.gui package 103
Figure 5.9 Logical actors in the ptolemy.actor.lib.logic actors 107
Figure 5.10 The actors in the ptolemy.actor.lib conversion package 108
Figure 6.1 Anatomy of an actor 111
Figure 6.2 Code segment showing the port definitions in the Transformer
Class 114
Figure 6.3 Code segment from the Scale actor, showing the handling of
Ports and parameters 115
Figure 6.4 Code segment from the Poisson actor, showing the attribute
Charged () method 116
Figure 6.5 Code segment from the Scale actor, showing the attribute
Changed() method 117
Figure 6.6 Code segment from the Scale actor showing the clone()
Method 118
Figure 6.7 Code segment from the Average actor, showing the
InitializeQ method 119
VI
Figure 6.8 Code for the Bernoulli actor, which is not data polymorphic 121
Figure 6.9 Code segment from the Average actor showing the action
Methods 122
Figure 6.10 Code segments from the SequenceSource and Time
Source base classes 124
Figure 7.1 Visual notation and terminology 133/134
Figure 7.2 Key classes in the kernel package and their methods 135
Figure 7.3 Support classes in the kernel.util package 136
Figure 7.4 Key classes supporting clustered graphs 140
Figure 7.5 Transparent ports are linked to relations below their 141
Figure 7.6 An example with level-crossing transitions 142
Figure 7.7 A tunneling entity contains a relation with inside links to
More than one port 143
Figure 7.8 An example of a clustered graph 144
Figure 7.9 The same topology as in figure 7.8 implemented as a Java
Class 143
Figure 7.10 The same topology as in figure 7.8 described by the Tel
Blend commands to create it 146
Figure 7.11 Key methods applied to figure 7.8 147
Figure 7.12 Using monitors for thread safety 148
Figure 7.13 Classes and interfaces in kernel.event 151
Figure 7.14 Summary of exceptions defined in the kernel.util package 154
Figure 8.1 Message passing is medicated by the IOPort class 157
Figure 8.2 Port and receiver classes that provide infrastructure for
Message passing under various communication protocols 158
Figure 8.3 A port can support more than on channel 157
Figure 8.4 A bus is an IORelation that represents multiple channels 159
Figure 8.5 Channels may reach multiple destination 159
Figure 8.6 An elaborate example showing several features of the data
transport mechanism 160
Figure 8.7 An example showing busses combined with input, output,
And transparent ports 161
Figure 8.8 Tel Blend code to construct the example in figure 8.7 161
Figure 8.9 Bus widths inside and outside a transparent port need not
Agree 162
Figure 8.10 Static structure diagram for the actor.util package 164
Figure 8.11 An actor that distributes successive input tokens to a set of
Output channels 165
Figure 8.12 Basic classes in the actor package that support execution 166
Figure 8.13 Example application showing a typical arrangement of
Actors, directors, and managers 168
Figure 8.14 Example execution sequence implemented by run() method
Of the Director class 170
Figure 8.15 Alternative execution sequence implemented by run() method
Of the Director class 171
vii
Figure 8.16 An example of an opaque composite actor 173
Figure 8.17 UML static structure diagram for actor.sched and actor.
Process packages 174
Figure 9.1 Static Structure Diagram (Class Diagram) for the classes
In the data package 176
Figure 9.2 The type lattice 179
Figure 9.3 Static structure diagram for the data.expr package 182
Figure 9.4 Functions available to the expression language from the
Java.lang.Math class 187
Figure 9.5 Functions available to the expression language from the
Ptolemy.data.expr.Utility-Functions class. 186
Figure 9.6 Organization of the FixPoint Data Type 191
Figure 10.1 Classes in the graph package 198
Figure 10.2 An undirected graph 199
Figure 10.3 A 4-point CPO that also happens to be a lattice 200
Figure 11.1 An imaginary Ptolemy II application 205
Figure 11.2 A topology with types 208
Figure 11.3 The Type Lattice 209
Figure 11.4 Classes in the datatype package 212
Figure 11.5 Classes in the actor package that support type checking 213
Figure 11.6 Two simple topologies with types 217
Figure 11.7 conversion between sequence and array 218
Figure 12.1 Result of invoking ptplot on the command line with no
Arguments 222
Figure 12.2 To zoom in, drag the left mouse button down and to the
Right to draw a box around the region you wish to see
In more detail 224
Figure 12.3 Encapsulated postscript generated by the Export command
In the File menu of ptplot can be imported into word
Processors 225
Figure 12.4 You can modify the data being plotted by selecting a data
Set and then dragging the right mouse button 225
Figure 12.5 You can control how data is displayed using the Format
Command in the Edit menu, which brings up the dialog
Shown at the bottom. 226
Figure 12.6 The core classes of the plot package 228
Figure 12.7 Core classes supporting applets and applications 230
Figure 12.8 UML static structure diagram for the plotml package 231
Figure 12.9 The document type definition (DTD) for the PlotML 235
Figure 12.10 The compat package provides compatibility with the older
Pxgraph program 243
Figure 13.1 Static structure diagram for effigies and tableaux 248
Figure 13.2 Static structure diagram for the Factory pattern 248
Figure 13.3 Static structure that is useful for handling text documents 249
Figure 13.4 Static structure of how the TableauFactory class, and an
Vlll
Example of how tableau factories are used with text
Documents 249
Figure 13.5 Static structure diagram for the Configuration and Mode
Directory classes 250
Figure 13.6 Static structure diagram for the TableauFrame class 251
Figure 13.7 Sequence diagram for opening and existing design artifact 252
Figure 13.8 Sequence diagram for creating a new design artifact 253
Figure 13.9 Static sequence for Ptolemy effigies 254
Figure 13.10 Static structure of the Ptolemy graph editor 255
Figure 13.11 Vergil Screenshot 256
Figure 13.12 Static structure of the Ptolemy graph editor 256
Figure 13.13 Vergil Screenshot 257
Figure 13.14 Static structure of the Ptolemy graph editor 257
Figure 13.15 Static structure oftheptolemy.vergil.tree package 258
Figure 14.1 Possible implementations of the system equations 260/261
Figure 14.2 A conceptual block diagram for continuous time systems 264
Figure 14.3 The block diagram for the example system 264
Figure 14.4 Embedding a DE component in a CT system 274
Figure 14.5 Embedding CT component in a DE system 274
Figure 14.6 Hybrid system modeling 274
Figure 14.7 Block diagram for the Lorenz system 275
Figure 14.8 The simulation result of the Lorenz system 275
Figure 14.9 Micro-accelerator with digital feedback 276
Figure 14.10 Block diagram for the micro-accelerator system 276
Figure 14.11 Execution result ofthemicroaccelerometor system 277
Figure 14.12 Sticky point masses system 277
Figure 14.13 Modeling sticky point masses 278
Figure 14.14 The simulation result of the sticky point masses system 278
Figure 14.15 The packages in the CT domain 279
Figure 14.16 UML for ct.kernel.util package 279
Figure 14.17 UML for ctkernel package, actor related classes 280
Figure 14.18 UML for ctkernel package, director related classes 282
Figure 14.19 UML for ctkernelsolver package 281
Figure 14.20 A chain of integrators 283
Figure 15.1 If there are simultaneous events B and D 288
Figure 15.2 An example of a directed zero-delay loop 289
Figure 15.3 A Delay actor can be used to break a zero-delay loop 289
Figure 15.4 UML static structure diagram for the DE kernel package 291
Figure 15.5 The library of DE-specific actors 293
Figure 15.6 Topology before and after mutation for the example in
Figure 15.7 293
Figure 15.7 An example of a class that constructs a model and then
Mutates it 294
Figure 15.8 A domain-specific actor in DE 297
Figure 15.9 Code for the Server actor 299
IX
Figure 15.10 Code listings for two style of writing the ABRecognizer 300
Figure 15.11 The run() method of the ABRO actor 302
Figure 15.12 An example of heterogeneous and hierarchical composition 303/304
Figure 16.1 A SDF model that deadlocks 306
Figure 16.2 The model of figure 16.1 corrected with an instance of
SampleDelay in the feedback loob 306
Figure 16.3 An SDF model with inconsistent rates 307
Figure 16.4 Figure 16.3 modified to have consistent rates 307
Figure 16.5 A model that plots the Fast Fourier Transform of a signal 308
Figure 16.6 A model that plots the values of a signal 308
Figure 16.7 An example SDF model 309
Figure 16.8 A consistent cyclic graph, properly annotated with delays 310
Figure 16.9 Two models with each port annotated with the appropriate
Rate properties 311
Figure 16.10 The static structure of the SDF kernel classes 312
Figure 16.11 The sequence of method calls during scheduling 314
Figure 17.1 Illustrating how processes block waiting to rendezvous 318
Figure 17.2 Example of how a CDO might be used in a buffer 320
Figure 17.3 Illustration of the dining philosophers problem 322
Figure 17.4 Processors contending for memory access 323
Figure 17.5 Illustrations of Sieve of Eratosthenes for obtaining first six
Primes 324
Figure 17.6 Actors involved in M/M/l demo 325
Figure 17.7 Template for executing a CDO construct 326
Figure 17.8 Code used to implement the buffer process described 327
Figure 17.9 Static structure diagram for classes in the CSP kernel 329
Figure 17.10 Sequence of steps involved in setting up and controlling the
Model 328
Figure 17.11 Code executed by ProcessThread.run() 330
Figure 17.12 Rendezvous algorithm 334
Figure 17.13 Conceptual view of how conditional communication is built
On top of rendezvous 335
Figure 17.14 Algorithm used to determine if a conditional rendezvous
Branch succeeds or fails 336
Figure 17.15 Modification of rendezvous algorithm 338
Figure 18.1 DDE actors and local time 341
Figure 18.2 Timed deadlock 341
Figure 18.3 Timed deadlock 342
Figure 18.4 Localized Zeno condition topology 343
Figure 18.5 Initializing Feedback Topologies 345
Figure 18.6 Key classes for locally Managing Time 346
Figure 18.7 Additional Classes in the DDE Kernel 348
Figure 18.8 Deadlock Due to Unordered Locking 349/350
Figure 19.1 UML diagram for classes and methods related to the PN
Domain 355
Figure 19.2 get() method of PNQueueReceiver 357
Figure 19.3 put() method of PNQueueReceiver 358
Figure 19.4 setFinish() method of PNQueueReceiver 358
XI
List of Tables
Xll
Introduction
Author: Edward A. Lee
Executable models are constructed under a model of computation, which is the set of "laws of
physics" that govern the interaction of components in the model. If the model is describing a mechani-
cal system, then the model of computation may literally be the laws of physics. More commonly, how-
ever, it is a set of rules that are more abstract, and provide a framework within which a designer builds
models. A set of rules that govern the interaction of components is called the semantics of the model of
computation. A model of computation may have more than one semantics, in that there might be dis-
tinct sets of rules that impose identical constraints on behavior.
The choice of model of computation depends strongly on the type of model being constructed. For
example, for a purely computational system that transforms a finite body of data into another finite
body of data, the imperative semantics that is common in programming languages such as C, C++,
Java, and Matlab will be adequate. For modeling a mechanical system, the semantics needs to be able
to handle concurrency and the time continuum, in which case a continuous-time model of computation
such that found in Simulink, Saber, Hewlett-Packard's ADS, and VHDL-AMS is more appropriate.
The ability of a model to mutate into an implementation depends heavily on the model of compu-
tation that is used. Some models of computation, for example, are suitable for implementation only in
customized hardware, while others are poorly matched to customized hardware because of their intrin-
sically sequential nature. Choosing an inappropriate model of computation may compromise the qual-
ity of design by leading the designer into a more costly or less reliable implementation.
For embedded systems, the most useful models of computation handle concurrency and time. This
is because embedded systems consist typically of components that operate simultaneously and have
multiple simultaneous sources of stimuli. In addition, they operate in a timed (real world) environment,
where the timeliness of their response to stimuli may be as important as the correctness of the
response.
Ptolemy II takes a component view of design, in that models are constructed as a set of interacting
components. A model of computation governs the semantics of the interaction, and thus imposes a dis-
cipline on the interaction of components.
1.3.3 Discrete-Events - DE
In the discrete-event (DE) domain, created by Lukito Muliadi [62], the actors communicate via
sequences of events placed in time, along a real time line. An event consists of a value and time stamp.
Actors can either be processes that react to events (implemented as Java threads) or functions that fire
when new events are supplied. This model of computation is popular for specifying digital hardware
and for simulating telecommunications systems, and has been realized in a large number of simulation
environments, simulation languages, and hardware description languages, including VHDL and Ver-
ilog.
DE models are excellent descriptions of concurrent hardware, although increasingly the globally
consistent notion of time is problematic. In particular, it over-specifies (or over-models) systems where
maintaining such a globally consistent notion is difficult, including large VLSI chips with high clock
rates. Every event is placed precisely on a globally consistent time line.
The DE domain implements a fairly sophisticated discrete-event simulator. DE simulators in gen-
eral need to maintain a global queue of pending events sorted by time stamp (this is called apriority
queue). This can be fairly expensive, since inserting new events into the list requires searching for the
right position at which to insert it. The DE domain uses a calendar queue data structure [11] for the
global event queue. A calendar queue may be thought of as a hashtable that uses quantized time as a
hashing function. As such, both enqueue and dequeue operations can be done in time that is indepen-
dent of the number of events in the queue.
In addition, the DE domain gives deterministic semantics to simultaneous events, unlike most
competing discrete-event simulators. This means that for any two events with the same time stamp, the
order in which they are processed can be inferred from the structure of the model. This is done by ana-
lyzing the graph structure of the model for data precedences so that in the event of simultaneous time
stamps, events can be sorted according to a secondary criterion given by their precedence relation-
ships. VHDL, for example, uses delta time to accomplish the same objective.
1.3.4 Distributed Discrete Events - DDE
The distributed discrete-event (DDE) domain, created by John Davis, can be viewed either as a
variant of DE or as a variant of PN (described below). Still highly experimental, it addresses a key
problem with discrete-event modeling, namely that the global event queue imposes a central point of
control on a model, greatly limiting the ability to distribute a model over a network. Distributing mod-
els might be necessary either to preserve intellectual property, to conserve network bandwidth, or to
exploit parallel computing resources.
The DDE domain maintains a local notion of time on each connection between actors, instead of a
single globally consistent notion of time. Each actor is a process, implemented as a Java thread, that
can advance its local time to the minimum of the local times on each of its input connections. The
domain systematizes the transmission of null events, which in effect provide guarantees that no event
will be supplied with a time stamp less than some specified value.
1.3.9 Synchronous/Reactive - SR
In the synchronous/reactive (SR) model of computation [7], the arcs represent data values that are
aligned with global clock ticks. Thus, they are discrete signals, but unlike discrete time, a signal need
not have a value at every clock tick. The entities represent relations between input and output values at
each tick, and are usually partial functions with certain technical restrictions to ensure determinacy.
Examples of languages that use the SR model of computation include Esterel [9], Signal [8], Lustre
[17], and Argos [55].
SR models are excellent for applications with concurrent and complex control logic. Because of
the tight synchronization, safety-critical real-time applications are a good match. However, also
because of the tight synchronization, some applications are overspecified in the SR model, limiting the
implementation alternatives. Moreover, in most realizations, modularity is compromised by the need
to seek a global fixed point at each clock tick. An SR domain has not yet been implemented in Ptolemy
II, although the methods used by Stephen Edwards in Ptolemy Classic can be adapted to this purpose
[20].
1. Consider the difference between the telephone (rendezvous) and email (asynchronous message passing). If you
are trying to schedule a meeting between four busy people, getting them all on a conference call would lead to a
quick resolution of the meeting schedule. Scheduling the meeting by email could take several days, and may in
fact never converge. Other sorts of communication, however, are far more efficient by email.
One of the principles of the Ptolemy project is that visual depictions of systems can
help to offset the increased complexity that is introduced by heterogeneous modeling.
These visual depictions offer an alternative syntax to associate with the semantics of a model of com-
putation. Visual syntaxes can be every bit as precise and complete as textual syntaxes, particularly
when they are judiciously combined with textual syntaxes.
Visual representations of models have a mixed history. In circuit design, schematic diagrams used
to be routinely used to capture all of the essential information needed to implement some systems.
Schematics are often replaced today by text in hardware description languages such as VHDL or Ver-
ilog. In other contexts, visual representations have largely failed, for example flowcharts for capturing
the behavior of software. Recently, a number of innovative visual formalisms have been garnering sup-
port, including visual dataflow, hierarchical concurrent finite state machines, and object models. The
UML visual language for object modeling has been receiving a great deal of attention, and in fact is
used fairly extensively in the design of Ptolemy II itself (see appendix A of this chapter).
A subset of visual languages that are recognizable as "block diagrams" represent concurrent sys-
tems. There are many possible concurrency semantics (and many possible models of computation)
associated with such diagrams. Formalizing the semantics is essential if these diagrams are to be used
for system specification and design. Ptolemy II supports exploration of the possible concurrency
semantics. A principle of the project is that the strengths and weaknesses of these alternatives make
them complementary rather than competitive. Thus, interoperability of diverse models is essential.
1.6 Ptolemy II
Ptolemy II offers a unified infrastructure for implementations of a number of models of computa-
tion. The overall architecture consists of a set of packages that provide generic support for all models
of computation and a set of packages that provide more specialized support for particular models of
computation. Examples of the former include packages that contain math libraries, graph algorithms,
an interpreted expression language, signal plotters, and interfaces to media capabilities such as audio.
Examples of the latter include packages that support clustered graph representations of models, pack-
ages that support executable models, and domains, which are packages that implement a particular
model of computation.
JL
FIGURE 1.2. The package structure of Ptolemy II, without the domains.
10
actor.lib This subpackage is a library of polymorphic actors.
actor.process This subpackage provides infrastructure for domains where actors are processes
implemented on top of Java threads.
actor.sched This subpackage provides infrastructure for domains where actors are statically
scheduled by the director.
actor.util This subpackage contains utilities that support directors in various domains. Spe-
cifically, it contains a simple FIFO Queue and a sophisticated priority queue called
a calendar queue.
data This package provides classes that encapsulate and manipulate data that is trans-
ported between actors in Ptolemy models.
data.expr This class supports an extensible expression language and an interpreter for that
language. Parameters can have values specified by expressions. These expressions
may refer to other parameters. Dependencies between parameters are handled
transparently, as in a spreadsheet, where updating the value of one will result in the
update of all those that depend on it.
data.type This package contains classes and interfaces for the type system.
domains This package contains one subpackage for each Ptolemy II domain.
graph This package provides algorithms for manipulating and analyzing mathematical
graphs. Mathematical graphs are simpler than Ptolemy II clustered graphs in that
there is no hierarchy, and arcs link exactly two nodes. This package is expected to
supply a growing library of algorithms.
gui This package contains generically useful user interface components.
kernel This package provides the software architecture for the key abstract syntax, clus-
tered graphs. The classes in this package support entities with ports, and relations
that connect the ports. Clustering is where a collection of entities is encapsulated in
a single composite entity, and a subset of the ports of the inside entities are exposed
as ports of the cluster entity.
kernel.event This package contains classes and interfaces that support controlled mutations of
clustered graphs. Mutations are modifications in the topology, and in general, they
are permitted to occur during the execution of a model. But in certain domains,
where maintaining determinacy is imperative, the director may wish to exercise
tight control over precisely when mutations are performed. This package supports
queueing of mutation requests for later execution. It uses a publish-and-subscribe
design pattern.
kernel.util This subpackage of the kernel package provides a collection of utility classes that
do not depend on the kernel package. It is separated into a subpackage so that these
utility classes can be used without the kernel. The utilities include a collection of
exceptions, classes supporting named objects with attributes, lists of named
objects, a specialized cross-reference list class, and a thread class that helps
Ptolemy keep track of executing threads.
math This package encapsulates mathematical functions and methods for operating on
matrices and vectors. It also includes a complex number class and a class support-
ing fractions.
media This package encapsulates a set of classes supporting audio and image processing.
11
moml This package contains classes for Model Markup Language (MoML) which is used
to describe Ptolemy II models.
plot This package provides two-dimensional signal plotting widgets.
Workspace
-O
NanwdObJ 0..n
Attribut*
0.1
—[>
«Interface»
I container On
,.
On link
T
ComponantPort
«Interface» <h
Actor ComponentEntlty ComposlteEntlty
O.n container
*—;rr
T^ ComponentRelatlon
{consistancy)
AtomlcActor
0.1 CompositaActor
Director
Manager
FIGURE 1.3. Some of the key classes in Ptolemy II. These are defined in the kernel, kemel.util, and actor
12
Entity, Port, and Relation are three key classes that extend NamedObj. These classes define the
primitives of the abstract syntax supported by Ptolemy II. They will be fully explained in the kernel
chapter. ComponentPort, ComponentRelation, and ComponentEntity extend these classes by adding
support for clustered graphs. CompositeEntity extends ComponentEntity and represents an aggrega-
tion of instances of ComponentEntity and ComponentRelation.
The Executable interface, explained in the actors chapter, defines objects that can be executed. The
Actor interface extends this with capability for transporting data through ports. AtomicActor and Com-
positeActor are concrete classes that implement this interface.
An executable Ptolemy II model consists of a top-level CompositeActor with an instance of Direc-
tor and an instance of Manager associated with it. The manager provides overall control of the execu-
tion (starting, stopping, pausing). The director implements a semantics of a model of computation to
govern the execution of actors contained by the CompositeActor.
Director is the base class for directors that implement models of computation. Each such director
is associated with a domain. We have defined in Ptolemy II directors that implement continuous-time
modeling (ODE solvers), process networks, synchronous dataflow, discrete-event modeling, and com-
municating sequential processes.
1.6.3 Domains
The domains in Ptolemy II are subpackages of the ptolemy.domains package, as shown in figure
1.4. These packages generally contain a kernel subpackage, which defines classes that extend classes
in the actor or kernel packages of Ptolemy II. The gui subpackage contains a domain-specific applet
class, which provides facilities for easily creating applets that use that domain. The lib subpackage,
when it exists, includes domain-specific actors.
1.6.4 Capabilities
Ptolemy II is a second generation system. Its predecessor, Ptolemy Classic, still has many active
users and developers, and may continue to evolve for some time. Ptolemy II has a somewhat different
emphasis, and through its use of Java, concurrency, and integration with the network, is aggressively
experimental. Some of the major capabilities in Ptolemy II that we believe to be new technology in
modeling and design environments include:
• Higher level concurrent design in Java . Java support for concurrent design is very low level,
based on threads and monitors. Maintaining safety and liveness can be quite difficult [44]. Ptolemy
II includes a number of domains that support design of concurrent systems at a much higher level
of abstraction, at the level of their software architecture. Some of these domains use Java threads
as an underlying mechanism, while others offer an alternative to Java threads that is much more
efficient and scalable.
• Better modularization through the use of packages. Ptolemy II is divided into packages that can be
used independently and distributed on the net, or drawn on demand from a server. This breaks with
tradition in design software, where tools are usually embedded in huge integrated systems with
interdependent parts.
• Complete separation of the abstract syntax from the semantics. Ptolemy designs are structured as
clustered graphs. Ptolemy II defines a clean and thorough abstract syntax for such clustered
graphs, and separates into distinct packages the infrastructure supporting such graphs from mecha-
nisms that attach semantics (such as dataflow, analog circuits, finite-state machines, etc.) to the
graphs.
13
csp
de pn
sdf
14
• Improved heterogeneity. Ptolemy Glassic provided a wormhole mechanism for hierarchically cou-
pling heterogeneous models of computation. This mechanism is improved in Ptolemy II through
the use of opaque composite actors, which provide better support for models of computation that
are very different from dataflow, the best supported model in Ptolemy Classic. These include hier-
archical concurrent finite-state machines and continuous-time modeling techniques.
• Thread-safe concurrent execution. Ptolemy models are typically concurrent, but in the past, sup-
port for concurrent execution of a Ptolemy model has been primitive. Ptolemy II supports concur-
rency throughout, allowing for instance for a model to mutate (modify its clustered graph
structure) while the user interface simultaneously modifies the structure in different ways. Consis-
tency is maintained through the use of monitors and read/write semaphores [37] built upon the
lower level synchronization primitives of Java.
• A software architecture based on object modeling. Since Ptolemy Classic was constructed, soft-
ware engineering has seen the emergence of sophisticated object modeling [57][74][77] and
design pattern [25] concepts. We have applied these concepts to the design of Ptolemy II, and they
have resulted in a more consistent, cleaner, and more robust design. We have also applied a simpli-
fied software engineering process that includes systematic design and code reviews [72].
• A truly polymorphic type system. Ptolemy Classic supported rudimentary polymorphism through
the "anytype" particle. Even with such limited polymorphism, type resolution proved challenging,
and the implementation is ad-hoc and fragile. Ptolemy II has a more modern type system based on
a partial order of types and monotonic type refinement functions associated with functional blocks.
Type resolution consists of finding a fixed point, using algorithms inspired by the type system in
ML [60].
• Domain-polymorphic actors. In Ptolemy Classic, actor libraries were separated by domain.
Through the notion of subdomains, actors could operate in more than one domain. In Ptolemy II,
this idea is taken much further. Actors with intrinsically polymorphic functionality can be written
to operate in a much larger set of domains. The mechanism they use to communicate with other
actors depends on the domain in which they are used. This is managed through a concept that we
call ^process level type system.
• Extensible XML-basedfile formats. XML is an emerging standard for representation of informa-
tion that focuses on the logical relationships between pieces of information. Human-readable rep-
resentations are generated with the help of style sheets. Ptolemy II will use XML as its primary
format for persistent design data.
15
ponents so that they are indistinguishable from any other Java thread within the model of computa-
tion. Domains that seem particularly well suited to this approach include PN and CSP.
Embedded hardware synthesis. Ptolemy Classic had only very weak mechanisms for migrating
designs from idealized floating-point simulations through fixed-point simulations to embedded
software, FPGA, and hardware designs. Ptolemy II will leverage polymorphism, allowing libraries
to be constructed where compatibility across implementation technologies is assured [76].
Integrated verification tools. Modern verification tools based on model checking [34] could be
integrated with Ptolemy II at least to the extent that finite state machine models can be checked.
We believe that the separation of control logic from concurrency will greatly facilitate verification,
since only much smaller cross-sections of the system behavior will be offered to the verification
tools.
Reflection of dynamics. Java supports reflection of static structure, but not of dynamic properties
of process-based objects. For example, the data layout required to communicate with an object is
available through the reflection package, but the communication protocol is not. We plan to extend
the notion of reflection to reflect such dynamic properties of objects.
16
Appendix A: UML — Unified Modeling Language
UML (the unified modeling language) [23] [71] defines a suite of visual syntaxes for describing
various aspects of software architecture. We make heavy use of two of these visual syntaxes, package
diagrams and static structure diagrams. These syntaxes are summarized here. As with most descriptive
syntaxes, any use of the syntax involves certain stylistic choices. These stylistic choices are not part of
UML, but nonetheless can be important to understanding the diagrams. We explain the style that we
use here.
A.2.1 Classes
A simplified static structure diagram for some Ptolemy II classes is shown in figure 1.5. In this dia-
gram, each class is shown in a box. The class name is at the top of each box, its attributes are below
that, and its methods below that. Thus, each box is divided into three segments separated by horizontal
lines. The attributes are members of the Java classes, which may be public, package friendly, pro-
tected, or private. Private members are prefixed by a minus sign "-", as for example the container
attribute of Port. Although private members are not visible directly to users of the class, they may
nonetheless be a useful part of the object model because they indicate the state information contained
by an instance of the class. Public members have a leading "+" and protected methods a leading "#" in
a UML diagram. There are no public or protected members shown in figure 1.5. The type of a member
is indicated after a colon, so for example, the container method of Port is of type Entity.
Methods, which are shown below attributes, also have a leading "+" for public, "#" for protected,
and "-" for private. Our object models do not show private methods, since they are not inherited and
are not visible in the interface to the object. Figure 1.5 shows a number of public methods and one pro-
tected method, _link() in Port. The return value of a method is given after a colon, so for example, get-
Container() of Port returns an Entity.
Although not usually included in UML diagrams, our diagrams show class constructors. They are
listed first among the methods and have names that are the same as the name of the class. No return
type is shown. For completeness, our object models typically show all public and protected methods of
these classes, although a proper object model might only show those relevant to the issues being dis-
cussed. Figure 1.5 does not show all methods, so that we can simplify the discussion of UML. Our dia-
17
grams do not include deprecated methods or methods that are present in parent classes.
Arguments to a method or constructor are shown in parentheses, with the types after a colon, so for
example, ComponentEntity shows a single constructor that takes two arguments, one of type Compos-
iteEntity and the other of type String.
A.2.2 Inheritance
Subclasses are indicated by lines with white triangles (or outlined arrow heads). The class on the
side of the arrow head is the superclass or base class. The class on the other end is the subclass or
derived class. The derived class is said to specialize the base class, or conversely, the base class to gen-
eralize the derived class. The derived class inherits all the methods shown in the base class and may
override or some of them. In our object models, we do not explicitly show methods that override those
defined in a base class or are inherited from a base class. For example, in figure 1.5, ComponentEntity
has all the methods of Entity and NamedObj, and may override some of those methods, but only shows
NamedObj
0.1
«Interface»
£xeci/taJb/e Entity container o..n Port
-_container: Entity
+fireQ +Entity() ♦Portf)
+getPorts(): Enumeraticrt ♦■getContainerO: Entity
A »Jink(r: Relation)
ComponentEntity
CompositeEntity
_container: CompositeEntity
^ompOfWrtEntity(containef: CompositeEntity, i : String)
+CompositeEntity(container: CompositeEntity. name: String)
+getContainerO: CompositeEntity
♦getEntiliesO: Enumeration
+isAtomic(): boolean /l\ 0..1
7T
«Interface»
Actor
*inpuiPott&Q : Enumeration
+outputPortsQ: Enumeration
1 ~
AtomieActor
O.n
+ComoositeActor{container: CompositeActor. name: String)
FIGURE 1.5. Simplified static structure diagram for some Ptolemy II classes. This diagram illustrates fea-
tures of UML syntax that we use.
18
the one method it adds. Thus, the complete set of methods of a class is cumulative. Every class has its
own methods plus those of all its superclasses.
An exception to this is constructors. In Java, constructors are not inherited. Thus, in our class dia-
grams, the only constructors available for a class are those shown in the box defining the class. Figure
1.5 does not show all the constructors of these classes, for simplicity.
Classes shown in boxes outlined with dashed lines, such as NamedObj in figure 1.5, are fully
described elsewhere. This is not standard UML notation, but it gives us a convenient way to partition
diagrams. Often, these classes belong to another package.
A.2.3 Interfaces
Figure 1.5 also shows two examples of interfaces, Executable and Actor. An interface is indicated
by the label "«Interface»" and by italics in the name. An interface defines a set of methods without
providing an implementation for them. It cannot be instantiated, and therefore has no constructors.
When a class implements an interface, the object model shows the relationship with a dotted-line with
an arrow. Any concrete class (one that can be instantiated) that implements an interface must provide
implementations of all its methods. In our object models, we do not show those methods explicitly in
the concrete class, just like inherited methods, but their presence is implicit in the relationship to the
interface.
One interface can extend another. For example, in figure 1.5, Actor extends Executable. This
means that any concrete class that implements Actor must implement the methods of Actor and Exe-
cutable.
We will occasionally show abstract classes, which are like interfaces in that they cannot be instan-
tiated, but unlike interfaces in that they may provide default implementations for some methods and
may even have private members. Abstract classes are indicated by italics in the class name. There are
no abstract classes in figure 1.5.
A.2.4 Associations
Inheritance and implementation are types of associations between entities in the object model.
Associations of other types are indicated by other lines, often annotated with ranges like "0..n" or with
diamonds on one end or the other.
Aggregations are shown as associations with diamonds. For example, an Entity is an aggregation
of any number (0..n) instances of Port. More strongly, we say that a Port is contained by 0 or 1
instances of Entity. By containment, we mean that a port can only be contained by a single Entity. In a
weaker form of aggregation, more than one aggregate may refer to the same component. The stronger
form of aggregation (containment) is indicated by the filled diamond, while the weaker form is indi-
cated by the unfilled diamond. There are no unfilled diamonds in figure 1.5. In fact, they are fairly rare
in Ptolemy II, since many of its architectural features depend on containment relationships, where an
object can have at most one container.
The relationship between ComponentEntity and CompositeEntity is particularly interesting. An
instance of CompositeEntity can contain any number of instances of ComponentEntity, but Composi-
teEntity is derived from ComponentEntity. Thus, a CompositeEntity can contain any number of
instances of either ComponentEntity or CompositeEntity. This is the classic Composite design pattern
[25], which supports arbitrarily deeply nested containment hierarchies.
In figure 1.5, a CompositeActor is an aggregation of AtomicActors and CompositeActors. These
19
two aggregation relations are derived from the aggregation relationship between ComponentEntity and
CompositeEntity. This derived association is indicated with a dashed line with an open arrowhead.
20
Appendix B: Ptolemy II Naming Conventions
We have made an effort to be consistent about naming of classes, methods and members. This
appendix describes our policy.
B.l Classes
Class names are capitalized with internal word boundaries also capitalized (as in "CompositeEn-
tity"). Most names are made up of complete words ("CompositeEntity" rather than "CompEnt")1.
Interface names suggest their potential (as in "Executable," which means "can be executed").
Despite having packages to divide up the namespace, we attempt nonetheless to keep class names
unique. This helps avoid confusion and bugs that may arise from having Java import statements in the
wrong order. In many cases, a domain includes a specialized version of some more generic class. In
this case, we create a unique name by prefixing the generic name with the domain name. For example,
while Director is a base class in the actor package, DEDirector is a derived class in the DE domain.
For the most part, we try to avoid prefixing actor names with the domain name, e.g., we define
Delay rather than DEDelay. Occasionally however, the domain prefix is useful to distinguish two ver-
sions of some similar functionality, both of which might be useful in a domain. For example, the DE
domain can use actors derived from Transformer or from DETransformer, where the latter is special-
ized to DE.
B.2 Members
Member names are not capitalized, although internal word boundaries usually are (e.g. "declared-
Type"). If the member is private or protected, then its name begins with a leading underscore (e.g.
"declaredType").
B.3 Methods
Method names are similar to member names, in that they are not capitalized, except on internal
word boundaries. Private and protected methods have a leading underscore. In text referring to meth-
ods, the method name is followed by open and close parentheses, as in "getName()." Usually, no argu-
ments are given, even if the method takes arguments.
Method names that are plural, such as getPorts(), usually return an enumeration (or sometimes an
array, or an iterator). Methods that return Lists are usually of the form portListO-
21/22
Using Vergil
Authors: Steve Neuendorffer
2.1 Introduction
Vergij is the Graphical User Interface for Ptolemy II. This chapter will guide you though using
Vergil to create and manipulate Ptolemy models. Figure 2.1 shows a simple Ptolemy II model in
Vergil, showing the graph editor, one of several editors available in Vergil.
Waveform
J® —*■
23
2.2 Quick Start
The traditional first programming example is Hello World. Why break tradition?
First start Vergil. From the command line, enter "vergil", or select Vergil from the Start menu. You
should see a welcome screen that looks like the one in figure 2.2. Feel free to explore the links in this
window. Most useful is probably the "Quick tour" link.
Create a new graph editor from the File->New menu in the welcome window. You should see
something like the window shown in Figure 2.3. Ignoring the menus and toolbar for a moment, on the
left is a palette of objects that can be dragged onto the page on the right. To begin with, the page on the
right is blank. Open the actor library in the palette, and go into the sources library. Find the Const
actor and drag an instance over onto the blank page. Then go into the sinks library and drag a Display
actor onto the page. Each of these actors can be dragged around on the page. However, we would like
to connect one to the other. To do this, drag a connection from the output port on the right of the Const
actor to the input port of the Display actor. Lastly, open the director library and drag an SDFDirector
onto the page. The Director gives an execution meaning to the graph, but for now we don't have to be
concerned about exactly what that is.
Now you should have something that looks like Figure 2.4. The Const actor is going to create our
string, and the Display actor is going to print it out for us. We need to take care of one small detail
before we run our example: we need to tell the Const actor that we want the string "Hello World". To
do this we need to edit one of the parameters of the Const. To do this, right click on the Const actor and
select "Edit Parameters". You should see the dialog box in figure 2.5. Enter the string "Hello World"
for the value parameter and click the Commit button. Be sure to include the double quotes, so that the
expression is interpreted as a string.
To run the example, go to the View menu and select the Run Window. If you click the "Go" button,
you will see a large number of strings in the Display at the right. To stop the execution, click the "Stop
button. To see only one string, change the iterations parameter of the director to 1, which can be done
^file:/C:/ptII/ptolemy/configs/intro.htm ^jnjxj
File Help
24
in the run window, or in the graph editor in the same way you edited the parameter of the Const actor
before. The run window is shown in Figure 2.6.
/o\
\^/ value: ['Hello World"|
25
the value parameter of the constant to be 0 and the iterations parameter of the director to 5. Running
the model should result in 5 numbers between 0 and 4. These are the values produced by the Ramp,
which are having the value of the Const actor subtracted from them. Experiment with changing the
value of the Const actor and see how it changes the 5 numbers at the output. Now for the real test:
^H Unnamed md ,jnjx]
File View Edit Graph Help
utilities
Hanna SDF
J actor library
B- ' I sources
& _J sinks
6 -_] math
Const Display
B- " I flow control
B- ' t logic
B- ' 1 conversions
B- " I signal processing
m-_J sdf
B- ' I continuous time
Model parameters:
has no parameters.
Director parameters:
iterations: |1
-
vectorizationFactor: I« <l I
;■ execution finished. I
FIGURE 2.6. Execution of the Hello World example.
26
Change the value of the Const actor back to "Hello World". When you execute the model, you should
see an error window popup. Not to worry, this window is just telling you that you have tried to subtract
a string value from an integer value, which doesn't make much sense at all.
Let's try a small change to the model to get something that is executable. Disconnect the Const
from the lower port of the AddSubtract actor and connect it instead to the upper port. You can do this
by selecting the connection and deleting it (using the delete key), then adding a new connection or by
selecting it and dragging one of its endpoints to the new location1. Notice that the upper port is hollow;
this indicates that it is a multiport, meaning that you can make more than one connection to it. Now
when you run the model you should see strings like "OHelloWorld".
There are actually two things going on here. The first is that all the connections to the same port
must have the same type. Ptolemy automatically converts the integers from the Ramp to strings. The
second is that the strings are added together as strings usually are in Java, which means concatenating
them.
2.4 Hierarchy
Let's look at a slightly more interesting problem. In this case, a small signal processing problem,
where we are interested in recovering a signal based only on noisy measurements of it. First open a
new document and drag in a Typed Composite Actor from the utilities library. This actor is going to add
the noise to our measurements. First, using the context menu (right click over the composite actor),
select "Rename" and give the composite a good name, like "Channel". Then, using the context menu
again, select "look inside" on the actor. You should get a blank graph editor. The original graph editor
is still open. To see it, move the new one using its title bar.
First we have to add some external ports. There are several ways to do this, but clicking on the port
toolbar button is probably the easiest. The port toolbar button is the small black triangle at the upper
left. Create two ports and rename them input and output. Using the context menu on the background,
SDF
Ramp
1. Hint: The connection can sometimes be difficult to select by clicking on it, since you have to precisely on it. To
select it more easily, drag out a small box that overlaps it.
27
select Configure ports and set input to be an input port and output to be an output port. Then using
these ports, create the diagram shown in Figure 2.8. Hint: to create a connection starting on one of the
external ports, hold down the control key when dragging.
The Gaussian actor creates values from a Gaussian distributed random variable, and is found in
the sources library. Now if you close this editor and return to the previous one, you should be able to
easily create the model shown in figure 2.9. The Sinewave actor is listed under signal processing, and
the SequencePlotter actor is found in sinks. Notice that the Sinewave actor is also a hierarchical model,
as suggested by its red outline. If you execute this model (you will probably want to set the iterations
to something reasonable, like 200), you should see something like Figure 2.10.
SDF
input AddSubtract
output
Gaussian
SDF
» > l^»~ » P
28
create the model shown in Figure 2.11. The parameter of the Const actor is 4, and the MultiplyDivide
actor is found in the math library.
/C:/users/neuendor/ptII/ptolemy/verail/noi$y.Kml JJJ3Jx|
Rle Vie* Oebug Help
Director parameters:
iterations:
vectorizationFactor:
200 J
0.0 0.5
I
im a
i
1.0
I
1.5 2.0
L
X10
execution finished.
FIGURE 2.10. The output of the simple signal processing example above.
Channel
SDF
Copyl :Channe!
Sinewave
AddSubtract MultiplyDivide SmoothedSpectrum
Copy2:Channel)—P
!►—*
Copy3;Channel
Const
SequencePlotter
D
G
FIGURE 2.11. An example of a broadcast relation.
29
single sample each time they are invoked (fired). Some require several input samples {tokens) before
they can be fired, and produce several tokens when they are fired.
One such actor is a spectral estimation actor. Figure 2.12 shows a system that computes the spec-
trum of a sine wave. The spectrum actor has a single parameter, which gives the order of the FFT used
to calculate the spectrum. Figure 2.13 shows the output of the model with order set to 8 and the num-
ber of iterations set to 1. Note that there are 256 output samples. This is because the Spectrum actor
requires 28, or 256 input samples to fire, and produces 28, or 256 output samples when it fires. Thus,
one iteration of the model produces 256 samples.
SDF
punctual
1 Go Jl Pause Resume Stop
><102
execution finished. |
1. Hint: Notice the "xlO2 " at the bottom right, which indicates that the label "2.5" stands for "250"
30
value to increment this by for each subsequent token. Setting these to "-PI" and "PI/128" respectively
results in the plot shown in figure 2.15.
This plot is better, but still missing useful information. To control more precisely the visual appear-
ance of the plot, click on the second button from the right in the row of buttons at the top right of the
plot. This button brings up a format control window. It is shown in figure 2.16, filled in with values
t^\m\w\WL\
::: :
| go J Pause 1 Resume | : ::Siop:"i:?:|
40
. Model parameters:
30
; has no parameters.
20
[
)\
■
. i
:
'
Director parameters: 10
/ \ {
^.^-
<\,"^^ i
iterations: fi \ .'
-10 ■■■■■-
vectorizationFactor: fi
: -20
-3 -2.-1 0 1 2 3
FIGURE 2.15. Better labeled plot, where the horizontal axis now properly represents the frequency values.
Apply j Cancel j
31
that result in the plot shown in figure 2.17. Most of these are self-explanatory, but the following point-
ers may be useful:
The grid is turned off to reduce clutter.
Titles and axis labels have been added.
The X range and Y range are determined by the fill button at the upper right of the plot.
Stem plots can be had by clicking on "Stems"
Individual tokens can be shown by clicking on "dots"
Connecting lines can be eliminated by deselecting "connect"
The X axis label has been changed to symbolically indicate multiples of PI/2. This is done by
entering the following in the X Ticks field:
-PI -3.14159, -PI/2 -1.570795, 0 0.0, PI/2 1.570795, PI 3.14159
The syntax in general is:
label value, label value, ...
where the label is any string (enclosed in quotation marks if it includes spaces), and the value is a
number.
^^file:/C:/ptII/doc/desiqn/src/NewFolder/SineSpectruniJ<ml JO|*J
File View Debug Help
oo Pause Resume
Model parameters:
Director parameters:
Iterations:
vectorlzationFactor:
-Pl/2 0 PU2
frequency
execution finished.
32
MoML
Authors:
Edward A. Lee
Steve Neuendorffer
3.1 Introduction
Ptolemy II models can be specified in a number of ways and used in a number of ways. They
might be simulations (executable models of some other system) or implementations (the system itself).
They might be classical computer programs (applications), or any of a number of network-integrated
programs (applets, servlets, or CORBA services, for example). One way to construct models is to cre-
ate XML text files using an XML schema called MoML. MoML is the primary persistent file format
for Ptolemy II models. It is also the primary mechanism for constructing models whose definition and
execution is distributed over the network.
MoML is a modeling markup schema in XML. It is intended for specifying interconnections of
parameterized components. A MoML file can be executed as an application using any of the following
commands,
ptolemy filename.xml
ptexecute filename.xml
vergil filename.xml
moml configuration.xml filename.xml
These commands are defined in the directory $PTli/bin, which must be in your path1, where $PTII
is the location of the Ptolemy II installation. In all cases, the filename can be replaced by a URL. The
first of these commands assumes that the file defines an executable Ptolemy II model, and opens a con-
trol panel to execute it. The second of these executes it without a control panel. The third opens a
1. These commands are executed this way on Unix systems and on Windows systems with Cygwin installed. On
other configurations, the equivalent commands are invoked in some other way.
33
graphical editor to edit and execute the model. The fourth uses the configuration file (a MoML file
containing a Ptolemy II configuration) to invoke some set of customized views or editors on the model.
The filename extension can be ".xml" or ".moml" for MoML files. And the same XML file can be
used in an applet .
To get a quick start, try entering the following into a file called test. xml (This file is also avail-
able as $PTII/ptolemy/moml/demo/test.xml):
This code defines a model in a top-level entity called "test". By convention, we use the same name for
the top-level model and the file in which it resides. The top-level model is an instance of the Ptolemy II
class ptolemy.actor.TypedCompositeActor. It contains a director, two entities, a relation, and
two links. The model is depicted in figure 3.1, where the director is not shown. It can be run using the
command
ptolemy test.xml
You should get a window looking like that in figure .3.2. Enter "10" in the iterations box and hit the
"Go" button to execute the model for 10 iterations (leaving the default "0" in the iterations box exe-
cutes it forever, until you hit the "Stop" button).
The structure of the above MoML text is explained in detail in this chapter. A more interesting
example is given in the appendix to this chapter. You may wish to refer to that example as you read
about the details. The next chapter explains how to bypass MoML and write applets directly. The chap-
test
1. An applet is a Java program that is downloaded from a web server by a browser and executed in the client's
computer (usually within a plug-in for the browser).
34
ter after that describes the actors libraries that are included in the current Ptolemy II version.
^file:/C:/ptII.'ptolemy/nioml/demo/test.Hml jnjx|
File Debug Help
»RfJR
Go Pause Resume Stop
1 I 1 1 1 1 1 1 1
Director parameters:
iterations: 10 j, ^
vectorizationFactor:
S \
y
1
■si1 i i i i i i
8 9 10
execution finished.
35
ports, and the connections between their ports. In Ptolemy II, the meaning of a connection (the
semantics of the model) is defined by the director for the model, which is a property of the top-
level entity. The director defines the semantics of the interconnection. MoML knows nothing
about directors except that they are instances of classes that can be loaded by the class loader and
assigned as properties.
Connection-
Link ▲. Link
Port
Entity
36
transition between states in a finite state machine, where the states are represented as entities. Or it
could mediate rendezvous between processes represented as entities. Or it could mediate method calls
between loosely associated objects, as for example in remote method invocation over a network.
3.2.2 Abstraction
Composite entities (clusters) are entities that can contain a topology (entities and relations). Clus-
tering is illustrated by the example in figure 3.4. A port contained by a composite entity has inside as
well as outside links. Such a port serves to expose ports in the contained entities as ports of the com-
posite. This is the converse of the "hiding" operator often found in process algebras. Ports within an
entity are hidden by default, and must be explicitly exposed to be visible (linkable) from outside the
entity1. The composite entity with ports thus provides an abstraction of the contents of the composite.
FIGURE 3.4. Ports (P3 and P4) are linked to relations (Rl and R2) below their container (El) in the hierar-
chy. They may also be linked to relations at the same level (R3 and R4).
1. Unless level-crossing links are allowed. MoML supports these, but they are discouraged.
37
<!ELEMENT class (class | configure | deleteEntity | deletePort | deleteRelation | director |
doc | entity | group | import | input | link | port | property | relation | rename |
rendition | unlink)*>
<!ATTLIST class name CDATA «REQUIRED
extends CDATA «IMPLIED
source CDATA »IMPLIED*
38
you are adept at reading these, it is a complete specification of the schema. However, since it is not
particularly easy to read, we explain its key features here.
Every MoML file must either contain or refer to a DTD. The simplest way to do this is with the
following file structure:
Here, "model definition" is a set of XML elements that specify a clustered graph. The syntax for
these elements is described in subsequent sections. The first line above is required in any XML file. It
asserts the version of XML that this file is based on (1.0) and states that the file includes external refer-
ences (in this case, to the DTD). The second and third lines declare the document type (model) and
provide references to the DTD.
The references to the DTD above refer to a "public" DTD. The name of the DTD is
which follows the standard naming convention of public DTDs. The leading dash "-" indicates that
this is not a DTD approved by any standards body. The first field, surrounded by double slashes, in the
name of the "owner" of the DTD, "UC Berkeley." The next field is the name of the DTD, "DTD
MoML l" where the "l" indicates version 1 of the MoML DTD. The final field, "EN" indicates that the
language assumed by the DTD is English. The Ptolemy II MoML parser requires that the public DTD
be given exactly as shown, or it will not recognize the file as MoML.
In addition to the name of the DTD, the DOCTYPE element includes a URL pointing to a copy of
the DTD on the web. If a particular MoML tool does not have access to a local copy of the DTD, then
it finds it at this web site.
The "entity" element may be replaced by a "class" element, as in:
start tag
39
body
end tag
<elementName attributes>
</elementName>
The body, if present, can contain additional elements as well as arbitrary text. If the body is not
present, then the element is said to be empty; it can optionally be written using the shorthand:
<elementName attributes/>
Which attributes are legal in an element is defined by the DTD. The quotation marks delimit the value
of the attributes, so if the attribute value needs to contain quotation marks, then they must be given
using the special XML entity "",-" as in the following example:
"foo"
40
acters, spaces, or the underscore "_" character. The first field is the name of the top-level model or
class object. The second field is the name of an object immediately contained by that top-level.
Any name that does not begin with a period is relative to the current context, the object defined or
referenced by an enclosing element. The first field of such a name refers to or defines an object imme-
diately contained by that object. For example, inside of an object with absolute name ".x" the name
"y.z" refers to an object with absolute name ".x.y.z".
A name is required to be unique within its container. That is, in any given model, the absolute
names of all the objects must be unique. There can be two objects named "z", but they must not be
both contained by ".x.y".
A model element has name and class attributes. This value of the class attribute must be a class that
instantiate by the MoML tool. For example, in Ptolemy II, we can define a model with:
Here, ptolemy. actor. TypedCompositeActor is a class that a Java class loader can find and that
the MoML parser can instantiate. In Ptolemy II, it is a container class for clustered graphs representing
executable models or libraries of instantiable model classes. A model can be an instance of NamedObj
or any derived class, although most useful models will be CompositeEntity or a derived class. Typed-
CompositeActor, as in the above example, is derived from CompositeEntity.
Notice the common XML shorthand here of writing "<entity ... />" rather than "<entity
41
. . . ></entity>." Of course, the shorthand only works if there is nothing in the body of the entity
element.
An entity can contain other entities, as shown in this example:
An entity must specify a class unless the entity already exists in the containing entity or model. The
name of the entity reflects the container hierarchy. Thus, in the above example, the source entity has
the full name ".ptllmodel. container. source".
The definition of an entity can be distributed in the MoML file. Once created, it can be referred to
again by name as follows:
<entity name="x">
<property name="y">
</entity>
</entity>
The property element (see section 3.3.6 below) is added to the pre-existing entity with name "x" when
the second entity element is encountered.
In principle, MoML supports multiple containment, as in the following:
Here, the element named "x" appears both in "top" and in ".top.y". Thus, it would have two full
names, ".top.x" and ".top.y.x". However, Ptolemy II does not support this, as it implements a strict
container relationship, where an object can have only one container. Thus, attempting to parse the
above MoML will result in an exception being thrown.
3.3.6 Properties
Entities (and some other elements) can be parameterized. There are two mechanisms. The simplest
one is to use the property element:
42
value="5"
class="ptolemy.data.expr.Parameter"/>
</entity>
The property element has a name, at minimum (the value and class are optional). It is common for the
enclosing class to already contain properties, in which case the property element is used only to set the
value. For example:
In the above, the enclosing object {source, an instance of ptolemy. actor. lib. Ramp) must already
contain a property with the name init. This is typically how library components are parameterized. In
Ptolemy II, the value of a property may be an expression, as in "Pi/50". The expression may refer to
other properties of the containing entity or of its container. Note that the expression language is not
part of MoML, but is rather part of Ptolemy II. In MoML, a property value is simply an uninterpreted
string. It is up to a MoML tool, such as Ptolemy II, to interpret that string.
A property can be declared without a class and without a pre-existing property if it is a pure prop-
erty, one with only a name and no value. For example:
A second, much more flexible mechanism is provided for parameterizing entities. The configure
element can be used to specify a relative or absolute URL pointing to a file that configures the entity,
or it can be used to include the configuration information in line. That information need not be MoML
information. It need not even be XML, and can even be binary encoded data (although binary data can-
not be in line; it must be in an external file). For example,
Here, url can give the name of a file containing data, or a URL for a remote file. (For the Sequence-
Plotter actor, that external data will have PlotML syntax; PlotML is another XML schema for config-
uring plotters.) Configure information can also be given in the body of the MoML file as follows:
43
</configure>
</entity>
With the above syntax, the configure information must be textual data. It can contain XML markup
with only one restriction: if the tag "</conf igure>" appears in the textual data, then it must be pro-
ceeded by a matching "<conf igure>". That is, any configure elements in the markup must have bal-
anced start and end tags.1
You can give both a source attribute and in-line configuration information, as in the following:
In this case, the file data will be passed to the application first, followed by the in-line configuration
data.
In Ptolemy II, the configure element is supported by any class that implements the Configurable
interface. That interface defines a configure() method that accepts an input stream. Both external file
data and in-line data are provided to the class as a character stream by calling this method.
There is a subtle limitation with using markup within the configure element. If any of the elements
within the configure element match MoML elements, then the MoML DTD will be applied to assign
default values, if any, to their attributes. Thus, this mechanism works best if the markup with the con-
figure element is not using an XML schema that happens to have element names that match those in
MoML, or if it does use MoML element names, then it is using them with their MoML meaning. This
limitation can be fixed using XML namespaces, something we will eventually implement.
With the above syntax, the documentation information must be textual data. It can include markup, as
in the following example, which uses XHTML2 formatting within the doc element:
1. XML allow markup to be included in arbitrary data as long as it appears within either a processing instruction or
a CDATA body. However, for reasons that would only baffle anyone familiar with modern programming lan-
guages, processing instructions and CDATA bodies cannot be nested within one another. The MoML configure
element can be nested, so it offers a much more flexible mechanism than the standard ones in XML.
44
<entity name="source" class="ptolemy.actor.lib.Ramp">
<doo<Hl>Using HTML</Hl>Text with <I>markup</I>.</doc>
</entity>
This requires that any utility that uses the documentation information be able to handle the xhtml pro-
cessing instruction, but it makes it very clear that the contents are XHTML. However, for reasons we
do not understand, XML does not allow processing instructions to be nested, so this technique has its
limitations.
More than one doc element can be included in an element. To do this, give each doc element a
name, as follows:
The name must not conflict with any preexisting property. If a doc element or a property with the spec-
ified name exists, then it is removed and replaced with the property. If no name is given, then the doc
element is assigned the name "doc".
A common convention, used in Ptolemy II, is to add doc elements with the name "tooltip" to
define a tooltip for GUI views of the component. A tooltip is a small window with short documenta-
tion that pops up when the mouse lingers on the graphical component.
There is a subtle limitation with using markup within the doc element. If any of the elements
within the doc element match MoML elements, then the MoML DTD will be applied to assign default
values, if any, to their attributes. Thus, this mechanism works best if the markup with the doc element
is not using an XML schema that happens to have element names that match those in MoML, or if it
does use MoML element names, then it is using them as MoML. This limitation can be fixed using
XML namespaces, something we will eventually implement.
3.3.8 Ports
An entity can declare a port:
2. XHTML is HTML with additional constraints so that it conforms with XML syntax rules. In particular, every
start tag must be matched by an end tag, something that ordinary HTML does not require (but fortunately, does
allow).
45
In the above example, no class is given for the port. If a port with the specified name already exists in
the class for entity A, then that port is the one referenced. Otherwise, a new port is created in Ptolemy
II by calling the newPort() method of the container. Alternatively, we can specify a class name, as in
In this case, a port will be created if one does not already exist. If it does already exist, then its class is
checked for consistency with the declared class (the pre-existing port must be an instance of the
declared class). In Ptolemy II, the typical classname for a port would be
ptolemy.actor.TypedlOPort
This is an example of a pure property. Optionally, the property can be given a boolean value, as in
The value can be either "true" or "false", where the latter will define the port to not be an output. A
port can be defined to be both an input and an output, as follows
It is also sometimes necessary to declare that a port is a multiport. To do this, enclose in the port a
property named "multiport" as in the following example:
The enclosing port must be an instance of IOPort (or a derived class such as TypedlOPort), or else the
property is treated as an ordinary property. As with the input and output attribute, the multiport prop-
erty can be given a boolean value, as in
46
<port name="out" class="ptolemy.actor.IOPort">
<property name="multiport" value="true"/>
</port>
If a port is an instance of TypedlOPort (for library actors, most are), then you can set the type of
the port in MoML as follows:
This is occasionally useful when you need to constrain the types beyond what the built-in type system
takes care of. The names of the built-in types are (currently) boolean, booleanMatrix, complex, com-
plexMatrix, double, doubleMatrix, fix, fixMatrix, int, intMatrix, long, longMatrix, object, string, and
general. These are defined in the class ptolemy.data.type.BaseType.
47
</port>
</entity>
<relation name="rl" class="classname"/>
<relation name="r2" class="classname"/>
<link port="A.out" relation="rl"/>
<link port="B.out" relation="r2"/>
<link port="C.in" relation="rl"/>
<link port="C.in" relation="r2"/>
</entity>
Whenever the insertAt option is not specified, the link is always appended to the end of the list of
links.
When the insertAt option is specified, the link is inserted at that position, so any pre-existing links
with larger indices will have their index numbers incremented. For example, if we do
then there will be a link to rl with index 0, a link to r2 with index 2 (note! not 1), and a link to r3 with
index 1.
If the specified index is beyond the existing number of links, then null links are created to fill in.
So for example, if the first link we create is given by
then the port will have two links, not one, but the first one will be an empty link. If we then say
then the port will have three links, with the first one being empty. If we then say
48
then there will be four links, with the second one being empty.
Note that the index number is not the same thing as the channel number in Ptolemy II. In Ptolemy
II, a relation may have a width greater than one, so a single link may represent more than one channel
(actually, it could even represent zero channels if that relation is not linked to another ports).
3.3.10 Classes
So far, entities have been instances of externally defined classes accessed via a class loader. They
can also be instances of classes defined in MoML. To define a class in MoML, use the class element,
as in the following example:1
The class element may be the top-level element in a file, in which case the DOCTYPE should be
declared as "class" as done above. It can also be nested within a model. The above example specifies
the topology shown in figure 3.7. Once defined, can be instantiated as if it were a class loaded by the
class loader:
or
<entity name="instancename" class="classname" source="url"/>
The first form can be used if the class definition can be found from the classname. There are two ways
that this could happen. First, the classname might be an absolute name for a class defined within the
same top level entity that this entity element is in. Second, the classname might be sufficient to find the
1. This is a simplified version of the Sinewave class, whose complete definition is given in the appendix.
49
class definition in a file, much the way Java classes are found. For example, if the classname is
ptolemy.actor.lib.Sinewave and the class is defined in the file $PTII/ptolemy/actor/
lib/sinewave .xml, then there is no need to use the second form to specify the URL where the class
is defined. Specifically, the CLASSPATH is searched for a file matching the classname. By conven-
tion, the file defining the class has the same as the class, with the extension " .xml" or" .moml".
In the first of these techniques, the class name follows the same convention as entity names, except
that a classname referring to a class defined within the same MoML top-level must be absolute. In fact,
a class is an entity with the additional feature that one can create new instances of it with the entity ele-
ment. Consider for example,
Here, the entity derived is an instance of .top.Gen, which is defined within the same MoML top
level. The absolute class name is ". top. Gen".
The ability to give a URL as the source of a class definition is very powerful. It means that a model
may be build from component libraries that are defined worldwide. There is no need to localize these.
Of course, referencing a URL means the usual risks that the link will become invalid. It is our hope
that reliable and trusted sources of components will emerge who will not allow this to happen.
The Gen class given at the beginning of this subsection generates a sine wave with a period of 50
samples. It is not all that useful without being parameterized. Let us extend it and add properties:2
Gen
1. CLASSPATH is an environment variable that Java uses to find Java classes. The Ptolemy II implementation of
MoML simply leverages this so that MoML classes can also be found if they are on the CLASSPATH.
2. This is still not quite as elaborate as the Sinewave class defined in the appendix, which is why we give it a
slightly different name, Sinegen.
50
value="8000.0"
class="ptolemy.data.expr.Parameter">
<doc>The sampling frequency in Hertz.</doc>
</property>
<property name="frequency"
value="440.0"
class="ptolemy.data.expr.Parameter">
<doc>The frequency in Hertz.</doc>
</property>
<property name="ramp.step"
value="frequency*2*PI/samplingFrequency">
<doc>Formula for the step size.</doc>
</property>
<property name="ramp.init"
value="phase">
</property>
</class>
This class extends Gen by adding two properties, and then sets the properties of the component entities
to have values that are expressions.-
3.3.11 Inheritance
MoML supports inheritance by permitting you to extend existing classes. For example, consider
the following MoML file:
Here, the "derived" class extends the "base" class by adding another entity to it, and "instance" is an
instance of derived. The class "derived" can also give a source attribute, which gives a URL for the
source definition.
3.3.12 Directors
Recall that a clustered graph in MoML has no semantics. However, a particular model has seman-
tics. It may be a dataflow graph, a state machine, a process network, or something else. To give it
semantics, Ptolemy II requires the specification of a director associated with a model, an entity, or a
51
class. The director is a property of the model. The following example gives discrete-event semantics to
a Ptolemy II model:
</entity>
This example also sets a property of the director. The name of the director is not important, except that
it cannot collide with the name of any other property in the model.
This takes the contents of the URL specified in the source attribute of the input element and places
them inside the entity named "a". The base of the current document (the one containing the import
statement) is used to interpret a relative URL, or if the current document has no base, then the current
working directory is used, or if that fails, the current CLASSPATH.
This assumes that there are three entities named A, B, and C. The relation is annotated with a set of
vertices, vl and v2, which will normally be rendered as graphical objects. The vertices are linked
together with paths, which in a simple visual tool might be straight lines, or in a more sophisticated
tool might be autorouted paths. In the above example, vl and v2 are linked by a path. The link ele-
52
ments specify not just a relation, but also a vertex within that relation. This tells the visual rendering
tool to draw a path from the specified port to the specified vertex.
Figure 3.8 illustrates how the above fragment might be rendered. The square boxes are icons for
the three entities. They have ports with arrowheads suggesting direction. There is a single relation,
which shows up visually only as a set of lines and two vertices. The vertices are shown as small dia-
monds.
A vertex is exactly like a property, except that it has an additional attribute, pathTo, used to link
vertices, and it can be referenced in a link element. Like any other property, it has a class attribute,
which specifies the class implementing the vertex. In Ptolemy II, the class for a vertex is typically
ptolemy.moml.Vertex. Like other properties, a vertex can have a value. This value will typically spec-
ify a location for a visual rendition. For example, in Ptolemy II, the first vertex above might be given
as
<vertex name="vl"
class="ptolemy.moml.Vertex"
value="184.0, 93.0"/>
This indicates that the vertex should be rendered at the location 184.0, 93.0.
Ptolemy II uses ordinary MoML properties to specify other visual aspects of a model. First, an
entity can contain a location property, which is a hint to a visual renderer, as follows:
This suggests to the visual renderer that the Ramp actor should be drawn at location 50.0, 50.0.
Ptolemy II also supports a powerful and extensible mechanism for specifying the visual rendition
of an entity. Consider the following example:
53
<rect x="0" y="0" width="80" height="20"
style="fill:green;stroke:black;stroke-width:5"/>
</svgx/conf igure>
</property>
</entity>
The SingletonAttribute class is used to attach an XML description of the rendition, which in this case
is a wide box filled with green. The XML schema used to define the icon is SVG (scalable vector
graphics), which can be found at http://www.w3.org/TR/SVG/.1
The rendering of the icon is done by another property of class XMLIcon, which need not be
explicitly specified because the visual Tenderer will create it if it isn't present. However, it is possible
to create totally customized renditions by defining classes derived from XMLIcon, and attaching them
to entities as properties. This is beyond the scope of this chapter.
Later, the following MoML element can be used to add an entity to the model:
<entity name=".top">
<entity name="inside" class="ptolemy.actor.TypedCompositeActor"/>
</entity>
The name of the outer entity ". top" is the name of the top-level model created by the first segment of
MoML. (Recall that the leading period means that the name is absolute.) The line
<entity name=".top">
1. Currently, diva, which is used by Vergil to render these icons, only supports a small subset of SVG Eventually,
we hope it will support the full specification.
54
Any entity constructed in a previous parsing phase can be specified as the context for evaluation of a
new MoML element.
Of course, the MoML parser must have a reference to the context in order to later parse this incre-
mental element. This is accomplished by either using the same parser, which keeps track of the top-
level entity in the last model it parsed, or by calling the setTopLevel() or setContext() methods of the
parser, passing as an argument the model.
The ".top. a" cannot be specified within "b" because it is already contained within "top."
<entity name=".top.inside">
<port name="input" class="ptolemy.actor.TypedIOPort"/>
</entity>
<entity name=".top">
<relation name="r" class="ptolemy.actor.TypedIORelation"/>
<link port="inside.input" relation="r"/>
</entity>
55
A port can be made into a multiport as follows:
<port name="portname">
<property name="multiport" value="true"/>
</port>
<entity name=".top">
<deleteEntity name="inside"/>
</entity>
Any links to ports of the entity will also be deleted. Similarly, relations can be deleted using the dele-
teRelation element, and ports can be deleted using the deletePort element.
<entity name="entityName">
<rename name="newName"/>
</entity>
The new name is required to not have any periods in it. It consists of alphanumeric characters, the
underscore, and spaces.
<doc name="docname"x/doc>
or
<doc name="docname"/>
Properties can have their value changed using the property element (see section 3.3.6) with a new
value, for example:
56
A property can be deleted using the deleteProperty element
<deleteProperty name="propertyname"/>
Since a director is a property, this same mechanism can be used to remove a director.
This unlinks a port from the specified relation. If the port is linked more than once to the specified rela-
tion, then all links to this relation are removed. It makes no difference whether the link is an inside link
or an outside link, since this can be determined from the containers of the port and the relation.
The second and third forms are
These both remove a link by index number. The first is used for an outside link, and the second for an
inside link. The valid indices range from 0 to one less than the number of links that the port has. If the
port is not a multiport, then there is at most one valid index, number 0. If an invalid index is given then
the element is ignored. Note that the indexes of links above that of the removed link will be decre-
mented by one.
The unlink element can be used to remove even null links. For example, if we have created a link
with
where there was previously no link on this port, then this leaves a null link (not linked to anything)
with index 0 (see section 3.3.9), and of course a link to relation r with index 1. The null link can be
removed with
57
<entity name="firstEntity" class="classname"/>
<entity name-"firstEntity" class="classname"/>
<entity name="firstEntity" class="classname"/>
However, the XML parser will fail to parse this because it requires that there be a single top-level ele-
ment. The group element is provided for this purpose:
<group>
<entity name="firstEntity" class="classname"/>
<entity name="firstEntity" class="classname"/>
<entity name="firstEntity" class="classname"/>
</group>
This element is ignored by the parser, in that it does not define a new container for the enclosed enti-
ties. It simply aggregates them, leaving the context the same as it is for the group element itself.
The group element may be given a name attribute, in which case it defines a namespace. All
named objects (such as entities) that are immediately inside the group will have their names modified
by prepending them with the name of the group and a colon. For example,
<group name="a">
<entity name="b" class="classname">
<entity name="c" class="classname"/>
</entity>
</group>
The entity "b" will actually be named "a:b". The entity "c" will not be affected by the group name. Its
full name, however, will be "a:b.c".
58
no such parser, then it is necessary to call the setToplevel () method of MoMLParser to associate
the parser with the pre-existing model.
Incremental parsing should (usually) be done using a change request. A change request is queued
with a composite entity container by calling its requestChange() method. This ensures that the muta-
tion is executed only when it is safe to modify the structure of the model. The class MoMLChangeRe-
quest (see figure 3.9) can be used for this purpose. Simply create an instance of this class, providing
the constructor with a string containing the MoML code that specifies the change.
The exportMoMLO methods of Ptolemy II objects can be used to produce a MoML file given a
model. Thus, MoML can be used as the persistent file format for Ptolemy II models
«Interface»
com.mlcrostar.xml.HanderBase ErrorHandler
+attribute(name: String, value : String, isSpecified: boolean) i +handleError(text: String, exception : Exception, parser: MoMLParser)
+charData(data : chart], start: int, length : int) I
+docTypeDecl(name : String, publicID: String, systemID: String)! 0..1
+endDocument() j
+endElement(name: String) j MoMLParser
+endExtemalEntity(systemlD: String) j
+error(message : String, systemID: String, line : int, column: int)] +MoML DTD 1 : String
+ignorableWhitespace(data : chart], start: int, length : int) ] base: URL
+processinglnstruction(target: String, data : String) i .current: Object
resolveEntity(publiclD: String, systemID: String) j -_currentElement: String
+startDocument() j -_manager: Manager
+startElement(name: String) i -_panel: Container
+startExtema!Entity(systemlD: String) • -_parser: XmlParser jcom.mlcrostar.xml.XmlParser!
-_toplevel: NamedObj
-_workspace: Workspace
MoMLParserO
MoMLParser(w: Workspace)
+MoMLParser(w : Workspace, loader: ClassLoader)
+addErrorHandlerthandler: ErrorHandler)
+getTopLevel(): NamedObj
+parser(base: URL, input: URL) ChangeRequest
Configurable +parse(base : URL, Input: InputStream): NamedObj
+parse(base : URL, reader: Reader): NamedObj
+parse(input: String): NamedObj :+execute()
+parse(base : URL, text: String)
A
ComposKeEnttty
If +parseFile(filename: String)
^ +reset()
+setContext(context: NamedObj)
J +setToplevel(toplevel: NamedObj)
#_currentExtemalEntity(): String
EntftyLlbrary
MoMLChangeRequast
■_parser; MoMLParser
+EntityLibrary()
■_base: URL
+EntityLibrary(workspace: Workspace)
■„context: NamedObj
+EntityLibrary(container: CompositeEntity, name : String)
■_parser: MoMLParser
+populate()
-„propagating: boolean
+MoMLChangeRequest(originator: Object, request: String)
♦MoMLChangeRequestjoriginator: Object, context: NamedObj, request: String)
+MoMLChangeRequest(originator: Object, context: NamedObj, request: String, t i: URL)
»oetDeferredToParentfobiect: NamedObj): NamedObj
59
3.6 Exporting MoML
Almost any Ptolemy II object can export of MoML description of itself. The following methods of
NamedObj (and derived classes) are particularly useful:
exportMoML(): String
exportMoML(output: Writer)
exportMoML(output: Writer, depth: int)
exportMoML(output: Writer, depth: int, name: String)
_exportMoMLContents(output: Writer, depth: int)
Since any object derived from NamedObj can export MoML, MoML becomes an effective persistent
format for Ptolemy II models. Almost everything in Ptolemy II is derived from NamedObj. It is much
more compact than serializing the objects, and the description is much more durable (serialized objects
might not load properly into future versions of the Java virtual machine).
There is one significant subtlety that occurs when an entity is instantiated from a class defined in
MoML. Consider the example:
This model defines one class and one entity that instantiates that class. When we export MoML for this
top-level model, we get:
Aside from some minor differences in syntax, this is identical to our specification above. In particular,
note that the entity "derived" does not describe its port "p" even though it certainly has such a port.
That port is implied because the entity instantiates the class ". top. master".
Suppose that using incremental parsing we subsequently modify the model as follows:
<entity name=".top.derived">
<port name="q" class="ptolemy.kernel.ComponentPort"/>
</entity>
That is, we add a port to the instantiated entity. Then the added port is exported when we export
60
MoML. That is, we get:
This is what we would expect. The entity is based on the specified class, but actually extends it with
additional features. Those features are persistent.
Properties are treated more simply. They are always described when MoML is exported, regardless
of whether they are defined in the class on which an entity is based. The reason for this is that proper-
ties are usually modified in instances, for example by giving them new values.
There is an additional subtlety. If a topology is modified by directly making kernel calls, then
exportMoMLO will normally export the modified topology. However, if a derived component is modi-
fied by direct kernel calls, then exportMoML() will fail to catch the changes. In fact, only if the
changes are made by evaluating MoML will the modifications be exported. This actually can prove to
be convenient. It means that if a model mutates during execution, and is later saved, that a user inter-
face can ensure that only the original model, before mutations, is saved.
3.8 Acknowledgements
Many thanks to Ed Willink of Racal Research Ltd. and Simon North of Synopsys for many helpful
suggestions, only some of which have made it into this version of MoML. Also, thanks to Tom Henz-
inger, Alberto Sangiovanni-Vincentelli, and Kees Vissers for helping clarify issues of abstract syntax.
61
Appendix C: Example
Figures 3.11 and 3.12 show a simple Ptolemy II model in the SDF domain. Figure 3.13 shows the
execution window for this model. This model generates two sinusoidal waveforms and multiplies them
together. This appendix gives the complete MoML code. The MoML code is divided into two files.
The first of these defined a component, a sinewave generator. The second creates two instances of this
sinewave generator and multiplies their outputs. The code listings are (hopefully) self-explanatory.
Locatable
Attribute
+getLocation(): doubleQ
+setLocation(location : doubleQ)
J_
Location MoMLAttribute
62
<port name="output" class="ptolemy.actor.TypedIOPort">
<property name="output"/>
<doc>Sinusoidal waveform output.</doo>
<property name="_location" class="ptolemy.moml.Location" value="4G0.0, 225.0">
</property>
</port>
<entity name="ramp" class="ptolemy.actor.lib.Ramp">
<property name="firingCountLimit" class="ptolemy.data.expr.Parameter" value="0">
</property>
<property name="init" class="ptolemy.data.expr.Parameter" value="phase">
</property>
<property name="step" class="ptolemy.data.expr.Parameter"
value="frequency*2*PI/samplingFrequency">
</property>
<property name="_location" class="ptolemy.moml.Location" value="140.0, 225.0">
</property>
<port name="output" class="ptolemy.actor.TypedlOPort">
<property name="output"/>
</port>
<port name="trigger" class="ptolemy.actor.TypedlOPort">
<property name="input"/>
<property name="multiport"/>
</port>
</entity>
<entity name="TrigFunctionO" class="ptolemy.actor.lib.TrigFunction">
<property name="function" class="ptolemy.kernel.util.StringAttribute" value="sin">
<property name="style" class="ptolemy.actor.gui.style.ChoiceStyle">
<property name="acos" class="ptolemy.kernel.util.StringAttribute" value="acos">
</property>
<property name="asin" class="ptolemy.kernel.util.StringAttribute" value="asin">
</property>
<property name="atan" class="ptolemy.kernel.util.StringAttribute" value="atan">
</property>
<property name="cos" class="ptolemy.kernel.util.StringAttribute" value="cos">
</property>
<property name="sin" class="ptolemy.kernel.util.StringAttribute" value="sin">
</property>
<property name="tan" class="ptolemy.kernel.util.StringAttribute" value="tan">
</property>
</property>
</property>
<property name="_location" class="ptolemy.moml.Location" value="300.0, 225.0">
</property>
<port name="input" class="ptolemy.actor.TypedIOPort">
<property name="input"/>
► ♦
CJ utilities
£j director library
Cj actor library
ramp TrigFunctionO
O Graphics
t ^ääl •^►1 fe
F
M
FIGURE 3.11. Rendition of the Sinewave class in Vergil 1.0.
63
</port>
<port name="output" class="ptolemy.actor.TypedIOPort">
<property name="output"/>
</port>
</entity>
<relation name=" relation]." class="ptoletny.actor.TypedlORelation">
</relation>
<relation name="relation2" class="ptolemy.actor.TypedlORelation" >
</relation>
<link port="output" relation="relationl"/>
<link port="ramp.output" relation="relation2"/>
<link port="TrigFunctionO.input" relation="relation2"/>
<link port="TrigFunctionO.output" relation="relationl"/>
</class>
C.2 Modulation
The top-level is defined in the file $PTII/ptolemy/moml/demo/modulation.xml, which is listed
below. The Vergil rendition of this model is shown in figure 3.12, and its execution is shown in figure
3.13.
y 1|file:/C:/ptII/ptolemy/moml/demo/modulation.Kml JOjxl
File View Edit Graph Help
► ♦
lA utilities director
1 director library
__J actor library
1 Graphics
signal display
El
L^J r • ^^^^^f^-
mult
ÜjäT. - Jf \
carrier n ►«
i
1
FIGURE 3.12. Rendition of the modulation model in Vergil 1.0.
64
<?xml version="1.0" standalone="no"?>
<!DOCTYPE entity PUBLIC "-//UC Berkeley//DTD MoML 1//EN"
"http://ptolemy.eecs.berkeley.edu/xml/dtd/MoML_l.dtd">
<entity name="modulation" class="ptolemy.actor.TypedCompositeActor">
<doc>Multiply a low-frequency sine wave (the signal) by a higher frequency one (the carrier).</doc>
<property name="frequencyl" class="ptolemy.data.expr.Parameter" value="PI*0.2">
<doc>Frequency of the carrier</doc>
</property>
<property name="frequency2" class="ptolemy.data.expr.Parameter" value="PI*0.02">
<doc>Frequency of the sinusoidal signal</doc>
</property>
<property name="director" class="ptolemy.domains.sdf.kernel.SDFDirector">
<property name="iterations" class="ptolemy.data.expr.Parameter" value="100">
<doc>Number of iterations in an execution.</doc>
</property>
<property name="vectorizationFactor" class="ptolemy.data.expr.Parameter" value="l">
</property>
<property name="_location" class="ptolemy.moml.Location" value="62.0, 23.0">
</property>
</property>
<entity name="carrier" class="ptolemy.actor.lib.Sinewave">
<doc>This composite actor generates a sine wave.</doc>
<property name="samplingFrequency" class="ptolemy.data.expr.Parameter" value="2*PI">
<doc>The sampling frequency, in the same units as the frequency.</doc>
</property>
<property name="frequency" class="ptolemy.data.expr.Parameter" value="frequencyl">
<doc>The frequency of the sinusoid, in the same units as the sampling frequency.</doc>
</property>
<property name="phase" class="ptolemy.data.expr.Parameter" value="0.0">
<doc>The phase, in radians.</doc>
</property>
<property name="_location" class="ptolemy.moml.Location" value="215.0, 250.0">
</property>
</entity>
<entity name="signal" class="ptolemy.actor.lib.Sinewave">
<doc>This composite actor generates a sine wave.</doc>
<property name="samplingFrequency" class="ptolemy.data.expr.Parameter" value="2*PI">
<doc>The sampling frequency, in the same units as the frequency.</doc>
</property>
<property name="frequency" class="ptolemy.data.expr.Parameter" value="frequency2">
<doc>The frequency of the sinusoid, in the same units as the sampling frequency.</doc>
</property>
frequencyl: |pi*o.2
frequency2: |PI*0.p2
Director parameters:
iterations:
vectorizationFactor:
0.4 0.6
sample count x10
execution finished.
65
<property name="phase" class="ptolemy.data.expr.Parameter" value="0.0">
«doc*The phase, in radians.</doc>
</property>
«property name="_location" class="ptolemy.moml.Location" value="143.0, 135.0">
</property>
</entity>
<entity name="mult" class="ptolemy.actor.lib.MultiplyDivide">
«property name="_location" class="ptolemy.moml.Location" value="347.0, 196.0">
</property>
«port name="multiply" class="ptolemy.actor.TypedIOPort">
<property name="input"/>
<property name="multiport"/*
</port>
<port name="divide" class="ptolemy.actor.TypedlOPort"*
«property name="input"/*
«property name="multiport"/>
«/port*
<port name="output" class="ptolemy.actor.TypedIOPort">
«property name="output"/>
</port>
</entity>
«entity name="display" class="ptolemy.actor.lib.gui.SequencePlotter"*
«property name="fillOnWrapup" class="ptolemy.data.expr.Parameter" value="true">
«/property>
«property name="startingDataset" class="ptolemy.data.expr.Parameter" value="0">
«/property*
«property name="xlnit" class="ptolemy.data.expr.Parameter" value="0.0">
«/property*
«property name="xUnit" class="ptolemy.data.expr.Parameter" value="1.0">
«/property*
«property name="_location" class="ptolemy.moml.Location" value="479.99998474121094, 135.0">
«/property*
«port name="input" class="ptolemy.actor.TypedlOPort"*
«property name="input"/*
«property name="multiport"/*
«/port*
«configure*«?plotml
«1D0CTYPE plot PUBLIC "-//UC Berkeley//DTD PlotML 1//EN"
"http://ptolemy.eecs.berkeley.edu/xml/dtd/PlotML_l.dtd"*
«plot*
<title*Modulated Waveform Example«/title*
<xLabel*sample count«/xLabel*
«yLabe1>amplitude«/yLabe1*
«xRange min="1.0" max="100.0"/*
«yRange min="-1.0" max="1.0"/*
«noGrid/*
«/plot*?*
«/configure*
«/entity*
«relation name="rl" class="ptolemy.actor.TypedlORelation"*
«/relation*
«relation name="r2" class="ptolemy.actor.TypedlORelation"*
«vertex name="vertexO" class="ptolemy.moml.Vertex" value="279.0, 141.0"*
«/vertex*
«/relation*
«relation name="r3" class="ptolemy.actor.TypedlORelation"*
«/relation*
«link port="carrier.output" relation="rl"/*
«link port="signal.output" relation="r2"/*
«link port="mult.multiply" relation="rl"/*
«link port="mult.multiply" relation="r2"/*
«link port="mult.output" relation="r3"/*
«link port="display.input" relation="r2"/*
«link port="display.input" relation="r3"/*
«/entity*
66
Custom Applets
Authors:
Edward A. Lee
Christopher Hylands
4.1 Introduction
Ptolemy II models can be embedded in applets. In most cases, the MoMLApplet class can be used,
as described in the previous chapter. Sometimes, however, it is useful to be able to define a custom
applet class with a more sophisticated user interface or a more elaborate method for model construc-
tion or manipulation. This chapter explains how to do that.
For convenience, most domains include a base class XYApplet, where XX is replaced by the
domain name. This section uses a DE domain applet to illustrate the basic concepts, so the base class is
DEApplet. Refer to subsequent chapters and to the code documentation for more complete information
about the classes and methods being used. DEApplet is derived from PtolemyApplet, as shown in fig-
ure 4.1 (see appendix A of chapter 1 for UML syntax).
67
«Interface»
ExKutlonLitlemr Applet
Ptolemy Applet
Manager
-_manager: Manager
-_toplevel: TypedCompositeActor
+PtotemyApplet()
+report(e: Exception)
+report(msg : String, e : Exception)
#_concatStringArrays(first: String(][], second : Stringdt]): StringQrj
#_createRunContrats(numButtons : int): Panel
■o *Lg°()
#_stop()
SDF Applet
!# director: SDFÖirector;
DEApplet
!# director: DEDirector:
FIGURE 4.1. UML static structure diagram for Ptolemy Applet, a convenience base class for constructing
applets. Ptolemy Applet is in the ptolemy.actor.gui package.
FIGURE 4.2. An HTML segment that invokes the Java 1.2 Plug-in under both Netscape and Internet
Explorer (it is regrettable how complex this is). This text can be found in $PTII/doc/tutorial/tutorial.htm.
68
files contain a series of applet definitions, each with increasing sophistication, that are discussed
below. To compile and use each file, it must be copied into TutorialAppletjava (without the ri).
Since our example applets are in a directory $PTII/doc/tutorial, the codebase for the applet is "../.."
in figure 4.2, which is the directory $PTII. This permits the applets to refer to any class in the Ptolemy
II tree.
There are some parameters in the HTML in figure 4.2 that you may want to change. The width and
the height, for example, specify the amount of space on the screen that the browser gives to the applet.
Unfortunately, they are specified twice in the file.
Fortunately, getting the Java code right is easy compared to getting the HTML right.
defines a class called TutorialApplet that extends DEApplet. The new class overrides the init() method
of the base class with the following body:
package doc.tutorial;
import ptolemy.domains.de.gui.DEApplet;
import ptolemy.actor.lib.Clock;
import ptolemy.actor.gui.TimedPlotter;
FIGURE 4.3. An extremely simple applet that runs in the DE domain. This text can be found in $PTII/tuto-
rial/TutorialAppletl java. It should be copied to TutorialAppletjava before compiling. This text can be
found in $PTII/doc/tutorial/TutorialApplet 1.java.
69
super.init();
try {
Clock clock = new Clock(_toplevel,"clock");
TimedPlotter plotter = new TimedPlotter(_toplevel,"plotter");
_toplevel.connect(clock.output, plotter.input);
} catch (Exception ex) {}
This first invokes the base class, then creates an instance of Clock and an instance of TimedPlotter, and
connects them together.
The constructors for Clock and TimedPlotter take two arguments, the container (a composite
actor), and an arbitrary name (which must be unique within the container). This example uses the vari-
able _toplevel, provided by the base class, as a container. The connection is accomplished by the
connect() method of the composite actor, which takes two ports as arguments. Instances of Clock have
one output port, output, which is a public member, and instances of TimedPlotter have one input
port, input, which is also a public member. We will discuss the try ... catch statement below.
4.2.2 Compiling
To compile this class definition, you must first copy Tutorial Applet 1 .Java into TutorialApplet.java
(Java requires that file names match the names of the classes they contain). Then set the CLASSPATH
environment variable to point to the root of the Ptolemy II tree. For example, in bash, assuming the
variable PTII is set,
bash-2.02$ cd $PTII/doc/tutorial
bash-2.02$ cp TutorialAppletl.Java TutorialApplet.java
bash-2.02$ javac -classpath ../.. TutorialApplet.java
(The part before the "$" is the prompt issued by bash). You should now be able to run the applet with
the command:
The result of running the applet is a new window which should look like that shown in figure 4.4. This
is not (yet) a very interesting applet. Let us improve on it.
70
window, as part of the text of a web page.
The TimedPlotter actor, and most other Ptolemy II components with graphical elements, imple-
ments the Placeable interface. This interface has a single method, setPanel(), which can be used to
specify the panel into which the object should be placed. If this method is not called before the object
is mapped to the screen, then the object will create its own frame into which to place itself. This is
what happened with our applet, resulting in the frame shown in figure 4.4.
Modified code that places the plot in the applet window itself (an instance of Applet is a Panel) is
shown in figure 4.6. Note that the size of the plot is now also specified to match the size of the applet,
using the statement:
plotter.plot.setSize (700,300);
This line refers to a public member of the plotter object, called "plot," which is an instance of the class
Plot, from the ptolemy.plot package. That class has a method, setSize(), that can be used to control the
size of the plot.
The resulting applet looks like that shown in figure 4.7. In this case, the default layout manager of
the Applet class is allowed to control where the plot is placed. Arbitrarily fine control can be exercised,
E^tutoHal-Tutofiai^ppäeiJopLevel.päot
■ fte SpecM
- "'^^J^ 1 1 ■ ,
- . -""^.^ . .
-0.0 0.1 0.! 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
ft/mng. Applet Window
FIGURE 4.4. Result of running the (all too simple) applet of figure 4.3.
package doc.tutorial;
import ptolemy. domains. de. gui. DEApplet ,-
import ptolemy.actor.lib.Clock;
import ptolemy.actor.gui.TimedPlotter;
FIGURE 4.5. An improved applet that properly reports errors in model construction. This text can be found
in $PTII/doc/tutoriaI/TutorialApplet2.java.
71
however, by placing a new instance of Panel in the applet using any suitable layout manager and then
specifying that panel for the plotter using its setPanel() method. This is done in the next section.
package doc.tutorial;
import ptolemy.domains.de.gui.DEApplet;
import ptolemy.actor.lib.Clock;
import ptolemy.actor.gui.TimedPlotter;
FIGURE 4.6. A modified applet that places the resulting plot in the browser window itself, as shown in fig-
ure 4.7. This text can be found in $PTII/doc/tutorial/TutorialApplet3.java.
Tutorial Applet
1.0
0.8
""~\.
0.6 "*~--^;_ "
0.4
-■^
0.2
"--
0.0 ^'-•-.. - -
-0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
72
_director.setStopTime(30.0);
Alternatively, you can set the stop time in the HTML file by setting an applet parameter called "stop-
Time." To set this applet parameter to 100, add the line
to the <OBJECT> ... </OBJECT> body in figure 4.2, and the parameter
stopTime="100"
to the < EMBED > tag. Regrettably, both must be added or the applet will behave differently in IE and
Netscape.
You can instruct the applet to put controls on the screen that allow the applet user to control the
model execution time with the following line (see figure 4.1):
add(_createRunControls(2));
The argument specifies how many run control buttons to create. A value of "1" or larger requests a
"go" button, while "2" or larger requests both a "go" and a "stop" button. We must also modify the size
of the plotter, as shown in figure 4.8. The size is set so that 50 pixels in the vertical direction are
allowed for the run controls. The resulting page, with an entered execution time of 30, is shown in fig-
ure 4.9.
The default stop time in the entry box can be controlled by setting an applet parameter "default-
StopTime." To set this applet parameter so that the default is 100, add the line
package doc.tutorial;
import ptolemy.domains.de.gui.DEApplet;
import ptolemy.actor.lib.Clock,-
import ptolemy.actor.gui.TimedPlotter;
import java.awt.Panel;
import java.awt.Dimension;
FIGURE 4.8. Code that adds execution time controls to the applet and resizes the plotter display to make
room for them. This code can be found in $PTII/doc/tutorial/TutorialApplet4.java.
73
<PARAM NAME="defaultStopTime" VALUE="100">
to the <OBJECT> ... </OBJECT> body in figure 4.2, and the parameter
defaultStopTime="100"
clock.period.setExpression("1.0");
More interestingly, you may wish to offer this control to the applet user. This can be done using built-
in Java classes to create an entry box in the applet, or more easily by using an instance of the Query
class in the ptolemy.gui package.
The code shown in figure 4.10 results in the browser display shown in figure 4.11. The important
changes are shown in bold. First, the class now has a private member _query that is an instance of
iJhiii.iiHU.i.nmiJusm PöTxl
: £fc E* Sfow fio £amur>c«t<x H<*
!): Back Fotwaid Reload Haw Sorch Nebeap* Pitt Sccu& : Rop
Tutorial Applet
JE]
74
Query. In addition, a reference to the Clock actor is now stored in a private member clock instead of
in a local variable in the init() method. The following lines have been added to the initQ method:
_query.setBackground(getBackground());
_guery.addLine("period", "Period", "2.0");
_guery.addQueryListener(this);
add(_query);
The first of these sets the background color of the query widget to match the background color of the
applet. The second adds a single-line entry box to the query, with name "period," label "Period," and
default entry "2.0." The name is an arbitrary string that can be used elsewhere to refer to this particular
entry in the query. In this applet, the query has only one entry, so giving it a name seems unnecessary.
But in general, a query can have any number of entries, so unique names are necessary.
The third line above informs the query that this applet is a listener, meaning that it should be noti-
fied when any entry in the query changes. To be a listener, it must implement the QueryListener inter-
face, which requires that a method changed() be implemented. That method is defined as:
FIGURE 4.10. Code that adds a parameter control to the applet. This code can be found in $PTII/doc/tuto-
rial/TutorialApplet5.java.
75
_clock.period.setExpression(_query.stringValue("period"));
try {
_go ();
} catch (Exception ex) {
report ("Error executing model.", ex);
}
}
This method is called by the query object whenever an entry in the query changes. The argument is the
name of the entry that changed. In this applet, there is only one entry, so we can ignore the argument.
We use the stringValue() method of the Query class to obtain the text of the entry, and the setExpres-
sion() method of the period parameter to set its value. In addition, we invoke the protected method
_go() of the parent class, DEApplet, to execute the model. Thus, whenever the applet user changes the
period parameter, the model automatically executes again.
Notice that the method name setExpression() suggests that the value need not be a number, but
rather can be an expression. Indeed, this is the case. The expression language that is supported is
defined in the Data Package chapter. In short, ordinary arithmetic operations on constants are sup-
ported, as are all the methods of the Java Math class (sin(), cos(), etc.), and some pre-defined constants
(pi, e, i,...). In addition, expressions can symbolically refer to other parameters (by name) contained in
the same container, or contained in the container of the container. Thus, for example, if you have sev-
eral actors that you want to have the same parameter value, define a parameter "x" in the container
(composite actor) and set the parameters in the actors to have value "x".
The plot display that is generated by this applet can be improved considerably. The TimedPlotter
MflWIWWWMIBffW
:
;.;ffa l* Jggw.-5o.:G"mricatv B*
,; £ 2 9 A A. & K* tf H
' B«dt;; Faiwad Reload Hem» Search Nattcap» Print Secufo Hop
,
:l ><f "Bockmrtt jfc Uca6oK|lite:///DI/pai< docAXMial/luloc'Bl.hlm p'Whtf» Ratted
E22Z
Tutorial Applet
Uli
10
\ r / i / ;
T 'A W\
[
■;
/"■
'/\:"
/ 1 / \
0.8
■i
f : / i / 1 \ ! :
■
0.6
\ \,
/ \i -:■/
/
■■
i
;■■
/
■ /
1 \1 //
0.4 :- '../ /
I
I
0.2
0.0
t/ I V i
V
!
V ■
■■-/■
/
■ '
i
i / i ,-
1/
1
i
i i
-0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30
76
actor is somewhat special in that it contains a public member that is not a Parameter, but is rather an
instance of Plot (see the Plot chapter). An instance of Plot can be configured in a large number of
ways. In figure 4.12, we have added the following lines to the applet init() method:
plotter.plot.setTitle("clock signal");
plotter.plot.setXLabel("time");
plotter.plot.setlmpulses(true);
plotter.plot.setConnected(false);
plotter.plot.setMarksStyle("dots");
The first line defines a title, and the second defines a label for the horizontal axis. The third requests a
"stem plot" style, where a line is drawn vertically from the value to the origin. The fourth requests that
events not be connected by lines, and the last requests that large dot be placed on each event. The
resulting applet, as viewed by Sun's appletviewer, is shown in figure 4.13. A more detailed example is
package doc.tutorial;
import ptolemy.domains.de.gui.DEApplet;
import ptolemy.actor.lib.Clock;
import ptolemy.actor.gui.TimedPlotter;
import ptolemy.gui.Query;
import ptolemy.gui.QueryListener;
import java.awt.Panel;
import java.awt.Dimension;
FIGURE 4.12. An applet that extends that in figure 4.10 by configuring the plotter. This code can be found
in $PTII/doc/tutorial/TutorialApplet6.java.
77
shown in the appendix.
clock signal
mi I
: 1.0
- 4 ■' ' ' ' ■ ►■■■■■ ■ ' ■ ' . '.....< ..'...' .
i 0.8 -
: 0.6 -
i 0.4
' 0.2 -
: -0.0 ■■♦ , , —
"t ■" ".
...* -t ■ ■• :■
* .
-0 2 4 8 10 12 14 16 19 20 22 24 26 28 30
time
Execution finished.
FIGURE 4.13. View of the applet in figure 4.12, as displayed by Sun's appletviewer.
78
peruse the Ptolemy II tree (rooted at $PTII) and look for jar files. There are a few exceptions; for exam-
ple, domain jar files, such as de.jar, do not include the demos, even though the demos are in a subpack-
age of the domain package.
The longer story is that the make install rule in Ptolemy II makefiles builds various jar files
that contain the Ptolemy II .class files. In general, make install builds a jar file in each directory that
contains more than one .class file. If a directory contains subdirectories that in turn contain jar files,
then the subdirectory jar files are expanded and included in the upper level jar file. For example, the
$PTII/ptolemy/kernel/makefile contains:
In this case make install will build ajar file called kernel. j ar that contains all the .class files in
the current directory and the contents of ptolemy/kernel/event/event.jar and ptolemy/kernel/util/
util.jar.
<OBJECT classid="clsid:8AD9C840-044E-llDl-B3E9-00805F499D93"
width="700"
height="300"
codebase="http://java.sun.com/products/plugin/1.2/jinstall-12-win32.cab#Version=l,2,0,0">
<PARAM NAME="code" VALUE="doc.tutorial.TutorialApplet.class">
<PARAM NAME="codebase" VALUE="../..">
<PARAM NAME="archive" VALOE="
ptolemy/ptsupport.j ar,
ptolemy/domains/de/de.jar">
<PARAM NAME="type" VALUE="application/x-java-applet;version=l.2">
<COMMENT>
<EMBED type="application/x-java-applet;version=1.2"
width="700"
height="300"
code="doc/tutorial/TutorialApplet.class"
codebase="../.."
archives"
ptolemy/ptsupport.j ar,
ptolemy/domains/de/de.j ar"
pluginspage="http://j ava.sun.com/products/plugin/l.2/plugin-install.html">
</COMMENT>
<NOEMBED>
No JDK 1.2 support for applet!
</NOEMBED>
</EMBED>
</OBJECT>
FIGURE 4.14. An HTML segment that modifies that of figure 4.2 to use jar files. This text can be found in
$PTII/doc/tutorial/tutorialJar.htm.
79
4.2.9 Hints for Developing Applets
Unfortunately, Java plug-in technology is fairly immature. In the current version, we have encoun-
tered a number of problems, and have found workarounds that we can share.
Processes Linger After the Browser Quits. Sometimes, after running an applet under a browser, a pro-
cess remains live even after the browser has been exited. This appears to be a bug with the just-in-time
(JIT) compiler included in the plug-in. You can configure the plug-in to disable the JIT. On a Windows
installation, the plug-in comes with a control panel (look under Programs in the Start menu). Select the
"advanced" tab, and disable the JIT. This will noticeably slow down your applets, but you will not
have to kill lingering processes.
Difficulty Reloading Applets. While developing applets, it is helpful to be able to reload the applet
after modifying the Java code. In Netscape, you must use Shift-reload, and in IE, Control-reload.
Unfortunately, this does not always cause the applet to be reloaded. A workaround is described on the
Sun website, http://iava.sun.eom/products/plugin/l.2/docs/controlpanel.html. which says:
"Cache JARs in memory. If this option is checked, the applet classes already
loaded will be cached and reused when applet is reloaded. This allows much more
efficient memory use. You should leave this option unchecked if you are debugging
an applet or are always interested in loading the latest applet classes. The default
setting is to cache JARs in memory. Warning: turning off this option makes it more
likely that you will get OutOfMemoryErrors, as this reduces sharing of memory. "
This option is under the "basic" tab of the same control panel described in the previous hint.
80
Appendix D: Inspection Paradox Example
In this appendix, we show the code for a more detailed and more interesting applet in the DE
domain. This applet illustrates the "inspection paradox," which briefly stated, says that the average
amount of time you wait for Poisson arrivals is twice what you might intuitively expect.
D.2 Observations
There are a number of interesting features of this applet. It includes an instance of Query, at the
upper left, with four entries. The first two are entry boxes similar to that in figure 4.10. The third is a
different style of entry called a check box. The fourth is a display-only entry that shows final results
from execution of the model.
The mechanism used to obtain and display final results is interesting. First, notice below that the
applet uses an instance of the Average actor to calculate the average waiting time of a passenger. The
output of this actor is fed an to instance of Recorder, an actor that simply stores the data it is given for
later querying. The final value of the average waiting time is obtained in the executionFinished()
method, which is invoked when the execution of the model is completed.
A second interesting feature of this applet is that in the _go() method, depending on the state of the
check box query, the model may be modified structurally before it is executed. Subsequent chapters
81
will explain precisely the meaning of the methods that are used to accomplish this modification.
import java.awt.event.*;
import Java.util.Enumeration;
import javax.swing.BoxLayout;
import ptolemy.kernel.*;
import ptolemy.kernel.util.*;
import ptolemy.data.*;
import ptolemy.actor.Manager;
import ptolemy.actor.lib.*;
import ptolemy.actor.gui.*;
import ptolemy.gui.Query;
import ptolemy.gui.QueryListener;
import ptolemy.domains.de.kernel.*;
import ptolemy.domains.de.1ib.* ;
I The above applet uses the Ptolemy II Discrete Event (DE) domain to illustrate the inspection paradox. The
ED _
ÜPFf iDoarant Done
FIGURE 4.15. View of the inspection paradox applet described in the appendix.
82
import ptolemy.domains.de.gui.DEApplet;
import ptolemy.plot.*;
//////////////////////////////////////////////////////////////////////////
//// InspectionApplet
/**
An applet that uses Ptolemy II DE domain to illustrate the inspection
paradox. The inspection paradox concerns Poisson arrivals of events.
The metaphor used in this applet is that of busses and passengers.
Passengers arrive according to a Poisson process.
Busses arrive either at regular intervals or according to a Poisson
process. The user selects from these two options by clicking the
appropriate on-screen control. The user can also control the mean
interarrival time of both busses and passengers.
<p>
The inspection paradox concerns the average time that a passenger
waits for a bus (more precisely, the expected value). If the busses
arrive at regular intervals with interarrival time equal to <i>T</i>,
then the expected waiting time is <i>T</i>/2, which is perfectly
intuitive. Counterintuitively, however, if the busses arrive
according to a Poisson process with mean interarrival time equal to
<i>T</i>, the expected waiting time is <i>T</i>, not <i>T</i>/2.
These expected waiting times are approximated in this applet by
the average waiting time. The applet also shows that actual
arrival times for both passengers and busses, and the waiting
time of each passenger.
<p>
The intuition that resolves the paradox is as follows.
If the busses are arriving according to a Poisson process,
then some intervals between busses are larger than other intervals.
A particular passenger is more likely to arrive at the bus stop
during one of these larger intervals than during one of the smaller
intervals. Thus, the expected waiting time is larger if the bus
arrival times are irregular.
<P>
This paradox is called the <i>inspection paradox</i> because the
passengers are viewed as inspecting the Poisson process of
bus arrivals.
///////////////////////////////////////////////////////////////////
//// public methods ////
83
catch (IllegalActionException ex) {
throw new InternalErrorExceptioniex.toStringf))
*/
public void executionFinished(Manager manager) {
super.executionFinished(manager);
_query.setDisplay("average", _recorder.getLatest(0).toStringO);
}
/** Initialize the applet.
*/
public void init 0 {
super.init();
try (
getContent Pane().setLayout(
new BoxLayout(getContentPane 0, BoxLayout.Y_AXIS));
if (_regular)'' ( '
// Create regular bus source.
_regularBus = new Clock (_topleve 1, "regularBus");
// Create Poisson bus source, but then remove from container,
// since it won't be used unless the.user toggles the button.
_poissonBus = new Poisson(_toplevel,"poissonBus");
_poissonBus.setContainer(null);
} else {
// Create Poisson bus source.
_poissonBus = new Poisson(_toplevel, "poissonBus");
// Create regular bus source, but then remove from container,
// since it won't be used unless the user toggles the button.
_regularBus = new Clock(_toplevel,"regularBus");
_regularBus.setContainer(null);
}
// Set default parameters for both sources.
_regularBus.values.setExpressionf"[2]");
_regularBus.period.setToken(new DoubleToken(1.0));
_regularBus.offsets.setExpression("[0.0]");
_poissonBus.values.setExpressionf"[2]") ;
_poissonBus.meanTime.setToken(new DoubleToken(l.O));
// Waiting time
_wait = new WaitingTime(_toplevel, "waitingTime");
// Average actor
Average average = new Average(_toplevel, "average");
84
// Create and configure plotter
_eventplot = new TimedPlotter(_toplevel, "plot");
_eventplot.place(getContentPane 0);
_eventplot.plot.setBackground(getBackground());
_eventplot.plot.setGrid(false);
_eventplot.plot.setTitle("Events");
_eventplot.plot.addLegend(0, "Bus");
_eventplot.plot.addLegend (1, "Passengers");
_eventplot.plot.addLegend(2, "Wait Time");
_eventplot.plot.setXLabel("Time");
_eventplot.plot.setXRange(0.0, _getStopTime());
_eventplot.plot.setYRange(0.0, 4.0);
_eventplot.plot.setConnected(false);
_eventplot.plot.setImpulses(true);
_eventplot.plot.setMarksStyle("dots");
_eventplot.fillOnWrapup.setToken(new BooleanToken(false));
///////////////////////////////////////////////////////////////////
//// protected methods ////
/** Execute the model. This overrides the base class to read the
* values in the query box first and set parameters.
* «exception IllegalActionException If topology changes on the
* model or parameter changes on the actors throw it.
*/
protected void _go() throws IllegalActionException {
// If an exception occurred during initialization, then we don't
// want to run here. The model is probably not complete,
if (!_initCompleted) return;
85
// Depending on the state of the radio button, we may want
// either regularly spaced bus arrivals, or Poisson arrivals.
// Here, we alter the model topology to implement one or the other,
if (_regular) {
try {
_poissonBus.setContainer(null);
_regularBus.setContainer (_toplevel) ,-
) catch (NameDuplicationException ex) {
throw new InternalErrorException(ex.toString()) ,•
}
_regularBus.period.setToken
(new DoubleToken(_query.doubleValue Cbusmean"))) ,-
_regularBus.output.link(_busRelation) ;
} else {
try.f
_regularBus.setContainer(null) ,-
jpoissonBus.setContainer(_toplevel) ;
} catch (NameDuplicationException ex) {
throw new InternalErrorException(ex.toString()) ,-
}
_poissonBus.meanTime.setToken
(new DoubleToken(_query.doubleValue Cbusmean"))) ,•
_passengerl.meanTime.setToken
(new DoubleToken(_query.doubleValue("passmean")));
_poissonBus.output.link(_busRelation);
}
// The the X range of the plotter to show the full run.
// The method being called is a protected member of DEApplet.
_eventplot.plot.setXRange(0.0, _getStopTime());
///////////////////////////////////////////////////////////////////
//// private variables ////
86
private Recorder recorder;
87/88
Actor Libraries
Authors: Edward A. Lee
Paul Whitaker
YuhongXiong
5.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 poly-
morphism, 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 poly-
morphic, 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.
89
in ptolemy.actor.lib. Domain-specific actors are in ptolemy.domainsjc.Iib, where is the domain
name.
5.2.1 Actor.Lib
Figure 5.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 5.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 5.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. The set of domain and data polymorphic actors have been extended with specific actors for
logic and conversions. The actors can be foud in ptolemy.actor.lib.logic and ptolemy.actor.lib.conver-
iTyped Atomic Actor;
actor.lib -oi_ <y
«Interface»
SequenceActor
...Sinks...
♦output: TypedlOPort ♦input: TypedlOPort
♦trigger: TypedlOPort(Token,multi) ♦output: TypedlOPort
Expression
♦expression: Parameter
... Sources... ♦output: TypedlOPort
Avenge ■_firing : int
- time: double
♦reset: TypedlOPort
♦gain : Parameter
actof.ib.logic
90
sions.
None of the classes in figure 5.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. Imple-
menting 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 pro-
vide 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.
5.2.2 Actor.GUI
Figure 5.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 con-
trast, 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 imple-
ment 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.
91
pie, 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 5.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 5.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. Sub-
sequently, 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 ques-
tion.
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 rea-
sonable meaning to subtraction. Thus, if AddSubtract is used on strings, then the minus port must not
ever receive tokens. It may be simply left disconnected, in which case minus.getWidth() returns zero.
If the subtractQ method of a StringToken is called, then a runtime exception will be thrown.
«Interface» j
;TypedAtomicActori StquenceActor j
actor.gui
-{> +place(p: Panel)
7\ Z' A"
| «Interface»
[ TlmedActor Sink
i+input: typedlÖPort(multiport);
Plotter HistogramPlotter
+ftllOnWrapup: Parametert,BooleanToken)
+fillOnWrapup: Parameter(BooteanToken) Display
+plot: Plot
♦histogram : Histogram
+startingDataset: IntToken
♦input: TypedlOPort{ DoubleToken) ♦rowsDisplayed: IntToken
♦textArea: JTextArea
SequencePlotter
XYPIotter
92
5.4 Domain Polymorphism
Most actors access their ports as shown in figure 5.4, using the hasToken(), get(), and send() meth-
ods. 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 sus-
pended until another thread sends data to the same port (using, for example, the send() method on one
of its ports). Correspondingly, a call to send() causes the calling thread to suspend until some other
thread calls a corresponding get(). In the PN domain, by contrast, send() returns immediately (if there
is room in the channel buffers), and only get() causes the calling thread to suspend.
+Token()
+add(rightArg : Token): Token
+addReverse(leftArg : Token): Token
+convert(token : Token): Token
+divide(divisor: Token): Token
+divideReverse(dividend : Token): Token
+isEquaiTo(token: Token): BooleanToken
+modulo(rightArg : Token): Token
+moduloReverse<leftArg : Token): Token
+multiply(leftFactor: Token): Token
+multiplyReverse(rightFactor: Token): Token
+one(): Token
+stringValue(): String
+subtract(rightArg: Token): Token
+subtractReverse(lettArg : Token): Token
+toString(): String
+zero{): Token
FIGURE 5.3. The Token class defines a polymorphic interface that includes basic arithmetic operations.
FIGURE 5.4. The fire() method of the AddSubtract shows the use of polymorphic add() and subtract() meth-
ods in the Token class (see figure 5.3).
93
Each domain has slightly different behavior associated with hasToken(), get(), send() and other
methods of ports. The actor, however, does not really care. The fire() method shown in figure 5.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 pro-
cess-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 c&W&A 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.
5.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(), fol-
lowed 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 Concurrent Computation chapter.
94
f(x)=x. (1)
A solution x to this equation is called a fixed point of the function/ 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 sig-
nals at the conclusion of an iteration, and/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.
95
It is also useful to know some general patterns of behavior.
• Unless otherwise stated, actors will read at most one input token from each input channel of each
input port, and will produce at most one output token. No output token is produced unless there are
input tokens.
• Unless otherwise stated, actors implement neither SequenceActor nor TimedActor.
)
if (input.hasToken(0)) {
Token in = input.get(0);
if (_latestSum == null) {
_latestSum = in;
} else {
_latestSum = _latestSum.add(in);
}
Token out = _latestSum.divide(new IntToken(_latestCount));
output.broadcast(out);
FIGURE 5:5. The fireO and postfire() methods of the Average actor show how state is updated only in post-
fireO.
96
5.5.1 Functional Actors
The functional actors in the ptolemy.actor.lib are shown in figure 5.1. They are summarized here.
AbsoluteValue
Ports: input (ScalarToken), output (ScalarToken).
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 (\ub(plus, minus)).
Add tokens on the plus input and subtract tokens on the minus input.
ArrayAppend
Ports: input (multiport, Array Token), output (ArrayToken).
Append arrays on the channels of the input port to produce a single output token.
dB
Ports: input (DoubleToken), output (DoubleToken).
Parameters: inputlsPower (BooleanToken), min (DoubleToken).
Produce a token that is the value in decibels (k*log10(z)) of the token received, where k is 10 if
inputlsPower is true, and 20 otherwise. The output is never less than min.
IIR
Ports: input (DoubleToken), output (DoubleToken).
Parameters: numerator (ArrayToken of Doubles), denominator (ArrayToken of Doubles).
Produce an output token with a value that is the input filtered by an IIR filter using a direct form II
implementation.
MathFunction
Ports: flrstOperand (DoubleToken), secondOperand (DoubleToken), output (DoubleToken).
Parameters: function (StringToken).
Produce an output token with a value that is a function of the input(s). The function is specified by
the function attribute, where valid functions are exp, log, modulo, sign, square, and sqrt. Only
remainder uses secondOperand.
Maximum
Ports: input (multiport, ScalarToken), maximumValue (multiport, ScalarToken), channelNumber
(multiport, IntToken).
Produce an output token on each firing on maximumValue with a value that is the maximum of the
values on the input channels. The index of this maximum is output on channelNumber.
Minimum
Ports: input (multiport, ScalarToken), minimumValue (multiport, ScalarToken), channelNumber
(multiport, IntToken).
Produce an output token on each firing on minimumValue with a value that is the minimum Of the
values on the input channels. The index of this minimum is output on channelNumber.
97
Multiplexor
Ports: input (multiport, polymorphic), select (IntToken), output (polymorphic).
Produce as output the token on the channel of input specified by the select input (one token is read
from each channel of input).
Multiply/Divide
Ports: multiply (multiport, polymorphic), divide (multiport, polymorphic), output (\ub(multiply,
divide)).
Multiply tokens on the multiply input and divide by tokens on the divide input.
Quantizer
Ports: input (DoubleToken), output (DoubleToken).
Parameters: levels (ArrayToken of Doubles).
Produce an output token with the value in levels that is closest to the input value.
RealTimeDelay
Ports: input (multiport, polymorphic), output (multiport, polymorphic).
Parameters: delay (LongToken).
Produce as output the tokens received on input after a delay specified by delay.
RecordAssembler
Ports: output (RecordToken).
Produce an output token that results from combining a token from each of the input ports (which
must be added by the user).
RecordDisassembler
Ports: input (RecordToken).
Produce output tokens on the output ports (which must be added by the user) that result from sepa-
rating the record on the input port.
Remainder
tainder
Ports: input (DoubleToken), output (DoubleToken).
Parameters: quotient (DoubleToken).
Produce an output token with the value that is the remainder after dividing the token on the input
port by the quotient.
lie
Ports: input (polymorphic), output (lub(input, gain)).
Parameters: factor (polymorphic).
Produce an output that is the product of the input and the factor.
Select
•ct
Ports:
Pc input (multiport, polymorphic), control (IntToken), output (polymorphic).
Produce as output the token on the channel of input specified by the control input (only this token
Pr
is read").
read)
98
Switch
Ports: input (polymorphic), control (IntToken), output (multiport, polymorphic).
Produce the token on the input port on the channel of output specified by the control input.
Synchronizer
Ports: input (multiport, polymorphic), output (multiport, polymorphic).
On each firing, if one token exists on each channel of input, read these, and produce them on the
corresponding channels of output.
TrigFunction
Ports: input (DoubleToken), output (DoubleToken).
Parameters:/w«c//o«(StringToken).
Produce an output token with a value that is a function of the input. The function is specified by
the function attribute, where valid functions are acos, asin, atan, cos, sin, and tan.
99
Time (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.
«Interface» f «interface» j
TimodActor • Sequence-Acfor j
:+output: TypedlOPort
'♦trigger: TypedlOPort(Token,multi):
-or
/A
I
L_
Pulse
Clock Ramp
VariableClock
100
DiscreteRandomSource
Ports: trigger (input multiport, Token), output (DoubleToken).
Parameters: pmf{Array Token of Doubles), values (Array Token), seed (LongToken).
Produce tokens with the given probability mass function.
Gaussian
Ports: trigger (input multiport, Token), output (DoubleToken).
Parameters: mean (DoubleToken), standardDeviation (DoubleToken), seed (LongToken).
Produce a random sequence where each output is the outcome of a Gaussian random variable.
Interpolator (implements SequenceSource)
Ports: trigger (input multiport, Token), output (lub(elements of values)).
Parameters: indexes (ArrayToken of Ints), order (IntToken), period (IntToken), values (ArrayTo-
ken of Doubles), firingCountLimit (IntToken).
Produce an interpolation based on the parameters.
PoissonClock
Ports: trigger (input multiport, Token), output (lub(elements of values)).
Parameters: meanTime (DoubleToken), values (ArrayToken), 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 expo-
nential random variables with mean meanTime.
Pulse (implements SequenceSource)
Ports: trigger (input multiport, Token), output (lub(elements of values)).
Parameters: indexes (IntMatrixToken), values (MaXrixToken), firingCountLimit (IntToken), repeat
(BooleanToken).
Produce a sequence of values at specified iterations. The sequence repeats itself when the repeat
parameter is set to true.
Ramp (implements SequenceSource)
Ports: trigger (input multiport, Token), output (lub(/n/f, 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.
Reader
Ports: trigger (input multiport, Token), output (DoubleToken).
Parameters: refresh (BooleanToken), sourceURL (StringToken).
Read tokens from a URL specified by sourceURL, and output them.
SketchedSource
Ports: trigger (input multiport, Token), output (DoubleToken).
Parameters: dataset (IntToken), length (IntToken), period (IntToken).
Output a signal that has been sketched by the user on the screen.
101
Uniform
Ports: trigger (input multiport, Token), output (DoubleToken).
Parameters: lowerBound (DoubleToken), upperBound (DoubleToken), seed (LongToken).
Produce a random sequence with a uniform distribution.
VariableClock (implements TimedActor)
Ports: trigger (input multiport, Token), output (lub(elements of values)), periodControl(input,
DoubleToken).
Parameters: offsets (DoubleMatrixToken), period (DoubleToken), values (MatrixToken), stopTime
(DoubleToken).
An extension of Clock with an input to dynamically control the period.
Sink
;+input: TypedAtomicActor(multi);
Recorder
Writer
-record: LinkedList
timeRecord: LinkedList _writer: java.io.Writer
+getCount(): int +setWriter(writer: java.io.Writer)
+getHistory(channel: int): List (of StringToken)
+gelLatest(channel: int): StringToken
+getTimeHistory(): List (of Double)
FileWriter
+filename: Parameter(String)
102
Discard
Ports: input (multiport, Token).
Consume and discard input tokens.
Display
Ports: input (multiport, Token).
Consume and display input tokens in a text area on the screen.
File Writer
Ports: input (multiport, Token).
Parameters:y?/e«a/Ke (StringToken).
Write the string representation of input tokens to the specified file or to standard out.
HistogramPlotter
Ports: input (multiport, DoubleToken).
Parameters: binOffset (DoubleToken), binWidth (DoubleToken), fillOnWrapup (BooleanToken),
legend (StringToken).
Display a histogram of the data on each input channel.
«Interface»
jTypedAtomlcActor: SeauenctAcfw
..<H
-O +setPanel(p: Panel)
«Interface»
TimwMctor
: A .A A A X""7TT
Sink
j-t-input: TypedlOPortQnultiport);
"7T x-
Hotter HistogramPlotter
TlmedPlotter SequencePlotter
►input: TypedlOPort(MatrixToken)
103
MatrixViewer
Ports: input (MatrixToken).
Parameters: height (IntToken), width (IntToken).
Display the entries of a Matrix in a table.
Recorder
Ports: input (multiport, Token).
Parameters: capacity (IntToken).
Record the inputs for later querying.
SequencePlotter
Ports: input (multiport, DoubleToken).
Parameters: fillOnWrapup (BooleanToken), legend (StringToken), startingDataset (IntToken),
xlnit (DoubleToken), xUnit (DoubleToken).
Plot sequences.
SequenceScope
Ports: input (multiport, DoubleToken).
Parameters: fillOnWrapup (BooleanToken), legend (StringToken), persistence (IntToken), start-
ingDataset (IntToken), width (IntToken), xlnit (DoubleToken), xUnit (DoubleToken).
Plot sequences that are potentially infinitely long.
Test
Ports: input (multiport, DoubleToken).
Parameters: correctValues (ArrayToken), tolerance (DoubleToken).
Check the input streams against a parameter value.
TimedPlotter
Ports: input (multiport, DoubleToken).
Parameters -.fillOnWrapup (BooleanToken), legend (StringToken), startingDataset (IntToken).
Plot inputs as a function of time.
TimedScope
Ports: input (multiport, DoubleToken).
Parameters: fillOnWrapup (BooleanToken), legend (StringToken), persistence (IntToken), start-
ingDataset (IntToken), width (IntToken).
Plot inputs as a function of time in oscilloscope style.
Writer
Ports: input (multiport, Token).
Write input data to the specified writer.
XYPlotter
Ports: /'«pw/X(multiport, DoubleToken), inputY(multiport, DoubleToken).
Parameters: fillOnWrapup (BooleanToken), legend (StringToken), startingDataset (IntToken).
Display a plot of the data on each inputXchannel vs. the data on the corresponding inputY channel.
104
XYScope
Ports: /«pu^X (multiport, DoubleToken), zrcpwf 7 (multiport, DoubleToken).
Parameters: fillOnWrapup (BooleanToken), legend (StringToken), persistence (IntToken), start-
ingDataset (IntToken).
Display a plot of the data on each inputX channel vs. the data on the corresponding inputY channel
with finite persistence.
105
Sequencer (implements SequenceActor)
Ports: input (polymorphic), sequenceNumber (IntToken), output (type of input).
Parameters: startingSequenceNumber (IntToken).
Put tokens in order according to their numbers in a sequence.
106
ComplexToCartesian
Ports: input (ComplexToken), real (DoubleToken), imag (DoubleToken).
Convert a token representing a complex number into its Cartesian components (which are output
on real and imag).
ComplexToPolar
Ports: input (ComplexToken), angle (DoubleToken), magnitude (DoubleToken).
Convert a token representing a complex number into two tokens representing its polar form (which
are output on angle and magnitude).
DoubleToFix
Ports: input (DoubleToken), output (FixToken).
Parameters: precision (MatrixToken of Ints), quantization (StringToken).
Convert a double into a fix point number with a specific precision, using a specific quantizer.
FixToDouble
Ports: input (FixToken), output (DoubleToken).
Parameters: precision (MatrixToken of Ints), quantization (StringToken).
Convert a fix point into a double, by first setting the precision of the fix point to the supplied preci-
sion, using a specific quantizer.
FixToFix
Ports: input (FixToken), output (FixToken).
Parameters: overflow (StringToken), precision (MatrixToken of Ints), quantization (StringToken).
Convert a fix point into another fix point with possibly a different precision, using a specific quan-
tizer and overflow strategy.
TypedAtomlcActor
Transformer
FixToDouble RectangularToPolar
ComplexToReal
♦precision: Parameter(StringToken) ♦xlnput: TypedlOPort(DoubleToken)
♦quantizer: Parameter(lntToken) ■ylnput: TypedlOPort(DoubleToken) input: TypedlOPort(ComplexToken)
♦input: TypedlOPort(FixToken) ♦magnitudeOutput: TypedlOPort(DoubleToken) ♦realOutput: TypedlOPort(DoubleToken)
output: TypedlOPort(DoubleToken) ♦angleOutput: TypedlOPort(DoubleToken) ♦imagOutput: TypedlOPort(DoubleToken)
107
PolarToCartesian
Ports: angle (DoubleToken), magnitude (DoubleToken), x (DoubleToken), y (DoubleToken).
Converts two tokens representing a polar coordinate (a token on angle and a token on magnitude)
to two tokens representing their Cartesian form (which are output on x and y).
PolarToCartesian
Ports: angle (DoubleToken), magnitude (DoubleToken), output (ComplexToken).
Converts two tokens representing a polar coordinate (a token on angle and a token on magnitude)
to a token representing their complex form.
Round
Ports: /«/?«/(DoubleToken), output (IntToken).
Parameters:/w«crioM (StringToken).
Produce an output token with a value that is a rounded version of the input. The rounding method
is specified by the function attribute, where valid functions are ceil, floor, round, and truncate.
TypedAtomlcActor
Transformer
FIxToDouble RectangularToPolar
ComplexToReal
♦precision: Parameter(StringToken) ♦xlnput: TypedlOPort(DoubleToken)
♦quantizer: Parameter(lntToken) ♦ylnput: TypedlOPort(DoubleToken) ♦input: TypedlOPort(ComplexToken)
input: TypedlOPort(FixToken) ♦magnitudeOutput: TypedlOPort(DoubleToken) ♦realOutput: TypedlOPort(DoubleToken)
♦output: TypedlOPort(DoubleToken) ♦angteOutput: TypedlOPort(DoubleToken) ♦imagOutput: TypedlOPort(DoubleToken)
108
Designing Actors
Authors:
Christopher Hylands
Edward A. Lee
Jie Liu
Xiaojun Liu
Steve Neuendorffer
YuhongXiong
6.1 Overview
Ptolemy is about component-based design. The domains define the semantics of the interaction
between components. This chapter explains the common, domain-independent principles in the design
of components that are actors. Actors are components with input and output that at least conceptually
operate concurrently with other actors.
As explained in the previous chapter, some actors are designed to be domain polymorphic, mean-
ing that they can operate in various domains. Others are domain specific. Refer to the domain chapters
in part 3 for domain-specific information relevant to the design of actors. This chapter explains how to
design actors so that they are maximally domain polymorphic. As also explained in the previous chap-
ter, many actors are also data polymorphic. This means that they can operate on a wide variety of token
types. Domain and data polymorphism help to minimize the amount of duplicated code when writing
actors.
Code duplication can be also be avoided using object-oriented inheritance. Inheritance can also be
used to enforce consistency across a set of classes. Figure 5.1, shows a UML static-structure diagram
for an actor library. Three base classes, Source, Sink, and Transformer, exist to ensure consistent nam-
ing of ports and to avoid duplicating code associated with those ports. Since most actors in the library
extend these base classes, users of the library can guess that an input port is named "input" and an out-
put port is named "output," and they will probably be right. Using base classes avoids input ports
named "in" or "inputSignal" or something else. This sort of consistency helps to promote re-use of
109
actors because it makes them easier to use. Thus, we recommend using a reasonably deep class hierar-
chy to promote consistency.
6.2.1 Ports
By convention, ports are public members of actors. They represent a set of input and output chan-
nels through which tokens may pass to other ports. Figure 6.1 shows a single port portName that is an
instance of TypedlOPort, declared in the line
Most ports in actors are instances of TypedlOPort, unless they require domain-specific services, in
which case they may be instances of a domain-specific subclass such as DEIOPort. The port is actually
created in the constructor by the line
The first argument to the constructor is the container of the port, this actor. The second is the name of
the port, which can be any string, but by convention, is the same as the name of the public member.
The third argument specifies whether the port is an input (it is in this example), and the fourth argu-
ment specifies whether it is an output (it is not in this example). There is no difficulty with having a
port that is both an input and an output, but it is rarely useful to have one that is neither.
Multiports and Single Ports. A port can be a single port or a multiport. By default, it is a single port. It
can be declared to be a multiport with a statement like
portName.setMultiport(true);
All ports have a width, which corresponds to the number of channels the port represents. If a port is not
connected, the width is zero. If a port is a single port, the width can be zero or one. If a port is a multi-
port, the width can be larger than one.
Reading and Writing. Data (encapsulated in a token) can be sent to a particular channel of an output
multiport with the syntax
portName.send(channelNumber, token);
where channelNumber is the number of the channel (beginning with 0 for the first channel). The width
of the port, the number of channels, can be obtained with the syntax
110
/** Javadoc comment for the class. */
public class ClassName extends BaseClass implements Markerinterface {
///////////////////////////////////////////////////////////////////
//// public methods ////
Ill
int width = portName. getWidthO ;
If the port is unconnected, then the token is not sent anywhere. The send() method does not complain.
Note that in general, if the channel number refers to a channel that does not exist, the send() method
does not complain.
A token can be sent to all output channels of a port (or none if there are none) with the syntax
portName.broadcast(token);
If the port is not a multiport then there is only one channel and it is more efficient to use the syntax
portName.send(0, token);
You can query an input port to see whether such a get() will succeed (whether a token is available or
can be made available) with the syntax
You can also query an output port to see whether a send() will succeed using
although with most current domains, the answer is always true. Note that the get(), hasRoom() and has-
Token() methods throw IllegalActionException if the channel is out of range, but send() just silently
returns.
Ptolemy II includes a sophisticated type system, described fully in the Type System chapter. This
type system supports specification of type constraints in the form of inequalities between types. These
inequalities can be easily understood as representing the possibility of lossless conversion. Type a is
less than type b if an instance of a can be losslessly converted to an instance of b. For example,
IntToken is less than DoubleToken, which is less than ComplexToken. However, LongToken is not
less than DoubleToken, and DoubleToken is not less than LongToken, so these two types are said to be
incomparable.
Suppose that you wish to ensure that the type of an output is greater than or equal to the type of a
parameter. You can do so by putting the following statement in the constructor:
portName.setTypeAtLeast(parameterName);
This is called a relative type constraint because it constrains the type of one object relative to the type
of another. Another form of relative type constraint forces two objects to have the same type, but with-
out specifying what that type should be:
112
portName.setTypeSameAs (.parameterName) ;
portName.setTypeEquals(BaseType.DOUBLE);
The above line declares that the port can only handle doubles. Another form of absolute type constraint
imposes an upper bound on the type,
portName.setTypeAtMost(BaseType.COMPLEX);
which declares that any type that can be losslessly converted to ComplexToken is acceptable.
If no type constraints are given for any ports of an actor, then by default, the output ports are con- .
strained to have at least the type(s) of the input ports. If any type constraints are given for any ports in
the actor, then this default is not applied for any of the other ports. Thus, if you specify any type con-
straints, you should specify all of them. For full details of the type system, see the Type System chap-
ter.
Examples. To be concrete, consider first the code segment shown in figure 6.2, from the Transformer
class in the ptolemy.actor.lib package. This actor is a base class for actors with one input and one out-
put. The code shows two ports, one that is an input and one that is an output. By convention, the Java-
doc1 comments indicate type constraints on the ports, if any. If the ports are multiports, then the
Javadoc comment will indicate that. Otherwise, they are assumed to be single ports. Derived classes
may change this, making the ports into multiports, in which case they should document this fact in the
class comment. Derived classes may also set the type constraints on the ports.
An extension of Transformer is shown in figure 6.3, the Scale actor. This actor produces an output
token on each firing with a value that is equal to a scaled version of the input. The actor is polymorphic
in that it can support any token type that supports multiplication by the factor parameter. In the con-
structor, the output type is constrained to be at least as general as both the input and the factor parame-
ter.
Notice in figure 6.3 how the fire() method uses hasToken() to ensure that no output is produced if
there is no input. Furthermore, only one token is consumed from each input channel, even if there is
more than one token available. This is generally the behavior of domain-polymorphic actors. Notice
1. Javadoc is a program that generates HTML documentation from Java files based on comments enclosed in "/**
... */".
113
also how it uses the multiply() method of the Token class. This method is polymorphic. Thus, this scale
actor can operate on any token type that supports multiplication, including all the numeric types and
matrices.
6.2.2 Parameters
Like ports, by convention, parameters are public members of actors. Figure 6.3 shows a parameter
factor that is an instance of Parameter, declared in the line
The third argument to the constructor, which is optional, is a default value for the parameter. In this
example, the factor parameter defaults to the integer one.
As with ports, you can specify type constraints on parameters. The most common type constraint is
to fix the type, using
parameterName.setTypeEquals(BaseType.DOUBLE);
/** The input port. This base class imposes no type constraints except
* that the type of the input cannot be greater than the type of the
* output.
*/
public TypedlOPort input;
/** The output port. By default, the type of this output is constrained
* to be at least that of the input.
*/
public TypedlOPort output;
FIGURE 6.2. Code segment showing the port definitions in the Transformer class.
114
In fact, exactly the same relative or absolute type constraints that one can specify for ports can be spec-
ified for parameters as well. But in addition, arbitrary constraints on parameter values are possible, not
just type constraints. An actor is notified when a parameter value changes by having its
attributeChanged() method called. Consider the example shown in figure 6.4, taken from the Poisson
actor. This actor generates timed events according to a Poisson process. One of its parameters is mean-
Time, which specifies the mean time between events. This must be a double, as asserted in the con-
structor.
The attributeChangedO method is passed the parameter that changed. If this is meanTime, then this
method checks to make sure that the specified value is positive, and if not, it throws an exception.
A change in a parameter value sometimes has broader repercussions than just the local actor. It
may, for example, impact the schedule of execution of actors. An actor can call the invalidateSched-
ule() method of the director, which informs the director that any statically computed schedule (if there
is one) is no longer valid. This would be used, for example, if the parameter affects the number of
tokens produced or consumed when an actor fires.
By default, actors do not allow type changes on parameters. Once a parameter is given a value,
then the value can change but not the type. Thus, the statement
meanTime.setTypeEquals(BaseType.DOUBLE);
///////////////////////////////////////////////////////////////////
//// public methods ////
FIGURE 6.3. Code segment from the Scale actor, showing the handling of ports and parameters.
115
in figure 6.4 is actually redundant, since the type was fixed in the previous line,
However, some actors may wish to allow type changes in their parameters. Consider again the Scale
actor, which is data polymorphic. The factor parameter can be of any type that supports multiplication,
so type changes should be allowed.
To allow type changes in an actor, you must override the attributeTypeChanged() method. This
method is defined in the NamedObj base class to throw an exception. The method is called whenever
the type of a parameter is changed. Consider figure 6.5, taken from the Scale actor. This code overrides
the base class to not throw an exception. This means that type changes are allowed. However, recall
from figure 6.3 that a type constraint was specified that relates the output type to factor. If the type
changes, then the resolved type of the input may no longer be valid. The code in figure 6.5 notifies the
director of this fact by calling its invalidateResolvedTypes() method.
6.2.3 Constructors
We have seen already that the major task of the constructor is to create and configure ports and
parameters. In addition, you may have noticed that it calls
}
/** If the argument is the meanTime parameter, check that it is
* positive.
* «exception IllegalActionException If the meanTime value is
* not positive.
*/
public void attributeChanged(Attribute attribute) throws IllegalActionException {
if (attribute == meanTime) {
double mean = ((DoubleToken)meanTime.getTokenO ) .doubleValue 0 ;
if (mean <= 0.0) {
throw new IllegalActionException(this,
"meanTime is required to be positive. meanTime given: " + mean);
}
) else if (attribute == values) {
ArrayToken val = (ArrayToken) (values.getTokenO ) ;
_length = val.length();
} else (
super.attributeChanged(attribute);
}
}
FIGURE 6.4. Code segment from the Poisson actor, showing the attributeChanged() method.
116
super(container, name);
and that it declares that it throws NameDuplicationException and IllegalActionException. The latter is
the most widely used exception, and many methods in actors declare that they can throw it. The former
is thrown if the specified container already contains an actor with the specified name. For more details
about exceptions, see the Kernel chapter.
6.2.4 Cloning
All actors are cloneable. A clone of an actor needs to be a new instance of the same class, with the
same parameter values, but without any connections to other actors.
Consider the clone() method in figure 6.6, taken from the Scale actor. This method begins with
The convention in Ptolemy II is that each clone method begins the same way, so that cloning works its
way up the inheritance tree until it ultimately uses the clone() method of the Java Object class. That
method performs what is called a "shallow copy," which is not sufficient for our purposes. In particu-
lar, members of the class that are references to other objects, including public members such as ports
and parameters, are copied by copying the references. The NamedObj and TypedAtomicActor base
classes (see the "Abstract Syntax" chapter) for most actors implement a "deep copy" so that all the
contained objects are cloned, and public members reference the proper cloned objects .
Although the base classes neatly handle most aspects of the clone operation, there are subtleties
involved with cloning type constraints. Absolute type constraints on ports and parameters are carried
automatically into the clone, so clone() methods should never call setTypeEquals(). However, relative
type constraints are not cloned automatically because of the difficulty of ensuring that the other object
being referred to in a relative constraint is the intended one. Thus, in figure 6.6, the clone() method
repeats the relative type constraints that were specified in the constructor:
public class Scale extends Transformer {
/** Notify the director when a type change in the parameter occurs.
* This will cause type resolution to be redone at the next opportunity.
* It is assumed that type changes in the parameter are implemented
* by the director's change request mechanism, so they are implemented
* when it is safe to redo type resolution.
* If there is no director, then do nothing.
*/
public void attributeTypeChanged(Attribute attribute) {
Director dir = getDirector();
if (dir != null) {
dir.invalidateResolvedTypes();
}
}
FIGURE 6.5. Code segment from the Scale actor, showing the attributeChangedO method.
1. Be aware that the implementation of the deep copy relies on a strict naming convention. Public members that
reference ports and parameters must have the same name as the object that they are referencing in order to be
properly cloned.
117
newObject.output.setTypeAtLeast(newObject.input);
newObject.output.setTypeAtLeast(newObject.factor);
Note that at no time during cloning is any constructor invoked, so it is necessary to repeat in the
clone() method any initialization in the constructor. For example, the clone() method in the Expression
actor sets the values of a few private Variables:
newObject._iterationCount = 1;
newObject._time = (Variable)newObject.getAttribute("time");
newObject._iteration =
(Variable)newObject.getAttribute("iteration");
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll
1111 ports and parameters ////
/** The factor. The default value of this parameter is the integer 1. */
public Parameter factor;
///////////////////////////////////////////////////////////////////
//// public methods ////
/** Clone the actor into the specified workspace. This calls the
* base class and then sets the type constraints.
* ©param workspace The workspace for the new object.
* «return A new actor.
* »exception CloneNotSupportedException If a derived class has
* has an attribute that cannot be cloned.
*/
public Object clone(Workspace workspace) throws CloneNotSupportedException
Scale newObject = (Scale)super.clone(workspace) ,•
newObject.output.setTypeAtLeast(newObject.input);
newObj ect. output. setTypeAtLeast (newObj ect. factor) ;
return newObject;
FIGURE 6.6. Code segment from the Scale actor, showing the clone() method.
118
resolution happens and all the type constraints are resolved. The initialize() method is invoked next,
and is typically used to initialize state variables in the actor, which generally depends on type resolu-
tion.
After the initialize() method, the actor experiences some number of iterations, where an iteration is
defined to be exactly one invocation of prefire(), some number of invocations of fire(), and at most one
invocation of postfire().
6.3.1 Initialization
The initialize() method of the Average actor is shown in figure 6.7. This data- and domain-poly-
morphic actor computes the average of tokens that have arrived. To do so, it keeps a running sum in a
private variable _sum, and a running count of the number of tokens it has seen in a private variable
count. Both of these variables are initialized in the initialize() method. Notice that the actor also calls
super.initialize(), allowing the base class to perform any initialization it expects to perform. This is
essential because one of the base classes initializes the ports. An actor will almost certainly fail to run
properly if super.initialize() is not called.
Note that the initialization of the Average actor does not affect, or depend on, type resolution. This
means that the code to initialize this actor can be placed either in the preinitialize() method, or in the
initialize() method. However, in some cases an actor may require part of its initialization to happen
before type resolution, in the preinitialize() method, or part after type resolution, in the initialize()
method. For example, an actor may need to dynamically create type constraints before each execu-
tion1. Such an actor must create its type constraints in preinitialize(). On the other hand, an actor may
wish to produce an initial output token once at the beginning of an execution of a model. This produc-
tion can only happen during initialize(), because data transport through ports depends on type resolu-
tion.
6.3.2 Prefire
The prefire() method is the only method that is invoked exactly once per iteration2. It returns a
lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll
1111 private members ////
private Token _sum;
private int _count = 0;
FIGURE 6.7. Code segment from the Average actor, showing the initialize!) method.
1. The need for this is relatively rare, but important. Examples include higher-order functions, which are actors that
replace themselves with other subsystems, and certain actors whose ports are not created at the time they are
constructed, but rather are added later. In most cases, the type constraints of an actor do not change and are sim-
ply specified in the constructor.
119
boolean that indicates to the director whether the actor wishes for firing to proceed. The fire() method
of an actor should never be called until after its prefire method has returned true. The most common
use of this method is to test a condition to see whether the actor is ready to fire.
Consider for example an actor that reads from truelnput if a private boolean variable state is true,
and otherwise reads from falseInput. The prefire() method might look like this:
It is good practice to check the superclass in case it has some reason to decline to be fired. The above
example becomes:
The prefire() method can also be used to perform an operation that will happen exactly once per
iteration. Consider the prefire method of the Bernoulli actor in figure 6.8:
This method selects a new boolean value that will correspond to the token creating during each firing
ofthat iteration.
6.3.3 Fire
The fire() method is the main point of execution and is generally responsible for reading inputs and
2. Some domains invoke the fire() method only once per iteration, but others will invoke it multiple times (search-
ing for global convergence to a solution, for example).
120
producing outputs. It may also read the current parameter values, and the output may depend on them.
Things to remember when writing fire() methods are:
• To get data polymorphism, use the methods of the Token class for arithmetic whenever possible
(see the Data Package chapter). Consider for example the Average actor, shown in figure 6.10.
Notice the use of the add() and divide() methods of the Token class to achieve data polymorphism.
When data polymorphism is not practical or not desired, then it is usually easiest to use the set-
TypeEquals() to define the type of input ports. The type system will assure that you can safely cast
the tokens that you read to the type of the port. Consider again the Average actor shown in figure
6.10. This actor declares the type of its reset input port to be BaseType.BOOLEAN. In the fire()
method, the input token is read and cast to a BooleanToken. The type system ensures that no cast
error will occur. The same can be done with a parameter, as with the Bernoulli actor shown in fig-
ure 6.10.
• A domain-polymorphic actor cannot assume that there is data at all the input ports. Most domain-
polymorphic actors will read at most one input token from each port, and if there are sufficient
inputs, produce exactly one token on each output port.
Some domains invoke the fire() method multiple times, iterating towards a converged solution.
Thus, each invocation can be thought of as doing a tentative computation with tentative inputs and
producing tentative outputs. Thus, the fireQ method should not update persistent state. Instead, that
output.setTypeEquals(BaseType.BOOLEAN);
FIGURE 6.8. Code for the Bernoulli actor, which is not data polymorphic.
121
public class Average extends Transformer {
///////////////////////////////////////////////////////////////////
//// ports and parameters ////
///////////////////////////////////////////////////////////////////
//// public methods ////
FIGURE 6.9. Code segment from the Average actor, showing the action methods.
122
should be done in the postfire() method, as discussed in the next section.
6.3.4 Postfire
The postfire() method has two tasks:
• updating persistent state, and
• determining whether the execution of an actor is complete.
Consider the fire() and postfire() methods of the Average actor in figure 6.10. Notice that the persistent
state variables _sum and _count are not updated in fire(). Instead, they are shadowed by _latestSum
and latestCount, and updated in postfire().
The return value of postfire() is a boolean that indicates to the director whether execution of the
actor is complete. By convention, the director should avoid iterating further an actor that returns false.
Consider the two examples shown in figure 6.10. These are base classes for source actors (those with
no input ports).
SequenceSource is a base class for actors that output sequences. Its key feature is a parameter flr-
ingCountLimit, which specifies a limit on the number of iterations of the actor. When this limit is
reached, the postfire() method returns false. Thus, this parameter can be used to define sources of finite
sequences.
TimedSource is similar, except that instead of specifying a limit on the number of iterations, it
specifies a limit on the current model time. When that limit is reached, the postfire() method returns
false.
6.3.5 Wrapup
The wrapupO method is invoked exactly once at the end of an execution, unless an exception
occurs during execution. It is used typically for displaying final results.
6.4 Time
An actor whose behavior depends on current model time should implement the TimedActor inter-
face. This is a marker interface (with no methods). Implementing this interface alerts the director that
the actor depends on time. Domains that have no meaningful notion of time can reject such actors.
An actor can access current model time with the syntax
Notice that although the director has a public method setCurrentTime(), an actor should never use it.
Typically, only another enclosing director will call this method.
An actor can request an invocation at a future time using the fireAt() method of the director. This
method returns immediately (for a correctly implemented director). It takes two arguments, an actor
and a time. The director is responsible for iterating the specified actor at the specified time. This
method can be used to get a source actor started, and to keep it operating. In its initialize^) method, it
can call fireAt() with a zero time. Then in each invocation of postfire(), it calls fireAt() again. Notice
that the call should be in postfireQ not in fire() because a request for a future firing is persistent state.
123
6.5 Code Format
Ptolemy software follows fairly rigorous conventions for code formatting. Although many of these
conventions are arbitrary, the resulting consistency makes reading the code much easier, once you get
used to the conventions. We recommend that if you extend Ptolemy II in any way, that you follow
these conventions. To be included in future versions of Ptolemy II, the code must follow the conven-
tions.
A template that corresponds to these rules can be found in $(PTII)/doc/coding/templates. There are
also templates for other common files. In general, if you have questions that are not covered here, then
consult the template or highly rated code.
FIGURE 6.10. Code segments from the SequenceSource and TimedSource base classes.
124
Several useful tools are provided in the $PTII/util/testsuite directory to help enforce the
standards, pt j avastyle. el is a lisp module for emacs that has appropriate indenting rules, j in-
dent is a unix script that uses emacs and the above module to properly indent many files at once.
ptspell is a script that checks Java code for proper spelling. It properly handles namesWithEmbed-
dedCapitalization and has a list of author names, chkj ava is a unix script for checking various other
potentially bad things in Java code, such as debugging code, and FIXME's.
6.5.1 Indentation
Nested statements should be indented 4 characters, as in:
if (container != null) {
Manager manager = container.getManager();
if (manager != null) {
manager.requestChange(change);
}
}
Closing brackets should be on a line by themselves, aligned with the beginning of the line that contains
the open bracket. Tabs are 8 space characters, not a Tab character. The reason for this is that code
becomes unreadable when the Tab character is interpreted differently by different programs. Do not
override this in your text editor. Long lines should be broken up into many small lines. The easiest
places to break long lines are usually just before operators, with the operator appearing on the next
line. Long strings can be broken up using the + operator in Java, with the + starting the next line. Con-
tinuation lines are indented by 8 characters, as in the throws clause of the constructor in figure 6.1.
6.5.2 Spaces
Use a space after each comma:
Right: foo(a, b);
Wrong: foo(a,b);
Use spaces around operators such as plus, minus, multiply, divide or equals signs, and after semi-
colons:
Right: a = b + 1;
Wrong: a=b+l;
Right: for(i = 0; i < 10; i += 2)
Wrong: for(i=0 ;i<10;i+=2)
6.5.3 Comments
Comments should be complete sentences and complete thoughts, capitalized at the beginning and
with a period at the end. Spelling and grammar should be correct. Comments should include honest
information about the limitations of the object definition.
Comments for base class methods that are intended to be overridden should include information
about what the method generally does, along with a description of how the base class implements it.
Comments in derived classes for methods that override the base class should copy the general descrip-
tion from the base class, and then document the particular implementation. In general comments with
FIXME's and implementation details should be used liberally in the code, but never in the interface
125
description.
6.5.4 Names
In general, the names of classes, methods and members should consist of complete words sepa-
rated using internal capitalization . Class names, and only class names have their first letter capital-
ized, as in AtomicActor. Method and member names are not capitalized, except at internal word
boundaries, as in getContainer(). Protected or private members and methods are preceded by a leading
underscore "_" as in _protectedMethod().
Static final constants should be in uppercase, with words separated by underscores, as in
INFINITECAPACITY. A leading underscore should be used if the constant is protected or private.
Package names should be short and not capitalized, as in "de" for the discrete-event domain.
In Java, there is no limit to name sizes (as it should be). Do not hesitate to use long names.
6.5.5 Exceptions
A number of exceptions are provided in the ptolemy.kernel.util package. Use these exceptions
when possible because they provide convenient arguments of type Nameable that identify the source of
the exception by name in a consistent way.
A key decision you need to make is whether to use a compile-time exception or a run-time excep-
tion. A run-time exception is one that implements the RuntimeException interface. Run-time excep-
tions are more convenient in that they do not need to be explicitly declared by methods that throw
them. However, this can have the effect of masking problems in the code.
The convention we follow is that a run-time exception is acceptable only if the cause of the excep-
tion can be tested for prior to calling the method. This is called a testable precondition. For example, if
a particular method will fail if the argument is negative, and this fact is documented, then the method
can throw a run-time exception if the argument is negative. On the other hand, consider a method that
takes a string argument and evaluates it as an expression. The expression may be malformed, in which
case an exception will be thrown. Can this be a run-time exception? No, because to determine whether
the expression is malformed, you really need to invoke the evaluator. Making this a compile-time
exception forces the caller to explicitly deal with the exception, or to declare that it too throws the
same exception. In general, we prefer to use compile-time exceptions wherever possible.
When throwing an exception, the detail message should be a complete sentence that includes a
string that fully describes what caused the exception. For example
throw IllegalActionException(this,
"Cannot append an object of type: "
+ obj .getClass () .getNameO +
+ "because it does not implement Cloneable.");
Note that the exception not only gives a way to identify the objects that caused the exception, but also
why the exception occured. There is no need to include in the message an identification of the "this"
object passed as the first argument to the exception constructor. That object will be identified when the
exception is reported to the user.
1. Yes, there are exceptions (NamedObj, CrossRefList, IOPort). Many discussions dealt with these names, and we
still regret not making them complete words.
126
6.5.6 Javadoc
Javadoc is a program distributed with Java that generates HTML documentation files from Java
source code files. Javadoc comments begin with "/**" and end with "*/". The comment immediately
preceding a method, member, or class documents that member, method, or class. Ptolemy II classes
include Javadoc documentation for all classes and all public and protected members and methods. Pri-
vate members and methods need not be documented. Documentation can include embedded HTML
formatting. For example, by convention, in actor documentation, we set in italics the names of the
ports and parameters using the syntax
/** In this actor, inputs are read from the <i>input</i> port ... */
By convention, method names are set in the default font, but followed by empty parentheses, as in
The parentheses are empty even if the method takes arguments. The arguments are not shown. If the
method is overloaded (has several versions with different argument sets), then the text of the documen-
tation needs to distinguish which version is being used.
It is common in the Java community to use the following style for documenting methods:
The reason we do this is that our sentence is a well-formed, grammatical English sentence, while the
usual convention is not (it is missing the subject). Moreover, calling a method is a command "do this,"
so it seems reasonable that the documentation say "Do this." The use of imperative tense has a large
impact on how interfaces are documented, especially when using the Listener design pattern. For
instance, the java.awt.event.ItemListener interface has the method:
/**
* Invoked when an item has been selected or deselected.
* The code written for this method performs the operations
* that need to occur when an item is selected (or deselected).
*/
127
void itemStateChanged(ItemEvent e);
/**
* Notify this object that an item has been selected or deselected.
*/
void itemStateChanged(ItemEvent e) ;
However, this sentence does not capture what the method does. The method may be called in order to
notify the listener, but the listener does not "notify this object". The correct way to concisely document
this method in imperative tense (and with meaningful names) is:
/**
* React to the selection or deselection of an item.
*/
void itemStateChanged(ItemEvent event);
The annotation for the arguments (the @param statement) is not a complete sentence, since it is
usually presented in tabular format. However, we do capitalize it and end it with a period.
Exceptions that are thrown by a method need to be identified in the Javadoc comment. An
©exception tag should read like this:
Notice that the body always starts with "If, not "Thrown if, or anything else. Just look at the Javadoc
output to see why this occurs. In the case of an interface or base class that does not throw the excep-
tion, use the following:
The exception still has to be declared so that derived classes can throw it, so it needs to be documented
as well.
The Javadoc program gives extensive diagnostics when run on a source file. Our policy is to for-
mat the comments until there are no Javadoc warnings.
128
Within each section, methods appear in alphabetical order, in order to easily search for a particular
method. If you wish to group methods together, try to name them so that they have a common pre-
fix. Static methods are generally mixed with non-static methods.
129/130
The Kernel
Author: Edward A. Lee
Contributors:
John Davis, II
Ron Galicia
Mudit Goel
Christopher Hylands
Jie Liu
Xiaojun Liu
Lukito Muliadi
Steve Neuendorffer
John Reekie
Neil Smyth
131/132
shown as filled circles, and relations connect the ports. We consistently use the term connection to
denote the association between connected ports (or their entities), and the term link to denote the asso-
ciation between ports and relations. Thus, a connection consists of a relation and two or more links.
The use of ports and hierarchy distinguishes our topologies from mathematical graphs. In a mathe-
matical graph, an entity would be a vertex, and an arc would be a connection between entities. A vertex
could be represented in our schema using entities that always contain exactly one port. In a directed
graph, the connections are divided into two subsets, one consisting of incoming arcs, and the other of
outgoing arcs. The vertices in such a graph could be represented by entities that contain two ports, one
for incoming arcs and one for outgoing arcs. Thus, in mathematical graphs, entities always have one or
two ports, depending on whether the graph is directed. Our schema generalizes this by permitting an
entity to have any number of ports, thus dividing its connections into an arbitrary number of subsets.
A second difference between our graphs and mathematical graphs is that our relations are multi-
way associations whereas an arc in a graph is a two-way association. A third difference is that mathe-
matical graphs normally have no notion of hierarchy (clustering).
Relations are intended to serve as mediators, in the sense of the Mediator design pattern of
Gamma, et al. [25]. "Mediator promotes loose coupling by keeping objects from referring to each
other explicitly..." For example, a relation could be used to direct messages passed between entities. Or
it could denote a transition between states in a finite state machine, where the states are represented as
entities. Or it could mediate rendezvous between processes represented as entities. Or it could mediate
method calls between loosely associated objects, as for example in remote method invocation over a
network.
7.2.1 Links
An Entity contains any number of Ports; such an aggregation is indicated by the association with
an unfilled diamond and the label "0..n" to show that the Entity can contain any number of Ports, and
the label "0..1" to show that the Port is contained by at most one Entity. This association is uses the
Connection
Link ^. Link X
Relation if\*ort
delation j
Connection
Port
Entity
133/134
NamedList class shown at the bottom of figure 7.2 and defined fully in figure 7.3. There is exactly one
instance of NamedList associated with Entity, and it aggregates the ports.
A Port is associated with any number of Relations (the association is called a link), and a Relation
is associated with any number of Ports. Link associations use CrossRefList, shown in figure 7.3. There
is exactly one instance of CrossRefList associated with each port and each relation. The links define a
web of interconnected entities.
On the port side, links have an order. They are indexed from 0 to n, where n is the number returned
by the numLinks() method of Port.
7.2.2 Consistency
A major concern in the choice of methods to provide and in their design is maintaining consis-
tency. By consistency we mean that the following key properties are satisfied:
• Every link between a port an a relation is symmetric and bidirectional. That is, if a port has a link
to a relation, then the relation has a link back to that port.
• Every object that appears on a container's list of contained objects has a back reference to its con-
tainer.
In particular, the design of these classes ensures that the _container attribute of a port refers to an entity
that includes the port on its _portList. This is done by limiting the access to both attributes. The only
way to specify that a port is contained by an entity is to call the setContainer() method of the port. That
NamadObJ
Port
_container: Entity
jBlationsList: CrossRefList
+Port()
+Port(w: Workspace)
+Port(container: Entity, name: String)
container +connectedPortList(): List
Entity ' 1 0..n ♦insertUnk(int: index, r: Relation) Relation
0..1 I +isünked(r: Relation): boolean
-_portList: NamedList +isOpaque(): boolean -_portList: CrossRefList
EntityO -MinkedRelationList(): List
Entityiname: String) linkedRelationsO: Enumeration +Relation()
+Entity(w : Workspace, name : String) link(r: Relation) +Relation(name: String)
+connectedPortList(): List ♦Relationjw : Workspace, name : String)
+numLinks(): int
+connectionsChanged(p: Port) +setContainer(c: Entity) tlinkedPortListO: List
+getPort(name: String): Port +unlink{index: int) +linkedPortList(except: Port): List
+!inkedRelationList(): List +unlink(r: Relation) linkedPortsO: Enumeration
+newPort(name: String): Port unlinkAIIO +numLinks(): int
+portüst(): List #_checkLink(relation: Relation) +unlinkAII()
»removeAIIPortsQ — A
ports in list O.n
1..1 I 0..n
1..1 ..A
port list
FIGURE 7.2. Key classes in the kernel package and their methods supporting basic (non-hierarchical) topol-
ogies. Methods that override those defined in a base class or implement those in an interface are not shown.
The "+" indicates public visibility, "#" indicates protected, and "-" indicates private. Capitalized methods are
constructors. The classes shown with dashed outlines are in the kernel.util subpackage.
135
«Interface» «utility»
Nameable CrossRefList
-JistVersion: long
+getContainer(): Nameable - size: int
NamedObj getFullNameQ : String +CrossRef List (container: Object)
+getName(): String +CrossRefList(container: Object, original: CrossRefList)
#_changeListeners : List setNamefname: String) +first(): Object
#_debugging ; boolean +getContainers(): Enumeration
#_uniqueNamelndex: int +insertLink(index : int, farList: CrossRefList)
#_workspace: Workspace +isLinked(o: Object): boolean
attributes : NamedList +link(fart_ist: CrossRefList)
debugListeners: LinkedList +size(): int
name: String +unlink(index: int)
Workspace +unlink(o: Object)
+NamedObj ()
+unlinkAII() ___^
NamedObj(name: String)
NamedObj(w : Workspace, name : String) -_directory: Linkedüst calls getReadDepth()
+addChangeListener(listener: ChangeListener) -_name: String
+addDebugl_istener(l: DebugListener) -_readers: Hashtable
+attributeChanged(a: Attribute) -_readOnly: boolean PtoJemyThread
+attributeüst(): List - writer: Thread
+attributeList(filter: Class): List +Workspace() #readDepth: int
+attributeTypeChanged(a: Attribute) +Workspace(name: String) PtotemyThread()
+clone(): Object +add(item: NamedObj) PtolemyThread(target: Runnable)
+clone(destination : Workspace): Object +directory(): Enumeration +PtolemyThread(target: Runnable, name : String)
+deepContains(inside : NamedObj): boolean +doneReading{) PtoJemyThread(name: String)
+deferredMoMLDefinitionFrom(): List ■doneWritingO +PtolemyThread(group : ThreadGroup, target: Runnable)
+exportMoML(); String getReadAccessQ +PtolemyThread(group : ThreadGroup, target: Runnable, name : String)
+exportMoML(name: String) +getWriteAccess() +PtolemyThread(group : ThreadGroup, name : String)
+exportMoML(output: Writer) +getVersion{): long ■getReadDepthO : int ___^_
+exportMoML(output: Writer, depth : int) +incrVersion()
+exportMoML(output: Writer, depth : int, name : String) +isReadOnly{): boolean
+getAttribute(name : String): Attribute +remove(item : NamedObj) String Utilities
+getDeferMoMLDefinitionTo(): NamedObj +removeAII()
getMoMLEIementNameO: String +setReadOnly(b: boolean)
getName(parent: NamedObj): String +wait(obj: Object)
+removeChangeListener(tistener: ChangeListener) +escapeFqrXML(strinQ : String): String
removeDebugListenerff: DebugListener) +substitute(string : $trino, oldsub ; String, newsub : Spring): String
+requestChange(change: ChangeRequest)
+setMoMLEIementName(name: String) «Interface» <y- Record« rUsttner
+toplevel(): NamedObj DebugLittBner
+uniqueName( prefix : String): String
+workspace(): Workspace
^RecorderListener()
+message(message: String) -getMessages(): String
y reset{)
«utility» StraamUstentr
NamtdUst
£ «Interface»
UurSettaM*
UbraryMarkorAttributo StringAitribut*
136
method guarantees consistency by first removing the port from any previous container's _portList,
then adding it to the new container's port list. A port is removed from an entity by calling setCon-
tainer() with a null argument.
A change in a containment association involves several distinct objects, and therefore must be
atomic, in the sense that other threads must not be allowed to intervene and modify or access relevant
attributes halfway through the process. This is ensured by synchronization on the workspace, as
explained below in section 7.6. Moreover, if an exception is thrown at any point during the process of
changing a containment association, any changes that have been made must be undone so that a consis-
tent state is restored.
7.3.1 Containers
Although these classes do not provide support for constructing clustered graphs, they provide rudi-
mentary support for container associations. An instance of these classes can have at most one con-
tainer. That container is viewed as the owner of the object, and "managed ownership" [44] is used as a
central tool in thread safety, as explained in section 7.6 below.
In the base classes shown in figure 7.2, only an instance of Port can have a non-null container. It is
the only class with a setContainer() method. Instances of all other classes have no container, and their
getContainer() method will return null. In the classes of figure 7.3, only Attribute has a setContainer()
method.
Every object is associated with exactly one instance of Workspace, as shown in figure 7.3, but the
workspace is not viewed as a container. The workspace is defined when an object is constructed, and
no methods are provided to change it. It is said to be immutable, a critical property in its use for thread
safety.
137
bolic links). Our design takes a stronger position on names, and views them as properties of the object,
much as we view the name of a person as a property of the person (vs. their employee number, for
example, which is a property of their association with an employer).
7.3.3 Workspace
Workspace is a concrete class that implements the Nameable interface, as shown in figure 7.3. All
objects in a topology are associated with a workspace, and almost all operations that involve multiple
objects are only supported for objects in the same workspace. This constraint is exploited to ensure
thread safety, as explained in section 7.6 below.
7.3.4 Attributes
In almost all applications of Ptolemy II, entities, ports, and relations need to be parameterized. The
base classes shown in figure 7.3 provide for these objects to have any number of instances of the
Attribute class attached to them. Attribute is a NamedObj that can be contained by another NamedObj,
and serves as a base class for parameters.
Attributes are added to a NamedObj by calling their setContainer() method and passing it a refer-
ence to the container. They are removed by calling setContainer() with a null argument. The Named-
Obj class provides the getAttribute() method, which takes an attribute name as an argument and returns
the attribute, and the attributeList() method, which returns a list of the attributes contained by the
object.
By itself, an instance of the Attribute class carries only a name, which may not be sufficient to
parameterize objects. Several derived classes implement the Settable interface, which indicates that
they can be assigned a value via a string. A simple attribute implementing the Settable interface is the
StringAttribute. It has a value that can be any string. A derived class called Variable that implements
the Settable interface is defined in the data package. The value of an instance of Variable is typically an
arithmetic expression.
An attribute that is not an instance of Settable is called a pure attribute. Its mere presence has sig-
nificance.
Attribute names can be any string that does not include periods, but it is recommend to stick to
alphanumeric characters, the space character, and the underscore. Names beginning with an underscore
are reserved for system use. The following names, for example, are in use:
Table 20: Names of special attributes
_library Marker ptolemy.kernel.util. Attribute Marks its container as a library vs. a composite entity.
138
Table 20: Names of special attributes
7.4.1 Abstraction
Composite entities are non-atomic (isAtomic() return false). They can contain a graph (entities and
relations). By default, a CompositeEntity is transparent (isOpaque() returns false). Conceptually, this
means that its contents are visible from the outside. The hierarchy can be ignored (flattened) by algo-
rithms operating on the topology. Some subclasses of CompositeEntity are opaque (see the Actor
Package chapter for examples). This forces algorithms to respect the hierarchy, effectively hiding the
contents of a composite and making it appear indistinguishable from atomic entities.
A ComponentPort contained by a CompositeEntity has inside as well as outside links. It maintains
two lists of links, those to relations inside and those to relations outside. Such a port serves to expose
139
NamedObJ
■"k>-
"Ä z
Port
^container: Entity
retationsüst: CrossRefList
Port()
Port(w : Workspace)
+Port(contäner: Entity, name : String)
+connectedPortList(): List
+insertLink(int: index, r: Relation)
+isLinked(r: Relation): boolean
+linkedRelationList(): List
EntityO +linkedRelations(): Enumeration
Relation()
+Entity(name: String) +link{r: Relation)
+Relation(name: String)
+Entity(w : Workspace, name : String) +numLinks(): int
Relation(w : Workspace, name : String)
+connectedPortUst(): List setContainerfc: Entity)
linkedPortListO : List
+connectionsChanged(p: Port) +unltnk(index: int)
+tinkedPortList(except: Port): List
+getPort(name : String): Port +unlink(r: Relation)
+linkedPorts(): Enumeration
linkedRelationList(): List +unlinkAII()
+numLinks(): int
+newPort(name : String): Port #_checkContainertcontainer: Entity)
unlinkAIIO
portList(): List #_checkLink(relation: Relation)
+removeAl I Ports () l\
c\ ports in list | On
1..1
X ComponentPort
CompositeEntity
ComponentRelation
_containedEntities : NamedList
containedRelations: NamedList
-_container: CompositeEntity
+CompositeEntity()
+CompositeEntity(w: Workspace) ComponentRelation()
+CompositeEnttty(container: CompositeEntity, name : String) ComponentRelation(w: Workspace)
♦allow LevelCrossingConnect(b: boolean) +ComponentRelation(container: CompositeEntity, name : String)
+connect(p1 : Component Port, p2 : ComponentPort): ComponentRelation +deeplinkedPortList(): List
+connect{p1 : ComponentPort, p2 : ComponentPort, name: String): ComponentRelation +setContainer(container: CompositeEntity)
+deepEntityList(): List #_checkContainer(container: CompositeEntity)
+entityList(): List 0.J1
+entityList(filter: Class): List
+exportLinks(indentation : int, filter: Collection): String container
+getEntity(name : String): Components ntity
+getRelation(name : String): ComponentRelation
+newRelation(name ; String): ComponentRelation
+numEntities(): int
+numRelations(): int
+relationList(): List
+removeAIIEntities()
+removeAIIRelations()
#_addEntity(entity: ComponentEntity)
#_addRelation( relation: ComponentRelation)
#_removeEntity(entity: ComponentEntity)
#_removeRelat ion (relation : ComponentRelation)
140
ports in the contained entities as ports of the composite. This is the converse of the "hiding" operator
often found in process algebras [58]. Ports within an entity are hidden by default, and must be explic-
itly exposed to be visible (linkable) from outside the entity1. The composite entity with ports thus pro-
vides an abstraction of the contents of the composite.
A port of a composite entity may be opaque or transparent. It is defined to be opaque if its con-
tainer is opaque. Conceptually, if it is opaque, then its inside links are not visible from the outside, and
the outside links are not visible from the inside. If it is opaque, it appears from the outside to be indis-
tinguishable from a port of an atomic entity.
The transparent port mechanism is illustrated by the example in figure 7.52. Some of the ports in
figure 7.5 are filled in white rather than black. These ports are said to be transparent. Transparent ports
(P3 and P4) are linked to relations (Rl and R2) below their container (El) in the hierarchy. They may
also be linked to relations at the same level (R3 and R4).
ComponentPort, ComponentRelation, and CompositeEntity have a set of methods with the prefix
"deep," as shown in figure 7.4. These methods flatten the hierarchy by traversing it. Thus, for example,
the ports that are "deeply" connected to port PI in figure 7.5 are P2, P5, and P6. No transparent port is
included, so note that P3 is not included.
Deep traversals of a graph follow a simple rule. If a transparent port is encountered from inside,
then the traversal continues with its outside links. If it is encountered from outside, then the traversal
continues with its inside links. Thus, for example, the ports deeply connected to P5 are PI and P2.
Note that P6 is not included. Similarly, the deepEntityList() method of CompositeEntity looks inside
transparent entities, but not inside opaque entities.
Since deep traversals are more expensive than just checking adjacent objects, both ComponentPort
and ComponentRelation cache them. To determine the validity of the cached list, the version of the
workspace is used. As shown in figure 7.2, the Workspace class includes a getVersion() and incrVer-
FIGURE 7.5. Transparent ports (P3 and P4) are linked to relations (Rl and R2) below their container (El)
in the hierarchy. They may also be linked to relations at the same level (R3 and R4).
141
sion() method. AH methods of objects within a workspace that modify the topology in any way are
expected to increment the version count of the workspace. That way, when a deep access is performed
by a ComponentPort, it can locally store the resulting list and the current version of the workspace.
The next time the deep access is requested, it checks the version of the workspace. If it is still the same,
then it returns the locally cached list. Otherwise, it reconstructs it.
For ComponentPort to support both inside links and outside links, it has to override the link() and
unlink() methods. Given a relation as an argument, these methods can determine whether a link is an
inside link or an outside link by checking the container of the relation. If that container is also the con-
tainer of the port, then the link is an inside link.
142
weaker when level-crossing connections are allowed.
The other two level-crossing connections in figure 7.6 are mediated by transparent ports. This sort
of hybrid could come about in heterogeneous representations, where level-crossing connections are
permitted in some parts but not in others. It is important, therefore, for the classes to support such
hybrids.
To support such hybrids, we have to modify slightly the algorithm by which a port recognizes an
inside link. Given a relation and a port, the link is an inside link if the relation is contained by an entity
that is either the same as or is deeply contained (i.e. directly or indirectly contained) by the entity that
contains the port. The deepContains() method of NamedObj supports this test.
7.4.4 Cloning
The kernel classes are all capable of being cloned, with some restrictions. Cloning means that an
identical but entirely independent object is created. Thus, if the object being cloned contains other
objects, then those objects are also cloned. If those objects are linked, then the links are replicated in
FIGURE 7.7. A tunneling entity contains a relation with inside links to more than one port.
143
the new objects. The clone() method in NamedObj provides the interface for doing this. Each subclass
provides an implementation.
There is a key restriction to cloning. Because they break modularity, level-crossing links prevent
cloning. With level-crossing links, a link does not clearly belong to any particular entity. An attempt to
clone a composite that contains level-crossing links will trigger an exception.
144
public class ExampleSystem {
private CompositeEntity eO, e3, e4, e7, elO;
private ComponentEntity el, e2, e5, e6, e8, e9;
private ComponentPort pO, pi, p2, p3, p4, p5, p6, p7, p8, p9, plO, pll, pl2, pl3, p4;
private ComponentRelation rl, r2, r3, r4, r5, r6, r7, r8, r9, rlO, rll, rl2;
el = newComponentEntity(e4, "El");
e2 = new ComponentEnn'ty(e4, "E2");
e5 = new ComponentEntity(e3, "E5");
e6 = new ComponentEnn'ty(e3, "E6");
e8 = new ComponentEnn'ty(e7, "E8");
e9 = new ComponentEnnty(elO, "E9");
pO = (ComponentPort) e4.newPort("P0");
pi = (ComponentPort) el.newPort("Pl");
p2 = (ComponentPort) e2.newPort("P2");
p3 = (ComponentPort) e2.newPort("P3");
p4 = (ComponentPort) e4.newPort("P4");
p5 = (ComponentPort) e5.newPort("P5");
p6 = (ComponentPort) e5.newPort("P6");
p7 = (ComponentPort) e3.newPort("P7");
p8 = (ComponentPort) e7.newPort("P8");
p9 = (ComponentPort) e8.newPort("P9");
plO = (ComponentPort) e8.newPort("P10");
pll = (ComponentPort) e7.newPort("Pll");
pl2 = (ComponentPort) el0.newPort("P12");
pl3 = (ComponentPort) el0.newPort("P13");
pl4 = (ComponentPort) e9.newPort("P14");
FIGURE 7.9. The same topology as in figure 7.8 implemented as a Java class.
145
The results of various method accesses on the graph are shown in figure 7.11. This table can be
studied to better understand the precise meaning of each of the methods.
# Create ports.
set pO [$e4 newPort PO]
set pi [$el newPort PI]
set p2[$e2 newPort P2]
set p3 [$e2 newPort P3]
set p4[$e4 newPort P4]
set p5[$e5 newPort P5]
set p6[$e6 newPort P6]
set p7[$e3 newPort P7]
set p8 [$e7 newPort P8]
set p9[$e8 newPort P9]
set pl0[$e8 newPort P10]
setpll [$e7 newPort PI 1]
set pl2[$el0 newPort P12]
set pl3 [SelO newPort P13]
set pl4[$e9 newPort P14]
# Create links
set rl [$e4 connect $pl SpO Rl]
set r2 [Se4 connect $pl $p4 R2]
$p3 link $r2
set r3 [$e4 connect $pl Sp2 R3]
set r4 [$e3 connect $p4 $p7 R4]
set r5 [$e3 connect $p4 $p5 R5]
$e3 allowLevelCrossingConnect true
set r6 [$e3 connect $p3 $p6 R6]
set r7 [SeO connect $p7 $pl 3 R7]
set r8 [Se7 connect $p9 $p8 R8]
set r9 [Se7 connect SplO Spl 1 R9]
set rlO [SeO connect $p8 $pl2 RIO]
setrll [SelO connect $pl2$pl3Rll]
set rl2 [SelO connect $pl4 $pl3 R12]
Spll link Sr7
L
FIGURE 7.10. The same topology as in figure 7.8 described by the Tel Blend commands to create it.
146
An entity can be opaque and composite at the same time. Ports are defined to be opaque if the
entity containing them is opaque (isOpaque() returns true), so deep traversals of the topology do not
cross these ports, even though the ports support inside and outside links. The actor package makes
extensive use of such entities to support mixed modeling. That use is described in the Actor Package
chapter. In the previous generation system, Ptolemy Classic, composite opaque entities were called
wormholes.
7.6 Concurrency
We expect concurrency. Topologies often represent the structure of computations. Those computa-
tions themselves may be concurrent, and a user interface may be interacting with the topologies while
they execute their computation. Moreover, Ptolemy II objects may interact with other objects concur-
rently over the network via RMI or CORBA.
Both computations within an entity and the user interface are capable of modifying the topology.
Thus, extra care is needed to make sure that the topology remains consistent in the face of simulta-
neous modifications (we defined consistency in section 7.2.2).
Concurrency could easily corrupt a topology if a modification to a symmetric pair of references is
interrupted by another thread that also tries to modify the pair. Inconsistency could result if, for exam-
ple, one thread sets the reference to the container of an object while another thread adds the same
object to a different container's list of contained objects. Ptolemy II prevents such inconsistencies from
deepGetConnectedPorts P9 PI PI P9 PI P3 P9 PI PI PI PI P9 PI PI
P14 P9 P14 P3 P14 P3 P3 P3 P3 P3 P3"
P10 P14 P10 P10 P10 P10 P9 P9 P10 P10
P5 P10 P5 P14 P14
P3 P5
P2 P6
147
occurring. Such enforced consistency is called thread safety.
public class A {
public synchronized void foo() |
try{
workspace().getReadAccess();
(a) //... code that reads
} finally {
public class B { workspace().doneReading();
public void foo() {
synchronized(obj) {
(d)
try{
workspace().getWriteAccess();
(b) //... code that writes
} finally {
workspace().doneWriting();
public class C extends NamedObj {
public void foo() {
synchronized(workspaceO) \
(e)
I
(c)
FIGURE 7.12. Using monitors for thread safety. The method used in Ptolemy II is in (d) and (e).
148
One possible solution is to ensure that locks are always acquired in the same order [44]. For exam-
ple, we could use the containment hierarchy and always acquire locks top-down in the hierarchy. Sup-
pose for example that a body of code involves two objects a and b, where a contains b (directly or
indirectly). In this case, "involved" means that it either modifies members of the objects or depends on
their values. Then this body of code would be surrounded by:
synchronized(a) {
synchronized (b) {
}
}
If all code that locks a and b respects this same order, then deadlock cannot occur. However, if the
code involves two objects where one does not contain the other, then it is not obvious what ordering to
use in acquiring the locks. Worse, a change might be initiated that reverses the containment hierarchy
while another thread is in the process of acquiring locks on it. A lock must be acquired to read the con-
tainment structure before the containment structure can be used to acquire a lock! Some policy could
certainly be defined, but the resulting code would be difficult to guarantee. Moreover, testing for dead-
lock conditions is notoriously difficult, so we implement a more conservative, and much simpler strat-
egy-
149
and a write lock). When the user is finished reading the workspace data, it must call doneReading().
Failure to do so will result in no writer ever again gaining write access to the workspace. Because it is
so important to call this method, it is enclosed in the finally clause of a try statement. That clause is
executed even if an exception occurs in the body of the try statement.
The code for writers is shown in figure 7.12(e). The writer first calls the getWriteAccess() method
of the Workspace class. That method does not return until it is safe to write into the workspace. It is
safe if no other thread has read or write permission on the workspace. The calling thread, of course,
may safely have both read and write permission at the same time. Once again, it is essential that done-
WritingO be called after writing is complete.
This solution, while not as conservative as the single monitor of figure 7.12(c), is still conservative
in that mutual exclusion is applied even on write actions that are independent of one another if they
share the same workspace. This effectively serializes some modifications that might otherwise occur in
parallel. However, there is no constraint in Ptolemy II on the number of workspaces used, so sub-
classes of these kernel classes could judiciously use additional workspaces to increase the parallelism.
But they must do so carefully to avoid deadlock. Moreover, most of the methods in the kernel refuse to
operate on multiple objects that are not in the same workspace, throwing an exception on any attempt
to do so. Thus, derived classes that are more liberal will have to implement their own mechanisms sup-
porting interaction across workspaces.
There is one significant subtlety regarding read and write permissions on the workspace. In a mul-
tithreaded application, normally, when a thread suspends (for example by calling wait()), if that thread
holds read permission on the workspace, that permission is not relinquished during the time the thread
is suspended. If another thread requires write permission to perform whatever action the first thread is
waiting for, then deadlock will ensue. That thread cannot get write access until the first thread releases
its read permission, and the first thread cannot continue until the second thread gets write access.
The way to avoid this situation is to use the wait() method of Workspace, passing as an argument
the object on which you wish to wait (see Workspace methods in figure 7.3). That method first relin-
quishes all read permissions before calling wait on the target object. When wait() returns, notice that it
is possible that the topology has changed, so callers should be sure to re-read any topology-dependent
information. In general, this technique should be used whenever a thread suspends while it holds read
permissions.
7.7 Mutations
Often it is necessary to carefully constrain when changes can be made in a topology. For example,
an application that uses the actor package to execute a model defined by a topology may require the
topology to remain fixed during segments of the execution. During these segments, the workspace can
150
be made read-only (see section 7.6.3), significantly improving performance.
The util subpackage of the kernel package provides support for carefully controlled mutations that
can occur during the execution of a model. The relevant classes and interfaces are shown in figure
7.13.
The usage pattern involves an originator that wishes to have a mutation performed, such as an
actor (see the Actor Package chapter) or a user interface component. The originator creates an instance
of the class ChangeRequest and enqueues that request by calling the requestChange() of any object in
the Ptolemy II hierarchy. That object typically delegates the request to the top-level of the hierarchy,
which in turn delegates to the manager. When it is safe, the manager executes the change by calling
execute() on each enqueued ChangeRequest. In addition, it informs any registered change listeners of
the mutations so that they can react accordingly. Their changeExecuted() method is called if the
change succeeds, and their changeFailed() method is called if the change fails. The list of listeners is
maintained by the manager, so when a listener is added to or removed from any object in the hierarchy,
that request is delegated to the manager.
NamedObj
Object
constructs ChangeRequest' enqueues with NamedObj j
>+addChangeListener(listener: ChangeListener)
!+removeChangeListener(lis(ener: ChangeListener)
"K ~j+requestChange(c: ChangeRequest)
0..n
originator specifies the list of
listeners by < ailing
setListenersf) delegates execution to Manager
O.n
ChtngoRaquast Manager \
originator I
.j
•-Originator: Object
+ChangeRequest(originator: Object, description : String) i+requestChange(c : ChangeRequest):
+execute()
+getDescription(): String
+getOriginator(): Object
+setListeners(lis!eners : List) s ChangeListener of completion
+waitForCompletion() ■ > «Interface»
tt_execute() ChangeListener
"TV"
+changeExecuted(c: ChangeRequest)
+changeFailed(c : ChangeRequest, e : Exception)
moml package
StreamChangeListener
MoMLChangeReq uest
_parser: MoMLParser
+StreamChangeLislener()
staticParser: MoMLParser
+StreamChangeListener(out: OutputStream)
+MoMLChangeRequest(originator: Object, parser: MoMLParser, request: String)
+MoMLChangeRequest(originator: Object, context: NamedObj, request: String)
FIGURE 7.13. Classes and interfaces in kemel.event, which supports controlled topology mutations. An origi-
nator requests topology changes and a manager performs them at a safe time.
151
protected _execute() method, which actually performs the change. If the _execute() method completes
successfully, then the ChangeRequest object notifies listeners of success. If the _execute() method
throws an exception, then the ChangeRequest object notifies listeners of failure.
The ChangeRequest class is abstract. Its _execute() method is undefined. In atypical use, an origi-
nator will define an anonymous inner class, like this:
By convention, the change request is usually posted with the container that will be affected by the
change. The body of the _execute() method can create entities, relations, ports, links, etc. For example,
the code in the _execute() method to create and link a new entity might look like this:
When _execute() is called, the entity named newentity will be created, added to originator (which is
assumed to be an instance of CompositeEntity here) and linked to relation.
A key concrete class extending ChangeRequest is implemented in the moml package, as shown in
figure 7.13. The MoMLChangeRequest class supports specification of a change in MoML. See the
MoML chapter for details about how to write MoML specifications for changes. The context argument
to the second constructor typically gives a composite entity within which the commands should be
interpreted. Thus, the same change request as above could be accomplished as follows:
152
The ChangeRequest class also provides a waitForCompletion() method. This method will not
return until the change request completes. If the request fails with an exception, then waitForComple-
tion() will throw that exception. Note that this method can be quite dangerous to use. It will not return
until the change request is processed. If for some reason change requests are not being processed (due
for a example to a bug in user code in some actor), then this method will never return. If you make the
mistake of calling this method from within the event thread in Java, then if it never returns, the entire
user interface will freeze, no longer responding to inputs from the keyboard or mouse, nor repainting
the screen. The user will have no choice but to kill the program, possibly losing his or her work.
7.8 Exceptions
Ptolemy II includes a set of exception classes that provide a uniform mechanism for reporting
errors that takes advantage of the identification of named objects by full name. These exception are
summarized in the class diagram in figure 7.14.
153
for this exception to ever occur, so occurrence is a bug. This exception is derived from the Java Runt-
imeException.
InternalErrorException. An unexpected error other than an inconsistent state has been encountered.
Our design should make it impossible for this exception to ever occur, so occurrence is a bug. This
exception is derived from the Java RuntimeException.
Exception RuntimeException
KemelException InvafldStateExceptlon
NoSuchKemExctption InternalErrorException
-_message: String
+NoSuchltemException(msg: String) +lntemalErrorException(message: String)
+NoSucriltemException(obj: Nameable, msg : String) +getMessage(): String
NarmDuplicatJonException
NlegalActi onException
■HlegalActionException(message: String)
■HlegalActionExceptionfobj: Nameable)
■Hlega!ActionException(obj: Nameable, message : String)
■HlegalActionException(o1 : Nameable, o2 : Nameable)
■HlegalActionException(o1 : Nameable, o2 : Nameable, msg : String)
FIGURE 7.14. Summary of exceptions defined in the kernel.util package. These are used primarily through
constructor calls. The form of the constructors is shown in the text. Exception and RuntimeException are
Java exceptions.
154
Actor Package
Author: Edward A. Lee
Contributors:
Mudit Goel
Christopher Hylands
Jie Liu
Lukito Muliadi
Steve Neuendorffer
Neil Smyth
YuhongXiong
In the kernel package, entities have no semantics. They are syntactic placeholders. In many of the
uses of Ptolemy II, entities are executable. The actor package provides basic support for executable
entities. It makes a minimal commitment to the semantics of these entities by avoiding specifying the
order in which actors execute (or even whether they execute sequentially or concurrently), and by
avoiding specifying the communication mechanism between actors. These properties are defined in the
domains.
In most uses, these executable entities conceptually (if not actually) execute concurrently. The goal
of the actor package is to provide a clean infrastructure for such concurrent execution that is neutral
about the model of computation. It is intended to support dataflow, discrete-event, synchronous-reac-
tive, continuous-time, communicating sequential processes, and process networks models of computa-
tion, at least. The detailed model of computation is then implemented in a set of derived classes called
a domain. Each domain is a separate package.
Ptolemy II is an object-oriented application framework. Actors [1] extend the concept of objects to
155
concurrent computation. Actors encapsulate a thread of control and have interfaces for interacting with
other actors. They provide a framework for "open distributed object-oriented systems." An actor can
create other actors, send messages, and modify its own local state.
Inspired by this model, we group a certain set of classes that support computation within entities in
the actor package. Our use of the term "actors," however, is somewhat broader, in that it does not
require an entity to be associated with a single thread of control, nor does it require the execution of
threads associated with entities to be fair. Some subclasses, in other packages, impose such require-
ments, as we will see, but not all.
Agha's actors [ 1 ] can only send messages to acquaintances — actors whose addresses it was given
at creation time, or whose addresses it has received in a message, or actors it has created. Our equiva-
lent constraint is that an actor can only send a message to an actor if it has (or can obtain) a reference to
an input port of that actor. The usual mechanism for obtaining a reference to an input port uses the
topology, probing for a port that it is connected to. Our relations, therefore, provide explicit manage-
ment of acquaintance associations. Derived classes may provide additional implicit mechanisms. We
define actor more loosely to refer to an entity that processes data that it receives through its ports, or
that creates and sends data to other entities through its ports.
The actor package provides templates for two key support functions. These templates support mes-
sage passing and the execution sequence (flow of control). They are templates in that no mechanism is
actually provided for message passing or flow of control, but rather base classes are defined so that
domains only need to override a few methods, and so that domains can interoperate.
156
method of the receiver, passing it the token. The destination actor retrieves the token by calling the
get() method of its input port, which in turn calls the get() method of the designated receiver.
Domains typically provide specialized receivers. These receivers override get() and put() to imple-
ment the communication protocol pertinent to that domain. A domain that uses asynchronous message
passing, for example, can usually use the QueueReceiver shown in figure 8.2. A domain that uses syn-
chronous message passing (rendezvous) has to provide a new receiver class.
In figure 8.1 there is only a single channel, indexed 0. The "0" argument of the send() and get()
methods refer to this channel. A port can support more than one channel, however, as shown in figure
8.3. This can be represented by linking more than one relation to the port, or by linking a relation that
has a width greater than one. A port that supports this is called a multiport. The channels are indexed
0, ..., N— 1, where N is the number of channels. An actor distinguishes between channels using this
index in its send() and get() methods. By default, an IOPort is not a multiport, and thus supports only
one channel (or zero, if it is left unconnected). It is converted into a multiport by calling its setMulti-
port() method with a true argument. After conversion, it can support any number of channels.
Multiports are typically used by actors that communicate via an indeterminate number of channels.
For example, a "distributor" or "demultiplexer" actor might divide an input stream into a number of
output streams, where the number of output streams depends on the connections made to the actor. A
stream is a sequence of tokens sent over a channel.
An IORelation, by default, represents a single channel. By calling its setWidth() method, however,
it can be converted to a bus. A multiport may use a bus instead of multiple relations to distribute its
data, as shown in figure 8.4. The width of a relation is the number of channels supported by the rela-
tion. If the relation is not a bus, then its width is one.
The width of a port is the sum of the widths of the relations linked to it. In figure 8.4, both the
FIGURE 8.1. Message passing is mediated by the IOPort class. Its send() method obtains a reference to a
remote receiver, and calls the put() method of the receiver, passing it the token /. The destination actor
retrieves the token by calling the getO method of its input port.
FIGURE 8.3. A port can support more than one channel, permitting an entity to send distinct data to distinct
destinations via the same port. This feature is typically used when the number of destinations varies in dif-
ferent instances of the source actor.
157
«Interface»
Actor
-Oi K
«Interface»
AtomicActor TypedActor CompostteActor |
T__
TypedlOPort
+TYPE : int
•_constraints: List
■_declaredType: Type
-_resolvedType: Type
-JypeTerm: TypeTerm lORelatfon
■_typeListeners: List
+TypedlOPort()
+TypedlOPort{container: ComponentEntity, name : String)
+TypedlOPort(container: ComponentEntity, name : String, islnput: boolean, isOutput: boolean)
+addTypeListener(listener: Typeüstener)
+removeTypeListener(listener: Typeüstener)
TypedlORelatlon
♦Typed lORelationO
+TypedlORelation(workspace: Workspace)
+Typed1QRelation(container : TypedCom positeActor, name : String)
FIGURE 8.2. Port and receiver classes that provide infrastructure for message passing under various com-
munication protocols.
158
sending and receiving ports are multiports with width two. This is indicated by the "2" adjacent to each
port. Note that the width of a port could be zero, if there are no relations linked to a port (such a port is
said to be disconnected). Thus, a port may have width zero, even though a relation cannot. By conven-
tion, in Ptolemy II, if a token is sent from such a port, the token goes nowhere. Similarly, if a token is
sent via a relation that is not linked to any input ports, then the token goes nowhere. Such a relation is
said to be dangling.
A given channel may reach multiple ports, as shown in figure 8.5. This is represented by a relation
that is linked to multiple input ports. In the default implementation, in class IOPort, a reference to the
token is sent to all destinations. Note that tokens are assumed to be immutable, so the recipients cannot
modify the value. This is important because in most domains, it is not obvious in what order the recip-
ients will see the token.
The send() method takes a channel number argument. If the channel does not exist, the send()
method silently returns without sending the token anywhere. This makes it easier for model builders,
since they can simply leave ports unconnected if they are not interested in the output data.
IOPort provides a broadcast) method for convenience. This method sends a specified token to all
receivers linked to the port, regardless of the width of the port. If the width is zero, of course, the token
will not be sent anywhere.
8.2.2 Example
An elaborate example showing all of the above features is shown in figure 8.6. In that example, we
assume that links are constructed in top-to-bottom order. The arrows in the ports indicate the direction
of the flow of tokens, and thus specify whether the port is an input, an output, or both. Multiports are
indicated by adjacent numbers larger than one.
FIGURE 8.4. A bus is an IORelation that represents multiple channels. It is indicated by a relation with a
slash through it, and the number adjacent to the bus is the width of the bus.
.^■^ token 1
receiver.put(t)
7^\
get(0)token (clone
oft)
E3
FIGURE 8.5. Channels may reach multiple destinations. This is represented by relations linking multiple
input ports to an output port.
159
The top relation is a bus with width two, and the rest are not busses. The width of port PI is four.
Its first two outputs (channels zero and one) go to P4 and to the first two inputs of P5. The third output
of PI goes nowhere. The fourth becomes the third input of P5, the first input of P6, and the only input
of PS, which is both an input and an output port. Ports P2 and P8 send their outputs to the same set of
destinations, except that P8 does not send to itself. Port P3 has width zero, so its send() method cannot
be called without triggering an exception. Port P6 has width two, but its second input channel has no
output ports connected to it, so calling get(l) will trigger an exception that indicates that there is no
data. Port P7 has width zero so calling get() with any argument will trigger an exception.
/2 2
X>P4 E4
c 1 J
P1
E1
^
)P5
f P2 {
)P6
)P7
E5
P3 { 0'
fc1
^E2
F>8
LE3 )
FIGURE 8.6. An elaborate example showing several features of the data transport mechanism.
160
FIGURE 8.7. An example showing busses combined with input, output, and transparent ports.
FIGURE 8.8. Tel Blend code to construct the example in figure 8.7.
161
width to the maximum of those port widths, minus the widths of other relations linked to those ports on
the inside. Each such port is allowed to have at most one inside relation with an unspecified width, or
an exception is thrown. If this inference yields a width of zero, then the width is defined to be one.
Thus, Rl will have width 4 and R5 will have width 3 in this example. The width of a transparent port is
the sum of the widths of the relations it is linked to on the outside (just like an ordinary port). Thus, P4
has width 0, P3 has width 2, and P2 has width 4. Recall that a port can have width 0, but a relation can-
not have width less than one.
When data is sent from PI, four distinct channels can be used. All four will go through P2 and P5,
the first three will reach P8, two copies of the fourth will reach P9, the first two will go through P3 to
P7, and none will go through P4.
By default, an IORelation is not a bus, so its width is one. To turn it into a bus with unspecified
width, call setWidth() with a zero argument. Note that getWidth() will nonetheless never return zero (it
returns at least one). To find out whether setWidth() has been called with a zero argument, call
isWidthFixed() (see figure 8.2). If a bus with unspecified width is not linked on the inside to any trans-
parent ports, then its width is one. It is not allowed for a transparent port to have more than one bus
with unspecified width linked on the inside (an exception will be thrown on any attempt to construct
such a topology). Note further that a bus with unspecified width is still a bus, and so can only be linked
to multiports.
In general, bus widths inside and outside a transparent port need not agree. For example, if M< N
in figure 8.9, then first M channels from PI reach P3, and the last N-M channels are dangling. If
M>N, then all N channels from PI reach P3, but the last M-N channels at P3 are dangling.
Attempting to get a token from these channels will trigger an exception. Sending a token to these chan-
nels just results in loss of the token.
Note that data is not actually transported through the relations or transparent ports in Ptolemy II.
Instead, each output port caches a list of the destination receivers (in the form of the two-dimensional
array returned by getRemoteReceivers()), and sends data directly to them. The cache is invalidated
whenever the topology changes, and only at that point will the topology be traversed again. This sig-
nificantly improves the efficiency of data transport.
FIGURE 8.9. Bus widths inside and outside a transparent port need not agree..
162
not be viewed as an exhaustive set, but rather as a particularly useful set of receivers. These receivers
are shown in figure 8.2.
Mailbox Communication. The Director base class by default returns a simple receiver called a Mail-
box. A mailbox is a receiver that has capacity for a single token. It will throw an exception if it is
empty and get() is called, or it is full and put() is called. Thus, a subclass of Director that uses this
should schedule the calls to put() and get() so that these exceptions do not occur, or it should catch
these exceptions.
Asynchronous Message Passing. This is supported by the QueueReceiver class. A QueueReceiver con-
tains an instance of FIFOQueue, from the actor.util package, which implements a first-in, first-out
queue. This is appropriate for all flavors of dataflow as well as Kahn process networks.
In the Kahn process networks model of computation [41], which is a generalization of dataflow [49],
each actor has its own thread of execution. The thread calling get() will stall if the corresponding queue
is empty. If the size of the queue is bounded, then the thread calling put() may stall if the queue is full.
This mechanism supports implementation of a strategy that ensures bounded queues whenever possi-
ble [69].
In the process networks model of computation, the history of tokens that traverse any connection is
determinate under certain simple conditions. With certain technical restrictions on the functionality of
the actors (they must implement monotonic functions under prefix ordering of sequences), our imple-
mentation ensures determinacy in that the history does not depend on the order in which the actors
carry out their computation. Thus, the history does not depend on the policies used by the thread
scheduler.
FIFOQueue is a support class that implements a first-in, first-out queue. It is part of the actor.util
package, shown in figure 8.10. This class has two specialized features that make it particularly useful
in this context. First, its capacity can be constrained or unconstrained. Second, it can record a finite or
infinite history, the sequence of objects previously removed from the queue. The history mechanism is
useful both to support tracing and debugging and to provide access to a finite buffer of previously con-
sumed tokens.
An example of an actor definition is shown in figure 8.11. This actor has a multiport output. It
reads successive input tokens from the input port and distributes them to the output channels. This
actor is written in a domain-polymorphic way, and can operate in any of a number of domains. If it is
used in the PN domain, then its input will have a QueueReceiver and the output will be connected to
ports with instances QueueReceiver.
Rendezvous Communications. Rendezvous, or synchronous communication, requires that the origina-
tor of a token and the recipient of a token both be simultaneously ready for the data transfer. As with
process networks, the originator and the recipient are separate threads. The originating thread indicates
a willingness to rendezvous by calling send(), which in turn calls the put() method of the appropriate
receiver. The recipient indicates a willingness to rendezvous by calling get() on an input port, which in
turn calls get() of the designated receiver. Whichever thread does this first must stall until the other
thread is ready to complete the rendezvous.
This style of communication is implemented in the CSP domain. In the receiver in that domain, the
put() method suspends the calling thread if the get() method has not been called. The get() method sus-
pends the calling thread if the put() method has not been called. When the second of these two methods
is called, it wakes up the suspended thread and completes the data transfer. The actor shown in figure
163
8.11 works unchanged in the CSP domain, although its behavior is different in that input and output
actions involve rendezvous with another thread.
Nondeterministic transfers can be easily implemented using this mechanism. Suppose for example
that a recipient is willing to rendezvous with any of several originating threads. It could spawn a thread
for each. These threads should each call get(), which will suspend the thread until the originator is will-
ing to rendezvous. When one of the originating threads is willing to rendezvous with it, it will call
put(). The multiple recipient threads will all be awakened, but only one of them will detect that its ren-
dezvous has been enabled. That one will complete the rendezvous, and others will die. Thus, the first
originating thread to indicate willingness to rendezvous will be the one that will transfer data. Guarded
communication [4] can also be implemented.
Discrete-Event Communication. In the discrete-event model of computation, tokens that are trans-
ferred between actors have a time stamp, which specifies the order in which tokens should be pro-
cessed by the recipients. The order is chronological, by increasing time stamp. To implement this, a
discrete-event system will normally use a single, global, sorted queue rather than an instance of FIFO-
Queue in each input port. The kernel.util package, shown in figure 8.10, provides the CalendarQueue
actor.util «Interface»
Cloneable j Comparator
FIFOQueue
|+compare(o1 : Object, o2 Object) int;
■„container: Nameabie
•_historyCapacity: int
■_historyl_ist: LinkedList
-_queueCapactty: int CQComparator
•_queueList: LinkedList
+FIFOQueue() +timeStamp: double
+FIFOQueue(container: Nameabie) +getVirtualBinNumber(entry ; Object): long ♦contents: Object
+FIFOQueue(model: FIFOQueue) +setBinWidth(keyArray: Objectfl) +TimedEvent(time : double, contents : Object)
+dear() +setZeroReference(zero: Object)
+elementList(): List
+get(offset: int): Object 6
+getCapacity(): int
+getContainert): Nameabie
+getHistoryCapadty(): int
+historyElementList(): List
+historySize<): int Doubl eC QCom parator TimedEvent.TimeComparatofuWic inner dass
-MsFullO: boolean
+put(element: Object): boolean .binWidth : Object ■_binWidth: Object
+setCapacity(maxstze: int) zeroReference: Object ■_zeroReference : Object
+setContainertcontainer: Nameabie)
+setHistoryCapacity(capaäty: int) +DoubleCQComparator()
+size(): Int
+take(): Object «Interface»
Dabuggable
CalendarQueue
-„bucket: CQLinkedListfl
-_nBuckets: int
qSize: int
+CalendarQueue(comparator: CQComparator)
+CalendarQueue(comparator: CQComparator, mi nN urn Buckets: int, binCount Factor: int) CQLInkedLlst
+clear()
opovale inner dass
+get(): Object
+indudes(entry: Object): boolean
+isEmpty(): boolean +CQLinkedUst()
+put(entry: Object): boolean +first{): Object
+remove(entry: Object): boolean +indudes(obj: Object): boolean
+setAdaptive(flag: boolean) +isEmpty(): boolean
+size(): int +insert(obj: Object)
+take(): Object +remove(c: CQEntry): boolean
♦toArrayO: Objectfl take(): Object
»to Array (limit: int): Object [] ______^_________
164
class, which gives an efficient and flexible implementation of such a sorted queue.
8.3 Execution
The Executable interface, shown in figure 8.12, is implemented by the Director class, and is
extended by the Actor interface. An actor is an executable entity. There are two types of actors, Atom-
icActor, which extends ComponentEntity, and CompositeActor, which extends CompositeEntity. As
the names imply, an AtomicActor is a single entity, while a CompositeActor is an aggregation of
actors. Two further extensions implement a type system, TypedAtomicActor and TypedCompositeAc-
tor.
The Executable interface defines how an object can be invoked. There are seven methods. The ini-
tialize() method is assumed to be invoked exactly once during the lifetime of an execution of a model.
It may be invoked again to restart an execution. The prefire(), fire(), and postfire() methods will usu-
ally be invoked many times. The fire() method may be invoked several times between invocations of
prefire() and postfire(). The stopFire() method is invoked to request suspension of firing. The wrapup()
public class Distributor extends TypedAtomicActor {
FIGURE 8.11. An actor that distributes successive input tokens to a set of output channels.
165
actor
«Interface»
Executable rO~
NamedObj Manager
«Interface»
♦firefl Runnable
CORRUPTED: State
^initializer) +IDLE: State
<]-- -KJ- INITIALING : State
+postfireO: boolean
+pnsfireO: boolean ♦ITERATING : State
+preinitializeQ ■wnQ ♦MUTATING; State
*stopFireQ ♦PAUSED: State
+terninateQ PREINITIAUZING: State
+wrapupQ ♦RESOLVING TYPES: State
+WRAPPING_TJP: State
A -_changeListeners: List
container: CompositeActor -_changeRequests: List
#_currentTime: double
-_container: CompositeActor
+DirectorO -_executjonUsteners: List
+Director(workspace: Workspace) .description: String ■_finishRequested: boolean
♦Directorjcontainer: CompositeActor. name: String) ♦getDescriptionO : String ■_iterationCount: irrt
+fireAt(actor: Actor, time: double) +getManager(): Manager ■_pauseReqested: boolean
+getCurrentTimeO: double State(descriPtion: String) ■_state: State
♦getNextlterationTimeO: double -_thread: PtolemyThread
+initialize{actor: Actor) types Resolved: boolean
+invalidateResolveaTypesO -_writeAccess Needed; boolean
+invalidateSchedute() ♦Managerf)
+needWriteAccess(): boolean +Manager(name: String)
+newReceiver(): Receiver ♦Managerfworkspace : Workspace, name : String)
+requestChange(change: ChangeRequest) +addChangeüstener{listener: Changeüstener)
+requestlnitiatizatjon(actof: Actor) +addExecutionListener(listener: ExecutionListener)
+setCurrentTime{newTirne: double)
♦executeO
+transfertnputs(port : lOPort): boolean ♦finishO
+transferoirtputs(port: lOPort): boolean
♦getlterationCountO: irrt
#_makeDtrectorOf(cast: CompositeActor) ♦getStateO: State
tfjMiteAccessRequiredQ: boolean ♦initializeO
+invalid ateResolvedTypesO
♦iterateO: boolean
notifyUstenersOfException(ex: Exception)
pauseO
«Interface» removeChangeUstenerflistener: Changeüstener)
Actor removeExecutionUstenerfJistener: ExecutionListener)
+requestChange(change: ChangeRequest)
♦requestinitiaJization(actor: Actor)
♦resolveTypesO
+getDirectorO: Director ♦resumeO
+getExecutiveDirectorO: Director
startRunO
+getManagenJ: Manager
♦wapupO
+inputPortListO: List
#_makeManagerOf<ca: CompositeActor)
+newReceiverO: Receiver
#_needWriteAccess(): boolean
+outputPortUstQ : List
l¥_nc<ifyUsteiwsOfCompletion()
6 #_notifyüstenersOfStateChangeO
*_processChangeRequests()
»_setState(newState: State)
0.1
CompositeActor
director: Director
-_manager: Manager
♦CompositeActort)
♦AtomicActorO ♦Compos KeActor(workspace: Workspace)
+AtomicActor(workspace: Workspace) ♦CompositeActortcontarner: ComposrteEntity, name : String)
♦AtomicActor(contäner: CompositeActor, name : String) ♦newlnsideReceiverO: Receiver
♦setDirectorfdi rector: Director)
♦setManagerfmanager: Manager)
«Interface»
Stream ExecutionListener ExecutionListener
FIGURE 8.12. Basic classes in the actor package that support execution.
166
method will be invoked exactly once per execution, at the end of the execution.
The terminateO method is provided as a last-resort mechanism to interrupt execution based on an
external event. It is not called during the normal flow of execution. It should be used only to stop run-
away threads that do not respond to more usual mechanism for stopping an execution.
An iteration is defined to be one invocation of prefire(), any number of invocation 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. While, the action methods in the execut-
able interface are executed in order during the normal flow of an iteration, the terminate() method can
be executed at any time, even during the execution of the other methods.
The preinitialize() method of each actor gets invoked exactly once. Typical actions of the preini-
tialize() method include creating receivers. The preinitialize() method cannot produce output data
since type resolution is typically not yet done. It also gets invoked prior to any static scheduling that
might occur in the domain, so it can change scheduling information.
The initialize() method of each actor gets invoked exactly once, much like the begin() method in
Ptolemy Classic. Typical actions of the initialize() method include creating and initializing private data
members. In domains that use typed ports and/or schedulers, type resolution and scheduling has not
been performed when initialize() is invoked. Thus, the initialize() method may define the types of the
ports and may set parameters that affect scheduling.
The prefire() method may be invoked multiple times during an execution, but only once per itera-
tion. The prefire() returns true to indicate that the actor is ready to fire. In other words, a return value of
true indicates "you can safely invoke my fire method," while a false value from prefire means "My
preconditions for firing are not satisfied. Call prefire again later when conditions have change." For
example, a dynamic dataflow actor might return false to indicate that not enough data is available on
the input ports for a meaningful firing to occur.
In opaque composite actors, the prefire() method is responsible for transferring data from the
opaque ports of the composite actor to the ports of the contained actors. See section 8.3.4 below.
The fire() method may be invoked multiple times during an iteration. In most domains, this
method defines the computation performed by the actor. Some domains will invoke fire() repeatedly
until some convergence condition is reached. Thus, fire() should not change the state of the actor.
Instead, update the state in postfire().
In some domains, the fire method initiates an open-ended computation. The stopFire() method
may be used to request that firing be ended and that the fire() method return as soon as practical.
The postfireO method will be invoked exactly once during an iteration, after all invocations of the
fire() method in that iteration. An actor may return false in postfire to request that the actor should not
be fired again. It has concluded its mission. However, a director may elect to continue to fire the actor
until the conclusion of its own iteration. Thus, the request may not be immediately honored.
The wrapup() method is invoked exactly once during the execution of a model, even if an excep-
tion causes premature termination of an execution. Typically, wrapup() is responsible for cleaning up
after execution has completed, and perhaps flushing output buffers before execution ends and killing
active threads.
The terminate() method may be called at any time during an execution, but is not necessarily
called at all. When terminate() is called, no more execution is important, and the actor should do every-
thing in its power to stop execution right away. This method should be used as a last resort if all other
mechanisms for stopping an execution fail.
167
8.3.1 Director
A director governs the execution of a composite entity. A manager governs the overall execution
of a model. An example of the use of these classes is shown in figure 8.13. In that example, a top-level
entity, EO, has an instance of Director, Dl, that serves the role of its local director. A local director is
responsible for execution of the components within the composite. It will perform any scheduling that
might be necessary, dispatch threads that need to be started, generate code that needs to be generated,
etc. In the example, Dl also serves as an executive director for E2. The executive director associated
with an actor is the director that is responsible for firing the actor.
A composite actor that is not at the top level may or may not have its own local director. If it has a
local director, then it defined to be opaque (isOpaque() returns true). In figure 8.13, E2 has a local
director and E3 does not. The contents of E3 are directly under the control of Dl, as if the hierarchy
were flattened. By contrast, the contents of E2 are under the control of D2, which in turn is under the
control of Dl. In the terminology of the previous generation, Ptolemy Classic, E2 was called a worm-
hole. In Ptolemy II, we simply call it a opaque composite actor. It will be explained in more detail
below in section 8.3.4.
We define the director (vs. local director or executive director) of an actor to be either its local
director (if it has one) or its executive director (if it does not). A composite actor that is not at the top
level has as its executive director the director of the container. Every executable actor has a director
except the top-level composite actor, and that director is what is returned by the getDirector() method
of the Actor interface (see figure 8.12).
When any action method is called ön an opaque composite actor, the composite actor will gener-
ally call the corresponding method in its local director. This interaction is crucial, since it is domain-
independent and allows for communication between different models of computation. When fire() is
called in the director, the director is free to invoke iterations in the contained topology until the stop-
ping condition for the model of computation is reached.
The postfireO method of a director returns false to stop its execution normally. It is the responsibil-
ity of the next director up in the hierarchy (or the manager if the director is at the top level) to conclude
the execution of this director by calling its wrapup() method.
The Director class provides a default implementation of an execution, although specific domains
may override this implementation. In order to ensure interoperability of domains, they should stick
fairly closely to the sequence.
M: Manager
FIGURE 8.13. Example application, showing a typical arrangement of actors, directors, and managers.
168
Two common sequences of method calls between actors and directors are shown in figure 8.14 and
8.15. These differ in the shaded areas, which define the domain-specific sequencing of actor firings. In
figure 8.14, the fire() method of the director selects an actor, invokes its prefireO method, and if that
returns true, invokes its fire() method some number of times (domain dependent) followed by its post-
fire() method. In figure 8.15, the fire() method of the director invokes the prefire() method of all the
actors before invoking any of their fire() methods.
When a director is initialized, via its initialize() method, it invokes initialize() on all the actors in
the next level of the hierarchy, in the order in which these actors were created. The wrapup() method
works in a similar way, deeply traversing the hierarchy. In other words, calling initialize() on a com-
posite actor is guaranteed to initialize in all the objects contained within that actor. Similarly for wra-
pup().
The methods prefire() and postfire(), on the other hand, are not deeply traversing functions. Call-
ing prefire() on a director does not imply that the director call prefire() on all its actors. Some directors
may need to call prefire() on some or all contained actors before being able to return, but some direc-
tors may not need to call prefire() on any contained objects at all. A director may even implement
short-circuit evaluation, where it calls prefire() on only enough of the contained actors to determine its
own return value. Postfire() works similarly, except that it may only be called after at least one suc-
cessful call to fire().
The fire() method is where the bulk of work for a director occurs. When a director is fired, it has
complete control over execution, and may initiate whatever iterations of other actors are appropriate
for the model of computation that it implements. It is important to stress that once a director is fired,
outside objects do not have control over when the iteration will complete. The director may not iterate
any contained actors at all, or it may iterate the contained actors forever, and not stop until terminate()
is called. Of course, in order to promote interoperability, directors should define a finite execution that
they perform in the fire() method.
In case it is not practical for the fire() method to define a bounded computation, the stopFire()
method is provided. A director should respond when this method is called by returning from its fire()
method as soon as practical.
In some domains, the firing of a director corresponds exactly to the sequential firing of the con-
tained actors in a specific predetermined order. This ordering is known as a static schedule for the
actors. Some domains support this style of execution. There is also a family of domains where actors
are associated with threads.
8.3.2 Manager
While a director implements a model of computation, a manager controls the overall execution of
a model. The manager interacts with a single composite actor, known as a top level composite actor.
The Manager class is shown in figure 8.12. Execution of a model is implemented by three methods,
execute(), run() and startRun(). The startRun() method spawns a thread that calls run(), and then imme-
diately returns. The run() method calls execute(), but catches all exceptions and reports them to listen-
ers (if there are any) or to the standard output (if there are no listeners).
More fine grain control over the execution can be achieved by calling initialize(), iterate(), and
wrapup() on the manager directly. The execute() method, in fact, calls these, repeating the call to iter-
ate() until it returns false. The iterate method invokes prefire(), fire() and postfire() on the top-level
composite actor, and returns false if the postfire() in the top-level composite actor returns false.
An execution can also be ended by calling terminate() or finishQ on the manager. The terminateQ
169
o
XI
o
O
c
X
w
170
1a
■ a.
«1 3
V a. a)
3 >
tx a>
2 CL
5 o
a. u
§=5
a. o
™T5
5 ■
= o §2
2*5
1 ■
i i
1 c
■o •
■3 / ■E
1
>
D
1 a
4
/o»\ I <D
/ a> \
\ o / / \ to
O
Q.
CO
r K \
A;
X J
■■SPJ» -mfeW £ o
| CD O / E ti
£ Ö m V o
t El O .fc:
8 a.
Q. O
■ / ? \ \ \ Q.T3 / / o \
.sQ
Q.-0
1 / \ -8 / '■ X n : \"°/
T ,
2 o
X
a 2
o ™
r /I
x
\ ;
/
CD j_
£CO °O
o
-o
o
o TI
O CO
Q- 6
ll CO / \ \ j O
c
T \ i| O
'•"f •
;:::ii53ör=:-:F':i
; \ v • c
t s
>>
£ •a
o
<c » / >• \ >* \ 'S
\
\ CO
« V Ä
2 S u
\ a° //
L.
/ n E
«=?1
u
£ 2
^ * \ I f t E
u
u
\ o
e
V
ii w
CD t3 3
s a.=
CD T3 a-
TI
A ■i
CO
\
c
.o
*, •,
t - 15
■S "
B- Si
\
\ §■53
n- c a-
Cfl 3
C O.
CO C
a 5 .E >
4 4 \4 to
K? •> ?
E
E 8°
1*
Q.E
8
a.
o o
1= t)
Q.-5
£
jD
Q.
<
,i \ ■www-«-«-«
oo
B« «•-«•-•v
«FT c
o 3
p I
cu "5
It- a.
~S
NO
11
.E -a
N «o
=5 0
c w
NO
1? I w
2
■H
= T3
JOPV JOPV
E jößeuew ajisodiuoo
|8A9| dO)
jopaiiQ BWsodmoo
anbedo
jopajjQ
171
method triggers an immediate halt of execution, and should be used only if other more graceful meth-
ods for ending an execution fail. It will probably leave the model in an inconsistent state, since it works
by unceremoniously killing threads. The finish() method allows the system to continue until the end of
the current iteration in the top-level composite actor, and then invokes wrapup(). Finish() encourages
actors to end gracefully by calling their stopFire() method.
Execution may also be paused between top-level iterations by calling the pauseQ method. This
method sets a flag in the manager and calls stopFire() on the top-level composite actor. After each top-
level iteration, the manager checks the flag. If it has been set, then the manager will not start the next
top-level iteration until after resume() is called. In certain domains, such as the process networks
domain, there is not a very well defined concept of an iteration. Generally these domains do not rely on
repeated iteration firings by the manager. The call to stopFire() requests of these domains that they sus-
pend execution.
8.3.3 ExecutionListener
The ExecutionListener interface provides a mechanism for a manager to report events of interest to
a user interface. Generally a user interface will use the events to notify the user of the progress of exe-
cution of a system. A user interface can register one or more ExecutionListeners with a manager using
the method addExecutionListener() in the Manager class. When an event occurs, the appropriate
method will get called in all the registered listeners.
Two kinds of events are defined in the ExecutionListener interface. A listener is notified of the
completion of an execution by the executionFinished() method. The executionError() method indicates
that execution has ended with an error condition. The managerStateChanged() indicates to the listener
that the manager has changed state. The new state can be obtained by calling getState() on the man-
ager.
A default implementation of the ExecutionListener interface is provided in the DefaultExecution-
Listener class. This class reports all events on the standard output.
172
P2 to P5. It does this by delegating to its local director, invoking its transferlnputs() method. It then
invokes the fire() method of D2, which in turn invokes the prefire(), fire(), and postfire() methods of
E4.
During its fire() method, E2 will invoke the fire() method of D2, which typically will fire the actor
E4, which may send a token via P6. Again, since the ports of E2 are opaque, that token goes only as far
as P3. The fire() method of E2 is then responsible for transferring that token to P4. It does this by dele-
gating to its executive director, invoking its transferOutputs() method.
The CompositeActor class delegates transfer of its inputs to its local director, and transfer of its
outputs to its executive director. This is the correct organization, because in each case, the director
appropriate to the model of computation of the destination port is the one handling the transfer. It can
therefore handle it in a manner appropriate to the receiver in that port.
Note that the port P3 is an output, but it has to be capable of receiving data from the inside, as well
as sending data to the outside. Thus, despite being an output, it contains a receiver. Such a receiver is
called an inside receiver. The methods of IOPort offer only limited access to the inside receivers (only
via the getInsideReceivers() method and getReceivers(retoio«), where relation is an inside linked
relation).
In general, a port may be both an input and an output. An opaque port of a composite opaque actor,
thus, must be capable of storing two distinct types of receivers, a set appropriate to the inside model of
computation, obtained from the local director, and a set appropriate to the outside model of computa-
tion, obtained from its executive director. Most methods that access receivers, such as hasToken() or
hasRoom(), refer only to the outside receivers. The use of the inside receivers is rather specialized,
only for handling composite opaque actors, so a more basic interface is sufficient.
FIGURE 8.16. An example of an opaque composite actor. EO and E2 both have local directors, not necessar-
ily implementing the same model of computation.
173
Static SchedulingDi rector
scheduler: Scheduler
+StaticSchedulingDirectorO
+StaticSchedulingDirector(workspace: Workspace) » DEFAULT SCHEDULER NAME : String
+StaticSchedulingDirector(container: CompositeActor, name : String) container: StaticScheduling Director
+getSchedulerO: Scheduler - valid: boolean
+invalidateSchedule() +Scheduter()
+isScheduleValid(): boolean +Scheduler(ws : Wortespace)
+setScheduler(scheduler: Scheduler) +isValid(): boolean
+setScheduleValid(valtd: boolean) +schedule(): Enumeration
+setValid(valid : boolean)
#_makeSchedulerOf(dir: StaticSchedulingDi rector)
#_schedule(): Enumeration
ProcessDirector
NoWyThread «Interface»
P/DcassReee/ver
-Jock: Object
locks: LinkedUst
+NotifyThread(k>ck : Object) +requestFini$h()
+NotifyThread(k>cks: UnkedUst) +requestPause(va!ue: boolean)
+mset() _____^^
FIGURE 8.17. UML static structure diagram for the actor.sched and actor.process packages.
174
Data Package
Authors:
Bart Kienhuis
Edward A. Lee
Xiaojun Liu
Neil Smyth
YuhongXiong
9.1 Introduction
The data package provides data encapsulation, polymorphism, parameter handling, an expression
language, and a type system. Figure 9.1 shows the key classes in the main package (subpackages will
be discussed later).
175
♦TokenO
add(rightArg : Token): Token
+addReverse(leftArg : Token): Token
j-convertftokcn; Token): Token
+dmde(divisor: Token): Token
*div)deReverse(dividend: Token): Token
♦getTypeO: Type
+isEquafTo(token: Token): BooleanToken
+modute(rightArg : Token): Token
ArrayToken
+moduloReverse<leftArg : Token): Token
+murttpry(rightFactor: Token): Token
■_vakje: TokenQ ♦murlipryReversefleftFactor: Token): Token
■_elemenfType: Type +oneO: Token
'An_yToken(value: TokenQ) .fields: Map +stringValueO: String
i-arrayValueO: TokenQ +RecordToken<labete : StringQ, values : TokenQ) +subtract(rightArg: Token): Token
►getElementfindex: int): Token +bbelSetO: Set +subtractReverse(leftAnj: Token): Token
•lengthQ :
"* +toStringO: String
+zeroO: Token
StringToken ObjectToken
ComplexToken LongToken
value: FtxPoint -„value: Complex - value: double _value: long _value: int
+FixToken<value: double, bits : int, intBtts : int) +ComptexTokenO +DoubleTokenO +LongTokenO ♦IntTokenO
+FixToken(vatue: double, precision: String) +Comptextoken(yaluB: Complex) DoubteToken(value: double) +LongToken^alue: long) +lntToken<value: int)
+FwToken(vakie: FoPoint) ♦DoubteToken(value: String) -LongTokenfvalue: String) +lntToken(value: String)
+convertToDoubleO: double
+scaleToPrecision(p: Precision): FaToken
+comptexMatrixO: ComptexQQ
+doubleMatrixO: doubleQQ
+getColumnCountO: int
IntMatrixToken Boolean MatrixToken +getElementAsToken(row: int, col: int): Token
♦getRowCountO: int
■_columnCount: int •_cokjmnCount: int ■HnWatTixO: intQQ
■_rowCount: int ■_rowCount: int bngMatrixO: tongQQ
-„value: intQQ -_value: booteanQQ +oneRightQ: Token
+lntMatrixTokenO ♦BooleanMatrixTokenO A
+lntMatrixToken(value: intQQ) +BooteanMatnxToken(value: booleanQQ)
+lntMatrixToken{value: intQ, rows; int, columns: int) ♦booleanMatrixO: booleanQQ
+getElementAt(row : int, col: int): int +getElementAt(row: int, column: in? : boolean
«■intArrayO: intQ
FIGURE 9.1. Static Structure Diagram (Class Diagram) for the classes in the data package.
176
same data. Each receiver is sent a reference to the same token. If the Token were not immutable,
then it would be necessary to clone the token for all receivers after the first one.
Second, we use tokens to parameterize objects, and parameters have mutual dependencies. That is,
the value of a parameter may depend on the value of other parameters. The value of a parameter is
represented by an instance of Token. If that token were allowed to change value without notifying
the parameter, then the parameter would not be able to notify other parameters that depend on its
value. Thus, a mutable token would have to implement a publish-and-subscribe mechanism so that
parameters could subscribe and thus be notified of any changes. By making tokens immutable, we
greatly simplify the design.
Finally, having our Tokens immutable makes them similar in concept to the data wrappers in Java,
like Double, Integer, etc., which are also immutable.
An ObjectToken contains a reference to an arbitrary Java object created by the user. Since the user
may modify the object after the token is constructed, ObjectToken is an exception to immutability.
Moreover, the getValue() method returns a reference to the object. That reference can be used to mod-
ify the object. Although ObjectToken could clone the object in the constructor and return another clone
in getValue(), this would require the object to be cloneable, which severely limits the use of the Object-
Token. In addition, even if the object is cloneable, since the default implementation of clone() only
makes a shallow copy, it is still not enough to enforce immutability. In addition, cloning a large object
could be expensive. For these reasons, the ObjectToken does not enforce immutability, but rather relies
on the cooperation from the user. Violating this convention could lead to unintended non-determinism.
For matrix tokens, immutability requires the contained matrix (Java array) to be copied when the
token is constructed, and when the matrix is returned in response to queries such as intMatrix(), dou-
bleMatrix(), etc. This is because arrays are objects in Java. Since the cost of copying large matrices is
non-trivial, the user should not make more queries than necessary. The getElementAt() method should
be used to read the contents of the matrix.
ArrayToken is a token that contains an array of tokens. All the element tokens must have the same
type, but that type can be any token type, including the type of the ArrayToken itself. That is, we can
have an array of arrays. ArrayToken is different from the MatrixTokens in that MatrixTokens contain
primitive data, such as int, double, while ArrayToken contains Ptolemy Tokens. MatrixTokens are very
efficient for storing two dimensional primitive data, while ArrayToken offers more flexibility in type
specifications.
RecordToken contains a set of labeled values, like the structure in the C language. The values can
be arbitrary tokens, and they are not required to have the same type. ArrayToken and RecordToken will
be discussed in more detail in the Type System chapter.
9.3 Polymorphism
9.3.1 Polymorphic Arithmetic Operators
One of the goals of the data package is to support polymorphic operations between tokens. For
this, the base Token class defines methods for the primitive arithmetic operations, which are add(),
multiplyO, subtract(), divide(), modulo() and equals(). Derived classes override these methods to pro-
vide class specific operation where appropriate. The objective here is to be able to say, for example,
a.add(b)
177
where a and b are arbitrary tokens. If the operation a + b makes sense for the particular tokens, then
the operation is carried out and a token of the appropriate type is returned. If the operation does not
make sense, then an exception is thrown. Consider the following example
then
a.add(b)
gives a new DoubleToken with value 7.2,
a.add(c)
gives a new StringToken with value "5Hello", and
a.modulo(c)
throws an exception. Thus in effect we have overloaded the operators +,-,*, /, %, and ==.
It is not always immediately obvious what is the correct implementation of an operation and what
the return type should be. For example, the result of adding an integer token to a double-precision
floating-point token should probably be a double, not an integer. The mechanism for making such
decisions depends on a type hierarchy that is defined separately from the class hierarchy. This type
hierarchy is explained in detail below.
The token classes also implement the methods zero() and one() which return the additive and mul-
tiplicative identities respectively. These methods are overridden so that each token type returns a token
of its type with the appropriate value. For numerical matrix tokens, zero() returns a zero matrix whose
dimension is the same as the matrix of the token where this method is called; and one() returns the left
identity, i.e., it returns an identity matrix whose dimension is the same as the number of rows of the
matrix of the token. Another method oneRight() is also provided in numerical matrix tokens, which
returns the right identity, i.e., the dimension is the same as the number of columns of the matrix of the
token.
Since data is transferred between entities using Tokens, it is straightforward to write polymorphic
actors that receive tokens on their inputs, perform one or more of the overloaded operations and output
the result. For example an add actor that looks like this:
+,
might contain code like:
We call such actors data polymorphic to contrast them from domain polymorphic actors, which are
actors that can operate in multiple domains. Of course, an actor may be both data and domain polymor-
178
phic.
Array Record
NaT
179
token types.
Two of the types, Numerical and Scalar, are abstract. They cannot be instantiated. This is indicated
in the type lattice by italics.
Type conversion is done by the static method convert() in the token classes. This method converts
the argument into an instance of the class implementing this method. For example, DoubleToken.con-
vert(Token token) converts the specified token into an instance of DoubleToken. The convert() method
can convert any token immediately below it in the type hierarchy into an instance of its own class. If
the argument is higher in the type hierarchy, or is incomparable with its own class, convert() throws an
exception. If the argument to convert() is already an instance of its own class, it is returned without any
change.
The implementation of the add(), subtract(), multiply(), divide(), modulo(), and equals() methods
requires that the type of the argument and the implementing class be comparable in the type hierarchy.
If this condition is not met, these methods will throw an exception. If the type of the argument is lower
than the type of the implementing class, then the argument is converted to the type of the implementing
class before the operation is carried out.
The implementation is more involved if the type of the argument is higher than the implementing
class, in which case, the conversion must be done in the other direction. Since the convert () method
only knows how to convert types lower in the type hierarchy up, the operation must take place in the
class of the argument. Furthermore, since many of the supported operations are not commutative, for
example, "Hello" + "world" is not the same as "world" + "Hello", and 3-2 is not the same as
2-3, the implementation of the arithmetic operations cannot simply call the same method on the class
of the argument. Instead, a separate set of methods must be used. These methods are addReverse(),
subtractReverse(), multiplyReverse(), divideReverse(), and moduloReverse(). The equality check is
always commutative so no equalsReverse() is needed. Under this setup, a.add(b) means a+b, and
a.addReverse(b) means b+a, where a and b are both tokens. If, for example, when a.add(b) is invoked
and the type of b is higher than a, the add() method of a will automatically call b.addReverse(a) to
carry out the addition.
For scalar and matrix tokens, methods are also provided to convert the content of the token into
another numeric type. In ScalarToken, these methods are intValue(), longValue(), doubleValue(), fix-
Value(), and ComplexValue(). In MatrixToken, the methods are intMatrix(), longMatrix(), doubleMa-
trix(), fixMatrix(), and ComplexMatrix(). The default implementation in these two base classes just
throw an exception. Derived classes override the methods if the corresponding conversion is lossless,
returning a new instance of the appropriate class. For example, IntToken overrides all the methods
defined in ScalarToken, but DoubleToken does not override intValue(). A double cannot, in general, be
losslessly converted to an integer.
9.3.3 Limitations
As of this writing, the following issues remain open:
• For numerical matrix tokens, only the add() and addReverse() methods are supported; other arith-
metic operations are not implemented yet.
180
can refer to other variables. A parameter is identical to a variable, but realized by instances of the
Parameter class, which is derived from Variable and adds no functionality. See figure 9.3.
The reason for having two classes with identical interfaces and functionality, Variable and Parame-
ter, is that their intended uses are different. Parameters are meant to be visible to the end user of a com-
ponent, whereas variables are meant to operate behind the scenes, unseen. A GUI, for example, might
present parameters for editing, but not variables.
9.4.1 Values
The value of a variable can be specified by a token passed to a constructor, a token set using the
setToken() method, or an expression set using the setExpression() method.
When the value of a variable is set by setExpression(), the expression is not actually evaluated
until you call getToken() or getType(). This is important, because it implies that a set of interrelated
expressions can be specified in any order. Consider for example the sequence:
Notice that the expression for v3 cannot be evaluated when it is set because v2 and vl do not yet have
values. But there is no problem because the expression is not evaluated until getToken() is called.
Obviously, an expression can only reference variables that are added to the scope of this variable
before the expression is evaluated (i.e., before getToken() is called). Otherwise, getToken() will throw
an exception. By default, all variables contained by the same container, and those contained by the
container's container, are in the scope of this variable. Thus, in the above, all three variables are in each
other's scope because they belong to the same container. This is why the expression "vl + v2" can be
evaluated.
A variable can also be reset. If the variable was originally set from a token, then this token is
placed again in the variable, and the type of the variable is set to equal that of the token. If the variable
was originally given an expression, then this expression is placed again in the variable (but not evalu-
ated), and the type is reset to null. The type will be determined when the expression is evaluated or
when type resolution is done.
9.4.2 Types
Ptolemy II, in contrast to Ptolemy Classic, does not have a plethora of type-specific parameter
classes. Instead, a parameter has a type that reflects the token it contains. You can constrain the allow-
able types of a parameter or variable using the following mechanisms:
• You can require the variable to have a specific type. Use the setTypeEquals() method.
• You can require the type to be at most some particular type in the type hierarchy (see the Type Sys-
tem chapter to see what this means).
181
«Interface» «Interface»
StttabU Value Ustemr
i-
!+addValueüstener<l: ValueListener) i+valueChanged( variable: Variable);
Typubl* i+getExpression(): String
:+remove ValueListenerfl: ValueListener)] Generated from PtParser.ijt
;+set Expression (expression : String) usingJJTree and JavaCC
PtParser
«Interface»
Nod*
•ParameterO
■Parameter(w: Workspace)
■Parameter(container: NamedObj, name: String)
■jjtOpenO
■Parameter(container: NamedObj, name : String, token : Token)
■jjtCloseO
+ijtSetParent(parent: Node)
■jjtGetParentO: Node
+jjtAddChild(child : Node, index: int)
•jjtGetChild(index : int): Node
+jjtGetNumCWIdren(): int
+displayParseTree(prefix : String)
+evaluateParseTree(): ptolemy.data.Token
+toString(prefix : String): String
#_reso)veNode(): ptolemy.data.Token
A
The root node is the root of Q^
the parse tree.
TA -but is also the base class
Generated by for all other node types
JavaCC
ASTPtMatrlxConstructNodt ASTPtFuncttonNode
ASTPtMethodCallNode ASTPtLtafNode
#_nRows: int ASTPtUnaryNod« #_funcName : String ASTPtSumNode
ft nColumns: int #_methodName: String #_isArrayRef: boolean # var: Variable
182
• You can constrain the type to be the same as that of some other object that implements the Type-
able interface.
• You can constrain the type to be at least that of some other object that implements the Typeable
interface.
Except for the first type constraint, these are not checked by the Variable class. They must be checked
by a type resolution algorithm, which is implemented in the graph package.
The type of the variable can be specified in a number of ways, all of which require the type to be
consistent with the specified constraints (or an exception will be thrown):
• It can be set directly by a call to setTypeEquals(). If this call occurs after the variable has a value,
then the specified type must be compatible with the value. Otherwise, an exception will be thrown.
Type resolution will not change the type set through setTypeEquals() unless the argument ofthat
call is null. If this method is not called, or called with a null argument, type resolution will resolve
the variable type according to all the type constraints. Note that when calling setTypeEquals() with
a non-null argument while the variable already contains a non-null token, the argument must be a
type no less than the type of the contained token. To set type of the variable lower than the type of
the currently contained token, setToken() must be called with a null argument before setType-
Equals().
• Setting the value of the variable to a non-null token constrains the variable type to be no less than
the type of the token. This constraint will be used in type resolution, together with other con-
straints.
• The type is also constrained when an expression is evaluated. The variable type must be no less
than the type of the token the expression is evaluated to.
• If the variable does not yet have a value, then the type of a variable may be determined by type res-
olution. In this case, a set of type constraints is derived from the expression of the variable (which
presumably has not yet been evaluated, or the type would be already determined). Additional type
constraints can be added by calls to the setTypeAtLeast() and setTypeSameAs() methods.
Subject to specified constraints, the type of a variable can be changed at any time. Some of the type
constraints, however, are not verified until type resolution is done. If type resolution is not done, then
these constraints are not enforced. Type resolution is normally done by the Manager that executes a
model.
The type of the variable may change when setToken() or setExpression() is called.
• If no expression, token, or type has been specified for the variable, then the type becomes that of
the current value being set.
• If the variable already has a type, and the value can be converted losslessly into a token ofthat
type, then the type is left unchanged.
• If the variable already has a type, and the value cannot be converted losslessly into a token ofthat
type, then the type is changed to that of the current value being set.
If the type of a variable is changed after having once been set, the container is notified of this by call-
ing its attributeTypeChangedO method. If the container does not allow type changes, it should throw
an exception in this method. If the value is changed after having once been set, then the container is
notified of this by calling its attributeChanged() method. If the new value is unacceptable to the con-
tainer, it should throw an exception. The old value will be restored.
183
The token returned by getToken() is always of the type given by the getType() method. This is not
necessarily the same as the type of the token that was inserted via setToken(). It might be a distinct
type if the contained token can be converted losslessly into one of the type given by getType(). In rare
circumstances, you may need to directly access the contained token without any conversion occurring.
To do this, use getContainedToken().
9.4.3 Dependencies
Expressions set by setExpression() can reference any other variable that is within scope. By
default, the scope includes all variables contained by the same container, and all variables contained by
the container's container. In addition, any variable can be explicitly added to the scope of a variable by
calling addToScope().
When an expression for one variable refers to another variable, then the value of the first variable
obviously depends on the value of the second. If the value of the second is modified, then it is impor-
tant that the value of the first reflects the change. This dependency is automatically handled. When you
call getToken(), the expression will be reevaluated if any of the referenced variables have changed val-
ues since the last evaluation.
9.5 Expressions
Ptolemy II includes a simple but extensible expression language. This language permits operations
on tokens to be specified in a scripting fashion, without requiring compilation of Java code. The
expression language can be used to define parameters in terms of other parameters, for example. It can
also be used to provide end-users with actors that compute a user-specified expression that refers to
inputs and parameters of the actor.
1. The Ptolemy II expression language uses operator overloading, unlike Java. Although we fully agree that the
designers of Java made a good decision in omitting operator overloading, our expression language is used in sit-
uations where compactness of expressions is extremely important. Expressions often appear in crowded dialog
boxes in the user interface, so we cannot afford the luxury of replacing operators with method calls. It is more
compact to say "2*(PI + 2i)" rather than "2.multiply(PI.add(2i))," although both will work in the expression lan-
guage.
184
Conditionals. The language is an expression language, not an imperative language with sequentially
executed statements. Thus, it makes no sense to have the usual if. . .then. . .else. . . construct.
Such a construct in Java (and most imperative languages) depends on side effects. However, Java does
have a functional version of this construct (one that returns a value). The syntax for this is
boolean ? valuel : value2
If the boolean is true, valuel is returned, else value2 is returned. The Ptolemy II expression lan-
guage uses this same syntax.
Comments. Anything inside /*...*/ is ignored.
Variables. Expressions can contain references by name to parameters within the scope of the expres-
sion. Consider a parameter P with container X which is in turn contained by Y The scope of an expres-
sion for P includes all the parameters contained by Xand Y The scope is implemented as an instance of
NamedList, which provides a symbol table. Note that a class derived from Parameter may define scope
differently.
Constants. If an identifier is encountered in an expression that does not match a parameter in the scope,
then it might be a constant that has been registered as part of the language. By default, the constants PI,
pi, E, e, true, false, i, andy are registered, but as we will see later, this can easily be extended. (The con-
stants i andy are complex numbers with value equal to 0.0 + l.Oi). In addition, literal string constants
are supported. Anything between quotes, "...", is interpreted as a string constant. Numerical values
without decimal points, such as "10" or "-3" are integers. Numerical values with decimal points, such
as "10.0" or "3.14159" are doubles. Integers followed by the character "1" (el) or "L" are long integers;
Arrays. Arrays are specified with curly brackets. E.g., "{1,2,3}" is an array of integers, while "{"x",
"y", " z"}" is an array of strings. An array is an ordered list of tokens of any type, with the only con-
straint being that the elements all have the same type. Thus, for example, "{1, 2.3}" is illegal because
the first element is an integer and the second is a double. The elements of the array can be given by
expressions, as in the example "{2*pi, 3*pi}." Arrays can be nested; for example, "{{1,2}, {3, 4, 5}}"
is an array of arrays of integers.
Matrices. Matrices are specified with square brackets, using commas to separate row elements and
semicolons to separate rows. E.g., "[1,2, 3; 4, 5, 5+1]" gives a two by three integer matrix (2 rows and
3 columns). Note that an array or matrix element can be given by an expression. A row vector can be
given as "[1, 2, 3]" and a column vector as "[1; 2; 3]". Some Matlab-style array constructors are sup-
ported. For example, "[1:2:9]" gives an array of odd numbers from 1 to 9, and is equivalent to "[1,3, 5,
7, 9]." Similarly, "[1:2:9; 2:2:10]" is equivalent to "[1, 3, 5, 7, 9; 2, 4,6, 8, 10]."
Matrix references. Reference to matrices have the form "name(n, m)" where name is the name of a
matrix variable in scope (or a constant matrix), n is the row index, and m is the column index. Index
numbers start with zero, as in Java, not 1, as in Matlab.
Records. A record token is a composite type where each element is named, and each element can have
a distinct type. Records are delimited by curly braces, with each element given a name. For example,
" {a=l, b=" f oo"}" is a record with two elements, named "a" and "b", with values 1 (an integer) and
"foo" (a string), respectively. The value of a record element can be an arbitrary expression, and records
can be nested (an element of a record token may be a record token).
Functions. The language includes an extensible set of functions, such as sin(), cos(), etc. The functions
that are built in include all static methods of the java.lang.Math class and the ptolemy.data.expr.Utility-
185
Functions class. This can easily be extended by registering another class that includes static methods.
The functions currently available are shown in figures 9.4 and 9.5, with the argument types and return
types1.
One slightly subtle function is the random() function. It takes no arguments, and hence is written
"random ()". It returns a random number. However, this function is evaluated only when the expres-
sion within which it appears is evaluated. The result of the expression may be used repeatedly without
re-evaluating the expression. The random() function is not called again. Thus, for example, if the value
parameter of the Const actor is set to "random ()", then its output will be a random constant; i.e., it
will not change on each firing.
Methods. Every element and subexpression in an expression represents an instance of Token (or more
likely, a class derived from Token). The expression language supports invocation of any method of a
given token, as long as the arguments of the method are of type Token and the return type is Token (or
a class derived from Token, or something that the expression parser can easily convert to a token, such
as a string, double, int, etc.). The syntax for this is {tokeri).name{args), where name is the name of the
method and args is a comma-separated set of arguments. Each argument can itself be an expression.
Note that the parentheses around the token are not required, but might be useful for clarity. As an
example, the Array Token class has a getElement(int) method, which can be used as follows:
{l, 2, 3}.getElement(1)
This returns the integer 2. Another useful function of array token is illustrated by the following exam-
ple:
{1, 2, 3}.length()
[1, 2; 3, 4; 5, 6].getRowCount()
gaussian double, double double Gaussian random variable with the specified mean, and
standard deviation
gaussian double, double, int, int double matrix Gaussian random matrix with the specified mean, stan-
dard deviation, rows, and columns
FIGURE 9.5. Functions available to the expression language from the ptolemy.data.expr.Utility-
Functions class. This class is still at a preliminary stage, and the function it provides will grow
over time.
1. At this time, in release 1.0, the types must match exactly for the expression evaluator to work. Thus, "sin(l)"
fails, because the argument to the sin() function is required to be a double.
186
function argument type(s) return type description
FIGURE 9.4. Functions available to the expression language from the java.lang.Math class.
187
which returns 3, and
[1, 2; 3, 4; 5, 6].getColumnCount()
[1, 2; 3, 4; 5, 6].toArray()
which returns {1, 2, 3, 4, 5, 6}. The latter function can be particularly useful for creating arrays using
Matlab-style syntax. For example, to obtain an array with the integers from 1 to 100, you can enter:
[1:1:100] .toArrayO
The get() method of RecordToken accesses a record field, as in the following example:
which returns 1.
Types. The types currently supported in the language are boolean, complex, fixed point, double, int,
long, arrays, matrices, records, and string. Note that there is no float or byte. Use double or int instead.
A long is defined by appending an integer with "1" (lower case L) or "L", as in Java. A complex is
defined by appending an "i" or a "j" to a double for the imaginary part. This gives a purely imaginary
complex number which can then leverage the polymorphic operations in the Token classes to create a
general complex number. Thus "2 + 3i" will result in the expected complex number. A fixed point
number is defined using the "fix" function, as will be explained below in section 9.6.4.
The Token classes from the data package form the primitives of the language. For example the
number 10 becomes an IntToken with the value 10 when evaluating an expression. Normally this is
invisible to the user. The expression language is object-oriented, of course, so methods can be invoked
on these primitives. A sophisticated user, therefore, can make use of the fact that "10" is in fact an
object to invoke methods ofthat object.
In particular, the convert() method of the Token class might be useful, albeit a bit subtle in how it
is used. For example:
(1.2).convert(1)
creates a DoubleToken with value 1.2, and then invokes its convert() method with argument 1, which
is an IntToken. The convert() method of DoubleToken converts the argument to a DoubleToken, so the
result of this expression is (somewhat surprisingly) 1.0. The convert() method supports only lossless
type conversion (see section 9.3.2). Lossy conversion has to be done explicitly via a function call.
The expression language is extensible. The basic mechanism for extension is object-oriented. The
reflection package in Java is used to recognize method invocations and user-defined constants. We also
expect the language to grow over time, so this description should be viewed as a snapshot of its capa-
bilities.
188
9.5.2 Limitations
The expression language has a rich potential, and only some of this potential has been realized.
Here are some of the current limitations:
• The class ptolemy.data.util.UtilityFunctions containing the utility functions has not yet been fully
written.
• Functions in the math package need to be supported in much the same way that java.lang.Math is
supported.
• Method calls are currently only allowed on tokens in the ptolemy.data package.
• Statements are not supported. It is not clear that they ever will be, since currently the expression
language is strictly functional, and converting it to imperative semantics could drastically change
its flavor.
We represent a fixed point value in the expression language using the following format:
Thus, a fixed point value of 5.375 that uses 8 bit precision of which 4 bits are used to represent the
integer part can be represented as:
fix(5.375, 8, 4)
These functions are implemented by the FixPointFunctions class in the ptolemy.data.expr package.
The value can also be a matrix of doubles. The values are rounded, yielding the nearest value repre-
sentable with the specified precision. If the value to represent is out of range, then it is saturated, mean-
ing that the maximum or minimum fixed point value is returned, depending on the sign of the specified
value. For example,
fix(5.375, 8, 3)
will yield 3.968758, the maximum value possible with the (8/3) precision.
In addition to the fix() function, the expression language offers a quantize() function. The argu-
ments are the same as those of the fix() function, but the return type is a DoubleToken or DoubleMa-
trixToken instead of a FixToken or FixMatrixToken. This function can therefore be used to quantize
189
double-precision values without ever explicitly working with the fixed-point representation.
9.6.2 FixPoint
The FixPoint type is written from scratch and it uses at it's core the Java package Biglnteger to
represent the finite precision value that is captured in a FixPoint. The advantage of using the Biglnte-
ger package is that it makes this FixPoint implementation truly platform independent and furthermore,
it doesn't put any restrictions on the maximal number of bits allowed to represent a value.
The FixPoint data type uses an innerclass to represent the Biglnteger. The innerclass is used to
keep track of errors as they may occur. These errors are that an overflow or rounding condition
occurred. The innerclass keeps the Biglnteger and error messages together. Besides the Biglnteger
package, the FixPoint class also relies on the BigDecimal package when converting values from Fix-
Points to doubles and vice versa.
The precision used in the FixPoint data type is represented by class Precision. This class does the
parsing and validation of the various specification styles we want to support. It stores a precision into
two separate integers. One number represents the number of integer bits, and the other number repre-
sents the number of fractional bits.
A FixPoint is created by supplying a Biglnteger and a Precision. This seems to be an odd way of
creating FixPoints. That is because the preferred way to create a FixPoint is to use one of the static
quantizer functions in class Quantizer. By selecting either the round or the truncate method, a different
quantizer is chosen to convert a double into a FixPoint.
To change the precision of a FixPoint, you have to use the specific implementation of round and
truncate. If the change of precision can be accommodated, the FixPoint value isn't changed. If the
change cannot be accommodated, then precision is changed and an overflow or quantization error may
occur. The way the overflow error is handled is determined by a mode switch.
mode = 0, Saturate: The fixed point value is set, depending on its sign, equal to the Maximum or
Minimum value possible with the new given precision.
mode = 1, Zero Saturate: The fixed point value is set equal to zero.
9.6.3 FixToken
A FixToken is realized by encapsulating a value of the FixPoint type and by implementing all
methods of super class Token using the methods available for FixPoint. Because FixToken is derived
from Token and ScalarToken, it can consequently be used in every data type polymorphic actors. In a
similar way data type FixMatrixToken is created. It encapsulates an two-dimensional array of fixed
point values.
The FixToken class implements all the methods of Token and ScalarToken. However, one specific
methods has been added: convertToDouble. The convertToDouble method converts a fixed point value
into a double representation. The getDouble method defined by Token cannot be used since the con-
190
FixPointFuncfions
PtParaer
MatrixToken
»reqisterFunctionClasslnewClassName: String)
FixToken FixMatrixToken
NxM
1..1
FixPoint
•_precision: Precision
value: Biglnteger Precision
1..1 «Uses»
Quantizar
«Uses»
«Uses»
191
version from a FixPoint to a double is not lossless and an exception will be thrown when tried.
192
Appendix E: Expression Evaluation
The evaluation of an expression is done in two steps. First the expression is parsed to create an
abstract syntax tree (AST) for the expression. Then the AST is evaluated to obtain the token to be
placed in the parameter. In this appendix, "token" refers to instances of the Ptolemy II token classes, as
opposed to lexical tokens generated when an expression is parsed.
Note that JavaCC generates top-down parsers, or LL(k) in parser terminology. This is different
from yacc (or bison) which generates bottom-up parsers, or more formally LALR(l). The JavaCC file
also differs from yacc in that it contains both the lexical analyzer and the grammar rules in the same
file.
The input expression string is first converted into lexical tokens, which the parser then tries to
match using the production rules for the grammar. Each time the parser matches a production rule it
creates a node object and places it in the abstract syntax tree. The type of node object created depends
on the production rule used to match that part of the expression. For example, when the parser comes
upon a multiplication in the expression, it creates an ASTPtProductNode.
The parser takes as input a string, and optionally a NamedList of parameters to which the input
expression can refer. That NamedList is the symbol table. If the parse is successful, it returns the root
node of the abstract syntax tree (AST) for the given string. Each node object can contain a token,
which represents both the type and value information for that node. The type of the token stored in a
node, e.g. DoubleToken, IntToken etc., represents the type of the node. The data value contained by the
token is the value information for the node. In the AST as it is returned from PtParser, the token types
and values are only resolved for the leaf nodes of the tree.
One of the key properties of the expression language is the ability to refer to other parameters by
name. Since an expression that refers to other parameters may need to be evaluated several times
(when the referred parameter changes), it is important that the parse tree does not need to be recreated
every time. When an identifier is parsed, the parser first checks whether it refers to a parameter within
the current scope. If it does it creates a ASTPtLeafNode with a reference to that parameter. Note that a
leaf node can have a parameter or a token. If it has a parameter then when the token to be stored in this
node is evaluated, it is set to the token contained by the parameter. Thus the AST tree does not need to
be recreated when a referenced parameter changes as upon evaluation it will just get the new token
stored in the referenced parameter. If the parser was created by a parameter, the parameter passes in a
193
reference to itself in the constructor. Then upon parsing a reference to another parameter, the parser
takes care of registering the parameter that created it as a listener with the referred parameter. This is
how dependencies between parameters get registered. There is also a mechanism built into parameters
to detect dependency loops.
If the identifier does not refer to a parameter, the parser then checks if it refers to a constant regis-
tered with the parser. If it does it creates a node with the token associated with the identifier. If the
identifier is neither a reference to a parameter or a constant, an exception is thrown.
(
3^DIntToken(2) <5ÜD DoubleToken(3.5)
(TeaT) DoubleToken(3.5)
&l«tToken(2)
194
parser for that purpose. The classes automatically searched are java.lang.Math and
ptolemy.data.expr.UntilityFunctions. To register another class to be searched when a function call is
parsed, call registerFunctionClass() on the parser with the full name of the class to be added to the
function search path.
When a parameter has been informed that another parameter it references changed, the parameter
re-evaluates the parse tree for the expression to obtain the new value when getToken() is called on the
parameter. It is not necessary to parse the expression again as the relevant leaf node stores a reference
to the referenced parameter, not the token contained in that parameter. Thus at any use, the value of a
parameter is up to date.
195
children nodes must have tokens of type BooleanToken. The resolved type of the node is also Boolean-
Token.
ASTPtRelationalNode. This is created when one of the relational operators(!=, =, >, >=, <, <=) is
parsed. The resolved type of the token of this node is BooleanToken. The "=" and "!=" operators are
overloaded via the equals() method in the token classes. The other operators are only valid on Scalar-
Tokens. Currently the numbers are converted to doubles and compared, this needs to be adjusted to
take account of Longs.
ASTPtUnaryNode. This is created when a unary negation operator(!, ~, -) is parsed. Type resolution
occurs in the node, with the resulting type being the same as the token in the only child of the node.
ASTPtArrayConstructNode. This is created when an array construction sub-expression is parsed.
ASTPtMatrixConstructNode. This is created when a matrix construction sub-expression is parsed.
ASTPtRecordConstructNode. This is created when a record construct sub-expression is parsed.
E.2.2 Extensibility
The Ptolemy II expression language has been designed to be extensible. The main mechanisms for
extending the functionality of the parser is the ability to register new constants with it and new classes
containing functions that can be called. However it is also possible to add and invoke methods on
tokens, or to even add new rules to the grammar, although both of these options should only be consid-
ered in rare situations.
To add a new constant that the parser will recognize, invoke the method registerConstant(String
name, Object value) on the parser. This is a static method so whatever constant you add will be visible
to all instances of PtParser in the Java virtual machine. The method works by converting, if possible,
whatever data the object has to a token and storing it in a hashtable indexed by name. By default, only
the constants in java.lang.Math are registered.
To add a new Class to the classes searched for a a function call, invoke the method register-
Class(String name) on the parser. This is also a static method so whatever class you add will be
searched by all instances of PtParser in the JVM. The name given must be the fully qualified name of
the class to be added, for example "java.lang.Math". The method works by creating and storing the
Class object corresponding to the given string. If the class does not exist an exception is thrown. When
a function call is parsed, an ASTPtFunctionNode is created. Then when the parse tree is being evalu-
ated, the node obtains a list of the classes it should search for the function and, using reflection,
searches the classes until it either finds the desired function or there are no more classes to search. The
classes are searched in the same order as they were registered with the parser, so it is better to register
those classes that are used frequently first. By default, only the classes java.Lang.Math and
ptolemy.data.expr.UtilityFunctions are searched.
196
10
Graph Package
Authors: Jie Liu
YuhongXiong
10.1 Introduction
The Ptolemy II kernel provides extensive infrastructure for creating and manipulating clustered
graphs of a particular flavor. Mathematical graphs, however, are simpler structures that consist of
nodes and edges, without hierarchy. Edges link only two nodes, and therefore are much simpler than
the relations of the Ptolemy II kernel. Moreover, in mathematical graphs, no distinction is made
between multiple edges that may be adjacent to a node, so the ports of the Ptolemy II kernel are not
needed. A large number of algorithms have been developed that operate on mathematical graphs, and
many of these prove extremely useful in support of scheduling, type resolution, and other operations in
Ptolemy II. Thus, we have created the graph package, which provides efficient data structures for
mathematical graphs, and collects algorithms for operating on them. At this time, the collection of
algorithms is nowhere near as complete as in some widely used packages, such as LEDA. But this
package will serve as a repository for a growing suite of algorithms.
The graph package provides basic infrastructure for both undirected and directed graphs. Acyclic
directed graphs, which can be used to model complete partial orders (CPOs) and lattices, are also sup-
ported with more specialized algorithms.
The graphs constructed using this package are lightweight, designed for fast implementation of
complex algorithms more than for generality. This makes them maximally complementary to the clus-
tered graphs of the Ptolemy II kernel, which emphasize generality. A typical use of this package is to
construct a graph that represents the topology of a CompositeEntity, run a graph algorithm, and extract
useful information from the result. For example, a graph might be constructed that represents data pre-
cedences, and a topological sort might be used to generate a schedule. In this kind of application, the
hierarchy of the original clustered graph is flattened, so nodes in the graph represent only opaque enti-
ties.
The architecture of this package is somewhat different from LEDA, in part because of the exist-
197
ence of the complementary kernel package. Unlike LEDA, there are no dedicated classes representing
nodes and edges in the graph. The nodes in this package are represented by arbitrary instances of the
Java Object class, and the graph topology is stored in a structure similar to an adjacency list.
The facilities that currently exist in this package are those that we have had most immediate need
for. Since the type system of Ptolemy II requires extensive operations on lattices and CPOs, support for
these is better developed than for other types of graphs.
«Interface»
Graph InequalityTerm
Inequality
InequalitySolver
cpo : CPO
DiracttdAcyclicGra ph -Just: ArrayUst
-_Chst: Hashtabte
■_bottom : Object +lnequalitySotver(cpo: CPO)
•^closure: booleanpö +addlnequality(ineq: Inequality)
•Jop: Object +bottomVariablesO: Iterator
♦DirectedAcyclicGrpahO ♦sorveGreatestO : boolean
♦DirectedAcyclicGraph(nodeCount: int) ♦solveLeastO: boolean
♦topologlcalSortO: ObjectfJ ■topVariabtesO: Iterator
+topologicalSort(o : ObjectQ): ObjectfJ -mn satisfied) nequalitiesO : Iterator
♦varlablesQ : Iterator
198
quality Term interface and the Inequality class model inequality constraints over the CPO. The details
of the constraints will be discussed later. The InequalitySolver class provides an algorithm to solve a
set of constraints. This is used by the Ptolemy II type system, but other uses may arise.
The implementation of the above classes is not synchronized. If multiple threads access a graph or
a set of constraints concurrently, external synchronization will be needed.
10.2.1 Graph
This class models a simple undirected graph. Each node in the graph is represented by an arbitrary
Java object. The method add() is used to add a node to the graph, and addEdge() is used to connect two
nodes in the graph. The arguments of addEdge() are two Objects representing two nodes already added
to the graph. To mirror a topology constructed in the kernel package, multiple edges between two
nodes are allowed. Each node is assigned a node ID based on the order the nodes are added. The trans-
lation from the node ID to the node Object is done by the _getNodeObject() method, and the transla-
tion in the other direction is done by _getNodeId(). Both methods are protected. The node ID is only
used by this class and the derived classes, it is not exposed in any of the public interfaces. The topol-
ogy is stored in the Vector _graph. The indexes of this Vector correspond to node IDs. Each entry of
_graph is also a Vector, in which a list of node IDs are stored. When an edge is added by calling add-
Edge() with the first argument having node ID /' and the second having node IDy, an Integer containing
j is added to the Vector at the z-th entry of _graph. For example, if the graph in figure 10.2(a) is con-
nected using the sequence of calls: addEdge(nO, nl); addEdge(nO, n2); addEdge(n2, nl), where nO, nl,
n2 are Objects representing the nodes with IDs 0, 1, 2, respectively, then the data structure will be in
the form of 10.2(b).
Note that in this undirected graph, the data format is dependent on the order of the two arguments
in the addEdge() calls. Since each edge is stored only once, this data structure is not exactly the same
as the adjacency list for undirected graphs, but it is quite similar. This structure is designed to be used
by subclasses that model directed graphs, as well as by this base class. If it appears awkward when
adding algorithms for undirected graph, a new class that derives from Graph may be added in the
future to model undirected graph exclusively, in which case, Graph will provide the basic support for
both undirected and directed graphs.
_graPh
0 1 2
2 1
(a) (b)
FIGURE 10.2. An undirected graph
199
a higher node, as opposed to from source to sink, head to tail, etc. The terms lower and higher con-
forms with the convention of the graphical representation of CPOs and lattices (the Hasse diagram), so
they can be consistently used on both directed graphs and CPOs.
The computation of transitive closure is implemented in this class. The transitive closure is inter-
nally stored as a 2-D boolean matrix, whose indexes correspond to node IDs. The entry (i,j) is true if
and only if there exists a path from the node with ID / to the node with IDy. This matrix is not exposed
at the public interface; instead, it is used by this class and its subclass to do other operations. Once the
transitive closure matrix is computed, graph operations like reachableNodes can be easily accom-
plished.
200
interface defines the operations on a term. If a term consists of a single variable, the value of the vari-
able can be set to a specific element of the underlying CPO. The isSettable() method queries whether
the value of a term can be set. It returns true if the term is a variable, and false if it is a constant or a
function. The setValue() method is used to set the value for variable terms. The getValue() method
returns the current value of the term, which is a constant if the term consists of a single constant, the
current value of a variable if the term consists of a single variable, or the evaluation of a function based
on the current value of the variables if the term is a function. The getVariables() method returns all the
variables contained in a term. This method is used by the inequality solver.
The Inequality class contains two Inequality Terms, a lesser term and the greater term. The isSatis-
fied() method tests whether the inequality is satisfied over the specified CPO based on the current
value of the variables. It returns true if the inequality is satisfied, and false otherwise.
The InequalitySolver class implements an algorithm to determine satisfiability of a set of inequal-
ity constraints and to find the solution to the constraints if they are satisfiable. This algorithm is
described in [73]. It is basically an iterative procedure to update the value of variables until all the con-
straints are satisfied, or until conflicts among the constraints are found. Some limitations on the type of
constraints apply for the algorithm to work. The method addlnequality() adds an inequality to the set of
constraints. Two methods solveLeast() and solveGreatest() can be used to solve the constraints. The
former tries to find the least solution, while the latter attempts to find the greatest solution. If a solu-
tion is found, these methods return true and the current value of the variables is the solution. The
method unsatisfiedlnequalities() returns an enumeration of the inequalities that are not satisfied based
on the current value of the variables. It can be used after solveLeast() or solveGreatest() return false to
find out which inequalities cannot be satisfied after the algorithm runs. The bottom Variables() and top-
VariablesO methods return enumerations of the variables whose current values are the bottom or the
top element of the CPO.
201
IOPort outputPort = (IOPort)outPorts.next() ;
Iterator inPorts =
outputPort.deepConnectedlnPortList 0 .iterator!) ;
while (inPorts.hasNext()) {
IOPort inputPort = (IOPort)inPorts.next();
Actor higherActor = (Actor)inputPort.getContainerf);
if (dag.contains(higherActor)) {
dag.addEdgedowerActor, higherActor) ;
}
return dag.topologicalSort 0 ,-
}
202
// Return an array containing this variable term,
public InequalityTermn getVariables () {
InequalityTermt] variable = new InequalityTerm[1] ;
variable[0] = this;
return variable;
}
// Variable terms are settable.
public boolean isSettableO {
return true;
}
// Set the value of this variable to the specified String.
// Not checking the type of the specified Object before casting for simplicity.
public void setValue(Object e) throws IllegalActionException {
_value = (String)e;
}
private String _value = null;
}
As a simple example, the following Java class constructs the 4-point CPO of figure 10.3, forms a
set of constraints with three inequalities, and solves for both the least and greatest solutions. The ine-
qualities are a < w; b < a; b < z, where w and z are constants in figure 2.3, and a and b are variables.
// An example of forming and solving inequality constraints,
public class TestSolver {
public static void main(String!] arv) {
// construct the 4-point CPO in figure 2.3.
CPO cpo = constructCPOO;
203
DirectedAcyclicGraph cpo = new DirectedAcyclicGraphO
cpo.add("w");
cpo.add("x");
cpo.add("y");
cpo.add("z") ;
cpo.addEdgeC'x", "w")
cpo.addEdgeC'y", "w")
cpo.addEdge("z", "x")
cpo.addEdge("z", "y")
return cpo;
204
11
Type System
Authors: Edward A. Lee
YuhongXiong
Contributors:
Steve Neuendorffer
11.1 Introduction
The computation infrastructure provided by the basic actor classes is not statically typed, i.e., the
IOPorts on actors do not specify the type of tokens that can pass through them. This can be changed by
giving each IOPort a type. One of the reasons for static typing is to increase the level of safety, which
means reducing the number of untrapped errors [16].
In a computation environment, two kinds of execution errors can occur, trapped errors and
untrapped errors. Trapped errors cause the computation to stop immediately, but untrapped errors may
go unnoticed (for a while) and later cause arbitrary behavior. Examples of untrapped errors in a general
purpose language are jumping to the wrong address, or accessing data past the end of an array. In
Ptolemy II, the underlying language Java is quite safe, so errors rarely, if ever, cause arbitrary behav-
ior. However, errors can certainly go unnoticed for an arbitrary amount of time. As an example, figure
11.1 shows an imaginary application where a signal from a source is downsampled, then fed to a fast
Fourier transform (FFT) actor, and the transform result is displayed by an actor. Suppose the FFT actor
Source
—[ Down-
sampler
—i FFT
—I Display
205
can accept ComplexToken at its input, and the behavior of the Downsampler is to just pass every sec-
ond token through regardless of its type. If the Source actor sends instances of ComplexToken, every-
thing works fine. But if, due to an error, the Source actor sends out a StringToken, then the StringToken
will pass through the sampler unnoticed. In a more complex system, the time lag between when a
token of the wrong type is sent by an actor and the detection of the wrong type may be arbitrarily long.
In languages without static typing, such as Lisp and the scripting language Tel, safety is achieved
by extensive run-time checking. In Ptolemy II, if we imitated this approach, we would have to require
actors to check the type of the received tokens before using them. For example, the FFT actor would
have to verify that the every received token is an instance of ComplexToken, or convert it to Complex-
Token if possible. This approach gives the burden of type checking to the actor developers, distracting
them from their development effort. It also relies on a policy that cannot be enforced by the system.
Furthermore, since type checking is postponed to the last possible moment, the system does not have
fail-stop behavior, so a system may generate an error only after running for an extended period of time,
as figure 11.1 shows. To make things worse, an actor may receive tokens from multiple sources. If a
token with the wrong type is received, it might be hard to identify from which source the token comes.
All these make debugging difficult.
To address this and other issues discussed later, we added static typing to Ptolemy II. This
approach is consistent with Ptolemy Classic. In general-purpose statically-typed languages, such as
C++ and Java, static type checking done by the compiler can find a large fraction of program errors. In
Ptolemy II, execution of a model does not involve compilation. Nonetheless, static type checking can
correspondingly detect problems before any actors fire. In figure 11.1, if the Source actor declares that
its output port type is String, meaning that it will send out StringTokens upon firing, the static type
checker will identify this type conflict in the topology.
In Ptolemy II, because models are not compiled, static typing alone is not enough to ensure type
safety at run-time. For example, even if the above Source actor declares its output type to be Complex,
nothing prevents it from sending out a StringToken at run-time. So run-time type checking is still nec-
essary. With the help of static typing, run-time type checking can be done when a token is sent out
from a port. I.e., the run-time type checker checks the token type against the type of the output port.
This way, a type error is detected at the earliest possible time, and run-time type checking (as well as
static type checking) can be performed by the system instead of by the actors.
One design principle of Ptolemy II is that data type conversions that lose information are not
implicitly performed by the system. In the data package, a lossless data type conversion hierarchy,
called the type lattice, is defined (see figure 9.2). In that hierarchy, the conversion from a lower type to
a higher type is lossless, and is supported by the token classes. This lossless conversion principle also
applies to data transfer. This means that across every connection from an output port to an input, the
type of the output must be the same as or lower than the type of the input. This requirement is called
the type compatibility rule. For example, an output port with type Int can be connected to an input port
with type Double, but a Double to Int connection will generate a type error during static type checking.
This behavior is different from Ptolemy Classic, but it should be useful in many applications where the
users do not want lossy conversion to take place without their knowledge.
As can be seen from above examples, when a system runs, the type of a token sent out from an out-
put port may not be the same as the type of the input port the token is sent to. If this happens, the token
must be converted to the input port type before it is used by the receiving actor. This kind of run-time
type conversion is done transparently by the Ptolemy II system (actors are not aware it). So the actors
can safely cast the received tokens to the type of the input port. This makes the actor development eas-
ier.
206
Ousterhout [68] argues that static typing discourages reuse.
In Ptolemy II, typing does apply some restrictions on the interaction of actors. Particularly, actors can-
not be interconnected arbitrarily if the type compatibility rule is violated. However, the benefit of typ-
ing should far outweigh the inconvenience caused by this restriction. In addition, the automatic run-
time type conversion provided by the system permits ports of different types to be connected (under
the type compatibility rule), which partly relaxes the restriction caused by static typing. Furthermore,
there is one important component in Ptolemy that brings much flexibility to the actor interface, the
type-polymorphic actors.
Type-polymorphic actors (called polymorphic actors in the rest of this chapter) are actors that can
accept multiple types on their ports. For example, the Downsampler in figure 11.1 does not care about
the type of token going through it; it works with any type of token. In general, the types on some or all
of the ports of a polymorphic actor are not rigidly defined to specific types when the actor is written, so
the actor can interact with other actors having different types, increasing reusability. In Ptolemy Clas-
sic, the ports on polymorphic actors whose types are not specified are said to have ANYTYPE, but
Ptolemy II uses the term undeclared type, since the type on those ports cannot be arbitrary in general.
The acceptable types on polymorphic actors are described by a set of type constraints. The static type
checker checks the applicability of a polymorphic actor in a topology by finding specific types for
them that satisfy the type constraints. This process is called type resolution, and the specific types are
called the resolved types.
In addition to ports, Parameters, which are often used to configure actors, are also typed objects.
By defining a uniform interface for setting up type constraints, Ptolemy II supports type constraints
between Parameters and ports, as well as among ports. This extends the range of type checking to
some of the internal states of actors.
Static typing and type resolution have other benefits in addition to the ones mentioned above.
Static typing helps to clarify the interface of actors and makes them more manageable. Just as typing
may improve run-time efficiency in a general-purpose language by allowing the compiler to generate
specialized code, when a Ptolemy system is synthesized to hardware, type information can be used for
efficient synthesis. For example, if the type checker asserts that a certain polymorphic actor will only
receive IntTokens, then only hardware dealing with integers needs to be synthesized.
To summarize, Ptolemy II takes an approach of static typing coupled with run-time type checking.
Lossless data type conversions during data transfer are automatically executed. Polymorphic actors are
supported through type resolution.
11.2 Formulation
11.2.1 Type Constraints
In a Ptolemy II topology, the type compatibility rule imposes a type constraint across every con-
nection from an output port to an input port. It requires that the type of the output port, outType, be the
same as the type of the input port, inType, or less than inType under the type lattice in figure 9.2. I.e.,
outType < inType (2)
207
This guarantees that information is not lost during data transfer. If both the outType and inType are
declared, the static type checker simply checks whether this inequality is satisfied, and reports a type
conflict if it is not.
In addition to the above constraint imposed by the topology, actors may also impose constraints.
This happens when one or both of the outType and inType is undeclared, in which case the actor con-
taining the undeclared port needs to describe the acceptable types through type constraints. All the type
constraints in Ptolemy II are described in the form of inequalities like the one in (2). If a port has a
declared type, its type appears as a constant in the inequalities. On the other hand, if a port has an
undeclared type, its type is represented by a variable, called the type variable, in the inequalities. The
domain of the type variable is the elements of the type lattice. The type resolution algorithm resolves
the undeclared types subject to the constraints. If resolution is not possible, a type conflict error will be
reported. As an example of the inequality constraints, consider figure 11.2.
The port on actors Al has declared type int; the ports on A3 and A4 have declared type double;
and the ports on A2 have their types undeclared. Let the type variables for the undeclared types be a,
ß, and Y, the type constraints from the topology are:
int < a
double < ß
Y < double
Now, assume A2 is a polymorphic adder, capable of doing addition for integer, double, and complex
numbers, and the requirement is that it does not lose precision during the operation. Then the type con-
straints for the adder can be written as:
cc<Y
ß<Y
Y ^ Complex
The first two inequalities constrain the output precision to be no less than input, the last one
requires that the data on the adder ports can be converted to Complex losslessly.
These six inequalities form the complete set of constraints and are used by the type resolution
algorithm to solve for cc, ß, and y.
This inequality formulation is inspired by the type inference algorithm in ML [60]. There, equali-
ties are used to represent type constraints. In Ptolemy II, the lossless type conversion hierarchy natu-
rally implies inequality relation among the types. In ML, the type constraints are generated from
program constructs. In a heterogeneous graphical programming environment like Ptolemy II, the sys-
tem does not have enough information about the function of the actors, so the actors must present their
doubl^~^\
208
type information by either declaring the type on their port, or specify a set of type constraints to
describe the acceptable types on the undeclared ports.
This formulation converts type resolution into a problem of solving a set of inequalities. An effi-
cient algorithm is available to solve constraints in finite lattices [73], which is described in the appen-
dix through an example and in figure 11.3. This algorithm finds the set of most specific types for the
undeclared types in the topology that satisfy the constraints, if they exist.
As mentioned earlier, the static type checker flags a type conflict error if the type compatibility
rule is violated on a certain connection. There are other kind of type conflicts indicated by one of the
following:
• The set of type constraints are not satisfiable.
• Some type variables are resolved to NaT.
• Some type variables are resolved to an abstract type, such as Numerical in the type hierarchy.
The first case can happen, for example, if the port on actor Al in figure 11.2 has declared type
Complex. The second case can happen if an actor does not specify any type constraints on an unde-
clared output port. This is due to the nature of the type resolution algorithm where it assigns all the
undeclared types to NaT at the beginning. If the type constraints do not restrict a type variable to be
greater than NaT, it will stay at NaT after resolution. The third case is considered a conflict since an
General
Array Record
NaT
FIGURE 11.3. The Type Lattice
209
abstract type does not correspond to an instantiable token class.
To avoid the second case above, any output port must either have a declared type, or some con-
straints to force its type to be greater than NaT. This requirement should be easily satisfied on most
actors. A situation that needs some attention is the source actor. A source actor cannot leave its output
port type unconstrained. One way to cope with this is to declare the type at a time after the type infor-
mation is known, but prior to type resolution. For example, if the output data is determined by a
parameter set by the user, the parameter can be evaluated during the initialization phase of the execu-
tion and the port type can be declared at the end of the initialization, which precedes type resolution.
210
In the type lattice in figure 11.3, array and record types are incomparable with all the base types,
except the top and the bottom elements of the lattice. Note that the lattice nodes Array and Record
actually represent an infinite number of types, so the type lattice becomes infinite.
The order relation between two array types is that type B is less than type A if the element type of
B is less than the element type of A. This is a recursive definition if the element types are structured
types. For example, Int Array < Double Array, Int Array Array < Double Array Array, where Int Array
Array is an array of array. And Int Array and Double Array Array are incomparable.
The order relation between two record types follow the standard depth subtyping and width sub-
typing relations [16]. In depth subtyping, a record type C is a subtype of a record type D if the type of
some fields of C is a subtype of the corresponding fields in D. In width subtyping, a record with more
fields is a subtype of a record with less fields. For example, we have:
{name: String, value: Int} < {name: String, value: Double}
{name: String, value: Double, id: Int} < {name: String, value: Double}
Here, we use the {label: type, label: type,...} syntax to denote record types.
Type constraints can be specified between the element type of a structured type and the type of a
Ptolemy object. For example, a type constraint can specify that the type of a port is no less than the
type of the elements of an Array Token.
11.4 Implementation
11.4.1 Implementation Classes
All the classes for representing the types and the type lattice are under the datatype package, as
shown in figure 11.4. The Type interface defines the basic operations on a type. BaseType contains a
type-safe enumeration of all the primitive types. The type UNKNOWN corresponds to the bottom ele-
ment of the type lattice, it represents a type variable that can be resolved to any type. Array Type and
RecordType are derived from an abstract class StructuredType. Each type has a convert() method to
convert a token lower in the type lattice to one of its type. For base types, this method just calls the
same method in the corresponding tokens. For structured types, the conversion is done within the con-
crete structured type classes.
The Typeable interface defines a set of methods to set type constraints between typed objects. It is
implemented by the Variable class in the data.expr package and the TypedlOPort class in the actor
package. TypeConstant encapsulate a constant type. It implements the Inequality Term interface and
can be used to set up type constraints between a typed object and a constant type.
In the actor package, the Actor interface, the AtomicActor, CompositeActor, IOPort and IORela-
tion classes are extended with TypedActor, TypedAtomicActor, TypedCompositeActor, TypedlOPort
and TypedlORelation, respectively, as shown in figure 11.5. The container for TypedlOPort must be a
ComponentEntity implementing the TypedActor interface, namely, TypedAtomicActor or TypedCom-
positeActor. The container for TypedAtomicActor and TypedCompositeActor must be a TypedCom-
positeActor. TypedlORelation constrains that TypedlOPort can only be connected with TypedlOPort.
TypedlOPort has a declared type and a resolved type. Undeclared type is represented by Base-
Type.UNKNOWN. If a port has a declared type that is not BaseType.UNKNOWN, the resolved type
will be the same as the declared type.
211
11.4.2 Type Checking and Type Resolution
Static type checking is done in the checkTypes() method of TypedCompositeActor. This method
finds all the connection within the composite by first finding the output ports on deep contained enti-
ties, and then finding the deeply connected input ports to those output ports. Transparent ports are
ignored for type checking. For each connection, if the types on both ends are declared, static type
«Interface»
Type
Type Lattice
n type conversion hierarchy
-Jattice: TheTypeLattice
+i$Compatible(token : Token): boolean
+isConstant(): boolean 0.1 +compare(t1 : Token, t2 : Token) :.int
+isEqualTo(type: Type): boolean +compare(t1 : Type. t2 : Type): int
+islnstantialble(): boolean +!attice(): graph.CPO
+isSubstitutionlnstance(type: Type):
boolean .
StructuredType
BaseType
■»UNKNOWN : BaseType
■»BOOLEAN : BaseTvpe initialize(type: Type)
updateTypefst: StructuredType)
■»BOOLEAN MATRIX : BaseType
#_compare(t: StructuredType): int
COMPLEX: BaseType
»COMPLEX MATRIX: BaseType #_getRepresentative(): StructuredType
#_greatestt_owerBound(t: StructuredType): StructuredType
#_leastUpperBound(t: StructuredType): StructuredType
»DOUBLE MATRIX : BaseType
»FIX: BaseType
■FIX MATRIX: BaseType
X
UNT : BaseType
■»INT MATRIX: BaseType
»LONG: BaseType
»LONG MATRIX: BaseType
»MATRIX: BaseType
AT : BaseTvpe
UMERICAL : BaseTvi
iBJECT : BaseTvpe
»SCALAR ; BaseTvpe
»STRING: BaseTvpe
GENERAL: Ba ArrayType RecordType
name: String
•_declaredElem entType: Type „fields: Map
■_elementType: Type _representative: RecordType
•_e!ementTypeTerm : ElementTypeTerm +RecofdType(labels : StringQ, types: Typefl)
• representative: An~avType +get(label: String): Type
►ArrayType(elementType: Type) oetTvpeTerm(label: String): InequalityTerm
KgetElementTypeO: Type
»■getElementTypeTermO : InequalityTerm
«Interface»
InequalityTerm
T «Interface»
Typeabie
212
checking is performed using the type compatibility rule. If the composite contains other opaque Typed-
CompositeActors, this method recursively calls the checkTypes() method of the contained actors to
perform type checking down the hierarchy. Hence, if this method is called on the top level TypedCom-
positeActor, type checking is performed through out the hierarchy.
If a type conflict is detected, i.e., if the declared type at the source end of a connection is greater
than or incomparable with the type at the destination end of the connection, the ports at both ends of
«Interface*
Actor
<]•-
«Interface»
TyptdActor ComposlteActor
-O +typeConstraintUstO ■ Lid
A —
TypfldAtomlcActor TypedCompositeActor
+TypedAtomicActor() ♦TypedCompositeActorO
♦TypedAtomicActor(workspace: Workspace) +TypedCompositeActor(workspace: Workspace)
+TypedAtomicActor(container: TypedCompositeActor, name: String) +TypedCompositeActor(container: TypedCompositeActor, i : String)
♦checkTypesO : List ^
0..1
«Interface»
Typaabh
~A""
TypedtOPort
»TYPE: int
■„constraints: List
■jdedaredType: Type
-_resolvedType: Type
-jtypeTemi: TypeTerm
•JypeUsteners: List
TypedlOPortO
+TypedlOPort(container: ComponentEntity, name : String)
+TypedlOPort(container: ComponentEntity. name : String, islnput: boolean, teOutput: boolean)
♦addTypeUstenertlistener: TypeUstener)
»removeTypeüstenerflistener: TypeUstener) ^^^
TypedlORelation
Typed lORelationO
■TypedlOReIation(workspace: Workspace)
rTypedlORelation(container: TypedCompositeActor. name: String)
FIGURE 11.5. Classes in the actor package that support type checking.
213
the connection are recorded and will be returned in a List at the end of type checking. Note that type
checking does not stop after detecting the first type conflict, so the returned List contains all the ports
that have type conflicts. This behavior is similar to a regular compiler, where compilation will gener-
ally continue after detecting errors in the source code.
The TypedActor interface has a typeConstraints() method, which returns the type constraints of
this actor. For atomic actors, the type constraints are different in different actors, but the TypedAtomi-
cActor class provides a default implementation, which is that the type of any input port with unde-
clared type must be less than or equal to the type of any undeclared output port. Ports with declared
types are not included in the default constraints. If all the ports have declared type, no constraints are
generated. This default works for most of the control actors such as commutator, multiplexer, and the
Downsampler in figure 11.1. In addition, the typeConstraints() method also collects all the constraints
from the contained Typeable objects, which are TypedlOPorts and Variables.
The typeConstraints() method in TypedCompositeActor collects all the constraints within the com-
posite. It works in a similar fashion as the checkTypes() method, where it recursively goes down the
containment hierarchy to collect type constraints of the contained actors. It also scans all the connec-
tions and forms type constraints on connections involving undeclared types. As with checkTypes(), if
this method is called on the top level container, all the type constraints within the composite are
returned.
The Manager class has a resolveTypes() method that invokes type checking and resolution. It uses
the Inequality Solver class in the graph package to solve the constraints. If type conflicts are detected
during type checking or after type resolution, this method throws TypeConflictException. This excep-
tion contains a List of Typeable objects where type conflicts occur. The resolveTypes() method is
called inside Manager after all the mutations are processed. If TypeConflictException is thrown, it is
caught within the Manager and an ExecutionEvent is generated to pass the exception information to
the user interface.
Run-time type checking is done in the send() method of TypedlOPort. The checking is simply a
comparison of the type of the token being sent with the resolved type of the port. If the type of the
token is less than or equal to the resolved type, type checking is passed, otherwise, an IllegalActionEx-
ception is thrown.
Type conversion, if needed, is also done in the send() method. The type of the destination port is
the resolved type of the port containing the receivers that the token is sent to. If the token does not have
that type, the convert() method on that type is called to perform the conversion.
214
parameter.setTypeEquals(new ArrayType(BaseType.UNKNOWN));
ArrayType arrayType = (ArrayType)parameter.getType();
InequalityTerm elementTerm = arrayType.getElementTypeTerm();
port.setTypeAtLeast(elementTerm);
These kinds of constraints appear in source actors such as Clock and Pulse, where the actor outputs a
sequence of values specified by an Array Token.
In some actors, monotonic functions can help specify less straightforward constraints. The type
resolution algorithm allows the lesser term to be a monotonic function when searching for the most
specific types. That is, constraints in the form f(oc) < b are admitted, where f(cc) is a monotonic func-
tion of a and b can be a constant or a variable. An example of this appears in the Absolute Value actor
in the actor library. Here, one of the type constraints is: If the input type is not Complex, the output type
is the same as the input type, otherwise, the output type is Double. This constraint can be expressed as
f(inputType) < outputType, where
This function is implemented by an inner class FunctionTerm of Absolute Value that implements
InequalityTerm. The evaluation is done in the getValue() method of InequalityTerm as:
215
rectional references make the implementation complicated, and consistency is hard to maintain. A bet-
ter way is to always clone the structured type when its container is cloned, or when constructing a new
instance of StructuredType. This is done in the data package. This implementation incurs some redun-
dant cloning, but the overhead is small.
A variable type can be updated to another type, provided that the new type is compatible with the
variable type. For example, a type variable a can be updated to any type, a Array can be updated to lnt
Array. However, a Array cannot be updated to Int. If a variable type can be updated to a new type, the
new type is called a substitution instance of the variable type. This term is borrowed from type litera-
ture. Formally, a type is a substitution instance of a variable type if the former can be obtained by sub-
stituting the type variables of the latter to another type. The method isSubstitutionInstance() in Type
does this check.
The updateType() method in StructuredType is used to change the variable element type of a struc-
tured type. For example, if the types of two ports are lnt Array and a Array respectively, and a type
constraint is that the second port is no less than the type of the first, that is, lnt Array < a Array, the
type resolution algorithm will change the type of the second port to lnt Array. This step cannot be done
by simply changing the type reference in the second port to an instance of lnt Array, since type con-
straints may be set up between a and another typed objects. Instead, updateType() only changes the
type reference for a to Int.
11.5 Examples
11.5.1 Polymorphic Downsampler
In figure 11.1, if the Downsampler is designed to do downsampling for any kind of token, its type
constraint is just sampler In < sampler Out, where samplerIn and sampler Out are the types of the input
and output ports, respectively. The default type constraints works in this case. Assuming the Display
actor just calls the toStringQ method of the received tokens and displays the string value in a certain
window, the declared type of its port would be General. Let the declared types on the ports of FFT be
Complex, the The type constraints of this simple application are:
sourceOut < samplerln
samplerIn < samplerOut
samplerOut < Complex
Complex < General
Where sourceOut represents the declared type of the Source output. The last constraint does not
involve a type variable, so it is just checked by the static type checker and not included in type resolu-
tion. Depending on the value of sourceOut, the ports on the Downsampler would be resolved to differ-
ent types. Some possibilities are:
• If sourceOut = Complex, the resolved types would be samplerln = samplerOut = Complex.
• If sourceOut = Double, the resolved types would be samplerln = samplerOut = Double. At run-
time, DoubleTokens sent out from the Source will be passed to the DownSampler unchanged.
Before they leave the Downsampler and are sent to the FFT actor, they are converted to Complex-
Tokens by the system. The ComplexToken output from the FFT actor are instances of Token,
which corresponds to the General type, so they are transferred to the input of the Display without
change.
216
• IfsourceOut = String, the set of type constraints do not have a solution, a typeConflictException
will be thrown by the static type checker.
217
the type of the corresponding ports.
H Array ToSequence
218
Appendix F: The Type Resolution Algorithm
The type resolution algorithm starts by assigning all the type variables the bottom element of the
type hierarchy, NaT, then repeatedly updating the variables to a greater element until all the constraints
are satisfied, or when the algorithm finds that the set of constraints are not satisfiable. The kind of ine-
quality constraints the algorithm can determine satisfiability are the ones with the greater term (the
right side of the inequality) being a variable, or a constant. The algorithm allows the left side of the
inequality to contain monotonic functions of the type variables, but not the right side. The first step of
the algorithm is to divide the inequalities into two categories, Cvar and Ccnst. The inequalities in Cvar
have a variable on the right side, and the inequalities in Ccnst have a constant on the right side. In the
example of figure 11.2, Cvar consists of:
Int < a
Double < ß
a<y
ß<y
And Ccnst consists of:
Y < Double
Y ^ Complex
The repeated evaluations are only done on Cvar, Ccnst are used as checks after the iteration is fin-
ished, as we will see later. Before the iteration, all the variables are assigned the value NaT, and Cvar
looks like:
Int < a(NaT)
Double < $(NaT)
a(NaT) < y(NaT)
$(NaT) < y(NaT)
Where the current value of the variables are inside the parenthesis next to the variable.
At this point, Cvar is further divided into two sets: those inequalities that are not currently satis-
fied, and those that are satisfied:
Not-satisfied Satisfied
Int < a{NaT) a{NaT) < -tfNaT)
Double < $(NaT) $(NaT) < y{NaT)
Now comes the update step. The algorithm takes out an arbitrary inequality from the Not-satisfied
set, and forces it to be satisfied by assigning the variable on the right side the least upper bound of the
values of both sides of the inequality. Assuming the algorithm takes out int < a(NaT), then
a = IntvNaT = Int (3)
After a is updated, all the inequalities in Cvar containing it are inspected and are switched to either
the Satisfied or Not-satisfied set, if they are not already in the appropriate set. In this example, after
this step, Cvar is:
Not-satisfied Satisfied
Double < $(NaT) Int < a(Inf)
a(Int)<y(NaT) $(NaT) < y(NaT)
The update step is repeated until all the inequalities in Cvar are satisfied. In this example, ß and y
219
will be updated and the solution is:
a = Int, ß = Y = Double
Note that there always exists a solution for Cvar. An obvious one is to assign all the variables to
the top element, General, although this solution may not satisfy the constraints in Const. The above
iteration will find the least solution, or the set of most specific types.
After the iteration, the inequalities in Ccnst are checked based on the current value of the variables.
If all of them are satisfied, a solution to the set of constraints is found.
This algorithm can be viewed as repeated evaluation of a monotonic function, and the solution is
the fixed point of the function. Equation (3) can be viewed as a monotonic function applied to a type
variable. The repeated update of all the type variables can be viewed as the evaluation of a monotonic
function that is the composition of individual functions like (3). The evaluation reaches a fixed point
when a set of type variable assignments satisfying the constraints in Cvar is found.
Rehof and Mogensen [73] proved that the above algorithm is linear time in the number of occur-
rences of symbols in the constraints, and gave an upper bound on the number of basic computations. In
our formulation, the symbols are type constants and type variables, and each constraint contains two
symbols. So the type resolution algorithm is linear in the number of constraints.
220
12
Plot Package
Authors:
Edward A. Lee
Christopher Hylands
Contributors:
Lukito Muliadi
William Wu
Jun Wu
12.1 Overview
The plot package provides classes, applets, and applications for two-dimensional graphical display
of data. It is available in a stand-alone distribution, or as part of the Ptolemy II system.
There are several ways to use the classes in the plot package:
You can use one of several domain-polymorphic actors in a Ptolemy II model to plot data that is
provided as an input to the actor.
You can invoke an executable, ptplot, which is a shell script, to plot data in a local file or on the
network (via a URL).
You can invoke an executable, histogram, which is a shell script, to plot histograms of data in a
local file or on the network (via a URL)
You can invoke an executable, pxgraph, which is a shell script, to plot data that is stored in an
ascii or binary format compatible with the older program pxgraph, which is an extension of
David Harrison's xgraph.
You can invoke a Java application, such as PlotMLApplication, by using the j ava program that is
included in your Java distribution.
You can use an existing applet class, such as PlotMLApplet, in an HTML file. The applet parame-
ter dataurl specifies the source of plot data. You do not even have to have Ptplot installed on
221
your server, since you can always reference the Berkeley installation.
• You can create new classes derived from applet, frame, or application classes to customize your
plots. This allows you to completely control the placement of plots on the screen, and to write Java
code that defines the data to be plotted.
• PlotML is an XML extension for plot data. Its syntax is similar to that of HTML. XML (extensible
markup language) is an internet language that is growing rapidly in popularity.
• An older, simpler textual syntax for plot data is also provided, although in the long term, that syn-
tax is unlikely to be maintained (it will not necessarily be expanded to support new features). For
simple data plots, however, it is adequate. Using it for applets has the advantage of making it pos-
sible to reference a slightly smaller jar file containing the code, which makes for more responsive
applets. Also, the data files are somewhat smaller.
• A binary file format used by pxgraph, is supported by classes in the compat package. Formatting
information in pxgraph (and in the compat package) is provided by command-line arguments,
rather than being included with the binary plot data, exactly as in the older program. Applets spec-
ify these command-line arguments as an applet parameter (pxgraphargs).
ptplot
with no arguments should open a window that looks like that in figure 12.1. You can also specify a file
to plot as a command-line argument. To find out about command-line options, type
Sampl« plot
FIGURE 12.1. Result of invoking ptplot on the command line with no arguments.
222
ptplot -help
The ptplot command is a shell script that invokes the following equivalent command:
Since it is a shell script, it will work on Unix machines and Windows machines that have Cygwin1
installed. In the same directory are three Windows versions that do not require Cygwin, ptplot .bat,
histogram.bat, and pxgraph.bat, which you can invoke by typing into the DOS command
prompt, for example,
ptplot.bat
1. The beta 20 version of the Cygwin Toolkit is a freely available package available from http://sourceware.cyg-
nus. com/cygwin/
2. Ghostview is available http://www.cs.wisc.edu/~ghost/gsview/
223
invoked from applets (which have no menu bar) and from the standalone scripts:
Sampl« plot
KS Ptolemy plot
iFle B* Spec*
Sample plot
FIGURE 12.2. To zoom in, drag the left mouse button down and to the right to draw a box around the region
you wish to see in more detail.
224
then dragging the right mouse button. Figure 12.4 shows the result of modifying one of the datasets
(the one in red on a color display). The modification is carried out by freehand drawing, although con-
siderable precision is possible by zooming in. Use the Save or SaveAs command in the File menu to
save the modified plot (in PlotML format).
Sample plot
!/' >m hi
JPiMüU«!
.
i! 'i!i':::::::üiJ!i|
I::::::: I ■■{!:i:■;:;:i'Mt fn i ' i Nl /
i!!!!!l!!!FH|!!!!!!!!#'iiiiiSiiS«h
\i i/ >j. 111M i y % .'' .i'' \i ■ • • •
-0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
time x10
FIGURE 12.3. Encapsulated postscript generated by the Export command in the File menu of ptplot can be
imported into word processors. This figure was imported into FrameMaker.
i.iaixi
Ufa Eat Spec«
EHHSlflnsn.« BT<1
^^ Sample plot
Bfl Cliaca: a li# A nwiiTni>. __. AW
PI-
OK Vipaua*::.
PU2-
o--
- ■ itllfl Imi
(111 ".'"/fit tilt
jl
ju ^Whiipl jMlitiij
Hfl
p e -PIß "
I -PI"
IHIIHII IIIIImivHiifl
tu-^
*^iHj M
ill SB "
iXIIIII/i i •■■W
-0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.B 0.9
FIGURE 12.4. You can modify the data being plotted by selecting a data set and then dragging the right
mouse button. Use the Edit menu to select a data set. Use the Save command in the File menu to save the
modified plot (in PlotML format).
225
brings up a dialog like that at bottom in figure 12.5. At the left is the dialog and the plot before changes
are made, and at the right is after changes are made. In particular, the grid has been removed, the stems
have been removed, the lines connecting the data points have been removed, the data points have been
rendered with points, and the color has been removed. Use the Save or SaveAs command in the File
menu to save the modified plot (in PlotML format). More sophisticated control over the plot can be
had by editing the PlotML file (which is a text file). The PlotML syntax is described below.
The entries in the format dialog are all straightforward to use except the "X Ticks" and "Y Ticks"
entries. These are used to specify how the axes are labeled. The tick marks for the axes are usually
computed automatically from the ranges of the data. Every attempt is made to choose reasonable posi-
tions for the tick marks regardless of the data ranges (powers often multiplied by 1, 2, or 5 are used).
To change what tick marks are included and how they are labeled, enter into the "X Ticks" or "Y
Ticks" entry boxes a string of the following form:
A label is a string that must be surrounded by quotation marks if it contains any spaces. A position is a
number giving the location of the tick mark along the axis. For example, a horizontal axis for a fre-
quency domain plot might have tick marks as follows:
l-lnlxl
Wto EdJ SpecW
Sample plot
New Title
—i 1 r-
'■'■>■%.
.*:'''*...-•*•*
• ••• •/•?::?:::?:::==::;:r-;:......£..^:L;..:::p,i::^:,::-
• * •_ V *•., "*•'•...,!.t^•••, i *• *
Title: horizontal
Sample plo^
X Label:
Grid: ®
v Label: Stems: ® Tlde:
X Ranue: 0.0,100.0
Connect: <8) H
B|
New Tille
X Labet horizontal
V Range: 4.0,4.0 Grtt O
Use Color:®
Marks: YLabet vertical
(•Jnone OpoMs Odots Ovarims XLog: O Stems: O
X Range 0.0.100.0 Connect: Q
X Ticks:
YLog: ®
YRange: -4.0. 4.0
Use Color: O
V Ticks: Marks:
Onone .«poWs Odots Ovarious XLog: O
| «PPV 11 Cancel
226
XTicks: -PI -3.14159, -PI/2 -1.570795, 0 0, PI/2 1.570795, PI 3.14159
Tick marks could also denote years, months, days of the week, etc.
<COMMENT>
<EMBED type="application/x-java-applet;version=l.2.2"
227
+PlotBox()
+addl_egend(dataset: int. legend
+addXTick(label: String, position
+addYTick(label: String, position
+clear(axes: boolean)
+export{out: OutputStream)
+fillPlot{)
+getColorO: boolean
+getColorByName(name : String): Color
+getGrid(): boolean
+getl_egend(dataset: int): String
+getMa»mumSize(): Dimension
Plot()
+getMinimumSize(): Dimension
+addPoint(dataset: int, x: double, y: double, connected : boolean)
+getPreferredSize(): Dimension
+addPointWithErrorBars(ds : int, x: double, y : double, yLow : double. yHigh : double, cnct: boolean)
+getTitle(): String
+clear(dataset: int)
+getXLabel(): String
+erasePoint(dataset: int. index : int)
+getXl_og{): boolean
+getConnected(): boolean
+getXRange(): double[]
+getlmpulses(): boolean
+getXTicks(): Vectort]
+getMarksStyle(): String
+getYLabel(): String
+getNumDataSets(): int
+getYLog(): boolean
+setBars(on: boolean)
+getYRange(): doublen
+setBars(width : double, offset: double)
+getYTicks(): VectorQ
+setConnected(on: boolean)
+print(g: Graphics, format: PageFormat, index: int)
+setConnected(on : boolean, dataset: int)
+read(in: InputStream)
+set!mpulses(on: boolean)
+read(line: String)
+setlmpulses(on: boolean, dataset: int)
+samplePlot()
+setMarksStyle(style: String)
+setBackground(cotor; Color)
+setMarksStyle(style: String, dataset: int)
+setBounds(x: int, y : int, width : int, height: int)
+setPointsPersistence{numPoints: int)
+setButtons{visible: boolean)
+setReuseDatasets(on: boolean)
+setForeground(color: Color)
#_checkDatasetlndex(dataset: int)
+setGrid(grid: boolean)
#_drawBar(g : Graphics, dataset: int. x : long, y: long, clip : boolean)
setLabelFont(fontname: String)
#_drawError8ar(g : Graphics, dataset: int, x: long, ylow : long, yhigh : long, clip : boolean)
+setSize(width : int, height: int)
#_drawlmpulse(g : Graphics, dataset: int, x : long, y: long, clip : boolean)
+setTitIe(title: String)
f*_drawLine(g : Graphics, dataset: int, startx: long, starty : long, endx : long, endy: long, clip : boolean)
+sefTitleFont(fontname: String)
#_drawPlot{g : Graphics, clearfirst: boolean)
+setWrap{wrap: boolean)
*_drawPoint(g : Graphics, dataset: int, x : long, y: long, clip: boolean)
+setXLabel(label: String)
*_parseLine(line : Sthng): boolean
+setXLog(log: boolean)
+setXRange(min : double, max : double) #_write(output: PrintWriter)
+setYLabel(label: String) A
+setYLog(log : boolean)
+setYRange(min : double, max : double) Runnabh j
+write(out: OutputStream)
+write(out: OutputStream, dtd : String) +x: double
+write(out: Writer, dtd : String) i-runO +y: double
writeFormat(out: Writer) yLowEB : double
+zoom(lowx: double, lowy : double, highx : double, highy: double) yHighEB: double
0_drawPlot(g : Graphics, clearfirst: boolean) ♦connected: boolean
#_drawPoint(g : Graphics, set: int, x: long, y: long, clip : boolean) +errorBar: boolean
#_helpO
_redoStack: Stack ►PlotPointQ
#_parseUne(line: String)
A__setButtonsVisibility(vis: boolean) _urtdoStack: Stack
#_zoom(x: int, y: int) editListeners: Vector
#_zoomBox(x: int, y: int) EditablePlotO
#_zoomStart(x: int, y: int) +addEditlistener(listener: EditListener)
♦getData (dataset: int): doubleQQ ■_plotLiveThread: Thread
+redo() •... : various
+removeEditListener(listener: EditListener) +addPoints()
+setEditable(dataset: int) pausef)
undo{) +setButtons(visible: boolean)
Histogram +start{)
+stop<)
...: vanous
+addPoint(dataset: int, value: double)
+addPoint(dataset: int, x : double, y: double, connected : boolean)
+setBars(width : double, offset: double)
+setBinOffset(offset: double)
+setBinWidth(width: double)
#_checkDatasetlndex(index: int)
tf_drawBar(g : Graphics, dataset: int, xpos : long, ypos : long, clip : boolean)
228
width="500"
height="300"
backgrounds #faf0e6"
code="ptolemy.plot.PlotMLApplet"
codebase="../../••"
archive="ptolemy/plot/plotmlapplet.jar"
dataur1="s inusoids.xml"
pluginspage="http://j ava.sun.com/products/plugin/1.2.2/plugin-install.html">
</COMMENT>
<NOEMBED>
No JDK 1.2 support for applet!
</NOEMBED>
</EMBED>
</OBJECT>
To use this yourself you will probably need to change the codebase and dataurl entries. The first points
to the root directory of the plot installation (usually, the value of the PTII environment variable). The
second points to a file containing data to be plotted, plus optional formatting information. The file for-
mat for the data is described in the next section. The applet is created by instantiating the PlotMLAp-
plet class.
The archive entry contains the name of the jar file that contains all the classes necessary to run a
PlotML applet. The advantage of specifying a jar file is that remote users are likely to experience a
faster download because all the classes come over at once, rather than the browser asking for each
class from the server. A downside of using jar files in applets is that if you are modifying the source of
Ptplot itself, then you must also update the jar file, or your changes will not appear. A common
workaround is to remove the archive entry during testing.
You can also easily create your own applet classes that include one or more plots. As shown in fig-
ure 12.6, the PlotBox class is derived from JPanel, a basic class of the Java Foundation Classes (JFC)
toolkit, also known as swing. It is easy to place a panel in an applet, positioned however you like, and
to combine multiple panels into an applet. PlotApplet is a simple class that adds an instance of Plot.
Creating an application that includes one or more plots is also easy. The PlotApplication class,
shown in figure 12.7, creates a single top-level window (a JFrame), and places within it an instance of
Plot. This class is derived from the PlotFrame class, which provides a menu that contains a set of com-
mands, including opening files, saving the plotted data to a file, printing, etc.
The difference between PlotFrame and PlotApplication is that PlotApplication includes a main()
method, and is designed to be invoked from the command line. You can invoke it using commands like
the following:
However, the classes shown in figure 12.7, which are in the plot package, are not usually the ones that
an end user will use. Instead, use the ones in figure 12.8. These extend the base classes to support the
PlotML language, described below. The only motivation for using the base classes in figure 12.7 is to
have a slightly smaller jar file to load for applets.
The classes that end users are likely to use, shown in figure 12.8, include:
• PlotMLApplet: An applet that can read PlotML files off the web and render them.
EditablePlotMLApplet: A version that allows editing of any data set in the plot.
• HistogramMLApplet: A version that uses the Histogram class to compute and plot histograms.
• PlotMLFrame: A top-level window containing a plot defined by a PlotML file.
229
• PlotMLApplication: An application that can be invoked from the command line and reads PlotML
files.
• EditablePlotMLApplication: An extension that allows editing of any data set in the plot.
• HistogramMLApplication: A version that uses the Histogram class to compute and plot histo-
grams.
EditablePlotMLApplication is the class invoked by the ptplot command-line script. It can open plot
files, edit them, print them, and save them.
+plot: Plot
+PlotFrame()
+PlotFrame(titie: String)
+PlotFrame(titie : String, plot: PlotBox)
+samplePlot()
#_about()
*_close()
#_edrtFomiat()
* export()
#_help{)
# openO
<fprint()
#_read(input: InputStream, base : URL)
#_save()
»_saveAs()
PlotApplication
Applet
PlotApplicationO
Histogram Plot +PlotAppllcation(title: String)
+PlotApplication(plot: PlotBox, args : StringO)
+main(args: String)
#_parseArgs(): int
*(_usage(): String
PlotApplet
■_plot: Plot
«•PlotAppletO
+newPlot(): PlotBox
♦plotO: Plot
EditablePlot PlotLlve #_read(input: InputStream)
»_5etPlotSize(appletWidth : int, appletHeight: int)
PlotLlveApplet
■PlotLiveAppletQ
FIGURE 12.7. Core classes supporting applets and applications. Most of the time, you will use the classes in
230
com.microstar.xml package
PlotBoxMLParser
+F1otML_DTDJ : String
#_attributes: HashtaNe
*_cum5ntCharData: StringBuffer
#_parser: XmlParser
»_ptot: PlotBox
+PlotBoxMLParserO plot package
+PlotBoxMLParser(plot: PlotBox)
+paree(base : URL, input: InputStream) configures
#_checkForNull(object: Object, message : String)
*_currentExternalEntityQ: String PlotApplet
:+_read<input: InputStream):
PlotMLParser
PlotAppllcatlon
#_connected: boolean
*_currentDataset: int
*_currentPointCount: double configures |+main(args: StringQ)
+PlotMLParserO !#_aboutO
+PlotMLParsen:plot: Plot) |#_read(base : URL, input: InputStream):
*_addPoint(connected : boolean, elementName : String)
s:
PtotMLApplet
EdttablePlotMLApplet
+Pk>tMLAppletO
+EditablePlotMLAppletQ ~C> »_newParserQ; PlotMLParsei
HIstgramMLParser
»HistogramMLAppletQ +PlotMLApp(icationO
EditablePlotMLAppllcatlon *PlotMLApplication(args: StringQ)
+PlotMLApplication(plot: PlotBox, args : StringQ)
»_newParserQ: PlotBoxMLParser
+EditabtePlotMLApplicati onO
+EditableRotMLApplication(args: StringQ)
T"
Histogram M LAppllcation
+Editable Plot MlApp1icabon( plot: EditablePlot, args : StringQ)
+HistogramMLApplicationO
+HistogramMLApplication(args: StringQ)
+HistogramMLApplication(plot: Histogram, args : StringQ)
FIGURE 12.8. UML static structure diagram for the plotml package, a subpackage of plot providing classes
that read PlotML, an XML extension for specifying plots.
231
12.3.3 Writing applets
A plot can be easily embedded within an applet, although there are some subtleties. The simplest
mechanism looks like this:
}
}
This places the plot in the center of the applet space, stretching it to fill the space available. To control
the size independently ofthat of the applet, for some mysterious reason that only Sun can answer, it is
necessary to embed the plot in a panel, as follows:
}
}
The setSize() method specifies the width and height in pixels. You will probably want to control the
background color and/or the border, using statements like:
myplot.setBackground(background color);
myplot.setBorder(new BevelBorder(BevelBorder.RAISED));
Alternatively, you may want to make the plot transparent, which results in the background showing
through:
myplot.setOpaque(false);
232
defining the content within that syntax. The syntax is a subset of SGML, and is similar to HTML. It is
intended for use on the internet. Plot classes can save data in this format (in fact, the Save operation
always saves data in this format), and the classes in the plotml subpackage, shown in figure 12.8, can
read data in this format. The key classes supporting this syntax are PlotBoxMLParser, which parses a
subset of PlotML supported by the PlotBox class, PlotMLParser, which parses the subset of PlotML
supported by the Plot class, and HistogramMLParser, which parses the subset that supports histo-
grams.
Here, "format commands" is a set of XML elements that specify what the plot looks like, and
"datasets" is a set of XML elements giving the data to plot. The syntax for these elements is described
below in subsequent sections. The first line above is a required part of any XML file. It asserts the ver-
sion of XML that this file is based on (1.0) and states that the file includes external references (in this
case, to the DTD). The second and third lines declare the document type (plot) and provide references
to the DTD.
The references to the DTD above refer to a "public" DTD. The name of the DTD is
which follows the standard naming convention of public DTDs. The leading dash "-" indicates that
this is not a DTD approved by any standards body. The first field, surrounded by double slashes, in the
name of the "owner" of the DTD, "uc Berkeley." The next field is the name of the DTD, "DTD
PlotML 1" where the "l" indicates version 1 of the PlotML DTD. The final field, "EN" indicates that
the language assumed by the DTD is English.
In addition to the name of the DTD, the DOCTYPE element includes a URL pointing to a copy of
the DTD on the web. If a particular PlotML tool does not have access to a local copy of the DTD, then
it finds it at this web site. PtPlot recognizes the public DTD, and uses its own local version of the DTD,
so it does not need to visit this website in order to open a PlotML file.
An alternative way to specify the DTD is:
233
<plot>
format commands...
datasets...
</plot>
These latter two methods are useful if you extend the DTD.
The DTD for PlotML is shown in figure 12.9. This defines the PlotML language. However, the
DTD is not particularly easy to read, so we define the language below in a more tutorial fashion.
The title is bracketed by the start element <title> and end element </title>. In XML, end ele-
ments are always the same as the start element, except for the slash. The DTD for this is simple:
This declares that the body consists of PCDATA, parsed character data.
Labels for the X and Y axes are similar,
Unlike HTML, in XML, case is important. So the element is xLabel not XLabel.
The ranges of the X and Y axes can be optionally given by:
The arguments min and max are numbers, possibly including a sign and a decimal point. If they are not
specified, then the ranges are computed automatically from the data and padded slightly so that
234
<!ELEMENT plot (barGraph | bin | dataset | default | noColor | noGrid size | title | wrap | xLabel
xLog | xRange | xTicks | yLabel | yLog | yRange | yTicks)*>
<!ELEMENT barGraph EMPTY>
<!ATTLIST barGraph width CDATA «IMPLIED
offset CDATA #IMPLIED>
<I ELEMENT bin EMPTY>
<!ATTLIST bin width CDATA #IMPLIED
offset CDATA #IMPLIED>
<!ELEMENT dataset (m | move | p | point)*>
<!ATTLIST dataset connected (yes | no) «IMPLIED
marks (none | dots | points | various | pixels) «IMPLIED
name CDATA «IMPLIED
stems (yes | no) #IMPLIED>
<!ELEMENT default EMPTY>
<!ATTLIST default connected (yes | no) "yes"
marks (none | dots | points | various | pixels) "none"
stems (yes | no) "no">
<!ELEMENT noColor EMPTY>
<!ELEMENT noGrid EMPTY>
<!ELEMENT reuseDatasets EMPTY>
<!ELEMENT size EMPTY>
<!ATTLIST size height CDATA «REQUIRED
width CDATA #REQUIRED>
<!ELEMENT title («PCDATA)>
<!ELEMENT wrap EMPTY>
<!ELEMENT xLabel («PCDATA)>
<!ELEMENT xLog EMPTY>
<!ELEMENT xRange EMPTY>
<!ATTLIST xRange min CDATA «REQUIRED
max CDATA #REQUIRED>
<!ELEMENT xTicks (tick)+»
<!ELEMENT yLabel («PCDATA)>
<!ELEMENT yLog EMPTY>
<!ELEMENT yRange EMPTY>
<!ATTLIST yRange min CDATA «REQUIRED
max CDATA «REQUIRED»
<!ELEMENT yTicks (tick)+»
<!ELEMENT tick EMPTY>
<!ATTLIST tick label CDATA «REQUIRED
position CDATA «REQUIRED»
<!ELEMENT m EMPTY>
<!ATTLIST m X CDATA «IMPLIED
y CDATA «REQUIRED
lowErrorBar CDATA «IMPLIED
highErrorBar CDATA «IMPLIED»
<! ELEMENT move EMPTY»
<!ATTLIST move x CDATA «IMPLIED
y CDATA «REQUIRED
lowErrorBar CDATA «IMPLIED
highErrorBar CDATA «IMPLIED»
<!ELEMENT p EMPTY»
<!ATTLIST p x CDATA «IMPLIED
y CDATA «REQUIRED
lowErrorBar CDATA «IMPLIED
highErrorBar CDATA «IMPLIED»
<!ELEMENT point EMPTY»
<!ATTLIST point X CDATA «IMPLIED
y CDATA «REQUIRED
lowErrorBar CDATA «IMPLIED
highErrorBar CDATA «IMPLIED»
FIGURE 12.9. The document type definition (DTD) for the PlotML language.
235
datapoints are not plotted on the axes. The DTD for these looks like:
The EMPTY means that the element does not have a separate start and end part, but rather has a final
slash before the closing character "/>"■ The two ATTLIST elements declare that min and max
attributes are required, and that they consist of character data.
The tick marks for the axes are usually computed automatically from the ranges. Every attempt is
made to choose reasonable positions for the tick marks regardless of the data ranges (powers of ten
multiplied by 1, 2, or 5 are used). However, they can also be specified explicitly using elements like:
<xTicks>
<tick label="label" position="position"/>
<tick label="label" position="position"/>
</xTicks>
A label is a string that replaces the number labels on the axes. A position is a number giving the loca-
tion of the tick mark along the axis. For example, a horizontal axis for a frequency domain plot might
have tick marks as follows:
<xTicks>
<tick label="-PI" position="-3.14159"/>
<tick label="-PI/2" position="-l.570795"/>
<tick label="0" position="0"/>
<tick label = "PI/2" positional.570795"/>
<tick label="PI" position="3.14159"/>
</xTicks>
Tick marks could also denote years, months, days of the week, etc. The relevant DTD information is:
The notation (tick) + indicates that the xTicks element contains one or more tick elements.
If ticks are not specified, then the X and Y axes can use a logarithmic scale with the following ele-
ments:
<xLog/>
<yLog/>
The tick labels, which are computed automatically, represent powers of 10. The log axis facility has a
number of limitations, which are documented in "Limitations" on page 12-243.
236
By default, tick marks are connected by a light grey background grid. This grid can be turned off
with the following element:
<noGrid/>
Also, by default, the first ten data sets are shown each in a unique color. The use of color can be turned
off with the element:
<noColor/>
<wrap/>
enables wrapping of the X (horizontal) axis, which means that if a point is added with X out of range,
its X value will be modified modulo the range so that it lies in range. This command only has an effect
if the X range has been set explicitly. It is designed specifically to support oscilloscope-like behavior,
where the X value of points is increasing, but the display wraps it around to left. A point that lands on
the right edge of the X range is repeated on the left edge to give a better sense of continuity. The fea-
ture works best when points do land precisely on the edge, and are plotted from left to right, increasing
inX.
You can also specify the size of the plot, in pixels, as in the following example:
All of the above commands can also be invoked directly by calling the corresponding public meth-
ods from Java code.
All of the arguments to the dataset element are optional. The name, if given, will appear in a legend
at the upper right of the plot. The marks option can take one of the following values:
• none: (the default) No mark is drawn for each data point.
• points: A small point identifies each data point.
• dots: A larger circle identifies each data point.
• various: Each dataset is drawn with a unique identifying mark. There are 10 such marks, so they
will be recycled after the first 10 data sets.
• pixels: A single pixel identified each data point.
The connected argument can take on the values "yes" and "no." It determines whether successive
datapoints are connected by a line. The default is that they are. Finally, the stems argument, which can
237
also take on the values "yes" and "no," specifies whether stems should be drawn. Stems are lines
drawn from a plotted point down to the x axis. Plots with stems are often called "stem plots."
The DTD is:
The default values of these arguments can be changed by preceding the dataset elements with a
default element, as in the following example:
<reuseDatasets/>
then datasets with the same name will be merged. This makes it easier to combine multiple data files
that contain the same datasets into one file. By default, this capability is turned off, so datasets with the
same name are not merged.
<dataset options>
data
</dataset>
The data itself are given by a sequence of elements with one of the following forms:
<point y="yValue">
<point K="xValue" y="yValue">
<point y="yValue" lowErrorBar="low" highErrorBar="high">
<point x="xValue" y="yValue" lowErrorBar="low" highErrorBar="high">
238
<p y="yValue">
<p x="xValue" y="yValue">
<p y="yValue" lowErrorBar="low" highErrorBar="high">
<p x="xValue" y="yValue" lowErrorBar="low" highErrorBar= "high">
The first form specifies only a Y value. The X value is implied (it is the count of points seen before in
this data set). The second form gives both the X and Y values. The third and fourth forms give low and
high error bar positions (error bars are use to indicate a range of values with one data point). Points
given using the syntax above will be connected by lines if the connected option has been given value
"yes" (or if nothing has been said about it).
Data points may also be specified using one of the following forms:
<move y="yValue">
<move x="xValue" y="yValue">
<move y="yValue" lowErrorBar="lew" highErrorBar="high">
<move x="xValue" y="yValue" lowErrorBar="low" highErrorBar="high">
<m y="yValue">
<m x="xValue" y="yValue">
<m y="yValue" lowErrorBar="low" highErrorBar="high">
<m x="xValue" y="yValue" lowErrorBar="low" highErrorBar="high">
This causes a break in connected points, if lines are being drawn between points. I.e., it overrides the
connected option for the particular data point being specified, and prevents that point from being
connected to the previous point.
You will also probably want the connected option to have value "no." The barWidth is a real num-
ber specifying the width of the bars in the units of the X axis. The barOffset is a real number speci-
fying how much the bar of the z'-th data set is offset from the previous one. This allows bars to "peek
out" from behind the ones in front. Note that the front-most data set will be the first one.
12.4.6 Histograms
To configure a histogram on a set of data, use
The binWidth option gives the width of a histogram bin. I.e., all data values within one binWidth
are counted together. The binOffset value is exactly like the barOffset option in bar graphs. It
specifies by how much successive histograms "peek out."
Histograms work only on Y data; X data is ignored.
239
12.5 Old Textual File Format
Instances of the PlotBox and Plot classes can read a simple file format that specifies the data to be
plotted. This file format predates the PlotML format, and is preserved primarily for backward compat-
ibility. In addition, it is significantly more concise than the PlotML syntax, which can be advanta-
geous, particularly in networked applications.
In this older syntax, each file contains a set of commands, one per line, that essentially duplicate
the methods of these classes. There are two sets of commands currently, those understood by the base
class PlotBox, and those understood by the derived class Plot. Both classes ignore commands that they
do not understand. In addition, both classes ignore lines that begin with "#", the comment character.
The commands are not case sensitive.
Tick marks could also denote years, months, days of the week, etc.
The X and Y axes can use a logarithmic scale with the following commands:
• XLog: on
• YLog: on
The tick labels, if computed automatically, represent powers of 10. The log axis facility has a number
240
of limitations, which are documented in "Limitations" on page 12-243.
By default, tick marks are connected by a light grey background grid. This grid can be turned off
with the following command:
• Grid: off
It can be turned back on with
• Grid: on
Also, by default, the first ten data sets are shown each in a unique color. The use of color can be
turned off with the command:
• Color: off
It can be turned back on with
• Color: on
Finally, the rather specialized command
• Wrap: on
enables wrapping of the X (horizontal) axis, which means that if a point is added with X out of range,
its X value will be modified modulo the range so that it lies in range. This command only has an effect
if the X range has been set explicitly. It is designed specifically to support oscilloscope-like behavior,
where the X value of points is increasing, but the display wraps it around to left. A point that lands on
the right edge of the X range is repeated on the left edge to give a better sense of continuity. The fea-
ture works best when points do land precisely on the edge, and are plotted from left to right, increasing
inX.
All of the above commands can also be invoked directly by calling the corresponding public meth-
ods from some Java code.
241
Plots with impulses are often called "stem plots." These are off by default, but can be turned on with
the command:
• Impulses: on
or back off with the command
• Impulses: off
If that command appears before any DataSet directive, then the command applies to all data sets. Oth-
erwise, it applies only to the current data set.
To create a bar graph, turn off lines and use any of the following commands:
• Bars: on
• Bars: width
• Bars: width, offset
The width is a real number specifying the width of the bars in the units of the x axis. The offset is a
real number specifying how much the bar of the i-th data set is offset from the previous one. This
allows bars to "peek out" from behind the ones in front. Note that the front-most data set will be the
first one. To turn off bars, use
• Bars: off
To specify data to be plotted, start a data set with the following command:
• DataSet: string
Here, string is a label that will appear in the legend. It is not necessary to enclose the string in quota-
tion marks.
To start a new dataset without giving it a name, use:
• DataSet:
In this case, no item will appear in the legend.
If the following directive occurs:
• ReuseDataSets: on
then datasets with the same name will be merged. This makes it easier to combine multiple data files
that contain the same datasets into one file. By default, this capability is turned off, so datasets with the
same name are not merged.
The data itself is given by a sequence of commands with one of the following forms:
• x, y
• draw: x, y
• move: x, y
• x, y, yLowErrorBar, yHighErrorBar
• draw: x, y, yLowErrorBar, yHighErrorBar
• move: x, y, yLowErrorBar, yHighErrorBar
The draw command is optional, so the first two forms are equivalent. The move command causes a
break in connected points, if lines are being drawn between points. The numbers x and y are arbitrary
numbers as supported by the Double parser in Java (e.g. "1.2", "6.39e-15", etc.). If there are four num-
bers, then the last two numbers are assumed to be the lower and upper values for error bars. The num-
bers can be separated by commas, spaces or tabs.
242
12.6 Compatibility
Figure 12.10 shows a small set of classes in the compat package that support an older ascii and
binary file formats used by the popular pxgraph program (an extension of xgraph to support binary
formats). The PxgraphApplication class can be invoked by the pxgraph executable in $PTII/bin. See
the PxgraphParser class documentation for information about the file format.
12.7 Limitations
The plot package is a starting point, with a number of significant limitations.
• A binary file format that includes plot format information is needed. This should be an extension
of PlotML, where an external entity is referenced.
• If you zoom in far enough, the plot becomes unreliable. In particular, if the total extent of the plot
is more than 2 times extent of the visible area, quantization errors can result in displaying points
or lines. Note that 232 is over 4 billion.
The log axis facility has a number of limitations. Note that if a logarithmic scale is used, then the
values must be positive. Non-positive values will be silently dropped. Further log axis limita-
tions are listed in the documentation of the _gridlnit() method in the PlotBox class.
• Graphs cannot be currently copied via the clipboard.
There is no mechanism for customizing the colors used in a plot.
plot package
PlotAppltcation
j Plot ! PlotAppiet i
+main(args: StringD)
i+_read(input: lnputStream)|
#_about()
L\ #_parseArgs(args: StringD)
# read(base: URL, input: InputStream)
configures
PxgraphParser
PxgraphApplet PxgraphApplication
#_pk>t: Plot
+PxgraphParser(plot: Plot)
+parseArgs(args: StringQ) +PxgraphAppletO +PxgraphApplicationO
+parseArgs(args: StringD, base: URL) +PxgraphApplication(args: StringD)
+parsePxgraphargs(args: StringD, base: URL) +PxgraphApplication(plot: Plot, args: StringD)
+read(input: nput! jtrea m)
FIGURE 12.10. The compat package provides compatibility with the older pxgraph program.
243/244
13
Vergil
Authors: Steve Neuendorffer, Edward Lee
13.1 Introduction
When the first computers were built, it was possible to program them, but only through an arduous
manual process. One of the first pieces of software that was written was a bootloader that simplified
the process of reprogramming those computers. For example, the bootloader may load a program into
memory from a floppy drive. The bootloader was the first, simplest form of operating system. It pro-
vided infrastructure for abstracting the process of initializing the code of computers. The simplest
operating system merely provides a mechanism for invoking other programs.
Later operating system layered services on top of the bootloader that provided more facilities to
ease programming and abstract hardware. Services like file systems, device drivers, and process sched-
uling provide mechanisms through which user applications use hardware resources. These services
provide a simple abstraction layer through which many pieces of computer hardware can be accessed.
These operating systems traditionally provided some sort of command shell, such as DOS or bash. In
some cases, the invocation mechanism takes the form of a graphical user interface, where icons repre-
sent files and applications.
Some operating systems also provide more complex application support, such as user preferences,
application component management, and file to application binding. These services attempt to make it
easier to develop applications, however they are not strictly necessary for developing applications. For
example, it is fully possible to write a Windows application without using the registry, or COM
objects. However, because these services are integrated into the Operating System at a very low level,
using them can be rather tricky. Overwriting the wrong registry entry may prevent the operating sys-
tem from working properly. Updating a COM object can prevent other applications from working
properly. Netscape and Internet Explorer constantly fight over the right to open HTML files. The diffi-
245
culty arises because these services are built into the operating system and also impose requirements on
how applications are managed. These types of services are important when building useable applica-
tions, but they are not appropriate for inclusion in a low-level operating system.
Vergil is a set of infrastructure tools that provides these application support services as another
operating system layer. This layer is built on top of the hardware abstraction layer while making mini-
mal use of the operating system's application support infrastructure. Java is the perfect platform on
which to build these services, since it provides good hardware abstraction on a wide variety of plat-
forms, but few services for building applications. We have used the infrastructure to build a design
application for Ptolemy II, but the infrastructure itself is general. Below we will describe the infra-
structural goals, the architecture, and how we have applied the infrastructure to the Ptolemy design
application. For information about using the Vergil Application to build a Ptolemy II model, see chap-
ter FIXME.
13.2 Infrastructure
The goals of building design application infrastructure are somewhat different from the goals of
building a design application. Where an application is often described by the features that it imple-
ments or the manipulation that it allows, infrastructure must provide solutions to common problems
within a certain area. Below we describe the various pieces of Vergil, and how each one makes it easier
to develop consistent, usable design applications.
246
parts of an application. Secondly, it is very important for application usability that the storage policy be
consistent.
13.2.3 Views
A particular design artifact may have different ways that it can be viewed and manipulated. For
example, an HTML document may be viewed as rendered HTML, or as plain text with HTML
markup. The infrastructure that we have built assumes that each different view of a design artifact is
associated with a toplevel frame. The creation of a view is in some respects independent from loading
a file. However, when a design artifact is first opened, a default view must be created for it. Further-
more, when the last view of the artifact is destroyed, the artifact should be closed. In this way, the view
(or views) of a design artifact are exactly analagous to the file in which the design artifact is stored.
When all of the frames are gone, the file is conceptually 'closed' and not accessible.
This correspondance has some important ramifications in the design of our infrastructure. Since,
from the point of view of the user the frames are the file, they must all display consistent data. Further-
more, opening a design artifact a second time should only create a new frame if the artifact is not
already open. If the design artifact is already open, then its views should simply be made visible.
13.3 Architecture
The key to the Vergil infrastructure is a set of classes that represent the different parts of common
design applications. The common application operations are then expressed in terms of these classes.
This makes it easy to create new application tools that are integrated with others built with the infra-
structure by simply extending a few classes.
247
URL and is used when creating a new blank effigy. These two methods roughly correspond to the
familiar File->Open and File->New operations.
The EffigyFactory base class is also useful for implementing a deference mechanism. The base
class can contain other effigy factories and will defer to the first contained factory that succesfully cre-
ates a effigy for a given file. This deference mechanism allows the factories to be ordered so that a
more specific effigy (such as one that represents HTML structure) can be checked before a more gen-
eral one (such as an effigy that simply contains a text string).
CompositeEntity
createe
+create(): AbstractClass
T A
createe
248
13.3.3 Tableau Factories
Once an effigy has been created, a frame on the screen doesn't actually exist to represent it yet.
The frame is created by a tableau, and the tableau is created by another factory. The TableauFactory
class implements the same deference mechanism as the EffigyFactory class. The static structure for the
tableau factory class, along with the related classes from the text example aboveis shown in figure
13.4.
EffigyFactory
Compos iteEntity
+EffigyFactory(workspace: Workspace)
Kr- +EffigyFactory(container: CompositeEntity, name: String)
+canCreateBlankEffigy(): boolean
+createEfflgy(container: CompositeEntity): Effigy
+createEffigy(container: CompositeEntity, baseURL: URL, url: URL): Effigy
+qetExtension(url: URL): String
Effigy
TextEffigy$Factory
Figure 13.3 Static structure that is useful for handling text documents.
CompositeEntity
TableauFactory Tableau
createe
+Factory(container: CompositeEntity, name : String) +TextEditorTableau(container: Effigy, name: String)
Figure 13.4 Static structure of how the TableauFactory class, and an example of how tableau factories are
used with text documents.
249
13.3.4 Model Directory
All effigies in the application are contained (directly, or indirectly in another effigy) in an instance
of the ModelDirectory class. The model directory allows entities to be found by identifier. Whenever a
design artifact is loaded from a URL, the model directory is searched first to prevent the artifact from
being loaded again.
13.3.5 Configurations
An instance of the Configuration class represents the configuration of an application. That config-
uration includes not only the directory of currently open effigies but also the effigy factories and tab-
leau factories. The static structure for the Configuration and ModelDirectory classes is show in figure
13.5.
13.3.6 TableauFrame
The TableauFrame class uses the above classes to implement a number of common operations.
The intention of this class is that the type-specific subclasses of the Tableau class would create
instances of TableauFrame specialized for displaying particular information. Generally, the Top base
class implements the menus for these operations and provides some abstract methods that are used for
reading and writing files. The TableauFrame class implements these abstract methods. For the rest of
this document, the line between the Top and TableauFrame classes is not terribly important, and will be
purposefully blurred for sake of clarity. The static structure for the TableauFrame class (and its super
classes) is show in figure 13.6.
-t>i Kh
CompositeEntity
Configuration
+Configuration(workspace: Workspace)
ModelDirectory +createPrimaryTableau(effigy: Effigy)
+openModel(base : URL, in : URL, identifier: String)
B
Figure 13.5 Static structure diagram for the Configuration and ModelDirectory classes.
250
13.4 Common operations
The goal of the infrastructure classes above is to implement common operations, such as storing
and creating new design artifacts, in a consistent fashion. These operations are (for the most part) actu-
ally implemented in the TableauFrame base class. Below are descriptions of each of these operations,
and how they are implemented using the architecture from the previous section.
#_directory: File
#_fileMenu: JMenu
#_fileMenultems: JMenultem[]
#_helpMenu: JMenu
A
#_helpMenultems: JMenultem[]
#_menuBar: JMenuBar
#_statusBar: StatusBar
.file: File
modified: boolean
+Top() StatusBar Tableau
+centerOnScreen()
+isModifiedO: boolean „progress: JProgressBar
+report(ex: Exception) ..message: JTextField
+report(message: String) +StatusBar()
+report(message: String, ex: Exception) +progressBar(): JProgressBar 1..1 creator
+setBackground(background: Color) setMessage(message: String)
•setModified(b: boolean)
#_about()
#_addMenusO
#_clear() TableauFrame
#_close(): boolean
#_exitO tableau: Tableau
#_getNameO: String +PtolemyTop()
#_help() +PtolemyTop(tableau: Tableau) 1..1
#_open() +getConfigurationO : Configuration
#_openURL() +getDirectoryO: ModelDirectory createe
#_print() <P +getEffigyO: Effigy
#_read(url: URL) +getEffigy(model: NamedObj): PtotemyEffigy
#_saveO: boolean +getTableau(): Tableau
#_saveAs(): boolean +setTableau(tableau: Tableau)
#_writeFile(file: File) #_addMenus()
251
chosen by the user. In this case, the tableaux (if any) contained by the effigy are simply made visible.
Remember that a single application is capable of opening a wide variety of design artifacts by virtues
of the effigy factory deference mechanism explained in section 13.3.2.
t:TableauFrame ^Configuration
c.ef:
b:JFileChooser c.d:ModelDirectory TextEffigy$Factory
c.d.e:TextEffigy c.tf:
c.d.e.t:TextTableau TextTableau$Factory
shcwOpenDialogO
►
u:URL
openModel(u,...)
getEffigy(u)
►
null
createEffig y(c.d, u, u)
c.d.e
createTableau(c d.e)
252
13.4.3 Saving Changes to a Design Artifact
The TableauFrame class implements menu items for both File->Save and File->SaveAs. The Save
operation rather simple. If the effigy is already associated with a URL that is writable, then the effigy is
simply written out to that location. Otherwise, the SaveAs operation is invoked instead. This may
occur if the design artifact was created from scratch as a blank effigy, or if the artifact was loaded by
HTTP. The SaveAs operation is a bit more complicated. The user specifies a destination URL using a
file chooser, just as when opening a new design. However, before writing the file it is necessary to
check that the URL does not already exist and that the URL is not already open. In these cases, the user
is prompted to be sure that important data is not inadvertently lost by being overwritten.
t:TableauFrame c:Confguration
c.ef:
c.d:ModelDirectory
TextEffigy$Factory
c.d.e:TextEffigy c.tf:
c.d.e.tTextTableau TextTableau$Factory
creeteEffigy(c.d;
c.d.e
createTableau(c d.e)
253
and closing all of a tableaux associated with any effigy should result in that effigy being closed.
' 1
1 «interface» | 1 1 1 EffigyFactory |
1 ChangeListener | 1 Effigy 1 1 1
1 | 1 1
1 1
i i i 1
I i I I 1 __1
A
_j ?
PtolemyEffigy PtolemyEffigy$Factory
1. Although it is probably good design practice to create an initial effigy and tableau that represent the
application and allows the user to open an initial file.
254
intended to be used as shown in Figure 13.10. The tableaux that are capable of creating a frame for a
Ptolemy effigy are described in the following sections.
Effigy 1.J1
ZT
PtolemyFrame
PtolemyEffigy
■_model: CompositeActor
+PtolemyTop(model: CompositeActor)
+PtolemyTop(model: CompositeActor, tableau : Tableau)
+getModel(): CompositeActor
1..1 +setModel(model: CompositeActor)
ConcreteTableau
0..1 ConcreteTableauFrame
1..n 1..1
255
13.5.2 FSM Tableau
The Ptolemy FSM editor graphically represents the the states and transitions of a Ptolemy FSM
domain model. It allows syntax-directed editing of the model, along with links to important design
information, such as Actor source code and HTML documentation. A screen shot is shown in Figure
13.13. States can be added by control-clicking on the schematic, or by dragging and dropping from the
palette on the left. Transitions are created by control dragging from an existing state.
The classes used to implement this tableau are shown in Figure 13.14. An instance of FSMGraph-
s*l file::C:.'users;neuendor/ptIl/ptoleniy/domains./sdMib/Spectrum.Knil Jfijx]
File View Edit Graph Help
Waveform
uT^H ► — ►—•►' N
r^;^.
GraphFrame
TableauFrame
+cut()
PtolemyFrame +copy()
+getJGraph()
Tableau +layoutGraph()
+paste()
A +print()
#_createGraphPane(): GraphPane
#_writeFile(file: File)
IS
KernelGraphTableau creator 1..1 KernelGraphFrame
1..1 createe
+_createGraphPane(): GraphPane
256
Frame is created by the tableau. The FSMGraphFrame class overrides the _createGraphPane factory
method to create the graph editor itself, while most of the user interface components (like menus and
the palette window) are created by the GraphFrame base class. Note the similarty to the KernelGraph-
Frame class described in section 13.5.1
P?|file:/C:/userv'neuendor/ptH.''p^ iMxj
File View Edit graph Help
utilities
I director library init
[D actor library
Cj Graphics
O
true \ Separate
touched S
Togeifa /
..L.
( J -^F_V > ST1_V i| F_V < -STl_V
GraphFrame
TableauFrame |
+cut()
PtolemyFrame +copy()
+getJGraphO
Tableau +layoutGraph()
+paste()
+printO
#_createGraphPane(): GraphPane
#_writeFile(file: File)
K
FSMGraphTableau creator 1..1 FSMGraphFrame
1..1 createe
+_createGraphPane(): GraphPane
257
13.5.3 Tree Tableau
Disregarding the relations between ports, a Ptolemy model is exactly the same as a hierarchical
tree of entities, ports, and attributes. The Tree Editor graphically renders a Ptolemy model in just this
way. It is most useful when the attributes of each object, or the hierarchy of objects needs to be empha-
sized. The current implementation of the Tree Tableau only allows browsing of the model, and is fairly
incomplete. It is built using the swing JTree component, and the same base classes are used to display
the palette in the Graph Editor described in section 13.5.1. The only difference is that the Tree Tableau
uses a FullTreeModel, which includes both entities and attributes, while the palette uses an Entity Tree-
Model, which only includes entities. The static structure of the ptolemy.vergil.tree package is shown in
Figure 13.12.
I I
Tableau I PtolemyFrame | JTree
Compos KeEntity
A
"A" A
TreeTableau
«interface»
TreeModel
TreeTableauSTreeFrame
"A" EntityTree Model
H_createGraphPane(): GraphPane
258/259
CT Domain
Author: Jie Liu
14.1 Introduction
The continuous-time (CT) domain in Ptolemy II aims to help the design and simulation of systems
that can be modeled using ordinary differential equations (ODEs). ODEs are often used to model ana-
log circuits, plant dynamics in control systems, lumped-parameter mechanical systems, lumped-
parameter heat flows and many other physical systems.
Let's start with an example. Consider a second order differential system,
The equations could be a model for an analog circuit as shown in figure 14.1(a), where z is the voltage
r•
260/261
of node 3, and
m = R\R2C\C2 (2)
k = R\ -C\ +R2-C2
b = 1
R4
c =
R3+R4
Or it could be a lumped-parameter model of a spring-mass mechanical model for the system shown in
figure 14.1(b), where z is the position of the mass, m is the mass, k is the spring constant, b is the
damping parameter, and c - 1.
In general, an ODE-based continuous-time system has the following form:
x = f{x, u, t) (3)
y = g(x, u, t) (4)
*('o) v
0> (5)
where, t € 9t, / > /0, a real number, is continuous time. At any time t, x e 91", an «-tuple of real num
/ .
bers, is the state of the system; «e3i is the m-dimensional input of the system; y e 91 is the I-
dimensional output of the system; x e 9t is the derivative of x with respect to time /, i.e.
dx
x = (6)
dt
Equations (3), (4), and (5) are called the system dynamics, the output map, and the initial condition of
the system, respectively.
For example, if we define a vector
y(0 = [c o]*(0
x(0) =
The solution, x(t), of the set of ODE (3)-(5), is a continuous function of time, also called a wave-
form, which satisfies the equation (3) and initial condition (5). The output of the system is then defined
as the function of x(f) and u(t), as specified in (4). The precise solution are usually impossible to be
found using digital computers. Numerical solutions are approximations of the precise solution. A
numerical solution of ODEs are usually done by integrating the right-hand side of (3) on a discrete set
262
of time points to find x(t). Using digital computers to simulate continuous-time systems has been stud-
ied for more than three decades. One of the most well-known tools is Spice [63]. The CT domain dif-
fers from Spice-like continuous-time simulators in two ways — the system specification is somewhat
different, and it is designed to interact with other models of computation.
263
by evaluating/and g, which can be achieved by firing the respective blocks. The "snapshot" of all the
signals at t is called the behavior of the system at time t.
The signal-flow block diagram model for the example system (1) is shown in figure 14.3. For com-
parison purpose, the conservation-law model (modified nodal analysis) of the system shown in figure
14.1(a) is shown in (9).
1 1 0 0 -1
R\
1 1 d 1 0 0
R\ R\ R2 dt R2
1 _L + _L + C2 1 0 (9)
0
R2 R2 R3 dt R3
0 0
1 -U-L 0
R3 R3 R4
1 0 0 0 0
By doing some math, we can see that (9) and (8) are in fact equivalent. Equation (9) can be easily
assembled from the circuit, but it is more complicated than (8). Notice that in (9) dldt is the derivative
operator, which is replaced by an integration algorithm at each time step, and the system equations
reduce to a set of algebraic equations. Spice software is known to have a very good simulation engine
for models in form of (9).
264
14.1.2 Time
One distinct characterization of the CT model is the continuity of time. This implies that a contin-
uous-time system have a behavior at any time instances. The simulation engine of the CT model
should be able to compute the behavior of the system at any time point, although it may march dis-
cretely in time. In order to achieve an accurate simulation, time should be carefully discretized. The
discretization of time, which appears as integration step sizes, may be determined by time points of
interest (e.g. discontinuities), by the numerical error of integration, and by the convergence in solving
algebraic equations.
Time is also global, which means that all components in the system share the same notion of time.
265
sequence is not important. For accuracy reason, h may not be uniform.
• foc(t„) -xt II: the 2-normed difference between the precise solution and the numerical solution at
step n is called the (global) error at step n; the difference, when we assume xt ...xt are precise,
is called the local error at step n. Local errors are usually easy to estimate and the estimation can be
used for controlling the accuracy of numerical solutions.
A general way of numerically simulating a continuous-time system is to compute the state and the
output of the system in an increasing order of /„. Such algorithms are called the time-marching algo-
rithms, and, in this chapter, we only consider these algorithms. There are variety of time marching
algorithms that differ on how xt is computed given x, ...xt . The choice of algorithms is applica-
tion dependent, and usually reflects the speed, accuracy, and numerical stability trade-offs.
or
x = F
t /(*/„> •■•>*, )' (15)
where FE and Ff are derived from the time /„, the input u{tn), the function f, and the history of x
and x. Solving (14) or (15) at a particular time /„ is called an iteration of the CT simulation at tn.
Equation (14) can be solved simply by a function evaluation and an assignment. But the solution
of (15) is the fixed point of Fj, which may not exist, may not be unique, or may not be able to be found.
The contraction mapping theorem [12] shows the existence and uniqueness of the fixed-point solution,
and provides one way to find it. Given the map Fj that is a local contraction map (generally true for
small enough step sizes) and let an initial guess a0 be in the contraction radius, then the unique fixed
point can be found by iteratively computing:
a, = FE(a0), a2 = FE(ax), a3 = FE(o2), ... (16)
Solving both (14) and (15) should be thought of as finding the fixed-point behavior of the system
at a particular time. This means both functions FE and F, should not change during one iteration of
the simulation. This further implies that the topology of the system, all the parameters, and all the
internal states that the firing functions depend on should be kept unchanged. We require that domain
266
polymorphic actors to update internal states only in the postf ire () method exactly for this reason.
*<. + ! = X
'n + h
"+i'i'n
(17)
+
= *i, *»+rM,.«v'i.)
2. Backward Euler solver:
^0 = h
n+\ ■/(*,„> ",„>*„) (19)
^3 = h
n+\-f(xl„ + 1'Utn + l'tn+0 (20)
2 3
72 ° 12 ' 9 8
if \LTE\ < ErrorTolerance ,xt = x, , otherwise, fail. If this step is successful, the next
integration step size is predicted by:
(22)
-*,„+ 2 (xln+f(xtn+i,uln+],tn+]))
Among these solvers, 1) and 3) are explicit; 2) and 4) are implicit. Also, 1) and 2) do not perform
step size control, so are called fixed-step-size solvers; 3) and 4) change step sizes according to error
estimation, so are called variable-step-size solvers. Variable-step-size solvers adapt the step sizes
according to changes of the system flow, thus are "smarter" than fixed-step-size solvers.
267
14.2.4 Discontinuity
The existence and uniqueness of the solution of an ODE (Theorem 1 in Appendix G) allows the
right-hand side of (3) to be discontinuous at a countable number of discrete points D, which are called
the breakpoints (also called the discontinuous points in some literature). These breakpoints may be
caused by the discontinuity of input signal u, or by the intrinsic flow of/ In theory, the solutions at
these points are not well defined. But the left and right limits are. So, instead of solving the ODE at
those points, we would actually try to find the left and right limits.
One impact of breakpoints on the ODE solvers is that history solutions are useless when approxi-
mating the derivative of x after the breakpoints. The solver should resolve the new initial conditions
and start the solving process as if it is at a starting point. So, the discretization of time should step
exactly on breakpoints for the left limit, and start at the breakpoint again after finding the right limit.
A breakpoint may be known beforehand, in which case it is called a predictable breakpoint. For
example, a square wave source actor knows its next flip time. This information can be used to control
the discretization of time. A breakpoint can also be unpredictable, which mean it is unknown until the
time it occurs. For example, an actor that varies its functionality when the input signal crosses a thresh-
old can only report a "missed" breakpoint after an integration step is finished. How to handle break-
points correctly is a big challenge for integrating continuous-time models with discrete models like DE
and FSM.
+ h
**.♦, =\ »+r\+1 <23>
X + X, /IH i i 'X +
C "+l 'n
The two time points tn and tn have the same time value. This solver is used for breakpoints at which a
Dirac impulse signal appears.
Notice that none of these solvers advance time. They can only be used at breakpoints.
268
14.3 CT Actors
A CT system can be built up using actors in the ptolemy.domains.ct.lib package and domain poly-
morphic actors that have continuous behaviors (i.e. all actors that do not implement the SequenceActor
interface). The key actor in CT is the integrator. It serves the unique position of wiring up ODEs. Other
actors in a CT system are usually stateless. A general understanding is that, in a pure continuous-time
model, all the information — the state — of a CT system is stored in the integrators.
269
• tentative state and tentative derivative: These are the state and derivative which have not been con-
firmed. It is a starting point for other actors to estimate the accuracy of this integration step.
• history: The previous states and derivatives. An integrator remembers the history states and their
derivatives for the past several steps. The history is used by multistep methods.
An integrator has one parameter: initialState. At the initialization stage of the simulation, the state
of the integrator is set to the initial state. Changes of initialState will be ignored after the simula-
tion starts, unless the initialize () method of the integrator is called again. The default value of
this parameter is 0.0. An integrator can possibly have several auxiliary variables. These auxiliary
variables are used by ODE solvers to store intermediate states for individual integrators.
2. CTPeriodicalSampler. This event generator periodically samples the input signal and generates
events with the value of the input signal at these time points. The sampling rate is given by the
samplePeriod parameter, which has default value 0.1. The sampling time points, which are known
beforehand, are examples of predictable breakpoints.
3. ZeroCrossingDetector. This is an event generator that monitors the signal coming in from an
input port - trigger. If the trigger is zero, then output the token from the input port. Otherwise,
there is no output. This actor controls the integration step size to accurately resolve the time that
the zero crossing happens. It has a parameter, errorTolerance, which controls how accurately the
zero crossing is determined.
4. ZeroOrderHold. This is a waveform generator that converts discrete events into continuous sig-
nals. This actor acts as a zero-order hold. It consumes the token when the consumeCur-
rentEvent () is called. This value will be held and emitted every time it is fired, until the next
time consumeCurrentEvent () is called. This actor has one single input port ,one single output
port, and no parameters.
5. ThresholdMonitor. This actor controls the integration steps so that the given threshold (on the
input) is not crossed in one step. This actor has one input port and one output port. It has two
parameters thresholdWidth and thresholdCenter, which have default value le-2 and 0, respectively.
If the input is within the range defined by the threshold center and threshold width, then a true
token is emitted from the output.
270
corresponding to the number of times that the actor is postfired. In CT, the number of times that the
actor is postfired depends on the discretization of time, which further depend on the choice of
ODE solvers and setting of parameters. As a result, the slope of the ramp may not be a constant,
and this may lead to very counterintuitive models. The same functionality is replaced by a Current-
Time actor and a Scale actor. If sequence behaviors are indeed required, event generators and
waveform generators may be helpful to convert continuous and discrete signals.
14.4 CT Directors
There are three CT directors — CTMultiSolverDirector, CTMixedSignalDirector, and CTEmbed-
dedDirector. The first one can only serve as a top-level director, a CTMixedSignalDirector can be used
both at the top-level or inside a composite actor, and a CTEmbeddedDirector can only be contained in
a CTCompositeActor. In terms of mixing models of computation, all the directors can execute com-
posite actors that implement other models of computation, as long as the composite actors are properly
connected (see section 14.5). Only CTMixedSignalDirector and CTEmbeddedDirector can be con-
tained by other domains. The outside domain of a composite actor with CTMixedSignalDirector can
be any discrete domain, such as DE, SDF, PN, CSP, etc. The outside domain of a composite actor with
CTEmbeddedDirector must also be CT or FSM, if the outside domain of the FSM model is CT. (See
also the HSDirector in the FSM domain.)
Default
Name Description Type
Value
errorTolerance The upper bound of local errors. Actors that perform integration error control (usually inte- double le-4
grators in variable step size ODE solving methods) will compare the estimated local error to
this value. If the local error estimation is greater than this value, then the integration step is
considered inaccurate, and should be restarted with a smaller step sizes.
initialStepSize This is the step size that users specify as the desired step size. For fixed step size solvers, this double 0.1
step size will be used in all non-breakpoint steps. For variable step size solvers, this is only a
suggestion.
maxlteration- This is used to avoid the infinite loops in (implicit) fixed-point iterations. If the number of int 20
sPerStep fixed-point iterations exceeds this value, but the fixed point is still not found, then the fixed-
point procedure is considered failed. The step size will be reduced by half and the integration
step will be restarted.
271
Table 21: CTDirector Parameters
Default
Name Description Type
Value
maxStepSize The maximum step size used in a simulation. This is the upper bound for adjusting step sizes double 1.0
in variable step-size methods. This value can be used to avoid sparse time points when the
system dynamic is simple.
minStepSize The minimum step size used in a simulation. This is the lower bound for adjusting step sizes. double le-5
If this step size is used and the errors are still not tolerable, the simulation aborts. This step
size is also used for the first step after breakpoints.
startTime The start time of the simulation. This is only applicable when CT is the top level domain double 0.0
Otherwise, the CT director follows the time of its executive director.
stopTime The stop time of the simulation. This is only applicable when CT is the top level domain. double 1.0
Otherwise, the CT director follows the time of its executive director.
timeResolution This controls the comparison of time. Since time in the CT domain is a double precision real double le-10
number, it is sometimes impossible to reach or step at a specific time point If two time points
are within this resolution, then they are considered identical.
valueResolution This is used in (implicit) fixed-point iterations. If in two successive iterations the difference double le-6
of the states is within this resolution, then the integration step is called converged, and the
fixed point is considered reached.
14.4.3 CTMultiSolverDirector
A CTMultiSolverDirector has two ODE solvers — one for ordinary use and one specifically for
breakpoints. Thus, besides the parameters in the CTDirector base class, this class adds two more
parameters as shown in Table 22 on page 272.
Table 22: Additional Parameter for CTMultiSolverDirector
ODESolver The fully qualified class name for the string "ptolemy.domains.ct kernel. solver.ForwardEulerSolver"
ODE solver class.
breakpointODESolver The fully qualified class name for the string "ptolemy.domains.ct.kernel.solver.DerivativeResolver"
breakpoint ODE solver class.
A CTMultiSolverDirector can direct a model that has composite actors implementing other models
of computation. One simulation iteration is done in two phases: the continuous phase and the discrete
phase. Let the current iteration be n. In the continuous phase, the differential equations are integrated
from time /„_, to tn. After that, in the discrete phase, all (discrete) events which happen at /„ are pro-
cessed. The step size control mechanism will assure that no events will happen between /„_, and tn.
14.4.4 CTMixedSignalDirector
This director is designed to be the director when a CT subsystem is contained in an event-based
system, like DE or DT. As proved in [52], when a CT subsystem is contained in the DE domain, the
CT subsystem should run ahead of the global time, and be ready for rollback. This director implements
this optimistic execution.
272
Since the outside domain is event-based, each time the embedded CT subsystem is fired, the input
data are events. In order to convert the events to continuous signals, breakpoints have to be introduced.
So this director extends CTMultiSolverDirector, which always has two ODE solvers. There is one
more parameter used by this director — the maxRunAheadLength, as shown in Table 23 on page 273.
Table 23: Additional Parameter for CTMixedSignalDirector
Name Default
Description Type
Value
maxRunAheadLength The maximum length of time for the CT subsystem to run ahead of the global time. double 1.0
When the CT subsystem is fired, the CTMixedSignalDirector will get the current time t and the
next iteration time t' from the outer domain, and take the min(x - x', /) as the fire end time, where /
is the value of the parameter maxRunAheadLength. The execution lasts as long as the fire end time is
not reached or an output event is not detected.
This director supports rollback; that is when the state of the continuous subsystem is confirmed (by
knowing that no events with a time earlier than the CT current time will be present), the state of the
system is marked. If an optimistic execution is known to be wrong, the state of the CT subsystem will
roll back to the latest marked state.
14.4.5 CTEmbeddedDirector
This director is used when a CT subsystem is embedded in another continuous time system, either
directly or through a hierarchy of finite state machines. This director is typically used in the hybrid sys-
tem scenario [53]. This director can pass step size control information up to its executive director. To
achieve this, the director must be contained in a CTCompositeActor, which will pass the step size con-
trol queries from the outer domain to the inner domain.
This director extends CTMultiSolverDirector, but has no additional parameters. A major differ-
ence between this director and the CTMixedSignalDirector is that this director does not support roll-
back. In fact, when a CT subsystem is embedded in a continuous-time environment, rollback is not
necessary.
273
A hierarchical composition of FSM and CT is shown in figure 14.6. A CT component, by adopting
the event generation technique, can have both continuous and discrete signals as its output. The FSM
can use predicates on these signals, as well as its own input signals, to build trigger conditions. The
actions associated with transitions are usually setting parameters in the destination state, including the
initial conditions of integrators.
any CT director
f\s
r
Event T! T DE TTT, Waveform
Generator Generator
DEDirector
CTMixedSignalDirector
1L Waveform
g(*.u)
Event 1_I_
Generator \ A*M) I - Generator
fTMnIti« otverC
—--^HSDirector
A
/W\ feX C
Nw
CTEmbeddedDiractor CTEmbeddedDirector
274
Xj = a(x2-Xj) (24)
Xj ^A* X'ijX] X*2
The system is built by integrators and stateless domain polymorphic actors, as shown in figure 14.7.
The result of the state trajectory projecting onto the {xx, x2) plane is shown in figure 14.8. The ini-
tial conditions of the state variables are all 1.0. The default value of the parameters are:
CT = l,X = 25, b = 2.0.
?;**«:
Stop Time: 50.0
Siqma: 10.0
Lambda: 25.0
6: 2.0
275
14.6.2 Microaccelerometor with Digital Feedback.
Microaccelerometors are MEMS devices that use beams, gaps, and electrostatics to measure accel-
eration. Beams and anchors, separated by gaps, form parallel plate capacitors. When the device is
accelerated in the sensing direction, the displacement of the beams causes a change of the gap size,
which further causes a change of the capacitance. By measuring the change of capacitance (using a
capacitor bridge), the acceleration can be obtained accurately. Feedback can be applied to the beams
by charging the capacitors. This feedback can reduce the sensitivity to process variations, eliminate
mechanical resonances, and increase sensor bandwidth, selectivity, and dynamic range.
Sigma-delta modulation [15], also called pulse density modulation or a bang-bang control, is a dig-
ital feedback technique, which also provides the A/D conversion functionality. Figure 14.9 shows the
conceptual diagram of system. The central part of the digital feedback is a one-bit quantizer.
We implemented the system as Mark Alan Lemkin designed [51]. As shown in the figure 14.10,
the second order CT subsystem is used to model the beam. The voltage on the beam-gap capacitor is
sampled every T seconds (much faster than the required output of the digital signal), then filtered by a
lead compensator (FIR filter), and fed to an one-bit quantizer. The outputs of the quantizer are con-
verted to force and fed back to the beams. The outputs are also counted and averaged every NT seconds
to produce the digital output. In our example, the external acceleration is a sine wave.
The execution result of the microaccelerometer system is shown in figure 14.11. The upper plot in
the figure plots the continuous signals, where the low frequency (blue) sine wave is the acceleration
vs
K(l)
Integrator Sampler
-o*-
Z«roOrderHoki
accumulator
276
input, the high frequency waveform (red) is the capacitance measurement, and the squarewave (green)
is the zero-order hold of the feedback from the digital part. In the lower plot, the dense events (blue)
are the quantized samples of the capacitance measurements, which has value +1 or -1, and the sparse
events (red) are the accumulation and average of the previous 64 quantized samples. The sparse events
are the digital output, and as expected, they have a sinsoidal shape.
Stop
Stop Time: 15.0
Sample Rate: 0.02
Feedback Gain: -20.0
ÜHMI
1.0 -
0.5 "
0.5 -
ill! 1
11 IWUiiil
1
1.0 .
execution finished.
277
pulling force between the two springs is big enough to pull the point masses apart. This separation
gives the two point masses a new set of initial positions, and they oscillate freely until they collide
again.
The system model, as shown in figure 14.13, has three levels of hierarchy — CT, FSM, and CT.
The top level is a continuous time model with two actors, a composite actor that outputs the position of
the two point masses, and a plotter that simply plots the trajectories. The composite actor is a finite
state machine with two modes, separated and together.
In the separated state, there are two differential equations modeling two independent oscillating
point masses. There is also an event detection mechanism, implemented by subtracting one position
from another and comparing the result to zero. If the positions are equal, within a certain accuracy,
then the two point masses collide, and a collision event is generated. This event will trigger a transition
from the separated state to the together state. And the actions on the transition set the velocity of the
stuck point mass based on Law of Conservation of Momentum.
In the together state, there is one differential equation modeling the stuck point masses, and
another first order differential equation modeling the exponentially decaying stickiness. There is
another expression computing the pulling force between the two springs. The guard condition from the
together state to the separated state compares the pulling force to the stickiness. If the pulling force is
bigger than the stickiness, then the transition is taken. The velocities of the two separated point masses
equal their velocities before the separation. The simulation result is shown in figure 14.14, where the
position of the two point masses are plotted.
MlilMIWMIIWHiiHliHIJfHiilfHSIA«WWiiliV«t!tia8Ba -JB1.51
AppUt
Sticky Mum
SSIgg
Mass 1 Position •
Mass 2 Position ■
5 10 15 20 25 30 35 40 45 50
execution finished.
FIGURE 14.14. The simulation result of the sticky point masses system.
278
14.7 Implementation
The CT domain consists of the following packages, ct.kernel, ct.kernel.util, ctkernel.solver, and
ct.lib, as shown in figure 14.15.
Integrator CTScheduler
Backward EulerSolver
CTPeriodicalSampler CTSingleSolverDirector
DerivativeResolver
ThresholdMonitor CTStatefulActor
ExplicitRK23Solver
ZeroCrossingDetector CTStepSizeContmlActor
FixedStepSizeSolver
ZeroOrderHold CTTransparentDirector
ImpulseBESolver
CTWavefbrmGenerator
TrapezoldalRulSolver
NumericalNonconvergeException
ODESolver
I
CTDirector |
TotallyOrderedSet
«Interface»
.comparator: Comparator Comparator
■ set: LinkedList
+TotallyOrderedSet(comp: Comparator)
+at(index: int) +compare(first: Object, second : Object): boolean
+clear()
+contains(obj: Object): boolean
7T
+elementList(): List
+first(): Object
+getComparator(): Comparator
+indexOf(obj: Object): int FuzzyDoubleComparator
+insert(obj: Object)
+isEmpty(): boolean threshold: double
+removeAIILessThan(o: Object) FuzzyDoubleComparatorO
removeAt(index: int) +FuzzyDoubleComparator(th: double)
removeFirstO +getThreshold(): double
+size(): int +setThreshold(threshold : double)
279
Mailbox
I
TypedAtomicActor] «Interface»
I «Interface»
CTEvwitGinantor
CTStotrfulAclor CTR*c*rv*r
+hasCurrentEvent(): boolean
+goToMarkedState() +CTReceiver()
+emitCurrentEvents()
♦markStatesQ +CTReceiver(container: IQPort)
«Interface»
CTStapS'atControlActor «Interface»
«Interface» CTWtveformGantntor TypedCompositeActor
CTDynMmieAetor
+isThisStepAccurate(): boolean
+predictedStepSize(): double +consumeCurrentEvents()
+emitTentativeOutput() +refinedStepSize(): double
T. A
CTBaselntegrator
CTCompositoActor
JL
NunwricalNonconvergeException
+NurnericalNonconvergeException(detail: String)
+NumericalNonconvergeException(obj: NamedObj, detail: String)
+NumericalNonconvergeException(obj1 : NamedObj, obj2: NamedObj, detail: String)
280
egation and the strategy design patterns [30][25] in the CTBaselntegrator and the ODESolver
classes to support seamlessly changing ODE solvers without reconstructing integrators. The execution
methods of the CTBaselntegrator class are delegated to the ODESolver class, and subclasses of
ODESolver provide the concrete implementations of these methods, depending on the ODE solving
algorithms.
CT directors implement the semantics of the continuous time execution. As shown in figure 14.18,
directors that are used in different scenarios derive from the CTDirector base class. The CTSched-
uler class provides schedules for the directors.
The ct.kernel.solver package provides a set of ODE solvers. The classes are shown in figure 14.19.
In order for the directors to choose among ODE solvers freely during the execution, the strategy design
pattern is used again. A director class talks to the abstract ODESolver base class and individual ODE
solver classes extend the ODESolver to provide concrete strategies.
14.7.3 Scheduling
This section and the following three sections provide technical details and design decisions made
in the implementation of the CT domain. These details are only necessary if the readers want to imple-
ment new directors or ODE solvers.
In general, simulating a continuous-time system (3)-(5) by a time-marching ODE solver involves
CTDJractor ODESolver
_S : double
■FixedStepSolverO _E: double +TrapezoidalRuleSolver()
■FixedStepSolverjn : String) order: int +TrapezoidalRuleSotver(ws : Workspace)
■ExplicitRK23Solver() #JsConverged(): boolean
-ExplicilRK23Solver(ws : Workspace) #_setConvergenoe(value: boolean): void
*_voteForConvergence(vote : boolean): void
BackwardEulerSolver I «inienace» .
|Br*akpolntODESolver.
ForwardEulerSolver
+BackwardEulerSolver()
+BackwardEulerSolver(ws: Workspace) I
-ForwardEulerSolverO #_isConverged(): boolean
■ForwardEulerSolver(ws : Workspace) *_setConvergence(value: boolean): void
#_voteForConvergence(vote : boolean): void
ImpulseBESolver DarrVatrVaftMO/var
+lmpulseBESotver() +DerivattveResolver()
+lmpulseBESolver(ws : Workspace) +DerivativeResolver{w: Workspace)
281
StaticSchedulingDirectorJ container Scheduler
1..1 containee
£"
CTDIrector
«Interface»
CTTransparerrtDirector
CTMixedSignalDi rector
+isThisStepAccurate(): boolean
+predictedStepSize(): double
■HunAheadLength: Parameter
+reHnedStepSize() •' douWe
inEventPhase: boolean
-JterationEndTime: double
_L knownGoodTime: double
outsideTime: double
CTEmbeddedDi rector +CTMixedSignalDirector()
+CTMixedSignalDirector(ws: Workspace)
-_outsideStepSize: double +CTMixedSignalDirector(ca: CompositeActor, nm : String)
outsideTime: double +getlterationEndTime(): double
+getOutsideTime(): double
+CTEmbeddedDirector()
#_catchup{)
+CTEmbeddedDirector(ws: Workspace)
#_markStates()
+CTEmbeddedDirector(ca: CompositeActor, nm : String)
#_rollback()
282
the following execution steps:
1. Given the state of the system xt ...xt at time points tQ... tn _,, if the current integration step size
is h, i.e. tn = tn_, + h, compute the new state xt using the numerical integration algorithms.
During the application of an integration algorithm" each evaluation ofthefia, b, t) function is
achieved by the following sequence:
• Integrators emit tokens corresponding to a;
• Source actors emit tokens corresponding to b;
• The current time is set to /;
• The tokens are passed through the topology (in a data-driven way) until they reach the integrators
again. The returned tokens are x| = = f(a,b,t).
2. After the new state xt is computed, test whether this step is successful. Local truncation error and
unpredictable breakpoints are the issues to be concerned with, since those could lead to an unsuc-
cessful step.
3. If the step is successful, predict the next step size. Otherwise, reduce the step size and try again.
Due to the signal-flow representation of the system, the numerical ODE solving algorithms are imple-
mented as actor firings and token passings under proper scheduling.
The scheduler partitions a CT system into two clusters: the state transition cluster and the output
cluster. In a particular system, these clusters may overlap.
The state transition cluster includes all the actors that are in the signal flow path for evaluating the
/ function in (3). It starts from the source actors and the outputs of the integrators, and ends at the
inputs of the integrators. In other words, integrators, and in general dynamic actors, are used to break
causality loops in the model. A topological sort of the cluster provides an enumeration of actors in the
order of their firings. This enumeration is called the state transition schedule. After the integrators pro-
duce tokens representing xt, one iteration of the state transition schedule gives the tokens representing
xt = f(xt, u(t), t) back to the integrators.
The output cluster consists of actors that are involved in the evaluation of the output map g in (4).
It is also similarly sorted in topological order. The output schedule starts from the source actors and the
integrators, and ends at the sink actors.
For example, for the system shown in figure 14.3, the state transition schedule is
U-G1-G2-G3-A
where the order of GI, G2, and G3 are interchangeable. The output schedule is
G4-Y
The event generating schedule is empty.
A special situation that must be taken care of is the firing order of a chain of integrators, as shown
in figure 14.20. For the implicit integration algorithms, the order of firings determines two distinct
kinds of fixed point iterations. If the integrators are fired in the topological order, namely x, —> x2 in
our example, the iteration is called the Gauss-Seidel iteration. That is, x2 always uses the new guess
l
2
\dt > \dt ►
283
from x, in this iteration for its new guess. On the other hand, if they are fired in the reverse topological
order, the iteration is called the Gauss-Jacobi iteration, where x2 uses the tentative output from x, in
the last iteration for its new estimation. The two iterations both have their pros and cons, which are
thoroughly discussed in [65]. Gauss-Seidel iteration is considered faster in the speed of convergence
than Gauss-Jacobi. For explicit integration algorithms, where the new states xt are calculated solely
from the history inputs up to xt , the integrators must be fired in their reverse topological order. For
simplicity, the scheduler of theCT domain, at this time, always returns the reversed topological order
of a chain of integrators. This order is considered safe for all integration algorithms.
284
14.7.5 Mixed-Signal Execution
DE inside CT.
Since time advances monotonically in CT and events are generated chronologically, the DE com-
ponent receives input events monotonically in time. In addition, a composition of causal DE compo-
nents is causal [46], so the time stamps of the output events from a DE component are always greater
than or equal to the global time. From the view point of the CT system, the events produced by a DE
component are predictable breakpoints.
Note that in the CT model, finding the numerical solution of the ODE at a particular time is seman-
tically an instantaneous behavior. During this process, the behavior of all components, including those
implemented in a DE model, should keep unchanged. This implies that the DE components should not
be executed during one integration step of CT, but only between two successive CT integration steps.
CT inside DE.
When a CT component is contained in a DE system, the CT component is required to be causal,
like all other components in the DE system. Let the CT component have local time t, when it receives
an input event with time stamp T . Since time is continuous in the CT model, it will execute from its
local time /, and may generate events at any time greater or equal to t. Thus we need
/>x (26)
to ensure causality. This means that the local time of the CT component should always be greater than
or equal to the global time whenever it is executed.
This ahead-of-time execution implies that the CT component should be able to remember its past
states and be ready to rollback if the input event time is smaller than its current local time. The state it
needs to remember is the state of the component after it has processed an input event. Consequently,
the CT component should not emit detected events to the outside DE system before the global time
reaches the event time. Instead, it should send a pure event to the DE system at the event time, and wait
until it is safe to emit it.
285
Appendix G: Brief Mathematical Background
Theorem 1. [Existence and uniqueness of the solution of an ODE] Consider the initial
value ODE problem
x = fix, t) . (27)
x(t0) = x0
If/satisfies the conditions:
1. [Continuity Condition] Let D be the set of possible discontinuity points; it may be empty. For each
fixed x e 5?" and w e 5?m, the function /:9i\ D -> 5*" in (27) is continuous. And Vx G D, the
left-hand and right-hand limit f{x, u, x") and f(x, u, x ) are finite.
+
2. [Lipschitz Condition] There is a piecewise continuous bounded function k :9* -> 51 , where 51
is the set of non-negative real numbers, such that V/ e 5v, V£, £ e 51 , V« e 5v
ll/lt",')-/(£, u,t)\\<k(tM-Q. (28)
Then, for each initial condition (/0,x0) c5t x5v" there exists a i/mgue continuous function
V('o) = *o (29)
and
V(0 =/WO. "(0,0 V/e9t\D. (30)
This function \y(0 is called the solution through (/0, x0) of the ODE (27).
Theorem 2. [Contraction Mapping Theorem.] If F:5{" -> 51" is a local contraction map at
x with contraction radius e, then there exists a unique fixed point of F within the e ball centered at x.
I.e. there exists a unique a G 51", ||a-x| <e, such that o = F(a). And VCT0 G 5t", ||a0-x|| <e,the
sequence
a, = F(o0), a2 = F(a,), a3 = F(o2), ... (31)
converges to a.
286
DE Domain
Lukito Muliadi
EdwardA. Lee
15.1 Introduction
The discrete-event (DE) domain supports time-oriented models of systems such as queueing sys-
tems, communication networks, and digital hardware. In this domain, actors communicate by sending
events, where an event is a data value (a token) and a time stamp. A DE scheduler ensures that events
are processed chronologically according to this time stamp by firing those actors whose available input
events are the oldest (having the earliest time stamp of all pending events).
A key strength in our implementation is that simultaneous events (those with identical time
stamps) are handled systematically and deterministically. A second key strength is that the global event
queue uses an efficient structure that minimizes the overhead associated with maintaining a sorted list
with a large number of events.
287
inputs) are thus able to be fired despite having no inputs to trigger a firing. Moreover, actors that intro-
duce delay (outputs have larger time stamps than the inputs) can use this mechanism to schedule a fir-
ing in the future to produce an output.
In the global event queue, events are sorted based on their time stamps. An event is removed from
the global event queue when the model time reaches its time stamp, and if it has a data token, then that
token is put into the destination input port.
At any point in the execution of a model, the events stored in the global event queue have time
stamps greater than or equal to the model time. The DE director is responsible for advancing (i.e.
incrementing) the model time when all events with time stamps equal to the current model time have
been processed (i.e. the global event queue only contains events with time stamps strictly greater than
the current time). The current time is advanced to the smallest time stamp of all events in the global
event queue.
FIGURE 15.1. If there are simultaneous events at B and D, then the one at B will have higher priority
because it may trigger another simultaneous event at D.
288
ing that the depths assigned to actors are somewhat arbitrary. But an upstream actor will always have a
lower depth than a downstream actor, unless there is an intervening delay actor. Thus, given simulta-
neous input events with the same microstep, an upstream actor will always fire before a downstream
actor. Such a strategy ensures that the execution is deterministic, assuming the actors only communi-
cate via events. In other words, even though there are several possible choices that a scheduler could
make for an ordering of firings, all choices that respect the priorities yield the same results.
There are situations where constructing a DAG following the topology is not possible. Consider
the topology shown in figure 15.2. It is evident from the figure that the topology is not acyclic. Indeed,
figure 15.2 depicts a zero-delay loop where topological sort cannot be done. The director will refuse to
run the model, and will terminate with an error message.
The TimedDelay actor in DE is a domain-specific actor that asserts a delay relationship between
its input and output. Thus, if we insert a TimedDelay actor in the loop, as shown in figure 15.3, then
constructing the DAG becomes once again possible. TheTimed Delay actor breaks the precedences.
Note in particular that the TimedDelay actor breaks the precedences even if its delay parameter is
set to zero. Thus, the DE domain is perfectly capable of modeling feedback loops with zero time delay,
but the model builder has to specify the order in which events should be processed by placing a Timed-
Delay actor with a zero value for its parameter.
15.1.3 Iteration
At each iteration, after advancing the current time, the director chooses all events in the global
event queue that have the smallest time stamps, microstep, and depth (tested in that order). The chosen
events are then removed from the global event queue and their data tokens are inserted into the appro-
priate input ports of the destination actor. Then, the director iterates the destination actor; i.e. it invokes
prefireO, fire(), and postfire(). All of these events are destined to the same actor, since the depth is
unique for each actor.
A firing may produce additional events at the current model time (the actor reacts instantaneously,
or has zero delay). There also may be other events with time stamp equal to the current model time still
pending on the event queue. The DE director repeats the above procedure until there are no more
3—tZjH
FIGURE 15.2. An example of a directed zero-delay loop.
T Delay T
>f-GH
FIGURE 15.3. A Delay actor can be used to break a zero-delay loop.
289
events with time stamp equal to the current time. This concludes one iteration of the model. An itera-
tion, therefore, processes all events on the event queue with the smallest time stamp.
This will schedule a pure event on the event queue with microstep zero and depth equal to that of the
calling actor.
An actor may also call fireAt() with the current time in its fire() method. This is a request to be
refired later in the current iteration. This is managed by queueing a pure event with microstep one
greater than the current microstep. In fact, this is only situation in which the microstep is incremented
beyond zero.
Events at the stop time are processed before stopping the model execution. The execution ends by call-
ing the wrapup() method of all actors.
It is also possible to explicitly invoke the iterate() method of the manager for some fixed number
of iterations. Recall that an iteration processes all events with a given time stamp, so this will run the
model through a specified number of discrete time steps.
290
DEDirector
-&
+binCountFactor: Parameter
+isCQAdaptive: Parameter +fire{) *getO: Token
+minBinCount: Parameter +fireAt(a: Actor, time: double) ■getContainerO: lOPort
+stopTime: Parameter +getCurrentTimeO: double +hasRoomO: boolean
+stopWhenQueuelsEmpty: Parameter +getNextlterationTime(): double! +hasToken(): boolean
•_deadActors: Set +initialize() *put(t: Token)
eventQueue: DEEventQueue «Interface» +invalidateSchedule() +setContainer(port: IOPort)\
-^microstep; int Debuggable +newRecerverO
+DEDirector() ■postfire()
+DEDirector(w: Workspace) +prefireO
+DEDirector{c: CompositeActor, name: String) +preinitializeO
+disabteActor(actor: Actor) ♦transfer! nputs(p: lOPort)
+getEventQueue{): DEEventQueue +transferOutputs(p: lOPort)
+getStartTime(): double +wrapup()
+getStopTime(): double
+setStopTime(t}me: double) «Interface» 0..n
+stopWhenQueuelsEmpty(f1ag : boolean) DEEvmntQueua DE Receiver
.container: lOPort
+clearO: void O- .delay: double
+get(): DEEvent depth: int
+isEmpty(): boolean -Jokens: LinkedList
+put(event: DEEvent) : vote +DEReceiver{)
+takeQ: DEEvent +DEReceiver(contatner: lOPort)
+setpelay(delay: double)
r
DECQEventQueue Comparable
-_cQueue: CalendarQueue
+DECQEventQueueO
+DECQEventQueue(minBinCount; int, binCountFactor: int, isAdaptive: boolean)
DECQEventQueue. DECQComparator
«Interface»
Runnabto TypedlOPort
DEThreadActor
■_delayToSet: HashSet
.thread: PtolemyThread +DEIOPortO
-jsWaiting: boolean +DEIOPort(container: CompositeEntity, name: String)
+DEThreadActor(container: TypedCompositeActor, name: String) +DEIOPort(c: CompositeEntity, name: String, isinput: boolean, isoutput: boolean)
+waitForNewlnputs() +delayTo(output: lOPort)
*waitForNewlnputs(ports: IQPortfJ) +getDelayToPortsO: Set
FIGURE 15.4. UML static structure diagram for the DE kernel package.
291
structure [11]. The time complexity for this particular implementation is O(l) in both enqueue and
dequeue operations, in theory. This means that the time complexity for enqueue and dequeue opera-
tions is independent of the number of pending events in the global event queue. However, to realize
this performance, it is necessary for the distribution of events to match certain assumptions. Our calen-
dar queue implementation observes events as they are dequeued and adapts the structure of the queue
according to their statistical properties. Nonetheless, the calendar queue structure will not prove opti-
mal for all models. For extensibility, alternative implementations of the global event queue can be real-
ized by implementing the DEEventQueue interface and specifying the event queue using the
appropriate constructor for DEDirector.
The DEEvent class carries tokens through the event queue. It contains their time stamp, their
microstep, and the depth of the destination actor, as well as a reference to the destination actor. It
implements the java.lang.Comparable interface, meaning that any two instances of DEEvent can be
compared. The private inner class DECQEventQueue.DECQComparator, which is provided to the cal-
endar queue at the time of its construction, performs the requisite comparisons of events.
The DEActor class provides convenient methods to access time, since time is an essential part of a
timed domain like DE. Nonetheless, actors in a DE model are not required to be derived from the
DEActor class. Simply deriving from TypedAtomicActor gives you the same capability, but without
the convenience. In the latter case, time is accessible through the director.
The DEIOPort class is be used by actors that are specialized to the DE domain. It supports annota-
tions that inform the scheduler about delays through the actor. It also provides two additional methods,
overloaded versions of broadcast() and send(). The overloaded versions have a second argument for
the time delay, allowing actors to send output data with a time delay (relative to current time).
Domain polymorphic actors, such as those described in the Actor Libraries chapter, have as ports
instances of TypedlOPort, not DEIOPort, and therefore cannot produce events in the future directly by
sending it through output ports. Note that tokens sent through TypedlOPort are treated as if they were
sent through DEIOPort with the time delay argument equal to zero. Domain polymorphic actors can
produce events in the future indirectly by using the fireAt() method of the director. By calling fireAt(),
the actor requests a refiring in the future. The actor can then produce a delayed event during the refir-
ing.
15.4 Mutations
The DE director tolerates changes to the model during execution. The change should be queued
with the director or manager using requestChange(). While invoking those changes, the method invali-
dateScheduleQ is expected to be called, notifying the director that the topology it used to calculate the
292
priorities of the actors is no longer valid. This will result in the priorities being recalculated the next
time prefire() is invoked.
An example of a mutation is shown in figures 15.6 and 15.7. Figure 15.7 defines a class that con-
structs a simple model in its constructor. The model consists of a clock connected to a recorder. The
method insertClock() creates an anonymous inner class that extends ChangeRequest. Its execute()
method disconnects the two existing actors, creates a new clock and a merge actor, and reconnects the
actors as shown in figure 15.6.
When the insertClock() method is called, a change request is queue with the manager. The man-
ager executes the request after the current iteration completes. Thus, the change will always be exe-
cuted between non-equal time stamps, since an iteration consists of processing all events at the current
lib
«Interface» «Interface»
TimedActor iTypedAtomicActorj SequenceActor
-5 "A"
WaitingTime DETransformer
clock
merge recorder
clock recorder i—►
clock2
before tifter
FIGURE 15.6. Topology before and after mutation for the example in figure 15.7.
293
time stamp.
Actors that are added in the change request are automatically initialized. Note, however, one sub-
tlety. The last line of the insertClock() method is:
_rec.input.createReceivers();
This method call is necessary because the connections of the recorder actor have changed, but since the
FIGURE 15.7. An example of a class that constructs a model and then mutates it.
294
actor is not new, it will not be reinitialized. Recall that the preinitialize() and initialize() methods are
guaranteed to be called only once, and one of the responsibilities of the preinitialize() method is to cre-
ate the receivers in all the input ports of an actor. Thus, whenever connections to an input port change
during a mutation, the mutation code itself must call createReceivers() to reconstruct the receivers.
Note that this will result in the loss of any tokens that might already be queued in the preexisting
receivers of the ports. It is because of this possible loss of data that the creation of receivers is not done
automatically. The designer of the mutation should be aware of the possible loss of data.
There is one additional subtlety about mutations. If an actor produces events in the future via
DEIOPort, then the destination actor will be fired even if it has been removed from the topology by the
time the execution reaches that future time. This may not always be the expected behavior. The Delay
actor in the DE library behaves this way, so if its destination is removed before processing delayed
events, then it may be invoked at a time when it has no container. Most actors will tolerate this and will
not cause problems. But some might have unexpected behavior. To prevent this behavior, the mutation
that removes the actor should also call the disableActor() method of the director.
295
ticular, even if the actor is deleted before model time reaches that of the future event, the event will
be delivered to the destination. If you use fireAt() instead to generate delayed events, then if the
actor is deleted (or returns false from postfire()) before the future event, then the future event will
not be produced.
• By convention in Ptolemy II, actors update their state only in the postfire() method. In DE, the
fire() method is only invoked once per iteration, so there is no particular reason to stick to this con-
vention. Nonetheless, we recommend that you do in case your actor becomes useful in other
domains. The simplest way to ensure this is follow the following pattern. For each state variable,
such as a private variable named count,
15.5.2 Examples
Simplified Delay Actor. An example of a domain-specific actor for DE is shown in figure 15.8. This
actor delays input events by some amount specified by a parameter. The domain-specific features of
the actor are shown in bold. They are:
• It uses DEIOPort rather than TypedlOPort.
• It has the statement:
input.delayTo(output);
This statement declares to the director that this actor implements a delay from input to output. The
actor uses this to break the precedences when constructing the DAG to find priorities.
It uses an overloaded send() method, which takes a delay argument, to produce the output. Notice
296
that the output is produced in the postfire() method, since by convention in Ptolemy II, persistent
state is not updated in the fire() method, but rather is updated in the postfire() method.
Server Actor. The Server actor in the DE library (see figure 15.5) uses a rich set of behavioral proper-
ties of the DE domain. A server is a process that takes some amount of time to serve "customers."
While it is serving a customer, other arriving customers have to wait. This actor can have a fixed ser-
vice time (set via the parameter serviceTime, or a variable service time, provided via the input port
newServiceTime). A typical use would be to supply random numbers to the newServiceTime port to
generate random service times. These times can be provided at the same time as arriving customers to
get an effect where each customer experiences a different, randomly selected service time.
package ptolemy.domains.de.lib.test;
import ptolemy.actor.TypedAtomicActor;
import ptolemy.domains.de.kernel.DEIOPort;
import ptolemy.data.DoubleToken;
import ptolemy.data.Token;
import ptolemy.data.expr.Parameter;
import ptolemy.actor.TypedCompositeActor;
import ptolemy.kernel.util.IllegalActionException;
import ptolemy.kernel.util.NameDuplicationException;
import ptolemy.kerne1.ut i1.Workspace;
297
The (compacted) code is shown in figure 15.9. This actor extends DETransformer, which has two
public members, input and output, both instances of DEIOPort. The constructor makes use of the
delayTo() method of these ports to indicate that the actor introduces delay between its inputs and its
output.
The actor keeps track of the time at which it will next be free in the private variable
_nextTimeFree. This is initialized to minus infinity to indicate that whenever the model begins execut-
ing, the server is free. The prefire() method determines whether the server is free by comparing this
private variable against the current model time. If it is free, then this method returns true, indicating to
the scheduler that it can proceed with firing the actor. If the server is not free, then the prefire() method
checks to see whether there is a pending input, and if there is, requests a firing when the actor will
become free. It then returns false, indicating to the scheduler that it does not wish to be fired at this
time. Note that the prefire() method uses the methods getCurrentTime() and fireAt() of DEActor,
which are simply convenient interfaces to methods of the same name in the director.
The fire() method is invoked only if the server is free. It first checks to see whether the newSer-
viceTime port is connected to anything, and if it is, whether it has a token. If it does, the token is read
and used to update the serviceTime parameter. No more than one token is read, even if there are more
in the input port, in case one token is being provided per pending customer.
The fire() method then continues by reading an input token, if there is one, and updating
_nextTimeFree. The input token that is read is stored temporarily in the private variable _currentlnput.
The postfire() method then produces this token on the output port, with an appropriate delay. This is
done in the postfire() method rather than the fire() method in keeping with the policy in Ptolemy II that
persistent state is not updated in the fire() method. Since the output is produced with a future time
stamp, then it is persistent state.
Note that when the actor will not get input tokens that are available in the fire() method, it is essen-
tial that prefire() return false. Otherwise, the DE scheduler will keep firing the actor until the inputs are
all consumed, which will never happen if the actor is not consuming inputs!
Like the SimpleDelay actor in figure 15.8, this one produces outputs with future time stamps,
using the overloaded send() method of DEIOPort that takes a delay argument. There is a subtlety asso-
ciated with this design. If the model mutates during execution, and the Server actor is deleted, it cannot
retract events that it has already sent to the output. Those events will be seen by the destination actor,
even if by that time neither the server nor the destination are in the topology! This could lead to some
unexpected results, but hopefully, if the destination actor is no longer connected to anything, then it
will not do much with the token.
298
package ptolemy.domains.de.lib;
import statements ...
return super.postfireO,•
FIGURE 15.9. Code for the Server actor. For more details, see the source code.
299
Produce an output event at outport as soon as events at inportA and inportB occurs
in that particular order, and repeat this behavior.
Note that the standard description needs a state variable state, unlike the case in the threaded
description. In general the threaded description encodes the state information in the position of the
code, while the standard description encodes it explicitly using state variables. While it is true that the
context switching overhead associated with multi-threading application reduces the performance, we
argue that the simplicity and clarity of writing actors in the threaded fashion is well worth the cost in
some applications.
The infrastructure for this feature is shown in figure 15.4. To write an actor in the threaded fashion,
one simply derives from the DEThreadActor class and implements the run() method. In many cases,
the content of the run() method is enclosed in the infinite 'while (true)' loop since many useful
threaded actors do not terminate.
The waitForNewInputs() method is overloaded and has two flavors, one that takes no arguments
and another that takes an IOPort array as argument. The first suspends the thread until there is at least
one input token in at least one of the input ports, while the second suspends until there is at least one
input token in any one of the specified input ports, ignoring all other tokens.
In the current implementation, both versions of waitForNewInputs() clear all input ports before the
thread suspends. This guarantees that when the thread resumes, all tokens available are new, in the
sense that they were not available before the waitForNewInput() method call.
The implementation also guarantees that between calls to the waitForNewInputs() method, the rest
of the DE model is suspended. This is equivalent to saying that the section of code between calls to the
waitForNewInput() method is a critical section. One immediate implication is that the result of the
method calls that check the configuration of the model (e.g. hasToken() to check the receiver) will not
be invalidated during execution in the critical section. It also means that this should not be viewed as a
way to get parallel execution in DE. For that, consider the DDE domain.
It is important to note that the implementation serializes the execution of threads, meaning that at
public class ABRecognizer extends DEThreadActor ( public class ABRecognizer extends DEActor (
StringToken msg = new StringToken("Seen AB"); StringToken msg = new StringToken("Seen AB"
II the run method is invoked when the thread // We need an explicit state variable in
// is started, // this case,
public void runt) { int state = 0;
while (true) {
waitForNewInputs 0; public void fireO {
if (inportA.hasToken(0)) { switch (state) {
IOPort[] nextinport = (inportB}; case 0:
waitForNewInputs(nextinport); if (inportA.hasToken(O)) (
outport.broadcast(msg); state = 1;
} break;
} }
} case 1:
if (inportB.hasToken(0)) (
state = 0;
outport.broadcast(msg);
}
}
FIGURE 15.10. Code listings for two style of writing the ABRecognizer actor.
300
any given time there is only one thread running. When a threaded actor is running (i.e. executing inside
its run() method), all other threaded actors and the director are suspended. It will keep running until a
waitForNewInputsO statement is reached, where the flow of execution will be transferred back to the
director. Note that the director thread executes all non-threaded actors. This serialization is needed
because the DE domain has a notion of global time, which makes parallelism much more difficult to
achieve.
The serialization is accomplished by the use of monitor in the DEThreadActor class. Basically, the
fire() method of the DEThreadActor class suspends the calling thread (i.e. the director thread) until the
threaded actor suspends itself (by calling waitForNewInputsO). One key point of this implementation
is that the threaded actors appear just like an ordinary DE actor to the DE director. The DEThreadActor
base class encapsulates the threaded execution and provides the regular interfaces to the DE director.
Therefore the threaded description can be used whenever an ordinary actor can, which is everywhere.
The code shown in figure 15.11 implements the run method of a slightly more elaborate actor with
the following behavior:
Emit an output O as soon as two inputs A and B have occurred. Reset this behavior
each time the input R occurs.
Future work in this area may involve extending the infrastructure to support various concurrency con-
structs, such as preemption, parallel execution, etc. It might also be interesting to explore new concur-
rency semantics similar to the threaded DE, but without the 'forced' serialization.
301
advances time by looking at the smallest time stamp in the event queue of the DE subsystem). Specifi-
cally, before the advancement of the current time of the DE subsystem tinmr is less than or equal to the
touter and after the advancement tinner is equal to the touter
Requesting a refiring is done in the postfire() method of the DE director by calling the fireAt()
method of the executive director. Its purpose is to ensure that events in the DE subsystem are processed
on time with respect to the current time of the outer domain, touter
Note that if the DE subsystem is fired due to the outer domain processing a refire request, then
there may not be any tokens in the input port of the opaque composite actor at the beginning of the DE
subsystem iteration. In that case, no new events with time stamps equal to touter will be put into the
global event queue. Interestingly, in this case, the time synchronization will still work because timer
will be advanced to the smallest time stamp in the global event queue which, in turn, has to be equal
if (A.hasToken(O)) (
// Seen A..
IOPortU ports = (B,R};
waitForNewInputs(ports);
if (!R.hasToken(01) {
// Seen A then B..
0. broadcast (new DoubleTokend. 0)) ;
IOPortN ports2 = (R};
waitForNewInputs(ports2);
else {
// Resetting
continue;
}
} else if (B.hasToken(O)) (
// Seen B..
IOPortH ports = {A,R};
waitForNewInputs(ports);
if (!R.hasToken(0)) {
// Seen B then A..
0.broadcast(new DoubleToken(l.O)) ;
IOPortH ports2 = {R};
waitForNewInputs(ports2);
} else (
// Resetting
continue;
1
) // while (true)
catch (IllegalActionException e) {
getManagerO.notifyListenersOfException(e) ;
302
f
outer because we always request a retire according to that time stamp.
r
Discrete
Event
~V,
•a *s~x^ ProcMs
Dalay KM
<S< i—► ]T * lü
Continuous
Time j
FIGURE 15.12. An example of heterogeneous and hierarchical composition. The CT subsystem and DE
subsystem are inside an outermost DE system. This example is developed by Jie Liu [52].
303/304
SDF Domain
Author: Steve Neuendorffer
Contributor: Brian Vogel
16.2.1 Deadlock
Consider the SDF model shown in figure 16.1. This actor has a feedback loop from the output of
the AddSubtract actor back to its own input. Attempting to run the model results in the exception
shown at the right in the figure. The director is unable to schedule the model because the input of the
AddSubtract actor depends on data from its own output. In general, feedback loops can result in such
conditions.
305
The fix for such deadlock conditions is to use the SampleDelay actor, shown highlighted in figure
16.2. This actor injects into the feedback loop an initial token, the value of which is given by the initia-
lOutputs parameter of the actor. In the figure, this parameter has the value {0}. This is an array with a
single token, an integer with value 0. A double delay with initial values 0 and 1 can be specified using
a two element array, such as {0, 1}.
It is important to note that it is occasionally necessary to add a delay that is not in a feedback loop
to match the delay of an in input with the delay around a feedback loop. It can sometimes be tricky to
see exactly where such delays should be placed without fully considering the flow of the initial tokens
described above.
Display
SDF
HI Exception 2<]
Display
SDF
n
AddSubtract SampleDelay
Const
>~-»
H
Edit parameters for SampleDelay Xl
FIGURE 16.2. The model of figure 16.1 corrected with an instance of SampleDelay in the feedback loop.
306
16.2.2 Consistency of data rates
Consider the SDF model shown in figure 16.3. The model is attempting to plot a sinewave and its
downsampled counterpart. However, there is an error because the number of tokens on each channel of
the input port of the plotter can never be made the same. The DownSample actor declares that it con-
sumes 2 tokens using the tokenConsumptionRate parameter of its input port. Its output port similarly
declasres that it produces only one token, so there will only be half as many tokens being plotted from
the DownSampler as from the Sinewave.
The fixed model is shown in figure 16.4, which uses two separate plotters. When the model is exe-
cuted, the plotter on the bottom will fire twice as often as the plotter on the top, since must consume
twice as many tokens. Notice that the problem appears because one of the actors (in this case, the
DownSample actor) produces or consumes more than one token on one of its ports. One easy way to
ensure rate consistency is to use actors that only produce and consume one token at a time. This special
case is known as homogenous SDF. Note that actors like the Sequence plotter which do not specify rate
parameters are assumed to be homogneous. For more specific information about the rate parameters
SDF
DownSample
Sinewave
i r^n SequencePlotter
(^Exception Wfg
»^—Hts-
Sinewave SequencePlotter3
307
and how they are used for scheduling, see section 16.3.1.
SDF
FIGURE 16.5. A model that plots the Fast Fourier Transform of a signal. Only one iteration must be exe-
cuted to plot all 256 values of the FFT, since the FFT actor produces and consumes 256 tokens each firing.
SDF
Pulse SequencePlotter3
FIGURE 16.6. A model that plots the values of a signal. 256 iterations must be executed to plot the entire
signal.
308
single iteration of the contained( 16.3.1) model. An SDF iteration consists of one execution of the pre-
calculated SDF schedule. The schedule is calculated so that the number of tokens on each relation is
the same at the end of an iteration as at the beginning. Thus, an infinite number of iterations can be
executed, without deadlock or infinite accumulation of tokens on each relation.
Execution in SDF is extremely efficient because of the scheduled execution. However, in order to
execute so efficiently, some extra information must be given to the scheduler. Most importantly, the
data rates on each port must be declared prior to execution. The data rate represents the number of
tokens produced or consumed on a port during every firing1. In addition, explicit data delays must be
added to feedback loops to prevent deadlock. At the beginning of execution, and any time these data
rates change, the schedule must be recomputed. If this happens often, then the advantages of scheduled
execution can quickly be lost.
16.3.1 Scheduling
The first step in constructing the schedule is to solve the balance equations [48]. These equations
determine the number of times each actor will fire during an iteration. For example, consider the model
in figure 16.7. This model implies the following system of equations, where ProductionRate and Con-
sumptionRate are declared properties of each port, and Firings is a property of each actor that will be
solved for:
Firings{k) X ProductionRate(A 1) = Firings(B) X ConsumptionRate(B 1)
Firings(A) X ProductionRate(A2) = Firings(C) X ConsumptionRate(C 1)
Firings(C) X ProductionRate(C2) = Firings(B) X ConsumptionRate(B2)
These equations express constraints that the number of tokens created on a relation during an iteration
is equal to the number of tokens consumed. These equations usually have an infinite number of lin-
early dependent solutions, and the least positive integer solution for Firings is chosen as the firing vec-
tor, or the repetitions vector.
The second step in constructing an SDF schedule is dataflow analysis. Dataflow analysis orders
the firing of actors, based on the relations between them. Since each relation represents the flow of
data, the actor producing data must fire before the consuming actor. Converting these data dependen-
cies to a sequential list of properly scheduled actors is equivalent to topologically sorting the SDF
graph, if the graph is acyclic2. Dataflow graphs with cycles cause somewhat of a problem, since such
EO
■»faii*.iifam.ii
1. This is known as multirate SDF, where arbitrary rates are allowed. Not to be confused with homogenous SDF,
where the data rates are fixed to be one.
309
graphs cannot be topologically sorted. In order to determine which actor of the loop to fire first, a data
delay must be explicitly inserted somewhere in the cycle. This delay is represented by an initial token
created by one of the output ports in the cycle during initialization of the model. The presence of the
delay allows the scheduler to break the dependency cycle and determine which actor in the cycle to fire
first. In Ptolemy II, the initial token (or tokens) can be sent from any port, as long as the port declares
an initProduction property. However, because this is such a common operation in SDF, the Delay actor
(see section 16.5) is provided that can be inserted in a feedback look to break the cycle. Cyclic graphs
not properly annotated with delays cannot be executed under SDF. An example of a cyclic graph prop-
erly annotated with a delay is shown in figure 16.8.
In some cases, a non-zero solution to the balance equations does not exist. Such models are said to
be inconsistent, and cannot be executed under SDF. Inconsistent graphs inevitably result in either
deadlock or unbounded memory usage for any schedule. As such, inconsistent graphs are usually bugs
in the design of a model. However, inconsistent graphs can still be executed using the PN domain, if
the behavior is truly necessary. Examples of consistent andinconsistent graphs are shown in figure
16.9.
FIGURE 16.8. A consistent cyclic graph, properly annotated with delays. A one token delay is represented
by a black circle. E3 is responsible for setting the tokenlnitProduction parameter on its output port, and creat-
ing the two tokens during initialization. This graph can be executed using the schedule El, El, E2, E3, E3.
Note that the topological sort does not correspond to a unique total ordering over the actors. Furthermore, espe-
cially in multirate models it may be possible to interleave the firings of actors that fire more than once. This can
result in many possible schedules that represent different performance tradeoffs. We anticipate that future sched-
ulers will be implemented to take advantage of these tradeoffs. For more information about these tradeoffs, see
[47].
310
hierarchy. The SDF domain does not have any information about the contained model, other than the
rate parameters that may be specifed on the ports of the composite actor. The SDF domain is designed
so that it automatically sets the rates of external ports when the schedule is computed. Most other
domains are designed (conveniently enough) so that their models are compatible with default rate
properties assumed by the SDF domain. For a complete description of these defaults, see the descrip-
tion of the SDFScheduler class in section 16.4.2.
FIGURE 16.9. Two models, with each port annotated with the appropriate rate properties. The model on
the top is consistent, and can be executed using the schedule A, A, C, B, B. The model on the bottom is
inconsistent because tokens will accumulate between ports C2 and B2.
311
specify a non-zero value in the director of the toplevel composite actor as the number of toplevel itera-
tions of the model.
The SDF director also has a vectorizationFactor parameter that can be used to request vectorized
execution of a model. This parameter suggests that the director modify the schedule so that instead of
firing each actor only once, it is fired vectorizationF actor times using the vectorized iterate method.
The specified factor serves only as a suggestion, and the director is free to ignore it or to use a different
factor. The vectorizationF actor parameter must contain a positive integer value. The default value is
an IntToken with value one, indicating that no vectorization should be done. Note that vectorizing the
execution of a model is not necessarily possible if the model contains feedback cycles. At the very
least, it is likely that the data delay specified for any cycle must be increased (possibly changing the
meaning of the model).
SDFSchedultr
SDFIOPort
AmyFIFOQiMiM
+SDFScheduler()
+SDFIOPort(c : ComponentEntity, name ; String)
♦SDFSchedulerfw: Workspace)
■»INFINITE CAPACITY: int +getFiringCount{entity: Entity): int
+getTokenConsumptionRate(): int
♦DEFAULT CAPACITY : int ♦oetTokenConsumptionRatefp: lOPort): int
+getTokenlnitProduction(): int
♦STARTING ARRAYSIZE : int ♦qetTokenlnitProductionfo ; IQPorl): int tgetTokenProductionRateO: int
♦DEFAULT HISTORY CAPACITY : int ►oetTokenProductionRatefp: IQPort)
+setTokenConsumptionRate(rate: int)
-_container: Nameable +setTokenlnitProduction(rate: int)
queuearray: ObjectfJ +setTokenProductionRate(rate: int)
MstoryList: LinkedList
ArrayFIFOQueueO
1.1
+ArrayFIFOQueue(size: int)
+ArrayFIFOQueue(container: Nameable)
+ArrayFIFOQueue(container: Nameable, size : int)
+ArrayFIFOQueue(model: ArrayFIFOQueue)
♦elements!): CollectionEnumeration
♦getfoffset: int)
SDFReceiver
♦getCapacityO: int
+getContainer(): Nameable
♦getHistoryCapadtyO: int :+get(): Token
♦NstoryElementsO: CollectionEnumeration +SDFReceiver() |+getArray(lengtri: int): TokenfJ
+historySize(): int +SDFReceiver(size : int) i+getContainer(): lOPort
♦isEmptyO: boolean +SDFReceiver(container: lOPort) jthasRoomO : boolean
+isFull(): boolean +SDFReceiver(container; lOPort, size : int) i+hasRoom(count: int): boolean
+put(o: Object) +elements(): Enumeration :+hasToken(): boolean
+putArray(o: ObjectQ) 1.1 +get(offset: int): Token |<-hasToken(count: int): boolean
♦putArrayjarray: ObjectfJ, count: int) +getCapacity(): int i+put(t; Token)
♦setCapacity(capacity: int) -t-getHistoryCapadtyO : int ;+putArray(tokens ; Token fj, length : int):
♦setHistoryCapacity(capacity: int) +historyElements(): Enumeration setContainerfport: lOPort)
♦size)): int +historySize(): int
+take(): Object +setCapacity(capacity; int)
takeArrayfo: ObjectfJ) +setHisto(yCapacity(capacity: int)
+takeArray(o: object fj, count: int) +size(): int
312
The newReceiverO method in SDF directors is overloaded to return instances of the SDFReceiver
class. This receiver contains optimized method for reading and writing blocks of tokens. For more
information about SDF receivers, see section 16.4.3.
1. The assumed values correspond to a homogeneous actor with no data delay. Input ports are assumed to have a
consumption rate of one, output ports are assumed to have a production rate of one, and no tokens are produced
during initialization.
313
scheduler. However, the SDF scheduler detects multiports that are not connected to anything (and thus
have zero width). Such ports are interpreted to have no channels, and will be ignored by the SDF
scheduler.
a:CompositeActor
a.d:SDFDirector a.p:IOPort a.b:CompositeActor
s1:SDFScheduler a.b.diSDFDirector a.b.p:IOPort
initializeQ s2:SDFScheduler
initialized
initializeO
getSchedule
►
setRates
sc1:
Schedule
getSchedule
►
getRates
setRates
sc2:
Schedule
FIGURE 16.11. The sequence of method calls during scheduling of a hierarchical model.
1. Although the buffer sizes can be statically determined, the current mechanism for creating receivers does not
easily support it. The SDF domain currently relies on the buffer expanding algorithm that the ArrayFIFOQueue
uses to implement circular buffers of unbounded size. Although there is some overhead during the first iteration,
the overhead is minimal during subsequent iterations (since the buffer is guaranteed never to grow larger).
314
The SDFIOPort class extends the TypedlOPort class. It exists mainly for convenience when creat-
ing actors in the SDF domain. It provides comvenience methods for setting and accessing the rate
parameters used by the SDF scheduler.
16.4.4 ArrayFIFOQueue
The ArrayFIFOQueue class implements a first in, first out (FIFO) queue by means of a circular
array buffer1. Functionally it is very similar to the FIFOQueue class, although with different enqueue
and dequeue performance. It provides a token history and an adjustable, possibly unspecified, bound
on the number token it contains.
If the bound on the size is specified, then the array is exactly the size of the bound. In other words,
the queue is full when the array becomes full. However, if the bound is unspecified, then the circular
buffer is given a small starting size and allowed to grow. Whenever the circular buffer fills up, it is
copied into a new buffer that is twice the original size.
16.5 Actors
Most domain-polymorphic actors can be used under the SDF domain. However, actors that depend
on a notion of time may not work as expected. For example, in the case of a TimedPlotter actor, all data
will be plotted at time zero when used in SDF. In general, domain-polymorphic actors (such as
AddSubtract) are written to consume at most one token from each input port and produce exactly one
token on each output port during each firing. Under SDF, such an actor will be assumed to have a rate
of one on each port, and the actor will consume exactly one token from each input port during each fir-
ing. There is one actor that is normally only used in SDF: the Delay actor. The delay actor is provided
to make it simple to build models with feedback, by automatically handling the tokenlnitProduction
parameter and providing a way to specify the tokens that are created.
Delay
Ports: input (Token), output (Token).
Parameters: initialOutputs (ArrayToken).
During initialization, create a token on the output for each token in the initialOutputs array. During
each firing, consume one token on the input and produce the same token on the output.
315/316
CSP Domain
Author: Neil Smyth
Contributors: John S. Davis II, Bilung Lee
17.1 Introduction
The communicating sequential processes (CSP) domain in Ptolemy II models a system as a net-
work of sequential processes that communicate by passing messages synchronously through channels.
If a process is ready to send a message, it blocks until the receiving process is ready to accept the mes-
sage. Similarly if a process is ready to accept a message, it blocks until the sending process is ready to
send the message. This model of computation is non-deterministic as a process can be blocked waiting
to send or receive on any number of channels. It is also highly concurrent.
The CSP domain is based on the model of computation (MoC) first proposed by Hoare [37][38] in
1978. In this MoC, a system is modeled as a network of processes communicate solely by passing mes-
sages through unidirectional channels. The transfer of messages between processes is via rendezvous,
which means both the sending and receiving of messages from a channel are blocking: i.e. the sending
or receiving process stalls until the message is transferred. Some of the notation used here is borrowed
from Gregory Andrews' book on concurrent programming [4], which refers to rendezvous-based mes-
sage passing as synchronous message passing.
Applications for the CSP domain include resource management and high level system modeling
early in the design cycle. Resource management is often required when modeling embedded systems,
and to further support this, a notion of time has been added to the model of computation used in the
domain. This differentiates our CSP model from those more commonly encountered, which do not typ-
ically have any notion of time, although several versions of timed CSP have been proposed [35]. It
might thus be more accurate to refer to the domain using our model of computation as the "Timed
CSP" domain, but since the domain can be used with and without time, it is simply referred to as the
CSP domain.
317
17.2 CSP Communication Semantics
At the core of CSP communication semantics are two fundamental ideas. First is the notion of
atomic communication and second is the notion of nondeterministic choice. It is worth mentioning a
related model of computation known as the calculus of communicating systems (CCS) that was inde-
pendently developed by Robin Milner in 1980 [59]. The communication semantics of CSP are identi-
cal to those of CCS.
Process A Process B
progress
receive(A, var)
v.
\
♦
FIGURE 17.1. Illustrating how processes block waiting to rendezvous
318
guard; communication => statements;
The guard is only allowed to reference local variables, and its evaluation cannot change the state of the
process. For example it is not allowed to assign to variables, only reference them. The communication
must be a simple send or receive, i.e. another conditional communication statement cannot be placed
here. Statements can contain any arbitrary sequence of statements, including more conditional commu-
nications.
If the guard is false, then the communication is not attempted and the statements are not executed.
If the guard is true, then the communication is attempted, and if it succeeds, the following statements
are executed. The guard may be omitted, in which case it is assumed to be true.
There are two conditional communication constructs built upon the guarded communication state-
ments: CIF and CDO. These are analogous to the if and while statements in most programming lan-
guages. They should be read as "conditional if and "conditional do". Note that each guarded
communication statement represents one branch of the CIF or CDO. The communication statement in
each branch can be either a send or a receive, and they can be mixed freely.
CIF: The form of a CIF is
CIF {
G1;C1 => SI;
[]
G2;C2 => S2;
[]
For each branch in the CIF, the guard {Gl, G2,...) is evaluated. If it is true (or absent, which
implies true), then the associated communication statement is enabled. If one or more branch is
enabled, then the entire construct blocks until one of the communications succeeds. If more than one
branch is enabled, the choice of which enabled branch succeeds with its communication is made non-
deterministically. Once the successful communication is carried out, the associated statements are exe-
cuted and the process continues. If all of the guards are false, then the process continues executing
statements after the end of the CIF.
It is important to note that, although this construct is analogous to the common */programming
construct, its behavior is very different. In particular, all guards of the branches are evaluated concur-
rently, and the choice of which one succeeds does not depend on its position in the construct. The nota-
tion "[]" is used to hint at the parallelism in the evaluation of the guards. In a common if, the branches
are evaluated sequentially and the first branch that is evaluated to true is executed. The CIF construct
also depends on the semantics of the communication between processes, and can thus stall the progress
of the thread if none of the enabled branches is able to rendezvous.
319
CDO: The form of the CDO is
CDO {
G1;C1 => SI;
[]
G2;C2 => S2;
[]
The behavior of the CDO is similar to the CIF in that for each branch the guard is evaluated and
the choice of which enabled communication to make is taken nondeterministically. However, the CDO
repeats the process of evaluating and executing the branches until all the guards return false. When this
happens the process continues executing statements after the CDO construct.
An example use of a CDO is in a buffer process which can both accept and send messages, but has
to be ready to do both at any stage. The code for this would look similar to that in figure 17.2. Note that
in this case both guards can never be simultaneously false so this process will execute the CDO for-
ever.
17.2.3 Deadlock
A deadlock situation is one in which none of the processes can make progress: they are all either
blocked trying to rendezvous or they are delayed (see the next section). Thus, two types of deadlock
can be distinguished:
real deadlock - all active processes are blocked trying to communicate
time deadlock - all active processes are either blocked trying to communicate or are delayed, and at
least one processes is delayed.
17.2.4 Time
In the CSP domain, time is centralized. That is, all processes in a model share the same time,
referred to as the current model time. Each process can only choose to delay itself for some period rel-
ative to the current model time, or a process can wait for time deadlock to occur at the current model
time. In both cases, a process is said to be delayed.
When a process delays itself for some length of time from the current model time, it is suspended
until time has sufficiently advanced, at which stage it wakes up and continues. If the process delays
itself for zero time, this will have no effect and the process will continue executing.
A process can also choose to delay its execution until the next occasion a time deadlock is reached.
The process resumes at the same model time at which it delayed, and this is useful as a model can have
several sequences of actions at the same model time. The next occasion time deadlock is reached, any
CDO {
(room in buffer?); receive(input, beginningOfBuffer) => update pointer to beginning of buffer;
[]
(messages in buffer?); send(output, endOfBuffer) => update pointer to end of buffer;
}
320
processes delayed in this manner will continue, and time will not be advanced. An example of using
time in this manner can be found in section 17.3.2.
Time may be advanced when all the processes are delayed or are blocked trying to rendezvous,
and at least one process is delayed. If one or more processes are delaying until a time deadlock occurs,
these processes are woken up and time is not advanced. Otherwise, the current model time is advanced
just enough to wake up at least one process. Note that there is a semantic difference between a process
delaying for zero time, which will have no effect, and a process delaying until the next occasion a time
deadlock is reached.
Note also that time, as perceived by a single process, cannot change during its normal execution;
only at rendezvous points or when the process delays can time change. A process can be aware of the
centralized time, but it cannot influence the current model time except by delaying itself. The choice
for modeling time was in part influenced by Pamela [27], a run time library that is used to model paral-
lel programs.
321
CSP domain. The third demonstration, sieve of Eratosthenes, serves to demonstrate the mutability that
is possible in CSP models. In this demonstration, the topology of the model changes during execution.
The final demonstration, M/M/l queue, features the pause/resume mechanism of Ptolemy II that can be
used to control the progression of a model's execution in the CSP domain.
This implementation uses an algorithm that lets each philosopher randomly chose which chopstick to
pick up first (via a CDO), and all philosophers eat and think at the same rates. Each philosopher and
each chopstick is represented by a separate process. Each chopstick has to be ready to be used by either
philosopher beside it at any time, hence the use of a CDO. After it is grabbed, it blocks waiting for a
message from the philosopher that is using it. After a philosopher grabs both the chopsticks next to
him, he eats for a random time. This is represented by calling delay() with the random interval to eat
for. The same approach is used when a philosopher is thinking. Note that because messages are passed
by rendezvous, the blocking of a philosopher when it cannot obtain a chopstick is obtained for free.
This algorithm is fair, as any time a chopstick is not being used, and both philosophers try to use it,
they both have an equal chance of succeeding. However this algorithm does not guarantee the absence
of deadlock, and if it is let run long enough this will eventually occur. The probability that deadlock
= chopstick
= philosopher
Vr^X
FIGURE 17.3. Illustration of the dining philosophers problem.
322
occurs sooner increases as the thinking times are decreased relative to the eating times.
323
not yet been served, and the third representing the server. Both the inter-arrival times of customers and
the service times at the server are exponentially distributed, which of course is what makes this an M/
M/l queue.
This demo makes use of basic rendezvous, conditional rendezvous and time. By varying the rates
for the customer arrivals and service times, and varying the length of the buffer, you can see various
trade-offs. For example if the buffer length is too short, customers may arrive that cannot be stored and
so are missed. Similarly if the service rate is faster than the customer arrival rate, then the server could
spend a lot of time idle.
Another example demonstrates how pausing and resumption works. The setup is exactly the same
printer ramp
1 1 I i sieve(2)
4 2 ll
—3——»
4 )—
—4——» siev 5(3)
—5— ■l
5 »
—6——►
—7——» sieve(5)
—8-—> 7 »
—9-—»
9 p
— 10-—►
— 11-—►
— 12-—» 11 » 1l
11 »
sieve(11)
ii
13 »
13 *
i r i r 1 r i ' 1 r i ' 1 r
FIGURE 17.5. Illustration of Sieve ofEratosthenes for obtaining first six primes.
324
as in the M/M/l demo, except that the thread executing the model calls pause() on the director as soon
as the model starts executing. It then waits two seconds, as arbitrary choice, and then calls resume().
The purpose of this demo is to show that the pausing and resuming of a model does not affect the
model results, only its rate of progress. The ability to pause and resume a model is primarily intended
for the user interface.
17.4.1 Rendezvous
Since the ports contain CSPReceivers, the basic communication statements send() and get() will
have rendezvous semantics. Thus the fact that a rendezvous is occurring on every communication is
transparent to the actor code.
A sample template for executing a CDO is shown in figure 17.7. The code for the buffer described
in figure 17.7 is shown in figure 17.8. In creating the ConditionalSend and ConditionalReceive
branches, the first argument represents the guard. The second and third arguments represent the port
and channel to send or receive the message on. The fourth argument is the identifier assigned to the
branch. The choice of placing the guard in the constructor was made to keep the syntax of using
guarded communication statements to the minimum, and to have the branch classes resemble the
Customers
arriving server
325
guarded communication statements they represent as closely as possible. This can give rise to the case
where the Token specified in a ConditionalSend branch may not yet exist, but this has no effect
because once the guard is false, the token in a ConditionalSend is never referenced.
The other option considered was to wrap the creation of each branch as follows:
if (guard) {
// create branch and place in branches array
} else {
// branches array entry for this branch is null
}
However this leads to longer actor code, and what is happening is not as syntactically obvious.
The code for using a CIF is similar to that in figure 17.7 except that the surrounding while loop is
omitted and the case when the identifier returned is -1 does nothing. At some stage the steps involved
in using a CIF or a CDO may be automated using a pre-parser, but for now the user must follow the
approach described above.
It is worth pointing out that if most channels in a model are buffered, it may be worthwhile consid-
ering implementing the model in the PN domain which implicitly has an unbounded buffer on every
channel. Also, if modeling time is the principal concern, the model builder should consider using the
DE domain.
17.4.3 Time
If a process wishes to use time, the actor representing it must derive from CSPActor. As explained
in section 17.2.4, each process in the CSP domain is able to delay itself, either for some period from
the current model time or until the next occasion time deadlock is reached at the current model time.
// step 2:
int result chooseBranch(branches);
// step 3:
if (result == 0) (
// execute statements associated with first branch
} else if (result == 1) {
// execute statements associated with second branch.
} else if ... // continue for each branch ID
326
The two methods to call are delay() and waitForDeadlock(). Recall that if a process delays itself for
zero time from the current time, the process will continue immediately. Thus delay(O.O) is not equiva-
lent to waitForDeadlock()
If no processes are delayed, it is also possible to set the model time by calling the method setCur-
rentTimeO on the director. However, this method can only be called when no processes are delayed,
because the state of the model may be rendered meaningless if the model time is advanced to a time
beyond the earliest delayed process. This method is present primarily for composing CSP with other
domains.
As mentioned in section 17.2.4, as far as each process is concerned, time can only increase while it
is blocked waiting to rendezvous or when delaying. A process can be aware of the current model time,
but it should only ever affect the model time by delaying its execution, thus forcing time to advance.
The method setCurrentTime() should never be called from a process.
By default every model in the CSP domain is timed. To use CSP without a notion of time, do not
use the delay() method. The infrastructure supporting time does not affect the model execution if the
delayO method is not used.
// step 2
int successfulBranch = chooseBranch(branches);
// step 3
if (successfulBranch == 0) {
_size++;
Jbuffer[_writeTo] = branches [0] .getTokenO ;
_writeTo = ++_writeTo % depth;
} else if (successfulBranch ==1) {
_size--;
_readFrom = ++_readFrom % depth;
} else if (successfulBranch == -1) {
// all guards false so exit CDO
// Note this cannot happen in this case
continueCDO = false;
} else {
throw new TerminateProcessException(getName() + ": " +
"branch id returned during execution of CDO.");
FIGURE 17.8. Code used to implement the buffer process described in figure 17.7.
327
CSPReceivers in the ports gives a model CSP semantics. The CSP domain associates each channel
with exactly one receiver, located at the receiving end of the channel. Thus any process that sends or
receives to any channel will rendezvous at a CSPReceiver. Figure 17.9 shows the static structure dia-
gram of the five main classes in the CSP kernel, and a few of their associations. These are the classes
that provide all the infrastructure needed for a CSP model.
CSPDirector: This gives a model CSP semantics. It takes care of starting all the processes and con-
trols/responds to both real and time deadlocks. It also maintains and advances the model time when
necessary.
CSPReceiver: This ensures that communication of messages between processes is via rendezvous.
CSPActor: This adds the notion of time and the ability to perform conditional communication.
ConditionalReceive, ConditionalSend: This is used to construct the guarded communication state-
ments necessary for the conditional communication constructs.
director.initialize 0 =>
create a thread for each actor
update count of active processes with the director
call initialize!) on each actor
director.postfire 0 =>
return a boolean indicating if the execution of the model should continue for another iteration
FIGURE 17.10. Sequence of steps involved in setting up and controlling the model.
328
«Interface»
ConditionalBnnchActor
+getCondrtionalBranchControllert): CondttionalBranchController
A~
CSPActor
-_conditionalBranchController; ConditionalBranchController
-„delayed: boolean
_actorsBlocked: int -JntemalLock: Object
_actorsDelayed : int ♦CSPActorO
_currentTime: double +CSPActor(ws: Workspace)
_delayedActofList: UnkedList +CSPActor(cont: CompositeActor, name : String)
jnutationsPending: bootean +chooseBranch(branches : CondttionalBranchQ): int
simulationUntimed: bootean +delay()
+delay(derta: double)
+CSPDirectorO
+CSPDirector(name: String) +terminateO
+CSPDirector(name: String, ws : Workspace) *_continueO
+getCurrentTime(): double »_waitForOeadlockQ
+setCurrentTime(newTime: double)
0..n
+setUntimed(value: boolean)
*_actorBlocked()
#_actorDelayed(delta : double, actor: CSPActor) Conditional Branchcontroller
»_actorUnblockedQ
■_blocked: boolean
•_branchesActive: int
■JaranchesBlocked: int
■_branchesDelayed: int
-_branchTrying: int
•_intemalLock: Object
-„successful Branch : int
CSPReeeiver -_threadList: Linkedüst
+ConditionalBranchController(container: Actor)
■_condrtionalReceiveWaiting : boolean +chooseBranch(branches: ConditionaJBranchfl): int
-_conditlonalSendWarting: boolean +terminate()
■_container: lOPort #_branchBlocked()
■_getWaiting: bootean #_branchFailed{branchNumber: int)
•_putWaiting: boolean #_branchSucceeded(branchNumber: int)
-_otherParent: CSPActor #_branchUnblockedO
„rendezvousComptete: bootean #_isBranchFirst(branchNumber: int): boolean
■_simulationPau5ed: boolean #_releaseFirst(branchNumber; int)
-_simulationFinished: boolean
- token: Token
+CSPReceiverO
ConditionalBranch
+CSPReceiver(p: lOPort)
+getO: Token
+put(token: Token) -_alive: boolean
+getContainer(): Nameable ■_branchNumber: int
■HiasRoomO: boolean -jguard: boolean
+hasToken(): boolean -_parerrt: CSPActor
+setContainen;parent: lOPort) #_receiver: CSPReeeiver
+setFinish() »token: Token
+setPause(newValue: boolean) +ConditionalBranch(guard: boolean, port : lOPort, branchID: int)
«LgetOtherParentO: CSPActor +getlD(>: int
#JsConditionalReceiveWaiting(): boolean ♦getGuardO: boolean
iHsCondrtionalSendWaitingO: boolean ♦getParentO: CSPActor
#_isGetWaitingO: boolean +getReceiver(): CSPReeeiver
#_isPutWaitlngO: boolean ♦getTokenO: Token
#_setConditionalRecieve(v: boolean, parent: CSPActor) -HsAliveO: boolean
#_setConditionalSend(v: boolean, parent: CSPActor) +setAlive(value: boolean)
iTcheckAndWaitO *_checkAndWait()
CondHionalSend ConditionalReceive
+ConditionalSend(guard : boolean, port: lOPort, channel: int, id : int, t: Token) +ConditionalReceive(guard : boolean, port: lOPort, channel: int, id : int)
+runQ +runQ
FIGURE 17.9. Static structure diagram for classes in the CSP kernel.
329
• the number of active processes which are threads that have started but have not yet finished
• the number of blocked processes which is the number of processes that are blocked waiting to ren-
dezvous, and
• the number of delayed processes, which is the number of processes waiting for time to advance
plus the number of processes waiting for time deadlock to occur at the current model time.
When the number of blocked processes equals the number of active processes, then real deadlock
has occurred and the fire method of the director returns. When the number of blocked plus the number
of delayed processes equals the number of active processes, and at least one process is delayed, then
time deadlock has occurred. If at least one process is delayed waiting for time deadlock to occur at the
current model time, then the director wakes up all such processes and does not advance time. Other-
wise the director looks at its list of processes waiting for time to advance, chooses the earliest one and
advances time sufficiently to wake it up. It also wakes up any other processes due to be awakened at
the new time. The director checks for deadlock each occasion a process blocks, delays or dies.
For the director to work correctly, these three counts need to be accurate at all stages of the model
execution, so when they are updated becomes important. Keeping the active count accurate is rela-
tively simple; the director increases it when it starts the thread, and decreases it when the thread dies.
Likewise the count of delayed processes is straightforward; when a process delays, it increases the
count of delayed processes, and the director keeps track of when to wake it up. The count is decreased
when a delayed process resumes.
However, due to the conditional communication constructs, keeping the blocked count accurate
requires a little more effort. For a basic send or receive, a process is registered as being blocked when
it arrives at the rendezvous point before the matching communication. The blocked count is then
decreased by one when the corresponding communication arrives. However what happens when an
actor is carrying out a conditional communication construct? In this case the process keeps track of all
of the branches for which the guards were true, and when all of those are blocked trying to rendezvous,
330
it registers the process as being blocked. When one of the branches succeeds with a rendezvous, the
process is registered as being unblocked.
331
17.6 Technical Details
17.6.1 Brief Introduction to Threads in Java
The CSP domain, like the rest of Ptolemy II, is written entirely in Java and takes advantage of the
features built into the language. In particular, the CSP domain depends heavily on threads and on mon-
itors for controlling the interaction between threads. In any multi-threaded environment, care has to be
taken to ensure that the threads do not interact in unintended ways, and that the model does not dead-
lock. Note deadlock in this sense is a bug in the modeling environment, which is different from the
deadlock talked about before which may or may not be a bug in the model being executed.
A monitor is a mechanism for ensuring mutual exclusion between threads. In particular if a thread
has a particular monitor, acquired in order to execute some code, then no other thread can simulta-
neously have that monitor. If another thread tries to acquire that monitor, it stalls until the monitor
becomes available. A monitor is also called a lock, and one is associated with every object in Java.
Code that is associated with a lock is defined by the synchronized keyword. This keyword can
either be in the signature of a method, in which case the entire method body is associated with that
lock, or it can be used in the body of a method using the syntax:
synchronized(object) {
// synchronized code goes here
}
This causes the code inside the brackets to be associated with the lock belonging to the specified
object. In either case, when a thread tries to execute code controlled by a lock, it must either acquire
the lock or stall until the lock becomes available. If a thread stalls when it already has some locks,
those locks are not released, so any other threads waiting on those locks cannot proceed. This can lead
to deadlock when all threads are stalled waiting to acquire some lock they need.
A thread can voluntarily relinquish a lock when stalling by calling object. wait() where object is the
object to relinquish and wait on. This causes the lock to become available to other threads. A thread
can also wake up any threads waiting on a lock associated with an object by calling notifyAll() on the
object. Note that to issue a notifyAH() on an object it is necessary to own the lock associated with that
object first. By careful use of these methods it is possible to ensure that threads only interact in
intended ways and that deadlock does not occur.
Approaches to locking used in the CSP domain.
One of the key coding patterns followed is to wrap each wait() call in a while loop that checks some
flag. Only when the flag is set to false can the thread proceed beyond that point. Thus the code will
often look like
synchronized(object) {
while(flag) {
object.wait();
}
332
The advantage to this is that it is not necessary to worry about what other thread issued the notifyAll()
on the lock; the thread can only continue when the notifyAll() is issued and the flag has been set to
false.
Another approach used is to keep the number of locks acquired by a thread as few as possible,
preferably never more than one at a time. If several threads share the same locks, and they must
acquire more than one lock at some stage, then the locks should always be acquired in the same order.
To see how this prevent deadlocks, consider two threads, threadl and thread!, that are using two locks
A and B. If threadl obtains A first, then B, and thread2 obtains B first then A, then a situation could
arise whereby threadl owns lock A and is waiting on B, and thread2 owns lock B and is waiting on A.
Neither thread can proceed and so deadlock has occurred. This would be prevented if both threads
obtained lock A first, then lock B. This approach is sufficient, but not necessary to prevent deadlocks,
as other approaches may also prevent deadlocks without imposing this constraint on the program [44].
Finally, deadlock often occurs even when a thread, which already has some lock, tries to acquire
another lock only to issue a notifyAH() on it. To avoid this situation, it is easiest if the notifyAll() is
issued from a new thread which has no locks that could be held if it stalls. This is often used in the CSP
domain to wake up any threads waiting on receivers, for example after a pause or when terminating the
model. The class Notify Thread, in the ptolemy.actor.process package, is used for this purpose. This
class takes a list of objects in a linked list, or a single object, and issues a notifyAll() on each of the
objects from within a new thread.
The CSP domain kernel makes extensive use of the above patterns and conventions to ensure the
modeling engine is deadlock free.
333
receiver (including the get), sets the rendezvousComplete flag to false and then waits on the
receiver while the rendezvousComplete flag is false,
(3) The thread executing the get() wakes up, sees that a put() has arrived, sets the rendezvousCom-
plete flag to true, wakes up any threads waiting on the receiver, and returns thus releasing the lock.
The thread executing the put() then wakes up, acquires the receiver lock, sees that the rendezvous
is complete and returns.
Following the rendezvous, the state of the receiver is exactly the same as before the rendezvous
Wakes up
CondSend if one
is waiting
334
arrived, and it is ready to mediate another rendezvous. It is worth noting that the final step, of making
sure the second communication to arrive does not return until the rendezvous is complete, is necessary
to ensure that the correct token gets transferred. Consider the case again when a get() arrives first,
except now the put() returns immediately if a get() is already waiting. A put() arrives, places a token in
the receiver, sets the get waiting flag to false and returns. Now suppose another put() arrives before the
get() wakes up, which will happen if the thread the put() is in wins the race to obtain the lock on the
receiver. Then the second put() places a new token in the receiver and sets the put waiting flag to true.
Then the get() wakes up, and returns with the wrong token! This is known as a race condition, which
will lead to unintended behavior in the model. This situation is avoided by our design.
* +
rendezvous
I
FIGURE 17.13. Conceptual view of how conditional communication is built on top of rendezvous.
335
Algorithm used by each branch:
Similar to the approach followed for rendezvous, the algorithm by which a thread representing a
branch determines whether or not it can proceed is entirely symmetrical for a ConditionalSend and a
ConditionalReceive. The algorithm followed by a ConditionalReceive is shown figure 17.14. Again
the locking point is the receiver, and all code concerned with the communication is synchronized on
the receiver. The receiver is also where all necessary flags are stored.
Consider three cases.
(1) a ConditionalReceive arrives and a put is waiting.
In this case, the branch checks if it is the first branch to be ready to rendezvous, and if so, it is goes
Case 1 Case 3 Case 2
FIGURE 17.14. Algorithm used to determine if a conditional rendezvous branch succeeds or fails
336
ahead and executes a get. If it is not the first, it waits on the receiver. When it wakes up, it checks
if it is still alive. If it is not, it registers that it has failed and dies. If it is still alive, it starts again by
trying to be the first branch to rendezvous. Note that a put cannot disappear.
(2) a conditionalReceive arrives and a conditionalSend is waiting
When both sides are conditional branches, it is up to the branch that arrives second to check
whether the rendezvous can proceed. If both branches are the first to try to rendezvous, the condi-
tionalReceive executes a get(), notifies its parent that it succeeded, issues a notifyAH() on the
receiver and dies. If not, it checks whether it has been terminated by chooseBranch(). If it has, it
registers with chooseBranch() that it has failed and dies. If it has not, it returns to the start of the
algorithm and tries again. This is because a ConditionalSend could disappear. Note that the parent
of the first branch to arrive at the receiver needs to be stored for the purpose of checking if both
branches are the first to arrive.
This part of the algorithm is somewhat subtle. When the second conditional branch arrives at the
rendezvous point it checks that both sides are the first to try to rendezvous for their respective pro-
cesses. If so, then the conditionalReceive executes a get(), so that the conditionalSend is never
aware that a conditionalReceive arrived: it only sees the get().
(3) a conditionalReceive arrives first.
It sets a flag in the receiver that it is waiting, then waits on the receiver. When it wakes up, it
checks whether it has been killed by chooseBranch. If it has, it registers with chooseBranch that it
has failed and dies. Otherwise it checks if a put is waiting. It only needs to check if a put is waiting
because if a conditionalSend arrived, it would have behaved as in case (2) above. If a put is wait-
ing, the branch checks if it is the first branch to be ready to rendezvous, and if so it is goes ahead
and executes a get. If it is not the first, it waits on the receiver and tries again.
337
FIGURE 17.15. Modification of rendezvous algorithm, section 17.6.4, shown in ellipse.
338
DDE Domain
Author: John S. Davis II
18.1 Introduction
The distributed discrete event (DDE) model of computation incorporates a distributed notion of
time into a dataflow style of communication. Time progresses in a DDE model when the actors in the
model execute and communicate. Actors in a DDE model communicate by sending messages through
bounded, FIFO channels. Time in a DDE model is distributed and localized, and the actors of a DDE
model each maintain their own local notion of the current time. Local time information is shared
between two connected actors whenever a communication between said actors occurs. Conversely,
communication between two connected actors can occur only when constraints on the relative local
time information of the actors are adhered to.
The DDE domain is based on distributed discrete event processing and leverages a wealth of
research devoted to this topic. Several tutorial publications on this topic exist in [18][24][40][61]. The
DDE domain implements a specific variant of distributed discrete event systems (DDES) as
expounded by Chandy and Misra [18]. While the DDE domain has similarities with DDES, the distrib-
uted discrete event domain serves as a framework for studying DDES with two special emphases. First
we consider DDES from a dataflow perspective; we view DDE as an implementation of the Kahn data-
flow model [42] with distributed time added on top. Second we study DDES not with the goal of
improving execution speed (as has been the case traditionally). Instead we study DDES to learn its use-
fulness in modeling and designing systems that are timed and distributed.
339
communication of null messages that consist solely of local time information.
340
that contains multiple input queues with identical receiver times. To accommodate this situation, each
actor assigns a unique priority to each input queue. An actor can consume a token from a queue if no
other queue has a lower receiver time and if all queues that have an identical receiver time also have a
lower priority.
Each receiver has a completion time that is set during the initialization of a model. The completion
time of the receiver specifies the time after which the receiver will no longer operate. If the time stamp
of the oldest token in a receiver exceeds the completion time, then that receiver will become inactive.
Actor B
> >
Actor A Actor D > Actor E
> >
• • • •
Actor C 3.0
8.0 5.0 2.0
341
upper input that depends on the output of 5!
Preventing Feedforward Timed Deadlock. To address feedforward timed deadlock, null tokens are
employed. A null token provides an actor with a means of communicating time advancement even
though data (real tokens) are not being transmitted. Whenever an actor consumes a token, it places a
null token on each of its output queues such that the time stamp of the null token is equal to the current
time of the actor. Thus, if actor A of figure 18.2, produced a token on its lower output queue at time
5.0, it would also produce a null token on its upper output queue at time 5.0.
If an actor encounters a null token on one of its input queues, then the actor does the following.
First it consumes the tokens of all other input queues it contains given that the other input queues have
receiver times that are less than or equal to the time stamp of the null token. Next the actor removes the
null token from the input queue and sets its current time to equal the time stamp of the null token. The
actor then places null tokens time stamped to the current time on all output queues that have a last time
that is less then the actor's current time. As an example, if B in figure 18.2 consumes a null token on its
input with a time stamp of 5.0 then it would also produce a null token on its output with a time stamp
of 5.0.
The result of using null tokens is that time information is evenly propagated through a model's
topology. The beauty of null tokens is that they inform actors of inactivity in other components of a
model without requiring centralized dissemination of this information. Given the use of null tokens,
feedforward timed deadlock is prevented in the execution of DDE models. It is important to recognize
that null tokens are used solely for the purpose of avoiding deadlocks. Null tokens do not represent any
actual components of the physical system being modeled. Hence, we do not think of a null token as a
real token. Furthermore, the production of a null token that is the direct result of the consumption of a
null token is not considered computation from the standpoint of the system being modeled. The idea of
null tokens was first espoused by Chandy and Misra [18].
Preventing Feedback Timed Deadlock. We address feedback timed deadlock as follows. All feedback
loops are required to have a cumulative time stamp increment that is greater than zero. In other words,
feedback loops are required to contain delay actors. Peacock, Wong and Manning [70] have shown that
a necessary condition for feedback timed deadlock is that a feedback loop must contain no delay
actors. The delay value (delay = output time - current time) of a delay actor must be chosen wisely; it
must be less then the smallest delta time of all other actors contained in the same feedback loop. Delta
time is the difference between the time stamps of a token that is consumed by an actor and the corre-
sponding token that is produced in direct response. If a system being modeled has characteristics that
prevent a fixed, positive lower bound on delta time from being specified, then our approach can not
solve feedback timed deadlock. Such a situation is referred to as a Zeno condition. An application
involving an approximated Zeno condition is discussed in section 18.3 below.
The DDE software architecture provides one delay actor for use in preventing feedback timed
deadlock: FeedBackDelay. See "Feedback Topologies" on page 18-345 for further details about this
8.0 5.0
342
actor.
Upper Branch
ZenoDelay
343
will not advance beyond a certain point even though the actors of the feedback loop continue to exe-
cute without deadlocking. ZenoDelay extends FeedBackDelay and is designed so that a Zeno condi-
tion will be encountered. When execution of the model begins, both FeedBackDelay and ZenoDelay
are used to feed back null tokens into Wire so that the model does not deadlock. After local time
exceeds a preset value, ZenoDelay reduces its delay so that the lower branch approximates a Zeno con-
dition.
In centralized discrete event systems, Zeno conditions prevent progress in the entire model. This is
true because the feedback cycle experiencing the Zeno condition prevents time from advancing in the
entire model. In contrast, distributed discrete event systems localize Zeno conditions as much as is
possible based on the topology of the system. Thus, a Zeno condition can exist in the lower branch and
the upper branch will continue its execution unimpeded. Localizing Zeno conditions can be useful in
large scale modeling in which a Zeno condition may not be discovered until a great deal of time has
been invested in execution of the model. In such situations, partial data collection may proceed prior to
correction of the delay error that resulted in the Zeno condition.
18.4.1 DDEActor
The DDE model of computation makes one very strong assumption about the execution of an
actor: all input ports of cm actor operating in a DDE model must be regularly polled to determine
which input channel has the oldest pending event. Any actor that adheres to this assumption can oper-
ate in a DDE model. Thus, many polymorphic actors found in ptolemy/actor/[lib, gui] are suitable for
operation in DDE models. For convenience, DDEActor was developed to simplify the construction of
actors that have DDE semantics. DDEActor has three key methods as follows:
getNextTokenQ. This method polls each input port of an actor and returns the (non-Null) token that
represents the oldest event. This method blocks accordingly as outlined in section 18.2.1 (Communi-
cating Time).
getLastPortO- This method returns the input IOPort from which the last (non-Null) token was con-
sumed. This method presumes that getNextToken() is being used for token consumption.
18.4.2 DDEIOPort
DDEIOPort extends TypedlOPort with parameters for specifying time stamp values of tokens that
are being sent to neighboring actors. Since DDEIOPort extends TypedlOPort, use of DDEIOPorts will
not violate the type resolution protocol. DDEIOPort is not necessary to facilitate communication
between actors executing in a DDE model; standard TypedlOPorts are sufficient in most communica-
tion. DDEIOPorts become useful when the time stamp to be associated with an outgoing token is
344
greater than the current time of the sending actor. Hence, DDEIOPorts are only useful in conjunction
with delay actors (see "Enabling Communication: Advancing Time" on page 18-340, for a definition
of delay actor). Most polymorphic actors available for Ptolemy II are not delay actors.
Actor B i
Actor D
Actor A Actor C /N
345
DDEThread. DDEThreads contain a TimeKeeper that manages the local notion of time that is associ-
ated with the DDEThread's actor.
#JastTime: double
#_priority: int +DDEThread(a : Actor, d : ProcessDirector)
completionTime: double +getTimeKeeper(): TimeKeeper
rcvrTime: double +noticeOfTermination()
+PrioritizedTimedQueue() +start()
+PrioritizedTimedQueue(p: lOPort) +wrapup{)
+PrioritizedTimedQueue(cntr: lOPort, priority : int)
+get(): Token 1.1 controls
+getCapacity(): int manages time for
+getContainer(): lOPort TimeKeeper
+getLastTime(): double O.n 1..1
+getRcvrTime(): double -_currentTime: double
+hasRoom(): boolean -jxitputTime: double
+hasToken(): boolean 1.1
+TimeKeeper(a: Actor): void
+put(token: Token, time: double) +getCurrentTime(): double
+removelgnoredTokens(): void +getFirstRcvr(): TimedQueueReceiver
+reset(): void AtomlcActor +getNextTime{): double
+setCapacily(c: int) +getOutputTime{): double
+setContainer(p: lOPort) +removeAlllgnoredTokens(): void
#_getCompletionTime(): double +sendOutNuUTokens(): void
#JiasNullToken(): boolean +setCurrentTime(time: double): void
#_setCompletionTime(t: double) contains 0..1 ■HjpdateRcvrListfrcvr: TimedQueueReceiver): void
#_setRcvrTime(time: double) #_setOutpuffime(time : double): void
#_setRcvrPriorities(): void
contains
O.n
lOPort
RcvrComparator
+RcvrComparator(keeper: TimeKeeper)
+compare(first: Object, second : Object): int
DDEReceiver
346
methods are prefire(), fire() and postfire(); ProcessThreads also invoke wrapup() on the actors they
control.
DDEThread extends the functionality of ProcessThread. Upon instantiation, a DDEThread creates
a TimeKeeper object and assigns this object to the actor that it controls. The TimeKeeper gets access to
each of the DDEReceivers that the actor contains. Each of the receivers can access the TimeKeeper
and through the TimeKeeper the receivers can then determine their relative receiver times. With this
information, the receivers are fully equipped to apply the appropriate blocking rules as they get and put
time stamped tokens.
DDEReceivers use a dynamic approach to accessing the DDEThread and TimeKeeper. To ensure
domain polymorphism, actors (DDE or otherwise) do not have static references to the TimeKeeper and
DDEThread that they are controlled by. To ensure simplified mutability support, DDEReceivers do not
have a static reference to TimeKeepers. Access to the local time management facilities is accom-
plished via the Java Thread.currentThread() method. Using this method, a DDEReceiver dynamically
accesses the thread responsible for invoking it. Presumably the calling thread is a DDEThread and
appropriate steps are taken if it is not. Once the DDEThread is accessed, the corresponding Time-
Keeper can be accessed as well. The DDE domain uses this approach extensively in DDERe-
ceiver.put(Token) and DDEReceiver.get().
DDEReceiver.put(Token) is derived from the Receiver interface and is accessible by all actors and
domains. To facilitate local time advancement, DDEReceiver has a second put() method that has a
time argument: DDEReceiver.put(Token, double). This second DDE-specific version of put() is taken
advantage of without extensive code by using Thread.currentThread(). DDEReceiver.put() is shown
below:
public void put(Token token)1 {
Thread thread = Thread. currentThread (') ;
double time = _lastTime;
if( thread instanceof DDEThread ) {
TimeKeeper timeKeeper = ((DDEThread)thread).getTimeKeeper();
time = timeKeeper.getOutputTime();}
put( token, time )2,-
}
Similar uses of Thread.currentThread() are found throughout DDEReceiver and DDEDirector. Note
that while Thread.currentThread() can be quite advantageous, it means that if some methodsa are
called by an inappropriate thread, problems may occur. Such an issue makes code testing difficult.
347
methods. The current release of DDE assumes that actors that execute according to a DDE model of
computation are atomic rather than composite. In a future Ptolemy II release, composite actors will be
facilitated in the DDE domain. At that time, it will be important to distinguish internal and external
read blocks. Until then, only internal read blocks are in use.
Typed lOPort
I ProcassOiractor
-oL
♦stopTime: Parameter
readBlocks: irrt
- writeBlocks: Hit
♦DDEDirectonO
.DDEDirectorfwkspace: Workspace)
♦DDEDirectoitcntr: CompositeActor, nm : String)
♦hasMutationO: boolean
DDEIOPortO
*_addExtemalRead8lockO
«_addlntemalReadBlockO +DDEIOPort(cntr: ComponentEntity, nm : String)
*_addWriteBlock(rcvr: OOEReceiver) +DDEIOPort(entr: ComponentEntity, nm : String, in : boolean, out: boolean)
+bfoadcast(token : Token, sendTime : double)
y.getlnKiammeTabfeO: Hashtable
+send(chan : irrt, token : Token, sendTime : double)
"_removeExtemalReadBlockO
«_removelntema!ReadBlockO
"_ramoveWriteBlockO
KJncrementLowestCapacity Porto I
Typed AtomlcActori
*_performMutationsO
I
T
-_currentTime : double
FeedBackDelay •JastPort: TypedlOPort
+DDEActorO
♦nullDelay: Parameter
+DDEActor(ws: Workspace)
♦realDelay: Parameter
+DDEActor(cntr: TypedCompositeActor. nm : String)
delay: double
♦getLastPortO : TypedlOPort
+FeedBackDelayO ♦getNextTokenO: Token
+FeedBackDelay(wkspc: Workspace) »_getNextlnputQ: Token
+FeedBackDelay(cntr: TypedComposReActor, nm : String)
+getDelayO: double
+setDelay(delay: double)
348
18.6 Technical Details
18.6.1 Synchronization Hierarchy
Previously we have discussed in great detail the notion of timed and non-timed deadlock. Separate
from these notions is a different kind of deadlock that can be inherent in a modeling environment if the
environment is not designed properly. This notion of deadlock can occur if a system is not thread safe.
Given the extensive use of Java threads throughout Ptolemy II, great care has been taken to ensure
thread safety; we want no bugs to exist that might lead to deadlock based on the structure of the
Ptolemy II modeling environment. Ptolemy II uses monitors to guarantee thread safety. A monitor is a
method for ensuring mutual exclusion between threads that both have access to a given portion of
code. To ensure mutual exclusion, threads must acquire a monitor (or lock) in order to access a given
portion of code. While a thread owns a lock, no other threads can access the corresponding code.
There are several objects that serve as locks in Ptolemy II. In the process domains, there are four
primary objects upon which locking occurs: Workspace, ProcessReceiver, ProcessDirector and Atomi-
c Actor. The danger of having multiple locks is that separate threads can acquire the locks in competing
orders and this can lead to deadlock. A simple illustration is shown in figure 18.8. Assume that both
lock^ and lock B are necessary to perform a given set of operations and that both thread 1 and thread
2 want to perform the operations. If thread 1 acquires A and then attempts to acquire B while thread 2
does the reverse, then deadlock can occur.
There are several ways to avoid the above problem. One technique is to combine locks so that
large sets of operations become atomic. Unfortunately this approach is in direct conflict with the whole
purpose behind multi-threading. As larger and larger sets of operations utilize a single lock, the limit of
the corresponding concurrent program is a sequential program!
Another approach is to adhere to a hierarchy of locks. A hierarchy of locks is an agreed upon order
in which locks are acquired. In the above case, it may be enforced that lock A is always acquired before
lock B. A hierarchy of locks will guarantee thread safety [44].
The process domains have an unenforced hierarchy of locks. It is strongly suggested that users of
Ptolemy II process domains adhere to this suggested locking hierarchy. The hierarchy specifies that
locks be acquired in the following order:
Workspace > ProcessReceiver > ProcessDirector > AtomicActor
The way to apply this rule is to prevent synchronized code in any of the above objects from making a
call to code that is to the left of the object in question.
Thread 1
o Lock A
o LockB
I, Thread 2
349/350
PN Domain
Author: Mudit Goel
19A Introduction
The process networks (PN) domain in Ptolemy II models a system as a network of processes that
communicate with each other by passing messages through unidirectional first-in-first-out (FIFO)
channels. A process blocks when trying to read from an empty channel until a message becomes avail-
able on it. This model of computation is deterministic in the sense that the sequence of values commu-
nicated on the channels is completely determined by the model. Consequently, a process network can
be evaluated using a complete parallel or sequential schedule and every schedule inbetween, always
yielding the same output results for a given input sequence.
PN is a natural model for describing signal processing systems where infinite streams of data sam-
ples are incrementally transformed by a collection of processes executing in parallel. Embedded signal
processing systems are good examples of such systems. They are typically designed to operate indefi-
nitely with limited resources. This behavior is naturally described as a process network that runs for-
ever but with bounded buffering on the communication channels whenever possible.
PN can also be used to model concurrency in the various hardware components of an embedded
system. The original process networks model of computation can model the functional behavior of
these systems and test them for their functional correctness, but it cannot directly model their real-time
behavior. To address the involvement of time, we have extended the PN model such that it can include
the notion of time.
Some systems might display adaptive behavior like migrating code, agents, and arrivals and depar-
tures of processes. To support this adaptive behavior, we provide a mutation mechanism that supports
addition, deletion, and changing of processes and channels. With untimed PN, this might display non-
determinism, while with timed-PN, it becomes deterministic.
The PN model of computation is a superset of the synchronous dataflow model of computation
(see the SDF Domain chapter). Consequently, any SDF actor can be used within the PN domain. Simi-
larly any domain-polymorphic actor can be used in the PN domain. Very different from SDF, a sepa-
351
rate process is created for each of these actors. These processes are implemented as Java threads [66].
The software architecture for PN is described in section 19.3 and the finer technical details are
explained in section 19.4.
352
detection of an artificial deadlock by increasing the capacity of the channels on which processes are
blocked on a write. In particular, Parks chooses to increase only the capacity of the channel with the
smallest capacity among the channels on which processes are blocked on a write to keep overall
required memory in the channels to a minimum.
19.2.3 Time
In real-time systems and embedded applications, the real time behavior of a system is as important
as the functional correctness. Developers can use process networks to test the functional correctness of
applications, but it lacks the notion of time. One solution is that the developer uses some other timed
model of computation, such as DE, for testing their timing behavior. Another solution is to extend the
process networks model of computation with time, as we have done in Ptolemy II. This extension is
based on the Pamela model [27], which is originally developed for performance modeling of parallel
systems using Dykstra's semaphores.
In the timed PN domain, time is global. That is, all processes in a model share the same time,
referred to as the current time or model time. A process can explicitly wait for time to advance, by
delaying itself for some period from the current time. When a process delays itself, it is suspended
until time has sufficiently advanced, at which stage it wakes up and continues to execute. If the process
delays itself for zero time the process simply continues to execute.
In the timed PN domain, time changes only at specific moments and never during the execution of
a process. The time a process observes, can only advance when it is in one of the following two states:
1. The process is delayed and is explicitly waiting for time to advance (delay block).
2. The process is waiting for data to arrive on one of its input channels (read block).
The global time advances when all processes are blocked on either a delay or on a read from a
channel with at least one process delayed. This state of the program is called a timed deadlock. The
fact that at least one process is delayed, distinguishes the timed deadlock from other deadlocks. In case
of a timed deadlock, the current time is advanced until at least one process is woken up.
19.2.4 Mutations
The PN domain tolerates mutations, which are run-time changes in the model structure. Normally,
mutations are realized as change requests queued with the director or manager. In PN there is no deter-
minate point where mutations can occur. The only determinate point is a read deadlock. However, per-
forming mutations at this point is unlikely as a real deadlock might never occur. For example, a model
with even one non-terminating source never experiences a real deadlock. Therefore, mutations are per-
formed as soon as they are requested (if they are queued with the director) and when real deadlock
occurs (if they are queued with the manager). As we do not know when these requests are served, the
process network can be in different schedule states when mutations are performed,. This introduces
non-determinism in PN. The details of implementations are presented later in section 19.3.
In timed PN, however, requests for mutations are not processed until there is a timed deadlock.
Because occurrence of a timed deadlock is determinate, mutations in timed PN are determinate.
353
19.3 The PN Software Architecture
19.3.1 PN Domain
The PN domain kernel is realized in package ptolemy.domains.pn.kernel. A UML static structure
diagram of the architecture used to realize the PN domain is shown in figure 19.1 (see appendix A of
chapter 1). In the successive sections we will highlight elements from the UML diagram, thereby
explaining the implementation of the PN domain in details.
354
TimedPNDirector has two functionalities that distinguishes it from BasePNDirector. It introduces
the notion of global time to the model and it allows for deterministic mutations. Mutations are per-
formed at the earliest timed-deadlock that occurs after they are queued. Since occurrence of timed-
deadlock is deterministic, performing mutations at this point makes mutations deterministic.
Execution of Actor:
A separate thread is responsible for the execution of each actor in PN. This thread is started in the
prefireO method of the director. After starting, this thread repeatedly calls the prefire(), fire(), and post-
•Interface» :
Actor ProcessThread
■_actor: Actor
j-_director: ProcessDirector
j-_manager: Manager
~T j+ProcessThread(actor: Actor, director: ProcessDirector)
j+getActorO: Actor
j+restartThreadO
:+stopThreadO
i+wrapupO
ProcessDirector
7T _.
•Interface»
ProcsssReeenw
QueueRecelvar
♦requestFinishO
+requestPause(pause: boolean)
♦resetO .K
BasePNDirector PNQueueReceiver
+BasePNDirectorO PNQueueReceiverO
+BasePNDirector(workspace: Workspace) +PNQueueReceiver(container: lOPort)
+BasePNDirector(container: CompositeActor, name: String) +petReadBlockedActorO: Actor
+addProcessListener(listener: PNProcessListener) getWriteBlockedActorO: Actor
»removeProcessUstenerflistener: PNProcessListener) *isConnectedToBoundaryO: boolean
♦islnsideBoundaryO: boolean
T ♦isReadPendingO: boolean
♦isWritePendingO: boolean
♦IsOutsideBoundaryO: boolean
+setReadPending(readpending: boolean)
+setWrttePending<writepending: boolean)
TimedPNDirector
+TimedPNDirectorO
+PNDirectorÖ +TimedPNDirectori>ort<space: Workspace)
+PNDirector(workspace: Workspace) +TimedPNDirector(container; CompositeActor, name : String)
»PNDirector(container: ComposHeActor, name : String)
FIGURE 19.1. UML diagram for classes and methods related to the PN domain.
355
fire() methods of the actor. This sequence continues until the postfire() or the prefire() method returns
false. The only way for an actor to terminate gracefully in PN is by returning from the fire() method
and returning false in the postfire() or prefire() method of the actor. If an actor finishes execution as
above, then the thread calls the wrapup() method of the actor. Once this method returns, the thread
informs the director about the termination of this actor and finishes its own execution. This actor is not
fired again unless the director creates and starts a new thread for the actor. In addition, if the first time
an actor returns false in its prefire() method, it will never be fired in PN.
Message Passing.
Recall that in Ptolemy II, data transfer between entities is achieved using ports and the receivers
embedded in the input ports. Each receiver in an input port is capable of receiving messages from a
distinct channel.
An actor calls the send() or broadcast) method on its output port to transmit a token to a remote
actor. The port obtains a reference to a remote receiver (via the relation connecting them) and calls the
put() method of the receiver, passing a token. The destination actor retrieves this token by calling the
get() method of its input port, which in turn calls the get() method of the designated receiver.
Both the get() and send() methods of the port take an integer index to distinguish between the dif-
ferent channels it is connected to. This index specifies the channel to which the data is being sent to or
being received from. If the ports are connected to a single channel, then the index is 0. But if the port is
connected to more than one channel (a multiport), say N channels, then the index ranges from 0 to N-l.
The broadcast() method of the port does not require an index as it transmits the token to all the chan-
nels it is connected to.
In the PN domain, receivers are instances of ptolemy.domains.pn.kernel.PNQueueReceiver. These
receivers have FIFO queue semantics thus realizing the FIFO channel in a process networks graph. In
addition to this, these receivers are also responsible for implementing the blocking reads and blocking
writes. They handle this using the get() and the put() methods. These methods are as shown in figures
19.2, and 19.3.
The get() method checks if a FIFO queue has any tokens. If not, then it increases the count that
tracks the number of actors that are blocked on a read in the director. It also sets its readpending flag
to true. Then the calling thread is suspended until some actor puts a token in the FIFO queue which
causes that the readpending flag of this receiver is set to false. (This is done in the put() method as
described later.) On resuming, it reads and removes the first token from the FIFO queue. In case some
process is blocked on a write to this receiver (the FIFO queue is full to capacity), it unblocks that pro-
cess, notifies it, and returns. This method also handles the termination of the simulation as is explained
later in section 19.3.4.
The put() method of the receiver is responsible for implementing the blocking writes. This method
checks whether the FIFO queue is full to capacity, in which case it sets writepending flag to true and
informs the director that a process is blocked on a write. Next, it suspends the calling process until
some other process sets the writepending flag to false and wakes it up the process doing the put. After
this, the put method puts the token into the FIFO queue and checks if some process is blocked on a
read from this receiver. If a process is blocked on a read, it unblocks it and informs it that a new token
is now available for it to read. Then the method returns.
356
mechanism suggested in [43]. This mechanism requires keeping count of the number of threads cur-
rently active, paused, and blocked in the simulation. The number of threads that are currently active in
the graph is set by a call to the _increaseActiveCount() of the director. This method is called whenever
a new thread corresponding to an actor is created in the simulation. The corresponding method for
decreasing the count of active actors (on termination of a process) is _decreaseActiveCount() in the
director.
Whenever an actor blocks on a read from a channel, the count of actors blocked on a read is incre-
mented by calling the _informOfReadBlock() method in director. Similarly, the number of actors
blocked on a write is incremented by a call to the _informOfWriteBlock() method of the director. The
corresponding methods for decreasing the count of the actors blocked on a read or a write are
_informOfReadUnblock() and _informOfWriteUnblock(), respectively. These methods are called from
the instances of the PNQueueReceiver class when an actor tries to read from or write to a channel
Similarly, when a process queues a mutation, it informs the director by a call to the
_informOfMutationBlock().
Every time an actor blocks, the director checks for a deadlock. If the total number of actors
blocked or paused equals the total number of actors active in the simulation, a deadlock is detected. On
detection of a deadlock, if one or more actors are blocked on a write, then this is an artificial deadlock
The channel with the smallest capacity among all the channels with actors blocked on a write is chosen
and its capacity is incremented by 1. This implements the bounded memory execution as suggested by
[69]. If a real deadlock is detected at the top-level composite actor, then the manager terminates the
357
Simulation.
super.put(token);
if (_readpending) {
director._readUnblock()
_readpending = false;
notifyAlK) ;
while (_pause) {
director.increasePausedCount 0;
workspace.wait(this);
358
19.3.5 Mutations of a Graph
The PN domain in Ptolemy II allow graphs to mutate during execution. This implies that old pro-
cesses or channels can disappear from the graph and new processes and channels can be created during
the simulation.
Though other domains, like SDF, also support mutations in their graphs, there is a big difference
between the two. In domains like SDF, mutations can occur only between iterations. This keeps the
simulation determinate as changes to the topology occur only at a fixed point in the execution cycle. In
PN, the execution of a graph is not centralized, and hence, the notion of an iteration is quite difficult to
define. Thus, in PN, we let mutations happen as soon as they are requested, if they are queued with the
director rather than the manager. This is the behavior of PNDirector. (TimedPNDirector performs
mutations only when there is a timed-deadlock. Mutations in this form are deterministic.) The point in
the execution where mutations occur would normally depend on the schedule of the underlying Java
threads. Under certain conditions where the application can guarantee a fixed point in the execution
cycle for mutations, or where the mutations are localized, they can still be determined.
In case of TimedPNDirector, all mutations are deterministic as request to perform mutations is not
processed unless there is a timed deadlock. Since occurrence of a timed deadlock does not depend on
the schedule of the underlying threads, the mutations are completely deterministic.
An actor can request a mutation by creating an instance of a class derived from ptolemy.ker-
nel.event.ChangeRequest. It should override the method execute() and include the commands that it
wants to use to perform mutations in this method.
synchronized (obj) {
... //Part of code that requires exclusive lock on obj.
}
This implies that if a thread wants to access the synchronized part of the code, then it has to grab
an exclusive lock on the monitor object, obj. Also while this thread has a lock on the monitor, no
thread can access any code that is synchronized on the same monitor.
There are many actions (like mutations) that could affect the consistency of more than one object,
such as the director and receivers. Java does not provide a mechanism to acquire multiple locks simul-
taneously. Acquiring locks sequentially is not good enough as this could lead to deadlocks. For exam-
359
pie, consider a thread trying to acquire locks on objects a and b in that order. Another thread might try
to obtain locks on the same objects in the opposite order. The first thread acquires a lock on a and stalls
to acquire a lock on b, while the second thread acquires a lock on b and waits to grab a lock on a. Both
threads stall indefinitely and the application is deadlocked.
The main problem in the above example is that different threads try to acquire locks in conflicting
orders. One possible solution to this is to define an order or hierarchy of locks and require all threads to
grab the locks in the same top-down order [44]. In the above example, we could force all the threads to
acquire locks in a strict order, say a followed by b. If all the code that requires synchronization respects
this order, then this strategy can work with some additional constraints, like making the order on locks
immutable. Although this strategy can work, this might not be very efficient and can make the code a
lot less readable. Also Java does not permit an easy and straightforward way of implementing this.
We follow a similar but easier strategy in the PN domain of Ptolemy II. We define a three level
strict hierarchy of locks with the lowest level being the director, the middle level being the various
receivers and the highest level being the workspace. The rule that all threads have to respect after
acquiring their first lock is to never try acquiring a lock at a higher or at the same level as their first
lock. Specifically, a block of code synchronized on the director should not try to access a block of code
that requires a lock on either the workspace or any of the receivers. Also, a block of code synchronized
on a receiver should not try to call a block of code synchronized on either the workspace or any other
receiver.
Some discussion about these locks in PN is presented in the following section.
360
ods or blocks of code accessing and modifying the state of the receivers, are forced to get an exclusive
lock on the receiver. These blocks might call methods that require a lock on the director, but do not call
methods that require a lock on any other receiver.
The lower most lock in the hierarchy is the PNDirector object. There are some internal state vari-
ables, such as the number of processes blocked on a read, that are accessed and modified by different
threads. For this, the code that modifies any internal state variable should not let more than one thread
access these variables at the same time. Since access to these variables is limited to the methods in
director, the blocks of code modifying these state variables obtain an exclusive lock on the director
itself. These blocks should not try to access any block of code that requires an exclusive lock on the
receivers or requires a read or a write access on the workspace.
361
References
[I] G. A. Agha, Actors: A Model of Concurrent Computation in Distributed Systems, MIT Press,
Cambridge, MA, 1986.
[2] G. A. Agha, "Abstracting Interaction Patterns: A Programming Paradigm for Open Distributed
Systems," in Formal Methods for Open Object-based Distributed Systems, IFIP Transactions, E.
Najm and J.-B. Stefani, Eds., Chapman & Hall, 1997.
[3] R. Allen and D. Garlan, "Formalizing Architectural Connection," in Proc. of the 16th Interna-
tional Conference on Software Engineering (ICSE 94), May 1994, pp. 71-80, IEEE Computer
Society Press.
[4] G. R. Andrews, Concurrent Programming — Principles and Practice, Addison-Wesley, 1991.
[5] R. L. Bagrodia, "Parallel Languages for Discrete Event Simulation Models," IEEE Computa-
tional Science & Engineering, vol. 5, no. 2, April-June 1998, pp 27-38.
[6] R. Bagrodia, R. Meyer, et al, "Parsec: A Parallel Simulation Environment for Complex Sys-
tems," IEEE Computer, vol. 31, no. 10, October 1998, pp 77-85.
[7] A. Benveniste and G Berry, "The Synchronous Approach to Reactive and Real-Time Systems,"
Proceedings of the IEEE, Vol. 79, No. 9, 1991, pp. 1270-1282.
[8] A. Benveniste and P. Le Guernic, "Hybrid Dynamical Systems Theory and the SIGNAL Lan-
guage," IEEE Tr. on Automatic Control, Vol. 35, No. 5, pp. 525-546, May 1990.
[9] G Berry and G Gonthier, "The Esterel synchronous programming language: Design, semantics,
implementation," Science of Computer Programming, 19(2):87-152, 1992.
[10] S. Bhatt, R. M. Fujimoto, A. Ogielski, and K. Perumalla, "Parallel Simulation Techniques for
Large-Scale Networks," IEEE Communications Magazine, Vol. 36, No. 8, August 1998, pp. 42-
47.
[II] Randy Brown, "CalendarQueue: A Fast Priority Queue Implementation for The Simulation Event
Set Problem", Communications of the ACM, October 1998, Volume 31, Number 10.
[12] V Bryant, "Metric Spaces," Cambridge University Press, 1985.
[13] J. T. Buck, S. Ha, E. A. Lee, and D. G Messerschmitt, "Ptolemy: A Framework for Simulating
and Prototyping Heterogeneous Systems," Int. Journal of Computer Simulation, special issue on
"Simulation Software Development," vol. 4, pp. 155-182, April, 1994. (http://ptolemy.eecs.berke-
ley.edu/publications/papers/94/JEurSim).
[14] A. Burns, Programming in OCCAM2, Addison-Wesley, 1988.
[15] James C. Candy, "A Use of Limit Cycle Oscillations to Obtain Robust Analog-to-Digital Con-
verters," IEEE Tr. on Communications, Vol. COM-22, No. 3, pp. 298-305, March 1974.
362
[16] L. Cardelli, Type Systems, Handbook of Computer Science and Engineering, CRC Press, 1997.
[17] P. Caspi, D. Pilaud, N. Halbwachs, and J. A. Plaice, "LUSTRE: A Declarative Language for Pro-
gramming Synchronous Systems," Conference Record of the 14th Annual ACM Symp. on Princi-
ples ofProgramming Languages, Munich, Germany, January, 1987.
[18] K. M. Chandy and J. Misra, "Asynchronous Distributed Simulation Via a Sequence of Parallel
Computations," Communications of the ACM, vol. 24, no. 11, November 1981, pp. 198-205.
[19] B. A. Davey and H. A. Priestly, Introduction to Lattices and Order, Cambridge University Press,
1990.
[20] S. A. Edwards, "The Specification and Execution of Heterogeneous Synchronous Reactive Sys-
tems," Ph.D. thesis, University of California, Berkeley, May 1997. Available as UCB/ERL M97/
31. (http://ptolemy.eecs.berkeley.edu/papers/97/sedwardsThesis/)
[21] P. H. J. van Eijk, C. A. Vissers, M. Diaz, The formal description technique LOTOS, Elsevier Sci-
ence, B.V., 1989. (http://wwwtios.cs.utwente.nl/lotos)
[22] P. A. Fishwick, Simulation Model Design and Execution: Building Digital Worlds, Prentice Hall,
1995.
[23] M. Fowler and K. Scott, UML Distilled, Addison-Wesley, 1997.
[24] R. M. Fujimoto, "Parallel Discrete Event Simulation," Communications of the ACM, vol. 33, no.
10, October 1990, pp 30-53.
[25] E. Gamma, R. Helm, R. Johnson, and J. Vlissides, Design Patterns: Elements of Reusable Object-
Oriented Software, Addison-Wesley, Reading MA, 1995.
[26] C. W. Gear, "Numerical Initial Value Problems in Ordinary Differential Equations," Prentice Hall
Inc. 1971.
[27] A. J. C. van Gemund, "Performance Prediction of Parallel Processing Systems: The PAMELA
Methodology, " Proc. 7th Int. Conf. on Supercomputing, pages 418-327, Tokyo, July 1993.
[28] A. Girault, B. Lee, and E. A. Lee, "Hierarchical Finite State Machines with Multiple Concurrency
Models," April 13, 1998 (revised from Memorandum UCB/ERL M97/57, Electronics Research
Laboratory, University of California, Berkeley, CA 94720, August 1997). (http://
ptolemy.eecs.berkeley.edu/publications/papers/98/starcharts)
[29] M. Goel, Process Networks in Ptolemy II, MS Report, ERL Technical Report UCB/ERL No.
M98/69, University of California, Berkeley, CA 94720, December 16, 1998. (http://
ptolemy.eecs.berkeley.edu/publications/papers/98/PNinPtolemyII)
[30] M. Grand, Patterns in Java, Volume 1, A Catalog of Reusable Design Patterns Illustrated with
UML, John Wiley & Sons, 1998
[31] D. Harel, "Statecharts: A Visual Formalism for Complex Systems," Sei. Comput. Program., vol 8,
pp. 231-274, 1987.
[32] P. G Harrison, "A Higher-Order Approach to Parallel Algorithms," The Computer Journal, Vol.
35, No. 6, 1992.
363
[33] T. A. Henzinger, "The theory of hybrid automata," in Proceedings of the 11th Annual Symposium
on Logic in Computer Science, IEEE Computer Society Press, 1996, pp. 278-292, invited tutorial.
[34] T.A. Henzinger, and O. Kupferman, and S. Qadeer, "From prehistoric to postmodern symbolic
model checking," in CAV 98: Computer-aided Verification, pp. 195-206, eds. A.J. Hu and M.Y.
Vardi, Lecture Notes in Computer Science 1427, Springer-Verlag, 1998.
[35] M. G. Hinchey and S. A. Jarvis, Concurrent Systems: Formal Developments in CSP, McGraw-
Hill, 1995.
[36] C. W. Ho, A. E. Ruehli, and P. A. Brennan, "The Modified Nodal Approach to Network Analy-
sis," IEEE Tran, on Circuits and Systems, Vol. CAS-22, No. 6, 1975, pp. 504-509.
[37] C. A. R. Hoare, "Communicating Sequential Processes," Communications of the ACM, Vol. 21,
No. 8, August 1978.
[38] C. A. R. Hoare, Communicating Sequential Processes, Prentice-Hall, 1985.
[39] IEEE DASC 1076.1 Working Group, "VHDL-A Design Objective Document, version 2.3," http:/
/www.vhdl.org/analog/ftp_files/requirements/DOD_v2.3.txt
[40] D. Jefferson, Brian Beckman, et al, "Distributed Simulation and the Time Warp Operating Sys-
tem," UCLA Computer Science Department: 870042,1987.
[41] G. Kahn, "The Semantics of a Simple Language for Parallel Programming," Proc. of the IFIP
Congress 74, North-Holland Publishing Co., 1974.
[42] G. Kahn and D. B. MacQueen, "Coroutines and Networks of Parallel Processes,'' Information
Processing 77, B. Gilchrist, editor, North-Holland Publishing Co., 1977.
[43] P. Laramie, R.S. Stevens, and M.Wan, "Kahn process networks in Java," ee290n class project
report, Univ. of California at Berkeley, 1996.
[44] D. Lea, Concurrent Programming in Java , Addison-Wesley, Reading, MA, 1997.
[45] B. Lee and E. A. Lee, "Interaction of Finite State Machines with Concurrency Models," Proc. of
Thirty Second Annual Asilomar Conference on Signals, Systems, and Computers, Pacific Grove,
California, November 1998. (http://ptolemy.eecs.berkeley.edu/publications/papers/98/Interaction-
FSM/)
[46] E. A. Lee, "Modeling Concurrent Real-time Processes Using Discrete Events," Invited paper to
Annals of Software Engineering, Special Volume on Real-Time Software Engineering, to appear,
1998. Also UCB/ERL Memorandum M98/7, March 4th 1998.(http://ptolemy.eecs.berkeley.edu/
publications/papers/98/realtime)
[47] B. Lee and E. A. Lee, "Hierarchical Concurrent Finite State Machines in Ptolemy," Proc. ofInter-
national Conference on Application of Concurrency to System Design, p. 34-40, Fukushima,
Japan, March 1998.
(http://ptolemy.eecs.berkeley.edu/publications/papers/98/HCFSMinPtolemy/)
[48] E. A. Lee and D. G. Messerschmitt, "Static Scheduling of Synchronous Data Flow Programs for
Digital Signal Processing," IEEE Trans, on Computers, January, 1987.
364
[49] E. A. Lee and T. M. Parks, "Dataflow Process Networks,", Proceedings of the IEEE, vol. 83, no.
5, pp. 773-801, May, 1995. (http://ptolemy.eecs.berkeley.edu/publications/papers/95/processNets)
[50] E. A. Lee and A. Sangiovanni-Vincentelli, "A Framework for Comparing Models of Computa-
tion,", to appear, IEEE Transactions on CAD, (Revised from ERL Memorandum UCB/ERL M97/
11, University of California, Berkeley, CA 94720, January 30, 1997). (http://ptolemy.eecs.berke-
ley.edu/publications/papers/97/denotational/)
[51] M. A. Lemkin, Micro Accelerometer Design with Digital Feedback Control, Ph.D. dissertation,
University of California, Berkeley, Fall 1997.
[52] J. Liu, Continuous lime and Mixed-Signal Simulation in Ptolemy II, MS Report, UCB/ERL
Memorandum M98/74, Dept. of EECS, University of California, Berkeley, CA 94720, December
1998. (http://ptolemy.eecs.berkeley.edu/publications/papers/98/MixedSignalinPtlI/)
[53] J. Liu, X. Liu, T. J. Koo, B. Sinopoli, S. Sastry, and E. A. Lee, "A Hierarchical Hybrid System and
Its Simulation", 1999 38th IEEE Conference on Decision and Control (CDC99), Phoenix, Ari-
zona.
[54] D. C. Luckham and J. Vera, "An Event-Based Architecture Definition Language," IEEE Transac-
tions on Software Engineering, 21(9), pp. 717-734, September, 1995.
[55] F. Maraninchi, "The Argos Language: Graphical Representation of Automata and Description of
Reactive Systems," in Proc. of the IEEE Workshop on Visual Languages, Kobe, Japan, Oct. 1991.
[56] S. McConnell, Code Complete: A Practical Handbook ofSoftware Construction, Microsoft Press,
1993.
[57] B. Meyer, Object Oriented Software Construction, 2nd ed., Prentice Hall, 1997.
[58] R. Milner, Communication and Concurrency, Prentice-Hall, Englewood Cliffs, NJ, 1989.
[59] R. Milner, "A Calculus of Communicating Systems", Lecture Notes in Computer Science, Vol.
92, Springer-Verlag, 1980.
[60] R. Milner, A Theory of Type Polymorphism in Programming, Journal of Computer and System
Sciences 17, pp. 384-375, 1978.
[61] J. Misra, "Distributed Discrete-Event Simulation," Computing Surveys, vol. 18, no. 1, March
1986, pp. 39-65.
[62] L. Muliadi, "Discrete Event Modeling in Ptolemy II," MS Report, Dept. of EECS, University of
California, Berkeley, CA 94720, May 1999. (http://ptolemy.eecs.berkeley.edu/publications/
papers/99/deModeling/)
[63] L. W. Nagal, "SPICE2: A Computer Program to Simulate Semiconductor Circuits," ERL Memo
No. ERL-M520, Electronics Research Laboratory, University of California, Berkeley, CA 94720.
[64] NASA Office of Safety and Mission Assurance, Software Formal Inspections Guidebook, August
1993 (http ://satc.gsfc.nasa.gov/fi/gdb/fitext.txt).
[65] A. R. Newton and A. L. Sangiovanni-Vincentelli, "Relaxation-Based Electrical Simulation,"
IEEE Tr. on Electronic Devices, Vol. ed-30, No. 9, Sept. 1983.
[66] S. Oaks and H. Wong, Java Threads, O'Reilly, 1997.
365
[67] J. K. Ousterhout, Tel and the Tk Toolkit, Addison-Wesley, Reading, MA, 1994.
[68] J. K. Ousterhout, Scripting: Higher Level Programming for the 21 Century, IEEE Computer mag-
azine, March 1998.
[69] T. M. Parks, Bounded Scheduling of Process Networks, Technical Report UCB/ERL-95-105.
Ph.D. Dissertation. EECS Department, University of California. Berkeley, CA 94720, December
1995. (http://ptolemy.eecs.berkeley.edu/publications/papers/95/parksThesis/)
[70] J. K. Peacock, J. W. Wong and E. G. Manning, "Distributed Simulation Using a Network of Pro-
cessors," Computer Networks, vol. 3, no. 1, February 1979, pp. 44-56.
[71] Rational Software Corporation, UML Notation Guide, Version 1.1, September 1997, http://
www.rational.com/uml/resources/documentation/notation
[72] J. Reekie, S. Neuendorffer, C. Hylands and E. A. Lee, "Software Practice in the Ptolemy Project,"
Technical Report Series, GSRC-TR-1999-01, Gigascale Silicon Research Center, University of
California, Berkeley, CA 94720, April 1999.(http://ptolemy.eecs.berkeley.edu/publications/
papers/99/sftwareprac/)
[73] J. Rehof and T. Mogensen, "Tractable Constraints in Finite Semilattices," Third International
Static Analysis Symposium, pp. 285-301, Volume 1145 of Lecture Notes in Computer Science,
Springer, Sept., 1996.
[74] A. J. Riel, Object Oriented Design Heuristics, Addison Wesley, 1996.
[75] R. C. Rosenberg and D.C. Karnopp, Introduction to Physical System Dynamics, McGraw-Hill,
NY, 1983.
[76] J. Rowson and A. Sangiovanni-Vincentelli, "Interface Based Design," Proc. ofDAC '97.
[77] J. Rumbaugh, et al. Object-Oriented Modeling and Design Prentice Hall, 1991.
[78] J. Rumbaugh, OMT Insights, SIGS Books, 1996.
[79] S. Saracco, J. R. W Smith, and R. Reed, Telecommunications Systems Engineering Using SDL,
North-Holland - Elsevier, 1989.
[80] N. Smyth, Communicating Sequential Processes Domain in Ptolemy II, MS Report, UCB/ERL
Memorandum M98/70, Dept. of EECS, University of California, Berkeley, CA 94720, December
1998. (http://ptolemy.eecs.berkeley.edu/publications/papers/98/CSPinPtolemyII/)
5G6
Glossary
abstract syntax ,,, A conceptual data organization, cf. concrete syntax.
action methods The methods initialize^), prefire(), fire(), postfire(), and wrapup() in
the Executable interface.
actor An executable entity. This was called a block in Ptolemy Classic.
anytype , The Ptolemy Classic name for undeclared type.
applet A Java program that is downloaded from a web server by a browser
and executed in the client's computer (usually within a plug-in for the
browser). An applet has restricted access to local resources for secu-
rity reasons.
application A Java program.that is executed as an ordinary program on a host
computer. Unlike an applet, an application can have full access to
local resources such as the file system.
atomic actor A primitive actor. That is, one that is not a composite actor. This was
called a star in Ptolemy Classic.
attribute A named property associated with a named object in Ptolemy II. Also,
in XML, a modifier to an element.
block The Ptolemy Classic name for an actor.
browser A program that renders HTML and accesses the worldwide web using
the HTTP protocol.
channel A path from an output port to an input port (via relations) that can
transport a single stream of tokens.
clustered graph A graph with hierarchy. Ptolemy II topologies are clustered graphs.
code generation Translation of a model into efficient, standalone software for execu-
tion autonomously from the design environment. Code generation was
a major emphasis of Ptolemy Classic.
composite actor An actor that is internally composed of other actors and relations. This
was called a galaxy in Ptolemy Classic.
concrete syntax A persistent representation of a data organization, cf. abstract syntax.
connection A path from one port to another via relations and possibly transparent
ports. A connection consists of one or more relations and two or more
links.
container An object that logically owns another. A Ptolemy II object can have at
most one container.
dangling relation A relation with only input ports or only output ports linked to it.
data polymorphism ,,,, Ability to operate with more than one token type.
deep traversals Traversals of a clustered graph that see through transparent cluster
boundaries (transparent composite entities and ports).
367
disconnected port A port with no relation linked to it.
director An object that controls the execution of a model or an opaque com-
posite entity according to some model of computation.
domain An implementation of a model of computation in Ptolemy II and
Ptolemy Classic.
domain polymorphism Ability to operate under more than one model of computation.
element In XML, a portion of a document consisting of a begin tag, a body,
and an end tag.
entity A node in a Ptolemy II clustered graph. Also, in XML, a named text
segment.
execution One invocation of initialized), followed by any number of iterations,
followed by one invocation of wrapup().
executive director From the perspective of an actor inside an opaque composite actor, the
director of the container of the opaque composite actor.
galaxy The Ptolemy Classic name for a composite actor.
immutable property A property of an object that is set up when the object is constructed
and that cannot be changed during the lifetime of the object.
iteration One invocation of prefire(), followed by any number of invocations of
fire(), followed by one invocation of postfire().
link An association between a port and a relation.
manager The top-level controller for the execution of a model.
model A complete Ptolemy II application. This was called a universe in
Ptolemy Classic.
model of computation The rules that govern the interaction, communication, and control
flow of a set of components.
MoML .-. Modeling markup language, an XML dialect for specifying compo-
nent-based designs such those in Ptolemy II.
multiport A port that can send or receive tokens over more than one channel.
opaque For a composite entity or a port, an attribute that indicates that the
inside should not be visible from the outside. That is, deep traversals
of the topology do not see through an opaque boundary.
opaque composite actor... A composite actor with a local director. Such an actor appears to the
outside domain to be atomic, but internally is composed of an inter-
connection of other actors. This was called a wormhole in Ptolemy
Classic.
package A collection of classes that forms a logical unit and occupies one
directory in the source code tree.
parameter An attribute with a value. This was called a state in Ptolemy Classic.
particle The Ptolemy Classic name for a token.
port A named interface of an entity to which connections be made.
Ptolemy Classic A C++ software system for construction of concurrent models and
implementation through code generation.
Ptolemy II A Java software system for construction and execution of concurrent
368
models.
Ptolemy Project A research oroiect at Berkeley that investigates modeling simulation
and design of concurrent, networked, embedded systems.
An object representing an interconnection between entities.
resolved type A type for a port that is consistent with the type constraints of the
actor and any port it is connected to. It is the result of type resolution.
servlet A Java program that is executed on a web server and that produces
results viewed remotely on a web browser.
The Ptolemy Classic name for an atomic actor.
state The Ptolemy Classic name for aparameter.
subpackage A package that is logically related to a parent package and occupies a
subdirectory within the parent package in the source code tree.
tag In XML, a portion of markup having the syntax <tagname>.
A unit of data that is communicated by actors. This was called a parti-
cle in Ptolemy Classic.
topology The structure of interconnections between entities (via relations) in a
Ptolemy II model. See clustered graph.
For an entity or port, not opaque. That is, deep traversals of the topol-
ogy pass right through its boundaries.
transparent composite actor
A composite actor with no local director.
The port of a transparent composite entity. Deep traversals of the
topology see right through such a port.
type constraints The declared constraints on the token types that an actor can work
with.
type resolution The process of reconciling type constraints prior to running a model.
Capable of working with any type of token. This was called anytype in
Ptolemy Classic.
The Ptolemy Classic name for a model.
width of a port The sum of the widths of the relations linked to it, or zero if there are
none.
width of a relation The number of channels supported by the relation.
The Ptolemy Classic name for an opaque composite actor.
369
blndex
- in UML 17 actor.util package 11, 163, 164
Symbols actors 4, 109, 155, 156
acyclic directed graphs 197
! in CSP 321 add() method
# in UML 17 Token class 177
" 40 addChangeListener() method
""charts 6 NamedObj class 152
+ in UML 17
addExecutionListener() method
? in CSP 321
Manager class 172
@exception 128 AddSubtract actor 27, 92, 97, 313
@param 128 addToScope() method
_createRunControls() method Variable class 184
Ptolemy/Applet class 73 ADL3
director member 69 ADS 2
_execute() method advancing time
ChangeRequest class 152 CSP domain 321
_go() method aggregation association 134
DEApplet class 76 aggregation UML notation 19
manager member 69
allowLevelCrossingConnect() method
_newReceiver() method
CompositeEntity class 142
IOPort class 162 analog circuits 5
toplevel member 69
analog electronics 1
A Andrews 317
absolute type constraint 113 animated plots 227
Absolute Value actor 97 anonymous inner classes 293
abstract class 19 ANYTYPE 207
abstract syntax 13, 36, 133, 369 anytype 369
abstract syntax tree 193 anytype particle 15
abstraction 37, 139 applet 34, 369
acquaintances 156 applets 9, 33, 224
action methods 94, 118, 167, 369 using plot package 221
active processes 330 appletviewer 77, 224
actor 165, 369 application 369
Actor interface 13, 165, 166 application framework 155
actor libraries 78 applications 9, 33
actor library 24 arc 36, 134
actor package 9, 90, 156 architecture 3
actor.event package 9 architecture description languages 3
actor.gui package 9, 68, 89, 91, 92, 102, 103 architecture design language 3
actor.lib package 11, 90, 100, 102, 113 archive 78
actor.process package 11, 173, 174 archive applet parameter 229
actor.sched package 11, 173, 174 arithmetic operators 177
arithmetic operators in expressions 184
570
arraycopy method 315 BDF7
ArrayFIFOQueue class 314, 315 begin() method
arrays in expressions 185 Ptolemy 0 167
Array Token class 177, 210 Bernoulli 121
Array Type class 211 Bernoulli actor 99, 102, 120, 121
artificial deadlock 340, 352 bidirectional ports 159, 165
associations 19 bin directory 33
AST 193 bin element
ASTPtBitwiseNode class 195 PlotML 239
ASTPtFunctionallfNode class 195 binary format
ASTPtFunctionNode class 194, 195 plot files 221
ASTPtLeafNode class 195 bison 193
ASTPtLogicalNode class 195 bitwise operators in expressions 184
ASTPtMethodCallNode class 195 block 369
ASTPtProductNode class 195 block diagrams 9
ASTPtRelationalNode class 196 block-and-arrow diagrams 4
ASTPtRootNode class 195 blocked processes 330
ASTPtSumNode class 195 blocking reads 339, 352
ASTPtUnaryNode class 196 blocking receive 317
asynchronous communication 163, 352 blocking send 317
asynchronous message passing 7, 157 blocking writes 339, 352
atomic actions 4 BNF 193
atomic actor 369 body of an element in XML 40
atomic communication 318 boolean dataflow 7
AtomicActor class 13, 165, 166 BooleanMatrixToken class 176
ATTLIST in DTD 236 BooleanToken class 176
attribute 369 bottom-up parsers 193
Attribute class 138, 180 bounded buffering 351
attributeChanged() method bounded memory 305, 352
NamedObj class 115, 183 boundedness 7
Poisson actor 116 broadcast() method 159
Scale actor 117 DEIOPort class 292, 295, 296
attributeListO method browser 34, 369, 371
NamedObj class 138 bubble-and-arc diagrams 4
attributes 12, 17, 180 buffer 163
attributes in XML 40 bus 157
attributeTypeChanged() method bus contention 321
NamedObj class 116, 183 bus widths and transparent ports 162
audio 11 busses, unspecified width 160
Average actor 81, 105, 119, 121, 122, 123 c
B C2
Backus normal form 193 C++2
balance equations 309 calculus of communicating systems 4, 318
bang in CSP 321 calendar queue 5,11, 290
barGraph element CalendarQueue class 164
PlotML 239 CCS 4, 318
Bars command 242 CDATA 44
base class 18 CD0 319, 335
BaseType class 47 Chandy 339
BaseType.NAT211 change listeners 151
371
change request 59 complete partial orders 197
change requests 353 completion time 341
changed() method complex numbers 11
QueryListener interface 75 complex numbers in expressions 188
changeExecuted() method ComplexMatrixToken class 176
ChangeListener interface 151 ComplexToken class 176
changeFailed() method component interactions 3
ChangeListener interface 151 component-based design 89, 109
ChangeListener interface 152 ComponentEntity class 13, 139, 140
ChangeRequest class 151, 152, 293 ComponentPort class 13, 139, 140
channel 156, 369 ComponentRelation class 13, 139, 140
channels 110 components 2, 15
check box entry 81 Composite Actor 27
checkTypes() method composite actor 369
TypedCompositeActor class 212 Composite design pattern 19, 139
chooseBranch() method composite opaque actor 168
CSPActor class 325 CompositeActor class 13, 165, 166
CIF 319, 326, 335 CompositeEntity class 13, 41, 58, 139, 140
circular buffer 315 concrete class 19
class attribute in MoML 40 concrete syntax 36, 133, 369
class diagrams 17 concurrency 2
class element 49 concurrent computation 156
class names 21, 126 concurrent design 13
CLASSPATH environment variable 70 concurrent finite state machines 6
clipboard 224 concurrent programming 322
Clock actor 74, 99, 215 conditional communication 325
Clock class 69 conditional do 319
clone() method conditional if 319
NamedObj class 144 ConditionalReceive class 325
Object class 117, 177 conditionals in expressions 185
Scale actor 118 ConditionalSend class 325
cloning 143 configure element 43
cloning actors 117 Configure ports 28
clustered graph 369 connect() method
clustered graphs 11, 13, 36, 133 CompositeEntity class 142
code duplication 109 connection 36, 134, 369
code generation 369 connections
codebase applet parameter 229 making in Vergil 28
coding conventions 124 conservative blocking 343
coin flips 99, 102 consistency 135
Color command 241 Const actor 24, 100
comments 125 constants
comments in expressions 185 expression language 185
communicating sequential processes 4, 13, 317 constants in expressions 185, 196
communication networks 287 constraints on parameter values 115
communication protocol 156, 162 constructive models 1
Commutator actor 105, 106 constructors
Comparable interface 292 in UML 17
compat package 222, 243 container 137, 369
compile-time exception 126 containment 19
compiling applets 70 contention 321
372
context menu 27 DDE 6, 339
continuous time modeling 4 DDE domain 300
continuous-time modeling 13 DDES 339
continuous-time systems 94 DDF 7
contract 210 DE 5, 287
control key 28 DE domain 94
control-clicking 28 DEActor class 290, 291, 292
convert() method deadlock 7, 148, 150, 309, 352
Token class 188 CSP domain 320
Token classes 180 DDE domain 339
CORBA 15, 33 DEApplet class 67, 69
cos() method DECQEventQueue class 291
Math class 76 DECQEventQueue.DECQComparator class 292
cosine 99, 108 DEDirector class 290, 291
CPO interface 200 deep traversals 141, 369
CPOs 197 deepContains() methodNamedObj class 143
CQComparator interface 164 deepEntityList() method
CrossRefList class 139 CompositeEntity class 141, 172
CSP4, 317 DEE vent class 291, 292
CSP domain 93, 163 DEEventQueue interface 291
CSPActor class 325 DEEventTag class 291
CSPDirector class 327 DefaultExecutionListener class 166, 172
CSPReceiver class 327 defaultlterations parameter
CT4 SDFapplets 1'4
CT domain 94 defaultStopTime parameter
current time 91, 287, 353 DE applets 73
CurrentTime actor 100, 101 DEIOPort class 290, 291, 292, 295, 296, 298
Cygwin 223 delay 288, 292
CSP domain 320
D
w SDF 306
DAG 288 PN domain 353
dangling ports SDF domain 310
SDF domain 313 Delay actor 289
dangling relation 159, 369 DE domain 292
data encapsulation 175 delay actors
data package 11, 175 DDE domain 340
data polymorphic 178 delayO method
data polymorphism 89, 109, 369 CSPActor class 327
data rates 309 delayed processes 330
data.expr package 11, 189 delay To() method
data.type package 11 DEIOPort class 296, 298
dataflow 163, 288, 305, 339 deleteEntity element 56
DataSet command 242 deletePorts element 56
dataset element deleteProperty element 57
PlotML 237, 238 deleteRelations element 56
dataurl 221 delta functions 5
dataurl applet parameter 229 delta time 5, 342
dataurl parameter demultiplexer actor 157
PlotApplet class 221 dependency loops 194
dB actor 97 depth for actors in DE 288
DCOM 15 DEReceiver class 291
373
derived class 18 doneReading() method
design 1 Workspace class 150
design patterns 15 doneWritingO method
determinacy 7, 163 Workspace class 150
determinism 317, 351 DoubleCQComparator interface 164
deterministic 289 DoubleMatrixToken class 176
DEThreadActor class 300 doubles 185
DETransformer class 292, 298 DoubleToFix actor 107
diamond in toolbar 28 DoubleToken class 176
digital electronics 1 DT6
digital hardware 5, 287 DTD 37, 233, 235
Dijkstra 322 dynamic dataflow 7
dining philosophers 321, 322 dynamic networks 165
Dirac delta functions 5 E
directed acyclic graph 288
directed graphs 36, 134, 197 E 185
DirectedAcyclicGraph class 198, 200 e 185
DirectedGraph class 198, 199 edges 197
director 13, 162, 168, 370 EDIF 36, 133
Director class 13, 162, 165, 166 EditablePlot class 227
director element 51 EditablePlotMLApplet class 229
director library 24 EditablePlotMLApplication class 230
di sable Actor() method element 370
DEDirector class 295 ELEMENT in DTD 234
disconnected graphs element in XML 39
SDF domain 313 embedded systems 1
disconnected port 159, 370 EMPTY
discrete event domain 287 in DTD 236
discrete-event domain 5 empty elements in XML 40
discrete-event model of computation 164 encapsulated PostScript 223
discrete-event modeling 13 encapsulated postscript 224
discrete-time domain 6 entities 4, 36, 133
Display actor 24 entity 370
distributed discrete event 339 Entity class 13, 134, 135
distributed discrete event systems 339 entity in XML 40
distributed discrete-event domain 6 EntityLibrary class 58
distributed models 6 EPS 223, 224
distributed time 339 equals() method
Distributor actor 105, 157, 165 Token class 177, 196
divide() method Eratosthenes 322, 323
Token class 177, 195 evaluateParseTree() method
doc element 44, 56 ASTPtRootNode class 194
DOCTYPE keyword in XML 34, 39, 41, 49, 50, 233 evaluation of expressions 181
document type definition 37, 233, 235 event 5
domain 155, 370 event queue 287
domain polymorphism 89, 109, 178, 370 events 287
domain-polymorphism 15 exceptions 126
domains 9, 13 exceptions in applets 70
domains package 11, 13 executable entities 155
domains.de.kernel package 290 Executable interface 13, 165, 166
domains.de.lib package 292 executable model 13
executable models 1 Director class 172, 173
execute() method Executable interface 94, 165
ChangeRequest class 151 in actors 119
execution 94, 167, 370 fireAt() method
executionError() method DEActor class 298
ExecutionListener interface 172 DEDirector class 296
ExecutionEvent class 166 Director class 99, 123, 287, 292, 295, 302
executionFinished() method fired 30
ExecutionListener interface 81, 172 firing vector 309
ExecutionListener class 166 firingCountLimit parameter
ExecutionListener interface 172 SequenceSource actor 99, 123
executive director 168, 172, 370 first-in-first-out 351
explicit integration algorithms 266 fix function in expression language 189
exporting MoML 60 fixed point data type 189
exportMoML() method fixed-point 4
NamedObj class 59, 60 fixed-point semantics 94
Expression actor 105 fixed-point simulations 16
expression evaluation 193 FixPoint class 189
expression language 6, 11, 184 FixPointFunctions class 189
extending 196 FixToDouble actor 107
expression parser 193 FixToken class 189
expressions 76 floating-point simulations 16
extensible markup language 35, 232 formatting of code 124
F fractions 11
FrameMaker 223
fail-stop behavior 206 FSM6
fairness 322 full name 137
false 185 functional actors 94
FBDelay actor 342 functions
FFT30 expression language 185
FIFO 156,315,351
FIFO Queue 11 G
FIFOQueue class 156, 163, 164, 314 galaxy 143, 370
file format for plots 232 Gaussian actor 28, 101
file formats 15 generalize 18
File->New menu 24 get() method
fill command lOPort class 157
in plots 223 Receiver interface 157
fillOnWrapup parameter getAttributeO method
Plotter actor 102 NamedObj class 138
finally keyword 150 getColumnCount() method
finish() method MatrixToken class 188
Manager class 169 getContainer() method
finished flag 331 Nameable interface 137
finite buffer 163 getCurrentTime() method
finite state machines 9 DEActor class 298
finite-state machine domain 6 Director class 123
fire() method getDirector() method
actor interface 289 Actor interface 168
Average actor 123 getElementO method
CompositeActor class 172, 173 ArrayToken class 186
375
getElementAt() method IOPort class 173
MatrixToken classes 177 heterogeneity 15, 146, 172
getFullName() method Hewlett-Packard 2
Nameable interface 137 hiding 37, 141
getInsideReceivers() method hierarchical concurrent finite state machines 9
IOPort class 173 hierarchical heterogeneity 146, 172
getOriginator() method hierarchy 139
ChangeRequest class 152 higher node 200
getReadAccess() method histogram 221, 222
Workspace class 149 Histogram class 227
getReceivers() method histogram.bat 223
IOPort class 173 HistogramMLApplet class 229
getRemoteReceivers() method 165 HistogramMLApplication class 230
IOPort class 162 HistogramMLParser class 233
getRowCount() method HistogramPlotter actor 103
MatrixToken class 186 history 163
getState() method Hoare317, 321
Manager class 172 HTML 35, 67, 127, 221, 233
getValue() method HTTP 78
ObjectToken class 177 hybrid systems 6
getWidth() method I
IORelation class 162
getWriteAccess() method i 185
Workspace class 150 if...then...else... 185
Ghostview 223 Illegal ActionException class 117
global error for numerical ODE solution 266 Illegal ArgumentException class 194
grammar rules 193 image processing 11
Graph class 198, 199 immutability
graph package 11, 197 tokens 175
graphical elements 71 Immutable 149
graphical user interface 89, 91 immutable 137
graphics 54 immutable property 370
graphs 197 imperative semantics 2
Grid command 241 implementation 33
group element 58 implementing an interface 19
guarded communication 164, 318, 325 implicit integration algorithms 266
guards 6 import 17
GUI 89, 91 Impulses command 242
gui package 11 inCSP319
incomparable 179
H incomparable types 112
hardware 1 inconsistent models 310
hardware bus contention 321 incremental parsing 54, 58
Harel, David 6 indentation 125
Harrison, David 221 index of links 135
hashtable 5 index of links to a port 48
hasRoom() method index of links to ports 57
IOPort class 173 Inequality class 199, 200, 214
Hasse 200 InequalitySolver class 200
Hasse diagram 200 Inequality Term interface 198, 200, 214
hasTokenQ method information-hiding 146
376
inheritance 18, 109 plot package 222
initial output tokens 119 Java 2
initial token 310 Java 223
initialize() method Java Archive File 78
Actor interface 290 Java Foundation Classes 229
Average actor 119 Java Plug-In 67
Director class 169 JavaRMI 15
Executable interface 94, 165 Java Runtime Environment 67
in actors 118, 119 java.lang.Math 196
input element 52 JavaCC 193
input port 156 Javadoc 113, 127
input property of ports 55 Jefferson 343
inputs JFC229
transparent ports 160 JFrame class 229
inside links 37, 139 JIT 80
inside receiver 173 JJTree 193
inspection paradox 81 JPanel class 229
instantaneous reaction 289 JRE67
integers 185 just-in-time compiler 80
intellectual property 6 K
interface 19
interoperability 2, 15 Kahn process networks 7, 163, 339
interpreter 11 kernel package 11
IntMatrixToken class 176 kernel.event package 11
IntToken class 176 kernel.util package 11, 126, 164
invalidateResolvedTypes() method L
Director class 116 LALR(l) 193
invalidateSchedule() method lattice 179
DEDirector class 292 lattices 197
Director class 115 layout manager 71
IOPort class 156 LED A 197
IORelation class 156, 157 length() method
isAtomic() method ArrayToken class 186
CompositeEntity class 139 level-crossing links 37, 141, 142
islnput() method 165 lexical analyzer 193
isOpaque() method lexical tokens 193
ComponentPort 147 HberalLink() method
CompositeActor class 168, 172 ComponentPort class 142
CompositeEntity class 139, 160 Lines command 241
isOutput() method 165 lingering processes 80
isWidthFixed() method link 36, 134, 135, 370
IORelation class 162 link element 47, 52
iteration 94, 167, 370 link element and channels 48
iterations 119 link index 48, 57, 135
iterations parameter 24 link() method
SDF applets 74 Port class 142
SDFDirector class 311 links
J in Vergil 28
j 185 literal constants 185
jar files 78 liveness 13, 322
377
LL(k) 193 microelectromechanical systems 1
local director 168, 172 Microstar XML parser 58
local error for numerical ODE solution 266 microstep 288
Location class 61 microwave circuits 5
lock 148, 332 Milner318
logarithmic axes for plots 236, 240 Minimum actor 98
logical boolean operators in expressions 184 Misra 339
long integers 185 mixed signal modeling 5
long integers in expressions 188 ML 15
LongMatrixToken class 176 MoC 317
LongToken class 176 modal model 5
Lorenz system 274 modal models 6
lossless type conversions 183 model 370
Lotos 4, 321 model element 41
lower node 199 model of computation 2, 155, 156, 370
M model time 287, 320, 353
modeling 1
M/M/l Queue 322 models of computation
mailbox 163 mixing 172
Mailbox class 156, 163 modulo() method
make install 79 Token class 177, 195
makefiles 79 MoML 33, 370
managed ownership 137 exporting 60
manager 168, 169, 370 moml package 12, 58, 59, 62
Manager class 13, 166, 169 MoMLAttribute class 61
managerStateChanged() method MoMLChangeRequest class 59, 152
ExecutionListener interface 172 monitor 148
Marks command 241 monitors 13, 15, 332
marks in ptplot 237 monomorphic 113
Math class 76 monotonic functions 163
math functions 196 multiple containment 42
math package 11, 189 multiplyO method
mathematical graphs 36, 134, 197 Token class 114, 177, 195
Matlab 2 MultiplyDivide actor 29, 98
matrices 11 multiport 27, 110, 157, 163, 370
matrices in expressions 185 multiport property of ports 55
matrix tokens 177 multiports
MatrixToken class 176, 186 SDF domain 313
MatrixViewer actor 104 multiports in MoML 46
Maximum actor 97, 98, 99, 106, 108 mutability
mechanical components 5 CSP domain 322
mechanical systems 5 mutation 11, 15
media package 11 mutations 150, 351, 353
Mediator design pattern 36, 134 DE domain 292, 298
MEMS 1, 5, 276 mutual exclusion 148, 332
Merge actor 292
Message class 228 N
message passing 156 name 137
methods name attribute in MoML 40
expression language 186 name server 165
microaccelerometer 276 Nameable interface 12, 126, 135, 137
i 3 78
NamedList class 139 oscilloscope 237
NamedObj class 12, 41, 60, 135, 137, 152 output property of ports 55
NameDuplicationException class 117 overloaded 127
namespaces 58 override 18
naming conventions 21 P
NaT 219
package 370
newPort() method
package diagrams 17
Entity class 46
package structure 9
newReceiver() method
Director class 162 packages 13
newRelation() method Pamela 353
Composite Entity class 48 Panel class 227
parallel discrete event simulation 343
noColor element
parameter 181, 370
PlotML 237
Parameter class 74, 181
node classes (parser) 195
parameters 11, 74, 114
nodes 197
constraints on values 115
noGrid element
Parks 352
PlotML 237
parse tree 193
non-determinism 317
nondeterminism with rendezvous 164 parse() method
MoMLParser class 58
nondeterministic choice 318
parsed character data 234
non-timed deadlock 340
parser 193
notifyAll() method
partial order 15
Object class 332
partial orders 197
null messages 340
partial recursive functions 6 ,
Numerical type 180
particle 370
O pathTo attribute
object model 17 vertex element 53
object modeling 15 pause() method
object models 9 CSPDirector class 331
object-oriented concurrency 155 Manager class 172
object-oriented design 89 PCDATA in DTD 234
ObjectToken class 175, 176, 177 PDES 343
OCCAM 321 period parameter
Occam 4 Clock actor 74
ODE solvers 13 persistent file format 59
one() method PI 185
Token class 178 pi 185
oneRight() method Placeable interface 71,91
MatrixToken classes 178 plot actors 221
opaque 370 Plot class 71, 227, 228
opaque actors 168, 172 plot package 12, 221
opaque composite actor 168, 173, 370 plot public member
opaque composite actors 15 Plotter class 71
opaque composite entities 146 PlotApplet class 228
opaque port 141 PlotApplication class 228, 229
operator overloading 184 PlotBox class 227, 228, 229
optimistic approach 343 PlotBoxMLParser class 233
originator PlotFrame class 228, 229
in change requests 151 PlotLive class 227, 228
379
PlotLiveApplet class 228 priority of events in DE 288
PlotML 43, 222, 227, 232, 235 priority queue 5, 11
plotml package 227, 233 private methods 17
PlotMLApplet class 229 process algebras 37, 141
PlotMLApplication class 230 process domains 173
PlotMLFrame class 229 process level type system 15
PlotMLParser class 233 Process Network Semantics 352
PlotPoint class 227, 228 process networks 13, 163, 305, 351
Plotter actors 30 process networks domain 7, 172
Plotter class 91 processing instruction 44, 45
plotting 12 process-oriented domains 94
Plug-In 67 ProcessThread class 328
plug-in 80 production rules 193
PN 7, 351 property element 42, 56
PN domain 93 protected members and methods 17
Poisson actor 101, 115, 116 protocol 156
polymorphic actors 178 protocols 89
polymorphism 15, 89, 109, 207 PTII environment variable 33, 222, 223, 229
data 178 Ptolemy Classic 13, 370
domain 178 ptolemy executable 33
port 370 Ptolemy II 370
type of a port 47 Ptolemy Project 371
Port class 13, 134, 135 ptolemy.data.expr package. 189
port element 45 PtolemyApplet class 68, 70
port toolbar button 27 PtParser 193
ports 36, 110, 133 ptplot 221, 222, 230
postfire() method ptplot.bat 223
actor interface 289 PUBLIC keyword in XML 34, 39, 41, 233
Average actor 123 public members and methods 17
CompositeActor class 169 Pulse actor 101, 215
DE domain 296 pure event 287
DEDirector class 302 pure property 46
Executable interface 94, 165 pure property in MoML 43
in actors 119 put() method
Server actor 298 Receiver interface 156
PostScript 223 pxgraph221,222, 243
precedences 5 pxgraph.bat 223
precondition 126 PxgraphApplication class 243
prefire() method pxgraphargs parameter
Actor interface 289 PxgraphApplet class 222
CompositeActor class 172 PxgraphParser class 243
DE domain 295
Executable interface 94, 165
Q
in actors 119 quantize() function in expression language 189
Server actor 298 Quantizer actor 98
prefix monotonic functions 7 Query class 74
prefix order 163 query in CSP 321
preview data QueryListener interface 75
in EPS 223 queue 163, 315, 322
prime numbers 323 queueing systems 287
priorities 323 QueueReceiver class 156, 157, 163
OoU
quotation marks in MoML attributes 40 NamedObj class 152
requestChange() method 59
R
Director class 151, 292
race conditions 148 Manager class 292
Ramp actor 101, 102, 103 REQUIRED in DTD 236
random() function in expressions 186 resolved type 207, 371
Rapide 3 resolveTypes() method
read blocked 352 Manager class 214
read blocks 340 resource contention 321
read/write semaphores 15 resource management 317
readers and writers 149 resume() method
read-only workspace 150 CSPDirector class 331
real deadlock 320, 340, 352 Manager class 172
RealToComplex actor 106, 107, 108 re-use 89
receiver ReuseDataSets command 242
wormhole ports 173 right click 27
Receiver interface 156 rollback 272
receiver time 340 RTTI 210
record tokens in expressions 185 Rumbaugh 137
Recorder actor 81, 104 Run Window 24
RecordToken 210 run() method
reduced-order modeling 15 Manager class 169
reference implementation 58 Runtime Environment 67
reflection 194, 196 run-time exception 126
registerClass() method run-time type checking 206, 210
PtParser class 196 run-time type conversion 206
registerConstant() method run-time type identification 210
PtParser class 196 RuntimeException interface 126
registerFunctionClass() method
PtParser class 195 s
relation 371 Saber 2, 5
in Vergil 28 safety 13
Relation class 13, 135 SampleDelay actor 306
relation element 47 scalable vector graphics 54
relational operators in expressions 184 Scalar type 180
relations 4, 36, 133 ScalarToken class 176
relative type constraint 112 Scale actor 98, 113, 115, 116, 117, 118
reloading applets 80 Scheduler class 313
removeChangeListener() method schedulers 173
NamedObj class 152 scheduling 167,311,313
removing entities 56 scope 181
removing links 57 scope in expressions 185
removing ports 56 Scriptics Inc. 144
removing relations 56 scripting 184
rename element 56 SDF 7, 305
rendezvous 4, 93, 157, 163, 317, 333 SDF scheduler 29
rendition of entities 53 SDFAtomicActor class 315
report() method SDFDirector class 311
Ptolemy Applet class 70 SDFReceiver class 313, 314
reporting errors in applets 70 SDFScheduler class 311, 313
requestChange method SDL 7
3^1
semantics 2, 13 setTypeEquals method 117
send() method setTypeEquals() method
DEIOPort class 292, 295, 296, 298 Variable class 181
IOPort class 156 setTypeSameAs() method
TypedlOPort class 214 Variable class 183
SequenceActor interface 91, 99, 292 setWidth() method
SequencePlotter actor 28, 30, 104 IORelation class 157, 162
SequencePlotter class 91 setXLabel() method
SequenceSource actor 124 Plot class 77
SequenceSource class 123 SGML 35, 233
Server actor 292, 297 shallow copy 117
servlet 371 shell script 223
servlets 33 sieve of Eratosthenes 322, 323
setConnected() method signal processing 351
Plot class 77 signal processing library 28
setContainer() method simulation 1, 33
kernel classes 135 simulation time 287
Port class 105 Simulink 2, 5
setContext() method simultaneous events 5, 287, 288
MoMLParser class 55 sin() method
setCurrentTime 327 Math class 76
setCurrentTime() method Sine actor 99, 108, 121
Director class 123, 327 Sinewave actor 28
setExpression() method Sine wave class 62
Parameter class 76 single port 110
Variable class 181 Sink class 109
setlmpulses() method sinks library 24
Plot class 77 size element
setMarksStyle() method() PlotML 237
Plot class 77 software 1
setMultiport() method software architecture 3
IOPort class 157 software components 15
setPanelO method software engineering 15
Placeable interface 71, 91 source actors 100, 102, 103, 123
setReadOnly() method Source class 109
Workspace class 150 sources library 24
setSize() method spaces 125
Plot class 71 specialize 18
PlotBox class 232 spectrum 30
setStopTime() method Spice 5
DEDirector class 73, 290 spreadsheet 11
Settable interface 138 SR7
setTitleO method star 143, 371
Plot class 77 starcharts 6
setToken() method Start menu 80
Variable class 181 start tag in XML 40
setTopLevelO method start time 290
MoMLParser class 55 startRun() method
setToplevel() method of MoMLParser 59 Manager class 169
setTypeAtLeast() method state 6, 371
Variable class 183 Statecharts 6
382
stateless actors 94 TerminateProcessException class 331
static schedule 169 terminating processes
static schedulers 173 CSP domain 331
static scheduling 308 testable precondition 126
static structure diagram 12, 90, 91, 134 thread actors
static structure diagrams 17 DE domain 298
static typing 205 thread safety 137, 147, 148
StaticSchedulingDirector class 311 threads 13, 163
stem plot 77 thread-safety 15
stem plots 238 threshold crossings 5
stop time 290 tick element
stopFire() method PlotML 236
Executable interface 165 tick marks 226
stopTime parameter time 2
DE Applets 73 CSP domain 320
TimedSource actor 99 DDE domain 339
stream 157 PN domain 353
string constants 185 time deadlock 320
StringAttribute class 138 time stamp 5, 164, 287
StringToken class 92, 176 DDE domain 340
stringValue() method Time Warp system 343
Query class 76 timed deadlock 341
StructuredType class 211 Timed Actor interface 91, 99, 123, 292
subclass 18 TimedPlotter actor 71, 104
subclass UML notation 18 TimedPlotter class 69, 91
subdomains 15 TimedSource actor 123
subpackage 371 TimedSource class 124
subtract^) method title elemen
Token class 177, 195 PlotML 234
superclass 18 TitleText command 240
SVG 54 toArrayO method
swing 229 MatrixToken class 188
symbol table 193 token 110,371
synchronized keyword 148, 332 Token class 92, 93, 121, 175, 176
synchronous communication 163 tokenConsumptionRate parameter
synchronous dataflow 7, 13, 305 port classes 313
synchronous dataflow domain 7 tokenlnitProduction parameter
synchronous message passing 4, 157, 317 port classes 313
synchronous/reactive models 7 tokenProductionRate parameter
syntax 9 port classes 313
System control panel 223 tokens 30, 90, 156
T tokens, lexical 193
toolbar 27, 28
Tab character 125 tooltips 45
tag 371 top level composite actor 169
tag in XML 40 top-down parsers 193
telecommunications systems 5 topological sort 198, 288
terminate() method topology 36, 133, 371
Director class 331 topology mutations 150
Executable interface 167 transferlnputs() method
Manager class 169 DEDirector class 301
383
Director class 173 undeclared type 207, 371
transferOutputs() method undeclared types 211
Director class 173 undirected graphs 197
Transformer class 91, 109, 113, 114 unified modeling language 17
transitions 6 uniqueness of names 137
transitive closure 198, 200 universe 371
transparent 371 Unix 223
transparent composite actor 371 unlink element 57
transparent entities 139 untrapped errors 205
transparent port 371 util subpackage of the kernel package 151
transparent ports 141, 160 utilities library 27
trapped errors 205
V
trigger input
Source actor 99 variable 180
true 185 Variable class 138
tunneling entity 143 VariableClock actor 102
type changes for variables 183 variables in expressions 185
type compatibility rule 206 vector graphics 54
type conflict 208 vectors 11
type constraint 112, 207 Verilog 5, 9
type constraints 112, 207, 214, 371 vertex 36, 134
type conversion 210 vertex attribute
type conversions 179 link element 52
type hierarchy 178 Vertex class 53, 61
type lattice 179 VHDL 5, 9
type of a port 47 VHDL-AMS 2, 5
type resolution 15, 167, 207, 371 View menu 24
type resolution algorithm 219 visual dataflow 9
type system 112 visual rendition of entities 53
process level 15 visual syntax 9
type variable 208 W
Typeable interface 183
wait() method
typeConstraints() method 214
Object class 332
Typed Composite Actor 27
Workspace class 150
TypedActor class 211
waitForCompletion() method
TypedAtomicActor 211
ChangeRequest class 153
TypedAtomicActor class 90, 165 waitForDeadlock() method
TypedCompositeActor 211
CSPActor class 327
TypedCompositeActor class 41, 165
waitForNewInputs() method
TypedIOPort211
DEThreadActor class 300
setting the type in MoML 47
web server 34, 369, 371
TypedlOPort class 46, 110, 156, 292 welcome window 24
TypedlORelation class 48, 156
width of a port 110, 157,371
TypedOIRelation211
width of a relation 49, 57, 157, 371
TypeLattice class 179
width of a transparent 162
type-polymorphic actor 207
Windows 223
types of parameters 181
wireless communication systems 165
U workspace 149
UML 9, 12, 17, 90, 91, 134 Workspace class 135, 138, 149
package diagram 9 wormhole 15, 147, 168, 172, 371
384
wrap element
PlotML 237
wrapup() method
Actor interface 290
Executable interface 94, 165
Wright 3
write blocked 352
write blocks 340
X
x ticks 226
xgraph 221, 243
XLabel command 240
XLog command 240
xLog element
PlotML 236
XML 15, 33, 222, 232
XML parser 58
XMLIcon class 54
XRange command 240
xRange element
PlotML 234
XTicks command 240
xTicks element
PlotML 236
XYPlotter actor 104, 105
XYPlotter class 91
y ticks 226
yacc 193
YLabel command 240
YLog command 240
yLog element
PlotML 236
YRange command 240
YTicks command 240
yTicks element
PlotML 236
385