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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added new attribute `OpenGraph.output_cliffords`
- Added `clifford` abstract method to `AbstractMeasurement`. Implemented it for `Plane` and `Axis`.

- #515:
- `Pattern.remove_local_clifford_commands` transpiles MBQC+LC patterns into pure MBQC patterns.
- `Clifford.to_opengraph` returns an open graph without local Cliffords that implements the given single-qubit Clifford gate.

- #515, #519:
- `Pattern.reindex` returns a pattern whose nodes have been re-indexed either by a supplied mapping or, by default, consecutively starting at 0.
- `Pattern.node_mapping` returns a mapping that can be passed to `Pattern.reindex`, with flexibility for specifying how nodes are mapped.

### Fixed

- #454, #481: Ensure `Pattern.minimize_space` only reduces max-space and does not increase it.
Expand Down
2 changes: 2 additions & 0 deletions docs/source/modifier.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ Pattern Manipulation

.. automethod:: remove_pauli_measurements

.. automethod:: remove_local_clifford_commands

.. automethod:: to_ascii

.. automethod:: to_unicode
Expand Down
29 changes: 29 additions & 0 deletions graphix/_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from graphix import utils
from graphix.fundamentals import Axis, Sign
from graphix.measurements import Measurement
from graphix.ops import Ops

# 24 unique 1-qubit Clifford gates
Expand Down Expand Up @@ -226,3 +227,31 @@ class _CMTuple(NamedTuple):
("h", "x", "sdg"),
("h", "x", "s"),
)

CLIFFORD_PAULI_DECOMPOSITION = (
Comment thread
thierry-martinez marked this conversation as resolved.
(),
(Measurement.X, -Measurement.X),
(-Measurement.X, -Measurement.X),
(-Measurement.X, Measurement.X),
(-Measurement.Y, Measurement.X),
(Measurement.Y, Measurement.X),
(Measurement.X,),
(Measurement.X, -Measurement.Y),
(-Measurement.X,),
(-Measurement.Y, -Measurement.X),
(Measurement.Y, -Measurement.X),
(Measurement.X, -Measurement.X, -Measurement.X),
(Measurement.X, -Measurement.X, Measurement.X),
(-Measurement.X, -Measurement.Y),
(-Measurement.X, Measurement.Y),
(Measurement.X, Measurement.Y),
(Measurement.Y,),
(Measurement.X, -Measurement.X, Measurement.Y),
(Measurement.X, -Measurement.X, -Measurement.Y),
(-Measurement.Y,),
(Measurement.Y, Measurement.Y),
(-Measurement.Y, -Measurement.Y),
(Measurement.Y, -Measurement.Y),
(-Measurement.Y, Measurement.Y),
)
"""Decomposition of every Clifford gate C into sequences of Pauli measurements."""
17 changes: 17 additions & 0 deletions graphix/clifford.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from enum import Enum
from typing import TYPE_CHECKING, Any

import networkx as nx
import numpy as np
import typing_extensions

Expand All @@ -18,6 +19,7 @@
CLIFFORD_LABEL,
CLIFFORD_MEASURE,
CLIFFORD_MUL,
CLIFFORD_PAULI_DECOMPOSITION,
CLIFFORD_TO_QASM3,
)
from graphix.fundamentals import Axis, ComplexUnit, I
Expand All @@ -26,6 +28,8 @@
if TYPE_CHECKING:
import numpy.typing as npt

from graphix import OpenGraph, PauliMeasurement


@dataclass
class Domains:
Expand Down Expand Up @@ -178,6 +182,19 @@ def commute_domains(self, domains: Domains) -> Domains:
raise RuntimeError(f"{gate} should be either I, H, S or Z.")
return Domains(s_domain, t_domain)

def to_opengraph(self) -> OpenGraph[PauliMeasurement]:
"""Return a local-Clifford-free open graph equivalent to the Clifford gate."""
from graphix import OpenGraph # noqa: PLC0415

decomposition = CLIFFORD_PAULI_DECOMPOSITION[self.value]
n = len(decomposition)
return OpenGraph(
graph=nx.path_graph(n + 1),
input_nodes=[0],
output_nodes=[n],
measurements=dict(enumerate(decomposition)),
)


Clifford.I = Clifford(0)
Clifford.X = Clifford(1)
Expand Down
49 changes: 47 additions & 2 deletions graphix/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
import dataclasses
import enum
import logging
from abc import ABC, abstractmethod
from enum import Enum
from typing import TYPE_CHECKING, ClassVar, Literal, TypeAlias

# override introduced in Python 3.12
from typing_extensions import override

from graphix import utils
from graphix.clifford import Clifford, Domains
from graphix.measurements import Measurement
Expand All @@ -16,6 +20,7 @@

if TYPE_CHECKING:
from collections.abc import Callable
from typing import Self

Node: TypeAlias = int

Expand Down Expand Up @@ -44,9 +49,13 @@ def __init_subclass__(cls) -> None:
utils.check_kind(cls, {"CommandKind": CommandKind, "Clifford": Clifford})


class BaseCommand(DataclassReprMixin):
class BaseCommand(ABC, DataclassReprMixin):
"""Base class for pattern command."""

@abstractmethod
def reindex(self, f: Callable[[Node], Node]) -> Self:
"""Return a command whose nodes have been reindexed using ``f``."""


@dataclasses.dataclass(repr=False)
class BaseN(BaseCommand):
Expand All @@ -70,6 +79,10 @@ class BaseN(BaseCommand):
node: int
kind: ClassVar[Literal[CommandKind.N]] = dataclasses.field(default=CommandKind.N, init=False)

@override
def reindex(self, f: Callable[[Node], Node]) -> Self:
return dataclasses.replace(self, node=f(self.node))


@dataclasses.dataclass(repr=False)
class N(BaseN, _KindChecker):
Expand Down Expand Up @@ -101,6 +114,10 @@ class `M`, with given plane, angles, and domains. The base class `BaseM` allows
node: Node
kind: ClassVar[Literal[CommandKind.M]] = dataclasses.field(default=CommandKind.M, init=False)

@override
def reindex(self, f: Callable[[Node], Node]) -> BaseM:
return BaseM(f(self.node))


@dataclasses.dataclass(repr=False)
class M(BaseM, _KindChecker):
Expand Down Expand Up @@ -156,10 +173,13 @@ def map(self, f: Callable[[Measurement], Measurement]) -> M:
-------
M
The resulting command.

"""
return M(self.node, f(self.measurement), self.s_domain, self.t_domain)

@override
def reindex(self, f: Callable[[Node], Node]) -> M:
return M(f(self.node), self.measurement, set(map(f, self.s_domain)), set(map(f, self.t_domain)))


@dataclasses.dataclass(repr=False)
class E(_KindChecker, BaseCommand):
Expand All @@ -174,6 +194,11 @@ class E(_KindChecker, BaseCommand):
nodes: tuple[Node, Node]
kind: ClassVar[Literal[CommandKind.E]] = dataclasses.field(default=CommandKind.E, init=False)

@override
def reindex(self, f: Callable[[Node], Node]) -> E:
u, v = self.nodes
return E((f(u), f(v)))


@dataclasses.dataclass(repr=False)
class C(_KindChecker, BaseCommand):
Expand All @@ -191,6 +216,10 @@ class C(_KindChecker, BaseCommand):
clifford: Clifford
kind: ClassVar[Literal[CommandKind.C]] = dataclasses.field(default=CommandKind.C, init=False)

@override
def reindex(self, f: Callable[[Node], Node]) -> C:
return C(f(self.node), self.clifford)


@dataclasses.dataclass(repr=False)
class X(_KindChecker, BaseCommand):
Expand All @@ -208,6 +237,10 @@ class X(_KindChecker, BaseCommand):
domain: set[Node] = dataclasses.field(default_factory=set)
kind: ClassVar[Literal[CommandKind.X]] = dataclasses.field(default=CommandKind.X, init=False)

@override
def reindex(self, f: Callable[[Node], Node]) -> X:
return X(f(self.node), set(map(f, self.domain)))


@dataclasses.dataclass(repr=False)
class Z(_KindChecker, BaseCommand):
Expand All @@ -225,6 +258,10 @@ class Z(_KindChecker, BaseCommand):
domain: set[Node] = dataclasses.field(default_factory=set)
kind: ClassVar[Literal[CommandKind.Z]] = dataclasses.field(default=CommandKind.Z, init=False)

@override
def reindex(self, f: Callable[[Node], Node]) -> Z:
return Z(f(self.node), set(map(f, self.domain)))


@dataclasses.dataclass(repr=False)
class S(_KindChecker, BaseCommand):
Expand All @@ -242,6 +279,10 @@ class S(_KindChecker, BaseCommand):
domain: set[Node] = dataclasses.field(default_factory=set)
kind: ClassVar[Literal[CommandKind.S]] = dataclasses.field(default=CommandKind.S, init=False)

@override
def reindex(self, f: Callable[[Node], Node]) -> S:
return S(f(self.node), set(map(f, self.domain)))


@dataclasses.dataclass(repr=False)
class T(_KindChecker, BaseCommand):
Expand All @@ -255,6 +296,10 @@ class T(_KindChecker, BaseCommand):

kind: ClassVar[Literal[CommandKind.T]] = dataclasses.field(default=CommandKind.T, init=False)

@override
def reindex(self, f: Callable[[Node], Node]) -> T:
return self


class Command:
"""Grouping of all commands for namespace exposure.
Expand Down
40 changes: 40 additions & 0 deletions graphix/optimization.py
Original file line number Diff line number Diff line change
Expand Up @@ -610,3 +610,43 @@ def decompose_domain(

new_pattern.reorder_output_nodes(pattern.output_nodes)
return new_pattern


def remove_local_clifford_commands(pattern: Pattern) -> Pattern:
Comment thread
matulni marked this conversation as resolved.
"""Return an equivalent pattern where local Clifford commands have been replaced by MBQC commands.

This function transpiles MBQC+LC patterns into MBQC patterns.
"""
from graphix.pattern import Pattern # noqa: PLC0415

nodes = pattern.extract_nodes()
if not nodes:
return pattern
max_node = max(nodes)
new_pattern = Pattern(input_nodes=pattern.input_nodes)
mapping: dict[Node, Node] = {}

def reindex(node: Node) -> Node:
return mapping.get(node, node)

for cmd in pattern:
match cmd.kind:
case CommandKind.C:
cmd_node = reindex(cmd.node)
clifford_pattern = cmd.clifford.to_opengraph().to_pattern()
(output_node,) = clifford_pattern.output_nodes
# We avoid using `new_pattern.compose` here because
# pattern composition is linear in the size of each
# pattern, which would make transpilation run in
# quadratic time.
# clifford_pattern satisfies the following properties:
# - The set of input nodes is {0}.
# - The output node is the highest-indexed node.
new_pattern.extend(clifford_pattern.reindex(lambda node: cmd_node if node == 0 else node + max_node)) # noqa: B023
Comment thread
thierry-martinez marked this conversation as resolved.
max_node += output_node
Comment thread
thierry-martinez marked this conversation as resolved.
mapping[cmd.node] = max_node
case _:
new_cmd = cmd.reindex(reindex)
new_pattern.add(new_cmd)
new_pattern.reorder_output_nodes(map(reindex, pattern.output_nodes))
Comment thread
thierry-martinez marked this conversation as resolved.
return new_pattern
Loading
Loading