Skip to content

Commit

Permalink
Handle bits selection in results for circuits with non-default regist…
Browse files Browse the repository at this point in the history
…ers (#407)
  • Loading branch information
cqc-alec authored Oct 25, 2024
1 parent cadaa11 commit 64da26f
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 40 deletions.
5 changes: 5 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
Changelog
~~~~~~~~~

Unreleased
----------

* Fix handling of non-default registers when selecting bits in results.

0.58.0 (October 2024)
---------------------

Expand Down
84 changes: 44 additions & 40 deletions pytket/extensions/qiskit/backends/ibm.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import itertools
import json
from ast import literal_eval
from collections import Counter
from collections import Counter, OrderedDict
from collections.abc import Sequence
from time import sleep
from typing import (
Expand Down Expand Up @@ -75,14 +75,17 @@
from pytket.utils import prepare_circuit
from pytket.utils.outcomearray import OutcomeArray
from pytket.utils.results import KwargTypes
from qiskit.primitives import PrimitiveResult, SamplerResult # type: ignore
from qiskit.primitives import ( # type: ignore
BitArray,
DataBin,
PrimitiveResult,
SamplerPubResult,
)

# RuntimeJob has no queue_position attribute, which is referenced
# via job_monitor see-> https://github.com/CQCL/pytket-qiskit/issues/48
# therefore we can't use job_monitor until fixed
# from qiskit.tools.monitor import job_monitor # type: ignore
from qiskit.result.distributions import QuasiDistribution # type: ignore

from .._metadata import __extension_version__
from ..qiskit_convert import (
_tk_gate_set,
Expand All @@ -99,14 +102,10 @@
_DEBUG_HANDLE_PREFIX = "_MACHINE_DEBUG_"


def _gen_debug_results(n_qubits: int, shots: int, index: int) -> SamplerResult:
debug_dist = {n: 0.0 for n in range(pow(2, n_qubits))}
debug_dist[0] = 1.0
qd = QuasiDistribution(debug_dist)
return SamplerResult(
quasi_dists=[qd] * (index + 1),
metadata=[{"header_metadata": {}, "shots": shots}] * (index + 1),
)
def _gen_debug_results(n_bits: int, shots: int) -> PrimitiveResult:
n_u8s = (n_bits - 1) // 8 + 1
arr = np.array([[0] * n_u8s for _ in range(shots)], dtype=np.uint8)
return PrimitiveResult([SamplerPubResult(DataBin(c=BitArray(arr, n_bits)))])


class NoIBMQCredentialsError(Exception):
Expand Down Expand Up @@ -205,7 +204,9 @@ def __init__(
self._monitor = monitor

# cache of results keyed by job id and circuit index
self._ibm_res_cache: dict[tuple[str, int], Counter] = dict()
self._ibm_res_cache: dict[
tuple[str, int], tuple[Counter, Optional[list[Bit]]]
] = dict()

if sampler_options is None:
sampler_options = SamplerOptions()
Expand Down Expand Up @@ -456,7 +457,7 @@ def default_compilation_pass_offline(

@property
def _result_id_type(self) -> _ResultIdTuple:
# IBMQ job ID, index, number of measurements per shot, post-processing circuit
# IBMQ job ID, index, number of bits, post-processing circuit
return (str, int, int, str)

def rebase_pass(self) -> BasePass:
Expand Down Expand Up @@ -521,14 +522,11 @@ def process_circuits(

qcs, ppcirc_strs = [], []
for tkc in batch_chunk:
tkc1 = tkc.copy()
# Flatten bits to default register in lexicographic order:
tkc1.rename_units({bit: Bit(i) for i, bit in enumerate(tkc1.bits)})
if postprocess:
c0, ppcirc = prepare_circuit(tkc1, allow_classical=False)
c0, ppcirc = prepare_circuit(tkc, allow_classical=False)
ppcirc_rep = ppcirc.to_dict()
else:
c0, ppcirc_rep = tkc1, None
c0, ppcirc_rep = tkc, None
if simplify_initial:
SimplifyInitial(
allow_classical=False, create_all_qubits=True
Expand All @@ -540,7 +538,7 @@ def process_circuits(
handle_list[ind] = ResultHandle(
_DEBUG_HANDLE_PREFIX + str((n_shots, batch_id)),
i,
batch_chunk[i].n_qubits,
batch_chunk[i].n_bits,
ppcirc_strs[i],
)
else:
Expand All @@ -549,7 +547,7 @@ def process_circuits(
job_id = job.job_id()
for i, ind in enumerate(indices_chunk):
handle_list[ind] = ResultHandle(
job_id, i, qcs[i].count_ops()["measure"], ppcirc_strs[i]
job_id, i, qcs[i].num_clbits, ppcirc_strs[i]
)
batch_id += 1 # noqa: SIM113
for handle in handle_list:
Expand Down Expand Up @@ -586,15 +584,15 @@ def get_result(self, handle: ResultHandle, **kwargs: KwargTypes) -> BackendResul
cached_result = self._cache[handle]
if "result" in cached_result:
return cast(BackendResult, cached_result["result"])
jobid, index, n_meas, ppcirc_str = handle
jobid, index, n_bits, ppcirc_str = handle
ppcirc_rep = json.loads(ppcirc_str)
ppcirc = Circuit.from_dict(ppcirc_rep) if ppcirc_rep is not None else None
cache_key = (jobid, index)
if cache_key not in self._ibm_res_cache:
if self._MACHINE_DEBUG or jobid.startswith(_DEBUG_HANDLE_PREFIX):
shots: int
shots, _ = literal_eval(jobid[len(_DEBUG_HANDLE_PREFIX) :])
res = _gen_debug_results(n_meas, shots, index)
res = _gen_debug_results(n_bits, shots)
else:
try:
job = self._retrieve_job(jobid)
Expand All @@ -612,32 +610,38 @@ def get_result(self, handle: ResultHandle, **kwargs: KwargTypes) -> BackendResul
sleep(10)

res = job.result(timeout=kwargs.get("timeout"))
if isinstance(res, SamplerResult):
# TODO Is this code still reachable?
for circ_index, (r, d) in enumerate(zip(res.quasi_dists, res.metadata)):
self._ibm_res_cache[(jobid, circ_index)] = Counter(
{n: int(0.5 + d["shots"] * p) for n, p in r.items()}
)
else:
assert isinstance(res, PrimitiveResult)
for circ_index, pub_result in enumerate(res._pub_results):
readouts = pub_result.data.c.array
self._ibm_res_cache[(jobid, circ_index)] = Counter(
_int_from_readout(readout) for readout in readouts
)

counts = self._ibm_res_cache[cache_key] # Counter[int]
assert isinstance(res, PrimitiveResult)
for circ_index, pub_result in enumerate(res._pub_results):
data = pub_result.data
c_regs = OrderedDict(
(reg_name, data.__getattribute__(reg_name).num_bits)
for reg_name in sorted(data.keys())
)
readouts = BitArray.concatenate_bits(
[data.__getattribute__(reg_name) for reg_name in c_regs]
).array
self._ibm_res_cache[(jobid, circ_index)] = (
Counter(_int_from_readout(readout) for readout in readouts),
list(
itertools.chain.from_iterable(
[Bit(reg_name, i) for i in range(reg_size)]
for reg_name, reg_size in c_regs.items()
)
),
)

counts, c_bits = self._ibm_res_cache[cache_key] # Counter[int], list[Bit]
# Convert to `OutcomeArray`:
tket_counts: Counter = Counter()
for outcome_key, sample_count in counts.items():
array = OutcomeArray.from_ints(
ints=[outcome_key],
width=n_meas,
width=n_bits,
big_endian=False,
)
tket_counts[array] = sample_count
# Convert to `BackendResult`:
result = BackendResult(counts=tket_counts, ppcirc=ppcirc)
result = BackendResult(c_bits=c_bits, counts=tket_counts, ppcirc=ppcirc)

self._cache[handle] = {"result": result}
return result

0 comments on commit 64da26f

Please sign in to comment.