Skip to content

Commit

Permalink
Merge pull request #406 from CQCL/bugfix/specify-what-we-expect-in-qi…
Browse files Browse the repository at this point in the history
…skit-result-conversion

Adds parameters to qiskit_result_to_backendresult / qiskit_experimentresult_to_backendresult to specify
which type of results we expect to have back.

Previously, circuits with classical bits submitted to AerStateBackend would have a counts field in their
returned result, because Qiskit reads from the classical register memory. Now we don't set that,
because we don't expect counts to be returned from AerStateBackend so we ignore that field.

Also, we weren't ever passing in anything to ppcirc in qiskit_experimentresult_to_backendresult,
so after discussion with @cqc-melf and @cqc-alec that's been removed as part of this PR.
  • Loading branch information
isobelhooper authored Oct 24, 2024
2 parents 96d01b1 + 6f320e2 commit 577b9a0
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 13 deletions.
9 changes: 8 additions & 1 deletion pytket/extensions/qiskit/backends/aer.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,14 @@ def get_result(self, handle: ResultHandle, **kwargs: KwargTypes) -> BackendResul
raise CircuitNotRunError(handle)

res = job.result()
backresults = qiskit_result_to_backendresult(res)
backresults = qiskit_result_to_backendresult(
res,
include_shots=self._supports_shots,
include_counts=self._supports_counts,
include_state=self._supports_state,
include_unitary=self._supports_unitary,
include_density_matrix=self._supports_density_matrix,
)
for circ_index, backres in enumerate(backresults):
self._cache[ResultHandle(jobid, circ_index, qubit_n, ppc)][
"result"
Expand Down
44 changes: 33 additions & 11 deletions pytket/extensions/qiskit/result_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from qiskit.result import Result # type: ignore
from qiskit.result.models import ExperimentResult # type: ignore

from pytket.circuit import Bit, Qubit, UnitID, Circuit
from pytket.circuit import Bit, Qubit, UnitID

from pytket.backends.backendresult import BackendResult
from pytket.utils.outcomearray import OutcomeArray
Expand Down Expand Up @@ -81,9 +81,17 @@ def _result_is_empty_shots(result: ExperimentResult) -> bool:
return False


# In some cases, Qiskit returns a result with fields we don't expect -
# for example, a circuit with classical bits run on AerStateBackend will
# return counts (whether or not there were measurements). The include_foo
# arguments should be set based on what the backend supports.
def qiskit_experimentresult_to_backendresult(
result: ExperimentResult,
ppcirc: Optional[Circuit] = None,
include_counts: bool = True,
include_shots: bool = True,
include_state: bool = True,
include_unitary: bool = True,
include_density_matrix: bool = True,
) -> BackendResult:
if not result.success:
raise RuntimeError(result.status)
Expand All @@ -105,16 +113,16 @@ def qiskit_experimentresult_to_backendresult(

shots, counts, state, unitary, density_matrix = (None,) * 5
datadict = result.data.to_dict()
if _result_is_empty_shots(result):
if _result_is_empty_shots(result) and include_shots:
n_bits = len(c_bits) if c_bits else 0
shots = OutcomeArray.from_readouts(
np.zeros((result.shots, n_bits), dtype=np.uint8)
)
else:
if "memory" in datadict:
if "memory" in datadict and include_shots:
memory = datadict["memory"]
shots = _hex_to_outar(memory, width)
elif "counts" in datadict:
elif "counts" in datadict and include_counts:
qis_counts = datadict["counts"]
counts = Counter(
dict(
Expand All @@ -123,13 +131,13 @@ def qiskit_experimentresult_to_backendresult(
)
)

if "statevector" in datadict:
if "statevector" in datadict and include_state:
state = datadict["statevector"].reverse_qargs().data

if "unitary" in datadict:
if "unitary" in datadict and include_unitary:
unitary = datadict["unitary"].reverse_qargs().data

if "density_matrix" in datadict:
if "density_matrix" in datadict and include_density_matrix:
density_matrix = datadict["density_matrix"].reverse_qargs().data

return BackendResult(
Expand All @@ -140,13 +148,27 @@ def qiskit_experimentresult_to_backendresult(
state=state,
unitary=unitary,
density_matrix=density_matrix,
ppcirc=ppcirc,
ppcirc=None,
)


def qiskit_result_to_backendresult(res: Result) -> Iterator[BackendResult]:
def qiskit_result_to_backendresult(
res: Result,
include_counts: bool = True,
include_shots: bool = True,
include_state: bool = True,
include_unitary: bool = True,
include_density_matrix: bool = True,
) -> Iterator[BackendResult]:
for result in res.results:
yield qiskit_experimentresult_to_backendresult(result)
yield qiskit_experimentresult_to_backendresult(
result,
include_counts,
include_shots,
include_state,
include_unitary,
include_density_matrix,
)


def backendresult_to_qiskit_resultdata(
Expand Down
7 changes: 6 additions & 1 deletion tests/qiskit_convert_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -549,12 +549,17 @@ def test_convert_result() -> None:
qc1.save_state()
qisk_result = simulator.run(qc1, shots=10).result()

tk_res = next(qiskit_result_to_backendresult(qisk_result))
# exclude counts from result (we don't expect them
# for the statevector sim after all)
tk_res = next(qiskit_result_to_backendresult(qisk_result, include_counts=False))

state = tk_res.get_state([Qubit("q2", 1), Qubit("q1", 0), Qubit("q2", 0)])
correct_state = np.zeros(1 << 3, dtype=complex)
correct_state[6] = 1 + 0j
assert compare_statevectors(state, correct_state)
# also check that we don't return counts in tket result
# even if the qiskit result includes them
assert tk_res._counts is None

# check measured
qc.measure(qr1[0], cr[0])
Expand Down

0 comments on commit 577b9a0

Please sign in to comment.