Skip to content

Commit

Permalink
📝 Add documentation for ZX-calculus package and C++ API docs setup (#817
Browse files Browse the repository at this point in the history
)

## Description

This PR introduces documentation for the C++ ZX-calculus library. 
It also adds the general infrastructure for C++ API documentation as
part of the MQT Core project based on Doxygen and the `breathe`
extension.

Fixes #813

This change addresses concerns about missing documentation that came up
in the revision openjournals/joss-reviews#7478.

## Checklist:

<!---
This checklist serves as a reminder of a couple of things that ensure
your pull request will be merged swiftly.
-->

- [x] The pull request only contains commits that are related to it.
- [x] I have added appropriate tests and documentation.
- [x] I have made sure that all CI jobs on GitHub pass.
- [x] The pull request introduces no new warnings and follows the
project's style guidelines.

---------

Signed-off-by: Tom Peham <pehamtom@gmx.at>
Signed-off-by: burgholzer <burgholzer@me.com>
Co-authored-by: Lukas Burgholzer <burgholzer@me.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Feb 26, 2025
1 parent 2c02f80 commit 9d8cfd5
Show file tree
Hide file tree
Showing 33 changed files with 3,813 additions and 96 deletions.
2 changes: 2 additions & 0 deletions .clang-tidy
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,5 @@ CheckOptions:
value: CamelCase
- key: readability-identifier-naming.VariableCase
value: camelBack
- key: misc-include-cleaner.IgnoreHeaders
value: pybind11/detail/.*
2,504 changes: 2,504 additions & 0 deletions docs/Doxyfile

Large diffs are not rendered by default.

30 changes: 30 additions & 0 deletions docs/_static/ghz.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 27 additions & 0 deletions docs/_static/ghz_simp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

from __future__ import annotations

import os
import subprocess
import warnings
from importlib import metadata
from pathlib import Path
Expand Down Expand Up @@ -66,6 +68,7 @@
"sphinx.ext.viewcode",
"sphinxcontrib.inkscapeconverter",
"sphinxcontrib.bibtex",
"breathe",
]

source_suffix = [".rst", ".md"]
Expand Down Expand Up @@ -161,6 +164,15 @@ def format_url(self, _e: Entry) -> HRef: # noqa: PLR6301
napoleon_google_docstring = True
napoleon_numpy_docstring = False


breathe_projects = {"mqt.core": "_build/doxygen/xml"}
breathe_default_project = "mqt.core"

read_the_docs_build = os.environ.get("READTHEDOCS", None) == "True"
if read_the_docs_build:
subprocess.call("doxygen", shell=True) # noqa: S602, S607
subprocess.call("mkdir api/cpp & breathe-apidoc -o api/cpp -m -f -T _build/doxygen/xml/", shell=True) # noqa: S602, S607

# -- Options for HTML output -------------------------------------------------
html_theme = "furo"
html_static_path = ["_static"]
Expand Down Expand Up @@ -204,6 +216,7 @@ def format_url(self, _e: Entry) -> HRef: # noqa: PLR6301
\DeclarePairedDelimiter\abs{\lvert}{\rvert}
\DeclarePairedDelimiter\mket{\lvert}{\rangle}
\DeclarePairedDelimiter\mbra{\langle}{\rvert}
\DeclareUnicodeCharacter{03C0}{$\pi$}
\newcommand*{\ket}[1]{\ensuremath{\mket{\mkern1mu#1}}}
\newcommand*{\bra}[1]{\ensuremath{\mbra{\mkern1mu#1}}}
Expand Down
11 changes: 10 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ self
installation
mqt_core_ir
dd_package
zx_package
references
```

Expand All @@ -60,7 +61,15 @@ DevelopmentGuide

```{toctree}
:hidden:
:caption: API Reference
:caption: Python API Reference
api/mqt/core/index
```

```{toctree}
:hidden:
:glob:
:caption: C++ API Reference
api/cpp/namespacelist
```
145 changes: 145 additions & 0 deletions docs/zx_package.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# MQT Core ZX

MQT Core provides a minimal library for creating ZX-diagrams and rewriting them using the ZX-calculus.
The main use of this library within the Munich Quantum Toolkit is as a tool for equivalence checking of quantum circuits, especially with a focus on symbolic quantum circuits.
Other uses for the ZX-calculus and ZX-diagrams include compilation and optimization.
Furthermore, other diagrammatic rewriting systems exist for reasoning about different aspects of quantum circuits such as the ZH-calculus.
If you are looking for general-purpose libraries that encompass these functionalities, we recommend [PyZX](https://pyzx.readthedocs.io/en/latest/) or [QuiZX](https://github.com/zxcalc/quizx).
For an introduction to the ZX-calculus, we recommend the excellent [ZX-calculus for the working quantum computer scientist](https://arxiv.org/abs/2012.13966).

## Quickstart

There are two ways of obtaining a ZX-diagram with MQT Core.
The simplest way is from a quantum circuit, or rather, an MQT Core `QuantumComputation`:

```cpp
#include "zx/ZXDiagram.hpp"
#include "ir/QuantumComputation.hpp"
#include "zx/FunctionalityConstruction"

// Create GHZ state preparation circuit
ir::QuantumComputation qc{3};
qc.h(0);
qc.cx(1, 0);
qc.cx(2, 0);

// Create ZX-diagram from circuit
auto diag = zx::FunctionalityConstruction.buildFunctionality(&qc);
```
This yields the following ZX-diagram.
```{image} _static/ghz.svg
:width: 40%
:align: center
```

The other way is to manipulate the diagram directly.
Let's create the ZX-diagram for the GHZ circuit above directly.

We start off by creating an empty ZX-diagram.

```cpp
#include "zx/ZXDiagram.hpp"
#include "zx/ZXDefinitions.hpp"

zx::ZXDiagram diag{};
```

This is a diagram without any vertices.
Next, we add qubits to the diagram.

```cpp
diag.addQubits(3);
```

Some vertices in a ZX-diagram are special because they represent inputs and outputs of the diagram.
All vertices in a ZX-diagram are either of type `zx::VertexType::Z`, `zx::VertexType::X` or `zx::VertexType::Boundary`.
Boundary vertices denote the inputs.
Therefore, at this point the diagram has 6 vertices (3 input vertices, 3 output vertices) and 3 edges connecting the respective inputs and outputs.

Next, we add the Hadamard gate on qubit 0.
While Hadamard gates have an Euler decomposition in terms of X- and Z-spiders, they are so common that it is helpful to have a notation for them.
To this end, there are two types of edges in the diagram.
An edge is either `zx::EdgeType::Simple` or `zx::EdgeType::Hadamard`, where Hadamard edges represent wires with a Hadamard gate on them.

To represent a Hadamard gate we, therefore, have to add a new Vertex to the diagram, connect it to the input of qubit 0 with a Hadamard edge.

```cpp
auto in0 = diag.getInputs()[0];
auto newVertex = diag.addVertex(0, 0, zx::PiExpression(), zx::VertexType::Z);
diag.addEdge(in0, newVertex, zx::EdgeType::Hadamard);
```

We see that adding a new vertex requires 4 parameters.
These are the x- and y-coordinates of the vertex, the phase of the vertex and the vertextype.
The coordinates are there to identify where vertices lie if we would arrange the diagram on a grid.
They have no other special semantics.

The phase of the diagram is of type `zx::PiExpression` which is a class representing symbolic sums of monomials.
We will talk a bit more about these further below.
For now, we simply need to know that `zx::PiExpression()` represents a phase of 0.

Next we need to add two CNOTs to the diagram.
A CNOT in the ZX-calculus is represented by a Z-vertex (the control) and an X-vertex (the target), connected by a single non-Hadamard wire.

```cpp
auto in1 = diag.getInputs()[1];
auto in2 = diag.getInputs()[2];

auto ctrl1 = diag.addVertex(1, 0, zx::PiExpression(), zx::VertexType::Z);
auto ctrl2 = diag.addVertex(1, 0, zx::PiExpression(), zx::VertexType::Z);
auto trgt1 = diag.addVertex(1, 0, zx::PiExpression(), zx::VertexType::X);
auto trgt2 = diag.addVertex(2, 0, zx::PiExpression(), zx::VertexType::X);

// connect vertices to their respective qubit lines.
diag.addEdge(newVertex, ctrl1); // omitting the edge type adds a non-Hadamard edge
diag.addEdge(ctrl1, ctrl2);
diag.addEdge(in1, trgt1);
diag.addEdge(in2, trgt2);

// add edges for CNOTs
diag.addEdge(ctrl1, trgt1);
diag.addEdge(ctrl2, trgt2);

// complete diagram by connecting last vertices to the outputs
diag.addEdge(ctrl2, diag.getOutputs()[0]);
diag.addEdge(trgt1, diag.getOutputs()[1]);
diag.addEdge(trgt2, diag.getOutputs()[2]);
```

Let us return to the matter of symbolic phases and phases in general.
Phases of vertices in a ZX-diagram are all of type `zx::PiExpression` even if the phases are variable-free.
In the variable-free case, the `zx::PiExpression` consists only of a constant of type `zx::PiRational`.
The `zx::PiRational` class represents angles in the half-open interval $(-\pi, \pi]$ as a fraction of $\pi$.
For example, the number $\pi$ itself would be represented by the fraction $\frac{1}{1}$, the number $-\pi / 2$ would be $\frac{-1}{2}$.
This is because phases in terms of fractions of $\pi$ appear frequently in the ZX-calculus.
For more on symbolic expressions we refer to the code documentation.

## Rewriting

The true power of ZX-diagrams lies in the ZX-calculus which allows for manipulating ZX-diagrams.
The MQT Core ZX-calculus library provides some rewriting rules for ZX-diagrams in the header `Rules.hpp`.
The simplification routines are provided in the header `Simplify.hpp`.

For example, the previous diagram has multiple connected Z-vertices on qubit 0.
According to the axioms of the ZX-calculus, these can be merged via spider fusion.
We can perform this simplification on the diagram as follows.

```cpp
#include "zx/Simplify.hpp"

auto n_simplifications = spiderSimp(diag);
```

This results in the following diagram.

```{image} _static/ghz_simp.svg
:width: 40%
:align: center
```

`n_simplifications` will be two when executing this code since two spiders can be fused.
The diagrams are manipulated inplace for performance reasons.

For an overview on simplifications, we refer to the code documentation.
4 changes: 3 additions & 1 deletion include/mqt-core/datastructures/SymmetricMatrix.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@

#include <cstddef>
#include <vector>

namespace qc {
/**
* @brief Symmetric matrix class with same number of rows and columns that
* allows access by row and column but uses less memory than a full matrix
*/
template <typename T> class SymmetricMatrix {
private:
std::vector<std::vector<T>> data;

public:
Expand Down Expand Up @@ -52,3 +53,4 @@ template <typename T> class SymmetricMatrix {

[[nodiscard]] size_t size() const { return data.size(); }
};
} // namespace qc
14 changes: 0 additions & 14 deletions include/mqt-core/dd/CachedEdge.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,12 @@

namespace dd {

///-----------------------------------------------------------------------------
/// \n Forward declarations \n
///-----------------------------------------------------------------------------
struct vNode; // NOLINT(readability-identifier-naming)
struct mNode; // NOLINT(readability-identifier-naming)
struct dNode; // NOLINT(readability-identifier-naming)
class ComplexNumbers;
template <typename T> class MemoryManager;

///-----------------------------------------------------------------------------
/// \n Type traits and typedefs \n
///-----------------------------------------------------------------------------
template <typename T>
using isVector = std::enable_if_t<std::is_same_v<T, vNode>, bool>;
template <typename T>
Expand Down Expand Up @@ -119,10 +113,6 @@ template <typename Node> struct CachedEdge {
return Node::isTerminal(p);
}

///---------------------------------------------------------------------------
/// \n Methods for vector DDs \n
///---------------------------------------------------------------------------

/**
* @brief Get a normalized vector DD from a fresh node and a list of edges.
* @tparam T template parameter to enable this method only for vNode
Expand All @@ -137,10 +127,6 @@ template <typename Node> struct CachedEdge {
static CachedEdge normalize(Node* p, const std::array<CachedEdge, RADIX>& e,
MemoryManager<Node>& mm, ComplexNumbers& cn);

///---------------------------------------------------------------------------
/// \n Methods for matrix DDs \n
///---------------------------------------------------------------------------

/**
* @brief Get a normalized (density) matrix) DD from a fresh node and a list
* of edges.
Expand Down
Loading

0 comments on commit 9d8cfd5

Please sign in to comment.