add quantum memory layout for quantum data#132
Conversation
|
Hi @JaipreethTiruvaipati thank you for this PR. Please make sure all the AI disclaimer fields are properly filled in. |
|
Thanks @Doomsk - updated, all four disclosure fields are filled now (added the missing author % under Code content). |
|
Thank you @JaipreethTiruvaipati . |
Doomsk
left a comment
There was a problem hiding this comment.
Hey @JaipreethTiruvaipati for now those are the points to address. Later I will continue with the rest.
| let mut struct_offset: u32 = 0; | ||
| let mut struct_qubits: u32 = 0; | ||
| let mut prev_size: u32 = 0; |
There was a problem hiding this comment.
why quantum layout logic is in the classical layout?
There was a problem hiding this comment.
good point - reverted this. StructLayout::layout is back to the original, no qubit logic. the quantum struct layout is built in layout/quantum.rs from the Ty now.
| /// quantum fields, kept apart since they do not live in classical memory; | ||
| /// their offset is the starting qubit inside the struct | ||
| pub quantum_members: Vec<MemberLayout>, | ||
| /// number of qubits used by the quantum fields; classical fields are 0 | ||
| pub qubits: u32, | ||
| } |
There was a problem hiding this comment.
why did you do it? The members are already handled inside the members field.
There was a problem hiding this comment.
agreed - removed both. StructLayout keeps only members. quantum fields live in QuantumStructLayout in quantum.rs, which skips the classical ones.
|
|
||
| pub fn qubits(&self) -> u32 { | ||
| match self { | ||
| TypeLayout::Primitive(layout, _) => layout.qubits, | ||
| TypeLayout::Struct(layout, _) => layout.qubits, | ||
| TypeLayout::Enum(layout, _) => layout.qubits, | ||
| TypeLayout::Array(layout, _) => layout.qubits, | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Quantum logic shouldn't sit in the same place as the classical logic for the quantum memory layout.
There was a problem hiding this comment.
removed - TypeLayout is untouched now. qubit counts come from QuantumLayout::qubits() in quantum.rs.
|
thanks for the review - moved all the quantum logic out of the classical layout.
pushed the update. |
a qubit-only layout kept separate from the classical layout, plus a quantum program built when a quantum variable is cast. the classical layout (base/adt/primitives) is untouched - everything lives in layout/quantum.rs and is built from the Ty. - QuantumLayout (primitive/struct/enum), qubits only, no alignment/padding - @Bool=1, @u2/@u3/@u4/@U8 = 2/3/4/8 - a struct stacks its quantum fields back-to-back; classical fields skipped - a 2-variant quantum enum is 1 qubit (first/second variant = |0>/|1>) - QuantumLayoutCache so each quantum type is laid out once and reused - QuantumInstr (ancilla count) and QuantumProgram holding the variable's layout, instructions and cast attributes, with qubit and classical register sizes - tests for primitives, struct, enum, nested/mixed structs, the cache, and programs from a struct and a primitive
f306b24 to
50d763c
Compare
Doomsk
left a comment
There was a problem hiding this comment.
Other than the comments in the code, I'd like to know what was you rationale behind organizing the code logic in that file in particular.
Is there a better way to do it? Answer it without using LLM.
| /// variant names in order; the first and second map to `|0>` and `|1>` | ||
| pub variants: Vec<SymbolId>, | ||
| } |
There was a problem hiding this comment.
why using vector here? can you justify without using LLM?
There was a problem hiding this comment.
A quantum enum maps variants to basis states: first → |0>, second → |1>. Order is the meaning. A Vec keeps insertion order, so variants[0]=|0>, variants[1]=|1> - the index is the basis-state assignment Q3L needs.
Did not used fixed arrray like ( [SymbolId; 2] ) because : "2-variant for now", but a Vec doesn't hard-code 2 if it grows (n variants → ceil(log2 n) qubits later).
Also didn't used set or map too since a set loses order (the whole point) and a name -> index map is redundant since the position already is the index
| /// Permanent store for quantum type layouts so each quantum type is laid out | ||
| /// once and reused across every quantum variable in the program. | ||
| /// | ||
| pub struct QuantumLayoutCache { | ||
| cache: HashMap<Ty, QuantumLayout>, | ||
| } |
There was a problem hiding this comment.
can you explain the reason why this struct is in the file without using LLM?
There was a problem hiding this comment.
The issues says quantum type layouts should be "stored in a more permanent place to avoid redoing work", so did for cache too. Like many variables can share a type(several @bell_ts) → lay each type out once, reuse. layout_of builds on miss, returns cached on hit.
also it mirrors the classical LayoutCache (HashMap<Ty, …>, new/has/layout_of) so it's consistent
| #[derive(Clone, Debug)] | ||
| pub struct QuantumProgram { | ||
| pub var: SymbolId, | ||
| pub layout: QuantumLayout, | ||
| pub instrs: Vec<QuantumInstr>, | ||
| pub cast: Vec<SymbolId>, | ||
| } |
There was a problem hiding this comment.
can you explain why you chose this structure for the QuantumProgram without using LLM?
There was a problem hiding this comment.
Similar to above, issue defines a program as "everything under a quantum variable @v".
The 4 fields are exactly the inputs to the numbers the issue wants:
- var = the root variable it's scoped to
- layout = its qubits (data)
- instrs = primitive instructions, each carrying ancillas → needed for total qubit count
- cast = which attributes were measured → defines the classical register
and data qubits ( layout) + ancillas (instrs) = total quantum memory... and here cast means measuring a quantum attribute and reading the result into classical memory
|
|
||
| pub fn add_cast(&mut self, attr: &SymbolId) { | ||
| self.cast.push(attr.clone()) | ||
| } |
There was a problem hiding this comment.
what does cast mean in this case?
There was a problem hiding this comment.
In Heather, a cast (the * in res:bool = @v.@t * bool) measures a quantum attribute and reads the outcome into classical memory. So cast here holds the attributes that get measured - each measured qubit becomes one classical bit in the register. In the issue only @v.@t is cast, which lowers to measure qv_qt -> res with creg res[1].
| /// 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 |
There was a problem hiding this comment.
can you explain this function without using LLM?
There was a problem hiding this comment.
csize returns how many classical bits the program needs, which is just the total qubits of the attributes that were cast (measured) - one bit per qubit.
First branch: if the whole variable was cast, the entire register is measured, so it returns qsize() (all the data qubits). Otherwise, for a struct it walks the members, keeps the ones whose name is in cast, and sums their qubits. A bare primitive that wasn't wholly cast measures nothing, so it's 0.
Example: @v:@bell_t casting only @v.@t (a @Bool, 1 qubit) → csize = 1 → creg res[1], matching the issue.
|
@JaipreethTiruvaipati would you like to have a discussion on Discord? I'm not convinced yet you (or the LLM) understood some parts. Happy to interact and clarify in real time. |
|
Sure.. I will try joining discord and ping you there... |
|
@JaipreethTiruvaipati I reached you out there but you haven't replied yet. I will wait for tomorrow to close this PR if no response is given. |
|
Hey @Doomsk , was travelling whole day... I pinged you in discord... Will be active there to discuss things... |
…pping after discussing the design with the maintainer: - layout/ is only for laying out types, so QuantumInstr and QuantumProgram move into a new program/ module - the quantum layout and its cache stay under layout/quantum/ - add QuantumProgram::attr_qubits(attr) -> (offset, width) so the Q3L emitter knows which qubits each attribute maps to, for measurement - ancillas are summed for the total (not freed), per the discussion - arrays and wiring into the cast pass left as follow-ups
Closes #113.
A qubit-only memory layout for quantum data, kept separate from the classical layout, plus a quantum program built when a quantum variable is cast. The classical layout (base/adt/primitives) is untouched - the quantum side is derived from the
Tyand lives in its own modules.What's in it:
|0>/|1>Ty, not the classical TypeLayout (the classical layout is quantum-blind - quantum primitives are zero-sized and quantum struct fields are dropped)Structure:
layout/quantum/- the quantum layout definition + its cache (layout concerns)program/- QuantumInstr + QuantumProgram, separate fromlayout/since that module is just for laying out typesTests (
cargo test -p hhat_core): all primitive quantum types, a struct, an enum (+ variant order), a nested struct, a mixed classical/quantum struct, a mixed inner struct, type -> quantum layout, the cache, a QuantumProgram from a struct and from a primitive (ancillas 0-3), the classical register width, and the attribute -> qubit mapping.Follow-ups (discussed with the maintainer): wiring QuantumProgram into the actual cast pass, and quantum arrays (left as
todo!()for now).Generative AI/LLM disclosure
Code and Logic Architecture/Design:
Code content:
Code review:
Code tests: