"""Utilities for generating certain graph shapes.
"""
import math
import networkx as nx
import numpy as np
# Following GraphWave's representation of structural similarity
[docs]def clique(start, nb_nodes, nb_to_remove=0, role_start=0):
""" Defines a clique (complete graph on nb_nodes nodes,
with nb_to_remove edges that will have to be removed),
index.rst of nodes starting at start
and role_ids at role_start
INPUT:
-------------
start : starting index.rst for the shape
nb_nodes : int correspondingraph to the nb of nodes in the clique
role_start : starting index.rst for the roles
nb_to_remove: int-- numb of edges to remove (unif at RDM)
OUTPUT:
-------------
graph : a house shape graph, with ids beginning at start
roles : list of the roles of the nodes (indexed starting at
role_start)
"""
a = np.ones((nb_nodes, nb_nodes))
np.fill_diagonal(a, 0)
graph = nx.from_numpy_matrix(a)
edge_list = graph.edges().keys()
roles = [role_start] * nb_nodes
if nb_to_remove > 0:
lst = np.random.choice(len(edge_list), nb_to_remove, replace=False)
# print(edge_list, lst)
to_delete = [edge_list[e] for e in lst]
graph.remove_edges_from(to_delete)
for e in lst:
# print(edge_list[e][0])
# print(len(roles))
roles[edge_list[e][0]] += 1
roles[edge_list[e][1]] += 1
mapping_graph = {k: (k + start) for k in range(nb_nodes)}
graph = nx.relabel_nodes(graph, mapping_graph)
return graph, roles
[docs]def cycle(start, len_cycle, role_start=0):
"""Builds a cycle graph, with index.rst of nodes starting at start
and role_ids at role_start
INPUT:
-------------
start : starting index.rst for the shape
role_start : starting index.rst for the roles
OUTPUT:
-------------
graph : a house shape graph, with ids beginning at start
roles : list of the roles of the nodes (indexed starting at
role_start)
"""
graph = nx.Graph()
graph.add_nodes_from(range(start, start + len_cycle))
for i in range(len_cycle - 1):
graph.add_edges_from([(start + i, start + i + 1)])
graph.add_edges_from([(start + len_cycle - 1, start)])
roles = [1 for _ in range(len_cycle)]
return graph, roles
[docs]def diamond(start, role_start=0):
"""Builds a diamond graph, with index.rst of nodes starting at start
and role_ids at role_start
INPUT:
-------------
start : starting index.rst for the shape
role_start : starting index.rst for the roles
OUTPUT:
-------------
graph : a house shape graph, with ids beginning at start
roles : list of the roles of the nodes (indexed starting at
role_start)
"""
graph = nx.Graph()
graph.add_nodes_from(range(start, start + 6))
graph.add_edges_from(
[
(start, start + 1),
(start + 1, start + 2),
(start + 2, start + 3),
(start + 3, start),
]
)
graph.add_edges_from(
[
(start + 4, start),
(start + 4, start + 1),
(start + 4, start + 2),
(start + 4, start + 3),
]
)
graph.add_edges_from(
[
(start + 5, start),
(start + 5, start + 1),
(start + 5, start + 2),
(start + 5, start + 3),
]
)
roles = [role_start] * 6
return graph, roles
[docs]def tree(start, height, r=2, role_start=0):
"""Builds a balanced r-tree of height h
INPUT:
-------------
start : starting index.rst for the shape
height : int height of the tree
r : int number of branches per node
role_start : starting index.rst for the roles
OUTPUT:
-------------
graph : a tree shape graph, with ids beginning at start
roles : list of the roles of the nodes (indexed starting at role_start)
"""
# import random
# r = random.randint(2, 3)
graph = nx.balanced_tree(r, height)
roles = [0] * graph.number_of_nodes()
return graph, roles
[docs]def ba(start, width, role_start=0, m=5):
"""Builds a BA preferential attachment graph, with index.rst of nodes starting at start
and role_ids at role_start
INPUT:
-------------
start : starting index.rst for the shape
width : int size of the graph
role_start : starting index.rst for the roles
OUTPUT:
-------------
graph : a house shape graph, with ids beginning at start
roles : list of the roles of the nodes (indexed starting at
role_start)
"""
graph = nx.barabasi_albert_graph(width, m)
graph.add_nodes_from(range(start, start + width))
nids = sorted(graph)
mapping = {nid: start + i for i, nid in enumerate(nids)}
graph = nx.relabel_nodes(graph, mapping)
roles = [role_start for i in range(width)]
return graph, roles
[docs]def wheel(start, width, role_start=0, m=5):
graph = nx.wheel_graph(width)
roles = [0] * graph.number_of_nodes()
return graph, roles
[docs]def ladder(start, width, role_start=0, m=5):
graph = nx.ladder_graph(width)
roles = [0] * graph.number_of_nodes()
return graph, roles
[docs]def house(start, role_start=0):
"""Builds a house-like graph, with index.rst of nodes starting at start
and role_ids at role_start
INPUT:
-------------
start : starting index.rst for the shape
role_start : starting index.rst for the roles
OUTPUT:
-------------
graph : a house shape graph, with ids beginning at start
roles : list of the roles of the nodes (indexed starting at
role_start)
"""
graph = nx.Graph()
graph.add_nodes_from(range(start, start + 5))
graph.add_edges_from(
[
(start + 1, start + 2),
(start + 4, start + 3),
(start + 3, start + 2),
(start + 4, start + 1),
]
)
# graph.add_edges_from([(start, start + 2), (start + 1, start + 3)])
graph.add_edges_from([(start, start + 1), (start, start + 4)])
roles = [role_start, role_start + 1, role_start + 2, role_start + 2, role_start + 1]
return graph, roles
[docs]def fan(start, nb_branches, role_start=0):
"""Builds a fan-like graph, with index.rst of nodes starting at start
and role_ids at role_start
INPUT:
-------------
nb_branches : int correspondingraph to the nb of fan branches
start : starting index.rst for the shape
role_start : starting index.rst for the roles
OUTPUT:
-------------
graph : a house shape graph, with ids beginning at start
roles : list of the roles of the nodes (indexed starting at
role_start)
"""
graph, roles = star(start, nb_branches, role_start=role_start)
for k in range(1, nb_branches - 1):
roles[k] += 1
roles[k + 1] += 1
graph.add_edges_from([(start + k, start + k + 1)])
return graph, roles
[docs]def varcycle(start, role_start=0):
graph = nx.Graph()
graph.add_nodes_from(range(start, start + 5))
graph.add_edges_from(
[
(start, start + 1),
(start + 1, start + 2),
(start + 3, start + 2),
(start, start + 4),
(start + 4, start + 3),
(start, start + 2),
(start, start + 3),
]
)
roles = [role_start, role_start + 1, role_start + 2, role_start + 2, role_start + 1]
return graph, roles
[docs]def crane(start, role_start=0):
"""Builds a house-like graph, with index.rst of nodes starting at start
and role_ids at role_start
INPUT:
-------------
start : starting index.rst for the shape
role_start : starting index.rst for the roles
OUTPUT:
-------------
graph : a house shape graph, with ids beginning at start
roles : list of the roles of the nodes (indexed starting at
role_start)
"""
graph = nx.Graph()
graph.add_nodes_from(range(start, start + 5))
graph.add_edges_from(
[
(start + 1, start + 2),
(start + 2, start + 3),
(start + 4, start + 3),
(start + 1, start + 4),
]
)
# graph.add_edges_from([(start, start + 2), (start + 1, start + 3)])
graph.add_edges_from([(start, start + 1), (start, start + 3)])
roles = [role_start, role_start + 1, role_start + 2, role_start + 1, role_start + 2]
return graph, roles
[docs]def dircycle(start, role_start=0):
graph = nx.Graph()
graph.add_nodes_from(range(start, start + 5))
graph.add_edges_from(
[
(start, start + 1),
(start + 1, start + 2),
(start + 3, start + 2),
(start, start + 4),
(start + 4, start + 3),
]
)
roles = [role_start, role_start + 1, role_start + 2, role_start + 2, role_start + 1]
return graph, roles
[docs]def crossgrid(start, role_start=0):
"""Builds a house-like graph, with index.rst of nodes starting at start
and role_ids at role_start
INPUT:
-------------
start : starting index.rst for the shape
role_start : starting index.rst for the roles
OUTPUT:
-------------
graph : a house shape graph, with ids beginning at start
roles : list of the roles of the nodes (indexed starting at
role_start)
"""
graph = nx.Graph()
graph.add_nodes_from(range(start, start + 4))
graph.add_edges_from(
[
(start, start + 1),
(start + 1, start + 2),
(start + 2, start + 3),
(start + 3, start),
]
)
# graph.add_edges_from([(start, start + 2), (start + 1, start + 3)])
graph.add_edges_from([(start, start + 2), (start + 1, start + 3)])
roles = [1 for _ in range(4)] # [role_start for i in range(4)]
return graph, roles
[docs]def star(start, nb_branches, role_start=0):
"""Builds a star graph, with index.rst of nodes starting at start
and role_ids at role_start
INPUT:
-------------
nb_branches : int correspondingraph to the nb of star branches
start : starting index.rst for the shape
role_start : starting index.rst for the roles
OUTPUT:
-------------
graph : a house shape graph, with ids beginning at start
roles : list of the roles of the nodes (indexed starting at
role_start)
"""
graph = nx.Graph()
graph.add_nodes_from(range(start, start + nb_branches + 1))
for k in range(1, nb_branches + 1):
graph.add_edges_from([(start, start + k)])
roles = [role_start + 1] * (nb_branches + 1)
roles[0] = role_start
return graph, roles
[docs]def path(start, width, role_start=0):
"""Builds a path graph, with index.rst of nodes starting at start
and role_ids at role_start
INPUT:
-------------
start : starting index.rst for the shape
width : int length of the path
role_start : starting index.rst for the roles
OUTPUT:
-------------
graph : a house shape graph, with ids beginning at start
roles : list of the roles of the nodes (indexed starting at
role_start)
"""
graph = nx.Graph()
graph.add_nodes_from(range(start, start + width))
for i in range(width - 1):
graph.add_edges_from([(start + i, start + i + 1)])
roles = [role_start] * width
roles[0] = role_start + 1
roles[-1] = role_start + 1
return graph, roles
[docs]def build_graph(
width_basis,
basis_type,
list_shapes,
start=0,
rdm_basis_plugins=False,
add_random_edges=0,
m=5,
):
"""This function creates a basis (scale-free, path, or cycle)
and attaches elements of the type in the list randomly along the basis.
Possibility to add random edges afterwards.
INPUT:
--------------------------------------------------------------------------------------
width_basis : width (in terms of number of nodes) of the basis
basis_type : (torus, string, or cycle)
shapes : list of shape list (1st arg: type of shape,
next args:args for building the shape,
except for the start)
start : initial nb for the first node
rdm_basis_plugins: boolean. Should the shapes be randomly placed
along the basis (True) or regularly (False)?
add_random_edges : nb of edges to randomly add on the structure
m : number of edges to attach to existing node (for BA graph)
OUTPUT:
--------------------------------------------------------------------------------------
basis : a nx graph with the particular shape
role_ids : labels for each role
plugins : node ids with the attached shapes
"""
if basis_type == "ba":
basis, role_id = eval(basis_type)(start, width_basis, m=m)
else:
basis, role_id = eval(basis_type)(start, width_basis)
n_basis, n_shapes = nx.number_of_nodes(basis), len(list_shapes)
start += n_basis # indicator of the id of the next node
# Sample (with replacement) where to attach the new motifs
if rdm_basis_plugins is True:
plugins = np.random.choice(n_basis, n_shapes, replace=False)
else:
spacing = math.floor(n_basis / n_shapes)
plugins = [int(k * spacing) for k in range(n_shapes)]
seen_shapes = {"basis": [0, n_basis]}
for shape_id, shape in enumerate(list_shapes):
shape_type = shape[0]
args = [start]
if len(shape) > 1:
args += shape[1:]
args += [0]
graph_s, roles_graph_s = eval(shape_type)(*args)
n_s = nx.number_of_nodes(graph_s)
try:
col_start = seen_shapes[shape_type][0]
except:
col_start = np.max(role_id) + 1
seen_shapes[shape_type] = [col_start, n_s]
# Attach the shape to the basis
basis.add_nodes_from(graph_s.nodes())
basis.add_edges_from(graph_s.edges())
basis.add_edges_from([(start, plugins[shape_id])])
temp_labels = [r + col_start for r in roles_graph_s]
role_id += temp_labels
start += n_s
if add_random_edges > 0:
# add random edges between nodes:
for p in range(add_random_edges):
src, dest = np.random.choice(nx.number_of_nodes(basis), 2, replace=False)
basis.add_edges_from([(src, dest)])
return basis, role_id, plugins