How to make external Mathematica functions interruptible?
Asked Answered
T

3

7

I had an earlier question about integrating Mathematica with functions written in C++.

This is a follow-up question:

If the computation takes too long I'd like to be able to abort it using Evaluation > Abort Evaluation. Which of the technologies suggested in the answers make it possible to have an interruptible C-based extension function? How can "interruptibility" be implemented on the C side?

I need to make my function interruptible in a way which will corrupt neither it, nor the Mathematica kernel (i.e. it should be possible to call the function again from Mathematica after it has been interrupted)

Tweed answered 26/11, 2011 at 15:41 Comment(0)
V
5

For MathLink - based functions, you will have to do two things (On Windows): use MLAbort to check for aborts, and call MLCallYieldFunction, to yield the processor temporarily. Both are described in the MathLink tutorial by Todd Gayley from way back, available here.

Using the bits from my previous answer, here is an example code to compute the prime numbers (in an inefficient manner, but this is what we need here for an illustration):

code = 
"
#include <stdlib.h>

extern void primes(int n);

static void yield(){
    MLCallYieldFunction(
        MLYieldFunction(stdlink), 
        stdlink,
       (MLYieldParameters)0 );
}

static void abort(){
    MLPutFunction(stdlink,\" Abort \",0);
}

void primes(int n){
    int i = 0, j=0,prime = 1, *d = (int *)malloc(n*sizeof(int)),ctr = 0;    
    if(!d) {
       abort();
       return;
    }
    for(i=2;!MLAbort && i<=n;i++){
        j=2;
        prime = 1;      
        while (!MLAbort && j*j <=i){
            if(i % j == 0){
                prime = 0;
                break;
            }
            j++;
        }
        if(prime) d[ctr++] = i;
        yield();
    }
    if(MLAbort){
        abort();
        goto R1;
    }

    MLPutFunction(stdlink,\"List\",ctr);
    for(i=0; !MLAbort && i < ctr; i++ ){
        MLPutInteger(stdlink,d[i]);
        yield();        
    }
    if(MLAbort) abort();

 R1: free(d);
 }
 ";

and the template:

template = 
"
void primes P((int ));

:Begin:
:Function:       primes
:Pattern:        primes[n_Integer]
:Arguments:      { n }
:ArgumentTypes:  { Integer }
:ReturnType:     Manual
:End:
";

Here is the code to create the program (taken from the previous answer, slightly modified):

Needs["CCompilerDriver`"];
fullCCode = makeMLinkCodeF[code];
projectDir = "C:\\Temp\\MLProject1";
If[! FileExistsQ[projectDir], CreateDirectory[projectDir]]
pname = "primes";
files = MapThread[
   Export[FileNameJoin[{projectDir, pname <> #2}], #1, 
     "String"] &, {{fullCCode, template}, {".c", ".tm"}}];

Now, here we create it:

In[461]:= exe=CreateExecutable[files,pname];
Install[exe]

Out[462]= LinkObject["C:\Users\Archie\AppData\Roaming\Mathematica\SystemFiles\LibraryResources\
Windows-x86-64\primes.exe",161,10]

and use it:

In[464]:= primes[20]
Out[464]= {2,3,5,7,11,13,17,19}

In[465]:= primes[10000000]
Out[465]= $Aborted

In the latter case, I used Alt+"." to abort the computation. Note that this won't work correctly if you do not include a call to yield.

The general ideology is that you have to check for MLAbort and call MLCallYieldFunction for every expensive computation, such as large loops etc. Perhaps, doing that for inner loops like I did above is an overkill though. One thing you could try doing is to factor the boilerplate code away by using the C preprocessor (macros).

Vandalize answered 26/11, 2011 at 20:3 Comment(6)
@Tweed Make sure to put the label R1 to the left (I edited to change). This is another downside of labels: when they are not at the beginning of the line, the code compiles, but interprets them differently. Then, the program crashes on Abort. This would be more obvious in IDE or text editor, but not when we make a string of code, like here.Vandalize
Why specifying that it works only on Windows ? Can you kindly add a pointer to lookup if someone wanted to do the same on a Mac OS X (or Linux) ?Experimentalize
@M.Alaggan MathLink code is generally not OS-specific. I believe Leonid meant that he tested it on Windows only.Tweed
@M.Allagan Should work cross-platform, but on Mac or Linux calling MLCallYieldFunction might not be necessary, and further, as Szabolcs guessed, I did not test it there.Vandalize
It seems that MLCallYieldFunction needs to be used only on pre OS X Macintoshes and pre 95 Windows systems (those that don't have preemptive multitasking), so for any recent version of Mathematica this call is not necessary.Tweed
@Tweed I found it necessary to use on Win7 64 bit when testing the above code - it did not react to Abort[] without it.Vandalize
T
3

Without ever having tried it, it looks like the Expression Packet functionality might work in this way - if your C code goes back and asks mathematica for some more work to do periodically, then hopefully aborting execution on the mathematica side will tell the C code that there is no more work to do.

Talkington answered 26/11, 2011 at 15:48 Comment(2)
Do you have experience with this? My C code doesn't need additional input to do its work. But perhaps the interruption signal is sent through MathLink?Tweed
So I found MLAbort, which seems to be the way to go for MathLink (I didn't test yet, as I'm not using MathLink for my code right now!) reference.wolfram.com/mathematica/tutorial/… This seems to be the way to go on MathLink. I still wonder about the other technologies, in particular LibraryLinkTweed
S
3

If you are using LibraryLink to link external C code to the Mathematica kernel, you can use the Library callback function AbortQ to check if an abort is in progress.

Shannan answered 27/11, 2011 at 9:44 Comment(1)
Thank you, I am using this at the moment. The function seems to be fast enough that I can call it from my inner loop without affecting performance.Tweed

© 2022 - 2024 — McMap. All rights reserved.