Source code for pytket.qir.conversion.api

# Copyright Quantinuum
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
public api for qir conversion from pytket
"""

from enum import Enum
from typing import TYPE_CHECKING

import pyqir

from pytket.circuit import Circuit
from pytket.passes import (
    scratch_reg_resize_pass,
)

from .azurebaseprofileqirgenerator import AzureBaseProfileQirGenerator
from .azureprofileqirgenerator import AzureAdaptiveProfileQirGenerator
from .baseprofileqirgenerator import BaseProfileQirGenerator
from .module import tketqirModule
from .profileqirgenerator import AdaptiveProfileQirGenerator
from .pytketqirgenerator import PytketQirGenerator

if TYPE_CHECKING:
    from .qirgenerator import AbstractQirGenerator


[docs] class QIRFormat(Enum): """Return types qir, options are BINARY for a binary output and STRING for a string output """ BINARY = 0 STRING = 1
[docs] class QIRProfile(Enum): """Profile for the QIR generation""" BASE = 0 AZUREBASE = 1 ADAPTIVE = 2 AZUREADAPTIVE = 3 ADAPTIVE_CREGSIZE = 4 PYTKET = 5
class ClassicalRegisterWidthError(Exception): """Error trying to convert a circuit with a classical register exceeding the maximum width""" def __init__( self, width: int, max_width: int = 64, hint: str | None = None ) -> None: self.width = width self.max_width = max_width self.hint = hint msg = ( f"Classical register of width {width} exceeds maximum width ({max_width})." ) if hint is not None: msg += f" Hint: {hint}." super().__init__(msg)
[docs] def pytket_to_qir( # noqa: PLR0912, PLR0913 circ: Circuit, name: str = "Generated from input pytket circuit", qir_format: QIRFormat = QIRFormat.BINARY, int_type: int = 64, cut_pytket_register: bool = False, profile: QIRProfile = QIRProfile.PYTKET, ) -> str | bytes | None: """converts given pytket circuit to qir :param circ: given circuit :param name: name for the qir module created :param qir_format: format of the generated qir, default value is binary :param wfh: wasm file handler used when creating the circuit. Only needed when there are wasm calls in the circuit. :param int_type: size of each integer, allowed value 32 and 64 :param cut_pytket_register: breaks up the internal scratch bit registers into smaller registers, default value false :param profile: generates QIR corresponding to the selected profile: - Use ``QIRProfile.BASE`` for the base profile. See: https://github.com/qir-alliance/qir-spec/blob/main/specification/under_development/profiles/Base_Profile.md - Use ``QIRProfile.ADAPTIVE`` for the adaptive profile. See: https://github.com/qir-alliance/qir-spec/tree/main/specification/under_development/profiles/Adaptive_Profile.md - Use ``QIRProfile.ADAPTIVE_CREGSIZE`` for the adaptive profile with additional truncation operations to assure that integers matching the classical registers have no unexpected set bits. See: https://github.com/qir-alliance/qir-spec/tree/main/specification/under_development/profiles/Adaptive_Profile.md - Use ``QIRProfile.PYTKET`` for QIR with additional functions for classical registers. - Use ``QIRProfile.AZUREBASE`` for the base profile with metadata for Azure devices and ``array_record`` for output recording. - Use ``QIRProfile.AZUREADAPTIVE`` for the adaptive profile with metadata for Azure devices and bitwise recording of results via ``array_record``. """ if cut_pytket_register: cpass = scratch_reg_resize_pass(int_type) cpass.apply(circ) check_circuit(circ, int_type) m = tketqirModule( name=name, num_qubits=circ.n_qubits, num_results=circ.n_qubits, ) trunc = False if profile == QIRProfile.ADAPTIVE_CREGSIZE: trunc = True if profile == QIRProfile.BASE: qir_generator: AbstractQirGenerator = BaseProfileQirGenerator( circuit=circ, module=m, wasm_int_type=int_type, qir_int_type=int_type, ) elif profile == QIRProfile.AZUREBASE: qir_generator = AzureBaseProfileQirGenerator( circuit=circ, module=m, wasm_int_type=int_type, qir_int_type=int_type, ) elif profile == QIRProfile.PYTKET: qir_generator = PytketQirGenerator( circuit=circ, module=m, wasm_int_type=int_type, qir_int_type=int_type, ) elif profile in (QIRProfile.ADAPTIVE, QIRProfile.ADAPTIVE_CREGSIZE): qir_generator = AdaptiveProfileQirGenerator( circuit=circ, module=m, wasm_int_type=int_type, qir_int_type=int_type, trunc=trunc, ) elif profile == QIRProfile.AZUREADAPTIVE: qir_generator = AzureAdaptiveProfileQirGenerator( circuit=circ, module=m, wasm_int_type=int_type, qir_int_type=int_type, trunc=trunc, ) else: raise NotImplementedError("unexpected profile") populated_module = qir_generator.circuit_to_module(qir_generator.circuit, True) if profile == QIRProfile.AZUREADAPTIVE: assert not qir_generator.has_wasm sar_azure_dict: dict[str, str] = qir_generator.get_azure_sar() initial_result = str(populated_module.module.ir()) for az, azv in sar_azure_dict.items(): initial_result = initial_result.replace(az, azv) result = initial_result bitcode = pyqir.Module.from_ir(pyqir.Context(), result).bitcode if qir_format == QIRFormat.BINARY: return bitcode if qir_format == QIRFormat.STRING: return result assert not "unsupported return type" # type: ignore # noqa: RET503 elif qir_generator.has_wasm: wasm_sar_dict: dict[str, str] = qir_generator.get_wasm_sar() initial_result = str(populated_module.module.ir()) for wf, wfv in wasm_sar_dict.items(): initial_result = initial_result.replace(wf, wfv) result = initial_result bitcode = pyqir.Module.from_ir(pyqir.Context(), result).bitcode if qir_format == QIRFormat.BINARY: return bitcode if qir_format == QIRFormat.STRING: return result assert not "unsupported return type" # type: ignore # noqa: RET503 elif qir_format == QIRFormat.BINARY: return populated_module.module.bitcode() elif qir_format == QIRFormat.STRING: return populated_module.module.ir() else: assert not "unsupported return type" # type: ignore # noqa: RET503
def check_circuit( circuit: Circuit, int_type: int = 64, ) -> None: """Checks the validity of the circuit. Running this check before conversion is recommended for big circuits that take a long time to be converted. :param circuit: given circuit :param int_type: integer bit width (32 or 64) :raises ValueError: with a suggestion on how to resolve the problems """ if len(circuit.q_registers) > 1 or ( len(circuit.q_registers) == 1 and circuit.q_registers[0].name != "q" ): raise ValueError( """The circuit that should be converted should only have the default quantum register. You can convert it using the pytket compiler pass `FlattenRelabelRegistersPass`.""", ) if int_type not in {32, 64}: raise ValueError("the integer size must be 32 or 64") for creg in circuit.c_registers: if creg.size > int_type: raise ClassicalRegisterWidthError( width=creg.size, max_width=int_type, hint="try setting cut_pytket_register=True` when calling `pytket_to_qir()`", ) set_circ_register = {creg.name for creg in circuit.c_registers} for b in {b.reg_name for b in circuit.bits}: if b not in set_circ_register: raise ValueError(f"Used register {b} in not a valid register")