Noisy Simulation
using Yao
using YaoBlocks.Optimise: replace_block
using CairoMakie
To start, we create a simple circuit that we want to simulate, the one generating the 4-qubit GHZ state $|\psi\rangle = \frac{1}{\sqrt{2}}(|0000\rangle + |1111\rangle)$. The code is as follows:
n_qubits = 4
circ = chain(
put(n_qubits, 1 => H),
[control(n_qubits, i, i+1 => X) for i in 1:n_qubits-1]...,
)
nqubits: 4
chain
├─ put on (1)
│ └─ H
├─ control(1)
│ └─ (2,) X
├─ control(2)
│ └─ (3,) X
└─ control(3)
└─ (4,) X
Visualize the circuit
vizcircuit(circ)
The ideal simulation gives the following result:
reg = zero_state(n_qubits) |> circ
samples = measure(reg, nshots=1000);
Visualize the results
hist(map(x -> x.buf, samples))
Add errors to noise model
circ_noisy = Optimise.replace_block(circ) do x
if x isa PutBlock && length(x.locs) == 1
chain(x, put(nqubits(x), x.locs => quantum_channel(BitFlipError(0.1))))
elseif x isa ControlBlock && length(x.ctrl_locs) == 1 && length(x.locs) == 1
chain(x, put(nqubits(x), (x.ctrl_locs..., x.locs...) => kron(quantum_channel(BitFlipError(0.1)), quantum_channel(BitFlipError(0.1)))))
else
x
end
end
push!(circ_noisy, repeat(quantum_channel(BitFlipError(0.05)), n_qubits)) # add measurement noise
nqubits: 4
chain
├─ chain
│ ├─ put on (1)
│ │ └─ H
│ └─ put on (1)
│ └─ mixed_unitary_channel
│ ├─ [0.9] I2
│ └─ [0.1] X
├─ chain
│ ├─ control(1)
│ │ └─ (2,) X
│ └─ put on (1, 2)
│ └─ mixed_unitary_channel
│ ├─ [0.81] kron
│ │ ├─ 1=>I2
│ │ └─ 2=>I2
│ ├─ [0.09000000000000001] kron
│ │ ├─ 1=>I2
│ │ └─ 2=>X
│ ├─ [0.09000000000000001] kron
│ │ ├─ 1=>X
│ │ └─ 2=>I2
│ └─ [0.010000000000000002] kron
│ ├─ 1=>X
│ └─ 2=>X
├─ chain
│ ├─ control(2)
│ │ └─ (3,) X
│ └─ put on (2, 3)
│ └─ mixed_unitary_channel
│ ├─ [0.81] kron
│ │ ├─ 1=>I2
│ │ └─ 2=>I2
│ ├─ [0.09000000000000001] kron
│ │ ├─ 1=>I2
│ │ └─ 2=>X
│ ├─ [0.09000000000000001] kron
│ │ ├─ 1=>X
│ │ └─ 2=>I2
│ └─ [0.010000000000000002] kron
│ ├─ 1=>X
│ └─ 2=>X
├─ chain
│ ├─ control(3)
│ │ └─ (4,) X
│ └─ put on (3, 4)
│ └─ mixed_unitary_channel
│ ├─ [0.81] kron
│ │ ├─ 1=>I2
│ │ └─ 2=>I2
│ ├─ [0.09000000000000001] kron
│ │ ├─ 1=>I2
│ │ └─ 2=>X
│ ├─ [0.09000000000000001] kron
│ │ ├─ 1=>X
│ │ └─ 2=>I2
│ └─ [0.010000000000000002] kron
│ ├─ 1=>X
│ └─ 2=>X
└─ repeat on (4)
└─ mixed_unitary_channel
├─ [0.95] I2
└─ [0.05] X
simulate the noisy circuit
rho = apply(density_matrix(reg), circ_noisy)
samples = measure(rho, nshots=1000);
Visualize the results
hist(map(x -> x.buf, samples))
Error Types and Quantum Channels
Yao provides various types of quantum errors and their corresponding quantum channel representations. Let's explore the different error types available:
1. Bit Flip Error
A bit flip error with probability p applies X gate with probability p
bit_flip = BitFlipError(0.1)
bit_flip_channel = quantum_channel(bit_flip)
println("Bit Flip Error Channel: ", bit_flip_channel)
Bit Flip Error Channel: nqubits: 1
mixed_unitary_channel
├─ [0.9] I2
└─ [0.1] X
2. Phase Flip Error
A phase flip error with probability p applies Z gate with probability p
phase_flip = PhaseFlipError(0.1)
phase_flip_channel = quantum_channel(phase_flip)
println("Phase Flip Error Channel: ", phase_flip_channel)
Phase Flip Error Channel: nqubits: 1
mixed_unitary_channel
├─ [0.9] I2
└─ [0.1] Z
3. Depolarizing Error
A depolarizing error with probability p applies X, Y, or Z gate with equal probability p/3
depolarizing = DepolarizingError(1, 0.1)
depolarizing_channel = quantum_channel(depolarizing)
println("Depolarizing Error Channel: ", depolarizing_channel)
Depolarizing Error Channel: nqubits: 1
DepolarizingChannel{1}(0.1)
4. Pauli Error
A Pauli error with probabilities px, py, pz for X, Y, Z gates respectively
pauli_error = PauliError(0.05, 0.03, 0.02)
pauli_channel = quantum_channel(pauli_error)
println("Pauli Error Channel: ", pauli_channel)
Pauli Error Channel: nqubits: 1
mixed_unitary_channel
├─ [0.8999999999999999] I2
├─ [0.05] X
├─ [0.03] Y
└─ [0.02] Z
5. Reset Error
A reset error that resets qubits to |0⟩ or |1⟩ with given probabilities
reset_error = ResetError(0.1, 0.05) # p0, p1
reset_channel = quantum_channel(reset_error)
println("Reset Error Channel: ", reset_channel)
Reset Error Channel: nqubits: 1
kraus_channel
├─ [scale: 0.9219544457292888] I2
├─ [scale: 0.31622776601683794] P0
├─ [scale: 0.31622776601683794] Pd
├─ [scale: 0.22360679774997896] P1
└─ [scale: 0.22360679774997896] Pu
6. Thermal Relaxation Error
Models decoherence with T1 and T2 times
thermal_relaxation = ThermalRelaxationError(100.0, 200.0, 1.0, 0.0)
thermal_channel = quantum_channel(thermal_relaxation)
println("Thermal Relaxation Error Channel: ", thermal_relaxation)
Thermal Relaxation Error Channel: ThermalRelaxationError{Float64}(100.0, 200.0, 1.0, 0.0)
7. Amplitude Damping Error
Models energy loss to environment
amplitude_damping = AmplitudeDampingError(0.1)
amplitude_channel = quantum_channel(amplitude_damping)
println("Amplitude Damping Error Channel: ", amplitude_damping)
Amplitude Damping Error Channel: AmplitudeDampingError{Float64}(0.1, 0.0)
8. Phase Damping Error
Models pure dephasing
phase_damping = PhaseDampingError(0.1)
phase_channel = quantum_channel(phase_damping)
println("Phase Damping Error Channel: ", phase_damping)
Phase Damping Error Channel: PhaseDampingError{Float64}(0.1)
9. Phase-Amplitude Damping Error
Combines both amplitude and phase damping
phase_amplitude_damping = PhaseAmplitudeDampingError(0.1, 0.05, 0.0)
phase_amplitude_channel = quantum_channel(phase_amplitude_damping)
println("Phase-Amplitude Damping Error Channel: ", phase_amplitude_damping)
Phase-Amplitude Damping Error Channel: PhaseAmplitudeDampingError{Float64}(0.1, 0.05, 0.0)
10. Coherent Error
A deterministic error represented by a quantum gate
coherent_error = CoherentError(X)
coherent_channel = quantum_channel(coherent_error)
println("Coherent Error Channel: ", coherent_error)
Coherent Error Channel: CoherentError{XGate}(X)
Channel Representations
Each error type can be converted to different channel representations:
Kraus Channel Representation
Represents the channel as a set of Kraus operators
bit_flip_kraus = KrausChannel(bit_flip)
println("Bit Flip Kraus Operators:")
for (i, op) in enumerate(bit_flip_kraus.operators)
println("K$i = ", mat(op))
end
Bit Flip Kraus Operators:
K1 = ComplexF64[0.9486832980505138 + 0.0im 0.0 + 0.0im; 0.0 + 0.0im 0.9486832980505138 + 0.0im]
K2 = LuxurySparse.SDPermMatrix{ComplexF64, Int64, Vector{ComplexF64}, Vector{Int64}}
(1, 2) = 0.31622776601683794 + 0.0im
(2, 1) = 0.31622776601683794 + 0.0im
Mixed Unitary Channel Representation
Represents the channel as a convex combination of unitary operators
bit_flip_mixed = MixedUnitaryChannel(bit_flip)
println("Bit Flip Mixed Unitary Channel:")
for (i, (prob, gate)) in enumerate(zip(bit_flip_mixed.probs, bit_flip_mixed.operators))
println("p$i = $prob, U$i = ", mat(gate))
end
Bit Flip Mixed Unitary Channel:
p1 = 0.9, U1 = ComplexF64[1.0 + 0.0im 0.0 + 0.0im; 0.0 + 0.0im 1.0 + 0.0im]
p2 = 0.1, U2 = LuxurySparse.SDPermMatrix{ComplexF64, Int64, Vector{ComplexF64}, Vector{Int64}}
(1, 2) = 1.0 + 0.0im
(2, 1) = 1.0 + 0.0im
Superoperator Representation
Represents the channel as a superoperator matrix
bit_flip_superop = SuperOp(bit_flip)
println("Bit Flip Superoperator Matrix:")
println(bit_flip_superop.superop)
Bit Flip Superoperator Matrix:
sparse([1, 4, 2, 3, 2, 3, 1, 4], [1, 1, 2, 2, 3, 3, 4, 4], ComplexF64[0.8999999999999999 + 0.0im, 0.1 + 0.0im, 0.8999999999999999 + 0.0im, 0.1 + 0.0im, 0.1 + 0.0im, 0.8999999999999999 + 0.0im, 0.1 + 0.0im, 0.8999999999999999 + 0.0im], 4, 4)
Example: Comparing Different Error Types
Let's compare how different error types affect a simple circuit:
Create a simple test circuit
test_circ = chain(2, put(1=>H), control(2, 1=>X))
nqubits: 2
chain
├─ put on (1)
│ └─ H
└─ control(2)
└─ (1,) X
Test different error types
error_types = [
("Bit Flip", BitFlipError(0.1)),
("Phase Flip", PhaseFlipError(0.1)),
("Depolarizing", DepolarizingError(1, 0.1)),
("Amplitude Damping", AmplitudeDampingError(0.1)),
("Phase Damping", PhaseDampingError(0.1))
]
println("\nComparing different error types on a 2-qubit circuit:")
for (name, error) in error_types
# Add error after each gate
noisy_circ = replace_block(test_circ) do block
if block isa PutBlock && length(block.locs) == 1
chain(block, put(nqubits(block), block.locs => quantum_channel(error)))
elseif block isa ControlBlock && length(block.ctrl_locs) == 1 && length(block.locs) == 1
chain(block, put(nqubits(block), (block.ctrl_locs..., block.locs...) =>
kron(quantum_channel(error), quantum_channel(error))))
else
block
end
end
# Simulate
rho = noisy_simulation(zero_state(2), noisy_circ)
fid = fidelity(rho, apply(density_matrix(zero_state(2)), test_circ))
println("$name Error: Fidelity = $(round(fid, digits=3))")
end
Comparing different error types on a 2-qubit circuit:
┌ Warning: Assignment to `rho` in soft scope is ambiguous because a global variable by the same name exists: `rho` will be treated as a new local. Disambiguate by using `local rho` to suppress this warning or `global rho` to assign to the existing global variable.
└ @ ~/work/Yao.jl/Yao.jl/docs/src/generated/examples/9.noisy-simulation/index.md:24
Bit Flip Error: Fidelity = 0.949
Phase Flip Error: Fidelity = 0.906
Depolarizing Error: Fidelity = 0.927
Amplitude Damping Error: Fidelity = 0.975
Phase Damping Error: Fidelity = 0.975
This page was generated using Literate.jl.