Building Quantum Kernels
A kernel is the unit of quantum computation. This page walks through every building block: defining kernels, allocating and initializing qubits, applying gates, control and adjoint modifiers, custom operations, composing kernels, and parameterization.
Defining a kernel
Decorate a Python function with @qalgora.kernel (in C++, mark it __qpu__).
Allocate a single qubit with qalgora.qubit() or a register with
qalgora.qvector(n); index it like a list, including q[-1] for the last.
import qalgora
@qalgora.kernel
def kernel(n: int):
a = qalgora.qubit() # single qubit
reg = qalgora.qvector(n) # register of n qubits
first, last = reg[0], reg[-1]
Initializing states
Qubits start in |0…0⟩. Allocating a register and applying gates is the runnable way to prepare a state on the reference build:
import qalgora
@qalgora.kernel
def prepare():
q = qalgora.qvector(2) # starts in |00⟩
h(q[0])
x.ctrl(q[0], q[1]) # now a Bell state
qalgora.qvector(vec), qalgora.amplitudes, or handing off a prepared State) is a specification interface; the snippet below is illustrative and is not yet runnable on the open reference build.import numpy as np
# pass a normalized amplitude vector as a kernel argument
@qalgora.kernel
def from_vector(vec: list[complex]):
q = qalgora.qvector(vec)
# precision-agnostic amplitudes (match the simulator's float width)
data = np.array([1, 0, 0, 1], dtype=np.complex128) / np.sqrt(2)
# amplitude preparation inside a kernel
@qalgora.kernel
def from_amplitudes():
q = qalgora.qvector(2)
qalgora.amplitudes(q, [0.5, 0.5, 0.5, 0.5])
# hand off a prepared state from one kernel to another
# `bell` is the predefined Bell-state kernel from the examples
state = qalgora.get_state(bell)
@qalgora.kernel
def continue_from(s: qalgora.State):
q = qalgora.qvector(s)Applying gates
The standard library currently provides single-qubit gates
h, x, y, z, s, t, r1, rx, ry, rz and the two-qubit swap; u3
is a specification interface that can be decomposed into rotation gates. Apply a gate to one qubit
or broadcast across a whole register.
@qalgora.kernel
def gates():
q = qalgora.qvector(3)
h(q) # broadcast Hadamard to every qubit
x(q[0]) # single qubit
rx(0.5, q[1]) # parameterized rotation
# u3(0.1, 0.2, 0.3, q[2]) # specification interface — decompose into rz/ry/rz
swap(q[0], q[2])
Controlled operations
Any gate takes a .ctrl modifier. One control gives a standard controlled gate
(e.g. CNOT from x.ctrl); multiple controls give a multi-controlled gate. Pass the controls
as an explicit list [q[0], q[1]] to stay runnable. A qview slice such as
q[0:k] denotes the same set of control qubits, but that form depends on slice support in
the reference build (see the note below) — prefer the explicit list.
@qalgora.kernel
def controls():
q = qalgora.qvector(3)
x.ctrl(q[0], q[1]) # CNOT
x.ctrl([q[0], q[1]], q[2]) # Toffoli — controls as an explicit list
rz.ctrl(0.5, q[0], q[1]) # controlled rotation
Adjoint operations
Append .adj to invert a single gate, or qalgora.adjoint to invert a
whole sub-kernel — the basis of compute–uncompute patterns.
@qalgora.kernel
def prep(q: qalgora.qview):
h(q[0]); x.ctrl(q[0], q[1])
@qalgora.kernel
def compute_uncompute():
q = qalgora.qvector(2)
prep(q)
t.adj(q[0]) # inverse of a single gate
qalgora.adjoint(prep, q) # inverse of the whole kernel (uncompute)
Custom operations
register_operation and custom-gate registration are a specification interface; the open reference build does not register custom unitaries yet, so the snippet below is illustrative.Register your own unitary from its matrix (given as a flat row-major array), then use it like
any built-in gate — including a .ctrl form.
import numpy as np
qalgora.register_operation("custom_x", np.array([0, 1, 1, 0])) # row-major 2x2
@qalgora.kernel
def use_custom():
q = qalgora.qvector(2)
custom_x(q[0])
custom_x.ctrl(q[0], q[1])The .ctrl and .adj modifiers currently apply to the built-in gates
supported by the reference build; .ctrl/.adj on custom gates is supported
in the specification, but is unavailable in the reference build until custom-gate registration is
built in.
Building kernels with kernels
Kernels compose: pass qubits as a qalgora.qview (a non-owning reference) and call
one kernel from another to build modular circuits.
q[0:k] as a qview into a sub-kernel depends on slice support in the reference build; if slicing is unavailable, pass the whole register or an explicit list of qubits.@qalgora.kernel
def entangle(q0: qalgora.qubit, q1: qalgora.qubit):
x.ctrl(q0, q1)
@qalgora.kernel
def chain():
reg = qalgora.qvector(10)
h(reg[0])
for i in range(9):
entangle(reg[i], reg[i + 1])
Parameterized kernels
Accept a list of classical parameters to drive rotation angles — the foundation of every variational algorithm.
@qalgora.kernel
def ansatz(thetas: list[float]):
q = qalgora.qvector(2)
ry(thetas[0], q[0])
ry(thetas[1], q[1])
x.ctrl(q[0], q[1])
构建量子内核
内核是量子计算的基本单元。本页逐一讲解各个构建模块:定义内核、分配并初始化量子比特、施加门、加受控与伴随修饰符、自定义操作、内核之间的组合,以及内核参数化。
定义内核
使用 @qalgora.kernel 装饰 Python 函数(C++ 中标记为 __qpu__)。
用 qalgora.qubit() 分配单个量子比特,用
qalgora.qvector(n) 分配含 n 个量子比特的寄存器;寄存器像列表一样索引,也能用 q[-1] 取最后一个。
import qalgora
@qalgora.kernel
def kernel(n: int):
a = qalgora.qubit() # single qubit
reg = qalgora.qvector(n) # register of n qubits
first, last = reg[0], reg[-1]
初始化量子态
量子比特初始处于 |0…0⟩ 态。在参考实现上,分配寄存器并施加门是可运行的初态制备方式:
import qalgora
@qalgora.kernel
def prepare():
q = qalgora.qvector(2) # 初始为 |00⟩
h(q[0])
x.ctrl(q[0], q[1]) # 现在是 Bell 态
qalgora.qvector(vec)、qalgora.amplitudes 或传入已制备的 State)属规范接口;下面的片段仅作示意,开放参考实现暂未支持运行。import numpy as np
# 以归一化振幅向量作为内核参数传入
@qalgora.kernel
def from_vector(vec: list[complex]):
q = qalgora.qvector(vec)
# 与精度无关的振幅(匹配模拟器的浮点宽度)
data = np.array([1, 0, 0, 1], dtype=np.complex128) / np.sqrt(2)
# 在内核内部进行振幅制备
@qalgora.kernel
def from_amplitudes():
q = qalgora.qvector(2)
qalgora.amplitudes(q, [0.5, 0.5, 0.5, 0.5])
# 把已制备的态从一个内核交给另一个内核
# bell 是示例中预定义的 Bell 态内核
state = qalgora.get_state(bell)
@qalgora.kernel
def continue_from(s: qalgora.State):
q = qalgora.qvector(s)施加门
标准库当前提供单比特门 h, x, y, z, s, t, r1, rx, ry, rz
和双比特门 swap;u3 属规范接口,可用旋转门分解。门既可作用在单个量子比特上,也可广播到整个寄存器。
@qalgora.kernel
def gates():
q = qalgora.qvector(3)
h(q) # 把 Hadamard 广播到每个量子比特
x(q[0]) # 单个量子比特
rx(0.5, q[1]) # 带参数的旋转
# u3(0.1, 0.2, 0.3, q[2]) # 规范接口——可分解为 rz/ry/rz
swap(q[0], q[2])
受控操作
任意门都能加 .ctrl 修饰符。一个控制比特得到标准受控门
(如 x.ctrl 就是 CNOT),多个控制比特则得到多控门。控制比特请写成显式列表
[q[0], q[1]]以保证可运行。qview 切片(如 q[0:k])表示同一组控制比特,
但该写法取决于参考实现的切片支持(见下方提示)——优先使用显式列表。
@qalgora.kernel
def controls():
q = qalgora.qvector(3)
x.ctrl(q[0], q[1]) # CNOT
x.ctrl([q[0], q[1]], q[2]) # Toffoli——控制位用显式列表
rz.ctrl(0.5, q[0], q[1]) # 受控旋转
伴随操作
在单个门后加 .adj 取它的逆,或用 qalgora.adjoint 对整个子内核取逆——这是计算-反计算(uncompute)模式的基础。
@qalgora.kernel
def prep(q: qalgora.qview):
h(q[0]); x.ctrl(q[0], q[1])
@qalgora.kernel
def compute_uncompute():
q = qalgora.qvector(2)
prep(q)
t.adj(q[0]) # inverse of a single gate
qalgora.adjoint(prep, q) # inverse of the whole kernel (uncompute)
自定义操作
register_operation 与自定义门注册属规范接口;开放参考实现尚未内置自定义酉矩阵注册,因此下面的片段仅作示意。用一个按行优先展开的矩阵数组注册自定义幺正算符,注册后就能像内建门那样使用,也包括 .ctrl 形式。
import numpy as np
qalgora.register_operation("custom_x", np.array([0, 1, 1, 0])) # row-major 2x2
@qalgora.kernel
def use_custom():
q = qalgora.qvector(2)
custom_x(q[0])
custom_x.ctrl(q[0], q[1]).ctrl 与 .adj 修饰符当前适用于参考实现支持的内建门;自定义门的 .ctrl/.adj 在规范上支持,但参考实现若未内置自定义门注册则暂不可用。
以内核构建内核
内核之间可以组合:把量子比特以 qalgora.qview(非拥有引用)的形式传入,在一个内核里调用另一个内核,搭出模块化的电路。
q[0:k])以 qview 传入子内核,取决于参考实现的切片支持;若不支持切片,请传入整个寄存器或显式的量子比特列表。@qalgora.kernel
def entangle(q0: qalgora.qubit, q1: qalgora.qubit):
x.ctrl(q0, q1)
@qalgora.kernel
def chain():
reg = qalgora.qvector(10)
h(reg[0])
for i in range(9):
entangle(reg[i], reg[i + 1])
带参数的内核
内核可以接受一组经典参数来驱动旋转角度——这是所有变分算法的基础。
@qalgora.kernel
def ansatz(thetas: list[float]):
q = qalgora.qvector(2)
ry(thetas[0], q[0])
ry(thetas[1], q[1])
x.ctrl(q[0], q[1])