Skip to content

Commit

Permalink
feat: Add interactive small world networks blog post with visualizations
Browse files Browse the repository at this point in the history
  • Loading branch information
sanity committed Nov 25, 2024
1 parent 636bf4f commit 173d2f4
Show file tree
Hide file tree
Showing 6 changed files with 445 additions and 0 deletions.
35 changes: 35 additions & 0 deletions hugo-site/content/news/small-world-networks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
title: "Understanding Small World Networks"
date: 2024-11-25
description: "An interactive exploration of small world networks, their properties, and why they matter"
draft: false
---

Small world networks are a fascinating type of network structure that appears naturally in many real-world systems - from social networks to the internet's topology. In this interactive post, we'll explore how these networks work and why they're so efficient at routing information.

## What Makes a Network "Small World"?

A small world network has a unique property: most nodes are not directly connected, but can reach each other through a surprisingly small number of steps. This is achieved through a specific pattern of connections:

1. Many short-range connections between nearby nodes
2. A few long-range connections that act as shortcuts

The visualization below shows this pattern. The nodes are arranged in a circle, with connections colored based on their length. Notice how the link distribution follows a specific pattern:

{{< partial "small-world-viz.html" >}}

## Greedy Routing in Small World Networks

One of the most interesting properties of small world networks is how efficiently they can route messages using only local information. At each step, a node simply passes the message to its neighbor that's closest to the final destination. This "greedy" strategy works remarkably well:

{{< partial "small-world-routing-viz.html" >}}

## How Do Small World Networks Scale?

As we add more nodes to a small world network, how does the average path length between nodes grow? The visualization below shows this relationship:

{{< partial "small-world-scale-viz.html" >}}

The remarkable thing about small world networks is that the average path length grows logarithmically with the network size. This means that even in very large networks, most nodes can reach each other in relatively few steps.

This property helps explain why the "six degrees of separation" phenomenon works in social networks, and why the internet can route packets efficiently despite its enormous size.
33 changes: 33 additions & 0 deletions hugo-site/layouts/partials/small-world-routing-viz.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<div class="visualization-container">
<div class="network-view">
<canvas id="routingCanvas" width="800" height="800"></canvas>
</div>
<div class="control-panel buttons are-small">
<button id="newRouteBtn" class="button is-primary">New Route</button>
</div>
</div>

<script src="/js/small-world-routing.js"></script>

<style>
.visualization-container {
margin: 2em 0;
display: flex;
flex-direction: column;
align-items: center;
}

.network-view {
border: 1px solid #ddd;
padding: 1em;
border-radius: 4px;
}

canvas {
border: 1px solid #eee;
}

.control-panel {
margin-top: 1em;
}
</style>
37 changes: 37 additions & 0 deletions hugo-site/layouts/partials/small-world-scale-viz.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<div class="visualization-container">
<div class="network-view">
<canvas id="scaleCanvas" width="800" height="800"></canvas>
<div id="scaleChart" class="box" style="margin-top: 20px;">
<h3 class="title is-4">Average Path Length vs Network Size</h3>
<svg width="800" height="400"></svg>
</div>
</div>
<div class="control-panel buttons are-small">
<button id="resetScaleBtn" class="button is-primary">Reset</button>
</div>
</div>

<script src="/js/small-world-scale.js"></script>

<style>
.visualization-container {
margin: 2em 0;
display: flex;
flex-direction: column;
align-items: center;
}

.network-view {
border: 1px solid #ddd;
padding: 1em;
border-radius: 4px;
}

canvas {
border: 1px solid #eee;
}

.control-panel {
margin-top: 1em;
}
</style>
35 changes: 35 additions & 0 deletions hugo-site/layouts/partials/small-world-viz.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<div class="visualization-container">
<div class="network-view">
<canvas id="networkCanvas" width="800" height="800"></canvas>
<div id="histogram" style="width: 800px; height: 200px;"></div>
</div>
<div class="control-panel buttons are-small">
<button id="resetBtn" class="button is-primary">Reset Network</button>
</div>
</div>

<script src="https://d3js.org/d3.v7.min.js"></script>
<script src="/js/small-world.js"></script>

<style>
.visualization-container {
margin: 2em 0;
display: flex;
flex-direction: column;
align-items: center;
}

.network-view {
border: 1px solid #ddd;
padding: 1em;
border-radius: 4px;
}

canvas {
border: 1px solid #eee;
}

.control-panel {
margin-top: 1em;
}
</style>
150 changes: 150 additions & 0 deletions hugo-site/static/js/small-world-routing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// Network parameters
const width = 800;
const height = 800;
const radius = 300;
const numPeers = 30;
const connectionProbability = (distance) => 1 / (distance + 1);

// Initialize canvas
const canvas = document.getElementById('routingCanvas');
const ctx = canvas.getContext('2d');

let peers = [];
let links = [];
let currentPath = [];
let sourceNode = null;
let targetNode = null;
let animationFrame = null;

function initializeNetwork() {
// Calculate positions for peers on a 1D ring
peers = d3.range(numPeers).map(i => {
const angle = (i / numPeers) * 2 * Math.PI;
return {
x: width / 2 + radius * Math.cos(angle),
y: height / 2 + radius * Math.sin(angle),
index: i
};
});

// Generate links between peers
links = [];
for (let i = 0; i < numPeers; i++) {
const nextIndex = (i + 1) % numPeers;
links.push({ source: peers[i], target: peers[nextIndex] });

for (let j = i + 2; j < numPeers; j++) {
const distance = Math.min(Math.abs(i - j), numPeers - Math.abs(i - j));
const prob = connectionProbability(distance);
if (Math.random() < prob) {
links.push({ source: peers[i], target: peers[j] });
}
}
}
}

function drawNetwork() {
ctx.clearRect(0, 0, width, height);

// Draw links
ctx.strokeStyle = 'rgba(100, 149, 237, 0.3)';
ctx.lineWidth = 1;
links.forEach(link => {
ctx.beginPath();
ctx.moveTo(link.source.x, link.source.y);
ctx.lineTo(link.target.x, link.target.y);
ctx.stroke();
});

// Draw routing path
if (currentPath.length > 1) {
ctx.strokeStyle = 'rgba(255, 0, 0, 0.5)';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(currentPath[0].x, currentPath[0].y);
for (let i = 1; i < currentPath.length; i++) {
ctx.lineTo(currentPath[i].x, currentPath[i].y);
}
ctx.stroke();
}

// Draw nodes
peers.forEach(peer => {
if (peer === sourceNode) {
ctx.fillStyle = 'green';
} else if (peer === targetNode) {
ctx.fillStyle = 'red';
} else {
ctx.fillStyle = 'tomato';
}
ctx.beginPath();
ctx.arc(peer.x, peer.y, 5, 0, 2 * Math.PI);
ctx.fill();
});
}

function findNextHop(current, target) {
let bestNeighbor = null;
let bestDistance = Infinity;

links.forEach(link => {
let neighbor = null;
if (link.source === current) neighbor = link.target;
if (link.target === current) neighbor = link.source;

if (neighbor && !currentPath.includes(neighbor)) {
const distance = Math.hypot(neighbor.x - target.x, neighbor.y - target.y);
if (distance < bestDistance) {
bestDistance = distance;
bestNeighbor = neighbor;
}
}
});

return bestNeighbor;
}

function animateRouting() {
if (!currentPath.length || currentPath[currentPath.length - 1] === targetNode) {
cancelAnimationFrame(animationFrame);
return;
}

const current = currentPath[currentPath.length - 1];
const next = findNextHop(current, targetNode);

if (next) {
currentPath.push(next);
drawNetwork();
} else {
cancelAnimationFrame(animationFrame);
return;
}

animationFrame = requestAnimationFrame(animateRouting);
}

function startNewRoute() {
// Cancel any ongoing animation
cancelAnimationFrame(animationFrame);

// Select random source and target
sourceNode = peers[Math.floor(Math.random() * peers.length)];
do {
targetNode = peers[Math.floor(Math.random() * peers.length)];
} while (sourceNode === targetNode);

// Initialize path with source
currentPath = [sourceNode];

// Start animation
animationFrame = requestAnimationFrame(animateRouting);
}

// Event listeners
document.getElementById('newRouteBtn').addEventListener('click', startNewRoute);

// Initial setup
initializeNetwork();
drawNetwork();
startNewRoute();
Loading

0 comments on commit 173d2f4

Please sign in to comment.