diff --git a/.gitignore b/.gitignore index a65d046..9f67c44 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,6 @@ docs/_build/ # PyBuilder target/ + +**/Manifest.toml +**/*.so diff --git a/LICENSE.md b/LICENSE.md index 3a49bdb..2d7170c 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,7 +1,7 @@ The MIT License (MIT) -Copyright (c) 2018: Chris Rackauckas. +Copyright (c) 2018, 2022: Chris Rackauckas. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/MANIFEST.in b/MANIFEST.in index b719a4f..45ff4e6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,7 @@ include README.md include LICENSE.md include docs/*.txt +include diffeqpy/sys_image/*.jl +include diffeqpy/sys_image/Project.toml +include diffeqpy/Project.toml include diffeqpy/*.jl diff --git a/README.md b/README.md index 02ce08c..ad0b01c 100644 --- a/README.md +++ b/README.md @@ -31,16 +31,14 @@ To install diffeqpy, use pip: pip install diffeqpy ``` -Using diffeqpy requires that Julia is installed and in the path, along -with DifferentialEquations.jl and PyCall.jl. To install Julia, -download a generic binary from +You can either install Julia yourself, or allow diffeqpy to do this for you. +To install Julia yourself, download a generic binary from [the JuliaLang site](https://julialang.org/downloads/) and add it to your path. To install Julia packages required for diffeqpy, open up Python interpreter then run: ```pycon >>> import diffeqpy ->>> diffeqpy.install() ``` and you're good! In addition, to improve the performance of your code it is diff --git a/diffeqpy/Project.toml b/diffeqpy/Project.toml new file mode 100644 index 0000000..7bbb6f3 --- /dev/null +++ b/diffeqpy/Project.toml @@ -0,0 +1,5 @@ +[deps] +DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" +DifferentialEquations = "0c46a032-eb83-5123-abaf-570d42b7fbaa" +OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" +PyCall = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0" diff --git a/diffeqpy/__init__.py b/diffeqpy/__init__.py index 511ae03..0abb23b 100644 --- a/diffeqpy/__init__.py +++ b/diffeqpy/__init__.py @@ -1,37 +1,3 @@ -import os -import shutil -import subprocess -import sys +from ._julia_project import julia, compile_diffeqpy, update_diffeqpy -from jill.install import install_julia - -script_dir = os.path.dirname(os.path.realpath(__file__)) - - -def _find_julia(): - # TODO: this should probably fallback to query jill - return shutil.which("julia") - - -def install(*, confirm=False): - """ - Install Julia (if required) and Julia packages required for diffeqpy. - """ - julia = _find_julia() - if not julia: - print("No Julia version found. Installing Julia.") - install_julia(confirm=confirm) - julia = _find_julia() - if not julia: - raise RuntimeError( - "Julia installed with jill but `julia` binary cannot be found in the path" - ) - env = os.environ.copy() - env["PYTHON"] = sys.executable - subprocess.check_call([julia, os.path.join(script_dir, "install.jl")], env=env) - - -def _ensure_installed(*kwargs): - if not _find_julia(): - # TODO: this should probably ensure that packages are installed too - install(*kwargs) +from ._version import __version__ diff --git a/diffeqpy/_julia_project.py b/diffeqpy/_julia_project.py new file mode 100644 index 0000000..4d496c9 --- /dev/null +++ b/diffeqpy/_julia_project.py @@ -0,0 +1,38 @@ +import julia +import logging + +from julia_project import JuliaProject + +import os +diffeqpy_path = os.path.dirname(os.path.abspath(__file__)) + +julia_project = JuliaProject( + name="diffeqpy", + package_path=diffeqpy_path, + preferred_julia_versions = ['1.7', '1.6', 'latest'], + env_prefix = 'DIFFEQPY_', + logging_level = logging.INFO, # or logging.WARN, + console_logging=False +) + +julia_project.run() + +# logger = julia_project.logger + +def compile_diffeqpy(): + """ + Compile a system image for `diffeqpy` in the subdirectory `./sys_image/`. This + system image will be loaded the next time you import `diffeqpy`. + """ + julia_project.compile_julia_project() + + +def update_diffeqpy(): + """ + Remove possible stale Manifest.toml files and compiled system image. + Update Julia packages and rebuild Manifest.toml file. + Before compiling, it's probably a good idea to call this method, then restart Python. + """ + julia_project.update() + + diff --git a/diffeqpy/_version.py b/diffeqpy/_version.py new file mode 100644 index 0000000..7124d09 --- /dev/null +++ b/diffeqpy/_version.py @@ -0,0 +1,2 @@ +"""Define version number here and read it from setup.py automatically""" +__version__ = "1.2.0" diff --git a/diffeqpy/de.py b/diffeqpy/de.py index a0567ce..dc3d66b 100644 --- a/diffeqpy/de.py +++ b/diffeqpy/de.py @@ -1,12 +1,6 @@ import os import sys -from . import _ensure_installed - -# This is terrifying to many people. However, it seems SciML takes pragmatic approach. -_ensure_installed() - -# PyJulia have to be loaded after `_ensure_installed()` from julia import Main script_dir = os.path.dirname(os.path.realpath(__file__)) diff --git a/diffeqpy/install.jl b/diffeqpy/install.jl deleted file mode 100644 index 00dfa35..0000000 --- a/diffeqpy/install.jl +++ /dev/null @@ -1,8 +0,0 @@ -using Pkg -Pkg.add("DifferentialEquations") -Pkg.add("OrdinaryDiffEq") -Pkg.add("DiffEqBase") -Pkg.add("PyCall") -Pkg.build("PyCall") -using DifferentialEquations -using PyCall diff --git a/diffeqpy/sys_image/Project.toml b/diffeqpy/sys_image/Project.toml new file mode 100644 index 0000000..978f722 --- /dev/null +++ b/diffeqpy/sys_image/Project.toml @@ -0,0 +1,6 @@ +[deps] +DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e" +DifferentialEquations = "0c46a032-eb83-5123-abaf-570d42b7fbaa" +OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed" +PackageCompiler = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d" +PyCall = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0" diff --git a/diffeqpy/sys_image/compile_exercise_script.jl b/diffeqpy/sys_image/compile_exercise_script.jl new file mode 100644 index 0000000..a9dd41f --- /dev/null +++ b/diffeqpy/sys_image/compile_exercise_script.jl @@ -0,0 +1,51 @@ +using DifferentialEquations +const DE = DifferentialEquations + +# This is curiously slow +function sde_exercise() + f = (u,p,t) -> 1.01*u + g = (u,p,t) -> 0.87*u + u0 = 0.5 + tspan = (0.0,1.0) + prob = DE.SDEProblem(f,g,u0,tspan) + sol = DE.solve(prob,reltol=1e-3,abstol=1e-3) + return nothing +end + +function ode_exercise() + f = (u,p,t) -> -u + u0 = 0.5 + tspan = (0., 1.) + prob = DE.ODEProblem(f, u0, tspan) + sol = DE.solve(prob) + return nothing +end + +function ode_exercise2() + f = function(u,p,t) + x, y, z = u + sigma, rho, beta = p + return [sigma * (y - x), x * (rho - z) - y, x * y - beta * z] + end + u0 = [1.0,0.0,0.0] + tspan = (0., 100.) + p = [10.0,28.0,8/3] + prob = DE.ODEProblem(f, u0, tspan, p) + sol = DE.solve(prob,saveat=0.01) + return nothing +end + +# From ODE docs +function ode_exercise3() + f(u,p,t) = 1.01*u + u0 = 1/2 + tspan = (0.0,1.0) + prob = ODEProblem(f,u0,tspan) + sol = solve(prob, Tsit5(), reltol=1e-8, abstol=1e-8) + return nothing +end + +ode_exercise() +ode_exercise2() +ode_exercise3() +sde_exercise() diff --git a/diffeqpy/sys_image/compile_julia_project.jl b/diffeqpy/sys_image/compile_julia_project.jl new file mode 100644 index 0000000..d743bc6 --- /dev/null +++ b/diffeqpy/sys_image/compile_julia_project.jl @@ -0,0 +1,11 @@ +using PackageCompiler +using Libdl: Libdl + +packages = [:PyCall, :DiffEqBase, :DifferentialEquations, :OrdinaryDiffEq] + +sysimage_path = joinpath(@__DIR__, "sys_julia_project." * Libdl.dlext) + +#create_sysimage(packages; sysimage_path=sysimage_path) + +create_sysimage(packages; sysimage_path=sysimage_path, + precompile_execution_file=joinpath(@__DIR__, "compile_exercise_script.jl")) diff --git a/setup.py b/setup.py index e72f068..24411b7 100644 --- a/setup.py +++ b/setup.py @@ -4,8 +4,14 @@ def readme(): with open('README.md') as f: return f.read() + +version = {} +with open("./diffeqpy/_version.py") as fp: + exec(fp.read(), version) + + setup(name='diffeqpy', - version='1.2.0', + version=version['__version__'], description='Solving Differential Equations in Python', long_description=readme(), long_description_content_type="text/markdown", @@ -24,6 +30,7 @@ def readme(): author_email='contact@juliadiffeq.org', license='MIT', packages=['diffeqpy','diffeqpy.tests'], - install_requires=['julia>=0.2', 'jill'], + install_requires=['julia>=0.2', + 'julia_project>=0.0.24'], include_package_data=True, zip_safe=False) diff --git a/tox.ini b/tox.ini index 525311a..35b2b5b 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,7 @@ deps = pytest pytest-cov commands = - python -c 'import diffeqpy; diffeqpy.install()' + python -c 'import diffeqpy' py.test \ --pyargs diffeqpy \ {posargs} @@ -12,6 +12,9 @@ whitelist_externals = setenv = # Do not use matplotlib GUI backend during tests. MPLBACKEND = agg + DIFFEQPY_INSTALL_JULIA = y + DIFFEQPY_COMPILE = y + DIFFEQPY_DEPOT = y passenv = # Allow a workaround for "error initializing LibGit2 module": # https://github.com/JuliaLang/julia/issues/18693