Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve issue 23 #26

Merged
merged 4 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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