Library
This part of
mathematica++is a recent addition please report issues in case you face problem and participate in chat.
LibraryLink is one of many ways to bring your C++ functions into mathematica. Some examples are given in Mathematica Website. Functions are supposed to be declared in one of the following signature.
int CFunctionM(WolframLibraryData libData, mint argc, MArgument* args, MArgument res) // 1: for simple arguments int CFunctionW WolframLibraryData libData, WSLINK mlp) // 2: arguments fetched as mathematica expression from the link
In the first variant input arguments sent from thr frontend are fetched through args where each element args[i] is an union. Returning a value from inside the library is also setting value to an union. Although this looks straight forward it imposes limititaions on types of arguments that you can pass or retrieve. Also sending or receiving MTensor of higher rank to/from an MArgument is non-trivial.
In the second variant a mathematica link is used to fetch any input or send any output as mathematica expression.
Along with this there are an initializer and uninitializer for each module that can be used as a boilerplate code for many instances.
EXTERN_C DLLEXPORT mint WolframLibrary_getVersion(){return WolframLibraryVersion;} EXTERN_C DLLEXPORT mint WolframLibrary_initialize(WolframLibraryData libData){return 0;} EXTERN_C DLLEXPORT void WolframLibrary_uninitialize(WolframLibraryData libData){return;}
With mathematica++ you will have to include (copy paste) this thee function.mathematica++ supports both MArgument and WSLINK/MLINK based library development. mathematica::mtransport is used for MArgument based libraries and mathematica::wtransport is used for LINK based library. WMK_LINK is a macro that resolves to MLINK or WSLINK depending on compilation flag (defaults to MLINK).
mathematica::wtransport¶
EchoX is a very basic example of using wtransport. that echo's its input.
EXTERN_C DLLEXPORT int EchoX(WolframLibraryData libData, WMK_LINK native_link){ mathematica::wtransport shell(libData, native_link); value input = shell.input(); return shell = input->stringify(); }
mathematica::wtransport::input returns the input which is a tree of tokens. In this example we just return the entire tree that was sent to the function as string. We can extract the i'th argument from the input or convert that input to tuple of C++ types that we show in next examples. This input can be sent back to mathematica to do computations over them. operator= is used to return something. So shell = 42 will return 42 to the mathematica frontend.
In[1]:= EchoX = LibraryFunctionLoad["/path/to/libmod01.so", "EchoX", LinkObject, LinkObject] In[2]:= EchoX["Hallo", Sin[0]] Out[2]= "List[Hallo, 0]"
Sin[0] was evaluated to 0 before sending it to EchoX
EXTERN_C DLLEXPORT int ArgsListX(WolframLibraryData libData, WMK_LINK native_link){ wtransport shell(libData, native_link); value input = shell.input(); value output; shell << Length(input); shell >> output; int nargs = *output; shell << Permutations(Append(input, nargs)); shell >> output; return shell = output; }
As mentioned in the other parts of the library you can declare any mathematica function with MATHEMATICA_DECLARE.
Length, Permutations, Append are the mathematica function which has previously been declared similarly. We pass the input through these functions.
In[4]:= ArgsListX = LibraryFunctionLoad["/path/to/libmod01.so", "ArgsListX", LinkObject, LinkObject] In[5]:= ArgsListX[1, "Algorithm", "Hypothesis"] Out[5]= {{1, "Algorithm", "Hypothesis", 3}, {1, "Algorithm", 3, "Hypothesis"}, {1, "Hypothesis", "Algorithm", 3}, {1, "Hypothesis", 3, "Algorithm"}, {1, 3, "Algorithm", "Hypothesis"}, {1, 3, "Hypothesis", "Algorithm"}, {"Algorithm", 1, "Hypothesis", 3}, {"Algorithm", 1, 3, "Hypothesis"}, {"Algorithm", "Hypothesis", 1, 3}, {"Algorithm", "Hypothesis", 3, 1}, {"Algorithm", 3, 1, "Hypothesis"}, {"Algorithm", 3, "Hypothesis", 1}, {"Hypothesis", 1, "Algorithm", 3}, {"Hypothesis", 1, 3, "Algorithm"}, {"Hypothesis", "Algorithm", 1, 3}, {"Hypothesis", "Algorithm", 3, 1}, {"Hypothesis", 3, 1, "Algorithm"}, {"Hypothesis", 3, "Algorithm", 1}, {3, 1, "Algorithm", "Hypothesis"}, {3, 1, "Hypothesis", "Algorithm"}, {3, "Algorithm", 1, "Hypothesis"}, {3, "Algorithm", "Hypothesis", 1}, {3, "Hypothesis", 1, "Algorithm"}, {3, "Hypothesis", "Algorithm", 1}}
Check full example on mod01.cpp
Function Overloading¶
Based on the user input we can use different functions thus overload SomeFunctionX as shown bellow. There are two types of overload resolution supported.
- Based on Head
- Based on number of arguments
The first overload check whether the two arguments have have GeoPosition. If it matches then the GeoPosition is automatically converted to point_2d<double> using the association rules (See Association page for more details on automatic serialization and deserialization).
For the second overload Complex is converted to std::complex and sent to some_function_impl_complex. The next two overloads differ by number of arguments so appropriate overload can be resolved by checking the size of input.
double some_function_impl_geo(mathematica::transport& shell, point_2d<double> p1, point_2d<double> p2){ std::string unit("Kilometers"); double res; shell << QuantityMagnitude(GeoDistance(p1, p2, Rule("!UnitSystem") = unit)); shell >> res; return res; } double some_function_impl_complex(std::complex<double> p1, std::complex<double> p2){ return p1.real()+p2.imag(); } int some_function_impl_binary(double x, double y){ return 10; } mathematica::m some_function_impl_unary(double x){ return Complex(x+2, x); } EXTERN_C DLLEXPORT int SomeFunctionX(WolframLibraryData libData, WMK_LINK native_link){ mathematica::wtransport shell(libData, native_link); try{ mathematica::resolver resolver(shell); resolver, overload(&some_function_impl_geo, shell) = {"GeoPosition", "GeoPosition"} , overload(&some_function_impl_complex) = {"Complex", "Complex"} , overload(&some_function_impl_binary) , overload(&some_function_impl_unary); return resolver.resolve(); }catch(...){ return shell.pass(); } return 0; }
If no suitable overload is found an exception is thrown which is captured by shell.pass() and sent to mathematica frontend.
SomeFunctionWX = LibraryFunctionLoad["/path/to/libmod02.so", "SomeFunctionWX", LinkObject, LinkObject] SomeFunctionWX[1, 2, 3, 4] SomeFunctionWX::overload: called with List[1, 2, 3, 4] arguments; no matching overloads found
Check full example on mod02.cpp
mathematica::mtransport¶
arguments are fetched as tuple. args = shell will fetch the input arguments from MArgument* argv to args which is of boost::tuple type.
EXTERN_C DLLEXPORT int SomeFunctionMX(WolframLibraryData libData, mint argc, MArgument* argv, MArgument res){ mathematica::mtransport shell(libData, argc, argv, res); boost::tuple<double, std::complex<int>> args = shell; // fetch and parse input double dbl; std::complex<int> cmplx; boost::tie(dbl, cmplx) = args; shell = std::complex<double>(dbl+cmplx.real(), dbl-cmplx.imag()); // send output return LIBRARY_NO_ERROR; }
SomeFunctionMX = LibraryFunctionLoad["/path/to/libmod03.so", "SomeFunctionMX", {Real, Complex}, Complex] In[2]:= SomeFunctionMX[12.0, 52 + 2 I] Out[2]= 64. + 10. I
receiving and sending MTensor of any rank or dimension is also supported. the type and the rank of the tensor and need to be specified at the compile time. The dimension is checked on run time. In the following example std::vector<std::vector<double>> implicitely mentions rank 2 (matrix) by its nesting level. We fetch two matrices and then multiply them using mathematica.
EXTERN_C DLLEXPORT int SomeFunctionMXT(WolframLibraryData libData, mint argc, MArgument* argv, MArgument res){ mathematica::mtransport shell(libData, argc, argv, res, "SomeFunctionMXT"); typedef std::vector<std::vector<double>> matrix_type; try{ boost::tuple<matrix_type, matrix_type> args = shell; // fetch input matrix_type matl, matr, mato; // declare variables to hold the input and output boost::tie(matl, matr) = args; // tie tuple to variables shell << Dot(matl, matr); // To lazy to write matrix multiplication code in C++ so using mathematica instead // This converts matl and matr to the corresponding List[..] expression shell >> mato; // retrieve multiplication result as List[...] and cast to matrix_type shell = mato; // return the result }catch(...){ return shell.pass(); } return LIBRARY_NO_ERROR; }
Function overloading is not supported in mtransport because you specify the argument types with LibraryFunctionLoad
Error control¶
If an exception is thrown from inside the library then it is ent to the mathematica frontend by the shell.pass() method. Sending an exceoption aborts the function. While aborting the corresponding exception error message is shown. If the exception contains any message then the message is also shown. Messages can be sent without thowing an execption.
shell << message("libmsg") % std::string("Hallo World");
shows the message libmsg with one argument "Hallo World"
SomeFunctionMXT::libmsg: Hallo World
This requires the MessageName SomeFunctionMXT::libmsg to be defined prior sending the message. You can do it in a .m package. Or use initializer to do it for you at the time of library initializer. Library specific messages are defined in mathematica::namespace as shown in the following snippet. mymsg is used as the tag. detail contains the string message which can have one or more placeholders. operator% is used to place values on placeholders.
namespace messages{ struct mymsg: library_message<mymsg>{ static constexpr const char* tag = "mymsg"; static constexpr const char* detail = "Hallo I am a message with one placeholder `1`"; }; }
In the exceptions.h header few messages are already defined like libmsg, argx, overload, type, rankerror, tensortype. The message libmsg is defined as the following
struct libmsg: library_message<libmsg>{ static constexpr const char* tag = "libmsg"; static constexpr const char* detail = "`1`"; };
mathematica::initializer is constructed with the libData and can be used to declare a library specific message at the time of library initialization.
EXTERN_C DLLEXPORT mint WolframLibrary_initialize(WolframLibraryData libData){ mathematica::initializer init(libData); init.declare(messages::libmsg()); return 0; }
The above sends the following to declare this message
MessageName[LIBRARY_NAME, "libmsg"] = "`1`"
Instead of using the generic message struct the specific message can also be constructed and sent.
shell << messages::libmsg() % std::string("Hallo Jupiter");
Just sending a message does not abort. However throwing an exception does. An exception can optionally comprise of a message as well as an additional string error message. As an exception is meant to abort a return code needs to be specfified with it.
throw library::exceptions::library_error(LIBRARY_TYPE_ERROR, messages::argx() % input_args % requested_args, "Failed to parse arguments");
The above example sends the message argx along with a string message while returning LIBRARY_TYPE_ERROR. Following is the effect of that exception.
In[2]:= SomeFunctionMXT[{{0.0, 1.0}, {0.0, 0.0}}, {{0.0, 0.0}, {1.0, 0.0}}, {{0.0, 0.0}, {1.0, 0.0}}] During evaluation of In[2]:= SomeFunctionMXT::argx: called with 3 argument(s) 2 argument(s) is expected. >> SomeFunctionMXTException Failed to parse arguments During evaluation of In[2]:= LibraryFunction::typerr: An error caused by inconsistent types was encountered evaluating the function SomeFunctionMXT. Out[2]= LibraryFunctionError["LIBRARY_TYPE_ERROR", 1]
There are mainly two types of exception that should be used to throw errors from inside the library.
library::exceptions::library_error: exception with a message. There are two overloads for conveniance
library_error(const basic_message& msg, int err=LIBRARY_NO_ERROR, std::string errmsg="") library_error(int err, const basic_message& msg, std::string errmsg="")
library::exceptions::internal_error: exception only with an error code that may be derived and what() method may be overridden and thrown.
Library Name¶
In the above example a library name is required while throwing the error messages. By default the library name is LibraryFunction. However that can be changed by setting that name on the shell. Both wtransport and mtransport takes an optional string argument as library name
mathematica::mtransport shell(libData, argc, argv, res, "SomeFunctionMXT");
mathematica::wtransport shell(libData, native_link, "SomeFunctionWX");
This causes any messages sent through shell to be sent as Message[MessageName[SomeFunctionMXT::tag], e1, e2, ...] or, Message[MessageName[SomeFunctionWX::tag], e1, e2, ...]. However sending the messages tagged with your preffered library name is not enough as these MessageName need to be declared along with their error message template before sending. This can be defined in a seperate .m package that declares the MessageNames while loading the library with LibraryFunctionLoad. This can also be done in the C++ side at the initialize function.
EXTERN_C DLLEXPORT mint WolframLibrary_initialize(WolframLibraryData libData){ mathematica::initializer(libData, "SomeFunctionMX"); mathematica::initializer(libData, "SomeFunctionMXT"); return 0; }
In the above example the initialize function is used to set up initialization of functions. The constructor declares the error messages that may be thrown by mathematica++. You can also add your own messages as shown in the previous Section.