loom.interpreter.interpretation_step
Copyright 2024 Entropica Labs Pte Ltd
Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
- class loom.interpreter.interpretation_step.InterpretationStep(*args, **kwargs)[source]
Bases:
objectThe InterpretationStep class stores all relevant information which was generated during interpretation up to the Operation which is currently interpreted. In every interpretation step, the old InterpretationStep instance is replaced with an updated instance. After all Operations have been interpreted, the last InterpretationStep instance contains the final output.
NOTE on mutability: During the interpretation of an EKA, there is a lot of data generated and modified which is stored inside the InterpretationStep objects. Having the InterpretationStep dataclass mutable makes it a lot easier to modify the data during interpretation. Therefore many of the convenience methods here have side effects on InterpretationStep. To make these side effects explicit, those methods with side effects have the suffix _MUT in their name. This follows the Julia convention where functions with side effects have a
!at the end of their name.NOTE: The operations implemented inside the applicators are not pure functions, they take the previous InterpretationStep as an input and returning an updated InterpretationStep, effectively emulating that behavior. Make sure to always keep track of the InterpretationStep you are currently working on, since it is updated with every operation.
- Parameters:
intermediate_circuit_sequence (tuple[tuple[Circuit, ...], ...]) – The circuit implementing all Operation s which have been interpreted until now. It consists of a tuple of timeslices, where each timeslice is a tuple of Circuit objects. They can be composite circuits. At the final step, this is used to generate final_circuit.
final_circuit (Circuit | None) – The final circuit object which is generated after interpreting all operations. This is the circuit which is used for the final output of the interpretation, it is generated automatically by interpreting all operations.
block_history (tuple[tuple[Block, ...], ...]) – A history of block configurations. The last element in the tuple is the current configuration of blocks. With the interpretation of every Operation a new tuple of blocks is added to this block_history field. While mostly only the last configuration of blocks is relevant, the whole history is stored which might be useful for plotting.
block_history_new (BlockHistory) – A BlockHistory object which keeps track of the block UUIDs present at every timestamp.
block_registry (dict[str, Block]) – A dictionary storing all Block objects which have been created during the interpretation. The keys are the UUIDs of the blocks. This is used to retrieve block objects based on their UUIDs which are stored in the block_history_new field.
syndromes (tuple[Syndrome, ...]) – A tuple of Syndrome`s which are created due to all syndrome extraction cycles up to the `Operation which is currently interpreted.
detectors (tuple[Detector, ...]) – A tuple of Detector`s which are created due to all syndrome extraction cycles up to the `Operation which is currently interpreted.
logical_observables (tuple[LogicalObservable, ...]) – A tuple of LogicalObservable s which were measured until now.
stabilizer_evolution (dict[str, tuple[str, ...]]) – Keeps track of which stabilizers transformed into which other stabilizers due to operations such as shrink or split. The dictionary is a FINAL-to-INITIAL mapping. In most cases both key and value will be a single string and there is a 1:1 mapping from an old stabilizer to a new stabilizer. If there is a case where multiple stabilizers are combined into a single stabilizer, the value will be a tuple of strings. Conversely, if a single stabilizer is split into multiple stabilizers, two keys would be associated with the same value. E.g. for a split we match new_stab1.uuid to (old_stab.uuid,) and new_stab2.uuid to (old_stab.uuid,). For a situation where we merge two stabilizers, we match merged_stab.uuid to (old_stab1.uuid, old_stab.uuid) .
logical_x_evolution (dict[str, tuple[str, ...]]) – Keeps track of which logical X operator(s) transformed into which other logical X operator(s) due to operations such as shrink or split and eventual stabilizer(s) required to go from one to the next. The dictionary is a FINAL-to-INITIAL mapping. E.g. for a split we match split_x_op1.uuid to (old_x_op.uuid,) and split_x_op2.uuid to (old_x_op.uuid,). For a shrink that moved the X operator using adjacent stabilizers, we match new_x_op.uuid to (old_x_op.uuid, stab1.uuid, stab2.uuid).
logical_z_evolution (dict[str, tuple[str, ...]]) – Keeps track of which logical Z operator(s) transformed into which other logical Z operator(s) due to operations such as shrink or split and eventual stabilizer(s) required to go from one to the next. The dictionary is a FINAL-to-INITIAL mapping. E.g. for a split we match split_z_op1.uuid to (old_z_op.uuid,) and split_z_op2.uuid to (old_z_op.uuid,). For a shrink that moved the Z operator using adjacent stabilizers, we match new_z_op.uuid to (old_z_op.uuid, stab1.uuid, stab2.uuid).
block_evolution (dict[str, tuple[str, ...]]) – Keeps track of which block(s) transformed into which other block(s) due to operations such as merge and split. If there is a 1:1 mapping between and old block and a new block (e.g. due to renaming), the value will be a tuple containing a single string. If one block is split into two blocks, two keys will be associated to the same value that is a tuple containing a single string. If two blocks are merged into a single block, the key will be a single string and the value will be a tuple of two strings. E.g. for a merge, we match merged_block.uuid to (block1.uuid, block2.uuid).
block_qec_rounds (dict[str, int]) – A dictionary storing for every block id how many syndrome extraction rounds have been performed on this block. This is needed for creating new Syndrome and Detector objects which have a round attribute, specifying the syndrome extraction round of the block in which they were measured.
cbit_counter (dict[str, int]) – A dictionary storing how many measurements have been performed and stored in each classical register. The keys are the labels of the classical registers which are used as the first element in Cbit.
block_decoding_starting_round (dict[str, int]) – A dictionary storing for every block the round from which the decoding of this block should start the next time real-time decoding is performed. E.g. if we encounter a non-Clifford gate on a block at time t, we need to decode until this time t. Then in this dictionary, we store that the next decoding round has to include detectors up to time t+1.
logical_x_operator_updates (dict[str, tuple[Cbit, ...]]) – A dictionary storing for every logical X operator, the measurements (in the form of Cbits) which need to be taken into account for updating the Pauli frame of this logical operator once this operator is measured. Elements will be added here when some of the data qubits of the respective logical operator are measured, e.g. in a shrink or split operation. In this case, these measurements lead to a change of pauli frame and need to be included in the next readout of this operator. This is also needed for real-time decoding. The values can be accessed via logical_x_operator_updates[logical_x.uuid]. E.g. for a shrink of length 2 we match new_x_op.uuid to (cbit1, cbit2,).
logical_z_operator_updates (dict[str, tuple[Cbit, ...]]) – A dictionary storing for every logical Z operator, the measurements (in the form of Cbits) which need to be taken into account for updating the Pauli frame of this logical operator once this operator is measured. Elements will be added here when some of the data qubits of the respective logical operator are measured, e.g. in a shrink or split operation. In this case, these measurements lead to a change of pauli frame and need to be included in the next readout of this operator. This is also needed for real-time decoding. The values can be accessed via logical_z_operator_updates[logical_x.uuid]. E.g. for a shrink of length 2 we match new_z_op.uuid to (cbit1, cbit2,).
stabilizer_updates (dict[str, tuple[Cbit, ...]]) – A dictionary storing updates for stabilizers which need to be included when the stabilizer is measured the next time. Elements will be added here when some of the data qubits of the respective stabilizer are measured (in other words when the weight of the stabilizer is reduced), e.g. in a shrink or split operation. The keys of the dictionary are uuids of stabilizers. E.g. for a shrink that changes a weight 4 stabilizer to a weight 2 stabilizer we match new_stab.uuid to (cbit1, cbit2). CAUTION: Some applicators may pop the entries from the stabilizer_updates field of the interpretation step to compute corrections. This may cause issues in the future if the information in this field also needs to be accessed somewhere else.
channel_dict (dict[str, Channel]) – A dictionary storing all channels which have been created during the interpretation. The keys are the labels of the channels (which are either the qubit coordinates or the Cbit tuple). The values are the Channel objects. Only one Channel is created per qubit. Measurements are associated to individual channels. I.e. for every Cbit, there is a separate Channel object.
composite_operation_session_stack (list[CompositeOperationSession]) – A stack of composite operation sessions which are currently open. Every time a composite operation is started, a new session is created and added to this stack. When the composite operation is ended, the session is removed from the stack.
timeslice_durations (list[int]) – A list storing the duration of each timeslice in the intermediate_circuit_sequence field.
is_frozen (bool) – A boolean flag, indicating whether the InterpretationStep is frozen. If it is set to True (frozen), calling methods which mutate the InterpretationStep will raise an exception. Defaults to False.
-
block_decoding_starting_round:
dict[str,int] = FieldInfo(annotation=dict[str, int], required=False, default_factory=dict, validate_default=True)
-
block_evolution:
dict[str,tuple[str,...]] = FieldInfo(annotation=dict[str, tuple[str, ...]], required=False, default_factory=dict, validate_default=True)
-
block_history:
tuple[tuple[Block,...],...] = FieldInfo(annotation=tuple[tuple[Block, ...], ...], required=False, default_factory=tuple, validate_default=True)
-
block_history_new:
BlockHistory= FieldInfo(annotation=BlockHistory, required=True, init=False)
-
block_qec_rounds:
dict[str,int] = FieldInfo(annotation=dict[str, int], required=False, default_factory=dict, validate_default=True)
-
block_registry:
dict[str,Block] = FieldInfo(annotation=dict[str, Block], required=False, default_factory=dict, validate_default=True, init=False)
-
cbit_counter:
dict[str,int] = FieldInfo(annotation=dict[str, int], required=False, default_factory=dict, validate_default=True)
-
channel_dict:
dict[str,Channel] = FieldInfo(annotation=dict[str, Channel], required=False, default_factory=dict, validate_default=True)
-
composite_operation_session_stack:
list[CompositeOperationSession] = FieldInfo(annotation=list[CompositeOperationSession], required=False, default_factory=list, validate_default=True, init=False)
-
detectors:
tuple[Detector,...] = FieldInfo(annotation=tuple[Detector, ...], required=False, default_factory=tuple, validate_default=True)
- get_all_syndromes(stab_id, block_id)[source]
Returns all syndromes associated with a given stabilizer id.
- Parameters:
stab_id (str) – Stabilizer uuid to search for.
block_id (str) – block uuid to search for.
- Returns:
List of all syndromes associated with the given stabilizer and block id.
- Return type:
list[Syndrome]
- get_block(label)[source]
Get the block with the given label from the current block configuration.
- Parameters:
label (str) – Unique label of the block
- Returns:
Block with the given label
- Return type:
- get_prev_syndrome(stabilizer_id, block_id, current_round=None)[source]
Finds the latest syndrome for a given stabilizer_id. If current_round is given, this function returns the latest syndrome for the associated stabilizer such that the round is less than current_round. If None is given, the latest syndrome is returned.
- Parameters:
stabilizer_id (str) – Stabilizer uuid to search for.
block_id (str) – block uuid to search for.
current_round (int | None, optional) – Round to compare to, by default None
- Returns:
The latest syndrome for the given stabilizer_id, block_id and current_round. Returns an empty list if no Syndrome is found.
- Return type:
list[Syndrome]
- get_timestamp()[source]
Get the current timestamp of the interpretation step. The timestamp indicates the time when the last circuit that was appended to the intermediate circuit sequence ends.
The timestamp is calculated by summing the maximum duration of each timeslice in the intermediate circuit sequence, omitting the timeslices which are just before active composite operation sessions that are marked as same_timeslice=True. This is because these timeslices run in parallel with the previous timeslice and thus the previous timeslice’s duration should not be considered in the total time.
NOTE: Access the time after appending the circuits of the current operation, so that it includes all relevant timeslices.
NOTE: To sector the final circuit based on timestamps, unroll the circuit and sum the durations of the timeslices until the desired timestamp is reached.
- Returns:
The current timestamp of the interpretation step.
- Return type:
int
Examples
Below we illustrate how the timestamp is calculated in different scenarios. Note that the values shown correspond to the values after appending the circuit they are associated with.
No composite operations:
"circuit_a": |--3--| time = 3 "circuit_b": |--2--| time = 5 "circuit_c": |-----5-----| time = 8 "circuit_d": |---3---| time = 6 "circuit_e": |---3---| time = 11
Nested composite operation sessions:
Base circuit: "some circuit": |--2--| time = 2 Session 0: "ses0 circuit": |---------9---------| time = 11 Session 1: "ses1 first circuit": |--2--| time = 4 Nested session 0: "parallel circuit_0": |----4----| time = 8 Nested session 1: "parallel circuit_1": |--2--| time = 6 Nested session 2: "parallel circuit_2": |-----5-----| time = 9 Nested session 3: "parallel circuit_3": |-1-| time = 5
-
intermediate_circuit_sequence:
tuple[tuple[Circuit,...],...] = FieldInfo(annotation=tuple[tuple[Circuit, ...], ...], required=False, default_factory=tuple, validate_default=False)
-
is_frozen:
bool= False
-
logical_measurements:
dict[LogicalMeasurement,tuple[Union[tuple[str,int],Literal[1,0]],...]] = FieldInfo(annotation=dict[LogicalMeasurement, tuple[Union[tuple[str, int], Literal[1, 0]], ...]], required=False, default_factory=dict, validate_default=True)
-
logical_observables:
tuple[LogicalObservable,...] = FieldInfo(annotation=tuple[LogicalObservable, ...], required=False, default_factory=tuple, validate_default=True)
-
logical_x_evolution:
dict[str,tuple[str,...]] = FieldInfo(annotation=dict[str, tuple[str, ...]], required=False, default_factory=dict, validate_default=True)
-
logical_x_operator_updates:
dict[str,tuple[Union[tuple[str,int],Literal[1,0]],...]] = FieldInfo(annotation=dict[str, tuple[Union[tuple[str, int], Literal[1, 0]], ...]], required=False, default_factory=dict, validate_default=True)
- property logical_x_operators_dict: dict[str, PauliOperator]
Return a dictionary of logical X operators with logical operator uuid as keys.
-
logical_z_evolution:
dict[str,tuple[str,...]] = FieldInfo(annotation=dict[str, tuple[str, ...]], required=False, default_factory=dict, validate_default=True)
-
logical_z_operator_updates:
dict[str,tuple[Union[tuple[str,int],Literal[1,0]],...]] = FieldInfo(annotation=dict[str, tuple[Union[tuple[str, int], Literal[1, 0]], ...]], required=False, default_factory=dict, validate_default=True)
- property logical_z_operators_dict: dict[str, PauliOperator]
Return a dictionary of logical Z operators with logical operator uuid as keys.
- retrieve_cbits_from_stabilizers(stabs_required, current_block)[source]
Retrieve the cbits associated with the most recent syndrome extraction of the stabilizers required to move the logical operator.
- Parameters:
stabs_required (tuple[Stabilizer, ...]) – Stabilizers required to update the logical operator.
current_block (Block) – Current block in which the stabilizers were measured.
- Returns:
Cbits associated with the measurement of the logical operator displacement.
- Return type:
tuple[Cbit, …]
-
stabilizer_evolution:
dict[str,tuple[str,...]] = FieldInfo(annotation=dict[str, tuple[str, ...]], required=False, default_factory=dict, validate_default=True)
-
stabilizer_updates:
dict[str,tuple[Union[tuple[str,int],Literal[1,0]],...]] = FieldInfo(annotation=dict[str, tuple[Union[tuple[str, int], Literal[1, 0]], ...]], required=False, default_factory=dict, validate_default=True)
- property stabilizers_dict: dict[str, Stabilizer]
Return a dictionary of stabilizers with stabilizer uuid as keys.
-
syndromes:
tuple[Syndrome,...] = FieldInfo(annotation=tuple[Syndrome, ...], required=False, default_factory=tuple, validate_default=True)
-
timeslice_durations:
list[int] = FieldInfo(annotation=list[int], required=False, default_factory=list, validate_default=True, init=False)