diff --git a/examples/clifford.jl b/examples/clifford.jl index daf0c12..892be58 100644 --- a/examples/clifford.jl +++ b/examples/clifford.jl @@ -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). @@ -31,12 +31,12 @@ 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). @@ -44,7 +44,7 @@ qc = chain(put(5, 1 => H), control(5, 1, 2 => Z), control(5, 3, 4 => X), control 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. diff --git a/src/cliffordgroup.jl b/src/cliffordgroup.jl index d5d459f..4d0d226 100644 --- a/src/cliffordgroup.jl +++ b/src/cliffordgroup.jl @@ -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. @@ -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)) @@ -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)] @@ -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 \ No newline at end of file +end diff --git a/src/paulibasis.jl b/src/paulibasis.jl index 21c4869..2fd10f1 100644 --- a/src/paulibasis.jl +++ b/src/paulibasis.jl @@ -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))] @@ -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 \ No newline at end of file +end diff --git a/src/paulistring.jl b/src/paulistring.jl index 4c2abfe..78e8ff5 100644 --- a/src/paulistring.jl +++ b/src/paulistring.jl @@ -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 diff --git a/test/cliffordgroup.jl b/test/cliffordgroup.jl index 75d1c54..0c66787 100644 --- a/test/cliffordgroup.jl +++ b/test/cliffordgroup.jl @@ -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) @@ -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 diff --git a/test/paulibasis.jl b/test/paulibasis.jl index 49ea78f..ff05d79 100644 --- a/test/paulibasis.jl +++ b/test/paulibasis.jl @@ -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) @@ -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 \ No newline at end of file