diff --git a/library/std/src/Std/ResourceEstimation.qs b/library/std/src/Std/ResourceEstimation.qs index 7d58ad9408..6660d14f3b 100644 --- a/library/std/src/Std/ResourceEstimation.qs +++ b/library/std/src/Std/ResourceEstimation.qs @@ -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, @@ -228,4 +248,6 @@ export RepeatEstimates, EnableMemoryComputeArchitecture, LeastRecentlyUsed, - LeastFrequentlyUsed; + LeastFrequentlyUsed, + MemoryQubitLoad, + MemoryQubitStore; diff --git a/source/resource_estimator/src/counts.rs b/source/resource_estimator/src/counts.rs index 1cf8d8066b..bce9b1ed64 100644 --- a/source/resource_estimator/src/counts.rs +++ b/source/resource_estimator/src/counts.rs @@ -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 /// @@ -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, + + /// 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, + /// 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 { @@ -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, } } } @@ -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 _, @@ -461,8 +499,15 @@ impl LogicalCounter { } fn assert_compute_qubits(&mut self, qubits: impl IntoIterator) { + 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"); + } } } @@ -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 } @@ -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)>, usize) { @@ -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, } } diff --git a/source/resource_estimator/src/counts/memory_compute.rs b/source/resource_estimator/src/counts/memory_compute.rs index e2df7d3fa5..4f188e68a6 100644 --- a/source/resource_estimator/src/counts/memory_compute.rs +++ b/source/resource_estimator/src/counts/memory_compute.rs @@ -5,6 +5,44 @@ use std::hash::Hash; #[cfg(test)] mod tests; +#[derive(Default)] +pub struct QubitPool { + free_list: Vec, + 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), LeastFrequentlyUsed(LeastFrequentlyUsedPriorityQueue), diff --git a/source/resource_estimator/src/counts/tests.rs b/source/resource_estimator/src/counts/tests.rs index f3fdc2a67f..425fe161f7 100644 --- a/source/resource_estimator/src/counts/tests.rs +++ b/source/resource_estimator/src/counts/tests.rs @@ -3,6 +3,7 @@ use std::convert::Into; +use crate::system::LogicalResourceCounts; use expect_test::{Expect, expect}; use indoc::indoc; use miette::Report; @@ -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()); @@ -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); @@ -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( @@ -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); +}