Skip to content

Commit

Permalink
Merge pull request #26 from nzy1997/jg/new-interfaces
Browse files Browse the repository at this point in the history
Resolve issue 23
  • Loading branch information
nzy1997 authored Jul 2, 2024
2 parents ad810b8 + 18778b2 commit 4af040b
Show file tree
Hide file tree
Showing 6 changed files with 42 additions and 27 deletions.
20 changes: 10 additions & 10 deletions examples/clifford.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,25 @@
# ## Pauli Strings
# A pauli string is a tensor product of Pauli operators acting on different qubits. [`PauliString`](@ref) is a subtype of [`CompositeBlock`] with a field `ids` storing the Pauli operators. We can define pauli string with [`PauliString`](@ref) or [`paulistring`](@ref).
using TensorQEC, TensorQEC.Yao
PauliString(2,1, 4, 3) # X_1Z_3Y_4
PauliString(X, I2, Z, Y) # X_1Z_3Y_4

paulistring(4, 2, (1, 2, 4)) # X_1X_2X_4
paulistring(4, X, (1, 2, 4)) # X_1X_2X_4

# We can use [`Yao.mat`] to get the matrix representation of a Pauli string.
mat(ComplexF64, PauliString(2,4)) # X_1Z_2
# We can use `Yao.mat` to get the matrix representation of a Pauli string.
mat(ComplexF64, PauliString(X, Z)) # X_1Z_2

# ## Pauli Basis
# [`pauli_basis`](@ref) generates all the Pauli strings of a given length. Those Pauli strings are stored in a high-dimensional array.
pauli_basis(2)

# [`pauli_decomposition`](@ref) returns the coefficients of a matrix in the Pauli basis.
pauli_decomposition(mat(ConstGate.CNOT))
pauli_decomposition(ConstGate.CNOT)

# That implies that $CNOT = \frac{1}{2} (I \otimes I + I \otimes X + Z \otimes I - Z \otimes X)$. We can check this by
0.5*(mat(kron(I2,I2) + kron(I2,X) + kron(Z,I2) - kron(Z,X))) == mat(ConstGate.CNOT)

# [`pauli_mapping`](@ref) returns the matrix representation of a quantum gate in the Pauli basis. For Hadamard gate H, we know that $HIH = I, HXH = Z, HYH = -Y, HZH = X$. We can convert $H$ into the Pauli basis.
pauli_mapping(mat(H))
pauli_mapping(H)

# ## Clifford Group
# Clifford group can be generated by Hadamard gate, S gate and CNOT gate[^Bravyi2022]. We can generate the Clifford group with [`clifford_group`](@ref).
Expand All @@ -31,20 +31,20 @@ clifford_group(1)
# Each element in the Clifford group acts on pauli basis as a permutation matrix.
# For $n= 1, 2$, and $3$, this group contains $24$, $11520$, and $92897280$ elements, respectively.
# We can use [`to_perm_matrix`](@ref) to convert a matrix into a permutation matrix.
pm = to_perm_matrix(Int8, Int, H)
pm = to_perm_matrix(H)
pm.perm, pm.vals

# With the permutation matrix, we can apply a Clifford gate to a Pauli string by [`perm_of_paulistring`](@ref). Here we apply the Hadamard gate to the second qubit of Pauli string $I_1X_2$ and get $I_1Z_2$ with a phase $1$.
ps1 = PauliString((1, 2))
ps2, phase = perm_of_paulistring(pm, ps1, [2])
ps1 = PauliString(I2, X)
ps2, phase = perm_of_paulistring(ps1, [2]=>pm)
ps1, ps2, phase

# Put those all together, we can apply a Clifford circuit to a Pauli string by [`clifford_simulate`](@ref).
qc = chain(put(5, 1 => H), control(5, 1, 2 => Z), control(5, 3, 4 => X), control(5, 5, 3 => X), put(5, 1 => X))
vizcircuit(qc)

# Apply the circuit to Pauli string $Z_1Y_2I_3Y_4X_5$, we get $Y_1X_2Y_3Y_4Y_5$ with a phase $1$.
ps = PauliString((4, 3, 1, 3, 2))
ps = PauliString(Z, Y, I2, Y, X)
ps2, phase = clifford_simulate(ps, qc)

# where `ps2` is the Pauli string after the Clifford circuit and `phase` is the phase factor. It corresponds to the following quantum circuit.
Expand Down
17 changes: 9 additions & 8 deletions src/cliffordgroup.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ function clifford_generators(n::Int)
end

"""
to_perm_matrix(::Type{T}, ::Type{Ti}, m::AbstractMatrix; atol=1e-8)
to_perm_matrix(::Type{T}, ::Type{Ti}, m::AbstractBlock; atol=1e-8)
to_perm_matrix([::Type{T}, ::Type{Ti}, ]matrix_or_yaoblock; atol=1e-8)
Convert a Clifford gate to its permutation representation.
Expand All @@ -36,6 +35,7 @@ Convert a Clifford gate to its permutation representation.
### Returns
- `pm`: The permutation matrix. pm.perm is the permutation vector, pm.vals is the phase factor.
"""
to_perm_matrix(m::AbstractBlock; atol=1e-8) = to_perm_matrix(Int8, Int, m; atol)
to_perm_matrix(::Type{T}, ::Type{Ti}, m::AbstractBlock; atol=1e-8) where {T, Ti} = to_perm_matrix(T, Ti, pauli_repr(m); atol)
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))
Expand Down Expand Up @@ -84,15 +84,16 @@ end
Map the Pauli string `ps` by a permutation matrix `pm`. Return the mapped Pauli string and the phase factor.
### Arguments
- `pm`: The permutation matrix.
- `ps`: The Pauli string.
- `pos`: The positions where the permuation is applied.
- `operation`: A pair of the positions to apply the permutation and the permutation matrix.
### Returns
- `ps`: The mapped Pauli string.
- `val`: The phase factor.
"""
function perm_of_paulistring(pm::PermMatrix, ps::PauliString, pos::Vector{Int})
function perm_of_paulistring(ps::PauliString, operation::Pair{Vector{Int}, <:PermMatrix})
pos, pm = operation
@assert 4^length(pos) == length(pm.perm)
v = collect(ps.ids)
ps_perm_num = 1+sum((ps.ids[pos] .-1) .* [4^i for i in 0:length(pos)-1])
v[pos]=[mod(div(pm.perm[ps_perm_num]-1, 4^(j-1)), 4)+1 for j in 1:length(pos)]
Expand All @@ -111,13 +112,13 @@ function clifford_simulate(ps::PauliString, qc::ChainBlock)
gate = toput(_gate)
key = hash(gate.content)
if haskey(gatedict, key)
ps, val = perm_of_paulistring(gatedict[key], ps, collect(gate.locs))
ps, val = perm_of_paulistring(ps, collect(gate.locs)=>gatedict[key])
else
pm = to_perm_matrix(Int8, UInt8, pauli_repr(mat(gate.content)))
push!(gatedict, key => pm)
ps,val = perm_of_paulistring(pm, ps, collect(gate.locs))
ps,val = perm_of_paulistring(ps, collect(gate.locs)=>pm)
end
valf *= val
end
return ps,valf
end
end
12 changes: 8 additions & 4 deletions src/paulibasis.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,25 @@ end
Decompose a matrix into the Pauli basis.
"""
function pauli_decomposition(m::AbstractMatrix)
function pauli_decomposition(m::AbstractMatrix{T}) where T
nqubits = Int(log2(size(m, 1)))
return [tr(mat(pauli) * m) for pauli in pauli_basis(nqubits)] / (2^nqubits)
return [tr(mat(T, pauli) * m) for pauli in pauli_basis(nqubits)] / (2^nqubits)
end
pauli_decomposition(::Type{T}, m::AbstractBlock) where T = pauli_decomposition(mat(T, m))
pauli_decomposition(m::AbstractBlock) = pauli_decomposition(ComplexF64, m)

"""
pauli_mapping(m::AbstractMatrix)
Convert a linear operator into the Pauli basis.
Convert a linear operator to a matrix in the Pauli basis.
"""
function pauli_mapping(m::AbstractMatrix)
nqubits = Int(log2(size(m, 1)))
paulis = pauli_basis(nqubits)
return [real(tr(mat(pi) * m * mat(pj) * m')/size(m, 1)) for pi in paulis, pj in paulis]
end
pauli_mapping(::Type{T}, m::AbstractBlock) where T = pauli_mapping(mat(T, m))
pauli_mapping(m::AbstractBlock) = pauli_mapping(ComplexF64, m)

function pauli_group(n::Int)
return [coeff => PauliString(ci.I) for coeff in 0:3, ci in CartesianIndices(ntuple(_ -> 4, n))]
Expand All @@ -66,4 +70,4 @@ 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
end
4 changes: 3 additions & 1 deletion src/paulistring.jl
Original file line number Diff line number Diff line change
Expand Up @@ -190,5 +190,7 @@ densitymatrix2sumofpaulis(dm::DensityMatrix) = tensor2sumofpaulis(real.(pauli_de
Create a Pauli string with `n` qubits, where the `i`-th qubit is `k` if `i` is in `ids`, otherwise `1`.
`k` = 1 for I2, 2 for X, 3 for Y, 4 for Z.
"""
paulistring(n, k, ids) = PauliString((i ids ? k : 1 for i in 1:n)...)
paulistring(n::Int, k, ids) = PauliString((i ids ? k : _I(k) for i in 1:n)...)
_I(::Int) = 1
_I(::YaoBlocks.PauliGate) = I2

8 changes: 5 additions & 3 deletions test/cliffordgroup.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ using TensorQEC: pauli_repr
m = pauli_repr(H)
pm = TensorQEC.to_perm_matrix(Int8, Int, m)
@test Matrix(pm) m
pm = TensorQEC.to_perm_matrix(H)
@test Matrix(pm) m

m = pauli_repr(ConstGate.CNOT)
pm = TensorQEC.to_perm_matrix(Int8, Int, m)
Expand All @@ -31,16 +33,16 @@ end
@testset "perm_of_paulistring" begin
pm = TensorQEC.to_perm_matrix(Int8, Int, pauli_repr(H))
ps = PauliString((1, 2))
ps2, val = TensorQEC.perm_of_paulistring(pm, ps, [2])
ps2, val = TensorQEC.perm_of_paulistring(ps, [2]=>pm)
@test ps2 == PauliString((1, 4))

pmcn = TensorQEC.to_perm_matrix(Int8, Int, TensorQEC.pauli_repr(ConstGate.CNOT))
ps = PauliString((2, 1, 3, 2))
ps2, val = TensorQEC.perm_of_paulistring(pmcn, ps, [4, 2])
ps2, val = TensorQEC.perm_of_paulistring(ps, [4, 2]=>pmcn)
@test ps2.ids == (2, 2, 3, 2)

ps = PauliString((2, 4, 3, 2))
ps2, val = TensorQEC.perm_of_paulistring(pmcn, ps, [3, 2])
ps2, val = TensorQEC.perm_of_paulistring(ps, [3, 2]=>pmcn)
@test ps2.ids == (2, 3, 2, 2)
end

Expand Down
8 changes: 7 additions & 1 deletion test/paulibasis.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Test, TensorQEC, TensorQEC.Yao
using Test, TensorQEC, TensorQEC.Yao, TensorQEC.LinearAlgebra

@testset "pauli_basis" begin
@test pauli_basis(1) == PauliString.(1:4)
Expand Down Expand Up @@ -29,4 +29,10 @@ end
ps = PauliString(1,2,2)
qc = chain(cnot(3,3,2), cnot(3,3,1))
@test TensorQEC.pauli_string_map_iter(ps, qc).ids == (2,1,2)
end

@testset "yao interfaces" begin
@test paulistring(5, X, (2, 3)) == PauliString(I2, X, X, I2, I2)
@test pauli_decomposition(X) [0, 1, 0, 0]
@test pauli_mapping(X) Diagonal([1, 1, -1, -1])
end

0 comments on commit 4af040b

Please sign in to comment.