Skip to content
Open
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
13 changes: 1 addition & 12 deletions docs/source/clifford.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,4 @@ Miscellaneous modules
.. currentmodule:: graphix.clifford

.. autoclass:: graphix.clifford.Clifford

.. data:: graphix.cliffford.CLIFFORD

list of 24 unique single-qubit Clifford operators as numpy array.

.. data:: graphix.clifford.CLIFFORD_MUL

the matrix multiplication of single-qubit Clifford gates, expressed as a mapping of CLIFFORD indices. This is possible because multiplication of two Clifford gates result in a Clifford gate.

.. data:: graphix.clifford.CLIFFORD_MEASURE

The mapping of Pauli operators under conjugation by single-qubit Clifford gates, expressed as a mapping into Pauli operator indices (in CLIFFORD) and sign.
:members:
91 changes: 31 additions & 60 deletions graphix/_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,26 @@
_C1 = Ops.X # X
_C2 = Ops.Y # Y
_C3 = Ops.Z # Z
_C4 = Ops.S # S = \sqrt{Z}
_C5 = Ops.SDG # SDG = S^{\dagger}
_C4 = Ops.S # S
_C5 = Ops.SDG # S†
_C6 = Ops.H # H
_C7 = utils.lock(np.asarray([[1, -1j], [-1j, 1]]) / np.sqrt(2)) # \sqrt{iX}
_C8 = utils.lock(np.asarray([[1, -1], [1, 1]]) / np.sqrt(2)) # \sqrt{iY}
_C9 = utils.lock(np.asarray([[0, 1 - 1j], [-1 - 1j, 0]]) / np.sqrt(2)) # sqrt{I}
_C10 = utils.lock(np.asarray([[0, -1 - 1j], [1 - 1j, 0]]) / np.sqrt(2)) # sqrt{-I}
_C11 = utils.lock(np.asarray([[1, -1], [-1, -1]]) / np.sqrt(2)) # sqrt{I}
_C12 = utils.lock(np.asarray([[-1, -1], [1, -1]]) / np.sqrt(2)) # sqrt{-iY}
_C13 = utils.lock(np.asarray([[1j, -1], [1, -1j]]) / np.sqrt(2)) # sqrt{-I}
_C14 = utils.lock(np.asarray([[1j, 1], [-1, -1j]]) / np.sqrt(2)) # sqrt{-I}
_C15 = utils.lock(np.asarray([[-1, -1j], [-1j, -1]]) / np.sqrt(2)) # sqrt{-iX}
_C16 = utils.lock(np.asarray([[-1 + 1j, 1 + 1j], [-1 + 1j, -1 - 1j]]) / 2) # I^(1/3)
_C17 = utils.lock(np.asarray([[-1 + 1j, -1 - 1j], [1 - 1j, -1 - 1j]]) / 2) # I^(1/3)
_C18 = utils.lock(np.asarray([[1 + 1j, 1 - 1j], [-1 - 1j, 1 - 1j]]) / 2) # I^(1/3)
_C19 = utils.lock(np.asarray([[-1 - 1j, 1 - 1j], [-1 - 1j, -1 + 1j]]) / 2) # I^(1/3)
_C20 = utils.lock(np.asarray([[-1 - 1j, -1 - 1j], [1 - 1j, -1 + 1j]]) / 2) # I^(1/3)
_C21 = utils.lock(np.asarray([[-1 + 1j, -1 + 1j], [1 + 1j, -1 - 1j]]) / 2) # I^(1/3)
_C22 = utils.lock(np.asarray([[1 + 1j, -1 - 1j], [1 - 1j, 1 - 1j]]) / 2) # I^(1/3)
_C23 = utils.lock(np.asarray([[-1 + 1j, 1 - 1j], [-1 - 1j, -1 - 1j]]) / 2) # I^(1/3)
_C7 = utils.lock(np.asarray([[1, -1j], [-1j, 1]]) / np.sqrt(2)) # S† H S†
_C8 = utils.lock(np.asarray([[1, -1], [1, 1]]) / np.sqrt(2)) # H Z
_C9 = utils.lock(np.asarray([[0, 1 - 1j], [-1 - 1j, 0]]) / np.sqrt(2)) # S† X
_C10 = utils.lock(np.asarray([[0, -1 - 1j], [1 - 1j, 0]]) / np.sqrt(2)) # S X
_C11 = utils.lock(np.asarray([[1, -1], [-1, -1]]) / np.sqrt(2)) # H Y
_C12 = utils.lock(np.asarray([[-1, -1], [1, -1]]) / np.sqrt(2)) # H X
_C13 = utils.lock(np.asarray([[1j, -1], [1, -1j]]) / np.sqrt(2)) # S† H S
_C14 = utils.lock(np.asarray([[1j, 1], [-1, -1j]]) / np.sqrt(2)) # S H S†
_C15 = utils.lock(np.asarray([[-1, -1j], [-1j, -1]]) / np.sqrt(2)) # S H S
_C16 = utils.lock(np.asarray([[-1 + 1j, 1 + 1j], [-1 + 1j, -1 - 1j]]) / 2) # H S†
_C17 = utils.lock(np.asarray([[-1 + 1j, -1 - 1j], [1 - 1j, -1 - 1j]]) / 2) # H S† X
_C18 = utils.lock(np.asarray([[1 + 1j, 1 - 1j], [-1 - 1j, 1 - 1j]]) / 2) # H S X
_C19 = utils.lock(np.asarray([[-1 - 1j, 1 - 1j], [-1 - 1j, -1 + 1j]]) / 2) # H S
_C20 = utils.lock(np.asarray([[-1 - 1j, -1 - 1j], [1 - 1j, -1 + 1j]]) / 2) # S H
_C21 = utils.lock(np.asarray([[-1 + 1j, -1 + 1j], [1 + 1j, -1 - 1j]]) / 2) # S† H
_C22 = utils.lock(np.asarray([[1 + 1j, -1 - 1j], [1 - 1j, 1 - 1j]]) / 2) # S H Y
_C23 = utils.lock(np.asarray([[-1 + 1j, 1 - 1j], [-1 - 1j, -1 - 1j]]) / 2) # S† H Y


CLIFFORD = (
Expand Down Expand Up @@ -65,35 +65,6 @@
_C23,
)

# Human-readable labels
CLIFFORD_LABEL = (
"I",
"X",
"Y",
"Z",
"S",
"Sdagger",
"H",
r"\sqrt{iX}",
r"\sqrt{iY}",
r"\sqrt{I}",
r"\sqrt{-I}",
r"\sqrt{I}",
r"\sqrt{-iY}",
r"\sqrt{-I}",
r"\sqrt{-I}",
r"\sqrt{-iX}",
"I^{1/3}",
"I^{1/3}",
"I^{1/3}",
"I^{1/3}",
"I^{1/3}",
"I^{1/3}",
"I^{1/3}",
"I^{1/3}",
"I^{1/3}",
)

# Clifford(CLIFFORD_MUL[i][j]) ~ CLIFFORD[i] @ CLIFFORD[j] (up to phase)
CLIFFORD_MUL = (
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23),
Expand Down Expand Up @@ -210,22 +181,22 @@ class _CMTuple(NamedTuple):
("sdg",),
("h",),
("sdg", "h", "sdg"),
("h", "x"),
("sdg", "y"),
("sdg", "x"),
("h", "y"),
("h", "z"),
("sdg", "h", "sdg", "y"),
("z", "h"),
("x", "sdg"),
("x", "s"),
("y", "h"),
("x", "h"),
("s", "h", "sdg"),
("sdg", "h", "s"),
("sdg", "h", "sdg", "x"),
("s", "h", "s"),
("sdg", "h"),
("sdg", "h", "y"),
("sdg", "h", "z"),
("sdg", "h", "x"),
("x", "sdg", "h"),
("x", "s", "h"),
("s", "h"),
("h", "s"),
("h", "sdg"),
("h", "x", "sdg"),
("h", "x", "s"),
("y", "h", "s"),
("y", "h", "sdg"),
)

CLIFFORD_PAULI_DECOMPOSITION = (
Expand Down
46 changes: 36 additions & 10 deletions graphix/clifford.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
CLIFFORD,
CLIFFORD_CONJ,
CLIFFORD_HSZ_DECOMPOSITION,
CLIFFORD_LABEL,
CLIFFORD_MEASURE,
CLIFFORD_MUL,
CLIFFORD_PAULI_DECOMPOSITION,
Expand All @@ -29,11 +28,12 @@
import numpy.typing as npt

from graphix import OpenGraph, PauliMeasurement
from graphix.pretty_print import OutputFormat


@dataclass
class Domains:
"""Represent `X^sZ^t` where s and t are XOR of results from given sets of indices."""
"""Represent :math:`X^sZ^t` where s and t are XOR of results from given sets of indices."""

s_domain: set[int]
t_domain: set[int]
Expand Down Expand Up @@ -112,8 +112,34 @@ def __repr__(self) -> str:
return f"({formula})"

def __str__(self) -> str:
"""Return the name of the Clifford gate."""
return CLIFFORD_LABEL[self.value]
"""Return a string representation of a Clifford in the default output format.

Example
-------
>>> from graphix import Clifford
>>> print(Clifford.SDG @ Clifford.H @ Clifford.SDG @ Clifford.Y)
S H Sdg
"""
return self.draw()

def draw(self, output: OutputFormat | None = None) -> str:
"""Return a string representation of a Clifford.

Parameters
----------
clifford : Clifford
The statevector to format.
output : OutputFormat | None
Desired formatting style (``ASCII``, ``LaTeX`` or ``Unicode``).

Returns
-------
str
The formatted Clifford.
"""
from graphix.pretty_print import clifford_to_str # noqa: PLC0415

return clifford_to_str(self, output)

@property
def conj(self) -> Clifford:
Expand All @@ -122,7 +148,7 @@ def conj(self) -> Clifford:

@property
def hsz(self) -> list[Clifford]:
"""Return a decomposition of the Clifford gate with the gates `H`, `S`, `Z`."""
"""Return a decomposition of the Clifford gate with the gates ``H``, ``S``, ``Z``."""
return [Clifford(i) for i in CLIFFORD_HSZ_DECOMPOSITION[self.value]]

@property
Expand Down Expand Up @@ -158,13 +184,13 @@ def measure(self, pauli: Pauli) -> Pauli:
return pauli.unit * new_pauli

def commute_domains(self, domains: Domains) -> Domains:
"""
Commute `X^sZ^t` with `C`.
r"""
Commute :math:`X^sZ^t` with :math:`C`.

Given `X^sZ^t`, return `X^s'Z^t'` such that `X^sZ^tC = CX^s'Z^t'`.
Given :math:`X^sZ^t`, return :math:`X^{s'}Z^{t'}` such that :math:`X^sZ^tC = CX^{s'}Z^{t'}`.

Note that applying the method to `self.conj` computes the reverse commutation:
indeed, `C†X^sZ^t = (X^sZ^tC)† = (CX^s'Z^t')† = X^s'Z^t'C†`.
Note that applying the method to :meth:`conj` computes the reverse commutation:
indeed, :math:`C^\dag X^s Z^t = (X^s Z^t C)^\dag = (C X^{s'} Z^{t'})^\dag = X^{s'}Z^{t'}C^\dag`.
"""
s_domain = domains.s_domain.copy()
t_domain = domains.t_domain.copy()
Expand Down
52 changes: 52 additions & 0 deletions graphix/pretty_print.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from typing_extensions import assert_never

from graphix import command
from graphix._db import CLIFFORD_TO_QASM3
from graphix.fundamentals import AbstractMeasurement, Axis, Plane, Sign, angle_to_rad, rad_to_angle
from graphix.measurements import BlochMeasurement, PauliMeasurement
from graphix.parameter import AffineExpression
Expand All @@ -23,6 +24,7 @@
from collections.abc import Container, Iterable, Mapping, Sequence
from collections.abc import Set as AbstractSet

from graphix import Clifford
from graphix.command import Node
from graphix.flow.core import PauliFlow, XZCorrections
from graphix.fundamentals import Angle
Expand Down Expand Up @@ -996,3 +998,53 @@ def density_matrix_to_str(
widths = [max(len(row[col]) for row in rows) for col in range(len(rows[0]))] if rows else []
lines = [" ".join(entry.rjust(widths[col]) for col, entry in enumerate(row)) for row in rows]
return "\n".join(f"[ {line} ]" for line in lines)


QASM3_INSTR_TO_STR = {
OutputFormat.ASCII: {
"id": "I",
"x": "X",
"y": "Y",
"z": "Z",
"s": "S",
"sdg": "Sdg",
"h": "H",
},
OutputFormat.Unicode: {
"id": "I",
"x": "X",
"y": "Y",
"z": "Z",
"s": "S",
"sdg": "S†",
"h": "H",
},
OutputFormat.LaTeX: {
"id": "I",
"x": "X",
"y": "Y",
"z": "Z",
"s": "S",
"sdg": r"S^\dag",
"h": "H",
},
}


def clifford_to_str(clifford: Clifford, output: OutputFormat | None = None) -> str:
r"""Return a string representation of a Clifford.

Parameters
----------
clifford : Clifford
The statevector to format.
output : OutputFormat | None
Desired formatting style (``ASCII``, ``LaTeX`` or ``Unicode``).

Returns
-------
str
The formatted Clifford.
"""
output = ensure_output_format(output)
return " ".join(QASM3_INSTR_TO_STR[output][instr] for instr in reversed(CLIFFORD_TO_QASM3[clifford.value]))
49 changes: 49 additions & 0 deletions tests/test_db.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from __future__ import annotations

import itertools
from functools import reduce
from operator import matmul
from typing import TYPE_CHECKING

import networkx as nx
Expand All @@ -15,6 +17,7 @@
CLIFFORD_MEASURE,
CLIFFORD_MUL,
CLIFFORD_PAULI_DECOMPOSITION,
CLIFFORD_TO_QASM3,
)
from graphix.clifford import Clifford
from graphix.opengraph import OpenGraphError
Expand Down Expand Up @@ -131,3 +134,49 @@ def unwrap(v: T | None) -> T:
def test_generate_clifford_pauli_decomposition(fx_rng: Generator) -> None:
clifford_pauli_decomposition = generate_clifford_pauli_decomposition(fx_rng)
assert clifford_pauli_decomposition == CLIFFORD_PAULI_DECOMPOSITION


QASM3_BASIS = {
Clifford.I: "id",
Clifford.X: "x",
Clifford.Y: "y",
Clifford.Z: "z",
Clifford.S: "s",
Clifford.SDG: "sdg",
Clifford.H: "h",
}

QASM3_TO_CLIFFORD = {instr: clifford for clifford, instr in QASM3_BASIS.items()}


def test_clifford_to_qasm3() -> None:
for clifford, decomposition in zip(Clifford, CLIFFORD_TO_QASM3, strict=True):
clifford2 = reduce(matmul, (QASM3_TO_CLIFFORD[instr] for instr in reversed(decomposition)))
assert clifford == clifford2


def generate_qasm3_decomposition() -> tuple[tuple[str, ...], ...]:
decompositions: list[tuple[str, ...] | None] = [None] * len(Clifford)
new_decompositions: dict[Clifford, tuple[str, ...]] = {
clifford: (instr,) for clifford, instr in QASM3_BASIS.items()
}
while new_decompositions:
current_decompositions = new_decompositions
new_decompositions = {}
for clifford, decomposition in current_decompositions.items():
decompositions[clifford.value] = decomposition
for clifford, decomposition in current_decompositions.items():
for instr_clifford, instr in QASM3_BASIS.items():
result = instr_clifford @ clifford
if decompositions[result.value] is not None:
continue
result_decomposition = (*decomposition, instr)
decompositions[result.value] = result_decomposition
new_decompositions[result] = result_decomposition
assert all(decomposition is not None for decomposition in decompositions)
return tuple(map(unwrap, decompositions))


def test_clifford_to_qasm3_optimal() -> None:
qasm3_decomposition = generate_qasm3_decomposition()
assert qasm3_decomposition == CLIFFORD_TO_QASM3
Loading