Skip to content

SeedReport: Saving & Replaying RNG seed capability. - #378

Open
drewconnelly-qntm wants to merge 11 commits into
devfrom
saving_rng_seeds
Open

SeedReport: Saving & Replaying RNG seed capability.#378
drewconnelly-qntm wants to merge 11 commits into
devfrom
saving_rng_seeds

Conversation

@drewconnelly-qntm

@drewconnelly-qntm drewconnelly-qntm commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Changes:

In crates/pecos-engines/src/monte_carlo/engine.rs:

  • Introduces SeedReport which stores the RNG seed information for a monte carlo run
  • Introduces run_with_workers_seed_report which runs a monte carlo job with workers, while managing the RNG seeds with a seed report. Has a bool option to save the report to a JSON names seed_report.json.
  • Introduces WorkerSeedRecord which stores the worker index, num shots, and seed for that worker, which is what is held in a vector inside the overall job's SeedReport for each monte carlo job.
  • Introduces methods for saving a SeedReport to JSON as well as loading them from JSON.

Potential Future Work:

  • Lets work start up on Error Pruning, Visualizing now that we can save monte carlo runs and rerun them.
  • Opens the possibility for single-shot replaying, so the user can identify a particular shot (or maybe all shots with logical failures) and rerun that exact shot. Right now only rerunning the entire monte carlo job is permitted.

Testing:

cargo test -p pecos-engines was all good
just build; just test ran through the rust tests fine but had issues with the python pytests due to some bugs with my environment locally.

Testing the following:
- tests seedreport data saving correctly
- tests determinism when seeds are the same, and disagreement when seeds are not
- tests agreement between a job and the re-running of that job
- tests loading a seed report from json and string, as well as failure when no file exists to import from.
@drewconnelly-qntm drewconnelly-qntm self-assigned this Jul 27, 2026
@drewconnelly-qntm drewconnelly-qntm added the enhancement New feature or request label Jul 27, 2026
@qciaran

qciaran commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

I haven't looked closely but just skimming things: You might want to see if you can do this without using an Arc Mutex. They are slightly expensive.

@drewconnelly-qntm

Copy link
Copy Markdown
Collaborator Author

I haven't looked closely but just skimming things: You might want to see if you can do this without using an Arc Mutex. They are slightly expensive.

Thanks, I'll check on how necessary that is.

@drewconnelly-qntm
drewconnelly-qntm marked this pull request as ready for review July 28, 2026 17:19
@ciaranra

Copy link
Copy Markdown
Member

Review

Nice feature and the right general shape: the report is the single source of truth for worker seeds, the JSON round-trip is tested, and error paths map to PecosError::Input. run_with_workers() delegating to the new method (rather than keeping a third copy of the run loop) is also good. I ran the PR's tests (7/7 pass) and clippy (clean) locally. That said, there are two blocking issues and a few smaller ones.

1. The replay tests are vacuous (blocking)

The two headline tests — rerun_from_seed_report_reproduces_original_results and rerun_from_seed_report_loaded_from_json_reproduces_original_results — currently prove nothing. Verified by experiment: running the fixture with seed 42 vs seed 999999 produces identical results. ExternalClassicalEngine always returns result: 0 and new_with_defaults uses pass-through noise, so shot output does not depend on seeds at all. The reproduce assertions would still pass if all seeding logic were deleted.

Shot-level determinism is untested anywhere: run_with_seed_report_is_deterministic_for_same_seed_workers_and_shots only compares report metadata, and its different-seed engine (c) only asserts the seeds differ, not the results.

Fix: use a fixture whose output actually depends on the RNG (e.g. new_with_depolarizing_noise with a circuit that measures), then mutation-check the tests: a different seed must produce different shots, and replaying the recorded seeds must produce equal shots.

2. ~110 lines duplicated verbatim (blocking)

rerun_from_seed_report is a byte-for-byte copy of the execution body of run_with_workers_seed_report — worker-engine setup, dedicated thread pool, panic-catching shot loop, result sorting — differing only in where the seeds come from. Any future fix to the pool/panic/ordering logic now has to be made twice, and the copies will drift. Suggest factoring one private helper, e.g.:

fn run_workers_with_seeds(
    &self,
    worker_seeds: &[u64],
    shots_per_worker: &[usize],
) -> Result<ShotVec, PecosError>

and having both paths call it.

3. Replay half-ignores the loaded report

A SeedReport loaded from JSON is a system boundary, and replay handles it loosely:

  • seed_report.workers[worker_idx] panics if workers.len() < num_workers. A truncated or hand-edited file should produce PecosError::Input, not a panic.
  • The recorded WorkerSeedRecord.shots and worker_idx fields are never used: replay recomputes distribute_shots(num_shots, num_workers) and indexes positionally. A report with reordered workers or edited shot counts replays silently wrong. Either use the recorded values or validate that they match — otherwise they're dead data.

4. self.seed = seed_report.root_seed leaves the engine inconsistent

rerun_from_seed_report assigns the seed field directly, but set_seed() also reseeds self.rng and the hybrid_engine_template. After a replay, the engine claims seed == root_seed while its RNG stream and template are elsewhere — a subsequent run_with_workers call produces a report whose root_seed doesn't correspond to its base_seed. Either call self.set_seed(seed_report.root_seed) or don't mutate engine state at all (replay doesn't need it — the worker seeds come from the report). The assignment also sits before the zero-shot/zero-worker asserts.

5. API shape

The save_seed_report: bool flag plus the hardcoded "seed_report.json" path is a footgun: it silently overwrites in the current working directory, concurrent runs clobber each other, and the caller can't choose a path. Simpler API: drop the bool, always return the report, and give SeedReport a to_json_file(path) method symmetric with from_json_file. That also removes MonteCarloEngine::save_seed_report_json, which doesn't touch the engine — the behavior belongs on SeedReport.

Nits

  • crates/pecos-engines/Cargo.toml: use tempfile.workspace = true — the workspace root already defines tempfile = "3" and the other crates use the workspace entry.
  • save_seed_report_json calls std::fs::write even though use std::fs; is imported.
  • Worth a doc note on SeedReport that root_seed alone cannot reproduce base_seed if the engine ran jobs before this one (the RNG has advanced) — the per-worker seeds are the actual replay contract.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants