quantum-kernel / julia /yao_kernel.jl
SNAPKITTYWEST's picture
push from SNAPKITTYWEST/quantum-kernel
5c61046 verified
Raw
History Blame Contribute Delete
12.1 kB
# yao_kernel.jl
#
# Complete Yao.jl circuit construction for Quantum Kernel Architecture:
# Feature Map U_Φ(x) + Inverse U_Φ(x')† + DFE Measurement + Classical Feedforward
include("yao_types.jl")
include("yao_circuit.jl")
include("yao_to_ir.jl")
using LinearAlgebra
using Random
# -----------------------------------------------------------------------
# Heron Topology & Qubit Mapping
# -----------------------------------------------------------------------
"""
HERON_HEAVY_HEX_EDGES
Heavy-hex connectivity for 10-qubit subset (from diagram):
q0-q1-q2
|/|/|/|
q3 q4 q5 q6
|\\|\\|\\|
q7-q8-q9
"""
const HERON_HEAVY_HEX_EDGES = [
(1,2), (2,3),
(1,4), (2,4), (2,5), (3,5), (3,6),
(4,5), (5,6), (6,7),
(4,8), (5,8), (5,9), (6,9), (6,10),
(8,9), (9,10)
]
const HERON_EDGES_0 = [(a-1, b-1) for (a,b) in HERON_HEAVY_HEX_EDGES]
# -----------------------------------------------------------------------
# Feature Map Parameters
# -----------------------------------------------------------------------
"""
FeatureMapParams
Trainable parameters θ for feature map.
Shape: (n_layers, n_qubits, 3) for [θz1, θy, θz2] per qubit per layer.
"""
struct FeatureMapParams
data::Array{Float64,3}
end
function FeatureMapParams(n_layers::Int, n_qubits::Int; init_scale::Float64=0.1)
data = randn(n_layers, n_qubits, 3) * init_scale .+ 1.0
return FeatureMapParams(data)
end
Base.getindex(p::FeatureMapParams, layer::Int, qubit::Int, param::Int) = p.data[layer, qubit, param]
Base.setindex!(p::FeatureMapParams, val, layer::Int, qubit::Int, param::Int) = p.data[layer, qubit, param] = val
# -----------------------------------------------------------------------
# Single-Qubit Data Encoding Block
# -----------------------------------------------------------------------
"""
data_encoding_block(qubit::Int, x::Float64, θz1::Float64, θy::Float64, θz2::Float64)
R_Z(2xθz1) · R_Y(2xθy) · R_Z(2xθz2) on a single qubit.
"""
function data_encoding_block(qubit::Int, x::Float64, θz1::Float64, θy::Float64, θz2::Float64)
return chain(1,
put(1, [1], Rz(2x * θz1)),
put(1, [1], Ry(2x * θy)),
put(1, [1], Rz(2x * θz2))
)
end
# -----------------------------------------------------------------------
# Feature Map U_Φ(x) Construction
# -----------------------------------------------------------------------
"""
build_feature_map(n_qubits, n_layers, features, params, ent_edges)
Build U_Φ(x) = ∏_l [U_ent · U_rot(x)] as a Yao.jl ChainBlock.
"""
function build_feature_map(n_qubits::Int, n_layers::Int,
features::Vector{Float64},
params::FeatureMapParams,
ent_edges::Vector{Tuple{Int,Int}}=HERON_EDGES_0)::ChainBlock
layers = AbstractBlock[]
for layer in 0:n_layers-1
# Parallel single-qubit data encoding (KronBlock = true parallelism)
encoding_blocks = Pair{Int,AbstractBlock}[]
for q in 0:n_qubits-1
x = features[(q % length(features)) + 1]
θz1 = params[layer+1, q+1, 1]
θy = params[layer+1, q+1, 2]
θz2 = params[layer+1, q+1, 3]
enc_block = data_encoding_block(1, x, θz1, θy, θz2)
push!(encoding_blocks, q+1 => enc_block)
end
push!(layers, kron(n_qubits, encoding_blocks...))
# Entangling layer on heavy-hex edges (sequential)
ent_blocks = AbstractBlock[]
for (q1, q2) in ent_edges
if q1 < n_qubits && q2 < n_qubits
cz_block = control(n_qubits, [q1+1], q2+1 => Z())
push!(ent_blocks, cz_block)
end
end
if !isempty(ent_blocks)
push!(layers, chain(n_qubits, ent_blocks...))
end
end
return chain(n_qubits, layers...)
end
# -----------------------------------------------------------------------
# Inverse Feature Map U_Φ(x)†
# -----------------------------------------------------------------------
"""
build_inverse_feature_map(n_qubits, n_layers, features, params, ent_edges)
Build U_Φ(x)† = ∏_l [U_ent† · U_rot(x)†] with reversed layer order and negative angles.
"""
function build_inverse_feature_map(n_qubits::Int, n_layers::Int,
features::Vector{Float64},
params::FeatureMapParams,
ent_edges::Vector{Tuple{Int,Int}}=HERON_EDGES_0)::ChainBlock
layers = AbstractBlock[]
for layer in n_layers-1:-1:0
# Entangling layer (CZ is self-adjoint)
ent_blocks = AbstractBlock[]
for (q1, q2) in ent_edges
if q1 < n_qubits && q2 < n_qubits
cz_block = control(n_qubits, [q1+1], q2+1 => Z())
push!(ent_blocks, cz_block)
end
end
if !isempty(ent_blocks)
push!(layers, chain(n_qubits, ent_blocks...))
end
# Single-qubit adjoint: reverse order, negative angles
encoding_blocks = Pair{Int,AbstractBlock}[]
for q in n_qubits-1:-1:0
x = features[(q % length(features)) + 1]
θz1 = params[layer+1, q+1, 1]
θy = params[layer+1, q+1, 2]
θz2 = params[layer+1, q+1, 3]
# Adjoint: RZ(-2xθz2) · RY(-2xθy) · RZ(-2xθz1)
enc_block = chain(1,
put(1, [1], Rz(-2x * θz2)),
put(1, [1], Ry(-2x * θy)),
put(1, [1], Rz(-2x * θz1))
)
push!(encoding_blocks, q+1 => enc_block)
end
push!(layers, kron(n_qubits, encoding_blocks...))
end
return chain(n_qubits, layers...)
end
# -----------------------------------------------------------------------
# DFE Measurement Circuit
# -----------------------------------------------------------------------
"""
build_dfe_measurement(n_qubits, pauli_basis)
Build mid-circuit measurement in Pauli basis with conditional reset.
"""
function build_dfe_measurement(n_qubits::Int, pauli_basis::Vector{Char})::ChainBlock
blocks = AbstractBlock[]
# Pauli basis rotation
rotation_blocks = Pair{Int,AbstractBlock}[]
for (q, pauli) in enumerate(pauli_basis)
if pauli == 'X'
push!(rotation_blocks, q => chain(1, put(1, [1], H())))
elseif pauli == 'Y'
push!(rotation_blocks, q => chain(1, put(1, [1], Sdg()), put(1, [1], H())))
end
end
if !isempty(rotation_blocks)
push!(blocks, kron(n_qubits, rotation_blocks...))
end
# Mid-circuit measurement
meas_locs = collect(1:n_qubits)
push!(blocks, measure(n_qubits, meas_locs))
# Conditional reset is handled in QASM emission (classical feedforward)
# Yao.jl doesn't directly support classical feedforward in blocks
return chain(n_qubits, blocks...)
end
# -----------------------------------------------------------------------
# Full DFE Kernel Circuit
# -----------------------------------------------------------------------
"""
build_dfe_kernel_circuit(n_qubits, n_layers, features_a, features_b, params, pauli_basis, ent_edges)
Build complete DFE kernel circuit for one shot:
U_Φ(x) · U_Φ(x')† · Pauli_rotation · Measure
"""
function build_dfe_kernel_circuit(n_qubits::Int, n_layers::Int,
features_a::Vector{Float64},
features_b::Vector{Float64},
params::FeatureMapParams,
pauli_basis::Vector{Char},
ent_edges::Vector{Tuple{Int,Int}}=HERON_EDGES_0)::ChainBlock
blocks = AbstractBlock[]
# U_Φ(x)
push!(blocks, build_feature_map(n_qubits, n_layers, features_a, params, ent_edges))
# U_Φ(x')†
push!(blocks, build_inverse_feature_map(n_qubits, n_layers, features_b, params, ent_edges))
# Pauli basis rotation + measurement
push!(blocks, build_dfe_measurement(n_qubits, pauli_basis))
return chain(n_qubits, blocks...)
end
# -----------------------------------------------------------------------
# Batch Kernel Matrix Circuit Generation
# -----------------------------------------------------------------------
"""
generate_kernel_circuits(dataset, params, n_layers, shots_per_entry, anu_bases)
Generate Yao circuits for all kernel matrix entries with ANU QRNG bases.
Returns Dict mapping (i,j) -> Vector{ChainBlock} (one per shot).
"""
function generate_kernel_circuits(dataset::Vector{Vector{Float64}},
params::FeatureMapParams,
n_layers::Int,
shots_per_entry::Int,
anu_bases::Vector{Vector{Char}};
ent_edges::Vector{Tuple{Int,Int}}=HERON_EDGES_0)
n_samples = length(dataset)
n_qubits = size(params.data, 2)
circuits = Dict{Tuple{Int,Int}, Vector{ChainBlock}}()
for i in 1:n_samples
for j in i:n_samples
shot_circuits = ChainBlock[]
for shot in 1:shots_per_entry
basis_idx = (i-1)*n_samples + (j-1)
basis_idx = (basis_idx * shots_per_entry + shot - 1) % length(anu_bases) + 1
basis = anu_bases[basis_idx]
circuit = build_dfe_kernel_circuit(
n_qubits, n_layers, dataset[i], dataset[j], params, basis, ent_edges
)
push!(shot_circuits, circuit)
end
circuits[(i,j)] = shot_circuits
end
end
return circuits
end
# -----------------------------------------------------------------------
# Lower All Circuits to QuantumIR
# -----------------------------------------------------------------------
"""
lower_kernel_to_ir(circuits) -> Vector{Dict}
Lower all kernel circuits to QuantumIR JSON format.
"""
function lower_kernel_to_ir(circuits::Dict{Tuple{Int,Int}, Vector{ChainBlock}})
ir_list = Dict{String,Any}[]
for ((i,j), shot_circuits) in circuits
for (shot, circuit) in enumerate(shot_circuits)
ir = yao_to_ir(circuit)
ir["metadata"]["kernel_entry"] = [i, j]
ir["metadata"]["shot"] = shot
push!(ir_list, ir)
end
end
return ir_list
end
# -----------------------------------------------------------------------
# Example: Generate Kernel for Circles Dataset
# -----------------------------------------------------------------------
function generate_circles_dataset(n::Int; noise::Float64=0.1)
X = Vector{Vector{Float64}}(undef, n)
y = Vector{Float64}(undef, n)
for i in 1:n
r = rand()
θ = rand() * 2π
if i ≤ n÷2
r = 0.5 + r * 0.3
y[i] = -1.0
else
r = 1.0 + r * 0.3
y[i] = 1.0
end
X[i] = [r * cos(θ), r * sin(θ)]
X[i] .+= randn(2) * noise
end
return X, y
end
function demo_kernel_generation()
# Dataset
X, y = generate_circles_dataset(20, noise=0.1)
# Parameters
n_qubits = 4
n_layers = 2
shots = 100
params = FeatureMapParams(n_layers, n_qubits)
# ANU QRNG bases (simulated for demo)
anu_bases = [rand(['I','X','Y','Z'], n_qubits) for _ in 1:10000]
# Generate circuits
circuits = generate_kernel_circuits(X, params, n_layers, shots, anu_bases)
# Lower to QuantumIR
ir_list = lower_kernel_to_ir(circuits)
# Save
open("kernel_ir.json", "w") do f
JSON3.write(f, ir_list)
end
println("Generated $(length(ir_list)) QuantumIR circuits")
println("Saved to kernel_ir.json")
# Convert first circuit to OpenQASM 3.0
first_ir = ir_list[1]
qasm = qir_to_openqasm3(first_ir; zne_factors=[1.0, 1.5, 2.0, 3.0],
anu_bases=anu_bases[1:shots],
dynamic_shots=true)
write("kernel.qasm3", qasm)
println("Written kernel.qasm3")
end
if abspath(PROGRAM_FILE) == @__FILE__
demo_kernel_generation()
end