Noise
MimiqCircuitsBase.AbstractNoiseRule — Type
AbstractNoiseRuleAbstract base type for all noise rules in the noise model. Each concrete noise rule defines when and how noise should be applied to circuit instructions.
MimiqCircuitsBase.CustomNoiseRule — Type
CustomNoiseRule <: AbstractNoiseRuleApply noise based on a custom matching function.
Fields
matcher::Function: Function (inst) -> Boolgenerator::Function: Function (inst) -> Instructionpriority_val::Int: Priority for this rule (default: 0)before::Bool: If true, apply noise before the matched instructionreplace::Bool: If true, noise replaces the original instruction
Examples
# Add noise to all 2-qubit operations
julia> rule = CustomNoiseRule(
inst -> numqubits(getoperation(inst)) == 2,
inst -> Instruction(Depolarizing2(0.01), getqubits(inst)...)
)
CustomNoiseRule(var"#3#5"(), var"#4#6"(), 0, false, false)MimiqCircuitsBase.ExactOperationInstanceQubitNoise — Type
ExactOperationInstanceQubitNoise <: AbstractNoiseRuleApply noise to a specific operation pattern only when it acts on specific qubits in exact order.
The operation pattern can have symbolic parameters (see OperationInstanceNoise).
Fields
operation::Operation: The operation pattern to match (may have symbolic parameters)qubits::Vector{Int}: Exact sequence of qubits (order matters)noise::Union{AbstractKrausChannel,AbstractGate}: The noise to apply (may use symbolic variables)before::Bool: If true, apply noise before the operation (default: false)replace::Bool: If true, replace the matched operation with the noise operation (default: false)
Examples
@variables a
# Only matches GateRX on qubit 1, with angle-dependent noise
julia> rule = ExactOperationInstanceQubitNoise(GateRX(a), [1], AmplitudeDamping(a / π))
ExactOperationInstanceQubitNoise(GateRX(a), [1], AmplitudeDamping(a / π), false, false)
# Compact syntax
julia> rule = ExactOperationInstanceQubitNoise(GateRX(a) => AmplitudeDamping(a / π), qubits=[1])
ExactOperationInstanceQubitNoise(GateRX(a), [1], AmplitudeDamping(a / π), false, false)MimiqCircuitsBase.ExactQubitReadoutNoise — Type
ExactQubitReadoutNoise <: AbstractNoiseRuleApply readout noise only to measurements on specific qubits in exact order.
Fields
qubits::Vector{Int}: Exact sequence of qubits (order matters)noise::ReadoutErr: The readout error to apply
Examples
# Only matches Measure on qubits [1, 2] in that exact order
julia> rule = ExactQubitReadoutNoise([1, 2], ReadoutErr(0.01, 0.02))
ExactQubitReadoutNoise([1, 2], ReadoutErr(0.01, 0.02))
# Different from [2, 1]
julia> rule2 = ExactQubitReadoutNoise([2, 1], ReadoutErr(0.02, 0.03))
ExactQubitReadoutNoise([2, 1], ReadoutErr(0.02, 0.03))MimiqCircuitsBase.GlobalReadoutNoise — Type
GlobalReadoutNoise <: AbstractNoiseRuleApply readout noise to all measurement operations in the circuit.
Fields
noise::ReadoutErr: The readout error to apply
Examples
rule = GlobalReadoutNoise(ReadoutErr([0.01, 0.02]))MimiqCircuitsBase.IdleNoise — Type
IdleNoise <: AbstractNoiseRuleApply noise to qubits that are idle (not involved in any operation) at a given time step.
The noise can depend on the idle time using a relation with a symbolic time variable.
Fields
relation::Union{Pair, Operation}: Either a relationtime_var => noise(time_var)or constant noise
Examples
Constant idle noise
julia> IdleNoise(AmplitudeDamping(0.0001))
IdleNoise(AmplitudeDamping(0.0001))Time-dependent idle noise
julia> @variables t
1-element Vector{Symbolics.Num}:
t
julia> IdleNoise(t => AmplitudeDamping(t / 1000))
IdleNoise(t => AmplitudeDamping(t / 1000))
julia> IdleNoise(t => AmplitudeDamping(1 - exp(-t / t^2)))
IdleNoise(t => AmplitudeDamping(1 - exp(-1 / t)))MimiqCircuitsBase.NoiseModel — Type
NoiseModelA collection of noise rules that define how noise is applied to a quantum circuit.
Fields
rules::Vector{AbstractNoiseRule}: List of noise rules in the modelname::String: Optional name for the noise model
Priority Order (lower number = higher priority)
CustomNoiseRule: configurable (default 0)ExactOperationInstanceQubitNoise: 40ExactQubitReadoutNoise: 50SetOperationInstanceQubitNoise: 60SetQubitReadoutNoise: 70OperationInstanceNoise: 80GlobalReadoutNoise: 90SetIdleQubitNoise: 190IdleNoise: 200
Examples
Using symbolic parameters for angle-dependent noise
julia> @variables θ
1-element Vector{Symbolics.Num}:
θ
julia> model = NoiseModel([
# Noise that scales with rotation angle
OperationInstanceNoise(GateRX(θ), Depolarizing1(θ / π)),
OperationInstanceNoise(GateRY(θ), Depolarizing1(θ / π)),
# Different noise for different qubit pairs
ExactOperationInstanceQubitNoise(GateCX(), [1, 2], Depolarizing2(0.01)),
ExactOperationInstanceQubitNoise(GateCX(), [2, 1], Depolarizing2(0.02)),
# General fallbacks
GlobalReadoutNoise(ReadoutErr(0.01, 0.02)),
IdleNoise(AmplitudeDamping(0.0001))
], name="Angle-Dependent Noise Model")
NoiseModel(AbstractNoiseRule[ExactOperationInstanceQubitNoise(GateCX(), [1, 2], Depolarizing(2, 0.01), false, false), ExactOperationInstanceQubitNoise(GateCX(), [2, 1], Depolarizing(2, 0.02), false, false), OperationInstanceNoise(GateRX(θ), Depolarizing(1, θ / π), false, false), OperationInstanceNoise(GateRY(θ), Depolarizing(1, θ / π), false, false), GlobalReadoutNoise(ReadoutErr(0.01, 0.02)), IdleNoise(AmplitudeDamping(0.0001))], "Angle-Dependent Noise Model")Using symbolic parameters with complex expressions
@variables α β
model = NoiseModel([
# Two-parameter operation with combined noise
OperationInstanceNoise(GateU(α, β, 0), Depolarizing1((α^2 + β^2) / (2π^2))),
], name="Complex Parameter Noise")MimiqCircuitsBase.OperationInstanceNoise — Type
OperationInstanceNoise <: AbstractNoiseRuleApply noise to operations matching a specific operation pattern.
The operation pattern can have symbolic parameters (e.g., GateRX(a)) which will be matched positionally against concrete operation instances. When a match occurs, the symbolic variables are substituted with the concrete parameter values in the noise.
Fields
operation::Operation: The operation pattern to match (may have symbolic parameters)noise::Union{AbstractKrausChannel,AbstractGate}: The noise to apply (may use symbolic variables)before::Bool: If true, apply noise before the operation (default: false)replace::Bool: If true, replace the matched operation with the noise operation (default: false)
Examples
Concrete operation matching
julia> rule = OperationInstanceNoise(GateRX(π/2), AmplitudeDamping(0.001))
OperationInstanceNoise(GateRX(π/2), AmplitudeDamping(0.001), false, false)Symbolic operation matching with parameter-dependent noise
julia> @variables a
1-element Vector{Symbolics.Num}:
a
# Matches any GateRX, applies noise that depends on the rotation angle
julia> rule = OperationInstanceNoise(GateRX(a), Depolarizing1(a / π))
OperationInstanceNoise(GateRX(a), Depolarizing(1, a / π), false, false)
# When GateRX(0.4) is encountered, applies Depolarizing1(0.4 / π)Multi-parameter symbolic matching
julia> @variables θ φ
2-element Vector{Symbolics.Num}:
θ
φ
julia> rule = OperationInstanceNoise(GateU(θ, φ, 0), Depolarizing1((θ^2 + φ^2) / (2π^2)))
OperationInstanceNoise(GateU(θ, φ, 0, 0π), Depolarizing(1, (θ^2 + φ^2) / 19.739208802178716), false, false)Compact relation syntax
julia> @variables a
1-element Vector{Symbolics.Num}:
a
julia> rule = OperationInstanceNoise(GateRX(a) => Depolarizing1(a + 2))
OperationInstanceNoise(GateRX(a), Depolarizing(1, 2 + a), false, false)Measurement noise with Pauli channels (apply before measurement)
julia> rule = OperationInstanceNoise(Measure(), PauliX(0.02); before=true)
OperationInstanceNoise(Measure(), PauliX(0.02), true, false)Reset noise
julia> rule = OperationInstanceNoise(Reset(), Depolarizing1(0.01))
OperationInstanceNoise(Reset(), Depolarizing(1, 0.01), false, false)Replace matched operation
julia> rule = OperationInstanceNoise(GateH(), AmplitudeDamping(0.001); replace=true)
OperationInstanceNoise(GateH(), AmplitudeDamping(0.001), false, true)MimiqCircuitsBase.SetIdleQubitNoise — Type
SetIdleQubitNoise <: AbstractNoiseRuleApply noise to idle qubits that are in a specified set.
The noise can depend on the idle time using a relation with a symbolic time variable.
Fields
relation::Union{Pair, Operation}: Either a relationtime_var => noise(time_var)or constant noisequbits::Set{Int}: Set of qubit indices where noise should be applied
Examples
Constant idle noise
SetIdleQubitNoise(AmplitudeDamping(0.0001), [1,2,3])Time-dependent idle noise
julia> @variables t
1-element Vector{Symbolics.Num}:
t
julia> SetIdleQubitNoise(t => AmplitudeDamping(t / 1000), [1,2,3])
SetIdleQubitNoise(t => AmplitudeDamping(t / 1000), Set([2, 3, 1]))MimiqCircuitsBase.SetOperationInstanceQubitNoise — Type
SetOperationInstanceQubitNoise <: AbstractNoiseRuleApply noise to a specific operation pattern when all its qubits are in a specified set.
The operation pattern can have symbolic parameters (see OperationInstanceNoise).
Fields
operation::Operation: The operation pattern to match (may have symbolic parameters)qubits::Set{Int}: Set of qubit indices where noise should be appliednoise::Union{AbstractKrausChannel,AbstractGate}: The noise to apply (may use symbolic variables)before::Bool: If true, apply noise before the operation (default: false)replace::Bool: If true, replace the matched operation with the noise operation (default: false)
Examples
@variables a
# Matches GateRX on any qubits in {1, 2, 3} with angle-dependent noise
julia> rule = SetOperationInstanceQubitNoise(GateRX(a), [1, 2, 3], PhaseAmplitudeDamping(1, 1, a / (2π)))
SetOperationInstanceQubitNoise(GateRX(a), Set([2, 3, 1]), PhaseAmplitudeDamping(1, 1, a / 6.283185307179586), false, false)
# Compact syntax
julia> rule = SetOperationInstanceQubitNoise(GateRX(a) => PhaseAmplitudeDamping(1, 1, a / (2π)), qubits=[1, 2, 3])
SetOperationInstanceQubitNoise(GateRX(a), Set([2, 3, 1]), PhaseAmplitudeDamping(1, 1, a / 6.283185307179586), false, false)MimiqCircuitsBase.SetQubitReadoutNoise — Type
SetQubitReadoutNoise <: AbstractNoiseRuleApply readout noise to measurements if all qubits are in the specified set.
Fields
qubits::Set{Int}: Set of qubit indices where noise should be appliednoise::ReadoutErr: The readout error to apply
Examples
# Matches any measurement where all qubits are in {1, 3, 5}
julia> rule = SetQubitReadoutNoise([1, 3, 5], ReadoutErr(0.01, 0.02))
SetQubitReadoutNoise(Set([5, 3, 1]), ReadoutErr(0.01, 0.02))MimiqCircuitsBase.add_idle_noise! — Method
add_idle_noise!(model, noise[; qubits=nothing])Add idle noise to a noise model.
Arguments
model: TheNoiseModelto which the rule will be addednoise: Either a constant noise operation or a relationtime_var => noise(time_var)qubits: (Optional) A collection of qubit indices to restrict the noise to
MimiqCircuitsBase.add_operation_noise! — Method
add_operation_noise!(model, operation, noise[; qubits=nothing[, exact=false[, before=false[, replace=false]]]])Add an operation-instance noise rule to a noise model.
This function adds noise to operation instances. The operation parameter can be either:
- A concrete operation (e.g.,
GateRX(π/2)orReset()) - matches only that exact operation - A symbolic operation (e.g.,
GateRX(a)whereais a symbolic variable) - matches any operation of that type and substitutes parameter values into the noise expression
Arguments
model: TheNoiseModelto which the rule will be addedoperation: The operation instance to target (concrete or symbolic)noise: The noise to apply (may contain symbolic expressions using variables fromoperation)qubits: (Optional) A collection of qubit indices to restrict the noise toexact: (Optional) If true and qubits is provided, the noise will only apply to operations on the exact sequence of qubits. Defaults to falsebefore: (Optional) If true, the noise is applied before the matched operation. Defaults to falsereplace: (Optional) If true, the matched operation is replaced by the noise operation. Defaults to false
Examples
Concrete operation matching
julia> model = NoiseModel()
NoiseModel(AbstractNoiseRule[], "")
# Apply noise only to RX(π/2) operations
julia> add_operation_noise!(model, GateRX(π/2), AmplitudeDamping(0.001))
NoiseModel(AbstractNoiseRule[OperationInstanceNoise(GateRX(π/2), AmplitudeDamping(0.001), false, false)], "")Symbolic operation matching with angle-dependent noise
julia> @variables θ
1-element Vector{Symbolics.Num}:
θ
julia> model = NoiseModel()
NoiseModel(AbstractNoiseRule[], "")
# Apply noise to all RX operations, with noise strength proportional to angle
julia> add_operation_noise!(model, GateRX(θ), Depolarizing1(θ / π))
NoiseModel(AbstractNoiseRule[OperationInstanceNoise(GateRX(θ), Depolarizing(1, θ / π), false, false)], "")
# When GateRX(0.4) is encountered, Depolarizing1(0.4 / π) ≈ Depolarizing1(0.127) is applied
# When GateRX(1.2) is encountered, Depolarizing1(1.2 / π) ≈ Depolarizing1(0.382) is appliedSymbolic multi-parameter operations
julia> @variables α β
2-element Vector{Symbolics.Num}:
α
β
# Noise depends on both parameters
julia> model = NoiseModel()
NoiseModel(AbstractNoiseRule[], "")
julia> add_operation_noise!(model, GateU(α, β, 0), Depolarizing1((α^2 + β^2) / (2π^2)))
NoiseModel(AbstractNoiseRule[OperationInstanceNoise(GateU(α, β, 0, 0π), Depolarizing(1, (α^2 + β^2) / 19.739208802178716), false, false)], "")Qubit-specific symbolic noise
julia> @variables θ
1-element Vector{Symbolics.Num}:
θ
# Only on specific qubits
julia> model = NoiseModel()
NoiseModel(AbstractNoiseRule[], "")
julia> add_operation_noise!(model, GateRX(θ), Depolarizing1(θ / π), qubits=[1, 2, 3], exact=false)
NoiseModel(AbstractNoiseRule[SetOperationInstanceQubitNoise(GateRX(θ), Set([2, 3, 1]), Depolarizing(1, θ / π), false, false)], "")
# Only on exact qubit order
julia> model = NoiseModel()
NoiseModel(AbstractNoiseRule[], "")
julia> add_operation_noise!(model, GateRX(θ), Depolarizing1(θ / π), qubits=[1], exact=true)
NoiseModel(AbstractNoiseRule[ExactOperationInstanceQubitNoise(GateRX(θ), [1], Depolarizing(1, θ / π), false, false)], "")Measurement noise with Pauli channels
julia> model = NoiseModel()
NoiseModel(AbstractNoiseRule[], "")
julia> add_operation_noise!(model, Measure(), PauliX(0.02); before=true)
NoiseModel(AbstractNoiseRule[OperationInstanceNoise(Measure(), PauliX(0.02), true, false)], "")Reset noise
julia> model = NoiseModel()
NoiseModel(AbstractNoiseRule[], "")
julia> add_operation_noise!(model, Reset(), Depolarizing1(0.01))
NoiseModel(AbstractNoiseRule[OperationInstanceNoise(Reset, Depolarizing(1, 0.01), false, false)], "")Replace matched operation
julia> model = NoiseModel()
NoiseModel(AbstractNoiseRule[], "")
julia> add_operation_noise!(model, GateH(), AmplitudeDamping(0.001); replace=true)
NoiseModel(AbstractNoiseRule[OperationInstanceNoise(GateH(), AmplitudeDamping(0.001), false, true)], "")MimiqCircuitsBase.add_readout_noise! — Method
add_readout_noise!(model, noise[; qubits=nothing[, exact=false]])Add a readout noise rule to a noise model.
This function simplifies the process of adding different types of readout noise.
Arguments
model: TheNoiseModelto which the rule will be addednoise: TheReadoutErrto applyqubits: (Optional) A collection of qubit indices. If not provided, the noise is globalexact: (Optional) Iftrueandqubitsis provided, the noise will only apply to measurements on the exact sequence of qubits. Defaults tofalse
Behavior
- If
qubitsisnothing, aGlobalReadoutNoiserule is added - If
qubitsis provided andexactisfalse, aSetQubitReadoutNoiserule is added - If
qubitsis provided andexactistrue, anExactQubitReadoutNoiserule is added
Examples
julia> model = NoiseModel()
NoiseModel(AbstractNoiseRule[], "")
# Global readout noise
julia> add_readout_noise!(model, ReadoutErr(0.01, 0.02))
NoiseModel(AbstractNoiseRule[GlobalReadoutNoise(ReadoutErr(0.01, 0.02))], "")
# Readout noise on a specific set of qubits
julia> add_readout_noise!(model, ReadoutErr(0.03, 0.04), qubits=[1, 3])
NoiseModel(AbstractNoiseRule[SetQubitReadoutNoise(Set([3, 1]), ReadoutErr(0.03, 0.04)), GlobalReadoutNoise(ReadoutErr(0.01, 0.02))], "")
# Readout noise for an exact qubit order
julia> add_readout_noise!(model, ReadoutErr(0.05, 0.06), qubits=[2, 1], exact=true)
NoiseModel(AbstractNoiseRule[ExactQubitReadoutNoise([2, 1], ReadoutErr(0.05, 0.06)), SetQubitReadoutNoise(Set([3, 1]), ReadoutErr(0.03, 0.04)), GlobalReadoutNoise(ReadoutErr(0.01, 0.02))], "")MimiqCircuitsBase.add_rule! — Method
add_rule!(model::NoiseModel, rule::AbstractNoiseRule)Add a new rule to the noise model. Rules are automatically sorted by priority.
MimiqCircuitsBase.apply_noise_model! — Method
apply_noise_model!(circuit::Circuit, model::NoiseModel)Apply a noise model to a circuit in-place.
This is a convenience function that replaces the circuit with its noisy version.
Arguments
circuit: The circuit to modifymodel: The noise model to apply
Returns
The modified circuit
MimiqCircuitsBase.apply_noise_model — Method
apply_noise_model(circuit::Circuit, model::NoiseModel) -> CircuitApply a noise model to a circuit, creating a new noisy circuit.
For each instruction in the circuit, the rules are tried in priority order until one applies. The apply_rule function returns nothing for non-matching rules, allowing efficient rule search without redundant matching checks.
Wrapper operations are processed recursively (Block, IfStatement, Parallel, Repeat, and GateCall). Nested wrappers are also traversed recursively.
Arguments
circuit: The original circuitmodel: The noise model to apply
Returns
A new circuit with noise applied according to the model
Examples
julia> @variables θ
1-element Vector{Symbolics.Num}:
θ
julia> c = Circuit()
empty circuit
julia> push!(c, GateRX(0.4), 1)
1-qubit circuit with 1 instruction:
└── RX(0.4) @ q[1]
julia> push!(c, GateRX(0.8), 2)
2-qubit circuit with 2 instructions:
├── RX(0.4) @ q[1]
└── RX(0.8) @ q[2]
julia> push!(c, Measure(), 1:2, 1:2)
2-qubit, 2-bit circuit with 4 instructions:
├── RX(0.4) @ q[1]
├── RX(0.8) @ q[2]+
├── M @ q[1], c[1]
└── M @ q[2], c[2]
julia> model = NoiseModel([
OperationInstanceNoise(GateRX(θ), Depolarizing1(θ / π)),
GlobalReadoutNoise(ReadoutErr(0.01, 0.02))
])
NoiseModel(AbstractNoiseRule[OperationInstanceNoise(GateRX(θ), Depolarizing(1, θ / π), false, false), GlobalReadoutNoise(ReadoutErr(0.01, 0.02))], "")
julia> noisy_circuit = apply_noise_model(c, model)
2-qubit, 2-bit circuit with 8 instructions:
├── RX(0.4) @ q[1]
├── Depolarizing(1,0.127324) @ q[1]
├── RX(0.8) @ q[2]
├── Depolarizing(1,0.254648) @ q[2]
├── M @ q[1], c[1]
├── RErr(0.01, 0.02) @ c[1]
├── M @ q[2], c[2]
└── RErr(0.01, 0.02) @ c[2]
# Result:
# - GateRX(0.4) followed by Depolarizing1(0.4 / π) ≈ Depolarizing1(0.127)
# - GateRX(0.8) followed by Depolarizing1(0.8 / π) ≈ Depolarizing1(0.255)
# - Measurements followed by ReadoutErr(0.01, 0.02)Recursive wrapper behavior (Block, GateCall, Parallel, Repeat, IfStatement)
julia> model = NoiseModel([OperationInstanceNoise(GateH(), AmplitudeDamping(0.01))])
NoiseModel(AbstractNoiseRule[OperationInstanceNoise(GateH(), AmplitudeDamping(0.01), false, false)], "")
# Block
julia> c_block = Circuit()
empty circuit
julia> push!(c_block, Block(1, 0, 0, [Instruction(GateH(), (1,), (), ())]), 1)
1-qubit circuit with 1 instruction:
└── block 2y91k9t1raigi @ q[1]
julia> n_block = apply_noise_model(c_block, model)
1-qubit circuit with 1 instruction:
└── block 1gbh70af4yogd @ q[1]
julia> n_block|>decompose_step
1-qubit circuit with 2 instructions:
├── H @ q[1]
└── AmplitudeDamping(0.01) @ q[1]
# GateCall
julia> decl = GateDecl(:local_h, (), [Instruction(GateH(), (1,), (), ())])
gate local_h() =
└── H @ q[1]
julia> c_gatecall = Circuit()
empty circuit
julia> push!(c_gatecall, GateCall(decl), 1)
1-qubit circuit with 1 instruction:
└── local_h @ q[1]
julia> n_gatecall = apply_noise_model(c_gatecall, model)
1-qubit circuit with 1 instruction:
└── block 32mk75z1nxr64 @ q[1]
julia> n_gatecall|>decompose_step
1-qubit circuit with 2 instructions:
├── H @ q[1]
└── AmplitudeDamping(0.01) @ q[1]
# Parallel
julia> c_parallel = Circuit()
empty circuit
julia> push!(c_parallel, Parallel(2, GateH()), 1, 2)
2-qubit circuit with 1 instruction:
└── ⨷ ² H @ q[1], q[2]
julia> n_parallel = apply_noise_model(c_parallel, model)
2-qubit circuit with 1 instruction:
└── block 2ee4nt7tqqx9l @ q[1:2]
julia> n_parallel|>decompose_step
2-qubit circuit with 4 instructions:
├── H @ q[1]
├── AmplitudeDamping(0.01) @ q[1]
├── H @ q[2]
└── AmplitudeDamping(0.01) @ q[2]
# Repeat
julia> c_repeat = Circuit()
empty circuit
julia> push!(c_repeat, Repeat(2, GateH()), 1)
1-qubit circuit with 1 instruction:
└── ∏² H @ q[1]
julia> n_repeat = apply_noise_model(c_repeat, model)
1-qubit circuit with 1 instruction:
└── block 33yafv6sg3yxg @ q[1]
julia> n_repeat|>decompose_step
1-qubit circuit with 4 instructions:
├── H @ q[1]
├── AmplitudeDamping(0.01) @ q[1]
├── H @ q[1]
└── AmplitudeDamping(0.01) @ q[1]
# IfStatement
julia> c_if = Circuit()
empty circuit
julia> push!(c_if, IfStatement(GateH(), BitString("1")), 1, 1)
1-qubit, 1-bit circuit with 1 instruction:
└── IF(c==1) H @ q[1], condition[1]
julia> n_if = apply_noise_model(c_if, model)
1-qubit, 1-bit circuit with 1 instruction:
└── IF(c==1) block 2mvmsxvfbxhb2 @ q[1], condition[1]
julia> n_if|>decompose_step
1-qubit, 1-bit circuit with 2 instructions:
├── IF(c==1) H @ q[1], condition[1]
└── IF(c==1) AmplitudeDamping(0.01) @ q[1], condition[1]MimiqCircuitsBase.apply_rule — Method
apply_rule(rule::AbstractNoiseRule, inst::Instruction) -> Union{Instruction, Nothing}Generate a noise instruction based on the rule and the matched instruction.
Returns nothing if the rule does not match the instruction. This allows the noise application loop to try multiple rules without needing separate match checks.
Arguments
rule: The noise rule to applyinst: The instruction that the rule should be applied to
Returns
- A new
Instructionrepresenting the noise to be added, or nothingif the rule does not match the instruction
MimiqCircuitsBase.before — Method
before(rule::AbstractNoiseRule) -> BoolReturn whether the noise should be applied before the operation. Default is false (apply after). Override for specific rule types.
MimiqCircuitsBase.describe — Method
describe(model::NoiseModel)Print a human-readable description of the noise model.
MimiqCircuitsBase.matches — Method
matches(rule::AbstractNoiseRule, inst::Instruction) -> BoolCheck if a noise rule matches a given instruction.
Arguments
rule: The noise rule to checkinst: The instruction to potentially add noise to
Returns
true if the rule applies to this instruction, false otherwise
MimiqCircuitsBase.priority — Method
priority(rule::AbstractNoiseRule) -> IntReturn the priority of a noise rule. Lower numbers have higher priority. Default priority is 100. Override this method to change rule priority.
MimiqCircuitsBase.add_noise! — Method
add_noise!(c, g, noise; before=false, parallel=false)Add a noise operation noise to every operation g in the circuit c.
The noise operation noise can be a Kraus channel or a gate and will act on the same qubits as the operation g it is being added to. The operations g and noise must act on the same number of qubits.
This function is maintained for backward compatibility. Consider using decorate! for more general decoration needs.
Arguments
c: Circuit to modifyg: Operation to which noise will be addednoise: Kraus channel or gate that will be added to each operationgbefore: Iftrue, add noise before the operation (default:false)parallel: Iftrue, group transversal operations into blocks (default:false)
Returns
The circuit c with the noise added in place
Examples
Parallel vs not parallel
julia> c = push!(Circuit(), GateH(), 1:3);
julia> add_noise!(c, GateH(), AmplitudeDamping(0.2))
3-qubit circuit with 6 instructions:
├── H @ q[1]
├── AmplitudeDamping(0.2) @ q[1]
├── H @ q[2]
├── AmplitudeDamping(0.2) @ q[2]
├── H @ q[3]
└── AmplitudeDamping(0.2) @ q[3]
julia> c = push!(Circuit(), GateH(), 1:3);
julia> add_noise!(c, GateH(), AmplitudeDamping(0.2); parallel=true)
3-qubit circuit with 6 instructions:
├── H @ q[1]
├── H @ q[2]
├── H @ q[3]
├── AmplitudeDamping(0.2) @ q[1]
├── AmplitudeDamping(0.2) @ q[2]
└── AmplitudeDamping(0.2) @ q[3]Noise before measurements
julia> c = push!(Circuit(), Measure(), 1:3, 1:3);
julia> add_noise!(c, Measure(), PauliX(0.1); before=true)
3-qubit, 3-bit circuit with 6 instructions:
├── PauliX(0.1) @ q[1]
├── M @ q[1], c[1]
├── PauliX(0.1) @ q[2]
├── M @ q[2], c[2]
├── PauliX(0.1) @ q[3]
└── M @ q[3], c[3]
MimiqCircuitsBase.add_noise — Method
add_noise(c, g, noise; before=false, parallel=false)Add noise operation noise to every operation g in circuit c.
A copy of c is created and then noise is added to the copy.
This function is maintained for backward compatibility. Consider using decorate for more general decoration needs.
See add_noise! for more information.
MimiqCircuitsBase.decorate! — Method
decorate!(circuit::Circuit, matcher::Function, generator::Function; before=false, parallel=false)Modifies a circuit in-place by adding generated instructions next to matched instructions.
This is the main mutating interface for circuit decoration. It combines the functionality of single and parallel decoration based on the parallel parameter.
Arguments
circuit: The circuit to modifymatcher: A function that takes an instruction and returnstrueif it should be decoratedgenerator: A function that takes a matched instruction and returns a new instruction to addbefore: Iftrue, add decorations before matched instructions (default:false)parallel: Iftrue, group transversal operations into blocks (default:false)
Returns
The modified circuit
Examples
julia> c = Circuit();
julia> push!(c, GateX(), 1);
julia> push!(c, GateY(), 2);
julia> decorate!(c,
inst -> getoperation(inst) isa GateX,
inst -> Instruction(GateH(), getqubits(inst)...);
before=true)
2-qubit circuit with 3 instructions:
├── H @ q[1]
├── X @ q[1]
└── Y @ q[2]
MimiqCircuitsBase.decorate! — Method
decorate!(circuit::Circuit, target::Operation, decoration::Operation; before=false, parallel=false)Modifies a circuit in-place by adding decoration operations next to all instances of target operation.
Mutating version of decorate.
MimiqCircuitsBase.decorate! — Method
decorate!(circuit::Circuit, target::Type{<:Operation}, decoration::Operation; before=false, parallel=false)Modifies a circuit in-place by adding decoration operations next to all operations of type target.
Mutating version of decorate.
MimiqCircuitsBase.decorate — Method
decorate(circuit::Circuit, matcher::Function, generator::Function; before=false, parallel=false)Creates a new decorated circuit by adding generated instructions next to matched instructions.
This is the main non-mutating interface for circuit decoration. It combines the functionality of single and parallel decoration based on the parallel parameter.
Arguments
circuit: The original circuit (not modified)matcher: A function that takes an instruction and returnstrueif it should be decoratedgenerator: A function that takes a matched instruction and returns a new instruction to addbefore: Iftrue, add decorations before matched instructions (default:false)parallel: Iftrue, group transversal operations into blocks (default:false)
Returns
A new circuit with decorations added
Examples
Basic decoration with custom matcher and generator
julia> c = Circuit();
julia> push!(c, GateH(), 1);
julia> push!(c, GateX(), 2);
julia> push!(c, GateH(), 3);
julia> is_hadamard(inst) = getoperation(inst) isa GateH;
julia> add_phase(inst) = Instruction(GateS(), getqubits(inst)...);
julia> c2 = decorate(c, is_hadamard, add_phase)
3-qubit circuit with 5 instructions:
├── H @ q[1]
├── S @ q[1]
├── X @ q[2]
├── H @ q[3]
└── S @ q[3]
Parallel decoration for transversal operations
julia> c = Circuit();
julia> push!(c, GateH(), 1:4);
julia> c2 = decorate(c,
inst -> getoperation(inst) isa GateH,
inst -> Instruction(GateT(), getqubits(inst)...);
parallel=true)
4-qubit circuit with 8 instructions:
├── H @ q[1]
├── H @ q[2]
├── H @ q[3]
├── H @ q[4]
├── T @ q[1]
├── T @ q[2]
├── T @ q[3]
└── T @ q[4]MimiqCircuitsBase.decorate — Method
decorate(circuit::Circuit, target::Operation, decoration::Operation; before=false, parallel=false)Creates a new circuit by adding decoration operations next to all instances of target operation.
Arguments
circuit: The original circuit (not modified)target: The specific operation to decoratedecoration: The operation to add as decorationbefore: Iftrue, add decorations before target operations (default:false)parallel: Iftrue, group transversal operations into blocks (default:false)
Returns
A new circuit with decorations added
Examples
julia> c = Circuit();
julia> push!(c, GateH(), 1:2);
julia> push!(c, GateX(), 1);
julia> c2 = decorate(c, GateH(), GateS())
2-qubit circuit with 5 instructions:
├── H @ q[1]
├── S @ q[1]
├── H @ q[2]
├── S @ q[2]
└── X @ q[1]
MimiqCircuitsBase.decorate — Method
decorate(circuit::Circuit, target::Type{<:Operation}, decoration::Operation; before=false, parallel=false)Creates a new circuit by adding decoration operations next to all operations of type target.
Arguments
circuit: The original circuit (not modified)target: The type of operations to decoratedecoration: The operation to add as decorationbefore: Iftrue, add decorations before target operations (default:false)parallel: Iftrue, group transversal operations into blocks (default:false)
Returns
A new circuit with decorations added
Examples
julia> c = Circuit();
julia> push!(c, GateH(), 1);
julia> push!(c, GateX(), 3);
julia> c2 = decorate(c, GateH, GateT(); before=true)
3-qubit circuit with 3 instructions:
├── T @ q[1]
├── H @ q[1]
└── X @ q[3]
MimiqCircuitsBase.decorate_on_match_parallel! — Method
decorate_on_match_parallel!(circuit::Circuit, matcher::Function, generator::Function; before::Bool=false)Modifies a circuit in-place by adding blocks of instructions next to transversal blocks of instructions identified by a matcher function.
This function identifies consecutive instructions that satisfy the matcher and act on disjoint resources (qubits, classical bits, variables), treating them as a transversal block. It then generates a corresponding block of decorations and inserts it before or after the entire original block.
Arguments
circuit: The circuit to modifymatcher: A function that takes an instruction and returnstrueif it should be decoratedgenerator: A function that takes a matched instruction and returns a new instruction to addbefore: Iftrue, add the generated block before the matched block (default:false)
Returns
The modified circuit
Examples
julia> c = Circuit();
julia> push!(c, GateH(), 1:3); # Transversal H gates
julia> push!(c, GateX(), 1);
julia> matcher(inst) = getoperation(inst) isa GateH;
julia> generator(inst) = Instruction(GateS(), getqubits(inst)...);
julia> decorate_on_match_parallel!(c, matcher, generator)
3-qubit circuit with 7 instructions:
├── H @ q[1]
├── H @ q[2]
├── H @ q[3]
├── S @ q[1]
├── S @ q[2]
├── S @ q[3]
└── X @ q[1]
MimiqCircuitsBase.decorate_on_match_parallel — Method
decorate_on_match_parallel(circuit::Circuit, matcher::Function, generator::Function; before::Bool=false)Creates a new circuit by adding blocks of instructions next to transversal blocks of instructions identified by a matcher function.
Non-mutating version of decorate_on_match_parallel!.
Arguments
circuit: The original circuit (not modified)matcher: A function that takes an instruction and returnstrueif it should be decoratedgenerator: A function that takes a matched instruction and returns a new instruction to addbefore: Iftrue, add the generated block before the matched block (default:false)
Returns
A new circuit with decorations added
Examples
julia> c = Circuit();
julia> push!(c, GateH(), 1:3); # Transversal H gates
julia> matcher(inst) = getoperation(inst) isa GateH;
julia> generator(inst) = Instruction(GateT(), getqubits(inst)...);
julia> c2 = decorate_on_match_parallel(c, matcher, generator; before=true)
3-qubit circuit with 6 instructions:
├── T @ q[1]
├── T @ q[2]
├── T @ q[3]
├── H @ q[1]
├── H @ q[2]
└── H @ q[3]
MimiqCircuitsBase.decorate_on_match_single! — Method
decorate_on_match_single!(circuit::Circuit, matcher::Function, generator::Function; before::Bool=false)Modifies a circuit in-place by adding instructions generated by a generator function next to each instruction identified by a matcher function.
For each instruction that satisfies the matcher, a corresponding instruction is generated using the generator function and inserted either before or after the matched instruction.
Arguments
circuit: The circuit to modifymatcher: A function that takes an instruction and returnstrueif it should be decoratedgenerator: A function that takes a matched instruction and returns a new instruction to addbefore: Iftrue, add the generated instruction before the matched one (default:false)
Returns
The modified circuit
Examples
julia> c = Circuit();
julia> push!(c, GateH(), 1);
julia> push!(c, GateX(), 2);
julia> push!(c, GateH(), 3);
julia> matcher(inst) = getoperation(inst) isa GateH;
julia> generator(inst) = Instruction(GateZ(), getqubits(inst)...);
julia> decorate_on_match_single!(c, matcher, generator)
3-qubit circuit with 5 instructions:
├── H @ q[1]
├── Z @ q[1]
├── X @ q[2]
├── H @ q[3]
└── Z @ q[3]MimiqCircuitsBase.decorate_on_match_single — Method
decorate_on_match_single(circuit::Circuit, matcher::Function, generator::Function; before::Bool=false)Creates a new circuit by adding instructions generated by a generator function next to each instruction identified by a matcher function.
Non-mutating version of decorate_on_match_single!.
Arguments
circuit: The original circuit (not modified)matcher: A function that takes an instruction and returnstrueif it should be decoratedgenerator: A function that takes a matched instruction and returns a new instruction to addbefore: Iftrue, add the generated instruction before the matched one (default:false)
Returns
A new circuit with decorations added
Examples
julia> c = Circuit();
julia> push!(c, GateH(), 1);
julia> push!(c, GateX(), 2);
julia> matcher(inst) = getoperation(inst) isa GateH;
julia> generator(inst) = Instruction(GateT(), getqubits(inst)...);
julia> c2 = decorate_on_match_single(c, matcher, generator; before=true)
2-qubit circuit with 3 instructions:
├── T @ q[1]
├── H @ q[1]
└── X @ q[2]
MimiqCircuitsBase.sample_mixedunitaries — Method
sample_mixedunitaries(c; rng, ids=false)Samples one unitary gate for each mixed unitary Kraus channel in the circuit.
This is possible because for mixed unitary noise channels the probabilities of each Kraus operator are fixed (state-independent).
Note: This function is internally called (before applying any gate) when executing a circuit with noise using trajectories, but it can also be used to generate samples of circuits without running them.
See also ismixedunitary, MixedUnitary, probabilities, and unitarygates.
Arguments
c: Circuit to be sampled.rng: (optional) Random number generator.ids: (optional) Boolean, default=false. When the selected Kraus operator is an identity it has no effect on the circuit. The parameteridsdecides whether to add it to the circuit (ids=true) or not (ids=false`; default). Usually, most of the Kraus operators selected will be identity gates.
Returns
A copy of circuit but with every mixed unitary Kraus channel replaced by one of the unitary gates of the channel (or nothing if identity and ids==false).
Examples
Gates and non-mixed-unitary Kraus channels remain unchanged.
julia> using Random
julia> c = push!(Circuit(), GateH(), 1:3);
julia> push!(c, Depolarizing1(0.5), 1:3);
julia> push!(c, AmplitudeDamping(0.5), 1:3)
3-qubit circuit with 9 instructions:
├── H @ q[1]
├── H @ q[2]
├── H @ q[3]
├── Depolarizing(1,0.5) @ q[1]
├── Depolarizing(1,0.5) @ q[2]
├── Depolarizing(1,0.5) @ q[3]
├── AmplitudeDamping(0.5) @ q[1]
├── AmplitudeDamping(0.5) @ q[2]
└── AmplitudeDamping(0.5) @ q[3]
julia> rng = MersenneTwister(42);
julia> sample_mixedunitaries(c; rng=rng, ids=true)
3-qubit circuit with 9 instructions:
├── H @ q[1]
├── H @ q[2]
├── H @ q[3]
├── Y @ q[1]
├── ID @ q[2]
├── ID @ q[3]
├── AmplitudeDamping(0.5) @ q[1]
├── AmplitudeDamping(0.5) @ q[2]
└── AmplitudeDamping(0.5) @ q[3]By default identities are not included.
julia> rng = MersenneTwister(42);
julia> sample_mixedunitaries(c; rng=rng)
3-qubit circuit with 7 instructions:
├── H @ q[1]
├── H @ q[2]
├── H @ q[3]
├── Y @ q[1]
├── AmplitudeDamping(0.5) @ q[1]
├── AmplitudeDamping(0.5) @ q[2]
└── AmplitudeDamping(0.5) @ q[3]Different calls to the function generate different results.
julia> sample_mixedunitaries(c; rng=rng)
3-qubit circuit with 6 instructions:
├── H @ q[1]
├── H @ q[2]
├── H @ q[3]
├── AmplitudeDamping(0.5) @ q[1]
├── AmplitudeDamping(0.5) @ q[2]
└── AmplitudeDamping(0.5) @ q[3]
julia> sample_mixedunitaries(c; rng=rng)
3-qubit circuit with 6 instructions:
├── H @ q[1]
├── H @ q[2]
├── H @ q[3]
├── AmplitudeDamping(0.5) @ q[1]
├── AmplitudeDamping(0.5) @ q[2]
└── AmplitudeDamping(0.5) @ q[3]MimiqCircuitsBase.ProjectiveNoiseX — Type
ProjectiveNoiseX()Single qubit projection noise onto a X Pauli basis.
This channel is defined by the Kraus operators
\[E_1 = |-\rangle \langle-|, \quad E_2 = |+\rangle \langle+|,\]
Where $\ket{+}$ and $\ket{-}$ are the eigenstates of Pauli X.
See also ProjectiveNoise, ProjectiveNoiseY, or ProjectiveNoiseZ.
MimiqCircuitsBase.ProjectiveNoiseY — Type
ProjectiveNoiseY()Single qubit projection noise onto a Y Pauli basis.
This channel is defined by the Kraus operators
\[E_1 = |Y0\rangle \langle Y0|, \quad E_2 = |Y1\rangle \langle Y1|,\]
Where $\ket{Y0}$ and $\ket{Y1}$ are the eigenstates of Pauli Y.
See also ProjectiveNoise, ProjectiveNoiseX, or ProjectiveNoiseZ.
MimiqCircuitsBase.ProjectiveNoiseZ — Type
ProjectiveNoiseZ()Single qubit projection noise onto a Z Pauli basis.
This channel is defined by the Kraus operators
\[E_1 = |0\rangle \langle Z0|, \quad E_2 = |1\rangle \langle Z1|,\]
Where $\ket{0}$ and $\ket{1}$ are the eigenstates of Pauli Z.
See also ProjectiveNoise, ProjectiveNoiseX, or ProjectiveNoiseY.
MimiqCircuitsBase.ProjectiveNoise — Function
ProjectiveNoise(basis)Single qubit projection noise onto a Pauli basis.
This channel is defined by the Kraus operators
\[E_1 = |\alpha\rangle \langle\alpha|, \quad E_2 = |\beta\rangle \langle\beta|,\]
where the states $|\alpha\rangle$ and $|\beta\rangle$ are the +1 and -1 eigenstates of a Pauli operator. Specifically, they correspond to $\{ |0\langle, |1\langle \}$ ($Z$ basis), $\{ |+\langle, |-\langle \}$ ($X$ basis), or $\{ |y+\langle, |y-\langle \}$ (Y basis).
This operation is similar to measuring in the corresponding basis ($X$, $Y$, or $Z$), except that the outcome of the measurement is not stored, i.e. there's loss of information.
Arguments
basis: Symbol, String or Char that selects the Pauli basis,"X","Y", or"Z".
Examples
julia> push!(Circuit(), ProjectiveNoise("Z"), 1)
1-qubit circuit with 1 instruction:
└── ProjectiveNoiseZ @ q[1]The Kraus matrices are given by:
julia> krausmatrices(ProjectiveNoise("X"))
2-element Vector{Matrix{Float64}}:
[0.5 0.5; 0.5 0.5]
[0.5 -0.5; -0.5 0.5]
julia> krausmatrices(ProjectiveNoise("Y"))
2-element Vector{Matrix{ComplexF64}}:
[0.5 + 0.0im 0.0 - 0.5im; 0.0 + 0.5im 0.5 + 0.0im]
[0.5 + 0.0im 0.0 + 0.5im; 0.0 - 0.5im 0.5 + 0.0im]
julia> krausmatrices(ProjectiveNoise("Z"))
2-element Vector{Matrix{Int64}}:
[1 0; 0 0]
[0 0; 0 1]MimiqCircuitsBase.Kraus — Type
Kraus(E)Custom $N$ qubit Kraus channel specified by a list of Kraus operators.
A Kraus channel is defined by
\[\mathcal{E}(\rho) = \sum_k E_k \rho E_k^\dagger,\]
where $E_k$ are Kraus operators that need to fulfill $\sum_k E_k^\dagger E_k = I$.
If the Kraus operators are all proportional to unitaries, use MixedUnitary instead.
The Kraus matrices are defined in the computational basis in the usual textbook order (the first qubit corresponds to the left-most qubit). For 1 qubit we have $|0\rangle$, $|1\rangle$. For 2 qubits we have $|00\rangle$, $|01\rangle$, $|10\rangle$, $|11\rangle$. See also GateCustom.
See also MixedUnitary, AbstractKrausChannel.
Arguments
E: Vector of $2^N \times 2^N$ complex matrices or $N$ qubit operators. Both can be mixed.
Examples
julia> push!(Circuit(), Kraus([[1 0; 0 sqrt(0.9)], [0 sqrt(0.1); 0 0]]), 1)
1-qubit circuit with 1 instruction:
└── Kraus(Operator([1.0 0.0; 0.0 0.948683]), Operator([0.0 0.316228; 0.0 0.0])) @ q[1]
julia> push!(Circuit(), Kraus([Projector0(), Projector1()]), 1)
1-qubit circuit with 1 instruction:
└── Kraus(Projector0(1), Projector1(1)) @ q[1]
julia> push!(Circuit(), Kraus([[1 0; 0 0], Projector1()]), 1)
1-qubit circuit with 1 instruction:
└── Kraus(Operator([1.0 0.0; 0.0 0.0]), Projector1(1)) @ q[1]
julia> @variables x
1-element Vector{Symbolics.Num}:
x
julia> g = Kraus([Projector0(), Projector1(x)])
Kraus(Projector0(1), Projector1(x))
julia> evaluate(g,Dict(x=>1))
Kraus(Projector0(1), Projector1(1))
julia> g = Kraus([[1 0; 0 sqrt(0.9)], [0 sqrt(0.1); 0 x]])
Kraus(Operator([1.0 0.0; 0.0 0.948683]), Operator(Real[0 0.316228; 0 x]))
julia> evaluate(g,Dict(x=>0))
Kraus(Operator([1.0 0.0; 0.0 0.948683]), Operator(Real[0 0.316228; 0 0]))MimiqCircuitsBase.AbstractKrausChannel — Type
AbstractKrausChannel{N} <: Operation{N,0,0}Supertype for all the $N$-qubit Kraus channels.
A Kraus channel is a quantum operation on a density matrix $\rho$ of the form
\[\mathcal{E}(\rho) = \sum_k E_k \rho E_k^\dagger,\]
where $E_k$ are Kraus operators that need to fulfill $\sum_k E_k^\dagger E_k \leq I$.
Special properties:
isCPTP: A Kraus channel a completely positive and trace preserving (CPTP) operation when $\sum_k E_k^\dagger E_k = I$. Currently, all noise channels are CPTP.ismixedunitary: A Kraus channel is called a mixed unitary channel when the Kraus operators $E_k$ are each proportional to a unitary matrix $U_k$, i.e. when $\mathcal{E}(\rho) = \sum_k p_k U_k \rho U_k^\dagger$ with some probabilities $0\leq p_k \leq 1$ that add up to 1 and $U_k^\dagger U_k = I$.
See also krausmatrices, unitarymatrices, probabilities.
MimiqCircuitsBase.cumprobabilities — Method
cumprobabilities(mixedunitarychannel)Cumulative sum of probabilities of a mixed unitary Kraus channel.
A mixed unitary channel is written as $\sum_k p_k U_k \rho U_k^\dagger$, where $p_k$ are the probabilities.
An error is returned for Kraus channels with ismixedunitary(krauschannel)==false.
if the Kraus channel is parametric, the cumprobabilities are wrapped in a Symbolics.Num object. To manipulate expressions use the Symbolics package.
See also probabilities, ismixedunitary.
Examples
julia> cumprobabilities(Depolarizing1(0.1))
4-element Vector{Symbolics.Num}:
0.9
0.9333333333333333
0.9666666666666667
1.0MimiqCircuitsBase.ismixedunitary — Method
ismixedunitary(krauschannel)Whether the quantum operation is a mixed unitary channel.
This is the case when all the Kraus operators $E_k$ are proportional to a unitary $U_k$, i.e. $\mathcal{E}(\rho) = \sum_k p_k U_k \rho U_k^\dagger$ with some probabilities $0\leq p_k \leq 1$ that add up to 1 and $U_k^\dagger U_k = I$.
Examples
julia> ismixedunitary(PauliX(0.1))
true
julia> ismixedunitary(AmplitudeDamping(0.1))
falseMimiqCircuitsBase.krausmatrices — Method
krausmatrices(krauschannel)Kraus matrices associated to the given Kraus channel.
A mixed unitary channel is written as $\sum_k p_k U_k \rho U_k^\dagger$, where $U_k$ are the unitary matrices returned by this function.
if the Kraus channel is parametric, the matrix elements are wrapped in a Symbolics.Num object. To manipulate expressions use the Symbolics package.
Examples
julia> krausmatrices(AmplitudeDamping(0.1))
2-element Vector{Matrix{Float64}}:
[1.0 0.0; 0.0 0.9486832980505138]
[0.0 0.31622776601683794; 0.0 0.0]For mixed unitary channels the Kraus matrices are the unitary matrices times the square root of the probabilities.
julia> krausmatrices(PauliX(0.2))
2-element Vector{Matrix{Symbolics.Num}}:
[sqrt(0.8) 0; 0 sqrt(0.8)]
[0 sqrt(0.2); sqrt(0.2) 0]MimiqCircuitsBase.krausoperators — Method
krausoperators(kraus)Kraus operators associated to the given Kraus channel.
See also krausmatrices.
Examples
julia> krausoperators(PauliX(0.2))
2-element Vector{Operator{1}}:
Operator(Real[0.8944271909999159 0; 0 0.8944271909999159])
Operator(Real[0 0.4472135954999579; 0.4472135954999579 0])
julia> krausoperators(AmplitudeDamping(0.1))
2-element Vector{AbstractOperator{1}}:
D(1, sqrt(0.9))
SigmaMinus(sqrt(0.1))MimiqCircuitsBase.probabilities — Method
probabilities(mixedunitarychannel)Probabilities of each Kraus operator for mixed unitary Kraus channels.
A mixed unitary channel is written as $\sum_k p_k U_k \rho U_k^\dagger$, where $p_k$ are the probabilities.
An error is returned for Kraus channels with ismixedunitary(krauschannel)==false.
if the Kraus channel is parametric, the probabilities are wrapped in a Symbolics.Num object. To manipulate expressions use the Symbolics package.
See also ismixedunitary, unitarymatrices, and krausmatrices.
Examples
julia> probabilities(PauliX(0.1))
2-element Vector{Symbolics.Num}:
0.9
0.1MimiqCircuitsBase.squaredkrausoperators — Method
squaredkrausoperators(kraus)Square of of Kraus operators ($O^\dagger O$) associated to the given Kraus channel.
See also krausoperators.
Examples
julia> squaredkrausoperators(AmplitudeDamping(0.1))
2-element Vector{AbstractOperator{1}}:
D(abs2(1), abs2(sqrt(0.9)))
P₁(abs2(sqrt(0.1)))MimiqCircuitsBase.unitarygates — Method
unitarygates(krauschannel)Unitary gates associated to the given mixed unitary Kraus channel.
A mixed unitary channel is written as $\sum_k p_k U_k \rho U_k^\dagger$, where $U_k$ are the unitary operators returned by this function.
An error is returned for Kraus channels with ismixedunitary(krauschannel)==false.
See also ismixedunitary, unitarymatrices, and krausmatrices.
Examples
julia> unitarygates(PauliNoise([0.9,0.1],["II","XX"]))
2-element Vector{PauliString{2}}:
II
XXMimiqCircuitsBase.unitarymatrices — Method
unitarymatrices(mixedunitarychannel)Unitary matrices associated to the given mixed unitary Kraus channel.
A mixed unitary channel is written as $\sum_k p_k U_k \rho U_k^\dagger$, where $U_k$ are the unitary matrices.
An error is returned for Kraus channels with ismixedunitary(krauschannel)==false.
if the Kraus channel is parametric, the matrix elements are wrapped in a Symbolics.Num object. To manipulate expressions use the Symbolics package.
See also ismixedunitary, probabilities, and krausmatrices.
Examples
julia> unitarymatrices(PauliX(0.2))
2-element Vector{Matrix}:
[1.0 -0.0; 0.0 1.0]
[0 1; 1 0]MimiqCircuitsBase.unwrappedcumprobabilities — Method
unwrappedcumprobabilities(mixedunitarychannel)Cumulative sum of probabilities associated to the specified mixed unitary Kraus channel without the Symbolics.Num wrapper.
See cumprobabilities for more information.
julia> unwrappedcumprobabilities(Depolarizing1(0.1))
4-element Vector{Float64}:
0.9
0.9333333333333333
0.9666666666666667
1.0MimiqCircuitsBase.unwrappedkrausmatrices — Method
unwrappedkrausmatrices(krauschannel)Returns the Kraus matrices associated to the specified Kraus channel without the Symbolics.Num wrapper.
See krausmatrices for more information.
Examples
julia> unwrappedkrausmatrices(AmplitudeDamping(0.1))
2-element Vector{Matrix{ComplexF64}}:
[1.0 + 0.0im 0.0 + 0.0im; 0.0 + 0.0im 0.9486832980505138 + 0.0im]
[0.0 + 0.0im 0.31622776601683794 + 0.0im; 0.0 + 0.0im 0.0 + 0.0im]MimiqCircuitsBase.unwrappedprobabilities — Method
unwrappedprobabilities(mixedunitarychannel)Probabilities associated to the specified mixed unitary Kraus channel without the Symbolics.Num wrapper.
See probabilities for more information.
julia> unwrappedprobabilities(PauliX(0.1))
2-element Vector{Float64}:
0.9
0.1MimiqCircuitsBase.unwrappedunitarymatrices — Method
unwrappedunitarymatrices(krauschannel)Returns the unitary Kraus matrices associated to the mixed unitary Kraus channel without the Symbolics.Num wrapper.
See unitarymatrices for more information.
Examples
julia> unwrappedunitarymatrices(PauliX(0.2))
2-element Vector{Matrix}:
[1.0 -0.0; 0.0 1.0]
[0 1; 1 0]MimiqCircuitsBase.Amplitude — Type
Amplitude(bs::BitString)Operation to get amplitude of a state vector element.
The operation gets the quantum state's amplitude (which is a complex number) corresponding to the state defined by the bitstring bs in the computational basis and stores it in a z-register.
See BitString.
Examples
When defining a circuit, only the z-register to store the result needs to be specified.
julia> Amplitude(BitString("001"))
Amplitude(bs"001")
julia> c = push!(Circuit(),Amplitude(BitString("001")), 1)
1-vars circuit with 1 instruction:
└── Amplitude(bs"001") @ z[1]
MimiqCircuitsBase.DiagonalOp — Type
DiagonalOp(a,b)One-qubit diagonal operator.
The corresponding matrix
\[\begin{pmatrix} a & 0\\ 0 & b \end{pmatrix}\]
is parametrized by complex numbers a and b.
See also Operator, Projector0, Projector1.
Examples
julia> DiagonalOp(1,0.5)
D(1, 0.5)
julia> push!(Circuit(), ExpectationValue(DiagonalOp(1,0.5)), 1, 2)
1-qubit, 2-vars circuit with 1 instruction:
└── ⟨D(1,0.5)⟩ @ q[1], z[2]MimiqCircuitsBase.AmplitudeDamping — Type
AmplitudeDamping(γ)One-qubit amplitude damping noise channel.
This channel is defined by the Kraus operators
\[E_1 = \begin{pmatrix} 1 & 0 \\ 0 & \sqrt{1-\gamma} \end{pmatrix} ,\quad E_2 = \begin{pmatrix} 0 & \sqrt{\gamma} \\ 0 & 0 \end{pmatrix},\]
where $\gamma \in [0,1]$.
Physically, it corresponds to an energy gain/loss process, such as spontaneous emission.
Examples
julia> push!(Circuit(), AmplitudeDamping(0.1), 1)
1-qubit circuit with 1 instruction:
└── AmplitudeDamping(0.1) @ q[1]MimiqCircuitsBase.GeneralizedAmplitudeDamping — Type
GeneralizedAmplitudeDamping(p,γ)One-qubit generalized amplitude damping noise channel.
This channel is defined by the Kraus operators
\[E_1 = \sqrt{p} \begin{pmatrix} 1 & 0 \\ 0 & \sqrt{1-\gamma} \end{pmatrix} ,\quad E_2 = \sqrt{p} \begin{pmatrix} 0 & \sqrt{\gamma} \\ 0 & 0 \end{pmatrix} ,\quad E_3 = \sqrt{1-p} \begin{pmatrix} \sqrt{1-\gamma} & 0 \\ 0 & 1 \end{pmatrix} ,\quad E_4 = \sqrt{1-p} \begin{pmatrix} 0 & 0 \\ \sqrt{\gamma} & 0 \end{pmatrix},\]
where $\gamma, p \in [0,1]$.
Physically, it corresponds to a combination of spontaneous emission and spontaneous absorption with probabilities $p$ and $1-p$, respectively.
Examples
julia> push!(Circuit(), GeneralizedAmplitudeDamping(0.1, 0.3), 1)
1-qubit circuit with 1 instruction:
└── GeneralizedAmplitudeDamping(0.1,0.3) @ q[1]MimiqCircuitsBase.PhaseAmplitudeDamping — Type
PhaseAmplitudeDamping(p,γ,β)One-qubit phase amplitude damping noise channel.
This channel is defined by:
\[\mathcal{E}(\rho) = \begin{pmatrix} (1-\gamma)\rho_{00}+\gamma p & (1-2\beta)\sqrt{1-\gamma}\rho_{01} \\ (1-2\beta)\sqrt{1-\gamma}\rho_{10} & (1-\gamma)\rho_{11} + (1-p)\gamma \end{pmatrix}\]
Here, $p, \gamma, \beta \in [0,1]$.
This channel is equivalent to a GeneralizedAmplitudeDamping(p,γ) channel (see GeneralizedAmplitudeDamping), followed by a PauliZ(β) channel (see PauliZ).
Use krausmatrices to see a Kraus matrix representation of the channel.
See also AmplitudeDamping, GeneralizedAmplitudeDamping, and ThermalNoise.
Examples
julia> push!(Circuit(), PhaseAmplitudeDamping(0.1, 0.2, 0.3), 1)
1-qubit circuit with 1 instruction:
└── PhaseAmplitudeDamping(0.1,0.2,0.3) @ q[1]MimiqCircuitsBase.ThermalNoise — Type
ThermalNoise(T₁, T₂, t, nₑ)One-qubit thermal noise channel.
The thermal noise channel is equivalent to the PhaseAmplitudeDamping channel, but it is parametrized instead as
\[\mathcal{E}(\rho) = \begin{pmatrix} e^{-\Gamma_1 t}\rho_{00}+(1-n_e)(1-e^{-\Gamma_1 t}) & e^{-\Gamma_2 t}\rho_{01} \\ e^{-\Gamma_2 t}\rho_{10} & e^{-\Gamma_1 t}\rho_{11} + n_e(1-e^{-\Gamma_1 t}) \end{pmatrix}\]
where $\Gamma_1=1/T_1$ and $\Gamma_2=1/T_2$, and the parameters must fulfill $T_1 \geq 0$, $T_2 \leq 2 T_1$, $t \geq 0$, and $0 \leq n_e \leq 1$.
These parameters can be related to the ones used to define the PhaseAmplitudeDamping channel through $p = 1-n_e$, $\gamma = 1-e^{-\Gamma_1 t}$, and $\beta = \frac{1}{2}(1-e^{-(\Gamma_2-\Gamma_1/2)t})$.
See also PhaseAmplitudeDamping, AmplitudeDamping, and GeneralizedAmplitudeDamping.
Arguments
T₁: Longitudinal relaxation rate.T₂: Transversal relaxation rate.t: Time duration of gate.nₑ: Excitation fraction when in thermal equilibrium with the environment.
Examples
julia> push!(Circuit(), ThermalNoise(0.5, 0.6, 1.2, 0.3), 1)
1-qubit circuit with 1 instruction:
└── ThermalNoise(0.5,0.6,1.2,0.3) @ q[1]MimiqCircuitsBase.Depolarizing — Type
Depolarizing(N,p)$N$ qubit depolarizing noise channel.
The Kraus operators for the depolarizing channel are given by
\[E_1 = \sqrt{1-p} I_N, \quad E_i = \sqrt{p/(4^N-1)} P_i\]
where $p\in [0,1]$ is a probability, and $P_i` is an$N$-qubit Pauli string operator, i.e. a tensor product of one-qubit Pauli operators (see [`Paulistring`](@ref)). There is exactly one Kraus operator$E{i>1}$for each distinct combination of Pauli operators$Pi$, except for the$N$-qubit identity$I_N = I\otimes I \otimes I \otimes...``
For example, for one qubit we have 3 operators $P_i \in \{X,Y,Z\}$, and for two qubits we have 15 operators $P_i \in \{ I\otimes X, I\otimes Y, I\otimes Z, X\otimes I, Y\otimes I, Z\otimes I, X\otimes X, X\otimes Y, X\otimes Z, Y\otimes X, Y\otimes Y, Y\otimes Z, Z\otimes X, Z\otimes Y, Z\otimes Z \}$. Use unitarygates to see this.
This channel is a mixed unitary channel, see ismixedunitary, and is a special case of PauliNoise.
See also PauliString and PauliNoise.
Arguments
N: Number of qubits.p: Probability of error, i.e. of not applying identity.
Examples
Depolarizing channels can be defined for any $N$:
julia> push!(Circuit(), Depolarizing(1, 0.1), 1)
1-qubit circuit with 1 instruction:
└── Depolarizing(1,0.1) @ q[1]
julia> push!(Circuit(), Depolarizing(5, 0.1), 1, 2, 3, 4, 5)
5-qubit circuit with 1 instruction:
└── Depolarizing(5,0.1) @ q[1:5]For one and two qubits you can use the shorthand notation:
julia> push!(Circuit(), Depolarizing1(0.1), 1)
1-qubit circuit with 1 instruction:
└── Depolarizing(1,0.1) @ q[1]
julia> push!(Circuit(), Depolarizing2(0.1), 1, 2)
2-qubit circuit with 1 instruction:
└── Depolarizing(2,0.1) @ q[1:2]MimiqCircuitsBase.Depolarizing1 — Type
Depolarizing1(p) Doc TODOMimiqCircuitsBase.Depolarizing2 — Type
Depolarizing2(p) Doc TODOMimiqCircuitsBase.PauliNoise — Type
PauliNoise(p, paulistrings)$N$ qubit Pauli noise channel specified by a list of probabilities and Pauli gates.
A Pauli channel is defined by
\[\mathcal{E}(\rho) = \sum_k p_k P_k \rho P_k,\]
where $0 \leq p_k \leq 1$ and $P_k$ are Pauli string operators, defined as tensor products of one-qubit Pauli operators (see PauliString) The probabilities must fulfill $\sum_k p_k = 1$.
This channel is a mixed unitary channel, see ismixedunitary.
See also Depolarizing, PauliX, PauliY, PauliZ, which are special cases of PauliNoise.
Arguments
p: Vector of probabilities that must add up to 1.paulistrings: Vector of strings, each one of length $N$ and with each character being either"I","X","Y", or"Z". The number of qubits is equal to $N$.
The vectors p and paulistrings must have the same length.
Examples
PauliNoise channels can be defined for any number of qubits, and for any number of Pauli strings.
julia> push!(Circuit(), PauliNoise([0.8, 0.1, 0.1], ["I","X","Y"]), 1)
1-qubit circuit with 1 instruction:
└── PauliNoise(...) @ q[1]
julia> push!(Circuit(), PauliNoise([0.9, 0.1], ["XY","II"]), 1, 2)
2-qubit circuit with 1 instruction:
└── PauliNoise(...) @ q[1:2]
julia> push!(Circuit(), PauliNoise([0.5, 0.2, 0.2, 0.1], ["IXIX","XYXY","ZZZZ","IXYZ"]), 1, 2, 3, 4)
4-qubit circuit with 1 instruction:
└── PauliNoise(...) @ q[1:4]MimiqCircuitsBase.PauliX — Type
PauliX(p)One-qubit Pauli X noise channel (bit flip error).
This channel is defined by the Kraus operators
\[E_1 = \sqrt{1-p}\,I, \quad E_2 = \sqrt{p}\,X,\]
where $0 \leq p \leq 1$.
This channel is a mixed unitary channel, see ismixedunitary, and is a special case of PauliNoise.
PauliX(p) is the same as PauliNoise([1-p,p],["I","X"]).
Examples
julia> push!(Circuit(), PauliX(0.1), 1)
1-qubit circuit with 1 instruction:
└── PauliX(0.1) @ q[1]MimiqCircuitsBase.PauliY — Type
PauliY(p)One-qubit Pauli Y noise channel (bit-phase flip error).
This channel is determined by the Kraus operators
\[E_1 = \\sqrt{1-p}\,I, \\quad E_2 = \sqrt{p}\,Y,\]
where $0\\leq p \\leq 1$.
This channel is a mixed unitary channel, see ismixedunitary, and is a special case of PauliNoise.
PauliY(p) is the same as PauliNoise([1-p,p],["I","Y"]).
Examples
julia> push!(Circuit(), PauliY(0.1), 1)
1-qubit circuit with 1 instruction:
└── PauliY(0.1) @ q[1]MimiqCircuitsBase.PauliZ — Type
PauliZ(p)One-qubit Pauli Z noise channel (phase flip error).
This channel is determined by the Kraus operators
\[E_1 = \sqrt{1-p}\,I, \quad E_2 = \sqrt{p}\,Z,\]
where $0 \leq p \leq 1$.
This channel is a mixed unitary channel, see ismixedunitary, and is a special case of PauliNoise.
PauliZ(p) is the same as PauliNoise([1-p,p],["I","Z"]).
Examples
julia> push!(Circuit(), PauliZ(0.1), 1)
1-qubit circuit with 1 instruction:
└── PauliZ(0.1) @ q[1]MimiqCircuitsBase.MixedUnitary — Type
MixedUnitary(p,U)Custom $N$ qubit mixed unitary channel specified by a list of unitary gates and a list of probabilities that add up to 1.
A mixed unitary noise channel is defined by
\[\mathcal{E}(\rho) = \sum_k p_k U_k \rho U_k^\dagger,\]
where $0\leq p_k \leq 1$ and $U_k$ are unitary matrices. The probabilities must fulfill $\sum_k p_k = 1$.
If your Kraus matrices are not all proportional to unitaries, use Kraus instead.
The Kraus matrices are defined in the computational basis in the usual textbook order (the first qubit corresponds to the left-most qubit). For 1 qubit we have $|0\rangle$, $|1\rangle$. For 2 qubits we have $|00\rangle$, $|01\rangle$, $|10\rangle$, $|11\rangle$. See also GateCustom.
See also Kraus, ismixedunitary, AbstractKrausChannel, and RescaledGate.
Arguments
p: Vector of probabilities, must be positive real numbers and add up to 1.U: Vector of either complex-valued $2^N \times 2^N$ matrices or unitary gates acting on $N$ qubits. Both can be mixed.
The length of the vectors p and U must be equal.
Examples
julia> push!(Circuit(), MixedUnitary([0.9, 0.1], [[1 0; 0 1], [0 1; 1 0]]), 1)
1-qubit circuit with 1 instruction:
└── MixedUnitary((0.9,Custom([1.0 0.0; 0.0 1.0])),(0.1,Custom([0.0 1.0; 1.0 0.0]))) @ q[1]
julia> push!(Circuit(), MixedUnitary([0.8, 0.2], [GateID(), GateRX(0.2)]), 1)
1-qubit circuit with 1 instruction:
└── MixedUnitary((0.8,ID),(0.2,RX(0.2))) @ q[1]
julia> push!(Circuit(), MixedUnitary([0.8, 0.2], [[1 0; 0 1], GateRX(0.2)]), 1)
1-qubit circuit with 1 instruction:
└── MixedUnitary((0.8,Custom([1.0 0.0; 0.0 1.0])),(0.2,RX(0.2))) @ q[1]
julia> @variables x
1-element Vector{Symbolics.Num}:
x
julia> g= MixedUnitary([0.9, x], [[1 0; 0 1], [0 1; 1 0]])
MixedUnitary((0.9, Custom([1.0 0.0; 0.0 1.0])), (x, Custom([0.0 1.0; 1.0 0.0])))
julia> evaluate(g,Dict(x=>.1))
MixedUnitary((0.9, Custom([1.0 0.0; 0.0 1.0])), (0.1, Custom([0.0 1.0; 1.0 0.0])))
julia> g= MixedUnitary([0.9, 0.1], [[1 0; 0 1], [0 1; 1 x]])
ERROR: MimiqCircuitsBase.UndefinedValue(x)
Stacktrace:
[1] unwrapvalue(g::Symbolics.Num)
@ MimiqCircuitsBase ~/QPerfect/Code/MimiqCircuitsBase.jl/src/utils.jl:159
[2] _broadcast_getindex_evalf
@ ./broadcast.jl:699 [inlined]
[3] _broadcast_getindex
@ ./broadcast.jl:672 [inlined]
[4] _getindex
@ ./broadcast.jl:620 [inlined]
[5] getindex
@ ./broadcast.jl:616 [inlined]
[6] copyto_nonleaf!(dest::Matrix{Int64}, bc::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{2}, Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}}, typeof(MimiqCircuitsBase.unwrapvalue), Tuple{Base.Broadcast.Extruded{Matrix{Symbolics.Num}, Tuple{Bool, Bool}, Tuple{Int64, Int64}}}}, iter::CartesianIndices{2, Tuple{Base.OneTo{Int64}, Base.OneTo{Int64}}}, state::CartesianIndex{2}, count::Int64)
@ Base.Broadcast ./broadcast.jl:1104
[7] copy
@ ./broadcast.jl:941 [inlined]
[8] materialize
@ ./broadcast.jl:894 [inlined]
[9] GateCustom{1}(U::Matrix{Symbolics.Num})
@ MimiqCircuitsBase ~/QPerfect/Code/MimiqCircuitsBase.jl/src/operations/gates/custom.jl:106
[10] GateCustom(U::Matrix{Symbolics.Num})
@ MimiqCircuitsBase ~/QPerfect/Code/MimiqCircuitsBase.jl/src/operations/gates/custom.jl:123
[11] #MixedUnitary##2
@ ~/QPerfect/Code/MimiqCircuitsBase.jl/src/operations/noisechannels/mixedunitary.jl:205 [inlined]
[12] iterate
@ ./generator.jl:48 [inlined]
[13] collect_to!(dest::Vector{GateCustom{1}}, itr::Base.Generator{Vector{Matrix{Symbolics.Num}}, MimiqCircuitsBase.var"#MixedUnitary##2#MixedUnitary##3"}, offs::Int64, st::Int64)
@ Base ./array.jl:848
[14] collect_to_with_first!(dest::Vector{GateCustom{1}}, v1::GateCustom{1}, itr::Base.Generator{Vector{Matrix{Symbolics.Num}}, MimiqCircuitsBase.var"#MixedUnitary##2#MixedUnitary##3"}, st::Int64)
@ Base ./array.jl:826
[15] _collect(c::Vector{Matrix{Symbolics.Num}}, itr::Base.Generator{Vector{Matrix{Symbolics.Num}}, MimiqCircuitsBase.var"#MixedUnitary##2#MixedUnitary##3"}, ::Base.EltypeUnknown, isz::Base.HasShape{1})
@ Base ./array.jl:820
[16] collect_similar
@ ./array.jl:732 [inlined]
[17] map
@ ./abstractarray.jl:3372 [inlined]
[18] MixedUnitary(p::Vector{Float64}, U::Vector{Matrix{Symbolics.Num}})
@ MimiqCircuitsBase ~/QPerfect/Code/MimiqCircuitsBase.jl/src/operations/noisechannels/mixedunitary.jl:201
[19] top-level scope
@ none:1
julia> evaluate(g,Dict(x=>0))
ERROR: ArgumentError: Probabilities should sum to 1. Instead they are 0.9
Stacktrace:
[1] MixedUnitary{1}(p::Vector{Symbolics.Num}, U::Vector{GateCustom{1}})
@ MimiqCircuitsBase ~/QPerfect/Code/MimiqCircuitsBase.jl/src/operations/noisechannels/mixedunitary.jl:160
[2] MixedUnitary(p::Vector{Symbolics.Num}, U::Vector{GateCustom{1}})
@ MimiqCircuitsBase ~/QPerfect/Code/MimiqCircuitsBase.jl/src/operations/noisechannels/mixedunitary.jl:193
[3] evaluate(m::MixedUnitary{1}, d::Dict{Symbolics.Num, Int64})
@ MimiqCircuitsBase ~/QPerfect/Code/MimiqCircuitsBase.jl/src/operations/noisechannels/mixedunitary.jl:180
[4] top-level scope
@ none:1MimiqCircuitsBase.ReadoutErr — Type
ReadoutErr(p0, p1)
ReadoutErr(confusionmatrix)Represents a classical readout error applied immediately after a measurement. Can be initialized either from a 2×2 confusion matrix or from the error probabilities $p0$ and $p1$.
The error is defined by a 2×2 confusion matrix:
\[\begin{pmatrix}{cc} P(\text{report} 0 | \text{true} 0) & P(\text{report} 1 | \text{true} 0)=P_0\\ P(\text{report} 0 | \text{true} 1) = P_1 & P(\text{report} 1 | {true} 1) \end{pmatrix}\]
Each row corresponds to the true quantum outcome (0 in the first row, 1 in the second row) while each column corresponds to the reported classical outcome after noise. Each entry at position $(i,j)$ gives the probability of reporting $j$when the true outcome wasi`.