ZXGraph#
- class tqec.computation.ZXGraph(name='')[source]#
Bases:
ComputationGraph
[ZXNode
,ZXEdge
]ZX graph representation of a logical computation.
This is a restricted form of the ZX diagram from the ZX-calculus. Currently, the restrictions include:
Every node is positioned at an integer 3D position.
Only nearest-neighbor nodes can be connected by an edge.
Nodes(spiders) are phase-free.
There is the additional
Y
kind spider representing the Y basis initialization/measurement. Traditionally, it is represented by a phased X/Z spider in the ZX-calculus.
The restrictions are needed because in the end, we need to convert the graph to a
BlockGraph
and compile to circuits. The block graph poses restrictions on the graph structure. An automatic compilation from a simplified or a more general ZX diagram to a block graph can relax some of the restrictions. But this is a future work.In principle, the rewriting rules from the ZX-calculus can be applied to simplify the graph and verify the functionality of the computation. This is not implemented yet but might be added in the future.
Methods
__init__
([name])add_edge
(u, v[, has_hadamard])Add an edge to the graph.
add_node
(node[, check_conflict])Add a node to the graph.
Check the invariants of a valid ZX graph.
copy
()Create a data-independent copy of the graph.
draw
(*[, figsize, title, node_size, ...])Plot the
ZXGraph
using matplotlib.edges_at
(position)Get the edges incident to a position.
fill_ports
(fill)Fill the ports at specified positions with nodes of the given kind.
Find all the
CorrelationSurface
in a ZX graph.get_degree
(position)Get the degree of a node in the graph, i.e. the number of edges incident to it.
get_edge
(pos1, pos2)Get the edge by its endpoint positions.
has_edge_between
(pos1, pos2)Check if there is an edge between two positions.
rotate
([rotation_axis, ...])Rotate the graph around an axis by
num_90_degree_rotation * 90
degrees and return a new rotated graph.to_block_graph
([name])Convert the ZX graph to a
BlockGraph
.Attributes
edges
The list of edges in the graph.
leaf_nodes
Get the leaf nodes of the graph, i.e. the nodes with degree 1.
name
Name of the graph.
nodes
The list of nodes in the graph.
num_edges
Number of edges in the graph.
num_nodes
Number of nodes in the graph.
num_ports
Number of ports in the graph.
ports
Mapping from port labels to their positions.
Detailed methods
- Parameters:
name (str)
- __init__(name='')#
- Parameters:
name (str)
- Return type:
None
- add_edge(u, v, has_hadamard=False)[source]#
Add an edge to the graph. If the nodes do not exist in the graph, the nodes will be created.
- Parameters:
- Raises:
TQECException – For each node in the edge, if there is already a node which is not equal to it at the same position, or the node is a port but there is already a different port with the same label in the graph.
- Return type:
None
- add_node(node, check_conflict=True)#
Add a node to the graph.
- Parameters:
node (_NODE) – The node to add to the graph.
check_conflict (bool) –
Whether to check for conflicts before adding the node. If set to True, either one of the following two circumstances will raise an exception:
There is already a node which is not equal to this one at the same position.
The node is a port but there is already a different port with the same label in the graph.
Otherwise, an existing node at the same position will be overwritten. Defaults to True.
- Raises:
TQECException – If
check_conflict
is True and there is a conflict when adding the node.- Return type:
None
- check_invariants()[source]#
Check the invariants of a valid ZX graph.
To represent a valid ZX graph, the graph must fulfill the following conditions:
The graph is a single connected component.
The ports and Y nodes are leaf nodes.
- Raises:
TQECException – If the invariants are not satisfied.
- Return type:
None
- copy()#
Create a data-independent copy of the graph.
- Return type:
Self
- draw(*, figsize=(5, 6), title=None, node_size=400, hadamard_size=200, edge_width=1, annotate_ports=True)[source]#
Plot the
ZXGraph
using matplotlib.- Parameters:
graph – The ZX graph to plot.
figsize (tuple[float, float]) – The figure size. Default is
(5, 6)
.title (str | None) – The title of the plot. Default to the name of the graph.
node_size (int) – The size of the node in the plot. Default is
400
.hadamard_size (int) – The size of the Hadamard square in the plot. Default is
200
.edge_width (int) – The width of the edge in the plot. Default is
1
.annotate_ports (bool) – Whether to annotate the ports if they are present. Default is
True
.
- Returns:
A tuple of the figure and the axes.
- Return type:
tuple[Figure, Axes3D]
- edges_at(position)#
Get the edges incident to a position.
- Parameters:
position (Position3D)
- Return type:
list[_EDGE]
- fill_ports(fill)[source]#
Fill the ports at specified positions with nodes of the given kind.
- Parameters:
fill (Mapping[str, ZXKind] | ZXKind) – A mapping from the label of the ports to the node kind to fill. If a single kind is given, all the ports will be filled with the same kind.
- Raises:
TQECException – if there is no port with the given label or the specified kind is
P
.- Return type:
None
- find_correlation_surfaces()[source]#
Find all the
CorrelationSurface
in a ZX graph.Starting from each leaf node in the graph, the function explores how can the X/Z logical observable move through the graph to form a correlation surface:
For a X/Z kind leaf node, it can only support the logical observable with the opposite type. Only a single type of logical observable is explored from the leaf node.
For a Y kind leaf node, it can only support the Y logical observable, i.e. the presence of both X and Z logical observable. Both X and Z type logical observable are explored from the leaf node. And the two correlation surfaces are combined to form the Y type correlation surface.
For the port node, it can support any type of logical observable. Both X and Z type logical observable are explored from the port node.
The function uses a flood fill like recursive algorithm to find the correlation surface in the graph. Firstly, we define two types of nodes in the graph:
broadcast node: A node that has seen logical observable with basis opposite to its own basis. A logical observable needs to be broadcasted to all the neighbors of the node.
passthrough node: A node that has seen logical observable with the same basis as its own basis. A logical observable needs to be only supported on an even number of edges connected to the node.
The algorithm starts from a set of frontier nodes and greedily expands the correlation surface until no more broadcast nodes are in the frontier. Then it explore the passthrough nodes, and select even number of edges to be included in the surface. If no such selection can be made, the search is pruned. For different choices, the algorithm recursively explores the next frontier until the search is completed. Finally, the branches at different nodes are produced to form the correlation surface.
- Returns:
A list of CorrelationSurface in the graph.
- Raises:
TQECException – If the graph does not contain any leaf node.
- Return type:
list[CorrelationSurface]
- get_degree(position)#
Get the degree of a node in the graph, i.e. the number of edges incident to it.
- Parameters:
position (Position3D)
- Return type:
int
- get_edge(pos1, pos2)#
Get the edge by its endpoint positions. If there is no edge between the given positions, an exception will be raised.
- Parameters:
pos1 (Position3D) – The first endpoint position.
pos2 (Position3D) – The second endpoint position.
- Returns:
The edge between the two positions.
- Raises:
TQECException – If there is no edge between the given positions.
- Return type:
_EDGE
- has_edge_between(pos1, pos2)#
Check if there is an edge between two positions.
- Parameters:
pos1 (Position3D) – The first endpoint position.
pos2 (Position3D) – The second endpoint position.
- Returns:
True if there is an edge between the two positions, False otherwise.
- Return type:
bool
- rotate(rotation_axis=Direction3D.Y, num_90_degree_rotation=1, counterclockwise=True)[source]#
Rotate the graph around an axis by
num_90_degree_rotation * 90
degrees and return a new rotated graph.- Parameters:
rotation_axis (Direction3D) – The axis around which to rotate the graph.
num_90_degree_rotation (int) – The number of 90-degree rotations to apply to the graph.
counterclockwise (bool) – Whether to rotate the graph counterclockwise. If set to False, the graph will be rotated clockwise. Defaults to True.
- Returns:
A data-independent copy of the graph rotated by the given number of 90-degree rotations.
- Return type:
- to_block_graph(name='')[source]#
Convert the ZX graph to a
BlockGraph
.Currently, the conversion respects the explicit 3D structure of the ZX graph. And convert node by node, edge by edge. Future work may include automatic compilation from a simplified or a more general ZX diagram to a block graph implementation.
To successfully convert a ZX graph to a block graph, the following necessary conditions must be satisfied:
The ZX graph itself is a valid representation, i.e.
check_invariants()
does not raise an exception.Every nodes in the ZX graph connects to edges at most in two directions, i.e. there is no 3D corner node.
Y nodes in the ZX graph can only have edges in time direction.
The conversion process is as follows:
Construct cubes for all the corner nodes in the ZX graph.
Construct pipes connecting ports/Y to ports/Y nodes.
Greedily construct the pipes until no more pipes can be inferred.
If there are still nodes left, then choose orientation for an arbitrary node and repeat step 3 and 4 until all nodes are handled or conflicts are detected.
- Parameters:
zx_graph – The ZX graph to be converted to a block graph.
name (str) – The name of the new block graph. If None, the name of the ZX graph will be used.
- Returns:
The
BlockGraph
object converted from the ZX graph.- Raises:
TQECException – If the ZX graph does not satisfy the necessary conditions or there are inference conflicts during the conversion.
- Return type: