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.
Code Block 7.1: The population trace and vaccination reports are associated with corresponding file names via the NIO report item handler.
public static void main(String[] args) throws IOException {
    if (args.length == 0) {
        throw new RuntimeException("One output directory argument is required");
    }
    Path outputDirectory = Paths.get(args[0]);
    if (!Files.exists(outputDirectory)) {
        Files.createDirectory(outputDirectory);
    } else {
        if (!Files.isDirectory(outputDirectory)) {
            throw new IOException("Provided path is not a directory");
        }
    }

    // reports
    NIOReportItemHandler nioReportItemHandler = //
            NIOReportItemHandler.builder()//
                    .addReport(ModelReportLabel.POPULATION_TRACE, //
                            outputDirectory.resolve("population_trace_report.csv"))//
                    .addReport(ModelReportLabel.VACCINATION, //
                            outputDirectory.resolve("vaccination_report.csv"))//
                    .build();

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.

Code Block 7.2: The various plugins are initialized with data and added to the experiment.
        // create the people plugin with an initial population of ten people,
        // numbered 1, 3, 5,...,19
        PeoplePluginData.Builder peoplePluginDataBuilder = PeoplePluginData.builder();
        for (int i = 0; i < 10; i++) {
            PersonId personId = new PersonId(i * 2 + 1);
            peoplePluginDataBuilder.addPersonRange(new PersonRange(personId.getValue(), personId.getValue()));
        }
        PeoplePluginData peoplePluginData = peoplePluginDataBuilder.build();
        Plugin peoplePlugin = PeoplePlugin.getPeoplePlugin(peoplePluginData);

        // create the stochastics plugin and build a dimension with 5 seed
        // values
        WellState wellState = WellState.builder().setSeed(463390897335624435L).build();
        StochasticsPluginData stochasticsPluginData = StochasticsPluginData.builder().setMainRNGState(wellState)
                .build();
        Plugin stochasticsPlugin = StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData);

        Dimension stochasticsDimension = getStochasticsDimension(5, 8265427588292179209L);

        // create the vaccine and model plugins
        Plugin vaccinePlugin = VaccinePlugin.getVaccinePlugin();
        Plugin modelPlugin = ModelPlugin.getModelPlugin();

        Experiment.builder()//
                .addPlugin(modelPlugin)//
                .addPlugin(peoplePlugin)//
                .addPlugin(stochasticsPlugin)//
                .addPlugin(vaccinePlugin)//
                .addExperimentContextConsumer(nioReportItemHandler)//
                .addDimension(stochasticsDimension)//
                .build()//
                .execute();//
    }
Code Block 7.3: The stochastics dimension contains levels for each replication value. Note that the generation of the random seed values occurs outside of the lambda code.
private static Dimension getStochasticsDimension(int replicationCount, long seed) {
    FunctionalDimensionData.Builder builder = FunctionalDimensionData.builder();//

    RandomGenerator randomGenerator = RandomGeneratorProvider.getRandomGenerator(seed);

    List<Long> seedValues = new ArrayList<>();
    for (int i = 0; i < replicationCount; i++) {
        seedValues.add(randomGenerator.nextLong());
    }

    IntStream.range(0, seedValues.size()).forEach((i) -> {
        builder.addValue("Level_" + i, (context) -> {
            StochasticsPluginData.Builder stochasticsPluginDataBuilder = context
                    .getPluginDataBuilder(StochasticsPluginData.Builder.class);
            long seedValue = seedValues.get(i);
            WellState wellState = WellState.builder().setSeed(seedValue).build();
            stochasticsPluginDataBuilder.setMainRNGState(wellState);

            ArrayList<String> result = new ArrayList<>();
            result.add(Integer.toString(i));
            result.add(Long.toString(seedValue) + "L");

            return result;
        });//
    });

    builder.addMetaDatum("seed_index");//
    builder.addMetaDatum("seed_value");//

    FunctionalDimensionData functionalDimensionData = builder.build();
    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.

Code Block 7.4: The population manager schedules 100 randomized actions to either add or remove people.
public void init(ActorContext actorContext) {
    StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);
    RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator();
    double planTime = randomGenerator.nextDouble();
    for (int i = 0; i < 100; i++) {
        actorContext.addPlan((c) -> {
            PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class);
            if (randomGenerator.nextDouble() < 0.1) {
                List<PersonId> people = peopleDataManager.getPeople();
                if (!people.isEmpty()) {
                    PersonId personId = people.get(randomGenerator.nextInt(people.size()));
                    peopleDataManager.removePerson(personId);
                }
            } else {
                int intialVaccineCount = randomGenerator.nextInt(3);
                VaccineInitialization vaccineInitialization = new VaccineInitialization(intialVaccineCount);
                PersonConstructionData personConstructionData = PersonConstructionData.builder()//
                        .add(vaccineInitialization)//
                        .build();
                peopleDataManager.addPerson(personConstructionData);
            }
        }, planTime);
        planTime += randomGenerator.nextDouble();
    }
}

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.

Code Block 7.5: The vaccinator administers 300 vaccine doses over 100 days.
public void init(ActorContext actorContext) {
    StochasticsDataManager stochasticsDataManager = actorContext.getDataManager(StochasticsDataManager.class);
    RandomGenerator randomGenerator = stochasticsDataManager.getRandomGenerator();
    double planTime = randomGenerator.nextDouble();
    for (int i = 0; i < 300; i++) {
        actorContext.addPlan((c) -> {
            PeopleDataManager peopleDataManager = c.getDataManager(PeopleDataManager.class);
            VaccinationDataManager vaccinationDataManager = c.getDataManager(VaccinationDataManager.class);
            List<PersonId> people = peopleDataManager.getPeople();
            if (!people.isEmpty()) {
                PersonId personId = people.get(randomGenerator.nextInt(people.size()));
                vaccinationDataManager.vaccinatePerson(personId);
            }
        }, planTime);
        planTime += randomGenerator.nextDouble() / 3;
    }
}

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.

Code Block 7.6: The vaccination data manager initializes by recording initial vaccine counts for each person and subscribing to person addition, person removal and person vaccination events.
public void init(DataManagerContext dataManagerContext) {
    super.init(dataManagerContext);
    dataManagerContext.subscribe(PersonRemovalEvent.class, this::handlePersonRemovalEvent);
    dataManagerContext.subscribe(PersonImminentAdditionEvent.class, this::handlePersonImminentAdditionEvent);
    personDataManager = dataManagerContext.getDataManager(PeopleDataManager.class);
    this.dataManagerContext = dataManagerContext;
    for (PersonId personId : personDataManager.getPeople()) {
        vaccinationCounts.put(personId, new MutableInteger());
    }
    dataManagerContext.subscribe(VaccinationMutationEvent.class, this::handleVaccinationMutationEvent);
}

The vaccine data manager uses a simple map from person id to a counter to track the number of vaccinations for each person:

Code Block 7.7: The vaccination 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.

Code Block 7.8: The vaccination manager removes people from its count tracking as needed. Newly added people may enter into the simulation with some vaccinations.
    private void handlePersonRemovalEvent(DataManagerContext dataManagerContext,
            PersonRemovalEvent personRemovalEvent) {
        PersonId personId = personRemovalEvent.personId();
        vaccinationCounts.remove(personId);
    }

    private void handlePersonImminentAdditionEvent(DataManagerContext dataManagerContext,
            PersonImminentAdditionEvent personImminentAdditionEvent) {
        PersonId personId = personImminentAdditionEvent.personId();
        validateNewPersonId(personId);
        MutableInteger mutableInteger = new MutableInteger();
        vaccinationCounts.put(personId, mutableInteger);
        Optional<VaccineInitialization> optional = personImminentAdditionEvent//
                .personConstructionData()//
                .getValue(VaccineInitialization.class);
        if (optional.isPresent()) {
            VaccineInitialization vaccineInitialization = optional.get();
            int vaccineCount = vaccineInitialization.getVaccineCount();
            validateInitialVaccineCount(vaccineCount);
            mutableInteger.setValue(vaccineCount);
        }
    }

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.

Figure 7.1: An excerpt of the population trace report.
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
Figure 7.2: An excerpt of the vaccination report.
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