diff --git a/ai-transpiler-demo.ipynb b/ai-transpiler-demo.ipynb index d4c0c42..8f1cf23 100644 --- a/ai-transpiler-demo.ipynb +++ b/ai-transpiler-demo.ipynb @@ -212,7 +212,7 @@ "\n", "qiskit_lvl3_transpiler_service = TranspilerService(\n", " backend_name=\"ibm_torino\",\n", - " ai='false',\n", + " ai=\"false\",\n", " optimization_level=3,\n", ")" ] @@ -281,7 +281,7 @@ "source": [ "qiskit_lvl3_ai_transpiler_service = TranspilerService(\n", " backend_name=\"ibm_torino\",\n", - " ai='true',\n", + " ai=\"true\",\n", " optimization_level=3,\n", ")" ] diff --git a/documentation-examples.ipynb b/documentation-examples.ipynb index 494179d..6d9bfca 100644 --- a/documentation-examples.ipynb +++ b/documentation-examples.ipynb @@ -22,7 +22,7 @@ "\n", "cloud_transpiler_service = TranspilerService(\n", " backend_name=\"ibm_sherbrooke\",\n", - " ai='false',\n", + " ai=\"false\",\n", " optimization_level=3,\n", ")\n", "transpiled_circuit_no_ai = cloud_transpiler_service.run(circuit)" @@ -50,7 +50,7 @@ "\n", "cloud_transpiler_service = TranspilerService(\n", " backend_name=\"ibm_sherbrooke\",\n", - " ai='true',\n", + " ai=\"true\",\n", " optimization_level=1,\n", ")\n", "transpiled_circuit_ai = cloud_transpiler_service.run(circuit)" diff --git a/qiskit_transpiler_service/transpiler_service.py b/qiskit_transpiler_service/transpiler_service.py index 9741123..6b6ee6c 100644 --- a/qiskit_transpiler_service/transpiler_service.py +++ b/qiskit_transpiler_service/transpiler_service.py @@ -57,7 +57,7 @@ class TranspilerService: def __init__( self, optimization_level: int, - ai: Literal['true', 'false', 'auto'] = 'true', + ai: Literal["true", "false", "auto"] = "true", coupling_map: Union[List[List[int]], None] = None, backend_name: Union[str, None] = None, qiskit_transpile_options: Dict = None, diff --git a/qiskit_transpiler_service/wrappers/base.py b/qiskit_transpiler_service/wrappers/base.py index e7afdb9..0b68ddc 100644 --- a/qiskit_transpiler_service/wrappers/base.py +++ b/qiskit_transpiler_service/wrappers/base.py @@ -129,9 +129,9 @@ def request_and_wait(self, endpoint: str, body: Dict, params: Dict): return self._request_and_wait(endpoint, body, params) except requests.exceptions.HTTPError as exc: _raise_transpiler_error_and_log(_get_error_msg_from_response(exc)) - except BackendTaskError: - # TODO: Do we want to give the user the internal error: "The background task df86e449-8bb5-43fb-92a9-c23818ee8ce7 FAILED" - _raise_transpiler_error_and_log("Service error.") + except BackendTaskError as exc: + error_msg = exc.msg or "Service error." + _raise_transpiler_error_and_log(error_msg) except Exception as exc: _raise_transpiler_error_and_log(f"Error: {exc}") @@ -173,18 +173,18 @@ def _raise_transpiler_error_and_log(msg: str): def _get_error_msg_from_response(exc: requests.exceptions.HTTPError): - if exc.response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY.value: - # Default message - msg = "Internal error." - resp = exc.response.json() - if detail := resp.get("detail"): - if isinstance(detail, str): - msg = detail - elif isinstance(detail, list): - # Try to get err msg from - # raw validation error - if "input" in resp["detail"][0] and "msg" in resp["detail"][0]: - msg = f"Wrong input '{resp['detail'][0]['input']}'. {resp['detail'][0]['msg']}" - else: - msg = f"Service error: {str(exc)}" + resp = exc.response.json() + detail = resp.get("detail") + # Default message + msg = "Internal error." + + if isinstance(detail, str): + msg = detail + elif isinstance(detail, list): + detail_input = detail[0]["input"] + detail_msg = detail[0]["msg"] + + if detail_input and detail_msg: + msg = f"Wrong input '{detail_input}'. {detail_msg}" + return msg diff --git a/qiskit_transpiler_service/wrappers/transpile.py b/qiskit_transpiler_service/wrappers/transpile.py index 053b126..96f02ef 100644 --- a/qiskit_transpiler_service/wrappers/transpile.py +++ b/qiskit_transpiler_service/wrappers/transpile.py @@ -43,7 +43,7 @@ def transpile( optimization_level: int = 1, backend: Union[str, None] = None, coupling_map: Union[List[List[int]], None] = None, - ai: Literal['true', 'false', 'auto'] = 'true', + ai: Literal["true", "false", "auto"] = "true", qiskit_transpile_options: Dict = None, ai_layout_mode: str = None, ): diff --git a/tests/test_transpiler_service.py b/tests/test_transpiler_service.py index 4661ab0..b6ba796 100644 --- a/tests/test_transpiler_service.py +++ b/tests/test_transpiler_service.py @@ -32,7 +32,7 @@ @pytest.mark.parametrize( "optimization_level", [1, 2, 3], ids=["opt_level_1", "opt_level_2", "opt_level_3"] ) -@pytest.mark.parametrize("ai", ['false', 'true'], ids=["no_ai", "ai"]) +@pytest.mark.parametrize("ai", ["false", "true"], ids=["no_ai", "ai"]) @pytest.mark.parametrize( "qiskit_transpile_options", [None, {"seed_transpiler": 0}], @@ -56,7 +56,7 @@ def test_rand_circ_backend_routing(optimization_level, ai, qiskit_transpile_opti @pytest.mark.parametrize( "optimization_level", [1, 2, 3], ids=["opt_level_1", "opt_level_2", "opt_level_3"] ) -@pytest.mark.parametrize("ai", ['false', 'true'], ids=["no_ai", "ai"]) +@pytest.mark.parametrize("ai", ["false", "true"], ids=["no_ai", "ai"]) @pytest.mark.parametrize( "qiskit_transpile_options", [None, {"seed_transpiler": 0}], @@ -85,7 +85,7 @@ def test_qv_backend_routing(optimization_level, ai, qiskit_transpile_options): ], ) @pytest.mark.parametrize("optimization_level", [1, 2, 3]) -@pytest.mark.parametrize("ai", ['false', 'true'], ids=["no_ai", "ai"]) +@pytest.mark.parametrize("ai", ["false", "true"], ids=["no_ai", "ai"]) @pytest.mark.parametrize("qiskit_transpile_options", [None, {"seed_transpiler": 0}]) def test_rand_circ_cmap_routing( coupling_map, optimization_level, ai, qiskit_transpile_options @@ -108,7 +108,7 @@ def test_qv_circ_several_circuits_routing(): cloud_transpiler_service = TranspilerService( backend_name="ibm_brisbane", - ai='true', + ai="true", optimization_level=1, ) transpiled_circuit = cloud_transpiler_service.run([qv_circ] * 2) @@ -129,7 +129,7 @@ def test_qv_circ_wrong_input_routing(): cloud_transpiler_service = TranspilerService( backend_name="ibm_brisbane", - ai='true', + ai="true", optimization_level=1, ) @@ -138,7 +138,7 @@ def test_qv_circ_wrong_input_routing(): cloud_transpiler_service.run(circ_dict) -@pytest.mark.parametrize("ai", ['false', 'true'], ids=["no_ai", "ai"]) +@pytest.mark.parametrize("ai", ["false", "true"], ids=["no_ai", "ai"]) def test_transpile_layout_reconstruction(ai): n_qubits = 27 @@ -161,6 +161,81 @@ def test_transpile_layout_reconstruction(ai): ) +def test_transpile_non_valid_backend(): + circuit = EfficientSU2(100, entanglement="circular", reps=1).decompose() + non_valid_backend_name = "ibm_torin" + transpiler_service = TranspilerService( + backend_name=non_valid_backend_name, + ai="false", + optimization_level=3, + ) + + try: + transpiler_service.run(circuit) + pytest.fail("Error expected") + except Exception as e: + assert ( + str(e) + == f'"User doesn\'t have access to the specified backend: {non_valid_backend_name}"' + ) + + +def test_transpile_exceed_circuit_size(): + circuit = EfficientSU2(100, entanglement="circular", reps=50).decompose() + transpiler_service = TranspilerService( + backend_name="ibm_kyoto", + ai="false", + optimization_level=3, + ) + + try: + transpiler_service.run(circuit) + pytest.fail("Error expected") + except Exception as e: + assert str(e) == "'Circuit has more gates than the allowed maximum of 5000.'" + + +def test_transpile_malformed_body(): + circuit = EfficientSU2(100, entanglement="circular", reps=1).decompose() + transpiler_service = TranspilerService( + backend_name="ibm_kyoto", + ai="false", + optimization_level=3, + qiskit_transpile_options={"failing_option": 0}, + ) + + try: + transpiler_service.run(circuit) + pytest.fail("Error expected") + except Exception as e: + assert ( + str(e) + == "\"transpile() got an unexpected keyword argument 'failing_option'\"" + ) + + +def test_transpile_failing_task(): + open_qasm_circuit = 'OPENQASM 2.0;\ninclude "qelib1.inc";\ngate dcx q0,q1 { cx q0,q1; cx q1,q0; }\nqreg q[3];\ncz q[0],q[2];\nsdg q[1];\ndcx q[2],q[1];\nu3(3.890139082217223,3.447697582994976,1.1583481971959322) q[0];\ncrx(2.3585459177723522) q[1],q[0];\ny q[2];' + circuit = QuantumCircuit.from_qasm_str(open_qasm_circuit) + transpiler_service = TranspilerService( + backend_name="ibm_kyoto", + ai="false", + optimization_level=3, + coupling_map=[[1, 2], [2, 1]], + qiskit_transpile_options={ + "basis_gates": ["u1", "u2", "u3", "cx"], + "seed_transpiler": 0, + }, + ) + + try: + transpiler_service.run(circuit) + pytest.fail("Error expected") + except Exception as e: + assert "The background task" in str(e) + assert "FAILED" in str(e) + + def compare_layouts(plugin_circ, non_ai_circ): assert ( plugin_circ.layout.initial_layout == non_ai_circ.layout.initial_layout