Matrix elements are simple but versatile containers for matrices of numbers. They are non-graphical elements.
The master header file for matrix element functionality is msmatrix.h
. The matrix element API is dependent upon ~s"Embedded Arrays".
The data entries in a matrix element are physically stored as an essentially contiguous list of values of a single primitive data type. Currently, two primitive data types are supported:
int
) double
) Various attributes (discussed below) indicate how these raw numbers are logically grouped into the entries and rows of a matrix, and how the matrix entries should be interpreted by applications.
A typical element descriptor hierarchy for a matrix element storing int
data is listed below. Matrices storing other data types are similar; e.g., substitute matrix double data elements (103) for matrices of double
data.
There is no artificial limit on the quantity of matrix data: multiple matrix data elements are stored underneath the matrix header element if one is not enough to hold all of the matrix data. For the most part, this chaining of sibling elements is transparent throughout the matrix API.
Attributes which determine the matrix structure are contained in the matrix header and returned by mdlMatrix_getFields. The ouput parameters of this function are listed below and discussed in more detail in the following subsections.
Parameter Name | Contents Returned |
---|---|
pNumDataPerStruct | number of primitive data values per matrix entry |
pNumStructsPerRow | number of matrix entries per logical row |
pTag | application-specific matrix identifier |
pIndexFamily | identifier shared by parallel matrices |
pIndexedBy | matrix tag of corresponding index matrix (if any) |
Table 1: Output parameters of mdlMatrix_getFields
There are two ways of logically structuring the list of data stored in a matrix element: structs and rows.
The data in a matrix are organized foremost into n-tuples of primitive values called structs
. The function mdlMatrix_getFields returns n in pNumDataPerStruct
. Each struct represents one entry in the logical matrix. For example, a matrix of double
s logically organized into xyz coordinates would have 3 double
s per struct; a matrix of int
s intended as indices into another data matrix would typically have 1 int
per struct.
Matrix structs are in turn organized into logical rows
of m structs each. The function mdlMatrix_getFields returns m in pNumStructsPerRow
. The matrix element emulates a two-dimensional grid when m > 1. When m = 1, each logical row contains one struct, and so the matrix is essentially a list. This latter case is common in data matrices which rely on a separate index matrix to impose structure. Variable-sized logical rows are also possible in index matrices which have m = 1; each row of indices is terminated by a special data value (encoded in the matrix transform type).
Note that struct and row counts are not stored in the matrix header. This avoids problems with consistency. Instead, each matrix data element stores the number of primitive data values it contains (more on this below).
Each matrix header element stores an integer identifier which is used to distinguish different matrices in a collection. The function mdlMatrix_getFields returns this identifier in pTag
. The function mdlMatrix_isTaggedMatrix verifies that the given matrix has the given tag. The function mdlElmdscr_findMatrixChild searches the children of the given element descriptor for the first matrix element with the given tag.
Note that matrix elements that are siblings underneath some other element descriptor should be uniquely tagged by your application, since the second of a pair of matrix children with the same tag will not be found by mdlElmdscr_findMatrixChild.
Giving a matrix a fixed row size m > 1 is one way of logically grouping its data; a more flexible approach is to use a separate matrix of indices
into the data matrix's list of structs. Data matrices that are indexed usually have row size m = 1.
Each matrix header stores an index family identifier that serves to group index matrices into families with a common structure. The function mdlMatrix_getFields returns the index family in pIndexFamily
; the special value MATRIX_ELM_INDEX_FAMILY_NONE
(defined in mselems.h
) signifies that the matrix is not an index matrix.
There are two matrix tags stored in each matrix header: its own tag, and the tag of its index matrix (if any). The function mdlMatrix_getFields returns the tag of the relevant index matrix in pIndexedBy
; the special value MATRIX_ELM_TAG_NONE
(defined in mselems.h
) signifies that the matrix is not indexed.
Note that an index matrix does not store the tag of the data matrix (or matrices) that it indexes.
Attributes which pertain to the matrix data are contained in each matrix data element and returned by mdlMatrixData_getFields. The ouput parameters of this function are listed below.
Parameter Name | Contents Returned |
---|---|
pNumAllocated | maximum number of primitive data values storable |
pNumUsed | actual number of primitive data values in use |
pTransformType | code indicating type of data or index |
Table 2: Output parameters of mdlMatrixData_getFields
The first two output parameters of mdlMatrixData_getFields hold counts of how many primitive data values (e.g., int
s, double
s) the given matrix data element can store, and how many it currently stores. These counts do not include the values in sibling matrix data elements under the same matrix header. Generally, these counts are equal.
The matrix data transform type
is used to categorize two special types of matrix data: transformable coordinates and indices. This code is returned in pTransformType
by mdlMatrixData_getFields and also by mdlMatrix_getTransformType. We discuss these codes in the next subsections.
The functions mdlMatrixDoubleData_encodeTransformType and mdlMatrixDoubleData_decodeTransformType create and interpret the transform type code stored in matrix double data elements. For these elements, this code determines if and how the double
s are transformed by a general geometric ~s"Transform" (e.g., with mdlMatrixDoubleDataElement_transform).
The components of the transform type are coordinate type and dimension, and a normalization flag. The coordinate type determines the kind of coordinate data each struct represents; these types are constants defined in mselems.h
and are explained below. The coordinate dimension is the number of coordinates (double
s) in each struct. The normalization flag determines whether or not unit length of each struct is preserved under transformation.
Coordinate Type | Example Struct | How Struct x Is Transformed |
---|---|---|
MATRIX_DATA_ELM_TRANSFORM_TYPE_COORD_NONE | RgbColorDef | (structs should not be transformed) |
MATRIX_DATA_ELM_TRANSFORM_TYPE_COORD_POINT | DPoint3d | [M t]x = Mx + t |
MATRIX_DATA_ELM_TRANSFORM_TYPE_COORD_VECTOR | tangent | [M t]x = Mx |
MATRIX_DATA_ELM_TRANSFORM_TYPE_COORD_COVECTOR | normal | [M t]x = M^{ -t}x (preserves orthogonality) |
Table 3: Coordinate types for transform type. [M t] is the ~s"Transform" and M^{ -t} is the inverse transpose of M.
Note that if the coordinate type is MATRIX_DATA_ELM_TRANSFORM_TYPE_COORD_NONE
, then the other components of the transform type are irrelevant.
The functions mdlMatrixIntData_encodeIndexType and mdlMatrixIntData_decodeIndexType create and interpret the transform type code stored in matrix int data elements. For these elements, this code determines if the int
s are to be considered indices, and if so, what kind. In this context, the transform type is also called the index type
.
The components of the index type are blocking type, index base, pad value and a sign flag. The blocking type describes the row structure of the indices; these types are constants defined in mselems.h
and are explained below. The index base is the smallest absolute value (0 or 1) of the indices. The pad value is a special non-index value (-1 or 0, depending on the base) that is used to pad rows of indices (or to terminate them, depending on the blocking). The sign flag indicates whether or not the indices can be negative.
Blocking Type | Description |
---|---|
MATRIX_DATA_ELM_INDEX_TYPE_BLOCK_NONE | (data do not represent indices) |
MATRIX_DATA_ELM_INDEX_TYPE_BLOCK_FIXED | fixed-length rows of indices, possibly right-padded with the pad value |
MATRIX_DATA_ELM_INDEX_TYPE_BLOCK_VARIABLE | variable-length rows of indices terminated with the pad value |
Table 4: Blocking types for index type.
Note that if the blocking type is MATRIX_DATA_ELM_INDEX_TYPE_BLOCK_NONE
, then the other components of the index type are irrelevant. Certain combinations of these components do not form valid index types; in these cases the function mdlMatrixIntData_encodeIndexType returns ERROR
.
A matrix element is created by passing a contiguous list of primitive data values, the matrix attributes, and the matrix data attributes into one of the following functions, which construct a matrix header element with enough matrix data element children to hold the given data.
The mdlMatrix_newRagged* functions take a numValue
parameter for the number of primitive data values supplied; it does not have to be a multiple of the row size. The other functions take a numRow
parameter instead, if you know the number of logical rows, of fixed size numPerStruct
* numPerRow
primitive data values, that the new matrix will contain.
Matrix creation functions guarantee that structs of double
s are not split between successive matrix double data elements if the matrix transform type allows transformation. This is to ensure that per-matrix data element transformation (e.g., with mdlMatrixDoubleDataElement_transform) works.
To process a data matrix, you need the matrix header's element descriptor, which can be found/verified if you know the matrix tag. Next, you may want to check for an associated index matrix and get its element descriptor as well. You can get the tag of the index matrix (if any) using mdlMatrix_getFields on the data matrix header.
Once you have the element descriptors of a data matrix and its index matrix (if any), there are several functions at your disposal for copying the data and/or indices into ~s"Embedded Arrays". Processing can then be performed on your locally copied data, but to commit any changes back to a matrix element, you must replace (e.g., using mdlElmdscr_replaceDscr) the full matrix element descriptor tree with one constructed from the changed data.
If you need count(s) of the logical rows or structs, or the primitive data values in a matrix, then the following functions, operating on the matrix header element descriptor, are available:
These functions take into account all of the matrix data elements underneath the matrix header. In addition to comparing these counts with optional expected values, the function mdlMatrix_readAndCheckCounts also returns the number of data values per struct, and the number of structs per row, as stored in the matrix header and returned by mdlMatrix_getFields.
The following functions copy data from the matrix into an embedded array structure (see ~s"Embedded Arrays"). The mdlMatrix_readToEmbedded* functions copy the entire contents of the matrix. The mdlMatrix_readChunkToEmbedded* functions allow you to copy only part of the data in a matrix by providing a global 0-based start index (start
) and the number (num
) of structs/values to copy. You may also pass the value -1 for num
to copy from start
to the end of the data in the matrix.
Here we present an example of matrix processing. In the context of a hypothetical application, matrix elements are used to store persistent data representing a lower triangular matrix of real numbers in the design file. This function computes the determinant of such a matrix (the product of its diagonal entries), given two matrix descriptor trees that together logically comprise the lower triangular matrix. The separate index matrix element is used to organize data array entries on and below the diagonal of the target matrix; this is a scheme commonly used to represent large sparse matrices.
Note that most application assumptions on the matrix elements are verified programmatically before processing the data. The only assumption that is not verified is meaning of the index family identifier, which in this example, is used to specify the correspondence between the rows of the index matrix and the rows of the physical matrix it represents. Such operational semantics must be known beforehand by the application since they are not explicitly encoded in the matrix element. Nevertheless, the index family or even the matrix tag may be used to signal the presence of these semantics.