-
Notifications
You must be signed in to change notification settings - Fork 1
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
use pytket-qir for qir generation #24
Changes from 5 commits
7e7db89
1c98cb1
5d4309d
0c12bb4
aa00ced
85913e3
21e6a1f
0f3e250
3a89c6e
22c0856
0155316
f9afca6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
__extension_version__ = "0.3.0" | ||
__extension_version__ = "0.4.0rc0" | ||
__extension_name__ = "pytket-azure" | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,12 @@ | |
Changelog | ||
~~~~~~~~~ | ||
|
||
0.4.0rc0 (December 2024) | ||
------------------------ | ||
|
||
* Update minimum pytket version to 1.37.0. | ||
* Update minimum pytket-qir version to 0.19.0. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems incomplete, are there no other changes we should list? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have added a few more details, see remove qiskit, update changelog and version |
||
|
||
0.3.0 (October 2024) | ||
-------------------- | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,8 +19,6 @@ | |
from functools import cache | ||
from typing import Any, Optional, Union, cast | ||
|
||
from qiskit_qir import to_qir_module | ||
|
||
from azure.quantum import Job, Workspace | ||
from pytket.backends import Backend, CircuitStatus, ResultHandle, StatusEnum | ||
from pytket.backends.backend import KwargTypes | ||
|
@@ -30,9 +28,9 @@ | |
from pytket.backends.resulthandle import _ResultIdTuple | ||
from pytket.circuit import Circuit, OpType | ||
from pytket.extensions.azure._metadata import __extension_version__ | ||
from pytket.extensions.qiskit import tk_to_qiskit | ||
from pytket.passes import AutoRebase, BasePass | ||
from pytket.predicates import GateSetPredicate, Predicate | ||
from pytket.qir import QIRFormat, QIRProfile, pytket_to_qir | ||
from pytket.utils import OutcomeArray | ||
|
||
from .config import AzureConfig | ||
|
@@ -78,6 +76,26 @@ def _get_workspace( | |
} | ||
|
||
|
||
_ADDITIONAL_GATES = { | ||
cqc-alec marked this conversation as resolved.
Show resolved
Hide resolved
|
||
OpType.Reset, | ||
OpType.Measure, | ||
OpType.Barrier, | ||
OpType.RangePredicate, | ||
OpType.MultiBit, | ||
OpType.ExplicitPredicate, | ||
OpType.ExplicitModifier, | ||
OpType.SetBits, | ||
OpType.CopyBits, | ||
OpType.ClassicalExpBox, | ||
OpType.ClExpr, | ||
OpType.WASM, | ||
} | ||
|
||
|
||
_ALL_GATES = _ADDITIONAL_GATES.copy() | ||
_ALL_GATES.update(_GATE_SET) | ||
|
||
|
||
class AzureBackend(Backend): | ||
"""Interface to Azure Quantum.""" | ||
|
||
|
@@ -129,14 +147,16 @@ def __init__( | |
) | ||
_persistent_handles = False | ||
self._jobs: dict[ResultHandle, Job] = {} | ||
self._result_bits: dict[ResultHandle, list] = {} | ||
self._result_c_regs: dict[ResultHandle, list] = {} | ||
|
||
@property | ||
def backend_info(self) -> BackendInfo: | ||
return self._backendinfo | ||
|
||
@property | ||
def required_predicates(self) -> list[Predicate]: | ||
return [GateSetPredicate(_GATE_SET)] | ||
return [GateSetPredicate(_ALL_GATES)] | ||
|
||
def rebase_pass(self) -> BasePass: | ||
return AutoRebase(gateset=_GATE_SET) | ||
|
@@ -177,18 +197,33 @@ def process_circuits( | |
|
||
handles = [] | ||
for i, (c, n_shots) in enumerate(zip(circuits, n_shots_list)): | ||
qkc = tk_to_qiskit(c) | ||
module, entry_points = to_qir_module(qkc) | ||
assert len(entry_points) == 1 | ||
input_params = { | ||
"entryPoint": entry_points[0], | ||
"entryPoint": "main", | ||
"arguments": [], | ||
"count": n_shots, | ||
} | ||
if self._backendinfo.device_name == "ionq.simulator": | ||
module_bitcode = pytket_to_qir( | ||
c, | ||
qir_format=QIRFormat.BINARY, | ||
int_type=64, | ||
cut_pytket_register=False, | ||
profile=QIRProfile.AZUREBASE, | ||
) | ||
raise ValueError("ionq devices currently not supported") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than drop support for ionq can we continue to use the old method (using qiskit-qir) in this case? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had removed this, because this had issues with a lot of gates and it is unclear what is supported / should be supported. I have added this now back in with the same support as we had before. This is probably fine, as this is all not a state to use in production. |
||
else: | ||
cqc-alec marked this conversation as resolved.
Show resolved
Hide resolved
|
||
module_bitcode = pytket_to_qir( | ||
c, | ||
qir_format=QIRFormat.BINARY, | ||
int_type=64, | ||
cut_pytket_register=False, | ||
profile=QIRProfile.AZUREADAPTIVE, | ||
) | ||
|
||
if option_params is not None: | ||
input_params.update(option_params) # type: ignore | ||
job = self._target.submit( | ||
input_data=module.bitcode, | ||
input_data=module_bitcode, | ||
input_data_format="qir.v1", | ||
output_data_format="microsoft.quantum-results.v1", | ||
name=f"job_{i}", | ||
|
@@ -198,6 +233,8 @@ def process_circuits( | |
handle = ResultHandle(jobid) | ||
handles.append(handle) | ||
self._jobs[handle] = job | ||
self._result_bits[handle] = c.bits | ||
self._result_c_regs[handle] = c.c_registers | ||
for handle in handles: | ||
self._cache[handle] = dict() | ||
return handles | ||
|
@@ -210,15 +247,27 @@ def _update_cache_result( | |
else: | ||
self._cache[handle] = result_dict | ||
|
||
def _make_backend_result(self, results: Any, job: Job) -> BackendResult: | ||
def _make_backend_result( | ||
self, results: Any, job: Job, handle: ResultHandle | ||
) -> BackendResult: | ||
n_shots = job.details.input_params["count"] | ||
counts: Counter[OutcomeArray] = Counter() | ||
for s, p in results.items(): | ||
outcome = literal_eval(s) | ||
n = int(n_shots * p + 0.5) | ||
oa = OutcomeArray.from_readouts([outcome]) | ||
counts[oa] = n | ||
return BackendResult(counts=counts) | ||
assert len(outcome) == len(self._result_c_regs[handle]) | ||
list_bits: list = [] | ||
for res, creg in zip(outcome, self._result_c_regs[handle]): | ||
long_res = bin(int(res)).replace( | ||
"0b", | ||
"0000000000000000000000000000000000000\ | ||
00000000000000000000000000", # 0 * 63 | ||
) | ||
list_bits.append(long_res[len(long_res) - creg.size : len(long_res)]) | ||
all_bits = "".join(list_bits) | ||
|
||
counts[OutcomeArray.from_readouts([[int(x) for x in list(all_bits)]])] = n | ||
cqc-melf marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return BackendResult(counts=counts, c_bits=self._result_bits[handle]) | ||
|
||
def circuit_status(self, handle) -> CircuitStatus: | ||
job = self._jobs[handle] | ||
|
@@ -228,7 +277,7 @@ def circuit_status(self, handle) -> CircuitStatus: | |
results = job.get_results() | ||
self._update_cache_result( | ||
handle, | ||
{"result": self._make_backend_result(results, job)}, | ||
{"result": self._make_backend_result(results, job, handle)}, | ||
) | ||
return CircuitStatus(StatusEnum.COMPLETED) | ||
elif status == "Waiting": | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -56,9 +56,10 @@ | |
include_package_data=True, | ||
install_requires=[ | ||
"azure-quantum >= 2.2.0", | ||
"pytket >= 1.34.0", | ||
"pytket >= 1.37.0", | ||
"pytket-qiskit >= 0.58.0", | ||
"qiskit-qir >= 0.5.0", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we're not using it we can we remove this dependency (but as commented elsewhere I think we should continue to use it for other devices rather than stop supporting them). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have removed them, see remove qiskit, update changelog and version |
||
"pytket-qir >= 0.19.0", | ||
], | ||
classifiers=[ | ||
"Environment :: Console", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,18 +14,26 @@ | |
|
||
import os | ||
from collections import Counter | ||
from warnings import warn | ||
|
||
import pytest | ||
|
||
from pytket.circuit import Circuit | ||
from pytket.circuit import Circuit, Qubit, if_not_bit | ||
from pytket.circuit.logic_exp import ( | ||
reg_eq, | ||
reg_geq, | ||
reg_gt, | ||
reg_leq, | ||
reg_lt, | ||
reg_neq, | ||
) | ||
from pytket.extensions.azure import AzureBackend | ||
|
||
skip_remote_tests: bool = os.getenv("PYTKET_RUN_REMOTE_TESTS") is None | ||
REASON = "PYTKET_RUN_REMOTE_TESTS not set (requires Azure credentials)" | ||
|
||
|
||
@pytest.mark.skipif(skip_remote_tests, reason=REASON) | ||
@pytest.mark.skip(reason="resulthandling currently not supported for ionq") | ||
@pytest.mark.parametrize("azure_backend", ["ionq.simulator"], indirect=True) | ||
def test_ionq_simulator(azure_backend: AzureBackend) -> None: | ||
c = Circuit(2).H(0).CX(0, 1).measure_all() | ||
|
@@ -37,7 +45,9 @@ def test_ionq_simulator(azure_backend: AzureBackend) -> None: | |
counts = r.get_counts() | ||
assert counts == Counter({(0, 0): 5, (1, 1): 5}) | ||
else: | ||
warn("ionq.simulator unavailable or queue time >= 60s: not submitting") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why change this? I think it should be a warning not an error. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I found it really tricky to debug this with only getting warnings. If you want to, I can move back to the warnings, but I think as long as we are in the testing stage, it would be better to keep this as an error. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK |
||
raise ValueError( | ||
"quantinuum.sim.h1-1sc unavailable or queue time >= 60s: not submitting" | ||
) | ||
|
||
|
||
@pytest.mark.skipif(skip_remote_tests, reason=REASON) | ||
|
@@ -52,19 +62,148 @@ def test_quantinuum_sim_h11e(azure_backend: AzureBackend) -> None: | |
counts = r.get_counts() | ||
assert sum(counts.values()) == 1000 | ||
else: | ||
warn("quantinuum.sim.h1-1sc unavailable or queue time >= 60s: not submitting") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment (and likewise in other tests below). |
||
raise ValueError( | ||
"quantinuum.sim.h1-1sc unavailable or queue time >= 60s: not submitting" | ||
) | ||
|
||
|
||
@pytest.mark.skipif(skip_remote_tests, reason=REASON) | ||
@pytest.mark.parametrize("azure_backend", ["quantinuum.sim.h1-1sc"], indirect=True) | ||
def test_quantinuum_sim_h11e_two_regs(azure_backend: AzureBackend) -> None: | ||
c = Circuit(2, name="test_classical") | ||
a = c.add_c_register("a", 10) | ||
b = c.add_c_register("b", 11) | ||
|
||
c.Measure(Qubit(0), a[0]) | ||
c.Measure(Qubit(1), b[0]) | ||
|
||
a_b = azure_backend | ||
c1 = a_b.get_compiled_circuit(c) | ||
if a_b.is_available() and a_b.average_queue_time_s() < 60: | ||
h = a_b.process_circuit(c1, n_shots=1000) | ||
r = a_b.get_result(h, timeout=120) | ||
counts = r.get_counts() | ||
assert sum(counts.values()) == 1000 | ||
else: | ||
raise ValueError( | ||
"quantinuum.sim.h1-1sc unavailable or queue time >= 60s: not submitting" | ||
) | ||
|
||
|
||
@pytest.mark.skipif(skip_remote_tests, reason=REASON) | ||
@pytest.mark.parametrize("azure_backend", ["quantinuum.sim.h1-1sc"], indirect=True) | ||
def test_quantinuum_sim_h11e_complex(azure_backend: AzureBackend) -> None: | ||
c = Circuit(1, name="test_classical") | ||
a = c.add_c_register("a", 10) | ||
b = c.add_c_register("b", 11) | ||
d = c.add_c_register("d", 20) | ||
|
||
c.Measure(Qubit(0), a[0]) | ||
|
||
c.add_c_setbits([True, True] + [False] * 9, list(b)) | ||
|
||
c.add_classicalexpbox_register(a + b, d) # type: ignore | ||
a_b = azure_backend | ||
c1 = a_b.get_compiled_circuit(c) | ||
if a_b.is_available() and a_b.average_queue_time_s() < 60: | ||
h = a_b.process_circuit(c1, n_shots=1000) | ||
r = a_b.get_result(h, timeout=120) | ||
counts = r.get_counts() | ||
assert sum(counts.values()) == 1000 | ||
else: | ||
raise ValueError( | ||
"quantinuum.sim.h1-1sc unavailable or queue time >= 60s: not submitting" | ||
) | ||
|
||
|
||
@pytest.mark.skipif(skip_remote_tests, reason=REASON) | ||
@pytest.mark.parametrize("azure_backend", ["quantinuum.sim.h1-1sc"], indirect=True) | ||
def test_quantinuum_sim_h11e_cond(azure_backend: AzureBackend) -> None: | ||
c = Circuit(1, name="test_classical") | ||
a = c.add_c_register("a", 32) | ||
b = c.add_c_register("b", 32) | ||
d = c.add_c_register("d", 32) | ||
|
||
c.Measure(Qubit(0), a[0]) | ||
|
||
c.add_c_setreg(23, b) | ||
|
||
c.add_classicalexpbox_register(a + b, d) # type: ignore | ||
|
||
c.X(0, condition=a[0]) | ||
c.Measure(Qubit(0), b[4]) | ||
|
||
a_b = azure_backend | ||
c1 = a_b.get_compiled_circuit(c) | ||
if a_b.is_available() and a_b.average_queue_time_s() < 60: | ||
h = a_b.process_circuit(c1, n_shots=1000) | ||
r = a_b.get_result(h, timeout=120) | ||
counts = r.get_counts() | ||
assert sum(counts.values()) == 1000 | ||
else: | ||
raise ValueError( | ||
"quantinuum.sim.h1-1sc unavailable or queue time >= 60s: not submitting" | ||
) | ||
|
||
|
||
@pytest.mark.skipif(skip_remote_tests, reason=REASON) | ||
@pytest.mark.parametrize("azure_backend", ["quantinuum.sim.h1-1sc"], indirect=True) | ||
def test_quantinuum_sim_h11e_cond_2(azure_backend: AzureBackend) -> None: | ||
c = Circuit(1, name="test_classical") | ||
a = c.add_c_register("a", 32) | ||
b = c.add_c_register("b", 32) | ||
d = c.add_c_register("d", 32) | ||
|
||
c.Measure(Qubit(0), a[0]) | ||
|
||
c.add_c_setreg(23, b) | ||
|
||
c.add_classicalexpbox_register(a + b, d) # type: ignore | ||
c.add_classicalexpbox_register(a - b, d) # type: ignore | ||
c.add_classicalexpbox_register(a << 1, a) # type: ignore | ||
c.add_classicalexpbox_register(a >> 1, b) # type: ignore | ||
|
||
c.X(0, condition=reg_eq(a ^ b, 1)) | ||
c.X(0, condition=(a[0] ^ b[0])) | ||
c.X(0, condition=reg_eq(a & b, 1)) | ||
c.X(0, condition=reg_eq(a | b, 1)) | ||
|
||
c.X(0, condition=a[0]) | ||
c.Measure(Qubit(0), b[4]) | ||
|
||
c.X(0, condition=reg_neq(a, 1)) | ||
c.X(0, condition=if_not_bit(a[0])) | ||
c.X(0, condition=reg_gt(a, 1)) | ||
c.X(0, condition=reg_lt(a, 1)) | ||
c.X(0, condition=reg_geq(a, 1)) | ||
c.X(0, condition=reg_leq(a, 1)) | ||
c.Measure(Qubit(0), b[4]) | ||
a_b = azure_backend | ||
c1 = a_b.get_compiled_circuit(c) | ||
if a_b.is_available() and a_b.average_queue_time_s() < 60: | ||
h = a_b.process_circuit(c1, n_shots=1000) | ||
r = a_b.get_result(h, timeout=120) | ||
counts = r.get_counts() | ||
assert sum(counts.values()) == 1000 | ||
else: | ||
raise ValueError( | ||
"quantinuum.sim.h1-1sc unavailable or queue time >= 60s: not submitting" | ||
) | ||
|
||
|
||
@pytest.mark.skipif(skip_remote_tests, reason=REASON) | ||
@pytest.mark.parametrize("azure_backend", ["quantinuum.sim.h1-1e"], indirect=True) | ||
def test_quantinuum_option_params(azure_backend: AzureBackend) -> None: | ||
c = Circuit(2).H(0).CX(0, 1).measure_all() | ||
b = azure_backend | ||
c1 = b.get_compiled_circuit(c) | ||
if b.is_available() and b.average_queue_time_s() < 600: | ||
h = b.process_circuit(c1, n_shots=1000, option_params={"error_model": False}) # type: ignore | ||
r = b.get_result(h, timeout=1200) | ||
c = Circuit(2, 2).H(0).CX(0, 1).measure_all() | ||
a_b = azure_backend | ||
c1 = a_b.get_compiled_circuit(c) | ||
if a_b.is_available() and a_b.average_queue_time_s() < 600: | ||
h = a_b.process_circuit(c1, n_shots=1000, option_params={"error_model": False}) # type: ignore | ||
r = a_b.get_result(h, timeout=1200) | ||
counts = r.get_counts() | ||
assert all(x0 == x1 for x0, x1 in counts) | ||
assert all(x[0] == x[1] for x in counts) | ||
assert any(x[0] == 1 for x in counts) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is likely but not guaranteed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought this is a valid assumption for 1000 shots. If you want me to remove this, I will remove it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have added a comment to make this clear. |
||
else: | ||
warn("quantinuum.sim.h1-1e unavailable or queue time >= 600s: not submitting") | ||
raise ValueError( | ||
"quantinuum.sim.h1-1e unavailable or queue time >= 600s: not submitting" | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need an rc for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, see remove qiskit, update changelog and version
3a89c6e