"""Interface to Julia via pyjulia."""
from typing import Callable, Union
import numpy as np
from ..function import Objective
def _as_array(input):
"""Convert output to numpy array."""
if callable(input):
def wrapper(*args, **kwargs):
return np.asarray(input(*args, **kwargs))
return wrapper
return np.asarray(input)
def _read_source(module_name: str, source_file: str) -> None:
"""Read source if module not attached to julia Main yet.
Parameters
----------
module_name: Julia module name.
source_file: Qualified Julia source file.
"""
from julia import Main
if not hasattr(Main, module_name):
Main.include(source_file)
[docs]
class JuliaObjective(Objective):
"""Wrapper around an objective defined in Julia.
This class provides objective function wrappers around Julia objects.
It expects the corresponding Julia objects to be defined in a
`source_file` within a `module`.
We use the PyJulia package to access Julia from inside Python.
It can be installed via `pip install pypesto[julia]`, however requires
additional Julia dependencies to be installed via:
>>> python -c "import julia; julia.install()"
For further information, see
https://pyjulia.readthedocs.io/en/latest/installation.html.
There are some known problems, e.g. with statically linked Python
interpreters, see
https://pyjulia.readthedocs.io/en/latest/troubleshooting.html
for details.
Possible solutions are to pass ``compiled_modules=False`` to the Julia
constructor early in your code:
>>> from julia.api import Julia
>>> jl = Julia(compiled_modules=False)
This however slows down loading and using Julia packages, especially for
large ones.
An alternative is to use the ``python-jl`` command shipped with PyJulia:
>>> python-jl MY_SCRIPT.py
This basically launches a Python interpreter inside Julia.
When using Jupyter notebooks, this wrapper can be installed as an
additional kernel via:
>>> python -m ipykernel install --name python-jl [--prefix=/path/to/python/env]
And changing the first argument in
``/path/to/python/env/share/jupyter/kernels/python-jl/kernel.json``
to ``python-jl``.
Model simulations are eagerly converted to Python objects
(specifically, `numpy.ndarray` and `pandas.DataFrame`).
This can introduce overhead and could be avoided by an alternative
lazy implementation.
Parameters
----------
module:
Julia module name.
source_file:
Julia source file name. Defaults to `{module}.jl`.
fun, grad, hess, res, sres:
Names of callables within the Julia code of the corresponding
objective functions and derivatives.
"""
[docs]
def __init__(
self,
module: str,
source_file: str = None,
fun: str = None,
grad: str = None,
hess: str = None,
res: str = None,
sres: str = None,
):
# lazy imports
try:
from julia import Main # noqa: F401
except ImportError:
raise ImportError(
"Install PyJulia, e.g. via `pip install pypesto[julia]`, "
"and see the class documentation",
) from None
# store module name and source file
self.module: str = module
if source_file is None:
source_file = module + ".jl"
self.source_file: str = source_file
_read_source(self.module, self.source_file)
# store function names
self._fun: str = fun
self._grad: str = grad
self._hess: str = hess
self._res: str = res
self._sres: str = sres
# get callables
fun, grad, hess, res, sres = self._get_callables()
super().__init__(fun=fun, grad=grad, hess=hess, res=res, sres=sres)
[docs]
def get(self, name: str, as_array: bool = False) -> Union[Callable, None]:
"""Get variable from Julia module.
Use this function to access any variable from the Julia module.
"""
from julia import Main
if name is not None:
ret = getattr(getattr(Main, self.module), name, None)
if as_array:
ret = _as_array(ret)
return ret
return None
def _get_callables(self) -> tuple:
"""Get all callables."""
fun = self.get(self._fun)
grad = self.get(self._grad, as_array=True)
hess = self.get(self._hess, as_array=True)
res = self.get(self._res, as_array=True)
sres = self.get(self._sres, as_array=True)
return fun, grad, hess, res, sres
def __getstate__(self):
return {
"module": self.module,
"source_file": self.source_file,
"_fun": self._fun,
"_grad": self._grad,
"_hess": self._hess,
"_res": self._res,
"_sres": self._sres,
}
def __setstate__(self, d):
for key, val in d.items():
setattr(self, key, val)
_read_source(self.module, self.source_file)
fun, grad, hess, res, sres = self._get_callables()
super().__init__(fun=fun, grad=grad, hess=hess, res=res, sres=sres)
def __deepcopy__(self, memodict=None) -> "JuliaObjective":
return JuliaObjective(
module=self.module,
source_file=self.source_file,
fun=self._fun,
grad=self._grad,
hess=self._hess,
res=self._res,
sres=self._sres,
)
[docs]
def display_source_ipython(source_file: str):
"""Display source code as syntax highlighted HTML within IPython."""
import IPython.display as display
from pygments import highlight
from pygments.formatters import HtmlFormatter
from pygments.lexers import JuliaLexer
with open(source_file) as f:
code = f.read()
formatter = HtmlFormatter()
return display.HTML(
'<style type="text/css">{}</style>{}'.format(
formatter.get_style_defs(".highlight"),
highlight(code, JuliaLexer(), formatter),
)
)