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.
How to Read the QubitGraph String Representation
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 labelled0
, 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'
Initializing a QubitGraph
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))
Traversing the Hierarchical Graph Structure
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
andis_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
andchildren
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]>]
Attributes
Gets an iterator over the set of children QubitGraph objects.
Gets the set of edge labels in the underlying qubit graph.
Gets the underlying qubit graph.
Gets the QubitGraph ID.
Checks if the underlying qubits have been initialized.
Checks if this QubitGraph object is a leaf node in the hierarchical graph structure.
Checks if this QubitGraph object is a root node in the hierarchical graph structure.
Gets an iterator over all of the QubitGraph objects connected to this QubitGraph (its neighbors).
Gets the set of node labels in the underlying qubit graph.
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 theedges
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 thenodes
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
Methods
clear
()Clears the graph of underlying qubits.
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.
Initialize the QubitGraph's underlying qubits as the 17-qubit surface code graph from
- 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