diff --git a/CHANGELOG.md b/CHANGELOG.md index e1ab201d9..58b59dee3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ ## v0.10.1-dev +- Add `apply_right!` that applies a clifford operator to the right of a dense clifford operator. +- Add `mul_right!` methods for inplace operations between tableaus - Add a `CliffordOperator` constructor that builds a dense clifford from a `PauliOperator` - Add a `phases` getter for `CliffordOperator` - The generalized hypergraph product code is implemented in the ECC submodule. diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index a2d22fb07..a43f6500f 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -40,7 +40,7 @@ export # Linear Algebra tensor, tensor_pow, logdot, expect, - apply!, apply_inv!, + apply!, apply_inv!, apply_right!, permutesystems, permutesystems!, # Low Level Function Interface generate!, project!, reset_qubits!, traceout!, @@ -1431,6 +1431,8 @@ include("fastmemlayout.jl") include("dense_cliffords.jl") # special one- and two- qubit operators include("symbolic_cliffords.jl") +# apply right operations +include("apply_right.jl") # linear algebra and array-like operations include("linalg.jl") # circuits diff --git a/src/apply_right.jl b/src/apply_right.jl new file mode 100644 index 000000000..3460be72d --- /dev/null +++ b/src/apply_right.jl @@ -0,0 +1,375 @@ +""" +the `apply_right!` function is used to right multiply any quantum operation to unitary +Clifford operation or Pauli product + +```jldoctest +julia> apply_right!(C"X Z", sHadamard(1)) +X₁ ⟼ + Z +Z₁ ⟼ + X +julia> apply_right!(C"Y Z", C"Z Y") +X₁ ⟼ + Z +Z₁ ⟼ - X +julia> apply_right!(C"Y Z", P"X") +X₁ ⟼ + Y +Z₁ ⟼ - Z +``` + +Example: Build a bell state decoder +```jldoctest +julia> cliff = one(CliffordOperator, 2) +X₁ ⟼ + X_ +X₂ ⟼ + _X +Z₁ ⟼ + Z_ +Z₂ ⟼ + _Z +julia> apply_right!(cliff, sHadamard(1)) +X₁ ⟼ + Z_ +X₂ ⟼ + _X +Z₁ ⟼ + X_ +Z₂ ⟼ + _Z +julia> apply_right!(cliff, sCNOT(1, 2)) +X₁ ⟼ + ZX +X₂ ⟼ + _X +Z₁ ⟼ + X_ +Z₂ ⟼ + XZ +julia> apply!(bell(), cliff) ++ Z_ ++ _Z +``` + +See also: [`apply!`](@ref), [`apply_inv!`](@ref) +""" +function apply_right! end + + +############################## +# Dense operators +############################## + +function apply_right!(l::CliffordOperator, r::PauliOperator) + nqubits(l)==nqubits(r) || throw(DimensionMismatch("The Clifford and Pauli operators need to act on the same number of qubits.")) + for i in 1:nqubits(l) + x, z = r[i] + if x + phases(l)[nqubits(l)+i] ⊻= 0x02 + end + if z + phases(l)[i] ⊻= 0x02 + end + end + return l +end + +function apply_right!(l::CliffordOperator, r::CliffordOperator) + nqubits(l)==nqubits(r) || throw(DimensionMismatch("The two Clifford operators need to act on the same number of qubits.")) + l_tab = tab(l) + l_tab_copy = copy(l_tab) + r_tab = tab(r) + threadlocal = l.buffer + @inbounds for row_r in eachindex(r_tab) + zero!(threadlocal) + apply_right_row_kernel!(threadlocal, l_tab, row_r, l_tab_copy, r_tab) + end + l +end + +"""helper for computing the right multiplication of a row of a Clifford operator with another Clifford operator.""" +@inline function apply_right_row_kernel!(new_lrow, l_tab, row, l_tab_copy, r_tab) + new_lrow.phase[] = r_tab.phases[row] + n = nqubits(l_tab) + for qubit in 1:n + x,z = r_tab[row,qubit] + if x&&z + new_lrow.phase[] -= 0x1 + end + if x + mul_left!(new_lrow, l_tab_copy, qubit) + end + if z + mul_left!(new_lrow, l_tab_copy, qubit+n) + end + end + l_tab[row] = new_lrow + new_lrow +end + + +############################## +# Single-qubit gates +############################## + +function apply_right!(l::CliffordOperator, r::sHadamard) + rowswap!(tab(l), r.q, nqubits(l)+r.q) + return l +end + +function apply_right!(l::CliffordOperator, r::sHadamardXY) + mul_right_ignore_anticomm!(tab(l), r.q, nqubits(l)+r.q) + apply_right!(l, sY(r.q)) + return l +end + +function apply_right!(l::CliffordOperator, r::sHadamardYZ) + mul_right_ignore_anticomm!(tab(l), nqubits(l)+r.q, r.q) + apply_right!(l, sZ(r.q)) + return l +end + +function apply_right!(l::CliffordOperator, r::sPhase) + apply_right!(l, sInvPhase(r.q)) + apply_right!(l, sZ(r.q)) + return l +end + +function apply_right!(l::CliffordOperator, r::sInvPhase) + mul_right_ignore_anticomm!(tab(l), r.q, nqubits(l)+r.q) + return l +end + +function apply_right!(l::CliffordOperator, r::sX) + phases(l)[nqubits(l)+r.q] ⊻= 0x02 + return l +end + +function apply_right!(l::CliffordOperator, r::sY) + phases(l)[r.q] ⊻= 0x02 + phases(l)[nqubits(l)+r.q] ⊻= 0x02 + return l +end + +function apply_right!(l::CliffordOperator, r::sZ) + phases(l)[r.q] ⊻= 0x02 + return l +end + +function apply_right!(l::CliffordOperator, r::sSQRTX) + apply_right!(l, sInvSQRTX(r.q)) + apply_right!(l, sX(r.q)) + return l +end + +function apply_right!(l::CliffordOperator, r::sInvSQRTX) + mul_right_ignore_anticomm!(tab(l), nqubits(l)+r.q, r.q) + return l +end + +function apply_right!(l::CliffordOperator, r::sSQRTY) + phases(l)[nqubits(l)+r.q] ⊻= 0x02 + rowswap!(tab(l), r.q, nqubits(l)+r.q) + return l +end + +function apply_right!(l::CliffordOperator, r::sInvSQRTY) + rowswap!(tab(l), r.q, nqubits(l)+r.q) + phases(l)[nqubits(l)+r.q] ⊻= 0x02 + return l +end + +function apply_right!(l::CliffordOperator, r::sCXYZ) + rowswap!(tab(l), r.q, nqubits(l)+r.q) + mul_right_ignore_anticomm!(tab(l), r.q, nqubits(l)+r.q) + return l +end + +function apply_right!(l::CliffordOperator, r::sCZYX) + rowswap!(tab(l), r.q, nqubits(l)+r.q) + mul_right_ignore_anticomm!(tab(l), nqubits(l)+r.q, r.q) + apply_right!(l, sX(r.q)) + return l +end + +function apply_right!(l::CliffordOperator, ::sId1) + return l +end + + +############################## +# Two-qubit gates +############################## + +function apply_right!(l::CliffordOperator, r::sSWAP) + rowswap!(tab(l), nqubits(l)+r.q1, nqubits(l)+r.q2) + rowswap!(tab(l), r.q1, r.q2) + return l +end + +function apply_right!(l::CliffordOperator, r::sSWAPCX) + apply_right!(l, sSWAP(r.q1, r.q2)) + apply_right!(l, sCNOT(r.q2, r.q1)) + return l +end + +function apply_right!(l::CliffordOperator, r::sInvSWAPCX) + apply_right!(l, sCNOT(r.q2, r.q1)) + apply_right!(l, sSWAP(r.q1, r.q2)) + return l +end + +function apply_right!(l::CliffordOperator, r::sISWAP) + apply_right!(l, sSWAP(r.q1, r.q2)) + apply_right!(l, sZCZ(r.q1, r.q2)) + apply_right!(l, sPhase(r.q1)) + apply_right!(l, sPhase(r.q2)) + return l +end + +function apply_right!(l::CliffordOperator, r::sInvISWAP) + apply_right!(l, sSWAP(r.q1, r.q2)) + apply_right!(l, sZCZ(r.q1, r.q2)) + apply_right!(l, sInvPhase(r.q1)) + apply_right!(l, sInvPhase(r.q2)) + return l +end + +function apply_right!(l::CliffordOperator, r::sCZSWAP) + apply_right!(l, sZCZ(r.q2, r.q1)) + apply_right!(l, sSWAP(r.q1, r.q2)) + return l +end + +function apply_right!(l::CliffordOperator, r::sCXSWAP) + apply_right!(l, sCNOT(r.q2, r.q1)) + apply_right!(l, sSWAP(r.q1, r.q2)) + return l +end + +function apply_right!(l::CliffordOperator, r::sCNOT) + return apply_right!(l, sZCX(r.q1, r.q2)) +end + +function apply_right!(l::CliffordOperator, r::sCPHASE) + apply_right!(l, sZCZ(r.q1, r.q2)) + return l +end + +function apply_right!(l::CliffordOperator, r::sZCX) + mul_right_ignore_anticomm!(tab(l), nqubits(l)+r.q2, nqubits(l)+r.q1) + mul_right_ignore_anticomm!(tab(l), r.q1, r.q2) + return l +end + +function apply_right!(l::CliffordOperator, r::sZCY) + apply_right!(l, sHadamardYZ(r.q2)) + apply_right!(l, sZCZ(r.q1, r.q2)) + apply_right!(l, sHadamardYZ(r.q2)) + return l +end + +function apply_right!(l::CliffordOperator, r::sZCZ) + mul_right_ignore_anticomm!(tab(l), r.q2, nqubits(l)+r.q1) + mul_right_ignore_anticomm!(tab(l), r.q1, nqubits(l)+r.q2) + return l +end + +function apply_right!(l::CliffordOperator, r::sXCX) + mul_right_ignore_anticomm!(tab(l), nqubits(l)+r.q2, r.q1) + mul_right_ignore_anticomm!(tab(l), nqubits(l)+r.q1, r.q2) + return l +end + +function apply_right!(l::CliffordOperator, r::sXCY) + apply_right!(l, sHadamardXY(r.q2)) + apply_right!(l, sXCX(r.q1, r.q2)) + apply_right!(l, sHadamardXY(r.q2)) + return l +end + +function apply_right!(l::CliffordOperator, r::sXCZ) + return apply_right!(l, sZCX(r.q2, r.q1)) +end + +function apply_right!(l::CliffordOperator, r::sYCX) + return apply_right!(l, sXCY(r.q2, r.q1)) +end + +function apply_right!(l::CliffordOperator, r::sYCY) + apply_right!(l, sHadamardYZ(r.q1)) + apply_right!(l, sHadamardYZ(r.q2)) + apply_right!(l, sZCZ(r.q1, r.q2)) + apply_right!(l, sHadamardYZ(r.q2)) + apply_right!(l, sHadamardYZ(r.q1)) + return l +end + +function apply_right!(l::CliffordOperator, r::sYCZ) + return apply_right!(l, sZCY(r.q2, r.q1)) +end + +function apply_right!(l::CliffordOperator, r::sZCrY) + mul_right_ignore_anticomm!(tab(l), r.q1, r.q2) + mul_right_ignore_anticomm!(tab(l), r.q2, nqubits(l)+r.q1) + mul_right_ignore_anticomm!(tab(l), nqubits(l)+r.q2, nqubits(l)+r.q1) + mul_right_ignore_anticomm!(tab(l), r.q1, nqubits(l)+r.q2) + return l +end + +function apply_right!(l::CliffordOperator, r::sInvZCrY) + mul_right_ignore_anticomm!(tab(l), r.q1, r.q2) + mul_right_ignore_anticomm!(tab(l), r.q2, nqubits(l)+r.q1) + mul_right_ignore_anticomm!(tab(l), nqubits(l)+r.q2, nqubits(l)+r.q1) + mul_right_ignore_anticomm!(tab(l), r.q1, nqubits(l)+r.q2) + phases(l)[r.q1] ⊻= 0x02 + return l +end + +function apply_right!(l::CliffordOperator, r::sSQRTZZ) + apply_right!(l, sInvSQRTZZ(r.q1, r.q2)) + apply_right!(l, sZ(r.q1)) + apply_right!(l, sZ(r.q2)) + return l +end + +function apply_right!(l::CliffordOperator, r::sInvSQRTZZ) + mul_right_ignore_anticomm!(tab(l), r.q1, nqubits(l)+r.q1) + mul_right_ignore_anticomm!(tab(l), r.q1, nqubits(l)+r.q2) + mul_right_ignore_anticomm!(tab(l), r.q2, nqubits(l)+r.q1) + mul_right_ignore_anticomm!(tab(l), r.q2, nqubits(l)+r.q2) + return l +end + +function apply_right!(l::CliffordOperator, r::sSQRTXX) + apply_right!(l, sInvSQRTXX(r.q1, r.q2)) + apply_right!(l, sX(r.q1)) + apply_right!(l, sX(r.q2)) + return l +end + +function apply_right!(l::CliffordOperator, r::sInvSQRTXX) + mul_right_ignore_anticomm!(tab(l), nqubits(l)+r.q1, r.q1) + mul_right_ignore_anticomm!(tab(l), nqubits(l)+r.q1, r.q2) + mul_right_ignore_anticomm!(tab(l), nqubits(l)+r.q2, r.q1) + mul_right_ignore_anticomm!(tab(l), nqubits(l)+r.q2, r.q2) + return l +end + +function apply_right!(l::CliffordOperator, r::sSQRTYY) + apply_right!(l, sInvSQRTYY(r.q1, r.q2)) + apply_right!(l, sY(r.q1)) + apply_right!(l, sY(r.q2)) + return l +end + +function apply_right!(l::CliffordOperator, r::sInvSQRTYY) + mul_right_ignore_anticomm!(tab(l), r.q1, nqubits(l)+r.q1) + mul_right_ignore_anticomm!(tab(l), nqubits(l)+r.q1, nqubits(l)+r.q2) + mul_right_ignore_anticomm!(tab(l), nqubits(l)+r.q1, r.q2) + mul_right_ignore_anticomm!(tab(l), r.q2, r.q1) + mul_right_ignore_anticomm!(tab(l), nqubits(l)+r.q2, r.q1) + mul_right_ignore_anticomm!(tab(l), r.q1, nqubits(l)+r.q1) + rowswap!(tab(l), r.q1, nqubits(l)+r.q1) + rowswap!(tab(l), r.q2, nqubits(l)+r.q2) + apply_right!(l, sZ(r.q2)) + return l +end + + +"""Multiply Pauli operators `l * r`, ignoring anticommutation phases (keeping only ±1, not ±i)""" +@inline function mul_right_ignore_anticomm!(s::Tableau, m, t::Tableau, i; phases::Val{B}=Val(true)) where B + extra_phase = mul_right!((@view s.xzs[:,m]), (@view t.xzs[:,i]); phases=phases) + B && (s.phases[m] = (extra_phase+s.phases[m]+s.phases[i])&0x2) + s +end +@inline function mul_right_ignore_anticomm!(s::Tableau, m, i; phases::Val{B}=Val(true)) where B + extra_phase = mul_right!((@view s.xzs[:,m]), (@view s.xzs[:,i]); phases=phases) + B && (s.phases[m] = (extra_phase+s.phases[m]+s.phases[i])&0x2) + s +end \ No newline at end of file diff --git a/src/mul_leftright.jl b/src/mul_leftright.jl index 31ef37e83..b1d8afc8c 100644 --- a/src/mul_leftright.jl +++ b/src/mul_leftright.jl @@ -180,6 +180,18 @@ end s end +@inline function mul_right!(s::Tableau, m, t::Tableau, i; phases::Val{B}=Val(true)) where B + extra_phase = mul_right!((@view s.xzs[:,m]), (@view t.xzs[:,i]); phases=phases) + B && (s.phases[m] = (extra_phase+s.phases[m]+s.phases[i])&0x3) + s +end + +@inline function mul_right!(s::Tableau, m, i; phases::Val{B}=Val(true)) where B + extra_phase = mul_right!((@view s.xzs[:,m]), (@view s.xzs[:,i]); phases=phases) + B && (s.phases[m] = (extra_phase+s.phases[m]+s.phases[i])&0x3) + s +end + @inline function mul_left!(s::Stabilizer, m::Integer, i::Integer; phases::Val{B}=Val(true)) where B mul_left!(tab(s), m, i; phases=phases) s diff --git a/test/test_allocations.jl b/test/test_allocations.jl index f38a5c8e6..2eb5ab660 100644 --- a/test/test_allocations.jl +++ b/test/test_allocations.jl @@ -1,6 +1,6 @@ @testitem "Allocation checks" begin using QuantumClifford: mul_left!, RandDestabMemory, Tableau - using Random + using Random, InteractiveUtils n = Threads.nthreads() allocated(f::F) where {F} = @allocations f() @testset "apply! mul_left! canonicalize!" begin @@ -99,4 +99,50 @@ allocated(f1) @test allocated(f1) <= 18 end + + test_sizes = [2,63,64,65,127,128,129,511,512,513] + @testset "apply_right! symbolic" begin + for q in test_sizes + q1 = rand(1:q) + q2 = rand(setdiff(1:q, [q1])) + for _gate in subtypes(AbstractSingleQubitOperator) + _gate == SingleQubitOperator && continue + + l = random_clifford(q) + gate = _gate(q1) + f1() = apply_right!(l, gate) + f1() + allocated(f1) + @test allocated(f1) == 0 + end + for _gate in subtypes(AbstractTwoQubitOperator) + l = random_clifford(q) + gate = _gate(q1, q2) + f2() = apply_right!(l, gate) + f2() + allocated(f2) + @test allocated(f2) == 0 + end + end + end + @testset "apply_right! pauli" begin + for q in test_sizes + l = random_clifford(q) + pauli = random_pauli(q) + f1() = apply_right!(l, pauli) + f1() + allocated(f1) + @test allocated(f1) == 0 + end + end + @testset "apply_right! dense" begin + for q in test_sizes + l = random_clifford(q) + r = random_clifford(q) + f1() = apply_right!(l, r) + f1() + allocated(f1) + @test_broken allocated(f1) == 0 + end + end end diff --git a/test/test_apply_right.jl b/test/test_apply_right.jl new file mode 100644 index 000000000..8d865e5a6 --- /dev/null +++ b/test/test_apply_right.jl @@ -0,0 +1,59 @@ +@testitem "Apply Right" begin + using InteractiveUtils + using QuantumClifford: AbstractCliffordOperator, CliffordOperator + + test_sizes = [1,2,63,64,65,127,128,129,511,512,513] + + # SLOW version of apply_right! for testing + function apply_right_slow!(l::CliffordOperator, r::CliffordOperator; phases=true) + apply!(r, l; phases) + end + function apply_right_slow!(l::CliffordOperator, r::PauliOperator; phases=true) + apply!(CliffordOperator(r), l; phases) + end + function apply_right_slow!(l::CliffordOperator, r::AbstractCliffordOperator; phases=true) + apply!(CliffordOperator(r, nqubits(l)), l; phases) + end + + @testset "Apply Right dense operators" begin + for q in test_sizes + l = random_clifford(q) + cliff = random_clifford(q) + pauli = random_pauli(q) + + @test isequal(apply_right!(copy(l), cliff), apply_right_slow!(l, cliff)) + @inferred apply_right!(copy(l), cliff) + + @test isequal(apply_right!(copy(l), pauli), apply_right_slow!(l, pauli)) + @inferred apply_right!(copy(l), pauli) + end + end + + @testset "Apply Right single-qubit" begin + for q in test_sizes + l = random_clifford(q) + q1 = rand(1:q) + + for gate in subtypes(AbstractSingleQubitOperator) + gate == SingleQubitOperator && continue + + @test isequal(apply_right!(copy(l), gate(q1)), apply_right_slow!(l, gate(q1))) + @inferred apply_right!(copy(l), gate(q1)) + end + end + end + + @testset "Apply Right two-qubit" begin + for q in test_sizes + q < 2 && continue + + l = random_clifford(q) + q1 = rand(1:q); q2 = rand(setdiff(1:q, [q1])) + + for gate in subtypes(AbstractTwoQubitOperator) + @test isequal(apply_right!(copy(l), gate(q1, q2)), apply_right_slow!(l, gate(q1, q2))) + @inferred apply_right!(copy(l), gate(q1, q2)) + end + end + end +end \ No newline at end of file diff --git a/test/test_throws.jl b/test/test_throws.jl index 5ed31820c..1a51f7721 100644 --- a/test/test_throws.jl +++ b/test/test_throws.jl @@ -71,4 +71,6 @@ @test_throws DimensionMismatch Stabilizer(fill(0x0, 2), Matrix{Bool}([1 0 1;1 1 1; 1 0 1]), Matrix{Bool}([1 0 0;1 1 1;1 0 1])) @test_throws DimensionMismatch Stabilizer(fill(0x0, 2), Matrix{Bool}([1 0 1 1 0 0; 1 1 1 1 1 1; 1 0 1 1 0 1])) + @test_throws DimensionMismatch apply_right!(random_clifford(1), random_pauli(2)) + @test_throws DimensionMismatch apply_right!(random_clifford(2), random_clifford(1)) end