Although the first question was about transferring a specific function from Mathematica to Matlab, with little change, it would be possible to call any Mathematica function from Matlab, a feature that would certainly add value along with Matlink
.
To summarize we use ZeroMQ
to create a socket on Matematica's side, from Matlab, we send our input, Mathematica receives it, and returns the calculated computation. As you might think this could be extended to almost any Mathematica function. This answer focuses on simple integer types.
A requirement for this method is that both Mathematica and Matlab should be running simultaneously.
Mathematica
Our function is simple and works with any length of input:
sampleFunction = {##} + 1 &;
First, define a ZeroMQ socket in Mathematica and use SocketListen
, so on receiving any input, the result would be sent back (port 36000 is used).
socket = SocketOpen[36000, {"ZMQ", "Reply"}]
SocketListen[socket,
Function[{assoc},
WriteString[assoc["SourceSocket"],
StringRiffle[
ToString /@
Apply[sampleFunction,
Internal`StringToMInteger /@ StringSplit[assoc["Data"]]],
" "]]]]
Matlab
Matlab as of now (Matlab 2022a
) does not have built-in support for ZeroMQ. It does offer TCP
but not ZeroMQ
. Since Matlab like Mathematica, can run Java, it's not a problem anymore. Java has multiple implementations of ZeroMQ
, I use JeroMQ
which can be downloaded from Maven.
Import the Jar
file in Matlab (I did face some odd results, so run this in command-line).
javaclasspath('C:\\jeromq-0.5.2.jar');
Import classes:
import org.zeromq.ZMQ;
import org.zeromq.ZContext;
Since we're going to use the socket
variable later in our function, we'll define it as global
(we used port 36000):
Connect to the created socket:
global socket;
context = javaObject('org.zeromq.ZContext');
socket=context.createSocket(org.zeromq.ZMQ.REQ);
socket.connect("tcp://127.0.0.1:36000");
Define the connector function:
function out = sample_function(input)
global socket
socket.send(strjoin(string(input)),0);
out=str2num(string(char(socket.recv()')));
end
Result
sample_function(1)
% Output: 2
sample_function([1 2 3])
% Output: [2 3 4]
Note:
- This is a general form and can be tuned for specific cases by manipulating the above code.
- Code uses
Internal`StringToMInteger
which is undocumented and is subject to change in future versions and only supports Integer
- Testing hundreds of calls shows that each call could take
.01
second
- Matlab's
str2num
implemented using eval
, read the documentation for more information on security considerations
Update 1
Benchmarking Mathematica's Socket
function in the above code shows for reasons beyond my knowledge, they're really slow. This motivated me for an alternative. This update will show an alternative that brings timing for each call from .01
second to 0.00049
second (almost 20X faster).
We'll use the same library as Matlab (JeroMQ
):
<< JLink`;
AddToClassPath["C:\\jeromq-0.5.2.jar"];
Define a class that extends Thread
with 2 input arguments:
- port to use
- function name to call on receiving input (as string)
temp=ExternalEvaluate["Java","
import com.wolfram.jlink.*;
import org.zeromq.ZMQ;
import org.zeromq.ZContext;
public class MyThread extends Thread {
ZMQ.Socket socket;
int port;
String function_name;
public MyThread(int port,String function_name){
this.port=port;
this.function_name=function_name;
}
public void close(){
this.socket.close();
this.stop();
}
public void run(){
KernelLink link = StdLink.getLink();
if (link != null) {
try (ZContext context = new ZContext()){
this.socket = context.createSocket(ZMQ.REP);
try{
socket.bind(\"tcp://127.0.0.1:\"+this.port);
while (!Thread.currentThread().isInterrupted()) {
byte[] reply = socket.recv(0);
String response = link.evaluateToInputForm(this.function_name+\"[\\\"\"+new String(reply, ZMQ.CHARSET)+\"\\\"]\",9999);
if(response.length()>2){
response=response.substring(1,response.length()-1);
}else{
response=\"0\";
}
socket.send(response.getBytes(ZMQ.CHARSET), 0);
}
}catch(Exception e){
link.print(\"Port is in use.\");
}finally{
socket.close();
}
}
}else{
System.out.println(\"link is null.\");
}
}
}
"];
Unlike the last solution, in this implementation, sampleFunction
receives a string and returns a string:
sampleFunction =
StringRiffle[
ToString /@ ((Internal`StringToMInteger /@ StringSplit[#, " "]) +
1), " "] &;
Matlab code is the same as above.
To execute, create an instance of the class and call the run
method:
socketServer = JavaNew[temp, 36000, "sampleFunction"];
socketServer@start[];
after finishing your work, to release the socket run the below code:
socketServer@close[];