Matrix Element Structure

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

Matrix Structure

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:

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.

Matrix Descriptor Hierarchy

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.

Matrix Attributes

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

Logical Data Grouping

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 doubles logically organized into xyz coordinates would have 3 doubles per struct; a matrix of ints 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).

Matrix Tags

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.

Index Matrices

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.

Index Family

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.

Indexed By

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.

Matrix Data Attributes

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., ints, doubles) 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.

Transform Type

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.

Double Data Transform Type

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 doubles 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 (doubles) 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_COVECTOR normal [M t]x = M -tx (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.

Int Data Transform Type

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

Matrix Creation

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

Matrix Processing

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.

Retrieving Data Counts

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.

Retrieving Data

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.

Sample Code

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.

StatusInt myApp_computeDeterminantOfLowerTriangularMatrix
MSElementDescrCP pMyIndexMatrixED,
MSElementDescrCP pMyDataMatrixED,
double *pDeterminant
EmbeddedIntArray *pIndices = jmdlEmbeddedIntArray_grab ();
EmbeddedDoubleArray *pData = jmdlEmbeddedDoubleArray_grab ();
StatusInt status = ERROR;
int indexedBy, nIndices, family, indexType, blocking, base;
if (
// verify expected data matrix tag, struct/row size; get tag of index matrix
true == mdlMatrix_isTaggedMatrix (pMyDataMatrixED, MYAPP_MATRIX_DATA)
&& SUCCESS == mdlMatrix_readAndCheckCounts (pMyDataMatrixED, NULL, NULL, NULL, NULL, NULL, 1, 1, -1, -1, -1)
&& SUCCESS == mdlMatrix_getFields (pMyDataMatrixED, NULL, NULL, NULL, NULL, &indexedBy)
// verify expected index matrix tag, struct/row size, nIndices, family
&& true == mdlMatrix_isTaggedMatrix (pMyIndexMatrixED, indexedBy)
&& SUCCESS == mdlMatrix_readAndCheckCounts (pMyIndexMatrixED, NULL, NULL, NULL, NULL, &nIndices, 1, 1, -1, -1, -1)
&& nIndices > 0
&& SUCCESS == mdlMatrix_getFields (pMyIndexMatrixED, NULL, NULL, NULL, &family, NULL)
// verify expected index type
&& SUCCESS == mdlMatrix_getTransformType (pMyIndexMatrixED, &indexType)
&& SUCCESS == mdlMatrixIntData_decodeIndexType (indexType, &blocking, &base, NULL, NULL ,NULL)
&& base == 0
// copy matrix data and indices
&& NULL != pData
&& NULL != pIndices
&& SUCCESS == mdlMatrix_readToEmbeddedDoubleArray (pMyDataMatrixED, pData)
&& SUCCESS == mdlMatrix_readToEmbeddedIntArray (pMyIndexMatrixED, pIndices))
int i;
static int terminator = -1; // this terminator is implied by our indexType
const int *pI = jmdlEmbeddedIntArray_getConstPtr (pIndices, 0);
double diag;
*pDeterminant = 1.0; // det (triangular matrix) = product of diagonal entries
for (i = 0; i < nIndices - 1; i++)
// Index row i directly corresponds to the part of matrix row i on and
// below the diagonal, hence the index before the terminator of each index
// row points to a factor of the determinant.
if (pI[i] != terminator && pI[i+1] == terminator)
jmdlEmbeddedDoubleArray_getDouble (pData, &diag, pI[i]);
*pDeterminant *= diag;
status = SUCCESS;
jmdlEmbeddedDoubleArray_drop (pData);
jmdlEmbeddedIntArray_drop (pIndices);
return status;

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.

Copyright © 2017 Bentley Systems, Incorporated. All rights reserved.