3
$\begingroup$

In C++ I have a vector of 2D points :

typedef std::array<double, 2> Point;
std::vector<Point> samples;

Now, I'm in LibraryLink and I'd want to pass a List of 2D points to Mathematica.

{{0.12345,0.98765},{0.53452,0.87512},...}

How I can approach this ?

$\endgroup$

2 Answers 2

4
$\begingroup$

You are overcomplicating this. There is no such thing as an "MTensor of MTensors" (i.e. an MTensor cannot contain other MTensors), but you don't need it anyway. A list of 2D points can be represented as an n by 2 matrix, i.e. a two-dimensional MTensor.

I would recommend not to use the MTensor_setXXX and MTensor_getXXX functions. These are very cumbersome to use (you need to construct the position argument) and more importantly: they are slow. Function calls are expensive, and if you call a function for each array indexing operation, it will kill performance.

Just get a pointer to the underlying storage with MTensor_getXXXData and use that directly. Data is stored in a contiguous memory region in row-major order, i.e. in an m by n matrix the position of the i, j element (zero-based indexing) is i*n + j.


Note: You can use my LTemplate package to simplify things and get convenient zero-based indexing as mat(i,j).

$\endgroup$
8
  • $\begingroup$ That was the first thing I tried but I had a stupid bug where I left in the for loop .. i<=nsamples .. ie. the = should have not been there (for 0-based ar).. so I was copying crap to non allocated mem and that was crashing the WM kernel randomly after many executions of the fnc.. and I just thought it wasn't the right way to go after found the MTensor_setMTensor :/ $\endgroup$ Nov 29, 2020 at 19:52
  • 1
    $\begingroup$ @MaxTarpini The internals of MTensor are not accessible. You wrote oT->data, but I assume this is just "hypothetical code". There is no information in the header files about what's in an MTensor. Furthermore, the storage is allocated and deallocated by Mathematica. Even if you knew the internals, swapping out a pointer like that would likely lead to a crash eventually. $\endgroup$
    – Szabolcs
    Nov 29, 2020 at 20:30
  • 1
    $\begingroup$ @MaxTarpini You don't have to copy memory: you can create an MTensor, get a pointer to its storage, then generate the data directly in to that space. If your existing functions can't place data into arbitrary memory, and must return their result through an std::vector, then this is not possible and you must copy. $\endgroup$
    – Szabolcs
    Nov 29, 2020 at 20:32
  • 1
    $\begingroup$ @MaxTarpini OK, I see you got that struct from WolframCompileLibrary.h. If you look at it in different versions of Mathematica you will see an entirely different structure. This is the first issue. Second, you don't know what Mathematica will do with whatever you put in there. It is likely to call free or maybe an entirely different deallocation function on it. Just don't do it. $\endgroup$
    – Szabolcs
    Nov 29, 2020 at 20:38
  • 1
    $\begingroup$ @MaxTarpini I don't care about reputation score or green checkmarks so you don't have to accept this. Just please make sure you don't suggest to people to use setMTensor. Do check out my LTemplate package. It's a bit quirky but it makes big projects easier. $\endgroup$
    – Szabolcs
    Nov 29, 2020 at 21:14
3
$\begingroup$

Now while one can do this with MTensor_setMTensor as I just discovered, one should NOT really go for this approach as it is simpler and nicer to get the starting address of your raw data and index from there as one would do with plain C/C++.

EXTERN_C DLLEXPORT int RandomRealLDBN(  WolframLibraryData libData, 
                                        mint Argc, MArgument* Args, MArgument Res) 
{
    // Error
    int err = LIBRARY_NO_ERROR;
    
    // Args
    const mint isamples = MArgument_getInteger(Args[0]);

    // Setup nb of samples
    const mint nsamples = std::floor(std::sqrt((double)isamples)) *2;
    const mint nrealslots = 2;

    // Tensor to hold nsamples slots of 2 reals
    MTensor oT;
    mint oDims[2];
    oDims[0] = nsamples;
    oDims[1] = nrealslots;
    const mint oRank = 2;
    err = libData->MTensor_new(MType_Real, oRank, oDims, &oT);
    if(err) return err;

    // Low-Discrepancy BlueNoise
    std::vector<Point> samples;
    initSamplers();
    ldbnBNOT(nsamples, samples);

    // Setup subtensor to hold sample data
    MTensor iT;
    mint iDims[] = {nrealslots};
    const mint iRank = 1;
    const mint nPos = 1;
    mint pos[1];

    // Yep, MTensor is a 1-based array as in WM
    for(int i=1; i<=nsamples; i++)
    {
        Point s = samples[i-1];

        // allocate a tensor to hold the sample
        err = libData->MTensor_new(MType_Real, iRank, iDims, &iT);
        if(err) return err;

        // fill in the sample data
        mreal *sample = libData->MTensor_getRealData(iT);
        sample[0] = s[0]; //copy sample data
        sample[1] = s[1];

        // update index
        pos[0] = i;

        //insert subtensor in our main tensor list at pos
        err = libData->MTensor_setMTensor(oT, iT, pos, nPos);
        if(err) return err;
    }

    //[....]

    // Get back to WM
    MArgument_setMTensor(Res, oT);
    return err;
}

The function signature in WM is:

RandomRealLDBN = 
 LibraryFunctionLoad[randomRealLDBNLib, 
  "RandomRealLDBN", {Integer}, {Real, 2}]

We'll have data back in WM with this layout

{{0.25, 0.4375}, {0.6875, 0.125}, {0.40625, 0.9375}, {0.8125, 0.625}}

So as Szabolcs is saying we can go as simple as

EXTERN_C DLLEXPORT int RandomRealLDBN(  WolframLibraryData libData, 
                                        mint Argc, MArgument *Args, MArgument Res) 
{
    // Error
    int err = LIBRARY_NO_ERROR;
    
    // Args
    const mint isamples = MArgument_getInteger(Args[0]);

    // Setup nb of samples
    const mint samplerow = std::floor(std::sqrt((double)isamples));
    const mint nsamples = pow(samplerow,2);
    const mint nrealslots = 2;

    // Tensor to hold nsamples slots of 2 reals
    MTensor oT;
    const mint oDims[2] = {nsamples, nrealslots};
    const mint oRank = 2;
    err = libData->MTensor_new(MType_Real, oRank, oDims, &oT);
    if(err) return err;

    // Low-Discrepancy BlueNoise
    std::vector<Point> samples;
    initSamplers();
    ldbnBNOT(samplerow, samples);

    // Get the starting address of the data to be filled in
    mreal *datastartingaddress = libData->MTensor_getRealData(oT);

    // Fill in samples data
    memcpy(datastartingaddress, &samples[0], sizeof(double)*nsamples*nrealslots);

    // Same as above
    /*for(int i=0; i < nsamples; i++){
        Point s = samples[i];
        datastartingaddress[i*nrealslots +0] = s[0];
        datastartingaddress[i*nrealslots +1] = s[1];
    }*/

    //[...]

    // Get back to WM
    MArgument_setMTensor(Res, oT);
    return err;
}
$\endgroup$
1
  • $\begingroup$ As I said in my answer, there is no such thing as an "MTensor of MTensors". You are repeatedly creating a small MTensor (iT) and never freeing it. There's a huge memory leak. Use getRealData on oT to get a pointer to the underlying storage and fill that in directly. $\endgroup$
    – Szabolcs
    Nov 29, 2020 at 11:40

Your Answer

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

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