The primary support for matrix types in Ptolemy is the

`PtMatrix`

class. `PtMatrix`

is derived from the `Message`

class, and uses the various kernel support functions for working with the `Message`

data type as described in Section
4.3 on page 4-14. This section discusses the `PtMatrix`

class and how to write stars and programs using this class.`PtMatrix`

class implements two dimensional arrays. There are four key classes derived from `PtMatrix`

: `ComplexMatrix`

, `FixMatrix`

, `FloatMatrix`

, and `IntMatrix`

. (Note that `FloatMatrix`

is a matrix of C++ `double`

s.) A review of matrix classes implemented by other programmers revealed two main styles of implementation: a vector of vectors, or a simple array. In addition, there are two main formats of storing the entries: column-major ordering, where all the entries in the first column are stored before the entries of the second column, and row-major ordering, where the entries are stored starting with the first row. Column-major ordering is how Fortran stores arrays whereas row-major ordering is how C stores arrays.
The Ptolemy `PtMatrix`

class stores data as a simple C array, and therefore uses row-major ordering. Row-major ordering also seems more natural for operations such as image and video processing, but it might make it more difficult to interface Ptolemy's `PtMatrix`

class with Fortran library calls. The limits of interfacing Ptolemy's `PtMatrix`

class with other software is discussed in Section
4.4.5 on page 4-33.

Although the array of vectors format has faster lookups, it is also more expensive to create and delete the matrix. Each vector of the array must be created in the matrix constructor, and each vector must also be deleted by the matrix destructor. The array of vectors format also requires more memory to store the data and the extra array of vectors.

With the advantages and disadvantages of the two systems in mind, we chose to implement the `PtMatrix`

class with the data stored in a standard C array. Ptolemy's environment is such that matrices are created and deleted constantly as needed by stars: this negates much of the speedup gained from faster lookups. Also, we felt it was important to keep the design of the class simple and the memory usage efficient because of Ptolemy's increasing size and complexity.

`PtMatrix`

base class is derived from the `Message`

class so that we can use Ptolemy's `Envelope`

class and message-handling system. However, the `MessageParticle`

class is not used by the `PtMatrix`

class; instead, there are special `MatrixEnvParticle`

classes defined to handle type checking between the various types of matrices. This allows the system to automatically detect when two stars with different matrix type inputs and outputs are incorrectly connected together.`MatrixEnvParticle`

class has some special functions not found in the standard `MessageParticle`

class to allow easier handling of `PtMatrix`

class messages. A discussion of how to pass `PtMatrix`

class objects using the `MatrixEnvParticles`

can be found in a following section.
The matrix classes were designed to take full advantage of operator overloading in C++ so that operations on matrix objects can be written much like operations on scalar ones. For example, the two-operand multiply `operator`

`*`

has been defined so that if `A`

and `B`

are matrices, `A`

`*`

`B`

will return a third matrix that is the matrix product of `A`

and `B`

.

`ComplexMatrix`

, `FixMatrix`

, `FloatMatrix`

, and `IntMatrix`

) unless otherwise noted. The symbols used are:`XXX`

refers to one of the following:`Complex`

,`Fix`

,`Float`

, or`Int`

`xxx`

refers to one of the following:`Complex`

,`Fix`

,`double`

, or`int`

`xxx& entry(int i) `

Example:

` A.entry(i)`

Return the `i`

th entry of the matrix when its data storage is considered to be a linear array. This is useful for quick operations on every entry of the `matrix`

without regard for the specific (row,column) position of that entry. The total number of entries in the matrix is defined to be `numRows() * numCols()`

, with indices ranging from 0 to `numRows() * numCols() - 1`

. This function returns a reference to the actual entry in the matrix so that assignments can be made to that entry. In general, functions that wish to linearly reference each entry of a matrix `A`

should use this function instead of the expression `A.data[i]`

because classes which are derived from `PtMatrix `

can then overload the `entry()`

method and reuse the same functions.`xxx* operator [] (int row) `

Example:

` A[row][column]`

Return a pointer to the start of the row in the matrix's data storage. (This operation is different to matrix classes defined as arrays of vectors, in which the `[]`

operator returns the vector representing the desired row.) This operator is generally not used alone but with the `[] `

operator defined on C arrays, so that `A[i][j]`

will give you the entry of the matrix in the `i`

th row and `j`

th column of the data storage. The range of rows is from 0 to `numRows()-1`

and the range of columns is from 0 to `numCols()-1`

.`XXXMatrix() `

Example:

` IntMatrix A`

;Create an uninitialized matrix. The number of rows and columns are set to zero and no memory is allocated for the storage of data.

`XXXMatrix(int numRow, int numCol) `

Example:

` FloatMatrix A(3,2)`

;Create a matrix with dimensions

`numRow`

by `numCol`

. Memory is allocated for the data storage but the entries are uninitialized.`XXXMatrix(int numRow, int numCol, PortHole& p) `

Example:

` ComplexMatrix(3,3,myPortHole)`

Create a matrix of the given dimensions and initialize the entries by assigning to them values taken from the porthole `my`

`PortHole`

. The entries are assigned in a rasterized sequence so that the value of the first particle removed from the porthole is assigned to entry (0,0), the second particle's value to entry (0,1), etc. It is assumed that the porthole has enough particles in its buffer to fill all the entries of the new matrix.`XXXMatrix(int numRow, int numCol, XXXArrayState& dataArray) `

Example:

`IntMatrix A(2,2,myIntArrayState);`

Create a matrix with the given dimensions and initialize the entries to the values in the given `ArrayState`

. The values of the `ArrayState`

fill the matrix in rasterized sequence so that entry (0,0) of the matrix is the first entry of the `ArrayState`

, entry (0,1) of the matrix is the second, etc. An error is generated if the `ArrayState`

does not have enough values to initialize the whole matrix.`XXXMatrix(const XXXMatrix& src) `

Example:

` FixMatrix A(B);`

This is the copy constructor. A new matrix is formed with the same dimensions as the source matrix and the data values are copied from the source.`XXXMatrix(const XXXMatrix& src, int startRow, int startCol, int numRow, int numCol) `

Example:

` IntMatrix A(B,2,2,3,3);`

This special "submatrix" constructor creates a new matrix whose values come from a submatrix of the source. The arguments `startRow`

and `startCols`

specify the starting row and column of the source matrix. The values `numRow`

and `numCol`

specify the dimensions of the new matrix. The sum `startRow + numRow`

must not be greater than the maximum number of rows in the source matrix; similarly, `startCol + numCol`

must not be greater than the maximum number of columns in the source. For example, if `B`

is a matrix with dimension (4,4), then `A(B,1,1,2,2)`

would create a new matrix `A`

that is a (2,2) matrix with data values from the center quadrant of matrix `B`

, so that `A[0][0] == B[1][1]`

, `A[0][1] == B[1][2]`

, `A[1][0] == B[2][1]`

, and `A[1][1] == B[2][2]`

.The following are special constructors for the

`FixMatrix`

class that allow the programmer to specify the precision of the entries of the `FixMatrix`

.`FixMatrix(int numRow, int numCol, int length, int intBits) `

Example:

` FixMatrix A(2,2,14,4);`

Create a

`FixMatrix`

with the given dimensions such that each entry is a fixed-point number with precision as given by the `length`

and `intBits`

inputs.`FixMatrix(int numRow, int numCol, int length, int intBits, PortHole& myPortHole) `

Example:

` FixMatrix A(2,2,14,4);`

Create a `FixMatrix`

with the given dimensions such that each entry is a fixed-point number with precision as given by the `length`

and `intBits`

inputs and initialized with the values that are read from the particles contained in the porthole `myPortHole`

.`FixMatrix(int numRow, int numCol, int length, int intBits, FixArrayState& dataArray) `

Example:

` FixMatrix A(2,2,14,4);`

Create a `FixMatrix`

with the given dimensions such that each entry is a fixed-point number with precision as given by the `length`

and `intBits`

inputs and initialized with the values in the given `FixArrayState`

.There are also special copy constructors for the

`FixMatrix`

class that allow the programmer to specify the precision of the entries of the `FixMatrix`

as they are copied from the sources. These copy constructors are usually used for easy conversion between the other matrix types. The last argument specifies the type of masking function (truncate, rounding, etc.) to be used when doing the conversion.`FixMatrix(const XXXMatrix& src, int length, int intBits,`

int round)

Example:

` FixMatrix A(CxMatrix,4,14,TRUE);`

Create a `FixMatrix`

with the given dimensions such that each entry is a fixed-point number with precision as given by the `length`

and `intBits`

arguments. Each entry of the new matrix is copied from the corresponding entry of the src matrix and converted as specified by the `round`

argument.`int operator == (const XXXMatrix& src) `

Example:

` if(A == B) then`

... Return

`TRUE`

if the two matrices have the same dimensions and every entry in `A`

is equal to the corresponding entry in `B`

. Return `FALSE`

otherwise.`int operator != (const XXXMatrix& src) `

Example:

` if(A != B) then`

...Return

`TRUE`

if the two matrices have different dimensions or if any entry of `A`

differs from the corresponding entry in `B`

. Return `FALSE`

otherwise.`operator XXXMatrix () const `

Example:

` FloatMatrix C = A * (FloatMatrix)B;`

Convert a matrix of one type into another. These conversions allow the various arithmetic operators, such as `*`

and `+`

, to be used on matrices of different type. For example, if `A`

in the example above is a (3,3) `FloatMatrix`

and `B`

is a (3,2) `IntMatrix`

, then `C`

is a `FloatMatrix`

with dimensions (3,2).`A`

is usually the lvalue (`*this`

). All operators return `*this`

:`XXXMatrix& operator = (const XXXMatrix& src) `

Example:

` A = B;`

This is the assignment operator: make

`A`

into a matrix that is a copy of `B`

. If `A`

already has allocated data storage, then the size of this data storage is compared to the size of `B`

. If they are equal, then the dimensions of `A`

are simply set to those of `B`

and the entries copied. If they are not equal, the data storage is freed and reallocated before copying.`XXXMatrix& operator = (xxx value) `

Example:

` A = value;`

Assign each entry of `A`

to have the given `value`

. Memory management is handled as in the previous operator.`XXXMatrix& operator += (const XXXMatrix& src) `

Example:

` A += B;`

Perform the operation

`A.entry(i)`

`+=`

`B.entry(i)`

for each entry in `A`

. `A`

and `B`

must have the same dimensions.`XXXMatrix& operator += (xxx value) `

Example:

` A += value;`

Add the scalar

`value`

to each entry in the matrix.`XXXMatrix& operator -= (const XXXMatrix& src) `

Example:

` A -= B;`

Perform the operation `A.entry(i)`

`-=`

`B`

`.entry(i)`

for each entry in `A`

. `A`

and `B`

must have the same dimensions.`XXXMatrix& operator -= (xxx value) `

Example:

` A -= value;`

Subtract the scalar

`value`

from each entry in the matrix.`XXXMatrix& operator *= (const XXXMatrix& src) `

Example:

` A *= B;`

Perform the operation `A.entry(i)`

`*=`

`B.entry(i)`

for each entry in `A`

. `A`

and `B`

must have the same dimensions. Note: this is an elementwise operation and is `XXXMatrix& operator *= (xxx value) `

Example:

` A *= value`

;Multiply each entry of the matrix by the scalar

`value`

.`XXXMatrix& operator /= (const XXXMatrix& src) `

Example:

` A /= B;`

Perform the operation `A.entry(i)`

`/=`

`B.entry(i)`

for each entry in `A`

. `A`

and `B`

must have the same dimensions.`XXXMatrix& operator /= (xxx value) `

Example: A /= value

Divide each entry of the matrix by the scalar

`value`

. The scalar value must be non-zero.`XXXMatrix& operator identity() `

Example:

` A.identity();`

Change `A`

to be an identity matrix so that each entry on the diagonal is 1 and all off-diagonal entries are 0.`XXXMatrix operator - () `

Example:

` B = -A; `

Return a new matrix such that each element is the negative of the element of the source.`XXXMatrix operator ~ () `

Example:

` B = ~A;`

Return a new matrix that is the transpose of the source.`XXXMatrix operator ! () `

Example:

` B = !A;`

Return a new matrix which is the inverse of the source.`XXXMatrix operator ^ (int exponent) `

Example:

` B = A^2;`

Return a new matrix which is the source matrix to the given

`exponent`

power. The `exponent`

can be negative, in which case the `exponent`

is first treated as a positive number and the final result is then inverted. So `A^2 == A*A`

and `A^(-3) == !(A*A*A)`

.`XXXMatrix transpose() `

Example:

` B = A.transpose();`

This is the same as the `~ operator`

but called by a function name instead of as an operator.`XXXMatrix inverse() `

Example:

` B = A.inverse();`

This is the same as the `! operator`

but called by a function name instead of as an operator.`ComplexMatrix conjugate() `

Example:

` ComplexMatrix B = A.conjugate();`

Return a new matrix such that each element is the complex conjugate of the source. This function is defined for the

`ComplexMatrix`

class only. `ComplexMatrix hermitian() `

Example:

` ComplexMatrix B = A.hermitian();`

Return a new matrix which is the Hermitian Transpose (conjugate transpose) of the source. This function is defined for the `ComplexMatrix`

class only. `XXXMatrix operator + (const XXXMatrix& left, const XXXMatrix& right) `

Example:

` A = B + C;`

Return a new matrix which is the sum of the first two. The `left`

and `right`

source matrices must have the same dimensions.`XXXMatrix operator + (const xxx& scalar, const XXXMatrix& matrix) `

Example:

` A = 5 + B;`

Return a new matrix that has entries of the

`source`

matrix added to a `scalar`

value.`XXXMatrix operator + (const XXXMatrix& matrix, const xxx& scalar) `

Example:

` A = B + 5;`

Return a new matrix that has entries of the source matrix added to a

scalar value. (This is the same as the previous operator but with the

`scalar`

on the right.)`XXXMatrix operator - (const XXXMatrix& left, const XXXMatrix& right) `

Example:

` A = B - C;`

Return a new matrix which is the difference of the first two. The `left`

and `right`

source matrices must have the same dimensions.`XXXMatrix operator - (const xxx& scalar, const XXXMatrix& matrix) `

Example:

` A = 5 - B;`

Return a new matrix that has the negative of the entries of the source `matrix`

added to a `scalar`

value.`XXXMatrix operator - (const XXXMatrix& matrix, const xxx& scalar) `

Example:

` A = B - 5;`

Return a new matrix such that each entry is the corresponding entry of the source `matrix`

minus the `scalar`

value.`XXXMatrix operator * (const XXXMatrix& left, const XXXMatrix& right) `

Example:

` A = B * C;`

Return a new matrix which is the matrix product of the first two. The `left`

and `right`

source matrices must have compatible dimensions (i.e. `A.numCols() == B.numRows()`

.`XXXMatrix operator * (const xxx& scalar, const XXXMatrix& matrix) `

Example:

` A = 5 * B;`

Return a new matrix that has entries of the source `matrix`

multiplied by a `scalar`

value.`XXXMatrix operator * (const XXXMatrix& matrix, const xxx& scalar) `

Example:

` A = B * 5;`

Return a new matrix that has entries of the source matrix multiplied

by a scalar value. (This is the same as the previous operator but with the

`scalar`

on the right.)`int numRows() `

Return the number of rows in the matrix.

`int numCols() `

Return the number of columns in the matrix.

`Message* clone() `

Example:

`IntMatrix *B = A.clone();`

Return a copy of `*this.`

`StringList print() `

Example:

` A.print()`

Return a formatted `StringList`

that can be printed to display the contents of the matrix in a reasonable format.`XXXMatrix& multiply (const XXXMatrix& left, const XXXMatrix& right, XXXMatrix& result) `

Example:

` multiply(A,B,C);`

This is a faster 3 operand form of matrix multiply such that the result matrix is passed as an argument so that we avoid the extra copy step that is involved when we write `C = A * B`

.`const char* dataType() `

Example:

` A.dataType()`

Return a string that specifies the name of the type of matrix. The strings are "`ComplexMatrix`

", "`FixMatrix`

", "`FloatMatrix`

", and "`IntMatrix`

".`int isA(const char* type) `

Example:

` `

`if(A.isA("FixMatrix")) then ...`

Return `TRUE`

if the argument string matches the type string of the matrix.`$PTOLEMY/src/domains/sdf/matrix/stars/*.pl`

and `$PTOLEMY/src/domains/sdf/image/stars/*.pl`

for more examples`Message`

class. Strange errors will occur if the star deletes the matrix before it is used by another star later in the execution sequence.`_M`

suffix to distinguish them from stars that operate on scalar particles. For example, the `SDFGain_M`

star multiplies an input matrix by a scalar value and outputs the resulting matrix. This is in contrast to `SDFGain`

, which multiplies an input value held in a `FloatParticle`

by a double and puts that result in an output `FloatParticle`

.`PtMatrix`

classes, it must include the file `Matrix.h`

in either its `.h`

or `.cc`

file. If the star has a matrix data member, then the declaration`Star`

definition. Otherwise, the declarationTo declare an input porthole that accepts matrices, the following syntax is used:

name { inputPortHole }

type { FLOAT_MATRIX_ENV }

}

`COMPLEX_MATRIX_ENV`

, `FLOAT_MATRIX_ENV`

, `FIX_MATRIX_ENV`

, or `INT_MATRIX_ENV`

. The icons created by Ptolemy will have terminals that are thicker and that have larger arrow points than the terminals for scalar particle types. The colors of the terminals follow the pattern of colors for scalar data types (e.g., blue represents Float and FloatMatrix).(inputPortHole%0).getMessage(inPkt);

const FloatMatrix& inputMatrix =

*(const FloatMatrix *)inPkt.myData();

`Envelope`

, which is used to access the matrix. Details of the `Envelope`

class are given in
"Use of the Envelope class" on page 4-17. The second line fills the envelope with the input matrix. Note that, because of the reference-counting mechanism, this line does not make a copy of the matrix. The last two lines extract a reference to the matrix from the envelope. It is up to the programmer to make sure that the cast agrees with the definition of the input port.
Because multiple envelopes might reference the same matrix, a star is generally not permitted to modify the matrix held by the `Envelope`

. Thus, the function `myData()`

returns a `const Message *`

. We cast that to be a `const FloatMatrix *`

and then de-reference it and assign the value to `inputMatrix`

. It is generally better to handle matrices by reference instead of by pointer because it is clearer to write "`A + B`

" rather than "`*A + *B`

" when working with matrix operations. Stars that wish to modify an input matrix should access it using the `writableCopy`

method, as explained in
"Use of the Envelope class" on page 4-17.

`(const FloatMatrix *)`

above is not always safe. Even if the source star is known to provide matrices of the appropriate type, a delay on the arc connecting the two stars can cause problems. In particular, delays in dataflow domains are implemented as initial particles on the arcs. These initial particles are given the value "zero" as defined by the type of particle. For `Message`

particles, a "zero" is an uninitialized `Message`

particle containing a "dummy" data value. This dummy `Message`

will be returned by the `myData`

method in the third line of the above code fragment. The dummy message is not a `FloatMatrix`

, rendering the above cast invalid. A star that expects matrix inputs must have code to handle empty particles. An example is:if(inPkt.empty()) {

FloatMatrix& result = *(new FloatMatrix(int(numRows),

int(numCols)));

result = 0.0;

output%0 << result;

}

A possible alternative to outputting a zero matrix is to simply pass that empty `MessageParticle`

along. This approach, however, can lead to counterintuitive results. Suppose that empty message reaches a display star like `TkText`

, which will attempt to call the `print()`

method of the object. An empty message has a `print()`

method that results in a message like

This is likely to prove extremely confusing to users, so we strongly recommend that each matrix star handle the empty input in a reasonable way, and produce a non-empty output.

, the syntax is:// ... do some operations on the outMatrix

outputPortHole%0 << outMatrix;

`<< operator`

for `MatrixEnvParticles`

to support `PtMatrix`

class inputs. The standard use of the `MessageParticle`

class requires you to put your message into an envelope first and then use `<<`

on the envelope (see
"Use of the Envelope class" on page 4-17), but we have specialized this so that the extra operation of creating an envelope first is not explicit.
Here is an example of a complete star definition that inputs and outputs` `

matrices:

defstar {

name { Mpy_M }

domain { SDF }

desc {

Does a matrix multiplication of two input Float matrices A and B to

produce matrix C.

Matrix A has dimensions (numRows,X).

Matrix B has dimensions (X,numCols).

Matrix C has dimensions (numRows,numCols).

The user need only specify numRows and numCols. An error will be

generated automatically if the number of columns in A does not match

the number of columns in B.

}

input {

name { Ainput }

type { FLOAT_MATRIX_ENV }

}

input {

name { Binput }

type { FLOAT_MATRIX_ENV }

}

output {

name { output }

type { FLOAT_MATRIX_ENV }

}

defstate {

name { numRows }

type { int }

default { 2 }

desc { The number of rows in Matrix A and Matrix C.}

}

defstate {

name { numCols }

type { int }

default { 2 }

desc { The number of columns in Matrix B and Matrix C}

}

ccinclude { "Matrix.h" }

go {

// get inputs

Envelope Apkt;

(Ainput%0).getMessage(Apkt);

const FloatMatrix& Amatrix =

*(const FloatMatrix *)Apkt.myData();

Envelope Bpkt;

(Binput%0).getMessage(Bpkt);

const FloatMatrix& Bmatrix =

*(const FloatMatrix *)Bpkt.myData();

// check for "null" matrix inputs, which could be

// caused by delays on the input line

if(Apkt.empty() || Bpkt.empty()) {

// if either input is empty, return a zero

// matrix with the state dimensions

FloatMatrix& result =

*(new FloatMatrix(int(numRows),

int(numCols)));

result = 0.0;

output%0 << result;

}

else {

// Amatrix and Bmatrix are both valid

if((Amatrix.numRows() != int(numRows)) ||

(Bmatrix.numCols() != int(numCols))) {

Error::abortRun(*this,

"Dimension size of FloatMatrix inputs do ",

"not match the given state parameters.");

return;

}

// do matrix multiplication

FloatMatrix& result =

*(new FloatMatrix(int(numRows),

int(numCols)));

// we could write

// result = Amatrix * Bmatrix;

// but the following is faster

multiply(Amatrix,Bmatrix,result);

output%0 << result;

}

}

}

`PtMatrix`

class by adding those well-tested libraries as callable functions. Unfortunately, many of those libraries are currently only available in Fortran, and there are some incompatibilities with Fortran's column major ordering and C's row major ordering. Those problems would still exist even if the Fortran code was converted to C. There are a few groups which are currently working on C++ ports of the numerical analysis libraries. One notable group is the Lapack++

*Copyright © 1990-1997, University of California. All rights
reserved.*