Spaces:
Running
Running
Merge pull request #228 from MilesCranmer/optimize=3
Browse filesMake Julia startup options configurable; set optimize=3
- docs/param_groupings.yml +1 -0
- pysr/julia_helpers.py +40 -11
- pysr/sr.py +27 -19
- pysr/test/test.py +18 -0
- pysr/version.py +1 -1
docs/param_groupings.yml
CHANGED
|
@@ -82,6 +82,7 @@
|
|
| 82 |
- delete_tempfiles
|
| 83 |
- julia_project
|
| 84 |
- update
|
|
|
|
| 85 |
- Exporting the Results:
|
| 86 |
- equation_file
|
| 87 |
- output_jax_format
|
|
|
|
| 82 |
- delete_tempfiles
|
| 83 |
- julia_project
|
| 84 |
- update
|
| 85 |
+
- julia_kwargs
|
| 86 |
- Exporting the Results:
|
| 87 |
- equation_file
|
| 88 |
- output_jax_format
|
pysr/julia_helpers.py
CHANGED
|
@@ -10,6 +10,8 @@ from .version import __version__, __symbolic_regression_jl_version__
|
|
| 10 |
|
| 11 |
juliainfo = None
|
| 12 |
julia_initialized = False
|
|
|
|
|
|
|
| 13 |
|
| 14 |
|
| 15 |
def _load_juliainfo():
|
|
@@ -143,13 +145,18 @@ def _check_for_conflicting_libraries(): # pragma: no cover
|
|
| 143 |
)
|
| 144 |
|
| 145 |
|
| 146 |
-
def init_julia(julia_project=None, quiet=False):
|
| 147 |
"""Initialize julia binary, turning off compiled modules if needed."""
|
| 148 |
global julia_initialized
|
|
|
|
|
|
|
| 149 |
|
| 150 |
if not julia_initialized:
|
| 151 |
_check_for_conflicting_libraries()
|
| 152 |
|
|
|
|
|
|
|
|
|
|
| 153 |
from julia.core import JuliaInfo, UnsupportedPythonError
|
| 154 |
|
| 155 |
_julia_version_assertion()
|
|
@@ -167,21 +174,37 @@ def init_julia(julia_project=None, quiet=False):
|
|
| 167 |
if not info.is_pycall_built():
|
| 168 |
raise ImportError(_import_error())
|
| 169 |
|
| 170 |
-
|
| 171 |
-
try:
|
| 172 |
-
from julia import Main as _Main
|
| 173 |
|
| 174 |
-
|
|
|
|
| 175 |
except UnsupportedPythonError:
|
| 176 |
# Static python binary, so we turn off pre-compiled modules.
|
| 177 |
-
|
|
|
|
| 178 |
|
| 179 |
-
|
| 180 |
-
from julia import Main as _Main
|
| 181 |
|
| 182 |
-
|
| 183 |
|
| 184 |
-
if
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
Main.eval("using Pkg")
|
| 186 |
|
| 187 |
io_arg = _get_io_arg(quiet)
|
|
@@ -193,6 +216,11 @@ def init_julia(julia_project=None, quiet=False):
|
|
| 193 |
f"{io_arg})"
|
| 194 |
)
|
| 195 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
julia_initialized = True
|
| 197 |
return Main
|
| 198 |
|
|
@@ -234,7 +262,7 @@ def _backend_version_assertion(Main):
|
|
| 234 |
if backend_version != expected_backend_version: # pragma: no cover
|
| 235 |
warnings.warn(
|
| 236 |
f"PySR backend (SymbolicRegression.jl) version {backend_version} "
|
| 237 |
-
"does not match expected version {expected_backend_version}. "
|
| 238 |
"Things may break. "
|
| 239 |
"Please update your PySR installation with "
|
| 240 |
"`python -c 'import pysr; pysr.install()'`."
|
|
@@ -257,6 +285,7 @@ def _update_julia_project(Main, is_shared, io_arg):
|
|
| 257 |
try:
|
| 258 |
if is_shared:
|
| 259 |
_add_sr_to_julia_project(Main, io_arg)
|
|
|
|
| 260 |
Main.eval(f"Pkg.resolve({io_arg})")
|
| 261 |
except (JuliaError, RuntimeError) as e:
|
| 262 |
raise ImportError(_import_error()) from e
|
|
|
|
| 10 |
|
| 11 |
juliainfo = None
|
| 12 |
julia_initialized = False
|
| 13 |
+
julia_kwargs_at_initialization = None
|
| 14 |
+
julia_activated_env = None
|
| 15 |
|
| 16 |
|
| 17 |
def _load_juliainfo():
|
|
|
|
| 145 |
)
|
| 146 |
|
| 147 |
|
| 148 |
+
def init_julia(julia_project=None, quiet=False, julia_kwargs=None):
|
| 149 |
"""Initialize julia binary, turning off compiled modules if needed."""
|
| 150 |
global julia_initialized
|
| 151 |
+
global julia_kwargs_at_initialization
|
| 152 |
+
global julia_activated_env
|
| 153 |
|
| 154 |
if not julia_initialized:
|
| 155 |
_check_for_conflicting_libraries()
|
| 156 |
|
| 157 |
+
if julia_kwargs is None:
|
| 158 |
+
julia_kwargs = {"optimize": 3}
|
| 159 |
+
|
| 160 |
from julia.core import JuliaInfo, UnsupportedPythonError
|
| 161 |
|
| 162 |
_julia_version_assertion()
|
|
|
|
| 174 |
if not info.is_pycall_built():
|
| 175 |
raise ImportError(_import_error())
|
| 176 |
|
| 177 |
+
from julia.core import Julia
|
|
|
|
|
|
|
| 178 |
|
| 179 |
+
try:
|
| 180 |
+
Julia(**julia_kwargs)
|
| 181 |
except UnsupportedPythonError:
|
| 182 |
# Static python binary, so we turn off pre-compiled modules.
|
| 183 |
+
julia_kwargs = {**julia_kwargs, "compiled_modules": False}
|
| 184 |
+
Julia(**julia_kwargs)
|
| 185 |
|
| 186 |
+
from julia import Main as _Main
|
|
|
|
| 187 |
|
| 188 |
+
Main = _Main
|
| 189 |
|
| 190 |
+
if julia_activated_env is None:
|
| 191 |
+
julia_activated_env = processed_julia_project
|
| 192 |
+
|
| 193 |
+
if julia_initialized and julia_kwargs_at_initialization is not None:
|
| 194 |
+
# Check if the kwargs are the same as the previous initialization
|
| 195 |
+
init_set = set(julia_kwargs_at_initialization.items())
|
| 196 |
+
new_set = set(julia_kwargs.items())
|
| 197 |
+
set_diff = new_set - init_set
|
| 198 |
+
# Remove the `compiled_modules` key, since it is not a user-specified kwarg:
|
| 199 |
+
set_diff = {k: v for k, v in set_diff if k != "compiled_modules"}
|
| 200 |
+
if len(set_diff) > 0:
|
| 201 |
+
warnings.warn(
|
| 202 |
+
"Julia has already started. The new Julia options "
|
| 203 |
+
+ str(set_diff)
|
| 204 |
+
+ " will be ignored."
|
| 205 |
+
)
|
| 206 |
+
|
| 207 |
+
if julia_initialized and julia_activated_env != processed_julia_project:
|
| 208 |
Main.eval("using Pkg")
|
| 209 |
|
| 210 |
io_arg = _get_io_arg(quiet)
|
|
|
|
| 216 |
f"{io_arg})"
|
| 217 |
)
|
| 218 |
|
| 219 |
+
julia_activated_env = processed_julia_project
|
| 220 |
+
|
| 221 |
+
if not julia_initialized:
|
| 222 |
+
julia_kwargs_at_initialization = julia_kwargs
|
| 223 |
+
|
| 224 |
julia_initialized = True
|
| 225 |
return Main
|
| 226 |
|
|
|
|
| 262 |
if backend_version != expected_backend_version: # pragma: no cover
|
| 263 |
warnings.warn(
|
| 264 |
f"PySR backend (SymbolicRegression.jl) version {backend_version} "
|
| 265 |
+
f"does not match expected version {expected_backend_version}. "
|
| 266 |
"Things may break. "
|
| 267 |
"Please update your PySR installation with "
|
| 268 |
"`python -c 'import pysr; pysr.install()'`."
|
|
|
|
| 285 |
try:
|
| 286 |
if is_shared:
|
| 287 |
_add_sr_to_julia_project(Main, io_arg)
|
| 288 |
+
Main.eval("using Pkg")
|
| 289 |
Main.eval(f"Pkg.resolve({io_arg})")
|
| 290 |
except (JuliaError, RuntimeError) as e:
|
| 291 |
raise ImportError(_import_error()) from e
|
pysr/sr.py
CHANGED
|
@@ -581,10 +581,15 @@ class PySRRegressor(MultiOutputMixin, RegressorMixin, BaseEstimator):
|
|
| 581 |
inputting to PySR. Can help PySR fit noisy data.
|
| 582 |
Default is `False`.
|
| 583 |
select_k_features : int
|
| 584 |
-
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 588 |
**kwargs : dict
|
| 589 |
Supports deprecated keyword arguments. Other arguments will
|
| 590 |
result in an error.
|
|
@@ -733,6 +738,7 @@ class PySRRegressor(MultiOutputMixin, RegressorMixin, BaseEstimator):
|
|
| 733 |
extra_jax_mappings=None,
|
| 734 |
denoise=False,
|
| 735 |
select_k_features=None,
|
|
|
|
| 736 |
**kwargs,
|
| 737 |
):
|
| 738 |
|
|
@@ -827,6 +833,7 @@ class PySRRegressor(MultiOutputMixin, RegressorMixin, BaseEstimator):
|
|
| 827 |
# Pre-modelling transformation
|
| 828 |
self.denoise = denoise
|
| 829 |
self.select_k_features = select_k_features
|
|
|
|
| 830 |
|
| 831 |
# Once all valid parameters have been assigned handle the
|
| 832 |
# deprecated kwargs
|
|
@@ -1259,6 +1266,17 @@ class PySRRegressor(MultiOutputMixin, RegressorMixin, BaseEstimator):
|
|
| 1259 |
+ len(packed_modified_params["unary_operators"])
|
| 1260 |
> 0
|
| 1261 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1262 |
return packed_modified_params
|
| 1263 |
|
| 1264 |
def _validate_and_set_fit_params(self, X, y, Xresampled, weights, variable_names):
|
|
@@ -1469,31 +1487,21 @@ class PySRRegressor(MultiOutputMixin, RegressorMixin, BaseEstimator):
|
|
| 1469 |
batch_size = mutated_params["batch_size"]
|
| 1470 |
update_verbosity = mutated_params["update_verbosity"]
|
| 1471 |
progress = mutated_params["progress"]
|
|
|
|
| 1472 |
|
| 1473 |
# Start julia backend processes
|
| 1474 |
-
|
| 1475 |
-
if multithreading:
|
| 1476 |
-
os.environ["JULIA_NUM_THREADS"] = str(self.procs)
|
| 1477 |
-
|
| 1478 |
-
Main = init_julia(self.julia_project)
|
| 1479 |
|
| 1480 |
if cluster_manager is not None:
|
| 1481 |
cluster_manager = _load_cluster_manager(cluster_manager)
|
| 1482 |
|
| 1483 |
-
if
|
| 1484 |
-
|
| 1485 |
-
Main.eval("using Pkg")
|
| 1486 |
io = "devnull" if update_verbosity == 0 else "stderr"
|
| 1487 |
io_arg = (
|
| 1488 |
f"io={io}" if is_julia_version_greater_eq(version=(1, 6, 0)) else ""
|
| 1489 |
)
|
| 1490 |
-
|
| 1491 |
-
Main.eval(
|
| 1492 |
-
f'Pkg.activate("{_escape_filename(julia_project)}", shared = Bool({int(is_shared)}), {io_arg})'
|
| 1493 |
-
)
|
| 1494 |
-
|
| 1495 |
-
if self.update:
|
| 1496 |
-
_update_julia_project(Main, is_shared, io_arg)
|
| 1497 |
|
| 1498 |
SymbolicRegression = _load_backend(Main)
|
| 1499 |
|
|
|
|
| 581 |
inputting to PySR. Can help PySR fit noisy data.
|
| 582 |
Default is `False`.
|
| 583 |
select_k_features : int
|
| 584 |
+
Whether to run feature selection in Python using random forests,
|
| 585 |
+
before passing to the symbolic regression code. None means no
|
| 586 |
+
feature selection; an int means select that many features.
|
| 587 |
+
Default is `None`.
|
| 588 |
+
julia_kwargs : dict
|
| 589 |
+
Keyword arguments to pass to `julia.core.Julia(...)` to initialize
|
| 590 |
+
the Julia runtime. The default, when `None`, is to set `threads` equal
|
| 591 |
+
to `procs`, and `optimize` to 3.
|
| 592 |
+
Default is `None`.
|
| 593 |
**kwargs : dict
|
| 594 |
Supports deprecated keyword arguments. Other arguments will
|
| 595 |
result in an error.
|
|
|
|
| 738 |
extra_jax_mappings=None,
|
| 739 |
denoise=False,
|
| 740 |
select_k_features=None,
|
| 741 |
+
julia_kwargs=None,
|
| 742 |
**kwargs,
|
| 743 |
):
|
| 744 |
|
|
|
|
| 833 |
# Pre-modelling transformation
|
| 834 |
self.denoise = denoise
|
| 835 |
self.select_k_features = select_k_features
|
| 836 |
+
self.julia_kwargs = julia_kwargs
|
| 837 |
|
| 838 |
# Once all valid parameters have been assigned handle the
|
| 839 |
# deprecated kwargs
|
|
|
|
| 1266 |
+ len(packed_modified_params["unary_operators"])
|
| 1267 |
> 0
|
| 1268 |
)
|
| 1269 |
+
|
| 1270 |
+
julia_kwargs = {}
|
| 1271 |
+
if self.julia_kwargs is not None:
|
| 1272 |
+
for key, value in self.julia_kwargs.items():
|
| 1273 |
+
julia_kwargs[key] = value
|
| 1274 |
+
if "optimize" not in julia_kwargs:
|
| 1275 |
+
julia_kwargs["optimize"] = 3
|
| 1276 |
+
if "threads" not in julia_kwargs and packed_modified_params["multithreading"]:
|
| 1277 |
+
julia_kwargs["threads"] = self.procs
|
| 1278 |
+
packed_modified_params["julia_kwargs"] = julia_kwargs
|
| 1279 |
+
|
| 1280 |
return packed_modified_params
|
| 1281 |
|
| 1282 |
def _validate_and_set_fit_params(self, X, y, Xresampled, weights, variable_names):
|
|
|
|
| 1487 |
batch_size = mutated_params["batch_size"]
|
| 1488 |
update_verbosity = mutated_params["update_verbosity"]
|
| 1489 |
progress = mutated_params["progress"]
|
| 1490 |
+
julia_kwargs = mutated_params["julia_kwargs"]
|
| 1491 |
|
| 1492 |
# Start julia backend processes
|
| 1493 |
+
Main = init_julia(self.julia_project, julia_kwargs=julia_kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1494 |
|
| 1495 |
if cluster_manager is not None:
|
| 1496 |
cluster_manager = _load_cluster_manager(cluster_manager)
|
| 1497 |
|
| 1498 |
+
if self.update:
|
| 1499 |
+
_, is_shared = _process_julia_project(self.julia_project)
|
|
|
|
| 1500 |
io = "devnull" if update_verbosity == 0 else "stderr"
|
| 1501 |
io_arg = (
|
| 1502 |
f"io={io}" if is_julia_version_greater_eq(version=(1, 6, 0)) else ""
|
| 1503 |
)
|
| 1504 |
+
_update_julia_project(Main, is_shared, io_arg)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1505 |
|
| 1506 |
SymbolicRegression = _load_backend(Main)
|
| 1507 |
|
pysr/test/test.py
CHANGED
|
@@ -12,6 +12,7 @@ import pickle as pkl
|
|
| 12 |
import tempfile
|
| 13 |
from pathlib import Path
|
| 14 |
|
|
|
|
| 15 |
from .. import PySRRegressor
|
| 16 |
from ..sr import (
|
| 17 |
run_feature_selection,
|
|
@@ -566,6 +567,23 @@ class TestMiscellaneous(unittest.TestCase):
|
|
| 566 |
with self.assertRaises(ValueError):
|
| 567 |
model.fit(X, y)
|
| 568 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 569 |
def test_extra_sympy_mappings_undefined(self):
|
| 570 |
"""extra_sympy_mappings=None errors for custom operators"""
|
| 571 |
model = PySRRegressor(unary_operators=["square2(x) = x^2"])
|
|
|
|
| 12 |
import tempfile
|
| 13 |
from pathlib import Path
|
| 14 |
|
| 15 |
+
from .. import julia_helpers
|
| 16 |
from .. import PySRRegressor
|
| 17 |
from ..sr import (
|
| 18 |
run_feature_selection,
|
|
|
|
| 567 |
with self.assertRaises(ValueError):
|
| 568 |
model.fit(X, y)
|
| 569 |
|
| 570 |
+
def test_changed_options_warning(self):
|
| 571 |
+
"""Check that a warning is given if Julia options are changed."""
|
| 572 |
+
if julia_helpers.julia_kwargs_at_initialization is None:
|
| 573 |
+
julia_helpers.init_julia(julia_kwargs={"threads": 2, "optimize": 3})
|
| 574 |
+
|
| 575 |
+
cur_init = julia_helpers.julia_kwargs_at_initialization
|
| 576 |
+
|
| 577 |
+
threads_to_change = cur_init["threads"] + 1
|
| 578 |
+
with warnings.catch_warnings():
|
| 579 |
+
warnings.simplefilter("error")
|
| 580 |
+
with self.assertRaises(Exception) as context:
|
| 581 |
+
julia_helpers.init_julia(
|
| 582 |
+
julia_kwargs={"threads": threads_to_change, "optimize": 3}
|
| 583 |
+
)
|
| 584 |
+
self.assertIn("Julia has already started", str(context.exception))
|
| 585 |
+
self.assertIn("threads", str(context.exception))
|
| 586 |
+
|
| 587 |
def test_extra_sympy_mappings_undefined(self):
|
| 588 |
"""extra_sympy_mappings=None errors for custom operators"""
|
| 589 |
model = PySRRegressor(unary_operators=["square2(x) = x^2"])
|
pysr/version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
| 1 |
-
__version__ = "0.11.
|
| 2 |
__symbolic_regression_jl_version__ = "0.14.4"
|
|
|
|
| 1 |
+
__version__ = "0.11.11"
|
| 2 |
__symbolic_regression_jl_version__ = "0.14.4"
|