EpisimPerson.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.annotations.Beta;
import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
import it.unimi.dsi.fastutil.doubles.DoubleList;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import it.unimi.dsi.fastutil.objects.Object2DoubleLinkedOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2DoubleMap;
import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap;
import org.matsim.api.core.v01.Id;
import org.matsim.api.core.v01.population.Person;
import org.matsim.episim.events.EpisimInfectionEvent;
import org.matsim.episim.events.EpisimInitialInfectionEvent;
import org.matsim.episim.events.EpisimPersonStatusEvent;
import org.matsim.episim.events.EpisimPotentialInfectionEvent;
import org.matsim.episim.model.VaccinationType;
import org.matsim.episim.model.VirusStrain;
import org.matsim.facilities.ActivityFacility;
import org.matsim.utils.objectattributes.attributable.Attributable;
import org.matsim.utils.objectattributes.attributable.Attributes;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.time.DayOfWeek;
import java.util.*;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import static org.matsim.episim.EpisimUtils.*;
/**
* Persons current state in the simulation.
*/
public final class EpisimPerson implements Immunizable, Attributable {
private final Id<Person> personId;
private final EpisimReporting reporting;
// This data structure is quite slow: log n costs, which should be constant...
private final Attributes attributes;
/**
* Whole trajectory over all days of the week.
* Entries contain the starting time of activities and the performed activity.
*/
private final List<PerformedActivity> trajectory = new ArrayList<>();
/**
* The position in the trajectory at the start for each day of the week.
*/
private final int[] startOfDay = new int[7];
/**
* The position in the trajectory for the end of the day.
*/
private final int[] endOfDay = new int[7];
/**
* The first visited {@link org.matsim.facilities.ActivityFacility} for each day.
* Can be null if person does not start in a container.
*/
private final Id<ActivityFacility>[] firstFacilityId = new Id[7];
/**
* The last visited {@link org.matsim.facilities.ActivityFacility} for each day.
* This is null if a person does not end its day in a container.
*/
private final Id<ActivityFacility>[] lastFacilityId = new Id[7];
// Fields above are initialized from the sim and not persisted
/**
* Whether person stays in container at the end of a day.
*/
private final boolean[] staysInContainer = new boolean[7];
/**
* Traced contacts with other persons.
*/
private final Object2DoubleMap<EpisimPerson> traceableContactPersons = new Object2DoubleLinkedOpenHashMap<>(4);
/**
* Stores first time of status changes to specific type.
*/
private final EnumMap<DiseaseStatus, Double> statusChanges = new EnumMap<>(DiseaseStatus.class);
/**
* Total spent time during activities.
*/
private final Object2DoubleMap<String> spentTime = new Object2DoubleOpenHashMap<>(4);
/**
* Activity participation of the current day. Same length as {@link #trajectory}
*/
private BitSet activityParticipation;
/**
* In the parallel version of the {@link ReplayHandler}, the infections
* are not happen in a chronically order. The earliestInfections
* check therefore, that the first infection is valued as the important
* infection
*/
private EpisimInfectionEvent earliestInfection = null;
/**
* List of all potential infection that happened during the day.
*/
private final List<EpisimPotentialInfectionEvent> potentialInfectionEvents = new ArrayList<>();
/**
* The facility where the person got infected. Can be null if person was initially infected.
*/
private Id<ActivityFacility> infectionContainer = null;
/**
* The infection type when the person got infected. Can be null if person was initially infected.
*/
private String infectionType = null;
/**
* Current {@link DiseaseStatus}.
*/
private DiseaseStatus status = DiseaseStatus.susceptible;
/**
* Current {@link QuarantineStatus}.
*/
private QuarantineStatus quarantineStatus = QuarantineStatus.no;
/**
* Current {@link TestStatus}.
*/
private TestStatus testStatus = TestStatus.untested;
/**
* Iteration when this person got into quarantine. Negative if person was never quarantined.
*/
private int quarantineDate = Integer.MIN_VALUE;
/**
* Iteration when this person was tested. Negative if person was never tested.
*/
private int testDate = -1;
/**
* Age of the person in years.
*/
private final int age;
/**
* Whether this person can be traced.
*/
private boolean traceable;
/**
* Whether this person can be vaccinated.
*/
private boolean vaccinable = true;
/**
* Individual susceptibility of a person.
*/
private double susceptibility = 1;
/**
* Types of received vaccination. Index 0 is the first received.
*/
private final List<VaccinationType> vaccinations = new ArrayList<>();
/**
* Iteration when this person was vaccinated. Negative if person was never vaccinated.
*/
private final IntList vaccinationDates = new IntArrayList();
/**
* Second at which a person is infected (divide by 24*60*60 to get iteration/day)
*/
private final DoubleList infectionDates = new DoubleArrayList();
/**
* Strain of the virus the person was infected with.
*/
private final List<VirusStrain> virusStrains = new ArrayList<>();
/**
* Antibody level for each virus strain.
*/
private final Object2DoubleMap<VirusStrain> antibodies = new Object2DoubleOpenHashMap<>();
/**
* Maximal antibody level reached by agent w/ respect to each strain
*/
private final Object2DoubleMap<VirusStrain> maxAntibodies = new Object2DoubleOpenHashMap<>();
/**
* Antibody level at last infection.
*/
private double antibodyLevelAtInfection = 0.;
/**
* Immune response multiplier, which is used to scale the antibody increase due to an immunity event
*/
private double immuneResponseMultiplier = 1.0;
/**
* Lookup age from attributes.
*/
private static int getAge(Attributes attrs) {
int age = -1;
for (String attr : attrs.getAsMap().keySet()) {
if (attr.contains("age")) {
age = Integer.parseInt(attrs.getAttribute(attr).toString());
break;
}
}
return age;
}
public List<PerformedActivity> getTrajectory() {
return trajectory;
}
public EpisimPerson(Id<Person> personId, Attributes attrs, EpisimReporting reporting) {
this(personId, attrs, true, reporting);
}
EpisimPerson(Id<Person> personId, Attributes attrs, boolean traceable, EpisimReporting reporting) {
this.personId = personId;
this.attributes = attrs;
this.traceable = traceable;
this.age = getAge(attrs);
this.reporting = reporting;
}
/**
* Reads persons state from stream.
*
* @param persons map of all persons in the simulation
*/
void read(ObjectInput in, Map<Id<Person>, EpisimPerson> persons) throws IOException {
int n = in.readInt();
traceableContactPersons.clear();
for (int i = 0; i < n; i++) {
Id<Person> id = Id.create(readChars(in), Person.class);
traceableContactPersons.put(persons.get(id), in.readDouble());
}
n = in.readInt();
statusChanges.clear();
for (int i = 0; i < n; i++) {
int status = in.readInt();
statusChanges.put(DiseaseStatus.values()[status], in.readDouble());
}
if (in.readBoolean()) {
infectionContainer = Id.create(readChars(in), ActivityFacility.class);
}
if (in.readBoolean()) {
infectionType = readChars(in);
}
n = in.readInt();
spentTime.clear();
for (int i = 0; i < n; i++) {
String act = readChars(in);
spentTime.put(act, in.readDouble());
}
n = in.readInt();
for (int i = 0; i < n; i++) {
vaccinations.add(VaccinationType.values()[in.readInt()]);
vaccinationDates.add(in.readInt());
}
n = in.readInt();
for (int i = 0; i < n; i++) {
infectionDates.add(in.readDouble());
virusStrains.add(VirusStrain.values()[in.readInt()]);
}
n = in.readInt();
for (int i = 0; i < n; i++) {
VirusStrain strain = VirusStrain.values()[in.readInt()];
antibodies.put(strain, in.readDouble());
}
n = in.readInt();
for (int i = 0; i < n; i++) {
VirusStrain strain = VirusStrain.values()[in.readInt()];
maxAntibodies.put(strain, in.readDouble());
}
status = DiseaseStatus.values()[in.readInt()];
quarantineStatus = QuarantineStatus.values()[in.readInt()];
quarantineDate = in.readInt();
testStatus = TestStatus.values()[in.readInt()];
testDate = in.readInt();
traceable = in.readBoolean();
// vaccinable, which is not restored from snapshot
in.readBoolean();
susceptibility = in.readDouble();
antibodyLevelAtInfection = in.readDouble();
immuneResponseMultiplier = in.readDouble();
}
/**
* Writes person state to stream.
*/
void write(ObjectOutput out) throws IOException {
out.writeInt(traceableContactPersons.size());
for (Object2DoubleMap.Entry<EpisimPerson> kv : traceableContactPersons.object2DoubleEntrySet()) {
writeChars(out, kv.getKey().getPersonId().toString());
out.writeDouble(kv.getDoubleValue());
}
out.writeInt(statusChanges.size());
for (Map.Entry<DiseaseStatus, Double> e : statusChanges.entrySet()) {
out.writeInt(e.getKey().ordinal());
out.writeDouble(e.getValue());
}
out.writeBoolean(infectionContainer != null);
if (infectionContainer != null) {
writeChars(out, infectionContainer.toString());
}
out.writeBoolean(infectionType != null);
if (infectionType != null) {
writeChars(out, infectionType);
}
out.writeInt(spentTime.size());
for (Object2DoubleMap.Entry<String> kv : spentTime.object2DoubleEntrySet()) {
writeChars(out, kv.getKey());
out.writeDouble(kv.getDoubleValue());
}
out.writeInt(vaccinations.size());
for (int i = 0; i < vaccinations.size(); i++) {
out.writeInt(vaccinations.get(i).ordinal());
out.writeInt(vaccinationDates.getInt(i));
}
out.writeInt(infectionDates.size());
for (int i = 0; i < infectionDates.size(); i++) {
out.writeDouble(infectionDates.getDouble(i));
out.writeInt(virusStrains.get(i).ordinal());
}
out.writeInt(antibodies.size());
for (Object2DoubleMap.Entry<VirusStrain> kv : antibodies.object2DoubleEntrySet()) {
out.writeInt(kv.getKey().ordinal());
out.writeDouble(kv.getDoubleValue());
}
out.writeInt(maxAntibodies.size());
for (Object2DoubleMap.Entry<VirusStrain> kv : maxAntibodies.object2DoubleEntrySet()) {
out.writeInt(kv.getKey().ordinal());
out.writeDouble(kv.getDoubleValue());
}
out.writeInt(status.ordinal());
out.writeInt(quarantineStatus.ordinal());
out.writeInt(quarantineDate);
out.writeInt(testStatus.ordinal());
out.writeInt(testDate);
out.writeBoolean(traceable);
out.writeBoolean(vaccinable);
out.writeDouble(susceptibility);
out.writeDouble(antibodyLevelAtInfection);
out.writeDouble(immuneResponseMultiplier);
}
public Id<Person> getPersonId() {
return personId;
}
public DiseaseStatus getDiseaseStatus() {
return status;
}
public void setDiseaseStatus(double now, DiseaseStatus status) {
this.status = status;
// when person goes back to susceptible, old states are removed
if (status == DiseaseStatus.susceptible || status == DiseaseStatus.deceased) {
statusChanges.keySet().removeIf(p -> p != DiseaseStatus.recovered);
}
if (!statusChanges.containsKey(status) || status == DiseaseStatus.recovered)
statusChanges.put(status, now);
reporting.reportPersonStatus(this, new EpisimPersonStatusEvent(now, personId, status));
}
/**
* Set and report initial infection.
*/
public void setInitialInfection(double now, VirusStrain strain) {
reporting.reportInfection(new EpisimInitialInfectionEvent(now, getPersonId(), strain, antibodies.getDouble(strain), maxAntibodies.getDouble(strain),getNumInfections()));
virusStrains.add(strain);
setDiseaseStatus(now, EpisimPerson.DiseaseStatus.infectedButNotContagious);
infectionDates.add(now);
antibodyLevelAtInfection = antibodies.getDouble(strain);
// TODO: add max antibodies
}
/**
* Adds an infection possibility to this person. Will be executed in {@link #checkInfection()}
*/
synchronized public void possibleInfection(EpisimInfectionEvent event) {
if (earliestInfection == null || event.compareTo(earliestInfection) < 0) {
earliestInfection = event;
}
}
/**
* Adds a potential infection to the list.
*/
synchronized public void potentialInfection(EpisimPotentialInfectionEvent event) {
potentialInfectionEvents.add(event);
}
/**
* Update state with a stored {@link EpisimInfectionEvent}.
*
* @return the event if an infection has occurred.
*/
public EpisimInfectionEvent checkInfection() {
if (earliestInfection != null) {
EpisimInfectionEvent event = this.earliestInfection;
setDiseaseStatus(event.getTime(), EpisimPerson.DiseaseStatus.infectedButNotContagious);
virusStrains.add(event.getVirusStrain());
infectionContainer = (Id<ActivityFacility>) event.getContainerId();
infectionType = event.getInfectionType();
infectionDates.add(event.getTime());
earliestInfection = null;
antibodyLevelAtInfection = antibodies.getDouble(event.getVirusStrain());
return event;
}
return null;
}
/**
* Get all potential infection events.
*/
List<EpisimPotentialInfectionEvent> getPotentialInfections() {
return potentialInfectionEvents;
}
public QuarantineStatus getQuarantineStatus() {
return quarantineStatus;
}
public void setQuarantineStatus(QuarantineStatus quarantineStatus, int iteration) {
this.quarantineStatus = quarantineStatus;
this.quarantineDate = iteration;
// this function should receive now instead of iteration
// only for testing currently
//reporting.reportPersonStatus(this, new EpisimPersonStatusEvent(iteration * 86400d, personId, quarantineStatus));
}
public VirusStrain getVirusStrain() {
// Backwards compatibility
if (virusStrains.isEmpty())
return VirusStrain.SARS_CoV_2;
return virusStrains.get(virusStrains.size() - 1);
}
/**
* Virus strain of infection.
* @param idx index of infection starting at 0
*/
public VirusStrain getVirusStrain(int idx) {
return virusStrains.get(idx);
}
/**
* List of dates (in second format) on which agent was infected
*/
public DoubleList getInfectionDates(){
return infectionDates;
}
/**
* Number of received vaccinations
*/
public int getNumVaccinations() {
return vaccinations.size();
}
public VaccinationStatus getVaccinationStatus() {
return vaccinations.size() > 0 ? VaccinationStatus.yes : VaccinationStatus.no;
}
/**
* Use {@link #getVaccinationType(int)}
*/
@Deprecated
public VaccinationType getVaccinationType() {
return vaccinations.get(0);
}
public VaccinationType getVaccinationType(int idx) {
return vaccinations.get(idx);
}
/**
* List of days that agent was infected on
*/
public IntList getVaccinationDates() {
return vaccinationDates;
}
/**
* Use {@link #getNumVaccinations()}
*/
@Deprecated
public VaccinationStatus getReVaccinationStatus() {
return vaccinations.size() > 1 ? VaccinationStatus.yes : VaccinationStatus.no;
}
public void setVaccinationStatus(VaccinationStatus vaccinationStatus, VaccinationType type, int iteration) {
if (vaccinationStatus != VaccinationStatus.yes) throw new IllegalArgumentException("Vaccination can only be set to yes.");
vaccinations.add(type);
vaccinationDates.add(iteration);
reporting.reportVaccination(personId, iteration, type, vaccinations.size());
}
public TestStatus getTestStatus() {
return testStatus;
}
public void setTestStatus(TestStatus testStatus, int iteration) {
this.testStatus = testStatus;
this.testDate = iteration;
}
public void setSusceptibility(double susceptibility) {
this.susceptibility = susceptibility;
}
public double getSusceptibility() {
return susceptibility;
}
/**
* Immunity factor based on antibody level at infection.
*/
public double getAntibodyLevelAtInfection() {
return antibodyLevelAtInfection;
}
/**
* get map with max antibodies reached per strain (before current infection)
*/
public Object2DoubleMap<VirusStrain> getMaxAntibodies() {
return maxAntibodies;
}
/**
* Get max antibodies reached for a particular strain (before current infection)
*/
public double getMaxAntibodies(VirusStrain virusStrain) {
return maxAntibodies.getDouble(virusStrain);
}
/**
* Updates maximum antibodies agent has had versus a particular strain (only if maxAb is in fact higher
* than previous maximum)
*/
public void updateMaxAntibodies(VirusStrain strain, double maxAb) {
if (!this.maxAntibodies.containsKey(strain) || maxAb > this.maxAntibodies.getDouble(strain)) {
this.maxAntibodies.put(strain, maxAb);
}
}
public double getAntibodies(VirusStrain strain) {
return antibodies.getDouble(strain);
}
public Object2DoubleMap<VirusStrain> getAntibodies() {
return antibodies;
}
public double setAntibodies(VirusStrain strain, double value) {
return antibodies.put(strain, value);
}
/**
* Days elapsed since a certain status was set.
* This will always round the change as if it happened on the start of a day.
*
* @param status requested status
* @param currentDay current day (iteration)
* @throws IllegalStateException when the requested status was never set
*/
public int daysSince(DiseaseStatus status, int currentDay) {
if (!statusChanges.containsKey(status)) throw new IllegalStateException("Person was never " + status);
double day = Math.floor(statusChanges.get(status) / EpisimUtils.DAY);
return currentDay - (int) day;
}
/**
* Days elapsed since nth infection occurred.
*
* @param idx index starting at 0
*/
public int daysSinceInfection(int idx, int currentDay) {
if (infectionDates.size() <= idx) throw new IllegalStateException("Person did not had infection with index " + idx);
double day = Math.floor(infectionDates.getDouble(idx) / EpisimUtils.DAY);
return currentDay - (int) day;
}
/**
* Return days since status, or default value if this status was not attained.
*/
public int daysSinceOrElse(DiseaseStatus status, int currentDay, int defaultValue) {
if (!hadDiseaseStatus(status)) return defaultValue;
return daysSince(status, currentDay);
}
/**
* Return whether a person had (or currently has) a certain disease status.
*/
public boolean hadDiseaseStatus(DiseaseStatus status) {
return statusChanges.containsKey(status);
}
/**
* Return whether a person received certain vaccination type.
*/
public boolean hadVaccinationType(VaccinationType type) {
return vaccinations.contains(type);
}
/**
* Return whether a person was infected with certain virus strain.
*/
public boolean hadStrain(VirusStrain strain) {
return virusStrains.contains(strain);
}
/**
* Days elapsed since person was put into quarantine.
*
* @param currentDay current day (iteration)
* @apiNote This is currently not used much and may change similar to {@link #daysSince(DiseaseStatus, int)}.
*/
@Beta
public int daysSinceQuarantine(int currentDay) {
// yyyy since this API is so unstable, I would prefer to have the class non-public. kai, apr'20
// -> api now marked as unstable and containing an api note, because it is used by the models it has to be public. chr, apr'20
//check removed; when starting simulation with immunisation history, quarantine date can very well be negative. -jr, nov'22
if (quarantineDate == Integer.MIN_VALUE) {
throw new IllegalStateException("Person was never quarantined");
}
return currentDay - quarantineDate;
}
/**
* Days elapsed since person got its first vaccination.
*
* @param currentDay current day (iteration)
*/
public int daysSince(VaccinationStatus status, int currentDay) {
if (status != VaccinationStatus.yes) throw new IllegalArgumentException("Only supports querying when person was vaccinated");
if (vaccinations.isEmpty()) throw new IllegalStateException("Person was never vaccinated");
return currentDay - vaccinationDates.getInt(vaccinationDates.size() - 1);
}
/**
* Days since the nth vaccination (starting at 0)
*/
public int daysSinceVaccination(int idx, int currentDay) {
if (vaccinations.size() <= idx) throw new IllegalStateException("Person did not receive vaccination with index " + idx);
return currentDay - vaccinationDates.getInt(idx);
}
/**
* Days elapsed since person got its first vaccination.
*
* @param currentDay current day (iteration)
*/
public int daysSinceTest(int currentDay) {
if (testDate < 0)
return Integer.MAX_VALUE;
return currentDay - testDate;
}
/**
* Number of times person was infected.
*/
public int getNumInfections() {
return infectionDates.size();
}
/**
* Whether this person is handled as a recovered person.
*
* @param threshold after how many days the status will expire
*/
public boolean isRecentlyRecovered(int currentDay, int threshold) {
return status == DiseaseStatus.recovered || (status == DiseaseStatus.susceptible && infectionDates.size() >= 1 && daysSince(DiseaseStatus.recovered, currentDay) <= threshold);
}
public synchronized void addTraceableContactPerson(EpisimPerson personWrapper, double now) {
// check if both persons have tracing capability
if (isTraceable() && personWrapper.isTraceable()) {
// Always use the latest tracking date
traceableContactPersons.put(personWrapper, now);
reporting.reportTracing(now, this, personWrapper);
}
}
/**
* Get all traced contacts that happened after certain time.
*/
public synchronized List<EpisimPerson> getTraceableContactPersons(double after) {
// needs to be sorted or results will be non deterministic with multithreading
return traceableContactPersons.object2DoubleEntrySet()
.stream().filter(p -> p.getDoubleValue() >= after)
.map(Map.Entry::getKey)
.sorted(Comparator.comparing(EpisimPerson::getPersonId))
.collect(Collectors.toList());
// yyyy if the computationally intensive operation is to search by time, we should sort traceableContactPersons by time. To simplify this, I
// would argue that it is not a problem to have a person in there multiple times. kai, may'20
}
/**
* Remove old contact tracing data before a certain date.
*/
public void clearTraceableContractPersons(double before) {
int oldSize = traceableContactPersons.size();
if (oldSize == 0) return;
traceableContactPersons.keySet().removeIf(k -> traceableContactPersons.getDouble(k) < before);
}
/**
* Returns whether the person can be traced.
*/
public boolean isTraceable() {
return traceable;
}
void setTraceable(boolean traceable) {
this.traceable = traceable;
}
public boolean isVaccinable() {
return vaccinable;
}
/**
* Set vaccinable status.
*/
public void setVaccinable(boolean vaccinable) {
this.vaccinable = vaccinable;
}
public PerformedActivity addToTrajectory(double time, EpisimConfigGroup.InfectionParams trajectoryElement, Id<ActivityFacility> facilityId) {
PerformedActivity act = new PerformedActivity(time, trajectoryElement, facilityId);
trajectory.add(act);
return act;
}
void setStartOfDay(DayOfWeek day) {
startOfDay[day.getValue() - 1] = trajectory.size();
}
int getStartOfDay(DayOfWeek day) {
return startOfDay[day.getValue() - 1];
}
void setEndOfDay(DayOfWeek day) {
endOfDay[day.getValue() - 1] = trajectory.size();
}
int getEndOfDay(DayOfWeek day) {
return endOfDay[day.getValue() - 1];
}
/**
* Matches all activities of a person for a day. Calls {@code reduce} on all matched activities.
* This method takes {@link #activityParticipation} into account.
*
* @param reduce reduce function called on each activity with current result
* @param defaultValue default value and initial value for the reduce function
*/
public <T> T matchActivities(DayOfWeek day, Set<String> activities, BiFunction<String, T, T> reduce, T defaultValue) {
T result = defaultValue;
for (int i = getStartOfDay(day); i < getEndOfDay(day); i++) {
String act = trajectory.get(i).params.getContainerName();
if (activityParticipation.get(i) && activities.contains(act))
result = reduce.apply(act, result);
}
return result;
}
/**
* Matches all activities of a person for a day. Calls {@code reduce} on all matched activities.
* This method takes {@link #activityParticipation} into account.
*
* @see #matchActivities(DayOfWeek, Set, BiFunction, Object)
*/
public <T> T matchActivities(DayOfWeek day, BiFunction<String, T, T> reduce, T defaultValue) {
T result = defaultValue;
for (int i = getStartOfDay(day); i < getEndOfDay(day); i++) {
String act = trajectory.get(i).params.getContainerName();
if (activityParticipation.get(i))
result = reduce.apply(act, result);
}
return result;
}
/**
* Whether this person has any activity for given day.
* Used during initialization. After that it should always return true.
*/
boolean hasActivity(DayOfWeek day) {
return getStartOfDay(day) < trajectory.size();
}
/**
* Init participation bit set.
*/
void initParticipation() {
activityParticipation = new BitSet(trajectory.size());
activityParticipation.set(0, trajectory.size(), true);
}
public BitSet getActivityParticipation() {
return activityParticipation;
}
/**
* Defines that day {@code target} has the same trajectory as {@code source}.
*/
void duplicateDay(DayOfWeek target, DayOfWeek source) {
startOfDay[target.getValue() - 1] = startOfDay[source.getValue() - 1];
endOfDay[target.getValue() - 1] = endOfDay[source.getValue() - 1];
firstFacilityId[target.getValue() - 1] = firstFacilityId[source.getValue() - 1];
lastFacilityId[target.getValue() - 1] = lastFacilityId[source.getValue() - 1];
staysInContainer[target.getValue() - 1] = staysInContainer[source.getValue() - 1];
}
/**
* Reset all trajectory information
*/
void resetTrajectory() {
trajectory.clear();
Arrays.fill(startOfDay, 0);
Arrays.fill(endOfDay, 0);
Arrays.fill(firstFacilityId, null);
Arrays.fill(lastFacilityId, null);
Arrays.fill(staysInContainer, false);
}
@Override
public Attributes getAttributes() {
return attributes;
}
public int getAge() {
assert age != -1 : "Person=" + getPersonId().toString() + " has no age.";
assert age >= 0 && age <= 120 : "Age of person=" + getPersonId().toString() + " is not plausible. Age is=" + age;
return age;
}
/**
* Return the age of a person or the default age if no age is specified.
*/
public int getAgeOrDefault(int defaultAge) {
return age != -1 ? age : defaultAge;
}
Id<ActivityFacility> getFirstFacilityId(DayOfWeek day) {
return firstFacilityId[day.getValue() - 1];
}
void setFirstFacilityId(Id<ActivityFacility> firstFacilityId, DayOfWeek day) {
this.firstFacilityId[day.getValue() - 1] = firstFacilityId;
}
Id<ActivityFacility> getLastFacilityId(DayOfWeek day) {
return lastFacilityId[day.getValue() - 1];
}
void setLastFacilityId(Id<ActivityFacility> lastFacilityId, DayOfWeek day, boolean stays) {
this.lastFacilityId[day.getValue() - 1] = lastFacilityId;
this.staysInContainer[day.getValue() - 1] = stays;
}
void setStaysInContainer(DayOfWeek day, boolean stays) {
this.staysInContainer[day.getValue() - 1] = stays;
}
boolean getStaysInContainer(DayOfWeek day) {
return staysInContainer[day.getValue() - 1];
}
public Id<ActivityFacility> getInfectionContainer() {
return infectionContainer;
}
public String getInfectionType() {
return infectionType;
}
/**
* Add amount of time to spent time for an activity.
*/
public synchronized void addSpentTime(String actType, double timeSpent) {
spentTime.mergeDouble(actType, timeSpent, Double::sum);
}
/**
* Spent time of this person by activity.
*/
public Object2DoubleMap<String> getSpentTime() {
return spentTime;
}
/**
* Getter for immune response multiplier, which is used to scale the antibody increase due to an immunity event
* @return
*/
public double getImmuneResponseMultiplier() {
return immuneResponseMultiplier;
}
/**
* Setter for immune response multiplier, which is used to scale the antibody increase due to an immunity event
* @param immuneResponseMultiplier
*/
public void setImmuneResponseMultiplier(double immuneResponseMultiplier) {
this.immuneResponseMultiplier = immuneResponseMultiplier;
}
@Override
public String toString() {
return "EpisimPerson{" +
"personId=" + personId +
'}';
}
private int findActivity(DayOfWeek day, double time) {
// do a linear search for matching activity
int last = getEndOfDay(day) - 1;
for (int i = getStartOfDay(day); i < last; i++) {
if (trajectory.get(i + 1).time > time)
return i;
}
return last;
}
private int findFirstActivity(DayOfWeek day, double time) {
int last = getEndOfDay(day) - 1;
for (int i = getStartOfDay(day); i < last; i++) {
if (trajectory.get(i + 1).time >= time)
return i;
}
return last;
}
/**
* Checks whether a certain activity is performed.
*/
boolean checkActivity(DayOfWeek day, double time) {
return activityParticipation.get(findActivity(day, time));
}
boolean checkFirstActivity(DayOfWeek day, double time) {
return activityParticipation.get(findFirstActivity(day, time));
}
/**
* Checks whether the next activity is performed.
*/
boolean checkNextActivity(DayOfWeek day, double time) {
int idx = findActivity(day, time);
if (idx < getEndOfDay(day) - 1)
return activityParticipation.get(idx + 1);
return true;
}
public List<PerformedActivity> getActivities(DayOfWeek day) {
int offset = getStartOfDay(day);
return trajectory.subList(offset, getEndOfDay(day));
}
/**
* Return the first activity of a person for specific day.
*/
PerformedActivity getFirstActivity(DayOfWeek day) {
return trajectory.get(getStartOfDay(day));
}
PerformedActivity getLastActivity(DayOfWeek day) {
return trajectory.get(getEndOfDay(day) - 1);
}
/**
* Get the activity normally performed by a person on a specific day and time.
*/
public PerformedActivity getActivity(DayOfWeek day, double time) {
assert getStartOfDay(day) >= 0;
assert getEndOfDay(day) <= trajectory.size();
return trajectory.get(findActivity(day, time));
}
/**
* Get the next activity of a person.
*
* @see #getActivity(DayOfWeek, double)
*/
@Nullable
public PerformedActivity getNextActivity(DayOfWeek day, double time) {
int idx = findActivity(day, time);
if (idx < getEndOfDay(day) - 1)
return trajectory.get(idx + 1);
return null;
}
/**
* Disease status of a person.
*/
public enum DiseaseStatus {
susceptible, infectedButNotContagious, contagious, showingSymptoms,
seriouslySick, critical, seriouslySickAfterCritical, recovered, deceased
}
/**
* Quarantine status of a person.
*/
public enum QuarantineStatus {full, atHome, testing, no}
/**
* Latest test result of this person.
*/
public enum TestStatus {untested, positive, negative}
/**
* Status of vaccination.
*/
public enum VaccinationStatus {yes, no}
/**
* Stores when an activity is performed and in which context.
*/
public static final class PerformedActivity {
public final double time;
public final EpisimConfigGroup.InfectionParams params;
public final Id<ActivityFacility> facilityId;
public PerformedActivity(double time, EpisimConfigGroup.InfectionParams params, Id<ActivityFacility> facilityId) {
this.time = time;
this.params = params;
this.facilityId = facilityId;
}
/**
* Starting time of an activity.
*/
public double time() {
return time;
}
/**
* Activity type as string.
*/
public String actType() {
// container name is quite misleading and not the correct anymore.
return params.getContainerName();
}
/**
* Facility Id for performed activity
*/
public Id<ActivityFacility> getFacilityId() {
return this.facilityId;
}
@Override
public String toString() {
return "PerformedActivity{" +
"time=" + time +
", params=" + params +
'}';
}
}
/**
* Not further specified activity that is used during initialization.
*/
static final PerformedActivity UNSPECIFIC_ACTIVITY = new PerformedActivity(Double.NaN, null, null);
/**
* If the ContagiousOptimization is enabled, containers count how many
* persons satisfy this predicate to call the infectionsDynamics methods
* only in the case that at least one person in the container
* can infect another (or in the infectedButNotContagious case,
* inform other persons later thanks to tracking).
*/
public boolean infectedButNotSerious() {
return (status == DiseaseStatus.infectedButNotContagious ||
status == DiseaseStatus.contagious ||
status == DiseaseStatus.showingSymptoms);
}
}