Skip to content

Migrating from v1 to v2 ​

PySR v2 upgrades the Julia backend from SymbolicRegression.jl v1.11 to v2. Most PySRRegressor constructor calls still work, but several search defaults changed. The old parametric-expression interface and v1 checkpoints do not carry over.

Before upgrading, keep a copy of each run directory, including hall_of_fame.csv. Install v2 in a new Python environment if you need to keep a v1 environment available for old pickles or exact search reproduction.

Search defaults changed ​

The following changes affect ordinary PySRRegressor(...) searches:

Settingv1 defaultv2 default
batchingFalse"auto"
batch_size50None
annealingFalseTrue
crossover_probability0.02590.2
Mutation selectionFixed v1 weightsNew backend weights, adapted during the search

With batching="auto", the backend enables batching for large datasets while still evaluating hall-of-fame candidates on the full dataset. With batch_size=None, it uses the full dataset for at most 1000 rows, then batches of 128, 256, or 512 as the dataset grows.

The default mutation mix changed, including a new feature mutation that can replace the input referenced by a variable node. The AdaptiveMutationWeightsPlugin also updates mutation weights from observed success rates. A weight_* argument now supplies an initial weight, rather than a fixed weight, while that plugin is active.

For a search close to the v1 defaults, set the changed values explicitly and omit the adaptive-mutation plugin:

python
from pysr import AdaptiveParsimonyPlugin, PySRRegressor

model = PySRRegressor(
    batching=False,
    batch_size=50,
    annealing=False,
    crossover_probability=0.0259,
    default_plugins=[AdaptiveParsimonyPlugin()],
    weight_add_node=2.47,
    weight_insert_node=0.0112,
    weight_delete_node=0.87,
    weight_do_nothing=0.273,
    weight_mutate_constant=0.0346,
    weight_mutate_operator=0.293,
    weight_mutate_feature=0.0,
    weight_swap_operands=0.198,
    weight_rotate_tree=4.26,
    weight_randomize=0.000502,
    weight_simplify=0.00209,
    weight_optimize=0.0,
    weight_backsolve=0.0,
)

This preserves the v1 search configuration, but it does not make a run bit-for-bit identical. Backend implementation changes still alter random-number consumption and search trajectories.

Two other backend changes can alter results:

  • Simplification now recomputes an expression's cost from scratch. Equivalent expressions can therefore appear in a different hall-of-fame order.
  • Constant optimization can restart zero-valued constants, allowing trajectories that v1 could not reach.

Loss functions are now validated when fit is called. An elementwise_loss or loss_function with the wrong number of arguments raises a ValueError before the search starts.

Parametric expressions ​

ParametricExpressionSpec and the category= argument to fit were removed. Use TemplateExpressionSpec.parameters, append the category to X, and index parameters from the template.

A v1 model such as:

python
from pysr import ParametricExpressionSpec, PySRRegressor

model = PySRRegressor(
    expression_spec=ParametricExpressionSpec(max_parameters=2),
)
model.fit(X, y, category=category)

becomes:

python
import numpy as np
from pysr import PySRRegressor, TemplateExpressionSpec

n_categories = len(np.unique(category))
spec = TemplateExpressionSpec(
    combine="f(x1, x2, p1[category], p2[category])",
    expressions=["f"],
    variable_names=["x1", "x2", "category"],
    parameters={"p1": n_categories, "p2": n_categories},
)

# Julia indexing starts at 1. Add one if Python categories start at 0.
X_with_category = np.column_stack([X, category + 1])
model = PySRRegressor(expression_spec=spec)
model.fit(X_with_category, y)

The category column must also be present when calling predict.

Template expressions ​

The deprecated function_symbols=... and positional constructor forms of TemplateExpressionSpec were replaced by explicit template fields. State the combining expression, inner expressions, and variable names directly:

python
spec = TemplateExpressionSpec(
    combine="sin(f(x1, x2)) + g(x3)^2",
    expressions=["f", "g"],
    variable_names=["x1", "x2", "x3"],
)

This form also supports optimizable parameter vectors through parameters={"p": length}. See Template expressions for complete examples.

Saved models and checkpoints ​

v2 uses checkpoint schema 3. A v1 pickle has no compatible schema marker, so pickle.load(...) and PySRRegressor.from_file(...) reject it with an Unsupported PySR checkpoint schema error. Warm starts cannot cross the v1 to v2 boundary. Re-run the search under v2 if you need to continue evolution.

For standard expressions, you can reconstruct the discovered equations from a copy of hall_of_fame.csv. Put the CSV in a run directory without the incompatible checkpoint.pkl, then provide the original operators and feature metadata:

python
model = PySRRegressor.from_file(
    run_directory="v1_csv_copy",
    binary_operators=["+", "-", "*", "/"],
    unary_operators=["sin", "cos"],
    n_features_in=3,
    feature_names_in=["x0", "x1", "x2"],
)

Custom expression specifications may require a fresh search because their Julia objects cannot be reconstructed from the CSV alone.

v1 spellings that still work ​

These forms emit a deprecation warning but retain their old behavior:

  • camelCase keyword arguments such as fractionReplaced, npop, weightMutateConstant, and crossoverProbability; use snake_case instead.
  • loss= and full_objective=; use elementwise_loss= and loss_function=.
  • multithreading=True or False; use parallelism="multithreading" or parallelism="serial". Use parallelism="multiprocessing", procs=N for process workers.
  • .equations and .raw_julia_state_; use .equations_ and .julia_state_.
  • The pysr() function, pysr.install(), and pysr.init_julia(); use PySRRegressor. Dependencies are installed when PySR is imported.

These constructor arguments are accepted but ignored with a warning:

  • julia_project and julia_kwargs. Configure the backend through juliapkg; see Customizing the backend.
  • weights, variable_names, and Xresampled. Pass them to fit instead.

New APIs in v2 ​

  • N-ary operators: use operators={1: ["sin"], 2: ["+", "*"], 3: ["clamp"]}. The legacy binary_operators and unary_operators arguments still work, but cannot be combined with operators. Constraints for n-ary operators use n-tuples, such as {"clamp": (-1, -1, 1)}.
  • First-class mutations, crossovers, and plugins: configure weighted instances with mutations= and crossovers=, and compose search behavior with plugins=. See the API reference.
  • Custom value types: TypeSpec supports expressions over strings, vectors, tensors, structs, and other Julia value types. See Custom value types.
  • Template parameters: TemplateExpressionSpec.parameters supports learned scalar or category-indexed parameter vectors.
  • Equation guesses: seed a search with guesses=["sin(x1 * 2.1)", ...]. fraction_replaced_guesses controls their injection rate.
  • Autodiff backends: autodiff_backend="Enzyme" is available as an experimental alternative to "Zygote".
  • Worker controls: worker_imports loads Julia packages on multiprocessing workers, and worker_timeout controls worker restart timeouts.
  • Additional mutations: weight_mutate_feature controls feature mutation. Backsolve is disabled by default and can be configured with BacksolveMutation(...) through mutations=.

Environment changes ​

  • The minimum versions are Python 3.10 and Julia 1.10.