/** ** SickStudentsModel.java ** ** Copyright 2011 by Joseph Harrison, Andrew Crooks, Mark Coletti, Cristina Metgher ** George Mason University. ** ** Licensed under the Academic Free License version 3.0 ** ** See the file "LICENSE" for more information ** ** $Id$ **/ package sim.app.geo.sickStudents; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import sim.engine.SimState; import sim.engine.Steppable; import sim.field.geo.GeomVectorField; import sim.io.geo.ShapeFileImporter; import sim.util.Bag; import sim.util.Interval; import sim.util.geo.MasonGeometry; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.Point; @SuppressWarnings("serial") public class SickStudentsModel extends SimState { public int numHouseholds = 10000; public int getNumHouseholds() { return numHouseholds; } public void setNumHouseholds(int val) { numHouseholds = val; reInitHouseholds = true; } public boolean reInitHouseholds = true; public boolean getReInitHouseholds() { return reInitHouseholds; } public void setReInitHouseholds(boolean val) { reInitHouseholds = val; } public double childrenPerHouseholdAve = 1.45; public double getChildrenPerHouseholdAve() { return childrenPerHouseholdAve; } public void setChildrenPerHouseholdAve(double val) { childrenPerHouseholdAve = val; } public double childrenPerHouseholdStdev = 0.5; public double getChildrenPerHouseholdStdev() { return childrenPerHouseholdStdev; } public void setChildrenPerHouseholdStdev(double val) { childrenPerHouseholdStdev = val; } public int numInitialInfections = 10; public int getNumInitialInfections() { return numInitialInfections; } public void setNumInitialInfections(int val) { numInitialInfections = val; } public int diseaseDuration = 10; // days public int getDiseaseDuration() { return diseaseDuration; } public void setDiseaseDuration(int val) { diseaseDuration = val; } public int incubationPeriod = 2; // days public int getIncubationPeriod() { return incubationPeriod; } public void setIncubationPeriod(int val) { incubationPeriod = val; } public double diseaseTransmissionProb = 0.3; public double getDiseaseTransmissionProb() { return diseaseTransmissionProb; } public void setDiseaseTransmissionProb(double val) { diseaseTransmissionProb = val; } public Object domDiseaseTransmissionProb() { return new Interval(0.0, 1.0); } public boolean closeSchoolUponOutbreak = false; public boolean getCloseSchoolUponOutbreak() { return closeSchoolUponOutbreak; } public void setCloseSchoolUponOutbreak(boolean val) { closeSchoolUponOutbreak = val; } public boolean closeAllSchoolsUponOutbreak = false; public boolean getCloseAllSchoolsUponOutbreak() { return closeAllSchoolsUponOutbreak; } public void setCloseAllSchoolsUponOutbreak(boolean val) { closeAllSchoolsUponOutbreak = val; } /** * The proportion of students at a school who must get sick before all * schools get closed. */ public double outbreakThreshold = 0.20; public double getOutbreakThreshold() { return outbreakThreshold; } public void setOutbreakThreshold(double val) { outbreakThreshold = val; } public Object domOutbreakThreshold() { return new Interval(0.0, 1.0); } /** * The following are to allow the tracking of SIR counts from the main simulation * panel. The movie-making tool works better there than through the custom chart. */ int sCount = 0, iCount = 0, rCount = 0; /** * Member variables. */ public int width = 800; public int height = 800; // where all the county geometry lives public GeomVectorField elementarySchoolZones = new GeomVectorField(width, height); public GeomVectorField middleSchoolZones = new GeomVectorField(width, height); public GeomVectorField highSchoolZones = new GeomVectorField(width, height); public GeomVectorField elementarySchools = new GeomVectorField(width, height); public GeomVectorField middleSchools = new GeomVectorField(width, height); public GeomVectorField highSchools = new GeomVectorField(width, height); public GeomVectorField householdsField = new GeomVectorField(width, height); ArrayList<Household> households = new ArrayList<Household>(); ArrayList<School> schools = new ArrayList<School>(); ArrayList<Student> students = new ArrayList<Student>(); HashMap<Integer, School> schoolMap = new HashMap<Integer, School>(); HashMap<MasonGeometry, School> catchmentSchoolMap = new HashMap<MasonGeometry, School>(); /************************************************************************** * Constructors start here * *************************************************************************/ public SickStudentsModel(long seed) { super(seed); readData(); createSchools(); } private void readData() { try { // read the data ShapeFileImporter.read(SickStudentsModel.class.getResource("data/ES_ATTENDANCE_AREAS.shp"), elementarySchoolZones); ShapeFileImporter.read(SickStudentsModel.class.getResource("data/MS_ATTENDANCE_AREAS.shp"), middleSchoolZones); ShapeFileImporter.read(SickStudentsModel.class.getResource("data/HS_ATTENDANCE_AREAS.shp"), highSchoolZones); ShapeFileImporter.read(SickStudentsModel.class.getResource("data/ElementarySchools.shp"), elementarySchools); ShapeFileImporter.read(SickStudentsModel.class.getResource("data/MiddleSchools.shp"), middleSchools); ShapeFileImporter.read(SickStudentsModel.class.getResource("data/HighSchools.shp"), highSchools); // Make all the bounding rectangles match one another Envelope MBR = elementarySchoolZones.getMBR(); MBR.expandToInclude(middleSchoolZones.getMBR()); MBR.expandToInclude(highSchoolZones.getMBR()); MBR.expandToInclude(elementarySchools.getMBR()); MBR.expandToInclude(middleSchools.getMBR()); MBR.expandToInclude(highSchools.getMBR()); elementarySchoolZones.setMBR(MBR); middleSchoolZones.setMBR(MBR); highSchoolZones.setMBR(MBR); elementarySchools.setMBR(MBR); middleSchools.setMBR(MBR); highSchools.setMBR(MBR); } catch (Exception ex) { System.out.println("Error opening shapefile!" + ex); System.exit(-1); } } private void createSchoolsFromData(GeomVectorField schoolField) { Bag geoms = schoolField.getGeometries(); for (int i = 0; i < geoms.numObjs; i++) { MasonGeometry mg = (MasonGeometry) geoms.get(i); Integer num = mg.getIntegerAttribute("SCHOOL_NUM"); String name = mg.getStringAttribute("SCHOOL_NAM"); String type = mg.getStringAttribute("SCHOOL_TYP"); if (num != null) { School s = new School(this, name, type); schoolMap.put(num, s); schools.add(s); } } } private void createSchools() { schools.clear(); createSchoolsFromData(elementarySchools); createSchoolsFromData(middleSchools); createSchoolsFromData(highSchools); //removeUnusedSchools(); // no longer necessary since removing the unused schools from the files } private void countCatchments(GeomVectorField catchments) { Bag geoms = catchments.getGeometries(); for (int i = 0; i < geoms.numObjs; i++) { MasonGeometry mg = (MasonGeometry) geoms.get(i); Integer num = mg.getIntegerAttribute("SCHOOL_NUM"); if (num != null) { School s = schoolMap.get(num); if (s != null) { s.catchmentCount++; } else { System.out.format("School %s not found.\n", num); } } } } private void printSchools() { Collections.sort(schools, new Comparator<School>() { @Override public int compare(School arg0, School arg1) { return arg0.name.compareTo(arg1.name); } }); for (School s : schools) { System.out.format("Name: %s, Count: %d\n", s.name, s.catchmentCount); } } private Coordinate getRandomCoordinate(Envelope bounds) { double x = random.nextDouble() * bounds.getWidth() + bounds.getMinX(); double y = random.nextDouble() * bounds.getHeight() + bounds.getMinY(); return new Coordinate(x, y); } private void createHouseholds() { // create households households.clear(); householdsField.clear(); householdsField.setMBR(highSchoolZones.getMBR()); Envelope bounds = highSchoolZones.getMBR(); // this is needed because Geometry exemplar = ((MasonGeometry) highSchoolZones.getGeometries().get(0)).getGeometry(); for (int i = 0; i < numHouseholds; i++) { Household h = new Household(this); households.add(h); Coordinate coord; do { coord = getRandomCoordinate(bounds); } while (!highSchoolZones.isCovered(coord)); Point pt = GeometryFactory.createPointFromInternalCoord(coord, exemplar); h.location = pt; householdsField.addGeometry(new MasonGeometry(pt)); } } private School findAppropriateSchool(int age, Point location) { GeomVectorField zones; if (age < 11) { zones = elementarySchoolZones; } else if (age < 15) { zones = middleSchoolZones; } else { zones = highSchoolZones; } Bag catchment = zones.getContainingObjects(location); if (catchment.numObjs != 1) { System.out.format("Error: school search (age: %d, location: %s) found %d catchments.\n", age, location, catchment.numObjs); for (int i = 0; i < catchment.numObjs; i++) { System.out.format(" Catchment %d: %s\n", i, ((MasonGeometry) catchment.get(i)).getAttribute("SCHID_3")); } return null; } MasonGeometry mg = (MasonGeometry) catchment.get(0); Integer num = mg.getIntegerAttribute("SCHOOL_NUM"); return schoolMap.get(num); } private void createStudents() { double mu = StatsTools.calcLognormalMu(childrenPerHouseholdAve, childrenPerHouseholdStdev); double sigma = StatsTools.calcLognormalSigma(childrenPerHouseholdAve, childrenPerHouseholdStdev); students.clear(); for (Household h : households) { // pick a random (non-zero) number of children from a lognormal distribution int numKids; do { numKids = (int) Math.round(StatsTools.normalToLognormal(mu, sigma, random.nextGaussian())); } while (numKids == 0); for (int j = 0; j < numKids; j++) { int age = 5 + random.nextInt(14); // [5,18] Student s = new Student(this, age); // pick a school for this child School school = findAppropriateSchool(age, h.location); if (school != null) school.students.add(s); else System.out.println("Error: school is null."); students.add(s); h.students.add(s); } } } /** * Initialize the population. The process goes as follows: * * point p = random location in overall bounding box * if p is not inside one of the catchment regions, repeat previous step * populate household with random number of children * for each child in household * if child is elementary school age, find elementary school catchment * if child is middle school age, find middle school catchment * if child is high school age, find high school catchment * lookup school associated with catchment * assign child to school */ private void init() { // reopen the schools for (School s : schools) s.closed = false; if (reInitHouseholds) { for (School s : schools) s.students.clear(); createHouseholds(); createStudents(); } else // at least reset the students { for (Student s : students) { s.status = Status.SUSCEPTIBLE; s.homebound = false; s.timeSinceInfected = 0; } } } private void beginInfection() { if (numInitialInfections > students.size()) { System.err.format("ERROR: Cannot infect %d students since there are only %d in the population.", numInitialInfections, students.size()); return; } // let's infect some kids! Student s; for (int j = 0; j < numInitialInfections; j++) { do { s = students.get(random.nextInt(students.size())); } while (s.status != Status.SUSCEPTIBLE); s.infect(); } } @Override public void start() { super.start(); init(); for (Student s : students) schedule.scheduleRepeating(s, 0, 1.0); for (School s : schools) schedule.scheduleRepeating(s, 1, 1.0); for (Household h : households) schedule.scheduleRepeating(h, 2, 1.0); // at step 20, begin the infection schedule.scheduleOnce(20, 3, new Steppable() { public void step(SimState state) { beginInfection(); } }); // containment strategy: if any school has an outbreak, close all the schools schedule.scheduleRepeating(0, 4, new Steppable() { public void step(SimState state) { if (closeSchoolUponOutbreak || closeAllSchoolsUponOutbreak) for (School s : schools) if (s.getProportionOfHomeboundStudents() > outbreakThreshold) { // if any school has an outbreak s.closed = true; if (closeAllSchoolsUponOutbreak) { for (School s2 : schools) // close all the schools s2.closed = true; break; } } } }); // check for end condition and terminate schedule.scheduleRepeating(0, 5, new Steppable() { public void step(SimState state) { sCount = 0; iCount = 0; rCount = 0; for (Student s : students) { switch (s.status) { case SUSCEPTIBLE: sCount++; break; case INFECTED: iCount++; break; case RECOVERED: rCount++; break; } } if ((iCount == 0) && (rCount > 0)) { //System.out.format("Step %d. Total people infected: %d out of: %d\n", state.schedule.getSteps(), rCount, students.size()); System.out.format("%d, %d, %d, %d, %d, %f, %b, %b, %f, %d, %d\n", schedule.getSteps(), numHouseholds, numInitialInfections, diseaseDuration, incubationPeriod, diseaseTransmissionProb, closeSchoolUponOutbreak, closeAllSchoolsUponOutbreak, outbreakThreshold, rCount, students.size()); state.kill(); } } }); } public static void main(String[] args) { doLoop(SickStudentsModel.class, args); System.exit(0); } }