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

Expose service errors to users #8

Merged
merged 10 commits into from
Jul 17, 2024
Merged
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
4 changes: 2 additions & 2 deletions ai-transpiler-demo.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -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",
")"
]
Expand Down Expand Up @@ -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",
")"
]
Expand Down
4 changes: 2 additions & 2 deletions documentation-examples.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
Expand Down Expand Up @@ -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)"
Expand Down
2 changes: 1 addition & 1 deletion qiskit_transpiler_service/transpiler_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
34 changes: 17 additions & 17 deletions qiskit_transpiler_service/wrappers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}")

Expand Down Expand Up @@ -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
2 changes: 1 addition & 1 deletion qiskit_transpiler_service/wrappers/transpile.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
):
Expand Down
87 changes: 81 additions & 6 deletions tests/test_transpiler_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}],
Expand All @@ -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}],
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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,
)

Expand All @@ -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

Expand All @@ -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
Expand Down