Source code for qermit.postselection.postselect_manager
from collections import Counter
from pytket.backends.backendresult import BackendResult
from pytket.utils.outcomearray import OutcomeArray
from pytket.circuit import Bit
from typing import List, Tuple, Dict
[docs]class PostselectMgr:
"""Class for tracking and applying post selection to results.
Includes other methods to analyse the results after post selection.
"""
[docs] def __init__(
self,
compute_cbits: List[Bit],
postselect_cbits: List[Bit],
):
"""Initialisation method.
:param compute_cbits: Bits in the circuit which are not affected
by post selection.
:type compute_cbits: List[Bit]
:param postselect_cbits: Bits on which the post selection is based.
:type postselect_cbits: List[Bit]
:raises Exception: Raised if a bit is in both compute_cbits
and postselect_cbits.
"""
intersect = set(compute_cbits).intersection(set(postselect_cbits))
if intersect:
raise Exception(
f"{intersect} are post select and compute qubits. "
+ "They cannot be both."
)
self.compute_cbits: List[Bit] = compute_cbits
self.postselect_cbits: List[Bit] = postselect_cbits
self.cbits: List[Bit] = compute_cbits + postselect_cbits
[docs] def get_postselected_shot(self, shot: Tuple[int, ...]) -> Tuple[int, ...]:
"Removes postselection bits from shot."
return tuple(
[
bit
for bit, reg in zip(shot, self.cbits)
if reg not in self.postselect_cbits
]
)
[docs] def is_postselect_shot(self, shot: Tuple[int, ...]) -> bool:
"Determines if shot survives postselection"
# TODO: It may be nice to generalise this so that other functions
# besides bit==0 can be used as a means of postselection.
return all(
bit == 0
for bit, reg in zip(shot, self.cbits)
if reg in self.postselect_cbits
)
[docs] def dict_to_result(self, result_dict: Dict[Tuple[int, ...], int]) -> BackendResult:
"""Convert dictionary to BackendResult.
:param result_dict: Dictionary to convert.
:type result_dict: Dict[Tuple[int, ...], int]
:return: Corresponding BackendResult.
:rtype: BackendResult
"""
# Special case where the dictionary is empty. Presently having
# an empty counter results in an error.
if not result_dict:
return BackendResult()
return BackendResult(
counts=Counter(
{
OutcomeArray.from_readouts([key]): val
for key, val in result_dict.items()
}
),
c_bits=self.compute_cbits,
)
[docs] def postselect_result(self, result: BackendResult) -> BackendResult:
"""Transforms BackendResult to keep only shots which should be
post selected.
:param result: Result to be modified.
:type result: BackendResult
:return: Postselected shots.
:rtype: BackendResult
"""
return self.dict_to_result(
{
self.get_postselected_shot(shot): count
for shot, count in result.get_counts(cbits=self.cbits).items()
if self.is_postselect_shot(shot)
}
)
[docs] def merge_result(self, result: BackendResult) -> BackendResult:
"""Transforms BackendResult so that postselection bits are
removed, but no shots are removed by postselection.
:param result: Result to be transformed.
:type result: BackendResult
:return: Result with postselection bits removed.
:rtype: BackendResult
"""
merge_dict: Dict[Tuple[int, ...], int] = {}
for shot, count in result.get_counts(cbits=self.cbits).items():
postselected_shot = self.get_postselected_shot(shot)
merge_dict[postselected_shot] = merge_dict.get(postselected_shot, 0) + count
return self.dict_to_result(merge_dict)