"""Graph generation."""
import itertools
import matplotlib.pyplot as plt
import networkx as nx
import numpy as np
import xgi
from scipy import spatial
from simplicial_kuramoto import SimplicialComplex
# pylint: disable=too-many-nested-blocks,too-many-branches
[docs]def modular_graph(Nc, Nn, Nie, rando=True, inter_weight=0.5, intra_weight=0.5):
"""Creates a modular network.
The network is constructed with Nc clique modules of size Nn and all connected
by Nie edges, in a linear way.
Args:
Nc: number of modules
Nn: number of nodes per module
Nie: number of edges between modules (added linearly), has to be smaller than Nn(Nn-1)/2
"""
G = nx.Graph()
G.add_nodes_from(np.linspace(1, Nc * Nn, Nc * Nn).astype(int).tolist())
node_assign = {}
# fully connected modules
for i in range(Nc):
for j in range(1, Nn + 1):
for k in range(j + 1, Nn + 1):
G.add_edge(
(i * Nn) + j,
(i * Nn) + k,
weight=intra_weight + np.random.normal(0, 0.1),
community=str((i, i)),
)
node_assign[(i * Nn) + j] = str(i)
node_assign[(i * Nn) + k] = str(i)
if rando:
for i in range(Nc):
for j in range(i + 1, Nc):
source = np.random.randint(i * Nn + 1, (i + 1) * Nn, Nie).tolist()
sink = np.random.randint(j * (Nn + 1) + 1, (j + 1) * Nn, Nie).tolist()
for e in range(Nie):
G.add_edge(source[e], sink[e], weight=inter_weight, community=str((i, j)))
else:
Neig = np.linspace(1, Nn, Nn).astype(int)
if Nie > 0:
nr, bonus = np.divmod(Nie, Nn)
for c1 in range(Nc):
for c2 in range(c1 + 1, Nc):
for i in range(nr):
for j in range(Nn):
G.add_edge(
Neig.tolist()[j] + c1 * Nn,
np.roll(Neig, -i).tolist()[j] + c2 * Nn,
weight=inter_weight,
community=str((c1, c2)),
)
for j in range(bonus):
a = Neig.tolist()[j] + c1 * Nn
b = np.roll(Neig, -(nr)).tolist()[j] + c2 * Nn
# this trick below is to get a symetric 3-module graph
if c1 == 0 and b == 11:
b = 12
elif c1 == 0 and b == 12: # and ok:
b = 11
G.add_edge(
a,
b,
weight=inter_weight + np.random.normal(0, 0.1),
community=str((c1, c2)),
)
nx.set_node_attributes(G, node_assign, "community")
return G
[docs]def ring_of_rings(num_rings, ring_size):
"""Create ring of rings network."""
G = nx.Graph()
for i in range(num_rings):
gc = nx.generators.classic.circulant_graph(ring_size, [1])
edges = gc.edges()
for edge in edges:
edge_ = [(i * ring_size) + x for x in edge]
G.add_edge(edge_[0], edge_[1], community=i)
G.add_edge(
i * ring_size + 1,
(i + 1) * ring_size % (num_rings * ring_size),
community=999,
)
return G
[docs]def delaunay_with_holes(n_points=None, centres=None, radii=None, n_nodes_hole=20, points=None):
"""Create a delanay mesh with holes, if centres=None, no holes will be created."""
if points is None:
points = np.random.uniform(0, 1, [n_points, 2])
x = np.linspace(0, 2 * np.pi, n_nodes_hole + 1)[:-1]
idx_inside = []
if centres is not None:
for i in range(len(centres)):
points = [p for p in points if np.linalg.norm(p - centres[i]) > radii[i]]
for i, (centre, radius) in enumerate(zip(centres, radii)):
points += list(
np.vstack(
[
centre[0] + radius * np.sin(x),
centre[1] + radius * np.cos(x),
]
).T
)
idx_inside.append(np.arange(len(points) - n_nodes_hole, len(points)))
points = np.array(points)
tri = spatial.Delaunay(points)
edge_list = []
for t in tri.simplices:
for edge in itertools.combinations(t, 2):
if centres is not None:
# add edges not touching the hole
edge0 = any(edge[0] in idx_inside[i] for i in range(len(centres)))
edge1 = any(edge[1] in idx_inside[i] for i in range(len(centres)))
if not edge0 or not edge1:
edge_list.append(edge)
# add the edges in the boundary of holes
for i in range(len(centres)):
if edge[0] in idx_inside[i] and edge[1] in idx_inside[i]:
if (
np.linalg.norm(points[edge[0]] - points[edge[1]])
< 2.0 * np.pi * radii[i] / n_nodes_hole
):
edge_list.append(edge)
else:
edge_list.append(edge)
graph = nx.Graph()
graph.add_edges_from(edge_list)
for n, p in zip(graph.nodes, points):
graph.nodes[n]["pos"] = p
return graph, points
def _make_simple_internal(larger=False):
G = nx.Graph()
G.add_edge(0, 1, weight=1)
G.add_edge(1, 2, weight=1)
G.add_edge(2, 0, weight=1)
G.add_edge(0, 3, weight=1)
G.add_edge(1, 3, weight=1)
if larger:
G.add_edge(0, 4, weight=1)
G.add_edge(4, 2, weight=1)
pos_ = {}
pos_[0] = np.array([0, 0])
pos_[1] = np.array([0, 1])
pos_[2] = np.array([1, 0.5])
pos_[3] = np.array([-1, 0.5])
if larger:
pos_[4] = np.array([1.0, -0.5])
for n in G.nodes:
G.nodes[n]["pos"] = pos_[n]
faces = [[0, 1, 2]]
if larger:
faces.append([0, 2, 4])
return SimplicialComplex(graph=G, faces=faces)
[docs]def make_simple(plot=False, larger=False):
"""Make simple simplicial complex with one face and one hole."""
sc = _make_simple_internal(larger=larger)
pos = {n: sc.graph.nodes[n]["pos"] for n in sc.graph.nodes}
sc_xgi = xgi.SimplicialComplex([list(e) for e in sc.graph.edges])
sc_xgi.add_simplices_from(sc.faces)
if plot:
plt.figure()
xgi.draw(sc_xgi, pos=pos)
plt.axis([-1.5, 1.5, -1.5, 1.5])
return sc_xgi
[docs]def make_complete(n=5, plot=False):
"""Make a complete graph."""
G = nx.complete_graph(n)
sc_xgi = xgi.SimplicialComplex([list(e) for e in G.edges])
if plot:
plt.figure()
xgi.draw(sc_xgi)
plt.axis([-1.5, 1.5, -1.5, 1.5])
return sc_xgi