EpisimContainer.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 it.unimi.dsi.fastutil.ints.*;
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.population.Person;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import static org.matsim.episim.EpisimUtils.*;
/**
* Wrapper class for a specific location that keeps track of currently contained agents and entering times.
*
* @param <T> the type where the agents are located in, e.g {@link org.matsim.vehicles.Vehicle} or {@link org.matsim.facilities.Facility}.
*/
public class EpisimContainer<T> {
private final Id<T> containerId;
private static final Logger log = LogManager.getLogger(EpisimContainer.class);
/**
* Persons currently in this container. Stored only as Ids.
*/
private final IntSet persons = new IntOpenHashSet(4);
/**
* Person list needed to draw random persons within container.
*/
private final List<EpisimPerson> personsAsList = new ArrayList<>();
private final Int2DoubleMap containerEnterTimes = new Int2DoubleOpenHashMap(4);
/**
* Activities of persons in the container.
*/
private final Int2ObjectMap<EpisimPerson.PerformedActivity> personActivities = new Int2ObjectArrayMap<>(4);
/**
* The maximum number of persons simultaneously in this container. Negative if unknown.
* Already scaled with sampleSize.
*/
private int maxGroupSize = -1;
/**
* The number of persons using this container over all days.
*/
private int totalUsers = -1;
/**
* Typical number of persons that can fit into this container.
*/
private int typicalCapacity = -1;
/**
* Number of distinct spaces in this facility. May be relevant for certain contact models.
*/
private double numSpaces = 1;
/**
* The id of the ReplayEventTask that handles the events for this container
*/
private int taskId = 0;
/**
* This counts the number of persons in this container
* which have the DiseaseStatus contagious or showingSymptoms.
*/
private int contagiousCounter = 0;
EpisimContainer(Id<T> containerId) {
this.containerId = containerId;
}
/**
* Reads containers state from stream.
*/
void read(ObjectInput in, Map<Id<Person>, EpisimPerson> persons) throws IOException {
this.persons.clear();
this.personsAsList.clear();
this.containerEnterTimes.clear();
int n = in.readInt();
for (int i = 0; i < n; i++) {
Id<Person> id = Id.create(readChars(in), Person.class);
this.persons.add(id.index());
personsAsList.add(persons.get(id));
containerEnterTimes.put(id.index(), in.readDouble());
}
}
/**
* Writes state to stream.
*/
void write(ObjectOutput out) throws IOException {
out.writeInt(containerEnterTimes.size());
for (EpisimPerson p : personsAsList) {
writeChars(out, p.getPersonId().toString());
out.writeDouble(containerEnterTimes.get(p.getPersonId().index()));
}
}
boolean containsPerson(EpisimPerson person) {
final int index = person.getPersonId().index();
return persons.contains(index);
}
void addPerson(EpisimPerson person, double now, EpisimPerson.PerformedActivity act) {
final int index = person.getPersonId().index();
//assert !persons.contains(index) : "Person already contained in this container.";
assert !persons.contains(index) : String.format("Person %s was already in container %s", person.getPersonId(), containerId);
persons.add(index);
personsAsList.add(person);
containerEnterTimes.put(index, now);
personActivities.put(index, act);
}
/**
* Removes a person from this container.
*
* @throws RuntimeException if the person was not in the container.
*/
void removePerson(EpisimPerson person) {
int index = person.getPersonId().index();
containerEnterTimes.remove(index);
personActivities.remove(index);
persons.remove(index);
boolean wasRemoved = personsAsList.remove(person);
if (!wasRemoved)
log.warn( "Person {} was not in container {}", person.getPersonId(), containerId);
if (person.infectedButNotSerious())
contagiousCounter -= 1;
}
/**
* Remove person using the iterator from {@link #getPersons()}.
* This allows to remove persons while iterating through them.
*/
void removePerson(EpisimPerson person, Iterator<EpisimPerson> it) {
int index = person.getPersonId().index();
containerEnterTimes.remove(index);
personActivities.remove(index);
persons.remove(index);
it.remove();
}
public Id<T> getContainerId() {
return containerId;
}
/**
* @return maximum group size in container.
*/
public int getMaxGroupSize() {
return maxGroupSize;
}
/**
* @return number of people using container. May be larger than {@link #getMaxGroupSize()}.
*/
public int getTotalUsers() {
return totalUsers;
}
public int getTypicalCapacity() {
return typicalCapacity;
}
public double getNumSpaces() {
return numSpaces;
}
/**
* Sets the max group size this container has during a day.
*
* @param num number of persons
*/
void setMaxGroupSize(int num) {
maxGroupSize = num;
}
/**
* Sets the total number of persons using this container.
*
* @param num number of persons
*/
void setTotalUsers(int num) {
totalUsers = num;
}
void setTypicalCapacity(int typicalCapacity) {
this.typicalCapacity = typicalCapacity;
}
public void setNumSpaces(double numSpaces) {
this.numSpaces = numSpaces;
}
public void setTaskId(int taskId) {
this.taskId = taskId;
}
public int getTaskId() {
return taskId;
}
void clearPersons() {
this.persons.clear();
this.personsAsList.clear();
this.containerEnterTimes.clear();
}
/**
* Returns the time the person entered the container, or {@link Double#NEGATIVE_INFINITY} if it never entered.
*/
public double getContainerEnteringTime(Id<Person> personId) {
return containerEnterTimes.getOrDefault(personId.index(), Double.NEGATIVE_INFINITY);
}
/**
* Return the activity that a person is performing in this container.
*/
public EpisimPerson.PerformedActivity getPerformedActivity(Id<Person> personId) {
return personActivities.get(personId.index());
}
public List<EpisimPerson> getPersons() {
// Using Collections.unmodifiableList(...) puts huge pressure on the GC if its called hundred thousand times per second
return personsAsList;
}
public void countContagious(int add) {
contagiousCounter += add;
assert contagiousCounter >= 0 : "We can not have a negative number of contagious persons";
}
public void resetContagiousCounter() {
contagiousCounter = 0;
}
public boolean containsContagious() {
return contagiousCounter > 0;
}
}