Topic Overview
What is Graph Representation?
Graph representation is the mathematical translation of irregular, relational data into strict matrix formats that a computer can process. It defines how we map a network of entities (nodes) and their relationships (edges) into tensors.
Why does it exist?
Standard deep learning architectures (CNNs, MLPs) require data to exist on a fixed, Euclidean grid. An image is a rigid \(H \times W\) grid of pixels. A sentence is a rigid 1D sequence of words. But real-world data—molecules, social networks, transportation grids—has no fixed structure. Node A might have 2 friends; Node B might have 10,000. There is no “up” or “down” neighbor. Graph representation exists to capture this arbitrary, non-Euclidean topology in a way that allows us to apply linear algebra and neural networks to it.
What problem does it solve?
It solves the “array mismatch” problem. It provides a standardized way to encode topology (who is connected to whom) and features (what are their properties) simultaneously, enabling parallelized computation on GPUs for relational data.
Why should engineers care?
If you work in drug discovery (molecules are graphs), recommendation systems (users and items are graphs), or infrastructure routing, you will use this. Furthermore, if you don’t understand how graphs are represented in memory, you will write code that runs out of VRAM on a graph with only 10,000 nodes because you accidentally allocated a 10,000 x 10,000 dense matrix instead of a sparse one.
Prerequisites
1. Matrices
- Why it is needed: The fundamental trick of graph representation is mapping a topological structure (nodes and lines) into a 2D matrix (rows and columns). You must understand matrix multiplication to understand how information flows from one node to another.
- Where it appears: The Adjacency Matrix \(\mathbf{A}\) and the Feature Matrix \(\mathbf{X}\) are the two core tensors of all Graph Neural Networks.
2. Linear Algebra (Specifically Diagonal Matrices)
- Why it is needed: To prevent neural networks from exploding, we must normalize the graph. This is done using the Degree Matrix \(\mathbf{D}\) and its inverse square root \(\mathbf{D}^{-1/2}\). Understanding how multiplying by a diagonal matrix scales rows and columns is non-negotiable.
- Where it appears: The symmetric normalization step: \(\mathbf{D}^{-1/2}\mathbf{A}\mathbf{D}^{-1/2}\).
Historical Motivation
In the early 2010s, Deep Learning dominated images (CNNs) and text (RNNs). Researchers looked at chemistry, social networks, and knowledge graphs and thought: “Let’s apply CNNs.”
The Engineering Failure: CNNs rely on convolutions—sliding a fixed-size filter over a grid. If you try to slide a \(3 \times 3\) filter over a molecule, the filter falls off the atoms. If you try to pad the molecule to make it a grid, you destroy the chemical topology (bonds). The fundamental data structure did not fit the algorithm.
The Emergence: Researchers realized that a convolution is mathematically just “aggregate your neighbors’ features and apply a weight matrix.” In 2016, Kipf and Welling (GCN) and others realized that if you represent the graph’s topology as an adjacency matrix, the operation \(\mathbf{A}\mathbf{X}\) performs exact neighborhood aggregation using highly optimized sparse matrix multiplication. This transformed an unsolvable topological problem into a standard linear algebra problem.
Intuitive Explanation
Imagine a company org chart. * Nodes: The employees. * Features: Each employee’s skills (Python, Management, Sales)—this is a list of numbers. * Edges: Who reports to whom.
Standard ML wants a spreadsheet. But a spreadsheet implies a rigid grid (Row 1 is always next to Row 2). An org chart is a tree; the CEO has 5 direct reports, but an intern has 0. You can’t put this in a standard spreadsheet without leaving millions of blank cells.
Graph Representation solves this by creating two
separate spreadsheets: 1. The Feature Spreadsheet (\(\mathbf{X}\)): Row \(i\) contains the skills of Employee \(i\). This works perfectly. 2. The
Map Spreadsheet (\(\mathbf{A}\)): A giant grid of 0s
and 1s. If Row \(i\), Column \(j\) is a 1, it means Employee
\(i\) is connected to Employee \(j\). Otherwise, it’s 0.
By keeping the “Map” separate from the “Skills,” we can use matrix multiplication to say: “For every employee, look at the Map to find their boss and peers, grab their Skills, and average them.” That is a Graph Neural Network.
Visual Understanding
Draw a simple network on the left, and a grid on the right. Connect them with arrows.
Left Side (The Graph): * Draw 3 circles: Node 0,
Node 1, Node 2. * Draw a line between 0 and 1. * Draw a line between 0
and 2. * Put a list of numbers inside each circle (e.g.,
[0.5, 1.2]). These are the features.
Right Side (The Matrices): * Top Matrix
(\(\mathbf{X}\)): A \(3 \times 2\) grid. Row 0 is
[0.5, 1.2], Row 1 is [..., Row 2 is
[...]. Show an arrow from the circles to the rows. *
Bottom Matrix (\(\mathbf{A}\)): A \(3 \times 3\) grid. * Row 0:
[0, 1, 1] (Node 0 connects to 1 and 2). * Row 1:
[1, 0, 0] (Node 1 connects to 0). * Row 2:
[1, 0, 0] (Node 2 connects to 0). * Show arrows from the
lines on the graph to the 1s in the matrix.
Visual Insight: Point to the first row of \(\mathbf{A}\) ([0, 1, 1]).
Explain that when you multiply this row by \(\mathbf{X}\), the 0s cancel
out Node 0’s own features, and the 1s grab the
features of Node 1 and Node 2. The matrix row acts as a mechanical “arm”
that reaches out and pulls data from neighbors.
Mathematical Foundations
Let’s formalize the org chart.
1. The Graph Definition \(G = (V, E)\) * \(V\): Set of nodes, \(|V| = n\). * \(E\): Set of edges, \(|E| = m\).
2. The Feature Matrix (\(\mathbf{X}\)) \[\mathbf{X} \in \mathbb{R}^{n \times d}\] * \(n\): Number of nodes. * \(d\): Number of features per node. * Row \(i\) is the feature vector \(\mathbf{x}_i\) for node \(i\).
3. The Adjacency Matrix (\(\mathbf{A}\)) \[\mathbf{A} \in \{0, 1\}^{n \times n}\] * \(A_{ij} = 1\) if edge \((i, j) \in E\). * For an undirected graph, \(\mathbf{A}\) is symmetric (\(A_{ij} = A_{ji}\)).
4. The Degree Matrix (\(\mathbf{D}\)) \[\mathbf{D} = \text{diag}(d_1, d_2, \dots, d_n)\] * \(d_i = \sum_{j} A_{ij}\) (The number of edges connected to node \(i\)).
5. The Symmetric Normalization (The Critical Step) If we just do \(\mathbf{A}\mathbf{X}\), Node 0 will sum the features of Node 1 and Node 2. But Node 1 has 10 neighbors, and Node 2 has 2 neighbors. Node 1’s features will get averaged across 10 nodes, becoming tiny. Node 2’s features will stay large. The neural network will explode or become unstable. We must normalize by the degree. We use the symmetric normalization (often called the Laplacian normalization): \[\tilde{\mathbf{A}} = \mathbf{D}^{-1/2}\mathbf{A}\mathbf{D}^{-1/2}\] * \(\mathbf{D}^{-1/2} = \text{diag}(1/\sqrt{d_1}, \dots, 1/\sqrt{d_n})\). * What this does algebraically: Instead of just summing neighbors, it scales the message from Node \(j\) to Node \(i\) by \(\frac{1}{\sqrt{d_i \cdot d_j}}\). It averages the features symmetrically based on both the sender’s degree and the receiver’s degree.
(Note: In actual GCNs, we add self-loops first: \(\mathbf{A} \leftarrow \mathbf{A} + \mathbf{I}\), so a node includes its own features in the average).
Worked Numerical Example
Let’s explicitly compute one step of feature aggregation for a 3-node graph. * Node 0 connects to 1 and 2. * Node 1 connects to 0. * Node 2 connects to 0. * No self-loops for this basic math example (to keep it clear).
Step 1: Define \(\mathbf{A}\) \[\mathbf{A} = \begin{bmatrix} 0 & 1 & 1 \\ 1 & 0 & 0 \\ 1 & 0 & 0 \end{bmatrix}\]
Step 2: Define \(\mathbf{D}\) Degrees: \(d_0 = 2\), \(d_1 = 1\), \(d_2 = 1\). \[\mathbf{D} = \begin{bmatrix} 2 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}\]
Step 3: Compute \(\mathbf{D}^{-1/2}\) \[\mathbf{D}^{-1/2} = \begin{bmatrix} 1/\sqrt{2} & 0 & 0 \\ 0 & 1/\sqrt{1} & 0 \\ 0 & 0 & 1/\sqrt{1} \end{bmatrix} = \begin{bmatrix} 0.707 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}\]
Step 4: Compute \(\tilde{\mathbf{A}} = \mathbf{D}^{-1/2}\mathbf{A}\mathbf{D}^{-1/2}\) First, do \(\mathbf{A}\mathbf{D}^{-1/2}\) (scales the columns of \(\mathbf{A}\)): \[\begin{bmatrix} 0 & 1 & 1 \\ 1 & 0 & 0 \\ 1 & 0 & 0 \end{bmatrix} \begin{bmatrix} 0.707 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix} = \begin{bmatrix} 0 & 1 & 1 \\ 0.707 & 0 & 0 \\ 0.707 & 0 & 0 \end{bmatrix}\]
Now, multiply by \(\mathbf{D}^{-1/2}\) on the left (scales the rows): \[\begin{bmatrix} 0.707 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} 0 & 1 & 1 \\ 0.707 & 0 & 0 \\ 0.707 & 0 & 0 \end{bmatrix} = \begin{bmatrix} 0 & 0.707 & 0.707 \\ 0.707 & 0 & 0 \\ 0.707 & 0 & 0 \end{bmatrix}\] This is our normalized adjacency matrix.
Step 5: Define Features \(\mathbf{X}\) and Aggregate Let each node have 1 feature (\(d=1\)). \(\mathbf{X} = \begin{bmatrix} 2 \\ 4 \\ 6 \end{bmatrix}\)
Compute new features: \(\mathbf{X}' = \tilde{\mathbf{A}}\mathbf{X}\) \[\mathbf{X}' = \begin{bmatrix} 0 & 0.707 & 0.707 \\ 0.707 & 0 & 0 \\ 0.707 & 0 & 0 \end{bmatrix} \begin{bmatrix} 2 \\ 4 \\ 6 \end{bmatrix}\]
- Node 0 new feature: \((0 \times 2) + (0.707 \times 4) + (0.707 \times 6) = 2.828 + 4.242 = \mathbf{7.07}\) (Average of neighbors, scaled)
- Node 1 new feature: \((0.707 \times 2) + (0 \times 4) + (0 \times 6) = \mathbf{1.414}\) (Feature of Node 0, scaled)
- Node 2 new feature: \((0.707 \times 2) + (0 \times 4) + (0 \times 6) = \mathbf{1.414}\) (Feature of Node 0, scaled)
Computational Interpretation
What happens on the hardware?
The Dense Trap (\(O(n^2)\)): If you naively create \(\mathbf{A}\) as an \(n \times n\) matrix, a graph with 1 million nodes requires a \(1,000,000 \times 1,000,000\) matrix. In FP32, that is 4 Terabytes of memory. Your GPU will instantly crash. Furthermore, multiplying it by \(\mathbf{X}\) takes \(O(n^2)\) operations, but \(99\%\) of those operations are multiplying by zero.
The Sparse Reality (\(O(|E|)\)): Real-world graphs are
incredibly sparse. A social network with 1 billion users might have an
average of 100 friends per user. That’s 100 billion edges. * Memory: We
only store the 1s. We store two arrays: Source nodes
[100B] and Destination nodes [100B]. This
takes ~800 GB, which fits in a distributed cluster, unlike 4 Exabytes. *
Compute: Sparse Matrix-Vector Multiplication (SpMV) skips the zeros. It
only performs \(O(|E|)\)
multiply-accumulate operations.
The GPU Bottleneck: GPUs are designed for dense, regular computation (matrix multiplication in CNNs/Transformers). Sparse graph operations are highly irregular. Fetching the non-zero elements from memory causes random memory accesses, destroying cache coherence. In GNNs, compute is almost never the bottleneck; memory bandwidth is.
Implementation Perspective
1. NumPy (Dense - Educational Only)
import numpy as np
# 3 nodes, 3 edges
A = np.array([[0, 1, 1],
[1, 0, 0],
[1, 0, 0]], dtype=np.float32)
X = np.array([[2], [4], [6]], dtype=np.float32)
# Compute Degree Matrix D
degrees = np.sum(A, axis=1) # [2, 1, 1]
D_inv_sqrt = np.diag(1.0 / np.sqrt(degrees))
# Compute normalized adjacency A_tilde
A_tilde = D_inv_sqrt @ A @ D_inv_sqrt
# Aggregate features (Message Passing!)
X_new = A_tilde @ X
print(X_new)
# Output matches our manual calc: [[7.07], [1.414], [1.414]]2. PyTorch Geometric (Sparse - Production Standard)
PyTorch Geometric (PyG) does not use the Adjacency Matrix \(\mathbf{A}\). It uses COO Format (Coordinate Format).
import torch
from torch_geometric.data import Data
# Node features: [num_nodes, feature_dim]
x = torch.tensor([[2.0], [4.0], [6.0]])
# Edge Index: [2, num_edges]
# Row 0 = Source nodes, Row 1 = Destination nodes
# Edge 0->1, Edge 0->2, Edge 1->0, Edge 2->0
edge_index = torch.tensor([[0, 0, 1, 2],
[1, 2, 0, 0]], dtype=torch.long)
# Create the graph object
graph = Data(x=x, edge_index=edge_index)
print(graph)
# Data(x=[3, 1], edge_index=[2, 4])
# UNDER THE HOOD:
# When PyG executes a GCN, it does NOT build matrix A.
# It reads edge_index[0] (sources) and edge_index[1] (destinations).
# It uses torch.scatter_add_ to directly add features from source nodes
# to destination nodes, computing D^(-1/2) on the fly.Why COO? It requires exactly \(2 \times |E|\) integers to store the entire topology, compared to \(n \times n\) floats for a dense matrix. It is the only way graphs scale on modern hardware.
Where It Appears In AI
| AI Component | Exact Usage |
|---|---|
| Graph Convolutional Networks (GCN) | The core operation \(\tilde{\mathbf{A}}\mathbf{X}\mathbf{W}\) is executed via sparse-dense matrix multiplication using the graph representation. |
| Molecular Property Prediction | Atoms are nodes (features: atomic number, charge). Bonds are edges (features: single, double, aromatic). Representation is the input to drug discovery models (e.g., ChemProp). |
| Knowledge Graphs (RAG) | Entities (e.g., “Albert Einstein”) are nodes. Relations (e.g., “born in”) are typed edges. Used in reasoning models like R-GCN or GraphRAG to retrieve multi-hop logical facts. |
| 3D Point Cloud Processing | Raw LiDAR points have no structure. Models like PointNet++ dynamically construct a k-Nearest Neighbor graph (nodes = points, edges = proximity) to represent local geometry. |
| Recommender Systems | Bipartite graph representation. Users are nodes, Items are nodes, Interactions (clicks/purchases) are edges. Used in PinSage (Pinterest) or GraphSAGE. |
Deep Dive Into Real Models
Graph Convolutional Network (GCN) Layer
Let’s look at the exact tensor mechanics inside a single GCN layer.
Equation: \[\mathbf{H}^{(l+1)} = \sigma\left( \tilde{\mathbf{A}} \mathbf{H}^{(l)} \mathbf{W}^{(l)} \right)\]
- \(\mathbf{H}^{(l)} \in \mathbb{R}^{n \times d_{in}}\): Node representations at layer \(l\) (initially \(\mathbf{X}\)).
- \(\mathbf{W}^{(l)} \in \mathbb{R}^{d_{in} \times d_{out}}\): A standard Neural Network weight matrix.
- \(\tilde{\mathbf{A}} \in \mathbb{R}^{n \times n}\): The sparse, normalized adjacency matrix.
The Two-Step Execution (Order Matters for Memory!): 1. \(\mathbf{Z} = \mathbf{H}^{(l)} \mathbf{W}^{(l)}\) (Dense Multiplication): We multiply the \([n, d_{in}]\) features by the \([d_{in}, d_{out}]\) weights. This is standard, highly optimized GEMM on the GPU. Output is \([n, d_{out}]\). 2. \(\mathbf{H}^{(l+1)} = \tilde{\mathbf{A}} \mathbf{Z}\) (Sparse-Dense Multiplication): We multiply the sparse adjacency matrix by the dense feature matrix. This is a SpMV operation (Sparse Matrix - Dense Vector/Matrix).
Engineering Failure: If you do \(\tilde{\mathbf{A}}\mathbf{H}^{(l)}\) first, you propagate \(d_{in}\) features, then multiply by \(\mathbf{W}\). This requires \(O(|E| \cdot d_{in})\) memory bandwidth. By doing \(\mathbf{H}^{(l)}\mathbf{W}\) first, you project to a lower dimension (e.g., \(d_{out} = 64\)), and the sparse multiplication only costs \(O(|E| \cdot 64)\). PyTorch Geometric enforces this order for performance.
Failure Modes
1. The Self-Loop Omission
- Cause: Constructing \(\tilde{\mathbf{A}}\) strictly from \(\mathbf{A}\) without adding the Identity matrix \(\mathbf{I}\).
- Symptoms: The model trains but converges incredibly slowly, or node representations “forget” their own identity and become an indistinguishable mush of their neighbors’ features.
- Solution: Always compute \(\tilde{\mathbf{A}} = \mathbf{D}^{-1/2}(\mathbf{A} + \mathbf{I})\mathbf{D}^{-1/2}\). Ensure \(\mathbf{D}\) is recalculated after adding \(\mathbf{I}\).
2. Directed Graph Assumptions
- Cause: Using an undirected GCN on a directed graph (e.g., Twitter followers). If A follows B, that doesn’t mean B follows A.
- Symptoms: Information leaks backward. The model assumes a mutual relationship where none exists.
- Solution: Use directed GNNs (like GAT or directed GraphSAGE) or represent the directed graph as two separate adjacency matrices (in-degree and out-degree).
3. Dense Matrix Allocation in Custom Code
- Cause: Converting
edge_indexto a denseAmatrix usingtorch.sparse.to_dense(). - Symptoms: CUDA Out Of Memory error on graphs with \(> 20,000\) nodes.
- Solution: Never materialize the adjacency matrix.
Operate entirely on
edge_indexusingtorch_scatteror PyG’s built-inMessagePassingbase class.
Engineering Insights
1. Mini-Batching is Hard In CNNs, a batch is just
stacking images: [B, C, H, W]. In GNNs, a graph has a
variable number of nodes and edges. How do you batch them?
Insight: PyG uses a trick called “Block Diagonal Matrix”
concatenation. It logically stacks the adjacency matrices of Graph 1,
Graph 2, and Graph 3 along the diagonal. Node 0 of Graph 2 becomes Node
\(N_1\) in the batch. The
edge_index is just offset by the number of nodes in
previous graphs. This allows batching without any padding.
2. The Neighborhood Explosion In graphs like Twitter (power-law distribution), some nodes have millions of edges. If a 2-layer GNN samples 10 neighbors per node, a node with 1M edges requires processing 10M nodes in one forward pass. Insight: Production systems (like PinSage) use Graph Sampling (randomly picking a fixed number of neighbors per node) rather than computing the exact full matrix multiplication, trading a tiny amount of accuracy for the ability to train at all.
Interview Questions
Beginner
Q: What are the three core matrices used to represent a graph in a neural network? A: The Feature Matrix \(\mathbf{X}\) (\(n \times d\)), which holds node attributes; the Adjacency Matrix \(\mathbf{A}\) (\(n \times n\)), which defines the topology (who is connected to whom); and the Degree Matrix \(\mathbf{D}\) (\(n \times n\)), a diagonal matrix holding the number of connections per node, used for normalization.
Intermediate
Q: Why do we use the symmetric normalization \(\mathbf{D}^{-1/2}\mathbf{A}\mathbf{D}^{-1/2}\) instead of just dividing by the degree \(\mathbf{D}^{-1}\mathbf{A}\)? A: Simple division by degree (\(\mathbf{D}^{-1}\mathbf{A}\)) creates an asymmetric normalization. The feature vector of a high-degree node gets split into many tiny pieces and sent to neighbors, but the high-degree node receives full-sized vectors from its low-degree neighbors. This causes the feature norms to explode as you go deeper into the network. The symmetric normalization scales by \(\frac{1}{\sqrt{d_i \cdot d_j}}\), which acts like a symmetric, doubly-stochastic matrix, preserving the norm of the feature vectors across layers.
Advanced
Q: In PyTorch Geometric, why is the edge_index
stored as a tensor of shape [2, num_edges] instead of an
\(n \times n\) adjacency
matrix? A: Memory and computational
efficiency. Real-world graphs are extremely sparse (e.g., \(|E| \approx n \times 100\), whereas \(n \times n \approx n^2\)). Storing an \(n \times n\) dense matrix in FP32 for a
1-million node graph requires 4 Terabytes of VRAM, which is impossible.
The COO format [2, num_edges] requires only \(2 \times |E| \times 4\) bytes. Furthermore,
modern GPUs are optimized for dense matrix multiplication, so GNN
frameworks use this COO format to perform Scatter/Gather operations
directly on the feature matrix \(\mathbf{X}\), completely avoiding the
construction of the adjacency matrix in memory.
Research
Q: How does the Graph Representation change when edges have features (e.g., different bond types in a molecule)? A: We introduce an Edge Feature Matrix \(\mathbf{E} \in \mathbb{R}^{|E| \times d_e}\). Simple adjacency multiplication \(\mathbf{A}\mathbf{X}\) is no longer sufficient because the edge type modulates the message. Models like Relational-GCN (R-GCN) or Gated Graph Convolutional Networks solve this by defining separate weight matrices \(\mathbf{W}_r\) for each relation type \(r\), or by using attention mechanisms where the edge features compute the attention coefficients between nodes.
Research Perspective
Current Limitations: Standard graph representation assumes a static snapshot. In reality, social networks and financial transaction graphs change every millisecond. Furthermore, standard representations treat nodes as points in Euclidean space, which is mathematically flawed for trees or spheres (negative curvature).
Modern Directions: 1. Temporal Graph
Networks (TGNs): Extending the representation to include a time
dimension. Edge index becomes [2, E] and edge features
include a timestamp vector, requiring continuous-time dynamics models.
2. Hypergraph Representations: Standard graphs have
1-to-1 edges (pairwise). Hypergraphs have hyperedges that connect
any number of nodes simultaneously (e.g., a group chat, or a
chemical reaction involving 3 reagents). The adjacency “matrix” becomes
a 3D tensor or an incidence matrix. 3. Geometric Deep Learning
(Manifolds): Moving away from matrix representations entirely
and representing graphs as continuous Riemannian manifolds, allowing
graphs to be processed natively on curved spaces without
discretization.
Connections
Prerequisites: * Matrices and Linear Algebra (Multiplication, Diagonal matrices) * Sparse Computation Concepts (Memory vs. Compute tradeoffs)
Enables: * Message Passing Neural Networks (MPNNs) * Graph Convolutional Networks (GCNs) * Graph Attention Networks (GATs)
Used by: * Molecular Generation Models (Drug Discovery) * Recommender Systems (Pinterest, Uber) * Large Language Model Reasoning (Knowledge Graphs, GraphRAG)
Extended by: * Heterogeneous Graph Networks (Multiple node/edge types) * Dynamic Graph Networks (Time-evolving edges) * 3D Geometric Learning (Point Clouds as k-NN graphs)
Related concepts: * Adjacency List (The software engineering data structure equivalent) * Laplacian Matrix (\(\mathbf{L} = \mathbf{D} - \mathbf{A}\), used in spectral graph theory) * Scatter/Gather Operations (The low-level GPU primitives used to execute graph ops)
Final Mental Model
To permanently remember Graph Representation, visualize the “Matrix-Based Zip Code System.”
Imagine a city with no grid—houses are connected by random winding roads. Standard ML forces you to bulldoze the city into a perfect square grid (CNN) or a single long highway (RNN). Most houses end up in the wrong place.
Graph Representation leaves the city exactly as it is. You create a
Registry (\(\mathbf{X}\)): a list of every
house’s attributes (paint color, size). You create a Phone Book
(\(\mathbf{A}\)): a massive
grid where a 1 at Row 50, Column 12 means “House 50 has a
direct road to House 12.”
To figure out what the neighborhood looks like, you don’t look out the window. You take Row 50 from the Phone Book, use it as a mask to pull data from the Registry, and average it. The topology is dead, static numbers; the features are moving data. This separation is the key to making irregular structures run on rigid hardware.
Comments
Post a Comment