12
$\begingroup$

Hopefully this will be a quick question + a quick answer:

Say I have a C++ (or C) code using LibraryLink. I am using a library that defines a specific matrix class, as many numerical libraries commonly do. So during the computations I am using this class and, at the very end, I want to convert it to an MTensor to be used as output.

What is the simplest way to do that? Hopefully, one that does not require reallocating the entire matrix?

[Not sure it helps, but the matrix elements are assumed to be stored sequentially.]

Thank you in advance for the support.

Gabriel

Edit:

Two examples are the NVector class in SUNDIALS and the Matrix class in numerical recipes (3rd Edition in C++). Here is a quote from Numerical Recipes ($n$,$m$ are integers): let

MatDoub a(n,m);
Doub *b &a[0][0];

Then $a[i][j]$ and $b[m*i+j]$ are equivalent.

$\endgroup$
6
  • $\begingroup$ Unless you can get a hold on the pointer to the (multi-dimensional) array which is used by that class to store the data (which is hardly recommended since most likely a different memory management scheme (new-delete) was used there, and in addition this will in any case require the knowledge of implementation details of that class, relying on which is also hardly recommended), I don't see a way for you to avoid the reallocation. Presumably you should be able to subclass the class in question and add a method that would convert its data to a proper MTensor form. $\endgroup$ Jul 31, 2012 at 13:49
  • $\begingroup$ @LeonidShifrin I have edited the question with a situation where it is possible to access the data via a pointer. In that case, a simple solution exists? $\endgroup$ Jul 31, 2012 at 15:37
  • $\begingroup$ A simple self-contained example with a toy matrix class would make it easier to answer. I am not aware of a bulk constructor of MTensor which would take an array and dimensionality and construct MTensor by passing a reference to an array. So, it seems that you will have to construct MTensor instance from your class's data at least once. You may choose to share memory with the kernel though, which may help you avoid making extra copies. $\endgroup$ Jul 31, 2012 at 16:00
  • $\begingroup$ @LeonidShifrin I'm sorry Mr. Shifrin. I am still learning about LibraryLink and most of what you said I did not understand. Say I have a 2x2 matrix a[i][j]. Then what I want is to define a 2x2 MTensor m such that m[i][j]=a[i][j]. Hopefully, it would be nice to do this without a double for loop with m[i][j]=a[i][j]. $\endgroup$ Aug 1, 2012 at 15:28
  • $\begingroup$ @GabrielLandi Are you still interested in a probable answer? $\endgroup$
    – halirutan
    Dec 7, 2012 at 3:49

2 Answers 2

8
$\begingroup$

Preface:

Since you want to

convert other C++ classes to MTensor

I have to tell you, that I don't see a possibility for that, because I haven't found how to tell the Wolfram Library to take a chunk of already allocated data and use it for an MTensor. What I do is the other way around. I use the internal data of an MTensor as underlying array for my own tensor class. Therefore, at best I don't have to allocate any data since I reuse an input MTensor and store the result directly in it. Therefore, I allocate the tensor inside Mathematica and change it.

The following does explain a bit more:

Implementation:

I don't want to leave this question unanswered, when I can give at least some hints. In my C++ code, I had the same problem as you have. The advantage in the following is, that I have my own tensor class which stores even multidimensional tensor data as a flat sequencial array.

If you use a third-party library, you can, as Leonid pointed out, not rely on internal implementation details and in general you are stuck with copying the data. What I show now, works quite nice and is tested on Linux64, Windows32/64 and OSX64, but nevertheless, there are always bugs you haven't found already.

Here is a simple LibraryLink function which loads 2 tensors and an integer, calls a function and returns the output.

EXTERN_C DLLEXPORT int dilation3d(
        WolframLibraryData lib,
        int argc,
        MArgument *args,
        MArgument res) {

    try {
        if (argc != 2)
            throw std::invalid_argument("Two arguments are expected");
        Tensor<mint,3> *input = fromMTensor<mint,3>(lib, MArgument_getMTensor(args[0]));
        Tensor<mint,3> output(*input);
        mint iterations = MArgument_getInteger(args[1]);
        MorphologicalOperations3d::dilate(*input,&output,iterations);
        MTensor m_result = toMTensor(lib, output);
        MArgument_setMTensor(res, m_result);


    } catch(std::exception &e) {
        reportExceptionToMathematica(e, lib);
        return LIBRARY_FUNCTION_ERROR;
    }

    return LIBRARY_NO_ERROR;
}

fromTensor is a template function which tries to create a tensor of appropriate rank from an MTensor. This call does not copy the MTensor, but it uses only a pointer to it's data. The implementation is

template <class Type, int Rank>
Tensor<Type,Rank>* fromMTensor(WolframLibraryData lib, MTensor mtensor) {
    int rank = lib->MTensor_getRank(mtensor);
    if( rank != Rank) throw std::invalid_argument("Rank does not match");

    Tensor<Type,Rank>* res = new Tensor<Type,Rank>();
    const int *mdims = lib->MTensor_getDimensions(mtensor);
    res->data = (MTensorType<Type>::getData(lib))(mtensor);

    res->dims = new int[Rank];
    for (int i=0; i<Rank; ++i) res->dims[i] = mdims[Rank-i-1];
    res->flattened_length = lib->MTensor_getFlattenedLength(mtensor);
    return res;
}

Most of the stuff lines are easy to understand, but this here needs maybe explanation

res->data = (MTensorType<Type>::getData(lib))(mtensor);

You know, that you can call MTensor_getRealData, MTensor_getIntegerData, ... on every MTensor but it is in your duty to call the right one. The question is, when I have a template parameter Type, how can call the right getData automatically?

Here I solved it by giving an instance of the class MTensorType for every possible library type. The instance for mint looks like

template <class Type> struct MTensorType;

template <> struct MTensorType<mint> {
    static const int val = MType_Integer;
    static mint* (*getData(WolframLibraryData lib))(MTensor) {
        return (lib->MTensor_getIntegerData);
    }
    static int mlPutType(MLINK mlp, mint m) {
        return MLPutInteger(mlp,m);
    }
    static int mlPutList(MLINK mlp, mint *list, long length) {
        return MLPutIntegerList(mlp,list,length);
    }
};

Therefore, the static class function getData returns a pointer to the correct WolframLibrary function lib->MTensor_getIntegerData.

The next line in the first code block with output(*input) is the copy constructor of my tensor class. There, I really make a copy of the underlying data. The dilate function writes into this output and at the end my Tensor<mint,3> type is converted back to an MTensor with the toMTensor call. Let me give you code for this too:

template <class DataType, int Rank>
MTensor toMTensor(WolframLibraryData lib, const Tensor<DataType,Rank> &t) {
    MTensor res;
    int *mdims = new int[Rank]();
    for (int i = 0; i < Rank; ++i) mdims[i] = t.dims[Rank-i-1];
    lib->MTensor_new(MTensorType<DataType>::val, Rank, mdims, &res);
    memcpy((void*) (MTensorType<DataType>::getData(lib))(res), (void*) t.data, t.flattened_length*sizeof(DataType));
    delete [] mdims;
    return res;
}

Again, for the parts of the code which have to decide which type of MTensor is constructed. I make use of MTensorType<DataType>::val to get the value for the appropriate type back (this is defined in WolframLibrary.h and I store it in MTensorType). And again, I use getData to get the pointer to the array of the MTensor.

For your purpose, you probably only need the first part of my answer which shows how to use the pointer to MTensor data in your own tensor library. When you transfered the tensors from Mathematica with the "Shared" specifier you can change the array contents without doing a single copy.

$\endgroup$
1
$\begingroup$

As @halirutan said, it is not possible to take already allocated memory and return it to Mathematica. The LibraryLink API does not provide any access to Mathematica's memory management (like malloc, free). You will need to copy the data.

However, with many libraries, you can do the reverse. You can allocate an MTensor, and use another library's matrix/vector class to wrap that existing storage. For example, Armadillo has this feature.

The LTemplate package that I wrote to make it easier to create LibraryLink libraries comes with some examples that show how to interface with Armadillo while doing a minimal amount of data copying.


Here's how to create a new MTensor and immediately copy some data into it using LTemplate.

Create a new vector of type Real (double in C++) of length n:

auto vec = mma::makeVector<double>(n, data);

data is a pointer to the data to be copied. It does not need to be of type double *. If it is of a different type (e.g. float), it will be automatically converted.

To create an n by m matrix of type Integer (mint in C++), use

auto mat = mma::makeMatrix<mint>(m, n, data);

To make a four-dimensional Real tensor of dimensions $k\times l\times m\times n$, use

auto ten = mma::makeTensor<double>({k,l,m,n});
std::copy(data, data + k*l*m*n, ten.begin());
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.