Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide support for Amplitude Estimation algorithms and similar #69

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 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
33 changes: 16 additions & 17 deletions qiskit_ionq/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@

from qiskit.circuit import controlledgate as q_cgates
from qiskit.circuit.library import standard_gates as q_gates
from qiskit.qobj import QasmQobj
from qiskit.assembler import disassemble

from . import exceptions

Expand All @@ -59,9 +61,7 @@
"id",
"mcp",
"mcphase",
"mct",
"mcx",
"mcx_gray",
# no mcx for now until terra issue 6271 is resolved
"measure",
"p",
"rx",
Expand Down Expand Up @@ -90,8 +90,6 @@
q_gates.x.CCXGate: "cx", # just one C for all mcx
q_gates.x.C3XGate: "cx", # just one C for all mcx
q_gates.x.C4XGate: "cx", # just one C for all mcx
q_gates.x.MCXGate: "cx", # just one C for all mcx
q_gates.x.MCXGrayCode: "cx", # just one C for all mcx
q_gates.t.TdgGate: "ti",
q_gates.p.PhaseGate: "z",
q_gates.RXXGate: "xx",
Expand Down Expand Up @@ -193,11 +191,7 @@ def qiskit_circ_to_ionq_circ(input_circuit):

# Update converted gate values.
converted.update(
{
"gate": gate,
"controls": controls,
"targets": targets,
}
{"gate": gate, "controls": controls, "targets": targets,}
)

# if there's a valid instruction after a measurement,
Expand Down Expand Up @@ -292,17 +286,25 @@ def decompress_metadata_string_to_dict(input_string): # pylint: disable=invalid
return json.loads(decompressed)


def qiskit_to_ionq(circuit, backend_name, passed_args=None):
"""Convert a Qiskit circuit to a IonQ compatible dict.
def qiskit_to_ionq(circuit_or_qobj, backend_name, passed_args=None):
"""Serialize a Qiskit circuit to a IonQ compatible dict.

Parameters:
circuit (:class:`qiskit.circuit.QuantumCircuit`): A Qiskit quantum circuit.
circuit_or_qobj (:class:`qiskit.circuit.QuantumCircuit` or :class:`qiskit.qobj.QasmQobj`):
A Qiskit quantum circuit or qobj containing a circuit in QASM.
backend_name (str): Backend name.
passed_args (dict): Dictionary containing additional passed arguments, eg. shots.

Returns:
dict: A dict with IonQ API compatible values.
"""
circuit = circuit_or_qobj
# if the submit job method gets passed a Qobj (possible via e.g. the Qiskit.execute method),
# pull the circuit off the Qobj. We don't need any of the rest of the context it provides.
if isinstance(circuit_or_qobj, QasmQobj):
circuit_list, _, _ = disassemble(circuit_or_qobj)
circuit = circuit_list.pop()

passed_args = passed_args or {}
ionq_circ, _, meas_map = qiskit_circ_to_ionq_circ(circuit)
creg_sizes, clbit_labels = get_register_sizes_and_labels(circuit.cregs)
Expand All @@ -324,10 +326,7 @@ def qiskit_to_ionq(circuit, backend_name, passed_args=None):
"lang": "json",
"target": backend_name[5:],
"shots": passed_args.get("shots"),
"body": {
"qubits": circuit.num_qubits,
"circuit": ionq_circ,
},
"body": {"qubits": circuit.num_qubits, "circuit": ionq_circ,},
"registers": {"meas_mapped": meas_map},
# store a couple of things we'll need later for result formatting
"metadata": {
Expand Down
10 changes: 2 additions & 8 deletions qiskit_ionq/ionq_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,13 +191,7 @@ def run(self, circuit, **kwargs):
kwargs["shots"] = self.options.shots
passed_args = kwargs

job = ionq_job.IonQJob(
self,
None,
self.client,
circuit=circuit,
passed_args=passed_args,
)
job = ionq_job.IonQJob(self, None, self.client, circuit=circuit, passed_args=passed_args,)
job.submit()
return job

Expand Down Expand Up @@ -290,7 +284,7 @@ def __init__(self, provider):
"memory": False,
"n_qubits": 29,
"conditional": False,
"max_shots": 1,
"max_shots": None,
"max_experiments": 1,
"open_pulse": False,
"gates": [{"name": "TODO", "parameters": [], "qasm_def": "TODO"}],
Expand Down
5 changes: 4 additions & 1 deletion qiskit_ionq/ionq_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ def get_probabilities(self, circuit=None): # pylint: disable=unused-argument
"""
return self.result().get_probabilities()

def result(self):
def result(self, **kwargs):
"""Retrieve job result data.

.. NOTE::
Expand All @@ -239,6 +239,9 @@ def result(self):
Returns:
Result: A Qiskit :class:`Result <qiskit.result.Result>` representation of this job.
"""
# we ignore all result args like timeout etc, but consume them for compatibility reasons
_ = kwargs

# Short-circuit if we have already cached the result for this job.
if self._result is not None:
return self._result
Expand Down
6 changes: 0 additions & 6 deletions test/helpers/test_gate_serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,6 @@
("i", [0], []),
("id", [0], []),
("mcp", [0.5, [0, 1], 2], [{"gate": "z", "rotation": 0.5, "targets": [2], "controls": [0, 1]}]),
("mct", [[0, 1], 2], [{"gate": "x", "targets": [2], "controls": [0, 1]}]),
("mcx", [[0, 1], 2], [{"gate": "x", "targets": [2], "controls": [0, 1]}]),
# make sure that multi-control can take any number of controls
("mcx", [[0, 1, 2], 3], [{"gate": "x", "targets": [3], "controls": [0, 1, 2]}]),
("mcx", [[0, 1, 2, 3], 4], [{"gate": "x", "targets": [4], "controls": [0, 1, 2, 3]}]),
("mcx", [[0, 1, 2, 3, 4], 5], [{"gate": "x", "targets": [5], "controls": [0, 1, 2, 3, 4]}]),
("measure", [0, 0], []),
("p", [0, 0], [{"gate": "z", "rotation": 0, "targets": [0]}]),
("p", [0.5, 0], [{"gate": "z", "rotation": 0.5, "targets": [0]}]),
Expand Down
56 changes: 56 additions & 0 deletions test/helpers/test_qiskit_to_ionq.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import json

from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit.compiler import assemble

from qiskit_ionq.helpers import qiskit_to_ionq, decompress_metadata_string_to_dict

Expand Down Expand Up @@ -190,3 +191,58 @@ def test_full_circuit(simulator_backend):
assert actual_metadata_header == expected_metadata_header
assert actual_output_map == expected_output_map
assert actual == expected_rest_of_payload


def test_full_circuit_from_qobj(simulator_backend):
"""Test a full circuit

Args:
simulator_backend (IonQSimulatorBackend): A simulator backend fixture.
"""
qc = QuantumCircuit(2, 2, name="test_name")
qc.cnot(1, 0)
qc.h(1)
qc.measure(1, 0)
qc.measure(0, 1)
qobj = assemble(qc, backend=simulator_backend, shots=200)
ionq_json = qiskit_to_ionq(
qobj, simulator_backend.name(), passed_args={"shots": 200, "sampler_seed": 42}
)
expected_metadata_header = {
"memory_slots": 2,
"global_phase": 0,
"n_qubits": 2,
"name": "test_name",
"creg_sizes": [["c", 2]],
"clbit_labels": [["c", 0], ["c", 1]],
"qreg_sizes": [["q", 2]],
"qubit_labels": [["q", 0], ["q", 1]],
}
expected_output_map = [1, 0]
expected_metadata = {"shots": "200", "sampler_seed": "42"}
expected_rest_of_payload = {
"lang": "json",
"target": "simulator",
"shots": 200,
"body": {
"qubits": 2,
"circuit": [
{"gate": "x", "controls": [1], "targets": [0]},
{"gate": "h", "targets": [1]},
],
},
}

actual = json.loads(ionq_json)
actual_metadata = actual.pop("metadata") or {}
actual_metadata_header = decompress_metadata_string_to_dict(
actual_metadata.pop("qiskit_header") or None
)
actual_maps = actual.pop("registers") or {}
actual_output_map = actual_maps.pop("meas_mapped") or []

# check dict equality:
assert actual_metadata == expected_metadata
assert actual_metadata_header == expected_metadata_header
assert actual_output_map == expected_output_map
assert actual == expected_rest_of_payload