2026-05-14 · paper
There and Back Again: From Arrows to Tables
Work in progress. Picks up where part one left off; same cast, same slow pace.
Last time we built the RDPG recipe. Every node gets a yellow arrow and a blue arrow, every potential edge is a coin flip whose bias is the dot product of the giver’s yellow and the receiver’s blue. The bigger that number, the more likely the edge. We did that with two nodes, Anubis and Bastet, and we waved at the world with the kind of confidence you can have when you only have two of anything.
This time the cast widens. Anubis and Bastet are joined by the rest of the Egyptian cosmogony: Ra, Isis, Osiris, Horus, Thoth, and friends, related to one another in all the messy mythical ways the texts allow. (I’m building the real pantheon network for a later post; for today the extras stay abstract.) Having more than two nodes is what lets us ask the question the whole research programme is really about: if all we get to see is the network, and not the arrows themselves, can we recover the positions of the nodes that produced it?
Here is the path for today. First we turn arrows into rows of numbers and stack those rows into tables. Then the yellow and blue tables make a green table of probabilities. Finally, from one noisy graph drawn from that green table, we ask how much of the hidden geometry can be found again.
Portraits by Jeff Dahl, CC BY-SA 4.0.
Let’s start by changing how we draw the arrows.
From an arrow to a list of numbers
An arrow in two dimensions is two numbers (an coordinate and a coordinate). An arrow in three dimensions is three numbers. An arrow in dimensions is numbers. We can write each arrow as a row of numbers, left to right, one entry per dimension. There’s an equivalent way to read those numbers, and it’s the one we’ll use from here on: they are the coordinates of a single point in -dimensional space, the tip of the arrow, with the shaft no longer drawn.
Each of those numbers is a coordinate along one axis. Anubis’s first yellow coordinate might be how funereal he is, his second how meticulous, his third how dog-like; whatever the abstract axes are meant to capture, his coordinates pin him to a point in yellow space, his identity-as-a-giver. The same holds for blue space: every node also has a point there, its identity as a receiver. The two positions together, one in each space, fully describe that node in the model.
There’s a catch hiding in that “might be”, and it’s worth flagging now. No single coordinate carries a meaning on its own. As we’ll see by the end of this post, the embedding is pinned down only up to a rotation (the thread you may have tugged with the rotate-together toggle in part one), and rotating reshuffles the individual coordinates while leaving all the geometry untouched. So “funereal” or “meticulous” cannot live on any one axis; what survives a rotation, and can therefore carry real meaning, are the lengths of the arrows, the distances between them, and the angles between them. A later post will read the cloud with exactly that in mind.
We also don’t always know what the axes are meant to capture. Often they’re implicit, found by the data; in that case we use them without bothering to name them, and the model still works.
Stacking arrows into matrices
Once each arrow is a list of numbers, nodes give us such lists, yellow and blue. Each colour stacks into its own table: rows of numbers. Mathematicians call that kind of table a matrix; this one has rows and columns, so we call it an matrix.
We end up with two matrices. A yellow matrix , whose row is node ‘s yellow coordinates. A blue matrix , whose row is node ‘s blue coordinates. Both are . You can read each matrix two ways: as a stack of arrows (one per row, the way we built it) or as a cloud of points in -dimensional space, each row a point. The point-cloud reading is the one that serves the geometry.
Each tip leaves its space, settles into a row of the matrix, and gives up its two coordinates: first as the colour intensity of two cells, deeper for larger values, and then as the literal numbers inside them. Same construction in yellow and in blue. From here on we’ll mostly draw and think about the tips, not the shafts.
The point of stacking is that we get to work with the whole population at once. When the arrows live one-per-node, we can only reach for them one or two at a time. Once they’re in matrices, we can operate on all nodes at once. We can stop counting too: becomes just a row index, and what we’ll do next works the same whatever value it takes.
Multiplying gives the bias matrix
We want all bias values at once: one for each directed pair of nodes, each one the dot product of one node’s yellow row against another’s blue row. Matrix multiplication computes every such dot product in a single operation. We multiply and , in the matrix sense of the word.
Matrix multiplication is a row-by-column operation: cell of the product is built by taking row of the left matrix, pairing it entry by entry with column of the right matrix, and summing the products. For that to make sense, the row and the column must have the same length. ’s rows have entries; so do ‘s rows, but its columns have . We transpose to swap rows and columns: row of becomes column of , same numbers, written along a column instead of along a row.
Now we can multiply: . Slot of the result is built by combining row of , which is node ‘s yellow arrow, with column of , which is node ‘s blue arrow. The pairing-and-summing rule turns those two arrows into a single number, and from part one we already know what that number is: the bias of the coin for the edge from node to node .
The product matrix therefore collects every coin bias the model has to offer, all of them, in one place. Call it , the bias matrix: each entry a number between zero and one, with larger entries meaning more likely edges. To keep the colour palette going, think of it as a green matrix, mixing the warm yellow and the cool blue.
opens beside and then pivots into its transpose , each cell travelling from its row and column in to its column and row in . The bias matrix then assembles cell by cell. The first entry, , is constructed in detail: row 3 of and column 1 of light up, leave their matrices, pair up entry by entry, multiply into green cells, and sum to . That value drops into . The right-hand inset shows the same dot product geometrically, as shadow rectangles of equal area along and . A quick sweep then fills the remaining fifteen cells.
The pairing-and-summing operation has a name we’ve already met. The dot product showed up in part one as a geometric object: the area of two equal shadow rectangles, or . There’s an equivalent algebraic recipe for it. If formulas are friendly to you, here is the same rule in symbols. If not, the animation has already carried the idea: pair the entries, multiply each pair, add the results.
If two arrows in dimensions are written as lists of numbers, and , their dot product is
In words: multiply the corresponding entries of the two lists, then add up the products. The geometric definition and this algebraic one are the same quantity, computed two different ways. You can check it by hand in two dimensions. For an intuition in general, imagine rotating the coordinate system so lies along the first axis. Then has only one nonzero component, , and the algebraic recipe collapses to times the first component of , which is exactly . The recipe doesn’t care which coordinate system we picked, so the formula holds in any dimension.
Every entry of is a single number between zero and one (the positive-orthant housekeeping from part one is what keeps it there). To generate a random network, flip coins, one per entry of , each coin’s bias being the corresponding entry. The result is an adjacency matrix of zeros and ones, with if the edge from to fired and otherwise.
Let’s pause. The yellow and blue tables make a green table of probabilities. That green table is not the network yet. It is the set of coin biases from which the network is drawn. The network we see is one noisy realization of that hidden green table.
What we get to see, and what we don’t
So far we’ve worked forward, from arrows to graph. Now we’re on the observer’s side, where the graph is all we have.
When we look at a real network, what we have is one draw of . We don’t have , , or ; one adjacency matrix of zeros and ones is all we get to work with.
The model says was generated from by independent coin flips. So is a noisy version of . If we could observe many independent draws of the same network (the same , repeated) and average them, the average would converge to as the number of draws grew. With infinitely many draws, the average of all the s would equal exactly. That’s the Law of Large Numbers, working entry by entry of the matrix.
The top two panels show the fixed yellow and blue tips of eight nodes, split into two clusters of four. Those tips produce a fixed bias matrix with green block structure (bottom left): high biases within each cluster, lower between. Each press of resample draws a fresh from the same (bottom right), dense within clusters and sparse between, but with the exact pattern of edges shifting every draw.
This is rarely how networks come to us. Usually we see one snapshot. It’s still useful to keep both regimes in mind: with one , we have to do statistics; with many independent draws of the same , we can almost see directly.
SVD and the recovery question
Suppose we have . Suppose we believe the RDPG model: is sampled from , each entry of an independent coin flip whose bias is the corresponding entry of . Can we recover and from ?
This is a matrix-factorisation question. We want to find two thin matrices ( each, with much smaller than ) whose product is approximately .
Another way to say the same thing: if this big table is mostly made from a few simple patterns, what are those patterns?
The classical tool for this is the Singular Value Decomposition. Loosely: hidden inside any matrix is an ordered list of orthogonal directions that capture its structure, from most important to least. The first direction is the one along which the matrix has the most variation; the second, perpendicular to the first, has the next-most; and so on. Each direction comes with a number, the singular value, that says how big the variation along it is.
For our , the underlying has only nonzero singular values: as the product of two thin matrices, only directions carry any structure; past those, the singular values are zero. is plus coin-flip noise, which leaks small extra singular values past the first . The signal stays concentrated in the top ; the rest is noise to throw away.
Formally, the SVD writes as , where and are orthogonal (their columns are perpendicular to each other and have length one) and is diagonal, with the singular values in decreasing order. The columns of are the row-side directions, the columns of the column-side directions, and the singular values the weights.
The truncated SVD keeps only the top singular values and their matching columns of and . It produces the best rank- approximation of : of every matrix of rank at most , the truncated SVD gives the closest to .
Try this widget first with and , then with and . The middle panel should start to look much more like the true green matrix as the population grows.
The rank- truncation is the best rank- approximation of the observed graph . At it is aiming for the signal we care about, but at small the coin-flip noise still gets in the way. As grows, the top two singular values pull clear of the noise floor, and starts to resemble the hidden .
The SVD writes as a product of three matrices, , , and . We want two matrices whose product approximates . So we need to fold the middle one into the other two.
The truncated SVD, , is the best rank- approximation of . (If we have many independent draws from the same , we run the SVD on their average instead, which has less coin-flip noise to fight through.) Three matrices, still, not two.
The folding is easier than it looks, because is special. It’s a diagonal matrix: only entries on its diagonal, , and zero everywhere else. Multiplying anything by just scales each direction by the corresponding . That scaling can be split in two: scale by on one side, scale by on the other, and you recover the full scaling.
Let denote the diagonal matrix with in its -th slot. Then : the “matrix square root” of a diagonal matrix is just the entry-wise square root. For instance, if and the top two singular values happen to be and , then
Now tuck one copy of into on the left and the other into on the right:
The two thin matrices we wanted are right there:
By construction, is the best rank- approximation of . Under reasonable assumptions, and converge to the true and as the number of nodes grows.
This whole recipe, taking the truncated SVD of the adjacency matrix and folding the square root of the singular values into each side, has a name we’ll reuse throughout the series: the adjacency spectral embedding, or ASE. It is the workhorse of RDPG estimation, the standard way to turn one observed network back into a cloud of estimated positions.
Why more nodes help
The last clause, “as the number of nodes grows”, is doing real work. Here’s why more nodes tighten the estimate. Each node is, in a sense, a probe of the others.
Consider Anubis. We want to know his yellow arrow. The information about that arrow lives entirely in the row of that records who Anubis sends edges to. That row has entries, one per potential receiver. Each entry is a coin flip whose bias is Anubis’s yellow against that receiver’s blue. So each receiver is, in effect, a slightly different probe of Anubis’s outgoing personality.
If we have only two nodes (Anubis and Bastet), Anubis’s yellow gets probed by exactly one other arrow, Bastet’s blue, for the single edge from Anubis to Bastet. One probe, one coin flip, almost no information. Anubis’s yellow could be anything compatible with that one flip.
If we have a hundred nodes, Anubis’s yellow is probed against a hundred different blue arrows. Each probe is a noisy measurement, but a hundred noisy measurements pinned to a hundred different reference directions can pin down a vector pretty tightly. The same logic applies for the blue arrows, working through the columns of .
The same Anubis at three sample sizes. Top row: the network we get to see, Anubis with directed edges out to the other nodes; solid edges fired, dashed grey ones didn’t. Neither his yellow nor any of the blues is visible to us. Bottom row: the feasible region for his yellow, the positive orthant intersected with the unit disk, shaded by how much weight the data places on each point. With few outgoing edges to learn from, the density is broad across the region; with many, it concentrates to a peak. The peak is sharp but not a single point: even with arbitrarily many observations there is a rotation we can’t get past, which we’ll meet in a moment.
More nodes does double work: a bigger network to look at, and more probes per node from which to triangulate that node’s arrows.
The rotation we left waiting
The SVD recipe works, but the “converge to the true and ” clause needs a qualifier. If you played with the rotate-together toggle in part one, you might already see it coming.
The dot product of two arrows is invariant under rotation. Rotate and together by the same angle, and every entry of and every entry of moves, but the bias matrix doesn’t budge at all. Watch:
The latent tips spin together around the origin (left), and the entries of and swing through new values each frame, some of them turning negative as the cloud leaves the positive orthant. The green matrix on the right sits perfectly still: every cell holds its value the whole way around. That’s the invariance.
So the positions of the nodes, as the dot-product rule sees them, are only defined up to a common orthogonal rotation. The SVD picks one particular rotation (the one that aligns the axes with the principal directions of the data). Any other rotation, applied to both and together, would be just as good an answer.
This is a structural feature of the model: identifiability of node positions, in RDPG, is up to rotation. When you do downstream work (clustering, alignment, comparing two networks) you have to live with it. Most of the time you either ignore it (clustering is rotation-invariant anyway) or fix it (find a particular rotation that lines the answer up with something you care about). We’ll come back to the alignment question in a later post.
Where we go next
That’s the basic recovery story. Stack arrows into matrices, multiply to get the bias matrix, flip coins to get the observed adjacency, do truncated SVD to get the arrows back, remember the rotation.
Two large questions stay on the table for later. First, what happens when the network changes over time: the arrows drift along some path, the bias matrix moves with them, and the sequence of observed graphs describes a dynamical system. The rotation ambiguity from this post turns into a real obstacle to recovering the underlying dynamics, and the SVD recovery brings its own artifacts when you try to track trajectories through a time series of graphs. Second, what if the population of nodes isn’t a fixed list but a random sprinkling drawn from an intensity over space: the arrows become a continuous cloud rather than a finite set of tips, and the bias matrix becomes a heat map over that space, with classical RDPGs emerging as a concentrated limit. These are where my recent papers live, and where the next posts in the series will go.
For now, here’s where we’ve arrived. From the arrow of a single node, to two stacked matrices; from those matrices to a bias matrix and then a random network; and from a random network, via SVD, back to estimates of the arrows.
The surprising part is that one messy network can still carry the shadow of the geometry that made it. From the visible pattern of who reached toward whom, we can begin to infer the hidden arrangement of tendencies: who gives, who receives, who is aligned with whom. Anubis and Bastet’s mutual desire was never written directly in the graph. It only appeared as a chance of connection, then as a coin flip, then as an edge. But with enough company around them, enough other gods acting as witnesses, the shape of that desire starts to become recoverable.