2.0.0 (2026-08-25)
SymbolicRegression.jl 2.0.0 is the conclusion of a two-year transformation of the library from a fixed search pipeline into a modular PyTorch-like framework for symbolic learning, while still keeping its familiar v1 functionality and API. Operators can take any number of arguments, the search loop accepts plugins, and mutations and crossovers are first-class objects you can configure or replace. This also introduces the ability to pass "guesses" for the expressions, which mix into the populations throughout a run - which helps connect SR.jl to agentic coding loops. Finally, we get some major speed boosts from a huge reduction in allocations thanks to a reusable evaluation buffer.
Version: release 2.0.0, tag v2.0.0.
Highlights
Operators of any arity
Pass an OperatorEnum keyed by arity, and ternary or higher-degree operators become real tree nodes (#471, #472, #464). Before v2 the enum had exactly a unary and a binary slot, so piecewise logic such as a > 0 ? b : c had to be approximated by nests of binary operators that the search rarely found.
using SymbolicRegression
using SymbolicRegression: machine, fit!, predict, report
scalar_ifelse(a, b, c) = a > 0 ? b : c
X = randn(3, 100)
y = [X[1, i] > 0 ? 2*X[2, i] : X[3, i] for i in 1:100]
model = SRRegressor(
operators=OperatorEnum(
1 => (),
2 => (+, -, *, /),
3 => (scalar_ifelse,),
),
niterations=35,
)
mach = machine(model, X', y)
fit!(mach)
report(mach)binary_operators and unary_operators still work, and they remain mutually exclusive with operators=. Constraints are now degree-indexed: an N-argument operator takes an N-tuple, with -1 meaning unconstrained, and unary operators default to -1 as before. Operator and connection mutations, append/delete/rotate operations, crossover, simplification, constraint checking, and dimensional analysis operate over the generalized tree, whose node type became Node{T,D} in DynamicExpressions (#127). The lower-level swap_operands helper accepts nodes of any degree greater than one, while SwapOperandsMutation remains enabled only when the tree contains a binary node.
A composable plugin interface
Plugins are the new extension point for the search loop (#645, #663). A plugin is a small struct subtyping AbstractPlugin: an immutable configuration paired with mutable runtime state created by init_plugin_state. Hooks cover lifecycle events (on_search_start!, on_generation_end!, on_cycle_start!/on_cycle_end!, on_mutation_end!, on_search_end!), selection and acceptance biases (tournament_cost_multiplier, mutation_acceptance_multiplier), mutation conditioning (condition_mutation!), and population seeding (init_member). Plugins can also contribute weighted mutation and crossover defaults through plugin_mutations and plugin_crossovers, which explicit mutations=/crossovers= entries override. Multiple plugins compose in tuple order, and extending the search no longer means forking the package.
using SymbolicRegression
using SymbolicRegression: AbstractPlugin, AbstractMutation, MutationEvent
struct MutationCounterPlugin <: AbstractPlugin end
mutable struct MutationCounterState
accepted::Int
rejected::Int
end
function SymbolicRegression.init_plugin_state(::MutationCounterPlugin, options, dataset)
return MutationCounterState(0, 0)
end
function SymbolicRegression.on_mutation_end!(
state::MutationCounterState, ::MutationCounterPlugin, ::AbstractMutation,
event::MutationEvent, dataset, options,
)
event.accepted ? (state.accepted += 1) : (state.rejected += 1)
return nothing
end
model = SRRegressor(;
binary_operators=[+, -, *, /],
plugins=(MutationCounterPlugin(),),
)The default set contains SimulatedAnnealingPlugin, AdaptiveParsimonyPlugin, and AdaptiveMutationWeightsPlugin. Annealing carries the previous temperature schedule; adaptive parsimony biases selection away from over-represented complexities; adaptive mutation weights learn multiplicative factors from observed improvements (see Changed defaults). MutationBurstPlugin is opt-in. Pass plugins=(...) to add or replace by type, and default_plugins=() to run the core loop without automatic plugins. The interface is experimental and may change in future releases.
First-class mutations and crossovers
Mutations are now types passed with weights, and each built-in carries its own hyperparameters (#610). An entry replaces the default weight for that mutation type; anything you leave out keeps its default, and default_mutations=() removes every automatic entry.
model = SRRegressor(
binary_operators=[+, -, *],
mutations=[
OptimizeMutation() => 0.1,
ConstantMutation(perturbation_factor=0.1) => 0.5,
],
)The fifteen built-ins are ConstantMutation, OperatorMutation, FeatureMutation, SwapOperandsMutation, RotateTreeMutation, AddNodeMutation, InsertNodeMutation, DeleteNodeMutation, SimplifyMutation, RandomizeMutation, OptimizeMutation, BacksolveMutation, DoNothingMutation, and the graph-only pair FormConnectionMutation and BreakConnectionMutation. FeatureMutation makes rewiring a leaf to a different input column its own weighted move (#475), which previously happened only as a side effect of delete-then-add.
Writing your own follows normal Julia dispatch. Subtype AbstractMutation, extend SymbolicRegression.mutate!, and return a MutationResult:
using SymbolicRegression
using SymbolicRegression: AbstractMutation, MutationResult
using DynamicExpressions: get_contents, AbstractExpression
struct PruneMutation <: AbstractMutation end
function SymbolicRegression.mutate!(
new_tree::N, parent_member::P, ::PruneMutation, options; nfeatures, kws...
) where {N<:AbstractExpression,P}
tree = get_contents(new_tree)
nodes = filter(n -> n.degree > 0, collect(tree))
if !isempty(nodes)
target = rand(nodes)
target.degree = 0
target.feature = rand(1:nfeatures)
end
return MutationResult{N,P}(; tree=new_tree)
end
model = SRRegressor(
binary_operators=[+, -, *, /],
mutations=[PruneMutation() => 0.1],
)Crossovers mirror the design (#664, #666): subtype AbstractCrossover, extend SymbolicRegression.crossover, return a CrossoverResult with two children, and pass it as crossovers=[MyCrossover() => weight]. The default SubtreeCrossover stays in place unless you remove it with default_crossovers=(). When children violate constraints the engine retries the sampled crossover and passes a 1-based attempt keyword, so a crossover backed by an expensive external model can return copies of the parents on retries.
The experimental BacksolveMutation
BacksolveMutation (#573, thanks @ayagh19) targets failures random perturbation handles poorly, such as a correct outer form around a wrong inner argument. It walks up the tree, inverting each operator on the path to the root to compute what a subtree should have produced, then fits a sparse replacement by greedy forward selection over a library of the best subtrees currently in the population, constrained by the remaining complexity budget. It is off by default (BacksolveMutation() => 0.0) and flagged experimental.
Seed the search with guesses
Give equation_search any guess for the final expressions, and it mixes those guesses into the populations throughout the search (#469, #500). Each guess is parsed with your configured operators. When should_optimize_constants=true, its constants are optimized before it joins a population. fraction_replaced_guesses controls the fraction mixed in at the end of each cycle, so guesses continue contributing throughout the run even when their initial constants are inaccurate.
using SymbolicRegression
using SymbolicRegression: machine, fit!, report
X = randn(Float32, 6, 2048)
y = @. sin(X[1, :] * X[2, :] + 0.1f0) + cos(X[3, :]) * X[4, :] +
X[5, :] / (X[6, :] * X[6, :] + 1)
model = SRRegressor(
binary_operators=[+, -, *, /],
unary_operators=[sin, cos],
guesses=["sin(x1 * x2) + cos(x3) * x4 + x5 / (x6 * x6 + 0.9)"],
niterations=35,
)
mach = machine(model, X', y)
fit!(mach)For multi-output searches pass a vector of vectors, one inner vector per output. With TemplateExpressionSpec, guesses are named tuples keyed by sub-expression name, using #1, #2 as placeholders for the arguments, such as (; f="cos(#1) + 0.1", g="sin(#2) - 0.9"). Guess constants evaluate inside a generated module, so guesses naming custom operators resolve correctly (#705), and overly complex guesses produce a warning rather than silently dominating a population.
A reusable evaluation arena
Evaluation buffers are allocated once in a contiguous arena and reused across mutation, crossover, loss evaluation, constant optimization, and template inner calls (#654, #668; DynamicExpressions #180, #186). On SymbolicRegression.jl's own 8-thread benchmark suite, as reported in those pull requests, a full search dropped from 9.541 s to 5.880 s median and allocated bytes fell from 59.10 GB to 10.71 GB, with the hall of fame byte-identical across the change. Treat these as the backend's measured workload, not a universal multiplier.
Ordinary users configure nothing. At the library level, EvalOptions became EvalContext with caller-owned arena lifetimes (DynamicExpressions #187, #192); the old binding remains as a deprecated alias, and evaluation entry points now reject unknown keywords instead of ignoring them (#670). Separately, precompilation uses single-output searches for Float32 and, by default, Float64. Set the precompile_float64 preference to false to omit the Float64 workload (#642).
Automatic batching and adaptive mutation defaults
With batching=:auto and batch_size=nothing now the defaults, searches over large datasets minibatch without configuration (#676). Datasets of 1000 rows or fewer use full data; above that the chosen batch size is 128 rows below 5000, 256 below 50000, and 512 otherwise, capped by an explicit batch_size. Minibatches guide the inner evolution, while hall-of-fame members are reevaluated on the full dataset before they are returned. Restore full-data evolution with batching=false, batch_size=50.
Adaptive mutation weights are on by default (#678): AdaptiveMutationWeightsPlugin tracks attempts and strict improvements per population and applies learned multiplicative factors, regularized toward your configured weights in log space. The enabling pull request's continuous-benchmark runs showed multithreaded runtime of 12.9 s without adaptation versus 13.1 s with it, an aggregate score improvement of 0.0143, so the mechanism operates at roughly parity overhead on that workload. Disable it alone with plugins=(AdaptiveMutationWeightsPlugin(adaptation_strength=0),) or drop all automatic plugins with default_plugins=().
The standard workflow without MLJ
machine, fit!, predict, and report work whenever a Tables-compatible input is supplied, through a new SymbolicRegressionTablesExt, so fitting a regressor no longer loads MLJ or MLJBase (#680). The extension handles table detection, column names, matrix conversion, and result materialization.
using SymbolicRegression
using SymbolicRegression: machine, fit!, predict, report
X = 2randn(1000, 5)
y = @. 2*cos(X[:, 4]) + X[:, 1]^2 - 2
model = SRRegressor(
binary_operators=[+, -, *, /],
unary_operators=[cos],
niterations=30,
)
mach = machine(model, X, y)
fit!(mach)
r = report(mach)
println(r.equations[r.best_idx])
yhat = predict(mach, randn(10, 5))Existing MLJ users keep the same surface: the models remain MLJ-compatible, and the core still uses MLJModelInterface.
Expansions for custom value types
Searching over nonnumeric values is not new: the string interface arrived in v1.10 and v1.12 already shipped the generic value interface, with GenericOperatorEnum, init_value, sample_value, mutate_value, scalar-constant counting, and custom printing, as shown in examples/custom_types.jl. Likewise, D(f, i) from DynamicDiff inside @template_spec predates this release. Version 2 expands these interfaces:
- Operators of arbitrary arity work with
GenericOperatorEnum, so custom structs can flow through ternary and higher-degree nodes. - Template expressions accept custom value types in their bodies (#690) and as parameter vectors (#693); parameter vectors themselves became generic and optimizable alongside the expression constants in #644, generalizing what
TemplateStructure(; num_parameters=...)already offered in v1. - Discrete custom-value mutation works again: a regression broke
mutate_valuemoves for noncontinuous types, fixed in #687. - DynamicDiff compatibility moved to 0.3, whose derivative operator supports expressions containing n-ary operator nodes (DynamicDiff #4).
Changed defaults
Search dynamics differ from v1.13 even when your code runs unchanged.
| setting | v1.13 | v2.0 | note |
|---|---|---|---|
batching |
false |
:auto |
engaged above 1000 rows; hall-of-fame members are reevaluated on the full dataset before return (#676) |
batch_size |
50 |
nothing |
full data up to 1000 rows, then 128 / 256 / 512 by size (#676) |
crossover_probability |
0.0259 |
0.20 |
about eight times more recombination; chosen by a 560-search factorial ablation showing +2.24% aggregate held-out Pareto NMSE and a 420-search sweep in which 0.20 was the only setting that helped, both reported in #643 |
| adaptive mutation weights | off | on | AdaptiveMutationWeightsPlugin in the default set (#678) |
Constant optimization can now escape zero-valued constants (#637), which changes optimization trajectories as well. To recover static v1-style weights, choose the plugin set explicitly:
Options(;
default_plugins=(SimulatedAnnealingPlugin(; alpha=3.17), AdaptiveParsimonyPlugin()),
batching=false,
batch_size=50,
crossover_probability=0.0259,
)Migration notes
Most renamed keywords continue to work through warning shims. The remaining migration points are below.
Renames with working, warning shims:
eval_options=becomeseval_context=in evaluation entry points;EvalOptionsremains as a deprecated alias forEvalContext.use_recorder/recorder_filebecomeuse_tracing/tracing_file, writing versioned JSONL records (#651).PopMember.scorebecomesPopMember.cost.- camelCase keywords such as
mutationWeights,useFrequency, andshouldOptimizeConstantsconvert automatically to snake_case.
Removals and signature changes:
ParametricExpression,ParametricNode, andParametricExpressionSpecare gone. Parameterized template expressions cover the same ground generically:@template_spec(expressions=(f,), parameters=(p=2,))declares a parameter vector optimized with the constants (#656, #644).Options,SearchState, andTemplateExpressionSpecgained type parameters, changing their concrete type arity, andSearchStatereplacesall_running_search_statisticswithplugin_states.Node{T}is nowNode{T,D}with the maximum arity as the second parameter (DynamicExpressions #127).- The internal
delete_random_op!helper gained an n-ary signature._random_opwas removed; custom mutations should useappend_random_op,insert_random_op, orprepend_random_opas appropriate.
Other changes
- Tracing is centralized:
Options(; use_tracing=true, tracing_file="run.jsonl")writes a versioned JSONL record stream covering population members, mutation events, crossover details, costs, and parent references (#651). Disabled tracing is designed to be zero-allocation, and memory scales with in-flight records rather than the whole search history. - Reliability fixes: mismatched
X/ysample counts error immediately with a clear message (#660); multiprocessing teardown no longer hangs (#641); members violating constraints are never stored;poisson_samplehandleslambda=0; gradient evaluation works withSubArrayinputs (#566); stdin quit monitoring is non-blocking (#562); expression-level losses skip simplification (#674); and discrete custom-value mutation works again (#687). - Diagnostics:
worker_timeoutnow controls connection startup by settingJULIA_WORKER_TIMEOUTwhile workers are created (commits 5b4a712 and f45e146); template expression mistakes raise specific errors; hall-of-fame CSV output escapes embedded quotes; iteration counters display consistently withprogress=false. - The simulated annealing temperature schedule survived its port to
SimulatedAnnealingPluginbit for bit, verified by identical hall-of-fame hashes (#652).
Dependency versions
SymbolicRegression.jl 2.0.0 (tag v2.0.0) requires Julia 1.10 or later and uses:
- DynamicExpressions.jl
~2.10(up from~1.10.1/~1.11). - DynamicDiff.jl
0.3(up from0.2). - SymbolicUtils.jl
4, as an optional extension for symbolic conversion. - Optional extension packages for autodiff backends: Mooncake
0.4.137/0.5and Enzyme0.12/0.13, selected withautodiff_backend=:Mooncakeor:Enzymealongside the existing Zygote route. - Tables.jl as an optional extension backing the MLJ-free workflow, and JSON3.jl for trace consumers.
Docs: ai.damtp.cam.ac.uk/symbolicregression. Repo: github.com/astroautomata/SymbolicRegression.jl.
All pull requests
Complete pull request list (135)
- Minimal compatibility with DynamicExpressions.jl v2 by @MilesCranmer in #464
- CompatHelper: bump compat for ProgressMeter to 1, (keep existing compat) by @github-actions[bot] in #437
- CompatHelper: bump compat for MLJModelInterface to 1, (keep existing compat) by @github-actions[bot] in #474
- full Mooncake integration by @MilesCranmer in #468
- test: fix filtering of tests by @MilesCranmer in #476
- Incorporate user "guesses" in equation search by @MilesCranmer in #469
- BREAKING: Full support for arbitrary degree operators by @MilesCranmer in #471
- Create
mutate_featurefor mutating a feature node exclusively by @MilesCranmer in #475 - Clean up output of errormonitor by @MilesCranmer in #479
- Support arbitrary degree operators for dimensional analysis by @MilesCranmer in #472
- feat: always save file even if niterations=0 by @MilesCranmer in #480
- fix: OperatorEnum gets mapped to safe operators by @MilesCranmer in #481
- docs: change to Vitepress by @MilesCranmer in #483
- chore(deps): bump actions/checkout from 4 to 5 by @dependabot[bot] in #486
- feat: add
worker_timeoutto allow workers more time to connect by @MilesCranmer in #488 - feat: add Number input support to ComposableExpression by @MilesCranmer in #484
- feat: handle mixed types in body of template expression by @MilesCranmer in #482
- docs: fix empty build dir by @MilesCranmer in #492
- fix: never store member if violating constraints by @MilesCranmer in #493
- Bump ProgressMeter compat by @Moelf in #495
- fix: caching of options in MLJ regressors by @MilesCranmer in #494
- WIP: Fix some issues with docs by @MilesCranmer in #491
- feat!: set adaptive_parsimony_scaling back to 20.0 on v2+ by @MilesCranmer in #496
- fix: preserve state when niterations=0 by @MilesCranmer in #498
- chore(deps): bump actions/download-artifact from 4 to 5 by @dependabot[bot] in #497
- fix: poisson_sample for lambda=0 by @MilesCranmer in #499
- Fix safe operator parsing in guesses by @MilesCranmer in #500
- chore(deps): bump actions/setup-node from 4 to 5 by @dependabot[bot] in #503
- style: update to formatter version 2 by @MilesCranmer in #504
- docs: add language picker by @MilesCranmer in #512
- docs: generate favicon from logo by @MilesCranmer in #513
- Refactor test suite into projects by @MilesCranmer in #524
- chore(deps): bump actions/setup-node from 5 to 6 by @dependabot[bot] in #516
- chore(deps): bump actions/upload-artifact from 4 to 5 by @dependabot[bot] in #519
- chore(deps): bump actions/download-artifact from 5 to 6 by @dependabot[bot] in #520
- test: split up mlj/templates by @MilesCranmer in #525
- chore(deps): bump actions/cache from 4 to 5 by @dependabot[bot] in #529
- Restore accidentally dropped Enzyme mention in docs by @wsmoses in #537
- merge: integrate PR #515 changes by @MilesCranmerBot in #540
- ci: allow-fail enzyme integration group by @MilesCranmerBot in #544
- Add SymbolicUtils v4 compatibility by @MilesCranmerBot in #542
- fix: add atan to ValidVector unary operators (rebased) by @MilesCranmerBot in #546
- fix!: cost after simplification must be recomputed (rebased) by @MilesCranmerBot in #550
- ci: run workflows on release branches by @MilesCranmerBot in #552
- fix(test): develop local package in integration envs by @MilesCranmerBot in #559
- docs: fix typo in custom types example by @MilesCranmerBot in #569
- fix: use eltype(x0) instead of T for randn in constant optimization by @MilesCranmerBot in #570
- chore(deps): bump actions/download-artifact from 6 to 8 by @dependabot[bot] in #578
- chore(deps): bump actions/upload-artifact from 5 to 7 by @dependabot[bot] in #577
- chore(deps): bump peter-evans/create-pull-request from 7 to 8 by @dependabot[bot] in #528
- chore(deps): bump julia-actions/cache from 2 to 3 by @dependabot[bot] in #583
- ci: bootstrap release-please by @MilesCranmerBot in #587
- chore(master): release 2.0.0-alpha.10 by @github-actions[bot] in #588
- chore(deps): bump codecov/codecov-action from 5 to 6 by @dependabot[bot] in #594
- chore(deps): bump actions/setup-node from 4 to 6 by @dependabot[bot] in #591
- chore(deps): bump lodash from 4.17.21 to 4.18.1 in /docs/all_contributors in the npm_and_yarn group across 1 directory by @dependabot[bot] in #598
- feat: add backsolve mutation by @ayagh19 in #573
- ci: run pull request matrix for same-repo branches by @MilesCranmer in #606
- Rename backsolve mutation weight by @MilesCranmer in #604
- chore(deps): bump julia-actions/setup-julia from 2 to 3 by @dependabot[bot] in #600
- chore(master): release 2.0.0-alpha.11 by @github-actions[bot] in #603
- ci: pin JuliaFormatter to 2.4.0 in format workflows by @MilesCranmer in #614
- refactor: convert float32 options fields to float64 by @MilesCranmer in #611
- chore(deps): bump codecov/codecov-action from 6 to 7 by @dependabot[bot] in #615
- docs: update equation_search docstring by @spinnau in #618
- chore(deps): bump actions/checkout from 5 to 7 by @dependabot[bot] in #620
- docs: wording tweaks for ad backends by @MilesCranmer in #613
- fix: suppress 'press q' prompt when input_stream is devnull by @MilesCranmerBot in #623
- chore(deps): bump actions/cache from 5 to 6 by @dependabot[bot] in #626
- ci: require enzyme tests on Julia 1.10 and 1.12 by @MilesCranmerBot in #631
- Mark Enzyme as non-experimental by @wsmoses in #632
- ci: separate required CI gate from extended matrix by @MilesCranmerBot in #630
- feat: composable plugin interface by @MilesCranmer in #609
- fix: prevent multiprocessing teardown hangs by @MilesCranmerBot in #641
- Fix constant-optimization restarts never escaping a zero-valued constant (v2) by @singhharsh1708 in #637
- feat!: first-class AbstractMutation type hierarchy by @MilesCranmer in #610
- ci: benchmark PRs with their own scripts by @MilesCranmer in #650
- feat!: adaptive-mutation / annealing / mutation-loop plugins + per-call mutation contexts by @MilesCranmerBot in #645
- fix: preserve simulated annealing temperature schedule by @MilesCranmerBot in #652
- fix: finalize logging after all search outputs complete by @adil-soubki in #646
- Add generic optimizable parameters for template expressions by @adil-soubki in #644
- chore(deps): bump actions/setup-node from 6 to 7 by @dependabot[bot] in #635
- perf: narrow precompile workload by @MilesCranmerBot in #642
- ci: split MLJ template integration tests by @MilesCranmerBot in #657
- docs: plugin tutorial, mutation examples, Options mutation reference by @MilesCranmerBot in #653
- feat!: remove ParametricExpression support by @MilesCranmerBot in #656
- ci: disable compiled-code cache for LoopVectorization tests by @MilesCranmerBot in #661
- Increase crossover probability in v2 defaults by @MilesCranmerBot in #643
- perf: reuse evaluation buffers during search by @MilesCranmerBot in #654
- refactor: centralize debug tracing by @MilesCranmerBot in #651
- fix: clear early DimensionMismatch for mismatched X/y sample counts by @MilesCranmerBot in #660
- feat: make crossover a customizable operation via AbstractCrossover by @MilesCranmerBot in #664
- fix: cap crossover constraint retries at max_tries attempts by @MilesCranmerBot in #666
- fix: make stdin quit monitoring non-blocking by @MilesCranmerBot in #562
- fix: preserve natural type promotion in ValidVector-Number ops by @MilesCranmerBot in #625
- compat: adopt DynamicExpressions 2.9 EvalContext by @MilesCranmerBot in #668
- fix: eval_grad_tree_array works with SubArray inputs by @MilesCranmerBot in #566
- feat: let plugins contribute operation defaults by @MilesCranmerBot in #663
- test: cover stdin monitoring branches by @MilesCranmerBot in #667
- fix: reject unsupported evaluation keywords by @MilesCranmerBot in #670
- chore(master): release 2.0.0-alpha.12 by @github-actions[bot] in #624
- chore: begin beta releases by @MilesCranmerBot in #671
- chore(master): release 2.0.0-beta.1 by @github-actions[bot] in #672
- fix: disable simplification for expression losses by @MilesCranmerBot in #674
- feat!: enable adaptive mutation weights by default by @MilesCranmerBot in #678
- chore(master): release 2.0.0-beta.2 by @github-actions[bot] in #675
- feat!: enable automatic batching by default by @MilesCranmerBot in #676
- chore(master): release 2.0.0-beta.3 by @github-actions[bot] in #679
- docs: update contributors list by @MilesCranmerBot in #681
- fix: use loss type for early-stop worker checks by @MilesCranmerBot in #683
- chore(master): release 2.0.0-beta.4 by @github-actions[bot] in #684
- style: format beta.4 changelog by @MilesCranmerBot in #685
- fix: enable discrete custom-value mutation by @MilesCranmerBot in #687
- chore(master): release 2.0.0-beta.5 by @github-actions[bot] in #689
- feat: compatibility between template expressions and custom types by @MilesCranmerBot in #690
- feat: MLJ-free machine/fit!/predict/report interface by @MilesCranmerBot in #680
- chore(master): release 2.0.0-beta.6 by @github-actions[bot] in #691
- test: assert complex evaluation of higher-order derivatives by @MilesCranmerBot in #694
- feat: allow custom types in TemplateExpression parameters by @MilesCranmerBot in #693
- chore(master): release 2.0.0-beta.7 by @github-actions[bot] in #695
- fix: display total iterations in units of niterations by @MilesCranmerBot in #696
- fix: escape quotes in hall of fame CSV by @MilesCranmerBot in #698
- chore(master): release 2.0.0-beta.8 by @github-actions[bot] in #697
- deps: allow Optim 2, Mooncake 0.5, and newer ProgressMeter by @MilesCranmerBot in #702
- ci: align JuliaFormatter pre-commit version by @MilesCranmerBot in #703
- docs: add v1 to v2 migration guide by @MilesCranmerBot in #701
- feat: evaluate typed guess constants in scope by @MilesCranmerBot in #705
- chore: update repository links after org migration by @MilesCranmer in #629
- chore(master): release 2.0.0-beta.9 by @github-actions[bot] in #706
- Rework backsolve mutation into a monotone budget-aware sparse fit by @MilesCranmerBot in #648
- Release 2.0.0 🥳 by @MilesCranmer in #707
New Contributors
- @ayagh19 made their first contribution in #573
- @spinnau made their first contribution in #618
- @singhharsh1708 made their first contribution in #637
- @adil-soubki made their first contribution in #646
Full Changelog: v1.13.4...v2.0.0