This version is in HTML, a PDF version can be found in Chapter 3 of Volume 1: Introduction to Ptolemy II.
Authors: Edward A. Lee
Xiaojun Liu
Steve Neuendorffer
Neil Smyth
Yuhong Xiong
The Ptolemy II expression language provides infrastructure for specifying algebraic expressions textually and for evaluating them. The expression language is used to specify the values of parameters, guards and actions in state machines, and for the calculation performed by the Expression actor. In fact, the expression language is part of the generic infrastructure in Ptolemy II, and it can be used by programmers extending the Ptolemy II system. In this chapter, we describe how to use expressions from the perspective of a user rather than a programmer.
The simplest expression is a constant, which can be given either by the symbolic name of the constant, or by a literal. By default, the symbolic names of constants supported are PI, pi, E, e, true, false, i, j, NaN, Infinity, PositiveInfinity, NegativeInfinity, MaxUnsignedByte, MinUnsignedByte, MaxInt, MinInt, MaxLong, MinLong, MaxDouble, MinDouble. For example,
PI/2.0is a valid expression that refers to the symbolic name "PI" and the literal "2.0." The constants i and j are the imaginary number with value equal to the square root of -1. The constant NaN is "not a number," which for example is the result of dividing 0.0/0.0. The constant Infinity is the result of dividing 1.0/0.0. The constants that start with "Max" and "Min" are the maximum and minimum values for their corresponding types.
Numerical values without decimal points, such as "10" or "-3" are integers (type int). Numerical values with decimal points, such as "10.0" or "3.14159" are of type double. Numerical values without decimal points followed by the character "l" (el) or "L" are of type long. Unsigned integers followed by "ub" or "UB" are of type unsignedByte, as in "5ub". An unsignedByte has a value between 0 and 255; note that it not quite the same as the Java byte, which has a value between -128 and 127.
Numbers of type int, long, or unsignedByte can be specified in decimal, octal, or hexadecimal. Numbers beginning with a leading "0" are octal numbers. Numbers beginning with a leading "0x" are hexadecimal numbers. For example, "012" and "0xA" are both equal to the integer 10.
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. You can optionally write this "2 + 3*i".
Literal string constants are also supported. Anything between double quotes, "...", is interpreted as a string constant. The following built-in string-valued constants are defined:
The ptolemy.ptII.dir property is set automatically when Vergil or any other Ptolemy II executable is started up. You can also set it when you start a Ptolemy II process using the java command by a syntax like the following:
java -Dptolemy.ptII.dir=${PTII} classnamewhere classname is the full class name of a Java application.
The constants() utility function returns a record with all the globally defined constants. If you open the expression evaluator and invoke this function, you will see that its value is something like:
{CWD="C:\ptII\ptolemy\data\expr", E=2.718281828459, HOME="C:\Documents and Settings\eal", Infinity=Infinity, MaxDouble=1.7976931348623E308, MaxInt=2147483647, MaxLong=9223372036854775807L, MaxUnsignedByte=255ub, MinDouble=4.9E-324, MinInt=-2147483648, MinLong=-9223372036854775808L, MinUnsignedByte=0ub, NaN=NaN, NegativeInfinity=-Infinity, PI=3.1415926535898, PTII="c:\ptII", PositiveInfinity=Infinity, boolean=false, complex=0.0 + 0.0i, double=0.0, e=2.718281828459, false=false, fixedpoint=fix(0.0,2,1), general=present, i=0.0 + 1.0i, int=0, j=0.0 + 1.0i, long=0L, matrix=[], object=object(null),pi=3.1415926535898, scalar=present, string="", true=true, unknown=present, unsignedByte=0ub}
Expressions can contain identifiers that are references to variables within the scope of the expression. For example,
PI*x/2.0is valid if "x" is a variable in scope. In the expression evaluator, the variables that are in scope include the built-in constants plus any assignments that have been previously made. For example,
>> x = pi/2 1.5707963267949 >> sin(x) 1.0 >>In the context of Ptolemy II models, the variables in scope include all parameters defined at the same level of the hierarchy or higher. So for example, if an actor has a parameter named "x" with value 1.0, then another parameter of the same actor can have an expression with value "PI*x/2.0", which will evaluate to π/2.
Consider a parameter P in actor X which is in turn contained by composite actor Y. The scope of an expression for P includes all the parameters contained by X and Y, plus those of the container of Y, its container, etc. That is, the scope includes any parameters defined above in the hierarchy.
You can add parameters to actors (composite or not) by right clicking on the actor, selecting "Configure" and then clicking on "Add", or by dragging in a parameter from the utilities library. Thus, you can add variables to any scope, a capability that serves the same role as the "let" construct in many functional programming languages.
The arithmetic operators are +, -, *, /, ^, and %. Most of these operators operate on most data types, including arrays, records, and matrices. The ^ operator computes "to the power of" or exponentiation where the exponent can only be an int or an unsignedByte.
The unsignedByte, int and long types can only represent integer numbers. Operations on these types are integer operations, which can sometimes lead to unexpected results. For instance, 1/2 yields 0 if 1 and 2 are integers, whereas 1.0/2.0 yields 0.5. The exponentiation operator '^' when used with negative exponents can similarly yield unexpected results. For example, 2^-1 is 0 because the result is computed as 1/(2^1).
The % operation is a modulo or remainder operation. The result is the remainder after division. The sign of the result is the same as that of the dividend (the left argument). For example,
>> 3.0 % 2.0 1.0 >> -3.0 % 2.0 -1.0 >> -3.0 % -2.0 -1.0 >> 3.0 % -2.0 1.0The magnitude of the result is always less than the magnitude of the divisor (the right argument). Note that when this operator is used on doubles, the result is not the same as that produced by the remainder() function (see Table 5). For instance,
>> remainder(-3.0, 2.0) 1.0The remainder() function calculates the IEEE 754 standard remainder operation. It uses a rounding division rather than a truncating division, and hence the sign can be positive or negative, depending on complicated rules (see remainder()). For example, counter intuitively,
>> remainder(3.0, 2.0) -1.0
When an operator involves two distinct types, the expression language has to make a decision about which type to use to implement the operation. If one of the two types can be converted without loss into the other, then it will be. For instance, int can be converted losslessly to double, so 1.0/2 will result in 2 being first converted to 2.0, so the result will be 0.5. Among the scalar types, unsignedByte can be converted to anything else, int can be converted to double, and double can be converted to complex. Note that long cannot be converted to double without loss, nor vice versa, so an expression like 2.0/2L yields the following error message:
Error evaluating expression "2.0/2L" in .Expression.evaluator Because: divide method not supported between ptolemy.data.DoubleToken '2.0' and ptolemy.data.LongToken '2L' because the types are incomparable.
All scalar types have limited precision and magnitude. As a result of this, arithmetic operations are subject to underflow and overflow.
The relational operators are <, <=, >, >=, == and !=. They return type boolean. Note that these relational operators check the values when possible, irrespective of type. So, for example,
1 == 1.0returns true. If you wish to check for equality of both type and value, use the equals() method, as in
>> 1.equals(1.0) false
Boolean-valued expressions can be used to give conditional values. The syntax for this is
boolean ? value1 : value2If the boolean is true, the value of the expression is value1; otherwise, it is value2.
The logical boolean operators are &&, ||, !, & and |. They operate on type boolean and return type boolean. 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. Thus, for example, the expression "false && x" will evaluate to false irrespective of whether x is defined. On the other hand, "false & x" will throw an exception.
The << and >> operators performs arithmetic left and right shifts respectively. The >>> operator performs a logical right shift, which does not preserve the sign. They operate on unsignedByte, int, and long.
A PortParameter might be contained by an atomic actor or a composite actor. To put one in a composite actor, drag it into a model from the utilities library, as shown in figure
3.2. The resulting icon is actually a combination of two icons, one representing the port, and the other representing the parameter. These can be moved separately, but doing so might create confusion, so we recommend selecting both by clicking and dragging over the pair and moving both together.
To be useful, a PortParameter has to be given a name (the default name, "portParameter," is not very compelling). To change the name, right click on the icon and select "Customize Name," as shown in figure 3.2. In the figure, the name is set to "noiseLevel." Then set the default value by either double clicking or selecting "Configure." In the figure, the default value is set to 10.0.
An example of a library actor that uses a PortParameter is the Sinewave actor, which is found in the sources library in Vergil. It is shown in figure
3.3. If you double click on this actor, you can set the default values for frequency and phase. But both of these values can also be set by the corresponding ports, which are shown with grey fill.
Arrays are specified with curly brackets, e.g., "{1, 2, 3}" is an array of int, while "{"x", "y", "z"}" is an array of string. The types are denoted "{int}" and "{string}" respectively. An array is an ordered list of tokens of any type, with the only constraint being that the elements all have the same type. If an array is given with mixed types, the expression evaluator will attempt to losslessly convert the elements to a common type. Thus, for example,
{1, 2.3}has value
{1.0, 2.3}Its type is {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. The elements of an array can be accessed as follows:
>> {1.0, 2.3}(1) 2.3which yields 2.3. Note that indexing begins at 0. Of course, if name is the name of a variable in scope whose value is an array, then its elements may be accessed similarly, as shown in this example:
>> x = {1.0, 2.3} {1.0, 2.3} >> x(0) 1.0
Arithmetic operations on arrays are carried out element-by-element, as shown by the following examples:
>> {1, 2}*{2, 2} {2, 4} >> {1, 2}+{2, 2} {3, 4} >> {1, 2}-{2, 2} {-1, 0} >> {1, 2}^2 {1, 4} >> {1, 2}%{2, 2} {1, 0}An array can be checked for equality with another array as follows:
>> {1, 2}=={2, 2} false >> {1, 2}!={2, 2} trueFor other comparisons of arrays, use the compare() function (see Table 5). As with scalars, testing for equality using the == or != operators tests the values, independent of type. For example,
>> {1, 2}=={1.0, 2.0} true
In Ptolemy II, arrays are ordered sets of tokens. Ptolemy II also supports matrices, which are more specialized than arrays. They contain only certain primitive types, currently boolean, complex, double, fixedpoint, int, and long. Currently unsignedByte matrices are not supported. Matrices cannot contain arbitrary tokens, so they cannot, for example, contain matrices. They are intended for data intensive computations.
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]." In the syntax "[p:q:r]", p is the first element, q is the step between elements, and r is an upper bound on the last element. That is, the matrix will not contain an element larger than r. If a matrix with mixed types is specified, then the elements will be converted to a common type, if possible. Thus, for example, "[1.0, 1]" is equivalent to "[1.0, 1.0]," but "[1.0, 1L]" is illegal (because there is no common type to which both elements can be converted losslessly).
Reference to elements of matrices have the form "matrix(n, m)" or "name(n, m)" where name is the name of a matrix variable in scope, n is the row index, and m is the column index. Index numbers start with zero, as in Java, not 1, as in MATLAB. For example,
>> [1, 2; 3, 4](0,0) 1 >> a = [1, 2; 3, 4] [1, 2; 3, 4] >> a(1,1) 4
Matrix multiplication works as expected. For example, as seen in the expression evaluator (see figure 3.1),
>> [1, 2; 3, 4]*[2, 2; 2, 2] [6, 6; 14, 14]Of course, if the dimensions of the matrix don't match, then you will get an error message. To do element wise multiplication, use the multipyElements() function (see Table 6). Matrix addition and subtraction are element wise, as expected, but the division operator is not supported. Element wise division can be accomplished with the divideElements() function, and multiplication by a matrix inverse can be accomplished using the inverse() function (see Table 6). A matrix can be raised to an int or unsignedByte power, which is equivalent to multiplying it by itself some number of times. For instance,
>> [3, 0; 0, 3]^3 [27, 0; 0, 27]A matrix can also be multiplied or divided by a scalar, as follows:
>> [3, 0; 0, 3]*3 [9, 0; 0, 9]A matrix can be added to a scalar. It can also be subtracted from a scalar, or have a scalar subtracted from it. For instance,
>> 1-[3, 0; 0, 3] [-2, 1; 1, -2]A matrix can be checked for equality with another matrix as follows:
>> [3, 0; 0, 3]!=[3, 0; 0, 6] true >> [3, 0; 0, 3]==[3, 0; 0, 3] trueFor other comparisons of matrices, use the compare() function (see Table 5). As with scalars, testing for equality using the == or != operators tests the values, independent of type. For example,
>> [1, 2]==[1.0, 2.0] trueTo get type-specific equality tests, use the equals() method, as in the following examples:
>> [1, 2].equals([1.0, 2.0]) false >> [1.0, 2.0].equals([1.0, 2.0]) true >>
Fields may be accessed using the period operator. For example,
{a=1,b=2}.ayields 1. You can optionally write this as if it were a method call:
{a=1,b=2}.a()The arithmetic operators +, -, *, /, and % can be applied to records. If the records do not have identical fields, then the operator is applied only to the fields that match, and the result contains only the fields that match. Thus, for example,
{foodCost=40, hotelCost=100} + {foodCost=20, taxiCost=20}yields the result
{foodCost=60}You can think of an operation as a set intersection, where the operation specifies how to merge the values of the intersecting fields. You can also form an intersection without applying an operation. In this case, using the intersect() function, you form a record that has only the common fields of two specified records, with the values taken from the first record. For example,
>> intersect({a=1, c=2}, {a=3, b=4}) {a=1}
Records can be joined (think of a set union) without any operation being applied by using the merge() function. This function takes two arguments, both of which are record tokens. If the two record tokens have common fields, then the field value from the first record is used. For example,
merge({a=1, b=2}, {a=3, c=3})yields the result {a=1, b=2, c=3}.
Records can be compared, as in the following examples:
>> {a=1, b=2}!={a=1, b=2} false >> {a=1, b=2}!={a=1, c=2} trueNote that two records are equal only if they have the same field labels and the values match. As with scalars, the values match irrespective of type. For example:
>> {a=1, b=2}=={a=1.0, b=2.0+0.0i} trueThe order of the fields is irrelevant. Hence
>> {a=1, b=2}=={b=2, a=1} trueMoreover, record fields are reported in alphabetical order, irrespective of the order in which they are defined. For example,
>> {b=2, a=1} {a=1, b=2}To get type-specific equality tests, use the equals() method, as in the following examples:
>> {a=1, b=2}.equals({a=1.0, b=2.0+0.0i}) false >> {a=1, b=2}.equals({b=2, a=1}) true >>
Every element and subexpression in an expression represents an instance of the Token class in Ptolemy II (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 (token).methodName(args), where methodName 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 ArrayToken and RecordToken classes have a length() method, illustrated by the following examples:
{1, 2, 3}.length() {a=1, b=2, c=3}.length()each of 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()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].toArray()
function(arg1:Type, arg2:Type...) function bodywhere "function" is the keyword for defining a function. The type of an argument can be left unspecified, in which case the expression language will attempt to infer it. The function body gives an expression that defines the return value of the function. The return type is always inferred based on the argument type and the expression. For example:
function(x:double) x*5.0defines a function that takes a double argument, multiplies it by 5.0, and returns a double. The return value of the above expression is the function itself. Thus, for example, the expression evaluator yields:
>> function(x:double) x*5.0 (function(x:double) (x*5.0)) >>To apply the function to an argument, simply do
>> (function(x:double) x*5.0) (10.0) 50.0 >>Alternatively, in the expression evaluator, you can assign the function to a variable, and then use the variable name to apply the function. For example,
>> f = function(x:double) x*5.0 (function(x:double) (x*5.0)) >> f(10) 50.0 >>
Functions can be passed as arguments to certain "higher-order functions" that have been defined (see table Table 9). For example, the iterate() function takes three arguments, a function, an integer, and an initial value to which to apply the function. It applies the function first to the initial value, then to the result of the application, then to that result, collecting the results into an array whose length is given by the second argument. For example, to get an array whose values are multiples of 3, try
>> iterate(function(x:int) x+3, 5, 0) {0, 3, 6, 9, 12}The function given as an argument simply adds three to its argument. The result is the specified initial value (0) followed by the result of applying the function once to that initial value, then twice, then three times, etc.
Another useful higher-order function is the map() function. This one takes a function and an array as arguments, and simply applies the function to each element of the array to construct a result array. For example,
>> map(function(x:int) x+3, {0, 2, 3}) {3, 5, 6}
A typical use of functions in a Ptolemy II model is to define a parameter in a model whose value is a function. Suppose that the parameter named "f" has value "function(x:double) x*5.0". Then within the scope of that parameter, the expression "f(10.0)" will yield result 50.0.
Functions can also be passed along connections in a Ptolemy II model. Consider the model shown in figure
3.6. In that example, the Const actor defines a function that simply squares the argument. Its output, therefore, is a token with type function. That token is fed to the "f" input of the Expression actor. The expression uses this function by applying it to the token provided on the "y" input. That token, in turn, is supplied by the Ramp actor, so the result is the curve shown in the plot on the right.
A more elaborate use is shown in figure
3.7. In that example, the Const actor produces a function, which is then used by the Expression actor to create new function, which is then used by Expression2 to perform a calculation. The calculation performed here adds the output of the Ramp to the square of the output of the Ramp.
Functions can be recursive, as illustrated by the following (rather arcane) example:
>> fact = function(x:int,f:(function(x,f) int)) (x<1?1:x*f(x-1,f)) (function(x:int, f:function(a0:general, a1:general) int) (x<1)?1:(x*f((x-1), f))) >> factorial = function(x:int) fact(x,fact) (function(x:int) (function(x:int, f:function(a0:general, a1:general) int) (x<1)?1:(x*f((x-1), f))) (x, (function(x:int, f:function(a0:general, a1:general) int) (x<1)?1:(x*f((x-1), f))))) >> map(factorial, [1:1:5].toArray()) {1, 2, 6, 24, 120} >>The first expression defines a function named "fact" that takes a function as an argument, and if the argument is greater than or equal to 1, uses that function recursively. The second expression defines a new function "factorial" using "fact." The final command applies the factorial function to an array to compute factorials.
The expression language includes a set of functions, such as sin(), cos(), etc. The functions that are built in include all static methods of the classes shown
in Table 2, which together provide a rich set2. The functions currently available are shown in the tables in the appendix, which also show the argument types and return types.
In most cases, a function that operates on scalar arguments can also operate on arrays and matrices. Thus, for example, you can fill a row vector with a sine wave using an expression like
sin([0.0:PI/100:1.0])Or you can construct an array as follows,
sin({0.0, 0.1, 0.2, 0.3})Functions that operate on type double will also generally operate on int or unsignedByte, because these can be losslessly converted to double, but not generally on long or complex.
Tables of available functions are shown in the appendix. For example, Table 4 shows trigonometric functions. Note that these operate on double or complex, and hence on int and unsignedByte, which can be losslessly converted to double. The result will always be double. For example,
>> cos(0) 1.0These functions will also operate on matrices and arrays, in addition to the scalar types shown in the table, as illustrated above. The result will be a matrix or array of the same size as the argument, but always containing elements of type double
Table 5 shows other arithmetic functions beyond the trigonometric functions. As with the trigonometric functions, those that indicate that they operate on double will also work on int and unsignedByte, and unless they indicate otherwise, they will return whatever they return when the argument is double. Those functions in the table that take scalar arguments will also operate on matrices and arrays. For example, since the table indicates that the max() function can take int, int as arguments, then by implication, it can also take {int}, {int}. For example,
>> max({1, 2}, {2, 1}) {2, 2}Notice that the table also indicates that max() can take {int} as an argument. E.g.
>> max({1, 2, 3}) 3In the former case, the function is applied pointwise to the two arguments. In the latter case, the returned value is the maximum over all the contents of the single argument.
Table 6 shows functions that only work with matrices, arrays, or records (that is, there is no corresponding scalar operation). Recall that most functions that operate on scalars will also operate on arrays and matricesTable 7 shows utility functions for evaluating expressions given as strings or representing numbers as strings. Of these, the eval()) function is the most flexible.
A few of the functions have sufficiently subtle properties that they require further explanation. That explanation is here.The built-in function eval() will evaluate a string as an expression in the expression language. For example,
eval("[1.0, 2.0; 3.0, 4.0]")will return a matrix of doubles. The following combination can be used to read parameters from a file:
eval(readFile("filename"))where the filename can be relative to the current working directory (where Ptolemy II was started, as reported by the property user.dir), the user's home directory (as reported by the property user.home), or the classpath, which includes the directory tree in which Ptolemy II is installed.
Note that if eval() is used in an Expression actor, then it will be impossible for the type system to infer any more specific output type than general. If you need the output type to be more specific, then you will need to cast the result of eval(). For example, to force it to type double:
>> cast(double, eval("pi/2")) 1.5707963267949The traceEvaluation() function evaluates an expression given as a string, much like eval(), but instead of reporting the result, reports exactly how the expression was evaluated. This can be used to debug expressions, particularly when the expression language is extended by users.
The functions random() and gaussian() shown in Table 5 return one or more random numbers. With the minimum number of arguments (zero or two, respectively), they return a single number. With one additional argument, they return an array of the specified length. With a second additional argument, they return a matrix with the specified number of rows and columns.
There is a key subtlety when using these functions in Ptolemy II. In particular, they are evaluated only when the expression within which they appear is evaluated. The result of the expression may be used repeatedly without re-evaluating the expression. 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. The output will change, however, on successive runs of the model. In contrast, if this is used in an Expression actor, then each firing triggers an evaluation of the expression, and consequently will result in a new random number.
property()The property() function accesses system properties by name. Some possibly useful system properties are:
>> remainder(1,2) 1.0 >> remainder(3,2) -1.0Compare this to
>> 3%2 1which is different in two ways. The result numerically different and is of type int, whereas remainder() always yields a result of type double. The remainder() function is implemented by the java.lang.Math class, which calls it IEEEremainder(). The documentation for that class gives the following special cases:
The DCT function can take one, two, or three arguments. In all three cases, the first argument is an array of length
and the DCT returns an
Name | Third argument | Normalization |
---|---|---|
![]() |
||
![]() |
||
![]() |
The IDCT function is similar, and can also take one, two, or three arguments. The formula in this case is
fix(value, totalBits, integerBits)Thus, a fixed point value of 5.375 that uses 8 bit precision of which 4 bits are used to represent the (signed) integer part can be represented as:
fix(5.375, 8, 4)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 double-precision values without ever explicitly working with the fixed-point representation.
To make the FixToken accessible within the expression language, the following functions are available:
fix(5.34, 10, 4)
fix([ -.040609, -.001628, .17853 ], 10, 2)
quantize(5.34, 10, 4)
quantize([ -.040609, -.001628, .17853 ], 10, 2)
A model using one of the simple provided units systems is shown in figure
3.8. This unit system is called BasicUnits; the units it defines can be examined by double clicking on its icon, or by invoking Configure, as shown in figure
3.9. In that figure, we see that "meters", "meter", and "m" are defined, and are all synonymous. Moreover, "cm" is defined, and given value "0.01*meters", and "in", "inch" and "inches" are defined, all with value "2.54*cm".
In the example in figure 3.8, a constant with value "1.0 * meter" is fed into a Scale actor with scale factor equal to "2.0/ms". This produces a result with dimensions of length over time. If we feed this result directly into a Display actor, then it is displayed as "2000.0 meters/seconds", as shown in figure
3.10, top display. The canonical units for length are meters, and for time are seconds.
In figure 3.8, we also take the result and feed it to the InUnitsOf actor, which performs divides its input by its argument, and checks to make sure that the result is unitless. This tells us that 2 meters/ms is equal to about 78,740 inches/second.
The InUnitsOf actor can be used to ensure that numbers are interpreted correctly in a model, which can be effective in catching certain kinds of critical errors. For example, if in figure 3.8 we had entered "seconds/inch" instead of "inches/second" in the InUnitsOf actor, we would have gotten the exception in figure
3.11 instead of the execution in figure 3.10.
Units systems are built entirely on the expression language infrastructure in Ptolemy II. The units system icons actually represent instances of scope-extending attributes, which are attributes whose parameters are in scope as if those parameters were directly contained by the container of the scope-extending attribute. That is, scope-extending attributes can define a collection of variables and constants that can be manipulated as a unit. In version 2.0 of Ptolemy II, two fairly extensive units systems are provided, CGSUnitBase and ElectronicUnitBase. Nonetheless, these are intended as examples only, and can no doubt be significantly improved and extended.
Appendix A. Tables of Functionsfunction | argument type(s) | return type | description |
---|---|---|---|
abs | double or int or long or complex | double or int or long (complex returns double) | absolute value complex case: ![]() |
angle | complex | double in the range [-pi, pi] | angle or argument of the complex number: ![]() |
ceil | double | double | ceiling function, which returns the smallest (closest to negative infinity) double value that is not less than the argument and is an integer. |
compare | double, double | int | compare two numbers, returning -1, 0, or 1 if the first argument is less than, equal to, or greater than the second. |
conjugate | complex | complex | complex conjugate |
exp | double or complex | double in the range[0.0, infinity] or complex | exponential function (e^argument) complex case: ![]() |
floor | double | double | floor function, which is the largest (closest to positive infinity) value not greater than the argument that is an integer. |
gaussian | double, double or double, double, int, or double, double, int, int | double or {double} or [double] | one or more Gaussian random variables with the specified mean and standard deviation. |
imag | complex | double | imaginary part |
isInfinite | double | boolean | return true if the argument is infinite |
isNaN | double | boolean | return true if the argument is "not a number" |
log | double or complex | double or complex | natural logarithm complex case: ![]() |
log10 | double | double | log base 10 |
log2 | double | double | log base 2 |
max | double, double or int, int or long, long or unsignedByte, unsignedByte or {double} or {int} or {long} or {unsignedByte} | double or int or long or unsignedByte | maximum |
min | double, double or int, int or long, long or unsignedByte, unsignedByte or {double} or {int} or {long} or {unsignedByte} | double or int or long or unsignedByte | minimum |
neighborhood | type, type, double | boolean | return true if the first argument is in the neighborhood of the second, meaning that the distance is less than or equal to the third argument. The first two arguments can be any type for which such a distance is defined. For composite types, arrays, records, and matrices, then return true if the first two arguments have the same structure, and each corresponding element is in the neighborhood. |
pow | double, double or complex, complex | double or complex | first argument to the power of the second |
random | no arguments or int or int, int | double or {double} or [double] | one or more random numbers between 0.0 and 1.0 |
real | complex | double | real part |
remainder | double, double | double | remainder after division, according to the IEEE 754 floating-point standard (see remainder()). |
round | double | long | round to the nearest long, choosing the next greater integer when exactly in between, and throwing an exception if out of range. If the argument is NaN, the result is 0L. If the argument is out of range, the result is either MaxLong or MinLong, depending on the sign. |
roundToInt | double | int | round to the nearest int, choosing the next greater integer when exactly in between, and throwing an exception if out of range. If the argument is NaN, the result is 0. If the argument is out of range, the result is either MaxInt or MinInt, depending on the sign. |
sgn | double | int | -1 if the argument is negative, 1 otherwise |
sqrt | double or complex | double or complex | square root. If the argument is double with value less than zero, then the result is NaN. complex case: ![]() |
toDegrees | double | double | convert radians to degrees |
toRadians | double | double | convert degrees to radians |
function | argument type(s) | return type | description |
---|---|---|---|
eval | string | any type | evaluate the specified expression (see eval()). |
parseInt | string or string, int | int | return an int read from a string, using the given radix if a second argument is provided. |
parseLong | string or string, int | int | return a long read from a string, using the given radix if a second argument is provided. |
toBinaryString | int or long | string | return a binary representation of the argument |
toOctalString | int or long | string | return an octal representation of the argument |
toString | double or int or int, int or long or long, int | string | return a string representation of the argument, using the given radix if a second argument is provided. |
traceEvaluation | string | string | evaluate the specified expression and report details on how it was evaluated (see eval()). |
function | argument type(s) | return type | description |
---|---|---|---|
convolve | {double}, {double} or {complex}, {complex} | {double} or {complex} | Convolve two arrays and return an array whose length is sum of the lengths of the two arguments minus one. Convolution of two arrays is the same as polynomial multiplication. |
DCT | {double} or {double}, int or {double}, int, int | {double} | Return the discrete cosine transform of the specified array, using the specified (optional) length and normalization strategy (see DCT() and IDCT()). |
downsample | {double}, int or {double}, int, int | {double} | Return a new array with every ![]() ![]() ![]() |
FFT | {double} or {complex} or {double}, int {complex}, int | {complex} | Return the fast Fourier transform of the specified array. If the second argument is given with value ![]() ![]() |
generateBartlettWindow | int | {double} | Return a Bartlett (rectangular) window with the specified length. The end points have value 0.0, and if the length is odd, the center point has value 1.0. For length M + 1, the formula is: ![]() |
generateBlackmanWindow | int | {double} | Return a Blackman window with the specified length. For length M + 1, the formula is: ![]() |
generateBlackmanHarrisWindow | int | {double} | Return a Blackman-Harris window with the specified length. For length M + 1, the formula is: ![]() |
generateGaussianCurve | double, double, int | {double} | Return a Gaussian curve with the specified standard deviation, extent, and length. The extent is a multiple of the standard deviation. For instance, to get 100 samples of a Gaussian curve with standard deviation 1.0 out to four standard deviations, use generateGaussianCurve(1.0, 4.0, 100). |
generateHammingWindow | int | {double} | Return a Hamming window with the specified length. For length M + 1, the formula is: ![]() |
generateHanningWindow | int | {double} | Return a Hanning window with the specified length. For length M + 1, the formula is: ![]() |
generatePolynomialCurve | {double}, double, double, int | {double} | Return samples of a curve specified by a polynomial. The first argument is an array with the polynomial coefficients, beginning with the constant term, the linear term, the squared term, etc. The second argument is the value of the polynomial variable at which to begin, and the third argument is the increment on this variable for each successive sample. The final argument is the length of the returned array. |
generateRaisedCosinePulse | double, double, int | {double} | Return an array containing a symmetric raised-cosine pulse. This pulse is widely used in communication systems, and is called a "raised cosine pulse" because the magnitude its Fourier transform has a shape that ranges from rectangular (if the excess bandwidth is zero) to a cosine curved that has been raised to be non-negative (for excess bandwidth of 1.0). The elements of the returned array are samples of the function: where x is the excess bandwidth (the first argument) and T is the number of samples from the center of the pulse to the first zero crossing (the second argument). The samples are taken with a sampling interval of 1.0, and the returned array is symmetric and has a length equal to the third argument. With an excessBandwidth of 0.0, this pulse is a sinc pulse. |
generateRectangularWindow | int | {double} | Return an array filled with 1.0 of the specified length. This is a rectangular window. |
IDCT | {double} or {double}, int or {double}, int, int | {double} | Return the inverse discrete cosine transform of the specified array, using the specified (optional) length and normalization strategy (see DCT() and IDCT()). |
IFFT | {double} or {complex} or {double}, int {complex}, int | {complex} | Return the inverse fast Fourier transform of the specified array. If the second argument is given with value ![]() ![]() |
nextPowerOfTwo | double | int | Return the next power of two larger than or equal to the argument. |
poleZeroToFrequency | {complex}, {complex}, complex, int | {complex} | Given an array of pole locations, an array of zero locations, a gain term, and a size, return an array of the specified size representing the frequency response specified by these poles, zeros, and gain. This is calculated by walking around the unit circle and forming the product of the distances to the zeros, dividing by the product of the distances to the poles, and multiplying by the gain. |
sinc | double | double | Return the sinc function, ![]() |
toDecibels | double | double | Return ![]() ![]() |
unwrap | {double} | {double} | Modify the specified array to unwrap the angles. That is, if the difference between successive values is greater than ![]() ![]() ![]() ![]() |
upsample | {double}, int | {double} | Return a new array that is the result of inserting ![]() ![]() ![]() ![]() |