Skip to content

Memory Qubits#3159

Open
fedimser wants to merge 18 commits intomainfrom
fedimser/qmem
Open

Memory Qubits#3159
fedimser wants to merge 18 commits intomainfrom
fedimser/qmem

Conversation

@fedimser
Copy link
Copy Markdown
Contributor

@fedimser fedimser commented Apr 26, 2026

This PR adds support for new Q# type MemoryQubit and extends standard library with operations on it.

A “Memory Qubit” is a useful abstraction in quantum computing. It is a logical qubit on which gates or measurements are not performed directly. It can only hold a quantum state. When we want to do something with that state, we must “load” it to a regular (“compute”) qubit. When we want to retain state of compute qubit for a while but don’t perform computations, we can “store” it to a memory qubit.

This PR contains:

  • MemoryQubit type definition and changes to compiler to support it.
  • Definitions of 4 intrinsics to allocate, free, load and store qubits.
  • Compiler changes to support use syntax (similarly to Qubits) and translate it to allocation instrinsic.
  • Compiler changes to automatically free MemoryQubit when going out of scope.
  • New 3 methods in Backend (memory_qubit_allocate, memory_qubit_load, memory_qubit_store). Together with existing qubit_free, this is the interface between the Q# language and backends.
  • Supported MemoryQubits in resource estimation backend (count.qs). In other backends use default implementation that uses Qubits as MemoryQubits and Reset+SWAP for stores and loads (which is correct for simulation).
  • Added Std.MemoryQubits which defines operations to work with memory qubits (Load, Store, LoadArray, StoreArray, DoComputation) that internally delegate to 2 intrinsics (load/store).
  • Added short description of MemoryQubits in Std.MemoryQubits.
  • Added tests for syntax, simulator and resource estimation in single rust test file.

Comment thread source/compiler/qsc_eval/src/tests.rs Fixed
Comment thread source/compiler/qsc_eval/src/tests.rs Fixed
Comment thread source/compiler/qsc_eval/src/tests.rs Fixed
@fedimser fedimser changed the title [Draft] Memory Qubits Memory Qubits Apr 27, 2026
@fedimser fedimser marked this pull request as ready for review April 27, 2026 16:45
@github-actions
Copy link
Copy Markdown

Change in memory usage detected by benchmark.

Memory Report for fd2595c

Test This Branch On Main Difference
compile core + standard lib 26549011 bytes 24572311 bytes 1976700 bytes

@fedimser fedimser marked this pull request as draft April 27, 2026 16:49
@fedimser fedimser marked this pull request as ready for review April 29, 2026 17:37
@github-actions
Copy link
Copy Markdown

Change in memory usage detected by benchmark.

Memory Report for 6c5e7da

Test This Branch On Main Difference
compile core + standard lib 26549011 bytes 24572311 bytes 1976700 bytes

Copy link
Copy Markdown
Collaborator

@swernli swernli left a comment

Choose a reason for hiding this comment

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

Reviewed code so far, and provided some feedback. Also, it looks like the pipeline is blocked due to some pending merge conflicts.

Comment thread library/src/tests.rs
}

/// Asserts that given Q# expression fails to compile with given error message.
pub fn test_compile_fails(expr: &str, lib: &str, expected_error_substring: &str) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is this new function necessary? There are other tests that use test_expression_fails defined above and then expect on the output string. Would that be sufficient for new tests?

Comment thread library/src/tests.rs
Comment on lines +318 to +329
logical_counts_expr(&mut interpreter, expr)
.unwrap_or_else(|errs| panic_with_resource_estimation_errors(&errs))
}

fn panic_with_resource_estimation_errors(errs: &[ResourceEstimatorError]) -> ! {
let joined = errs
.iter()
.map(|e| format!("{e}:{e:?}"))
.collect::<Vec<_>>()
.join("\n");
panic!("resource estimation failed:\n{joined}");
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Rather than have a utility to panic with all the errors joined, you could just use the first error returned. Most of the APIs other than compile use a vector of errors for compatibility sake with parts of the infra but only ever return one error.

/// Memory register to transform.
/// ## op
/// Operation to apply to the temporary compute buffer.
operation DoComputation(mem_qs : MemoryQubit[], op : Qubit[] => Unit) : Unit {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I feel like "do" reads weird here... I think ApplyComputation would better match our naming patterns.

}

// MemoryQubit operations.
operation __quantum__qis__memory_qubit_load(memory_qubit : MemoryQubit, qubit : Qubit) : Unit {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

To follow QIR naming conventions, things with __qis__ should end in __body, making this one __quantum__qis__memory_qubit_load__body. I think the "qubit" part may be a bit redundant, since we already have "quantum" and "qis" which stands for Quantum Intruction Set, so __quantum__qis__memory_load__body would be fine. Likewise the corresponding operation below would be __quantum__qis__memory_store__body.

Side note: it seems awkward to add __body but it is really only to differentiation between the inverse/adjoint operations, which have the __adj suffix.

Comment on lines +4 to +18
// # Description
// Prepares a compute qubit in |1>, stores it in a memory qubit, then loads
// it back and measures. The result should be `One`.

import Std.MemoryQubits.*;

operation Main() : Result {
use (q, mem) = (Qubit(), MemoryQubit());

X(q);
Store(q, mem);
Load(mem, q);

return MResetZ(q);
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think this sample could use some additional explanatory text in the top level description and comments in the body itself, like our other language samples do. The motivation is both to help educate users and to act as guidance for LLMs, so it's woth explaining the patterns and motivations here.

"__quantum__qis__mresetz__body" => {
Ok(self.measure_qubit(builder::mresetz_decl(), args_value))
}
"__quantum__qis__memory_qubit_load" | "__quantum__qis__memory_qubit_load__body" => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Once the name fix in the libraries is added, only the __body name is needed here. Same for the case below.

@@ -1766,6 +1770,56 @@ impl<'a> PartialEvaluator<'a> {
"__quantum__qis__mresetz__body" => {
Ok(self.measure_qubit(builder::mresetz_decl(), args_value))
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks like this file is missing some clippy lint clean up. The two cases for load and store are identical, since the arguments ordering is enough to change the directionality of the operation.

.try_into()
.expect("could not convert qubit ID to u32"),
)),
Value::MemoryQubit(q) => Operand::Literal(Literal::Qubit(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This can be combined with the qubit case above.

Comment on lines +3789 to +3795
let callable = Callable {
name: "__quantum__qis__swap__body".to_string(),
input_type: vec![rir::Ty::Prim(rir::Prim::Qubit), rir::Ty::Prim(rir::Prim::Qubit)],
output_type: None,
body: None,
call_type: CallableType::Regular,
};
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

to match the pattern we have above, this should be adder to the builder class instead of inlined here.

"__quantum__rt__qubit_allocate" | "__quantum__rt__qubit_borrow" => {
"__quantum__rt__qubit_allocate"
| "__quantum__rt__qubit_borrow"
| "__quantum__rt__memory_qubit_allocate" => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

no action on this, just a note to confirm understanding: this is consistent with the goal of supporting memory qubit programs as normal compute programs in QIR, as this does not guarantee that the two qubit types will use distinct pools, and if/when we want to support memory qubits as a distinct pool of ids we'd want to differentiate the two kinds of allocation/release calls with separate management (among other changes, like emitting load and store directly). Does that match your expectation as well?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants