Multilayer¶
A multilayer graph lets the same vertex live in multiple layers —
time points, conditions, modalities. Each (vertex_id, layer_tuple)
pair is a supra-node.
Edges fall into three roles:
- intra — both endpoints in the same layer
- inter — endpoints in different layers, different vertex ids
- coupling — same vertex id, different layers (the same node "now" vs "later")
In [1]:
Copied!
import warnings
from annnet import AnnNet
M = AnnNet(directed=False)
with warnings.catch_warnings():
warnings.simplefilter('ignore')
# One aspect 't' with two elementary layers
M.layers.set_aspects(['t'], {'t': ['t1', 't2']})
# Same vertices A, B in both layers
M.add_vertices(['A', 'B'], layer={'t': 't1'})
M.add_vertices(['A', 'B'], layer={'t': 't2'})
print('aspects:', M.layers.list_aspects())
print('layers: ', M.layers.list_layers())
print('nv (supra-nodes):', M.nv)
import warnings
from annnet import AnnNet
M = AnnNet(directed=False)
with warnings.catch_warnings():
warnings.simplefilter('ignore')
# One aspect 't' with two elementary layers
M.layers.set_aspects(['t'], {'t': ['t1', 't2']})
# Same vertices A, B in both layers
M.add_vertices(['A', 'B'], layer={'t': 't1'})
M.add_vertices(['A', 'B'], layer={'t': 't2'})
print('aspects:', M.layers.list_aspects())
print('layers: ', M.layers.list_layers())
print('nv (supra-nodes):', M.nv)
aspects: ('t',)
layers: {'t': ['t1', 't2']}
nv (supra-nodes): 2
Intra-layer edges¶
Pass full (vertex_id, layer_tuple) supra-node keys to add_edges.
The engine infers intra because both endpoints sit in the same layer.
In [2]:
Copied!
M.add_edges(('A', ('t1',)), ('B', ('t1',)), edge_id='e_t1', weight=1.0)
M.add_edges(('A', ('t2',)), ('B', ('t2',)), edge_id='e_t2', weight=1.0)
for eid, (layer_a, layer_b) in M.edge_layers.items():
kind = 'intra' if layer_a == layer_b else 'inter'
print(f' {eid}: kind={kind}, layers={(layer_a, layer_b)}')
M.add_edges(('A', ('t1',)), ('B', ('t1',)), edge_id='e_t1', weight=1.0)
M.add_edges(('A', ('t2',)), ('B', ('t2',)), edge_id='e_t2', weight=1.0)
for eid, (layer_a, layer_b) in M.edge_layers.items():
kind = 'intra' if layer_a == layer_b else 'inter'
print(f' {eid}: kind={kind}, layers={(layer_a, layer_b)}')
e_t1: kind=intra, layers=(('t1',), ('t1',))
e_t2: kind=intra, layers=(('t2',), ('t2',))
Coupling edges¶
G.layers.add_layer_coupling_pairs([(L_a, L_b), ...]) adds one edge
per vertex from L_a to its copy in L_b. Pure
"interlayer-identity" connections.
In [3]:
Copied!
with warnings.catch_warnings():
warnings.simplefilter('ignore')
n_added = M.layers.add_layer_coupling_pairs([(('t1',), ('t2',))])
print('coupling edges added:', n_added)
with warnings.catch_warnings():
warnings.simplefilter('ignore')
n_added = M.layers.add_layer_coupling_pairs([(('t1',), ('t2',))])
print('coupling edges added:', n_added)
coupling edges added: 2
Subgraph from a layer¶
Pull one layer out as a flat AnnNet.
In [4]:
Copied!
sub_t1 = M.layers.subgraph_from_layer_tuple(('t1',))
print('t1 subgraph nv/ne:', sub_t1.shape)
print('t1 vertices: ', sorted(sub_t1.vertices()))
sub_t1 = M.layers.subgraph_from_layer_tuple(('t1',))
print('t1 subgraph nv/ne:', sub_t1.shape)
print('t1 vertices: ', sorted(sub_t1.vertices()))
t1 subgraph nv/ne: (2, 1) t1 vertices: ['A', 'B']
Multilayer math (light touch)¶
A few high-level descriptors:
In [5]:
Copied!
import numpy as np
A = M.layers.supra_adjacency()
print('supra-adjacency shape:', A.shape, ' nnz:', A.nnz)
deg = M.layers.supra_degree()
print('supra degree:', deg)
L = M.layers.supra_laplacian(kind='comb').toarray()
print('symmetric Laplacian?', np.allclose(L, L.T))
import numpy as np
A = M.layers.supra_adjacency()
print('supra-adjacency shape:', A.shape, ' nnz:', A.nnz)
deg = M.layers.supra_degree()
print('supra degree:', deg)
L = M.layers.supra_laplacian(kind='comb').toarray()
print('symmetric Laplacian?', np.allclose(L, L.T))
supra-adjacency shape: (4, 4) nnz: 8 supra degree: [2. 2. 2. 2.] symmetric Laplacian? True
For the full math API — random walks, Laplacian eigenvalues, modularity — see the Special Topics notebook Multilayer math in the sidebar.
That's the tutorial sequence. Where to next:
- The Use Cases and Special Topics notebooks in the sidebar for applied, end-to-end examples.
- The API reference in the docs sidebar for every public method.