MATLAB Answers

0

Get a subset of a structure array in mex

Asked by Jim Hokanson on 9 Dec 2016
Latest activity Edited by James Tursa
on 19 Dec 2016
Is it possible to get a subset of a structure array in mex? In other words, the mex equivalent of the following in Matlab:
s2 = s1(2:5); %s1 is a structure array
In my (current) use case, the structure array is empty, although fields have been defined.

  3 Comments

Jan
on 9 Dec 2016
Yes, of course this is possible. Please post the current MEX source to allow the readers to suggest matching changes.
I should have clarified, I want to know if this possible to do in a simple, nice way. I don't currently have any code that is related to copying the fields since I refused to start coding until I could think of a simple solution.
I think the best approach will probably be to have an empty structure (with fields) and to duplicate and resize the array.
I'll post some code at some point. I think the important part to know is that a structure array has its "data" as an array of mxArrays with the size equal to => (# of fields)*(# of elements of array) (based on the link above).

Sign in to comment.

Tags

3 Answers

Answer by Jim Hokanson on 19 Dec 2016
Edited by Jim Hokanson on 19 Dec 2016
 Accepted Answer

This is the code I ended up using. At the end of the day I essentially wanted to have an object "template" (i.e. field names defined) that I then populate with values at the appropriate time.
Prior to the example code below, I've initialized empty "template" objects into a cell array, 1 for each unique type of object that I've encountered.
//- This is specific to my code, but ref_object is an empty structure
// BUT with fields, like in Matlab => s = struct('a',{},'b',{},'c',{})
int object_id = data.object_ids[object_data_index];
mxArray *ref_object = mxGetCell(data.objects,object_id);
//- Create a fresh structure/structure array from the reference
//- Copies the field names
mxArray *return_obj = mxDuplicateArray(ref_object);
//- I know the # of fields, this is a bit of a note to my self
// in that I'm not sure which of these will execute faster
//- "data" is a C structure
//Not sure which is better ...
//int n_fields = data.child_count_object[object_data_index];
int n_fields = mxGetNumberOfFields(return_obj);
//- This is the magic that I think I pulled from one of James Tursa's other
// posts (and this one too). The important part is that the "data" (in mxGetData, mxSetData)
// for a structure or structure array is a linear array of pointers to mxArrays.
mxArray **object_data = mxCalloc(n_fields*n_objects,sizeof(mxArray*));
mxSetData(return_obj,object_data);
mxSetN(return_obj,n_objects);
return return_obj;

  1 Comment

Looks good, but depending on how you are actually building your reference empty structures, you might need to add the following also:
mxSetM(return_obj,1);
I.e., there may be a 0 in the M spot that you need to set to 1.
Regarding the methods used to get n_fields, the function mxGetNumberOfFields simply peeks at a location behind the pi pointer where the number of fields is stored as an int. So there is no counting involved. You only incur the function call overhead and a simple int pointer dereference. (The field names themselves are actually stored behind the pi pointer as well).

Sign in to comment.


Answer by James Tursa
on 12 Dec 2016
Edited by James Tursa
on 13 Dec 2016

Here is some sample code to create a subset of a struct array inside a mex routine. One key point is that there are no official API functions that allow you to do this the same way that MATLAB does it at the m-file level. At the m-file level, MATLAB creates reference copies (a type of shared copy) of all of the field elements. This only involves incrementing a single counter inside the mxArray itself ... no deep data copy or even a new mxArray struct header is needed, so very efficient. The only thing available to you officially at the mex level is mxDuplicateArray, which could be a really nasty memory/performance hit. Hence this is one case where I would highly advise going outside the official API and using the undocumented API function mxCreateReference. This will allow you to efficiently construct the subset array exactly the same way MATLAB does at the m-file level. Code is listed below with a simple driver routine. You may want to modify how the code behaves in the case of invalid index inputs ... I just left that element NULL but you might want to generate an error.
/* Sample code to create subset of a struct array
*
* B = CreateStructSubset( S, x )
*
* S = a struct array
* x = a vector of indexes
* B = S(x)
*
* This mex routine does the equivalent of the B = S(x) statement at the
* m-code level. It uses the undocumented API function mxCreateReference
* to create reference copies of the "copied" contents rather than deep
* copies (just like MATLAB would do at the m-file level).
*
* Programmer: James Tursa
* Date: 12-Dec-2016
*
*/
/* Includes ----------------------------------------------------------- */
#include "mex.h"
/* Prototypes --------------------------------------------------------- */
mxArray *mxCreateReference(mxArray *); /* Undocumented API function */
mxArray *mxCreateStructSubset(const mxArray *mx, mwSize n, int *elements);
/* Gateway ------------------------------------------------------------ */
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
mxArray *mx_elements;
int i, n;
int *elements;
/* Example driver code. 1st input must be a struct. 2nd input must be a
* numeric variable, which is turned into an int32 just to get an equivalent
* int array to pass into the subset creation routine */
if( nrhs == 2 && mxIsStruct(prhs[0]) && mxIsNumeric(prhs[1]) ) {
mexCallMATLAB( 1, &mx_elements, 1, prhs+1, "int32" );
n = mxGetNumberOfElements(mx_elements);
elements = (int *) mxGetData(mx_elements);
for( i=0; i<n; i++ ) {
elements[i]--; /* Turn 1-based MATLAB indexing into 0-based C indexing */
}
plhs[0] = mxCreateStructSubset( prhs[0], n, elements );
mxDestroyArray(mx_elements);
} else {
mexErrMsgTxt("Invalid inputs");
}
}
/* Function to create a subset from an input struct. If an element value
* is outside the range of the dimensions of the input struct, that
* particular element is left empty and no copy is made. Could of course
* change this behavior to whatever you want instead (e.g., error).
* The elements array is assumed to be 0-based C indexing. */
mxArray *mxCreateStructSubset(const mxArray *mx, mwSize n, int *elements)
{
mxArray *result = NULL;
mwSize i, j, k, nelements, nfields, M, N;
mxArray **mdata, **rdata, **data;
const char **fieldnames;
if( mx && mxIsStruct(mx) ) { /* Make sure we have a struct to work with */
nfields = mxGetNumberOfFields(mx);
nelements = mxGetNumberOfElements(mx);
fieldnames = (char **) mxMalloc(nfields * sizeof(*fieldnames));
for( i=0; i<nfields; i++ ) {
fieldnames[i] = mxGetFieldNameByNumber(mx,i); /* Copy the fieldname pointers */
}
if( mxGetNumberOfDimensions(mx) == 2 && mxGetM(mx) == 1 ) {
M = 1; N = n;
} else {
M = n; N = 1;
}
result = mxCreateStructMatrix(M, N, nfields, fieldnames);
mxFree(fieldnames);
mdata = (mxArray **) mxGetData(mx);
rdata = (mxArray **) mxGetData(result);
for( i=0; i<n; i++ ) {
k = elements[i];
if( k >= 0 && k < nelements ) {
data = mdata + k * nfields;
for( j=0; j<nfields; j++ ) { /* For each element, copy all the fields */
if( *data ) {
*rdata++ = mxCreateReference(*data++); /* Make a reference copy, not a deep copy */
} else {
rdata++; data++; /* *data is NULL, so nothing to copy */
}
}
} else {
rdata += nfields; /* k is out of range, so leave all fields NULL */
}
}
}
return result;
}

  1 Comment

Jan
on 16 Dec 2016
@James: Keep care. An angry armadillo has entered your room and types on your keyboard while you are away.

Sign in to comment.


Answer by Jan
on 9 Dec 2016

You have to create a new struct array at first by mxCreateStructMatrix. You need the number of elements and number of fields as well as the field names in a char** obtained from the original struct. Then you copy the wanted fields in a loop from the old to the new struct. The code is not tricky, but tedious to type.
An excellent example for the beauty of Matlab! While s2 = s1(2:5) is simply nice, the C code looks like somebody had rolled an angry armadillo over the keyboard.

  1 Comment

Yuck, angry armadillo indeed :) Perhaps I need to change my approach. I wonder if mexCallMatlab is able to do perform better (behind the scenes optimizations), although I was hoping for something a bit more elegant.

Sign in to comment.