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 qubit-loss events in a circuit and apply a loss model. |
Classes
|
User-defined loss rule. |
|
Add a decoration before or after a matched instruction. |
|
Drop matched instructions that touch lost qubits. |
|
Collection of prioritized loss rules. |
|
Replace a matched instruction with new instructions. |
- class mimiqcircuits.lossmodel.AbstractCircuitRule[source]¶
Bases:
objectShared base class for circuit transformation rules.
- class mimiqcircuits.lossmodel.DropRule(operation=None)[source]¶
Bases:
AbstractCircuitRuleDrop matched instructions that touch lost qubits.
A
DropRuleis useful when a partially affected operation should not be salvaged. Ifoperationis 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]
- class mimiqcircuits.lossmodel.DecorateRule(operation, decoration=None, *, before=False)[source]¶
Bases:
AbstractCircuitRuleAdd 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
DecorateRuleuseful 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]
- class mimiqcircuits.lossmodel.ReplaceRule(operation, replacement=None)[source]¶
Bases:
AbstractCircuitRuleReplace a matched instruction with new instructions.
Use
ReplaceRulewhen 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]
- class mimiqcircuits.lossmodel.CustomRule(matcher, generator)[source]¶
Bases:
AbstractCircuitRuleUser-defined loss rule.
CustomRuleis the escape hatch for loss policies that cannot be expressed withDropRule,ReplaceRule, orDecorateRule. The matcher decides whether the rule applies. The generator returnsNoneto drop the instruction, oneInstruction, or a sequence of instructions.The generator may accept
(inst),(inst, lost), or(inst, lost, rng). It may also acceptrngas a keyword argument.- Parameters:
matcher – Callable that receives an instruction and returns
Trueif 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 failedCXbyXon the control. If the control is lost, returnNoneto 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
CXbecomesZon 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]
- class mimiqcircuits.lossmodel.LossModel(rules=None, name='')[source]¶
Bases:
objectCollection of prioritized loss rules.
A
LossModeltellssample_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]
- 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
LossErrevents. Any object providing arandom()method is accepted.lossmodel (optional) – A
LossModeldescribing how to rewrite gates touching lost qubits. A positionalLossModelis 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]