From 7be32b6e81ca07c3fa6547e1142f4fdbc8e2934b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Pa=CC=81rraga?= Date: Thu, 13 Aug 2020 00:18:27 +0200 Subject: [PATCH 1/7] Added contact tracing app simulation Added R0 calculation (based on S-R-I model) --- Processing/Main/A_ConfigModel.pde | 21 ++--- Processing/Main/A_ConfigView.pde | 6 +- Processing/Main/BM_CityModel.pde | 34 ++++++- Processing/Main/B_ChoiceModel.pde | 4 +- Processing/Main/B_Person.pde | 22 +++++ Processing/Main/CV_EpiView.pde | 5 ++ Processing/Main/C_Host.pde | 14 +++ Processing/Main/C_PathogenEffect.pde | 8 ++ Processing/Main/RV_ResultView.pde | 130 +++++++++++++++++++++++++++ Processing/Main/R_Result.pde | 37 ++++++++ Processing/Main/Z_Enum.pde | 6 ++ 11 files changed, 273 insertions(+), 14 deletions(-) diff --git a/Processing/Main/A_ConfigModel.pde b/Processing/Main/A_ConfigModel.pde index bdee82a..d4fd84b 100644 --- a/Processing/Main/A_ConfigModel.pde +++ b/Processing/Main/A_ConfigModel.pde @@ -66,7 +66,7 @@ private void configModel() { behavior.setRecoverAnomoly(new Rate(0.40)); // Chance that Person will disobey quarantine order and proceed to regular activity - behavior.setObeyanceAnomoly(new Rate(0.05)); + behavior.setObeyanceAnomoly(new Rate(0.00)); // Set whether or not a quarantine is in effect behavior.setQuarantine(Quarantine.NONE); @@ -104,12 +104,12 @@ private void configModel() { int CENTER_X = 500; int CENTER_Y = 500; int BASE_RANGE = 100; - epidemic.randomPlaces(N*25, "Public Space", LandUse.PUBLIC, CENTER_X, CENTER_Y, 5*BASE_RANGE, 5*BASE_RANGE, 500, 2000); - epidemic.randomPlaces(N*250, "Dwelling Unit", LandUse.DWELLING, CENTER_X, CENTER_Y, 5*BASE_RANGE, 5*BASE_RANGE, 50, 200); - epidemic.randomPlaces(N*20, "Office Space", LandUse.OFFICE, CENTER_X, CENTER_Y, 4*BASE_RANGE, 4*BASE_RANGE, 500, 1000); - epidemic.randomPlaces(N*4, "School", LandUse.SCHOOL, CENTER_X, CENTER_Y, 5*BASE_RANGE, 5*BASE_RANGE, 500, 2000); - epidemic.randomPlaces(N*25, "Retail Shopping", LandUse.RETAIL, CENTER_X, CENTER_Y, 4*BASE_RANGE, 4*BASE_RANGE, 50, 1000); - epidemic.randomPlaces(N*1, "Hospital", LandUse.HOSPITAL, CENTER_X, CENTER_Y, 1*BASE_RANGE, 1*BASE_RANGE, 2000, 2000); + epidemic.randomPlaces(N*25*2, "Public Space", LandUse.PUBLIC, CENTER_X, CENTER_Y, 5*BASE_RANGE, 5*BASE_RANGE, 500, 2000); + epidemic.randomPlaces(N*250*2, "Dwelling Unit", LandUse.DWELLING, CENTER_X, CENTER_Y, 5*BASE_RANGE, 5*BASE_RANGE, 50, 200); + epidemic.randomPlaces(N*20*2, "Office Space", LandUse.OFFICE, CENTER_X, CENTER_Y, 4*BASE_RANGE, 4*BASE_RANGE, 500, 1000); + epidemic.randomPlaces(N*4*2, "School", LandUse.SCHOOL, CENTER_X, CENTER_Y, 5*BASE_RANGE, 5*BASE_RANGE, 500, 2000); + epidemic.randomPlaces(N*25*2, "Retail Shopping", LandUse.RETAIL, CENTER_X, CENTER_Y, 4*BASE_RANGE, 4*BASE_RANGE, 50, 1000); + epidemic.randomPlaces(N*1*2, "Hospital", LandUse.HOSPITAL, CENTER_X, CENTER_Y, 1*BASE_RANGE, 1*BASE_RANGE, 2000, 2000); // Resilience*: Impact of Demographic on Pathogen Intensities (1.0 == no impact; < 1 == less resilient; > 1 == more resilient) Rate childResilience = new Rate(1.5); @@ -123,14 +123,15 @@ private void configModel() { int maxAge = 85; int minDwellingSize = 1; int maxDwellingSize = 4; - + + int contactTracingAppInstallationPercentage = 20; // Add people to Model assigned to one random primary location (home) and one random secondary location (job or school) - epidemic.populate(minAge, maxAge, adultAge, seniorAge, childResilience, adultResilience, seniorResilience, minDwellingSize, maxDwellingSize); + epidemic.populate(minAge, maxAge, adultAge, seniorAge, contactTracingAppInstallationPercentage, childResilience, adultResilience, seniorResilience, minDwellingSize, maxDwellingSize); // Number of Ventilator ICU Beds Per Capita: // In actuality, this rate is only about 0.04% in the United States according to NYTimes!!! // https://www.nytimes.com/2020/03/18/business/coronavirus-ventilator-shortage.html - Rate bedsPerCapita = new Rate(0.004); + Rate bedsPerCapita = new Rate(0.008); epidemic.setBedsPerCapita(bedsPerCapita); // Configure Covid Pathogen diff --git a/Processing/Main/A_ConfigView.pde b/Processing/Main/A_ConfigView.pde index 3e273f1..3949315 100644 --- a/Processing/Main/A_ConfigView.pde +++ b/Processing/Main/A_ConfigView.pde @@ -53,7 +53,7 @@ public void configView(CityModel model) { String info = "Epidemic Simulation" + "\n" + "EDGEof Planetary Insight Center" + "\n" + - "by Ira Winder, F. Calalang, D. Goldman" + "\n\n" + + "by Ira Winder, F. Calalang, D. Goldman and Antonio Parraga" + "\n\n" + "This is a simple simulation of a viral outbreak using " + "an agent-based population activity model. " + "\n\n" + @@ -96,6 +96,7 @@ public void configView(CityModel model) { // Compartment Names viz.setName(Compartment.SUSCEPTIBLE, "Susceptible"); + viz.setName(Compartment.EXPOSURE_NOTIFIED, "Exposure notified"); viz.setName(Compartment.INCUBATING, "Incubating"); viz.setName(Compartment.INFECTIOUS, "Infectious"); viz.setName(Compartment.RECOVERED, "Recovered"); @@ -104,12 +105,13 @@ public void configView(CityModel model) { // Compartment Colors viz.setColor(Compartment.SUSCEPTIBLE, color(250, 250, 250, 255)); // White; + viz.setColor(Compartment.EXPOSURE_NOTIFIED, color( 255, 255, 0, 255)); // Yellow viz.setColor(Compartment.INCUBATING, color(255, 150, 0, 255)); // Orange viz.setColor(Compartment.INFECTIOUS, color(255, 0, 0, 255)); // Dark Red viz.setColor(Compartment.RECOVERED, color(100, 100, 100, 255)); // Black viz.setColor(Compartment.DEAD_TREATED, color( 0, 50, 255, 255)); // Teal viz.setColor(Compartment.DEAD_UNTREATED, color( 0, 255, 0, 255)); // Green - + // Pathogen Names viz.setName(PathogenType.CORONAVIRUS, "Coronavirus"); viz.setName(PathogenType.RHINOVIRUS, "Rhinovirus"); diff --git a/Processing/Main/BM_CityModel.pde b/Processing/Main/BM_CityModel.pde index 7fabe26..a32f850 100644 --- a/Processing/Main/BM_CityModel.pde +++ b/Processing/Main/BM_CityModel.pde @@ -352,7 +352,7 @@ public class CityModel extends EpiModel { * @param minDwellingSize smallest household size of a dwelling unit * @param maxDwellingSize largest household size of a dwelling unit */ - public void populate(int minAge, int maxAge, int adultAge, int seniorAge, Rate childResilience, Rate adultResilience, Rate seniorResilience, int minDwellingSize, int maxDwellingSize) { + public void populate(int minAge, int maxAge, int adultAge, int seniorAge, int contactTracingAppInstallationPercentage, Rate childResilience, Rate adultResilience, Rate seniorResilience, int minDwellingSize, int maxDwellingSize) { for(Place l : this.place.get(LandUse.DWELLING)) { int numTenants = (int) random(minDwellingSize, maxDwellingSize+1); @@ -366,6 +366,13 @@ public class CityModel extends EpiModel { int age = (int) random(minAge, maxAge); person.setAge(age); + if((int) random(0, 100) < contactTracingAppInstallationPercentage) { + person.setContactTracingApp(true); + } + else { + person.setContactTracingApp(false); + } + // Set Demographic person.setDemographic(adultAge, seniorAge); @@ -517,6 +524,11 @@ public class CityModel extends EpiModel { // Add Person encounter to results if(h instanceof Person && h2 instanceof Person) { stats.tallyEncounter((Person)h, (Person)h2); + + if(((Person)h).hasContactTracingApp() == true && ((Person)h2).hasContactTracingApp()) { + stats.registerContactTracingExposure((Person)h, (Person)h2); + } + } // 1. Transmit pathogen from Host to Host if one is infectious @@ -602,6 +614,11 @@ public class CityModel extends EpiModel { } h.update(current, treated); + for(Pathogen pt : this.getPathogens()) { + if(h.getStatus(pt).infectious()) { + stats.notifyExposureToPersons((Person) h); + } + } } } @@ -624,6 +641,21 @@ public class CityModel extends EpiModel { } } + // Also quarantine people living in the same house as someone notified + for(Demographic d1 : Demographic.values()) { + for(Person p1 : this.person.get(d1)) { + for(Demographic d2 : Demographic.values()) { + for(Person p2 : this.person.get(d2)) { + if(p1.getPrimaryPlace() == p2.getPrimaryPlace() && + (p1.hasBeenExposedToInfected() || p2.hasBeenExposedToInfected())) { + p1.notifyExposure(); + p2.notifyExposure(); + } + } + } + } + } + // Add Number of Hospital Beds to Results Table stats.setHospitalBeds(this.hospitalBeds); diff --git a/Processing/Main/B_ChoiceModel.pde b/Processing/Main/B_ChoiceModel.pde index 4d8cce7..4a0f970 100644 --- a/Processing/Main/B_ChoiceModel.pde +++ b/Processing/Main/B_ChoiceModel.pde @@ -386,7 +386,7 @@ public class ChoiceModel { p.moveToHospital(); // Are you Obeying a Strict Quarantine? - } else if(quarantine == Quarantine.STRICT && !disobeyQuarantine) { + } else if((quarantine == Quarantine.STRICT || (p.hasBeenExposedToInfected() && p.isInfected())) && !disobeyQuarantine) { p.moveToPrimary(); // Carry one! @@ -394,6 +394,8 @@ public class ChoiceModel { // Current Place Place currentPlace = p.getPlace(); + + // Dominant Place PlaceCategory phaseDomain = this.getPhaseDomain(currentPhase); Place dominantPlace = currentPlace; diff --git a/Processing/Main/B_Person.pde b/Processing/Main/B_Person.pde index d3e7df0..e1b1f5c 100644 --- a/Processing/Main/B_Person.pde +++ b/Processing/Main/B_Person.pde @@ -24,6 +24,10 @@ public class Person extends Host { // Demographic of Person private Demographic demographic; + private Boolean contactTracingApp; + + private Boolean exposedToInfected; + // Primary Environment (e.g. home, dwelling, etc) private Place primaryPlace; @@ -40,6 +44,8 @@ public class Person extends Host { super(); this.age = 18; this.setDemographic(18, 65); + this.contactTracingApp = false; //by default + this.exposedToInfected = false; //by default this.primaryPlace = new Place(); this.secondaryPlace = new Place(); this.closestHospital = new Place(); @@ -66,6 +72,14 @@ public class Person extends Host { return this.age; } + public void setContactTracingApp(Boolean contactTracingApp) { + this.contactTracingApp = contactTracingApp; + } + + public Boolean hasContactTracingApp() { + return this.contactTracingApp; + } + /** * Set the Person's Demographic * @@ -165,6 +179,14 @@ public class Person extends Host { this.move(destination); } + public void notifyExposure() { + this.exposedToInfected = true; + } + + public Boolean hasBeenExposedToInfected() { + return this.exposedToInfected; + } + /** * Move Person to Secondary Place */ diff --git a/Processing/Main/CV_EpiView.pde b/Processing/Main/CV_EpiView.pde index c6cbc0e..1e92887 100644 --- a/Processing/Main/CV_EpiView.pde +++ b/Processing/Main/CV_EpiView.pde @@ -246,7 +246,12 @@ public class EpiView extends ViewModel { } int w = (int) this.getValue(ViewParameter.HOST_DIAMETER); Compartment c = h.getStatus(pathogen).getCompartment(); + color viewFill = this.getColor(c); + if(h instanceof Person && ((Person) h).hasBeenExposedToInfected() && h.isInfected()) { + viewFill = this.getColor(Compartment.EXPOSURE_NOTIFIED); + } + color viewStroke = this.getColor(ViewParameter.HOST_STROKE); int alpha = (int) this.getValue(ViewParameter.HOST_ALPHA); int strokeWeight = (int) this.getValue(ViewParameter.HOST_WEIGHT); diff --git a/Processing/Main/C_Host.pde b/Processing/Main/C_Host.pde index d96436c..1837f40 100644 --- a/Processing/Main/C_Host.pde +++ b/Processing/Main/C_Host.pde @@ -170,6 +170,20 @@ public class Host extends Element { return true; } + /** + * Check if Host is Infected + * + * @return true if infected + */ + public boolean isInfected() { + for(HashMap.Entry entry : this.status.entrySet()) { + if(entry.getValue().isInfected()) { + return true; + } + } + return false; + } + @Override public String toString() { return "Host UID: " + this.getUID() + "; Name: " + this.getName(); diff --git a/Processing/Main/C_PathogenEffect.pde b/Processing/Main/C_PathogenEffect.pde index 95fc240..eee610e 100644 --- a/Processing/Main/C_PathogenEffect.pde +++ b/Processing/Main/C_PathogenEffect.pde @@ -298,6 +298,14 @@ public class PathogenEffect { } } + public boolean isInfected() { + if(this.compartment == Compartment.INCUBATING || this.compartment == Compartment.INFECTIOUS) { + return true; + } else { + return false; + } + } + /** * Check if currently hospitalized * diff --git a/Processing/Main/RV_ResultView.pde b/Processing/Main/RV_ResultView.pde index cb76b43..6b74893 100644 --- a/Processing/Main/RV_ResultView.pde +++ b/Processing/Main/RV_ResultView.pde @@ -89,6 +89,10 @@ public class ResultView extends CityView { int deaths = 0; int deathsSurvivable = 0; int recovered = 0; + double r0 = 0; + double avgR0 = 0; + double medianR0 = 0; + double maxR0 = 0; for(Demographic d : Demographic.values()) { hospitalized += r.getHospitalizedTally(d); infected += r.getCompartmentTally(d, p, Compartment.INCUBATING); @@ -102,6 +106,36 @@ public class ResultView extends CityView { deathsSurvivable += r.getCompartmentTally(d, p, Compartment.DEAD_UNTREATED); } + if(outcome.getTimes().size() > 24) { + r0 = this.calculateR0(outcome, outcome.getTimes().size()); + + double[] historicalR0 = new double[outcome.getTimes().size() - 1]; + for(int i = 1; i < outcome.getTimes().size(); i++) { + historicalR0[i - 1] = this.calculateR0(outcome, i); + avgR0 += historicalR0[i - 1]; + if(historicalR0[i - 1] > maxR0) { + maxR0 = historicalR0[i - 1]; + } + } + maxR0 = (double) Math.round(maxR0 * 100) / 100; //round + + avgR0 = avgR0 / outcome.getTimes().size(); + avgR0 = (double) Math.round(avgR0 * 100) / 100; //round + + this.bubbleSort(historicalR0); + if (historicalR0.length % 2 == 0) { + medianR0 = ((double)historicalR0[historicalR0.length/2] + (double)historicalR0[historicalR0.length/2 - 1])/2; + } + else { + medianR0 = (double) historicalR0[historicalR0.length/2]; + } + + medianR0 = (double) Math.round(medianR0 * 100) / 100; //round + } + + //String clock = lastTime.toClock(); + //String dayOfWeek = lastTime.toDayOfWeek(); + Rate deathRate = new Rate((double) deaths / (recovered + deaths)); String otherStats = ""; @@ -112,9 +146,11 @@ public class ResultView extends CityView { otherStats += "Currently Hospitalized: " + hospitalized + "\n\n"; otherStats += "Pathogen: " + p.getName() + "\n"; otherStats += "Total Infected: " + infected + "\n"; + otherStats += "Total Notified (exposured): " + r.getExposureNotified() + "\n"; otherStats += "Total Recovered: " + recovered + "\n"; otherStats += "Total Deaths: " + deaths + "\n"; otherStats += "Death Rate [Dead]/[Rocovered+Dead]: " + deathRate + "\n\n"; + otherStats += "R0: " + r0 + " (avg: " + avgR0 + ", median: " + medianR0 + ", peak: " + maxR0 + ")\n\n"; otherStats += "Survivable* Deaths: " + deathsSurvivable + "\n\n"; otherStats += "*Survivable deaths are deaths that could have been\n prevented if hospitals had not been overburdened."; color textFill = this.getColor(ViewParameter.TEXT_FILL); @@ -126,6 +162,82 @@ public class ResultView extends CityView { } } + /** + * Calculates the R0 of a given moment based on the S-R-I model + * + */ + private double calculateR0(ResultSeries outcome, int momentTick) { + + double returnValue = 0; + + int previousS = 0; + int previousI = 0; + int previousR = 0; + int currentS = 0; + int currentI = 0; + int currentR = 0; + int dS = 0; + int dI = 0; + int dR = 0; + + Time lastTime = outcome.getTimes().get(momentTick - 1); + + //String clock = lastTime.toClock(); + //String dayOfWeek = lastTime.toDayOfWeek(); + + Result r = outcome.getResult(lastTime); + int population = r.getPeopleTally(); + Pathogen p = getCurrentPathogen(); + if(momentTick > 24) { + for(int i = 0; i < momentTick; i++) { + + Time time = outcome.getTimes().get(i); + Result result = outcome.getResult(time); + + int dInfected = 0; + int dRemoval = 0; + for(Demographic d : Demographic.values()) { + dInfected += result.getCompartmentTally(d, p, Compartment.INCUBATING); + dInfected += result.getCompartmentTally(d, p, Compartment.INFECTIOUS); + dRemoval += result.getCompartmentTally(d, p, Compartment.RECOVERED); + dRemoval += result.getCompartmentTally(d, p, Compartment.DEAD_TREATED); + dRemoval += result.getCompartmentTally(d, p, Compartment.DEAD_UNTREATED); + } + + if(i == momentTick - 24) { + previousS = population - dInfected - dRemoval; + previousI = dInfected; + previousR = dRemoval; + } + else if(i == momentTick - 1) { + currentS = population - dInfected - dRemoval; + currentI = dInfected; + currentR = dRemoval; + } + + } + + dS = currentS - previousS; + dI = currentI - previousI; + dR = currentR - previousR; + + if(dI + dS == 0) { + dI++; + } + double vValue = -1 * dR / (dI + dS); + double iValue = dR / vValue; + double betaValue = -1 * dS / iValue; + + + returnValue = betaValue / vValue; + + returnValue = (double) Math.round(returnValue * 100) / 100; //round + + } + + return returnValue; + } + /** * Render Graph Axes and Labels * @@ -163,6 +275,24 @@ public class ResultView extends CityView { axes.endDraw(); } + public void bubbleSort(double[] array) { + boolean swapped = true; + int j = 0; + double tmp; + while (swapped) { + swapped = false; + j++; + for (int i = 0; i < array.length - j; i++) { + if (array[i] > array[i + 1]) { + tmp = array[i]; + array[i] = array[i + 1]; + array[i + 1] = tmp; + swapped = true; + } + } + } + } + /** * Render Graph */ diff --git a/Processing/Main/R_Result.pde b/Processing/Main/R_Result.pde index b9630d2..e85e302 100644 --- a/Processing/Main/R_Result.pde +++ b/Processing/Main/R_Result.pde @@ -15,6 +15,9 @@ public class Result { // Number of HospitalBeds private int numHospitalBeds; + // Number of exposure notification + private int exposureNotificationsCount; + // Tally of compartment statuses itemized by pathogen and demographic private HashMap>> compartmentTally; @@ -30,6 +33,9 @@ public class Result { // Average Trips Per Demographic private HashMap tripTally; + // Encounters of people having the tracing app installed + private HashMap> contactTracingExposures; + // Quarantine Status private Quarantine quarantine; @@ -42,12 +48,14 @@ public class Result { this.step = new Time(); this.peopleTally = 0; + this.exposureNotificationsCount = 0; this.compartmentTally = new HashMap>>(); this.symptomTally = new HashMap>>(); this.hospitalizedTally = new HashMap(); this.encounterTally = new HashMap(); this.tripTally = new HashMap(); + this.contactTracingExposures = new HashMap>(); // Initialize tallies with values of zero for(Demographic d : Demographic.values()) { @@ -135,6 +143,10 @@ public class Result { int hTally = this.hospitalizedTally.get(d); this.hospitalizedTally.put(d, hTally + 1); } + + if(person.hasBeenExposedToInfected()) { + this.exposureNotificationsCount ++; + } for(HashMap.Entry entry : person.getStatusMap().entrySet()) { Pathogen pathogen = entry.getKey(); @@ -177,6 +189,27 @@ public class Result { tripTally.put(d2, p2Tally + 1); } + public void registerContactTracingExposure(Person p1, Person p2) { + if(!contactTracingExposures.keySet().contains(p1.getUID())) { + contactTracingExposures.put(p1.getUID(), new ArrayList()); + } + contactTracingExposures.get(p1.getUID()).add(p2); + if(!contactTracingExposures.keySet().contains(p2.getUID())) { + contactTracingExposures.put(p2.getUID(), new ArrayList()); + } + contactTracingExposures.get(p2.getUID()).add(p1); + } + + public void notifyExposureToPersons(Person p1) { + if(contactTracingExposures.keySet().contains(p1.getUID())) { + for(Person p2 : this.contactTracingExposures.get(p1.getUID())) { + if(!p2.hasBeenExposedToInfected()) { + p2.notifyExposure(); + } + } + } + } + @Override public String toString() { return "Results at " + this.getTime(); @@ -213,6 +246,10 @@ public class Result { return this.hospitalizedTally.get(d); } + public int getExposureNotified() { + return this.exposureNotificationsCount; + } + /** * Get tally for specified encounters * diff --git a/Processing/Main/Z_Enum.pde b/Processing/Main/Z_Enum.pde index c3f73cd..29ebf54 100644 --- a/Processing/Main/Z_Enum.pde +++ b/Processing/Main/Z_Enum.pde @@ -22,12 +22,18 @@ public enum Demographic { public enum Compartment { SUSCEPTIBLE, RECOVERED, + EXPOSURE_NOTIFIED, INCUBATING, INFECTIOUS, DEAD_TREATED, DEAD_UNTREATED } +public enum ExposureNotified { + NONE, + NOTIFIED +} + public enum Symptom { FEVER, COUGH, From e7c67dbc01c70fb064563289ba75175fd086a255 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20P=C3=A1rraga=20Navarro?= Date: Thu, 13 Aug 2020 00:20:19 +0200 Subject: [PATCH 2/7] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index f2ca63b..c456f73 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # EpiSim This is a simple simulation of a viral outbreak using an agent-based population activity model. +Added: Simulate contact tracing apps effects on pandemy (adoption % is configurable, being by default 20%) +Added: R0 calculation in real time based on S-R-I model + ![Epidemic Simulation by Ira Winder](screenshots/screenshot.png?raw=true "Epidemic Simulation by Ira Winder") ## How to open and run From 52b6811df08bb719e01dff30f7f5861671f7575c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20P=C3=A1rraga=20Navarro?= Date: Thu, 13 Aug 2020 00:20:37 +0200 Subject: [PATCH 3/7] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c456f73..1952548 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # EpiSim This is a simple simulation of a viral outbreak using an agent-based population activity model. -Added: Simulate contact tracing apps effects on pandemy (adoption % is configurable, being by default 20%) -Added: R0 calculation in real time based on S-R-I model +* Added: Simulate contact tracing apps effects on pandemy (adoption % is configurable, being by default 20%) +* Added: R0 calculation in real time based on S-R-I model ![Epidemic Simulation by Ira Winder](screenshots/screenshot.png?raw=true "Epidemic Simulation by Ira Winder") From c0ada8f628f2834307137cf6e5f9a66d08442c39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20P=C3=A1rraga=20Navarro?= Date: Thu, 13 Aug 2020 00:21:04 +0200 Subject: [PATCH 4/7] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1952548..6d2bde1 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # EpiSim This is a simple simulation of a viral outbreak using an agent-based population activity model. -* Added: Simulate contact tracing apps effects on pandemy (adoption % is configurable, being by default 20%) -* Added: R0 calculation in real time based on S-R-I model +* New: Simulate contact tracing apps effects on pandemy (adoption % is configurable, being by default 20%) +* New: R0 calculation in real time based on S-R-I model ![Epidemic Simulation by Ira Winder](screenshots/screenshot.png?raw=true "Epidemic Simulation by Ira Winder") From 7ca9456a731306881f66e01b2573e01b6d91d761 Mon Sep 17 00:00:00 2001 From: Antonio Parraga Date: Thu, 13 Aug 2020 16:22:38 +0200 Subject: [PATCH 5/7] Small fixes --- Processing/Main/A_ConfigModel.pde | 7 ++++--- Processing/Main/BM_CityModel.pde | 6 +++--- Processing/Main/RV_ResultView.pde | 12 ++++++------ 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Processing/Main/A_ConfigModel.pde b/Processing/Main/A_ConfigModel.pde index d4fd84b..1c6fd98 100644 --- a/Processing/Main/A_ConfigModel.pde +++ b/Processing/Main/A_ConfigModel.pde @@ -124,14 +124,15 @@ private void configModel() { int minDwellingSize = 1; int maxDwellingSize = 4; - int contactTracingAppInstallationPercentage = 20; + Rate contactTracingAppAdoptionRate = new Rate(0.2); + // Add people to Model assigned to one random primary location (home) and one random secondary location (job or school) - epidemic.populate(minAge, maxAge, adultAge, seniorAge, contactTracingAppInstallationPercentage, childResilience, adultResilience, seniorResilience, minDwellingSize, maxDwellingSize); + epidemic.populate(minAge, maxAge, adultAge, seniorAge, contactTracingAppAdoptionRate, childResilience, adultResilience, seniorResilience, minDwellingSize, maxDwellingSize); // Number of Ventilator ICU Beds Per Capita: // In actuality, this rate is only about 0.04% in the United States according to NYTimes!!! // https://www.nytimes.com/2020/03/18/business/coronavirus-ventilator-shortage.html - Rate bedsPerCapita = new Rate(0.008); + Rate bedsPerCapita = new Rate(0.004); epidemic.setBedsPerCapita(bedsPerCapita); // Configure Covid Pathogen diff --git a/Processing/Main/BM_CityModel.pde b/Processing/Main/BM_CityModel.pde index a32f850..2f75c64 100644 --- a/Processing/Main/BM_CityModel.pde +++ b/Processing/Main/BM_CityModel.pde @@ -352,7 +352,7 @@ public class CityModel extends EpiModel { * @param minDwellingSize smallest household size of a dwelling unit * @param maxDwellingSize largest household size of a dwelling unit */ - public void populate(int minAge, int maxAge, int adultAge, int seniorAge, int contactTracingAppInstallationPercentage, Rate childResilience, Rate adultResilience, Rate seniorResilience, int minDwellingSize, int maxDwellingSize) { + public void populate(int minAge, int maxAge, int adultAge, int seniorAge, Rate contactTracingAppAdoptionRate, Rate childResilience, Rate adultResilience, Rate seniorResilience, int minDwellingSize, int maxDwellingSize) { for(Place l : this.place.get(LandUse.DWELLING)) { int numTenants = (int) random(minDwellingSize, maxDwellingSize+1); @@ -366,7 +366,7 @@ public class CityModel extends EpiModel { int age = (int) random(minAge, maxAge); person.setAge(age); - if((int) random(0, 100) < contactTracingAppInstallationPercentage) { + if(random(0, 100) < 100 * contactTracingAppAdoptionRate.rate) { person.setContactTracingApp(true); } else { @@ -615,7 +615,7 @@ public class CityModel extends EpiModel { h.update(current, treated); for(Pathogen pt : this.getPathogens()) { - if(h.getStatus(pt).infectious()) { + if(pt.getType() == PathogenType.CORONAVIRUS && h.getStatus(pt).infectious()) { stats.notifyExposureToPersons((Person) h); } } diff --git a/Processing/Main/RV_ResultView.pde b/Processing/Main/RV_ResultView.pde index 6b74893..04324b0 100644 --- a/Processing/Main/RV_ResultView.pde +++ b/Processing/Main/RV_ResultView.pde @@ -163,12 +163,12 @@ public class ResultView extends CityView { } /** - * Calculates the R0 of a given moment based on the S-R-I model + * Calculates the R0 of a given moment based on the S-I-R model * */ private double calculateR0(ResultSeries outcome, int momentTick) { - double returnValue = 0; + double r0 = 0; int previousS = 0; int previousI = 0; @@ -224,18 +224,18 @@ public class ResultView extends CityView { if(dI + dS == 0) { dI++; } + double vValue = -1 * dR / (dI + dS); double iValue = dR / vValue; double betaValue = -1 * dS / iValue; + r0 = betaValue / vValue; - returnValue = betaValue / vValue; - - returnValue = (double) Math.round(returnValue * 100) / 100; //round + r0 = (double) Math.round(r0 * 100) / 100; //round } - return returnValue; + return r0; } /** From 872a1baa761a4a885a5be51e640df4898ea234dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20P=C3=A1rraga=20Navarro?= Date: Thu, 13 Aug 2020 16:26:06 +0200 Subject: [PATCH 6/7] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6d2bde1..3e2e9c2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This is a simple simulation of a viral outbreak using an agent-based population activity model. * New: Simulate contact tracing apps effects on pandemy (adoption % is configurable, being by default 20%) -* New: R0 calculation in real time based on S-R-I model +* New: R0 calculation in real time based on S-I-R model ![Epidemic Simulation by Ira Winder](screenshots/screenshot.png?raw=true "Epidemic Simulation by Ira Winder") From 0cbaf63f28b9124192bdd2f9bc66e4f67f22c116 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonio=20Pa=CC=81rraga?= Date: Thu, 13 Aug 2020 16:26:41 +0200 Subject: [PATCH 7/7] Add obeyance to 5% --- Processing/Main/A_ConfigModel.pde | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Processing/Main/A_ConfigModel.pde b/Processing/Main/A_ConfigModel.pde index 1c6fd98..90f4927 100644 --- a/Processing/Main/A_ConfigModel.pde +++ b/Processing/Main/A_ConfigModel.pde @@ -66,7 +66,7 @@ private void configModel() { behavior.setRecoverAnomoly(new Rate(0.40)); // Chance that Person will disobey quarantine order and proceed to regular activity - behavior.setObeyanceAnomoly(new Rate(0.00)); + behavior.setObeyanceAnomoly(new Rate(0.05)); // Set whether or not a quarantine is in effect behavior.setQuarantine(Quarantine.NONE);