The vMAT Manifesto
The vMAT library is intended to make moving code and data between MATLAB1 and Objective-C++ apps as straightforward and painless as possible.
- The
vMAT_Arrayclass provides an Objective-C container type encapsulating matrices and arrays. - Overloaded operators and core numerical algorithms are provided by the Eigen
C++ template library; the
vMAT::Mattemplate class provides C++ code access to the contents of avMAT_Arrayas anEigen::Mapmatrix. - The
vMAT_loadandvMAT_saveAPI functions provide read/write access to MATLAB’s.matfiles for array data storage. - A small (but growing) library of commonly-used MATLAB functions are provided as C-style
vMAT_APIfunctions. - The library is packaged using CocoaPods for convenient dependency management, and distributed under the OSI-approved, commercial-developer-friendly BSD 2-Clause License.
The vMAT_Array Class
The “classical” approach to managing numeric arrays in Objective-C is to marshal them
as NSData objects. This works pretty well if you’re only dealing with a small number of
relatively uniform array types (say for instance, float vectors); but when you want to
introduce more generalized matrices, it makes sense to introduce a class that carries along
metadata describing the array.
The vMAT_Array class serves exactly this purpose. This code snippet shows one way2 an instance may be created
and initialized:
1 2 3 4 5 6 7 8 9 | |
And here is what gets output to the console when the above snippet is executed:
1 2 3 4 5 | |
There are a couple of important things to note here: First, the console output appears transposed from
the float M[] initializer used above. That’s because vMAT arrays are stored in column-major order,
as is common for numerical libraries (but often dismaying to C programmers3). Second, the
vMAT_Array class is actually a class cluster (as is evident from the vMAT_SingleArray class
name shown in the console log).
The vMAT::Mat Eigen Adaptor Class
While vMAT_Array instances are convenient for marshaling numeric data, Objective-C isn’t
a very good language for actually implementing numeric algorithms; that’s where Objective-C++
comes into its own. Thanks to the ability to (mostly) freely mix Objective-C and C++ code
in the same source file, vMAT is able to let the Eigen template library do all the
heavy lifting. All that’s needed is an adaptor class that maps between vMAT_Array instances
and Eigen objects.
This is the role of the vMAT::Mat template class. This snippet uses an overload of operator *
provided by Eigen to compute the element-wise square of matM as defined above:
1 2 3 4 5 6 7 8 9 | |
Here the X variable used to receive the matrix multiplication result is a “plain” Eigen::Matrix instance.4
The vMAT_cast function (or operator, if you prefer) marshals X into a
vMAT_Array for further processing. In this case, we just log it to the console:
1 2 3 4 5 | |
To instead verify the expected output (as a proper SenTestingKit test case should),
the following snippet allocates matXv and then initializes it using Eigen’s operator << notation.5
1 2 3 4 5 6 7 8 9 | |
Readers unfamiliar with Eigen may be wondering at this point how its templates handle
matrices whose dimensions are not known at compile-time. The short answer is that Eigen::Dynamic
may be specified in place of either or both the rows and columns template arguments.
When the number of rows or columns is known at compile-time, specifying it in the template
type instantiation results both in more efficient code, and in compile-time and run-time checks
of the matrix dimensions.
Loading Arrays
Specifying array contents in source code as compile-time constants is convenient for small
matrices, but impractical for larger arrays. vMAT provides two basic ways of loading (and saving) arrays:
raw binary file I/O and the ability to read and write MATLAB v5-compatible .mat files. The latter is
generally preferred, as it preserves array metadata, allows files to be exchanged
between big-endian and little-endian platforms, etc.
The following snippet loads a single matrix variable from a .mat file bundled with a test case.
In this instance, the .mat file was produced by MATLAB, and the workspace variable "M"
contains the matrix result of the MATLAB command M = magic(4).
1 2 3 4 5 6 7 | |
The second argument to vMAT_load is an array of workspace variable names; here we’re loading
only one variable, but more than one can be specified. If the .mat file contains variables that don’t
match an entry in the array, they are skipped. If the array contains names that don’t match
any variable in the .mat file, the returned NSDictionary will not contain that name as a key.
Here is what workspace looks like when it’s logged to the console by the above snippet:
1 2 3 | |
The workspace result normally points to an NSDictionary; the keys are
the variable names that were matched as described above, and the values are instances
of the vMAT_MATv5Variable class cluster. For our purposes here, the only salient functionality
of that cluster is that it has a -matrix accessor method which returns a vMAT_Array.
The error result (customarily ignored above) will normally be nil, but will point to an NSError if there’s
any problem opening URL, reading from the resulting NSInputStream or parsing the stream’s
contents. Per the usual conventions, if an error occurs the workspace result may also be nil.
Saving an array (unsurprisingly) requires first marshaling it into a workspace dictionary
as a vMAT_MATv5Variable, then calling vMAT_save.
The vMAT_API Functions
The most daunting part of porting code developed in MATLAB to Objective-C++ (or to any other language) is re-implementing all of those wonderful toolbox functions. If only there were a collection of open-source equivalents being assembled somewhere… Well, that’s exactly what vMAT hopes to be when it grows up.
Here are a few of the vMAT_API functions available in the pre-1.0
Bootstrap release:
| MATLAB | vMAT_API | Notes |
|---|---|---|
double(X) |
vMAT_double(X) |
X is coerced to double-precision floating point; if X was not already in that format, this allocates and returns a new matrix. |
eye(3) |
vMAT_eye(vMAT_MakeSize(3), nil) |
The additional argument, nil here, allows an array type specification.6 |
find(Y) |
vMAT_find(Y, nil) |
Y is coerced to a logical array. See the documentation for supported options. |
X' |
X.mtrans or vMAT_mtrans(X) |
The functional version is guaranteed not to modify X; the property makes no such guarantee (!) but may still return a new matrix! |
pdist(X) |
vMAT_pdist(X.mtrans) |
Some vMAT functions take arguments transposed from their MATLAB equivalents.7 |
single(X) |
vMAT_single(X) |
X is coerced to single-precision floating point; if X was not already in that format, this allocates and returns a new matrix. |
The table above is meager, yes; but it’s just a small sampling of the growing collection of vMAT_API functions.
There are more being added all the time. If a function you need isn’t already included in vMAT,
chances are some bits and pieces you could use to write it yourself are; and you can
choose to contribute your own implementations back to vMAT or not. vMAT is distributed
under an OSI-approved, commercial-developer-friendly
BSD 2-Clause License.
Getting Started with vMAT
CocoaPods is what the cool kids are using these days to manage dependencies for iOS and Mac OS X projects. The easiest way to get started with vMAT is to join in the fun.
The minimum PodFile for specifying that your Xcode project depends on vMAT looks like this:
1 2 | |
There is a vMAT FAQ that covers common installation issues and other topics of interest to new users.
-
MATLAB™® is a registered trademark of The MathWorks, Inc.↩
-
In interests of pedagogy, the code snippets in this document do not (necessarily) represent recommended vMAT coding practices. For example, the earliest snippets forego the use of convenience functions that haven’t been introduced yet, and all of the examples ignore potential efficiency concerns when doing so makes the intent of the code more clear.↩
-
At least I found it dismaying, to the point that I initially used the more familiar row-major ordering of C; but I soon found I was constantly needing to transpose array data imported from MATLAB, and then transpose it again before passing it along to BLAS or LAPACK routines. The last straw for me was discovering that Eigen also expected column-major array storage.↩
-
Note that the
Eigen::Matrixtemplate instantiation requires you to specify the matrix dimensions, while thevMAT::Mattemplate defaults therowsandcolumnsparameters toEigen::Dynamic.↩ -
Note that, unlike the first example snippet, the matrix now appears in the Objective-C++ source code in the same order (column-major) that it appears in the log output. For reasons which I hope are obvious, this is the preferred way to initialize matrices from source constants.↩
-
Actually MATLAB also accepts an array type specification; but MATLAB allows for more dynamic argument handling than Objective-C does. vMAT’s compromise usually involves marshalling optional arguments into an
NSArray; when no options are desired,nilcan take the place of the empty array.↩ -
Sometimes this is done to optimize for vectorization; specifically to be able to call vDSP functions with unit strides. And sometimes it’s done because the vMAT function usually used to prepare the input matrix produces output transposed from its MATLAB equivalent. And sometimes it’s an expression of personal preference on the part of your humble author; but not usually.↩