MilesCranmer commited on
Commit
da0bef9
2 Parent(s): 6b46e9f e7650cd

Merge pull request #228 from MilesCranmer/optimize=3

Browse files

Make Julia startup options configurable; set optimize=3

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
- Main = None
171
- try:
172
- from julia import Main as _Main
173
 
174
- Main = _Main
 
175
  except UnsupportedPythonError:
176
  # Static python binary, so we turn off pre-compiled modules.
177
- from julia.core import Julia
 
178
 
179
- jl = Julia(compiled_modules=False)
180
- from julia import Main as _Main
181
 
182
- Main = _Main
183
 
184
- if julia_initialized:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- 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
  **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
- if Main is None:
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 not already_ran:
1484
- julia_project, is_shared = _process_julia_project(self.julia_project)
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.10"
2
  __symbolic_regression_jl_version__ = "0.14.4"
 
1
+ __version__ = "0.11.11"
2
  __symbolic_regression_jl_version__ = "0.14.4"