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 a vMAT_Array as an Eigen::Map matrix.
  • The vMAT_load and vMAT_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
const float iniM[] = {
     2, 11,  7, 14,
     3, 10,  6, 15,
    13,  8, 12,  1,
};
vMAT_Array * matM = [vMAT_Array arrayWithSize:vMAT_MakeSize(4, 3)
                                         type:miSINGLE
                                         data:[NSData dataWithBytes:iniM length:sizeof(iniM)]];
NSLog(@"%@", matM.dump);

And here is what gets output to the console when the above snippet is executed:

1
2
3
4
5
2013-04-13 09:28:52.901 otest[93332:303] <vMAT_SingleArray: 0x10053f380; size: [4 3]> = 
 2  3 13
11 10  8
 7  6 12
14 15  1

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
namespace {
    using namespace Eigen;
    using namespace vMAT;
}

Mat<float> M = matM;
Matrix<float, 4, 3> X = M.array() * M.array();
vMAT_Array * matX = vMAT_cast(X);
NSLog(@"%@", matX.dump);

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
2013-04-13 09:28:52.902 otest[93332:303] <vMAT_SingleArray: 0x103a92890; size: [4 3]> = 
  4   9 169
121 100  64
 49  36 144
196 225   1

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
vMAT_Array * matXv = [vMAT_Array arrayWithSize:vMAT_MakeSize(4, 3)
                                          type:miSINGLE];
Mat<float> Xv = matXv;
Xv <<
  4,   9, 169,
121, 100,  64,
 49,  36, 144,
196, 225,   1;
STAssertEqualObjects(matX, matXv, @"Ensure equal arrays");

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
NSURL * URL = [[NSBundle bundleForClass:[self class]] URLForResource:@"test-magic-4x4-v6"
                                                       withExtension:@"mat"];
NSError * error = nil;
NSDictionary * workspace = vMAT_load(URL, @[ @"M" ], &error);
NSLog(@"workspace = %@", workspace);
vMAT_Array * matM = [workspace variable:@"M"].matrix;
STAssertNotNil(matM, @"Failed to load M");

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
2013-04-13 12:05:03.292 otest[8382:303] workspace = {
    M = "<vMAT_MATv5NumericArray: 0x101801380; mxClass: [6]mxDOUBLE_CLASS, size: [4 4], name: 'M'>";
}

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
platform :osx, '10.8'
pod 'vMAT', '~> 0.0.1'

There is a vMAT FAQ that covers common installation issues and other topics of interest to new users.


  1. MATLAB™® is a registered trademark of The MathWorks, Inc.

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

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

  4. Note that the Eigen::Matrix template instantiation requires you to specify the matrix dimensions, while the vMAT::Mat template defaults the rows and columns parameters to Eigen::Dynamic.

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

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

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

Comments