Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable repeat use of UCCDefault1 and update docs #268

Merged
merged 3 commits into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice addition!

run: cd docs/source && poetry run make doctest SPHINXOPTS="-W --keep-going -n"
14 changes: 0 additions & 14 deletions docs/source/api.rst

This file was deleted.

7 changes: 6 additions & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]

Expand Down
9 changes: 9 additions & 0 deletions docs/source/contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://python-poetry.org/docs/managing-environments/#activating-the-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
Expand All @@ -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 <https://www.sphinx-doc.org/en/master/usage/extensions/doctest.html>`_ .

We also use `pre-commit <https://pre-commit.com/>`_ to run code formatting and linting checks before each commit.
To enable the pre-commit hooks, run

Expand Down
1 change: 0 additions & 1 deletion docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ Welcome to the docs!
:caption: Contents:

User Guide <user_guide.rst>
API-doc <api.rst>
Contributing Guide <contributing.rst>
Benchmarking <benchmarking.rst>
Developer Documentation <dev.rst>
Expand Down
53 changes: 38 additions & 15 deletions docs/source/user_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
***********
Expand All @@ -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
Expand All @@ -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
***********
Expand All @@ -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`.
Expand All @@ -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

Expand All @@ -80,15 +93,27 @@ 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
=====================
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
Expand All @@ -110,28 +135,26 @@ 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']
target_basis = single_q_basis.append('cx')
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()
Expand All @@ -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.
1 change: 1 addition & 0 deletions ucc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__
5 changes: 3 additions & 2 deletions ucc/compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 7 additions & 8 deletions ucc/transpilers/ucc_defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand All @@ -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):
Expand Down Expand Up @@ -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):
Expand Down