感染症の伝播と集団免疫効果を視覚化する。
先日、たまたま見つけて購入した「Nature in Code」に掲載されていた、感染症シミュレーション(SIRモデル)が面白そうだったので作成してみた。
白いセルが感染可能者(感染する前の健康な人)、赤いセルが感染者、緑のセルが隔離者(死亡、もしくは快復して免疫を獲得した人)となる。
参考書のコードを少しカスタマイズして、感染率や隔離率を調整できるようにした。
感染率が高いと早く広範囲に感染が広がり、隔離率が高いと早く収束する。
数値は、「初期感染者」以外は、0〜1の値で割合を示す。例えば、感染率が1の場合、隣接する感染可能者は100%感染する。
初期免疫保持者率は、感染が広がる前に免疫を獲得している人(灰色)の割合。
初期免疫保持者が多いと、拡散せずに早期に収束する。(集団免疫効果)
このシミュレーターだと初期隔離者がランダムに配置されるため、集団免疫率は入力した数値より低くなる。
(重複したセルに配置されることがあるので、1を入力しても100%にならない)
改良の余地が多いけど、数値をいろいろ調整して試してみると、なかなか楽しい。
サンプルコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
!(function(){ "use strict"; var width = 600; var height = 600; var svg = d3.select('svg') .attr('width', width) .attr('height', height); var grid_length = 100; var grid = []; var temp_grid = []; var point = 3 var touch = 0.75; var beta = 0.1; var gamma = 0.05; var timer; var flag = true; init(); d3.select("#point").on("change", init); d3.select("#touch").on("change", init); d3.select("#start").on("click", function(){ beta = d3.select("#beta").node().value; gamma = d3.select("#gamma").node().value; function simulate_and_visualize() { run_time_step(); update_grid(grid,["S","white","I","red","R","green", "W", "gray"]); } if(flag) timer = setInterval(simulate_and_visualize, 50); flag = false; }); d3.select("#stop").on("click", function(){ clearInterval(timer); flag = true; }); d3.select("#reset").on("click", function(){ clearInterval(timer); init_grid(); update_grid(grid,["S","white","I","red","R","green", "W", "gray"]); flag = true; }); function init() { clearInterval(timer); point = d3.select("#point").node().value; touch = d3.select("#touch").node().value; init_grid(); update_grid(grid,["S","white","I","red","R","green", "W", "gray"]); } function get_random_int(min, max) { return Math.floor(Math.random()*(max - min + 1)) + min; } function init_grid() { for(var i=0; i < grid_length; i = i + 1){ grid[i] = []; for (var ii = 0; ii < grid_length; ii = ii + 1){ grid[i][ii] = "S"; } } for (var i = 0; i < (grid_length*grid_length) * (1 - touch ) ; i++) { grid[get_random_int(0, grid_length-1)][get_random_int(0, grid_length-1)] = "W"; } for (var i = 0; i < point ; i++) { grid[get_random_int(0, grid_length-1)][get_random_int(0, grid_length-1)] = "I"; } } function run_time_step() { for(var i=0; i < grid_length; i = i + 1){ temp_grid[i] = []; for (var ii = 0; ii < grid_length; ii = ii + 1){ temp_grid[i][ii] = grid[i][ii]; } } for (i=0; i < grid_length; i = i+1){ for(ii = 0; ii < grid_length; ii = ii + 1){ if (grid[i][ii] == "I"){ expose_neighbors(i, ii); tray_recovery(i, ii); } } } for (i=0; i < grid_length; i = i+1){ for(ii = 0; ii < grid_length; ii = ii + 1){ grid[i][ii] = temp_grid[i][ii]; } } } function expose_neighbors(i, ii) { for(var n_i = i-1; n_i <= i+1; n_i = n_i + 1){ for(var n_ii = ii-1; n_ii <= ii+1; n_ii = n_ii + 1){ if (n_i == i && n_ii == ii){ continue; } try_infection(get_bounded_index(n_i), get_bounded_index(n_ii)); } } } function try_infection(i, ii) { if (grid[i][ii] == "S"){ if (Math.random() < beta){ temp_grid[i][ii] = "I" } } } function tray_recovery(i, ii) { if (grid[i][ii] == "I"){ if (Math.random() < gamma){ temp_grid[i][ii] = "R" } } } function get_bounded_index(index){ var bounded_index = index; if (index < 0){ bounded_index = index + grid_length; } if (index >= grid_length){ bounded_index = index - grid_length; } return bounded_index; } function update_grid(data,colors) { var grid_length = data.length; var rw = Math.floor(width/grid_length); var rh = Math.floor(height/grid_length); var g = svg.selectAll('g').data(data) var newG = g.enter().append('g') .attr('transform', function (d, i) { return 'translate(0, ' + (width/grid_length) * i + ')'; }); var rect = g.merge(newG).selectAll('rect') .data(function (d) { return d; }) var newRect = rect.enter().append('rect') .attr('x', function (d, i) { return (width/grid_length) * i; }) .attr('width', rw) .attr('height', rh) rect.merge(newRect).attr('class',function(d) { return d; }); for (var i = 0; i < colors.length; i = i + 2) { d3.selectAll("."+colors[i]).style("fill",colors[i+1]); } } }()); |