From 972d201023a3c7e4e7c74bafee8a422bbfffa53b Mon Sep 17 00:00:00 2001 From: richrines1 <85512171+richrines1@users.noreply.github.com> Date: Wed, 13 Sep 2023 18:18:56 -0500 Subject: [PATCH] validate num_qubits in qscout compile (#766) --- cirq-superstaq/cirq_superstaq/service.py | 15 ++++++-- cirq-superstaq/cirq_superstaq/service_test.py | 37 +++++++++++++++++++ .../qiskit_superstaq/superstaq_backend.py | 15 ++++++-- .../superstaq_provider_test.py | 27 ++++++++++---- 4 files changed, 80 insertions(+), 14 deletions(-) diff --git a/cirq-superstaq/cirq_superstaq/service.py b/cirq-superstaq/cirq_superstaq/service.py index 9305f1f2c..4024e405b 100644 --- a/cirq-superstaq/cirq_superstaq/service.py +++ b/cirq-superstaq/cirq_superstaq/service.py @@ -581,6 +581,11 @@ def qscout_compile( **kwargs, } + if circuits_is_list: + max_circuit_qubits = max(cirq.num_qubits(c) for c in circuits) + else: + max_circuit_qubits = cirq.num_qubits(circuits) + if error_rates is not None: error_rates_list = list(error_rates.items()) options_dict["error_rates"] = error_rates_list @@ -590,9 +595,13 @@ def qscout_compile( max_index = max(q for qs, _ in error_rates_list for q in qs) num_qubits = max_index + 1 - if num_qubits is not None: - gss.validation.validate_integer_param(num_qubits) - options_dict["num_qubits"] = num_qubits + elif num_qubits is None: + num_qubits = max_circuit_qubits + + gss.validation.validate_integer_param(num_qubits) + if num_qubits < max_circuit_qubits: + raise ValueError(f"At least {max_circuit_qubits} qubits are required for this input.") + options_dict["num_qubits"] = num_qubits json_dict = self._client.qscout_compile( { diff --git a/cirq-superstaq/cirq_superstaq/service_test.py b/cirq-superstaq/cirq_superstaq/service_test.py index f2cf3450f..603fdb42f 100644 --- a/cirq-superstaq/cirq_superstaq/service_test.py +++ b/cirq-superstaq/cirq_superstaq/service_test.py @@ -430,6 +430,41 @@ def test_service_qscout_compile_single(mock_qscout_compile: mock.MagicMock) -> N service.qscout_compile(cirq.Circuit(), target="ss_example_qpu") +@mock.patch("general_superstaq.superstaq_client._SuperstaqClient.qscout_compile") +def test_service_qscout_compile_multiple(mock_qscout_compile: mock.MagicMock) -> None: + q0, q1 = cirq.LineQubit.range(2) + circuits = [ + cirq.Circuit(cirq.H(q0), cirq.measure(q0)), + cirq.Circuit(cirq.ISWAP(q0, q1)), + ] + final_logical_to_physicals = [{q0: q0}, {q0: q1, q1: q0}] + + jaqal_programs = ["jaqal", "programs"] + + mock_qscout_compile.return_value = { + "cirq_circuits": css.serialization.serialize_circuits(circuits), + "final_logical_to_physicals": cirq.to_json( + [list(l2p.items()) for l2p in final_logical_to_physicals] + ), + "jaqal_programs": jaqal_programs, + } + + service = css.Service(api_key="key", remote_host="http://example.com") + out = service.qscout_compile(circuits) + assert out.circuits == circuits + assert out.final_logical_to_physicals == final_logical_to_physicals + assert out.jaqal_programs == jaqal_programs + + assert json.loads(mock_qscout_compile.call_args[0][0]["options"]) == { + "mirror_swaps": False, + "base_entangling_gate": "xx", + "num_qubits": 2, + } + + with pytest.raises(ValueError, match="At least 2 qubits are required"): + _ = service.qscout_compile(circuits, num_qubits=1) + + @mock.patch("general_superstaq.superstaq_client._SuperstaqClient.qscout_compile") @pytest.mark.parametrize("mirror_swaps", (True, False)) def test_qscout_compile_swap_mirror( @@ -456,6 +491,7 @@ def test_qscout_compile_swap_mirror( assert json.loads(mock_qscout_compile.call_args[0][0]["options"]) == { "mirror_swaps": mirror_swaps, "base_entangling_gate": "xx", + "num_qubits": 1, } @@ -513,6 +549,7 @@ def test_qscout_compile_base_entangling_gate( assert json.loads(mock_qscout_compile.call_args[0][0]["options"]) == { "mirror_swaps": False, "base_entangling_gate": base_entangling_gate, + "num_qubits": 1, } diff --git a/qiskit-superstaq/qiskit_superstaq/superstaq_backend.py b/qiskit-superstaq/qiskit_superstaq/superstaq_backend.py index 00060bd87..7b5570931 100644 --- a/qiskit-superstaq/qiskit_superstaq/superstaq_backend.py +++ b/qiskit-superstaq/qiskit_superstaq/superstaq_backend.py @@ -354,6 +354,11 @@ def qscout_compile( "base_entangling_gate": base_entangling_gate, } + if isinstance(circuits, qiskit.QuantumCircuit): + max_circuit_qubits = circuits.num_qubits + else: + max_circuit_qubits = max(c.num_qubits for c in circuits) + if error_rates is not None: error_rates_list = list(error_rates.items()) options["error_rates"] = error_rates_list @@ -363,9 +368,13 @@ def qscout_compile( max_index = max(q for qs, _ in error_rates_list for q in qs) num_qubits = max_index + 1 - if num_qubits is not None: - gss.validation.validate_integer_param(num_qubits) - options["num_qubits"] = num_qubits + elif num_qubits is None: + num_qubits = max_circuit_qubits + + gss.validation.validate_integer_param(num_qubits) + if num_qubits < max_circuit_qubits: + raise ValueError(f"At least {max_circuit_qubits} qubits are required for this input.") + options["num_qubits"] = num_qubits request_json = self._get_compile_request_json(circuits, **options) json_dict = self._provider._client.qscout_compile(request_json) diff --git a/qiskit-superstaq/qiskit_superstaq/superstaq_provider_test.py b/qiskit-superstaq/qiskit_superstaq/superstaq_provider_test.py index 9f343b4fd..dcb6357e3 100644 --- a/qiskit-superstaq/qiskit_superstaq/superstaq_provider_test.py +++ b/qiskit-superstaq/qiskit_superstaq/superstaq_provider_test.py @@ -226,14 +226,23 @@ def test_qscout_compile( assert out.circuits == [qc] assert out.final_logical_to_physicals == [{0: 13}] + qc2 = qiskit.QuantumCircuit(2) mock_post.return_value.json = lambda: { - "qiskit_circuits": qss.serialization.serialize_circuits([qc, qc]), - "final_logical_to_physicals": json.dumps([[(0, 13)], [(0, 13)]]), + "qiskit_circuits": qss.serialization.serialize_circuits([qc, qc2]), + "final_logical_to_physicals": json.dumps([[(0, 13)], [(0, 13), (1, 11)]]), "jaqal_programs": [jaqal_program, jaqal_program], } - out = fake_superstaq_provider.qscout_compile([qc, qc]) - assert out.circuits == [qc, qc] - assert out.final_logical_to_physicals == [{0: 13}, {0: 13}] + out = fake_superstaq_provider.qscout_compile([qc, qc2]) + assert out.circuits == [qc, qc2] + assert out.final_logical_to_physicals == [{0: 13}, {0: 13, 1: 11}] + assert json.loads(mock_post.call_args.kwargs["json"]["options"]) == { + "mirror_swaps": False, + "base_entangling_gate": "xx", + "num_qubits": 2, + } + + with pytest.raises(ValueError, match="At least 2 qubits are required"): + _ = fake_superstaq_provider.qscout_compile([qc, qc2], num_qubits=1) def test_invalid_target_qscout_compile(fake_superstaq_provider: MockSuperstaqProvider) -> None: @@ -246,7 +255,7 @@ def test_invalid_target_qscout_compile(fake_superstaq_provider: MockSuperstaqPro def test_qscout_compile_swap_mirror( mock_post: MagicMock, mirror_swaps: bool, fake_superstaq_provider: MockSuperstaqProvider ) -> None: - qc = qiskit.QuantumCircuit() + qc = qiskit.QuantumCircuit(1) mock_post.return_value.json = lambda: { "qiskit_circuits": qss.serialization.serialize_circuits(qc), @@ -259,6 +268,7 @@ def test_qscout_compile_swap_mirror( assert json.loads(mock_post.call_args.kwargs["json"]["options"]) == { "mirror_swaps": mirror_swaps, "base_entangling_gate": "xx", + "num_qubits": 1, } _ = fake_superstaq_provider.qscout_compile(qc, mirror_swaps=mirror_swaps, num_qubits=3) @@ -274,7 +284,7 @@ def test_qscout_compile_swap_mirror( def test_qscout_compile_change_entangler( mock_post: MagicMock, base_entangling_gate: str, fake_superstaq_provider: MockSuperstaqProvider ) -> None: - qc = qiskit.QuantumCircuit() + qc = qiskit.QuantumCircuit(2) mock_post.return_value.json = lambda: { "qiskit_circuits": qss.serialization.serialize_circuits(qc), @@ -287,6 +297,7 @@ def test_qscout_compile_change_entangler( assert json.loads(mock_post.call_args.kwargs["json"]["options"]) == { "mirror_swaps": False, "base_entangling_gate": base_entangling_gate, + "num_qubits": 2, } _ = fake_superstaq_provider.qscout_compile( @@ -310,7 +321,7 @@ def test_qscout_compile_wrong_entangler(fake_superstaq_provider: MockSuperstaqPr def test_qscout_compile_error_rates( mock_post: MagicMock, fake_superstaq_provider: MockSuperstaqProvider ) -> None: - circuit = qiskit.QuantumCircuit() + circuit = qiskit.QuantumCircuit(1) mock_post.return_value.json = lambda: { "qiskit_circuits": qss.serialization.serialize_circuits(circuit),