mimiqcircuits.lossmodel

Loss model definitions and qubit-loss sampling.

This module provides the rule system used by sample_losses() when a circuit instruction touches both lost and surviving qubits. A LossModel lets users decide whether such an instruction should be dropped, replaced, decorated, or handled by custom logic.

Examples

>>> from mimiqcircuits import *
>>> circuit = Circuit()
>>> circuit.push(QubitLoss(), 1)
2-qubit circuit with 1 instruction:
└── QubitLoss @ q[1]

>>> circuit.push(GateCX(), 0, 1)
2-qubit circuit with 2 instructions:
├── QubitLoss @ q[1]
└── CX @ q[0], q[1]

>>> model = LossModel().add_replace(GateCX(), Depolarizing1(0.2))
>>> sample_losses(circuit, lossmodel=model)
2-qubit circuit with 2 instructions:
├── QubitLoss @ q[1]
└── Depolarizing(0.2) @ q[0]

Functions

sample_losses(circuit[, rng, lossmodel])

Sample qubit-loss events in a circuit and apply a loss model.

Classes

CustomRule(matcher, generator)

User-defined loss rule.

DecorateRule(operation[, decoration, before])

Add a decoration before or after a matched instruction.

DropRule([operation])

Drop matched instructions that touch lost qubits.

LossModel([rules, name])

Collection of prioritized loss rules.

ReplaceRule(operation[, replacement])

Replace a matched instruction with new instructions.

class mimiqcircuits.lossmodel.AbstractCircuitRule[source]

Bases: object

Shared base class for circuit transformation rules.

priority()[source]
before()[source]
replaces()[source]
matches(inst)[source]
apply_rule(inst)[source]
class mimiqcircuits.lossmodel.DropRule(operation=None)[source]

Bases: AbstractCircuitRule

Drop matched instructions that touch lost qubits.

A DropRule is useful when a partially affected operation should not be salvaged. If operation is omitted, the rule matches any operation that reaches the loss model.

Parameters:

operation (optional) – Operation pattern to drop. If omitted, the rule is a catch-all rule.

Examples

>>> from mimiqcircuits import *
>>> model = LossModel().add_drop(GateCX())
>>> model
LossModel (unnamed, 1 rules)
└── DropRule(CX)
>>> circuit = Circuit()
>>> circuit.push(QubitLoss(), 1)
2-qubit circuit with 1 instruction:
└── QubitLoss @ q[1]

>>> circuit.push(GateCX(), 0, 1)
2-qubit circuit with 2 instructions:
├── QubitLoss @ q[1]
└── CX @ q[0], q[1]

>>> circuit.sample_losses(lossmodel=model)
2-qubit circuit with 1 instruction:
└── QubitLoss @ q[1]
__init__(operation=None)[source]
priority()[source]
matches(inst)[source]
replaces()[source]
apply_rule(inst)[source]
class mimiqcircuits.lossmodel.DecorateRule(operation, decoration=None, *, before=False)[source]

Bases: AbstractCircuitRule

Add a decoration before or after a matched instruction.

During loss sampling, any generated instruction that still touches a lost qubit is filtered out. This makes DecorateRule useful for modeling a side effect on surviving qubits when an operation was attempted but one of its qubits was missing.

Parameters:
  • operation – Operation pattern to match.

  • decoration – Operation or instruction sequence to add.

  • before (bool) – If True, add the decoration before the matched instruction. Otherwise, add it after.

Examples

>>> from mimiqcircuits import *
>>> model = LossModel().add_decorate(GateCZ(), Depolarizing1(0.01), before=True)
>>> model
LossModel (unnamed, 1 rules)
└── DecorateRule(CZ, Depolarizing(0.01), before)
>>> circuit = Circuit()
>>> circuit.push(QubitLoss(), 1)
2-qubit circuit with 1 instruction:
└── QubitLoss @ q[1]

>>> circuit.push(GateCZ(), 0, 1)
2-qubit circuit with 2 instructions:
├── QubitLoss @ q[1]
└── CZ @ q[0], q[1]

>>> circuit.sample_losses(lossmodel=model)
2-qubit circuit with 2 instructions:
├── QubitLoss @ q[1]
└── Depolarizing(0.01) @ q[0]
__init__(operation, decoration=None, *, before=False)[source]
before()[source]
matches(inst)[source]
apply_rule(inst)[source]
class mimiqcircuits.lossmodel.ReplaceRule(operation, replacement=None)[source]

Bases: AbstractCircuitRule

Replace a matched instruction with new instructions.

Use ReplaceRule when a partially affected operation should be removed and replaced by another operation on surviving qubits. A one-qubit replacement is broadcast to each target of the matched instruction, and copies on lost qubits are filtered out.

Parameters:
  • operation – Operation pattern to match.

  • replacement – Replacement operation or instruction sequence.

Examples

>>> from mimiqcircuits import *
>>> model = LossModel().add_replace(GateCX(), Depolarizing1(0.2))
>>> model
LossModel (unnamed, 1 rules)
└── ReplaceRule(CX => Depolarizing(0.2))
>>> circuit = Circuit()
>>> circuit.push(QubitLoss(), 1)
2-qubit circuit with 1 instruction:
└── QubitLoss @ q[1]

>>> circuit.push(GateCX(), 0, 1)
2-qubit circuit with 2 instructions:
├── QubitLoss @ q[1]
└── CX @ q[0], q[1]

>>> circuit.sample_losses(lossmodel=model)
2-qubit circuit with 2 instructions:
├── QubitLoss @ q[1]
└── Depolarizing(0.2) @ q[0]
__init__(operation, replacement=None)[source]
matches(inst)[source]
replaces()[source]
apply_rule(inst)[source]
class mimiqcircuits.lossmodel.CustomRule(matcher, generator)[source]

Bases: AbstractCircuitRule

User-defined loss rule.

CustomRule is the escape hatch for loss policies that cannot be expressed with DropRule, ReplaceRule, or DecorateRule. The matcher decides whether the rule applies. The generator returns None to drop the instruction, one Instruction, or a sequence of instructions.

The generator may accept (inst), (inst, lost), or (inst, lost, rng). It may also accept rng as a keyword argument.

Parameters:
  • matcher – Callable that receives an instruction and returns True if the rule should apply.

  • generator – Callable that generates replacement instructions.

Examples

Define a custom fallback for a partially lost CX. If the control qubit survives, replace the failed CX by X on the control. If the control is lost, return None to drop the instruction.

>>> from mimiqcircuits import *
>>> def cx_control_fallback(inst, lost):
...     control = inst.get_qubits()[0]
...     if lost.get(control, False):
...         return None
...     return Instruction(GateX(), (control,))
...
>>> model = LossModel([
...     CustomRule(
...         lambda inst: isinstance(inst.get_operation(), GateCX),
...         cx_control_fallback,
...     )
... ])
>>> model
LossModel (unnamed, 1 rules)
└── CustomRule(<callable>)
>>> circuit = Circuit()
>>> circuit.push(QubitLoss(), 1)
2-qubit circuit with 1 instruction:
└── QubitLoss @ q[1]

>>> circuit.push(GateCX(), 0, 1)
2-qubit circuit with 2 instructions:
├── QubitLoss @ q[1]
└── CX @ q[0], q[1]

>>> circuit.sample_losses(lossmodel=model)
2-qubit circuit with 2 instructions:
├── QubitLoss @ q[1]
└── X @ q[0]

Another custom rule can generate one instruction for each surviving qubit. Here a partially lost CX becomes Z on every qubit that is still present:

>>> model = LossModel([
...     CustomRule(
...         lambda inst: isinstance(inst.get_operation(), GateCX),
...         lambda inst, lost: [
...             Instruction(GateZ(), (q,))
...             for q in inst.get_qubits()
...             if not lost.get(q, False)
...         ],
...     )
... ])
>>> circuit.sample_losses(lossmodel=model)
2-qubit circuit with 2 instructions:
├── QubitLoss @ q[1]
└── Z @ q[0]
__init__(matcher, generator)[source]
matches(inst)[source]
replaces()[source]
apply_rule(inst)[source]
class mimiqcircuits.lossmodel.LossModel(rules=None, name='')[source]

Bases: object

Collection of prioritized loss rules.

A LossModel tells sample_losses() what to do when an instruction touches both lost and surviving qubits. With no rules, the conservative behavior is used: instructions touching lost qubits are dropped.

Parameters:
  • rules (optional) – Iterable of loss rules.

  • name (str) – Optional model name used in display output.

Examples

>>> from mimiqcircuits import *
>>> model = LossModel(name="My Loss Model")
>>> model
LossModel (My Loss Model, 0 rules)

Rules can be added incrementally:

>>> model.add_replace(GateCX(), Depolarizing1(0.2))
LossModel (My Loss Model, 1 rules)
└── ReplaceRule(CX => Depolarizing(0.2))

The model can then be passed to sample_losses:

>>> circuit = Circuit()
>>> circuit.push(QubitLoss(), 1)
2-qubit circuit with 1 instruction:
└── QubitLoss @ q[1]

>>> circuit.push(GateCX(), 0, 1)
2-qubit circuit with 2 instructions:
├── QubitLoss @ q[1]
└── CX @ q[0], q[1]

>>> circuit.sample_losses(lossmodel=model)
2-qubit circuit with 2 instructions:
├── QubitLoss @ q[1]
└── Depolarizing(0.2) @ q[0]
__init__(rules=None, name='')[source]
add_rule(rule)[source]
add_drop(operation=None)[source]
add_replace(operation, replacement=None)[source]
add_decorate(operation, decoration=None, *, before=False)[source]
sample_losses(circuit, rng=None)[source]
describe()[source]
saveproto(file)[source]
static loadproto(file)[source]
mimiqcircuits.lossmodel.sample_losses(circuit, rng=None, lossmodel=None)[source]

Sample qubit-loss events in a circuit and apply a loss model.

Parameters:
  • circuit (Circuit) – Circuit to sample.

  • rng (optional) – Random number generator used to sample LossErr events. Any object providing a random() method is accepted.

  • lossmodel (optional) – A LossModel describing how to rewrite gates touching lost qubits. A positional LossModel is also accepted as the second argument.

Returns:

A new circuit where loss events are sampled and the corresponding loss rules are applied.

Return type:

mc.Circuit

Examples

Reusing one seeded RNG advances its state and yields a reproducible sequence:

>>> import random
>>> from mimiqcircuits import Circuit, GateH, LossErr, sample_losses
>>> c = Circuit()
>>> c.push(LossErr(0.5), 1)
2-qubit circuit with 1 instruction:
└── LossErr(0.5) @ q[1]

>>> c.push(GateH(), 1)
2-qubit circuit with 2 instructions:
├── LossErr(0.5) @ q[1]
└── H @ q[1]

>>> rng = random.Random(70)
>>> sample_losses(c, rng=rng)
2-qubit circuit with 1 instruction:
└── H @ q[1]

>>> sample_losses(c, rng=rng)
2-qubit circuit with 1 instruction:
└── QubitLoss @ q[1]

Creating a fresh RNG with the same seed for each call repeats the same sample:

>>> sample_losses(c, rng=random.Random(20))
2-qubit circuit with 1 instruction:
└── H @ q[1]

>>> sample_losses(c, rng=random.Random(20))
2-qubit circuit with 1 instruction:
└── H @ q[1]