update pigeonhole

This commit is contained in:
2025-12-07 14:24:52 +01:00
parent 78a6ee63c7
commit 19ec048d72
3 changed files with 179 additions and 0 deletions

View File

@@ -0,0 +1,173 @@
{% extends "base.html" %}
{% block title %}Pigeonhole Principle Simulation{% endblock %}
{% block content %}
<div class="max-w-screen grid grid-cols-1 items-center divide-y p-6">
<div class="flex flex-col justify-center text-center">
<h1 class="text-3xl font-bold mb-4">Pigeonhole Principle Simulation</h1>
<p class="text-lg text-gray-700 dark:text-gray-300 mb-2">
If we place 5 dots in a square of side length $s$, there must be at least two dots whose distance $d$
satisfies:
$$ d < \frac{s}{\sqrt{2}} $$ </p>
<p class="text-md text-gray-600 dark:text-gray-400 mb-6">
(The user requested condition: "distance is smaller than square root of square ($s^2$)", which means
$d < \sqrt{s^2}=s$. Since $\frac{s}{\sqrt{2}} \approx 0.707 s < s$, the condition is always met if
the stronger Pigeonhole Principle holds.) </p>
<div class="flex justify-center mb-4">
<button onclick="generatePoints()"
class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition">
Generate New Points
</button>
</div>
</div>
<div class="flex flex-col items-center justify-center pt-6">
<div class="relative">
<canvas id="pigeonholeCanvas" width="500" height="500"
class="border-2 border-gray-800 dark:border-gray-200 bg-white shadow-lg"></canvas>
<div id="result-overlay"
class="absolute top-2 left-2 bg-white/90 p-2 rounded shadow text-sm font-mono border hidden"></div>
</div>
<div id="stats" class="mt-4 text-center text-lg font-semibold min-h-[100px]"></div>
</div>
</div>
{% endblock %}
{% block js %}
<script>
window.MathJax = {
tex: {
inlineMath: [['$', '$'], ['\\(', '\\)']]
}
};
</script>
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<script>
"use strict";
const canvas = document.getElementById('pigeonholeCanvas');
const ctx = canvas.getContext('2d');
const statsDiv = document.getElementById('stats');
const overlayDiv = document.getElementById('result-overlay');
// Square dimensions (leaving some padding)
const PADDING = 50;
const SIDE = 400; // side length
function drawSquare() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw the main square
ctx.strokeStyle = '#333';
ctx.lineWidth = 2;
ctx.strokeRect(PADDING, PADDING, SIDE, SIDE);
// Draw grid lines to visualize the quadrants (the proof concept)
ctx.strokeStyle = '#ccc';
ctx.setLineDash([5, 5]);
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(PADDING + SIDE / 2, PADDING);
ctx.lineTo(PADDING + SIDE / 2, PADDING + SIDE);
ctx.moveTo(PADDING, PADDING + SIDE / 2);
ctx.lineTo(PADDING + SIDE, PADDING + SIDE / 2);
ctx.stroke();
ctx.setLineDash([]);
}
function generatePoints() {
drawSquare();
const points = [];
for (let i = 0; i < 5; i++) {
// Random x, y within the square
const x = Math.random() * SIDE + PADDING;
const y = Math.random() * SIDE + PADDING;
points.push({ x, y, id: i });
// Draw point
ctx.fillStyle = '#ef4444'; // red-500
ctx.beginPath();
ctx.arc(x, y, 6, 0, Math.PI * 2);
ctx.fill();
// Label
ctx.fillStyle = '#000';
ctx.font = '12px sans-serif';
ctx.fillText(i + 1, x + 8, y - 8);
}
findClosestPair(points);
}
function findClosestPair(points) {
let minDist = Infinity;
let p1 = null;
let p2 = null;
for (let i = 0; i < points.length; i++) {
for (let j = i + 1; j < points.length; j++) {
const dx = points[i].x - points[j].x;
const dy = points[i].y - points[j].y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
p1 = points[i];
p2 = points[j];
}
}
}
// Highlight closest pair
if (p1 && p2) {
ctx.strokeStyle = '#2563eb'; // blue-600
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.lineTo(p2.x, p2.y);
ctx.stroke();
const limit = SIDE / Math.sqrt(2); // ~282.84 for SIDE=400
// Generate Coordinate List
let pointsHtml = `<div class="mt-4 text-left inline-block"><h3 class="font-bold text-center mb-2">Point Coordinates (relative to square)</h3><ul class="list-none space-y-1">`;
points.forEach(p => {
const isClosest = (p === p1 || p === p2);
// Calculate relative coordinates
const relX = (p.x - PADDING).toFixed(1);
const relY = (p.y - PADDING).toFixed(1);
const style = isClosest ? "color: #2563eb; font-weight: bold;" : "";
const mark = isClosest ? "★ " : "&nbsp;&nbsp;";
pointsHtml += `<li style="${style}" class="font-mono text-sm">${mark}Point ${p.id + 1}: (${relX}, ${relY})</li>`;
});
pointsHtml += `</ul></div>`;
// Using LaTeX for the dynamic output as well
statsDiv.innerHTML = `
<div>Closest Pair Distance: $d = ${minDist.toFixed(2)}$ px (between P${p1.id + 1} and P${p2.id + 1})</div>
<div>Threshold: $\\frac{s}{\\sqrt{2}} \\approx ${limit.toFixed(2)}$ px</div>
<div>Square Side: $s = ${SIDE}$ px</div>
${pointsHtml}
`;
if (minDist < SIDE) {
statsDiv.innerHTML += `<div class="mt-2 text-green-600 font-bold">✔ $d < s$ (Requested Condition Holds)</div>`;
}
// Trigger MathJax re-render if available
if (window.MathJax && window.MathJax.typesetPromise) {
window.MathJax.typesetPromise([statsDiv]);
}
}
}
// Initial draw - wait for MathJax to load for the first text,
// but we can draw immediately, typesetting stats happens in findClosestPair
generatePoints();
</script>
{% endblock %}