Register Management¶
Registers are the core abstraction of PySparQ’s “Register Level Programming”. Unlike traditional qubit-level programming, PySparQ raises the level of operations to quantum registers, which makes algorithm development more intuitive.
The Nature of Registers¶
A register in the System hosts storage of n uint64_t values. Each register has a name, a type, and a bit width, and its value is stored as a uint64_t. This design allows us to encode quantum states in a multi-register form such as \(|a\rangle|b\rangle|c\rangle\) without having to care about how the underlying qubits are encoded.
For example, the QRAM access \(|i\rangle|0\rangle \to |i\rangle|d[i]\rangle\) requires only an address register i and a data register d: the QRAMLoad operator completes the mapping directly at the register level, without managing any qubits at all.
where each \(|a_j\rangle |b_j\rangle |c_j\rangle\) corresponds to one entry of the registers array in a System.
Adding Registers: AddRegister¶
Adding a register is equivalent to taking the tensor product with a \(|0\rangle\) register. The new register has the initial value 0 in all existing basis states.
import pysparq as ps
ps.System.clear()
ps.System.add_register("a", ps.UnsignedInteger, 4)
state = ps.SparseState()
# Add register "b": equivalent to |a⟩ ⊗ |0⟩
ps.AddRegister("b", ps.UnsignedInteger, 4)(state)
# The "b" value of every basis state in state is 0
ps.pprint(state)
AddRegister updates both the static metadata (name_register_map) and the register values of all existing basis states.
You can also use AddRegisterWithHadamard to apply a Hadamard while adding the register, directly creating a uniform superposition:
# Add "q" and create a uniform superposition of |0⟩, |1⟩, ..., |2^n - 1⟩
ps.AddRegisterWithHadamard("q", ps.UnsignedInteger, 2)(state)
Removing Registers: RemoveRegister¶
Removing a register is equivalent to taking the PartialTrace over that register, i.e. eliminating its contribution to the quantum state. In a simulation this has the same effect as measuring the register and then discarding the measurement result.
# Remove register "b": equivalent to a PartialTrace over "b"
ps.RemoveRegister("b")(state)
Important
RemoveRegister checks, before executing, whether the register is entangled with the remaining registers (via TestRemovable). If entanglement exists, the removal raises an exception, because in that case the PartialTrace over a single register can no longer be treated simply as discarding it.
Splitting Registers: SplitRegister¶
SplitRegister splits one register into two: the original register keeps the high bits, and the new register receives the low bits.
ps.System.clear()
ps.System.add_register("full", ps.UnsignedInteger, 8)
state = ps.SparseState()
ps.Init_Unsafe("full", 0b10110011)(state)
# Split: the original "full" keeps the high 4 bits, the new "low" receives the low 4 bits
ps.SplitRegister("full", "low", 4)(state)
# "full" = 0b1011 (high 4 bits), "low" = 0b0011 (low 4 bits)
The split process:
Add the new register (it receives the low bits)
Shrink the bit width of the original register
In all basis states, keep the high bits of the original value in the original register and write the low bits into the new register
Combining Registers: CombineRegister¶
CombineRegister merges two registers into one: the value of the first register is shifted left and concatenated with the value of the second register.
# Combine: "full" = (full << 4) + low
ps.CombineRegister("full", "low")(state)
# "full" = 0b10110011
The merge process:
Extend the bit width of the first register (by the width of the second)
In all basis states, shift the first register’s value left by the second register’s width and add the second register’s value
Remove the second register
PartialTrace: Measurement¶
In PySparQ, PartialTrace is treated as being equivalent to a measurement. This is reasonable at the simulation level — taking a partial trace over some registers is the same as measuring them and discarding the results.
PartialTrace provides three modes:
Class |
Behavior |
Return value |
|---|---|---|
|
Random measurement: randomly pick a measurement outcome according to the probability distribution, then collapse the state |
|
|
Selective collapse: keep the basis states whose specified register has a specified value, and renormalize the remaining ones |
Normalized probability |
|
Range collapse: keep the basis states whose specified register value lies within a given range |
Normalized probability |
import pysparq as ps
ps.System.clear()
ps.System.add_register("a", ps.UnsignedInteger, 2)
ps.System.add_register("b", ps.UnsignedInteger, 2)
state = ps.SparseState()
ps.Hadamard_Int("a")(state)
ps.Hadamard_Int("b")(state)
# Randomly measure register "a"
measured_values, prob = ps.PartialTrace("a")(state)
print(f"Measured values: {measured_values}, probability: {prob}")
# The value of "a" in state collapses to the measurement result
# Selective collapse: force "b" = 2
prob = ps.PartialTraceSelect("b", [2])(state)
# Keep only the basis states with "a" = 1 and normalize
prob = ps.PartialTraceSelect("a", [1])(state)
Register Stack Operations: Push / Pop¶
Push and Pop provide stack management for temporary registers, which is useful when an algorithm needs temporary variables:
# Push: push a temporary register onto the stack
ps.Push("temp", ps.UnsignedInteger, 4)(state)
# Perform computations with the temporary register...
# Pop: pop the temporary register
ps.Pop()(state)
Summary: Register Operations and the Quantum State¶
Operation |
Physical meaning |
Effect on the quantum state |
|---|---|---|
|
Tensor product with \(|0\rangle\) |
Every basis state gains one register with value 0 |
|
PartialTrace (measure and discard) |
Eliminates the register, provided it is not entangled |
|
Splits one subsystem into two |
Number of basis states unchanged, number of registers +1 |
|
Merges two subsystems into one |
Number of basis states unchanged, number of registers -1 |
|
Measurement |
Collapses the state according to the probabilities, reducing the number of basis states |
|
Push/pop of temporary registers |
Auxiliary operations that save/restore register state |
API Reference¶
For the complete API documentation of the register management operators, see the following operator reference pages:
or consult the Operator Reference section for the complete list of all operators.
中文版 ===
寄存器管理¶
寄存器是 PySparQ “寄存器级编程”(Register Level Programming)的核心抽象。与传统的量子比特级编程不同,PySparQ 将操作层面提升到量子寄存器,使算法开发更加直观。
寄存器的本质¶
系统中的寄存器托管了 n 个 uint64_t 的存储。每个寄存器拥有名称、类型和比特宽度,其值以 uint64_t 存储。这种设计允许我们将量子态编码为类似 \(|a\rangle|b\rangle|c\rangle\) 的多寄存器形式,而无需关心底层量子比特的编码方式。
例如,QRAM 访问 \(|i\rangle|0\rangle \to |i\rangle|d[i]\rangle\) 只需要一个地址寄存器 i 和一个数据寄存器 d,QRAMLoad 算子直接在寄存器级别完成映射,完全不需要管理量子比特。
其中每个 \(|a_j\rangle |b_j\rangle |c_j\rangle\) 对应一个 System 中的 registers 数组。
添加寄存器:AddRegister¶
添加寄存器等价于与 \(|0\rangle\) 寄存器做直积(tensor product)。新寄存器在所有现有基态中的初始值为 0。
import pysparq as ps
ps.System.clear()
ps.System.add_register("a", ps.UnsignedInteger, 4)
state = ps.SparseState()
# 添加寄存器 "b":等价于 |a⟩ ⊗ |0⟩
ps.AddRegister("b", ps.UnsignedInteger, 4)(state)
# state 中所有基态的 "b" 值为 0
ps.pprint(state)
AddRegister 会同时更新静态元数据(name_register_map)和所有现有基态的寄存器值。
也可以使用 AddRegisterWithHadamard 在添加寄存器的同时施加 Hadamard,直接创建均匀叠加态:
# 添加 "q" 并创建 |0⟩, |1⟩, ..., |2^n - 1⟩ 的均匀叠加
ps.AddRegisterWithHadamard("q", ps.UnsignedInteger, 2)(state)
删除寄存器:RemoveRegister¶
删除寄存器等价于对该寄存器做 PartialTrace(偏迹),即消除该寄存器对量子态的贡献。在模拟中,这相当于测量该寄存器后丢弃测量结果的效果。
# 删除寄存器 "b":等价于对 "b" 做 PartialTrace
ps.RemoveRegister("b")(state)
Important
RemoveRegister 在执行前会检查该寄存器是否与剩余寄存器存在纠缠(通过 TestRemovable)。如果存在纠缠,删除操作会抛出异常,因为此时对单个寄存器的 PartialTrace 不能简单地等价于丢弃。
拆分寄存器:SplitRegister¶
SplitRegister 将一个寄存器拆分为两个:原寄存器保留高位,新寄存器获取低位。
ps.System.clear()
ps.System.add_register("full", ps.UnsignedInteger, 8)
state = ps.SparseState()
ps.Init_Unsafe("full", 0b10110011)(state)
# 拆分:原 "full" 保留高 4 位,新 "low" 获取低 4 位
ps.SplitRegister("full", "low", 4)(state)
# "full" = 0b1011(高 4 位),"low" = 0b0011(低 4 位)
拆分过程:
添加新寄存器(获取低位)
缩小原寄存器的比特宽度
在所有基态中,将原值的高位保留在原寄存器,低位写入新寄存器
合并寄存器:CombineRegister¶
CombineRegister 将两个寄存器合并为一个:第一个寄存器的值左移后拼接第二个寄存器的值。
# 合并:"full" = (full << 4) + low
ps.CombineRegister("full", "low")(state)
# "full" = 0b10110011
合并过程:
扩展第一个寄存器的比特宽度(加上第二个的宽度)
在所有基态中,将第一个寄存器的值左移第二个的宽度后加上第二个的值
删除第二个寄存器
PartialTrace:测量¶
在 PySparQ 中,PartialTrace 被等价视为测量操作。这在模拟层面是合理的——对某些寄存器进行偏迹等同于对它们进行测量并丢弃结果。
PartialTrace 提供三种模式:
类 |
行为 |
返回值 |
|---|---|---|
|
随机测量:按概率分布随机选择测量结果,然后坍缩态 |
|
|
选择性坍缩:保留指定寄存器值为指定值的基态,归一化其余基态 |
归一化概率 |
|
范围坍缩:保留指定寄存器值在给定范围内的基态 |
归一化概率 |
import pysparq as ps
ps.System.clear()
ps.System.add_register("a", ps.UnsignedInteger, 2)
ps.System.add_register("b", ps.UnsignedInteger, 2)
state = ps.SparseState()
ps.Hadamard_Int("a")(state)
ps.Hadamard_Int("b")(state)
# 随机测量寄存器 "a"
measured_values, prob = ps.PartialTrace("a")(state)
print(f"测量结果: {measured_values}, 概率: {prob}")
# state 中 "a" 的值坍缩为测量结果
# 选择性坍缩:强制 "b" = 2
prob = ps.PartialTraceSelect("b", [2])(state)
# 只保留 "a" = 1 的基态并归一化
prob = ps.PartialTraceSelect("a", [1])(state)
寄存器栈操作:Push / Pop¶
Push 和 Pop 提供临时寄存器的栈管理,适用于算法中需要临时变量的场景:
# Push:将临时寄存器入栈
ps.Push("temp", ps.UnsignedInteger, 4)(state)
# 使用临时寄存器进行计算...
# Pop:弹出临时寄存器
ps.Pop()(state)
寄存器操作与量子态的关系总结¶
操作 |
物理意义 |
对量子态的影响 |
|---|---|---|
|
与 \(|0\rangle\) 直积 |
所有基态增加一个值为 0 的寄存器 |
|
PartialTrace(测量后丢弃) |
消除该寄存器,前提是无纠缠 |
|
将一个子系统拆分为两个 |
基态数量不变,寄存器数加 1 |
|
将两个子系统合并为一个 |
基态数量不变,寄存器数减 1 |
|
测量 |
按概率坍缩态,基态数量减少 |
|
临时寄存器入栈/出栈 |
辅助操作,保存/恢复寄存器状态 |
API 参考¶
寄存器管理算子的完整 API 文档,请参见以下算子参考页面:
或查阅 算子分类详解 章节获取所有算子的完整列表。