Graphs#

Hiperwalk gives support to three different types of graphs: simples graphs, multigraphs and weighted graphs. There is a class for each of these types of graph: hiperwalk.Graph, hiperwalk.Multigraph, and hiperwalk.WeightedGraph, respectively.

Simple Graphs#

The hiperwalk.Graph class is used for handling simple graphs with self-loops.

You can create any graph by passing the adjacency matrix to the hiperwalk.Graph constructor. You can obtain the adjacency matrix using NetworkX’s networkx.adjacency_matrix() function. For instance, to generate the adjacency matrix of a ladder graph, you would execute the following commands:

>>> import networkx as nx
>>> nx_ladder = nx.ladder_graph(10)
>>> adj = nx.adjacency_matrix(nx_ladder)
>>> adj 
<20x20 sparse array of type '<class 'numpy.int64'>'
    with 56 stored elements in Compressed Sparse Row format>

Once you have the adjacency matrix, you can then create a Hiperwalk graph.

>>> import hiperwalk as hpw
>>> hpw_ladder = hpw.Graph(adj)
>>> hpw_ladder 
<hiperwalk.graph.graph.Graph object at 0x7f3bd0627eb0>

Alternativaly, you can create a Hiperwalk graph by passing an instance of networkx.Graph.

>>> hpw_ladder2 = hpw.Graph(nx_ladder)
>>> hpw_ladder2 
<hiperwalk.graph.graph.Graph object at 0x74f25e41d4e0>
>>> # checking if the adjacency matrices are equal
>>> nx_adj = nx.adjacency_matrix(nx_ladder)
>>> hpw_adj1 = hpw_ladder.adjacency_matrix()
>>> hpw_adj2 = hpw_ladder2.adjacency_matrix()
>>> (nx_adj - hpw_adj1).nnz == 0
True
>>> (hpw_adj1 - hpw_adj2).nnz == 0
True

After instantiating a Hiperwalk graph, it’s advisable to delete the NetworkX graph. This step is particularly important when dealing with large graphs, as it helps to prevent memory overload.

>>> del nx_ladder

Following this, you can use hpw_ladder to simulate any quantum walk, regardless of the model.

Order of Neighbors#

By default, the neighbors of a vertex are listed in ascending order.

>>> hpw_ladder.neighbors(1)
array([ 0,  2, 11])

Depending on the graph, you may wish to specify a different order of neighbors. This is particularly useful for the coined quantum walk model (hiperwalk.Coined). To specify an order of neighbors, create the adjacency matrix using scipy.sparse.csr_array such that scipy.sparse.csr_array.has_sorted_indices is False. This can be done by expliciting scipy.sparse.csr_array’s indices. Then, the neighbors of u are listed in the order indices[indptr[u]:indptr[u+1]]. For example, the following commands create a complete graph with self-loops where the neighbors of u are listed in the order [u, u+1, ..., u-1].

>>> import scipy.sparse
>>> # creating the csr_array
>>> num_vert = 5
>>> data = np.ones(num_vert**2)
>>> indices = [(u + shift) % num_vert
...            for u in range(num_vert)
...            for shift in range(num_vert)]
>>> indptr = np.arange(0, num_vert**2 + 1, num_vert)
>>> adj_matrix = scipy.sparse.csr_array((data, indices, indptr))
>>> # creating graph with non-default order of neighbors
>>> g = hpw.Graph(adj_matrix)
>>> # testing the order of neighbors
>>> for u in range(num_vert):
...     print(g.neighbors(u))
[0 1 2 3 4]
[1 2 3 4 0]
[2 3 4 0 1]
[3 4 0 1 2]
[4 0 1 2 3]

Multigraphs#

You can create a multigraph by passing its adjacency matrix. The adjacency matrix entries are the number of edges simultaneously incident to pairs of vertices.

>>> # creating the adjacency matrix of a complete multigraph
>>> num_vert = 5
>>> adj_matrix = np.zeros((num_vert, num_vert))
>>> for i in range(num_vert):
...     for j in range(num_vert):
...         adj_matrix[i, j] = i + j + 1
...
>>> # creating multigraph
>>> g = hpw.Multigraph(adj_matrix)
>>> # checking if multigraph was created properly
>>> np.all(np.array(
...         [g.number_of_edges(u, v) == u + v + 1
...         for u in range(num_vert)
...         for v in range(num_vert)]) == True)
True

Multigraphs are used to create coined quantum walks (hiperwalk.Coined).

Order of Neighbors#

To specify a non-default order of neighbors, you can follow the same steps used for simple graphs. But be aware that the values of data must be rearranged with respect to indices.

>>> # creating data with the appropriate values
>>> data = [u + v + 1 for u in range(num_vert)
...         for v in indices[indptr[u]:indptr[u+1]]]
>>> adj_matrix = scipy.sparse.csr_array((data, indices, indptr))
>>> # create multigraph with desired order of neighbors
>>> g = hpw.Multigraph(adj_matrix)
>>> for u in range(num_vert):
...     print(g.neighbors(u))
[0 1 2 3 4]
[1 2 3 4 0]
[2 3 4 0 1]
[3 4 0 1 2]
[4 0 1 2 3]
>>> # checking if multigraph was created properly
>>> np.all(np.array(
...         [g.number_of_edges(u, v) == u + v + 1
...         for u in range(num_vert)
...         for v in range(num_vert)]) == True)
True

Weighted Graphs#

You can create a weighted graph by passing its adjacency matrix. The adjacency matrix entries are real values that represent the edge weights.

>>> # creating the adjacency matrix of a complete weighted graph
>>> num_vert = 5
>>> adj_matrix = np.zeros((num_vert, num_vert))
>>> for i in range(num_vert):
...     for j in range(num_vert):
...         adj_matrix[i, j] = (i + j)/10
...
>>> # creating weighted
>>> g = hpw.WeightedGraph(adj_matrix)
>>> adj_matrix2 = g.adjacency_matrix().todense()
>>> np.all(np.isclose(adj_matrix, adj_matrix2))
True

Weighted graphs are used to create continuous-time quantum walks (hiperwalk.ContinuousTime).

Order of Neighbors#

The order of neighbors does not play a crucial role in the continuous-time quantum walk. Even so, you can specify the order of neighbors in weighted graphs using commands akin to those used for multigraphs.