Tensor-Valued Edges in TGraphX: Relations as Structured Feature Maps
Most graph learning treats an edge as a scalar: a 1.0 to say "connected," maybe a weight to say "strongly connected." That is enough for a social link or a citation, but it throws away a lot of structure in problems where the relation itself carries content — a relative pose between two image regions, a learned transformation between two scene objects, a typed predicate between two entities. TGraphX lets an edge carry a feature of the same kinds of shapes a node can: a vector, a [C, H, W] map, or a [C, D, H, W] volume. This note explains what that buys you mathematically and exactly how each layer family consumes it, based on docs/edge_features.md and the layer source.
The companion piece Inside Tensor Message Passing covers the message/aggregate/update skeleton; here we zoom into the E_{i→j} term.
Where edges enter the math
Recall the message step M_{i→j} = φ(X_i, X_j, E_{i→j}). A scalar-weight model degenerates this to M_{i→j} = w_{i→j} · φ(X_i, X_j) — the edge can only rescale a message. A tensor-valued edge restores E_{i→j} as a first-class argument, so the relation can reshape the message, not just dim it up or down. In TGraphX a Graph stores this as edge_features (PyG alias edge_attr), validated against the edge count at construction, and a parallel edge_labels field exists for edge-level supervision such as link classification.
Three supported shapes
The documentation enumerates exactly what is accepted, and which layers handle each:
| Format | Shape | Consumed by |
|---|---|---|
| Vector | [E, D_e] |
GAT (attention bias), SAGE (vector bias), GIN (broadcast bias) |
| 2-D spatial | [E, C_e, H, W] |
ConvMessagePassing, SAGE (concat), GIN (1×1 conv) |
| 3-D volumetric | [E, C_e, D, H, W] |
ConvMessagePassing, SAGE (concat), GIN |
The shape you choose is not cosmetic — it determines which layer can use the edge and how. That is the honest trade-off: richer edge tensors are more expressive but impose compatibility rules.
How each layer family consumes an edge
ConvMessagePassing treats the edge as another feature map to fuse. The source concatenates source, destination, and edge tensors along the channel axis before the 1×1 convolution, which is why docs/edge_features.md states the rule plainly: edge_features.shape[1] must equal in_shape[0] — the edge must have the same channel count as the nodes. A minimal, verified example:
from tgraphx.layers.conv_message import ConvMessagePassing
layer = ConvMessagePassing(in_shape=(16, 8, 8), out_shape=(32, 8, 8),
use_edge_features=True)
out = layer(x, edge_index, edge_features=edge_feats) # edge_feats: [E, 16, 8, 8]
TensorGATLayer uses the edge differently: a vector edge feature becomes an additive bias on the attention logit, per (edge, head). Spatial edge tensors [E, D_e, H_e, W_e] are mean-pooled to vectors before that projection, so the edge's spatial dimensions do not have to match the node's. This is a deliberate design choice — attention wants a scalar-per-head nudge, so a spatial edge is summarised rather than convolved.
TensorGraphSAGELayer is the most flexible on edge_dim: it accepts an edge_features_kind of "vector" (added as a per-edge channel bias) or "spatial" (concatenated to the source before the neighbour transform). If your edge dimensionality does not match the node channels, SAGE or GIN is the path the docs point you to, since ConvMessagePassing requires the channel-match above.
A worked intuition
Think of a graph of detected objects in a scene, each node a [C, H, W] region embedding. An edge could encode the relative geometry between two objects as its own small feature map — orientation, overlap, relative scale rendered spatially. With ConvMessagePassing, that edge map is convolved together with the two object maps, so the message "object A as seen through its relation to B" is computed in feature space rather than reduced to a single number. The same problem with a scalar edge weight could only say "A and B are 0.7-related," which cannot represent how they relate.
Honest boundaries
Three caveats keep this accurate. First, the channel-match constraint on ConvMessagePassing is real; you will hit a shape error if an edge tensor's channel count differs from the node channels, and the fix is to pick SAGE/GIN or reshape. Second, attention layers summarise spatial edges by mean-pooling, so they are not using the full spatial edge structure — if per-location edge reasoning matters, a convolutional layer is the right choice. Third, expressive edge tensors increase memory; whether the extra structure improves a model is task-specific and should be measured, not assumed. None of this is a defect — it is the normal shape bookkeeping that comes with taking edges seriously.
How to describe this accurately
A safe summary for a paper or report: In TGraphX, edges may carry vector, 2-D, or 3-D tensor features (edge_features / edge_attr); convolutional message passing fuses spatial edge maps by channel concatenation, while attention and SAGE layers consume vector or pooled edge features as biases. That statement is backed directly by docs/edge_features.md.
Related guides
- Inside Tensor Message Passing: The φ → AGG → ψ Pipeline
- Knowledge Graph Embedding with Tensor Features
- Tensor-Valued Nodes in Graph Neural Networks
Conclusion
Edges in TGraphX are not limited to scalar weights: they can be vectors or full feature-map tensors, and the layer you choose decides how that structure is used — channel concatenation for convolutional messages, pooled biases for attention. Treating relations as structured objects is the natural counterpart to treating nodes as structured objects, and the per-layer rules in docs/edge_features.md tell you precisely what shapes fit where.