Skip to content

Commit b448769

Browse files
Merge pull request #132 from LilithHafner/lh/install
Make package installation automatic and deprecate manual installation
2 parents e280355 + b2f6e7a commit b448769

File tree

13 files changed

+70
-60
lines changed

13 files changed

+70
-60
lines changed

README.md

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,6 @@ To install diffeqpy, use pip:
3131
pip install diffeqpy
3232
```
3333

34-
To install Julia packages required (and Julia if needed) for diffeqpy, open up Python
35-
interpreter then run:
36-
37-
```pycon
38-
>>> import diffeqpy
39-
>>> diffeqpy.install()
40-
```
41-
4234
and you're good!
4335

4436
## Collab Notebook Examples
@@ -171,7 +163,7 @@ sol = de.solve(prob)
171163
#### Limitations
172164

173165
`de.jit`, uses ModelingToolkit.jl's `modelingtoolkitize` internally and some
174-
restrictions apply. Not all models can be jitted. See the
166+
restrictions apply. Not all models can be jitted. See the
175167
[`modelingtoolkitize` documentation](https://docs.sciml.ai/ModelingToolkit/stable/tutorials/modelingtoolkitize/#What-is-modelingtoolkitize?)
176168
for more info.
177169

@@ -552,29 +544,19 @@ sol = de.solve(ensembleprob,de.Tsit5(),de.EnsembleSerial(),trajectories=10000,sa
552544
```
553545

554546
To add GPUs to the mix, we need to bring in [DiffEqGPU](https://github.com/SciML/DiffEqGPU.jl).
555-
The `diffeqpy.install_cuda()` will install CUDA for you and bring all of the bindings into the returned object:
547+
The command `from diffeqpy import cuda` will install CUDA for you and bring all of the bindings into the returned object:
556548

557-
```py
558-
diffeqpy.install_cuda()
559-
```
560-
561-
then run the cuda import:
562-
563-
```py
564-
from diffeqpy import cuda
565-
```
566-
567-
#### Note: `diffeqpy.install_cuda()` and `from diffeqpy import cuda` can take awhile to run the first time as it installs the drivers!
549+
#### Note: `from diffeqpy import cuda` can take awhile to run the first time as it installs the drivers!
568550

569551
Now we simply use `EnsembleGPUKernel(cuda.CUDABackend())` with a
570-
GPU-specialized ODE solver `cuda.GPUTsit5()` to solve 10,000 ODEs on the GPU in
552+
GPU-specialized ODE solver `cuda.GPUTsit5()` to solve 10,000 ODEs on the GPU in
571553
parallel:
572554

573555
```py
574556
sol = de.solve(ensembleprob,cuda.GPUTsit5(),cuda.EnsembleGPUKernel(cuda.CUDABackend()),trajectories=10000,saveat=0.01)
575557
```
576558

577-
For the full list of choices for specialized GPU solvers, see
559+
For the full list of choices for specialized GPU solvers, see
578560
[the DiffEqGPU.jl documentation](https://docs.sciml.ai/DiffEqGPU/stable/manual/ensemblegpukernel/).
579561

580562
Note that `EnsembleGPUArray` can be used as well, like:
@@ -598,20 +580,20 @@ ensemble generation:
598580
```py
599581
import numpy as np
600582
from scipy.integrate import odeint
601-
583+
602584
def lorenz(state, t, sigma, beta, rho):
603585
x, y, z = state
604-
586+
605587
dx = sigma * (y - x)
606588
dy = x * (rho - z) - y
607589
dz = x * y - beta * z
608-
590+
609591
return [dx, dy, dz]
610592

611593
sigma = 10.0
612594
beta = 8.0 / 3.0
613-
rho = 28.0
614-
p = (sigma, beta, rho)
595+
rho = 28.0
596+
p = (sigma, beta, rho)
615597
y0 = [1.0, 1.0, 1.0]
616598

617599
t = np.arange(0.0, 100.0, 0.01)

diffeqpy/__init__.py

Lines changed: 53 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,58 @@
1-
import os
21
import shutil
3-
import subprocess
4-
import sys
5-
62
from jill.install import install_julia
73

8-
script_dir = os.path.dirname(os.path.realpath(__file__))
9-
4+
# juliacall must be loaded after `_ensure_julia_installed()` is run,
5+
# so this import is in `load_julia_packages()`
6+
# from juliacall import Main
107

118
def _find_julia():
129
# TODO: this should probably fallback to query jill
1310
return shutil.which("julia")
1411

12+
def _ensure_julia_installed():
13+
if not _find_julia():
14+
print("No Julia version found. Installing Julia.")
15+
install_julia()
16+
if not _find_julia():
17+
raise RuntimeError(
18+
"Julia installed with jill but `julia` binary cannot be found in the path"
19+
)
20+
21+
# TODO: upstream this function or an alternative into juliacall
22+
def load_julia_packages(*names):
23+
"""
24+
Load Julia packages and return references to them, automatically installing julia and
25+
the packages as necessary.
26+
"""
27+
# This is terrifying to many people. However, it seems SciML takes pragmatic approach.
28+
_ensure_julia_installed()
29+
30+
script = """import Pkg
31+
Pkg.activate(\"diffeqpy\", shared=true)
32+
try
33+
import {0}
34+
catch e
35+
e isa ArgumentError || throw(e)
36+
Pkg.add([{1}])
37+
import {0}
38+
end
39+
{0}""".format(", ".join(names), ", ".join(f'"{name}"' for name in names))
40+
41+
# Unfortunately, `seval` doesn't support multi-line strings
42+
# https://github.com/JuliaPy/PythonCall.jl/issues/433
43+
script = script.replace("\n", ";")
44+
45+
# Must be loaded after `_ensure_julia_installed()`
46+
from juliacall import Main
47+
return Main.seval(script)
48+
49+
50+
51+
# Deprecated (julia and packages now auto-install)
52+
import os
53+
import subprocess
54+
import sys
55+
script_dir = os.path.dirname(os.path.realpath(__file__))
1556

1657
def install(*, confirm=False):
1758
"""
@@ -28,7 +69,7 @@ def install(*, confirm=False):
2869
)
2970
env = os.environ.copy()
3071
env["PYTHON"] = sys.executable
31-
subprocess.check_call([julia, os.path.join(script_dir, "install.jl")], env=env)
72+
subprocess.check_call([julia, os.path.join(script_dir, "deprecated/install.jl")], env=env)
3273

3374
def install_cuda():
3475
julia = _find_julia()
@@ -38,7 +79,7 @@ def install_cuda():
3879
)
3980
env = os.environ.copy()
4081
env["PYTHON"] = sys.executable
41-
subprocess.check_call([julia, os.path.join(script_dir, "install_cuda.jl")], env=env)
82+
subprocess.check_call([julia, os.path.join(script_dir, "deprecated/install_cuda.jl")], env=env)
4283

4384
def install_amdgpu():
4485
julia = _find_julia()
@@ -48,17 +89,17 @@ def install_amdgpu():
4889
)
4990
env = os.environ.copy()
5091
env["PYTHON"] = sys.executable
51-
subprocess.check_call([julia, os.path.join(script_dir, "install_amdgpu.jl")], env=env)
92+
subprocess.check_call([julia, os.path.join(script_dir, "deprecated/install_amdgpu.jl")], env=env)
5293

5394
def install_metal():
5495
julia = _find_julia()
5596
if not julia:
5697
raise RuntimeError(
57-
"Julia must be installed before adding Metal. Please run `diffeqpy.install()` first"
98+
"Julia must be installed before adding Metal. Please run `deprecated/diffeqpy.install()` first"
5899
)
59100
env = os.environ.copy()
60101
env["PYTHON"] = sys.executable
61-
subprocess.check_call([julia, os.path.join(script_dir, "install_metal.jl")], env=env)
102+
subprocess.check_call([julia, os.path.join(script_dir, "deprecated/install_metal.jl")], env=env)
62103

63104
def install_oneapi():
64105
julia = _find_julia()
@@ -68,18 +109,4 @@ def install_oneapi():
68109
)
69110
env = os.environ.copy()
70111
env["PYTHON"] = sys.executable
71-
subprocess.check_call([julia, os.path.join(script_dir, "install_oneapi.jl")], env=env)
72-
73-
def _ensure_installed(*kwargs):
74-
if not _find_julia():
75-
# TODO: this should probably ensure that packages are installed too
76-
install(*kwargs)
77-
78-
# TODO: upstream this function or an alternative into juliacall
79-
def load_julia_packages(names):
80-
# This is terrifying to many people. However, it seems SciML takes pragmatic approach.
81-
_ensure_installed()
82-
83-
# Must be loaded after `_ensure_installed()`
84-
from juliacall import Main
85-
return Main.seval(f"import Pkg; Pkg.activate(\"diffeqpy\", shared=true); import {names}; {names}")
112+
subprocess.check_call([julia, os.path.join(script_dir, "deprecated/install_oneapi.jl")], env=env)

diffeqpy/amdgpu.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import sys
22
from . import load_julia_packages
3-
amdgpu, _ = load_julia_packages("DiffEqGPU, AMDGPU")
3+
amdgpu, _ = load_julia_packages("DiffEqGPU", "AMDGPU")
44
from juliacall import Main
55
amdgpu.AMDGPUBackend = Main.seval("AMDGPU.AMDGPUBackend") # kinda hacky
66
sys.modules[__name__] = amdgpu # mutate myself

diffeqpy/cuda.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import sys
22
from . import load_julia_packages
3-
cuda, _ = load_julia_packages("DiffEqGPU, CUDA")
3+
cuda, _ = load_julia_packages("DiffEqGPU", "CUDA")
44
from juliacall import Main
55
cuda.CUDABackend = Main.seval("CUDA.CUDABackend") # kinda hacky
66
sys.modules[__name__] = cuda # mutate myself

diffeqpy/de.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import sys
22
from . import load_julia_packages
3-
de, _ = load_julia_packages("DifferentialEquations, ModelingToolkit")
3+
de, _, _ = load_julia_packages("DifferentialEquations", "ModelingToolkit", "PythonCall")
44
from juliacall import Main
55
de.jit = Main.seval("jit(x) = typeof(x).name.wrapper(ModelingToolkit.modelingtoolkitize(x), x.u0, x.tspan, x.p)") # kinda hackey
66
de.jit32 = Main.seval("jit(x) = typeof(x).name.wrapper(ModelingToolkit.modelingtoolkitize(x), Float32.(x.u0), Float32.(x.tspan), Float32.(x.p))") # kinda hackey
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

diffeqpy/metal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import sys
22
from . import load_julia_packages
3-
metal, _ = load_julia_packages("DiffEqGPU, Metal")
3+
metal, _ = load_julia_packages("DiffEqGPU", "Metal")
44
from juliacall import Main
55
metal.MetalBackend = Main.seval("Metal.MetalBackend") # kinda hacky
66
sys.modules[__name__] = metal # mutate myself

diffeqpy/ode.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
import sys
22
from . import load_julia_packages
3-
sys.modules[__name__] = load_julia_packages("OrdinaryDiffEq") # mutate myself
3+
ode, _ = load_julia_packages("OrdinaryDiffEq", "PythonCall")
4+
sys.modules[__name__] = ode # mutate myself

diffeqpy/oneapi.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import sys
22
from . import load_julia_packages
3-
oneapi, _ = load_julia_packages("DiffEqGPU, oneAPI")
3+
oneapi, _ = load_julia_packages("DiffEqGPU", "oneAPI")
44
from juliacall import Main
55
oneapi.oneAPIBackend = Main.seval("oneAPI.oneAPIBackend") # kinda hacky
66
sys.modules[__name__] = oneapi # mutate myself

0 commit comments

Comments
 (0)