Skip to content

Commit eeb5c81

Browse files
committed
test: per-op-type rejection at the SVM frame width
For each mask-carrying op type (MEASURE, MPP, CONDITIONAL_PAULI, NOISE, EXP_VAL), trace a minimal circuit that produces one such op touching qubits at kMaxInlineQubits, assert trace() preserves the high-qubit support in the HIR mask, and assert lower() rejects with the SVM- frame-width error. These both lock down the gate's per-op-type semantics today and double as PR3 task stubs: when the SVM frame migrates to runtime-width storage, each REQUIRE_THROWS_AS becomes a Stim-oracle equivalence check. Includes a cross-word case (MPP X63 * X128) that exercises the multi-target build_pauli_string path; a fixed-width intermediate would clip the high target. Drops the surface d=11 r=11 benchmark added in the previous commit of this branch -- with the gate at kMaxInlineQubits, n=274 can no longer compile. The bench will be re-added in the migration PR that lifts the gate. Assisted-by: Claude (Opus 4.7) <noreply@anthropic.com>
1 parent f8d7843 commit eeb5c81

1 file changed

Lines changed: 88 additions & 0 deletions

File tree

tests/test_frontend.cc

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,94 @@ TEST_CASE("Frontend: high-qubit Paulis preserved across width boundaries",
653653
check_at(513, 512); // bit 512 (word 8); 513 qubits => 9-word arena
654654
}
655655

656+
// =============================================================================
657+
// Per-op-type rejection at the SVM-frame-width gate
658+
// =============================================================================
659+
//
660+
// Each test traces a minimal circuit that produces one op of a given type
661+
// touching qubits >= kMaxInlineQubits, asserts that trace() preserves the
662+
// high-qubit support in the HIR mask, and asserts that lower() refuses to
663+
// compile because the SVM frame can't yet hold high-qubit state. When PR3
664+
// migrates the SVM frame to runtime-width storage and lifts the gate, each
665+
// of these flips into a Stim-oracle equivalence check.
666+
//
667+
// Use num_qubits = kMaxInlineQubits + 1 so the high qubit (kMaxInlineQubits)
668+
// sits exactly at the boundary. q == kMaxInlineQubits = 128 lives in word 2
669+
// for arena num_words = 3, which is also the first word a fixed-width
670+
// PauliBitMask would have dropped.
671+
672+
TEST_CASE("Backend: rejects MEASURE above SVM frame width", "[frontend][high_qubit]") {
673+
auto circuit = parse("H 128\nM 128");
674+
circuit.num_qubits = kMaxInlineQubits + 1;
675+
circuit.num_measurements = 1;
676+
677+
auto hir = trace(circuit);
678+
REQUIRE(hir.ops.size() == 1);
679+
REQUIRE(hir.ops[0].op_type() == OpType::MEASURE);
680+
REQUIRE(hir.destab_mask(hir.ops[0]).bit_get(128)); // X on q128 after H
681+
682+
REQUIRE_THROWS_AS(lower(hir), std::runtime_error);
683+
}
684+
685+
TEST_CASE("Backend: rejects MPP above SVM frame width", "[frontend][high_qubit]") {
686+
// MPP X63 * X128 spans words 0 and 2 -- exercises the cross-word
687+
// build_pauli_string path that fixed-width intermediates would clip.
688+
auto circuit = parse("MPP X63*X128");
689+
circuit.num_qubits = kMaxInlineQubits + 1;
690+
circuit.num_measurements = 1;
691+
692+
auto hir = trace(circuit);
693+
REQUIRE(hir.ops.size() == 1);
694+
REQUIRE(hir.ops[0].op_type() == OpType::MEASURE);
695+
REQUIRE(hir.destab_mask(hir.ops[0]).bit_get(63));
696+
REQUIRE(hir.destab_mask(hir.ops[0]).bit_get(128));
697+
698+
REQUIRE_THROWS_AS(lower(hir), std::runtime_error);
699+
}
700+
701+
TEST_CASE("Backend: rejects CONDITIONAL_PAULI above SVM frame width", "[frontend][high_qubit]") {
702+
// Classical feedback: M 128 ; CX rec[-1] 128 emits a CONDITIONAL_PAULI.
703+
auto circuit = parse("M 128\nCX rec[-1] 128");
704+
circuit.num_qubits = kMaxInlineQubits + 1;
705+
circuit.num_measurements = 1;
706+
707+
auto hir = trace(circuit);
708+
REQUIRE(hir.ops.size() == 2);
709+
REQUIRE(hir.ops[1].op_type() == OpType::CONDITIONAL_PAULI);
710+
REQUIRE(hir.destab_mask(hir.ops[1]).bit_get(128));
711+
712+
REQUIRE_THROWS_AS(lower(hir), std::runtime_error);
713+
}
714+
715+
TEST_CASE("Backend: rejects NOISE above SVM frame width", "[frontend][high_qubit]") {
716+
auto circuit = parse("X_ERROR(0.1) 128");
717+
circuit.num_qubits = kMaxInlineQubits + 1;
718+
719+
auto hir = trace(circuit);
720+
REQUIRE(hir.ops.size() == 1);
721+
REQUIRE(hir.ops[0].op_type() == OpType::NOISE);
722+
// Channel mask must reflect the high-qubit support.
723+
REQUIRE(hir.noise_sites.size() == 1);
724+
REQUIRE(hir.noise_sites[0].channels.size() == 1);
725+
auto channel_view = hir.noise_channel_masks.at(hir.noise_sites[0].channels[0].mask);
726+
REQUIRE(channel_view.x().bit_get(128));
727+
728+
REQUIRE_THROWS_AS(lower(hir), std::runtime_error);
729+
}
730+
731+
TEST_CASE("Backend: rejects EXP_VAL above SVM frame width", "[frontend][high_qubit]") {
732+
auto circuit = parse("EXP_VAL Z128");
733+
circuit.num_qubits = kMaxInlineQubits + 1;
734+
circuit.num_exp_vals = 1;
735+
736+
auto hir = trace(circuit);
737+
REQUIRE(hir.ops.size() == 1);
738+
REQUIRE(hir.ops[0].op_type() == OpType::EXP_VAL);
739+
REQUIRE(hir.stab_mask(hir.ops[0]).bit_get(128));
740+
741+
REQUIRE_THROWS_AS(lower(hir), std::runtime_error);
742+
}
743+
656744
TEST_CASE("Backend: rejects circuits above the VM axis ceiling", "[frontend]") {
657745
// Skip trace() because allocating a TableauSimulator for 65k+ qubits
658746
// is itself prohibitively expensive. The ceiling lives in lower(),

0 commit comments

Comments
 (0)