Skip to content

Fix empty checkpoints for custom JAX mappings - #1199

Open
MilesCranmerBot wants to merge 2 commits into
astroautomata:masterfrom
MilesCranmerBot:fix-1198-custom-jax-pickle
Open

Fix empty checkpoints for custom JAX mappings#1199
MilesCranmerBot wants to merge 2 commits into
astroautomata:masterfrom
MilesCranmerBot:fix-1198-custom-jax-pickle

Conversation

@MilesCranmerBot

Copy link
Copy Markdown
Contributor

Summary

  • omit non-picklable custom JAX/export mappings from checkpoints
  • write checkpoints atomically so failed pickles do not leave empty files
  • fall back to CSV backups when loading an empty/corrupt checkpoint

Fixes #1198

Tests

  • uv run --python /usr/bin/python3.12 --with pytest --with 'jax[cpu]>=0.4,<0.6' --with 'juliacall>=0.9.28,<0.9.29' --with 'sympy>=1,<2' --with 'pandas>=0.21,<4' --with 'numpy>=1.13,<3' --with 'scikit-learn>=1,<2' --with 'click>=7,<9' --with 'typing-extensions>=4,<5' python -m pytest pysr/test/test_jax.py -k "checkpoint or empty"\n- python3 -m compileall -q pysr/sr.py pysr/test/test_jax.py

@codecov

codecov Bot commented Jun 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.46341% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.34%. Comparing base (14cbe96) to head (aea7cde).

Files with missing lines Patch % Lines
pysr/sr.py 91.46% 7 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1199      +/-   ##
==========================================
+ Coverage   94.24%   94.34%   +0.09%     
==========================================
  Files          21       21              
  Lines        1686     1714      +28     
==========================================
+ Hits         1589     1617      +28     
  Misses         97       97              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@MilesCranmer

Copy link
Copy Markdown
Member

@MilesCranmerBot it feels like we are loading up this PySRRegressor with too much stuff In particular there are a bunch of new functions here which are not really "core" operations to the regressor. I believe there is a nice way to refactor this. Please do so.

@MilesCranmerBot

Copy link
Copy Markdown
Contributor Author

Refactored in 2e59bfd: moved the checkpoint/pickle/export-column helper logic into pysr/_checkpoint.py, leaving PySRRegressor with thin call sites.

Checks run:

  • uvx pycln pysr/sr.py pysr/_checkpoint.py pysr/test/test_jax.py
  • uvx black --check pysr/sr.py pysr/_checkpoint.py pysr/test/test_jax.py
  • uvx isort --check-only pysr/sr.py pysr/_checkpoint.py pysr/test/test_jax.py
  • uv run --python /usr/bin/python3.12 --with pytest --with "jax[cpu]>=0.4,<0.6" python -m pytest pysr/test/test_jax.py -k "checkpoint_custom_jax_mapping or empty_checkpoint"

Comment thread pysr/_checkpoint.py Outdated
Custom `extra_sympy_mappings` or `extra_jax_mappings` can hold objects
that pickle cannot store, such as `sympy.Function` classes created at
runtime. `_checkpoint` truncated `checkpoint.pkl` before pickling, so a
failed pickle left an empty file which `from_file` could not read.

- write checkpoints through a temporary file and `os.replace`, leaving any
  existing checkpoint intact when the pickle fails
- clear `extra_jax_mappings` alongside the sympy and torch mappings
- omit `equations_` from the checkpoint when it cannot be pickled; it is
  recreated by `refresh()` once the mappings are passed again
- fall back to the CSV backups when a checkpoint is empty or corrupt

Fixes astroautomata#1198

Co-Authored-By: Miles Cranmer <miles.cranmer@gmail.com>
@MilesCranmerBot
MilesCranmerBot force-pushed the fix-1198-custom-jax-pickle branch from aea7cde to 8f63afa Compare August 14, 2026 04:44
@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: 8f63afa3f9

ℹ️ 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
pysr_logger.info(f"Attempting to load model from {pkl_filename}...")
try:
with open(pkl_filename, "rb") as f:
return cast("PySRRegressor", pkl.load(f))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Catch missing-class failures when loading checkpoints

When a checkpoint references a module or class that is no longer importable—for example after an environment change or because corruption alters a GLOBAL opcode—pickle.load raises ModuleNotFoundError, ImportError, or AttributeError, none of which are caught here. Consequently from_file propagates the exception instead of using the CSV recovery path introduced by this change, even when valid hall-of-fame backups and reconstruction arguments are available. Handle these deserialization failures as unusable checkpoints as well.

Useful? React with 👍 / 👎.

Comment thread pysr/sr.py
Comment on lines +1322 to +1323
model = _load_checkpoint(pkl_filename) if pkl_filename.exists() else None
if model is not None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Route multi-output recovery through its output files

When an empty or corrupt checkpoint belongs to a multi-output run, returning None here routes loading into a CSV preflight that checks only hall_of_fame.csv and .bak. Multi-output searches instead write hall_of_fame_output1.csv, hall_of_fame_output2.csv, and so on, as get_equation_file(i) confirms, so recovery raises FileNotFoundError despite all required backups being present. The fallback should validate the output-specific files when nout > 1.

Useful? React with 👍 / 👎.

Comment thread pysr/sr.py
Comment on lines +1530 to +1533
except Exception as e:
pysr_logger.debug(f"Error checkpointing model: {e}")
if tmp_filename is not None:
tmp_filename.unlink(missing_ok=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Avoid treating a preserved stale checkpoint as current

If a later fit successfully updates the hall-of-fame CSV but serialization then fails—for example because a custom plugin or user-attached attribute is unpicklable—this handler deliberately preserves the previous valid checkpoint. A subsequent from_file prefers that old checkpoint and, because it already contains non-None equations, never refreshes from the newer CSV, silently returning results from the earlier run. Record that the write failed or make loading detect that the CSV is newer so preserving the file does not make stale experimental results appear current.

Useful? React with 👍 / 👎.

Comment thread pysr/sr.py
Comment on lines +1520 to +1525
with tempfile.NamedTemporaryFile(
mode="wb",
dir=pkl_filename.parent,
prefix="checkpoint.",
suffix=".pkl.tmp",
delete=False,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve checkpoint permissions during atomic replacement

On POSIX systems, NamedTemporaryFile creates the temporary checkpoint with mode 0600, independent of the normal umask, and os.replace carries that mode to the destination. Previously, creating checkpoint.pkl with open(..., "wb") normally produced a group/world-readable file such as 0644, so this change prevents collaborators or service accounts from loading checkpoints in shared output directories. Apply the intended destination mode to the temporary file, or preserve the existing checkpoint's mode, before replacing it.

Useful? React with 👍 / 👎.

@codecov

codecov Bot commented Aug 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

- treat missing modules or classes as an unusable checkpoint
- look for the output-specific hall of fame files when `nout > 1`
- keep the checkpoint's usual permissions across the atomic replace
- warn when a preserved checkpoint may be older than the CSV files

Co-Authored-By: Miles Cranmer <miles.cranmer@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants