Skip to content

Commit

Permalink
Merge pull request #7 from nzy1997/jg/clifford2
Browse files Browse the repository at this point in the history
Clifford group generator
  • Loading branch information
nzy1997 authored Mar 22, 2024
2 parents 0362549 + 0fb77b0 commit 0099767
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 21 deletions.
5 changes: 4 additions & 1 deletion src/TensorQEC.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ using LinearAlgebra
# pauli basis
export pauli_basis, pauli_decomposition, pauli_mapping
import Yao.YaoArrayRegister.StaticArrays: SizedVector
import Yao.YaoArrayRegister.LuxurySparse

# Mod
export Mod2
Expand All @@ -30,10 +31,12 @@ export ToricCode, SurfaceCode
#error correction
export correction_pauli_string


# clifford group
export pauli_group, clifford_group

include("mod2.jl")
include("paulistring.jl")
include("cliffordgroup.jl")
include("paulibasis.jl")
include("tensornetwork.jl")
include("encoder.jl")
Expand Down
57 changes: 57 additions & 0 deletions src/cliffordgroup.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# generate Clifford group members
# https://www.nature.com/articles/s41534-022-00583-7
clifford_group(n::Int) = generate_group(clifford_generators(n))

function clifford_generators(n::Int)
@assert n > 0
if n == 1
return to_perm_matrix.(Int8, UInt8, pauli_repr.([H, ConstGate.S]))
else
return to_perm_matrix.(Int8, UInt8, pauli_repr.(vcat(
[put(n, i=>H) for i=1:n],
[put(n, i=>ConstGate.S) for i=1:n],
[put(n, (i, j)=>ConstGate.CNOT) for j=1:n for i=j+1:n],
[put(n, (j, i)=>ConstGate.CNOT) for j=1:n for i=j+1:n]
)))
end
end
function to_perm_matrix(::Type{T}, ::Type{Ti}, m::AbstractMatrix; atol=1e-8) where {T, Ti}
@assert all(j -> count(i->abs(i) > atol, view(m, :, j)) == 1, 1:size(m, 2))
perm = [findfirst(i->abs(i) > atol, view(m, :, j)) for j=1:size(m, 2)]
vals = [_safe_convert(T, m[perm[j], j]) for j=1:size(m, 2)]
@assert size(m, 1) <= typemax(Ti)
return PermMatrix{T, Ti}(perm, vals) |> LuxurySparse.staticize
end
function _safe_convert(::Type{T}, x::Complex) where T
return _safe_convert(T, real(x)) + _safe_convert(T, imag(x)) * im
end
function _safe_convert(::Type{T}, x::Real) where T
y = round(T, x)
@assert x y "fail to convert target to type: $T"
return y
end

function generate_group(v::Vector; max_size=Inf)
# loop until no new elements are added
keep_loop = true
items_vector = copy(v)
items = Dict(zip(items_vector, 1:length(items_vector)))
while keep_loop && length(items_vector) < max_size
keep_loop = false
for m in v, k in 1:length(items_vector)
candidate = m * items_vector[k]
if !haskey(items, candidate)
keep_loop = true
items[candidate] = k + 1
push!(items_vector, candidate)
end
end
end
return items_vector
end

# integer type should fit the size of the matrix
struct CliffordTable{N, Ti}
basis::Vector{PauliString{N}}
table::Vector{PermMatrix{Int8, Ti, Vector{Int8}, Vector{Ti}}}
end
44 changes: 26 additions & 18 deletions src/paulibasis.jl
Original file line number Diff line number Diff line change
@@ -1,44 +1,52 @@
# pauli basis
# let P = {I, X, Y, Z}, a n-qubit pauli basis is the set of all possible tensor products of elements in P.
# !!! note
# The order of qubits is following the little-endian convention, i.e. the first qubit is the least significant qubit. For example, `pauli_basis(2)` returns
# II, XI (in Yao, it is kron(I2, X)), YI, ZI, IX, XX, YX, ZX, IY, XY, YY, ZY, IZ, XZ, YZ, ZZ

# The following pauli strings may in different format, but are the same thing:
# 1. PauliString(1, 2)
# 2. Yao.kron(I2, X)
# 3. pauli_basis(2)[1,2]
# 4. pauli_basis(2)[5]
# 5. LinearAlgebra.kron(X,I2)
# 6. IX shown in the print
# 7. Apply I on the first qubit and X on the second qubit. This will convert the state |00> to |10>.

# 4. LinearAlgebra.kron(X,I2)
# 5. X ⊗ I shown in the print
function pauli_basis(nqubits::Int)
paulis = [I2, X, Y, Z]
return [Matrix{ComplexF64}(kron(pauli...)) for pauli in product(fill(paulis, nqubits)...)]
return [PauliString{nqubits}(ci.I) for ci in CartesianIndices(ntuple(_ -> 4, nqubits))]
end

# pauli decomposition of a matrix
# returns the coefficients in pauli basis
function pauli_decomposition(m::AbstractMatrix)
nqubits = Int(log2(size(m, 1)))
return [tr(pauli * m) for pauli in pauli_basis(nqubits)] / (2^nqubits)
return [tr(mat(pauli) * m) for pauli in pauli_basis(nqubits)] / (2^nqubits)
end

# defined the linear mapping in the pauli basis
# from the coding space to the physical qubits, i.e. (physical..., coding...) or (output..., input...)
# from the coding space to the physical qubits, i.e. (physical..., coding...)
function pauli_mapping(m::AbstractMatrix)
nqubits = Int(log2(size(m, 1)))
paulis = pauli_basis(nqubits)
return [real(tr(pi * m * pj * m')/size(m, 1)) for pi in paulis, pj in paulis]
return [real(tr(mat(pi) * m * mat(pj) * m')/size(m, 1)) for pi in paulis, pj in paulis]
end

function pauli_string_map(ps::PauliString{N}, paulimapping::Array, qubits::Vector{Int}) where N
c=findall(!iszero, paulimapping[fill(:,length(size(paulimapping)) ÷ 2)...,ps.ids[qubits]...])[1]
return PauliString(([k qubits ? c[findfirst(==(k),qubits)] : ps.ids[k] for k in 1:N]...,))
function pauli_group(n::Int)
return [coeff => PauliString(ci.I) for coeff in 0:3, ci in CartesianIndices(ntuple(_ -> 4, n))]
end

pauli_repr(m::AbstractBlock) = pauli_repr(mat(m))
function pauli_repr(m::AbstractMatrix)
pmat = pauli_mapping(m)
return reshape(pmat, (size(m) .^2)...)
end

function pauli_string_map_iter(ps::PauliString{N}, qc::ChainBlock) where N
if length(qc)==0
return ps
end
block=convert_to_put(qc[1])
return pauli_string_map_iter(pauli_string_map(ps,pauli_mapping(mat(ComplexF64,block.content)),[block.locs...]),qc[2:end])
if length(qc)==0
return ps
end
block=convert_to_put(qc[1])
return pauli_string_map_iter(pauli_string_map(ps,pauli_mapping(mat(ComplexF64,block.content)),[block.locs...]),qc[2:end])
end
function pauli_string_map(ps::PauliString{N}, paulimapping::Array, qubits::Vector{Int}) where N
c=findall(!iszero, paulimapping[fill(:,length(size(paulimapping)) ÷ 2)...,ps.ids[qubits]...])[1]
return PauliString(([k qubits ? c[findfirst(==(k),qubits)] : ps.ids[k] for k in 1:N]...,))
end
29 changes: 29 additions & 0 deletions test/cliffordgroup.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using TensorQEC, Test, TensorQEC.Yao
using TensorQEC: pauli_repr

@testset "perm repr" begin
m = pauli_repr(H)
pm = TensorQEC.to_perm_matrix(Int8, Int, m)
@test Matrix(pm) m

m = pauli_repr(ConstGate.CNOT)
pm = TensorQEC.to_perm_matrix(Int8, Int, m)
@test Matrix(pm) m
end

@testset "generate group" begin
@test TensorQEC.generate_group([1im]) |> length == 4
end

@testset "pauli group" begin
@test size(pauli_group(1)) == (4, 4)
@test all(getfield.(pauli_group(1)[1,:], :first) .== 0)
@test all(getfield.(pauli_group(1)[2,:], :first) .== 1)
@test size(pauli_group(2)) == (4, 4, 4)
end

@testset "clifford group" begin
csize(n::Int) = prod(k->2 * 4^k * (4^k - 1), 1:n)
@test length(clifford_group(1)) == csize(1)
@test length(clifford_group(2)) == csize(2)
end
2 changes: 1 addition & 1 deletion test/paulibasis.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Test, TensorQEC, TensorQEC.Yao

@testset "pauli_basis" begin
@test pauli_basis(1) == [Matrix(I2), Matrix(X), Matrix(Y), Matrix(Z)]
@test pauli_basis(1) == PauliString.(1:4)
end

@testset "pauli_decomposition" begin
Expand Down
5 changes: 4 additions & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ end
include("paulistring.jl")
end

@testset "clifford" begin
include("cliffordgroup.jl")
end
@testset "pauli basis" begin
include("paulibasis.jl")
end
Expand All @@ -27,4 +30,4 @@ end

@testset "error correction" begin
include("errorcorrect.jl")
end
end

0 comments on commit 0099767

Please sign in to comment.