Tensor Shape Semantics for GNN Researchers: A Notation Guide
Half the confusion in tensor-aware graph learning is notational. A paper writes X ∈ ℝ^{N×D} and everyone agrees; switch to [N, C, H, W] node features and suddenly authors disagree on what each axis means, which axes a layer touches, and where the batch dimension hides. This note proposes a consistent notation for tensor-valued graphs and ties every symbol to the corresponding object in TGraphX. It is meant as a reference you can adopt in your own write-ups so that a shape claim is unambiguous.
It formalises the vocabulary used operationally in Shape Algebra Through GNN Layers.
The core symbols
Fix a graph with N nodes and E directed edges. The objects and their shapes:
| Symbol | Meaning | Shape | TGraphX object |
|---|---|---|---|
N |
number of nodes | scalar | g.num_nodes |
E |
number of edges | scalar | g.num_edges |
X |
node features | [N, *s] |
g.node_features / g.x |
s |
per-node trailing shape | tuple, e.g. (C, H, W) |
g.feature_shape |
A |
edge index | [2, E] |
g.edge_index |
Eᶠ |
edge features | [E, *sₑ] |
g.edge_features / g.edge_attr |
M |
per-edge messages | [E, *s'] |
output of message() |
B |
number of graphs in a batch | scalar | batch.num_graphs |
b |
node→graph map | [N_total] |
batch.batch |
The single most important convention is the distinction between the leading count axis (N for nodes, E for edges, B for graphs) and the trailing shape s. TGraphX makes this explicit: feature_shape reports s (the trailing shape only), and layers declare in_shape/out_shape as trailing shapes, never including N. Adopting "leading count, trailing shape" in your notation removes most ambiguity.
Rank conventions by modality
The trailing shape s encodes the modality of a node. The standard ranks:
Rank of X |
s |
Models | Example node |
|---|---|---|---|
| 2 | (D,) |
classic GNNs | embedding vector |
| 3 | (C, L) |
sequence | token/time series |
| 4 | (C, H, W) |
image | patch feature map |
| 5 | (C, D, H, W) |
volume | medical scan block |
This is the framework's reason for existing, stated in the arXiv preprint: preserve s instead of flattening it to (∏ s,). The rank tells a reader immediately what kind of structure each node carries, which is far more legible than a flattened dimension count.
Shape transformations through a forward pass
With the symbols fixed, the message-passing math from the φ→AGG→ψ article has unambiguous shapes:
message: φ : (X_i, X_j, Eᶠ_{ij}) ↦ M_{ij} # [E, *s']
aggregate: AGG : { M_{ij} }_{i∈N(j)} ↦ A_j # [E, *s'] → [N, *s']
update: ψ : (X_j, A_j) ↦ X'_j # [N, *s'']
pool: R : { X'_v }_{v∈Gᵢ} ↦ z_{Gᵢ} # [N, *] → [B, *]
Note the aggregation step is the only one that changes the leading axis (from E edges to N nodes), and pooling is the only one that goes from N to B. Layers change the trailing shape (s → s'). Keeping "which axis changed" explicit in notation is exactly what prevents the classic confusion between collapsing nodes and collapsing spatial dimensions.
Edge rank conventions
Edges follow the same leading-count, trailing-shape rule. Per docs/edge_features.md, Eᶠ may be [E, D_e] (vector), [E, C_e, H, W] (2-D), or [E, C_e, D, H, W] (3-D), and g.edge_feature_shape reports the trailing part. Writing Eᶠ ∈ ℝ^{E×C_e×H×W} in a paper is then unambiguous about both the count axis and the structure.
Honest framing
Two caveats. First, notation is a convention, not an enforced contract — TGraphX validates ranks and counts (via validate_graph and assert_tensor_native), but it cannot know that your (C, H, W) means "channels, height, width" rather than something else; you must document the semantics of s. Second, this guide standardises the common ranks; exotic node structures are possible, and you should state their shape explicitly rather than assuming a reader infers it. The value here is a shared default that makes the usual cases unambiguous.
The notation applied end to end
To see the notation earn its keep, apply it to a full pipeline. Suppose X ∈ ℝ^{N×3×8×8} (image-patch nodes), A ∈ ℤ^{2×E} (patch adjacency), and a two-layer convolutional model. Layer one maps the trailing shape (3,8,8) → (32,8,8), so X becomes ℝ^{N×32×8×8}; the leading N is untouched. Layer two maps (32,8,8) → (64,8,8). A spatial mean over H,W then reduces the trailing shape to (64,), giving ℝ^{N×64}. Finally global_mean_pool reduces the leading axis using b, producing ℝ^{B×64}, and a linear head gives ℝ^{B×C}.
Written this way, every transition names exactly which axis changed — the trailing shape in the layers, the spatial axes in the reduction, the leading axis in the pool. A reader (or reviewer) can verify the model's shape logic without running it. That is the practical payoff of disciplined notation: the forward pass becomes auditable on paper, and a shape bug becomes a line you can point at rather than a runtime mystery.
Related guides
Conclusion
A consistent notation — leading count axis, trailing shape s, explicit rank by modality — removes most of the ambiguity in tensor-valued graph learning, and it maps cleanly onto TGraphX's feature_shape, edge_feature_shape, and in_shape/out_shape. Adopt it in your own write-ups, document what each axis of s means, and your shape claims become checkable rather than guessable.