InfectionEventHandler.java
/*-
* #%L
* MATSim Episim
* %%
* Copyright (C) 2020 matsim-org
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.matsim.episim;
import com.google.common.collect.ImmutableMap;
import com.google.inject.*;
import com.google.inject.name.Names;
import com.google.inject.util.Types;
import com.typesafe.config.ConfigFactory;
import it.unimi.dsi.fastutil.objects.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.IdMap;
import org.matsim.api.core.v01.Scenario;
import org.matsim.api.core.v01.events.*;
import org.matsim.api.core.v01.population.Person;
import org.matsim.core.api.internal.HasPersonId;
import org.matsim.core.config.Config;
import org.matsim.core.config.ConfigUtils;
import org.matsim.core.utils.collections.Tuple;
import org.matsim.episim.events.*;
import org.matsim.episim.model.*;
import org.matsim.episim.model.activity.ActivityParticipationModel;
import org.matsim.episim.model.testing.TestingModel;
import org.matsim.episim.model.vaccination.VaccinationModel;
import org.matsim.episim.policy.Restriction;
import org.matsim.episim.policy.ShutdownPolicy;
import org.matsim.facilities.ActivityFacility;
import org.matsim.run.AnalysisCommand;
import org.matsim.utils.objectattributes.attributable.Attributes;
import org.matsim.vehicles.Vehicle;
import java.io.*;
import java.nio.file.Path;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutorService;
import java.util.function.Function;
import static org.matsim.episim.EpisimUtils.*;
/**
* Main event handler of episim.
* It consumes the events of a standard MATSim run and puts {@link EpisimPerson}s into {@link EpisimContainer}s during their activity.
* At the end of activities an {@link ContactModel} is executed and also a {@link ProgressionModel} at the end of the day.
* See {@link EpisimModule} for which components may be substituted.
* <p>
* This handler should be used in conjunction with a {@link ReplayHandler}, which filters and preprocesses events.
* For performance reasons it is not used with the {@link org.matsim.core.api.experimental.events.EventsManager}.
*/
public final class InfectionEventHandler implements Externalizable {
// Some notes:
// * Especially if we repeat the same events file, then we do not have complete mixing. So it may happen that only some subpopulations gets infected.
// * However, if with infection proba=1 almost everybody gets infected, then in our current setup (where infected people remain in the iterations),
// this will also happen with lower probabilities, albeit slower. This is presumably the case that we want to investigate.
// * We seem to be getting two different exponential spreading rates. With infection proba=1, the crossover is (currently) around 15h.
// TODO
// * yyyyyy There are now some things that depend on ID conventions. We should try to replace them. This presumably would mean to interpret
// additional events. Those would need to be prepared for the "reduced" files. kai, mar'20
private static final Logger log = LogManager.getLogger(InfectionEventHandler.class);
/**
* Injector instance.
*/
private final Injector injector;
/**
* List of trajectory handlers that can be run in parallel.
*/
private final List<TrajectoryHandler> handlers = new ArrayList<>();
private final Map<Id<Person>, EpisimPerson> personMap = new IdMap<>(Person.class);
private final Map<Id<Vehicle>, EpisimVehicle> vehicleMap = new IdMap<>(Vehicle.class);
private final Map<Id<ActivityFacility>, EpisimFacility> pseudoFacilityMap = new IdMap<>(ActivityFacility.class,
// the number of facility ids is not known beforehand, so we use this as initial estimate
(int) (Id.getNumberOfIds(Vehicle.class) * 1.3));
/**
* Maps activity type to its parameter.
* This can be an identity map because the strings are canonicalized by the {@link ReplayHandler}.
*/
private final Map<String, EpisimConfigGroup.InfectionParams> paramsMap = new IdentityHashMap<>();
/**
* Holds the current restrictions in place for all the activities.
*/
private final Map<String, Restriction> restrictions;
/**
* Policy that will be enforced at the end of each day.
*/
private final ShutdownPolicy policy;
/**
* Progress of the sickness at the end of the day.
*/
private final ProgressionModel progressionModel;
/**
* Progress of antibodies.
*/
private final AntibodyModel antibodyModel;
/**
* Handle initial infections.
*/
private final InitialInfectionHandler initialInfections;
/**
* Handle vaccinations.
*/
private final VaccinationModel vaccinationModel;
/**
* Activity participation.
*/
private final ActivityParticipationModel activityParticipationModel;
private final TestingModel testingModel;
/**
* Scenario with population information.
*/
private final Scenario scenario;
/**
* Executors for trajectories.
*/
private final ExecutorService executor;
private final Config config;
private final EpisimConfigGroup episimConfig;
private final TracingConfigGroup tracingConfig;
private final VaccinationConfigGroup vaccinationConfig;
private final EpisimReporting reporting;
private final SplittableRandom rnd;
/**
* Local random, e.g. used for person initialization.
*/
private final SplittableRandom localRnd;
private boolean init = false;
private int iteration = 0;
/**
* Most recent infection report for all persons.
*/
private EpisimReporting.InfectionReport report;
/**
* Installed simulation listeners.
*/
private Set<SimulationListener> listener;
/**
* Set of additional vaccination strategies.
*/
private Set<VaccinationModel> vaccinations;
@Inject
public InfectionEventHandler(Injector injector, SplittableRandom rnd) {
this.injector = injector;
this.rnd = rnd;
this.config = injector.getInstance(Config.class);
this.episimConfig = ConfigUtils.addOrGetModule(config, EpisimConfigGroup.class);
this.tracingConfig = ConfigUtils.addOrGetModule(config, TracingConfigGroup.class);
this.vaccinationConfig = ConfigUtils.addOrGetModule(config, VaccinationConfigGroup.class);
this.scenario = injector.getInstance(Scenario.class);
this.policy = injector.getInstance(ShutdownPolicy.class);
this.restrictions = episimConfig.createInitialRestrictions();
this.reporting = injector.getInstance(EpisimReporting.class);
this.localRnd = new SplittableRandom(65536); // fixed seed, because it should not change between snapshots
this.progressionModel = injector.getInstance(ProgressionModel.class);
this.antibodyModel = injector.getInstance(AntibodyModel.class);
this.initialInfections = injector.getInstance(InitialInfectionHandler.class);
this.initialInfections.setInfectionsLeft(episimConfig.getInitialInfections());
this.vaccinationModel = injector.getInstance(VaccinationModel.class);
this.activityParticipationModel = injector.getInstance(ActivityParticipationModel.class);
this.testingModel = injector.getInstance(TestingModel.class);
this.executor = injector.getInstance(ExecutorService.class);
}
/**
* Returns the last {@link EpisimReporting.InfectionReport}.
*/
public EpisimReporting.InfectionReport getReport() {
return report;
}
/**
* Returns true if more iterations won't change the results anymore and the simulation is finished.
*/
public boolean isFinished() {
return iteration > 0 && !progressionModel.canProgress(report);
}
public void finish() {
executor.shutdown();
}
/**
* Initializes all needed data structures before the simulation can start.
* This *always* needs to be called before starting.
*
* @param events All events in the simulation
*/
void init(Map<DayOfWeek, List<Event>> events) {
iteration = 0;
updateEvents(events);
policy.init(episimConfig.getStartDate(), ImmutableMap.copyOf(this.restrictions));
// Clear time-use after first iteration
personMap.values().forEach(p -> p.getSpentTime().clear());
personMap.values().forEach(EpisimPerson::initParticipation);
// init person vaccination compliance sorted by age descending
personMap.values().stream()
.sorted(Comparator.comparingInt(p -> ((EpisimPerson) p).getAgeOrDefault(-1)).reversed()
.thenComparing(p -> ((EpisimPerson) p).getPersonId()))
.forEach(p -> {
Double compliance = EpisimUtils.findValidEntry(vaccinationConfig.getCompliancePerAge(), 1.0, p.getAgeOrDefault(-1));
p.setVaccinable(localRnd.nextDouble() < compliance);
});
listener = (Set<SimulationListener>) injector.getInstance(Key.get(Types.setOf(SimulationListener.class)));
vaccinations = (Set<VaccinationModel>) injector.getInstance(Key.get(Types.setOf(VaccinationModel.class)));
for (SimulationListener s : listener) {
log.info("Executing simulation init listener {}", s.toString());
s.init(localRnd, personMap, pseudoFacilityMap, vehicleMap);
}
vaccinationModel.init(localRnd, personMap, pseudoFacilityMap, vehicleMap);
for (SimulationListener s : vaccinations) {
log.info("Executing vaccination init listener {}", s.toString());
s.init(localRnd, personMap, pseudoFacilityMap, vehicleMap);
}
createTrajectoryHandlers();
init = true;
}
/**
* Update events data and internal person data structure.
*
* @param events
*/
void updateEvents(Map<DayOfWeek, List<Event>> events) {
Object2IntMap<EpisimContainer<?>> groupSize = new Object2IntOpenHashMap<>();
Object2IntMap<EpisimContainer<?>> totalUsers = new Object2IntOpenHashMap<>();
Object2IntMap<EpisimContainer<?>> maxGroupSize = new Object2IntOpenHashMap<>();
// This is used to distribute the containers to the different ReplayEventTasks
List<Tuple<EpisimContainer<?>, Double>> estimatedLoad = new LinkedList<>();
Map<EpisimContainer<?>, Object2IntMap<String>> activityUsage = new HashMap<>();
Map<List<Event>, DayOfWeek> sameDay = new IdentityHashMap<>(7);
this.personMap.values().forEach(EpisimPerson::resetTrajectory);
for (Map.Entry<DayOfWeek, List<Event>> entry : events.entrySet()) {
DayOfWeek day = entry.getKey();
List<Event> eventsForDay = entry.getValue();
if (sameDay.containsKey(eventsForDay)) {
DayOfWeek same = sameDay.get(eventsForDay);
log.info("Init Day {} same as {}", day, same);
this.personMap.values().forEach(p -> p.duplicateDay(day, same));
continue;
}
log.info("Init day {}", day);
this.personMap.values().forEach(p -> p.setStartOfDay(day));
for (Event event : eventsForDay) {
EpisimPerson person = null;
EpisimFacility facility = null;
// Add all person and facilities
if (event instanceof HasPersonId) {
person = this.personMap.computeIfAbsent(((HasPersonId) event).getPersonId(), this::createPerson);
// If a person was added late, previous days are initialized at home
for (int i = 1; i < day.getValue(); i++) {
DayOfWeek it = DayOfWeek.of(i);
if (!person.hasActivity(it)) {
person.setStartOfDay(it);
Id<ActivityFacility> homeId = createHomeFacility(person).getContainerId();
person.setFirstFacilityId(homeId, it);
person.setLastFacilityId(homeId, it, true);
EpisimConfigGroup.InfectionParams home = paramsMap.computeIfAbsent("home", this::createActivityType);
person.addToTrajectory(0, home, homeId);
person.setEndOfDay(it);
person.setStartOfDay(it.plus(1));
}
}
}
if (event instanceof HasFacilityId) {
Id<ActivityFacility> episimFacilityId = ((HasFacilityId) event).getFacilityId();
facility = this.pseudoFacilityMap.computeIfAbsent(episimFacilityId, EpisimFacility::new);
}
if (event instanceof ActivityStartEvent) {
String actType = ((ActivityStartEvent) event).getActType();
EpisimConfigGroup.InfectionParams act = paramsMap.computeIfAbsent(actType, this::createActivityType);
totalUsers.mergeInt(facility, 1, Integer::sum);
Id<ActivityFacility> facilityId = ((ActivityStartEvent) event).getFacilityId();
person.addToTrajectory(event.getTime(), act, facilityId);
person.setLastFacilityId(facility.getContainerId(), day, true);
} else if (event instanceof ActivityEndEvent) {
String actType = ((ActivityEndEvent) event).getActType();
EpisimConfigGroup.InfectionParams act = paramsMap.computeIfAbsent(actType, this::createActivityType);
activityUsage.computeIfAbsent(facility, k -> new Object2IntOpenHashMap<>()).mergeInt(actType, 1, Integer::sum);
// if this is the first event, container is saved and trajectory element created
if (!person.hasActivity(day)) {
Id<ActivityFacility> facilityId = ((ActivityEndEvent) event).getFacilityId();
person.addToTrajectory(0, act, facilityId);
person.setFirstFacilityId(facility.getContainerId(), day);
}
// person is not in this container anymore
person.setLastFacilityId(facility.getContainerId(), day, false);
}
if (event instanceof PersonEntersVehicleEvent) {
EpisimVehicle vehicle = this.vehicleMap.computeIfAbsent(((PersonEntersVehicleEvent) event).getVehicleId(), EpisimVehicle::new);
maxGroupSize.mergeInt(vehicle, groupSize.mergeInt(vehicle, 1, Integer::sum), Integer::max);
totalUsers.mergeInt(vehicle, 1, Integer::sum);
person.setStaysInContainer(day, false);
} else if (event instanceof PersonLeavesVehicleEvent) {
EpisimVehicle vehicle = this.vehicleMap.computeIfAbsent(((PersonLeavesVehicleEvent) event).getVehicleId(), EpisimVehicle::new);
groupSize.mergeInt(vehicle, -1, Integer::sum);
activityUsage.computeIfAbsent(vehicle, k -> new Object2IntOpenHashMap<>()).mergeInt("tr", 1, Integer::sum);
// vehicle don't count as end of day containers
person.setStaysInContainer(day, false);
}
}
int cnt = 0;
for (EpisimPerson person : this.personMap.values()) {
// person that didn't move will be put at home the whole day
if (!person.hasActivity(day)) {
person.setStartOfDay(day);
EpisimConfigGroup.InfectionParams home = paramsMap.computeIfAbsent("home", this::createActivityType);
EpisimFacility facility = createHomeFacility(person);
person.setFirstFacilityId(facility.getContainerId(), day);
person.setLastFacilityId(facility.getContainerId(), day, true);
person.addToTrajectory(0, home, facility.getContainerId());
cnt++;
}
person.setEndOfDay(day);
}
log.info("Persons stationary on {}: {} ({}%)", day, cnt, cnt * 100.0 / personMap.size());
sameDay.put(eventsForDay, day);
}
insertStationaryAgents();
// Add missing facilities, with only stationary agents
for (EpisimFacility facility : pseudoFacilityMap.values()) {
if (!activityUsage.containsKey(facility)) {
Object2IntOpenHashMap<String> act = new Object2IntOpenHashMap<>();
act.put("home", facility.getPersons().size());
activityUsage.put(facility, act);
}
}
double now = EpisimUtils.getCorrectedTime(episimConfig.getStartOffset(), 0, iteration);
// Go through each day again to compute max group sizes
sameDay.clear();
for (Map.Entry<DayOfWeek, List<Event>> entry : events.entrySet()) {
DayOfWeek day = entry.getKey();
List<Event> eventsForDay = entry.getValue();
if (sameDay.containsKey(eventsForDay)) {
continue;
}
// Simulate the behaviour for unclosed trajectories
for (EpisimPerson person : personMap.values()) {
Id<ActivityFacility> first = person.getFirstFacilityId(day);
Id<ActivityFacility> last = person.getLastFacilityId(day.minus(1));
if (person.getStaysInContainer(day.minus(1))) {
if (pseudoFacilityMap.get(last).containsPerson(person))
pseudoFacilityMap.get(last).removePerson(person);
if (!pseudoFacilityMap.get(first).containsPerson(person))
pseudoFacilityMap.get(first).addPerson(person, now, person.getFirstActivity(day));
} else {
if (!pseudoFacilityMap.get(first).containsPerson(person))
pseudoFacilityMap.get(first).addPerson(person, now, person.getFirstActivity(day));
}
}
pseudoFacilityMap.forEach((k, v) -> maxGroupSize.mergeInt(v, v.getPersons().size(), Integer::max));
for (Event event : eventsForDay) {
if (event instanceof HasFacilityId && event instanceof HasPersonId) {
Id<ActivityFacility> episimFacilityId = ((HasFacilityId) event).getFacilityId();
EpisimFacility facility = pseudoFacilityMap.get(episimFacilityId);
EpisimPerson person = this.personMap.get(((HasPersonId) event).getPersonId());
// happens on filtered events that are not relevant
if (facility == null)
continue;
if (event instanceof ActivityStartEvent) {
if (!facility.containsPerson(person))
facility.addPerson(person, now, person.getActivity(day, event.getTime()));
maxGroupSize.mergeInt(facility, facility.getPersons().size(), Integer::max);
} else if (event instanceof ActivityEndEvent) {
if (facility.containsPerson(person))
facility.removePerson(person);
}
}
}
sameDay.put(eventsForDay, day);
}
pseudoFacilityMap.values().forEach(EpisimContainer::clearPersons);
// Put persons into their correct initial container
DayOfWeek startDay = EpisimUtils.getDayOfWeek(episimConfig, iteration);
for (EpisimPerson person : personMap.values()) {
if (person.getStaysInContainer(startDay)) {
EpisimFacility facility = pseudoFacilityMap.get(person.getLastFacilityId(startDay));
facility.addPerson(person, now, person.getLastActivity(startDay));
}
}
log.info("Computed max group sizes");
reporting.reportContainerUsage(maxGroupSize, totalUsers, activityUsage);
boolean useVehicles = !scenario.getVehicles().getVehicles().isEmpty();
log.info("Using capacity from vehicles file: {}", useVehicles);
// these always needs to be present
paramsMap.computeIfAbsent("tr", this::createActivityType);
paramsMap.computeIfAbsent("home", this::createActivityType);
// entry for undefined activity type
AbstractObject2IntMap.BasicEntry<String> undefined = new AbstractObject2IntMap.BasicEntry<>("undefined", -1);
for (Object2IntMap.Entry<EpisimContainer<?>> kv : maxGroupSize.object2IntEntrySet()) {
EpisimContainer<?> container = kv.getKey();
double scale = 1 / episimConfig.getSampleSize();
final int numUsers = totalUsers.getInt(container);
container.setTotalUsers((int) (numUsers * scale));
container.setMaxGroupSize((int) (kv.getIntValue() * scale));
estimatedLoad.add(Tuple.of(container, (double) numUsers * kv.getIntValue()));
Object2IntMap<String> usage = activityUsage.get(kv.getKey());
if (usage != null) {
Object2IntMap.Entry<String> max = usage.object2IntEntrySet().stream()
.reduce(undefined, (s1, s2) -> s1.getIntValue() > s2.getIntValue() ? s1 : s2);
if (max != undefined) {
// set container spaces to spaces of most used activity
EpisimConfigGroup.InfectionParams act = paramsMap.get(max.getKey());
if (act == null)
log.warn("No activity found for {}", max.getKey());
else
container.setNumSpaces(act.getSpacesPerFacility());
}
}
if (useVehicles && container instanceof EpisimVehicle) {
Id<Vehicle> vehicleId = Id.createVehicleId(container.getContainerId().toString());
Vehicle vehicle = scenario.getVehicles().getVehicles().get(vehicleId);
if (vehicle == null) {
log.warn("No type found for vehicleId={}; using capacity of 150.", vehicleId);
container.setTypicalCapacity(150);
} else {
int capacity = vehicle.getType().getCapacity().getStandingRoom() + vehicle.getType().getCapacity().getSeats();
container.setTypicalCapacity(capacity);
}
}
}
balanceContainersByLoad(estimatedLoad);
}
/**
* Called when a snapshot has been loaded.
*/
void onSnapshotLoaded(int iteration) {
// Listener and vaccinations should already be present
for (SimulationListener s : listener) {
s.onSnapshotLoaded(iteration, localRnd, personMap, pseudoFacilityMap, vehicleMap);
}
for (SimulationListener s : vaccinations) {
s.onSnapshotLoaded(iteration, localRnd, personMap, pseudoFacilityMap, vehicleMap);
}
}
/**
* Distribute the containers to the different ReplayEventTasks, by setting
* the taskId attribute of the containers to values between 0 and episimConfig.getThreds() - 1,
* so that the sum of numUsers * maxGroupSize has an even distribution
*/
private void balanceContainersByLoad(List<Tuple<EpisimContainer<?>, Double>> estimatedLoad) {
// We need the containers sorted by the load, with the highest load first.
// To get a deterministic distribution, we use the containerId for
// sorting the containers with the same estimatedLoad.
Comparator<Tuple<EpisimContainer<?>, Double>> loadComperator =
Comparator.<Tuple<EpisimContainer<?>, Double>, Double>comparing(
t -> t.getSecond(), Comparator.reverseOrder()).
thenComparing(t -> t.getFirst().getContainerId().toString());
Collections.sort(estimatedLoad, loadComperator);
final int numThreads = episimConfig.getThreads();
// the overall load of the containers assigned to the thread/taskId
final Double[] loadPerThread = new Double[numThreads];
for (int i = 0; i < numThreads; i++)
loadPerThread[i] = 0.0;
for (Tuple<EpisimContainer<?>, Double> tuple : estimatedLoad) {
// search for the thread/taskId with the minimal load
int useThread = 0;
Double minLoad = loadPerThread[0];
for (int i = 1; i < numThreads; i++) {
if (loadPerThread[i] < minLoad) {
useThread = i;
minLoad = loadPerThread[i];
}
}
// add the load to this thread and set the taskId for the container
loadPerThread[useThread] += tuple.getSecond();
tuple.getFirst().setTaskId(useThread);
}
}
/**
* Distribute the containers to the different ReplayEventTasks, using
* the hashCode of the containerId (the original distribution schema)
*/
private void balanceContainersByHash(List<Tuple<EpisimContainer<?>, Double>> estimatedLoad) {
for (Tuple<EpisimContainer<?>, Double> tuple : estimatedLoad) {
final EpisimContainer<?> container = tuple.getFirst();
final int useThread = Math.abs(container.getContainerId().hashCode()) % episimConfig.getThreads();
container.setTaskId(useThread);
}
}
/**
* Create handlers for executing th
*/
protected void createTrajectoryHandlers() {
log.info("Initializing {} trajectory handlers", episimConfig.getThreads());
for (int i = 0; i < episimConfig.getThreads(); i++) {
AbstractModule childModule = new AbstractModule() {
@Override
protected void configure() {
// the seed state is set later by this class
bind(SplittableRandom.class).toInstance(new SplittableRandom(rnd.nextLong()));
bind(TrajectoryHandler.class);
TypeLiteral<Map<Id<Person>, EpisimPerson>> pMap = new TypeLiteral<>() {
};
TypeLiteral<Map<Id<Vehicle>, InfectionEventHandler.EpisimVehicle>> vMap = new TypeLiteral<>() {
};
TypeLiteral<Map<Id<ActivityFacility>, InfectionEventHandler.EpisimFacility>> fMap = new TypeLiteral<>() {
};
bind(pMap).annotatedWith(Names.named("personMap")).toInstance(personMap);
bind(vMap).annotatedWith(Names.named("vehicleMap")).toInstance(vehicleMap);
bind(fMap).annotatedWith(Names.named("pseudoFacilityMap")).toInstance(pseudoFacilityMap);
}
};
// create child injector with separate instance of models
Injector inj = GuiceUtils.createCopiedInjector(injector, List.of(childModule), ContactModel.class, InfectionModel.class, FaceMaskModel.class);
TrajectoryHandler handler = inj.getInstance(TrajectoryHandler.class);
handlers.add(handler);
}
}
/**
* Create a new person and lookup attributes from scenario.
*/
private EpisimPerson createPerson(Id<Person> id) {
Person person = scenario.getPopulation().getPersons().get(id);
Attributes attrs;
if (person != null) {
attrs = person.getAttributes();
} else {
attrs = new Attributes();
}
boolean traceable = localRnd.nextDouble() < tracingConfig.getEquipmentRate();
return new EpisimPerson(id, attrs, traceable, reporting);
}
/**
* Creates the home facility of a person.
*/
private EpisimFacility createHomeFacility(EpisimPerson person) {
String homeId = (String) person.getAttributes().getAttribute("homeId");
if (homeId == null)
homeId = "home_of_" + person.getPersonId().toString();
Id<ActivityFacility> facilityId = Id.create(homeId, ActivityFacility.class);
// add facility that might not exist yet
return this.pseudoFacilityMap.computeIfAbsent(facilityId, EpisimFacility::new);
}
private EpisimConfigGroup.InfectionParams createActivityType(String actType) {
return episimConfig.selectInfectionParams(actType);
}
/**
* Insert agents that appear in the population, but not in the event file, into their home container.
*/
private void insertStationaryAgents() {
int inserted = 0;
int skipped = 0;
for (Person p : scenario.getPopulation().getPersons().values()) {
if (!personMap.containsKey(p.getId())) {
String homeId = (String) p.getAttributes().getAttribute("homeId");
if (homeId != null) {
Id<ActivityFacility> facilityId = Id.create(homeId, ActivityFacility.class);
EpisimFacility facility = pseudoFacilityMap.computeIfAbsent(facilityId, EpisimFacility::new);
EpisimPerson episimPerson = personMap.computeIfAbsent(p.getId(), this::createPerson);
// Person stays here the whole week
for (DayOfWeek day : DayOfWeek.values()) {
episimPerson.setFirstFacilityId(facilityId, day);
episimPerson.setLastFacilityId(facilityId, day, true);
episimPerson.setStartOfDay(day);
}
EpisimPerson.PerformedActivity home = episimPerson.addToTrajectory(0, paramsMap.get("home"), facilityId);
facility.addPerson(episimPerson, 0, home);
// set end index
for (DayOfWeek day : DayOfWeek.values()) {
episimPerson.setEndOfDay(day);
}
inserted++;
} else
skipped++;
}
}
if (skipped > 0)
log.warn("Ignored {} stationary agents, because of missing home ids", skipped);
log.info("Inserted {} stationary agents, total = {}", inserted, personMap.size());
}
public void reset(int iteration) {
// safety checks
if (!init)
throw new IllegalStateException(".init() was not called!");
if (iteration <= 0)
throw new IllegalArgumentException("Iteration must be larger 1!");
if (paramsMap.size() > 1000)
log.warn("Params map contains many entries. Activity types may not be .intern() Strings");
if (iteration == 1)
reporting.reportStart(episimConfig.getStartDate(), episimConfig.getStartFromImmunization());
double now = EpisimUtils.getCorrectedTime(episimConfig.getStartOffset(), 0, iteration);
LocalDate date = episimConfig.getStartDate().plusDays(iteration - 1);
reporting.reportCpuTime(iteration, "ProgressionModel", "start", -1);
progressionModel.setIteration(iteration);
progressionModel.beforeStateUpdates(personMap, iteration, this.report);
// Sum of antibodies
Object2DoubleMap<VirusStrain> antibodies = new Object2DoubleOpenHashMap<>();
for (EpisimPerson person : personMap.values()) {
progressionModel.updateState(person, iteration);
antibodyModel.updateAntibodies(person, iteration);
for (Object2DoubleMap.Entry<VirusStrain> kv : person.getAntibodies().object2DoubleEntrySet()) {
antibodies.mergeDouble(kv.getKey(), kv.getDoubleValue(), Double::sum);
}
}
// uncomment if you want immunization stats to be printed on a certain
// date or e.g. every month. This produces a lot of large files so use
// sparingly.
// if (date.getDayOfMonth() == 1) {
// reporting.reportDetailedPersonStats(date, personMap.values());
// }
reporting.reportCpuTime(iteration, "ProgressionModelParallel", "start", -2);
progressionModel.afterStateUpdates(personMap, iteration);
reporting.reportCpuTime(iteration, "ProgressionModelParallel", "finished", -2);
reporting.reportCpuTime(iteration, "ProgressionModel", "finished", -1);
reporting.reportCpuTime(iteration, "VaccinationModel", "start", -1);
// vaccination:
int available = EpisimUtils.findValidEntry(vaccinationConfig.getVaccinationCapacity(), -1, date);
vaccinationModel.handleVaccination(personMap, false, available > 0 ? (int) (available * episimConfig.getSampleSize()) : -1, date, iteration, now);
// re-vaccination:
available = EpisimUtils.findValidEntry(vaccinationConfig.getReVaccinationCapacity(), -1, date);
vaccinationModel.handleVaccination(personMap, true, available > 0 ? (int) (available * episimConfig.getSampleSize()) : -1, date, iteration, now);
// additional vaccinations:
for (VaccinationModel vaccination : vaccinations) {
vaccination.handleVaccination(personMap, date, iteration, now);
}
reporting.reportCpuTime(iteration, "VaccinationModel", "finished", -1);
this.iteration = iteration;
reporting.reportCpuTime(iteration, "HandleInfections", "start", -1);
Object2IntMap infected = this.initialInfections.handleInfections(personMap, iteration);
reporting.reportCpuTime(iteration, "HandleInfections", "finished", -1);
reporting.reportCpuTime(iteration, "Reporting", "start", -1);
Map<String, EpisimReporting.InfectionReport> reports = reporting.createReports(personMap.values(), iteration);
reporting.reportAntibodyLevel(antibodies, personMap.size(), iteration);
this.report = reports.get("total");
reporting.reporting(reports, iteration, report.date);
reporting.reportCpuTime(iteration, "ReportTimeUse", "start", -2);
reporting.reportTimeUse(restrictions.keySet(), personMap.values(), iteration, report.date);
reporting.reportCpuTime(iteration, "ReportTimeUse", "finished", -2);
reporting.reportDiseaseImport(infected, iteration, report.date);
ImmutableMap<String, Restriction> im = ImmutableMap.copyOf(this.restrictions);
policy.updateRestrictions(report, im);
reporting.reportCpuTime(iteration, "TestingModel", "start", -1);
DayOfWeek day = EpisimUtils.getDayOfWeek(episimConfig, iteration);
testingModel.setIteration(iteration);
testingModel.beforeStateUpdates(personMap, iteration, this.report);
activityParticipationModel.setRestrictionsForIteration(iteration, im);
for (EpisimPerson person : personMap.values()) {
// update person activity participation for the day
activityParticipationModel.updateParticipation(person, person.getActivityParticipation(),
person.getStartOfDay(day), person.getActivities(day));
testingModel.performTesting(person, iteration);
activityParticipationModel.applyQuarantine(person, person.getActivityParticipation(), person.getStartOfDay(day), person.getActivities(day));
}
reporting.reportCpuTime(iteration, "TestingModel", "finished", -1);
handlers.forEach(h -> {
h.setRestrictionsForIteration(iteration, im);
EpisimUtils.setSeed(h.getRnd(), rnd.nextLong());
});
reporting.reportRestrictions(restrictions, iteration, report.date);
reporting.reportCpuTime(iteration, "Reporting", "finished", -1);
for (SimulationListener l : listener) {
l.onIterationStart(iteration, date);
}
}
public Collection<EpisimPerson> getPersons() {
return Collections.unmodifiableCollection(personMap.values());
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeLong(EpisimUtils.getSeed(rnd));
out.writeInt(initialInfections.getInfectionsLeft());
out.writeInt(iteration);
out.writeInt(restrictions.size());
for (Map.Entry<String, Restriction> e : restrictions.entrySet()) {
writeChars(out, e.getKey());
writeChars(out, e.getValue().asMap().toString());
}
out.writeInt(personMap.size());
for (Map.Entry<Id<Person>, EpisimPerson> e : personMap.entrySet()) {
writeChars(out, e.getKey().toString());
e.getValue().write(out);
}
out.writeInt(vehicleMap.size());
for (Map.Entry<Id<Vehicle>, EpisimVehicle> e : vehicleMap.entrySet()) {
writeChars(out, e.getKey().toString());
e.getValue().write(out);
}
out.writeInt(pseudoFacilityMap.size());
for (Map.Entry<Id<ActivityFacility>, EpisimFacility> e : pseudoFacilityMap.entrySet()) {
writeChars(out, e.getKey().toString());
e.getValue().write(out);
}
}
@Override
public void readExternal(ObjectInput in) throws IOException {
long storedSeed = in.readLong();
if (episimConfig.getSnapshotSeed() == EpisimConfigGroup.SnapshotSeed.restore) {
EpisimUtils.setSeed(rnd, storedSeed);
} else if (episimConfig.getSnapshotSeed() == EpisimConfigGroup.SnapshotSeed.reseed) {
log.info("Reseeding snapshot with {}", config.global().getRandomSeed());
EpisimUtils.setSeed(rnd, config.global().getRandomSeed());
}
initialInfections.setInfectionsLeft(in.readInt());
iteration = in.readInt();
int r = in.readInt();
for (int i = 0; i < r; i++) {
String act = readChars(in);
restrictions.put(act, Restriction.fromConfig(ConfigFactory.parseString(readChars(in))));
}
int persons = in.readInt();
for (int i = 0; i < persons; i++) {
Id<Person> id = Id.create(readChars(in), Person.class);
personMap.get(id).read(in, personMap);
}
int vehicles = in.readInt();
for (int i = 0; i < vehicles; i++) {
Id<Vehicle> id = Id.create(readChars(in), Vehicle.class);
vehicleMap.get(id).read(in, personMap);
}
int container = in.readInt();
for (int i = 0; i < container; i++) {
Id<ActivityFacility> id = Id.create(readChars(in), ActivityFacility.class);
pseudoFacilityMap.get(id).read(in, personMap);
}
ImmutableMap<String, Restriction> im = ImmutableMap.copyOf(this.restrictions);
policy.restore(episimConfig.getStartDate().plusDays(iteration), im);
handlers.forEach(h -> h.setRestrictionsForIteration(iteration, im));
}
/**
* Execute trajectory events.
*
* @param day current day
* @param events events to execute
*/
void handleEvents(DayOfWeek day, List<Event> events) {
if (handlers.size() > 1) {
var futures = new CompletableFuture[handlers.size()];
for (int i = 0; i < handlers.size(); i++) {
ReplayEventsTask task = new ReplayEventsTask(handlers.get(i), events, i, handlers.size());
futures[i] = CompletableFuture.runAsync(task, executor);
}
try {
CompletableFuture.allOf(futures).join();
} catch (CompletionException e) {
log.error("A TrajectoryHandler caused the exception: ", e.getCause());
executor.shutdown();
throw e;
}
} else {
// single threaded task is run directly
ReplayEventsTask task = new ReplayEventsTask(handlers.get(0), events, 0, 1);
task.run();
}
// store the infections for a day
List<Event> infections = new ArrayList<>();
// "execute" collected infections
for (EpisimPerson person : personMap.values()) {
EpisimInfectionEvent e;
if ((e = person.checkInfection()) != null)
infections.add(e);
if (!person.getPotentialInfections().isEmpty()) {
infections.addAll(person.getPotentialInfections());
person.getPotentialInfections().clear();
}
}
// report infections in order
infections.stream().sorted()
.forEach(reporting::reportInfection);
int totalContacts = handlers.stream().mapToInt(TrajectoryHandler::getNumContacts).sum();
reporting.reportTotalContacts(totalContacts);
for (SimulationListener l : listener) {
l.onIterationEnd(iteration, episimConfig.getStartDate().plusDays(iteration - 1));
}
}
/**
* Read immunization history and init persons.
*/
void initImmunization(Path history) {
log.info("Reading immunization from {}", history);
InitialImmunizationHandler handler = new InitialImmunizationHandler(personMap,episimConfig, antibodyModel,progressionModel);
List<String> days = AnalysisCommand.forEachEvent(history, handler, true, handler);
if (handler.isContinueProcessingEvents()) {
throw new RuntimeException("Immunisation history is not long enough (only contains " + days.size() + " days)");
}
}
/**
* Container that is always a vehicle.
*/
public static final class EpisimVehicle extends EpisimContainer<Vehicle> {
EpisimVehicle(Id<Vehicle> vehicleId) {
super(vehicleId);
}
}
/**
* Container that is a facility and occurred during an activity.
*/
public static final class EpisimFacility extends EpisimContainer<ActivityFacility> {
EpisimFacility(Id<ActivityFacility> facilityId) {
super(facilityId);
}
}
}