def random_layout(G, center=None, dim=2, random_state=None): """Position nodes uniformly at random in the unit square. For every node, a position is generated by choosing each of dim coordinates uniformly at random on the interval [0.0, 1.0). NumPy (http://scipy.org) is required for this function. Parameters ---------- G : NetworkX graph or list of nodes A position will be assigned to every node in G. center : array-like or None Coordinate pair around which to center the layout. dim : int Dimension of layout. random_state : int, RandomState instance or None optional (default=None) Set the random state for deterministic node layouts. If int, `random_state` is the seed used by the random number generator, if numpy.random.RandomState instance, `random_state` is the random number generator, if None, the random number generator is the RandomState instance used by numpy.random. Returns ------- pos : dict A dictionary of positions keyed by node Examples -------- >>> G = nx.lollipop_graph(4, 3) >>> pos = nx.random_layout(G) """ import numpy as np G, center = _process_params(G, center, dim) shape = (len(G), dim) pos = random_state.rand(*shape) + center pos = pos.astype(np.float32) pos = dict(zip(G, pos)) return pos
def _sparse_fruchterman_reingold(A, k=None, pos=None, fixed=None, iterations=50, threshold=1e-4, dim=2, random_state=None): # Position nodes in adjacency matrix A using Fruchterman-Reingold # Entry point for NetworkX graph is fruchterman_reingold_layout() # Sparse version try: import numpy as np except ImportError: m = "_sparse_fruchterman_reingold() requires numpy: http://scipy.org/" raise ImportError(m) try: nnodes, _ = A.shape except AttributeError: msg = "fruchterman_reingold() takes an adjacency matrix as input" raise nx.NetworkXError(msg) try: from scipy.sparse import spdiags, coo_matrix except ImportError: msg = "_sparse_fruchterman_reingold() scipy numpy: http://scipy.org/ " raise ImportError(msg) # make sure we have a LIst of Lists representation try: A = A.tolil() except: A = (coo_matrix(A)).tolil() if pos is None: # random initial positions pos = np.asarray(random_state.rand(nnodes, dim), dtype=A.dtype) else: # make sure positions are of same type as matrix pos = pos.astype(A.dtype) # no fixed nodes if fixed is None: fixed = [] # optimal distance between nodes if k is None: k = np.sqrt(1.0 / nnodes) # the initial "temperature" is about .1 of domain area (=1x1) # this is the largest step allowed in the dynamics. t = max(max(pos.T[0]) - min(pos.T[0]), max(pos.T[1]) - min(pos.T[1])) * 0.1 # simple cooling scheme. # linearly step down by dt on each iteration so last iteration is size dt. dt = t / float(iterations + 1) displacement = np.zeros((dim, nnodes)) for iteration in range(iterations): displacement *= 0 # loop over rows for i in range(A.shape[0]): if i in fixed: continue # difference between this row's node position and all others delta = (pos[i] - pos).T # distance between points distance = np.sqrt((delta**2).sum(axis=0)) # enforce minimum distance of 0.01 distance = np.where(distance < 0.01, 0.01, distance) # the adjacency matrix row Ai = np.asarray(A.getrowview(i).toarray()) # displacement "force" displacement[:, i] +=\ (delta * (k * k / distance**2 - Ai * distance / k)).sum(axis=1) # update positions length = np.sqrt((displacement**2).sum(axis=0)) length = np.where(length < 0.01, 0.1, length) delta_pos = (displacement * t / length).T pos += delta_pos # cool temperature t -= dt err = np.linalg.norm(delta_pos) / nnodes if err < threshold: break return pos
def _fruchterman_reingold(A, k=None, pos=None, fixed=None, iterations=50, threshold=1e-4, dim=2, random_state=None): # Position nodes in adjacency matrix A using Fruchterman-Reingold # Entry point for NetworkX graph is fruchterman_reingold_layout() try: import numpy as np except ImportError: msg = "_fruchterman_reingold() requires numpy: http://scipy.org/ " raise ImportError(msg) try: nnodes, _ = A.shape except AttributeError: msg = "fruchterman_reingold() takes an adjacency matrix as input" raise nx.NetworkXError(msg) # make sure we have an array instead of a matrix A = np.asarray(A) if pos is None: # random initial positions pos = np.asarray(random_state.rand(nnodes, dim), dtype=A.dtype) else: # make sure positions are of same type as matrix pos = pos.astype(A.dtype) # optimal distance between nodes if k is None: k = np.sqrt(1.0 / nnodes) # the initial "temperature" is about .1 of domain area (=1x1) # this is the largest step allowed in the dynamics. # We need to calculate this in case our fixed positions force our domain # to be much bigger than 1x1 t = max(max(pos.T[0]) - min(pos.T[0]), max(pos.T[1]) - min(pos.T[1])) * 0.1 # simple cooling scheme. # linearly step down by dt on each iteration so last iteration is size dt. dt = t / float(iterations + 1) delta = np.zeros((pos.shape[0], pos.shape[0], pos.shape[1]), dtype=A.dtype) # the inscrutable (but fast) version # this is still O(V^2) # could use multilevel methods to speed this up significantly for iteration in range(iterations): # matrix of difference between points delta = pos[:, np.newaxis, :] - pos[np.newaxis, :, :] # distance between points distance = np.linalg.norm(delta, axis=-1) # enforce minimum distance of 0.01 np.clip(distance, 0.01, None, out=distance) # displacement "force" displacement = np.einsum('ijk,ij->ik', delta, (k * k / distance**2 - A * distance / k)) # update positions length = np.linalg.norm(displacement, axis=-1) length = np.where(length < 0.01, 0.1, length) delta_pos = np.einsum('ij,i->ij', displacement, t / length) if fixed is not None: # don't change positions of fixed nodes delta_pos[fixed] = 0.0 pos += delta_pos # cool temperature t -= dt err = np.linalg.norm(delta_pos) / nnodes if err < threshold: break return pos
def fruchterman_reingold_layout(G, k=None, pos=None, fixed=None, iterations=50, threshold=1e-4, weight='weight', scale=1, center=None, dim=2, random_state=None): """Position nodes using Fruchterman-Reingold force-directed algorithm. Parameters ---------- G : NetworkX graph or list of nodes A position will be assigned to every node in G. k : float (default=None) Optimal distance between nodes. If None the distance is set to 1/sqrt(n) where n is the number of nodes. Increase this value to move nodes farther apart. pos : dict or None optional (default=None) Initial positions for nodes as a dictionary with node as keys and values as a coordinate list or tuple. If None, then use random initial positions. fixed : list or None optional (default=None) Nodes to keep fixed at initial position. iterations : int optional (default=50) Maximum number of iterations taken threshold: float optional (default = 1e-4) Threshold for relative error in node position changes. The iteration stops if the error is below this threshold. weight : string or None optional (default='weight') The edge attribute that holds the numerical value used for the edge weight. If None, then all edge weights are 1. scale : number (default: 1) Scale factor for positions. Not used unless `fixed is None`. center : array-like or None Coordinate pair around which to center the layout. Not used unless `fixed is None`. dim : int Dimension of layout. random_state : int, RandomState instance or None optional (default=None) Set the random state for deterministic node layouts. If int, `random_state` is the seed used by the random number generator, if numpy.random.RandomState instance, `random_state` is the random number generator, if None, the random number generator is the RandomState instance used by numpy.random. Returns ------- pos : dict A dictionary of positions keyed by node Examples -------- >>> G = nx.path_graph(4) >>> pos = nx.spring_layout(G) # The same using longer but equivalent function name >>> pos = nx.fruchterman_reingold_layout(G) """ import numpy as np G, center = _process_params(G, center, dim) if fixed is not None: nfixed = dict(zip(G, range(len(G)))) fixed = np.asarray([nfixed[v] for v in fixed]) if pos is not None: # Determine size of existing domain to adjust initial positions dom_size = max(coord for pos_tup in pos.values() for coord in pos_tup) if dom_size == 0: dom_size = 1 shape = (len(G), dim) pos_arr = random_state.rand(*shape) * dom_size + center for i, n in enumerate(G): if n in pos: pos_arr[i] = np.asarray(pos[n]) else: pos_arr = None if len(G) == 0: return {} if len(G) == 1: return {nx.utils.arbitrary_element(G.nodes()): center} try: # Sparse matrix if len(G) < 500: # sparse solver for large graphs raise ValueError A = nx.to_scipy_sparse_matrix(G, weight=weight, dtype='f') if k is None and fixed is not None: # We must adjust k by domain size for layouts not near 1x1 nnodes, _ = A.shape k = dom_size / np.sqrt(nnodes) pos = _sparse_fruchterman_reingold(A, k, pos_arr, fixed, iterations, threshold, dim, random_state) except: A = nx.to_numpy_matrix(G, weight=weight) if k is None and fixed is not None: # We must adjust k by domain size for layouts not near 1x1 nnodes, _ = A.shape k = dom_size / np.sqrt(nnodes) pos = _fruchterman_reingold(A, k, pos_arr, fixed, iterations, threshold, dim, random_state) if fixed is None: pos = rescale_layout(pos, scale=scale) + center pos = dict(zip(G, pos)) return pos
def _sparse_fruchterman_reingold(A, k=None, pos=None, fixed=None, iterations=50, threshold=1e-4, dim=2, random_state=None): # Position nodes in adjacency matrix A using Fruchterman-Reingold # Entry point for NetworkX graph is fruchterman_reingold_layout() # Sparse version try: import numpy as np except ImportError: m = "_sparse_fruchterman_reingold() requires numpy: http://scipy.org/" raise ImportError(m) try: nnodes, _ = A.shape except AttributeError: msg = "fruchterman_reingold() takes an adjacency matrix as input" raise nx.NetworkXError(msg) try: from scipy.sparse import spdiags, coo_matrix except ImportError: msg = "_sparse_fruchterman_reingold() scipy numpy: http://scipy.org/ " raise ImportError(msg) # make sure we have a LIst of Lists representation try: A = A.tolil() except: A = (coo_matrix(A)).tolil() if pos is None: # random initial positions pos = np.asarray(random_state.rand(nnodes, dim), dtype=A.dtype) else: # make sure positions are of same type as matrix pos = pos.astype(A.dtype) # no fixed nodes if fixed is None: fixed = [] # optimal distance between nodes if k is None: k = np.sqrt(1.0/nnodes) # the initial "temperature" is about .1 of domain area (=1x1) # this is the largest step allowed in the dynamics. t = 0.1 # simple cooling scheme. # linearly step down by dt on each iteration so last iteration is size dt. dt = t / float(iterations+1) displacement = np.zeros((dim, nnodes)) for iteration in range(iterations): displacement *= 0 # loop over rows for i in range(A.shape[0]): if i in fixed: continue # difference between this row's node position and all others delta = (pos[i] - pos).T # distance between points distance = np.sqrt((delta**2).sum(axis=0)) # enforce minimum distance of 0.01 distance = np.where(distance < 0.01, 0.01, distance) # the adjacency matrix row Ai = np.asarray(A.getrowview(i).toarray()) # displacement "force" displacement[:, i] +=\ (delta * (k * k / distance**2 - Ai * distance / k)).sum(axis=1) # update positions length = np.sqrt((displacement**2).sum(axis=0)) length = np.where(length < 0.01, 0.1, length) delta_pos = (displacement * t / length).T pos += delta_pos # cool temperature t -= dt err = np.linalg.norm(delta_pos)/nnodes if err < threshold: break return pos
def _fruchterman_reingold(A, k=None, pos=None, fixed=None, iterations=50, threshold=1e-4, dim=2, random_state=None): # Position nodes in adjacency matrix A using Fruchterman-Reingold # Entry point for NetworkX graph is fruchterman_reingold_layout() try: import numpy as np except ImportError: msg = "_fruchterman_reingold() requires numpy: http://scipy.org/ " raise ImportError(msg) try: nnodes, _ = A.shape except AttributeError: msg = "fruchterman_reingold() takes an adjacency matrix as input" raise nx.NetworkXError(msg) # make sure we have an array instead of a matrix A = np.asarray(A) if pos is None: # random initial positions pos = np.asarray(random_state.rand(nnodes, dim), dtype=A.dtype) else: # make sure positions are of same type as matrix pos = pos.astype(A.dtype) # optimal distance between nodes if k is None: k = np.sqrt(1.0/nnodes) # the initial "temperature" is about .1 of domain area (=1x1) # this is the largest step allowed in the dynamics. # We need to calculate this in case our fixed positions force our domain # to be much bigger than 1x1 t = max(max(pos.T[0]) - min(pos.T[0]), max(pos.T[1]) - min(pos.T[1]))*0.1 # simple cooling scheme. # linearly step down by dt on each iteration so last iteration is size dt. dt = t/float(iterations+1) delta = np.zeros((pos.shape[0], pos.shape[0], pos.shape[1]), dtype=A.dtype) # the inscrutable (but fast) version # this is still O(V^2) # could use multilevel methods to speed this up significantly for iteration in range(iterations): # matrix of difference between points delta = pos[:, np.newaxis, :] - pos[np.newaxis, :, :] # distance between points distance = np.linalg.norm(delta, axis=-1) # enforce minimum distance of 0.01 np.clip(distance, 0.01, None, out=distance) # displacement "force" displacement = np.einsum('ijk,ij->ik', delta, (k * k / distance**2 - A * distance / k)) # update positions length = np.linalg.norm(displacement, axis=-1) length = np.where(length < 0.01, 0.1, length) delta_pos = np.einsum('ij,i->ij', displacement, t / length) if fixed is not None: # don't change positions of fixed nodes delta_pos[fixed] = 0.0 pos += delta_pos # cool temperature t -= dt err = np.linalg.norm(delta_pos)/nnodes if err < threshold: break return pos