public static void main(String[] args) throws IOException {
if (args.length == 0) {
throw new RuntimeException("One output directory argument is required");
}
= Paths.get(args[0]);
Path outputDirectory if (!Files.exists(outputDirectory)) {
.createDirectory(outputDirectory);
Files} else {
if (!Files.isDirectory(outputDirectory)) {
throw new IOException("Provided path is not a directory");
}
}
// reports
= //
NIOReportItemHandler nioReportItemHandler .builder()//
NIOReportItemHandler.addReport(ModelReportLabel.POPULATION_TRACE, //
.resolve("population_trace_report.csv"))//
outputDirectory.addReport(ModelReportLabel.VACCINATION, //
.resolve("vaccination_report.csv"))//
outputDirectory.build();
7 People Plugin
The people plugin implements the dynamic management of person identity. Each person is identified with an immutable PersonId object that wraps an int value. People are numbered with non-negative values starting with zero and filling a contiguous range, but may contain any number of gaps in that range.
7.1 Plugin Data Initialization
The plugin is initialized using a PeoplePluginData object that collects person id values in contiguous ranges. Note that there is no auxiliary data about people and only their existence as a person at the start of the simulation is captured. Other plugins that deal with the various characteristics of people will separately handle adding that data via their own plugin data structures.
7.2 Plugin Behavior
The plugin adds a single data manager to the simulation as an instance of the PeopleDataManager that is initialized with the PeoplePluginData.
7.3 Data Manager
The data manager provides access to people and provides the ability to:
- Add or remove a person
- Answer questions about person existence
- Get the current set of PersonId values
- Get the total number of people
- Transform PersonId objects to and from int values
- Answer questions about int value ranges used in managing internal data structures in various data managers
The data manager also produces observable events when people are added or removed from the simulation:
- PersonImminentAdditionEvent – notifies that a person is about to be removed
- PersonAdditionEvent – notifies that a person is removed
- PersonImminentRemovalEvent – notifies that a person is being added
- PersonRemovalEvent – notifies that a person is fully added
7.4 Add/Remove event patterns
A common pattern used throughout many plugins for events signifying the addition or removal of an item from the simulation is to represent each of these with two events. The first event is to notify all concerned actors and data managers that an item is about to be removed, but the removal has not yet occurred so any reference to the item will still be available and any finalization or bookkeeping can be performed. The second event will act as an instruction to remove the item and it is expected that the item will not be available for further inspection.
As an example, let’s consider the removal of a person by an actor. The person to remove is PersonId[47] and the actor requests the person be removed by the PeopleDataManager. The data manager first plans to release the PersonRemovalEvent as soon as possible. This will schedule the release of the event onto the planning queue and time will not move forward before the execution of this event. However, this is a plan and it will only take place after the all current activities are complete. The data manager next releases the PersonImminentRemovalEvent. This event will propagate immediately to the other data managers and to any actors or reports that are subscribed to person removals. Since the data managers generally do not act on the imminent removal, the actors are able to retrieve any information about the person they need to take final actions or produce reports. Once everyone has had a chance to see that the person will be removed, the planned PersonRemovalEvent will be released and the data managers will finally remove any information related to the person from their data structures. This two-phase removal pattern is useful and practical but does present one problem: Consider the original actor that was deleting person 47. On the very next line of their code after they request the removal of the person, the person still exists. The removal is not immediate, but is slightly delayed in that it will occur only after flow of control has returned to the simulation. This delay will not correspond to any time flow, so the removal of the person will occur at the same time as the request for the removal.
The addition of a person follows a similar pattern. To understand this, we first need to look at the PersonConstructionData used to add a person. The PersonConstructionData is a container for zero to many objects that carry information about the new person to be used by the various data managers who will need to integrate corresponding data about the person. For example, if the Regions plugin is being used, it requires that every person has a region assignment and thus a RegionId will need to be included in the PersonConstructionData. The people data manager does not understand this auxiliary data but simply repackages it into the PersonImminentAdditionEvent. The event is released and all the relevant data managers take what they need from the data stored in the event to fully initialize the state of the person. Once all data managers have initialized the person, the people data manager releases the PersonAdditionEvent and actors/reports, will now see that the new person has been added to the simulation and will have access to the person’s full initialized complement of data.
In summary, the general convention is:
- imminent addition event
- used by data managers to piecemeal add an item’s details
- ignored by actors and reports
- addition event
- ignored by data managers
- used by actors and reports to integrate the addition now that all the details are in place
- imminent removal event
- ignored by data managers
- used by actors to have a last chance to reference details on the item
- removal event
- used by data managers to fully remove all stored data on the item
- ignored by the actors since the item will be fully removed
7.5 Example Code (Lesson 14)
Example_14.java shows the use of the people plugin. The example includes four plugins:
- People plugin – (GCM core plugin) used to manage people
- Stochastics Plugin – (GCM plugin) provides random number generation
- Model plugin – (local plugin) used to introduce two actors that will
- add/remove people
- vaccinate people
- Vaccine plugin – (local plugin) used to track vaccinations for each person
The example’s main method starts in Code Block 7.1 by establishing two reports:
- The population trace report simply lists the additions and deletions of people by time. The report is managed by the PopulationTraceReport and is added to the simulation by the model plugin.
- The vaccination report shows a daily accounting of the number of people having 0, 1…6+ vaccinations. The report is managed by the VaccineReport class added by the vaccine plugin.
The main method continues by creating the people plugin and initializing it with 10 people. Note that the people will have id values of 1, 3, 5, … ,19 showing that any set of non-negative values are acceptable. The stochastics plugin is next and is initialized with a seed value. We will be controlling the random seed values via a dimension as presented in Code Block 7.3. As a result, the experiment will have 5 scenarios, with each scenario differing in only the random seed value that starts the simulation.
// create the people plugin with an initial population of ten people,
// numbered 1, 3, 5,...,19
.Builder peoplePluginDataBuilder = PeoplePluginData.builder();
PeoplePluginDatafor (int i = 0; i < 10; i++) {
= new PersonId(i * 2 + 1);
PersonId personId .addPersonRange(new PersonRange(personId.getValue(), personId.getValue()));
peoplePluginDataBuilder}
= peoplePluginDataBuilder.build();
PeoplePluginData peoplePluginData = PeoplePlugin.getPeoplePlugin(peoplePluginData);
Plugin peoplePlugin
// create the stochastics plugin and build a dimension with 5 seed
// values
= WellState.builder().setSeed(463390897335624435L).build();
WellState wellState = StochasticsPluginData.builder().setMainRNGState(wellState)
StochasticsPluginData stochasticsPluginData .build();
= StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData);
Plugin stochasticsPlugin
Dimension stochasticsDimension = getStochasticsDimension(5, 8265427588292179209L);
// create the vaccine and model plugins
= VaccinePlugin.getVaccinePlugin();
Plugin vaccinePlugin = ModelPlugin.getModelPlugin();
Plugin modelPlugin
.builder()//
Experiment.addPlugin(modelPlugin)//
.addPlugin(peoplePlugin)//
.addPlugin(stochasticsPlugin)//
.addPlugin(vaccinePlugin)//
.addExperimentContextConsumer(nioReportItemHandler)//
.addDimension(stochasticsDimension)//
.build()//
.execute();//
}
private static Dimension getStochasticsDimension(int replicationCount, long seed) {
.Builder builder = FunctionalDimensionData.builder();//
FunctionalDimensionData
= RandomGeneratorProvider.getRandomGenerator(seed);
RandomGenerator randomGenerator
List<Long> seedValues = new ArrayList<>();
for (int i = 0; i < replicationCount; i++) {
.add(randomGenerator.nextLong());
seedValues}
.range(0, seedValues.size()).forEach((i) -> {
IntStream.addValue("Level_" + i, (context) -> {
builder.Builder stochasticsPluginDataBuilder = context
StochasticsPluginData.getPluginDataBuilder(StochasticsPluginData.Builder.class);
long seedValue = seedValues.get(i);
= WellState.builder().setSeed(seedValue).build();
WellState wellState .setMainRNGState(wellState);
stochasticsPluginDataBuilder
ArrayList<String> result = new ArrayList<>();
.add(Integer.toString(i));
result.add(Long.toString(seedValue) + "L");
result
return result;
});//
});
.addMetaDatum("seed_index");//
builder.addMetaDatum("seed_value");//
builder
= builder.build();
FunctionalDimensionData functionalDimensionData return new FunctionalDimension(functionalDimensionData);
}
There are two actors provided by the model plugin. The first is the PopulationManager (Code Block 7.4) that upon its initialization plans 100 future actions to randomly remove (10% chance) or add (90% chance) people to the simulation. For people who are added, an initial vaccination count is included in the request to add the person so that the vaccine data manager can set the proper count.
public void init(ActorContext actorContext) {
= actorContext.getDataManager(StochasticsDataManager.class);
StochasticsDataManager stochasticsDataManager = stochasticsDataManager.getRandomGenerator();
RandomGenerator randomGenerator double planTime = randomGenerator.nextDouble();
for (int i = 0; i < 100; i++) {
.addPlan((c) -> {
actorContext= c.getDataManager(PeopleDataManager.class);
PeopleDataManager peopleDataManager if (randomGenerator.nextDouble() < 0.1) {
List<PersonId> people = peopleDataManager.getPeople();
if (!people.isEmpty()) {
= people.get(randomGenerator.nextInt(people.size()));
PersonId personId .removePerson(personId);
peopleDataManager}
} else {
int intialVaccineCount = randomGenerator.nextInt(3);
= new VaccineInitialization(intialVaccineCount);
VaccineInitialization vaccineInitialization = PersonConstructionData.builder()//
PersonConstructionData personConstructionData .add(vaccineInitialization)//
.build();
.addPerson(personConstructionData);
peopleDataManager}
}, planTime);
+= randomGenerator.nextDouble();
planTime }
}
Code Block 7.5 shows the second actor, the Vaccinator. It plans 300 vaccination actions over a period of approximately 100 days, selecting a random person to vaccinate each time. There is no limit to the number of vaccinations a person can have and we would expect that some people will have a relatively high number of vaccinations in the vaccine report.
public void init(ActorContext actorContext) {
= actorContext.getDataManager(StochasticsDataManager.class);
StochasticsDataManager stochasticsDataManager = stochasticsDataManager.getRandomGenerator();
RandomGenerator randomGenerator double planTime = randomGenerator.nextDouble();
for (int i = 0; i < 300; i++) {
.addPlan((c) -> {
actorContext= c.getDataManager(PeopleDataManager.class);
PeopleDataManager peopleDataManager = c.getDataManager(VaccinationDataManager.class);
VaccinationDataManager vaccinationDataManager List<PersonId> people = peopleDataManager.getPeople();
if (!people.isEmpty()) {
= people.get(randomGenerator.nextInt(people.size()));
PersonId personId .vaccinatePerson(personId);
vaccinationDataManager}
}, planTime);
+= randomGenerator.nextDouble() / 3;
planTime }
}
7.6 Interacting with the addition and removal events
The remaining code blocks will focus on the handling of the four person addition and removal events in the vaccine data manager and the population trace report. The vaccine report is periodic and does not subscribe to any events and is left for the reader to examine.
Following the general conventions above, the vaccine data manager subscribes to the PersonRemovalEvent and the PersonImminentAdditionEvent during its initialization in Code Block 7.6.
public void init(DataManagerContext dataManagerContext) {
super.init(dataManagerContext);
.subscribe(PersonRemovalEvent.class, this::handlePersonRemovalEvent);
dataManagerContext.subscribe(PersonImminentAdditionEvent.class, this::handlePersonImminentAdditionEvent);
dataManagerContext= dataManagerContext.getDataManager(PeopleDataManager.class);
personDataManager this.dataManagerContext = dataManagerContext;
for (PersonId personId : personDataManager.getPeople()) {
.put(personId, new MutableInteger());
vaccinationCounts}
.subscribe(VaccinationMutationEvent.class, this::handleVaccinationMutationEvent);
dataManagerContext}
The vaccine data manager uses a simple map from person id to a counter to track the number of vaccinations for each person:
private Map<PersonId, MutableInteger> vaccinationCounts = new LinkedHashMap<>();
The subscriptions above refer to the local methods of the vaccine data manager in Code Block 7.8. Handling the removal of a person is simple; the person id dropped from the map. Handling the addition requires that the manager try to locate a VaccinationInitialization object (which is just a wrapper around and integer count) contained in the construction. If the VaccinationInitialization is present, then the manager further validates the count is not negative.
private void handlePersonRemovalEvent(DataManagerContext dataManagerContext,
) {
PersonRemovalEvent personRemovalEvent= personRemovalEvent.personId();
PersonId personId .remove(personId);
vaccinationCounts}
private void handlePersonImminentAdditionEvent(DataManagerContext dataManagerContext,
) {
PersonImminentAdditionEvent personImminentAdditionEvent= personImminentAdditionEvent.personId();
PersonId personId validateNewPersonId(personId);
= new MutableInteger();
MutableInteger mutableInteger .put(personId, mutableInteger);
vaccinationCounts<VaccineInitialization> optional = personImminentAdditionEvent//
Optional.personConstructionData()//
.getValue(VaccineInitialization.class);
if (optional.isPresent()) {
= optional.get();
VaccineInitialization vaccineInitialization int vaccineCount = vaccineInitialization.getVaccineCount();
validateInitialVaccineCount(vaccineCount);
.setValue(vaccineCount);
mutableInteger}
}
7.7 Inspecting the output
Figure 7.1 shows the population trace report spanning the five scenarios and 500 additions and removals of people. In Figure 7.2 we have the vaccination report showing the number of people having from 0 to 6+ vaccinations over each day of the simulation across the five scenarios. As expected, the number of people having six or more vaccinations starts out at zero and monotonically increases as the days progress.
scenario | seed_index | seed_value | time | personId | action |
---|---|---|---|---|---|
0 | 0 | 1126862960420803077L | 0.0000000 | 1 | ADDITION |
0 | 0 | 1126862960420803077L | 0.0000000 | 3 | ADDITION |
0 | 0 | 1126862960420803077L | 0.0000000 | 5 | ADDITION |
0 | 0 | 1126862960420803077L | 0.0000000 | 7 | ADDITION |
0 | 0 | 1126862960420803077L | 0.0000000 | 9 | ADDITION |
0 | 0 | 1126862960420803077L | 0.0000000 | 11 | ADDITION |
0 | 0 | 1126862960420803077L | 0.0000000 | 13 | ADDITION |
0 | 0 | 1126862960420803077L | 0.0000000 | 15 | ADDITION |
0 | 0 | 1126862960420803077L | 0.0000000 | 17 | ADDITION |
0 | 0 | 1126862960420803077L | 0.0000000 | 19 | ADDITION |
0 | 0 | 1126862960420803077L | 0.8335755 | 19 | REMOVAL |
0 | 0 | 1126862960420803077L | 1.2826070 | 20 | ADDITION |
0 | 0 | 1126862960420803077L | 1.6263299 | 21 | ADDITION |
… | |||||
1 | 1 | -4486033808643580070L | 0.0000000 | 17 | ADDITION |
1 | 1 | -4486033808643580070L | 0.0000000 | 19 | ADDITION |
1 | 1 | -4486033808643580070L | 0.0667491 | 20 | ADDITION |
1 | 1 | -4486033808643580070L | 0.9322293 | 21 | ADDITION |
1 | 1 | -4486033808643580070L | 1.6514183 | 22 | ADDITION |
1 | 1 | -4486033808643580070L | 2.1177839 | 23 | ADDITION |
1 | 1 | -4486033808643580070L | 2.2623884 | 24 | ADDITION |
1 | 1 | -4486033808643580070L | 2.4082708 | 25 | ADDITION |
1 | 1 | -4486033808643580070L | 2.8132398 | 26 | ADDITION |
1 | 1 | -4486033808643580070L | 2.9103862 | 27 | ADDITION |
1 | 1 | -4486033808643580070L | 3.1314042 | 28 | ADDITION |
1 | 1 | -4486033808643580070L | 3.9782907 | 29 | ADDITION |
1 | 1 | -4486033808643580070L | 4.8481078 | 30 | ADDITION |
1 | 1 | -4486033808643580070L | 5.7753568 | 31 | ADDITION |
1 | 1 | -4486033808643580070L | 6.0714214 | 32 | ADDITION |
1 | 1 | -4486033808643580070L | 6.4493810 | 33 | ADDITION |
… | |||||
4 | 4 | 2435395143614485495L | 44.8710482 | 100 | ADDITION |
4 | 4 | 2435395143614485495L | 45.0533288 | 101 | ADDITION |
4 | 4 | 2435395143614485495L | 45.1289192 | 102 | ADDITION |
4 | 4 | 2435395143614485495L | 45.5197380 | 103 | ADDITION |
4 | 4 | 2435395143614485495L | 45.8369307 | 104 | ADDITION |
4 | 4 | 2435395143614485495L | 46.2957569 | 105 | ADDITION |
4 | 4 | 2435395143614485495L | 46.8744186 | 106 | ADDITION |
4 | 4 | 2435395143614485495L | 47.3473702 | 107 | ADDITION |
4 | 4 | 2435395143614485495L | 48.3166466 | 108 | ADDITION |
4 | 4 | 2435395143614485495L | 49.2053410 | 109 | ADDITION |
4 | 4 | 2435395143614485495L | 49.2524537 | 110 | ADDITION |
4 | 4 | 2435395143614485495L | 49.5378229 | 111 | ADDITION |
scenario | seed_index | seed_value | day | count_0 | count_1 | count_2 | count_3 | count_4 | count_5 | count_6+ |
---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 1126862960420803077L | 0 | 10 | 0 | 0 | 0 | 0 | 0 | 0 |
0 | 0 | 1126862960420803077L | 1 | 7 | 2 | 0 | 0 | 0 | 0 | 0 |
0 | 0 | 1126862960420803077L | 2 | 4 | 5 | 2 | 0 | 0 | 0 | 0 |
0 | 0 | 1126862960420803077L | 3 | 2 | 3 | 8 | 0 | 0 | 0 | 0 |
0 | 0 | 1126862960420803077L | 4 | 1 | 2 | 10 | 0 | 1 | 0 | 0 |
0 | 0 | 1126862960420803077L | 5 | 2 | 3 | 6 | 3 | 3 | 0 | 0 |
0 | 0 | 1126862960420803077L | 6 | 1 | 5 | 5 | 3 | 2 | 2 | 0 |
0 | 0 | 1126862960420803077L | 7 | 0 | 7 | 4 | 4 | 2 | 3 | 0 |
0 | 0 | 1126862960420803077L | 8 | 0 | 7 | 6 | 5 | 2 | 2 | 1 |
… | ||||||||||
0 | 0 | 1126862960420803077L | 52 | 6 | 13 | 13 | 7 | 10 | 9 | 22 |
0 | 0 | 1126862960420803077L | 53 | 7 | 13 | 15 | 7 | 10 | 9 | 22 |
0 | 0 | 1126862960420803077L | 54 | 7 | 13 | 16 | 7 | 10 | 9 | 22 |
1 | 1 | -4486033808643580070L | 0 | 10 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 1 | -4486033808643580070L | 1 | 9 | 3 | 0 | 0 | 0 | 0 | 0 |
1 | 1 | -4486033808643580070L | 2 | 5 | 6 | 2 | 0 | 0 | 0 | 0 |
1 | 1 | -4486033808643580070L | 3 | 5 | 8 | 4 | 1 | 0 | 0 | 0 |
1 | 1 | -4486033808643580070L | 4 | 5 | 6 | 5 | 3 | 1 | 0 | 0 |
1 | 1 | -4486033808643580070L | 5 | 4 | 6 | 6 | 2 | 3 | 0 | 0 |
1 | 1 | -4486033808643580070L | 6 | 2 | 6 | 7 | 3 | 4 | 0 | 0 |
1 | 1 | -4486033808643580070L | 7 | 2 | 4 | 11 | 4 | 4 | 0 | 0 |
… | ||||||||||
3 | 3 | -821383327301461075L | 42 | 5 | 13 | 15 | 9 | 8 | 10 | 15 |
3 | 3 | -821383327301461075L | 43 | 5 | 13 | 15 | 9 | 8 | 11 | 15 |
3 | 3 | -821383327301461075L | 44 | 6 | 12 | 15 | 10 | 8 | 9 | 17 |
3 | 3 | -821383327301461075L | 45 | 6 | 13 | 15 | 9 | 8 | 7 | 19 |
3 | 3 | -821383327301461075L | 46 | 6 | 13 | 15 | 8 | 8 | 7 | 20 |
3 | 3 | -821383327301461075L | 47 | 6 | 14 | 17 | 5 | 11 | 6 | 21 |
3 | 3 | -821383327301461075L | 48 | 5 | 14 | 17 | 6 | 10 | 5 | 22 |
3 | 3 | -821383327301461075L | 49 | 5 | 13 | 18 | 7 | 9 | 6 | 22 |
… | ||||||||||
4 | 4 | 2435395143614485495L | 42 | 11 | 12 | 11 | 10 | 12 | 7 | 19 |
4 | 4 | 2435395143614485495L | 43 | 9 | 13 | 12 | 9 | 11 | 8 | 20 |
4 | 4 | 2435395143614485495L | 44 | 7 | 14 | 11 | 10 | 10 | 8 | 21 |
4 | 4 | 2435395143614485495L | 45 | 7 | 16 | 10 | 11 | 9 | 7 | 23 |
4 | 4 | 2435395143614485495L | 46 | 7 | 16 | 13 | 10 | 11 | 6 | 24 |
4 | 4 | 2435395143614485495L | 47 | 8 | 14 | 15 | 10 | 11 | 7 | 24 |
4 | 4 | 2435395143614485495L | 48 | 9 | 13 | 15 | 11 | 11 | 7 | 24 |
4 | 4 | 2435395143614485495L | 49 | 9 | 13 | 15 | 12 | 11 | 7 | 24 |
4 | 4 | 2435395143614485495L | 50 | 12 | 13 | 15 | 12 | 11 | 7 | 24 |