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

use pytket-qir for qir generation #24

Merged
merged 12 commits into from
Dec 20, 2024
2 changes: 1 addition & 1 deletion _metadata.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
__extension_version__ = "0.3.0"
__extension_version__ = "0.4.0"
__extension_name__ = "pytket-azure"
9 changes: 8 additions & 1 deletion docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@
Changelog
~~~~~~~~~

0.4.0 (unreleased)
------------------

* Update minimum pytket version to 1.37.0.
* Update minimum pytket-qir version to 0.19.0.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems incomplete, are there no other changes we should list?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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
3a89c6e

* Use pytket-qir for the QIR generation
* remove qiskit-qir and pytket-qiskit as dependencies

0.3.0 (October 2024)
--------------------

Expand All @@ -11,7 +19,6 @@ Changelog
* Update minimum pytket version to 1.34.0.
* Update minimum pytket-qiskit version to 0.58.0.


0.2.0 (October 2024)
---------------------

Expand Down
99 changes: 82 additions & 17 deletions pytket/extensions/azure/backends/azure.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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."""

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -177,18 +197,38 @@ 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
and self._backendinfo.device_name[:11] == "quantinuum."
):

module_bitcode = pytket_to_qir(
c,
qir_format=QIRFormat.BINARY,
int_type=64,
cut_pytket_register=False,
profile=QIRProfile.AZUREADAPTIVE,
)
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.AZUREBASE,
)

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}",
Expand All @@ -198,6 +238,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
Expand All @@ -210,15 +252,38 @@ 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)
if (
self._backendinfo.device_name
and self._backendinfo.device_name[:11] == "quantinuum."
):
for s, p in results.items():
outcome = literal_eval(s)
n = int(n_shots * p + 0.5)
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[-creg.size :])
all_bits = "".join(list_bits)

counts[OutcomeArray.from_readouts([[int(x) for x in all_bits]])] = n
return BackendResult(counts=counts, c_bits=self._result_bits[handle])
else:
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)

def circuit_status(self, handle) -> CircuitStatus:
job = self._jobs[handle]
Expand All @@ -228,7 +293,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":
Expand Down
5 changes: 2 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,8 @@
include_package_data=True,
install_requires=[
"azure-quantum >= 2.2.0",
"pytket >= 1.34.0",
"pytket-qiskit >= 0.58.0",
"qiskit-qir >= 0.5.0",
"pytket >= 1.37.0",
"pytket-qir >= 0.19.0",
],
classifiers=[
"Environment :: Console",
Expand Down
Loading
Loading