-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add interactive small world networks blog post with visualizations
- Loading branch information
Showing
6 changed files
with
445 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); |
Oops, something went wrong.