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 PortHoles. 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