Return to Week 10 of course schedule.
We will add rendering to this model as Assignment 7 (due March 28).
We will add realistic associations, places, and have people do different things on breaks than during terms as Assignment 8 (probably due April 4 unless we need more cellular automata assignments instead).
// 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 will allow us to determine if it is meeting.
// If an association is meeting, and a person belongs to that association,
// then that is what that person's place will become.
// 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 they
// are at home. Not all of this is implemented in this early version.
// The thing that manages the entire list of places, the entire list
// of people, and the entire list of assciations, is a singleton called
// community.
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 in the US 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 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;
Person() {
id = ++last_id;
}
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();
}
}
}
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 Staffulty extends Person {
}
class Student extends Person {
}
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>();
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>();
Community() {
Association leftAssociation = new Association();
Association rightAssociation = new Association();
associations.add(leftAssociation);
associations.add(rightAssociation);
for (int i = 0; i < 26; i++) {
Person person = new Person();
people.add(person);
if (i < 13) {
leftAssociation.members.add(person);
} else {
rightAssociation.members.add(person);
}
}
Person patient0 = people.get(13);
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);
for (int i = 0; i < people.size(); ++i) {
ellipseMode(CENTER);
stroke(0);
Person person = people.get(i);
if (person.isInfected()) {
fill(255, 0, 0);
} else {
fill(255);
}
ellipse(10 + i * 20 + 10, 15, 10, 10);
}
}
}
void setup() {
size(540, 30);
ac = new AcademicCalendar();
community = new Community();
}
void draw() {
ac.increment();
community.increment();
// community.report();
community.draw();
}