mimiqcircuits.lossmodel

Loss model definitions and qubit-loss sampling.

This module provides the rule system used by lower_losses() (and hence resolve_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(Loss(), 1)
>>> _ = circuit.push(GateCX(), 0, 1)
>>> model = LossModel().add_replace(GateCX(), Depolarizing1(0.2))
>>> resolve_losses(circuit, lossmodel=model)
2-qubit circuit with 2 instructions:
├── Lost @ q[1]
└── Depolarizing(0.2) @ q[0]

Functions

lossmodel_rewrite(inst, lost, model, rng)

Decide how a single instruction touching both lost and present qubits is rewritten under a LossModel, returning the instructions to emit in its place (an empty list when the instruction is dropped).

lower_losses(circuit[, rng, lossmodel])

Lower a circuit with loss operations into one using only primitives.

resolve_losses(circuit[, rng, lossmodel])

Fully resolve loss into a runnable, loss-free circuit.

sample_loss_scenario(circuit, loss_indices, *)

Build a deterministic "what-if" loss scenario from a circuit.

sample_losses(circuit[, rng])

Resolve the random qubit-loss events in a circuit.

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(Loss(), 1)
>>> _ = circuit.push(GateCX(), 0, 1)
>>> circuit.resolve_losses(lossmodel=model)
2-qubit circuit with 1 instruction:
└── Lost @ 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(Loss(), 1)
>>> _ = circuit.push(GateCZ(), 0, 1)
>>> circuit.resolve_losses(lossmodel=model)
2-qubit circuit with 2 instructions:
├── Lost @ 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(Loss(), 1)
>>> _ = circuit.push(GateCX(), 0, 1)
>>> circuit.resolve_losses(lossmodel=model)
2-qubit circuit with 2 instructions:
├── Lost @ 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(Loss(), 1)
>>> _ = circuit.push(GateCX(), 0, 1)
>>> circuit.resolve_losses(lossmodel=model)
2-qubit circuit with 2 instructions:
├── Lost @ 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.resolve_losses(lossmodel=model)
2-qubit circuit with 2 instructions:
├── Lost @ 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(Loss(), 1)
>>> _ = circuit.push(GateCX(), 0, 1)
>>> circuit.resolve_losses(lossmodel=model)
2-qubit circuit with 2 instructions:
├── Lost @ 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]
lower_losses(circuit, rng=None)[source]
resolve_losses(circuit, rng=None)[source]
describe()[source]
saveproto(file)[source]
static loadproto(file)[source]
mimiqcircuits.lossmodel.sample_losses(circuit, rng=None)[source]

Resolve the random qubit-loss events in a circuit.

Each mimiqcircuits.Loss (p) is drawn: with probability p it becomes a certain loss Loss(1.0), otherwise it is removed. Every other operation, including the deterministic loss bookkeeping (Reload, Check, MeasureCheck), is kept unchanged. The result is deterministic but still expressed with loss operations; use lower_losses() or resolve_losses() to turn it into a runnable, loss-free circuit.

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

  • rng (optional) – Random number generator providing a random() method.

Returns:

A circuit whose random loss events have been resolved.

Return type:

mc.Circuit

mimiqcircuits.lossmodel.lower_losses(circuit, rng=None, lossmodel=None)[source]

Lower a circuit with loss operations into one using only primitives.

This is the deterministic half of loss resolution. It expects the random Loss(p) events to be resolved already (see sample_losses()) and treats any remaining Loss as a certain loss. It rewrites every loss operation into Reset / Measure / SetBit0 / SetBit1 plus passive Lost / Reloaded markers, and drops or replaces gates on lost qubits according to lossmodel. The returned circuit contains no loss operations.

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

  • rng (optional) – Random number generator for stochastic loss-model rules. A positional LossModel is also accepted.

  • lossmodel (optional) – A LossModel for gates touching lost qubits.

Returns:

A loss-free circuit that runs on any backend.

Return type:

mc.Circuit

mimiqcircuits.lossmodel.resolve_losses(circuit, rng=None, lossmodel=None)[source]

Fully resolve loss into a runnable, loss-free circuit.

The user-facing entry point: it runs sample_losses() to draw the random Loss(p) events, then lower_losses() to rewrite the result into primitives. The returned circuit contains no loss operations.

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

  • rng (optional) – Random number generator. A positional LossModel is also accepted.

  • lossmodel (optional) – A LossModel for gates touching lost qubits.

Returns:

A loss-free circuit that runs on any backend.

Return type:

mc.Circuit

mimiqcircuits.lossmodel.sample_loss_scenario(circuit, loss_indices, *, p=1.0, rng=None, lossmodel=None)[source]

Build a deterministic “what-if” loss scenario from a circuit.

The selected mimiqcircuits.Loss instructions, counted in circuit order (1-based), are forced to Loss(p) while every other Loss is forced to Loss(0.0). The result is then resolved with resolve_losses(), so the returned circuit shows the effect of losing exactly those sites.

Parameters:
  • circuit (Circuit) – Circuit containing Loss instructions.

  • loss_indices – A 1-based index or iterable of indices selecting which Loss instructions to force.

  • p (optional) – Probability assigned to the selected sites (default 1.0).

  • rng (optional) – Random number generator.

  • lossmodel (optional) – A LossModel for gates touching lost qubits.

Returns:

A loss-free circuit for the chosen scenario.

Return type:

mc.Circuit

Examples

>>> from mimiqcircuits import *
>>> circuit = Circuit()
>>> _ = circuit.push(Loss(0.2), 0)
>>> _ = circuit.push(GateCX(), 0, 1)
>>> _ = circuit.push(Loss(0.4), 1)
>>> sample_loss_scenario(circuit, 2)
2-qubit circuit with 2 instructions:
├── CX @ q[0], q[1]
└── Lost @ q[1]