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>
  );
}

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です