diff --git a/cirq-google/cirq_google/engine/abstract_local_program.py b/cirq-google/cirq_google/engine/abstract_local_program.py index e6dcb8e2a0b..f74885d08da 100644 --- a/cirq-google/cirq_google/engine/abstract_local_program.py +++ b/cirq-google/cirq_google/engine/abstract_local_program.py @@ -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) diff --git a/cirq-ionq/cirq_ionq/results.py b/cirq-ionq/cirq_ionq/results.py index d0f93f77fb8..5efe5c0513a 100644 --- a/cirq-ionq/cirq_ionq/results.py +++ b/cirq-ionq/cirq_ionq/results.py @@ -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 @@ -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()) @@ -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 @@ -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]]: @@ -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. @@ -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) ) diff --git a/cirq-ionq/cirq_ionq/results_test.py b/cirq-ionq/cirq_ionq/results_test.py index c7cebbf9faa..b2e913103f3 100644 --- a/cirq-ionq/cirq_ionq/results_test.py +++ b/cirq-ionq/cirq_ionq/results_test.py @@ -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(): @@ -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}, diff --git a/examples/deutsch.py b/examples/deutsch.py index 231b3367e62..18a96f21a14 100644 --- a/examples/deutsch.py +++ b/examples/deutsch.py @@ -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]: