diff --git a/qiskit_ionq/helpers.py b/qiskit_ionq/helpers.py index ad06ecbe..a6fcbc49 100644 --- a/qiskit_ionq/helpers.py +++ b/qiskit_ionq/helpers.py @@ -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 @@ -59,9 +61,7 @@ "id", "mcp", "mcphase", - "mct", - "mcx", - "mcx_gray", + # no mcx for now until terra issue 6271 is resolved "measure", "p", "rx", @@ -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", @@ -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, @@ -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) @@ -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": { diff --git a/qiskit_ionq/ionq_backend.py b/qiskit_ionq/ionq_backend.py index d500818c..50f74bb6 100644 --- a/qiskit_ionq/ionq_backend.py +++ b/qiskit_ionq/ionq_backend.py @@ -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 @@ -210,15 +204,6 @@ def retrieve_jobs(self, job_ids): return [ionq_job.IonQJob(self, job_id, self.client) for job_id in job_ids] - # TODO: Implement backend status checks. - def status(self): - """Not yet implemented. - - Raises: - NotImplementedError: This behavior is not currently supported. - """ - raise NotImplementedError("Backend status check is not supported.") - def calibration(self): """Fetch the most recent calibration data for this backend. @@ -290,7 +275,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"}], diff --git a/qiskit_ionq/ionq_job.py b/qiskit_ionq/ionq_job.py index d6dfc447..0ada4643 100644 --- a/qiskit_ionq/ionq_job.py +++ b/qiskit_ionq/ionq_job.py @@ -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:: @@ -239,6 +239,9 @@ def result(self): Returns: Result: A Qiskit :class:`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 diff --git a/test/helpers/test_gate_serialization.py b/test/helpers/test_gate_serialization.py index 0ce0b43c..e4ff6b68 100644 --- a/test/helpers/test_gate_serialization.py +++ b/test/helpers/test_gate_serialization.py @@ -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]}]), diff --git a/test/helpers/test_qiskit_to_ionq.py b/test/helpers/test_qiskit_to_ionq.py index ad65d227..6e45781d 100644 --- a/test/helpers/test_qiskit_to_ionq.py +++ b/test/helpers/test_qiskit_to_ionq.py @@ -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 @@ -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 diff --git a/test/ionq_backend/test_base_backend.py b/test/ionq_backend/test_base_backend.py index 7032073e..e425cd6f 100644 --- a/test/ionq_backend/test_base_backend.py +++ b/test/ionq_backend/test_base_backend.py @@ -37,19 +37,6 @@ from .. import conftest -def test_status_not_implemented(mock_backend): - """Test that by default, `status` is not implemented. - - Args: - mock_backend (MockBackend): A fake/mock IonQBackend. - """ - - with pytest.raises(NotImplementedError) as exc_info: - mock_backend.status() - - assert str(exc_info.value) == "Backend status check is not supported." - - def test_client_property(mock_backend): """ Test that the client property is an IonQClient instance.