diff --git a/rust/hhat_lang/hhat_core/src/layout/mod.rs b/rust/hhat_lang/hhat_core/src/layout/mod.rs index 5803357..69d53e3 100644 --- a/rust/hhat_lang/hhat_core/src/layout/mod.rs +++ b/rust/hhat_lang/hhat_core/src/layout/mod.rs @@ -9,5 +9,6 @@ pub mod arch; pub mod primitives; +pub mod quantum; mod adt; mod base; \ No newline at end of file diff --git a/rust/hhat_lang/hhat_core/src/layout/quantum/cache.rs b/rust/hhat_lang/hhat_core/src/layout/quantum/cache.rs new file mode 100644 index 0000000..6bb7a37 --- /dev/null +++ b/rust/hhat_lang/hhat_core/src/layout/quantum/cache.rs @@ -0,0 +1,72 @@ +//! +//! Caching of the quantum layouts. +//! +//! Laying out the quantum layout and storing it are different concerns: this +//! holds the permanent store so each quantum type is laid out once and reused +//! across every quantum variable in the program. +//! + +use std::collections::HashMap; +use crate::frontend::types::Ty; +use crate::layout::quantum::layout::QuantumLayout; + + +/// Permanent store for quantum type layouts. +/// +pub struct QuantumLayoutCache { + cache: HashMap, +} + +#[allow(clippy::new_without_default)] +impl QuantumLayoutCache { + pub fn new() -> Self { + Self { cache: HashMap::new() } + } + + pub fn has(&self, ty: &Ty) -> bool { + self.cache.contains_key(ty) + } + + /// Quantum layout for a type, building it on a miss and storing it. + /// + pub fn layout_of(&mut self, ty: &Ty) -> QuantumLayout { + match self.cache.get(ty) { + Some(q) => q.clone(), + None => { + let q = QuantumLayout::layout(ty); + self.cache.insert(ty.clone(), q.clone()); + q + } + } + } +} + + +#[cfg(test)] +mod tests { + use crate::frontend::types::{Ty, TyPrimitive, TyStruct}; + use crate::layout::quantum::QuantumLayoutCache; + use crate::SymbolId; + + /// Check the quantum layout cache stores layouts permanently and reuses them. + /// + #[test] + fn check_quantum_layout_cache() { + let bell_name = SymbolId(1, true); + let mut bell_ty = TyStruct::new(&bell_name); + bell_ty.add_member(&SymbolId(2, true), Ty::Primitive(TyPrimitive::QBool)); + bell_ty.add_member(&SymbolId(3, true), Ty::Primitive(TyPrimitive::QBool)); + bell_ty.done(); + + let mut qcache = QuantumLayoutCache::new(); + assert!(!qcache.has(&Ty::Struct(bell_ty.clone()))); + + let first = qcache.layout_of(&Ty::Struct(bell_ty.clone())); + assert_eq!(first.qubits(), 2); + assert!(qcache.has(&Ty::Struct(bell_ty.clone()))); + + // second lookup is served from the cache and matches + let second = qcache.layout_of(&Ty::Struct(bell_ty)); + assert_eq!(second.qubits(), 2); + } +} diff --git a/rust/hhat_lang/hhat_core/src/layout/quantum/layout.rs b/rust/hhat_lang/hhat_core/src/layout/quantum/layout.rs new file mode 100644 index 0000000..5ce3d4b --- /dev/null +++ b/rust/hhat_lang/hhat_core/src/layout/quantum/layout.rs @@ -0,0 +1,362 @@ +//! +//! Quantum layout definitions: the qubit-only layout of a type. +//! +//! No alignment and no padding; we cannot afford to waste qubits. +//! - primitive qubit counts: `@bool`=1, `@u2`=2, `@u3`=3, `@u4`=4, `@u8`=8; +//! - a struct stacks its quantum fields back-to-back (recursive); +//! - a quantum enum (2-variant, for now) takes a single qubit (`|0>`/`|1>`). +//! + +use crate::core::{Arenable, ArenaIndexHolder}; +use crate::frontend::types::{Ty, TyEnum, TyPrimitive, TyStruct}; +use crate::SymbolId; + + +/// Quantum layout of a type: qubit counts only, no alignment, no padding. +/// +#[derive(Clone, Debug)] +pub enum QuantumLayout { + Primitive(QuantumPrimitiveLayout), + Struct(QuantumStructLayout), + Enum(QuantumEnumLayout), +} + +impl QuantumLayout { + /// Total number of qubits this type occupies. + /// + pub fn qubits(&self) -> u32 { + match self { + QuantumLayout::Primitive(q) => q.qubits, + QuantumLayout::Struct(q) => q.qubits, + QuantumLayout::Enum(q) => q.qubits, + } + } + + /// Build the quantum layout for a type from the frontend type. + /// + pub fn layout(ty: &Ty) -> Self { + match ty { + Ty::Primitive(p) => QuantumLayout::Primitive(QuantumPrimitiveLayout::layout(p)), + Ty::Struct(s) => QuantumLayout::Struct(QuantumStructLayout::layout(s)), + Ty::Enum(e) => QuantumLayout::Enum(QuantumEnumLayout::layout(e)), + Ty::Array(_) => todo!(), + } + } +} + +impl Arenable for QuantumLayout {} + + +#[derive(Clone, Debug)] +pub struct QuantumPrimitiveLayout { + pub qubits: u32, +} + +impl QuantumPrimitiveLayout { + pub fn layout(ty: &TyPrimitive) -> Self { + let qubits = match ty { + TyPrimitive::QBool => 1, + TyPrimitive::QU2 => 2, + TyPrimitive::QU3 => 3, + TyPrimitive::QU4 => 4, + TyPrimitive::QU8 => 8, + _ => 0, + }; + Self { qubits } + } +} + +impl Arenable for QuantumPrimitiveLayout {} + + +/// Quantum layout of a struct: its quantum fields packed back-to-back (no +/// padding) plus the total qubit count. Classical fields hold no qubits and are +/// left out. +/// +#[derive(Clone, Debug)] +pub struct QuantumStructLayout { + pub qubits: u32, + pub members: Vec, +} + +impl QuantumStructLayout { + pub fn layout(ty: &TyStruct) -> Self { + let mut offset: u32 = 0; + let mut members: Vec = Vec::with_capacity( + ty.members.iter().filter(|(field_sid, _ty)| field_sid.is_quantum()).count() + ); + let _ = ty.iter().map(|(sid, t)| { + let layout = QuantumLayout::layout(t); + let tmp_qubits = layout.qubits(); + if tmp_qubits > 0 { // only quantum fields take qubits + members.push( + QuantumMemberLayout { + name: sid.clone(), + offset, + layout, + } + ); + offset += tmp_qubits; + } + }).collect::>(); + + Self { + qubits: offset, + members, + } + } + + /// Quantum field (attribute) with the given name, if any. + /// + pub fn member(&self, name: &SymbolId) -> Option<&QuantumMemberLayout> { + self.members.iter().find(|m| m.name == *name) + } + + pub fn get(&self, index: usize) -> Option<&QuantumMemberLayout> { + self.members.get(index) + } +} + +impl Arenable for QuantumStructLayout {} + + +/// A quantum attribute (struct field): its name, the qubit `offset` of its +/// first qubit inside the struct (no padding), and its own quantum layout. +/// +#[derive(Clone, Debug)] +pub struct QuantumMemberLayout { + pub name: SymbolId, + pub offset: u32, + pub layout: QuantumLayout, +} + + +/// Quantum layout of an enum. For now only 2-variant quantum enums exist and +/// they take a single qubit; classical enums hold no qubits. +/// +#[derive(Clone, Debug)] +pub struct QuantumEnumLayout { + pub qubits: u32, + /// variant names in order; the first and second map to `|0>` and `|1>` + pub variants: Vec, +} + +impl QuantumEnumLayout { + pub fn layout(ty: &TyEnum) -> Self { + let variants: Vec = ty.iter().map(|v| v.name().clone()).collect(); + Self { + qubits: if ty.is_quantum() { 1 } else { 0 }, + variants, + } + } +} + +impl Arenable for QuantumEnumLayout {} + + +#[cfg(test)] +mod tests { + use crate::frontend::types::{Ty, TyEnum, TyPrimitive, TyStruct}; + use crate::layout::arch::Arch; + use crate::layout::base::LayoutCache; + use crate::layout::quantum::QuantumLayout; + use crate::SymbolId; + + /// Check the qubit count for every primitive quantum type. + /// + #[test] + fn check_quantum_primitive_layouts() { + // @bool: 1, @u2: 2, @u3: 3, @u4: 4, @u8: 8 + let prims = [ + (TyPrimitive::QBool, 1u32), + (TyPrimitive::QU2, 2u32), + (TyPrimitive::QU3, 3u32), + (TyPrimitive::QU4, 4u32), + (TyPrimitive::QU8, 8u32), + ]; + let _ = prims.iter().map(|(p, qubits)| { + let qlayout = QuantumLayout::layout(&Ty::Primitive(p.clone())); + println!("quantum primitive layout: {:?}", qlayout); + assert_eq!(qlayout.qubits(), *qubits); + }).collect::>(); + } + + /// Check a simple quantum struct: @bell_t { @s:@bool, @t:@bool } => 2 qubits, + /// fields packed at offsets 0 and 1. + /// + #[test] + fn check_quantum_struct_layout() { + let bell_name = SymbolId(1, true); + let s_name = SymbolId(2, true); + let t_name = SymbolId(3, true); + let mut bell_ty = TyStruct::new(&bell_name); + bell_ty.add_member(&s_name, Ty::Primitive(TyPrimitive::QBool)); + bell_ty.add_member(&t_name, Ty::Primitive(TyPrimitive::QBool)); + bell_ty.done(); + + let qlayout = QuantumLayout::layout(&Ty::Struct(bell_ty)); + println!("quantum struct layout: {:?}", qlayout); + assert_eq!(qlayout.qubits(), 2); + + let qstruct = match qlayout { + QuantumLayout::Struct(q) => q, + other => panic!("expected quantum struct layout, got {:?}", other), + }; + assert_eq!(qstruct.members.len(), 2); + assert_eq!(qstruct.members[0].name, s_name); + assert_eq!(qstruct.members[0].offset, 0); + assert_eq!(qstruct.members[1].name, t_name); + assert_eq!(qstruct.members[1].offset, 1); + } + + /// Check a simple quantum enum: @polarization { @V, @H } => 1 qubit. + /// + #[test] + fn check_quantum_enum_layout() { + let polarization_name = SymbolId(1, true); + let v_name = SymbolId(2, true); + let h_name = SymbolId(3, true); + let mut polarization_ty = TyEnum::new(&polarization_name); + polarization_ty.add_variant(&v_name, None); + polarization_ty.add_variant(&h_name, None); + polarization_ty.done(); + + let qlayout = QuantumLayout::layout(&Ty::Enum(polarization_ty)); + println!("quantum enum layout: {:?}", qlayout); + assert_eq!(qlayout.qubits(), 1); + + let qenum = match qlayout { + QuantumLayout::Enum(q) => q, + other => panic!("expected quantum enum layout, got {:?}", other), + }; + // @V -> |0>, @H -> |1> + assert_eq!(qenum.variants, vec![v_name, h_name]); + } + + /// Check a nested quantum struct: + /// @bell_t { @s:@bool, @t:@bool } => 2 qubits + /// @outer { @a:@bool, @inner:@bell_t } => 1 + 2 = 3 qubits + /// + #[test] + fn check_nested_quantum_struct_layout() { + let bell_name = SymbolId(1, true); + let mut bell_ty = TyStruct::new(&bell_name); + bell_ty.add_member(&SymbolId(2, true), Ty::Primitive(TyPrimitive::QBool)); + bell_ty.add_member(&SymbolId(3, true), Ty::Primitive(TyPrimitive::QBool)); + bell_ty.done(); + + let outer_name = SymbolId(4, true); + let inner_name = SymbolId(5, true); + let mut outer_ty = TyStruct::new(&outer_name); + outer_ty.add_member(&SymbolId(6, true), Ty::Primitive(TyPrimitive::QBool)); + outer_ty.add_member(&inner_name, Ty::Struct(bell_ty)); + outer_ty.done(); + + let qlayout = QuantumLayout::layout(&Ty::Struct(outer_ty)); + println!("nested quantum struct layout: {:?}", qlayout); + assert_eq!(qlayout.qubits(), 3); + + let qstruct = match qlayout { + QuantumLayout::Struct(q) => q, + other => panic!("expected quantum struct layout, got {:?}", other), + }; + assert_eq!(qstruct.members[0].offset, 0); // @a + assert_eq!(qstruct.members[1].offset, 1); // @inner + // the nested field is itself a quantum struct of 2 qubits + assert_eq!(qstruct.members[1].layout.qubits(), 2); + } + + /// Check a struct mixing classical and quantum fields: only the quantum + /// fields appear in the quantum layout, packed with no alignment. + /// + #[test] + fn check_mixed_quantum_struct_layout() { + // type @mixed { c1:U32, @q1:@bool, c2:Bool, @q2:@u8 } + // quantum layout: @q1 @0 (1q), @q2 @1 (8q) => 9 qubits + let mixed_name = SymbolId(1, true); + let q1_name = SymbolId(3, true); + let q2_name = SymbolId(5, true); + let mut mixed_ty = TyStruct::new(&mixed_name); + mixed_ty.add_member(&SymbolId(2, false), Ty::Primitive(TyPrimitive::U32)); + mixed_ty.add_member(&q1_name, Ty::Primitive(TyPrimitive::QBool)); + mixed_ty.add_member(&SymbolId(4, false), Ty::Primitive(TyPrimitive::Bool)); + mixed_ty.add_member(&q2_name, Ty::Primitive(TyPrimitive::QU8)); + mixed_ty.done(); + + let qlayout = QuantumLayout::layout(&Ty::Struct(mixed_ty)); + println!("mixed quantum struct layout: {:?}", qlayout); + assert_eq!(qlayout.qubits(), 9); + + let qstruct = match qlayout { + QuantumLayout::Struct(q) => q, + other => panic!("expected quantum struct layout, got {:?}", other), + }; + // only the quantum fields, no classical ones + assert_eq!(qstruct.members.len(), 2); + assert_eq!(qstruct.members[0].name, q1_name); + assert_eq!(qstruct.members[0].offset, 0); + assert_eq!(qstruct.members[1].name, q2_name); + assert_eq!(qstruct.members[1].offset, 1); + assert_eq!(qstruct.member(&q2_name).unwrap().layout.qubits(), 8); + } + + /// Check a struct holding a mixed inner struct (classical + quantum fields): + /// only the inner quantum field's qubits count, the classical one is ignored. + /// + #[test] + fn check_mixed_inner_struct_layout() { + // type @inner { c:U32, @q:@u4 } => 4 qubits on the quantum side + let inner_name = SymbolId(1, true); + let q_name = SymbolId(3, true); + let mut inner_ty = TyStruct::new(&inner_name); + inner_ty.add_member(&SymbolId(2, false), Ty::Primitive(TyPrimitive::U32)); + inner_ty.add_member(&q_name, Ty::Primitive(TyPrimitive::QU4)); + inner_ty.done(); + + // type @wrap { @a:@bool, inner:@inner } => 1 + 4 = 5 qubits + let wrap_name = SymbolId(4, true); + let inner_field = SymbolId(6, true); + let mut wrap_ty = TyStruct::new(&wrap_name); + wrap_ty.add_member(&SymbolId(5, true), Ty::Primitive(TyPrimitive::QBool)); + wrap_ty.add_member(&inner_field, Ty::Struct(inner_ty)); + wrap_ty.done(); + + let qlayout = QuantumLayout::layout(&Ty::Struct(wrap_ty)); + println!("mixed inner struct quantum layout: {:?}", qlayout); + assert_eq!(qlayout.qubits(), 5); + + let qstruct = match qlayout { + QuantumLayout::Struct(q) => q, + other => panic!("expected quantum struct layout, got {:?}", other), + }; + assert_eq!(qstruct.members.len(), 2); + assert_eq!(qstruct.members[0].offset, 0); // @a + assert_eq!(qstruct.members[1].offset, 1); // inner, packed right after @a + assert_eq!(qstruct.member(&inner_field).unwrap().layout.qubits(), 4); + } + + /// Check a type that also has a classical TypeLayout can have its quantum + /// layout built: the two are independent derivations of the same type. + /// + #[test] + fn check_typelayout_to_quantum_layout() { + let mut lcache = LayoutCache::new(Arch::get_arch64()); + lcache.initialize(Some(Arch::get_arch64())); + + // a quantum struct also goes through the (untouched) classical pass + let bell_name = SymbolId(1, true); + let mut bell_ty = TyStruct::new(&bell_name); + bell_ty.add_member(&SymbolId(2, true), Ty::Primitive(TyPrimitive::QBool)); + bell_ty.add_member(&SymbolId(3, true), Ty::Primitive(TyPrimitive::QBool)); + bell_ty.done(); + + lcache.insert_layout(&Ty::Struct(bell_ty.clone()), &Some(Arch::get_arch64())); + let classical = lcache.layout_of(&Ty::Struct(bell_ty.clone())); + // quantum data occupies no classical memory + assert_eq!(classical.size(), 0); + + // the quantum layout is derived separately, from the type + let quantum = QuantumLayout::layout(&Ty::Struct(bell_ty)); + assert_eq!(quantum.qubits(), 2); + } +} diff --git a/rust/hhat_lang/hhat_core/src/layout/quantum/mod.rs b/rust/hhat_lang/hhat_core/src/layout/quantum/mod.rs new file mode 100644 index 0000000..9987692 --- /dev/null +++ b/rust/hhat_lang/hhat_core/src/layout/quantum/mod.rs @@ -0,0 +1,25 @@ +//! +//! Quantum memory layout for quantum data. +//! +//! Kept fully apart from the classical layout (`base`, `adt`, `primitives`): +//! the classical side lays data out in bytes (size, alignment, memory region), +//! while here we only count qubits, derived straight from the frontend type +//! [`crate::frontend::types::Ty`] so the classical layout never carries quantum +//! information. +//! +//! Split by concern: +//! - [`layout`]: the quantum layout definitions (primitive/struct/enum); +//! - [`cache`]: the permanent store that lays each quantum type out once. +//! +//! The quantum instructions and the quantum program live in the +//! [`crate::program`] module, since `layout` is only for laying out types. +//! + +pub mod layout; +pub mod cache; + +pub use layout::{ + QuantumEnumLayout, QuantumLayout, QuantumMemberLayout, QuantumPrimitiveLayout, + QuantumStructLayout, +}; +pub use cache::QuantumLayoutCache; diff --git a/rust/hhat_lang/hhat_core/src/lib.rs b/rust/hhat_lang/hhat_core/src/lib.rs index 9d1f466..d0d41f7 100644 --- a/rust/hhat_lang/hhat_core/src/lib.rs +++ b/rust/hhat_lang/hhat_core/src/lib.rs @@ -4,10 +4,13 @@ pub mod frontend; pub mod backend; mod core; pub mod layout; +pub mod program; pub use frontend::base::IRInfra; pub use backend::base::BackendCompiler; pub use core::{CoreCompiler, Literal, SymbolId}; +pub use layout::quantum::{QuantumLayout, QuantumLayoutCache}; +pub use program::{QuantumInstr, QuantumProgram}; #[cfg(test)] diff --git a/rust/hhat_lang/hhat_core/src/program/instr.rs b/rust/hhat_lang/hhat_core/src/program/instr.rs new file mode 100644 index 0000000..d2cff82 --- /dev/null +++ b/rust/hhat_lang/hhat_core/src/program/instr.rs @@ -0,0 +1,21 @@ +//! +//! Primitive quantum instructions. +//! + +use crate::SymbolId; + + +/// A primitive quantum instruction. Besides its identity it may append ancilla +/// qubits when lowered to the Q3L code. +/// +#[derive(Clone, Debug)] +pub struct QuantumInstr { + pub name: SymbolId, + pub ancilla: u32, +} + +impl QuantumInstr { + pub fn new(name: &SymbolId, ancilla: u32) -> Self { + Self { name: name.clone(), ancilla } + } +} diff --git a/rust/hhat_lang/hhat_core/src/program/mod.rs b/rust/hhat_lang/hhat_core/src/program/mod.rs new file mode 100644 index 0000000..1e79fda --- /dev/null +++ b/rust/hhat_lang/hhat_core/src/program/mod.rs @@ -0,0 +1,234 @@ +//! +//! Quantum programs. +//! +//! A quantum program is everything that happens under a single quantum variable +//! once it is cast: its quantum layout, the primitive quantum instructions +//! applied to it, and the cast attributes that form the classical register. +//! This is a separate concern from laying types out, which lives in +//! [`crate::layout`]; the primitive quantum instructions live in [`instr`]. +//! + +pub mod instr; + +use crate::frontend::types::Ty; +use crate::layout::quantum::{QuantumLayout, QuantumLayoutCache}; +use crate::SymbolId; + +pub use instr::QuantumInstr; + + +/// Everything that happens under a single quantum variable. +/// +/// Built when the variable is cast, it holds the variable's quantum layout, the +/// primitive quantum instructions applied to it (each carrying ancilla qubits), +/// and the cast attributes that define the classical register. +/// +#[derive(Clone, Debug)] +pub struct QuantumProgram { + pub var: SymbolId, + pub layout: QuantumLayout, + pub instrs: Vec, + pub cast: Vec, +} + +impl QuantumProgram { + pub fn new(var: &SymbolId, layout: QuantumLayout) -> Self { + Self { + var: var.clone(), + layout, + instrs: Vec::new(), + cast: Vec::new(), + } + } + + /// Build the program for a quantum variable from its type, using and + /// populating the permanent quantum layout cache. + /// + pub fn from_variable(var: &SymbolId, ty: &Ty, qlayouts: &mut QuantumLayoutCache) -> Self { + let layout = qlayouts.layout_of(ty); + Self::new(var, layout) + } + + pub fn add_instr(&mut self, instr: QuantumInstr) { + self.instrs.push(instr) + } + + pub fn add_cast(&mut self, attr: &SymbolId) { + self.cast.push(attr.clone()) + } + + /// Qubit range `(offset, width)` of a quantum attribute inside the variable's + /// register, so the Q3L code knows which qubits to name and measure for that + /// attribute. The whole variable maps to its full register. + /// + pub fn attr_qubits(&self, attr: &SymbolId) -> Option<(u32, u32)> { + if *attr == self.var { + return Some((0, self.qsize())); + } + match &self.layout { + QuantumLayout::Struct(s) => s.member(attr).map(|m| (m.offset, m.layout.qubits())), + _ => None, + } + } + + /// Qubits used by the variable's quantum data. + /// + pub fn qsize(&self) -> u32 { + self.layout.qubits() + } + + /// Ancilla qubits required by the quantum instructions. + /// + pub fn ancilla(&self) -> u32 { + self.instrs.iter().map(|i| i.ancilla).sum() + } + + /// Total quantum memory size: data qubits plus instruction ancillas. + /// + pub fn qmem_size(&self) -> u32 { + self.qsize() + self.ancilla() + } + + /// Classical register size: the qubits of the cast attributes, since + /// measuring an attribute yields that many classical bits. + /// + pub fn csize(&self) -> u32 { + // casting the whole variable measures the whole register + if self.cast.contains(&self.var) { + return self.qsize(); + } + match &self.layout { + QuantumLayout::Struct(s) => s.members.iter() + .filter(|m| self.cast.contains(&m.name)) + .map(|m| m.layout.qubits()) + .sum(), + _ => 0, + } + } +} + + +#[cfg(test)] +mod tests { + use crate::frontend::types::{Ty, TyPrimitive, TyStruct}; + use crate::layout::quantum::QuantumLayoutCache; + use crate::program::QuantumProgram; + use crate::program::instr::QuantumInstr; + use crate::SymbolId; + + /// Check the logic from a quantum type (a struct) and a dummy quantum + /// instruction (ancilla 0 to 3) into a QuantumProgram with the full quantum + /// memory mapped out, including the ancillas. + /// + #[test] + fn check_quantum_program() { + // type @bell_t { @s:@bool, @t:@bool } + let bell_name = SymbolId(1, true); + let s_name = SymbolId(2, true); + let t_name = SymbolId(3, true); + let mut bell_ty = TyStruct::new(&bell_name); + bell_ty.add_member(&s_name, Ty::Primitive(TyPrimitive::QBool)); + bell_ty.add_member(&t_name, Ty::Primitive(TyPrimitive::QBool)); + bell_ty.done(); + + let mut qcache = QuantumLayoutCache::new(); + let v_name = SymbolId(4, true); + let sync_name = SymbolId(5, true); + + let _ = (0u32..=3).map(|ancilla| { + let mut prog = QuantumProgram::from_variable( + &v_name, &Ty::Struct(bell_ty.clone()), &mut qcache, + ); + prog.add_instr(QuantumInstr::new(&sync_name, ancilla)); + prog.add_cast(&t_name); // only @v.@t is cast => creg res[1] + println!("quantum program: {:?}", prog); + + assert_eq!(prog.qsize(), 2); + assert_eq!(prog.ancilla(), ancilla); + assert_eq!(prog.qmem_size(), 2 + ancilla); + assert_eq!(prog.csize(), 1); + }).collect::>(); + } + + /// Check a primitive quantum variable @q:@u8 and a dummy quantum instruction + /// (ancilla 0 to 3) build a whole-variable program. + /// + #[test] + fn check_quantum_program_primitive_variable() { + let mut qcache = QuantumLayoutCache::new(); + let q_name = SymbolId(1, true); + let instr_name = SymbolId(2, true); + + let _ = (0u32..=3).map(|ancilla| { + let mut prog = QuantumProgram::from_variable( + &q_name, &Ty::Primitive(TyPrimitive::QU8), &mut qcache, + ); + prog.add_instr(QuantumInstr::new(&instr_name, ancilla)); + prog.add_cast(&q_name); + println!("quantum program (primitive): {:?}", prog); + + assert_eq!(prog.qsize(), 8); + assert_eq!(prog.ancilla(), ancilla); + assert_eq!(prog.qmem_size(), 8 + ancilla); + assert_eq!(prog.csize(), 8); // whole @u8 cast => 8 classical bits + }).collect::>(); + } + + /// Check the classical register reflects the qubit width of the cast + /// attributes: nothing cast gives 0, a @u8 attribute gives 8, both give 9. + /// + #[test] + fn check_quantum_program_classical_register() { + // type @reg { @a:@bool, @b:@u8 } + let reg_name = SymbolId(1, true); + let a_name = SymbolId(2, true); + let b_name = SymbolId(3, true); + let mut reg_ty = TyStruct::new(®_name); + reg_ty.add_member(&a_name, Ty::Primitive(TyPrimitive::QBool)); + reg_ty.add_member(&b_name, Ty::Primitive(TyPrimitive::QU8)); + reg_ty.done(); + + let mut qcache = QuantumLayoutCache::new(); + let v_name = SymbolId(4, true); + + // nothing cast + let prog = QuantumProgram::from_variable(&v_name, &Ty::Struct(reg_ty.clone()), &mut qcache); + assert_eq!(prog.qsize(), 9); + assert_eq!(prog.csize(), 0); + + // only @b:@u8 cast => 8 classical bits (not 1) + let mut prog = QuantumProgram::from_variable(&v_name, &Ty::Struct(reg_ty.clone()), &mut qcache); + prog.add_cast(&b_name); + assert_eq!(prog.csize(), 8); + + // both cast => 1 + 8 = 9 classical bits + prog.add_cast(&a_name); + assert_eq!(prog.csize(), 9); + } + + /// Check each quantum attribute maps to its qubit range inside the register, + /// so the Q3L code can name and measure the right qubits. + /// + #[test] + fn check_quantum_program_attr_qubits() { + // type @reg { @a:@bool, @b:@u8 } => @a at qubit 0 (1q), @b at qubit 1 (8q) + let reg_name = SymbolId(1, true); + let a_name = SymbolId(2, true); + let b_name = SymbolId(3, true); + let mut reg_ty = TyStruct::new(®_name); + reg_ty.add_member(&a_name, Ty::Primitive(TyPrimitive::QBool)); + reg_ty.add_member(&b_name, Ty::Primitive(TyPrimitive::QU8)); + reg_ty.done(); + + let mut qcache = QuantumLayoutCache::new(); + let v_name = SymbolId(4, true); + let prog = QuantumProgram::from_variable(&v_name, &Ty::Struct(reg_ty), &mut qcache); + + assert_eq!(prog.attr_qubits(&a_name), Some((0, 1))); + assert_eq!(prog.attr_qubits(&b_name), Some((1, 8))); + // the whole variable maps to its full register + assert_eq!(prog.attr_qubits(&v_name), Some((0, 9))); + // an unknown attribute has no mapping + assert_eq!(prog.attr_qubits(&SymbolId(99, true)), None); + } +}