Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions cirq-ionq/cirq_ionq/ionq_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ def __init__(
), f'Target can only be one of {self.SUPPORTED_TARGETS} but was {default_target}.'
assert max_retry_seconds >= 0, 'Negative retry not possible without time machine.'

self.url = f'{url.scheme}://{url.netloc}/{api_version}'
self.url_base = f'{url.scheme}://{url.netloc}'
self.url = f'{self.url_base}/{api_version}'
self.headers = self.api_headers(api_key)
self.default_target = default_target
self.max_retry_seconds = max_retry_seconds
Expand Down Expand Up @@ -220,7 +221,7 @@ def get_results(
extra_query_params: Specify any parameters to include in the request.

Returns:
extra_query_paramsresponse as a dict.
response as a dict.

Raises:
IonQNotFoundException: If job or results don't exist.
Expand Down Expand Up @@ -251,6 +252,24 @@ def request():

return self._make_request(request, {}).json()

def get_shots(self, shots_url):
"""Get job shotwise output from IonQ API.

Args:
shots_url: The shots URL as returned by the IonQ API.

Returns:
response as a dict.

Raises:
IonQException: For other API call failures.
"""

def request():
return requests.get(f"{self.url_base}/{shots_url}", headers=self.headers)

return self._make_request(request, {}).json()

def list_jobs(
self, status: str | None = None, limit: int = 100, batch_size: int = 1000
) -> list[dict[str, Any]]:
Expand Down
14 changes: 14 additions & 0 deletions cirq-ionq/cirq_ionq/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,18 @@ def results(
f'Job was not completed successfully. Instead had status: {self.status()}'
)

shotwise_results = None
retrieve_shotwise_result = self.target().startswith('qpu') or (
"noise" in self._job
and "model" in self._job["noise"]
and self._job["noise"]["model"] != "ideal"
)
if retrieve_shotwise_result:
try:
shotwise_results = self._client.get_shots(self._job["results"]["shots"]["url"])
except:
pass

backend_results = self._client.get_results(
job_id=self.job_id(), sharpen=sharpen, extra_query_params=extra_query_params
)
Expand Down Expand Up @@ -267,6 +279,7 @@ def results(
counts=counts,
num_qubits=self.num_qubits(circuit_index),
measurement_dict=self.measurement_dict(circuit_index=circuit_index),
shotwise_results=shotwise_results,
)
)
return big_endian_results_qpu
Expand All @@ -283,6 +296,7 @@ def results(
num_qubits=self.num_qubits(circuit_index),
measurement_dict=self.measurement_dict(circuit_index=circuit_index),
repetitions=self.repetitions(),
shotwise_results=shotwise_results,
)
)
return big_endian_results_sim
Expand Down
64 changes: 44 additions & 20 deletions cirq-ionq/cirq_ionq/results.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,19 @@ class QPUResult:
"""The results of running on an IonQ QPU."""

def __init__(
self, counts: dict[int, int], num_qubits: int, measurement_dict: dict[str, Sequence[int]]
self,
counts: dict[int, int],
num_qubits: int,
measurement_dict: dict[str, Sequence[int]],
shotwise_results: list[int] | None = None,
):
# 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())
self._shotwise_results = shotwise_results

def num_qubits(self) -> int:
"""Returns the number of qubits the circuit was run on."""
Expand Down Expand Up @@ -169,11 +174,13 @@ def __init__(
num_qubits: int,
measurement_dict: dict[str, Sequence[int]],
repetitions: int,
shotwise_results: list[int] | None = None,
):
self._probabilities = probabilities
self._num_qubits = num_qubits
self._measurement_dict = measurement_dict
self._repetitions = repetitions
self._shotwise_results = shotwise_results

def num_qubits(self) -> int:
"""Returns the number of qubits the circuit was run on."""
Expand Down Expand Up @@ -264,26 +271,43 @@ def to_cirq_result(
'Can convert to cirq results only if the circuit had measurement gates '
'with measurement keys.'
)
rand = cirq.value.parse_random_state(seed)
measurements = {}
values, weights = zip(*list(self.probabilities().items()))

# normalize weights to sum to 1 if within tolerance because
# IonQ's pauliexp gates results are not extremely precise
total = sum(weights)
if np.isclose(total, 1.0, rtol=0, atol=1e-5):
weights = tuple((w / total for w in weights))

indices = rand.choice(
range(len(values)), p=weights, size=override_repetitions or self.repetitions()
)
rand_values = np.array(values)[indices]
for key, targets in self.measurement_dict().items():
bits = [
[(value >> (self.num_qubits() - target - 1)) & 1 for target in targets]
for value in rand_values
]
measurements[key] = np.array(bits)
measurements = {}
print("SHOTWISE RESULTS:", self._shotwise_results)

if self._shotwise_results is not None:
for key, targets in self.measurement_dict().items():
# why do we need to reverse here? In QpuResult we don't do that ..
bits = [
list(cirq.big_endian_int_to_bits(int(x), bit_count=len(targets)))[::-1]
for x in self._shotwise_results
]
measurements[key] = np.array(bits)
else:
rand = cirq.value.parse_random_state(seed)
values, weights = zip(*list(self.probabilities().items()))
print("--------------------------------- Values:", values)
print("--------------------------------- Weights:", weights)
# normalize weights to sum to 1 if within tolerance because
# IonQ's pauliexp gates results are not extremely precise
total = sum(weights)
if np.isclose(total, 1.0, rtol=0, atol=1e-5):
weights = tuple((w / total for w in weights))

indices = rand.choice(
range(len(values)), p=weights, size=override_repetitions or self.repetitions()
)
print("INDICES:", indices)
rand_values = np.array(values)[indices]
print("RANDOM VALUES:", rand_values)
for key, targets in self.measurement_dict().items():
print(" **************** Key:", key, " targets:", targets)
bits = [
[(value >> (self.num_qubits() - target - 1)) & 1 for target in targets]
for value in rand_values
]
measurements[key] = np.array(bits)
print("Here are the measurement results: ", measurements)
return cirq.ResultDict(params=params or cirq.ParamResolver({}), measurements=measurements)

def __eq__(self, other):
Expand Down
Loading