Skip to content

add quantum memory layout for quantum data#132

Open
JaipreethTiruvaipati wants to merge 2 commits into
hhat-lang:dev/rust_impl/pre0.3beta/arch_scaffoldfrom
JaipreethTiruvaipati:feat/quantum-memory-layout
Open

add quantum memory layout for quantum data#132
JaipreethTiruvaipati wants to merge 2 commits into
hhat-lang:dev/rust_impl/pre0.3beta/arch_scaffoldfrom
JaipreethTiruvaipati:feat/quantum-memory-layout

Conversation

@JaipreethTiruvaipati

@JaipreethTiruvaipati JaipreethTiruvaipati commented Jun 9, 2026

Copy link
Copy Markdown

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 Ty and lives in its own modules.

What's in it:

  • primitives report a qubit count - @Bool=1, @u2/@u3/@u4/@U8 = 2/3/4/8
  • a struct sums its quantum fields back-to-back with no alignment/padding (classical fields carry no qubits and are skipped); nested structs recurse
  • a 2-variant quantum enum is 1 qubit, first/second variant -> |0>/|1>
  • QuantumLayout is built from the Ty, not the classical TypeLayout (the classical layout is quantum-blind - quantum primitives are zero-sized and quantum struct fields are dropped)
  • QuantumLayoutCache lays each quantum type out once and reuses it
  • QuantumInstr carries an ancilla count; QuantumProgram holds the variable's layout, instructions and cast attributes, and works out data qubits, ancillas (summed), total quantum memory and the classical register size
  • QuantumProgram::attr_qubits(attr) -> (offset, width) so the Q3L emitter knows which qubits each attribute maps to, for measurement

Structure:

  • layout/quantum/ - the quantum layout definition + its cache (layout concerns)
  • program/ - QuantumInstr + QuantumProgram, separate from layout/ since that module is just for laying out types
  • the classical layout files are untouched

Tests (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:

  • The code and logic architecture/design contain no generative AI/LLM
  • The code and logic architecture/design are partially performed by generative AI/LLM
    • 60% performed by the author(s)
  • The code and logic architecture/design are fully performed by generative AI/LLM

Code content:

  • The code contains no generative AI/LLM work
  • The code is partially written by generative AI/LLM
    • 80% performed by the author(s)
  • The code is fully written by generative AI/LLM

Code review:

  • The code review contains no generative AI/LLM
  • The code review is partially done by generative AI/LLM
    • 50% performed by the author(s)
  • The code review is fully done by generative AI/LLM

Code tests:

  • The code tests contain no generative AI/LLM
  • The code tests are partially written by generative AI/LLM
  • The code tests are fully written by generative AI/LLM

@Doomsk

Doomsk commented Jun 9, 2026

Copy link
Copy Markdown
Member

Hi @JaipreethTiruvaipati thank you for this PR. Please make sure all the AI disclaimer fields are properly filled in.

@JaipreethTiruvaipati

Copy link
Copy Markdown
Author

Thanks @Doomsk - updated, all four disclosure fields are filled now (added the missing author % under Code content).

@Doomsk

Doomsk commented Jun 9, 2026

Copy link
Copy Markdown
Member

Thank you @JaipreethTiruvaipati .
I will start with the review now.

@Doomsk Doomsk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @JaipreethTiruvaipati for now those are the points to address. Later I will continue with the rest.

Comment on lines 26 to 28
let mut struct_offset: u32 = 0;
let mut struct_qubits: u32 = 0;
let mut prev_size: u32 = 0;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why quantum layout logic is in the classical layout?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines 15 to 20
/// 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,
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did you do it? The members are already handled inside the members field.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed - removed both. StructLayout keeps only members. quantum fields live in QuantumStructLayout in quantum.rs, which skips the classical ones.

Comment on lines +40 to +49

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,
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quantum logic shouldn't sit in the same place as the classical logic for the quantum memory layout.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed - TypeLayout is untouched now. qubit counts come from QuantumLayout::qubits() in quantum.rs.

@JaipreethTiruvaipati

Copy link
Copy Markdown
Author

thanks for the review - moved all the quantum logic out of the classical layout.

  • base.rs / adt.rs / primitives.rs are back to the original, no quantum fields
  • everything lives in layout/quantum.rs now, built from the Ty
  • QuantumLayout (primitive/struct/enum), a small cache, QuantumInstr + QuantumProgram
  • diff is 3 files: quantum.rs, one line in mod.rs, one re-export in lib.rs

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
@JaipreethTiruvaipati JaipreethTiruvaipati force-pushed the feat/quantum-memory-layout branch from f306b24 to 50d763c Compare June 9, 2026 21:59

@Doomsk Doomsk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +151 to +153
/// variant names in order; the first and second map to `|0>` and `|1>`
pub variants: Vec<SymbolId>,
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why using vector here? can you justify without using LLM?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +168 to +173
/// 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>,
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you explain the reason why this struct is in the file without using LLM?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +222 to +228
#[derive(Clone, Debug)]
pub struct QuantumProgram {
pub var: SymbolId,
pub layout: QuantumLayout,
pub instrs: Vec<QuantumInstr>,
pub cast: Vec<SymbolId>,
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you explain why you chose this structure for the QuantumProgram without using LLM?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +251 to +254

pub fn add_cast(&mut self, attr: &SymbolId) {
self.cast.push(attr.clone())
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does cast mean in this case?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +274 to +278
/// 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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you explain this function without using LLM?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@Doomsk

Doomsk commented Jun 13, 2026

Copy link
Copy Markdown
Member

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

@JaipreethTiruvaipati

Copy link
Copy Markdown
Author

Sure.. I will try joining discord and ping you there...

@Doomsk

Doomsk commented Jun 15, 2026

Copy link
Copy Markdown
Member

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

@JaipreethTiruvaipati

Copy link
Copy Markdown
Author

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

2 participants