EpisimConfigGroup.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 com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.magnos.trie.Trie;
import org.magnos.trie.TrieMatch;
import org.magnos.trie.Tries;
import org.matsim.core.config.ConfigGroup;
import org.matsim.core.config.ReflectiveConfigGroup;
import org.matsim.episim.model.VirusStrain;
import org.matsim.episim.policy.Restriction;
import org.matsim.episim.policy.ShutdownPolicy;
import javax.validation.constraints.NotNull;
import java.io.File;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;
/**
* Main config for episim.
*/
public final class EpisimConfigGroup extends ReflectiveConfigGroup {
private static final Splitter.MapSplitter SPLITTER = Splitter.on(";").withKeyValueSeparator("=");
private static final Joiner.MapJoiner JOINER = Joiner.on(";").withKeyValueSeparator("=");
private static final String WRITE_EVENTS = "writeEvents";
private static final String CALIBRATION_PARAMETER = "calibrationParameter";
private static final String HOSPITAL_FACTOR = "hospitalFactor";
private static final String INITIAL_INFECTIONS = "initialInfections";
private static final String INITIAL_INFECTION_DISTRICT = "initialInfectionDistrict";
private static final String INFECTIONS_PER_DAY = "infectionsPerDay";
private static final String LOWER_AGE_BOUNDARY_FOR_INIT_INFECTIONS = "lowerAgeBoundaryForInitInfections";
private static final String UPPER_AGE_BOUNDARY_FOR_INIT_INFECTIONS = "upperAgeBoundaryForInitInfections";
private static final String MAX_CONTACTS = "maxContacts";
private static final String SAMPLE_SIZE = "sampleSize";
private static final String START_DATE = "startDate";
private static final String SNAPSHOT_INTERVAL = "snapshotInterval";
private static final String START_FROM_SNAPSHOT = "startFromSnapshot";
private static final String START_FROM_IMMUNIZATION = "startFromImmunization";
private static final String SNAPSHOT_PREFIX = "snapshotPrefix";
private static final String SNAPSHOT_SEED = "snapshotSeed";
private static final String LEISUREOUTDOORFRACTION = "leisureOutdoorFraction";
private static final String INPUT_DAYS = "inputDays";
private static final String DAYS_INFECTIOUS = "daysInfectious";
private static final String ACTIVITY_HANDLING = "activityHandling";
private static final String THREADS = "threads";
private static final String CURFEW_COMPLIANCE = "curfewCompliance";
private static final String DISTRICT_LEVEL_RESTRICTIONS = "districtLevelRestrictions";
private static final String DISTRICT_LEVEL_RESTRICTIONS_ATTRIBUTE = "districtLevelRestrictionsAttribute";
private static final String CONTAGIOUS_CONTAINER_OPTIMIZATION = "contagiousContainerOptimization";
private static final String REPORT_TIME_USE = "reportTimeUse";
private static final String SINGLE_EVENT_FILE = "singleEventFile";
private static final String END_EARLY = "endEarly";
private static final Logger log = LogManager.getLogger(EpisimConfigGroup.class);
private static final String GROUPNAME = "episim";
private final Trie<String, InfectionParams> paramsTrie = Tries.forStrings();
/**
* Number of initial infections per day.
* Default is 1 infection per day for {@link VirusStrain#SARS_CoV_2}.
*/
private final Map<VirusStrain, NavigableMap<LocalDate, Integer>> infectionsPerDay = new EnumMap<>(Map.of(VirusStrain.SARS_CoV_2, new TreeMap<>()));
/**
* Leisure outdoor fractions per day.
*/
private final Map<LocalDate, Double> leisureOutdoorFraction = new TreeMap<>(Map.of(
LocalDate.parse("2020-01-15"), 0.1,
LocalDate.parse("2020-04-15"), 0.8,
LocalDate.parse("2020-09-15"), 0.8,
LocalDate.parse("2020-11-15"), 0.1,
LocalDate.parse("2021-02-15"), 0.1,
LocalDate.parse("2021-04-15"), 0.8,
LocalDate.parse("2021-09-15"), 0.8)
);
/**
* Re-mapping of specific dates to different week days events.
*/
private final Map<LocalDate, DayOfWeek> inputDays = new HashMap<>();
/**
* Which events to write in the output.
*/
private WriteEvents writeEvents = WriteEvents.episim;
// this is current default for 25% scenarios
private double calibrationParameter = 0.000002;
private double hospitalFactor = 1.;
private double sampleSize = 0.1;
private int initialInfections = 10;
private int lowerAgeBoundaryForInitInfections = -1;
private int upperAgeBoundaryForInitInfections = -1;
/**
* If not null, filter persons for initial infection by district.
*/
private String initialInfectionDistrict = null;
/**
* Start date of the simulation (Day 1).
*/
private LocalDate startDate = LocalDate.of(1970, 1, 1);
/**
* Offset of start date in unix epoch seconds.
*/
private long startOffset = 0;
/**
* Write snapshot every x days.
*/
private int snapshotInterval = 0;
/**
* Path to snapshot file.
*/
private String startFromSnapshot = null;
private String startFromImmunization = null;
/**
* Filename prefix for snapshot.
*/
private String snapshotPrefix = "episim-snapshot";
/**
* How the internal rng state should be handled.
*/
private SnapshotSeed snapshotSeed = SnapshotSeed.restore;
private FacilitiesHandling facilitiesHandling = FacilitiesHandling.snz;
private ActivityHandling activityHandling = ActivityHandling.duringContact;
private Config policyConfig = ConfigFactory.empty();
private Config progressionConfig = ConfigFactory.empty();
private String overwritePolicyLocation = null;
private double maxContacts = 3.;
private int daysInfectious = 4;
private DistrictLevelRestrictions districtLevelRestrictions = DistrictLevelRestrictions.no;
private String districtLevelRestrictionsAttribute = "";
private ContagiousOptimization contagiousContainerOptimization = ContagiousOptimization.no;
private ReportTimeUse reportTimeUse = ReportTimeUse.no;
private SingleEventFile singleEventFile = SingleEventFile.yes;
private boolean endEarly = false;
private int threads = 2;
/**
* Compliance if a curfew is set.
*/
private NavigableMap<LocalDate, Double> curfewCompliance = new TreeMap<>();
/**
* Default constructor.
*/
public EpisimConfigGroup() {
super(GROUPNAME);
}
public String getInputEventsFile() {
List<EventFileParams> list = Lists.newArrayList(getInputEventsFiles());
if (list.size() != 1) {
throw new IllegalStateException("There is not exactly one input event file. Use .getEventFileParams() instead.");
}
return list.get(0).path;
}
/**
* Adds one single input event file for all days of the week.
*/
public void setInputEventsFile(String inputEventsFile) {
clearParameterSetsForType(EventFileParams.SET_TYPE);
addInputEventsFile(inputEventsFile)
.addDays(DayOfWeek.values());
}
@StringSetter(THREADS)
public void setThreads(int threads) {
this.threads = threads;
}
@StringGetter(THREADS)
public int getThreads() {
return threads;
}
@StringGetter(WRITE_EVENTS)
public WriteEvents getWriteEvents() {
return writeEvents;
}
@StringSetter(WRITE_EVENTS)
public void setWriteEvents(WriteEvents writeEvents) {
this.writeEvents = writeEvents;
}
@StringGetter(CALIBRATION_PARAMETER)
public double getCalibrationParameter() {
return this.calibrationParameter;
}
@StringSetter(CALIBRATION_PARAMETER)
public void setCalibrationParameter(double calibrationParameter) {
this.calibrationParameter = calibrationParameter;
}
/**
* Is multiplied with probability to transition to seriously sick in age dependent progression model
*/
@StringGetter(HOSPITAL_FACTOR)
public double getHospitalFactor() {
return this.hospitalFactor;
}
@StringSetter(HOSPITAL_FACTOR)
public void setHospitalFactor(double hospitalFactor) {
this.hospitalFactor = hospitalFactor;
}
@StringGetter(INITIAL_INFECTIONS)
public int getInitialInfections() {
return this.initialInfections;
}
/**
* @param initialInfections -- number of initial infections to start the dynamics. These will be distributed over several days.
* @see #setInfections_pers_per_day(Map)
*/
@StringSetter(INITIAL_INFECTIONS)
public void setInitialInfections(int initialInfections) {
this.initialInfections = initialInfections;
}
@StringGetter(LOWER_AGE_BOUNDARY_FOR_INIT_INFECTIONS)
public int getLowerAgeBoundaryForInitInfections() {
return this.lowerAgeBoundaryForInitInfections;
}
@StringSetter(LOWER_AGE_BOUNDARY_FOR_INIT_INFECTIONS)
public void setLowerAgeBoundaryForInitInfections(int lowerAgeBoundaryForInitInfections) {
this.lowerAgeBoundaryForInitInfections = lowerAgeBoundaryForInitInfections;
}
@StringGetter(UPPER_AGE_BOUNDARY_FOR_INIT_INFECTIONS)
public int getUpperAgeBoundaryForInitInfections() {
return this.upperAgeBoundaryForInitInfections;
}
@StringSetter(UPPER_AGE_BOUNDARY_FOR_INIT_INFECTIONS)
public void setUpperAgeBoundaryForInitInfections(int upperAgeBoundaryForInitInfections) {
this.upperAgeBoundaryForInitInfections = upperAgeBoundaryForInitInfections;
}
public Map<VirusStrain, NavigableMap<LocalDate, Integer>> getInfections_pers_per_day() {
return infectionsPerDay;
}
/**
* @param infectionsPerDay -- From each given date, this will be the number of infections. Until {@link #setInitialInfections(int)} are used up.
*/
public void setInfections_pers_per_day(Map<LocalDate, Integer> infectionsPerDay) {
this.setInfections_pers_per_day(VirusStrain.SARS_CoV_2, infectionsPerDay);
}
public void setInfections_pers_per_day(VirusStrain strain, Map<LocalDate, Integer> infectionsPerDay) {
Map<LocalDate, Integer> perDay = this.infectionsPerDay.computeIfAbsent(strain, (k) -> new TreeMap<>());
// yyyy Is it really so plausible to have this here _and_ the plain integer initial infections? kai, oct'20
// yyyyyy Is it correct that the default of this is empty, so even if someone sets the initial infections to some number, this will not have any effect? kai, nov'20
// No, If no entry is present, 1 will be assumed (because this was default at some point).
// This logic of handling no entries is not part of the config, but the initial infection handler - cr, nov'20
perDay.clear();
perDay.putAll(infectionsPerDay);
}
@StringGetter(INFECTIONS_PER_DAY)
String getInfectionsPerDay() {
Map<VirusStrain, String> collect =
infectionsPerDay.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> JOINER.join(e.getValue())));
return Joiner.on("|").withKeyValueSeparator(">").join(collect);
}
@StringSetter(INFECTIONS_PER_DAY)
void setInfectionsPerDay(String capacity) {
Map<String, String> cap = Splitter.on("|").withKeyValueSeparator(">").split(capacity);
for (Map.Entry<String, String> v : cap.entrySet()) {
if (v.getValue().isBlank()) {
setInfections_pers_per_day(VirusStrain.valueOf(v.getKey()), new TreeMap<>());
continue;
}
Map<String, String> map = SPLITTER.split(v.getValue());
setInfections_pers_per_day(VirusStrain.valueOf(v.getKey()), map.entrySet().stream().collect(Collectors.toMap(
e -> LocalDate.parse(e.getKey()), e -> Integer.parseInt(e.getValue())
)));
}
}
@StringGetter(INITIAL_INFECTION_DISTRICT)
public String getInitialInfectionDistrict() {
return initialInfectionDistrict;
}
@StringSetter(INITIAL_INFECTION_DISTRICT)
public void setInitialInfectionDistrict(String initialInfectionDistrict) {
this.initialInfectionDistrict = initialInfectionDistrict;
}
@StringGetter(START_DATE)
public LocalDate getStartDate() {
return startDate;
}
@StringSetter(START_DATE)
public void setStartDate(String startDate) {
setStartDate(LocalDate.parse(startDate));
}
public void setStartDate(LocalDate startDate) {
this.startDate = startDate;
this.startOffset = EpisimUtils.getStartOffset(startDate);
}
@StringGetter(SNAPSHOT_INTERVAL)
public int getSnapshotInterval() {
return snapshotInterval;
}
@StringSetter(SNAPSHOT_INTERVAL)
public void setSnapshotInterval(int snapshotInterval) {
this.snapshotInterval = snapshotInterval;
}
@StringGetter(START_FROM_SNAPSHOT)
public String getStartFromSnapshot() {
return startFromSnapshot;
}
@StringSetter(START_FROM_SNAPSHOT)
public void setStartFromSnapshot(String startFromSnapshot) {
this.startFromSnapshot = startFromSnapshot;
}
@StringGetter(START_FROM_IMMUNIZATION)
public String getStartFromImmunization() {
return startFromImmunization;
}
@StringSetter(START_FROM_IMMUNIZATION)
public void setStartFromImmunization(String startFromImmunization) {
this.startFromImmunization = startFromImmunization;
}
@StringGetter(SNAPSHOT_PREFIX)
public String getSnapshotPrefix() {
return snapshotPrefix;
}
@StringSetter(SNAPSHOT_PREFIX)
public void setSnapshotPrefix(String snapshotPrefix) {
this.snapshotPrefix = snapshotPrefix;
}
@StringGetter(SNAPSHOT_SEED)
public SnapshotSeed getSnapshotSeed() {
return snapshotSeed;
}
@StringSetter(SNAPSHOT_SEED)
public void setSnapshotSeed(SnapshotSeed snapshotSeed) {
this.snapshotSeed = snapshotSeed;
}
public long getStartOffset() {
return startOffset;
}
/**
* Sample size in relation to whole population, between (0, 1].
*/
@StringGetter(SAMPLE_SIZE)
public double getSampleSize() {
return sampleSize;
}
@StringSetter(SAMPLE_SIZE)
public void setSampleSize(double sampleSize) {
this.sampleSize = sampleSize;
}
@StringGetter("policyConfig")
public String getPolicyConfig() {
if (overwritePolicyLocation != null)
return overwritePolicyLocation;
return policyConfig.origin().filename();
}
/**
* Set the policy config instance.
*/
public void setPolicyConfig(Config policyConfig) {
this.policyConfig = policyConfig;
}
/**
* Sets policy config by loading it from a file first.
*
* @param policyConfig resource of filename to policy
*/
@StringSetter("policyConfig")
public void setPolicyConfig(String policyConfig) {
if (policyConfig == null)
this.policyConfig = ConfigFactory.empty();
else {
File file = new File(policyConfig);
if (!policyConfig.equals("null") && !file.exists())
throw new IllegalArgumentException("Policy config does not exist: " + policyConfig);
this.policyConfig = ConfigFactory.parseFileAnySyntax(file);
}
}
/**
* Overwrite the policy location, which will be returned by {@link #getPolicyConfig()}.
*/
public void setOverwritePolicyLocation(String overwritePolicyLocation) {
this.overwritePolicyLocation = overwritePolicyLocation;
}
/**
* Gets the actual policy configuration.
*/
public Config getPolicy() {
return policyConfig;
}
/**
* Sets policy class and desired config.
* @deprecated set policy class via guice.
* @see #setPolicy(Config)
*/
@Deprecated
public void setPolicy(Class<? extends ShutdownPolicy> policy, Config config) {
this.policyConfig = config;
}
/**
* Sets policy config.
*/
public void setPolicy(Config config) {
this.policyConfig = config;
}
@StringGetter("progressionConfig")
public String getProgressionConfigName() {
if (progressionConfig.origin().filename() != null)
return progressionConfig.origin().filename();
return progressionConfig.origin().description();
}
/**
* Gets the progression config configuration.
*/
public Config getProgressionConfig() {
return progressionConfig;
}
public void setProgressionConfig(Config progressionConfig) {
this.progressionConfig = progressionConfig;
}
/**
* Sets the progression config location as file name.
*/
@StringSetter("progressionConfig")
public void setProgressionConfig(String progressionConfig) {
if (progressionConfig == null)
this.progressionConfig = ConfigFactory.empty();
else {
File file = new File(progressionConfig);
if (!progressionConfig.equals("null") && !file.exists())
throw new IllegalArgumentException("Progression config does not exist: " + progressionConfig);
this.progressionConfig = ConfigFactory.parseFileAnySyntax(file);
}
}
@StringGetter(MAX_CONTACTS)
public double getMaxContacts() {
return maxContacts;
}
@StringSetter(MAX_CONTACTS)
public void setMaxContacts(double maxContacts) {
this.maxContacts = maxContacts;
}
@StringGetter(DAYS_INFECTIOUS)
public int getDaysInfectious() {
return daysInfectious;
}
@StringSetter(DAYS_INFECTIOUS)
public void setDaysInfectious(int daysInfectious) {
this.daysInfectious = daysInfectious;
}
/**
* Sets the leisure outdoor fraction for the whole simulation period.
*/
public void setLeisureOutdoorFraction(double fraction) {
setLeisureOutdoorFraction(Map.of(LocalDate.of(1970, 1, 1), fraction));
}
/**
* Sets the leisure outdoor fraction for individual days. If a day has no entry the values will be interpolated.
*/
public void setLeisureOutdoorFraction(Map<LocalDate, Double> fraction) {
leisureOutdoorFraction.clear();
leisureOutdoorFraction.putAll(fraction);
}
public Map<LocalDate, Double> getLeisureOutdoorFraction() {
return leisureOutdoorFraction;
}
@StringSetter(LEISUREOUTDOORFRACTION)
void setLeisureOutdoorFraction(String capacity) {
Map<String, String> map = SPLITTER.split(capacity);
setLeisureOutdoorFraction(map.entrySet().stream().collect(Collectors.toMap(
e -> LocalDate.parse(e.getKey()), e -> Double.parseDouble(e.getValue())
)));
}
@StringGetter(LEISUREOUTDOORFRACTION)
String getLeisureOutdoorFractionString() {
return JOINER.join(leisureOutdoorFraction);
}
public NavigableMap<LocalDate, Double> getCurfewCompliance() {
return curfewCompliance;
}
public void setCurfewCompliance(Map<LocalDate, Double> curfewCompliance) {
this.curfewCompliance.clear();
this.curfewCompliance.putAll(curfewCompliance);
}
@StringGetter(CURFEW_COMPLIANCE)
String getCurfewComplianceString() {
return JOINER.join(curfewCompliance);
}
@StringSetter(CURFEW_COMPLIANCE)
void setCurfewCompliance(String config) {
Map<String, String> map = SPLITTER.split(config);
setCurfewCompliance(map.entrySet().stream().collect(Collectors.toMap(
e -> LocalDate.parse(e.getKey()), e -> Double.parseDouble(e.getValue())
)));
}
/**
* Get remapping of input days.
*/
public Map<LocalDate, DayOfWeek> getInputDays() {
return inputDays;
}
public void setInputDays(Map<LocalDate, DayOfWeek> days) {
this.inputDays.clear();
this.inputDays.putAll(days);
}
@StringSetter(INPUT_DAYS)
void setInputDays(String days) {
Map<String, String> map = SPLITTER.split(days);
setInputDays(map.entrySet().stream().collect(Collectors.toMap(
e -> LocalDate.parse(e.getKey()), e -> DayOfWeek.valueOf(e.getValue())
)));
}
@StringGetter(INPUT_DAYS)
String getInputDaysString() {
return JOINER.join(inputDays);
}
/**
* Create restriction for each {@link InfectionParams}.
*/
public Map<String, Restriction> createInitialRestrictions() {
Map<String, Restriction> r = new LinkedHashMap<>();
getContainerParams().forEach((s, p) -> r.put(s, Restriction.none()));
return r;
}
@StringGetter("facilitiesHandling")
public FacilitiesHandling getFacilitiesHandling() {
return facilitiesHandling;
}
@StringSetter("facilitiesHandling")
public void setFacilitiesHandling(FacilitiesHandling facilitiesHandling) {
this.facilitiesHandling = facilitiesHandling;
}
@StringGetter(ACTIVITY_HANDLING)
public ActivityHandling getActivityHandling() {
return activityHandling;
}
@StringSetter(ACTIVITY_HANDLING)
public void setActivityHandling(ActivityHandling activityHandling) {
this.activityHandling = activityHandling;
}
/**
* District level restrictions for location based restrictions;
*/
@StringGetter(DISTRICT_LEVEL_RESTRICTIONS)
public DistrictLevelRestrictions getDistrictLevelRestrictions() {
return this.districtLevelRestrictions;
}
@StringSetter(DISTRICT_LEVEL_RESTRICTIONS)
public void setDistrictLevelRestrictions(DistrictLevelRestrictions districtLevelRestrictions) {
this.districtLevelRestrictions = districtLevelRestrictions;
}
@StringGetter(DISTRICT_LEVEL_RESTRICTIONS_ATTRIBUTE)
public String getDistrictLevelRestrictionsAttribute() {
return this.districtLevelRestrictionsAttribute;
}
@StringSetter(DISTRICT_LEVEL_RESTRICTIONS_ATTRIBUTE)
public void setDistrictLevelRestrictionsAttribute(String districtLevelRestrictionsAttribute) {
this.districtLevelRestrictionsAttribute = districtLevelRestrictionsAttribute;
}
@StringGetter(CONTAGIOUS_CONTAINER_OPTIMIZATION)
public ContagiousOptimization getContagiousOptimization() {
return this.contagiousContainerOptimization;
}
@StringSetter(CONTAGIOUS_CONTAINER_OPTIMIZATION)
public void setContagiousOptimization(ContagiousOptimization contagiousOptimization) {
this.contagiousContainerOptimization = contagiousOptimization;
}
@StringGetter(SINGLE_EVENT_FILE)
public SingleEventFile getSingleEventFile() {
return singleEventFile;
}
@StringSetter(SINGLE_EVENT_FILE)
public void setSingleEventFile(SingleEventFile singleEventFile) {
this.singleEventFile = singleEventFile;
}
@StringGetter(REPORT_TIME_USE)
public ReportTimeUse getReportTimeUse() {
return reportTimeUse;
}
@StringSetter(REPORT_TIME_USE)
public void setReportTimeUse(ReportTimeUse reportTimeUse) {
this.reportTimeUse = reportTimeUse;
}
@Override
public void addParameterSet(final ConfigGroup set) {
// this is, I think, necessary for the automatic reading from file, and possibly for the commandline stuff.
switch (set.getName()) {
case InfectionParams.SET_TYPE:
addContainerParams((InfectionParams) set);
break;
case EventFileParams.SET_TYPE:
super.addParameterSet(set);
break;
default:
throw new IllegalArgumentException(set.getName());
}
}
@Override
public ConfigGroup createParameterSet(final String type) {
switch (type) {
case InfectionParams.SET_TYPE:
return new InfectionParams();
case EventFileParams.SET_TYPE:
return new EventFileParams();
default:
throw new IllegalArgumentException(type);
}
}
@Override
protected void checkParameterSet(final ConfigGroup module) {
switch (module.getName()) {
case InfectionParams.SET_TYPE:
if (!(module instanceof InfectionParams)) {
throw new IllegalArgumentException("unexpected class for module " + module);
}
break;
case EventFileParams.SET_TYPE:
if (!(module instanceof EventFileParams)) {
throw new IllegalArgumentException("unexpected class for module " + module);
}
break;
default:
throw new IllegalArgumentException(module.getName());
}
}
/**
* Adds given params to the parameter set, replacing existing ones.
*/
public void addContainerParams(final InfectionParams params) {
final InfectionParams previous = this.getContainerParams().get(params.getContainerName());
Optional<String> match = params.mappedNames.stream().filter(s -> paramsTrie.get(s, TrieMatch.STARTS_WITH) != null).findAny();
if (match.isPresent()) {
throw new IllegalArgumentException("New param for " + match.get() + " matches one of the already present params. Try to define the params in different order.");
}
params.mappedNames.forEach(name -> paramsTrie.put(name, params));
if (previous != null) {
log.info("scoring parameters for activityType=" + previous.getContainerName() + " were just replaced.");
params.mappedNames.forEach(paramsTrie::remove);
final boolean removed = removeParameterSet(previous);
if (!removed)
throw new IllegalStateException("problem replacing params");
}
super.addParameterSet(params);
}
/**
* Returns a container from the parameter set if it exists or creates a new one.
*/
public InfectionParams getOrAddContainerParams(final String containerName, String... mappedNames) {
InfectionParams params = this.getContainerParams().get(containerName);
if (params != null)
return params;
params = new InfectionParams(containerName, mappedNames);
addParameterSet(params);
return params;
}
/**
* Adds an event file to the config.
*/
public EventFileParams addInputEventsFile(final String path) {
for (EventFileParams f : getInputEventsFiles()) {
if (f.path.equals(path)) throw new IllegalArgumentException("Input file already defined: " + path);
}
EventFileParams params = new EventFileParams(path);
addParameterSet(params);
return params;
}
/**
* Removes all defined input files.
*/
public void clearInputEventsFiles() {
clearParameterSetsForType(EventFileParams.SET_TYPE);
}
/**
* Get a copy of container params. Don't use this heavily, it is slow because a new map is created every time.
*/
Map<String, InfectionParams> getContainerParams() {
@SuppressWarnings("unchecked") final Collection<InfectionParams> parameters = (Collection<InfectionParams>) getParameterSets(InfectionParams.SET_TYPE);
final Map<String, InfectionParams> map = new LinkedHashMap<>();
for (InfectionParams pars : parameters) {
if (this.isLocked()) {
pars.setLocked();
}
map.put(pars.getContainerName(), pars);
}
return Collections.unmodifiableMap(map);
}
/**
* Lookup which infection param is relevant for an activity. Throws exception when none was found.
*
* @param activity full activity identifier (including id etc.)
* @return matched infection param
* @throws NoSuchElementException when no param could be matched
*/
@NotNull
public InfectionParams selectInfectionParams(String activity) {
InfectionParams params = paramsTrie.get(activity, TrieMatch.STARTS_WITH);
if (params != null)
return params;
throw new NoSuchElementException(String.format("No params known for activity %s. Please add prefix to one infection parameter.", activity));
}
/**
* Get the {@link InfectionParams} of a container by its name.
*/
public InfectionParams getInfectionParam(String containerName) {
return this.getContainerParams().get(containerName);
}
/**
* All defined infection parameter.
*/
public Collection<InfectionParams> getInfectionParams() {
return (Collection<InfectionParams>) getParameterSets(InfectionParams.SET_TYPE);
}
/**
* All defined input event files.
*/
public Collection<EventFileParams> getInputEventsFiles() {
return (Collection<EventFileParams>) getParameterSets(EventFileParams.SET_TYPE);
}
/**
* Whether simulation ends when there are no further infected persons.
*/
@StringGetter(END_EARLY)
public boolean isEndEarly() {
return endEarly;
}
@StringSetter(END_EARLY)
public void setEndEarly(boolean endEarly) {
this.endEarly = endEarly;
}
/**
* Defines how facilities should be handled.
*/
public enum FacilitiesHandling {
/**
* A facility id will be constructed using the link id where the activity is performed.
*/
bln,
/**
* Facilities ids of activities will be used directly.
*/
snz
}
/**
* Defines which events will be written.
*/
public enum WriteEvents {
/**
* Disable event writing completely.
*/
none,
/**
* Write basic events like infections or disease status change.
*/
episim,
/**
* Write the input event each day.
*/
input,
/**
* Write additional contact tracing events.
*/
tracing,
/**
* Write all, including input events.
*/
all
}
/**
* Defines how the snapshot seed should be processed.
*/
public enum SnapshotSeed {
/**
* Restore rng state from the snapshot and continue as before.
*/
restore,
/**
* Overwrite the rng state with a new seed taken from config.
*/
reseed,
}
/**
* Defines how activity participation is handled.
*/
public enum ActivityHandling {
/**
* Activity participation is randdom during each contact.
*/
duringContact,
/**
* Activity participation is fixed at start of the day.
*/
startOfDay
}
/**
* Decides whether location based restrictions should be implemented
*/
public enum DistrictLevelRestrictions {
yes,
no
}
/**
* In the case that this optimization is enabled, the infectionDynamics
* methods are only called, if a contagious person is in the container
*/
public enum ContagiousOptimization {
yes,
no
}
/**
* The used time tracking costs a lot of CPU cycles, so this
* can be disabled with
*/
public enum ReportTimeUse {
yes,
no
}
/**
* Whether to write all events into a single file.
*/
public enum SingleEventFile {
yes,
no
}
/**
* Parameter set for one activity type.
*/
public static final class InfectionParams extends ReflectiveConfigGroup {
public static final String ACTIVITY_TYPE = "activityType";
public static final String CONTACT_INTENSITY = "contactIntensity";
public static final String SPACES_PER_FACILITY = "nSpacesPerFacility";
public static final String SEASONALITY = "seasonal";
public static final String MAPPED_NAMES = "mappedNames";
static final String SET_TYPE = "infectionParams";
/**
* Name of the container as reference by {@link ShutdownPolicy}.
*/
private String containerName;
/**
* Prefixes of activity names that will be associated with this container type.
*/
private Set<String> mappedNames;
private double contactIntensity = 1.;
/**
* Typical number of distinct spaces per facility.
*/
private double spacesPerFacility = 20.;
/**
* If 1.0, activity type is seasonal; 0.0 means it is not.
*/
private double seasonality = 0.0;
/**
* See {@link #InfectionParams(String, String...)}. Name itself will also be used as prefix.
*/
InfectionParams(final String containerName) {
this();
this.containerName = containerName;
this.mappedNames = Sets.newHashSet(containerName);
}
/**
* Constructor.
*
* @param containerName name of this activity type
* @param mappedNames activity prefixes that will also be mapped to this container
*/
InfectionParams(final String containerName, String... mappedNames) {
this();
this.containerName = containerName;
this.mappedNames = mappedNames.length == 0 ?
Sets.newHashSet(containerName) : Sets.newHashSet(mappedNames);
}
private InfectionParams() {
super(SET_TYPE);
}
@StringGetter(MAPPED_NAMES)
public String getMappedNames() {
return Joiner.on(",").join(mappedNames);
}
@StringSetter(MAPPED_NAMES)
void setMappedNames(String mappedNames) {
this.mappedNames = Sets.newHashSet(mappedNames.split(","));
}
@StringGetter(ACTIVITY_TYPE)
public String getContainerName() {
return containerName;
}
@StringSetter(ACTIVITY_TYPE)
void setContainerName(String actType) {
this.containerName = actType;
}
/**
* this is from iteration 0!
*/
@StringGetter(CONTACT_INTENSITY)
public double getContactIntensity() {
return contactIntensity;
}
/**
* this is from iteration 0!
**/
@StringSetter(CONTACT_INTENSITY)
public InfectionParams setContactIntensity(double contactIntensity) {
this.contactIntensity = contactIntensity;
return this;
}
/**
* Returns the spaces for facilities.
*
* @implNote Don't use this yet, may be removed or renamed.
*/
@Beta
@StringGetter(SPACES_PER_FACILITY)
public double getSpacesPerFacility() {
return spacesPerFacility;
}
@Beta
@StringSetter(SPACES_PER_FACILITY)
public InfectionParams setSpacesPerFacility(double nSpacesPerFacility) {
this.spacesPerFacility = nSpacesPerFacility;
return this;
}
@StringSetter(SEASONALITY)
public InfectionParams setSeasonality(double seasonality) {
this.seasonality = seasonality;
return this;
}
/**
* The extent of an activity's seasonal effects
*/
@StringGetter(SEASONALITY)
public double getSeasonality() {
return seasonality;
}
/**
* Check whether an activity belong to this container group.
*/
public boolean includesActivity(String actType) {
for (String mapped : mappedNames)
if (actType.startsWith(mapped))
return true;
return false;
}
}
/**
* Event file configuration for certain weekdays.
*/
public static final class EventFileParams extends ReflectiveConfigGroup {
public static final String DAYS = "days";
public static final String PATH = "path";
static final String SET_TYPE = "eventFiles";
private final Set<DayOfWeek> days = EnumSet.noneOf(DayOfWeek.class);
private String path;
EventFileParams(String path) {
this();
this.path = path;
}
private EventFileParams() {
super(SET_TYPE);
}
@StringGetter(PATH)
public String getPath() {
return path;
}
@StringSetter(PATH)
void setPath(String path) {
this.path = path;
}
/**
* Adds week days when this event file should be used.
*/
public void addDays(DayOfWeek... days) {
this.days.addAll(Arrays.asList(days));
}
@StringGetter(DAYS)
public Set<DayOfWeek> getDays() {
return days;
}
@StringSetter(DAYS)
public void setDays(String days) {
String str = days.replace("[", "").replace(" ", "").replace("]", "");
this.days.addAll(
Arrays.stream(str.split(",")).map(DayOfWeek::valueOf).collect(Collectors.toSet())
);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EventFileParams that = (EventFileParams) o;
return path.equals(that.path) &&
days.equals(that.days);
}
@Override
public int hashCode() {
return Objects.hash(path, days);
}
}
}