// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #![allow(clippy::unicode_not_nfc)] use super::{CircuitEntryPoint, Debugger, Interpreter}; use crate::{ interpret::{CircuitGenerationMethod, Error}, target::Profile, }; use expect_test::expect; use miette::Diagnostic; use qsc_circuit::{Circuit, TracerConfig}; use qsc_data_structures::{language_features::LanguageFeatures, source::SourceMap}; use qsc_eval::output::GenericReceiver; use qsc_eval::val::Value; use qsc_passes::PackageType; fn interpreter(code: &str, package_type: PackageType, profile: Profile) -> Interpreter { let sources = SourceMap::new([("test.qs".into(), code.into())], None); let (std_id, store) = crate::compile::package_store_with_stdlib(profile.into()); Interpreter::new( sources, package_type, profile.into(), LanguageFeatures::default(), store, &[(std_id, None)], ) .expect("interpreter creation should succeed") } fn interpreter_with_circuit_trace(code: &str, profile: Profile) -> Interpreter { let sources = SourceMap::new([("test.qs".into(), code.into())], None); let (std_id, store) = crate::compile::package_store_with_stdlib(profile.into()); Interpreter::with_circuit_trace( sources, PackageType::Exe, profile.into(), LanguageFeatures::default(), store, &[(std_id, None)], default_test_tracer_config(), ) .expect("interpreter creation should succeed") } fn circuit_without_groups(code: &str, entry: CircuitEntryPoint) -> String { let eval_circ = circuit_with_options_success( code, Profile::Unrestricted, entry.clone(), CircuitGenerationMethod::ClassicalEval, TracerConfig { group_by_scope: false, ..default_test_tracer_config() }, ); let eval_circ_without_source_locations = circuit_with_options_success( code, Profile::Unrestricted, entry.clone(), CircuitGenerationMethod::ClassicalEval, TracerConfig { group_by_scope: false, source_locations: false, ..default_test_tracer_config() }, ); let static_circ_without_source_locations = circuit_with_options_success( code, Profile::AdaptiveRIF, entry, CircuitGenerationMethod::Static, TracerConfig { group_by_scope: false, source_locations: false, ..default_test_tracer_config() }, ); // Source locations for qubit allocation are not currently supported in static. // For now, we'll just ignore this difference between the classicalEval and static methods. assert_eq!( eval_circ_without_source_locations.to_string(), static_circ_without_source_locations.to_string() ); eval_circ.to_string() } fn circuit_with_groups(code: &str, entry: CircuitEntryPoint) -> String { let eval_circ = circuit_with_options_success( code, Profile::Unrestricted, entry.clone(), CircuitGenerationMethod::ClassicalEval, default_test_tracer_config(), ); let eval_circ_without_source_locations = circuit_with_options_success( code, Profile::Unrestricted, entry.clone(), CircuitGenerationMethod::ClassicalEval, TracerConfig { source_locations: false, ..default_test_tracer_config() }, ); let static_circ_without_source_locations = circuit_with_options_success( code, Profile::AdaptiveRIF, entry, CircuitGenerationMethod::Static, TracerConfig { source_locations: false, ..default_test_tracer_config() }, ); // Source locations for qubit allocation are not currently supported in static. // For now, we'll just ignore this difference between the classicalEval and static methods. assert_eq!( eval_circ_without_source_locations .display_with_groups() .to_string(), static_circ_without_source_locations .display_with_groups() .to_string() ); eval_circ.display_with_groups().to_string() } /// Generates a grouped circuit with source locations disabled, asserts that /// classical evaluation and static generation produce the same grouped display, /// and returns the static rendering for snapshot comparison. fn circuit_with_groups_without_source_locations(code: &str, entry: CircuitEntryPoint) -> String { let eval_circ = circuit_with_options_success( code, Profile::Unrestricted, entry.clone(), CircuitGenerationMethod::ClassicalEval, TracerConfig { source_locations: false, ..default_test_tracer_config() }, ); let static_circ = circuit_with_options_success( code, Profile::AdaptiveRIF, entry, CircuitGenerationMethod::Static, TracerConfig { source_locations: false, ..default_test_tracer_config() }, ); assert_eq!( eval_circ.display_with_groups().to_string(), static_circ.display_with_groups().to_string() ); static_circ.display_with_groups().to_string() } fn circuit_static(code: &str) -> Circuit { circuit_with_options_success( code, Profile::AdaptiveRIF, CircuitEntryPoint::EntryPoint, CircuitGenerationMethod::Static, default_test_tracer_config(), ) } fn circuit_err( code: &str, entry: CircuitEntryPoint, method: CircuitGenerationMethod, tracer_config: TracerConfig, ) -> Vec { let profile = if method == CircuitGenerationMethod::Static { Profile::AdaptiveRIF } else { Profile::Unrestricted }; circuit_with_options(code, profile, entry, method, tracer_config) .expect_err("circuit generation should fail") } fn circuit_with_profile_both_ways( code: &str, entry: CircuitEntryPoint, profile: Profile, ) -> String { let eval_circ = circuit_with_options_success( code, profile, entry.clone(), CircuitGenerationMethod::ClassicalEval, default_test_tracer_config(), ); let static_circ = circuit_with_options_success( code, profile, entry, CircuitGenerationMethod::Static, default_test_tracer_config(), ); format!("Eval:\n{eval_circ}\nStatic:\n{static_circ}") } pub(crate) fn circuit_with_options_success( code: &str, profile: Profile, entry: CircuitEntryPoint, method: CircuitGenerationMethod, config: TracerConfig, ) -> Circuit { circuit_with_options(code, profile, entry, method, config) .expect("circuit generation should succeed") } fn circuit_with_options( code: &str, profile: Profile, entry: CircuitEntryPoint, method: CircuitGenerationMethod, config: TracerConfig, ) -> Result> { let mut interpreter = interpreter(code, PackageType::Exe, profile); interpreter.set_quantum_seed(Some(2)); interpreter.circuit(entry, method, config) } pub(crate) fn default_test_tracer_config() -> TracerConfig { TracerConfig { max_operations: TracerConfig::DEFAULT_MAX_OPERATIONS, source_locations: true, group_by_scope: true, prune_classical_qubits: false, } } // Drives a SyntheticEntry-routed callable+args shape through the Static circuit // path and asserts that it matches the ClassicalEval circuit. A captureless // callable value (`AllH`) passed as an argument routes through // `prepare_codegen_fir_from_callable_args` -> `CallableArgsBackend::SyntheticEntry`, // exercising the `static_circuit_from_callable` SyntheticEntry arm. Parity is // asserted with `source_locations: false` because qubit-allocation source spans // are not currently represented in the Static method (matching the existing // `circuit_without_groups` harness constraint). #[test] fn static_circuit_from_callable_with_callable_arg_matches_classical_eval() { let code = r#" namespace Test { operation InvokeWithQubits(nQubits : Int, f : Qubit[] => Unit) : Unit { use qs = Qubit[nQubits]; f(qs); } operation AllH(qs : Qubit[]) : Unit { for q in qs { H(q); } } } "#; let mut interp = interpreter(code, PackageType::Lib, Profile::AdaptiveRIF); interp.set_quantum_seed(Some(2)); let globals = interp.source_globals(); let invoke = globals .iter() .find(|(_, n, _)| &**n == "InvokeWithQubits") .expect("InvokeWithQubits should be a source global") .2 .clone(); let all_h = globals .iter() .find(|(_, n, _)| &**n == "AllH") .expect("AllH should be a source global") .2 .clone(); let args = Value::Tuple(vec![Value::Int(3), all_h].into(), None); let cfg = TracerConfig { group_by_scope: false, source_locations: false, ..default_test_tracer_config() }; let static_circ = interp .circuit( CircuitEntryPoint::Callable(invoke.clone(), args.clone()), CircuitGenerationMethod::Static, cfg, ) .expect("static circuit generation should succeed"); let eval_circ = interp .circuit( CircuitEntryPoint::Callable(invoke, args), CircuitGenerationMethod::ClassicalEval, cfg, ) .expect("classical-eval circuit generation should succeed"); // Regression lock: the Static path must consume the synthetic entry so // its circuit matches the ClassicalEval circuit for SyntheticEntry-routed args. assert_eq!(static_circ.to_string(), eval_circ.to_string()); expect![[r#" q_0 ── H ── q_1 ── H ── q_2 ── H ── "#]] .assert_eq(&static_circ.to_string()); } #[test] fn empty() { let circ = circuit_without_groups( r#" namespace Test { @EntryPoint() operation Main() : Unit { Message("hi"); } } "#, CircuitEntryPoint::EntryPoint, ); expect![""].assert_eq(&circ); } #[test] fn one_gate() { let circ = circuit_without_groups( r" namespace Test { @EntryPoint() operation Main() : Unit { use q = Qubit(); H(q); } } ", CircuitEntryPoint::EntryPoint, ); expect![[r#" q_0@test.qs:4:20 ─ H@test.qs:5:20 ── "#]] .assert_eq(&circ); } #[test] fn measure_same_qubit_twice() { let circ = circuit_without_groups( r" namespace Test { @EntryPoint() operation Main() : Result[] { use q = Qubit(); H(q); let r1 = M(q); let r2 = M(q); [r1, r2] } } ", CircuitEntryPoint::EntryPoint, ); expect![[r#" q_0@test.qs:4:20 ─ H@test.qs:5:20 ─── M@test.qs:6:29 ─── M@test.qs:7:29 ── ╘══════════════════╪═════════ ╘═════════ "#]] .assert_eq(&circ); } #[test] fn toffoli() { let circ = circuit_without_groups( r" namespace Test { @EntryPoint() operation Main() : Unit { use q = Qubit[3]; CCNOT(q[0], q[1], q[2]); } } ", CircuitEntryPoint::EntryPoint, ); expect![[r#" q_0@test.qs:4:20 ──────── ● ──────── q_1@test.qs:4:20 ──────── ● ──────── q_2@test.qs:4:20 ─ X@test.qs:5:20 ── "#]] .assert_eq(&circ); } #[test] fn rotation_gate() { let circ = circuit_without_groups( r" namespace Test { @EntryPoint() operation Main() : Unit { use q = Qubit(); Rx(Microsoft.Quantum.Math.PI()/2.0, q); } } ", CircuitEntryPoint::EntryPoint, ); expect![[r#" q_0@test.qs:4:20 ─ Rx(1.5708)@test.qs:5:20 ─ "#]] .assert_eq(&circ); } #[test] fn grouping_nested_callables() { let circ = circuit_with_groups( r" namespace Test { @EntryPoint() operation Main() : Unit { use q = Qubit(); Foo(q); MResetZ(q); } operation Foo(q: Qubit) : Unit { H(q); } } ", CircuitEntryPoint::EntryPoint, ); expect![[r#" q_0@test.qs:4:20 ─ Main[1] ─ ╘═════ [1] Main: q_0@test.qs:4:20 ─ [ [Foo@test.qs:5:20] ─── H@test.qs:10:20 ─── ] ─── M@test.qs:6:20 ──── |0〉@test.qs:6:20 ─── ╘════════════════════════════════ "#]] .assert_eq(&circ); } #[test] fn classical_for_loop_is_grouped() { let circ = circuit_without_groups( r" namespace Test { @EntryPoint() operation Main() : Unit { use q = Qubit(); for i in 0..2 { Foo(q); } } operation Foo(q: Qubit) : Unit { X(q); Y(q); } } ", CircuitEntryPoint::EntryPoint, ); expect![[r#" q_0@test.qs:4:20 ─ X@test.qs:11:20 ── Y@test.qs:12:20 ── X@test.qs:11:20 ── Y@test.qs:12:20 ── X@test.qs:11:20 ── Y@test.qs:12:20 ─ "#]] .assert_eq(&circ); } #[test] fn dynamic_for_loop_is_grouped() { let circ = circuit_with_options_success( r" operation Main() : Unit { use qubit = Qubit(); repeat { H(qubit); } until M(qubit) == Zero fixup { Reset(qubit); } } ", Profile::Unrestricted, CircuitEntryPoint::EntryPoint, CircuitGenerationMethod::Simulate, TracerConfig { max_operations: 1000, source_locations: true, group_by_scope: true, prune_classical_qubits: false, }, ); let circ = circ.display_with_groups().to_string(); expect![[r#" q_0@test.qs:2:15 ─ Main[1] ─ ╘═════ ╘═════ ╘═════ ╘═════ [1] Main: q_0@test.qs:2:15 ─ loop: M(qubit) == Zero@test.qs:3:16[2] ── ╘═════════════════════ ╘═════════════════════ ╘═════════════════════ ╘═════════════════════ [2] loop: M(qubit) == Zero: q_0@test.qs:2:15 ─ (1)@test.qs:3:23[3] ── (2)@test.qs:3:23[4] ── (3)@test.qs:3:23[5] ── (4)@test.qs:3:23[6] ─ ╘══════════════════════┆══════════════════════┆══════════════════════┆═══════════ ╘══════════════════════┆══════════════════════┆═══════════ ╘══════════════════════┆═══════════ ╘═══════════ [3] (1): q_0@test.qs:2:15 ─ H@test.qs:4:20 ─── M@test.qs:5:24 ──── |0〉@test.qs:7:20 ─── ╘════════════════════════════════ [4] (2): q_0@test.qs:2:15 ─ H@test.qs:4:20 ─── M@test.qs:5:24 ──── |0〉@test.qs:7:20 ─── │ ╘════════════════════════════════ [5] (3): q_0@test.qs:2:15 ─ H@test.qs:4:20 ─── M@test.qs:5:24 ──── |0〉@test.qs:7:20 ─── │ │ ╘════════════════════════════════ [6] (4): q_0@test.qs:2:15 ─ H@test.qs:4:20 ─── M@test.qs:5:24 ── │ │ │ ╘═════════ "#]] .assert_eq(&circ); } #[test] fn repeat_until_loop_is_grouped() { let circ = circuit_with_groups( r" namespace Test { @EntryPoint() operation Main() : Unit { use q = Qubit(); mutable i = 0; repeat { Foo(q); } until i == 2 fixup { set i += 1; } } operation Foo(q: Qubit) : Unit { X(q); Y(q); } } ", CircuitEntryPoint::EntryPoint, ); expect![[r#" q_0@test.qs:4:20 ─ [ [Main] ─── [ [loop: i == 2@test.qs:6:20] ── [ [(1)@test.qs:6:27] ─── [ [Foo@test.qs:7:24] ─── X@test.qs:15:20 ── Y@test.qs:16:20 ─── ] ──── ] ─── [ [(2)@test.qs:6:27] ─── [ [Foo@test.qs:7:24] ─── X@test.qs:15:20 ── Y@test.qs:16:20 ─── ] ──── ] ─── [ [(3)@test.qs:6:27] ─── [ [Foo@test.qs:7:24] ─── X@test.qs:15:20 ── Y@test.qs:16:20 ─── ] ──── ] ──── ] ──── ] ── "#]] .assert_eq(&circ); } #[test] fn while_loop_is_grouped() { let circ = circuit_with_groups( r" namespace Test { @EntryPoint() operation Main() : Unit { use q = Qubit(); mutable i = 0; while (i < 2) { Foo(q); set i += 1; } } operation Foo(q: Qubit) : Unit { X(q); Y(q); } } ", CircuitEntryPoint::EntryPoint, ); expect![[r#" q_0@test.qs:4:20 ─ [ [Main] ─── [ [loop: i < 2@test.qs:6:20] ─── [ [(1)@test.qs:6:34] ─── [ [Foo@test.qs:7:24] ─── X@test.qs:13:20 ── Y@test.qs:14:20 ─── ] ──── ] ─── [ [(2)@test.qs:6:34] ─── [ [Foo@test.qs:7:24] ─── X@test.qs:13:20 ── Y@test.qs:14:20 ─── ] ──── ] ──── ] ──── ] ── "#]] .assert_eq(&circ); } #[test] fn loop_single_iteration_is_not_grouped() { let circ = circuit_with_groups( r" namespace Test { @EntryPoint() operation Main() : Unit { use q = Qubit(); for i in 0..0 { Foo(q); } } operation Foo(q: Qubit) : Unit { X(q); Y(q); } } ", CircuitEntryPoint::EntryPoint, ); expect![[r#" q_0@test.qs:4:20 ─ [ [Main] ─── [ [Foo@test.qs:6:24] ─── X@test.qs:11:20 ── Y@test.qs:12:20 ─── ] ──── ] ── "#]] .assert_eq(&circ); } #[test] fn loop_vertical_is_not_grouped() { let circ = circuit_with_groups( r" namespace Test { @EntryPoint() operation Main() : Unit { use qs = Qubit[6]; for i in 0..5 { Foo(qs[i]); } } operation Foo(q: Qubit) : Unit { X(q); } } ", CircuitEntryPoint::EntryPoint, ); expect![[r#" q_0@test.qs:4:20 ─ Main[1] ─ ┆ q_1@test.qs:4:20 ─ Main[1] ─ ┆ q_2@test.qs:4:20 ─ Main[1] ─ ┆ q_3@test.qs:4:20 ─ Main[1] ─ ┆ q_4@test.qs:4:20 ─ Main[1] ─ ┆ q_5@test.qs:4:20 ─ Main[1] ─ [1] Main: q_0@test.qs:4:20 ─ [ [Foo@test.qs:6:24] ─── X@test.qs:11:20 ─── ] ── q_1@test.qs:4:20 ─ [ [Foo@test.qs:6:24] ─── X@test.qs:11:20 ─── ] ── q_2@test.qs:4:20 ─ [ [Foo@test.qs:6:24] ─── X@test.qs:11:20 ─── ] ── q_3@test.qs:4:20 ─ [ [Foo@test.qs:6:24] ─── X@test.qs:11:20 ─── ] ── q_4@test.qs:4:20 ─ [ [Foo@test.qs:6:24] ─── X@test.qs:11:20 ─── ] ── q_5@test.qs:4:20 ─ [ [Foo@test.qs:6:24] ─── X@test.qs:11:20 ─── ] ── "#]] .assert_eq(&circ); } #[test] fn for_loop_nested() { let circ = circuit_with_options( r" namespace Test { @EntryPoint() operation Main() : Unit { use qs = Qubit[3]; for j in 0..2 { for i in 0..2 { Foo(qs[i]); } } } operation Foo(q: Qubit) : Unit { X(q); } } ", Profile::Unrestricted, CircuitEntryPoint::EntryPoint, CircuitGenerationMethod::ClassicalEval, TracerConfig { max_operations: 1000, source_locations: true, group_by_scope: true, prune_classical_qubits: false, }, ) .expect("circuit generation should succeed"); let circ = circ.display_with_groups().to_string(); expect![[r#" q_0@test.qs:4:20 ─ Main[1] ─ ┆ q_1@test.qs:4:20 ─ Main[1] ─ ┆ q_2@test.qs:4:20 ─ Main[1] ─ [1] Main: q_0@test.qs:4:20 ─ loop: 0..2@test.qs:5:20[2] ── ┆ q_1@test.qs:4:20 ─ loop: 0..2@test.qs:5:20[2] ── ┆ q_2@test.qs:4:20 ─ loop: 0..2@test.qs:5:20[2] ── [2] loop: 0..2: q_0@test.qs:4:20 ─ (1)@test.qs:5:34[3] ── (2)@test.qs:5:34[4] ── (3)@test.qs:5:34[5] ─ ┆ ┆ ┆ q_1@test.qs:4:20 ─ (1)@test.qs:5:34[3] ── (2)@test.qs:5:34[4] ── (3)@test.qs:5:34[5] ─ ┆ ┆ ┆ q_2@test.qs:4:20 ─ (1)@test.qs:5:34[3] ── (2)@test.qs:5:34[4] ── (3)@test.qs:5:34[5] ─ [3] (1): q_0@test.qs:4:20 ─ [ [Foo@test.qs:7:28] ─── X@test.qs:13:20 ─── ] ── q_1@test.qs:4:20 ─ [ [Foo@test.qs:7:28] ─── X@test.qs:13:20 ─── ] ── q_2@test.qs:4:20 ─ [ [Foo@test.qs:7:28] ─── X@test.qs:13:20 ─── ] ── [4] (2): q_0@test.qs:4:20 ─ [ [Foo@test.qs:7:28] ─── X@test.qs:13:20 ─── ] ── q_1@test.qs:4:20 ─ [ [Foo@test.qs:7:28] ─── X@test.qs:13:20 ─── ] ── q_2@test.qs:4:20 ─ [ [Foo@test.qs:7:28] ─── X@test.qs:13:20 ─── ] ── [5] (3): q_0@test.qs:4:20 ─ [ [Foo@test.qs:7:28] ─── X@test.qs:13:20 ─── ] ── q_1@test.qs:4:20 ─ [ [Foo@test.qs:7:28] ─── X@test.qs:13:20 ─── ] ── q_2@test.qs:4:20 ─ [ [Foo@test.qs:7:28] ─── X@test.qs:13:20 ─── ] ── "#]] .assert_eq(&circ); } #[test] fn m_base_profile() { let circ = circuit_with_profile_both_ways( r" namespace Test { import Std.Measurement.*; @EntryPoint() operation Main() : Result[] { use q = Qubit(); H(q); [M(q)] } } ", CircuitEntryPoint::EntryPoint, Profile::Base, ); expect![[r#" Eval: q_0@test.qs:5:20 ─ H@test.qs:6:20 ─── M@test.qs:7:21 ── ╘═════════ Static: q_0 ─ H@test.qs:6:20 ─── M@test.qs:7:21 ── ╘═════════ "#]] .assert_eq(&circ); } #[test] fn m_default_profile() { let circ = circuit_without_groups( r" namespace Test { import Std.Measurement.*; @EntryPoint() operation Main() : Result[] { use q = Qubit(); H(q); [M(q)] } } ", CircuitEntryPoint::EntryPoint, ); expect![[r#" q_0@test.qs:5:20 ─ H@test.qs:6:20 ─── M@test.qs:7:21 ── ╘═════════ "#]] .assert_eq(&circ); } #[test] fn mresetz_unrestricted_profile() { let circ = circuit_without_groups( r" namespace Test { import Std.Measurement.*; @EntryPoint() operation Main() : Result[] { use q = Qubit(); H(q); [MResetZ(q)] } } ", CircuitEntryPoint::EntryPoint, ); expect![[r#" q_0@test.qs:5:20 ─ H@test.qs:6:20 ─── M@test.qs:7:21 ──── |0〉@test.qs:7:21 ─── ╘════════════════════════════════ "#]] .assert_eq(&circ); } #[test] fn mresetz_base_profile() { let circ = circuit_with_profile_both_ways( r" namespace Test { import Std.Measurement.*; @EntryPoint() operation Main() : Result[] { use q = Qubit(); H(q); [MResetZ(q)] } } ", CircuitEntryPoint::EntryPoint, Profile::Base, ); // code gen in Base turns the MResetZ into an M expect![[r#" Eval: q_0@test.qs:5:20 ─ H@test.qs:6:20 ─── M@test.qs:7:21 ──── |0〉@test.qs:7:21 ─── ╘════════════════════════════════ Static: q_0 ─ H@test.qs:6:20 ─── M@test.qs:7:21 ── ╘═════════ "#]] .assert_eq(&circ); } #[test] fn qubit_relabel() { let circ = circuit_without_groups( " namespace Test { operation Main() : Unit { use (q1, q2) = (Qubit(), Qubit()); H(q1); CNOT(q1, q2); Relabel([q1, q2], [q2, q1]); H(q1); CNOT(q1, q2); MResetZ(q1); MResetZ(q2); } } ", CircuitEntryPoint::EntryPoint, ); expect![[r#" q_0@test.qs:3:32 ─ H@test.qs:4:16 ────────── ● ──────────────────────────── X@test.qs:8:16 ─── M@test.qs:10:16 ─── |0〉@test.qs:10:16 ── │ │ ╘════════════════════════════════ q_1@test.qs:3:41 ──────────────────── X@test.qs:5:16 ─── H@test.qs:7:16 ────────── ● ───────── M@test.qs:9:16 ──── |0〉@test.qs:9:16 ─── ╘════════════════════════════════ "#]] .assert_eq(&circ); } #[test] fn qubit_reuse() { let circ = circuit_without_groups( " namespace Test { operation Main() : Unit { { use q1 = Qubit(); X(q1); MResetZ(q1); } { use q2 = Qubit(); Y(q2); MResetZ(q2); } } } ", CircuitEntryPoint::EntryPoint, ); expect![[r#" q_0@test.qs:4:20, test.qs:9:20 ─ X@test.qs:5:20 ─── M@test.qs:6:20 ──── |0〉@test.qs:6:20 ──── Y@test.qs:10:20 ── M@test.qs:11:20 ─── |0〉@test.qs:11:20 ── ╘════════════════════════════════════════════════════════════╪════════════════════════════════ ╘════════════════════════════════ "#]] .assert_eq(&circ); } #[test] fn qubit_reuse_no_measurements() { let circ = circuit_without_groups( " namespace Test { operation Main() : Unit { { use q1 = Qubit(); X(q1); Reset(q1); } { use q2 = Qubit(); Y(q2); Reset(q2); } } } ", CircuitEntryPoint::EntryPoint, ); expect![[r#" q_0@test.qs:4:20, test.qs:9:20 ─ X@test.qs:5:20 ──── |0〉@test.qs:6:20 ──── Y@test.qs:10:20 ─── |0〉@test.qs:11:20 ── "#]] .assert_eq(&circ); } #[test] fn eval_method_result_comparison() { let mut interpreter = interpreter_with_circuit_trace( r" namespace Test { import Std.Measurement.*; @EntryPoint() operation Main() : Result[] { use q1 = Qubit(); use q2 = Qubit(); H(q1); H(q2); let r1 = M(q1); let r2 = M(q2); if (r1 == r2) { X(q1); } ResetAll([q1, q2]); [r1, r2] } } ", Profile::Unrestricted, ); interpreter.set_quantum_seed(Some(2)); let circuit_err = interpreter .circuit( CircuitEntryPoint::EntryPoint, CircuitGenerationMethod::ClassicalEval, default_test_tracer_config(), ) .expect_err("circuit should return error") .pop() .expect("error should exist"); expect!["Qsc.Eval.ResultComparisonUnsupported"].assert_eq( &circuit_err .code() .expect("error code should exist") .to_string(), ); let circuit = interpreter.get_circuit(); expect![""].assert_eq(&circuit.to_string()); let mut out = std::io::sink(); let mut r = GenericReceiver::new(&mut out); // Result comparisons are okay when tracing // circuit with the simulator. let circ = interpreter .circuit( CircuitEntryPoint::EntryPoint, CircuitGenerationMethod::Simulate, default_test_tracer_config(), ) .expect("circuit generation should succeed"); expect![[r#" q_0@test.qs:5:20 ─ H@test.qs:7:20 ─── M@test.qs:9:29 ───── X@test.qs:12:24 ───── |0〉@test.qs:14:20 ── ╘═══════════════════════════════════════════════════════ q_1@test.qs:6:20 ─ H@test.qs:8:20 ─── M@test.qs:10:29 ─── |0〉@test.qs:14:20 ───────────────────────── ╘═══════════════════════════════════════════════════════ "#]] .assert_eq(&circ.to_string()); // Result comparisons are also okay if calling // get_circuit() after incremental evaluation, // because we're using the current simulator // state. interpreter .eval_fragments(&mut r, "Test.Main();") .expect("eval should succeed"); let circuit = interpreter.get_circuit(); expect![[r#" q_0@test.qs:5:20 ─ H@test.qs:7:20 ─── M@test.qs:9:29 ───── X@test.qs:12:24 ───── |0〉@test.qs:14:20 ── ╘═══════════════════════════════════════════════════════ q_1@test.qs:6:20 ─ H@test.qs:8:20 ─── M@test.qs:10:29 ─── |0〉@test.qs:14:20 ───────────────────────── ╘═══════════════════════════════════════════════════════ "#]] .assert_eq(&circuit.to_string()); } #[test] fn custom_intrinsic() { let circ = circuit_without_groups( r" namespace Test { operation foo(q: Qubit): Unit { body intrinsic; } @EntryPoint() operation Main() : Unit { use q = Qubit(); foo(q); } }", CircuitEntryPoint::EntryPoint, ); expect![[r#" q_0@test.qs:8:12 ─ foo@test.qs:9:12 ── "#]] .assert_eq(&circ); } #[test] fn custom_intrinsic_classical_arg() { let circ = circuit_without_groups( r" namespace Test { operation foo(n: Int): Unit { body intrinsic; } @EntryPoint() operation Main() : Unit { use q = Qubit(); X(q); foo(4); } }", CircuitEntryPoint::EntryPoint, ); // A custom intrinsic that doesn't take qubits just doesn't // show up on the circuit. expect![[r#" q_0@test.qs:8:12 ─ X@test.qs:9:12 ── "#]] .assert_eq(&circ); } #[test] fn custom_intrinsic_one_classical_arg() { let circ = circuit_without_groups( r" namespace Test { operation foo(n: Int, q: Qubit): Unit { body intrinsic; } @EntryPoint() operation Main() : Unit { use q = Qubit(); X(q); foo(4, q); } }", CircuitEntryPoint::EntryPoint, ); expect![[r#" q_0@test.qs:8:12 ─ X@test.qs:9:12 ─── foo(4)@test.qs:10:12 ── "#]] .assert_eq(&circ); } #[test] fn custom_intrinsic_no_qubit_args() { let circ = circuit_without_groups( r" namespace Test { operation foo(n: Int): Unit { body intrinsic; } @EntryPoint() operation Main() : Unit { use q = Qubit(); X(q); foo(4); } }", CircuitEntryPoint::EntryPoint, ); expect![[r#" q_0@test.qs:8:12 ─ X@test.qs:9:12 ── "#]] .assert_eq(&circ); } #[test] fn custom_intrinsic_mixed_args_classical_eval() { let circ = circuit_with_options_success( r" namespace Test { import Std.ResourceEstimation.*; @EntryPoint() operation Main() : Unit { use qs = Qubit[10]; AccountForEstimates( [ AuxQubitCount(1), TCount(2), RotationCount(3), RotationDepth(4), CczCount(5), MeasurementCount(6), ], PSSPCLayout(), qs); } }", Profile::AdaptiveRIF, CircuitEntryPoint::EntryPoint, CircuitGenerationMethod::ClassicalEval, default_test_tracer_config(), ); expect![[r#" q_0@test.qs:6:12 ─ AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1)@test.qs:7:12 ─ ┆ q_1@test.qs:6:12 ─ AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1)@test.qs:7:12 ─ ┆ q_2@test.qs:6:12 ─ AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1)@test.qs:7:12 ─ ┆ q_3@test.qs:6:12 ─ AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1)@test.qs:7:12 ─ ┆ q_4@test.qs:6:12 ─ AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1)@test.qs:7:12 ─ ┆ q_5@test.qs:6:12 ─ AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1)@test.qs:7:12 ─ ┆ q_6@test.qs:6:12 ─ AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1)@test.qs:7:12 ─ ┆ q_7@test.qs:6:12 ─ AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1)@test.qs:7:12 ─ ┆ q_8@test.qs:6:12 ─ AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1)@test.qs:7:12 ─ ┆ q_9@test.qs:6:12 ─ AccountForEstimatesInternal([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], 1)@test.qs:7:12 ─ "#]] .assert_eq(&circ.to_string()); } #[test] fn custom_intrinsic_mixed_args_static() { let circ = circuit_static( r" namespace Test { import Std.ResourceEstimation.*; @EntryPoint() operation Main() : Unit { use qs = Qubit[10]; AccountForEstimates( [ AuxQubitCount(1), TCount(2), RotationCount(3), RotationDepth(4), CczCount(5), MeasurementCount(6), ], PSSPCLayout(), qs); } }", ); // This intrinsic never gets codegenned, so it's missing from the // circuit too. expect![[r#" q_0 q_1 q_2 q_3 q_4 q_5 q_6 q_7 q_8 q_9 "#]] .assert_eq(&circ.to_string()); } #[test] fn custom_intrinsic_apply_idle_noise_classical_eval() { let circ = circuit_with_options_success( r" namespace Test { import Std.Diagnostics.*; @EntryPoint() operation Main() : Unit { ConfigurePauliNoise(BitFlipNoise(1.0)); use q = Qubit(); ApplyIdleNoise(q); } }", Profile::AdaptiveRIF, CircuitEntryPoint::EntryPoint, CircuitGenerationMethod::ClassicalEval, default_test_tracer_config(), ); expect![[r#" q_0@test.qs:6:12 ─ ApplyIdleNoise@test.qs:7:12 ─ "#]] .assert_eq(&circ.to_string()); } #[test] fn custom_intrinsic_apply_idle_noise_static() { let circ = circuit_static( r" namespace Test { import Std.Diagnostics.*; @EntryPoint() operation Main() : Unit { ConfigurePauliNoise(BitFlipNoise(1.0)); use q = Qubit(); ApplyIdleNoise(q); } }", ); // These intrinsics never get codegenned, so they're missing from the // circuit too. expect![[r#" q_0 "#]] .assert_eq(&circ.to_string()); } #[test] fn operation_with_qubits() { let circ = circuit_without_groups( r" namespace Test { @EntryPoint() operation Main() : Result[] { [] } operation Test(q1: Qubit, q2: Qubit) : Result[] { H(q1); CNOT(q1, q2); [M(q1), M(q2)] } }", CircuitEntryPoint::Operation("Test.Test".into()), ); expect![[r#" q_0@test.qs:5:27 ─ H@test.qs:6:16 ────────── ● ───────── M@test.qs:8:17 ── │ ╘═════════ q_1@test.qs:5:38 ──────────────────── X@test.qs:7:16 ─── M@test.qs:8:24 ── ╘═════════ "#]] .assert_eq(&circ); } #[test] fn operation_with_qubit_arrays() { let circ = circuit_without_groups( r" namespace Test { @EntryPoint() operation Main() : Result[] { [] } import Std.Measurement.*; operation Test(q1: Qubit[], q2: Qubit[][], q3: Qubit[][][], q: Qubit) : Result[] { for q in q1 { H(q); } for qs in q2 { for q in qs { X(q); } } for qss in q3 { for qs in qss { for q in qs { Y(q); } } } X(q); MeasureEachZ(q1) } }", CircuitEntryPoint::Operation("Test.Test".into()), ); expect![[r#" q_0@test.qs:6:27 ─ H@test.qs:8:20 ─── M@test.qs:23:16 ─ ╘═════════ q_1@test.qs:6:27 ─ H@test.qs:8:20 ─── M@test.qs:23:16 ─ ╘═════════ q_2@test.qs:6:40 ─ X@test.qs:12:24 ──────────────────── q_3@test.qs:6:40 ─ X@test.qs:12:24 ──────────────────── q_4@test.qs:6:40 ─ X@test.qs:12:24 ──────────────────── q_5@test.qs:6:40 ─ X@test.qs:12:24 ──────────────────── q_6@test.qs:6:55 ─ Y@test.qs:18:28 ──────────────────── q_7@test.qs:6:55 ─ Y@test.qs:18:28 ──────────────────── q_8@test.qs:6:55 ─ Y@test.qs:18:28 ──────────────────── q_9@test.qs:6:55 ─ Y@test.qs:18:28 ──────────────────── q_10@test.qs:6:55 ─ Y@test.qs:18:28 ──────────────────── q_11@test.qs:6:55 ─ Y@test.qs:18:28 ──────────────────── q_12@test.qs:6:55 ─ Y@test.qs:18:28 ──────────────────── q_13@test.qs:6:55 ─ Y@test.qs:18:28 ──────────────────── q_14@test.qs:6:72 ─ X@test.qs:22:16 ──────────────────── "#]] .assert_eq(&circ); } #[test] fn adjoint_operation() { let circ = circuit_without_groups( r" namespace Test { @EntryPoint() operation Main() : Result[] { [] } operation Foo (q : Qubit) : Unit is Adj + Ctl { body (...) { X(q); } adjoint (...) { Y(q); } controlled (cs, ...) { } } }", CircuitEntryPoint::Operation("Adjoint Test.Foo".into()), ); expect![[r#" q_0@test.qs:5:27 ─ Y@test.qs:13:20 ─ "#]] .assert_eq(&circ); } #[test] fn lambda() { let circ = circuit_without_groups( r" namespace Test { @EntryPoint() operation Main() : Result[] { [] } }", CircuitEntryPoint::Operation("q => H(q)".into()), ); expect![[r#" q_0@line_0:0:0 ─ H@:2:18 ── "#]] .assert_eq(&circ); } #[test] fn controlled_operation() { let circ_err = circuit_err( r" namespace Test { @EntryPoint() operation Main() : Result[] { [] } operation SWAP (q1 : Qubit, q2 : Qubit) : Unit is Adj + Ctl { body (...) { CNOT(q1, q2); CNOT(q2, q1); CNOT(q1, q2); } adjoint (...) { SWAP(q1, q2); } controlled (cs, ...) { CNOT(q1, q2); Controlled CNOT(cs, (q2, q1)); CNOT(q1, q2); } } }", CircuitEntryPoint::Operation("Controlled Test.SWAP".into()), CircuitGenerationMethod::ClassicalEval, default_test_tracer_config(), ); // Controlled operations are not supported at the moment. // We don't generate an accurate call signature with the tuple arguments. expect![[r" [ Circuit( ControlledUnsupported, ), ] "]] .assert_debug_eq(&circ_err); } #[test] fn internal_operation() { let circ = circuit_without_groups( r" namespace Test { @EntryPoint() operation Main() : Result[] { [] } internal operation Test(q1: Qubit, q2: Qubit) : Result[] { H(q1); CNOT(q1, q2); [M(q1), M(q2)] } }", CircuitEntryPoint::Operation("Test.Test".into()), ); expect![[r#" q_0@test.qs:5:36 ─ H@test.qs:6:16 ────────── ● ───────── M@test.qs:8:17 ── │ ╘═════════ q_1@test.qs:5:47 ──────────────────── X@test.qs:7:16 ─── M@test.qs:8:24 ── ╘═════════ "#]] .assert_eq(&circ); } #[test] fn operation_with_non_qubit_args() { let circ_err = circuit_err( r" namespace Test { @EntryPoint() operation Main() : Result[] { [] } operation Test(q1: Qubit, q2: Qubit, i: Int) : Unit { } }", CircuitEntryPoint::Operation("Test.Test".into()), CircuitGenerationMethod::ClassicalEval, default_test_tracer_config(), ); expect![[r" [ Circuit( NoQubitParameters, ), ] "]] .assert_debug_eq(&circ_err); } #[test] fn operation_with_long_gates_properly_aligned() { let circ = circuit_without_groups( r" namespace Test { import Std.Measurement.*; @EntryPoint() operation Main() : Result[] { use q0 = Qubit(); use q1 = Qubit(); H(q0); H(q1); X(q1); Ry(1.0, q1); CNOT(q0, q1); M(q0); use q2 = Qubit(); H(q2); Rx(1.0, q2); H(q2); Rx(1.0, q2); H(q2); Rx(1.0, q2); use q3 = Qubit(); Rxx(1.0, q1, q3); CNOT(q0, q3); [M(q1), M(q3)] } } ", CircuitEntryPoint::EntryPoint, ); expect![[r#" q_0@test.qs:6:20 ─ H@test.qs:9:20 ───────────────────────────────────────────────────────────────────────── ● ────────────── M@test.qs:14:20 ─────────────────────────────────────────────────────────────────── ● ─────────────────────────── │ ╘════════════════════════════════════════════════════════════════════════════╪════════════════════════════ q_1@test.qs:7:20 ─ H@test.qs:10:20 ─────── X@test.qs:11:20 ─────── Ry(1.0000)@test.qs:12:20 ──────── X@test.qs:13:20 ─────────────────────────────────────────────────────── Rxx(1.0000)@test.qs:27:20 ──────────┼────────── M@test.qs:31:21 ─ ┆ │ ╘═════════ q_2@test.qs:16:20 ─ H@test.qs:18:20 ── Rx(1.0000)@test.qs:19:20 ──────── H@test.qs:20:20 ─────── Rx(1.0000)@test.qs:21:20 ─── H@test.qs:22:20 ── Rx(1.0000)@test.qs:23:20 ────────────────┆───────────────────────┼──────────────────────────── q_3@test.qs:25:20 ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Rxx(1.0000)@test.qs:27:20 ── X@test.qs:29:20 ── M@test.qs:31:28 ─ ╘═════════ "#]] .assert_eq(&circ); } #[test] fn operation_with_subsequent_qubits_gets_horizontal_lines() { let circ = circuit_without_groups( r" namespace Test { import Std.Measurement.*; @EntryPoint() operation Main() : Unit { use q0 = Qubit(); use q1 = Qubit(); Rxx(1.0, q0, q1); use q2 = Qubit(); use q3 = Qubit(); Rxx(1.0, q2, q3); } } ", CircuitEntryPoint::EntryPoint, ); expect![[r#" q_0@test.qs:6:20 ─ Rxx(1.0000)@test.qs:8:20 ── ┆ q_1@test.qs:7:20 ─ Rxx(1.0000)@test.qs:8:20 ── q_2@test.qs:10:20 ─ Rxx(1.0000)@test.qs:12:20 ─ ┆ q_3@test.qs:11:20 ─ Rxx(1.0000)@test.qs:12:20 ─ "#]] .assert_eq(&circ); } #[test] fn operation_with_subsequent_qubits_no_double_rows() { let circ = circuit_static( r" namespace Test { import Std.Measurement.*; @EntryPoint() operation Main() : Unit { use q0 = Qubit(); use q1 = Qubit(); Rxx(1.0, q0, q1); Rxx(1.0, q0, q1); } } ", ); expect![[r#" q_0 ─ Rxx(1.0000)@test.qs:8:20 ─── Rxx(1.0000)@test.qs:9:20 ── ┆ ┆ q_1 ─ Rxx(1.0000)@test.qs:8:20 ─── Rxx(1.0000)@test.qs:9:20 ── "#]] .assert_eq(&circ.to_string()); } #[test] fn operation_with_subsequent_qubits_no_added_rows() { let circ = circuit_static( r" namespace Test { import Std.Measurement.*; @EntryPoint() operation Main() : Result[] { use q0 = Qubit(); use q1 = Qubit(); Rxx(1.0, q0, q1); use q2 = Qubit(); use q3 = Qubit(); Rxx(1.0, q2, q3); [M(q0), M(q2)] } } ", ); expect![[r#" q_0 ─ Rxx(1.0000)@test.qs:8:20 ─── M@test.qs:14:21 ─ ┆ ╘═════════ q_1 ─ Rxx(1.0000)@test.qs:8:20 ───────────────────── q_2 ─ Rxx(1.0000)@test.qs:12:20 ── M@test.qs:14:28 ─ ┆ ╘═════════ q_3 ─ Rxx(1.0000)@test.qs:12:20 ──────────────────── "#]] .assert_eq(&circ.to_string()); } #[test] fn operation_declared_in_eval() { let mut interpreter = interpreter("", PackageType::Lib, Profile::Unrestricted); let mut out = std::io::sink(); let mut r = GenericReceiver::new(&mut out); interpreter .eval_fragments( &mut r, "operation Foo() : Result { use q = Qubit(); H(q); return M(q) }", ) .expect("eval should succeed"); let c = interpreter .circuit( CircuitEntryPoint::EntryExpr("Foo()".into()), CircuitGenerationMethod::ClassicalEval, TracerConfig { max_operations: usize::MAX, source_locations: false, group_by_scope: true, ..default_test_tracer_config() }, ) .expect("circuit generation should succeed"); expect![[r#" q_0 ─ Foo[1] ── ╘═════ [1] Foo: q_0 ── H ──── M ── ╘═══ "#]] .assert_eq(&c.display_with_groups().to_string()); } #[test] fn static_entrypoint_handles_callable_returned_from_function() { let circ = circuit_with_options_success( r#" namespace Test { operation ApplyOp(op : Qubit => Unit, q : Qubit) : Unit { op(q); } function GetOp() : Qubit => Unit { H } @EntryPoint() operation Main() : Unit { use q = Qubit(); ApplyOp(GetOp(), q); } } "#, Profile::AdaptiveRIF, CircuitEntryPoint::EntryPoint, CircuitGenerationMethod::Static, TracerConfig { source_locations: false, ..default_test_tracer_config() }, ) .to_string(); expect![[r#" q_0 ── H ── "#]] .assert_eq(&circ); } #[test] fn grouped_scopes_use_source_name_for_specialized_direct_callables() { let circ = circuit_with_groups_without_source_locations( r#" namespace Test { operation ApplyOp(op : Qubit => Unit, q : Qubit) : Unit { op(q); } @EntryPoint() operation Main() : Unit { use q = Qubit(); ApplyOp(H, q); } } "#, CircuitEntryPoint::EntryPoint, ); expect![[r#" q_0 ─ [ [Main] ─── [ [ApplyOp] ─── H ──── ] ──── ] ── "#]] .assert_eq(&circ); } #[test] fn grouped_scopes_use_source_name_for_specialized_callable_arrays() { let circ = circuit_with_groups_without_source_locations( r#" namespace Test { operation ApplyOp(op : Qubit => Unit, q : Qubit) : Unit { op(q); } @EntryPoint() operation Main() : Unit { use q = Qubit(); let ops = [H, X]; for op in ops { ApplyOp(op, q); } } } "#, CircuitEntryPoint::EntryPoint, ); expect![[r#" q_0 ─ [ [Main] ─── [ [loop: ops] ── [ [(1)] ── [ [ApplyOp] ─── H ──── ] ──── ] ─── [ [(2)] ── [ [ApplyOp] ─── X ──── ] ──── ] ──── ] ──── ] ── "#]] .assert_eq(&circ); } #[test] fn grouped_scopes_match_for_user_defined_adjoint_specialization() { let circ = circuit_with_groups_without_source_locations( r#" namespace Test { operation EncodeAsLogicalQubit(physicalQubit : Qubit, aux : Qubit[]) : Unit is Adj { ApplyToEachA(CNOT(physicalQubit, _), aux); } @EntryPoint() operation Main() : Unit { use logicalQubit = Qubit[3]; EncodeAsLogicalQubit(logicalQubit[0], logicalQubit[1...]); Adjoint EncodeAsLogicalQubit(logicalQubit[0], logicalQubit[1...]); } } "#, CircuitEntryPoint::EntryPoint, ); expect![[r#" q_0 ─ Main[1] ─ ┆ q_1 ─ Main[1] ─ ┆ q_2 ─ Main[1] ─ [1] Main: q_0 ─ EncodeAsLogicalQubit[2] ── EncodeAsLogicalQubit'[3] ── ┆ ┆ q_1 ─ EncodeAsLogicalQubit[2] ── EncodeAsLogicalQubit'[3] ── ┆ ┆ q_2 ─ EncodeAsLogicalQubit[2] ── EncodeAsLogicalQubit'[3] ── [2] EncodeAsLogicalQubit: q_0 ─ .lambda_3[4] ── ┆ q_1 ─ .lambda_3[4] ── ┆ q_2 ─ .lambda_3[4] ── [3] EncodeAsLogicalQubit: q_0 ─ .lambda_3'[5] ─ ┆ q_1 ─ .lambda_3'[5] ─ ┆ q_2 ─ .lambda_3'[5] ─ [4] .lambda_3: q_0 ── ● ──── ● ── q_1 ── X ─────┼─── q_2 ───────── X ── [5] .lambda_3: q_0 ── ● ──── ● ── q_1 ───┼───── X ── q_2 ── X ───────── "#]] .assert_eq(&circ); } #[test] fn grouped_scopes_match_for_apply_operation_power_ca_lambda() { let circ = circuit_with_groups_without_source_locations( r#" namespace Test { operation U(q : Qubit) : Unit is Ctl + Adj { Rz(Std.Math.PI() / 3.0, q); } @EntryPoint() operation Main() : Unit { use state = Qubit(); use phase = Qubit[2]; let oracle = ApplyOperationPowerCA(_, qs => U(qs[0]), _); ApplyQPE(oracle, [state], phase); } } "#, CircuitEntryPoint::EntryPoint, ); expect![[r#" q_0 ─ Main[1] ─ ┆ q_1 ─ Main[1] ─ ┆ q_2 ─ Main[1] ─ [1] Main: q_0 ──────── U[2] ──────────────────────────────────────────────────────────────────── ┆ q_1 ── H ─── U[2] ──────── H ─────── Rz(-0.7854) ─── X ─── Rz(0.7854) ──── X ───────── ┆ │ │ q_2 ── H ─── U[2] ─── Rz(-0.7854) ────────────────── ● ─────────────────── ● ──── H ── [2] U: q_0 ─ Rz(0.5236) ──── X ─── Rz(-0.5236) ─── X ─── Rz(0.5236) ──── X ─── Rz(-0.5236) ─── X ─── Rz(0.5236) ──── X ─── Rz(-0.5236) ─── X ── q_1 ───────────────── ● ─────────────────── ● ─────────────────── ● ─────────────────── ● ────────────────────┼─────────────────────┼─── q_2 ───────────────────────────────────────────────────────────────────────────────────────────────────────── ● ─────────────────── ● ── "#]] .assert_eq(&circ); } #[test] fn grouped_scopes_match_for_repeated_draw_random_bit_calls() { let circ = circuit_with_groups_without_source_locations( r#" namespace Test { operation DrawRandomBit() : Unit { use q = Qubit(); H(q); MResetZ(q); } @EntryPoint() operation Main() : Unit { DrawRandomBit(); DrawRandomBit(); } } "#, CircuitEntryPoint::EntryPoint, ); expect![[r#" q_0 ─ Main[1] ─ ╘═════ ╘═════ [1] Main: q_0 ─ DrawRandomBit[2] ─── DrawRandomBit[3] ── ╘════════════════════┆══════════ ╘══════════ [2] DrawRandomBit: q_0 ── H ──── M ──── |0〉 ── ╘════════════ [3] DrawRandomBit: q_0 ── H ──── M ──── |0〉 ── │ ╘════════════ "#]] .assert_eq(&circ); } /// Tests that invoke circuit generation through the debugger. mod debugger_stepping { use super::Debugger; use crate::target::Profile; use expect_test::expect; use qsc_data_structures::language_features::LanguageFeatures; use qsc_data_structures::line_column::Encoding; use qsc_data_structures::source::SourceMap; use qsc_eval::{StepAction, StepResult, output::GenericReceiver}; use std::fmt::Write; /// Steps through the code in the debugger and collects the /// circuit representation at each step. fn generate_circuit_steps(code: &str, profile: Profile) -> String { let sources = SourceMap::new([("test.qs".into(), code.into())], None); let (std_id, store) = crate::compile::package_store_with_stdlib(profile.into()); let mut debugger = Debugger::new( sources, profile.into(), Encoding::Utf8, LanguageFeatures::default(), store, &[(std_id, None)], ) .expect("debugger creation should succeed"); debugger.interpreter.set_quantum_seed(Some(2)); let mut out = std::io::sink(); let mut r = GenericReceiver::new(&mut out); let mut circs = String::new(); let mut result = debugger .eval_step(&mut r, &[], StepAction::In) .expect("step should succeed"); write!(&mut circs, "step:\n{}", debugger.circuit()).expect("write should succeed"); while !matches!(result, StepResult::Return(_)) { result = debugger .eval_step(&mut r, &[], StepAction::Next) .expect("step should succeed"); write!(&mut circs, "step:\n{}", debugger.circuit()).expect("write should succeed"); } circs } #[test] fn base_profile() { let circs = generate_circuit_steps( r" namespace Test { import Std.Measurement.*; @EntryPoint() operation Main() : Result[] { use q = Qubit(); H(q); let r = M(q); Reset(q); [r] } } ", Profile::Base, ); expect![[r#" step: step: q_0@test.qs:5:24 step: q_0@test.qs:5:24 ─ H@test.qs:6:24 ── step: q_0@test.qs:5:24 ─ H@test.qs:6:24 ─── M@test.qs:7:32 ── ╘═════════ step: q_0@test.qs:5:24 ─ H@test.qs:6:24 ─── M@test.qs:7:32 ──── |0〉@test.qs:8:24 ─── ╘════════════════════════════════ step: q_0@test.qs:5:24 ─ H@test.qs:6:24 ─── M@test.qs:7:32 ──── |0〉@test.qs:8:24 ─── ╘════════════════════════════════ step: q_0@test.qs:5:24 ─ H@test.qs:6:24 ─── M@test.qs:7:32 ──── |0〉@test.qs:8:24 ─── ╘════════════════════════════════ "#]] .assert_eq(&circs); } #[test] fn unrestricted_profile() { let circs = generate_circuit_steps( r" namespace Test { import Std.Measurement.*; @EntryPoint() operation Main() : Result[] { use q = Qubit(); H(q); let r = M(q); Reset(q); [r] } } ", Profile::Unrestricted, ); expect![[r#" step: step: q_0@test.qs:5:24 step: q_0@test.qs:5:24 ─ H@test.qs:6:24 ── step: q_0@test.qs:5:24 ─ H@test.qs:6:24 ─── M@test.qs:7:32 ── ╘═════════ step: q_0@test.qs:5:24 ─ H@test.qs:6:24 ─── M@test.qs:7:32 ──── |0〉@test.qs:8:24 ─── ╘════════════════════════════════ step: q_0@test.qs:5:24 ─ H@test.qs:6:24 ─── M@test.qs:7:32 ──── |0〉@test.qs:8:24 ─── ╘════════════════════════════════ step: q_0@test.qs:5:24 ─ H@test.qs:6:24 ─── M@test.qs:7:32 ──── |0〉@test.qs:8:24 ─── ╘════════════════════════════════ "#]] .assert_eq(&circs); } #[test] fn unrestricted_profile_result_comparison() { let circs = generate_circuit_steps( r" namespace Test { import Std.Measurement.*; @EntryPoint() operation Main() : Result[] { use q = Qubit(); H(q); let r = M(q); if (r == One) { X(q); } [r] } } ", Profile::Unrestricted, ); // We set the random seed in the test to account for // the nondeterministic output. Since the debugger is running // the real simulator, the circuit is going to vary from run to run // depending on measurement outcomes. expect![[r#" step: step: q_0@test.qs:5:24 step: q_0@test.qs:5:24 ─ H@test.qs:6:24 ── step: q_0@test.qs:5:24 ─ H@test.qs:6:24 ─── M@test.qs:7:32 ── ╘═════════ step: q_0@test.qs:5:24 ─ H@test.qs:6:24 ─── M@test.qs:7:32 ── ╘═════════ step: q_0@test.qs:5:24 ─ H@test.qs:6:24 ─── M@test.qs:7:32 ─── X@test.qs:9:28 ── ╘════════════════════════════ step: q_0@test.qs:5:24 ─ H@test.qs:6:24 ─── M@test.qs:7:32 ─── X@test.qs:9:28 ── ╘════════════════════════════ step: q_0@test.qs:5:24 ─ H@test.qs:6:24 ─── M@test.qs:7:32 ─── X@test.qs:9:28 ── ╘════════════════════════════ step: q_0@test.qs:5:24 ─ H@test.qs:6:24 ─── M@test.qs:7:32 ─── X@test.qs:9:28 ── ╘════════════════════════════ "#]] .assert_eq(&circs); } }