From 30cd9969f72d9ca7e5ea4619cf50a10f7e19a0f3 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 1 Oct 2026 13:09:55 +0000 Subject: [PATCH 1/4] [na] Score time and error-budget with cost weights The placeholder time objective and error-budget mode both read the target cost_model. A stage-weight change, with the other weights held fixed, flips a verified schedule, and each verified total is lower under the weights that chose it. qaoa_graph.qn still verifies; its resource report matches that schedule. Refs #484 Co-authored-by: Arnab Ghosh --- quon_na/src/objective.rs | 205 +++++++++++++++++- quon_na/src/pipeline.rs | 18 +- quon_na/src/plan.rs | 60 ++--- quonc/src/compile.rs | 5 +- quonc/src/main.rs | 6 +- quonc/tests/na_showcase.rs | 22 +- .../qaoa_graph_zoned.resource_report.json | 22 +- 7 files changed, 267 insertions(+), 71 deletions(-) diff --git a/quon_na/src/objective.rs b/quon_na/src/objective.rs index a80c3f2..0ac3b76 100644 --- a/quon_na/src/objective.rs +++ b/quon_na/src/objective.rs @@ -27,9 +27,8 @@ use crate::geometry::{SpeedModel, movement_duration_for_model}; /// Placeholder weights shipped on the checked-in neutral-atom targets. /// /// architecture_model.md §8.6 / §9: these are tuning knobs, not measurements. -/// The time-shaped placer keeps its existing choice when a target still -/// carries exactly this vector. Any other vector is scored with the formula -/// above. +/// The time objective and error-budget mode both score with the target's +/// `cost_model`, including when that vector is exactly this placeholder. pub const PLACEHOLDER_COST_WEIGHTS: NeutralAtomCostModel = NeutralAtomCostModel { rydberg_stage_weight: 1.0, movement_time_weight: 1.0, @@ -1485,4 +1484,204 @@ mod tests { (spec, movement_time_us, objective) } } + + /// The time objective and error-budget mode both read `cost_model`. + /// Movement, transfer, and idle stay fixed and positive. Only the stage + /// weight changes, and each verified total is lower under the weights + /// that chose it. The error model stays the loaded target's model. + #[cfg(feature = "mlir")] + #[test] + fn time_and_error_budget_objectives_read_stage_weight() { + use std::path::Path; + + use crate::entangling_schedule::schedule_entangling_layers; + use crate::geometry::movement_duration_for_model; + use crate::graph::{ + DEFAULT_GAMMA, Interaction, InteractionGraph, InteractionId, InteractionSegment, + LogicalQubitId, SegmentKind, + }; + use crate::layout::{AtomBinding, AtomId, NeutralAtomLayout, SiteId, TrapBinding}; + use crate::lower::{ScheduleLowerParams, lower_schedule}; + use crate::pipeline::{NaObjective, NaScheduleOptions, zoned_architecture}; + use crate::plan::plan_backend; + use crate::replay::{DeclaredArchitecture, verify_emitted_schedule}; + use crate::schedule_entry::schedule_from_graph; + use backend::{NeutralAtomZone, ZoneKind}; + + let column_pitch_um = 400.0_f64; + let row_pitch_um = 10.0_f64; + let movement_weight = 1.0; + let transfer_weight = 1.0; + let idle_weight = 1.0; + let probe = fixture_target(weights(1.0, movement_weight, transfer_weight, idle_weight)); + let arch = zoned_architecture(&probe); + let travel_um = (column_pitch_um * column_pitch_um + row_pitch_um * row_pitch_um).sqrt(); + let movement_us = movement_duration_for_model(travel_um, &arch.speed_model) as f64; + let trap_us = arch.trap_transfer_us as f64; + let transfers = 4.0; + let stationary_idle_us = 2.0 * (movement_us + 2.0 * trap_us); + let placer_long_move = movement_weight * movement_us + + transfer_weight * transfers + + idle_weight * stationary_idle_us; + let verified_long_move = placer_long_move - idle_weight * 4.0; + let stage_below = verified_long_move - 1.0; + let stage_above = placer_long_move + 1.0; + assert!(stage_below > 0.0); + let below = weights(stage_below, movement_weight, transfer_weight, idle_weight); + let above = weights(stage_above, movement_weight, transfer_weight, idle_weight); + + for objective in [NaObjective::Time, NaObjective::ErrorBudget] { + let (split_spec, split_obj) = scheduled(objective, below); + let (kept_spec, kept_obj) = scheduled(objective, above); + assert_eq!(split_obj.rydberg_stages, 2, "{objective:?}"); + assert_eq!(kept_obj.rydberg_stages, 1, "{objective:?}"); + assert_eq!(split_obj.movement_time_us, 0); + assert_eq!(kept_obj.movement_time_us as f64, movement_us); + assert_eq!( + split_obj.movement_time_weight, + kept_obj.movement_time_weight + ); + assert_eq!( + split_obj.trap_transfer_weight, + kept_obj.trap_transfer_weight + ); + assert_eq!(split_obj.idle_time_weight, kept_obj.idle_time_weight); + assert!(split_obj.movement_time_weight > 0.0); + assert!(split_obj.trap_transfer_weight > 0.0); + assert!(split_obj.idle_time_weight > 0.0); + let kept_under_low = + objective_from_verified_schedule(&kept_spec, &below).expect("rescore"); + let split_under_high = + objective_from_verified_schedule(&split_spec, &above).expect("rescore"); + assert!(split_obj.total < kept_under_low.total); + assert!(kept_obj.total < split_under_high.total); + } + + fn fixture_target(cost: NeutralAtomCostModel) -> backend::NeutralAtomTarget { + let path = Path::new(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../targets/neutral_atom/generic_rna_v0.json" + )); + let loaded = backend::json::load(path).expect("load target"); + let mut na = loaded.neutral_atom_target().expect("na target").clone(); + assert!(na.error_model.is_some()); + na.zones = vec![ + NeutralAtomZone { + zone_id: 0, + kind: ZoneKind::Storage, + rows: 2, + cols: 2, + origin_um: (-100.0, -100.0), + site_pitch_um: (4.0, 4.0), + pair_gap_um: None, + }, + NeutralAtomZone { + zone_id: 1, + kind: ZoneKind::Entanglement, + rows: 2, + cols: 2, + origin_um: (0.0, 0.0), + site_pitch_um: (400.0, 10.0), + pair_gap_um: Some(6.0), + }, + ]; + na.cost_model = cost; + na + } + + fn scheduled( + objective: NaObjective, + cost: NeutralAtomCostModel, + ) -> (crate::dialect::ScheduleSpec, ScheduleObjective) { + let na = fixture_target(cost); + let arch = zoned_architecture(&na); + let left = InteractionId(0); + let right = InteractionId(1); + let graph = InteractionGraph::from_interactions( + vec![ + LogicalQubitId(0), + LogicalQubitId(1), + LogicalQubitId(2), + LogicalQubitId(3), + ], + vec![ + Interaction { + id: left, + qubits: vec![LogicalQubitId(0), LogicalQubitId(1)], + gate_name: "CZ".into(), + dag_layer: 0, + on_critical_path: false, + }, + Interaction { + id: right, + qubits: vec![LogicalQubitId(2), LogicalQubitId(3)], + gate_name: "CZ".into(), + dag_layer: 0, + on_critical_path: false, + }, + ], + vec![InteractionSegment { + kind: SegmentKind::CommutationGroup, + interactions: vec![left, right], + }], + DEFAULT_GAMMA, + ) + .expect("graph"); + let mut request = + schedule_entangling_layers(schedule_from_graph(graph).expect("request"), 340) + .expect("layers") + .request; + request.layout = Some(NeutralAtomLayout { + sites: Vec::new(), + initial_bindings: vec![ + AtomBinding { + atom: AtomId(0), + trap: TrapBinding::Slm { site: SiteId(4) }, + }, + AtomBinding { + atom: AtomId(1), + trap: TrapBinding::Slm { site: SiteId(5) }, + }, + AtomBinding { + atom: AtomId(2), + trap: TrapBinding::Slm { site: SiteId(8) }, + }, + AtomBinding { + atom: AtomId(3), + trap: TrapBinding::Slm { site: SiteId(9) }, + }, + ], + declared_initial_bindings: Vec::new(), + }); + let opts = NaScheduleOptions { + objective, + ..NaScheduleOptions::default() + }; + let (scheduled, _) = plan_backend(request, &na, opts).expect("plan"); + let layout = scheduled.layout.as_ref().expect("layout"); + let spec = lower_schedule( + &scheduled, + &ScheduleLowerParams { + target_id: "objective-weights".into(), + rydberg_range_um: arch.rydberg_range_um, + min_rydberg_spacing_um: arch.min_rydberg_spacing_um, + aod_min_separation_um: arch.aod_min_separation_um, + }, + ) + .expect("lower"); + let declared = DeclaredArchitecture { + sites: layout.sites.clone(), + initial_bindings: layout.declared_initial_bindings.clone(), + zones: arch.zones.clone(), + check_zones: true, + require_readout_zone: false, + rydberg_range_um: arch.rydberg_range_um, + min_rydberg_spacing_um: arch.min_rydberg_spacing_um, + aod_min_separation_um: arch.aod_min_separation_um, + }; + verify_emitted_schedule(&spec, &declared).expect("verified schedule"); + let objective = objective_from_verified_schedule(&spec, &cost).expect("objective"); + (spec, objective) + } + } } diff --git a/quon_na/src/pipeline.rs b/quon_na/src/pipeline.rs index 746d886..4a71804 100644 --- a/quon_na/src/pipeline.rs +++ b/quon_na/src/pipeline.rs @@ -88,12 +88,11 @@ pub enum StatePrepMode { /// Placement/routing objective (issue #309). /// -/// `Time` (default) minimizes the time-shaped RAP Eq. (1) cost `Σ √(d_max)`. -/// `ErrorBudget` minimizes analytic error-model contributions instead — -/// `rate × count` (ADR-0017/0020), not logical error rates or thresholds. -/// -/// Requesting `ErrorBudget` on a target without an `error_model` is a hard -/// error (fail-closed, mirroring `--emit-resource-report` discipline). +/// `Time` (default) and `ErrorBudget` both score with the target's +/// `cost_model` weights. `ErrorBudget` still requires an `error_model` and +/// fails closed without one (mirroring `--emit-resource-report` discipline). +/// The analytic `error_model` rates stay on the resource report; they are +/// not the placement score. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "snake_case")] pub enum NaObjective { @@ -120,10 +119,9 @@ pub struct NaScheduleOptions { /// `Exact` uses the z3-backed exact state-prep scheduler when the /// `solver` feature is enabled, falling back to heuristic on timeout. pub state_prep: StatePrepMode, - /// Placement/routing objective (issue #309): `Time` (default) minimizes - /// the RAP Eq. (1) time-shaped cost; `ErrorBudget` minimizes analytic - /// error-model contributions instead (ADR-0017/0020). Requires the - /// target's `error_model` — requesting it without one is a hard error. + /// Placement/routing objective. `Time` (default) and `ErrorBudget` both + /// score with the target's `cost_model` weights. `ErrorBudget` requires + /// the target's `error_model` — requesting it without one is a hard error. pub objective: NaObjective, } impl Default for NaScheduleOptions { diff --git a/quon_na/src/plan.rs b/quon_na/src/plan.rs index 4e2f8bf..7ca2170 100644 --- a/quon_na/src/plan.rs +++ b/quon_na/src/plan.rs @@ -110,25 +110,24 @@ pub fn plan_backend( ); } let arch = zoned_architecture(na); - // Issue #309: construct the placement cost model from the - // objective and target's error_model. Fail-closed if - // ErrorBudget is requested without an error_model (mirrors - // --emit-resource-report discipline, ADR-0017). + // Both objectives score with the target's `cost_model` weights. + // ErrorBudget still fails closed without an `error_model` + // (mirrors --emit-resource-report discipline, ADR-0017). let cost_model = match opts.objective { - crate::pipeline::NaObjective::Time => placement_cost_for_time_objective( + crate::pipeline::NaObjective::Time => placement_cost_from_target_weights( &na.cost_model, arch.speed_model, arch.trap_transfer_us, ), crate::pipeline::NaObjective::ErrorBudget => { - let model = na - .error_model - .as_ref() - .ok_or(NaPipelineError::MissingErrorModelForObjective)?; - PlacementCostModel::ErrorBudget { - model: *model, - speed_model: arch.speed_model, + if na.error_model.is_none() { + return Err(NaPipelineError::MissingErrorModelForObjective); } + placement_cost_from_target_weights( + &na.cost_model, + arch.speed_model, + arch.trap_transfer_us, + ) } }; let stage_started = Instant::now(); @@ -384,22 +383,20 @@ impl QecStageAccumulator { } } -/// Time-objective placer. The checked-in placeholder weights keep the -/// hard-coded RAP time choice. Any other `cost_model` vector is scored with -/// the §9 weights. -fn placement_cost_for_time_objective( +/// Placement score for the time objective and for error-budget mode. +/// +/// Both read the target's `cost_model`, including the checked-in placeholder +/// vector. That vector is a weight setting, not a switch back to the RAP +/// time cost. +fn placement_cost_from_target_weights( weights: &backend::NeutralAtomCostModel, speed_model: crate::geometry::SpeedModel, trap_transfer_us: u64, ) -> PlacementCostModel { - if *weights == crate::objective::PLACEHOLDER_COST_WEIGHTS { - PlacementCostModel::Time - } else { - PlacementCostModel::Weighted { - weights: *weights, - speed_model, - trap_transfer_us, - } + PlacementCostModel::Weighted { + weights: *weights, + speed_model, + trap_transfer_us, } } @@ -408,16 +405,19 @@ mod tests { use super::*; #[test] - fn time_objective_reads_non_placeholder_cost_weights() { + fn time_and_error_budget_dispatch_reads_cost_weights() { let speed = crate::geometry::SpeedModel::default(); - assert_eq!( - placement_cost_for_time_objective(&crate::PLACEHOLDER_COST_WEIGHTS, speed, 15), - PlacementCostModel::Time - ); + let placeholder = + placement_cost_from_target_weights(&crate::PLACEHOLDER_COST_WEIGHTS, speed, 15); + assert!(matches!( + placeholder, + PlacementCostModel::Weighted { weights, trap_transfer_us: 15, .. } + if weights == crate::PLACEHOLDER_COST_WEIGHTS + )); let mut weights = crate::PLACEHOLDER_COST_WEIGHTS; weights.trap_transfer_weight = 10.0; assert!(matches!( - placement_cost_for_time_objective(&weights, speed, 15), + placement_cost_from_target_weights(&weights, speed, 15), PlacementCostModel::Weighted { weights: got, trap_transfer_us: 15, .. } if got == weights )); } diff --git a/quonc/src/compile.rs b/quonc/src/compile.rs index c9ca26b..105dde7 100644 --- a/quonc/src/compile.rs +++ b/quonc/src/compile.rs @@ -68,8 +68,9 @@ pub struct CompileRequest { /// State-preparation scheduling mode (issue #302): `Heuristic` (default) /// or `Exact` (SMT-optimal, requires `solver` feature). pub na_state_prep: quon_na::pipeline::StatePrepMode, - /// Placement/routing objective (issue #309): `Time` (default) or - /// `ErrorBudget` (requires target `error_model`; fail-closed otherwise). + /// Placement/routing objective: `Time` (default) or `ErrorBudget`. Both + /// score with the target `cost_model` weights. `ErrorBudget` requires + /// the target `error_model` and fails closed without one. pub na_objective: quon_na::pipeline::NaObjective, /// Parse the source as OpenQASM 2/3 (#304) instead of Quon (`.qn`). /// Set by the CLI when the input is `.qasm` or `--from-qasm` is passed; diff --git a/quonc/src/main.rs b/quonc/src/main.rs index e5fe904..208b055 100644 --- a/quonc/src/main.rs +++ b/quonc/src/main.rs @@ -283,9 +283,9 @@ struct Cli { value_parser = parse_state_prep_mode )] na_state_prep: StatePrepMode, - /// Placement objective: time (default, minimizes Σ √(d_max)) or - /// error-budget (minimizes analytic error_model contributions — - /// requires the target's error_model; ADR-0017/0020, not logical rates) + /// Placement objective: time (default) or error-budget. Both score with + /// the target cost_model weights. error-budget requires the target's + /// error_model. #[arg( long, value_name = "OBJECTIVE", diff --git a/quonc/tests/na_showcase.rs b/quonc/tests/na_showcase.rs index 816de0d..c70b7fc 100644 --- a/quonc/tests/na_showcase.rs +++ b/quonc/tests/na_showcase.rs @@ -284,28 +284,24 @@ fn placer_headline_numbers_match_readme() { // `measurement_rounds` was 0 and the ~1500us readout never entered // `total_time_us`. That fix is the pre-residency baseline. `generic_rna_v0` // declares a readout zone, so the zoned backend now shuttles those atoms - // before the measure. `qaoa_graph.qn` measures 4 atoms: agnostic needs - // three AOD groups (81/8/22/3186 -> 90/11/30/3887) and aware needs one - // (84/9/24/3242 -> 87/10/32/3473). `ising.qn` measures 6 atoms that share + // before the measure. `qaoa_graph.qn` measures 4 atoms. With the target + // cost_model weights, both placers land on 92 cycles, 11 rearrangement + // steps, 30 transfers, and 3693 µs. `ising.qn` measures 6 atoms that share // one group on both placers (49/9/20/3717 -> 52/10/32/3956). let m = &qaoa_agnostic["metrics"]; - assert_eq!(m["estimated_cycles"], 90, "qaoa agnostic: {qaoa_agnostic}"); + assert_eq!(m["estimated_cycles"], 92, "qaoa agnostic: {qaoa_agnostic}"); assert_eq!( m["rearrangement_steps"], 11, "qaoa agnostic: {qaoa_agnostic}" ); assert_eq!(m["trap_transfers"], 30, "qaoa agnostic: {qaoa_agnostic}"); - assert_eq!(m["total_time_us"], 3887, "qaoa agnostic: {qaoa_agnostic}"); + assert_eq!(m["total_time_us"], 3693, "qaoa agnostic: {qaoa_agnostic}"); let m = &qaoa_aware["metrics"]; - assert_eq!(m["estimated_cycles"], 87, "qaoa aware: {qaoa_aware}"); - assert_eq!(m["rearrangement_steps"], 10, "qaoa aware: {qaoa_aware}"); - assert_eq!(m["trap_transfers"], 32, "qaoa aware: {qaoa_aware}"); - // 1764 -> 1742 under #297 (same step/transfer counts, corrected - // grouped-cost search finds a lower-travel-distance placement), then - // +1500us for the terminal-measurement fix, then +231us for the one - // 4-atom readout grab above. - assert_eq!(m["total_time_us"], 3473, "qaoa aware: {qaoa_aware}"); + assert_eq!(m["estimated_cycles"], 92, "qaoa aware: {qaoa_aware}"); + assert_eq!(m["rearrangement_steps"], 11, "qaoa aware: {qaoa_aware}"); + assert_eq!(m["trap_transfers"], 30, "qaoa aware: {qaoa_aware}"); + assert_eq!(m["total_time_us"], 3693, "qaoa aware: {qaoa_aware}"); let agnostic_m = &ising_agnostic["metrics"]; let aware_m = &ising_aware["metrics"]; diff --git a/samples/visualization/goldens/na_schedule_metrics/qaoa_graph_zoned.resource_report.json b/samples/visualization/goldens/na_schedule_metrics/qaoa_graph_zoned.resource_report.json index 1a779df..25db291 100644 --- a/samples/visualization/goldens/na_schedule_metrics/qaoa_graph_zoned.resource_report.json +++ b/samples/visualization/goldens/na_schedule_metrics/qaoa_graph_zoned.resource_report.json @@ -1,9 +1,9 @@ { "evidence_kind": "analytic", "evidence_disclaimer": "Compiler analytic metrics only — not fused with Python/Sinter sampled CSV; neither artifact is a threshold claim (ADR-0020).", - "rydberg_stages": 10, + "rydberg_stages": 12, "rearrangement_steps": 11, - "rearrangement_time_us": 2001, + "rearrangement_time_us": 1805, "trap_transfers": 30, "transfer_time_us": 450, "entangle2_count": 12, @@ -11,16 +11,16 @@ "measurement_rounds": 1, "reset_rounds": 0, "wait_time_us": 0, - "total_time_us": 3887, + "total_time_us": 3693, "logical_qubits": 4, "physical_atoms": 4, - "estimated_cycles": 90, + "estimated_cycles": 92, "bottleneck": "rearrangement", "aware_search_completed_layers": 0, "aware_search_fell_back_layers": 0, "agnostic_placer_mechanism": "greedy_fallback", "error_budget": { - "rydberg": 0.02, + "rydberg": 0.024, "measurement": 0.003, "reset": 0.0, "movement": 0.0055, @@ -119,6 +119,8 @@ 4, 4, 4, + 4, + 4, 4 ], "measurement_count": 4, @@ -133,16 +135,16 @@ "global_ry_count": 8, "global_ry_time_us": 8, "gate_fidelity_product": 0.9012545135423649, - "estimated_fidelity": 0.8974799926210866, + "estimated_fidelity": 0.8978939369840531, "schedule_objective": { - "rydberg_stages": 10, - "movement_time_us": 2001, + "rydberg_stages": 12, + "movement_time_us": 1805, "trap_transfers": 30, - "idle_time_us": 6292, + "idle_time_us": 5601, "rydberg_stage_weight": 1.0, "movement_time_weight": 1.0, "trap_transfer_weight": 1.0, "idle_time_weight": 1e-6, - "total": 2041.006292 + "total": 1847.005601 } } From ebb2f834f54c125ade964edb471107378ee34518 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 1 Oct 2026 13:41:55 +0000 Subject: [PATCH 2/4] [na] Refresh QEC reports for cost-weight scoring The time objective scores with the target cost_model, so the verified repetition and surface hybrid schedules change. Their resource-report snapshots match those schedules. Routing-aware search on the cubic stats fixture completes five layers. Refs #484 Co-authored-by: Arnab Ghosh --- ...pshots__qec_repetition_d3_hybrid_json.snap | 26 ++++---- ...napshots__qec_repetition_d3_hybrid_md.snap | 23 ++++--- ...snapshots__qec_surface_d3_hybrid_json.snap | 62 +++++++++++-------- ...t_snapshots__qec_surface_d3_hybrid_md.snap | 25 ++++---- ...hots__zoned_routing_aware_config_only.snap | 4 +- 5 files changed, 76 insertions(+), 64 deletions(-) diff --git a/quon_na/tests/snapshots/report_snapshots__qec_repetition_d3_hybrid_json.snap b/quon_na/tests/snapshots/report_snapshots__qec_repetition_d3_hybrid_json.snap index bd546ac..69269ce 100644 --- a/quon_na/tests/snapshots/report_snapshots__qec_repetition_d3_hybrid_json.snap +++ b/quon_na/tests/snapshots/report_snapshots__qec_repetition_d3_hybrid_json.snap @@ -1,22 +1,21 @@ --- source: quon_na/tests/report_snapshots.rs -assertion_line: 267 expression: json --- { "evidence_kind": "analytic", "evidence_disclaimer": "Compiler analytic metrics only — not fused with Python/Sinter sampled CSV; neither artifact is a threshold claim (ADR-0020).", "rydberg_stages": 6, - "rearrangement_steps": 12, - "rearrangement_time_us": 2163, - "trap_transfers": 42, - "transfer_time_us": 630, + "rearrangement_steps": 13, + "rearrangement_time_us": 2169, + "trap_transfers": 44, + "transfer_time_us": 660, "entangle2_count": 8, "entangle_n_count": 0, "measurement_rounds": 3, "reset_rounds": 2, "wait_time_us": 2, - "total_time_us": 2536, + "total_time_us": 2572, "logical_qubits": 1, "physical_atoms": 5, "atoms_per_logical": 5, @@ -27,15 +26,15 @@ expression: json "tdag_count": 0, "ccz_count": 0, "magic_state_demand": 0, - "estimated_cycles": 49, + "estimated_cycles": 52, "bottleneck": "rearrangement", - "agnostic_placer_mechanism": "mixed", + "agnostic_placer_mechanism": "greedy_fallback", "error_budget": { "rydberg": 0.012, "measurement": 0.009, "reset": 0.008, - "movement": 0.006, - "transfer": 0.0294, + "movement": 0.0065, + "transfer": 0.0308, "idle": 4e-9 }, "temporal_atom_metrics": { @@ -89,6 +88,9 @@ expression: json 5, 5, 5, + 5, + 5, + 5, 5 ], "measurement_count": 7, @@ -102,6 +104,6 @@ expression: json "local_gate_time_us": 0, "global_ry_count": 0, "global_ry_time_us": 0, - "gate_fidelity_product": 0.921160170417802, - "estimated_fidelity": 0.9162118692822037 + "gate_fidelity_product": 0.9193187712371369, + "estimated_fidelity": 0.914292508666199 } diff --git a/quon_na/tests/snapshots/report_snapshots__qec_repetition_d3_hybrid_md.snap b/quon_na/tests/snapshots/report_snapshots__qec_repetition_d3_hybrid_md.snap index 45c4782..4fa0dd7 100644 --- a/quon_na/tests/snapshots/report_snapshots__qec_repetition_d3_hybrid_md.snap +++ b/quon_na/tests/snapshots/report_snapshots__qec_repetition_d3_hybrid_md.snap @@ -1,6 +1,5 @@ --- source: quon_na/tests/report_snapshots.rs -assertion_line: 268 expression: md --- # Neutral-atom analytic resource report @@ -18,20 +17,20 @@ expression: md ## Schedule metrics | Metric | Value | | --- | ---: | -| Estimated cycles | 49 | +| Estimated cycles | 52 | | Bottleneck | rearrangement | | Rydberg stages | 6 | -| Rearrangement steps | 12 | -| Rearrangement time (µs) | 2163 | -| Trap transfers | 42 | -| Transfer time (µs) | 630 | +| Rearrangement steps | 13 | +| Rearrangement time (µs) | 2169 | +| Trap transfers | 44 | +| Transfer time (µs) | 660 | | Entangle2 count | 8 | | EntangleN count | 0 | | Measurement rounds | 3 | | Reset rounds | 2 | | Wait time (µs) | 2 | -| Total time (µs) | 2536 | -| Routing-agnostic placement mechanism | mixed | +| Total time (µs) | 2572 | +| Routing-agnostic placement mechanism | greedy_fallback | ## Atom pressure & reuse | Metric | Value | @@ -48,15 +47,15 @@ expression: md | Rydberg | 0.012 | | Measurement | 0.009 | | Reset | 0.008 | -| Movement | 0.006 | -| Transfer | 0.0294 | +| Movement | 0.0065 | +| Transfer | 0.0308 | | Idle | 4e-9 | ## Fidelity estimate (Enola Eq. (1)) | Metric | Value | | --- | ---: | -| Gate fidelity product | 0.921160170418 | -| Estimated fidelity (with idle decay) | 0.916211869282 | +| Gate fidelity product | 0.919318771237 | +| Estimated fidelity (with idle decay) | 0.914292508666 | ## Notes - Compiler analytic metrics only — not fused with Python/Sinter sampled CSV; neither artifact is a threshold claim (ADR-0020). diff --git a/quon_na/tests/snapshots/report_snapshots__qec_surface_d3_hybrid_json.snap b/quon_na/tests/snapshots/report_snapshots__qec_surface_d3_hybrid_json.snap index 15379b0..d8b9aff 100644 --- a/quon_na/tests/snapshots/report_snapshots__qec_surface_d3_hybrid_json.snap +++ b/quon_na/tests/snapshots/report_snapshots__qec_surface_d3_hybrid_json.snap @@ -1,22 +1,21 @@ --- source: quon_na/tests/report_snapshots.rs -assertion_line: 267 expression: json --- { "evidence_kind": "analytic", "evidence_disclaimer": "Compiler analytic metrics only — not fused with Python/Sinter sampled CSV; neither artifact is a threshold claim (ADR-0020).", - "rydberg_stages": 16, - "rearrangement_steps": 56, - "rearrangement_time_us": 8699, - "trap_transfers": 226, - "transfer_time_us": 3390, + "rydberg_stages": 17, + "rearrangement_steps": 60, + "rearrangement_time_us": 8159, + "trap_transfers": 236, + "transfer_time_us": 3540, "entangle2_count": 48, "entangle_n_count": 0, "measurement_rounds": 3, "reset_rounds": 2, "wait_time_us": 2, - "total_time_us": 10406, + "total_time_us": 9987, "logical_qubits": 1, "physical_atoms": 17, "atoms_per_logical": 17, @@ -27,36 +26,27 @@ expression: json "tdag_count": 0, "ccz_count": 0, "magic_state_demand": 0, - "estimated_cycles": 195, + "estimated_cycles": 208, "bottleneck": "rearrangement", "agnostic_placer_mechanism": "mixed", "error_budget": { - "rydberg": 0.032, + "rydberg": 0.034, "measurement": 0.009, "reset": 0.008, - "movement": 0.028, - "transfer": 0.1582, + "movement": 0.03, + "transfer": 0.1652, "idle": 4e-9 }, "temporal_atom_metrics": { "peak_atoms": 17, "allocated_atoms_series": [ - 4, - 4, - 4, - 6, - 6, - 6, + 5, + 5, + 5, 8, 8, 8, 8, - 11, - 11, - 11, - 11, - 11, - 11, 12, 12, 12, @@ -71,6 +61,12 @@ expression: json 12, 12, 12, + 12, + 12, + 12, + 13, + 13, + 13, 13, 13, 13, @@ -235,6 +231,22 @@ expression: json 17, 17, 17, + 17, + 17, + 17, + 17, + 17, + 17, + 17, + 17, + 17, + 17, + 17, + 17, + 17, + 17, + 17, + 17, 17 ], "measurement_count": 25, @@ -248,6 +260,6 @@ expression: json "local_gate_time_us": 16, "global_ry_count": 0, "global_ry_time_us": 0, - "gate_fidelity_product": 0.6240556055877741, - "estimated_fidelity": 0.5625720636942588 + "gate_fidelity_product": 0.6178430572783696, + "estimated_fidelity": 0.5597175024796199 } diff --git a/quon_na/tests/snapshots/report_snapshots__qec_surface_d3_hybrid_md.snap b/quon_na/tests/snapshots/report_snapshots__qec_surface_d3_hybrid_md.snap index 06a4af7..178161e 100644 --- a/quon_na/tests/snapshots/report_snapshots__qec_surface_d3_hybrid_md.snap +++ b/quon_na/tests/snapshots/report_snapshots__qec_surface_d3_hybrid_md.snap @@ -1,6 +1,5 @@ --- source: quon_na/tests/report_snapshots.rs -assertion_line: 268 expression: md --- # Neutral-atom analytic resource report @@ -18,19 +17,19 @@ expression: md ## Schedule metrics | Metric | Value | | --- | ---: | -| Estimated cycles | 195 | +| Estimated cycles | 208 | | Bottleneck | rearrangement | -| Rydberg stages | 16 | -| Rearrangement steps | 56 | -| Rearrangement time (µs) | 8699 | -| Trap transfers | 226 | -| Transfer time (µs) | 3390 | +| Rydberg stages | 17 | +| Rearrangement steps | 60 | +| Rearrangement time (µs) | 8159 | +| Trap transfers | 236 | +| Transfer time (µs) | 3540 | | Entangle2 count | 48 | | EntangleN count | 0 | | Measurement rounds | 3 | | Reset rounds | 2 | | Wait time (µs) | 2 | -| Total time (µs) | 10406 | +| Total time (µs) | 9987 | | Routing-agnostic placement mechanism | mixed | ## Atom pressure & reuse @@ -45,18 +44,18 @@ expression: md ## Physical error budget | Category | Contribution (rate × count) | | --- | ---: | -| Rydberg | 0.032 | +| Rydberg | 0.034 | | Measurement | 0.009 | | Reset | 0.008 | -| Movement | 0.028 | -| Transfer | 0.1582 | +| Movement | 0.03 | +| Transfer | 0.1652 | | Idle | 4e-9 | ## Fidelity estimate (Enola Eq. (1)) | Metric | Value | | --- | ---: | -| Gate fidelity product | 0.624055605588 | -| Estimated fidelity (with idle decay) | 0.562572063694 | +| Gate fidelity product | 0.617843057278 | +| Estimated fidelity (with idle decay) | 0.55971750248 | ## Notes - Compiler analytic metrics only — not fused with Python/Sinter sampled CSV; neither artifact is a threshold claim (ADR-0020). diff --git a/quon_na/tests/snapshots/stats_snapshots__zoned_routing_aware_config_only.snap b/quon_na/tests/snapshots/stats_snapshots__zoned_routing_aware_config_only.snap index e8a5ab4..485c51b 100644 --- a/quon_na/tests/snapshots/stats_snapshots__zoned_routing_aware_config_only.snap +++ b/quon_na/tests/snapshots/stats_snapshots__zoned_routing_aware_config_only.snap @@ -18,12 +18,12 @@ expression: redact_timings(&json) "schema_version": 1, "search": { "aware_search_budget_exceeded_layers": 0, - "aware_search_completed_layers": 3, + "aware_search_completed_layers": 5, "aware_search_deepening_factor": 0.6, "aware_search_deepening_value": 0.2, "aware_search_no_legal_assignment_layers": 0, "aware_search_node_budget": 100000, - "aware_search_node_expansions": 67, + "aware_search_node_expansions": 74, "aware_search_pruning_window": 32 }, "stage_timings_us": { From b6ad32be56e0b5eafbc273af79856c571443260a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 1 Oct 2026 14:09:16 +0000 Subject: [PATCH 3/4] [na] Refresh cycle and parallel-gate pins for cost-weight scoring Time and error-budget scoring with the target cost_model verifies repetition_d3_memory.qn at 52 cycles. The two disjoint CNOTs in parallel_gates_na_mlir.qn each take their own Rydberg stage; the FileCheck pins match that schedule. Refs #484 Co-authored-by: Arnab Ghosh --- quonc/tests/na_showcase.rs | 3 ++- test/lit/na/parallel_gates_na_mlir.qn | 30 +++++++++++++++------------ 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/quonc/tests/na_showcase.rs b/quonc/tests/na_showcase.rs index c70b7fc..440dba8 100644 --- a/quonc/tests/na_showcase.rs +++ b/quonc/tests/na_showcase.rs @@ -394,8 +394,9 @@ fn repetition_d3_memory_schedule_is_genuinely_mid_circuit() { assert_eq!(m["measurement_rounds"], 3, "{schedule}"); assert_eq!(m["reset_rounds"], 2, "{schedule}"); // 37 -> 49: readout-zone shuttles around the measure and reset layers. + // 49 -> 52: time and error-budget scoring use the target cost weights. // The three counts above do not move. - assert_eq!(m["estimated_cycles"], 49, "{schedule}"); + assert_eq!(m["estimated_cycles"], 52, "{schedule}"); let layers = schedule["layers"] .as_array() diff --git a/test/lit/na/parallel_gates_na_mlir.qn b/test/lit/na/parallel_gates_na_mlir.qn index 3136de4..353adfa 100644 --- a/test/lit/na/parallel_gates_na_mlir.qn +++ b/test/lit/na/parallel_gates_na_mlir.qn @@ -1,18 +1,19 @@ -- RUN: %quonc %s --target %S/../../../targets/neutral_atom/generic_rna_v0.json --emit-na-mlir - --quiet | %FileCheck %s -- --- Rydberg-spacing regression: two disjoint CNOTs land in one entangling --- layer. The zoned placer must keep the simultaneous pairs at least --- min_rydberg_spacing_um (18.75 um) apart — adjacent pair columns are only --- 10-12 um apart, so naive nearest-pair assignment fails the dialect's R3 --- verifier. Placed correctly, the pairs sit at x = 0/2 and x = 24/26. +-- Rydberg-spacing regression: two disjoint CNOTs. Adjacent entanglement +-- pair columns are only 10-12 um apart, inside min_rydberg_spacing_um +-- (18.75 um), so a same-layer assignment has to spread them to x = 0/2 and +-- x = 24/26. That spread crosses AOD columns, so the same-layer move takes +-- two groups and the farther column is one microsecond longer. With the +-- target's cost_model weights, giving each CNOT its own Rydberg stage is +-- cheaper. Each stage uses a 2 um partner pair: atoms 0 and 2 at x = 0/2 +-- (cycle 21), atoms 1 and 3 at x = 12/14 (cycle 25). -- --- Issue #298: `H @0 |> H @2` are now preserved (local `rz` + a +-- Issue #298: `H @0 |> H @2` are preserved (local `rz` + a -- Hahn-echo-refocused global `ry` raster each — every other trapped atom -- gets a local `Rz(pi)`/`Rz(-pi)` echo pair around the raster's second half -- so it provably doesn't also rotate; see --- `quon_na::pipeline::push_global_ry_with_refocus` — before any movement), --- pushing the final Rydberg layer's cycle from 3 to 24 versus pre-#298 — --- positions and the "same layer" structure are unaffected. +-- `quon_na::pipeline::push_global_ry_with_refocus` — before any movement). fn two_bells(): Circuit<4, 4, 4, Clifford> = circuit { H @0 |> H @2 |> CNOT @(0, 1) |> CNOT @(2, 3) @@ -25,19 +26,22 @@ fn par_na(): Q> = run { -- CHECK: "quantum.na.schedule"() -- --- One movement stage brings all four atoms in. +-- The first movement stage shuttles one pair into the entanglement zone. -- CHECK: "quantum.na.transfer"() -- CHECK-SAME: direction = "slm_to_aod" -- CHECK: "quantum.na.move"() -- CHECK: "quantum.na.transfer"() -- CHECK-SAME: direction = "aod_to_slm" -- --- Both gates fire in the same Rydberg layer, on pairs 22 um apart. +-- Each CNOT is its own Rydberg stage, on a 2 um partner pair. -- CHECK: "quantum.na.entangle"() -- CHECK-SAME: x_um\22:0.0 +-- CHECK-SAME: x_um\22:2.0 +-- CHECK: cycle = 21 : i64 -- CHECK: "quantum.na.entangle"() --- CHECK-SAME: x_um\22:24.0 --- CHECK: cycle = 24 : i64 +-- CHECK-SAME: x_um\22:12.0 +-- CHECK-SAME: x_um\22:14.0 +-- CHECK: cycle = 25 : i64 -- -- CHECK: min_rydberg_spacing_um = 1.875000e+01 -- CHECK-SAME: target_id = "generic_reconfigurable_neutral_atom_v0" From f2e1a5e11d11b5e3ab111dc5e338e627ce78615e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 1 Oct 2026 14:59:16 +0000 Subject: [PATCH 4/4] [na] Pin ising_n42 cost-weight stage counts QASM and QN preflight both verify at 82 Entangle2 gates and 7 Rydberg stages. Routing-aware still schedules the four circuit layers. The RAP routing comparison stays inside its existing tolerances. Refs #484 Co-authored-by: Arnab Ghosh --- quonc/tests/qasm_ingestion.rs | 21 ++++++++------ quonc/tests/rap_table_i.rs | 54 +++++++++++++++++++++-------------- test/na/ising_n42.qasm | 8 ++++-- test/na/ising_n42.qn | 8 ++++-- 4 files changed, 55 insertions(+), 36 deletions(-) diff --git a/quonc/tests/qasm_ingestion.rs b/quonc/tests/qasm_ingestion.rs index bc39024..b05243d 100644 --- a/quonc/tests/qasm_ingestion.rs +++ b/quonc/tests/qasm_ingestion.rs @@ -4,11 +4,13 @@ //! Acceptance: a `.qasm` counterpart of `test/na/ising_n42.qn` enters the //! existing NA pipeline at the interaction-graph seam and reproduces the //! same pre-flight invariants — `entangle2_count == 82`, `rydberg_stages == -//! 4` — as the `.qn` fixture (see `quonc/tests/rap_table_i.rs`). The QASM +//! 7` — as the `.qn` fixture (see `quonc/tests/rap_table_i.rs`). The QASM //! circuit is a structural twin (two Trotter steps of a 42-qubit chain, //! even/odd matchings per step); it carries no 1-qubit gates, so the -//! 82/4 counts are identical to the `.qn` fixture's interaction-graph-only +//! 82/7 counts are identical to the `.qn` fixture's interaction-graph-only //! slice (the `.qn` fixture's `Rx` layer is invisible to 2q extraction). +//! Cost-weight placement defers some of the four dependency layers, which +//! is why both reports have 7 Rydberg stages rather than 4. //! //! Also covers that unsupported QASM constructs fail with actionable, //! line-tagged errors rather than silently dropping gates (#304 step 6). @@ -73,7 +75,8 @@ fn resource_report(src: &std::path::Path) -> Value { } /// The QASM fixture reproduces the `.qn` fixture's pre-flight invariants -/// ([RAP] Table I ising n=42): 82 two-qubit gates over 4 entangling layers. +/// ([RAP] Table I ising n=42): 82 two-qubit gates, scheduled into 7 Rydberg +/// stages under the target cost weights. #[test] fn ising_n42_qasm_matches_qn_preflight_invariants() { let qasm = resource_report(&qasm_fixture()); @@ -84,14 +87,14 @@ fn ising_n42_qasm_matches_qn_preflight_invariants() { ); assert_eq!( u64_field(&qasm, "rydberg_stages"), - 4, - "ising_n42.qasm must schedule into exactly 4 rydberg stages; got: {qasm}" + 7, + "ising_n42.qasm must schedule into exactly 7 rydberg stages; got: {qasm}" ); assert_eq!(u64_field(&qasm, "logical_qubits"), 42); - // The `.qn` fixture must agree on these placer-independent pre-flight - // counts — guarding against a regression that only realigns one side of - // the twin-fixture seam. + // The `.qn` fixture must agree on these pre-flight counts — guarding + // against a regression that only realigns one side of the twin-fixture + // seam. let qn = resource_report(&qn_fixture()); assert_eq!( u64_field(&qn, "entangle2_count"), @@ -100,7 +103,7 @@ fn ising_n42_qasm_matches_qn_preflight_invariants() { ); assert_eq!( u64_field(&qn, "rydberg_stages"), - 4, + 7, "ising_n42.qn baseline drifted; got: {qn}" ); } diff --git a/quonc/tests/rap_table_i.rs b/quonc/tests/rap_table_i.rs index 8b167f7..5153809 100644 --- a/quonc/tests/rap_table_i.rs +++ b/quonc/tests/rap_table_i.rs @@ -66,10 +66,13 @@ const PUBLISHED_AWARE_STEPS: u64 = 9; const PUBLISHED_AGNOSTIC_TIME_US: u64 = 3100; const PUBLISHED_AWARE_TIME_US: u64 = 1600; -/// Pre-flight circuit structure (placer-independent): 82 two-qubit gates -/// over 4 entangling layers. +/// Pre-flight gate count is placer-independent: 82 two-qubit gates. +/// Routing-agnostic cost-weight placement defers some of the four circuit +/// layers, so the verified stage count is 7. Routing-aware still schedules +/// those four layers. const EXPECTED_ENTANGLE2_COUNT: u64 = 82; -const EXPECTED_RYDBERG_STAGES: u64 = 4; +const EXPECTED_AGNOSTIC_RYDBERG_STAGES: u64 = 7; +const EXPECTED_AWARE_RYDBERG_STAGES: u64 = 4; /// Locked Phase 2 tolerances (documented; not enforced unless /// `QUON_RAP_TABLE_I_ENFORCE=1` — see docs/neutral_atom/rap_table_i_methodology.md). @@ -266,17 +269,16 @@ fn required_u64_field(report: &Value, field: &str) -> u64 { } /// Fails first, before any placer comparison, if `ising_n42.qn` or the -/// pinned target regress. Uses the (fast) default routing-agnostic placer: -/// for this fixture/target pairing there is no placement-induced deferral -/// (ample entanglement-zone capacity, no spacing conflicts — see the -/// methodology doc), so `rydberg_stages` here is the same placer-independent -/// circuit property either mode would report, and `entangle2_count` is -/// placer-independent by construction (one Entangle2 action per gate). +/// pinned target regress. Uses the (fast) default routing-agnostic placer. +/// `entangle2_count` is placer-independent (one Entangle2 action per gate). +/// `rydberg_stages` is not: cost-weight placement defers some agnostic gates, +/// so this path reports 7 stages. Routing-aware still reports 4; the ignored +/// dump below pins that separately. /// Deliberately does **not** also run routing-aware (too slow for the /// default gate, see `Runtime` in the methodology doc); the slower ignored -/// dump test below hard-asserts both placers see the same 82/4 structure -/// directly, so that check is not skipped, only deferred to the release-mode -/// job (finding #5 of the #111 review). +/// dump test below hard-asserts both placers' stage counts directly, so that +/// check is not skipped, only deferred to the release-mode job (finding #5 +/// of the #111 review). #[test] fn ising_n42_preflight_gate_and_layer_counts() { let report = resource_report("routing-agnostic"); @@ -288,9 +290,10 @@ fn ising_n42_preflight_gate_and_layer_counts() { ); assert_eq!( u64_field(&report, "rydberg_stages"), - EXPECTED_RYDBERG_STAGES, - "ising_n42.qn must schedule into exactly {EXPECTED_RYDBERG_STAGES} entangling layers \ - ([RAP] Table I ising n=42); got report: {report}" + EXPECTED_AGNOSTIC_RYDBERG_STAGES, + "ising_n42.qn must schedule into exactly {EXPECTED_AGNOSTIC_RYDBERG_STAGES} entangling \ + layers under routing-agnostic cost-weight placement ([RAP] Table I ising n=42); \ + got report: {report}" ); assert_eq!(u64_field(&report, "logical_qubits"), 42); @@ -340,11 +343,20 @@ fn ising_n42_dumps_both_placer_rearrangement_metrics() { let agnostic = &agnostic_schedule["metrics"]; let aware = &aware_schedule["metrics"]; - // Hard, structural: both placers must see the identical 82-gate/4-layer - // circuit (#111 review finding #5) — a wrong circuit invalidates the - // whole comparison before it starts, for *either* placer, not just - // agnostic (which is all the fast preflight test above can check). - for (label, report) in [("routing-agnostic", agnostic), ("routing-aware", aware)] { + // Hard, structural: both placers must see the identical 82-gate circuit + // (#111 review finding #5). Stage count is not shared: cost-weight + // placement defers agnostic gates (7 stages) and the aware search keeps + // the four circuit layers. A wrong gate count invalidates the comparison + // before it starts, for *either* placer, not just agnostic (which is all + // the fast preflight test above can check). + for (label, report, expected_stages) in [ + ( + "routing-agnostic", + agnostic, + EXPECTED_AGNOSTIC_RYDBERG_STAGES, + ), + ("routing-aware", aware, EXPECTED_AWARE_RYDBERG_STAGES), + ] { assert_eq!( u64_field(report, "entangle2_count"), EXPECTED_ENTANGLE2_COUNT, @@ -352,7 +364,7 @@ fn ising_n42_dumps_both_placer_rearrangement_metrics() { ); assert_eq!( u64_field(report, "rydberg_stages"), - EXPECTED_RYDBERG_STAGES, + expected_stages, "{label}: pre-flight layer count regressed" ); } diff --git a/test/na/ising_n42.qasm b/test/na/ising_n42.qasm index 9167513..220e54f 100644 --- a/test/na/ising_n42.qasm +++ b/test/na/ising_n42.qasm @@ -8,12 +8,14 @@ // qubit last-use tracking, identical to the `.qn` extraction path) places // every gate in a matching on the SAME dag_layer and serializes between // matchings. Result: layers 0/1 (step 1 even/odd) then 2/3 (step 2 -// even/odd) — 4 rydberg stages, 21+20+21+20 = 82 two-qubit gates. +// even/odd) — 4 dependency layers, 21+20+21+20 = 82 two-qubit gates. +// Routing-agnostic cost-weight placement defers some of those layers, so +// the resource report has 7 Rydberg stages. // // Uses native `cz` (not `cx`/`rzz`): every NA target's `native_gates` lists // `cz`, and the NA entangling scheduler models each ≥2-qubit gate as one // symmetric Entangle2 (undirected interaction edge), so `cz` keeps the -// 82/4 pre-flight counts intact — a `cx` would be an equally valid single +// 82-gate pre-flight count intact — a `cx` would be an equally valid single // Entangle2 here, but `cz` matches the `.qn` fixture verbatim. Do not // "simplify" this file into one ascending loop over `range(41)` (that // chains every gate through its shared neighbor and yields 41 serial layers @@ -21,7 +23,7 @@ // // Acceptance: `quonc test/na/ising_n42.qasm --target // targets/neutral_atom/rap_table_i.json --emit-resource-report -` reports -// `entangle2_count == 82` and `rydberg_stages == 4` — the same pre-flight +// `entangle2_count == 82` and `rydberg_stages == 7` — the same pre-flight // invariants as the `.qn` fixture. OPENQASM 2.0; include "qelib1.inc"; diff --git a/test/na/ising_n42.qn b/test/na/ising_n42.qn index cae8e5a..2c26705 100644 --- a/test/na/ising_n42.qn +++ b/test/na/ising_n42.qn @@ -17,19 +17,21 @@ -- (last-qubit-use tracking; ties to actual SSA wire threading, not program -- text order) places every gate in a group on the SAME dag_layer and only -- serializes between groups. Result: layers 0/1 (step 1 even/odd) then 2/3 --- (step 2 even/odd) — 4 layers total, 21+20+21+20 = 82 two-qubit gates. +-- (step 2 even/odd) — 4 dependency layers, 21+20+21+20 = 82 two-qubit gates. +-- Routing-agnostic cost-weight placement defers some of those layers (7 +-- Rydberg stages). Routing-aware keeps the four layers. -- A single ascending `for i in range(41)` loop would instead chain every -- gate through its shared neighbor and produce 41 serial layers per step — -- do not "simplify" this file back to that shape. -- -- Uses native `CZ` (not `Rzz`): `Rzz(theta) @(a,b)` elaborates to -- `CNOT; Rz; CNOT` (frontend/src/elaborate.rs's decompose_rzz), which would --- double the two-qubit gate count to 164 and break the 82/4 pre-flight +-- double the two-qubit gate count to 164 and break the 82-gate pre-flight -- check. `CZ` is in every NA target's `native_gates` and is not decomposed. -- The single-qubit `Rx` layer is cosmetic Trotter-shape flavor: `qubits(42)` -- iteration is a parallel (depth-1) construct, and single-qubit gates are -- invisible to the two-qubit interaction-graph extraction, so it cannot --- perturb the 82/4 counts either way. +-- perturb the 82-gate count either way. fn zz_even_42(tau: Float): Circuit<42, 42, 21, Universal> = circuit { for k in range(21) { CZ @(2 * k, 2 * k + 1) }