Skip to content

Hop dataclass

Hop(
    target_layer: int,
    source_layers: tuple[int, ...],
    source_dims: tuple[int, ...],
    source_nodes: tuple[str, ...],
    target_dim: int,
    source_index: tuple[int, ...],
    target_index: tuple[int, ...],
)

Every edge entering one layer, as packed indices.

One entry of LayeredSpec.hops, never built by hand: a hop is exactly what a single PackedLinear or MaskedLinear computes. Packed source_index / target_index hold all parents of target_layer, so a skip edge is an ordinary pair rather than a term added later, and no edge can be dropped. Columns are the source layers concatenated, the axis gather_hop_inputs assembles. There is no stored mask; to_mask() allocates one.

Parameters:

Name Type Description Default

target_layer

int

Depth of the layer this hop produces. Always at least 1; layer 0 has no parents. LayeredSpec.hops[i] has target_layer == i + 1.

required

source_layers

tuple[int, ...]

Depths this hop reads, ascending, each one below target_layer. Only layers that really feed the target appear. Under longest-path ranking, target_layer - 1 is always one of them; with parse_layered(..., ranks=) a hop may omit the previous layer when every parent is a skip. A single entry is a plain adjacent hop when that entry is target_layer - 1; hops[0] is always (0,), so an aligned layer-0 tensor feeds it with no gathering.

required

source_dims

tuple[int, ...]

Units contributed by each entry of source_layers, same order. Their sum is in_features.

required

source_nodes

tuple[str, ...]

Node names of the concatenated source axis, source layers in source_layers order. One name per node, not per unit. With width greater than 1, len(source_nodes) is smaller than in_features.

required

target_dim

int

Units in the target layer, equal to layer_dims[target_layer] and to out_features.

required

source_index

tuple[int, ...]

Concat-column of each live unit pair. A named edge A -> B expands into every pair of the (k_B, k_A) block, target-unit outer, source-unit inner. Same length as target_index. Named edges are canonical: lexicographic by (source name, target name).

required

target_index

tuple[int, ...]

Target-layer row of each live unit pair. A dense rectangle would have 1.0 at [target_index[i], source_index[i]].

required
See Also

LayeredSpec : Holds hops, one per layer after the first. gather_hop_inputs : Builds the tensor whose columns these indices address. scatter_hop_outputs : Splits that concatenated axis back onto source layers. PackedLinear : Applies one hop from the packed indices. PackedLinear.transpose : Tied decode of this hop. MaskedLinear : Applies one hop after to_mask(). Skip : Metadata for the edges in this hop that span layers. LayeredSpec.hop_units : Slice of one named node on this hop's concatenated source axis. LayeredSpec.node_units : Slice of one named node on its layer tensor.

Notes

Every named edge is a block of packed unit pairs in exactly one hop, the one of its target layer. At width 1 that block is a single pair and the pairs summed over all hops give the named-edge count. Applying a hop applies every parent of its layer at once.

To locate one source layer's block on the concatenated axis, add the widths in front of it:

offset = sum(source_dims[:source_layers.index(layer)])

column_offsets does that for you. LayeredSpec.hop_units locates one named node on that axis, widths included.

Examples:

A chain A -> H -> C plus the skip A -> C:

>>> import pandas as pd
>>> import kpnn2
>>> edgelist = pd.DataFrame(
...     {
...         "source": ["A", "H", "A"],
...         "target": ["H", "C", "C"],
...     }
... )
>>> spec = kpnn2.parse_layered(edgelist)
>>> hop = spec.hops[1]
>>> hop.target_layer, hop.source_layers
(2, (0, 1))
>>> hop.source_nodes
('A', 'H')
>>> hop.source_index, hop.target_index
((0, 1), (0, 0))
>>> hop.to_mask().tolist()
[[1.0, 1.0]]

Attributes

column_offsets property

column_offsets: tuple[int, ...]

First source column of each entry of source_layers.

Same length and order as source_layers. Add a node's block start inside its own layer to get its concatenated first unit. At width 1 the block start equals the node's ordinal in layer_nodes.