diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1a01c428..7eb964e8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,6 +16,8 @@ on: options: - "3.10" - "3.11" + - "3.12" + - "3.13" commit_ref: description: Specific ref (branch, tag or SHA) default: "" @@ -27,7 +29,7 @@ on: default: false type: boolean providers: - description: "Providers to enable (space-separated) e.g., 'qiskit cirq myqlm braket')" + description: "Providers to enable. Select 'all' or choose a specific provider." required: false default: "all" type: choice @@ -42,7 +44,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ${{ fromJSON(github.event_name == 'workflow_dispatch' && format('["{0}"]', github.event.inputs.python_v) || '["3.10", "3.11"]') }} + python-version: ${{ fromJSON(github.event_name == 'workflow_dispatch' && format('["{0}"]', github.event.inputs.python_v) || '["3.10", "3.11", "3.12", "3.13"]') }} provider: ${{ fromJSON(github.event_name == 'workflow_dispatch' && format('["{0}"]', github.event.inputs.providers) || (github.ref_name == 'main' && '["all", "qiskit", "cirq", "braket", "myqlm"]' || '["all"]')) }} steps: - name: Checkout repository @@ -57,10 +59,8 @@ jobs: - name: Install python dependencies run: | pip install --upgrade pip + pip install ".[${{ matrix.provider }}]" pip install -r requirements-dev.txt - providers_comma=$(echo "${{ matrix.provider }}" | sed 's/ /,/g') - echo "$providers_comma" - pip install ".[$providers_comma]" - name: Install os specific dependencies if: ${{ github.event.inputs.long == 'true' || github.ref_name == 'main' }} run: | @@ -78,7 +78,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ${{ fromJSON(github.event_name == 'workflow_dispatch' && format('["{0}"]', github.event.inputs.python_v) || '["3.10", "3.11"]') }} + python-version: ${{ fromJSON(github.event_name == 'workflow_dispatch' && format('["{0}"]', github.event.inputs.python_v) || '["3.10", "3.11", "3.12", "3.13"]') }} steps: - name: Checkout repository uses: actions/checkout@v4 @@ -92,7 +92,7 @@ jobs: - name: Install dependencies run: | pip install --upgrade pip - pip install -r requirements-dev.txt pip install -r requirements-all.txt + pip install -r requirements-dev.txt - name: Run type checker run: pyright \ No newline at end of file diff --git a/.gitignore b/.gitignore index 06371e61..66aacd18 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ # build artifacts build/ +docs/notebooks/ +docs/requirements_providers/ docs/html/ out/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ea1b2278..c4f39721 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -94,13 +94,15 @@ repository, and find the one you need to modify to achieve your goal. Here are some useful scripts for when you are developing: -| Command | Description | -| ------------------------------------- | ----------------------------------------- | -| `sphinx-build -b html docs build` | Builds the documentation | -| `python -m pytest` | Runs the test suite | -| `python -m pytest --long` | Runs the long tests too | -| `python -m pytest --long-local` | Runs the local long tests | -| `python -m pytest --seed=` | Runs the test suite with a specified seed | +| Command | Description | +| ---------------------------------------- | --------------------------------------------- | +| `sphinx-build -b html docs build` | Builds the documentation | +| `python -m pytest` | Runs the test suite | +| `python -m pytest --long` | Runs the long tests too | +| `python -m pytest --long-local` | Runs the local long tests | +| `python -m pytest --seed=` | Runs the test suite with a specified seed | +| `python -m pytest --provider ` | Runs the test suite with a specified provider | + When making commits, make sure to follow the [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/) diff --git a/Dockerfile b/Dockerfile index 9b08c8a9..5175b29f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Stage 1: Build dependencies -FROM python:3.9 AS builder +FROM python:3.10 AS builder WORKDIR /usr/src/app @@ -7,7 +7,7 @@ COPY requirements.txt requirements-dev.txt ./ RUN pip install --upgrade pip && \ pip install --no-cache-dir -r requirements-dev.txt -FROM python:3.9 +FROM python:3.10 RUN apt update && \ apt install -y \ @@ -27,7 +27,7 @@ RUN chmod +x linux_awscli_install.sh && ./linux_awscli_install.sh WORKDIR /usr/src/app/mpqp -COPY --from=builder /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages +COPY --from=builder /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages COPY .. /usr/src/app/mpqp/ COPY requirements.txt requirements-dev.txt /usr/src/app/ diff --git a/README.md b/README.md index ac82e448..bd963abd 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ On this page, you will find: ## Install -For now, we support python versions 3.9 to 3.11, and every major OS (Windows, +For now, we support python versions 3.10 to 3.13, and every major OS (Windows, Linux and MacOS). We are dependant on the SDKs we support to enable various python versions and OS support, for instance, MPQP was validated on Ubuntu LTS 20.04, while Ubuntu 18.04 is not supported because myQLM does not support it. diff --git a/docs/all-modules.rst b/docs/all-modules.rst index 5ae7b223..70325f23 100644 --- a/docs/all-modules.rst +++ b/docs/all-modules.rst @@ -15,5 +15,4 @@ mpqp execution qasm tools - local_storage - environment \ No newline at end of file + local_storage \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 98fe9bc8..6b85eb44 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,6 +1,8 @@ from __future__ import annotations import os +from pathlib import Path +import shutil import sys from typing import Literal @@ -40,7 +42,6 @@ "sphinx_rtd_dark_mode", "sphinx_copybutton", "nbsphinx", # requires pandoc ? - "nbsphinx_link", ] default_dark_mode = True autodoc_typehints = "description" @@ -87,6 +88,68 @@ \sphinxcode{\sphinxupquote{\strut {{ docname | escape_latex }}}} \dotfill}} """ + +def copy_notebooks(app: Sphinx): + app_dir = Path(app.srcdir).absolute() + src_dir = app_dir / "../examples/notebooks" + dest_dir = app_dir / "notebooks" + + dest_dir.mkdir(exist_ok=True) + + if not src_dir.exists(): + raise FileNotFoundError(f"Source notebooks directory not found: {src_dir}") + + for nb in src_dir.iterdir(): + if nb.suffix == ".ipynb": + shutil.copy2(src_dir / nb, dest_dir) + + +def copy_requirements_providers(app: Sphinx): + """ + Copy requirements_providers/*.txt into docs/requirements_providers + so Sphinx can access them. + """ + app_dir = Path(app.srcdir).absolute() + src_dir = app_dir / "../requirements_providers" + dest_dir = app_dir / "requirements_providers" + + dest_dir.mkdir(exist_ok=True) + + if not src_dir.exists(): + raise FileNotFoundError( + f"Source requirements_providers directory not found: {src_dir}" + ) + + for src_file in src_dir.glob("*.txt"): + dest_file = dest_dir / src_file.name + + if dest_file.exists(): + dest_file.unlink() + + shutil.copy2(src_file, dest_file) + + +def generate_notebooks_toctree(app: Sphinx): + """ + Automatically generate a toctree listing all notebooks + found in notebooks/. + """ + notebooks_dir = Path(app.srcdir) / "notebooks" + notebooks_dir.mkdir(exist_ok=True) + output_file = notebooks_dir / "notebooks_toctree.rst" + + notebooks = sorted(f for f in notebooks_dir.iterdir() if f.suffix == ".ipynb") + prefix = """.. toctree:: + :maxdepth: 1 + :caption: Notebooks: + +""" + with open(output_file, "w", encoding="utf-8") as f: + f.write(prefix) + for nb in notebooks: + f.write(f" notebooks/{nb.stem}\n") + + # The suffix of source filenames. source_suffix = ".rst" @@ -385,4 +448,7 @@ def maybe_skip_member( def setup(app: Sphinx): + app.connect("builder-inited", copy_notebooks) + app.connect("builder-inited", copy_requirements_providers) app.connect("autodoc-skip-member", maybe_skip_member) + app.connect("builder-inited", generate_notebooks_toctree) diff --git a/docs/examples.rst b/docs/examples.rst index 12f9ad98..f978f3c8 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -4,14 +4,4 @@ Examples On this page, you will find a few notebooks we provide as examples for various ``MPQP`` features. -.. toctree:: - :maxdepth: 1 - :caption: Notebooks: - - notebooks/1_Basics_of_circuit - notebooks/2_Execution_Bell_circuit - notebooks/3_Expectation_value_of_observables - notebooks/4_Quantum_Fourier_Transform - notebooks/5_Variational_Quantum_Algorithms - notebooks/6_Noise_Simulation - notebooks/8_TSP_QAOA \ No newline at end of file +.. include:: notebooks/notebooks_toctree.rst \ No newline at end of file diff --git a/docs/gates.rst b/docs/gates.rst index 40d2d40e..fbd4d487 100644 --- a/docs/gates.rst +++ b/docs/gates.rst @@ -58,4 +58,4 @@ Custom Gates Controlled Custom Gates ----------------------- -.. automodule:: mpqp.core.instruction.gates.controlled_custom_gate +.. automodule:: mpqp.core.instruction.gates.custom_controlled_gate diff --git a/docs/getting-started.rst b/docs/getting-started.rst index b2cde8a7..048c2193 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -7,23 +7,95 @@ Installation .. TODO: grab the compatibility matrix from MyQLM and relax our requirements .. when possible, test on many different configurations (tox or other ?) -For now, we support Python versions 3.9 to 3.11, and all of Windows, Linux and +For now, we support Python versions 3.10 to 3.13, and all of Windows, Linux and MacOS (specifically, Linux was validated on Ubuntu LTS 20.04, while Ubuntu 18.04 is not supported, so your milage may vary). -To install mpqp, you can run in a terminal +To install mpqp, you can run in a terminal: .. code-block:: console $ pip install mpqp -And if you have already a previous version and want to update to the latest -version, run instead +If you have already a previous version and want to update to the latest version, +run instead: .. code-block:: console $ pip install -U mpqp +.. note:: + To keep the installation lightweight and avoid installing unnecessary + dependencies, each provider is distributed as a **separate pip extra**. + By default, only the core functionalities of ``mpqp`` and qiskit local + simulation are installed, which means that you can create and manipulate circuits, + but you won't be able to run them on any backend. + +Add more providers +-------------------------- + +``mpqp`` provides integrations with several quantum SDKs and execution backends. + +- **Installing all providers**: + + .. code-block:: console + + $ pip install mpqp["all"] + + +- **Qiskit**: + + .. code-block:: console + + $ pip install mpqp["qiskit"] + +.. literalinclude:: requirements_providers/qiskit.txt + :language: text + +- **Azure Quantum**: + + .. code-block:: console + + $ pip install mpqp["azure"] + +.. literalinclude:: requirements_providers/azure.txt + :language: text + +- **Amazon Braket**: + + .. code-block:: console + + $ pip install mpqp["braket"] + +.. literalinclude:: requirements_providers/braket.txt + :language: text + +- **myQLM**: + + .. code-block:: console + + $ pip install mpqp["myqlm"] + +.. literalinclude:: requirements_providers/myqlm.txt + :language: text + + +- **Cirq**: + + .. code-block:: console + + $ pip install mpqp["cirq"] + +.. literalinclude:: requirements_providers/cirq.txt + :language: text + + +You can also combine extras, for example, to install both Qiskit and Braket support: + + .. code-block:: console + + $ pip install mpqp["qiskit", "braket"] + .. note:: For Mac users, additional steps are required before installation, specifically because of the ``myqlm`` library. To run these steps, you can @@ -36,7 +108,7 @@ version, run instead $ curl -L https://raw.githubusercontent.com/ColibrITD-SAS/mpqp/main/mac-install.sh | bash -s -- where ```` is the binary you use to invoke python. For instance, it could - be ``python``, ``python3``, or ``python3.9``. + be ``python``, ``python3``, or ``python3.10``. .. warning:: The migration from ``qiskit`` version ``0.x`` to ``1.x`` caused a few issues. diff --git a/docs/index.rst b/docs/index.rst index 62761a8d..8eef9427 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -41,7 +41,6 @@ on the current available SDKs: vqa qasm tools - environment local_storage changelog examples diff --git a/docs/notebooks/1_Basics_of_circuit.nblink b/docs/notebooks/1_Basics_of_circuit.nblink deleted file mode 100644 index c6e76376..00000000 --- a/docs/notebooks/1_Basics_of_circuit.nblink +++ /dev/null @@ -1,3 +0,0 @@ -{ - "path": "../../examples/notebooks/1_Basics_of_circuit.ipynb" -} \ No newline at end of file diff --git a/docs/notebooks/2_Execution_Bell_circuit.nblink b/docs/notebooks/2_Execution_Bell_circuit.nblink deleted file mode 100644 index 03019f2b..00000000 --- a/docs/notebooks/2_Execution_Bell_circuit.nblink +++ /dev/null @@ -1,3 +0,0 @@ -{ - "path": "../../examples/notebooks/2_Execution_Bell_circuit.ipynb" -} \ No newline at end of file diff --git a/docs/notebooks/3_Expectation_value_of_observables.nblink b/docs/notebooks/3_Expectation_value_of_observables.nblink deleted file mode 100644 index c92bb201..00000000 --- a/docs/notebooks/3_Expectation_value_of_observables.nblink +++ /dev/null @@ -1,3 +0,0 @@ -{ - "path": "../../examples/notebooks/3_Expectation_value_of_observables.ipynb" -} diff --git a/docs/notebooks/4_Quantum_Fourier_Transform.nblink b/docs/notebooks/4_Quantum_Fourier_Transform.nblink deleted file mode 100644 index e660485d..00000000 --- a/docs/notebooks/4_Quantum_Fourier_Transform.nblink +++ /dev/null @@ -1,3 +0,0 @@ -{ - "path": "../../examples/notebooks/4_Quantum_Fourier_Transform.ipynb" -} \ No newline at end of file diff --git a/docs/notebooks/5_Variational_Quantum_Algorithms.nblink b/docs/notebooks/5_Variational_Quantum_Algorithms.nblink deleted file mode 100644 index 0d56bd6f..00000000 --- a/docs/notebooks/5_Variational_Quantum_Algorithms.nblink +++ /dev/null @@ -1,3 +0,0 @@ -{ - "path": "../../examples/notebooks/5_Variational_Quantum_Algorithms.ipynb" -} \ No newline at end of file diff --git a/docs/notebooks/6_Noise_Simulation.nblink b/docs/notebooks/6_Noise_Simulation.nblink deleted file mode 100644 index 116bea11..00000000 --- a/docs/notebooks/6_Noise_Simulation.nblink +++ /dev/null @@ -1,3 +0,0 @@ -{ - "path": "../../examples/notebooks/6_Noise_Simulation.ipynb" -} \ No newline at end of file diff --git a/docs/notebooks/8_TSP_QAOA.nblink b/docs/notebooks/8_TSP_QAOA.nblink deleted file mode 100644 index 307839b9..00000000 --- a/docs/notebooks/8_TSP_QAOA.nblink +++ /dev/null @@ -1,3 +0,0 @@ -{ - "path": "../../examples/notebooks/8_TSP_QAOA.ipynb" -} \ No newline at end of file diff --git a/docs/qasm.rst b/docs/qasm.rst index ed434d58..1518f466 100644 --- a/docs/qasm.rst +++ b/docs/qasm.rst @@ -42,7 +42,6 @@ Braket ^^^^^^ .. automodule:: mpqp.qasm.qasm_to_braket -.. automodule:: mpqp.qasm.braket_noise_to_mpqp Cirq ^^^^ diff --git a/mpqp/core/circuit.py b/mpqp/core/circuit.py index 6261f433..6f9b3c0d 100644 --- a/mpqp/core/circuit.py +++ b/mpqp/core/circuit.py @@ -825,18 +825,14 @@ def to_matrix(self) -> npt.NDArray[np.complex128]: [0.70711, 0 , -0.70711, 0 ]] """ - from qiskit import QuantumCircuit from qiskit.quantum_info.operators import Operator - qiskit_circuit = self.to_other_language(Language.QISKIT) + qiskit_circuit = self.to_other_language(Language.QISKIT).reverse_bits() if TYPE_CHECKING: assert isinstance(qiskit_circuit, QuantumCircuit) matrix = Operator.from_circuit(qiskit_circuit).to_matrix() if TYPE_CHECKING: assert isinstance(matrix, np.ndarray) - gphase = self.input_g_phase + self._generated_g_phase - if gphase != 0: - matrix *= np.exp(1j * gphase) return matrix def inverse(self) -> QCircuit: @@ -963,7 +959,7 @@ def initializer(cls, state: npt.NDArray[np.complex128]) -> QCircuit: qiskit_circuit.append( StatePreparation(Statevector(normalize(state))), range(size) ) - circ, phase = replace_custom_gate(qiskit_circuit[0], size) + circ, phase = replace_custom_gate(qiskit_circuit[0], size, list(range(size))) cls = QCircuit.from_other_language(circ.reverse_bits()) cls.input_g_phase = phase return cls @@ -1295,20 +1291,23 @@ def to_other_language( if isinstance(instruction, CustomGate): if TYPE_CHECKING: assert isinstance(qiskit_inst, Operator) - qargs = [self.nb_qubits - 1 - q for q in instruction.targets] if printing and len(instruction.free_symbols) > 0: - new_circ.append(qiskit_inst, list(reversed(qargs))) + new_circ.append( + qiskit_inst, list(reversed(instruction.targets)) + ) else: new_circ.unitary( qiskit_inst, - list(reversed(qargs)), + list(reversed(instruction.targets)), instruction.label, ) else: if isinstance(instruction, ControlledGate): - qargs = instruction.targets + instruction.controls + qargs = list(reversed(instruction.controls)) + list( + reversed(instruction.targets) + ) elif isinstance(instruction, Gate): - qargs = instruction.targets + qargs = list(reversed(instruction.targets)) elif isinstance(instruction, Barrier): qargs = range(self.nb_qubits) else: @@ -1316,10 +1315,9 @@ def to_other_language( if TYPE_CHECKING: assert not isinstance(qiskit_inst, Operator) - qargs = [self.nb_qubits - 1 - q for q in qargs] new_circ.append( qiskit_inst, - list(reversed(qargs)), + list(qargs), cargs, ) @@ -1333,7 +1331,7 @@ def to_other_language( ) new_circ.append( qiskit_pre_measure, - pre_measure.targets, + list(reversed(pre_measure.targets)), cargs=cargs, ) if not skip_measurements: @@ -1345,10 +1343,6 @@ def to_other_language( if isinstance(measurement, BasisMeasure): if TYPE_CHECKING: assert measurement.c_targets is not None - qargs = [[self.nb_qubits - 1 - q for q in measurement.targets]] - cargs = [ - [self.nb_qubits - 1 - q for q in measurement.c_targets] - ] else: raise ValueError(f"measurement not handled: {measurement}") @@ -1356,10 +1350,11 @@ def to_other_language( assert not isinstance(qiskit_inst, Operator) new_circ.append( qiskit_inst, - list(reversed(qargs)), - cargs, + [measurement.targets], + [measurement.c_targets], ) + new_circ.global_phase += self.input_g_phase + self._generated_g_phase return new_circ elif language == Language.MY_QLM: @@ -1941,13 +1936,9 @@ def from_other_language( from mpqp.qasm.qasm_to_mpqp import qasm2_parse qasm3_code = qasm3.dumps(qcircuit) - qasm2_code, phase = open_qasm_3_to_2( - str(qasm3_code), language=Language.QISKIT - ) + qasm2_code = open_qasm_3_to_2(str(qasm3_code), language=Language.QISKIT) qc = qasm2_parse(qasm2_code) - qc.input_g_phase = phase - return qc if InstalledProviders.CIRQ in _INSTALLED_MPQP_PROVIDERS: from cirq.circuits.circuit import Circuit as cirq_Circuit @@ -1973,27 +1964,29 @@ def from_other_language( from braket.ir.openqasm.program_v1 import Program from mpqp.qasm.open_qasm_2_and_3 import open_qasm_3_to_2 - from mpqp.qasm.qasm_to_braket import ( - braket_custom_gates_to_mpqp, - braket_noise_to_mpqp, - ) + from mpqp.qasm.qasm_to_braket import braket_noise_to_mpqp from mpqp.qasm.qasm_to_mpqp import qasm2_parse + remove_measure = True + for instr in qcircuit.instructions: + if instr.operator.name == "Measure": + remove_measure = False + break + qasm3_code = qcircuit.to_ir(IRType.OPENQASM) + if TYPE_CHECKING: assert isinstance(qasm3_code, Program) + noises, qasm3_code = braket_noise_to_mpqp(qasm3_code.source) - custom_gates = braket_custom_gates_to_mpqp(qasm3_code.source) - noises = braket_noise_to_mpqp(qasm3_code.source) - - qasm2_code, phase = open_qasm_3_to_2( - str(qasm3_code.source), language=Language.BRAKET + qasm2_code = open_qasm_3_to_2( + qasm3_code, + language=Language.BRAKET, + remove_measure=remove_measure, ) + qc = qasm2_parse(qasm2_code) - qc.input_g_phase = phase - qc = qc.without_measurements(deep_copy=False) - if len(custom_gates) != 0: - qc.add(custom_gates) + # qc.input_g_phase = phase if len(noises) != 0: qc.add(noises) return qc @@ -2022,16 +2015,15 @@ def from_other_language( qasm2_code, gphase = parse_qasm2_gates(qcircuit) qc = qasm2_parse(qasm2_code) - qc.input_g_phase = gphase + qc.input_g_phase += gphase return qc elif line.startswith("OPENQASM 3.0"): from mpqp.qasm import open_qasm_3_to_2 - qasm2_code, phase = open_qasm_3_to_2(qcircuit) + qasm2_code = open_qasm_3_to_2(qcircuit) qc = qasm2_parse(qasm2_code) - qc.input_g_phase = phase return qc break @@ -2115,15 +2107,13 @@ def pretty_print(self): for noise in self.noises: print(noise.info()) - qiskit_circuit = self.to_other_language(Language.QISKIT).reverse_bits() + qiskit_circuit = self.to_other_language(Language.QISKIT) if TYPE_CHECKING: assert isinstance(qiskit_circuit, QuantumCircuit) print(qiskit_circuit.draw(output="text", fold=0)) def __str__(self) -> str: - qiskit_circ = self.to_other_language( - Language.QISKIT, printing=True - ).reverse_bits() + qiskit_circ = self.to_other_language(Language.QISKIT, printing=True) if TYPE_CHECKING: from qiskit import QuantumCircuit diff --git a/mpqp/core/instruction/barrier.py b/mpqp/core/instruction/barrier.py index 8eb954fa..2f4d4cdf 100644 --- a/mpqp/core/instruction/barrier.py +++ b/mpqp/core/instruction/barrier.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Optional if TYPE_CHECKING: - from qiskit.circuit import Parameter + from qiskit._accelerate.circuit import Parameter from mpqp.core.languages import Language diff --git a/mpqp/core/instruction/breakpoint.py b/mpqp/core/instruction/breakpoint.py index 9cea0c1f..08c9cb3f 100644 --- a/mpqp/core/instruction/breakpoint.py +++ b/mpqp/core/instruction/breakpoint.py @@ -12,7 +12,7 @@ from mpqp.core.languages import Language if TYPE_CHECKING: - from qiskit.circuit import Parameter + from qiskit._accelerate.circuit import Parameter class Breakpoint(Instruction): diff --git a/mpqp/core/instruction/gates/custom_controlled_gate.py b/mpqp/core/instruction/gates/custom_controlled_gate.py index f5e978d1..d2783231 100644 --- a/mpqp/core/instruction/gates/custom_controlled_gate.py +++ b/mpqp/core/instruction/gates/custom_controlled_gate.py @@ -5,7 +5,7 @@ from mpqp.core.languages import Language if TYPE_CHECKING: - from qiskit.circuit import Parameter + from qiskit._accelerate.circuit import Parameter from mpqp.core.instruction.gates.gate import Gate diff --git a/mpqp/core/instruction/gates/custom_gate.py b/mpqp/core/instruction/gates/custom_gate.py index 3f6997fb..96e43b5f 100644 --- a/mpqp/core/instruction/gates/custom_gate.py +++ b/mpqp/core/instruction/gates/custom_gate.py @@ -15,7 +15,7 @@ from mpqp.tools import Matrix if TYPE_CHECKING: - from qiskit.circuit import Parameter + from qiskit._accelerate.circuit import Parameter from mpqp.core.circuit import QCircuit from sympy import Basic @@ -171,16 +171,19 @@ def to_other_language( qiskit_circ = QuantumCircuit(nb_qubits) instr = self.to_other_language(Language.QISKIT) if TYPE_CHECKING: - from qiskit.quantum_info.operators import Operator as QiskitOperator + from qiskit.circuit.library import UnitaryGate + + assert isinstance(instr, UnitaryGate) - assert isinstance(instr, QiskitOperator) qiskit_circ.unitary( instr, - list(reversed(self.targets)), # dang qiskit qubits order + list(reversed(self.targets)), self.label, ) - circuit, gphase = replace_custom_gate(qiskit_circ.data[0], nb_qubits) + circuit, gphase = replace_custom_gate( + qiskit_circ.data[0], nb_qubits, self.targets + ) qasm_str = qasm2.dumps(circuit) qasm_lines = qasm_str.splitlines() @@ -212,6 +215,7 @@ def decompose(self) -> "QCircuit": >>> U = np.array([[0,1], [1,0]]) >>> gate = CustomGate(U, [0]) >>> print(gate.decompose()) # doctest: +NORMALIZE_WHITESPACE + global phase: π/2 ┌─────────┐┌───────┐┌──────────┐ q: ┤ Rz(π/2) ├┤ Ry(π) ├┤ Rz(-π/2) ├ └─────────┘└───────┘└──────────┘ diff --git a/mpqp/core/instruction/gates/gate.py b/mpqp/core/instruction/gates/gate.py index 5477adec..e2199aa3 100644 --- a/mpqp/core/instruction/gates/gate.py +++ b/mpqp/core/instruction/gates/gate.py @@ -314,7 +314,11 @@ def tensor_product(self, other: Gate, targets: Optional[list[int]] = None) -> Ga l1 = "g1" if self.label is None else self.label l2 = "g2" if self.label is None else self.label - return CustomGate(matrix=gd, targets=targets, label=f"{l1}⊗{l2}") + return CustomGate( + matrix=gd, # pyright: ignore[reportArgumentType] + targets=targets, + label=f"{l1}⊗{l2}", + ) def _mandatory_label(self, postfix: str = ""): return "g" + postfix if self.label is None else self.label diff --git a/mpqp/core/instruction/gates/gate_definition.py b/mpqp/core/instruction/gates/gate_definition.py index 2d6724dc..51fbb17f 100644 --- a/mpqp/core/instruction/gates/gate_definition.py +++ b/mpqp/core/instruction/gates/gate_definition.py @@ -212,7 +212,11 @@ def caster(v: Expr | Complex) -> Expr | Complex: # the types in sympy are relatively badly handled # Argument of type "Unknown | Basic | Expr" cannot be assigned to parameter "v" of type "Expr | Complex" return ( - caster(val.subs(values)) # pyright: ignore[reportArgumentType] + caster( + val.subs( + values # pyright: ignore[reportArgumentType, reportCallIssue] + ) + ) if isinstance(val, Expr) else val ) diff --git a/mpqp/core/instruction/gates/native_gates.py b/mpqp/core/instruction/gates/native_gates.py index fdad818d..87df881d 100644 --- a/mpqp/core/instruction/gates/native_gates.py +++ b/mpqp/core/instruction/gates/native_gates.py @@ -21,7 +21,7 @@ if TYPE_CHECKING: from sympy import Expr - from qiskit.circuit import Parameter + from qiskit._accelerate.circuit import Parameter from braket.circuits import FreeParameter import numpy as np @@ -94,7 +94,7 @@ def _sympy_to_braket_param(val: Expr | float) -> "float | FreeParameter": return FreeParameter(str(val)) # note: Braket won't parse expressions else: try: - return float(val.evalf()) # pyright: ignore[reportArgumentType] + return float(val.evalf()) except Exception as e: raise ValueError(f"Failed to evaluate sympy expression '{val}': {e}") else: @@ -667,14 +667,12 @@ def __init__(self, theta: Expr | float, target: int): super().__init__(theta, target) def to_canonical_matrix(self) -> Matrix: - return np.array( # pyright: ignore[reportCallIssue] + return np.array( [ [1, 0], [ 0, - exp( - self.parameters[0] * 1j # pyright: ignore[reportOperatorIssue] - ), + exp(self.parameters[0] * 1j), ], ] ) @@ -727,7 +725,7 @@ def __init__(self, theta: Expr | float, control: int, target: int): ParametrizedGate.__init__(self, definition, [target], [theta], "CP") def to_canonical_matrix(self): - e = exp(self.theta * 1j) # pyright: ignore[reportOperatorIssue] + e = exp(self.theta * 1j) return np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, e]]) def __repr__(self) -> str: @@ -973,10 +971,13 @@ def to_matrix(self, desired_gate_size: int = 0) -> npt.NDArray[np.complex128]: swap_matrix[swapped_index, i] = 1 if desired_gate_size != 0: - swap_matrix = np.kron(np.eye(2**min_nb_qubits), swap_matrix) - swap_matrix = np.kron( - swap_matrix, np.eye(2 ** (desired_gate_size - max_qubits)) - ) + swap_matrix: npt.NDArray[np.complex128] = np.kron( + np.eye(2**min_nb_qubits), swap_matrix + ).astype(np.complex128) + swap_matrix: npt.NDArray[np.complex128] = np.kron( + swap_matrix, + np.eye(2 ** (desired_gate_size - max_qubits)), + ).astype(np.complex128) return swap_matrix @@ -1125,15 +1126,15 @@ def to_other_language( def to_canonical_matrix(self): c, s, eg, ep = ( - cos(self.theta / 2), # pyright: ignore[reportOperatorIssue] - sin(self.theta / 2), # pyright: ignore[reportOperatorIssue] - exp(self.gamma * 1j), # pyright: ignore[reportOperatorIssue] - exp(self.phi * 1j), # pyright: ignore[reportOperatorIssue] + cos(self.theta / 2), + sin(self.theta / 2), + exp(self.gamma * 1j), + exp(self.phi * 1j), ) - return np.array( # pyright: ignore[reportCallIssue] + return np.array( [ - [c, -eg * s], # pyright: ignore[reportOperatorIssue] - [ep * s, eg * ep * c], # pyright: ignore[reportOperatorIssue] + [c, -eg * s], + [ep * s, eg * ep * c], ] ) @@ -1182,11 +1183,9 @@ def __init__(self, theta: Expr | float, target: int): super().__init__(theta, target) def to_canonical_matrix(self): - c = cos(self.parameters[0] / 2) # pyright: ignore[reportOperatorIssue] - s = sin(self.parameters[0] / 2) # pyright: ignore[reportOperatorIssue] - return np.array( # pyright: ignore[reportCallIssue] - [[c, -1j * s], [-1j * s, c]] # pyright: ignore[reportOperatorIssue] - ) + c = cos(self.parameters[0] / 2) + s = sin(self.parameters[0] / 2) + return np.array([[c, -1j * s], [-1j * s, c]]) class Ry(RotationGate, SingleQubitGate): @@ -1230,8 +1229,8 @@ def __init__(self, theta: Expr | float, target: int): super().__init__(theta, target) def to_canonical_matrix(self): - c = cos(self.parameters[0] / 2) # pyright: ignore[reportOperatorIssue] - s = sin(self.parameters[0] / 2) # pyright: ignore[reportOperatorIssue] + c = cos(self.parameters[0] / 2) + s = sin(self.parameters[0] / 2) return np.array([[c, -s], [s, c]]) @@ -1276,10 +1275,8 @@ def __init__(self, theta: Expr | float, target: int): super().__init__(theta, target) def to_canonical_matrix(self): - e = exp(-1j * self.parameters[0] / 2) # pyright: ignore[reportOperatorIssue] - return np.array( # pyright: ignore[reportCallIssue] - [[e, 0], [0, 1 / e]] # pyright: ignore[reportOperatorIssue] - ) + e = exp(-1j * self.parameters[0] / 2) + return np.array([[e, 0], [0, 1 / e]]) class Rk(RotationGate, SingleQubitGate): @@ -1335,7 +1332,7 @@ def theta(self) -> Expr | float: from sympy import pi p = np.pi if isinstance(self.k, Integral) else pi - return p / 2 ** (self.k - 1) # pyright: ignore[reportOperatorIssue] + return p / 2 ** (self.k - 1) @property def k(self) -> Expr | int: @@ -1343,7 +1340,7 @@ def k(self) -> Expr | int: return self.parameters[0] def to_canonical_matrix(self): - e = exp(self.theta * 1j) # pyright: ignore[reportOperatorIssue] + e = exp(self.theta * 1j) return np.array([[1, 0], [0, e]]) def __repr__(self): @@ -1422,7 +1419,7 @@ def theta(self) -> Expr | float: # TODO study the relevance of having pi from sympy p = np.pi if isinstance(self.k, Integral) else pi - return -(p / 2 ** (self.k - 1)) # pyright: ignore[reportOperatorIssue] + return -(p / 2 ** (self.k - 1)) @property def k(self) -> Expr | float: @@ -1430,7 +1427,7 @@ def k(self) -> Expr | float: return self.parameters[0] def to_canonical_matrix(self): - e = exp(self.theta * 1j) # pyright: ignore[reportOperatorIssue] + e = exp(self.theta * 1j) return np.array([[1, 0], [0, e]]) def to_other_language( @@ -1626,7 +1623,7 @@ def theta(self) -> Expr | float: from sympy import pi p = np.pi if isinstance(self.k, Integral) else pi - return p / 2 ** (self.k - 1) # pyright: ignore[reportOperatorIssue] + return p / 2 ** (self.k - 1) @property def k(self) -> Expr | float: @@ -1634,7 +1631,7 @@ def k(self) -> Expr | float: return self.parameters[0] def to_canonical_matrix(self): - e = exp(self.theta * 1j) # pyright: ignore[reportOperatorIssue] + e = exp(self.theta * 1j) return np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, e]]) def to_other_language( @@ -1724,7 +1721,7 @@ def theta(self) -> Expr | float: from sympy import pi p = np.pi if isinstance(self.k, Integral) else pi - return -(p / 2 ** (self.k - 1)) # pyright: ignore[reportOperatorIssue] + return -(p / 2 ** (self.k - 1)) @property def k(self) -> Expr | int: @@ -1732,7 +1729,7 @@ def k(self) -> Expr | int: return self.parameters[0] def to_canonical_matrix(self): - e = exp(self.theta * 1j) # pyright: ignore[reportOperatorIssue] + e = exp(self.theta * 1j) return np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, e]]) def __repr__(self) -> str: diff --git a/mpqp/core/instruction/gates/parametrized_gate.py b/mpqp/core/instruction/gates/parametrized_gate.py index 7debf793..306a316a 100644 --- a/mpqp/core/instruction/gates/parametrized_gate.py +++ b/mpqp/core/instruction/gates/parametrized_gate.py @@ -65,7 +65,15 @@ def subs( ) caster = lambda v: float(v) if remove_symbolic else v concrete_gate.parameters = [ - caster(param.subs(values)) if isinstance(param, Expr) else param + ( + caster( + param.subs( + values # pyright: ignore[reportArgumentType, reportCallIssue] + ) + ) + if isinstance(param, Expr) + else param + ) for param in self.parameters ] diff --git a/mpqp/core/instruction/instruction.py b/mpqp/core/instruction/instruction.py index 38e6690f..042ad577 100644 --- a/mpqp/core/instruction/instruction.py +++ b/mpqp/core/instruction/instruction.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: from sympy import Expr - from qiskit.circuit import Parameter + from qiskit._accelerate.circuit import Parameter from mpqp.core.languages import Language from mpqp.tools.generics import SimpleClassReprABC, flatten diff --git a/mpqp/core/instruction/measurement/basis_measure.py b/mpqp/core/instruction/measurement/basis_measure.py index 786a30b1..659bada0 100644 --- a/mpqp/core/instruction/measurement/basis_measure.py +++ b/mpqp/core/instruction/measurement/basis_measure.py @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, Optional if TYPE_CHECKING: - from qiskit.circuit import Parameter + from qiskit._accelerate.circuit import Parameter from mpqp.core.instruction.gates import Gate from mpqp.core.languages import Language diff --git a/mpqp/core/instruction/measurement/expectation_value.py b/mpqp/core/instruction/measurement/expectation_value.py index 63fea6d4..f0aed244 100644 --- a/mpqp/core/instruction/measurement/expectation_value.py +++ b/mpqp/core/instruction/measurement/expectation_value.py @@ -34,7 +34,7 @@ class to define your observable, and a :class:`ExpectationMeasure` to perform from cirq.ops.linear_combinations import PauliSum as CirqPauliSum from cirq.ops.pauli_string import PauliString as CirqPauliString from qat.core.wrappers.observable import Observable as QLMObservable - from qiskit.circuit import Parameter + from qiskit._accelerate.circuit import Parameter from qiskit.quantum_info import SparsePauliOp from sympy import Expr @@ -320,7 +320,7 @@ def to_other_language( >>> obs = Observable([0.7, -1, 1, 1]) >>> obs_qiskit = obs.to_other_language(Language.QISKIT) >>> obs_qiskit.to_list() # doctest: +NORMALIZE_WHITESPACE - [('II', (0.425+0j)), ('IZ', (0.425+0j)), ('ZI', (-0.575+0j)), ('ZZ', (0.425+0j))] + [('II', (0.425+0j)), ('IZ', (-0.575+0j)), ('ZI', (0.425+0j)), ('ZZ', (0.425+0j))] """ # TODO: use PauliString instead of matrix @@ -330,7 +330,9 @@ def to_other_language( if self._pauli_string: return self.pauli_string.to_other_language(Language.QISKIT) else: - return SparsePauliOp.from_operator(Operator(self.matrix)) + return SparsePauliOp.from_operator( + Operator(self.matrix).reverse_qargs() + ) elif language == Language.MY_QLM: from qat.core.wrappers.observable import Observable as QLMObservable @@ -467,6 +469,9 @@ def __init__( label_counter += 1 self.observables.append(new_obs) + + if targets is None: + self.targets = list(range(observable[0].nb_qubits)) self._check_targets_order() @property diff --git a/mpqp/core/instruction/measurement/pauli_string.py b/mpqp/core/instruction/measurement/pauli_string.py index 42d2bbb2..c1087a89 100644 --- a/mpqp/core/instruction/measurement/pauli_string.py +++ b/mpqp/core/instruction/measurement/pauli_string.py @@ -156,7 +156,9 @@ def from_str(compact_str: str, dict_value: Optional[dict[str, Coef]] = None): coef_str = re.sub(r'([a-zA-Z])(\d)', r'\1*\2', coef_str) coef = sympify(coef_str) if dict_value: - coef = coef.subs(dict_value) + coef = coef.subs( + dict_value # pyright: ignore[reportArgumentType, reportCallIssue] + ) atoms_dict = { "I": pI, @@ -1042,7 +1044,7 @@ def build_repr(self): return f"PauliStringMonomial({coef}{atoms})" def to_matrix(self) -> Matrix: - return ( + return ( # pyright: ignore[reportOperatorIssue,reportReturnType] reduce( np.kron, map(lambda a: a.to_matrix(), self.atoms), @@ -1070,7 +1072,9 @@ def __add__(self, other: "PauliString") -> PauliString: return res def __imul__(self, other: "Coef") -> PauliStringMonomial: - new_coef: "Coef" = self.coef * other # pyright: ignore[reportOperatorIssue] + new_coef: "Coef" = ( + self.coef * other + ) # pyright: ignore[reportAssignmentType, reportOperatorIssue] self.coef = new_coef return self @@ -1213,7 +1217,11 @@ def subs( new_monomial = deepcopy(self) caster = lambda v: _unpack_expr(v) if remove_symbolic else v if isinstance(new_monomial.coef, Expr): - new_coef: "Coef" = caster(new_monomial.coef.subs(values)) + new_coef: "Coef" = caster( + new_monomial.coef.subs( + values # pyright: ignore[reportArgumentType, reportCallIssue] + ) + ) new_monomial.coef = new_coef return new_monomial diff --git a/mpqp/execution/connection/ibm_connection.py b/mpqp/execution/connection/ibm_connection.py index aa1718dd..3aa9cc57 100644 --- a/mpqp/execution/connection/ibm_connection.py +++ b/mpqp/execution/connection/ibm_connection.py @@ -27,9 +27,7 @@ def config_ibm_account(token: str): from qiskit_ibm_runtime import QiskitRuntimeService try: - QiskitRuntimeService.save_account( - channel="ibm_quantum", token=token, overwrite=True - ) + QiskitRuntimeService.save_account(token=token, overwrite=True) save_env_variable("IBM_CONFIGURED", "True") save_env_variable("IBM_TOKEN", token) except Exception as err: @@ -81,7 +79,7 @@ def test_connection() -> bool: global Runtime_Service try: - Runtime_Service = QiskitRuntimeService(channel="ibm_quantum") + Runtime_Service = QiskitRuntimeService() except IBMNotAuthorizedError as err: if "Login failed" in str(err): print(colored("Wrong credentials", "red")) @@ -118,7 +116,7 @@ def get_QiskitRuntimeService() -> "QiskitRuntimeService": "Error when instantiating QiskitRuntimeService. No IBM account configured." ) try: - Runtime_Service = QiskitRuntimeService(channel="ibm_quantum") + Runtime_Service = QiskitRuntimeService() except Exception as err: raise IBMRemoteExecutionError( "Error when instantiating QiskitRuntimeService (probably wrong token saved " diff --git a/mpqp/execution/providers/atos.py b/mpqp/execution/providers/atos.py index 97e816a1..ce194efe 100644 --- a/mpqp/execution/providers/atos.py +++ b/mpqp/execution/providers/atos.py @@ -567,9 +567,11 @@ def extract_sample_result( # we here take the average of errors over all samples error = mean([sample.err for sample in myqlm_result]) + if TYPE_CHECKING: + assert job.measure is not None samples = [ Sample( - nb_qubits, + job.measure.nb_qubits, index=sample.state.int, probability=sample.probability, bin_str=sample.state.bitstring, diff --git a/mpqp/execution/providers/azure.py b/mpqp/execution/providers/azure.py index f3cfa848..87641603 100644 --- a/mpqp/execution/providers/azure.py +++ b/mpqp/execution/providers/azure.py @@ -183,7 +183,7 @@ def extract_samples(job: Job, result: QiskitResult) -> list[Sample]: job_data = result.data() return [ Sample( - bin_str="".join(map(str, state)), + bin_str="".join(map(str, state))[::-1], nb_qubits=job.circuit.nb_qubits, count=int(count), ) diff --git a/mpqp/execution/providers/ibm.py b/mpqp/execution/providers/ibm.py index 1d2afd56..b9aadbed 100644 --- a/mpqp/execution/providers/ibm.py +++ b/mpqp/execution/providers/ibm.py @@ -535,12 +535,11 @@ def submit_remote_ibm(job: Job) -> tuple[str, "RuntimeJobV2"]: check_job_compatibility(job) - service = get_QiskitRuntimeService() if TYPE_CHECKING: assert isinstance(job.device, IBMDevice) backend = get_backend(job.device) job.device = IBMDevice(backend.name) - session = Session(service=service, backend=backend) + session = Session(backend=backend) if job.circuit.transpiled_circuit is None: qiskit_circ = job.circuit.to_other_device(job.device) @@ -701,7 +700,7 @@ def extract_result( counts = counts.get_counts() if counts else {} data = [ Sample( - bin_str=item, + bin_str=item[::-1], count=counts[item], nb_qubits=job.circuit.nb_qubits, ) @@ -743,17 +742,18 @@ def extract_result( shots = result.metadata[0]["shots"] if "shots" in result.metadata[0] else 0 for i in range(len(result.values)): + qiskit_order = len(result.values) - i - 1 label = ( job.measure.observables[i].label if isinstance(job.measure, ExpectationMeasure) else f"ibm_obs_{i}" ) variance = ( - result.metadata[i]["variance"] - if "variance" in result.metadata[i] + result.metadata[qiskit_order]["variance"] + if "variance" in result.metadata[qiskit_order] else None ) - exp_values_dict[label] = result.values[i] + exp_values_dict[label] = result.values[qiskit_order] errors_dict[label] = variance return Result(job, exp_values_dict, errors_dict, shots) @@ -770,6 +770,7 @@ def extract_result( elif "counts" in job_data: job_type = JobType.SAMPLE nb_qubits = len(list(result.get_counts())[0]) + assert result.results is not None shots = result.results[0].shots job = Job( job_type, @@ -791,12 +792,12 @@ def extract_result( ) if job.job_type == JobType.STATE_VECTOR: - vector = np.array(result.get_statevector()) + vector = np.array(result.get_statevector().reverse_qargs()) # type: ignore[reportUnnecessaryIsInstance] state_vector = StateVector( - vector, # pyright: ignore[reportArgumentType] + vector, job.circuit.nb_qubits, ) - return Result(job, state_vector, 0, 0) + return Result(job, state_vector, 0, 0, False) elif job.job_type == JobType.SAMPLE: if TYPE_CHECKING: assert job.measure is not None @@ -827,7 +828,7 @@ def get_result_from_ibm_job_id(job_id: str) -> Result: Returns: The result (or batch of result) converted to our format. """ - from qiskit.providers import BackendV1, BackendV2 + from qiskit.providers import BackendV2 connector = get_QiskitRuntimeService() ibm_job = ( @@ -851,7 +852,7 @@ def get_result_from_ibm_job_id(job_id: str) -> Result: result = ibm_job.result() backend = ibm_job.backend() if TYPE_CHECKING: - assert isinstance(backend, (BackendV1, BackendV2)) + assert isinstance(backend, BackendV2) ibm_device = IBMDevice(backend.name) return extract_result(result, None, ibm_device) @@ -872,7 +873,7 @@ def extract_samples(job: Job, result: QiskitResult) -> list[Sample]: job_data = result.data() return [ Sample( - bin_str=item, + bin_str=item[::-1], count=counts[item], nb_qubits=job.circuit.nb_qubits, probability=( diff --git a/mpqp/execution/result.py b/mpqp/execution/result.py index 4f679741..45eef829 100644 --- a/mpqp/execution/result.py +++ b/mpqp/execution/result.py @@ -70,8 +70,10 @@ def __init__( int(math.log(len(vector), 2)) if nb_qubits is None else nb_qubits ) """See parameter description.""" - self.probabilities = ( - abs(self.vector) ** 2 if probabilities is None else np.array(probabilities) + self.probabilities: npt.NDArray[np.float64] = ( + (abs(self.vector) ** 2).astype(np.float64) + if probabilities is None + else np.array(probabilities, dtype=np.float64) ) """See parameter description.""" @@ -291,6 +293,7 @@ def __init__( data: float | dict["str", float] | StateVector | list[Sample], errors: Optional[float | dict[Any, Any]] = None, shots: int = 0, + g_phase_handling: bool = True, ): self.job = job """See parameter description.""" @@ -326,7 +329,7 @@ def __init__( job.circuit.input_g_phase + job.circuit._generated_g_phase # pyright: ignore[reportPrivateUsage] ) - if gphase != 0: + if g_phase_handling and gphase != 0: # Reverse the global phase introduced when using CustomGate, due to Qiskit decomposition in QASM2 self._state_vector.vector *= np.exp(1j * gphase) self._probabilities = data.probabilities diff --git a/mpqp/execution/runner.py b/mpqp/execution/runner.py index 7873eabb..e42ff6df 100644 --- a/mpqp/execution/runner.py +++ b/mpqp/execution/runner.py @@ -91,7 +91,11 @@ def adjust_measure(measure: ExpectationMeasure, circuit: QCircuit): Id_before = np.eye(2**n_before) Id_after = np.eye(2**n_after) tweaked_observables.append( - Observable(np.kron(np.kron(Id_before, obs.matrix), Id_after)) + Observable( + np.kron( + np.kron(Id_before, obs.matrix), Id_after + ) # pyright: ignore[reportArgumentType] + ) ) tweaked_measure = ExpectationMeasure( diff --git a/mpqp/execution/vqa/qaoa.py b/mpqp/execution/vqa/qaoa.py index 43b28e40..941d221c 100644 --- a/mpqp/execution/vqa/qaoa.py +++ b/mpqp/execution/vqa/qaoa.py @@ -289,7 +289,7 @@ def _gen_ith_oper( if qubits - i - 1 != 0: result = np.kron(result, np.eye(2 ** (qubits - i - 1))) - return result + return result # pyright: ignore[reportReturnType] def _apply_unitary(circuit: QCircuit, operator: Matrix, parameter: float): diff --git a/mpqp/noise/noise_model.py b/mpqp/noise/noise_model.py index b6661680..78d0a88c 100644 --- a/mpqp/noise/noise_model.py +++ b/mpqp/noise/noise_model.py @@ -156,7 +156,7 @@ def to_adjusted_kraus_operators( for ops in product( *[K if t in targets else [pI.matrix] for t in range(size)] ) - ] + ] # pyright: ignore[reportReturnType] @abstractmethod def to_other_language( diff --git a/mpqp/qasm/lexer_utils.py b/mpqp/qasm/lexer_utils.py index 331a3720..000c6d9f 100644 --- a/mpqp/qasm/lexer_utils.py +++ b/mpqp/qasm/lexer_utils.py @@ -31,6 +31,7 @@ 'COMMA', 'SEMICOLON', 'ID', + 'PRAGMA_MPQP', ) # Reserved words @@ -108,6 +109,7 @@ def t_error(t): # pyright: ignore[reportMissingParameterType] t_SEMICOLON = r';' t_MINUS = r'-' t_DIV = r'/' +t_PRAGMA_MPQP = r'\#pragma\s+mpqp[^\n]*' from mpqp.gates import * diff --git a/mpqp/qasm/mpqp_to_qasm.py b/mpqp/qasm/mpqp_to_qasm.py index 516f626e..8d502af1 100644 --- a/mpqp/qasm/mpqp_to_qasm.py +++ b/mpqp/qasm/mpqp_to_qasm.py @@ -211,6 +211,8 @@ def mpqp_to_qasm2( if previous: qasm_str += _simplify_instruction_to_qasm(previous, targets, c_targets) + if gphase != 0: + qasm_str += f"\n// gphase:{gphase}" qasm_str += qasm_measure return qasm_str, gphase diff --git a/mpqp/qasm/open_qasm_2_and_3.py b/mpqp/qasm/open_qasm_2_and_3.py index e265ab12..835eb3c0 100644 --- a/mpqp/qasm/open_qasm_2_and_3.py +++ b/mpqp/qasm/open_qasm_2_and_3.py @@ -886,7 +886,7 @@ def replace(match: re.Match[str]): return qasm_code -def remove_include_and_comment(qasm_code: str) -> str: +def remove_include_and_comment(qasm_code: str) -> tuple[str, float]: r""" Removes lines that start with 'include' or comments (starting with '\\') from a given OpenQASM code string. @@ -901,20 +901,27 @@ def remove_include_and_comment(qasm_code: str) -> str: >>> qasm_code = '''include "stdgates.inc"; ... qreg q[2]; ... // This is a comment + ... // gphase: 1.57 ... H q[0];''' - >>> print(remove_include_and_comment(qasm_code)) + >>> qasm, gphase = remove_include_and_comment(qasm_code) + >>> print(qasm) qreg q[2]; H q[0]; + >>> gphase + 1.57 """ replaced_code = [] + gphase = 0.00 for line in qasm_code.split("\n"): line = line.lstrip() - if line.startswith("include") or line.startswith("//"): + if line.startswith("// gphase:") or line.startswith("//gphase:"): + gphase += float(line.split(":")[1].strip()) + elif line.startswith("include") or line.startswith("//"): pass else: replaced_code.append(line) - return "\n".join(replaced_code) + return "\n".join(replaced_code), gphase def parse_gphase_instruction( @@ -948,7 +955,13 @@ def parse_gphase_instruction( arg_expr = instr[start : i - 1].strip() try: - val = float(sympify(arg_expr).evalf(subs={"pi": np.pi})) + val = float( + sympify( + arg_expr + ).evalf( # pyright: ignore[reportAttributeAccessIssue] + subs={"pi": np.pi} + ) + ) values.append(val) except ValueError: if instr_match: @@ -970,6 +983,7 @@ def convert_instruction_3_to_2( path_to_main: Optional[str] = None, gphase: float = 0.0, language: Language = Language.QASM3, + remove_measure: bool = False, ) -> tuple[str, str, float]: r"""Some instructions changed name from QASM 2 to QASM 3, also the way to import files changed slightly. This function operates those changes on a @@ -1047,8 +1061,8 @@ def add_qe_lib(): ): with open(f"{path_to_main}/{path}", "r") as f: child = Node(path, parent=included_tree_current) - converted_content, gphase = open_qasm_3_to_2( - f.read(), child, path_to_main, defined_gates, gphase + converted_content = open_qasm_3_to_2( + f.read(), child, path_to_main, defined_gates ) new_path = splitext(path)[0] + "_converted" + splitext(path)[1] with open(f"{path_to_main}/{new_path}", "w") as f: @@ -1067,7 +1081,7 @@ def add_qe_lib(): m = re.match( r"\s*([\w\d_]+)(\[.*?\])?\s*=\s*measure\s*([\w\d_]+)(\[.*?\])?\s*", instr ) - if m: + if not remove_measure and m: c, nb_c, q, nb_q = m.groups() if nb_c and nb_q: instructions_code += f"measure {q}{nb_q} -> {c}{nb_c};\n" @@ -1111,6 +1125,8 @@ def add_qe_lib(): defined_gates, path_to_main, gphase, + language, + remove_measure, ) g_string += " " * 4 + i_code # Add indentation to body instructions header_code += h_code @@ -1130,6 +1146,8 @@ def add_qe_lib(): defined_gates, path_to_main, gphase, + language, + remove_measure, ) instructions_code += if_statement + " " + i_code header_code += h_code @@ -1139,8 +1157,12 @@ def add_qe_lib(): if instr_match: gphase = parse_gphase_instruction(gphase, instr, instr_match) elif language == Language.BRAKET and instr_name == "pragma": - pass + from mpqp.qasm.qasm_to_braket import braket_custom_gates_to_mpqp + custom_gate = braket_custom_gates_to_mpqp(instr) + instructions_code += ( + "#pragma mpqp" + repr(custom_gate).replace('\n', ' ') + "\n" + ) else: gate = instr.split()[0].split("(")[0] if gate not in defined_gates: @@ -1188,7 +1210,8 @@ def open_qasm_3_to_2( defined_gates: Optional[set[str]] = None, gphase: float = 0.0, language: Language = Language.QASM3, -) -> tuple[str, float]: + remove_measure: bool = False, +) -> str: """Converts an OpenQASM 3.0 code back to OpenQASM 2.0. This function will also recursively go through the imported files to @@ -1217,7 +1240,7 @@ def open_qasm_3_to_2( ... c[0] = measure q[0]; ... c[1] = measure q[1]; ... ''' - >>> qasm_2, gphase = open_qasm_3_to_2(qasm3_str) + >>> qasm_2 = open_qasm_3_to_2(qasm3_str) >>> print(qasm_2) # doctest: +NORMALIZE_WHITESPACE OPENQASM 2.0; include "qelib1.inc"; @@ -1274,13 +1297,14 @@ def open_qasm_3_to_2( path_to_file, gphase, language, + remove_measure, ) header_code += h_code instructions_code += i_code - gphase_code = f"// gphase {gphase}\n" if gphase != 0 else "" + gphase_code = f"// gphase:{gphase}\n" if gphase != 0 else "" target_code = header_code + gphase_code + instructions_code - return target_code, gphase + return target_code def parse_openqasm_3_file(code: str) -> list[str]: @@ -1298,6 +1322,7 @@ def parse_openqasm_3_file(code: str) -> list[str]: cleaned_code = re.sub(r"//.*?$|/\*.*?\*/", "", code, flags=re.DOTALL | re.MULTILINE) cleaned_code = cleaned_code.replace("\t", " ").strip() + cleaned_code = re.sub(r"(#pragma[^\n]*)", r"\1;", cleaned_code) gate_matches = list(re.finditer(r"gate .*?}", cleaned_code, re.DOTALL)) @@ -1326,7 +1351,7 @@ def parse_openqasm_3_file(code: str) -> list[str]: return list(filter(lambda i: i.strip() != "", instructions)) -def open_qasm_file_conversion_3_to_2(path: str) -> tuple[str, float]: +def open_qasm_file_conversion_3_to_2(path: str) -> str: """Converts an OpenQASM code in a file from version 3.0 and 2.0. This function is a shorthand to initialize :func:`open_qasm_3_to_2` with the @@ -1355,7 +1380,7 @@ def open_qasm_file_conversion_3_to_2(path: str) -> tuple[str, float]: gate3 q[0], q[1]; c[0] = measure q[0]; c[1] = measure q[1]; - >>> qasm_2, gphase = open_qasm_file_conversion_3_to_2(example_dir + "main_converted.qasm") + >>> qasm_2 = open_qasm_file_conversion_3_to_2(example_dir + "main_converted.qasm") >>> print(qasm_2) # doctest: +NORMALIZE_WHITESPACE OPENQASM 2.0; include 'include1_converted_converted.qasm'; diff --git a/mpqp/qasm/qasm_to_braket.py b/mpqp/qasm/qasm_to_braket.py index 6c9dddfb..a384e4d0 100644 --- a/mpqp/qasm/qasm_to_braket.py +++ b/mpqp/qasm/qasm_to_braket.py @@ -141,7 +141,7 @@ def qasm3_to_braket_Circuit(qasm3_str: str) -> "Circuit": return circuit -def braket_noise_to_mpqp(qasm3_code: str) -> list[NoiseModel]: +def braket_noise_to_mpqp(qasm3_code: str) -> tuple[list[NoiseModel], str]: """ Parse braket's qasm3 pragmas into mpqp's Noise Models. @@ -149,7 +149,7 @@ def braket_noise_to_mpqp(qasm3_code: str) -> list[NoiseModel]: qasm3_code: The OpenQASM3 string containing the noise information. Returns: - A list of MPQP Noise models contained in the qasm3 string. + A tuple of list of MPQP Noise models and cleaned qasm3 string without noise pragmas. Example: >>> qasm_code = ''' @@ -158,8 +158,13 @@ def braket_noise_to_mpqp(qasm3_code: str) -> list[NoiseModel]: ... h q[0]; ... #pragma braket noise phase_damping(0.1) q[0] ... ''' - >>> print(braket_noise_to_mpqp(qasm_code)) # doctest: +NORMALIZE_WHITESPACE + >>> noise, clean_qasm = braket_noise_to_mpqp(qasm_code) + >>> print(noise) # doctest: +NORMALIZE_WHITESPACE [PhaseDamping(0.1, [0])] + >>> print(clean_qasm) # doctest: +NORMALIZE_WHITESPACE + OPENQASM 3.0; + qubit[2] q; + h q[0]; """ from mpqp.noise import ( @@ -171,6 +176,8 @@ def braket_noise_to_mpqp(qasm3_code: str) -> list[NoiseModel]: ) noises = [] + cleaned_lines = [] + for line in qasm3_code.split("\n"): if "depolarizing" in line or "two_qubit_depolarizing" in line: noises.append(NoiseModel.from_other_language(line, Depolarizing)) @@ -192,39 +199,41 @@ def braket_noise_to_mpqp(qasm3_code: str) -> list[NoiseModel]: raise NotImplementedError( f"Error: {noise_model.group(1)} is not supported." ) + else: + cleaned_lines.append(line) - return noises + cleaned_qasm = "\n".join(cleaned_lines) + return noises, cleaned_qasm -def braket_custom_gates_to_mpqp(qasm3_code: str) -> list[CustomGate]: +def braket_custom_gates_to_mpqp(qasm3_code: str) -> CustomGate: """ Parse braket's qasm3 pragmas into mpqp's Custom Gate. Args: - qasm3_code: The OpenQASM3 string containing the noise information. + qasm3_code: The OpenQASM3 string containing the instruction information. Returns: - A list of MPQP Custom Gate contained in the qasm3 string. + A MPQP Custom Gate contained in the qasm3 string. Example: - >>> qasm_code = '''OPENQASM 3.0; - ... bit[1] b; - ... qubit[1] q; - ... #pragma braket unitary([[0, 1.0], [1.0, 0]]) q[0] - ... b[0] = measure q[0]; - ... ''' - >>> print(braket_custom_gates_to_mpqp(qasm_code)) # doctest: +NORMALIZE_WHITESPACE - [CustomGate(array([[0., 1.], [1., 0.]]), [0])] + >>> qasm_code = '''#pragma braket unitary([[0, 1.0], [1.0, 0]]) q[0]''' + >>> print(repr(braket_custom_gates_to_mpqp(qasm_code))) # doctest: +NORMALIZE_WHITESPACE + CustomGate(array([[0., 1.], [1., 0.]]), [0]) """ import ast import re import numpy as np - custom_gates = [] - for line in qasm3_code.split("\n"): - if "braket unitary" in line: - matrix = np.array(ast.literal_eval(line[line.find('[') : line.rfind(')')])) - indices = [int(i) for i in re.findall(r"q\[(\d+)\]", line)] - custom_gates.append(CustomGate(matrix, indices)) - return custom_gates + if "braket unitary" in qasm3_code: + matrix = np.array( + ast.literal_eval(qasm3_code[qasm3_code.find('[') : qasm3_code.rfind(')')]) + ) + indices = [int(i) for i in re.findall(r"q\[(\d+)\]", qasm3_code)] + + return CustomGate(matrix, indices) + else: + raise NotImplementedError( + "Only #pragma braket unitary is supported for the moment." + ) diff --git a/mpqp/qasm/qasm_to_cirq.py b/mpqp/qasm/qasm_to_cirq.py index 60bce588..96b846fd 100644 --- a/mpqp/qasm/qasm_to_cirq.py +++ b/mpqp/qasm/qasm_to_cirq.py @@ -218,6 +218,12 @@ def __str__(self): num_args=1, cirq_gate=(lambda params: MyQasmUGate(*[p for p in params])), ), + "u3": QasmGateStatement( + qasm_gate="u3", + num_params=3, + num_args=1, + cirq_gate=(lambda params: MyQasmUGate(*[p for p in params])), + ), "rxx": QasmGateStatement( qasm_gate="rxx", num_params=1, @@ -231,7 +237,7 @@ def __str__(self): cirq_gate=(lambda params: Rzz(params[0])), ), } - qasm_parser.all_gates |= qs_dict + qasm_parser.qelib_gates |= qs_dict def p_new_reg2(self, p): # pyright: ignore[reportMissingParameterType] """new_reg : QREG ID '[' NATURAL_NUMBER ']' ';' diff --git a/mpqp/qasm/qasm_to_mpqp.py b/mpqp/qasm/qasm_to_mpqp.py index 201ba549..4802cea5 100644 --- a/mpqp/qasm/qasm_to_mpqp.py +++ b/mpqp/qasm/qasm_to_mpqp.py @@ -3,6 +3,7 @@ from typing import TYPE_CHECKING from venv import logger +import numpy as np from ply.lex import lex if TYPE_CHECKING: @@ -70,7 +71,8 @@ def qasm2_parse(input_string: str) -> QCircuit: from mpqp.core.circuit import QCircuit input_string = remove_user_gates(input_string, skip_qelib1=True) - input_string = remove_include_and_comment(input_string) + input_string, gphase = remove_include_and_comment(input_string) + tokens = lex_openqasm(input_string) if ( @@ -83,6 +85,7 @@ def qasm2_parse(input_string: str) -> QCircuit: idx = 3 circuit = QCircuit() + circuit.input_g_phase = gphase i_max = len(tokens) while idx < i_max: logger.debug(circuit) @@ -105,6 +108,8 @@ def _TokenSwitch(circuit: QCircuit, tokens: list[LexToken], idx: int) -> int: return _TokenBarrier(circuit, tokens, idx) elif token.type == 'ID': return _TokenGate(circuit, tokens, idx) + elif token.type == 'PRAGMA_MPQP': + return _TokenCustom(circuit, tokens, idx) else: raise SyntaxError(f"Invalid token: {idx} {token.type}") @@ -302,10 +307,31 @@ def _eval_expr(tokens: list[LexToken], idx: int) -> tuple[Any, int]: import numpy as np # pyright: ignore[reportUnusedImport] expr = "" - while tokens[idx].type != 'COMMA' and tokens[idx].type != 'RPAREN': - if check_num_expr(tokens[idx].type): + open_paren = 0 + while tokens[idx].type != 'COMMA' and ( + tokens[idx].type != 'RPAREN' or open_paren > 0 + ): + if tokens[idx].type == 'LPAREN': + open_paren += 1 + expr += "(" + elif tokens[idx].type == 'RPAREN': + open_paren -= 1 + expr += ")" + elif tokens[idx].type == 'ID' and tokens[idx].value == 'e': + expr += 'e' + idx += 1 + + if tokens[idx].type in ('PLUS', 'MINUS'): + expr += tokens[idx].value + idx += 1 + + if tokens[idx].type not in ('INTN', 'REALN'): + raise SyntaxError("Invalid scientific notation") + + expr += str(tokens[idx].value) + elif check_num_expr(tokens[idx].type): raise SyntaxError(f"not a nb or expr: {idx}, {tokens[idx]}") - if tokens[idx].type == 'PI': + elif tokens[idx].type == 'PI': expr += "np.pi" else: expr += str(tokens[idx].value) @@ -355,6 +381,33 @@ def _Gate_U(circuit: QCircuit, gate_str: str, tokens: list[LexToken], idx: int) return idx + 5 +def _TokenCustom(circuit: QCircuit, tokens: list[LexToken], idx: int) -> int: + raw = tokens[idx].value.strip() + + if not raw.startswith("#pragma mpqp"): + raise SyntaxError(f"Unknown pragma: {raw}") + + expr = raw[len("#pragma mpqp") :].strip() + + safe_globals = { + "__builtins__": {}, + } + + safe_locals = { + "CustomGate": CustomGate, + "array": np.array, + "np": np, + } + + try: + gate = eval(expr, safe_globals, safe_locals) + except Exception as e: + raise SyntaxError(f"Custom gate eval failed: {expr}") from e + + circuit.add(gate) + return idx + 1 + + def parse_qasm2_gates(code: str) -> tuple[str, float]: from mpqp.qasm.open_qasm_2_and_3 import ( qasm_code, @@ -365,7 +418,7 @@ def parse_qasm2_gates(code: str) -> tuple[str, float]: ) import re - code = remove_include_and_comment(code) + code, gphase = remove_include_and_comment(code) lines = code.split(";") lines.insert(1, qasm_code(Instr.QISKIT_CUSTOM_INCLUDE)) @@ -373,7 +426,6 @@ def parse_qasm2_gates(code: str) -> tuple[str, float]: code = remove_user_gates(code) - gphase = 0 clean_code = [] to_add = True diff --git a/mpqp/tools/circuit.py b/mpqp/tools/circuit.py index a0c3d029..16b1fbe2 100644 --- a/mpqp/tools/circuit.py +++ b/mpqp/tools/circuit.py @@ -34,7 +34,7 @@ if TYPE_CHECKING: from qiskit import QuantumCircuit - from qiskit.circuit.quantumcircuitdata import CircuitInstruction + from qiskit._accelerate.circuit import CircuitInstruction def random_circuit( @@ -175,7 +175,7 @@ def random_gate( target, ) elif issubclass(gate_class, Rk): - return Rk(rng.integers(1, 10), target) + return Rk(int(rng.integers(1, 10)), target) elif issubclass(gate_class, RotationGate): if TYPE_CHECKING: assert issubclass(gate_class, (Rx, Ry, Rz, P)) @@ -190,7 +190,7 @@ def random_gate( if TYPE_CHECKING: assert issubclass(gate_class, CRk) return gate_class( - rng.integers(1, 10), + int(rng.integers(1, 10)), control, target, ) @@ -294,7 +294,7 @@ def compute_expected_matrix(qcircuit: QCircuit): def replace_custom_gate( - custom_unitary: "CircuitInstruction", nb_qubits: int + custom_unitary: "CircuitInstruction", nb_qubits: int, targets: list[int] ) -> "tuple[QuantumCircuit, float]": """Decompose and replace the (custom) qiskit unitary given in parameter by a qiskit `QuantumCircuit` composed of ``U`` and ``CX`` gates. @@ -317,19 +317,28 @@ def replace_custom_gate( """ from qiskit import QuantumCircuit, transpile from qiskit.exceptions import QiskitError + from qiskit.circuit.library import UnitaryGate transpilation_circuit = QuantumCircuit(nb_qubits) transpilation_circuit.append(custom_unitary) try: - transpiled = transpile(transpilation_circuit, basis_gates=['u', 'cx']) + transpiled = transpile( + transpilation_circuit, basis_gates=['u3', 'cx'], optimization_level=0 + ) except QiskitError as e: # if the error is arising from TwoQubitWeylDecomposition, we replace the # matrix by the closest unitary if "TwoQubitWeylDecomposition" in str(e): - custom_unitary.operation.params[0] = closest_unitary( - custom_unitary.operation.params[0] + custom_closest_unitary = UnitaryGate(closest_unitary(custom_unitary.matrix)) + transpilation_circuit = QuantumCircuit(nb_qubits) + transpilation_circuit.unitary( + custom_closest_unitary, list(reversed(targets)) + ) + transpiled = transpile( + transpilation_circuit, + basis_gates=['u1', 'u2', 'u3', 'cx'], + optimization_level=0, ) - transpiled = transpile(transpilation_circuit, basis_gates=['u', 'cx']) else: raise e return transpiled, transpiled.global_phase diff --git a/mpqp/tools/maths.py b/mpqp/tools/maths.py index 58e3c966..dfa5e053 100644 --- a/mpqp/tools/maths.py +++ b/mpqp/tools/maths.py @@ -316,16 +316,16 @@ def rand_clifford_matrix( Examples: >>> pprint(rand_clifford_matrix(2)) - [[-0.5 , 0.5 , 0.5j , -0.5j], - [-0.5j, -0.5j, 0.5 , 0.5 ], - [-0.5j, -0.5j, -0.5 , -0.5 ], - [-0.5 , 0.5 , -0.5j, 0.5j ]] + [[0.5 , -0.5j, -0.5 , -0.5j], + [0.5j, 0.5 , 0.5j , -0.5 ], + [0.5j, -0.5 , -0.5j, -0.5 ], + [0.5 , 0.5j , 0.5 , -0.5j]] >>> pprint(rand_clifford_matrix(2, seed=123)) - [[0.70711j, 0 , -0.70711j, 0 ], - [0 , -0.70711j, 0 , -0.70711j], - [0 , 0.70711j , 0 , -0.70711j], - [0.70711j, 0 , 0.70711j , 0 ]] + [[0.70711 , 0 , 0.70711 , 0 ], + [0 , -0.70711, 0 , -0.70711 ], + [-0.70711j, 0 , 0.70711j, 0 ], + [0 , 0.70711j, 0 , -0.70711j]] """ from qiskit.quantum_info import random_clifford @@ -408,7 +408,9 @@ def rand_product_local_unitaries( """ rng = np.random.default_rng(seed) - return reduce(np.kron, [rand_unitary_2x2_matrix(rng) for _ in range(nb_qubits)]) + return reduce( + np.kron, [rand_unitary_2x2_matrix(rng) for _ in range(nb_qubits)] + ) # pyright: ignore[reportReturnType] def rand_unitary_matrix(size: int) -> Matrix: diff --git a/mpqp/tools/pauli_grouping.py b/mpqp/tools/pauli_grouping.py index b88150be..c3c79d28 100644 --- a/mpqp/tools/pauli_grouping.py +++ b/mpqp/tools/pauli_grouping.py @@ -82,7 +82,7 @@ def pauli_monomial_eigenvalues(monom: PauliStringMonomial) -> npt.NDArray[np.flo result = np.array([1], dtype=np.float64) for atom in monom.atoms: result = np.kron(result, atom.eigen_values) - return result + return result # pyright: ignore[reportReturnType] def full_commutation_pauli_grouping_ibm_clique(monomials: list[PauliStringMonomial]): diff --git a/mpqp/tools/theoretical_simulation.py b/mpqp/tools/theoretical_simulation.py index 744118df..bb3a3ad5 100644 --- a/mpqp/tools/theoretical_simulation.py +++ b/mpqp/tools/theoretical_simulation.py @@ -52,13 +52,10 @@ def amplitude( state = np.zeros((d), dtype=np.complex128) state[0] = 1 gates = circ.gates - print(state) for gate in gates: g = gate.to_matrix(circ.nb_qubits) - print(g) state = g @ state - print(state) for noise in circ.noises: if ( len(noise.gates) == 0 diff --git a/mpqp/tools/unitary_decomposition.py b/mpqp/tools/unitary_decomposition.py index 0f445ca9..7ccda890 100644 --- a/mpqp/tools/unitary_decomposition.py +++ b/mpqp/tools/unitary_decomposition.py @@ -162,17 +162,23 @@ def _decompose(U: Matrix, circuit: QCircuit, position: int = 0) -> QCircuit: Vv, MuxRzv, Wv = _unitary_SVD(V12) # Extracts the rotations of both multiplexed Rz for later decomposition - du = np.angle(MuxRzu.diagonal()) + du = np.angle( + MuxRzu.diagonal() # pyright: ignore[reportCallIssue, reportArgumentType] + ) for i in range(len(du) // 2): du[i] *= -1 - dv = np.angle(MuxRzv.diagonal()) + dv = np.angle( + MuxRzv.diagonal() # pyright: ignore[reportCallIssue, reportArgumentType] + ) for i in range(len(dv) // 2): dv[i] *= -1 # Now recursively decompose every obtained matrices. circuit = _decompose(Wv, circuit, position + 1) - circuit = _gray_code_decomposition(dv, circuit, position, Rz) + circuit = _gray_code_decomposition( + dv, circuit, position, Rz # pyright: ignore[reportArgumentType] + ) circuit = _decompose(Vv, circuit, position + 1) circuit = _gray_code_decomposition( @@ -183,7 +189,9 @@ def _decompose(U: Matrix, circuit: QCircuit, position: int = 0) -> QCircuit: ) circuit = _decompose(Wu, circuit, position + 1) - circuit = _gray_code_decomposition(du, circuit, position, Rz) + circuit = _gray_code_decomposition( + du, circuit, position, Rz # pyright: ignore[reportArgumentType] + ) circuit = _decompose(Vu, circuit, position + 1) return circuit diff --git a/requirements-dev.txt b/requirements-dev.txt index dd08bec2..01fef36c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,13 +1,13 @@ -r requirements.txt -setuptools>=56.0.0 -pytest==7.1.1 +setuptools==81.0.0 +pytest==9.0.2 nbmake -sphinx<=5.1.1 +sphinx==9.1.0 ; python_version >= "3.12" +sphinx==8.1.3 ; python_version < "3.12" sphinx-rtd-dark-mode>=1.3.0 sphinx-copybutton>=0.5.2 sphinx_github_changelog>=1.3.0 -nbsphinx==0.9.3 -nbsphinx_link==1.3.0 -pyright==1.1.364 +nbsphinx +pyright==1.1.408 boto3-stubs black==24.4.2 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 3199e7fe..c58924b7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,18 +1,18 @@ -numpy>=1.26.4 -scipy>=1.11.4 -python_dotenv>=1.0.0 -jsonschema>=4.17.3 -typeguard>=4.2.1 -pylatexenc==2.10 -anytree>=2.12.1 -sympy==1.13.1 -pick==2.2.0 -termcolor==2.4.0 -symengine>=0.11.0 -numba==0.60.0 -aenum==3.1.15 +numpy +scipy +python_dotenv +jsonschema +typeguard +pylatexenc +anytree +sympy +pick +termcolor +symengine +numba +aenum==3.1.16 ply matplotlib networkx -qiskit==1.2.4 -qiskit-aer==0.15.1 \ No newline at end of file +qiskit==2.3.0 +qiskit-aer==0.17.2 \ No newline at end of file diff --git a/requirements_providers/azure.txt b/requirements_providers/azure.txt index 518d7fb2..b7034d12 100644 --- a/requirements_providers/azure.txt +++ b/requirements_providers/azure.txt @@ -1,2 +1,2 @@ -azure-quantum==3.1.0 +azure-quantum==3.6.1 azure-quantum[qiskit] \ No newline at end of file diff --git a/requirements_providers/braket.txt b/requirements_providers/braket.txt index dc849fa5..8063abf5 100644 --- a/requirements_providers/braket.txt +++ b/requirements_providers/braket.txt @@ -1,4 +1,5 @@ -amazon-braket-sdk==1.106.5 +amazon-braket-sdk==1.112.1 ; python_version >= "3.11" +amazon-braket-sdk==1.106.5 ; python_version < "3.11" amazon-braket-default-simulator==1.32.0 -boto3==1.35.82 +boto3==1.42.53 aws-configure==2.1.8 \ No newline at end of file diff --git a/requirements_providers/cirq.txt b/requirements_providers/cirq.txt index a624648f..c12dfa26 100644 --- a/requirements_providers/cirq.txt +++ b/requirements_providers/cirq.txt @@ -1,5 +1,10 @@ -cirq-core==1.3.0 -qsimcirq -cirq-google==1.3.0 -cirq-ionq==1.3.0 -cirq-aqt==1.3.0 \ No newline at end of file +cirq-core==1.5.0 ; python_version < "3.11" +cirq-google==1.5.0 ; python_version < "3.11" +cirq-ionq==1.5.0 ; python_version < "3.11" +cirq-aqt==1.5.0 ; python_version < "3.11" + +cirq-core==1.6.1 ; python_version >= "3.11" +cirq-google==1.6.1 ; python_version >= "3.11" +cirq-ionq==1.6.1 ; python_version >= "3.11" +cirq-aqt==1.6.1 ; python_version >= "3.11" +qsimcirq \ No newline at end of file diff --git a/requirements_providers/myqlm.txt b/requirements_providers/myqlm.txt index b1cd6e02..8fb703da 100644 --- a/requirements_providers/myqlm.txt +++ b/requirements_providers/myqlm.txt @@ -1,2 +1,3 @@ -myqlm==1.10.6 -myqlm-interop \ No newline at end of file +myqlm==1.12.4 +myqlm-interop +setuptools==80.10.2 \ No newline at end of file diff --git a/requirements_providers/qiskit.txt b/requirements_providers/qiskit.txt index 0c8c4291..cbd689a7 100644 --- a/requirements_providers/qiskit.txt +++ b/requirements_providers/qiskit.txt @@ -1,2 +1,2 @@ -qiskit-ibm-runtime==0.31.0 -qiskit-aer==0.15.1 \ No newline at end of file +qiskit-ibm-runtime==0.45.1 +qiskit-ionq==1.0.2 \ No newline at end of file diff --git a/tests/core/instruction/gates/test_native_gates.py b/tests/core/instruction/gates/test_native_gates.py index 22489846..8f24355c 100644 --- a/tests/core/instruction/gates/test_native_gates.py +++ b/tests/core/instruction/gates/test_native_gates.py @@ -11,8 +11,8 @@ theta, k = symbols("θ k") c, s, e = cos(theta), sin(theta), exp(1.0 * I * theta) c2, s2, e2 = ( - cos(theta / 2), # pyright: ignore[reportOperatorIssue] - sin(theta / 2), # pyright: ignore[reportOperatorIssue] + cos(theta / 2), + sin(theta / 2), exp(1.0 * I * theta / 2), ) @@ -52,7 +52,7 @@ def test_P(angle: float, result_matrix: Matrix): theta, 0, 0, - np.array([[c2, -1.0 * s2], [1.0 * s2, 1.0 * c2]]), # pyright: ignore + np.array([[c2, -1.0 * s2], [1.0 * s2, 1.0 * c2]]), ), ], ) @@ -76,7 +76,7 @@ def test_U(theta: float, phi: float, gamma: float, result_matrix: Matrix): ), ( theta, - np.array([[c2, -1j * s2], [-1j * s2, c2]]), # pyright: ignore + np.array([[c2, -1j * s2], [-1j * s2, c2]]), ), ], ) diff --git a/tests/core/instruction/measurement/test_basis_measure.py b/tests/core/instruction/measurement/test_basis_measure.py index 04820664..8831c6c3 100644 --- a/tests/core/instruction/measurement/test_basis_measure.py +++ b/tests/core/instruction/measurement/test_basis_measure.py @@ -1,6 +1,17 @@ import pytest -from mpqp import BasisMeasure, ComputationalBasis +from mpqp import ( + BasisMeasure, + ComputationalBasis, + run, + QCircuit, + IBMDevice, + ATOSDevice, + AWSDevice, + GOOGLEDevice, +) +from mpqp.core.instruction.gates.native_gates import X +from mpqp.execution.devices import AvailableDevice def test_basis_measure_init(): @@ -19,3 +30,94 @@ def test_basis_measure_repr(): measure = BasisMeasure([0, 1], shots=1025) representation = repr(measure) assert representation == "BasisMeasure([0, 1], shots=1025)" + + +def qcircuit_basis_measure() -> list[tuple[QCircuit, list[int]]]: + return [ + ( + QCircuit( + [X(0), X(1), X(2), X(3), BasisMeasure([3], shots=1024)], nb_qubits=4 + ), + [0, 1024], + ), + ( + QCircuit( + [X(0), X(1), X(2), X(3), BasisMeasure([1], shots=1024)], nb_qubits=4 + ), + [0, 1024], + ), + ( + QCircuit( + [X(0), X(1), X(2), X(3), BasisMeasure([0, 1], shots=1024)], nb_qubits=4 + ), + [0, 0, 0, 1024], + ), + ( + QCircuit( + [X(0), X(1), X(2), X(3), BasisMeasure([2, 3], shots=1024)], nb_qubits=4 + ), + [0, 0, 0, 1024], + ), + ( + QCircuit( + [X(0), X(1), X(2), X(3), BasisMeasure([1, 2, 3], shots=1024)], + nb_qubits=4, + ), + [0, 0, 0, 0, 0, 0, 0, 1024], + ), + ] + + +@pytest.mark.provider("qiskit") +@pytest.mark.parametrize( + "qcircuit, result_count", + qcircuit_basis_measure(), +) +def test_basis_measure_not_all_targets_qiskit( + qcircuit: QCircuit, result_count: list[int] +): + exec_basis_measure_not_all_targets(IBMDevice.AER_SIMULATOR, qcircuit, result_count) + + +@pytest.mark.provider("cirq") +@pytest.mark.parametrize( + "qcircuit, result_count", + qcircuit_basis_measure(), +) +def test_basis_measure_not_all_targets_cirq( + qcircuit: QCircuit, result_count: list[int] +): + exec_basis_measure_not_all_targets( + GOOGLEDevice.CIRQ_LOCAL_SIMULATOR, qcircuit, result_count + ) + + +@pytest.mark.provider("braket") +@pytest.mark.parametrize( + "qcircuit, result_count", + qcircuit_basis_measure(), +) +def test_basis_measure_not_all_targets_braket( + qcircuit: QCircuit, result_count: list[int] +): + exec_basis_measure_not_all_targets( + AWSDevice.BRAKET_LOCAL_SIMULATOR, qcircuit, result_count + ) + + +@pytest.mark.provider("atos") +@pytest.mark.parametrize( + "qcircuit, result_count", + qcircuit_basis_measure(), +) +def test_basis_measure_not_all_targets_atos( + qcircuit: QCircuit, result_count: list[int] +): + exec_basis_measure_not_all_targets(ATOSDevice.MYQLM_CLINALG, qcircuit, result_count) + + +def exec_basis_measure_not_all_targets( + provider: AvailableDevice, qcircuit: QCircuit, result_count: list[int] +): + result = run(qcircuit, provider) + assert result.counts == result_count diff --git a/tests/core/instruction/measurement/test_measure.py b/tests/core/instruction/measurement/test_measure.py index 5b30d44c..827cce28 100644 --- a/tests/core/instruction/measurement/test_measure.py +++ b/tests/core/instruction/measurement/test_measure.py @@ -3,7 +3,7 @@ import pytest if TYPE_CHECKING: - from qiskit.circuit import Parameter + from qiskit._accelerate.circuit import Parameter from mpqp import Language, Measure from mpqp.tools.generics import flatten diff --git a/tests/core/test_circuit.py b/tests/core/test_circuit.py index 8c8c2f75..633f8839 100644 --- a/tests/core/test_circuit.py +++ b/tests/core/test_circuit.py @@ -191,39 +191,39 @@ def list_braket_funky_circuits() -> list[BraketCircuit]: def list_cirq_funky_circuits() -> list[cirq_Circuit | Moment]: import cirq - q0, q1, q2 = cirq.LineQubit.range(3) # type: ignore[reportPrivateImportUsage] - - cirq_circuit_1 = cirq.Circuit() # type: ignore[reportPrivateImportUsage] - - cirq_circuit_1.append(cirq.X(q0)) # type: ignore[reportPrivateImportUsage] - cirq_circuit_1.append(cirq.Y(q0)) # type: ignore[reportPrivateImportUsage] - cirq_circuit_1.append(cirq.Z(q0)) # type: ignore[reportPrivateImportUsage] - cirq_circuit_1.append(cirq.H(q0)) # type: ignore[reportPrivateImportUsage] - cirq_circuit_1.append(cirq.S(q0)) # type: ignore[reportPrivateImportUsage] - cirq_circuit_1.append(cirq.S(q0) ** -1) # type: ignore[reportPrivateImportUsage] - cirq_circuit_1.append(cirq.T(q0)) # type: ignore[reportPrivateImportUsage] - cirq_circuit_1.append(cirq.T(q0) ** -1) # type: ignore[reportPrivateImportUsage] - cirq_circuit_1.append(cirq.rx(np.pi / 4)(q0)) # type: ignore[reportPrivateImportUsage] - cirq_circuit_1.append(cirq.ry(np.pi / 4)(q0)) # type: ignore[reportPrivateImportUsage] - cirq_circuit_1.append(cirq.rz(np.pi / 4)(q0)) # type: ignore[reportPrivateImportUsage] - - cirq_circuit_1.append(cirq.CX(q0, q1)) # type: ignore[reportPrivateImportUsage] - cirq_circuit_1.append(cirq.ControlledGate(cirq.Y).on(q0, q1)) # type: ignore[reportPrivateImportUsage] - cirq_circuit_1.append(cirq.CZ(q0, q1)) # type: ignore[reportPrivateImportUsage] - cirq_circuit_1.append(cirq.ControlledGate(cirq.H).on(q0, q1)) # type: ignore[reportPrivateImportUsage] - cirq_circuit_1.append(cirq.SWAP(q0, q1)) # type: ignore[reportPrivateImportUsage] - cirq_circuit_1.append(cirq.ControlledGate(cirq.rz(np.pi / 4)).on(q0, q1)) # type: ignore[reportPrivateImportUsage] - - cirq_circuit_1.append(cirq.CCX(q0, q1, q2)) # type: ignore[reportPrivateImportUsage] - cirq_circuit_1.append(cirq.CSWAP(q0, q1, q2)) # type: ignore[reportPrivateImportUsage] - - qubit = cirq.LineQubit(0) # type: ignore[reportPrivateImportUsage] - cirq_circuit_2 = cirq.Circuit() # type: ignore[reportPrivateImportUsage] - cirq_circuit_2.append(cirq.H(qubit)) # type: ignore[reportPrivateImportUsage] - cirq_circuit_2.append(cirq.measure(qubit)) # type: ignore[reportPrivateImportUsage] - - q0, q1 = cirq.LineQubit.range(2) # type: ignore[reportPrivateImportUsage] - moment = cirq.Moment([cirq.H(q0), cirq.H(q1)]) # type: ignore[reportPrivateImportUsage] + q0, q1, q2 = cirq.LineQubit.range(3) + + cirq_circuit_1 = cirq.Circuit() + + cirq_circuit_1.append(cirq.X(q0)) + cirq_circuit_1.append(cirq.Y(q0)) + cirq_circuit_1.append(cirq.Z(q0)) + cirq_circuit_1.append(cirq.H(q0)) + cirq_circuit_1.append(cirq.S(q0)) + cirq_circuit_1.append(cirq.S(q0) ** -1) + cirq_circuit_1.append(cirq.T(q0)) + cirq_circuit_1.append(cirq.T(q0) ** -1) + cirq_circuit_1.append(cirq.rx(np.pi / 4)(q0)) + cirq_circuit_1.append(cirq.ry(np.pi / 4)(q0)) + cirq_circuit_1.append(cirq.rz(np.pi / 4)(q0)) + + cirq_circuit_1.append(cirq.CX(q0, q1)) + cirq_circuit_1.append(cirq.ControlledGate(cirq.Y).on(q0, q1)) + cirq_circuit_1.append(cirq.CZ(q0, q1)) + cirq_circuit_1.append(cirq.ControlledGate(cirq.H).on(q0, q1)) + cirq_circuit_1.append(cirq.SWAP(q0, q1)) + cirq_circuit_1.append(cirq.ControlledGate(cirq.rz(np.pi / 4)).on(q0, q1)) + + cirq_circuit_1.append(cirq.CCX(q0, q1, q2)) + cirq_circuit_1.append(cirq.CSWAP(q0, q1, q2)) + + qubit = cirq.LineQubit(0) + cirq_circuit_2 = cirq.Circuit() + cirq_circuit_2.append(cirq.H(qubit)) + cirq_circuit_2.append(cirq.measure(qubit)) + + q0, q1 = cirq.LineQubit.range(2) + moment = cirq.Moment([cirq.H(q0), cirq.H(q1)]) return [cirq_circuit_1, cirq_circuit_2, moment] @@ -511,10 +511,10 @@ def test_without_measurements(circuit: QCircuit, printed_result_filename: str): QiskitCircuit, ( "[CircuitInstruction(operation=Instruction(name='x', num_qubits=1," - " num_clbits=0, params=[]), qubits=(Qubit(QuantumRegister(2, 'q'), 1),)," + " num_clbits=0, params=[]), qubits=(,)," " clbits=()), CircuitInstruction(operation=Instruction(name='cx'," - " num_qubits=2, num_clbits=0, params=[]), qubits=(Qubit(QuantumRegister(2," - " 'q'), 1), Qubit(QuantumRegister(2, 'q'), 0)), clbits=())]" + " num_qubits=2, num_clbits=0, params=[]), qubits=(, ), clbits=())]" ), ), ], @@ -588,7 +588,7 @@ def test_to_other_language_braket( def _create_large_circuits_for_tests() -> tuple[QiskitCircuit, QiskitCircuit]: from qiskit import ClassicalRegister, QuantumRegister - from qiskit.circuit.library import RC3XGate + from qiskit.circuit.library import RC3XGate, RZZGate qreg_q = QuantumRegister(4, 'q') creg_c = ClassicalRegister(4, 'c') @@ -620,7 +620,8 @@ def _create_large_circuits_for_tests() -> tuple[QiskitCircuit, QiskitCircuit]: circuit.id(qreg_q[3]) circuit_2 = QiskitCircuit(qreg_q, creg_c) - circuit_2.rzz(np.pi / 2, qreg_q[1], qreg_q[2]).c_if(creg_c, 0) + with circuit_2.if_test((creg_c, 0)): + circuit_2.append(RZZGate(np.pi / 2), [qreg_q[1], qreg_q[2]]) return circuit, circuit_2 @@ -641,9 +642,8 @@ def test_from_qiskit(circuit: QiskitCircuit, expected_output: Optional[str]): if not isinstance(expected_output, str): qcircuit = QCircuit.from_other_language(circuit) - matrix = Operator(circuit.reverse_bits()).data - assert matrix_eq( - matrix, qcircuit.to_matrix() # pyright: ignore[reportArgumentType] + assert Operator(circuit).equiv( + Operator(qcircuit.to_other_language(Language.QISKIT)) ) else: with pytest.raises(ValueError, match=expected_output): diff --git a/tests/execution/test_validity.py b/tests/execution/test_validity.py index 0cd1e3e8..69980245 100644 --- a/tests/execution/test_validity.py +++ b/tests/execution/test_validity.py @@ -562,7 +562,7 @@ def exec_observable_ideal_case( expected_vector.transpose() .conjugate() .dot(observable.dot(expected_vector)) - .real + .real # pyright: ignore[reportAttributeAccessIssue] ) batch = run(c, sampling_devices) assert isinstance(batch, BatchResult) @@ -977,7 +977,7 @@ def test_validity_optim_ideal_single_diag_obs_and_regular_run_qiskit( exec_validity_optim_ideal_single_diag_obs_and_regular_run( circuit, observable, - GOOGLEDevice.CIRQ_LOCAL_SIMULATOR, + IBMDevice.AER_SIMULATOR, ) diff --git a/tests/qasm/test_mpqp_to_qasm.py b/tests/qasm/test_mpqp_to_qasm.py index d525747b..fdc6444b 100644 --- a/tests/qasm/test_mpqp_to_qasm.py +++ b/tests/qasm/test_mpqp_to_qasm.py @@ -4,7 +4,6 @@ from mpqp import Barrier, BasisMeasure, Instruction, Language, QCircuit from mpqp.gates import * from mpqp.qasm.mpqp_to_qasm import mpqp_to_qasm2 -from mpqp.qasm.open_qasm_2_and_3 import remove_user_gates from mpqp.tools.circuit import random_circuit from mpqp.tools.display import format_element_str @@ -266,17 +265,10 @@ def test_mpqp_to_qasm_gate(instructions: list[Instruction], qasm_expectation: st ) def test_mpqp_to_qasm_custom_gate(instructions: list[Instruction]): circuit = QCircuit(instructions) - from qiskit import QuantumCircuit, qasm2 - qiskit_circuit = circuit.to_other_language(Language.QISKIT) - assert isinstance(qiskit_circuit, QuantumCircuit) - str_qiskit_circuit = remove_user_gates(qasm2.dumps(qiskit_circuit), True) str_circuit = circuit.to_other_language(Language.QASM2) - assert isinstance(str_circuit, str) - for i in str_circuit: - assert i in str_qiskit_circuit - for i in str_qiskit_circuit: - assert i in str_circuit + mpqp_qasm = QCircuit.from_other_language(str_circuit) + assert np.allclose(circuit.to_matrix(), mpqp_qasm.to_matrix()) @pytest.mark.parametrize( @@ -525,7 +517,7 @@ def test_random_mpqp_to_qasm(): qcircuit = random_circuit(nb_qubits=6, nb_gates=20) from qiskit import QuantumCircuit, qasm2 - qiskit_circuit = qcircuit.to_other_language(Language.QISKIT).reverse_bits() + qiskit_circuit = qcircuit.to_other_language(Language.QISKIT) assert isinstance(qiskit_circuit, QuantumCircuit) qiskit_qasm = normalize_string(qasm2.dumps(qiskit_circuit)) mpqp_qasm = qcircuit.to_other_language(Language.QASM2) diff --git a/tests/qasm/test_open_qasm_2_and_3.py b/tests/qasm/test_open_qasm_2_and_3.py index 47f75e38..133f9489 100644 --- a/tests/qasm/test_open_qasm_2_and_3.py +++ b/tests/qasm/test_open_qasm_2_and_3.py @@ -151,7 +151,7 @@ def test_circular_dependency_detection_false_positive_3_to_2(): ], ) def test_conversion_2_and_3(qasm_code: str): - convert, _ = open_qasm_3_to_2(open_qasm_2_to_3(qasm_code)) + convert = open_qasm_3_to_2(open_qasm_2_to_3(qasm_code)) assert normalize_whitespace(convert) == normalize_whitespace(qasm_code) @@ -357,7 +357,7 @@ def test_conversion_2_to_3(qasm_code: str, expected_output: str): ], ) def test_conversion_3_to_2(expected_output: str, qasm_code: str): - convert, _ = open_qasm_3_to_2(qasm_code) + convert = open_qasm_3_to_2(qasm_code) assert normalize_whitespace(convert) == normalize_whitespace(expected_output) @@ -889,9 +889,7 @@ def test_remove_user_gates(qasm_code: str, expected_output: str): def test_sample_counts_in_trust_interval( qasm3: str, expected: tuple[list[Instruction], float] ): - qasm_2, gphase = open_qasm_3_to_2(qasm3) - print(gphase) - print(qasm_2) + qasm_2 = open_qasm_3_to_2(qasm3) circuit = qasm2_parse(qasm_2) instructions, expected_gphase = expected @@ -900,19 +898,12 @@ def test_sample_counts_in_trust_interval( err_rate_percentage = 1 - np.power(1 - err_rate, (1 / 2)) expected_amplitudes = amplitude(expected_circuit) * exp(expected_gphase * 1j) - - print(circuit.input_g_phase) - circuit.input_g_phase = gphase - print(circuit.input_g_phase) result = run(circuit, IBMDevice.AER_SIMULATOR) assert isinstance(result, Result) - print("result_amplitudes: " + str(result.amplitudes)) - print("expected_amplitudes: " + str(expected_amplitudes)) counts = result.amplitudes # check if the true value is inside the trust interval for i in range(len(counts)): trust_interval = err_rate_percentage * expected_amplitudes[i] - print(trust_interval) assert ( counts[i] - trust_interval <= expected_amplitudes[i] diff --git a/tests/test_doc.py b/tests/test_doc.py index c9806fdc..66e3af8f 100644 --- a/tests/test_doc.py +++ b/tests/test_doc.py @@ -264,10 +264,7 @@ def run_doctest( skip_provider_flags = {} if active_providers is not None: for name, flag in PROVIDER_FLAGS.items(): - if ( - len(active_providers) == 0 # pyright: ignore[reportArgumentType] - or name not in active_providers # pyright: ignore[reportOperatorIssue] - ): + if len(active_providers) == 0 or name not in active_providers: skip_provider_flags[name] = flag monkeypatch.setattr('numpy.random.default_rng', stable_random)