世界史時代別 共通祖先シミュレーション(おまけ)
この小説で語られていることを実際に試してみたい場合は、以下のコードをメモ帳にコピーして適当な名前にして、拡張子(.html)で保存して、ブラウザで開けば、世界のMRCAがいつ頃現れるかシミュレーションが可能です。
このシミュレータでは、地域の孤立度合いとか、複雑な事は考慮していませんので、論文『Modelling the recent common ancestry of all living humans』のように、MRCAの登場が数千年前とはならず、中世ごろに現れます。それでも、当時の人口カバー率からすれば9割程度はいっているかと。
ポツンと一軒家みたいな僻地に住んでいると人まで細かく考慮すると数千年かかるということで、人口の9割を占める主要部に住む人々の共通祖先は、それほど昔じゃない、とても近い時代に居たという事になります。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>世界史版 共通祖先シミュレーション</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&family=Noto+Sans+JP:wght@400;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', 'Noto Sans JP', sans-serif;
background-color: #111827; /* bg-gray-900 */
}
.progress-ring__circle {
transition: stroke-dashoffset 0.35s;
transform: rotate(-90deg);
transform-origin: 50% 50%;
}
.log-entry {
border-left: 2px solid #374151; /* bg-gray-700 */
transition: background-color 0.3s ease-in-out, border-color 0.3s ease-in-out;
}
.log-entry:hover {
background-color: #1f2937; /* bg-gray-800 */
}
.log-entry.highlight {
background-color: #3730a3; /* bg-indigo-800 */
border-left-color: #6366f1; /* bg-indigo-500 */
}
.log-entry.saturation {
background-color: #be123c; /* bg-rose-700 */
border-left-color: #f43f5e; /* bg-rose-500 */
}
/* Custom scrollbar */
::-webkit-scrollbar { width: 8px; }
::-webkit-scrollbar-track { background: #1f2937; }
::-webkit-scrollbar-thumb { background: #4b5563; border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: #6b7280; }
/* Custom slider styles */
input[type=range] {
-webkit-appearance: none;
appearance: none;
background: transparent;
cursor: pointer;
width: 100%;
}
input[type=range]:focus {
outline: none;
}
input[type=range]::-webkit-slider-runnable-track {
background: #374151; /* bg-gray-700 */
height: 0.5rem;
border-radius: 0.5rem;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
margin-top: -6px; /* Position thumb correctly */
background-color: #6366f1; /* bg-indigo-500 */
height: 1.5rem;
width: 1.5rem;
border-radius: 50%;
border: 2px solid #e0e7ff; /* bg-indigo-100 */
}
input[type=range]:focus::-webkit-slider-thumb {
outline: 3px solid #818cf8; /* bg-indigo-400/50 */
outline-offset: 0.125rem;
}
</style>
</head>
<body class="text-gray-200">
<div class="min-h-screen flex flex-col items-center justify-center p-4">
<div class="w-full max-w-7xl mx-auto bg-gray-900/50 backdrop-blur-sm rounded-2xl shadow-2xl p-6 md:p-8">
<header class="text-center mb-6">
<h1 class="text-3xl md:text-4xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-indigo-400 to-cyan-400">世界史版 共通祖先シミュレーション</h1>
<p class="mt-2 text-gray-400">現代の人間一人の祖先を辿ると、いつの時代の世界人口をカバーするのかを計算します。</p>
</header>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 items-start">
<div class="sticky top-8 flex flex-col items-center bg-gray-800/50 p-6 rounded-xl">
<div id="visualization" class="relative w-64 h-64 flex items-center justify-center mb-4">
<svg class="w-full h-full" viewBox="0 0 120 120">
<circle class="text-gray-700" stroke-width="8" stroke="currentColor" fill="transparent" r="52" cx="60" cy="60" />
<circle id="progress-circle" class="progress-ring__circle text-indigo-500" stroke-width="8" stroke-linecap="round" stroke="currentColor" fill="transparent" r="52" cx="60" cy="60" />
</svg>
<div class="absolute text-center">
<div id="coverage-percent" class="text-4xl font-bold text-white">0.00%</div>
<div class="text-sm text-gray-400">人口カバー率</div>
</div>
</div>
<div id="current-status" class="text-center w-full mb-6">
<p class="text-lg"><span id="generation-display" class="font-bold text-2xl text-cyan-400">-</span> 世代前</p>
<p class="text-2xl font-semibold text-white" id="year-display">- 年頃</p>
<p class="text-xl text-gray-300" id="era-display">-</p>
</div>
<div class="w-full max-w-xs mb-6">
<label for="generationSlider" class="block text-center text-sm font-medium text-gray-300 mb-2">世代間隔: <span id="sliderValue" class="font-bold text-cyan-400">25年</span></label>
<input id="generationSlider" type="range" min="15" max="35" value="25" step="1" class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer">
</div>
<div class="flex space-x-4">
<button id="startButton" class="w-40 bg-indigo-600 hover:bg-indigo-500 text-white font-bold py-3 px-6 rounded-lg shadow-lg transition-transform transform hover:scale-105 focus:outline-none focus:ring-4 focus:ring-indigo-400/50">
シミュレーション開始
</button>
<button id="resetButton" class="w-40 bg-gray-600 hover:bg-gray-500 text-white font-bold py-3 px-6 rounded-lg shadow-lg transition-transform transform hover:scale-105 focus:outline-none focus:ring-4 focus:ring-gray-400/50 hidden">
リセット
</button>
</div>
</div>
<div class="bg-gray-800/50 p-4 rounded-xl h-[70vh] flex flex-col">
<h2 class="text-xl font-bold mb-4 text-center lg:text-left">シミュレーションログ</h2>
<div id="log-container" class="flex-grow overflow-y-auto pr-2 space-y-3">
<div id="log-placeholder" class="h-full flex items-center justify-center text-gray-500">
<p>開始ボタンを押してシミュレーションを開始します。</p>
</div>
</div>
</div>
</div>
<div id="mrca-message" class="hidden mt-8 p-6 bg-indigo-800/50 border border-indigo-600 rounded-xl text-center">
<h2 class="text-2xl font-bold text-indigo-300"> 最近共通祖先(MRCA)の可能性</h2>
<p id="mrca-text" class="mt-2 text-lg"></p>
</div>
<div id="saturation-message" class="hidden mt-8 p-6 bg-rose-800/50 border border-rose-600 rounded-xl text-center">
<h2 class="text-2xl font-bold text-rose-300"> 飽和点(Identical Ancestors Point)に到達</h2>
<p id="saturation-text" class="mt-2 text-lg"></p>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// --- DATA ---
const historicalPeriods = [
{ "name": "現代", "start_year": 2000, "population": 6145000000 },
{ "name": "20世紀後期", "start_year": 1970, "population": 3700000000 },
{ "name": "20世紀中期", "start_year": 1950, "population": 2520000000 },
{ "name": "20世紀初頭", "start_year": 1900, "population": 1650000000 },
{ "name": "産業革命期", "start_year": 1800, "population": 1000000000 },
{ "name": "啓蒙時代", "start_year": 1700, "population": 680000000 },
{ "name": "大航海時代", "start_year": 1600, "population": 500000000 },
{ "name": "ルネサンス期", "start_year": 1500, "population": 450000000 },
{ "name": "中世後期", "start_year": 1300, "population": 370000000 },
{ "name": "中世盛期", "start_year": 1000, "population": 310000000 },
{ "name": "中世初期", "start_year": 500, "population": 210000000 },
{ "name": "ローマ帝国時代", "start_year": 1, "population": 200000000 },
{ "name": "古典古代", "start_year": -500, "population": 100000000 },
{ "name": "鉄器時代", "start_year": -1000, "population": 50000000 },
{ "name": "青銅器時代", "start_year": -3000, "population": 14000000 },
{ "name": "新石器革命期", "start_year": -8000, "population": 5000000 },
];
const CURRENT_YEAR = 2025;
// --- DOM Elements ---
const startButton = document.getElementById('startButton');
const resetButton = document.getElementById('resetButton');
const logContainer = document.getElementById('log-container');
const logPlaceholder = document.getElementById('log-placeholder');
const progressCircle = document.getElementById('progress-circle');
const coveragePercent = document.getElementById('coverage-percent');
const generationDisplay = document.getElementById('generation-display');
const yearDisplay = document.getElementById('year-display');
const eraDisplay = document.getElementById('era-display');
const saturationMessage = document.getElementById('saturation-message');
const saturationText = document.getElementById('saturation-text');
const mrcaMessage = document.getElementById('mrca-message');
const mrcaText = document.getElementById('mrca-text');
const generationSlider = document.getElementById('generationSlider');
const sliderValue = document.getElementById('sliderValue');
const radius = progressCircle.r.baseVal.value;
const circumference = radius * 2 * Math.PI;
progressCircle.style.strokeDasharray = `${circumference} ${circumference}`;
progressCircle.style.strokeDashoffset = circumference;
// --- State ---
let GENERATION_YEARS = 25;
let generationCounter = 0;
let simulationInterval = null;
let mrcaAlertShown = false;
// --- Helper Functions ---
const getPeriodForYear = (year, periodsData) => {
for (const period of periodsData) {
if (year >= period.start_year) {
return [period.name, period.population];
}
}
return [periodsData[periodsData.length - 1].name, periodsData[periodsData.length - 1].population];
};
const setProgress = (percent) => {
const offset = circumference - (percent / 100) * circumference;
progressCircle.style.strokeDashoffset = offset;
coveragePercent.textContent = `${percent.toFixed(2)}%`;
};
const formatNumber = (num) => {
if (num > 1e15) return num.toExponential(2);
return num.toLocaleString('ja-JP');
};
// --- Main Simulation Logic ---
const runSimulationStep = () => {
generationCounter++;
const yearsAgo = generationCounter * GENERATION_YEARS;
const yearOfGeneration = CURRENT_YEAR - yearsAgo;
if (yearOfGeneration < historicalPeriods[historicalPeriods.length - 1].start_year) {
stopSimulation("シミュレーション範囲の最古に到達しました。");
return;
}
const [period_name, population] = getPeriodForYear(yearOfGeneration, historicalPeriods);
const theoretical_ancestors = 2n ** BigInt(generationCounter);
const theoretical_ancestors_num = Number(theoretical_ancestors);
let distinct_ancestors;
if (theoretical_ancestors_num <= population * 0.01) {
distinct_ancestors = theoretical_ancestors_num;
} else {
const ratio = theoretical_ancestors_num / population;
distinct_ancestors = population * (1 - Math.exp(-ratio));
}
const coverage_ratio = distinct_ancestors / population;
// --- Update UI ---
generationDisplay.textContent = generationCounter;
yearDisplay.textContent = `${Math.round(yearOfGeneration)}年頃`;
eraDisplay.textContent = period_name;
setProgress(coverage_ratio * 100);
const logEntry = document.createElement('div');
logEntry.className = 'log-entry p-3 rounded-r-lg';
logEntry.innerHTML = `
<p class="font-bold text-cyan-400">${generationCounter}世代前 (${Math.round(yearOfGeneration)}年頃 / ${period_name})</p>
<div class="text-sm mt-1 text-gray-300">
<p>ユニーク祖先数: <span class="font-semibold text-white">${formatNumber(Math.round(distinct_ancestors))}</span> / ${formatNumber(population)}人</p>
<p>人口カバー率: <span class="font-semibold text-white">${(coverage_ratio * 100).toFixed(4)}%</span></p>
</div>
`;
if (coverage_ratio > 0.5 && coverage_ratio < 0.9999) {
logEntry.classList.add('highlight');
}
logContainer.prepend(logEntry);
if (coverage_ratio > 0.5 && !mrcaAlertShown) {
mrcaAlertShown = true;
mrcaMessage.classList.remove('hidden');
mrcaText.textContent = `${generationCounter}世代前(西暦${Math.round(yearOfGeneration)}年頃)、あなたの祖先が当時の世界人口の半数を超えました。この時代には現代の全人類の「最近共通祖先(MRCA)」が存在する可能性が非常に高まります。`;
}
if (coverage_ratio >= 0.9999) {
logEntry.classList.add('saturation');
stopSimulation();
saturationText.textContent = `${generationCounter}世代前(西暦${Math.round(yearOfGeneration)}年頃)、この時代に生きて現代まで子孫を残したほぼ全ての人が、あなたの祖先である可能性が非常に高くなります。`;
saturationMessage.classList.remove('hidden');
}
};
const stopSimulation = (message = null) => {
clearInterval(simulationInterval);
simulationInterval = null;
startButton.disabled = false;
generationSlider.disabled = false;
resetButton.classList.remove('hidden');
startButton.classList.add('hidden');
};
const startSimulation = () => {
if (simulationInterval) return;
logPlaceholder.classList.add('hidden');
startButton.disabled = true;
generationSlider.disabled = true;
simulationInterval = setInterval(runSimulationStep, 150);
};
const resetSimulation = () => {
stopSimulation();
generationCounter = 0;
mrcaAlertShown = false;
logContainer.innerHTML = '';
logPlaceholder.classList.remove('hidden');
setProgress(0);
generationDisplay.textContent = '-';
yearDisplay.textContent = '- 年頃';
eraDisplay.textContent = '-';
saturationMessage.classList.add('hidden');
mrcaMessage.classList.add('hidden');
resetButton.classList.add('hidden');
startButton.classList.remove('hidden');
};
// --- Event Listeners ---
startButton.addEventListener('click', startSimulation);
resetButton.addEventListener('click', resetSimulation);
generationSlider.addEventListener('input', (e) => {
const newValue = parseInt(e.target.value);
GENERATION_YEARS = newValue;
sliderValue.textContent = `${newValue}年`;
if (simulationInterval || generationCounter > 0) {
resetSimulation();
}
});
});
</script>
</body>
</html>
90%までは簡単だけど、そこから先、100%にするのが難しいと言う話ですね。携帯電話の人口カバー率みたいなものです。
プログラムは、「血統の崩壊」を考慮して、理論上の祖先数とその時代の総人口から、より現実的なユニーク(重複しない)祖先数を数学モデル (人口)* (1 - e^(-理論祖先数/人口)) を使って推定します。




