Return to Week 10 of course schedule.
We added rendering to our Simple COVID Spread Model as Assignment 7.
Next we will add realistic associations (they will meet regularly during terms, and we will have people do different things on breaks, including associate with the outside world, wherein they can come back to campus with COVID. I suppose we could add visitors too.
// The main concepts are person, place, and association.
// A person can belong to many associations.
// An association has a meeting schedule, which with the help
// of the academic calendar allows us to determine if it is meeting.
// If an association is meeting, and a person belongs to that association,
// then that meetings place is what that person's place() function returns.
// If there is a conflict (because the person has multiple associations
// that are meeting) then the first association in the list wins.
// If the person is in no associations that are meeting, then place()
// returns their home.
// The thing that manages the list of places, people, and assciations,
// is a singleton called community. Although we only have one, the way
// it is written, we could have more. If we didn't have community, then
// the functions on community would all need to be global functions, and
// the three lists that community manages would need to be global variables.
AcademicCalendar ac;
Community community;
class AcademicCalendar {
static final int HOURS_IN_DAY = 24;
static final int HOURS_IN_WEEK = 168;
static final int HOURS_IN_YEAR = 8760;
// let us begin our simulation with the beginning of COVID
int hours = 12 * HOURS_IN_WEEK; // COVID really got going on the 12th week of 2020
int[] breakWeeks = {
0, /* third week of winter break */
/* Term 4 */
8, 9, /* two-week break */
/* Term 5 */
17, /* one-week break */
/* Term 6 */
25,
/* Term 1 */
33, 34, /* two-week break */
/* Term 2 */
42, /* Term 3 */
50, 51, /* first two weeks of three-week break */
52 /* there is actually only one day in week 52 */
};
void increment() {
hours++;
}
boolean isAssociationMeeting(Association a) {
int h = hours % a.frequency;
return h >= a.meetingTime && h < a.meetingTime + a.duration;
}
int year() {
return 2020 + hours / HOURS_IN_YEAR;
}
int week() {
int h = hours % HOURS_IN_YEAR;
return h / HOURS_IN_WEEK;
}
boolean isCampusOnBreak() {
int w = week();
for (int i = 0; i < breakWeeks.length; ++i) {
if (w == breakWeeks[i]) return true;
}
return false;
}
}
class Place {
int x, y, w, h;
String name;
int people_place_has_drawn;
Place(String name_, int x_, int y_, int w_, int h_) {
name = name_;
x = x_;
y = y_;
w = w_;
h = h_;
}
void draw() {
rect(x, y, w, h);
people_place_has_drawn = 0;
}
PVector whereToPutPerson() {
float person_x = x + people_place_has_drawn * 20;
people_place_has_drawn++;
return new PVector(person_x, y);
}
}
class Infection {
static final int INCUBATION_PERIOD = 1 * AcademicCalendar.HOURS_IN_DAY;
static final int WORST_OF_IT_ENDS = 4 * AcademicCalendar.HOURS_IN_DAY;
static final int INFECTION_ENDS = 14 * AcademicCalendar.HOURS_IN_DAY;
static final float HOURLY_R_VALUE = 0.5;
int infectionHours;
Infection() {
infectionHours = ac.hours;
}
float infectiousness() {
// during day 0, not very infectious (incubating)
// real infectious during days 1, 2, 3 (inclusive)
// steadily declining infectiousness during days 4-13 (inclusive)
int delta = ac.hours - infectionHours;
float infectiousness;
if (delta < AcademicCalendar.HOURS_IN_DAY) {
// incubating, low infectiousness
infectiousness = 0.2;
} else if (delta >= INCUBATION_PERIOD && delta < WORST_OF_IT_ENDS) {
// worst of it
infectiousness = 1.0;
} else if (delta < INFECTION_ENDS) {
// recovering
infectiousness = (delta - WORST_OF_IT_ENDS) / (INFECTION_ENDS - WORST_OF_IT_ENDS);
} else {
// recovered
infectiousness = 0.0;
}
return infectiousness;
}
boolean over() {
return ac.hours - infectionHours >= INFECTION_ENDS;
}
}
static int last_id = -1;
class Person {
Infection infection = null;
float immunity = 0.0; // a number between 0 and 1, with 1 being immune
float fastiduousness = 0.5; // a number between 0 and 1 with 1 being ultra-hygienic
int id;
Place home;
Person(Place home_) {
id = ++last_id;
home = home_;
}
boolean isInfected() {
return infection != null;
}
float infectiousness() {
return infection != null ? infection.infectiousness() : 0.0;
}
float slovenliness() {
return 1.0 - fastiduousness;
}
float susceptibility() {
return 1.0 - immunity;
}
void catchCOVID() {
infection = new Infection();
immunity = 1.0;
}
void expose(Person otherPerson, float closeness) {
if (this != otherPerson && isInfected() && !otherPerson.isInfected()) {
float exposureIntensity = closeness * infectiousness() * slovenliness() * otherPerson.slovenliness();
if (random(1.0) < exposureIntensity * Infection.HOURLY_R_VALUE) {
otherPerson.catchCOVID();
}
}
}
Place place() {
for (Association association : community.associations) {
if (association.members.contains(this)) {
return association.place;
}
}
return home;
}
void draw() {
ellipseMode(CENTER);
pushMatrix();
Place place = place();
PVector offset = place.whereToPutPerson();
translate(offset.x, offset.y);
stroke(0);
if (isInfected()) {
fill(255, 0, 0);
} else {
fill(255);
}
ellipse(10 + 20 + 10, 15, 10, 10);
popMatrix();
}
void age() {
if (infection != null && infection.over()) {
infection = null;
}
// 1.00007912952 is 2^(1/8760). Using this number
// gives immunity a half-life of one year.
immunity /= 1.00007912952;
}
}
class Association {
int meetingTime;
int duration; // either DAILY OR WEEKLY
int frequency; // either DAILY OR WEEKLY
float closeness = 0.5;
ArrayList<Person> members = new ArrayList<Person>();
Place place;
Association(Place place_) {
place = place_;
}
void expose() {
for (Person person : members) {
for (Person otherPerson : members) {
person.expose(otherPerson, closeness);
}
}
}
}
class Community {
ArrayList<Person> people = new ArrayList<Person>();
ArrayList<Association> associations = new ArrayList<Association>();
ArrayList<Place> places = new ArrayList<Place>();
Community() {
Place mainBuilding = new Place("Main Building", 200, 100, 400, 100);
places.add(mainBuilding);
Place dorm = new Place("Dorm", 200, 400, 300, 100);
places.add(dorm);
Association endlessMeetings = new Association(mainBuilding);
associations.add(endlessMeetings);
for (int i = 0; i < 26; i++) {
Person person = new Person(dorm);
people.add(person);
if (i <= 12) {
endlessMeetings.members.add(person);
}
}
Person patient0 = people.get(12);
patient0.catchCOVID();
}
void increment() {
for (Association association : associations) {
association.expose();
}
for (Person person : people) {
person.age();
}
}
void report() {
int covidCount = 0;
for (Person person : people) {
if (person.isInfected()) {
++covidCount;
}
}
println("The community has " + covidCount + " cases.");
}
void draw() {
background(255);
rectMode(CORNER); // the default
stroke(0);
fill(255);
for (Place place : places) {
place.draw();
}
for (Person person : people) {
person.draw();
}
}
}
void setup() {
size(1000, 600);
ac = new AcademicCalendar();
community = new Community();
}
void draw() {
ac.increment();
community.increment();
// community.report();
community.draw();
}