Skip to content

Commit 12ff5c6

Browse files
committed
feat: Added cost_confidence API on jobs, hugr and qir
1 parent 2cf7c77 commit 12ff5c6

File tree

7 files changed

+198
-1
lines changed

7 files changed

+198
-1
lines changed

integration/test_jobs.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import pandas as pd
1010
import pytest
11+
from hugr.package import Package
1112
from pytket.backends.backendinfo import BackendInfo
1213
from pytket.backends.backendresult import BackendResult
1314
from pytket.circuit import Circuit
@@ -26,6 +27,7 @@
2627
ExecutionResultRef,
2728
IncompleteJobItemRef,
2829
JobRef,
30+
ProjectRef,
2931
Ref,
3032
)
3133

@@ -674,3 +676,35 @@ def test_job_cost(
674676
result_ref = results[0]
675677
assert isinstance(result_ref, ExecutionResultRef)
676678
assert result_ref.cost is not None
679+
680+
681+
def test_job_cost_confidence(
682+
test_case_name: str,
683+
create_project: Callable[[str], ContextManager[ProjectRef]],
684+
qa_hugr_package: Package,
685+
) -> None:
686+
"""Test the cost confidence of a Hugr program on a cost checking device,
687+
and that the cost confidence is available using jobs.cost_confidence()."""
688+
689+
with create_project(f"project for {test_case_name}") as project_ref:
690+
hugr_ref = qnx.hugr.upload(
691+
hugr_package=qa_hugr_package,
692+
name=f"hugr for {test_case_name}",
693+
project=project_ref,
694+
)
695+
696+
# Check that we can get a cost confidence estimate (using Helios-1SC)
697+
execute_job_ref = qnx.start_execute_job(
698+
programs=[hugr_ref],
699+
n_shots=[10],
700+
# No other parameters matter for cost estimation, so construct a minimal costing config
701+
backend_config=qnx.QuantinuumConfig(device_name="Helios-1SC"),
702+
project=project_ref,
703+
name="Circuit cost confidence estimation job",
704+
)
705+
706+
qnx.jobs.wait_for(execute_job_ref)
707+
708+
cost_confidence = qnx.jobs.cost_confidence(execute_job_ref)
709+
assert isinstance(cost_confidence, list)
710+
assert len(cost_confidence) > 0

integration/test_qir.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,32 @@ def test_costing_qir_on_NG_devices(
247247
assert isinstance(cost, float)
248248

249249

250+
def test_qir_cost_confidence(
251+
test_case_name: str,
252+
create_qir_in_project: Callable[[str, str, bytes], ContextManager[QIRRef]],
253+
) -> None:
254+
"""Test the cost confidence of a QIR program on a cost checking device."""
255+
256+
project_name = f"project for {test_case_name}"
257+
qir_name = f"qir for {test_case_name}"
258+
259+
with create_qir_in_project(
260+
project_name,
261+
qir_name,
262+
make_qir_bitcode_from_file("base.ll"),
263+
) as qir_ref:
264+
project_ref = qnx.projects.get(name=project_name)
265+
266+
# Check that we can get a cost confidence estimate (using Helios-1SC)
267+
cost_confidence = qnx.qir.cost_confidence(
268+
programs=[qir_ref],
269+
n_shots=[10],
270+
project=project_ref,
271+
)
272+
assert isinstance(cost_confidence, list)
273+
assert len(cost_confidence) > 0
274+
275+
250276
def make_qir_bitcode_from_file(filename: str) -> bytes:
251277
with open(
252278
Path("tests/data").resolve() / filename,

integration/test_qsys_jobs.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,27 @@ def test_hugr_costing(
114114
project=project_ref,
115115
)
116116
assert isinstance(cost, float)
117+
118+
119+
def test_hugr_cost_confidence(
120+
test_case_name: str,
121+
create_project: Callable[[str], ContextManager[ProjectRef]],
122+
qa_hugr_package: Package,
123+
) -> None:
124+
"""Test the cost confidence of a Hugr program on a cost checking device."""
125+
126+
with create_project(f"project for {test_case_name}") as project_ref:
127+
hugr_ref = qnx.hugr.upload(
128+
hugr_package=qa_hugr_package,
129+
name=f"hugr for {test_case_name}",
130+
project=project_ref,
131+
)
132+
133+
# Check that we can get a cost confidence estimate (using Helios-1SC)
134+
cost_confidence = qnx.hugr.cost_confidence(
135+
programs=[hugr_ref],
136+
n_shots=[10],
137+
project=project_ref,
138+
)
139+
assert isinstance(cost_confidence, list)
140+
assert len(cost_confidence) > 0

qnexus/client/circuits.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ def cost(
355355
syntax_checker_device_name = syntax_checker
356356

357357
if not syntax_checker_device_name.startswith("H2-"):
358-
raise ValueError("Cicuit cost estimation is only supported for H2-x systems.")
358+
raise ValueError("Circuit cost estimation is only supported for H2-x systems.")
359359

360360
if not syntax_checker_device_name.endswith("SC"):
361361
syntax_checker_device_name += "SC"

qnexus/client/hugr.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
uploaded to Nexus before stability is achieved might not work in the future.
55
"""
66

7+
import warnings
78
import base64
89
from datetime import datetime
910
from typing import Any, Literal, Union, cast
@@ -287,6 +288,11 @@ def cost(
287288
"""
288289
import qnexus as qnx
289290

291+
warnings.warn(
292+
"hugr.cost() is deprecated. Please update to use hugr.cost_confidence() instead.",
293+
category=DeprecationWarning,
294+
)
295+
290296
if isinstance(programs, HUGRRef):
291297
programs = [programs]
292298

@@ -302,6 +308,40 @@ def cost(
302308
return cast(float, status.cost)
303309

304310

311+
def cost_confidence(
312+
programs: HUGRRef | list[HUGRRef],
313+
n_shots: int | list[int],
314+
project: ProjectRef | None = None,
315+
system_name: Literal["Helios-1"] = "Helios-1",
316+
) -> list[tuple[float, float]]:
317+
"""Estimate the cost (in HQC) of running Hugr programs for n_shots
318+
number of shots on a Quantinuum Helios system.
319+
320+
Returns a list of tuples of (cost, confidence) for each job item.
321+
322+
NB: This will execute a costing job on a dedicated cost estimation device.
323+
Once run, the cost will be visible also in the Nexus web portal
324+
as part of the job.
325+
"""
326+
import qnexus as qnx
327+
328+
if isinstance(programs, HUGRRef):
329+
programs = [programs]
330+
331+
job_ref = qnx.start_execute_job(
332+
programs=cast(list[ExecutionProgram], programs),
333+
n_shots=n_shots,
334+
# No other parameters matter for cost estimation, so construct a minimal costing config
335+
backend_config=QuantinuumConfig(device_name=f"{system_name}SC"),
336+
project=project,
337+
name="Circuit cost confidence estimation job",
338+
)
339+
340+
qnx.jobs.wait_for(job_ref)
341+
342+
return qnx.jobs.cost_confidence(job_ref)
343+
344+
305345
@merge_scope_from_context
306346
def _fetch_by_id(
307347
hugr_id: UUID | str, scope: ScopeFilterEnum = ScopeFilterEnum.USER

qnexus/client/jobs/__init__.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
SystemRef,
6161
WasmModuleRef,
6262
)
63+
6364
from qnexus.models.scope import ScopeFilterEnum
6465
from qnexus.models.utils import assert_never
6566

@@ -659,6 +660,7 @@ def cost(
659660
job: CompileJobRef | ExecuteJobRef, scope: ScopeFilterEnum = ScopeFilterEnum.USER
660661
) -> float:
661662
"""Get the HQC cost of a job from a JobRef."""
663+
662664
resp = get_nexus_client().get(
663665
f"/api/jobs/v1beta3/{job.id}",
664666
params={"scope": scope.value},
@@ -671,3 +673,35 @@ def cost(
671673
job_status = resp_data["attributes"]["status"]
672674
cost = job_status.get("cost")
673675
return float(cost) if cost is not None else 0.0
676+
677+
678+
@merge_scope_from_context
679+
def cost_confidence(
680+
job: CompileJobRef | ExecuteJobRef, scope: ScopeFilterEnum = ScopeFilterEnum.USER
681+
) -> list[tuple[float, float]]:
682+
"""
683+
Get the HQC cost and confidence of a job from a JobRef.
684+
685+
Returns a list of tuples of (cost, confidence) for each job item.
686+
"""
687+
resp = get_nexus_client().get(
688+
f"/api/jobs/v1beta3/{job.id}", params={"scope": scope.value}
689+
)
690+
if resp.status_code != 200:
691+
raise qnx_exc.ResourceFetchFailed(
692+
message=resp.text, status_code=resp.status_code
693+
)
694+
695+
# Loop over all items from the API and gather cost_confidence_items
696+
resp_data = resp.json()["data"]
697+
items = resp_data["attributes"]["definition"]["items"]
698+
cost_confidence_items = []
699+
for i, item in enumerate(items):
700+
cost_confidence_items.append(
701+
(
702+
float(item["status"].get("cost", 0.0)),
703+
float(item["status"].get("cost_confidence", -1.0)),
704+
)
705+
)
706+
707+
return cost_confidence_items

qnexus/client/qir.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Client API for QIR in Nexus."""
22

3+
import warnings
34
import base64
45
from datetime import datetime
56
from typing import Any, Literal, Union, cast
@@ -266,6 +267,11 @@ def cost(
266267
"""
267268
import qnexus as qnx
268269

270+
warnings.warn(
271+
"qir.cost() is deprecated. Please update to use qir.cost_confidence() instead.",
272+
category=DeprecationWarning,
273+
)
274+
269275
if isinstance(programs, QIRRef):
270276
programs = [programs]
271277

@@ -281,6 +287,39 @@ def cost(
281287
return cast(float, status.cost)
282288

283289

290+
def cost_confidence(
291+
programs: QIRRef | list[QIRRef],
292+
n_shots: int | list[int],
293+
project: ProjectRef | None = None,
294+
system_name: Literal["Helios-1"] = "Helios-1",
295+
) -> list[tuple[float, float]]:
296+
"""Estimate the cost (in HQC) of running QIR programs for n_shots
297+
number of shots on a Quantinuum Helios system.
298+
299+
Returns a list of tuples of (cost, confidence) for each job item.
300+
301+
NB: This will execute a costing job on a dedicated cost estimation device.
302+
Once run, the cost will be visible also in the Nexus web portal
303+
as part of the job.
304+
"""
305+
import qnexus as qnx
306+
307+
if isinstance(programs, QIRRef):
308+
programs = [programs]
309+
310+
job_ref = qnx.start_execute_job(
311+
programs=cast(list[ExecutionProgram], programs),
312+
n_shots=n_shots,
313+
backend_config=QuantinuumConfig(device_name=f"{system_name}SC"),
314+
project=project,
315+
name="QIR cost estimation job",
316+
)
317+
318+
qnx.jobs.wait_for(job_ref)
319+
320+
return qnx.jobs.cost_confidence(job_ref)
321+
322+
284323
@merge_scope_from_context
285324
def _fetch_by_id(
286325
qir_id: UUID | str, scope: ScopeFilterEnum = ScopeFilterEnum.USER

0 commit comments

Comments
 (0)