Overview
PageRank Console is two projects living in one repo, and I like it that way.
The first is a numerical core. I implemented PageRank for the row vector
convention x G = x and solved it with two methods so I could compare them
honestly: the power method, and an ascending Gauss Seidel iteration. The whole
point of the study was measuring how each one behaves as the damping factor
alpha climbs toward 1, where convergence gets genuinely painful.
The second is a full stack console that turns that engine into something you can actually click around in. Upload a graph, pick a solver, set alpha and a tolerance, hit launch, and watch the run converge in real time. There is a written report in the repo too, since this began life as an academic project.
Key features
- Two solvers, one contract. Power method and ascending Gauss Seidel, both solving the same system, so their results can be compared directly. The experiment mode reports the L1 distance between the two ranking vectors at each alpha, which is a nice sanity check that both are converging to the same answer.
- The Google matrix is never materialized. Building the dense matrix would be hopeless on real web graphs. Instead the teleportation and dangling node terms are folded into each iteration analytically, so cost stays linear in the number of nonzeros.
- Sparse storage in both directions. The link matrix lives in CSR for the power method and gets converted once to CSC for Gauss Seidel, since that solver walks columns. The conversion is a single O(nnz) pass at startup and then the column view is reused for every sweep.
- Gauss Seidel accelerations. I added SOR over relaxation, optional block decomposition with inner iterations per block, and Minimal Polynomial Extrapolation that extrapolates a fresh iterate from a sliding window of recent ones. A running weighted sum keeps the teleport contribution cheap to update in place rather than recomputing it every node.
- Alpha sweep experiments. Queue one job that runs both methods across a list of alpha values and get back iteration counts, runtimes, residuals, and full convergence histories, ready to chart or export.
- Live runs. Long solves run as background jobs. A run is created as queued, flips to running, and the UI polls until it is completed, failed, or canceled. Cancellation is wired through a cancellation token so a cancel request is respected even mid solve.
- CSV everywhere. Top k rankings and convergence histories download straight from the API, the same artifacts the CLI writes to disk.
Tech stack
The backend is a Rust Cargo workspace split into three crates. pagerank-core
holds all the math and has no web dependencies at all, which kept it easy to test
and reason about. pagerank-cli is a thin Clap wrapper that exposes run and
experiment commands. pagerank-api is an Axum service on Tokio with SQLx and
SQLite for persistence.
The frontend is Next.js 15 on the App Router with React 19 and TypeScript. Server Components fetch data server to server, client components handle the mutations and the polling. Tailwind drives a Material 3 look, Recharts renders the convergence and comparison charts, TanStack Table powers the data grids, and forms use React Hook Form with Zod validation.
Architecture highlights
The split between the core library and the API is the decision I am happiest
about. The API does not shell out to a binary; it calls pagerank-core
functions directly and runs them on a blocking thread via spawn_blocking so the
async runtime stays responsive while a solve chews through a big matrix. An async
task owns the database lifecycle while the CPU bound work happens off to the side,
and results are persisted in a single transaction once the solve finishes.
Deployment is split too. The Rust backend runs on a DigitalOcean droplet in Docker, with Caddy giving it automatic HTTPS through a free nip.io hostname so I never had to buy a domain. The frontend is on Vercel and talks to the backend over HTTPS. Build time environment variables inline the right API URL for the browser.
My part in it
I built all of it. The numerical core came first: parsing the matrix format from the project statement, the sparse storage, the two solvers, the dangling node handling, and the optimization experiments. I wrote a separate set of Python scripts with matplotlib to render the figures for the report, comparing iteration counts and runtime across alpha for both methods.
Then I refactored the original solver code into the pagerank-core library
without changing its behavior, wrapped it in the Axum API with background jobs
and SQLite, and built the Next.js console on top. Last came deployment, the
Docker images, the Caddy HTTPS setup on the droplet, and moving the frontend onto
Vercel.