From b85b37919e365bc61c18114d8042d73c3683ae5d Mon Sep 17 00:00:00 2001 From: NoriyukiK-1qbit <141876649+NoriyukiK-1qbit@users.noreply.github.com> Date: Sun, 8 Oct 2023 07:33:09 -0700 Subject: [PATCH 01/11] Remove comments after meaningful statements in QASM reader (#218) * QASMBench has comments after instructions QASMBench has lines that have comments after instructions, and those lines cause errors. This fix removes such comments. --- src/qutip_qip/qasm.py | 8 +++++ tests/qasm_files/w-state_with_comments.qasm | 33 +++++++++++++++++++++ tests/test_qasm.py | 11 +++++++ 3 files changed, 52 insertions(+) create mode 100644 tests/qasm_files/w-state_with_comments.qasm diff --git a/src/qutip_qip/qasm.py b/src/qutip_qip/qasm.py index 456133c4..7b2fecc4 100644 --- a/src/qutip_qip/qasm.py +++ b/src/qutip_qip/qasm.py @@ -886,6 +886,14 @@ def read_qasm(qasm_input, mode="qiskit", version="2.0", strmode=False): # split input into lines and ignore comments qasm_lines = [line.strip() for line in qasm_lines] qasm_lines = list(filter(lambda x: x[:2] != "//" and x != "", qasm_lines)) + # QASMBench Benchmark Suite has lines that have comments after instructions. + # Not sure if QASM standard allows this. + for i in range(len(qasm_lines)): + qasm_line = qasm_lines[i] + loc_comment = qasm_line.find("//") + if loc_comment >= 0: + qasm_line = qasm_line[0:loc_comment] + qasm_lines[i] = qasm_line if version != "2.0": raise NotImplementedError( diff --git a/tests/qasm_files/w-state_with_comments.qasm b/tests/qasm_files/w-state_with_comments.qasm new file mode 100644 index 00000000..c05d1885 --- /dev/null +++ b/tests/qasm_files/w-state_with_comments.qasm @@ -0,0 +1,33 @@ + +// Name of Experiment: W-state v1 + +OPENQASM 2.0; +include "qelib1.inc"; + + +qreg q[4]; // This comments also should be handled. +creg c[3]; // QASMBench has this type of comments. +gate cH a,b { +h b; +sdg b; +cx a,b; +h b; +t b; +cx a,b; +t b; +h b; +s b; +x b; +s a; +} + +u3(1.91063,0,0) q[0]; +cH q[0],q[1]; +ccx q[0],q[1],q[2]; +x q[0]; +x q[1]; +cx q[0],q[1]; + +measure q[0] -> c[0]; +measure q[1] -> c[1]; +measure q[2] -> c[2]; diff --git a/tests/test_qasm.py b/tests/test_qasm.py index 33d7260a..95434c8a 100644 --- a/tests/test_qasm.py +++ b/tests/test_qasm.py @@ -134,3 +134,14 @@ def test_export_import(): for u0, u1 in zip(props, read_props): assert (u0 - u1).norm() < 1e-12 + + +def test_read_qasm(): + filename = "w-state.qasm" + filepath = Path(__file__).parent / 'qasm_files' / filename + filename2 = "w-state_with_comments.qasm" + filepath2 = Path(__file__).parent / 'qasm_files' / filename2 + + qc = read_qasm(filepath) + qc2 = read_qasm(filepath2) + assert True From cccf4d60629ea6a0e7a00831b0b5dbb97d844aa2 Mon Sep 17 00:00:00 2001 From: Boxi Li Date: Fri, 3 Nov 2023 00:30:30 +0100 Subject: [PATCH 02/11] Merge pull request #221 from kpobrien/master fix circuit plot error for gates with classical controls when reverse_states=False --- src/qutip_qip/circuit/circuit.py | 4 +++- tests/test_circuit.py | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/qutip_qip/circuit/circuit.py b/src/qutip_qip/circuit/circuit.py index 6d20e107..e619f16c 100644 --- a/src/qutip_qip/circuit/circuit.py +++ b/src/qutip_qip/circuit/circuit.py @@ -1013,7 +1013,9 @@ def latex_code(self): gate.classical_controls and (n - self.N) in gate.classical_controls ): - control_tag = n - gate.targets[0] + control_tag = (-1 if self.reverse_states else 1) * ( + gate.targets[0] - n + ) col.append(r" \ctrl{%d} " % control_tag) elif not gate.controls and not gate.targets: diff --git a/tests/test_circuit.py b/tests/test_circuit.py index 3b55c53d..346c2878 100644 --- a/tests/test_circuit.py +++ b/tests/test_circuit.py @@ -692,6 +692,25 @@ def test_latex_code_teleportation_circuit(self): "", ]) + def test_latex_code_classical_controls(self): + qc = QubitCircuit(1, num_cbits=1, reverse_states=True) + qc.add_gate("X", targets=0, classical_controls=[0]) + latex = qc.latex_code() + assert latex == self._latex_template % "\n".join([ + r" & & \ctrl{1} & \qw \\ ", + r" & & \gate{X} & \qw \\ ", + "", + ]) + + qc = QubitCircuit(1, num_cbits=1, reverse_states=False) + qc.add_gate("X", targets=0, classical_controls=[0]) + latex = qc.latex_code() + assert latex == self._latex_template % "\n".join([ + r" & & \gate{X} & \qw \\ ", + r" & & \ctrl{-1} & \qw \\ ", + "", + ]) + H = Qobj([[1/np.sqrt(2), 1/np.sqrt(2)], [1/np.sqrt(2), -1/np.sqrt(2)]]) H_zyz_gates = _ZYZ_rotation(H) H_zyz_quantum_circuit = QubitCircuit(1) From 108d69894cf96c955b7affc7fb86dfdd2fa9dc4f Mon Sep 17 00:00:00 2001 From: Boxi Li Date: Thu, 16 Nov 2023 14:37:44 +0100 Subject: [PATCH 03/11] Update RTD configuration for create_tutorials_html (#222) --- .readthedocs.yaml | 23 +++++++++++++++++++---- doc/requirements.txt | 2 +- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 9324641d..3127a6ab 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,14 +1,29 @@ -# File: .readthedocs.yaml +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details +# Required version: 2 -# Build from the doc/ directory with Sphinx +# Set the version of Python and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.10" + apt_packages: + - pandoc + jobs: + pre_build: + # Each command is executed in a new shell process + - cd doc; python source/tutorials-website/create_tutorials_html.py + +# Build documentation in the docs/ directory with Sphinx sphinx: configuration: doc/source/conf.py -# Explicitly set the version of Python and its requirements +# We recommend specifying your dependencies to enable reproducible builds: +# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html python: - version: 3.8 install: - requirements: doc/requirements.txt - method: pip diff --git a/doc/requirements.txt b/doc/requirements.txt index 0dc731b6..d2491457 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -1,6 +1,6 @@ numpy==1.22.4 scipy==1.10.0 -qutip==4.7.0 +qutip==4.7.3 cython==0.29.30 sphinx==5.0.2 sphinx_rtd_theme==1.0.0 From 88f0c93ff6b3c5891c6fef6037205e2fb3ab835e Mon Sep 17 00:00:00 2001 From: Boxi Li Date: Thu, 22 Feb 2024 16:28:52 +0100 Subject: [PATCH 04/11] Merge pull request #229 from BoxiLi/fix_bugs Update black version and fix it to version to 24 --- .github/workflows/black.yml | 3 ++- .github/workflows/test.yml | 6 ++--- src/qutip_qip/algorithms/qft.py | 1 - src/qutip_qip/circuit/__init__.py | 1 + src/qutip_qip/circuit/_decompose.py | 1 + src/qutip_qip/circuit/circuit.py | 1 + src/qutip_qip/compat.py | 1 + src/qutip_qip/compiler/__init__.py | 1 + src/qutip_qip/decompose/__init__.py | 1 + src/qutip_qip/device/__init__.py | 1 + src/qutip_qip/noise.py | 1 + src/qutip_qip/operations/__init__.py | 1 + src/qutip_qip/pulse.py | 1 + src/qutip_qip/qasm.py | 7 ++--- src/qutip_qip/qir.py | 18 ++++++------- src/qutip_qip/qiskit/backend.py | 40 +++++++++++++++++----------- src/qutip_qip/qiskit/converter.py | 6 ++--- src/qutip_qip/qubits.py | 1 + src/qutip_qip/vqa.py | 1 + tests/test_compiler.py | 2 +- 20 files changed, 59 insertions(+), 36 deletions(-) diff --git a/.github/workflows/black.yml b/.github/workflows/black.yml index d4a72a82..21a42176 100644 --- a/.github/workflows/black.yml +++ b/.github/workflows/black.yml @@ -10,4 +10,5 @@ jobs: - uses: psf/black@stable with: options: "--check --diff" - src: "./src" \ No newline at end of file + src: "./src" + version: "~= 24.0" \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 22fb79fa..3f8fe1c4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,9 +12,9 @@ jobs: strategy: matrix: include: - - case-name: qutip@5.0.0a1 + - case-name: qutip@5.0.0a2 os: ubuntu-latest - qutip-version: '==5.0.0a1' + qutip-version: '==5.0.0a2' pyqir-version: '' python-version: '3.10' - case-name: qutip@qutip-4.7.X @@ -77,7 +77,7 @@ jobs: # For qutip-v5 qutip.control is replaced by qutip-qtrl - name: Install qutip-qtrl from PyPI - if: ${{ matrix.qutip-version == '@master'}} + if: ${{ matrix.qutip-version == '@master' || startsWith( matrix.qutip-version, '==5') }} run: | python -m pip install 'git+https://github.com/qutip/qutip-qtrl.git' diff --git a/src/qutip_qip/algorithms/qft.py b/src/qutip_qip/algorithms/qft.py index f3830a3b..e9329a12 100644 --- a/src/qutip_qip/algorithms/qft.py +++ b/src/qutip_qip/algorithms/qft.py @@ -2,7 +2,6 @@ This module provides the circuit implementation for Quantum Fourier Transform. """ - import numpy as np from ..operations import Gate, snot, cphase, swap, expand_operator from ..circuit import QubitCircuit diff --git a/src/qutip_qip/circuit/__init__.py b/src/qutip_qip/circuit/__init__.py index ff861f3e..ba84ccf0 100644 --- a/src/qutip_qip/circuit/__init__.py +++ b/src/qutip_qip/circuit/__init__.py @@ -1,4 +1,5 @@ """Circuit representation and simulation at the gate level.""" + from .circuit import * from .circuit_latex import * from .circuitsimulator import * diff --git a/src/qutip_qip/circuit/_decompose.py b/src/qutip_qip/circuit/_decompose.py index 28b05767..324d1b31 100644 --- a/src/qutip_qip/circuit/_decompose.py +++ b/src/qutip_qip/circuit/_decompose.py @@ -3,6 +3,7 @@ Those decomposition functions should be moved to the individual gate classes. """ + import numpy as np from ..operations import Gate diff --git a/src/qutip_qip/circuit/circuit.py b/src/qutip_qip/circuit/circuit.py index e619f16c..d74ad665 100644 --- a/src/qutip_qip/circuit/circuit.py +++ b/src/qutip_qip/circuit/circuit.py @@ -1,6 +1,7 @@ """ Quantum circuit representation and simulation. """ + from collections.abc import Iterable from itertools import product import inspect diff --git a/src/qutip_qip/compat.py b/src/qutip_qip/compat.py index 79632a7f..13858c10 100644 --- a/src/qutip_qip/compat.py +++ b/src/qutip_qip/compat.py @@ -1,6 +1,7 @@ """ For the compatibility between qutip-v5 and v4. """ + from itertools import chain from functools import reduce from packaging.version import parse as parse_version diff --git a/src/qutip_qip/compiler/__init__.py b/src/qutip_qip/compiler/__init__.py index 2c2c8768..b90b0a29 100644 --- a/src/qutip_qip/compiler/__init__.py +++ b/src/qutip_qip/compiler/__init__.py @@ -1,4 +1,5 @@ """Compilers for the hardware models in :obj:`device`""" + from .instruction import Instruction from .scheduler import Scheduler from .gatecompiler import GateCompiler diff --git a/src/qutip_qip/decompose/__init__.py b/src/qutip_qip/decompose/__init__.py index a497a5f5..0f4e4035 100644 --- a/src/qutip_qip/decompose/__init__.py +++ b/src/qutip_qip/decompose/__init__.py @@ -1,2 +1,3 @@ """Unitary decomposition. (experimental)""" + from .decompose_single_qubit_gate import * diff --git a/src/qutip_qip/device/__init__.py b/src/qutip_qip/device/__init__.py index 80c6f312..468315c7 100644 --- a/src/qutip_qip/device/__init__.py +++ b/src/qutip_qip/device/__init__.py @@ -1,6 +1,7 @@ """ Simulation of quantum hardware. """ + from .processor import Processor, Model from .modelprocessor import ModelProcessor from .spinchain import ( diff --git a/src/qutip_qip/noise.py b/src/qutip_qip/noise.py index 70036b31..94e61c5b 100644 --- a/src/qutip_qip/noise.py +++ b/src/qutip_qip/noise.py @@ -1,4 +1,5 @@ """Noise of quantum hardware.""" + import numbers import warnings from collections.abc import Iterable diff --git a/src/qutip_qip/operations/__init__.py b/src/qutip_qip/operations/__init__.py index 129aafa5..cf82660b 100644 --- a/src/qutip_qip/operations/__init__.py +++ b/src/qutip_qip/operations/__init__.py @@ -1,6 +1,7 @@ """ Operations on quantum circuits. """ + from .gates import * from .gateclass import * from .measurement import * diff --git a/src/qutip_qip/pulse.py b/src/qutip_qip/pulse.py index e9e9ea7e..675593cd 100644 --- a/src/qutip_qip/pulse.py +++ b/src/qutip_qip/pulse.py @@ -1,4 +1,5 @@ """Pulse representation of a quantum circuit.""" + from packaging.version import parse as parse_version import numpy as np from scipy.interpolate import CubicSpline diff --git a/src/qutip_qip/qasm.py b/src/qutip_qip/qasm.py index 7b2fecc4..a2260b9e 100644 --- a/src/qutip_qip/qasm.py +++ b/src/qutip_qip/qasm.py @@ -1,4 +1,5 @@ """Importation and exportation of QASM circuits""" + import re import os from itertools import chain @@ -505,9 +506,9 @@ def _regs_processor(self, regs, reg_type): return zip( *list( map( - lambda x: x - if isinstance(x, list) - else [x] * expand, + lambda x: ( + x if isinstance(x, list) else [x] * expand + ), new_regs, ) ) diff --git a/src/qutip_qip/qir.py b/src/qutip_qip/qir.py index cfd4eb67..f9d8bcfd 100644 --- a/src/qutip_qip/qir.py +++ b/src/qutip_qip/qir.py @@ -66,8 +66,7 @@ def circuit_to_qir( circuit: QubitCircuit, format: Union[Literal[QirFormat.BITCODE], Literal["bitcode"]], module_name: str, -) -> bytes: - ... +) -> bytes: ... @overload @@ -75,8 +74,7 @@ def circuit_to_qir( circuit: QubitCircuit, format: Union[Literal[QirFormat.TEXT], Literal["text"]], module_name: str, -) -> str: - ... +) -> str: ... @overload @@ -84,8 +82,7 @@ def circuit_to_qir( circuit: QubitCircuit, format: Union[Literal[QirFormat.MODULE], Literal["module"]], module_name: str, -) -> pqp.QirModule: - ... +) -> pqp.QirModule: ... def circuit_to_qir(circuit, format, module_name="qutip_circuit"): @@ -131,9 +128,12 @@ def append_operation( if isinstance( op_with_less_controls.classical_control_value, int ) - else (op_with_less_controls.classical_control_value[1:]) - if op_with_less_controls.classical_control_value is not None - else None + else ( + (op_with_less_controls.classical_control_value[1:]) + if op_with_less_controls.classical_control_value + is not None + else None + ) ) branch_body = { value: ( diff --git a/src/qutip_qip/qiskit/backend.py b/src/qutip_qip/qiskit/backend.py index 601e8416..543d6a0e 100644 --- a/src/qutip_qip/qiskit/backend.py +++ b/src/qutip_qip/qiskit/backend.py @@ -71,12 +71,16 @@ def run(self, qiskit_circuit: QuantumCircuit, **run_options) -> Job: """ # configure the options self.set_options( - shots=run_options["shots"] - if "shots" in run_options - else self._default_options().shots, - allow_custom_gate=run_options["allow_custom_gate"] - if "allow_custom_gate" in run_options - else self._default_options().allow_custom_gate, + shots=( + run_options["shots"] + if "shots" in run_options + else self._default_options().shots + ), + allow_custom_gate=( + run_options["allow_custom_gate"] + if "allow_custom_gate" in run_options + else self._default_options().allow_custom_gate + ), ) qutip_circ = convert_qiskit_circuit( qiskit_circuit, @@ -212,9 +216,11 @@ def convert_to_hex(count): header = QobjExperimentHeader.from_dict( { - "name": qutip_circuit.name - if hasattr(qutip_circuit, "name") - else "", + "name": ( + qutip_circuit.name + if hasattr(qutip_circuit, "name") + else "" + ), "n_qubits": qutip_circuit.N, } ) @@ -363,16 +369,20 @@ def _parse_results( exp_res_data = ExperimentResultData( counts=counts, - statevector=Statevector(data=final_state.full()) - if final_state.type == "ket" - else DensityMatrix(data=final_state.full()), + statevector=( + Statevector(data=final_state.full()) + if final_state.type == "ket" + else DensityMatrix(data=final_state.full()) + ), ) header = QobjExperimentHeader.from_dict( { - "name": qutip_circuit.name - if hasattr(qutip_circuit, "name") - else "", + "name": ( + qutip_circuit.name + if hasattr(qutip_circuit, "name") + else "" + ), "n_qubits": qutip_circuit.N, } ) diff --git a/src/qutip_qip/qiskit/converter.py b/src/qutip_qip/qiskit/converter.py index 35b20b04..12075eaf 100644 --- a/src/qutip_qip/qiskit/converter.py +++ b/src/qutip_qip/qiskit/converter.py @@ -182,9 +182,9 @@ def convert_qiskit_circuit( ) unitary = np.array(Operator(qiskit_instruction)) - qutip_circuit.user_gates[ - qiskit_instruction.name - ] = _make_user_gate(unitary, qiskit_instruction) + qutip_circuit.user_gates[qiskit_instruction.name] = ( + _make_user_gate(unitary, qiskit_instruction) + ) qutip_circuit.add_gate( qiskit_instruction.name, targets=_get_mapped_bits(qiskit_qregs, bit_map=qubit_map), diff --git a/src/qutip_qip/qubits.py b/src/qutip_qip/qubits.py index e6fac176..2e19ada2 100644 --- a/src/qutip_qip/qubits.py +++ b/src/qutip_qip/qubits.py @@ -1,4 +1,5 @@ """Generating qubit states.""" + __all__ = ["qubit_states"] from qutip import tensor, basis diff --git a/src/qutip_qip/vqa.py b/src/qutip_qip/vqa.py index 156750f1..a993862c 100644 --- a/src/qutip_qip/vqa.py +++ b/src/qutip_qip/vqa.py @@ -1,4 +1,5 @@ """Variational Quantum Algorithms generation and optimization""" + import types import random import numpy as np diff --git a/tests/test_compiler.py b/tests/test_compiler.py index 4652c76f..a1789731 100644 --- a/tests/test_compiler.py +++ b/tests/test_compiler.py @@ -208,5 +208,5 @@ def test_pulse_shape(shape): coeff, tlist = GateCompiler.generate_pulse_shape( shape, 1001, maximum=1.0, area=1.0) assert pytest.approx(coeff[500], 1.e-2) == 1 # max - result = integrate.trapz(coeff, tlist) + result = integrate.trapezoid(coeff, tlist) assert pytest.approx(result, rel=1.e-2) == 1 # area From df46003d7dcd68f98227efe2489fcb884e6ba50f Mon Sep 17 00:00:00 2001 From: Boxi Li Date: Thu, 22 Feb 2024 16:32:23 +0100 Subject: [PATCH 05/11] Remove unnecessary use of circuit.propagatos() (#226) `QubitCircuit.compute_unitary()` is faster and saves memory. --- src/qutip_qip/qasm.py | 2 +- tests/test_circuit.py | 16 ++++++++-------- tests/test_device.py | 8 ++++---- tests/test_optpulseprocessor.py | 2 +- tests/test_scheduler.py | 10 ++-------- 5 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/qutip_qip/qasm.py b/src/qutip_qip/qasm.py index a2260b9e..97a6d6a1 100644 --- a/src/qutip_qip/qasm.py +++ b/src/qutip_qip/qasm.py @@ -774,7 +774,7 @@ def _gate_add( self._custom_gate( qc_temp, [command[0], args, [str(i) for i in range(n)]] ) - unitary_mat = gate_sequence_product(qc_temp.propagators()) + unitary_mat = qc_temp.compute_unitary() custom_gates[gate_name] = unitary_mat qc.user_gates = custom_gates diff --git a/tests/test_circuit.py b/tests/test_circuit.py index 346c2878..98032bb2 100644 --- a/tests/test_circuit.py +++ b/tests/test_circuit.py @@ -102,9 +102,9 @@ class TestQubitCircuit: def testresolve(self, gate_from, gate_to, targets, controls): qc1 = QubitCircuit(2) qc1.add_gate(gate_from, targets=targets, controls=controls) - U1 = gate_sequence_product(qc1.propagators()) + U1 = qc1.compute_unitary() qc2 = qc1.resolve_gates(basis=gate_to) - U2 = gate_sequence_product(qc2.propagators()) + U2 = qc2.compute_unitary() assert _op_dist(U1, U2) < 1e-12 def testSNOTdecompose(self): @@ -114,9 +114,9 @@ def testSNOTdecompose(self): """ qc1 = QubitCircuit(1) qc1.add_gate("SNOT", targets=0) - U1 = gate_sequence_product(qc1.propagators()) + U1 = qc1.compute_unitary() qc2 = qc1.resolve_gates() - U2 = gate_sequence_product(qc2.propagators()) + U2 = qc2.compute_unitary() assert _op_dist(U1, U2) < 1e-12 def testFREDKINdecompose(self): @@ -126,9 +126,9 @@ def testFREDKINdecompose(self): """ qc1 = QubitCircuit(3) qc1.add_gate("FREDKIN", targets=[0, 1], controls=[2]) - U1 = gate_sequence_product(qc1.propagators()) + U1 = qc1.compute_unitary() qc2 = qc1.resolve_gates() - U2 = gate_sequence_product(qc2.propagators()) + U2 = qc2.compute_unitary() assert _op_dist(U1, U2) < 1e-12 def testadjacentgates(self): @@ -138,10 +138,10 @@ def testadjacentgates(self): """ qc1 = QubitCircuit(3) qc1.add_gate("ISWAP", targets=[0, 2]) - U1 = gate_sequence_product(qc1.propagators()) + U1 = qc1.compute_unitary() qc0 = qc1.adjacent_gates() qc2 = qc0.resolve_gates(basis="ISWAP") - U2 = gate_sequence_product(qc2.propagators()) + U2 = qc2.compute_unitary() assert _op_dist(U1, U2) < 1e-12 def test_add_gate(self): diff --git a/tests/test_device.py b/tests/test_device.py index 2c734a54..6170f2eb 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -76,7 +76,7 @@ def test_device_against_gate_sequence( circuit = QubitCircuit(num_qubits) for gate in gates: circuit.add_gate(gate) - U_ideal = gate_sequence_product(circuit.propagators()) + U_ideal = circuit.compute_unitary() device = device_class(num_qubits) U_physical = gate_sequence_product(device.run(circuit)) @@ -91,7 +91,7 @@ def test_analytical_evolution(num_qubits, gates, device_class, kwargs): circuit.add_gate(gate) state = qutip.rand_ket(2**num_qubits) state.dims = [[2]*num_qubits, [1]*num_qubits] - ideal = gate_sequence_product([state] + circuit.propagators()) + ideal = circuit.run(state) device = device_class(num_qubits) operators = device.run_state(init_state=state, qc=circuit, analytical=True) result = gate_sequence_product(operators) @@ -112,7 +112,7 @@ def test_numerical_evolution( state = qutip.rand_ket(2**num_qubits) state.dims = [[2]*num_qubits, [1]*num_qubits] - target = gate_sequence_product([state] + circuit.propagators()) + target = circuit.run(state) if isinstance(device, DispersiveCavityQED): num_ancilla = len(device.dims)-num_qubits ancilla_indices = slice(0, num_ancilla) @@ -169,7 +169,7 @@ def test_numerical_circuit(circuit, device_class, kwargs, schedule_mode): state = qutip.rand_ket(2**num_qubits) state.dims = [[2]*num_qubits, [1]*num_qubits] - target = gate_sequence_product([state] + circuit.propagators()) + target = circuit.run(state) if isinstance(device, DispersiveCavityQED): num_ancilla = len(device.dims)-num_qubits ancilla_indices = slice(0, num_ancilla) diff --git a/tests/test_optpulseprocessor.py b/tests/test_optpulseprocessor.py index 828fbcfc..b5fc9dd9 100644 --- a/tests/test_optpulseprocessor.py +++ b/tests/test_optpulseprocessor.py @@ -102,7 +102,7 @@ def test_multi_gates(self): rho0 = rand_ket(4) # use random generated ket state rho0.dims = [[2, 2], [1, 1]] - U = gate_sequence_product(qc.propagators()) + U = qc.compute_unitary() rho1 = U * rho0 result = test.run_state(rho0) assert_(fidelity(result.states[-1], rho1) > 1-1.0e-6) diff --git a/tests/test_scheduler.py b/tests/test_scheduler.py index 58723528..38d1bf5e 100644 --- a/tests/test_scheduler.py +++ b/tests/test_scheduler.py @@ -13,12 +13,12 @@ def _verify_scheduled_circuit(circuit, gate_cycle_indices): The input gate_cycle_indices is the scheduling result, i.e., a list of integers denoting the execution cycle of each gate in the circuit. """ - result0 = gate_sequence_product(circuit.propagators()) + result0 = circuit.compute_unitary() scheduled_gate = [[] for i in range(max(gate_cycle_indices) + 1)] for i, cycles in enumerate(gate_cycle_indices): scheduled_gate[cycles].append(circuit.gates[i]) circuit.gates = sum(scheduled_gate, []) - result1 = gate_sequence_product(circuit.propagators()) + result1 = circuit.compute_unitary() return tracedist(result0 * result1.dag(), qeye(result0.dims[0])) < 1.0e-7 @@ -27,7 +27,6 @@ def test_allow_permutation(): circuit.add_gate("X", 0) circuit.add_gate("CNOT", 0, 1) circuit.add_gate("X", 1) - result0 = gate_sequence_product(circuit.propagators()) scheduler = Scheduler("ASAP", allow_permutation=True) gate_cycle_indices = scheduler.schedule(circuit) @@ -138,7 +137,6 @@ def test_scheduling_gates1( repeat_num = 5 else: repeat_num = 0 - result0 = gate_sequence_product(circuit.propagators()) # run the scheduler scheduler = Scheduler(method) @@ -168,7 +166,6 @@ def test_scheduling_gates2( repeat_num = 5 else: repeat_num = 0 - result0 = gate_sequence_product(circuit.propagators()) # run the scheduler scheduler = Scheduler(method) @@ -200,7 +197,6 @@ def test_scheduling_gates3( repeat_num = 5 else: repeat_num = 0 - result0 = gate_sequence_product(circuit.propagators()) # run the scheduler scheduler = Scheduler(method) @@ -232,7 +228,6 @@ def test_scheduling_gates4( repeat_num = 5 else: repeat_num = 0 - result0 = gate_sequence_product(circuit.propagators()) # run the scheduler scheduler = Scheduler(method) @@ -278,7 +273,6 @@ def test_scheduling_pulse( repeat_num = 5 else: repeat_num = 0 - result0 = gate_sequence_product(circuit.propagators()) # run the scheduler scheduler = Scheduler(method) From b0b017aa1d774e894dd538c6b168bca319caea2f Mon Sep 17 00:00:00 2001 From: Boxi Li Date: Wed, 3 Apr 2024 07:57:31 +0200 Subject: [PATCH 06/11] Merge pull request #231 from BoxiLi/drop-old-version Drop support of Python=3.7 and 3.8 --- .github/workflows/build.yml | 6 ++-- .github/workflows/build_documentation.yml | 5 ++-- .github/workflows/test.yml | 29 ++++++------------- README.md | 2 +- doc/source/conf.py | 2 +- doc/source/contribution-code.rst | 2 +- doc/source/installation.rst | 2 +- .../tutorials-website/tutorials.html.jinja | 2 +- setup.cfg | 2 ++ 9 files changed, 22 insertions(+), 30 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b193f463..4f47f623 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -54,7 +54,7 @@ jobs: # For the sdist we should be as conservative as possible with our # Python version. This should be the lowest supported version. This # means that no unsupported syntax can sneak through. - python-version: '3.7' + python-version: '3.11' - name: Install pip build run: | @@ -89,7 +89,7 @@ jobs: name: Install Python with: # This is about the build environment, not the released wheel version. - python-version: '3.7' + python-version: '3.11' - name: Install pip build run: | @@ -129,7 +129,7 @@ jobs: - uses: actions/setup-python@v4 name: Install Python with: - python-version: '3.7' + python-version: '3.11' - name: Verify this is not a dev version shell: bash diff --git a/.github/workflows/build_documentation.yml b/.github/workflows/build_documentation.yml index d41d71ff..63da4050 100644 --- a/.github/workflows/build_documentation.yml +++ b/.github/workflows/build_documentation.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/setup-python@v4 name: Install Python with: - python-version: '3.8' + python-version: '3.9' - name: Install Pandoc run: | @@ -27,7 +27,8 @@ jobs: - name: Install qutip-qip from GitHub run: | - python -mpip install -e .[full] + python -mpip install qutip==4.7.3 + python -mpip install -e . # Install in editable mode so it doesn't matter if we import from # inside the installation directory, otherwise we can get some errors # because we're importing from the wrong location. diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3f8fe1c4..4b03b3b0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,11 +12,11 @@ jobs: strategy: matrix: include: - - case-name: qutip@5.0.0a2 + - case-name: qutip@5.0.0 os: ubuntu-latest - qutip-version: '==5.0.0a2' + qutip-version: '==5.0.0' pyqir-version: '' - python-version: '3.10' + python-version: '3.11' - case-name: qutip@qutip-4.7.X os: ubuntu-latest qutip-version: '@qutip-4.7.X' @@ -26,25 +26,19 @@ jobs: os: ubuntu-latest qutip-version: '@master' pyqir-version: '' - python-version: '3.11' - - case-name: qutip@4.6 - os: windows-latest - qutip-version: '==4.6.*' - qiskit-version: '' - pyqir-version: '' - python-version: '3.8' + python-version: '3.12' - case-name: qutip@4.7 os: macOS-latest qutip-version: '==4.7.*' qiskit-version: '' pyqir-version: '' - python-version: '3.9' + python-version: '3.11' - case-name: qiskit+qir os: windows-latest qutip-version: '' qiskit-version: '==0.36.*' pyqir-version: '==0.6.2' - python-version: '3.8' + python-version: '3.9' - case-name: qiskit+qir os: macOS-latest qutip-version: '' @@ -56,7 +50,7 @@ jobs: qutip-version: '' qiskit-version: '==0.36.*' pyqir-version: '==0.6.2' - python-version: '3.7' + python-version: '3.9' steps: - uses: actions/checkout@v3 @@ -75,12 +69,6 @@ jobs: python -m pip install numpy scipy cython python -m pip install 'git+https://github.com/qutip/qutip.git${{ matrix.qutip-version }}' - # For qutip-v5 qutip.control is replaced by qutip-qtrl - - name: Install qutip-qtrl from PyPI - if: ${{ matrix.qutip-version == '@master' || startsWith( matrix.qutip-version, '==5') }} - run: | - python -m pip install 'git+https://github.com/qutip/qutip-qtrl.git' - - name: Install Qiskit from PyPI if: ${{ matrix.qiskit-version != '' }} run: python -m pip install 'qiskit${{ matrix.qiskit-version }}' @@ -112,11 +100,12 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: 3.8 + python-version: 3.9 - name: Install dependencies run: | python -m pip install --upgrade pip python -mpip install -r doc/requirements.txt + pip install qutip==4.7.3 pip install . - name: Test code snippets in the documentation run: | diff --git a/README.md b/README.md index cb6b8e59..727840b5 100755 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ The qutip-qip package, QuTiP quantum information processing, aims at providing b Compared to other libraries for quantum information processing, qutip-qip puts additional emphasis on the physics layer and the interaction with the QuTiP package. The package offers two different approaches for simulating quantum circuits, one with `QubitCircuit` calculating unitary evolution under quantum gates by matrix product, another called `Processor` using open system solvers in QuTiP to simulate noisy quantum device. -If you would like to know the future development plan and ideas, have a look at the [discussion panel](https://github.com/qutip/qutip-qip/discussions) as well as the [qutip documentation for ideas](https://qutip.org/docs/latest/development/ideas.html). +If you would like to know the future development plan and ideas, have a look at the [discussion panel](https://github.com/qutip/qutip-qip/discussions) as well as the [qutip documentation for ideas](https://qutip.readthedocs.io/en/stable/development/ideas.html). Quick start ----------- diff --git a/doc/source/conf.py b/doc/source/conf.py index b3137362..0c14f79e 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -185,5 +185,5 @@ def qutip_qip_version(): # -- Options for intersphinx --------------------------------------- intersphinx_mapping = { - 'qutip': ('https://qutip.org/docs/latest/', None), + 'qutip': ('https://qutip.readthedocs.io/en/stable/', None), } \ No newline at end of file diff --git a/doc/source/contribution-code.rst b/doc/source/contribution-code.rst index ed266c98..fb464137 100644 --- a/doc/source/contribution-code.rst +++ b/doc/source/contribution-code.rst @@ -7,7 +7,7 @@ Contributing to the source code Build up an development environment =================================== -Please follow the instruction on the `QuTiP contribution guide `_ to +Please follow the instruction on the `QuTiP contribution guide `_ to build a conda environment. You don't need to build ``qutip`` in the editable mode unless you also want to contribute to `qutip`. Instead, you need to install ``qutip-qip`` by downloading the source code and run diff --git a/doc/source/installation.rst b/doc/source/installation.rst index 9e073b2a..d4239615 100644 --- a/doc/source/installation.rst +++ b/doc/source/installation.rst @@ -24,7 +24,7 @@ Prerequisites This package is built upon QuTiP, of which the installation guide can be found at on `QuTiP Installation `_. The only difference is that C++ compilers are not required here since there is no run-time compiling for qutip-qip. -The minimal Python version supported is Python 3.7. +The minimal Python version supported is Python 3.9. In particular, following packages are necessary for running qutip-qip diff --git a/doc/source/tutorials-website/tutorials.html.jinja b/doc/source/tutorials-website/tutorials.html.jinja index 2fc559c8..19f17955 100644 --- a/doc/source/tutorials-website/tutorials.html.jinja +++ b/doc/source/tutorials-website/tutorials.html.jinja @@ -5,7 +5,7 @@

Quantum information processing

-

This section contains tutorials for QuTiP Version 5. You can find the tutorials for QuTiP Version 4 here. To check the version of QuTiP, you can use the qutip.about() command.

+

This section contains tutorials for QuTiP Version 5. You can find the tutorials for QuTiP Version 4 here. To check the version of QuTiP, you can use the qutip.about() command.

Quantum circuits and algorithms
    diff --git a/setup.cfg b/setup.cfg index b974d029..976b26ca 100644 --- a/setup.cfg +++ b/setup.cfg @@ -40,10 +40,12 @@ where = src [options.extras_require] graphics = matplotlib>=1.3.0 +control = qutip-qtrl tests = pytest>=5.2 full = %(graphics)s %(tests)s + %(control)s qiskit = qiskit>=0.36.2 From 9a11835493cfc6fa5c5c85f0b6c018363c6f9764 Mon Sep 17 00:00:00 2001 From: Boxi Li Date: Wed, 3 Apr 2024 08:13:15 +0200 Subject: [PATCH 07/11] Update qiskit support version to 0.46 (#232) --- .github/workflows/test.yml | 8 ++++---- doc/requirements.txt | 3 ++- setup.cfg | 3 ++- tests/pytest.ini | 2 ++ tests/test_qiskit.py | 26 +++++++++++--------------- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4b03b3b0..42b1e2c3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -36,19 +36,19 @@ jobs: - case-name: qiskit+qir os: windows-latest qutip-version: '' - qiskit-version: '==0.36.*' + qiskit-version: '==0.46.*' pyqir-version: '==0.6.2' python-version: '3.9' - case-name: qiskit+qir os: macOS-latest qutip-version: '' - qiskit-version: '==0.36.*' + qiskit-version: '==0.46.*' pyqir-version: '==0.6.2' python-version: '3.9' - case-name: qiskit+qir os: ubuntu-latest qutip-version: '' - qiskit-version: '==0.36.*' + qiskit-version: '==0.46.*' pyqir-version: '==0.6.2' python-version: '3.9' @@ -71,7 +71,7 @@ jobs: - name: Install Qiskit from PyPI if: ${{ matrix.qiskit-version != '' }} - run: python -m pip install 'qiskit${{ matrix.qiskit-version }}' + run: python -m pip install 'qiskit${{ matrix.qiskit-version }}' 'qiskit-aer==0.14.0.1' - name: Install PyQIR from PyPI if: ${{ matrix.pyqir-version != '' }} diff --git a/doc/requirements.txt b/doc/requirements.txt index d2491457..e81a103c 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -11,4 +11,5 @@ docutils==0.17.1 sphinxcontrib-bibtex==2.4.2 pyqir-generator==0.6.2 pyqir-parser==0.6.2 -qiskit==0.37.2 \ No newline at end of file +qiskit==0.46.1 +qiskit-aer==0.14.0.1 diff --git a/setup.cfg b/setup.cfg index 976b26ca..a05259f6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -48,4 +48,5 @@ full = %(tests)s %(control)s qiskit = - qiskit>=0.36.2 + qiskit<1.0.0 + qiskit_aer diff --git a/tests/pytest.ini b/tests/pytest.ini index 97c5f517..fdd79f63 100644 --- a/tests/pytest.ini +++ b/tests/pytest.ini @@ -12,3 +12,5 @@ filterwarnings = ignore:matplotlib not found:UserWarning ignore:the imp module is deprecated in favour of importlib:DeprecationWarning ignore:Dedicated options class are no longer needed, options should be passed as dict to solvers.:FutureWarning + ignore::DeprecationWarning:qiskit.utils.algorithm_globals: + diff --git a/tests/test_qiskit.py b/tests/test_qiskit.py index a1aeb2c9..c2168021 100644 --- a/tests/test_qiskit.py +++ b/tests/test_qiskit.py @@ -9,25 +9,21 @@ DispersiveCavityQED, ) -try: - from qiskit import QuantumCircuit - from qiskit.providers.aer import AerSimulator - from qutip_qip.qiskit.provider import ( - QiskitCircuitSimulator, - QiskitPulseSimulator, - ) - from qutip_qip.qiskit.converter import ( - convert_qiskit_circuit, - _get_qutip_index, - ) -except ImportError: - pass - - # will skip tests in this entire file # if qiskit is not installed pytest.importorskip("qiskit") +from qiskit import QuantumCircuit +from qiskit_aer import AerSimulator +from qutip_qip.qiskit.provider import ( + QiskitCircuitSimulator, + QiskitPulseSimulator, +) +from qutip_qip.qiskit.converter import ( + convert_qiskit_circuit, + _get_qutip_index, +) + class TestConverter: """ From 9fa3d64127105f9da34ad200c57538f4dfb26025 Mon Sep 17 00:00:00 2001 From: Boxi Li Date: Wed, 3 Apr 2024 08:29:48 +0200 Subject: [PATCH 08/11] Merge pull request #227 from BoxiLi/expand_operator_csr Use CSR as default for expand_operator --- src/qutip_qip/operations/gates.py | 12 ++++++++++-- tests/test_gates.py | 13 +++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/qutip_qip/operations/gates.py b/src/qutip_qip/operations/gates.py index e080971c..7a294f2f 100644 --- a/src/qutip_qip/operations/gates.py +++ b/src/qutip_qip/operations/gates.py @@ -1,4 +1,5 @@ import numbers +from packaging.version import parse as parse_version from collections.abc import Iterable from itertools import product from functools import partial, reduce @@ -1207,7 +1208,7 @@ def _targets_to_list(targets, oper=None, N=None): def expand_operator( - oper, N=None, targets=None, dims=None, cyclic_permutation=False + oper, N=None, targets=None, dims=None, cyclic_permutation=False, dtype=None ): """ Expand an operator to one that acts on a system with desired dimensions. @@ -1225,13 +1226,16 @@ def expand_operator( The indices of subspace that are acted on. Permutation can also be realized by changing the orders of the indices. N : int - Deprecated. Number of qubits. Please use `dims`. + Deprecated. Number of qubits. Please use `dims`. cyclic_permutation : boolean, optional Deprecated. Expand for all cyclic permutation of the targets. E.g. if ``N=3`` and `oper` is a 2-qubit operator, the result will be a list of three operators, each acting on qubits 0 and 1, 1 and 2, 2 and 0. + dtype : str, optional + Data type of the output `Qobj`. Only for qutip version larger than 5. + Returns ------- @@ -1276,6 +1280,10 @@ def expand_operator( [0. 0. 0. 0. 0. 0. 1. 0.] [0. 0. 0. 1. 0. 0. 0. 0.]] """ + if parse_version(qutip.__version__) >= parse_version("5.dev"): + # If no data type specified, use CSR + dtype = dtype or qutip.settings.core["default_dtype"] or qutip.data.CSR + oper = oper.to(dtype) if N is not None: warnings.warn( "The function expand_operator has been generalized to " diff --git a/tests/test_gates.py b/tests/test_gates.py index 3cc8d120..49938105 100644 --- a/tests/test_gates.py +++ b/tests/test_gates.py @@ -338,6 +338,19 @@ def test_non_qubit_systems(self, dimensions): assert test.dims == expected.dims np.testing.assert_allclose(test.full(), expected.full()) + @pytest.mark.skipif( + not parse_version(qutip.__version__) >= parse_version('5.dev'), + reason= + "Data type only exist in v5." + ) + def test_dtype(self): + expanded_qobj = expand_operator(gates.cnot(), dims=[2, 2, 2]).data + assert isinstance(expanded_qobj, qutip.data.CSR) + expanded_qobj = expand_operator( + gates.cnot(), dims=[2, 2, 2], dtype="dense").data + assert isinstance(expanded_qobj, qutip.data.Dense) + + def test_gates_class(): if parse_version(qutip.__version__) < parse_version('5.dev'): init_state = qutip.rand_ket(8, dims=[[2, 2, 2], [1, 1, 1]]) From eede9af6a728ab65461c18f8d3729ec438a23258 Mon Sep 17 00:00:00 2001 From: Boxi Li Date: Wed, 3 Apr 2024 09:28:59 +0200 Subject: [PATCH 09/11] Merge pull request #224 from BoxiLi/qasm Update the parsing mode for read_qasm --- doc/source/w-state.qasm | 1 - src/qutip_qip/qasm.py | 60 +++++++++++------ tests/qasm_files/bracket_error.qasm | 1 - tests/qasm_files/command_error.qasm | 1 - tests/qasm_files/qasm_error.qasm | 2 - tests/qasm_files/qft.qasm | 2 +- tests/qasm_files/teleportation.qasm | 1 - tests/qasm_files/test_add.qasm | 1 - tests/qasm_files/test_custom_gates.qasm | 1 - tests/qasm_files/w-state.qasm | 1 - tests/qasm_files/w-state_with_comments.qasm | 1 - tests/test_qasm.py | 73 ++++++++++++++++++++- 12 files changed, 113 insertions(+), 32 deletions(-) diff --git a/doc/source/w-state.qasm b/doc/source/w-state.qasm index 35a47e3f..2c27c93a 100644 --- a/doc/source/w-state.qasm +++ b/doc/source/w-state.qasm @@ -2,7 +2,6 @@ // Name of Experiment: W-state v1 OPENQASM 2.0; -include "qelib1.inc"; qreg q[3]; diff --git a/src/qutip_qip/qasm.py b/src/qutip_qip/qasm.py index 97a6d6a1..c736b13b 100644 --- a/src/qutip_qip/qasm.py +++ b/src/qutip_qip/qasm.py @@ -36,9 +36,8 @@ def __init__(self, name, gate_args, gate_regs): def _get_qiskit_gates(): """ - Create and return a dictionary containing custom gates needed - for "qiskit" mode. These include a subset of gates usually defined - in the file "qelib1.inc". + Create and return a dictionary containing a few commonly used qiskit gates + that are not predefined in qutip-qip. Returns a dictionary mapping gate names to QuTiP gates. """ @@ -180,17 +179,22 @@ class QasmProcessor: Class which holds variables used in processing QASM code. """ - def __init__(self, commands, mode="qiskit", version="2.0"): + def __init__(self, commands, mode="default", version="2.0"): self.qubit_regs = {} self.cbit_regs = {} self.num_qubits = 0 self.num_cbits = 0 - self.qasm_gates = {} + self.qasm_gates = {} # Custom defined QASM gates + if mode not in ["default", "external_only", "predefined_only"]: + warnings.warn( + "Unknown parsing mode, using the default mode instead." + ) + mode = "default" self.mode = mode self.version = version self.predefined_gates = set(["CX", "U"]) - if self.mode == "qiskit": + if self.mode != "external_only": self.qiskitgates = set( [ "u3", @@ -222,6 +226,8 @@ def __init__(self, commands, mode="qiskit", version="2.0"): self.qiskitgates ) + # A set of available gates, including both predefined gate and + # custom defined gates from `qelib1.inc` (added later). self.gate_names = deepcopy(self.predefined_gates) for gate in self.predefined_gates: self.qasm_gates[gate] = QasmGate( @@ -246,7 +252,11 @@ def _process_includes(self): filename = command[1].strip('"') - if self.mode == "qiskit" and filename == "qelib1.inc": + if self.mode == "predefined_only": + warnings.warn( + "Ignoring external gate definition" + " in the predefined_only mode." + ) continue if os.path.exists(filename): @@ -265,8 +275,14 @@ def _process_includes(self): ) prev_index = curr_index + 1 else: - raise ValueError(command[1] + ": such a file does not exist") + if self.mode == "default": + warnings.warn(command[1] + "not found, ignored.") + else: + raise ValueError( + command[1] + ": such a file does not exist" + ) + # Insert the custom gate configurations to the list of commands expanded_commands += self.commands[prev_index:] self.commands = expanded_commands @@ -276,8 +292,10 @@ def _initialize_pass(self): each user-defined gate, process register declarations. """ - gate_defn_mode = False - open_bracket_mode = False + gate_defn_mode = False # If in the middle of defining a custom gate + open_bracket_mode = ( + False # If in the middle of defining a decomposition + ) unprocessed = [] @@ -291,6 +309,7 @@ def _initialize_pass(self): else: raise SyntaxError("QASM: incorrect bracket formatting") elif open_bracket_mode: + # Define the decomposition of custom QASM gate if command[0] == "{": raise SyntaxError("QASM: incorrect bracket formatting") elif command[0] == "}": @@ -313,11 +332,11 @@ def _initialize_pass(self): gate_added = self.qasm_gates[name] curr_gate.gates_inside.append([name, gate_args, gate_regs]) elif command[0] == "gate": + # Custom definition of gates. gate_name = command[1] gate_args, gate_regs = _gate_processor(command[1:]) curr_gate = QasmGate(gate_name, gate_args, gate_regs) gate_defn_mode = True - elif command[0] == "qreg": groups = re.match(r"(.*)\[(.*)\]", "".join(command[1:])) if groups: @@ -648,7 +667,7 @@ def _add_qiskit_gates( classical_controls=classical_controls, classical_control_value=classical_control_value, ) - if name == "cx": + elif name == "cx": qc.add_gate( "CNOT", targets=int(regs[1]), @@ -716,7 +735,7 @@ def _add_predefined_gates( classical_controls=classical_controls, classical_control_value=classical_control_value, ) - elif name in self.qiskitgates and self.mode == "qiskit": + elif name in self.qiskitgates: self._add_qiskit_gates( qc, name, @@ -809,7 +828,7 @@ def _final_pass(self, qc): """ custom_gates = {} - if self.mode == "qiskit": + if self.mode != "external_only": custom_gates = _get_qiskit_gates() for command in self.commands: @@ -848,7 +867,7 @@ def _final_pass(self, qc): raise SyntaxError(err) -def read_qasm(qasm_input, mode="qiskit", version="2.0", strmode=False): +def read_qasm(qasm_input, mode="default", version="2.0", strmode=False): """ Read OpenQASM intermediate representation (https://github.com/Qiskit/openqasm) and return @@ -861,10 +880,13 @@ def read_qasm(qasm_input, mode="qiskit", version="2.0", strmode=False): File location or String Input for QASM file to be imported. In case of string input, the parameter strmode must be True. mode : str - QASM mode to be read in. When mode is "qiskit", - the "qelib1.inc" include is automatically included, - without checking externally. Otherwise, each include is - processed. + Parsing mode for the qasm file. + - "default": For predefined gates in qutip-qip, use the predefined + version, otherwise use the custom gate defined in qelib1.inc. + The predefined gate can usually be further processed (e.g. decomposed) + within qutip-qip. + - "predefined_only": Use only the predefined gates in qutip-qip. + - "external_only": Use only the gate defined in qelib1.inc, except for CX and QASMU gate. version : str QASM version of the QASM file. Only version 2.0 is currently supported. strmode : bool diff --git a/tests/qasm_files/bracket_error.qasm b/tests/qasm_files/bracket_error.qasm index 1cc9d1e9..9a5a531f 100644 --- a/tests/qasm_files/bracket_error.qasm +++ b/tests/qasm_files/bracket_error.qasm @@ -1,5 +1,4 @@ OPENQASM 2.0; -include "qelib1.inc"; qreg q[2]; creg c[4]; diff --git a/tests/qasm_files/command_error.qasm b/tests/qasm_files/command_error.qasm index 4efa7a43..e6afafe0 100644 --- a/tests/qasm_files/command_error.qasm +++ b/tests/qasm_files/command_error.qasm @@ -1,6 +1,5 @@ // Implementation of Deutsch algorithm with two qubits for f(x)=x OPENQASM 2.0; -include "qelib1.inc"; qreg q[5]; creg c[5]; diff --git a/tests/qasm_files/qasm_error.qasm b/tests/qasm_files/qasm_error.qasm index 2febf61e..858f9b43 100644 --- a/tests/qasm_files/qasm_error.qasm +++ b/tests/qasm_files/qasm_error.qasm @@ -1,5 +1,3 @@ -include "qelib1.inc"; - qreg q[2]; creg c[4]; gate cu1fixed (a) c,t { diff --git a/tests/qasm_files/qft.qasm b/tests/qasm_files/qft.qasm index f33314c3..c92a11ee 100644 --- a/tests/qasm_files/qft.qasm +++ b/tests/qasm_files/qft.qasm @@ -1,5 +1,5 @@ OPENQASM 2.0; -include "qelib1.inc"; + qreg q[4]; x q[0]; x q[2]; diff --git a/tests/qasm_files/teleportation.qasm b/tests/qasm_files/teleportation.qasm index 331e3bcf..2c4751c0 100644 --- a/tests/qasm_files/teleportation.qasm +++ b/tests/qasm_files/teleportation.qasm @@ -1,5 +1,4 @@ OPENQASM 2.0; -include "qelib1.inc"; qreg q[3]; creg c0[1]; diff --git a/tests/qasm_files/test_add.qasm b/tests/qasm_files/test_add.qasm index 2df3c9b7..2f90090d 100644 --- a/tests/qasm_files/test_add.qasm +++ b/tests/qasm_files/test_add.qasm @@ -1,5 +1,4 @@ OPENQASM 2.0; -include "qelib1.inc"; qreg q[2]; creg c[2]; diff --git a/tests/qasm_files/test_custom_gates.qasm b/tests/qasm_files/test_custom_gates.qasm index fe15abba..07d655d7 100644 --- a/tests/qasm_files/test_custom_gates.qasm +++ b/tests/qasm_files/test_custom_gates.qasm @@ -1,5 +1,4 @@ OPENQASM 2.0; -include "qelib1.inc"; qreg q[2]; diff --git a/tests/qasm_files/w-state.qasm b/tests/qasm_files/w-state.qasm index dc7542f1..71e57899 100644 --- a/tests/qasm_files/w-state.qasm +++ b/tests/qasm_files/w-state.qasm @@ -2,7 +2,6 @@ // Name of Experiment: W-state v1 OPENQASM 2.0; -include "qelib1.inc"; qreg q[4]; diff --git a/tests/qasm_files/w-state_with_comments.qasm b/tests/qasm_files/w-state_with_comments.qasm index c05d1885..f726c9be 100644 --- a/tests/qasm_files/w-state_with_comments.qasm +++ b/tests/qasm_files/w-state_with_comments.qasm @@ -2,7 +2,6 @@ // Name of Experiment: W-state v1 OPENQASM 2.0; -include "qelib1.inc"; qreg q[4]; // This comments also should be handled. diff --git a/tests/test_qasm.py b/tests/test_qasm.py index 95434c8a..c469b181 100644 --- a/tests/test_qasm.py +++ b/tests/test_qasm.py @@ -1,11 +1,13 @@ import pytest import numpy as np from pathlib import Path +import warnings +import qutip from qutip_qip.qasm import read_qasm, circuit_to_qasm_str from qutip_qip.circuit import QubitCircuit from qutip import tensor, rand_ket, basis, rand_dm, identity -from qutip_qip.operations import cnot, ry, Measurement +from qutip_qip.operations import cnot, ry, Measurement, swap @pytest.mark.parametrize(["filename", "error", "error_message"], [ @@ -127,7 +129,11 @@ def test_export_import(): qc.add_gate("T", targets=[0]) # qc.add_gate("CSIGN", targets=[0], controls=[1]) - read_qc = read_qasm(circuit_to_qasm_str(qc), strmode=True) + # The generated code by default has a inclusion statement of + # qelib1.inc, which will trigger a warning when read. + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + read_qc = read_qasm(circuit_to_qasm_str(qc), strmode=True) props = qc.propagators() read_props = read_qc.propagators() @@ -145,3 +151,66 @@ def test_read_qasm(): qc = read_qasm(filepath) qc2 = read_qasm(filepath2) assert True + + +def test_parsing_mode(tmp_path): + mode = "qiskit" + qasm_input_string = ( + 'OPENQASM 2.0;\n\ncreg c[2];' + '\nqreg q[2];cx q[0],q[1];\n' + ) + with pytest.warns(UserWarning) as record_warning: + read_qasm( + qasm_input_string, + mode=mode, + strmode=True, + ) + assert "Unknown parsing mode" in record_warning[0].message.args[0] + + mode = "predefined_only" + qasm_input_string = ( + 'OPENQASM 2.0;\ninclude "qelib1.inc"\n\ncreg c[2];' + '\nqreg q[2];swap q[0],q[1];\n' + ) + with pytest.raises(SyntaxError): + with pytest.warns(UserWarning) as record_warning: + circuit = read_qasm( + qasm_input_string, + mode=mode, + strmode=True, + ) + assert ( + "Ignoring external gate definition in the predefined_only mode." + in record_warning[0].message.args[0] + ) + + mode = "external_only" + file_path = tmp_path / "custom_swap.inc" + file_path.write_text( + "gate cx c,t { CX c,t; }\n" + "gate swap a,b { cx a,b; cx b,a; cx a,b; }\n" + ) + qasm_input_string = ( + 'OPENQASM 2.0;\ninclude "' + + str(file_path) + + '"\ncreg c[2];' + '\nqreg q[2];swap q[0],q[1];\n' + ) + circuit = read_qasm( + qasm_input_string, + mode=mode, + strmode=True, + ) + propagator = circuit.compute_unitary() + + fidelity = qutip.average_gate_fidelity(propagator, swap()) + pytest.approx(fidelity, 1.0) + + circuit = read_qasm( + qasm_input_string, + strmode=True, + ) + propagator = circuit.compute_unitary() + + fidelity = qutip.average_gate_fidelity(propagator, swap()) + pytest.approx(fidelity, 1.0) From f0247d1dabbb1f112d3c16732019ccbf69679967 Mon Sep 17 00:00:00 2001 From: Boxi Li Date: Sat, 20 Apr 2024 15:17:30 +0200 Subject: [PATCH 10/11] Update Changelog --- doc/source/changelog.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/doc/source/changelog.rst b/doc/source/changelog.rst index 05a00ee4..c780e6d5 100644 --- a/doc/source/changelog.rst +++ b/doc/source/changelog.rst @@ -3,6 +3,23 @@ Changelog ********* +Version 0.3.1 (April 21, 2024) +++++++++++++++++++++++++++++++ + +Improvements +------------ +- Update qiskit support version to ``0.46.*`` (`#232 `_) + +Bug Fixes +--------- +- Remove comments after meaningful statements in QASM reader. (`#218 `_ by NoriyukiK-1qbit) +- Fix circuit plot error for gates with classical controls when ``reverse_states=False``. (`#221 `_ by Kevin P. O'Brien) +- Update the RTD configuration file to execute ``create_tutorials_html.py`` (`#222 `_, reported by Ramyashri Padmanabhakumar) +- Remove the unnecessary use of circuit.propagatos (`#226 `_) +- Use CSR as default for ``expand_operator`` (`#227 `_) +- Update the parsing mode for ``read_qasm`` (`#224 `_ reported by kevinab107) + + Version 0.3.0 (July 19, 2023) +++++++++++++++++++++++++++++ From 63b0f29d719303bf5a564338cf54d39d777e6605 Mon Sep 17 00:00:00 2001 From: Boxi Li Date: Sat, 20 Apr 2024 15:17:35 +0200 Subject: [PATCH 11/11] Update version --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 0d91a54c..9e11b32f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.0 +0.3.1