Skip to content

Commit 3532c08

Browse files
authored
Merge pull request #197 from MilesCranmer/custom-julia-project
Allow custom shared projects for `julia_project`
2 parents b6ac303 + 7fec442 commit 3532c08

10 files changed

+97
-31
lines changed

.github/workflows/CI.yml

+4
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ jobs:
6969
run: pip install torch # (optional import)
7070
- name: "Run Torch tests"
7171
run: coverage run --append --source=pysr --omit='*/feynman_problems.py' -m unittest test.test_torch
72+
- name: "Run custom env tests"
73+
run: coverage run --append --source=pysr --omit='*/feynman_problems.py' -m unittest test.test_env
7274
- name: Coveralls
7375
env:
7476
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -121,6 +123,8 @@ jobs:
121123
run: python3 -m pip install coverage coveralls
122124
- name: "Run tests"
123125
run: coverage run --append --source=pysr --omit='*/feynman_problems.py' -m unittest test.test
126+
- name: "Run custom env tests"
127+
run: coverage run --append --source=pysr --omit='*/feynman_problems.py' -m unittest test.test_env
124128
- name: Coveralls
125129
env:
126130
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/CI_Windows.yml

+2
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,5 @@ jobs:
6363
run: pip install torch # (optional import)
6464
- name: "Run Torch tests"
6565
run: python -m unittest test.test_torch
66+
- name: "Run custom env tests"
67+
run: python -m unittest test.test_env

.github/workflows/CI_conda_forge.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,4 @@ jobs:
3434
- name: "Install pysr-forge"
3535
run: conda activate test && mamba install pysr
3636
- name: "Run tests"
37-
run: conda activate test && python3 -m unittest test.test
37+
run: conda activate test && python3 -m unittest test.test && python3 -m unittest test.test_env

.github/workflows/CI_docker.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,4 @@ jobs:
3838
- name: Build docker
3939
run: docker build -t pysr --build-arg ARCH=${{ matrix.arch }} --build-arg VERSION=${{ matrix.julia-version }} --build-arg PYVERSION=${{ matrix.python-version }} .
4040
- name: Test docker
41-
run: docker run --platform=${{ matrix.arch }} --rm pysr /bin/bash -c 'python3 -m unittest test.test'
41+
run: docker run --platform=${{ matrix.arch }} --rm pysr /bin/bash -c 'python3 -m unittest test.test && python3 -m unittest test.test_env'

.github/workflows/CI_docker_large_nightly.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,4 @@ jobs:
3131
- name: Build docker
3232
run: docker build -t pysr --build-arg ARCH=${{ matrix.arch }} --build-arg VERSION=${{ matrix.julia-version }} --build-arg PYVERSION=${{ matrix.python-version }} .
3333
- name: Test docker
34-
run: docker run --platform=${{ matrix.arch }} --rm pysr /bin/bash -c 'python3 -m unittest test.test'
34+
run: docker run --platform=${{ matrix.arch }} --rm pysr /bin/bash -c 'python3 -m unittest test.test && python3 -m unittest test.test_env'

.github/workflows/CI_large_nightly.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,4 @@ jobs:
4141
python setup.py install
4242
python -c 'import pysr; pysr.install()'
4343
- name: "Run tests"
44-
run: python -m unittest test.test
44+
run: python -m unittest test.test && python -m unittest test.test_env

.github/workflows/CI_mac.yml

+2
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,5 @@ jobs:
6767
run: pip install torch # (optional import)
6868
- name: "Run Torch tests"
6969
run: python -m unittest test.test_torch
70+
- name: "Run custom env tests"
71+
run: python -m unittest test.test_env

environment.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ dependencies:
99
- scikit-learn
1010
- setuptools
1111
- pyjulia
12-
- openlibm<0.8.0
12+
- openlibm
13+
- openspecfun

pysr/julia_helpers.py

+35-26
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ def _get_julia_env_dir():
3333
# Have to manually get env dir:
3434
try:
3535
julia_env_dir_str = subprocess.run(
36-
["julia", "-e using Pkg; print(Pkg.envdir())"], capture_output=True
36+
["julia", "-e using Pkg; print(Pkg.envdir())"],
37+
capture_output=True,
38+
env=os.environ,
3739
).stdout.decode()
3840
except FileNotFoundError:
3941
env_path = os.environ["PATH"]
@@ -54,6 +56,12 @@ def _set_julia_project_env(julia_project, is_shared):
5456
os.environ["JULIA_PROJECT"] = str(julia_project)
5557

5658

59+
def _get_io_arg(quiet):
60+
io = "devnull" if quiet else "stderr"
61+
io_arg = f"io={io}" if is_julia_version_greater_eq(version=(1, 6, 0)) else ""
62+
return io_arg
63+
64+
5765
def install(julia_project=None, quiet=False): # pragma: no cover
5866
"""
5967
Install PyCall.jl and all required dependencies for SymbolicRegression.jl.
@@ -64,28 +72,13 @@ def install(julia_project=None, quiet=False): # pragma: no cover
6472

6573
_version_assertion()
6674
# Set JULIA_PROJECT so that we install in the pysr environment
67-
julia_project, is_shared = _process_julia_project(julia_project)
68-
_set_julia_project_env(julia_project, is_shared)
75+
processed_julia_project, is_shared = _process_julia_project(julia_project)
76+
_set_julia_project_env(processed_julia_project, is_shared)
6977

7078
julia.install(quiet=quiet)
79+
Main = init_julia(julia_project, quiet=quiet)
80+
io_arg = _get_io_arg(quiet)
7181

72-
if is_shared:
73-
# is_shared is only true if the julia_project arg was None
74-
# See _process_julia_project
75-
Main = init_julia(None)
76-
else:
77-
Main = init_julia(julia_project)
78-
79-
Main.eval("using Pkg")
80-
81-
io = "devnull" if quiet else "stderr"
82-
io_arg = f"io={io}" if is_julia_version_greater_eq(version=(1, 6, 0)) else ""
83-
84-
# Can't pass IO to Julia call as it evaluates to PyObject, so just directly
85-
# use Main.eval:
86-
Main.eval(
87-
f'Pkg.activate("{_escape_filename(julia_project)}", shared = Bool({int(is_shared)}), {io_arg})'
88-
)
8982
if is_shared:
9083
# Install SymbolicRegression.jl:
9184
_add_sr_to_julia_project(Main, io_arg)
@@ -117,11 +110,14 @@ def _import_error_string(julia_project=None):
117110
def _process_julia_project(julia_project):
118111
if julia_project is None:
119112
is_shared = True
120-
julia_project = f"pysr-{__version__}"
113+
processed_julia_project = f"pysr-{__version__}"
114+
elif julia_project[0] == "@":
115+
is_shared = True
116+
processed_julia_project = julia_project[1:]
121117
else:
122118
is_shared = False
123-
julia_project = Path(julia_project)
124-
return julia_project, is_shared
119+
processed_julia_project = Path(julia_project)
120+
return processed_julia_project, is_shared
125121

126122

127123
def is_julia_version_greater_eq(juliainfo=None, version=(1, 6, 0)):
@@ -151,7 +147,7 @@ def _check_for_conflicting_libraries(): # pragma: no cover
151147
)
152148

153149

154-
def init_julia(julia_project=None):
150+
def init_julia(julia_project=None, quiet=False):
155151
"""Initialize julia binary, turning off compiled modules if needed."""
156152
global julia_initialized
157153

@@ -161,8 +157,8 @@ def init_julia(julia_project=None):
161157
from julia.core import JuliaInfo, UnsupportedPythonError
162158

163159
_version_assertion()
164-
julia_project, is_shared = _process_julia_project(julia_project)
165-
_set_julia_project_env(julia_project, is_shared)
160+
processed_julia_project, is_shared = _process_julia_project(julia_project)
161+
_set_julia_project_env(processed_julia_project, is_shared)
166162

167163
try:
168164
info = JuliaInfo.load(julia="julia")
@@ -189,11 +185,24 @@ def init_julia(julia_project=None):
189185

190186
Main = _Main
191187

188+
if julia_initialized:
189+
Main.eval("using Pkg")
190+
191+
io_arg = _get_io_arg(quiet)
192+
# Can't pass IO to Julia call as it evaluates to PyObject, so just directly
193+
# use Main.eval:
194+
Main.eval(
195+
f'Pkg.activate("{_escape_filename(processed_julia_project)}",'
196+
f"shared = Bool({int(is_shared)}), "
197+
f"{io_arg})"
198+
)
199+
192200
julia_initialized = True
193201
return Main
194202

195203

196204
def _add_sr_to_julia_project(Main, io_arg):
205+
Main.eval("using Pkg")
197206
Main.sr_spec = Main.PackageSpec(
198207
name="SymbolicRegression",
199208
url="https://github.com/MilesCranmer/SymbolicRegression.jl",

test/test_env.py

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""Contains tests for creating and initializing custom Julia projects."""
2+
3+
import unittest
4+
import os
5+
from pysr import julia_helpers
6+
from tempfile import TemporaryDirectory
7+
8+
9+
class TestJuliaProject(unittest.TestCase):
10+
"""Various tests for working with Julia projects."""
11+
12+
def test_custom_shared_env(self):
13+
"""Test that we can use PySR in a custom shared env."""
14+
with TemporaryDirectory() as tmpdir:
15+
# Create a temp depot to store julia packages (and our custom env)
16+
Main = julia_helpers.init_julia()
17+
18+
# Set up env:
19+
if "JULIA_DEPOT_PATH" not in os.environ:
20+
old_env = None
21+
os.environ["JULIA_DEPOT_PATH"] = tmpdir
22+
else:
23+
old_env = os.environ["JULIA_DEPOT_PATH"]
24+
os.environ[
25+
"JULIA_DEPOT_PATH"
26+
] = f"{tmpdir}:{os.environ['JULIA_DEPOT_PATH']}"
27+
Main.eval(
28+
f'pushfirst!(DEPOT_PATH, "{julia_helpers._escape_filename(tmpdir)}")'
29+
)
30+
test_env_name = "@pysr_test_env"
31+
julia_helpers.install(julia_project=test_env_name)
32+
Main = julia_helpers.init_julia(julia_project=test_env_name)
33+
34+
# Try to use env:
35+
Main.eval("using SymbolicRegression")
36+
Main.eval("using Pkg")
37+
38+
# Assert we actually loaded it:
39+
cur_project_dir = Main.eval("splitdir(dirname(Base.active_project()))[1]")
40+
potential_shared_project_dirs = Main.eval("Pkg.envdir(DEPOT_PATH[1])")
41+
self.assertEqual(cur_project_dir, potential_shared_project_dirs)
42+
43+
# Clean up:
44+
Main.eval("pop!(DEPOT_PATH)")
45+
if old_env is None:
46+
del os.environ["JULIA_DEPOT_PATH"]
47+
else:
48+
os.environ["JULIA_DEPOT_PATH"] = old_env

0 commit comments

Comments
 (0)