Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions library/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ indoc = { workspace = true }
num-bigint = { workspace = true }
expect-test = { workspace = true }
qsc = { path = "../source/compiler/qsc" }
resource_estimator = { path = "../source/resource_estimator" }

[lints]
workspace = true
Expand Down
27 changes: 26 additions & 1 deletion library/core/qir.qs
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,30 @@ namespace QIR.Runtime {
body intrinsic;
}

export __quantum__rt__qubit_allocate, __quantum__rt__qubit_borrow, __quantum__rt__qubit_release, AllocateQubitArray, BorrowQubitArray, ReleaseQubitArray, __quantum__rt__read_loss;
operation __quantum__rt__memory_qubit_allocate() : MemoryQubit {
body intrinsic;
}

operation __quantum__rt__memory_qubit_release(q : MemoryQubit) : Unit {
body intrinsic;
}

operation AllocateMemoryQubitArray(size : Int) : MemoryQubit[] {
if size < 0 {
fail "Cannot allocate memory qubit array with a negative length";
}
mutable qs = [];
for _ in 0..size - 1 {
set qs += [__quantum__rt__memory_qubit_allocate()];
}
qs
}

operation ReleaseMemoryQubitArray(qs : MemoryQubit[]) : Unit {
for q in qs {
__quantum__rt__memory_qubit_release(q);
}
}

export __quantum__rt__qubit_allocate, __quantum__rt__qubit_borrow, __quantum__rt__qubit_release, AllocateQubitArray, BorrowQubitArray, ReleaseQubitArray, __quantum__rt__read_loss, __quantum__rt__memory_qubit_allocate, __quantum__rt__memory_qubit_release, AllocateMemoryQubitArray, ReleaseMemoryQubitArray;
}
4 changes: 4 additions & 0 deletions library/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ pub const STD_LIB: &[(&str, &str)] = &[
"qsharp-library-source:Std/Measurement.qs",
include_str!("../std/src/Std/Measurement.qs"),
),
(
"qsharp-library-source:Std/MemoryQubits.qs",
include_str!("../std/src/Std/MemoryQubits.qs"),
),
(
"qsharp-library-source:QIR/Intrinsic.qs",
include_str!("../std/src/QIR/Intrinsic.qs"),
Expand Down
63 changes: 63 additions & 0 deletions library/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod intrinsic;
mod logical;
mod math;
mod measurement;
mod memory_qubits;
mod openqasm;
mod state_preparation;
mod table_lookup;
Expand All @@ -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
///
Expand All @@ -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) {
Copy link
Copy Markdown
Collaborator

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_fails defined above and then expect on the output string. Would that be sufficient for new tests?

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)
}
Expand Down Expand Up @@ -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
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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.

235 changes: 235 additions & 0 deletions library/src/tests/memory_qubits.rs
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));
}
1 change: 1 addition & 0 deletions library/std/qsharp.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"src/Std/Logical.qs",
"src/Std/Math.qs",
"src/Std/Measurement.qs",
"src/Std/MemoryQubits.qs",
"src/Std/Random.qs",
"src/Std/Range.qs",
"src/Std/ResourceEstimation.qs",
Expand Down
Loading