Repository: qutip/qutip-qip (language: python)

Issue:
Update the parsing mode for read_qasm
Remove the ambiguous mode "qiskit" and add "default", "predefine_only" and "external_only".

Fix #208 


Discussion / hints:

Fix patch:
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/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/.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/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/VERSION b/VERSION
index 0d91a54c..9e11b32f 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-0.3.0
+0.3.1
diff --git a/doc/requirements.txt b/doc/requirements.txt
index 0dc731b6..e81a103c 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
@@ -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/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 <https://github.com/qutip/qutip-qip/pull/232>`_)
+
+Bug Fixes
+---------
+- Remove comments after meaningful statements in QASM reader. (`#218 <https://github.com/qutip/qutip-qip/pull/218>`_ by NoriyukiK-1qbit)
+- Fix circuit plot error for gates with classical controls when ``reverse_states=False``. (`#221 <https://github.com/qutip/qutip-qip/pull/221>`_ by Kevin P. O'Brien)
+- Update the RTD configuration file to execute ``create_tutorials_html.py`` (`#222 <https://github.com/qutip/qutip-qip/pull/222>`_, reported by Ramyashri Padmanabhakumar)
+- Remove the unnecessary use of circuit.propagatos (`#226 <https://github.com/qutip/qutip-qip/pull/226>`_)
+- Use CSR as default for ``expand_operator`` (`#227 <https://github.com/qutip/qutip-qip/pull/227>`_)
+- Update the parsing mode for ``read_qasm`` (`#224 <https://github.com/qutip/qutip-qip/pull/224>`_ reported by kevinab107)
+
+
 Version 0.3.0 (July 19, 2023)
 +++++++++++++++++++++++++++++
 
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 <https://qutip.org/docs/latest/development/contributing.html#building>`_ to
+Please follow the instruction on the `QuTiP contribution guide <https://qutip.readthedocs.io/en/stable/development/contributing.html#building>`_ 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 <http://qutip.org/docs/latest/installation.html>`_.
 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 @@
 </div>
 
 <h4 id="quantum-information-processing">Quantum information processing</h4>
-<p>This section contains tutorials for QuTiP Version 5. You can find the tutorials for QuTiP Version 4 <a href="https://qutip.org/qutip-tutorials/">here</a>. To check the version of QuTiP, you can use the <code>qutip.about()</code> command.</p>
+<p>This section contains tutorials for QuTiP Version 5. You can find the tutorials for QuTiP Version 4 <a href="https://qutip.org/qutip-tutorials/index-v4">here</a>. To check the version of QuTiP, you can use the <code>qutip.about()</code> command.</p>
 
 <h5 id="qip-circuits">Quantum circuits and algorithms</h5>
 <ul>
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/setup.cfg b/setup.cfg
index b974d029..a05259f6 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -40,10 +40,13 @@ 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
+    qiskit<1.0.0
+    qiskit_aer
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 6d20e107..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
@@ -1013,7 +1014,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/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/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/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 456133c4..c736b13b 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
@@ -35,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.
     """
@@ -179,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",
@@ -221,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(
@@ -245,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):
@@ -264,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
 
@@ -275,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 = []
 
@@ -290,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] == "}":
@@ -312,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:
@@ -505,9 +525,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,
                         )
                     )
@@ -647,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]),
@@ -715,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,
@@ -773,7 +793,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
@@ -808,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:
@@ -847,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
@@ -860,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
@@ -886,6 +909,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/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


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 22fb79fa..42b1e2c3 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -12,11 +12,11 @@ jobs:
     strategy:
       matrix:
         include:
-          - case-name: qutip@5.0.0a1
+          - case-name: qutip@5.0.0
             os: ubuntu-latest
-            qutip-version: '==5.0.0a1'
+            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,37 +26,31 @@ 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.*'
+            qiskit-version: '==0.46.*'
             pyqir-version: '==0.6.2'
-            python-version: '3.8'
+            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.7'
+            python-version: '3.9'
 
     steps:
     - uses: actions/checkout@v3
@@ -75,15 +69,9 @@ 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'}}
-      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 }}'
+      run: python -m pip install 'qiskit${{ matrix.qiskit-version }}' 'qiskit-aer==0.14.0.1'
 
     - name: Install PyQIR from PyPI
       if: ${{ matrix.pyqir-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/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/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
new file mode 100644
index 00000000..f726c9be
--- /dev/null
+++ b/tests/qasm_files/w-state_with_comments.qasm
@@ -0,0 +1,32 @@
+
+// Name of Experiment: W-state v1
+
+OPENQASM 2.0;
+
+
+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_circuit.py b/tests/test_circuit.py
index 3b55c53d..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):
@@ -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)
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
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_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]])
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_qasm.py b/tests/test_qasm.py
index 33d7260a..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,10 +129,88 @@ 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()
 
     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
+
+
+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)
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:
     """
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)