From e53cb63d52506bac9653a435a0a68c1d706a499b Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Wed, 26 Feb 2025 17:22:24 -0500 Subject: [PATCH 1/3] Set all custom passes in UCCDefault1 initializer --- ucc/compile.py | 5 +++-- ucc/transpilers/ucc_defaults.py | 15 +++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ucc/compile.py b/ucc/compile.py index 6ee1559..f48beaa 100644 --- a/ucc/compile.py +++ b/ucc/compile.py @@ -50,9 +50,10 @@ def compile( # Translate to Qiskit Circuit object qiskit_circuit = transpile(circuit, "qiskit") - compiled_circuit = UCCDefault1().run( + compiled_circuit = UCCDefault1( + coupling_list=get_backend_connectivity(target_device) + ).run( qiskit_circuit, - coupling_list=get_backend_connectivity(target_device), ) # Translate the compiled circuit to the desired format diff --git a/ucc/transpilers/ucc_defaults.py b/ucc/transpilers/ucc_defaults.py index 7fb6c82..3bd604c 100644 --- a/ucc/transpilers/ucc_defaults.py +++ b/ucc/transpilers/ucc_defaults.py @@ -27,7 +27,7 @@ class UCCDefault1: - def __init__(self, local_iterations=1): + def __init__(self, local_iterations=1, coupling_list=None): self.pass_manager = PassManager() self._1q_basis = ["rz", "rx", "ry", "h"] self._2q_basis = ["cx"] @@ -43,6 +43,10 @@ def __init__(self, local_iterations=1): }, } self.add_local_passes(local_iterations) + self.add_map_passes(coupling_list) + self.pass_manager.append( + BasisTranslator(sel, target_basis=self.target_basis) + ) @property def default_passes(self): @@ -103,13 +107,8 @@ def add_map_passes(self, coupling_list=None): self.pass_manager.append(VF2PostLayout(coupling_map=coupling_map)) self.pass_manager.append(ApplyLayout()) - def run(self, circuits, coupling_list=None): - self.add_map_passes(coupling_list) - self.pass_manager.append( - BasisTranslator(sel, target_basis=self.target_basis) - ) - out_circuits = self.pass_manager.run(circuits) - return out_circuits + def run(self, circuits): + return self.pass_manager.run(circuits) def _get_trial_count(default_trials=5): From fe3f2e209c78e5b481a11765e2496319f93e9e91 Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Wed, 26 Feb 2025 17:58:50 -0500 Subject: [PATCH 2/3] Enable doctest and fix examples in docs --- .github/workflows/test.yml | 8 ++++-- docs/source/api.rst | 4 --- docs/source/conf.py | 7 ++++- docs/source/contributing.rst | 9 ++++++ docs/source/user_guide.rst | 53 ++++++++++++++++++++++++++---------- ucc/__init__.py | 1 + 6 files changed, 60 insertions(+), 22 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 03f8a9a..b8ea0f8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,12 +31,16 @@ jobs: - name: Install dependencies & ucc run: poetry install - + - name: Run tests run: poetry run pytest ucc --verbose - + - name: Run linter run: poetry run ruff check - name: Run formatter check run: poetry run ruff format --check + + - name: Run doctest + # Check that code examples in docs execute as expected, and treat warnings as errors + run: cd docs/source && poetry run make doctest SPHINXOPTS="-W --keep-going -n" diff --git a/docs/source/api.rst b/docs/source/api.rst index 45aea27..26a268a 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -8,7 +8,3 @@ This page details the publicly accessible functions available in ``ucc``. .. autoclass:: ucc.transpilers.ucc_defaults.UCCDefault1 :members: - -.. automodule:: ucc.transpiler_passes - :members: - :imported-members: diff --git a/docs/source/conf.py b/docs/source/conf.py index 49c5d60..b0ee4d2 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -17,7 +17,12 @@ # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -extensions = ["sphinx.ext.napoleon", "sphinx.ext.autodoc", "myst_parser"] +extensions = [ + "sphinx.ext.napoleon", + "sphinx.ext.autodoc", + "myst_parser", + "sphinx.ext.doctest", +] # Suppress warnings related to heading levels suppress_warnings = ["myst.header"] diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst index 3810b4c..bf6eb56 100644 --- a/docs/source/contributing.rst +++ b/docs/source/contributing.rst @@ -26,6 +26,7 @@ For all of the following commands, we assume you either prefix each command with you first active the `poetry managed virtual environment `_ by running the output of ``poetry env activate`` in your shell. To run the unit tests, you can use the following command + .. code:: bash pytest ucc @@ -38,6 +39,14 @@ and build the documentation by changing to the ``docs/source`` directory where y The built documentation will then live in ``ucc/docs/source/_build/html``. +To test that code examples in the documentation work as expected, you can run + +.. code:: bash + + make doctest + +This leverages Sphinx `doctest extension `_ . + We also use `pre-commit `_ to run code formatting and linting checks before each commit. To enable the pre-commit hooks, run diff --git a/docs/source/user_guide.rst b/docs/source/user_guide.rst index efb87eb..762b41a 100644 --- a/docs/source/user_guide.rst +++ b/docs/source/user_guide.rst @@ -13,7 +13,7 @@ To install ``ucc`` run pip install ucc -UCC requires Python version ≥ 3.12. +UCC requires Python version ≥ 3.12. Basic usage *********** @@ -23,7 +23,14 @@ For basic usage, the circuit of interest is simply input into the function ``ucc The output of ``ucc.compile()`` is a transpiled circuit that is logically equivalent to the input circuit but with reduced gate counts (and by default returned in the same format) as the input circuit. For example, we can define a random circuit in Qiskit and optimize it using the default settings of ``ucc.compile()``, as shown in the following example. -.. code:: python +.. + This comment is around the testcode/testoutput block below. These leverage + doctest extension of sphinx to test this code actually runs and any output + matches. The ELLIPSIS directive (and the use of ... in the expected output) of + the testoutput block avoids us needing to explicitly have the gate count, which + is liable to change as ucc changes over time + +.. testcode:: from qiskit.circuit.random import random_clifford_circuit import ucc @@ -39,6 +46,13 @@ For example, we can define a random circuit in Qiskit and optimize it using the print(f"Number of multi-qubit gates in original circuit: {count_multi_qubit_gates_qiskit(raw_circuit)}") print(f"Number of multi-qubit gates in compiled circuit: {count_multi_qubit_gates_qiskit(compiled_circuit)}") +.. testoutput:: + :hide: + :options: +ELLIPSIS, +NORMALIZE_WHITESPACE + + Number of multi-qubit gates in original circuit: ... + Number of multi-qubit gates in compiled circuit: ... + Key modules *********** @@ -56,7 +70,6 @@ UCC includes the following modules: - ``Optimize1qGatesDecomposition`` - ``CollectCliffords`` - ``HighLevelSynthesis`` (greedy Clifford synthesis) -- ``transpiler_passes`` consisting of submodules, each designed to perform a different optimization or analysis pass on the circuit. These include the passes listed in ``UCC_Default1``, along with others for specialized use. The full list of transpiler passes available in UCC can be found in the :doc:`api`. @@ -65,11 +78,11 @@ The full list of transpiler passes available in UCC can be found in the :doc:`ap Customization ************* -UCC offers different levels of customization, from settings accepted by the "default" pass ``UCCDefault1`` to the ability to add custom transpiler passes. +UCC offers different levels of customization, from settings accepted by the "default" pass ``UCCDefault1`` to the ability to add custom transpiler passes. Transpilation settings ====================== -UCC settings can be adjusted using the keyword arguments of the ``ucc.compile()`` function, as shown. +UCC settings can be adjusted using the keyword arguments of the ``ucc.compile()`` function, as shown. .. code:: python @@ -80,7 +93,7 @@ UCC settings can be adjusted using the keyword arguments of the ``ucc.compile()` ) -- ``return_format`` is the format in which the input circuit will be returned, e.g. "TKET" or "OpenQASM2". Check ``ucc.supported_circuit_formats()`` for supported circuit formats. Default is the format of input circuit. +- ``return_format`` is the format in which the input circuit will be returned, e.g. "TKET" or "OpenQASM2". Check ``ucc.supported_circuit_formats()`` for supported circuit formats. Default is the format of input circuit. - ``target_device`` can be specified as a Qiskit backend or coupling map, or a list of connections between qubits. If None, all-to-all connectivity is assumed. If a Qiskit backend or coupling map is specified, only the coupling list extracted from the backend is used. Writing a custom pass @@ -88,7 +101,19 @@ Writing a custom pass UCC reuses part of the Qiskit transpiler framework for creation of custom transpiler passes, specifically the ``TransformationPass`` type of pass and the ``PassManager`` object for running custom passes and sequences of passes. In the following example, we demonstrate how to create a custom pass, where the Directed Acycylic Graph (DAG) representation of the circuit is the object manipulated by the pass. -.. code:: python +.. + This testsetup is associated with subsequent blocks that also have the custom_pass group. + This setup is run, followed by all the blocks with this group in order and + ensures the "circuit_to_compile" variable is defined. + +.. testsetup:: custom_pass + + from qiskit import QuantumCircuit as QiskitCircuit + circuit_to_compile = QiskitCircuit(2) + circuit_to_compile.h(0) + circuit_to_compile.cx(0, 1) + +.. testcode:: custom_pass from qiskit.transpiler.basepasses import TransformationPass from qiskit.dagcircuit import DAGCircuit @@ -110,13 +135,11 @@ Applying a non-default pass in the transpilation sequence UCC's built-in pass manager ``UCCDefault1().pass_manager`` can be used to apply a non-default or a custom pass in the sequence of transpilation passes. In the following example we show how to add passes for merging single qubit rotations interrupted by a commuting 2 qubit gate. -.. code:: python - - from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel - from qiskit.transpiler.passes import Optimize1qGatesSimpleCommutation +.. testcode:: custom_pass + from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel + from qiskit.transpiler.passes import BasisTranslator, Optimize1qGatesSimpleCommutation from ucc import UCCDefault1 - from ucc.transpiler_passes import BasisTranslator single_q_basis = ['rz', 'rx', 'ry', 'h'] @@ -124,14 +147,14 @@ In the following example we show how to add passes for merging single qubit rota ucc_compiler = UCCDefault1() ucc_compiler.pass_manager.append(Optimize1qGatesSimpleCommutation(basis=single_q_basis)) - ucc_compiler.pass_manager.append(BasisTranslator(sel, target_basis=target_basis)) + ucc_compiler.pass_manager.append(BasisTranslator(sel, target_basis=target_basis)) custom_compiled_circuit = ucc_compiler.run(circuit_to_compile) Alternatively, we can add a custom pass in the sequence, as shown in the following example. -.. code:: python +.. testcode:: custom_pass from ucc import UCCDefault1 ucc_compiler = UCCDefault1() @@ -146,5 +169,5 @@ A note on terminology .. important:: There is some disagreement in the quantum computing community on the proper usage of the terms "transpilation" and "compilation." - For instance, Qiskit refers to optimization of the Directed Acyclic Graph (DAG) of a circuit as "transpilation," whereas in qBraid, the 1:1 translation of one circuit representation into another without optimization (e.g. a Cirq circuit to a Qiskit circuit; OpenQASM 2 into PyTKET) is called "transpilation." + For instance, Qiskit refers to optimization of the Directed Acyclic Graph (DAG) of a circuit as "transpilation," whereas in qBraid, the 1:1 translation of one circuit representation into another without optimization (e.g. a Cirq circuit to a Qiskit circuit; OpenQASM 2 into PyTKET) is called "transpilation." In addition, Cirq uses the term "transformer" and PyTKET uses :code:`CompilationUnit` to refer to what Qiskit calls a transpiler pass. diff --git a/ucc/__init__.py b/ucc/__init__.py index d211750..06c4e71 100644 --- a/ucc/__init__.py +++ b/ucc/__init__.py @@ -3,4 +3,5 @@ supported_circuit_formats as supported_circuit_formats, ) +from .transpilers.ucc_defaults import UCCDefault1 as UCCDefault1 from ucc._version import __version__ as __version__ From 18d536d5c2f6c6fb46be2262f8b384d8d973962c Mon Sep 17 00:00:00 2001 From: Brad Chase Date: Thu, 27 Feb 2025 14:16:59 -0500 Subject: [PATCH 3/3] Remove autodoc page --- docs/source/api.rst | 10 ---------- docs/source/index.rst | 1 - 2 files changed, 11 deletions(-) delete mode 100644 docs/source/api.rst diff --git a/docs/source/api.rst b/docs/source/api.rst deleted file mode 100644 index 26a268a..0000000 --- a/docs/source/api.rst +++ /dev/null @@ -1,10 +0,0 @@ -API-doc -======= - -This page details the publicly accessible functions available in ``ucc``. - -.. automodule:: ucc - :members: compile - -.. autoclass:: ucc.transpilers.ucc_defaults.UCCDefault1 - :members: diff --git a/docs/source/index.rst b/docs/source/index.rst index fd798ba..8175972 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -14,7 +14,6 @@ Welcome to the docs! :caption: Contents: User Guide - API-doc Contributing Guide Benchmarking Developer Documentation