Embedding BosonSampling.jl in Python

Embedding Julia can be done in many ways and in several programming languages such as C/C++, C#, Fortran or Python. As an example we describe here how to integrate BosonSampling.jl to a python project by using PyJulia that provides a high-level interface to Julia through Python. In the following, we will assume that both Julia and Python are installed and added to the PATH.

Installation

The first step is to install the Julia module PyCall

julia> using Pkg
julia> Pkg.add("PyCall")

Make sure that Julia uses your default Python distribution by using the Julia REPL in Shell mode (by entering ;) through the command which python. If the answer is another Python distribution than your default one, reset the path before building PyCall:

julia> ENV["PYTHON"] = "/path/to/python/binary"
julia> Pkg.build("PyCall")

One can now install PyJulia via pip through

$ python -m pip install --user julia

or directly via the Github repository: https://github.com/JuliaPy/pyjulia.

Finally, install the remaining dependencies requiered by PyJulia:

$ python
>>> import julia
>>> julia.install

It might still be possible that the Python interpreter used by PyCall.jl is not supported by PyJulia, throwing errors when importing modules from Julia. In this case, run the following lines

>>> from julia.api import Julia
>>> jl = Julia(compiled_modules=False)

Example and workarounds

Once Julia imported in your Python project, any Julia module can be loaded via the standard import:

>>> from julia import Main
>>> from julia import Base
>>> from julia import BosonSampling as bs

to finally use BosonSampling.jl as a Python module

>>> r = bs.first_modes(4,4)
>>> r
<PyCall.jlwrap state = [1, 1, 1, 1]>

A key strength of BosonSampling.jl for efficient computing and modularity is its type architecture. As we have seen previously in the tutorial in Basic usage, the definition of an Input requires to play with types and therefore use the delimiters {}. Those symbols are not valid identifiers for Python which therefore prevents to define any Input or take advantage of any type hierarchy in Julia. The easiest workaround is to define a function, e.g

>>> curly = Main.eval("(a,b...) -> a{b...}")

This function defined once for all, one can now define Bosonic Input with the ModeOccupation defined here above

>>> i = curly(bs.Input, bs.Bosonic)(r)

to finally choose an interferometer and run a boson sampling simulation via the cliffords_sampler

>>> U = bs.RandHaar(4)
>>> bs.cliffords_sampler(input=i, interf=U)