Julia is a high-performance programming language with many useful numerical analysis tools baked in. It's easy to write and has a rapidly growing ecosystem of useful packages for scientific computing, machine learning, and much more. Julia, like JMP, is an invaluable tool for scientists, researchers, and engineers.
Julia has a reputation for easy interoperability with other programming languages, and I was curious how hard it would be to call Julia from JSL. If it could be done (it can!), I see a lot of potential in calling Julia from JSL. You could build interactive JMP user interfaces that call highly-performant Julia programs. You could build models in Julia and plot results in JMP or vice versa. You could take the slow parts of your JMP workflow and do them in Julia.
I will discuss two methods for calling Julia from JSL. The first method uses JSL to call Python to call Julia. In the second method, we embed Julia in a Dynamic-link library (DLL) written in C.
Note that I won't be covering much about programming in Julia. If you are curious to learn more about Julia, check out their documentation. There are also learning materials available from Julia Academy.
Calling Julia through Python
Python in JSL
JMP 14 added JSL commands for interfacing with Python directly. Here is a simple example from the scripting index:
// Initialize the Python interface
PythonInit();
// Submit some Python code to run as a JSL string
PythonSubmit("\[message = 'The quick brown fox jumps over the lazy dog']\");
// message is now a variable in Python and can be accessed from JSL with PythonGet()
getStr = PythonGet(message);
Show(getStr);
// Terminate the Python interface
PythonTerm();
PyJulia
If we could call Julia from Python, then we could call Julia from JSL. PyJulia is a Python package that lets you do just that. Let's get it set up. Note: I was successful in using Julia 1.5.4, Python 3.9.5, PyJulia 0.5.7, and JMP 16.1 on Mac.
Assuming a working Python installation, the setup is as follows:
If you haven't already, install Julia. For instructions, visit Platform Specific Instructions for Official Binaries. Downloads of the latest version are available at the Julia download page.
Next, follow the instructions to install PyJulia.
If you are able to use PyJulia's usage instructions in a regular Python session, you are good to go!
PyJulia in JSL
Now in JSL, you first need to initialize Julia in your Python session. Be sure to pass the path to your Julia executable as the runtime keyword argument as below.
Python Init();
// Initialize julia
Python Submit(
"\[
from julia import Julia
Julia(runtime="/usr/local/bin/julia")
]\");
From here, you are able to use Python Submit to run any Julia code.
// Import the main module. This is Julia's workspace.
Python Submit("\[
from julia import Main
Main.xs = [1,2,3] # xs belongs to julia
sines = Main.eval("sin.(xs)") # This returns the sine of each element in xs
]\");
show(PythonGet(sines)); // [0.841470984807897 0.909297426825682 0.141120008059867]
If you have other Julia packages installed, you can access them by Python import. Here I have used Pandas.jl, DataFrames.jl, and Lasso.jl to run a lasso fit for height on Big Class.jmp. Then I use JMP Graph Builder to plot the Actual vs. Predicted.
// Convert datatable to julia dataframe
// Must convert it to a julia Pandas dataframe first
dt = open("$SAMPLE_DATA/Big Class.jmp");
Pythonsend(dt);
Python Submit(
"\[
from julia import Pandas
from julia import DataFrames
from julia import Lasso
Main.dt = dt
predicted = Main.eval("""
using Lasso
df = DataFrames.DataFrame(Pandas.DataFrame(dt))
m = fit(LassoModel, @formula(height ~ sex + weight), df)
predict(m, df)
""")
]\");
// Back to JMP where we plot Actual vs. Predicted
predicted = pythonget("predicted");
dt << newColumn("Predicted height", <<setValues(predicted));
dt << Graph Builder(
Show Control Panel(0),
Show Legend(0),
Variables(X(:Predicted height), Y(:height)),
Elements(Points(X, Y))
);
An actual vs. predicted Graph Builder plot with results calculated in Julia
Embedding Julia in a DLL
It is also possible to embed Julia in C code. I will share some trivial examples, but I'm sure someone more familiar with C could take this further.
LoadDLL
The key here is JSL's LoadDLL function. A great resource for learning about how to use LoadDLL is this paper. As a simple example, we can take the following C function in a file called test.c
// test.c
double myMultiply(double num1, double num2)
{
return num1 * num2;
}
And compile to a DLL
# On Mac
gcc -shared -o test.dylib test.c
# On Windows
gcc -shared -o test.dll test.c
Then in JMP, we can call the C function
dll = loadDLL(convertfilepath("/path/to/my/test.dylib"));
dll << declareFunction("myMultiply",
Convention( STDCALL ),
Arg(Double, "num1"),
Arg(Double, "num2"),
Returns( Double )
);
show(dll << myMultiply(Pi(), 100));
dll << unloadDLL;
Julia DLL
Now let's take a look at a DLL with some embedded Julia. We're going to create a function that calculates the sine of its input, where the input is in degrees.
// call_julia.c
#include <julia.h>
double sineInDegrees(double degrees)
{
// required: setup the Julia context
jl_init();
// You can evaluate Julia code from strings
jl_value_t *factor = jl_eval_string("pi/180");
double factor_unboxed = jl_unbox_float64(factor);
double radians = degrees * factor_unboxed;
// You can call Julia functions directly
jl_function_t *func = jl_get_function(jl_base_module, "sin");
jl_value_t *argument = jl_box_float64(radians);
jl_value_t *ret = jl_call1(func, argument);
double ret_unboxed = jl_unbox_float64(ret);
jl_atexit_hook(0);
return ret_unboxed;
}
Run the below to compile on Mac, where $JULIA_DIR is an environment variable with the path to your julia binary. For me, this was /Applications/Julia-1.7.app/Contents/Resources/julia. I did not try this on Windows, but something similar should work.
# On Mac
gcc -shared -o call_julia.dylib call_julia.c -fPIC -I$JULIA_DIR/include/julia -L$JULIA_DIR/lib -Wl,-rpath,$JULIA_DIR/lib -ljulia
Now we can call sineInDegrees in JSL!
dll = loadDLL(convertfilepath("/path/to/my/call_julia.dylib"));
dll << declareFunction("sineInDegrees",
Convention( STDCALL ),
Arg( Double, "degrees" ),
Returns( Double )
);
show(dll << sineInDegrees(90)); // 1
dll << unloadDLL;
Conclusion
JSL's Python and LoadDLL interfaces allow us to reach beyond JMP and call Julia. It took some trial and error to get this working, but once it is set up, it works quite well. I have shown some simple examples in this post, but I see the potential to build new and interesting applications with JMP and Julia combined.
Thanks for reading. I hope I have left you informed and curious to explore.
A note on versions
I was only able to get Julia to work through Python with Julia 1.5 and the Julia 1.8 beta on Mac. I did not have success using Julia through Python on Windows but I did not try as many versions as on Mac. I used JMP 16 for all my testing.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.