From f682b9167fe0b83afcd1cead4185c6e6d03df9cc Mon Sep 17 00:00:00 2001 From: Jon Donovan Date: Wed, 4 Aug 2021 12:03:27 -0700 Subject: [PATCH 1/4] Retain ordering information in conversion. Previously, when you requested a cirq result with meaasurement 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. Comment slightly more Autopep8 some of the changes pyfmt Update cirq-ionq/cirq_ionq/results.py Use an iterable of tuples instead of a temporary dict. --- cirq-ionq/cirq_ionq/results.py | 49 +++++++++++++++++++++++------ cirq-ionq/cirq_ionq/results_test.py | 9 ++++++ 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/cirq-ionq/cirq_ionq/results.py b/cirq-ionq/cirq_ionq/results.py index 34960ebdb33..c1bd7825a86 100644 --- a/cirq-ionq/cirq_ionq/results.py +++ b/cirq-ionq/cirq_ionq/results.py @@ -13,7 +13,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 @@ -26,7 +26,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()) @@ -39,8 +41,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 @@ -66,12 +98,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]]: @@ -88,7 +116,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. @@ -107,7 +136,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 33bec6cbe55..b3af869128a 100644 --- a/cirq-ionq/cirq_ionq/results_test.py +++ b/cirq-ionq/cirq_ionq/results_test.py @@ -116,6 +116,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(): From 6a02e5a530cb1eae67ad590eb96610a054fe3255 Mon Sep 17 00:00:00 2001 From: jon donovan Date: Wed, 1 Dec 2021 09:34:19 -0800 Subject: [PATCH 2/4] fmt --- cirq-google/cirq_google/engine/abstract_local_program.py | 2 +- cirq-ionq/cirq_ionq/results.py | 4 ++-- examples/deutsch.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) 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 4f17618d933..5efe5c0513a 100644 --- a/cirq-ionq/cirq_ionq/results.py +++ b/cirq-ionq/cirq_ionq/results.py @@ -48,7 +48,7 @@ def ordered_results(self, key: Optional[str] = None) -> List[int]: 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 @@ -71,7 +71,7 @@ def ordered_results(self, key: Optional[str] = None) -> List[int]: 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 processed counts of the measurement results. 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]: From 79547a35a2df10ae68391306fa8ce701dd8b74a8 Mon Sep 17 00:00:00 2001 From: jon donovan Date: Tue, 14 Dec 2021 13:24:13 -0800 Subject: [PATCH 3/4] Appease test coverage check --- cirq-ionq/cirq_ionq/results_test.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cirq-ionq/cirq_ionq/results_test.py b/cirq-ionq/cirq_ionq/results_test.py index 0425c92255c..faa0a44e22f 100644 --- a/cirq-ionq/cirq_ionq/results_test.py +++ b/cirq-ionq/cirq_ionq/results_test.py @@ -146,6 +146,11 @@ def test_qpu_result_to_cirq_result_no_keys(): with pytest.raises(ValueError, match='cirq results'): _ = 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( From eee9431ee6b7317fdee746c3ff62e77cbe8c7fd9 Mon Sep 17 00:00:00 2001 From: jon donovan Date: Tue, 14 Dec 2021 15:09:10 -0800 Subject: [PATCH 4/4] Appease format check --- cirq-ionq/cirq_ionq/results_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cirq-ionq/cirq_ionq/results_test.py b/cirq-ionq/cirq_ionq/results_test.py index faa0a44e22f..b2e913103f3 100644 --- a/cirq-ionq/cirq_ionq/results_test.py +++ b/cirq-ionq/cirq_ionq/results_test.py @@ -146,6 +146,7 @@ def test_qpu_result_to_cirq_result_no_keys(): with pytest.raises(ValueError, match='cirq results'): _ = 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'):