Mathematica: сообщения об ошибках MathLink - PullRequest
7 голосов
/ 30 июня 2011

Кажется, я начинаю понимать, как связать функции, написанные на C / C ++, с Mathematica .Проблема, с которой я сталкиваюсь, заключается в том, что я не знаю, как отправлять сообщения об ошибках из моей оболочки C в Mathematica.После поиска в Google я нашел это Учебное пособие по MathLink .

Раздел 1.7 дал мне понимание того, как отправлять сообщения об ошибках, но я получаю странные результаты.Вот код, с которым я работаю.


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

Файл cppFunctions.h объявляет классы Point и Line.Я разделил этот класс до минимума, за исключением функции, которую я хочу использовать в Mathematica .Я хочу найти расстояние от одной линии до другой.Эта функция является C-версией lineInt в каркасах в Mathematica .Чтобы использовать эту функцию в Mathematica , нам нужна функция-обертка, которая получает входные данные от Mathematica и отправляет выходные данные обратно в 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);
}

Я создал две вспомогательные функции: ML_GetPoint и ML_GetLine, чтобы помочь мне получить данные от Mathematica .Строка получается из списка, содержащего два списка.Каждый подсписок представляет собой список из 2 действительных чисел (точка).Чтобы попробовать эту функцию в Mathematica, нам нужно больше файлов.


//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`"

В этом файле указывается, что функция LineDistance будет получать аргументы вручную и будет возвращать действительное число.Последние две строки важны.Первый Evaluate объявляет usage функции.Он дает краткое сообщение о функции, когда ?LineDistance введено в Mathematica .Другой Evaluate - это тот, который я хочу использовать при возникновении ошибки (подробнее об этом позже).


#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

Последний файл - это Makefile.На этом этапе мы все настроены для тестирования функции в Mathematica.


Я должен был упомянуть, что раньше я использую Mac OS X, я не уверен, как это будет работать в Windows.В mlwrapper.cpp основной функции требуется намного больше кода, который вы можете найти в одном из примеров, предоставленных Mathematica .

В терминале, который я знаю, сделайте это:

make > makelog.txt
make clean

Это сделает исполняемый файл mlwrapper.Теперь мы можем начать использовать 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"}

]

Вывод, который мы получаем:

Output

Все работает нормально, какПока вы вводите правильные аргументы.То есть 2 списка, каждый из которых представляет собой список из 2 списков по 2 двойных.Может быть, есть другой способ получения входных данных, если вы знаете, как, пожалуйста, дайте мне знать.Если мы придерживаемся этого метода, все, что нам нужно, это способ сообщить пользователю Mathematica , если есть какие-либо ошибки.Очень простой ввод неправильного ввода.Допустим, я ввел это:

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

Вывод $Failed.Как насчет следующего:

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

Вывод LineDistance[{{1, -1}, {1, 1}}].Я предполагаю, что это происходит, потому что мы описали в разделе Pattern .tm, что функция принимает два списка, и поскольку мы дали только один, он не соответствует шаблону.Это правда?

В любом случае, следуя найденному мною руководству, давайте изменим файл mlwrapper.cpp следующим образом:

#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);
}

И добавим следующее в конец mlwrapper.tmfile

: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`"

Теперь давайте используем make и пытаемся сделать некоторые ошибки в Mathematica.

Я выкладываю скриншот того, что выводит вместо того, чтобы писать все.

Output

Обратите внимание, как мы получаем различные ошибки после повторного вызова.Кажется, что функция продолжается в строке после ошибки.Если я не использую какие-либо другие функции ML, как в функции ML_Attempt, и использую только MLEvaluate для отправки тега ошибки, то MathLink не работает, и я должен переустановить ссылку.Кто-нибудь знает, как отправлять сообщения об ошибках Mathematica из C?


ОБНОВЛЕНИЕ:

На основании полученных ответов и другого полезного документа (Глава 8) мне удалось заставить его работать.На данный момент код не такой красивый, но это заставило меня задать следующий вопрос.Возможно ли завершить функцию раньше?В обычной C-программе, если я обнаружу ошибку, я напечатаю сообщение об ошибке и использую функцию exit.Можем ли мы сделать что-то подобное?Если мы используем функцию exit, ссылка будет разорвана, и нам придется переустановить функцию.Возьмем, к примеру, функции ML_GetPoint и ML_GetLine.Если здесь произошла ошибка, то нет смысла повторять вычисления в основной функции LineDistance.Нам нужно очистить любую полученную ошибку, отправить сообщение в Mathematica с указанием ошибки, выйти из системы и дождаться следующего вызова.

Ответы [ 3 ]

2 голосов
/ 30 июня 2011

В качестве альтернативы решению @ragfield вы можете объявить свою функцию как void и вернуть результат вручную.Вот пример, основанный на addTwo стандартном примере.Вот шаблон:

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"

и функция:

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");
    }
}

Вот примеры использования:

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

Это немного более высокий уровень"способ сделать это, таким образом, вам не нужно явно иметь дело с пакетами.

Однако я чувствую, что полную проверку ошибок входных аргументов лучше выполнять на стороне Mathematica с помощью соответствующих шаблонов, и возможность сохранения сообщений об ошибках для некоторых внутренних ошибок, обнаруженных в вашем коде C (на самом деле яИдите дальше и вернитесь в Mathematica просто с кодами ошибок в виде целых чисел или строк, и пусть обертки mma более высокого уровня обрабатывают их и выдают сообщения).Вы можете поместить эти шаблоны в файл шаблона или обернуть функцию MathLink Mathematica в другую функцию, которая будет выполнять проверку ошибок.Однако у меня очень ограниченный опыт работы с Mathlink, поэтому мое мнение здесь, возможно, не должно иметь большого значения.

РЕДАКТИРОВАТЬ

За запрос в комментарии (хотя я не былуверен, что я правильно понял запрос):

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");
}

и шаблон

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"

Примеры:

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 голосов
/ 30 июня 2011

Как-то так у меня обычно работает:

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);
}

Вам все равно придется вернуть результат, например,

MLPutSymbol(stdlink, "$Failed");
1 голос
/ 01 июля 2011

Этот пост предназначен для всех, кому интересно, как я написал свой окончательный код.Этот код основан на полезных обсуждениях с @Leonid.Начнем с файла утилиты.


//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");
}

Этот файл содержит класс MLException и функцию ML_SendMessage.Вот простое объяснение.Объект типа MLException содержит 2 строки и вектор строк.Если e является MLException, тогда e.sym должен быть действительным Mathematica символом и e.tag действительным тегом.Мы определяем это Symbol::tag в файле шаблона.Если Symbol::tag содержит параметры, то они сохраняются в e.err.Например, скажем, что вы объявили следующее в файле шаблона:

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

Тогда, если по какой-либо причине есть ошибка в функции C, вы можете выйти оттуда, вызвав исключение с некоторым сообщением.Например:

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

Обратите внимание, что третий аргумент является целым числом.Это количество сообщений, которые будут помещены вместо «1» и «2» в сообщении.Это означает, что сообщение, которое вы увидите в Mathematica: «Ошибка, обнаружена программа: это, это нужно, это».Если вам нужно включить числа в сообщение, я сделал это так, чтобы вы написали строку, за которой следовал номер.Например, если вы хотите написать число 100, а затем какое-то другое сообщение, вы можете выдать исключение следующим образом:

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

Если вы хотите включить двойное число, используйте вместо этого «% d».

Функция ML_sendMessage принимает исключение, очищает ошибки, отправляет сообщение Mathematica и помещает $Failed.

Вот мой файл шаблона:

//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[]

Я решил сделать это в упаковке.Я также объявил функции EMPH и LINK.Первый подчеркивает слова, а второй позволяет нам писать гиперссылки.Как только я научусь правильно документировать, я добавлю гиперссылки к описаниям.

Теперь мы можем описывать ошибки так же, как в 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);
}

Теперь, так как я делаю пакет, нам нужен один последний файл: mlwrapper.m.В этом файле мы добавляем эту строку: Install["mlwrapper"];.Конечно, мы предполагаем, что исполняемый файл mlwrapper находится в том же каталоге.Наконец, я показываю скриншот результатов:

Output

Я написал последнее утверждение, чтобы иметь представление о накладных расходах исключений.В основном я хочу, чтобы исключения обрабатывали получение ввода и, возможно, некоторые другие ошибки, основанные на операторах возврата из функции C / C ++.В любом случае, написание оболочки без исключений не принесло больших результатов, чем за исключением.

Итак, вот оно.Еще один пример вызова функций C / C ++ из Mathematica .

Я также хотел бы поблагодарить @ alexey-popkov за идею написать EMPH и LINK.Это доставляло мне головную боль, когда я узнал, как форматировать мои сообщения.

...