From da80a22a79a7eccea78baeb1f0a5ae1e8f80d358 Mon Sep 17 00:00:00 2001 From: Stefan Krastanov Date: Mon, 26 Dec 2022 23:25:05 -0500 Subject: [PATCH] move Noise out of Experimental Signed-off-by: Stefan Krastanov --- CHANGELOG.md | 10 +- docs/make.jl | 3 +- docs/src/API.md | 46 ++-------- docs/src/allops.md | 146 ++++++++++++++++++++++++++++++ docs/src/commonstates.md | 9 +- docs/src/noise.md | 23 +++++ src/QuantumClifford.jl | 5 + src/affectedqubits.jl | 11 +++ src/classical_register.jl | 1 + src/experimental/NoisyCircuits.jl | 94 ++----------------- src/linalg.jl | 2 +- src/noise.jl | 66 ++++++++++++++ 12 files changed, 283 insertions(+), 133 deletions(-) create mode 100644 docs/src/allops.md create mode 100644 docs/src/noise.md create mode 100644 src/affectedqubits.jl create mode 100644 src/noise.jl diff --git a/CHANGELOG.md b/CHANGELOG.md index 538a5f538..326b03dcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,15 @@ Planned for v1.0.0: ## v0.6.7 -- Test coverage increase. -- Fixes to inference failures (detected by JET). - **(fix)** Fixed bug in `CliffordOperator(AbstractTwoQubitOperator)`. +- Fixes to inference failures (detected by JET). +- Significant test coverage increase. +- Stabilizing a few features, moving out of `Experimental.NoisyCircuits` + - `applynoise!` + - `affectedqubits` + - `NoisyGate` + - `NoiseOp` and `NoiseOpAll` + - `UnbiasedUncorrelatedNoise` ## v0.6.6 - 2022-12-24 diff --git a/docs/make.jl b/docs/make.jl index 7060a7306..0c587afb5 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -23,7 +23,8 @@ pages = [ "Mixed States" => "mixed.md", "Graph States" => "graphs.md", "Datastructure Choice" => "datastructures.md", -"Useful States and Operators" => "commonstates.md", +"Useful States" => "commonstates.md", +"All Gates" => "allops.md", "Plotting" => "plotting.md", "API" => "API.md", "Tutorials and Publications" => "tutandpub.md", diff --git a/docs/src/API.md b/docs/src/API.md index fbf48a60d..899bd0354 100644 --- a/docs/src/API.md +++ b/docs/src/API.md @@ -17,48 +17,16 @@ Moreover, a [`MixedDestabilizer`](@ref) can be stored inside a [`Register`](@ref There are [convenience constructors for common types of states and operators](@ref Useful-States-and-Operators). -## [Operations](@id all-operations) +## Operations -All of these can be applied on a state with the [`apply!`](@ref) function. Whether they are deterministic and their computational complexity is listed in the table below. A list of lower-level functions for more control over how an operation is performed is also given. +Acting on quantum states can be performed either: -```@raw html - -``` +- In a "linear algebra" language, where the operators are [Clifford operators](@ref CliffordOperator) represented as tableaux. This is an explicitly deterministic lower-level interface, which provides a great deal of control over how tableaux are manipulated. +- Or in a "circuit" language, where the operators (and measurements and noise) are represented as circuit gates. This is a higher-level interface in which the outcome of an operation can be stochastic. Particularly useful for [Monte Carlo simulations](@ref noisycircuits_mc) and [Perturbative Expansion Symbolic Results](@ref noisycircuits_perturb). + +See the [manual](@ref Manual) as a primer on these approaches. -| Type | Deterministic | 𝒪(nˣ) | Low-level functions -|:--|:-:|---|---| -|`AbstractOperation `| | | | -|` ├─ AbstractCliffordOperator `| | | | -|` │ ├─ AbstractSymbolicOperator `| | | | -|` │ │ ├─ AbstractSingleQubitOperator`| | | | -|` │ │ │ ├─ SingleQubitOperator `|✔️ | n | | -|` │ │ │ ├─ sHadamard `|✔️ | n | | -|` │ │ │ ├─ sId1 `|✔️ | n | | -|` │ │ │ ├─ sInvPhase `|✔️ | n | | -|` │ │ │ ├─ sPhase `|✔️ | n | | -|` │ │ │ ├─ sX `|✔️ | n | | -|` │ │ │ ├─ sY `|✔️ | n | | -|` │ │ │ └─ sZ `|✔️ | n | | -|` │ │ └─ AbstractTwoQubitOperator `| | | | -|` │ │ ├─ sCNOT `|✔️ | n | | -|` │ │ ├─ sCPHASE `|✔️ | n | | -|` │ │ └─ sSWAP `|✔️ | n | | -|` │ │ `| | | | -|` │ ├─ CliffordOperator `|✔️ | n³| | -|` │ ├─ PauliOperator `|✔️ | n²| | -|` │ └─ SparseGate `|✔️ |kn²| | -|` ├─ AbstractMeasurement `| | | | -|` │ ├─ PauliMeasurement `|❌ | n²| [`project!`](@ref), [`projectrand!`](@ref) | -|` │ ├─ sMX `|❌ | n²| [`projectX!`](@ref) | -|` │ ├─ sMY `|❌ | n²| [`projectY!`](@ref) | -|` │ └─ sMZ `|❌ | n²| [`projectZ!`](@ref) | -|` │ `| | | | -|` ├─ BellMeasurement `|❌ | n²| | -|` └─ Reset `|✔️ |kn²| [`reset_qubits!`](@ref)| +See the [full list of operations](@ref all-operations) for a list of implemented operations. ## Autogenerated API list diff --git a/docs/src/allops.md b/docs/src/allops.md new file mode 100644 index 000000000..d864ad429 --- /dev/null +++ b/docs/src/allops.md @@ -0,0 +1,146 @@ +# [Operations - Gates, Measurements, and More](@id all-operations) + +```@meta +DocTestSetup = quote + using QuantumClifford + using StableRNGs + rng = StableRNG(42) +end +``` + +## [Operations](@id all-operations) + +Acting on quantum states can be performed either: + +- In a "linear algebra" language, where the operators are [Clifford operators](@ref CliffordOperator) represented as tableaux. This is an explicitly deterministic lower-level interface, which provides a great deal of control over how tableaux are manipulated. +- Or in a "circuit" language, where the operators (and measurements and noise) are represented as circuit gates. This is a higher-level interface in which the outcome of an operation can be stochastic. Particularly useful for [Monte Carlo simulations](@ref noisycircuits_mc) and [Perturbative Expansion Symbolic Results](@ref noisycircuits_perturb) + +In the circuit language, all operations can be applied on a state with the [`apply!`](@ref) function. Whether they are deterministic and their computational complexity is listed in the table below. A list of lower-level "linear algebra style" functions for more control over how an operation is performed is also given. + +```@raw html + +``` + +| Type | Deterministic | 𝒪(nˣ) | Low-level functions +|:--|:-:|---|---| +|`AbstractOperation `| | | | +|` ├─ AbstractCliffordOperator `| | | | +|` │ ├─ AbstractSymbolicOperator `| | | | +|` │ │ ├─ AbstractSingleQubitOperator`| | | | +|` │ │ │ ├─ SingleQubitOperator `|✔️ | n | | +|` │ │ │ ├─ sHadamard `|✔️ | n | | +|` │ │ │ ├─ sId1 `|✔️ | n | | +|` │ │ │ ├─ sInvPhase `|✔️ | n | | +|` │ │ │ ├─ sPhase `|✔️ | n | | +|` │ │ │ ├─ sX `|✔️ | n | | +|` │ │ │ ├─ sY `|✔️ | n | | +|` │ │ │ └─ sZ `|✔️ | n | | +|` │ │ └─ AbstractTwoQubitOperator `| | | | +|` │ │ ├─ sCNOT `|✔️ | n | | +|` │ │ ├─ sCPHASE `|✔️ | n | | +|` │ │ └─ sSWAP `|✔️ | n | | +|` │ │ `| | | | +|` │ ├─ CliffordOperator `|✔️ | n³| | +|` │ ├─ PauliOperator `|✔️ | n²| | +|` │ └─ SparseGate `|✔️ |kn²| | +|` ├─ AbstractMeasurement `| | | | +|` │ ├─ PauliMeasurement `|❌ | n²| [`project!`](@ref), [`projectrand!`](@ref) | +|` │ ├─ sMX `|❌ | n²| [`projectX!`](@ref) | +|` │ ├─ sMY `|❌ | n²| [`projectY!`](@ref) | +|` │ └─ sMZ `|❌ | n²| [`projectZ!`](@ref) | +|` │ `| | | | +|` ├─ BellMeasurement `|❌ | n²| | +|` ├─ NoiseOp `|❌ | ?| [`applynoise!`](@ref) | +|` ├─ NoiseOpAll `|❌ | ?| [`applynoise!`](@ref) | +|` ├─ NoisyGate `|❌ | ?| [`applynoise!`](@ref) | +|` └─ Reset `|✔️ |kn²| [`reset_qubits!`](@ref)| + +## Details of Operations Supported by [`apply!`](@ref) + +### Unitary Gates + +We distinguish between symbolic gates like [`sCNOT`](@ref) that have specialized (fast) `apply!` methods (usually just for single and two qubit gates) and general tableau representation of gates like [`CliffordOperator`](@ref) that can represent any multi-qubit gate. + +Predefined unitary gates are available, like [`sCNOT`](@ref), [`sHadamard`](@ref), etc. + +```@example 1 +using QuantumClifford # hide +using QuantumClifford.Experimental.NoisyCircuits # hide +using QuantumCliffordPlots # hide +[sCNOT(2,4),sHadamard(2),sCPHASE(1,3),sSWAP(2,4)] +``` + +Any arbitrary tableaux can be used as a gate too. + +They can be specified by giving a Clifford operator tableaux and the indices on which it acts +(particularly useful for gates acting on a small part of a circuit): + +```@example 1 +using QuantumClifford # hide +using QuantumClifford.Experimental.NoisyCircuits # hide +using QuantumCliffordPlots # hide +SparseGate(tCNOT, [2,4]) +``` + +The Clifford operator tableaux can be completely arbitrary. +```@example 1 +SparseGate(random_clifford(3), [2,4,5]) +``` + +If the Clifford operator acts on all qubits, we do not need to specify indices, just use the operator. + +### Noisy Gates + +Each gate can be followed by noise applied to the qubits on which it has acted. +This is done by wrapping the given gate into a [`NoisyGate`](@ref) + +```@example 1 +ε = 0.03 # X/Y/Z error probability +noise = UnbiasedUncorrelatedNoise(ε) +noisy_gate = NoisyGate(SparseGate(tCNOT, [2,4]), noise) +``` + +In circuit diagrams the noise is not depicted, but after each application of the gate defined in `noisy_gate`, a noise operator will also be applied. The example above is of Pauli Depolarization implemented by [`UnbiasedUncorrelatedNoise`](@ref). + +One can also apply only the noise operator by using [`NoiseOp`](@ref) which acts only on specified qubits. Or alternatively, one can use [`NoiseOpAll`](@ref) in order to apply noise to all qubits. + +```@example 1 +[NoiseOp(noise, [4,5]), NoiseOpAll(noise)] +``` + +The machinery behind noise processes and different types of noise is detailed in [the section on noise](@ref noise) + +### Coincidence Measurements + +Global parity measurements involving single-qubit projections and classical communication are implemented with [`BellMeasurement`](@ref). One needs to specify the axes of measurement and the qubits being measured. If the parity is trivial, the circuit continues, if the parity is non-trivial, the circuit ends and reports a detected failure. +This operator is frequently used in the simulation of entanglement purification. + +```@example 1 +BellMeasurement([sMX(1), sMY(3), sMZ(4)]) +``` + +There is also [`NoisyBellMeasurement`](@ref) that takes the bit-flip probability of a single-qubit measurement as a third argument. + +### Stabilizer Measurements + +A measurement over one or more qubits can also be performed, e.g., a direct stabilizer measurement on multiple qubits without the use of ancillary qubits. When applied to multiple qubits, this differs from `BellMeasurement` as it performs a single projection, unlike `BellMeasurement` which performs a separate projection for every single qubit involved. This measurement is implemented in [`PauliMeasurement`](@ref) which requires a Pauli operator on which to project and the index of the classical bit in which to store the result. Alternatively, there are [`sMX`](@ref), [`sMZ`](@ref), [`sMY`](@ref) if you are measuring a single qubit. + +```@example 1 +[PauliMeasurement(P"XYZ", 1), sMZ(2, 2)] +``` + +### Reset Operations + +The [`Reset`](@ref) operations lets you trace out the specified qubits and set their state to a specific tableau. + +```@example 1 +new_state = random_stabilizer(3) +qubit_indices = [1,2,3] +Reset(new_state, qubit_indices) +``` + +It can be done anywhere in a circuit, not just at the beginning. \ No newline at end of file diff --git a/docs/src/commonstates.md b/docs/src/commonstates.md index a4b44ba97..5b821a9a6 100644 --- a/docs/src/commonstates.md +++ b/docs/src/commonstates.md @@ -7,9 +7,14 @@ DocTestSetup = quote rng = StableRNG(42) end ``` +## States -There are numerous frequently used stabilizer states already implemented in this -library. +Stabilizer states can be represented with the [`Stabilizer`](@ref), [`Destabilizer`](@ref), [`MixedStabilizer`](@ref), and [`MixedDestabilizer`](@ref) tableau data structures. You probably want to use [`MixedDestabilizer`](@ref) which supports the widest set of operations. + +Moreover, a [`MixedDestabilizer`](@ref) can be stored inside a [`Register`](@ref) together with a set of classical bits in which measurement results can be written. + +Below are convenience constructors for common types of states and operators, +already implemented in this library. ## Pauli Operators diff --git a/docs/src/noise.md b/docs/src/noise.md new file mode 100644 index 000000000..6afd4de28 --- /dev/null +++ b/docs/src/noise.md @@ -0,0 +1,23 @@ +# [Noise Processes](@id noise) + +```@meta +DocTestSetup = quote + using QuantumClifford + using StableRNGs + rng = StableRNG(42) +end +``` + +As seen in the list of [possible gates](@id all-operations), +the simulator is capable of modeling different types of noise. +If that is your goal, please consider using the available +[Monte Carlo simulator](@ref noisycircuits_md) or the +[Symbolic Perturbative Expansion system](@ref noisycircuit_perturb). + +The implemented types of noise include: + +- [`UnbiasedUncorrelatedNoise`](@ref) + +The low-level functionality to work with noise is `applynoise!`, +but most of the time you would probably just want to use +[`NoisyGate`](@ref) and [`NoisyOpAll`](@ref). \ No newline at end of file diff --git a/src/QuantumClifford.jl b/src/QuantumClifford.jl index 94f64dfbd..87e6cacc4 100644 --- a/src/QuantumClifford.jl +++ b/src/QuantumClifford.jl @@ -27,6 +27,7 @@ export stabilizerview, destabilizerview, logicalxview, logicalzview, phases, bitview, quantumstate, tab, BadDataStructure, + affectedqubits, # GF2 stab_to_gf2, gf2_gausselim!, gf2_isinvertible, gf2_invert, gf2_H_to_G, # Canonicalization @@ -57,6 +58,8 @@ export enumerate_cliffords, symplecticGS, clifford_cardinality, enumerate_phases, random_invertible_gf2, random_pauli, random_stabilizer, random_destabilizer, random_clifford, + # Noise + applynoise!, UnbiasedUncorrelatedNoise, NoiseOp, NoiseOpAll, NoisyGate, # Useful States bell, ghz, single_z, single_x, single_y, @@ -1387,6 +1390,8 @@ include("classical_register.jl") include("enumeration.jl") include("randoms.jl") include("useful_states.jl") +include("noise.jl") +include("affectedqubits.jl") include("experimental/Experimental.jl") include("graphs.jl") include("entanglement.jl") diff --git a/src/affectedqubits.jl b/src/affectedqubits.jl new file mode 100644 index 000000000..f8e6c7694 --- /dev/null +++ b/src/affectedqubits.jl @@ -0,0 +1,11 @@ +"""A method giving the qubits acted upon by a given operation. Part of the Noise interface.""" +function affectedqubits end +affectedqubits(g::AbstractSingleQubitOperator) = [g.q,] +affectedqubits(g::AbstractTwoQubitOperator) = [g.q1, g.q2] +affectedqubits(g::NoisyGate) = affectedqubits(g.gate) +affectedqubits(g::SparseGate) = g.indices +affectedqubits(b::BellMeasurement) = [m.qubit for m in b.measurements] +affectedqubits(r::Reset) = r.indices +affectedqubits(n::NoiseOp) = n.indices +affectedqubits(g::PauliMeasurement) = 1:length(g.pauli) +affectedqubits(m::AbstractMeasurement) = [m.qubit] diff --git a/src/classical_register.jl b/src/classical_register.jl index d2c544d45..1caeaa98f 100644 --- a/src/classical_register.jl +++ b/src/classical_register.jl @@ -9,6 +9,7 @@ Register(s,bits) = Register(MixedDestabilizer(s), bits) Register(s) = Register(s, Bool[]) Base.copy(r::Register) = Register(copy(r.stab),copy(r.bits)) +Base.:(==)(l::Register,r::Register) = l.stab==r.stab && l.bits==r.bits stabilizerview(r::Register) = stabilizerview(quantumstate(r)) destabilizerview(r::Register) = destabilizerview(quantumstate(r)) diff --git a/src/experimental/NoisyCircuits.jl b/src/experimental/NoisyCircuits.jl index a533d7b0b..7a388aa95 100644 --- a/src/experimental/NoisyCircuits.jl +++ b/src/experimental/NoisyCircuits.jl @@ -1,13 +1,10 @@ -""" -A module for simulating noisy Clifford circuits. -""" module NoisyCircuits #TODO permit the use of alternative RNGs using QuantumClifford using QuantumClifford: AbstractQCState, AbstractStabilizer, AbstractOperation, AbstractMeasurement, AbstractCliffordOperator, apply_single_x!, apply_single_y!, apply_single_z!, registered_statuses -import QuantumClifford: applywstatus! +import QuantumClifford: applywstatus!, affectedqubits using Combinatorics: combinations using Base.Cartesian @@ -22,34 +19,10 @@ export AbstractOperation, petrajectory, petrajectories, ConditionalGate, DecisionGate -abstract type AbstractNoise end - #TODO all these structs should use specified types #TODO all of the methods should better specified type signatures #TODO measure allocation in various apply* methods and verify it is not superfluous -"""Depolarization noise model with total probability of error `3*errprobthird`.""" -struct UnbiasedUncorrelatedNoise{T} <: AbstractNoise - errprobthird::T -end - -"""An operator that applies the given `noise` model to the qubits at the selected `indices`.""" -struct NoiseOp <: AbstractOperation - noise::AbstractNoise - indices::AbstractVector{Int} -end - -"""An operator that applies the given `noise` model to all qubits.""" -struct NoiseOpAll <: AbstractOperation - noise::AbstractNoise -end - -"""A gate consisting of the given `noise` applied after the given perfect Clifford `gate`.""" -struct NoisyGate <: AbstractOperation - gate::AbstractOperation - noise::AbstractNoise -end - """A Bell measurement in which each of the measured qubits has a chance to have flipped.""" struct NoisyBellMeasurement{T} <: AbstractOperation meas::AbstractOperation @@ -77,31 +50,6 @@ struct DecisionGate <: AbstractOperation decisionfunction end -"""A method giving the qubits acted upon by a given operation. Part of the Noise interface.""" -function affectedqubits end -affectedqubits(g::AbstractSingleQubitOperator) = [g.q,] -affectedqubits(g::AbstractTwoQubitOperator) = [g.q1, g.q2] -affectedqubits(g::NoisyGate) = affectedqubits(g.gate) -affectedqubits(g::SparseGate) = g.indices -affectedqubits(b::BellMeasurement) = [m.qubit for m in b.measurements] -affectedqubits(r::Reset) = r.indices -affectedqubits(m::NoisyBellMeasurement) = affectedqubits(m.meas) -affectedqubits(n::NoiseOp) = n.indices -affectedqubits(v::VerifyOp) = v.indices -affectedqubits(g::PauliMeasurement) = 1:length(g.pauli) -affectedqubits(d::ConditionalGate) = union(affectedqubits(d.truegate), affectedqubits(d.falsegate)) -affectedqubits(d::DecisionGate) = [(union(affectedqubits.(d.gates))...)...] -affectedqubits(m::AbstractMeasurement) = [m.qubit] - - -function QuantumClifford.apply!(s::AbstractStabilizer, g::NoisyGate) - s = applynoise!( - apply!(s,g.gate), - g.noise, - affectedqubits(g.gate)), - return s -end - function applywstatus!(s::AbstractQCState, m::NoisyBellMeasurement) state, status = applywstatus!(s,m.meas) nqubits = length(affectedqubits(m)) @@ -113,36 +61,6 @@ function applywstatus!(s::AbstractQCState, m::NoisyBellMeasurement) end end -"""A method modifying a given state by applying the corresponding noise model. Non-deterministic, part of the Noise interface.""" -function applynoise! end - -function applynoise!(s::AbstractStabilizer,noise::UnbiasedUncorrelatedNoise,indices::AbstractVector{Int}) - n = nqubits(s) - infid = noise.errprobthird - for i in indices - r = rand() - if rstatus_probs[i] for i in eachindex(status_probs)]) end -function applynoise!(state::Register, noise, indices) - s, status = applynoise!(state.stab, noise, indices) - state, status -end - function applynoise_branches(state::Register, noise, indices; max_order=1) [(Register(newstate,copy(state.bits)), prob, order) for (newstate, prob, order) in applynoise_branches(state, noise, indices; max_order=max_order)] @@ -367,4 +280,9 @@ function applybranches(state::Register, op::SparseMeasurement; max_order=1) end =# +affectedqubits(m::NoisyBellMeasurement) = affectedqubits(m.meas) +affectedqubits(d::ConditionalGate) = union(affectedqubits(d.truegate), affectedqubits(d.falsegate)) +affectedqubits(d::DecisionGate) = [(union(affectedqubits.(d.gates))...)...] +affectedqubits(v::VerifyOp) = v.indices + end diff --git a/src/linalg.jl b/src/linalg.jl index 96eaa34cd..2d4b86d06 100644 --- a/src/linalg.jl +++ b/src/linalg.jl @@ -89,7 +89,7 @@ function ⊗(ops::AbstractStabilizer...) # TODO optimize this by doing conversio end """Repeated tensor product of an operators or a tableau. See also [`⊗`](@ref) and [`tensor_pow`](@ref).""" -function tensor_pow(op,power) +function tensor_pow(op::Union{<:AbstractStabilizer,<:AbstractCliffordOperator},power) if power==1 return op else diff --git a/src/noise.jl b/src/noise.jl new file mode 100644 index 000000000..8d075767f --- /dev/null +++ b/src/noise.jl @@ -0,0 +1,66 @@ +abstract type AbstractNoise end + +"""A method modifying a given state by applying the corresponding noise model. Non-deterministic, part of the Noise interface.""" +function applynoise! end + +function applynoise!(state::Register, noise, indices) + s = applynoise!(state.stab, noise, indices) + state +end + +"""Depolarization noise model with total probability of error `3*errprobthird`.""" +struct UnbiasedUncorrelatedNoise{T} <: AbstractNoise + errprobthird::T +end + +function applynoise!(s::AbstractStabilizer,noise::UnbiasedUncorrelatedNoise,indices::AbstractVector{Int}) + n = nqubits(s) + infid = noise.errprobthird + for i in indices + r = rand() + if r