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_Array
class 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::Mat
template class provides C++ code access to the contents of avMAT_Array
as anEigen::Map
matrix. - The
vMAT_load
andvMAT_save
API functions provide read/write access to MATLAB’s.mat
files for array data storage. - A small (but growing) library of commonly-used MATLAB functions are provided as C-style
vMAT_API
functions. - 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::Matrix
template instantiation requires you to specify the matrix dimensions, while thevMAT::Mat
template defaults therows
andcolumns
parameters 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,nil
can 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.↩