Skip to content

Layers

Multilayer helpers from annnet.core._Layers.

Use G.layers and the AnnNet layer methods for layer workflows. Direct imports from underscore modules follow the internal API policy.

annnet.core._Layers.LayerAccessor

Namespace for multilayer operations on an :class:~annnet.core.graph.AnnNet graph.

Functions

list_aspects
list_aspects()

Return declared aspect names, or an empty tuple for flat graphs.

list_layers
list_layers(aspect=None, include_placeholder=False)

Return the user-declared layers, omitting the '_' placeholder by default.

Parameters:

Name Type Description Default
aspect str | None

If given, return a sorted list for that single aspect.

None
include_placeholder bool

Include the synthetic '_' placeholder layer in the result. Nodes assigned without an explicit layer= live on this coordinate; most callers want it hidden.

False
__dir__
__dir__()

The layer operations, and not the fields of the graph behind them.

__getattr__ forwards every unknown name to the graph, __dict__ included, so the default dir() reported the graph's whole instance state as though it were part of this namespace — graph_attributes and node_aligned among them, neither of which is an operation.

set_aspects
set_aspects(aspects, elem_layers=None)

Define multi-aspect structure.

Parameters:

Name Type Description Default
aspects list[str]

Aspect identifiers (e.g., ["time", "relation"]).

required
elem_layers dict[str, list[str]]

Elementary labels per aspect (e.g., {"time": ["t1","t2"]}).

None

Returns:

Type Description
None

Raises:

Type Description
ValueError

If aspects is empty.

Examples:

G.set_aspects(['time', 'relation'], {'time': ['t1', 't2'], 'relation': ['F', 'A']})
aspect
aspect(name)

Return one aspect, with its values in declaration order.

Parameters:

Name Type Description Default
name str

The aspect.

required

Returns:

Type Description
Aspect

Its values, and whether they come one before another.

Raises:

Type Description
KeyError

If the aspect is not declared.

Examples:

>>> G.layers.aspect('time').consecutive_pairs()
[('0h', '1h'), ('1h', '12h')]
values
values()

Return the resolver that answers for node-layer values.

Returns:

Type Description
ValueResolver

The contextual store first, then every attached array in the order it was attached. A later backing wins for a cell it can answer.

attach_values
attach_values(
    arrays,
    *,
    layers,
    nodes,
    rows=None,
    columns=None,
    mask=None
)

Attach an array of node-layer values without copying a cell.

The contextual store keys every value by its pair, which costs about 360 bytes a cell. That is the right shape for values a person typed and the wrong one for values that arrived as a table. Attaching costs the two index maps and nothing else: the array is not copied, not converted, and not read until a cell is asked for.

Parameters:

Name Type Description Default
arrays dict[str, array - like]

One two-dimensional array per attribute name, len(layers) rows by len(nodes) columns.

required
layers Sequence[tuple[str, ...]]

The layer each row stands for, in row order.

required
nodes Sequence[str]

The node each column stands for, in column order.

required
rows dict

Explicit index maps. An explicit column map lets two nodes share one column without the column being copied.

None
columns dict

Explicit index maps. An explicit column map lets two nodes share one column without the column being copied.

None
mask array - like

A boolean array gating which cells hold a value at all.

None

Returns:

Type Description
MatrixValues

The backing, so a caller can detach it later.

Raises:

Type Description
ValueError

If arrays is empty, or an array does not match the maps.

Examples:

>>> G.layers.attach_values(
...     {'response': matrix},
...     layers=[(c,) for c in conditions],
...     nodes=node_ids,
... )
detach_values
detach_values(backing)

Drop one attached array. The contextual store is never dropped.

matrix
matrix(name, *, nodes=None, layers=None, missing=nan)

Read one attribute as an array, with the labels that index it.

This is what to hand a method. A frame of Python objects has to be unpacked before any arithmetic; an array is the arithmetic's own shape, and the two label lists are what put an answer back on the right rows.

Where the values live in an attached array this reads them in one pass in C. Where they live in the dict store, or span both, it falls back to reading cell by cell — and the two give the same numbers, which is pinned by test.

Parameters:

Name Type Description Default
name str

The attribute.

required
nodes Sequence[str]

The node of each column. Default: every node carrying a value, sorted.

None
layers Sequence[tuple[str, ...]]

The layer of each row. Default: every layer carrying one, in the order the graph declares them.

None
missing Any

What a cell no backing answers for holds.

``numpy.nan``

Returns:

Type Description
ValueMatrix

Examples:

>>> block = G.layers.matrix('expression', nodes=['akt', 'erk'])
>>> block.values.mean(axis=0)
node_frame
node_frame(
    nodes=None,
    layers=None,
    attrs=None,
    *,
    pairs=None,
    format="wide",
    missing=nan,
    backend=None
)

Read node-layer values as a table.

The scalar accessor :meth:node_attrs answers for one pair and returns a dict, so reading a node by layer by attribute cube meant a Python loop with a .get() default in it — a helper every analysis wrote for itself. This is that cube.

Parameters:

Name Type Description Default
nodes Sequence[str]

Node ids. Default: every node that carries a value.

None
layers Sequence[tuple[str, ...]]

Layer coordinates. Default: every layer that carries a value, in the order the graph declares them.

None
attrs Sequence[str]

Attribute names. Default: every name present.

None
pairs Mapping[str, tuple[str, str]] | Sequence[tuple[str, str]]

Explicit (node_id, attr) columns, optionally labelled. Given this, nodes and attrs are not used, and the frame costs the pairs asked for rather than their cross product.

None
format ('wide', 'long')

"wide" is one row per layer, one column per (node, attr). "long" is one row per (node, layer, attr, value).

"wide"
missing Any

What a cell no backing answers for holds. Never a KeyError.

``numpy.nan``
backend str

Dataframe backend. Defaults to the graph's.

None

Returns:

Type Description
DataFrame - like

In "wide" form: layer, layer_id, then one column per requested pair — named for the attribute when one node was asked for, for the node when one attribute was, and "{node}.{attr}" otherwise, or by the label pairs gave it.

Raises:

Type Description
ValueError

If format is neither "wide" nor "long".

Notes

Values are read through :meth:matrix, so an attached array is gathered in one pass rather than a cell at a time, and the frame is the same whichever store answered.

Examples:

>>> G.layers.node_frame(nodes=['akt'], attrs=['observed'])
>>> G.layers.node_frame(pairs={'TGFA': ('tgfa', 'input')})
place
place(nodes, layers, *, mask=None)

Put every node on every layer, in one call.

Identity and values are separate questions, and this is the identity one. The value of a node-layer may live in an attached array, which costs two index maps; its presence still lives in the structure store, and placing a rectangle one pair at a time is what makes a real measurement table slow to attach whatever its values do.

Parameters:

Name Type Description Default
nodes Sequence[str]

The node ids to place.

required
layers Sequence[tuple[str, ...]]

The layer coordinates to place them on.

required
mask array - like

A boolean array, len(layers) by len(nodes). Only the cells it holds true are placed, so a condition that was never measured stays absent rather than becoming an empty node-layer.

None

Returns:

Type Description
int

The number of node-layers created.

Examples:

>>> G.layers.place(node_ids, [(c,) for c in conditions])
set_node_attrs_bulk
set_node_attrs_bulk(values, *, layer=None, key=None)

Write many node-layer values in one call.

The scalar :meth:set_node_attrs takes one pair, so filling a table meant a loop with a call in it.

Parameters:

Name Type Description Default
values Mapping

One of three shapes:

  • {(node_id, layer): {name: value}} — fully explicit.
  • {node_id: {name: value}} with layer= — one layer, many nodes.
  • {(node_id, layer): value} or {node_id: value} with key= — one attribute, its name given once.
required
layer tuple[str, ...]

The layer, when the keys are bare node ids.

None
key str

The attribute name, when the values are scalars.

None

Returns:

Type Description
int

The number of pairs written.

Raises:

Type Description
ValueError

If a key is a bare node id and no layer is given, or a value is a scalar and no key is.

Examples:

>>> G.layers.set_node_attrs_bulk({'akt': 0.9}, layer=('stim',), key='observed')
1
where
where(**predicates)

Select the layers whose aspect values satisfy every predicate.

A predicate is aspect=value or aspect__operator=value. The operators are eq (the default), ne, in, not_in, lt, lte, gt and gte. The last four ask where a value sits, so they need an ordered aspect and refuse a categorical one — the answer would otherwise be the declaration order pretending to be a meaning.

The window is resolved off the aspect declaration, so it costs the number of layers rather than the size of the graph. What it is then asked for — .nodes, .edges, .crossing, .boundary — costs one pass over the axis in question.

Parameters:

Name Type Description Default
**predicates

One or more aspect/aspect__operator keywords. With none, the selection is every layer.

{}

Returns:

Type Description
LayerSelection

Raises:

Type Description
KeyError

If an aspect is not declared.

ValueError

If an operator is unknown, or a comparison is asked of a categorical aspect.

Examples:

>>> G.layers.where(time__lte='12h')
LayerSelection(3 layer(s): [('0h',), ('1h',), ('12h',)])
>>> G.layers.where(time__lte='12h', mechanism='mapk').nodes
{'akt', 'erk'}
set_ordered
set_ordered(name, ordered=True)

Declare whether one aspect's values come one before another.

An ordinal aspect — a timepoint, a dose, a stage — answers before, after and consecutive_pairs, and can be windowed with the comparison predicates of :meth:where. A categorical one refuses them, because the answer would be the declaration order pretending to be a meaning.

Parameters:

Name Type Description Default
name str

The aspect.

required
ordered bool
True

Raises:

Type Description
KeyError

If the aspect is not declared.

Examples:

>>> G.layers.set_ordered('time')
set_elementary_layers
set_elementary_layers(layers_by_aspect)

Declare concrete elementary layer values for existing aspects.

augment_elementary_layers
augment_elementary_layers(layers_by_aspect)

Add layer values to the aspects the graph already declares.

This is not :meth:set_elementary_layers, because that helper drops an unused placeholder layer. A restore needs the '_' placeholder to survive, or a coordinate the file stored against it stops validating. An aspect the graph does not declare is ignored.

flatten_layers
flatten_layers()

Remove multilayer structure in-place and project to a flat graph.

Returns:

Type Description
AnnNet

The mutated graph itself.

Notes

This projects node identities from (node_id, layer_tuple) to bare node_id strings and drops multilayer-only metadata such as aspects, layer registries, supra-node attributes, and multilayer edge roles.

add_elementary_layer
add_elementary_layer(aspect, label)

Register a new elementary layer label under an existing aspect.

Parameters:

Name Type Description Default
aspect str

Existing aspect name.

required
label str

New elementary layer label.

required

Returns:

Type Description
None
has_presence
has_presence(u, layer_tuple)

Check whether the graph holds the entity (u, aa).

Parameters:

Name Type Description Default
u str

Node identifier.

required
layer_tuple tuple[str, ...]

Aspect tuple layer.

required

Returns:

Type Description
bool
iter_layers
iter_layers()

Iterate over all aspect-tuples (Cartesian product).

Yields:

Type Description
tuple[str, ...]

Layer tuples in configured order.

iter_node_layers
iter_node_layers(u)

Iterate layer tuples where (u, aa) is in V_M.

Parameters:

Name Type Description Default
u str

Node identifier.

required

Yields:

Type Description
tuple[str, ...]

Layer tuples for u.

ensure_node_layer_index
ensure_node_layer_index(restrict_layers=None)

Return the number of indexed node–layer pairs.

Parameters:

Name Type Description Default
restrict_layers list[tuple[str, ...]] | None

If provided, count only these layers.

None

Returns:

Type Description
int

Number of indexed node–layer pairs.

Notes

Kept for backward compatibility. Use _build_supra_index() internally.

nl_to_row
nl_to_row(u, layer_tuple)

Map (u, aa) to row index.

Parameters:

Name Type Description Default
u str

Node identifier.

required
layer_tuple tuple[str, ...]

Aspect tuple layer.

required

Returns:

Type Description
int

Raises:

Type Description
KeyError

If the node–layer pair is not indexed.

row_to_nl
row_to_nl(row)

Map row index to (u, aa).

Parameters:

Name Type Description Default
row int

Row index.

required

Returns:

Type Description
tuple[str, tuple[str, ...]]

Raises:

Type Description
KeyError

If the row is not indexed.

layer_id_to_tuple
layer_id_to_tuple(layer_id)

Map legacy string layer id to aspect tuple.

Parameters:

Name Type Description Default
layer_id str

Layer identifier (single-aspect only).

required

Returns:

Type Description
tuple[str, ...]

Raises:

Type Description
ValueError

If not in single-aspect mode.

layer_tuple_to_id
layer_tuple_to_id(aa)

Canonical string id for a layer tuple.

Parameters:

Name Type Description Default
aa tuple[str, ...]

Aspect tuple layer.

required

Returns:

Type Description
str

Canonical id (single label for 1 aspect, or "×"-joined).

set_elementary_attrs
set_elementary_attrs(aspect, label, /, **attrs)

Attach attributes to an elementary Kivela layer.

aspect and label are positional-only so user attribute keys (including label=) are passed through verbatim.

Parameters:

Name Type Description Default
aspect str

Aspect identifier (positional-only).

required
label str

Elementary layer label (positional-only).

required
**attrs

Key-value metadata to store.

{}

Returns:

Type Description
None
elementary_attrs
elementary_attrs(aspect, label)

Get attributes for an elementary Kivela layer.

Parameters:

Name Type Description Default
aspect str

Aspect identifier.

required
label str

Elementary layer label.

required

Returns:

Type Description
dict

Attributes dict; empty if not set.

set_aspect_attrs
set_aspect_attrs(aspect, **attrs)

Attach metadata to a Kivela aspect.

Parameters:

Name Type Description Default
aspect str

Aspect identifier.

required
**attrs

Key-value metadata to store.

{}

Returns:

Type Description
None
aspect_attrs
aspect_attrs(aspect)

Return a shallow copy of metadata for a Kivela aspect.

Parameters:

Name Type Description Default
aspect str

Aspect identifier.

required

Returns:

Type Description
dict
set_attrs
set_attrs(layer_tuple, **attrs)

Attach metadata to a Kivela layer.

Parameters:

Name Type Description Default
layer_tuple tuple[str, ...]

Aspect tuple layer.

required
**attrs

Key-value metadata to store.

{}

Returns:

Type Description
None
attrs
attrs(layer_tuple)

Get metadata dict for a Kivela layer.

Parameters:

Name Type Description Default
layer_tuple tuple[str, ...]

Aspect tuple layer.

required

Returns:

Type Description
dict

Shallow copy; empty if not set.

set_node_attrs
set_node_attrs(u, layer_tuple, **attrs)

Attach metadata to a node–layer pair.

Parameters:

Name Type Description Default
u str

Node identifier.

required
layer_tuple tuple[str, ...]

Aspect tuple layer.

required
**attrs

Key-value metadata to store.

{}

Returns:

Type Description
None

Raises:

Type Description
KeyError

If (u, layer_tuple) is not present in V_M.

node_attrs
node_attrs(u, layer_tuple)

Get metadata dict for a node–layer pair.

Parameters:

Name Type Description Default
u str

Node identifier.

required
layer_tuple tuple[str, ...]

Aspect tuple layer.

required

Returns:

Type Description
dict

Shallow copy; empty if not set.

layer_node_set
layer_node_set(layer_tuple)

Nodes present in a Kivela layer.

Parameters:

Name Type Description Default
layer_tuple Iterable[str]

Aspect tuple layer.

required

Returns:

Type Description
set[str]
layer_edge_set
layer_edge_set(
    layer_tuple,
    *,
    include_inter=False,
    include_coupling=False
)

Edges associated with a Kivela layer.

Parameters:

Name Type Description Default
layer_tuple Iterable[str]

Aspect tuple layer.

required
include_inter bool

Include inter-layer edges touching layer_tuple.

False
include_coupling bool

Include coupling edges touching layer_tuple.

False

Returns:

Type Description
set[str]

Every edge touching this layer. This is the primitive the layer algebra is built on, and it carries no boundary= for that reason: "touching" is the question a single layer answers, and whether a selection may keep an edge that leaves it is a question about the selection.

layer_union
layer_union(
    layer_tuples,
    *,
    include_inter=False,
    include_coupling=False,
    boundary="closed"
)

Union of several Kivela layers.

Parameters:

Name Type Description Default
layer_tuples Iterable[Iterable[str]]

Layer tuples to union.

required
include_inter bool

Include inter-layer edges touching any layer in the union.

False
include_coupling bool

Include coupling edges touching any layer in the union.

False
boundary ('closed', 'open')

"closed" keeps only the edges whose every layer is in the union, so the selection cannot reach outside the window it names. "open" keeps an edge that merely touches it, which is what this did before the behaviour had a name.

"closed"

Returns:

Type Description
dict

{"nodes": set[str], "edges": set[str]}.

Notes

With the default include_inter=False and include_coupling=False the two boundaries agree, because an intra-layer edge never leaves its layer. They differ exactly when a crossing edge was asked for.

layer_intersection
layer_intersection(
    layer_tuples,
    *,
    include_inter=False,
    include_coupling=False,
    boundary="closed"
)

Intersection of several Kivela layers.

Parameters:

Name Type Description Default
layer_tuples Iterable[Iterable[str]]

Layer tuples to intersect.

required
include_inter bool

Include inter-layer edges touching any layer in the intersection.

False
include_coupling bool

Include coupling edges touching any layer in the intersection.

False
boundary ('closed', 'open')

"closed" keeps only the edges whose every layer is one of these.

"closed"

Returns:

Type Description
dict

{"nodes": set[str], "edges": set[str]}.

Notes

An intra-layer edge belongs to one layer, so it cannot be in the intersection of two. What survives here is a crossing edge that touches every named layer, and only when one was asked for.

layer_difference
layer_difference(
    layer_a,
    layer_b,
    *,
    include_inter=False,
    include_coupling=False,
    boundary="closed"
)

Set difference: elements in layer_a but not in layer_b.

Parameters:

Name Type Description Default
layer_a Iterable[str]

Minuend layer tuple.

required
layer_b Iterable[str]

Subtrahend layer tuple.

required
include_inter bool

Include inter-layer edges touching layer_a.

False
include_coupling bool

Include coupling edges touching layer_a.

False
boundary ('closed', 'open')

"closed" keeps only the edges whose every layer is layer_a.

"closed"

Returns:

Type Description
dict

{"nodes": set[str], "edges": set[str]}.

create_slice_from_layer
create_slice_from_layer(
    slice_id,
    layer_tuple,
    *,
    include_inter=False,
    include_coupling=False,
    boundary="closed",
    **attributes
)

Create a slice induced by a single Kivela layer.

Parameters:

Name Type Description Default
slice_id str

Slice identifier.

required
layer_tuple Iterable[str]

Layer tuple.

required
include_inter bool

Include inter-layer edges touching layer_tuple.

False
include_coupling bool

Include coupling edges touching layer_tuple.

False
boundary ('closed', 'open')

"closed" keeps only the edges whose every layer is in the selection, so it cannot reach outside the window it names.

"closed"
**attributes

Slice attributes to store.

{}

Returns:

Type Description
str

The created slice id.

Examples:

G.create_slice_from_layer('t1_F', ('t1', 'F'))
create_slice_from_layer_union
create_slice_from_layer_union(
    slice_id,
    layer_tuples,
    *,
    include_inter=False,
    include_coupling=False,
    **attributes
)

Create a slice as the union of several layers.

Parameters:

Name Type Description Default
slice_id str

Slice identifier.

required
layer_tuples Iterable[Iterable[str]]

Layer tuples to union.

required
include_inter bool

Include inter-layer edges touching any layer in the union.

False
include_coupling bool

Include coupling edges touching any layer in the union.

False
**attributes

Slice attributes to store.

{}

Returns:

Type Description
str

The created slice id.

create_slice_from_layer_intersection
create_slice_from_layer_intersection(
    slice_id,
    layer_tuples,
    *,
    include_inter=False,
    include_coupling=False,
    **attributes
)

Create a slice as the intersection of several layers.

Parameters:

Name Type Description Default
slice_id str

Slice identifier.

required
layer_tuples Iterable[Iterable[str]]

Layer tuples to intersect.

required
include_inter bool

Include inter-layer edges touching any layer in the intersection.

False
include_coupling bool

Include coupling edges touching any layer in the intersection.

False
**attributes

Slice attributes to store.

{}

Returns:

Type Description
str

The created slice id.

create_slice_from_layer_difference
create_slice_from_layer_difference(
    slice_id,
    layer_a,
    layer_b,
    *,
    include_inter=False,
    include_coupling=False,
    **attributes
)

Create a slice as the difference of two layers.

Parameters:

Name Type Description Default
slice_id str

Slice identifier.

required
layer_a Iterable[str]

Minuend layer tuple.

required
layer_b Iterable[str]

Subtrahend layer tuple.

required
include_inter bool

Include inter-layer edges touching layer_a.

False
include_coupling bool

Include coupling edges touching layer_a.

False
**attributes

Slice attributes to store.

{}

Returns:

Type Description
str

The created slice id.

subgraph_from_layer_tuple
subgraph_from_layer_tuple(
    layer_tuple,
    *,
    include_inter=False,
    include_coupling=False,
    boundary="closed"
)

Concrete subgraph induced by a single Kivela layer.

Parameters:

Name Type Description Default
layer_tuple Iterable[str]

Layer tuple.

required
include_inter bool

Include inter-layer edges touching layer_tuple.

False
include_coupling bool

Include coupling edges touching layer_tuple.

False
boundary ('closed', 'open')

"closed" keeps only the edges whose every layer is in the selection, so it cannot reach outside the window it names.

"closed"

Returns:

Type Description
AnnNet
subgraph_from_layer_union
subgraph_from_layer_union(
    layer_tuples,
    *,
    include_inter=False,
    include_coupling=False,
    boundary="closed"
)

Concrete subgraph induced by the union of several layers.

Parameters:

Name Type Description Default
layer_tuples Iterable[Iterable[str]]

Layer tuples to union.

required
include_inter bool

Include inter-layer edges touching any layer in the union.

False
include_coupling bool

Include coupling edges touching any layer in the union.

False
boundary ('closed', 'open')

"closed" keeps only the edges whose every layer is in the selection, so it cannot reach outside the window it names.

"closed"

Returns:

Type Description
AnnNet
subgraph_from_layer_intersection
subgraph_from_layer_intersection(
    layer_tuples,
    *,
    include_inter=False,
    include_coupling=False
)

Concrete subgraph induced by the intersection of several layers.

Parameters:

Name Type Description Default
layer_tuples Iterable[Iterable[str]]

Layer tuples to intersect.

required
include_inter bool

Include inter-layer edges touching any layer in the intersection.

False
include_coupling bool

Include coupling edges touching any layer in the intersection.

False

Returns:

Type Description
AnnNet
subgraph_from_layer_difference
subgraph_from_layer_difference(
    layer_a,
    layer_b,
    *,
    include_inter=False,
    include_coupling=False
)

Concrete subgraph induced by a set-difference of two layers.

Parameters:

Name Type Description Default
layer_a Iterable[str]

Minuend layer tuple.

required
layer_b Iterable[str]

Subtrahend layer tuple.

required
include_inter bool

Include inter-layer edges touching layer_a.

False
include_coupling bool

Include coupling edges touching layer_a.

False

Returns:

Type Description
AnnNet
supra_adjacency
supra_adjacency(layers=None)

Build the supra adjacency matrix.

Parameters:

Name Type Description Default
layers list[str] | list[tuple[str, ...]] | None

Optional subset of layers. In single-aspect mode, string ids are accepted.

None

Returns:

Type Description
csr_matrix

Supra adjacency over the chosen node–layer index.

Examples:

A = G.supra_adjacency()
supra_incidence
supra_incidence(
    layers=None, include_inter=True, include_coupling=True
)

Build the supra-incidence matrix over selected layers.

Unlike supra_adjacency, this preserves the full hyperedge structure —
a k-ary hyperedge becomes a single column with k nonzero entries, with
stoichiometric coefficients intact. Binary intra, inter, coupling, and
hyperedges are all handled in a unified column-oriented representation.

Rows  : node-layer pairs (u, aa) — identical index to supra_adjacency,
        built by ensure_node_layer_index.
Cols  : one per selected edge, ordered as: intra edges (per layer, sorted
        by eid), then inter/coupling edges, then unassigned hyperedges last.

Column sign convention (matches _matrix):
    - Binary directed   : +w at source row, -w at target row
    - Binary undirected : +w at both rows
    - Hyperedge directed: +w at head rows, -w at tail rows (stoich-aware)
    - Hyperedge undirected: +w at all member rows (stoich-aware)
    - Inter/coupling    : +w at (u, La) row, -w at (v, Lb) row (directed)

Hyperedges MUST have a layer assignment in edge_layers (set via
set_edge_kivela_role(eid, "intra", layer_tuple) after add_hyperedge).
Hyperedges without a layer assignment are collected in the returned
skipped list and excluded from the matrix — they do NOT silently corrupt
the result.

Parameters:

Name Type Description Default
layers list[str] | list[tuple[str, ...]] | None

Optional subset of layers. None = all layers in V_M. Single-aspect string ids are accepted.

None
include_inter bool

Include inter-layer edges in the output columns. Default True.

True
include_coupling bool

Include coupling edges in the output columns. Default True.

True

Returns:

Name Type Description
B csr_matrix

Shape (|V_M|, |E_selected|). Rows are node-layer pairs in the order given by self._row_to_nl after ensure_node_layer_index.

edge_ids list[str]

Edge id for each column of B, in column order. Use this to map columns back to edges for interpretability.

skipped list[str]

Edge ids that were excluded because their layer assignment could not be resolved. Inspect these if B looks sparse.

Notes
The hypergraph random-walk diffusion operator follows directly::

    B_csr = B  (this output)
    D_v = diag(|B| @ ones)          # node degree (sum of |entries| per row)
    D_e = diag(|B|.T @ ones)        # edge degree (sum of |entries| per col)
    Theta = D_v_inv @ B @ D_e_inv @ B.T

Examples:

    B, eids, skipped = G.supra_incidence()
build_intra_block
build_intra_block(layers=None)

Supra matrix containing only intra-layer edges (diagonal blocks).

Parameters:

Name Type Description Default
layers list[str] | list[tuple[str, ...]] | None

Optional subset of layers.

None

Returns:

Type Description
csr_matrix
build_inter_block
build_inter_block(layers=None)

Supra matrix containing only inter-layer (non-diagonal) edges.

Parameters:

Name Type Description Default
layers list[str] | list[tuple[str, ...]] | None

Optional subset of layers.

None

Returns:

Type Description
csr_matrix
build_coupling_block
build_coupling_block(layers=None)

Supra matrix containing only coupling edges.

Parameters:

Name Type Description Default
layers list[str] | list[tuple[str, ...]] | None

Optional subset of layers.

None

Returns:

Type Description
csr_matrix
supra_degree
supra_degree(layers=None)

Degree vector over the supra-graph.

Parameters:

Name Type Description Default
layers list[str] | list[tuple[str, ...]] | None

Optional subset of layers.

None

Returns:

Type Description
ndarray
supra_laplacian
supra_laplacian(kind='comb', layers=None)

Build supra-Laplacian.

Parameters:

Name Type Description Default
kind str

"comb" for combinatorial L = D - A or "norm" for normalized L = I - D^{-1/2} A D^{-1/2}.

'comb'
layers list[str] | list[tuple[str, ...]] | None

Optional subset of layers.

None

Returns:

Type Description
csr_matrix
couple
couple(
    aspect,
    *,
    kind="ordinal",
    pairs=None,
    within=None,
    on=None,
    edge_kind=None,
    weight=1.0,
    directed=False,
    both_present=True
)

Couple the layers of one aspect, in one call.

A multilayer graph has two families of coupling and they follow from whether the aspect is ordered. An ordinal aspect couples consecutive values — a timepoint to the next timepoint. A categorical one couples across values — every mechanism to every other. Both were reachable before only by building the value pairs by hand from a list kept beside the graph, which is the fact :meth:aspect now holds.

Parameters:

Name Type Description Default
aspect str

The aspect to couple along.

required
kind ('ordinal', 'categorical')

Which family. "ordinal" takes the aspect's consecutive pairs and needs it ordered; "categorical" takes every pair of values. Ignored when pairs is given.

"ordinal"
pairs Sequence[tuple[str, str]]

Explicit value pairs on this aspect, for a coupling neither family describes.

None
within dict

Restrict to layers whose other aspects match, as {aspect: value} or {aspect: {values}}. Two node-layers are coupled only when they agree on every aspect but this one, whether or not within is given.

None
on str

A node attribute two different node ids may share when they denote one entity — a shared symbol, where the same thing is measured two ways and each way names it differently. Default: couple a node to itself.

None
edge_kind str

The family name carried in the edge id and in the edge_kind attribute. Default: kind, or "pairs" when pairs is given.

None
weight float
1.0
directed bool
False
both_present bool

Couple only where both node-layers already exist. False places the missing one first, which a timecourse whose nodes appear late wants; it needs on=None, because there is no answer to which id a missing node would have.

True

Returns:

Type Description
int

The number of coupling edges added.

Raises:

Type Description
KeyError

If the aspect is not declared, or a pair names a value it does not hold.

ValueError

If kind is unknown, if "ordinal" is asked of a categorical aspect, or if both_present=False is combined with on.

Examples:

>>> G.layers.couple('time')  # consecutive timepoints
>>> G.layers.couple('mechanism', kind='categorical')
>>> G.layers.couple('assay', kind='categorical', on='symbol')
add_layer_coupling_pairs
add_layer_coupling_pairs(
    layer_pairs,
    *,
    weight=1.0,
    directed=False,
    edge_kind=None
)

Add diagonal couplings for explicit layer pairs.

Parameters:

Name Type Description Default
layer_pairs list[tuple[tuple[str, ...], tuple[str, ...]]]

Layer tuple pairs (aa, bb).

required
weight float

Edge weight.

1.0
edge_kind str

The family name carried in the edge id and in the edge_kind attribute, so two coupling schemes over one node pair do not collide.

None

Returns:

Type Description
int

Number of edges added.

add_categorical_coupling
add_categorical_coupling(
    aspect,
    groups,
    *,
    weight=1.0,
    directed=False,
    edge_kind=None
)

Add categorical couplings along one aspect.

Parameters:

Name Type Description Default
aspect str

Aspect name to couple over.

required
groups list[list[str]]

Groups of elementary labels to fully connect per node.

required
weight float

Edge weight.

1.0
edge_kind str

The family name carried in the edge id and in the edge_kind attribute, so two coupling schemes over one node pair do not collide.

None

Returns:

Type Description
int

Number of edges added.

add_diagonal_coupling_filter
add_diagonal_coupling_filter(
    layer_filter,
    *,
    weight=1.0,
    directed=False,
    edge_kind=None
)

Add diagonal couplings within a filtered layer subspace.

Parameters:

Name Type Description Default
layer_filter dict[str, set]

Aspect filters (e.g., {"time": {"t1","t2"}}).

required
weight float

Edge weight.

1.0
edge_kind str

The family name carried in the edge id and in the edge_kind attribute, so two coupling schemes over one node pair do not collide.

None

Returns:

Type Description
int

Number of edges added.

tensor_index
tensor_index(layers=None)

Build indices for tensor view.

Parameters:

Name Type Description Default
layers list[str] | list[tuple[str, ...]] | None

Optional subset of layers.

None

Returns:

Type Description
tuple

(nodes, layers_t, node_to_i, layer_to_i).

Examples:

nodes, layers_t, v2i, l2i = G.tensor_index()
adjacency_tensor_view
adjacency_tensor_view(layers=None)

Sparse 4-index adjacency view.

Parameters:

Name Type Description Default
layers list[str] | list[tuple[str, ...]] | None

Optional subset of layers.

None

Returns:

Type Description
dict

{"nodes","layers","node_to_i","layer_to_i","ui","ai","vi","bi","w"}.

Notes

Symmetric entries are emitted twice: (ui, ai, vi, bi) and (vi, bi, ui, ai).

flatten_to_supra
flatten_to_supra(tensor_view)

Flatten a tensor view into a supra adjacency matrix.

Parameters:

Name Type Description Default
tensor_view dict

Output of :meth:adjacency_tensor_view or :meth:unflatten_from_supra.

required

Returns:

Type Description
csr_matrix
unflatten_from_supra
unflatten_from_supra(A, layers=None)

Unflatten a supra adjacency matrix into a tensor view.

Parameters:

Name Type Description Default
A sparray

Supra adjacency matrix.

required
layers list[str] | list[tuple[str, ...]] | None

Optional subset of layers.

None

Returns:

Type Description
dict

Tensor view with the same schema as :meth:adjacency_tensor_view.

supra_adjacency_scaled
supra_adjacency_scaled(
    *, coupling_scale=1.0, include_inter=True, layers=None
)

Build scaled supra adjacency.

Parameters:

Name Type Description Default
coupling_scale float

Scaling factor for coupling edges.

1.0
include_inter bool

Whether to include inter-layer edges.

True
layers list[str] | list[tuple[str, ...]] | None

Optional subset of layers.

None

Returns:

Type Description
csr_matrix
transition_matrix
transition_matrix(layers=None)

Row-stochastic transition matrix P = D^{-1} A.

Parameters:

Name Type Description Default
layers list[str] | list[tuple[str, ...]] | None

Optional subset of layers.

None

Returns:

Type Description
csr_matrix
random_walk_step
random_walk_step(p, layers=None)

One random-walk step p' = p P.

Parameters:

Name Type Description Default
p array - like

Row vector of length |V_M|.

required
layers list[str] | list[tuple[str, ...]] | None

Optional subset of layers.

None

Returns:

Type Description
ndarray
diffusion_step
diffusion_step(x, tau=1.0, kind='comb', layers=None)

One explicit Euler step of diffusion on the supra-graph.

Parameters:

Name Type Description Default
x array - like

State vector of length |V_M|.

required
tau float

Time step.

1.0
kind str

"comb" or "norm".

'comb'
layers list[str] | list[tuple[str, ...]] | None

Optional subset of layers.

None

Returns:

Type Description
ndarray
algebraic_connectivity
algebraic_connectivity(layers=None)

Algebraic connectivity of the supra-graph.

Parameters:

Name Type Description Default
layers list[str] | list[tuple[str, ...]] | None

Optional subset of layers.

None

Returns:

Type Description
tuple[float, ndarray | None]

(lambda_2, fiedler_vector) or (0.0, None) if too small.

k_smallest_laplacian_eigs
k_smallest_laplacian_eigs(k=6, kind='comb', layers=None)

Return k smallest eigenvalues/eigenvectors of the supra-Laplacian.

Parameters:

Name Type Description Default
k int

Number of eigenpairs to compute.

6
kind str

"comb" or "norm".

'comb'
layers list[str] | list[tuple[str, ...]] | None

Optional subset of layers.

None

Returns:

Type Description
tuple[ndarray, ndarray]

(eigenvalues, eigenvectors).

dominant_rw_eigenpair
dominant_rw_eigenpair(layers=None)

Dominant eigenpair of the random-walk operator.

Parameters:

Name Type Description Default
layers list[str] | list[tuple[str, ...]] | None

Optional subset of layers.

None

Returns:

Type Description
tuple[float, ndarray | None]

(lambda_max, v).

sweep_coupling_regime
sweep_coupling_regime(
    scales, metric="algebraic_connectivity", layers=None
)

Scan coupling scales and evaluate a metric.

Parameters:

Name Type Description Default
scales Iterable[float]

Coupling scales to evaluate.

required
metric str | callable

"algebraic_connectivity" or a callable metric(A)->float.

'algebraic_connectivity'
layers list[str] | list[tuple[str, ...]] | None

Optional subset of layers.

None

Returns:

Type Description
list[float]

Metric values aligned with scales.

layer_degree_vectors
layer_degree_vectors(layers=None)

Per-layer degree vectors (intra-layer only).

Parameters:

Name Type Description Default
layers list[str] | list[tuple[str, ...]] | None

Optional subset of layers.

None

Returns:

Type Description
dict

{layer_tuple: (rows_idx_list, deg_vector_np)}.

participation_coefficient
participation_coefficient(layers=None)

Participation coefficient per node.

Parameters:

Name Type Description Default
layers list[str] | list[tuple[str, ...]] | None

Optional subset of layers.

None

Returns:

Type Description
dict[str, float]
versatility
versatility(layers=None)

Versatility proxy based on dominant eigenvector of supra adjacency.

Parameters:

Name Type Description Default
layers list[str] | list[tuple[str, ...]] | None

Optional subset of layers.

None

Returns:

Type Description
dict[str, float]
multislice_modularity
multislice_modularity(
    partition,
    *,
    gamma=1.0,
    omega=1.0,
    include_inter=False,
    layers=None
)

Mucha et al. multislice modularity (scorer only).

Parameters:

Name Type Description Default
partition array - like

Community ids, length |V_M| in the current index.

required
gamma float

Resolution parameter.

1.0
omega float

Coupling strength (binary coupling structure scaled by omega).

1.0
include_inter bool

Whether to include inter-layer (non-diagonal) edges.

False
layers list[str] | list[tuple[str, ...]] | None

Optional subset of layers to score on.

None

Returns:

Type Description
float

Modularity score Q.

Examples:

Q = G.multislice_modularity(partition)

Aspects

An aspect's values, and whether they come one before another. See Aspects, order, and windows.

annnet.core._aspects.Aspect

One aspect of a multilayer graph, and whether its values are ordered.

Parameters:

Name Type Description Default
values Sequence[str]

The elementary labels, in the order they are meant to be read.

required
ordered bool

Whether one value comes before another. An ordered aspect answers :meth:index, :meth:consecutive_pairs, :meth:normalized_position, :meth:before and :meth:after; a categorical one refuses them, because the answer would be the declaration order pretending to be a meaning.

False

Examples:

>>> time = Aspect(['0h', '1h', '12h', '24h'], ordered=True)
>>> time.index('12h')
2
>>> time.consecutive_pairs()
[('0h', '1h'), ('1h', '12h'), ('12h', '24h')]
>>> time.normalized_position('12h')
0.6666666666666666
>>> time.before('12h')
['0h', '1h']

Functions

index
index(value)

The position of one value.

Raises:

Type Description
ValueError

If this aspect is categorical.

KeyError

If the value is not one of this aspect's.

consecutive_pairs
consecutive_pairs()

Every (value, next value) pair, which is what ordinal coupling couples.

normalized_position
normalized_position(value)

The position of one value scaled onto [0, 1].

A one-value aspect answers 0.0 rather than dividing by zero.

before
before(value, inclusive=False)

The values that come before one value.

after
after(value, inclusive=False)

The values that come after one value.

annnet.core._aspects.OrderedLabels

A set of labels that remembers the order they were declared in.

A set was here before, which lost that order: a graph declared with ['basal', 'stim', 'late'] read its layers back as ['basal', 'late', 'stim'], and for an ordinal aspect that is not a cosmetic difference — it is the wrong order, silently.

A dict is an ordered set with the same membership cost, so this is one wrapping thin enough to leave every call site unchanged.

Functions

add
add(label)

Append one label, keeping the position of one already held.

discard
discard(label)

Drop one label if it is held.

update
update(labels)

Append many labels, keeping the positions of those already held.

annnet.core._aspects.BOUNDARIES module-attribute

BOUNDARIES = ('closed', 'open')

annnet.core._aspects.require_boundary

require_boundary(value)

Return value if it names a boundary, and raise otherwise.

Parameters:

Name Type Description Default
value str
required

Returns:

Type Description
str

Raises:

Type Description
ValueError

If value is not one of :data:BOUNDARIES.

annnet.core._aspects.as_aspect

as_aspect(value)

Return one aspect declaration as an :class:Aspect.

A bare sequence is the shorthand for a categorical aspect, which is what every declaration written before :class:Aspect existed is.

Parameters:

Name Type Description Default
value Aspect | Sequence[str]
required

Returns:

Type Description
Aspect

Layer selection

annnet.core._selection.LayerSelection

The layers one window names, and what sits on them.

Built by :meth:LayerAccessor.where. Iterating it gives the layer coordinates; the four properties answer the questions a window is usually asked, each in one pass.

Attributes:

Name Type Description
layers tuple[tuple[str, ...], ...]

The coordinates in the window, in the graph's declaration order.

Attributes

nodes property
nodes

The ids of the nodes on these layers.

node_layers property
node_layers

The (node_id, layer) keys on these layers.

The distinction from :attr:nodes matters as soon as one node sits on two of the selected layers, which is the ordinary case.

edges property
edges

The ids of the edges whose every endpoint is on these layers.

Closed: an edge with one endpoint outside the window is not in it. What that leaves out is :attr:crossing.

crossing property
crossing

The ids of the edges with an endpoint inside and an endpoint outside.

boundary property
boundary

The ids of the nodes inside the window that a crossing edge touches.

Where the window is cut. A node here has a neighbour the window does not hold, so an analysis over :attr:edges alone treats it as though it had none.

annnet.core._selection.parse_predicate

parse_predicate(key, aspects)

Split one aspect__op keyword into its aspect and its operator.

Parameters:

Name Type Description Default
key str

"time", or "time__lte".

required
aspects Sequence[str]

The declared aspects, for the error message and to keep an aspect whose own name contains "__" readable.

required

Returns:

Type Description
tuple[str, str]

(aspect, operator).

Raises:

Type Description
KeyError

If the aspect is not declared.

ValueError

If the operator is not one of :data:OPERATORS.

annnet.core._selection.satisfies

satisfies(aspect, operator, value, wanted)

Whether one layer's value for one aspect satisfies one predicate.

Parameters:

Name Type Description Default
aspect Aspect

The aspect the value belongs to; consulted for order.

required
operator str

One of :data:OPERATORS.

required
value Any

What this layer holds for the aspect.

required
wanted Any

What the predicate asked for.

required

Returns:

Type Description
bool

Raises:

Type Description
ValueError

If a comparison in :data:ORDERED_ONLY is asked of a categorical aspect.

Node-layer values

The two backings a value may live in, the resolver over them, and the array a method is handed. See Node-layer values and scale.

annnet.core._values.ValueMatrix dataclass

One attribute, as an array and the two labels that index it.

What a method wants when it is handed a graph. A frame of Python objects has to be unpacked before any arithmetic; this is the arithmetic's own shape, plus the labels needed to put an answer back.

Attributes:

Name Type Description
values ndarray

len(layers) rows by len(nodes) columns.

nodes list[str]

The node of each column, in order.

layers list[tuple]

The layer of each row, in order.

name str

The attribute read.

Attributes

shape property
shape

The shape of :attr:values.

Functions

__array__
__array__(dtype=None, copy=None)

Read as an array, so numpy.asarray(m) is the values.

annnet.core._values.MatrixValues

An array of values, addressed by two index maps.

One array per attribute name, laid out layer by node, plus the two maps that say which row is which layer and which column is which node. That is the shape a measurement table already has, so attaching one costs building the maps and nothing else — no cell is copied, and the array stays whatever it was.

Parameters:

Name Type Description Default
arrays dict[str, array - like]

One two-dimensional array per attribute name, each len(layers) rows by len(nodes) columns.

required
layers Sequence[tuple]

The layer each row stands for, in row order.

required
nodes Sequence[str]

The node each column stands for, in column order.

required
rows dict

Explicit index maps, when the defaults from layers and nodes are not what is wanted. An explicit column map lets two nodes share one column, which is what a join of many nodes onto one measured entity needs — and it needs it without copying the column, which is the whole reason the array is attached rather than unpacked.

None
columns dict

Explicit index maps, when the defaults from layers and nodes are not what is wanted. An explicit column map lets two nodes share one column, which is what a join of many nodes onto one measured entity needs — and it needs it without copying the column, which is the whole reason the array is attached rather than unpacked.

None
mask array - like

A boolean array the same shape as the values. A cell it gates out holds no value whatever the array carries there.

None

Raises:

Type Description
ValueError

If arrays is empty, if one is not two-dimensional, if one is too small for the maps, or if mask does not match their shape.

Functions

block
block(nodes, layers, name)

(values, answered) for one rectangle, read straight off the array.

This is what the array was attached for. The per-cell path costs a few microseconds a cell and dominates any read of a real measurement table; fancy-indexing the same cells costs one pass in C.

Parameters:

Name Type Description Default
nodes Sequence[str]

The node of each column, in order.

required
layers Sequence[tuple]

The layer of each row, in order.

required
name str

The attribute.

required

Returns:

Type Description
tuple[ndarray, ndarray] | None

The values, and a boolean array of which cells this backing actually answers for. None when it does not hold name at all.

annnet.core._values.ContextualValues

The contextual store, read as a backing.

Canonical for values a caller sets one at a time through :meth:LayerAccessor.set_node_attrs. A pair carrying nothing occupies nothing, which is what makes it right for a sparse, hand-written table and wrong for a dense one.

Functions

block
block(nodes, layers, name)

Always None: a dict has no rectangle to hand back.

Declared rather than left off, so a reader can ask every backing the same question and read the refusal as an answer.

annnet.core._values.ValueResolver

Every backing of one graph, asked in order.

A later backing wins for a cell it can answer, so attaching a table shadows whatever the contextual store held for the same pair rather than blending with it. Two sources for one cell is a conflict, and blending would hide it.

Functions

block
block(nodes, layers, name, default=nan)

One rectangle of values, or None when it cannot be read as one.

Every backing holding name is asked for its rectangle and they are laid over each other in attachment order, so the same "later wins" rule :meth:get follows also holds here — including the second backing an aggregate lands in.

None comes back when some holder has no rectangle to give, which the dict store never does. It means ask cell by cell, and it is never a wrong answer.

Parameters:

Name Type Description Default
nodes Sequence

The columns and rows of the rectangle, in order.

required
layers Sequence

The columns and rows of the rectangle, in order.

required
name str

The attribute.

required
default Any

What a cell no backing answers for holds.

``numpy.nan``

Returns:

Type Description
ndarray | None

len(layers) by len(nodes).

cells
cells(nodes, layers, names, default=nan)

Yield (node_id, layer, name, value) for every asked-for cell.

annnet.core._values.ValueBacking

One place node-layer values may live.

Functions

names
names()

The attribute names this backing can answer for.

get
get(node_id, layer, name, default=None)

The value of one cell, or default when this backing has none.

block
block(nodes, layers, name)

(values, answered) for a rectangle, or None when unable.

layers
layers()

The layers this backing holds a value in.

nodes
nodes()

The nodes this backing holds a value for.