Skip to content

feat: add runtime TypeSpec support - #1280

Merged
MilesCranmer merged 69 commits into
astroautomata:masterfrom
MilesCranmerBot:codex/type-spec
Aug 18, 2026
Merged

feat: add runtime TypeSpec support#1280
MilesCranmer merged 69 commits into
astroautomata:masterfrom
MilesCranmerBot:codex/type-spec

Conversation

@MilesCranmerBot

@MilesCranmerBot MilesCranmerBot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add TypeSpec(name, fields, sample, ...) for symbolic regression over custom Julia value types
  • compile each TypeSpec into one deterministic private Julia module per process, with exact-source reuse, replacement warnings, and conflicting checkpoint-restore protection
  • make fitting transactional so failed searches and rejected warm starts leave the previously fitted Julia state intact
  • preserve and replay operators, objectives, complexity mappings, early-stop conditions, templates, and worker definitions across serialization and multiprocessing
  • support discrete constants through mutate and continuous constant optimization through scalar_constants and with_scalar_constants
  • support typed OperatorEnum evaluation, full objectives, ExpressionSpec, explicit-keyword TemplateExpressionSpec, and custom expression specs that declare TypeSpec support
  • remove the legacy positional TemplateExpressionSpec constructor and its obsolete compatibility machinery
  • pin SymbolicRegression.jl v2.0.0-beta.5 for custom-value mutation support

Example

type_spec = TypeSpec(
    name="Vec2",
    fields={"data": "Vector{Float64}"},
    sample="rng -> Vec2(randn(rng, 2))",
    scalar_constants="value -> value.data",
    with_scalar_constants="(value, constants) -> Vec2(constants)",
)

model = PySRRegressor(
    type_spec=type_spec,
    operators={
        1: ["rotate90(x::Vec2) = Vec2([-x.data[2], x.data[1]])"],
        2: ["add_vectors(x::Vec2, y::Vec2) = Vec2(x.data + y.data)"],
    },
    elementwise_loss=(
        "vector_loss(x::Vec2, y::Vec2)::Float64 = "
        "sum(abs2, x.data - y.data)"
    ),
)

The documentation includes custom full objectives plus vector-valued, string-valued, heterogeneous tensor, variable-length constant, template, multiprocessing, and checkpoint examples.

Verification

  • 44 passed, 36 subtests passed in pysr/test/test_type_specs.py
  • 157 passed, 38 subtests passed in pysr/test/test_main.py
  • changed-file pre-commit hooks passed
  • git diff --check passed

@codecov

codecov Bot commented Aug 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Comment thread pysr/sr.py Outdated
Comment thread pysr/type_specs.py Outdated
Comment thread pysr/type_specs.py Outdated
Comment thread pysr/sr.py Outdated
@MilesCranmer

Copy link
Copy Markdown
Member

@MilesCranmerBot please address the above

@MilesCranmer

Copy link
Copy Markdown
Member

@MilesCranmerBot also we need 100% diff coverage

@MilesCranmerBot

Copy link
Copy Markdown
Contributor Author

Addressed in 9601b5a:

  • renamed the TypeSpec setup method to instantiate
  • replaced the @eval wrappers with direct jl.seval definitions
  • removed the serial-only restriction and added a multithreaded TypeSpec regression test
  • added coverage for TypeSpec conversions and callback variants

Checks: normal numeric regression test passes; TypeSpec direct-instantiation coverage is 100%.

@MilesCranmer
MilesCranmer self-requested a review August 9, 2026 02:46
@MilesCranmer

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 69d5b4ded4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pysr/type_specs.py Outdated
Comment thread pysr/sr.py Outdated
Comment thread pysr/type_specs.py Outdated
Comment thread pysr/expression_specs.py Outdated
@MilesCranmerBot
MilesCranmerBot force-pushed the codex/type-spec branch 5 times, most recently from 0069e72 to 0bfdb21 Compare August 10, 2026 22:21
MilesCranmerBot and others added 4 commits August 14, 2026 11:22
`names(...; all=true)` reports gensym symbols such as `#1#2` for the anonymous
functions in a TypeSpec module. Copying those into the configuration module
reserved the same names that Julia 1.11 assigns to that module's own closures,
so loading any configuration failed with an invalid constant redefinition.

Also correct the runtime boundary annotations for numeric early stopping and
the search output tuple.

Co-authored-by: Miles Cranmer <miles.cranmer@gmail.com>
MilesCranmerBot and others added 8 commits August 14, 2026 21:08
A configuration source that raised left the module holding the constants for
the sources that had already run, so retrying the same definition redefined
them and failed on Julia 1.10 and 1.11. Replace the module on every install
and stop binding the per-source values, which nothing read.

Also state what a preamble shares with configuration sources.

Co-authored-by: Miles Cranmer <miles.cranmer@gmail.com>
SymbolicRegression wraps the equation in quotes without escaping its contents,
so an equation holding a string constant is not valid CSV: pandas either
raises or shifts a later field into the complexity column, which then fails
arithmetic. The file has three columns in a fixed order, so read the equation
as the rest of the line.

Co-authored-by: Miles Cranmer <miles.cranmer@gmail.com>
The fixtures wrote a leading index column, so the file had four fields where
the Julia writer emits three, and the equation was read as the last two.

Co-authored-by: Miles Cranmer <miles.cranmer@gmail.com>
SymbolicRegression 2.0.0-beta.8 escapes quotes in the equation column, so the
file is valid CSV and the hand-rolled reader added for beta.7 is unnecessary.

Co-authored-by: Miles Cranmer <miles.cranmer@gmail.com>
Co-authored-by: Miles Cranmer <miles.cranmer@gmail.com>
A type module that failed partway stayed defined in Main, so every later
load skipped reinstall and failed with a missing binding. Mark the module
as installed with its last statement and reinstall whenever that marker is
absent.

Evaluating an expression through its callable let DynamicExpressions fill
the output with NaN, which cannot convert to a generated type. Evaluate
with eval_tree_array and report the rejected value instead.

Co-authored-by: Miles Cranmer <miles.cranmer@gmail.com>
The parameter test only checked fingerprint stability, so nothing proved
that a parameter holding a TypeSpec value actually changes during a
search. Fit a model whose only route to zero loss is the mutate hook
rewriting the parameter, and document how parameters are created,
mutated, and optimized.

Co-authored-by: Miles Cranmer <miles.cranmer@gmail.com>
@MilesCranmer

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 01e998d528

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pysr/sr.py
MilesCranmerBot and others added 2 commits August 17, 2026 18:04
TypeSpec and TemplateExpressionSpec models hold live Julia objects in
`julia_expression` and `lambda_format`, which cannot be revived in a
fresh Julia session. Drop both columns for either spec when pickling and
rebuild them from the search output whenever equations are accessed, so
`get_best`, `from_file`, and the export helpers all work after a restore
instead of only `predict`.

Co-authored-by: Miles Cranmer <miles.cranmer@gmail.com>
Co-authored-by: Miles Cranmer <miles.cranmer@gmail.com>
Comment thread docs/src/examples.md Outdated
MilesCranmer
MilesCranmer previously approved these changes Aug 18, 2026
Every error branch reported uncovered by codecov on this PR now runs
under the test suite: spec and configuration validation, fit and
prediction data validation, array conversion failures, the runtime
value-ordering guard, warm-start toggling, and the fitted-model
score/predict guards.

Co-authored-by: Miles Cranmer <miles.cranmer@gmail.com>
@MilesCranmer
MilesCranmer merged commit e2a159b into astroautomata:master Aug 18, 2026
31 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants