Mathematica: MathLink error messages

I think I'm starting to understand how to associate functions written in C / C ++ with Mathematica. The problem I am facing is that I do not know how to send error messages from my C shell to Mathematica. After searching google, I found this MathLink Tutorial .

Section 1.7 gave me an idea of ​​how to send error messages, but I get strange results. Here is the code I'm working with.


//File cppFunctions.h #ifndef CPPFUNCTIONS_H #define CPPFUNCTIONS_H class Point { public: double x, y; Point(){ x=y=0.0;} Point(double a, double b): x(a), y(b) {} }; class Line { public: Point p1, p2; Line(void) {} Line(const Point &P, const Point &Q): p1(P), p2(Q) {} double distanceTo(const Line &M, const double &EPS = 0.000001){ double x21 = p2.x - p1.x; double y21 = p2.y - p1.y; double x43 = M.p2.x - M.p1.x; double y43 = M.p2.y - M.p1.y; double x13 = p1.x - M.p1.x; double y13 = p1.y - M.p1.y; double den = y43*x21 - x43*y21; if (den*den < EPS) return -INFINITY; double numL = (x43*y13 - y43*x13)/den; double numM = (x21*y13 - y21*x13)/den; if (numM < 0 || numM > 1) return -INFINITY; return numL; } }; #endif 

The cppFunctions.h file declares the Point and Line classes. I split this class into bare minium, with the exception of the function that I want to use in Mathematica. I want to find the distance from one line to another. This function is a version of C lineInt in lineInt in Mathematica . To use this function in Mathematica, we need a wrapper function that receives input from Mathematica and sends the result back to Mathematica.


 //mlwrapper.cpp #include "mathlink.h" #include <math.h> #include "cppFunctions.h" void ML_GetPoint(Point &P){ long n; MLCheckFunction(stdlink, "List", &n); MLGetReal64(stdlink, &P.x); MLGetReal64(stdlink, &P.y); } void ML_GetLine(Line &L){ long n; MLCheckFunction(stdlink, "List", &n); ML_GetPoint(L.p1); ML_GetPoint(L.p2); } double LineDistance(void) { Line L, M; ML_GetLine(L); ML_GetLine(M); return L.distanceTo(M); } int main(int argc, char* argv[]) { return MLMain(argc, argv); } 

I created two helper functions: ML_GetPoint and ML_GetLine to help me get data from Mathematica. The string is obtained from a list containing two lists. Each sublist is a list of 2 real numbers (dots). To try this feature in Mathematica, we need more files.


 //mlwrapper.tm double LineDistance P((void)); :Begin: :Function: LineDistance :Pattern: LineDistance[L_List, M_List] :Arguments: {L, M} :ArgumentTypes: {Manual} :ReturnType: Real :End: :Evaluate: LineDistance::usage = "LineDistance[{{x1,y1}, {x2,y2}}, {{x3,y3}, {x4,y4}}] gives the distance between two lines." :Evaluate: LineDistance::mlink = "There has been a low-level MathLink error. The message is: `1`" 

This file indicates that the LineDistance function will receive the arguments manually and return a real number. The last two lines are important. The first Evaluate declares usage functions. It gives a brief message about the function when ?LineDistance is introduced in Mathematica. Another Evaluate is the one I want to use when there is an error (more on this later).


 #Makefile VERSION=8.0 MLINKDIR = . SYS = MacOSX-x86-64 CADDSDIR = /Applications/Mathematica.app/SystemFiles/Links/MathLink/DeveloperKit/CompilerAdditions INCDIR = ${CADDSDIR} LIBDIR = ${CADDSDIR} MPREP = ${CADDSDIR}/mprep RM = rm CXX = g++ BINARIES = mlwrapper all : $(BINARIES) mlwrapper : mlwrappertm.o mlwrapper.o ${CXX} -I${INCDIR} mlwrappertm.o mlwrapper.o -L${LIBDIR} -lMLi3 -lstdc++ -framework Foundation -o $@ .cpp.o : ${CXX} -c -I${INCDIR} $< mlwrappertm.cpp : mlwrapper.tm ${MPREP} $? -o $@ clean : @ ${RM} -rf *.o *tm.c mlwrappertm.cpp 

The last file is the Makefile. At this point, we are all set to test functions in Mathematica.


I should have mentioned that I use Mac OS X, I'm not sure how this will work on Windows. In mlwrapper.cpp, the main function requires much more code, which you can find in one of the examples provided by Mathematica.

In the terminal, I know this:

 make > makelog.txt make clean 

This will make the mlwrapper . Now we can start using Mathematica:

 SetDirectory[NotebookDirectory[]]; link = Install["mlwrapper"]; ?LineDistance Manipulate[ Grid[{{ Graphics[{ Line[{p1, p2}, VertexColors -> {Red, Red}], Line[{p3, p4}] }, PlotRange -> 3, Axes -> True], LineDistance[{p1, p2}, {p3, p4}] }}], {{p1, {-1, 1}}, Locator, Appearance -> "L1"}, {{p2, {2, 1}}, Locator, Appearance -> "L2"}, {{p3, {2, -2}}, Locator, Appearance -> "M1"}, {{p4, {2, 3}}, Locator, Appearance -> "M2"} 

]

The result obtained:

Output

Everything works fine as long as you enter the correct arguments. That is, 2 lists, each of which is a list of 2 lists of 2 two-local. Maybe there is another way to get input, if you know how to please let me know. If we stick with this method, all we need is a way to let the Mathematica user know if there are any errors. Very simple wrong input. Let's say I entered this:

 LineDistance[{{0, 0}, {0}}, {{1, -1}, {1, 1}}] 

The output is $Failed . How about the following:

 LineDistance[{{1, -1}, {1, 1}}] 

Output signal LineDistance[{{1, -1}, {1, 1}}] . I assume this happens because we described in the Pattern .tm section that the function accepts two lists, and since we gave only one, it does not match the pattern. It's true?

In any case, following the tutorial below, you can modify the mlwrapper.cpp file as follows:

 #include "mathlink.h" #include <math.h> #include <string> #include "cppFunctions.h" bool ML_Attempt(int func, const char* err_tag){ if (!func) { char err_msg[100]; sprintf(err_msg, "Message[%s,\"%.76s\"]", err_tag, MLErrorMessage(stdlink)); MLClearError(stdlink); MLNewPacket(stdlink); MLEvaluate(stdlink, err_msg); MLNextPacket(stdlink); MLNewPacket(stdlink); MLPutSymbol(stdlink, "$Failed"); return false; } return true; } void ML_GetPoint(Point &P){ long n; if(!ML_Attempt(MLCheckFunction(stdlink, "List", &n), "LineDistance::mlink2"))return; if(!ML_Attempt(MLGetReal64(stdlink, &P.x), "LineDistance::mlink3")) return; if(!ML_Attempt(MLGetReal64(stdlink, &P.y), "LineDistance::mlink4")) return; } void ML_GetLine(Line &L){ long n; if(!ML_Attempt(MLCheckFunction(stdlink, "List", &n), "LineDistance::mlink1"))return; ML_GetPoint(L.p1); ML_GetPoint(L.p2); } double LineDistance(void) { Line L, M; ML_GetLine(L); ML_GetLine(M); return L.distanceTo(M); } int main(int argc, char* argv[]) { return MLMain(argc, argv); } 

And add the following to the end of the mlwrapper.tm file

 :Evaluate: LineDistance::mlink1 = "There has been a low-level MathLink error. The message is: `1`" :Evaluate: LineDistance::mlink2 = "There has been a low-level MathLink error. The message is: `1`" :Evaluate: LineDistance::mlink3 = "There has been a low-level MathLink error. The message is: `1`" :Evaluate: LineDistance::mlink4 = "There has been a low-level MathLink error. The message is: `1`" 

Now let's use make and try to make some mistakes in Mathematica.

I post a screenshot of what displays instead of writing everything.

Output

Notice how we get different errors after repeating the call. It seems that the function continues on the line after an error occurs. If I do not use any of the other ML functions, as in the ML_Attempt function, and I use only MLEvaluate to send the error tag, then MathLink is broken and I need to reset the link. Does anyone know how to send error messages to Mathematica from C?


UPDATE:

Based on the answers received and another useful document (chapter 8), I managed to get it to work. The code is not very good at the moment, but it made me ask the following question. Is it possible to terminate the function earlier? In a regular C program, if I encounter an error, I would print an error message and use the exit function. Can we do something like this? If we use the exit function, the connection will be broken, and we will have to reinstall this function. Take, for example, the ML_GetPoint and ML_GetLine . If an error occurs here, then it makes no sense to process the calculations in the main LineDistance function. We need to clear all the errors that we received, send a Mathematica message indicating the error, exit now and wait for the next call.

+7
source share
3 answers

As an alternative to @ragfield's solution, you can declare your function as void and return the result manually. The following is an example based on the standard addTwo example. Here is the template:

 void addtwo P(( int, int)); :Begin: :Function: addtwo :Pattern: AddTwo[i_Integer, j_Integer] :Arguments: { i, j } :ArgumentTypes: { Integer, Integer } :ReturnType: Manual :End: :Evaluate: AddTwo::usage = "AddTwo[x, y] gives the sum of two machine integers x and y." :Evaluate: AddTwo::badargs = "Arguments are expected to be positive numbers" 

and function:

 void addtwo( int i, int j) { if(i>0&&j>0){ MLPutInteger(stdlink,i+j); }else{ MLPutFunction(stdlink,"CompoundExpression",2); MLPutFunction(stdlink,"Message",1); MLPutFunction(stdlink,"MessageName",2); MLPutSymbol(stdlink,"AddTwo"); MLPutString(stdlink,"badargs"); MLPutSymbol(stdlink,"$Failed"); } } 

Here are some usage examples:

 In[16]:= AddTwo[1,2] Out[16]= 3 In[17]:= AddTwo[-1,2] During evaluation of In[17]:= AddTwo::badargs: Arguments are expected to be positive numbers Out[17]= $Failed 

This is a slightly more β€œhigher level” way to do this, so you don't need to deal with packages explicitly.

However, I feel that full error checking of input arguments is better done on the Mathematica side with the appropriate templates, as well as the ability to report errors saved for some internal errors found in your C code (I really go ahead and return to Mathematica only error codes as integers or strings, and let the more-average mma wrappers handle them and issue messages). You can put these templates in your templates file, or you can put the MathLink Mathematica function in another function that will perform error checking. I have very limited experience with Mathlink, so my opinion here should probably not be considered big.

EDIT

By request in the comment (although I was not sure that I understood the request correctly):

 extern void addeight( void ); extern void addall(void); static void putErrorMessageAndReturnFailure(const char *fname, const char *msgtag); void addeight(void) { int i,j,k,l,i1,j1,k1,l1; MLGetInteger(stdlink,&i); MLGetInteger(stdlink,&j); MLGetInteger(stdlink,&k); MLGetInteger(stdlink,&l); MLGetInteger(stdlink,&i1); MLGetInteger(stdlink,&j1); MLGetInteger(stdlink,&k1); MLGetInteger(stdlink,&l1); if(i<0||j<0||k<0||l<0||i1<0||j1<0||k1<0||l1<0){ putErrorMessageAndReturnFailure("AddEight","badargs"); }else{ MLPutFunction(stdlink,"List",2); MLPutFunction(stdlink,"List",2); MLPutInteger(stdlink,i+i1); MLPutInteger(stdlink,j+j1); MLPutFunction(stdlink,"List",2); MLPutInteger(stdlink,k+k1); MLPutInteger(stdlink,l+l1); } } void addall(){ int *data, len, i = 0,sum = 0; if(!MLGetIntegerList(stdlink, &data, &len)){ putErrorMessageAndReturnFailure("AddAll","interr"); return; } for(i=0;i<len;i++){ if(data[i]<0){ putErrorMessageAndReturnFailure("AddAll","badargs"); return; }else{ sum+=data[i]; } } MLPutInteger(stdlink,sum); MLReleaseInteger32List(stdlink, data, len); } static void putErrorMessageAndReturnFailure(const char *fname, const char *msgtag){ MLPutFunction(stdlink,"CompoundExpression",2); MLPutFunction(stdlink,"Message",1); MLPutFunction(stdlink,"MessageName",2); MLPutSymbol(stdlink,fname); MLPutString(stdlink,msgtag); MLPutSymbol(stdlink,"$Failed"); } 

and pattern

 void addeight P(( )); :Begin: :Function: addeight :Pattern: AddEight[{{i_Integer, j_Integer},{k_Integer,l_Integer}},{{i1_Integer,j1_Integer},{k1_Integer,l1_Integer}}] :Arguments: { i, j, k ,l, i1,j1,k1,l1 } :ArgumentTypes: { Manual } :ReturnType: Manual :End: :Evaluate: AddEight::usage = "AddEight[{{i_Integer, j_Integer},{k_Integer,l_Integer}}, {{i1_Integer, j1_Integer},{k1_Integer,l1_Integer}}] gives the sum as a list: {{i+i1,j+j1},{k+k1,l+l1}}." :Evaluate: AddEight::badargs = "Arguments are expected to be positive numbers" void addall P(( )); :Begin: :Function: addall :Pattern: AddAll[fst:{{_Integer, _Integer},{_Integer,_Integer}},sec:{{_Integer, _Integer},{_Integer,_Integer}}] :Arguments: { Flatten[{fst,sec}]} :ArgumentTypes: { Manual } :ReturnType: Manual :End: :Evaluate: AddAll::usage = "AddAll[{{i_Integer, j_Integer},{k_Integer,l_Integer}},{{i1_Integer, j1_Integer},{k1_Integer,l1_Integer}}] gives the total sum of elemens of the sub-lists." :Evaluate: AddAll::badargs = "Arguments are expected to be positive numbers" :Evaluate: AddAll::interr = "Internal error - error getting the data from Mathematica" 

Examples:

 In[8]:= AddEight[{{1,2},{3,4}},{{5,6},{7,8}}] Out[8]= {{6,8},{10,12}} In[9]:= AddEight[{{-1,2},{3,4}},{{5,6},{7,8}}] During evaluation of In[9]:= AddEight::badargs: Arguments are expected to be positive numbers Out[9]= $Failed In[10]:= AddAll[{{1,2},{3,4}},{{5,6},{7,8}}] Out[10]= 36 In[11]:= AddAll[{{-1,2},{3,4}},{{5,6},{7,8}}] During evaluation of In[11]:= AddAll::badargs: Arguments are expected to be positive numbers Out[11]= $Failed 
+2
source

Something like this usually works for me:

 void putMessage(const char* messageSymbol, const char* messageTag, const char* messageParam) { MLNewPacket(stdlink); MLPutFunction(stdlink, "EvaluatePacket", 1); MLPutFunction(stdlink, "Message", 2); MLPutFunction(stdlink, "MessageName", 2); MLPutSymbol(stdlink, messageSymbol); MLPutString(stdlink, messageTag); MLPutString(stdlink, messageParam); MLFlush(stdlink); MLNextPacket(stdlink); MLNewPacket(stdlink); } 

You still have to return the result, for example.

 MLPutSymbol(stdlink, "$Failed"); 
+2
source

This post is for anyone interested in how I wrote my latest code. This code is based on helpful discussions with @Leonid. Let's start with the utility file.


 //MLErrors.h #include <stdarg.h> #include <vector> #include <sstream> #define CCHAR const char* #define UINT unsigned int class MLException { public: CCHAR sym; CCHAR tag; std::vector<std::string> err; MLException(CCHAR msgSym, CCHAR msgTag, UINT n, ...): sym(msgSym), tag(msgTag), err(n) { std::stringstream ss; va_list args; va_start(args, n); for (UINT i=0; i < n; ++i) { err[i] = va_arg(args, CCHAR); if (err[i][0] == '%') { switch (err[i][1]) { case 'i': ss << va_arg(args, int); break; case 'd': ss << va_arg(args, double); break; default: break; } err[i] = ss.str(); } } va_end(args); } }; #undef CCHAR #undef UINT void ML_SendMessage(const MLException& e){ if (MLError(stdlink) != MLEOK) MLClearError(stdlink); MLNewPacket(stdlink); MLPutFunction(stdlink, "EvaluatePacket", 1); MLPutFunction(stdlink, "Message", e.err.size()+1); MLPutFunction(stdlink, "MessageName", 2); MLPutSymbol(stdlink, e.sym); MLPutString(stdlink, e.tag); for (int i=0; i < e.err.size(); ++i) { MLPutString(stdlink, e.err[i].c_str()); } MLFlush(stdlink); MLNextPacket(stdlink); MLNewPacket(stdlink); MLPutSymbol(stdlink, "$Failed"); } 

This file contains the MLException class and the ML_SendMessage function. Here is a simple explanation. An object of type MLException contains 2 lines and a vector of lines. If e is a MLException , then e.sym must be a valid Mathematica symbol and e.tag valid tag. We define this Symbol::tag in the template file. If Symbol::tag contains parameters, they are stored in e.err . For example, let's say that you specified the following in the template file:

 :Evaluate: someSymbol::someTag = "Error, the program encountered: `1`, it needed `2`." 

Then, if for any reason there is an error in function C, you can get out of there by throwing an exception with some message. Like this:

 if(ERROR) throw MLException("someSymbol", "someTag", 2, "this", "that"); 

Notice how the third argument is an integer. This is the number of messages that will be placed instead of "1" and "2" in the message. This means that the message you will see in Mathematica reads: "Error that occurred in the program: you need it for this." If you need to include numbers in a message, I have you write a line followed by a number. For example, if you want to write the number 100, and then some other message, you can make an exception as follows:

  if(ERROR) throw MLException("someSymbol", "someTag", 2, "%i", 100, "msg"); 

If you want to include double, use "% d" instead.

The ML_SendMessage function accepts an exception, clears errors, sends a message to Mathematica, and puts $Failed .

Here is my template file:

 //mlwrapper.tm :Evaluate: BeginPackage["mlwrapper`"] :Evaluate: EMPH[a_] := ToString[Style[a, "TI"], StandardForm] :Evaluate: LINK[url_, label_ : Style["\[RightSkeleton]", "SR"]] := ToString[Hyperlink[label, url], StandardForm] :Evaluate: LineDistance::usage = "LineDistance["<>EMPH["L"]<>", "<>EMPH["M"]<>"] gives the distance between two lines. "<>LINK["#"] :Evaluate: mlwrapper::mlink = "There has been a low-level MathLink error. The message is: `1`" :Evaluate: GetPoint::narg = "A point is a list of 2 numbers. A list of `1` elements was passed instead. "<>LINK["#"] :Evaluate: GetLine::narg = "A line is a list of 2 points. A list of `1` elements was passed instead. "<>LINK["#"] :Evaluate: EndPackage[] :Evaluate: Begin["mlwrapper`Private`"] void LineDistance P((void)); :Begin: :Function: LineDistance :Pattern: LineDistance[L_List, M_List] :Arguments: {L, M} :ArgumentTypes: {Manual} :ReturnType: Manual :End: :Evaluate: End[] 

I decided to do it in a package. I also declared EMPH and LINK functions. The first emphasizes words, and the other allows us to write hyperlinks. As soon as I learn how to properly document, I will add hyperlinks to the descriptions.

Now we can describe the errors in the same way as in Mathematica:

 //mlwrapper.cpp #include "mathlink.h" #include "MLErrors.h" #include <cmath> #include "cppFunctions.h" #define MLINKERROR MLException("mlwrapper", "mlink", 1, MLErrorMessage(stdlink)) void ML_GetPoint(Point &P){ long n = 0; MLCheckFunction(stdlink, "List", &n); if (n != 2) throw MLException("GetPoint", "narg", 1, "%i", n); MLGetReal64(stdlink, &P.x); MLGetReal64(stdlink, &P.y); if (MLError(stdlink) != MLEOK) throw MLINKERROR; } void ML_GetLine(Line &L){ long n = 0; MLCheckFunction(stdlink, "List", &n); if (n != 2) throw MLException("GetLine", "narg", 1, "%i", n); ML_GetPoint(L.p1); ML_GetPoint(L.p2); } void LineDistance(void) { Line L, M; try { ML_GetLine(L); ML_GetLine(M); } catch (MLException& e) { ML_SendMessage(e); return; } MLPutReal64(stdlink, L.distanceTo(M)); } int main(int argc, char* argv[]) { return MLMain(argc, argv); } 

Now, since I am making the package, we need one last file: mlwrapper.m. In this file we will add the following line: Install["mlwrapper"]; . Of course, we assume that the mlwrapper is in the same directory. Finally, I will show a screenshot of the results:

Output

I wrote the last statement to get an idea of ​​the costs of exceptions. Basically, I want the exceptions to handle receiving input and possibly some other errors based on return statements from a C / C ++ function. In any case, writing a wrapper without exception is not much better than with exceptions.

So you have it. Another example of how to call C / C ++ functions from Mathematica.

I would also like to thank @ alexey-popkov for giving me the idea to write EMPH and LINK . It gave me a headache learning how to format my messages.

+1
source

All Articles