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#4"(), var"#5#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
julia> @variables a
1-element Vector{Symbolics.Num}:
a
julia> rule = ExactOperationInstanceQubitNoise(GateRX(a), [1], AmplitudeDamping(a / π))
ExactOperationInstanceQubitNoise(GateRX(a), [1], AmplitudeDamping(a / π), false, false)
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))
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
julia> rule = GlobalReadoutNoise(ReadoutErr(0.01, 0.02))
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([
OperationInstanceNoise(GateRX(θ), Depolarizing1(θ / π)),
OperationInstanceNoise(GateRY(θ), Depolarizing1(θ / π))])
NoiseModel(AbstractNoiseRule[OperationInstanceNoise(GateRX(θ), Depolarizing(1, θ / π), false, false), OperationInstanceNoise(GateRY(θ), Depolarizing(1, θ / π), false, false)], "") Using symbolic parameters with complex expressions
julia> @variables α β
2-element Vector{Symbolics.Num}:
α
β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
julia> rule = OperationInstanceNoise(GateRX(a), Depolarizing1(a / π))
OperationInstanceNoise(GateRX(a), Depolarizing(1, a / π), false, false)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
julia> SetIdleQubitNoise(AmplitudeDamping(0.0001), [1,2,3])
SetIdleQubitNoise(AmplitudeDamping(0.0001), Set([2, 3, 1]))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
Match GateRX on any qubits in {1, 2, 3} with angle-dependent noise:
julia> @variables a
1-element Vector{Symbolics.Num}:
a
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)
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[], "")
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
Apply noise to all RX operations with noise strength proportional to the angle. At evaluation time, e.g. GateRX(0.4) is followed by Depolarizing1(0.4 / π) ≈ Depolarizing1(0.127), and GateRX(1.2) by Depolarizing1(1.2 / π) ≈ Depolarizing1(0.382).
julia> @variables θ;
julia> model = NoiseModel();
julia> add_operation_noise!(model, GateRX(θ), Depolarizing1(θ / π))
NoiseModel(AbstractNoiseRule[OperationInstanceNoise(GateRX(θ), Depolarizing(1, θ / π), false, false)], "")Symbolic multi-parameter operations
julia> @variables α β
2-element Vector{Symbolics.Num}:
α
β
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
Match the operation when applied to any of the listed qubits:
julia> @variables θ;
julia> model = NoiseModel();
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)], "")Match only on an exact qubit order:
julia> @variables θ;
julia> model = NoiseModel();
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
Add a global readout-noise rule:
julia> model = NoiseModel();
julia> add_readout_noise!(model, ReadoutErr(0.01, 0.02))
NoiseModel(AbstractNoiseRule[GlobalReadoutNoise(ReadoutErr(0.01, 0.02))], "")Add readout noise on a specific set of qubits:
julia> model = NoiseModel();
julia> add_readout_noise!(model, ReadoutErr(0.03, 0.04), qubits=[1, 3])
NoiseModel(AbstractNoiseRule[SetQubitReadoutNoise(Set([3, 1]), ReadoutErr(0.03, 0.04))], "")Add readout noise for an exact qubit order:
julia> model = NoiseModel();
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))], "")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]Each RX(α) is followed by Depolarizing1(α / π) (so ≈ 0.127 for α=0.4 and ≈ 0.255 for α=0.8), and each Measure is followed by ReadoutErr(0.01, 0.02).
Recursive wrapper behavior (Block, GateCall, Parallel, Repeat, IfStatement)
Block names are randomly generated and may differ across runs; the decompose_step output is what we actually verify here.
Block
julia> model = NoiseModel([OperationInstanceNoise(GateH(), AmplitudeDamping(0.01))]);
julia> c_block = push!(Circuit(), Block(1, 0, 0, [Instruction(GateH(), (1,), (), ())]), 1);
julia> apply_noise_model(c_block, model) |> decompose_step
1-qubit circuit with 2 instructions:
├── H @ q[1]
└── AmplitudeDamping(0.01) @ q[1]GateCall
julia> model = NoiseModel([OperationInstanceNoise(GateH(), AmplitudeDamping(0.01))]);
julia> decl = GateDecl(:local_h, (), [Instruction(GateH(), (1,), (), ())]);
julia> c_gatecall = push!(Circuit(), GateCall(decl), 1);
julia> apply_noise_model(c_gatecall, model) |> decompose_step
1-qubit circuit with 2 instructions:
├── H @ q[1]
└── AmplitudeDamping(0.01) @ q[1]Parallel
julia> model = NoiseModel([OperationInstanceNoise(GateH(), AmplitudeDamping(0.01))]);
julia> c_parallel = push!(Circuit(), Parallel(2, GateH()), 1, 2);
julia> apply_noise_model(c_parallel, model) |> 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> model = NoiseModel([OperationInstanceNoise(GateH(), AmplitudeDamping(0.01))]);
julia> c_repeat = push!(Circuit(), Repeat(2, GateH()), 1);
julia> apply_noise_model(c_repeat, model) |> 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> model = NoiseModel([OperationInstanceNoise(GateH(), AmplitudeDamping(0.01))]);
julia> c_if = push!(Circuit(), IfStatement(GateH(), BitString("1")), 1, 1);
julia> apply_noise_model(c_if, model) |> 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.describe — Method
describe(model::NoiseModel)Print a human-readable description of the noise model.
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.
A Kraus channel becomes loss-aware simply by including one or more LossyOperator branches in E; in that case hasloss returns true and lossoperators / survivaloperators / losseffect describe its leakage structure.
See also MixedUnitary, AbstractKrausChannel, LossyOperator.
Arguments
E: Vector of $2^N \times 2^N$ complex matrices or $N$ qubit operators. Both can be mixed. IncludingLossyOperatorbranches makes the channel loss-aware.
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([0.0 0.316228; 0.0 0.0]))MimiqCircuitsBase.hasloss — Method
hasloss(kraus)Return true if kraus contains any LossyOperator branch.
MimiqCircuitsBase.losseffect — Method
losseffect(kraus)Compute the loss-probability operator $\Lambda = \sum_k L_k^\dagger L_k$ over all LossyOperator-tagged branches $L_k$ of kraus.
$\Lambda$ is a positive semidefinite matrix whose diagonal entries are the loss probabilities from each computational basis state: $\langle i|\Lambda|i\rangle$ is the probability that state $|i\rangle$ leaks out of the computational subspace. For a general state $|\psi\rangle$, the total loss probability is $\langle\psi|\Lambda|\psi\rangle$.
Note that $\Lambda$ contains probabilities, not amplitudes. The LossyOperator matrices hold amplitudes, so their entries are the square roots of the corresponding loss probabilities (e.g. an amplitude of $\sqrt{0.1}$ gives a loss probability of $0.1$).
The survival and lossy operators together satisfy $\sum_k S_k^\dagger S_k + \Lambda = I$, where $S_k$ are the non-lossy branches.
If the channel has no lossy operators, the zero matrix is returned.
See also Kraus, lossoperators, survivaloperators.
Examples
julia> g = Kraus([[1 0; 0 sqrt(0.9)], LossyOperator([0 sqrt(0.1); 0 0])]);
julia> losseffect(g)
1-qubit Operator:
├── 0.0 0.0
└── 0.0 0.1The result shows that $|0\rangle$ has zero loss probability and $|1\rangle$ has 10% loss probability. For a state $\alpha|0\rangle + \beta|1\rangle$ the total loss probability is $0.1|\beta|^2$.
MimiqCircuitsBase.lossoperators — Method
lossoperators(kraus)Return the LossyOperator branches of a Kraus channel.
MimiqCircuitsBase.survivaloperators — Method
survivaloperators(kraus)Return the non-LossyOperator branches of a Kraus channel.
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 0]])
MixedUnitary((0.9, Custom([1.0 0.0; 0.0 1.0])), (0.1, Custom([0.0 1.0; 1.0 0.0])))
julia> evaluate(g,Dict(x=>0))
MixedUnitary((0.9, Custom([1.0 0.0; 0.0 1.0])), (0.1, Custom([0.0 1.0; 1.0 0.0])))MimiqCircuitsBase.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`.