diff --git a/CHANGELOG.md b/CHANGELOG.md index 75c4fd6a8..0464aa236 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,13 @@ # News -## v0.9.4 - dev +## v0.10.0 - dev +- **(breaking)** `StabMixture` was renamed to `GeneralizedStabilizer`. +- **(fix)** `rowdecompose` was not accounting for the phase of the input Pauli string, leading to potential errors in nonclifford functionality. +- `expect` is now implemented for `GeneralizedStabilizer`. +- Constructing a `Destabilizer` out of a full-rank `Stabilizer` does not require a canonicalization anymore, i.e. `stabilizerview(Destabilizer(s))==s` is guaranteed. +- The `maximally_mixed` function is now available for creating maximally mixed multi-qubit states. - Gate errors are now conveniently supported by the various ECC benchmark setups in the `ECC` module. - Remove printing of spurious debug info from the PyBP decoder. diff --git a/Project.toml b/Project.toml index 18db0fa02..01a9640a1 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumClifford" uuid = "0525e862-1e90-11e9-3e4d-1b39d7109de1" authors = ["Stefan Krastanov and QuantumSavory community members"] -version = "0.9.4" +version = "0.10.0" [deps] Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa" diff --git a/docs/src/stab-algebra-manual.md b/docs/src/stab-algebra-manual.md index 8de368b43..875848845 100644 --- a/docs/src/stab-algebra-manual.md +++ b/docs/src/stab-algebra-manual.md @@ -627,12 +627,12 @@ julia> s=S"-XXX julia> d = Destabilizer(s) 𝒟ℯ𝓈𝓉𝒶𝒷 + Z__ -+ _X_ ++ _XX + __X 𝒮𝓉𝒶𝒷━ - XXX - ZZ_ -- Z_Z ++ _ZZ ``` They have convenience methods to extract only the stabilizer and destabilizer @@ -642,11 +642,11 @@ pieces: julia> stabilizerview(d) - XXX - ZZ_ -- Z_Z ++ _ZZ julia> destabilizerview(d) + Z__ -+ _X_ ++ _XX + __X ``` @@ -672,12 +672,12 @@ Clifford operations can be applied the same way they are applied to stabilizers. julia> apply!(d,tCNOT⊗tHadamard) 𝒟ℯ𝓈𝓉𝒶𝒷 - X_Z -+ XXZ ++ _XZ + X__ 𝒮𝓉𝒶𝒷━ + _ZX - _Z_ -- Z_X ++ ZZX ``` # Mixed States diff --git a/ext/QuantumCliffordQOpticsExt/QuantumCliffordQOpticsExt.jl b/ext/QuantumCliffordQOpticsExt/QuantumCliffordQOpticsExt.jl index b76094335..cca3011df 100644 --- a/ext/QuantumCliffordQOpticsExt/QuantumCliffordQOpticsExt.jl +++ b/ext/QuantumCliffordQOpticsExt/QuantumCliffordQOpticsExt.jl @@ -72,7 +72,14 @@ Ket(dim=4) ```""" Ket(s::QuantumClifford.AbstractStabilizer) = stab_to_ket(s) -function stabmix_to_densityop(s::StabMixture) +""" +$TYPEDSIGNATURES + +Convert a stabilizer tableau to a density matrix corresponding to the given state. +""" +Operator(s::QuantumClifford.AbstractStabilizer) = dm(Ket(s)) # TODO support mixed stabilizer states + +function genstab_to_densityop(s::GeneralizedStabilizer) ρ₀ = zero(dm(Ket(s.stab))) for ((Pₗᵇⁱᵗˢ,Pᵣᵇⁱᵗˢ), χ) in s.destabweights ρ̃ = dm(Ket(s.stab)) @@ -96,7 +103,7 @@ end $TYPEDSIGNATURES """ -Operator(s::StabMixture) = stabmix_to_densityop(s) +Operator(s::GeneralizedStabilizer) = genstab_to_densityop(s) """ diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index 458a10ff5..22d506e2f 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -69,7 +69,7 @@ export # Pauli frames PauliFrame, pftrajectories, pfmeasurements, # Useful States - bell, ghz, + bell, ghz, maximally_mixed, single_z, single_x, single_y, # Graphs graphstate, graphstate!, graph_gatesequence, graph_gate, @@ -81,7 +81,7 @@ export # petrajectories petrajectories, applybranches, # nonclifford - StabMixture, UnitaryPauliChannel, PauliChannel, pcT, + GeneralizedStabilizer, UnitaryPauliChannel, PauliChannel, pcT, # makie plotting -- defined only when extension is loaded stabilizerplot, stabilizerplot_axis, # sum types @@ -98,6 +98,22 @@ function __init__() BIG_INT_MINUS_ONE[] = BigInt(-1) BIG_INT_TWO[] = BigInt(2) BIG_INT_FOUR[] = BigInt(4) + + # Register error hint for the `project!` method for GeneralizedStabilizer + if isdefined(Base.Experimental, :register_error_hint) + Base.Experimental.register_error_hint(MethodError) do io, exc, argtypes, kwargs + if exc.f === project! && argtypes[1] <: GeneralizedStabilizer + print(io, """ + \nThe method `project!` is not appropriate for use with`GeneralizedStabilizer`. + You probably are looking for `projectrand!`. + `project!` in this library is a low-level "linear algebra" method to verify + whether a measurement operator commutes with a set of stabilizers, and to + potentially simplify the tableau and provide the index of the anticommuting + term in that tableau. This linear algebra operation is not defined for + `GeneralStabilizer` as there is no single tableau to provide an index into.""") + end + end + end end const NoZeroQubit = ArgumentError("Qubit indices have to be larger than zero, but you are attempting to create a gate acting on a qubit with a non-positive index. Ensure indexing always starts from 1.") @@ -442,21 +458,54 @@ tab(s::AbstractStabilizer) = s.tab """ A tableau representation of a pure stabilizer state. The tableau tracks the -destabilizers as well, for efficient projections. On initialization there are +destabilizers as well, for efficient projections. + +For full-rank tableaux, the stabilizer part of the tableau is guaranteed to be kept the same as the input stabilizer tableau given to the constructor (a guarantee not kept by [`MixedDestabilizer`](@ref)). + +On initialization there are no checks that the provided state is indeed pure. This enables the use of this -data structure for mixed stabilizer state, but a better choice would be to use +data structure for mixed stabilizer state, but usually a better choice would be [`MixedDestabilizer`](@ref). -""" # TODO clean up and document constructor + +``` +julia> Destabilizer(S"ZZI XXX") +𝒟ℯ𝓈𝓉𝒶𝒷 ++ Z__ ++ _X_ +𝒮𝓉𝒶𝒷━ ++ XXX ++ ZZ_ + +julia> Destabilizer(S"ZZI XXX IZZ") +𝒟ℯ𝓈𝓉𝒶𝒷 ++ X__ ++ _Z_ ++ __X +𝒮𝓉𝒶𝒷━ ++ ZZ_ ++ XXX ++ _ZZ +``` +""" struct Destabilizer{T<:Tableau} <: AbstractStabilizer tab::T end function Destabilizer(s::Stabilizer) row, col = size(s) - row>col && error(DomainError("The input stabilizer has more rows than columns, making it inconsistent or overdetermined.")) - mixed_destab = MixedDestabilizer(s) - t = vcat(tab(destabilizerview(mixed_destab)),tab(stabilizerview(mixed_destab))) - Destabilizer(t) + if row d = Destabilizer(S"Y") 𝒟ℯ𝓈𝓉𝒶𝒷 -+ Z ++ X 𝒮𝓉𝒶𝒷 + Y julia> CliffordOperator(d) -X₁ ⟼ + Z +X₁ ⟼ + X Z₁ ⟼ + Y ``` """ diff --git a/src/nonclifford.jl b/src/nonclifford.jl index f25cc54c2..a70bb2a01 100644 --- a/src/nonclifford.jl +++ b/src/nonclifford.jl @@ -17,7 +17,7 @@ $(TYPEDEF) Represents mixture ∑ ϕᵢⱼ Pᵢ ρ Pⱼ† where ρ is a pure stabilizer state. ```jldoctest -julia> StabMixture(S"-X") +julia> GeneralizedStabilizer(S"-X") A mixture ∑ ϕᵢⱼ Pᵢ ρ Pⱼ† where ρ is 𝒟ℯ𝓈𝓉𝒶𝒷 + Z @@ -32,7 +32,7 @@ with ϕᵢ | Pᵢ 0.853553+0.353553im | + _ 0.146447-0.353553im | + Z -julia> apply!(StabMixture(S"-X"), pcT) +julia> apply!(GeneralizedStabilizer(S"-X"), pcT) A mixture ∑ ϕᵢⱼ Pᵢ ρ Pⱼ† where ρ is 𝒟ℯ𝓈𝓉𝒶𝒷 + Z @@ -47,25 +47,27 @@ with ϕᵢⱼ | Pᵢ | Pⱼ: See also: [`PauliChannel`](@ref) """ -mutable struct StabMixture{T,F} +mutable struct GeneralizedStabilizer{T,F} stab::T destabweights::DefaultDict{Tuple{BitVector, BitVector}, F, F} end -function StabMixture(state) +function GeneralizedStabilizer(state) n = nqubits(state) md = MixedDestabilizer(state) rank(md)==n || throw(ArgumentError(lazy""" - Attempting to convert a `Stabilizer`-like object to `StabMixture` object failed, + Attempting to convert a `Stabilizer`-like object to `GeneralizedStabilizer` object failed, because the initial state does not represent a pure state. - Currently only pure states can be used to initialize a `StabMixture` mixture of stabilizer states. + Currently only pure states can be used to initialize a `GeneralizedStabilizer` mixture of stabilizer states. """)) - StabMixture(md, DefaultDict(0.0im, (falses(n),falses(n))=>1.0+0.0im)) # TODO maybe it should default to Destabilizer, not MixedDestabilizer + GeneralizedStabilizer(md, DefaultDict(0.0im, (falses(n),falses(n))=>1.0+0.0im)) # TODO maybe it should default to Destabilizer, not MixedDestabilizer end -StabMixture(s::StabMixture) = s +GeneralizedStabilizer(s::GeneralizedStabilizer) = s +Base.copy(sm::GeneralizedStabilizer) = GeneralizedStabilizer(copy(sm.stab),copy(sm.destabweights)) +Base.:(==)(sm₁::GeneralizedStabilizer, sm₂::GeneralizedStabilizer) = sm₁.stab==sm₂.stab && sm₁.destabweights==sm₂.destabweights -function Base.show(io::IO, s::StabMixture) +function Base.show(io::IO, s::GeneralizedStabilizer) println(io, "A mixture ∑ ϕᵢⱼ Pᵢ ρ Pⱼ† where ρ is") show(io,s.stab) println(io) @@ -89,14 +91,160 @@ function _stabmixdestab(mixeddestab, d) p end -function apply!(state::StabMixture, gate::AbstractCliffordOperator) # TODO conjugate also the destabs +""" +Apply a Clifford gate to a generalized stabilizer state, i.e. a weighted sum of stabilizer states. + +```jldoctest +julia> sm = GeneralizedStabilizer(S"-X") +A mixture ∑ ϕᵢⱼ Pᵢ ρ Pⱼ† where ρ is +𝒟ℯ𝓈𝓉𝒶𝒷 ++ Z +𝒮𝓉𝒶𝒷 +- X +with ϕᵢⱼ | Pᵢ | Pⱼ: + 1.0+0.0im | + _ | + _ + +julia> apply!(sm, CliffordOperator(tHadamard)) +A mixture ∑ ϕᵢⱼ Pᵢ ρ Pⱼ† where ρ is +𝒟ℯ𝓈𝓉𝒶𝒷 ++ X +𝒮𝓉𝒶𝒷 +- Z +with ϕᵢⱼ | Pᵢ | Pⱼ: + 1.0+0.0im | + _ | + _ +``` + +See also: [`GeneralizedStabilizer`](@ref) +""" +function apply!(state::GeneralizedStabilizer, gate::AbstractCliffordOperator) apply!(state.stab, gate) state end +"""$(TYPEDSIGNATURES) + +Expectation value for the [PauliOperator](@ref) observable given the [`GeneralizedStabilizer`](@ref) state `s`. + +```jldoctest genstab +julia> sm = GeneralizedStabilizer(S"-X") +A mixture ∑ ϕᵢⱼ Pᵢ ρ Pⱼ† where ρ is +𝒟ℯ𝓈𝓉𝒶𝒷 ++ Z +𝒮𝓉𝒶𝒷 +- X +with ϕᵢⱼ | Pᵢ | Pⱼ: + 1.0+0.0im | + _ | + _ + +julia> apply!(sm, pcT) +A mixture ∑ ϕᵢⱼ Pᵢ ρ Pⱼ† where ρ is +𝒟ℯ𝓈𝓉𝒶𝒷 ++ Z +𝒮𝓉𝒶𝒷 +- X +with ϕᵢⱼ | Pᵢ | Pⱼ: + 0.0+0.353553im | + _ | + Z + 0.0-0.353553im | + Z | + _ + 0.853553+0.0im | + _ | + _ + 0.146447+0.0im | + Z | + Z + +julia> χ′ = expect(P"-X", sm) +0.7071067811865475 + 0.0im + +julia> prob = (real(χ′)+1)/2 +0.8535533905932737 +``` + +""" +function expect(p::PauliOperator, s::GeneralizedStabilizer) # TODO optimize + χ′ = zero(valtype(s.destabweights)) + phase, b, c = rowdecompose(p, s.stab) + for ((dᵢ,dⱼ), χ) in s.destabweights + _allthreesumtozero(dᵢ,dⱼ,b) || continue + χ′ += χ * (-1)^(dᵢ'*c) + end + return (im)^(phase) * χ′ +end + +"""Same as `all(==(0), (a.+b.+c) .% 2)`""" +function _allthreesumtozero(a,b,c) + n = length(a) + @inbounds @simd for i in 1:n + odd = (a[i]+b[i]+c[i]) & 1 + if odd != 0 + return false + end + end + true +end + +"""$(TYPEDSIGNATURES) + +Performs a randomized projection of the state represented by the [`GeneralizedStabilizer`](@ref) `sm`, +based on the measurement of a [PauliOperator](@ref) `p`. + +Unlike in the case of stabilizer states, the expectation value χ′ of a Pauli operator +with respect to these more general states can be any real number between -1 and 1. +The expectation value can be calculated with `expect(p, sm)`. + +```math +\\chi' = \\langle p \\rangle = \\text{expect}(p, sm) +``` + +To convert χ′ into a probability of projecting on the +1 eigenvalue branch: + +```math +\\text{probability}_{1} = \\frac{\\text{real}(\\chi') + 1}{2} +``` + +!!! note Because the possible measurement results are themselves not stabilizer states anymore, +we can not use the `project!` API, which assumes a stabilizer tableau and reports detailed +information about whether the tableau and measurement commute or anticommute. + +```jldoctest genstab +julia> sm = GeneralizedStabilizer(S"-X"); + +julia> apply!(sm, pcT) +A mixture ∑ ϕᵢⱼ Pᵢ ρ Pⱼ† where ρ is +𝒟ℯ𝓈𝓉𝒶𝒷 ++ Z +𝒮𝓉𝒶𝒷 +- X +with ϕᵢⱼ | Pᵢ | Pⱼ: + 0.0+0.353553im | + _ | + Z + 0.0-0.353553im | + Z | + _ + 0.853553+0.0im | + _ | + _ + 0.146447+0.0im | + Z | + Z + +julia> χ′ = expect(P"-X", sm) +0.7071067811865475 + 0.0im + +julia> prob₁ = (real(χ′)+1)/2 +0.8535533905932737 +``` + +See also: [`expect`](@ref) +""" +function projectrand!(sm::GeneralizedStabilizer, p::PauliOperator) + χ′ = expect(p, sm) + # Compute the probability of measuring in the +1 eigenstate + prob₁ = (real(χ′)+1)/2 + # Randomly choose projection based on this probability + return _proj(sm, rand() < prob₁ ? p : -p) +end + +function _proj(sm::GeneralizedStabilizer, p::PauliOperator) + error("This functionality is not implemented yet") +end + +function project!(s::GeneralizedStabilizer, p::PauliOperator) + throw(MethodError(project!, (s, p))) +end + +nqubits(sm::GeneralizedStabilizer) = nqubits(sm.stab) + abstract type AbstractPauliChannel <: AbstractOperation end -"""A Pauli channel datastructure, mainly for use with [`StabMixture`](@ref) +"""A Pauli channel datastructure, mainly for use with [`GeneralizedStabilizer`](@ref) See also: [`UnitaryPauliChannel`](@ref)""" struct PauliChannel{T,S} <: AbstractPauliChannel @@ -123,7 +271,8 @@ end function Base.show(io::IO, pc::PauliChannel) println(io, "Pauli channel ρ ↦ ∑ ϕᵢⱼ Pᵢ ρ Pⱼ† with the following branches:") print(io, "with ϕᵢⱼ | Pᵢ | Pⱼ:") - for ((di,dj), χ) in zip(pc.paulis, pc.weights) + for (i, (di,dj)) in enumerate(pc.paulis) + χ = pc.weights[i] println(io) print(io, " ") print(IOContext(io, :compact => true), χ) @@ -137,14 +286,21 @@ end nqubits(pc::PauliChannel) = nqubits(pc.paulis[1][1]) -function apply!(state::StabMixture, gate::PauliChannel) +"""Applies a (potentially non-unitary) Pauli channel to a generalized stabilizer. + +See also: [`GeneralizedStabilizer`](@ref), [`PauliChannel`](@ref), [`UnitaryPauliChannel`](@ref) +""" +function apply!(state::GeneralizedStabilizer, gate::AbstractPauliChannel; prune_threshold=1e-10) + nqubits(state) == nqubits(gate) || throw(DimensionMismatch(lazy"GeneralizedStabilizer has $(nqubits(state)) qubits, but PauliChannel has $(nqubits(gate)). Use `embed` to create an appropriately padded PauliChannel.")) dict = state.destabweights stab = state.stab - tzero = zero(eltype(dict).parameters[2]) - tone = one(eltype(dict).parameters[2]) - newdict = typeof(dict)(tzero) # TODO jeez, this is ugly + dtype = valtype(dict) + tzero = zero(dtype) + tone = one(dtype) + newdict = typeof(dict)(tzero) for ((dᵢ,dⱼ), χ) in dict # the state - for ((Pₗ,Pᵣ), w) in zip(gate.paulis,gate.weights) # the channel + for (i, (Pₗ,Pᵣ)) in enumerate(gate.paulis) # the channel + w = gate.weights[i] phaseₗ, dₗ, dₗˢᵗᵃᵇ = rowdecompose(Pₗ,stab) phaseᵣ, dᵣ, dᵣˢᵗᵃᵇ = rowdecompose(Pᵣ,stab) c = (dot(dₗˢᵗᵃᵇ,dᵢ) + dot(dᵣˢᵗᵃᵇ,dⱼ))*2 @@ -154,11 +310,7 @@ function apply!(state::StabMixture, gate::PauliChannel) newdict[(dᵢ′,dⱼ′)] += χ′ end end - for (k,v) in newdict # TODO is it safe to modify a dict while iterating over it? - if abs(v) < 1e-14 # TODO parameterize this pruning parameter - delete!(newdict, k) - end - end + filter!(x -> abs(x[2]) > prune_threshold, newdict) state.destabweights = newdict state end @@ -207,10 +359,10 @@ function rowdecompose(pauli,state::Union{MixedDestabilizer, Destabilizer}) end end p = mod(-Pₜ.phase[],4) # complex conjugate - return p, b, c + return p+pauli.phase[], b, c end -"""A Pauli channel datastructure, mainly for use with [`StabMixture`](@ref). +"""A Pauli channel datastructure, mainly for use with [`GeneralizedStabilizer`](@ref). More convenient to use than [`PauliChannel`](@ref) when you know your Pauli channel is unitary. @@ -247,11 +399,14 @@ struct UnitaryPauliChannel{T,S,P} <: AbstractPauliChannel end PauliChannel(p::UnitaryPauliChannel) = p.paulichannel +Base.copy(p::UnitaryPauliChannel) = UnitaryPauliChannel(map(copy, p.paulis), map(copy, p.weights)) +Base.:(==)(p₁::UnitaryPauliChannel, p₂::UnitaryPauliChannel) = p₁.paulis==p₂.paulis && p₁.weights==p₂.weights function Base.show(io::IO, pc::UnitaryPauliChannel) println(io, "A unitary Pauli channel P = ∑ ϕᵢ Pᵢ with the following branches:") print(io, "with ϕᵢ | Pᵢ") - for (p, χ) in zip(pc.paulis, pc.weights) + for (i, p) in enumerate(pc.paulis) + χ = pc.weights[i] println(io) print(io, " ") print(IOContext(io, :compact => true), χ) @@ -265,7 +420,45 @@ end nqubits(pc::UnitaryPauliChannel) = nqubits(pc.paulis[1]) -apply!(state::StabMixture, gate::UnitaryPauliChannel) = apply!(state, gate.paulichannel) +apply!(state::GeneralizedStabilizer, gate::UnitaryPauliChannel; prune_threshold=1e-10) = apply!(state, gate.paulichannel; prune_threshold) + +""" +Calculates the number of non-zero elements in the density matrix `χ` +of a [`GeneralizedStabilizer`](@ref), representing the inverse sparsity +of `χ`. It provides a measure of the state's complexity, with bounds +`Λ(χ) ≤ 4ⁿ`. + +```jldoctest heuristic +julia> using QuantumClifford: invsparsity; # hide + +julia> sm = GeneralizedStabilizer(S"X") +A mixture ∑ ϕᵢⱼ Pᵢ ρ Pⱼ† where ρ is +𝒟ℯ𝓈𝓉𝒶𝒷 ++ Z +𝒮𝓉𝒶𝒷 ++ X +with ϕᵢⱼ | Pᵢ | Pⱼ: + 1.0+0.0im | + _ | + _ + +julia> apply!(sm, pcT) |> invsparsity +4 +``` + +Similarly, it calculates the number of non-zero elements in the density +matrix `ϕᵢⱼ`​ of a PauliChannel, providing a measure of the channel +complexity. + +```jldoctest heuristic +julia> invsparsity(pcT) +4 +``` + +See also: [`GeneralizedStabilizer`](@ref) +""" +function invsparsity end + +invsparsity(sm::GeneralizedStabilizer) = count(!iszero, values(sm.destabweights::DefaultDict{Tuple{BitVector, BitVector}, ComplexF64, ComplexF64})) +invsparsity(gate::AbstractPauliChannel) = count(!iszero, values(gate.paulichannel.weights::Vector{ComplexF64})) ## # Predefined Pauli Channels diff --git a/src/useful_states.jl b/src/useful_states.jl index bde70554c..fd89d08db 100644 --- a/src/useful_states.jl +++ b/src/useful_states.jl @@ -157,5 +157,13 @@ function ghz(n::Int) s end +""" +Prepare a maximally mixed state of n qubits. +""" +function maximally_mixed(n) + tab = vcat(one(Stabilizer,n; basis=:X).tab, one(Stabilizer,n; basis=:Z).tab) + return MixedDestabilizer(tab, 0) +end + # TODO document these explicitly # TODO cluster states, toric code, planar code, other codes from python libraries diff --git a/test/runtests.jl b/test/runtests.jl index b4846e7cb..724170894 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -58,6 +58,8 @@ end @doset "entanglement" @doset "enumerate" @doset "quantumoptics" +@doset "nonclifford" +@doset "nonclifford_quantumoptics" @doset "ecc" @doset "ecc_codeproperties" @doset "ecc_decoder_all_setups" diff --git a/test/test_jet.jl b/test/test_jet.jl index 131ffa8eb..61b5ce1c0 100644 --- a/test/test_jet.jl +++ b/test/test_jet.jl @@ -5,6 +5,8 @@ using Static using Graphs using StridedViews using LinearAlgebra +using Nemo +using AbstractAlgebra using JET: ReportPass, BasicPass, InferenceErrorReport, UncaughtExceptionReport @@ -32,9 +34,11 @@ end AnyFrameModule(Static), AnyFrameModule(StridedViews), AnyFrameModule(LinearAlgebra), + AnyFrameModule(Nemo), + AnyFrameModule(AbstractAlgebra), ) ) @show rep @test_broken length(JET.get_reports(rep)) == 0 - @test length(JET.get_reports(rep)) <= 25 + @test length(JET.get_reports(rep)) <= 24 end diff --git a/test/test_nonclifford.jl b/test/test_nonclifford.jl index 292aaac8d..79773b56c 100644 --- a/test/test_nonclifford.jl +++ b/test/test_nonclifford.jl @@ -1,5 +1,5 @@ using QuantumClifford -using QuantumClifford: StabMixture, rowdecompose, PauliChannel, mul_left!, mul_right! +using QuantumClifford: GeneralizedStabilizer, rowdecompose, PauliChannel, invsparsity, mul_left!, mul_right! using Test using InteractiveUtils using Random @@ -8,7 +8,7 @@ using Random @testset "Pauli decomposition into destabilizers" begin for n in [1,2,63,64,65,66,300] - p = random_pauli(n; nophase=true) + p = random_pauli(n; nophase=false, realphase=true) s = random_destabilizer(n) phase, b, c = rowdecompose(p,s) p0 = zero(p) @@ -26,21 +26,37 @@ end @testset "PauliChannel T gate ^4 = Id" begin tgate = pcT - state = StabMixture(S"X") + state = GeneralizedStabilizer(S"X") apply!(state, tgate) apply!(state, tgate) apply!(state, tgate) apply!(state, tgate) - @test state.destabweights |> values |> collect == [1] + @test state.destabweights |> values |> collect ≈ [1] @test state.destabweights |> keys |> collect == [([1],[1])] end ## -@test_throws ArgumentError StabMixture(S"XX") +@testset "Inverse sparsity" begin + for n in 1:5 + s = random_stabilizer(n) + gs = GeneralizedStabilizer(s) + for i in 1:rand(1:4) + apply!(gs, embed(n, i, pcT)) + end + # Λ(χ) ≤ 4ⁿ + @test invsparsity(gs) <= 4^n + channels = [embed(n, i, pcT) for i in 1:rand(1:4)] + # Λ(ϕᵢⱼ) ≤ 4ⁿ + @test all(invsparsity(channel) <= 4^n for channel in channels) + end +end + +@test_throws ArgumentError GeneralizedStabilizer(S"XX") @test_throws ArgumentError PauliChannel(((P"X", P"Z"), (P"X", P"ZZ")), (1,2)) @test_throws ArgumentError PauliChannel(((P"X", P"Z"), (P"X", P"Z")), (1,)) @test_throws ArgumentError UnitaryPauliChannel((P"X", P"ZZ"), (1,2)) @test_throws ArgumentError UnitaryPauliChannel((P"X", P"Z"), (1,)) +@test_throws MethodError project!(GeneralizedStabilizer(S"X"), P"X") diff --git a/test/test_nonclifford_quantumoptics.jl b/test/test_nonclifford_quantumoptics.jl new file mode 100644 index 000000000..3b4ba307f --- /dev/null +++ b/test/test_nonclifford_quantumoptics.jl @@ -0,0 +1,88 @@ +using QuantumClifford +using QuantumClifford: GeneralizedStabilizer, rowdecompose, PauliChannel, mul_left!, mul_right! +using QuantumClifford: @S_str, random_stabilizer +using QuantumOpticsBase +using LinearAlgebra +using Test +using InteractiveUtils +using Random + +qo_basis = SpinBasis(1//2) +qo_tgate = sparse(identityoperator(qo_basis)) +qo_tgate.data[2,2] = exp(im*pi/4) + +## + +@testset "expect" begin + for s in [S"X", S"Y", S"Z", S"-X", S"-Y", S"-Z"] + for p in [P"X", P"Y", P"Z", P"-X", P"-Y", P"-Z"] + gs = GeneralizedStabilizer(s) + apply!(gs, pcT) + ρ = dm(qo_tgate*Ket(s)) + @test Operator(gs) ≈ ρ + @test isapprox(expect(p, gs), expect(Operator(p),ρ); atol=1e-5) + end + end + + for _ in 1:10 + for n in 1:1 + i = rand(1:n) + stab = random_stabilizer(n) + genstab = GeneralizedStabilizer(stab) + ket = Ket(stab) + @test dm(ket) ≈ Operator(stab) + @test dm(ket) ≈ Operator(genstab) + + pauli = random_pauli(n; nophase=false, realphase=true) + qo_pauli = Operator(pauli) + + qo_bigtgate = n==1 ? qo_tgate : embed(qo_basis^n, i, qo_tgate) + bigtgate = embed(n,i, pcT) + @test qo_bigtgate ≈ Operator(bigtgate) + + for step in 1:10 + # apply!(ket, qo_bigtgate) TODO implement this API + ket = qo_bigtgate*ket + apply!(genstab, bigtgate) + @test dm(ket) ≈ Operator(genstab) + @test isapprox(expect(qo_pauli, ket), expect(pauli, genstab); atol=1e-5) + end + end + end +end + +@testset "apply!" begin + for n in [1,2,3,4] # exponential cost in this term + s = random_stabilizer(n) + sm = GeneralizedStabilizer(s) + @test dm(Ket(s)) ≈ Operator(sm) + # test clifford gates + for _ in 1:10 + c = random_clifford(n) + uc = Operator(c) + @test uc * Operator(sm) * uc' ≈ Operator(apply!(sm, c)) # sm is modified in place for the next round + end + # and now some (repeated) non-clifford ops + for _ in 1:5 # exponential cost in this term + i = rand(1:n) + nc = embed(n, i, pcT) + apply!(sm, nc) # in-place + c = random_clifford(n) + uc = Operator(c) + @test uc * Operator(sm) * uc' ≈ Operator(apply!(sm, c)) # sm is modified in place for the next round + end + end +end + +@testset "copy and ==" begin + for n in 1:10 + s = random_stabilizer(n) + sm = GeneralizedStabilizer(s) + i = rand(1:n) + apply!(sm, embed(n, i, pcT)) + smcopy = copy(sm) + @test smcopy == sm + nc = embed(n, rand(1:n), pcT) + @test copy(nc) == nc + end +end diff --git a/test/test_quantumoptics.jl b/test/test_quantumoptics.jl index 9637daba4..989b5c07d 100644 --- a/test/test_quantumoptics.jl +++ b/test/test_quantumoptics.jl @@ -48,10 +48,10 @@ end end end -@testset "conversion from StabMixture to Operator" begin +@testset "conversion from GeneralizedStabilizer to Operator" begin for n in 1:5 stab = random_stabilizer(n) - @test dm(Ket(stab)) == Operator(StabMixture(stab)) + @test dm(Ket(stab)) == Operator(GeneralizedStabilizer(stab)) end end @@ -72,7 +72,7 @@ end tgate = sparse(identityoperator(SpinBasis(1//2))) tgate.data[2,2] = exp(im*pi/4) -@testset "StabMixture/PauliChannel to QuantumOptics - explicit single-qubit Pauli channels" begin +@testset "GeneralizedStabilizer/PauliChannel to QuantumOptics - explicit single-qubit Pauli channels" begin # manual checks @test Operator(pcT)≈tgate @@ -80,7 +80,7 @@ tgate.data[2,2] = exp(im*pi/4) for single_qubit_explicit_channel in [pcT] qo_gate = Operator(single_qubit_explicit_channel) for single_qubit_tableau in [S"X", S"Y", S"Z", S"-X", S"-Y", S"-Z"] - sm = StabMixture(single_qubit_tableau) + sm = GeneralizedStabilizer(single_qubit_tableau) ψ = Ket(single_qubit_tableau) for rep in 1:8 apply!(sm, single_qubit_explicit_channel) @@ -98,7 +98,7 @@ tgate.data[2,2] = exp(im*pi/4) qo_gate1 = Operator(single_qubit_explicit_channel) qo_gate = embed(basis(qo_gate1)^n, i, qo_gate1) stab = random_stabilizer(n) - sm = StabMixture(stab) + sm = GeneralizedStabilizer(stab) ψ = Ket(stab) for rep in 1:8 apply!(sm, channel) diff --git a/test/test_stabs.jl b/test/test_stabs.jl index 59a81d441..10249feef 100644 --- a/test/test_stabs.jl +++ b/test/test_stabs.jl @@ -43,6 +43,7 @@ test_sizes = [1,2,10,63,64,65,127,128,129] # Including sizes that would test off end end end + @testset "Tensor products over stabilizers" begin for n in test_sizes n<10 && continue @@ -59,6 +60,7 @@ end @test canonicalize!(⊗(stabs...)) == canonicalize!(stabilizerview(⊗(mdstabs...))) end end + @testset "Stabilizer indexing" begin s = random_stabilizer(9,10) @test s[1,1] == s[[1,3,4],[1,3,5]][1,1] @@ -91,3 +93,11 @@ end @test stab_to_gf2(s2a) == stab_to_gf2(s2b) end end + +@testset "Consistency between Destabilizer and MixedDestabilizer" begin # They have different construction algorithms so a consistency check is in order + for n in test_sizes + s = random_stabilizer(n) + @test stabilizerview(Destabilizer(s))==s # Destabilizer is supposed to guarantee same stabilizer generators + @test canonicalize!(stabilizerview(MixedDestabilizer(s)))==canonicalize!(stabilizerview(Destabilizer(s))) + end +end diff --git a/test/test_throws.jl b/test/test_throws.jl index 724cc7de5..bc5fc8901 100644 --- a/test/test_throws.jl +++ b/test/test_throws.jl @@ -19,7 +19,8 @@ using InteractiveUtils: subtypes @test_throws DimensionMismatch mul_left!(P"X", P"XX") @test_throws DimensionMismatch mul_right!(P"X", P"XX") -@test_throws ArgumentError StabMixture(S"XX") +@test_throws ArgumentError GeneralizedStabilizer(S"XX") +@test_throws DimensionMismatch apply!(GeneralizedStabilizer(random_stabilizer(2)), pcT) @test_throws ArgumentError UnitaryPauliChannel([P"X"], [1,2]) @test_throws ArgumentError UnitaryPauliChannel([P"X",P"XX"], [1,2])