qml.ftqc.QubitGraph

class QubitGraph(graph=None, id=None)[source]

Bases: object

A class to represent a hierarchical qubit memory model as nested graphs of qubits.

A QubitGraph is a qubit that contains a graph of underlying qubits, where each underlying qubit is itself a QubitGraph. This representation allows for nesting of lower-level qubits with arbitrary depth to allow easy insertion of arbitrarily many levels of abstractions between logical qubits and physical qubits.

Parameters
  • graph (graph-like, optional) – The graph structure to use for the QubitGraph’s underlying qubits. The graph must be “flat” (unnested). An object is considered “graph-like” if it has both a ‘nodes’ and an ‘edges’ attribute. Defaults to None, which leaves the QubitGraph in an uninitialized state.

  • id (Any, optional) – An identifier for this QubitGraph object. The identifier is generally an integer, string, or a tuple of integers and strings, but it may be any object. Inputting None (the default), assigns a random universally unique identifier (uuid) to id.

Note

The input graph defines the structure of the underlying qubit graph only; only the set of nodes and the set of edges defining the connectivity between nodes are used to construct the underlying qubit graph. Any data contained within the input graph’s nodes, including nested graphs, are ignored.

Note

QubitGraph expects an undirected graph as input, specifically an instance of the networkx.Graph class, although other NetworkX graphs and graph-like types are also permitted.

Examples

Create a QubitGraph (with id=0) using a 2x2 Cartesian grid to define the structure of its underlying qubits:

>>> import networkx as nx
>>> from pennylane.ftqc import QubitGraph
>>> q = QubitGraph(nx.grid_graph((2,2)), id=0)
>>> q
QubitGraph<id=0, loc=[]>
>>> for child in q.children:
...     print(child)
QubitGraph<id=(0, 0), loc=[0]>
QubitGraph<id=(0, 1), loc=[0]>
QubitGraph<id=(1, 0), loc=[0]>
QubitGraph<id=(1, 1), loc=[0]>

Using the QubitGraph object defined above as a starting point, it’s possible to nest other QubitGraph objects in any of the nodes of its underlying qubit graph:

>>> q[(0,0)].init_graph(nx.grid_graph((2,)))
>>> q[(0,0)]
QubitGraph<id=(0, 0), loc=[0]>
>>> for child in q[(0,0)].children:
...     print(child)
QubitGraph<id=0, loc=[(0, 0), 0]>
QubitGraph<id=1, loc=[(0, 0), 0]>

Notice that you do not have to provide an ID when constructing the QubitGraph; when a QubitGraph object is assigned to the node of another QubitGraph, its ID takes on the label of the node it was assigned to.

The examples above showed that any QubitGraph in the hierarchical structure can be displayed with information on its ID and its location in the hierarchy. For example, the string representation

QubitGraph<id=1, loc=[(0, 0), 0]>

indicates that this QubitGraph has an ID of 1, and it is nested within a parent node labelled (0, 0), which is nested within a parent node labelled 0, which is a root node. If a QubitGraph is itself a root node (meaning it is not nested within a parent QubitGraph), its location is displayed as an empty list, for example:

QubitGraph<id=0, loc=[]>

If no ID parameter was given upon construction of the QubitGraph, you will notice that a random UUID has been assigned to it:

>>> q = QubitGraph()
>>> q
QubitGraph<id=7491161c, loc=[]>

This ID is truncated for brevity; the full ID is a 32-digit hexadecimal number:

>>> q.id
'7491161c-ca7e-42cc-af3e-8ca6250a370e'

The examples above showed how to initialize the graph structure of a QubitGraph by passing in a NetworkX graph. It is also possible to construct a QubitGraph object with no graph structure:

>>> q = QubitGraph(id=0)
>>> q
QubitGraph<id=0, loc=[]>

In this case, the QubitGraph is still valid for use, but it is in an uninitialized state. You can check the initialization state of a QubitGraph with its is_initialized attribute:

>>> q.is_initialized
False

You may not want to initialize a graph of underlying qubits when representing physical qubits at the hardware layer, for example.

An uninitialized QubitGraph can always be initialized later using one of its graph-initialization methods. The most general of these is init_graph(), which accepts an arbitrary NetworkX graph as input:

>>> graph = nx.grid_graph((2,))
>>> q.init_graph(graph)
>>> q.is_initialized
True

The init_graph() method behaves in the same way as passing the graph input directly to the QubitGraph constructor, and the same requirements and caveats listed above apply here as well (the input must be graph-like and any data annotated on the nodes are ignored).

Other graph-initialization methods that automatically construct common graph structures are also available. For example, init_graph_nd_grid() initializes a QubitGraph’s underlying qubits as an n-dimensional Cartesian grid:

>>> q_3d_grid = QubitGraph(id=0)
>>> q_3d_grid.init_graph_nd_grid((3, 4, 5))

The graph hierarchy is structured as a tree data type, with “root” nodes representing the highest-level qubits (for example, the logical qubits in a quantum circuit), down to “leaf” nodes representing the lowest-level qubits (for example, the physical qubits at the hardware level). The is_root and is_leaf attributes are available to quickly determine if a QubitGraph is a root or leaf node, respectively. Consider the following example with a three-layer nesting structure:

>>> single_node_graph = nx.grid_graph((1,))
>>> q = QubitGraph(single_node_graph, id=0)
>>> q[0].init_graph(single_node_graph)
>>> print(f"is root? {q.is_root}, is leaf? {q.is_leaf}")
is root? True, is leaf? False
>>> print(f"is root? {q[0].is_root}, is leaf? {q[0].is_leaf}")
is root? False, is leaf? False
>>> print(f"is root? {q[0][0].is_root}, is leaf? {q[0][0].is_leaf}")
is root? False, is leaf? True

The parent and children attributes are also available to access the parent of a given QubitGraph and its set of children, respectively:

>>> q[0].parent is q
True
>>> q[0][0].parent is q[0]
True
>>> list(q[0].children)
[QubitGraph<id=0, loc=[0, 0]>]

children

Gets an iterator over the set of children QubitGraph objects.

edge_labels

Gets the set of edge labels in the underlying qubit graph.

graph

Gets the underlying qubit graph.

id

Gets the QubitGraph ID.

is_initialized

Checks if the underlying qubits have been initialized.

is_leaf

Checks if this QubitGraph object is a leaf node in the hierarchical graph structure.

is_root

Checks if this QubitGraph object is a root node in the hierarchical graph structure.

neighbors

Gets an iterator over all of the QubitGraph objects connected to this QubitGraph (its neighbors).

node_labels

Gets the set of node labels in the underlying qubit graph.

parent

Gets the parent QubitGraph of this QubitGraph object.

children

Gets an iterator over the set of children QubitGraph objects.

To access the node labels of the underlying qubit graph, rather than the QubitGraph objects themselves, use the node_labels attribute.

Yields

QubitGraph – The next QubitGraph object in the set of children QubitGraphs.

Example

>>> q = QubitGraph(nx.grid_graph((2,)), id=0)
>>> set(q.node_labels)
{0, 1}
>>> list(q.children)
[QubitGraph<id=0, loc=[0]>, QubitGraph<id=1, loc=[0]>]
edge_labels

Gets the set of edge labels in the underlying qubit graph.

If the underlying qubit graph has not been initialized, emit a UserWarning and return None.

Accessing QubitGraph.edges_labels is equivalent to accessing the edges attribute of the NetworkX graph:

>>> g = nx.grid_graph((2,))
>>> g.edges
EdgeView([(0, 1)])
>>> q = QubitGraph(g, id=0)
>>> q.edge_labels
EdgeView([(0, 1)])
Returns

The set of edges, with native support for operations such as len(g.edges), e in g.edges, g.edges & h.edges, etc. See the NetworkX documentation for more information.

Return type

networkx.EdgeView

graph

Gets the underlying qubit graph.

id

Gets the QubitGraph ID.

is_initialized

Checks if the underlying qubits have been initialized.

The underlying qubit graph is considered uninitialized if and only if it is NoneType. A QubitGraph consisting of a null graph (one with zero nodes) is considered initialized. A QubitGraph may be uninitialized if it is a leaf node in the hierarchical graph structure.

Returns

Returns True if the underlying qubits have been initialized, False otherwise.

Return type

bool

is_leaf

Checks if this QubitGraph object is a leaf node in the hierarchical graph structure.

A QubitGraph node is a leaf when it has no underlying qubit graph, either if the underlying qubit graph has not been initialized (i.e. it is NoneType) or if the underlying qubits graph has been initialized but is a null graph (one with zero nodes).

Returns

Returns True if this QubitGraph object is a leaf node.

Return type

bool

is_root

Checks if this QubitGraph object is a root node in the hierarchical graph structure.

A QubitGraph node is a root when it has no parent QubitGraph object.

Returns

Returns True if this QubitGraph object is a root node.

Return type

bool

neighbors

Gets an iterator over all of the QubitGraph objects connected to this QubitGraph (its neighbors).

A QubitGraph does not have to be initialized for it to have neighbors. Similarly, a root-level QubitGraph does not have any neighboring qubits, by construction.

Yields

QubitGraph – The next QubitGraph object in the set of neighboring QubitGraphs.

Example

>>> q = QubitGraph(nx.grid_graph((2, 2)), id=0)
>>> list(q[(0,0)].neighbors)
[QubitGraph<id=(1, 0), loc=[0]>, QubitGraph<id=(0, 1), loc=[0]>]
node_labels

Gets the set of node labels in the underlying qubit graph.

If the underlying qubit graph has not been initialized, emit a UserWarning and return None.

Accessing QubitGraph.node_labels is equivalent to accessing the nodes attribute of the NetworkX graph:

>>> g = nx.grid_graph((2,))
>>> g.nodes
NodeView((0, 1))
>>> q = QubitGraph(g, id=0)
>>> q.node_labels
NodeView((0, 1))

To access the underlying QubitGraph objects, rather than their labels, use the children attribute.

Returns

A view of the set of nodes, with native support for operations such as len(g.nodes), n in g.nodes, g.nodes & h.nodes, etc. See the NetworkX documentation for more information.

Return type

networkx.NodeView

parent

Gets the parent QubitGraph of this QubitGraph object.

Returns

The parent QubitGraph object.

Return type

QubitGraph

clear()

Clears the graph of underlying qubits.

has_cycle()

Checks if the QubitGraph contains a cycle in its nesting structure.

init_graph(graph)

Initialize the QubitGraph's underlying qubits with the given graph.

init_graph_2d_grid(m, n)

Initialize the QubitGraph's underlying qubits as a 2-dimensional Cartesian grid of other QubitGraphs.

init_graph_nd_grid(dim)

Initialize the QubitGraph's underlying qubits as an n-dimensional Cartesian grid of other QubitGraphs.

init_graph_surface_code_17()

Initialize the QubitGraph's underlying qubits as the 17-qubit surface code graph from

clear()[source]

Clears the graph of underlying qubits.

has_cycle()[source]

Checks if the QubitGraph contains a cycle in its nesting structure.

This method uses Floyd’s cycle-finding algorithm (also known as Floyd’s tortoise and hare algorithm) by iterating up through the QubitGraphs’ parents.

Returns

Returns True if this QubitGraph has a cycle in its nesting structure.

Return type

bool

init_graph(graph)[source]

Initialize the QubitGraph’s underlying qubits with the given graph.

Parameters

graph (networkx.Graph) – The undirected graph to use as the QubitGraph’s underlying qubits. This object must not be None.

Example

This example creates a NetworkX graph with two nodes, labelled 0 and 1, and one edge between them, and uses this graph to initialize the graph structure of a QubitGraph:

>>> import networkx as nx
>>> graph = nx.Graph()
>>> graph.add_edge(0, 1)
>>> q = QubitGraph(id=0)
>>> q.init_graph(graph)
>>> list(q.children)
[QubitGraph<id=0, loc=[0]>, QubitGraph<id=1, loc=[0]>]
init_graph_2d_grid(m, n)[source]

Initialize the QubitGraph’s underlying qubits as a 2-dimensional Cartesian grid of other QubitGraphs.

Parameters
  • m (int) – The number of rows in the grid.

  • n (int) – The number of columns in the grid.

Example

This example initializes the underlying qubits as a 2x3 2-dimensional Cartesian grid with graph structure and qubit indexing below:

(0,0) --- (0,1) --- (0,2)
  |         |         |
(1,0) --- (1,1) --- (1,2)
>>> q = QubitGraph(id=0)
>>> q.init_graph_2d_grid(2, 3)
>>> list(q.children)
[QubitGraph<id=(0, 0), loc=[0]>, QubitGraph<id=(0, 1), loc=[0]>,
QubitGraph<id=(0, 2), loc=[0]>, QubitGraph<id=(1, 0), loc=[0]>,
QubitGraph<id=(1, 1), loc=[0]>, QubitGraph<id=(1, 2), loc=[0]>]
init_graph_nd_grid(dim)[source]

Initialize the QubitGraph’s underlying qubits as an n-dimensional Cartesian grid of other QubitGraphs.

Parameters

dim (list or tuple of ints) – The size of each dimension.

Example

This example initializes the underlying qubits as a 2x2x3 3-dimensional Cartesian grid with graph structure and qubit indexing below:

      (2,0,0) ------------- (2,0,1)
     /|                    /|
   (1,0,0) ------------- (1,0,1)
  /|  |                 /|  |
(0,0,0) ------------- (0,0,1)
|  |  |               |  |  |
|  |  (2,1,0) --------|--|- (2,1,1)
|  | /                |  | /
|  (1,1,0) -----------|- (1,1,1)
| /                   | /
(0,1,0) ------------- (0,1,1)
>>> q = QubitGraph(id=0)
>>> q.init_graph_nd_grid((2, 2, 3))
>>> list(q.children)
[QubitGraph<id=(0, 0, 0), loc=[0]>, QubitGraph<id=(0, 0, 1), loc=[0]>,
QubitGraph<id=(0, 1, 0), loc=[0]>, QubitGraph<id=(0, 1, 1), loc=[0]>,
QubitGraph<id=(1, 0, 0), loc=[0]>, QubitGraph<id=(1, 0, 1), loc=[0]>,
QubitGraph<id=(1, 1, 0), loc=[0]>, QubitGraph<id=(1, 1, 1), loc=[0]>,
QubitGraph<id=(2, 0, 0), loc=[0]>, QubitGraph<id=(2, 0, 1), loc=[0]>,
QubitGraph<id=(2, 1, 0), loc=[0]>, QubitGraph<id=(2, 1, 1), loc=[0]>]
init_graph_surface_code_17()[source]

Initialize the QubitGraph’s underlying qubits as the 17-qubit surface code graph from

Y. Tomita & K. Svore, 2014, Low-distance Surface Codes under Realistic Quantum Noise. arXiv:1404.3747.

This graph structure is commonly referred to as the “ninja star” surface code given its shape.

The nodes are indexed as follows, where ‘d’ refers to data qubits and ‘a’ to auxiliary qubits:

              a9
             /   \
    d0     d1     d2
   /  \   /  \   /
a10    a11    a12
   \  /   \  /   \
    d3     d4     d5
      \   /  \   /  \
       a13    a14    a15
      /   \  /   \  /
    d6     d7     d8
      \   /
       a16