Preview only show first 10 pages with watermark. For full document please download

Ptolemy Ii, Heterogeneous Concurrent Modeling And Design In Java

Figure 5.3. An improved applet that properly reports errors in model ...... This helps avoid confusion and bugs that may arise from having Java import statements in the ...... 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.

   EMBED

  • Rating

  • Date

    July 2017
  • Size

    19.6MB
  • Views

    8,654
  • Categories


Share

Transcript

AFRL-IF-RS-TR-2001-85 Final Technical Report May 2001 PTOLEMY II, HETEROGENEOUS CONCURRENT MODELING AND DESIGN IN JAVA University of California at Berkeley Sponsored by Defense Advanced Research Projects Agency DARPA Order No. El 17/87 APPROVED FOR PUBLIC RELEASE; DISTRIBUTION UNLIMITED. 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 AIR FORCE RESEARCH LABORATORY INFORMATION DIRECTORATE ROME RESEARCH SITE ROME, NEW YORK 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. AFRL-IF-RS-TR-2001-85 has been reviewed and is approved for publication. APPROVED: JS P. HANNA Project Engineer FOR THE DIRECTOR: "yA&y^ & {J%&TU JAMES A. COLLINS, Acting Chief Information Technology Division 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 Edward A. Lee, John Davis II, Christopher Hylands, Bart Kienhuis, Jie Liu, Xiaojun Liu, Lukito Muliadi, Steve Neuendorffer, Jeff Tsay, Brian Vogel, and Yuhong Xiong Contractor: University of California at Berkeley Contract Number: F30602-97-C-0282 Effective Date of Contract: 19 November 1996 Contract Expiration Date: 31 August 2000 Short Title of Work: Ptolemy II, Heterogeneous Concurrent Modeling and Design in Java Period of Work Covered: Nov 96 - Aug 00 Principal Investigator: Phone: AFRL Project Engineer: Phone: Edward A. Lee (510)642-8109 James P. Hanna (315)330-3473 APPROVED FOR PUBLIC RELEASE; DISTRIBUTION UNLIMITED. This research was supported by the Defense Advanced Research Projects Agency of the Department of Defense and was monitored by James P. Hanna, AFRL/IFTC, 26 Electronic Pky, Rome, NY. REPORT DOCUMENTATION PAGE OMB No. 0704-0188 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 IN JAVA F - F3O602-97-C-0282 PE- 63739E PR- El 17 TA- 00 WU-32 6. AUTHOR(S) Edward A. Lee, John Davis II, Christopher Hylands, Bart Kienhuis, Jie Liu, Xiaojun Liu, Lukito Muliadi, Steve Neuendorffer, Jeff Tsay, Brian Vogel, and Yuhong Xiong 7. PERFORMING ORGANIZATION NAMEIS) AND ADDRESSIES) 8. PERFORMING ORGANIZATION REPORT NUMBER University of California at Berkeley Department of Electrical Engineering and Computer Sciences 231 Cory Hall Berkeley California 94720-1770 N/A 9. SPONSORINGIMONITORING AGENCY NAME(S) AND ADDRESSIES) Defense Advanced Research Projects Agency Air Force Research Laboratory/IFTC 3701 North Fairfax Drive 26 Electronic Pky Arlington Virginia 22203-1714 Rome New York 13441-4514 10. SPONSORINGIMONITORING AGENCY REPORT NUMBER AFRL-IF-RS-TR-2001-85 11. SUPPLEMENTARY NOTES Air Force Research Laboratory Project Engineer: James P. Hanna/IFTC/(315) 330-3473 12a. DISTRIBUTION AVAILABILITY STATEMENT 12b. DISTRIBUTION CODE APPROVED FOR PUBLIC RELEASE; DISTRIBUTION UNLIMITED. 13. ABSTRACT (Maximum 200 words! The Ptolemy project studied heterogeneous modeling, simulation, and design of concurrent systems. The focus is on embedded systems, particularly those that mix technologies including, for example, analog and digital electronics, hardware and software, and electronics and mechanical devices (including MEMS, microelectromechanical systems). The focus is alsc on systems that are complex in the sense that they mix widely different operations such as signal processing, feedback control, sequential decision making, and user interfaces. 14. SUBJECT TERMS 15. NUMBER OF PAGES Mixed Technology, Simulation Modeling, Analog 402 16. PRICE CODE 17. SECURITY CLASSIFICATION OF REPORT UNCLASSIFIED 18. SECURITY CLASSIFICATION OF THIS PAGE UNCLASSIFIED 19. SECURITY CLASSIFICATION OF ABSTRACT UNCLASSIFIED 20. LIMITATION OF ABSTRACT 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 1.1 1.2 1.3 1.4 1.5 1.6 Introduction Modeling and Design Architecture Design Models of Computation Choosing Models of Computation Visual Syntaxes Ptolemy II 1 3 4 8 8 9 Appendix A Appendix B 17 21/22 2 2.1 2.2 2.3 2.4 2.5 2.6 2.7 Using Vergil Introduction Quick Start Data Types and the Type System Hierarchy Broadcast Relations SDF and Multirate Systems Using the Plotter 23 24 25 27 28 29 30 3 3.1 3.2 3.3 3.4 3.5 3.6 3.7 3.8 MoML Introduction MoML Principles Specification of a Model Incremental Parsing Parsing MoML Exporting MoML Special Attributes Acknowledgements 33 25 37 54 58 60 61 61 Appendix C 62 4 4.1 4.2 67 67 Custom Applets Introduction HTML Files Containing Applets Appendix D 81 5 5.1 5.2 5.3 5.4 89 89 91 Actor Libraries Overview Library Organization Data Polymorphism Domain Polymorphism i 5.5 Descriptions of Libraries 95 6 6.1 6.2 6.3 6.4 6.5 Designing Actors Overview Anatomy of an Actor Action Methods Time Code Format 109 110 118 123 124 7 7.1 7.2 7.3 7.4 7.5 7.6 7.7 7.8 The Kernel Abstract Syntax Non-Hierarchical Topologies Support Classes Clustered Graphs Opaque Composite Entities Concurrency Mutations Exceptions 8 8.1 8.2 8.3 Actor Package Concurrent Computation Message Passing Execution 155 156 165 9 9.1 9.2 9.3 9.4 9.5 9.6 Data Package Introduction Data Encapsulation Polymorphism Variables and Parameters Expressions Fixed Point Data Type 175 175 177 180 184 189 131/132 133/134 137 139 146 147 150 153 Appendix E 193 10 10.1 10.2 10.3 Graph Package Introduction Classes and Interfaces in the Graph Package Example Use 197 198 201 11 11.1 11.2 11.3 11.4 11.5 Type System Introduction Formulation Structured Types Implementation Examples 205 207 210 211 216 ii 11.6 Actors Constructing Tokens with Structured Types 219 Appendix F 12 12.1 12.2 12.3 12.4 12.5 12.6 12.7 Plot Package Overview Using plots Class Structure PlotML File Format Old Textual File Format Compatibility Limitations 13 13.1 13.2 13.3 13.4 13.5 Vergil Introduction Infrastructure Architecture Common Operations Ptolemy Model Visualization 14 14.1 14.2 14.3 14.4 14.5 14.6 14.7 CT Domain Introduction Solving ODEs numerically CT Actors CT Directors Interacting with Other Domains CT Domain Demos Implementation 217 221 222 227 232 240 243/244 '243/244 245 246 247 251 254 260/261 265 269 271 273 274 279 286 Appendix G 15 15.1 15.2 15.3 15.4 15.5 15.6 DE Domain Introduction Overview of The Software Architecture The DE Actor Library Mutations Writing DE Actors Composing DE with Other Domains 16 16.1 16.2 16.3 16.4 16.5 SDF Domain Purpose of the Domain Using SDF Properties of the SDF domain Software Architecture Actors 287 290 292 292 295 301 305 305 308 311 315/316 iii 17 17.1 17.2 17.3 17.4 17.5 17.6 CSP Domains Introduction CSP Communication Semantics Example CSP Applications Building CSP Applications The CSP Software Architecture Technical Details 18 18.1 18.2 18.3 18.4 18.5 18.6 DDE Domain Introduction DDE Semantics Example DDE Applications Building DDE Applications The DDE Software Architecture Technical Details 19 19.1 19.2 19.3 19.4 PN Domain Introduction Process Network Semantics The PN Software Architecture Technical Details 317 318 321 325 327 332 339 339 343 344 345 349/350 351 352 354 359 362 367 370 References Glossary BIndex iv List of Figures Figure Figure Figure Figure Figure 1.1 1.2 1.3 1.4 1.5 Figure 2.1 Figure 2.2 Figure 2.3 Figure 2.4 Figure 2.5 Figure 2.6 Figure 2.7 Figure 2.8 Figure 2.9 Figure 2.10 Figure 2.11 Figure 2.12 Figure 2.13 Figure 2.14 Figure 2.15 Figure Figure Figure Figure 2.16 2.17 3.1 3.2 Figure 3.3 Figure 3.4 Figure 3.5 Figure 3.6 Figure 3.7 Figure 3.8 Figure 3.9 Figure 3.10 Figure 3.11 Figure 3.12 Figure 3.13 Figure 4.1 Figure 4.2 Figure 4.3 Figure 4.4 A single syntax can have a number of possible semantics 4 The package structure of Ptolemy II, without the domains 10 12 Some of the key classes in Ptolemy II 14 Package structure of Ptolemy II domains Simplified static structure diagram for some Ptolemy II 18 Classes 23 Example of a Vergil window. 24 Welcome window 25 An empty Vergil Graph Editor 26 The Hello World Example 25 The Const parameter editor 26 Execution of the Hello World example 27 Another example 28 An example of a hierarchical model 28 A simple signal processing example 29 The output of the simple processing example above 29 An example of a broadcast relation 30 A multirate SDF model 30 Execution of the multirate SDF model 31 Parameters of the Sequence Plotter actor r Better labeled plot, where the horizontal axis now properly 31 Represents the frequency values 31 Format control window for a plot 32 Still better labeled plot Simple example in the SPTIL/ptolemy/moml/demo/test,xnl 34 Simple example of a Ptolemy II model execution control 35 Window 36 Visual notation and terminology Ports are linked to relations below their container in the 37 Hierarchy 38 MoML version 1.2 DTD 47 Example topology 50 Sine wave generator topology Example showing how MoML might be visually rendered 53 Classes supporting MoML parsing in the MoML package 59 62 Attributes in the MoML package 63 Rendition of the Sinewave class in Vergil 1.0 64 Rendition of the modulation model in Vergil 1.0 65 Execution window for the modulation model 68 UML static structure diagram for Ptolemy Applet 68 An HTML segment that invokes the Java 1.2 Plug-in 69 An extremely simple applet that runs in the DE domain 71 Result of running the applet of figure 4.3 V Figure 4.5 Figure 4.6 Figure 4.7 Figure Figure Figure Figure Figure 4.8 4.9 4.10 4.11 4.12 Figure 4.13 Figure 4.14 Figure 4.15 Figure 5.1 Figure 5.2 Figure 5.3 Figure 5.4 Figure 5.5 Figure Figure Figure Figure Figure Figure Figure 5.6 5.7 5.8 5.9 5.10 6.1 6.2 Figure 6.3 Figure 6.4 Figure 6.5 Figure 6.6 Figure 6.7 An improved applet that properly reports errors in model Construction 71 A modified applet that places the resulting plot in the Browser window itself, as shown in figure 4.7 72 Applet with embedded plot as displayed in Netscape Navigator 72 Code that adds execution time controls to the applet 73 Browser view of the applet in figure 4.8 74 Code that adds a parameter control to the applet 75 Browser view of the applet in figure 4.10 76 An applet that extends that in figure 4.10 by configuring the Plotter 77 View of the applet in figure 4.12, as displayed by Sun's Appletviewer. 78 An HTML segment that modifies that of figure 4.2 to use Jar files 79 View of the inspection paradox applet described in the Appendix 82 Organization of actors in the ptolemy.actor.lib package 90 Organization of actors in the ptolemy.actor.gui package 92 The Token class defines a polymorphic interface that Includes basic arithmetic operations 93 The fire() mthod of the AddSubtract shows the use of Polymorphic add() and subtract() methods in the Token Class (see figure 5.3) 93 The fire() and postfire() mehtods of the Average actor show How state is updated only in postfire() 96 Source actors in the ptolemy.actor.lib package 100 Sink actors in the ptolemy.actor.lib package 102 Display actors in the ptolemy.actor.gui package 103 Logical actors in the ptolemy.actor.lib.logic actors 107 The actors in the ptolemy.actor.lib conversion package 108 Anatomy of an actor 111 Code segment showing the port definitions in the Transformer Class 114 Code segment from the Scale actor, showing the handling of Ports and parameters 115 Code segment from the Poisson actor, showing the attribute Charged () method 116 Code segment from the Scale actor, showing the attribute Changed() method 117 Code segment from the Scale actor showing the clone() Method 118 Code segment from the Average actor, showing the InitializeQ method 119 VI Figure 6.8 Figure 6.9 Figure 6.10 Figure Figure Figure Figure Figure Figure Figure 7.1 7.2 7.3 7.4 7.5 7.6 7.7 Figure 7.8 Figure 7.9 Figure 7.10 Figure Figure Figure Figure Figure Figure 7.11 7.12 7.13 7.14 8.1 8.2 Figure Figure Figure Figure 8.3 8.4 8.5 8.6 Figure 8.7 Figure 8.8 Figure 8.9 Figure 8.10 Figure 8.11 Figure 8.12 Figure 8.13 Figure 8.14 Figure 8.15 Code for the Bernoulli actor, which is not data polymorphic 121 Code segment from the Average actor showing the action Methods 122 Code segments from the SequenceSource and Time Source base classes 124 Visual notation and terminology 133/134 Key classes in the kernel package and their methods 135 Support classes in the kernel.util package 136 Key classes supporting clustered graphs 140 Transparent ports are linked to relations below their 141 An example with level-crossing transitions 142 A tunneling entity contains a relation with inside links to More than one port 143 An example of a clustered graph 144 The same topology as in figure 7.8 implemented as a Java Class 143 The same topology as in figure 7.8 described by the Tel Blend commands to create it 146 Key methods applied to figure 7.8 147 Using monitors for thread safety 148 Classes and interfaces in kernel.event 151 Summary of exceptions defined in the kernel.util package 154 Message passing is medicated by the IOPort class 157 Port and receiver classes that provide infrastructure for Message passing under various communication protocols 158 A port can support more than on channel 157 A bus is an IORelation that represents multiple channels 159 Channels may reach multiple destination 159 An elaborate example showing several features of the data transport mechanism 160 An example showing busses combined with input, output, And transparent ports 161 Tel Blend code to construct the example in figure 8.7 161 Bus widths inside and outside a transparent port need not Agree 162 Static structure diagram for the actor.util package 164 An actor that distributes successive input tokens to a set of Output channels 165 Basic classes in the actor package that support execution 166 Example application showing a typical arrangement of Actors, directors, and managers 168 Example execution sequence implemented by run() method Of the Director class 170 Alternative execution sequence implemented by run() method Of the Director class 171 vii Figure 8.16 Figure 8.17 An example of an opaque composite actor UML static structure diagram for actor.sched and actor. Process packages Figure 9.1 Static Structure Diagram (Class Diagram) for the classes In the data package Figure 9.2 The type lattice Figure 9.3 Static structure diagram for the data.expr package Figure 9.4 Functions available to the expression language from the Java.lang.Math class Figure 9.5 Functions available to the expression language from the Ptolemy.data.expr.Utility-Functions class. Figure 9.6 Organization of the FixPoint Data Type Figure 10.1 Classes in the graph package Figure 10.2 An undirected graph Figure 10.3 A 4-point CPO that also happens to be a lattice Figure 11.1 An imaginary Ptolemy II application Figure 11.2 A topology with types Figure 11.3 The Type Lattice Figure 11.4 Classes in the datatype package Figure 11.5 Classes in the actor package that support type checking Figure 11.6 Two simple topologies with types Figure 11.7 conversion between sequence and array Figure 12.1 Result of invoking ptplot on the command line with no Arguments 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 Figure 12.3 Encapsulated postscript generated by the Export command In the File menu of ptplot can be imported into word Processors Figure 12.4 You can modify the data being plotted by selecting a data Set and then dragging the right mouse button 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. Figure 12.6 The core classes of the plot package Figure 12.7 Core classes supporting applets and applications Figure 12.8 UML static structure diagram for the plotml package Figure 12.9 The document type definition (DTD) for the PlotML Figure 12.10 The compat package provides compatibility with the older Pxgraph program Figure 13.1 Static structure diagram for effigies and tableaux Figure 13.2 Static structure diagram for the Factory pattern Figure 13.3 Static structure that is useful for handling text documents Figure 13.4 Static structure of how the TableauFactory class, and an Vlll 173 174 176 179 182 187 186 191 198 199 200 205 208 209 212 213 217 218 222 224 225 225 226 228 230 231 235 243 248 248 249 Figure 13.5 Figure 13.6 Figure 13.7 Figure 13.8 Figure 13.9 Figure 13.10 Figure 13.11 Figure 13.12 Figure 13.13 Figure 13.14 Figure 13.15 Figure 14.1 Figure 14.2 Figure 14.3 Figure 14.4 Figure 14.5 Figure 14.6 Figure 14.7 Figure 14.8 Figure 14.9 Figure 14.10 Figure 14.11 Figure 14.12 Figure 14.13 Figure 14.14 Figure 14.15 Figure 14.16 Figure 14.17 Figure 14.18 Figure 14.19 Figure 14.20 Figure 15.1 Figure 15.2 Figure 15.3 Figure 15.4 Figure 15.5 Figure 15.6 Figure 15.7 Figure 15.8 Figure 15.9 Example of how tableau factories are used with text Documents 249 Static structure diagram for the Configuration and Mode Directory classes 250 Static structure diagram for the TableauFrame class 251 Sequence diagram for opening and existing design artifact 252 Sequence diagram for creating a new design artifact 253 Static sequence for Ptolemy effigies 254 Static structure of the Ptolemy graph editor 255 Vergil Screenshot 256 Static structure of the Ptolemy graph editor 256 Vergil Screenshot 257 Static structure of the Ptolemy graph editor 257 Static structure oftheptolemy.vergil.tree package 258 Possible implementations of the system equations 260/261 A conceptual block diagram for continuous time systems 264 The block diagram for the example system 264 Embedding a DE component in a CT system 274 Embedding CT component in a DE system 274 Hybrid system modeling 274 Block diagram for the Lorenz system 275 The simulation result of the Lorenz system 275 Micro-accelerator with digital feedback 276 Block diagram for the micro-accelerator system 276 Execution result ofthemicroaccelerometor system 277 Sticky point masses system 277 Modeling sticky point masses 278 The simulation result of the sticky point masses system 278 The packages in the CT domain 279 UML for ct.kernel.util package 279 UML for ctkernel package, actor related classes 280 UML for ctkernel package, director related classes 282 UML for ctkernelsolver package 281 A chain of integrators 283 If there are simultaneous events B and D 288 An example of a directed zero-delay loop 289 A Delay actor can be used to break a zero-delay loop 289 UML static structure diagram for the DE kernel package 291 The library of DE-specific actors 293 Topology before and after mutation for the example in Figure 15.7 293 An example of a class that constructs a model and then Mutates it 294 A domain-specific actor in DE 297 Code for the Server actor 299 IX Figure Figure Figure Figure Figure 15.10 15.11 15.12 16.1 16.2 Figure Figure Figure Figure Figure Figure Figure 16.3 16.4 16.5 16.6 16.7 16.8 16.9 Figure Figure Figure Figure Figure Figure Figure 16.10 16.11 17.1 17.2 17.3 17.4 17.5 Figure Figure Figure Figure Figure 17.6 17.7 17.8 17.9 17.10 Figure 17.11 Figure 17.12 Figure 17.13 Figure 17.14 Figure Figure Figure Figure Figure Figure Figure Figure Figure Figure 17.15 18.1 18.2 18.3 18.4 18.5 18.6 18.7 18.8 19.1 Code listings for two style of writing the ABRecognizer 300 The run() method of the ABRO actor 302 An example of heterogeneous and hierarchical composition 303/304 A SDF model that deadlocks 306 The model of figure 16.1 corrected with an instance of SampleDelay in the feedback loob 306 An SDF model with inconsistent rates 307 Figure 16.3 modified to have consistent rates 307 A model that plots the Fast Fourier Transform of a signal 308 A model that plots the values of a signal 308 An example SDF model 309 A consistent cyclic graph, properly annotated with delays 310 Two models with each port annotated with the appropriate Rate properties 311 The static structure of the SDF kernel classes 312 The sequence of method calls during scheduling 314 Illustrating how processes block waiting to rendezvous 318 Example of how a CDO might be used in a buffer 320 Illustration of the dining philosophers problem 322 Processors contending for memory access 323 Illustrations of Sieve of Eratosthenes for obtaining first six Primes 324 Actors involved in M/M/l demo 325 Template for executing a CDO construct 326 Code used to implement the buffer process described 327 Static structure diagram for classes in the CSP kernel 329 Sequence of steps involved in setting up and controlling the Model 328 Code executed by ProcessThread.run() 330 Rendezvous algorithm 334 Conceptual view of how conditional communication is built On top of rendezvous 335 Algorithm used to determine if a conditional rendezvous Branch succeeds or fails 336 Modification of rendezvous algorithm 338 DDE actors and local time 341 Timed deadlock 341 Timed deadlock 342 Localized Zeno condition topology 343 Initializing Feedback Topologies 345 Key classes for locally Managing Time 346 Additional Classes in the DDE Kernel 348 Deadlock Due to Unordered Locking 349/350 UML diagram for classes and methods related to the PN Domain 355 Figure 19.2 Figure 19.3 Figure 19.4 get() method of PNQueueReceiver put() method of PNQueueReceiver setFinish() method of PNQueueReceiver XI 357 358 358 List of Tables Table 20 Table 21 Table 22 Names of special attributes Methods of Component Relation Methods of ComponentPort Table 21 Table 22 Table 23 CT Director Parameters Additional Parameter for CTMultiSolverDirector Additional Parameter for CTMixedSignalDirector Xll 138-139 147 147 271 272 273 Introduction Author: Edward A. Lee 1.1 Modeling and Design The Ptolemy project studies heterogeneous modeling, simulation, and design of concurrent systems. The focus is on embedded systems, particularly those that mix technologies including, for example, analog and digital electronics, hardware and software, and electronics and mechanical devices (including MEMS, microelectromechanical systems). The focus is also on systems that are complex in the sense that they mix widely different operations, such as signal processing, feedback control, sequential decision making, and user interfaces. Modeling is the act of representing a system or subsystem formally. A model might be mathematical, in which case it can be viewed as a set of assertions about properties of the system such as its functionality or physical dimensions. A model can also be constructive, in which case it defines a computational procedure that mimics a set of properties of the system. Constructive models are often used to describe behavior of a system in response to stimulus from outside the system. Constructive models are also called executable models. Design is the act of defining a system or subsystem. Usually this involves defining one or more models of the system and refining the models until the desired functionality is obtained within a set of constraints. Design and modeling are obviously closely coupled. In some circumstances, models may be immutable, in the sense that they describe subsystems, constraints, or behaviors that are externally imposed on a design. For instance, they may describe a mechanical system that is not under design, but must be controlled by an electronic system that is under design. Executable models are sometimes called simulations, an appropriate term when the executable model is clearly distinct from the system it models. However, in many electronic systems, a model that starts as a simulation mutates into a software implementation of the system. The distinction between the model and the system itself becomes blurred in this case. This is particularly true for embedded software. Embedded software is software that resides in devices that are not first-and-foremost computers. It is pervasive, appearing in automobiles, telephones, pagers, consumer electronics, toys, aircraft, trains, security systems, weapons systems, printers, modems, copiers, thermostats, manufacturing systems, appliances, etc. A technically active person probably interacts regularly with more pieces of embedded software than conventional software. A major emphasis in Ptolemy II is on the methodology for defining and producing embedded software together with the systems within which it is embedded. 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 mechanical system, then the model of computation may literally be the laws of physics. More commonly, however, 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 distinct 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 computation 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 intrinsically sequential nature. Choosing an inappropriate model of computation may compromise the quality of design by leading the designer into a more costly or less reliable implementation. A principle of the Ptolemy project is that the choices of models of computation strongly affect the quality of a system design. 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. The objective in Ptolemy II is to support the construction and interoperability of executable models that are built under a wide variety of models of computation. 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 discipline on the interaction of components. Component-based design in Ptolemy II involves disciplined interactions between components governed by a model of computation. 1.2 Architecture Design Architecture description languages (ADLs), such as Wright [3] and Rapide [54], focus on formalisms for describing the rich sorts of component interactions that commonly arise in software architecture. Ptolemy II, by contrast, might be called an architecture design language, because its objective is not so much to describe existing interactions, but rather to promote coherent software architecture by imposing some structure on those interactions. Thus, while an ADL might focus on the compatibility of a sender and receiver in two distinct components, we would focus on a pattern of interactions among a set of components. Instead of, for example, verifying that a particular protocol in a single port-to-port interaction does not deadlock [3], we would focus on whether an assemblage of components can deadlock. It is arguable that our approach is less modular, because components must be designed to the framework. Typical ADLs can describe pre-existing components, whereas in Ptolemy II, such preexisting components would have to wrapped in Ptolemy II actors. Moreover, designing components to a particular interface may limit their reusability, and in fact the interface may not match their needs well. All of these are valid points, and indeed a major part of our research effort is to ameliorate these limitations. The net effect, we believe, is an approach that is much more powerful than ADLs. First, we design components to be domain polymorphic, meaning that they can interact with other components within a wide variety of domains. In other words, instead of coming up with an ADL that can describe a number of different interaction mechanisms, we have come up with an architecture where components can be easily designed to interact in a number of ways. We argue that this makes the components more reusable, not less, because disciplined interaction within a well-defined semantics is possible. By contrast, with pre-existing components that have rigid interfaces, the best we can hope for is ad-hoc synthesis of adapters between incompatible interfaces, something that is likely to lead to designs that are very difficult to understand and to verify. Whereas ADLs draw an analogy between compatibility of interfaces and type checking [3], we use a technique much more powerful than type checking alone, namely polymorphism. Second, to avoid the problem that a particular interaction mechanism may not fit the needs of a component well, we provide a rich set of interaction mechanisms embodied in the Ptolemy II domains. The domains force component designers to think about the overall pattern of interactions, and trade off uniformity for expressiveness. Where expressiveness is paramount, the ability of Ptolemy II to hierarchically mix domains offers essentially the same richness of more ad-hoc designs, but with much more discipline. By contrast, a non-trivial component designed without such structure is likely to use a melange, or ad-hoc mixture of interaction mechanisms, making it difficult to embed it within a comprehensible system. Third, whereas an ADL might choose a particular model of computation to provide it with a formal structure, such as CSP for Wright [3], we have developed a more abstract formal framework that describes models of computation at a meta level [50]. This means that we do not have to perform awkward translations to describe one model of computation in terms of another. For example, stream based communication via FIFO channels are awkward in Wright [3]. We make these ideas concrete by describing the models of computation implemented in the Ptolemy II domains. 1.3 Models of Computation There is a rich variety of models of computation that deal with concurrency and time in different ways. Each gives an interaction mechanism for components. In this section, we describe models of computation that are implemented in Ptolemy II domains, plus a couple of additional ones that are planned. Our focus has been on models of computation that are most useful for embedded systems. All of these can lend a semantics to the same bubble-and-arc, or block-and-arrow diagram shown in figure 1.1. Ptolemy II models are (clustered, or hierarchical) graphs of the form of figure 1.1, where the nodes are entities and the arcs are relations. For most domains, the entities are actors (entities with functionality) and the relations connecting them represent communication between actors. 1.3.1 Communicating Sequential Processes - CSP In the CSP domain (communicating sequential processes), created by Neil Smyth [80], actors represent concurrently executing processes, implemented as Java threads. These processes communicate by atomic, instantaneous actions called rendezvous (or sometimes, synchronous message passing). If two processes are to communicate, and one reaches the point first at which it is ready to communicate, then it stalls until the other process is ready to communicate. "Atomic" means that the two processes are simultaneously involved in the exchange, and that the exchange is initiated and completed in a single uninterruptable step. Examples of rendezvous models include Hoare's communicating sequential processes (CSP) [37] and Milner's calculus of communicating systems (CCS) [58]. This model of computation has been realized in a number of concurrent programming languages, including Lotos and Occam. Rendezvous models are particularly well-matched to applications where resource sharing is a key element, such as client-server database models and multitasking or multiplexing of hardware resources. A key feature of rendezvous-based models is their ability to cleanly model nondeterminate interactions. The CSP domain implements both conditional send and conditional receive. It also includes an experimental timed extension. 1.3.2 Continuous Time - CT In the CT domain (continuous time), created Jie Liu [52], actors represent components that interact via continuous-time signals. Actors typically specify algebraic or differential relations between inputs and outputs. The job of the director in the domain is to find a fixed-point, i.e., a set of continuous-time functions that satisfy all the relations. FIGURE 1.1. A single syntax (bubble-and-arc or block-and-arrow diagram) can have a number of possible semantics (interpretations). The CT domain includes an extensible set of differential equation solvers. The domain, therefore, is useful for modeling physical systems with linear or nonlinear algebraic/differential equation descriptions, such as analog circuits and many mechanical systems. Its model of computation is similar to that used in Simulink, Saber, and VHDL-AMS, and is closely related to that in Spice circuit simulators. Embedded systems frequently contain components that are best modeled using differential equations, such as MEMS and other mechanical components, analog circuits, and microwave circuits. These components, however, interact with an electronic system that may serve as a controller or a recipient of sensor data. This electronic system may be digital. Joint modeling of a continuous subsystem with digital electronics is known as mixed signal modeling. The CT domain is designed to interoperate with other Ptolemy domains, such as DE, to achieve mixed signal modeling. To support such modeling, the CT domain models of discrete events as Dirac delta functions. It also includes the ability to precisely detect threshold crossings to produce discrete events. Physical systems often have simple models that are only valid over a certain regime of operation. Outside that regime, another model may be appropriate. A modal model is one that switches between these simple models when the system transitions between regimes. The CT domain interoperates with the FSM domain to create modal models. 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 Verilog. 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 general 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 independent 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 analyzing 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 relationships. 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 models 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.5 Discrete Time - DT The discrete-time (DT) domain, written by Chamberlain Fong, extends the SDF domain (described below) with a notion of time between tokens. Communication between actors takes the form of a sequence of tokens where the time between tokens is uniform. Multirate models, where distinct connections have distinct time intervals between tokens, are also supported. 1.3.6 Finite-State Machines - FSM The finite-state machine (FSM) domain, written by Xiaojun Liu, is radically different from the other Ptolemy II domains. The entities in this domain represent not actors but rather state, and the connections represent transitions between states. Execution is a strictly ordered sequence of state transitions. The FSM domain leverages the built-in expression language in Ptolemy II to evaluate guards, which determine when state transitions can be taken. FSM models are excellent for control logic in embedded systems, particularly safety-critical systems. FSM models are amenable to in-depth formal analysis, and thus can be used to avoid surprising behavior. FSM models have a number of key weaknesses. First, at a very fundamental level, they are not as expressive as the other models of computation described here. They are not sufficiently rich to describe all partial recursive functions. However, this weakness is acceptable in light of the formal analysis that becomes possible. Many questions about designs are decidable for FSMs and undecidable for other models of computation. A second key weakness is that the number of states can get very large even in the face of only modest complexity. This makes the models unwieldy. The latter problem can often be solved by using FSMs in combination with concurrent models of computation. This was first noted by David Harel, who introduced that Statecharts formalism. Statecharts combine a loose version of synchronous-reactive modeling (described below) with FSMs [31]. FSMs have also been combined with differential equations, yielding the so-called hybrid systems model of computation [33]. The FSM domain in Ptolemy II can be hierarchically combined with other domains. We call the resulting formalism "*charts" (pronounced "starcharts") where the star represents a wildcard [28]. Since most other domains represent concurrent computations, *charts model concurrent finite state machines with a variety of concurrency semantics. When combined with CT, they yield hybrid systems and modal models. When combined with SR (described below), they yield something close to State- charts. When combined with process networks, they resemble SDL [79]. 1.3.7 Process Networks - PN In the process networks (PN) domain, created by Mudit Goel [29], processes communicate by sending messages through channels that can buffer the messages. The sender of the message need not wait for the receiver to be ready to receive the message. This style of communication is often called asynchronous message passing. There are several variants of this technique, but the PN domain specifically implements one that ensures determinate computation, namely Kahn process networks [41]. In the PN model of computation, the arcs represent sequences of data values (tokens), and the entities represent functions that map input sequences into output sequences. Certain technical restrictions on these functions are necessary to ensure determinacy, meaning that the sequences are fully specified. In particular, the function implemented by an entity must be prefix monotonic. The PN domain realizes a subclass of such functions, first described by Kahn and MacQueen [42], where blocking reads ensure monotonicity. PN models are loosely coupled, and hence relatively easy to parallelize or distribute. They can be implemented efficiently in both software and hardware, and hence leave implementation options open. A key weakness of PN models is that they are awkward for specifying control logic, although much of this awkwardness may be ameliorated by combining them with FSM. The PN domain in Ptolemy II has a highly experimental timed extension. This adds to the blocking reads a method for stalling processes until time advances. We anticipate that this timed extension will make interoperation with timed domains much more practical. 1.3.8 Synchronous Dataflow - SDF The synchronous dataflow (SDF) domain, created by Steve Neuendorffer, handles regular computations that operate on streams. Dataflow models, popular in signal processing, are a special case of process networks (for the complete explanation of this, see [49]). Dataflow models construct processes of a process network as sequences of atomic actor firings. Synchronous dataflow (SDF) is a particularly restricted special case with the extremely useful property that deadlock and boundedness are decidable. Moreover, the schedule of firings, parallel or sequential, is computable statically, making SDF an extremely useful specification formalism for embedded real-time software and for hardware. Certain generalizations sometimes yield to similar analysis. Boolean dataflow (BDF) models sometimes yield to deadlock and boundedness analysis, although fundamentally these questions are undecidable. Dynamic dataflow (DDF) uses only run-time analysis, and thus makes no attempt to statically answer questions about deadlock and boundedness. Neither a BDF nor DDF domain has yet been written in Ptolemy II. Process networks (PN) serves in the interrim to handle computations that do not match the restrictions of SDF. 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.4 Choosing Models of Computation The rich variety of concurrent models of computation outlined in the previous section can be daunting to a designer faced with having to select them. Most designers today do not face this choice because they get exposed to only one or two. This is changing, however, as the level of abstraction and domain-specificity of design software both rise. We expect that sophisticated and highly visual user interfaces will be needed to enable designers to cope with this heterogeneity. An essential difference between concurrent models of computation is their modeling of time. Some are very explicit by taking time to be a real number that advances uniformly, and placing events on a time line or evolving continuous signals along the time line. Others are more abstract and take time to be discrete. Others are still more abstract and take time to be merely a constraint imposed by causality. This latter interpretation results in time that is partially ordered, and explains much of the expressiveness in process networks and rendezvous-based models of computation. Partially ordered time provides a mathematical framework for formally analyzing and comparing models of computation [50]. A grand unified approach to modeling would seek a concurrent model of computation that serves all purposes. This could be accomplished by creating a melange, a mixture of all of the above, but such a mixture would be extremely complex and difficult to use, and synthesis and simulation tools would be difficult to design. Another alternative would be to choose one concurrent model of computation, say the rendezvous model, and show that all the others are subsumed as special cases. This is relatively easy to do, in theory. It is the premise of Wright, for example [3]. Most of these models of computation are sufficiently expressive to be able to subsume most of the others. However, this fails to acknowledge the strengths and weaknesses of each model of computation. Rendezvous is very good at resource management, but very awkward for loosely coupled data-oriented computations. Asynchronous message passing is the reverse, where resource management is awkward, but data-oriented computations are natural1. Thus, to design interesting systems, designers need to use heterogeneous models. 1.5 Visual Syntaxes Visual depictions of electronic systems have always held a strong human appeal, making them extremely effective in conveying information about a design. Many of the domains of interest in the Ptolemy project use such depictions to completely and formally specify models. 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 computation. 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 Verilog. 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 support, 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 systems. 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 computation. 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, packages that support executable models, and domains, which are packages that implement a particular model of computation. 1.6.1 Package Structure The package structure is shown in figure 1.2. This is a UML package diagram. The name of each package is in the tab at the top of each box. Subpackages are contained within their parent package. Dependencies between packages are shown by dotted lines with arrow heads. For example, actor depends on kernel.event which depends on kernel which depends on kernel.util. Actor also depends on data and graph. The role of each package is explained below. actor This package supports executable entities that receive and send data through ports. It includes both untyped and typed actors. For typed actors, it implements a sophisticated type system that supports polymorphism. It includes the base class Director for domain-specific classes that control the execution of a model. actor.event Event Handling (see also kernel.event) actor.gui This subpackage is a library of polymorphic actors with user interface components, plus some convenience base classes for applets and applications. math kernel Components ntity ComponentPort ComponentR elation Composite Entity Entity Port Relation ~> ~> kernel, event Array String Format ComplexArrayMath DoubleArrayStat ExtendedMath FloatArrayMath Fraction IntegerMatrixMath LongArrayMath MatrixMath Quantizer SignalProcessing graph kernel, util U^- Attribute CrossRefüst Debug Listener Debuggable llegalActionException I ntemalE rro rE xcepti o n Invalids täte Exception KemelE xcepti on NameDupticationException Nameable NamedLisI NamedObj N oS uchl tern E xcepti o n P to lern yT bread RecorderListener StreamListener Workspace -^> CPO Di rectedAcydicGraph DirectedGraph Graph Inequality Inequality Solver InequalityTerm X ^ data C hange FailedException ChangeList ChangeListener ChangeRequest StreamChange Listener -^ actor Actor AtomicActor CompositeActor Configurable Director Executable ExecutionListener lOPort lORelation Mailbox Manager NoRoomException NoTokenException QueueReceiver Receiver Stream ExecutionListener TypeConflictException TypeEvent TypeListener TypedActor TypedAto mi cActor Ty pedCompo siteActo r TypedlOPort TypedlORelation ->j actor, lib AbsoluteValue AddSubtract Average Bernoulli Clock Commutator plot actor, util CQComparator CalendarQueue DoubleCQComparator FIFOQueue TimedEvent actor, gui Const Complex DoubleArrayMath Double MatrixMath FixPoint FloatMatrixMath IntegerArrayMath Interpolation LongMatrixMath Precision SampleGenerator -> -> Com po siteActo rAppI CurrentTime ication Distributor Display Expression EditorPaneFactory FileWriter ExecEvent Gaussian ExecEventListener Maximum Histogram Plotter Minimum MatrixView Multiply Divide MoM LApplet Poisson MoMLApplication Pulse ModelFrame Quantizer ModelPane Ramp Placeable RandomSource Plotter Recorder PtolemyApplet Scale PtolemyApplication SequenceActor I «Interface» Exacut*bl* Entity container ,. On 0..1 Port On link link O.n Relation T ComponantPort «Interface» Actor ComponentEntlty 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 ^jnjxj ^file:/C:/ptII/ptolemy/configs/intro.htm File Help Ptolemy II Version 1.0 • Overview • Authors • Quick tour • Limitations • Troubleshooting • Acknowledgements • Copyright FIGURE 2.2. Welcome window. 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. 2.3 Data Types and the Type System So what is really going on here? The Const actor is creating values on its output port. The Display actor is consuming data values from its input port and displaying them in the run window. The value that is created by the Const actor can be any type of object. For example, try giving the value 1 (the integer with value one), or 1.0 (the floating point number with value one), or {1.0} (An array containing a one), or {value=l, name="one"} (A record of two elements: an integer named value and a string named name), or even [1,1; 1,1] (a two by two matrix). They all seem pretty much the same in the Display, but Ptolemy knows the difference between them! To see the difference, try creating the model in Figure 2.7. The Ramp actor is listed under sources and the AddSubtract actor is listed under math. Set ^Jöjxj l;H Unnamed File View Edit Graph Help ■"""A director library actor library Graphics FIGURE 2.3. An empty Vergil Graph Editor. JEdit parameters for Const /o\ \^/ value: ['Hello World"| Commit Add 1 Remove j Edit Styles J FIGURE 2.5. The Const parameter editor. 25 Cancel j 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 ,jnjx] md File View Edit Graph Help utilities Hanna SDF J actor library B- ' I sources & _J sinks 6 -_] math B- " I flow control B- ' t logic B- ' 1 conversions B- " I signal processing m-_J sdf B- ' I continuous time Display Const FIGURE 2.4. The Hello World example. |x| M:f| Unnamed File View Debug Help | GO 1 Pause 1 Resu me 1 A Hello World Stop Model parameters: has no parameters. Director parameters: ;■ iterations: |1 vectorizationFactor: I« - l^»~ » FIGURE 2.9. A simple signal processing example. 28 SequencePlotter P 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. 2.6 SDF and Multirate Systems So far we have been dealing with relatively simple systems. They are simple in the sense that each actor produces and consumes one token from each port at a time. In this case, the SDF director simply ensures that an actor fires after the actors whose value it depends on. The number of output values that is created is determined by the number of iterations. It turns out that the SDF scheduler is actually much more sophisticated. It is capable of scheduling the execution of actors with arbitrary prespecified data rates. Not all actors produce and consume just a JJJ3Jx| /C:/users/neuendor/ptII/ptolemy/verail/noi$y.Kml Rle Vie* Oebug Help i Go i! Pause Resume agin Stop i n J I 0.0 0.5 n 1 1" Model parameters: noisy has no parameters. Director parameters: iterations: 200 vectorizationFactor: im a i I L 1.0 1.5 2.0 X10 execution finished. FIGURE 2.10. The output of the simple signal processing example above. Channel SDF Copyl :Channe! Sinewave AddSubtract Copy2:Channel)—P MultiplyDivide SmoothedSpectrum !►—* Const Copy3;Channel SequencePlotter G FIGURE 2.11. An example of a broadcast relation. 29 D 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 spectrum 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 number 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. 2.7 Using the Plotter The plot shown in figure 2.13 is not particularly satisfying. It has no title, the axes are not labeled, and the horizontal axis ranges from 0 to 2551, because in one iteration, the Spectrum actor produces 256 output tokens. These outputs represent frequency bins that range between -n and n radians per second. The SequencePlotter actor has some pertinent parameters, shown in figure 2.14. The xlnit parameter specifies the value to use on the horizontal axis for the first token. The xllnit parameter specifies the SDF Sinewave Spectrum SequencePlotter FIGURE 2.12. A multirate SDF model. ^Unnamed File View ';; Debug _lDlx| »■■''■'■ Help punctual 1 Go Jl Pause Resume Stop Model Execute the model 1 30 has n o parameters. - 20 Direct )r parameters: : I 10 iteratlc ns: |i vector! zationFactor: li P I / \/\ ''? -20 0.0 0.5 10 1.5 2.0 2.5 ><102 execution finished. | FIGURE 2.13. Execution of the multirate SDF model. 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 appearance 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 jEdit parameters for SequencePlotter N?) fillOnWrapup: IM W'r' true legend: startingDataset 0 xlnlt 0.0 xunit 1.0 Commit | Remove Ade Edit Styles | Cancel l FIGURE 2.14. Parameters of the SequencePlotter actor. IIIH''- I £ f|Unnamed File View Debug -iDlXl Help t^\m\w\WL\ | go J Pause 1 Resume | ::: : : ::Siop:"i:?:| 40 . Model parameters: 30 ; has no parameters. 20 Director parameters: 10 iterations: fi vectorizationFactor: fi . [ )\ i ^.^-10 -20 : / \ ■ \ .' : <\,"^^ i ' { ■■■■■- -3 -2.-1 0 execution finished. 1 2 3 f;,:;..-: FIGURE 2.15. Better labeled plot, where the horizontal axis now properly represents the frequency values. {Set plot format C^) Title: Spectrum X Label: frequency Y Label: dB Grid: C X Range: •3.141592653589793, 3.11704896098361 Stems: r Y Range: ■18 900959335714024, 42.0275991961768 Connect (f Marks; XTlcks: P none C points C dote C various r pixels: Use Color (f .14159, -PI/2 -1.570795, 0 0.0, PI/2 1.570795, PI 3.1415£ YTicks: Apply j FIGURE 2.16. Format control window for a plot. 31 Cancel j that result in the plot shown in figure 2.17. Most of these are self-explanatory, but the following pointers 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 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 executes 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 FIGURE 3.1. Simple example in the file SPTII/ptolemy/moml/demo/test.xml. 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. 3.2 MoML Principles The key features of MoML include: Web integration. MoML is an XML schema. XML, the popular extensible markup language, provides a standard syntax and a standard way of 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, and is intended for precisely this sort of specialization into Schemas. File references are via URIs (in practice, URLs), both relative and absolute, so MoML is equally comfortable working in applets and applications. Implementation independence. The MoML language is designed to work with a variety of tools. A modeling tool that reads MoML files is expected to provide a class loader in some form. Given the name of a class, and possibly a URL for the class definition, the class loader must be able to instantiate it. Classes might be defined in MoML or in some base language such as Java. In Java, the class loader could be that built in to the JVM. In C++ or other languages, the class loader would have to implemented by the modeling tool. Ptolemy II can be viewed as a reference implementation of a MoML tool that uses Java as its base language. Extensibility. Components can be parameterized in two ways. First, they can have named properties with string values. Second, they can be associated with an external configuration file that can be in any format understood by the component. Typically, the configuration will be in some other XML schema, such as PlotML or SVG (scalable vector graphics). Classes and inheritance. Components can be defined in MoML as classes which can then be instantiated in a model. Components can extend other components through an object-oriented inheritance mechanism. Semantics independence. MoML defines no semantics for an interconnection of components. It represents only the hierarchical containment relationships between entities with properties, their jnjx| ^file:/C:/ptII.'ptolemy/nioml/demo/test.Hml File Debug Help Go Pause Resume »RfJR Stop 1 I 1 1 1 1 1 1 1 Director parameters: iterations: 10 j, ^ vectorizationFactor: S \ y ■si 1 1 i execution finished. FIGURE 3.2. Simple example of a Ptolemy II model execution control window. 35 i i i i i 8 9 10 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 toplevel 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. 3.2.1 Clustered Graphs A model is given as a clustered graph, an abstract syntax for netlists, state transition diagrams, block diagrams, etc. An abstract syntax is a conceptual data organization. It can be contrasted with a concrete syntax, which is a syntax for a persistent, readable representation of the data, such as EDIF for netlists. MoML is a concrete syntax for the clustered graph abstract syntax. A particular graph configuration is called a topology. A topology is a collection of entities and relations. We use the graphical notation shown in figure 3.3, where entities are depicted as rounded boxes and relations as diamonds. Entities have ports, 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 association 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 mathematical 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 multiway associations, whereas an arc in a graph is a two-way association. A third difference is that mathematical graphs normally have no notion of hierarchy (clustering). Relations are intended to serve a mediators, in the sense of the Mediator design pattern of Gamma, et al. "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 ConnectionLink ▲. Port Entity FIGURE 3.3. Visual notation and terminology. 36 Link 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). Clustering 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 composite. 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. 3.3 Specification of a Model In this section, we describe the XML elements that are used to define MoML models. 3.3.1 Data Organization As with all XML files, MoML files have two parts, one defining the MoML language and one containing the model data. The first part is called the document type definition, or DTD. This dual specification of content and structure is a key XML innovation. The DTD for MoML is given in figure 3.5. If FIGURE 3.4. 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). 1. Unless level-crossing links are allowed. MoML supports these, but they are discouraged. 37 <[ELEMENT deleteProperty EMPTY> <[ELEMENT doc (»PCDATA)* <[ELEMENT entity (class | configure | deleteEntity | deletePort | deleteRelation | director | doc | entity | group | import | input | link | port | property | relation | rename | rendition | unlink)*> <[ELEMENT link EMPTY> <[ELEMENT port (configure | doc | property | rename)*> <[ELEMENT property (configure | doc | property | rename)*> <[ELEMENT relation (configure | doc | property | rename | vertex)*> <1ATTLIST relation name CDATA »REQUIRED Class CDATA »IMPLIED* <[ELEMENT rename EMPTY* <[ELEMENT unlink EMPTY> model definition ... 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 references (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 -//UC Berkeley//DTD MoML 1//EN 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: class definition ... We will say more about class definitions below. 3.3.2 Overview of XML An XML document consists of the header tags "" and "" followed by exactly one element. The element has the structure: start tag 39 body end tag where the start tag has the form and the end tag has the form 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: where the body and end tag are omitted. The attributes are given as follows: 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: The value of the attribute will be "foo" (with the quotation marks). In XML """ is called an entity, creating possible confusion with our use of entity in Ptolemy II. In XML, an entity is a named storage unit of data. Thus, """ references an entity called "quot" that stores a double quote character. 3.3.3 Names and Classes Most MoML elements have name and class attributes. The name is a handle for the object being defined or referenced by the element. In MoML, the same syntax is used to reference a pre-existing object as to create a new object. If a new object is being created, then the class attribute (usually) must be given. If a pre-existing object is being referenced, or if the MoML reader has a built-in default class for the element, then the class attribute is optional. If the class attribute is given, then the pre-existing object must be an instance of the specified class. A name is either absolute or relative. Absolute names begin with a period "." and consist of a series of name fields separated by periods, as in ".x.y.z". Each name field can have alphanumeric char- 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 immediately 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". 3.3.4 Model Element A very simple MoML file looks like this: 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. TypedCompositeActor, as in the above example, is derived from CompositeEntity. 3.3.5 Entity Element A model typically contains entities, as in the following Ptolemy II example: Notice the common XML shorthand here of writing "" rather than "." 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: 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: 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 property, one with only a name and no value. For example: A property can also contain a property, as in 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 cannot 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 SequencePlotter actor, that external data will have PlotML syntax; PlotML is another XML schema for configuring plotters.) Configure information can also be given in the body of the MoML file as follows: configure information 43 With the above syntax, the configure information must be textual data. It can contain XML markup with only one restriction: if the tag "" appears in the textual data, then it must be proceeded by a matching "". That is, any configure elements in the markup must have balanced start and end tags.1 You can give both a source attribute and in-line configuration information, as in the following: configure information 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 configure 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. 3.3.7 Doc Element Some elements can be documented using the doc element. For example, Initialize the ramp above the default because... This actor produces an increasing sequence beginning with 5. 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 languages, 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 Using HTMLText with markup. An alternative method is to use an XML processing instruction as follows: Using HTMLText with markup. ?> This requires that any utility that uses the documentation information be able to handle the xhtml processing 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: doc contents The name must not conflict with any preexisting property. If a doc element or a property with the specified 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 documentation 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 In Ptolemy II, the container of a port is required to be an instance of ptolemy.kernel.Entity or a derived class. It is often useful to declare a port to be an input, an output, or both. To do this, enclose in the port a property named "input" or "output" or both, as in the following example: 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 property can be given a boolean value, as in 46 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, complexMatrix, double, doubleMatrix, fix, fixMatrix, int, intMatrix, long, longMatrix, object, string, and general. These are defined in the class ptolemy.data.type.BaseType. 3.3.9 Relations and Links To connect entities, you create relations and links. The following example describes the topology shown in figure 3.6: FIGURE 3.6. Example topology. 47 In Ptolemy II, the typical classname for a relation would be ptolemy.actor.TypedlORelation. The classname may be omitted, in which case the newRelation() method of the container is used to create a new relation. The container is required to be an instance of ptolemy.kernel.CompositeEntity, or a derived class. As usual, the class attribute may be omitted if the relation already exists in the containing entity. Notice that this example has two distinct links to c. in from two different relations. The order of these links may be important to a MoML tool, so any MoML tool must preserve the order in which they are specified, as Ptolemy II does. We say that C has two links, indexed 0 and 1. The link element can explicitly give the index number at which to insert the link. For example, we could have achieved the same effect above by saying 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 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 convention, 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 element. Consider for example, class definition ... 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 The sampling frequency in Hertz. The frequency in Hertz. Formula for the step size. 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 semantics. 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: 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. 3.3.13 Input Element It is possible to insert MoML from another file or URL into a particular point in your model. For example: 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. 3.3.14 Annotations for Visual Rendering The abstract syntax of MoML, clustered graphs, is amenable to visual renditions as bubble and arc diagrams or as block diagrams. To support tools that display and/or edit MoML files visually, MoML allows a relation to have multiple vertices that form a path. Links can then be made to individual vertices. Consider the following example: 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 diamonds. 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 specify a location for a visual rendition. For example, in Ptolemy II, the first vertex above might be given as 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: B FIGURE 3.8. Example showing how MoML might be visually rendered. 53 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. 3.4 Incremental Parsing MoML may be used as a command language to modify existing models, as well as being used to specify complete models. This technique is known as incremental parsing. 3.4.1 Adding Entities Consider for example the simple model created as follows: ... contents of the model ... Later, the following MoML element can be used to add an entity to the model: 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 defines the context for evaluation of the element 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 incremental element. This is accomplished by either using the same parser, which keeps track of the toplevel entity in the last model it parsed, or by calling the setTopLevel() or setContext() methods of the parser, passing as an argument the model. 3.4.2 Using Absolute Names Above, we have used the fact that an entity element can refer to a pre-existing element by name. That name can be relative to the context in which the entity element exists, or it can be absolute. If it is absolute, then it must nonetheless be properly contained by the enclosing entity. The following example is incorrect, and will trigger an exception: The ".top. a" cannot be specified within "b" because it is already contained within "top." 3.4.3 Adding Ports, Relations, and Links A port or relation can be added to an entity that has been previously constructed by the parser. For example, assuming that . top. inside has been constructed as before, we can add a port to it with the following MoML segment: A relation and link can then be added as follows: 3.4.4 Changing Port Configurations A port that is an input can be converted to an output with the following MoML segment: 55 A port can be made into a multiport as follows: 3.4.5 Deleting Entities, Relations, and Ports An entity that has been previously constructed by a parser can be deleted by evaluating MoML. For example, assuming that . top. inside has been constructed as before, we can delete it with the following MoML segment: Any links to ports of the entity will also be deleted. Similarly, relations can be deleted using the deleteRelation element, and ports can be deleted using the deletePort element. 3.4.6 Renaming Objects A previously existing entity can be renamed using the rename element, as follows: The new name is required to not have any periods in it. It consists of alphanumeric characters, the underscore, and spaces. 3.4.7 Changing Documentation, Properties, and Directors Documentation is attached to entities using the doc element (see section 3.3.7). A doc element can optionally be given a name; if no name is given, then the name is implicitly "doc". To replace a doc element, just give a new doc element with the same name. To remove a doc element, give a doc element with the same name and an empty body, as in or 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 Since a director is a property, this same mechanism can be used to remove a director. 3.4.8 Removing Links To remove individual links, use the unlink element. This element has three forms. The first is This unlinks a port from the specified relation. If the port is linked more than once to the specified relation, 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 ■eunlink port= "portname" index= "linknumber"/> 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 decremented 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 which leaves the link to r as the sole link, having index 0. Note that the index is not the same thing as the channel number. 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 other suitable ports). 3.4.9 Grouping Elements Occasionally, you may wish to incrementally parse a set of elements. For example, in the Ptolemy II implementation, the parser has a method for setting the context, so you could set the context to a CompositeEntity and then create several entities by parsing the following MoML: 57 However, the XML parser will fail to parse this because it requires that there be a single top-level element. The group element is provided for this purpose: This element is ignored by the parser, in that it does not define a new container for the enclosed entities. 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, 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". 3.5 Parsing MoML MoML is intended to be a generic modeling markup language, not one that is specialized to Ptolemy II. As such, Ptolemy II may be viewed as a reference implementation of a MoML tool. In Ptolemy II, MoML is supported primarily by the moml package. The moml package contains the classes shown in figure 3.9 (see appendix A of chapter 1 for UML syntax). The basis for the MoML parser is the parser distributed by Microstar. The parse() methods of the MoMLParser class read MoML data and construct a Ptolemy II model. They return the top-level model. The same parser can then be used to incrementally parse MoML segments to modify that model. The EntityLibrary class takes particular advantage of the MoML persistent file format. This class extends CompositeEntity, and is designed to contain a library of entities. But it is carefully designed to avoid instantiating those entities until there is some request for them. Instead, it maintains a MoML representation of the library. This allows for arbitrarily large libraries without the overhead of instantiating components in the library that might not be needed. Incremental parsing is when a MoML parser is used to modify a pre-existing model (see section 3.4). A MoML parser that was used to create the pre-existing model can be used to modify it. If there is 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 mutation is executed only when it is safe to modify the structure of the model. The class MoMLChangeRequest (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» ErrorHandler com.mlcrostar.xml.HanderBase +attribute(name: String, value : String, isSpecified: boolean) i +charData(data : chart], start: int, length : int) I +docTypeDecl(name : String, publicID: String, systemID: String)! +endDocument() j +endElement(name: String) j +endExtemalEntity(systemlD: String) j +error(message : String, systemID: String, line : int, column: int)] +ignorableWhitespace(data : chart], start: int, length : int) ] +processinglnstruction(target: String, data : String) i resolveEntity(publiclD: String, systemID: String) j +startDocument() j +startElement(name: String) i +startExtema!Entity(systemlD: String) • Configurable ComposKeEnttty If J +handleError(text: String, exception : Exception, parser: MoMLParser) 0..1 MoMLParser +MoML DTD 1 : String base: URL .current: Object -_currentElement: String -_manager: Manager -_panel: Container -_parser: XmlParser -_toplevel: NamedObj -_workspace: Workspace MoMLParserO MoMLParser(w: Workspace) +MoMLParser(w : Workspace, loader: ClassLoader) +addErrorHandlerthandler: ErrorHandler) +getTopLevel(): NamedObj +parser(base: URL, input: URL) +parse(base : URL, Input: InputStream): NamedObj +parse(base : URL, reader: Reader): NamedObj +parse(input: String): NamedObj +parse(base : URL, text: String) +parseFile(filename: String) ^ +reset() +setContext(context: NamedObj) +setToplevel(toplevel: NamedObj) #_currentExtemalEntity(): String jcom.mlcrostar.xml.XmlParser! ChangeRequest :+execute() A EntftyLlbrary ■_parser; MoMLParser +EntityLibrary() +EntityLibrary(workspace: Workspace) +EntityLibrary(container: CompositeEntity, name : String) +populate() MoMLChangeRequast ■_base: URL ■„context: NamedObj ■_parser: MoMLParser -„propagating: boolean +MoMLChangeRequest(originator: Object, request: String) ♦MoMLChangeRequestjoriginator: Object, context: NamedObj, request: String) +MoMLChangeRequest(originator: Object, context: NamedObj, request: String, t »oetDeferredToParentfobiect: NamedObj): NamedObj FIGURE 3.9. Classes supporting MoML parsing in the moml package. 59 i: URL) 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: 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 properties 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 modified 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 interface can ensure that only the original model, before mutations, is saved. 3.7 Special Attributes The moml package also includes a set of attribute classes that decorate the objects in a model with MoML-specific information, as shown in figure 3.10. These classes are used to decorate a Ptolemy II object with additional information that is relevant to a GUI or other user interface. For example, the Location class is used to specify the location of visual rendition of a component in a visual editor. A Vertex decorates a relation with one of several visual handles to which connections can be made. A MoMLAttribute decorates an object with a property that can describe itself with arbitrary MoML. 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 Henzinger, 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. C.l Sinewave Generator The Sinewave component is defined in the file $PTII/ptolemy/actor/lib/Sinewave.xml, which is listed below. This file defines a MoML class, which can then be referenced by the class name ptolemy.actor.lib.Sinewave. The Vergil rendition of this model is shown in figure 3.11. This composite actor generates a sine wave. The sampling frequency, in the same units as the frequency. The frequency of the sinusoid, in the same units as the sampling frequency. The phase, in radians. Locatable Attribute +getLocation(): doubleQ +setLocation(location : doubleQ) J_ Location location : doublet] valueListeners : List +Location(container: NamedObj. name : String) +addValueListener(listener: ValueListener) +getLocatk)n{): doublet] +removeValueListenerflistener: ValueListener) +setLocation(location : doublep) MoMLAttribute jnomlDescription : List i -MoMLAttribute(container: NamedObj, name : String) •appendMo ML Description (moml: String) Vertex A •Jinked : Vertex ■_ports : List _.V. Settable i+getExpression(): String i+setExpression(expression : String) +Vertex(container: Relation, name : String) +addLinkedPort(port : Port) ■getLinkedVertexO : Vertex +linkedPorts(): List +removeLinkedPort(port: Port) +setLinkedVertex(vertex: Vertex) FIGURE 3.10. Attributes in the moml package. 62 Relation Sinusoidal waveform output. |>;^nie:/C:/ptII/ptolemy/actor/lib/Sinewave.Kml Sfile View ► CJ £j Cj O ♦ Edit Graph "> ^^^^S^t0SM Help utilities director library actor library Graphics ramp t TrigFunctionO ^ääl fe •^►1 F M FIGURE 3.11. Rendition of the Sinewave class in Vergil 1.0. 63 -iDlxl 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. JOjxl y 1|file:/C:/ptII/ptolemy/moml/demo/modulation.Kml File ► lA 1 __J 1 View ♦ Edit Graph Help director utilities director library actor library Graphics display signal El L^J r mult n ►« carrier ÜjäT. - Jf \ i 1 FIGURE 3.12. Rendition of the modulation model in Vergil 1.0. 64 • ^^^^^f^- Multiply a low-frequency sine wave (the signal) by a higher frequency one (the carrier). Frequency of the carrier Frequency of the sinusoidal signal Number of iterations in an execution. This composite actor generates a sine wave. The sampling frequency, in the same units as the frequency. The frequency of the sinusoid, in the same units as the sampling frequency. The phase, in radians. This composite actor generates a sine wave. The sampling frequency, in the same units as the frequency. The frequency of the sinusoid, in the same units as the sampling frequency. ?flfile:/C:/ptII/ptolemy/moml/demo/modulation.Kml File View iit§tisl ^jnjJ£l Debug Help Modulated Waveform Example frequencyl: |pi*o.2 frequency2: |PI*0.p2 Director parameters: iterations: vectorizationFactor: 0.4 0.6 sample count execution finished. FIGURE 3.13. Execution window for the modulation model. 65 x10 «doc*The phase, in radians. «property name="_location" class="ptolemy.moml.Location" value="143.0, 135.0"> «property name="_location" class="ptolemy.moml.Location" value="347.0, 196.0"> «port name="multiply" class="ptolemy.actor.TypedIOPort"> «/port* «property name="output"/> «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* 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 construction 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 figure 4.1 (see appendix A of chapter 1 for UML syntax). 4.2 HTML Files Containing Applets An applet is a Java class that can be referenced by an HTML file and accessed over the web. Unfortunately, most browsers available today do not have built-in support for the (relatively recent) version of Java that Ptolemy II is based on. The work around is to use the Java Plug-In, which invokes Sun's Java Runtime Environment (JRE), instead of the default Java runtime in the browser. Sample HTML for a custom applet is shown in figure 4.2. The incredible ugliness and awkwardness of this text is hopefully transitory, while browser vendors agree on how to properly support plugins. You should be able to essentially copy what we have, making very few modifications. An HTML file containing the segment shown in figure 4.2 can be found in $PTII/doc/tutorial/tutorial.htm, where $PTII is the home directory of the Ptolemy II installation. Also in that directory are a number of sample Java files for applets, each named TutorialAppletw.java, where n is an integer starting with 1. These 67 «Interface» ExKutlonLitlemr Applet !+executionError(m : Manager, ex : Exception): j+executionFinished(m: Manager) managerStateChanged(m : Manager) ;+getAppletlnfo(): String getParametertnfoO : String[][] init() i+startO i+stop() 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. No JDK 1.2 support for applet! 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. 4.2.1 Creating Models In figure 4.3 is a listing of an extremely simple applet that runs in the discrete-event (DE) domain. The first line declares that the applet is in a package called "doc.tutorial," which matches the directory name relative to the codebase specified in the HTML file. In the next three lines, the applet imports three classes from Ptolemy II: • DEApplet: A base class for DE applets that is provided for convenience. This base class creates a top-level composite actor called _toplevel, a manager called _manager, and a director called _director (all protected members of the class, shown in figure 4.1). We will see shortly how to use these. • Clock: This is an actor that generates a clock signal, which by default is a sequence of events placed one time unit apart and alternating in value between 1 and 0. • TimedPlotter: This is an actor that plots functions of time. Next, the construct public class TutorialApplet extends DEApplet { ... } 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; public class TutorialApplet extends DEApplet { public void initO { super.init(); try { Clock clock = new Clock(_toplevel,"clock"); TimedPlotter plotter = new TimedPlotter(_toplevel,"plotter"); _toplevel.connect(clock.output, plotter.input); } catch (Exception ex) {} } } FIGURE 4.3. An extremely simple applet that runs in the DE domain. This text can be found in $PTII/tutorial/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 variable _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: bash-2.02$ appletviewer tutorial.htm 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. 4.2.3 Reporting Errors The code in figure 4.3 has a try ... catch statement that does something that is almost never a good idea: it discards exceptions. If an error were to occur during construction of the model, this statement would mask the error, and the applet would silently fail. The base class PtolemyApplet, fortunately, provides a report() method (see figure 4.1) for reporting errors in a uniform way. The modified code is shown in figure 4.5. 4.2.4 Graphical Elements The applet, as written so far, has the annoying feature that it opens a new window to display the plotted results, as shown in figure 4.4. Most applets will want to display their results in the browser 70 window, as part of the text of a web page. The TimedPlotter actor, and most other Ptolemy II components with graphical elements, implements 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.5 0.6 0.7 , . -""^.^ . . -0.0 0.1 0.! 0.3 0.4 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; public class TutorialApplet extends DEApplet { public void init() { super.init(); try { Clock clock = new Clock(_toplevel,"clock") ; TimedPlotter plotter = new TimedPlotter(_toplevel,"plotter"); _toplevel.connect(clock.output, plotter.input); } catch (Exception ex) { report("Error constructing model.", ex); ) 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. 4.2.5 Controlling Execution Time By default, the director in the DE domain executes a model for one time unit. This does not give a a very satisfactory plot in figure 4.7. To change the amount of time to 30, include in the body of the init() method the following line: package doc.tutorial; import ptolemy.domains.de.gui.DEApplet; import ptolemy.actor.lib.Clock; import ptolemy.actor.gui.TimedPlotter; public class TutorialApplet extends DEApplet { public void init() { super, init () ; try { Clock clock = new Clock(_toplevel,"clock"); TimedPlotter plotter = new TimedPlotter(_toplevel,"plotter"), plotter.setPanel(this); plotter.plot.setSize(700,300); _toplevel.connect(clock.output, plotter.input); } catch (Exception ex) { report("Error constructing model.", ex); } ) FIGURE 4.6. A modified applet that places the resulting plot in the browser window itself, as shown in figure 4.7. This text can be found in $PTII/doc/tutorial/TutorialApplet3.java. 0» £* ¥ew Back SO Coonuicata tldp Reload How« r.-.n-;.»r; Search NeHcap» Pnrt Security '~ m \&J ^ JDocumanfc Done FIGURE 4.7. Applet with embedded plot as displayed in Netscape Navigator. 72 _director.setStopTime(30.0); Alternatively, you can set the stop time in the HTML file by setting an applet parameter called "stopTime." To set this applet parameter to 100, add the line to the ... 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 figure 4.9. The default stop time in the entry box can be controlled by setting an applet parameter "defaultStopTime." 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; public class TutorialApplet extends DEApplet { public void initO ( super, init () ; try { Clock clock = new Clock(_toplevel,"clock"); TimedPlotter plotter = new TimedPlotter(_toplevel,"plotter") ; plotter.setPanel(this); plotter.plot.setSize(700, 250); _toplevel.connect(clock.output, plotter.input); add(_createRunControls(2)); } catch (Exception ex) { report("Error constructing model.", ex); } } 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 to the ... body in figure 4.2, and the parameter defaultStopTime="100" to the < EMBED > tag. In the SDF domain, the corresponding parameters are "iterations" and "defaultlterations." For other domains, see the documentation for the XCYApplet class, where XXX is the domain name. 4.2.6 Controlling Model Parameters Most actors in Ptolemy II have parameters that control their behavior. The Clock actor, for example, has a period parameter. Parameters are generally public members, instances of the class Parameter. To change the period of the clock from the default 2.0 to, say, 1.0, add the line to the init() method of the applet: clock.period.setExpression("1.0"); More interestingly, you may wish to offer this control to the applet user. This can be done using builtin 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 : £fc E* Sfow !): Back j fio Fotwaid 1tt*Boctaw»k» PöTxl £amur>c«tni Sccu& : Rop "3,»R«k'l»<) Tutorial Applet JE] apäPf & m &p m -Jt-\ & [Execution hnshed FIGURE 4.9. Browser view of the applet in figure 4.8. 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 notified when any entry in the query changes. To be a listener, it must implement the QueryListener interface, which requires that a method changed() be implemented. That method is defined as: public void changed(String name) { 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; public class TutorialApplet extends DEApplet implements QueryListener { private Query _query = new Query(); private Clock clock; public void initO { super.init() ; try { _clock = new Clock(_toplevel,"clock"); TimedPlotter plotter = new TimedPlotter(_toplevel,"plotter"), plotter.setPanel(this); plotter.plot.setSize(700, 250); _toplevel.connect(_clock.output, plotter.input); add(_createRunControls(2)); _query.setBackground(getBackground()); _query.addLine("period", "Period", "2.0"); query.addQueryListener(this); add(_query); } catch (Exception ex) { report("Error constructing model.", ex); } } public void changed(String name) { _clock.period.setExpression(_query.stringValue("period")); try { _go(); } catch (Exception ex) { report("Error executing model.", ex); } } FIGURE 4.10. Code that adds a parameter control to the applet. This code can be found in $PTII/doc/tutorial/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 setExpression() 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 supported, 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 several 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* ,; :l £ 2 ' B«dt;; 9 Faiwad A Reload Hem» A. & Search K* Nattcap» tf Print H Secufo Hop , > No JDK 1.2 support for applet! 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 encountered 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 process 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.l Description of the Problem 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. 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 T, then the expected waiting time is 772, which is perfectly intuitive. Counter intuitively, however, if the busses arrive according to a Poisson process with mean interarrival time equal to T, the expected waiting time is T, not 772. These expected waiting times are approximated in this applet by the average waiting time. The applet also shows the actual arrival times for both passengers and busses, and the waiting time of each passenger. 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. This paradox is called the inspection paradox because the passengers are viewed as inspecting the Poisson process of bus arrivals. The applet code is listed below, and is included in the demo directory of the DE domain. A browser window displaying the applet is shown in figure 4.15. In that figure, there are two plots, one showing the events in the model, namely arrivals of busses and passengers plus the waiting time of passengers as they board the busses. The lower plot shows a histogram of waiting times, which has an approximately exponential shape. 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. D.3 Code Listing package ptoletny. domains. de. demo. Inspect ion ; import java.awt.event.*; import Java.util.Enumeration; import javax.swing.BoxLayout; import import import import import import import import import import ptolemy.kernel.*; ptolemy.kernel.util.*; ptolemy.data.*; ptolemy.actor.Manager; ptolemy.actor.lib.*; ptolemy.actor.gui.*; ptolemy.gui.Query; ptolemy.gui.QueryListener; ptolemy.domains.de.kernel.*; ptolemy.domains.de.1ib.* ; I The above applet uses the Ptolemy II Discrete Event (DE) domain to illustrate the inspection paradox. The ÜPFf ED _ 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.

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 T, then the expected waiting time is T/2, which is perfectly intuitive. Counterintuitively, however, if the busses arrive according to a Poisson process with mean interarrival time equal to T, the expected waiting time is T, not T/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.

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.

This paradox is called the inspection paradox because the passengers are viewed as inspecting the Poisson process of bus arrivals. ©author Edward A. Lee and Lukito Muliadi ©version $Id: InspectionApplet.Java,v 1.20 2000/01/18 23:42:46 ptll Exp $ */ public class InspectionApplet extends DEApplet implements QueryListener { /////////////////////////////////////////////////////////////////// //// /** * * * * public methods //// If the argument is the string "regular", then set the variable that controls whether bus arrivals will be regular or Poisson. If the argument is anything else, update the parameters of the model from the values in the query boxes. Sparam name The name of the entry that changed. */ public void changed(String name) { if (name == "regular") { _regular = _query.booleanValue("regular"); } try { if (_regular) { _regularBus.period.setToken (new DoubleToken(_query.doubleValue("busmean"))); } else { _poissonBus.meanTime.setToken (new DoubleToken(_query.doubleValue("busmean"))); _passengerl.meanTime.setToken (new DoubleToken(_query.doubleValue("passmean"))); } _go(); 83 catch (IllegalActionException ex) { throw new InternalErrorExceptioniex.toStringf)) /** Override the base class to display the recorded average. */ 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)); _query = new Query(),■ _query.setBackground(getBackground()); _query.addLine("busmean", "Bus mean interarrival time", "1.0"); _query.addLine("passmean", "Passenger mean interarrival time", "1.0"); _query.addCheckBox("regular", "Regular bus arrivals", false); _query.addDisplay("average", "Average waiting time of passengers", "") ; _query.addQueryListener(this); getContentPane().add(_guery); 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)); // Create and configure passenger source _passengerl = new Poisson(_toplevel, "passenger"); _passengerl.values.setExpression(" [1]") ; _passengerl.meanTime.setToken(new DoubleToken(1.0)); // Waiting time _wait = new WaitingTime(_toplevel, "waitingTime"); // Average actor Average average = new Average(_toplevel, "average"); // Record the average _recorder = new Recorder(_toplevel, "recorder") 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)); // Create and configure histogram _histplot = new HistogramPlotter(_toplevel, "histplot"); _histplot.place(getContentPane0); _histplot.histogram.setBackground(getBackground()); _histplot.histogram.setGrid(false); _histplot.histogram.setTitlef"Histogram of Waiting Times"); _histplot.histogram.setXLabel("Waiting Time"); _histplot.histogram.setXRange(0.0, 6.0); _histplot.histogram.setYRange(0.0, 20.0); _histplot.histogram.addLegend(0, "Passengers"); _histplot.histogram.setBinWidtht0.2); _histplot.fillOnWrapup.setToken(new BooleanToken(false)); // Connections, except the bus source, which is postponed. _busRelation = _toplevel.connect (_wait.waitee, _eventplot. input) ,ComponentRelation rel2 = _toplevel.connect (__passengerl.output, _eventplot.input) ; _wait.waiter.Iink(rel2) ; ComponentRelation rel3 = _toplevel.connect(_wait.output, _eventplot.input); _histplot.input.link(rel3); average.input.link(rel3) ,• _toplevel.connect(average.output, _recorder.input); _initCompleted = true; // The 2 argument requests a go and stop button. getContentPane().add(_createRunControls(2)); catch (Exception ex) { report("Setup failed:", ex); } } /////////////////////////////////////////////////////////////////// //// 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; // // // if If the manager is not idle then either a run is in progress or the model has been corrupted. In either case, we do not want to run. (_manager.getState() != jnanager.IDLE) return; 85 // // // if 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, (_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()); // Clear the average display. _query.setDisplay("average", """) ; // The superclass sets the stop time of the director based on // the value in the entry box on the screen. Then it starts // execution of the model in its own thread, leaving the user // interface of this applet live, super._go() ; /////////////////////////////////////////////////////////////////// //// private variables //// // Actors in the model, private Query _query; private Poisson jpoissonBus; private Clock _regularBus; private Poisson __passengerl ; private TimedPlotter _eventplot; private HistogramPlotter _histplot; private WaitingTime _wait; // An indicator of whether regular or Poisson bus arrivals are // desired. private boolean _regular = false,// The relation to which links are made and unmade in response to // changes in the radio button state that selects regular or Poisson // bus arrivals, private ComponentRelation _busRelation; // Flag to prevent spurious exception being thrown by _go() method. // If this flag is not true, the _go() method will not execute the model. private boolean _initCompleted = false; // The observer of the average. 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 polymorphism, where a component adapts in a controlled way to the protocols that are used to exchange data between components. For example, an addition operation can accept input data delivered by any of a number of mechanisms, including discrete-events, rendezvous, or asynchronous message passing. Ptolemy includes libraries of polymorphic actors using both kinds of polymorphism to maximize re-usability. Actors from these libraries can be used in a broad range of domains, where the domain provides the communication protocol between actors. In addition, most of these actors are data polymorphic, meaning that they can operate on a broad range of data types. In general, writing data and domain polymorphic actors is considerably more difficult than writing more specialized actors. This chapter discusses some of the issues. 5.2 Library Organization Two key libraries of domain-polymorphic actors are provided by Ptolemy II. The actors with graphical user interface (GUI) functions are collected in the ptolemy.actor.gui package, and the rest are 89 is the domain in ptolemy.actor.lib. Domain-specific actors are in ptolemy.domainsjc.Iib, where 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.converiTyped Atomic Actor; actor.lib -oi_ +place(p: Panel) | [ 7\ «Interface» TlmedActor Z' A" Sink i+input: typedlÖPort(multiport); Plotter HistogramPlotter +ftllOnWrapup: Parametert,BooleanToken) +plot: Plot +startingDataset: IntToken +fillOnWrapup: Parameter(BooteanToken) ♦histogram : Histogram ♦input: TypedlOPort{ DoubleToken) Display ♦rowsDisplayed: IntToken ♦textArea: JTextArea SequencePlotter Timed PI otter ♦input: Typed I OPort(Double Token, multi) ♦xUnit: DoubleToken ♦xlnit: DoubleToken Matrix Viewer ♦input: TypedlOPort(MatrixToken) ♦table: JTable ♦input: TypedlOPort( DoubleToken, multi) XYPIotter ■inputX : TypedIOPort(DoubleToken, multi) •inputY : Typed I OPort( DoubleToken), multi FIGURE 5.2. Organization of actors in the ptolemy.actor.gui package. 92 5.4 Domain Polymorphism Most actors access their ports as shown in figure 5.4, using the hasToken(), get(), and send() methods. Those methods are polymorphic, in that their exact behavior depends on the domain. For example, get() in the CSP domain causes a rendezvous to occur, which means that the calling thread is suspended until another thread sends data to the same port (using, for example, the 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 +moduloReverseseed: Parameter{LongToken) +firingCountlimit: Parameter(lntToken) TimedSource >stopTime: Parameter(DoubleToken) +value: Parameter L_ ♦mean: ParameterfDoubleToken) standardDeviation: ParameterfDoubleToken) ♦output: TypedlOPort(DoubteToken) +trueProbability: Parameter DoubleToken) Clock Ramp ♦offsets: Parameter(DoubleMatrixToken) ♦period: Parameter(DoubleToken) ♦values: Parameter(MatrixToken) ♦init: Parameter ♦step: Parameter VariableClock ♦periodControt: TypedlOPort( DoubleToken) FIGURE 5.6. Source actors in the ptolemy.actor.lib package. 100 Pulse ♦indexes: Parameter^ ntMatrixToken) ♦interpolation : Parameter^ntToken) ♦values: Parameter(ArrayToken) ♦repeat: Parameter(BooleanToken) 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 (ArrayToken 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 exponential 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. 5.5.3 Polymorphic Sinks and Displays The following actors are in the ptolemy.actor.lib package (see figure 5.7) if they have no graphical component, and in ptolemy.actor.gui (see figure 5.8) otherwise. Several of the plotters have afillOnWrapup parameter, which has a boolean value. If the value is true (the default), then at the conclusion of the execution of the model, the axes of the plot will be adjusted to just fit the observed data. BarGraph Ports: input (multiport, Array Token of Doubles). Plot bar graphs, given arrays of doubles as inputs. Sink ;+input: TypedAtomicActor(multi); Recorder Writer -record: LinkedList timeRecord: LinkedList +getCount(): int +getHistory(channel: int): List (of StringToken) +gelLatest(channel: int): StringToken +getTimeHistory(): List (of Double) _writer: java.io.Writer +setWriter(writer: java.io.Writer) FileWriter +filename: Parameter(String) FIGURE 5.7. Sink actors in the ptolemy.actor.lib package. 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» SeauenctAcfw jTypedAtomlcActor: ..,> = ,<, <=, and ==. Equals Ports: input (multiport, polymorphic), output (BooleanToken). Consume one token from each channel of input, and produce an output token with value true if these tokens are equal, or false otherwise. LogicalNot Ports: input (BooleanToken), output (BooleanToken). Produce an output token which is the logical negation of the input token. LogicFunction Ports: input (multiport, BooleanToken), output (BooleanToken). Parameters -.function (StringToken). Produce an output token with a value that is a logical function of the tokens on the channels of input. The function is specified by the function attribute, where valid functions are and, or, xor, nand, nor, and xnor. 5.5.7 Actors in actor.lib.conversions package. The actor.lib package has been extended with actors that perform conversions from one representation into another representation, such as from Cartesian coordinates into Polar coordinates. These actors are put in package actor.lib.conversions (See Figure 5.10). FIXME: Update these conversions. CartesianToComplex Ports: real (DoubleToken), imag (DoubleToken), output (ComplexToken). Convert two tokens representing the real and imaginary of a complex number into their complex representation. CartesianToPolar Ports: x (DoubleToken), y (DoubleToken), angle (DoubleToken), magnitude (DoubleToken). Convert a Cartesian pair (a token on x and a token on y) to two tokens representing its polar form (which are output on angle and magnitude). 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 precision, 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 quantizer and overflow strategy. TypedAtomlcActor Transformer DoubleToFix PolarToRectangular RealToComplex ♦precision: Parameter(StringToken) ♦quantizer: Parameter(lntToken) input: TypedlOPort(DoubleToken) ♦output: TypedlOPort(FixToken) unagnitudelnput: TypedlOPort(Doubl8Token) i-anglelnput: TypedlOPott(DoubleToken) i-xOutput: TypedlOPort(DoubleToken) ►yOutput: TypedlOPort(DoubleToken) reallnput: TypedlOPort(DoubleToken) ♦imaglnput: TypedlOPort(DoubleToken) output: TypedlOPort(ComplexToken) FixToDouble RectangularToPolar ♦precision: Parameter(StringToken) ♦quantizer: Parameter(lntToken) ♦input: TypedlOPort(FixToken) output: TypedlOPort(DoubleToken) ♦xlnput: TypedlOPort(DoubleToken) ■ylnput: TypedlOPort(DoubleToken) ♦magnitudeOutput: TypedlOPort(DoubleToken) ♦angleOutput: TypedlOPort(DoubleToken) ComplexToReal FIGURE 5.9. Logical actors in the ptolemy.actor.lib.logic actors 107 input: TypedlOPort(ComplexToken) ♦realOutput: TypedlOPort(DoubleToken) ♦imagOutput: TypedlOPort(DoubleToken) 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 DoubleToFix PolarToRectangular RealToComplex ♦precision : Parameter(StringToken) quantizer: Parameler(lnlToken) ♦input: TypedlOPorl(DoubleToken) output: TypedlOPort(FixToken) +magnitudelnput: TypedlOPort(DoubleToken) +anglelnput: TypedlOPort(DoubleToken) ♦xOutput: TypedlOPort(DoubleToken) ■yOutput: TypedlOPort(DoubleToken) ♦reallnput: TypedlOPort(DoubleToken) ♦imaglnput: TypedtOPort(DoubleToken) ♦output: TypedlOPort(ComplexToken) FIxToDouble RectangularToPolar ♦precision: Parameter(StringToken) ♦quantizer: Parameter(lntToken) input: TypedlOPort(FixToken) ♦output: TypedlOPort(DoubleToken) ♦xlnput: TypedlOPort(DoubleToken) ♦ylnput: TypedlOPort(DoubleToken) ♦magnitudeOutput: TypedlOPort(DoubleToken) ♦angteOutput: TypedlOPort(DoubleToken) FIGURE 5.10. The actors in the ptolemy.actor.lib.conversion package. 108 ComplexToReal ♦input: TypedlOPort(ComplexToken) ♦realOutput: TypedlOPort(DoubleToken) ♦imagOutput: TypedlOPort(DoubleToken) 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, meaning 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 chapter, 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 naming 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 output 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 hierarchy to promote consistency. 6.2 Anatomy of an Actor The basic structure of an actor is shown in figure 6.1. In that figure, keywords in bold are features of Ptolemy II that are briefly described here and described in more detail in the chapters of part 2. Italic text would be substituted with something else in an actual actor definition. We will go over this structure in detail in this chapter. The source code for existing Ptolemy II actors, located mostly in $PTII/ptolemy/actor/lib, should also be viewed as a key resource. 6.2.1 Ports By convention, ports are public members of actors. They represent a set of input and output channels 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 public TypedlOPort portName; 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 portName = new TypedlOPort(this, "portName", true, false); 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 argument 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 multiport, 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 { /** Javadoc comment for constructor. */ public ClassName(CompositeEntity container, String name) throws NameDuplicationException, IllegalAotionException super (container, name) ; // Create and configure ports, e.g. ... portWame = new TypedIOPort(this, "portName", true, false); // Create and configure parameters, e.g. . . . parameterwame = new Parameter(this, "parameterwame"); parameterwame.setTypeEquals(BaseType.DOUBLE); { } /////////////////////////////////////////////////////////////////// //// ports and parameters //// /** Javadoc comment for port. */ public TypedlOPort portWame; /** Javadoc comment for parameter. */ public Parameter parameterwame; /////////////////////////////////////////////////////////////////// //// public methods //// /** Javadoc comment for fire method. */ public void fired { super, fire ().; ... read inputs and produce outputs ... } /** Javadoc comment for initialize method. */ public void initialize() { super.initialize(); ... initialize local variables ... ) /** Javadoc comment for prefire method. */ public boolean prefire() { ... determine whether firing should proceed and return false if not ... return super.prefire(); } /** Javadoc comment for postfire method. * I public boolean postfire0 { ... update persistent state ... ... determine whether firing should continue to next iteration and return false if not return super.postfire0; } /** Javadoc comment for wrapup method. */ public void wrapup() { super.wrapup(); ... display final results ... FIGURE 6.1. Anatomy of an actor. 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); A token can be read from a channel with the syntax Token token = portName.get (channelNumber) ; 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 boolean tokenAvailable = portName.hasToken{channelNumber); You can also query an output port to see whether a send() will succeed using boolean spaceAvailable = portName.hasRoom(channelNumber); although with most current domains, the answer is always true. Note that the get(), hasRoom() and hasToken() 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 without specifying what that type should be: 112 portName.setTypeSameAs (.parameterName) ; These constraints could be specified in the other order, parameterName. setTypeSameAs (portName) ; which obviously means the same thing, or parameterName. setTypeAtLeast [portName) ; which is not quite the same. Another common type constraint is an absolute type constraint, which fixes the type of the port (i.e. making it monomorphic rather than polymorphic), 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 constraints, you should specify all of them. For full details of the type system, see the Type System chapter. 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 output. The code shows two ports, one that is an input and one that is an output. By convention, the Javadoc1 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 constructor, the output type is constrained to be at least as general as both the input and the factor parameter. 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 public Parameter factor; and created in the line factor = new Parameter(this, "factor", new IntToken(1)); 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); public class Transformer extends TypedAtomicActor { /** * * * * * * Construct an actor with the given container and name. Sparam container The container. ©pararn name The name of this actor. ©exception IllegalActionException If the actor cannot be contained by the proposed container. ©exception NameDuplicationException If the container already has an actor with this name. */ public Transformer(CompositeEntity container, String name) throws NameDuplicationException, IllegalActionException super(container, name); input = new TypedlOPort(this, "input", true, false); output = new TypedlOPort(this, "output", false, true); { } lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll 1111 ports and parameters //// /** 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 specified 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 meanTime, which specifies the mean time between events. This must be a double, as asserted in the constructor. 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 invalidateSchedule() 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 class Scale extends Transformer { public Scale(CompositeEntity container, String name) throws NameDuplicationException, IllegalActionException super(container, name); factor = new Parameter (this, "factor",' new IntToken(l)); {. // set the type constraints, output.setTypeAtLeast(input); output.setTypeAtLeast(factor); } /////////////////////////////////////////////////////////////////// //// ports and parameters //// /** The factor. * This parameter can contain any token that supports multiplication. * The default value of this parameter is the IntToken 1. */ public Parameter factor; /////////////////////////////////////////////////////////////////// //// public methods //// /** Compute the product of the input and the factor. * If there is no input, then produce no output. * «exception IllegalActionException If there is no director. */ public void fire() throws IllegalActionException { if (input.hasToken(0)) { Token in = input.get(0); Token factorToken = factor.getToken(); Token result = factorToken.multiply(in); output.send(0, result); } } 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, meanTime = new Parameter(this, "meanTime", new DoubleToken(1.0)); 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 public class Poisson extends TimedSource { public Parameter meanTime; public Parameter values; public Poisson(CompositeEntity container, String name) throws NameDuplicationException, IllegalActionException ( super(container, name); meanTime = new Parameter (this, "meanTime", new DoubleToken(1.0)); meanTime.setTypeEquals(BaseType.DOUBLE); } /** 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 Scale newObject = (Scale)super.clone(workspace); 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 particular, 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"); 6.3 Action Methods Figure 6.1 shows a set of public methods called the action methods because they specify the action performed by the actor. By convention, these are given in alphabetical order in Ptolemy II Java files, but we will discuss them here in the order that they are invoked. The first to be invoked is the preinitialize() method, which is invoked exactly once before any other action method is invoked. The preinitialize() method is often use to set type constraints. After the preinitializeQ method is called, type public class Scale extends Transformer { public Scale(CompositeEntity container, String name) throws NameDuplicationException, IllegalActionException super(container, name); output .setTypeAtLeast (input) ; output.setTypeAtLeast(factor) ; { 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 resolution. 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-polymorphic 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 execution1. 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 production can only happen during initialize(), because data transport through ports depends on type resolution. 6.3.2 Prefire The prefire() method is the only method that is invoked exactly once per iteration2. It returns a public class Average extends Transformer { public void initialize)) throws IllegalActionException { super.initialize () ; _count = 0; _sum = null; } 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 simply 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: public boolean prefire0 throws IllegalActionException { if(_state) { if(truelnput.hasToken(O)) return true; } else { if(falselnput.hasToken(0)) return true; } return false; } It is good practice to check the superclass in case it has some reason to decline to be fired. The above example becomes: public boolean prefire() throws IllegalActionException { if(_state) { if(truelnput.hasToken(0)) return super.prefire(); } else { if(falselnput.hasToken(0)) return super.prefire(); } return false; } 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: public boolean prefire() throws IllegalActionException { if (_random.nextDouble() < ((DoubleToken)(trueProbability.getToken())).doubleValue0) _current = true; } else { _current = false; { } return super.prefire0; } 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 (searching 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 setTypeEquals() 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 figure 6.10. • A domain-polymorphic actor cannot assume that there is data at all the input ports. Most domainpolymorphic 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 public class Bernoulli extends RandomSource { public Bernoulli(CompositeEntity container, String name) throws NameDuplicationException, IllegalActionException super(container, name); { output.setTypeEquals(BaseType.BOOLEAN); trueProbability = new Parameter(this, "trueProbability", new DoubleToken(O.S)); trueProbability.setTypeEquals(BaseType.DOUBLE); } . public Parameter trueProbability; public void fireO { try { super.fire(); output.send(0, new BooleanToken(_current)); } catch (IllegalActionException ex) { // Should not be thrown because this is an output port, throw new InternalErrorException(ex.getMessage()); } } public boolean prefire() throws IllegalActionException { if (_random.nextDouble() < ((DoubleToken) (trueProbability.getTokenO )) .doubleValue ()) { _current = true; } else { _current = false; } return super.prefire 0; } . private boolean _current; } FIGURE 6.8. Code for the Bernoulli actor, which is not data polymorphic. 121 public class Average extends Transformer { ... constructor ... /////////////////////////////////////////////////////////////////// //// ports and parameters //// public TypedlOPort reset; /////////////////////////////////////////////////////////////////// //// public methods //// ... clone method ... public void fireO throws IllegalActionException { _latestSum = _sum; _latestCount = _count + 1; // Check whether to reset, for (int i = 0; i < reset.getWidthO ; i++) { if (reset.hasToken(i)) { BooleanToken r = (BooleanToken)reset.get(0); if(r.booleanValuel)) { // Being reset at this firing. _latestSum = null; _latestCount = 1; } } } if (input.hasToken(O)) { Token in = input.get(0); if (_latestSum == null) { _latestSum = in; } else { _latestSum = _latestSum.add(in) ; } Token out = _latestSum.divide(new IntToken(_latestCount)); output.send(0, out); } } public void initialize!) throws IllegalActionException { super.initialized ; _count = 0; _sum = null; } public boolean postfireO throws IllegalActionException { _sum = _latestSum; _count = _latestCount; return super.postfire 0; } /////////////////////////////////////////////////////////////////// //// private private private private private members //// Token _sum; Token _latestSum; int _count = 0; int _latestCount; 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 flringCountLimit, 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 interface. 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 double currentTime = getDirector().getCurrentTime(); 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 conventions. 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. public class SequenceSource extends Source implements SequenceActor ( public SequenceSource(CompositeEntity container, String name) throws NameDuplicationException, IllegalActionException ( super(container, name); firingCountLimit = new Parameter(this, "firingCountLimit", new IntToken(0)); } public Parameter firingCountLimit; public boolean postfireO throws IllegalActionException { _iterationCount++; if (_iterationCount == ((IntToken) firingCountLimit.getTokenO ) .intValue ()) return false; } return true; } private int _iterationCount = 0; public class TimedSource extends Source implements TimedActor { public TimedSource(CompositeEntity container, String name) throws NameDuplicationException, IllegalActionException ( super(container, name); stopTime = new Parameter(this, "stopTime", new DoubleToken(O.O)); public Parameter stopTime; public boolean postfireO throws IllegalActionException { double time = ((DoubleToken)stopTime.getTokenO).doubleValue0; if (time > 0.0 && getDirector0.getCurrentTime() >= time) { return false; } return true; } 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 indent 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 namesWithEmbeddedCapitalization 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. Continuation 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 semicolons: 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 description 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 separated using internal capitalization . Class names, and only class names have their first letter capitalized, 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 exception. A run-time exception is one that implements the RuntimeException interface. Run-time exceptions 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 exception 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. Private 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 input port ... */ By convention, method names are set in the default font, but followed by empty parentheses, as in /** The fireO method is called when ... */ 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 documentation needs to distinguish which version is being used. It is common in the Java community to use the following style for documenting methods: /** Sets the expression of this variable. * Oparam expression The expression for this variable. */ public void setExpression(String expression) { } We use instead the imperative tense, as in /** Set the expression of this variable. * ©param expression The expression for this variable. */ public void setExpression(String expression) { } 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); A naive attempt to rewrite this in imperative tense might result in: /** * 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: * ©exception MyException If such and such occurs. 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 exception, use the following: * ©exception MyException Not thrown in this base class. Derived * classes may throw it if such and such happens. 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 format the comments until there are no Javadoc warnings. 6.5.7 Code Organization The basic file structure that we use follows the outline in figure 6.1, preceded by a one-line description of the file and a copyright notice. The key points to note about this organization are: • The file is divided into sections with highly visible delimiters. The sections contain constructors, ports and parameters (and other public members, if there are any), public methods, protected methods, protected members, private methods, and private members, in that order. Note in particular that although it is customary in the Java community to list private members at the beginning of a class definition, we put them at the end. They are not part of the public interface, and thus should not be the first thing you see. 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 prefix. 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 7.1 Abstract Syntax The kernel defines a small set of Java classes that implement a data structure supporting a general form of uninterpreted clustered graphs, plus methods for accessing and manipulating such graphs. These graphs provide an abstract syntax for netlists, state transition diagrams, block diagrams, etc. An abstract syntax is a conceptual data organization. It can be contrasted with a concrete syntax, which is a syntax for a persistent, readable representation of the data, such as EDIF for netlists. A particular graph configuration is called a topology. Although this idea of an uninterpreted abstract syntax is present in the original Ptolemy kernel [13], in fact the original Ptolemy kernel has more semantics than we would like. It is heavily biased towards dataflow, the model of computation used most heavily. Much of the effort involved in implementing models of computation that are very different from dataflow stems from having to work around certain assumptions in the kernel that, in retrospect, proved to be particular to dataflow. A topology is a collection of entities and relations. We use the graphical notation shown in figure 7.1, where entities are depicted as rounded boxes and relations as diamonds. Entities have ports, 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 association 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 mathematical 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 multiway associations whereas an arc in a graph is a two-way association. A third difference is that mathematical 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 Non-Hierarchical Topologies The classes shown in figure 7.2 support non-hierarchical topologies, like that shown in figure 7.1. Figure 7.2 is a UML static structure diagram (see appendix A of chapter 1). 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 delation j if\*ort Connection Port Entity FIGURE 7.1. Visual notation and terminology. 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 consistency. 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 container. 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 ... •o- CrossRefList 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) 0..1 I +isünked(r: Relation): boolean -_portList: NamedList +isOpaque(): boolean EntityO -MinkedRelationList(): List Entityiname: String) linkedRelationsO: Enumeration +Entity(w : Workspace, name : String) link(r: Relation) +connectedPortList(): List +numLinks(): int +connectionsChanged(p: Port) +setContainer(c: Entity) +getPort(name: String): Port +unlink{index: int) +!inkedRelationList(): List +unlink(r: Relation) +newPort(name: String): Port unlinkAIIO +portüst(): List #_checkLink(relation: Relation) — A »removeAIIPortsQ ports in list O.n I 0..n 1..1 Relation -_portList: CrossRefList +Relation() +Relation(name: String) ♦Relationjw : Workspace, name : String) tlinkedPortListO: List +linkedPortList(except: Port): List linkedPortsO: Enumeration +numLinks(): int +unlinkAII() 1..1 ..A port list FIGURE 7.2. Key classes in the kernel package and their methods supporting basic (non-hierarchical) topologies. 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 «utility» CrossRefList «Interface» Nameable NamedObj #_changeListeners : List #_debugging ; boolean #_uniqueNamelndex: int #_workspace: Workspace attributes : NamedList debugListeners: LinkedList name: String +NamedObj () NamedObj(name: String) NamedObj(w : Workspace, name : String) +addChangeListener(listener: ChangeListener) +addDebugl_istener(l: DebugListener) +attributeChanged(a: Attribute) +attributeüst(): List +attributeList(filter: Class): List +attributeTypeChanged(a: Attribute) +clone(): Object +clone(destination : Workspace): Object +deepContains(inside : NamedObj): boolean +deferredMoMLDefinitionFrom(): List +exportMoML(); String +exportMoML(name: String) +exportMoML(output: Writer) +exportMoML(output: Writer, depth : int) +exportMoML(output: Writer, depth : int, name : String) +getAttribute(name : String): Attribute +getDeferMoMLDefinitionTo(): NamedObj getMoMLEIementNameO: String getName(parent: NamedObj): String +removeChangeListener(tistener: ChangeListener) removeDebugListenerff: DebugListener) +requestChange(change: ChangeRequest) +setMoMLEIementName(name: String) +toplevel(): NamedObj +uniqueName( prefix : String): String +workspace(): Workspace -JistVersion: long - size: int +CrossRef List (container: Object) +CrossRefList(container: Object, original: CrossRefList) +first(): Object +getContainers(): Enumeration +insertLink(index : int, farList: CrossRefList) +isLinked(o: Object): boolean +link(fart_ist: CrossRefList) +size(): int +unlink(index: int) +unlink(o: Object) +unlinkAII() ___^ +getContainer(): Nameable getFullNameQ : String +getName(): String setNamefname: String) Workspace -_directory: Linkedüst -_name: String -_readers: Hashtable -_readOnly: boolean - writer: Thread +Workspace() +Workspace(name: String) +add(item: NamedObj) +directory(): Enumeration +doneReading{) ■doneWritingO getReadAccessQ +getWriteAccess() +getVersion{): long +incrVersion() +isReadOnly{): boolean +remove(item : NamedObj) +removeAII() +setReadOnly(b: boolean) +wait(obj: Object) calls getReadDepth() PtoJemyThread #readDepth: int PtotemyThread() PtolemyThread(target: +PtolemyThread(target: PtoJemyThread(name: +PtolemyThread(group : +PtolemyThread(group : +PtolemyThread(group : ■getReadDepthO : int «Interface» DebugLittBner Runnable) Runnable, name : String) String) ThreadGroup, target: Runnable) ThreadGroup, target: Runnable, name : String) ThreadGroup, name : String) ___^_ String Utilities +escapeFqrXML(strinQ : String): String +substitute(string : $trino, oldsub ; String, newsub : Spring): String - "Ä 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 +linkedRelations(): Enumeration +link{r: Relation) +numLinks(): int setContainerfc: Entity) +unltnk(index: int) +unlink(r: Relation) +unlinkAII() #_checkContainertcontainer: Entity) #_checkLink(relation: Relation) EntityO +Entity(name: String) +Entity(w : Workspace, name : String) +connectedPortUst(): List +connectionsChanged(p: Port) +getPort(name : String): Port linkedRelationList(): List +newPort(name : String): Port portList(): List +removeAl I Ports () c\ ports in list | On 1..1 l\ X port list ComponentPort Named List ComponantEntrty -„container: CompositeEntity Component Entity () +Component Entity(w : Workspace) +ComponentEntity(container: CompositeEntity, name : String) +isAtomic(): boolean +isOpaque(): boolean +setContainer(container: CompositeEntity) #_checkContainer(container: CompositeEntity) 0..n Relation() +Relation(name: String) Relation(w : Workspace, name : String) linkedPortListO : List +tinkedPortList(except: Port): List +linkedPorts(): Enumeration +numLinks(): int unlinkAIIO containee r jo.. - insideünks: CrossRefList +ComponentPort() +ComponentPort(w : Workspace) +ComponentPort(container: ComponentEntity, name : String) +deepConnectedPortList(): List +deeplnsidePortList(): List +insidePortList(): List +insideRelationList(): List +isDeeplyConnected(p : ComponentPort): boolean +islnsideLinked{r: Relation): boolean +isOpaque(): boolean +liberalLink(r: Relation) +numlnsideLinks(): int +unlinkAII Inside () +unlinklnside(index: int) +unlink!nside(r: Relation) #_checkLiberalLink(retation: Relation) CompositeEntity _containedEntities : NamedList containedRelations: NamedList +CompositeEntity() +CompositeEntity(w: Workspace) +CompositeEnttty(container: CompositeEntity, name : String) ♦allow LevelCrossingConnect(b: boolean) +connect(p1 : Component Port, p2 : ComponentPort): ComponentRelation +connect{p1 : ComponentPort, p2 : ComponentPort, name: String): ComponentRelation +deepEntityList(): List +entityList(): List +entityList(filter: Class): List +exportLinks(indentation : int, filter: Collection): String +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) FIGURE 7.4. Key classes supporting clustered graphs. 140 ComponentRelation -_container: CompositeEntity ComponentRelation() ComponentRelation(w: Workspace) +ComponentRelation(container: CompositeEntity, name : String) +deeplinkedPortList(): List +setContainer(container: CompositeEntity) #_checkContainer(container: CompositeEntity) 0.J1 container 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 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. A port of a composite entity may be opaque or transparent. It is defined to be opaque if its container 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 indistinguishable 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). 1. Unless level-crossing links are allowed, which is discouraged. 2. In that figure, every object has been given a unique name. This is not necessary since names only need to be unique within a container. In this case, we could refer to P5 by its full name .E0.E4.P5 (the leading period indicates that this name is absolute). However, using unique names makes our explanations more readable. 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 container of the port, then the link is an inside link. 7.4.2 Level-Crossing Connections For a few applications, such as Statecharts [31], level-crossing links and connections are needed. The example shown in figure 7.6 has three level-crossing connections that are slightly different from one another. The links in these connections are created using the liberalLink() method of ComponentPort. The link() method prohibits such links, throwing an exception if they are attempted (most applications will prohibit level-crossing connections by using only the link() method). An alternative that may be more convenient for a user interface is to use the connect() methods of CompositeEntity rather than the link() or liberalLink() method of ComponentPort. To allow levelcrossing links using connect(), first call allowLevelCrossingConnect() with a true argument. The simplest level-crossing connection in figure 7.6 is at the bottom, connecting P2 to P7 via the relation R5. The relation is contained by El, but the connection would be essentially identical if it were contained by any other entity. Thus, the notion of composite entities containing relations is somewhat FIGURE 7.6. An example with level-crossing transitions. 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.3 Tunneling Entities The transparent port mechanism we have described supports connections like that between PI and P5 in figure 7.7. That connection passes through the entity E2. The relation R2 is linked to the inside of each of P2 and P4, in addition to its link to the outside of P3. Thus, the ports deeply connected to PI are P3 and P5, and those deeply connected to P3 are PI and P5, and those deeply connected to P5 are PI and P3. A tunneling entity is one that contains a relation with links to the inside of more than one port. It may of course also contain more standard links, but the term "tunneling" suggests that at least some deep graph traversals will see right through it. Support for tunneling entities is a major increment in capability over the previous Ptolemy kernel [13] (Ptolemy Classic). That infrastructure required an entity (which was called a star) to intervene in any connection through a composite entity (which was called a galaxy). Two significant limitations resulted. The first was that compositionality was compromised. A connection could not be subsumed into a composite entity without fundamentally changing the structure of the application (by introducing a new intervening entity). The second was that implementation of higher-order functions that mutated the graph [49] was made much more complicated. These higher-order functions had to be careful to avoid mutations that created tunneling. 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. 7.4.5 An Elaborate Example An elaborate example of a clustered graph is shown in figure 7.8. This example includes instances of all the capabilities we have discussed. The top-level entity is named "EO." All other entities in this example have containers. A Java class that implements this example is shown in figure 7.9. A script in the Tel language [67] that constructs the same graph is shown in figure 7.10. This script uses Tel Blend, an interface between Tel and Java that is distributed by Scriptics. The order in which links are constructed matters, in the sense that methods that return lists of objects preserve this order. The order implemented in both figures 7.9 and 7.10 is top-to-bottom and left-to-right in figure 7.8. A graphical syntax, however, does not generally have a particularly convenient way to completely control this order. FIGURE 7.8. An example of a clustered graph. 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; public ExampleSystem() throws IllegalActionException, NameDuplicationException { eO = new CompositeEntity(); eO.setNamefEO"); e3 = new CompositeEntity(eO, "E3"); e4 = new CompositeEntity(e3, "E4"); e7 = new CompositeEntity(eO, "E7"); elO = new CompositeEntity(eO, "ElO"); 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"); rl = e4.connect(pl, pO, "Rl"); r2 = e4.connect(pl, p4, "R2"); p3.1ink(r2); r3 = e4.connect(pl, p2, "R3"); r4 = e3.connect(p4, p7, "R4"); r5 = e3.connect(p4, p5, "R5"); e3.allowLevelCrossingConnect(true); r6 = e3.connect(p3, p6, "R6"); r7 = e0.connect(p7, pl3, "R7"); r8 = e7.connect(p9, p8, "R8"); r9 = e7.connect(pl0,pll, "R9"); rlO = e0.connect(p8, pl2, "RIO"); rll =el0.connect(pl2,pl3, "Rll"); rl2 = el0.connect(pl4, pl3, "R12"); pll.link(r7); 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. 7.5 Opaque Composite Entities One of the major tenets of the Ptolemy project is that of modeling heterogeneous systems through the use of hierarchical heterogeneity. Information-hiding is a central part of this. In particular, transparent ports and entities compromise information hiding by exposing the internal topology of an entity. In some circumstances, this is inappropriate, for example when the entity internally operates under a different model of computation from its environment. The entity should be opaque in this case. # Create composite entities set eO [java::new pt.kernel.CompositeEntity EO] set e3 [java::new pt.kernel.CompositeEntity $e0 E3] set e4 [java::new pt.kernel.CompositeEntity $e3 E4] set e7 [java::new pt.kernel.CompositeEntity $e0 E7] set elO [java::new pt.kernel.CompositeEntity $e0 E10] # Create component entities. set el [java::new pt.kernel.ComponentEntity $e4 El] set e2 [java::new pt.kernel.ComponentEntity $e4 E2] set e5 [java::new pt.kernel.ComponentEntity $e3 E5] set e6 [java::newpt.kernel.ComponentEntity $e3 E6] set e8 [java::new pt.kemel ComponentEntity $e7 E8] set e9 [java::new pt.kernel.ComponentEntity $elO E9] # 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] L # 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 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 computations 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 concurrently 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 simultaneous 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 example, 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 Table 21: Methods of ComponentRelation Method Name Rl R2 R3 R4 R5 R6 R7 R8 R9 RIO Rll R12 getLinkedPorts PI PO PI P4 P3 PI P2 P4 P7 P4 P5 P3 P6 P7 P13 Pll P9 P8 P10 Pll P8 P12 P12 P13 P14 P13 deepGetLinkedPorts PI PI P9 P14 P10 P5 P3 PI P2 PI P3 P9 P14 P10 PI P3 P5 P3 P6 PI P3 P9 P14 P10 P9 PI P3 P10 P10 PI P3 P9 P14 P9 PI P3 P10 P9 PI P3 P10 P14 PI P3 P10 Table 22: Methods of ComponentPort Method Name PO PI P2 P3 P4 P5 P6 P7 P8 P9 P10 Pll P12 P13 P14 getConnectedPorts PO P4 P3 P2 PI PI P4 P6 P7 P5 P4 P3 P13 Pll P12 P8 Pll P7 P13 P8 P7 Pll P13 deepGetConnectedPorts P9 P14 P10 P5 P3 P2 PI PI P9 P14 P10 P5 P6 P9 P14 P10 P5 PI P3 P3 P9 P14 P10 PI P3 P10 PI P3 P10 PI P3 P9 P14 PI P3 P9 P14 P9 PI P3 P10 PI P3" P10 FIGURE 7.11. Key methods applied to figure 7.8. 147 occurring. Such enforced consistency is called thread safety. 7.6.1 Limitations of Monitors Java threads provide a low-level mechanism called a monitor for controlling concurrent access to data structures. A monitor locks an object preventing other threads from accessing the object (a design pattern called mutual exclusion). Unfortunately, the mechanism is fairly tricky to use correctly. It is non-trivial to avoid deadlock and race conditions. One of the major objectives of Ptolemy II is provide higher-level concurrency models that can be used with confidence by non experts. Monitors are invoked in Java via the "synchronized" keyword. This keyword annotates a body of code or a method, as shown in figure 7.12. It indicates that an exclusive lock should be obtained on a specific object before executing the body of code. If the keyword annotates a method, as in figure 7.12(a), then the method's object is locked (an instance of class A in the figure). The keyword can also be associated with an arbitrary body of code and can acquire a lock on an arbitrary object. In figure 7.12(b), the code body represented by ellipses (...) can be executed only after a lock has been acquired on object obj. Modifications to a topology that run the risk of corrupting the consistency of the topology involve more than one object. Java does not directly provide any mechanism for simultaneously acquiring a lock on multiple objects. Acquiring the locks sequentially is not good enough because it introduces deadlock potential. I.e., one thread could acquire the lock on the first object block trying to acquire a lock on the second, while a second thread acquires a lock on the second object and blocks trying to acquire a lock on the first. Both methods block permanently, and the application is deadlocked. Neither thread can proceed. public class A { public synchronized void foo() | try{ workspace().getReadAccess(); //... code that reads } finally { workspace().doneReading(); (a) public class B { public void foo() { synchronized(obj) { (d) try{ workspace().getWriteAccess(); //... code that writes } finally { workspace().doneWriting(); (b) 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 example, we could use the containment hierarchy and always acquire locks top-down in the hierarchy. Suppose 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 containment 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 deadlock conditions is notoriously difficult, so we implement a more conservative, and much simpler strategy- 7.6.2 Read and Write Access Permissions for Workspace One way to guarantee thread safety without introducing the risk of deadlock is to give every object an immutable association with another object, which we call its workspace. Immutable means that the association is set up when the object is constructed, and then cannot be modified. When a change involves multiple objects, those objects must be associated with the same workspace. We can then acquire a lock on the workspace before making any changes or reading any state, preventing other threads from making changes at the same time. Ptolemy II uses monitors only on instances of the class Workspace. As shown in figure 7.3, every instance of NamedObj (or derived classes) is associated with a single instance of Workspace. Each body of code that alters or depends on the topology must acquire a lock on its workspace. Moreover, the workspace associated with an object is immutable. It is set in the constructor and never modified. This is enforced by a very simple mechanism: a reference to the workspace is stored in a private variable of the base class NamedObj, as shown in figure 7.3, and no methods are provided to modify it. Moreover, in instances of these kernel classes, a container and its containees must share the same workspace (derived classes may be more liberal in certain circumstances). This "managed ownership" [44] is our central strategy in thread safety. As shown in figure 7.12(c), a conservative approach would be to acquire a monitor on the workspace for each body of code that reads or modified objects in the workspace. However, this approach is too conservative. Instead, Ptolemy II allows any number of readers to simultaneously access a workspace. Only one writer can access the workspace, however, and only if no readers are concurrently accessing the workspace. The code for readers and writers is shown in figure 7.12(d) and (e). In (d), a reader first calls the getReadAccess() method of the Workspace class. That method does not return until it is safe to read data anywhere in the workspace. It is safe if there is no other thread concurrently holding (or requesting) a write lock on the workspace (the thread calling getReadAccess() may safely hold both a read 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 doneWritingO 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 subclasses 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 supporting interaction across workspaces. There is one significant subtlety regarding read and write permissions on the workspace. In a multithreaded 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 relinquishes 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.6.3 Making a Workspace Read Only Acquiring read and write access permissions on the workspace is not free, and it is performed so often in a typical application that it can significantly degrade performance. In some situations, an application may simply wish to prohibit all modifications to the topology for some period of time. This can be done by calling setReadOnly() on the workspace (see Workspace methods in figure 7.3). Once the workspace is read only, requests for read permission are routinely (and very quickly) granted, and requests for write permission trigger an exception. Thus, making a workspace read only can significantly improve performance, at the expense of denying changes to the topology. 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. 7.7.1 Change Requests A manager processes a change request by calling its execute() method. That method then calls the NamedObj Object constructs ChangeRequest' enqueues with NamedObj "K originator specifies the list of listeners by < ailing setListenersf) j >+addChangeListener(listener: ChangeListener) !+removeChangeListener(lis(ener: ChangeListener) ~j+requestChange(c: ChangeRequest) 0..n delegates execution to Manager O.n ChtngoRaquast originator Manager \ I .j •-Originator: Object +ChangeRequest(originator: Object, description : String) i+requestChange(c : ChangeRequest): +execute() +getDescription(): String +getOriginator(): Object s ChangeListener of completion +setListeners(lis!eners : List) +waitForCompletion() ■ «Interface» > tt_execute() ChangeListener "TV" +changeExecuted(c: ChangeRequest) +changeFailed(c : ChangeRequest, e : Exception) moml package StreamChangeListener MoMLChangeReq uest _parser: MoMLParser staticParser: MoMLParser +StreamChangeLislener() +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 originator 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 originator will define an anonymous inner class, like this: CompositeEntity container = ...; ChangeRequest change = new ChangeRequest(originator, "description") { protected void _execute() throws Exception { ... perform change here ... } }; container.requestChange(change); 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: Entity newentity = new MyEntityClass(originator, "NewEntity"); relation.link(newentity.port); 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: CompositeEntity container = ...; String moml = "" + "" + "" + ""; ChangeRequest change = new MoMLChangeRequest(originator, container, moml); container.requestChange(change); 7.7.2 NamedObj and Listeners The NamedObj class provides addChangeListener() and removeChangeListener() methods, so that interested objects can register to be notified when topology changes occur. In addition, it provides a method that originators can use to queue requests, requestChange(). A change listener is any object that implements the ChangeListener interface, and will typically include user interfaces and visualization components. The instance of ChangeRequest is passed to the listener. Typically the listener will call getOriginator() to determine whether it is being notified of a change that it requested. This might be used for example to determine whether a requested change succeeds or fails. 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 waitForCompletion() 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. 7.8.1 Base Class KernelException. Not used directly. Provides common functionality for the kernel exceptions. In particular, it provides methods that take zero, one, or two Nameable objects plus an optional detail message (a String). The arguments provided are arranged in a default organization that is overridden in derived classes. 7.8.2 Less Severe Exceptions These exceptions generally indicate that an operation failed to complete. These can result in a topology that is not what the caller expects, since the caller's modifications to the topology did not succeed. However, they should never result in an inconsistent or contradictory topology. IllegalActionException. Thrown on an attempt to perform an action that is disallowed. For example, the action would result in an inconsistent or contradictory data structure if it were allowed to complete. E.g., attempt to set the container of an object to be another object that cannot contain it because it is of the wrong class. NameDuplicationException. Thrown on an attempt to add a named object to a collection that requires unique names, and finding that there already is an object by that name in the collection. NoSuchltemException. Thrown on access to an item that doesn't exist. E.g., attempt to remove a port by name and no such port exists. 7.8.3 More Severe Exceptions The following exceptions should never trigger. If they trigger, it indicates a serious inconsistency in the topology and/or a bug in the code. At the very least, the topology being operated on should be abandoned and reconstructed from scratch. They are runtime exceptions, so they do not need to be explicitly declared to be thrown. InvalidStateException. Some object or set of objects has a state that in theory is not permitted. E.g., a NamedObj has a null name. Or a topology has inconsistent or contradictory information in it, e.g. an entity contains a port that has a different entity as its container. Our design should make it impossible 153 for this exception to ever occur, so occurrence is a bug. This exception is derived from the Java RuntimeException. 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 „message: String +KemelException() +KemelException(o1 : Nameable, o2 : Nameable, message : String) +getMessage(); String „message: String +lnvafidStateException(msg: String) lnva!idStateException{obj: Nameable. msg: String) lnvalidStateException{o1 : Nameable, o2 : Nameable, msg : String) +lnvalidStateException! CompostteActor | +typeConstraintListQ: List A TypedAtomi cActor TypedCom positeActor +TypedAtomicActor{) +TypedAtomicActor{workspace: Workspace) +TypedAtomicActoitcontainer: TypedCompositeActor, name : String) +Ty pedCom positeActor() +TypedCom positeActor(workspace : Workspace) +TypedCompositeActor(container: TypedCom positeActor, name : String) +checkTypes(): List 0..1 3T «Interface» Typeabte lOPort T__ TypedlOPort +TYPE : int •_constraints: List ■_declaredType: Type -_resolvedType: Type -JypeTerm: TypeTerm ■_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) lORelatfon 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 communication 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 convention, 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 recipients 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. receiver. put( to) receiver.put(tl) 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. send(O.t) N receiver. put(t) E1 f get(0) n E2 .^■^ 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. 8.2.3 Transparent Ports Recall that a port is transparent if its container is transparent (isOpaque() returns false). A CompositeActor is transparent unless it has a local director. Figure 8.7 shows an elaborate example where busses, input, and output ports are combined with transparent ports. The transparent ports are filled in white, and again arrows indicate the direction of token flow. The Tel Blend code to construct this example is shown in figure 8.8. By definition, a transparent port is an input if either • it is connected on the inside to the outside of an input port, or • it is connected on the inside to the inside of an output port. That is, a transparent port is an input port if it can accept data (which it may then just pass through to a transparent output port). Correspondingly, a transparent port is an output port if either • it is connected on the inside to the outside of an output port, or • it is connected on the inside to the inside of an input port. Thus, assuming PI is an output port and P7, P8, and P9 are input ports, then P2, P3, and P4 are both input and output ports, while P5 and P6 are input ports only. Two of the relations that are inside composite entities (Rl and R5) are labeled as busses with a star (*) instead of a number. These are busses with unspecified width. The width is inferred from the topology. This is done by checking the ports that this relation is linked to from the inside and setting the 2 /2 X>P4 c 1 E4 J P1 E1 ^ )P5 f )P6 P2 { E5 )P7 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. set eO [Java::new ptolemy.actor.CompositeActor] $e0 setDirector $director $e0 setManager $manager set set set set set set el e2 e3 e4 e5 e6 tjava::new [Java::new [Java::new [java::new [Java::new [Java::new ptolemy.actor.CompositeActor $e0 El] ptolemy.actor.AtomicActor $el E2] ptolemy.actor.CompositeActor $e0 E3] ptolemy.actor.AtomicActor $e3 E4] ptolemy.actor.AtomicActor $e3 E5] ptolemy.actor.AtomicActor $e0 E6] set set set set set set set set set pi p2 p3 p4 p5 p6 p7 p8 p9 [java::new [java::new [java::new [java::new [Java::new [Java::new [java::new [java::new [Java:mew ptolemy.actor.IOPort ptolemy.actor.IOPort ptolemy.actor.IOPort ptolemy.actor.IOPort ptolemy.actor.IOPort ptolemy.actor.IOPort ptolemy.actor.IOPort ptolemy.actor.IOPort ptolemy.actor.IOPort $e2 $el $el $el $e3 $e3 $e6 $e4 $e5 PI P2] P3] P4] P5] P6] P7 P8 P9 set set set set set set set rl r2 r3 r4 r5 r6 r7 [java::new [java::new [java::new [java::new [java::new [java::new [java::new ptolemy.actor.IORelation ptolemy.actor.IORelation ptolemy.actor.IORelation ptolemy.actor.IORelation ptolemy.actor.IORelation ptolemy.actor.IORelation ptolemy.actor.IORelation $el $e0 $e0 $e0 $e3 $e3 $e3 $pl $p2 $p3 $p4 $p5 $p7 $p8 $p9 setMultiport setMultiport setMultiport setMultiport setMultiport setMultiport setMultiport setMultiport false true] true false] true false] true false] Rl] R2) R3] R4] R5] R6] R7] true true true true true true true true FIGURE 8.8. Tel Blend code to construct the example in figure 8.7. 161 $rl $r2 $r4 $r5 setWidth setwidth setWidth setWidth $pl $p2 $P3 $P4 $p2 $p5 $p2 $p5 $p6 $p3 $p7 $p5 $p8 $p5 $p9 $p6 $p9 link link link link link link link link link link link link link link link link link $rl $rl $rl $rl $r2 $r2 $r3 $r3 $r3 $r4 $r4 $r5 $r5 Sr6 $r6 $r7 $r7 0 3 2 0 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 cannot 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 transparent 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 channels 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 significantly improves the efficiency of data transport. 8.2.4 Data Transfer in Various Models of Computation The receiver used by an input port determines the communication protocol. This is closely bound to the model of computation. The IOPort class creates a new receiver when necessary by calling its _newReceiver() protected method. That method delegates to the director returned by getDirector(), calling its newReceiver() method (the Director class will be discussed in section 8.3 below). Thus, the director controls the communication protocol, in addition to its primary function of determining the flow of control. Here we discuss the receivers that are made available in the actor package. This should 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 Mailbox. 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 contains 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 possible [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 implementation 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 consumed 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 originator 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 suspends 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 willing 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 rendezvous 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 transferred between actors have a time stamp, which specifies the order in which tokens should be processed 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 FIFOQueue in each input port. The kernel.util package, shown in figure 8.10, provides the CalendarQueue actor.util «Interface» Cloneable j Comparator FIFOQueue ■„container: Nameabie •_historyCapacity: int ■_historyl_ist: LinkedList -_queueCapactty: int •_queueList: LinkedList +FIFOQueue() +FIFOQueue(container: Nameabie) +FIFOQueue(model: FIFOQueue) +dear() +elementList(): List +get(offset: int): Object +getCapacity(): int +getContainert): Nameabie +getHistoryCapadty(): int +historyElementList(): List +historySize<): int -MsFullO: boolean +put(element: Object): boolean +setCapacity(maxstze: int) +setContainertcontainer: Nameabie) +setHistoryCapacity(capaäty: int) +size(): Int +take(): Object |+compare(o1 : Object, o2 Object) int; CQComparator +getVirtualBinNumber(entry ; Object): long +setBinWidth(keyArray: Objectfl) +setZeroReference(zero: Object) +timeStamp: double ♦contents: Object +TimedEvent(time : double, contents : Object) 6 Doubl eC QCom parator TimedEvent.TimeComparatofuWic inner dass .binWidth : Object zeroReference: Object +DoubleCQComparator() ■_binWidth: Object ■_zeroReference : Object «Interface» Dabuggable CalendarQueue -„bucket: CQLinkedListfl -_nBuckets: int qSize: int +CalendarQueue(comparator: CQComparator) +CalendarQueue(comparator: CQComparator, mi nN urn Buckets: int, binCount Factor: int) +clear() opovale inner dass +get(): Object +indudes(entry: Object): boolean +isEmpty(): boolean +put(entry: Object): boolean +remove(entry: Object): boolean +setAdaptive(flag: boolean) +size(): int +take(): Object ♦toArrayO: Objectfl »to Array (limit: int): Object [] ______^_________ FIGURE 8.10. Static structure diagram for the actor.util package. 164 CQLInkedLlst +CQLinkedUst() +first{): Object +indudes(obj: Object): boolean +isEmpty(): boolean +insert(obj: Object) +remove(c: CQEntry): boolean take(): Object class, which gives an efficient and flexible implementation of such a sorted queue. 8.2.5 Discussion of the Data Transfer Mechanism This data transfer mechanism has a number of interesting features. First, note that the actual transfer of data does not involve relations, so a model of computation could be defined that did not rely on relations. For example, a global name server might be used to address recipient ports. For example, to construct highly dynamic networks, such as wireless communication systems, it may be more intuitive to model a system as a aggregation of unconnected actors with addresses. A name server would return a reference to a port given an address. This could be accomplished simply by overriding the getRemoteReceivers() method of IOPort or TypedlOPort, or by providing an alternative method for getting references to receivers. The subclass of IOPort would also have to ensure the creation of the appropriate number of receivers. The base class relies on the width of the port to determine how many receivers to create, and the width is zero if there are no relations linked. Note further that the mechanism here supports bidirectional ports. An IOPort may return true to both the islnput() and isOutput() methods. 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, AtomicActor, 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 TypedCompositeActor. The Executable interface defines how an object can be invoked. There are seven methods. The initialize() 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 usually 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 { public TypedlOPort _input; public TypedlOPort _output; public Distributor(CompositeActor container. String name) throws NameDuplicationException, IllegalActionException { super(container, name); _input = new TypedlOPort(this, "input", true, false); _output = new TypedlOPort(this, "output", false, true); _output.setMultiport(true); } public void fired throws IllegalActionException { for (int i=0; i < _output.getWidth(); i++) { _output.send(i, _input.get(0)) ; FIGURE 8.11. An actor that distributes successive input tokens to a set of output channels. 165 actor «Interface» Executable NamedObj rO~ Manager «Interface» Runnable ♦firefl ^initializer) +postfireO: boolean +pnsfireO: boolean +preinitializeQ *stopFireQ +terninateQ +wrapupQ A <]-- -KJ■wnQ container: CompositeActor #_currentTime: double +DirectorO +Director(workspace: Workspace) ♦Directorjcontainer: CompositeActor. name: String) +fireAt(actor: Actor, time: double) +getCurrentTimeO: double ♦getNextlterationTimeO: double +initialize{actor: Actor) +invalidateResolveaTypesO +invalidateSchedute() +needWriteAccess(): boolean +newReceiver(): Receiver +requestChange(change: ChangeRequest) +requestlnitiatizatjon(actof: Actor) +setCurrentTime{newTirne: double) +transfertnputs(port : lOPort): boolean +transferoirtputs(port: lOPort): boolean #_makeDtrectorOf(cast: CompositeActor) tfjMiteAccessRequiredQ: boolean .description: String ♦getDescriptionO : String +getManager(): Manager State(descriPtion: String) CORRUPTED: State +IDLE: State INITIALING : State ♦ITERATING : State ♦MUTATING; State ♦PAUSED: State PREINITIAUZING: State ♦RESOLVING TYPES: State +WRAPPING_TJP: State -_changeListeners: List -_changeRequests: List -_container: CompositeActor -_executjonUsteners: List ■_finishRequested: boolean ■_iterationCount: irrt ■_pauseReqested: boolean ■_state: State -_thread: PtolemyThread types Resolved: boolean -_writeAccess Needed; boolean ♦Managerf) +Manager(name: String) ♦Managerfworkspace : Workspace, name : String) +addChangeüstener{listener: Changeüstener) +addExecutionListener(listener: ExecutionListener) ♦executeO ♦finishO ♦getlterationCountO: irrt ♦getStateO: State ♦initializeO +invalid ateResolvedTypesO ♦iterateO: boolean notifyUstenersOfException(ex: Exception) pauseO removeChangeUstenerflistener: Changeüstener) removeExecutionUstenerfJistener: ExecutionListener) +requestChange(change: ChangeRequest) ♦requestinitiaJization(actor: Actor) ♦resolveTypesO ♦resumeO startRunO ♦wapupO #_makeManagerOfr(manager: Manager, exception : Exception) +executionFinisned(manager: Manager) +managerStateChanged(manager: Manager) ♦Stream Executi onUstenerO ♦StreamExecutionListener{out: OutputStream) 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 runaway 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 executable 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 preinitialize() 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 iteration. 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 exception 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 everything 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 wormhole. 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 generally call the corresponding method in its local director. This interaction is crucial, since it is domainindependent 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 stopping condition for the model of computation is reached. The postfireO method of a director returns false to stop its execution normally. It is the responsibility 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 EO D1: local director 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 postfire() 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 composite actor is guaranteed to initialize in all the objects contained within that actor. Similarly for wrapup(). The methods prefire() and postfire(), on the other hand, are not deeply traversing functions. Calling 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 directors 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 successful 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 contained 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 immediately returns. The run() method calls execute(), but catches all exceptions and reports them to listeners (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 iterate() 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 a 170 1a ■ a. «1 3 V a. a) 3 > tx a> 2 CL 5 o i §2 2*5 1 ■ 5 ■ §=5 i 1 c ■o ■3 / 1 = o a. o ™T5 a. u • ■E a 1 > D 4 I /o»\ / \ a> o \ / • \ CO \ a° // CO \ £CO °O O CO Q- • « V Ä 4 t 2 S n / \ t I f o CD t3 CD T3 cu "5 It- a. 8 a. \ jößeuew >> 'S u E u E u u e V 3 a- 15 §■53 n- c aa 5 .E Cfl 3 C O. CO C > to o o N «o =5 0 11 .E -a c w JOPV ajisodiuoo jopaiiQ |8A9| dO) 171 £ 1= t) oo «•-«•-•v 3 NO 1? = T3 JOPV BWsodmoo anbedo < jD Q. Q.-5 ■www-«-«-« NO ~S s E ,i I 6 O c c .o t \4 1* c o o -o o TI \ \ c o .sQ •a o •, 8° B« E v \ Q.E ■H CD j_ j a.= CO *, \"°/ •> ? E p ; \ ii w ■i ■S " «FT /I \ L. * A K? x \ >* s 4 : / o \ / «=?1 £ 2 Si n \ \ / ; ^ B- r / '•"f • i| O \ \ '■ X ;:::ii53ör=:-:F':i \ m V O .fc: Q.T3 CO J X £ o E ti / / ? \ \ \ X 2 ck : Object) +NotifyThread(k>cks: UnkedUst) +requestFini$h() +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). 9.2 Data Encapsulation The Token class and its derived classes encapsulate application data. The encapsulated data can be transported via message passing between Ptolemy II objects. Alternatively, it can be used to parameterize Ptolemy II objects. Encapsulating the data in such a way provides a standard interface so that such data can be handled uniformly regardless of its detailed structure. Such encapsulation allows for a great degree of extensibility, permitting developers to extend the library of data types that Ptolemy II can handle. It also permits a user interface to interact with application data without detailed prior knowledge of the structure of the data. Tokens in Ptolemy II, except ObjectToken, are immutable. This means that their value cannot be changed after the instance of Token is constructed. The value of a token must therefore be specified as a constructor argument, and there must be no other mechanism for setting the value. If the value must be changed, a new instance of Token must be constructed. There are several reasons for making tokens immutable. • First, when a token is to be sent to several receivers, we want to be sure that all receivers get the 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 +moduloReverseng +get£leroentAt(row: int, column: int): Complex +getElementAt(rw: int, column; int): FtxPoint +-ftxMatrixQ: FjxPoJntQD 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 modify 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 ObjectToken. 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(), doubleMatrix(), 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 provide 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 IntToken a = new IntToken(5); DoubleToken b = new DoubleToken(2.2); StringToken c = new StringToken("hello"); 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 multiplicative 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: Token inputl, input2, output; // read Tokens from the input channels into inputl and input2 variables output = input1.add(input2); // send the output Token to the output channel. 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. 9.3.2 Lossless Type Conversion For the above arithmetic operations, if the two tokens being operated on have different types, type conversion is needed. In Ptolemy II, only conversions that do not lose information are implicitly performed. Lossy conversions must be explicitly done by the user, either through casting or by other means. The lossless type conversion relation among different token types is modeled as a partially ordered set called the type lattice, shown in figure 9.2. In that diagram, type A is greater than type B if there is a path upwards from B to A. Thus, ComplexMatrix is greater than Int. Type A is less than type B if there is a path downwards from B to A. Thus, Int is less than ComplexMatrix. Otherwise, types A and B are incomparable. Complex and Long, for example, are incomparable. In the type lattice, a type can be losslessly converted to any type greater than it. This hierarchy is related to the inheritance hierarchy of the token classes in that a subclass is always less than its super class in the type lattice. However, some adjacent types in the lattice are not related by inheritance. This hierarchy is realized by the TypeLattice class in the data.type subpackage. Each node in the lattice is an instance of the Type interface. The TypeLattice class provides methods to compare two Array NaT FIGURE 9.2. The type lattice. 179 Record 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.convert(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(), fixValue(), and ComplexValue(). In MatrixToken, the methods are intMatrix(), longMatrix(), doubleMatrix(), 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 arithmetic operations are not implemented yet. 9.4 Variables and Parameters In Ptolemy II, any instance of NamedObj can have attributes, which are instances of the Attribute class. A variable is an attribute that contains a token. Its value can be specified by an expression that 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 Parameter, is that their intended uses are different. Parameters are meant to be visible to the end user of a component, 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: Variable v3 = new Variable(container,"v3") Variable v2 = new Variable(container,"v2") Variable vl = new Variable(container,"vl") v3 .setExpressionC'vl + v2"); v2 .setExpressionC'1.0") ; vl.setExpression("2.0"); v3.getToken(),- 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 evaluated), 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 allowable 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 System chapter to see what this means). 181 «Interface» StttabU «Interface» Value Ustemr !+addValueüstener, >=, == and !=. They return booleans. Logical operators. The logical boolean operators are &&, ||, !, & and |. They operate on booleans and return booleans. Note that the difference between logical && and logical & is that & evaluates all the operands regardless of whether their value is now irrelevant. Similarly for logical || and |. This approach is borrowed from Java. 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 situations 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 language. 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 language uses this same syntax. Comments. Anything inside /*...*/ is ignored. Variables. Expressions can contain references by name to parameters within the scope of the expression. Consider a parameter P with container X which is in turn contained by Y The scope of an expression 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 constants 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 constraint 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 supported. 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.Utility185 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 expression 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 example: {1, 2, 3}.length() which returns the integer 3. The MatrixToken classes have three particularly useful methods, illustrated in the following examples: [1, 2; 3, 4; 5, 6].getRowCount() function argument type(s) return type description 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, standard deviation, rows, and columns FIGURE 9.5. Functions available to the expression language from the ptolemy.data.expr.UtilityFunctions 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 abs double double absolute value abs int int absolute value abs long long absolute value acos double double arc cosine asin double double arc sine atari double double arc tangent atan2 double, double double angle of a vector ceil double double ceiling function cos double double cosine exp double double exponential function (eAargument) floor double double floor function IEEEremainder double, double double remainder after division lob double double natural logarithm max double, double double maximum max int, int int maximum max long, long long maximum min double, double double minimum min int, int int minimum min long, long long minimum pow double, double double first argument to the power of the second double random number between 0.0 and 1.0 random rint double double round to the nearest integer round double long round to the nearest integer sin double double sine function sqrt double double square root tan double double tangent function toDegrees double double convert radians to degrees toRadians double double convert degrees to radians 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() which returns 2, and [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: {a=l, b=2} .getC'a") 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 capabilities. 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. 9.6 Fixed Point Data Type Ptolemy II includes a preliminary fixed point data type. The FixPoint class in the math package represents fixed point numbers. The FixToken class encapsulates fixed point data for exchange between Ptolemy II actors. The precision of fixed point data is denoted in two different ways: (jnlri): The total precision of the output is m bits, with the integer part having n bits. The fractional part thus has m - n bits. (m.n): The total precision of the output is n + m bits, with the integer part having m bits, and the fractional part having n bits. We represent a fixed point value in the expression language using the following format: fix(value, integerBits, fractionBits) 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 representable with the specified precision. If the value to represent is out of range, then it is saturated, meaning 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 arguments are the same as those of the fix() function, but the return type is a DoubleToken or DoubleMatrixToken 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.1 FixPoint Implementation We will now discuss how the FixPoint data type is implemented in Ptolemy II, and how it interacts with the Token types and expression parser. The overall UML diagram showing classes involved in the definition of the FixPoint data type is shown in Figure 9.6 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 Biglnteger 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 FixPoints 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 represents 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 tfixlvalue: double, numberCHBits: int. inteoefBils: int): FixToken tfixlvalues: DoubleMatrixToken, numberOIBits: int. inteqerBits: in»: FixMatrixToken »guantizefvalues: DoubleMatrixToken. numberOfBits: int. integerBils: jnf>: DoubleMatrixToken touantizefvalue: double, numberOfBits: int, inteqerBits: int): DoubleToken PtParaer MatrixToken »reqisterFunctionClasslnewClassName: String) FixMatrixToken FixToken -_columnCount: int ■_precision: Precision -_rowCount: int value: FixPoint +FixToken(vaiue: double, bits: int, intBits: int) +FixToken(yalue: double, precision: String) +FixToken(value: FixPoint) .value: FixPointQD +FixMatrixToken() +convertToDoubleO: double »scaleToPrecision(p: Precision): FixToken +FixMatrixToken(value: FixPointQn) getElementAt(row: int, column : int): FixPoint +fJxMatrixQ: FixPointpfl 1..1 NxM 1..1 FixPoint •_precision: Precision value: Biglnteger Precision ■FixPoint(precision: Precision, value: Biglnteger) tabsoluteO +add(arg : FixPoint): FixPoint +divide(arg: FixPoint): FixPoint +doublevalue(): double +equals(arg: FixPoint): boolean *getErrorDescription(): String -Jength: int integerBits: int -_fraction: int +Precision(length: int, intBits: int) +Precision(precision: String) ♦findMaximumO: BigDecimal +findMinimum(): BigDecimal tgetPredsionO: Precision *mulBply(arg: FixPoint): FixPoint «■setError(error: FixPointError) tsubtractjarg: FixPoint): FixPoint «■toBitStringO: String ttoStringQ: String getFractionBitLengthO: int •getNumberOfBitsO: int +matchThePoint(precisionA: Precision, PrecisionB: Precision): Precision *toString(): String «Uses» 1..1 Quantizar «Uses» +round(value: double, precision: Precision): FixPoint +round(value: BigDecimal, precision: Precision): FixPoint JL iava.math.Biglntager +round(value: FixPoint, newprecision: Precision, mode: int): FixPoint ■Uruncatefvalue: double, precision: Precision): FixPoint iava.math.BigDecimal '->. <■ +truncate(value: BigDecimal, precision: Precision): FixPoint +truncate(value: FixPoint newprecision: Precision, mode: int): FixPoint «Uses» FIGURE 9.6. Organization of the FixPoint Data Type. 191 version from a FixPoint to a double is not lossless and an exception will be thrown when tried. 9.6.4 Expression Language To make the FixToken accessible within the expression language, we have added a number of fixed point specific utility functions. These functions have been registered with the expression parser, using its function registration mechanism. • To create a single FixPoint Token using the expression language: fix(5.34, 10, 4) • This will create a FixToken. In this case, we try to fit the number 5.34 into a 10 bit representation with 4 bits used in the integer part. This may lead to quantization errors. By default the round quantizer is used. To create a Matrix with FixPoint values using the expression language: fix([ -.040609, -.001628, .17853 ], 10, 2) This will create a FixMatrixToken with 1 row and 3 columns, in which each element is a FixPoint value with precision (10/2). The resulting FixMatrixToken will try to fit each element of the given double matrix into a 10 bit representation with 2 bits used for the integer part. It uses by default the round quantizer. To create a single DoubleToken, which is the quantized version of the double value given, using the expression language: quantize(5.34, 10, 4) • This will create a DoubleToken. The resulting DoubleToken contains the double value obtained by fitting the number 5.34 into a 10 bit representation with 4 bits used in the integer part. This may lead to quantization errors. By default the round quantizer is used. To create a Matrix with doubles quantized to a particular precision using the expression language: quantized -.040609, -.001628, .17853 ], 10, 2) This will create a DoubleMatrixToken with 1 row and 3 columns. The elements of the token are obtained by fitting the given matrix elements into a 10 bit representation with 2 bits used for the integer part. Instead of being a fixed point value, the values are converted back to their double representation and by default the round quantizer is used. 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. E.1 Generating the parse tree In Ptolemy II the expression parser, called PtParser, is generated using JavaCC and JJTree. JavaCC is a compiler-compiler that takes as input a file containing both the definitions of the lexical tokens that the parser matches and the production rules used for generating the parse tree for an expression. The production rules are specified in Backus normal form (BNF). JJTree is a preprocessor for JavaCC that enables it to create an AST. The parser definition is stored in the file PtParser.jjt, and the generated file is PtParser.java. Thus the procedure is PtParser.jjt /JJTree ^ ) PtParser.jj ^ ► ( N PtParser.java JavaCC 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 registered 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. E.2 Evaluating the parse tree The AST can be evaluated by invoking the method evaluateParseTree() on the root node. The AST is evaluated in a bottom up manner as each node can only determine its type after the types of all its children have been resolved. When the type of the token stored in the root node has been resolved, this token is returned as the result of evaluating the parse tree. As an example consider the input string 2 + 3.5. The parse tree returned from the parser will look like this: Stepl: (root) * (sum) Tree evaluation ( 3^DIntToken(2) <5ÜD DoubleToken(3.5) which will then get evaluated to this: Step 2: (root) DoubleToken(5-5) (sum) &l«tToken(2) A Tree evaluation DoubleToken(5.5) (TeaT) DoubleToken(3.5) and DoubleToken(5.5) will be returned as the result. As seen in the above example, when evaluateParseTree() is invoked on the root node, the type and value of the tokens stored at each node in the tree is resolved, and finally the token stored in the root node is returned. If an error occurs during either the creation of the parse tree or the evaluation of the parse tree, an IllegalArgumentException is thrown with a error message about where the error occurred. If a node has more than two children, type resolution is done pairwise from the left. Thus "2 + 3 + "hello"" resolves to 5hello. This is the same approach that Java follows. Each time the parser encounters a function call, it creates an ASTPtFunctionNode. When this node is being evaluated, it uses reflection to look for that function in the list of classes registered with the 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. E.2.1 Node types There are currently fourteen node classes used in creating the syntax tree. For some of these nodes the types of their children are fairly restricted and so type and value resolution is done in the node. For others, the operators that they represent are overloaded, in which case methods in the token classes are called to resolve the node type and value (i.e. the contained token). By type resolution we are referring to the type of the token to be stored in the node. ASTPtBitwiseNode. This is created when a bitwise operation (&, |, A) happens. Type resolution occurs in the node. The & and | operators are only valid between two booleans, or two integer types. The A operator is only valid between two integer types. ASTPtLeafNode. This represents the leaf nodes in the AST. The parser will always place either a token of the appropriate type (e.g. IntToken if "2" is what is parsed) or a parameter in a leaf node. A parameter is placed so that the parse tree can be reevaluated without reparsing whenever the value of the parameter changes. No type resolution is necessary in this node. ASTPtRootNode. Parent class of all the other nodes. As its name suggests, it is the root node of the AST. It always has only one child, and its type and value is that of its child. ASTPtFunctionNode. This is created when a function is called. Type resolution occurs in the node. It uses reflection to call the appropriate function with the arguments supplied. It searches the classes registered with the parser for the function. By default it only looks in java.lang.Math and ptolemy.data.expr.UtilityFunctions. ASTPtFunctionallfNode. This is created when a functional if is parsed. Type resolution occurs in the node. For a functional if, the first child node must contain a BooleanToken, which is used to chose which of the other two tokens of the child nodes to store at this node. ASTPtMethodCallNode. This is created when a method call is parsed. Method calls are currently only allowed on tokens in the ptolemy.data package. All of the arguments to the method, and the return type, must be of type Token (or a subclass). ASTPtProductNode. This is created when a *, / or % is parsed. Type resolution does not occur in the node. It uses the multiply(), divide() and modulo() methods in the token classes to resolve the nodes type. ASTPtSumNode. This is created when a + or - is parsed. Type resolution does not occur in the node. It uses the add() and subtract() methods in the token classes to resolve the nodes type. ASTPtLogicalNode. This is created when a && or || is parsed. Type resolution occurs in the node. All 195 children nodes must have tokens of type BooleanToken. The resolved type of the node is also BooleanToken. 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 ScalarTokens. 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 considered 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 registerClass(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 evaluated, 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 supported 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 clustered 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 precedences, 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 entities. 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. 10.2 Classes and Interfaces in the Graph Package Figure 10.1 shows the class diagram of the graph package. The classes Graph, DirectedGraph and DirectedAcyclicGraph support graph construction and provide graph algorithms. Currently, only topological sort and transitive closure are implemented; other algorithms will be added as needed. The CPO interface defines the basic CPO operations, and the class DirectedAcyclicGraph implements this interface. An instance of DirectedAcyclicGraph is also a finite CPO where all the elements and order relations are explicitly specified. Defining the CPO operations in an interface allows future expansion to support infinite CPOs and finite CPOs where the elements are not explicitly enumerated. The Inegraph Graph #_graph: ArrayUst -_nodeObjects : ArrayUst -_nodeldTable: Mashtable -_edgeCount: int +Graph() +Graph(nodeCount: int) +add(o: Object) +addEdge(o1: Object. o2 : Object) +contains(o : Object): boolean +descriptionO: String +getEdgeCount(): Int +getEdge$0: Objectfjn +getNodeCount(): int -tgetNodesO : ObjectQ #_getNodeld(o : Object): Int *"getNodeObject(nodeld: int): Object DirectedGraph (MnDegree: ArrayUst «MransitiveClosure: bootoanQB •JsAcyclic: boolean +DirectedGraphO +DiroctedGraphs(nodeCount: int) +backwardReachaWeNodes(o : Object): ObjectQ +backwardReachaWeNodes(o : ObjectfJ): ObjectQ ■cycleNodesOO' QbjectQ +lsAcycltcO: boolean +reachableNorJes(o : Object): ObjectfJ ♦reachableNodesjo : ObjectQ): Objectf] #_computeTransltiveaosureO «Interface» InequalityTerm Inequality -JesserTenn : InequalityTerm greaterTerm: InequalityTerm +1nequality(lesser: InequalityTerm. greater: InequalityTerm) +getGreaterTermO: InequalttyTerm +getLesserTerm(): InequalityTerm +isSatisfied{cpo : CPO): boolean _____ +ftxValueO +getAssociatedObject(): Object +o»f ValueQ: Object ■HjetVariablesf): InequalityTermQ ♦initialize^: Object) *isSettable(): boolean +isValueAcceotable(): boolean *setValue(e: Object) •HjnfJXValueO O.n «Interface» CPO HIGHER : int INCOMPARABLE : +LOWER : int ♦SAME: int +bottom(): Object *compare(e1: Object, e2. Object): int *downSet(e: Object): ObjectQ +gnaatestLowerBound{e1: Object, e2; Object): Object +gn»atestLowerBound(sub5et: ObjectQ): Object +gnatestElement(subsot: ObjectQ): Object +isLatticeO: boolean +feasiBement(subset: ObjectQ): Object *feastUppen3ound(e1: Object, e2: Object): Object +feastUpoerBound(subset: ObjectQ): Object +top(): Object *upSet(e: Object): ObjectQ A InequalitySolver DiracttdAcyclicGra ph ■_bottom : Object •^closure: booleanpö •Jop: Object ♦DirectedAcyclicGrpahO ♦DirectedAcyclicGraph(nodeCount: int) ♦topologlcalSortO: ObjectfJ +topologicalSort(o : ObjectQ): ObjectfJ cpo : CPO -Just: ArrayUst -_Chst: Hashtabte +lnequalitySotver(cpo: CPO) +addlnequality(ineq: Inequality) +bottomVariablesO: Iterator ♦sorveGreatestO : boolean ♦solveLeastO: boolean ■topVariabtesO: Iterator -mn satisfied) nequalitiesO : Iterator ♦varlablesQ : Iterator FIGURE 10.1. Classes in the graph package 198 constraints 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 translation from the node ID to the node Object is done by the _getNodeObject() method, and the translation 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 topology 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 addEdge() 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 connected 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. 10.2.2 Directed Graphs The DirectedGraph class is derived from Graph. The addEdge() method in DirectedGraph adds a directed edge to the graph. In this package, the direction of the edge is said to go from a lower node to _graPh 1 0 1 2 1 (b) (a) FIGURE 10.2. An undirected graph 199 2 a higher node, as opposed to from source to sink, head to tail, etc. The terms lower and higher conforms 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 internally 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 accomplished. 10.2.3 Directed Acyclic Graphs and CPO The DirectedAcyclicGraph class further restricts DirectedGraph by not allowing cycles. For performance reasons, this requirement is not checked when edges are added to the graph, but is checked when any of the graph operations is invoked. An exception is thrown if the graph is found to be cyclic. The CPO interface defines the common operations on CPOs. The mathematical definition of these operations can be found in [19]. Informal definitions are given in the class documentation. This interface is implemented by the class DirectedAcyclicGraph. Since most of the CPO operations involve the comparison of two elements, and comparison can be done in constant time once the transitive closure is available, DirectedAcyclicGraph makes heavy use of the transitive closure. Also, since most of the operations on a CPO have a dual operation, such as least upper bound and greatest lower bound, least element and greatest element, etc., the code for the dual operations can be shared if the order relation on the CPO is reversed. This is done by transposing the transitive closure matrix. 10.2.4 Inequality Terms, Inequalities, and the Inequality Solver The Inequality Term interface and Inequality and InequalitySolver classes supports the construction of a set of inequality constraints over a CPO and the identification of a member of the CPO that satisfies the constraints. A constraint is an inequality defined over a CPO, which can involve constants, variables, and functions. As an example, the following is a set of constraints over the 4-point CPO in figure 10.3: ccm hi JPiMüU«! i! 'i!i':::::::üiJ!i| I::::::: I ■■{!:i:■;:;:i'Mt fn i ' i Nl / . i!!!!!l!!!FH|!!!!!!!!#'iiiiiSiiS«h \i -0.0 0.1 0.2 0.3 0.4 0.5 i/ 0.6 >j. 111M i y 0.7 % .'' 0.8 .i'' \i ■ • • • 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 ^^ Bfl Cliaca: a Sample plot li# A nwiiTni>. __. PI- OK Vipaua*::. - ■ itllfl PU2- jl ju o-- ^Whiipl I (111 Imi tu-^ -PI" iXIIIII/i -0.0 0.1 0.2 0.3 ".'"/fit tilt Hfl jMlitiij *^iHj M IHIIHII IIIIImivHiifl p e -PIß " AW ill SB •■■W i 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 positions 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: label position, label position, ... 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 frequency domain plot might have tick marks as follows: l-lnlxl Wto EdJ SpecW Sample plot —i 1 New Title r- '■'■>■%. .*:'''*...-•*•* • ••• •/•?::?:::?:::==::;:r-;:......£..^:L;..:::p,i::^:,::• * •_ V *•., "*•'•...,!.t^•••, 0.2 Title: Grid: ® ® v Label: Stems: X Ranue: 0.0,100.0 V Range: 4.0,4.0 Connect: <8) (•Jnone H B| Use Color:® OpoMs Odots Ovarims X Ticks: XLog: O YLog: ® V Ticks: *PP» i 04 l I i ! 0.5 0.6 *• * I**, 0.7 ,**i *1! 0.9 ! i—: 1.0 horizontal Sample plo^ X Label: Marks: -**i 0.3 i Cancel Tlde: New Tille X Labet horizontal YLabet vertical Grtt O Stems: O X Range 0.0.100.0 YRange: -4.0. 4.0 Marks: Onone .«poWs Odots Use Color: O XLog: O Connect: Q Ovarious YLog: X Ticks Y Ticks: | «PPV 11 Cancel 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. On the left is before changes are made, and on the right is after. 226 O 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. 12.3 Class Structure The plot package has two subpackages, plotml and compat. The core package, plot, contains toolkit classes, which are used in Java programs as building blocks. The two subpackages contain classes that are usable by an end-user (vs. a programmer). 12.3.1 Toolkit classes The class diagram for the core of the plot package is shown in figure 12.6. These classes provide a toolkit for constructing plotting applications and applets. The base class is PlotBox, which renders the axes and the title. It extends Panel, a basic container class in Java. Consequently, plots can be incorporated into virtually any Java-based user interface. The Plot class extends PlotBox with data sets, which are collections of instances of PlotPoint. The EditablePlot class extends this further by adding the ability to modify data sets. Live (animated) data plots are supported by the PlotLive class. This class is abstract; a derived class must be created to generate the data to plot (or collect it from some other application). The Histogram class extends PlotBox rather than Plot because many of the facilities of Plot are irrelevant. This class computes and displays a histogram from a data file. The same data file can be read by this class and the other plot classes, so you can plot both the histogram and the raw data that is used to generate it from the same file. 12.3.2 Applets and applications A number of classes are provided to use the plot toolkit classes in common ways, but you should keep in mind that these classes are by no means comprehensive. Many interesting uses of the plot package involve writing Java code to create customized user interfaces that include one or more plots. The most commonly used built-in classes are those in the plotml package, which can read PlotML files, as well as the older textual syntax. Ptplot 4.1, which shipped with Ptolemy II 0.4 requires Swing. The easiest way to get Swing is to install the JDK 1.2.2 plugin, which is part of the JDK 1.2 installation. Unfortunately, using the JDK 1.2.2 plugin makes the applet HTML more complex. Briefly, there are two sections, one for Microsoft Internet Explorer, the other for Netscape Communicator. The following segment of HTML is an example: . No JDK 1.2 support for applet! 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 format for the data is described in the next section. The applet is created by instantiating the PlotMLApplet 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 figure 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 commands, 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: java -classpath $PTII ptolemy.plot.PlotApplication args 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 histograms. 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 tMLAppletO ~C> »_newParserQ; PlotMLParsei HIstgramMLParser Histogram MLAppItt PlotMLAppllcatlon »HistogramMLAppletQ +PlotMLApp(icationO *PlotMLApplication(args: StringQ) +PlotMLApplication(plot: PlotBox, args : StringQ) »_newParserQ: PlotBoxMLParser 4-HistogramMLParser(plot: Histogram) EditablePlotMLAppllcatlon +EditabtePlotMLApplicati onO +EditableRotMLApplication(args: StringQ) +Editable Plot MlApp1icabon( plot: EditablePlot, args : StringQ) T" Histogram M LAppllcation +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: public class MyApplet extends JApplet { public void initO { super.init(); Plot myplot = new PlotO; getContentPane().add(myplot); myplot.setTitle("Title of plot"); } } 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: public class MyApplet extends JApplet { public void initO { super.init(); Plot myplot = new PlotO ; JPanel panel = new JPanelO; getContentPane (,) .add (panel) ; panel.add(myplot); myplot.setSize(500, 300); myplot.setTitle("Title of plot"); } } 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); 12.4 PlotML File Format Plots can be specified as textual data in a language called PlotML, which is an XML extension. XML, the popular extensible markup language, provides a standard syntax and a standard way of 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 histograms. 12.4.1 Data organization Plot data in PlotML has two parts, one containing the plot data, including format information (how the plot looks), and the other defining the PlotML language. The latter part is called the document type definition, or DTD. This dual specification of content and structure is a key XML innovation. Every MoML file must either contain or refer to a DTD. The simplest way to do this is with the following file structure: format commands... datasets... 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 version 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 -//UC Berkeley//DTD PlotML 1//EN 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 format commands... datasets... Here, the DTD location is a relative or absolute URL. A third alternative is to create a standalone PlotML file that includes the DTD. The result is rather verbose, but has the general structure shown below: format commands datasets 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. 12.4.2 Configuring the axes The elements described in this subsection are understood by the base class PlotBoxMLParser. Your Text Here The title is bracketed by the start element and end element . In XML, end elements 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, Your Text Here Your Text Here 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 various | pixels) «IMPLIED various | pixels) "none" FIGURE 12.9. The document type definition (DTD) for the PlotML language. 235 size | title | wrap | xLabel 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: A label is a string that replaces the number labels on the axes. A position is a number giving the location of the tick mark along the axis. For example, a horizontal axis for a frequency domain plot might have tick marks as follows: 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 elements: 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: 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: Finally, the rather specialized element 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 feature 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 methods from Java code. 12.4.3 Configuring data Each data set has the form of the following example «iataset name="grades" marks="dots" connected="no" stems="no"> data 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: The DTD for this element is: If the following element occurs: 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. 12.4.4 Specifying data A dataset has the form data The data itself are given by a sequence of elements with one of the following forms: To reduce file size somewhat, they can also be given as 238

x="xValue" y="yValue"> y="yValue" lowErrorBar="low" highErrorBar="high"> 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: x="xValue" y="yValue"> y="yValue" lowErrorBar="lew" highErrorBar="high"> x="xValue" y="yValue" lowErrorBar="low" highErrorBar="high"> y="yValue"> x="xValue" y="yValue"> y="yValue" lowErrorBar="low" highErrorBar="high"> 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. 12.4.5 Bar graphs To create a bar graph, use: You will also probably want the connected option to have value "no." The barWidth is a real number specifying the width of the bars in the units of the X axis. The barOffset is a real number specifying 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 compatibility. In addition, it is significantly more concise than the PlotML syntax, which can be advantageous, 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. 12.5.1 Commands Configuring the Axes The following commands are understood by the base class PlotBox. These commands can be placed in a file and then read via the read() method of PlotBox, or via a URL using the PlotApplet class. The recognized commands include: • • • TitleText: string XLabel: string YLabel: string These commands provide a title and labels for the X (horizontal) and Y (vertical) axes. A string is simply a sequence of characters, possibly including spaces. There is no need here to surround them with quotation marks, and in fact, if you do, the quotation marks will be included in the labels. The ranges of the X and Y axes can be optionally given by commands like: • • XRange: min, max YRange : min, max 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 datapoints are not plotted on the axes. 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 commands like: • XTicks: label position, label position, ... • YTicks: label position, label position, ... 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 frequency domain plot might have tick marks as follows: 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. 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 feature 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 methods from some Java code. 12.5.2 Commands for Plotting Data The set of commands understood by the Plot class support specification of data to be plotted and control over how the data is shown. The style of marks used to denote a data point is defined by one of the following commands: • • • • • Marks: Marks: Marks: Marks: Marks: none points dots various pixels Here, points are small dots, while dots are larger. If various is specified, then unique marks are used for the first ten data sets, and then recycled. If pixels is specified, then a single pixel is drawn. Using no marks is useful when lines connect the points in a plot, which is done by default. If the above directive appears before any DataSet directive, then it specifies the default for all data sets. If it appears after a DataSet directive, then it applies only to that data set. To disable connecting lines, use: • Lines: off To re-enable them, use • Lines: on You can also specify "impulses", which are lines drawn from a plotted point down to the x axis. 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. Otherwise, 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 quotation 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 numbers, then the last two numbers are assumed to be the lower and upper values for error bars. The numbers 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 limitations 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 j Plot ! PlotAppiet i i+_read(input: lnputStream)| L\ PlotAppltcation +main(args: StringD) #_about() #_parseArgs(args: StringD) # read(base: URL, input: InputStream) configures PxgraphParser PxgraphApplet #_pk>t: Plot +PxgraphParser(plot: Plot) +parseArgs(args: StringQ) +parseArgs(args: StringD, base: URL) +parsePxgraphargs(args: StringD, base: URL) +read(input: nput! jtrea m) +PxgraphAppletO PxgraphApplication +PxgraphApplicationO +PxgraphApplication(args: StringD) +PxgraphApplication(plot: Plot, args: StringD) 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 provided 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 scheduling 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 represent 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 system 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 applications, 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 minimal 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 platforms, 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 infrastructural 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 chapter 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 implements 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. 13.2.1 Design Artifacts The goal of a design application is the creation of a particulart type of design artifact. A design artifact is any electronic entity that is created to serve a specific purpose such as a text file, a circuit design, or a piece of computer software. Design artifacts almost always have a variety of aspects, and it is usually difficult to display all of these aspects at once. Good examples of this are Microsoft PowerPoint presentations. A presentation contains many slides, and each slide can be individually displayed and manipulated. Each slide can contain many different kinds of objects (which are often themselves distinct embedded design artifacts). The presentation itself can also contain timing, narration and navigation information. The PowerPoint application can change the information displayed to emphasize a particular aspect of the presentation, such as a particular slide or a slide overview or a text-only view. 13.2.2 Storage policies The most basic operation that almost any application must perform is the storage and retreival of designs. Most applications store design artifacts as files visible through the operating system, however we would like to be somewhat more general and allow design artifacts to be stored in databases or accessed through the World Wide Web. We believe that URLs are general enough to describe any such location. The infrastructure that we would like build for handling files revolves around a storage policy. The storage policy gives a basic set of consistent rules for how design objects are persistently stored. In plain English, these rules can be simple, or fairly complex. One example of a simple storage policy rule might be that to open a design artifact, the location is specified using a filebrowser dialog. A more complex rule could state that a design artifact cannot be closed unexpectedly without giving the user an opportunity to save. Implementing a storage policy in basic infrastructure is good for several reasons. First of all, it prevents application writers from being concerned with relatively boring 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. Furthermore, 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. Furthermore, 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 infrastructure by simply extending a few classes. 13.3.1 Effigies and Tableaux Each design artifact is represented by an instance of the Effigy class. Each effigy is associated with a URL, corresponding to the location of the persistent storage of the effigy. Each effigy also has an identifier, which is the unique string that identifies the effigy. This identifier should be a string representation of the effigy's URL. Each view of the design artifact is represented by an instance of the Tableau class contained by the design artifact's effigy. Each tableau is associated with a single frame that presents information from the effigy. In some cases, in order to reuse code for tableaux, it is sometimes useful to have an effigy contain other effigies. The static structure diagram for this is shown in figure 13.1. 13.3.2 Effigy Factories Notice that the Effigy base class does not specify how it represents a particular design artifact. This is intentional, since we are building infrastructure and do not wish to restrict ourselves to any particular representation. However, at some point the infrastructure will need to create new effigies that are useful for a particular application. In this situation, the Factory design pattern is appropriate, which is shown in figure 13.2. An example of how the Effigy and EffigyFactory base classes are used is shown in figure 13.3. The example shows an effigy and factory appropriate for handling text documents. The EffigyFactory class contains two factory methods for creating new effigies. The first factory method takes a source URL and is used when opening a file. The second method does not take a source 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 creates 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 general one (such as an effigy that simply contains a text string). CompositeEntity A 1.1 Effigy +identifier: StringAttribute +url: URLAttribute +Effigy(workspace: Workspace) +Effigy(container: CompositeEntity, name : String) +closeTableaux(): boolean +getTableauFactory(): TableauFactory +isModifiable(): boolean +isModified(): boolean +setModifiable(flag: boolean) +setModified(flag: boolean) +setTableauFactory(factory: TableauFactory) +showTableaux() +topEffigy(): Effigy +writeFile(file: File) Tableau 1..n 1..n 1..1 +size : StringAttribute frame: _JFrame master: boolean +Tableau(container: Effigy, name : String) +close(): boolean +getFrame(): JFrame +getTitle(): String +isEditable(): boolean +isMasterO: boolean +setEditable(flag : boolean) +setFrame(frame: JFrame) +setMaster(flag: boolean) +setTitle(title: String) +show() Figure 13.1 Static structure diagram for effigies and tableaux. AbstractClass AbstractFactory creator createe +create(): AbstractClass T A ConcreteClass creator createe Figure 13.2 Static structure diagram for the Factory pattern. 248 ConcreteFactory 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 Kr- +EffigyFactory(workspace: Workspace) +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 TextEffigy +Factory(container: CompositeEntity, name : String) doc: Document +TextEffigy(workspace : Workspace) +TextEffigy(container: CompositeEntity, name : String) +getDocument(): Document +setDocument(doc: Document) creator createe Figure 13.3 Static structure that is useful for handling text documents. CompositeEntity TableauFactory Tableau +TableauFactory(container: Configuration, name: String) +createTableau(effigy: Effigy): Tableau TextEditorTableau$Factory creator TextEditorTableau 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 configuration includes not only the directory of currently open effigies but also the effigy factories and tableau 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) +createPrimaryTableau(effigy: Effigy) +openModel(base : URL, in : URL, identifier: String) B ModelDirectory 1..1 +ModelDirectory(container: Configuration, name : String) +getEffigy(identifier: String): Effigy 1..n 1..n I I directory effigyFactory 1..1 ^ TableauFactory Effigy EffigyFactory 1..1 I Tableau 1..1 createe 1..n 0.1 tableauFactory creator 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) actually 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. 13.4.1 Opening an Existing Design Artifact The File->Open menu item first opens a file browser to allow the user to select a URL, and then uses the Configuration to open the URL. The configuration firsts checks the model directory to see if there is already an effigy associated that URL. If there is no such effigy, then the configuration uses its effigy factory to create a new effigy, and then uses its tableau factory to create a tableau for the effigy. Lastly, the tableau is made visible, which results in it creating a frame on the user's screen. The sequence diagram is shown in figure 13.7. In addition, this first tableau is set to be a master, and it is set to be editable if the URL represents a writable location. Alternatively, there may already an effigy present in the directory that is associated with the URL JFrame Top #_directory: File #_fileMenu: JMenu #_fileMenultems: JMenultem[] #_helpMenu: JMenu #_helpMenultems: JMenultem[] #_menuBar: JMenuBar #_statusBar: StatusBar .file: File modified: boolean +Top() +centerOnScreen() +isModifiedO: boolean +report(ex: Exception) +report(message: String) +report(message: String, ex: Exception) +setBackground(background: Color) •setModified(b: boolean) #_about() #_addMenusO #_clear() #_close(): boolean #_exitO #_getNameO: String #_help() #_open() #_openURL() #_print() #_read(url: URL) #_saveO: boolean #_saveAs(): boolean #_writeFile(file: File) I JPanel A StatusBar „progress: JProgressBar ..message: JTextField +StatusBar() +progressBar(): JProgressBar setMessage(message: String) Tableau 1..1 creator TableauFrame

New menu item is somewhat similar to opening an existing design artifact. However, only effigy factories that declare that they can create a blank effigy that is not associated with a previous URL may be used. Furthermore, since an application can conceivably create different types of blank effigies, it is not possible to use the effigy factory deference mechanism to determine which effigy factory is used. The user must have another way of specifying which effigy factory will create the blank effigy. When a TableauFrame is created, the File->New menu is populated with a menu item for each possible effigy factory. The name of the menu item is the same as the name of the effigy factory. The sequence diagram for creating a new design artifact is shown in figure 13.8. t:TableauFrame ^Configuration b:JFileChooser c.ef: TextEffigy$Factory c.d:ModelDirectory c.d.e:TextEffigy c.d.e.t:TextTableau shcwOpenDialogO c.tf: TextTableau$Factory ► u:URL openModel(u,...) getEffigy(u) ► null createEffig y(c.d, u, u) new TextE Tigy(c.d, e) c.d.e createTableau(c d.e) new TextTc bleau(c.d.e N c.d.e.t Figure 13.7 Sequence diagram for opening an existing design artifact. 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. 13.4.4 Closing designs and Exiting the Application The only complexities in implementing these operations are again involved with ensuring that important data is not lost. In this case, we simply ensure that all designs are closed before exiting the application, and that a design is not closed without attempting to save it first. Both of these cases are prevented by setting a flag in each effigy whenever it is modified. If the flag indicates that the effigy has been modified, then the Save operation is invoked before discarding the effigy. Activating the close operation of a frame only results in the tableau associated with that frame being removed. The tableau's effigy and the other tableaux assocated with that effigy are not generally affected. There is a subtlety that arises because the application itself exists separately from any visual representation of it. In other words, a tableau (and therefore a frame) exists for each effigy, but there is no tableau that simply represents the application as a whole1. The subtlety is that closing all the effigies should result in the application exiting. A similar issue occurs for a similar reason with effigies, t:TableauFrame c:Confguration c.ef: TextEffigy$Factory c.d:ModelDirectory c.d.e:TextEffigy c.d.e.tTextTableau c.tf: TextTableau$Factory creeteEffigy(c.d; new TextE figy(c.d, e) c.d.e createTableau(c d.e) new TextT« bleau(c.d.e t) « c.d.e.t Figure 13.8 Sequence diagram for creating a new design artifact. 253 and closing all of a tableaux associated with any effigy should result in that effigy being closed. 13.5 Ptolemy Model Visualization We have used the Vergil infrastructure to construct several visualizations that are capable of viewing and manipulating a Ptolemy model. For the most part, these editors are intended to work with any ptolemy kernel model and are not limited to models based on the Actor package or a specific domain. This is an extremely powerful use of the Ptolemy abstract syntax, since it allows manipulation not only of executable models (see Chapter FIXME), but also actor libraries (see Figure FIXME) and the Vergil configuration itself (see Figure 13.5), since they are also based on the Ptolemy Kernel (see Chapter FIXME). This section serves a dual purpose: it describes not only a usable set of application tools, but also a well developed example of using the Vergil infrastructure to present multiple views of a design artifact. In order to represent a Ptolemy model in Vergil, there must be an effigy that has a reference to it. The PtolemyEffigy class maintains this reference, and is also responsible for reporting any change requests (see Chapter FIXME) in the model that fail. It also contains an inner class that is an effigy factory and writes out a model using MoML (see Chapter FIXME). The static structure diagram for these classes is shown in Figure 13.9 There is also an accompaning frame class, PtolemyFrame, that is 1 «interface» | 1 ChangeListener | 1 | 1 1 1 i i i 1 1 1 1 I i I I A Effigy ' 1 1 1 1 1 EffigyFactory | 1 1 __1 ? _j PtolemyEffigy PtolemyEffigy$Factory -_model: NamedObj +PtolemyEffigy(workspace: Workspace) +PtolemyEffigy(container: CompositeEntity, name: String) +getModel(): NamedObj +setModel(model: NamedObj) - Darser: MoMLParser +Factory(container: CompositeEntity, name : String) < 0..1 1 model 1 1 1 1 1 1 1 1 1 ComponentEntity | 1 a component named "blank" is cloned to create a b ank effigy. L ^ Figure 13.9 Static Structure for Ptolemy effigies. 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. 13.5.1 Graph Tableau The Ptolemy graph editor graphically represents the contained entities, ports, and relations of any Ptolemy composite entity. It allows syntax-directed editing of the model and browsing of important design information, such as Actor source code and HTML documentation. A screen shot is shown in Figure 13.11. The left hand side provides a palette of available entities and a high-level navigation window. Entities can be dragged and dropped from the palette. External ports are created by using the toolbar button, and relations can be created from the toolbar button, or by control clicking on the schematic. Links to relations can be created be made by control clicking on a port or a relation. The visualization also allows connections directly from one port to another. These links correspond to a relation that is linked to both ports, but the relation is not explicitly represented itself. Note that although the editor allows any ptolemy model to be edited, it does display some information that is specific to the actor package. For example, ports are rendered differently depending on whether they are input or output ports, and the multiports of the Multiply actor are rendered hollow. The director (in this case, an SDF director) is also displayed as a green box. The classes used to implement this tableau are shown in Figure 13.12. An instance of KernelGraphFrame is created by the tableau. The KernelGraphFrame 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. This allows the code in GraphFrame to be reused with a different visual representation, such as the FSM editor described in Section 13.5.2. Effigy 1.J1 0..1 1..I TableauFrame Tableau 7T 1..1 1..n 1..1 ZT PtolemyFrame PtolemyEffigy ■_model: CompositeActor +PtolemyTop(model: CompositeActor) +PtolemyTop(model: CompositeActor, tableau : Tableau) +getModel(): CompositeActor +setModel(model: CompositeActor) 1..1 ConcreteTableau 1..n 0..1 1..1 Figure 13.10 Static structure of the Ptolemy graph editor. 255 ConcreteTableauFrame 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 FSMGraphJfijx] s*l file::C:.'users;neuendor/ptIl/ptoleniy/domains./sdMib/Spectrum.Knil File View Edit Graph Help _J utilities director library This composite actor produces a magnitude-only frequency-domain representation of the input The number of inputs required to produce any output is 2Aorder, and the number of outputs produced will be 2Aorder. The output represents frequencies from -pi to pi radians per second, centered at zero frequency. SDFO ' iflfflllfflfl _J Graphics Multiply Waveform FFT AbsotuteValueO DB uT^H ► — ►—•►' N r^;^. Figure 13.11 Vergil Screenshot. GraphFrame TableauFrame PtolemyFrame Tableau A +cut() +copy() +getJGraph() +layoutGraph() +paste() +print() #_createGraphPane(): GraphPane #_writeFile(file: File) IS KernelGraphTableau creator 1..1 1..1 KernelGraphFrame createe +_createGraphPane(): GraphPane Figure 13.12 Static structure of the Ptolemy graph editor. 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 KernelGraphFrame class described in section 13.5.1 iMxj P?|file:/C:/userv'neuendor/ptH.''p^ File View Edit graph Help utilities I director library [D actor library Cj Graphics init O true \ Separate touched S Togeifa ..L. ( J / -^F_V > ST1_V i| F_V < -STl_V Figure 13.13 Vergil Screenshot. GraphFrame TableauFrame | PtolemyFrame Tableau +cut() +copy() +getJGraphO +layoutGraph() +paste() +printO #_createGraphPane(): GraphPane #_writeFile(file: File) K FSMGraphTableau creator 1..1 1..1 FSMGraphFrame createe +_createGraphPane(): GraphPane Figure 13.14 Static structure of the Ptolemy graph editor. 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 emphasized. 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 TreeModel, which only includes entities. The static structure of the ptolemy.vergil.tree package is shown in Figure 13.12. I I Tableau PtolemyFrame I | A "A" JTree A TreeTableau «interface» TreeModel TreeCellRenderer creator 1..1 1..1 createe TreeTableauSTreeFrame "A" EntityTree Model H_createGraphPane(): GraphPane PtolemyTreeCellRenderer FullTreeModel PTree Figure 13.15 Static structure of the ptolemy.vergil.tree package. 258/259 Compos KeEntity 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 analog circuits, plant dynamics in control systems, lumped-parameter mechanical systems, lumpedparameter heat flows and many other physical systems. Let's start with an example. Consider a second order differential system, mz(t) + bz(t) + kz(t) = u(t) y(t) = c ■ z(t) z(0) = 10,z(0) = 0. 0) . The equations could be a model for an analog circuit as shown in figure 14.1(a), where z is the voltage r• (a) A circuit implementation. (b) A mechanical implementation. FIGURE 14.1. Possible implementations of the system equations. 260/261 of node 3, and m = R\R2C\C2 (2) k = R\ -C\ +R2-C2 b = 1 c = R4 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) y = g(x, u, t) v *('o) 0> (3) (4) (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 Idimensional output of the system; x e 9t is the derivative of x with respect to time /, i.e. x = dx dt (6) 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 x{t) = z(t) m (7) system (1) can be writen in form of (3)-(5), like x(t) = - 0 1 x(t) + 0 u(t) m -k-b 1/lH (8) 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 waveform, 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 studied for more than three decades. One of the most well-known tools is Spice [63]. The CT domain differs 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. 14.1.1 System Specification There are usually two ways to specify a continuous-time system, the conservation-law model and the signal-flow model [39]. The conservation-law models, like the nodal analysis in circuit simulation [36] and bond graphs [75] in mechanical models, define systems by their physical components, which specify relations of cross and through variables, and the conservation laws are used to compile the component relations into global system equations. For example, in circuit simulation, the cross variables are voltages, the through variables are currents, and the conservation laws are Kirchhoff s laws. This model directly reflects the physical components of a system, thus is easy to construct from a potential implementation. The actual mathematical representation of the system is hidden. In signalflow models, entities in a system are maps that define the mathematical relation between their input and output signals. Entities communicate by passing signals. This kind of models directly reflects the mathematical relations among signals, and is more convenient for specifying systems that do not have an explicit physical implementation yet. In the CT domain of Ptolemy II, the signal-flow model is chosen as the interaction semantics. The conservation-law semantics may be used within an entity to define its I/O relation. There are four major reasons for this decision: 1. The signal-flow model is more abstract. Ptolemy II focuses on system-level design and behavior simulation. It is usually the case that, at this stage of a design, users are working with abstract mathematical models of a system, and the implementation details are unknown or not cared about. 2. The signal flow model is more flexible and extensible, in the sense that it is easy to embed components that are designed using other models. For example, a discrete controller can be modelled as a component that internally follows a discrete event model of computation but exposes a continuous-time interface. 3. The signal flow model is consistent with other models of computation in Ptolemy II. Most models of computation in Ptolemy use message-passing as the interaction semantics. Choosing the signalflow model for CT makes it consistent with other domains, so the interaction of heterogeneous systems is easy to study and implement. This also allows domain polymorphic actors to be used in the CT domain. 4. The signal flow model is compatible with the conservation law model. For physical systems that are based on conservation laws, it is usually possible to wrap them into an entity in the signal flow model. The inputs of the entity are the excitations, like the voltages on voltage sources, and the outputs are the variables that the rest of the system may be interested in. The signal flow block diagram of the system (3) - (5) is shown in figure 14.2. The system dynamics (3) is built using integrators with feedback. In this figure, u, x, x, and y, are continuous signals (waveforms) flowing from one block to the next. Notice that this diagram is only conceptual, most models may involve multiple integrators1. Time is shared by all components, so it is not considered as an input. At any fixed time t, if the "snapshot" values x(t) and «(/) are given, x(t) and j/' (") where the set of the discrete time points. To explicitly illustrate the discretization of time and the difference between the precise solution and the numerical solution, we use the following notation in the rest of the chapter: tn: the n-th time point, to explicitly show the discretization of time. However, we write t, if the index n is not important. • x[tj, tj]: the precise (continuous) state trajectory from time tt to L; • x(tn): the precise solution of (3) at time /„; xt : the numerical solution of (3) at time /„ ; h n = t n ~ tn-1: ste P size of the discretization of time. We also write h if the index n in the 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 algorithms, 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 application dependent, and usually reflects the speed, accuracy, and numerical stability trade-offs. 14.2.2 Fixed-Point Behavior Numerical ODE solving algorithms approximate the derivative operator in (3) using the history and the current knowledge on the state trajectory. That is, at time r„, the derivative of x is approximated by a function of x., ...,x,'it-I ,x.'» , i.e. '0 J *<„ = P(Xl0'-'X>n-l'X>J- (12) Plugging (3) in this, we get P X ( 'o-X •■•>*, )' (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. 14.2.3 ODE Solvers Implemented The following solvers has been implemented in the CT domain. 1. Forward Euler solver: = *<. + ! X 'n = *i, (17) + h "+i'i'n + *»+rM,.«v'i.) 2. Backward Euler solver: 'n+1 n+1 'n = (18) 'n+1 A ^/ »+r/(*w"w>fl+i) 3. 2(3)-order Explicit Runge-Kutta solver ^0 = (19) n+\ ■/(*,„> ",„>*„) h K\ = K+i-f(xtii + K0/2,utn + K+i/2,tn + hn + l/2) K 2 = Ä«+rM„ + 3^l/4'M/„ + 3/I„+1/4^n + 3Än+l/4) 2 1 ' 4 x.'n+i — x,'„ + ~^Lfi 9 o ~l~ ~Ki 3 i + ~K-> 9 2 with error control: ^3 (20) = h n+\-f(xl„ + 1'Utn + l'tn+0 72 ° if \LTE\ < ErrorTolerance ,xt = x, integration step size is predicted by: 12 ' 9 2 8 3 , otherwise, fail. If this step is successful, the next h„ + 2 = hn+l ■ max(0.5, 0.8 • \](ErrorTolerance)/\LTE\) (21) 4. Trapezoidal Rule solver: (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 approximating 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 threshold can only report a "missed" breakpoint after an integration step is finished. How to handle breakpoints correctly is a big challenge for integrating continuous-time models with discrete models like DE and FSM. 14.2.5 Breakpoint ODE Solvers Breakpoints in the CT domain are handled by adjusting integration steps. We use a table to handle predictable breakpoints, and use the step size control mechanism to handle unpredictable breakpoints. The breakpoint handling are transparent to users, and the implementation details (provided in section 14.7.4) are only needed when developing new directors, solvers, or event generators. Since the history information is useless at breakpoints, special ODE solvers are designed to restart the numerical integration process. In particular, we have implemented the following breakpoint ODE solvers. 1. DerivativeResolver: dx It calculates the derivative of the current state, i.e. — . This is simply done by evaluation the righthand side of (3). At breakpoints, this solver is used for the first step to generate history information for other methods. * 2. ImpulseBESolver: **.♦, =\ X + C X, "+l + h »+r\+1 <23> /IH i i 'X + '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 polymorphic 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. 14.3.1 CT Actor Interfaces In order to schedule the execution of actors in a CT model and to support the interaction between CT and other domains (which are usually discrete), we provide the following interfaces. • CTDynamicActor. Dynamic actors are actors that contains continuous dynamics in their I/O path. An integrator is a dynamic actor, and so are all actors that have integration relations from their inputs to their outputs. • CTEventGenerator. Event generators are actors that convert continuous time input signals to discrete output signals. CTStatefulActor. Stateful actors are actors that have internal states. The reason to classify this kind of actor is to support rollback, which may happen when a CT model is embedded in a discrete event model. CTStepSizeControlActor. Step size control actors influence the integration step size by telling the director whether the current step is accurate. The accuracy is in the sense of both numerical errors and absence of unpredictable breakpoints. It may also provide information about refining a step size for an inaccurate step and suggesting the next step size for an accurate step. CTWaveformGenerator. Waveform generators are actors that convert discrete input signals to continuous-time output signals. Strictly speaking, event generators and waveform generators do not belong to any domain, but the CT domain is design to handle them intrinsically. When building systems, CT parts can always provide discrete interface to other domains. Neither a loop of dynamic actors nor a loop of non-dynamic actors are allowed in a CT model. They introduce problems on the order that actors should be executed. A loop of dynamic actors can be easily broken by a Scale actor with scale 1. A loop of non-dynamic actors builds an algebraic equation. The CT domain does not support modeling algebraic equations, yet. 14.3.2 Actor Library 1. Integrator: The integrator for continuous-time simulation. An integrator has one input port and one output port. Conceptually, the input is the derivative of the output, and an ordinary differential equation is modeled as an integrator with feedback. • An integrator is a dynamic actor, and it emits a token (with value equal to its internal state) at the beginning of the simulation. An integrator is a step size control actor, which estimates local errors at each integration step and controls the accuracy of the solution. An integrator has memory, which is its state. To help resolve the new state from previous states, a set of variables are used: state and its derivative: These are the new state and its derivative at a time point, which have been confirmed by all the step size control actors. 269 • • tentative state and tentative derivative: These are the state and derivative which have not been confirmed. 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 simulation 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 signals. This actor acts as a zero-order hold. It consumes the token when the consumeCurrentEvent () 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. 14.3.3 Domain Polymorphic Actors Not all domain polymorphic actors can be used in the CT domain. Whether an actor can be used depends on how the internal states of the actor evolve when executing. • Stateless actors: All stateless actors can be used in CT. In fact, most CT systems are built by integrators and stateless actors. • Timed actors: Timed actors change their states according to the notion of time in the model. All actors that implement the TimedActor interface can be used in CT, as long as they do not also implement SequenceActor. Timed actors that can be used in CT include plotters that are designed to plot timed signals. • Sequence actors: Sequence actors change their states according to the number of input tokens received by the actor and the number of times that the actor is postfired. Since CT is a time driven model, rather than a data driven model, the number of received tokens and the number of postfires do not have a significant semantic meaning. So, none of the sequence actors can be used in the CT domain. For example, the Ramp actor in Ptolemy II changes its state — the next token to emit — 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 CurrentTime 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 CTEmbeddedDirector. 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 composite 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 contained 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.) 14.4.1 ODE Solvers There are six ODE solvers implemented in the ptolemy.domains.ct.kernel.solver package. Some of them are specific for handling breakpoints. These solvers are ForwardEulerSolver, BackwardEulerSolver, ExplicitRK23Solver, TrapezoidalRuleSolver, DerivativeResolver, and ImpulseBESolver. They implement the ODE solving algorithms in section 14.2.3 and section 14.2.5, respectively. 14.4.2 CT Director Parameters The CTDirector base class maintains a set of parameters which control the execution. The parameters shared by all CT directors are listed in Table 21 on page 271. Individual directors may have their own (additional) parameters, which will be discussed in the appropriate sections. Table 21: CTDirector Parameters Default Value Description Type errorTolerance The upper bound of local errors. Actors that perform integration error control (usually integrators 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. double le-4 initialStepSize This is the step size that users specify as the desired step size. For fixed step size solvers, this step size will be used in all non-breakpoint steps. For variable step size solvers, this is only a suggestion. double 0.1 maxlterationsPerStep This is used to avoid the infinite loops in (implicit) fixed-point iterations. If the number of fixed-point iterations exceeds this value, but the fixed point is still not found, then the fixedpoint procedure is considered failed. The step size will be reduced by half and the integration step will be restarted. int 20 Name 271 Table 21: CTDirector Parameters Name Default Value Description Type maxStepSize The maximum step size used in a simulation. This is the upper bound for adjusting step sizes in variable step-size methods. This value can be used to avoid sparse time points when the system dynamic is simple. double 1.0 minStepSize The minimum step size used in a simulation. This is the lower bound for adjusting step sizes. 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. double le-5 startTime The start time of the simulation. This is only applicable when CT is the top level domain Otherwise, the CT director follows the time of its executive director. double 0.0 stopTime The stop time of the simulation. This is only applicable when CT is the top level domain. Otherwise, the CT director follows the time of its executive director. double 1.0 timeResolution This controls the comparison of time. Since time in the CT domain is a double precision real 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. double le-10 valueResolution This is used in (implicit) fixed-point iterations. If in two successive iterations the difference of the states is within this resolution, then the integration step is called converged, and the fixed point is considered reached. double le-6 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 Name Description Type Default Value ODESolver The fully qualified class name for the ODE solver class. string "ptolemy.domains.ct kernel. solver.ForwardEulerSolver" breakpointODESolver The fully qualified class name for the breakpoint ODE solver class. string "ptolemy.domains.ct.kernel.solver.DerivativeResolver" 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 processed. 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 Description maxRunAheadLength The maximum length of time for the CT subsystem to run ahead of the global time. Default Value Type 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 system 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 control queries from the outer domain to the inner domain. This director extends CTMultiSolverDirector, but has no additional parameters. A major difference between this director and the CTMixedSignalDirector is that this director does not support rollback. In fact, when a CT subsystem is embedded in a continuous-time environment, rollback is not necessary. 14.5 Interacting with Other Domains The CT domain can interact with other domains in Ptolemy II. In particular, we consider interaction among the CT domain, the discrete event (DE) domain and the finite state machine (FSM) domain. Following circuit design communities, we call a composition of CT and DE a mixed-signal model; following control and computation communities, we call a composition of CT and FSM a hybrid system model. There are two ways to put CT and DE models together, depending on the containment relation. In either case, event generators and waveform generators are used to convert the two types of signals. Figure 14.4 shows a DE component wrapped by an event generator and a waveform generator. From the input/output point of view, it is a continuous time component. Figure 14.5 shows a CT subsystem wrapped by a waveform generator and an event generator. From the input/output point of view, it is a discrete event component. Notice that event generators and waveform generators always stay in the CT domain. 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. 14.6 CT Domain Demos Here are some demos in the CT domain showing how this domain works and the interaction with other domains. 14.6.1 Lorenz System The Lorenz System (see, for example, pp. 213-214 in [22]) is a famous nonlinear dynamic system that shows chaotic attractors. The system is given by: any CT director f\s r Event Generator T! T TTT, DE Waveform Generator FIGURE 14.4. Embedding a DE component in a CT system. CTMixedSignalDirector 1L Waveform Generator \ A*M) I - Event Generator g(*.u) FIGURE 14.5. Embedding a CT component in a DE system. fTMnIti« otverC —--^HSDirector A feX /W\ C Nw CTEmbeddedDiractor CTEmbeddedDirector FIGURE 14.6. Hybrid system modeling. 274 DEDirector 1_I_ (24) Xj = a(x2-Xj) Xj ^A* X'ijX] X*2 X-j Xi * X'j — D * X-> 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 initial conditions of the state variables are all 1.0. The default value of the parameters are: CT = l,X = 25, b = 2.0. FIGURE 14.7. Block diagram for the Lorenz system. ^Applet Viewer: ptolemy.do.mi ?;**«: Stop Time: 50.0 Siqma: 10.0 Lambda: 25.0 6: 2.0 FIGURE 14.8. The simulation result of the Lorenz system. 275 14.6.2 Microaccelerometor with Digital Feedback. Microaccelerometors are MEMS devices that use beams, gaps, and electrostatics to measure acceleration. 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 digital 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 converted 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 I vs FIGURE 14.9. Micro-accelerator with digital feedback DEDirector CTMixedSignalDirector K(l) Integrator Sampler -o*Z«roOrderHoki accumulator FIGURE 14.10. Block diagram for the micro-accelerator system. 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. 14.6.3 Sticky Point Masses System This sticky point mass demo shows a simple hybrid system. As shown in figure 14.12, there are two point masses on a frictionless table with two springs attaching them to fixed walls. Given initial positions other than the equilibrium points, the point masses oscillate. The distance between the two walls are close enough that the two point masses may collide. The point masses are sticky, in the way so that when they collide, they will sticky together and become one point mass with two springs attached to it. We also assume that the stickiness decays after the collision, such that eventually the I.M'H4JIH,ll^-»'INII^'llfflmff'»ntf1-£ Jjaijsi Stop Stop Time: 15.0 Sample Rate: 0.02 Feedback Gain: -20.0 ÜHMI 1.0 0.5 " 0.5 1.0 . ill! 1 1 11 IWUiiil execution finished. FIGURE 14.11. Execution result of the microaccelerometor system. FIGURE 14.12. Sticky point masses system 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. FIGURE 14.13. Modeling sticky point masses. MlilMIWMIIWHiiHliHIJfHiilfHSIA«WWiiliV«t!tia8Ba -JB1.51 AppUt Stickiness Decay: i-1 0 SSIgg Sticky Mum 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. 14.7.1 ct.kernel.util package The ct.kernel.util package provides a basic data structure — Total lyOrderedSet, which is used to store breakpoints. The UML for this package is shown in figure 14.16. A totally ordered set is a set (i.e. no duplicated elements) in which the elements are totally comparable. This data structure is used to store breakpoints since breakpoints are processed in their chronological order. 14.7.2 ct.kernel package The ctkernel package is the key package of the CT domain. It provided interfaces to classify actors. These interfaces are used by the scheduler to generate schedules. The classes, including the CTBaselntegrator class and the ODESolver class, are shown in figure 14.17. Here, we use the del-> ct.kernel ct lib Integrator CTPeriodicalSampler ThresholdMonitor ZeroCrossingDetector ZeroOrderHold BreakpointODESolver CTBaselntegrator CTCompositeActor CTDirector CTDynamicActor CTEmbeddedDirector CTEventGenerator CTMixedSignalDirector CTMultiSolverDirector CTScheduler CTSingleSolverDirector CTStatefulActor CTStepSizeContmlActor CTTransparentDirector CTWavefbrmGenerator NumericalNonconvergeException ODESolver FuzzyDoubleComparator TotallyOrderedSet ct.gui CTApplet ctkernel.solver Backward EulerSolver DerivativeResolver ExplicitRK23Solver FixedStepSizeSolver ImpulseBESolver TrapezoldalRulSolver FIGURE 14.15. The packages in the CT domain. CTDirector I | TotallyOrderedSet «Interface» Comparator .comparator: Comparator ■ set: LinkedList +TotallyOrderedSet(comp: Comparator) +at(index: int) +clear() +contains(obj: Object): boolean +elementList(): List +first(): Object +getComparator(): Comparator +indexOf(obj: Object): int +insert(obj: Object) +isEmpty(): boolean +removeAIILessThan(o: Object) removeAt(index: int) removeFirstO +size(): int FIGURE 14.16. UML for ct.kernel.util package. 279 +compare(first: Object, second : Object): boolean 7T FuzzyDoubleComparator threshold: double FuzzyDoubleComparatorO +FuzzyDoubleComparator(th: double) +getThreshold(): double +setThreshold(threshold : double) Mailbox I TypedAtomicActor] I «Interface» CTEvwitGinantor «Interface» CTStotrfulAclor +hasCurrentEvent(): boolean +emitCurrentEvents() +goToMarkedState() ♦markStatesQ «Interface» CTStapS'atControlActor T. +CTReceiver() +CTReceiver(container: IQPort) «Interface» CTWtveformGantntor «Interface» CTDynMmieAetor +emitTentativeOutput() CTR*c*rv*r +isThisStepAccurate(): boolean +predictedStepSize(): double +refinedStepSize(): double TypedCompositeActor +consumeCurrentEvents() A CTBaselntegrator CTCompositoActor +lnitialState: Parameter +input: TypedlOPort +output: TypedlOPort <_auxVariables: doublet] ■_history: double!.]]] -JnitState: double state : double tentativeDerivative: double tentativeState: double +CTBaselntegrator() +CTBaselntegrator(w: Workspace) +CTBaselntegrator(ca : TypedCompositeActor, name : String) +getAuxVanables(): doublet] getDerivativef): double +getHistory(index: int): double]] getHistoryCapacityO: int +getlnitialState(): double getStatef): double +getTentativeState(): double +setAuxVariables(index : int, value : double) +setHistoryCapacity(cap: int) +setTentativeDerivative(value: double) +setTentativeState(value: double) delegate !lnvalidSUt»Exe»Dtion| +CTCompositeActor() +CTCompositeActor(ws : Workspace) +CTCompositeActor(ca : TypedCompositeActor, nm : String) NarrwdObj CTDiractor ODESohnr container: Director - round: int +ODESolver() +ODESolver(name: String) +ODESolver(w : Workspace, name : String) +getRound() +getHistoryCapacityRequirement(t: int *getlnlegratorAuxVariableCounl(): int +incrRound() *integratorFire(integ: CTBaselntegrator) +integratorisAccurate0nteg: CTBaselntegrator): boolean +integratorPredictedStepSize(integ: CTBaselntegrator): double +resetRound() *reso/veStates(): boolean JL NunwricalNonconvergeException +NurnericalNonconvergeException(detail: String) +NumericalNonconvergeException(obj: NamedObj, detail: String) +NumericalNonconvergeException(obj1 : NamedObj, obj2: NamedObj, detail: String) FIGURE 14.17. UML for ct.kernel package, actor related classes. 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 CTScheduler 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 implement new directors or ODE solvers. In general, simulating a continuous-time system (3)-(5) by a time-marching ODE solver involves CTDJractor F/xadSMpSrzeSo/ver ■FixedStepSolverO ■FixedStepSolverjn : String) ODESolver ExpllcKRK23Solver TrapezoidalRuleSolver _S : double _E: double order: int ■ExplicitRK23Solver() -ExplicilRK23Solver(ws : Workspace) BackwardEulerSolver +TrapezoidalRuleSolver() +TrapezoidalRuleSotver(ws : Workspace) #JsConverged(): boolean #_setConvergenoe(value: boolean): void *_voteForConvergence(vote : boolean): void I «inienace» . |Br*akpolntODESolver. ForwardEulerSolver -ForwardEulerSolverO ■ForwardEulerSolver(ws : Workspace) +BackwardEulerSolver() +BackwardEulerSolver(ws: Workspace) #_isConverged(): boolean *_setConvergence(value: boolean): void #_voteForConvergence(vote : boolean): void I ImpulseBESolver DarrVatrVaftMO/var +lmpulseBESotver() +lmpulseBESolver(ws : Workspace) +DerivattveResolver() +DerivativeResolver{w: Workspace) FIGURE 14.19. UML for ct.kernel.solver package. 281 Scheduler StaticSchedulingDirectorJ container containee 1..1 £" CTDIrector +errorTolerance: Parameter +initStepSize: Parameter +maxlteration: Parameter maxStepSize: Parameter +minStepSize: Parameter +startTime: Parameter +stopTime: Parameter +timeResolution: Parameter +valueResolution: Parameter .breakpoints: TotallyOrderedSet -_currentSolver: ODESolver -_currentStepSize: double -_suggestedNextStepSize: double CTScheduler ODESolver +CTDirector() +CTDirector(ws: Workspace) +CTDirector(ca: CompositeActor, nm : String) +canBelnsideDirector(): boolean W+cabBeTopLevelDirector(): boolean ■getBreakPoints(): TotallyOrderedSet +getCurrentODESolver(): ODESolver +getCurrentStepSize(): double +getlnitialStepSize(): double +getErrorTolerance(): double >getMaxlterations(): int fgetMaxStepSizef): double +getMinStepSize(): double +getStartTime(): double +getStopTime(): double +getSuggestedNextStepSize(): double +getTimeResolution(): double +getValueResolution(): double +isBreakpointlteration(): boolean +produceOutput(): void +setCurrentStepSize(step: double) +setSuggestedNextStepSize(nextstep: double) +updateStates(): void +CTScheduler() +CTScheduler(w: Workspace) +arithmaticActorlJst(): List •KiynamicActorListO: List +eventGeneratorList(): List +outputSSCActorList(): List +scheduledDynamicActorList(): List +scheduledOutputActorList(): List +scheduledStateTransitionActorList(): List +sinkActorList(): List ■stateTransitionSSCActorListO: List +statefulActorList(): List +waveformGeneratorList(): List CTMultiSolverDirector +breakpointODESolver: Parameter +ODESolver: Parameter +CTMultiSolverDiredor() +CTMultiSolverDirector(ws: Workspace) +CTMultiSolverDirector(ca: CompositeActor, nm : String) +getBreakpointSolver(): ODESolver +getODESolver(): ODESolver A «Interface» CTTransparerrtDirector CTMixedSignalDi rector +isThisStepAccurate(): boolean +predictedStepSize(): double +reHnedStepSize() •' douWe _L CTEmbeddedDi rector -_outsideStepSize: double outsideTime: double +CTEmbeddedDirector() +CTEmbeddedDirector(ws: Workspace) +CTEmbeddedDirector(ca: CompositeActor, nm : String) ■HunAheadLength: Parameter inEventPhase: boolean -JterationEndTime: double knownGoodTime: double outsideTime: double +CTMixedSignalDirector() +CTMixedSignalDirector(ws: Workspace) +CTMixedSignalDirector(ca: CompositeActor, nm : String) +getlterationEndTime(): double +getOutsideTime(): double #_catchup{) #_markStates() #_rollback() FIGURE 14.18. UML for ct.kernel package, director related classes. 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 unsuccessful 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 implemented 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 produce 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 \dt > FIGURE 14.20. A chain of integrators. 283 \dt 2 ► 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. 14.7.4 Controlling Step Sizes Choosing the right time points to approximate a continuous time system behavior is one of the major tasks of simulation. There are three factors that may impact the choice of the step size. • Error control. For all integration algorithms, the local error at time /„ is defined as a vector norm (say, the 2-norm) of the difference between the actual solution x(tn) and the approximation xt calculated by the integration method, given that the last step is accurate. That is, assuming x l , = *('»-!) then £<= K-*('«)«■ • • (25) It can be shown that by carefully choosing the parameters in the integration algorithms, the local error is approximately of the p-th order of the step size, where p, an integer closely related to the number of/function evaluations in one integration step, is called the order of the integration algorithm, i.e. Et ~ 0((tn - tn_,)p). Therefore, in order to achieve an accurate solution, the step size should be chosen to be small. But on the other hand, small step sizes means long simulation time. In general, the choice of step size reflects the trade-off between speed and accuracy of a simulation. Convergence. The local contraction mapping theorem (Theorem 2 in Appendix G) shows that for implicit ODE solvers, in order to find the fixed point at tn, the map F;( ) in (15) must be a (local) contraction map, and the initial guess must be within an e ball (the contraction radius) of the solution. It can be shown that F,( ) can be made contractive if the step size is small enough. (The choice of the step size is closely related to the Lipschitz constant). So the general approach for resolving the fixed point is that if the iterating function F,( ) does not converge at one step size, then reduce the step size by half and try again. Discontinuity. At discontinuous points, the derivatives of the signals are not continuous, so the integration formula is not applicable. That means the discontinuous points can not be crossed by one integration step. In particular, suppose the current time is t and the intended next time point is t+h. If there is a discontinuous point at t + 8, where 8 < h, then the next step size should be reduced to / + 8. For a predictable breakpoint, the director can adjust the step size accordingly before starting an integration step. However for an unpredictable breakpoint, which is reported "missed" after an integration step, the director should be able to discard its last step and restart with a smaller step size to locate the actual discontinuous point. Notice that convergence and accuracy concerns only apply to some ODE solvers. For example, explicit algorithms do not have the convergence problem, and fixed step size algorithms do not have the error control capability. On the other hand, discontinuity control is a generic feature that is independent on the choice of ODE solvers. 284 14.7.5 Mixed-Signal Execution DE inside CT. Since time advances monotonically in CT and events are generated chronologically, the DE component receives input events monotonically in time. In addition, a composition of causal DE components 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 semantically 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. 14.7.6 Hybrid System Execution Although FSM is an untimed model, its composition with a timed model requires it to transfer the notion of time from its external model to its internal model. During continuous evolution, the system is simulated as a CT system where the FSM is replaced by the continuous component refining the current FSM state. After each time point of CT simulation, the triggers on the transitions starting from the current FSM state are evaluated. If a trigger is enabled, the FSM makes the corresponding transition. The continuous dynamics of the destination state is initialized by the actions on the transition. The simulation continues with the transition time treated as a breakpoint. 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) x(t0) = x0 . (27) 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 is the set of non-negative real numbers, such that V/ e 5v, V£, £ e 51 , V« e 5v ll/lt",')-/(£, u,t)\\ 5{" such that, (29) V('o) = *o 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| 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 iteration, therefore, processes all events on the event queue with the smallest time stamp. 15.1.4 Getting a Model Started Before one of the iterations described above can be run, there have to be initial events in the global event queue. Actors may produce initial pure events or regular output events in their initialize() method. Thus, to get a model started, at least one actor must produce events. All the domain-polymorphic timed sources described in the Actor Libraries chapter produce pure events, so these can be used in DE. We can define the start time to be the smallest time stamp of these initial events. 15.1.5 Pure Events at the Current Time An actor calls fireAt() to schedule a pure event. The pure event is a request to the scheduler to fire the actor sometime in the future. However, the actor may choose to call fireAt() with the time argument equal to the current time. In fact, the preferred method for domain-polymorphic source actors to get started is to have code like the following in their initialize() method: Director director = getDirector0; director.fireAt(this, director.getCurrentTime()); 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. 15.1.6 Stopping Execution • • Execution stops when one of these conditions become true: The current time reaches the stop time, set by calling the setStopTime() method of the DE director. The global event queue becomes empty. Events at the stop time are processed before stopping the model execution. The execution ends by calling 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. 15.2 Overview of The Software Architecture The UML static structure diagram for the DE kernel package is shown in figure 15.4. For model builders, the important classes are DEDirector, DEActor and DEIOPort. At the heart of DEDirector is a global event queue that sorts events according to their time stamps and priorities. The DEDirector uses an efficient implementation of the global event queue, a calendar queue data 290 DEDirector -& +binCountFactor: Parameter +isCQAdaptive: Parameter +minBinCount: Parameter +stopTime: Parameter +stopWhenQueuelsEmpty: Parameter •_deadActors: Set eventQueue: DEEventQueue -^microstep; int +DEDirector() +DEDirector(w: Workspace) +DEDirector{c: CompositeActor, name: String) +disabteActor(actor: Actor) +getEventQueue{): DEEventQueue +getStartTime(): double +getStopTime(): double +setStopTime(t}me: double) +stopWhenQueuelsEmpty(f1ag : boolean) +fire{) +fireAt(a: Actor, time: double) +getCurrentTimeO: double +getNextlterationTime(): double! +initialize() +invalidateSchedule() +newRecerverO ■postfire() «Interface» Debuggable *getO: Token ■getContainerO: lOPort +hasRoomO: boolean +hasToken(): boolean *put(t: Token) +setContainer(port: IOPort)\ +prefireO +preinitializeO ♦transfer! nputs(p: lOPort) +transferOutputs(p: lOPort) +wrapup() 0..n «Interface» DEEvmntQueua DE Receiver +clearO: void +get(): DEEvent +isEmpty(): boolean +put(event: DEEvent) : vote +takeQ: DEEvent .container: lOPort .delay: double depth: int -Jokens: LinkedList O- +DEReceiver{) +DEReceiver(contatner: lOPort) +setpelay(delay: double) r DECQEventQueue Comparable -_cQueue: CalendarQueue +DECQEventQueueO +DECQEventQueue(minBinCount; int, binCountFactor: int, isAdaptive: boolean) DECQEventQueue. DECQComparator binWidth: DEEvent zeroReferenoe: DEEvent -_actor: Actor -_depth: int -_microstep: int -_receiver: DEReceiver .timeStamp: double .token: Token +DEEvent(rc: DEReceiver, tk: Token, tStamp: double, microstep: int, depth: int) +DEEvent(actor: Actor, timeStamp: double, microstep: int, depth: int) +actorO: Actor +depth(): int +isSimultaneousWith(ev: DEEvent) +microstepO: int +receiver(): DEReceiver +timeStarnpO: double +token(): Token «Interface» Runnabto TypedlOPort ■mnO |+broadcast(t: Token) r*-send(channel: int, t: Token)! n DEThreadActor ■_delayToSet: HashSet .thread: PtolemyThread -jsWaiting: boolean +DEThreadActor(container: TypedCompositeActor, name: String) +waitForNewlnputs() *waitForNewlnputs(ports: IQPortfJ) +DEIOPortO +DEIOPort(container: CompositeEntity, name: String) +DEIOPort(c: CompositeEntity, name: String, isinput: boolean, isoutput: boolean) +delayTo(output: lOPort) +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 operations 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 calendar 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 optimal for all models. For extensibility, alternative implementations of the global event queue can be realized 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 calendar 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 annotations 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 refiring. 15.3 The DE Actor Library The DE domain has a small library of actors in the ptolemy.domains.de.lib package, shown in figure 15.5. These actors are particularly characterized by implementing both the TimedActor and SequenceActor interfaces. These actors use the current model time, and in addition, assume they are dealing with sequences of discrete events. Some of them use domain-specific infrastructure, such as the convenience class DEActor and the base class DETransformer. The DETransformer class provides in input and output port that are instances of DEIOPort. The Delay and Server actors use facilities of these ports to influence the firing priorities. The Merge actor merges events sequences in chronological order. 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 invalidateScheduleQ 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 constructs 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 manager executes the request after the current iteration completes. Thus, the change will always be executed between non-equal time stamps, since an iteration consists of processing all events at the current lib «Interface» TimedActor iTypedAtomicActorj -5 «Interface» SequenceActor "A" WaitingTime DETransformer ♦output: TypedlOPort(DoubleToken) +waitee: TypedlOPortfToken) ♦waiter: TypedlOPortfToken) wailing: Vector ♦input: DEIOPort ♦output: DEIOPort Delay Server ♦time: Parameter (DoubleToken) Sampler Merge ♦newServiceTime: DEIOPort(Doub)eToken) ♦trigger: TypedlOPortfToken) ♦serviceTime: Parameter(DoubleToken) -Jastlnputs: TokenfJ -_nextTimeFree: double FIGURE 15.5. The library of DE-specific actors. clock merge clock recorder i—► clock2 tifter before FIGURE 15.6. Topology before and after mutation for the example in figure 15.7. 293 recorder time stamp. Actors that are added in the change request are automatically initialized. Note, however, one subtlety. 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 public class Mutate { public Manager manager; private private private private Recorder _rec; Clock _clock; TypedCompositeActor _top; DEDirector _director; public Mutate 0 throws IllegalActionException, NameDuplicationException _top = new TypedCompositeActor!); _top.setName("top"); manager = new Manager(); _director = new DEDirector0; _top.setDirector(_director); _top.setManager(_manager); _clock = new Clock(_top, "clock"); _clock.values.setExpression(" [1.01"); _clock.offsets.setExpression(" [0.0]") ,_clock.period.setExpress ion ("1.0"); _rec = new Recorder(_top, "recorder"); _top.connect(_clock.output, _rec.input); } public void insertclockt) throws ChangeFailedException { // Create an anonymous inner class ChangeRequest change = new ChangeRequest(_top, "test2") { public void executed throws ChangeFailedException { try { _clock.output.unlinkAll(); _rec.input.unlinkAll(); Clock clock2 = new Clock(_top, "clock2"); clock2.values.setExpression("[2.0]") ; clock2.offsets.setExpression!"[0.5]") ; clock2.period.setExpression("2.0"); Merge merge = new Merge(_top, "merge"); _top.connect(_clock.output, merge.input); _top.connect(clock2.output, merge.input); _top.connect(merge.output, _rec.input); // Any pre-existing input port whose connections // are modified needs to have this method called. _rec.input.createReceivers0 ; ) catch (IllegalActionException ex) { throw new ChangeFailedException(this, ex); } catch (NameDuplicationException ex) { throw new ChangeFailedException(this, ex); } } )> manager.requestChange(change); 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 create 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. 15.5 Writing DE Actors It is very common in DE modeling to include custom-built actors. No pre-defined actor library seems to prove sufficient for all applications. For the most part, writing actors for the DE domain is no different than writing actors for any other domain. Some actors, however, need to exercise particular control over time stamps and actor priorities. Such actors use instances of DEIOPort rather than TypedlOPort. The first section below gives general guidelines for writing DE actors and domain-polymorphic actors that work in DE. The second section explains in detail the priorities, and in particular, how to write actors that declare delays. The final section discusses actors that operate as a Java thread. 15.5.1 General Guidelines • • • • • The points to keep in mind are: When an actor fires, not all ports have tokens, and some ports may have more than one token. The time stamps of the events that contained these tokens are no longer explicitly available. The current model time is assumed to be the time stamp of the events. If the actor leaves unconsumed tokens on its input ports, then it will be iterated again before model time is advanced. This ensures that the current model time is in fact the time stamp of the input events. However, occasionally, an actor will want to leave unconsumed tokens on its input ports, and not be fired again until there is some other new event to be processed. To get this behavior, it should return false from prefire(). This indicates to the DE director that it does not wish to be iterated. If the actor returns/a/se from postfire(), then the director will not fire that actor again. Events that are destined for that actor are discarded. When an actor produces an output token, the time stamp for the output event is taken to be the current model time. If the actor wishes to produce an event at a future model time, one way to accomplish this is to call the director's fireAt() method to schedule a future firing, and then to produce the token at that time. A second way to accomplish this is to use instances of DEIOPort and use the overloaded send() or broadcast) methods that take a time delay argument. The DEIOPort class (see figure 15.4) can produce events in the future, but there is an important subtlety with using these methods. Once an event has been produced, it cannot be retracted. In par295 • 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 convention. 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, private int _count; create a shadow variable private int _countShadow; Then write the methods as follows: public void fireO { _countShadow = _count; ... perform some computation that may modify _countShadow ... } public boolean postfireO { _count = _countShadow; return super.postfire(); } This ensures that the state is updated only in postfire(). In a similar fashion, delayed outputs (produced by either mechanism) should be produced only in the postfire() method, since a delayed outputs are persistent state. Thus, fireAt() should be called in postfire() only, as should the overloaded send() and broadcast) of DEIOPort. 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 properties 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 service 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 import import import import import import import import ptolemy.actor.TypedAtomicActor; ptolemy.domains.de.kernel.DEIOPort; ptolemy.data.DoubleToken; ptolemy.data.Token; ptolemy.data.expr.Parameter; ptolemy.actor.TypedCompositeActor; ptolemy.kernel.util.IllegalActionException; ptolemy.kernel.util.NameDuplicationException; ptolemy.kerne1.ut i1.Workspace; public class SimpleDelay extends TypedAtomicActor { public SimpleDelay(TypedCompositeActor container. String name) throws NameDuplicationException, IllegalActionException super(container, name); input = new DEIOPort(this, "input", true, false); output = new DEIOPort(this, "output", false, true); delay = new Parameter(this, "delay", new DoubleToken(1.0)); delay.setTypeEquals(DoubleToken.class); input.delayTo(output); { } public Parameter delay; public DEIOPort input; public DEIOPort output; private Token _currentInput; public Object clone(Workspace ws) throws CloneNotSupportedException SimpleDelay newobj = (SimpleDelay)super.clone(ws); newobj.delay = (Parameter)newobj.getAttribute("delay"); newobj.input = (DEIOPort)newobj.getPort("input") ;: newobj.output = (DEIOPort)newobj.getPort("output"); return newobj; } public void fireO throws IllegalActionException { _currentlnput = input.get (0); public boolean postfireO throws IllegalActionException { output.sendIO, _currentlnput, ((DoubleToken) delay. getTokenO) .doubleValueO ) ; return super.postfire(); } FIGURE 15.8. A domain-specific actor in DE. 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 executing, 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 newServiceTime 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 essential 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 associated 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. 15.5.3 Thread Actors In some cases, it is useful to describe an actor as a thread that waits for input tokens on its input ports. The thread suspends while waiting for input tokens and is resumed when some or all of its input ports have input tokens. While this description is functionally equivalent to the standard description explained above, it leverages on the Java multi-threading infrastructure to save the state information. Consider the code for the ABRecognizer actor shown in figure 15.10. The two code listings implement two actors with equivalent behavior. The left one implements it as a threaded actor, while the right one implements it as a standard actor. We will from now on refer to the left one as the threaded description and the right one as the standard description. In both description, the actor has two input ports, inportA and inportB, and one output port, outport. The behavior is as follows. 298 package ptolemy.domains.de.lib; import statements ... public class Server extends DETransformer { public DEIOPort newServiceTime; public Parameter serviceTime; private Token _currentlnput; private double _nextTimeFree = Double.NEGATIVE_INFINITY; public Server(TypedCompositeActor container, String name) throws NameDuplicationException, IllegalActionException { super(container, name); serviceTime = new Parameter(this, "serviceTime", new DoubleToken(1.0)); serviceTime.setTypeEquals(DoubleToken.class); newServiceTime = new DEIOPort(this, "newServiceTime", true, false); newServiceTime.setTypeEquals(DoubleToken.class); output.setTypeAtLeast(input); input.delayTo(output); newServiceTime.dalayTo(output); ... attributeChangedCI, clone 0 methods ... public void initialized throws IllegalActionException { super.initialize)); _nextTimeFree = Double.NEGATIVE INFINITY; } public boolean prefireO throws IllegalActionException { if (getCurrentTimet) >= _nextTimeFree) { return true; } else { // Schedule a firing if there is a pending token so it can be served, if (input.hasToken(O)) { fireAt(_nextTimePree); } return false; } } public void fireO throws IllegalActionException { if (newServiceTime.getWidthO > 0 && newServiceTime.hasToken(O)) { DoubleToken time = (DoubleToken)(newServiceTime.get(0)); serviceTime.setToken(time) ,- } if (input.getWidthO > 0 && input.hasToken(0)) { _currentInput = input.get(0); double delay = ((DoubleToken)serviceTime.getTokenO).doubleValue(); _nextTimeFree = getCurrentTime() + delay; } else { _currentlnput = null; } } public boolean postfireO throws IllegalActionException { if (_currentInput != null) { double delay = ((DoubleToken)serviceTime.getTokenO).doubleValue() ; output.send(0, _currentInput, delay); 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 ( StringToken msg = new StringToken("Seen AB"); public class ABRecognizer extends DEActor ( StringToken msg = new StringToken("Seen AB" // We need an explicit state variable in // this case, int state = 0; II the run method is invoked when the thread // is started, public void runt) { while (true) { waitForNewInputs 0; if (inportA.hasToken(0)) { IOPort[] nextinport = (inportB}; waitForNewInputs(nextinport); outport.broadcast(msg); public void fireO { switch (state) { case 0: if (inportA.hasToken(O)) 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 constructs, such as preemption, parallel execution, etc. It might also be interesting to explore new concurrency semantics similar to the threaded DE, but without the 'forced' serialization. 15.6 Composing DE with Other Domains One of the major concepts in Ptolemy II is modeling heterogeneous systems through the use of hierarchical heterogeneity. Actors on the same level of hierarchy obey the same set of semantics rules. Inside some of these actors may be another domain with a different model of computation. This mechanism is supported through the use of opaque composite actors. An example is shown in figure 15.12. The outermost domain is DE and it contains seven actors, two of them are opaque and composite. The opaque composite actors contain subsystems, which in this case are in the DE and CT domains. 15.6.1 DE inside Another Domain The DE subsystem completes one iteration whenever the opaque composite actor is fired by the outer domain. One of the complications in mixing domains is in the synchronization of time. Denote the current time of the DE subsystem by timer and the current time of the outer domain by touter An iteration of the DE subsystem is similar to an iteration of a top-level DE model, except that prior to the iteration tokens are transferred from the ports of the opaque composite actors into the ports of the contained DE subsystem, and after the end of the iteration, the director requesting a refire at the smallest time stamp in the event queue of the DE subsystem. The first of these is done in the transferlnputs() method of the DE director. This method is extended from its default implementation in the Director class. The implementation in the DEDirector class advances the current time of the DE subsystem to the current time of the outer domain, then calls super.transferlnputs(). It is done in order to correctly associate tokens seen at the input ports of the opaque composite actor, if any, with events at the current time of the outer domain, touten and put these events into the global event queue. This mechanism is, in fact, how the DE subsystem synchronize its current time, timen with the current time of the outer domain, /0„ter(Recall that the DE director 301 advances time by looking at the smallest time stamp in the event queue of the DE subsystem). Specifically, 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 public void run() { try { while (true) ( // In initial state.. waitForNewInputs(); if (R.hasToken(O)) { // Resetting.. continue; 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) ; FIGURE 15.11. The run() method of the ABRO actor. 302 f outer because we always request a retire according to that time stamp. 15.6.2 Another Domain inside DE Due to its nature, the opaque composite actor is opaque and therefore, as far as the DE Director is concerned, behaves exactly like a domain polymorphic actor. Recall that domain polymorphic actors are treated as functions with zero delay in computation time. To produce events in the future, domain polymorphic actors request a refire from the DE director and then produce the events when it is refired. r Discrete Event ~V, •a *s~x^ ~-» H Xl Edit parameters for SampleDelay \^/ inltialOutputs: Cö}" Commit Add Remove Edit Styles Cancel 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 consumes 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 executed, 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 Mp | Wfg solution exists for the balance equations. Graph is not consistent under the SDF domain NO Dismiss Display Stack Trace FIGURE 16.3. An SDF model with inconsistent rates. SDF DownSample SequencePlotter »^—HtsSequencePlotter3 Sinewave FIGURE 16.4. Figure 16.3 modified to have consistent rates. 307 and how they are used for scheduling, see section 16.3.1. 16.2.3 How many iterations? One final issue when using the SDF domain concerns the value of the iterations parameter of the SDF director. In homogenous models one token is usually produced for every iteration. However, when token rates other than one are used, more than one interesting output value may be created for each iteration. For example, consider figure 16.5 which contains a model that plots the Fast Fourier Transform of the input signal. The important thing to realize about this model is that the FFT actor declares that it consumes 256 tokens from its input port and produces 256 tokens from its output port, corresponding to an order 8 FFT. This means that only one iteration is necessary to produce all 256 values of the FFT. Contrast this with the model in figure 16.6. This model plots the individual values of the signal. Here 256 iterations are necessary to see the entire input signal, since only one output value is plotted in each iteration. 16.3 Properties of the SDF domain SDF is an untimed model of computation. All actors under SDF consume input tokens, perform their computation and produce outputs in one atomic operation. If an SDF model is embedded within a timed model, then the SDF model will behave as a zero-delay actor. In addition, SDF is a statically scheduled domain. The firing of a composite actor corresponds to a SDF Pulse SequencePlotter3 FFT FIGURE 16.5. A model that plots the Fast Fourier Transform of a signal. Only one iteration must be executed to plot all 256 values of the FFT, since the FFT actor produces and consumes 256 tokens each firing. SDF SequencePlotter3 Pulse 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 precalculated 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 ConsumptionRate 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 linearly dependent solutions, and the least positive integer solution for Firings is chosen as the firing vector, 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 dependencies 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 s~~ Al* \ «9-H A w H c _*B1 N ■—9B2 ) C2j FIGURE 16.7. An example SDF model. 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 properly 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. 16.3.2 Hierarchical Scheduling So far, we have assumed that the SDF graph is not hierarchical. The simplest way to schedule a hierarchical SDF model is flatten the model to remove the hierarchy, and then schedule the model as usual. This technique allows the most efficient schedule to be constructed for a model, and avoids certain composability problems when creating hierarchical models. In Ptolemy II, a model created using a transparent composite actor to define the hierarchy is scheduled in exactly this way. Ptolemy II also supports a stronger version of hierarchy, in the form of opaque composite actors. In this case, the hierarchical actor appears to be no different from the outside than an atomic actor with no 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 creating 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, especially 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 schedulers 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 description of the SDFScheduler class in section 16.4.2. 16.4 Software Architecture The SDF kernel package implements the SDF model of computation. The structure of the classes in this package is shown in figure 16.10. 16.4.1 SDF Director The SDFDirector class extends the StaticSchedulingDirector class. When an SDF director is created, it is automatically associated with an instance of the default scheduler class, SDFScheduler. This scheduler is intended to be relatively fast, but not designed to optimize for any particular performance goal. The SDF director does not currently restrict the schedulers that may be used with it. For more information about SDF schedulers, see section 16.4.2. The director has a parameter, iterations, which determines a limit on the number of times the director wishes to be fired1. After the director has been fired the given number of times, it will always return false in its postfire() method, indicating that it does not wish to be fired again. The iterations parameter must contain a non-negative integer value. The default value is an IntToken with value 0, indicating that there is no preset limit for the number of times the director will fire. Users will likely 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. 1. This parameter acts similarly to the Time-to-Stop parameter in Ptolemy Classic. 311 specify a non-zero value in the director of the toplevel composite actor as the number of toplevel iterations 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). SUtlcSchedulingDirectori !♦ _J 1'1 Ports may have these [ parameters, but are not required to. If the parameters are not present, then appropriate default values are assumed. Scheduler TypadAiomicAetofi i +getSchedule(): Schedule: |+schedule(): Enumeration; A TypadlOPort On SDFDirector tokenConsumptionRate : int; tokenlnitProduction; int tokenProductionRate : int iterations: int vectorizationFactor: int +SDFDirector() +SDFDirecton> : Workspace) +SDFDirector(c: CompositeEntity, name : String) uses parameters to schedule SDFSchedultr AmyFIFOQiMiM ■»INFINITE CAPACITY: int ♦DEFAULT CAPACITY : int ♦STARTING ARRAYSIZE : int ♦DEFAULT HISTORY CAPACITY : int -_container: Nameable queuearray: ObjectfJ MstoryList: LinkedList ArrayFIFOQueueO +ArrayFIFOQueue(size: int) +ArrayFIFOQueue(container: Nameable) +ArrayFIFOQueue(container: Nameable, size : int) +ArrayFIFOQueue(model: ArrayFIFOQueue) ♦elements!): CollectionEnumeration ♦getfoffset: int) ♦getCapacityO: int +getContainer(): Nameable ♦getHistoryCapadtyO: int ♦NstoryElementsO: CollectionEnumeration +historySize(): int ♦isEmptyO: boolean +isFull(): boolean +put(o: Object) +putArray(o: ObjectQ) ♦putArrayjarray: ObjectfJ, count: int) ♦setCapacity(capacity: int) ♦setHistoryCapacity(capacity: int) ♦size)): int +take(): Object takeArrayfo: ObjectfJ) +takeArray(o: object fj, count: int) +SDFScheduler() ♦SDFSchedulerfw: Workspace) +getFiringCount{entity: Entity): int ♦oetTokenConsumptionRatefp: lOPort): int ♦qetTokenlnitProductionfo ; IQPorl): int ►oetTokenProductionRatefp: IQPort) SDFIOPort +SDFIOPort(c : ComponentEntity, name ; String) +getTokenConsumptionRate(): int +getTokenlnitProduction(): int tgetTokenProductionRateO: int +setTokenConsumptionRate(rate: int) +setTokenlnitProduction(rate: int) +setTokenProductionRate(rate: int) 1.1 SDFReceiver 1.1 +SDFReceiver() +SDFReceiver(size : int) +SDFReceiver(container: lOPort) +SDFReceiver(container; lOPort, size : int) +elements(): Enumeration +get(offset: int): Token +getCapacity(): int -t-getHistoryCapadtyO : int +historyElements(): Enumeration +historySize(): int +setCapacity(capacity; int) +setHisto(yCapacity(capacity: int) +size(): int FIGURE 16.10. The static structure of the SDF kernel classes. 312 :+get(): Token |+getArray(lengtri: int): TokenfJ i+getContainer(): lOPort jthasRoomO : boolean i+hasRoom(count: int): boolean :+hasToken(): boolean |<-hasToken(count: int): boolean i+put(t; Token) ;+putArray(tokens ; Token fj, length : int): setContainerfport: lOPort) 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. 16.4.2 SDF Scheduler The basic SDFScheduler derives directly from the Scheduler class. This scheduler provides unlooped, sequential schedules suitable for use on a single processor. No attempt is made to optimize the schedule by minimizing data buffer sizes, minimizing the size of the schedule, or detecting parallelism to allow execution on multiple processors. We anticipate that more elaborate schedulers capable of these optimizations will be added in the future. The scheduling algorithm is based on the simple multirate algorithm in [48]. Currently, only single processor schedules are supported. The multirate scheduling algorithm relies on the actors in the system to declare the data rates of each port. The data rates of ports are specified using three parameters on each port named tokenConsumptionRate, tokenProductionRate, and tokenlnitProduction. The production parameters are valid only for output ports, while the consumption parameter is valid only for input ports. If a parameter exists that is not valid for a given port, then the value of the parameter must be zero, or the scheduler will throw an exception. If a valid parameter is not specified when the scheduler runs, then default values of the parameters will be assumed, however the parameters are not then created1. After scheduling, the SDF scheduler will set the rate parameters on any external ports of the composite actor. This allows a containing actor, which may represent an SDF model, to properly schedule the contained model, as long as the contained model is scheduled first. To ensure this, the SDF director forces the creation of the schedule after initializing all the actors in the model. This mechanism is illustrated in the sequence diagram in figure 16.11. Disconnected graphs. SDF graphs should generally be connected. If an SDF graph is not connected, then there is some concurrency between the disconnected parts that is not captured by the SDF rate parameters. In such cases, another model of computation (such as process networks) should be used to explicitly specify the concurrency. As such, the current SDF scheduler disallows disconnected graphs, and will throw an exception if you attempt to schedule such a graph. However, sometimes it is useful to avoid introducing another model of computation, so it is possible that a future scheduler will allow disconnected graphs with a default notion of concurrency. Multiports. Notice that it is impossible to set a rate parameter on individual channels of a port. This is intentional, and all the channels of an actor are assumed to have the same rate. For example, when the AddSubtract actor fires under SDF, it will consume exactly one token from each channel of its input plus port, consume one token from each channel of its minus port, and produce one token the single channel of its output port. Notice that although the domain-polymorphic adder is written to be more general than this (it will consume up to one token on each channel of the input port), the SDF scheduler will ensure that there is always at least one token on each input port before the actor fires. Dangling ports. All channels of a port are required to be connected to a remote port under the SDF domain. A regular port that is not connected will always result in an exception being thrown by the 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. 16.4.3 SDF ports and receivers Unlike most domains, multirate SDF systems tend to produce and consume large blocks of tokens during each firing. Since there can be significant overhead in data transport for these large blocks, SDF receivers are optimized for sending and receiving a block of tokens en masse. The SDFReceiver class implements the Receiver interface. Instead of using the FIFOQueue class to store data, which is based on a linked list structure, SDF receivers use the ArrayFIFOQueue class, which is based on a circular buffer. This choice is much more appropriate for SDF, since the size of the buffer is bounded, and can be determined statically1. a:CompositeActor a.d:SDFDirector a.b:CompositeActor a.p:IOPort a.b.diSDFDirector s1:SDFScheduler a.b.p:IOPort s2:SDFScheduler initializeQ 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 creating 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 firing. 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. Adding an array of objects to an ArrayFIFOQueue is implemented using the java.lang.system.arraycopy method. This method is capable of safely removing certain checks required by the Java language. On most Java implementations, this is significantly faster than a hand coded loop for large arrays. However, depending on the Java implementation it could actually be slower for small arrays. The cost is usually negligible, but can be avoided when the size of the array is small and known when the actor is written. 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 network 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 message. 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 messages 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 message 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 typically 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 independently developed by Robin Milner in 1980 [59]. The communication semantics of CSP are identical to those of CCS. 17.2.1 Atomic Communication: Rendezvous Atomic communication is carried out via rendezvous and implies that the sending and receiving of a message occur simultaneously. During rendezvous both the sending and receiving processes block until the other side is ready to communicate; the act of sending and receiving is indistinguishable activities since one can not happen without the other. A real world analogy to rendezvous can be found in telephone communications (without answering machines). Both the caller and callee must be simultaneously present for a phone conversation to occur. Figure 17.1 shows the case where one process is ready to send before the other process is ready to receive. The communication of information in this way can be viewed as a distributed assignment statement. The sending process places some data in the message that it wants to send. The receiving process assigns the data in the message to a local variable. Of course, the receiving process may decide to ignore the contents of the message and only concern itself with the fact that a message arrived. 17.2.2 Choice: Nondeterministic Rendezvous Nondeterministic choice provides processes with the ability to randomly select between a set of possible atomic communications. We refer to this ability as nondeterministic rendezvous and herein lies much of the expressiveness of the CSP model of computation. The CSP domain implements nondeterministic rendezvous via guarded communication statements. A guarded communication statement has the form 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 communications. 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 statements: CIF and CDO. These are analogous to the if and while statements in most programming languages. 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 nondeterministically. Once the successful communication is carried out, the associated statements are executed 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 concurrently, and the choice of which one succeeds does not depend on its position in the construct. The notation "[]" 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 forever. 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 relative 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; } FIGURE 17.2. Example of how a CDO might be used in a 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 parallel programs. 17.2.5 Differences from Original CSP Model as Proposed by Hoare The model of computation used by the CSP domain differs from the original CSP [37] model in two ways. First, a notion of time has been added. The original proposal had no notion of time, although there have been several proposals for timed CSP [35]. Second, as mentioned in section 17.2.2, it is possible to use both send and receive in guarded communication statements. The original model only allowed receives to appear in these statements, though Hoare subsequently extended their scope to allow both communication primitives [38]. One final thing to note is that in much of the CSP literature, send is denoted using a "!", pronounced "bang", and receive is denoted using a "?", pronounced "query". This syntax was what was used in the original CSP paper by Hoare. For example, the languages Occam [14] and Lotos [21] both follow this syntax. In the CSP domain in Ptolemy II we use send and get, the choice of which is influenced by the desire to maintain uniformity of syntax across domains in Ptolemy II that use message passing. This supports the heterogeneity principle in Ptolemy II which enables the construction and interoperability of executable models that are built under a variety of models of computation. Similarly, the notation used in the CSP domain for conditional communication constructs differs from that commonly found in the CSP literature. 17.3 Example CSP Applications Several example applications have been developed which serve to illustrate the modeling capabilities of the CSP model of computation in Ptolemy II. Each demonstration incorporates several features of CSP and the general Ptolemy II framework. Below, four demonstrations have been selected that each emphasize particular semantic capabilities over others. The applications are described here, but not the code. See the directory $PTH/ptolemy/domains/csp/demo for the code. The first demonstration, dining philosophers, serves as a natural example of core CSP communication semantics. This demonstration models nondeterministic resource contention, e.g., five philosophers randomly accessing chopstick resources. Nondeterministic rendezvous serves as a natural modeling tool for this example. The second example, hardware bits contention, models deterministic resource contention in the context of time. As will be shown, the determinacy of this demonstration constrains the natural nondeterminacy of the CSP semantics and results in difficulties. Fortunately these difficulties can be smoothly circumvented by the timing model that has been integrated into the 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. 17.3.1 Dining Philosophers Nondeterministic Resource Contention. This implementation of the dining philosophers problem illustrates both time and conditional communication in the CSP domain. Five philosophers are seated at a table with a large bowl of food in the middle. Between each pair of philosophers is one chopstick, and to eat, a philosopher needs both the chopsticks beside him. Each philosopher spends his life in the following cycle: thinks for a while, gets hungry, picks up one of the chopsticks beside him, then the other, eats for a while and puts the chopsticks down on the table again. If a philosopher tries to grab a chopstick but it is already being used by another philosopher, then the philosopher waits until that chopstick becomes available. This implies that no neighboring philosophers can eat at the same time and at most two philosophers can eat at a time. The Dining Philosophers problem was first dreamt up by Edsger W. Dijkstra in 1965. It is a classic concurrent programming problem that illustrates the two basic properties of concurrent programming: Liveness. How can we design the program to avoid deadlock, where none of the philosophers can make progress because each is waiting for someone else to do something? Fairness. How can we design the program to avoid starvation, where one of the philosophers could make progress but does not because others always go first? 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. 17.3.2 Hardware Bus Contention Deterministic Resource Contention. This demonstration consists of a controller, N processors and a memory block. At randomly selected points in time, each processor requests permission from the controller to access the memory block. The processors each have priorities associated with them and in cases where there is a simultaneous memory access request, the controller grants permission to the processor with the highest priority. Due to the atomic nature of rendezvous, it is impossible for the controller to check priorities of incoming requests at the same time that requests are occurring. To overcome this difficulty, an alarm is employed. The alarm is started by the controller immediately following the first request for memory access at a given instant in time. It is awakened when a delay block occurs to indicate to the controller that no more memory requests will occur at the given point in time. Hence, the alarm uses CSP's notion of delay blocking to make deterministic an inherently nondeterministic activity. 17.3.3 Sieve of Eratosthenes Dynamic Topology. This example implements the sieve of Eratosthenes. This is an algorithm for generating a list of prime numbers, illustrated in figure 17.5. It originally consists of a source generating integers, and one sieve filtering out all multiples of two. When the end sieve sees a number that it cannot filter, it creates a new sieve to filter out all multiples ofthat number. Thus after the sieve filtering out multiples of two sees the number three, it creates a new sieve that filters out multiples of three. This then continues with the three sieve eventually creating a sieve to filter out all multiples of five, and so on. Thus after a while there will be a chain of sieves each filtering out a different prime number. If any number passes through all the sieves and reaches the end with no sieve waiting, it must be another prime and so a new sieve is created for it. This demo is an example of how changes to the topology can be made in the CSP domain. Each topology change here involves creating a new CSPSieve actor and connecting it to the end of the chain of sieves. 17.3.4 An M/M/l Queue Pause/Resume. The example in figure 17.6 illustrates a simple M/M/l queue. It has three actors, one representing the arrival of customers, one for the queue holding customers that have arrived and have Memory Controller Alarm Processor Processor Processor FIGURE 17.4. Processors contending for memory access 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 4 sieve(2) I i ll 2 —3——» )— 4 —4——» siev 5(3) ■l —5— 5 » —6——► —7——» sieve(5) —8-—> 7 » 11 » —9-—» 9 p — 10-—► — 11-—► — 12-—» 1l 11 » sieve(11) ii 13 » 13 i r i r 1 r i ' 1 r i ' FIGURE 17.5. Illustration of Sieve ofEratosthenes for obtaining first six primes. 324 * 1 r 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 Building CSP Applications For a model to have CSP semantics, it must have a CSPDirector controlling it. This ensures that the receivers in the ports are CSPReceivers, so that all communication of messages between processes is via rendezvous. Note that each actor in the CompositeActor under the control of the CSPDirector represents a separate process in the model. 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. 17.4.2 Conditional Communication Constructs In order to use the conditional communication constructs, an actor must be derived from CSPActor. There are three steps involved: 1) Create a ConditionalReceive or ConditionalSend branch for each guarded communication statement, depending on the communication. Pass each branch a unique integer identifier, starting from zero, when creating it. The identifiers only need to be unique within the scope ofthat CDO or CIF. 2) Pass the branches to the chooseBranch() method in CSPActor. This method evaluates the guards, and decides which branch gets to rendezvous, performs the rendezvous and returns the identification number of the branch that succeeded. If all of the guards were false, -1 is returned. 3) Execute the statements for the guarded communication that succeeded. 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 FIGURE 17.6. Actors involved in M/M/l demo. 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 considering 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. boolean continueCDO = true; while (continueCDO) { // step 1: ConditionalBranchH branches = new ConditionalBranch[#branchesRequired]; // Create a ConditionalReceive or a ConditionalSend for each branch // e.g. branchestO] = new ConditionalReceive((guard), input, 0, 0) ; // 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 } else if (result == -1) { // all guards were false so exit CDO. continueCDO = false; } else { // error ) FIGURE 17.7. Template for executing a CDO construct. 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 equivalent to waitForDeadlock() If no processes are delayed, it is also possible to set the model time by calling the method setCurrentTimeO 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. 17.5 The CSP Software Architecture 17.5.1 Class Structure In a CSP model, the director is an instance of CSPDirector. Since the model is controlled by a CSPDirector, all the receivers in the ports are CSPReceivers. The combination of the CSPDirector and boolean guard = false; boolean continueCDO = true; ConditionalBranch[] branches = new ConditionalBranch[2]; while (continueCDO) { // step 1 guard = (_size < depth); branches[0] = new ConditionalReceive(guard, input, 0, 0); guard = (_size > 0); branches[1] = new ConditionalSend(guard, output, 0, 1, _buffer[_readFrom]); // 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 diagram 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 controls/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 statements necessary for the conditional communication constructs. 17.5.2 Starting the model The director creates a thread for each actor under its control in its initialize() method. It also invokes the initialize() method on each actor at this time. The director starts the threads in its prefire() method, and detects and responds to deadlocks in its fire() method. The thread for each actor is an instance of ProcessThread, which invokes the prefire(), fire() and postfire() methods for the actor until it finishes or is terminated. It then invokes the wrapup() method and the thread dies. Figure 17.11 shows the code executed by the ProcessThread class. Note that it makes no assumption about the actor it is executing, so it can execute any domain-polymorphic actor as well as CSP domain-specific actors. In fact, any other domain actor that does not rely on the specifics of its parent domain can be executed in the CSP domain by the ProcessThread. 17.5.3 Detecting deadlocks: For deadlock detection, the director maintains three counts: director.initialize 0 => create a thread for each actor update count of active processes with the director call initialize!) on each actor director.prefire0 => start the process threads => calls actor.prefire() calls actor.fire() calls actor.postfiref) repeat. director.fire 0 => handle deadlocks until a real deadlock occurs. director.postfire 0 => return a boolean indicating if the execution of the model should continue for another iteration director. wrapupO => terminate all the processes => calls actor.wrapupO decrease the count of active processes with the director 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 -JntemalLock: Object _actorsBlocked: int _actorsDelayed : int _currentTime: double _delayedActofList: UnkedList jnutationsPending: bootean simulationUntimed: bootean ♦CSPActorO +CSPActor(ws: Workspace) +CSPActor(cont: CompositeActor, name : String) +chooseBranch(branches : CondttionalBranchQ): int +delay() +delay(derta: double) +terminateO *_continueO »_waitForOeadlockQ +CSPDirectorO +CSPDirector(name: String) +CSPDirector(name: String, ws : Workspace) +getCurrentTime(): double +setCurrentTime(newTime: double) +setUntimed(value: boolean) *_actorBlocked() #_actorDelayed(delta : double, actor: CSPActor) »_actorUnblockedQ 0..n Conditional Branchcontroller ■_blocked: boolean •_branchesActive: int ■JaranchesBlocked: int ■_branchesDelayed: int -_branchTrying: int •_intemalLock: Object -„successful Branch : int -_threadList: Linkedüst CSPReeeiver +ConditionalBranchController(container: Actor) +chooseBranch(branches: ConditionaJBranchfl): int +terminate() #_branchBlocked() #_branchFailed{branchNumber: int) #_branchSucceeded(branchNumber: int) #_branchUnblockedO #_isBranchFirst(branchNumber: int): boolean #_releaseFirst(branchNumber; int) ■_condrtionalReceiveWaiting : boolean -_conditlonalSendWarting: boolean ■_container: lOPort ■_getWaiting: bootean •_putWaiting: boolean -_otherParent: CSPActor „rendezvousComptete: bootean ■_simulationPau5ed: boolean -_simulationFinished: boolean - token: Token +CSPReceiverO +CSPReceiver(p: lOPort) +getO: Token +put(token: Token) +getContainer(): Nameable ■HiasRoomO: boolean +hasToken(): boolean +setContainen;parent: lOPort) +setFinish() +setPause(newValue: boolean) «LgetOtherParentO: CSPActor #JsConditionalReceiveWaiting(): boolean iHsCondrtionalSendWaitingO: boolean #_isGetWaitingO: boolean #_isPutWaitlngO: boolean #_setConditionalRecieve(v: boolean, parent: CSPActor) #_setConditionalSend(v: boolean, parent: CSPActor) iTcheckAndWaitO ConditionalBranch -_alive: boolean ■_branchNumber: int -jguard: boolean -_parerrt: CSPActor #_receiver: CSPReeeiver »token: Token +ConditionalBranch(guard: boolean, port : lOPort, branchID: int) +getlD(>: int ♦getGuardO: boolean ♦getParentO: CSPActor +getReceiver(): CSPReeeiver ♦getTokenO: Token -HsAliveO: boolean +setAlive(value: boolean) *_checkAndWait() CondHionalSend ConditionalReceive +ConditionalSend(guard : boolean, port: lOPort, channel: int, id : int, t: Token) +runQ +ConditionalReceive(guard : boolean, port: lOPort, channel: int, id : int) +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 rendezvous, 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. Otherwise 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 relatively 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, public void run() { try ( boolean iterate = true; while (iterate) { // container is checked for null to detect the termination //of the actor, iterate = false; if ((Entity)_actor).getContainer0 1= null && _actor.prefire 0) { _actor.fire(); iterate = _actor.postfire (); ) } } catch (TerminateProcessException t) { // Process was terminated early } catch (IllegalActionException e) { _manager.fireExecutionError(e); } finally f try ( _actor.wrapup(); } catch (IllegalActionExeption e) { jnanager.fireExecutionError(e); } director.decreaseActiveCount 0; FIGURE 17.11. Code executed by ProcessThread.run(). 330 it registers the process as being blocked. When one of the branches succeeds with a rendezvous, the process is registered as being unblocked. 17.5.4 Terminating the model A process can finish in one of two ways: either by returning false in its prefire() or postfire() methods, in which case it is said to have finished normally, or by being terminated early by a TerminateProcessException. For example, if a source process is intended to send ten tokens and then finish, it would exit its fire() method after sending the tenth token, and return false in its postfire() method. This causes the ProcessThread, see figure 17.11, representing the process, to exit the while loop and execute the finally clause. The finally clause calls wrapup() on the actor it represents, decreases the count of active processes in the director, and the thread representing the process dies. A TerminateProcessException is thrown whenever a process tries to communicate via a channel whose receiver has its finished flag set to true. When a TerminateProcessException is caught in ProcessThread, the finally clause is also executed and the thread representing the process dies. To terminate the model, the director sets the finished flag in each receiver. The next occasion a process tries to send to or receive from the channel associated with that receiver, a TerminateProcessException is thrown. This mechanism can also be used in a selective fashion to terminate early any processes that communicate via a particular channel. When the director controlling the execution of the model detects real deadlock, it returns from its fire() method. In the absence of hierarchy, this causes the wrapup() method of the director to be invoked. It is the wrapup() method of the director that sets the finished flag in each receiver. Note that the TerminateProcessException is a runtime exception so it does not need to be declared as being thrown. There is also the option of abruptly terminating all the processes in the model by calling terminate() on the director. This method differs from the approach described in the previous paragraph in that it stops all the threads immediately and does not give them a chance to update the model state. After calling this method, the state of the model is unknown and so the model should be recreated after calling this method. This method is only intended for situations when the execution of the model has obviously gone wrong, and for it to finish normally would either take too long or could not happen. It should rarely be called. 17.5.5 Pausing/Resuming the Model Pausing and resuming a model does not affect the outcome of a particular execution of the model, only the rate of progress. The execution of a model can be paused at any stage by calling the pause() method on the director. This method is blocking, and will only return when the model execution has been successfully paused. To pause the execution of a model, the director sets a paused flag in every receiver, and the next occasion a process tries to send to or receive from the channel associated with that receiver, it is paused. The whole model is paused when all the active processes are delayed, paused or blocked. To resume the model, the resume() method can similarly be called on the director This method resets the paused flag in every receiver and wakes up every process waiting on a receiver lock. If a process was paused, it sees that it is no longer paused and continues. The ability to pause and resume the execution of a model is intended primarily for user interface control. 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 monitors 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 deadlock. 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 simultaneously 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. 17.6.2 Rendezvous Algorithm In CSP, the locking point for all communication between processes is the receiver. Any occasion a process wishes to send or receive, it must first acquire the lock for the receiver associated with the channel it is communicating over. Two key facts to keep in mind when reading the following algorithms are that each channel has exactly one receiver associated with it and that at most one process can be trying to send to (or receive from) a channel at any stage. The constraint that each channel can have at most one process trying to send to (or receive from) a channel at any stage is not currently enforced, but an exception will be thrown if such a model is not constructed. The rendezvous algorithm is entirely symmetric for the put() and the get(), except for the direction the token is transferred. This helps reduce the deadlock situations that could arise and also makes the interaction between processes more understandable and easier to explain. The algorithm controlling how a get() proceeds is shown in figure 17.12. The algorithm for a put() is exactly the same except that put and get are swapped everywhere. Thus it suffices to explain what happens when a get() arrives at a receiver, i.e. when a process tries to receive from the channel associated with the receiver. When a get() arrives at a receiver, a put() is either already waiting to rendezvous or it is not. Both the get() and putO methods are entirely synchronized on the receiver so they cannot happen simultaneously (only one thread can possess a lock at any given time). Without loss of generality assume a get() arrives before a put(). The rendezvous mechanism is basically three steps: a get() arrives, a put() arrives, the rendezvous completes. (1) When the get() arrives, it sees that it is first and sets a flag saying a get is waiting. It then waits on the receiver lock while the flag is still true, (2) When a put() arrives, it sets the getWaiting flag to false, wakes up any threads waiting on the 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 rendezvousComplete 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 FIGURE 17.12. Rendezvous algorithm. 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. 17.6.3 Conditional Communication Algorithm There are two steps involved in executing a CIF or a CDO: first deciding which enabled branch succeeds, then carrying out the rendezvous. Built on top of rendezvous: When a conditional construct has more than one enabled branch (guard is true or absent), a new thread is spawned for each enabled branch. The job of the chooseBranch() method is to control these threads and to determine which branch should be allowed to successfully rendezvous. These threads and the mechanism controlling them are entirely separate from the rendezvous mechanism described in section 17.6.2, with the exception of one special case, which is described in section 17.6.4. Thus the conditional mechanism can be viewed as being built on top of basic rendezvous: conditional communication knows about and needs basic rendezvous, but the opposite is not true. Again this is a design decision which leads to making the interaction between threads easier to understand and is less prone to deadlock as there are fewer interaction possibilities to consider. Choosing which branch succeeds. The manner in which the choice of which branch can rendezvous is worth explaining. The chooseBranch() method in CSPActor takes an array of branches as an argument. If all of the guards are false, it returns -1, which indicates that all the branches failed. If exactly one of the guards is true, it performs the rendezvous directly and returns the identification number of the successful branch» The interesting case is when more than one guard is true. In this case, it creates and starts a new thread for each branch whose guard is true. It then waits, on an internal lock, for one branch to succeed. At that point it gets woken up, sets a finished flag in the remaining branches and waits for them to fail. When all the threads representing the branches are finished, it returns the identification number of the successful branch. This approach is designed to ensure that exactly one of the branches created successfully performs a rendezvous. which branch should succeed? * + 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 conditionalReceive 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 processes. 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 waiting, 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. 17.6.4 Modification of Rendezvous Algorithm Consider the case when a conditional send arrives before a get. If all the branches in the conditional communication that the conditional send is a part of are blocked, then the process will register itself as blocked with the director. Then the get comes along, and even though a conditional send is waiting, it too would register itself as blocked. This leads to one too many processes being registered as blocked, which could lead to premature deadlock detection. To avoid this, it is necessary to modify the algorithm used for rendezvous slightly. The change to the algorithm is shown in the dashed ellipse in figure 17.15. It does not affect the algorithm except in the case when a conditional send is waiting when a get arrives at the receiver. In this case the process that calls the get should wait on the receiver until the conditional send waiting flag is false. If the conditional send succeeded, and hence executed a put, then the get waiting flag and the conditional send waiting flag should both be false and the actor proceeds through to the third step of the rendezvous. If the conditional send failed, it will have reset the conditional send waiting flag and issued a notifyAll() on the receiver, thus waking up the get and allowing it to properly wait for a put. The same reasoning also applies to the case when a conditional receive arrives at a receiver before a put. 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 distributed 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 dataflow 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 usefulness in modeling and designing systems that are timed and distributed. 18.2 DDE Semantics Operationally, the semantics of the DDE domain can be separated into two functionalities. The first functionality relates to how time advances during the communication of data and how communication proceeds via blocking reads and writes. The second functionality considers how a DDE model prevents deadlock due to local time dependencies. The technique for preventing deadlock involves the 339 communication of null messages that consist solely of local time information. 18.2.1 Enabling Communication: Advancing Time Communicating Tokens. A DDE model consists of a network of sequential actors that are connected via unidirectional, bounded, FIFO queues. Tokens are sent from a sending actor to a receiving actor by placing a token in the appropriate queue where the token is stored until the receiving actor consumes it. If a process attempts to read a token from a queue that is empty, then the process will block until a token becomes available on the channel. If a process attempts to write a token to a queue that is full, then the process will block until space becomes available for more tokens in that queue. Note that this blocking read/write paradigm is equivalent to the operational semantics found in non-timed process networks (PN) as implemented in Ptolemy II (see the PN Domain chapter). If all processes in a DDE model simultaneously block, then the model deadlocks. Deadlock that is due to processes that are either waiting to read from an empty queue, read blocks, or waiting to write to a full queue, write blocks, then we say that the model has experienced non-timed deadlock. Non-timed deadlock is equivalent to the notion of deadlock found in bounded process networks scheduling problems as outlined by Parks [69]. If a non-timed deadlock is due to a model that consists solely of processes that are read blocked, then we say that a real deadlock has occurred and the model is terminated. If a non-timed deadlock is due to a model that consists of at least one process that is write blocked, then the capacity of the full queues are increased until deadlock no longer exists. Such deadlocks are called artificial deadlock, and the policy of increasing the capacity of full queues was shown by Parks to guarantee the execution of a model in bounded memory whenever possible. Communicating Time. Each actor in a DDE model maintains a local notion of time. Any non-negative real number may serve as a valid value of time. As tokens are communicated between actors, time stamps are associated with each token. Whenever an actor consumes a token, the actor's current time is set to be equal to that of the consumed token's time stamp. The time stamp value applied to outgoing tokens of an actor is equivalent to that actor's output time. For actors that model a process in which there is delay between incoming time stamps and corresponding outgoing time stamps, then the output time is always greater than the current time; otherwise, the output time is equal to the current time. We refer to actors of the former case as delay actors. For a given queue containing time stamped tokens, the time stamp of the first token currently contained by the queue is referred to as the receiver time of the queue. If a queue is empty, its receiver time is the value of the time stamp associated with the last token to flow through the queue, or 0.0 if no tokens have traveled through the queue. An actor may consume a token from an input queue given that the queue has a token available and the receiver time of the queue is less than the receiver times of all other input queues contained by the actor. If the queue with the smallest receiver time is empty, then the actor blocks until this queue receives a token, at which time the actor considers the updated receiver time in selecting a queue to read from. The last time of a queue is the time stamp of the last token to be placed in the queue. If no tokens have been placed in the queue, then the last time is 0.0 Figure 18.1 shows three actors, each with three input queues. Actor A has two tokens available on the top queue, no tokens available on the middle queue and one token available on the bottom queue. The receiver times of the top, middle and bottom queue are respectively, 17.0, 12.0 and 15.0. Since the queue with the minimum receiver time (the middle queue) is empty, A blocks on this queue before it proceeds. In the case of actor B, the minimum receiver time belongs to the bottom queue. Thus, B proceeds by consuming the token found on the bottom queue. After consuming this token, B compares all of its receiver times to determine which token it can consume next. Actor C is an example of an actor 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. 18.2.2 Maintaining Communication: Null Tokens Deadlocks can occur in a DDE model in a form that differs from the deadlocks described in the previous section. This alternative form of deadlock occurs when an actor read blocks on an input port even though it contains other ports with tokens. The topology of a DDE model can lead to deadlock as read blocked actors wait on each other for time stamped tokens that will never appear. Figure 18.2 illustrates this problem. In this topology, consider a situation in which actor A only creates tokens on its lower output queue. This will lead to tokens being created on actor C's output queue but no tokens will be created on B's output queue (since B has no tokens to consume). This situation results in D read blocking indefinitely on its upper input queue even though it is clear that no tokens will ever flow through this queue. The result: timed deadlock! The situation shown in figure 18.2 is only one example of timed deadlock. In fact there are two types of timed deadlock: feedforward and feedback. Figure 18.2 is an example of feedforward deadlock. Feedforward deadlock occurs when a set of connected actors are deadlocked such that all actors in the set are read blocked and at least one of the actors in the set is read blocked on an input queue that has a receiver time that is less than the local clock of the input queue's source actor. In the example shown above, the upper input queue of B has a receiver time of 0.0 even though the local clock of A has advanced to 8.0. Feedback deadlock occurs when a set of cyclically connected actors are deadlocked such that all actors in the set are read blocked and at least one actor in the set, say actor X, is read blocked on an input queue that can read tokens which are directly or indirectly a result of output from that same actor (actor X). Figure 18.3 is an example of feedback timed deadlock. Note that B can not produce an output based on the consumption of the token timestamped at 5.0 because it must wait for a token on the 22.0 22.0 J7.0 12.0 22.0 Actor A 15.0 22.0 J7.0 * Actor B 15.0 • 15.0 FIGURE 18.1. DDE actors and local time. Actor B J5.0 > > > • • 8.0 5.0 > • • Actor C 3.0 2.0 Actor A FIGURE 18.2. Timed deadlock (feedforward). 341 Actor D > Actor E H Actor C 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 corresponding 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 Actor A Actor B Actor C 8.0 5.0 FIGURE 18.3. Timed Deadlock (Feedback) 342 Actor D actor. 18.2.3 Alternative Distributed Discrete Event Methods The field of distributed discrete event simulation, also referred to as parallel discrete event simulation (PDES), has been an active area of research since the late 1970's [18][24][40][61][70]. Recently there has been a resurgence of activity [5][6][10]. This is due in part to the wide availability of distributed frameworks for hosting simulations and the application of parallel simulation techniques to nonresearch oriented domains. For example, several WWW search engines are based on network of workstation technology. The field of distributed discrete event simulation can be cast into two camps that are distinguished by the blocking read approach taken by the actors. One camp was introduced by Chandy and Misra [18][24][61][70] and is known as conservative blocking. The second camp was introduced by David Jefferson through the Jet Propulsion Laboratory Time Warp system and is referred to as the optimistic approach [40][24]. In certain problems, the optimistic approach executes faster than the conservative approach, nevertheless, the gains in speed result in significant increases in program memory. The conservative approach does not perform faster than the optimistic approach but it executes efficiently for all classes of discrete event systems. Given the modeling semantics emphasis of Ptolemy II, performance (speed) is not considered a premium. Furthermore, Ptolemy IPs embedded systems emphasis suggests that memory constraints are likely. For these reasons, the implementation found in the DDE domain follows the conservative approach. 18.3 Example DDE Applications To illustrate distributed discrete event execution, we have developed an applet that features a feedback topology and incorporates polymorphic as well as DDE specific actors. The model, shown in figure 18.4, consists of a single source actor (ptolemy/actor/lib/Clock) and an upper and lower branch of four actors each. The upper and lower branches have identical topologies and are fed an identical stream of tokens from the Clock source with the exception that in the lower branch ZenoDelay replaces FeedBackDelay. As with all feedback topologies in DDE (and DE) models, a positive time delay is necessary in feedback loops to prevent deadlock. If the time delay of a given loop is lower bounded by zero but can not be guaranteed to be greater than a fixed positive value, then a Zeno condition occurs in which time Upper Branch -> Wire DoubleFork Wire DoubleFork i Timed Plotter Clock ■? ZenoDelay FIGURE 18.4. Localized Zeno condition topology. 343 5> Timed Plotter Lower Branch will not advance beyond a certain point even though the actors of the feedback loop continue to execute without deadlocking. ZenoDelay extends FeedBackDelay and is designed so that a Zeno condition 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 condition. 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 Building DDE Applications To build a DDE application, use a DDEDirector. This ensures that each actor under control of the director is allocated DDEReceivers and that each actor is assigned a TimeKeeper to manage the actor's local notion of time. The DDE domain is typed so that actors used in a model must be derived from ptolemy/actor/TypedAtomicActor. The DDE domain is designed to use both DDE specific actors as well as polymorphic actors. DDE specific actors take advantage of DDEActor and DDEIOPort which are designed to provide convenient support for specifying time in the production and consumption of tokens. 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 operate 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 (Communicating Time). getLastPortO- This method returns the input IOPort from which the last (non-Null) token was consumed. 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 communication. 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. 18.4.3 Feedback Topologies In order to execute feedback topologies that will not deadlock, FeedBackDelay actors must be used. FeedBackDelay is found in the DDE kernel package. FeedBackDelay actors do not perform computation, but instead increment the time stamps of tokens that flow through them by a specified delay. The delay value of a FeedBackDelay actor must be chosen to be less than the delta time of the feedback cycle in which the FeedBackDelay actor is contained. Elaborate delay values can be specified by overriding the getDelayO method in subclasses of FeedBackDelay. An example of such can be found in ptolemy/domains/dde/demo/LocalZeno/ZenoDelay.java. A difficulty found in feedback cycles occurs in the initialization of a model's execution. In figure 18.5 we see that even if Actor B is a FeedBackDelay actor, the system will deadlock if the first event is created by A since C will block on an event from B. To alleviate this problem a special time stamp value has been reserved: TimeQueuedReceiver.IGNORE. When an actor encounters an event with a time stamp of IGNORE (an ignore event), the actor will ignore the event and the input channel it is associated with. The actor then considers the other input channels in determining the next available event. After a non-ignore event is encountered and consumed by the actor, all ignore events will be cleared from the receivers. If all of an actor's input channels contain ignore events, then the actor will clear all ignore events and then proceed with normal operation. The initialize method of FeedBackDelay produces an ignore event. Thus, in figure 18.5, if B is a FeedBackDelay actor, the ignore event it produces will be sent to C's upper input channel allowing C to consume the first event of A. The production of null tokens and feedback delays will then be sufficient to continue execution from that point on. Note that the production of an ignore event by a FeedBackDelay actor serves as a major distinction between it and all other actors. If a delay is desired simply to represent the computational delay of a given model, a FeedBackDelay actor should not be used. The intricate operation of ignore events requires special consideration when determining the position of a FeedBackDelay actor in a feedback topology. A FeedBackDelay actor should be placed so that the ignore event it produces will be ignored in deference to the first real event that enters a feedback cycle. Thus, choosing actor D as a FeedBackDelay actor in figure 18.5 would not be useful given that the first real event entering the cycle is created by A. 18.5 The DDE Software Architecture For a model to have DDE semantics, it must have a DDEDirector controlling it. This ensures that the receivers in the ports are DDEReceivers. Each actor in a DDE model is under the control of a Actor B i Actor D Actor A Actor C FIGURE 18.5. Initializing Feedback Topologies 345 /N DDEThread. DDEThreads contain a TimeKeeper that manages the local notion of time that is associated with the DDEThread's actor. 18.5.1 Local Time Management The UML diagram of the local time management system of the DDE domain is shown in figure 18.6 and consists of PrioritizedTimedQueue, DDEReceiver, DDEThread and TimeKeeper. Since time is localized, the DDEDirector does not have a direct role in this process. Note that DDEReceiver is derived from PrioritizedTimedQueue. The primary purpose of PrioritizedTimedQueue is to keep track of a receiver's local time information. DDEReceiver adds blocking read/write functionality to PrioritizedTimedQueue. When a DDEDirector is initialized, it instantiates a DDEThread for each actor that the director manages. DDEThread is derived from ProcessThread. The ProcessThread class provides functionality that is common to all of the process domains (e.g., CSP, DDE and PN). The directors of all process domains (including DDE) assign a single actor to each ProcessThread. ProcessThreads take responsibility of their assigned actor's execution by invoking the iteration methods of the actor. The iteration DDEThread PrioritizedTimedQueue #JastTime: double #_priority: int completionTime: double rcvrTime: double +PrioritizedTimedQueue() +PrioritizedTimedQueue(p: lOPort) +PrioritizedTimedQueue(cntr: lOPort, priority : int) +get(): Token +getCapacity(): int +getContainer(): lOPort +getLastTime(): double +getRcvrTime(): double +hasRoom(): boolean +hasToken(): boolean +put(token: Token, time: double) +removelgnoredTokens(): void +reset(): void +setCapacily(c: int) +setContainer(p: lOPort) #_getCompletionTime(): double #JiasNullToken(): boolean #_setCompletionTime(t: double) #_setRcvrTime(time: double) I ProcessThread +DDEThread(a : Actor, d : ProcessDirector) +getTimeKeeper(): TimeKeeper +noticeOfTermination() +start() +wrapup{) 1.1 controls manages time for O.n TimeKeeper 1..1 1.1 AtomlcActor contains 0..1 -_currentTime: double -jxitputTime: double +TimeKeeper(a: Actor): void +getCurrentTime(): double +getFirstRcvr(): TimedQueueReceiver +getNextTime{): double +getOutputTime{): double +removeAlllgnoredTokens(): void +sendOutNuUTokens(): void +setCurrentTime(time: double): void ■HjpdateRcvrListfrcvr: TimedQueueReceiver): void #_setOutpuffime(time : double): void #_setRcvrPriorities(): void contains O.n lOPort RcvrComparator +RcvrComparator(keeper: TimeKeeper) +compare(first: Object, second : Object): int DDEReceiver «Interface» ProcessRece/ver .terminate: boolean hideNullTokens: boolean +DDEReceiver() +DDEReceiver(ontr: lOPort) +DDEReceiver(cntr: lOPort, priority: int) +put(token: Token) #_hideNulfTokens(hide: boolean) I I I I I «Interface» Comparator A FIGURE 18.6. Key Classes for Locally Managing Time. 346 | I I I I 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 accomplished 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 TimeKeeper can be accessed as well. The DDE domain uses this approach extensively in DDEReceiver.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. 18.5.2 Detecting Deadlock The other kernel classes of the DDE domain are shown in figure 18.7. The purpose of the DDEDirector is to detect and (if possible) resolve timed and/or non-timed deadlock of the model it controls. Whenever a receiver blocks, it informs the director. The director keeps track of the number of active processes, and the number of processes that are either blocked on a read or write. Artificial deadlocks are resolved by increasing the queue capacity of write-blocked receivers. Note the distinction between internal and external read blocks in DDEDirector's package friendly 1. DDEReceiver.put(Token) is equivalent to the put() signature of the ptolemy.actor.Receiver interface. 2. Polymorphic actors need not be aware of DDE-specific code such as DDEReceiver.put(Token, Double). 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. 18.5.3 Ending Execution Execution of a model ends if either an unresolvable deadlock occurs, the director's completion time is exceeded by all of the actors it manages, or early termination is requested (e.g., by a user interface button). The director's completion time is set via the public stopTime parameter of DDEDirector. The completion time is passed on to each DDEReceiver. If a receiver's receiver time exceeds the completion time, then the receiver becomes inactive. If all receivers of an actor become inactive and the actor is not a source actor, then the actor will end execution and its wrapup() method will be called. In such a scenario, the actor is said to have terminated normally. Early terminations and unresolvable deadlocks share a common mechanism for ending execution. Each DDEReceiver has a boolean terminate flag. If the flag is set to true, then the receiver will throw a TerminateProcessException the next time any of its methods are invoked. TerminateProcessExceptions are part of the ptolemy/actor/process package and ProcessThreads know to end an actor's execution if this exception is caught. In the case of unresolvable deadlock, the _terminate flag of all blocked receivers is set to true. The receivers are then awakened from blocking and they each throw the exception. Typed lOPort I ProcassOiractor -oL ♦stopTime: Parameter readBlocks: irrt - writeBlocks: Hit ♦DDEDirectonO .DDEDirectorfwkspace: Workspace) ♦DDEDirectoitcntr: CompositeActor, nm : String) ♦hasMutationO: boolean *_addExtemalRead8lockO «_addlntemalReadBlockO *_addWriteBlock(rcvr: OOEReceiver) y.getlnKiammeTabfeO: Hashtable "_removeExtemalReadBlockO «_removelntema!ReadBlockO "_ramoveWriteBlockO KJncrementLowestCapacity Porto *_performMutationsO DDEIOPortO +DDEIOPort(cntr: ComponentEntity, nm : String) +DDEIOPort(entr: ComponentEntity, nm : String, in : boolean, out: boolean) +bfoadcast(token : Token, sendTime : double) +send(chan : irrt, token : Token, sendTime : double) I Typed AtomlcActori I T FeedBackDelay ♦nullDelay: Parameter ♦realDelay: Parameter delay: double +FeedBackDelayO +FeedBackDelay(wkspc: Workspace) +FeedBackDelay(cntr: TypedComposReActor, nm : String) +getDelayO: double +setDelay(delay: double) -_currentTime : double •JastPort: TypedlOPort +DDEActorO +DDEActor(ws: Workspace) +DDEActor(cntr: TypedCompositeActor. nm : String) ♦getLastPortO : TypedlOPort ♦getNextTokenO: Token »_getNextlnputQ: Token FIGURE 18.7. Additional Classes in the DDE Kernel. 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 Atomic 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 o I, Lock A LockB Thread 2 FIGURE 18.8. Deadlock Due to Unordered Locking. 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 available on it. This model of computation is deterministic in the sense that the sequence of values communicated 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 samples 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 indefinitely with limited resources. This behavior is naturally described as a process network that runs forever 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 departures 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 nondeterminism, 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. Similarly 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. 19.2 Process Network Semantics 19.2.1 Asynchronous Communication Kahn and MacQueen [41] [42] describe a model of computation where processes are connected by communication channels to form a network. Processes produce data elements or tokens and send them along a unidirectional communication channel where they are stored in a FIFO queue until the destination process consumes them. This is a form of asynchronous communication between processes. Communication channels are the only method processes may use to exchange information. A set of processes that communicate through a network of FIFO queues defines a program. Kahn and MacQueen require that execution of a process be suspended when it attempts to get data from an empty input channel {blocking reads). Hence, a process may not poll a channel for presence or absence of data. At any given point, a process is either doing some computation (enabled) or it is blocked waiting for data {read blocked) on exactly one of its input channels; it cannot wait for data from more than one channel simultaneously. Systems that obey this model are determinate; the history of tokens produced on the communication channels does not depend on the execution order. Therefore, the results produced by executing a program are not affected by the scheduling of the various processes. In case all the processes in a model are blocked while trying to read from some channel, then we have a real deadlock, none of the processes can proceed. Real deadlock is a program state that happens irrespectively of the schedule chosen to schedule the processes in a model. This characteristic is guaranteed by the determinacy property of process networks. 19.2.2 Bounded Memory Execution The high level of concurrency in process networks makes it an ideal match for embedded system software and for modeling hardware implementations. A characteristic of these embedded applications and hardware processes, is that they are intended to run indefinitely with a limited amount of memory. A problem than is that the Kahn-MacQueen semantics do not guarantee bounded memory execution of process networks even if it is possible for the application to execute in bounded memory. Hence bounded memory execution of process networks becomes crucial for its usefulness for hardware and embedded software. Parks [69] addresses this aspect of process networks and provides an algorithm to make a process network application execute in bounded memory whenever possible. He provides an implementation of the Kahn-MacQueen semantics using blocking writes that assigns a fixed capacity to each FIFO channel and forces processes to block temporarily if a channel is full. Thus a process has now three states: running {executing), read blocked, or write blocked and a process may not poll a channel for either data or room. The introduction of blocking read and write can cause deadlock, caused by processes that are blocked either on a read or on a write to a channel. This leads to the notion of artificial deadlock which means that all processes in a model block, but with at least one process blocked on a write. However, different from a real deadlock, Parks has shown that a program can continue to make progress on 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 determinate point where mutations can occur. The only determinate point is a read deadlock. However, performing 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 performed 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. 19.3.2 The Execution Sequence Director. In process networks, each node is a separate process. In the PN domain in Ptolemy II, this is achieved by letting each actor have its own separate thread of execution based on the native Java threads [66][44]. Process network processes are instances of ptolemy.actors.ProcessThread. BasePNDirector: This is the base class for the directors that govern the execution of a CompositeActor with Kahn process networks (PN) semantics. This base class attaches the Kahn-MacQueen process networks semantics to a composite actor. This director does not support mutations or a notion of time. It provides only a mechanism to perform blocking reads and writes using bounded memory execution whenever possible. It is capable of handling both real and artificial deadlocks. The execution sequence starts by a call to the initialize() method of the director. This method creates the receivers in the input ports of the actors for all the channels, creates a thread for each actor and initializes these actors. It also sets the count of active actors in the model to the number of actors in the composite actor. This is used in the detection of deadlocks and termination,. The next step in the sequence starts with a call to the prefire() method of the director. This method starts up all the created threads. In PN, this method always returns true. Then the fire() method of the director is called next. At this stage, the director resolves artificial deadlocks as soon as it arises using Parks's algorithm as explained in section 19.2.2. When a real deadlock is detected, the method returns. The last stage in the execution sequence, is the call to the postfire() method of the director. This method returns false if the composite actor containing the director has no input ports. Otherwise, it returns true. Returning true implies that if some new data is provided to the composite actor it's execution can resume. Returing false implies that this composite actor will not be fired again. In that case, the executive director or the manager will call the wrapup() method of the top-level composite actor, which in turn calls the wrapup() method of the director. This causes the director to terminate the execution of the composite actor. Details of termination are discussed in section 19.3.4. PNDirector: PNDirector is akin BasePNDirector with one additional functionality. It supports mutations of a process network graph. Mutations are processed as soon as they are requested. The point at which the mutations are processed depends on the schedule of the threads in the model. This might introduce non-determinism to the model. TimedPNDirector: 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 performed at the earliest timed-deadlock that occurs after they are queued. Since occurrence of timeddeadlock 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- 1 j j CompositeActor j j j Manager •Interface» Actor | : ~T PtolemyThread j ProcessThread ■_actor: Actor j-_director: ProcessDirector j-_manager: Manager 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 +BasePNDirector(workspace: Workspace) +BasePNDirector(container: CompositeActor, name: String) +addProcessListener(listener: PNProcessListener) »removeProcessUstenerflistener: PNProcessListener) PNQueueReceiverO +PNQueueReceiver(container: lOPort) +petReadBlockedActorO: Actor getWriteBlockedActorO: Actor *isConnectedToBoundaryO: boolean ♦islnsideBoundaryO: boolean ♦isReadPendingO: boolean ♦isWritePendingO: boolean ♦IsOutsideBoundaryO: boolean +setReadPending(readpending: boolean) +setWrttePendingort. A unit of data that is communicated by actors. This was called a particle 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 topology 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 actors 4, 109, 155, 156 acyclic directed graphs 197 add() method Token class 177 addChangeListener() method NamedObj class 152 addExecutionListener() method Manager class 172 AddSubtract actor 27, 92, 97, 313 addToScope() method Variable class 184 ADL3 ADS 2 advancing time CSP domain 321 aggregation association 134 aggregation UML notation 19 allowLevelCrossingConnect() method CompositeEntity class 142 analog circuits 5 analog electronics 1 Andrews 317 animated plots 227 anonymous inner classes 293 ANYTYPE 207 anytype 369 anytype particle 15 applet 34, 369 applets 9, 33, 224 using plot package 221 appletviewer 77, 224 application 369 application framework 155 applications 9, 33 arc 36, 134 architecture 3 architecture description languages 3 architecture design language 3 archive 78 archive applet parameter 229 arithmetic operators 177 arithmetic operators in expressions 184 Symbols ! in CSP 321 # in UML 17 " 40 ""charts 6 + in UML 17 ? in CSP 321 @exception 128 @param 128 _createRunControls() method Ptolemy/Applet class 73 director member 69 _execute() method ChangeRequest class 152 _go() method DEApplet class 76 manager member 69 _newReceiver() method IOPort class 162 toplevel member 69 A absolute type constraint 113 Absolute Value actor 97 abstract class 19 abstract syntax 13, 36, 133, 369 abstract syntax tree 193 abstraction 37, 139 acquaintances 156 action methods 94, 118, 167, 369 active processes 330 actor 165, 369 Actor interface 13, 165, 166 actor libraries 78 actor library 24 actor package 9, 90, 156 actor.event package 9 actor.gui package 9, 68, 89, 91, 92, 102, 103 actor.lib package 11, 90, 100, 102, 113 actor.process package 11, 173, 174 actor.sched package 11, 173, 174 570 arraycopy method 315 ArrayFIFOQueue class 314, 315 arrays in expressions 185 Array Token class 177, 210 Array Type class 211 artificial deadlock 340, 352 associations 19 AST 193 ASTPtBitwiseNode class 195 ASTPtFunctionallfNode class 195 ASTPtFunctionNode class 194, 195 ASTPtLeafNode class 195 ASTPtLogicalNode class 195 ASTPtMethodCallNode class 195 ASTPtProductNode class 195 ASTPtRelationalNode class 196 ASTPtRootNode class 195 ASTPtSumNode class 195 ASTPtUnaryNode class 196 asynchronous communication 163, 352 asynchronous message passing 7, 157 atomic actions 4 atomic actor 369 atomic communication 318 AtomicActor class 13, 165, 166 ATTLIST in DTD 236 attribute 369 Attribute class 138, 180 attributeChanged() method NamedObj class 115, 183 Poisson actor 116 Scale actor 117 attributeListO method NamedObj class 138 attributes 12, 17, 180 attributes in XML 40 attributeTypeChanged() method NamedObj class 116, 183 audio 11 Average actor 81, 105, 119, 121, 122, 123 B Backus normal form 193 balance equations 309 bang in CSP 321 barGraph element PlotML 239 Bars command 242 base class 18 BaseType class 47 BaseType.NAT211 BDF7 begin() method Ptolemy 0 167 Bernoulli 121 Bernoulli actor 99, 102, 120, 121 bidirectional ports 159, 165 bin directory 33 bin element PlotML 239 binary format plot files 221 bison 193 bitwise operators in expressions 184 block 369 block diagrams 9 block-and-arrow diagrams 4 blocked processes 330 blocking reads 339, 352 blocking receive 317 blocking send 317 blocking writes 339, 352 BNF 193 body of an element in XML 40 boolean dataflow 7 BooleanMatrixToken class 176 BooleanToken class 176 bottom-up parsers 193 bounded buffering 351 bounded memory 305, 352 boundedness 7 broadcast() method 159 DEIOPort class 292, 295, 296 browser 34, 369, 371 bubble-and-arc diagrams 4 buffer 163 bus 157 bus contention 321 bus widths and transparent ports 162 busses, unspecified width 160 c C2 C++2 calculus of communicating systems 4, 318 calendar queue 5,11, 290 CalendarQueue class 164 CCS 4, 318 CDATA 44 CD0 319, 335 Chandy 339 change listeners 151 371 change request 59 change requests 353 changed() method QueryListener interface 75 changeExecuted() method ChangeListener interface 151 changeFailed() method ChangeListener interface 151 ChangeListener interface 152 ChangeRequest class 151, 152, 293 channel 156, 369 channels 110 check box entry 81 checkTypes() method TypedCompositeActor class 212 chooseBranch() method CSPActor class 325 CIF 319, 326, 335 circular buffer 315 class attribute in MoML 40 class diagrams 17 class element 49 class names 21, 126 CLASSPATH environment variable 70 clipboard 224 Clock actor 74, 99, 215 Clock class 69 clone() method NamedObj class 144 Object class 117, 177 Scale actor 118 cloning 143 cloning actors 117 clustered graph 369 clustered graphs 11, 13, 36, 133 code duplication 109 code generation 369 codebase applet parameter 229 coding conventions 124 coin flips 99, 102 Color command 241 comments 125 comments in expressions 185 communicating sequential processes 4, 13, 317 communication networks 287 communication protocol 156, 162 Commutator actor 105, 106 Comparable interface 292 compat package 222, 243 compile-time exception 126 compiling applets 70 complete partial orders 197 completion time 341 complex numbers 11 complex numbers in expressions 188 ComplexMatrixToken class 176 ComplexToken class 176 component interactions 3 component-based design 89, 109 ComponentEntity class 13, 139, 140 ComponentPort class 13, 139, 140 ComponentRelation class 13, 139, 140 components 2, 15 Composite Actor 27 composite actor 369 Composite design pattern 19, 139 composite opaque actor 168 CompositeActor class 13, 165, 166 CompositeEntity class 13, 41, 58, 139, 140 concrete class 19 concrete syntax 36, 133, 369 concurrency 2 concurrent computation 156 concurrent design 13 concurrent finite state machines 6 concurrent programming 322 conditional communication 325 conditional do 319 conditional if 319 ConditionalReceive class 325 conditionals in expressions 185 ConditionalSend class 325 configure element 43 Configure ports 28 connect() method CompositeEntity class 142 connection 36, 134, 369 connections making in Vergil 28 conservative blocking 343 consistency 135 Const actor 24, 100 constants expression language 185 constants in expressions 185, 196 constraints on parameter values 115 constructive models 1 constructors in UML 17 container 137, 369 containment 19 contention 321 372 context menu 27 continuous time modeling 4 continuous-time modeling 13 continuous-time systems 94 contract 210 control key 28 control-clicking 28 convert() method Token class 188 Token classes 180 CORBA 15, 33 cos() method Math class 76 cosine 99, 108 CPO interface 200 CPOs 197 CQComparator interface 164 CrossRefList class 139 CSP4, 317 CSP domain 93, 163 CSPActor class 325 CSPDirector class 327 CSPReceiver class 327 CT4 CT domain 94 current time 91, 287, 353 CurrentTime actor 100, 101 Cygwin 223 D DAG 288 dangling ports SDF domain 313 dangling relation 159, 369 data encapsulation 175 data package 11, 175 data polymorphic 178 data polymorphism 89, 109, 369 data rates 309 data.expr package 11, 189 data.type package 11 dataflow 163, 288, 305, 339 DataSet command 242 dataset element PlotML 237, 238 dataurl 221 dataurl applet parameter 229 dataurl parameter PlotApplet class 221 dB actor 97 DCOM 15 DDE 6, 339 DDE domain 300 DDES 339 DDF 7 DE 5, 287 DE domain 94 DEActor class 290, 291, 292 deadlock 7, 148, 150, 309, 352 CSP domain 320 DDE domain 339 DEApplet class 67, 69 DECQEventQueue class 291 DECQEventQueue.DECQComparator class 292 DEDirector class 290, 291 deep traversals 141, 369 deepContains() methodNamedObj class 143 deepEntityList() method CompositeEntity class 141, 172 DEE vent class 291, 292 DEEventQueue interface 291 DEEventTag class 291 DefaultExecutionListener class 166, 172 defaultlterations parameter SDFapplets 1'4 defaultStopTime parameter DE applets 73 DEIOPort class 290, 291, 292, 295, 296, 298 delay 288, 292 CSP domain 320 w SDF 306 PN domain 353 SDF domain 310 Delay actor 289 DE domain 292 delay actors DDE domain 340 delayO method CSPActor class 327 delayed processes 330 delay To() method DEIOPort class 296, 298 deleteEntity element 56 deletePorts element 56 deleteProperty element 57 deleteRelations element 56 delta functions 5 delta time 5, 342 demultiplexer actor 157 dependency loops 194 depth for actors in DE 288 DEReceiver class 291 373 derived class 18 design 1 design patterns 15 determinacy 7, 163 determinism 317, 351 deterministic 289 DEThreadActor class 300 DETransformer class 292, 298 diamond in toolbar 28 digital electronics 1 digital hardware 5, 287 Dijkstra 322 dining philosophers 321, 322 Dirac delta functions 5 directed acyclic graph 288 directed graphs 36, 134, 197 DirectedAcyclicGraph class 198, 200 DirectedGraph class 198, 199 director 13, 162, 168, 370 Director class 13, 162, 165, 166 director element 51 director library 24 di sable Actor() method DEDirector class 295 disconnected graphs SDF domain 313 disconnected port 159, 370 discrete event domain 287 discrete-event domain 5 discrete-event model of computation 164 discrete-event modeling 13 discrete-time domain 6 Display actor 24 distributed discrete event 339 distributed discrete event systems 339 distributed discrete-event domain 6 distributed models 6 distributed time 339 Distributor actor 105, 157, 165 divide() method Token class 177, 195 doc element 44, 56 DOCTYPE keyword in XML 34, 39, 41, 49, 50, 233 document type definition 37, 233, 235 domain 155, 370 domain polymorphism 89, 109, 178, 370 domain-polymorphism 15 domains 9, 13 domains package 11, 13 domains.de.kernel package 290 domains.de.lib package 292 doneReading() method Workspace class 150 doneWritingO method Workspace class 150 DoubleCQComparator interface 164 DoubleMatrixToken class 176 doubles 185 DoubleToFix actor 107 DoubleToken class 176 DT6 DTD 37, 233, 235 dynamic dataflow 7 dynamic networks 165 E E 185 e 185 edges 197 EDIF 36, 133 EditablePlot class 227 EditablePlotMLApplet class 229 EditablePlotMLApplication class 230 element 370 ELEMENT in DTD 234 element in XML 39 embedded systems 1 EMPTY in DTD 236 empty elements in XML 40 encapsulated PostScript 223 encapsulated postscript 224 entities 4, 36, 133 entity 370 Entity class 13, 134, 135 entity in XML 40 EntityLibrary class 58 EPS 223, 224 equals() method Token class 177, 196 Eratosthenes 322, 323 evaluateParseTree() method ASTPtRootNode class 194 evaluation of expressions 181 event 5 event queue 287 events 287 exceptions 126 exceptions in applets 70 executable entities 155 Executable interface 13, 165, 166 executable model 13 Director class 172, 173 Executable interface 94, 165 in actors 119 fireAt() method DEActor class 298 DEDirector class 296 Director class 99, 123, 287, 292, 295, 302 fired 30 firing vector 309 firingCountLimit parameter SequenceSource actor 99, 123 first-in-first-out 351 fix function in expression language 189 fixed point data type 189 fixed-point 4 fixed-point semantics 94 fixed-point simulations 16 FixPoint class 189 FixPointFunctions class 189 FixToDouble actor 107 FixToken class 189 floating-point simulations 16 formatting of code 124 fractions 11 FrameMaker 223 FSM6 full name 137 functional actors 94 functions expression language 185 executable models 1 execute() method ChangeRequest class 151 execution 94, 167, 370 executionError() method ExecutionListener interface 172 ExecutionEvent class 166 executionFinished() method ExecutionListener interface 81, 172 ExecutionListener class 166 ExecutionListener interface 172 executive director 168, 172, 370 explicit integration algorithms 266 exporting MoML 60 exportMoML() method NamedObj class 59, 60 Expression actor 105 expression evaluation 193 expression language 6, 11, 184 extending 196 expression parser 193 expressions 76 extensible markup language 35, 232 F fail-stop behavior 206 fairness 322 false 185 FBDelay actor 342 FFT30 FIFO 156,315,351 FIFO Queue 11 FIFOQueue class 156, 163, 164, 314 file format for plots 232 file formats 15 File->New menu 24 fill command in plots 223 fillOnWrapup parameter Plotter actor 102 finally keyword 150 finish() method Manager class 169 finished flag 331 finite buffer 163 finite state machines 9 finite-state machine domain 6 fire() method actor interface 289 Average actor 123 CompositeActor class 172, 173 G galaxy 143, 370 Gaussian actor 28, 101 generalize 18 get() method lOPort class 157 Receiver interface 157 getAttributeO method NamedObj class 138 getColumnCount() method MatrixToken class 188 getContainer() method Nameable interface 137 getCurrentTime() method DEActor class 298 Director class 123 getDirector() method Actor interface 168 getElementO method ArrayToken class 186 375 getElementAt() method MatrixToken classes 177 getFullName() method Nameable interface 137 getInsideReceivers() method IOPort class 173 getOriginator() method ChangeRequest class 152 getReadAccess() method Workspace class 149 getReceivers() method IOPort class 173 getRemoteReceivers() method 165 IOPort class 162 getRowCount() method MatrixToken class 186 getState() method Manager class 172 getValue() method ObjectToken class 177 getWidth() method IORelation class 162 getWriteAccess() method Workspace class 150 Ghostview 223 global error for numerical ODE solution 266 grammar rules 193 Graph class 198, 199 graph package 11, 197 graphical elements 71 graphical user interface 89, 91 graphics 54 graphs 197 Grid command 241 group element 58 guarded communication 164, 318, 325 guards 6 GUI 89, 91 gui package 11 IOPort class 173 heterogeneity 15, 146, 172 Hewlett-Packard 2 hiding 37, 141 hierarchical concurrent finite state machines 9 hierarchical heterogeneity 146, 172 hierarchy 139 higher node 200 histogram 221, 222 Histogram class 227 histogram.bat 223 HistogramMLApplet class 229 HistogramMLApplication class 230 HistogramMLParser class 233 HistogramPlotter actor 103 history 163 Hoare317, 321 HTML 35, 67, 127, 221, 233 HTTP 78 hybrid systems 6 I i 185 if...then...else... 185 Illegal ActionException class 117 Illegal ArgumentException class 194 image processing 11 immutability tokens 175 Immutable 149 immutable 137 immutable property 370 imperative semantics 2 implementation 33 implementing an interface 19 implicit integration algorithms 266 import 17 Impulses command 242 inCSP319 incomparable 179 incomparable types 112 inconsistent models 310 incremental parsing 54, 58 indentation 125 index of links 135 index of links to a port 48 index of links to ports 57 Inequality class 199, 200, 214 InequalitySolver class 200 Inequality Term interface 198, 200, 214 information-hiding 146 H hardware 1 hardware bus contention 321 Harel, David 6 Harrison, David 221 hashtable 5 hasRoom() method IOPort class 173 Hasse 200 Hasse diagram 200 hasTokenQ method 376 inheritance 18, 109 initial output tokens 119 initial token 310 initialize() method Actor interface 290 Average actor 119 Director class 169 Executable interface 94, 165 in actors 118, 119 input element 52 input port 156 input property of ports 55 inputs transparent ports 160 inside links 37, 139 inside receiver 173 inspection paradox 81 instantaneous reaction 289 integers 185 intellectual property 6 interface 19 interoperability 2, 15 interpreter 11 IntMatrixToken class 176 IntToken class 176 invalidateResolvedTypes() method Director class 116 invalidateSchedule() method DEDirector class 292 Director class 115 IOPort class 156 IORelation class 156, 157 isAtomic() method CompositeEntity class 139 islnput() method 165 isOpaque() method ComponentPort 147 CompositeActor class 168, 172 CompositeEntity class 139, 160 isOutput() method 165 isWidthFixed() method IORelation class 162 iteration 94, 167, 370 iterations 119 iterations parameter 24 SDF applets 74 SDFDirector class 311 J j 185 jar files 78 plot package 222 Java 2 Java 223 Java Archive File 78 Java Foundation Classes 229 Java Plug-In 67 JavaRMI 15 Java Runtime Environment 67 java.lang.Math 196 JavaCC 193 Javadoc 113, 127 Jefferson 343 JFC229 JFrame class 229 JIT 80 JJTree 193 JPanel class 229 JRE67 just-in-time compiler 80 K Kahn process networks 7, 163, 339 kernel package 11 kernel.event package 11 kernel.util package 11, 126, 164 L LALR(l) 193 lattice 179 lattices 197 layout manager 71 LED A 197 length() method ArrayToken class 186 level-crossing links 37, 141, 142 lexical analyzer 193 lexical tokens 193 HberalLink() method ComponentPort class 142 Lines command 241 lingering processes 80 link 36, 134, 135, 370 link element 47, 52 link element and channels 48 link index 48, 57, 135 link() method Port class 142 links in Vergil 28 literal constants 185 liveness 13, 322 377 microelectromechanical systems 1 Microstar XML parser 58 microstep 288 microwave circuits 5 Milner318 Minimum actor 98 Misra 339 mixed signal modeling 5 ML 15 MoC 317 modal model 5 modal models 6 model 370 model element 41 model of computation 2, 155, 156, 370 model time 287, 320, 353 modeling 1 models of computation mixing 172 modulo() method Token class 177, 195 MoML 33, 370 exporting 60 moml package 12, 58, 59, 62 MoMLAttribute class 61 MoMLChangeRequest class 59, 152 monitor 148 monitors 13, 15, 332 monomorphic 113 monotonic functions 163 multiple containment 42 multiplyO method Token class 114, 177, 195 MultiplyDivide actor 29, 98 multiport 27, 110, 157, 163, 370 multiport property of ports 55 multiports SDF domain 313 multiports in MoML 46 mutability CSP domain 322 mutation 11, 15 mutations 150, 351, 353 DE domain 292, 298 mutual exclusion 148, 332 LL(k) 193 local director 168, 172 local error for numerical ODE solution 266 Location class 61 lock 148, 332 logarithmic axes for plots 236, 240 logical boolean operators in expressions 184 long integers 185 long integers in expressions 188 LongMatrixToken class 176 LongToken class 176 Lorenz system 274 lossless type conversions 183 Lotos 4, 321 lower node 199 M M/M/l Queue 322 mailbox 163 Mailbox class 156, 163 make install 79 makefiles 79 managed ownership 137 manager 168, 169, 370 Manager class 13, 166, 169 managerStateChanged() method ExecutionListener interface 172 Marks command 241 marks in ptplot 237 Math class 76 math functions 196 math package 11, 189 mathematical graphs 36, 134, 197 Matlab 2 matrices 11 matrices in expressions 185 matrix tokens 177 MatrixToken class 176, 186 MatrixViewer actor 104 Maximum actor 97, 98, 99, 106, 108 mechanical components 5 mechanical systems 5 media package 11 Mediator design pattern 36, 134 MEMS 1, 5, 276 Merge actor 292 Message class 228 message passing 156 methods expression language 186 microaccelerometer 276 N name 137 name attribute in MoML 40 name server 165 Nameable interface 12, 126, 135, 137 i 3 78 oscilloscope 237 output property of ports 55 overloaded 127 override 18 NamedList class 139 NamedObj class 12, 41, 60, 135, 137, 152 NameDuplicationException class 117 namespaces 58 naming conventions 21 NaT 219 newPort() method Entity class 46 newReceiver() method Director class 162 newRelation() method Composite Entity class 48 noColor element PlotML 237 node classes (parser) 195 nodes 197 noGrid element PlotML 237 non-determinism 317 nondeterminism with rendezvous 164 nondeterministic choice 318 non-timed deadlock 340 notifyAll() method Object class 332 null messages 340 Numerical type 180 P package 370 package diagrams 17 package structure 9 packages 13 Pamela 353 Panel class 227 parallel discrete event simulation 343 parameter 181, 370 Parameter class 74, 181 parameters 11, 74, 114 constraints on values 115 Parks 352 parse tree 193 parse() method MoMLParser class 58 parsed character data 234 parser 193 partial order 15 partial orders 197 partial recursive functions 6 particle 370 pathTo attribute vertex element 53 pause() method CSPDirector class 331 Manager class 172 PCDATA in DTD 234 PDES 343 period parameter Clock actor 74 persistent file format 59 PI 185 pi 185 Placeable interface 71,91 plot actors 221 Plot class 71, 227, 228 plot package 12, 221 plot public member Plotter class 71 PlotApplet class 228 PlotApplication class 228, 229 PlotBox class 227, 228, 229 PlotBoxMLParser class 233 PlotFrame class 228, 229 PlotLive class 227, 228 O object model 17 object modeling 15 object models 9 object-oriented concurrency 155 object-oriented design 89 ObjectToken class 175, 176, 177 OCCAM 321 Occam 4 ODE solvers 13 one() method Token class 178 oneRight() method MatrixToken classes 178 opaque 370 opaque actors 168, 172 opaque composite actor 168, 173, 370 opaque composite actors 15 opaque composite entities 146 opaque port 141 operator overloading 184 optimistic approach 343 originator in change requests 151 379 , PlotLiveApplet class 228 PlotML 43, 222, 227, 232, 235 plotml package 227, 233 PlotMLApplet class 229 PlotMLApplication class 230 PlotMLFrame class 229 PlotMLParser class 233 PlotPoint class 227, 228 Plotter actors 30 Plotter class 91 plotting 12 Plug-In 67 plug-in 80 PN 7, 351 PN domain 93 Poisson actor 101, 115, 116 polymorphic actors 178 polymorphism 15, 89, 109, 207 data 178 domain 178 port 370 type of a port 47 Port class 13, 134, 135 port element 45 port toolbar button 27 ports 36, 110, 133 postfire() method actor interface 289 Average actor 123 CompositeActor class 169 DE domain 296 DEDirector class 302 Executable interface 94, 165 in actors 119 Server actor 298 PostScript 223 precedences 5 precondition 126 prefire() method Actor interface 289 CompositeActor class 172 DE domain 295 Executable interface 94, 165 in actors 119 Server actor 298 prefix monotonic functions 7 prefix order 163 preview data in EPS 223 prime numbers 323 priorities 323 priority of events in DE 288 priority queue 5, 11 private methods 17 process algebras 37, 141 process domains 173 process level type system 15 Process Network Semantics 352 process networks 13, 163, 305, 351 process networks domain 7, 172 processing instruction 44, 45 process-oriented domains 94 ProcessThread class 328 production rules 193 property element 42, 56 protected members and methods 17 protocol 156 protocols 89 PTII environment variable 33, 222, 223, 229 Ptolemy Classic 13, 370 ptolemy executable 33 Ptolemy II 370 Ptolemy Project 371 ptolemy.data.expr package. 189 PtolemyApplet class 68, 70 PtParser 193 ptplot 221, 222, 230 ptplot.bat 223 PUBLIC keyword in XML 34, 39, 41, 233 public members and methods 17 Pulse actor 101, 215 pure event 287 pure property 46 pure property in MoML 43 put() method Receiver interface 156 pxgraph221,222, 243 pxgraph.bat 223 PxgraphApplication class 243 pxgraphargs parameter PxgraphApplet class 222 PxgraphParser class 243 Q quantize() function in expression language 189 Quantizer actor 98 Query class 74 query in CSP 321 QueryListener interface 75 queue 163, 315, 322 queueing systems 287 QueueReceiver class 156, 157, 163 OoU NamedObj class 152 requestChange() method 59 Director class 151, 292 Manager class 292 REQUIRED in DTD 236 resolved type 207, 371 resolveTypes() method Manager class 214 resource contention 321 resource management 317 resume() method CSPDirector class 331 Manager class 172 re-use 89 ReuseDataSets command 242 right click 27 rollback 272 RTTI 210 Rumbaugh 137 Run Window 24 run() method Manager class 169 Runtime Environment 67 run-time exception 126 run-time type checking 206, 210 run-time type conversion 206 run-time type identification 210 RuntimeException interface 126 quotation marks in MoML attributes 40 R race conditions 148 Ramp actor 101, 102, 103 random() function in expressions 186 Rapide 3 read blocked 352 read blocks 340 read/write semaphores 15 readers and writers 149 read-only workspace 150 real deadlock 320, 340, 352 RealToComplex actor 106, 107, 108 receiver wormhole ports 173 Receiver interface 156 receiver time 340 record tokens in expressions 185 Recorder actor 81, 104 RecordToken 210 reduced-order modeling 15 reference implementation 58 reflection 194, 196 registerClass() method PtParser class 196 registerConstant() method PtParser class 196 registerFunctionClass() method PtParser class 195 relation 371 in Vergil 28 Relation class 13, 135 relation element 47 relational operators in expressions 184 relations 4, 36, 133 relative type constraint 112 reloading applets 80 removeChangeListener() method NamedObj class 152 removing entities 56 removing links 57 removing ports 56 removing relations 56 rename element 56 rendezvous 4, 93, 157, 163, 317, 333 rendition of entities 53 report() method Ptolemy Applet class 70 reporting errors in applets 70 requestChange method s Saber 2, 5 safety 13 SampleDelay actor 306 scalable vector graphics 54 Scalar type 180 ScalarToken class 176 Scale actor 98, 113, 115, 116, 117, 118 Scheduler class 313 schedulers 173 scheduling 167,311,313 scope 181 scope in expressions 185 Scriptics Inc. 144 scripting 184 SDF 7, 305 SDF scheduler 29 SDFAtomicActor class 315 SDFDirector class 311 SDFReceiver class 313, 314 SDFScheduler class 311, 313 SDL 7 3^1 setTypeEquals method 117 setTypeEquals() method Variable class 181 setTypeSameAs() method Variable class 183 setWidth() method IORelation class 157, 162 setXLabel() method Plot class 77 SGML 35, 233 shallow copy 117 shell script 223 sieve of Eratosthenes 322, 323 signal processing 351 signal processing library 28 simulation 1, 33 simulation time 287 Simulink 2, 5 simultaneous events 5, 287, 288 sin() method Math class 76 Sine actor 99, 108, 121 Sinewave actor 28 Sine wave class 62 single port 110 Sink class 109 sinks library 24 size element PlotML 237 software 1 software architecture 3 software components 15 software engineering 15 source actors 100, 102, 103, 123 Source class 109 sources library 24 spaces 125 specialize 18 spectrum 30 Spice 5 spreadsheet 11 SR7 star 143, 371 starcharts 6 Start menu 80 start tag in XML 40 start time 290 startRun() method Manager class 169 state 6, 371 Statecharts 6 semantics 2, 13 send() method DEIOPort class 292, 295, 296, 298 IOPort class 156 TypedlOPort class 214 SequenceActor interface 91, 99, 292 SequencePlotter actor 28, 30, 104 SequencePlotter class 91 SequenceSource actor 124 SequenceSource class 123 Server actor 292, 297 servlet 371 servlets 33 setConnected() method Plot class 77 setContainer() method kernel classes 135 Port class 105 setContext() method MoMLParser class 55 setCurrentTime 327 setCurrentTime() method Director class 123, 327 setExpression() method Parameter class 76 Variable class 181 setlmpulses() method Plot class 77 setMarksStyle() method() Plot class 77 setMultiport() method IOPort class 157 setPanelO method Placeable interface 71, 91 setReadOnly() method Workspace class 150 setSize() method Plot class 71 PlotBox class 232 setStopTime() method DEDirector class 73, 290 Settable interface 138 setTitleO method Plot class 77 setToken() method Variable class 181 setTopLevelO method MoMLParser class 55 setToplevel() method of MoMLParser 59 setTypeAtLeast() method Variable class 183 382 stateless actors 94 static schedule 169 static schedulers 173 static scheduling 308 static structure diagram 12, 90, 91, 134 static structure diagrams 17 static typing 205 StaticSchedulingDirector class 311 stem plot 77 stem plots 238 stop time 290 stopFire() method Executable interface 165 stopTime parameter DE Applets 73 TimedSource actor 99 stream 157 string constants 185 StringAttribute class 138 StringToken class 92, 176 stringValue() method Query class 76 StructuredType class 211 subclass 18 subclass UML notation 18 subdomains 15 subpackage 371 subtract^) method Token class 177, 195 superclass 18 SVG 54 swing 229 symbol table 193 synchronized keyword 148, 332 synchronous communication 163 synchronous dataflow 7, 13, 305 synchronous dataflow domain 7 synchronous message passing 4, 157, 317 synchronous/reactive models 7 syntax 9 System control panel 223 TerminateProcessException class 331 terminating processes CSP domain 331 testable precondition 126 thread actors DE domain 298 thread safety 137, 147, 148 threads 13, 163 thread-safety 15 threshold crossings 5 tick element PlotML 236 tick marks 226 time 2 CSP domain 320 DDE domain 339 PN domain 353 time deadlock 320 time stamp 5, 164, 287 DDE domain 340 Time Warp system 343 timed deadlock 341 Timed Actor interface 91, 99, 123, 292 TimedPlotter actor 71, 104 TimedPlotter class 69, 91 TimedSource actor 123 TimedSource class 124 title elemen PlotML 234 TitleText command 240 toArrayO method MatrixToken class 188 token 110,371 Token class 92, 93, 121, 175, 176 tokenConsumptionRate parameter port classes 313 tokenlnitProduction parameter port classes 313 tokenProductionRate parameter port classes 313 tokens 30, 90, 156 tokens, lexical 193 toolbar 27, 28 tooltips 45 top level composite actor 169 top-down parsers 193 topological sort 198, 288 topology 36, 133, 371 topology mutations 150 transferlnputs() method DEDirector class 301 T Tab character 125 tag 371 tag in XML 40 telecommunications systems 5 terminate() method Director class 331 Executable interface 167 Manager class 169 383 Director class 173 transferOutputs() method Director class 173 Transformer class 91, 109, 113, 114 transitions 6 transitive closure 198, 200 transparent 371 transparent composite actor 371 transparent entities 139 transparent port 371 transparent ports 141, 160 trapped errors 205 trigger input Source actor 99 true 185 tunneling entity 143 type changes for variables 183 type compatibility rule 206 type conflict 208 type constraint 112, 207 type constraints 112, 207, 214, 371 type conversion 210 type conversions 179 type hierarchy 178 type lattice 179 type of a port 47 type resolution 15, 167, 207, 371 type resolution algorithm 219 type system 112 process level 15 type variable 208 Typeable interface 183 typeConstraints() method 214 Typed Composite Actor 27 TypedActor class 211 TypedAtomicActor 211 TypedAtomicActor class 90, 165 TypedCompositeActor 211 TypedCompositeActor class 41, 165 TypedIOPort211 setting the type in MoML 47 TypedlOPort class 46, 110, 156, 292 TypedlORelation class 48, 156 TypedOIRelation211 TypeLattice class 179 type-polymorphic actor 207 types of parameters 181 undeclared type 207, 371 undeclared types 211 undirected graphs 197 unified modeling language 17 uniqueness of names 137 universe 371 Unix 223 unlink element 57 untrapped errors 205 util subpackage of the kernel package 151 utilities library 27 V variable 180 Variable class 138 VariableClock actor 102 variables in expressions 185 vector graphics 54 vectors 11 Verilog 5, 9 vertex 36, 134 vertex attribute link element 52 Vertex class 53, 61 VHDL 5, 9 VHDL-AMS 2, 5 View menu 24 visual dataflow 9 visual rendition of entities 53 visual syntax 9 W wait() method Object class 332 Workspace class 150 waitForCompletion() method ChangeRequest class 153 waitForDeadlock() method CSPActor class 327 waitForNewInputs() method DEThreadActor class 300 web server 34, 369, 371 welcome window 24 width of a port 110, 157,371 width of a relation 49, 57, 157, 371 width of a transparent 162 Windows 223 wireless communication systems 165 workspace 149 Workspace class 135, 138, 149 wormhole 15, 147, 168, 172, 371 U UML 9, 12, 17, 90, 91, 134 package diagram 9 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 Zeno condition 342 zero delay actors 289 zero() method Token class 178 zero-delay loop 289 zoom in plots 223 385 •U.S. GOVERNMENT PRiNTING OFFICE: 2001-610-055-10 MISSION OF AFRL/INFORMATIONDIRECTORATE (IF) The advancement and application ofInformation Systems Science and Technology to meet Air Force unique requirements for Information Dominance and its transition to aerospace systems to meet Air Force needs.