Exercise
Note: This exercise was tested on Stampede2 and Frontera; details on running on those systems are on the next page, MATLAB at TACC. The exercises should normally run on any MATLAB installation from version 2017a to 2020a, though other versions will likely work as well.
Previously, we showed the basic interface and build process for mex functions. Now we go into additional detail on the use of mexFunctions and the MATLAB API in C, in particular the procedure for extracting information from the passed mxArrays, and putting information computed in C back into mxArrays for eventual use in MATLAB. Readers not familiar with C may want to come back to this section after having completed Intro to C Programming.
This extended exercise computes the row averages of an m*n input matrix
and returns them in an m*1 array to the user. Note: writing an
averaging function of this sort is not really useful for anything
outside of an exercise in learning to write mex files. In general,
one should always prefer built-in MATLAB functions (in this case,
mean
) to perform basic tasks as they are always correct and
nearly always as efficient or more than anything user-coded in C.
Although the following code can be copied and pasted, it is recommended to type it out while reading this tutorial to get a feel for writing mex functions, especially if you plan to use your own mex functions soon.
File: avg.c
As in the previous code, our mex function is named mexFunction
and is defined in its own C file, in this case avg.c
. Also as before,
the mexFunction
takes four arguments:
nlhs
, nrhs
, plhs
, and prhs
,
and returns void
.
The function begins by defining several variables
which will be required in the computation of the averages. input
and output
are of type pointer-to-double and will eventually be
used to point at the incoming m*n array and the resulting m*1
outgoing result array. i
and j
are loop variables, n_rows
and n_cols
are the number of rows and columns, respectively, of
the incoming m*n array. Finally, avg
is a temporary variable
which will be used inside the loop to accumulate row sums and
temporarily store the average before it is moved to the output
storage.
Next, we perform an essential task for any mex function: checking
the input variables. Recall that because MATLAB functions can be
called from the interpreter with any number of input (and output)
variables, including zero, we must be able to handle unexpected
argument combinations in our mex file gracefully. In this particular
case, we can handle exactly 1 right-hand side argument (nrhs==1
)
and 0 or 1 left-hand side arguments: the user may catch the output
or may not. Therefore, as shown we print an error
message and return immediately if either of these conditions is not
met. Accessing an undefined entry in plhs will almost certainly
lead to a segmentation fault violation and crash the running MATLAB
process, so this input-checking step is essential.
After checking the user input, we need to actually extract the data
from the MATLAB mxArray
objects for processing. The mxGetPr
function takes a pointer to an mxArray object and returns a
pointer-to-double which is usable in C. This function, which stands
for "get pointer to real," is defined, like all other
MATLAB-specific functions, in the mex.h
header file included at the
beginning of the avg.c
source. Note that in R2018a and later, it
is suggested
to use mxGetDoubles
instead of mxGetPr
, for better type safety;
you may need to use the -R2018a
option to mex
for this to work.
Try it yourself with this
exercise if you have a qualifying version; you can access it by including
"matrix.h", part of the C Matrix API previously discussed.
In addition to mxGetPr
, MATLAB also
provides the mxGetPi
function which returns a pointer to the
complex-valued data (if any) held in an mxArray
(in R2018a and
later you should instead use mxGetComplexDoubles
). We next call the
mxGetM
and mxGetN
functions which return, respectively, the
number of rows "M" and columns "N" which are represented by the
passed mxArray
data object. In each of the mxGetPr
, mxGetM
, and
mxGetN
calls we have passed prhs[0]
, that is, a pointer to the
zeroth location of the prhs array. We know this location is safe to
access since we have previously checked that nrhs==1
. (Recall that
C is "zero-based" so array accesses go from 0 to nrhs-1 in contrast
to MATLAB which is "one-based".)
Next we need to create storage for the return result, but only if the user has actually tried to "catch" the result, i.e. if nlhs==1. MATLAB's mex API provides a special function to allocate storage in mxArrays called mxCreateDoubleMatrix. The mxCreateDoubleMatrix function must be passed three arguments: the number of rows, the number of columns and the type to be stored in the matrix, in this case mxREAL. The return value of mxCreateDoubleMatrix is a pointer to an mxArray object, and in this rendition we store this returned pointer in plhs[0]. Once storage in the mxArray has been created, we still need to be able to access it from C, and for this purpose we will use the pointer-to-double 'output' declared earlier. We can assign 'output' to point at plhs[0] by once again utilizing the 'mxGetPr' function.
Now that the data arrays have been created and properly set up, we
are ready to perform the actual averaging algorithm. The algorithm
itself is rather simple, and uses the fact that the input array is
stored in so-called "row-major" format. Row-major storage means
that a logically two (or more) dimensional array is stored in a
one-dimensional storage location, row by row. To access the (i,j)
entry of the original two-dimensional data, you must compute the
correct offset into the one-dimensional storage, which is given by
i*n_cols + j
, where n_cols
is the number of columns in the
two-dimensional array.
The averaging algorithm first loops over
each row in the input data and computes the sum of the row entries.
This step is followed by division by the number of columns. Note
that we explicitly cast the integer variable n_cols
to a double
before performing the division to ensure that floating-point
division is actually performed instead of integer division.
Finally, if the user has requested to catch the output, we assign
the newly-computed average to the ith entry of the output vector.
This concludes the mex function, and the file avg.c
.
To compile the mex function, please run the mex command from within the MATLAB interpreter as shown below. You can then try a few values as inputs to see if it is working as expected.
>>> mex avg.c
>> y = avg(rand(3));
>> y
0.6977
0.7476
0.4931
After compiling our function (by running mex avg.c
from the MATLAB
interpreter) we can test out its function on, for example, a 3*3 matrix
of random data given by rand(3)
. The reader should verify that
MATLAB's built-in mean
function gives the same result as the
hand-coded mex function when passed the same data array.
Finally,
let us also attempt an invalid calling sequence with our
newly-created mex function. If we ask for two return values, say
[y,z]
instead of 1, the error message from avg.c
should be
triggered and the function should return immediately. Try it, and you
should get a similar error:
>>> [y,z] = avg(rand(3));
Error! Expecting exactly 1 rhs and up ...
to 1 lhs argument!
??? One or more output arguments not ...
assigned during call to "avg".
Note that
MATLAB also warns that one of the output arguments was not assigned
during the call to avg
: this additional error checking happens
automatically for all mex functions without any additional input
from the user. The same error message would be triggered if
e.g. the user had called the 'avg' function without any rhs inputs.
This exercise has only scratched the surface of what is possible in
MATLAB mex file programming. For example, we have not discussed
working with strings, scalar values, complex data, calling MATLAB
functions from mex files, creating cell arrays in mex files,
generating MATLAB errors from mex files, or writing FORTRAN-based
mex files. Additional mex file examples can be found in the
$matlab/extern/examples/mx
directory of your MATLAB installation,
and a number of useful tutorials can be found on the Internet.
Finally, we note that GNU Octave also provides support for writing
mex files and therefore you are not limited to using your mex
functions only in the official MATLAB product.