Noise

MimiqCircuitsBase.AbstractNoiseRuleType
AbstractNoiseRule

Abstract 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.

source
MimiqCircuitsBase.CustomNoiseRuleType
CustomNoiseRule <: AbstractNoiseRule

Apply noise based on a custom matching function.

Fields

  • matcher::Function: Function (inst) -> Bool
  • generator::Function: Function (inst) -> Instruction
  • priority_val::Int: Priority for this rule (default: 0)
  • before::Bool: If true, apply noise before the matched instruction
  • replace::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)
source
MimiqCircuitsBase.ExactOperationInstanceQubitNoiseType
ExactOperationInstanceQubitNoise <: AbstractNoiseRule

Apply 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)
source
MimiqCircuitsBase.ExactQubitReadoutNoiseType
ExactQubitReadoutNoise <: AbstractNoiseRule

Apply 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))
source
MimiqCircuitsBase.GlobalReadoutNoiseType
GlobalReadoutNoise <: AbstractNoiseRule

Apply 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]))
source
MimiqCircuitsBase.IdleNoiseType
IdleNoise <: AbstractNoiseRule

Apply 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 relation time_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)))
source
MimiqCircuitsBase.NoiseModelType
NoiseModel

A collection of noise rules that define how noise is applied to a quantum circuit.

Fields

  • rules::Vector{AbstractNoiseRule}: List of noise rules in the model
  • name::String: Optional name for the noise model

Priority Order (lower number = higher priority)

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")
source
MimiqCircuitsBase.OperationInstanceNoiseType
OperationInstanceNoise <: AbstractNoiseRule

Apply 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)
source
MimiqCircuitsBase.SetIdleQubitNoiseType
SetIdleQubitNoise <: AbstractNoiseRule

Apply 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 relation time_var => noise(time_var) or constant noise
  • qubits::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]))
source
MimiqCircuitsBase.SetOperationInstanceQubitNoiseType
SetOperationInstanceQubitNoise <: AbstractNoiseRule

Apply 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 applied
  • 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
# 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)
source
MimiqCircuitsBase.SetQubitReadoutNoiseType
SetQubitReadoutNoise <: AbstractNoiseRule

Apply readout noise to measurements if all qubits are in the specified set.

Fields

  • qubits::Set{Int}: Set of qubit indices where noise should be applied
  • noise::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))
source
MimiqCircuitsBase.add_idle_noise!Method
add_idle_noise!(model, noise[; qubits=nothing])

Add idle noise to a noise model.

Arguments

  • model: The NoiseModel to which the rule will be added
  • noise: Either a constant noise operation or a relation time_var => noise(time_var)
  • qubits: (Optional) A collection of qubit indices to restrict the noise to
source
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:

  1. A concrete operation (e.g., GateRX(π/2) or Reset()) - matches only that exact operation
  2. A symbolic operation (e.g., GateRX(a) where a is a symbolic variable) - matches any operation of that type and substitutes parameter values into the noise expression

Arguments

  • model: The NoiseModel to which the rule will be added
  • operation: The operation instance to target (concrete or symbolic)
  • noise: The noise to apply (may contain symbolic expressions using variables from operation)
  • qubits: (Optional) A collection of qubit indices to restrict the noise to
  • exact: (Optional) If true and qubits is provided, the noise will only apply to operations on the exact sequence of qubits. Defaults to false
  • before: (Optional) If true, the noise is applied before the matched operation. Defaults to false
  • replace: (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 applied

Symbolic 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)], "")
source
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: The NoiseModel to which the rule will be added
  • noise: The ReadoutErr to apply
  • qubits: (Optional) A collection of qubit indices. If not provided, the noise is global
  • exact: (Optional) If true and qubits is provided, the noise will only apply to measurements on the exact sequence of qubits. Defaults to false

Behavior

  • If qubits is nothing, a GlobalReadoutNoise rule is added
  • If qubits is provided and exact is false, a SetQubitReadoutNoise rule is added
  • If qubits is provided and exact is true, an ExactQubitReadoutNoise rule 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))], "")
source
MimiqCircuitsBase.add_rule!Method
add_rule!(model::NoiseModel, rule::AbstractNoiseRule)

Add a new rule to the noise model. Rules are automatically sorted by priority.

source
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 modify
  • model: The noise model to apply

Returns

The modified circuit

source
MimiqCircuitsBase.apply_noise_modelMethod
apply_noise_model(circuit::Circuit, model::NoiseModel) -> Circuit

Apply 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 circuit
  • model: 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]
source
MimiqCircuitsBase.apply_ruleMethod
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 apply
  • inst: The instruction that the rule should be applied to

Returns

  • A new Instruction representing the noise to be added, or
  • nothing if the rule does not match the instruction
source
MimiqCircuitsBase.beforeMethod
before(rule::AbstractNoiseRule) -> Bool

Return whether the noise should be applied before the operation. Default is false (apply after). Override for specific rule types.

source
MimiqCircuitsBase.matchesMethod
matches(rule::AbstractNoiseRule, inst::Instruction) -> Bool

Check if a noise rule matches a given instruction.

Arguments

  • rule: The noise rule to check
  • inst: The instruction to potentially add noise to

Returns

true if the rule applies to this instruction, false otherwise

source
MimiqCircuitsBase.priorityMethod
priority(rule::AbstractNoiseRule) -> Int

Return the priority of a noise rule. Lower numbers have higher priority. Default priority is 100. Override this method to change rule priority.

source
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.

Warn

This function is maintained for backward compatibility. Consider using decorate! for more general decoration needs.

Arguments

  • c: Circuit to modify
  • g: Operation to which noise will be added
  • noise: Kraus channel or gate that will be added to each operation g
  • before: If true, add noise before the operation (default: false)
  • parallel: If true, 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]
source
MimiqCircuitsBase.add_noiseMethod
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.

Warn

This function is maintained for backward compatibility. Consider using decorate for more general decoration needs.

See add_noise! for more information.

source
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 modify
  • matcher: A function that takes an instruction and returns true if it should be decorated
  • generator: A function that takes a matched instruction and returns a new instruction to add
  • before: If true, add decorations before matched instructions (default: false)
  • parallel: If true, 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]
source
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.

source
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.

source
MimiqCircuitsBase.decorateMethod
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 returns true if it should be decorated
  • generator: A function that takes a matched instruction and returns a new instruction to add
  • before: If true, add decorations before matched instructions (default: false)
  • parallel: If true, 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]
source
MimiqCircuitsBase.decorateMethod
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 decorate
  • decoration: The operation to add as decoration
  • before: If true, add decorations before target operations (default: false)
  • parallel: If true, 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]
source
MimiqCircuitsBase.decorateMethod
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 decorate
  • decoration: The operation to add as decoration
  • before: If true, add decorations before target operations (default: false)
  • parallel: If true, 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]
source
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 modify
  • matcher: A function that takes an instruction and returns true if it should be decorated
  • generator: A function that takes a matched instruction and returns a new instruction to add
  • before: If true, 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]
source
MimiqCircuitsBase.decorate_on_match_parallelMethod
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 returns true if it should be decorated
  • generator: A function that takes a matched instruction and returns a new instruction to add
  • before: If true, 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]
source
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 modify
  • matcher: A function that takes an instruction and returns true if it should be decorated
  • generator: A function that takes a matched instruction and returns a new instruction to add
  • before: If true, 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]
source
MimiqCircuitsBase.decorate_on_match_singleMethod
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 returns true if it should be decorated
  • generator: A function that takes a matched instruction and returns a new instruction to add
  • before: If true, 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]
source
MimiqCircuitsBase.sample_mixedunitariesMethod
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 parameter ids decides 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]
source
MimiqCircuitsBase.ProjectiveNoiseFunction
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]
source
MimiqCircuitsBase.KrausType
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.

Note

Currently only 1 and 2-qubit custom Kraus channels are supported.

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]))
source
MimiqCircuitsBase.AbstractKrausChannelType
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.

source
MimiqCircuitsBase.cumprobabilitiesMethod
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.

Note

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.0
source
MimiqCircuitsBase.ismixedunitaryMethod
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))
false
source
MimiqCircuitsBase.krausmatricesMethod
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.

Note

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]
source
MimiqCircuitsBase.krausoperatorsMethod
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))
source
MimiqCircuitsBase.probabilitiesMethod
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.

Note

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.1
source
MimiqCircuitsBase.squaredkrausoperatorsMethod
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)))
source
MimiqCircuitsBase.unitarygatesMethod
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
 XX
source
MimiqCircuitsBase.unitarymatricesMethod
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.

Note

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]
source
MimiqCircuitsBase.unwrappedcumprobabilitiesMethod
unwrappedcumprobabilities(mixedunitarychannel)

Cumulative sum of probabilities associated to the specified mixed unitary Kraus channel without the Symbolics.Num wrapper.

Note

If any of the noise channel's parameters is symbolic, an error is thrown.

See cumprobabilities for more information.

julia> unwrappedcumprobabilities(Depolarizing1(0.1))
4-element Vector{Float64}:
 0.9
 0.9333333333333333
 0.9666666666666667
 1.0
source
MimiqCircuitsBase.unwrappedkrausmatricesMethod
unwrappedkrausmatrices(krauschannel)

Returns the Kraus matrices associated to the specified Kraus channel without the Symbolics.Num wrapper.

Note

If any of the noise channel's parameters is symbolic, an error is thrown.

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]
source
MimiqCircuitsBase.unwrappedprobabilitiesMethod
unwrappedprobabilities(mixedunitarychannel)

Probabilities associated to the specified mixed unitary Kraus channel without the Symbolics.Num wrapper.

Note

If any of the noise channel's parameters is symbolic, an error is thrown.

See probabilities for more information.

julia> unwrappedprobabilities(PauliX(0.1))
2-element Vector{Float64}:
 0.9
 0.1
source
MimiqCircuitsBase.unwrappedunitarymatricesMethod
unwrappedunitarymatrices(krauschannel)

Returns the unitary Kraus matrices associated to the mixed unitary Kraus channel without the Symbolics.Num wrapper.

Note

If any of the noise channel's parameters is symbolic, an error is thrown.

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]
source
MimiqCircuitsBase.AmplitudeType
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]
source
MimiqCircuitsBase.DiagonalOpType
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]
source
MimiqCircuitsBase.AmplitudeDampingType
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]
source
MimiqCircuitsBase.GeneralizedAmplitudeDampingType
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]
source
MimiqCircuitsBase.PhaseAmplitudeDampingType
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]
source
MimiqCircuitsBase.ThermalNoiseType
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]
source
MimiqCircuitsBase.DepolarizingType
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]
source
MimiqCircuitsBase.PauliNoiseType
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]
source
MimiqCircuitsBase.PauliXType
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]
source
MimiqCircuitsBase.PauliYType
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]
source
MimiqCircuitsBase.PauliZType
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]
source
MimiqCircuitsBase.MixedUnitaryType
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.

Note

Currently only 1 and 2-qubit custom MixedUnitary channels are supported.

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:1
source
MimiqCircuitsBase.ReadoutErrType
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`.

source