C++ code segments are an important part of any star definition. They can appear in the
setup
, begin
, go
, wrapup
, constructor
, destructor
, exectime
, header
, code
, and method
directives in the Ptolemy preprocessor. These directives all include a body of arbitrary C++ code, enclosed by curly braces, "{
" and "}
". In all but the code
and header
directives, the C++ code between braces defines the body of a method of the star class. Methods can access any member of the class, including portholes (for input and output), states, and members defined with the public
, protected
, and private
directives.2.4.1 The structure of a Ptolemy star
In general, the task of a Ptolemy star is to receive input particles and/or produce output particles; in addition, there may be side effects (reading or writing files, displaying graphs, or even updating shared data structures). As for all C++ objects, the constructor is called when the star is created, and the destructor is called when it is destroyed. In addition, the setup
and begin
methods, if any, are called every time a new simulation run is started, the go
method (which always exists except for stars like BlackHole
and Null
that do nothing) is called each time a star is executed, and the wrapup
method is called after the simulation run completes without errors.2.4.2 Reading inputs and writing outputs
The precise mechanism for references to input and output portholes depends somewhat on the domain. This is because stars in the domain XXX
use objects of class InXXXPort
and OutXXXPort
(derived from PortHole
) for input and output, respectively. The examples we use here are for the SDF domain. See the appropriate domain chapter for variations that apply to other domains. PortHoles and Particles
In the SDF domain, normal inputs and outputs become members of type InSDFPort
and OutSDFPort
after the preprocessor is finished. These are derived from base class PortHole
. For example, given the following directive in the defstar
of an SDF star,
input {
name {in}
type {float}
}in
, of type InSDFPort
, will become part of the star. Particle
. Each particle contains data of the type specified in the type
subdirective of the input
or output
directive. %
" operating on a porthole returns a reference to a particle. Consider the following example:
Particle& currentSample = in%0;
Particle& pastSample = in%1;
...
}%
" operator specifies the delay of the access. A zero always means the most recent particle. A one means the particle arriving just before the most recent particle. The same rules apply to outputs. Given an output named out
, the same particles that are read from in
can be written to out
in the same order as follows:
go {
...
out%1 = pastSample;
out%0 = currentSample;
}out%n
returns a reference to a particle, and hence can accept an assignment. The assignment operator for the class Particle
is overloaded to make a copy of the data field of the particle. Particle
, as in the above examples, is useful for writing stars that accept anytype
of input. The operations need not concern themselves with the type of data contained by the particle. But it is far more common to operate numerically on the data carried by a particle. This can be done using a cast to a compatible type. For example, since in
above is of type float
, its data can be accessed as follows:
go {
Particle& currentSample = in%0;
double value = double(currentSample);
...
}
go {
double value = double(in%0);
...
}double(in%0)
can be used anywhere that a double can be used. In many contexts, where there is no ambiguity, the conversion operator can be omitted:
double value = in%0;Particle
, as shown in the above example. An operator <<
is defined for particle classes to make this more convenient. Consider the following example:
float t;
t = some value to be sent to the output
out%0 << t;
}<<
operator and the assignment operator; the latter operator copies Particles, the former operator loads data into particles. The type of the right-side operand of <<
may be int
, float
, double
, Fix
, Complex
or Envelope
; the appropriate type conversion will be performed. For more information on the Envelope
and Message
types, please see the chapter
"Data Types" on page 4-1.
SDF PortHole parameters
In the above example, where in%1
was referenced, some special action is required to tell Ptolemy that past input particles are to be saved. Special action is also required to tell the SDF scheduler how many particles will be consumed at each input and produced at each output when a star fires. This information can be provided through a call to setSDFParams
in the setup
method. This has the syntax
setup {
name.setSDFParams(multiplicity, past)
}name
is the name of the input or output porthole, multiplicity
is the number of particles consumed or produced, and past
is the maximum value that offset
can take in any expression of the form name%offset
. For example, if the go
method references name
%0
and name
%1
, then past
would have to be at least one. It is zero by default. Multiple PortHoles
Sometimes a star should be defined with n input portholes or n output portholes, where n is variable. This is supported by the class MultiPortHole
and its derived classes. An object of this class has a sequential list of PortHole
s. For SDF, we have the specialized derived class MultiInSDFPort
(which contains InSDFPorts
) and MultiOutSDFPort
(which contains OutSDFPorts
).
...
inmulti {
name {input_name}
type {input_type}
}
outmulti {
name {output_name}
type {output_type}
}
...
}MultiPortHole
, the MPHIter
iterator class should be used. Iterators are explained in more detail in
"Iterators" on page 3-10. Consider the following code segment from the definition of the SDF Fork
star:
input {
name{input}
type{ANYTYPE}
}
outmulti {
name{output}
type{= input}
}
go {
MPHIter nextp(output);
PortHole* p;
while ((p = nextp++) != 0)
(*p)%0 = input%0;
}type
of the output MultiPortHole
is inherited from the type of the input. The first line of the go
method creates an MPHIter
iterator called nextp
, initialized to point to portholes in output
. The "++
" operator on the iterator returns a pointer to the next porthole in the list, until there are no more portholes, at which time it returns NULL
. So the while
construct steps through all output portholes, copying the input particle data to each one. Add
star:
inmulti {
name {input}
type {float}
}
output {
name {output}
type {float}
}
go {
MPHIter nexti(input);
PortHole *p;
double sum = 0.0;
while ((p = nexti++) != 0)
sum += double((*p)%0);
output%0 << sum;
}MPHIter
iterator named nexti
is created and used to access the inputs. numberPorts
method of class MultiPortHole
, which returns the number of ports, is useful. This is called simply as portname
.numberPorts()
, and it returns an int
. Type conversion
The type conversion operators and "<<
" operators are defined as virtual methods in the base class Particle
. There are never really objects of class Particle
in the system; instead, there are objects of class IntParticle
, FloatParticle
, ComplexParticle
, and FixParticle
, which hold data of type int
, double
(not float!), Complex
, and Fix
, respectively (there are also MessageParticle
and a variety of matrix particles, described later). The conversion and loading operators are designed to "do the right thing" when an attempt is made to convert between mismatched types. int
to a double
or Complex
, or a double
to a Complex
, with no loss of information. Attempts to convert in the opposite direction work as follows: conversion of a Complex
to a double
produces the magnitude of the complex number. Conversion of a double
to an int
produces the greatest integer that is less than or equal to the double
value. There are also operators to convert to or from float
and Fix
.print
method, so a star that writes particles to a file can accept anytype
.2.4.3 States
A state is defined by the state
directive. The star can use a state to store data values, remembering them from one invocation to another. They differ from ordinary members of the star, which are defined using the public
, protected
, and private
directives, in that they have a name, and can be accessed from outside the star in systematic ways. For instance, the graphical interface pigi
permits the user to set any state with the A_SETTABLE
attribute to some value prior to a run, using the edit-params command. The interpreter provides similar functionality through the setstate
command. The state attributes are set in the state
directive. A state may be modified by the star code during a run. The attribute A_NONCONSTANT
is used as a
pragma to mark a state as one that gets modified during a run. There is currently no mechanism for checking the correctness of these attributes. State
, defined in the Ptolemy kernel. The derived state classes currently defined in the kernel are FloatState
, IntState
, ComplexState
, StringState
, FloatArrayState
, IntArrayState
, ComplexArrayState
, and StringArrayState
.
name { myState }
type { float }
default { 1.0 }
descriptor { Gain parameter. }
}FloatState
with default value 1.0. No attributes are defined, so A_CONSTANT
and A_SETTABLE
, the default attributes, are assumed. To use the value of a state, it should be cast to type double
, either explicitly by the programmer or implicitly by the context. For example, the value of this state can be accessed in the go
method as follows:
go {
output%0 << double(myState) * double(input%0);
}myState
has an explicit cast to double
; this cast is defined in FloatState
class. Similarly, a cast to int
is available for IntState
, to Complex
from ComplexState
, and to const
char*
for Stringstate
). In principle, it is possible to rely on the compiler to automatically invoke this cast. However:Fix
if the explicit cast is omitted in expressions similar to that above. The arithmetic is then performed using fixed-point point computations. This will be dramatically slower than double or integer arithmetic, and may yield unexpected results. It is best to explicitly cast states to the desired form. An exception is with simple assignment statements, likeSDFChop
star, in which use_past_inputs
is an integer state,
input.setSDFParams(int(nread),int(nread)+int(offset)-1);
else
input.setSDFParams(int(nread),int(nread)-1);Complex
is not a fundamental part of C++. We have implemented a subset of the Complex
class as defined by several library vendors; we use our own version for maximum portability. Using the ComplexState
class will automatically ensure the inclusion of the appropriate header files. A member of the Complex
class can be initialized and operated upon any number of ways. For details, see
"The Complex data type" on page 4-1.
A state may be updated by ordinary assignment in C++, as in the following lines
double t = expression;
myState = t;FloatState
class definition has overloaded the assignment operator ("=
") to set its value from a double
. Similarly, an IntState
can be set from an int
and a StringState
can be set from a char*
or const char*
. 2.4.4 Array States
The ArrayState
classes ( FloatArrayState
, IntArrayState
and ComplexArrayState
) are used to store arrays of data. For example,
state {
name { taps }
type { FloatArray }
default { "0.0 0.0 0.0 0.0" }
descriptor { An array of length four. }
}double
with dimension four, with each element initialized to zero. Quotes must surround the initial values. Alternatively, you can specify a file name with a prefix
<
. If you have a file named foo
that contains the default values for an array state, you can write,
default { "< foo 2.0" }
default { "0.5 < foo < bar" }ArrayState
objects: the two value strings
default { "1.0 [5]" }
default { "1.0 1.0 1.0 1.0 1.0" }[]
. The number of elements in an ArrayState
can be determined by calling its size
method. The size is not specified explicitly, but is calculated by scanning the default value. ArrayState
, suppose fState
is a FloatState
and aState
is a FloatArrayState
. The accesses, like those in the following lines, are routine:
fState = aState[1] + 0.5;
aState[1] = (double)fState * 10.0;
aState[0] = (double)fState * aState[2];FloatArrayState
, consider the FIR
star defined below. Note that this is a simplified version of the SDF FIR
star that does not permit interpolation or decimation.
name {FIR}
domain {SDF}
desc {
A Finite Impulse Response (FIR) filter.
}
input {
name {signalIn}
type {float}
}
output {
name {signalOut}
type {float}
}
state {
name {taps}
type {floatarray}
default { "-.04 -.001 .17 .37 .37 .17 -.0018 -.04" }
desc { Filter tap values. }
}
setup {
// tell the PortHole the maximum delay we will use
signalIn.setSDFParams(1, taps.size() - 1);
}
go {
double out = 0.0;
for (int i = 0; i < taps.size(); i++)
out += taps[i] * double(signalIn%i);
signalOut%0 << out;
}
}setup
method; this is necessary to allocate a buffer in the input PortHole
large enough to hold the particles that are accessed in the go
method. Notice the use of the size
method of the FloatArrayState
.
ptcl
interpreter session using the above FIR
star. Assume there is a galaxy called singen
that generates a sine wave. you can use it with the FIR
star, as in:
star fir FIR
star printer Printer
connect foop output fir signalIn
connect fir signalOut printer input
print fir
Star: mainGalaxy.fir
...
States in the star fir:
mainGalaxy.fir.taps type: FloatArray
initial value: -.040609 -.001628 .17853 .37665 .37665 .17853
-.001628 -.040609
current value:
0 -0.040609
1 -0.001628
2 .17853
3 .37665
4 .37665
5 .17853
6 -0.001628
7 -0.040609foo
", which contains the data:
1.1
-2.2
3.3
-4.4
setstate fir taps "<foo 5.5"
print fir
Star: mainGalaxy.fir
...
States in the star fir:
mainGalaxy.fir.taps type: FloatArray
initial value: <foo 5.5
current value:
0 1.1
1 -2.2
2 3.3
3 -4.4
4 5.5
PTOLEMY:FloatArrayState
are changed by a setstate
command. Also, notice that file values may be combined with string values; when