from __future__ import annotations
import time
from julia import Julia
from julia.core import JuliaError
# Create an instance of julia without incremental precompilation.
# This does not seem to affect performance much
base_julia = Julia(compiled_modules=False)
# These must be imported after creating the julia name space
from julia import Pkg
from julia import Main
import uuid
# Random number generator on Julia side
_random_number_generator = None
[docs]def random_number_generator(seed=None):
''' Return a random number generator on the Julia side. MersenneTwister is the
only option here.
Parameters
----------
seed : integer
A long integer used as a seed when creating the generator
Returns
-------
dp.JuliaName
The random number generator wrapped in a JuliaName
'''
global _random_number_generator
if _random_number_generator is None:
if seed is None:
seed = int(time.time())
_random_number_generator = JuliaName()
Main.eval('using Random')
Main.eval(f'{_random_number_generator._name} = MersenneTwister({seed})')
return _random_number_generator
[docs]class JuliaMain():
''' Maps to julia.main from the julia library, unless the setting
an object implemented here (inherits JuliaName). JuliaNames are
assigned directly.
'''
def __setattr__(self, name, value):
if type(value) == JuliaName or JuliaName in type(value).__bases__:
Main.eval(f'{name} = {value._name}')
else:
Main.__setattr__(name, value)
def __getattr__(self, name):
if Main.eval(f"isdefined(Main, :{name})"):
return Main.__getattr__(name)
else:
raise AttributeError(f'{name} not defined in Julia name space')
[docs] def eval(self, command):
''' Evaluate Julia code
Parameters
----------
command: string
A string containing Julia code
'''
return Main.eval(command)
# Expose the Julia runner as dp.julia
julia = JuliaMain()
[docs]def load_libs():
''' Load Julia dependencies
'''
Main.eval('using DecisionProgramming')
Main.eval('using Gurobi')
Main.eval('using JuMP')
# Define a PathUtility type on Julia side
command = f'''struct PathUtility <: AbstractPathUtility
data::Array{{AffExpr}}
end
Base.getindex(U::PathUtility, i::State) = getindex(U.data, i)
Base.getindex(U::PathUtility, I::Vararg{{State,N}}) where N = getindex(U.data, I...)
(U::PathUtility)(s::Path) = value.(U[s...])
'''
Main.eval(command)
[docs]def activate():
""" Activate a Julia environment in the working
directory and load requirements
"""
Pkg.activate(".")
load_libs()
[docs]def setupProject():
""" Activate a Julia environment in the working
directory and install DecisionProgramming,
Gurobi and JuMP
"""
Pkg.activate(".")
github_url = "https://github.com/gamma-opt/DecisionProgramming.jl.git"
Pkg.add(url=github_url)
Pkg.add("Gurobi")
Pkg.build("Gurobi")
Pkg.add("JuMP")
load_libs()
[docs]def handle_index_syntax(key):
""" Turn a key tuple into Julia slicing and indexing syntax
Parameters
----------
key: String, integer, slice or a tuple of these
Returns
-------
string
The index string in Julia format
"""
if isinstance(key, tuple):
indexes = []
for index in key:
if index == slice(None):
indexes += ':'
elif isinstance(index, str):
indexes += [f'"{index}"']
elif isinstance(index, int):
indexes += [str(index+1)]
else:
raise IndexError('Index not must be string, integer or ":"')
index_string = ','.join(indexes)
elif key == slice(None):
index_string = ':'
elif isinstance(key, str):
index_string = f'"{key}"'
elif isinstance(key, int):
index_string = key+1
else:
raise IndexError('Index must be string, integer or ":"')
return index_string
[docs]class JuliaName():
''' Base class for all following Julia objects. Stores the object
name in the Julia main name space and defines string
representation from Julia.
'''
def __init__(self):
self._name = 'pyDP'+uuid.uuid4().hex[:10]
def __str__(self):
return Main.eval(f'repr({self._name})')
def __repr__(self):
return Main.eval(f'repr({self._name})')
def __getattr__(self, name):
if Main.eval(f"isdefined(Main, :{self._name})") and Main.eval(f"hasproperty({self._name}, :{name})"):
r = JuliaName()
Main.eval(f'{r._name} = {self._name}.{name}')
return r
raise AttributeError
def __getitem__(self, key):
r = JuliaName()
index_string = handle_index_syntax(key)
try:
Main.eval(f'{r._name} = {self._name}[{index_string}]')
except JuliaError as j:
raise IndexError(j)
return r
def __setitem__(self, key, value):
index_string = handle_index_syntax(key)
try:
command = f'{self._name}[{index_string}] = {value}'
Main.eval(command)
except JuliaError as j:
raise IndexError(j)