[Tex/LaTex] How to use Julia code in the Latex document

ffijulialuatex

according to https://towardsdatascience.com/5-ways-julia-is-better-than-python-334cc66d64ae it says (under item 2) the following

  Julia code is universally executable in R, Latex, Python, and C.

I googled a little bit and could not find an example of how to do this. of course one would need to have Julia itself installed on one's PC in addition to TexLive.

I am not talking about listing Julia code in Latex of course. Or about using Latex inside Julia.

The above says Julia code executable in Latex, which for me, means one can write some Julia function and call it from Latex and get the result back. Same as using Lua from Lualatex now. And the same when calling Python from Latex.

At least this is how I read the above.

If so, could we have a very small example of how to do this?

For starter, here is a MWE I wrote sometime ago about using Python in Latex

\documentclass[11pt]{article}%
  \usepackage{pythontex}
  \usepackage[T1]{fontenc}
  \usepackage[utf8]{inputenc}
  \begin{document}
  \begin{pyconsole}
  x = 987.27
  x = x**2
  \end{pyconsole}

  The variable is $x=\pycon{x}$
  \end{document}

Which needs to be compiled like this:

  pdflatex foo.tex
  /usr/local/texlive/2019/bin/x86_64-linux/pythontex  foo.tex
  pdflatex foo.tex

How to do something similar to the above for Julia?

Best Answer

Julia has a C-API which you can use in LuaTeX via FFI. It requires some boilerplate code where you repeat some function prototypes from the API and some wrappers to convert between C types and Lua types. This is actually pretty painless. Save this file as julia.lua in the same directory as your document.

julia.lua

local ffi = require("ffi")
local JULIA = ffi.load("julia", true)

ffi.cdef [[
// Types
typedef struct _jl_value_t jl_value_t;

// Initialization
void jl_init__threading(void);
void jl_init_with_image__threading(const char *julia_bindir,
                                   const char *image_relative_path);

// Execution and conversion
jl_value_t *jl_eval_string(const char*);
const char *jl_string_ptr(jl_value_t *);
const char *jl_typeof_str(jl_value_t *);
]]

local julia_initialized = false

local julia = {}

function julia.init(rpath)
    -- Initialize the Julia interpreter (singleton)
    if not julia_initialized then
        if rpath then
            JULIA.jl_init_with_image__threading(rpath, "sys.so")
        else
            JULIA.jl_init__threading()
        end
        julia_initialized = true
    end
end

function julia.run(expr)
    assert(julia_initialized)
    JULIA.jl_eval_string(expr)
end

function julia.eval(expr)
    assert(julia_initialized)
    local jlval, cstr, str
    jlval = JULIA.jl_eval_string(expr)
    -- First check that the datatype
    if jlval ~= ffi.NULL then
        cstr = JULIA.jl_typeof_str(jlval)
    else
        return nil
    end
    if cstr ~= ffi.NULL then
        str = ffi.string(cstr)
    else
        return nil
    end
    -- if the datatype is string, convert to Lua string
    if str == "String" then
        cstr = JULIA.jl_string_ptr(jlval)
    else
        return nil
    end
    if cstr ~= ffi.NULL then
        str = ffi.string(cstr)
    else
        return nil
    end
    return str
end

return julia

You can now use this module in your document via require("julia"). It provides three functions:

  1. julia.init(rpath) This function has to be called once before the other functions to initialize the interpreter. Depending on your installation it might be necessary to provide the runtime path of Julia as the argument rpath (see also Calling Julia from Lua). In my installation this is not necessary and I can just omit the argument.

  2. julia.run(expr) Just runs expr.

  3. julia.eval(expr) This also runs the expression expr but attempts convert the last value of expr to string and returns it.

The Julia code you pass to this should be error-free, because there is not checking for errors on the Julia level. I don't know whether interpreter just bails if it sees an error, but this would also crash your document compilation.

Here I wrap the julia.eval function into some TeX-specific argument scanning and printing and assign it to a macro called \directjulia which can be used similar to \directlua. The big bonus of this over things like pythontex is that everything is fully expandable. You have to process the document with --shell-escape.

\documentclass{article}
\usepackage{luacode}
\begin{luacode*}
julia = require("julia")
julia.init()

local t = lua.get_functions_table()
t[#t + 1] = function()
    local expr = token.scan_string()
    local str = julia.eval(expr)
    if str then
        tex.sprint(str)
    end
end
token.set_lua("directjulia", #t, "global")
\end{luacode*}
\begin{document}

\begin{luacode*}
julia.run[===[
x = [1 2 3]'
A = [1 0 1; 0 1 1; 1 1 0]

y = x'*A*x
]===]
\end{luacode*}

\edef\value{\directjulia{string(y[1])}}

\texttt{\meaning\value}

\end{document}