diff --git a/compiler/qsc/src/codegen/tests.rs b/compiler/qsc/src/codegen/tests.rs index 82ef545884..fca4a58ca6 100644 --- a/compiler/qsc/src/codegen/tests.rs +++ b/compiler/qsc/src/codegen/tests.rs @@ -250,6 +250,137 @@ mod base_profile { !3 = !{i32 1, !"dynamic_result_management", i1 false} "#]].assert_eq(&qir); } + + #[test] + fn qubit_id_swap_results_in_different_id_usage() { + let source = "namespace Test { + @EntryPoint() + operation Main() : (Result, Result) { + use (q0, q1) = (Qubit(), Qubit()); + X(q0); + SwapLabels(q0, q1); + X(q1); + (MResetZ(q0), MResetZ(q1)) + } + }"; + let sources = SourceMap::new([("test.qs".into(), source.into())], None); + let language_features = LanguageFeatures::default(); + let capabilities = TargetCapabilityFlags::empty(); + + let (std_id, store) = crate::compile::package_store_with_stdlib(capabilities); + let qir = get_qir( + sources, + language_features, + capabilities, + store, + &[(std_id, None)], + ) + .expect("Failed to generate QIR"); + expect![[r#" + %Result = type opaque + %Qubit = type opaque + + define void @ENTRYPOINT__main() #0 { + block_0: + call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) + call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) + call void @__quantum__rt__tuple_record_output(i64 2, i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null) + ret void + } + + declare void @__quantum__qis__x__body(%Qubit*) + + declare void @__quantum__rt__tuple_record_output(i64, i8*) + + declare void @__quantum__rt__result_record_output(%Result*, i8*) + + declare void @__quantum__qis__m__body(%Qubit*, %Result*) #1 + + attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="base_profile" "required_num_qubits"="2" "required_num_results"="2" } + attributes #1 = { "irreversible" } + + ; module flags + + !llvm.module.flags = !{!0, !1, !2, !3} + + !0 = !{i32 1, !"qir_major_version", i32 1} + !1 = !{i32 7, !"qir_minor_version", i32 0} + !2 = !{i32 1, !"dynamic_qubit_management", i1 false} + !3 = !{i32 1, !"dynamic_result_management", i1 false} + "#]].assert_eq(&qir); + } + + #[test] + fn qubit_id_swap_across_reset_uses_updated_ids() { + let source = "namespace Test { + @EntryPoint() + operation Main() : (Result, Result) { + { + use (q0, q1) = (Qubit(), Qubit()); + X(q0); + SwapLabels(q0, q1); + X(q1); + Reset(q0); + Reset(q1); + } + use (q0, q1) = (Qubit(), Qubit()); + (MResetZ(q0), MResetZ(q1)) + } + }"; + let sources = SourceMap::new([("test.qs".into(), source.into())], None); + let language_features = LanguageFeatures::default(); + let capabilities = TargetCapabilityFlags::empty(); + + let (std_id, store) = crate::compile::package_store_with_stdlib(capabilities); + let qir = get_qir( + sources, + language_features, + capabilities, + store, + &[(std_id, None)], + ) + .expect("Failed to generate QIR"); + expect![[r#" + %Result = type opaque + %Qubit = type opaque + + define void @ENTRYPOINT__main() #0 { + block_0: + call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 3 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) + call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) + call void @__quantum__rt__tuple_record_output(i64 2, i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null) + ret void + } + + declare void @__quantum__qis__x__body(%Qubit*) + + declare void @__quantum__rt__tuple_record_output(i64, i8*) + + declare void @__quantum__rt__result_record_output(%Result*, i8*) + + declare void @__quantum__qis__m__body(%Qubit*, %Result*) #1 + + attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="base_profile" "required_num_qubits"="4" "required_num_results"="2" } + attributes #1 = { "irreversible" } + + ; module flags + + !llvm.module.flags = !{!0, !1, !2, !3} + + !0 = !{i32 1, !"qir_major_version", i32 1} + !1 = !{i32 7, !"qir_minor_version", i32 0} + !2 = !{i32 1, !"dynamic_qubit_management", i1 false} + !3 = !{i32 1, !"dynamic_result_management", i1 false} + "#]].assert_eq(&qir); + } } mod adaptive_profile { @@ -686,6 +817,239 @@ mod adaptive_ri_profile { "#]].assert_eq(&qir); } + #[test] + fn qubit_id_swap_results_in_different_id_usage() { + let source = "namespace Test { + @EntryPoint() + operation Main() : (Result, Result) { + use (q0, q1) = (Qubit(), Qubit()); + X(q0); + SwapLabels(q0, q1); + X(q1); + (MResetZ(q0), MResetZ(q1)) + } + }"; + let sources = SourceMap::new([("test.qs".into(), source.into())], None); + let language_features = LanguageFeatures::default(); + let capabilities = TargetCapabilityFlags::Adaptive + | TargetCapabilityFlags::QubitReset + | TargetCapabilityFlags::IntegerComputations; + + let (std_id, store) = crate::compile::package_store_with_stdlib(capabilities); + let qir = get_qir( + sources, + language_features, + capabilities, + store, + &[(std_id, None)], + ) + .expect("Failed to generate QIR"); + expect![[r#" + %Result = type opaque + %Qubit = type opaque + + define void @ENTRYPOINT__main() #0 { + block_0: + call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) + call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) + call void @__quantum__rt__tuple_record_output(i64 2, i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null) + ret void + } + + declare void @__quantum__qis__x__body(%Qubit*) + + declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*) #1 + + declare void @__quantum__rt__tuple_record_output(i64, i8*) + + declare void @__quantum__rt__result_record_output(%Result*, i8*) + + attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="2" "required_num_results"="2" } + attributes #1 = { "irreversible" } + + ; module flags + + !llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7, !8, !9, !10} + + !0 = !{i32 1, !"qir_major_version", i32 1} + !1 = !{i32 7, !"qir_minor_version", i32 0} + !2 = !{i32 1, !"dynamic_qubit_management", i1 false} + !3 = !{i32 1, !"dynamic_result_management", i1 false} + !4 = !{i32 1, !"classical_ints", i1 true} + !5 = !{i32 1, !"qubit_resetting", i1 true} + !6 = !{i32 1, !"classical_floats", i1 false} + !7 = !{i32 1, !"backwards_branching", i1 false} + !8 = !{i32 1, !"classical_fixed_points", i1 false} + !9 = !{i32 1, !"user_functions", i1 false} + !10 = !{i32 1, !"multiple_target_branching", i1 false} + "#]].assert_eq(&qir); + } + + #[test] + fn qubit_id_swap_across_reset_uses_updated_ids() { + let source = "namespace Test { + @EntryPoint() + operation Main() : (Result, Result) { + { + use (q0, q1) = (Qubit(), Qubit()); + X(q0); + SwapLabels(q0, q1); + X(q1); + Reset(q0); + Reset(q1); + } + use (q0, q1) = (Qubit(), Qubit()); + (MResetZ(q0), MResetZ(q1)) + } + }"; + let sources = SourceMap::new([("test.qs".into(), source.into())], None); + let language_features = LanguageFeatures::default(); + let capabilities = TargetCapabilityFlags::Adaptive + | TargetCapabilityFlags::QubitReset + | TargetCapabilityFlags::IntegerComputations; + + let (std_id, store) = crate::compile::package_store_with_stdlib(capabilities); + let qir = get_qir( + sources, + language_features, + capabilities, + store, + &[(std_id, None)], + ) + .expect("Failed to generate QIR"); + expect![[r#" + %Result = type opaque + %Qubit = type opaque + + define void @ENTRYPOINT__main() #0 { + block_0: + call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 1 to %Qubit*)) + call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) + call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) + call void @__quantum__rt__tuple_record_output(i64 2, i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null) + ret void + } + + declare void @__quantum__qis__x__body(%Qubit*) + + declare void @__quantum__qis__reset__body(%Qubit*) + + declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*) #1 + + declare void @__quantum__rt__tuple_record_output(i64, i8*) + + declare void @__quantum__rt__result_record_output(%Result*, i8*) + + attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="2" "required_num_results"="2" } + attributes #1 = { "irreversible" } + + ; module flags + + !llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7, !8, !9, !10} + + !0 = !{i32 1, !"qir_major_version", i32 1} + !1 = !{i32 7, !"qir_minor_version", i32 0} + !2 = !{i32 1, !"dynamic_qubit_management", i1 false} + !3 = !{i32 1, !"dynamic_result_management", i1 false} + !4 = !{i32 1, !"classical_ints", i1 true} + !5 = !{i32 1, !"qubit_resetting", i1 true} + !6 = !{i32 1, !"classical_floats", i1 false} + !7 = !{i32 1, !"backwards_branching", i1 false} + !8 = !{i32 1, !"classical_fixed_points", i1 false} + !9 = !{i32 1, !"user_functions", i1 false} + !10 = !{i32 1, !"multiple_target_branching", i1 false} + "#]].assert_eq(&qir); + } + + #[test] + fn qubit_id_swap_with_out_of_order_release_uses_correct_ids() { + let source = "namespace Test { + @EntryPoint() + operation Main() : (Result, Result) { + let q0 = QIR.Runtime.__quantum__rt__qubit_allocate(); + let q1 = QIR.Runtime.__quantum__rt__qubit_allocate(); + let q2 = QIR.Runtime.__quantum__rt__qubit_allocate(); + X(q0); + X(q1); + X(q2); + SwapLabels(q0, q1); + QIR.Runtime.__quantum__rt__qubit_release(q0); + let q3 = QIR.Runtime.__quantum__rt__qubit_allocate(); + X(q3); + (MResetZ(q3), MResetZ(q1)) + } + }"; + let sources = SourceMap::new([("test.qs".into(), source.into())], None); + let language_features = LanguageFeatures::default(); + let capabilities = TargetCapabilityFlags::Adaptive + | TargetCapabilityFlags::QubitReset + | TargetCapabilityFlags::IntegerComputations; + + let (std_id, store) = crate::compile::package_store_with_stdlib(capabilities); + let qir = get_qir( + sources, + language_features, + capabilities, + store, + &[(std_id, None)], + ) + .expect("Failed to generate QIR"); + expect![[r#" + %Result = type opaque + %Qubit = type opaque + + define void @ENTRYPOINT__main() #0 { + block_0: + call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 1 to %Qubit*)) + call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 2 to %Qubit*)) + call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 1 to %Qubit*)) + call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) + call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 1 to %Result*)) + call void @__quantum__rt__tuple_record_output(i64 2, i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null) + ret void + } + + declare void @__quantum__qis__x__body(%Qubit*) + + declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*) #1 + + declare void @__quantum__rt__tuple_record_output(i64, i8*) + + declare void @__quantum__rt__result_record_output(%Result*, i8*) + + attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="3" "required_num_results"="2" } + attributes #1 = { "irreversible" } + + ; module flags + + !llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7, !8, !9, !10} + + !0 = !{i32 1, !"qir_major_version", i32 1} + !1 = !{i32 7, !"qir_minor_version", i32 0} + !2 = !{i32 1, !"dynamic_qubit_management", i1 false} + !3 = !{i32 1, !"dynamic_result_management", i1 false} + !4 = !{i32 1, !"classical_ints", i1 true} + !5 = !{i32 1, !"qubit_resetting", i1 true} + !6 = !{i32 1, !"classical_floats", i1 false} + !7 = !{i32 1, !"backwards_branching", i1 false} + !8 = !{i32 1, !"classical_fixed_points", i1 false} + !9 = !{i32 1, !"user_functions", i1 false} + !10 = !{i32 1, !"multiple_target_branching", i1 false} + "#]].assert_eq(&qir); + } + #[test] fn dynamic_integer_with_branch_and_phi_supported() { let source = "namespace Test { diff --git a/compiler/qsc_circuit/src/builder.rs b/compiler/qsc_circuit/src/builder.rs index c40d8d9be6..6c00ab3224 100644 --- a/compiler/qsc_circuit/src/builder.rs +++ b/compiler/qsc_circuit/src/builder.rs @@ -7,7 +7,6 @@ use crate::{ }; use num_bigint::BigUint; use num_complex::Complex; -use qsc_codegen::remapper::{HardwareId, Remapper}; use qsc_data_structures::index_map::IndexMap; use qsc_eval::{backend::Backend, val::Value}; use std::{fmt::Write, mem::take, rc::Rc}; @@ -181,6 +180,10 @@ impl Backend for Builder { self.remapper.qubit_release(q); } + fn qubit_swap_id(&mut self, q0: usize, q1: usize) { + self.remapper.swap(q0, q1); + } + fn capture_quantum_state(&mut self) -> (Vec<(BigUint, Complex)>, usize) { (Vec::new(), 0) } @@ -370,6 +373,92 @@ impl Builder { } } +/// Provides support for qubit id allocation, measurement and +/// reset operations for Base Profile targets. +/// +/// Since qubit reuse is disallowed, a mapping is maintained +/// from allocated qubit ids to hardware qubit ids. Each time +/// a qubit is reset, it is remapped to a fresh hardware qubit. +/// +/// Note that even though qubit reset & reuse is disallowed, +/// qubit ids are still reused for new allocations. +/// Measurements are tracked and deferred. +#[derive(Default)] +struct Remapper { + next_meas_id: usize, + next_qubit_id: usize, + next_qubit_hardware_id: HardwareId, + qubit_map: IndexMap, + measurements: Vec<(HardwareId, usize)>, +} + +impl Remapper { + fn map(&mut self, qubit: usize) -> HardwareId { + if let Some(mapped) = self.qubit_map.get(qubit) { + *mapped + } else { + let mapped = self.next_qubit_hardware_id; + self.next_qubit_hardware_id.0 += 1; + self.qubit_map.insert(qubit, mapped); + mapped + } + } + + fn m(&mut self, q: usize) -> usize { + let mapped_q = self.map(q); + let id = self.get_meas_id(); + self.measurements.push((mapped_q, id)); + id + } + + fn mreset(&mut self, q: usize) -> usize { + let id = self.m(q); + self.reset(q); + id + } + + fn reset(&mut self, q: usize) { + self.qubit_map.remove(q); + } + + fn qubit_allocate(&mut self) -> usize { + let id = self.next_qubit_id; + self.next_qubit_id += 1; + let _ = self.map(id); + id + } + + fn qubit_release(&mut self, _q: usize) { + self.next_qubit_id -= 1; + } + + fn swap(&mut self, q0: usize, q1: usize) { + let q0_mapped = self.map(q0); + let q1_mapped = self.map(q1); + self.qubit_map.insert(q0, q1_mapped); + self.qubit_map.insert(q1, q0_mapped); + } + + fn measurements(&self) -> impl Iterator { + self.measurements.iter() + } + + #[must_use] + fn num_qubits(&self) -> usize { + self.next_qubit_hardware_id.0 + } + + #[must_use] + fn get_meas_id(&mut self) -> usize { + let id = self.next_meas_id; + self.next_meas_id += 1; + id + } +} + +#[derive(Copy, Clone, Default)] +pub struct HardwareId(pub usize); + #[allow(clippy::unicode_not_nfc)] static KET_ZERO: &str = "|0〉"; diff --git a/compiler/qsc_codegen/src/lib.rs b/compiler/qsc_codegen/src/lib.rs index b4023885f0..c577611898 100644 --- a/compiler/qsc_codegen/src/lib.rs +++ b/compiler/qsc_codegen/src/lib.rs @@ -3,4 +3,3 @@ pub mod qir; pub mod qsharp; -pub mod remapper; diff --git a/compiler/qsc_codegen/src/remapper.rs b/compiler/qsc_codegen/src/remapper.rs deleted file mode 100644 index d2f506a590..0000000000 --- a/compiler/qsc_codegen/src/remapper.rs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use qsc_data_structures::index_map::IndexMap; - -/// Provides support for qubit id allocation, measurement and -/// reset operations for Base Profile targets. -/// -/// Since qubit reuse is disallowed, a mapping is maintained -/// from allocated qubit ids to hardware qubit ids. Each time -/// a qubit is reset, it is remapped to a fresh hardware qubit. -/// -/// Note that even though qubit reset & reuse is disallowed, -/// qubit ids are still reused for new allocations. -/// Measurements are tracked and deferred. -#[derive(Default)] -pub struct Remapper { - next_meas_id: usize, - next_qubit_id: usize, - next_qubit_hardware_id: HardwareId, - qubit_map: IndexMap, - measurements: Vec<(HardwareId, usize)>, -} - -impl Remapper { - pub fn map(&mut self, qubit: usize) -> HardwareId { - if let Some(mapped) = self.qubit_map.get(qubit) { - *mapped - } else { - let mapped = self.next_qubit_hardware_id; - self.next_qubit_hardware_id.0 += 1; - self.qubit_map.insert(qubit, mapped); - mapped - } - } - - pub fn m(&mut self, q: usize) -> usize { - let mapped_q = self.map(q); - let id = self.get_meas_id(); - self.measurements.push((mapped_q, id)); - id - } - - pub fn mreset(&mut self, q: usize) -> usize { - let id = self.m(q); - self.reset(q); - id - } - - pub fn reset(&mut self, q: usize) { - self.qubit_map.remove(q); - } - - pub fn qubit_allocate(&mut self) -> usize { - let id = self.next_qubit_id; - self.next_qubit_id += 1; - let _ = self.map(id); - id - } - - pub fn qubit_release(&mut self, _q: usize) { - self.next_qubit_id -= 1; - } - - pub fn measurements(&self) -> impl Iterator { - self.measurements.iter() - } - - #[must_use] - pub fn num_qubits(&self) -> usize { - self.next_qubit_hardware_id.0 - } - - #[must_use] - pub fn num_measurements(&self) -> usize { - self.next_meas_id - } - - #[must_use] - fn get_meas_id(&mut self) -> usize { - let id = self.next_meas_id; - self.next_meas_id += 1; - id - } -} - -#[derive(Copy, Clone, Default)] -pub struct HardwareId(pub usize); diff --git a/compiler/qsc_eval/src/backend.rs b/compiler/qsc_eval/src/backend.rs index b281ed9448..b1fbab68b5 100644 --- a/compiler/qsc_eval/src/backend.rs +++ b/compiler/qsc_eval/src/backend.rs @@ -85,17 +85,18 @@ pub trait Backend { fn qubit_release(&mut self, _q: usize) { unimplemented!("qubit_release operation"); } + fn qubit_swap_id(&mut self, _q0: usize, _q1: usize) { + unimplemented!("qubit_swap_id operation"); + } fn capture_quantum_state(&mut self) -> (Vec<(BigUint, Complex)>, usize) { unimplemented!("capture_quantum_state operation"); } fn qubit_is_zero(&mut self, _q: usize) -> bool { unimplemented!("qubit_is_zero operation"); } - fn custom_intrinsic(&mut self, _name: &str, _arg: Value) -> Option> { None } - fn set_seed(&mut self, _seed: Option) {} } @@ -240,6 +241,10 @@ impl Backend for SparseSim { self.sim.release(q); } + fn qubit_swap_id(&mut self, q0: usize, q1: usize) { + self.sim.swap_qubit_ids(q0, q1); + } + fn capture_quantum_state(&mut self) -> (Vec<(BigUint, Complex)>, usize) { let (state, count) = self.sim.get_state(); // Because the simulator returns the state indices with opposite endianness from the @@ -458,6 +463,11 @@ where self.main.qubit_release(q); } + fn qubit_swap_id(&mut self, q0: usize, q1: usize) { + self.chained.qubit_swap_id(q0, q1); + self.main.qubit_swap_id(q0, q1); + } + fn capture_quantum_state( &mut self, ) -> (Vec<(num_bigint::BigUint, num_complex::Complex)>, usize) { diff --git a/compiler/qsc_eval/src/intrinsic.rs b/compiler/qsc_eval/src/intrinsic.rs index 7b5a3420ce..cb4a334bd5 100644 --- a/compiler/qsc_eval/src/intrinsic.rs +++ b/compiler/qsc_eval/src/intrinsic.rs @@ -15,7 +15,7 @@ use crate::{ }; use num_bigint::BigInt; use rand::{rngs::StdRng, Rng}; -use rustc_hash::FxHashSet; +use rustc_hash::{FxHashMap, FxHashSet}; use std::array; use std::convert::TryFrom; @@ -77,6 +77,7 @@ pub(crate) fn call( Err(_) => Err(Error::OutputFail(name_span)), } } + "Relabel" => qubit_relabel(arg, arg_span, |q0, q1| sim.qubit_swap_id(q0, q1)), "Message" => match out.message(&arg.unwrap_string()) { Ok(()) => Ok(Value::unit()), Err(_) => Err(Error::OutputFail(name_span)), @@ -249,6 +250,82 @@ fn two_qubit_rotation( } } +/// Performs relabeling of qubits from the a given left array to the corresponding right array. +/// The function will swap qubits with the given function to match the new relabeling, returning an error +/// if the qubits are not unique or if the relabeling is not a valid permutation. +pub fn qubit_relabel( + arg: Value, + arg_span: PackageSpan, + mut swap: impl FnMut(usize, usize), +) -> Result { + let [left, right] = unwrap_tuple(arg); + let left = left + .unwrap_array() + .iter() + .map(|q| q.clone().unwrap_qubit().0) + .collect::>(); + let right = right + .unwrap_array() + .iter() + .map(|q| q.clone().unwrap_qubit().0) + .collect::>(); + let left_set = left.iter().collect::>(); + let right_set = right.iter().collect::>(); + if left.len() != left_set.len() || right.len() != right_set.len() { + return Err(Error::QubitUniqueness(arg_span)); + } + if left_set != right_set { + return Err(Error::RelabelingMismatch(arg_span)); + } + + let mut map = FxHashMap::default(); + for (l, r) in left.into_iter().zip(right.into_iter()) { + if l == r { + continue; + } + match (map.contains_key(&l), map.contains_key(&r)) { + (false, false) => { + // Neither qubit has been relabeled yet. + swap(l, r); + map.insert(l, r); + map.insert(r, l); + } + (false, true) => { + // The right qubit has been relabeled, so we need to swap the left qubit with the + // qubit that the right qubit was relabeled to. + let mapped = *map + .keys() + .find(|k| map[*k] == r) + .expect("mapped qubit should be present as both key and value"); + swap(l, mapped); + map.insert(l, r); + map.insert(mapped, l); + } + (true, false) => { + // The left qubit has been relabeled, so we swap the qubits as normal but + // remember the new mapping of the right qubit. + let mapped = *map.get(&l).expect("mapped qubit should be present"); + swap(l, r); + map.insert(l, r); + map.insert(r, mapped); + } + (true, true) => { + // Both qubits have been relabeled, so we need to swap the mapped right qubit with + // the left qubit and remember the new mapping of both qubits. + let mapped_l = *map.get(&l).expect("mapped qubit should be present"); + let mapped_r = *map.get(&r).expect("mapped qubit should be present"); + if mapped_l != r && mapped_r != l { + swap(mapped_r, l); + map.insert(mapped_r, mapped_l); + map.insert(l, r); + } + } + } + } + + Ok(Value::unit()) +} + fn unwrap_tuple(value: Value) -> [Value; N] { let values = value.unwrap_tuple(); array::from_fn(|i| values[i].clone()) diff --git a/compiler/qsc_eval/src/intrinsic/tests.rs b/compiler/qsc_eval/src/intrinsic/tests.rs index 1746c0578f..dd707a76fe 100644 --- a/compiler/qsc_eval/src/intrinsic/tests.rs +++ b/compiler/qsc_eval/src/intrinsic/tests.rs @@ -127,6 +127,10 @@ impl Backend for CustomSim { self.sim.qubit_release(q); } + fn qubit_swap_id(&mut self, q0: usize, q1: usize) { + self.sim.qubit_swap_id(q0, q1); + } + fn capture_quantum_state( &mut self, ) -> (Vec<(num_bigint::BigUint, num_complex::Complex)>, usize) { diff --git a/compiler/qsc_eval/src/lib.rs b/compiler/qsc_eval/src/lib.rs index 431105e4ce..45b036c309 100644 --- a/compiler/qsc_eval/src/lib.rs +++ b/compiler/qsc_eval/src/lib.rs @@ -20,7 +20,7 @@ mod tests; pub mod backend; pub mod debug; mod error; -mod intrinsic; +pub mod intrinsic; pub mod output; pub mod state; pub mod val; @@ -136,6 +136,11 @@ pub enum Error { #[diagnostic(code("Qsc.Eval.RangeStepZero"))] RangeStepZero(#[label("invalid range")] PackageSpan), + #[error("qubit arrays used in relabeling must be a permutation of the same set of qubits")] + #[diagnostic(help("ensure that each qubit is present exactly once in both arrays"))] + #[diagnostic(code("Qsc.Eval.RelabelingMismatch"))] + RelabelingMismatch(#[label] PackageSpan), + #[error("Qubit{0} released while not in |0⟩ state")] #[diagnostic(help("qubits should be returned to the |0⟩ state before being released to satisfy the assumption that allocated qubits start in the |0⟩ state"))] #[diagnostic(code("Qsc.Eval.ReleasedQubitNotZero"))] @@ -188,6 +193,7 @@ impl Error { | Error::QubitsNotCounted(span) | Error::QubitsNotSeparable(span) | Error::RangeStepZero(span) + | Error::RelabelingMismatch(span) | Error::ReleasedQubitNotZero(_, span) | Error::ResultComparisonUnsupported(span) | Error::UnboundName(span) diff --git a/compiler/qsc_eval/src/tests.rs b/compiler/qsc_eval/src/tests.rs index f392286e1d..56aadd3261 100644 --- a/compiler/qsc_eval/src/tests.rs +++ b/compiler/qsc_eval/src/tests.rs @@ -3724,7 +3724,7 @@ fn controlled_operation_with_duplicate_controls_fails() { 1, ), item: LocalItemId( - 130, + 132, ), }, caller: PackageId( @@ -3774,7 +3774,7 @@ fn controlled_operation_with_target_in_controls_fails() { 1, ), item: LocalItemId( - 130, + 132, ), }, caller: PackageId( diff --git a/compiler/qsc_partial_eval/src/lib.rs b/compiler/qsc_partial_eval/src/lib.rs index ee8f234958..cfe0ff749f 100644 --- a/compiler/qsc_partial_eval/src/lib.rs +++ b/compiler/qsc_partial_eval/src/lib.rs @@ -20,11 +20,12 @@ use miette::Diagnostic; use qsc_data_structures::{functors::FunctorApp, span::Span, target::TargetCapabilityFlags}; use qsc_eval::{ self, are_ctls_unique, exec_graph_section, + intrinsic::qubit_relabel, output::GenericReceiver, resolve_closure, val::{ self, index_array, slice_array, update_functor_app, update_index_range, - update_index_single, Value, Var, VarTy, + update_index_single, Qubit, Value, Var, VarTy, }, Error as EvalError, PackageSpan, State, StepAction, StepResult, Variable, }; @@ -568,7 +569,7 @@ impl<'a> PartialEvaluator<'a> { // Create the operands. let lhs_operand = Operand::Literal(Literal::Bool(lhs_bool)); - let rhs_operand = map_eval_value_to_rir_operand(&rhs_value); + let rhs_operand = self.map_eval_value_to_rir_operand(&rhs_value); // If both operands are literals, evaluate the binary operation and return its value. if let (Operand::Literal(lhs_literal), Operand::Literal(rhs_literal)) = @@ -657,7 +658,7 @@ impl<'a> PartialEvaluator<'a> { self.get_expr_package_span(rhs_expr_id), )); }; - let rhs_operand = map_eval_value_to_rir_operand(&rhs_value); + let rhs_operand = self.map_eval_value_to_rir_operand(&rhs_value); // Get the comparison result depending on the operator and the RHS value. let result_var = match (bin_op, rhs_operand) { @@ -733,7 +734,7 @@ impl<'a> PartialEvaluator<'a> { self.get_expr_package_span(rhs_expr_id), )); }; - let rhs_operand = map_eval_value_to_rir_operand(&rhs_value); + let rhs_operand = self.map_eval_value_to_rir_operand(&rhs_value); // Store the RHS value into the the variable that represents the result of the Boolean operation. let store_ins = Instruction::Store(rhs_operand, result_rir_var); @@ -783,7 +784,7 @@ impl<'a> PartialEvaluator<'a> { self.get_expr_package_span(rhs_expr_id), )); }; - let rhs_operand = map_eval_value_to_rir_operand(&rhs_value); + let rhs_operand = self.map_eval_value_to_rir_operand(&rhs_value); assert!( matches!(rhs_operand.get_type(), rir::Ty::Integer), "LHS value is expected to be of integer type" @@ -1246,6 +1247,7 @@ impl<'a> PartialEvaluator<'a> { store_item_id, callable_decl, args_value, + args_span, callee_expr_span, )? } @@ -1283,6 +1285,7 @@ impl<'a> PartialEvaluator<'a> { store_item_id: StoreItemId, callable_decl: &CallableDecl, args_value: Value, + args_span: PackageSpan, // For diagnostic purposes only. callee_expr_span: PackageSpan, // For diagnostic puprposes only. ) -> Result { // There are a few special cases regarding intrinsic callables. Identify them and handle them properly. @@ -1290,6 +1293,10 @@ impl<'a> PartialEvaluator<'a> { // Qubit allocations and measurements have special handling. "__quantum__rt__qubit_allocate" => Ok(self.allocate_qubit()), "__quantum__rt__qubit_release" => Ok(self.release_qubit(args_value)), + "Relabel" => qubit_relabel(args_value, args_span, |q0, q1| { + self.resource_manager.swap_qubit_ids(Qubit(q0), Qubit(q1)); + }) + .map_err(std::convert::Into::into), "__quantum__qis__m__body" => Ok(self.measure_qubit(builder::m_decl(), args_value)), "__quantum__qis__mresetz__body" => { Ok(self.measure_qubit(builder::mresetz_decl(), args_value)) @@ -1359,7 +1366,7 @@ impl<'a> PartialEvaluator<'a> { ); let args_operands = args .into_iter() - .map(|arg| map_eval_value_to_rir_operand(&arg.into_value())) + .map(|arg| self.map_eval_value_to_rir_operand(&arg.into_value())) .collect(); let instruction = Instruction::Call(callable_id, args_operands, None); @@ -1516,7 +1523,7 @@ impl<'a> PartialEvaluator<'a> { // If there is a variable to save the value of the if expression to, add a store instruction. if let Some(if_expr_var) = if_expr_var { - let body_operand = map_eval_value_to_rir_operand(&body_control.into_value()); + let body_operand = self.map_eval_value_to_rir_operand(&body_control.into_value()); let store_ins = Instruction::Store(body_operand, if_expr_var); self.get_current_rir_block_mut().0.push(store_ins); } @@ -1673,7 +1680,7 @@ impl<'a> PartialEvaluator<'a> { }; // Generate the instruction depending on the unary operator. - let value_operand = map_eval_value_to_rir_operand(&value); + let value_operand = self.map_eval_value_to_rir_operand(&value); let instruction = match un_op { UnOp::Neg => { let constant = match rir_variable_type { @@ -2184,7 +2191,7 @@ impl<'a> PartialEvaluator<'a> { .insert_hybrid_local_value(local_var_id, Value::Var(eval_var)); // Insert a store instruction. - let value_operand = map_eval_value_to_rir_operand(value); + let value_operand = self.map_eval_value_to_rir_operand(value); let rir_var = map_eval_var_to_rir_var(eval_var); let store_ins = Instruction::Store(value_operand, rir_var); self.get_current_rir_block_mut().0.push(store_ins); @@ -2234,9 +2241,9 @@ impl<'a> PartialEvaluator<'a> { // Get the qubit and result IDs to use in the qubit measure instruction. let qubit = args_value.unwrap_qubit(); let qubit_value = Value::Qubit(qubit); - let qubit_operand = map_eval_value_to_rir_operand(&qubit_value); + let qubit_operand = self.map_eval_value_to_rir_operand(&qubit_value); let result_value = Value::Result(self.resource_manager.next_result_register()); - let result_operand = map_eval_value_to_rir_operand(&result_value); + let result_operand = self.map_eval_value_to_rir_operand(&result_value); // Check if the callable has already been added to the program and if not do so now. let measure_callable_id = self.get_or_insert_callable(measure_callable); @@ -2486,7 +2493,7 @@ impl<'a> PartialEvaluator<'a> { .get_hybrid_local_value(local_var_id); if let Value::Var(var) = bound_value { // Insert a store instruction when the value of a variable is updated. - let rhs_operand = map_eval_value_to_rir_operand(&value); + let rhs_operand = self.map_eval_value_to_rir_operand(&value); let rir_var = map_eval_var_to_rir_var(*var); let store_ins = Instruction::Store(rhs_operand, rir_var); self.get_current_rir_block_mut().0.push(store_ins); @@ -2762,6 +2769,30 @@ impl<'a> PartialEvaluator<'a> { self.program.callables.insert(callable_id, callable); callable_id } + + fn map_eval_value_to_rir_operand(&self, value: &Value) -> Operand { + match value { + Value::Bool(b) => Operand::Literal(Literal::Bool(*b)), + Value::Double(d) => Operand::Literal(Literal::Double(*d)), + Value::Int(i) => Operand::Literal(Literal::Integer(*i)), + Value::Qubit(q) => Operand::Literal(Literal::Qubit( + self.resource_manager + .map_qubit(*q) + .try_into() + .expect("could not convert qubit ID to u32"), + )), + Value::Result(r) => match r { + val::Result::Id(id) => Operand::Literal(Literal::Result( + (*id) + .try_into() + .expect("could not convert result ID to u32"), + )), + val::Result::Val(bool) => Operand::Literal(Literal::Bool(*bool)), + }, + Value::Var(var) => Operand::Variable(map_eval_var_to_rir_var(*var)), + _ => panic!("{value} cannot be mapped to a RIR operand"), + } + } } fn eval_un_op_with_literals(un_op: UnOp, value: Value) -> Value { @@ -2906,27 +2937,6 @@ fn get_spec_decl(spec_impl: &SpecImpl, functor_app: FunctorApp) -> &SpecDecl { } } -fn map_eval_value_to_rir_operand(value: &Value) -> Operand { - match value { - Value::Bool(b) => Operand::Literal(Literal::Bool(*b)), - Value::Double(d) => Operand::Literal(Literal::Double(*d)), - Value::Int(i) => Operand::Literal(Literal::Integer(*i)), - Value::Qubit(q) => Operand::Literal(Literal::Qubit( - q.0.try_into().expect("could not convert qubit ID to u32"), - )), - Value::Result(r) => match r { - val::Result::Id(id) => Operand::Literal(Literal::Result( - (*id) - .try_into() - .expect("could not convert result ID to u32"), - )), - val::Result::Val(bool) => Operand::Literal(Literal::Bool(*bool)), - }, - Value::Var(var) => Operand::Variable(map_eval_var_to_rir_var(*var)), - _ => panic!("{value} cannot be mapped to a RIR operand"), - } -} - fn map_eval_var_to_rir_var(var: Var) -> rir::Variable { rir::Variable { variable_id: var.id.into(), diff --git a/compiler/qsc_partial_eval/src/management.rs b/compiler/qsc_partial_eval/src/management.rs index 5325b905ab..8d211a99de 100644 --- a/compiler/qsc_partial_eval/src/management.rs +++ b/compiler/qsc_partial_eval/src/management.rs @@ -3,6 +3,7 @@ use num_bigint::BigUint; use num_complex::Complex; +use qsc_data_structures::index_map::IndexMap; use qsc_eval::{ backend::Backend, val::{Qubit, Result, Value}, @@ -13,6 +14,7 @@ use qsc_rir::rir::{BlockId, CallableId, VariableId}; #[derive(Default)] pub struct ResourceManager { qubits_in_use: Vec, + qubit_id_map: IndexMap, next_callable: CallableId, next_block: BlockId, next_result_register: usize, @@ -20,6 +22,13 @@ pub struct ResourceManager { } impl ResourceManager { + pub fn map_qubit(&self, q: Qubit) -> usize { + *self + .qubit_id_map + .get(q.0) + .expect("qubit id should be in map") + } + /// Count of qubits used. pub fn qubit_count(&self) -> usize { self.qubits_in_use.len() @@ -32,19 +41,29 @@ impl ResourceManager { /// Allocates a qubit by favoring available qubit IDs before using new ones. pub fn allocate_qubit(&mut self) -> Qubit { - if let Some(qubit_id) = self.qubits_in_use.iter().position(|in_use| !in_use) { - self.qubits_in_use[qubit_id] = true; - Qubit(qubit_id) + let qubit = if let Some(qubit) = self.qubits_in_use.iter().position(|in_use| !in_use) { + self.qubits_in_use[qubit] = true; + qubit } else { self.qubits_in_use.push(true); - let qubit_id = self.qubits_in_use.len() - 1; - Qubit(qubit_id) + self.qubits_in_use.len() - 1 + }; + let mut next_id = 0; + loop { + if !self.qubit_id_map.contains_key(next_id) { + self.qubit_id_map.insert(next_id, qubit); + break; + } + next_id += 1; } + Qubit(next_id) } /// Releases a qubit ID for future use. pub fn release_qubit(&mut self, q: Qubit) { - self.qubits_in_use[q.0] = false; + let qubit = self.map_qubit(q); + self.qubit_id_map.remove(q.0); + self.qubits_in_use[qubit] = false; } /// Gets the next block ID. @@ -74,6 +93,13 @@ impl ResourceManager { self.next_var += 1; var_id.into() } + + pub fn swap_qubit_ids(&mut self, q0: Qubit, q1: Qubit) { + let id0 = self.map_qubit(q0); + let id1 = self.map_qubit(q1); + self.qubit_id_map.insert(q0.0, id1); + self.qubit_id_map.insert(q1.0, id0); + } } /// Custom backend meant to panic when most of its methods are called. diff --git a/library/src/tests/canon.rs b/library/src/tests/canon.rs index 1c30e5a37f..d77a72f879 100644 --- a/library/src/tests/canon.rs +++ b/library/src/tests/canon.rs @@ -107,6 +107,75 @@ fn check_fst_snd() { test_expression("Snd(7,6)", &Value::Int(6)); } +#[test] +fn check_swap_labels() { + test_expression( + "{ + use qs = Qubit[2]; + X(qs[0]); + SwapLabels(qs[0], qs[1]); + MResetEachZ(qs) + }", + &Value::Array(vec![Value::RESULT_ZERO, Value::RESULT_ONE].into()), + ); +} + +#[test] +fn check_relabel_rotational_permutation() { + test_expression( + "{ + use qs = Qubit[3]; + // Prepare |01+⟩ + X(qs[1]); + H(qs[2]); + Relabel([qs[0], qs[1], qs[2]], [qs[1], qs[2], qs[0]]); + // Expected state is |1+0⟩, perform adjoint to get back to ground state. + X(qs[0]); + H(qs[1]); + // Qubit release will fail if the state is not |000⟩ + }", + &Value::unit(), + ); +} + +#[test] +fn check_relabel_rotational_permutation_alternate_expression() { + test_expression( + "{ + use qs = Qubit[3]; + // Prepare |01+⟩ + X(qs[1]); + H(qs[2]); + Relabel([qs[2], qs[0], qs[1]], [qs[0], qs[1], qs[2]]); + // Expected state is |1+0⟩, perform adjoint to get back to ground state. + X(qs[0]); + H(qs[1]); + // Qubit release will fail if the state is not |000⟩ + }", + &Value::unit(), + ); +} + +#[test] +fn check_relabel_four_qubit_shuffle_permutation() { + test_expression( + "{ + use qs = Qubit[4]; + // Prepare |01+i⟩ + X(qs[1]); + H(qs[2]); + Y(qs[3]); + Relabel([qs[0], qs[1], qs[2], qs[3]], [qs[1], qs[0], qs[3], qs[2]]); + // Expected state is |10i+⟩, perform adjoint to get back to ground state. + X(qs[0]); + Y(qs[2]); + H(qs[3]); + // Qubit release will fail if the state is not |0000⟩ + }", + &Value::unit(), + ); +} + #[test] fn check_apply_cnot_chain_2() { test_expression( diff --git a/library/std/src/canon.qs b/library/std/src/canon.qs index 6bccc1c1fa..84d3c06876 100644 --- a/library/std/src/canon.qs +++ b/library/std/src/canon.qs @@ -3,6 +3,7 @@ namespace Microsoft.Quantum.Canon { open QIR.Intrinsic; + open QIR.Runtime; open Microsoft.Quantum.Intrinsic; open Microsoft.Quantum.Diagnostics; open Microsoft.Quantum.Math; @@ -246,6 +247,51 @@ namespace Microsoft.Quantum.Canon { return snd; } + /// # Summary + /// Exchanges the labels of two qubits. This can function like a SWAP gate, but without needing to + /// perform any operations on the qubits themselves. The relabeling is done purely in qubit ID management. + /// + /// # Input + /// ## q0 + /// The first qubit to swap. + /// ## q1 + /// The second qubit to swap. + /// + /// # Remarks + /// This operation is useful when you need to swap qubits in a way that does not incur any quantum operations. + /// Note that when compiling for execution on hardware with limited qubit connectivity, this operation + /// may not result in any changes to qubit adjacency and a SWAP gate may still be required. + operation SwapLabels(q0 : Qubit, q1 : Qubit) : Unit { + Relabel([q0, q1], [q1, q0]); + } + + /// # Summary + /// Relabels the qubits in the `current` array with the qubits in the `updated` array. The `updated` array + /// must be a valid permutation of the `current` array. + /// + /// # Input + /// ## current + /// Array of qubits to be relabeled. + /// ## updated + /// Array of qubits with which to relabel the `current` array. + /// + /// # Remarks + /// This operation is useful when you need to relabel qubits in a way that does not incur any quantum operations. + /// Note that when compiling for execution on hardware with limited qubit connectivity, this operation + /// may not result in any changes to qubit adjacency and one or more SWAP gates may still be required. + /// + /// # Example + /// The following example demonstrates how to relabel qubits in a register: + /// ```qsharp + /// use qubits = Qubit[3]; + /// let newOrder = [qubits[2], qubits[0], qubits[1]]; + /// Relabel(qubits, newOrder); + /// ``` + /// After this operation, any use of `qubits[0]` will refer to the qubit that was originally `qubits[2]`, and so on. + operation Relabel(current : Qubit[], updated : Qubit[]) : Unit { + body intrinsic; + } + /// # Summary /// Computes the parity of a register of qubits in-place. /// @@ -584,6 +630,6 @@ namespace Microsoft.Quantum.Canon { adjoint self; } - export ApplyToEach, ApplyToEachA, ApplyToEachC, ApplyToEachCA, CX, CY, CZ, Fst, Snd, ApplyCNOTChain, ApplyP, ApplyPauli, ApplyPauliFromBitString, ApplyPauliFromInt, ApplyControlledOnInt, ApplyControlledOnBitString, ApplyQFT, SwapReverseRegister, ApplyXorInPlace, ApplyXorInPlaceL; + export ApplyToEach, ApplyToEachA, ApplyToEachC, ApplyToEachCA, CX, CY, CZ, Fst, Snd, SwapLabels, Relabel, ApplyCNOTChain, ApplyP, ApplyPauli, ApplyPauliFromBitString, ApplyPauliFromInt, ApplyControlledOnInt, ApplyControlledOnBitString, ApplyQFT, SwapReverseRegister, ApplyXorInPlace, ApplyXorInPlaceL; } diff --git a/pip/tests/test_interpreter.py b/pip/tests/test_interpreter.py index 86838e1899..a3b886e6db 100644 --- a/pip/tests/test_interpreter.py +++ b/pip/tests/test_interpreter.py @@ -274,6 +274,20 @@ def test_entry_expr_circuit() -> None: ) +def test_swap_label_circuit() -> None: + e = Interpreter(TargetProfile.Unrestricted) + e.interpret( + "operation Foo() : Unit { use q1 = Qubit(); use q2 = Qubit(); X(q1); SwapLabels(q1, q2); X(q2); }" + ) + circuit = e.circuit("Foo()") + assert str(circuit) == dedent( + """\ + q_0 ── X ──── X ── + q_1 ────────────── + """ + ) + + def test_callables_failing_profile_validation_are_not_registered() -> None: e = Interpreter(TargetProfile.Adaptive_RI) with pytest.raises(Exception) as excinfo: diff --git a/resource_estimator/src/counts.rs b/resource_estimator/src/counts.rs index 3e9aa685da..f25ad2b950 100644 --- a/resource_estimator/src/counts.rs +++ b/resource_estimator/src/counts.rs @@ -506,6 +506,11 @@ impl Backend for LogicalCounter { self.free_list.push(q); } + fn qubit_swap_id(&mut self, _q0: usize, _q1: usize) { + // This can safely be treated as a no-op, because counts don't care which qubit is operated on, + // just how many operations are performed, and relabeling is non-physical. + } + fn capture_quantum_state(&mut self) -> (Vec<(BigUint, Complex)>, usize) { (Vec::new(), 0) }