diff --git a/.github/workflows/build.yml b/.github/workflows/build_linux_2_17.yml similarity index 69% rename from .github/workflows/build.yml rename to .github/workflows/build_linux_2_17.yml index 073bc1160d4f4d92c54d1d8a7029dca0fb7a7404..678d477a04d211bbfb021c9933da2ce2429396be 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build_linux_2_17.yml @@ -1,4 +1,4 @@ -name: Build Wheels +name: Linux 2_17 Build Wheels on: push: @@ -13,20 +13,10 @@ jobs: fail-fast: false matrix: include: - - os: macos-12 # 测试通过 - arch: arm64 - - os: macos-12 # 测试通过 - arch: x86_64 - os: ubuntu-latest # 测试通过 arch: x86_64 - - os: ubuntu-latest # 测试通过 - arch: i686 -# - os: windows-2019 -# arch: AMD64 -# - os: windows-latest -# arch: ARM64 -# - os: windows-latest -# arch: x86 +# - os: ubuntu-latest # 测试通过 +# arch: i686 runs-on: ${{ matrix.os }} @@ -35,28 +25,27 @@ jobs: uses: actions/checkout@v4 - name: Install cibuildwheel - run: pip install -U cibuildwheel delocate + run: pip install -U cibuildwheel delocate numpy - name: Build wheels with cibuildwheel env: CIBW_ARCHS_LINUX: ${{ matrix.arch }} CIBW_ARCHS_WINDOWS: ${{ matrix.arch }} CIBW_ARCHS_MACOS: ${{ matrix.arch }} - CIBW_BUILD: cp310-* CIBW_ENVIRONMENT_WINDOWS: "CFLAGS=/openmp" CIBW_MANYLINUX_X86_64_IMAGE: quay.io/pypa/manylinux2014_x86_64:latest CIBW_MANYLINUX_I686_IMAGE: quay.io/pypa/manylinux2014_i686:latest CIBW_PROJECT_REQUIRES_PYTHON: ">=3.10" - MACOSX_DEPLOYMENT_TARGET: 12.0 + CIBW_BUILD_VERBOSITY: 1 run: cibuildwheel --output-dir wheelhouse - name: Upload built wheels uses: actions/upload-artifact@v4 with: - name: wheels-${{ matrix.os }}-${{ matrix.arch }} + name: wheels-${{ matrix.os }}-${{ matrix.arch }}-2_17 path: wheelhouse/*.whl - build_wheels_aarch64: + build_wheels_linux_aarch64: name: Build wheels on ${{ matrix.os }} aarch64 environment: release strategy: @@ -70,11 +59,6 @@ jobs: - name: Checkout source code uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - name: Install Python - with: - python-version: '3.10' - - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: @@ -85,7 +69,7 @@ jobs: - name: Build wheels with cibuildwheel env: - CIBW_BUILD: cp310-* + CIBW_BUILD: cp31?-* CIBW_PROJECT_REQUIRES_PYTHON: ">=3.10" CIBW_ARCHS_LINUX: ${{ matrix.arch }} CIBW_MANYLINUX_AARCH64_IMAGE: quay.io/pypa/manylinux2014_aarch64:latest @@ -94,5 +78,5 @@ jobs: - name: Upload built wheels uses: actions/upload-artifact@v4 with: - name: wheels-${{ matrix.os }}-${{ matrix.arch }} - path: wheelhouse/*.whl \ No newline at end of file + name: wheels-${{ matrix.os }}-${{ matrix.arch }}-2_17 + path: wheelhouse/*.whl diff --git a/.github/workflows/build_linux_2_28.yml b/.github/workflows/build_linux_2_28.yml new file mode 100644 index 0000000000000000000000000000000000000000..029719fe91bc2130aa8d2940fbae1ee99afc3f40 --- /dev/null +++ b/.github/workflows/build_linux_2_28.yml @@ -0,0 +1,79 @@ +name: Linux 2_28 Build Wheels + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build_wheels: + environment: release + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest # 测试通过 + arch: x86_64 + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Install cibuildwheel + run: pip install -U cibuildwheel delocate numpy + + - name: Build wheels with cibuildwheel + env: + CIBW_ARCHS_LINUX: ${{ matrix.arch }} + CIBW_ARCHS_WINDOWS: ${{ matrix.arch }} + CIBW_ARCHS_MACOS: ${{ matrix.arch }} + CIBW_ENVIRONMENT_WINDOWS: "CFLAGS=/openmp" + CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28 + CIBW_PROJECT_REQUIRES_PYTHON: ">=3.10" + CIBW_BUILD_VERBOSITY: 1 + run: cibuildwheel --output-dir wheelhouse + + - name: Upload built wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-${{ matrix.os }}-${{ matrix.arch }}-2_28 + path: wheelhouse/*.whl + + build_wheels_linux_aarch64: + name: Build wheels on ${{ matrix.os }} aarch64 + environment: release + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest # 测试通过 + arch: aarch64 + runs-on: ${{ matrix.os }} + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: all + + - name: Install cibuildwheel + run: pip install -U cibuildwheel delocate + + - name: Build wheels with cibuildwheel + env: + CIBW_BUILD: cp31?-* + CIBW_PROJECT_REQUIRES_PYTHON: ">=3.10" + CIBW_ARCHS_LINUX: ${{ matrix.arch }} + CIBW_MANYLINUX_AARCH64_IMAGE: manylinux_2_28 + run: cibuildwheel --output-dir wheelhouse + + - name: Upload built wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-${{ matrix.os }}-${{ matrix.arch }}-2_28 + path: wheelhouse/*.whl diff --git a/.github/workflows/build_mac.yml b/.github/workflows/build_mac.yml new file mode 100644 index 0000000000000000000000000000000000000000..bb7a280a56b2fb9f2c9fe610956c8a214b00902e --- /dev/null +++ b/.github/workflows/build_mac.yml @@ -0,0 +1,56 @@ +name: MacOS Build Wheels + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build_wheels: + environment: release + strategy: + fail-fast: false + matrix: + include: + - os: macos-latest # 测试通过 + arch: arm64 + - os: macos-13 # 测试通过 + arch: x86_64 + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Install cibuildwheel + run: pip install -U cibuildwheel delocate numpy + + - name: Install system dependencies + if: runner.os == 'macOS' + run: | + curl -LO https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.6/openmp-14.0.6.src.tar.xz + tar -xf openmp-14.0.6.src.tar.xz + cd openmp-14.0.6.src + mkdir build && cd build + export MACOSX_DEPLOYMENT_TARGET=12.0 + cmake .. -DCMAKE_INSTALL_PREFIX=$HOME/libomp + make + make install + echo "LIBOMP_PREFIX=$HOME/libomp" >> $GITHUB_ENV + + - name: Build wheels with cibuildwheel + env: + CIBW_ARCHS_MACOS: ${{ matrix.arch }} + CIBW_ENVIRONMENT_WINDOWS: "CFLAGS=/openmp" + CIBW_BUILD_VERBOSITY: 1 + MACOSX_DEPLOYMENT_TARGET: 12.0 + CIBW_MACOSX_DEPLOYMENT_TARGET: 12.0 + run: cibuildwheel --output-dir wheelhouse + + - name: Upload built wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-${{ matrix.os }}-${{ matrix.arch }} + path: wheelhouse/*.whl diff --git a/.github/workflows/build_win.yml b/.github/workflows/build_win.yml new file mode 100644 index 0000000000000000000000000000000000000000..f8b2d68b6a7e5e6525f191b761d5d6e4126744ee --- /dev/null +++ b/.github/workflows/build_win.yml @@ -0,0 +1,111 @@ +name: Windows Build Wheels + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: +# build_wheels_windows_x86: +# environment: release +# strategy: +# fail-fast: false +# matrix: +# include: +# - os: windows-latest +# arch: x86 +## - os: windows-latest # 不支持,不折腾了 +## arch: ARM64 +# +# runs-on: ${{ matrix.os }} +# +# steps: +# - name: Checkout source code +# uses: actions/checkout@v4 +# +# - name: Set up Python +# uses: actions/setup-python@v5 +# id: setpy +# with: +# python-version: | +# 3.10.11 +# 3.11.9 +# 3.12.9 +# 3.13.2 +# architecture: 'x86' +# - run: echo '${{ steps.setpy.outputs }}' +# +# - name: Install cibuildwheel +# run: pip install -U cibuildwheel delocate +# +# - name: Build wheels with cibuildwheel +# shell: cmd +# env: +# CIBW_ENVIRONMENT_WINDOWS: > +# PATH="C:\\mingw32\\bin;$PATH" +# CC="C:/mingw32/bin/gcc.exe" +# LD="C:/mingw32/bin/ld.exe" +# CIBW_ARCHS_WINDOWS: ${{ matrix.arch }} +# CIBW_PROJECT_REQUIRES_PYTHON: '>=3.10' +# CIBW_BUILD_VERBOSITY: 1 +# CIBW_PYTHON_HOME: ${{ steps.setpy.outputs.python-path }} +# run: | +# where gcc +# gcc --version +# where ld +# ld --version +# python -m cibuildwheel --output-dir wheelhouse --archs ${{ matrix.arch }} +# +# - name: Upload built wheels +# uses: actions/upload-artifact@v4 +# with: +# name: wheels-${{ matrix.os }}-${{ matrix.arch }} +# path: wheelhouse/*.whl + + build_wheels_windows_amd64: + environment: release + strategy: + fail-fast: false + matrix: + include: + - os: windows-latest + arch: AMD64 + + runs-on: ${{ matrix.os }} + + defaults: + run: + shell: bash + + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + id: setpy + with: + python-version: | + 3.10.11 + 3.11.9 + 3.12.9 + 3.13.2 + - run: echo '${{ steps.setpy.outputs }}' + + - name: Install cibuildwheel + run: pip install -U cibuildwheel delocate + + - name: Build wheels with cibuildwheel + env: + CIBW_ARCHS_WINDOWS: ${{ matrix.arch }} + CIBW_PROJECT_REQUIRES_PYTHON: '>=3.10' + CIBW_BUILD_VERBOSITY: 1 + CIBW_PYTHON_HOME: ${{ steps.setpy.outputs.python-path }} + run: cibuildwheel --output-dir wheelhouse + + - name: Upload built wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-${{ matrix.os }}-${{ matrix.arch }} + path: wheelhouse/*.whl \ No newline at end of file diff --git a/.gitignore b/.gitignore index d1c1f2fe478c67ea1d592864c36edd4c583329f1..87560ae1ef61b0a4d4b2df573d9e229698d51e01 100644 --- a/.gitignore +++ b/.gitignore @@ -139,4 +139,7 @@ cython_debug/ .idea .vscode -.DS_Store \ No newline at end of file +.DS_Store +.dylibs +c_fusion/ +wheelhouse/ \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 587dc46f34b872f8365d8d38c7c9c72b262018ac..663002ad1824f6dd951cbaed0e39c8d5d86ef94a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,6 +3,6 @@ repos: hooks: - id: pylint name: pylint - entry: poetry run pylint + entry: pylint language: system types: [python] diff --git a/cqlib/VERSION.txt b/cqlib/VERSION.txt new file mode 100644 index 0000000000000000000000000000000000000000..cb174d58a5349fd5df9dfd2014d74225d1756780 --- /dev/null +++ b/cqlib/VERSION.txt @@ -0,0 +1 @@ +1.2.1 \ No newline at end of file diff --git a/cqlib/_version.py b/cqlib/_version.py new file mode 100644 index 0000000000000000000000000000000000000000..09cafa4b52b5feeda3c3f1dbed5202eecf8f75b1 --- /dev/null +++ b/cqlib/_version.py @@ -0,0 +1,33 @@ +# This code is part of cqlib. +# +# Copyright (C) 2024 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + + +"""Version number""" +import os +import sys + +if sys.version_info < (3, 10, 0): # pragma: no cover + raise SystemError( + "cqlib requires Python 3.10 or higher. \n" + "Please upgrade your Python version to continue using this library. \n" + "You can download the latest version of Python at https://www.python.org/downloads/\n" + ) + +ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) +with open(os.path.join(ROOT_DIR, "VERSION.txt")) as version_file: + VERSION = version_file.read().strip() + +version: str +__version__: str + +__version__ = version = VERSION diff --git a/cqlib/circuits/bit.py b/cqlib/circuits/bit.py new file mode 100644 index 0000000000000000000000000000000000000000..30a8c85314aad34916aa88b36381c23f833fe5cf --- /dev/null +++ b/cqlib/circuits/bit.py @@ -0,0 +1,70 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" quantum bit base class""" + +from __future__ import annotations + + +class Bit: + """Quantum bit.""" + __slots__ = ["_index", '_initialized', '_hash'] + + def __init__(self, index: int): + """ + Initialize a new Qubit instance. + + Args: + index: logical index of the qubit + """ + if index < 0: + raise ValueError("Index must be non-negative.") + if not hasattr(self, '_initialized'): + self._index = index + self._hash = None + self._initialized = True + + @property + def index(self) -> int: + """Returns the logical index of the qubit.""" + return self._index + + def __repr__(self): + return f"{self.__class__.__name__}({self.index})" + + def __str__(self): + return f'Bit{self.index}' + + def __copy__(self): + """ + Returns a reference to the same qubit instance since qubits + should be unique. + """ + return self + + def __deepcopy__(self, memo=None): + return self + + def __eq__(self, other: Bit) -> bool: + """Check equality with another qubit based on the index.""" + return (isinstance(other, self.__class__) and + self.index == other.index) + + def __hash__(self) -> int: + """ + Return the hash based on the qubit's index, used for collections + that depend on hashable items. + """ + if self._hash is None: + self._hash = hash(f"{self.__class__.__name__}({self._index})") + return self._hash diff --git a/cqlib/circuits/circuit.py b/cqlib/circuits/circuit.py index d8c57e588c426ced93b3194c24b11d28e503ae56..a9a6920f55528d7e3e180d2e27492a9e14c47e77 100644 --- a/cqlib/circuits/circuit.py +++ b/cqlib/circuits/circuit.py @@ -17,6 +17,7 @@ from __future__ import annotations import re from collections import defaultdict +from copy import deepcopy from typing import Union, Sequence, Optional from cqlib.circuits import gates @@ -45,14 +46,14 @@ class Circuit: qubits (Qubits): Number of qubits or list of qubits.: parameters (list[Parameter | str], optional): List of parameters or parameter names. """ - self._qubits = self._initialize_qubits(qubits) - self._parameters = self._initialize_parameters(parameters) - self._instruction_sequence: list[InstructionData] = [] + self._qubits: dict[str, Qubit] = self._initialize_qubits(qubits) + self._parameters: dict[Parameter, Optional[float]] = self._initialize_parameters(parameters) + self._circuit_data: list[InstructionData] = [] @staticmethod def _initialize_parameters( parameters: Sequence[Union[Parameter, str]] - ) -> dict[Union[Parameter, Optional[float]]]: + ) -> dict[Parameter, Optional[float]]: """Helper function to initialize parameters.""" params = {} if parameters is None: @@ -68,7 +69,7 @@ class Circuit: return params @staticmethod - def _initialize_qubits(qubits: Qubits) -> dict[int, Qubit]: + def _initialize_qubits(qubits: Qubits) -> dict[str, Qubit]: """ Helper function to initialize qubits. @@ -81,9 +82,9 @@ class Circuit: if isinstance(qubits, int): if qubits < 0: raise ValueError("Number of qubits must be non-negative.") - return {i: Qubit(i) for i in range(qubits)} + return {str(Qubit(i)): Qubit(i) for i in range(qubits)} if isinstance(qubits, Qubit): - return {qubits.index: qubits} + return {str(qubits): qubits} if isinstance(qubits, (list, tuple)): qs = {} for qubit in qubits: @@ -93,7 +94,7 @@ class Circuit: raise TypeError("Qubit must be an int or QuBit instance.") if qubit.index in qs: raise ValueError(f"Duplicate qubit detected: {qubit}") - qs[qubit.index] = qubit + qs[str(qubit)] = qubit return qs raise ValueError("Invalid qubits input. Expected int, QuBit, or list/tuple of these.") @@ -107,13 +108,17 @@ class Circuit: """Return number of qubits.""" return len(self._qubits) + def circuit_data(self): + """Circuit data""" + return self._circuit_data + def qubits_path(self) -> dict[Qubit, list[InstructionData]]: """ Constructs a path of operations for each qubit. """ path = defaultdict(list) max_depths = defaultdict(int) - for op in self._instruction_sequence: + for op in self._circuit_data: instruction = op.instruction qubits = op.qubits if instruction.num_qubits != len(qubits): @@ -141,9 +146,9 @@ class Circuit: qubit = Qubit(qubit) elif not isinstance(qubit, Qubit): raise TypeError("Qubit must be an int or QuBit instance.") - if qubit.index in self._qubits: + if str(qubit) in self._qubits: raise ValueError("Qubit already exists in the circuit.") - self._qubits[qubit.index] = qubit + self._qubits[str(qubit)] = qubit @property def parameters(self) -> list[Parameter]: @@ -192,7 +197,7 @@ class Circuit: list[InstructionData]: The sequence of instructions added to the circuit, each represented by an InstructionData object. """ - return self._instruction_sequence + return self._circuit_data def insert(self, instruction: Instruction, qubits: Qubits, index: int = None): """ @@ -206,14 +211,14 @@ class Circuit: """ instruction, qubits = self._prepare_instruction(instruction, qubits) if index is None: - index = len(self._instruction_sequence) + index = len(self._circuit_data) elif not isinstance(index, int): raise TypeError("Index must be an integer or None.") - elif index < 0 or index > len(self._instruction_sequence): + elif index < 0 or index > len(self._circuit_data): raise ValueError(f"Index {index} out of bounds for instructions list of length " - f"{len(self._instruction_sequence)}.") + f"{len(self._circuit_data)}.") - self._instruction_sequence.insert( + self._circuit_data.insert( index, InstructionData(instruction=instruction, qubits=qubits) ) @@ -227,7 +232,7 @@ class Circuit: qubits (Qubits): The qubit(s) to which the instruction is applied. """ instruction, qubits = self._prepare_instruction(instruction, qubits) - self._instruction_sequence.append(InstructionData(instruction=instruction, qubits=qubits)) + self._circuit_data.append(InstructionData(instruction=instruction, qubits=qubits)) def append_instruction_data(self, instruction_data: InstructionData): """ @@ -236,7 +241,7 @@ class Circuit: Args: instruction_data (InstructionData): The instruction_data to be appended. """ - self._instruction_sequence.append(instruction_data) + self._circuit_data.append(instruction_data) def _prepare_instruction(self, instruction: Instruction, qubits: Qubits): """ @@ -263,8 +268,8 @@ class Circuit: if isinstance(q, int): q = Qubit(q) if not isinstance(q, Qubit): - raise TypeError("Qubit must be an instance of QuBit or an integer.") - if q.index not in self._qubits: + raise TypeError(f"{q} must be an instance of QuBit or an integer.") + if str(q) not in self._qubits: raise ValueError(f"{q} not found in circuit.") qs.append(q) return qs @@ -278,7 +283,8 @@ class Circuit: if isinstance(param, str): param = Parameter(param) if not isinstance(param, (Parameter, float, int)): - raise TypeError("Parameter must be a float, int, str, or an instance of Parameter.") + raise TypeError("Parameter must be a float, int, str, or an instance " + "of Parameter or PulseParameter.") if isinstance(param, Parameter): for p in param.base_params: if p not in self._parameters: @@ -312,61 +318,75 @@ class Circuit: Args: qubit (IntQubit): The qubit to apply the RX gate. - theta (Union[float, Parameter]): The rotation angle in radians or as a symbolic parameter. + theta (Union[float, Parameter]): The rotation angle in radians or as + a symbolic parameter. """ self.append(gates.RX(theta), [qubit]) - def rxy(self, qubit: IntQubit, phi: Union[float, Parameter], theta: Union[float, Parameter]): + def rxy(self, qubit: IntQubit, phi: Union[float, Parameter], + theta: Union[float, Parameter]): """ Applies the RX gate (rotation around the X-axis) to a specified qubit with a given angle. Args: qubit (IntQubit): The qubit to apply the RX gate. - phi (Union[float, Parameter]): The rotation angle in radians or as a symbolic parameter. - theta (Union[float, Parameter]): The rotation angle in radians or as a symbolic parameter. + phi (Union[float, Parameter]): The rotation angle in radians or + as a symbolic parameter. + theta (Union[float, Parameter]): The rotation angle in radians or + as a symbolic parameter. """ self.append(gates.RXY(phi, theta), [qubit]) - def crx(self, control_qubit: IntQubit, target_qubit: IntQubit, theta: Union[float, Parameter]): + def crx(self, control_qubit: IntQubit, target_qubit: IntQubit, + theta: Union[float, Parameter]): """ - Applies the controlled-RX gate (controlled rotation around the X-axis) between two qubits. + Applies the controlled-RX gate (controlled rotation around the X-axis) + between two qubits. Args: control_qubit (IntQubit): The control qubit. target_qubit (IntQubit): The target qubit where the rotation is applied. - theta (Union[float, Parameter]): The rotation angle in radians or as a symbolic parameter. + theta (Union[float, Parameter]): The rotation angle in radians or as a + symbolic parameter. """ self.append(gates.CRX(theta), [control_qubit, target_qubit]) def ry(self, qubit: IntQubit, theta: Union[float, Parameter]): """ - Applies the RY gate (rotation around the Y-axis) to a specified qubit with a given angle. + Applies the RY gate (rotation around the Y-axis) to a specified qubit + with a given angle. Args: qubit (IntQubit): The qubit to apply the RY gate. - theta (Union[float, Parameter]): The rotation angle in radians or as a symbolic parameter. + theta (Union[float, Parameter]): The rotation angle in radians or + as a symbolic parameter. """ self.append(gates.RY(theta), [qubit]) - def cry(self, control_qubit: IntQubit, target_qubit: IntQubit, theta: Union[float, Parameter]): + def cry(self, control_qubit: IntQubit, target_qubit: IntQubit, + theta: Union[float, Parameter]): """ - Applies the controlled-RY gate (controlled rotation around the Y-axis) between two qubits. + Applies the controlled-RY gate (controlled rotation around the Y-axis) + between two qubits. Args: control_qubit (IntQubit): The control qubit. target_qubit (IntQubit): The target qubit where the rotation is applied. - theta (Union[float, Parameter]): The rotation angle in radians or as a symbolic parameter. + theta (Union[float, Parameter]): The rotation angle in radians or + as a symbolic parameter. """ self.append(gates.CRY(theta), [control_qubit, target_qubit]) def rz(self, qubit: IntQubit, theta: Union[float, Parameter]): """ - Applies the RZ gate (rotation around the Z-axis) to a specified qubit with a given angle. + Applies the RZ gate (rotation around the Z-axis) to a specified qubit + with a given angle. Args: qubit (IntQubit): The qubit to apply the RZ gate. - theta (Union[float, Parameter]): The rotation angle in radians or as a symbolic parameter. + theta (Union[float, Parameter]): The rotation angle in radians or + as a symbolic parameter. """ self.append(gates.RZ(theta), [qubit]) @@ -377,7 +397,8 @@ class Circuit: Args: control_qubit (IntQubit): The control qubit. target_qubit (IntQubit): The target qubit where the rotation is applied. - theta (Union[float, Parameter]): The rotation angle in radians or as a symbolic parameter. + theta (Union[float, Parameter]): The rotation angle in radians or + as a symbolic parameter. """ self.append(gates.CRZ(theta), [control_qubit, target_qubit]) @@ -518,6 +539,17 @@ class Circuit: """ self.append(gates.Y(), [qubit]) + def cy(self, control_qubit: IntQubit, target_qubit: IntQubit): + """ + Applies the CY (Controlled-Y) gate to the specified control and target qubits. + + Args: + control_qubit (IntQubit): The control qubit. + target_qubit (IntQubit): The target qubit where the CY operation is applied + conditionally. + """ + self.append(gates.CY(), [control_qubit, target_qubit]) + def y2p(self, qubit: IntQubit): """ Applies the Y2P gate to a specified qubit. @@ -556,6 +588,24 @@ class Circuit: """ self.append(gates.CZ(), [control_qubit, target_qubit]) + def u( + self, + qubit: IntQubit, + theta: Union[float, Parameter], + phi: Union[float, Parameter], + lam: Union[float, Parameter] + ): + """ + Applies the U gate to a specified qubit. + + Args: + qubit (IntQubit): The target qubit to which the U gate is applied. + theta (float | Parameter): The rotation angle theta (θ) around the Bloch sphere. + phi (float | Parameter): The phase angle phi (φ) for the first Z-axis rotation. + lam (float | Parameter): The phase angle lambda (λ) for the final Z-axis rotation. + """ + self.append(gates.U(theta, phi, lam), [qubit]) + def barrier(self, *qubits: IntQubit): """ Inserts a barrier into the circuit that affects all specified qubits. @@ -584,13 +634,23 @@ class Circuit: """ Measures all qubits in the circuit that have not yet been measured. """ - measured_qubits = {ins[1][0] for ins in self._instruction_sequence - if isinstance(ins[0], Measure)} - unmeasured_qubits = [q for index, q in self._qubits.items() if q not in measured_qubits] - for qubit in unmeasured_qubits: - self.append(Measure(), qubit) - - def set_parameter_value(self, values: Optional[dict[Union[str, Parameter], Union[float, int]]] = None, **kwargs): + measured_qubits = set() + for ins in self._circuit_data: + if isinstance(ins.instruction, Measure): + for qubit in ins.qubits: + measured_qubits.add(qubit) + + for _, qubit in self._qubits.items(): + if qubit not in measured_qubits: + self.append(Measure(), qubit) + + # pylint: disable=too-many-branches + def assign_parameters( + self, + values: dict[str | Parameter, float | int] = None, + inplace: bool = False, + **kwargs + ): """ Sets the values of specified parameters in the quantum circuit. This method allows parameters to be set using a dictionary or @@ -603,7 +663,7 @@ class Circuit: numerical values (float or int) to set. If a parameter is specified as a string, it must be a valid identifier and already exist in the circuit's parameter list. - + inplace (bool): create new circuit or not? **kwargs: Additional parameters and their values provided as keyword arguments. This is useful for directly setting values when calling the method. @@ -611,13 +671,18 @@ class Circuit: Example: >>> from cqlib.circuits import Circuit >>> circuit = Circuit(2, ['theta', 'phi']) - >>> circuit.set_parameter_value({'theta': 3.14159}) - >>> circuit.set_parameter_value(theta=0.5, phi=0.25) + >>> circuit.assign_parameters({'theta': 3.14159}) + >>> circuit.assign_parameters(theta=0.5, phi=0.25) This method updates the internal dictionary of parameters to reflect the new values, allowing these parameters to be used with their new values in subsequent operations within the circuit. """ + if inplace: + target = self + else: + target = self.copy() + if values is None: values = {} values.update(kwargs) @@ -640,14 +705,30 @@ class Circuit: f" got {type(param).__name__}.") self._parameters[param] = value + for item in target.instruction_sequence: + instruction = item.instruction + if instruction.params: + ps = [] + for p in instruction.params: + if isinstance(p, Parameter): + p = p.value(self._parameters) + if p.is_Number: + p = float(p) + elif p.is_symbol: + p = Parameter(p) + + ps.append(p) + instruction.params = ps + return target + @property def qcis(self) -> str: """ Generates a qcis string of all instructions in the circuit. """ - return self._export_circuit_str(self._instruction_sequence, True, self._parameters) + return self._export_circuit_str(self._circuit_data, True, self._parameters) - def export_circuit_str(self, qcis_compliant: bool = False): + def as_str(self, qcis_compliant: bool = False): """ Exports the circuit as a string format, with an option to make the output QCIS-compliant. @@ -656,7 +737,7 @@ class Circuit: if False, it will retain the original format with composite gates. """ params = self._parameters - return self._export_circuit_str(self._instruction_sequence, qcis_compliant, params) + return self._export_circuit_str(self._circuit_data, qcis_compliant, params) @classmethod def _export_circuit_str( @@ -700,61 +781,165 @@ class Circuit: ops.append(' '.join(line)) return '\n'.join(ops) - @staticmethod - def load(qcis: str) -> Circuit: + @classmethod + def load(cls, qcis: str) -> Circuit: """ - Loads a quantum circuit from a formatted string. - - Args: - qcis (str): A string containing quantum circuit instructions, where each line - represents a circuit operation. The format for each line is - "GATE QUBITS [PARAMETERS]", e.g., "H Q0 Q1", "CX Q0 Q1", "RZ Q0 0.5". + Loads a quantum circuit from a QCIS string. - Returns: - Circuit: A quantum circuit object constructed based on the input string. + Args: + qcis (str): A string containing quantum circuit instructions, where each line + represents a circuit operation. The format for each line is + "GATE QUBITS [PARAMETERS]", e.g., "H Q0 Q1", "CX Q0 Q1", "RZ Q0 0.5". - Raises: - ValueError: If the input string is improperly formatted or contains - unknown gate operations. + Returns: + Circuit: A quantum circuit object constructed based on the input string. - Example: - >>> circuit_description = "H Q0\\nCX Q0 Q1\\nM Q0" - >>> c = Circuit.load(circuit_description) - >>> print(c) - Circuit with 3 instructions - """ - # from cqlib.circuits import gates, Measure, Barrier + Raises: + ValueError: If the input string is improperly formatted or contains + unknown gate operations. - circuit = Circuit(0) - line_pattern = re.compile(r'^([A-Z][A-Z0-9]*)\s+((?:Q[0-9]+\s*)+)((?:-?\d*\.?\d+\s*)*)$') + Example: + >>> circuit_description = "H Q0\\nCX Q0 Q1\\nM Q0" + >>> c = Circuit.load(circuit_description) + >>> print(c) + Circuit with 3 instructions + """ + circuit = cls(qubits=[]) + line_pattern = re.compile(r'^([A-Z][A-Z0-9]*)\s+((?:Q[0-9]+\s*)+)' + r'((?:[+-]?(?:\d*\.\d+|\d+)(?:[Ee][+-]?\d+)?\s*)*)$') for line in qcis.split('\n'): - line = line.strip().upper() + # Delete comments `#` `//`. + line = re.sub(r'(#|//).*', '', line).strip() if not line: continue match = line_pattern.match(line) if not match: - raise ValueError(f'Invalid line format: {line}') - + raise ValueError(f'Invalid instruction format: {line}') gate, qubits_str, params_str = match.groups() - qubits = [circuit._qubits.setdefault(int(q[1:]), Qubit(int(q[1:]))) - for q in qubits_str.split()] + qubits = circuit._parse_qubits_str(qubits_str) params = [float(p) for p in params_str.split()] if params_str else [] + circuit._process_instruction(gate, qubits, params) + return circuit - if gate == 'M': - for qubit in qubits: - circuit.append_instruction_data( - InstructionData(instruction=Measure(), qubits=[qubit])) - continue - if gate == 'B': - circuit.append_instruction_data( - InstructionData(instruction=Barrier(len(qubits)), qubits=qubits)) - continue + def _parse_qubits_str(self, qubits_str: str) -> list[Qubit]: + """ + Parses and initializes standard qubit identifiers from QCIS instruction. + + Converts space-separated qubit tokens (Q-prefixed) into Qubit objects, + creating new instances only when encountering previously unseen qubits. - if not hasattr(gates, gate): - raise ValueError(f'Unknown gate: {gate}') + Args: + qubits_str(str): Raw qubit specification segment from QCIS line. + + Returns: + list[Qubit]: Initialized Qubit objects in order of appearance. + """ + qubits = [] + for q_str in qubits_str.split(): + if q_str.startswith('Q'): + qubit = self._qubits.setdefault(q_str, Qubit(int(q_str[1:]))) + qubits.append(qubit) + else: + raise ValueError(f"Invalid qubit format: {q_str}") + return qubits + + def _process_instruction(self, gate: str, qubits: list, params: list): + """ + Core dispatcher for converting parsed components into circuit operations. + + Args: + gate: Uppercase gate identifier (e.g., 'H', 'CX', 'RZ') + qubits: Qubit targets from prior parsing stage + params: Numerical parameters for parameterized gates + """ + if gate == 'M': + for qubit in qubits: + self.append_instruction_data( + InstructionData(instruction=Measure(), qubits=[qubit])) + elif gate == 'B': + self.append_instruction_data( + InstructionData(instruction=Barrier(len(qubits)), qubits=qubits)) + elif hasattr(gates, gate): data = InstructionData(instruction=getattr(gates, gate)(*params, label=None), qubits=qubits) - circuit.append_instruction_data(data) + self.append_instruction_data(data) + else: + raise ValueError(f"Unsupported gate: {gate}") + + def __str__(self): + return self.as_str() + + def __add__(self, other: Circuit): + """ + Overloads the addition operator to allow concatenation of two quantum circuits + using the `+` operator. + + Args: + other(Circuit): Another Circuit instance to be concatenated with the current circuit. + Returns: + Circuit: A new Circuit object containing the combined qubits, parameters, and + instruction sequences of both circuits. + + """ + qubits = list(set(self.qubits + other.qubits)) + params = list(set(self.parameters + other.parameters)) + circuit = Circuit(qubits=qubits, parameters=params) + for inst in self.instruction_sequence: + inst = deepcopy(inst) + circuit.append(inst.instruction, inst.qubits) + for inst in other.instruction_sequence: + inst = deepcopy(inst) + circuit.append(inst.instruction, inst.qubits) + return circuit + + def __iadd__(self, other: Circuit): + """ + Overloads the in-place addition operator to append another quantum circuit to the + current circuit using the `+=` operator. + + Args: + other (Circuit): Another Circuit instance to be appended to the current circuit. + + Returns: + Circuit: The modified current Circuit object (`self`) with the additional qubits, + parameters, and instruction sequences from the other circuit. + """ + for qubit in other.qubits: + if qubit not in self.qubits: + self.add_qubit(qubit) + self_params = [str(p) for p in self.parameters] + other_params = [str(p) for p in other.parameters] + for param in other_params: + if param not in self_params: + self.add_parameter(param) + for inst in other.instruction_sequence.copy(): + inst = deepcopy(inst) + self.append(inst.instruction, inst.qubits) + return self + + def to_qasm2(self) -> str: + """ + Convert the Circuit object to an OpenQASM 2.0 formatted string. + + Example: + >>> circuit = Circuit(1) + >>> circuit.h(0) + >>> # Add gates and operations to the circuit + >>> qasm_code = circuit.to_qasm2() + >>> print(qasm_code) + """ + # pylint: disable=import-outside-toplevel + from cqlib.utils.qasm2 import dumps + + return dumps(self) + + def copy(self) -> Circuit: + """ + Copy the circuit. + """ + circuit = Circuit(qubits=self.qubits, parameters=self.parameters) + for item in self._circuit_data: + circuit.append(item.instruction.copy(), qubits=item.qubits) return circuit diff --git a/cqlib/circuits/commutation/circuit_commutate.py b/cqlib/circuits/commutation/circuit_commutate.py index 186cfc3ee2f699fab2e53ff0dcd2d087c18b7b66..5da33e87faaa00608807b2504b72896fb32fef0c 100644 --- a/cqlib/circuits/commutation/circuit_commutate.py +++ b/cqlib/circuits/commutation/circuit_commutate.py @@ -13,7 +13,7 @@ """ Quantum circuit commutation checker """ -import networkx as nx +from rustworkx.rustworkx import topological_sort from cqlib.circuits.circuit import Circuit from cqlib.circuits.instruction_data import InstructionData @@ -24,8 +24,11 @@ from .commutation import check_commutation from ._gates_commutations import gates_commutations -def circuit_commutator(circuit: Circuit, use_cache: bool = True, cache_commutations: dict = None) \ - -> list[tuple[InstructionData, InstructionData]]: +def circuit_commutator( + circuit: Circuit, + use_cache: bool = True, + cache_commutations: dict = None +) -> list[tuple[InstructionData, InstructionData]]: """ Check whether there is a commutation relationship between consecutive instructions in the circuit. @@ -42,8 +45,9 @@ def circuit_commutator(circuit: Circuit, use_cache: bool = True, cache_commutati cache_commutations = gates_commutations dag = circuit_to_dag(circuit) data = [] - for node in nx.topological_sort(dag): - for next_node in dag[node]: + for node_idx in topological_sort(dag): + node = dag.get_node_data(node_idx) + for next_node in dag.successors(node_idx): if use_cache: res = query_commute(node, next_node, cache_commutations) if res is not None: diff --git a/cqlib/circuits/dag.py b/cqlib/circuits/dag.py index f25c93db66121b72a868ac16579efbeccf32c6e1..9484d33f2f26f2fe042ff7101deffd76a1dc43ac 100644 --- a/cqlib/circuits/dag.py +++ b/cqlib/circuits/dag.py @@ -13,14 +13,15 @@ """Circuits as Directed Acyclic Graphs.""" -import networkx as nx - +# pylint: disable=E0611 +from rustworkx import PyDiGraph, topological_sort, DAGHasCycle, NodeIndices from cqlib.circuits.circuit import Circuit from cqlib.circuits.instruction_data import InstructionData from cqlib.circuits.parameter import Parameter +from cqlib.exceptions import CqlibError -def circuit_to_dag(circuit: Circuit) -> nx.DiGraph: +def circuit_to_dag(circuit: Circuit) -> PyDiGraph: """ Convert a quantum circuit into a Directed Acyclic Graph (DAG). @@ -35,22 +36,24 @@ def circuit_to_dag(circuit: Circuit) -> nx.DiGraph: Returns: nx.DiGraph: The directed acyclic graph representation of the circuit. """ - dag = nx.DiGraph() + dag = PyDiGraph(check_cycle=True) qubit_last_nodes = {} - + node_ids = {} for op in circuit.instruction_sequence: if not isinstance(op, InstructionData): raise TypeError(f"{op} must be instance of InstructionData") - dag.add_node(op, label=str(op)) + node_id = dag.add_node(op) + node_ids[op] = node_id for qubit in op.qubits: if qubit in qubit_last_nodes: - dag.add_edge(qubit_last_nodes[qubit], op) + last_node = qubit_last_nodes[qubit] + dag.add_edge(node_ids[last_node], node_id, f'{last_node}-{op}') qubit_last_nodes[qubit] = op return dag -def dag_to_circuit(dag: nx.DiGraph) -> Circuit: +def dag_to_circuit(dag: PyDiGraph) -> Circuit: """ Converts a Directed Acyclic Graph (DAG) back into a quantum circuit. The DAG is expected to have nodes representing quantum operations @@ -63,15 +66,17 @@ def dag_to_circuit(dag: nx.DiGraph) -> Circuit: Returns: Circuit: A quantum circuit reconstructed from the DAG. """ - if not nx.is_directed_acyclic_graph(dag): - raise ValueError("The provided graph must be acyclic to form a valid quantum circuit.") - + try: + topological_order = topological_sort(dag) + except DAGHasCycle as e: + raise CqlibError("The provided graph must be acyclic to form a valid" + " quantum circuit.") from e circuit = Circuit(0) - topological_order: list[InstructionData] = list(nx.topological_sort(dag)) - for node in topological_order: + for index in topological_order: + node = dag.get_node_data(index) if not isinstance(node, InstructionData): - raise ValueError(f"{node} in the DAG must be instance of InstructionData") + raise CqlibError(f"{node} in the DAG must be instance of InstructionData") for qubit in node.qubits: if qubit not in circuit.qubits: circuit.add_qubit(qubit) @@ -81,3 +86,34 @@ def dag_to_circuit(dag: nx.DiGraph) -> Circuit: circuit.append_instruction_data(node) return circuit + + +def topological_layers(graph: PyDiGraph) -> list[list[NodeIndices | int]]: + """ + Perform topological sorting to decompose a directed acyclic graph + (DAG) into layers of nodes. + + This function implements a Kahn's algorithm-based approach to determine + the hierarchical layers of nodes in a DAG. Each layer contains nodes that + can be processed simultaneously in dependency resolution scenarios. + + Args: + graph (PyDiGraph): The input directed acyclic graph represented as + a `PyDiGraph` object from the `rustworkx` library. + + Returns: + list[list[NodeIndices]]: A list of lists where each inner list represents + a layer of node IDs. + """ + node_in_degree = {node: graph.in_degree(node) for node in graph.node_indices()} + front_layer = [node_id for node_id, in_degree in node_in_degree.items() if in_degree == 0] + + while front_layer: + yield front_layer + next_layer = [] + for node_id in front_layer: + for successor in graph.successor_indices(node_id): + node_in_degree[successor] -= 1 + if node_in_degree[successor] == 0: + next_layer.append(successor) + front_layer = next_layer diff --git a/cqlib/circuits/gates/__init__.py b/cqlib/circuits/gates/__init__.py index 3a1a4ede1d477ccac37a7d3fc8aa480d760644e9..893c079ecc1e0fa5947ed8b00106048a6f2b75ca 100644 --- a/cqlib/circuits/gates/__init__.py +++ b/cqlib/circuits/gates/__init__.py @@ -24,9 +24,10 @@ from .rz import RZ, CRZ from .s import S, SD from .swap import SWAP from .t import T, TD +from .u import U from .x import X, CX, CNOT, CCX, CCNOT, X2P, X2M from .xy import XY, XY2M, XY2P -from .y import Y, Y2M, Y2P +from .y import Y, Y2M, Y2P, CY from .z import Z, CZ __all__ = ( @@ -39,8 +40,9 @@ __all__ = ( 'S', 'SD', 'SWAP', 'T', 'TD', + 'U', 'X', 'CX', 'CCX', 'CNOT', 'CCNOT', 'X2P', 'X2M', 'XY', 'XY2P', 'XY2M', - 'Y', 'Y2M', 'Y2P', + 'Y', 'Y2M', 'Y2P', 'CY', 'Z', 'CZ' ) diff --git a/cqlib/circuits/gates/gate.py b/cqlib/circuits/gates/gate.py index 9ee30b07c705ca92eeb7cb7d035b26569706dc96..36c54459de24065b0f157ac60e7067bde01b9c5b 100644 --- a/cqlib/circuits/gates/gate.py +++ b/cqlib/circuits/gates/gate.py @@ -66,11 +66,13 @@ class ControlledGate(Gate): This class represents a quantum gate that has control qubits in addition to target qubits. """ - # pylint: disable=useless-parent-delegation + # pylint: disable=useless-parent-delegation,too-many-arguments,too-many-positional-arguments def __init__( self, name: str, num_qubits: int, + control_index: list[int], + base_gate: Gate, params: List[Union[Parameter, float, complex]], label: Optional[str] = None ): @@ -80,10 +82,17 @@ class ControlledGate(Gate): Args: name (str): The name of the controlled gate. num_qubits (int): The number of qubits the gate acts on. + control_index (list[int]): A list of indices specifying the control qubits + for the gate. Each index represents a qubit within the range of + `[0, num_qubits - 1]` that acts as a control for the gate operation. + base_gate (Gate): Gate object to be controlled. params (list[Parameter | float | complex]): A list of parameters for the gate. label (str | None, optional): An optional label for the gate. Defaults to None. """ super().__init__(name, num_qubits, params, label) + self.control_index = control_index + self.base_gate = base_gate + assert all(map(lambda s: s < self.num_qubits, self.control_index)) def __array__(self, dtype=None): """ diff --git a/cqlib/circuits/gates/rx.py b/cqlib/circuits/gates/rx.py index 30ee91d6e2ce8b4d21400a55a39abaf17b2701f0..3834942cdb7bd5750a7b9258551034b251e2886b 100644 --- a/cqlib/circuits/gates/rx.py +++ b/cqlib/circuits/gates/rx.py @@ -76,7 +76,14 @@ class CRX(ControlledGate): theta (float | Parameter): The rotation angle in radians around the X-axis. label (str | None, optional): An optional label for the CRX gate. Defaults to None. """ - super().__init__('CRX', 2, [theta], label=label) + super().__init__( + 'CRX', + 2, + control_index=[0], + base_gate=RX(theta), + params=[theta], + label=label + ) def __array__(self, dtype=np.complex128): """ diff --git a/cqlib/circuits/gates/rxy.py b/cqlib/circuits/gates/rxy.py index 90b7863a5157aaa5ba860e3f7580fb5eaf4dbf50..5c0f55b2cbea96edcdcc3e5f2fcd63312b2402e6 100644 --- a/cqlib/circuits/gates/rxy.py +++ b/cqlib/circuits/gates/rxy.py @@ -28,7 +28,11 @@ class RXY(Gate): making it a versatile tool for manipulating qubit states. """ - def __init__(self, phi: Union[float, Parameter], theta: Union[float, Parameter], label: Optional[str] = None): + def __init__( + self, + phi: Union[float, Parameter], theta: Union[float, Parameter], + label: Optional[str] = None + ): """ Initialize the Rxy gate. diff --git a/cqlib/circuits/gates/ry.py b/cqlib/circuits/gates/ry.py index ed5707076f178ab03fcfc3f40cbfd28af094a787..e2a02ffc020dcf8f31f2b267fa8d5ac48622c3de 100644 --- a/cqlib/circuits/gates/ry.py +++ b/cqlib/circuits/gates/ry.py @@ -75,7 +75,14 @@ class CRY(ControlledGate): theta (float | Parameter): The rotation angle in radians around the Y-axis. label (str | None, optional): An optional label for the CRY gate. Defaults to None. """ - super().__init__('CRY', 2, [theta], label=label) + super().__init__( + 'CRY', + 2, + control_index=[0], + base_gate=RY(theta), + params=[theta], + label=label + ) def __array__(self, dtype=np.complex128): """ diff --git a/cqlib/circuits/gates/rz.py b/cqlib/circuits/gates/rz.py index 5769a5f97a2329122e61f26aa4f603e2c62310ca..bc05321b0c3fb521dfbffa3c5acbd9c53156c7b0 100644 --- a/cqlib/circuits/gates/rz.py +++ b/cqlib/circuits/gates/rz.py @@ -75,7 +75,14 @@ class CRZ(ControlledGate): theta (float | Parameter): The rotation angle in radians around the Z-axis. label (str | None, optional): An optional label for the CRZ gate. Defaults to None. """ - super().__init__('CRZ', 2, [theta], label=label) + super().__init__( + 'CRZ', + 2, + control_index=[0], + base_gate=RZ(theta), + params=[theta], + label=label + ) def __array__(self, dtype=np.complex128): """ diff --git a/cqlib/circuits/gates/u.py b/cqlib/circuits/gates/u.py new file mode 100644 index 0000000000000000000000000000000000000000..25a4594d20376c4e1421634c9cc87701f9decf4f --- /dev/null +++ b/cqlib/circuits/gates/u.py @@ -0,0 +1,103 @@ +# This code is part of cqlib. +# +# Copyright (C) 2024 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +This module defines the U quantum gates commonly used in quantum computing. +These gates represent single-qubit rotations around the Bloch sphere and are parameterized +by angles that control the rotation and phase. + +Definitions: + - U(θ, ϕ, λ): General single-qubit rotation gate. + - U3(θ, ϕ, λ) = U(θ, ϕ, λ) + - U2(ϕ, λ) = U(π/2, ϕ, λ) + - U1(λ) = P(λ) = U(0, 0, λ) +""" + +import math +from typing import Optional, Sequence + +import numpy as np + +from cqlib.circuits.instruction_data import InstructionData +from cqlib.circuits.qubit import Qubit +from cqlib.circuits.gates.gate import Gate +from cqlib.circuits.parameter import Parameter + + +class U(Gate): + """ + U gate in quantum computing + + It is represented by the matrix: + U(theta,phi,lam)= + [ [ cos(theta/2), -exp(i * lam) * sin(theta/2)], + [ exp(i * phi) * sin(theta/2), exp(i * (phi+lam)) * cos(theta/2)]] + + """ + is_supported_by_qcis = False + + def __init__( + self, + theta: float | Parameter, + phi: float | Parameter, + lam: float | Parameter, + label: Optional[str] = None + ): + """ + Initializes the U gate with parameters theta, phi, and lam. + + Args: + theta (float | Parameter): Rotation angle theta. + phi (float | Parameter): Phase angle phi. + lam (float | Parameter): Phase angle lambda. + label (str | None, optional): An optional label for the X gate. Defaults to None. + """ + super().__init__('U', 1, [theta, phi, lam], label=label) + + def __array__(self, dtype: np.dtype = np.complex128): + """ + Returns the matrix representation of the U gate. + + Args: + dtype (optional): The data type of the matrix elements. Default is np.complex128. + + Returns: + numpy.ndarray: A 2x2 matrix with complex entries representing the U gate. + """ + theta, phi, lam = (float(param) for param in self.params) + cos = math.cos(theta / 2) + sin = math.sin(theta / 2) + return np.array( + [ + [cos, -np.exp(1j * lam) * sin], + [np.exp(1j * phi) * sin, np.exp(1j * (phi + lam)) * cos], + ], + dtype=dtype or complex, + ) + + def to_qcis(self, qubits: Sequence[Qubit]) -> list[InstructionData]: + """ + Convert the U gate to a sequence of QCIS instructions. + """ + # pylint: disable=import-outside-toplevel + from cqlib.circuits.gates.rz import RZ + from cqlib.circuits.gates.rx import RX + qubit = qubits[0] + theta, phi, lam = (float(param) for param in self.params) + return [ + InstructionData(RZ(lam), [qubit]), + InstructionData(RX(math.pi / 2), [qubit]), + InstructionData(RZ(theta), [qubit]), + InstructionData(RX(-math.pi / 2), [qubit]), + InstructionData(RZ(phi), [qubit]), + ] diff --git a/cqlib/circuits/gates/x.py b/cqlib/circuits/gates/x.py index fed8c1301239534211db48defc3db9f6e6dd7883..e14df5c40e76997cca0080937cd38523b0dceab7 100644 --- a/cqlib/circuits/gates/x.py +++ b/cqlib/circuits/gates/x.py @@ -89,7 +89,14 @@ class CX(ControlledGate): Args: label (str | None, optional): An optional label for the CX gate. Defaults to None. """ - super().__init__('CX', 2, [], label=label) + super().__init__( + 'CX', + 2, + control_index=[0], + base_gate=X(), + params=[], + label=label + ) def __array__(self, dtype=np.complex128): """ @@ -165,7 +172,8 @@ class CCX(ControlledGate): label (str | None, optional): An optional label for the CCX gate. Defaults to None. """ - super().__init__('CCX', 3, [], label=label) + super().__init__('CCX', 3, control_index=[0, 1], + base_gate=X(), params=[], label=label) def __array__(self, dtype=np.complex128): """ diff --git a/cqlib/circuits/gates/y.py b/cqlib/circuits/gates/y.py index c6cadb0b298aa48085f8ed4891210fc6c2dc4bb7..ccb1cf19f2d85a6db14d2cd0726bdc0874ca4669 100644 --- a/cqlib/circuits/gates/y.py +++ b/cqlib/circuits/gates/y.py @@ -20,10 +20,10 @@ Classes: Y2P: Performs a rotation around the y-axis by pi/2. Y2M: Performs a rotation around the y-axis by -pi/2. """ -from typing import Optional +from typing import Optional, Sequence import numpy as np -from cqlib.circuits.gates.gate import Gate +from cqlib.circuits.gates.gate import Gate, ControlledGate from cqlib.circuits.utils import sqrt2_inv @@ -59,6 +59,70 @@ class Y(Gate): return np.array([[0, -1j], [1j, 0]], dtype=dtype) +class CY(ControlledGate): + """ + CY gate. + + This gate acts as both a bit flip and a phase shift on a qubit. + It is represented by the matrix: + [[ 1, 0, 0, 0], + [ 0, 0, 0, -i], + [ 0, 0, 1, 0], + [ 0, i, 0, 0]] + """ + is_supported_by_qcis = False + + def __init__(self, label: Optional[str] = None): + """ + Initialize the CY gate. + + Args: + label (str | None, optional): An optional label for the CY gate. Defaults to None. + """ + super().__init__('CY', 2, control_index=[0], + base_gate=Y(), params=[], label=label) + + def __array__(self, dtype: np.dtype = np.complex128): + """ + The numpy matrix of the Y gate. + + Args: + dtype (optional): The data type of the matrix elements. Default is np.complex128. + + Returns: + numpy.ndarray: A 2x2 matrix with complex entries representing the Y gate. + """ + return np.array([ + [1, 0, 0, 0], + [0, 0, 0, -1j], + [0, 0, 1, 0], + [0, 1j, 0, 0], + ], dtype=dtype) + + def to_qcis(self, qubits: Sequence) -> list: + """ + Convert the CY gate to a sequence of QCIS instructions. + + Args: + qubits (list | tuple): The list or tuple of qubits the gate acts on. + It requires exactly three qubits. + """ + # pylint: disable=import-outside-toplevel + from cqlib.circuits.gates.h import H + from cqlib.circuits.gates.z import CZ + from cqlib.circuits.gates.s import S, SD + from cqlib.circuits.instruction_data import InstructionData + + control_qubit, target_qubit = self._parse_qubits(qubits) + return [ + InstructionData(SD(), [target_qubit]), + InstructionData(H(), [target_qubit]), + InstructionData(CZ(), [control_qubit, target_qubit]), + InstructionData(H(), [target_qubit]), + InstructionData(S(), [target_qubit]), + ] + + class Y2P(Gate): """ Y2P gate performs a rotation around the y-axis of the Bloch sphere by pi/2. diff --git a/cqlib/circuits/gates/z.py b/cqlib/circuits/gates/z.py index ee71a3c39fa068e980e231a67a1c0801f9d111bf..a5018716ae46259bf631f3a2deb2b311e5988c52 100644 --- a/cqlib/circuits/gates/z.py +++ b/cqlib/circuits/gates/z.py @@ -77,7 +77,8 @@ class CZ(ControlledGate): Args: label (str | None, optional): An optional label for the CZ gate. Defaults to None. """ - super().__init__('CZ', 2, [], label=label) + super().__init__('CZ', 2, control_index=[0], + base_gate=Z(), params=[], label=label) def __array__(self, dtype=np.complex128): """ diff --git a/cqlib/circuits/instruction.py b/cqlib/circuits/instruction.py index 8d88f88cc46db030a35820c1f0ae1494525fa96b..34b31b86f73b2721f0cad2904694ee90d4441c10 100644 --- a/cqlib/circuits/instruction.py +++ b/cqlib/circuits/instruction.py @@ -12,8 +12,13 @@ # that they have been altered from the originals. """Generic quantum instruction.""" + +from __future__ import annotations + +from copy import copy from typing import List, Optional, Union, Sequence -from cqlib.circuits.parameter import Parameter + +from .parameter import Parameter class Instruction: @@ -54,6 +59,17 @@ class Instruction: def __repr__(self) -> str: return self.__str__() + def copy(self) -> "Instruction": + """ + Copy the Instruction. + """ + new_instance = type(self).__new__(type(self)) + new_instance.__dict__.update(self.__dict__) + if self.params: + new_instance.params = [p.copy() if isinstance(p, Parameter) + else copy(p) for p in self.params] + return new_instance + def _format_params(self) -> str: """Formats the parameter list for string representation.""" return ",".join(str(p) for p in self.params) diff --git a/cqlib/circuits/instruction_data.py b/cqlib/circuits/instruction_data.py index 5386d31ba96441867fa4b41dd4de58fe8ad67b1f..bf9804ffa6cfe1ff8326a7342a7fbc5c14dbefbb 100644 --- a/cqlib/circuits/instruction_data.py +++ b/cqlib/circuits/instruction_data.py @@ -15,7 +15,7 @@ from typing import NamedTuple, Sequence -from cqlib.circuits.qubit import Qubit +from cqlib.circuits.bit import Bit from cqlib.circuits.instruction import Instruction @@ -25,10 +25,13 @@ class InstructionData(NamedTuple): instruction and the qubits it affects. """ instruction: Instruction - qubits: Sequence[Qubit] + qubits: Sequence[Bit] def __repr__(self): - return f'{self.instruction} {" ".join(map(str, self.qubits))}' + s = [self.instruction.name, " ".join(map(str, self.qubits))] + if self.instruction.params: + s.append(" ".join(map(str, self.instruction.params))) + return ' '.join(s) def __hash__(self): return hash((self.instruction, tuple(self.qubits))) diff --git a/cqlib/circuits/parameter.py b/cqlib/circuits/parameter.py index 6eee70e3fe589cf305661f6af6820cbef2a846d7..5924d7fab5857a86144fcb53b08cc79785de7992 100644 --- a/cqlib/circuits/parameter.py +++ b/cqlib/circuits/parameter.py @@ -14,7 +14,7 @@ """parameters in the quantum circuit""" from __future__ import annotations -from typing import Union, Optional +from typing import Union from sympy import Symbol, sympify, sstr, Add, Mul, Pow, Expr, exp as sym_exp @@ -150,6 +150,12 @@ class Parameter: """ return str(self) == str(other) + def copy(self) -> Parameter: + """ + copy the Parameter. + """ + return Parameter(symbol=self._symbol) + @property def base_params(self): """ diff --git a/cqlib/circuits/qubit.py b/cqlib/circuits/qubit.py index e4ecdb1872ae78a3a3fdce78a036e70a0afde637..879efa2f46cac43009698724895474b44560b1d6 100644 --- a/cqlib/circuits/qubit.py +++ b/cqlib/circuits/qubit.py @@ -17,11 +17,13 @@ from __future__ import annotations import weakref +from .bit import Bit -class Qubit: + +class Qubit(Bit): """Quantum bit.""" - __slots__ = ["_index", '_initialized', '_hash', '__weakref__'] - _cache = weakref.WeakValueDictionary[int, 'QuBit']() + __slots__ = ["__weakref__"] + _cache = weakref.WeakValueDictionary[int, 'Qubit']() def __new__(cls, index: int) -> Qubit: """ @@ -36,55 +38,15 @@ class Qubit: """ if index < 0: raise ValueError("Qubit index must be non-negative.") + inst = cls._cache.get(index) if inst is None: inst = super().__new__(cls) inst._index = index inst._hash = None + inst._initialized = True cls._cache[index] = inst return inst - def __init__(self, index: int): - """ - Initialize a new Qubit instance. - - Args: - index: logical index of the qubit - """ - if not hasattr(self, '_initialized'): - self._index = index - self._hash = None - self._initialized = True - - @property - def index(self) -> int: - """Returns the logical index of the qubit.""" - return self._index - - def __repr__(self): - return f"{self.__class__.__name__}({self.index})" - def __str__(self): return f'Q{self.index}' - - def __copy__(self): - """ - Returns a reference to the same qubit instance since qubits - should be unique. - """ - return self - - def __eq__(self, other: Qubit) -> bool: - """Check equality with another qubit based on the index.""" - if isinstance(other, Qubit): - return self.index == other.index - return False - - def __hash__(self) -> int: - """ - Return the hash based on the qubit's index, used for collections - that depend on hashable items. - """ - if self._hash is None: - self._hash = hash(self._index) - return self._hash diff --git a/cqlib/exceptions.py b/cqlib/exceptions.py index e5277060abae0c97f428bca9868d7f11e97caff5..8f7ad309074fc35cdc9b718d6ae3ea2eb3c25141 100644 --- a/cqlib/exceptions.py +++ b/cqlib/exceptions.py @@ -68,3 +68,11 @@ class CqlibRequestError(CqlibError): class CqlibInputParaError(CqlibError): """Class for input errors raised by Cqlib.""" + + +class QASMParserError(CqlibError): + """Exception raised for errors in the OpenQASM parser within Cqlib.""" + + def __init__(self, message="Error occurred during OpenQASM parsing."): + """Set the error message specific to QASM parsing.""" + super().__init__(message) diff --git a/cqlib/mapping/cir_dg.py b/cqlib/mapping/cir_dg.py index 608e42a9f14199b65e35d4ba288b2d8ffa8c11e6..dbd5b3b792dfb2e02a3782fff166be8104763b9b 100644 --- a/cqlib/mapping/cir_dg.py +++ b/cqlib/mapping/cir_dg.py @@ -69,9 +69,6 @@ def add_gate_to_cir(cir, gate): if name == 'u1': cir.add_u1(q[0]) - - - class xor_tensor(): def __init__(self, q): self.name = 'xor' @@ -368,26 +365,17 @@ class DG(DiGraph): gates = self.get_node_gates(node) for gate in gates: if gate[0].lower() == 'swap': - cir_qcis.append(Instruction(name='y2m', num_qubits=1, params=[]), gate[1][1]) - cir_qcis.append(Instruction(name='cz', num_qubits=2, params=[]), gate[1]) - cir_qcis.append(Instruction(name='y2p', num_qubits=1, params=[]), gate[1][1]) - - cir_qcis.append(Instruction(name='y2m', num_qubits=1, params=[]), gate[1][0]) - cir_qcis.append(Instruction(name='cz', num_qubits=2, params=[]), tuple(reversed(gate[1]))) - cir_qcis.append(Instruction(name='y2p', num_qubits=1, params=[]), gate[1][0]) - - cir_qcis.append(Instruction(name='y2m', num_qubits=1, params=[]), gate[1][1]) - cir_qcis.append(Instruction(name='cz', num_qubits=2, params=[]), gate[1]) - cir_qcis.append(Instruction(name='y2p', num_qubits=1, params=[]), gate[1][1]) + instruction_temp = Instruction(name=gate[0].upper(), num_qubits=2, params=[]) + cir_qcis.append(instruction_temp, gate[1]) else: # format parameters param_format = [format_para(para_temp) for para_temp in gate[2]] - instruction_temp = Instruction(name=gate[0], + instruction_temp = Instruction(name=gate[0].upper(), num_qubits=len(gate[1]), params=param_format) cir_qcis.append(instruction_temp, gate[1]) if add_barrier: - cir_qcis.barrier(cir_qcis.qubits) + cir_qcis.barrier(*cir_qcis.qubits) circuit.execute_front_layer() return cir_qcis diff --git a/cqlib/mapping/mapping.py b/cqlib/mapping/mapping.py index 3b95ce25b262040a02deed0f9f23b53601e90b10..3e39e551667ffafdaf4b113295f2ccc72674babe 100644 --- a/cqlib/mapping/mapping.py +++ b/cqlib/mapping/mapping.py @@ -10,11 +10,13 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. # +import re + from .cir_dg import DG import networkx as nx import numpy as np from .monte_carlo_tree import MCTree -from cqlib import BasePlatform +from cqlib import BasePlatform, Circuit method_init_mapping = 'Topgraph' @@ -42,7 +44,6 @@ def layout_dict_reverse(layout_dict): from .init_mapping.get_init_map import get_init_map - def get_adjacent_list(platform: BasePlatform): config = platform.download_config() couplers_map = config['overview']['coupler_map'] @@ -71,6 +72,33 @@ def create_ag_from_chip_structure(platform: BasePlatform): return ag +def generate_qubit_mapping(qcis_str): + mapping = {} + line_pattern = re.compile( + r'^([A-Z][A-Z0-9]*)\s+((?:Q[0-9]+\s*)+)' + r'((?:[+-]?(?:\d*\.\d+|\d+)(?:[Ee][+-]?\d+)?\s*)*)$' + ) + qcis = [] + for line in qcis_str.split('\n'): + items = [] + line = line.strip() + if not line: + continue + if line.startswith('PLS ') or line.startswith('PULSE '): + raise ValueError("PULSE/PLS not supported") + match = line_pattern.match(line) + if not match: + raise ValueError(f'Invalid line format: {line}') + gate, qubits_str, params_str = match.groups() + qubits = [] + for q in qubits_str.split(): + i = int(q[1:]) + mapping.setdefault(i, len(mapping)) + qubits.append(f'Q{mapping[i]}') + qcis.append(' '.join([gate, ' '.join(qubits), params_str])) + return mapping, '\n'.join(qcis) + + def transpile_qcis(qcis_str, platform: BasePlatform, initial_layout=None, objective='size', seed=None): """ @@ -102,18 +130,19 @@ def transpile_qcis(qcis_str, platform: BasePlatform, initial_layout=None, object Returns ------- - qasm_transpiled : string - qasm string after transpilation - layout : dict - mapping from virtual to physical qubit - e.g.: - {0:1, 1:2, 2:3, 3:4, 4:0} - swap_mapping : dict - mapping from initial physical qubit to final physical qubit after a series of swaps - e.g.: - {0:1, 1:2, 2:3, 3:4, 4:0} - mapping_virtual_to_final: - mapping from virtual to final physical qubit + res: list + circuit : Circuit + New circuit object. + layout : dict + mapping from virtual to physical qubit + e.g.: + {0:1, 1:2, 2:3, 3:4, 4:0} + swap_mapping : dict + mapping from initial physical qubit to final physical qubit after a series of swaps + e.g.: + {0:1, 1:2, 2:3, 3:4, 4:0} + mapping_virtual_to_final: + mapping from virtual to final physical qubit raised: TranspileError: @@ -133,13 +162,16 @@ def transpile_qcis(qcis_str, platform: BasePlatform, initial_layout=None, object score_layer = 5 # generate dependency graph dg = DG() + qubit_mapping, qcis_str = generate_qubit_mapping(qcis_str) + m_instructs = dg.from_qcis_str(qcis_str) num_q_vir = dg.num_q - # initial mapping if initial_layout is None: init_map = get_init_map(dg, ag, method_init_mapping) initial_layout = layout_list_to_dict(init_map) + else: + initial_layout = {qubit_mapping[k]: v for k, v in initial_layout.items()} # init search tree search_tree = MCTree(ag, dg, objective=objective, @@ -147,7 +179,7 @@ def transpile_qcis(qcis_str, platform: BasePlatform, initial_layout=None, object score_layer=score_layer, use_prune=use_prune, use_hash=use_hash, - init_mapping=init_map, ) + init_mapping=initial_layout) # MCT search process while search_tree.nodes[search_tree.root_node]['num_remain_gates'] > 0: # for _ in range(selec_times): @@ -178,4 +210,8 @@ def transpile_qcis(qcis_str, platform: BasePlatform, initial_layout=None, object if q >= num_q_vir: initial_layout.pop(q) mapping_virtual_to_final.pop(q) - return qcis_circuit, initial_layout, swap_mapping, mapping_virtual_to_final + r_qubit_mapping = {v: k for k, v in qubit_mapping.items()} + initial_layout = {r_qubit_mapping[k]: v for k, v in initial_layout.items()} + mapping_virtual_to_final = {r_qubit_mapping[k]: v for k, v in mapping_virtual_to_final.items()} + c = Circuit.load(qcis_circuit.as_str().upper()) + return c, initial_layout, swap_mapping, mapping_virtual_to_final diff --git a/cqlib/pulse/__init__.py b/cqlib/pulse/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..d634487aa7260abefd941b444bd29e57c3d9d0c5 --- /dev/null +++ b/cqlib/pulse/__init__.py @@ -0,0 +1,34 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Pulse module based on the pulse instruction in the QCIS instruction. +""" + +from .coupler_qubit import CouplerQubit +from .waveform import Waveform, WaveformType, CosineWaveform, \ + FlattopWaveform, SlepianWaveform, NumericWaveform +from .g import G +from .pz import PZ +from .pz0 import PZ0 +from .pxy import PXY +from .pulse_circuit import PulseCircuit + +__all__ = [ + 'CouplerQubit', + 'Waveform', 'WaveformType', + 'CosineWaveform', 'FlattopWaveform', + 'SlepianWaveform', 'NumericWaveform', + 'G', 'PXY', 'PZ', 'PZ0', + 'PulseCircuit', +] diff --git a/cqlib/pulse/base_pulse.py b/cqlib/pulse/base_pulse.py new file mode 100644 index 0000000000000000000000000000000000000000..dda4e2e7993dd3a72770d7b6607e3f64b90992bf --- /dev/null +++ b/cqlib/pulse/base_pulse.py @@ -0,0 +1,55 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Base Class for QCIS Pulse instruction +""" + +from abc import ABC, abstractmethod + +from cqlib.circuits.instruction import Instruction + +from .waveform import Waveform + + +class BasePulse(Instruction, ABC): + """ + Abstract base class for Quantum Control Instruction System (QCIS) pulse commands. + """ + + def __init__( + self, + name: str, + waveform: Waveform = None, + label: str = None + ): + """Initialize base pulse instance + + Args: + name (str): Pulse type identifier (e.g. PZ/PZ0/PXY/G) + waveform (Waveform): Waveform parameter container storing physical values. + label (str): Optional operational label for experimental tracking + """ + self._waveform = waveform + if waveform: + params = waveform.data + else: + params = [] + super().__init__(name=name, num_qubits=1, params=params, label=label) + self.validate() + + @abstractmethod + def validate(self): + """ + Abstract template method for parameter validation + """ diff --git a/cqlib/pulse/coupler_qubit.py b/cqlib/pulse/coupler_qubit.py new file mode 100644 index 0000000000000000000000000000000000000000..cc8e6852b83cabad7d2d2590ff7754b8c9e1483f --- /dev/null +++ b/cqlib/pulse/coupler_qubit.py @@ -0,0 +1,41 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Coupler Qubit""" + +from __future__ import annotations + +import weakref + +from cqlib.circuits.bit import Bit + + +class CouplerQubit(Bit): + """A class representing a coupled qubit in a quantum system.""" + + _cache = weakref.WeakValueDictionary[int, 'CouplerQubit']() + + def __new__(cls, index: int) -> CouplerQubit: + if index < 0: + raise ValueError("CouplerQubit index must be non-negative.") + inst = cls._cache.get(index) + if inst is None: + inst = super().__new__(cls) + inst._index = index + inst._initialized = True + inst._hash = None + cls._cache[index] = inst + return inst + + def __str__(self): + return f'G{self.index}' diff --git a/cqlib/pulse/g.py b/cqlib/pulse/g.py new file mode 100644 index 0000000000000000000000000000000000000000..f0cb626fc0323585ff49b7b34a15ad2308721e92 --- /dev/null +++ b/cqlib/pulse/g.py @@ -0,0 +1,52 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" G: Adjust and control the coupling function of the coupling qubit. """ +from cqlib.exceptions import CqlibError +from .base_pulse import BasePulse + + +class G(BasePulse): + """ + Coupling strength controller for coupling qubits via sequential DC(Direct Current) pulses. + Applies tunable interaction between adjacent qubits by setting duration (DAC cycles) + and coupling amplitude (Hz). Exclusively operates on coupling qubits. + + Parameters: + - length: Coupling activation duration in DAC sampling cycles (1 cycle=0.5ns) + - coupling_strength: Interaction magnitude in Hertz (Hz) + + Example: + `G G107 100 -3E6` sets 3MHz coupling on G107 for 50ns (100 cycles). + """ + + def __init__(self, length: int, coupling_strength: int, label: str = None): + self.length = length + self.coupling_strength = coupling_strength + super().__init__('G', waveform=None, label=label) + self.params = [length, coupling_strength] + + def validate(self): + """ + Validate pulse parameters for G-type coupling operations. + + Ensures: + - Duration (length) is within valid range [0, 1e5] DAC cycles + - Coupling strength meets implementation constraints + """ + if self.length < 0 or self.length > 1e5: + raise CqlibError(f"Invalid duration {self.length}: " + f"Must be 0-100,000 DAC cycles (1 cycle=0.5ns)") + + def __str__(self): + return f'{self.__class__.__name__}({self.length},{self.coupling_strength})' diff --git a/cqlib/pulse/pulse_circuit.py b/cqlib/pulse/pulse_circuit.py new file mode 100644 index 0000000000000000000000000000000000000000..8aec60b5c0857ca2e38b46ba504e4d5fd1d1f84e --- /dev/null +++ b/cqlib/pulse/pulse_circuit.py @@ -0,0 +1,341 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +A specialized quantum circuit class designed for pulse-level control +of qubits and coupler qubits. +""" +import re +from collections.abc import Sequence + +from cqlib.circuits.circuit import Circuit, Qubits +from cqlib.circuits.instruction import Instruction +from cqlib.circuits.instruction_data import InstructionData +from cqlib.circuits.parameter import Parameter +from cqlib.circuits.qubit import Qubit +from cqlib.exceptions import CqlibError + +from .coupler_qubit import CouplerQubit +from .g import G +from .pxy import PXY +from .pz import PZ +from .pz0 import PZ0 +from .waveform import Waveform +from .base_pulse import BasePulse + +CouplerQubits = int | CouplerQubit | Sequence[int | CouplerQubit] + +PULSE_GATES = ['PZ', 'PZ0', 'PXY', 'G'] + + +class PulseCircuit(Circuit): + """ + Circuit with pulse support. + """ + + def __init__( + self, + qubits: Qubits, + coupler_qubits: CouplerQubits, + parameters: Sequence[Parameter | str] | None = None + ) -> None: + """ + Initialize PulseCircuit. + + Args: + qubits(Qubits): Specification for standard qubits. + coupler_qubits(CouplerQubits): Specification for coupler qubits. + parameters(Sequence[Parameter | str] | None): Circuit parameters. + """ + super().__init__(qubits, parameters) + self._coupler_qubits = self._initialize_coupler_qubits(coupler_qubits) + + @property + def coupler_qubits(self) -> list[CouplerQubit]: + """ + A list of initialized coupler qubit objects in the circuit. + """ + return list(self._coupler_qubits.values()) + + def append_pulse(self, instruction: Instruction, qubit: CouplerQubit | Qubit): + """ + Appends a pulse instruction to the circuit. + + Args: + instruction (Instruction): Pulse instruction to add. + qubit (Qubit | CouplerQubit): Target qubit or coupler qubit. + """ + if isinstance(qubit, CouplerQubit) and str(qubit) not in self._coupler_qubits: + raise CqlibError(f"Coupler qubit {qubit} is not registered in the circuit. " + f"Available coupler qubits: {self.coupler_qubits}") + if isinstance(qubit, Qubit) and str(qubit) not in self._qubits: + raise CqlibError(f"Qubit {qubit} is not registered in the circuit. " + f"Available qubits: {self.qubits}") + self._circuit_data.append(InstructionData(instruction=instruction, qubits=[qubit])) + + def g(self, couple_qubit: CouplerQubit | int, length: int, coupling_strength: int): + """ + Applies a G-pulse (coupling pulse) to a coupler qubit. + + Args: + couple_qubit(CouplerQubit | int): Target coupler qubit or its index. + length(int): Duration of the pulse in time steps. + coupling_strength(int): Strength of the coupling interaction. + """ + if isinstance(couple_qubit, int): + couple_qubit = CouplerQubit(couple_qubit) + if not isinstance(couple_qubit, CouplerQubit): + raise CqlibError(f"Invalid type for coupler qubit. Expected CouplerQubit or int, " + f"got {type(couple_qubit).__name__} instead.") + self.append_pulse(G(length, coupling_strength), couple_qubit) + + def pxy(self, qubit: Qubit | int, waveform: Waveform): + """ + Applies a PXY-pulse (XY control pulse) to a standard qubit. + + Args: + qubit(Qubit | int): Target qubit or its index. + waveform(Waveform): Waveform object defining the pulse shape. + """ + if isinstance(qubit, int): + qubit = Qubit(qubit) + if not isinstance(qubit, Qubit): + raise CqlibError(f"Invalid type for qubit. Expected Qubit or int, " + f"got {type(qubit).__name__} instead.") + self.append_pulse(PXY(waveform=waveform), qubit) + + def pz(self, qubit: CouplerQubit | Qubit, waveform: Waveform): + """ + Applies a PZ-pulse (Z control pulse) to a qubit or coupler qubit. + + Args: + qubit(CouplerQubit | Qubit): Target CouplerQubit or Qubit. + waveform (Waveform): Waveform object defining the pulse shape. + """ + self.append_pulse(PZ(waveform=waveform), qubit) + + def pz0(self, qubit: CouplerQubit | Qubit, waveform: Waveform): + """ + Applies a PZ0-pulse (parallel) to a qubit or coupler qubit. + + Args: + qubit(CouplerQubit | Qubit): Target CouplerQubit or Qubit. + waveform (Waveform): Waveform object defining the pulse shape. + """ + self.append_pulse(PZ0(waveform=waveform), qubit) + + def add_coupler_qubits(self, couple_qubits: CouplerQubits): + """ + Adds a coupler qubit(or list) to the circuit, ensuring it does not already exist. + + Args: + couple_qubits (CouplerQubits): The coupler qubits to add, specified as an + integer index or a Qubit object. + """ + if not isinstance(couple_qubits, Sequence): + couple_qubits = [couple_qubits] + + for qubit in couple_qubits: + if isinstance(qubit, int): + qubit = CouplerQubit(qubit) + elif not isinstance(qubit, CouplerQubit): + raise TypeError(f"{qubit} must be an int or CouplerQubit instance.") + if str(qubit) in self._qubits: + raise ValueError(f"CouplerQubit {qubit} already exists in the circuit.") + self._coupler_qubits[str(qubit)] = qubit + + @staticmethod + def _initialize_coupler_qubits(coupler_qubits: CouplerQubits) -> dict[str, CouplerQubit]: + """ + Helper function to initialize coupler_qubits. + + Args: + coupler_qubits (CouplerQubits): Input coupler qubits specification. + + Returns: + dict[int, CouplerQubit]: Dictionary of CouplerQubit objects. + """ + if isinstance(coupler_qubits, int): + if coupler_qubits < 0: + raise ValueError("Number of coupler qubits must be non-negative.") + return {str(Qubit(i)): CouplerQubit(i) for i in range(coupler_qubits)} + if isinstance(coupler_qubits, CouplerQubit): + return {str(coupler_qubits): coupler_qubits} + if not isinstance(coupler_qubits, (list, tuple)): + raise ValueError("Invalid coupler_qubits input. Expected int, CouplerQubit," + " or list/tuple of these.") + + qs = {} + for qubit in coupler_qubits: + if isinstance(qubit, int): + qubit = CouplerQubit(qubit) + elif not isinstance(qubit, CouplerQubit): + raise TypeError("CouplerQubit must be an int or CouplerQubit instance.") + if qubit.index in qs: + raise ValueError(f"Duplicate qubit detected: {qubit}") + qs[str(qubit)] = qubit + return qs + + def to_qasm2(self) -> str: + """ + Generate a QASM 2.0 string representation of the circuit. + + Note: + Circuits containing coupler qubits cannot be exported to QASM 2.0 format, + as the standard does not support coupler qubit operations. + + Returns: + str: QASM 2.0 compliant code representing the circuit + + Raises: + CqlibError: If the circuit contains coupler qubits, as they are not supported + in QASM 2.0. + """ + if self._coupler_qubits: + raise CqlibError(f"QASM 2.0 export not supported for circuits with coupler qubits. " + f"Found coupler(s): {self.coupler_qubits}") + return super().to_qasm2() + + @classmethod + def load(cls, qcis: str) -> 'PulseCircuit': + """ + Loads quantum circuit with pulse-level instructions from QCIS string. + + Extends base Circuit functionality with support for: + - Coupler qubits (G-prefixed identifiers) + - Pulse waveforms (PXY, PZ, PZ0) + - Hybrid circuits mixing gates and pulses + + Args: + qcis: String containing hybrid quantum instructions. + + Returns: + PulseCircuit: Circuit with pulse capabilities initialized. + """ + circuit = cls(qubits=[], coupler_qubits=[]) + pattern = re.compile(r'^([A-Z][A-Z0-9]*)\s+((?:[QG][0-9]+\s*)+)' + r'((?:[+-]?(?:\d*\.\d+|\d+)(?:[Ee][+-]?\d+)?\s*)*)$') + for line in qcis.split('\n'): + line = re.sub(r'(#|//).*', '', line).strip() + if not line: + continue + match = pattern.match(line) + if not match: + raise ValueError(f'Invalid instruction format: {line}') + gate, qubits_str, params_str = match.groups() + qubits, coupler_qubits = cls._parse_pulse_qubits_str(circuit, qubits_str) + params = [float(p) for p in params_str.split()] if params_str else [] + if gate in PULSE_GATES: + cls._process_pulse_instruction(circuit, gate, qubits, coupler_qubits, params) + else: + cls._process_instruction(circuit, gate, qubits, params) + + return circuit + + def _parse_pulse_qubits_str( + self, + qubits_str: str + ) -> tuple[list[Qubit], list[CouplerQubit]]: + """ + Parses and categorizes qubit identifiers from QCIS instruction string. + + Processes space-separated qubit tokens distinguishing between: + - Standard qubits (Q-prefixed) + - Coupler qubits (G-prefixed) + + Args: + qubits_str: Raw qubit specification from QCIS instruction line. + + Returns: + tuple: Contains two lists respectively holding: + [0] Parsed standard Qubit objects + [1] Parsed CouplerQubit objects + """ + qubits, coupler_qubits = [], [] + for q_str in qubits_str.split(): + if q_str.startswith('Q'): + qubit = self._qubits.setdefault(q_str, Qubit(int(q_str[1:]))) + qubits.append(qubit) + elif q_str.startswith('G'): + coupler_qubit = self._coupler_qubits.setdefault(q_str, CouplerQubit(int(q_str[1:]))) + coupler_qubits.append(coupler_qubit) + else: + raise ValueError(f"Invalid qubit format: {q_str}") + return qubits, coupler_qubits + + def _process_pulse_instruction( + self, + gate: str, + qubits: list[Qubit], + coupler_qubits: list[CouplerQubit], + params: list[float | int] + ): + """ + Processes pulse-specific quantum instructions from parsed components. + + Handles three categories of pulse operations: + 1. G-type coupling pulses + 2. PXY parametric XY control pulses + 3. General pulse operations (PZ/PZ0 and dynamically resolved instructions) + + Args: + gate(str): Uppercase gate identifier (e.g., 'G', 'PXY', 'PZ') + qubits( list[Qubit]): Standard qubit targets parsed from instruction + coupler_qubits(list[CouplerQubit]): Coupler qubit targets parsed from instruction + params(list[float | int]): Numerical parameters following the qubit specification + """ + if gate == 'G': + if len(params) != 2: + raise CqlibError("G gate requires exactly 2 parameters (length, coupling_strength)") + length, coupling_strength = params + if length != int(length): + raise CqlibError( + f"G pulse length parameter must be integer value, " + f"received {type(length).__name__} {length}" + ) + if coupling_strength != int(coupling_strength): + raise CqlibError( + f"G pulse coupling_strength parameter requires integer value, " + f"got {type(coupling_strength).__name__} {coupling_strength}" + ) + self._circuit_data.append( + InstructionData(instruction=G(int(length), int(coupling_strength)), + qubits=coupler_qubits) + ) + return + # Create waveform for pulse operations + waveform = Waveform.load(params, gate) + if gate == 'PXY': + if not qubits: + raise CqlibError("PXY pulse requires valid qubit targets") + self._circuit_data.append( + InstructionData(instruction=PXY(waveform), qubits=qubits) + ) + return + # For PZ/PZ0, validate mutual exclusivity of qubit types + if bool(qubits) == bool(coupler_qubits): + raise CqlibError("Must provide exactly one of qubits or coupler_qubits for PZ/PZ0") + qubits = qubits if len(qubits) > 0 else coupler_qubits + + # Dynamic gate resolution + try: + # pylint: disable=import-outside-toplevel, cyclic-import + from cqlib import pulse + instruction_class = getattr(pulse, gate) + except AttributeError as ex: + raise CqlibError(f"Unknown pulse instruction: {gate}") from ex + if not issubclass(instruction_class, BasePulse): + raise CqlibError(f"{gate} is not a valid pulse instruction") + self._circuit_data.append( + InstructionData(instruction=instruction_class(waveform=waveform), qubits=qubits) + ) diff --git a/cqlib/pulse/pxy.py b/cqlib/pulse/pxy.py new file mode 100644 index 0000000000000000000000000000000000000000..e0944a09accd37cf94d893bd35e4c01314990da8 --- /dev/null +++ b/cqlib/pulse/pxy.py @@ -0,0 +1,48 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Sequential AC(Alternating Current) pulse controller for data qubit XY channels. +""" +from cqlib.exceptions import CqlibError +from .base_pulse import BasePulse +from .waveform import Waveform, NumericWaveform + + +class PXY(BasePulse): + """ + Pulse controller for data qubit XY channels. + + For PXY pulses, the numerical list of data pulses is in the form of + [i0, i1, ..., in, q0, q1, ..., qn]. The first half describes the + pulse values of I channel, and the second half describes the + pulse values of Q channel. The length of its list is >=6 and + must be even. + """ + + def __init__(self, waveform: Waveform, label: str = None): + """ + Initialize XY channel pulse with waveform parameters + """ + super().__init__('PXY', waveform=waveform, label=label) + + def validate(self): + """ + Verify PXY-specific waveform constraints. + """ + waveform = self._waveform + if waveform.phase is None or waveform.drag_alpha is None: + raise CqlibError('PXY must have phase and drag_alpha params') + if isinstance(waveform, NumericWaveform) \ + and (len(waveform.data_list) < 6 or len(waveform.data_list) % 2 == 1): + raise CqlibError('The length of its list must >=6 and must be even.') diff --git a/cqlib/pulse/pz.py b/cqlib/pulse/pz.py new file mode 100644 index 0000000000000000000000000000000000000000..d2e277d2be7a1e9ea6675a43b2ad204d8db698b3 --- /dev/null +++ b/cqlib/pulse/pz.py @@ -0,0 +1,32 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" Sequential DC pulse controller for data/coupling qubit Z channels. """ +from cqlib.exceptions import CqlibError +from .base_pulse import BasePulse +from .waveform import Waveform + + +class PZ(BasePulse): + """ + Sequential DC pulse controller for data/coupling qubit Z channels. + """ + + def __init__(self, waveform: Waveform, label: str = None): + """Initialize Z-axis DC pulse with Stark shift parameters""" + super().__init__('PZ', waveform=waveform, label=label) + + def validate(self): + p = self._waveform + if p.phase is not None or p.drag_alpha is not None: + raise CqlibError('PZ must not have phase and drag_alpha params') diff --git a/cqlib/pulse/pz0.py b/cqlib/pulse/pz0.py new file mode 100644 index 0000000000000000000000000000000000000000..20a8bd73e564643dba142e1a1bbf1653b883dbe9 --- /dev/null +++ b/cqlib/pulse/pz0.py @@ -0,0 +1,43 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + + +"""Parallel DC pulse controller for data/coupling qubit Z channels.""" +from cqlib.exceptions import CqlibError +from .base_pulse import BasePulse +from .waveform import Waveform + + +class PZ0(BasePulse): + """ + Parallel DC pulse controller for data/coupling qubit Z channels. + + Enables synchronized multi-channel DC pulse generation for simultaneous + frequency adjustments. + """ + + def __init__(self, waveform: Waveform, label: str = None): + """ + Initialize parallel Z-axis DC pulse controller. + + Args: + waveform (Waveform): Waveform. + label (str, optional): Operational identifier for batch processing + """ + super().__init__('PZ0', waveform, label=label) + + def validate(self): + """ Verify parallel DC pulse constraints. """ + p = self._waveform + if p.phase is not None or p.drag_alpha is not None: + raise CqlibError('PZ0 must not have phase and drag_alpha params') diff --git a/cqlib/pulse/waveform.py b/cqlib/pulse/waveform.py new file mode 100644 index 0000000000000000000000000000000000000000..c766d365fe4a0098a852a5c029f7a93cdf38bfa5 --- /dev/null +++ b/cqlib/pulse/waveform.py @@ -0,0 +1,272 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Waveform + +This module defines waveform type enumerations and parameter classes +for generating quantum pulse sequences. It provides a standardized +interface to configure and validate waveform parameters across different +waveform types. +""" +from __future__ import annotations +from dataclasses import dataclass, field +from enum import IntEnum, unique +from math import pi + +from cqlib.exceptions import CqlibError + + +@unique +class WaveformType(IntEnum): + """ + Enumeration of supported waveform types for quantum pulse sequences. + + Members: + COSINE (0): Cosine-shaped waveform. + FLATTOP (1): Flat-top waveform with customizable edge length. + SLEPIAN (2): Slepian tapering waveform defined by time-bandwidth + product parameters. + NUMERIC (3): Arbitrary numeric waveform specified via sample array. + """ + COSINE = 0 + FLATTOP = 1 + SLEPIAN = 2 + NUMERIC = 3 + + +@dataclass(kw_only=True) +class Waveform: + """ + Base dataclass for waveform parameter validation and string representation. + + Attributes: + waveform (WaveformType): Enumerated waveform type. + length (int): Total waveform duration (1-1e5). + amplitude (float): Normalized signal amplitude (0.0-1.0). + phase (float, optional): Phase shift for PXY modulations. + drag_alpha (float, optional): Drag coefficient for PXY modulations. + """ + waveform: WaveformType + length: int + amplitude: float + phase: float = None # PXY only + drag_alpha: float = None # PXY only + + @staticmethod + def create( + w_type: WaveformType, + length: int, + amplitude: float, + phase: float = None, + drag_alpha: float = None, + **kwargs + ) -> 'Waveform': + """ + Factory method to create waveform parameter objects based on the specified type. + + Instantiates a concrete waveform parameter class according to the given waveform type, + validates parameters, and returns the initialized object. + + Args: + w_type(WaveformType): Enumerated waveform type identifier. + length(int): Total duration of the waveform in samples (1-1e5). + amplitude: Normalized signal amplitude (0.0-1.0). + phase (float, optional): Phase shift for PXY. Must be in (-π, π] range. + drag_alpha (float, optional): DRAG correction coefficient for PXY. Must be positive. + **kwargs: Type-specific parameters for advanced waveform configurations: + - FLATTOP: edge (int) + - SLEPIAN: thf, thi, lam2, lam3 (float) + - NUMERIC: samples (list[float]) + """ + param_classes = { + WaveformType.COSINE: CosineWaveform, + WaveformType.FLATTOP: FlattopWaveform, + WaveformType.SLEPIAN: SlepianWaveform, + WaveformType.NUMERIC: NumericWaveform + } + + if w_type not in param_classes: + raise ValueError(f"Unsupported waveform type: {w_type}") + + return param_classes[w_type]( + waveform=w_type, + length=length, + amplitude=amplitude, + phase=phase, + drag_alpha=drag_alpha, + **kwargs + ) + + def validate(self): + """Performs basic parameter validation.""" + if not isinstance(self.length, int) or self.length <= 0 or self.length > 1e5: + raise CqlibError(f"Invalid length: {self.length}. Must be in [0, 1e5]") + if not 0.0 <= self.amplitude <= 1.0: + raise CqlibError(f"Invalid amplitude: {self.amplitude}. Must be in [0, 1]") + if self.phase is not None and (self.phase <= -pi or self.phase >= pi): + raise CqlibError("The phase value must be within (-pi, pi].") + if self.drag_alpha is not None and self.drag_alpha < 0: + raise CqlibError("The drag_alpha value must be positive float.") + + def __str__(self): + return ' '.join(map(str, self.data)) + + @property + def data(self) -> list[float]: + """Data list""" + ps = [self.waveform.value, self.length, self.amplitude] + if self.phase is not None and self.drag_alpha is not None: + ps.extend([self.phase, self.drag_alpha]) + return ps + + @classmethod + def load(cls, waveform: str | list[float | int], gate: str) -> Waveform: + """ + Constructs Waveform from serialized data. + + Args: + waveform (str | list[float | int]): Input data as either: + - Space-separated string of parameters + - List of numeric values + gate (str): Target gate operation (PXY/PZ/etc.) determining + parameter interpretation + + Returns: + Waveform: Instantiated waveform object + """ + if isinstance(waveform, str): + waveform = map(float, waveform.split()) + ps = waveform + try: + waveform_type = WaveformType(ps[0]) + except ValueError as e: + raise CqlibError(f"Invalid waveform type value: {ps[0]}") from e + length, amplitude = ps[1:3] + ps = ps[3:] + if gate == 'PXY': + phase, drag_alpha = ps[:2] + ps = ps[2:] + else: + phase, drag_alpha = None, None + kwargs = { + 'w_type': waveform_type, + 'length': int(length), + 'amplitude': amplitude, + 'phase': phase, + 'drag_alpha': drag_alpha + } + + if waveform_type == WaveformType.FLATTOP: + edge = ps[0] + if edge != int(edge): + raise CqlibError('') + kwargs['edge'] = int(edge) + else: + ps = [int(p) if p == int(p) else p for p in ps] + if waveform_type == WaveformType.SLEPIAN: + kwargs.update({ + 'thf': ps[0], + 'thi': ps[1], + 'lam2': ps[2], + 'lam3': ps[3], + }) + elif waveform_type == WaveformType.NUMERIC: + kwargs['data_list'] = ps + return Waveform.create(**kwargs) + + +@dataclass +class CosineWaveform(Waveform): + """Configuration parameters for cosine-shaped waveforms.""" + waveform: WaveformType = field(default=WaveformType.COSINE) + + def __post_init__(self): + self.validate() + + +@dataclass +class FlattopWaveform(Waveform): + """ + Specialized parameters for flat-top waveforms with edge control. + + Additional Attributes: + edge (int): Length of rising/falling edges (must be < total length/2). + """ + waveform: WaveformType = field(default=WaveformType.FLATTOP) + edge: int = None + + def __post_init__(self): + self.validate() + if self.edge < 0 or not isinstance(self.edge, int): + raise CqlibError(f"Flattop waveform edge parameter must be a positive integer, " + f"got {type(self.edge).__name__} {self.edge}") + if 2 * self.edge >= self.length: + raise CqlibError(f"Edge ({self.edge}) too large for length {self.length}") + + @property + def data(self) -> list[float]: + ps = super().data + ps.append(self.edge) + return ps + + +@dataclass +class SlepianWaveform(Waveform): + """ + Specialized parameters for slepian waveforms + + Additional Attributes: + - thf: + - thi: + - lam2: + - lam3: + """ + waveform: WaveformType = field(default=WaveformType.SLEPIAN) + thf: float = None + thi: float = None + lam2: float = None + lam3: float = None + + @property + def data(self) -> list[float]: + ps = super().data + ps.extend([self.thf, self.thi, self.lam2, self.lam3]) + return ps + + +@dataclass +class NumericWaveform(Waveform): + """ + Specialized parameters for numeric waveforms. + + Additional Attributes: + - params: List of floats, Values must be in [0.0, 1.0], + Minimum length of 3 samples; + """ + waveform: WaveformType = field(default=WaveformType.NUMERIC) + data_list: list[float] = None + + def __post_init__(self): + super().validate() + if any(not (0.0 <= x <= 1.0) for x in self.data_list): + raise CqlibError("All data values must be in [0,1]") + if len(self.data_list) < 3: + raise CqlibError("The numerical list of data pulses is in the form" + " of [d0, d1, ..., dn], and the list length must be >= 3") + + @property + def data(self) -> list[float]: + ps = super().data + ps.extend(self.data_list) + return ps diff --git a/cqlib/simulator/__init__.py b/cqlib/simulator/__init__.py index 1d6b27e2e51f08e00fb4489e9e531070ed03394a..92543e4c10955434e50f7c6e3ce03381c8f1ab56 100644 --- a/cqlib/simulator/__init__.py +++ b/cqlib/simulator/__init__.py @@ -16,3 +16,5 @@ Simulator module use classical computer to simulate quantum computers' behavior. """ from .statevector_simulator import StatevectorSimulator + +__all__ = ['StatevectorSimulator'] diff --git a/cqlib/simulator/mergy.py b/cqlib/simulator/mergy.py index 5cf11fd4210ac27cb9f9b7e4d4fb85a2ea6e13d9..6775babc394b00f19ade20bb250721dcce66e183 100644 --- a/cqlib/simulator/mergy.py +++ b/cqlib/simulator/mergy.py @@ -55,13 +55,22 @@ class Gate: else: self.mat = np.asarray(mat) + def __repr__(self) -> str: + return (f"\nname: {self.name}\nqubits: {self.qubits}\n" + f"theta: {self.theta}\nmat: {self.mat}\n") + class FusionGate: """ Represents a fused gate """ - def __init__(self, name: str, qubits: list[int] = None, idx: list = None): + def __init__( + self, + name: str, + qubits: list[int] = None, + idx: list = None + ): """ Initializes a FusionGate object. @@ -72,14 +81,16 @@ class FusionGate: """ self.name = name if qubits is None: - self.qubits = [] - else: - self.qubits = qubits + raise TypeError("Qubits is needed.") + self.qubits = qubits if idx is None: self.idx = [] else: self.idx = idx + def __repr__(self) -> str: + return f"\nname: {self.name}\nqubits: {self.qubits}\nidx: {self.idx}\n" + def to_dag(gates: Sequence[Gate]) -> nx.DiGraph: """ @@ -93,36 +104,38 @@ def to_dag(gates: Sequence[Gate]) -> nx.DiGraph: """ dg = nx.DiGraph() pre_nodes = defaultdict(lambda: -1) + for idx, gate in enumerate(gates): - if len(gate.qubits) == 1: - qubit = gate.qubits[0] - node = pre_nodes[qubit] - if node == -1: - dg.add_node(idx, gate=FusionGate(gate.name, gate.qubits.copy(), [idx])) - pre_nodes[qubit] = idx - else: - dg.nodes[node]["gate"].idx.append(idx) - dg.nodes[node]["gate"].name = "fgate" + pre = [pre_nodes[q] for q in gate.qubits] + dg.add_node(idx, gate=FusionGate(gate.name, gate.qubits.copy(), [idx])) + for p in pre: + if p != -1: + dg.add_edge(p, idx) + predecessors = list(dg.predecessors(idx)) + + if len(predecessors) == 1: + gate1 = dg.nodes[idx]["gate"] + gate2 = dg.nodes[predecessors[0]]["gate"] + if set(gate1.qubits) & set(gate2.qubits) == set(gate1.qubits): + dg.nodes[predecessors[0]]["gate"].idx += gate1.idx + dg.nodes[predecessors[0]]["gate"].name = "fgate" + dg.remove_node(idx) else: - c, t = gate.qubits - if pre_nodes[c] == pre_nodes[t] and pre_nodes[c] != -1: - # dg.nodes[pre_nodes[c]]['gate'].mat = gate.mat @ dg.nodes[pre_nodes[c]]['gate'].mat - dg.nodes[pre_nodes[c]]["gate"].idx.append(idx) - dg.nodes[pre_nodes[c]]["gate"].name = "fgate" - else: - dg.add_node(idx, gate=FusionGate(gate.name, gate.qubits.copy(), [idx])) - for predecessor in [pre_nodes[c], pre_nodes[t]]: - if predecessor == -1: - continue - gate2 = dg.nodes[predecessor]["gate"] - if len(gate2.qubits) == 1: + gate1 = dg.nodes[idx]["gate"] + for predecessor in predecessors: + gate2 = dg.nodes[predecessor]["gate"] + if set(gate1.qubits) & set(gate2.qubits) == set(gate2.qubits): + if all(pre_nodes[q] == predecessor for q in gate2.qubits): dg.nodes[idx]["gate"].idx += gate2.idx dg.nodes[idx]["gate"].name = "fgate" + for j in dg.predecessors(predecessor): + dg.add_edge(j, idx) dg.remove_node(predecessor) - else: - dg.add_edge(predecessor, idx) - pre_nodes[c] = idx - pre_nodes[t] = idx + + if dg.has_node(idx): + for q in gate.qubits: + pre_nodes[q] = idx + return dg @@ -191,15 +204,11 @@ class CostBasedFusion: list: A list of fused gates. """ fusions = [] - costs = [] - fusion_to = [] - fusion_to.append(fusion_start) - costs.append( - np.power( - self.cost_factor, - max(len(fusion_gates[fusion_start].qubits) - 1, 1), - ) - ) + fusion_to = [fusion_start] + costs = [np.power( + self.cost_factor, + max(len(fusion_gates[fusion_start].qubits) - 1, 1), + )] for i in range(fusion_start + 1, fusion_end): fusion_to.append(i) @@ -235,7 +244,8 @@ class CostBasedFusion: # return fusions return self.convert(fusions) - def convert(self, fusions: list): + @staticmethod + def convert(fusions: list): dg = nx.DiGraph() pre_nodes = defaultdict(lambda: -1) for idx, gate in enumerate(fusions): @@ -269,62 +279,59 @@ class CostBasedFusion: for q in gate.qubits: pre_nodes[q] = idx - fusionsgate = [] + fusions_gate = [] for node in dg.nodes: - fusionsgate.append(dg.nodes[node]["gate"]) - fusionsgate[-1].idx.sort() - return fusionsgate + fusions_gate.append(dg.nodes[node]["gate"]) + fusions_gate[-1].idx.sort() + return fusions_gate -def get_mat(gate: Gate, qnum: int, qmap: dict) -> np.ndarray: +def get_mat( + gates: list[Gate], + fusion: FusionGate, + qnum: int, + qmap: dict +) -> np.ndarray: """ Generates the matrix for a given gate and maps it to the appropriate qubits. Args: - gate (Gate): The quantum gate. + gates (list[Gate]): The quantum gate list. qnum (int): The total number of qubits. qmap (dict): A dictionary mapping the qubit indices to the correct position. Returns: np.ndarray: The matrix representation of the gate applied to the correct qubits. """ - qubits = [qmap[q] for q in gate.qubits] - if len(qubits) == 1: - q = qubits[0] - mat = np.eye(2 ** q) - mat = np.kron(mat, gate.mat) - mat = np.kron(mat, np.eye(2 ** (qnum - 1 - q))) - return mat - else: - c, t = qubits - mat1 = np.eye(2 ** c) - mat1 = np.kron(mat1, np.diag([1, 0])) - mat1 = np.kron(mat1, np.eye(2 ** (qnum - 1 - c))) - - if c < t: - mat2 = np.eye(2 ** c) - mat2 = np.kron(mat2, np.diag([0, 1])) - mat2 = np.kron(mat2, np.eye(2 ** (t - 1 - c))) - if gate.name in ["CNOT", "CX"]: - mat2 = np.kron(mat2, x_mat) - if gate.name == "CY": - mat2 = np.kron(mat2, y_mat) - if gate.name == "CZ": - mat2 = np.kron(mat2, z_mat) - mat2 = np.kron(mat2, np.eye(2 ** (qnum - 1 - t))) - return mat1 + mat2 - else: - mat2 = np.eye(2 ** t) - if gate.name in ["CNOT", "CX"]: - mat2 = np.kron(mat2, x_mat) - if gate.name == "CY": - mat2 = np.kron(mat2, y_mat) - if gate.name == "CZ": - mat2 = np.kron(mat2, z_mat) - mat2 = np.kron(mat2, np.eye(2 ** (c - 1 - t))) - mat2 = np.kron(mat2, np.diag([0, 1])) - mat2 = np.kron(mat2, np.eye(2 ** (qnum - 1 - c))) - return mat1 + mat2 + shape = [2 ** qnum] + [2 for _ in range(qnum)] + mat = np.eye(2 ** qnum).reshape(shape) + for i in fusion.idx: + op = gates[i] + qubits = [qmap[q] for q in op.qubits] + subscripts = ( + [0] + + [qubit + 1 for qubit in qubits] + + [ + i + for i in range(1, qnum + 1) + if i not in {qubit + 1 for qubit in qubits} + ] + ) + mat = np.reshape(np.transpose(mat, subscripts), (2 ** qnum, 2 ** len(qubits), -1)) + mat = np.matmul(np.reshape(op.mat, (2 ** len(qubits), -1)), mat) + mat = np.transpose( + np.reshape(mat, shape), + _inv_subscripts(subscripts), + ) + mat = mat.reshape(2 ** qnum, -1).T.copy() + return mat + + +def _inv_subscripts(subscripts: list[int]) -> list[int]: + inv = [0] * len(subscripts) + for i, sub in enumerate(subscripts): + inv[sub] = i + return inv def fusion_to_gate(gates: list, fusion: FusionGate) -> Gate: @@ -339,7 +346,6 @@ def fusion_to_gate(gates: list, fusion: FusionGate) -> Gate: Returns: Gate: A new Gate object representing the fused operation. """ - if fusion.name == "fgate": qubits = list(fusion.qubits) qnum = len(qubits) @@ -347,12 +353,9 @@ def fusion_to_gate(gates: list, fusion: FusionGate) -> Gate: qmap = {} for i, v in enumerate(qubits): qmap[v] = i - mat = np.eye(2 ** qnum) - for i in fusion.idx: - mat = get_mat(gates[i], qnum, qmap) @ mat + mat = get_mat(gates, fusion, qnum, qmap) return Gate(fusion.name, qubits, theta=[], mat=mat) - else: - return gates[fusion.idx[0]] + return gates[fusion.idx[0]] def merge_gate(ori_gates: list[Gate], max_qubit: int) -> list[Gate]: @@ -370,17 +373,17 @@ def merge_gate(ori_gates: list[Gate], max_qubit: int) -> list[Gate]: if max_qubit == 1: return ori_gates dg = to_dag(ori_gates) - fusiongates = [] + fusion_gates = [] for node in dg.nodes: - fusiongates.append(dg.nodes[node]["gate"]) + fusion_gates.append(dg.nodes[node]["gate"]) fusion_method = CostBasedFusion() - fusiongates = fusion_method.aggregate_gates( - fusiongates, 0, len(fusiongates), max_qubit + fusion_gates = fusion_method.aggregate_gates( + fusion_gates, 0, len(fusion_gates), max_qubit ) new_gates = [] - for fusion in fusiongates: + for fusion in fusion_gates: new_gates.append(fusion_to_gate(ori_gates, fusion)) return new_gates diff --git a/cqlib/simulator/statevector_simulator.py b/cqlib/simulator/statevector_simulator.py index de44322962d97f76210918cedc1b182ca6fdd0a5..97539c368eedf61d11fc38d7bf972e404a60a88b 100644 --- a/cqlib/simulator/statevector_simulator.py +++ b/cqlib/simulator/statevector_simulator.py @@ -33,16 +33,17 @@ The results will be returned in the order of Q1, Q0. """ import ctypes +import time from collections import Counter -from os import path import numpy as np from cqlib.circuits.circuit import Circuit from cqlib.circuits.parameter import Parameter -from .mergy import Gate, merge_gate +from cqlib.exceptions import CqlibError +from cqlib.simulator.mergy import Gate, merge_gate -lib_file_name = path.abspath(path.join(path.dirname(__file__), "lib", "libcfusion.so")) +from cqlib.simulator.statevector_simulator_c import get_state, get_probs, get_measure, get_sample gate_name_map = { "H": 72, @@ -64,41 +65,21 @@ gate_name_map = { "RY": 8289, "RZ": 8290, "U3": 8551, + "U": 8551, "fgate": 102, "XY": 8889, "XY2M": 88895077, "XY2P": 88895080, "RXY": 828889, + "CCX": 102, + "SWAP": 102, + "CRX": 102, + "CRY": 102, + "CRZ": 102, + "custom_gate": 102, } -class CGate(ctypes.Structure): - """ - CGate class represents a quantum gate structure in C, which is used to interface - with the low-level C library for quantum gate simulations. - - """ - _fields_ = [ - ("gate_id", ctypes.c_size_t), - ("qubits", ctypes.POINTER(ctypes.c_uint32)), - ("qubits_len", ctypes.c_size_t), - ("theta", ctypes.POINTER(ctypes.c_double)), - ("mat", ctypes.c_void_p), - ] - - def __init__(self, gate: Gate): - """ - Initializes the CGate object by converting a Python Gate object to the C structure. - """ - super().__init__() - self.gate_id = gate_name_map[gate.name] - # NOTE: may raise key Err, but this is a feature - self.qubits = (len(gate.qubits) * ctypes.c_uint32)(*gate.qubits) - self.qubits_len = len(gate.qubits) - self.theta = (len(gate.theta) * ctypes.c_double)(*gate.theta) - self.mat = gate.mat.ctypes.data_as(ctypes.c_void_p) - - class StatevectorSimulator: """ StatevectorSimulator is a quantum circuit simulator that simulates quantum circuits @@ -108,7 +89,7 @@ class StatevectorSimulator: def __init__( self, circuit: Circuit, - is_fusion: bool = True, + is_fusion: bool = False, fusion_max_qubit: int = 5, omp_threads: int = 0, fusion_th: int = 15, @@ -131,7 +112,6 @@ class StatevectorSimulator: self.omp_threads = omp_threads self.fusion_th = fusion_th self._fusion_applied = False - self.libc = ctypes.CDLL(lib_file_name) self.nq = len(circuit.qubits) self.dim: int = 2 ** self.nq @@ -139,6 +119,10 @@ class StatevectorSimulator: self.measure_qubits = [] self.qubit_mapping = {q: i for i, q in enumerate(circuit.qubits)} self._parse_circuit() + self.state_ptr_capsule = None + self.probs_ptr_capsule = None + self.measure_ptr_capsule = None + self.samples_ptr_capsule = None def _parse_circuit(self): """ @@ -158,12 +142,13 @@ class StatevectorSimulator: if isinstance(param, Parameter): param = float(param.value(params=self.circuit.parameters_value)) ps.append(param) + name = item.instruction.name self.gates.append(Gate( - name=item.instruction.name, + name=name, qubits=qubits, theta=ps, - mat=np.array(item.instruction) + mat=np.asarray(item.instruction) )) def _check_fusion(self): @@ -207,22 +192,11 @@ class StatevectorSimulator: np.ndarray: The state vector representing the quantum state. """ self._check_fusion() - state_ptr = ctypes.c_void_p() - c_gates_len = len(self.gates) - c_gates = (c_gates_len * CGate)(*(CGate(gate) for gate in self.gates)) - self.libc.get_state.restype = ctypes.c_int - errcode = self.libc.get_state( - ctypes.byref(ctypes.c_uint32(self.nq)), - ctypes.byref(ctypes.c_uint64(self.dim)), - ctypes.byref(ctypes.c_size_t(c_gates_len)), - c_gates, # array addr - ctypes.byref(state_ptr), # void** - ctypes.c_uint32(self.omp_threads), - ) - self._check_errcode(errcode) - state_ptr = ctypes.cast(state_ptr, ctypes.POINTER(ctypes.c_double)) - state = np.ctypeslib.as_array(state_ptr, shape=(self.dim * 2,)).view(dtype=np.complex128) - return {np.binary_repr(i, width=self.nq): s for i, s in enumerate(state)} + gates_list = self.get_gates() + state_list, state_ptr_capsule = get_state(self.nq, 2 ** self.nq, gates_list, self.omp_threads) + self.state_ptr_capsule = state_ptr_capsule + state = {np.binary_repr(i, width=self.nq): val for i, val in enumerate(state_list)} + return state def probs(self) -> dict[str, np.float64]: """ @@ -233,22 +207,11 @@ class StatevectorSimulator: np.ndarray: A list of probabilities for each possible outcome. """ self._check_fusion() - probs_ptr = ctypes.c_void_p() - c_gates_len = len(self.gates) - c_gates = (c_gates_len * CGate)(*(CGate(gate) for gate in self.gates)) - self.libc.get_probs.restype = ctypes.c_int - errcode = self.libc.get_probs( - ctypes.byref(ctypes.c_uint32(self.nq)), - ctypes.byref(ctypes.c_uint64(self.dim)), - ctypes.byref(ctypes.c_size_t(c_gates_len)), - c_gates, # array addr - ctypes.byref(probs_ptr), # void** - ctypes.c_uint32(self.omp_threads), - ) - self._check_errcode(errcode) - probs_ptr = ctypes.cast(probs_ptr, ctypes.POINTER(ctypes.c_double)) - probs = np.ctypeslib.as_array(probs_ptr, shape=(self.dim,)).view(dtype=np.float64) - return {np.binary_repr(i, width=self.nq): p for i, p in enumerate(probs)} + if self.state_ptr_capsule is None: + self.statevector() + probs_array, probs_ptr_capsule = get_probs(self.nq, 2 ** self.nq, self.omp_threads, self.state_ptr_capsule) + self.probs_ptr_capsule = probs_ptr_capsule + return {np.binary_repr(i, width=self.nq): val for i, val in enumerate(probs_array)} def measure(self) -> np.array: """ @@ -258,36 +221,24 @@ class StatevectorSimulator: Returns: np.ndarray: A probability distribution of the measurement results. """ + self._check_fusion() + if self.probs_ptr_capsule is None: + self.probs() + if not self.measure_qubits: # all measured - return self.probs() - mq_len = len(self.measure_qubits) - assert mq_len == len(set(self.measure_qubits)) - if self.measure_qubits == sorted(self.measure_qubits.copy()) and mq_len == self.nq: - return self.probs() + raise CqlibError("Measured qubits are empty, please add measurement gates first.") mq_ordered = sorted(self.measure_qubits.copy()) - - self._check_fusion() - measure_ptr = ctypes.c_void_p() - c_gates_len = len(self.gates) - c_gates = (c_gates_len * CGate)(*(CGate(gate) for gate in self.gates)) - - self.libc.get_measure.restype = ctypes.c_int - errcode = self.libc.get_measure( - ctypes.byref(ctypes.c_uint32(self.nq)), - ctypes.byref(ctypes.c_uint64(self.dim)), - ctypes.byref(ctypes.c_size_t(c_gates_len)), - c_gates, # array addr - ctypes.byref(measure_ptr), # void** - ctypes.c_uint32(self.omp_threads), - (mq_len * ctypes.c_uint32)(*self.measure_qubits), - (mq_len * ctypes.c_uint32)(*mq_ordered), - ctypes.byref(ctypes.c_size_t(mq_len)), + # Call get_measure from the C extension module + measure_array, measure_ptr_capsule = get_measure( + self.nq, + self.omp_threads, + self.measure_qubits, + mq_ordered, + self.probs_ptr_capsule ) - self._check_errcode(errcode) - measure_ptr = ctypes.cast(measure_ptr, ctypes.POINTER(ctypes.c_double)) - data = np.ctypeslib.as_array(measure_ptr, shape=(2 ** mq_len,)).view(dtype=np.float64) - return data + self.measure_ptr_capsule = measure_ptr_capsule + return {np.binary_repr(i, width=len(self.measure_qubits)): val for i, val in enumerate(measure_array)} def sample( self, @@ -295,6 +246,7 @@ class StatevectorSimulator: is_sorted: bool = False, sample_block_th: int = 10, is_raw_data: bool = False, + rng_seed: int = None ) -> np.ndarray | dict[str, int]: """ Samples the quantum circuit multiple times, returning either the raw sampled data or a @@ -305,6 +257,7 @@ class StatevectorSimulator: is_sorted (bool): Whether to return the results sorted by state (default: False). sample_block_th (int): Block threshold for sampling optimization (default: 10). is_raw_data (bool): If True, returns raw sample data instead of a frequency dictionary (default: False). + rng_seed (int): Seed for the random number generator (default: None). Returns: np.ndarray | dict[str, int]: The sampled results, either as raw data or as a frequency distribution. @@ -312,43 +265,58 @@ class StatevectorSimulator: if not self.measure_qubits: # all qubits measured self.measure_qubits = list(range(self.nq)) assert shots < 4294967296 # uint32_t - measure = self.measure() - mq_len = len(self.measure_qubits) - samples_ptr = ctypes.c_void_p() - self.libc.get_sample.restype = ctypes.c_int - errcode = self.libc.get_sample( - ctypes.byref(ctypes.c_uint32(shots)), - ctypes.byref(ctypes.c_size_t(mq_len)), - ctypes.c_uint32(self.omp_threads), - measure.ctypes.data_as(ctypes.POINTER(ctypes.c_double)), - ctypes.byref(samples_ptr), # void** - ctypes.byref(ctypes.c_uint32(self.nq)), - ctypes.byref(ctypes.c_size_t(sample_block_th)), - ) - self._check_errcode(errcode) - self.free_array(measure) - samples_ptr = ctypes.cast(samples_ptr, ctypes.POINTER(ctypes.c_uint64)) - samples = np.ctypeslib.as_array(samples_ptr, shape=(shots,)).view( - dtype=np.uint64 + + if self.measure_ptr_capsule is None: + # If measure_ptr_capsule doesn't exist, perform measurement first + self.measure() + + if rng_seed is None: + rng_seed = int(time.time()) + + # Call get_sample from the C extension module + samples_array, samples_ptr_capsule = get_sample( + shots, + len(self.measure_qubits), + self.measure_ptr_capsule, + self.omp_threads, + self.nq, + sample_block_th, + rng_seed ) + # Store samples_ptr_capsule to keep the samples_ptr alive + self.samples_ptr_capsule = samples_ptr_capsule + + # Check if samples_array is None + if samples_array is None: + raise RuntimeError("Failed to get samples from the simulator.") + if is_raw_data: - return samples + return samples_array + + mq_len = len(self.measure_qubits) + counts = Counter(samples_array) + # Convert counts to binary strings result = { - np.binary_repr(k, width=mq_len): v for k, v in Counter(samples).items() + np.binary_repr(int(k), width=mq_len): v for k, v in counts.items() } - self.free_array(samples) if is_sorted: result = dict(sorted(result.items())) return result - def free_array(self, data: np.ndarray): + def get_gates(self) -> list[dict]: """ - Frees memory allocated for a NumPy array in the C library. + Retrieves detailed information about all gates in the current quantum circuit. """ - if isinstance(data, np.ndarray): - self.libc.free_memory.restype = ctypes.c_int - self.libc.free_memory( - ctypes.byref(data.ctypes.data_as(ctypes.c_void_p)) - ) - else: - raise TypeError(f"Expected `np.ndarray` but got `{type(data)}`.") + gates_list = [] + for gate in self.gates: + gate_dict = { + 'gate_id': gate_name_map[gate.name], + 'qubits': gate.qubits, + } + if gate.theta: + gate_dict['theta'] = gate.theta + if gate.mat is not None: + mat_list = [complex(val) for val in gate.mat.flatten()] + gate_dict['mat'] = mat_list + gates_list.append(gate_dict) + return gates_list diff --git a/cqlib/utils/qasm2/__init__.py b/cqlib/utils/qasm2/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..b000dc087537a130325a3f77cc909d93be57e56b --- /dev/null +++ b/cqlib/utils/qasm2/__init__.py @@ -0,0 +1,26 @@ +# This code is part of cqlib. +# +# Copyright (C) 2024 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +cqlib.utils.qasm2 + +This module provides functionality to serialize and deserialize quantum circuits +to and from the OpenQASM 2.0 format. It serves as the interface for converting +`Circuit` objects within the `cqlib` library to OpenQASM strings or files, and +for loading OpenQASM representations back into `Circuit` objects. +""" + +from .load import loads, load +from .dump import dumps, dump + +__all__ = ['loads', 'load', 'dump', 'dumps'] diff --git a/cqlib/utils/qasm2/_antlr4/OpenQASM2.g4 b/cqlib/utils/qasm2/_antlr4/OpenQASM2.g4 new file mode 100644 index 0000000000000000000000000000000000000000..36f2b74188dde79cc56add73a70842e9fc149160 --- /dev/null +++ b/cqlib/utils/qasm2/_antlr4/OpenQASM2.g4 @@ -0,0 +1,167 @@ +grammar OpenQASM2; + +// 定义文件入口 +mainprogram : 'OPENQASM' real ';' program EOF; + +program + : statement* + ; + +// 声明语句 +statement + : includeStatement // 新增文件包含语句 + | decl // 量子或经典寄存器声明 + | gatedecl // 空门定义 + | 'opaque' ID idlist ';' // 不透明门 + | 'opaque' ID '(' idlist ')' idlist ';' + | qop // 量子操作 + | 'if' '(' ID '==' nninteger ')' qop + | 'barrier' anylist ';' + ; + + +// include 语句定义 +includeStatement + : 'include' STRING ';' + ; + + +// 寄存器声明 +decl + : 'qreg' ID '[' nninteger ']' ';' + | 'creg' ID '[' nninteger ']' ';' + ; + + +// 门定义(包含 goplist 的内容体) +gatedecl + : 'gate' ID idlist '{' goplist '}' // 无参数的 gate + | 'gate' ID '(' ')' idlist '{' goplist '}' // 空参数列表的 gate + | 'gate' ID '(' idlist ')' idlist '{' goplist '}' // 带参数的 gate + ; + +goplist + : (uop | 'barrier' idlist ';')* + ; + + +// 量子操作 +qop + : uop + | 'measure' argument '->' argument ';' + | 'reset' argument ';' + ; + +// 基本量子操作 +uop + : 'U' '(' explist ')' argument ';' + | 'CX' argument ',' argument ';' + | ID anylist ';' + | ID '(' explist ')' anylist ';' + ; + +// 通用列表 +anylist + : idlist + | mixedlist + ; + +idlist + : ID (',' ID)* + ; + +mixedlist + : ID ('[' nninteger ']')? (',' ID ('[' nninteger ']')?)* + ; + +// 参数与标识符 +argument + : ID + | ID '[' nninteger ']' + ; + +explist + : exp (',' exp)* + ; + +// 表达式 +//exp +// : real +// | nninteger +// | 'pi' +// | ID +// | exp '+' exp +// | exp '-' exp +// | exp '*' exp +// | exp '/' exp +// | '-' exp +// | exp '^' exp +// | '(' exp ')' +// | unaryop '(' exp ')' +// ; +// 表达式,考虑优先级 +exp + : additiveExp + ; + +additiveExp + : additiveExp '+' multiplicativeExp // Addition + | additiveExp '-' multiplicativeExp // Subtraction + | multiplicativeExp // Pass-through + ; + +multiplicativeExp + : multiplicativeExp '*' exponentialExp // Multiplication + | multiplicativeExp '/' exponentialExp // Division + | exponentialExp // Pass-through + ; + +exponentialExp + : unaryExp '^' exponentialExp // Exponentiation (Right-associative) + | unaryExp // Pass-through + ; + +unaryExp + : '-' unaryExp // Unary negation + | primaryExp // Pass-through + ; + +primaryExp + : real // Real numbers + | nninteger // Non-negative integers + | 'pi' // Constant pi + | ID // Identifiers + | '(' exp ')' // Parentheses + | unaryop '(' exp ')' // Unary operations (e.g., sin, cos) + ; + + +// 单目操作符 +unaryop + : 'sin' | 'cos' | 'tan' | 'exp' | 'ln' | 'sqrt' | 'asin' | 'acos' | 'atan' + ; + +// 字符串定义 +STRING : '"' (~["\r\n])* '"'; + +// 数值定义 +real + : REAL + ; + +nninteger + : NNINTEGER + ; + +// 标识符 +ID : [a-z] [A-Za-z0-9_]*; + +// 实数和非负整数定义 +REAL : ([0-9]+ '.' [0-9]* | '.' [0-9]+) ([eE] [-+]? [0-9]+)?; +NNINTEGER : [1-9][0-9]* | '0'; + +// 忽略的空白字符 +WS : [ \t\r\n]+ -> skip; + +// 忽略单行注释 +COMMENT : '//' ~[\r\n]* -> skip; diff --git a/cqlib/utils/qasm2/_antlr4/OpenQASM2.interp b/cqlib/utils/qasm2/_antlr4/OpenQASM2.interp new file mode 100644 index 0000000000000000000000000000000000000000..3303e121f409def3441d5f2aef31d4249e930fec --- /dev/null +++ b/cqlib/utils/qasm2/_antlr4/OpenQASM2.interp @@ -0,0 +1,120 @@ +token literal names: +null +'OPENQASM' +';' +'opaque' +'(' +')' +'if' +'==' +'barrier' +'include' +'qreg' +'[' +']' +'creg' +'gate' +'{' +'}' +'measure' +'->' +'reset' +'U' +'CX' +',' +'+' +'-' +'*' +'/' +'^' +'pi' +'sin' +'cos' +'tan' +'exp' +'ln' +'sqrt' +'asin' +'acos' +'atan' +null +null +null +null +null +null + +token symbolic names: +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +STRING +ID +REAL +NNINTEGER +WS +COMMENT + +rule names: +mainprogram +program +statement +includeStatement +decl +gatedecl +goplist +qop +uop +anylist +idlist +mixedlist +argument +explist +exp +additiveExp +multiplicativeExp +exponentialExp +unaryExp +primaryExp +unaryop +real +nninteger + + +atn: +[4, 1, 43, 298, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 5, 1, 54, 8, 1, 10, 1, 12, 1, 57, 9, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 3, 2, 88, 8, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 3, 4, 108, 8, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, 5, 136, 8, 5, 1, 6, 1, 6, 1, 6, 1, 6, 1, 6, 5, 6, 143, 8, 6, 10, 6, 12, 6, 146, 9, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 159, 8, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 3, 8, 185, 8, 8, 1, 9, 1, 9, 3, 9, 189, 8, 9, 1, 10, 1, 10, 1, 10, 5, 10, 194, 8, 10, 10, 10, 12, 10, 197, 9, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 3, 11, 204, 8, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 3, 11, 212, 8, 11, 5, 11, 214, 8, 11, 10, 11, 12, 11, 217, 9, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 3, 12, 225, 8, 12, 1, 13, 1, 13, 1, 13, 5, 13, 230, 8, 13, 10, 13, 12, 13, 233, 9, 13, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 5, 15, 246, 8, 15, 10, 15, 12, 15, 249, 9, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 5, 16, 260, 8, 16, 10, 16, 12, 16, 263, 9, 16, 1, 17, 1, 17, 1, 17, 1, 17, 1, 17, 3, 17, 270, 8, 17, 1, 18, 1, 18, 1, 18, 3, 18, 275, 8, 18, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 3, 19, 290, 8, 19, 1, 20, 1, 20, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 0, 2, 30, 32, 23, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 0, 1, 1, 0, 29, 37, 310, 0, 46, 1, 0, 0, 0, 2, 55, 1, 0, 0, 0, 4, 87, 1, 0, 0, 0, 6, 89, 1, 0, 0, 0, 8, 107, 1, 0, 0, 0, 10, 135, 1, 0, 0, 0, 12, 144, 1, 0, 0, 0, 14, 158, 1, 0, 0, 0, 16, 184, 1, 0, 0, 0, 18, 188, 1, 0, 0, 0, 20, 190, 1, 0, 0, 0, 22, 198, 1, 0, 0, 0, 24, 224, 1, 0, 0, 0, 26, 226, 1, 0, 0, 0, 28, 234, 1, 0, 0, 0, 30, 236, 1, 0, 0, 0, 32, 250, 1, 0, 0, 0, 34, 269, 1, 0, 0, 0, 36, 274, 1, 0, 0, 0, 38, 289, 1, 0, 0, 0, 40, 291, 1, 0, 0, 0, 42, 293, 1, 0, 0, 0, 44, 295, 1, 0, 0, 0, 46, 47, 5, 1, 0, 0, 47, 48, 3, 42, 21, 0, 48, 49, 5, 2, 0, 0, 49, 50, 3, 2, 1, 0, 50, 51, 5, 0, 0, 1, 51, 1, 1, 0, 0, 0, 52, 54, 3, 4, 2, 0, 53, 52, 1, 0, 0, 0, 54, 57, 1, 0, 0, 0, 55, 53, 1, 0, 0, 0, 55, 56, 1, 0, 0, 0, 56, 3, 1, 0, 0, 0, 57, 55, 1, 0, 0, 0, 58, 88, 3, 6, 3, 0, 59, 88, 3, 8, 4, 0, 60, 88, 3, 10, 5, 0, 61, 62, 5, 3, 0, 0, 62, 63, 5, 39, 0, 0, 63, 64, 3, 20, 10, 0, 64, 65, 5, 2, 0, 0, 65, 88, 1, 0, 0, 0, 66, 67, 5, 3, 0, 0, 67, 68, 5, 39, 0, 0, 68, 69, 5, 4, 0, 0, 69, 70, 3, 20, 10, 0, 70, 71, 5, 5, 0, 0, 71, 72, 3, 20, 10, 0, 72, 73, 5, 2, 0, 0, 73, 88, 1, 0, 0, 0, 74, 88, 3, 14, 7, 0, 75, 76, 5, 6, 0, 0, 76, 77, 5, 4, 0, 0, 77, 78, 5, 39, 0, 0, 78, 79, 5, 7, 0, 0, 79, 80, 3, 44, 22, 0, 80, 81, 5, 5, 0, 0, 81, 82, 3, 14, 7, 0, 82, 88, 1, 0, 0, 0, 83, 84, 5, 8, 0, 0, 84, 85, 3, 18, 9, 0, 85, 86, 5, 2, 0, 0, 86, 88, 1, 0, 0, 0, 87, 58, 1, 0, 0, 0, 87, 59, 1, 0, 0, 0, 87, 60, 1, 0, 0, 0, 87, 61, 1, 0, 0, 0, 87, 66, 1, 0, 0, 0, 87, 74, 1, 0, 0, 0, 87, 75, 1, 0, 0, 0, 87, 83, 1, 0, 0, 0, 88, 5, 1, 0, 0, 0, 89, 90, 5, 9, 0, 0, 90, 91, 5, 38, 0, 0, 91, 92, 5, 2, 0, 0, 92, 7, 1, 0, 0, 0, 93, 94, 5, 10, 0, 0, 94, 95, 5, 39, 0, 0, 95, 96, 5, 11, 0, 0, 96, 97, 3, 44, 22, 0, 97, 98, 5, 12, 0, 0, 98, 99, 5, 2, 0, 0, 99, 108, 1, 0, 0, 0, 100, 101, 5, 13, 0, 0, 101, 102, 5, 39, 0, 0, 102, 103, 5, 11, 0, 0, 103, 104, 3, 44, 22, 0, 104, 105, 5, 12, 0, 0, 105, 106, 5, 2, 0, 0, 106, 108, 1, 0, 0, 0, 107, 93, 1, 0, 0, 0, 107, 100, 1, 0, 0, 0, 108, 9, 1, 0, 0, 0, 109, 110, 5, 14, 0, 0, 110, 111, 5, 39, 0, 0, 111, 112, 3, 20, 10, 0, 112, 113, 5, 15, 0, 0, 113, 114, 3, 12, 6, 0, 114, 115, 5, 16, 0, 0, 115, 136, 1, 0, 0, 0, 116, 117, 5, 14, 0, 0, 117, 118, 5, 39, 0, 0, 118, 119, 5, 4, 0, 0, 119, 120, 5, 5, 0, 0, 120, 121, 3, 20, 10, 0, 121, 122, 5, 15, 0, 0, 122, 123, 3, 12, 6, 0, 123, 124, 5, 16, 0, 0, 124, 136, 1, 0, 0, 0, 125, 126, 5, 14, 0, 0, 126, 127, 5, 39, 0, 0, 127, 128, 5, 4, 0, 0, 128, 129, 3, 20, 10, 0, 129, 130, 5, 5, 0, 0, 130, 131, 3, 20, 10, 0, 131, 132, 5, 15, 0, 0, 132, 133, 3, 12, 6, 0, 133, 134, 5, 16, 0, 0, 134, 136, 1, 0, 0, 0, 135, 109, 1, 0, 0, 0, 135, 116, 1, 0, 0, 0, 135, 125, 1, 0, 0, 0, 136, 11, 1, 0, 0, 0, 137, 143, 3, 16, 8, 0, 138, 139, 5, 8, 0, 0, 139, 140, 3, 20, 10, 0, 140, 141, 5, 2, 0, 0, 141, 143, 1, 0, 0, 0, 142, 137, 1, 0, 0, 0, 142, 138, 1, 0, 0, 0, 143, 146, 1, 0, 0, 0, 144, 142, 1, 0, 0, 0, 144, 145, 1, 0, 0, 0, 145, 13, 1, 0, 0, 0, 146, 144, 1, 0, 0, 0, 147, 159, 3, 16, 8, 0, 148, 149, 5, 17, 0, 0, 149, 150, 3, 24, 12, 0, 150, 151, 5, 18, 0, 0, 151, 152, 3, 24, 12, 0, 152, 153, 5, 2, 0, 0, 153, 159, 1, 0, 0, 0, 154, 155, 5, 19, 0, 0, 155, 156, 3, 24, 12, 0, 156, 157, 5, 2, 0, 0, 157, 159, 1, 0, 0, 0, 158, 147, 1, 0, 0, 0, 158, 148, 1, 0, 0, 0, 158, 154, 1, 0, 0, 0, 159, 15, 1, 0, 0, 0, 160, 161, 5, 20, 0, 0, 161, 162, 5, 4, 0, 0, 162, 163, 3, 26, 13, 0, 163, 164, 5, 5, 0, 0, 164, 165, 3, 24, 12, 0, 165, 166, 5, 2, 0, 0, 166, 185, 1, 0, 0, 0, 167, 168, 5, 21, 0, 0, 168, 169, 3, 24, 12, 0, 169, 170, 5, 22, 0, 0, 170, 171, 3, 24, 12, 0, 171, 172, 5, 2, 0, 0, 172, 185, 1, 0, 0, 0, 173, 174, 5, 39, 0, 0, 174, 175, 3, 18, 9, 0, 175, 176, 5, 2, 0, 0, 176, 185, 1, 0, 0, 0, 177, 178, 5, 39, 0, 0, 178, 179, 5, 4, 0, 0, 179, 180, 3, 26, 13, 0, 180, 181, 5, 5, 0, 0, 181, 182, 3, 18, 9, 0, 182, 183, 5, 2, 0, 0, 183, 185, 1, 0, 0, 0, 184, 160, 1, 0, 0, 0, 184, 167, 1, 0, 0, 0, 184, 173, 1, 0, 0, 0, 184, 177, 1, 0, 0, 0, 185, 17, 1, 0, 0, 0, 186, 189, 3, 20, 10, 0, 187, 189, 3, 22, 11, 0, 188, 186, 1, 0, 0, 0, 188, 187, 1, 0, 0, 0, 189, 19, 1, 0, 0, 0, 190, 195, 5, 39, 0, 0, 191, 192, 5, 22, 0, 0, 192, 194, 5, 39, 0, 0, 193, 191, 1, 0, 0, 0, 194, 197, 1, 0, 0, 0, 195, 193, 1, 0, 0, 0, 195, 196, 1, 0, 0, 0, 196, 21, 1, 0, 0, 0, 197, 195, 1, 0, 0, 0, 198, 203, 5, 39, 0, 0, 199, 200, 5, 11, 0, 0, 200, 201, 3, 44, 22, 0, 201, 202, 5, 12, 0, 0, 202, 204, 1, 0, 0, 0, 203, 199, 1, 0, 0, 0, 203, 204, 1, 0, 0, 0, 204, 215, 1, 0, 0, 0, 205, 206, 5, 22, 0, 0, 206, 211, 5, 39, 0, 0, 207, 208, 5, 11, 0, 0, 208, 209, 3, 44, 22, 0, 209, 210, 5, 12, 0, 0, 210, 212, 1, 0, 0, 0, 211, 207, 1, 0, 0, 0, 211, 212, 1, 0, 0, 0, 212, 214, 1, 0, 0, 0, 213, 205, 1, 0, 0, 0, 214, 217, 1, 0, 0, 0, 215, 213, 1, 0, 0, 0, 215, 216, 1, 0, 0, 0, 216, 23, 1, 0, 0, 0, 217, 215, 1, 0, 0, 0, 218, 225, 5, 39, 0, 0, 219, 220, 5, 39, 0, 0, 220, 221, 5, 11, 0, 0, 221, 222, 3, 44, 22, 0, 222, 223, 5, 12, 0, 0, 223, 225, 1, 0, 0, 0, 224, 218, 1, 0, 0, 0, 224, 219, 1, 0, 0, 0, 225, 25, 1, 0, 0, 0, 226, 231, 3, 28, 14, 0, 227, 228, 5, 22, 0, 0, 228, 230, 3, 28, 14, 0, 229, 227, 1, 0, 0, 0, 230, 233, 1, 0, 0, 0, 231, 229, 1, 0, 0, 0, 231, 232, 1, 0, 0, 0, 232, 27, 1, 0, 0, 0, 233, 231, 1, 0, 0, 0, 234, 235, 3, 30, 15, 0, 235, 29, 1, 0, 0, 0, 236, 237, 6, 15, -1, 0, 237, 238, 3, 32, 16, 0, 238, 247, 1, 0, 0, 0, 239, 240, 10, 3, 0, 0, 240, 241, 5, 23, 0, 0, 241, 246, 3, 32, 16, 0, 242, 243, 10, 2, 0, 0, 243, 244, 5, 24, 0, 0, 244, 246, 3, 32, 16, 0, 245, 239, 1, 0, 0, 0, 245, 242, 1, 0, 0, 0, 246, 249, 1, 0, 0, 0, 247, 245, 1, 0, 0, 0, 247, 248, 1, 0, 0, 0, 248, 31, 1, 0, 0, 0, 249, 247, 1, 0, 0, 0, 250, 251, 6, 16, -1, 0, 251, 252, 3, 34, 17, 0, 252, 261, 1, 0, 0, 0, 253, 254, 10, 3, 0, 0, 254, 255, 5, 25, 0, 0, 255, 260, 3, 34, 17, 0, 256, 257, 10, 2, 0, 0, 257, 258, 5, 26, 0, 0, 258, 260, 3, 34, 17, 0, 259, 253, 1, 0, 0, 0, 259, 256, 1, 0, 0, 0, 260, 263, 1, 0, 0, 0, 261, 259, 1, 0, 0, 0, 261, 262, 1, 0, 0, 0, 262, 33, 1, 0, 0, 0, 263, 261, 1, 0, 0, 0, 264, 265, 3, 36, 18, 0, 265, 266, 5, 27, 0, 0, 266, 267, 3, 34, 17, 0, 267, 270, 1, 0, 0, 0, 268, 270, 3, 36, 18, 0, 269, 264, 1, 0, 0, 0, 269, 268, 1, 0, 0, 0, 270, 35, 1, 0, 0, 0, 271, 272, 5, 24, 0, 0, 272, 275, 3, 36, 18, 0, 273, 275, 3, 38, 19, 0, 274, 271, 1, 0, 0, 0, 274, 273, 1, 0, 0, 0, 275, 37, 1, 0, 0, 0, 276, 290, 3, 42, 21, 0, 277, 290, 3, 44, 22, 0, 278, 290, 5, 28, 0, 0, 279, 290, 5, 39, 0, 0, 280, 281, 5, 4, 0, 0, 281, 282, 3, 28, 14, 0, 282, 283, 5, 5, 0, 0, 283, 290, 1, 0, 0, 0, 284, 285, 3, 40, 20, 0, 285, 286, 5, 4, 0, 0, 286, 287, 3, 28, 14, 0, 287, 288, 5, 5, 0, 0, 288, 290, 1, 0, 0, 0, 289, 276, 1, 0, 0, 0, 289, 277, 1, 0, 0, 0, 289, 278, 1, 0, 0, 0, 289, 279, 1, 0, 0, 0, 289, 280, 1, 0, 0, 0, 289, 284, 1, 0, 0, 0, 290, 39, 1, 0, 0, 0, 291, 292, 7, 0, 0, 0, 292, 41, 1, 0, 0, 0, 293, 294, 5, 40, 0, 0, 294, 43, 1, 0, 0, 0, 295, 296, 5, 41, 0, 0, 296, 45, 1, 0, 0, 0, 22, 55, 87, 107, 135, 142, 144, 158, 184, 188, 195, 203, 211, 215, 224, 231, 245, 247, 259, 261, 269, 274, 289] \ No newline at end of file diff --git a/cqlib/utils/qasm2/_antlr4/OpenQASM2.tokens b/cqlib/utils/qasm2/_antlr4/OpenQASM2.tokens new file mode 100644 index 0000000000000000000000000000000000000000..4e742a348b3729a0cdd58865aac1c032fbb38c31 --- /dev/null +++ b/cqlib/utils/qasm2/_antlr4/OpenQASM2.tokens @@ -0,0 +1,80 @@ +T__0=1 +T__1=2 +T__2=3 +T__3=4 +T__4=5 +T__5=6 +T__6=7 +T__7=8 +T__8=9 +T__9=10 +T__10=11 +T__11=12 +T__12=13 +T__13=14 +T__14=15 +T__15=16 +T__16=17 +T__17=18 +T__18=19 +T__19=20 +T__20=21 +T__21=22 +T__22=23 +T__23=24 +T__24=25 +T__25=26 +T__26=27 +T__27=28 +T__28=29 +T__29=30 +T__30=31 +T__31=32 +T__32=33 +T__33=34 +T__34=35 +T__35=36 +T__36=37 +STRING=38 +ID=39 +REAL=40 +NNINTEGER=41 +WS=42 +COMMENT=43 +'OPENQASM'=1 +';'=2 +'opaque'=3 +'('=4 +')'=5 +'if'=6 +'=='=7 +'barrier'=8 +'include'=9 +'qreg'=10 +'['=11 +']'=12 +'creg'=13 +'gate'=14 +'{'=15 +'}'=16 +'measure'=17 +'->'=18 +'reset'=19 +'U'=20 +'CX'=21 +','=22 +'+'=23 +'-'=24 +'*'=25 +'/'=26 +'^'=27 +'pi'=28 +'sin'=29 +'cos'=30 +'tan'=31 +'exp'=32 +'ln'=33 +'sqrt'=34 +'asin'=35 +'acos'=36 +'atan'=37 diff --git a/cqlib/utils/qasm2/_antlr4/OpenQASM2Lexer.interp b/cqlib/utils/qasm2/_antlr4/OpenQASM2Lexer.interp new file mode 100644 index 0000000000000000000000000000000000000000..93677b344a9646378dabfa8e9b13ad556a7cea09 --- /dev/null +++ b/cqlib/utils/qasm2/_antlr4/OpenQASM2Lexer.interp @@ -0,0 +1,146 @@ +token literal names: +null +'OPENQASM' +';' +'opaque' +'(' +')' +'if' +'==' +'barrier' +'include' +'qreg' +'[' +']' +'creg' +'gate' +'{' +'}' +'measure' +'->' +'reset' +'U' +'CX' +',' +'+' +'-' +'*' +'/' +'^' +'pi' +'sin' +'cos' +'tan' +'exp' +'ln' +'sqrt' +'asin' +'acos' +'atan' +null +null +null +null +null +null + +token symbolic names: +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +null +STRING +ID +REAL +NNINTEGER +WS +COMMENT + +rule names: +T__0 +T__1 +T__2 +T__3 +T__4 +T__5 +T__6 +T__7 +T__8 +T__9 +T__10 +T__11 +T__12 +T__13 +T__14 +T__15 +T__16 +T__17 +T__18 +T__19 +T__20 +T__21 +T__22 +T__23 +T__24 +T__25 +T__26 +T__27 +T__28 +T__29 +T__30 +T__31 +T__32 +T__33 +T__34 +T__35 +T__36 +STRING +ID +REAL +NNINTEGER +WS +COMMENT + +channel names: +DEFAULT_TOKEN_CHANNEL +HIDDEN + +mode names: +DEFAULT_MODE + +atn: +[4, 0, 43, 305, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, 42, 7, 42, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 15, 1, 15, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 19, 1, 19, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 22, 1, 22, 1, 23, 1, 23, 1, 24, 1, 24, 1, 25, 1, 25, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 28, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 1, 31, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, 1, 35, 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 37, 1, 37, 5, 37, 233, 8, 37, 10, 37, 12, 37, 236, 9, 37, 1, 37, 1, 37, 1, 38, 1, 38, 5, 38, 242, 8, 38, 10, 38, 12, 38, 245, 9, 38, 1, 39, 4, 39, 248, 8, 39, 11, 39, 12, 39, 249, 1, 39, 1, 39, 5, 39, 254, 8, 39, 10, 39, 12, 39, 257, 9, 39, 1, 39, 1, 39, 4, 39, 261, 8, 39, 11, 39, 12, 39, 262, 3, 39, 265, 8, 39, 1, 39, 1, 39, 3, 39, 269, 8, 39, 1, 39, 4, 39, 272, 8, 39, 11, 39, 12, 39, 273, 3, 39, 276, 8, 39, 1, 40, 1, 40, 5, 40, 280, 8, 40, 10, 40, 12, 40, 283, 9, 40, 1, 40, 3, 40, 286, 8, 40, 1, 41, 4, 41, 289, 8, 41, 11, 41, 12, 41, 290, 1, 41, 1, 41, 1, 42, 1, 42, 1, 42, 1, 42, 5, 42, 299, 8, 42, 10, 42, 12, 42, 302, 9, 42, 1, 42, 1, 42, 0, 0, 43, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, 13, 27, 14, 29, 15, 31, 16, 33, 17, 35, 18, 37, 19, 39, 20, 41, 21, 43, 22, 45, 23, 47, 24, 49, 25, 51, 26, 53, 27, 55, 28, 57, 29, 59, 30, 61, 31, 63, 32, 65, 33, 67, 34, 69, 35, 71, 36, 73, 37, 75, 38, 77, 39, 79, 40, 81, 41, 83, 42, 85, 43, 1, 0, 9, 3, 0, 10, 10, 13, 13, 34, 34, 1, 0, 97, 122, 4, 0, 48, 57, 65, 90, 95, 95, 97, 122, 1, 0, 48, 57, 2, 0, 69, 69, 101, 101, 2, 0, 43, 43, 45, 45, 1, 0, 49, 57, 3, 0, 9, 10, 13, 13, 32, 32, 2, 0, 10, 10, 13, 13, 317, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1, 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, 1, 0, 0, 0, 0, 55, 1, 0, 0, 0, 0, 57, 1, 0, 0, 0, 0, 59, 1, 0, 0, 0, 0, 61, 1, 0, 0, 0, 0, 63, 1, 0, 0, 0, 0, 65, 1, 0, 0, 0, 0, 67, 1, 0, 0, 0, 0, 69, 1, 0, 0, 0, 0, 71, 1, 0, 0, 0, 0, 73, 1, 0, 0, 0, 0, 75, 1, 0, 0, 0, 0, 77, 1, 0, 0, 0, 0, 79, 1, 0, 0, 0, 0, 81, 1, 0, 0, 0, 0, 83, 1, 0, 0, 0, 0, 85, 1, 0, 0, 0, 1, 87, 1, 0, 0, 0, 3, 96, 1, 0, 0, 0, 5, 98, 1, 0, 0, 0, 7, 105, 1, 0, 0, 0, 9, 107, 1, 0, 0, 0, 11, 109, 1, 0, 0, 0, 13, 112, 1, 0, 0, 0, 15, 115, 1, 0, 0, 0, 17, 123, 1, 0, 0, 0, 19, 131, 1, 0, 0, 0, 21, 136, 1, 0, 0, 0, 23, 138, 1, 0, 0, 0, 25, 140, 1, 0, 0, 0, 27, 145, 1, 0, 0, 0, 29, 150, 1, 0, 0, 0, 31, 152, 1, 0, 0, 0, 33, 154, 1, 0, 0, 0, 35, 162, 1, 0, 0, 0, 37, 165, 1, 0, 0, 0, 39, 171, 1, 0, 0, 0, 41, 173, 1, 0, 0, 0, 43, 176, 1, 0, 0, 0, 45, 178, 1, 0, 0, 0, 47, 180, 1, 0, 0, 0, 49, 182, 1, 0, 0, 0, 51, 184, 1, 0, 0, 0, 53, 186, 1, 0, 0, 0, 55, 188, 1, 0, 0, 0, 57, 191, 1, 0, 0, 0, 59, 195, 1, 0, 0, 0, 61, 199, 1, 0, 0, 0, 63, 203, 1, 0, 0, 0, 65, 207, 1, 0, 0, 0, 67, 210, 1, 0, 0, 0, 69, 215, 1, 0, 0, 0, 71, 220, 1, 0, 0, 0, 73, 225, 1, 0, 0, 0, 75, 230, 1, 0, 0, 0, 77, 239, 1, 0, 0, 0, 79, 264, 1, 0, 0, 0, 81, 285, 1, 0, 0, 0, 83, 288, 1, 0, 0, 0, 85, 294, 1, 0, 0, 0, 87, 88, 5, 79, 0, 0, 88, 89, 5, 80, 0, 0, 89, 90, 5, 69, 0, 0, 90, 91, 5, 78, 0, 0, 91, 92, 5, 81, 0, 0, 92, 93, 5, 65, 0, 0, 93, 94, 5, 83, 0, 0, 94, 95, 5, 77, 0, 0, 95, 2, 1, 0, 0, 0, 96, 97, 5, 59, 0, 0, 97, 4, 1, 0, 0, 0, 98, 99, 5, 111, 0, 0, 99, 100, 5, 112, 0, 0, 100, 101, 5, 97, 0, 0, 101, 102, 5, 113, 0, 0, 102, 103, 5, 117, 0, 0, 103, 104, 5, 101, 0, 0, 104, 6, 1, 0, 0, 0, 105, 106, 5, 40, 0, 0, 106, 8, 1, 0, 0, 0, 107, 108, 5, 41, 0, 0, 108, 10, 1, 0, 0, 0, 109, 110, 5, 105, 0, 0, 110, 111, 5, 102, 0, 0, 111, 12, 1, 0, 0, 0, 112, 113, 5, 61, 0, 0, 113, 114, 5, 61, 0, 0, 114, 14, 1, 0, 0, 0, 115, 116, 5, 98, 0, 0, 116, 117, 5, 97, 0, 0, 117, 118, 5, 114, 0, 0, 118, 119, 5, 114, 0, 0, 119, 120, 5, 105, 0, 0, 120, 121, 5, 101, 0, 0, 121, 122, 5, 114, 0, 0, 122, 16, 1, 0, 0, 0, 123, 124, 5, 105, 0, 0, 124, 125, 5, 110, 0, 0, 125, 126, 5, 99, 0, 0, 126, 127, 5, 108, 0, 0, 127, 128, 5, 117, 0, 0, 128, 129, 5, 100, 0, 0, 129, 130, 5, 101, 0, 0, 130, 18, 1, 0, 0, 0, 131, 132, 5, 113, 0, 0, 132, 133, 5, 114, 0, 0, 133, 134, 5, 101, 0, 0, 134, 135, 5, 103, 0, 0, 135, 20, 1, 0, 0, 0, 136, 137, 5, 91, 0, 0, 137, 22, 1, 0, 0, 0, 138, 139, 5, 93, 0, 0, 139, 24, 1, 0, 0, 0, 140, 141, 5, 99, 0, 0, 141, 142, 5, 114, 0, 0, 142, 143, 5, 101, 0, 0, 143, 144, 5, 103, 0, 0, 144, 26, 1, 0, 0, 0, 145, 146, 5, 103, 0, 0, 146, 147, 5, 97, 0, 0, 147, 148, 5, 116, 0, 0, 148, 149, 5, 101, 0, 0, 149, 28, 1, 0, 0, 0, 150, 151, 5, 123, 0, 0, 151, 30, 1, 0, 0, 0, 152, 153, 5, 125, 0, 0, 153, 32, 1, 0, 0, 0, 154, 155, 5, 109, 0, 0, 155, 156, 5, 101, 0, 0, 156, 157, 5, 97, 0, 0, 157, 158, 5, 115, 0, 0, 158, 159, 5, 117, 0, 0, 159, 160, 5, 114, 0, 0, 160, 161, 5, 101, 0, 0, 161, 34, 1, 0, 0, 0, 162, 163, 5, 45, 0, 0, 163, 164, 5, 62, 0, 0, 164, 36, 1, 0, 0, 0, 165, 166, 5, 114, 0, 0, 166, 167, 5, 101, 0, 0, 167, 168, 5, 115, 0, 0, 168, 169, 5, 101, 0, 0, 169, 170, 5, 116, 0, 0, 170, 38, 1, 0, 0, 0, 171, 172, 5, 85, 0, 0, 172, 40, 1, 0, 0, 0, 173, 174, 5, 67, 0, 0, 174, 175, 5, 88, 0, 0, 175, 42, 1, 0, 0, 0, 176, 177, 5, 44, 0, 0, 177, 44, 1, 0, 0, 0, 178, 179, 5, 43, 0, 0, 179, 46, 1, 0, 0, 0, 180, 181, 5, 45, 0, 0, 181, 48, 1, 0, 0, 0, 182, 183, 5, 42, 0, 0, 183, 50, 1, 0, 0, 0, 184, 185, 5, 47, 0, 0, 185, 52, 1, 0, 0, 0, 186, 187, 5, 94, 0, 0, 187, 54, 1, 0, 0, 0, 188, 189, 5, 112, 0, 0, 189, 190, 5, 105, 0, 0, 190, 56, 1, 0, 0, 0, 191, 192, 5, 115, 0, 0, 192, 193, 5, 105, 0, 0, 193, 194, 5, 110, 0, 0, 194, 58, 1, 0, 0, 0, 195, 196, 5, 99, 0, 0, 196, 197, 5, 111, 0, 0, 197, 198, 5, 115, 0, 0, 198, 60, 1, 0, 0, 0, 199, 200, 5, 116, 0, 0, 200, 201, 5, 97, 0, 0, 201, 202, 5, 110, 0, 0, 202, 62, 1, 0, 0, 0, 203, 204, 5, 101, 0, 0, 204, 205, 5, 120, 0, 0, 205, 206, 5, 112, 0, 0, 206, 64, 1, 0, 0, 0, 207, 208, 5, 108, 0, 0, 208, 209, 5, 110, 0, 0, 209, 66, 1, 0, 0, 0, 210, 211, 5, 115, 0, 0, 211, 212, 5, 113, 0, 0, 212, 213, 5, 114, 0, 0, 213, 214, 5, 116, 0, 0, 214, 68, 1, 0, 0, 0, 215, 216, 5, 97, 0, 0, 216, 217, 5, 115, 0, 0, 217, 218, 5, 105, 0, 0, 218, 219, 5, 110, 0, 0, 219, 70, 1, 0, 0, 0, 220, 221, 5, 97, 0, 0, 221, 222, 5, 99, 0, 0, 222, 223, 5, 111, 0, 0, 223, 224, 5, 115, 0, 0, 224, 72, 1, 0, 0, 0, 225, 226, 5, 97, 0, 0, 226, 227, 5, 116, 0, 0, 227, 228, 5, 97, 0, 0, 228, 229, 5, 110, 0, 0, 229, 74, 1, 0, 0, 0, 230, 234, 5, 34, 0, 0, 231, 233, 8, 0, 0, 0, 232, 231, 1, 0, 0, 0, 233, 236, 1, 0, 0, 0, 234, 232, 1, 0, 0, 0, 234, 235, 1, 0, 0, 0, 235, 237, 1, 0, 0, 0, 236, 234, 1, 0, 0, 0, 237, 238, 5, 34, 0, 0, 238, 76, 1, 0, 0, 0, 239, 243, 7, 1, 0, 0, 240, 242, 7, 2, 0, 0, 241, 240, 1, 0, 0, 0, 242, 245, 1, 0, 0, 0, 243, 241, 1, 0, 0, 0, 243, 244, 1, 0, 0, 0, 244, 78, 1, 0, 0, 0, 245, 243, 1, 0, 0, 0, 246, 248, 7, 3, 0, 0, 247, 246, 1, 0, 0, 0, 248, 249, 1, 0, 0, 0, 249, 247, 1, 0, 0, 0, 249, 250, 1, 0, 0, 0, 250, 251, 1, 0, 0, 0, 251, 255, 5, 46, 0, 0, 252, 254, 7, 3, 0, 0, 253, 252, 1, 0, 0, 0, 254, 257, 1, 0, 0, 0, 255, 253, 1, 0, 0, 0, 255, 256, 1, 0, 0, 0, 256, 265, 1, 0, 0, 0, 257, 255, 1, 0, 0, 0, 258, 260, 5, 46, 0, 0, 259, 261, 7, 3, 0, 0, 260, 259, 1, 0, 0, 0, 261, 262, 1, 0, 0, 0, 262, 260, 1, 0, 0, 0, 262, 263, 1, 0, 0, 0, 263, 265, 1, 0, 0, 0, 264, 247, 1, 0, 0, 0, 264, 258, 1, 0, 0, 0, 265, 275, 1, 0, 0, 0, 266, 268, 7, 4, 0, 0, 267, 269, 7, 5, 0, 0, 268, 267, 1, 0, 0, 0, 268, 269, 1, 0, 0, 0, 269, 271, 1, 0, 0, 0, 270, 272, 7, 3, 0, 0, 271, 270, 1, 0, 0, 0, 272, 273, 1, 0, 0, 0, 273, 271, 1, 0, 0, 0, 273, 274, 1, 0, 0, 0, 274, 276, 1, 0, 0, 0, 275, 266, 1, 0, 0, 0, 275, 276, 1, 0, 0, 0, 276, 80, 1, 0, 0, 0, 277, 281, 7, 6, 0, 0, 278, 280, 7, 3, 0, 0, 279, 278, 1, 0, 0, 0, 280, 283, 1, 0, 0, 0, 281, 279, 1, 0, 0, 0, 281, 282, 1, 0, 0, 0, 282, 286, 1, 0, 0, 0, 283, 281, 1, 0, 0, 0, 284, 286, 5, 48, 0, 0, 285, 277, 1, 0, 0, 0, 285, 284, 1, 0, 0, 0, 286, 82, 1, 0, 0, 0, 287, 289, 7, 7, 0, 0, 288, 287, 1, 0, 0, 0, 289, 290, 1, 0, 0, 0, 290, 288, 1, 0, 0, 0, 290, 291, 1, 0, 0, 0, 291, 292, 1, 0, 0, 0, 292, 293, 6, 41, 0, 0, 293, 84, 1, 0, 0, 0, 294, 295, 5, 47, 0, 0, 295, 296, 5, 47, 0, 0, 296, 300, 1, 0, 0, 0, 297, 299, 8, 8, 0, 0, 298, 297, 1, 0, 0, 0, 299, 302, 1, 0, 0, 0, 300, 298, 1, 0, 0, 0, 300, 301, 1, 0, 0, 0, 301, 303, 1, 0, 0, 0, 302, 300, 1, 0, 0, 0, 303, 304, 6, 42, 0, 0, 304, 86, 1, 0, 0, 0, 14, 0, 234, 243, 249, 255, 262, 264, 268, 273, 275, 281, 285, 290, 300, 1, 6, 0, 0] \ No newline at end of file diff --git a/cqlib/utils/qasm2/_antlr4/OpenQASM2Lexer.py b/cqlib/utils/qasm2/_antlr4/OpenQASM2Lexer.py new file mode 100644 index 0000000000000000000000000000000000000000..6d69da42287aad8ef7085233c441ea949fc4acd9 --- /dev/null +++ b/cqlib/utils/qasm2/_antlr4/OpenQASM2Lexer.py @@ -0,0 +1,206 @@ +# Generated from OpenQASM2.g4 by ANTLR 4.13.2 +from antlr4 import * +from io import StringIO +import sys +if sys.version_info[1] > 5: + from typing import TextIO +else: + from typing.io import TextIO + + +def serializedATN(): + return [ + 4,0,43,305,6,-1,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5, + 2,6,7,6,2,7,7,7,2,8,7,8,2,9,7,9,2,10,7,10,2,11,7,11,2,12,7,12,2, + 13,7,13,2,14,7,14,2,15,7,15,2,16,7,16,2,17,7,17,2,18,7,18,2,19,7, + 19,2,20,7,20,2,21,7,21,2,22,7,22,2,23,7,23,2,24,7,24,2,25,7,25,2, + 26,7,26,2,27,7,27,2,28,7,28,2,29,7,29,2,30,7,30,2,31,7,31,2,32,7, + 32,2,33,7,33,2,34,7,34,2,35,7,35,2,36,7,36,2,37,7,37,2,38,7,38,2, + 39,7,39,2,40,7,40,2,41,7,41,2,42,7,42,1,0,1,0,1,0,1,0,1,0,1,0,1, + 0,1,0,1,0,1,1,1,1,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,3,1,3,1,4,1,4,1, + 5,1,5,1,5,1,6,1,6,1,6,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,8,1,8,1, + 8,1,8,1,8,1,8,1,8,1,8,1,9,1,9,1,9,1,9,1,9,1,10,1,10,1,11,1,11,1, + 12,1,12,1,12,1,12,1,12,1,13,1,13,1,13,1,13,1,13,1,14,1,14,1,15,1, + 15,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,17,1,17,1,17,1,18,1, + 18,1,18,1,18,1,18,1,18,1,19,1,19,1,20,1,20,1,20,1,21,1,21,1,22,1, + 22,1,23,1,23,1,24,1,24,1,25,1,25,1,26,1,26,1,27,1,27,1,27,1,28,1, + 28,1,28,1,28,1,29,1,29,1,29,1,29,1,30,1,30,1,30,1,30,1,31,1,31,1, + 31,1,31,1,32,1,32,1,32,1,33,1,33,1,33,1,33,1,33,1,34,1,34,1,34,1, + 34,1,34,1,35,1,35,1,35,1,35,1,35,1,36,1,36,1,36,1,36,1,36,1,37,1, + 37,5,37,233,8,37,10,37,12,37,236,9,37,1,37,1,37,1,38,1,38,5,38,242, + 8,38,10,38,12,38,245,9,38,1,39,4,39,248,8,39,11,39,12,39,249,1,39, + 1,39,5,39,254,8,39,10,39,12,39,257,9,39,1,39,1,39,4,39,261,8,39, + 11,39,12,39,262,3,39,265,8,39,1,39,1,39,3,39,269,8,39,1,39,4,39, + 272,8,39,11,39,12,39,273,3,39,276,8,39,1,40,1,40,5,40,280,8,40,10, + 40,12,40,283,9,40,1,40,3,40,286,8,40,1,41,4,41,289,8,41,11,41,12, + 41,290,1,41,1,41,1,42,1,42,1,42,1,42,5,42,299,8,42,10,42,12,42,302, + 9,42,1,42,1,42,0,0,43,1,1,3,2,5,3,7,4,9,5,11,6,13,7,15,8,17,9,19, + 10,21,11,23,12,25,13,27,14,29,15,31,16,33,17,35,18,37,19,39,20,41, + 21,43,22,45,23,47,24,49,25,51,26,53,27,55,28,57,29,59,30,61,31,63, + 32,65,33,67,34,69,35,71,36,73,37,75,38,77,39,79,40,81,41,83,42,85, + 43,1,0,9,3,0,10,10,13,13,34,34,1,0,97,122,4,0,48,57,65,90,95,95, + 97,122,1,0,48,57,2,0,69,69,101,101,2,0,43,43,45,45,1,0,49,57,3,0, + 9,10,13,13,32,32,2,0,10,10,13,13,317,0,1,1,0,0,0,0,3,1,0,0,0,0,5, + 1,0,0,0,0,7,1,0,0,0,0,9,1,0,0,0,0,11,1,0,0,0,0,13,1,0,0,0,0,15,1, + 0,0,0,0,17,1,0,0,0,0,19,1,0,0,0,0,21,1,0,0,0,0,23,1,0,0,0,0,25,1, + 0,0,0,0,27,1,0,0,0,0,29,1,0,0,0,0,31,1,0,0,0,0,33,1,0,0,0,0,35,1, + 0,0,0,0,37,1,0,0,0,0,39,1,0,0,0,0,41,1,0,0,0,0,43,1,0,0,0,0,45,1, + 0,0,0,0,47,1,0,0,0,0,49,1,0,0,0,0,51,1,0,0,0,0,53,1,0,0,0,0,55,1, + 0,0,0,0,57,1,0,0,0,0,59,1,0,0,0,0,61,1,0,0,0,0,63,1,0,0,0,0,65,1, + 0,0,0,0,67,1,0,0,0,0,69,1,0,0,0,0,71,1,0,0,0,0,73,1,0,0,0,0,75,1, + 0,0,0,0,77,1,0,0,0,0,79,1,0,0,0,0,81,1,0,0,0,0,83,1,0,0,0,0,85,1, + 0,0,0,1,87,1,0,0,0,3,96,1,0,0,0,5,98,1,0,0,0,7,105,1,0,0,0,9,107, + 1,0,0,0,11,109,1,0,0,0,13,112,1,0,0,0,15,115,1,0,0,0,17,123,1,0, + 0,0,19,131,1,0,0,0,21,136,1,0,0,0,23,138,1,0,0,0,25,140,1,0,0,0, + 27,145,1,0,0,0,29,150,1,0,0,0,31,152,1,0,0,0,33,154,1,0,0,0,35,162, + 1,0,0,0,37,165,1,0,0,0,39,171,1,0,0,0,41,173,1,0,0,0,43,176,1,0, + 0,0,45,178,1,0,0,0,47,180,1,0,0,0,49,182,1,0,0,0,51,184,1,0,0,0, + 53,186,1,0,0,0,55,188,1,0,0,0,57,191,1,0,0,0,59,195,1,0,0,0,61,199, + 1,0,0,0,63,203,1,0,0,0,65,207,1,0,0,0,67,210,1,0,0,0,69,215,1,0, + 0,0,71,220,1,0,0,0,73,225,1,0,0,0,75,230,1,0,0,0,77,239,1,0,0,0, + 79,264,1,0,0,0,81,285,1,0,0,0,83,288,1,0,0,0,85,294,1,0,0,0,87,88, + 5,79,0,0,88,89,5,80,0,0,89,90,5,69,0,0,90,91,5,78,0,0,91,92,5,81, + 0,0,92,93,5,65,0,0,93,94,5,83,0,0,94,95,5,77,0,0,95,2,1,0,0,0,96, + 97,5,59,0,0,97,4,1,0,0,0,98,99,5,111,0,0,99,100,5,112,0,0,100,101, + 5,97,0,0,101,102,5,113,0,0,102,103,5,117,0,0,103,104,5,101,0,0,104, + 6,1,0,0,0,105,106,5,40,0,0,106,8,1,0,0,0,107,108,5,41,0,0,108,10, + 1,0,0,0,109,110,5,105,0,0,110,111,5,102,0,0,111,12,1,0,0,0,112,113, + 5,61,0,0,113,114,5,61,0,0,114,14,1,0,0,0,115,116,5,98,0,0,116,117, + 5,97,0,0,117,118,5,114,0,0,118,119,5,114,0,0,119,120,5,105,0,0,120, + 121,5,101,0,0,121,122,5,114,0,0,122,16,1,0,0,0,123,124,5,105,0,0, + 124,125,5,110,0,0,125,126,5,99,0,0,126,127,5,108,0,0,127,128,5,117, + 0,0,128,129,5,100,0,0,129,130,5,101,0,0,130,18,1,0,0,0,131,132,5, + 113,0,0,132,133,5,114,0,0,133,134,5,101,0,0,134,135,5,103,0,0,135, + 20,1,0,0,0,136,137,5,91,0,0,137,22,1,0,0,0,138,139,5,93,0,0,139, + 24,1,0,0,0,140,141,5,99,0,0,141,142,5,114,0,0,142,143,5,101,0,0, + 143,144,5,103,0,0,144,26,1,0,0,0,145,146,5,103,0,0,146,147,5,97, + 0,0,147,148,5,116,0,0,148,149,5,101,0,0,149,28,1,0,0,0,150,151,5, + 123,0,0,151,30,1,0,0,0,152,153,5,125,0,0,153,32,1,0,0,0,154,155, + 5,109,0,0,155,156,5,101,0,0,156,157,5,97,0,0,157,158,5,115,0,0,158, + 159,5,117,0,0,159,160,5,114,0,0,160,161,5,101,0,0,161,34,1,0,0,0, + 162,163,5,45,0,0,163,164,5,62,0,0,164,36,1,0,0,0,165,166,5,114,0, + 0,166,167,5,101,0,0,167,168,5,115,0,0,168,169,5,101,0,0,169,170, + 5,116,0,0,170,38,1,0,0,0,171,172,5,85,0,0,172,40,1,0,0,0,173,174, + 5,67,0,0,174,175,5,88,0,0,175,42,1,0,0,0,176,177,5,44,0,0,177,44, + 1,0,0,0,178,179,5,43,0,0,179,46,1,0,0,0,180,181,5,45,0,0,181,48, + 1,0,0,0,182,183,5,42,0,0,183,50,1,0,0,0,184,185,5,47,0,0,185,52, + 1,0,0,0,186,187,5,94,0,0,187,54,1,0,0,0,188,189,5,112,0,0,189,190, + 5,105,0,0,190,56,1,0,0,0,191,192,5,115,0,0,192,193,5,105,0,0,193, + 194,5,110,0,0,194,58,1,0,0,0,195,196,5,99,0,0,196,197,5,111,0,0, + 197,198,5,115,0,0,198,60,1,0,0,0,199,200,5,116,0,0,200,201,5,97, + 0,0,201,202,5,110,0,0,202,62,1,0,0,0,203,204,5,101,0,0,204,205,5, + 120,0,0,205,206,5,112,0,0,206,64,1,0,0,0,207,208,5,108,0,0,208,209, + 5,110,0,0,209,66,1,0,0,0,210,211,5,115,0,0,211,212,5,113,0,0,212, + 213,5,114,0,0,213,214,5,116,0,0,214,68,1,0,0,0,215,216,5,97,0,0, + 216,217,5,115,0,0,217,218,5,105,0,0,218,219,5,110,0,0,219,70,1,0, + 0,0,220,221,5,97,0,0,221,222,5,99,0,0,222,223,5,111,0,0,223,224, + 5,115,0,0,224,72,1,0,0,0,225,226,5,97,0,0,226,227,5,116,0,0,227, + 228,5,97,0,0,228,229,5,110,0,0,229,74,1,0,0,0,230,234,5,34,0,0,231, + 233,8,0,0,0,232,231,1,0,0,0,233,236,1,0,0,0,234,232,1,0,0,0,234, + 235,1,0,0,0,235,237,1,0,0,0,236,234,1,0,0,0,237,238,5,34,0,0,238, + 76,1,0,0,0,239,243,7,1,0,0,240,242,7,2,0,0,241,240,1,0,0,0,242,245, + 1,0,0,0,243,241,1,0,0,0,243,244,1,0,0,0,244,78,1,0,0,0,245,243,1, + 0,0,0,246,248,7,3,0,0,247,246,1,0,0,0,248,249,1,0,0,0,249,247,1, + 0,0,0,249,250,1,0,0,0,250,251,1,0,0,0,251,255,5,46,0,0,252,254,7, + 3,0,0,253,252,1,0,0,0,254,257,1,0,0,0,255,253,1,0,0,0,255,256,1, + 0,0,0,256,265,1,0,0,0,257,255,1,0,0,0,258,260,5,46,0,0,259,261,7, + 3,0,0,260,259,1,0,0,0,261,262,1,0,0,0,262,260,1,0,0,0,262,263,1, + 0,0,0,263,265,1,0,0,0,264,247,1,0,0,0,264,258,1,0,0,0,265,275,1, + 0,0,0,266,268,7,4,0,0,267,269,7,5,0,0,268,267,1,0,0,0,268,269,1, + 0,0,0,269,271,1,0,0,0,270,272,7,3,0,0,271,270,1,0,0,0,272,273,1, + 0,0,0,273,271,1,0,0,0,273,274,1,0,0,0,274,276,1,0,0,0,275,266,1, + 0,0,0,275,276,1,0,0,0,276,80,1,0,0,0,277,281,7,6,0,0,278,280,7,3, + 0,0,279,278,1,0,0,0,280,283,1,0,0,0,281,279,1,0,0,0,281,282,1,0, + 0,0,282,286,1,0,0,0,283,281,1,0,0,0,284,286,5,48,0,0,285,277,1,0, + 0,0,285,284,1,0,0,0,286,82,1,0,0,0,287,289,7,7,0,0,288,287,1,0,0, + 0,289,290,1,0,0,0,290,288,1,0,0,0,290,291,1,0,0,0,291,292,1,0,0, + 0,292,293,6,41,0,0,293,84,1,0,0,0,294,295,5,47,0,0,295,296,5,47, + 0,0,296,300,1,0,0,0,297,299,8,8,0,0,298,297,1,0,0,0,299,302,1,0, + 0,0,300,298,1,0,0,0,300,301,1,0,0,0,301,303,1,0,0,0,302,300,1,0, + 0,0,303,304,6,42,0,0,304,86,1,0,0,0,14,0,234,243,249,255,262,264, + 268,273,275,281,285,290,300,1,6,0,0 + ] + +class OpenQASM2Lexer(Lexer): + + atn = ATNDeserializer().deserialize(serializedATN()) + + decisionsToDFA = [ DFA(ds, i) for i, ds in enumerate(atn.decisionToState) ] + + T__0 = 1 + T__1 = 2 + T__2 = 3 + T__3 = 4 + T__4 = 5 + T__5 = 6 + T__6 = 7 + T__7 = 8 + T__8 = 9 + T__9 = 10 + T__10 = 11 + T__11 = 12 + T__12 = 13 + T__13 = 14 + T__14 = 15 + T__15 = 16 + T__16 = 17 + T__17 = 18 + T__18 = 19 + T__19 = 20 + T__20 = 21 + T__21 = 22 + T__22 = 23 + T__23 = 24 + T__24 = 25 + T__25 = 26 + T__26 = 27 + T__27 = 28 + T__28 = 29 + T__29 = 30 + T__30 = 31 + T__31 = 32 + T__32 = 33 + T__33 = 34 + T__34 = 35 + T__35 = 36 + T__36 = 37 + STRING = 38 + ID = 39 + REAL = 40 + NNINTEGER = 41 + WS = 42 + COMMENT = 43 + + channelNames = [ u"DEFAULT_TOKEN_CHANNEL", u"HIDDEN" ] + + modeNames = [ "DEFAULT_MODE" ] + + literalNames = [ "", + "'OPENQASM'", "';'", "'opaque'", "'('", "')'", "'if'", "'=='", + "'barrier'", "'include'", "'qreg'", "'['", "']'", "'creg'", + "'gate'", "'{'", "'}'", "'measure'", "'->'", "'reset'", "'U'", + "'CX'", "','", "'+'", "'-'", "'*'", "'/'", "'^'", "'pi'", "'sin'", + "'cos'", "'tan'", "'exp'", "'ln'", "'sqrt'", "'asin'", "'acos'", + "'atan'" ] + + symbolicNames = [ "", + "STRING", "ID", "REAL", "NNINTEGER", "WS", "COMMENT" ] + + ruleNames = [ "T__0", "T__1", "T__2", "T__3", "T__4", "T__5", "T__6", + "T__7", "T__8", "T__9", "T__10", "T__11", "T__12", "T__13", + "T__14", "T__15", "T__16", "T__17", "T__18", "T__19", + "T__20", "T__21", "T__22", "T__23", "T__24", "T__25", + "T__26", "T__27", "T__28", "T__29", "T__30", "T__31", + "T__32", "T__33", "T__34", "T__35", "T__36", "STRING", + "ID", "REAL", "NNINTEGER", "WS", "COMMENT" ] + + grammarFileName = "OpenQASM2.g4" + + def __init__(self, input=None, output:TextIO = sys.stdout): + super().__init__(input, output) + self.checkVersion("4.13.2") + self._interp = LexerATNSimulator(self, self.atn, self.decisionsToDFA, PredictionContextCache()) + self._actions = None + self._predicates = None + + diff --git a/cqlib/utils/qasm2/_antlr4/OpenQASM2Lexer.tokens b/cqlib/utils/qasm2/_antlr4/OpenQASM2Lexer.tokens new file mode 100644 index 0000000000000000000000000000000000000000..4e742a348b3729a0cdd58865aac1c032fbb38c31 --- /dev/null +++ b/cqlib/utils/qasm2/_antlr4/OpenQASM2Lexer.tokens @@ -0,0 +1,80 @@ +T__0=1 +T__1=2 +T__2=3 +T__3=4 +T__4=5 +T__5=6 +T__6=7 +T__7=8 +T__8=9 +T__9=10 +T__10=11 +T__11=12 +T__12=13 +T__13=14 +T__14=15 +T__15=16 +T__16=17 +T__17=18 +T__18=19 +T__19=20 +T__20=21 +T__21=22 +T__22=23 +T__23=24 +T__24=25 +T__25=26 +T__26=27 +T__27=28 +T__28=29 +T__29=30 +T__30=31 +T__31=32 +T__32=33 +T__33=34 +T__34=35 +T__35=36 +T__36=37 +STRING=38 +ID=39 +REAL=40 +NNINTEGER=41 +WS=42 +COMMENT=43 +'OPENQASM'=1 +';'=2 +'opaque'=3 +'('=4 +')'=5 +'if'=6 +'=='=7 +'barrier'=8 +'include'=9 +'qreg'=10 +'['=11 +']'=12 +'creg'=13 +'gate'=14 +'{'=15 +'}'=16 +'measure'=17 +'->'=18 +'reset'=19 +'U'=20 +'CX'=21 +','=22 +'+'=23 +'-'=24 +'*'=25 +'/'=26 +'^'=27 +'pi'=28 +'sin'=29 +'cos'=30 +'tan'=31 +'exp'=32 +'ln'=33 +'sqrt'=34 +'asin'=35 +'acos'=36 +'atan'=37 diff --git a/cqlib/utils/qasm2/_antlr4/OpenQASM2Listener.py b/cqlib/utils/qasm2/_antlr4/OpenQASM2Listener.py new file mode 100644 index 0000000000000000000000000000000000000000..bcfa6b57e7f24608e2dc995553cfcf3d468207d0 --- /dev/null +++ b/cqlib/utils/qasm2/_antlr4/OpenQASM2Listener.py @@ -0,0 +1,219 @@ +# Generated from OpenQASM2.g4 by ANTLR 4.13.2 +from antlr4 import * +if "." in __name__: + from .OpenQASM2Parser import OpenQASM2Parser +else: + from OpenQASM2Parser import OpenQASM2Parser + +# This class defines a complete listener for a parse tree produced by OpenQASM2Parser. +class OpenQASM2Listener(ParseTreeListener): + + # Enter a parse tree produced by OpenQASM2Parser#mainprogram. + def enterMainprogram(self, ctx:OpenQASM2Parser.MainprogramContext): + pass + + # Exit a parse tree produced by OpenQASM2Parser#mainprogram. + def exitMainprogram(self, ctx:OpenQASM2Parser.MainprogramContext): + pass + + + # Enter a parse tree produced by OpenQASM2Parser#program. + def enterProgram(self, ctx:OpenQASM2Parser.ProgramContext): + pass + + # Exit a parse tree produced by OpenQASM2Parser#program. + def exitProgram(self, ctx:OpenQASM2Parser.ProgramContext): + pass + + + # Enter a parse tree produced by OpenQASM2Parser#statement. + def enterStatement(self, ctx:OpenQASM2Parser.StatementContext): + pass + + # Exit a parse tree produced by OpenQASM2Parser#statement. + def exitStatement(self, ctx:OpenQASM2Parser.StatementContext): + pass + + + # Enter a parse tree produced by OpenQASM2Parser#includeStatement. + def enterIncludeStatement(self, ctx:OpenQASM2Parser.IncludeStatementContext): + pass + + # Exit a parse tree produced by OpenQASM2Parser#includeStatement. + def exitIncludeStatement(self, ctx:OpenQASM2Parser.IncludeStatementContext): + pass + + + # Enter a parse tree produced by OpenQASM2Parser#decl. + def enterDecl(self, ctx:OpenQASM2Parser.DeclContext): + pass + + # Exit a parse tree produced by OpenQASM2Parser#decl. + def exitDecl(self, ctx:OpenQASM2Parser.DeclContext): + pass + + + # Enter a parse tree produced by OpenQASM2Parser#gatedecl. + def enterGatedecl(self, ctx:OpenQASM2Parser.GatedeclContext): + pass + + # Exit a parse tree produced by OpenQASM2Parser#gatedecl. + def exitGatedecl(self, ctx:OpenQASM2Parser.GatedeclContext): + pass + + + # Enter a parse tree produced by OpenQASM2Parser#goplist. + def enterGoplist(self, ctx:OpenQASM2Parser.GoplistContext): + pass + + # Exit a parse tree produced by OpenQASM2Parser#goplist. + def exitGoplist(self, ctx:OpenQASM2Parser.GoplistContext): + pass + + + # Enter a parse tree produced by OpenQASM2Parser#qop. + def enterQop(self, ctx:OpenQASM2Parser.QopContext): + pass + + # Exit a parse tree produced by OpenQASM2Parser#qop. + def exitQop(self, ctx:OpenQASM2Parser.QopContext): + pass + + + # Enter a parse tree produced by OpenQASM2Parser#uop. + def enterUop(self, ctx:OpenQASM2Parser.UopContext): + pass + + # Exit a parse tree produced by OpenQASM2Parser#uop. + def exitUop(self, ctx:OpenQASM2Parser.UopContext): + pass + + + # Enter a parse tree produced by OpenQASM2Parser#anylist. + def enterAnylist(self, ctx:OpenQASM2Parser.AnylistContext): + pass + + # Exit a parse tree produced by OpenQASM2Parser#anylist. + def exitAnylist(self, ctx:OpenQASM2Parser.AnylistContext): + pass + + + # Enter a parse tree produced by OpenQASM2Parser#idlist. + def enterIdlist(self, ctx:OpenQASM2Parser.IdlistContext): + pass + + # Exit a parse tree produced by OpenQASM2Parser#idlist. + def exitIdlist(self, ctx:OpenQASM2Parser.IdlistContext): + pass + + + # Enter a parse tree produced by OpenQASM2Parser#mixedlist. + def enterMixedlist(self, ctx:OpenQASM2Parser.MixedlistContext): + pass + + # Exit a parse tree produced by OpenQASM2Parser#mixedlist. + def exitMixedlist(self, ctx:OpenQASM2Parser.MixedlistContext): + pass + + + # Enter a parse tree produced by OpenQASM2Parser#argument. + def enterArgument(self, ctx:OpenQASM2Parser.ArgumentContext): + pass + + # Exit a parse tree produced by OpenQASM2Parser#argument. + def exitArgument(self, ctx:OpenQASM2Parser.ArgumentContext): + pass + + + # Enter a parse tree produced by OpenQASM2Parser#explist. + def enterExplist(self, ctx:OpenQASM2Parser.ExplistContext): + pass + + # Exit a parse tree produced by OpenQASM2Parser#explist. + def exitExplist(self, ctx:OpenQASM2Parser.ExplistContext): + pass + + + # Enter a parse tree produced by OpenQASM2Parser#exp. + def enterExp(self, ctx:OpenQASM2Parser.ExpContext): + pass + + # Exit a parse tree produced by OpenQASM2Parser#exp. + def exitExp(self, ctx:OpenQASM2Parser.ExpContext): + pass + + + # Enter a parse tree produced by OpenQASM2Parser#additiveExp. + def enterAdditiveExp(self, ctx:OpenQASM2Parser.AdditiveExpContext): + pass + + # Exit a parse tree produced by OpenQASM2Parser#additiveExp. + def exitAdditiveExp(self, ctx:OpenQASM2Parser.AdditiveExpContext): + pass + + + # Enter a parse tree produced by OpenQASM2Parser#multiplicativeExp. + def enterMultiplicativeExp(self, ctx:OpenQASM2Parser.MultiplicativeExpContext): + pass + + # Exit a parse tree produced by OpenQASM2Parser#multiplicativeExp. + def exitMultiplicativeExp(self, ctx:OpenQASM2Parser.MultiplicativeExpContext): + pass + + + # Enter a parse tree produced by OpenQASM2Parser#exponentialExp. + def enterExponentialExp(self, ctx:OpenQASM2Parser.ExponentialExpContext): + pass + + # Exit a parse tree produced by OpenQASM2Parser#exponentialExp. + def exitExponentialExp(self, ctx:OpenQASM2Parser.ExponentialExpContext): + pass + + + # Enter a parse tree produced by OpenQASM2Parser#unaryExp. + def enterUnaryExp(self, ctx:OpenQASM2Parser.UnaryExpContext): + pass + + # Exit a parse tree produced by OpenQASM2Parser#unaryExp. + def exitUnaryExp(self, ctx:OpenQASM2Parser.UnaryExpContext): + pass + + + # Enter a parse tree produced by OpenQASM2Parser#primaryExp. + def enterPrimaryExp(self, ctx:OpenQASM2Parser.PrimaryExpContext): + pass + + # Exit a parse tree produced by OpenQASM2Parser#primaryExp. + def exitPrimaryExp(self, ctx:OpenQASM2Parser.PrimaryExpContext): + pass + + + # Enter a parse tree produced by OpenQASM2Parser#unaryop. + def enterUnaryop(self, ctx:OpenQASM2Parser.UnaryopContext): + pass + + # Exit a parse tree produced by OpenQASM2Parser#unaryop. + def exitUnaryop(self, ctx:OpenQASM2Parser.UnaryopContext): + pass + + + # Enter a parse tree produced by OpenQASM2Parser#real. + def enterReal(self, ctx:OpenQASM2Parser.RealContext): + pass + + # Exit a parse tree produced by OpenQASM2Parser#real. + def exitReal(self, ctx:OpenQASM2Parser.RealContext): + pass + + + # Enter a parse tree produced by OpenQASM2Parser#nninteger. + def enterNninteger(self, ctx:OpenQASM2Parser.NnintegerContext): + pass + + # Exit a parse tree produced by OpenQASM2Parser#nninteger. + def exitNninteger(self, ctx:OpenQASM2Parser.NnintegerContext): + pass + + + +del OpenQASM2Parser \ No newline at end of file diff --git a/cqlib/utils/qasm2/_antlr4/OpenQASM2Parser.py b/cqlib/utils/qasm2/_antlr4/OpenQASM2Parser.py new file mode 100644 index 0000000000000000000000000000000000000000..eaa1d5d0b45aa08873f00f871fc1197b61c0d176 --- /dev/null +++ b/cqlib/utils/qasm2/_antlr4/OpenQASM2Parser.py @@ -0,0 +1,2070 @@ +# Generated from OpenQASM2.g4 by ANTLR 4.13.2 +# encoding: utf-8 +from antlr4 import * +from io import StringIO +import sys +if sys.version_info[1] > 5: + from typing import TextIO +else: + from typing.io import TextIO + +def serializedATN(): + return [ + 4,1,43,298,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7, + 6,2,7,7,7,2,8,7,8,2,9,7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,13,7,13, + 2,14,7,14,2,15,7,15,2,16,7,16,2,17,7,17,2,18,7,18,2,19,7,19,2,20, + 7,20,2,21,7,21,2,22,7,22,1,0,1,0,1,0,1,0,1,0,1,0,1,1,5,1,54,8,1, + 10,1,12,1,57,9,1,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2, + 1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2, + 1,2,3,2,88,8,2,1,3,1,3,1,3,1,3,1,4,1,4,1,4,1,4,1,4,1,4,1,4,1,4,1, + 4,1,4,1,4,1,4,1,4,1,4,3,4,108,8,4,1,5,1,5,1,5,1,5,1,5,1,5,1,5,1, + 5,1,5,1,5,1,5,1,5,1,5,1,5,1,5,1,5,1,5,1,5,1,5,1,5,1,5,1,5,1,5,1, + 5,1,5,1,5,3,5,136,8,5,1,6,1,6,1,6,1,6,1,6,5,6,143,8,6,10,6,12,6, + 146,9,6,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,3,7,159,8,7, + 1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,8, + 1,8,1,8,1,8,1,8,1,8,1,8,1,8,1,8,3,8,185,8,8,1,9,1,9,3,9,189,8,9, + 1,10,1,10,1,10,5,10,194,8,10,10,10,12,10,197,9,10,1,11,1,11,1,11, + 1,11,1,11,3,11,204,8,11,1,11,1,11,1,11,1,11,1,11,1,11,3,11,212,8, + 11,5,11,214,8,11,10,11,12,11,217,9,11,1,12,1,12,1,12,1,12,1,12,1, + 12,3,12,225,8,12,1,13,1,13,1,13,5,13,230,8,13,10,13,12,13,233,9, + 13,1,14,1,14,1,15,1,15,1,15,1,15,1,15,1,15,1,15,1,15,1,15,5,15,246, + 8,15,10,15,12,15,249,9,15,1,16,1,16,1,16,1,16,1,16,1,16,1,16,1,16, + 1,16,5,16,260,8,16,10,16,12,16,263,9,16,1,17,1,17,1,17,1,17,1,17, + 3,17,270,8,17,1,18,1,18,1,18,3,18,275,8,18,1,19,1,19,1,19,1,19,1, + 19,1,19,1,19,1,19,1,19,1,19,1,19,1,19,1,19,3,19,290,8,19,1,20,1, + 20,1,21,1,21,1,22,1,22,1,22,0,2,30,32,23,0,2,4,6,8,10,12,14,16,18, + 20,22,24,26,28,30,32,34,36,38,40,42,44,0,1,1,0,29,37,310,0,46,1, + 0,0,0,2,55,1,0,0,0,4,87,1,0,0,0,6,89,1,0,0,0,8,107,1,0,0,0,10,135, + 1,0,0,0,12,144,1,0,0,0,14,158,1,0,0,0,16,184,1,0,0,0,18,188,1,0, + 0,0,20,190,1,0,0,0,22,198,1,0,0,0,24,224,1,0,0,0,26,226,1,0,0,0, + 28,234,1,0,0,0,30,236,1,0,0,0,32,250,1,0,0,0,34,269,1,0,0,0,36,274, + 1,0,0,0,38,289,1,0,0,0,40,291,1,0,0,0,42,293,1,0,0,0,44,295,1,0, + 0,0,46,47,5,1,0,0,47,48,3,42,21,0,48,49,5,2,0,0,49,50,3,2,1,0,50, + 51,5,0,0,1,51,1,1,0,0,0,52,54,3,4,2,0,53,52,1,0,0,0,54,57,1,0,0, + 0,55,53,1,0,0,0,55,56,1,0,0,0,56,3,1,0,0,0,57,55,1,0,0,0,58,88,3, + 6,3,0,59,88,3,8,4,0,60,88,3,10,5,0,61,62,5,3,0,0,62,63,5,39,0,0, + 63,64,3,20,10,0,64,65,5,2,0,0,65,88,1,0,0,0,66,67,5,3,0,0,67,68, + 5,39,0,0,68,69,5,4,0,0,69,70,3,20,10,0,70,71,5,5,0,0,71,72,3,20, + 10,0,72,73,5,2,0,0,73,88,1,0,0,0,74,88,3,14,7,0,75,76,5,6,0,0,76, + 77,5,4,0,0,77,78,5,39,0,0,78,79,5,7,0,0,79,80,3,44,22,0,80,81,5, + 5,0,0,81,82,3,14,7,0,82,88,1,0,0,0,83,84,5,8,0,0,84,85,3,18,9,0, + 85,86,5,2,0,0,86,88,1,0,0,0,87,58,1,0,0,0,87,59,1,0,0,0,87,60,1, + 0,0,0,87,61,1,0,0,0,87,66,1,0,0,0,87,74,1,0,0,0,87,75,1,0,0,0,87, + 83,1,0,0,0,88,5,1,0,0,0,89,90,5,9,0,0,90,91,5,38,0,0,91,92,5,2,0, + 0,92,7,1,0,0,0,93,94,5,10,0,0,94,95,5,39,0,0,95,96,5,11,0,0,96,97, + 3,44,22,0,97,98,5,12,0,0,98,99,5,2,0,0,99,108,1,0,0,0,100,101,5, + 13,0,0,101,102,5,39,0,0,102,103,5,11,0,0,103,104,3,44,22,0,104,105, + 5,12,0,0,105,106,5,2,0,0,106,108,1,0,0,0,107,93,1,0,0,0,107,100, + 1,0,0,0,108,9,1,0,0,0,109,110,5,14,0,0,110,111,5,39,0,0,111,112, + 3,20,10,0,112,113,5,15,0,0,113,114,3,12,6,0,114,115,5,16,0,0,115, + 136,1,0,0,0,116,117,5,14,0,0,117,118,5,39,0,0,118,119,5,4,0,0,119, + 120,5,5,0,0,120,121,3,20,10,0,121,122,5,15,0,0,122,123,3,12,6,0, + 123,124,5,16,0,0,124,136,1,0,0,0,125,126,5,14,0,0,126,127,5,39,0, + 0,127,128,5,4,0,0,128,129,3,20,10,0,129,130,5,5,0,0,130,131,3,20, + 10,0,131,132,5,15,0,0,132,133,3,12,6,0,133,134,5,16,0,0,134,136, + 1,0,0,0,135,109,1,0,0,0,135,116,1,0,0,0,135,125,1,0,0,0,136,11,1, + 0,0,0,137,143,3,16,8,0,138,139,5,8,0,0,139,140,3,20,10,0,140,141, + 5,2,0,0,141,143,1,0,0,0,142,137,1,0,0,0,142,138,1,0,0,0,143,146, + 1,0,0,0,144,142,1,0,0,0,144,145,1,0,0,0,145,13,1,0,0,0,146,144,1, + 0,0,0,147,159,3,16,8,0,148,149,5,17,0,0,149,150,3,24,12,0,150,151, + 5,18,0,0,151,152,3,24,12,0,152,153,5,2,0,0,153,159,1,0,0,0,154,155, + 5,19,0,0,155,156,3,24,12,0,156,157,5,2,0,0,157,159,1,0,0,0,158,147, + 1,0,0,0,158,148,1,0,0,0,158,154,1,0,0,0,159,15,1,0,0,0,160,161,5, + 20,0,0,161,162,5,4,0,0,162,163,3,26,13,0,163,164,5,5,0,0,164,165, + 3,24,12,0,165,166,5,2,0,0,166,185,1,0,0,0,167,168,5,21,0,0,168,169, + 3,24,12,0,169,170,5,22,0,0,170,171,3,24,12,0,171,172,5,2,0,0,172, + 185,1,0,0,0,173,174,5,39,0,0,174,175,3,18,9,0,175,176,5,2,0,0,176, + 185,1,0,0,0,177,178,5,39,0,0,178,179,5,4,0,0,179,180,3,26,13,0,180, + 181,5,5,0,0,181,182,3,18,9,0,182,183,5,2,0,0,183,185,1,0,0,0,184, + 160,1,0,0,0,184,167,1,0,0,0,184,173,1,0,0,0,184,177,1,0,0,0,185, + 17,1,0,0,0,186,189,3,20,10,0,187,189,3,22,11,0,188,186,1,0,0,0,188, + 187,1,0,0,0,189,19,1,0,0,0,190,195,5,39,0,0,191,192,5,22,0,0,192, + 194,5,39,0,0,193,191,1,0,0,0,194,197,1,0,0,0,195,193,1,0,0,0,195, + 196,1,0,0,0,196,21,1,0,0,0,197,195,1,0,0,0,198,203,5,39,0,0,199, + 200,5,11,0,0,200,201,3,44,22,0,201,202,5,12,0,0,202,204,1,0,0,0, + 203,199,1,0,0,0,203,204,1,0,0,0,204,215,1,0,0,0,205,206,5,22,0,0, + 206,211,5,39,0,0,207,208,5,11,0,0,208,209,3,44,22,0,209,210,5,12, + 0,0,210,212,1,0,0,0,211,207,1,0,0,0,211,212,1,0,0,0,212,214,1,0, + 0,0,213,205,1,0,0,0,214,217,1,0,0,0,215,213,1,0,0,0,215,216,1,0, + 0,0,216,23,1,0,0,0,217,215,1,0,0,0,218,225,5,39,0,0,219,220,5,39, + 0,0,220,221,5,11,0,0,221,222,3,44,22,0,222,223,5,12,0,0,223,225, + 1,0,0,0,224,218,1,0,0,0,224,219,1,0,0,0,225,25,1,0,0,0,226,231,3, + 28,14,0,227,228,5,22,0,0,228,230,3,28,14,0,229,227,1,0,0,0,230,233, + 1,0,0,0,231,229,1,0,0,0,231,232,1,0,0,0,232,27,1,0,0,0,233,231,1, + 0,0,0,234,235,3,30,15,0,235,29,1,0,0,0,236,237,6,15,-1,0,237,238, + 3,32,16,0,238,247,1,0,0,0,239,240,10,3,0,0,240,241,5,23,0,0,241, + 246,3,32,16,0,242,243,10,2,0,0,243,244,5,24,0,0,244,246,3,32,16, + 0,245,239,1,0,0,0,245,242,1,0,0,0,246,249,1,0,0,0,247,245,1,0,0, + 0,247,248,1,0,0,0,248,31,1,0,0,0,249,247,1,0,0,0,250,251,6,16,-1, + 0,251,252,3,34,17,0,252,261,1,0,0,0,253,254,10,3,0,0,254,255,5,25, + 0,0,255,260,3,34,17,0,256,257,10,2,0,0,257,258,5,26,0,0,258,260, + 3,34,17,0,259,253,1,0,0,0,259,256,1,0,0,0,260,263,1,0,0,0,261,259, + 1,0,0,0,261,262,1,0,0,0,262,33,1,0,0,0,263,261,1,0,0,0,264,265,3, + 36,18,0,265,266,5,27,0,0,266,267,3,34,17,0,267,270,1,0,0,0,268,270, + 3,36,18,0,269,264,1,0,0,0,269,268,1,0,0,0,270,35,1,0,0,0,271,272, + 5,24,0,0,272,275,3,36,18,0,273,275,3,38,19,0,274,271,1,0,0,0,274, + 273,1,0,0,0,275,37,1,0,0,0,276,290,3,42,21,0,277,290,3,44,22,0,278, + 290,5,28,0,0,279,290,5,39,0,0,280,281,5,4,0,0,281,282,3,28,14,0, + 282,283,5,5,0,0,283,290,1,0,0,0,284,285,3,40,20,0,285,286,5,4,0, + 0,286,287,3,28,14,0,287,288,5,5,0,0,288,290,1,0,0,0,289,276,1,0, + 0,0,289,277,1,0,0,0,289,278,1,0,0,0,289,279,1,0,0,0,289,280,1,0, + 0,0,289,284,1,0,0,0,290,39,1,0,0,0,291,292,7,0,0,0,292,41,1,0,0, + 0,293,294,5,40,0,0,294,43,1,0,0,0,295,296,5,41,0,0,296,45,1,0,0, + 0,22,55,87,107,135,142,144,158,184,188,195,203,211,215,224,231,245, + 247,259,261,269,274,289 + ] + +class OpenQASM2Parser ( Parser ): + + grammarFileName = "OpenQASM2.g4" + + atn = ATNDeserializer().deserialize(serializedATN()) + + decisionsToDFA = [ DFA(ds, i) for i, ds in enumerate(atn.decisionToState) ] + + sharedContextCache = PredictionContextCache() + + literalNames = [ "", "'OPENQASM'", "';'", "'opaque'", "'('", + "')'", "'if'", "'=='", "'barrier'", "'include'", "'qreg'", + "'['", "']'", "'creg'", "'gate'", "'{'", "'}'", "'measure'", + "'->'", "'reset'", "'U'", "'CX'", "','", "'+'", "'-'", + "'*'", "'/'", "'^'", "'pi'", "'sin'", "'cos'", "'tan'", + "'exp'", "'ln'", "'sqrt'", "'asin'", "'acos'", "'atan'" ] + + symbolicNames = [ "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "", "", + "", "", "STRING", "ID", "REAL", + "NNINTEGER", "WS", "COMMENT" ] + + RULE_mainprogram = 0 + RULE_program = 1 + RULE_statement = 2 + RULE_includeStatement = 3 + RULE_decl = 4 + RULE_gatedecl = 5 + RULE_goplist = 6 + RULE_qop = 7 + RULE_uop = 8 + RULE_anylist = 9 + RULE_idlist = 10 + RULE_mixedlist = 11 + RULE_argument = 12 + RULE_explist = 13 + RULE_exp = 14 + RULE_additiveExp = 15 + RULE_multiplicativeExp = 16 + RULE_exponentialExp = 17 + RULE_unaryExp = 18 + RULE_primaryExp = 19 + RULE_unaryop = 20 + RULE_real = 21 + RULE_nninteger = 22 + + ruleNames = [ "mainprogram", "program", "statement", "includeStatement", + "decl", "gatedecl", "goplist", "qop", "uop", "anylist", + "idlist", "mixedlist", "argument", "explist", "exp", + "additiveExp", "multiplicativeExp", "exponentialExp", + "unaryExp", "primaryExp", "unaryop", "real", "nninteger" ] + + EOF = Token.EOF + T__0=1 + T__1=2 + T__2=3 + T__3=4 + T__4=5 + T__5=6 + T__6=7 + T__7=8 + T__8=9 + T__9=10 + T__10=11 + T__11=12 + T__12=13 + T__13=14 + T__14=15 + T__15=16 + T__16=17 + T__17=18 + T__18=19 + T__19=20 + T__20=21 + T__21=22 + T__22=23 + T__23=24 + T__24=25 + T__25=26 + T__26=27 + T__27=28 + T__28=29 + T__29=30 + T__30=31 + T__31=32 + T__32=33 + T__33=34 + T__34=35 + T__35=36 + T__36=37 + STRING=38 + ID=39 + REAL=40 + NNINTEGER=41 + WS=42 + COMMENT=43 + + def __init__(self, input:TokenStream, output:TextIO = sys.stdout): + super().__init__(input, output) + self.checkVersion("4.13.2") + self._interp = ParserATNSimulator(self, self.atn, self.decisionsToDFA, self.sharedContextCache) + self._predicates = None + + + + + class MainprogramContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def real(self): + return self.getTypedRuleContext(OpenQASM2Parser.RealContext,0) + + + def program(self): + return self.getTypedRuleContext(OpenQASM2Parser.ProgramContext,0) + + + def EOF(self): + return self.getToken(OpenQASM2Parser.EOF, 0) + + def getRuleIndex(self): + return OpenQASM2Parser.RULE_mainprogram + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterMainprogram" ): + listener.enterMainprogram(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitMainprogram" ): + listener.exitMainprogram(self) + + def accept(self, visitor:ParseTreeVisitor): + if hasattr( visitor, "visitMainprogram" ): + return visitor.visitMainprogram(self) + else: + return visitor.visitChildren(self) + + + + + def mainprogram(self): + + localctx = OpenQASM2Parser.MainprogramContext(self, self._ctx, self.state) + self.enterRule(localctx, 0, self.RULE_mainprogram) + try: + self.enterOuterAlt(localctx, 1) + self.state = 46 + self.match(OpenQASM2Parser.T__0) + self.state = 47 + self.real() + self.state = 48 + self.match(OpenQASM2Parser.T__1) + self.state = 49 + self.program() + self.state = 50 + self.match(OpenQASM2Parser.EOF) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class ProgramContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def statement(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(OpenQASM2Parser.StatementContext) + else: + return self.getTypedRuleContext(OpenQASM2Parser.StatementContext,i) + + + def getRuleIndex(self): + return OpenQASM2Parser.RULE_program + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterProgram" ): + listener.enterProgram(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitProgram" ): + listener.exitProgram(self) + + def accept(self, visitor:ParseTreeVisitor): + if hasattr( visitor, "visitProgram" ): + return visitor.visitProgram(self) + else: + return visitor.visitChildren(self) + + + + + def program(self): + + localctx = OpenQASM2Parser.ProgramContext(self, self._ctx, self.state) + self.enterRule(localctx, 2, self.RULE_program) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 55 + self._errHandler.sync(self) + _la = self._input.LA(1) + while (((_la) & ~0x3f) == 0 and ((1 << _la) & 549759641416) != 0): + self.state = 52 + self.statement() + self.state = 57 + self._errHandler.sync(self) + _la = self._input.LA(1) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class StatementContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def includeStatement(self): + return self.getTypedRuleContext(OpenQASM2Parser.IncludeStatementContext,0) + + + def decl(self): + return self.getTypedRuleContext(OpenQASM2Parser.DeclContext,0) + + + def gatedecl(self): + return self.getTypedRuleContext(OpenQASM2Parser.GatedeclContext,0) + + + def ID(self): + return self.getToken(OpenQASM2Parser.ID, 0) + + def idlist(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(OpenQASM2Parser.IdlistContext) + else: + return self.getTypedRuleContext(OpenQASM2Parser.IdlistContext,i) + + + def qop(self): + return self.getTypedRuleContext(OpenQASM2Parser.QopContext,0) + + + def nninteger(self): + return self.getTypedRuleContext(OpenQASM2Parser.NnintegerContext,0) + + + def anylist(self): + return self.getTypedRuleContext(OpenQASM2Parser.AnylistContext,0) + + + def getRuleIndex(self): + return OpenQASM2Parser.RULE_statement + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterStatement" ): + listener.enterStatement(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitStatement" ): + listener.exitStatement(self) + + def accept(self, visitor:ParseTreeVisitor): + if hasattr( visitor, "visitStatement" ): + return visitor.visitStatement(self) + else: + return visitor.visitChildren(self) + + + + + def statement(self): + + localctx = OpenQASM2Parser.StatementContext(self, self._ctx, self.state) + self.enterRule(localctx, 4, self.RULE_statement) + try: + self.state = 87 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,1,self._ctx) + if la_ == 1: + self.enterOuterAlt(localctx, 1) + self.state = 58 + self.includeStatement() + pass + + elif la_ == 2: + self.enterOuterAlt(localctx, 2) + self.state = 59 + self.decl() + pass + + elif la_ == 3: + self.enterOuterAlt(localctx, 3) + self.state = 60 + self.gatedecl() + pass + + elif la_ == 4: + self.enterOuterAlt(localctx, 4) + self.state = 61 + self.match(OpenQASM2Parser.T__2) + self.state = 62 + self.match(OpenQASM2Parser.ID) + self.state = 63 + self.idlist() + self.state = 64 + self.match(OpenQASM2Parser.T__1) + pass + + elif la_ == 5: + self.enterOuterAlt(localctx, 5) + self.state = 66 + self.match(OpenQASM2Parser.T__2) + self.state = 67 + self.match(OpenQASM2Parser.ID) + self.state = 68 + self.match(OpenQASM2Parser.T__3) + self.state = 69 + self.idlist() + self.state = 70 + self.match(OpenQASM2Parser.T__4) + self.state = 71 + self.idlist() + self.state = 72 + self.match(OpenQASM2Parser.T__1) + pass + + elif la_ == 6: + self.enterOuterAlt(localctx, 6) + self.state = 74 + self.qop() + pass + + elif la_ == 7: + self.enterOuterAlt(localctx, 7) + self.state = 75 + self.match(OpenQASM2Parser.T__5) + self.state = 76 + self.match(OpenQASM2Parser.T__3) + self.state = 77 + self.match(OpenQASM2Parser.ID) + self.state = 78 + self.match(OpenQASM2Parser.T__6) + self.state = 79 + self.nninteger() + self.state = 80 + self.match(OpenQASM2Parser.T__4) + self.state = 81 + self.qop() + pass + + elif la_ == 8: + self.enterOuterAlt(localctx, 8) + self.state = 83 + self.match(OpenQASM2Parser.T__7) + self.state = 84 + self.anylist() + self.state = 85 + self.match(OpenQASM2Parser.T__1) + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class IncludeStatementContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def STRING(self): + return self.getToken(OpenQASM2Parser.STRING, 0) + + def getRuleIndex(self): + return OpenQASM2Parser.RULE_includeStatement + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterIncludeStatement" ): + listener.enterIncludeStatement(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitIncludeStatement" ): + listener.exitIncludeStatement(self) + + def accept(self, visitor:ParseTreeVisitor): + if hasattr( visitor, "visitIncludeStatement" ): + return visitor.visitIncludeStatement(self) + else: + return visitor.visitChildren(self) + + + + + def includeStatement(self): + + localctx = OpenQASM2Parser.IncludeStatementContext(self, self._ctx, self.state) + self.enterRule(localctx, 6, self.RULE_includeStatement) + try: + self.enterOuterAlt(localctx, 1) + self.state = 89 + self.match(OpenQASM2Parser.T__8) + self.state = 90 + self.match(OpenQASM2Parser.STRING) + self.state = 91 + self.match(OpenQASM2Parser.T__1) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class DeclContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def ID(self): + return self.getToken(OpenQASM2Parser.ID, 0) + + def nninteger(self): + return self.getTypedRuleContext(OpenQASM2Parser.NnintegerContext,0) + + + def getRuleIndex(self): + return OpenQASM2Parser.RULE_decl + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterDecl" ): + listener.enterDecl(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitDecl" ): + listener.exitDecl(self) + + def accept(self, visitor:ParseTreeVisitor): + if hasattr( visitor, "visitDecl" ): + return visitor.visitDecl(self) + else: + return visitor.visitChildren(self) + + + + + def decl(self): + + localctx = OpenQASM2Parser.DeclContext(self, self._ctx, self.state) + self.enterRule(localctx, 8, self.RULE_decl) + try: + self.state = 107 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [10]: + self.enterOuterAlt(localctx, 1) + self.state = 93 + self.match(OpenQASM2Parser.T__9) + self.state = 94 + self.match(OpenQASM2Parser.ID) + self.state = 95 + self.match(OpenQASM2Parser.T__10) + self.state = 96 + self.nninteger() + self.state = 97 + self.match(OpenQASM2Parser.T__11) + self.state = 98 + self.match(OpenQASM2Parser.T__1) + pass + elif token in [13]: + self.enterOuterAlt(localctx, 2) + self.state = 100 + self.match(OpenQASM2Parser.T__12) + self.state = 101 + self.match(OpenQASM2Parser.ID) + self.state = 102 + self.match(OpenQASM2Parser.T__10) + self.state = 103 + self.nninteger() + self.state = 104 + self.match(OpenQASM2Parser.T__11) + self.state = 105 + self.match(OpenQASM2Parser.T__1) + pass + else: + raise NoViableAltException(self) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class GatedeclContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def ID(self): + return self.getToken(OpenQASM2Parser.ID, 0) + + def idlist(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(OpenQASM2Parser.IdlistContext) + else: + return self.getTypedRuleContext(OpenQASM2Parser.IdlistContext,i) + + + def goplist(self): + return self.getTypedRuleContext(OpenQASM2Parser.GoplistContext,0) + + + def getRuleIndex(self): + return OpenQASM2Parser.RULE_gatedecl + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterGatedecl" ): + listener.enterGatedecl(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitGatedecl" ): + listener.exitGatedecl(self) + + def accept(self, visitor:ParseTreeVisitor): + if hasattr( visitor, "visitGatedecl" ): + return visitor.visitGatedecl(self) + else: + return visitor.visitChildren(self) + + + + + def gatedecl(self): + + localctx = OpenQASM2Parser.GatedeclContext(self, self._ctx, self.state) + self.enterRule(localctx, 10, self.RULE_gatedecl) + try: + self.state = 135 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,3,self._ctx) + if la_ == 1: + self.enterOuterAlt(localctx, 1) + self.state = 109 + self.match(OpenQASM2Parser.T__13) + self.state = 110 + self.match(OpenQASM2Parser.ID) + self.state = 111 + self.idlist() + self.state = 112 + self.match(OpenQASM2Parser.T__14) + self.state = 113 + self.goplist() + self.state = 114 + self.match(OpenQASM2Parser.T__15) + pass + + elif la_ == 2: + self.enterOuterAlt(localctx, 2) + self.state = 116 + self.match(OpenQASM2Parser.T__13) + self.state = 117 + self.match(OpenQASM2Parser.ID) + self.state = 118 + self.match(OpenQASM2Parser.T__3) + self.state = 119 + self.match(OpenQASM2Parser.T__4) + self.state = 120 + self.idlist() + self.state = 121 + self.match(OpenQASM2Parser.T__14) + self.state = 122 + self.goplist() + self.state = 123 + self.match(OpenQASM2Parser.T__15) + pass + + elif la_ == 3: + self.enterOuterAlt(localctx, 3) + self.state = 125 + self.match(OpenQASM2Parser.T__13) + self.state = 126 + self.match(OpenQASM2Parser.ID) + self.state = 127 + self.match(OpenQASM2Parser.T__3) + self.state = 128 + self.idlist() + self.state = 129 + self.match(OpenQASM2Parser.T__4) + self.state = 130 + self.idlist() + self.state = 131 + self.match(OpenQASM2Parser.T__14) + self.state = 132 + self.goplist() + self.state = 133 + self.match(OpenQASM2Parser.T__15) + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class GoplistContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def uop(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(OpenQASM2Parser.UopContext) + else: + return self.getTypedRuleContext(OpenQASM2Parser.UopContext,i) + + + def idlist(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(OpenQASM2Parser.IdlistContext) + else: + return self.getTypedRuleContext(OpenQASM2Parser.IdlistContext,i) + + + def getRuleIndex(self): + return OpenQASM2Parser.RULE_goplist + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterGoplist" ): + listener.enterGoplist(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitGoplist" ): + listener.exitGoplist(self) + + def accept(self, visitor:ParseTreeVisitor): + if hasattr( visitor, "visitGoplist" ): + return visitor.visitGoplist(self) + else: + return visitor.visitChildren(self) + + + + + def goplist(self): + + localctx = OpenQASM2Parser.GoplistContext(self, self._ctx, self.state) + self.enterRule(localctx, 12, self.RULE_goplist) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 144 + self._errHandler.sync(self) + _la = self._input.LA(1) + while (((_la) & ~0x3f) == 0 and ((1 << _la) & 549758959872) != 0): + self.state = 142 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [20, 21, 39]: + self.state = 137 + self.uop() + pass + elif token in [8]: + self.state = 138 + self.match(OpenQASM2Parser.T__7) + self.state = 139 + self.idlist() + self.state = 140 + self.match(OpenQASM2Parser.T__1) + pass + else: + raise NoViableAltException(self) + + self.state = 146 + self._errHandler.sync(self) + _la = self._input.LA(1) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class QopContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def uop(self): + return self.getTypedRuleContext(OpenQASM2Parser.UopContext,0) + + + def argument(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(OpenQASM2Parser.ArgumentContext) + else: + return self.getTypedRuleContext(OpenQASM2Parser.ArgumentContext,i) + + + def getRuleIndex(self): + return OpenQASM2Parser.RULE_qop + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterQop" ): + listener.enterQop(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitQop" ): + listener.exitQop(self) + + def accept(self, visitor:ParseTreeVisitor): + if hasattr( visitor, "visitQop" ): + return visitor.visitQop(self) + else: + return visitor.visitChildren(self) + + + + + def qop(self): + + localctx = OpenQASM2Parser.QopContext(self, self._ctx, self.state) + self.enterRule(localctx, 14, self.RULE_qop) + try: + self.state = 158 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [20, 21, 39]: + self.enterOuterAlt(localctx, 1) + self.state = 147 + self.uop() + pass + elif token in [17]: + self.enterOuterAlt(localctx, 2) + self.state = 148 + self.match(OpenQASM2Parser.T__16) + self.state = 149 + self.argument() + self.state = 150 + self.match(OpenQASM2Parser.T__17) + self.state = 151 + self.argument() + self.state = 152 + self.match(OpenQASM2Parser.T__1) + pass + elif token in [19]: + self.enterOuterAlt(localctx, 3) + self.state = 154 + self.match(OpenQASM2Parser.T__18) + self.state = 155 + self.argument() + self.state = 156 + self.match(OpenQASM2Parser.T__1) + pass + else: + raise NoViableAltException(self) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class UopContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def explist(self): + return self.getTypedRuleContext(OpenQASM2Parser.ExplistContext,0) + + + def argument(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(OpenQASM2Parser.ArgumentContext) + else: + return self.getTypedRuleContext(OpenQASM2Parser.ArgumentContext,i) + + + def ID(self): + return self.getToken(OpenQASM2Parser.ID, 0) + + def anylist(self): + return self.getTypedRuleContext(OpenQASM2Parser.AnylistContext,0) + + + def getRuleIndex(self): + return OpenQASM2Parser.RULE_uop + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterUop" ): + listener.enterUop(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitUop" ): + listener.exitUop(self) + + def accept(self, visitor:ParseTreeVisitor): + if hasattr( visitor, "visitUop" ): + return visitor.visitUop(self) + else: + return visitor.visitChildren(self) + + + + + def uop(self): + + localctx = OpenQASM2Parser.UopContext(self, self._ctx, self.state) + self.enterRule(localctx, 16, self.RULE_uop) + try: + self.state = 184 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,7,self._ctx) + if la_ == 1: + self.enterOuterAlt(localctx, 1) + self.state = 160 + self.match(OpenQASM2Parser.T__19) + self.state = 161 + self.match(OpenQASM2Parser.T__3) + self.state = 162 + self.explist() + self.state = 163 + self.match(OpenQASM2Parser.T__4) + self.state = 164 + self.argument() + self.state = 165 + self.match(OpenQASM2Parser.T__1) + pass + + elif la_ == 2: + self.enterOuterAlt(localctx, 2) + self.state = 167 + self.match(OpenQASM2Parser.T__20) + self.state = 168 + self.argument() + self.state = 169 + self.match(OpenQASM2Parser.T__21) + self.state = 170 + self.argument() + self.state = 171 + self.match(OpenQASM2Parser.T__1) + pass + + elif la_ == 3: + self.enterOuterAlt(localctx, 3) + self.state = 173 + self.match(OpenQASM2Parser.ID) + self.state = 174 + self.anylist() + self.state = 175 + self.match(OpenQASM2Parser.T__1) + pass + + elif la_ == 4: + self.enterOuterAlt(localctx, 4) + self.state = 177 + self.match(OpenQASM2Parser.ID) + self.state = 178 + self.match(OpenQASM2Parser.T__3) + self.state = 179 + self.explist() + self.state = 180 + self.match(OpenQASM2Parser.T__4) + self.state = 181 + self.anylist() + self.state = 182 + self.match(OpenQASM2Parser.T__1) + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class AnylistContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def idlist(self): + return self.getTypedRuleContext(OpenQASM2Parser.IdlistContext,0) + + + def mixedlist(self): + return self.getTypedRuleContext(OpenQASM2Parser.MixedlistContext,0) + + + def getRuleIndex(self): + return OpenQASM2Parser.RULE_anylist + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterAnylist" ): + listener.enterAnylist(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitAnylist" ): + listener.exitAnylist(self) + + def accept(self, visitor:ParseTreeVisitor): + if hasattr( visitor, "visitAnylist" ): + return visitor.visitAnylist(self) + else: + return visitor.visitChildren(self) + + + + + def anylist(self): + + localctx = OpenQASM2Parser.AnylistContext(self, self._ctx, self.state) + self.enterRule(localctx, 18, self.RULE_anylist) + try: + self.state = 188 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,8,self._ctx) + if la_ == 1: + self.enterOuterAlt(localctx, 1) + self.state = 186 + self.idlist() + pass + + elif la_ == 2: + self.enterOuterAlt(localctx, 2) + self.state = 187 + self.mixedlist() + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class IdlistContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def ID(self, i:int=None): + if i is None: + return self.getTokens(OpenQASM2Parser.ID) + else: + return self.getToken(OpenQASM2Parser.ID, i) + + def getRuleIndex(self): + return OpenQASM2Parser.RULE_idlist + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterIdlist" ): + listener.enterIdlist(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitIdlist" ): + listener.exitIdlist(self) + + def accept(self, visitor:ParseTreeVisitor): + if hasattr( visitor, "visitIdlist" ): + return visitor.visitIdlist(self) + else: + return visitor.visitChildren(self) + + + + + def idlist(self): + + localctx = OpenQASM2Parser.IdlistContext(self, self._ctx, self.state) + self.enterRule(localctx, 20, self.RULE_idlist) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 190 + self.match(OpenQASM2Parser.ID) + self.state = 195 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==22: + self.state = 191 + self.match(OpenQASM2Parser.T__21) + self.state = 192 + self.match(OpenQASM2Parser.ID) + self.state = 197 + self._errHandler.sync(self) + _la = self._input.LA(1) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class MixedlistContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def ID(self, i:int=None): + if i is None: + return self.getTokens(OpenQASM2Parser.ID) + else: + return self.getToken(OpenQASM2Parser.ID, i) + + def nninteger(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(OpenQASM2Parser.NnintegerContext) + else: + return self.getTypedRuleContext(OpenQASM2Parser.NnintegerContext,i) + + + def getRuleIndex(self): + return OpenQASM2Parser.RULE_mixedlist + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterMixedlist" ): + listener.enterMixedlist(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitMixedlist" ): + listener.exitMixedlist(self) + + def accept(self, visitor:ParseTreeVisitor): + if hasattr( visitor, "visitMixedlist" ): + return visitor.visitMixedlist(self) + else: + return visitor.visitChildren(self) + + + + + def mixedlist(self): + + localctx = OpenQASM2Parser.MixedlistContext(self, self._ctx, self.state) + self.enterRule(localctx, 22, self.RULE_mixedlist) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 198 + self.match(OpenQASM2Parser.ID) + self.state = 203 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==11: + self.state = 199 + self.match(OpenQASM2Parser.T__10) + self.state = 200 + self.nninteger() + self.state = 201 + self.match(OpenQASM2Parser.T__11) + + + self.state = 215 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==22: + self.state = 205 + self.match(OpenQASM2Parser.T__21) + self.state = 206 + self.match(OpenQASM2Parser.ID) + self.state = 211 + self._errHandler.sync(self) + _la = self._input.LA(1) + if _la==11: + self.state = 207 + self.match(OpenQASM2Parser.T__10) + self.state = 208 + self.nninteger() + self.state = 209 + self.match(OpenQASM2Parser.T__11) + + + self.state = 217 + self._errHandler.sync(self) + _la = self._input.LA(1) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class ArgumentContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def ID(self): + return self.getToken(OpenQASM2Parser.ID, 0) + + def nninteger(self): + return self.getTypedRuleContext(OpenQASM2Parser.NnintegerContext,0) + + + def getRuleIndex(self): + return OpenQASM2Parser.RULE_argument + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterArgument" ): + listener.enterArgument(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitArgument" ): + listener.exitArgument(self) + + def accept(self, visitor:ParseTreeVisitor): + if hasattr( visitor, "visitArgument" ): + return visitor.visitArgument(self) + else: + return visitor.visitChildren(self) + + + + + def argument(self): + + localctx = OpenQASM2Parser.ArgumentContext(self, self._ctx, self.state) + self.enterRule(localctx, 24, self.RULE_argument) + try: + self.state = 224 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,13,self._ctx) + if la_ == 1: + self.enterOuterAlt(localctx, 1) + self.state = 218 + self.match(OpenQASM2Parser.ID) + pass + + elif la_ == 2: + self.enterOuterAlt(localctx, 2) + self.state = 219 + self.match(OpenQASM2Parser.ID) + self.state = 220 + self.match(OpenQASM2Parser.T__10) + self.state = 221 + self.nninteger() + self.state = 222 + self.match(OpenQASM2Parser.T__11) + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class ExplistContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def exp(self, i:int=None): + if i is None: + return self.getTypedRuleContexts(OpenQASM2Parser.ExpContext) + else: + return self.getTypedRuleContext(OpenQASM2Parser.ExpContext,i) + + + def getRuleIndex(self): + return OpenQASM2Parser.RULE_explist + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterExplist" ): + listener.enterExplist(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitExplist" ): + listener.exitExplist(self) + + def accept(self, visitor:ParseTreeVisitor): + if hasattr( visitor, "visitExplist" ): + return visitor.visitExplist(self) + else: + return visitor.visitChildren(self) + + + + + def explist(self): + + localctx = OpenQASM2Parser.ExplistContext(self, self._ctx, self.state) + self.enterRule(localctx, 26, self.RULE_explist) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 226 + self.exp() + self.state = 231 + self._errHandler.sync(self) + _la = self._input.LA(1) + while _la==22: + self.state = 227 + self.match(OpenQASM2Parser.T__21) + self.state = 228 + self.exp() + self.state = 233 + self._errHandler.sync(self) + _la = self._input.LA(1) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class ExpContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def additiveExp(self): + return self.getTypedRuleContext(OpenQASM2Parser.AdditiveExpContext,0) + + + def getRuleIndex(self): + return OpenQASM2Parser.RULE_exp + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterExp" ): + listener.enterExp(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitExp" ): + listener.exitExp(self) + + def accept(self, visitor:ParseTreeVisitor): + if hasattr( visitor, "visitExp" ): + return visitor.visitExp(self) + else: + return visitor.visitChildren(self) + + + + + def exp(self): + + localctx = OpenQASM2Parser.ExpContext(self, self._ctx, self.state) + self.enterRule(localctx, 28, self.RULE_exp) + try: + self.enterOuterAlt(localctx, 1) + self.state = 234 + self.additiveExp(0) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class AdditiveExpContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def multiplicativeExp(self): + return self.getTypedRuleContext(OpenQASM2Parser.MultiplicativeExpContext,0) + + + def additiveExp(self): + return self.getTypedRuleContext(OpenQASM2Parser.AdditiveExpContext,0) + + + def getRuleIndex(self): + return OpenQASM2Parser.RULE_additiveExp + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterAdditiveExp" ): + listener.enterAdditiveExp(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitAdditiveExp" ): + listener.exitAdditiveExp(self) + + def accept(self, visitor:ParseTreeVisitor): + if hasattr( visitor, "visitAdditiveExp" ): + return visitor.visitAdditiveExp(self) + else: + return visitor.visitChildren(self) + + + + def additiveExp(self, _p:int=0): + _parentctx = self._ctx + _parentState = self.state + localctx = OpenQASM2Parser.AdditiveExpContext(self, self._ctx, _parentState) + _prevctx = localctx + _startState = 30 + self.enterRecursionRule(localctx, 30, self.RULE_additiveExp, _p) + try: + self.enterOuterAlt(localctx, 1) + self.state = 237 + self.multiplicativeExp(0) + self._ctx.stop = self._input.LT(-1) + self.state = 247 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,16,self._ctx) + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1: + if self._parseListeners is not None: + self.triggerExitRuleEvent() + _prevctx = localctx + self.state = 245 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,15,self._ctx) + if la_ == 1: + localctx = OpenQASM2Parser.AdditiveExpContext(self, _parentctx, _parentState) + self.pushNewRecursionContext(localctx, _startState, self.RULE_additiveExp) + self.state = 239 + if not self.precpred(self._ctx, 3): + from antlr4.error.Errors import FailedPredicateException + raise FailedPredicateException(self, "self.precpred(self._ctx, 3)") + self.state = 240 + self.match(OpenQASM2Parser.T__22) + self.state = 241 + self.multiplicativeExp(0) + pass + + elif la_ == 2: + localctx = OpenQASM2Parser.AdditiveExpContext(self, _parentctx, _parentState) + self.pushNewRecursionContext(localctx, _startState, self.RULE_additiveExp) + self.state = 242 + if not self.precpred(self._ctx, 2): + from antlr4.error.Errors import FailedPredicateException + raise FailedPredicateException(self, "self.precpred(self._ctx, 2)") + self.state = 243 + self.match(OpenQASM2Parser.T__23) + self.state = 244 + self.multiplicativeExp(0) + pass + + + self.state = 249 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,16,self._ctx) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.unrollRecursionContexts(_parentctx) + return localctx + + + class MultiplicativeExpContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def exponentialExp(self): + return self.getTypedRuleContext(OpenQASM2Parser.ExponentialExpContext,0) + + + def multiplicativeExp(self): + return self.getTypedRuleContext(OpenQASM2Parser.MultiplicativeExpContext,0) + + + def getRuleIndex(self): + return OpenQASM2Parser.RULE_multiplicativeExp + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterMultiplicativeExp" ): + listener.enterMultiplicativeExp(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitMultiplicativeExp" ): + listener.exitMultiplicativeExp(self) + + def accept(self, visitor:ParseTreeVisitor): + if hasattr( visitor, "visitMultiplicativeExp" ): + return visitor.visitMultiplicativeExp(self) + else: + return visitor.visitChildren(self) + + + + def multiplicativeExp(self, _p:int=0): + _parentctx = self._ctx + _parentState = self.state + localctx = OpenQASM2Parser.MultiplicativeExpContext(self, self._ctx, _parentState) + _prevctx = localctx + _startState = 32 + self.enterRecursionRule(localctx, 32, self.RULE_multiplicativeExp, _p) + try: + self.enterOuterAlt(localctx, 1) + self.state = 251 + self.exponentialExp() + self._ctx.stop = self._input.LT(-1) + self.state = 261 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,18,self._ctx) + while _alt!=2 and _alt!=ATN.INVALID_ALT_NUMBER: + if _alt==1: + if self._parseListeners is not None: + self.triggerExitRuleEvent() + _prevctx = localctx + self.state = 259 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,17,self._ctx) + if la_ == 1: + localctx = OpenQASM2Parser.MultiplicativeExpContext(self, _parentctx, _parentState) + self.pushNewRecursionContext(localctx, _startState, self.RULE_multiplicativeExp) + self.state = 253 + if not self.precpred(self._ctx, 3): + from antlr4.error.Errors import FailedPredicateException + raise FailedPredicateException(self, "self.precpred(self._ctx, 3)") + self.state = 254 + self.match(OpenQASM2Parser.T__24) + self.state = 255 + self.exponentialExp() + pass + + elif la_ == 2: + localctx = OpenQASM2Parser.MultiplicativeExpContext(self, _parentctx, _parentState) + self.pushNewRecursionContext(localctx, _startState, self.RULE_multiplicativeExp) + self.state = 256 + if not self.precpred(self._ctx, 2): + from antlr4.error.Errors import FailedPredicateException + raise FailedPredicateException(self, "self.precpred(self._ctx, 2)") + self.state = 257 + self.match(OpenQASM2Parser.T__25) + self.state = 258 + self.exponentialExp() + pass + + + self.state = 263 + self._errHandler.sync(self) + _alt = self._interp.adaptivePredict(self._input,18,self._ctx) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.unrollRecursionContexts(_parentctx) + return localctx + + + class ExponentialExpContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def unaryExp(self): + return self.getTypedRuleContext(OpenQASM2Parser.UnaryExpContext,0) + + + def exponentialExp(self): + return self.getTypedRuleContext(OpenQASM2Parser.ExponentialExpContext,0) + + + def getRuleIndex(self): + return OpenQASM2Parser.RULE_exponentialExp + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterExponentialExp" ): + listener.enterExponentialExp(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitExponentialExp" ): + listener.exitExponentialExp(self) + + def accept(self, visitor:ParseTreeVisitor): + if hasattr( visitor, "visitExponentialExp" ): + return visitor.visitExponentialExp(self) + else: + return visitor.visitChildren(self) + + + + + def exponentialExp(self): + + localctx = OpenQASM2Parser.ExponentialExpContext(self, self._ctx, self.state) + self.enterRule(localctx, 34, self.RULE_exponentialExp) + try: + self.state = 269 + self._errHandler.sync(self) + la_ = self._interp.adaptivePredict(self._input,19,self._ctx) + if la_ == 1: + self.enterOuterAlt(localctx, 1) + self.state = 264 + self.unaryExp() + self.state = 265 + self.match(OpenQASM2Parser.T__26) + self.state = 266 + self.exponentialExp() + pass + + elif la_ == 2: + self.enterOuterAlt(localctx, 2) + self.state = 268 + self.unaryExp() + pass + + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class UnaryExpContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def unaryExp(self): + return self.getTypedRuleContext(OpenQASM2Parser.UnaryExpContext,0) + + + def primaryExp(self): + return self.getTypedRuleContext(OpenQASM2Parser.PrimaryExpContext,0) + + + def getRuleIndex(self): + return OpenQASM2Parser.RULE_unaryExp + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterUnaryExp" ): + listener.enterUnaryExp(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitUnaryExp" ): + listener.exitUnaryExp(self) + + def accept(self, visitor:ParseTreeVisitor): + if hasattr( visitor, "visitUnaryExp" ): + return visitor.visitUnaryExp(self) + else: + return visitor.visitChildren(self) + + + + + def unaryExp(self): + + localctx = OpenQASM2Parser.UnaryExpContext(self, self._ctx, self.state) + self.enterRule(localctx, 36, self.RULE_unaryExp) + try: + self.state = 274 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [24]: + self.enterOuterAlt(localctx, 1) + self.state = 271 + self.match(OpenQASM2Parser.T__23) + self.state = 272 + self.unaryExp() + pass + elif token in [4, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 39, 40, 41]: + self.enterOuterAlt(localctx, 2) + self.state = 273 + self.primaryExp() + pass + else: + raise NoViableAltException(self) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class PrimaryExpContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def real(self): + return self.getTypedRuleContext(OpenQASM2Parser.RealContext,0) + + + def nninteger(self): + return self.getTypedRuleContext(OpenQASM2Parser.NnintegerContext,0) + + + def ID(self): + return self.getToken(OpenQASM2Parser.ID, 0) + + def exp(self): + return self.getTypedRuleContext(OpenQASM2Parser.ExpContext,0) + + + def unaryop(self): + return self.getTypedRuleContext(OpenQASM2Parser.UnaryopContext,0) + + + def getRuleIndex(self): + return OpenQASM2Parser.RULE_primaryExp + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterPrimaryExp" ): + listener.enterPrimaryExp(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitPrimaryExp" ): + listener.exitPrimaryExp(self) + + def accept(self, visitor:ParseTreeVisitor): + if hasattr( visitor, "visitPrimaryExp" ): + return visitor.visitPrimaryExp(self) + else: + return visitor.visitChildren(self) + + + + + def primaryExp(self): + + localctx = OpenQASM2Parser.PrimaryExpContext(self, self._ctx, self.state) + self.enterRule(localctx, 38, self.RULE_primaryExp) + try: + self.state = 289 + self._errHandler.sync(self) + token = self._input.LA(1) + if token in [40]: + self.enterOuterAlt(localctx, 1) + self.state = 276 + self.real() + pass + elif token in [41]: + self.enterOuterAlt(localctx, 2) + self.state = 277 + self.nninteger() + pass + elif token in [28]: + self.enterOuterAlt(localctx, 3) + self.state = 278 + self.match(OpenQASM2Parser.T__27) + pass + elif token in [39]: + self.enterOuterAlt(localctx, 4) + self.state = 279 + self.match(OpenQASM2Parser.ID) + pass + elif token in [4]: + self.enterOuterAlt(localctx, 5) + self.state = 280 + self.match(OpenQASM2Parser.T__3) + self.state = 281 + self.exp() + self.state = 282 + self.match(OpenQASM2Parser.T__4) + pass + elif token in [29, 30, 31, 32, 33, 34, 35, 36, 37]: + self.enterOuterAlt(localctx, 6) + self.state = 284 + self.unaryop() + self.state = 285 + self.match(OpenQASM2Parser.T__3) + self.state = 286 + self.exp() + self.state = 287 + self.match(OpenQASM2Parser.T__4) + pass + else: + raise NoViableAltException(self) + + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class UnaryopContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + + def getRuleIndex(self): + return OpenQASM2Parser.RULE_unaryop + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterUnaryop" ): + listener.enterUnaryop(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitUnaryop" ): + listener.exitUnaryop(self) + + def accept(self, visitor:ParseTreeVisitor): + if hasattr( visitor, "visitUnaryop" ): + return visitor.visitUnaryop(self) + else: + return visitor.visitChildren(self) + + + + + def unaryop(self): + + localctx = OpenQASM2Parser.UnaryopContext(self, self._ctx, self.state) + self.enterRule(localctx, 40, self.RULE_unaryop) + self._la = 0 # Token type + try: + self.enterOuterAlt(localctx, 1) + self.state = 291 + _la = self._input.LA(1) + if not((((_la) & ~0x3f) == 0 and ((1 << _la) & 274341036032) != 0)): + self._errHandler.recoverInline(self) + else: + self._errHandler.reportMatch(self) + self.consume() + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class RealContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def REAL(self): + return self.getToken(OpenQASM2Parser.REAL, 0) + + def getRuleIndex(self): + return OpenQASM2Parser.RULE_real + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterReal" ): + listener.enterReal(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitReal" ): + listener.exitReal(self) + + def accept(self, visitor:ParseTreeVisitor): + if hasattr( visitor, "visitReal" ): + return visitor.visitReal(self) + else: + return visitor.visitChildren(self) + + + + + def real(self): + + localctx = OpenQASM2Parser.RealContext(self, self._ctx, self.state) + self.enterRule(localctx, 42, self.RULE_real) + try: + self.enterOuterAlt(localctx, 1) + self.state = 293 + self.match(OpenQASM2Parser.REAL) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + class NnintegerContext(ParserRuleContext): + __slots__ = 'parser' + + def __init__(self, parser, parent:ParserRuleContext=None, invokingState:int=-1): + super().__init__(parent, invokingState) + self.parser = parser + + def NNINTEGER(self): + return self.getToken(OpenQASM2Parser.NNINTEGER, 0) + + def getRuleIndex(self): + return OpenQASM2Parser.RULE_nninteger + + def enterRule(self, listener:ParseTreeListener): + if hasattr( listener, "enterNninteger" ): + listener.enterNninteger(self) + + def exitRule(self, listener:ParseTreeListener): + if hasattr( listener, "exitNninteger" ): + listener.exitNninteger(self) + + def accept(self, visitor:ParseTreeVisitor): + if hasattr( visitor, "visitNninteger" ): + return visitor.visitNninteger(self) + else: + return visitor.visitChildren(self) + + + + + def nninteger(self): + + localctx = OpenQASM2Parser.NnintegerContext(self, self._ctx, self.state) + self.enterRule(localctx, 44, self.RULE_nninteger) + try: + self.enterOuterAlt(localctx, 1) + self.state = 295 + self.match(OpenQASM2Parser.NNINTEGER) + except RecognitionException as re: + localctx.exception = re + self._errHandler.reportError(self, re) + self._errHandler.recover(self, re) + finally: + self.exitRule() + return localctx + + + + def sempred(self, localctx:RuleContext, ruleIndex:int, predIndex:int): + if self._predicates == None: + self._predicates = dict() + self._predicates[15] = self.additiveExp_sempred + self._predicates[16] = self.multiplicativeExp_sempred + pred = self._predicates.get(ruleIndex, None) + if pred is None: + raise Exception("No predicate with index:" + str(ruleIndex)) + else: + return pred(localctx, predIndex) + + def additiveExp_sempred(self, localctx:AdditiveExpContext, predIndex:int): + if predIndex == 0: + return self.precpred(self._ctx, 3) + + + if predIndex == 1: + return self.precpred(self._ctx, 2) + + + def multiplicativeExp_sempred(self, localctx:MultiplicativeExpContext, predIndex:int): + if predIndex == 2: + return self.precpred(self._ctx, 3) + + + if predIndex == 3: + return self.precpred(self._ctx, 2) + + + + + diff --git a/cqlib/utils/qasm2/_antlr4/OpenQASM2Visitor.py b/cqlib/utils/qasm2/_antlr4/OpenQASM2Visitor.py new file mode 100644 index 0000000000000000000000000000000000000000..638f327d5794eb2fa8e2a920498b870b389ef7af --- /dev/null +++ b/cqlib/utils/qasm2/_antlr4/OpenQASM2Visitor.py @@ -0,0 +1,128 @@ +# Generated from OpenQASM2.g4 by ANTLR 4.13.2 +from antlr4 import * +if "." in __name__: + from .OpenQASM2Parser import OpenQASM2Parser +else: + from OpenQASM2Parser import OpenQASM2Parser + +# This class defines a complete generic visitor for a parse tree produced by OpenQASM2Parser. + +class OpenQASM2Visitor(ParseTreeVisitor): + + # Visit a parse tree produced by OpenQASM2Parser#mainprogram. + def visitMainprogram(self, ctx:OpenQASM2Parser.MainprogramContext): + return self.visitChildren(ctx) + + + # Visit a parse tree produced by OpenQASM2Parser#program. + def visitProgram(self, ctx:OpenQASM2Parser.ProgramContext): + return self.visitChildren(ctx) + + + # Visit a parse tree produced by OpenQASM2Parser#statement. + def visitStatement(self, ctx:OpenQASM2Parser.StatementContext): + return self.visitChildren(ctx) + + + # Visit a parse tree produced by OpenQASM2Parser#includeStatement. + def visitIncludeStatement(self, ctx:OpenQASM2Parser.IncludeStatementContext): + return self.visitChildren(ctx) + + + # Visit a parse tree produced by OpenQASM2Parser#decl. + def visitDecl(self, ctx:OpenQASM2Parser.DeclContext): + return self.visitChildren(ctx) + + + # Visit a parse tree produced by OpenQASM2Parser#gatedecl. + def visitGatedecl(self, ctx:OpenQASM2Parser.GatedeclContext): + return self.visitChildren(ctx) + + + # Visit a parse tree produced by OpenQASM2Parser#goplist. + def visitGoplist(self, ctx:OpenQASM2Parser.GoplistContext): + return self.visitChildren(ctx) + + + # Visit a parse tree produced by OpenQASM2Parser#qop. + def visitQop(self, ctx:OpenQASM2Parser.QopContext): + return self.visitChildren(ctx) + + + # Visit a parse tree produced by OpenQASM2Parser#uop. + def visitUop(self, ctx:OpenQASM2Parser.UopContext): + return self.visitChildren(ctx) + + + # Visit a parse tree produced by OpenQASM2Parser#anylist. + def visitAnylist(self, ctx:OpenQASM2Parser.AnylistContext): + return self.visitChildren(ctx) + + + # Visit a parse tree produced by OpenQASM2Parser#idlist. + def visitIdlist(self, ctx:OpenQASM2Parser.IdlistContext): + return self.visitChildren(ctx) + + + # Visit a parse tree produced by OpenQASM2Parser#mixedlist. + def visitMixedlist(self, ctx:OpenQASM2Parser.MixedlistContext): + return self.visitChildren(ctx) + + + # Visit a parse tree produced by OpenQASM2Parser#argument. + def visitArgument(self, ctx:OpenQASM2Parser.ArgumentContext): + return self.visitChildren(ctx) + + + # Visit a parse tree produced by OpenQASM2Parser#explist. + def visitExplist(self, ctx:OpenQASM2Parser.ExplistContext): + return self.visitChildren(ctx) + + + # Visit a parse tree produced by OpenQASM2Parser#exp. + def visitExp(self, ctx:OpenQASM2Parser.ExpContext): + return self.visitChildren(ctx) + + + # Visit a parse tree produced by OpenQASM2Parser#additiveExp. + def visitAdditiveExp(self, ctx:OpenQASM2Parser.AdditiveExpContext): + return self.visitChildren(ctx) + + + # Visit a parse tree produced by OpenQASM2Parser#multiplicativeExp. + def visitMultiplicativeExp(self, ctx:OpenQASM2Parser.MultiplicativeExpContext): + return self.visitChildren(ctx) + + + # Visit a parse tree produced by OpenQASM2Parser#exponentialExp. + def visitExponentialExp(self, ctx:OpenQASM2Parser.ExponentialExpContext): + return self.visitChildren(ctx) + + + # Visit a parse tree produced by OpenQASM2Parser#unaryExp. + def visitUnaryExp(self, ctx:OpenQASM2Parser.UnaryExpContext): + return self.visitChildren(ctx) + + + # Visit a parse tree produced by OpenQASM2Parser#primaryExp. + def visitPrimaryExp(self, ctx:OpenQASM2Parser.PrimaryExpContext): + return self.visitChildren(ctx) + + + # Visit a parse tree produced by OpenQASM2Parser#unaryop. + def visitUnaryop(self, ctx:OpenQASM2Parser.UnaryopContext): + return self.visitChildren(ctx) + + + # Visit a parse tree produced by OpenQASM2Parser#real. + def visitReal(self, ctx:OpenQASM2Parser.RealContext): + return self.visitChildren(ctx) + + + # Visit a parse tree produced by OpenQASM2Parser#nninteger. + def visitNninteger(self, ctx:OpenQASM2Parser.NnintegerContext): + return self.visitChildren(ctx) + + + +del OpenQASM2Parser \ No newline at end of file diff --git a/cqlib/utils/qasm2/_antlr4/__init__.py b/cqlib/utils/qasm2/_antlr4/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..939b27e8ba4b65aa8d455913e10620c7eb049e0e --- /dev/null +++ b/cqlib/utils/qasm2/_antlr4/__init__.py @@ -0,0 +1 @@ +# pylint: skip-file \ No newline at end of file diff --git a/cqlib/utils/qasm2/_parse.py b/cqlib/utils/qasm2/_parse.py new file mode 100644 index 0000000000000000000000000000000000000000..8f818199f973189a9d10549a8443af0fffeeb7cd --- /dev/null +++ b/cqlib/utils/qasm2/_parse.py @@ -0,0 +1,941 @@ +# This code is part of cqlib. +# +# Copyright (C) 2024 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +OpenQASM 2.0 Parser for cqlib + +This module provides a parser that reads OpenQASM 2.0 files and converts +them into cqlib Circuit objects. + + +Note: +- Classical bits are not supported. +- File imports are limited to `qelib1.inc` only. +- Conditional operations (if Statements) are implemented. +- Reset operation (reset) are implemented. +- Opaque gate definitions (opaque) are implemented. +- Measurement result mapping to classical bits is not supported. +""" + +import math +import operator + +import numpy as np +import sympy as sp +from antlr4 import FileStream, CommonTokenStream, InputStream + +from cqlib.exceptions import QASMParserError +from cqlib.circuits.circuit import Circuit + +from ._antlr4.OpenQASM2Lexer import OpenQASM2Lexer +from ._antlr4.OpenQASM2Parser import OpenQASM2Parser +from ._antlr4.OpenQASM2Visitor import OpenQASM2Visitor + +# openqasm2.0 id gate wait time +I_DURATION = 60 + +BASE_GATES = { + 'U': [3, 1], + 'CX': [0, 2], +} + +QELIB1_GATES = { + 'u3': [3, 1], + 'u2': [2, 1], + 'u1': [1, 1], + 'u': [3, 1], + 'cx': [0, 2], + 'id': [0, 1], + 'x': [0, 1], + 'y': [0, 1], + 'z': [0, 1], + 'h': [0, 1], + 'p': [1, 1], + 's': [0, 1], + 'sdg': [0, 1], + 'sx': [0, 1], + 'sxdg': [0, 1], + 't': [0, 1], + 'tdg': [0, 1], + 'rx': [1, 1], + 'ry': [1, 1], + 'rz': [1, 1], + 'cz': [0, 2], + 'cy': [0, 2], + 'ch': [0, 2], + 'swap': [0, 2], + 'cswap': [0, 3], + 'ccx': [0, 3], + 'crx': [1, 2], + 'cry': [1, 2], + 'crz': [1, 2], + 'csx': [0, 2], +} + +FUNCTIONS = { + 'sin': sp.sin, + 'cos': sp.cos, + 'tan': sp.tan, + 'exp': sp.exp, + 'ln': sp.log, + 'sqrt': sp.sqrt, + 'acos': sp.acos, + 'atan': sp.atan, + 'asin': sp.asin, +} + +OPERATORS = { + '+': operator.add, + '-': operator.sub, + '*': operator.mul, + '/': operator.truediv, + '^': operator.pow, +} + + +# pylint: disable=too-few-public-methods +class CustomGateItem: + """ + Represents an operation within a custom gate definition. + """ + + def __init__(self, gate: str, params: list[str | float], qubits: list[int]): + """ + init CustomGateItem + + Args: + gate (str): The name of the gate operation. + params (list): A list of parameters for the gate operation. + qubits (list): A list of qubit identifiers the gate acts on. + """ + self.gate = gate + self.params = params + self.qubits = qubits + + +class CustomGate: + """ + Represents a custom quantum gate defined in OpenQASM. + """ + + def __init__( + self, + name: str, + params: list, + qubits: list, + converter: 'OpenQASM2Converter' + ): + """ + + Args: + name (str): The name of the custom gate. + params (list): Parameter names for the custom gate. + qubits (list): Qubit argument names for the custom gate. + converter (OpenQASM2Converter): Reference to the main converter for context. + """ + self.name = name + self.params = params + if len(qubits) < 1: + raise QASMParserError("At least one quantum argument is mandatory for gates to act on") + self.qubits = qubits + self.op_list: list[CustomGateItem] = [] + self.converter = converter + + def visit_goplist(self, ctx: OpenQASM2Parser.GoplistContext): + """ + Parses the gate operation list within a custom gate definition. + + Args: + ctx (OpenQASM2Parser.GoplistContext): The context for the gate operation list. + """ + operations = [] + if not ctx.children: + return + + for child in ctx.children: + if isinstance(child, OpenQASM2Parser.UopContext): + operations.append(self.visit_uop(child)) + elif child.getText() == 'barrier': + # Process a barrier operation + barrier_index = ctx.children.index(child) + idlist_ctx = ctx.children[barrier_index + 1] + ids = [id_tok.getText() for id_tok in idlist_ctx.ID()] + operations.append(CustomGateItem('barrier', params=[], qubits=ids)) + self.op_list = operations + + def visit_uop(self, ctx: OpenQASM2Parser.UopContext) -> CustomGateItem: + """ + Parses a single gate operation within a custom gate definition. + + Args: + ctx (OpenQASM2Parser.UopContext): The context for the gate operation. + """ + gate = ctx.getChild(0).getText() + if gate == self.name: + raise QASMParserError(f"Quantum gates cannot be called in a loop.\n " + f"gate {gate} line {ctx.start.line} {ctx.getText()}") + + parameters = [] + if gate == 'U': + parameters = self.converter.visitExplist(ctx.explist()) + qubits = self.converter.visitArgument(ctx.argument(0)) + elif gate == 'CX': + ctl_qubit = self.converter.visitArgument(ctx.argument(0)) + target_qubit = self.converter.visitArgument(ctx.argument(1)) + qubits = [ctl_qubit, target_qubit] + else: + if exp_list := ctx.explist(): + parameters = self.converter.visitExplist(exp_list) + qubits = self.converter.visitAnylist(ctx.anylist()) + + return CustomGateItem(gate, params=parameters, qubits=qubits) + + def execute(self, gate: str, exp_list: list[str], qubit_list: list[int]): + """ + Executes the custom gate by applying its operations to the circuit. + + Args: + gate (str): The name of the custom gate. + exp_list (list): List of parameter values for the gate. + qubit_list (list): List of qubit indices the gate acts on. + """ + if len(self.params) != len(exp_list) or len(self.qubits) != len(qubit_list): + raise QASMParserError(f"gate args error: {gate}") + ps = dict(zip(self.params, exp_list)) + qs = dict(zip(self.qubits, qubit_list)) + + for op in self.op_list: + if op.gate in self.converter.custom_gates: + self.converter.custom_gates[op.gate].execute(op.gate, op.params, op.qubits) + continue + params = [] + for p in op.params: + if isinstance(p, sp.Basic): + value = p.subs(ps) + params.append(round(float(value), self.converter.precision)) + elif isinstance(p, str): + params.append(ps[str(p)]) + else: + params.append(p) + # params = [ps[p] if isinstance(p, str) else p for p in op.params] + qubits = [qs[q] for q in op.qubits] + match op.gate: + case 'U': + self.converter.circuit.u(qubits[0], *params) + case 'CX': + self.converter.circuit.cx(*qubits, *params) + case 'barrier': + self.converter.circuit.barrier(*qubits, *params) + case _: + self.converter.execute_op(self.converter.circuit, op.gate, qubits, params) + + +# pylint: disable=too-many-instance-attributes +class OpenQASM2Converter(OpenQASM2Visitor): + """ + Converts OpenQASM 2.0 code into a cqlib Circuit object. + """ + + def __init__(self, precision: int = 10): + """ + init Converter. + + Args: + precision (int, optional): The number of decimal places to round parameters to. + """ + super().__init__() + self.circuit = Circuit(0) + self.gates = BASE_GATES + self.functions = FUNCTIONS + self.operators = OPERATORS + self.include_qelib1 = False + self.qubits = {} + self.clbits = {} + self.custom_gates: dict[str, CustomGate] = {} + self.current_custom_gate = None + self.measure_ops = {} + self.precision = precision + + def visitMainprogram(self, ctx: OpenQASM2Parser.MainprogramContext): + """ + Parses the main program structure of the OpenQASM file. + + Args: + ctx (OpenQASM2Parser.MainprogramContext): The context of the main program. + + ```antlr4 + mainprogram : 'OPENQASM' real ';' program EOF; + ``` + """ + child_count = ctx.getChildCount() + if child_count < 2: + raise QASMParserError(f"format error: {ctx.getText()}") + if ctx.getChild(0).getText() != 'OPENQASM': + raise QASMParserError(f" {ctx.getText()}") + if float(ctx.getChild(1).getText()) != 2.0: + raise QASMParserError(f" {ctx.getText()}") + return self.visitChildren(ctx) + + def visitIncludeStatement(self, ctx: OpenQASM2Parser.IncludeStatementContext): + """ + Handles include statements in the OpenQASM code. + + Args: + ctx (OpenQASM2Parser.IncludeStatementContext): The context of the include statement. + + ```antlr4 + includeStatement + : 'include' STRING ';' + ; + ``` + """ + filename = ctx.STRING().getText() + if filename[1:-1] != 'qelib1.inc': + raise QASMParserError(f"Unsupported file included: {filename}. " + f"Only 'qelib1.inc' is supported.") + self.gates.update(QELIB1_GATES) + self.include_qelib1 = True + return self.visitChildren(ctx) + + def visitStatement(self, ctx: OpenQASM2Parser.StatementContext): + """ + Parses individual statements in the OpenQASM code. + + Args: + ctx (OpenQASM2Parser.StatementContext): The context of the statement. + + ```antlr4 + statement + : includeStatement // 新增文件包含语句 + | decl // 量子或经典寄存器声明 + | gatedecl // 空门定义 + | 'opaque' ID idlist ';' // 不透明门 + | 'opaque' ID '(' idlist ')' idlist ';' + | qop // 量子操作 + | 'if' '(' ID '==' nninteger ')' qop + | 'barrier' anylist ';' + ; + ``` + """ + if statement := ctx.includeStatement(): + return self.visitIncludeStatement(statement) + if decl := ctx.decl(): + return self.visitDecl(decl) + if gate_decl := ctx.gatedecl(): + return self.visitGatedecl(gate_decl) + if qop := ctx.qop(): + return self.visitQop(qop) + gate = ctx.getChild(0).getText() + if gate == 'opaque': + raise QASMParserError(f"Unsupported gate: opaque. {ctx.getText()}") + if gate == 'if': + raise QASMParserError(f"Unsupported gate: if. {ctx.getText()}") + if gate == 'barrier': + qs = [] + for a in self.visitAnylist(ctx.anylist()): + qs.extend(self.get_qubits(a)) + self.circuit.barrier(*qs) + return + raise QASMParserError(f"Unknown gate: {gate}") + + def visitDecl(self, ctx: OpenQASM2Parser.DeclContext): + """ + Handles quantum and classical register declarations. + + Args: + ctx (OpenQASM2Parser.DeclContext): The context of the declaration. + + ```antlr4 + decl + : 'qreg' ID '[' nninteger ']' ';' + | 'creg' ID '[' nninteger ']' ';' + ; + ``` + """ + tag = ctx.getChild(0).getText() + name = ctx.ID().getText() + size = int(ctx.nninteger().getText()) + + if tag == 'qreg': + qubits_len = len(self.qubits) + for i in range(size): + self.circuit.add_qubit(i + qubits_len) + k = f'{name}_{i}' + if k in self.qubits: + raise QASMParserError(f"Duplicate qreg definitions: {ctx.getText()}") + self.qubits[k] = i + qubits_len + elif tag == 'creg': + cl_len = len(self.clbits) + for i in range(size): + k = f'{name}_{i}' + if k in self.clbits: + raise QASMParserError(f"Duplicate creg definitions: {ctx.getText()}") + self.clbits[k] = cl_len + i + else: + raise QASMParserError(f"Unknown definitions: {ctx.getText()}") + + def visitGatedecl(self, ctx: OpenQASM2Parser.GatedeclContext): + """ + Parses custom gate definitions. + + Args: + ctx (OpenQASM2Parser.GatedeclContext): The context of the gate definition. + + ```antlr4 + gatedecl + : 'gate' ID idlist '{' goplist '}' // 无参数的 gate + | 'gate' ID '(' ')' idlist '{' goplist '}' // 空参数列表的 gate + | 'gate' ID '(' idlist ')' idlist '{' goplist '}' // 带参数的 gate + ; + ``` + """ + gate_name = ctx.ID().getText() + + def get_id(id_list): + ids = [] + for t in id_list.ID(): + ids.append(t.getText()) + return ids + + if len(ctx.idlist()) == 1: + parameters = [] + qubit_args = get_id(ctx.idlist(0)) + else: + parameters = get_id(ctx.idlist(0)) + qubit_args = get_id(ctx.idlist(1)) + if gate_name in self.custom_gates: + raise QASMParserError(f"Duplicate gate definitions: {gate_name} \n" + f"line {ctx.start.line}, {ctx.getText()}") + + gate = CustomGate(gate_name, params=parameters, qubits=qubit_args, converter=self) + gate.visit_goplist(ctx.goplist()) + self.custom_gates[gate_name] = gate + + def visitQop(self, ctx: OpenQASM2Parser.QopContext): + """ + Parses quantum operations (gates, measurements, resets). + + Args: + ctx (OpenQASM2Parser.QopContext): The context of the quantum operation. + + ```antlr4 + qop + : uop + | 'measure' argument '->' argument ';' + | 'reset' argument ';' + ; + ``` + """ + child_count = ctx.getChildCount() + if ctx.uop() and child_count == 1: + return self.visitUop(ctx.uop()) + if ctx.getChild(0).getText() == 'measure' and child_count == 5: + qubit = self.visitArgument(ctx.argument(0)) + qubit = self.get_qubits(qubit) + for q in qubit: + self.circuit.measure(q) + elif ctx.getChild(0) == 'reset': + raise QASMParserError("Unsupported quantum gates: reset") + return None + + def visitUop(self, ctx: OpenQASM2Parser.UopContext): + """ + Parses quantum gate operations. + + Args: + ctx (OpenQASM2Parser.UopContext): The context of the gate operation. + + ```antlr4 + uop + : 'U' '(' explist ')' argument ';' + | 'CX' argument ',' argument ';' + | ID anylist ';' + | ID '(' explist ')' anylist ';' + ; + ``` + """ + gate = ctx.getChild(0).getText() + if not gate: + return + if gate not in self.gates and gate not in self.custom_gates: + tip = f'Unknown gate "{gate}"' + if not self.include_qelib1: + tip += ', did you forget to include qelib1.inc?' + raise QASMParserError(tip) + + if gate in self.custom_gates: + if exp_list := ctx.explist(): + exp_list = self.visitExplist(exp_list) + else: + exp_list = [] + any_list = self.visitAnylist(ctx.anylist()) + for group in self.parse_qubit_group(any_list): + self.custom_gates[gate].execute(gate, exp_list, group) + return + + if gate == 'U': + parameters = self.visitExplist(ctx.explist()) + arguments = self.visitArgument(ctx.argument(0)) + for a in self.parse_qubit_group(arguments): + self.circuit.u(*a, *parameters) + elif gate == 'CX': + ctl_qubit = self.visitArgument(ctx.argument(0)) + # cs = self.get_qubits(ctl_qubit) + target_qubit = self.visitArgument(ctx.argument(1)) + for group in self.parse_qubit_group([ctl_qubit, target_qubit]): + self.circuit.cx(*group) + else: + self.handle_custom_operation(ctx) + + def handle_custom_operation(self, ctx): + """ + Handles built-in and custom gate operations. + + Args: + ctx (OpenQASM2Parser.UopContext): The context of the gate operation. + """ + gate = ctx.ID().getText() + if ctx.explist(): + parameters = self.visitExplist(ctx.explist()) + else: + parameters = [] + arguments = self.visitAnylist(ctx.anylist()) + gp = self.gates[gate] + if gp[0] != len(parameters) or gp[1] != len(arguments): + raise QASMParserError(f"parameter abnormality: line {ctx.start.line} " + f"{gate}\n{ctx.getText()}") + qubit_count = len(arguments) + if qubit_count == 1: + qubits = self.get_qubits(arguments) + for q in qubits: + self.execute_op(self.circuit, gate, [q], parameters) + elif qubit_count == 2: + for qubits in self.parse_qubit_group(arguments): + self.execute_op(self.circuit, gate, qubits, parameters) + elif qubit_count == 3: + qubits_0 = self.get_qubits(arguments[0]) + qubits_1 = self.get_qubits(arguments[1]) + qubits_2 = self.get_qubits(arguments[2]) + if len(qubits_0) != len(qubits_1) != len(qubits_2): + raise QASMParserError(f'The number of qubit 0/1/2 must be the same.' + f'\n{ctx.getText()}') + for i, q0 in enumerate(qubits_0): + self.execute_op(self.circuit, gate, [q0, qubits_1[i], qubits_2[i]], parameters) + else: + raise QASMParserError(f"Unknown gate: {ctx.getText()}") + + def visitExplist(self, ctx: OpenQASM2Parser.ExplistContext): + """ + Parses a list of expressions. + + Args: + ctx (OpenQASM2Parser.ExplistContext): The context of the expression list. + + ```antlr4 + explist + : exp (',' exp)* + ; + ``` + """ + expressions = [self.visitExp(exp_ctx) for exp_ctx in ctx.exp()] + return expressions + + def visitAnylist(self, ctx: OpenQASM2Parser.AnylistContext): + """ + Parses a list of identifiers or mixed lists. + + Args: + ctx (OpenQASM2Parser.AnylistContext): The context of the anylist. + + ```antlr4 + anylist + : idlist + | mixedlist + ; + ``` + """ + if ctx.idlist(): + return self.visitIdlist(ctx.idlist()) + if ctx.mixedlist(): + return self.visitMixedlist(ctx.mixedlist()) + raise QASMParserError(f"Unknown op: {ctx.getText()}") + + def visitIdlist(self, ctx: OpenQASM2Parser.IdlistContext): + """ + Parses a list of identifiers. + + Args: + ctx (OpenQASM2Parser.IdlistContext): The context of the identifier list. + + ```antlr4 + idlist + : ID (',' ID)* + ; + ``` + """ + ids = [id_tok.getText() for id_tok in ctx.ID()] + return ids + + # pylint: disable=too-many-return-statements + # def visitExp(self, ctx: OpenQASM2Parser.ExpContext): + # """ + # Parses expressions, including numerical values and mathematical operations. + # + # Args: + # ctx (OpenQASM2Parser.ExpContext): The context of the expression. + # + # ```antlr4 + # exp + # : real + # | nninteger + # | 'pi' + # | ID + # | exp '+' exp + # | exp '-' exp + # | exp '*' exp + # | exp '/' exp + # | '-' exp + # | exp '^' exp + # | '(' exp ')' + # | unaryop '(' exp ')' + # ; + # ``` + # """ + # child_count = ctx.getChildCount() + # if ctx.real(): + # return round(float(ctx.real().getText()), self.precision) + # if ctx.nninteger(): + # return int(ctx.nninteger().getText()) + # if child_count == 1 and ctx.getText() == 'pi': + # return round(np.pi, self.precision) + # if ctx.ID(): + # return ctx.ID().getText() + # if child_count == 3: + # if ctx.getChild(0).getText() == '(' and ctx.getChild(2).getText() == ')': + # return self.visitExp(ctx.exp(0)) + # left = self.visitExp(ctx.exp(0)) + # op = ctx.getChild(1).getText() + # right = self.visitExp(ctx.exp(1)) + # if op not in self.operators: + # raise QASMParserError(f'Unknown op: {op}\n{ctx.getText()}') + # # exp = f'{left} {op} {right}' + # return round(self.operators[op](left, right), self.precision) + # # return exp + # if child_count == 2 and ctx.getChild(0).getText() == '-': + # # Unary minus + # exp = self.visitExp(ctx.exp(0)) + # return exp * -1 + # if unaryop := ctx.unaryop(): + # func = unaryop.getText() + # exp = self.visitExp(ctx.exp(0)) + # if func not in self.functions: + # raise QASMParserError(f'Unknown op function: {func}\n{ctx.getText()}') + # return round(self.functions[func](exp), self.precision) + # raise QASMParserError(f'Unknown op: {ctx.getText()}') + + def visitExp(self, ctx: OpenQASM2Parser.ExpContext): + exp = self.visitAdditiveExp(ctx.additiveExp()) + if isinstance(exp, sp.Basic): + exp = sp.simplify(exp) + if exp.is_real: + return round(float(exp.evalf()), self.precision) + return exp + + def visitAdditiveExp(self, ctx: OpenQASM2Parser.AdditiveExpContext): + exp = self.visitMultiplicativeExp(ctx.multiplicativeExp()) + if additive_exp := ctx.additiveExp(): + additive_exp = self.visitAdditiveExp(additive_exp) + op = ctx.getChild(1).getText() + if op == '+': + exp = additive_exp + exp + elif op == '-': + exp = additive_exp - exp + return exp + + def visitMultiplicativeExp(self, ctx: OpenQASM2Parser.MultiplicativeExpContext): + exp = self.visitExponentialExp(ctx.exponentialExp()) + if multiplicative_exp := ctx.multiplicativeExp(): + multiplicative_exp = self.visitMultiplicativeExp(multiplicative_exp) + op = ctx.getChild(1).getText() + if op == '*': + exp = multiplicative_exp * exp + elif op == '/': + exp = multiplicative_exp / exp + return exp + + def visitExponentialExp(self, ctx: OpenQASM2Parser.ExponentialExpContext): + exp = self.visitUnaryExp(ctx.unaryExp()) + if exponential_exp := ctx.exponentialExp(): + exp = exp ** self.visitExponentialExp(exponential_exp) + return exp + + def visitUnaryExp(self, ctx: OpenQASM2Parser.UnaryExpContext): + if exp := ctx.primaryExp(): + return self.visitPrimaryExp(exp) + if exp := ctx.unaryExp(): + return 0 - self.visitUnaryExp(exp) + + def visitPrimaryExp(self, ctx: OpenQASM2Parser.PrimaryExpContext): + token = ctx.getText() + if real := ctx.real(): + return sp.Float(real.getText(), self.precision) + if nninteger := ctx.nninteger(): + return sp.Integer(nninteger.getText()) + if token == 'pi': + return sp.pi + if unaryop := ctx.unaryop(): + func = unaryop.getText() + if func not in self.functions: + raise QASMParserError(f'Unknown op function: {func}\n{ctx.getText()}') + exp = self.visitExp(ctx.exp()) + return self.functions[func](exp) + if exp := ctx.exp(): + return self.visitExp(exp) + if id_ := ctx.ID(): + return sp.symbols(id_.getText()) + + def visitMixedlist(self, ctx: OpenQASM2Parser.MixedlistContext): + """ + Parses mixed lists of identifiers with optional indices. + + Args: + ctx (OpenQASM2Parser.MixedlistContext): The context of the mixed list. + + ```antlr4 + mixedlist + : ID ('[' nninteger ']')? (',' ID ('[' nninteger ']')?)* + ; + ``` + """ + result = [] + i = 0 + child_count = ctx.getChildCount() + + while i < child_count: + id_token = ctx.getChild(i) + id_name = id_token.getText() + i += 1 + index = None + if i < child_count and ctx.getChild(i).getText() == '[': + i += 1 # 跳过 '[' + nninteger_token = ctx.getChild(i) + index = int(nninteger_token.getText()) + i += 2 + if index is not None: + result.append(f'{id_name}_{index}') + else: + result.append(id_name) + if i < child_count and ctx.getChild(i).getText() == ',': + i += 1 # 跳过 ',' + return result + + def visitArgument(self, ctx: OpenQASM2Parser.ArgumentContext) -> str | list[str]: + """ + Parses gate arguments, which can be identifiers with optional indices. + + Args: + ctx (OpenQASM2Parser.ArgumentContext): The context of the argument. + + ```antlr4 + argument + : ID + | ID '[' nninteger ']' + ; + ``` + """ + aid = ctx.ID().getText() + if index := ctx.nninteger(): + return [f'{aid}_{index.getText()}'] + return aid + + def get_qubits(self, qubits: str | list[str]) -> list[int]: + """ + Retrieves qubit indices for a given qubit identifier. + + Args: + qubits (str | list[str]): The qubit identifier. + """ + if isinstance(qubits, str): + qubits = [qubits] + target = [] + for qubit in qubits: + if '_' in qubit: + if qubit not in self.qubits: + raise QASMParserError(f"Qubit not defined: {qubit}") + target.append(self.qubits.get(qubit)) + continue + for q, i in self.qubits.items(): + if q.startswith(f'{qubit}_'): + target.append(i) + return target + + def get_clbits(self, clbit: str) -> list[int]: + """ + Retrieves classical bit indices for a given classical bit identifier. + + Args: + clbit (str): The classical bit identifier. + """ + if '_' in clbit: + return [self.clbits.get(clbit)] + return [i for q, i in self.clbits.items() if q.startswith(f'{clbit}_')] + + @staticmethod + def execute_op(circuit: Circuit, gate: str, qubits: list[int], parameters: list[str | float]): + """ + Executes a gate operation on the circuit. + + Args: + circuit (Circuit): The cqlib Circuit object. + gate (str): The name of the gate. + qubits (list[int]): The indices of qubits the gate acts on. + parameters (list): The parameters for the gate operation. + """ + match gate: + case 'u3': + circuit.u(qubits[0], *parameters) + case 'u2': + circuit.u(qubits[0], np.pi / 2, *parameters) + case 'u1': + circuit.u(qubits[0], 0, 0, *parameters) + case 'u': + circuit.u(qubits[0], *parameters) + case 'cx': + circuit.cx(qubits[0], qubits[1]) + case 'id': + circuit.i(qubits[0], I_DURATION) + case 'x': + circuit.x(qubits[0]) + case 'y': + circuit.y(qubits[0]) + case 'z': + circuit.z(qubits[0]) + case 'h': + circuit.h(qubits[0]) + case 'p': + circuit.u(qubits[0], 0, 0, *parameters) + case 's': + circuit.s(qubits[0]) + case 'sdg': + circuit.sd(qubits[0]) + case 'sx': + circuit.x2p(qubits[0]) + case 'sxdg': + circuit.x2m(qubits[0]) + case 't': + circuit.t(qubits[0]) + case 'tdg': + circuit.td(qubits[0]) + case 'rx': + circuit.rx(qubits[0], *parameters) + case 'ry': + circuit.ry(qubits[0], *parameters) + case 'rz': + circuit.rz(qubits[0], *parameters) + case 'cz': + circuit.cz(qubits[0], qubits[1]) + case 'cy': + circuit.cy(qubits[0], qubits[1]) + case 'ch': + qubit_1 = qubits[1] + circuit.ry(qubit_1, -np.pi / 4) + circuit.rz(qubit_1, np.pi / 2) + circuit.cz(qubits[0], qubit_1) + circuit.rz(qubit_1, -np.pi / 2) + circuit.ry(qubit_1, np.pi / 4) + case 'swap': + circuit.swap(qubits[0], qubits[1]) + case 'cswap': + circuit.cx(qubits[2], qubits[1]) + circuit.ccx(qubits[0], qubits[1], qubits[2]) + circuit.cx(qubits[2], qubits[1]) + case 'ccx': + circuit.ccx(qubits[0], qubits[1], qubits[2]) + case 'crx': + circuit.crx(qubits[0], qubits[1], *parameters) + case 'cry': + circuit.cry(qubits[0], qubits[1], *parameters) + case 'crz': + circuit.crz(qubits[0], qubits[1], *parameters) + case 'csx': + circuit.rz(qubits[0], math.pi / 4) + circuit.h(qubits[1]) + circuit.cx(qubits[0], qubits[1]) + circuit.rz(qubits[1], -math.pi / 4) + circuit.cx(qubits[0], qubits[1]) + circuit.rz(qubits[1], math.pi / 4) + circuit.h(qubits[1]) + case _: + raise QASMParserError(f'Unknown gate: {gate}') + + def parse_qubit_group(self, any_list: list[str]): + """ + qreg q[3]; + qreg p[3]; + + 1. cx p, q; + CX p0, q0; + CX p1, q1; + CX p2, q2; + 2. cx p, q[0]; + CX p0, q0; + CX p1, q0; + CX p2, q0; + 3. h p; + H p0; + H p1; + H p2; + """ + qubit_group = [] + result = [] + max_count = 0 + for q in any_list: + qubits = self.get_qubits(q) + max_count = max(len(qubits), max_count) + qubit_group.append(qubits) + for group in qubit_group: + if 1 != len(group) != max_count: + raise QASMParserError(f'Non matching quantum registers of length: ' + f'{[len(s) for s in qubit_group]}') + if len(group) == 1: + result.append(group * max_count) + else: + result.append(group) + return zip(*result) + + def parse( + self, + qasm_file: str = None, + qasm_str: str = None, + ) -> Circuit: + """ + Parses an OpenQASM 2.0 input (either a file or a string) + and constructs the corresponding Circuit. + + Args: + qasm_file (str): Path to the OpenQASM 2.0 file. + qasm_str (str): The OpenQASM 2.0 circuit. + """ + if qasm_file: + input_stream = FileStream(qasm_file) + elif qasm_str: + input_stream = InputStream(qasm_str) + else: + raise QASMParserError('Either `qasm_file` or `qasm_str` must be provided.') + lexer = OpenQASM2Lexer(input_stream) + token_stream = CommonTokenStream(lexer) + parser = OpenQASM2Parser(token_stream) + tree = parser.mainprogram() + + self.visit(tree) + return self.circuit diff --git a/cqlib/utils/qasm2/dump.py b/cqlib/utils/qasm2/dump.py new file mode 100644 index 0000000000000000000000000000000000000000..ca28a244f45dbcdc8e83d841e0ce8ac0b2054a19 --- /dev/null +++ b/cqlib/utils/qasm2/dump.py @@ -0,0 +1,204 @@ +# This code is part of cqlib. +# +# Copyright (C) 2024 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + + +""" +This module provides functionality to convert a cqlib Circuit object into +an OpenQASM 2.0 formatted string or file. It includes handling of various +quantum gates, measurements, and optional annotations. + +Functions: +- dumps(circuit, header, precision, annotate): Convert a Circuit to an OpenQASM string. +- dump(circuit, filename): Write the OpenQASM string of a Circuit to a file. +""" + +import math + +from cqlib import _version +from cqlib.circuits.circuit import Circuit +from cqlib.circuits.instruction import Instruction +from cqlib.circuits.parameter import Parameter +from cqlib.exceptions import CqlibError + +I_DURATION = 60 + + +def dumps( + circuit: Circuit, + header: str = None, + precision: int = 10, + annotate: bool = False, +) -> str: + """ + Convert a cqlib Circuit object to an OpenQASM 2.0 formatted string. + + Args: + circuit (Circuit): The quantum circuit to convert. + header (str, optional): Additional header information to include as comments. + precision (int, optional): The number of decimal places to round parameters to. + annotate (bool, optional): If True, include original instructions as comments. + + Returns: + str: The OpenQASM 2.0 representation of the circuit. + """ + qubits = circuit.qubits + header = f"// Generated by Cqlib v{_version.__version__}\n" \ + f"{('//' + header) if header else ''}\n" \ + "OPENQASM 2.0;\n" \ + 'include "qelib1.inc";\n' + + qreg_def = f'qreg q[{len(qubits)}];\n' + if annotate: + qreg_def = f'// Qubits: {",".join(map(str, qubits))}\n' + qreg_def + + measure_qubits = [] + operations = [] + + for ins in circuit.instruction_sequence: + if annotate: + operations.append(f'// {str(ins)}') + + qs = [qubits.index(q) for q in ins.qubits] + ps = [] + for p in ins.instruction.params: + if isinstance(p, Parameter): + raise CqlibError('OpenQASM2.0 do not support parameter') + ps.append(round(p, precision)) + + if ins.instruction.name == 'M': + measure_qubits.extend(ins.qubits) + operations.append(f'measure q[{qs[0]}] ' + f'-> c[{len(measure_qubits) - 1}];') + continue + if item := translate(ins.instruction, qs, ps): + operations.append(item) + + creg_def = f'creg c[{len(measure_qubits)}];\n' + if annotate: + creg_def = f'// Classical register, obtained based on the measured qubits.\n' \ + f'// {",".join(map(str, measure_qubits))}\n' \ + f'creg c[{len(measure_qubits)}];\n' + creg_def + + return '\n'.join([ + header, + qreg_def, + creg_def, + *operations, + ]) + + +def dump( + circuit: Circuit, + filename: str, + precision: int = 10, + annotate: bool = False +) -> None: + """ + Write the OpenQASM 2.0 representation of a cqlib Circuit to a file. + + Args: + circuit (Circuit): The quantum circuit to convert and write. + filename (str): The path to the file where the OpenQASM code will be written. + precision (int, optional): The number of decimal places to round parameters to. + annotate (bool, optional): If True, include original instructions as comments. + """ + qasm = dumps(circuit, precision=precision, annotate=annotate) + with open(filename, 'w', encoding='utf-8') as fp: + fp.write(qasm) + + +def translate( + instruction: Instruction, + qubits: list[int], + params: list[int | float] +) -> str: + """ + Translate a quantum instruction into its corresponding OpenQASM 2.0 representation. + + Args: + instruction (Instruction): The quantum gate instruction to be translated. It must + have the following attributes: + qubits (list[int]): A list of qubit indices that the instruction is applied to. + The order of qubits should match the gate's requirements (e.g., control and + target qubits for two-qubit gates). + params (list[int | float]): A list of numerical parameters associated with the + instruction. These parameters are used for parameterized gates such as rotations + (e.g., RX, RY, RZ) and must be provided in the order expected by the gate. + """ + gate = instruction.name.lower() + item = '' + match instruction.name: + case 'TD': + item = f'tdg q[{qubits[0]}];' + case 'SD': + item = f'sdg q[{qubits[0]}];' + case 'H' | 'X' | 'Y' | 'Z' | 'S' | 'T': + item = f'{gate} q[{qubits[0]}];' + case 'RX' | 'RY' | 'RZ': + item = f'{gate}({params[0]}) q[{qubits[0]}];' + case 'CX' | 'CY' | 'CZ': + item = f'{gate} q[{qubits[0]}], q[{qubits[1]}];' + case 'CCX': + item = f'{gate} q[{qubits[0]}], q[{qubits[1]}], q[{qubits[2]}];' + case 'U': + item = f'u3({",".join(map(str, params))}) q[{qubits[0]}];' + case 'X2P': + item = f'sx q[{qubits[0]}];' + case 'X2M': + item = f'sxdg q[{qubits[0]}];' + case 'Y2P': + item = f'ry(pi/2) q[{qubits[0]}];' + case 'Y2M': + item = f'ry(-pi/2) q[{qubits[0]}];' + case 'XY': + item = f'rz(pi/2 - {params[0]}) q[{qubits[0]}];\n' \ + f'y q[{qubits[0]}];\n' \ + f'rz({params[0]} - pi/2) q[{qubits[0]}];' + case 'XY2P': + item = f'rz(pi/2 - {params[0]}) q[{qubits[0]}];\n' \ + f'ry(pi/2) q[{qubits[0]}];\n' \ + f'rz({params[0]} - pi/2) q[{qubits[0]}];' + case 'XY2M': + item = f'rz(-pi/2 - {params[0]}) q[{qubits[0]}];\n' \ + f'ry(pi/2) q[{qubits[0]}];\n' \ + f'rz({params[0]} + pi/2) q[{qubits[0]}];' + case 'RXY': + item = f"u3({params[1]},{params[0]} - pi/2,pi/2 - {params[0]}) q[{qubits[0]}];" + case 'CRX': + item = f's q[{qubits[1]}];\n' \ + f'cx q[{qubits[0]}], q[{qubits[1]}];\n' \ + f'ry(-{params[0]} / 2) q[{qubits[1]}];\n' \ + f'cx q[{qubits[0]}], q[{qubits[1]}];\n' \ + f'ry({params[0]} / 2) q[{qubits[1]}];\n' \ + f'sdg q[{qubits[1]}];' + case 'CRY': + item = f'ry({params[0]} / 2) q[{qubits[1]}];\n' \ + f'cx q[{qubits[0]}], q[{qubits[1]}];\n' \ + f'ry(-{params[0]} / 2) q[{qubits[1]}];\n' \ + f'cx q[{qubits[0]}], q[{qubits[1]}];' + case 'CRZ': + item = f'rz({params[0]} / 2) q[{qubits[1]}];\n' \ + f'cx q[{qubits[0]}], q[{qubits[1]}];\n' \ + f'rz(-{params[0]} / 2) q[{qubits[1]}];\n' \ + f'cx q[{qubits[0]}], q[{qubits[1]}];' + case 'SWAP': + item = f'cx q[{qubits[0]}], q[{qubits[1]}];\n' \ + f'cx q[{qubits[1]}], q[{qubits[0]}];\n' \ + f'cx q[{qubits[0]}], q[{qubits[1]}];' + case 'B': + item = f'barrier {",".join([f"q[{q}]" for q in qubits])};' + case 'I': + t = instruction.params[0] + for _ in range(math.ceil(t // I_DURATION)): + item = f'id q[{qubits[0]}];' + return item diff --git a/cqlib/utils/qasm2/load.py b/cqlib/utils/qasm2/load.py new file mode 100644 index 0000000000000000000000000000000000000000..a116ff9200936db2f549b53ba3210573e94f280f --- /dev/null +++ b/cqlib/utils/qasm2/load.py @@ -0,0 +1,47 @@ +# This code is part of cqlib. +# +# Copyright (C) 2024 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +OpenQASM 2.0 Program Loader + +This module provides functions to load and parse OpenQASM 2.0 programs, +converting them into `cqlib` Circuit objects. It supports loading from +both file paths and direct OpenQASM 2.0 strings, making it convenient +to handle OpenQASM programs from different sources. +""" +import os + +from cqlib.circuits.circuit import Circuit +from ._parse import OpenQASM2Converter + + +def load(filename: str | os.PathLike, precision: int = 10) -> Circuit: + """ + Load an OpenQASM 2.0 program from a file and convert it to a Circuit. + + Args: + filename(str | os.PathLike): The OpenQASM 2 program file name. + precision (int, optional): The number of decimal places to round parameters to. + """ + return OpenQASM2Converter(precision=precision).parse(qasm_file=filename) + + +def loads(qasm: str, precision: int = 10) -> Circuit: + """ + Load an OpenQASM 2.0 program from a string and convert it to a Circuit. + + Args: + qasm (str): OpenQASM 2.0 program as a string. + precision (int, optional): The number of decimal places to round parameters to. + """ + return OpenQASM2Converter(precision=precision).parse(qasm_str=qasm) diff --git a/cqlib/utils/qasm_to_qcis/rules.py b/cqlib/utils/qasm_to_qcis/rules.py index afdb689ec309afd4f3a9c13ee781d7e9d187314f..7e04ca2d43cd6bf05d198bad02ed9409e75711ba 100644 --- a/cqlib/utils/qasm_to_qcis/rules.py +++ b/cqlib/utils/qasm_to_qcis/rules.py @@ -151,7 +151,7 @@ class NativeQcisRules: def u3(input_instruction: Instruction): return [ Instruction( - "rz", input_instruction.qubit_index, [input_instruction.arguments[1]] + "rz", input_instruction.qubit_index, [input_instruction.arguments[2]] ), Instruction("x2p", input_instruction.qubit_index), Instruction( @@ -159,7 +159,7 @@ class NativeQcisRules: ), Instruction("x2m", input_instruction.qubit_index), Instruction( - "rz", input_instruction.qubit_index, [input_instruction.arguments[2]] + "rz", input_instruction.qubit_index, [input_instruction.arguments[1]] ), ] diff --git a/cqlib/visualization/__init__.py b/cqlib/visualization/__init__.py index 2bc382bcb1d9bfb6d0f2259e419d2e68ac6a597d..166d24c83dd5688cc89118f4b89284bb286ea419 100644 --- a/cqlib/visualization/__init__.py +++ b/cqlib/visualization/__init__.py @@ -15,6 +15,12 @@ visualization module defines methods to visualize qcis circuits and plotting experiment results. """ -from .circuit import draw_circuit +from .circuit import draw_text, draw_mpl, TextDrawer, MatplotlibDrawer from .gplot import draw_gplot from .result import draw_histogram + +__all__ = [ + 'draw_text', 'draw_mpl', 'TextDrawer', 'MatplotlibDrawer', + 'draw_gplot', + 'draw_histogram' +] diff --git a/cqlib/visualization/circuit.py b/cqlib/visualization/circuit.py deleted file mode 100644 index a28c5e093ee1074b9c60db5ac788454e99530711..0000000000000000000000000000000000000000 --- a/cqlib/visualization/circuit.py +++ /dev/null @@ -1,432 +0,0 @@ -# This code is part of cqlib. -# -# Copyright (C) 2024 China Telecom Quantum Group, QuantumCTek Co., Ltd., -# Center for Excellence in Quantum Information and Quantum Physics. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -from ..exceptions import VisualizationError -import os -import re -from typing import Optional -import numpy as np -import matplotlib.pyplot as plt -import matplotlib.image as mpimg -from matplotlib.patches import Circle, Rectangle -from collections import defaultdict - -""" -Quantum circuit visualization -""" -LINE_GATE = {"CZ", "ISWAP", "SWAP", "CCZ", "CCX"} -PARAM_GATE = {"RZ", "RY", "RXY", "RX", "U3", "XY2M", "XY2P"} -MULTIPLE_QUBIT_GATE = {'B', 'M'} - - -def _processing_circuit(qcis_str): - """ - Parses and processes circuit strings in preparation for drawing - """ - draw_data = defaultdict(list) - qcis_list = qcis_str.upper().split("\n") - # 双比特门的x坐标集合, 在这个集合里面的x轴坐标除了已添加的双比特门,不能再添加其他门 - c_line_number = [] - for index, qcis in enumerate(qcis_list): - if c_line_number: - for key, value in draw_data.items(): - if len(value) - 1 < max(c_line_number): - for _ in range(max(c_line_number) - len(value)): - value.append(None) - value.append("") - if qcis: - try: - qcis_split = qcis.split(" ") - line_num = validate_and_extract_bit(qcis_split[1]) - bit_gate_name = qcis_split[0] - # 特殊处理,此类型属于连线类型的门 - if any( - substr in bit_gate_name - for substr in LINE_GATE - ): - # 此类型属于三个点连线 - if bit_gate_name in ["CCZ", "CCX"]: - line_num = validate_and_extract_bit(qcis_split[3]) - another_line_number = ( - ("".join(filter(str.isdigit, qcis_split[1]))) - + " " - + ("".join(filter(str.isdigit, qcis_split[2]))) - ) - else: # 此类型属于两个点连线 - line_num = validate_and_extract_bit(qcis_split[2]) - another_line_number = int( - "".join(filter(str.isdigit, qcis_split[1])) - ) - bit_gate_name = " ".join([bit_gate_name, str(another_line_number)]) - _multiple_gate_process( - draw_data=draw_data, - bit_gate_name=bit_gate_name, - line_num=line_num, - c_line_number=c_line_number, - ) - # 特殊处理 此类型属于带有参数的门 - elif any( - substr in bit_gate_name - for substr in PARAM_GATE - ): - if "RXY" in bit_gate_name: - bit_gate_name = " ".join( - [bit_gate_name, qcis_split[2], qcis_split[3]] - ) - elif "U3" in bit_gate_name: - bit_gate_name = " ".join( - [bit_gate_name, qcis_split[2], qcis_split[3], qcis_split[4]] - ) - else: - bit_gate_name = " ".join([bit_gate_name, qcis_split[2]]) - _normal_gate_process( - draw_data=draw_data, - bit_gate_name=bit_gate_name, - line_num=line_num, - c_line_number=c_line_number, - ) - elif bit_gate_name in MULTIPLE_QUBIT_GATE: - line_num_list = [validate_and_extract_bit(qcis_split[i]) for i in range(1, len(qcis_split))] - line_num_gate_index_dict = {} - for line_num in line_num_list: - gate_line_index = _normal_gate_process( - draw_data=draw_data, - bit_gate_name=bit_gate_name, - line_num=line_num, - c_line_number=c_line_number, - ) - line_num_gate_index_dict[line_num] = gate_line_index - if bit_gate_name == 'B': - _align_barrier(draw_data, line_num_gate_index_dict) - else: - _normal_gate_process( - draw_data=draw_data, - bit_gate_name=bit_gate_name, - line_num=line_num, - c_line_number=c_line_number, - ) - except Exception as e: - raise e - # raise VisualizationError( - # f"线路可视化错误---线路第{index}行:'{qcis}' 存在问题" - # ) - simplify_draw_data(draw_data) - return draw_data - - -def validate_and_extract_bit(qcis_bit: Optional[str]): - """ - Verify qcis_bit string format is correct and extract the number of bit lines - """ - pattern = r"^Q(\d+)$" - match = re.match(pattern, qcis_bit) - if match: - number = match.group(1) - return int(number) - else: - raise Exception() - - -def _align_barrier(draw_data, line_num_gate_index_dict): - max_gate_index = max(line_num_gate_index_dict.values()) - for line_num, gate_index in line_num_gate_index_dict.items(): - if gate_index < max_gate_index: - for _ in range(max_gate_index - gate_index): - draw_data[line_num].insert(gate_index, '') - - -def simplify_draw_data(draw_data): - min_len = np.inf - min_line_num = None - for line_num, line_data in draw_data.items(): - if len(line_data) < min_len: - min_len = len(line_data) - min_line_num = line_num - for i in range(min_len - 1, -1, -1): - if all(not (line_data[i]) for line_data in draw_data.values()): - for line_data in draw_data.values(): - line_data.pop(i) - - -def _normal_gate_process(draw_data, bit_gate_name, line_num, c_line_number): - """ - processes bit gates with no special drawing, such as X, Y, M gates.... - """ - line_data = draw_data.get(line_num) - if line_data: - for line_data_index, value in enumerate(line_data): - if value is None and line_data_index not in c_line_number: - line_data[line_data_index] = bit_gate_name - return line_data_index - draw_data[line_num].append(bit_gate_name) - return len(draw_data[line_num]) - 1 - - -def _multiple_gate_process(draw_data, bit_gate_name, line_num, c_line_number): - """ - processes bits that need to be wired, e.g. cz, swap,ccz gate..... - """ - # 目前最长的一条线的长度(连线类型的比特门放到此长度之后) - line_max_size = 0 - for data in draw_data.values(): - if len(data) > line_max_size: - line_max_size = len(data) - line_data = draw_data.get(line_num) - if line_data: - if len(line_data) < line_max_size: - for _ in range(line_max_size - len(line_data)): - line_data.append("") - else: - line_data = [] - for _ in range(line_max_size): - line_data.append("") - draw_data[line_num] = line_data - - for index in range(0, len(line_data)): - if line_data[index] is None: - line_data[index] = "" - draw_data[line_num].append(bit_gate_name) - - another_line_number_list = bit_gate_name.split(" ") - for index in range(1, len(another_line_number_list)): - another_line_number = int(another_line_number_list[index]) - another_line_data = draw_data.get(another_line_number) - if another_line_data: - if len(another_line_data) < line_max_size: - for _ in range(line_max_size - len(another_line_data)): - another_line_data.append("") - else: - another_line_data = draw_data[another_line_number] = [] - for _ in range(line_max_size): - draw_data[another_line_number].append("") - draw_data[another_line_number].append("") - - for i in range(0, len(another_line_data)): - if another_line_data[i] is None: - another_line_data[i] = "" - c_line_number.append(line_max_size) - - -def draw_circuit( - qcis_str: Optional[str], - file_name: Optional[str] = None, - scale: Optional[float] = 1.0, - interactive: Optional[bool] = False, -): - """Draw a quantum circuit based on matplotlib. - - Args: - qcis_str: A visual qcis string is required - file_name: The address to save the image.Default to 'None' - scale: Scale of image to draw .The value must be greater than 0,if it is greater than 1.0, it is enlarged, and if it is less than 1.0, it is shrunk. Default to '1.0' - interactive:Displays the created image.Default to 'False' - - Returns: - matplotlib.figure.Figure: a matplotlib figure object for the qcis circuit diagram - - Raises: - VisualizationError: There is a problem with the line format - ValueError: 'scale' parameter range error - """ - draw_data = _processing_circuit(qcis_str) - if not draw_data: - raise VisualizationError("解析qcis线路失败") - if scale <= 0: - raise ValueError("scale参数值不能小于等于0") - total_num_line = len(draw_data) - x_len = max([len(value) for value in draw_data.values()]) - y_len = total_num_line + 2 - # 设置画布大小 - plt.figure(figsize=((x_len + 1), y_len), dpi=150 * scale) - plt.xlim(0, (x_len + 1)) - plt.ylim(0, y_len) - y_values = range(1, total_num_line + 1) - qubit_index_list = sorted([key for key in draw_data]) - line_num_list = range(0, total_num_line) - map_q2line = dict(zip(qubit_index_list, line_num_list)) - for y, line_num in zip(reversed(y_values), line_num_list): - q_index = qubit_index_list[line_num] - line_data = draw_data.get(q_index) - # 画一个水平线 - plt.axhline(y=y, color="black", linestyle="-", zorder=0, linewidth=3.5) - # 在水平线左边添加文字信息 - rect = Rectangle( - (0, (y - 0.25)), 0.6, 0.505, edgecolor="none", facecolor="white" - ) - plt.gca().add_patch(rect) - plt.annotate( - f"q[{q_index}]", - (0.1 - (q_index // 10) * 0.1, y), - color="black", - fontsize=13, - fontweight="bold", - ha="left", - va="center", - ) - if line_data: - for index, data in enumerate(line_data): - index = index + 0.75 - if not data or "two_gate" in data: - continue - image_name = data - - # 特殊处理-cz iswap swap - if any( - substr in data for substr in LINE_GATE - ): - data_split = data.split(" ") - another_line = map_q2line[int(data_split[1])] - image_name = data_split[0] - another_line1 = None - color = "#D6271D" - if image_name in ["CCZ", "CCX"]: - another_line1 = map_q2line[int(data_split[2])] - color = "#1A4DF2" - # 画一条竖线 - plt.axvline( - x=(index + 0.3), - ymin=(y - (another_line - line_num)) / y_len, - ymax=y / y_len, - color=color, - linewidth=3.5, - ) - if another_line1 is not None: - plt.axvline( - x=(index + 0.3), - ymin=(y - (another_line1 - line_num)) / y_len, - ymax=y / y_len, - color=color, - linewidth=3.5, - ) - ##读取图片 - current_dir = os.path.dirname(os.path.abspath(__file__)) - image_folder = "image" - image_path = os.path.join( - current_dir, image_folder, f"{image_name}.png" - ) - img = mpimg.imread(image_path) - ##将图片置于水平线上 - plt.imshow( - img, - extent=[index, (index + 0.6), (y - 0.3), (y + 0.3)], - aspect="auto", - zorder=3, - alpha=1, - ) - # iswap和swap每个比特都是图片 - if image_name in ["ISWAP", "SWAP"]: - - plt.imshow( - img, - extent=[ - index, - (index + 0.6), - ((y - (another_line - line_num)) - 0.3), - ((y - (another_line - line_num)) + 0.3), - ], - aspect="auto", - zorder=3, - alpha=1, - ) - else: - # 画一个圆 - circle = Circle( - ((index + 0.3), (y - (another_line - line_num))), - 0.12, - color=color, - ) - plt.gca().add_patch(circle) - if another_line1 is not None: - circle = Circle( - ((index + 0.3), (y - (another_line1 - line_num))), - 0.12, - color=color, - ) - plt.gca().add_patch(circle) - - # 特殊处理-rx ry rxy rz u3 - elif any(substr in data for substr in PARAM_GATE): - bit_gate_name = data.split(" ")[0] - if "RXY" in data: - angle1 = str(round(float(data.split(" ")[1]), 4)) - angle2 = str(round(float(data.split(" ")[2]), 4)) - angle = angle1 + "_" + angle2 - elif "U3" in data: - angle1 = str(round(float(data.split(" ")[1]), 4)) - angle2 = str(round(float(data.split(" ")[2]), 4)) - angle3 = str(round(float(data.split(" ")[3]), 4)) - angle = angle1 + "_" + angle2 + "\n" + "_" + angle3 - else: - angle = str(round(float(data.split(" ")[1]), 4)) - if bit_gate_name == "RZ": - color = "#D6271D" - else: - color = "#1A4DF2" - rect = Rectangle( - ((index - 0.1), (y - 0.3)), - 0.8, - 0.6, - linewidth=1, - edgecolor="none", - facecolor=color, - ) - plt.gca().add_patch(rect) - plt.annotate( - bit_gate_name, - ((index + 0.3), (y + 0.1)), - color="white", - fontsize=18, - ha="center", - va="center", - ) - if len(angle) > 12: - fontsize = 8 - elif 8 < len(angle) <= 12: - fontsize = 10 - else: - fontsize = 12 - - plt.annotate( - angle, - ((index + 0.3), (y - 0.15)), - color="white", - fontsize=fontsize, - ha="center", - va="center", - ) - - else: - # 读取图片 - current_dir = os.path.dirname(os.path.abspath(__file__)) - image_folder = "image" - image_path = os.path.join( - current_dir, image_folder, f"{image_name}.png" - ) - img = mpimg.imread(image_path) - # 将图片置于水平线上 - plt.imshow( - img, - extent=[index, (index + 0.6), (y - 0.3), (y + 0.3)], - aspect="auto", - zorder=3, - alpha=1, - ) - plt.axis("off") - - plt.subplots_adjust(top=1, bottom=0, left=0, right=1) - if file_name is not None: - plt.savefig(f"{file_name}.png") - fig = plt.gcf() - if interactive: - plt.show() - return fig diff --git a/cqlib/visualization/circuit/__init__.py b/cqlib/visualization/circuit/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..9617dff35ba12067344a2092d762a6f35e427e98 --- /dev/null +++ b/cqlib/visualization/circuit/__init__.py @@ -0,0 +1,24 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Quantum circuit visualization +""" + +from .text import draw_text, TextDrawer +from .mpl import MatplotlibDrawer, draw_mpl + +__all__ = [ + 'draw_text', 'TextDrawer', + 'draw_mpl', 'MatplotlibDrawer', +] diff --git a/cqlib/visualization/circuit/base.py b/cqlib/visualization/circuit/base.py new file mode 100644 index 0000000000000000000000000000000000000000..d3239d3884215605c8ffa2e62563cdbe635a903c --- /dev/null +++ b/cqlib/visualization/circuit/base.py @@ -0,0 +1,183 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Quantum circuit visualization +""" + +import logging +from abc import ABC, abstractmethod +from enum import Enum +from typing import Iterator + +from cqlib.circuits.bit import Bit +from cqlib.circuits.qubit import Qubit +from cqlib.circuits.circuit import Circuit +from cqlib.circuits.dag import circuit_to_dag, topological_layers +from cqlib.circuits.instruction_data import InstructionData +from cqlib.exceptions import CqlibError + +logger = logging.getLogger('cqlib.vis') + + +class BoxChar(str, Enum): + """ + Unicode box-drawing characters collection for circuit visualization + """ + TOP = '╵' + BOTTOM = '╷' + LEFT = '╴' + RIGHT = '╶' + TOP_BOTTOM = '│' + LEFT_RIGHT = '─' + + TOP_LEFT = '┘' + TOP_RIGHT = '└' + BOTTOM_LEFT = '┐' + BOTTOM_RIGHT = '┌' + + TOP_BOTTOM_LEFT = '┤' + TOP_BOTTOM_RIGHT = '├' + TOP_LEFT_RIGHT = '┴' + BOTTOM_LEFT_RIGHT = '┬' + TOP_BOTTOM_LEFT_RIGHT = '┼' + + DOT = '■' + CONNECT = 'X' + LEFT_ARROW = '«' + RIGHT_ARROW = '»' + + +class BaseDrawer(ABC): + """ + Abstract Quantum circuit Drawer. + """ + MIN_LINE_WIDTH = 10 + DEFAULT_LINE_WIDTH = 30 + + def __init__( + self, + circuit: Circuit, + qubit_order: list[int | Bit] | None = None, + ): + """ + Initialization Parameters + + Args: + circuit(Circuit): Quantum circuit to be visualized + qubit_order(list[int | Bit] | None): Optional list specifying the display + order of qubits + """ + self.circuit = circuit + self.qubit_order = qubit_order + self.sorted_qubits: list[Bit] = [] + self.qubit_mapping: dict[Bit, int] = {} + + self.order_qubits() + + def order_qubits(self): + """ + Determines the display order of qubits and initializes related attributes. + Uses specified qubit_order if provided (supplementing with remaining circuit qubits), + otherwise sorts qubits by their indices. Initializes: + - sorted_qubits: List of qubits in display order + - qubit_mapping: Dictionary mapping qubits to display line numbers + """ + if self.qubit_order is None: + sorted_qubits = sorted(self.circuit.qubits, key=lambda s: s.index) + else: + sorted_qubits = [q if isinstance(q, Qubit) else Qubit(q) for q in self.qubit_order] + for qubit in self.circuit.qubits: + if qubit not in sorted_qubits: + sorted_qubits.append(qubit) + self.sorted_qubits = sorted_qubits + self.qubit_mapping = {q: i * 2 + 1 for i, q in enumerate(sorted_qubits)} + + def generate_moment(self) -> Iterator[list[InstructionData]]: + """ + Generates an iterator of topological layers (moments) from the circuit DAG. + Each moment contains non-overlapping operations that can be executed in parallel. + + Yields: + list[InstructionData]: A moment containing parallel-compatible operations + + Raises: + CqlibError: If qubit conflicts are detected within a topological layer + """ + dag_circuit = circuit_to_dag(self.circuit) + for layer in topological_layers(dag_circuit): + moment = [] + used_qubits = set() + for node_id in layer: + node: InstructionData = dag_circuit.get_node_data(node_id) + if any(q in used_qubits for q in node.qubits): + raise CqlibError(f"Qubit conflict in topological layer: {node.qubits}") + used_qubits.update(node.qubits) + moment.append(node) + yield moment + + @staticmethod + def moment_to_columns(moment: list[InstructionData]) -> list[list[InstructionData]]: + """ + Organizes a moment into display columns by analyzing qubit ranges. + Operations with overlapping qubit ranges are placed in separate columns. + + Args: + moment (list[InstructionData]): A collection of operations from one topological layer + + Returns: + list[list[InstructionData]]: Column-based structure for visualization, + where each sublist represents a display column + """ + + qubit_ranges = [] + qubit_min_to_ins = {} + for ins in moment: + qubits_index = [q.index for q in ins.qubits] + qubit_min_to_ins[min(qubits_index)] = ins + qubit_ranges.append([min(qubits_index), max(qubits_index)]) + + moment_columns = [] + for qubit_range in qubit_ranges: + min_index, max_index = qubit_range + for column in moment_columns: + for item in column: + mi, ma = item + if max_index >= mi and min_index <= ma: + break + else: + column.append(qubit_range) + break + else: + moment_columns.append([qubit_range]) + return [[qubit_min_to_ins[r[0]] for r in col] for col in moment_columns] + + def qubit_line_no(self, qubit: Bit) -> int: + """ + Retrieves the display line number for a given qubit in the visualization + + Args: + qubit (Qubit): Target qubit to query + + Returns: + int: Odd-numbered line position corresponding to the qubit's display order + """ + return self.qubit_mapping[qubit] + + @abstractmethod + def drawer(self) -> str: + """ start draw lines""" + + @abstractmethod + def draw_column(self, column) -> list[list[str]]: + """ draw column lines""" diff --git a/cqlib/visualization/circuit/mpl.py b/cqlib/visualization/circuit/mpl.py new file mode 100644 index 0000000000000000000000000000000000000000..2552b51c7a085070c993676eb7035834c72ebaff --- /dev/null +++ b/cqlib/visualization/circuit/mpl.py @@ -0,0 +1,493 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Matplotlib quantum circuit drawer module +""" + +import logging +from collections.abc import Sequence +from enum import IntEnum + +from matplotlib import pyplot as plt +from matplotlib.axes import Axes +from matplotlib.figure import Figure +from matplotlib.patches import Rectangle, Arc + +from cqlib.circuits.barrier import Barrier +from cqlib.circuits.bit import Bit +from cqlib.circuits.circuit import Circuit +from cqlib.circuits.instruction_data import InstructionData +from cqlib.circuits.gates import SWAP, CZ +from cqlib.circuits.gates.gate import ControlledGate +from cqlib.circuits.measure import Measure +from cqlib.circuits.parameter import Parameter +from cqlib.circuits.qubit import Qubit +from cqlib.exceptions import CqlibError + +from .base import BaseDrawer, BoxChar +from .style import Style + +logger = logging.getLogger('cqlib.vis') + + +class ZOrder(IntEnum): + """ + Defines z-order layers for rendering circuit components. + + Ensures proper layering of visual elements (e.g., lines under boxes). + + Members: + LINE (int): Layer for quantum wires and connectors (z=10) + BOX (int): Layer for gate background boxes (z=20) + GATE (int): Layer for gate symbols and text (z=30) + """ + LINE = 10 + BOX = 20 + GATE = 30 + + +# pylint: disable=too-many-instance-attributes +class MatplotlibDrawer(BaseDrawer): + """ + Quantum circuit visualizer using Matplotlib. + + Renders circuit diagrams with customizable styling and layout. Supports + multi-qubit gates, parameterized operations, and classical registers. + """ + figure_styles = { + 'figure_color': 'white', + 'axes_color': 'white', + 'show_axis': '0', + 'margin_top': 0.88, + 'margin_bottom': 0.11, + 'margin_left': 0.125, + 'margin_right': 0.9, + 'moment_width': 0.3, + 'gate_width': 0.8, + 'gate_height': 1.2, + } + + # pylint: disable=too-many-arguments,too-many-positional-arguments + def __init__( + self, + circuit: Circuit, + qubit_order: list[int | Qubit] | None = None, + figure_styles: dict[str, str | bool | float | tuple[float | int]] | None = None, + gate_styles: dict[str, str | bool | float | tuple[float | int]] | None = None, + title: str | None = None, + fonts: str | list[str] | None = None, + filename: str | None = None, + style: str = 'default', + ): + """ + Initialization Parameters + + Args: + circuit (Circuit): Quantum circuit to visualize. + qubit_order (list[int | Qubit] | None): Custom display order for qubits. + Defaults to circuit's natural ordering. + figure_styles (dict | None): Overrides for default figure styles. + gate_styles (dict | None): Overrides for default gate rendering styles. + title (str | None): Title displayed above the circuit. + fonts (str | list[str] | None): Font family/families for text rendering. + filename (str, optional): Optional filename to save the diagram to. + """ + super().__init__(circuit, qubit_order) + + self.gate_style = Style(style, gate_styles) + if figure_styles: + self.figure_styles.update(figure_styles) + + if fonts and isinstance(fonts, str): + fonts = [fonts] + self._fonts = fonts + self._title = title + self._filename = filename + self._fig = None + self._ax = None + self._real_line_width = 1 + self._current_x = 0 + self._current_col_index = 0 + self._current_moment_index = 0 + self.column_width = [] + self.qubit_mapping = {q: i * 2 for i, q in enumerate(self.sorted_qubits)} + + def drawer(self) -> Figure: + """ + Generates complete circuit visualization. + + Processes circuit structure, computes layout, and renders all components. + + Returns: + Figure: Matplotlib figure containing circuit diagram + """ + lines = self._make_lines() + + self._fig, self._ax = self.setup_figure() + for i, qubit in enumerate(self.sorted_qubits): + self._ax.text(-0.6, i * 2, str(qubit), ha='right', va='center', + fontsize=12, color='black') + self._ax.hlines(i * 2, xmin=-self.figure_styles['moment_width'], + xmax=self._real_line_width, color='gray', linewidth=1) + self._current_x = 0 + for moment in lines: + start = self._current_x + for column in moment['moment']: + column_width = column['width'] + self._current_x += column_width / 2 + self.draw_column(column['column']) + self._current_x += column_width / 2 + # Draw light gray background area when multiple parallel gate + # columns exist in the same moment + if len(moment['moment']) > 1: + self._ax.axvspan( + xmin=start - 0.1, + xmax=self._current_x + 0.1, + color='lightgray', + alpha=0.2, + zorder=0 + ) + self._current_x += self.figure_styles['moment_width'] + if self._filename: + self._fig.savefig(self._filename, transparent=None, dpi='figure', ) + return self._fig + + def _make_lines(self): + """ + Organizes circuit moments into columns for rendering. Internal layout logic. + """ + lines = [] + + for moment in self.generate_moment(): + columns = [] + max_width = 0 + for column in self.moment_to_columns(moment): + col_width = 0 + instructions = [] + for ins in column: + ins_width = self.figure_styles['gate_width'] + if ins.instruction.params: + t = self._params_str(ins) + ins_width = max(ins_width, len(t) / 5.0 * self.figure_styles['gate_width']) + instructions.append({ + 'width': ins_width, + 'instruction': ins + }) + col_width = max(ins_width, col_width) + self._real_line_width += col_width + columns.append({ + 'width': col_width, + 'column': instructions + }) + max_width += col_width + self._real_line_width += self.figure_styles['moment_width'] + lines.append({ + 'width': max_width, + 'moment': columns + }) + return lines + + def setup_figure(self) -> tuple[Figure, Axes]: + """ + Configures Matplotlib figure and axes with specified styles. + + Returns: + tuple[Figure, Axes]: Configured figure and axes objects. + """ + fig, ax = plt.subplots(figsize=(self._real_line_width, len(self.sorted_qubits))) + fig.set_facecolor(self.figure_styles['figure_color']) + ax.set_facecolor(self.figure_styles['axes_color']) + plt.subplots_adjust( + left=self.figure_styles['margin_left'], + right=self.figure_styles['margin_right'], + top=self.figure_styles['margin_top'], + bottom=self.figure_styles['margin_bottom'] + ) + ax.set_xlim(-1, self._real_line_width - 1) + ax.set_ylim(-1, len(self.sorted_qubits) * 2 - 1) + ax.invert_yaxis() + if self._title: + ax.set_title(self._title, loc='center') + + ax.axis(self.figure_styles['show_axis'] in [1, True, 'true', '1']) + if self._fonts: + plt.rcParams['font.sans-serif'] = self._fonts + plt.rcParams['axes.unicode_minus'] = False + + return fig, ax + + def draw_column(self, column: list[dict]): + """ + Renders a single column of instructions onto the axes. + + Args: + column (list[InstructionData]): Group of gates to draw vertically aligned. + """ + for item in column: + ins_width = item['width'] + ins = item['instruction'] + if len(ins.qubits) == 1: + self._draw_single_gate(ins, ins_width) + else: + self._draw_multi_gate(ins, ins_width) + + def _draw_single_gate( + self, + instruction_data: InstructionData, + width: float, + gate_style=None + ): + """ + Renders single-qubit gate with parameter display. + + Args: + instruction_data (InstructionData): Gate and associated qubit(s). + """ + qubit_no = self.qubit_line_no(instruction_data.qubits[0]) + if gate_style: + gs = gate_style + else: + gs = self.gate_style[instruction_data.instruction.name] + + rect = Rectangle( + (self._current_x - width / 2, qubit_no - self.figure_styles['gate_height'] / 2), + width, + self.figure_styles['gate_height'], + fc=gs['background_color'], + ec=gs['border_color'], + lw=2, + zorder=ZOrder.BOX + ) + self._ax.add_patch(rect) + ins = instruction_data.instruction + + if isinstance(ins, Measure): + self._draw_measure(qubit_no) + return + if ins.params: + t = f"{ins.name}\n{self._params_str(instruction_data)}" + else: + t = ins.name + + self._ax.text( + self._current_x, + qubit_no, + t, + ha='center', + va='center', + fontsize=gs['font_size'], + color=gs['text_color'], + zorder=ZOrder.GATE + ) + + def _draw_measure(self, qubit_no: int): + """ + Draws a measurement symbol on the circuit diagram. + + Args: + qubit_no (int): Line number for the qubit where measurement is performed. + """ + gs = self.gate_style['M'] + self._ax.add_patch(Arc( + (self._current_x, qubit_no + 0.3 * self.figure_styles['gate_height']), + width=0.5 * self.figure_styles['gate_width'], + height=0.8 * self.figure_styles['gate_width'], + theta1=180, + theta2=0, + lw=2, + color=gs['text_color'], + zorder=ZOrder.GATE, + )) + + self._ax.arrow( + x=self._current_x - 0.1 * self.figure_styles['gate_width'], + y=qubit_no + 0.5, + dx=0.2 * self.figure_styles['gate_width'], + dy=-0.6 * self.figure_styles['gate_height'], + head_width=self.figure_styles['gate_width'] / 9.0, + zorder=ZOrder.GATE, + color=gs['text_color'], + length_includes_head=False + ) + + def _draw_multi_gate(self, instruction_data: InstructionData, width: float): + """ + Draws multi-qubit gates with connection lines. + Args: + instruction_data (InstructionData): Gate and associated qubits. + width (float): Width of the gate column. + """ + qs = instruction_data.qubits + instruction = instruction_data.instruction + if isinstance(instruction, SWAP): + self._draw_swap(qs) + elif isinstance(instruction, CZ): + self._draw_cz(qs) + elif isinstance(instruction, Barrier): + self._draw_barrier(qs) + elif isinstance(instruction, ControlledGate): + self._draw_controlled_gate(instruction_data, width) + + def _draw_swap(self, qs: Sequence[Bit]): + gs = self.gate_style['SWAP'] + for q in qs: + self._ax.plot( + [self._current_x - 0.3, self._current_x + 0.3], + [self.qubit_line_no(q) + 0.3, self.qubit_line_no(q) - 0.3], + color=gs['text_color'], + linewidth=gs['line_width'], + zorder=ZOrder.GATE, + ) + self._ax.plot( + [self._current_x - 0.3, self._current_x + 0.3], + [self.qubit_line_no(q) - 0.3, self.qubit_line_no(q) + 0.3], + color=gs['text_color'], + linewidth=1.5, + zorder=ZOrder.GATE, + ) + + self._ax.vlines( + self._current_x, + ymin=self.qubit_line_no(qs[0]), + ymax=self.qubit_line_no(qs[1]), + color=gs['line_color'], + linewidth=gs['line_width'] + ) + + def _draw_barrier(self, qs: Sequence[Bit]): + gs = self.gate_style['B'] + for q in qs: + self._ax.vlines( + self._current_x, + ymin=self.qubit_line_no(q) - 1, + ymax=self.qubit_line_no(q) + 1, + # linestyles='dashed', + color=gs['line_color'], + linestyles=(0, (10, 3)), + linewidth=gs['line_width'] + ) + + def _draw_cz(self, qs: Sequence[Bit]): + gs = self.gate_style['CZ'] + for q in qs: + self._ax.text( + self._current_x, + self.qubit_line_no(q), + BoxChar.DOT.value, + ha='center', + va='center', + color=gs['line_color'], + fontsize=12, + zorder=ZOrder.GATE, + ) + self._ax.vlines( + self._current_x, + ymin=self.qubit_line_no(qs[0]), + ymax=self.qubit_line_no(qs[1]), + color=gs['line_color'], + linewidth=1 + ) + + def _draw_controlled_gate(self, instruction_data: InstructionData, width: float): + """ + Renders controlled gates with connection lines. + + Args: + instruction_data: Must contain a ControlledGate instance + """ + gs = self.gate_style[instruction_data.instruction.name] + if not isinstance(instruction_data.instruction, ControlledGate): + raise CqlibError(f"Invalid instruction type for controlled gate. {instruction_data}") + qubit_nos = [] + for i, qubit in enumerate(instruction_data.qubits): + qubit_nos.append(self.qubit_line_no(qubit)) + if i in instruction_data.instruction.control_index: + self._ax.text( + self._current_x, + self.qubit_line_no(qubit), + BoxChar.DOT.value, + ha='center', + va='center', + color=gs['line_color'], + fontsize=12, + zorder=ZOrder.GATE, + ) + else: + self._draw_single_gate( + InstructionData(instruction_data.instruction.base_gate, [qubit]), + gate_style=gs, + width=width + ) + self._ax.vlines( + self._current_x, + ymin=min(qubit_nos), + ymax=max(qubit_nos), + color=gs['line_color'], + linewidth=1, + zorder=ZOrder.LINE + ) + + @staticmethod + def _params_str(ins: InstructionData): + if ins.instruction.params: + t = [] + for p in ins.instruction.params: + if isinstance(p, Parameter): + t.append(str(p.symbol)) + else: + t.append(str(p)) + t = ','.join(t) + else: + t = '' + return t + + +# pylint: disable=too-many-arguments, too-many-positional-arguments +def draw_mpl( + circuit: Circuit, + title: str | None = None, + filename: str | None = None, + qubit_order: list[int | Qubit] | None = None, + figure_styles: dict[str, str | bool | float | tuple[float | int]] | None = None, + gate_styles: dict[str, str | bool | float | tuple[float | int]] | None = None, + fonts: str | list[str] | None = None, + style: str = 'default', +) -> Figure: + """ + Quick visualization entry point. + + Args: + circuit(Circuit): Quantum circuit to draw + title (str | None): Optional diagram title + filename (str | None): Optional filename to save the diagram + qubit_order (list[int | Qubit] | None): Custom display order for qubits. + Defaults to circuit's natural ordering. + figure_styles (dict | None): Overrides for default figure styles (colors, margins, etc.). + gate_styles (dict | None): Overrides for default gate rendering styles. + fonts (str | list[str] | None): Font family/families for text rendering. + style (str): Style name for consistent visual appearance. + + Returns: + Figure: Ready-to-display circuit visualization + """ + return MatplotlibDrawer( + circuit, + title=title, + filename=filename, + figure_styles=figure_styles, + qubit_order=qubit_order, + gate_styles=gate_styles, + fonts=fonts, + style=style + ).drawer() diff --git a/cqlib/visualization/circuit/style.py b/cqlib/visualization/circuit/style.py new file mode 100644 index 0000000000000000000000000000000000000000..14c02469e50d80db37e2eebc22214f96e2313751 --- /dev/null +++ b/cqlib/visualization/circuit/style.py @@ -0,0 +1,79 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Handles circuit visualization styles by loading from JSON files and allowing customizations. + +This module provides the Style class which manages gate display styles loaded from +JSON files in the styles directory, with optional runtime customizations. +""" +import json +from pathlib import Path + + +class Style: + """ + Manages visualization styles for quantum circuit gates. + + Styles are loaded from JSON files and can be customized at runtime. Each style + defines graphical parameters for different types of quantum gates. + + Args: + style: Name of the base style JSON file (without extension) + gate_styles: Optional dictionary of style overrides to merge with base style + + Attributes: + _style: Combined dictionary of all style parameters + """ + + def __init__(self, style: str, gate_styles: dict | None = None): + """Initialize style with base configuration and optional customizations.""" + self._style = self.load_style(style) + if gate_styles: + self._style.update(gate_styles) + + @staticmethod + def load_style(style): + """ + Load style configuration from JSON file in styles directory. + + Args: + style: Name of the style file (without .json extension) + + Returns: + dict: Style parameters loaded from JSON file + + Raises: + ValueError: If the specified style file doesn't exist + """ + path = Path(__file__).parent / "styles" / f'{style}.json' + if not path.exists(): + raise ValueError(f"Style {style} not found.") + with open(path, 'r', encoding='utf-8') as f: + gate_styles = json.load(f) + return gate_styles + + def __getitem__(self, item: str): + """Retrieve style parameters for a specific gate type. + + Falls back to 'default' style if gate-specific parameters aren't found. + + Args: + item: Name of the gate type to retrieve style for + + Returns: + dict: Style parameters for the requested gate type + """ + if item in self._style: + return self._style[item] + return self._style['default'] diff --git a/cqlib/visualization/circuit/styles/default.json b/cqlib/visualization/circuit/styles/default.json new file mode 100644 index 0000000000000000000000000000000000000000..056341f3bcea7065e98eaab016f8038931aea557 --- /dev/null +++ b/cqlib/visualization/circuit/styles/default.json @@ -0,0 +1,202 @@ +{ + "default": { + "border_color": "None", + "background_color": "red", + "font_size": 12, + "text_color": "black", + "line_color": "black" + }, + "H": { + "border_color": "none", + "background_color": "red", + "font_size": 12, + "text_color": "black" + }, + "I": { + "border_color": "none", + "background_color": "#002D9C", + "font_size": 12, + "text_color": "white" + }, + "RX": { + "border_color": "none", + "background_color": "#9F1853", + "font_size": 12, + "text_color": "white" + }, + "CRX": { + "border_color": "none", + "background_color": "#9F1853", + "font_size": 12, + "text_color": "white", + "line_color": "#9F1853" + }, + "RXY": { + "border_color": "none", + "background_color": "#9F1853", + "font_size": 12, + "text_color": "white" + }, + "RY": { + "border_color": "none", + "background_color": "#9F1853", + "font_size": 12, + "text_color": "white" + }, + "CRY": { + "border_color": "none", + "background_color": "#9F1853", + "font_size": 12, + "text_color": "white", + "line_color": "#9F1853" + }, + "RZ": { + "border_color": "none", + "background_color": "#33B1FF", + "font_size": 12, + "text_color": "white" + }, + "CRZ": { + "border_color": "none", + "background_color": "#33B1FF", + "font_size": 12, + "text_color": "white", + "line_color": "#33B1FF" + }, + "S": { + "border_color": "none", + "background_color": "#33B1FF", + "font_size": 12, + "text_color": "white" + }, + "SD": { + "border_color": "none", + "background_color": "#33B1FF", + "font_size": 12, + "text_color": "white" + }, + "SWAP": { + "border_color": "none", + "background_color": "#002D9C", + "line_width": 2, + "text_color": "#002D9C", + "line_color": "#002D9C" + }, + "T": { + "border_color": "none", + "background_color": "#33B1FF", + "font_size": 12, + "text_color": "white" + }, + "TD": { + "border_color": "none", + "background_color": "#33B1FF", + "font_size": 12, + "text_color": "white" + }, + "U": { + "border_color": "none", + "background_color": "#9F1853", + "font_size": 12, + "text_color": "white" + }, + "X": { + "border_color": "none", + "background_color": "#002D9C", + "font_size": 12, + "text_color": "white" + }, + "CX": { + "border_color": "none", + "background_color": "#002D9C", + "font_size": 12, + "text_color": "white", + "line_color": "#002D9C" + }, + "CCX": { + "border_color": "none", + "background_color": "#002D9C", + "font_size": 12, + "text_color": "white", + "line_color": "#002D9C" + }, + "X2P": { + "border_color": "none", + "background_color": "red", + "font_size": 12, + "text_color": "black" + }, + "X2M": { + "border_color": "none", + "background_color": "red", + "font_size": 12, + "text_color": "black" + }, + "XY": { + "border_color": "none", + "background_color": "red", + "font_size": 12, + "text_color": "black" + }, + "XY2P": { + "border_color": "none", + "background_color": "red", + "font_size": 12, + "text_color": "black" + }, + "XY2M": { + "border_color": "none", + "background_color": "red", + "font_size": 12, + "text_color": "red" + }, + "Y": { + "border_color": "none", + "background_color": "#9F1853", + "font_size": 12, + "text_color": "black" + }, + "Y2M": { + "border_color": "none", + "background_color": "red", + "font_size": 12, + "text_color": "black" + }, + "Y2P": { + "border_color": "none", + "background_color": "red", + "font_size": 12, + "text_color": "black" + }, + "CY": { + "border_color": "none", + "background_color": "#9F1853", + "font_size": 12, + "text_color": "black", + "line_color": "#9F1853" + }, + "Z": { + "border_color": "none", + "background_color": "#33B1FF", + "font_size": 12, + "text_color": "white" + }, + "CZ": { + "border_color": "none", + "background_color": "#33B1FF", + "font_size": 12, + "text_color": "white", + "line_color": "#33B1FF" + }, + "M": { + "border_color": "gray", + "background_color": "white", + "text_color": "gray", + "line_color": "gray" + }, + "B": { + "border_color": "none", + "line_color": "blue", + "line_width": 5 + } +} \ No newline at end of file diff --git a/cqlib/visualization/circuit/styles/gray.json b/cqlib/visualization/circuit/styles/gray.json new file mode 100644 index 0000000000000000000000000000000000000000..ed15beb9ca64b70a6ee3f7e1b0e8701fc639ccf6 --- /dev/null +++ b/cqlib/visualization/circuit/styles/gray.json @@ -0,0 +1,14 @@ +{ + "default": { + "border_color": "gray", + "background_color": "white", + "font_size": 12, + "text_color": "black", + "line_color": "black" + }, + "B": { + "border_color": "gray", + "line_color": "black", + "line_width": 5 + } +} \ No newline at end of file diff --git a/cqlib/visualization/circuit/text.py b/cqlib/visualization/circuit/text.py new file mode 100644 index 0000000000000000000000000000000000000000..54e0f897733dff3f269135cbe770dbb067215845 --- /dev/null +++ b/cqlib/visualization/circuit/text.py @@ -0,0 +1,296 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Text-based quantum circuit visualization module + +Provides ASCII/Unicode art representation of quantum circuits using box-drawing characters. +""" +import copy +import logging +import shutil + +from cqlib.circuits.circuit import Circuit +from cqlib.circuits.gates import SWAP, CZ +from cqlib.circuits.barrier import Barrier +from cqlib.circuits.gates.gate import ControlledGate +from cqlib.circuits.instruction_data import InstructionData +from cqlib.circuits.qubit import Qubit + +from .base import BaseDrawer, BoxChar + +logger = logging.getLogger('cqlib.vis') + + +class TextDrawer(BaseDrawer): + """ + Renders quantum circuits as text diagrams using box-drawing characters + """ + + def __init__( + self, + circuit: Circuit, + qubit_order: list[int | Qubit] | None = None, + line_width: int | None = None, + ): + """ + Initialization Parameters + + Args: + circuit (Circuit): Quantum circuit to visualize + qubit_order (list[int | Qubit] | None): Custom ordering of qubits. + line_width(int | None): Optional width of the visualization output + """ + super().__init__(circuit, qubit_order) + self.line_width = line_width + + def drawer(self) -> str: + """ + Generates the complete text-based circuit diagram. + + Returns: + str: Full circuit representation characters. + """ + max_line_width = self.get_line_width() + lines = self.make_lines() + data = [] + lines_count = len(self.sorted_qubits) * 2 + 1 + start_qubits = copy.deepcopy(lines[0]) + current_data = [lines[0][i] for i in range(lines_count)] + current_width = 0 + for moment in lines[1:]: + + moment_len = len(''.join(moment[0])) + if moment_len + current_width > max_line_width: + data.append(current_data) + for d in current_data: + d.append(BoxChar.RIGHT_ARROW) + current_data = [] + for i in range(lines_count): + s = [BoxChar.LEFT_ARROW] + s.extend(start_qubits[i]) + current_data.append(s) + current_width = 0 + for i, line in enumerate(moment): + current_data[i].extend(line) + current_width += moment_len + data.append(current_data) + t = [] + for lines in data: + for line in lines: + t.extend(line) + t.append('\n') + t.append('\n') + return ''.join(t) + + # pylint: disable=too-many-locals + def make_lines(self) -> list[list[list[str]]]: + """ + Constructs the circuit visualization line structure. + + Builds a 3-level nested list representing the circuit layout: + - Outer list: Moments (vertical slices of parallel operations) + - Middle list: Text lines per moment (including qubit labels and connections) + - Inner list: Character sequences for each line segment + + Returns: + List of moments, each containing lists of text lines with box characters. + Structure details: + - Even indices: Spacer lines between qubits + - Odd indices: Qubit lines with labels and gate symbols + - First element: Initial qubit label headers + - Subsequent elements: Circuit moments with gate representations + """ + lines = [] + qubit_len = max(len(str(q.index)) for q in self.sorted_qubits) + empty_line = ' ' * (qubit_len + 6) + lines_count = len(self.sorted_qubits) * 2 + 1 + # qubits + start_lines = [] + for qubit in self.sorted_qubits: + start_lines.extend([ + [empty_line], + [''.join([' ', f'Q{qubit.index}'.rjust(qubit_len + 1), + ': ', BoxChar.LEFT_RIGHT * 2])] + ]) + start_lines.append([empty_line]) + lines.append(start_lines) + + # instructions + for moment in self.generate_moment(): + # before moment, add one symbol + moment_lines = [[BoxChar.LEFT_RIGHT if i % 2 == 1 else ' '] for i in range(lines_count)] + + # drawer one moment, maybe multi columns + columns = self.moment_to_columns(moment) + for column in columns: + column_lines = self.draw_column(column) + for line_i, line in enumerate(column_lines): + moment_lines[line_i].extend(line) + + # after moment, add one symbol + for i, line in enumerate(moment_lines): + line.append(BoxChar.LEFT_RIGHT if i % 2 == 1 else ' ') + + # mark many column as one moment + col_len = len(columns) + if col_len > 1: + s = BoxChar.LEFT_RIGHT * (len(''.join(moment_lines[0])) - 2) + moment_lines[0] = [BoxChar.BOTTOM_RIGHT, s, BoxChar.BOTTOM_LEFT] + moment_lines[-1] = [BoxChar.TOP_RIGHT, s, BoxChar.TOP_LEFT] + lines.append(moment_lines) + return lines + + def draw_column(self, column: list[InstructionData]): + """ + Processes a vertical column of parallel operations + + Args: + column: Group of non-overlapping operations from the same moment + + Returns: + list: Formatted lines ready for insertion into main drawing + """ + max_width = 1 + + # container + lines = [[] for _ in range(len(self.sorted_qubits) * 2 + 1)] + # draw every InstructionData + for ins in column: + match len(ins.qubits): + case 1: + lines = self.draw_single_gate(ins, lines) + case _: + lines = self.draw_multi_gate(ins, lines) + # calculate max width + for line in lines: + for s in line: + max_width = max(max_width, len(s)) + # fit max width + empty_line = ' ' * max_width + left_right_line = BoxChar.LEFT_RIGHT * max_width + for i, line in enumerate(lines): + if i % 2 == 0: + if line: + lines[i] = [line[0].center(max_width)] + else: + lines[i].append(empty_line) + else: + if line: + lines[i] = [line[0].center(max_width, BoxChar.LEFT_RIGHT)] + else: + lines[i].append(left_right_line) + + return lines + + def draw_single_gate(self, ins: InstructionData, lines: list[list[str]]): + """ + Handles single-qubit gate visualization + + Args: + ins: Gate instruction to render + lines: Current drawing lines state + + Returns: + Updated lines with gate symbol placed on target qubit line + """ + lines[self.qubit_line_no(ins.qubits[0])].append(str(ins.instruction)) + return lines + + def draw_multi_gate(self, ins: InstructionData, lines: list[list[str]]): + """ + Visualizes multi-qubit operations with vertical connections + + Special cases: + - SWAP: Uses 'X' connection symbols + - CZ: Uses solid dots + - Barriers: Vertical lines spanning involved qubits + - Controlled gates: Differentiates control/target qubits + + Args: + ins: Multi-qubit instruction to render + lines: Current drawing lines state + + Returns: + Updated lines with gate symbols and connection lines + """ + if isinstance(ins.instruction, SWAP): + lines[self.qubit_line_no(ins.qubits[0])].append(BoxChar.CONNECT) + lines[self.qubit_line_no(ins.qubits[1])].append(BoxChar.CONNECT) + elif isinstance(ins.instruction, CZ): + lines[self.qubit_line_no(ins.qubits[0])].append(BoxChar.DOT) + lines[self.qubit_line_no(ins.qubits[1])].append(BoxChar.DOT) + elif isinstance(ins.instruction, Barrier): + for qubit in ins.qubits: + idx = self.qubit_line_no(qubit) + if idx not in (1, len(lines) - 1): + lines[idx - 1].append(BoxChar.TOP_BOTTOM) + lines[idx].append(BoxChar.TOP_BOTTOM) + # Barrier, No vertical connections required + return lines + elif isinstance(ins.instruction, ControlledGate): + for index, qubit in enumerate(ins.qubits): + s = BoxChar.DOT if index in ins.instruction.control_index \ + else str(ins.instruction.base_gate) + lines[self.qubit_line_no(qubit)].append(s) + + # add connect vertical line + qubit_index = [self.qubit_line_no(q) for q in ins.qubits] + min_index, max_index = min(qubit_index), max(qubit_index) + for idx in range(min_index + 1, max_index): + lines[idx].append(BoxChar.TOP_BOTTOM_LEFT_RIGHT if idx % 2 == 1 + else BoxChar.TOP_BOTTOM) + + return lines + + def get_line_width(self): + """ + Determines the effective display width for circuit rendering. + + Priority order for width determination: + 1. User-specified line_width (if > MIN_LINE_WIDTH) + 2. Current terminal width + 3. DEFAULT_LINE_WIDTH fallback + + Returns: + int: display width in characters. + """ + if self.line_width: + if self.line_width > self.MIN_LINE_WIDTH: + return self.line_width + if self.line_width < 0: + return float('inf') + # try to get terminal size + width, _ = shutil.get_terminal_size() + if not width: + width = self.DEFAULT_LINE_WIDTH + return width + + +def draw_text( + circuit: Circuit, + qubit_order: list[int | Qubit] = None, + line_width: int = None, +): + """ + Quick-access function for text circuit visualization. + + Args: + circuit (Circuit): Quantum circuit to visualize + qubit_order (list[int | Qubit]): Optional custom qubit arrangement + line_width (int): + + Returns: + str: Ready-to-print circuit diagram + """ + return TextDrawer(circuit, qubit_order, line_width).drawer() diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index cf19c40ffd52e39ef5215d848ea9520d1807236e..0000000000000000000000000000000000000000 --- a/poetry.lock +++ /dev/null @@ -1,1281 +0,0 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. - -[[package]] -name = "antlr4-python3-runtime" -version = "4.13.1" -description = "ANTLR 4.13.1 runtime for Python 3" -optional = false -python-versions = "*" -files = [ - {file = "antlr4-python3-runtime-4.13.1.tar.gz", hash = "sha256:3cd282f5ea7cfb841537fe01f143350fdb1c0b1ce7981443a2fa8513fddb6d1a"}, - {file = "antlr4_python3_runtime-4.13.1-py3-none-any.whl", hash = "sha256:78ec57aad12c97ac039ca27403ad61cb98aaec8a3f9bb8144f889aa0fa28b943"}, -] - -[[package]] -name = "astroid" -version = "3.2.4" -description = "An abstract syntax tree for Python with inference support." -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "astroid-3.2.4-py3-none-any.whl", hash = "sha256:413658a61eeca6202a59231abb473f932038fbcbf1666587f66d482083413a25"}, - {file = "astroid-3.2.4.tar.gz", hash = "sha256:0e14202810b30da1b735827f78f5157be2bbd4a7a59b7707ca0bfc2fb4c0063a"}, -] - -[package.dependencies] -typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} - -[[package]] -name = "certifi" -version = "2024.7.4" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -files = [ - {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, - {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, -] - -[[package]] -name = "cfgv" -version = "3.4.0" -description = "Validate configuration and produce human readable error messages." -optional = false -python-versions = ">=3.8" -files = [ - {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, - {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.3.2" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, -] - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "contourpy" -version = "1.2.1" -description = "Python library for calculating contours of 2D quadrilateral grids" -optional = false -python-versions = ">=3.9" -files = [ - {file = "contourpy-1.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bd7c23df857d488f418439686d3b10ae2fbf9bc256cd045b37a8c16575ea1040"}, - {file = "contourpy-1.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5b9eb0ca724a241683c9685a484da9d35c872fd42756574a7cfbf58af26677fd"}, - {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c75507d0a55378240f781599c30e7776674dbaf883a46d1c90f37e563453480"}, - {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11959f0ce4a6f7b76ec578576a0b61a28bdc0696194b6347ba3f1c53827178b9"}, - {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb3315a8a236ee19b6df481fc5f997436e8ade24a9f03dfdc6bd490fea20c6da"}, - {file = "contourpy-1.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39f3ecaf76cd98e802f094e0d4fbc6dc9c45a8d0c4d185f0f6c2234e14e5f75b"}, - {file = "contourpy-1.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:94b34f32646ca0414237168d68a9157cb3889f06b096612afdd296003fdd32fd"}, - {file = "contourpy-1.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:457499c79fa84593f22454bbd27670227874cd2ff5d6c84e60575c8b50a69619"}, - {file = "contourpy-1.2.1-cp310-cp310-win32.whl", hash = "sha256:ac58bdee53cbeba2ecad824fa8159493f0bf3b8ea4e93feb06c9a465d6c87da8"}, - {file = "contourpy-1.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:9cffe0f850e89d7c0012a1fb8730f75edd4320a0a731ed0c183904fe6ecfc3a9"}, - {file = "contourpy-1.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6022cecf8f44e36af10bd9118ca71f371078b4c168b6e0fab43d4a889985dbb5"}, - {file = "contourpy-1.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ef5adb9a3b1d0c645ff694f9bca7702ec2c70f4d734f9922ea34de02294fdf72"}, - {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6150ffa5c767bc6332df27157d95442c379b7dce3a38dff89c0f39b63275696f"}, - {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c863140fafc615c14a4bf4efd0f4425c02230eb8ef02784c9a156461e62c965"}, - {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:00e5388f71c1a0610e6fe56b5c44ab7ba14165cdd6d695429c5cd94021e390b2"}, - {file = "contourpy-1.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4492d82b3bc7fbb7e3610747b159869468079fe149ec5c4d771fa1f614a14df"}, - {file = "contourpy-1.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:49e70d111fee47284d9dd867c9bb9a7058a3c617274900780c43e38d90fe1205"}, - {file = "contourpy-1.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b59c0ffceff8d4d3996a45f2bb6f4c207f94684a96bf3d9728dbb77428dd8cb8"}, - {file = "contourpy-1.2.1-cp311-cp311-win32.whl", hash = "sha256:7b4182299f251060996af5249c286bae9361fa8c6a9cda5efc29fe8bfd6062ec"}, - {file = "contourpy-1.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2855c8b0b55958265e8b5888d6a615ba02883b225f2227461aa9127c578a4922"}, - {file = "contourpy-1.2.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:62828cada4a2b850dbef89c81f5a33741898b305db244904de418cc957ff05dc"}, - {file = "contourpy-1.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:309be79c0a354afff9ff7da4aaed7c3257e77edf6c1b448a779329431ee79d7e"}, - {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e785e0f2ef0d567099b9ff92cbfb958d71c2d5b9259981cd9bee81bd194c9a4"}, - {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cac0a8f71a041aa587410424ad46dfa6a11f6149ceb219ce7dd48f6b02b87a7"}, - {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af3f4485884750dddd9c25cb7e3915d83c2db92488b38ccb77dd594eac84c4a0"}, - {file = "contourpy-1.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ce6889abac9a42afd07a562c2d6d4b2b7134f83f18571d859b25624a331c90b"}, - {file = "contourpy-1.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a1eea9aecf761c661d096d39ed9026574de8adb2ae1c5bd7b33558af884fb2ce"}, - {file = "contourpy-1.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:187fa1d4c6acc06adb0fae5544c59898ad781409e61a926ac7e84b8f276dcef4"}, - {file = "contourpy-1.2.1-cp312-cp312-win32.whl", hash = "sha256:c2528d60e398c7c4c799d56f907664673a807635b857df18f7ae64d3e6ce2d9f"}, - {file = "contourpy-1.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:1a07fc092a4088ee952ddae19a2b2a85757b923217b7eed584fdf25f53a6e7ce"}, - {file = "contourpy-1.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bb6834cbd983b19f06908b45bfc2dad6ac9479ae04abe923a275b5f48f1a186b"}, - {file = "contourpy-1.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1d59e739ab0e3520e62a26c60707cc3ab0365d2f8fecea74bfe4de72dc56388f"}, - {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd3db01f59fdcbce5b22afad19e390260d6d0222f35a1023d9adc5690a889364"}, - {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a12a813949e5066148712a0626895c26b2578874e4cc63160bb007e6df3436fe"}, - {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe0ccca550bb8e5abc22f530ec0466136379c01321fd94f30a22231e8a48d985"}, - {file = "contourpy-1.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1d59258c3c67c865435d8fbeb35f8c59b8bef3d6f46c1f29f6123556af28445"}, - {file = "contourpy-1.2.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f32c38afb74bd98ce26de7cc74a67b40afb7b05aae7b42924ea990d51e4dac02"}, - {file = "contourpy-1.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d31a63bc6e6d87f77d71e1abbd7387ab817a66733734883d1fc0021ed9bfa083"}, - {file = "contourpy-1.2.1-cp39-cp39-win32.whl", hash = "sha256:ddcb8581510311e13421b1f544403c16e901c4e8f09083c881fab2be80ee31ba"}, - {file = "contourpy-1.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:10a37ae557aabf2509c79715cd20b62e4c7c28b8cd62dd7d99e5ed3ce28c3fd9"}, - {file = "contourpy-1.2.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a31f94983fecbac95e58388210427d68cd30fe8a36927980fab9c20062645609"}, - {file = "contourpy-1.2.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef2b055471c0eb466033760a521efb9d8a32b99ab907fc8358481a1dd29e3bd3"}, - {file = "contourpy-1.2.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b33d2bc4f69caedcd0a275329eb2198f560b325605810895627be5d4b876bf7f"}, - {file = "contourpy-1.2.1.tar.gz", hash = "sha256:4d8908b3bee1c889e547867ca4cdc54e5ab6be6d3e078556814a22457f49423c"}, -] - -[package.dependencies] -numpy = ">=1.20" - -[package.extras] -bokeh = ["bokeh", "selenium"] -docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"] -mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.8.0)", "types-Pillow"] -test = ["Pillow", "contourpy[test-no-images]", "matplotlib"] -test-no-images = ["pytest", "pytest-cov", "pytest-xdist", "wurlitzer"] - -[[package]] -name = "cycler" -version = "0.12.1" -description = "Composable style cycles" -optional = false -python-versions = ">=3.8" -files = [ - {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, - {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, -] - -[package.extras] -docs = ["ipython", "matplotlib", "numpydoc", "sphinx"] -tests = ["pytest", "pytest-cov", "pytest-xdist"] - -[[package]] -name = "dill" -version = "0.3.8" -description = "serialize all of Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"}, - {file = "dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca"}, -] - -[package.extras] -graph = ["objgraph (>=1.7.2)"] -profile = ["gprof2dot (>=2022.7.29)"] - -[[package]] -name = "distlib" -version = "0.3.8" -description = "Distribution utilities" -optional = false -python-versions = "*" -files = [ - {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, - {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, -] - -[[package]] -name = "exceptiongroup" -version = "1.2.2" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, - {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, -] - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "execnet" -version = "2.1.1" -description = "execnet: rapid multi-Python deployment" -optional = false -python-versions = ">=3.8" -files = [ - {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, - {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, -] - -[package.extras] -testing = ["hatch", "pre-commit", "pytest", "tox"] - -[[package]] -name = "filelock" -version = "3.15.4" -description = "A platform independent file lock." -optional = false -python-versions = ">=3.8" -files = [ - {file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"}, - {file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"}, -] - -[package.extras] -docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"] -typing = ["typing-extensions (>=4.8)"] - -[[package]] -name = "fonttools" -version = "4.53.1" -description = "Tools to manipulate font files" -optional = false -python-versions = ">=3.8" -files = [ - {file = "fonttools-4.53.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0679a30b59d74b6242909945429dbddb08496935b82f91ea9bf6ad240ec23397"}, - {file = "fonttools-4.53.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8bf06b94694251861ba7fdeea15c8ec0967f84c3d4143ae9daf42bbc7717fe3"}, - {file = "fonttools-4.53.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b96cd370a61f4d083c9c0053bf634279b094308d52fdc2dd9a22d8372fdd590d"}, - {file = "fonttools-4.53.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1c7c5aa18dd3b17995898b4a9b5929d69ef6ae2af5b96d585ff4005033d82f0"}, - {file = "fonttools-4.53.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e013aae589c1c12505da64a7d8d023e584987e51e62006e1bb30d72f26522c41"}, - {file = "fonttools-4.53.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9efd176f874cb6402e607e4cc9b4a9cd584d82fc34a4b0c811970b32ba62501f"}, - {file = "fonttools-4.53.1-cp310-cp310-win32.whl", hash = "sha256:c8696544c964500aa9439efb6761947393b70b17ef4e82d73277413f291260a4"}, - {file = "fonttools-4.53.1-cp310-cp310-win_amd64.whl", hash = "sha256:8959a59de5af6d2bec27489e98ef25a397cfa1774b375d5787509c06659b3671"}, - {file = "fonttools-4.53.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da33440b1413bad53a8674393c5d29ce64d8c1a15ef8a77c642ffd900d07bfe1"}, - {file = "fonttools-4.53.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ff7e5e9bad94e3a70c5cd2fa27f20b9bb9385e10cddab567b85ce5d306ea923"}, - {file = "fonttools-4.53.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6e7170d675d12eac12ad1a981d90f118c06cf680b42a2d74c6c931e54b50719"}, - {file = "fonttools-4.53.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bee32ea8765e859670c4447b0817514ca79054463b6b79784b08a8df3a4d78e3"}, - {file = "fonttools-4.53.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6e08f572625a1ee682115223eabebc4c6a2035a6917eac6f60350aba297ccadb"}, - {file = "fonttools-4.53.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b21952c092ffd827504de7e66b62aba26fdb5f9d1e435c52477e6486e9d128b2"}, - {file = "fonttools-4.53.1-cp311-cp311-win32.whl", hash = "sha256:9dfdae43b7996af46ff9da520998a32b105c7f098aeea06b2226b30e74fbba88"}, - {file = "fonttools-4.53.1-cp311-cp311-win_amd64.whl", hash = "sha256:d4d0096cb1ac7a77b3b41cd78c9b6bc4a400550e21dc7a92f2b5ab53ed74eb02"}, - {file = "fonttools-4.53.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d92d3c2a1b39631a6131c2fa25b5406855f97969b068e7e08413325bc0afba58"}, - {file = "fonttools-4.53.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3b3c8ebafbee8d9002bd8f1195d09ed2bd9ff134ddec37ee8f6a6375e6a4f0e8"}, - {file = "fonttools-4.53.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32f029c095ad66c425b0ee85553d0dc326d45d7059dbc227330fc29b43e8ba60"}, - {file = "fonttools-4.53.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10f5e6c3510b79ea27bb1ebfcc67048cde9ec67afa87c7dd7efa5c700491ac7f"}, - {file = "fonttools-4.53.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f677ce218976496a587ab17140da141557beb91d2a5c1a14212c994093f2eae2"}, - {file = "fonttools-4.53.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9e6ceba2a01b448e36754983d376064730690401da1dd104ddb543519470a15f"}, - {file = "fonttools-4.53.1-cp312-cp312-win32.whl", hash = "sha256:791b31ebbc05197d7aa096bbc7bd76d591f05905d2fd908bf103af4488e60670"}, - {file = "fonttools-4.53.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ed170b5e17da0264b9f6fae86073be3db15fa1bd74061c8331022bca6d09bab"}, - {file = "fonttools-4.53.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c818c058404eb2bba05e728d38049438afd649e3c409796723dfc17cd3f08749"}, - {file = "fonttools-4.53.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:651390c3b26b0c7d1f4407cad281ee7a5a85a31a110cbac5269de72a51551ba2"}, - {file = "fonttools-4.53.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e54f1bba2f655924c1138bbc7fa91abd61f45c68bd65ab5ed985942712864bbb"}, - {file = "fonttools-4.53.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9cd19cf4fe0595ebdd1d4915882b9440c3a6d30b008f3cc7587c1da7b95be5f"}, - {file = "fonttools-4.53.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2af40ae9cdcb204fc1d8f26b190aa16534fcd4f0df756268df674a270eab575d"}, - {file = "fonttools-4.53.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:35250099b0cfb32d799fb5d6c651220a642fe2e3c7d2560490e6f1d3f9ae9169"}, - {file = "fonttools-4.53.1-cp38-cp38-win32.whl", hash = "sha256:f08df60fbd8d289152079a65da4e66a447efc1d5d5a4d3f299cdd39e3b2e4a7d"}, - {file = "fonttools-4.53.1-cp38-cp38-win_amd64.whl", hash = "sha256:7b6b35e52ddc8fb0db562133894e6ef5b4e54e1283dff606fda3eed938c36fc8"}, - {file = "fonttools-4.53.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75a157d8d26c06e64ace9df037ee93a4938a4606a38cb7ffaf6635e60e253b7a"}, - {file = "fonttools-4.53.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4824c198f714ab5559c5be10fd1adf876712aa7989882a4ec887bf1ef3e00e31"}, - {file = "fonttools-4.53.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:becc5d7cb89c7b7afa8321b6bb3dbee0eec2b57855c90b3e9bf5fb816671fa7c"}, - {file = "fonttools-4.53.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84ec3fb43befb54be490147b4a922b5314e16372a643004f182babee9f9c3407"}, - {file = "fonttools-4.53.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:73379d3ffdeecb376640cd8ed03e9d2d0e568c9d1a4e9b16504a834ebadc2dfb"}, - {file = "fonttools-4.53.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:02569e9a810f9d11f4ae82c391ebc6fb5730d95a0657d24d754ed7763fb2d122"}, - {file = "fonttools-4.53.1-cp39-cp39-win32.whl", hash = "sha256:aae7bd54187e8bf7fd69f8ab87b2885253d3575163ad4d669a262fe97f0136cb"}, - {file = "fonttools-4.53.1-cp39-cp39-win_amd64.whl", hash = "sha256:e5b708073ea3d684235648786f5f6153a48dc8762cdfe5563c57e80787c29fbb"}, - {file = "fonttools-4.53.1-py3-none-any.whl", hash = "sha256:f1f8758a2ad110bd6432203a344269f445a2907dc24ef6bccfd0ac4e14e0d71d"}, - {file = "fonttools-4.53.1.tar.gz", hash = "sha256:e128778a8e9bc11159ce5447f76766cefbd876f44bd79aff030287254e4752c4"}, -] - -[package.extras] -all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] -graphite = ["lz4 (>=1.7.4.2)"] -interpolatable = ["munkres", "pycairo", "scipy"] -lxml = ["lxml (>=4.0)"] -pathops = ["skia-pathops (>=0.5.0)"] -plot = ["matplotlib"] -repacker = ["uharfbuzz (>=0.23.0)"] -symfont = ["sympy"] -type1 = ["xattr"] -ufo = ["fs (>=2.2.0,<3)"] -unicode = ["unicodedata2 (>=15.1.0)"] -woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] - -[[package]] -name = "identify" -version = "2.6.0" -description = "File identification library for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "identify-2.6.0-py2.py3-none-any.whl", hash = "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0"}, - {file = "identify-2.6.0.tar.gz", hash = "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf"}, -] - -[package.extras] -license = ["ukkonen"] - -[[package]] -name = "idna" -version = "3.7" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.5" -files = [ - {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, - {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, -] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "isort" -version = "5.13.2" -description = "A Python utility / library to sort Python imports." -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, - {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, -] - -[package.extras] -colors = ["colorama (>=0.4.6)"] - -[[package]] -name = "jinja2" -version = "3.1.4" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -files = [ - {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, - {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "kiwisolver" -version = "1.4.5" -description = "A fast implementation of the Cassowary constraint solver" -optional = false -python-versions = ">=3.7" -files = [ - {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05703cf211d585109fcd72207a31bb170a0f22144d68298dc5e61b3c946518af"}, - {file = "kiwisolver-1.4.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:146d14bebb7f1dc4d5fbf74f8a6cb15ac42baadee8912eb84ac0b3b2a3dc6ac3"}, - {file = "kiwisolver-1.4.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6ef7afcd2d281494c0a9101d5c571970708ad911d028137cd558f02b851c08b4"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9eaa8b117dc8337728e834b9c6e2611f10c79e38f65157c4c38e9400286f5cb1"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec20916e7b4cbfb1f12380e46486ec4bcbaa91a9c448b97023fde0d5bbf9e4ff"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b42c68602539407884cf70d6a480a469b93b81b7701378ba5e2328660c847a"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa12042de0171fad672b6c59df69106d20d5596e4f87b5e8f76df757a7c399aa"}, - {file = "kiwisolver-1.4.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a40773c71d7ccdd3798f6489aaac9eee213d566850a9533f8d26332d626b82c"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:19df6e621f6d8b4b9c4d45f40a66839294ff2bb235e64d2178f7522d9170ac5b"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:83d78376d0d4fd884e2c114d0621624b73d2aba4e2788182d286309ebdeed770"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e391b1f0a8a5a10ab3b9bb6afcfd74f2175f24f8975fb87ecae700d1503cdee0"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:852542f9481f4a62dbb5dd99e8ab7aedfeb8fb6342349a181d4036877410f525"}, - {file = "kiwisolver-1.4.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59edc41b24031bc25108e210c0def6f6c2191210492a972d585a06ff246bb79b"}, - {file = "kiwisolver-1.4.5-cp310-cp310-win32.whl", hash = "sha256:a6aa6315319a052b4ee378aa171959c898a6183f15c1e541821c5c59beaa0238"}, - {file = "kiwisolver-1.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:d0ef46024e6a3d79c01ff13801cb19d0cad7fd859b15037aec74315540acc276"}, - {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:11863aa14a51fd6ec28688d76f1735f8f69ab1fabf388851a595d0721af042f5"}, - {file = "kiwisolver-1.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8ab3919a9997ab7ef2fbbed0cc99bb28d3c13e6d4b1ad36e97e482558a91be90"}, - {file = "kiwisolver-1.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fcc700eadbbccbf6bc1bcb9dbe0786b4b1cb91ca0dcda336eef5c2beed37b797"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dfdd7c0b105af050eb3d64997809dc21da247cf44e63dc73ff0fd20b96be55a9"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76c6a5964640638cdeaa0c359382e5703e9293030fe730018ca06bc2010c4437"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbea0db94288e29afcc4c28afbf3a7ccaf2d7e027489c449cf7e8f83c6346eb9"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ceec1a6bc6cab1d6ff5d06592a91a692f90ec7505d6463a88a52cc0eb58545da"}, - {file = "kiwisolver-1.4.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:040c1aebeda72197ef477a906782b5ab0d387642e93bda547336b8957c61022e"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f91de7223d4c7b793867797bacd1ee53bfe7359bd70d27b7b58a04efbb9436c8"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:faae4860798c31530dd184046a900e652c95513796ef51a12bc086710c2eec4d"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:b0157420efcb803e71d1b28e2c287518b8808b7cf1ab8af36718fd0a2c453eb0"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:06f54715b7737c2fecdbf140d1afb11a33d59508a47bf11bb38ecf21dc9ab79f"}, - {file = "kiwisolver-1.4.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fdb7adb641a0d13bdcd4ef48e062363d8a9ad4a182ac7647ec88f695e719ae9f"}, - {file = "kiwisolver-1.4.5-cp311-cp311-win32.whl", hash = "sha256:bb86433b1cfe686da83ce32a9d3a8dd308e85c76b60896d58f082136f10bffac"}, - {file = "kiwisolver-1.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c08e1312a9cf1074d17b17728d3dfce2a5125b2d791527f33ffbe805200a355"}, - {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:32d5cf40c4f7c7b3ca500f8985eb3fb3a7dfc023215e876f207956b5ea26632a"}, - {file = "kiwisolver-1.4.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f846c260f483d1fd217fe5ed7c173fb109efa6b1fc8381c8b7552c5781756192"}, - {file = "kiwisolver-1.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ff5cf3571589b6d13bfbfd6bcd7a3f659e42f96b5fd1c4830c4cf21d4f5ef45"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7269d9e5f1084a653d575c7ec012ff57f0c042258bf5db0954bf551c158466e7"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da802a19d6e15dffe4b0c24b38b3af68e6c1a68e6e1d8f30148c83864f3881db"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3aba7311af82e335dd1e36ffff68aaca609ca6290c2cb6d821a39aa075d8e3ff"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763773d53f07244148ccac5b084da5adb90bfaee39c197554f01b286cf869228"}, - {file = "kiwisolver-1.4.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2270953c0d8cdab5d422bee7d2007f043473f9d2999631c86a223c9db56cbd16"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d099e745a512f7e3bbe7249ca835f4d357c586d78d79ae8f1dcd4d8adeb9bda9"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:74db36e14a7d1ce0986fa104f7d5637aea5c82ca6326ed0ec5694280942d1162"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e5bab140c309cb3a6ce373a9e71eb7e4873c70c2dda01df6820474f9889d6d4"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0f114aa76dc1b8f636d077979c0ac22e7cd8f3493abbab152f20eb8d3cda71f3"}, - {file = "kiwisolver-1.4.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:88a2df29d4724b9237fc0c6eaf2a1adae0cdc0b3e9f4d8e7dc54b16812d2d81a"}, - {file = "kiwisolver-1.4.5-cp312-cp312-win32.whl", hash = "sha256:72d40b33e834371fd330fb1472ca19d9b8327acb79a5821d4008391db8e29f20"}, - {file = "kiwisolver-1.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:2c5674c4e74d939b9d91dda0fae10597ac7521768fec9e399c70a1f27e2ea2d9"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3a2b053a0ab7a3960c98725cfb0bf5b48ba82f64ec95fe06f1d06c99b552e130"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd32d6c13807e5c66a7cbb79f90b553642f296ae4518a60d8d76243b0ad2898"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59ec7b7c7e1a61061850d53aaf8e93db63dce0c936db1fda2658b70e4a1be709"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da4cfb373035def307905d05041c1d06d8936452fe89d464743ae7fb8371078b"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2400873bccc260b6ae184b2b8a4fec0e4082d30648eadb7c3d9a13405d861e89"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1b04139c4236a0f3aff534479b58f6f849a8b351e1314826c2d230849ed48985"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4e66e81a5779b65ac21764c295087de82235597a2293d18d943f8e9e32746265"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:7931d8f1f67c4be9ba1dd9c451fb0eeca1a25b89e4d3f89e828fe12a519b782a"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b3f7e75f3015df442238cca659f8baa5f42ce2a8582727981cbfa15fee0ee205"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:bbf1d63eef84b2e8c89011b7f2235b1e0bf7dacc11cac9431fc6468e99ac77fb"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:4c380469bd3f970ef677bf2bcba2b6b0b4d5c75e7a020fb863ef75084efad66f"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-win32.whl", hash = "sha256:9408acf3270c4b6baad483865191e3e582b638b1654a007c62e3efe96f09a9a3"}, - {file = "kiwisolver-1.4.5-cp37-cp37m-win_amd64.whl", hash = "sha256:5b94529f9b2591b7af5f3e0e730a4e0a41ea174af35a4fd067775f9bdfeee01a"}, - {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:11c7de8f692fc99816e8ac50d1d1aef4f75126eefc33ac79aac02c099fd3db71"}, - {file = "kiwisolver-1.4.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:53abb58632235cd154176ced1ae8f0d29a6657aa1aa9decf50b899b755bc2b93"}, - {file = "kiwisolver-1.4.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:88b9f257ca61b838b6f8094a62418421f87ac2a1069f7e896c36a7d86b5d4c29"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3195782b26fc03aa9c6913d5bad5aeb864bdc372924c093b0f1cebad603dd712"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc579bf0f502e54926519451b920e875f433aceb4624a3646b3252b5caa9e0b6"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a580c91d686376f0f7c295357595c5a026e6cbc3d77b7c36e290201e7c11ecb"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cfe6ab8da05c01ba6fbea630377b5da2cd9bcbc6338510116b01c1bc939a2c18"}, - {file = "kiwisolver-1.4.5-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d2e5a98f0ec99beb3c10e13b387f8db39106d53993f498b295f0c914328b1333"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a51a263952b1429e429ff236d2f5a21c5125437861baeed77f5e1cc2d2c7c6da"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3edd2fa14e68c9be82c5b16689e8d63d89fe927e56debd6e1dbce7a26a17f81b"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:74d1b44c6cfc897df648cc9fdaa09bc3e7679926e6f96df05775d4fb3946571c"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:76d9289ed3f7501012e05abb8358bbb129149dbd173f1f57a1bf1c22d19ab7cc"}, - {file = "kiwisolver-1.4.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:92dea1ffe3714fa8eb6a314d2b3c773208d865a0e0d35e713ec54eea08a66250"}, - {file = "kiwisolver-1.4.5-cp38-cp38-win32.whl", hash = "sha256:5c90ae8c8d32e472be041e76f9d2f2dbff4d0b0be8bd4041770eddb18cf49a4e"}, - {file = "kiwisolver-1.4.5-cp38-cp38-win_amd64.whl", hash = "sha256:c7940c1dc63eb37a67721b10d703247552416f719c4188c54e04334321351ced"}, - {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9407b6a5f0d675e8a827ad8742e1d6b49d9c1a1da5d952a67d50ef5f4170b18d"}, - {file = "kiwisolver-1.4.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15568384086b6df3c65353820a4473575dbad192e35010f622c6ce3eebd57af9"}, - {file = "kiwisolver-1.4.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0dc9db8e79f0036e8173c466d21ef18e1befc02de8bf8aa8dc0813a6dc8a7046"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cdc8a402aaee9a798b50d8b827d7ecf75edc5fb35ea0f91f213ff927c15f4ff0"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6c3bd3cde54cafb87d74d8db50b909705c62b17c2099b8f2e25b461882e544ff"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:955e8513d07a283056b1396e9a57ceddbd272d9252c14f154d450d227606eb54"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:346f5343b9e3f00b8db8ba359350eb124b98c99efd0b408728ac6ebf38173958"}, - {file = "kiwisolver-1.4.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9098e0049e88c6a24ff64545cdfc50807818ba6c1b739cae221bbbcbc58aad3"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:00bd361b903dc4bbf4eb165f24d1acbee754fce22ded24c3d56eec268658a5cf"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7b8b454bac16428b22560d0a1cf0a09875339cab69df61d7805bf48919415901"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f1d072c2eb0ad60d4c183f3fb44ac6f73fb7a8f16a2694a91f988275cbf352f9"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:31a82d498054cac9f6d0b53d02bb85811185bcb477d4b60144f915f3b3126342"}, - {file = "kiwisolver-1.4.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6512cb89e334e4700febbffaaa52761b65b4f5a3cf33f960213d5656cea36a77"}, - {file = "kiwisolver-1.4.5-cp39-cp39-win32.whl", hash = "sha256:9db8ea4c388fdb0f780fe91346fd438657ea602d58348753d9fb265ce1bca67f"}, - {file = "kiwisolver-1.4.5-cp39-cp39-win_amd64.whl", hash = "sha256:59415f46a37f7f2efeec758353dd2eae1b07640d8ca0f0c42548ec4125492635"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5c7b3b3a728dc6faf3fc372ef24f21d1e3cee2ac3e9596691d746e5a536de920"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:620ced262a86244e2be10a676b646f29c34537d0d9cc8eb26c08f53d98013390"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:378a214a1e3bbf5ac4a8708304318b4f890da88c9e6a07699c4ae7174c09a68d"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf7be1207676ac608a50cd08f102f6742dbfc70e8d60c4db1c6897f62f71523"}, - {file = "kiwisolver-1.4.5-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ba55dce0a9b8ff59495ddd050a0225d58bd0983d09f87cfe2b6aec4f2c1234e4"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fd32ea360bcbb92d28933fc05ed09bffcb1704ba3fc7942e81db0fd4f81a7892"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5e7139af55d1688f8b960ee9ad5adafc4ac17c1c473fe07133ac092310d76544"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dced8146011d2bc2e883f9bd68618b8247387f4bbec46d7392b3c3b032640126"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9bf3325c47b11b2e51bca0824ea217c7cd84491d8ac4eefd1e409705ef092bd"}, - {file = "kiwisolver-1.4.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5794cf59533bc3f1b1c821f7206a3617999db9fbefc345360aafe2e067514929"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e368f200bbc2e4f905b8e71eb38b3c04333bddaa6a2464a6355487b02bb7fb09"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5d706eba36b4c4d5bc6c6377bb6568098765e990cfc21ee16d13963fab7b3e7"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85267bd1aa8880a9c88a8cb71e18d3d64d2751a790e6ca6c27b8ccc724bcd5ad"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210ef2c3a1f03272649aff1ef992df2e724748918c4bc2d5a90352849eb40bea"}, - {file = "kiwisolver-1.4.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:11d011a7574eb3b82bcc9c1a1d35c1d7075677fdd15de527d91b46bd35e935ee"}, - {file = "kiwisolver-1.4.5.tar.gz", hash = "sha256:e57e563a57fb22a142da34f38acc2fc1a5c864bc29ca1517a88abc963e60d6ec"}, -] - -[[package]] -name = "markupsafe" -version = "2.1.5" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.7" -files = [ - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, - {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, -] - -[[package]] -name = "matplotlib" -version = "3.9.1" -description = "Python plotting package" -optional = false -python-versions = ">=3.9" -files = [ - {file = "matplotlib-3.9.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7ccd6270066feb9a9d8e0705aa027f1ff39f354c72a87efe8fa07632f30fc6bb"}, - {file = "matplotlib-3.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:591d3a88903a30a6d23b040c1e44d1afdd0d778758d07110eb7596f811f31842"}, - {file = "matplotlib-3.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd2a59ff4b83d33bca3b5ec58203cc65985367812cb8c257f3e101632be86d92"}, - {file = "matplotlib-3.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0fc001516ffcf1a221beb51198b194d9230199d6842c540108e4ce109ac05cc0"}, - {file = "matplotlib-3.9.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:83c6a792f1465d174c86d06f3ae85a8fe36e6f5964633ae8106312ec0921fdf5"}, - {file = "matplotlib-3.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:421851f4f57350bcf0811edd754a708d2275533e84f52f6760b740766c6747a7"}, - {file = "matplotlib-3.9.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b3fce58971b465e01b5c538f9d44915640c20ec5ff31346e963c9e1cd66fa812"}, - {file = "matplotlib-3.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a973c53ad0668c53e0ed76b27d2eeeae8799836fd0d0caaa4ecc66bf4e6676c0"}, - {file = "matplotlib-3.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82cd5acf8f3ef43f7532c2f230249720f5dc5dd40ecafaf1c60ac8200d46d7eb"}, - {file = "matplotlib-3.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab38a4f3772523179b2f772103d8030215b318fef6360cb40558f585bf3d017f"}, - {file = "matplotlib-3.9.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2315837485ca6188a4b632c5199900e28d33b481eb083663f6a44cfc8987ded3"}, - {file = "matplotlib-3.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:a0c977c5c382f6696caf0bd277ef4f936da7e2aa202ff66cad5f0ac1428ee15b"}, - {file = "matplotlib-3.9.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:565d572efea2b94f264dd86ef27919515aa6d629252a169b42ce5f570db7f37b"}, - {file = "matplotlib-3.9.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6d397fd8ccc64af2ec0af1f0efc3bacd745ebfb9d507f3f552e8adb689ed730a"}, - {file = "matplotlib-3.9.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26040c8f5121cd1ad712abffcd4b5222a8aec3a0fe40bc8542c94331deb8780d"}, - {file = "matplotlib-3.9.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12cb1837cffaac087ad6b44399d5e22b78c729de3cdae4629e252067b705e2b"}, - {file = "matplotlib-3.9.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0e835c6988edc3d2d08794f73c323cc62483e13df0194719ecb0723b564e0b5c"}, - {file = "matplotlib-3.9.1-cp312-cp312-win_amd64.whl", hash = "sha256:44a21d922f78ce40435cb35b43dd7d573cf2a30138d5c4b709d19f00e3907fd7"}, - {file = "matplotlib-3.9.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:0c584210c755ae921283d21d01f03a49ef46d1afa184134dd0f95b0202ee6f03"}, - {file = "matplotlib-3.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:11fed08f34fa682c2b792942f8902e7aefeed400da71f9e5816bea40a7ce28fe"}, - {file = "matplotlib-3.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0000354e32efcfd86bda75729716b92f5c2edd5b947200be9881f0a671565c33"}, - {file = "matplotlib-3.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4db17fea0ae3aceb8e9ac69c7e3051bae0b3d083bfec932240f9bf5d0197a049"}, - {file = "matplotlib-3.9.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:208cbce658b72bf6a8e675058fbbf59f67814057ae78165d8a2f87c45b48d0ff"}, - {file = "matplotlib-3.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:dc23f48ab630474264276be156d0d7710ac6c5a09648ccdf49fef9200d8cbe80"}, - {file = "matplotlib-3.9.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3fda72d4d472e2ccd1be0e9ccb6bf0d2eaf635e7f8f51d737ed7e465ac020cb3"}, - {file = "matplotlib-3.9.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:84b3ba8429935a444f1fdc80ed930babbe06725bcf09fbeb5c8757a2cd74af04"}, - {file = "matplotlib-3.9.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b918770bf3e07845408716e5bbda17eadfc3fcbd9307dc67f37d6cf834bb3d98"}, - {file = "matplotlib-3.9.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f1f2e5d29e9435c97ad4c36fb6668e89aee13d48c75893e25cef064675038ac9"}, - {file = "matplotlib-3.9.1.tar.gz", hash = "sha256:de06b19b8db95dd33d0dc17c926c7c9ebed9f572074b6fac4f65068a6814d010"}, -] - -[package.dependencies] -contourpy = ">=1.0.1" -cycler = ">=0.10" -fonttools = ">=4.22.0" -kiwisolver = ">=1.3.1" -numpy = ">=1.23" -packaging = ">=20.0" -pillow = ">=8" -pyparsing = ">=2.3.1" -python-dateutil = ">=2.7" - -[package.extras] -dev = ["meson-python (>=0.13.1)", "numpy (>=1.25)", "pybind11 (>=2.6)", "setuptools (>=64)", "setuptools_scm (>=7)"] - -[[package]] -name = "mccabe" -version = "0.7.0" -description = "McCabe checker, plugin for flake8" -optional = false -python-versions = ">=3.6" -files = [ - {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, - {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, -] - -[[package]] -name = "mpmath" -version = "1.3.0" -description = "Python library for arbitrary-precision floating-point arithmetic" -optional = false -python-versions = "*" -files = [ - {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"}, - {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"}, -] - -[package.extras] -develop = ["codecov", "pycodestyle", "pytest (>=4.6)", "pytest-cov", "wheel"] -docs = ["sphinx"] -gmpy = ["gmpy2 (>=2.1.0a4)"] -tests = ["pytest (>=4.6)"] - -[[package]] -name = "networkx" -version = "3.3" -description = "Python package for creating and manipulating graphs and networks" -optional = false -python-versions = ">=3.10" -files = [ - {file = "networkx-3.3-py3-none-any.whl", hash = "sha256:28575580c6ebdaf4505b22c6256a2b9de86b316dc63ba9e93abde3d78dfdbcf2"}, - {file = "networkx-3.3.tar.gz", hash = "sha256:0c127d8b2f4865f59ae9cb8aafcd60b5c70f3241ebd66f7defad7c4ab90126c9"}, -] - -[package.extras] -default = ["matplotlib (>=3.6)", "numpy (>=1.23)", "pandas (>=1.4)", "scipy (>=1.9,!=1.11.0,!=1.11.1)"] -developer = ["changelist (==0.5)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] -doc = ["myst-nb (>=1.0)", "numpydoc (>=1.7)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.14)", "sphinx (>=7)", "sphinx-gallery (>=0.14)", "texext (>=0.6.7)"] -extra = ["lxml (>=4.6)", "pydot (>=2.0)", "pygraphviz (>=1.12)", "sympy (>=1.10)"] -test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] - -[[package]] -name = "nodeenv" -version = "1.9.1" -description = "Node.js virtual environment builder" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, - {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, -] - -[[package]] -name = "numpy" -version = "1.26.4" -description = "Fundamental package for array computing in Python" -optional = false -python-versions = ">=3.9" -files = [ - {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, - {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, - {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, - {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, - {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, - {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, - {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, - {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, - {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, - {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, - {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, -] - -[[package]] -name = "openqasm3" -version = "1.0.0" -description = "Reference OpenQASM AST in Python" -optional = false -python-versions = "*" -files = [ - {file = "openqasm3-1.0.0-py3-none-any.whl", hash = "sha256:d4371737b4a49b0d56248ed3d30766a94000bccfb43303ec9c7ead351a1b6cc3"}, - {file = "openqasm3-1.0.0.tar.gz", hash = "sha256:3f2bb1cca855cff114e046bac22d59adbf9b754cac6398961aa6d22588fb688e"}, -] - -[package.extras] -all = ["antlr4-python3-runtime (>=4.7,<4.14)", "importlib-metadata", "pytest (>=6.0)", "pyyaml"] -parser = ["antlr4-python3-runtime (>=4.7,<4.14)", "importlib-metadata"] -tests = ["pytest (>=6.0)", "pyyaml"] - -[[package]] -name = "packaging" -version = "24.1" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, - {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, -] - -[[package]] -name = "pillow" -version = "10.4.0" -description = "Python Imaging Library (Fork)" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, - {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"}, - {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"}, - {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"}, - {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"}, - {file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"}, - {file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"}, - {file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"}, - {file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"}, - {file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"}, - {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"}, - {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"}, - {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"}, - {file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"}, - {file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"}, - {file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"}, - {file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"}, - {file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"}, - {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"}, - {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"}, - {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"}, - {file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"}, - {file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"}, - {file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"}, - {file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"}, - {file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"}, - {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"}, - {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"}, - {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"}, - {file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"}, - {file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"}, - {file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"}, - {file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"}, - {file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"}, - {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"}, - {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"}, - {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"}, - {file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"}, - {file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"}, - {file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"}, - {file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"}, - {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"}, - {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"}, - {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"}, - {file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"}, - {file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"}, - {file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"}, - {file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"}, - {file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"}, - {file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"}, -] - -[package.extras] -docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] -fpx = ["olefile"] -mic = ["olefile"] -tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] -typing = ["typing-extensions"] -xmp = ["defusedxml"] - -[[package]] -name = "platformdirs" -version = "4.2.2" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -optional = false -python-versions = ">=3.8" -files = [ - {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, - {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, -] - -[package.extras] -docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] -type = ["mypy (>=1.8)"] - -[[package]] -name = "pluggy" -version = "1.5.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "pre-commit" -version = "3.8.0" -description = "A framework for managing and maintaining multi-language pre-commit hooks." -optional = false -python-versions = ">=3.9" -files = [ - {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, - {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, -] - -[package.dependencies] -cfgv = ">=2.0.0" -identify = ">=1.0.0" -nodeenv = ">=0.11.1" -pyyaml = ">=5.1" -virtualenv = ">=20.10.0" - -[[package]] -name = "pylint" -version = "3.2.6" -description = "python code static checker" -optional = false -python-versions = ">=3.8.0" -files = [ - {file = "pylint-3.2.6-py3-none-any.whl", hash = "sha256:03c8e3baa1d9fb995b12c1dbe00aa6c4bcef210c2a2634374aedeb22fb4a8f8f"}, - {file = "pylint-3.2.6.tar.gz", hash = "sha256:a5d01678349454806cff6d886fb072294f56a58c4761278c97fb557d708e1eb3"}, -] - -[package.dependencies] -astroid = ">=3.2.4,<=3.3.0-dev0" -colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -dill = [ - {version = ">=0.2", markers = "python_version < \"3.11\""}, - {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, - {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, -] -isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" -mccabe = ">=0.6,<0.8" -platformdirs = ">=2.2.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -tomlkit = ">=0.10.1" - -[package.extras] -spelling = ["pyenchant (>=3.2,<4.0)"] -testutils = ["gitpython (>3)"] - -[[package]] -name = "pyparsing" -version = "3.1.2" -description = "pyparsing module - Classes and methods to define and execute parsing grammars" -optional = false -python-versions = ">=3.6.8" -files = [ - {file = "pyparsing-3.1.2-py3-none-any.whl", hash = "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"}, - {file = "pyparsing-3.1.2.tar.gz", hash = "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad"}, -] - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - -[[package]] -name = "pytest" -version = "8.3.2" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, - {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=1.5,<2" -tomli = {version = ">=1", markers = "python_version < \"3.11\""} - -[package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytest-html" -version = "4.1.1" -description = "pytest plugin for generating HTML reports" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest_html-4.1.1-py3-none-any.whl", hash = "sha256:c8152cea03bd4e9bee6d525573b67bbc6622967b72b9628dda0ea3e2a0b5dd71"}, - {file = "pytest_html-4.1.1.tar.gz", hash = "sha256:70a01e8ae5800f4a074b56a4cb1025c8f4f9b038bba5fe31e3c98eb996686f07"}, -] - -[package.dependencies] -jinja2 = ">=3.0.0" -pytest = ">=7.0.0" -pytest-metadata = ">=2.0.0" - -[package.extras] -docs = ["pip-tools (>=6.13.0)"] -test = ["assertpy (>=1.1)", "beautifulsoup4 (>=4.11.1)", "black (>=22.1.0)", "flake8 (>=4.0.1)", "pre-commit (>=2.17.0)", "pytest-mock (>=3.7.0)", "pytest-rerunfailures (>=11.1.2)", "pytest-xdist (>=2.4.0)", "selenium (>=4.3.0)", "tox (>=3.24.5)"] - -[[package]] -name = "pytest-metadata" -version = "3.1.1" -description = "pytest plugin for test session metadata" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest_metadata-3.1.1-py3-none-any.whl", hash = "sha256:c8e0844db684ee1c798cfa38908d20d67d0463ecb6137c72e91f418558dd5f4b"}, - {file = "pytest_metadata-3.1.1.tar.gz", hash = "sha256:d2a29b0355fbc03f168aa96d41ff88b1a3b44a3b02acbe491801c98a048017c8"}, -] - -[package.dependencies] -pytest = ">=7.0.0" - -[package.extras] -test = ["black (>=22.1.0)", "flake8 (>=4.0.1)", "pre-commit (>=2.17.0)", "tox (>=3.24.5)"] - -[[package]] -name = "pytest-xdist" -version = "3.6.1" -description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"}, - {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"}, -] - -[package.dependencies] -execnet = ">=2.1" -pytest = ">=7.0.0" - -[package.extras] -psutil = ["psutil (>=3.0)"] -setproctitle = ["setproctitle"] -testing = ["filelock"] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "pyyaml" -version = "6.0.1" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.6" -files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, - {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, - {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, - {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, - {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, - {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, - {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, - {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, - {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, -] - -[[package]] -name = "requests" -version = "2.32.3" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.8" -files = [ - {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, - {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] - -[[package]] -name = "sympy" -version = "1.13.1" -description = "Computer algebra system (CAS) in Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "sympy-1.13.1-py3-none-any.whl", hash = "sha256:db36cdc64bf61b9b24578b6f7bab1ecdd2452cf008f34faa33776680c26d66f8"}, - {file = "sympy-1.13.1.tar.gz", hash = "sha256:9cebf7e04ff162015ce31c9c6c9144daa34a93bd082f54fd8f12deca4f47515f"}, -] - -[package.dependencies] -mpmath = ">=1.1.0,<1.4" - -[package.extras] -dev = ["hypothesis (>=6.70.0)", "pytest (>=7.1.0)"] - -[[package]] -name = "tabulate" -version = "0.9.0" -description = "Pretty-print tabular data" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, - {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, -] - -[package.extras] -widechars = ["wcwidth"] - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] - -[[package]] -name = "tomlkit" -version = "0.13.0" -description = "Style preserving TOML library" -optional = false -python-versions = ">=3.8" -files = [ - {file = "tomlkit-0.13.0-py3-none-any.whl", hash = "sha256:7075d3042d03b80f603482d69bf0c8f345c2b30e41699fd8883227f89972b264"}, - {file = "tomlkit-0.13.0.tar.gz", hash = "sha256:08ad192699734149f5b97b45f1f18dad7eb1b6d16bc72ad0c2335772650d7b72"}, -] - -[[package]] -name = "typing-extensions" -version = "4.12.2" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, -] - -[[package]] -name = "urllib3" -version = "2.2.2" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.8" -files = [ - {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, - {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "virtualenv" -version = "20.26.3" -description = "Virtual Python Environment builder" -optional = false -python-versions = ">=3.7" -files = [ - {file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"}, - {file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"}, -] - -[package.dependencies] -distlib = ">=0.3.7,<1" -filelock = ">=3.12.2,<4" -platformdirs = ">=3.9.1,<5" - -[package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] - -[metadata] -lock-version = "2.0" -python-versions = "^3.10" -content-hash = "135acdd759591b012f60b10d646e4389e66174bba22072fb5bbf5fee794c169c" diff --git a/pyproject.toml b/pyproject.toml index 1a90745599c79fc0f0bfa7884b63ad4a03e7441f..7646509b18ec0f63332142b82c1281cb220ec606 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,5 @@ [project] name = "cqlib" -version = "1.2.1" description = "China Quantum Library" authors = [ { name = "China Telecom Quantum Group Quantum Computing Team", email = "tianyan@chinatelecom.cn" }, @@ -30,25 +29,11 @@ keywords = [ "quantum", "sdk", ] +dynamic = ["version", "dependencies",] - -[tool.poetry.dependencies] -python = "^3.10" -matplotlib = "^3.7.5" -networkx = "^3.0" -numpy = "^1.26.0" -requests = "^2.31.0" -sympy = "^1.12.0" -tabulate = "^0.9.0" -openqasm3 = { extras = ["parse"], version = "^1.0.0" } -antlr4-python3-runtime = "^4.13.1" - -[tool.poetry.group.dev.dependencies] -pytest = "^8.0.2" -pylint = "^3.2.2" -pre-commit = "^3.7.1" -pytest-xdist = "^3.6.1" -pytest-html = "^4.1.1" +[tool.setuptools.dynamic] +version = { file = "cqlib/VERSION.txt" } +dependencies = {file = "requirements.txt" } [project.urls] Homepage = "https://qc.zdxlz.com/" @@ -57,7 +42,7 @@ Repository = "https://gitee.com/cq-lib/cqlib" Issues = "https://gitee.com/cq-lib/cqlib/issues" [build-system] -requires = ["setuptools>=61.0", "wheel", "poetry-core"] +requires = ["setuptools>=61.0", "wheel", 'delocate', 'pipx'] build-backend = "setuptools.build_meta" [tool.setuptools] @@ -65,12 +50,25 @@ packages = ["cqlib"] [tool.cibuildwheel] before-all = "uname -a" -manylinux-x86_64-image = "manylinux2014" -manylinux-i686-image = "manylinux2014" skip = "pp* cp36-* cp37-* cp38-* cp39-* *musllinux*" +test-command = "python -c \"import cqlib.simulator.statevector_simulator_c\"" + +[tool.cibuildwheel.linux] +repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel} && pipx run abi3audit --strict --report {wheel}" [tool.cibuildwheel.macos] environment = "MACOSX_DEPLOYMENT_TARGET=12" +repair-wheel-command = "delocate-wheel -w {dest_dir} -v {wheel} && pipx run abi3audit --strict --report {wheel}" + +[tool.cibuildwheel.windows] +environment = { CC="gcc", CXX="g++" } +repair-wheel-command = [ + "pip install delvewheel", + 'delvewheel repair -w {dest_dir} {wheel}', +# windows 上不使用 abi3,而是每个 Python 版本打包一个 wheel 文件 +# "pipx run abi3audit --strict --report {wheel}" +] + [tool.setuptools.package-data] "cqlib.simulator" = ["lib/*"] diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000000000000000000000000000000000000..25ed76d98a6f35ecc2b280ea559230ae737ddf40 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,11 @@ +pytest>=8.0.2 +pylint>=3.2.2 +pre-commit>=3.7.1 +pytest-xdist>=3.6.1 +pytest-html>=4.1.1 +antlr4-tools>=0.2.1 +delocate +poetry-core +pipx +psutil +pytest-mpl \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..07f6331e3f9b83acd366d8a5dca11fec1647ab58 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,9 @@ +matplotlib >= 3.7.5 +networkx>=3.0 +numpy>=2.1.2 +requests>=2.31.0 +sympy>=1.12.0 +tabulate>=0.9.0 +openqasm3[parser]>=1.0.0 +antlr4-python3-runtime>=4.13.2 +rustworkx>=0.16.0 \ No newline at end of file diff --git a/setup.py b/setup.py index fbe399a20584f5a3b143d4594ea8fe23b1efcea7..61672a1af815b38d49ca91e992ae69885785f01d 100644 --- a/setup.py +++ b/setup.py @@ -1,52 +1,109 @@ import os +import platform import sys +import sysconfig -from setuptools import setup, Extension, find_packages +from setuptools import setup, Extension, find_packages, find_namespace_packages +from setuptools.command.build_ext import build_ext as build_ext_orig + +python_paths = sysconfig.get_paths() +print(f'python_paths: {python_paths}') + +library_dirs = [] +py_limited_api = True if sys.platform == 'win32': - extra_compile_args = ['/openmp'] - extra_link_args = [] + v = sys.version_info + if v.minor == 10: + version = '3.10.11' + elif v.minor == 11: + version = '3.11.9' + elif v.minor == 12: + version = '3.12.9' + elif v.minor == 13: + version = '3.13.2' + else: + raise ValueError('不知道的 Python 版本') + + arch = os.getenv('CIBW_ARCHS_WINDOWS', 'AMD64') + print(f'arch: {arch}') + if arch == 'AMD64': + arch = 'x64' + python_lib = os.path.join(r'C:\hostedtoolcache\windows\Python', version, arch, 'libs') + print(f'python_lib: {python_lib} {os.path.exists(python_lib)} {os.walk(python_lib)}') + os.environ['CC'] = 'gcc' + os.environ['LD'] = 'ld' + extra_compile_args = ['-O3', '-fopenmp', '-static'] + extra_link_args = ['-static', '-fopenmp', '-Wl,-Bstatic', '-lgomp'] + library_dirs = [python_lib] + py_limited_api = False elif sys.platform == 'darwin': + os.environ['CC'] = 'gcc-14' os.environ['MACOSX_DEPLOYMENT_TARGET'] = '12.0' - extra_compile_args = ['-Xpreprocessor', '-fopenmp', '-I/usr/local/opt/libomp/include'] - extra_link_args = ['-L/usr/local/opt/libomp/lib', '-lomp'] + libomp_prefix = os.environ.get('LIBOMP_PREFIX', '/opt/homebrew/opt/libomp') + extra_compile_args = [ + '-fopenmp', + f'-I{libomp_prefix}/include' + ] + extra_link_args = [ + f'-L{libomp_prefix}/lib', + '-fopenmp', + '-lomp', + f'-Wl,-rpath,{libomp_prefix}/lib' + ] + if platform.machine() == 'arm64': + # 针对 ARM 架构(Apple Silicon) + extra_compile_args.append('-march=armv8-a') else: extra_compile_args = ['-fopenmp'] extra_link_args = ['-fopenmp'] + +class CustomBuildExt(build_ext_orig): + def build_extensions(self): + # 检查系统类型,自动配置编译器 + if os.name == 'nt': + from distutils import ccompiler + compiler = ccompiler.new_compiler(compiler='mingw32') + os.environ['CC'] = 'gcc' + os.environ['CXX'] = 'g++' + self.compiler = compiler + super().build_extensions() + + ext_modules = [ Extension( - 'cqlib.simulator.lib.statevector_simulator', - include_dirs=['cqlib/simulator/c_fusion/include'], - sources=['cqlib/simulator/c_fusion/src/c_fusion.c'], - library_dirs=['cqlib/simulator/lib'], - define_macros=[('Py_LIMITED_API', '0x030A0000')], # 对应 Python 3.10 - py_limited_api=True, - extra_compile_args=extra_compile_args, + 'cqlib.simulator.statevector_simulator_c', + include_dirs=[ + 'cqlib/simulator/c_fusion/include', + python_paths['include'], + ], + library_dirs=[python_paths['platlib']] + library_dirs, # 链接库文件所在目录 + sources=[ + 'cqlib/simulator/c_fusion/src/c_fusion.c', + 'cqlib/simulator/c_fusion/src/state_vector.c', + ], + define_macros=[('Py_LIMITED_API', '0x030A0000')] if py_limited_api else [], + py_limited_api=py_limited_api, + extra_compile_args=extra_compile_args + (['-DPy_LIMITED_API=0x030A0000'] if py_limited_api else []), extra_link_args=extra_link_args, ), ] +print(f"find_namespace_packages: {find_namespace_packages(include=['cqlib', 'cqlib.*'])}") +print(f"find_packages: {find_packages(include=['cqlib', 'cqlib.*'])}") setup( name='cqlib', ext_modules=ext_modules, - packages=find_packages(), + packages=find_namespace_packages(include=['cqlib', 'cqlib.*']), package_data={ 'cqlib': ['*'], }, include_package_data=True, python_requires='>=3.10, <4', exclude_package_data={ - 'cqlib.simulator': ['*.c', '*.h'], # 排除 C 源代码文件 + 'cqlib.simulator': ['*.c', '*.h'], }, - install_requires=[ - 'matplotlib>=3.7.5', - 'networkx>=3.0', - 'numpy>=1.26.0', - 'requests>=2.31.0', - 'sympy>=1.12.0', - 'tabulate>=0.9.0', - 'openqasm3[parse]>=1.0.0', - 'antlr4-python3-runtime>=4.13.1' - ], + cmdclass={'build_ext': CustomBuildExt}, + options={"bdist_wheel": {"py_limited_api": "cp310"} if py_limited_api else {}}, ) diff --git a/tests/circuit/__init__.py b/tests/circuit/__init__.py index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c32c814b80e89ddb81b1dac29fc75336112c257c 100644 --- a/tests/circuit/__init__.py +++ b/tests/circuit/__init__.py @@ -0,0 +1,12 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. diff --git a/tests/circuit/test_circuit.py b/tests/circuit/test_circuit.py index 5cfde9e4a450aec9c68dca867ad6b34baaba907c..205b1e4baacbd5be22eb709156f18c86eeac51c6 100644 --- a/tests/circuit/test_circuit.py +++ b/tests/circuit/test_circuit.py @@ -89,8 +89,8 @@ M Q1 M Q2 M Q3 M Q4""" - assert circuit.export_circuit_str() == s - assert circuit.export_circuit_str() == circuit.qcis + assert circuit.as_str() == s + assert circuit.as_str() == circuit.qcis def test_all_gate(): @@ -161,81 +161,7 @@ M Q1 M Q2 M Q3 M Q4""" - assert circuit.export_circuit_str(qcis_compliant=False) == s - qcis_compliant = """H Q0 -I Q1 2 -RX Q2 1 -H Q2 -CZ Q1 Q2 -RZ Q2 1.0 -CZ Q1 Q2 -H Q2 -RXY Q3 2 2 -RY Q4 1 -H Q4 -CZ Q3 Q4 -RY Q4 2 -CZ Q3 Q4 -H Q4 -RZ Q0 1 -RZ Q0 -1.0 -CZ Q4 Q0 -RZ Q0 1.0 -S Q0 -SD Q1 -H Q0 -CZ Q4 Q0 -H Q0 -H Q4 -CZ Q0 Q4 -H Q4 -H Q0 -CZ Q4 Q0 -H Q0 -H Q2 -CZ Q1 Q2 -H Q2 -H Q1 -CZ Q2 Q1 -H Q1 -H Q2 -CZ Q1 Q2 -H Q2 -T Q2 -TD Q3 -X Q4 -H Q1 -CZ Q0 Q1 -H Q1 -H Q4 -CZ Q3 Q4 -TD Q4 -CZ Q2 Q4 -T Q4 -CZ Q3 Q4 -TD Q4 -CZ Q2 Q4 -T Q2 -T Q3 -T Q4 -H Q4 -X2P Q0 -X2M Q1 -XY Q2 2 -XY2P Q3 1.0 -XY2M Q4 2.0 -Y Q0 -Y2P Q1 -Y2M Q2 -Z Q3 -CZ Q3 Q4 -B Q0 Q1 Q2 Q3 Q4 -M Q0 -M Q1 -M Q2 -M Q3 -M Q4""" - assert circuit.export_circuit_str(True) == qcis_compliant + assert circuit.as_str(qcis_compliant=False) == s def test_circuit_param(): @@ -247,9 +173,9 @@ def test_circuit_param(): circuit.rx(1, phi + 1) circuit.measure_all() assert circuit.qcis == """H Q1\nRX Q0 phi\nRX Q1 phi + 1\nM Q0\nM Q1""" - circuit.set_parameter_value(phi=1.2) + circuit.assign_parameters(phi=1.2) assert circuit.qcis == """H Q1\nRX Q0 1.2\nRX Q1 2.2\nM Q0\nM Q1""" - circuit.set_parameter_value(phi=2.2) + circuit.assign_parameters(phi=2.2) assert circuit.qcis == """H Q1\nRX Q0 2.2\nRX Q1 3.2\nM Q0\nM Q1""" @@ -290,12 +216,34 @@ def test_circuit_params(): circuit.rx(1, theta) circuit.measure_all() assert circuit.qcis == """H Q1\nRX Q0 phi\nRX Q1 theta\nM Q0\nM Q1""" - circuit.set_parameter_value(phi=1.2) + circuit.assign_parameters(phi=1.2) assert circuit.qcis == """H Q1\nRX Q0 1.2\nRX Q1 theta\nM Q0\nM Q1""" - circuit.set_parameter_value(theta=2.2) + circuit.assign_parameters(theta=2.2) assert circuit.qcis == """H Q1\nRX Q0 1.2\nRX Q1 2.2\nM Q0\nM Q1""" +def test_assign_parameters(): + """test circuit params""" + phi = Parameter('phi') + theta = Parameter('theta') + circuit = Circuit(3, [phi, theta]) + circuit.h(1) + circuit.rx(0, phi) + circuit.ry(1, theta) + circuit.measure_all() + assert circuit.qcis == 'H Q1\nRX Q0 phi\nRY Q1 theta\nM Q0\nM Q1\nM Q2' + + c2 = circuit.assign_parameters(phi=1.2) + c_data = c2.instruction_sequence + assert id(c2) != id(circuit) + assert str(c_data[1]) == 'RX Q0 1.2' + assert str(c_data[2]) == 'RY Q1 theta' + c3 = circuit.assign_parameters(theta=2.2) + assert str(c3.instruction_sequence[1]) == 'RX Q0 1.2' + assert str(c3.instruction_sequence[2]) == 'RY Q1 2.2' + assert c3.qcis == """H Q1\nRX Q0 1.2\nRY Q1 2.2\nM Q0\nM Q1\nM Q2""" + + def test_load_circuit(): """test load circuit""" qcis = "H Q1\nRX Q0 1.2\nRX Q1 2.2\nM Q0\nM Q1" @@ -309,5 +257,62 @@ def test_load_circuit(): def test_load_circuit_params(): """test load circuit with param, but will fail""" qcis = "H Q1\nRX Q0 theta\nRX Q1 2.2\nM Q0\nM Q1" - with pytest.raises(ValueError): + with pytest.raises(ValueError) as exec_info: Circuit.load(qcis) + assert 'Invalid instruction format: RX Q0 theta' in exec_info.value.args[0] + + +def test_load_e(): + """ test load Scientific notation """ + qcis = """ + Y2M Q0 + RZ Q0 3.4641020674186507e-07 + Y2M Q1 + RZ Q1 3.4641020674186507e-07 + RX Q1 3.46e2 + X2P Q2 + """ + circuit = Circuit.load(qcis) + target = """Y2M Q0 +RZ Q0 3.4641020674186507e-07 +Y2M Q1 +RZ Q1 3.4641020674186507e-07 +RX Q1 346.0 +X2P Q2""" + assert circuit.qcis == target + + +def test_add(): + """ test parameters add """ + qcis = "H Q0\nCZ Q0 Q1" + c1 = Circuit(2, parameters=['gamma']) + c1.h(0) + c1.cz(0, 1) + c2 = Circuit(2, parameters=['gamma']) + c2.h(0) + c2.cz(0, 1) + assert c1.qcis == qcis + assert c2.qcis == qcis + + c3 = c1 + c2 + assert c3.qcis == '\n'.join([qcis, qcis]) + assert id(c2) != id(c3) + assert id(c1) != id(c3) + + c1 += c2 + assert c1.qcis == '\n'.join([qcis, qcis]) + assert c2.qcis == qcis + + +def test_copy(): + """ test copy circuit """ + phi = Parameter('phi') + theta = Parameter('theta') + circuit = Circuit(2, [phi, theta]) + circuit.h(0) + circuit.cry(1, 0, theta) + circuit.crz(0, 1, phi) + circuit.measure_all() + + c2 = circuit.copy() + assert id(circuit) != id(c2) diff --git a/tests/circuit/test_commutation.py b/tests/circuit/test_commutation.py index da0414c4c3de4441d00d6aa03d128610c32e289b..d87ba55eac8e8bf046bea5c50f422e4baa7e1bd8 100644 --- a/tests/circuit/test_commutation.py +++ b/tests/circuit/test_commutation.py @@ -153,8 +153,10 @@ M Q1 M Q2""" c1 = Circuit.load(qcis) data = circuit_commutator(c1) - res = '[(CZ Q0 Q1, CZ Q1 Q2), (H Q2, H Q2), (CZ Q0 Q1, CZ Q1 Q2), (CZ Q0 Q1, CZ Q1 Q2)]' - assert str(data) == res + res = ['(CZ Q0 Q1, CZ Q1 Q2)', '(H Q2, H Q2)', '(CZ Q0 Q1, CZ Q1 Q2)', '(CZ Q0 Q1, CZ Q1 Q2)'] + assert len(data) == len(res) + for item in data: + assert str(item) in res # pylint: disable=redefined-outer-name diff --git a/tests/circuit/test_dag.py b/tests/circuit/test_dag.py new file mode 100644 index 0000000000000000000000000000000000000000..fda2af3048cf0e1c195a3e802ef8ec15c1a2d6a4 --- /dev/null +++ b/tests/circuit/test_dag.py @@ -0,0 +1,92 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" Test DAG Circuit """ +import pytest +# pylint: disable=E0611 +from rustworkx import PyDiGraph + +from cqlib.circuits.circuit import Circuit +from cqlib.circuits.instruction_data import InstructionData +from cqlib.circuits.gates import H, CX +from cqlib.circuits.qubit import Qubit +from cqlib.circuits.dag import circuit_to_dag, dag_to_circuit +from cqlib.exceptions import CqlibError + + +class TestCircuitToDAG: + """test Circuit object to dag""" + + def test_basic(self): + """ basic """ + circuit = Circuit(0) + dag = circuit_to_dag(circuit) + assert len(dag.nodes()) == 0 + + circuit = Circuit(2) + circuit.h(0) + circuit.h(1) + circuit.cx(0, 1) + dag = circuit_to_dag(circuit) + assert str(dag.nodes()) == '[H Q0, H Q1, CX Q0 Q1]' + assert str(dag.edges()) == "['H Q0-CX Q0 Q1', 'H Q1-CX Q0 Q1']" + + def test_same_ins(self): + """ test same instruction """ + circuit = Circuit(3) + circuit.h(0) + circuit.h(1) + circuit.h(0) + circuit.h(1) + circuit.cx(0, 1) + circuit.measure_all() + dag = circuit_to_dag(circuit) + assert len(dag.nodes()) == 8 + assert len(dag.edges()) == 6 + assert str(dag.nodes()) == '[H Q0, H Q1, H Q0, H Q1, CX Q0 Q1, M Q0, M Q1, M Q2]' + assert str(dag.edges()) == ("['H Q0-H Q0', 'H Q1-H Q1', 'H Q0-CX Q0 Q1', " + "'H Q1-CX Q0 Q1', 'CX Q0 Q1-M Q0', 'CX Q0 Q1-M Q1']") + + +class TestDAGToCircuit: + """test dag to Circuit object """ + + def test_basic(self): + """ basic """ + dag = PyDiGraph(check_cycle=True) + h0 = dag.add_node(InstructionData(H(), [Qubit(0)])) + h1 = dag.add_node(InstructionData(H(), [Qubit(1)])) + cx_0_1 = dag.add_node(InstructionData(CX(), [Qubit(1), Qubit(0)])) + dag.add_edge(h0, cx_0_1, edge='e1') + dag.add_edge(h1, cx_0_1, edge='e2') + + circuit = dag_to_circuit(dag) + + assert len(circuit.qubits) == 2 + assert set(circuit.qubits) == {Qubit(0), Qubit(1)} + assert len(circuit.circuit_data()) == 3 + assert circuit.as_str() == 'H Q1\nH Q0\nCX Q1 Q0' + + def test_cycle_error(self): + """ + test cycle graph error + """ + dag = PyDiGraph() + h0 = dag.add_node(InstructionData(H(), [Qubit(0)])) + cx_0_1 = dag.add_node(InstructionData(CX(), [Qubit(1), Qubit(0)])) + dag.add_edge(h0, cx_0_1, edge='e1') + dag.add_edge(cx_0_1, h0, edge='e2') + + with pytest.raises(CqlibError) as exc_info: + dag_to_circuit(dag) + assert "The provided graph must be acyclic to form a valid" in str(exc_info.value) diff --git a/tests/circuit/test_parameter.py b/tests/circuit/test_parameter.py index 93b0234fa920cde5d7c47538dae778e8db4a928b..f92d9ca8316d87ff9f256db5e6706deeab314d2f 100644 --- a/tests/circuit/test_parameter.py +++ b/tests/circuit/test_parameter.py @@ -131,6 +131,6 @@ def test_value(): theta = Parameter('theta') a = phi / theta - phi ** theta assert isinstance(a, Parameter) - assert a.value({phi: 1, theta: 2}) == -0.5 + assert a.value({phi: 1, theta: 2}).evalf() == -0.5 assert a.value({phi: 100, theta: 1}) == 0 assert str(a.value({phi: 1, theta: 0})) == '-1 + 1/theta' diff --git a/tests/core/qcis_circuit.py b/tests/core/qcis_circuit.py deleted file mode 100644 index 5b9eb38366f9ef3246c830e8c3c44548b953c39a..0000000000000000000000000000000000000000 --- a/tests/core/qcis_circuit.py +++ /dev/null @@ -1,180 +0,0 @@ -# This code is part of cqlib. -# # -# (C) Copyright qc.zdxlz.com 2024. -# # -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# # -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -# -from cqlib.core import QcisInstruction, QcisCircuit - - -class TestQcisStr2Circuit: - """ - Test method to convert from qcis string to QcisCircuit. - """ - - def test_from_qcis_str_all_gate(self): - """ - test convert_qcis_str_to_circuit. - """ - all_gate_qcis_order = """ -x2p q0 -x2m q1 -y2p q3 -y2m q2 -cz q0 q1 -rz q0 3.14159 -i q1 2 -b q0 q1 q2 -m q0 -m q1 q2 q3 -x q1 -y q2 -z q3 -h q0 -s q1 -t q2 -td q3 -rx q0 1.334 -ry q1 1.567 -rxy q2 3.14159 1.723 -""" - circuit = QcisCircuit.circuit_from_qcis_str(all_gate_qcis_order) - assert all_gate_qcis_order.lower().strip('\n') == str(circuit) - - def test_double_blank_qcis_str(self): - """ - convert_qcis_str_to_circuit method can handle double blank space exists in qcis string. - - test convert_qcis_str_to_circuit with double blank space. - """ - double_space_qcis = """ - x q0 - Y Q1 - Z Q2 - M Q1 - """ - target_qcis = """ -x q0 -y q1 -z q2 -m q1 -""" - assert target_qcis.strip('\n') == str(QcisCircuit.circuit_from_qcis_str(double_space_qcis)) - - def test_empty_line_qcis_str(self): - """ - convert_qcis_str_to_circuit method can handle double blank space exists in qcis string. - - test convert_qcis_str_to_circuit with double blank space. - """ - empty_line_qcis = """ - x q0 - - y q1 - z q2 - m q1 -""" - target_qcis = """ -x q0 -y q1 -z q2 -m q1 -""" - assert target_qcis.strip('\n') == str(QcisCircuit.circuit_from_qcis_str(empty_line_qcis)) - - -class TestQcisCircuit: - """ - Test QcisCircuit class methods. - """ - basic_order = """ -x q0 -y q1 -m q0 q1 -""" - - def test_add_qcis_instruction_with_instruction(self): - """ - Test QcisCircuit.add_instruction method with input parameter as QcisInstruction - """ - # test add qcis with a new QcisInstruction - qcis_circuit = QcisCircuit.circuit_from_qcis_str(self.basic_order) - qcis_instruction = QcisInstruction('x', [2]) - qcis_circuit.add_instruction(qcis_instruction) - target_qcis_str = """ -x q0 -y q1 -m q0 q1 -x q2 -""" - assert target_qcis_str.strip('\n') == str(qcis_circuit) - - def test_add_qcis_instruction_with_input(self): - """ - Test QcisCircuit.add_instruction method with input parameter as gate name, qubit index and other parameters. - """ - # test add qcis with a new QcisInstruction - qcis_circuit = QcisCircuit.circuit_from_qcis_str(self.basic_order) - qcis_circuit.add_instruction('x', 2) - target_qcis_str = """ -x q0 -y q1 -m q0 q1 -x q2 -""" - assert target_qcis_str.strip('\n') == str(qcis_circuit) - - def test_insert_qcis_instruction_with_instruction(self): - """ - Test QcisCircuit.insert_instruction method with input parameter as QcisInstruction - """ - qcis_circuit = QcisCircuit.circuit_from_qcis_str(self.basic_order) - qcis_instruction = QcisInstruction('x', [2]) - qcis_circuit.insert_instruction(1, qcis_instruction) - target_qcis_str = """ -x q0 -x q2 -y q1 -m q0 q1 -""" - assert target_qcis_str.strip('\n') == str(qcis_circuit) - - def test_insert_qcis_instruction_with_input(self): - """ - Test QcisCircuit.insert_instruction method with input parameter as gate name, qubit index and other parameters. - """ - qcis_circuit = QcisCircuit.circuit_from_qcis_str(self.basic_order) - qcis_circuit.insert_instruction(1, 'x', 2) - target_qcis_str = """ -x q0 -x q2 -y q1 -m q0 q1 -""" - assert target_qcis_str.strip('\n') == str(qcis_circuit) - - def test_append_qcis_order_with_operation(self): - """ - Test QcisCircuit add qcis order with call gate func. - """ - qcis_circuit = QcisCircuit.circuit_from_qcis_str(self.basic_order) - qcis_circuit.cz([0, 1]) - qcis_circuit.x(2) - qcis_circuit.rz(0, 3.14159) - target_qcis_str = """ -x q0 -y q1 -m q0 q1 -cz q0 q1 -x q2 -rz q0 3.14159 -""" - assert target_qcis_str.strip('\n') == str(qcis_circuit) - - diff --git a/tests/core/qcis_instruction.py b/tests/core/qcis_instruction.py deleted file mode 100644 index 53f88acbda716df9615b2de453350c2be15bcb4e..0000000000000000000000000000000000000000 --- a/tests/core/qcis_instruction.py +++ /dev/null @@ -1,116 +0,0 @@ -# This code is part of cqlib. -# # -# (C) Copyright qc.zdxlz.com 2024. -# # -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# # -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. -# -import pytest -from cqlib.core.exceptions import CqlibQcisCircuitError -from cqlib.core import QcisInstruction -from cqlib.core.base import QcisQubit, QcisAngle - - -class TestQcisInstruction: - """ - Test to init QcisInstruction and set its attributes. - """ - def test_get_valid_operation(self): - """ - Test method to get valid operation. - """ - operation = QcisInstruction.get_valid_operation() - assert operation.shape[1] >= 3 - - def test_instruction_from_str(self): - """ - Test init a rz QcisInstruction with qcis string. - """ - # test input instruction with \n and blank space - rz_instruction = QcisInstruction.instruction_from_str(' rz q0 3.14159\n') - assert str(rz_instruction) == 'rz q0 3.14159' - # test invalid qcis string - with pytest.raises(CqlibQcisCircuitError) as exception_info: - QcisInstruction.instruction_from_str('cz q0 q1 3.14159') - assert exception_info.value.message == 'CZ requires 2 inputs but 3 is given' - - def test_invalid_gate(self): - """ - Test given invalid gate name, it will throw a KeyError. - """ - with pytest.raises(KeyError) as exception_info: - QcisInstruction('arbitrary gate', 0) - assert 'is not a valid gate name' in str(exception_info.value) - - def test_rz_order(self): - """ - Test init a rz QcisInstruction and try to assign its attributes. - """ - # new rz instruction with different input types - rz_instruction = QcisInstruction('rz', 0, 3.14159) - assert str(rz_instruction) == 'rz q0 3.14159' - rz_instruction = QcisInstruction('rz', [0], [3.14159]) - assert str(rz_instruction) == 'rz q0 3.14159' - rz_instruction = QcisInstruction('rz', [QcisQubit(0)], [QcisAngle(3.14159)]) - assert str(rz_instruction) == 'rz q0 3.14159' - rz_instruction = QcisInstruction('rz', QcisQubit(0), QcisAngle(3.14159)) - assert str(rz_instruction) == 'rz q0 3.14159' - - with pytest.raises(CqlibQcisCircuitError) as exception_info: - QcisInstruction('rz', 0, [1, 3.144]) - assert exception_info.value.message == 'RZ requires 1 parameters but 2 is given' - - def test_cz_order(self): - """ - Test init a cz QcisInstruction and try to assign its attributes. - """ - # new cz instruction - cz_instruction = QcisInstruction('cz', [0, 1]) - assert str(cz_instruction) == 'cz q0 q1' - # change cz qubit index to (2, 3) - cz_instruction.qubits_idx = (2, 3) - assert str(cz_instruction) == 'cz q2 q3' - # change cz qubit index to [1, 2] and check its underlying qubits_idx is (1, 2) - cz_instruction.qubits_idx = [1, 2] - assert cz_instruction.qubits_idx == (1, 2) - # if given invalid input qubit index like (1, 2, 3), it will throw an CqlibQcisCircuitError error. - with pytest.raises(CqlibQcisCircuitError) as exception_info: - cz_instruction.qubits_idx = (1, 2, 3) - assert exception_info.value.message == 'CZ requires 2 parameters but 3 is given' - # if try to change qubits_index use index, - # it will throw a tuple error because underlying qubits_index is a tuple. - with pytest.raises(TypeError) as exception_info: - cz_instruction.qubits_idx[0] = 5 - assert exception_info.value.__str__() == "'tuple' object does not support item assignment" - # if construct instruction with 1 qubit index, it will throw an CqlibQcisCircuitError error. - with pytest.raises(CqlibQcisCircuitError) as exception_info: - QcisInstruction('cz', 0) - assert exception_info.value.message == 'CZ requires 2 qubit index but 1 is given' - # if construct instruction with 1 params, it will throw an CqlibQcisCircuitError error. - with pytest.raises(CqlibQcisCircuitError) as exception_info: - QcisInstruction('cz', [0, 1], 3.14159) - assert exception_info.value.message == 'CZ requires no parameters but 1 is given' - - def test_m_oder(self): - """ - Test init a m QcisInstruction and try to assign its attributes. - """ - # test invalid m instruction input. Given an extra parameter input. - with pytest.raises(CqlibQcisCircuitError) as exception_info: - QcisInstruction('m', 0, 1) - assert exception_info.value.message == 'M requires no parameters but 1 is given' - # test new m instruction with three qubits - m_instruction = QcisInstruction('m', [0, 1, 2]) - assert str(m_instruction) == 'm q0 q1 q2' - # set m instruction qubit to be 1 - m_instruction.qubits_idx = 1 - assert str(m_instruction) == 'm q1' - # test if given repeated qubit index, it will throw an CqlibQcisCircuitError error. - with pytest.raises(CqlibQcisCircuitError) as exception_info: - m_instruction.qubits_idx = [1, 1, 2] - assert exception_info.value.message == 'Qubit input [1, 1, 2] has repeated qubit index' diff --git a/tests/mapping/test_transpile.py b/tests/mapping/test_transpile.py new file mode 100644 index 0000000000000000000000000000000000000000..84cd886f1bc5dd939682ab9ba0d06bf4a317ac5f --- /dev/null +++ b/tests/mapping/test_transpile.py @@ -0,0 +1,53 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +transpile testcase +""" + +import os +import warnings + +import pytest + +from cqlib.mapping import transpile_qcis +from cqlib import TianYanPlatform, Circuit + +warnings.filterwarnings("ignore") + + +@pytest.fixture +def pf(): + """ new TianYanPlatform object""" + login_key = os.getenv('LOGIN_KEY', '') + quantum_machine = os.getenv('QUANTUM_MACHINE', 'tianyan176-2') + return TianYanPlatform(login_key=login_key, machine_name=quantum_machine) + + +# pylint: disable=redefined-outer-name +def test_base(pf): + """ test base """ + circuit = Circuit(3) + circuit.cz(0, 1) + circuit.cz(0, 2) + circuit.cz(1, 2) + circuit.measure_all() + + topo_check = pf.qcis_check_regular(circuit.qcis) + assert topo_check is False + + result = transpile_qcis(circuit.as_str(), pf) + new_circuit = result[0] + + topo_check = pf.qcis_check_regular(new_circuit.qcis) + assert topo_check is True diff --git a/tests/pulse/__init__.py b/tests/pulse/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..c32c814b80e89ddb81b1dac29fc75336112c257c --- /dev/null +++ b/tests/pulse/__init__.py @@ -0,0 +1,12 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. diff --git a/tests/pulse/test_instruction.py b/tests/pulse/test_instruction.py new file mode 100644 index 0000000000000000000000000000000000000000..a9ce7bec63d70a882286055d15d05d4129458b8a --- /dev/null +++ b/tests/pulse/test_instruction.py @@ -0,0 +1,128 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" Tests for pulse instruction """ + +import pytest + +from cqlib.exceptions import CqlibError +from cqlib.pulse import WaveformType, Waveform, PZ, PXY, PZ0, G + +# pylint: disable=missing-function-docstring + +class TestPXY: + """Test cases for the PXY pulse class.""" + + def test_cosine(self): + waveform = Waveform.create(WaveformType.COSINE, length=10, amplitude=0.1) + with pytest.raises(CqlibError) as exec_info: + PXY(waveform=waveform) + assert "PXY must have phase and drag_alpha params" in exec_info.value.args[0] + waveform = Waveform.create(WaveformType.COSINE, length=10, amplitude=0.1, + phase=2, drag_alpha=0.2) + pxy = PXY(waveform=waveform) + assert str(pxy) == 'PXY(0,10,0.1,2,0.2)' + + def test_flattop(self): + waveform = Waveform.create(WaveformType.FLATTOP, length=10, amplitude=0.1, edge=1, + phase=2, drag_alpha=0.2) + pxy = PXY(waveform=waveform) + assert str(pxy) == 'PXY(1,10,0.1,2,0.2,1)' + + def test_slepian(self): + waveform = Waveform.create(WaveformType.SLEPIAN, length=10, amplitude=0.1, phase=2, + drag_alpha=0.2, thf=0.1, thi=0.2, lam2=0.3, lam3=1) + pxy = PXY(waveform=waveform) + assert str(pxy) == 'PXY(2,10,0.1,2,0.2,0.1,0.2,0.3,1)' + + def test_numeric(self): + waveform = Waveform.create(WaveformType.NUMERIC, length=10, amplitude=0.1, phase=2, + drag_alpha=0.2, data_list=[0.1, 0.2, 0.3]) + with pytest.raises(CqlibError) as exec_info: + PXY(waveform=waveform) + assert 'The length of its list must >=6 and must be even.' in exec_info.value.args[0] + waveform = Waveform.create(WaveformType.NUMERIC, length=10, amplitude=0.1, phase=2, + drag_alpha=0.2, data_list=[0.1, 0.2, 0.3, 0.11, 0.22, 0.33]) + pxy = PXY(waveform=waveform) + assert str(pxy) == 'PXY(3,10,0.1,2,0.2,0.1,0.2,0.3,0.11,0.22,0.33)' + + +class TestPZ: + """Test cases for the PZ pulse class.""" + + def test_cosine(self): + waveform = Waveform.create(WaveformType.COSINE, length=10, amplitude=0.1) + pz = PZ(waveform=waveform) + assert str(pz) == 'PZ(0,10,0.1)' + + def test_flattop(self): + waveform = Waveform.create(WaveformType.FLATTOP, length=10, amplitude=0.1, edge=1) + pz = PZ(waveform=waveform) + assert str(pz) == 'PZ(1,10,0.1,1)' + + def test_slepian(self): + waveform = Waveform.create(WaveformType.SLEPIAN, length=10, amplitude=0.1, + thf=0.1, thi=0.2, lam2=0.3, lam3=1) + pz = PZ(waveform=waveform) + assert str(pz) == 'PZ(2,10,0.1,0.1,0.2,0.3,1)' + + def test_numeric(self): + waveform = Waveform.create(WaveformType.NUMERIC, length=10, amplitude=0.1, + data_list=[0.1, 0.2, 0.3]) + pz = PZ(waveform=waveform) + assert str(pz) == 'PZ(3,10,0.1,0.1,0.2,0.3)' + + with pytest.raises(CqlibError) as exec_info: + Waveform.create(WaveformType.NUMERIC, length=10, amplitude=0.1, data_list=[0.1, 0.2]) + assert ('The numerical list of data pulses is in the form of [d0, d1, ..., dn]' + ', and the list length must be >= 3') in exec_info.value.args[0] + + +class TestPZ0: + """Test cases for the PZ0 pulse class.""" + + def test_cosine(self): + waveform = Waveform.create(WaveformType.COSINE, length=10, amplitude=0.1) + pz0 = PZ0(waveform=waveform) + assert str(pz0) == 'PZ0(0,10,0.1)' + + def test_flattop(self): + waveform = Waveform.create(WaveformType.FLATTOP, length=10, amplitude=0.1, edge=1) + pz = PZ0(waveform=waveform) + assert str(pz) == 'PZ0(1,10,0.1,1)' + + def test_slepian(self): + waveform = Waveform.create(WaveformType.SLEPIAN, length=10, amplitude=0.1, + thf=0.1, thi=0.2, lam2=0.3, lam3=1) + pz0 = PZ0(waveform=waveform) + assert str(pz0) == 'PZ0(2,10,0.1,0.1,0.2,0.3,1)' + + def test_numeric(self): + waveform = Waveform.create(WaveformType.NUMERIC, length=10, amplitude=0.1, + data_list=[0.1, 0.2, 0.3]) + pz0 = PZ0(waveform=waveform) + assert str(pz0) == 'PZ0(3,10,0.1,0.1,0.2,0.3)' + + +class TestG: + """Test cases for the G (global coupling) pulse class.""" + + def test_g(self): + g = G(length=100, coupling_strength=-3) + assert str(g) == 'G(100,-3)' + + def test_g1(self): + with pytest.raises(TypeError) as exec_info: + # pylint: disable=unexpected-keyword-arg,no-value-for-parameter + G(waveform=100) + assert "got an unexpected keyword argument 'waveform'" in exec_info.value.args[0] diff --git a/tests/pulse/test_pulse_circuit.py b/tests/pulse/test_pulse_circuit.py new file mode 100644 index 0000000000000000000000000000000000000000..ea0d4e4de175c64aab680b03b21df635ca5a0c59 --- /dev/null +++ b/tests/pulse/test_pulse_circuit.py @@ -0,0 +1,58 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" testcase for pulse circuit """ + +from cqlib.circuits.qubit import Qubit +from cqlib.pulse import PulseCircuit, CouplerQubit, Waveform, WaveformType + + +def test_coupler_qubit(): + """ test coupler qubits""" + cq = CouplerQubit(1) + assert cq.index == 1 + assert str(cq) == 'G1' + + +def test_pulse_circuit(): + """ test base pulse circuit """ + pc = PulseCircuit(qubits=[0], coupler_qubits=[0]) + assert pc.qubits == [Qubit(0)] + assert pc.coupler_qubits == [CouplerQubit(0)] + + +def test_base_qcis(): + """ test base qcis """ + pc = PulseCircuit(qubits=[0], coupler_qubits=[0]) + pc.h(0) + pc.measure(0) + assert pc.qcis == 'H Q0\nM Q0' + + pc = PulseCircuit(qubits=[0], coupler_qubits=[0]) + waveform = Waveform.create(WaveformType.COSINE, length=100, amplitude=0.2, + phase=1.1, drag_alpha=0.1) + pc.pxy(0, waveform) + pc.pz(CouplerQubit(0), Waveform.create(WaveformType.COSINE, length=100, amplitude=0.2)) + pc.measure(0) + assert pc.qcis == 'PXY Q0 0 100 0.2 1.1 0.1\nPZ G0 0 100 0.2\nM Q0' + + +def test_load_pulse(): + """ test load pulse qcis """ + qcis = """PXY Q0 0 100 0.2 1.1 0.1 +PZ G0 0 100 0.2 +PZ0 Q1 1 100 0.2 10 +G G0 10 1000 +M Q0""" + qc = PulseCircuit.load(qcis) + assert qc.as_str() == qcis diff --git a/tests/pulse/test_waveform.py b/tests/pulse/test_waveform.py new file mode 100644 index 0000000000000000000000000000000000000000..ab4df6fd6d52068e894e7d6eab11b0fe411d12c3 --- /dev/null +++ b/tests/pulse/test_waveform.py @@ -0,0 +1,77 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""test waveform""" + +# pylint: disable=no-value-for-parameter + +from cqlib.pulse import Waveform, WaveformType, CosineWaveform, FlattopWaveform, \ + SlepianWaveform, NumericWaveform + + +def test_cosine(): + """test cosine waveform""" + length = 10 + amplitude = 1 + cos = Waveform.create(WaveformType.COSINE, length=length, amplitude=amplitude) + assert cos.data == [WaveformType.COSINE, length, amplitude] + assert str(cos) == f'{WaveformType.COSINE.value} {length} {amplitude}' + + cos = CosineWaveform(length=length, amplitude=amplitude) + assert str(cos) == f'{WaveformType.COSINE.value} {length} {amplitude}' + + +def test_flattop(): + """test flattop waveform""" + length = 10 + amplitude = 1 + edge = 1 + waveform = Waveform.create(WaveformType.FLATTOP, length=length, amplitude=amplitude, edge=edge) + assert waveform.data == [WaveformType.FLATTOP, length, amplitude, edge] + assert str(waveform) == f'{WaveformType.FLATTOP.value} {length} {amplitude} {edge}' + waveform = FlattopWaveform(length=length, amplitude=amplitude, edge=edge) + assert str(waveform) == f'{WaveformType.FLATTOP.value} {length} {amplitude} {edge}' + + +def test_slepian(): + """test slepian waveform""" + length = 10 + amplitude = 1 + thf, thi, lam2, lam3 = 0.1, 0.2, 0.3, 0.4 + waveform = Waveform.create(WaveformType.SLEPIAN, length=length, amplitude=amplitude, + thf=thf, thi=thi, lam2=lam2, lam3=lam3) + assert waveform.data == [WaveformType.SLEPIAN, length, amplitude, thf, thi, lam2, lam3] + assert str(waveform) == (f'{WaveformType.SLEPIAN.value} {length} ' + f'{amplitude} {thf} {thi} {lam2} {lam3}') + + waveform = SlepianWaveform(length=length, amplitude=amplitude, + thf=thf, thi=thi, lam2=lam2, lam3=lam3) + assert str(waveform) == (f'{WaveformType.SLEPIAN.value} {length} ' + f'{amplitude} {thf} {thi} {lam2} {lam3}') + + +def test_numeric(): + """test numeric waveform""" + length = 10 + amplitude = 1 + params = [0.1, 0.2, 0.3, 0.4] + waveform = Waveform.create(WaveformType.NUMERIC, length=length, amplitude=amplitude, + data_list=params) + d = [WaveformType.NUMERIC.value, length, amplitude] + d.extend(params) + assert waveform.data == d + assert str(waveform) == (f'{WaveformType.NUMERIC.value} {length} ' + f'{amplitude} {" ".join(map(str, params))}') + waveform = NumericWaveform(length=length, amplitude=amplitude, data_list=params) + assert str(waveform) == (f'{WaveformType.NUMERIC.value} {length} ' + f'{amplitude} {" ".join(map(str, params))}') diff --git a/tests/simulator/__init__.py b/tests/simulator/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a328c568c2a7f913f42d97aec89ed51dd765fdd5 --- /dev/null +++ b/tests/simulator/__init__.py @@ -0,0 +1,12 @@ +# This code is part of cqlib. +# +# (C) Copyright China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics 2024. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. diff --git a/tests/simulator/test_statevector_sim.py b/tests/simulator/test_statevector_sim.py new file mode 100644 index 0000000000000000000000000000000000000000..7c4dee1c13d42497f620de8e336eda6598f068a3 --- /dev/null +++ b/tests/simulator/test_statevector_sim.py @@ -0,0 +1,737 @@ +# This code is part of cqlib. +# +# (C) Copyright China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics 2024. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Test statevector simulator""" +import gc +import math +import os + +import numpy as np +import psutil + +from cqlib import Circuit, Parameter +from cqlib.simulator import StatevectorSimulator + + +def all_close(a: dict, b: dict): + """assert dict data""" + assert a.keys() == b.keys() + for ak, av in a.items(): + assert np.allclose(av, b[ak]) + + +def test_h_circuit(): + """test h circuit""" + c = Circuit(1) + c.h(0) + sim = StatevectorSimulator(circuit=c) + state = sim.statevector() + prob = sim.probs() + samples = sim.sample(shots=1000) + s1 = { + '0': 0.70710678 + 0.j, + '1': 0.70710678 + 0.j, + } + p1 = { + '0': 0.5, + '1': 0.5 + } + all_close(state, s1) + all_close(prob, p1) + assert set(samples.keys()) == {'0', '1'} + assert sum(samples.values()) == 1000 + + +def test_s_circuit(): + """test s circuit""" + c = Circuit(1) + c.h(0) + c.s(0) + sim = StatevectorSimulator(circuit=c) + state = sim.statevector() + prob = sim.probs() + s1 = { + '0': 0.70710678 + 0.j, + '1': -0. + 0.70710678j + } + p1 = { + '0': 0.5, + '1': 0.5 + } + all_close(state, s1) + all_close(prob, p1) + + c = Circuit(1) + c.h(0) + c.sd(0) + sim = StatevectorSimulator(circuit=c) + state = sim.statevector() + prob = sim.probs() + s1 = { + '0': 0.70710678 + 0.j, + '1': 0. - 0.70710678j + } + p1 = { + '0': 0.5, + '1': 0.5 + } + all_close(state, s1) + all_close(prob, p1) + + +def test_t_circuit(): + """test t circuit""" + c = Circuit(1) + c.h(0) + c.t(0) + sim = StatevectorSimulator(circuit=c) + state = sim.statevector() + prob = sim.probs() + s1 = { + '0': 0.70710678 + 0.j, + '1': 0.5 + 0.5j + } + p1 = { + '0': 0.5, + '1': 0.5 + } + all_close(state, s1) + all_close(prob, p1) + + c = Circuit(1) + c.h(0) + c.td(0) + sim = StatevectorSimulator(circuit=c) + state = sim.statevector() + prob = sim.probs() + s1 = { + '0': 0.70710678 + 0.j, + '1': 0.5 - 0.5j + } + p1 = { + '0': 0.5, + '1': 0.5 + } + all_close(state, s1) + all_close(prob, p1) + + +def test_xyz_circuit(): + """test xyz circuit""" + c = Circuit(2) + c.h(0) + c.x(1) + sim = StatevectorSimulator(circuit=c) + state = sim.statevector() + prob = sim.probs() + s1 = { + '00': 0, + '01': 0, + '10': 0.70710678 + 0.j, + '11': 0.70710678 + 0.j, + } + p1 = { + '00': 0, + '01': 0, + '10': 0.5, + '11': 0.5, + } + all_close(state, s1) + all_close(prob, p1) + + c = Circuit(1) + c.h(0) + c.y(0) + sim = StatevectorSimulator(circuit=c) + state = sim.statevector() + prob = sim.probs() + s1 = { + '0': 0. - 0.70710678j, + '1': 0. + 0.70710678j, + } + p1 = { + '0': 0.5, + '1': 0.5, + } + all_close(state, s1) + all_close(prob, p1) + + c = Circuit(1) + c.h(0) + c.z(0) + sim = StatevectorSimulator(circuit=c) + state = sim.statevector() + prob = sim.probs() + s1 = { + '0': 0.70710678 + 0.j, + '1': -0.70710678 + 0.j, + } + p1 = { + '0': 0.5, + '1': 0.5, + } + all_close(state, s1) + all_close(prob, p1) + + +def test_rz_circuit(): + """test rz circuit""" + theta = Parameter('theta') + c = Circuit(1, parameters=[theta]) + c.h(0) + c.rz(0, theta) + c1 = c.assign_parameters(theta=np.pi / 3) + sim = StatevectorSimulator(circuit=c1) + state = sim.statevector() + prob = sim.probs() + s2 = { + '0': 0.61237244 - 0.35355339j, + '1': 0.61237244 + 0.35355339j, + } + p1 = { + '0': 0.5, + '1': 0.5 + } + all_close(state, s2) + all_close(prob, p1) + + +def test_cy_circuit(): + """test cy circuit""" + c = Circuit(2) + c.h(0) + c.cy(0, 1) + sim = StatevectorSimulator(circuit=c) + state = sim.statevector() + prob = sim.probs() + s2 = { + '00': 0.70710678 + 0.j, + '01': 0 - 0.j, + '10': 0 - 0.j, + '11': 0 + 0.70710678j + } + p1 = { + '00': 0.5, + '01': 0, + '10': 0., + '11': 0.5 + } + all_close(state, s2) + all_close(prob, p1) + + +def test_bell_state_circuit(): + """test bell state circuit""" + c = Circuit(2) + c.h(0) + c.cx(0, 1) + c.measure_all() + sim = StatevectorSimulator(circuit=c) + state = sim.statevector() + prob = sim.probs() + s3 = { + '00': 0.70710678 + 0.j, + '01': 0 + 0.j, + '10': 0 + 0.j, + '11': 0.70710678 + 0.j, + } + p3 = { + '00': 0.5, + '01': 0, + '10': 0, + '11': 0.5, + } + all_close(state, s3) + all_close(prob, p3) + + +def test_ccx_circuit(): + """test ccx circuit""" + c = Circuit([1, 5, 10]) + c.h(1) + c.h(5) + c.h(10) + c.ccx(1, 5, 10) + sim = StatevectorSimulator(circuit=c) + state = sim.statevector() + prob = sim.probs() + s1 = { + '000': 0.35355339 + 0.j, + '001': 0.35355339 + 0.j, + '010': 0.35355339 + 0.j, + '011': 0.35355339 + 0.j, + '100': 0.35355339 + 0.j, + '101': 0.35355339 + 0.j, + '110': 0.35355339 + 0.j, + '111': 0.35355339 + 0.j, + } + p1 = { + '000': 0.125, + '001': 0.125, + '010': 0.125, + '011': 0.125, + '100': 0.125, + '101': 0.125, + '110': 0.125, + '111': 0.125, + } + all_close(state, s1) + all_close(prob, p1) + + +def test_cx_circuit(): + """test cx circuit""" + c = Circuit(3) + c.h(0) + c.h(1) + c.h(2) + c.cx(0, 1) + c.cz(0, 2) + + sim = StatevectorSimulator(circuit=c) + state = sim.statevector() + prob = sim.probs() + + s1 = { + '000': 0.35355339 + 0.j, + '001': 0.35355339 + 0.j, + '010': 0.35355339 + 0.j, + '011': 0.35355339 + 0.j, + '100': 0.35355339 + 0.j, + '101': -0.35355339 + 0.j, + '110': 0.35355339 + 0.j, + '111': -0.35355339 + 0.j, + } + p1 = { + '000': 0.125, + '001': 0.125, + '010': 0.125, + '011': 0.125, + '100': 0.125, + '101': 0.125, + '110': 0.125, + '111': 0.125, + } + all_close(state, s1) + all_close(prob, p1) + + +def test_r_circuit(): + """test r circuit""" + theta = Parameter('theta') + c = Circuit(3, parameters=[theta]) + c.h(0) + c.h(1) + c.h(2) + c.rx(0, theta) + c.ry(1, theta) + c.rz(2, theta) + + sim = StatevectorSimulator(circuit=c.assign_parameters(theta=np.pi / 3)) + state = sim.statevector() + prob = sim.probs() + + s1 = { + '000': 0.06470476 - 1.12071934e-01j, + '001': 0.06470476 - 1.12071934e-01j, + '010': 0.24148146 - 4.18258152e-01j, + '011': 0.24148146 - 4.18258152e-01j, + '100': 0.12940952 - 0j, + '101': 0.12940952 - 0j, + '110': 0.48296291 + 0j, + '111': 0.48296291 - 0j + } + p1 = { + '000': 0.01674682, + '001': 0.01674682, + '010': 0.23325318, + '011': 0.23325318, + '100': 0.01674682, + '101': 0.01674682, + '110': 0.23325318, + '111': 0.23325318, + } + all_close(state, s1) + all_close(prob, p1) + + sim = StatevectorSimulator(circuit=c.assign_parameters(theta=np.pi / 10)) + state = sim.statevector() + prob = sim.probs() + + s1 = { + '000': 0.2795085 - 9.08178160e-02j, + '001': 0.2795085 - 9.08178160e-02j, + '010': 0.38471044 - 1.25000000e-01j, + '011': 0.38471044 - 1.25000000e-01j, + '100': 0.29389263 + 0j, + '101': 0.29389263 - 0j, + '110': 0.4045085 + 0j, + '111': 0.4045085 - 0j + } + + p1 = { + '000': 0.08637288, + '001': 0.08637288, + '010': 0.16362712, + '011': 0.16362712, + '100': 0.08637288, + '101': 0.08637288, + '110': 0.16362712, + '111': 0.16362712, + } + all_close(state, s1) + all_close(prob, p1) + + +def test_cr_circuit(): + """test cr gate""" + theta = Parameter('theta') + c = Circuit(4, parameters=[theta]) + c.h(0) + c.h(1) + c.h(2) + c.h(3) + c.cry(0, 1, theta) + c.cry(0, 2, theta) + c.cry(0, 3, theta) + + sim = StatevectorSimulator(circuit=c.assign_parameters(theta=np.pi / 4)) + state = sim.statevector() + prob = sim.probs() + s1 = { + '0000': 0.25 + 0j, + '0001': 0.03962817 + 0.j, + '0010': 0.25 + 0j, + '0011': 0.09567086 + 0.j, + '0100': 0.25 + 0j, + '0101': 0.09567086 + 0.j, + '0110': 0.25 + 0j, + '0111': 0.23096988 + 0.j, + + '1000': 0.25 + 0j, + '1001': 0.09567086 + 0.j, + '1010': 0.25 + 0j, + '1011': 0.23096988 + 0.j, + '1100': 0.25 + 0j, + '1101': 0.23096988 + 0.j, + '1110': 0.25 + 0j, + '1111': 0.55761062 + 0.j + } + p1 = { + '0000': 0.0625, '0001': 0.0015703916154427271, + '0010': 0.0625, '0011': 0.009152913087920383, + '0100': 0.0625, '0101': 0.009152913087920376, + '0110': 0.0625, '0111': 0.05334708691207958, + '1000': 0.0625, '1001': 0.00915291308792038, + '1010': 0.0625, '1011': 0.05334708691207958, + '1100': 0.0625, '1101': 0.053347086912079546, + '1110': 0.0625, '1111': 0.3109296083845571 + } + + all_close(state, s1) + all_close(prob, p1) + + theta = Parameter('theta') + c = Circuit(3, parameters=[theta]) + c.h(0) + c.h(1) + c.h(2) + c.crx(0, 1, theta) + c.cry(0, 2, theta) + sim = StatevectorSimulator(circuit=c.assign_parameters(theta=np.pi / 5)) + state = sim.statevector() + s1 = { + '000': 0.35355339 + 0.j, + '001': 0.21588531 - 0.07014539j, + '010': 0.35355339 + 0.j, + '011': 0.21588531 - 0.07014539j, + '100': 0.35355339 + 0.j, + '101': 0.42369878 - 0.13766808j, + '110': 0.35355339 + 0.j, + '111': 0.42369878 - 0.13766808j, + } + all_close(state, s1) + + +def test_qcis_gate_circuit(): + """test qcis gate circuit""" + c = Circuit(1) + c.h(0) + c.x2p(0) + sim = StatevectorSimulator(circuit=c) + state = sim.statevector() + s1 = { + '0': 0.5 - 0.5j, + '1': 0.5 - 0.5j + } + all_close(state, s1) + + c = Circuit(1) + c.h(0) + c.x2m(0) + sim = StatevectorSimulator(circuit=c) + state = sim.statevector() + s1 = { + '0': 0.5 + 0.5j, + '1': 0.5 + 0.5j + } + all_close(state, s1) + + c = Circuit(1) + c.h(0) + c.y2p(0) + sim = StatevectorSimulator(circuit=c) + state = sim.statevector() + s1 = { + '0': 0 + 0j, + '1': 1 + 0j + } + all_close(state, s1) + + c = Circuit(1) + c.h(0) + c.y2m(0) + sim = StatevectorSimulator(circuit=c) + state = sim.statevector() + s1 = { + '0': 1 + 0j, + '1': 0 + 0j + } + all_close(state, s1) + + c = Circuit(2) + c.h(0) + c.h(1) + c.x2p(0) + c.x2m(1) + c.y2p(0) + c.y2m(1) + sim = StatevectorSimulator(circuit=c) + state = sim.statevector() + s1 = { + '00': 0. + 0j, + '01': 1. + 0j, + '10': 0 + 0j, + '11': 0 + 0j + } + all_close(state, s1) + + +def test_qcis_gate_circuit_2(): + """test qcis gate circuit""" + theta = Parameter('theta') + c = Circuit(1, parameters=[theta]) + c.h(0) + c.xy(0, theta) + sim = StatevectorSimulator(circuit=c.assign_parameters(theta=np.pi / 10)) + state = sim.statevector() + s1 = { + '0': -0.21850801 - 0.67249851j, + '1': 0.21850801 - 0.67249851j + } + all_close(state, s1) + + theta = Parameter('theta') + c = Circuit(1, parameters=[theta]) + c.h(0) + c.xy2p(0, theta) + sim = StatevectorSimulator(circuit=c.assign_parameters(theta=np.pi / 4)) + state = sim.statevector() + s1 = { + '0': 0.14644661 - 0.35355339j, + '1': 0.85355339 - 0.35355339j + } + all_close(state, s1) + + c = Circuit(1, parameters=[theta]) + c.h(0) + c.xy2m(0, theta) + sim = StatevectorSimulator(circuit=c.assign_parameters(theta=np.pi / 4)) + state = sim.statevector() + s1 = { + '0': 0.85355339 + 0.35355339j, + '1': 0.14644661 + 0.35355339j + } + all_close(state, s1) + + c = Circuit(3, parameters=[theta]) + c.h(0) + c.h(1) + c.h(2) + c.xy(0, theta) + c.xy2p(1, theta) + c.xy2m(2, theta) + sim = StatevectorSimulator(circuit=c.assign_parameters(theta=np.pi / 10)) + state = sim.statevector() + s1 = { + '000': -1.97642354e-01 - 0.27203136j, + '001': 2.77555756e-17 - 0.33624926j, + '010': -1.43015351e-01 - 0.44015599j, + '011': 1.43015351e-01 - 0.44015599j, + '100': -7.54926615e-02 - 0.23234252j, + '101': 7.54926615e-02 - 0.23234252j, + '110': 5.55111512e-17 - 0.33624926j, + '111': 1.97642354e-01 - 0.27203136j + } + all_close(state, s1) + + +def test_swap_circuit(): + """ + """ + c = Circuit(2) + c.h(0) + c.x(1) + c.swap(0, 1) + sim = StatevectorSimulator(circuit=c) + state = sim.statevector() + s1 = { + '00': 0. + 0.j, + '01': 0.70710678 + 0.j, + '10': 0. + 0.j, + '11': 0.70710678 + 0.j + } + all_close(state, s1) + + c = Circuit(3) + c.h(0) + c.h(1) + c.h(2) + c.s(0) + c.swap(0, 1) + c.swap(0, 2) + sim = StatevectorSimulator(circuit=c) + state = sim.statevector() + s1 = { + '000': 0.35355339 + 0.j, + '001': 0.35355339 + 0.j, + '010': -0. + 0.35355339j, + '011': -0. + 0.35355339j, + '100': 0.35355339 + 0.j, + '101': 0.35355339 + 0.j, + '110': -0. + 0.35355339j, + '111': -0. + 0.35355339j + } + all_close(state, s1) + + +def test_u(): + """Test U""" + theta = Parameter('theta') + phi = Parameter('phi') + lam = Parameter('lam') + c = Circuit(2, parameters=['theta', 'phi', 'lam']) + c.h(0) + c.h(1) + c.u(1, theta, phi, lam) + sim = StatevectorSimulator(circuit=c.assign_parameters({ + 'theta': np.pi / 3, + 'phi': np.pi / 4, + 'lam': np.pi / 5, + })) + state = sim.statevector() + s1 = { + '00': 0.23075845 - 0.14694631j, + '01': 0.23075845 - 0.14694631j, + '10': 0.24451481 + 0.60445829j, + '11': 0.24451481 + 0.60445829j + } + all_close(state, s1) + + sim = StatevectorSimulator(circuit=c.assign_parameters({ + 'theta': np.pi / 10, + 'phi': np.pi / 9, + 'lam': np.pi / 8, + })) + state = sim.statevector() + s1 = { + '00': 0.42158087 - 0.02993244j, + '01': 0.42158087 - 0.02993244j, + '10': 0.43760027 + 0.36038815j, + '11': 0.43760027 + 0.36038815j + } + all_close(state, s1) + + # u1 + lam = Parameter('lam') + c = Circuit(2, parameters=['lam']) + c.h(0) + c.h(1) + c.u(1, 0, 0, lam) + sim = StatevectorSimulator(circuit=c.assign_parameters({ + 'lam': np.pi / 5, + })) + state = sim.statevector() + s1 = { + '00': 0.5 + 0.j, + '01': 0.5 + 0.j, + '10': 0.4045085 + 0.29389263j, + '11': 0.4045085 + 0.29389263j + } + all_close(state, s1) + + # u2 + phi = Parameter('phi') + lam = Parameter('lam') + c = Circuit(2, parameters=['lam', 'phi']) + c.h(0) + c.h(1) + c.u(1, math.pi / 2, phi, lam) + sim = StatevectorSimulator(circuit=c.assign_parameters({ + 'phi': -np.pi / 9, + 'lam': np.pi / 5, + })) + state = sim.statevector() + s1 = { + '00': 0.06752269 - 0.20781347j, + '01': 0.06752269 - 0.20781347j, + '10': 0.67208884 - 0.02346986j, + '11': 0.67208884 - 0.02346986j + } + all_close(state, s1) + + +def test_memory(): + """test memory recycling""" + process = psutil.Process(os.getpid()) + initial_mem = process.memory_info().rss + + def run_simulation(num_qubits): + c = Circuit(num_qubits) + for i in range(num_qubits): + c.h(i) + c.measure_all() + + sim = StatevectorSimulator(c) + state = sim.statevector() + return sim, state + + for i in range(10, 21, 2): + print(f'\nqubits: {i}') + sim, state = run_simulation(i) + peak_mem = process.memory_info().rss + del sim + del state + gc.collect() + final_mem = process.memory_info().rss + + used_mem = peak_mem - initial_mem + remaining_mem = final_mem - initial_mem + print(f"initial: {initial_mem / 1024 / 1024:.2f} MB") + print(f"peak: {peak_mem / 1024 / 1024:.2f} MB (+{used_mem / 1024 / 1024:.2f} MB)") + print(f"released: {final_mem / 1024 / 1024:.2f} MB (remain: {remaining_mem / 1024 / 1024:.2f} MB)") + + # Allow a maximum of 10 MB of temporary residual data + max_allowed_leak = 10 * 1024 * 1024 + assert remaining_mem < max_allowed_leak, \ + f"Memory leak detected. Residual memory: {remaining_mem / 1024 / 1024:.2f} MB" diff --git a/tests/utils/qasm2/__init__.py b/tests/utils/qasm2/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..2ccc943871ee6cd4b2012adcb22a4ded13a54bb2 --- /dev/null +++ b/tests/utils/qasm2/__init__.py @@ -0,0 +1,12 @@ +# This code is part of cqlib. +# +# Copyright (C) 2024 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. diff --git a/tests/utils/qasm2/test_dump.py b/tests/utils/qasm2/test_dump.py new file mode 100644 index 0000000000000000000000000000000000000000..fc3df07cfd13c977a07eb21a466d7b9a44ed5e97 --- /dev/null +++ b/tests/utils/qasm2/test_dump.py @@ -0,0 +1,193 @@ +# This code is part of cqlib. +# +# Copyright (C) 2024 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Test OpenQASM2.0 dump""" + +import numpy as np + +from cqlib.circuits.circuit import Circuit +from cqlib.circuits.parameter import Parameter +from cqlib.utils.qasm2 import loads + +from .test_load import assert_circuit_equal + + +def test_base(): + """ + basic test + """ + circuit = Circuit(2) + circuit.h(0) + circuit.cx(0, 1) + circuit.measure_all() + + qasm = """// Generated by Cqlib v1.2.1 + OPENQASM 2.0; + include "qelib1.inc"; + + qreg q[2]; + + creg c[2]; + + h q[0]; + cx q[0], q[1]; + measure q[0] -> c[0]; + measure q[1] -> c[1]; + """ + assert_circuit_equal(circuit.to_qasm2(), qasm) + + +def test_all_gate(): + """ + test all gate + + 'H', + 'I', + 'RX', 'CRX', + 'RXY', + 'RY', 'CRY', + 'RZ', 'CRZ', + 'S', 'SD', + 'SWAP', + 'T', 'TD', + 'U', + 'X', 'CX', 'CCX', 'CNOT', 'CCNOT', 'X2P', 'X2M', + 'XY', 'XY2P', 'XY2M', + 'Y', 'Y2M', 'Y2P', 'CY', + 'Z', 'CZ' + + """ + theta = Parameter('theta') + phi = Parameter('phi') + lam = Parameter('lam') + circuit = Circuit(4, [theta, phi, lam]) + circuit.h(0) + circuit.rx(0, theta) + circuit.crx(0, 1, theta) + circuit.rxy(0, phi, theta) + circuit.rz(0, theta) + circuit.crz(1, 2, theta) + circuit.s(0) + circuit.sd(1) + circuit.swap(0, 3) + circuit.t(0) + circuit.td(3) + circuit.u(1, theta, phi, lam) + circuit.x(1) + circuit.cx(1, 2) + circuit.ccx(1, 2, 3) + circuit.x2p(0) + circuit.x2m(0) + circuit.xy(1, theta) + circuit.xy2m(2, theta) + circuit.xy2p(3, theta) + + circuit.y(0) + circuit.y2m(0) + circuit.y2p(0) + circuit.cy(0, 1) + circuit.z(0) + circuit.cz(0, 1) + + circuit.measure_all() + circuit = circuit.assign_parameters( + theta=np.pi / 4, + phi=np.pi / 9, + lam=np.pi / 10 + ) + + qasm = """// Generated by Cqlib v1.2.1 + OPENQASM 2.0; + include "qelib1.inc"; + + qreg q[4]; + creg c[4]; + + // H Q0 + h q[0]; + // rx Q0 theta + rx(0.7853981634) q[0]; + // crx Q0 Q1 theta + s q[1]; + cx q[0], q[1]; + ry(-0.7853981634 / 2) q[1]; + cx q[0], q[1]; + ry(0.7853981634 / 2) q[1]; + sdg q[1]; + // rxy Q0 phi, theta + u3(0.7853981634,0.3490658504 - pi/2,pi/2 - 0.3490658504) q[0]; + // rz Q0 theta + rz(0.7853981634) q[0]; + // CRZ Q1 Q2 theta + rz(0.7853981634 / 2) q[2]; + cx q[1], q[2]; + rz(-0.7853981634 / 2) q[2]; + cx q[1], q[2]; + // S Q0 + s q[0]; + // SD Q1 + sdg q[1]; + // SWAP Q0 Q1 + cx q[0], q[3]; + cx q[3], q[0]; + cx q[0], q[3]; + // T Q0 + t q[0]; + // TD Q3 + tdg q[3]; + // u + u3(0.7853981634,0.3490658504,0.3141592654) q[1]; + // x + x q[1]; + // cx + cx q[1], q[2]; + // ccx + ccx q[1], q[2], q[3]; + // x2p + sx q[0]; + // x2m + sxdg q[0]; + + // xy + rz(pi/2 - 0.7853981634) q[1]; + y q[1]; + rz(0.7853981634 - pi/2) q[1]; + // xy2m + rz(-pi/2 - 0.7853981634) q[2]; + ry(pi/2) q[2]; + rz(0.7853981634 + pi/2) q[2]; + //mxy2p + rz(pi/2 - 0.7853981634) q[3]; + ry(pi/2) q[3]; + rz(0.7853981634 - pi/2) q[3]; + // y + y q[0]; + // y2m + ry(-pi/2) q[0]; + // y2p + ry(pi/2) q[0]; + // cy + cy q[0], q[1]; + // z + z q[0]; + // cz + cz q[0], q[1]; + measure q[0] -> c[0]; + measure q[1] -> c[1]; + measure q[2] -> c[2]; + measure q[3] -> c[3]; + """ + qasm2 = circuit.to_qasm2() + assert_circuit_equal(qasm2, qasm) + c2 = loads(qasm=qasm2) + assert len(c2.qubits) == 4 diff --git a/tests/utils/qasm2/test_load.py b/tests/utils/qasm2/test_load.py new file mode 100644 index 0000000000000000000000000000000000000000..93bf077121915da610136a121827e15665b732ad --- /dev/null +++ b/tests/utils/qasm2/test_load.py @@ -0,0 +1,718 @@ +# This code is part of cqlib. +# +# Copyright (C) 2024 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""test OpenQASM2.0 dump""" + +from cqlib.exceptions import QASMParserError +from cqlib.utils.qasm2 import loads +from cqlib.utils.qasm2._parse import OpenQASM2Converter + + +def assert_circuit_equal(circuit_0: str, circuit_1: str): + """ + Assert circuit equal + + Args: + circuit_0(str): circuit 0 + circuit_1(str): circuit 1 + """ + + def pure_circuit(circuit: str): + lines = [] + for c in circuit.split('\n'): + c = c.strip() + if not c or c.startswith('//'): + continue + lines.append(c) + return '\n'.join(lines) + + assert pure_circuit(circuit_0) == pure_circuit(circuit_1) + + +def test_base(): + """ + test base qasm + """ + qasm = """ + OPENQASM 2.0; + include "qelib1.inc"; + qreg q[3]; + qreg r[3]; + h q; + cx q, r; + creg c[3]; + creg d[3]; + barrier q; + measure q->c; + measure r->d; + """ + circuit = loads(qasm) + qcis = """H Q0 + H Q1 + H Q2 + CX Q0 Q3 + CX Q1 Q4 + CX Q2 Q5 + B Q0 Q1 Q2 + M Q0 + M Q1 + M Q2 + M Q3 + M Q4 + M Q5""" + assert_circuit_equal(circuit.as_str(), qcis) + + +def test_all_gate(): + """ + test all supported gates + """ + qasm = """ + OPENQASM 2.0; + include "qelib1.inc"; + qreg q[3]; + creg c[3]; + + // note that the order and where the gates are applied to is important! + + // "abstract" gates (for legacy) + U(0.2,0.1,0.6) q[0]; // Creates u + CX q[0], q[1]; // Creates cx + + // the hardware primitives + u3(0.2,0.1,0.6) q[0]; + u2(0.1,0.6) q[0]; + u1(0.6) q[0]; + id q[0]; + cx q[0], q[1]; + + // the standard single qubit gates + u(0.2, 0.1, 0.6) q[0]; + p(0.6) q[0]; + x q[0]; + y q[0]; + z q[0]; + h q[0]; + s q[0]; + t q[0]; + sdg q[0]; + tdg q[0]; + sx q[0]; + sxdg q[0]; + + // the standard rotations + rx(0.1) q[0]; + ry(0.1) q[0]; + rz(0.1) q[0]; + + // the barrier + barrier q; + + // the standard user-defined gates + swap q[0], q[1]; + cswap q[0], q[1], q[2]; + + cy q[0], q[1]; + cz q[0], q[1]; + ch q[0], q[1]; + csx q[0], q[1]; + ccx q[0], q[1], q[2]; + + crx(0.6) q[0], q[1]; + cry(0.6) q[0], q[1]; + crz(0.6) q[0], q[1]; + + // measure + measure q->c; + """ + circuit = loads(qasm) + qcis = """ + // U(0.2,0.1,0.6) q[0]; + U Q0 0.2 0.1 0.6 + // CX q[0], q[1]; + CX Q0 Q1 + // u3(0.2,0.1,0.6) q[0]; + U Q0 0.2 0.1 0.6 + // u2(0.1,0.6) q[0]; + U Q0 1.5707963267948966 0.1 0.6 + // u1(0.6) q[0]; + U Q0 0 0 0.6 + // id q[0]; + I Q0 60 + // cx q[0], q[1]; + CX Q0 Q1 + // u(0.2, 0.1, 0.6) q[0]; + U Q0 0.2 0.1 0.6 + // p(0.6) q[0]; + U Q0 0 0 0.6 + // x q[0]; + X Q0 + // y q[0]; + Y Q0 + // z q[0]; + Z Q0 + // h q[0]; + H Q0 + // s q[0]; + S Q0 + // t q[0]; + T Q0 + // sdg q[0]; + SD Q0 + // tdg q[0]; + TD Q0 + // sx q[0]; + X2P Q0 + // sxdg q[0]; + X2M Q0 + // rx(0.1) q[0]; + RX Q0 0.1 + // ry(0.1) q[0]; + RY Q0 0.1 + // rz(0.1) q[0]; + RZ Q0 0.1 + // barrier q; + B Q0 Q1 Q2 + // swap q[0], q[1]; + SWAP Q0 Q1 + // cswap q[0], q[1], q[2]; + CX Q2 Q1 + CCX Q0 Q1 Q2 + CX Q2 Q1 + // cy q[0], q[1]; + CY Q0 Q1 + // cz q[0], q[1]; + CZ Q0 Q1 + // ch q[0], q[1]; + RY Q1 -0.7853981633974483 + RZ Q1 1.5707963267948966 + CZ Q0 Q1 + RZ Q1 -1.5707963267948966 + RY Q1 0.7853981633974483 + + // csx q[0], q[1]; + RZ Q0 0.7853981633974483 + H Q1 + CX Q0 Q1 + RZ Q1 -0.7853981633974483 + CX Q0 Q1 + RZ Q1 0.7853981633974483 + H Q1 + // ccx q[0], q[1], q[2]; + CCX Q0 Q1 Q2 + // crx(0.6) q[0], q[1]; + CRX Q0 Q1 0.6 + // cry(0.6) q[0], q[1]; + CRY Q0 Q1 0.6 + // crz(0.6) q[0], q[1]; + CRZ Q0 Q1 0.6 + // measure q->c; + M Q0 + M Q1 + M Q2""" + assert_circuit_equal(circuit.as_str(), qcis) + + +def test_define(): + """ + test version define + """ + qasm = """OPENQASM 2.0;""" + circuit = loads(qasm) + assert circuit.as_str() == '' + + +def test_include(): + """ + test include qelib1.inc + """ + qasm = """OPENQASM 2.0;\ninclude "qelib1.inc";""" + circuit = loads(qasm) + assert circuit.as_str() == '' + cv = OpenQASM2Converter() + cv.parse(qasm_str=qasm) + assert cv.include_qelib1 + + +def test_include_error(): + """ + test include qelib1.inc + """ + qasm = """OPENQASM 2.0;\ninclude "abcd.inc";""" + try: + loads(qasm) + except QASMParserError as e: + assert e.message == ('Unsupported file included: "abcd.inc". ' + 'Only \'qelib1.inc\' is supported.') + qasm = """OPENQASM 2.0; + qreg q[3]; + u3(0, 1, 2) q[0]; + """ + try: + loads(qasm) + except QASMParserError as e: + assert e.message == 'Unknown gate "u3", did you forget to include qelib1.inc?' + + qasm = """OPENQASM 2.0; + include "qelib1.inc"; + qreg q[3]; + u3(0, 1, 2) q[0]; + """ + cv = OpenQASM2Converter() + circuit = cv.parse(qasm_str=qasm) + assert cv.include_qelib1 + assert circuit.as_str() == 'U Q0 0.0 1.0 2.0' + + +def test_register(): + """ + test register + """ + qasm = """OPENQASM 2.0; +qreg q[1]; +qreg q[2]; +""" + try: + loads(qasm) + except QASMParserError as e: + assert e.message == 'Duplicate qreg definitions: qregq[2];' + + qasm = """OPENQASM 2.0; +qreg q[2]; +""" + circuit = loads(qasm) + assert circuit.as_str() == '' + assert len(circuit.qubits) == 2 + assert str(circuit.qubits) == '[Qubit(0), Qubit(1)]' + + qasm = """OPENQASM 2.0; +qreg q[3]; +creg c[2]; +""" + circuit = loads(qasm) + assert circuit.as_str() == '' + assert len(circuit.qubits) == 3 + + +def test_abstract_gate(): + """ + test abstract gate + """ + qasm = """ + OPENQASM 2.0; + qreg q[3]; + creg c[3]; + + U(0.2,0.1,0.6) q[0]; + CX q[0], q[1]; + """ + circuit = loads(qasm) + assert circuit.as_str() == 'U Q0 0.2 0.1 0.6\nCX Q0 Q1' + assert len(circuit.qubits) == 3 + + +def test_barrier_gate(): + """Test barrier gate""" + qasm = """ + OPENQASM 2.0; + qreg q[3]; + creg c[3]; + + barrier q; + barrier q[0], q[1]; + """ + circuit = loads(qasm) + assert circuit.as_str() == 'B Q0 Q1 Q2\nB Q0 Q1' + assert len(circuit.qubits) == 3 + + +def test_hardware_primitives(): + """ + test qasm2.0 hardware primitives gates + + u3(0.2,0.1,0.6) q[0]; + u2(0.1,0.6) q[0]; + u1(0.6) q[0]; + id q[0]; + cx q[0], q[1]; + """ + qasm = """ + OPENQASM 2.0; + include "qelib1.inc"; + + qreg q[3]; + qreg q2[3]; + creg c[3]; + + u3(0.2,0.1,0.6) q[0]; + u2(0.1,0.6) q[0]; + u1(0.6) q[0]; + cx q[0], q[1]; + cx q, q2; + id q[0]; + barrier q; + barrier q[0], q[1]; + """ + circuit = loads(qasm) + qcis = """ +U Q0 0.2 0.1 0.6 +U Q0 1.5707963267948966 0.1 0.6 +U Q0 0 0 0.6 +CX Q0 Q1 +CX Q0 Q3 +CX Q1 Q4 +CX Q2 Q5 +I Q0 60 +B Q0 Q1 Q2 +B Q0 Q1 +""" + assert_circuit_equal(circuit.as_str(), qcis) + assert len(circuit.qubits) == 6 + + +def test_standard_single_qubit_gate(): + """test standard single qubit gate""" + qasm = """ + OPENQASM 2.0; + include "qelib1.inc"; + + qreg q[3]; + creg c[3]; + + u(0.2, 0.1, 0.6) q[0]; + p(0.6) q[0]; + x q[0]; + x q; + y q[0]; + z q[0]; + h q[0]; + s q[0]; + t q[0]; + sdg q[0]; + tdg q[0]; + sx q[0]; + sxdg q[0]; + """ + circuit = loads(qasm) + qcis = """ + U Q0 0.2 0.1 0.6 + U Q0 0 0 0.6 + X Q0 + X Q0 + X Q1 + X Q2 + Y Q0 + Z Q0 + H Q0 + S Q0 + T Q0 + SD Q0 + TD Q0 + X2P Q0 + X2M Q0 + """ + assert_circuit_equal(circuit.as_str(), qcis) + + +def test_rotations(): + """test rotations""" + qasm = """ + OPENQASM 2.0; + include "qelib1.inc"; + + qreg q[3]; + creg c[3]; + + rx(0.1) q; + rx(0.1) q[0]; + ry(0.2) q[0]; + rz(0.3) q[0]; + """ + circuit = loads(qasm) + qcis = """ + RX Q0 0.1 + RX Q1 0.1 + RX Q2 0.1 + RX Q0 0.1 + RY Q0 0.2 + RZ Q0 0.3 + """ + assert_circuit_equal(circuit.as_str(), qcis) + + +def test_user_define_gate(): + """test user define gate""" + qasm = """ + OPENQASM 2.0; + include "qelib1.inc"; + qreg q[3]; + + swap q[0], q[1]; + cswap q[0], q[1], q[2]; + + cy q[0], q[1]; + cz q[0], q[1]; + ch q[0], q[1]; + csx q[0], q[1]; + ccx q[0], q[1], q[2]; + + crx(0.6) q[0], q[1]; + cry(0.6) q[0], q[1]; + crz(0.6) q[0], q[1]; + """ + circuit = loads(qasm) + qcis = """ + // swap q[0], q[1]; + SWAP Q0 Q1 + // cswap q[0], q[1], q[2]; + CX Q2 Q1 + CCX Q0 Q1 Q2 + CX Q2 Q1 + // cy q[0], q[1]; + CY Q0 Q1 + // cz q[0], q[1]; + CZ Q0 Q1 + // ch q[0], q[1]; + RY Q1 -0.7853981633974483 + RZ Q1 1.5707963267948966 + CZ Q0 Q1 + RZ Q1 -1.5707963267948966 + RY Q1 0.7853981633974483 + // csx q[0], q[1]; + RZ Q0 0.7853981633974483 + H Q1 + CX Q0 Q1 + RZ Q1 -0.7853981633974483 + CX Q0 Q1 + RZ Q1 0.7853981633974483 + H Q1 + // ccx q[0], q[1], q[2]; + CCX Q0 Q1 Q2 + // crx(0.6) q[0], q[1]; + CRX Q0 Q1 0.6 + // cry(0.6) q[0], q[1]; + CRY Q0 Q1 0.6 + // crz(0.6) q[0], q[1]; + CRZ Q0 Q1 0.6 + """ + assert_circuit_equal(circuit.as_str(), qcis) + + +def test_measure(): + """test measure""" + qasm = """ + OPENQASM 2.0; + include "qelib1.inc"; + qreg q[3]; + qreg q2[2]; + creg c[3]; + creg c2[3]; + + measure q->c; + measure q2[0]->c2[1]; + """ + circuit = loads(qasm) + assert circuit.as_str() == 'M Q0\nM Q1\nM Q2\nM Q3' + + +def test_self_define_gate_0(): + """Test self define gate""" + qasm = """ + OPENQASM 2.0; + include "qelib1.inc"; + qreg q[3]; + creg c[3]; + + gate mygate(theta, phi) q { + u3(theta, phi, 0) q; + U(theta, phi, 0) q; + h q; + } + mygate(0.1, 0.2) q[2]; + + measure q->c; + """ + circuit = loads(qasm) + qcis = """ + U Q2 0.1 0.2 0.0 + U Q2 0.1 0.2 0.0 + H Q2 + M Q0 + M Q1 + M Q2 + """ + assert_circuit_equal(circuit.as_str(), qcis) + + +def test_self_define_gate_1(): + """Test self define gate""" + qasm = """ + OPENQASM 2.0; + include "qelib1.inc"; + qreg q1[3]; + qreg q2[3]; + creg c[3]; + + gate mygate q, qq { + cz q, qq; + } + gate mygate2() q, qq { + cx q, qq; + } + mygate q1, q2; + mygate2 q1[0], q2[1]; + """ + circuit = loads(qasm) + qcis = """ + CZ Q0 Q3 + CZ Q1 Q4 + CZ Q2 Q5 + CX Q0 Q4 + """ + assert_circuit_equal(circuit.as_str(), qcis) + + +def test_self_define_gate_2(): + """Test self define gate""" + qasm = """ + OPENQASM 2.0; + include "qelib1.inc"; + qreg q1[3]; + qreg q2[3]; + creg c[3]; + + gate mygate q, qq { + cz q, qq; + } + mygate q1, q2[1]; + """ + circuit = loads(qasm) + qcis = """ + CZ Q0 Q4 + CZ Q1 Q4 + CZ Q2 Q4 + """ + assert_circuit_equal(circuit.as_str(), qcis) + + +def test_qubits(): + """test qubits""" + qasm = """ + OPENQASM 2.0; + include "qelib1.inc"; + qreg q1[3]; + qreg q2[3]; + creg c[3]; + cz q1, q2; + cx q1, q2[0]; + CX q1[0], q2[0]; + cz q2[0], q1; + """ + circuit = loads(qasm) + qcis = """ + // cz q1, q2; + CZ Q0 Q3 + CZ Q1 Q4 + CZ Q2 Q5 + // cx q1, q2[0]; + CX Q0 Q3 + CX Q1 Q3 + CX Q2 Q3 + // CX q1[0], q2[0]; + CX Q0 Q3 + // cz q2[0], q1; + CZ Q3 Q0 + CZ Q3 Q1 + CZ Q3 Q2 + """ + assert_circuit_equal(circuit.as_str(), qcis) + + +def test_funcs(): + """test funcs""" + qasm = """ + OPENQASM 2.0; + include "qelib1.inc"; + qreg q[3]; + creg c[3]; + + rx(sin(pi/2)) q; + ry(cos(pi/2)) q[0]; + rz(tan(1)) q[0]; + rx(exp(2 * 2)) q[0]; + rz(ln(1)) q[0]; + rx(sqrt(2 * 2)) q[0]; + rz(acos(1 / 2)) q[0]; + rz(atan(1)) q[0]; + rz(asin(1)) q[0]; + """ + circuit = loads(qasm) + qcis = """ + RX Q0 1.0 + RX Q1 1.0 + RX Q2 1.0 + RY Q0 -0.0 + RZ Q0 1.5574077247 + RX Q0 54.5981500331 + RZ Q0 0.0 + RX Q0 2.0 + RZ Q0 1.0471975512 + RZ Q0 0.7853981634 + RZ Q0 1.5707963268 + """ + assert_circuit_equal(circuit.as_str(), qcis) + + +def test_ops(): + """test ops +-*/^""" + qasm = """ + OPENQASM 2.0; + include "qelib1.inc"; + qreg q[3]; + creg c[3]; + + rx(sin(pi*3)) q[0]; + rx(pi/2) q[0]; + rx(1+2) q[0]; + rx(1-2*2) q[0]; + """ + circuit = loads(qasm) + qcis = """ + RX Q0 -0.0 + RX Q0 1.5707963268 + RX Q0 3.0 + RX Q0 -3.0 + """ + assert_circuit_equal(circuit.as_str(), qcis) + + +def test_ops_1(): + """test ops +-*/^""" + qasm = """ + OPENQASM 2.0; + include "qelib1.inc"; + qreg q[3]; + creg c[3]; + + gate mygate(theta_0, theta_1) q { + rx((theta_0 + theta_1) * 2 -3) q; + } + mygate(2.2, 3.3) q; + """ + circuit = loads(qasm) + qcis = """ + RX Q0 8.0 + RX Q1 8.0 + RX Q2 8.0 + """ + assert_circuit_equal(circuit.as_str(), qcis) diff --git a/tests/visualization/.gitignore b/tests/visualization/.gitignore deleted file mode 100644 index e33609d251c814ccd3a30337c965a875645c2117..0000000000000000000000000000000000000000 --- a/tests/visualization/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.png diff --git a/tests/core/__init__.py b/tests/visualization/circuit/__init__.py similarity index 62% rename from tests/core/__init__.py rename to tests/visualization/circuit/__init__.py index 46cebe9041bb194839a2dd6dca239a8ec3640ccd..185105c53ab49ffccccc08a41b0653eb73191089 100644 --- a/tests/core/__init__.py +++ b/tests/visualization/circuit/__init__.py @@ -1,12 +1,12 @@ # This code is part of cqlib. -# # -# (C) Copyright qc.zdxlz.com 2024. -# # +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# # This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory +# obtain a copy of this license in the LICENSE file in the root directory # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# # +# # Any modifications or derivative works of this code must retain this # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -# diff --git a/tests/visualization/circuit/baseline/test_barrier.png b/tests/visualization/circuit/baseline/test_barrier.png new file mode 100644 index 0000000000000000000000000000000000000000..cc9f3fbf6ddf470acd7570bb2fc76fef76a22be3 Binary files /dev/null and b/tests/visualization/circuit/baseline/test_barrier.png differ diff --git a/tests/visualization/circuit/baseline/test_basic.png b/tests/visualization/circuit/baseline/test_basic.png new file mode 100644 index 0000000000000000000000000000000000000000..ac6b3807f06e0f42fb62ec051e573362dcdfc3c8 Binary files /dev/null and b/tests/visualization/circuit/baseline/test_basic.png differ diff --git a/tests/visualization/circuit/baseline/test_gates_0.png b/tests/visualization/circuit/baseline/test_gates_0.png new file mode 100644 index 0000000000000000000000000000000000000000..9f5a4e1346eaed3726d795c52260618055d0d5b0 Binary files /dev/null and b/tests/visualization/circuit/baseline/test_gates_0.png differ diff --git a/tests/visualization/circuit/baseline/test_gates_1.png b/tests/visualization/circuit/baseline/test_gates_1.png new file mode 100644 index 0000000000000000000000000000000000000000..7bab4c2bbc72b265cc8879778289cd2df76d32a4 Binary files /dev/null and b/tests/visualization/circuit/baseline/test_gates_1.png differ diff --git a/tests/visualization/circuit/baseline/test_gray.png b/tests/visualization/circuit/baseline/test_gray.png new file mode 100644 index 0000000000000000000000000000000000000000..b09ba7b937c48b9b83a66d7f69f76061bdd4119d Binary files /dev/null and b/tests/visualization/circuit/baseline/test_gray.png differ diff --git a/tests/visualization/circuit/baseline/test_long_theta.png b/tests/visualization/circuit/baseline/test_long_theta.png new file mode 100644 index 0000000000000000000000000000000000000000..55dc2cdec1450a26461316365ddc6f1bf938fcbb Binary files /dev/null and b/tests/visualization/circuit/baseline/test_long_theta.png differ diff --git a/tests/visualization/circuit/baseline/test_moment.png b/tests/visualization/circuit/baseline/test_moment.png new file mode 100644 index 0000000000000000000000000000000000000000..e22199ebe5b75e31f956fe59c7be7dda12e2635d Binary files /dev/null and b/tests/visualization/circuit/baseline/test_moment.png differ diff --git a/tests/visualization/circuit/baseline/test_parameter.png b/tests/visualization/circuit/baseline/test_parameter.png new file mode 100644 index 0000000000000000000000000000000000000000..915cc11ea20bbfb5b6dfa6d701bb9173956f57a0 Binary files /dev/null and b/tests/visualization/circuit/baseline/test_parameter.png differ diff --git a/tests/visualization/circuit/baseline/test_phy_qubits.png b/tests/visualization/circuit/baseline/test_phy_qubits.png new file mode 100644 index 0000000000000000000000000000000000000000..3c31484cf902f0c806f0a93e81e89ee65de1bdfd Binary files /dev/null and b/tests/visualization/circuit/baseline/test_phy_qubits.png differ diff --git a/tests/visualization/circuit/baseline/test_rotation_gates.png b/tests/visualization/circuit/baseline/test_rotation_gates.png new file mode 100644 index 0000000000000000000000000000000000000000..592eb5edfbc61908e70b2c49a12088f552d6c266 Binary files /dev/null and b/tests/visualization/circuit/baseline/test_rotation_gates.png differ diff --git a/tests/visualization/circuit/baseline/test_swap.png b/tests/visualization/circuit/baseline/test_swap.png new file mode 100644 index 0000000000000000000000000000000000000000..991ede9e82b148360b659f882aab35851c52447d Binary files /dev/null and b/tests/visualization/circuit/baseline/test_swap.png differ diff --git a/tests/visualization/circuit/baseline/test_title.png b/tests/visualization/circuit/baseline/test_title.png new file mode 100644 index 0000000000000000000000000000000000000000..fbdc0fa0f681ac06a2c9c6efb0acdb886137a99c Binary files /dev/null and b/tests/visualization/circuit/baseline/test_title.png differ diff --git a/tests/visualization/circuit/test_plt.py b/tests/visualization/circuit/test_plt.py new file mode 100644 index 0000000000000000000000000000000000000000..eab83ce5103150fa6662889d0ca645a76e7dc0e6 --- /dev/null +++ b/tests/visualization/circuit/test_plt.py @@ -0,0 +1,135 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" Test matplotlib visualization. + +run: +```bash +pytest .\\tests\\visualization\\circuit\\test_plt.py::TestCircuitPltVis --mpl +``` +""" + +# pylint: disable=missing-function-docstring +from math import pi + +import matplotlib +import pytest + +from cqlib import Circuit, Parameter +from cqlib.visualization.circuit import draw_mpl + + +class TestCircuitPltVis: + """ Test matplotlib visualization.""" + + def setup_method(self): + matplotlib.use('Agg') + + @pytest.mark.mpl_image_compare + def test_basic(self): + circuit = Circuit(2) + circuit.h(0) + circuit.cx(0, 1) + circuit.measure_all() + figure = draw_mpl(circuit) + return figure + + @pytest.mark.mpl_image_compare + def test_gray(self): + circuit = Circuit(2) + circuit.h(0) + circuit.cx(0, 1) + circuit.measure_all() + figure = draw_mpl(circuit, style='gray') + return figure + + @pytest.mark.mpl_image_compare + def test_parameter(self): + theta = Parameter('theta') + circuit = Circuit(2, parameters=[theta]) + circuit.h(0) + circuit.rx(1, theta) + circuit.cry(0, 1, theta) + circuit.measure_all() + return draw_mpl(circuit) + + @pytest.mark.mpl_image_compare + def test_rotation_gates(self): + circuit = Circuit(3) + circuit.h(0) + circuit.rx(1, 0.1) + circuit.ry(2, 0.2) + circuit.rz(0, 0.3) + circuit.measure_all() + return draw_mpl(circuit) + + @pytest.mark.mpl_image_compare + def test_moment(self): + circuit = Circuit(3) + circuit.h(1) + circuit.cx(0, 2) + circuit.measure_all() + return draw_mpl(circuit) + + @pytest.mark.mpl_image_compare + def test_long_theta(self): + circuit = Circuit(3) + circuit.h(0) + circuit.rx(1, pi) + circuit.measure_all() + return draw_mpl(circuit) + + @pytest.mark.mpl_image_compare + def test_phy_qubits(self): + circuit = Circuit([1, 7, 13]) + circuit.h(1) + circuit.cx(7, 13) + circuit.measure_all() + return draw_mpl(circuit) + + @pytest.mark.mpl_image_compare + def test_swap(self): + circuit = Circuit(3) + circuit.swap(0, 1) + circuit.swap(1, 2) + circuit.measure_all() + return draw_mpl(circuit) + + @pytest.mark.mpl_image_compare + def test_gates_0(self): + theta = Parameter('theta') + circuit = Circuit(5, parameters=[theta]) + circuit.h(0) + circuit.i(1, 100) + circuit.rx(2, 0.123) + circuit.crx(3, 4, theta) + circuit.rxy(0, 1, 0.123) + circuit.ry(1, 0.123) + circuit.cry(2, 3, 0.123) + circuit.rz(4, theta) + circuit.measure_all() + return draw_mpl(circuit) + + @pytest.mark.mpl_image_compare + def test_gates_1(self): + theta = Parameter('theta') + phi = Parameter('phi') + circuit = Circuit(5, parameters=[theta, phi]) + circuit.s(0) + circuit.sd(1) + circuit.swap(2, 3) + circuit.t(4) + circuit.td(0) + circuit.u(1, theta, phi, 0.123) + circuit.measure_all() + return draw_mpl(circuit) diff --git a/tests/visualization/circuit/test_text.py b/tests/visualization/circuit/test_text.py new file mode 100644 index 0000000000000000000000000000000000000000..b3fc85fb80499cbdb4bedc7cc645429eda916240 --- /dev/null +++ b/tests/visualization/circuit/test_text.py @@ -0,0 +1,335 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" test text visualization """ +from math import pi + +from cqlib.circuits.circuit import Circuit +from cqlib.circuits.parameter import Parameter +from cqlib.visualization.circuit.text import draw_text + +# pylint: disable=missing-function-docstring, trailing-whitespace + + +class TestCircuitTextVis: + """ test text visualization """ + + def test_basic(self): + circuit = Circuit(2) + circuit.h(0) + circuit.cx(0, 1) + circuit.measure_all() + text = draw_text(circuit) + resp = """ + Q0: ───H──■──M─ + │ + Q1: ──────X──M─ + + +""" + assert text == resp + + def test_parameter(self): + theta = Parameter('theta') + circuit = Circuit(2, parameters=[theta]) + circuit.h(0) + circuit.rx(1, theta) + circuit.cx(0, 1) + circuit.measure_all() + text = draw_text(circuit) + resp = """ + Q0: ───────H──────■──M─ + │ + Q1: ───RX(theta)──X──M─ + + +""" + assert text == resp + + def test_rotation_gates(self): + circuit = Circuit(3) + circuit.h(0) + circuit.rx(1, 0.1) + circuit.ry(2, 0.2) + circuit.rz(0, 0.3) + circuit.measure_all() + text = draw_text(circuit) + resp = """ + Q0: ──────H─────RZ(0.3)──M─ + + Q1: ───RX(0.1)─────M─────── + + Q2: ───RY(0.2)─────M─────── + + +""" + assert text == resp + + def test_barrier(self): + circuit = Circuit(3) + circuit.barrier(0, 2) + circuit.barrier(0, 1, 2) + text = draw_text(circuit) + resp = """ + Q0: ───│──│─ + │ + Q1: ──────│─ + │ │ + Q2: ───│──│─ + + +""" + assert text == resp + + def test_moment(self): + circuit = Circuit(3) + circuit.h(1) + circuit.cx(0, 2) + circuit.measure_all() + text = draw_text(circuit) + resp = """ ┌──┐ + Q0: ────■──M─ + │ + Q1: ───H┼──M─ + │ + Q2: ────X──M─ + └──┘ + +""" + assert text == resp + + def test_long_theta(self): + circuit = Circuit(3) + circuit.h(0) + circuit.rx(1, pi) + circuit.measure_all() + text = draw_text(circuit) + resp = """ + Q0: ─────────────H────────────M─ + + Q1: ───RX(3.141592653589793)──M─ + + Q2: ─────────────M────────────── + + +""" + assert text == resp + + def test_phy_qubits(self): + circuit = Circuit([1, 7, 13]) + circuit.h(1) + circuit.cx(7, 13) + circuit.measure_all() + text = draw_text(circuit) + resp = """ + Q1: ───H──M─ + + Q7: ───■──M─ + │ + Q13: ───X──M─ + + +""" + assert text == resp + + def test_swap(self): + circuit = Circuit(3) + circuit.swap(0, 1) + circuit.swap(1, 2) + circuit.measure_all() + text = draw_text(circuit) + resp = """ + Q0: ───X──M──── + │ + Q1: ───X──X──M─ + │ + Q2: ──────X──M─ + + +""" + assert text == resp + + def test_width(self): + circuit = Circuit(3) + for _ in range(10): + circuit.h(0) + circuit.rx(2, pi / 2) + circuit.ry(1, 0.2) + circuit.barrier(*circuit.qubits) + circuit.measure_all() + text = draw_text(circuit, line_width=80) + # pylint: disable=line-too-long + resp = """ » + Q0: ─────────────H─────────────│────────────H─────────────│────────────H────────────» + │ │ » + Q1: ──────────RY(0.2)──────────│─────────RY(0.2)──────────│─────────RY(0.2)─────────» + │ │ » + Q2: ───RX(1.5707963267948966)──│──RX(1.5707963267948966)──│──RX(1.5707963267948966)─» + » + +« » +« Q0: ───│────────────H─────────────│────────────H─────────────│─» +« │ │ │ » +« Q1: ───│─────────RY(0.2)──────────│─────────RY(0.2)──────────│─» +« │ │ │ » +« Q2: ───│──RX(1.5707963267948966)──│──RX(1.5707963267948966)──│─» +« » + +« » +« Q0: ─────────────H─────────────│────────────H─────────────│────────────H────────────» +« │ │ » +« Q1: ──────────RY(0.2)──────────│─────────RY(0.2)──────────│─────────RY(0.2)─────────» +« │ │ » +« Q2: ───RX(1.5707963267948966)──│──RX(1.5707963267948966)──│──RX(1.5707963267948966)─» +« » + +« +« Q0: ───│────────────H─────────────│────────────H─────────────│──M─ +« │ │ │ +« Q1: ───│─────────RY(0.2)──────────│─────────RY(0.2)──────────│──M─ +« │ │ │ +« Q2: ───│──RX(1.5707963267948966)──│──RX(1.5707963267948966)──│──M─ +« + +""" + assert text == resp + + def test_one_line(self): + circuit = Circuit(5) + for _ in range(5): + circuit.x(0) + circuit.swap(3, 4) + circuit.rxy(1, 0.12, pi) + circuit.barrier(*circuit.qubits) + circuit.measure_all() + text = draw_text(circuit, line_width=-1) + resp = """ + Q0: ────────────────X───────────────│───────────────X───────────────│───────────────X───────────────│───────────────X───────────────│───────────────X───────────────│──M─ + │ │ │ │ │ + Q1: ───RXY(0.12,3.141592653589793)──│──RXY(0.12,3.141592653589793)──│──RXY(0.12,3.141592653589793)──│──RXY(0.12,3.141592653589793)──│──RXY(0.12,3.141592653589793)──│──M─ + │ │ │ │ │ + Q2: ────────────────────────────────│───────────────────────────────│───────────────────────────────│───────────────────────────────│───────────────────────────────│──M─ + │ │ │ │ │ + Q3: ────────────────X───────────────│───────────────X───────────────│───────────────X───────────────│───────────────X───────────────│───────────────X───────────────│──M─ + │ │ │ │ │ │ │ │ │ │ + Q4: ────────────────X───────────────│───────────────X───────────────│───────────────X───────────────│───────────────X───────────────│───────────────X───────────────│──M─ + + +""" + assert text == resp + + def test_i(self): + circuit = Circuit(3) + circuit.i(0, 100) + circuit.measure_all() + text = draw_text(circuit) + resp = """ + Q0: ───I(100)──M─ + + Q1: ─────M─────── + + Q2: ─────M─────── + + +""" + assert text == resp + + def test_gates_0(self): + theta = Parameter('theta') + circuit = Circuit(5, parameters=[theta]) + circuit.h(0) + circuit.i(1, 100) + circuit.rx(2, 0.123) + circuit.crx(3, 4, theta) + circuit.rxy(0, 1, 0.123) + circuit.ry(1, 0.123) + circuit.cry(2, 3, 0.123) + circuit.rz(4, theta) + circuit.measure_all() + text = draw_text(circuit, line_width=100) + resp = """ + Q0: ───────H──────RXY(1,0.123)──M─ + + Q1: ─────I(100)────RY(0.123)────M─ + + Q2: ───RX(0.123)───────■────────M─ + │ + Q3: ───────■───────RY(0.123)────M─ + │ + Q4: ───RX(theta)───RZ(theta)────M─ + + +""" + assert text == resp + + def test_gates_1(self): + theta = Parameter('theta') + phi = Parameter('phi') + circuit = Circuit(5, parameters=[theta, phi]) + circuit.s(0) + circuit.sd(1) + circuit.swap(2, 3) + circuit.t(4) + circuit.td(0) + circuit.u(1, theta, phi, 0.123) + circuit.measure_all() + text = draw_text(circuit) + resp = """ + Q0: ───S───────────TD──────────M─ + + Q1: ───SD──U(theta,phi,0.123)──M─ + + Q2: ───X───────────M───────────── + │ + Q3: ───X───────────M───────────── + + Q4: ───T───────────M───────────── + + +""" + assert text == resp + + def test_gates_2(self): + theta = Parameter('theta') + phi = Parameter('phi') + circuit = Circuit(5, parameters=[theta, phi]) + circuit.x(0) + circuit.cx(1, 2) + circuit.ccx(3, 4, 0) + circuit.x2p(1) + circuit.x2m(2) + circuit.xy(3, theta) + circuit.xy2p(4, theta) + circuit.xy2m(0, theta) + circuit.y(1) + circuit.y2p(2) + circuit.y2m(3) + circuit.cy(4, 0) + circuit.z(0) + circuit.cz(1, 2) + circuit.measure_all() + text = draw_text(circuit) + resp = """ ┌────┐ ┌────┐ + Q0: ───X──X─────XY2M(theta)──Y─────Z──M─ + │ │ + Q1: ───■──┼X2P───────Y───────┼─■───M──── + │ │ │ │ + Q2: ───X──┼X2M──────Y2P──────┼─■───M──── + │ │ + Q3: ──────■──────XY(theta)───┼Y2M──M──── + │ │ + Q4: ──────■─────XY2P(theta)──■─────M──── + └────┘ └────┘ + +""" + assert text == resp diff --git a/tests/visualization/test_visualization.py b/tests/visualization/test_visualization.py index 180e624f63683ffa564ac4a4b0c654562ca4cfc8..54a473282f0ca8ca5bc36596412e1c9056e2bbf5 100644 --- a/tests/visualization/test_visualization.py +++ b/tests/visualization/test_visualization.py @@ -21,49 +21,10 @@ sys.path.append( os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir)) ) -from cqlib.visualization.circuit import draw_circuit from cqlib.visualization.gplot import draw_gplot from cqlib.visualization.result import draw_histogram -def test_draw_circuit(): - s1 = """ -x q1 -y q1 -RXy q1 1.26 1.33 -ry q0 0.33 -rx q3 0.23 -td q2 -cz q1 q2 -cz q3 q8 -xy2m q1 3.1415 -xy2p q8 1.732 -rz q11 2.13 -iswap q1 q2 -sd q2 -s q8 -h q5 -swap q2 q8 -b q4 q2 q5 -cz q3 q4 -s q9 -t q11 -x2p q0 -ccz q2 q8 q11 -x2m q0 -y2p q0 -y2m q0 -ccx q1 q0 q2 -i q9 -rz q0 1.63 -m q1 q3 -u3 Q3 -3.14 -3.14 -3.14 -m q11 -""" - a = draw_circuit(s1, file_name="circuit", interactive=False) - return a - - def test_draw_gplot(): # ['T1', 'T2', 'gate_error', 'two_gate_error', 'Readout_Error'] a = draw_gplot( @@ -92,5 +53,3 @@ def test_draw_histogram(): {"2": 10, "9": 1.6}, ] draw_histogram(a, file_name='hist', interactive=False, fig_size=(20, 15)) - -