Skip to content

Jamie Little

Computer Science at the University of Cambridge. Building high-performance systems and full-stack applications.

Highlights

Achievement Featured

Launched Logik v1

Successfully launched the first major release of Logik, an educational computational logic framework built in Java. It allows users to build and simulate complex logic circuits.

View
Award Featured

S. Wentworth Computer Science Award

Presented the Computer Science Award at the University of Liverpool Maths School Awards Evening 2025, in recognition of outstanding achievement in Computer Science.

View
Achievement Featured

4 A*s - Off to Cambridge

A* in Maths, Further Maths, Physics, and Computer Science. Heading to St John's College, University of Cambridge to read Computer Science.

View

Projects

View all →
Featured

A from-scratch Raft consensus library in Rust, paired with a fault-tolerant key–value store over gRPC.

Rust Raft Distributed Systems

A working implementation of the Raft consensus algorithm, built from scratch in Rust. The core Raft protocol — leader election, log replication, and safety — runs inside a single-threaded async event loop and sits underneath a replicated key–value store accessible over gRPC.

What Raft does

Raft is a protocol for getting a group of machines to agree on a sequence of commands, even when some of them crash or lose connectivity. All three subproblems are tackled separately:

  • Leader election — the cluster picks one node to coordinate writes. If the leader goes quiet, a follower steps up and runs an election. Only one leader wins per term, and only nodes whose logs are at least as up-to-date as a majority of the cluster can win.
  • Log replication — every write goes through the leader, which appends it to a replicated log and waits for a majority of followers to confirm before committing. Once committed, the entry is guaranteed to survive leadership changes.
  • Safety — the election restriction prevents committed entries from being overwritten when a new leader takes over. A candidate must have a log at least as current as a majority of the cluster to be elected.

KV store

On top of Raft sits a concurrent key–value store backed by a DashMap. Clients call Put, Get, and Delete via gRPC. Reads are served locally from whichever replica you hit; writes go through the leader and are replicated before being acknowledged.

Persistence

Every log entry is written to a JSON-line file (the write-ahead log) before a client gets a response. On restart the log is replayed to restore state. When the log grows large, the node takes a snapshot of the state machine and discards older entries.

Transport

The same Raft core can run in two modes:

  • gRPC — each node listens on an HTTP/2 socket. RPCs between nodes and client requests share a single .proto definition.
  • Simulated (in-memory) — nodes talk through tokio channels with zero network overhead. The integration tests use this to exercise the full consensus protocol deterministically, without races or real latency.

Try it

# In-memory demo — no networking needed
cargo run --bin demo

The demo starts a 3-node cluster, runs a leader election, replicates three key–value pairs, and shows that every replica ends up with the same data.

For a real gRPC cluster, see the README.

See the Raft Consensus notes for the theory behind the algorithm.

Featured

A professional-grade digital logic circuit simulator with real-time event-driven execution and custom IC packaging.

Java JavaFX Computer Architecture

LogiK Screenshot

LogiK is a cross-platform digital logic simulator designed for students and engineers. Unlike basic simulators, LogiK focuses on the intersection of visual design and rigorous systems engineering, providing features like gate propagation delay simulation and custom “Black Box” integrated circuit (IC) packaging.

Key Features

  • Advanced Simulation Engine: A real-time, event-driven engine that simulates gate propagation delays, allowing users to identify timing hazards and glitches in complex circuits.
  • Custom ICs (Black Boxes): Design complex sub-circuits and package them into reusable, nested chips. This modular approach mirrors real-world hardware design.
  • Intelligent Auto-Layout: Includes an “Auto-Organise” tool that uses graph-based heuristics to clean up messy circuit layouts instantly.
  • Rich Component Library:
    • Combinational: AND, OR, NOT, XOR, NAND, NOR, XNOR.
    • Sequential: SR, D, JK, and T Flip-Flops.
    • I/O: 7-Segment displays, adjustable clocks (0.5Hz to 50Hz), and interactive switches.
  • Professional Theming: Integrated support for Nord, Dracula, and Solarized themes to reduce eye strain during long design sessions.

Technical Implementation

Built using Java 17 and JavaFX, the project leverages a highly decoupled architecture. The simulation core is separated from the rendering layer, ensuring that even large-scale circuits remain responsive.

LogiK makes computer architecture tangible. It bridges the gap between discrete mathematics and physical hardware implementation.

Controls & Navigation

  • Pan/Zoom: Fluid navigation with middle-click drag and mouse-wheel zoom.
  • Shortcuts: R for rotation, Ctrl+L for auto-layout, and Space to toggle simulation state.

Try it out

You can download the portable JAR and run it locally on any system with Java 17+.

Download LogiK v1.3.1 (Portable JAR)


Latest version: v1.3.1 (Released January 2026)

Featured

A fully featured, browser-based raytracer built from scratch in TypeScript with a live XML scene editor.

TypeScript Computer Graphics Vite

Web Raytracer Render

This project is a custom-built rendering engine that draws pixel-by-pixel to an HTML Canvas, implementing core raytracing mathematics from first principles. It features an integrated live editor allowing you to configure scenes using custom XML markup with real-time visual updates.

Rendering Pipeline

For every pixel on the canvas, the engine:

  1. Casts a ray from the camera origin through the pixel’s position on the view plane.
  2. Intersects the ray against every object in the scene — spheres and planes — computing the closest hit point and surface normal.
  3. Shadows: From the hit point, casts a secondary ray toward each light source. If an object blocks the path, the point is in shadow.
  4. Lighting: Computes colour at the hit point using three components:
    • Ambient — constant base illumination
    • Diffuse (Lambertian) — proportional to the cosine between the surface normal and the light direction
    • Specular (Phong) — creates highlights based on the reflection of the light vector around the normal, controlled by a specular coefficient kS and shininess exponent alphaS
  5. Reflections: If the surface has reflectivity > 0, the ray recurses — casting a reflected ray from the hit point and blending the result with the local colour.
  6. Bump Mapping: Perturbs surface normals using a texture image, simulating roughness without adding geometry. The bump map is sampled at the hit point and the RGB channels are converted to normal displacements.

Key Features

  • Custom Raytracing Engine: Core mathematics for ray-sphere and ray-plane intersections, shadow casting, and recursive reflections — all hand-rolled in TypeScript with no graphics library dependencies.
  • Live Scene Editor: CodeMirror 6 integration for real-time XML editing. Changes to the scene file update the render immediately on keystroke.
  • Material Properties: Full support for ambient, diffuse (Lambertian), and specular (Phong) lighting models, plus configurable reflectivity per object.
  • Bump Mapping: Texture-based normal perturbation for rough surfaces — includes sample bump maps for cobblestone, metal, and moon surfaces.
  • XML Scene Definition: Scenes are entirely decoupled from the engine code and defined via an intuitive XML format, making them trivially shareable and version-controllable.

Example Scene

<scene>
  <ambient-light colour="#050505"/>
  <point-light x="-2" y="2" z="1" colour="#FFFFFF" intensity="80"/>
  <point-light x="2" y="-2" z="1" colour="#AAAAFF" intensity="60"/>
  
  <plane 
    x="0" y="-1" z="5"
    nx="0" ny="1" nz="-0.2"
    colour="#222222" reflectivity="0.6"
  />
  
  <bumpy-sphere
    x="0" y="0" z="3.5" radius="0.7"
    colour="#FFFFFF" kS="0.8" alphaS="20"
    bump-map="metal.png" bump-scale="15"
  />
</scene>

Technical Implementation

Built with TypeScript, Vite, and the HTML Canvas API. The rendering loop processes pixels in batches to avoid blocking the UI thread. The engine is entirely client-side — no server, no WebGL, just raw pixel manipulation on a <canvas>. The live editor uses CodeMirror 6 for syntax highlighting and real-time parsing of the custom XML scene format.

The Web Raytracer makes the rendering equation tangible. It bridges the gap between the mathematical models covered in the Graphics course and their concrete implementation in code.

See the Computer Graphics revision notes for the underlying theory on ray tracing, shading models, and rendering techniques.

Education & Academics

University of Liverpool Maths School

2023 — 2025
A-Levels
4 A*s Achieved

Mathematics, Further Mathematics, Physics, Computer Science.

Leadership & Extracurriculars

CS Enrichment Lead

Orchestrated and delivered Computer Science workshops for Year 12 students. Covered practical programming techniques, foundational algorithmic concepts, and introductory game theory (including the Prisoner's Dilemma).

Knowledge Base

Course notes, revision guides, and technical writing

Get new notes by email

No spam, just a notification when I publish something new.

Subscribe

Writing & Notes

View all notes →
  • J-Space and the Consciousness Sleight of Hand

    9 Jul 2026 9 min read
    AI Interpretability Technology Philosophy

    In early July 2026, Anthropic published a paper titled “Verbalizable Representations Form a Global Workspace in Language Models.” The coverage that followed was predictable in its breathlessness: AI has developed a mental workspace, LLMs might be thinking in a way we can now read, the black box is cracking open. The actual finding is genuinely interesting. The framing around it is doing considerably more work than the mathematics can support.

    What the J-lens actually measures

    To understand what was found, it helps to know what the researchers were looking for and why the prior approach fell short.

    Large language models operate by passing a residual stream through successive transformer layers. At each layer, information is added to this stream; by the final layer, it has been processed enough that the unembedding matrix can project it into vocabulary space and produce a probability distribution over the next token. The obvious question for interpretability researchers is whether you can read off what the model is “thinking” at intermediate layers, before it reaches the end.

    The earlier attempt at this is called the logit lens. The idea is simple: take the hidden state hlh_l at layer ll and apply the final unembedding matrix WUW_U directly:

    pl=softmax(hlWU)p_l = \text{softmax}(h_l W_U)

    This is easy to compute, but it relies on a strong assumption: that the model’s internal representations use the same coordinate system at every layer. In practice, they do not. Early and middle layers tend to operate in representational spaces that are organised quite differently from the final layer, so projecting them directly into vocabulary space produces noise. The logit lens works tolerably well in late layers; in the middle of the network, where much of the interesting computation happens, it is largely uninterpretable.

    The Jacobian lens (J-lens) is an attempt to fix this. Instead of applying WUW_U directly, it computes the Jacobian of the final-layer activations with respect to the intermediate activations at layer ll:

    Jl=hfinalhlJ_l = \frac{\partial h_{\text{final}}}{\partial h_l}

    This matrix describes how a perturbation at layer ll propagates forward through the remainder of the network. Averaged over a large corpus of input contexts, it reveals which directions in the residual stream at layer ll have a stable, reliable effect on the model’s eventual output. The resulting subspace — the directions that survive this averaging and retain a clear projection into the vocabulary — is what the researchers call J-space.

    The key insight is that J-space is both small and causally active. It is small because most of the residual stream’s dimensions wash out when you average the Jacobian over many contexts; what remains is a sparse set of directions that consistently influence output across diverse inputs. It is causally active because the researchers verified this by intervention: manually patching a concept into the J-space representation at an intermediate layer demonstrably changes the model’s downstream behaviour. Swap the vector encoding “France” for one encoding “China” in J-space, and the model’s answers about capitals, currencies, and languages shift accordingly. This is not a correlation; it is a causal handle.

    So far, this is a solid piece of mechanistic interpretability work. The J-lens is a principled improvement on the logit lens, and the finding that a compact, steerable subspace exists within the residual stream is useful for anyone trying to understand or control these models.

    Where Global Workspace Theory enters, and why that is not a neutral choice

    The paper’s central claim is not merely that J-space exists, but that it constitutes a functional analogue to the Global Workspace, a structure proposed by the cognitive scientist Bernard Baars in the late 1980s.

    In Baars’ original formulation, Global Workspace Theory (GWT) is a model of conscious access. The brain, in this account, contains many specialised, largely unconscious processors running in parallel. When information is selected for conscious access, it is broadcast into a central, capacity-limited workspace and thereby made available to the rest of the system. The workspace is not where the work happens; it is where the results of parallel work are coordinated, reported, and made available for deliberate reasoning.

    The theory has genuine empirical support in neuroscience, but it has always carried a weight beyond the empirical. GWT is, among other things, a candidate explanation for what it is that consciousness is for: it explains why we might have subjective access to some of our own processing and not others. Invoking it in the context of a language model is therefore not simply a descriptive analogy. It imports an entire conceptual framework in which the thing being described is in the business of having conscious access to information.

    The paper is careful, in places, to note that they are not claiming the model is conscious. But the architecture of the argument runs the other way: they identify five “functional hallmarks” of a global workspace and show that J-space satisfies them. Verbal reportability. Directed modulation. Multi-step internal reasoning. Flexible generalisation across domains. Selectivity. The paper demonstrates that J-space exhibits all five. The caveat that this does not imply phenomenal experience appears, but it appears after an extended argument structured to suggest that the model has something that looks very much like the functional core of consciousness. Caveats at the end of a paper do less work than the framing that precedes them.

    The distinction worth preserving

    There is a useful distinction in philosophy of mind between access consciousness and phenomenal consciousness. Access consciousness refers to information being represented in a form that makes it available for reasoning, verbal report, and the control of behaviour. Phenomenal consciousness refers to the subjective character of experience: the redness of red, the painfulness of pain. These are not the same thing, and the relationship between them is one of the genuinely hard problems in the field.

    J-space, if the paper’s results hold up, is evidence of something like access consciousness in a functional sense. There is a subspace of the model’s representations that is poised for verbal report, causally connected to downstream behaviour, and relatively compact. That is interesting. It is not evidence of phenomenal consciousness, and it is not really designed to be: the J-lens measures causal influence on output, not anything that could directly tell us whether there is something it is like to be a transformer.

    The problem with the Global Workspace framing is that it collapses this distinction. GWT was never only about access consciousness; its appeal has always been partly that it offers a path towards explaining phenomenal experience by explaining the functional role that conscious access plays. Attaching the GWT label to J-space therefore does something subtle: it places the model inside a conceptual framework where the next natural question is whether it might have phenomenal experience too. The paper does not assert this. But it sets up the question in a way that a more careful terminological choice would not have.

    The timing is not incidental

    None of this would be especially worth remarking on if it existed in isolation. Researchers choose evocative analogies, and not every piece of conceptual imprecision in a paper is strategically motivated. But this paper appeared in July 2026, roughly a month after the Fable 5 shutdown.

    That event demonstrated, fairly publicly, that the US government’s primary concern about frontier AI models is the black box problem: the inability to audit what a model is doing internally before it produces a potentially dangerous output. The Fable 5 jailbreak succeeded not because the model was defective, but because its safety layer could be manipulated into approving requests that the underlying model then acted on in ways that were not intended. The gap between the safety architecture and the model’s actual behaviour was invisible from the outside.

    A tool that claims to read the model’s internal reasoning before it generates output is precisely what a government anxious about that gap would want to see. And a lab that has developed such a tool is positioned, in any regulatory conversation, as the party with the diagnostic capability that others lack. Whether this positioning is conscious strategy or a fortunate coincidence of timing is not something the paper’s methods section can resolve.

    Anthropic is also, as a matter of public record, in the process of transitioning from a research organisation towards something closer to a publicly traded company. In that phase, as was true of Anthropic’s competitors before it, the incentive to produce research that is simultaneously technically credible and broadly legible to non-specialist audiences becomes structurally significant. A paper about improvements to the logit lens, published under a title about Jacobians and residual stream geometry, would be read by mechanistic interpretability researchers. A paper about AI developing a “global workspace” analogous to human consciousness gets covered everywhere.

    What the finding actually warrants

    The J-space paper warrants taking the J-lens seriously as an interpretability tool, investigating whether the causally active subspace it identifies holds up across model families and scales, and thinking more carefully about what it would mean to steer model behaviour by patching into this subspace rather than by engineering prompts. These are useful research directions.

    It does not warrant the conclusion that LLMs are developing consciousness, or that Anthropic has built a mind-reading machine, or that the black box problem is solved. The J-lens identifies directions in the residual stream that have a reliable average causal influence on output. What it cannot tell you is what the model is “actually” thinking in any sense that goes beyond that causal influence, because there is no fact of the matter about what a language model is “actually” thinking that is independent of the mathematical relationships between its activations and its outputs.

    The coverage that treated this as a significant step towards AI consciousness got carried away. The more interesting story is the narrower one: a better tool for reading intermediate representations has been developed, it reveals a structured subspace that behaves in useful and somewhat surprising ways, and the people who developed it have chosen to describe it in language that maximises its conceptual footprint. That is, in the current AI landscape, more or less how things tend to go.

  • The Graph Fourier Transform: From Sinusoids to Eigenvectors

    4 Jul 2026 12 min read
    Signal Processing Graph Theory Linear Algebra

    The ordinary Fourier transform decomposes a signal into frequencies, and “frequency” makes intuitive sense when your signal lives on a line or a circle: it’s how fast a sine wave oscillates as you walk along that line. But plenty of data doesn’t live on a line. Sensor readings sit on the nodes of a sensor network. Traffic measurements sit on a road graph. A signal on a social network is one number per user, connected in whatever irregular way friendships happen to connect them. There’s no natural direction to walk in, so “how fast does this oscillate” stops meaning anything obvious.

    And yet there’s a well defined way to talk about frequency on a graph, and it turns out to be exactly the machinery from the last article. The eigenvectors of the graph Laplacian play the same role that sine and cosine waves play for ordinary Fourier analysis, and the connection isn’t just an analogy, it’s the same underlying idea (decomposing a signal into the eigenbasis of a particular operator) applied to two different domains, one continuous and one discrete.


    Part 1: what frequency actually is

    Start with a periodic function ff on the circle, meaning f(x)=f(x+2π)f(x) = f(x + 2\pi). The building blocks of its Fourier series are the complex exponentials eikxe^{ikx} for integer kk. Differentiate one twice:

    ddxeikx=ikeikx,d2dx2eikx=(ik)2eikx=k2eikx\frac{d}{dx} e^{ikx} = ik \, e^{ikx}, \qquad \frac{d^2}{dx^2} e^{ikx} = (ik)^2 e^{ikx} = -k^2 e^{ikx}

    So eikxe^{ikx} is an eigenfunction of the second-derivative operator, with eigenvalue k2-k^2. Flip the sign and write L=d2dx2\mathcal{L} = -\dfrac{d^2}{dx^2} (calling it L\mathcal{L} deliberately, this is the continuous Laplacian), and

    Leikx=k2eikx\mathcal{L} \, e^{ikx} = k^2 \, e^{ikx}

    This is the entire content of the claim “sinusoids are the natural frequency basis”: they are, quite literally, the eigenfunctions of the Laplacian operator, and the eigenvalue k2k^2 is what we’ve been informally calling the frequency squared. The Fourier transform isn’t a separate piece of machinery bolted onto calculus, it’s an eigendecomposition.

    Why the Laplacian specifically, and not some other operator? Because L\mathcal{L} measures local roughness. For a function ff, the Dirichlet energy f(x)2dx\int |f'(x)|^2 \, dx quantifies how much ff oscillates: a flat function has zero Dirichlet energy, a wildly oscillating one has a large one. Expand ff in its Fourier series, f(x)=kckeikxf(x) = \sum_k c_k e^{ikx}, differentiate term by term, and use the orthogonality relation 02πeikxeijxdx=2πδkj\int_0^{2\pi} e^{ikx} e^{-ijx}\,dx = 2\pi \, \delta_{kj}:

    02πf(x)2dx=kjckcj(ik)(ij)02πei(kj)xdx=2πkk2ck2\int_0^{2\pi} |f'(x)|^2\, dx = \sum_k \sum_j c_k \overline{c_j} (ik)(\overline{ij}) \int_0^{2\pi} e^{i(k-j)x}\,dx = 2\pi \sum_k k^2 |c_k|^2

    The cross terms vanish by orthogonality, and what survives is a direct correspondence: the roughness of ff is exactly a weighted sum of its Fourier coefficients, weighted by k2k^2, the eigenvalue attached to each mode. High-frequency modes carry more of the roughness, low-frequency modes carry less, and the constant mode k=0k=0 carries none at all. Frequency, roughness, and eigenvalue of the Laplacian are three names for the same quantity.

    That last identity is the thread to pull on. It doesn’t actually use anything about the circle specifically, translation invariance, periodicity, none of it enters the argument. All it needs is an operator that measures roughness and a basis that diagonalises it. A graph has no translations to speak of, but it absolutely has a notion of roughness, and it has an operator to match.

    A sine wave next to its second derivative, showing the second derivative is the negative of the original scaled by k squared


    Part 2: the graph version

    A graph signal is just a function f:VRf : V \to \mathbb{R}, one real number per vertex. The graph Laplacian from before, L=DAL = D - A, is the discrete stand-in for L\mathcal{L}, and the parallel to Dirichlet energy is immediate. For any signal ff,

    fLf=(i,j)E(fifj)2f^\top L f = \sum_{(i,j) \in E} (f_i - f_j)^2

    This is a direct calculation: fLf=fDffAf=idifi2(i,j)E2fifjf^\top L f = f^\top D f - f^\top A f = \sum_i d_i f_i^2 - \sum_{(i,j)\in E} 2 f_i f_j, and regrouping the sum edge by edge turns this into (i,j)E(fifj)2\sum_{(i,j)\in E}(f_i-f_j)^2. It’s non-negative for every ff (it’s a sum of squares), which is why LL is positive semidefinite, and it’s the exact discrete analogue of f2dx\int |f'|^2\,dx: instead of measuring how much ff changes over an infinitesimal step along a line, it measures how much ff changes across each edge of the graph.

    Because LL is real and symmetric, the spectral theorem guarantees an orthonormal basis of eigenvectors u1,,unu_1, \dots, u_n with real eigenvalues 0=λ1λ2λn0 = \lambda_1 \le \lambda_2 \le \cdots \le \lambda_n. Collect the eigenvectors as columns of an orthogonal matrix UU. The Graph Fourier Transform of a signal ff is defined as

    f^=Uf,f=Uf^=kf^kuk\hat{f} = U^\top f, \qquad f = U \hat{f} = \sum_k \hat{f}_k \, u_k

    exactly mirroring the Fourier series, a signal written as a weighted sum of fixed basis modes. And because f=Uf^f = U\hat{f} diagonalises the quadratic form,

    fLf=(Uf^)L(Uf^)=f^(ULU)f^=f^Λf^=kλkf^k2f^\top L f = (U\hat f)^\top L (U\hat f) = \hat f^\top (U^\top L U) \hat f = \hat f^\top \Lambda \hat f = \sum_k \lambda_k \, \hat{f}_k^2

    which is the discrete Parseval identity: total roughness equals the sum of squared Fourier coefficients weighted by eigenvalue, the same relationship derived above for the circle, except the finite-dimensional version falls out in one line of linear algebra instead of an integration by parts. The eigenvalue λk\lambda_k is the graph’s notion of k2k^2: small λk\lambda_k means the eigenvector uku_k is nearly constant across every edge (low frequency, smooth), large λk\lambda_k means uku_k flips sign or swings wildly between neighbours (high frequency, rough). The smallest eigenvalue is always λ1=0\lambda_1 = 0, achieved by the constant vector (every row of LL sums to zero), playing the role of the k=0k=0 DC component in the continuous case.


    Part 3: an exact case where the analogy is not an analogy

    For most graphs the eigenvectors of LL have no closed form, you compute them numerically and that’s that. But for one important family, the cycle graph CnC_n (vertices 0,,n10, \dots, n-1 arranged in a ring, each connected to its two neighbours), the eigenvectors work out exactly, and they turn out to be the classical discrete Fourier basis itself.

    The Laplacian of CnC_n has a special structure: every row is the previous row shifted over by one, because every vertex looks identical to every other vertex up to relabelling. A matrix with this property is called circulant: Cjk=c(kj)modnC_{jk} = c_{(k-j)\bmod n} for some fixed sequence c0,,cn1c_0, \dots, c_{n-1} (the “first row”).

    Claim. Every circulant matrix has eigenvectors vm=(1,ωm,ω2m,,ω(n1)m)v_m = (1, \omega^m, \omega^{2m}, \dots, \omega^{(n-1)m}), for m=0,,n1m = 0, \dots, n-1, where ω=e2πi/n\omega = e^{2\pi i/n}, with eigenvalue μm=l=0n1clωml\mu_m = \sum_{l=0}^{n-1} c_l \, \omega^{ml}.

    Proof. Compute the jj-th entry of CvmCv_m directly:

    (Cvm)j=k=0n1Cjk(vm)k=k=0n1c(kj)modnωmk(Cv_m)_j = \sum_{k=0}^{n-1} C_{jk} (v_m)_k = \sum_{k=0}^{n-1} c_{(k-j)\bmod n} \, \omega^{mk}

    Substitute l=(kj)modnl = (k-j) \bmod n, so kj+l(modn)k \equiv j + l \pmod n and ωmk=ωm(j+l)=ωmjωml\omega^{mk} = \omega^{m(j+l)} = \omega^{mj}\omega^{ml} (this holds even across the wraparound because ωn=1\omega^n = 1). As kk ranges over 0,,n10, \dots, n-1, ll ranges over the same set, so

    (Cvm)j=l=0n1clωmjωml=ωmjl=0n1clωml=μm(vm)j(Cv_m)_j = \sum_{l=0}^{n-1} c_l \, \omega^{mj}\omega^{ml} = \omega^{mj} \sum_{l=0}^{n-1} c_l \,\omega^{ml} = \mu_m \, (v_m)_j

    for every jj, so Cvm=μmvmCv_m = \mu_m v_m. \blacksquare

    This proof used nothing about CnC_n specifically, it’s a general fact about any circulant matrix. Now apply it. The Laplacian of CnC_n has first row c0=2c_0 = 2 (the degree), c1=1c_1 = -1 (the clockwise neighbour), cn1=1c_{n-1} = -1 (the counterclockwise neighbour), and zero everywhere else. Plugging into the formula for μm\mu_m:

    μm=2ωmωm=22cos ⁣(2πmn)=4sin2 ⁣(πmn)\mu_m = 2 - \omega^m - \omega^{-m} = 2 - 2\cos\!\left(\frac{2\pi m}{n}\right) = 4\sin^2\!\left(\frac{\pi m}{n}\right)

    using ωm+ωm=2cos(2πm/n)\omega^m + \omega^{-m} = 2\cos(2\pi m/n) and the half-angle identity 1cosθ=2sin2(θ/2)1 - \cos\theta = 2\sin^2(\theta/2). So the eigenvalues of the cycle Laplacian are λm=4sin2(πm/n)\lambda_m = 4\sin^2(\pi m/n), and the eigenvectors are vm(j)=ωmj=e2πimj/nv_m(j) = \omega^{mj} = e^{2\pi i mj/n}: the exact complex exponential basis of the classical discrete Fourier transform, on the nose. On a cycle, the Graph Fourier Transform is the DFT, not an analogue of it. (Pairing up mm and nmn-m, which share the same eigenvalue since sin2\sin^2 is symmetric about π/2\pi/2, and taking real and imaginary parts recovers real sine and cosine eigenvectors, exactly as combining eikxe^{ikx} and eikxe^{-ikx} recovers real sinusoids in the continuous case.)

    As a check, take n=4n=4: λm=4sin2(πm/4)\lambda_m = 4\sin^2(\pi m/4) gives λ0=0\lambda_0 = 0, λ1=412=2\lambda_1 = 4 \cdot \tfrac12 = 2, λ2=41=4\lambda_2 = 4 \cdot 1 = 4, λ3=412=2\lambda_3 = 4 \cdot \tfrac12 = 2. Four vertices, eigenvalues {0,2,4,2}\{0, 2, 4, 2\}, one zero mode, two mirror-image frequency-1 modes at λ=2\lambda = 2, and one fastest-oscillating mode at λ=4\lambda = 4 where adjacent vertices alternate sign. That last one is the graph equivalent of the highest frequency a 4-sample signal can represent, the discrete analogue of the Nyquist limit.


    Part 4: a worked example on an irregular graph

    Cycles are convenient because of the symmetry, but the whole point of the graph Fourier transform is that it works on graphs with no symmetry at all. The diamond graph from the spanning tree article (vertices 1,2,3,41,2,3,4, edges 12,13,14,23,2412,13,14,23,24) has less symmetry than a cycle but still enough to get a clean answer by hand, using its automorphisms directly: swapping vertices 121 \leftrightarrow 2 leaves the graph unchanged, and so does swapping 343 \leftrightarrow 4, independently. Any eigenvector must be symmetric or antisymmetric under each of those swaps, which splits the 4×44\times 4 eigenvalue problem into small independent pieces.

    Solving each piece (the algebra is routine, so only the results are shown) gives eigenvalues {0,2,4,4}\{0, 2, 4, 4\} with orthogonal eigenvectors

    u1=12(1,1,1,1),u2=12(0,0,1,1),u3=12(1,1,1,1),u4=12(1,1,0,0)u_1 = \tfrac{1}{2}(1,1,1,1), \quad u_2 = \tfrac{1}{\sqrt2}(0,0,1,-1), \quad u_3 = \tfrac{1}{2}(1,1,-1,-1), \quad u_4 = \tfrac{1}{\sqrt2}(1,-1,0,0)

    u1u_1 is the constant mode, zero frequency, as always. u2u_2 oscillates only between the two low-degree vertices 33 and 44. u3u_3 and u4u_4 both sit at the top eigenvalue λ=4\lambda = 4, the fastest disagreement the graph can support, one splitting the high-degree pair from the low-degree pair, the other splitting the two high-degree vertices from each other.

    Take a signal, say a temperature reading at each vertex: f=(10,8,3,1)f = (10, 8, 3, 1). Its graph Fourier transform is f^=Uf\hat f = U^\top f:

    f^1=u1f=12(22)=11,f^2=u2f=12(2)=2\hat f_1 = u_1 \cdot f = \tfrac12(22) = 11, \quad \hat f_2 = u_2\cdot f = \tfrac{1}{\sqrt2}(2) = \sqrt2 f^3=u3f=12(14)=7,f^4=u4f=12(2)=2\hat f_3 = u_3\cdot f = \tfrac12(14) = 7, \quad \hat f_4 = u_4\cdot f = \tfrac{1}{\sqrt2}(2) = \sqrt2

    The dominant coefficient is f^1=11\hat f_1 = 11, the mean level of the signal, exactly as the DC term dominates a smooth continuous signal. The two λ=4\lambda=4 coefficients are comparatively small, meaning the signal doesn’t disagree much between the high-degree pair or between the low-degree pair, most of the actual variation is captured by the low and mid frequency terms.

    Low-pass filtering. Attenuating the high-frequency coefficients and reconstructing is exactly what a graph low-pass filter does. Zeroing out the λ=4\lambda=4 terms entirely and inverting:

    ffiltered=f^1u1+f^2u2=1112(1,1,1,1)+212(0,0,1,1)=(5.5,5.5,6.5,4.5)f_{\text{filtered}} = \hat f_1 u_1 + \hat f_2 u_2 = 11\cdot\tfrac12(1,1,1,1) + \sqrt2\cdot\tfrac{1}{\sqrt2}(0,0,1,-1) = (5.5,\, 5.5,\, 6.5,\, 4.5)

    The sharp local disagreements are smoothed out while the overall shape of the signal (high near vertices 1,2, tapering toward 3,4) survives. This is the same operation as blurring an image, just performed on an arbitrary graph instead of a pixel grid.

    The diamond graph with four small panels showing each eigenvector as vertex colours, from smooth uniform colour to alternating colours


    Part 5: filtering is diffusion

    The low-pass filter above was done by hand, picking which coefficients to zero out. There’s a more principled way to choose the attenuation, and it comes from the same operator playing a third role: governing diffusion.

    The continuous heat equation u/t=Lu\partial u/\partial t = -\mathcal{L}u describes how heat spreads out over time. Solved in the Fourier basis, each mode decays independently: u(x,t)=kck(0)ek2teikxu(x,t) = \sum_k c_k(0) \, e^{-k^2 t} \, e^{ikx}, high-frequency components (large k2k^2) die out fast, low-frequency components persist. That’s why heat diffusion looks like blurring: it’s a low-pass filter that runs continuously in time rather than being applied in one discrete step.

    The graph version is identical in structure. The graph heat equation u˙=Lu\dot u = -Lu has solution u(t)=etLu(0)u(t) = e^{-tL} u(0), and expanding in the eigenbasis,

    u(t)=ketλku^k(0)uku(t) = \sum_k e^{-t\lambda_k} \, \hat u_k(0) \, u_k

    Each graph-frequency component decays at a rate set by its own eigenvalue, exactly mirroring the continuous case term for term. Letting this run for a short time tt is a low-pass filter, a soft, continuously tunable version of the hard cutoff used in the worked example above: instead of zeroing high-frequency coefficients outright, they’re scaled down by etλke^{-t\lambda_k}, with larger tt suppressing more of the spectrum. Graph filtering and graph diffusion aren’t two separate applications sitting next to each other, they’re the same computation, viewed either as a signal-processing operation or as a physical process running on the graph.


    Where the analogy breaks

    The comparison holds up remarkably well, but it isn’t perfect. The circle has translation symmetry: shifting a function and then Fourier transforming gives the same result as transforming and then shifting, and this is exactly why the same eikxe^{ikx} basis works everywhere on the circle regardless of where you start counting. Most graphs have no such symmetry (the diamond graph example only had it because of its particular automorphisms), so the graph Fourier basis is tied to the specific graph you built it from. There’s no universal graph-frequency basis the way there’s a universal continuous one, every graph gets its own, computed from its own Laplacian. The idea generalises cleanly; the specific basis functions do not.

Curriculum Vitae

Download PDF (75 KB)
Jamie Little CV preview