From b7f254f151cd051234f7df1bd642dc2c8f7d5af0 Mon Sep 17 00:00:00 2001 From: Nicky Case Date: Thu, 23 Apr 2020 12:21:18 -0400 Subject: [PATCH] nice stuff --- icons/hand.png | Bin 0 -> 4535 bytes sim/index.html | 57 +++- sim/js/Controls.js | 373 ++++++++++++++++++++++++ sim/js/Model.js | 316 ++++++++++++++++++++ sim/js/Stages.js | 510 +++++++++++++++++++++++++++++++++ sim/{sim.js => js/_OLD_sim.js} | 2 - sim/js/helpers.js | 10 + sim/js/main.js | 8 + sim/sim.css | 37 +++ words_epi_101.md | 134 +++++++++ 10 files changed, 1434 insertions(+), 13 deletions(-) create mode 100644 icons/hand.png create mode 100644 sim/js/Controls.js create mode 100644 sim/js/Model.js create mode 100644 sim/js/Stages.js rename sim/{sim.js => js/_OLD_sim.js} (99%) create mode 100644 sim/js/helpers.js create mode 100644 sim/js/main.js create mode 100644 words_epi_101.md diff --git a/icons/hand.png b/icons/hand.png new file mode 100644 index 0000000000000000000000000000000000000000..d0a278f9d3ac0c0caaab6cece973dc8624c5c0d1 GIT binary patch literal 4535 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 X3m0V^nbW-)A2I+lBP&z`5}WitTHBcn literal 0 HcmV?d00001 diff --git a/sim/index.html b/sim/index.html index 053603a..7288a20 100644 --- a/sim/index.html +++ b/sim/index.html @@ -19,25 +19,26 @@

- Infects a every N days + Infects 1 per N days
- (when almost everyone's still ) -
- + + (at the start of the epidemic)
+
+
Takes N days to go from to
- +
Recovers in N days
- +
@@ -168,10 +169,21 @@
- Susceptible -
Exposed
-
Infectious -
Removed
+ + Susceptible +
+
+ + Exposed +
+
+ + Infectious +
+
+ + Removed +
@@ -188,6 +200,25 @@
+
+
+
+
+
+ + Try re-running the simulation + with different numbers! + + (note: you can change the numbers while the sim is running) + + + + Once you're done playing around, + scroll down to keep reading! + +
+
+ @@ -207,4 +238,8 @@ - \ No newline at end of file + + + + + \ No newline at end of file diff --git a/sim/js/Controls.js b/sim/js/Controls.js new file mode 100644 index 0000000..acbba83 --- /dev/null +++ b/sim/js/Controls.js @@ -0,0 +1,373 @@ +///////////////////////////////////// +// PARAMETERS /////////////////////// +///////////////////////////////////// + +let S,E,I,R; + +let params = {}; + +// Update Month Ticks! +let mn = [...$('#month_names').children].map((dom)=>{ + return dom.innerHTML; +}); +let updateMonthTicks = ()=>{ + + let ticks; + + switch(params.p_years){ + case 0.5: + ticks = [ + mn[0]+" 2020", + mn[1]+" 2020", + mn[2]+" 2020", + mn[3]+" 2020", + mn[4]+" 2020", + mn[5]+" 2020", + null + ]; + break; + case 1.0: + ticks = [ + mn[0]+" 2020", + mn[3]+" 2020", + mn[6]+" 2020", + mn[9]+" 2020", + null + ]; + break; + default: + ticks = []; + for(let time=0; time<=params.p_years; time+=0.5){ + if(time%1 != 0){ + ticks.push(null); + }else{ + ticks.push(time+2020); + } + } + ticks[ticks.length-1] = null; + break; + } + + let monthTicksDOM = $('#month_ticks'); + monthTicksDOM.innerHTML = ''; + + ticks.forEach((tick,i)=>{ + + if(i==ticks.length-1 || !tick) return; + + let tickDOM = document.createElement('div'); + let tickSpanDOM = document.createElement('span'); + tickSpanDOM.innerHTML = tick; + tickDOM.style.left = ((i/(ticks.length-1))*500)+"px"; + tickDOM.appendChild(tickSpanDOM); + monthTicksDOM.appendChild(tickDOM); + + }); + +} + +// Sliders +let DONT_RECORD_HISTORY = true; +let _DO_NOT_RECURSE = true; +let yearsSlider = $('#p_years'); +$all('.sim_input').forEach((slider)=>{ + let id = slider.id; + let label = $('#label_'+id); + let isRecordable = slider.classList.contains('recordable'); + let onChange = ()=>{ + + // Change label & param + let val = parseFloat(slider.value); + if(label){ + let digits = parseInt(label.getAttribute('toFixed')); + label.innerHTML = digits ? val.toFixed(digits) : val; + } + params[id] = val; + + // Record history (@ this day) + if(isRecordable){ + if(!DONT_RECORD_HISTORY){ + recordedHistory.push([id, val, Math.round(daysCurrent)]); + } + } + + // Just to update other random crap + if(!_DO_NOT_RECURSE){ + _DO_NOT_RECURSE = true; + updateModel(1, true); // one day + _DO_NOT_RECURSE = false; + } + + // HACK: If this is years, RESET SIM & CHANGE THE MONTHS + if(slider==yearsSlider){ + if(daysCurrent>0){ + _resetTheSim(); + _updateButtons(); + } + updateMonthTicks(); + } + + // MORE HAX + if(daysCurrent==0){ + sbDOM.setAttribute('label','params'); + } + + }; + slider.oninput = onChange; + onChange(); +}); +DONT_RECORD_HISTORY = false; +_DO_NOT_RECURSE = false; + +// Checkboxes +$all('.sim_checkbox').forEach((checkbox)=>{ + let id = checkbox.id; + let label = $('#label_'+id); + let onChange = ()=>{ + if(label){ + if(!checkbox.checked){ + label.setAttribute("strikethrough","yes"); + }else{ + label.removeAttribute("strikethrough"); + } + } + params[id] = checkbox.checked; + }; + checkbox.oninput = onChange; + onChange(); +}); + + +///////////////////////////////////// +// (RE)START //////////////////////// +///////////////////////////////////// + +let IS_PLAYING = false; + +let recordedHistory = []; +let IS_REPLAYING_HISTORY = false; + +let START_S = 0.999, + START_E = 0.001, + START_I = 0.001, + START_R = 0.000; + +let restart = ()=>{ + + // Model + S = START_S; + E = START_E; + I = START_I; + R = START_R; + + // Drawing + daysCurrent = 0; + daysDrawn = 0; + daysTotal = params.p_years*365; + daysPerFrame = 0; // calc it on the fly + let ctx = context; + ctx.setTransform(1,0,0,1,0,0); + ctx.clearRect(0,0,canvas.width,canvas.height); + +}; + + +///////////////////////////////////// +// BIG & SMALL BUTTONS ////////////// +///////////////////////////////////// + +let bbDOM = $('.big_button'); +let sbDOM = $('.small_button'); + +let handTutorial = 0; + +bbDOM.onclick = ()=>{ + + if(CURRENT_STAGE.SHOW_HAND=="tutorial_0"){ + if(handTutorial==0){ + hideHand(); + handTutorial = 1; + }else if(handTutorial==1){ + hideHand(); + }else if(handTutorial==2){ + hideHand(); + setTimeout(()=>{ + showHand('end'); + },1); + handTutorial = 3; + } + } + if(CURRENT_STAGE.SHOW_HAND=="tutorial_1"){ + if(handTutorial==0){ + hideHand(); + handTutorial = 1; + }else if(handTutorial==1){ + hideHand(); + handTutorial = 2; + } + } + + if(daysCurrent>daysTotal || params._HACK_RESET_WHEN_I_100=="go" || params._HACK_RESET_WHEN_R_100=="go"){ + + _resetTheSim(); + params._HACK_RESET_WHEN_I_100 = undefined; + params._HACK_RESET_WHEN_R_100 = undefined; + + if(CURRENT_STAGE.SHOW_HAND=="tutorial_0" && handTutorial<=1){ + setTimeout(()=>{ + showHand('params'); + },1000); + handTutorial = 2; + } + + }else{ + if(daysCurrent==0){ + restart(); + } + IS_PLAYING = !IS_PLAYING; + } + _updateButtons(); +}; + +let defaultParams = [ + ["p_transmission", 4], + ["p_exposed", 3], + ["p_recovery", 11], + ["p_waning", 1], + ["p_hospital", 100], + ["p_years", 2], + ["p_speed", 30], +]; + +sbDOM.onclick = ()=>{ + if(daysCurrent==0){ + + changeSliders(defaultParams); + changeSliders(CURRENT_STAGE.inputs); + + }else if(daysCurrent>daysTotal){ + _replayTheSim(); + }else{ + _resetTheSim(); + } + _updateButtons(); +}; + +let _updateButtons = ()=>{ + + if(daysCurrent > daysTotal){ + + bbDOM.setAttribute('label','reset'); + sbDOM.setAttribute('label',params.CANNOT_REPLAY_HISTORY ? '' : 'replay'); + + }else if(IS_PLAYING){ + + bbDOM.setAttribute('label','pause'); + sbDOM.setAttribute('label','reset'); + + }else{ + + if(daysCurrent==0){ + bbDOM.setAttribute('label','start'); + sbDOM.setAttribute('label','NONE'); + }else{ + bbDOM.setAttribute('label','continue'); + sbDOM.setAttribute('label','reset'); + } + + } + +}; + +let hofp = $('#hide_on_first_playthrough'); +let _showAllControls = ()=>{ + + let originalHeight = hofp.getBoundingClientRect().height; + if(originalHeight>0) return; + + hofp.style.height = "auto"; + hofp.style.position = "absolute"; + hofp.style.top = "-1000px"; + setTimeout(()=>{ + let newHeight = hofp.getBoundingClientRect().height; + hofp.style.position = ""; + hofp.style.top = ""; + hofp.style.height = originalHeight+"px"; + setTimeout(()=>{ + hofp.style.marginTop = ""; + hofp.style.height = newHeight+"px"; + },20); + },20); +}; +let _hideAllControls = ()=>{ + hofp.style.height = "0px"; +}; + +let _resetTheSim = ()=>{ + _showAllControls(); + IS_REPLAYING_HISTORY = false; + recordedHistory = []; + restart(); + IS_PLAYING = false; +}; +let _replayTheSim = ()=>{ + _hideAllControls(); + IS_REPLAYING_HISTORY = true; + restart(); + IS_PLAYING = true; +}; + + +///////////////////////////////////// +// THE HAND ///////////////////////// +///////////////////////////////////// + +let pointerDOM = $('#pointer'); +let handDOM = $('#hand_container'); +let wordsDOM = $('#pointer_words'); +let HAND_IS_VISIBLE = false; +let showHand = (position)=>{ + + HAND_IS_VISIBLE = true; + pointerDOM.style.display = 'block'; + + $all('span',pointerDOM).forEach((span)=>{ + span.style.display = 'none'; + }); + + switch(position){ + case 'start': + handDOM.style.top = '111px'; + handDOM.style.left = '98px'; + break; + case 'params': case 'params2': + handDOM.style.top = '100px'; + handDOM.style.left = '280px'; + handDOM.style.transform = 'rotate(270deg)'; + wordsDOM.style.top = '117px'; + wordsDOM.style.left = '377px'; + wordsDOM.style.textAlign = 'left'; + $('#pointer_params').style.display = 'inline'; + $('#pointer_params_2').style.display = (position=='params2') ? 'inline' : 'none'; + break; + case 'end': + handDOM.style.top = '445px'; + handDOM.style.left = '101px'; + handDOM.style.transform = 'rotate(180deg) scaleX(-1)'; + wordsDOM.style.top = '363px'; + wordsDOM.style.left = '17px'; + wordsDOM.style.textAlign = 'center'; + $('#pointer_scroll').style.display = 'inline'; + break; + } + + +}; +let hideHand = ()=>{ + + HAND_IS_VISIBLE = false; + pointerDOM.style.display = 'none'; + +}; + + diff --git a/sim/js/Model.js b/sim/js/Model.js new file mode 100644 index 0000000..35fbeba --- /dev/null +++ b/sim/js/Model.js @@ -0,0 +1,316 @@ +///////////////////////////////////// +// UPDATE /////////////////////////// +///////////////////////////////////// + +let int = { + hygiene: 0, + distancing: 0, + isolate: 0, + quarantine: 0, + cleaning: 0, + masks: 0, + summer: 0, + vaccines: 0 +}; + +let daysCurrent, daysDrawn, daysTotal, daysPerFrame; +let r0_dom = $('#p_r0'); +let s_dom = $('#p_s'); +let re_dom = $('#p_re'); + +let updateModel = (days, fake)=>{ + + let real_S=S, real_E=E, real_I=I, real_R=R; + + let transmissionRate = 1/params.p_transmission, + incubationRate = 1/params.p_exposed, + recoveryRate = 1/params.p_recovery, + immunityLossRate = 1/(params.p_waning*365); + + // R0 + r0_dom.value = transmissionRate/recoveryRate; + if(r0_dom.oninput) r0_dom.oninput(); + + // Transmission affected by interventions + + int.hygiene = params.p_hygiene; + transmissionRate *= 1 - int.hygiene*0.5; + + int.distancing = params.p_distancing; + transmissionRate *= 1 - int.distancing*0.7; + + int.isolate = params.p_isolate; + transmissionRate *= 1 - int.isolate*0.4; + + int.quarantine = params.p_quarantine; + transmissionRate *= 1 - int.quarantine*0.5; + + int.cleaning = params.p_cleaning; + transmissionRate *= 1 - int.cleaning*0.1; + + int.masks = params.p_masks; + transmissionRate *= 1 - int.masks*0.1; + + int.summer = (1 - Math.cos((daysCurrent-30)/365 * Math.TAU))/2; + //int.summer = Math.max(0, int.summer); + int.summer *= params.p_summer; + transmissionRate *= 1 - int.summer* 0.333; // 15°C diff * 0.0225 (Wang et al) + + // Vaccination... + if(S > 1-params.p_vaccines){ + let newlyVaccinated = S - (1-params.p_vaccines); + S -= newlyVaccinated; + R += newlyVaccinated; + } + int.vaccines = params.p_vaccines; + + // S... + if(!s_dom.disabled){ + S = parseFloat(s_dom.value); + } + + // Update Model + let newlyExposed; + if(params.EXPONENTIAL){ + newlyExposed = I*transmissionRate*days; + }else{ + newlyExposed = S*I*transmissionRate*days; + } + + let newlyInfectious = params.c_exposed ? (E*incubationRate*days) : 0; + let newlyRecovered = params.c_recovery ? (I*recoveryRate*days) : 0; + let newlySusceptible = params.c_waning ? (R*immunityLossRate*days) : 0; + + S -= newlyExposed; + E += newlyExposed; + + if(params.c_exposed){ + E -= newlyInfectious; + I += newlyInfectious; + }else{ + I += E; // instant transfer + E = 0; + } + + I -= newlyRecovered; + R += newlyRecovered; + + R -= newlySusceptible; + S += newlySusceptible; + + // bounds + if(S<0) S=0; + if(I>1) I=1; + + // Susceptible & Re + if(s_dom.disabled){ + s_dom.value = S; + } + re_dom.value = newlyExposed/newlyRecovered; + if(re_dom.oninput) re_dom.oninput(); + + // IF FAKE, UNDO EVERYTHING + if(fake){ + S = real_S; + E = real_E; + I = real_I; + R = real_R; + } + +} + +///////////////////////////////////// +// DRAW ///////////////////////////// +///////////////////////////////////// + +let canvas = $('#graphCanvas'); +let context = canvas.getContext('2d'); +canvas.width = 1000; +canvas.height = 1000; +canvas.style.width = (canvas.width/2)+"px"; +canvas.style.height = (canvas.height/2)+"px"; + + +let interventionColors = [ + ['hygiene', 'hsl(230,100%,63%)', 0.1], + ['distancing', 'hsl(200,100%,63%)', 0.2], + ['isolate', 'hsl(140,100%,63%)', 0.2], + ['quarantine', 'hsl(100,100%,63%)', 0.2], + ['cleaning', 'hsl(290,100%,63%)', 0.2], + ['masks', 'hsl(260,100%,63%)', 0.2], + ['summer', 'hsl(20,100%,63%)', 0.3], + ['vaccines', 'hsl(53, 100%, 73%)', 0.6], +]; + +let _isItPastHerd = false; + +let draw = ()=>{ + + // Redraw + requestAnimationFrame(draw); + + // Update SI + if(params._HACK_SHOW_SI_PERCENTS){ + let digits = 3; //(params._HACK_SHOW_SI_PERCENTS===true) ? 5 : params._HACK_SHOW_SI_PERCENTS; + $('#show_percent_s').innerHTML = ': '+(S*100).toFixed(digits)+'%'; + $('#show_percent_e').innerHTML = ': '+(I*100).toFixed(digits)+'%'; + $('#show_percent_i').innerHTML = ': '+(I*100).toFixed(digits)+'%'; + $('#show_percent_r').innerHTML = ': '+(R*100).toFixed(digits)+'%'; + } + + // Paused? At the end? + if(!IS_PLAYING) return; + if(params.FROZEN_IN_TIME) return; + if(daysCurrent > daysTotal){ + + IS_PLAYING = false; + _updateButtons(); + + if(params._HACK_RESET_WHEN_R_100=="ready"){ + params._HACK_RESET_WHEN_R_100 = "go"; + if(CURRENT_STAGE.SHOW_HAND=="tutorial_1" && handTutorial==1){ + if(!HAND_IS_VISIBLE){ + showHand('start'); + } + } + } + + return; + } + + // HACK + if(params._HACK_RESET_WHEN_I_100=="ready" && I>=0.999999){ + + params._HACK_RESET_WHEN_I_100 = "go"; + bbDOM.setAttribute('label','reset'); + sbDOM.setAttribute('label',params.CANNOT_REPLAY_HISTORY ? '' : 'replay'); + + if(CURRENT_STAGE.SHOW_HAND=="tutorial_0" && handTutorial==1){ + if(!HAND_IS_VISIBLE){ + setTimeout(()=>{ + showHand('start'); + },2500); + } + } + if(CURRENT_STAGE.SHOW_HAND=="tutorial_1" && handTutorial==1){ + if(!HAND_IS_VISIBLE){ + showHand('start'); + } + } + + } + + + // Replay History! + if(IS_REPLAYING_HISTORY){ + let keepLooping = true; + while(keepLooping){ + + let record = recordedHistory[0]; + if(!record || daysCurrent{ + ctx.fillStyle = ic[1]; + ctx.globalAlpha = int[ic[0]] * ic[2]; + ctx.fillRect(0,y,w,h); + ctx.globalAlpha = 1; + }); + + // ICU bed capacity + if(params.p_hospital){ + y = (1-((params.p_hospital/100)*0.02))*canvas.height; + h = 2; + ctx.fillStyle = "#000000"; + ctx.fillRect(0,y,w,h); + } + + // Herd Immunity + if(params.c_recovery && !params.DO_NOT_SHOW_HERD_IMMUNITY){ + + let transmissionRate = 1/params.p_transmission, + recoveryRate = 1/params.p_recovery, + r0 = transmissionRate/recoveryRate; + let herdImmunity = 1 - (1/r0); + + // Dashed + if((daysDrawn/daysTotal)*100 % 1 < 0.5){ + y = (1-herdImmunity)*canvas.height; + h = 2; + ctx.fillStyle = "#000000"; + ctx.fillRect(0,y,w,h); + } + + // Line when it passes! + let isItCurrentlyAboveHerd = S<(1/r0); + if(!_isItPastHerd && isItCurrentlyAboveHerd){ + h = canvas.height; + ctx.fillStyle = "rgba(0,0,0,0.2)"; + ctx.fillRect(0,0,w,h); + } + _isItPastHerd = isItCurrentlyAboveHerd; + + } + + // RESET + ctx.setTransform(1,0,0,1,0,0); + daysDrawn += timeDelta; + + } + +}; diff --git a/sim/js/Stages.js b/sim/js/Stages.js new file mode 100644 index 0000000..19e6ac2 --- /dev/null +++ b/sim/js/Stages.js @@ -0,0 +1,510 @@ +///////////////////////////////////// +// SIM STAGES /////////////////////// +///////////////////////////////////// + +/* + +01 Just exponential +02 Just logistic +03 Decay +04 The famous curve +05 SEIR +06a - r0 calc +06b - r0 calc with S +07 SEIR with R + +06 Do Nothing +07 Flatten The Curve +08 Lockdown for a while +09 Intermittent Lockdown +10 Lockdown, then Test & Trace... and with Vaccination! +11 Also deep cleaning & masks & summer (with one more lockdown) + +12 Decay of Recovered +13 Oscillations +13b w Hospital Capacity +14 Oscillations with Summer (Hospital Capacity) +15 Intermittent Vaccines + +SB Full Sandbox + +*/ + +const STAGES = { + + "00": { + hide: [ + "section_dynamics", + "c_recovery","label_c_waning","section_meta_years","c_exposed", + "int_block_1","int_block_2","int_block_3","int_block_4","int_block_5","hospital_capacity" + ], + inputs: [ + ["p_transmission",4], + ["p_years",1], + ["p_speed",5], + ["TIME_DELTA", 0.5], + //["EXPONENTIAL",true], + ["CANNOT_REPLAY_HISTORY", true] + ], + checkboxes: [ + ["c_recovery", true], + ["c_exposed",true] + ], + }, + + ////////////////////////////////////////// + // THE SIMULATION //////////////////////// + ////////////////////////////////////////// + + "01": { + hide: [ + "section_r","label_c_recovery","label_c_waning","section_meta_years","label_c_exposed", + "label_exposed","label_removed", + "label_herd_immunity","label_capacity", + "label_transmission_caveat" + ], + inputs: [ + ["p_years",0.5], + ["p_speed",40], + ["p_hospital", 0], + ["EXPONENTIAL",true], + ["CANNOT_REPLAY_HISTORY", true], + ["_HACK_RESET_WHEN_I_100","ready"], + ["_HACK_SHOW_SI_PERCENTS",true] + ], + SIR: [0.99999,0.00001,0], + SHOW_HAND: "tutorial_0" + }, + + "02": { + hide: [ + "section_r","label_c_recovery","label_c_waning","section_meta_years","label_c_exposed", + "label_exposed","label_removed", + "label_herd_immunity","label_capacity" + ], + inputs: [ + ["p_years",0.5], + ["p_speed",20], + ["p_hospital", 0], + ["_HACK_RESET_WHEN_I_100","ready"], + ["_HACK_SHOW_SI_PERCENTS",true], + ["CANNOT_REPLAY_HISTORY", true] + ], + SIR: [0.99999,0.00001,0], + SHOW_HAND: "tutorial_1" + }, + + "03": { + hide: [ + "section_r","label_transmission","label_c_waning","section_meta_years","label_c_exposed", + "c_recovery", + "label_exposed","label_susceptible", + "label_herd_immunity","label_capacity" + ], + inputs: [ + ["p_years",0.5], + ["p_speed",10], + ["p_hospital", 0], + ["_HACK_RESET_WHEN_R_100","ready"], + ["_HACK_SHOW_SI_PERCENTS",true], + ["CANNOT_REPLAY_HISTORY", true], + ["DO_NOT_SHOW_HERD_IMMUNITY", true] + ], + checkboxes: [ + ["c_recovery", true] + ], + SIR: [0,1,0], + SHOW_HAND: "tutorial_1" + }, + + "04": { + hide: [ + "section_r","label_c_waning","c_recovery","label_c_exposed","section_meta_years", + //"section_meta", + "label_exposed", + "label_herd_immunity","label_capacity" + ], + inputs: [ + ["p_years",1], + ["p_speed",10], + ["p_hospital", 0], + //["TIME_DELTA", 0.1], + ["CANNOT_REPLAY_HISTORY", true], + ["DO_NOT_SHOW_HERD_IMMUNITY", true], + ["_HACK_SHOW_SI_PERCENTS",true], + ], + checkboxes: [ + ["c_recovery", true] + ], + SIR: [0.99999,0.00001,0], + //SHOW_ALL_AT_START: true,, + SHOW_HAND: "tutorial_1" + }, + + "05": { + hide: [ + "section_r","label_c_waning","c_recovery","c_exposed","section_meta_years", + //"section_meta", + //"label_exposed", + "label_herd_immunity","label_capacity" + ], + inputs: [ + ["p_years",1], + ["p_speed",10], + ["p_hospital", 0], + //["TIME_DELTA", 0.1], + ["CANNOT_REPLAY_HISTORY", true], + ["DO_NOT_SHOW_HERD_IMMUNITY", true], + ["_HACK_SHOW_SI_PERCENTS",3], + ], + checkboxes: [ + ["c_recovery", true], + ["c_exposed",true] + ], + SIR: [0.99999,0.00001,0], + //SHOW_ALL_AT_START: true, + SHOW_HAND: "tutorial_1" + }, + + "06a": { + hide: [ + "section_meta","label_c_waning","c_recovery", + "int_block_0","int_block_1","int_block_2","int_block_3","int_block_4","int_block_5","hospital_capacity", + "graph", + "label_s","label_re", + "sim_controls" + ], + checkboxes: [ + ["c_recovery", true] + ], + inputs: [ + ["FROZEN_IN_TIME", true], + ], + }, + + "06b": { + hide: [ + "section_meta","label_c_waning","c_recovery","label_c_exposed", + "int_block_0","int_block_1","int_block_2","int_block_3","int_block_4","int_block_5","hospital_capacity", + "graph", + "sim_controls" + ], + checkboxes: [ + ["c_recovery", true] + ], + inputs: [ + ["FROZEN_IN_TIME", true], + ], + disabled:[ + ["p_s", false] + ] + }, + + "07": { + hide: [ + //"section_dynamics", + /*"section_r",*/"label_c_waning","c_recovery","c_exposed", + "section_meta_years", + "int_block_0","int_block_1","int_block_2","int_block_3","int_block_4","int_block_5","hospital_capacity", + /*"label_herd_immunity",*/"label_capacity" + ], + inputs: [ + ["p_years",1], + ["p_speed",30], + ["p_hospital", 0], + //["TIME_DELTA", 0.1], + ["CANNOT_REPLAY_HISTORY", true], + //["DO_NOT_SHOW_HERD_IMMUNITY", true], + ["_HACK_SHOW_SI_PERCENTS",3], + ], + checkboxes: [ + ["c_recovery", true], + ["c_exposed",true] + ], + SIR: [0.99999,0.00001,0], + SHOW_ALL_AT_START: true, + //SHOW_HAND: "tutorial_1" + }, + + ////////////////////////////////////////// + // THE NEXT FEW MONTHS /////////////////// + ////////////////////////////////////////// + + /* + "06": { + hide: [ + "section_dynamics", + "section_meta","label_c_waning","c_recovery", + "int_block_0","int_block_1","int_block_2","int_block_3","int_block_4","int_block_5","hospital_capacity" + ], + inputs: [ + ["p_years",2], + ["p_speed",20], + //["TIME_DELTA", 0.2], + ], + checkboxes: [ + ["c_recovery", true] + ] + }, + + "07": { + hide: [ + "section_dynamics", + "section_meta","label_c_waning","c_recovery", + "int_block_1","int_block_2","int_block_3","int_block_4","int_block_5","hospital_capacity" + ], + inputs: [ + ["p_years",2], + ["p_speed",20], + //["TIME_DELTA", 0.2], + ], + checkboxes: [ + ["c_recovery", true] + ] + }, + + "08": { + hide: [ + "section_dynamics", + "section_meta","label_c_waning","c_recovery", + "int_block_2","int_block_3","int_block_4","int_block_5","hospital_capacity" + ], + inputs: [ + ["p_years",2], + ["p_speed",20], + //["TIME_DELTA", 0.2], + ], + checkboxes: [ + ["c_recovery", true] + ] + }, + + "09": { + hide: [ + "section_dynamics", + "section_meta","label_c_waning","c_recovery", + "int_block_2","int_block_3","int_block_4","int_block_5","hospital_capacity" + ], + inputs: [ + ["p_years",2], + ["p_speed",20], + //["TIME_DELTA", 0.2], + ], + checkboxes: [ + ["c_recovery", true] + ] + }, + + "10": { + hide: [ + "section_dynamics", + "section_meta","label_c_waning","c_recovery", + "int_block_3","int_block_4","hospital_capacity" + ], + inputs: [ + ["p_years",2], + ["p_speed",20], + //["TIME_DELTA", 0.2], + ], + checkboxes: [ + ["c_recovery", true] + ] + }, + + "11": { + hide: [ + "section_dynamics", + "section_meta","label_c_waning","c_recovery", + "hospital_capacity" + ], + inputs: [ + ["p_years",2], + ["p_speed",20], + //["TIME_DELTA", 0.2], + ], + checkboxes: [ + ["c_recovery", true] + ] + }, + + ////////////////////////////////////////// + // THE NEXT FEW YEARS //////////////////// + ////////////////////////////////////////// + + "12": { + hide: ["section_r","section_meta","label_transmission","label_c_recovery","c_waning"], + inputs: [ + ["p_years",5], + ["p_speed",10] + ], + checkboxes: [ + ["c_waning", true] + ], + SIR: [0,0,1] + }, + + "13": { + hide: [ + "section_meta","c_waning","c_recovery", + "int_block_0","int_block_1","int_block_2","int_block_3","int_block_4","int_block_5","hospital_capacity" + ], + inputs: [ + ["p_years",5], + ["p_speed",20], + //["TIME_DELTA", 0.2], + ], + checkboxes: [ + ["c_recovery", true], + ["c_waning", true] + ], + //SIR: [0.09,0.01,0.9] + }, + + "13b": { + hide: [ + "section_dynamics", + "section_meta","c_waning","c_recovery", + "int_block_0","int_block_1","int_block_2","int_block_3","int_block_4","int_block_5", + ], + inputs: [ + ["p_years",5], + ["p_speed",20], + //["TIME_DELTA", 0.2], + ], + checkboxes: [ + ["c_recovery", true], + ["c_waning", true] + ], + SIR: [0.09,0.01,0.9] + }, + + "14": { + hide: [ + "section_dynamics", + "section_meta","c_waning","c_recovery", + "int_block_0","int_block_1","int_block_2","int_block_3","int_block_5", + ], + inputs: [ + ["p_years",5], + ["p_speed",20], + ["p_summer",1], + //["TIME_DELTA", 0.2], + ], + checkboxes: [ + ["c_recovery", true], + ["c_waning", true] + ], + //SIR: [0.09,0.01,0.9] + }, + + "15": { + hide: [ + "section_dynamics", + "section_meta","c_waning","c_recovery", + "int_block_0","int_block_1","int_block_2","int_block_3", + ], + inputs: [ + ["p_years",5], + ["p_speed",20], + ["p_summer",1], + //["TIME_DELTA", 0.2], + ], + checkboxes: [ + ["c_recovery", true], + ["c_waning", true] + ], + SIR: [0.09,0.01,0.9] + }, + */ + + ////////////////////////////////////////// + // SANDBOX /////////////////////////////// + ////////////////////////////////////////// + + "SB": { + checkboxes: [ + ["c_recovery", true], + ["c_waning", true] + ] + }, + + +}; + +let changeSliders = (idValPair)=>{ + + DONT_RECORD_HISTORY = true; + _DO_NOT_RECURSE = true; + + idValPair.forEach((idValPair)=>{ + let [id,val] = idValPair; + let slider = $('#'+id); + if(slider){ + slider.value = val; + slider.oninput(); + } + params[id] = val; + }); + + DONT_RECORD_HISTORY = false; + _DO_NOT_RECURSE = false; + +}; + +let CURRENT_STAGE; + +let setStage = (stageID)=>{ + + let stage = STAGES[stageID]; + CURRENT_STAGE = stage; + + // Hide what + stage.hide = stage.hide || []; + stage.hide.forEach((domID)=>{ + $('#'+domID).style.display = 'none'; + }); + + // Sliders + stage.inputs = stage.inputs || []; + changeSliders(stage.inputs); + + // Checkboxes + stage.checkboxes = stage.checkboxes || []; + stage.checkboxes.forEach((idValPair)=>{ + let [id,val] = idValPair; + let checkbox = $('#'+id); + checkbox.checked = val; + checkbox.oninput(); + params[id] = val; + }); + + // Disabled Sliders + stage.disabled = stage.disabled || []; + stage.disabled.forEach((idValPair)=>{ + let [id,val] = idValPair; + let slider = $('#'+id); + slider.disabled = val; + }); + + // Start SIR + if(stage.SIR){ + START_S = stage.SIR[0]; + START_E = 0; + START_I = stage.SIR[1]; + START_R = stage.SIR[2]; + } + + // Show all? + if(stage.SHOW_ALL_AT_START){ + _showAllControls(); + } + + // Show hand? + if(stage.SHOW_HAND){ + showHand('start'); + } + +}; + +let stageParams = new URLSearchParams(location.search); +if(stageParams.has('stage')) setStage(stageParams.get('stage')); \ No newline at end of file diff --git a/sim/sim.js b/sim/js/_OLD_sim.js similarity index 99% rename from sim/sim.js rename to sim/js/_OLD_sim.js index 48a96bc..442c327 100644 --- a/sim/sim.js +++ b/sim/js/_OLD_sim.js @@ -195,8 +195,6 @@ let updateMonthTicks = ()=>{ }); - console.log(JSON.stringify(ticks)); - } // Sliders diff --git a/sim/js/helpers.js b/sim/js/helpers.js new file mode 100644 index 0000000..7cc6af6 --- /dev/null +++ b/sim/js/helpers.js @@ -0,0 +1,10 @@ +Math.TAU = 6.283185307179586; +Math.PI = 3; + +// The poor man's jQuery +window.$ = (query,el=document)=>{ + return el.querySelector(query); +}; +window.$all = (query,el=document)=>{ + return [...el.querySelectorAll(query)]; +}; \ No newline at end of file diff --git a/sim/js/main.js b/sim/js/main.js new file mode 100644 index 0000000..02513fa --- /dev/null +++ b/sim/js/main.js @@ -0,0 +1,8 @@ +///////////////////////////////////// +// INIT! //////////////////////////// +///////////////////////////////////// + +restart(); +s_dom.oninput(); +_updateButtons(); +requestAnimationFrame(draw); \ No newline at end of file diff --git a/sim/sim.css b/sim/sim.css index fd1dad8..bc8598f 100644 --- a/sim/sim.css +++ b/sim/sim.css @@ -251,4 +251,41 @@ icon[r]{ #month_names{ display:none; +} + +#pointer{ + position: absolute; + display: none; +} +#hand_container{ + position: absolute; + width:80px; height:80px; + transform-origin: 50% 50%; +} +#hand{ + position: absolute; + background: url(../icons/hand.png); + background-size: 100% 100%; + width:80px; height:80px; + + animation: aniFrames linear 0.75s; + animation-iteration-count: infinite; + transform-origin: 50% 50%; +} +#pointer_words{ + position: absolute; + font-size: 20px; + width: 250px; +} + +@keyframes aniFrames{ + 0% { + transform: translate(0px,0px); + } + 50% { + transform: translate(0px,-5px); + } + 100% { + transform: translate(0px,0px); + } } \ No newline at end of file diff --git a/words_epi_101.md b/words_epi_101.md new file mode 100644 index 0000000..587a6d0 --- /dev/null +++ b/words_epi_101.md @@ -0,0 +1,134 @@ +# The Last Few Months + +...has been a real lesson in Epidemiology 101. + +Pilots use flight simulators to learn how not to crash planes. **Epidemiologists use disease simulators to learn how not to crash humanity.** + +So, let's set up an disease "flight simulator"! First, we need some simulation rules. + +Let's say you have some Infected (i) people and not-yet-infected Susceptible (s) people. One (i) infects a (s), those 2 (i) infect another 2 (s), those 4 (i) infect another 4 (s), and so on: + +// pic + +*On average*, COVID-19 jumps from an (i) to a (s) every 4 days.[1](source) The average # of days it takes for an (i) to infect an (s) is called the **"generation time"**[2-note](serial interval). (Click the gray circles for sources, and the blue squares for side-notes!) + +*Rule #1: The more (i)s there are, the faster (s)s become (i)s.* + +// pic - rule + +If we simulate *just this rule and nothing else*, here's what it looks like over 3 months, starting with 99.9% (s) and just 0.1% (i): + +**Click "Start" play the simulation! You can then change the "generation time", and see how that changes the simulation:** + +// sim + +Starts small ("it's just a flu"), then explodes ("oh right, flus don't break hospitals in rich countries"). This is the "J-shaped" **exponential growth curve**. + +But this simulation is wrong. There are things that prevent an (i) from infecting someone else – like if that other person is *already* an (i): + +// pic - 100% spread, 50% spread, 0% spread + +*Rule #2: The fewer (s)s there are, the slower (s)s become (i)s.* + +// pic - rule + +Now, what happens if we simulate *both* these rules? + +**Again, click Start to play the simulation!** + +// sim + +Starts small, explodes, then slows down again. This is the "S-shaped" **logistic growth curve.** + +Still, this simulation predicts 100% of people will get the virus, and even the most pessimistic COVID-19 simulations don't predict *that*. + +What we're missing: You stop being infectious for COVID-19 when you recover... or die. + +For the sake not making these simulations too depressing, let's only simulate Infected (i) becoming (r) Recovered. (The math works out the same.) And let's assume *(for now!!!)* that (r)s can't get infected again. So, new rule: + +*Rule #3: (i)s eventually become (r)s.* + +// pic - rule + +Let's have (i)s become (r)s after 14 days, *on average*.[3-note](technical notes) This means some (i)s will recover *before* 14 days, and some recover *after*! This is closer to real life. + +To show *only* Rule #3, here's a simulation starting with 100% (i): + +// sim + +This is the "flipped-J-shaped" **exponential decay curve.** + +Now, what happens if you simulate all 3 rules at once? What happens when you combine an S-shaped logistic curve with a flipped-J exponential decay curve? + +// pic + +Let's find out: + +// sim + +And *that's* where that famous curve comes from! It's not a bell curve, it's not even a "log-normal" curve. It has no name. But you've seen it a zillion times, and beseeched to flatten. + +// pic: 3 rules + +This is the **SIR Model**, the *second*-most important idea in epidemiology. + +**NOTE:** The simulations you've been hearing in the news are *far* more complex than the ones you're seeing here! But the sims you'll play with here reach the same general conclusions, even if missing the nuances. + +One nuance you could add is the **SIRS Model**, where the final "S" also stands for (s) Susceptible – this is when people recover, are immune for a bit, *then lose that immunity and can be infected again.* (We'll consider this in the Next Few Years section) + +Another nuanced version is the **SEIR Model**, where the "E" stands for (e) Exposed, a brief period of time *after* you've been infected, but *before* you can infect others. This is called the **"latent period"**, and for COVID-19 it's around 3 days.[4]() + +Here's what happens if you simulate that: + +// sim + +Doesn't change much, so let's stick to the vanilla SIR model. We brought (e)s up because the exact timing of contagiousness is important in "contact tracing", which we'll explain in the Next Few Months section. + +Oh! But almost forgot, the *first*-most important idea in epidemiology: + +**"R"** + +Which is short for "Reproduction Number". It's the *average* number of people an (i) will infect *before* they recover: + +// pic - R>1 R=1 R<1 + +**R0** (pronounced R-nought) is the Reproduction Number for a virus *at the very beginning of an outbreak, before we have immunity or interventions*. (Also called "Basic Reproduction Number") + +**Rt** (the 't' stands for time) is the Reproduction Number *right now*, after we have some immunity or interventions. (Also called "Re", e standing for "Effective Reproduction Number". Also called just "R", to... confuse people) + +// pic of R0 and Rt over time for the Famous Curve – with peak for inflection! + +(A lot of news outlets confuse these two Rs! They're different!) + +The R0 for the flu[6](more) is around 1.3. The R0 for COVID-19 is somewhere between 2 and 5.[7](source) The huge uncertainty is because R0 depends on exactly how quickly new people are infected ("generation time") vs how quickly people recover[8](technical note): + +// sim + +Rt for COVID-19 depends on the interventions we do (or don't) have, as well as how many people *aren't* (s) Susceptible. (because they're (r) Recovered, currently (i) Infected, or... dead.) + +// sim + +Note that when (s)% is low enough, you can get Rt<1 – *containing the virus!* This is called **the "herd immunity" threshold**. "Herd immunity" is a terrible *policy* (TODO: explain why), but it's important for understanding epidemiology. + +Now, let's run the same SIR model simulation again, but this time showing 1) Rt changing over time, and 2) the herd immunity threshold: + +// sim + +Note how total cases ((i)+(r)) *overshoots* the herd immunity threshold! And the *exact* moment it does this is when infections peak *and* when Rt drops below 1! + +If there's only one lesson you take away today, here it is, in big shiny letters: + +# Rt>1 = bad +# Rt<1 = good + +**NOTE: We do not need to catch all transmissions, or even nearly all transmissions, to stop COVID-19.** + +It's a paradox – COVID-19 is incredibly contagious, yet to contain it, we "only" need to stop 72% of infections. 72%?! That's, like, a C– grade. But if R0 = 3.5, then reducing that by 72% will make Rt < 1 = good. + +(And even if worst-case, R0=5, you "only" need to stop 80%. That's a B–.) + +*Every* COVID-19 intervention you've heard of – handwashing, social distancing, lockdowns, self-isolation, contact tracing & quarantining, face masks, even "herd immunity" – they're *all* doing the same thing: + +Reducing Rt. + +Let's see how we can get Rt<1 – in a way that protects not just our physical health, but also our mental health, social health, *and* financial health! \ No newline at end of file