Skip to content
Draft
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
24 changes: 23 additions & 1 deletion library/std/src/Std/ResourceEstimation.qs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,26 @@ function LeastFrequentlyUsed() : Int {
return 1;
}


/// Signals to resource estimator that this qubit is stored to memory, i.e.
/// transitions from "compute qubit" to "memory qubit".
/// Each qubit is allocated as "compute" by default.
/// MemoryQubitStore can be applied only to "compute" qubit. That is, qubit on which
/// MemoryQubitLoad wasn't applied or if it was applied, it was followed by
/// MemoryQubitLoad.
/// While qubit is in "memory" state, no gates or measurements can be applied to it.
function MemoryQubitStore(q: Qubit) : Unit {
body intrinsic;
}

/// Signals to resource estimator that this qubit is loaded from memory, i.e.
/// transitions from "memory qubit" to "compute qubit".
/// Can be applied only to "memory qubit", i.e. on which previously MemoryQubitStore
/// was applied.
function MemoryQubitLoad(q: Qubit) : Unit {
body intrinsic;
}

export
SingleVariant,
BeginEstimateCaching,
Expand All @@ -228,4 +248,6 @@ export
RepeatEstimates,
EnableMemoryComputeArchitecture,
LeastRecentlyUsed,
LeastFrequentlyUsed;
LeastFrequentlyUsed,
MemoryQubitLoad,
MemoryQubitStore;
112 changes: 107 additions & 5 deletions source/resource_estimator/src/counts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,13 @@ use rustc_hash::FxHashMap;
use std::{array, cell::RefCell, f64::consts::PI, fmt::Debug, iter::Sum};

use crate::{counts::memory_compute::CachingStrategy, system::LogicalResourceCounts};
use memory_compute::MemoryComputeInfo;
use memory_compute::{MemoryComputeInfo, QubitPool};

#[derive(Clone, Copy)]
enum TypedQubit {
Compute(usize),
Memory(usize),
}

/// Resource counter implementation
///
Expand Down Expand Up @@ -53,6 +59,19 @@ pub struct LogicalCounter {
/// Map to track any post-select measurements by their associated qubit.
/// This value is used in a measurement, if present, before generating a random result.
post_select_measurements: FxHashMap<usize, bool>,

/// Pool of typed compute qubits used by manual MemoryQubitStore/Load.
compute_qubit_pool: QubitPool,
/// Pool of typed memory qubits used by manual MemoryQubitStore/Load.
memory_qubit_pool: QubitPool,
/// Mapping from untyped allocated qubit id to its typed counterpart/state.
typed_qubit_map: FxHashMap<usize, TypedQubit>,
/// Peak number of typed qubits alive at once in manual memory-qubit mode.
typed_qubit_peak: usize,

manual_memory_reads: usize,
manual_memory_writes: usize,
manual_memory_mode: bool,
}

impl Default for LogicalCounter {
Expand All @@ -73,6 +92,13 @@ impl Default for LogicalCounter {
memory_compute: None,
rnd: RefCell::new(StdRng::seed_from_u64(0)),
post_select_measurements: FxHashMap::default(),
compute_qubit_pool: QubitPool::default(),
memory_qubit_pool: QubitPool::default(),
typed_qubit_map: FxHashMap::default(),
typed_qubit_peak: 0,
manual_memory_reads: 0,
manual_memory_writes: 0,
manual_memory_mode: false,
}
}
}
Expand All @@ -87,12 +113,24 @@ impl LogicalCounter {
Some(memory_compute.read_from_memory_count() as u64),
Some(memory_compute.write_to_memory_count() as u64),
)
} else if self.manual_memory_mode {
(
Some(self.compute_qubit_pool.max_in_use() as u64),
Some(self.manual_memory_reads as u64),
Some(self.manual_memory_writes as u64),
)
} else {
(None, None, None)
};

let extra_manual_typed_qubits = if self.manual_memory_mode {
self.typed_qubit_peak
} else {
0
};

LogicalResourceCounts {
num_qubits: self.next_free as _,
num_qubits: (self.next_free + extra_manual_typed_qubits) as _,
t_count: self.t_count as _,
rotation_count: self.r_count as _,
rotation_depth: self.layers.iter().filter(|layer| layer.r != 0).count() as _,
Expand Down Expand Up @@ -461,8 +499,15 @@ impl LogicalCounter {
}

fn assert_compute_qubits(&mut self, qubits: impl IntoIterator<Item = usize>) {
let qubits: Vec<_> = qubits.into_iter().collect();
if let Some(memory_compute) = &mut self.memory_compute {
memory_compute.assert_compute_qubits(qubits);
memory_compute.assert_compute_qubits(qubits.iter().copied());
}

for qubit in qubits {
if let Some(TypedQubit::Memory(_)) = self.typed_qubit_map.get(&qubit) {
panic!("cannot apply compute operation to a memory qubit");
}
}
}

Expand Down Expand Up @@ -602,17 +647,32 @@ impl Backend for LogicalCounter {
fn z(&mut self, _q: usize) {}

fn qubit_allocate(&mut self) -> usize {
if let Some(index) = self.free_list.pop() {
let index = if let Some(index) = self.free_list.pop() {
index
} else {
let index = self.next_free;
self.next_free += 1;
self.max_layer.push(self.allocation_barrier);
index
}
};

let compute_index = self.compute_qubit_pool.allocate();
self.typed_qubit_map
.insert(index, TypedQubit::Compute(compute_index));
self.typed_qubit_peak = self.typed_qubit_peak.max(self.typed_qubit_map.len());

index
}

fn qubit_release(&mut self, q: usize) -> bool {
if let Some(typed) = self.typed_qubit_map.remove(&q) {
match typed {
TypedQubit::Compute(compute_index) => {
self.compute_qubit_pool.release(compute_index)
}
TypedQubit::Memory(memory_index) => self.memory_qubit_pool.release(memory_index),
}
}
self.free_list.push(q);
true
}
Expand All @@ -630,6 +690,15 @@ impl Backend for LogicalCounter {
if let Some(val) = q1_post_select {
self.post_select_measurements.insert(q0, val);
}

let q0_typed = self.typed_qubit_map.remove(&q0);
let q1_typed = self.typed_qubit_map.remove(&q1);
if let Some(typed) = q0_typed {
self.typed_qubit_map.insert(q1, typed);
}
if let Some(typed) = q1_typed {
self.typed_qubit_map.insert(q0, typed);
}
}

fn capture_quantum_state(&mut self) -> (Vec<(BigUint, Complex<f64>)>, usize) {
Expand Down Expand Up @@ -706,6 +775,39 @@ impl Backend for LogicalCounter {

Some(Ok(Value::unit()))
}
"MemoryQubitLoad" => {
self.manual_memory_mode = true;
let q = arg.unwrap_qubit().deref().0;
let Some(TypedQubit::Memory(memory_index)) = self.typed_qubit_map.get(&q).copied()
else {
return Some(Err(
"MemoryQubitLoad can only be applied to a memory qubit".to_string()
));
};
self.memory_qubit_pool.release(memory_index);
let compute_index = self.compute_qubit_pool.allocate();
self.typed_qubit_map
.insert(q, TypedQubit::Compute(compute_index));
self.manual_memory_reads += 1;
Some(Ok(Value::unit()))
}
"MemoryQubitStore" => {
self.manual_memory_mode = true;
let q = arg.unwrap_qubit().deref().0;
let Some(TypedQubit::Compute(compute_index)) =
self.typed_qubit_map.get(&q).copied()
else {
return Some(Err(
"MemoryQubitStore can only be applied to a compute qubit".to_string(),
));
};
self.compute_qubit_pool.release(compute_index);
let memory_index = self.memory_qubit_pool.allocate();
self.typed_qubit_map
.insert(q, TypedQubit::Memory(memory_index));
self.manual_memory_writes += 1;
Some(Ok(Value::unit()))
}
_ => None,
}
}
Expand Down
38 changes: 38 additions & 0 deletions source/resource_estimator/src/counts/memory_compute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,44 @@ use std::hash::Hash;
#[cfg(test)]
mod tests;

#[derive(Default)]
pub struct QubitPool {
free_list: Vec<usize>,
next_id: usize,
in_use: usize,
max_in_use: usize,
}

impl QubitPool {
pub fn allocate(&mut self) -> usize {
let index = if let Some(index) = self.free_list.pop() {
index
} else {
let index = self.next_id;
self.next_id += 1;
index
};
self.in_use += 1;
if self.in_use > self.max_in_use {
self.max_in_use = self.in_use;
}

index
}

pub fn release(&mut self, index: usize) {
self.free_list.push(index);
self.in_use = self
.in_use
.checked_sub(1)
.expect("releasing from an empty qubit pool");
}

pub fn max_in_use(&self) -> usize {
self.max_in_use
}
}

pub enum CachingStrategy {
LeastRecentlyUsed(LeastRecentlyUsedPriorityQueue<usize>),
LeastFrequentlyUsed(LeastFrequentlyUsedPriorityQueue<usize>),
Expand Down
32 changes: 28 additions & 4 deletions source/resource_estimator/src/counts/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

use std::convert::Into;

use crate::system::LogicalResourceCounts;
use expect_test::{Expect, expect};
use indoc::indoc;
use miette::Report;
Expand All @@ -14,7 +15,7 @@ use qsc::{

use super::LogicalCounter;

fn verify_logical_counts(source: &str, entry: Option<&str>, expect: &Expect) {
fn run_logical_counts(source: &str, entry: Option<&str>) -> LogicalResourceCounts {
let source_map = SourceMap::new([("test".into(), source.into())], entry.map(Into::into));
let (std_id, store) = qsc::compile::package_store_with_stdlib(TargetCapabilityFlags::all());

Expand All @@ -40,9 +41,7 @@ fn verify_logical_counts(source: &str, entry: Option<&str>, expect: &Expect) {
let mut out = GenericReceiver::new(&mut stdout);

match interpreter.eval_entry_with_sim(&mut counter, &mut out) {
Ok(_) => {
expect.assert_debug_eq(&counter.logical_resources());
}
Ok(_) => counter.logical_resources(),
Err(err) => {
for e in err {
let report = Report::from(e);
Expand All @@ -53,6 +52,11 @@ fn verify_logical_counts(source: &str, entry: Option<&str>, expect: &Expect) {
}
}

fn verify_logical_counts(source: &str, entry: Option<&str>, expect: &Expect) {
let logical_counts = run_logical_counts(source, entry);
expect.assert_debug_eq(&logical_counts);
}

#[test]
fn gates_are_counted() {
verify_logical_counts(
Expand Down Expand Up @@ -427,3 +431,23 @@ fn post_selection_can_take_impossible_branch() {
"#]],
);
}

#[test]
fn manual_memory_qubits() {
let counts = run_logical_counts(
indoc! {"
import Std.Diagnostics.PostSelectZ;

operation Main() : Unit {
use q = Qubit();
Std.ResourceEstimation.MemoryQubitStore(q);
Std.ResourceEstimation.MemoryQubitLoad(q);
}
"},
None,
);
assert_eq!(counts.write_to_memory_count, Some(1));
assert_eq!(counts.read_from_memory_count, Some(1));
assert_eq!(counts.num_compute_qubits, Some(1));
assert_eq!(counts.num_qubits, 2);
}
Loading