Кажется, я начинаю понимать, как связать функции, написанные на 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"}
]
Вывод, который мы получаем:
Все работает нормально, какПока вы вводите правильные аргументы.То есть 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.
Я выкладываю скриншот того, что выводит вместо того, чтобы писать все.
Обратите внимание, как мы получаем различные ошибки после повторного вызова.Кажется, что функция продолжается в строке после ошибки.Если я не использую какие-либо другие функции ML, как в функции ML_Attempt
, и использую только MLEvaluate
для отправки тега ошибки, то MathLink не работает, и я должен переустановить ссылку.Кто-нибудь знает, как отправлять сообщения об ошибках Mathematica из C?
ОБНОВЛЕНИЕ:
На основании полученных ответов и другого полезного документа (Глава 8) мне удалось заставить его работать.На данный момент код не такой красивый, но это заставило меня задать следующий вопрос.Возможно ли завершить функцию раньше?В обычной C-программе, если я обнаружу ошибку, я напечатаю сообщение об ошибке и использую функцию exit
.Можем ли мы сделать что-то подобное?Если мы используем функцию exit
, ссылка будет разорвана, и нам придется переустановить функцию.Возьмем, к примеру, функции ML_GetPoint
и ML_GetLine
.Если здесь произошла ошибка, то нет смысла повторять вычисления в основной функции LineDistance
.Нам нужно очистить любую полученную ошибку, отправить сообщение в Mathematica с указанием ошибки, выйти из системы и дождаться следующего вызова.