Skip to content

Commit

Permalink
Merge dc2fef5 into 31ffc0e
Browse files Browse the repository at this point in the history
  • Loading branch information
MilesCranmer committed Nov 29, 2022
2 parents 31ffc0e + dc2fef5 commit d12a61c
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 67 deletions.
2 changes: 1 addition & 1 deletion pysr/__init__.py
Expand Up @@ -7,7 +7,7 @@
best_callable,
best_row,
)
from .julia_helpers import install
from .package_compiler import install, compile
from .feynman_problems import Problem, FeynmanProblem
from .export_jax import sympy2jax
from .export_torch import sympy2torch
126 changes: 64 additions & 62 deletions pysr/julia_helpers.py
Expand Up @@ -32,11 +32,44 @@ def _load_juliainfo():
return juliainfo


def _get_julia_project_dir():
# Assumes it is in JULIA_PROJECT:
assert "JULIA_PROJECT" in os.environ and os.environ["JULIA_PROJECT"] != ""
try:
cmds = [
"julia",
"--compile=min",
"--startup-file=no",
"-O0",
"-g0",
"-e import Pkg: project; print(project().path)",
]
julia_project_dir_str = subprocess.run(
cmds,
capture_output=True,
env=os.environ,
).stdout.decode()
except FileNotFoundError:
env_path = os.environ["PATH"]
raise FileNotFoundError(
f"Julia is not installed in your PATH. Please install Julia and add it to your PATH.\n\nCurrent PATH: {env_path}",
)
return Path(julia_project_dir_str).parent


def _get_julia_env_dir():
# Have to manually get env dir:
try:
cmds = [
"julia",
"--compile=min",
"--startup-file=no",
"-O0",
"-g0",
"-e import Pkg: envdir; print(envdir())",
]
julia_env_dir_str = subprocess.run(
["julia", "-e using Pkg; print(Pkg.envdir())"],
cmds,
capture_output=True,
env=os.environ,
).stdout.decode()
Expand Down Expand Up @@ -65,37 +98,6 @@ def _get_io_arg(quiet):
return io_arg


def install(julia_project=None, quiet=False): # pragma: no cover
"""
Install PyCall.jl and all required dependencies for SymbolicRegression.jl.
Also updates the local Julia registry.
"""
import julia

_julia_version_assertion()
# Set JULIA_PROJECT so that we install in the pysr environment
processed_julia_project, is_shared = _process_julia_project(julia_project)
_set_julia_project_env(processed_julia_project, is_shared)

julia.install(quiet=quiet)
Main = init_julia(julia_project, quiet=quiet)
io_arg = _get_io_arg(quiet)

if is_shared:
# Install SymbolicRegression.jl:
_add_sr_to_julia_project(Main, io_arg)

Main.eval("using Pkg")
Main.eval(f"Pkg.instantiate({io_arg})")
Main.eval(f"Pkg.precompile({io_arg})")
if not quiet:
warnings.warn(
"It is recommended to restart Python after installing PySR's dependencies,"
" so that the Julia environment is properly initialized."
)


def _import_error():
return """
Required dependencies are not installed or built. Run the following code in the Python REPL:
Expand Down Expand Up @@ -145,7 +147,13 @@ def _check_for_conflicting_libraries(): # pragma: no cover
)


def init_julia(julia_project=None, quiet=False, julia_kwargs=None):
def init_julia(
julia_project=None,
quiet=False,
use_sysimage=True,
sysimage_name=None,
julia_kwargs=None,
):
"""Initialize julia binary, turning off compiled modules if needed."""
global julia_initialized
global julia_kwargs_at_initialization
Expand All @@ -154,16 +162,35 @@ def init_julia(julia_project=None, quiet=False, julia_kwargs=None):
if not julia_initialized:
_check_for_conflicting_libraries()

_julia_version_assertion()
processed_julia_project, is_shared = _process_julia_project(julia_project)
_set_julia_project_env(processed_julia_project, is_shared)

# TODO: Make checking optional.
# Check if sysimage exists:
if use_sysimage and sysimage_name is None and not julia_initialized:
# TODO: Is there a faster way to get this dir?
expected_sysimage = _get_julia_project_dir() / "pysr.so"
# Check if this file exists:
if expected_sysimage.exists():
sysimage_name = str(expected_sysimage)

if julia_kwargs is None:
julia_kwargs = {"optimize": 3}

from julia.core import JuliaInfo, UnsupportedPythonError
if (
sysimage_name is not None
and "sysimage" not in julia_kwargs
and not julia_initialized
):
sysimage = str(sysimage_name)
print(f"Found existing sysimage at {sysimage}. Loading.")
julia_kwargs["sysimage"] = sysimage_name

_julia_version_assertion()
processed_julia_project, is_shared = _process_julia_project(julia_project)
_set_julia_project_env(processed_julia_project, is_shared)
from julia.core import JuliaInfo, UnsupportedPythonError

try:
# TODO: Can we just get env info from this?
info = JuliaInfo.load(julia="julia")
except FileNotFoundError:
env_path = os.environ["PATH"]
Expand Down Expand Up @@ -225,21 +252,6 @@ def init_julia(julia_project=None, quiet=False, julia_kwargs=None):
return Main


def _add_sr_to_julia_project(Main, io_arg):
Main.eval("using Pkg")
Main.sr_spec = Main.PackageSpec(
name="SymbolicRegression",
url="https://github.com/MilesCranmer/SymbolicRegression.jl",
rev="v" + __symbolic_regression_jl_version__,
)
Main.clustermanagers_spec = Main.PackageSpec(
name="ClusterManagers",
url="https://github.com/JuliaParallel/ClusterManagers.jl",
rev="14e7302f068794099344d5d93f71979aaf4fbeb3",
)
Main.eval(f"Pkg.add([sr_spec, clustermanagers_spec], {io_arg})")


def _escape_filename(filename):
"""Turn a path into a string with correctly escaped backslashes."""
str_repr = str(filename)
Expand Down Expand Up @@ -281,16 +293,6 @@ def _load_cluster_manager(Main, cluster_manager):
return Main.eval(f"addprocs_{cluster_manager}")


def _update_julia_project(Main, is_shared, io_arg):
try:
if is_shared:
_add_sr_to_julia_project(Main, io_arg)
Main.eval("using Pkg")
Main.eval(f"Pkg.resolve({io_arg})")
except (JuliaError, RuntimeError) as e:
raise ImportError(_import_error()) from e


def _load_backend(Main):
try:
# Load namespace, so that various internal operators work:
Expand Down
118 changes: 118 additions & 0 deletions pysr/package_compiler.py
@@ -0,0 +1,118 @@
"""Functions to create a sysimage for PySR."""

from pathlib import Path
import warnings
from multiprocessing import cpu_count

import numpy as np
from julia.api import JuliaError

from .version import __symbolic_regression_jl_version__
from .julia_helpers import (
init_julia,
_julia_version_assertion,
_set_julia_project_env,
_get_io_arg,
_process_julia_project,
_import_error,
)


def _add_sr_to_julia_project(Main, io_arg):
Main.eval("using Pkg")
Main.sr_spec = Main.PackageSpec(
name="SymbolicRegression",
url="https://github.com/MilesCranmer/SymbolicRegression.jl",
rev="v" + __symbolic_regression_jl_version__,
)
Main.clustermanagers_spec = Main.PackageSpec(
name="ClusterManagers",
rev="v0.4.2",
)
Main.packagecompiler_spec = Main.PackageSpec(
name="PackageCompiler",
rev="v2.1.0",
)
Main.pycall_spec = Main.PackageSpec(
name="PyCall",
rev="v1.94.1",
)
Main.eval(
"Pkg.add(["
+ ", ".join(
["sr_spec", "clustermanagers_spec", "packagecompiler_spec", "pycall_spec"]
)
+ f"], {io_arg})"
)
Main.eval(f'Pkg.build("PyCall", {io_arg})')


def _update_julia_project(Main, is_shared, io_arg):
try:
if is_shared:
_add_sr_to_julia_project(Main, io_arg)
Main.eval("using Pkg")
Main.eval(f"Pkg.resolve({io_arg})")
except (JuliaError, RuntimeError) as e:
raise ImportError(_import_error()) from e


def install(julia_project=None, quiet=False): # pragma: no cover
"""
Install PyCall.jl and all required dependencies for SymbolicRegression.jl.
Also updates the local Julia registry.
"""
import julia

_julia_version_assertion()
# Set JULIA_PROJECT so that we install in the pysr environment
processed_julia_project, is_shared = _process_julia_project(julia_project)
_set_julia_project_env(processed_julia_project, is_shared)

julia.install(quiet=quiet)
Main = init_julia(julia_project, quiet=quiet)
io_arg = _get_io_arg(quiet)

if is_shared:
# Install SymbolicRegression.jl:
_add_sr_to_julia_project(Main, io_arg)

Main.eval("using Pkg")
Main.eval(f"Pkg.instantiate({io_arg})")
Main.eval(f"Pkg.precompile({io_arg})")

if not quiet:
warnings.warn(
"It is recommended to restart Python after installing PySR's dependencies,"
" so that the Julia environment is properly initialized."
)


def compile(
julia_project=None,
quiet=False,
sysimage_name="pysr.so",
):
"""Create a PackageCompiler.jl sysimage for SymbolicRegression.jl."""
Main = init_julia(
julia_project=julia_project,
quiet=quiet,
use_sysimage=False,
julia_kwargs={
"compiled_modules": False,
"optimize": 3,
"threads": cpu_count(),
"compile": "all",
},
)
cur_project_dir = Main.eval("dirname(Base.active_project())")
sysimage_path = str(Path(cur_project_dir) / sysimage_name)
from julia import PackageCompiler

Main.eval("using PyCall")
Main.eval("using SymbolicRegression")

PackageCompiler.create_sysimage(
["SymbolicRegression", "PyCall"], sysimage_path=sysimage_path
)
2 changes: 1 addition & 1 deletion pysr/sr.py
Expand Up @@ -21,13 +21,13 @@
check_is_fitted,
)

from .package_compiler import _update_julia_project
from .julia_helpers import (
init_julia,
_process_julia_project,
is_julia_version_greater_eq,
_escape_filename,
_load_cluster_manager,
_update_julia_project,
_load_backend,
)
from .export_numpy import CallableEquation
Expand Down
3 changes: 2 additions & 1 deletion pysr/test/test_env.py
Expand Up @@ -5,6 +5,7 @@
from tempfile import TemporaryDirectory

from .. import julia_helpers
from .. import package_compiler


class TestJuliaProject(unittest.TestCase):
Expand All @@ -29,7 +30,7 @@ def test_custom_shared_env(self):
f'pushfirst!(DEPOT_PATH, "{julia_helpers._escape_filename(tmpdir)}")'
)
test_env_name = "@pysr_test_env"
julia_helpers.install(julia_project=test_env_name)
package_compiler.install(julia_project=test_env_name)
Main = julia_helpers.init_julia(julia_project=test_env_name)

# Try to use env:
Expand Down
4 changes: 2 additions & 2 deletions pysr/version.py
@@ -1,2 +1,2 @@
__version__ = "0.11.11"
__symbolic_regression_jl_version__ = "0.14.4"
__version__ = "0.11.12"
__symbolic_regression_jl_version__ = "0.14.5"

0 comments on commit d12a61c

Please sign in to comment.