3
$\begingroup$

This question is the same as this post which was closed too early to be answered and efforts to reopen it were unsuccessful.

How could we use a defined function inside Mathematica from Matlab?

With Mathematica's growing number of functions, the notion of transferring would not be generalizable to the whole collection. There is a similar answer like Create a Mathematica function that can be called from R or Matlab which uses CloudDeploy but if you want to work on the same computer, What are other alternatives?

$\endgroup$

1 Answer 1

7
$\begingroup$

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:

  1. port to use
  2. 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[];
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.