Running Julia from JAVA? What is crazier?

@venuur thanks for following along. It’s kind of a scary place to be but its also kind of fun! I am really brushing up on some concepts that were dormont in my brain for a while.

So I made a minimum viable example that requires JeroMQ to be built with maven(don’t run the tests trust me its fine and it’ll save you a half hour), and linked to the JAVA project(https://github.com/zeromq/jeromq/releases/tag/v0.5.1). Also need the ZMQ package to be installed in Julia! What its doing…

  1. Java calls a Julia program to be run in the background
  2. Java and Julia exchange a classic syn-ack-synack handshake (useful for debugging)
  3. Java zmq’s to julia the julia function it wants to use
  4. Julia evaluates the java instruction, applies it to a float64, then sends it back to java.
  5. Java converts the number from little endian to big endian then displays it.
  6. They both close the connections and the world is at peace
import org.zeromq.ZMQ;
import org.zeromq.ZContext;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class ZMQBareBones {
    Process juliaServer;
    BufferedReader stdInput;
    BufferedReader stdError;

    public static void main(String[] args) throws Exception {
        ZMQBareBones zmqApp = new ZMQBareBones();
        zmqApp.run();
    }

    public void run() throws Exception {
        String s = null;
        try {
            juliaServer = Runtime.getRuntime().exec("julia /home/caseykneale/Desktop/whater.jl");

            stdInput = new BufferedReader(new InputStreamReader(juliaServer.getInputStream()));
            stdError = new BufferedReader(new InputStreamReader(juliaServer.getErrorStream()));
            // read the output from the Julia console directly into java!
            //Currently not working needs a loop/wait/cancel routine but whatever the concept does work.
            System.out.println("Communication Log:\n");
            if ((s = stdInput.readLine()) != null) {
                System.out.println(s);
            }
            System.out.println("Beginning ZMQ:\n");
            try (ZContext context = new ZContext()) {
                // Socket to talk to clients
                ZMQ.Socket socket = context.createSocket(ZMQ.REP);
                socket.bind("tcp://*:5555");
                boolean handshake = false;
                double result = Double.NaN;
                while ( (!Thread.currentThread().isInterrupted()) ) {
                    // Block until a message is received
                    byte[] reply = socket.recv(0);

                    if(handshake == false) {
                        String replymsg = new String(reply, ZMQ.CHARSET);

                        if (replymsg.equals("SYN")) {
                            System.out.println("Received: Communication from Julia Client.");
                            String response = "ACK";
                            socket.send(response.getBytes(ZMQ.CHARSET), 0);
                        }
                        if (replymsg.equals("SYNACK")) {
                            System.out.println("ZMQ Handshake completed!");
                            handshake = true;
                            socket.setReceiveBufferSize(8);
                            System.out.println("Sending fn to Julia for evaluation...");
                            //Now that we know we have communication back and forth... let's do something dastardly...
                            socket.send("exp_decay".getBytes(ZMQ.CHARSET), 0);
                            //socket.send("reciprocal".getBytes(ZMQ.CHARSET), 0);
                        }
                    } else {
                        System.out.println(reply.length);
                        //Java likes big endian, Julia likes little endian - such is life...
                        result = ByteBuffer.wrap(reply).order(ByteOrder.LITTLE_ENDIAN).getDouble();
                        System.out.println("Computed result: ");
                        System.out.println(result);
                    }
                }

            }

            //Close Julia server
            juliaServer.destroy();
            System.exit(0);
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(-1);
        }
    }

}
println("Execution of Julia code successful.\n Starting compute client...")

#define some inhouse fns for our "library"
exp_decay(x) = exp(-x)
reciprocal(x) = 1 / x

using ZMQ
client_socket = Socket(REQ)
connect(client_socket, "tcp://localhost:5555")

send(client_socket, "SYN")
msgback = recv(client_socket, String)

if ( msgback == "ACK" )
    send(client_socket, "SYNACK")
end

awesomest_data_ever = 111.111
#get the Java user's desired function call
function_call = recv(client_socket, String)
result = eval( Meta.parse("$function_call($awesomest_data_ever)") )
#pass back the result as a Float64
send(client_socket, result)

#we're done here...
close(client_socket)

What I’m wondering now is - is it easier to just use a BufferedWriter to write to a background Julia REPL or something? Not sure…

Also the pattern of a massive ZMQ loop is pretty impractical for an interactive application. Might need to see how other people are doing this. My guess is they have listeners polling 24/7 and they drop in and drop out of the server context regularly.