Skip to content

Commit

Permalink
Make QuantinuumBackend.cost() safe (#298)
Browse files Browse the repository at this point in the history
  • Loading branch information
cqc-alec authored Dec 8, 2023
1 parent d800e69 commit 0f7b5fc
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 25 deletions.
8 changes: 8 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
Changelog
~~~~~~~~~

Unreleased
----------

* ``QuantinuumBackend.cost()`` now raises an error if the ``syntax_checker``
argument doesn't correspond to the device's reported syntax checker or if it
specifies a device that isn't a syntax checker; and the method returns 0 if
called on syntax-checker backends.

0.26.0 (November 2023)
----------------------

Expand Down
53 changes: 37 additions & 16 deletions pytket/extensions/quantinuum/backends/quantinuum.py
Original file line number Diff line number Diff line change
Expand Up @@ -1094,11 +1094,14 @@ def cost(
**kwargs: QuumKwargTypes,
) -> Optional[float]:
"""
Return the cost in HQC to complete this `circuit` with `n_shots`
repeats.
If the backend is not a syntax checker (backend name does not end with
"SC"), it is automatically appended
to check against the relevant syntax checker.
Return the cost in HQC to process this `circuit` with `n_shots`
repeats on this backend.
The cost is obtained by sending the circuit to a "syntax-checker"
backend, which incurs no cost itself but reports what the cost would be
for the actual backend (``self``).
If ``self`` is a syntax checker then the cost will be zero.
See :py:meth:`QuantinuumBackend.process_circuits` for the
supported kwargs.
Expand All @@ -1119,20 +1122,38 @@ def cost(
+ " Try running `backend.get_compiled_circuit` first"
)

if self._MACHINE_DEBUG:
return 0.0

assert self.backend_info is not None

if self.backend_info.get_misc("system_type") == "syntax checker":
return 0.0

try:
syntax_checker = (
syntax_checker
or cast(BackendInfo, self.backend_info).misc["syntax_checker"]
)
syntax_checker_name = self.backend_info.misc["syntax_checker"]
if syntax_checker is not None and syntax_checker != syntax_checker_name:
raise ValueError(
f"Device {self._device_name}'s syntax checker is "
"{syntax_checker_name} but a different syntax checker "
"({syntax_checker}) was specified. You should omit the "
"`syntax_checker` argument to ensure the correct one is "
"used."
)
except KeyError:
raise NoSyntaxChecker(
"Could not find syntax checker for this backend,"
" try setting one explicitly with the ``syntax_checker`` parameter"
)
if syntax_checker is not None:
syntax_checker_name = syntax_checker
else:
raise NoSyntaxChecker(
"Could not find syntax checker for this backend, "
"try setting one explicitly with the ``syntax_checker`` "
"parameter (it will normally have a name ending in 'SC')."
)
backend = QuantinuumBackend(syntax_checker_name, api_handler=self.api_handler)
assert backend.backend_info is not None
if backend.backend_info.get_misc("system_type") != "syntax checker":
raise ValueError(f"Device {backend._device_name} is not a syntax checker.")

backend = QuantinuumBackend(
cast(str, syntax_checker), api_handler=self.api_handler
)
try:
handle = backend.process_circuit(circuit, n_shots, kwargs=kwargs) # type: ignore
except DeviceNotAvailable as e:
Expand Down
51 changes: 42 additions & 9 deletions tests/integration/backend_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,19 +326,52 @@ def test_cost_estimate(
c = b.get_compiled_circuit(c)
estimate = None
if b._device_name.endswith("SC"):
with pytest.raises(NoSyntaxChecker) as e:
_ = b.cost(c, n_shots)
assert "Could not find syntax checker" in str(e.value)
estimate = b.cost(c, n_shots, syntax_checker=b._device_name, no_opt=False)
estimate = b.cost(c, n_shots)
assert estimate == 0.0
else:
# All other real hardware backends should have the
# "syntax_checker" misc property set, so there should be no
# need of providing it explicitly.
estimate = b.cost(c, n_shots, no_opt=False)
if estimate is None:
pytest.skip("API is flaky, sometimes returns None unexpectedly.")
assert isinstance(estimate, float)
assert estimate > 0.0
if estimate is None:
pytest.skip("API is flaky, sometimes returns None unexpectedly.")
assert isinstance(estimate, float)
assert estimate > 0.0


@pytest.mark.skipif(skip_remote_tests, reason=REASON)
@pytest.mark.parametrize(
"authenticated_quum_backend",
[
{"device_name": name}
for name in [
*pytest.ALL_QUANTUM_HARDWARE_NAMES, # type: ignore
]
],
indirect=True,
)
@pytest.mark.timeout(120)
def test_cost_estimate_wrong_syntax_checker(
authenticated_quum_backend: QuantinuumBackend,
) -> None:
b = authenticated_quum_backend
c = Circuit(1).PhasedX(0.5, 0.5, 0).measure_all()
with pytest.raises(ValueError):
_ = b.cost(c, 10, syntax_checker="H6-2SC")


@pytest.mark.skipif(skip_remote_tests, reason=REASON)
@pytest.mark.parametrize(
"authenticated_quum_backend", [{"device_name": "H1-1E"}], indirect=True
)
@pytest.mark.timeout(120)
def test_cost_estimate_bad_syntax_checker(
authenticated_quum_backend: QuantinuumBackend,
) -> None:
b = authenticated_quum_backend
c = Circuit(1).PhasedX(0.5, 0.5, 0).measure_all()
with pytest.raises(ValueError):
_ = b.cost(c, 10, syntax_checker="H2-1E")


@pytest.mark.skipif(skip_remote_tests, reason=REASON)
Expand Down Expand Up @@ -730,7 +763,7 @@ def test_wasm(

@pytest.mark.skipif(skip_remote_tests, reason=REASON)
@pytest.mark.parametrize(
"authenticated_quum_backend", [{"device_name": "H1-1SC"}], indirect=True
"authenticated_quum_backend", [{"device_name": "H1-1E"}], indirect=True
)
@pytest.mark.timeout(120)
def test_wasm_costs(
Expand Down

0 comments on commit 0f7b5fc

Please sign in to comment.