/* Epidemic Simulator * Author: Douglas Jones * Status: MP8 solution; it works, but see BUG notices * Version: 11/2/2020 */ import java.lang.Math; import java.lang.NumberFormatException; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.PriorityQueue; import java.util.Random; import java.util.regex.Pattern; import java.util.Scanner; import java.io.File; import java.io.FileNotFoundException; // Utility classes /** * Error handling */ class Error{ private static int errorCount = 0; private static final int errorLimit = 10; /** Report a warning to System.err * @param message the text of the warning */ public static void warn( String message ) { System.err.println( message ); errorCount = errorCount + 1; if (errorCount > errorLimit) System.exit( 1 ); } /** Report a fatal error to System.err * @param message the text reporting the error * Note that this code exits the program with an error indication */ public static void fatal( String message ) { warn( message ); System.exit( 1 ); } /** Quit if there were any errors */ public static void quitIfAny() { if (errorCount > 0) System.exit( 1 ); } } /** * Wrapper or Adapter for Scanners that integrates error handling * @see java.util.Scanner * @see Error */ class MyScanner { Scanner self; // the scanner this object wraps /** * Parameter carrier class for deferred string construction * used only for error message parameters to getXXX() methods */ public static interface ErrorMessage { String myString(); } // patterns for popular scannables, compiled just once static Pattern delimPat = Pattern.compile( "([ \t\r\n]|(//[\\S \t]*\n))*" ); // allow empty delimiters, and allow Java style comments static Pattern intPat = Pattern.compile( "-?[0-9]*" ); // integers static Pattern realPat = Pattern.compile( "-?\\d*\\.?\\d*(E(\\+|-)?\\d*)?" ); /** Construct a MyScanner to read from a file * @param f the file to read from * @throws FileNotFoundException if the file could not be read */ public MyScanner( File f ) throws FileNotFoundException { self = new Scanner( f ); } // methods we wish could inherit from Scanner but can't beause it's final // BUG -- to properly handle end of line delimiters, these need redefinition public boolean hasNext( String s ) { return self.hasNext( s ); } public boolean hasNextDouble() { return self.hasNextFloat(); } public boolean hasNextFloat() { return self.hasNextFloat(); } public boolean hasNextInt() { return self.hasNextInt(); } public String next( String s ) { return self.next( s ); } public float nextDouble() { return self.nextFloat(); } public float nextFloat() { return self.nextFloat(); } public int nextInt() { return self.nextInt(); } public String nextLine() { return self.nextLine(); } // redefined methods from class Scanner /** Is there a next token? * but first skip optional extended delimiters * @return true if there is a token, otherwise false */ public boolean hasNext() { self.skip( delimPat ); // skip the delimiter, if any return self.hasNext(); } /** Get the next token, * but first skip optional extended delimiters * @return the token as a string */ public String next() { self.skip( delimPat ); // skip the delimiter, if any return self.next(); } // new methods we add to this class /** Get the next string, if one is available * @param def the default value if no string is available * @param msg the error message to print if no string is available * @return the token as a String or the default */ public String getNext( String def, ErrorMessage msg ) { if (self.hasNext()) return self.next(); Error.warn( msg.myString() ); return def; } /** Get the next match to pattern, if one is available * @param pat the pattern string we are trying to match * @param def the default value if no match available * @param msg the error message to print if no match available * @return the token as a String or the default */ public String getNext( String pat, String def, ErrorMessage msg ) { self.skip( delimPat ); // skip the delimiter, if any self.skip( "(" + pat + ")?" ); // skip the pattern if present String next = self.match().group(); if (!next.isEmpty()) { // non-empty means next thing matched pat return next; } else { Error.warn( msg.myString() ); return def; } } /** Get the next double, if one is available * @param def the default value if no float is available * @param msg the error message to print if no double is available * @return the token as a double or the default */ public double getNextDouble( double def, ErrorMessage msg ) { self.skip( delimPat ); // skip the delimiter, if any self.skip( realPat ); // skip the float, if any String next = self.match().group(); try { return Double.parseDouble( next ); } catch ( NumberFormatException e ) { Error.warn( msg.myString() ); return def; } } /** Get the next float, if one is available * @param def the default value if no float is available * @param msg the error message to print if no float is available * @return the token as a float or the default */ public float getNextFloat( float def, ErrorMessage msg ) { self.skip( delimPat ); // skip the delimiter, if any self.skip( realPat ); // skip the float, if any String next = self.match().group(); try { return Float.parseFloat( next ); } catch ( NumberFormatException e ) { Error.warn( msg.myString() ); return def; } } /** Get the next int, if one is available * @param def the default value if no int is available * @param msg the error message to print if no int is available * @return the token as an int or the default */ public int getNextInt( int def, ErrorMessage msg ) { self.skip( delimPat ); // skip the delimiter, if any self.skip( intPat ); // skip the float, if any String next = self.match().group(); try { return Integer.parseInt( next ); } catch ( NumberFormatException e ) { Error.warn( msg.myString() ); return def; } } } /** Singleton wrapper for Java's Random class */ class MyRandom extends Random { private MyRandom() { // uncomment exactly one of the following! super(); // let Java pick a random seed // super( 3004 ); // set seed so we can debug } /** the only stream visible to users */ static final MyRandom stream = new MyRandom(); /** an alternate way to expose users to the stream * @return handle on the stream */ public static MyRandom stream() { return stream; } /** get the next exponentially distributed pseudo-random number * @param mean value of the distribution * @return the next number drawn from this distribution */ public double nextExponential( double mean ) { return -Math.log( this.nextDouble() ) * mean; } /** get the next log-normally distributed pseudo-random number * @param median value of the distribution * @param scatter of the distribution * @return the next number drawn from this distribution */ public double nextLogNormal( double median, double scatter ) { double sigma = Math.log( (scatter + median) / median ); return Math.exp( sigma * this.nextGaussian() ) * median; } } /** Framework for discrete event simulation. */ abstract class Simulator { private Simulator(){} // prevent anyone from instantiating this class // BUG -- this may not be the right place to specify time units public static final double day = 1.0; public static final double hour = day / 24.0; public static final double minute = day / (24.0 * 60.0); public static final double second = day / (24.0 * 60.0 * 60.0); public static final double week = day * 7; /** Interface to allow lambda parameters to schedule() * as such, no external code ever uses Action */ public interface Action { // actions contain the specific code of each event void trigger( double time ); } private static class Event { public double time; // the time of this event public Action act; // what to do at that time } private static PriorityQueue eventSet = new PriorityQueue ( (Event e1, Event e2)-> Double.compare( e1.time, e2.time ) ); /** Call schedule to make act happen at time. * Users typically pass the action as a lambda expression: *
     *  Simulator.schedule( t, ( double time )-> method( ... time ... ) )
     *  
*/ static void schedule( double time, Action act ) { Event e = new Event(); e.time = time; e.act = act; eventSet.add( e ); } /** run the simulation. * Call run() after scheduling some initial events to run the simulation. */ static void run() { while (!eventSet.isEmpty()) { Event e = eventSet.remove(); e.act.trigger( e.time ); } } } // Model classes /** * People occupy places * @see Place * @see Employee */ class Person { // private stuff needed for instances protected enum States { uninfected, latent, infectious, bedridden, recovered, dead // the order of the above is significant: >= uninfected is infected } // static attributes describing progression of infection // BUG -- These should come from model description file, not be hard coded double latentMedT = 2 * Simulator.day; double latentScatT = 1 * Simulator.day; double bedriddenProb = 0.7; double infectRecMedT = 1 * Simulator.week; double infectRecScatT = 6 * Simulator.day; double infectBedMedT = 3 * Simulator.day; double infectBedScatT = 5 * Simulator.day; double deathProb = 0.2; double bedRecMedT = 2 * Simulator.week; double bedRecScatT = 1 * Simulator.week; double bedDeadMedT = 1.5 * Simulator.week; double bedDeadScatT = 1 * Simulator.week; // static counts of infection progress private static int numUninfected = 0; private static int numLatent = 0; private static int numInfectious = 0; private static int numBedridden = 0; private static int numRecovered = 0; private static int numDead = 0; // fixed attributes of each instance private final HomePlace home; // all people have homes public final String name; // all people have names // instance variables protected Place place; // when not in transit, where the person is public States infectionState; // all people have infection states // the collection of all instances private static final LinkedList allPeople = new LinkedList (); // need a source of random numbers private static final MyRandom rand = MyRandom.stream(); /** The only constructor * @param h the home of the newly constructed person */ public Person( HomePlace h ) { name = super.toString(); home = h; place = h; // all people start out at home infectionState = States.uninfected; numUninfected = numUninfected + 1; h.addResident( this ); allPeople.add( this ); // this is the only place items are added! } /** Predicate to test person for infectiousness * @returns true if the person can transmit infection */ public boolean isInfectious() { return (infectionState == States.infectious) || (infectionState == States.bedridden); } /** Primarily for debugging * @return textual name and home of this person */ public String toString() { return name ;// DEBUG + " " + home.name + " " + infectionState; } /** Shuffle the population * This allows correlations between attributes of people to be broken */ public static void shuffle() { Collections.shuffle( allPeople, rand ); } /** Allow outsiders to iterate over all people * @return an iterator over people */ public static Iterator iterator() { return allPeople.iterator(); } // simulation methods relating to infection process /** Infect a person * called when circumstances call for a person to become infected */ public void infect( double time ) { if (infectionState == States.uninfected) { // infecting an already infected person has no effect double delay = rand.nextLogNormal( latentMedT, latentScatT ); numUninfected = numUninfected - 1; infectionState = States.latent; numLatent = numLatent + 1; Simulator.schedule( delay + time, (double t)-> beInfectious( t ) ); } } /** An infected but latent person becomes infectous * scheduled by infect() to make a latent person infectious */ private void beInfectious( double time ) { numLatent = numLatent - 1; infectionState = States.infectious; numInfectious = numInfectious + 1; if (place != null) place.oneMoreInfectious( time ); if (rand.nextFloat() > bedriddenProb) { // person stays asymptomatic double delay = rand.nextLogNormal( infectRecMedT, infectRecScatT ); Simulator.schedule( delay + time, (double t)-> beRecovered( t ) ); } else { // person becomes bedridden double delay = rand.nextLogNormal( infectBedMedT, infectBedScatT ); Simulator.schedule( delay + time, (double t)-> beBedridden( t ) ); } } /** An infectious person becomes bedridden * scheduled by beInfectious() to make an infectious person bedridden */ private void beBedridden( double time ) { numInfectious = numInfectious - 1; infectionState = States.bedridden; numBedridden = numBedridden + 1; if (rand.nextFloat() > deathProb) { // person recovers double delay = rand.nextLogNormal( bedRecMedT, bedRecScatT ); Simulator.schedule( delay + time, (double t)-> beRecovered( t ) ); } else { // person dies double delay = rand.nextLogNormal( bedDeadMedT, bedDeadScatT ); Simulator.schedule( delay + time, (double t)-> beDead( t ) ); } // if in a place (not in transit) that is not home, go home now! if ((place != null) && (place != home)) goHome( time ); } /** A infectious or bedridden person recovers * scheduled by beInfectious() or beBedridden to make a person recover */ private void beRecovered( double time ) { if (infectionState == States.infectious) { numInfectious = numInfectious - 1; } else { numBedridden = numBedridden - 1; } infectionState = States.recovered; numRecovered = numRecovered + 1; if (place != null) place.oneLessInfectious( time ); } /** A bedridden person dies * scheduled by beInfectious() to make a bedridden person die */ private void beDead( double time ) { numBedridden = numBedridden - 1; infectionState = States.dead; // needed to prevent resurrection numDead = numDead + 1; // if the person died in a place, make them leave it! if (place != null) place.depart( this, time ); // BUG: leaves them in the directory of residents and perhaps employees } // simulation methods relating to daily reporting /** Make the daily midnight report * @param t the current time */ public static void report( double time ) { System.out.println( "at " + time + ", un = " + numUninfected + ", lat = " + numLatent + ", inf = " + numInfectious + ", bed = " + numBedridden + ", rec = " + numRecovered + ", dead = " + numDead ); // make this happen cyclically Simulator.schedule( time + Simulator.day, (double t)-> report( t ) ); } // simulation methods relating to personal movement /** Make a person arrive at a new place * @param p new place * @param t the current time * scheduled */ private void arriveAt( double time, Place p ) { if ((infectionState == States.bedridden) && (p != home)) { // go straight home if you arrive at work while sick goHome( time ); } else if (infectionState == States.dead) { // died on the way to work // allow this person to be forgotten } else { // only really arrive if not sick p.arrive( this, time ); this.place = p; } } /** Move a person to a new place * @param place where the person travels * @param time at which the move will be completed takes place * BUG -- if time was the time the trip started: * travelTo could do the call to this.place.depart() * and it could compute the travel time */ public void travelTo( Place p, double time ) { this.place = null; Simulator.schedule( time, (double t) -> arriveAt( t, p ) ); } /** Simulate the trip home from wherever * @param time of departure */ public void goHome( double time ) { double travelTime = rand.nextLogNormal( 20 * Simulator.minute, // mean travel time 3 * Simulator.minute // scatter in travel time ); // the possibility of arriving at work after falling ill requires this if (this.place != null) this.place.depart( this, time ); this.travelTo( this.home, time + travelTime ); } } /** * Employees are People who work * @see Person * @see WorkPlace */ class Employee extends Person { // instance variables private WorkPlace job; // employees have WorkPlaces // can't be final because set post constructor // need a source of random numbers private static final MyRandom rand = MyRandom.stream(); /** The only constructor * @param h the HomePlace of the newly constructed Employee * Note that employees are created without well-defined workplaces */ public Employee( HomePlace h ) { super( h ); // construct the base person job = null; // go to work every day at 25 minutes before 8 AM Simulator.schedule( (8 * Simulator.hour) - (25 * Simulator.minute), (double t)-> goToWork( t ) ); } /** Set workplace of employee * @param w the workPlace of the newly constructed Employee * No employee's workplace may be set more than once */ public void setWorkplace( WorkPlace w ) { assert job == null; job = w; w.addEmployee( this ); } /** Primarily for debugging * @return textual name home and employer of this person */ public String toString() { return super.toString() ;// DEBUG + " " + job.name; } // simulation methods /** Simulate the daily trip to work * @param time of departure */ private void goToWork( double time ) { if (infectionState == States.dead) return; // finish killing the dead! // people only leave home if feeling OK if (infectionState != States.bedridden) { double travelTime = rand.nextLogNormal( 20 * Simulator.minute, // mean travel time 3 * Simulator.minute // scatter in travel time ); this.place.depart( this, time ); this.travelTo( this.job, time + travelTime ); } // go to work every day at the same time Simulator.schedule( time + Simulator.day, (double t)-> goToWork( t ) ); } } /** * Places are occupied by people * @see HomePlace * @see WorkPlace */ abstract class Place { // invariant attributes of each place public final String name; protected double transmissivity; // how infectious is this place // initialized by subclass! // dynamic attributes of each place protected final LinkedList occupants = new LinkedList<> (); private int infectiousCount = 0; // number of infected occupants; double lastCheck = 0.0; // time of last check on infectiousness // contructor (effectively protected Place() { name = super.toString(); allPlaces.add( this ); } // manage the infectiousness of this place // need a source of random numbers private static final MyRandom rand = MyRandom.stream(); /** see who to infect at this time * @param time, the time of the change * called just before any any change to the population or infection count */ private void whoToInfect( double time ) { // note that transmissivities are per hour, so convert time to hours double interval = (time - lastCheck) / Simulator.hour; double pInfection = transmissivity * infectiousCount * interval; if (interval <= 0) return; // short circuit the process for efficiency // probability cannot exceed one! if (pInfection > 1.0) pInfection = 1.0; // BUG -- should it be: pInfection = 1.0 - Math.exp( -pInfection ); // give everyone a fair chance to catch the infection for (Person p: occupants) { if (rand.nextDouble() < pInfection) { p.infect( time ); } } lastCheck = time; } /** another person here has become infectious * @param time, the time of the change * they either arrived while infectous * or transitioned to infectous while here */ public void oneMoreInfectious( double time ) { whoToInfect( time ); infectiousCount = infectiousCount + 1; } /** one less person here is infectious * @param time, the time of the change * they either departed here while infectous * or transitioned to recovered or dead while here */ public void oneLessInfectious( double time ) { whoToInfect( time ); infectiousCount = infectiousCount - 1; } // tools for moving people in and out of places /** a person arrives at this place * @param p, the person who arrives */ public void arrive( Person p, double time ) { occupants.add( p ); if (p.isInfectious()) { oneMoreInfectious( time ); } else { whoToInfect( time ); } // DEBUG System.out.println( // (Object)p.toString() + " arrives " + (Object)this + " at " + time // ); // DEBUG } /** a person leaves from this place * @param p, the person who leaves */ public void depart( Person p, double time ) { if (p.isInfectious()) { oneLessInfectious( time ); } else { whoToInfect( time ); } boolean wasPresent = occupants.remove( p ); assert wasPresent: "p=" + p + " this=" + this; assert !occupants.contains( p ): "p=" + p + " this=" + this; // DEBUG System.out.println( // (Object)p.toString() + " departs " + (Object)this + " at " + time // ); // DEBUG } // the collection of all instances private static final LinkedList allPlaces = new LinkedList (); /** Allow outsiders to iterate over all places * @return an iterator over places */ public static Iterator iterator() { return allPlaces.iterator(); } } /** * HomePlaces are occupied by any type of person * @see Place * @see Person */ class HomePlace extends Place { private final LinkedList residents = new LinkedList (); // transmissivity median and scatter for homes // BUG -- These should come from model description file, not be hard coded private static final double transMed = 0.03 * Simulator.hour; private static final double transScat = 0.02 * Simulator.hour; // need a source of random numbers private static final MyRandom rand = MyRandom.stream(); /** The only constructor for Place * Places are constructed with no occupants */ public HomePlace() { super(); // initialize the underlying place super.transmissivity = rand.nextLogNormal( transMed, transScat ); } /** Add a resident to a place * Should only be called from the person constructor * @param r a Person, the new resident */ public void addResident( Person r ) { residents.add( r ); occupants.add( r ); // no need to check to see if the person already lives there? } /** Primarily for debugging * @return textual name and residents of the home */ public String toString() { String res = name; // DEBUG for (Person p: residents) { res = res + " " + p.name; } return res; } } /** * WorkPlaces are occupied by employees * @see Place * @see Employee */ class WorkPlace extends Place { private final LinkedList employees = new LinkedList (); // transmissivity median and scatter for workplaces // BUG -- These should come from model description file, not be hard coded private static final double transMed = 0.02 * Simulator.hour; private static final double transScat = 0.25 * Simulator.hour; // need a source of random numbers private static final MyRandom rand = MyRandom.stream(); /** The only constructor for WorkPlace * WorkPlaces are constructed with no residents */ public WorkPlace() { super(); // initialize the underlying place super.transmissivity = rand.nextLogNormal( transMed, transScat ); // make the workplace open at 8 AM Simulator.schedule( 8 * Simulator.hour, (double t)-> this.open( t ) ); } /** Add an employee to a WorkPlace * Should only be called from the person constructor * @param r an Employee, the new worker */ public void addEmployee( Employee r ) { employees.add( r ); // no need to check to see if the person already works there? } /** Primarily for debugging * @return textual name and employees of the workplace */ public String toString() { String res = name; // DEBUG for (Employee p: employees) { res = res + " " + p.name; } return res; } // simulation methods /** open the workplace for business * @param t the time of day * Note that this workplace will close itself 8 hours later, and * opening plus closing should create a 24-hour cycle. * @see close */ private void open( double time ) { // System.out.println( this.toString() + " opened at time " + time ); // BUG -- we should probably do something useful too // close this workplace 8 hours later Simulator.schedule( time + 8 * Simulator.hour, (double t)-> this.close( t ) ); } /** close the workplace for the day * @param t the time of day * note that this workplace will reopen 16 hours later, and * opening plus closing should create a 24-hour cycle. * @see open */ private void close( double time ) { //System.out.println( this.toString() + " closed at time " + time ); // open this workplace 16 hours later, with no attention to weekends Simulator.schedule( time + 16 * Simulator.hour, /* opens 8 hours later */ (double t)-> this.open( t ) ); // send everyone home for (Person p: occupants) { // schedule it for now in order to avoid modifying list inside loop // not doing this gives risk of ConcurrentModificationException Simulator.schedule( time, (double t)-> p.goHome( t ) ); } } } /** * Main class builds model and will someday simulate it * @see Person * @see Place */ public class Epidemic { // the following are set by readCommunity and used by buildCommunity // default values are used to check for failure to initialize static int pop = -1; /* the target population */ static double houseMed = -1; /* median household size */ static double houseSc = -1; /* household size scatter */ static double workMed = -1; /* median workplace size */ static double workSc = -1; /* workplace size scatter */ static int infected = -1; /* the target number of infected people */ static double employed = -1; /* the likelihood that someone is employed */ /** Read and check the simulation parameters * @param sc the scanner to read the community description from * Called only from the main method. */ private static void readCommunity( MyScanner sc ) { while (sc.hasNext()) { // until the input file is finished String command = sc.next(); if ("pop".equals( command )) { if (pop > 0) Error.warn( "population already set" ); pop = sc.getNextInt( 1, ()-> "pop with no argument" ); sc.getNext( ";", "", ()-> "pop " +pop+ ": missed semicolon" ); if (pop < 1) { /* sanity check on value given */ Error.warn( "pop " +pop+ ": non-positive population?" ); pop = 0; } } else if ("house".equals( command )) { if (houseMed > 0) Error.warn( "household size already set" ); if (houseSc >= 0) Error.warn( "household scatter already set" ); houseMed = sc.getNextDouble( 1, ()-> "house with no argument" ); sc.getNext( ",", "", ()-> "house "+houseMed+": missed comma" ); houseSc = sc.getNextDouble( 0, ()-> "house "+houseMed+", missing argument " ); sc.getNext( ";", "", ()-> "house "+houseMed+", "+houseSc+": missed semicolon" ); if (houseMed < 1) { /* sanity check on value given */ Error.warn( "house "+houseMed+", "+houseSc+": median nonpositive?" ); houseMed = 0; } if (houseSc < 0) { /* sanity check on value given */ Error.warn( "house "+houseMed+", "+houseSc+": scatter negative?" ); houseSc = 0; } } else if ("workplace".equals( command )) { if (workMed > 0) Error.warn( "workplace size already set" ); if (workSc >= 0) Error.warn( "workplace scatter already set" ); workMed = sc.getNextDouble( 1, ()-> "workplace with no argument" ); sc.getNext( ",", "", ()-> "workplace "+workMed+": missed comma" ); workSc = sc.getNextDouble( 0, ()-> "workplace "+workMed+", missed argument " ); sc.getNext( ";", "", ()-> "workplace "+workMed+", "+workSc+": missed semicolon" ); if (workMed < 1) { /* sanity check on value given */ Error.warn( "workplace "+workMed+", "+workSc+": median nonpositive?" ); workMed = 0; } if (workSc < 0) { /* sanity check on value given */ Error.warn( "workplace "+workMed+", "+workSc+": scatter negative?" ); workSc = 0; } } else if ("infected".equals( command )) { if (infected > 0) Error.warn( "infected already set" ); infected = sc.getNextInt( 1, ()-> "infected with no argument" ); sc.getNext( ";", "", ()-> "infected " +infected+ ": missed semicolon" ); if (infected < 0) { /* sanity check on value given */ Error.warn( "infected "+infected+": negative value?" ); infected = 0; } if (infected > pop) { /* sanity check on value given */ Error.warn( "infected "+infected+": greater than population?" ); infected = pop; } } else if ("employed".equals( command )) { if (employed >= 0) Error.warn( "employed rate already set" ); employed = sc.getNextDouble( 1, ()-> "employed with no argument" ); sc.getNext( ";", "", ()-> "employed "+employed+": missed semicolon" ); if (employed < 0) { /* sanity check on value given */ Error.warn( "employed "+employed+": negative value?" ); employed = 0; } if (employed > 1) { /* sanity check on value given */ Error.warn( "employed "+employed+": greater than 1.0?" ); employed = 1.0; } } else if ("end".equals( command )) { Double endTime = sc.getNextDouble( 1, ()-> "end: floating point end time expected" ); if (endTime <= 0) { Error.warn( "end "+endTime+": non positive end of time?" ); } sc.getNext( ";", "", ()-> "end "+endTime+": missed semicolon" ); Simulator.schedule( endTime, (double t)->System.exit( 0 ) ); // BUG -- A better end mechanism would output a results report } else { Error.warn( "unknown command: "+command ); } } // BUG -- if there were errors, it might be best to quit now // check for complete initialization if (pop < 0) Error.warn( "population not initialized" ); if (houseMed < 0) Error.warn( "median household size not set" ); if (houseSc < 0) Error.warn( "household scatter not set" ); if (workMed < 0) Error.warn( "median workplace size not set" ); if (workSc < 0) Error.warn( "workplace scatter not set" ); if (infected < 0) Error.warn( "infected number not given" ); if (employed < 0) Error.warn( "employment rate not given" ); } /** Build a community that the simulation parameters describe * Called only from the main method. */ private static void buildCommunity() { // must always have a home available as we create people int currentHomeCapacity = 0; int currentWorkCapacity = 0; HomePlace currentHome = null; WorkPlace currentWork = null; // need a source of random numbers final MyRandom rand = MyRandom.stream(); // create the population for (int i = 0; i < pop; i++) { Person p = null; if (currentHomeCapacity < 1) { // must create a new home currentHome = new HomePlace(); currentHomeCapacity = (int)Math.ceil( rand.nextLogNormal( houseMed, houseSc ) ); } currentHomeCapacity = currentHomeCapacity - 1; // create the right kind of person if (rand.nextDouble() <= employed) { // this is as an employee p = new Employee( currentHome ); } else { // this is an unemployed generic person p = new Person( currentHome ); } // decide who to infect // note: pop - i = number of people not yet considered to infect // and infected = number we need to infect, always <= (pop - i) if (rand.nextInt( pop - i ) < infected) { p.infect( 0 ); // infected from the beginning of time infected = infected - 1; } } Person.shuffle(); // shuffle the population to break correlations // go through the population again for (Iterator i = Person.iterator(); i.hasNext(); ){ Person p = i.next(); // for each person if (p instanceof Employee) { Employee e = (Employee)p; if (currentWorkCapacity < 1) { // must create new workplace currentWork = new WorkPlace(); currentWorkCapacity = (int)Math.ceil( rand.nextLogNormal( workMed, workSc ) ); } currentWorkCapacity = currentWorkCapacity - 1; e.setWorkplace( currentWork ); } } Simulator.schedule( 0.0, (double t)->Person.report( t ) ); } /** Output the community * Called only from the main method. * This code exists only for debugging. */ private static void writeCommunity() { System.out.println( "People" ); // Note: Not required in assignment for (Iterator i = Person.iterator(); i.hasNext(); ){ System.out.println( i.next().toString() ); } System.out.println( "Places" ); // Note: Not required in assignment for (Iterator i = Place.iterator(); i.hasNext(); ){ System.out.println( i.next().toString() ); } } /** The main method * This handles the command line arguments. * If the args are OK, it calls other methods to build and test a model. */ public static void main( String[] args ) { if (args.length < 1) { Error.fatal( "Missing file name argument\n" ); } else try { readCommunity( new MyScanner( new File( args[0] ) ) ); Error.quitIfAny(); buildCommunity(); // build what was read above // writeCommunity(); // DEBUG -- this is just for debugging Simulator.run(); } catch ( FileNotFoundException e) { Error.fatal( "Can't open file: " + args[0] + "\n" ); } } }