Skip to content

Commit

Permalink
add 400
Browse files Browse the repository at this point in the history
  • Loading branch information
taoeffect committed Jul 19, 2024
1 parent d585ac3 commit d348f95
Showing 1 changed file with 195 additions and 0 deletions.
195 changes: 195 additions & 0 deletions public/simulation/realistic_5percent_vbi_cap_400.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Money Distribution Simulation</title>
<style>
body {
display: flex;
flex-direction: column;
align-items: center;
font-family: Arial, sans-serif;
}
canvas {
border: 1px solid black;
}
#controls {
margin: 10px 0;
}
#gini {
margin-top: 10px;
font-size: 18px;
font-weight: bold;
}
</style>
</head>
<body>
<h1 style="margin: 0 auto">5% exchange ("realistic") VBI (1% donation cap)</h1>
<div id="controls">
<button id="playPause">Play/Pause</button>
<button id="reset">Reset</button>
</div>
<div id="gini">Gini Coefficient: 0.000</div>
<canvas id="simulationCanvas" width="1600" height="650"></canvas>

<script>
const canvas = document.getElementById('simulationCanvas');
const ctx = canvas.getContext('2d');
const playPauseBtn = document.getElementById('playPause');
const resetBtn = document.getElementById('reset');
const giniDisplay = document.getElementById('gini');

const numIndividuals = 400;
const EPOCH_LENGTH = 200;
const TRANSACTION_CHANCE = 0.10; // 10% chance of transaction
let individuals = [];
let isRunning = false;
let animationId;
let roundCount = 0;
let wealthiestIndividual = null;

function gaussianRandom(mean = 0.5, stdev = 0.15) {
const u = 1 - Math.random();
const v = Math.random();
const z = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
return Math.min(Math.max(z * stdev + mean, 0), 1);
}

function initializeSimulation() {
individuals = Array(numIndividuals).fill().map((_, i) => ({
id: i,
wealth: 100,
moneySkill: gaussianRandom(),
generosity: gaussianRandom()
}));
roundCount = 0;
wealthiestIndividual = null;
}

function runSimulation() {
// Sort individuals by wealth
individuals.sort((a, b) => a.wealth - b.wealth);
// Calculate total wealth
const totalWealth = individuals.reduce((sum, individual) => sum + individual.wealth, 0)
const maxDonation = totalWealth * .01
const medianIndex = Math.floor(numIndividuals / 2);

for (let i = 0; i < numIndividuals; i++) {
// Random transaction
if (Math.random() < TRANSACTION_CHANCE) {
const giver = i;
let receiver;
do {
receiver = Math.floor(Math.random() * numIndividuals);
} while (receiver === giver);

const amount = Math.floor(individuals[giver].wealth * 0.05);
if (amount > 0) {
individuals[giver].wealth -= amount;
individuals[receiver].wealth += amount;
}
}

// Apply investment based on money skill and 10% of wealth
const investment = 0.1 * individuals[i].wealth;
individuals[i].wealth += individuals[i].moneySkill * investment;

// Voluntary Basic Income (VBI)
if (i >= medianIndex) { // Top 50% of wealthy individuals
const vbi = Math.min(maxDonation, Math.floor(0.1 * individuals[i].wealth * individuals[i].generosity))
if (vbi > 0) {
const receiver = Math.floor(Math.random() * medianIndex); // Random individual from bottom 50%
individuals[i].wealth -= vbi;
individuals[receiver].wealth += vbi;
}
}
}

individuals.sort((a, b) => a.wealth - b.wealth)

roundCount++;

if (roundCount % EPOCH_LENGTH === 0 || wealthiestIndividual === null) {
wealthiestIndividual = individuals[individuals.length - 1];
} else {
wealthiestIndividual = individuals.find(ind => ind.id === wealthiestIndividual.id);
}
}

function calculateGini() {
const totalWealth = individuals.reduce((sum, ind) => sum + ind.wealth, 0);
const lorenzPoints = [];
let sumSoFar = 0;
for (let i = 0; i < numIndividuals; i++) {
sumSoFar += individuals[i].wealth;
lorenzPoints.push(sumSoFar / totalWealth);
}

let areaUnderLorenz = 0;
for (let i = 0; i < numIndividuals; i++) {
areaUnderLorenz += lorenzPoints[i] / numIndividuals;
}

const gini = (0.5 - areaUnderLorenz) / 0.5;
return gini;
}

function drawBarGraph() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const barWidth = canvas.width / numIndividuals;
const maxMoney = Math.max(...individuals.map(ind => ind.wealth));

for (let i = 0; i < numIndividuals; i++) {
const barHeight = (individuals[i].wealth / maxMoney) * (canvas.height - 50);
ctx.fillStyle = `hsl(${(individuals[i].wealth / maxMoney) * 120}, 100%, 50%)`;
ctx.fillRect(i * barWidth, canvas.height - 50 - barHeight, barWidth, barHeight);
}

// Draw arrow pointing to wealthiest individual
if (wealthiestIndividual) {
const wealthiestIndex = individuals.findIndex(ind => ind.id === wealthiestIndividual.id);
const arrowX = (wealthiestIndex + 0.5) * barWidth;
ctx.beginPath();
ctx.moveTo(arrowX, canvas.height - 40);
ctx.lineTo(arrowX - 10, canvas.height - 20);
ctx.lineTo(arrowX + 10, canvas.height - 20);
ctx.closePath();
ctx.fillStyle = 'red';
ctx.fill();
}

const gini = calculateGini();
const currentEpoch = Math.floor(roundCount / EPOCH_LENGTH);
giniDisplay.textContent = `Gini Coefficient: ${gini.toFixed(3)} | Round: ${roundCount} | Epoch: ${currentEpoch}`;
}

function animate() {
if (isRunning) {
runSimulation();
drawBarGraph();
animationId = requestAnimationFrame(animate);
}
}

playPauseBtn.addEventListener('click', () => {
isRunning = !isRunning;
if (isRunning) {
animate();
} else {
cancelAnimationFrame(animationId);
}
});

resetBtn.addEventListener('click', () => {
isRunning = false;
cancelAnimationFrame(animationId);
initializeSimulation();
drawBarGraph();
});

initializeSimulation();
drawBarGraph();
</script>
</body>
</html>

0 comments on commit d348f95

Please sign in to comment.