Hyperedges¶
A hyperedge connects any number of vertices, not just two. Two flavours:
- Undirected — one set of members. Useful for complexes, sets, motifs.
- Directed — a head set and a tail set. Useful for reactions (substrates → products), causal motifs.
from annnet import AnnNet
H = AnnNet(directed=False)
H.add_vertices(['Glc', 'ATP', 'G6P', 'ADP', 'F6P'])
H
AnnNet object with n_vertices × n_edges = 5 × 0
directed: False
slices: ['default']
Undirected hyperedges¶
Pass a list of members. Order doesn't matter.
H.add_edges(['Glc', 'ATP', 'G6P'], edge_id='complex_A')
H.add_edges(['G6P', 'F6P'], edge_id='complex_B') # 2-member is still a hyperedge here
# Inspect the members through the public edge view
e = H.get_edge('complex_A')
print('complex_A members:', set(e.members))
print(' directed? ', e.directed)
complex_A members: {'ATP', 'Glc', 'G6P'}
directed? False
Directed hyperedges (reactions)¶
Pass src=[heads] and tgt=[tails]. The head set produces the tail set
(or with the opposite convention — pick once and be consistent).
# Hexokinase: Glc + ATP -> G6P + ADP
H.add_edges(src=['Glc', 'ATP'], tgt=['G6P', 'ADP'], edge_id='hexokinase')
e = H.get_edge('hexokinase')
print('hexokinase head (source):', set(e.source))
print('hexokinase tail (target):', set(e.target))
print(' directed? ', e.directed)
hexokinase head (source): {'ATP', 'Glc'}
hexokinase tail (target): {'ADP', 'G6P'}
directed? True
Reading hyperedges via views¶
G.views.edges() exposes hyperedges through the members column (for
undirected) or head / tail columns (for directed). The kind column
is 'hyper' for both.
H.views.edges().select(['edge_id', 'kind', 'members', 'head', 'tail']).head()
| edge_id | kind | members | head | tail |
|---|---|---|---|---|
| str | str | list[str] | list[str] | list[str] |
| "complex_A" | "hyper" | ["ATP", "G6P", "Glc"] | null | null |
| "complex_B" | "hyper" | ["F6P", "G6P"] | null | null |
| "hexokinase" | "hyper" | null | ["ATP", "Glc"] | ["ADP", "G6P"] |
Stoichiometric coefficients (advanced)¶
The incidence matrix is the source of truth. Each column is one edge;
each row is one entity. For a binary edge in a directed graph the
convention is +w at source, -w at target. For a directed hyperedge
the same rule applies per member of head / tail (positive on head,
negative on tail by default).
You can set custom coefficients per member via G.set_edge_coeffs.
# Read the column for hexokinase straight off the incidence matrix.
eid = 'hexokinase'
col = H.edge_to_idx[eid] # edge id -> matrix column
incidence = H.ops.incidence(values=True) # dense: vertices x edges
labels = list(H.ops.incidence_as_lists()) # vertex ids, in matrix-row order
print(f'incidence column for {eid}:')
for row, vid in enumerate(labels):
val = incidence[row, col]
if val != 0:
print(f' {vid:>5}: {val:+.2f}')
incidence column for hexokinase:
Glc: +1.00
ATP: +1.00
G6P: -1.00
ADP: -1.00
Next: 06 — Multilayer.