Claude 3.7 Sonnetのコーディング能力を試す 6
以前の投稿でClaude 3.7 Sonnetのコーディング能力を試した.Reactを使った動的な処理が素晴らしく色々試している.今回は日本地図を組み合わせた流行伝播モデルを組んでみた.すると予想外の方法で描画してきたが,逆により分かりやすい形になった.(なお,挙動は要修正)
日本地図と流行伝播を組み合わせた興味深いシミュレーションを行うコードを書いて.
Reactで動的に処理できるようにして.
エラーが出ないように自己点検して.
import { useState, useEffect } from 'react';
// 日本の都道府県データ(名前とおおよその位置座標)
const prefectures = [
{ id: 1, name: "北海道", x: 80, y: 10, neighbors: [2] },
{ id: 2, name: "青森", x: 70, y: 30, neighbors: [1, 3, 5] },
{ id: 3, name: "岩手", x: 75, y: 35, neighbors: [2, 4, 5] },
{ id: 4, name: "宮城", x: 75, y: 40, neighbors: [3, 5, 6, 7] },
{ id: 5, name: "秋田", x: 65, y: 35, neighbors: [2, 3, 6] },
{ id: 6, name: "山形", x: 65, y: 40, neighbors: [4, 5, 7, 15] },
{ id: 7, name: "福島", x: 70, y: 45, neighbors: [4, 6, 8, 9, 15] },
{ id: 8, name: "茨城", x: 75, y: 50, neighbors: [7, 9, 11, 12] },
{ id: 9, name: "栃木", x: 70, y: 50, neighbors: [7, 8, 10, 15] },
{ id: 10, name: "群馬", x: 65, y: 50, neighbors: [9, 11, 15, 20] },
{ id: 11, name: "埼玉", x: 65, y: 55, neighbors: [8, 10, 12, 13, 19, 20] },
{ id: 12, name: "千葉", x: 75, y: 55, neighbors: [8, 11, 13] },
{ id: 13, name: "東京", x: 70, y: 55, neighbors: [11, 12, 14, 19] },
{ id: 14, name: "神奈川", x: 65, y: 60, neighbors: [13, 19, 22] },
{ id: 15, name: "新潟", x: 60, y: 45, neighbors: [6, 7, 9, 10, 16, 20] },
{ id: 16, name: "富山", x: 55, y: 45, neighbors: [15, 17, 20, 21] },
{ id: 17, name: "石川", x: 50, y: 45, neighbors: [16, 18, 21] },
{ id: 18, name: "福井", x: 50, y: 50, neighbors: [17, 21, 25, 26] },
{ id: 19, name: "山梨", x: 60, y: 55, neighbors: [11, 13, 14, 20, 22] },
{ id: 20, name: "長野", x: 60, y: 50, neighbors: [10, 11, 15, 16, 19, 21, 23] },
{ id: 21, name: "岐阜", x: 55, y: 50, neighbors: [16, 17, 18, 20, 23, 24, 25] },
{ id: 22, name: "静岡", x: 60, y: 60, neighbors: [14, 19, 23] },
{ id: 23, name: "愛知", x: 55, y: 55, neighbors: [20, 21, 22, 24] },
{ id: 24, name: "三重", x: 50, y: 55, neighbors: [21, 23, 25, 29, 30] },
{ id: 25, name: "滋賀", x: 45, y: 50, neighbors: [18, 21, 24, 26, 29] },
{ id: 26, name: "京都", x: 45, y: 55, neighbors: [18, 25, 27, 28, 29] },
{ id: 27, name: "大阪", x: 40, y: 55, neighbors: [26, 28, 29, 30] },
{ id: 28, name: "兵庫", x: 35, y: 55, neighbors: [26, 27, 31, 33, 34] },
{ id: 29, name: "奈良", x: 45, y: 60, neighbors: [24, 25, 26, 27, 30] },
{ id: 30, name: "和歌山", x: 40, y: 60, neighbors: [24, 27, 29] },
{ id: 31, name: "鳥取", x: 30, y: 50, neighbors: [28, 32, 33] },
{ id: 32, name: "島根", x: 25, y: 50, neighbors: [31, 33, 35] },
{ id: 33, name: "岡山", x: 30, y: 55, neighbors: [28, 31, 32, 34, 35] },
{ id: 34, name: "広島", x: 25, y: 55, neighbors: [28, 33, 35, 38] },
{ id: 35, name: "山口", x: 20, y: 55, neighbors: [32, 33, 34, 40, 41] },
{ id: 36, name: "徳島", x: 35, y: 65, neighbors: [37, 39] },
{ id: 37, name: "香川", x: 35, y: 60, neighbors: [36, 38, 39] },
{ id: 38, name: "愛媛", x: 25, y: 65, neighbors: [34, 37, 39, 44] },
{ id: 39, name: "高知", x: 30, y: 70, neighbors: [36, 37, 38] },
{ id: 40, name: "福岡", x: 15, y: 60, neighbors: [35, 41, 42, 44] },
{ id: 41, name: "佐賀", x: 10, y: 60, neighbors: [35, 40, 42, 43] },
{ id: 42, name: "長崎", x: 5, y: 60, neighbors: [40, 41, 43] },
{ id: 43, name: "熊本", x: 10, y: 65, neighbors: [41, 42, 44, 45, 46] },
{ id: 44, name: "大分", x: 15, y: 65, neighbors: [38, 40, 43, 45] },
{ id: 45, name: "宮崎", x: 15, y: 70, neighbors: [43, 44, 46] },
{ id: 46, name: "鹿児島", x: 10, y: 75, neighbors: [43, 45, 47] },
{ id: 47, name: "沖縄", x: 10, y: 90, neighbors: [46] }
];
export default function JapanEpidemicSimulation() {
const [infectionState, setInfectionState] = useState(() =>
prefectures.map(p => ({ id: p.id, infected: false, infectionLevel: 0 }))
);
const [startingPrefecture, setStartingPrefecture] = useState(13); // デフォルト: 東京
const [infectionProbability, setInfectionProbability] = useState(0.5);
const [spreadRate, setSpreadRate] = useState(0.1);
const [decayRate, setDecayRate] = useState(0.02);
const [isSimulating, setIsSimulating] = useState(false);
const [iterationCount, setIterationCount] = useState(0);
const [autoSimulate, setAutoSimulate] = useState(false);
// シミュレーションのリセット
const resetSimulation = () => {
setInfectionState(prefectures.map(p => ({
id: p.id,
infected: p.id === parseInt(startingPrefecture),
infectionLevel: p.id === parseInt(startingPrefecture) ? 1 : 0
})));
setIterationCount(0);
setIsSimulating(false);
};
// シミュレーションの開始
const startSimulation = () => {
resetSimulation();
setIsSimulating(true);
};
// シミュレーションの一歩進行
const stepSimulation = () => {
setInfectionState(prevState => {
const newState = [...prevState];
// 各都道府県について処理
prefectures.forEach(prefecture => {
const currentState = newState.find(s => s.id === prefecture.id);
// 感染状態の更新(減衰)
if (currentState.infectionLevel > 0) {
currentState.infectionLevel = Math.max(0, currentState.infectionLevel - decayRate);
currentState.infected = currentState.infectionLevel > 0.1;
}
// 隣接県への伝播
if (currentState.infected) {
prefecture.neighbors.forEach(neighborId => {
const neighborState = newState.find(s => s.id === neighborId);
// 感染率に基づいて隣接県に伝播
if (Math.random() < infectionProbability * currentState.infectionLevel * spreadRate) {
const newLevel = Math.min(1, neighborState.infectionLevel + spreadRate);
neighborState.infectionLevel = newLevel;
neighborState.infected = true;
}
});
}
});
return newState;
});
setIterationCount(prev => prev + 1);
};
// 自動シミュレーション
useEffect(() => {
let interval;
if (isSimulating && autoSimulate) {
interval = setInterval(() => {
stepSimulation();
}, 500);
}
return () => clearInterval(interval);
}, [isSimulating, autoSimulate, infectionProbability, spreadRate, decayRate]);
// 色を計算(感染レベルに応じて緑から赤へ)
const getColor = (level) => {
const r = Math.floor(255 * level);
const g = Math.floor(255 * (1 - level));
const b = 0;
return `rgb(${r}, ${g}, ${b})`;
};
// 感染状況の統計
const infectedCount = infectionState.filter(p => p.infected).length;
const totalInfectionLevel = infectionState.reduce((sum, p) => sum + p.infectionLevel, 0);
const averageInfectionLevel = totalInfectionLevel / prefectures.length;
return (
<div className="flex flex-col items-center p-4 bg-gray-100 rounded-lg">
<h1 className="text-2xl font-bold mb-4">日本の流行伝播シミュレーション</h1>
<div className="w-full flex flex-wrap gap-4 mb-4">
<div className="flex flex-col">
<label className="mb-1 text-sm">開始地点</label>
<select
className="p-2 border rounded"
value={startingPrefecture}
onChange={(e) => setStartingPrefecture(e.target.value)}
disabled={isSimulating}
>
{prefectures.map(p => (
<option key={p.id} value={p.id}>{p.name}</option>
))}
</select>
</div>
<div className="flex flex-col">
<label className="mb-1 text-sm">伝播確率: {infectionProbability.toFixed(2)}</label>
<input
type="range"
min="0"
max="1"
step="0.01"
value={infectionProbability}
onChange={(e) => setInfectionProbability(parseFloat(e.target.value))}
className="w-32"
/>
</div>
<div className="flex flex-col">
<label className="mb-1 text-sm">伝播率: {spreadRate.toFixed(2)}</label>
<input
type="range"
min="0.01"
max="0.5"
step="0.01"
value={spreadRate}
onChange={(e) => setSpreadRate(parseFloat(e.target.value))}
className="w-32"
/>
</div>
<div className="flex flex-col">
<label className="mb-1 text-sm">減衰率: {decayRate.toFixed(2)}</label>
<input
type="range"
min="0"
max="0.1"
step="0.001"
value={decayRate}
onChange={(e) => setDecayRate(parseFloat(e.target.value))}
className="w-32"
/>
</div>
</div>
<div className="flex gap-4 mb-4">
<button
onClick={startSimulation}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
disabled={isSimulating}
>
シミュレーション開始
</button>
<button
onClick={stepSimulation}
className="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600"
disabled={!isSimulating}
>
1ステップ進める
</button>
<button
onClick={resetSimulation}
className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
>
リセット
</button>
<div className="flex items-center">
<input
type="checkbox"
id="auto-simulate"
checked={autoSimulate}
onChange={(e) => setAutoSimulate(e.target.checked)}
className="mr-2"
/>
<label htmlFor="auto-simulate">自動シミュレーション</label>
</div>
</div>
<div className="text-sm mb-4">
<p>シミュレーション回数: {iterationCount}</p>
<p>感染県数: {infectedCount} / {prefectures.length}</p>
<p>平均感染レベル: {averageInfectionLevel.toFixed(4)}</p>
</div>
<div className="relative w-full max-w-2xl h-96 bg-blue-50 border rounded">
{/* 都道府県間の接続線 */}
<svg className="absolute top-0 left-0 w-full h-full">
{prefectures.map(prefecture => (
prefecture.neighbors.map(neighborId => {
const neighbor = prefectures.find(p => p.id === neighborId);
if (!neighbor) return null;
// 両方の都道府県の感染レベルを取得
const prefState = infectionState.find(s => s.id === prefecture.id);
const neighborState = infectionState.find(s => s.id === neighborId);
// 線の太さを感染レベルに応じて調整
const strokeWidth = (prefState.infectionLevel + neighborState.infectionLevel) * 2 + 0.5;
return (
<line
key={`${prefecture.id}-${neighborId}`}
x1={prefecture.x * 6}
y1={prefecture.y * 4}
x2={neighbor.x * 6}
y2={neighbor.y * 4}
stroke="gray"
strokeWidth={strokeWidth}
opacity="0.3"
/>
);
})
))}
</svg>
{/* 都道府県のノード */}
<div className="absolute top-0 left-0 w-full h-full">
{prefectures.map(prefecture => {
const state = infectionState.find(s => s.id === prefecture.id);
const size = 10 + state.infectionLevel * 20;
return (
<div
key={prefecture.id}
className="absolute rounded-full flex items-center justify-center text-xs font-bold shadow transition-all duration-300"
style={{
left: `${prefecture.x * 6 - size/2}px`,
top: `${prefecture.y * 4 - size/2}px`,
width: `${size}px`,
height: `${size}px`,
backgroundColor: getColor(state.infectionLevel),
cursor: 'pointer',
zIndex: 10
}}
title={`${prefecture.name}: 感染レベル ${state.infectionLevel.toFixed(2)}`}
>
{/* 小さいノードでは番号は表示しない */}
{size > 15 && prefecture.id}
</div>
);
})}
</div>
</div>
<div className="mt-4 text-sm">
<p className="font-bold">モデルの説明:</p>
<p>• 都道府県間の接続関係に基づいて流行が伝播します</p>
<p>• 感染レベルが高いほど近隣に伝播しやすくなります</p>
<p>• 時間経過とともに自然に感染レベルは低下します</p>
<p>• 色は緑(未感染)から赤(高感染)まで変化します</p>
</div>
</div>
);
}