IBM QAOA: dead-code cleanup, full-recursion Nautilus campaigns - #84
Conversation
…d paper draft - Remove 21 dead functions/constants from src/simulation_validation.py and src/utils.py (backend-choice suite, hardware-alignment stats, WS campaign builders, plotting helpers, etc.) with no callers anywhere in the repo, confirmed not to affect reproducing Analysis.ipynb or Simulation_Method_Validation_and_WS.ipynb. - Propagate chi and evaluator N/M (maxiter, shots) to every depth of RecursionTrainer's nested MPSAerEvaluator in the PSS campaign, not just p=1. - Add Nautilus jobs/scripts for full-recursion Interp*/I_MPSAer campaigns at p=7 and p=9 (sharded runs, cleanup/finalize/setup jobs, extended-Q-grid FA_no_opt/PT re-runs). - Add hardware-calibrated multi-strategy window sticker and Pareto frontier overlay cells to Simulation_Method_Validation_and_WS.ipynb. - Add AUDIT_REPORT.md (source of the PR #50 review-feedback fixes) and the resource-cost-extension paper draft (resource_cost_extension.tex/.pdf, v1.tex). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Not meant to be included here. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
bernalde
left a comment
There was a problem hiding this comment.
Thanks for pulling the audit fixes out into their own PR — the dead-code sweep is well evidenced, and the prepare_training_bricks_data change (positional .sem().values → merge on ["job_p", "method_base"]) is a real misalignment bug worth catching on its own.
Verified locally at 9c76ae7:
pytest tests/ -q→ 333 passed (matches your test plan)flake8 --select=F401,F841,E9,F63,F7,F82on the touched Python files → clean- Both notebooks: 13 and 23 code cells, monotonic execution counts, zero error outputs
- Every top-level name removed from
src/simulation_validation.py/src/utils.py(34 by my count) has zero remaining references anywhere in the tree — the "no callers" claim holds - GitHub checks on this head: 6/6 green (
test 3.10/3.11/3.12,tutorial-smoke,integration-tests,coverage-summary)
Four things block merge.
Blocking
1. This PR targets main but is stacked on the still-open #50
git merge-base main 9c76ae7 is 2680ac7 — the same merge base as #50 — and #50's head is an ancestor of this head. The split:
| diff | files | insertions |
|---|---|---|
main..#50 |
82 | 45,683 |
main..#84 (what GitHub shows here) |
90 | 46,715 |
#50..#84 (what this PR actually adds) |
15 | 2,394 (−1,362) |
80 of the 82 commits and ~97.8% of the added lines belong to #50. Merging this as-is lands all of #50 into main before #50's own review concludes.
Please retarget the base to QAOA_Parameter_Setting_IBM, or hold this until #50 merges and then rebase. Either way the review surface becomes the 15 files you actually changed.
This is not cosmetic. Because the pull-files endpoint truncates positionally on a diff this size, src/simulation_validation.py, src/utils.py, run_job.sh and run_prepare_pss_campaign.py all return patch: null with +0/-0 — GitHub renders no hunks for them, so none of your actual code changes can receive an inline comment. That is why the findings below are in the review body rather than anchored on the lines they describe. Retargeting fixes that too.
2. Fixed Angles* and Fixed Angles† render as near-identical colours, and the legend that used to separate them was removed
_FAMILY_CMAP_SPEC assigns FA_star → (Blues, 0.38, 0.92) and FA_dagger → (Blues, 0.32, 0.58). Those ranges overlap on 0.38–0.58, so the two families draw from the same stretch of the same colormap. Running _build_family_color_map over the 14 method labels actually present in Simulation_Method_Validation_and_WS.ipynb:
d=0.051 'Fixed Angles$^\star$ (p=5)' vs 'Fixed Angles† (p=3)'
d=0.092 'Fixed Angles$^\star$ (p=5)' vs 'Fixed Angles† (p=2)'
d=0.113 'Fixed Angles$^\star$ (p=6)' vs 'Fixed Angles† (p=6)'
(RGB Euclidean distance; for reference the smallest pre-existing cross-family gap is 0.194.) The last row is the damaging one: at the same depth p=6, full COBYLA optimisation and no optimisation are drawn in effectively the same blue — and that contrast is the point the figure exists to make.
Previously each method base got its own legend handle, so colour similarity was recoverable. This PR drops method_handles in favour of per-family colorbars, which leaves colour as the only discriminator, and the two Blues colorbars sit side by side.
Concrete fix — I swept the candidates against those same 14 labels:
FA_dagger colormap |
worst cross-family distance | colliding pair |
|---|---|---|
Blues (0.32, 0.58) — as merged |
0.051 | FA* vs FA† |
BuPu |
0.050 | FA* vs FA† |
Oranges |
0.082 | FA† vs Linear Ramp |
Purples |
0.111 | FA† vs Param. Transfer |
PuRd |
0.160 | FA† vs Param. Transfer |
Reds / RdPu / copper |
0.194 | FA* vs Param. Transfer |
plt.cm.Reds (or RdPu/copper) at (0.35, 0.85) raises the worst separation to 0.194, which is the ceiling already imposed by the pre-existing FA*/Param.-Transfer pair — roughly 4× the current worst case. Note Purples alone does not fix it; it just moves the collision to FA† vs Param. Transfer.
I would also keep one legend handle per method family alongside the colorbars, so the figure does not depend on colour discrimination alone.
3. resource_match_bins=150 changes campaign results by default, and works around a defect in the core package
run_stochastic_benchmark_pss gains resource_match_bins: int | None = 150, which snaps sb.interp_results onto a 150-point grid before the recipe/evaluate matching. The premise checks out — src/training.py:199 evaluate_single does match on exact float equality:
df_eval[df_eval[resource_col] == recipe[resource_col]]Three problems with fixing it here:
- It is on by default, so it silently coarsens the ~1000-point shared grid to 150 for every caller. Anyone rerunning a campaign gets different numbers than the committed notebook outputs, with nothing in the PR description flagging that.
- The defect is in this repository's own core package, not a third-party dependency. Papering over it in one example leaves
evaluate_singlebroken for every other consumer. Please open an issue againsttraining.evaluate_singlefor tolerance-based (or binned) resource matching, and link it from the comment here. - The comment states that "bootstrap resampling below isn't seeded", so "the same campaign can yield wildly different numbers of surviving actionable-fit rows on separate runs." That is a reproducibility problem in its own right and deserves its own issue rather than only a mitigating workaround.
At minimum: document the parameter in the docstring, state the default in the PR description, and link the two follow-up issues.
Nonblocking
4. Title and description no longer match the head
The head commit 9c76ae7 ("Remove paper draft files from this PR") deletes resource_cost_extension.tex, resource_cost_extension_preview.pdf and v1.tex (−2,014 lines), but the title still ends "and paper draft" and the fifth summary bullet still advertises them. Also, the first bullet says 21 removed functions/constants; comparing top-level names between #50 and this head gives 29 from simulation_validation.py plus 5 from utils.py = 34.
Separately, several substantive changes are not described at all: build_bound_circuit_simulator (reuses one Aer backend across a grid sweep), exact-checkpoint batching (_CHECKPOINT_BATCH_SIZE = 10), the symmetrised sign matrix in line_to_full instance generation, the resource_match_bins change above, and the run_job.sh zero-training routing. Those are the changes a reviewer most needs pointed out.
5. No test covers any of the new logic
_pareto_envelope_and_owner, _build_family_color_map, _detect_method_family, _label_depth, build_bound_circuit_simulator and the resource_match_bins snapping have zero references under tests/. _pareto_envelope_and_owner in particular is pure, deterministic, dependency-free array logic — a handful of cases (all-NaN column, single-point entry, running-max persistence after a method's data ends) would pin the behaviour cheaply. _detect_method_family is worth pinning too, since a label that matches none of its branches falls through to s[:20], which is not in _FAMILY_ORDER and therefore gets no colorbar.
6. ZERO_TRAINING_METHODS is duplicated across Python and shell
simulation_validation.py:56 and run_job.sh:71 now hold the same three-element list in two languages. They agree today, but Bug 2 in your own audit report is precisely this pair drifting apart. Worth having run_job.sh read the list out of Python (e.g. python -c 'import ...; print(" ".join(sorted(ZERO_TRAINING_METHODS)))') so it cannot drift again.
7. extend_curves_to_xlim=True draws a flat hold with no visual cue
Both notebook call sites pass extend_curves_to_xlim=True, which repeats each curve's last value out to the right edge. The Pareto/background computation correctly uses the natural pre-extension series — that part is handled well and the comment explaining it is good. But the drawn line is indistinguishable from measured data, so a reader sees a plateau where there are simply no measurements. Consider drawing the extended segment dashed or at lower alpha.
Question
8. Overwriting the nested evaluator_init in RecursionTrainer
The new block replaces the previous chi-only propagation:
nested_init = trainer_init.setdefault("trainer_init", {})
if "evaluator" in nested_init:
nested_init["evaluator"] = "MPSAerSampleEvaluator"
nested_init["evaluator_init"] = dict(evaluator_init)Two behaviour changes I want to confirm are intended, since the method configs live in the sibling QAOA-Parameter-Setting checkout and I can't check them from here:
- The old code used
mps_args.setdefault(...), preserving anything a config had set for the nested evaluator. This assigns over the wholeevaluator_init, so a config that deliberately set a different chi (or other MPS args) for the recursion evaluator now has it discarded. Deliberate? .get(...)became.setdefault(...), which fixes a real latent bug (the old call mutated a throwaway dict), but it also means aRecursionTrainerconfig with no nestedtrainer_initnow gets one created carryingminimize_args/COBYLA. The outer block guards ontrainer_name in {"ScipyTrainer", "TQATrainer"}; the nested one appliescobyla_maxiterunconditionally. Is the nested trainer always aScipyTrainerin practice?
Blocking: 4 (1 inline, 3 in this body) · Nonblocking: 4 · Questions: 1 · Total: 9
Tests run at this head: pytest tests/ -q → 333 passed; flake8 --select=F401,F841,E9,F63,F7,F82 on touched files → clean; flake8 src --select=E9,F63,F7,F82 → 0; notebook execution-state audit → 0 errors, 0 unexecuted cells, monotonic counts. GitHub checks on 9c76ae7: 6/6 SUCCESS, none skipped. These are PR-head results; I did not evaluate the merge result, since finding 1 means the merge result would also carry all of #50.
Merge readiness: not ready. I would not merge this until the blocking issues above are addressed. The base retarget (finding 1) is the one to do first — it changes what the rest of this review is even looking at, and it restores inline commenting on your code.
Blocking: - Drop AUDIT_REPORT.md: a point-in-time audit shipped alongside its own fixes goes stale on arrival; its findings are already reflected in the diff, and preserved in the PR description instead. - Fix the FA_star/FA_dagger colormap collision: FA_dagger moves off Blues (which overlapped FA_star's range at p=6, drawing full-COBYLA and no-optimization curves in effectively the same blue) onto Reds. Restore one legend handle per method family alongside the per-family colorbars so the figure doesn't depend on colour discrimination alone. - Document run_stochastic_benchmark_pss's resource_match_bins parameter (on by default, changes campaign results): it's a local workaround for training.evaluate_single's exact-float-equality resource matching, now tracked as a follow-up against the core package (#85), interacting with the also-tracked unseeded bootstrap resampling (#86). Nonblocking: - run_job.sh's zero-training method routing now reads ZERO_TRAINING_METHODS out of src.simulation_validation at run time instead of maintaining a second copy of the list in shell (they used to be able to drift apart). - Draw the extend_curves_to_xlim flat-hold segment dashed at half alpha instead of styling it identically to measured data. - Add tests/test_ibm_qaoa_simulation_validation.py: 27 tests covering _pareto_envelope_and_owner, _detect_method_family, _label_depth, _build_family_color_map (including a mutation-tested regression test for the color-collision fix), and build_bound_circuit_simulator. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Re: the RecursionTrainer nested Leaving this code as-is rather than changing the overwrite/ |
…fixes Re-executed end-to-end so the four cells that call plot_multi_method_window_sticker_component_panels and plot_pareto_frontier_overlay show the current Reds FA_dagger colormap, restored per-family legend handles, and dashed extend_curves_to_xlim segments, instead of stale pre-fix images. Also carries over the print/display path-scrubbing fix from PR #50 (this branch predates that commit, so re-executing without it reintroduced contributor absolute paths in the output). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Restore the pre-existing coloring for plot_multi_method_window_sticker_component_panels and plot_pareto_frontier_overlay: both Fixed Angles* and Fixed Angles† use the single paper-standard blue (QPS_METHOD_COLORS["Fixed Angles*"] = #4477AA, the same color Analysis.ipynb's Pareto/recommendation plots use), with Fixed Angles† getting one fixed lighter shade of that blue -- not a per-depth colormap gradient, and not a different hue. This is the exact scheme these two functions used before this review round (git history: f32bf8e), which I had replaced first with a depth-gradient colorbar system and then with a different-hue (Reds) FA_dagger colormap while responding to review feedback about a color collision, without checking that either change was consistent with the already-published color convention. Removes _build_family_color_map, _draw_family_colorbars, _FAMILY_CMAP_SPEC, _FAMILY_DISPLAY, _FAMILY_ORDER, _detect_method_family, and _label_depth, which are now unused by both call sites. _pareto_envelope_and_owner is unrelated (Pareto envelope/background-shading, not color assignment) and stays. Removed the corresponding tests for the deleted functions from tests/test_ibm_qaoa_simulation_validation.py. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
test_specs_df's graph_path/minmax_path can point directly into the sibling QAOA-Parameter-Setting checkout, not just into stochastic-benchmark's own data/generated_instances cache. The path-scrubbing helper added earlier only handled paths under REPO_ROOT, so any row loaded from the sibling repo raised ValueError: not in the subpath of ... in Path.relative_to. Use WORKSPACE_ROOT (the shared parent of both sibling checkouts) as the base instead, and fall back to leaving a path unscrubbed rather than crashing if it's ever outside even that -- this is a display convenience, not something that should be able to break the notebook. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…gy plots
My previous commit ("Revert Fixed Angles color scheme to the paper's
original convention") went too far: it deleted the entire
_build_family_color_map/_draw_family_colorbars system for all five method
families (FA_star, FA_dagger, PT, LR, Interp), not just the Fixed Angles
hue. That system -- per-family colorbars showing a circuit-depth gradient,
orange Linear Ramp, green Interpolation, etc. -- is what these two plots
already used and is what's in the user's existing slide decks; deleting it
was a mistake, not a fix.
Restored _build_family_color_map, _draw_family_colorbars,
_FAMILY_CMAP_SPEC, _FAMILY_DISPLAY, _FAMILY_ORDER, _detect_method_family,
and _label_depth verbatim from before this review round (git show
bf4e163), and both call sites' color/legend code. The one actual change,
matching what the reviewer originally flagged: FA_star/FA_dagger's shade
ranges on plt.cm.Blues were (0.38, 0.92) and (0.32, 0.58), which overlap on
0.38-0.58 -- narrowed to disjoint bands (0.55, 0.92) and (0.15, 0.42), both
still plt.cm.Blues (same hue, not the earlier Reds/custom-colormap
attempts), same as every other family's colors, which are unchanged.
Re-added tests for the restored functions, including a mutation-tested
regression test for the disjoint-band fix.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… two more path leaks Re-executed end-to-end: zero error outputs, all cells present. This run surfaced two more places printing contributor absolute paths that the earlier scrubbing pass didn't cover: - save_current_plot's "Saved: ..." print used the raw (absolute) plot_dir. Now prints relative to cwd via os.path.relpath, which never raises (unlike Path.relative_to), so it degrades gracefully instead of crashing if cwd and plot_dir are on different filesystem roots. - The multi-strategy cell's "Loaded strategy roots:" print used the raw item['root'] path. Now printed relative to REPO_ROOT. Also scrubbed the contributor-path prefix from this run's committed outputs, same as the earlier passes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
# Conflicts: # examples/IBM_QAOA/notebooks/Simulation_Method_Validation_and_WS.ipynb
The T_proxy formula used exact_df['N'] * exact_df['M'] uniformly for every strategy, which only reflects the cost of the p=1 training stage. It then "corrected" for INTERP's RecursionTrainer sub-depths (p=2..target) with a hand-derived (p-1) * 50 * M term, assuming a hardcoded 50-iteration maxiter that doesn't match reality. Verified empirically (constructing a small RecursionTrainer run through the actual pipeline, and inspecting the committed heavy_hex_144_I_p7_expanded campaign data) that build_sampled_training_config already propagates the same (N, M) grid point to every recursion depth, and that total_training_shots already correctly sums real shot counts across every stage -- including all recursion sub-depths, via RecursionTrainer sharing its nested ScipyTrainer's evaluator (RecursionTrainer.__init__ passes trainer.evaluator to BaseTrainer.__init__). The N*M+hardcoded-50 proxy and its correction were both approximating data that was already computed correctly and sitting unused in total_training_shots. For INTERP strategies (I_MPSAer, I_PP, I_MPS, I_SV), use total_training_shots * hardware_time_per_shot directly instead. No campaign regeneration or Nautilus job needed -- existing committed data already has the correct total_training_shots column. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Re-executed: first with RESULT_TAG=heavy_hex_144_I_p7_expanded to regenerate that tag's cached summary CSVs (window_sticker_summary.csv, etc.) under the fixed total_training_shots-based T_proxy formula, then back with the original RESULT_TAG=heavy_hex_144_FA_no_opt_p6_expanded so the multi-strategy/calibration/Pareto cells pick up the refreshed INTERP data from disk. The corrected Interpolation curve now enters the Pareto frontier abruptly partway through the budget range instead of sweeping smoothly from the lowest budgets. This is real, not a regression: verified against the actual pipeline (matching the campaign's exact N grid and warm-started delta_n increments) that RecursionTrainer's deeper sub-depths hit COBYLA's num_params+2 minimum-evaluation floor (16 evaluations at p=7's 14 params) regardless of how small a maxiter is requested, so Interpolation has a real minimum training cost that the previous N*M+hardcoded-50 formula hid. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
bernalde
left a comment
There was a problem hiding this comment.
Targeted re-review at f3c36db, superseding my review at 9c76ae7. The base merge and the color work landed well; CI is red on one item that has now been open across four rounds, and two commits brought in a methodology change I have not reviewed before.
@anurag-r20 — the concrete asks are the four numbered items below. The first is the only one blocking merge.
Resolved since 9c76ae7
- Base merge —
c1e906bamergedmain(with #50) into the branch.git merge-treeagainstmainnow exits 0 with no conflicts, andmergeable: true. - Colour collision — verified after the merge rather than assumed, since a base merge can move a fix's dependencies.
FA_star (0.55, 0.92)/FA_dagger (0.15, 0.42)onBlues, non-overlapping: worst cross-family separation is 0.172, up from the 0.051 I flagged. Splitting the ranges keeps the publishedQPS_METHOD_COLORSconvention and fixes the collision, which is better than either theRedsswap I originally prescribed or the flat revert. I also re-ran the mutation at the merged head — restoring the overlapping ranges reddenstest_fa_star_and_fa_dagger_do_not_collideand leaves its three siblings green, so the guard still guards. AUDIT_REPORT.md,resource-match-bins-default(issues #85/#86),zero-training-methods-duplication,extend-curves-flat-hold, and the title/body rewrite all remain addressed.
1. Blocking — the qiskit guard (see the inline comment)
Fourth round for this one, and CI can finally see it: 4 failed / 471 passed on f3c36db, and the same four failures on c1e906ba and a609fac. Details and a verified fix are in the inline comment on tests/test_ibm_qaoa_simulation_validation.py.
2. Question — does total_training_shots really include the recursion sub-depths?
a609fac replaces the INTERP training-cost proxy — N × M plus a hand-derived (p−1) × 50 × M correction — with total_training_shots × t_shot. The reasoning in the commit message is careful and the code guards on the column being present, so this is a question rather than a blocker. But it rests on a claim I cannot check from the repository, and it contradicts the comment it deletes.
The removed comment stated that stage 3's RecursionTrainer uses MPSAerEvaluator — exact, no .shots — so _augment_stage_result sets training_shots_used=0 and the sub-depths contribute nothing to total_training_shots. The new comment states the opposite. Both cannot be true, and which one holds decides whether INTERP training cost is now correct or silently understated by the entire recursion cost.
The reconciliation is probably this PR's own change at src/simulation_validation.py:1492-1494:
if "evaluator" in nested_init:
nested_init["evaluator"] = "MPSAerSampleEvaluator"
nested_init["evaluator_init"] = dict(evaluator_init)That makes the sub-depths shot-based, so their shots would be counted — but only for campaigns generated after that change. If heavy_hex_144_I_p7_expanded predates it, its total_training_shots still excludes the sub-depths and the new formula understates INTERP cost.
Two things would settle it, and neither needs a campaign re-run:
- Post the actual numbers for a few INTERP rows —
total_training_shotsagainstN × Mat the same(N, M, p). If sub-depths are counted, the ratio should trackp, not sit at 1. - Note in the notebook which campaign generation the data comes from, since
results/is not committed (onlydata/generated_instancesis), so neither CI nor a reviewer can reproduce this.
Related: this is the behaviour you deferred under nested-recursion-evaluator-init, which now has a consumer. Worth folding into that follow-up rather than leaving both open.
3. Nonblocking — the T_proxy formula is now stated two ways
a609fac updated the methodology markdown (cell 4) to document INTERP using total_training_shots, but the section header further down (cell 8) still tells the reader:
All Window Sticker results below use
T_proxy = t_preprocessing + N M t_shot + Q t_shotas the resource coordinate.
That is now wrong for INTERP, and cell 8 is the one a reader hits immediately before the figures. Please add the carve-out there, or point back to the cell 4 table. Same cell has a typo introduced in this commit: "a classical proxy to quatum hardware" → "quantum".
4. Nonblocking — the test plan in the PR body is stale
The body claims pytest tests/ -q — 360 passed and "27 new" tests. At this head it is 471 passed, 4 failed (the count moved with the base merge, and the colour revert/restore changed the new-test count). Worth refreshing once CI is green so the merge record matches.
5. Nonblocking — the unseeded bootstrap is now visible in committed output
Between 604befd and f3c36db the fitted resource model in the single-campaign cell moved from
Q(T) = exp(7.138 + 0.9703 log(T) + 0.02818 log(T)^2)
Q(T) = exp(7.063 + 0.9801 log(T) + 0.03174 log(T)^2)
with no code change touching that path — that campaign (heavy_hex_144_FA_no_opt_p6_expanded) contains only FA_PP_no_opt, so the INTERP change cannot explain it. This is the unseeded resampling tracked in #86 showing up directly in committed artifacts: re-running the notebook moves published coefficients. No action needed in this PR, but it is a concrete instance worth attaching to that issue, since it means figure refreshes are not reproducible.
Tests run and outcomes
At PR head f3c36db, in a clean copy of the head tree:
pytest tests/ -q— 4 failed, 471 passed, reproducing CI exactly. All four failures areTestBuildBoundCircuitSimulator::*withImportError: Sampling requires qiskit and qiskit-aer- With the prescribed guard applied — 471 passed, 4 skipped
- Colour-map measurement and mutation probe as described above
git merge-tree --write-tree upstream/main f3c36db— exit 0, no conflicts- Notebook: 13 code cells, 0 error outputs, monotonic execution counts, 8 figures, 0 absolute paths in output
- Changed-file set matches GitHub exactly (15/15)
GitHub checks on f3c36db: test (3.11) and coverage-summary fail; test (3.10) and test (3.12) report cancelled from fail-fast, not independent failures; integration-tests reports skipped because its dependency failed, so it is a cascade rather than an intentional skip; tutorial-smoke passes.
I did not run a separate merge-result check: the branch now contains main, so the head is the merge result.
Summary
Blocking: 1 (inline) · Questions: 1 · Nonblocking: 3 · Total: 5
I would not merge this until the blocking issues above are addressed. Item 1 alone turns CI green and is independent of everything else — worth pushing on its own rather than batching it with the rest.
plot_multi_method_window_sticker_component_panels's extend_curves_to_xlim draws a dashed, half-alpha flat hold past each curve's own measured grid (e.g. Interpolation's p=7 campaign never swept Q as high as the extended-Q FA*/PT campaigns), but nothing in the figure explained what the dashing meant. Add a single "Held flat past measured grid" legend entry, shown only when at least one curve is actually extended, instead of per-curve text annotations that would clutter a plot with this many overlapping curves. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…hold" This reverts commit 23f8e06.
Plotting code (src/utils.py) is unchanged from the last committed refresh (the annotation add-then-revert canceled out exactly), so this diff is normal run-to-run bootstrap resampling variance, not a code-driven change. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…b repo/branch - Skip TestBuildBoundCircuitSimulator when qiskit-aer isn't installed, using a class-level skipif (not a module-level importorskip, which would wrongly skip the other 23 tests in this file too). CI's test/integration-tests/ coverage-summary lanes don't install qiskit-aer since it's an examples-only dependency. Reviewer-verified fix. - Note the INTERP total_training_shots carve-out in the "Window Sticker on Clean Proxy Resource" markdown cell, which still stated the universal N*M*t_shot formula; fix a "quatum" -> "quantum" typo in the same cell. - Point interp_full_recursion_job.sh at usra-riacs/stochastic-benchmark (was a personal fork) and IBM_QAOA_audit_recursion_and_paper_draft (was the merged, now-stale QAOA_Parameter_Setting_IBM) -- the recursion-shots fix this job exists to pick up only lives on this branch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…quest --max-parallel-threads was hardcoded to 4 while each shard requests 16 CPU (limit 32), so most of the already-requested compute sat idle. Use SHARD_CPU_REQUEST directly instead. This is a pure utilization fix -- Aer's max_parallel_threads is internal thread-level parallelism within one process, so it doesn't increase memory footprint. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two shards of the first real run failed with ModuleNotFoundError: No module named 'quimb'. qaoa_training_pipeline's main has since moved to pyproject.toml (PR #88) and gated quimb/cotengra/juliacall behind an optional [tns] extra our plain `pip install -e` doesn't request; juliacall additionally needs a Julia runtime we don't have in the container, so installing the extra risks cascading into a second failure. v0.1.0 predates that refactor: quimb is a base requirement (not an extra), and training/param_result.py, training/recursion.py are still at the paths our code imports (no framework/ restructuring yet). Verified end-to-end in an isolated env against this exact tag: run_fa_pss_exact_points with reps=1 vs reps=7 reproduces the same 235 vs 307 objective-evaluation split already confirmed against a local checkout, including the recursion floor effect. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ion_job.sh
Second job run failed with FileNotFoundError on methods/I_MPSAer.json.
QAOA-Parameter-Setting's main renamed it to methods/I_MPSAer_opt.json on
2026-06-29 ("Update method filenames and add summary table", 14e849b7). The
repo has no tags and doesn't support shallow-clone-by-SHA via --branch, so
this pre-clones the parent commit (50a17c6) directly with the same sparse
paths run_simulation_validation.sh normally sets up, then sets
SKIP_REPO_UPDATE=1 so it leaves that checkout alone instead of re-cloning
main over it. Verified the sparse+blob-filtered fetch is fast (~24MB) and
methods/I_MPSAer.json is present at that commit.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ne contention Third job run: multiple shards failed with "RPC failed; curl 18 Transferred a partial file" / "unexpected disconnect while reading sideband packet" during the stochastic-benchmark clone, consistent with 10 simultaneous shallow clones of the same repo hitting GitHub at the same instant. Stagger by JOB_COMPLETION_INDEX (0, 6, 12, ..., 54s) plus small jitter before the clone starts. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ob.sh
Fourth job run hit the same "methods/I_MPSAer.json not found" error the
QPS pin was supposed to fix. Root cause: run_simulation_validation.sh reads
IBM_QAOA_SKIP_REPO_UPDATE (SKIP_REPO_UPDATE="${IBM_QAOA_SKIP_REPO_UPDATE:-0}"),
not SKIP_REPO_UPDATE. The job was exporting the wrong name, so the script
never actually skipped, re-ran clone_or_update_sparse with QPS_BRANCH=main,
and silently overwrote the pinned pre-clone with the broken current main.
Verified the intended pin+skip sequence works locally; this was purely the
env var name being wrong.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Existing --restart resume was instance-level only, so an interrupted recursion-depth run near the 48h Nautilus job deadline lost all progress within the in-progress instance. This adds _cached_fa_grid_point to detect fully-computed (N, M) points in prior checkpoint data, skips them on resume, and flushes newly-computed points via an on_new_rows callback as soon as each is finalized. Warm-start angles aren't cached, so the first point computed after any skip cold-starts (trains at its own N rather than an incremental delta). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
backoffLimit: 2 was a job-wide budget shared across all 10 indices, so one shard's transient git-clone network blips (RPC failed; curl 55 Send failure: Broken pipe) alone exceeded it and killed the entire job, including other shards that were making good progress. backoffLimitPerIndex gives each shard its own retry budget; maxFailedIndexes allows up to 2 shards to permanently fail without taking the rest down with them. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… Ramp job The shards job has repeatedly failed with "RPC failed; curl 55/56 ..." on the stochastic-benchmark clone across nearly every shard -- genuine transient GitHub-egress flakiness from some federated Nautilus nodes, not clone contention (already staggered). Wrap the clone and the QPS fetch in an in-script retry loop instead of relying on Kubernetes to kill and fully re-provision the pod (apt-get + build-essential) on every transient blip. Also add lr_no_opt_job.sh: a single non-sharded Nautilus job for the zero-training "Linear Ramp" (linear_ramp_no_opt) window-sticker campaign at p=9, matching LR_opt_p9's depth and the same real Q grid already used for FA_PP_no_opt/PT_PP_AAA, to fill the one strategy in Analysis.ipynb's hardware Pareto plot with no simulated counterpart yet. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The Nautilus LR job failed immediately: run_pt_pss_exact_points unconditionally tried to load methods/linear_ramp_no_opt.json, which doesn't exist upstream -- linear_ramp_no_opt has no method config because it computes angles analytically (a TQATrainer with evaluator=None), the same way generate_angle_payload's native-mode path already handles it. Add the same analytic branch here instead of routing it through load_method_config/run_method_from_config. Verified locally end-to-end against real QAOA-Parameter-Setting/ qaoa_training_pipeline checkouts on a small instance before touching Nautilus again. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…; re-run notebook An unconstrained degree-2 log-log polynomial fit to the recommended (N, M, Q) recipe curve is a parabola -- it can't represent "rises then plateaus", so it was curving back down at high resource and recommending fewer test shots with more budget, which is nonsensical (a resource-budget recipe should never decrease as budget grows). Replace it with isotonic regression (monotonic non-decreasing) in log-log space, implemented directly via pool-adjacent-violators rather than adding scikit-learn as a new dependency. Verified on synthetic bump-shaped data and with new unit tests. Also wire linear_ramp_no_opt into run_pt_pss_exact_points (it computes angles analytically via generate_linear_ramp_angles/TQATrainer with evaluator=None, same as generate_angle_payload's native-mode path already does -- it has no methods/*.json config and doesn't need one). Split the "LR" method family into LR_star/LR/LR_dagger (mirroring the existing FA_star/FA_dagger split) so Linear Ramp's three optimization tiers -- LR_PP_angle_opt (ramp + full angle opt), LR_PP_opt (ramp opt only), and no-opt -- get distinct legend entries and color shades instead of collapsing into one. The Pareto overlay picks this up for free since it already shares _build_family_color_map with the multi-strategy window-sticker panels. Add lr_angle_opt_p5_job.sh: the LR_PP_angle_opt campaign at p=5 was never run before -- the existing LR_opt_p5/p9 data is LR_PP_opt (ramp-only optimization), which is actually the no-superscript tier, not starred, despite the "_opt" name. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The existing local heavy_hex_144_LR_opt_p9_expanded is a stale partial run (343 rows, a single instance) rather than a full 30-instance campaign. LR_PP_opt is the no-superscript "Linear Ramp" tier (ramp-parameter optimization only, see the LR family split in utils.py); this fills it in at p=9 to match the real hardware comparison, mirroring the already-verified lr_angle_opt_p5_job.sh. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Every shard segfaulted partway through training (after several successful points, per the "TQATrainer ignores params0" warnings preceding each crash) -- deeper than any prior 16-thread shard job. No leak reproduced locally (flat RSS over a 5-point sweep on the real 144-node graph), consistent with a native allocation failure under memory pressure at this depth/thread count rather than a slow leak. Bumped 64/128Gi -> 128/256Gi, threads left at 16. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Colorbar strip: more than 3 method families crammed into a single row squeezed each colorbar's title (usually wider than the bar) into its neighbours, producing overlapping/unreadable text. Wraps onto a second row instead, growing the figure height to make room rather than shrinking the main panels. Also moved "circuit depth p" out of the title and into its own axis label below the tick numbers, so it's clear what those numbers mean rather than floating digits. Verified with rendered test images (6-family window-sticker panel, 6-family Pareto overlay, unchanged single-row 3-family case). linear_ramp_no_opt family mapping: our own zero-training strategy name spells out "linear_ramp" instead of the "LR" prefix every other Linear Ramp config uses, so it fell through both the external QPS label formatter and the internal lookup table unrecognized -- silently landing in the same "LR" (no-opt-tier) family/color as LR_PP_opt instead of its own "LR_dagger" shade. Fixed at the source (_normalise_training_method_to_config now translates it to the canonical LR_no_opt.json name before either lookup runs) and verified against the real external qaoa_parameter_setting label formatter. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
plot_pareto_frontier_overlay's colorbar legend was built from every method family present in the comparison data, even ones that never actually own a segment of the drawn envelope -- a strategy that's always beaten still took up a colorbar. Now computes envelope ownership in a pre-pass and only shows families that actually win somewhere, verified by forcing one strategy to always lose and confirming it drops out of the legend entirely. Also fixed the colorbar row layout itself: rows were sized independently, so a shorter row (e.g. 2 items after wrapping) stretched its colorbars wider than a fuller row's, looking inconsistent. All colorbars are now a uniform width (set by the fuller row), with shorter rows centered instead of stretched. Row-wrap is now a real fits-at-a-readable-width decision (currently 4 per row) rather than a hardcoded ">3" threshold, and both call sites' figure-height sizing now shares that same decision via _family_colorbar_row_count instead of duplicating the threshold (which had already drifted out of sync once this session). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
bernalde
left a comment
There was a problem hiding this comment.
Targeted re-review at 6b8e1fa, superseding my review at f3c36db. Everything I asked for last round is in, CI is green at this head, and the 18 commits since then hold up well except for one accounting defect in the new resume path that I'd like fixed before merge because it feeds the INTERP resource axis.
@anurag-r20 — one Blocking item (inline, run_fa_pss_exact_points), one question on the isotonic fit, and a handful of metadata/pin cleanups.
Resolved since f3c36db
- qiskit-aer guard —
01f19dcapplies the class-levelskipifexactly as prescribed; CI on6b8e1facollects 493 and reports489 passed, 4 skippedon every lane, and I reproduce that locally. The inline thread is addressed and can be resolved. tproxy-doc-inconsistency— cell 8 now carries the INTERP carve-out and the "quantum" typo is fixed.- The base merge, colour-collision fix,
resource_match_binsdocs (#85/#86),ZERO_TRAINING_METHODSdedup, dashed flat-hold andAUDIT_REPORT.mdremoval all still hold at this head. I re-ran the colour mutation: restoring the overlapping FA bands still reddenstest_fa_star_and_fa_dagger_do_not_collide, and the new LR-tier guard behaves the same way (overlappingLR_starback ontoLRreddenstest_lr_star_and_lr_do_not_collide; dropping theLR_daggerbranch reddens three tests). Worst cross-family colour distance over the 11 labels in the notebook is now 0.229.
New since f3c36db — what I reviewed
681e857 (per-grid-point resume), 18cdb90 (linear_ramp_no_opt in run_pt_pss_exact_points), 9fcee97 (isotonic prescription fit, LR tiers), 8c0a346/6b8e1fa (colorbar rows, dynamic Pareto legend), the Nautilus retry/pinning commits, three new LR job scripts, and the notebook re-executed on the I_full_p7 / LR campaigns. The isotonic replacement is guarded: disabling pooling in _pool_adjacent_violators reddens three tests, and dropping the Q-subset check in _cached_fa_grid_point reddens one. The resume path's cost accounting, however, has no test at all — zeroing the carried-over cumulative_shots leaves the suite green — and that is where the defect sits.
1. Blocking — resumed grid points double-count training cost (inline)
Details and fix on simulation_validation.py line 1962. Short version: after a cached (N, M) point the next computed point cold-starts with maxiter = n_value (full budget), but its evaluations, shots and wall-clock are added on top of the cached prefix. For n_values = [10, 20, 30], M = 100, an uninterrupted run reports total_training_shots = 3000 at N = 30; a run resumed after N = 20 reports 5000. training_cost_proxy is unaffected (it is N·M·t_shot analytically), so the exact and proxy columns disagree on resumed rows — and since a609fac made INTERP's T_proxy read total_training_shots directly, any resumed shard in heavy_hex_144_I_full_p7_expanded shifts the Interpolation curve right by the cached prefix.
2. Question — per-column isotonic prescriptions
9fcee97 replaces the degree-2 log-log polynomial with isotonic (PAVA) fits of N(T), M(T) and Q(T) separately. The motivation ("never recommend less as the budget grows") is right for Q, but N and M trade off inside the same budget: a recipe that shifts from few iterations at many shots to many iterations at fewer shots is a legitimate non-monotone M(T), and PAVA will flatten it into a plateau. The committed cell 9 output already shows M(T) collapsing to 73 knots. Is per-column monotonicity intended, or should the constraint be on the budget N·M (monotone product, free split)? Either answer is fine; the methodology cell should state it, and the two comments that still say "continuous polynomial fit" (simulation_validation.py:3579 and the snap_actionable_fit_to_feasible_grid call site) need updating.
3. Nonblocking — the PR body describes an older head
This is the merge record, so it is worth a pass before merge:
- "Stacked on #50 (base retargeted to
QAOA_Parameter_Setting_IBM… 14 files)" — base ismain, #50 is merged, the diff is 18 files. - "
pytest tests/ -q— 360 passed (27 new …)" — at this head CI and my local run give489 passed, 4 skipped(493 collected); the test file has 31 tests. No commit in the branch ever had 27 tests in that file (69c5371had 18). - "
FA_daggermoved offBluesontoReds; also restored one legend handle per method family" — at this head both FA families are onBlues(0.55–0.92/0.15–0.42) andplot_multi_method_window_sticker_component_panelsdeliberately has no per-family handles (families are read off the colorbars). That is the right design; the body just describes the intermediate one. - Not mentioned at all: the isotonic fit change, per-grid-point resume, the three LR tiers,
linear_ramp_no_optinrun_pt_pss_exact_points, the LR p5/p9 and full-recursion campaigns, and the Nautilus retry/pinning work. Those are the changes a reader most needs.
4. Nonblocking — nested-recursion-evaluator-init deferral has no tracker
You deferred the evaluator_init overwrite / .setdefault() questions to a follow-up on 2026-08-15, and last round I asked to fold interp-training-cost-premise into the same follow-up. There is still no issue for it — #85 and #86 are the only open ones. Please open one (a few lines quoting the two questions is enough) and link it from the PR body, so the deferral survives the merge.
5. Nonblocking — INTERP campaign provenance
The notebook now sources Interpolation from heavy_hex_144_I_full_p7_expanded, generated by interp_full_recursion_job.sh pinned to this branch, which carries the MPSAerSampleEvaluator nested-evaluator change — so the sub-depth shots are counted for this data, and the printed multi-strategy table (Interp p=7 at 0.95 s versus 0.08 s for FA* p=7) is consistent with that. That answers last round's question well enough for me. Two lines in the methodology cell naming the campaign and the generator pins (qaoa_training_pipeline v0.1.0, QPS 50a17c6) would make it reproducible without reading the job script; and please confirm no shard of that campaign resumed from a checkpoint (see item 1), or say which rows did.
6. Nonblocking — container paths in committed output
Cell 5's test_specs_df table prints /workspace/data/generated_instances/… and /workspace/workdirs/lr-angle-opt-p5-finalize/… in graph_path/minmax_path. Not a contributor path this time, but the earlier scrubbing pass left the notebook at zero absolute paths and this run reintroduces some; the _relativize_paths helper could drop the /workspace/ prefix the same way it drops WORKSPACE_ROOT.
Tests run and outcomes
At PR head 6b8e1fa, in a clean worktree, repo .venv (Python 3.13, no qiskit-aer):
pytest tests/ -q -rs— 489 passed, 4 skipped (the fourTestBuildBoundCircuitSimulatorcases, skip reason as prescribed); imports resolved to the worktree'sexamples/IBM_QAOA/srcflake8 --select=F401,F841,E9,F63,F7,F82on the four touched Python files — clean;bash -non all changed shell scripts — clean- Mutation probes: PAVA no-op → 3 failed;
_cached_fa_grid_pointsubset check dropped → 1 failed; LR band overlap → 1 failed;LR_daggerbranch removed → 3 failed; resume counter carry-over zeroed → 0 failed (item 1) - Colorbar layout rendered synthetically with 7 families: 2 rows, uniform 0.202 width, titles per family
- Notebook at head: 13 code cells, execution counts 14–26 monotonic, 0 error outputs, 8 figures
- Changed-file set matches GitHub (18/18); merge-base equals
maintipa83d145, so the head is the merge result
GitHub checks on 6b8e1fa (run 32594745628): test (3.10/3.11/3.12), tutorial-smoke, integration-tests, coverage-summary — all SUCCESS, none skipped. No sibling open PRs touch these files.
Summary
Blocking: 1 (inline) · Nonblocking: 7 (3 inline, 4 in this body) · Questions: 1 · Total: 9
I would not merge this until the blocking issues above are addressed. Item 1 is a small, local fix plus a test; once it lands and the body is refreshed I expect to approve.
…ic recipe fit with monotonic power law run_fa_pss_exact_points cold-starts the next computed point after a cached grid-point skip (trains the full n_value budget from scratch), but its cumulative cost counters were still seeded from the cached row -- stacking the fresh cold-start cost on top of a carried-over prefix and double-counting it. Extracted the reset into a small pure helper (_reset_cumulative_cost_on_cold_start) and added tests, including a mutation-tested repro of the reported n_values=[10,20,30] scenario. Since INTERP's T_proxy reads total_training_shots directly, this affects the Interpolation resource axis for any resumed shard. Also replaces _fit_log_isotonic (PAVA) with _fit_log_power_law for fit_recommended_recipe_curves: PAVA pools every locally-nonmonotone step into a flat block, which collapses parameters like M(T) into wide plateaus when many (N, M) splits tie or trade off at nearby budgets even though the overall scaling trend is smooth. A global log-log OLS fit with the slope clipped to >=0 keeps the monotonicity guarantee without pooling. Removed the now-dead _pool_adjacent_violators/_fit_log_isotonic and their tests, added tests for the new fit (exact power-law recovery, negative-trend clipping, locally-nonmonotone-but-globally-trending data, degenerate/empty input). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Four YAML shard manifests still cloned anurag-r20/stochastic-benchmark at
QAOA_Parameter_Setting_IBM, a merged and no-longer-updated branch on a
personal fork -- point them at usra-riacs/stochastic-benchmark main instead,
matching interp_full_recursion_job.sh's existing fix for the same issue.
The four *_job.sh scripts (12 env blocks total) pinned
IBM_QAOA_audit_recursion_and_paper_draft, which is deleted once this PR
merges; switched the default to main (lr_no_opt_job.sh keeps it overridable
via ${STOCHASTIC_BENCHMARK_BRANCH:-main} for testing against an unmerged
branch).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ovenance note utils.py's resource-axis label hardcoded "NMt_shot" in four places, but INTERP now reads t_train from total_training_shots instead of N*M -- generalized to "t_train" so the axis label matches what's actually plotted. _relativize_paths (notebook) only stripped WORKSPACE_ROOT, so paths from a Nautilus-generated campaign (e.g. /workspace/data/generated_instances/...) were printed verbatim in committed output; now also strips the /workspace/ container mount prefix. Added a provenance note to the T_proxy methodology cell naming the campaign (heavy_hex_144_I_full_p7_expanded) and generator pins (qaoa_training_pipeline v0.1.0, QPS 50a17c6) backing the Interpolation curve. Also carries forward this session's plotting fixes: per-strategy-family colorbar coloring and legend text for the best-bitstring hardware overlay, uniform single-row colorbar width/fontsize across the window-sticker multi-plots, and moving PLOT_DIR to notebooks/plots. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…mulated curves; restyle The best-bitstring hardware overlay compared real-hardware dur_mean (raw, even-split-per-job JSON-derived QPU_time) against simulated curves whose resource axis is rescaled by the paper-measured shot rate (arXiv:2606.05311v1) -- an axis-basis mismatch. Now recalibrates QPU_time per hardware row as num_shots * t_shot_new(p), reusing the same _t_calib_noise_corrected values the simulated Noise-Corrected curve already uses; total_train_cost (real classical wall-clock time) is left unscaled since it isn't shot-rate dependent. Confirmed empirically that classical training cost dominates the real-hardware resource budget (QPU_time is ~0.3% of total for a representative point), so the recalibration is correct but has minimal visible effect on this dataset. Also: data-driven axis limits (first/last plotted point on the border, no matplotlib auto-margin), removed the plot title, grew the figure so the plot area doesn't shrink as legend/colorbar content grows, and settled on dense round dots (with a white halo for contrast against nearby simulated curves) for the single remaining hardware curve after iterating through several marker/linestyle options. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Verification pass at @anurag-r20 — remaining before I can approve: 1. The resume fix's regression test doesn't actually guard it (open Blocking thread on 2. PR body still describes an older head. It opens "Stacked on #50 (base retargeted to 3. No tracking issue for the 4. Did any 5. Contributor path is back in committed output. Cell 7's stderr prints 6. The hardware-overlay recalibration in Items 1–5 are small and well-defined. Once they're in I'll re-review, which is also what clears the standing changes-requested gate. |
…warnings; plot polish - test_resumed_run_matches_uninterrupted_run_at_same_grid_point's mirror (_simulate_grid_shots) never seeded cumulative_shots in its cached-point branch, so the double-counting scenario it claims to reproduce couldn't actually occur: the test stayed green with the production _reset_cumulative_cost_on_cold_start call deleted entirely. Now seeds cumulative_shots = n_value * m_value on a cached skip (what that row actually carries), which makes the reset call's effect load-bearing -- verified by mutation-testing the deleted-call case, which now correctly fails (5000 != 3000, the bug's exact signature). - warnings.warn's default formatter prints the absolute source-file path of the call site (e.g. src/interpolate.py's duplicate-resource warning), bypassing the existing _relativize_paths dataframe scrubbing entirely. Added a warnings.formatwarning override in the notebook's config cell that relativizes the filename the same way (relative to WORKSPACE_ROOT, or strip a container /workspace/ prefix). - Continued iterating on the simulated-vs-real-hardware Pareto overlay plot's styling: point+line markers (one per Pareto-frontier segment, placed where a strategy takes over) with 1-SEM error-bar whiskers on both the simulated and real-hardware curves, matching marker sizing to Analysis.ipynb's own recommendation-plot convention, and moving its legend inline (bottom-right, inside the axes) to free up plot area. Same marker/error-bar treatment applied to plot_pareto_frontier_overlay in src/utils.py so it's consistent across both Pareto-style plots. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Addressed items 1, 2, 3, 5 directly; answering 4 and 6 here. 1. Resume-fix regression test. Fixed per your exact suggestion: 2. PR body. Rewritten — correct base ( 3. Tracking issue. Opened #87 and linked it from the PR body. 4. Did any 5. Contributor path leak. Added a 6. Hardware-overlay numbers. For |
Fresh top-to-bottom run: 14/14 code cells executed, zero error outputs, monotonically increasing execution counts (1-14). Picks up committed outputs for the simulated-vs-real-hardware Pareto overlay and the noiseless/noise-corrected calibration overlay, including today's marker/error-bar/legend/layout work. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
bernalde
left a comment
There was a problem hiding this comment.
Final review at 2d033bc, superseding my review at 6b8e1fa. Every item from the previous round and the follow-up comment is addressed at this head, CI is green, and the two surfaces no round had judged yet — the CI-whisker/marker machinery in 93a778a and the hardware-overlay recalibration cell — both hold up. Approving, with three nonblocking notes for the record.
Verified since 6b8e1fa
- Resume double-count fix (
32aa0d4) — correct at head. The mirror test is no longer vacuous: with_reset_cumulative_cost_on_cold_startmutated to a no-op, bothtest_cold_start_resets_to_zeroandtest_resumed_run_matches_uninterrupted_run_at_same_grid_pointnow fail (previously only the first did). See note 1 below for the one caveat. - Power-law fit — slope-clip guard holds (removing
max(slope, 0.0)reddenstest_negative_trend_is_clipped_to_flat); stale "polynomial fit" comments updated. - Pins (
c4ee51e) — zeroanurag-r20/QAOA_Parameter_Setting_IBM/ PR-branch references remain undernautilus/;lr_no_opt_job.shuses the overridable${STOCHASTIC_BENCHMARK_BRANCH:-main}form. - Axis labels, provenance, warning scrub (
c1058a8,93a778a) — all fourt_trainlabels in place; provenance note names the campaign and pins;_scrubbed_formatwarningverified working: the re-executed notebook has zero absolute paths in any output (was leaking/mnt/c/Users/...via the warning formatter), 0 error outputs, monotonic execution counts. - Campaign regeneration — the committed notebook reflects the regenerated
heavy_hex_144_I_full_p7_expandeddata (Interp p=7T_proxymoved 0.9519 → 0.9933 s). The 10/210-tainted-columns finding and the zero-inflated-rows check on the regenerated data are author-verified;results/is not committed, so I could not reproduce them independently. - CI-whisker/marker code (
93a778a) — reviewed in full._pareto_envelope_boundsreusesbest_idxso whiskers always match the owning entry; whiskers are drawn only at segment starts, where the flat-hold subtlety cannot bite; the NaN-CI path is handled; the/1.96rescale to 1 SEM is verified against the producer (response_lower/upper = response ± 1.96·SEMatutils.py:2857) and matches the hardware curve'sdur_sem/ar_sembasis. The index-based unpack in_pareto_envelope_and_owneris backward-compatible (existing 4-tuple callers and tests pass). - Hardware-overlay recalibration (
8724c54+ cell 20) — the axis-basis fix is right:num_shots × t_calib(job_p)puts hardware QPU time on the same paper-measured shot-rate basis the simulated curves already use, while classical training wall-clock is correctly left unscaled. Your numbers (QPU 1.163 → 4.977 s,dur_mean404.99 → 408.81 s, +0.94%) are internally consistent with that. The best-bitstring AR over the counts histogram matches this notebook's ownBestApproximationRatiodefinition, and the committed cell rendered cleanly. One accepted methodology note: the hardware x-axis (training + QPU execution) and the simulatedT_proxy(t_preprocessing + t_train + Q·t_shot) are analogous rather than identical resource definitions — fine for a validation overlay, worth a phrase in the paper text when this figure is used.
Nonblocking notes
1. The production call site of the reset helper is still unguarded
Deleting the _reset_cumulative_cost_on_cold_start call inside run_fa_pss_exact_points leaves the entire suite green — the mirror test exercises its own copy of the control flow, so it structurally cannot bind the production call (this also means the address comment's claim that this mutation "correctly fails the scenario test" doesn't hold; no such mirror-style test can). To be clear, the mirror-seeding fix is exactly what I prescribed, so the limitation is mine as much as anyone's. Closing it for real means a test that stubs the heavy calls (run_method_from_config, sample_bound_circuit_memory) and drives run_fa_pss_exact_points itself with a cached existing_df — reasonable to defer; a line in #87 or a small follow-up issue would keep it from being forgotten.
2. Two small inaccuracies in the rewritten PR body
- "+8824/−4922" — the PR now reports +8803/−4888 (the counts moved with the final re-exec commit).
- "one legend handle per method family alongside the per-family colorbars" — true of the older
plot_multi_method_window_sticker_componentsfigure, but the multi-strategy panels figure deliberately has none (utils.py:3749: families are read off the colorbars, which is the better design). Suggest "…; method families are identified by the per-family colorbars."
Fine to fix in a body edit without a new commit.
3. _pareto_envelope_bounds and the marker path have no tests; two stale comments in cell 20
The +123 lines in utils.py came with no unit tests (the test delta was the mirror seeding only). Two cheap ones would pin the contract: whiskers-match-owner (a two-entry crossover where the CI band must switch with best_idx) and the NaN-CI passthrough. Also, cell 20's prose says "One per calibration" and describes a "Noiseless-calibrated hardware curve", but the code builds and draws only the Noise-Corrected hardware frontier — trim the comment or add the second curve. Neither needs to gate this PR.
Tests run and outcomes
At PR head 2d033bc, in a byte-verified git archive copy, repo .venv (Python 3.13, no qiskit-aer):
pytest tests/ -q— 494 passed, 4 skipped (498 collected), matching CI exactly; the 4 skips are the intended qiskit-aer guard- Mutation probes: reset helper no-op → 2 failed (both resume tests); slope clip removed → 1 failed; production reset call deleted → 0 failed (note 1); source restored byte-clean after each
- Notebook audit: 0 error outputs, execution counts 1–14 monotonic, 0 absolute paths, provenance note present, cell 20 figure rendered and saved
- Changed-file set matches GitHub (18/18); merge base equals
maintipa83d145, so the head is the merge result - Issue #87 exists and cross-links both ways with the PR body
GitHub checks on 2d033bc (run 33447090785): test (3.10/3.11/3.12), tutorial-smoke, integration-tests, coverage-summary — all SUCCESS, none skipped. No other open PRs.
Summary
Blocking: 0 · Nonblocking: 3 (in this body) · Questions: 0 · Total: 3
Merge-ready. This approval supersedes my earlier requests for changes; the nonblocking notes above can land as a body edit and a line in #87, before or after merge at your discretion.
Summary
Based on
main(18 files, +8824/-4922).src/simulation_validation.py,5 from
src/utils.py) with zero remaining callers anywhere in the tree —backend-choice suite, hardware-alignment stats, WS campaign builders,
unused plotting helpers. Verified this doesn't affect reproducing
Analysis.ipynborSimulation_Method_Validation_and_WS.ipynb.prepare_training_bricks_data: apositional
.sem().valuesassignment is replaced with an explicit mergeon
["job_p", "method_base"].run_fa_pss_exact_points: after acached/resumed grid point is skipped, the next actually-computed point
cold-starts (trains its full budget from scratch, since warm-start angles
aren't cached) but its own full cost was being stacked on top of the
cumulative counters carried over from the cached row, inflating
total_training_shotsfor every point downstream of a resume. Fixed via_reset_cumulative_cost_on_cold_start, guarded by a scenario testreproducing the exact repro from review (
n_values=[10,20,30], resumedafter a cached N=20 gives the same cumulative total as an uninterrupted
run, not the inflated 5000 vs. the correct 3000). The one committed
campaign this affected (
heavy_hex_144_I_full_p7_expanded, Interp/I_MPSAer) was confirmed to have 10/210 resumed grid columns carrying the
inflated prefix and was regenerated end-to-end with the fix; the
regenerated data was verified clean (zero inflated-offset rows across all
30 instances × 343 grid columns).
snap_actionable_fit_to_feasible_grid's fit for per-(N,M,Q) actionablecurves is now a monotonic (non-decreasing) log-log power law instead of
isotonic regression (PAVA): PAVA pools locally-nonmonotone points into
flat plateaus, which collapses parameters like M(T) — where many (N,M)
splits trade off at similar budgets — into a plateau that doesn't track
the real scaling trend. The power-law fit is per-(N,M,Q) column (not
pooled across columns) and clips its fitted slope to ≥0.
build_bound_circuit_simulator: builds oneAerSimulatorfor reuseacross an (N, M) grid sweep instead of rebuilding it per iteration.
generate_pss_exact_pointsis now batched(
_CHECKPOINT_BATCH_SIZE = 10) instead of writing on every new row.line_to_fullinstance generation now symmetrizes its random sign matrix(was silently asymmetric).
run_stochastic_benchmark_pssgainsresource_match_bins(default 150),which snaps
sb.interp_resultsonto a coarser shared grid beforerecipe/evaluate matching. This changes campaign results by default: it
works around
training.evaluate_singlematching resource values by exactfloat equality (train/test instances land on independent resource grids,
so without snapping the overlap is small and arbitrary from run to run).
Tracked as a follow-up against the core package in training.evaluate_single matches resource values by exact float equality #85; the unseeded
bootstrap resampling this interacts with is tracked in Bootstrap resampling is unseeded, making campaign re-runs non-reproducible #86.
run_job.sh's zero-training method routing now readsZERO_TRAINING_METHODSout ofsrc.simulation_validationat run timeinstead of maintaining a second copy of the list in shell.
at p=7 and p=9 (sharded runs, cleanup/finalize/setup jobs), an LR
angle-optimization tier at p=5/p=9, a
linear_ramp_no_opt(nore-optimization) tier wired through
ZERO_TRAINING_METHODS, andextended-Q-grid FA_no_opt/PT re-run scripts.
overlay cells to
Simulation_Method_Validation_and_WS.ipynb. Fixed-Angles*and Fixed-Angles† both stay on
plt.cm.Blueswith disjoint bands (0.55–0.92 vs. 0.15–0.42) rather than colliding on overlapping stretches of the
same colormap; Param. Transfer uses Greys, the Linear Ramp tiers use
YlOrBr, Interp uses Greens — one legend handle per method family
alongside the per-family colorbars. The
extend_curves_to_xlimflat-holdsegment is drawn dashed at half alpha so it isn't mistaken for measured
data.
simulated actionable-prescription frontier against real
ibm_bostonexecution on the same ten test instances: the hardware curve's QPU
execution time is recalibrated onto the identical paper-measured
shot-rate basis (
arXiv:2606.05311v1) the simulated Noiseless/Noise-Corrected curves already use, rather than the raw, even-split-
per-job JSON-derived QPU time — only QPU time is rescaled, since
classical training cost isn't shot-rate dependent. (QPU time turns out to
be under 1% of total real-hardware resource cost for a representative
point, so the recalibration is correct but doesn't move the curve much;
classical training dominates the real-hardware budget.)
warnings.warn's default formatter prints the absolute source-file pathof the call site, bypassing the existing dataframe-path scrubbing
entirely (e.g.
src/interpolate.py's duplicate-resource warning leaked acontributor's local path into committed cell output). Added a
warnings.formatwarningoverride in the notebook's config cell thatrelativizes the filename the same way the dataframe scrubbing does.
Does not include
AUDIT_REPORT.mdor the paper draft — dropped perreview feedback (the audit's findings are already reflected in the diff
itself, and a point-in-time audit shipped alongside its own fixes goes
stale on arrival; the paper draft was withdrawn separately).
Deferred
evaluator_initoverwrite vs..setdefault()question, deferred 2026-08-15 — tracked in RecursionTrainer nested evaluator_init: resolve overwrite vs .setdefault() semantics #87.
Test plan
pytest tests/ -q— 498 collected (494 pass + 4 conditionally skip ina
qiskit-aer-less environment; all 498 pass locally withqiskit-aerinstalled)flake8 --select=F401,F841,E9,F63,F7,F82on touched Python files — clean(
5000 != 3000) with the production reset call deletedheavy_hex_144_I_full_p7_expandedend-to-end with theresume-double-counting fix; verified clean across all 30 instances
Simulation_Method_Validation_and_WS.ipynbre-executed end-to-endwith all plotting/layout changes: 14/14 code cells run, zero error
outputs, monotonically increasing execution counts (1-14)