-
Notifications
You must be signed in to change notification settings - Fork 173
Memory Qubits #3159
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Memory Qubits #3159
Changes from all commits
ace3ed6
35cb700
fb37316
cd0e0d3
31fe45b
33507f3
e698099
9d776fc
2aab860
e240506
8548d11
3674b45
8bd906e
832d99b
8b39cfc
f886255
809c3cd
7b609c4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ mod intrinsic; | |
| mod logical; | ||
| mod math; | ||
| mod measurement; | ||
| mod memory_qubits; | ||
| mod openqasm; | ||
| mod state_preparation; | ||
| mod table_lookup; | ||
|
|
@@ -21,6 +22,9 @@ use qsc::{ | |
| interpret::{self, GenericReceiver, Interpreter, Value}, | ||
| target::Profile, | ||
| }; | ||
| use resource_estimator::{ | ||
| Error as ResourceEstimatorError, logical_counts_expr, system::LogicalResourceCounts, | ||
| }; | ||
|
|
||
| /// # Panics | ||
| /// | ||
|
|
@@ -40,6 +44,35 @@ pub fn test_expression_fails(expr: &str) -> String { | |
| ) | ||
| } | ||
|
|
||
| /// Asserts that given Q# expression fails to compile with given error message. | ||
| pub fn test_compile_fails(expr: &str, lib: &str, expected_error_substring: &str) { | ||
| let profile = Profile::Unrestricted; | ||
| let sources = SourceMap::new([("test".into(), lib.into())], Some(expr.into())); | ||
|
|
||
| let (std_id, store) = qsc::compile::package_store_with_stdlib(profile.into()); | ||
|
|
||
| let Err(compile_errors) = Interpreter::new( | ||
| sources, | ||
| PackageType::Exe, | ||
| profile.into(), | ||
| LanguageFeatures::default(), | ||
| store, | ||
| &[(std_id, None)], | ||
| ) else { | ||
| panic!("test should fail to compile") | ||
| }; | ||
|
|
||
| let error = compile_errors | ||
| .iter() | ||
| .map(|e| format!("{e}:{e:?}")) | ||
| .collect::<Vec<_>>() | ||
| .join("\n"); | ||
| assert!( | ||
| error.contains(expected_error_substring), | ||
| "Expected to contain: {expected_error_substring}\nGot: {error}" | ||
| ); | ||
| } | ||
|
|
||
| pub fn test_expression_with_lib(expr: &str, lib: &str, expected: &Value) -> String { | ||
| test_expression_with_lib_and_profile(expr, lib, Profile::Unrestricted, expected) | ||
| } | ||
|
|
@@ -264,3 +297,33 @@ fn stdlib_reexport_single_case() { | |
| &Value::Tuple(vec![].into(), None), | ||
| ); | ||
| } | ||
|
|
||
| // Runs resource estimator for given expression, returns logical counts. | ||
| pub fn logical_counts_with_lib(expr: &str, lib: &str) -> LogicalResourceCounts { | ||
| let profile = Profile::Unrestricted; | ||
| let sources = SourceMap::new([("test".into(), lib.into())], Some(expr.into())); | ||
|
|
||
| let (std_id, store) = qsc::compile::package_store_with_stdlib(profile.into()); | ||
|
|
||
| let mut interpreter = Interpreter::new( | ||
| sources, | ||
| PackageType::Exe, | ||
| profile.into(), | ||
| LanguageFeatures::default(), | ||
| store, | ||
| &[(std_id, None)], | ||
| ) | ||
| .expect("test should compile"); | ||
|
|
||
| logical_counts_expr(&mut interpreter, expr) | ||
| .unwrap_or_else(|errs| panic_with_resource_estimation_errors(&errs)) | ||
| } | ||
|
|
||
| fn panic_with_resource_estimation_errors(errs: &[ResourceEstimatorError]) -> ! { | ||
| let joined = errs | ||
| .iter() | ||
| .map(|e| format!("{e}:{e:?}")) | ||
| .collect::<Vec<_>>() | ||
| .join("\n"); | ||
| panic!("resource estimation failed:\n{joined}"); | ||
| } | ||
|
Comment on lines
+318
to
+329
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than have a utility to panic with all the errors joined, you could just use the first error returned. Most of the APIs other than compile use a vector of errors for compatibility sake with parts of the infra but only ever return one error. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,235 @@ | ||
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| use super::{ | ||
| logical_counts_with_lib, test_compile_fails, test_expression_fails, test_expression_with_lib, | ||
| }; | ||
| use indoc::indoc; | ||
| use qsc::interpret::Value; | ||
|
|
||
| // Tests for memory qubits and Std.MemoryQubits namespace. | ||
|
|
||
| #[test] | ||
| fn check_store_load() { | ||
| test_expression_with_lib( | ||
| "Test.Main()", | ||
| indoc! {r#" | ||
| namespace Test { | ||
| operation Main() : Result { | ||
| use (q, mem) = (Qubit(), MemoryQubit()); | ||
| X(q); | ||
| Std.MemoryQubits.Store(q, mem); | ||
| Std.MemoryQubits.Load(mem, q); | ||
| return MResetZ(q); | ||
| } | ||
| } | ||
| "#}, | ||
| &Value::RESULT_ONE, | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn check_array_store_load() { | ||
| test_expression_with_lib( | ||
| "Test.Main()", | ||
| indoc! {r#" | ||
| namespace Test { | ||
| operation Main() : Result[] { | ||
| use qs = Qubit[2]; | ||
| use mems = MemoryQubit[2]; | ||
| X(qs[0]); | ||
| Std.MemoryQubits.StoreArray(qs, mems); | ||
| Std.MemoryQubits.LoadArray(mems, qs); | ||
| return [MResetZ(qs[0]), MResetZ(qs[1])]; | ||
| } | ||
| } | ||
| "#}, | ||
| &Value::Array(vec![Value::RESULT_ONE, Value::RESULT_ZERO].into()), | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn check_cannot_apply_gate_to_memory_qubit() { | ||
| test_compile_fails("{ use q = MemoryQubit(); X(q); }", "", "type error"); | ||
| } | ||
|
|
||
| #[test] | ||
| fn check_cannot_measure_memory_qubit() { | ||
| test_compile_fails("{ use q = MemoryQubit(); M(q); }", "", "type error"); | ||
| } | ||
|
|
||
| // MemoryQubit cannot be released in non-zero state (same as Qubit). | ||
| #[test] | ||
| fn check_release_non_zero_fails() { | ||
| let err = test_expression_fails(indoc! {r#" | ||
| { | ||
| use (q, m) = (Qubit(), MemoryQubit()); | ||
| X(q); | ||
| Std.MemoryQubits.Store(q, m); | ||
| Reset(q); | ||
| } | ||
| "#}); | ||
|
|
||
| assert!(err.contains("released while not in |0")); | ||
| } | ||
|
|
||
| // Check that after the computation, all qubits are released in 0 state. | ||
| #[test] | ||
| fn check_do_computation_with_qft() { | ||
| test_expression_with_lib( | ||
| "Test.Main()", | ||
| indoc! {r#" | ||
| namespace Test { | ||
| operation Main() : Bool { | ||
| use (qs, mems) = (Qubit[4], MemoryQubit[4]); | ||
| Adjoint ApplyQFT(qs); | ||
| Std.MemoryQubits.StoreArray(qs, mems); | ||
| Std.MemoryQubits.DoComputation(mems, ApplyQFT); | ||
| return true; | ||
| } | ||
| } | ||
| "#}, | ||
| &Value::Bool(true), | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn check_borrow_memory_qubit_not_supported() { | ||
| test_compile_fails( | ||
| "{ borrow q = MemoryQubit(); }", | ||
| "", | ||
| "MemoryQubit cannot be borrowed.", | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn check_borrow_memory_qubit_array_not_supported() { | ||
| test_compile_fails( | ||
| "{ borrow q = MemoryQubit[5]; }", | ||
| "", | ||
| "MemoryQubit cannot be borrowed.", | ||
| ); | ||
| } | ||
|
|
||
| // Resource estimation tests. | ||
|
|
||
| #[test] | ||
| fn check_resource_estimation_single_qubit() { | ||
| let counts = logical_counts_with_lib( | ||
| "Test.Main()", | ||
| indoc! {r#" | ||
| namespace Test { | ||
| operation Main() : Unit { | ||
| use q = Qubit(); | ||
| use mem = MemoryQubit(); | ||
| X(q); | ||
| Std.MemoryQubits.Store(q, mem); | ||
| Std.MemoryQubits.Load(mem, q); | ||
| } | ||
| } | ||
| "#}, | ||
| ); | ||
|
|
||
| assert_eq!(counts.num_qubits, 2); | ||
| assert_eq!(counts.num_compute_qubits, Some(1)); | ||
| assert_eq!(counts.read_from_memory_count, Some(1)); | ||
| assert_eq!(counts.write_to_memory_count, Some(1)); | ||
| } | ||
|
|
||
| // Resource estimation of computation with memory qubits. | ||
| #[test] | ||
| fn check_resource_estimation_do_computation() { | ||
| let counts = logical_counts_with_lib( | ||
| "Test.Main()", | ||
| indoc! {r#" | ||
| namespace Test { | ||
| operation Main() : Unit { | ||
| use qs = MemoryQubit[10]; | ||
| Std.MemoryQubits.DoComputation(qs[0..4], ApplyQFT); | ||
| Std.MemoryQubits.DoComputation(qs[5..9], ApplyQFT); | ||
| } | ||
| } | ||
| "#}, | ||
| ); | ||
|
|
||
| // 10 memory qubits, 5 compute qubits. | ||
| assert_eq!(counts.num_qubits, 15); | ||
| assert_eq!(counts.num_compute_qubits, Some(5)); | ||
| assert_eq!(counts.read_from_memory_count, Some(10)); | ||
| assert_eq!(counts.write_to_memory_count, Some(10)); | ||
| } | ||
|
|
||
| #[test] | ||
| fn check_resource_estimation_store_only() { | ||
| let counts = logical_counts_with_lib( | ||
| "Test.Main()", | ||
| indoc! {r#" | ||
| namespace Test { | ||
| operation Main() : Unit { | ||
| use (qs, mem) = (Qubit[10], MemoryQubit[10]); | ||
| H(qs[5]); | ||
| Std.MemoryQubits.StoreArray(qs, mem); | ||
| } | ||
| } | ||
| "#}, | ||
| ); | ||
|
|
||
| assert_eq!(counts.num_qubits, 20); | ||
| assert_eq!(counts.num_compute_qubits, Some(10)); | ||
| assert_eq!(counts.read_from_memory_count, Some(0)); | ||
| assert_eq!(counts.write_to_memory_count, Some(10)); | ||
| } | ||
|
|
||
| #[test] | ||
| fn check_resource_estimation_load_only() { | ||
| let counts = logical_counts_with_lib( | ||
| "Test.Main()", | ||
| indoc! {r#" | ||
| namespace Test { | ||
| operation Main() : Unit { | ||
| use (qs, mem) = (Qubit[10], MemoryQubit[10]); | ||
| Std.MemoryQubits.LoadArray(mem, qs); | ||
| } | ||
| } | ||
| "#}, | ||
| ); | ||
|
|
||
| assert_eq!(counts.num_qubits, 20); | ||
| assert_eq!(counts.num_compute_qubits, Some(10)); | ||
| assert_eq!(counts.read_from_memory_count, Some(10)); | ||
| assert_eq!(counts.write_to_memory_count, Some(0)); | ||
| } | ||
|
|
||
| // This test checks that MemoryQubits cannot be reused as Qubits. | ||
| // Resource estimator must draw them from separate pools. | ||
| #[test] | ||
| fn check_resource_estimation_separate_qubit_pools() { | ||
| let counts = logical_counts_with_lib( | ||
| "Test.Main()", | ||
| indoc! {r#" | ||
| namespace Test { | ||
| operation Op1() : Unit { | ||
| use q = Qubit(); | ||
| use mem = MemoryQubit(); | ||
| H(q); | ||
| Std.MemoryQubits.Store(q, mem); | ||
| Std.MemoryQubits.Load(mem, q); | ||
| } | ||
| operation Op2() : Unit { | ||
| use qs = Qubit[2]; | ||
| H(qs[0]); H(qs[1]); | ||
| M(qs[0]); M(qs[1]); | ||
| } | ||
| operation Main() : Unit { | ||
| Op1(); | ||
| Op2(); | ||
| } | ||
| } | ||
| "#}, | ||
| ); | ||
|
|
||
| // Maximum allocated qubits at any point is 2, but we need 1 memory qubit and | ||
| // 2 compute qubits, so total number of qubits needed is 3. | ||
| assert_eq!(counts.num_qubits, 3); | ||
| assert_eq!(counts.num_compute_qubits, Some(2)); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this new function necessary? There are other tests that use
test_expression_failsdefined above and then expect on the output string. Would that be sufficient for new tests?