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.
4.4.1 Design philosophy
The 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. 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.
The design decision to store data entries in a C array rather than as an array of vector objects has a greater effect on performance than the decision whether to use row major or column major ordering. There are a couple of advantages to implementing a matrix class as an array of vector class objects: referencing an entry may be faster, and it is easier to do operations on a whole row or column of the matrix, depending on whether the format is an array of column vectors or an array of row vectors. An entry lookup in an array of row vectors requires two index lookups: one to find the desired row vector in the array and one to find the desired entry of that row. A linear array, in contrast, requires a multiplication to find the location of first element of the desired row and then an index lookup to find the column in that row. For example,
A[row][col]
is equivalent to looking up &data + (row*numRows + col)
if the entries are stored in a C array data[]
, whereas it is *(&rowArray + row) + col
if looking up the entry in an array of vectors format.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.4.4.2 The PtMatrix class
The 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.1 Also, the 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.
As explained previously, there are currently four data-specific matrix classes:
ComplexMatrix
, FixMatrix
, FloatMatrix
, and IntMatrix
. Each of these classes stores its entries in a standard C array named data
, which is an array of data objects corresponding to the PtMatrix
type: Complex
, Fix
, double
, or int
. These four matrix classes implement a common set of operators and functions; in addition, the ComplexMatrix
class has a few special methods such as conjugate()
and hermitian()
and the FixMatrix
class has a number of special constructors that allow the user to specify the precision of the entries in the matrix. Generally, all entries of a FixMatrix
will have the same precision.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
.4.4.3 Public functions and operators for the PtMatrix class
The functions and operators listed below are implemented by all matrix classes (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)
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)
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()
IntMatrix A
;XXXMatrix(int numRow, int numCol)
FloatMatrix A(3,2)
;numRow
by numCol
. Memory is allocated for the data storage but the entries are uninitialized.XXXMatrix(int numRow, int numCol, PortHole& p)
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)
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)
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)
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]
.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)
FixMatrix A(2,2,14,4);
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)
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)
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
.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)
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)
if(A == B) then
... 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)
if(A != B) then
...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
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)
A = B;
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)
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)
A += B;
A.entry(i)
+=
B.entry(i)
for each entry in A
. A
and B
must have the same dimensions.XXXMatrix& operator += (xxx value)
A += value;
value
to each entry in the matrix.XXXMatrix& operator -= (const XXXMatrix& src)
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)
A -= value;
value
from each entry in the matrix.XXXMatrix& operator *= (const XXXMatrix& src)
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 not equivalent to A = A * B. XXXMatrix& operator *= (xxx value)
A *= value
;value
.XXXMatrix& operator /= (const XXXMatrix& src)
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)
value
. The scalar value must be non-zero.XXXMatrix& operator identity()
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 - ()
B = -A;
Return a new matrix such that each element is the negative of the element of the source.XXXMatrix operator ~ ()
B = ~A;
Return a new matrix that is the transpose of the source.XXXMatrix operator ! ()
B = !A;
Return a new matrix which is the inverse of the source.XXXMatrix operator ^ (int exponent)
B = A^2;
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()
B = A.transpose();
This is the same as the ~ operator
but called by a function name instead of as an operator.XXXMatrix inverse()
B = A.inverse();
This is the same as the ! operator
but called by a function name instead of as an operator.ComplexMatrix conjugate()
ComplexMatrix B = A.conjugate();
ComplexMatrix
class only. ComplexMatrix hermitian()
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)
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)
A = 5 + B;
source
matrix added to a scalar
value.XXXMatrix operator + (const XXXMatrix& matrix, const xxx& scalar)
A = B + 5;
scalar
on the right.)XXXMatrix operator - (const XXXMatrix& left, const XXXMatrix& right)
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)
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)
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)
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)
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)
A = B * 5;
scalar
on the right.)int numRows()
int numCols()
Message* clone()
IntMatrix *B = A.clone();
Return a copy of *this.
StringList print()
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)
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()
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)
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 declaration
To declare an input porthole that accepts matrices, the following syntax is used:
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).
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.
Allowing delays on inputs
The cast to (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;
}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 Matrix outputs
To put a matrix into an output porthole, 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;
}
}
}4.4.5 Future extensions
After reviewing the libraries of numerical analysis software that is freely available on the Internet, it is clear that it would be beneficial to extend the 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++2 project which is developing a flexible matrix class of their own, besides porting the Fortran algorithms of Lapack into C++. This might possibly be incorporated in a future release.
2
LAPACK++: A Design Overview of Object-Oriented Extensions for High Performance Linear Algebra, by Jack J. Dongarra, Roldan Pozo, and David W. Walker, available on netlib.