Skip to content

Commit

Permalink
Retain ordering information in conversion. (quantumlib#4384)
Browse files Browse the repository at this point in the history
Previously, when you requested a cirq result with measurement A, and B, we would return a Result object that contained the correct counts for A and B, but which eliminated any correlation information we had (by throwing the results order out in count()). This changes so that count() is derived from another method, ordered_results, which takes a key and returns the list of list of bit-wise results in a consistent way from key to key.
  • Loading branch information
Cynocracy authored and rht committed May 1, 2023
1 parent 4db207e commit 4440232
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 12 deletions.
2 changes: 1 addition & 1 deletion cirq-google/cirq_google/engine/abstract_local_program.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,5 +202,5 @@ def get_circuit(self, program_num: Optional[int] = None) -> cirq.Circuit:
return self._circuits[0]

def batch_size(self) -> int:
"""Returns the number of programs in a batch program. """
"""Returns the number of programs in a batch program."""
return len(self._circuits)
49 changes: 39 additions & 10 deletions cirq-ionq/cirq_ionq/results.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"""Result types for the IonQ API."""

import collections
from typing import Dict, Counter, Optional, Sequence
from typing import Dict, Counter, List, Optional, Sequence

import numpy as np

Expand All @@ -27,7 +27,9 @@ class QPUResult:
def __init__(
self, counts: Dict[int, int], num_qubits: int, measurement_dict: Dict[str, Sequence[int]]
):
self._counts = counts
# We require a consistent ordering, and here we use bitvector as such.
# OrderedDict can be removed in python 3.7, where it is part of the contract.
self._counts = collections.OrderedDict(sorted(counts.items()))
self._num_qubits = num_qubits
self._measurement_dict = measurement_dict
self._repetitions = sum(self._counts.values())
Expand All @@ -40,8 +42,38 @@ def repetitions(self) -> int:
"""Returns the number of times the circuit was run."""
return self._repetitions

def ordered_results(self, key: Optional[str] = None) -> List[int]:
"""Returns a list of arbitrarily but consistently ordered results as big endian ints.
If a key parameter is supplied, these are the counts for the measurement results for
the qubits measured by the measurement gate with that key. If no key is given, these
are the measurement results from measuring all qubits in the circuit.
The value in the returned list is the computational basis state measured for the
qubits that have been measured. This is expressed in big-endian form. For example, if
no measurement key is supplied and all qubits are measured, each entry in this returned dict
has a bit string where the `cirq.LineQubit`s are expressed in the order:
(cirq.LineQubit(0), cirq.LineQubit(1), ..., cirq.LineQubit(n-1))
In the case where only `r` qubits are measured corresponding to targets t_0, t_1,...t_{r-1},
the bit string corresponds to the order
(cirq.LineQubit(t_0), cirq.LineQubit(t_1), ... cirq.LineQubit(t_{r-1}))
"""

if key is not None and not key in self._measurement_dict:
raise ValueError(
f'Measurement key {key} is not a key for a measurement gate in the'
'circuit that produced these results.'
)
targets = self._measurement_dict[key] if key is not None else range(self.num_qubits())
result: List[int] = []
for value, count in self._counts.items():
bits = [(value >> (self.num_qubits() - target - 1)) & 1 for target in targets]
bit_value = sum(bit * (1 << i) for i, bit in enumerate(bits[::-1]))
result.extend([bit_value] * count)
return result

def counts(self, key: Optional[str] = None) -> Counter[int]:
"""Returns the raw counts of the measurement results.
"""Returns the processed counts of the measurement results.
If a key parameter is supplied, these are the counts for the measurement results for
the qubits measured by the measurement gate with that key. If no key is given, these
Expand All @@ -67,12 +99,8 @@ def counts(self, key: Optional[str] = None) -> Counter[int]:
f'Measurement key {key} is not a key for a measurement gate in the'
'circuit that produced these results.'
)
targets = self._measurement_dict[key]
result: Counter[int] = collections.Counter()
for value, count in self._counts.items():
bits = [(value >> (self.num_qubits() - target - 1)) & 1 for target in targets]
bit_value = sum(bit * (1 << i) for i, bit in enumerate(bits[::-1]))
result[bit_value] += count
result.update([bit_value for bit_value in self.ordered_results(key)])
return result

def measurement_dict(self) -> Dict[str, Sequence[int]]:
Expand All @@ -89,7 +117,8 @@ def to_cirq_result(
the IonQ API. Typically these results are also ordered by when they were run, though
that contract is implicit. Because the IonQ API does not retain that ordering information,
the order of these `cirq.Result` objects should *not* be interpetted as representing the
order in which the circuit was repeated.
order in which the circuit was repeated. Correlations between measurements keys are
preserved.
Args:
params: The `cirq.ParamResolver` used to generate these results.
Expand All @@ -108,7 +137,7 @@ def to_cirq_result(
)
measurements = {}
for key, targets in self.measurement_dict().items():
qpu_results = list(self.counts(key).elements())
qpu_results = self.ordered_results(key)
measurements[key] = np.array(
list(cirq.big_endian_int_to_bits(x, bit_count=len(targets)) for x in qpu_results)
)
Expand Down
15 changes: 15 additions & 0 deletions cirq-ionq/cirq_ionq/results_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,15 @@ def test_qpu_result_to_cirq_result():
# cirq.Result only compares pandas data frame, so possible to have supplied an list of
# list instead of a numpy multidimensional array. Check this here.
assert type(result.to_cirq_result().measurements['x']) == np.ndarray
# Results bitstreams need to be consistent betwween measurement keys
# Ordering is by bitvector, so 0b01 0b01 0b10 should be the ordering for all measurement dicts.
result = ionq.QPUResult(
{0b10: 1, 0b01: 2}, num_qubits=2, measurement_dict={'x': [0, 1], 'y': [0], 'z': [1]}
)
assert result.to_cirq_result() == cirq.Result(
params=cirq.ParamResolver({}),
measurements={'x': [[0, 1], [0, 1], [1, 0]], 'y': [[0], [0], [1]], 'z': [[1], [1], [0]]},
)


def test_qpu_result_to_cirq_result_multiple_keys():
Expand All @@ -138,6 +147,12 @@ def test_qpu_result_to_cirq_result_no_keys():
_ = result.to_cirq_result()


def test_ordered_results_invalid_key():
result = ionq.QPUResult({0b00: 1, 0b01: 2}, num_qubits=2, measurement_dict={'x': [1]})
with pytest.raises(ValueError, match='is not a key for'):
_ = result.ordered_results('y')


def test_simulator_result_fields():
result = ionq.SimulatorResult(
{0: 0.4, 1: 0.6},
Expand Down
2 changes: 1 addition & 1 deletion examples/deutsch.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def main():


def make_oracle(q0, q1, secret_function):
""" Gates implementing the secret function f(x)."""
"""Gates implementing the secret function f(x)."""

# coverage: ignore
if secret_function[0]:
Expand Down

0 comments on commit 4440232

Please sign in to comment.