parse_layered ¶
parse_layered(
edgelist: DataFrame,
*,
widths: Mapping[str, int] | None = None,
ranks: Mapping[str, int] | None = None
) -> LayeredSpec
Parse a source/target edgelist into a LayeredSpec.
Prior-knowledge edges, such as genes into pathways, become
depth-ranked layers plus one packed hop per layer, ready for
a PackedLinear stack. Reach for it when a DAG should
become one hop per layer; parse_adjacency is the
shared-state packed alternative, which also allows cycles.
Depth is longest path from inputs unless ranks assigns
compact layers, names sort alphabetically within a layer,
and no hop mask is allocated. Optional widths lets a
named node own several units. Optional ranks places
nodes at official ontology levels instead of longest-path
hops.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
|
DataFrame
|
Edge table with required columns |
required |
|
mapping of str to int
|
Units per named node. Omitted names, |
None
|
|
mapping of str to int
|
User depth per named node. |
None
|
Returns:
| Type | Description |
|---|---|
LayeredSpec
|
Frozen structure: layers, one |
Raises:
| Type | Description |
|---|---|
Kpnn2Error
|
If |
See Also
parse_adjacency : Pack the same table into one state vector;
allows cycles and self-loops. Has no ranks argument.
PackedLinear : Apply one hop from its packed indices.
gather_hop_inputs : Build one hop's input from the saved layer
tensors.
Notes
Every named edge belongs to exactly one hop, the one of its
target layer, whether its depth gap is 1 or larger. Packed
indices are in unit space: named edge A -> B expands
into a (k_B, k_A) block of live pairs. At default width
1 that is one pair per named edge. A hop whose target has
parents further back reads several layers, and its source
columns are those layers concatenated in ascending order, so
a skip edge is an ordinary weight rather than a dummy neuron
or a second mechanism; skips only reports it. Under
longest-path ranking a hop always reads the previous layer;
with ranks it may omit that layer when every parent is
a skip. Terminals below maximum depth (early outputs) are
allowed, and isolated nodes cannot appear, since the node
set is the union of source and target. to_dict()
stores compacted ranks only when they differ from
longest-path on the same edges.
Examples:
A chain A -> H -> C plus the skip A -> C. The hop into
C reads both earlier layers, so the skip is a packed pair
of that hop:
>>> import pandas as pd
>>> import kpnn2
>>> edgelist = pd.DataFrame(
... {"source": ["A", "H", "A"], "target": ["H", "C", "C"]}
... )
>>> spec = kpnn2.parse_layered(edgelist)
>>> spec.layer_nodes
(('A',), ('H',), ('C',))
>>> spec.hops[1].source_nodes
('A', 'H')
>>> spec.hops[1].source_index, spec.hops[1].target_index
((0, 1), (0, 0))
>>> spec.hops[1].to_mask().tolist()
[[1.0, 1.0]]
>>> spec.skips[0].source, spec.skips[0].target
('A', 'C')
A hidden node of width 2 owns two units; the named edge
A -> H becomes a 2-by-1 block:
>>> spec = kpnn2.parse_layered(
... edgelist,
... widths={"H": 2},
... )
>>> spec.layer_dims
(1, 2, 1)
>>> spec.layer_widths
((1,), (2,), (1,))
>>> spec.hops[0].to_mask().tolist()
[[1.0], [1.0]]
Two siblings at the same official level share a layer even when longest-path depths differ:
>>> siblings = pd.DataFrame(
... {
... "source": ["A", "A", "Mid"],
... "target": ["Short", "Mid", "Long"],
... }
... )
>>> spec = kpnn2.parse_layered(
... siblings,
... ranks={
... "A": 0,
... "Mid": 1,
... "Short": 2,
... "Long": 2,
... },
... )
>>> spec.layer_nodes
(('A',), ('Mid',), ('Long', 'Short'))