Skip to content

Commit

Permalink
adding code review suggestions and also fix QuantumSavory#150: use Do…
Browse files Browse the repository at this point in the history
…cStringExtensions much more agressively
  • Loading branch information
Fe-r-oz committed Aug 6, 2024
1 parent adacc67 commit bf6b6a1
Show file tree
Hide file tree
Showing 29 changed files with 1,037 additions and 572 deletions.
3 changes: 2 additions & 1 deletion docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ pages = [
"Datastructure Choice" => "datastructures.md",
"Useful States" => "commonstates.md",
],
"Noisy Circuits" => [
"Circuit Simulation" => [
"Manual" => "circuit-simulation-manual.md",
"Simulation of Noisy Circuits" => "noisycircuits.md",
"Monte Carlo" => "noisycircuits_mc.md",
"Perturbative Expansions" => "noisycircuits_perturb.md",
Expand Down
205 changes: 205 additions & 0 deletions docs/src/circuit-simulation-manual.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
# [Circuit Simulation Manual](@id circuit-simulation-manual)

```@meta
DocTestSetup = quote
using QuantumClifford
using StableRNGs
end
```

The library consists of two main parts: Tools for working with the algebra of Stabilizer tableaux and tools specifically for efficient Circuit Simulation. This chapter discusses the latter "higher level" circuit simulation tools.

# Classical Register

A [`Register`](@ref) encapsulates the state of a computer and includes both a tableaux and an array of classical bits, which can be used, for example, to store measurement outcomes. A [`MixedDestabilizer`](@ref) can be stored within the [`Register`](@ref), along with a set of classical bits for recording measurement results. The array of classical bits can be accessed through [`bitview`](@ref), while the tableaux can be examined using [`quantumstate`](@ref).

```jldoctest register
julia> s = MixedDestabilizer(T"YZ -XX XI IZ", 2)
𝒟ℯ𝓈𝓉𝒶𝒷
+ YZ
- XX
𝒮𝓉𝒶𝒷
+ X_
+ _Z
julia> reg = Register(s, [0,0])
Register{QuantumClifford.Tableau{Vector{UInt8}, Matrix{UInt64}}}(MixedDestablizer 2×2, Bool[0, 0])
julia> quantumstate(reg)
𝒟ℯ𝓈𝓉𝒶𝒷
+ YZ
- XX
𝒮𝓉𝒶𝒷
+ X_
+ _Z
julia> stabilizerview(reg)
+ X_
+ _Z
julia> destabilizerview(reg)
+ YZ
- XX
julia> bitview(reg)
2-element Vector{Bool}:
0
0
```

Measurement results can be obtained using symbolic measurement operations such as [`sMX`](@ref), [`sMY`](@ref), and [`sMZ`](@ref), which can be applied with [`apply!`](@ref).

```jldoctest
julia> rng = StableRNG(42); # hide
julia> s = MixedDestabilizer(T"XY -ZZ -XX -YZ", 2)
𝒟ℯ𝓈𝓉𝒶𝒷
+ XY
- ZZ
𝒮𝓉𝒶𝒷
- XX
- YZ
julia> reg = Register(s, [0, 0]);
julia> apply!(rng, reg, sMX(1, 1));
julia> quantumstate(reg)
𝒟ℯ𝓈𝓉𝒶𝒷
+ XY
- YZ
𝒮𝓉𝒶𝒷
- XX
- X_
julia> apply!(rng, reg, sMRX(2, 2));
julia> quantumstate(reg)
𝒟ℯ𝓈𝓉𝒶𝒷
+ XY
- YZ
𝒮𝓉𝒶𝒷
- XX
- X_
```

Projective measurements with automatic phase randomization, including [`projectY!`](@ref), [`projectZ!`](@ref) and [`projectrand!`](@ref) are available for the [`Register`](@ref) object.

```jldoctest
julia> rng = StableRNG(42); # hide
julia> s = MixedDestabilizer(T"YZ -XX XI IZ", 2)
𝒟ℯ𝓈𝓉𝒶𝒷
+ YZ
- XX
𝒮𝓉𝒶𝒷
+ X_
+ _Z
julia> reg = Register(s, [0, 0]);
julia> projectXrand!(rng, reg, 2);
julia> quantumstate(reg)
𝒟ℯ𝓈𝓉𝒶𝒷
+ Y_
+ _Z
𝒮𝓉𝒶𝒷
+ X_
- _X
julia> projectYrand!(rng,reg, 1);
julia> quantumstate(reg)
𝒟ℯ𝓈𝓉𝒶𝒷
+ X_
+ _Z
𝒮𝓉𝒶𝒷
+ Y_
- _X
julia> projectZrand!(rng, reg, 2);
julia> quantumstate(reg)
𝒟ℯ𝓈𝓉𝒶𝒷
+ X_
- _X
𝒮𝓉𝒶𝒷
+ Y_
+ _Z
```

# Pauli Frame

[`PauliFrame`](@ref) is a wrapper for a "frame" tableau. Each row represents the Pauli operation differing
the frame from the reference, behaving uniquely under Clifford operations.

```jldoctest frame
julia> circuit = [sHadamard(2),
sHadamard(5),
sCNOT(1, 2),
sCNOT(2, 5),
sMZ(1),
sMZ(2),
sMZ(4),
sMZ(5)];
julia> frame = 5; qubits = 5; measurements=4;
julia> pframe = PauliFrame(frame, qubits, measurements);
julia> pframe = pftrajectories(pframe, circuit);
julia> pfmeasurements(pframe)
5×4 Matrix{Bool}:
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
julia> sum(pframe.measurements)
0
```

Perturbative expansion with compactifying the same circuit used above with ['compactify_circuit'](@ref) and
`QuantumClifford._create_pauliframe` to create the [`PauliFrame`](@ref).

```jldoctest frame
julia> pfcircuit = compactify_circuit(circuit);
julia> pframe = QuantumClifford._create_pauliframe(pfcircuit; trajectories=5);
julia> result = pftrajectories(pframe, pfcircuit);
julia> pfmeasurements(result)
5×1 Matrix{Bool}:
0
0
0
0
0
julia> sum(result.measurements)
0
```

Inject random Z errors on all frames and qubits with 0.5 probability.

```jldoctest
julia> rng = StableRNG(42); # hide
julia> pframe = PauliFrame(4, 4, 2);
julia> QuantumClifford.initZ!(rng, pframe);
julia> pframe.frame
+ Z___
+ ___Z
+ __ZZ
+ ____
```

[`PauliFrame`](@ref) supports circuits with [`PauliError`](@ref), [`UnbiasedUncorrelatedNoise`](@ref), [`sMRZ`](@ref), [`sMRX`](@ref), [`sMX`](@ref), and [`ClassicalXOR`](@ref) for perturbative expansions. See [`applynoise!`](@ref), and [`pftrajectories`](@ref) as well.

For more examples, see the [notebook comparing the Monte Carlo and Perturbative method](https://nbviewer.jupyter.org/github/QuantumSavory/QuantumClifford.jl/blob/master/docs/src/notebooks/Perturbative_Expansions_vs_Monte_Carlo_Simulations.ipynb) or this tutorial on [entanglement purification for many examples](https://github.com/QuantumSavory/QuantumClifford.jl/blob/master/docs/src/notebooks/Noisy_Circuits_Tutorial_with_Purification_Circuits.ipynb).
23 changes: 23 additions & 0 deletions docs/src/ecc_example_sim.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ DocTestSetup = quote
using QuantumClifford
using Quantikz
end
CurrentModule = QuantumClifford.Experimental.NoisyCircuits
```

!!! warning "The documentation is incomplete"
Expand Down Expand Up @@ -41,11 +42,33 @@ nframes = 4
frames = pftrajectories(circuit; trajectories=nframes) # run the sims
pfmeasurements(frames) # extract the measurements
```
... and the `sum(pftraj.measurements)` should be 0.

```@example 1
sum(frames.measurements)
```
The [`pftrajectories`](@ref) function is multithreaded.
If you want more low-level control over these Pauli frame simulations, check out the [`PauliFrame`](@ref) structure,
the other methods of [`pftrajectories`](@ref), and the circuit compactifaction function [`compactify_circuit`](@ref).


```@example 1
pfcircuit = eltype(circuit) <: QuantumClifford.CompactifiedGate ? circuit : compactify_circuit(circuit)
frames = QuantumClifford._create_pauliframe(pfcircuit; trajectories=nframes)
result = pftrajectories(pfcircuit)
pfmeasurements(frames)
```
...and to view `frame`, use:

```@example 1
result.frame
```
... and the `sum(pftraj.measurements)` should be 0.

```@example 1
sum(result.measurements)
```

If you want to model Pauli errors, use:

- The helper [`PauliError`](@ref) for unbiased Pauli noise operation acting on a given qubit
Expand Down
1 change: 1 addition & 0 deletions docs/src/noisycircuits.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ DocTestSetup = quote
using QuantumClifford
using QuantumClifford.Experimental.NoisyCircuits
end
CurrentModule = QuantumClifford.Experimental.NoisyCircuits
```

!!! warning "Unstable"
Expand Down
26 changes: 26 additions & 0 deletions docs/src/noisycircuits_mc.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ DocTestSetup = quote
using QuantumClifford.Experimental.NoisyCircuits
using Quantikz
end
CurrentModule = QuantumClifford.Experimental.NoisyCircuits
```

!!! warning "Unstable"
Expand Down Expand Up @@ -44,6 +45,31 @@ And we can run a Monte Carlo simulation of that circuit with [`mctrajectories`](
```@example 1
mctrajectories(initial_state, circuit, trajectories=500)
```
... and without purification

```@example 1
mctrajectories(initial_state, [n, v], trajectories=500)
```

... and without noise

```@example 1
mctrajectories(initial_state, [g1,g2,m,v], trajectories=500)
```

... and use of [`Register`](@ref) and [`SparseGate`](@gate) for Monte Carlo simulation of that circuit with [`mctrajectories`](@ref).

```@example 2
g1 = SparseGate(tCNOT, [1,3])
g2 = SparseGate(tCNOT, [2,4])
m = BellMeasurement([sMX(3),sMX(4)])
good_bell_state = ghz(2)
canonicalize_rref!(good_bell_state)
v = VerifyOp(good_bell_state,[1,2])
n = NoiseOpAll(UnbiasedUncorrelatedNoise(0.03))
init = Register(MixedDestabilizer(good_bell_state⊗good_bell_state))
with_purification = mctrajectories(init, [n,g1,g2,m,v], trajectories=500)
```

For more examples, see the [notebook comparing the Monte Carlo and Perturbative method](https://nbviewer.jupyter.org/github/QuantumSavory/QuantumClifford.jl/blob/master/docs/src/notebooks/Perturbative_Expansions_vs_Monte_Carlo_Simulations.ipynb) or this tutorial on [entanglement purification for many examples](https://github.com/QuantumSavory/QuantumClifford.jl/blob/master/docs/src/notebooks/Noisy_Circuits_Tutorial_with_Purification_Circuits.ipynb).

Expand Down
1 change: 1 addition & 0 deletions docs/src/noisycircuits_ops.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ DocTestSetup = quote
using QuantumClifford.Experimental.NoisyCircuits
using Quantikz
end
CurrentModule = QuantumClifford.Experimental.NoisyCircuits
```

!!! warning "Unstable"
Expand Down
41 changes: 39 additions & 2 deletions docs/src/noisycircuits_perturb.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ DocTestSetup = quote
using QuantumClifford.Experimental.NoisyCircuits
using Quantikz
end
CurrentModule = QuantumClifford.Experimental.NoisyCircuits
```

!!! warning "Unstable"
Expand All @@ -18,7 +19,7 @@ Instead of simulating many Monte Carlo trajectories, only the leading order traj

Here is an example of a purification circuit (the same circuit seen in the [Monte Carlo example](@ref noisycircuits_mc))

```@example
```@example 1
using QuantumClifford # hide
using QuantumClifford.Experimental.NoisyCircuits # hide
using Quantikz # hide
Expand All @@ -40,7 +41,27 @@ n = NoiseOpAll(UnbiasedUncorrelatedNoise(epsilon))
# followed by checking whether the final result indeed corresponds to the correct Bell pair.
circuit = [n,g1,g2,m,v]
petrajectories(initial_state, circuit)
petraj = petrajectories(initial_state, circuit)
```
... and the final statuses for circuit simulations are `:undetected_failure`, `:detected_failure`, and `:true_success`. The `SparseGate` component always signals to continue with `:continue`. The `BellMeasurement` may result in either `:detected_failure` or `:continue`, based on detection of a coincidence or anti-coincidence. The `VerifyOp` will indicate `:true_success` if the desired state is achieved, or `:undetected_failure` if it is not, despite `:continue` being suggested at measurement step. Custom statuses can be created as needed.

```@example 1
petraj[true_success_stat]
```
... and the sum of probabilities in the corresponding perturbative expansion

```@example 1
sum(values(petraj))
```
...and without purification

```@example 1
petraj = petrajectories(initial_state, [n,v])
```
...and with no noise

```@example 1
petraj = petrajectories(initial_state, [g1,g2,m,v])
```

For more examples, see the [notebook comparing the Monte Carlo and Perturbative method](https://nbviewer.jupyter.org/github/QuantumSavory/QuantumClifford.jl/blob/master/docs/src/notebooks/Perturbative_Expansions_vs_Monte_Carlo_Simulations.ipynb) or this tutorial on [entanglement purification](https://github.com/QuantumSavory/QuantumClifford.jl/blob/master/docs/src/notebooks/Noisy_Circuits_Tutorial_with_Purification_Circuits.ipynb).
Expand All @@ -58,4 +79,20 @@ The `Symbol` is the status of the operation, the `Real` is the probability for t

There is also `applynoise_branches!` which is convenient for use in `NoisyGate`, but you can also just make up your own noise operator simply by implementing `applyop_branches!` for it.

Showcasing higher-order expansions using `applynoise_branches!` on purification circuit (the same circuit seen in the [Monte Carlo example](@ref noisycircuits_mc))

```@example 1
netnoise = UnbiasedUncorrelatedNoise(epsilon);
for order in [0,1,2,3,4]
branches = QuantumClifford.applynoise_branches(initial_state, netnoise, [1,2,3,4], max_order=order)
println((order = order, nb_of_branches = length(branches), total_probability = sum(b[2] for b in branches)))
end
```
... and using `applybranches!` on the same purification circuit

```@example 1
branches = QuantumClifford.applybranches(initial_state, n)
```

You can also consult the [list of implemented operators](@ref noisycircuits_ops).
Loading

0 comments on commit bf6b6a1

Please sign in to comment.