EpisimConfigGroup.java

  1. /*-
  2.  * #%L
  3.  * MATSim Episim
  4.  * %%
  5.  * Copyright (C) 2020 matsim-org
  6.  * %%
  7.  * This program is free software: you can redistribute it and/or modify
  8.  * it under the terms of the GNU Affero General Public License as published by
  9.  * the Free Software Foundation, either version 3 of the License, or
  10.  * (at your option) any later version.
  11.  *
  12.  * This program is distributed in the hope that it will be useful,
  13.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15.  * GNU General Public License for more details.
  16.  *
  17.  * You should have received a copy of the GNU Affero General Public License
  18.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  19.  * #L%
  20.  */
  21. package org.matsim.episim;

  22. import com.google.common.annotations.Beta;
  23. import com.google.common.base.Joiner;
  24. import com.google.common.base.Splitter;
  25. import com.google.common.collect.Lists;
  26. import com.google.common.collect.Sets;
  27. import com.typesafe.config.Config;
  28. import com.typesafe.config.ConfigFactory;
  29. import org.apache.logging.log4j.LogManager;
  30. import org.apache.logging.log4j.Logger;
  31. import org.magnos.trie.Trie;
  32. import org.magnos.trie.TrieMatch;
  33. import org.magnos.trie.Tries;
  34. import org.matsim.core.config.ConfigGroup;
  35. import org.matsim.core.config.ReflectiveConfigGroup;
  36. import org.matsim.episim.model.VirusStrain;
  37. import org.matsim.episim.policy.Restriction;
  38. import org.matsim.episim.policy.ShutdownPolicy;

  39. import javax.validation.constraints.NotNull;
  40. import java.io.File;
  41. import java.time.DayOfWeek;
  42. import java.time.LocalDate;
  43. import java.util.*;
  44. import java.util.stream.Collectors;

  45. /**
  46.  * Main config for episim.
  47.  */
  48. public final class EpisimConfigGroup extends ReflectiveConfigGroup {

  49.     private static final Splitter.MapSplitter SPLITTER = Splitter.on(";").withKeyValueSeparator("=");
  50.     private static final Joiner.MapJoiner JOINER = Joiner.on(";").withKeyValueSeparator("=");

  51.     private static final String WRITE_EVENTS = "writeEvents";
  52.     private static final String CALIBRATION_PARAMETER = "calibrationParameter";
  53.     private static final String HOSPITAL_FACTOR = "hospitalFactor";
  54.     private static final String INITIAL_INFECTIONS = "initialInfections";
  55.     private static final String INITIAL_INFECTION_DISTRICT = "initialInfectionDistrict";
  56.     private static final String INFECTIONS_PER_DAY = "infectionsPerDay";
  57.     private static final String LOWER_AGE_BOUNDARY_FOR_INIT_INFECTIONS = "lowerAgeBoundaryForInitInfections";
  58.     private static final String UPPER_AGE_BOUNDARY_FOR_INIT_INFECTIONS = "upperAgeBoundaryForInitInfections";
  59.     private static final String MAX_CONTACTS = "maxContacts";
  60.     private static final String SAMPLE_SIZE = "sampleSize";
  61.     private static final String START_DATE = "startDate";
  62.     private static final String SNAPSHOT_INTERVAL = "snapshotInterval";
  63.     private static final String START_FROM_SNAPSHOT = "startFromSnapshot";
  64.     private static final String START_FROM_IMMUNIZATION = "startFromImmunization";
  65.     private static final String SNAPSHOT_PREFIX = "snapshotPrefix";
  66.     private static final String SNAPSHOT_SEED = "snapshotSeed";
  67.     private static final String LEISUREOUTDOORFRACTION = "leisureOutdoorFraction";
  68.     private static final String INPUT_DAYS = "inputDays";
  69.     private static final String DAYS_INFECTIOUS = "daysInfectious";
  70.     private static final String ACTIVITY_HANDLING = "activityHandling";
  71.     private static final String THREADS = "threads";
  72.     private static final String CURFEW_COMPLIANCE = "curfewCompliance";
  73.     private static final String DISTRICT_LEVEL_RESTRICTIONS = "districtLevelRestrictions";
  74.     private static final String DISTRICT_LEVEL_RESTRICTIONS_ATTRIBUTE = "districtLevelRestrictionsAttribute";
  75.     private static final String CONTAGIOUS_CONTAINER_OPTIMIZATION = "contagiousContainerOptimization";
  76.     private static final String REPORT_TIME_USE = "reportTimeUse";
  77.     private static final String SINGLE_EVENT_FILE = "singleEventFile";
  78.     private static final String END_EARLY = "endEarly";

  79.     private static final Logger log = LogManager.getLogger(EpisimConfigGroup.class);
  80.     private static final String GROUPNAME = "episim";

  81.     private final Trie<String, InfectionParams> paramsTrie = Tries.forStrings();

  82.     /**
  83.      * Number of initial infections per day.
  84.      * Default is 1 infection per day for {@link VirusStrain#SARS_CoV_2}.
  85.      */
  86.     private final Map<VirusStrain, NavigableMap<LocalDate, Integer>> infectionsPerDay = new EnumMap<>(Map.of(VirusStrain.SARS_CoV_2, new TreeMap<>()));

  87.     /**
  88.      * Leisure outdoor fractions per day.
  89.      */
  90.     private final Map<LocalDate, Double> leisureOutdoorFraction = new TreeMap<>(Map.of(
  91.             LocalDate.parse("2020-01-15"), 0.1,
  92.             LocalDate.parse("2020-04-15"), 0.8,
  93.             LocalDate.parse("2020-09-15"), 0.8,
  94.             LocalDate.parse("2020-11-15"), 0.1,
  95.             LocalDate.parse("2021-02-15"), 0.1,
  96.             LocalDate.parse("2021-04-15"), 0.8,
  97.             LocalDate.parse("2021-09-15"), 0.8)
  98.     );

  99.     /**
  100.      * Re-mapping of specific dates to different week days events.
  101.      */
  102.     private final Map<LocalDate, DayOfWeek> inputDays = new HashMap<>();

  103.     /**
  104.      * Which events to write in the output.
  105.      */
  106.     private WriteEvents writeEvents = WriteEvents.episim;
  107.     // this is current default for 25% scenarios
  108.     private double calibrationParameter = 0.000002;
  109.     private double hospitalFactor = 1.;
  110.     private double sampleSize = 0.1;
  111.     private int initialInfections = 10;
  112.     private int lowerAgeBoundaryForInitInfections = -1;
  113.     private int upperAgeBoundaryForInitInfections = -1;
  114.     /**
  115.      * If not null, filter persons for initial infection by district.
  116.      */
  117.     private String initialInfectionDistrict = null;
  118.     /**
  119.      * Start date of the simulation (Day 1).
  120.      */
  121.     private LocalDate startDate = LocalDate.of(1970, 1, 1);
  122.     /**
  123.      * Offset of start date in unix epoch seconds.
  124.      */
  125.     private long startOffset = 0;
  126.     /**
  127.      * Write snapshot every x days.
  128.      */
  129.     private int snapshotInterval = 0;
  130.     /**
  131.      * Path to snapshot file.
  132.      */
  133.     private String startFromSnapshot = null;
  134.     private String startFromImmunization = null;

  135.     /**
  136.      * Filename prefix for snapshot.
  137.      */
  138.     private String snapshotPrefix = "episim-snapshot";

  139.     /**
  140.      * How the internal rng state should be handled.
  141.      */
  142.     private SnapshotSeed snapshotSeed = SnapshotSeed.restore;
  143.     private FacilitiesHandling facilitiesHandling = FacilitiesHandling.snz;
  144.     private ActivityHandling activityHandling = ActivityHandling.duringContact;
  145.     private Config policyConfig = ConfigFactory.empty();
  146.     private Config progressionConfig = ConfigFactory.empty();
  147.     private String overwritePolicyLocation = null;
  148.     private double maxContacts = 3.;
  149.     private int daysInfectious = 4;
  150.     private DistrictLevelRestrictions districtLevelRestrictions = DistrictLevelRestrictions.no;
  151.     private String districtLevelRestrictionsAttribute = "";
  152.     private ContagiousOptimization contagiousContainerOptimization = ContagiousOptimization.no;
  153.     private ReportTimeUse reportTimeUse = ReportTimeUse.no;
  154.     private SingleEventFile singleEventFile = SingleEventFile.yes;
  155.     private boolean endEarly = false;
  156.     private int threads = 2;


  157.     /**
  158.      * Compliance if a curfew is set.
  159.      */
  160.     private NavigableMap<LocalDate, Double> curfewCompliance = new TreeMap<>();

  161.     /**
  162.      * Default constructor.
  163.      */
  164.     public EpisimConfigGroup() {
  165.         super(GROUPNAME);
  166.     }

  167.     public String getInputEventsFile() {
  168.         List<EventFileParams> list = Lists.newArrayList(getInputEventsFiles());

  169.         if (list.size() != 1) {
  170.             throw new IllegalStateException("There is not exactly one input event file. Use .getEventFileParams() instead.");
  171.         }

  172.         return list.get(0).path;
  173.     }

  174.     /**
  175.      * Adds one single input event file for all days of the week.
  176.      */
  177.     public void setInputEventsFile(String inputEventsFile) {

  178.         clearParameterSetsForType(EventFileParams.SET_TYPE);
  179.         addInputEventsFile(inputEventsFile)
  180.                 .addDays(DayOfWeek.values());
  181.     }

  182.     @StringSetter(THREADS)
  183.     public void setThreads(int threads) {
  184.         this.threads = threads;
  185.     }

  186.     @StringGetter(THREADS)
  187.     public int getThreads() {
  188.         return threads;
  189.     }

  190.     @StringGetter(WRITE_EVENTS)
  191.     public WriteEvents getWriteEvents() {
  192.         return writeEvents;
  193.     }

  194.     @StringSetter(WRITE_EVENTS)
  195.     public void setWriteEvents(WriteEvents writeEvents) {
  196.         this.writeEvents = writeEvents;
  197.     }

  198.     @StringGetter(CALIBRATION_PARAMETER)
  199.     public double getCalibrationParameter() {
  200.         return this.calibrationParameter;
  201.     }

  202.     @StringSetter(CALIBRATION_PARAMETER)
  203.     public void setCalibrationParameter(double calibrationParameter) {
  204.         this.calibrationParameter = calibrationParameter;
  205.     }

  206.     /**
  207.      * Is multiplied with probability to transition to seriously sick in age dependent progression model
  208.      */
  209.     @StringGetter(HOSPITAL_FACTOR)
  210.     public double getHospitalFactor() {
  211.         return this.hospitalFactor;
  212.     }

  213.     @StringSetter(HOSPITAL_FACTOR)
  214.     public void setHospitalFactor(double hospitalFactor) {
  215.         this.hospitalFactor = hospitalFactor;
  216.     }

  217.     @StringGetter(INITIAL_INFECTIONS)
  218.     public int getInitialInfections() {
  219.         return this.initialInfections;
  220.     }

  221.     /**
  222.      * @param initialInfections -- number of initial infections to start the dynamics.  These will be distributed over several days.
  223.      * @see #setInfections_pers_per_day(Map)
  224.      */
  225.     @StringSetter(INITIAL_INFECTIONS)
  226.     public void setInitialInfections(int initialInfections) {
  227.         this.initialInfections = initialInfections;
  228.     }

  229.     @StringGetter(LOWER_AGE_BOUNDARY_FOR_INIT_INFECTIONS)
  230.     public int getLowerAgeBoundaryForInitInfections() {
  231.         return this.lowerAgeBoundaryForInitInfections;
  232.     }

  233.     @StringSetter(LOWER_AGE_BOUNDARY_FOR_INIT_INFECTIONS)
  234.     public void setLowerAgeBoundaryForInitInfections(int lowerAgeBoundaryForInitInfections) {
  235.         this.lowerAgeBoundaryForInitInfections = lowerAgeBoundaryForInitInfections;
  236.     }

  237.     @StringGetter(UPPER_AGE_BOUNDARY_FOR_INIT_INFECTIONS)
  238.     public int getUpperAgeBoundaryForInitInfections() {
  239.         return this.upperAgeBoundaryForInitInfections;
  240.     }

  241.     @StringSetter(UPPER_AGE_BOUNDARY_FOR_INIT_INFECTIONS)
  242.     public void setUpperAgeBoundaryForInitInfections(int upperAgeBoundaryForInitInfections) {
  243.         this.upperAgeBoundaryForInitInfections = upperAgeBoundaryForInitInfections;
  244.     }

  245.     public Map<VirusStrain, NavigableMap<LocalDate, Integer>> getInfections_pers_per_day() {
  246.         return infectionsPerDay;
  247.     }

  248.     /**
  249.      * @param infectionsPerDay -- From each given date, this will be the number of infections.  Until {@link #setInitialInfections(int)} are used up.
  250.      */
  251.     public void setInfections_pers_per_day(Map<LocalDate, Integer> infectionsPerDay) {
  252.         this.setInfections_pers_per_day(VirusStrain.SARS_CoV_2, infectionsPerDay);
  253.     }

  254.     public void setInfections_pers_per_day(VirusStrain strain, Map<LocalDate, Integer> infectionsPerDay) {

  255.         Map<LocalDate, Integer> perDay = this.infectionsPerDay.computeIfAbsent(strain, (k) -> new TreeMap<>());

  256.         // yyyy Is it really so plausible to have this here _and_ the plain integer initial infections?  kai, oct'20
  257.         // 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
  258.         // No, If no entry is present, 1 will be assumed (because this was default at some point).
  259.         // This logic of handling no entries is not part of the config, but the initial infection handler  - cr, nov'20
  260.         perDay.clear();
  261.         perDay.putAll(infectionsPerDay);
  262.     }

  263.     @StringGetter(INFECTIONS_PER_DAY)
  264.     String getInfectionsPerDay() {
  265.         Map<VirusStrain, String> collect =
  266.                 infectionsPerDay.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> JOINER.join(e.getValue())));

  267.         return Joiner.on("|").withKeyValueSeparator(">").join(collect);
  268.     }

  269.     @StringSetter(INFECTIONS_PER_DAY)
  270.     void setInfectionsPerDay(String capacity) {

  271.         Map<String, String> cap = Splitter.on("|").withKeyValueSeparator(">").split(capacity);

  272.         for (Map.Entry<String, String> v : cap.entrySet()) {

  273.             if (v.getValue().isBlank()) {
  274.                 setInfections_pers_per_day(VirusStrain.valueOf(v.getKey()), new TreeMap<>());
  275.                 continue;
  276.             }

  277.             Map<String, String> map = SPLITTER.split(v.getValue());
  278.             setInfections_pers_per_day(VirusStrain.valueOf(v.getKey()), map.entrySet().stream().collect(Collectors.toMap(
  279.                     e -> LocalDate.parse(e.getKey()), e -> Integer.parseInt(e.getValue())
  280.             )));
  281.         }
  282.     }

  283.     @StringGetter(INITIAL_INFECTION_DISTRICT)
  284.     public String getInitialInfectionDistrict() {
  285.         return initialInfectionDistrict;
  286.     }

  287.     @StringSetter(INITIAL_INFECTION_DISTRICT)
  288.     public void setInitialInfectionDistrict(String initialInfectionDistrict) {
  289.         this.initialInfectionDistrict = initialInfectionDistrict;
  290.     }

  291.     @StringGetter(START_DATE)
  292.     public LocalDate getStartDate() {
  293.         return startDate;
  294.     }

  295.     @StringSetter(START_DATE)
  296.     public void setStartDate(String startDate) {
  297.         setStartDate(LocalDate.parse(startDate));
  298.     }

  299.     public void setStartDate(LocalDate startDate) {
  300.         this.startDate = startDate;
  301.         this.startOffset = EpisimUtils.getStartOffset(startDate);
  302.     }

  303.     @StringGetter(SNAPSHOT_INTERVAL)
  304.     public int getSnapshotInterval() {
  305.         return snapshotInterval;
  306.     }

  307.     @StringSetter(SNAPSHOT_INTERVAL)
  308.     public void setSnapshotInterval(int snapshotInterval) {
  309.         this.snapshotInterval = snapshotInterval;
  310.     }

  311.     @StringGetter(START_FROM_SNAPSHOT)
  312.     public String getStartFromSnapshot() {
  313.         return startFromSnapshot;
  314.     }

  315.     @StringSetter(START_FROM_SNAPSHOT)
  316.     public void setStartFromSnapshot(String startFromSnapshot) {
  317.         this.startFromSnapshot = startFromSnapshot;
  318.     }

  319.     @StringGetter(START_FROM_IMMUNIZATION)
  320.     public String getStartFromImmunization() {
  321.         return startFromImmunization;
  322.     }

  323.     @StringSetter(START_FROM_IMMUNIZATION)
  324.     public void setStartFromImmunization(String startFromImmunization) {
  325.         this.startFromImmunization = startFromImmunization;
  326.     }

  327.     @StringGetter(SNAPSHOT_PREFIX)
  328.     public String getSnapshotPrefix() {
  329.         return snapshotPrefix;
  330.     }

  331.     @StringSetter(SNAPSHOT_PREFIX)
  332.     public void setSnapshotPrefix(String snapshotPrefix) {
  333.         this.snapshotPrefix = snapshotPrefix;
  334.     }

  335.     @StringGetter(SNAPSHOT_SEED)
  336.     public SnapshotSeed getSnapshotSeed() {
  337.         return snapshotSeed;
  338.     }

  339.     @StringSetter(SNAPSHOT_SEED)
  340.     public void setSnapshotSeed(SnapshotSeed snapshotSeed) {
  341.         this.snapshotSeed = snapshotSeed;
  342.     }

  343.     public long getStartOffset() {
  344.         return startOffset;
  345.     }

  346.     /**
  347.      * Sample size in relation to whole population, between (0, 1].
  348.      */
  349.     @StringGetter(SAMPLE_SIZE)
  350.     public double getSampleSize() {
  351.         return sampleSize;
  352.     }

  353.     @StringSetter(SAMPLE_SIZE)
  354.     public void setSampleSize(double sampleSize) {
  355.         this.sampleSize = sampleSize;
  356.     }

  357.     @StringGetter("policyConfig")
  358.     public String getPolicyConfig() {
  359.         if (overwritePolicyLocation != null)
  360.             return overwritePolicyLocation;

  361.         return policyConfig.origin().filename();
  362.     }

  363.     /**
  364.      * Set the policy config instance.
  365.      */
  366.     public void setPolicyConfig(Config policyConfig) {
  367.         this.policyConfig = policyConfig;
  368.     }

  369.     /**
  370.      * Sets policy config by loading it from a file first.
  371.      *
  372.      * @param policyConfig resource of filename to policy
  373.      */
  374.     @StringSetter("policyConfig")
  375.     public void setPolicyConfig(String policyConfig) {
  376.         if (policyConfig == null)
  377.             this.policyConfig = ConfigFactory.empty();
  378.         else {
  379.             File file = new File(policyConfig);
  380.             if (!policyConfig.equals("null") && !file.exists())
  381.                 throw new IllegalArgumentException("Policy config does not exist: " + policyConfig);
  382.             this.policyConfig = ConfigFactory.parseFileAnySyntax(file);
  383.         }
  384.     }

  385.     /**
  386.      * Overwrite the policy location, which will be returned by {@link #getPolicyConfig()}.
  387.      */
  388.     public void setOverwritePolicyLocation(String overwritePolicyLocation) {
  389.         this.overwritePolicyLocation = overwritePolicyLocation;
  390.     }

  391.     /**
  392.      * Gets the actual policy configuration.
  393.      */
  394.     public Config getPolicy() {
  395.         return policyConfig;
  396.     }

  397.     /**
  398.      * Sets policy class and desired config.
  399.      * @deprecated set policy class via guice.
  400.      * @see #setPolicy(Config)
  401.      */
  402.     @Deprecated
  403.     public void setPolicy(Class<? extends ShutdownPolicy> policy, Config config) {
  404.         this.policyConfig = config;
  405.     }

  406.     /**
  407.      * Sets policy config.
  408.      */
  409.     public void setPolicy(Config config) {
  410.         this.policyConfig = config;
  411.     }

  412.     @StringGetter("progressionConfig")
  413.     public String getProgressionConfigName() {
  414.         if (progressionConfig.origin().filename() != null)
  415.             return progressionConfig.origin().filename();

  416.         return progressionConfig.origin().description();
  417.     }

  418.     /**
  419.      * Gets the progression config configuration.
  420.      */
  421.     public Config getProgressionConfig() {
  422.         return progressionConfig;
  423.     }

  424.     public void setProgressionConfig(Config progressionConfig) {
  425.         this.progressionConfig = progressionConfig;
  426.     }

  427.     /**
  428.      * Sets the progression config location as file name.
  429.      */
  430.     @StringSetter("progressionConfig")
  431.     public void setProgressionConfig(String progressionConfig) {
  432.         if (progressionConfig == null)
  433.             this.progressionConfig = ConfigFactory.empty();
  434.         else {
  435.             File file = new File(progressionConfig);
  436.             if (!progressionConfig.equals("null") && !file.exists())
  437.                 throw new IllegalArgumentException("Progression config does not exist: " + progressionConfig);
  438.             this.progressionConfig = ConfigFactory.parseFileAnySyntax(file);
  439.         }
  440.     }

  441.     @StringGetter(MAX_CONTACTS)
  442.     public double getMaxContacts() {
  443.         return maxContacts;
  444.     }

  445.     @StringSetter(MAX_CONTACTS)
  446.     public void setMaxContacts(double maxContacts) {
  447.         this.maxContacts = maxContacts;
  448.     }

  449.     @StringGetter(DAYS_INFECTIOUS)
  450.     public int getDaysInfectious() {
  451.         return daysInfectious;
  452.     }

  453.     @StringSetter(DAYS_INFECTIOUS)
  454.     public void setDaysInfectious(int daysInfectious) {
  455.         this.daysInfectious = daysInfectious;
  456.     }

  457.     /**
  458.      * Sets the leisure outdoor fraction for the whole simulation period.
  459.      */
  460.     public void setLeisureOutdoorFraction(double fraction) {
  461.         setLeisureOutdoorFraction(Map.of(LocalDate.of(1970, 1, 1), fraction));
  462.     }

  463.     /**
  464.      * Sets the leisure outdoor fraction for individual days. If a day has no entry the values will be interpolated.
  465.      */
  466.     public void setLeisureOutdoorFraction(Map<LocalDate, Double> fraction) {
  467.         leisureOutdoorFraction.clear();
  468.         leisureOutdoorFraction.putAll(fraction);
  469.     }

  470.     public Map<LocalDate, Double> getLeisureOutdoorFraction() {
  471.         return leisureOutdoorFraction;
  472.     }

  473.     @StringSetter(LEISUREOUTDOORFRACTION)
  474.     void setLeisureOutdoorFraction(String capacity) {

  475.         Map<String, String> map = SPLITTER.split(capacity);
  476.         setLeisureOutdoorFraction(map.entrySet().stream().collect(Collectors.toMap(
  477.                 e -> LocalDate.parse(e.getKey()), e -> Double.parseDouble(e.getValue())
  478.         )));
  479.     }

  480.     @StringGetter(LEISUREOUTDOORFRACTION)
  481.     String getLeisureOutdoorFractionString() {
  482.         return JOINER.join(leisureOutdoorFraction);
  483.     }


  484.     public NavigableMap<LocalDate, Double> getCurfewCompliance() {
  485.         return curfewCompliance;
  486.     }

  487.     public void setCurfewCompliance(Map<LocalDate, Double> curfewCompliance) {
  488.         this.curfewCompliance.clear();
  489.         this.curfewCompliance.putAll(curfewCompliance);
  490.     }

  491.     @StringGetter(CURFEW_COMPLIANCE)
  492.     String getCurfewComplianceString() {
  493.         return JOINER.join(curfewCompliance);
  494.     }

  495.     @StringSetter(CURFEW_COMPLIANCE)
  496.     void setCurfewCompliance(String config) {
  497.         Map<String, String> map = SPLITTER.split(config);
  498.         setCurfewCompliance(map.entrySet().stream().collect(Collectors.toMap(
  499.                 e -> LocalDate.parse(e.getKey()), e -> Double.parseDouble(e.getValue())
  500.         )));
  501.     }

  502.     /**
  503.      * Get remapping of input days.
  504.      */
  505.     public Map<LocalDate, DayOfWeek> getInputDays() {
  506.         return inputDays;
  507.     }

  508.     public void setInputDays(Map<LocalDate, DayOfWeek> days) {
  509.         this.inputDays.clear();
  510.         this.inputDays.putAll(days);
  511.     }

  512.     @StringSetter(INPUT_DAYS)
  513.     void setInputDays(String days) {
  514.         Map<String, String> map = SPLITTER.split(days);
  515.         setInputDays(map.entrySet().stream().collect(Collectors.toMap(
  516.                 e -> LocalDate.parse(e.getKey()), e -> DayOfWeek.valueOf(e.getValue())
  517.         )));
  518.     }

  519.     @StringGetter(INPUT_DAYS)
  520.     String getInputDaysString() {
  521.         return JOINER.join(inputDays);
  522.     }


  523.     /**
  524.      * Create restriction for each {@link InfectionParams}.
  525.      */
  526.     public Map<String, Restriction> createInitialRestrictions() {
  527.         Map<String, Restriction> r = new LinkedHashMap<>();
  528.         getContainerParams().forEach((s, p) -> r.put(s, Restriction.none()));
  529.         return r;
  530.     }

  531.     @StringGetter("facilitiesHandling")
  532.     public FacilitiesHandling getFacilitiesHandling() {
  533.         return facilitiesHandling;
  534.     }

  535.     @StringSetter("facilitiesHandling")
  536.     public void setFacilitiesHandling(FacilitiesHandling facilitiesHandling) {
  537.         this.facilitiesHandling = facilitiesHandling;
  538.     }


  539.     @StringGetter(ACTIVITY_HANDLING)
  540.     public ActivityHandling getActivityHandling() {
  541.         return activityHandling;
  542.     }

  543.     @StringSetter(ACTIVITY_HANDLING)
  544.     public void setActivityHandling(ActivityHandling activityHandling) {
  545.         this.activityHandling = activityHandling;
  546.     }

  547.     /**
  548.      * District level restrictions for location based restrictions;
  549.      */
  550.     @StringGetter(DISTRICT_LEVEL_RESTRICTIONS)
  551.     public DistrictLevelRestrictions getDistrictLevelRestrictions() {
  552.         return this.districtLevelRestrictions;
  553.     }

  554.     @StringSetter(DISTRICT_LEVEL_RESTRICTIONS)
  555.     public void setDistrictLevelRestrictions(DistrictLevelRestrictions districtLevelRestrictions) {
  556.         this.districtLevelRestrictions = districtLevelRestrictions;
  557.     }

  558.     @StringGetter(DISTRICT_LEVEL_RESTRICTIONS_ATTRIBUTE)
  559.     public String getDistrictLevelRestrictionsAttribute() {
  560.         return this.districtLevelRestrictionsAttribute;
  561.     }

  562.     @StringSetter(DISTRICT_LEVEL_RESTRICTIONS_ATTRIBUTE)
  563.     public void setDistrictLevelRestrictionsAttribute(String districtLevelRestrictionsAttribute) {
  564.         this.districtLevelRestrictionsAttribute = districtLevelRestrictionsAttribute;
  565.     }

  566.     @StringGetter(CONTAGIOUS_CONTAINER_OPTIMIZATION)
  567.     public ContagiousOptimization getContagiousOptimization() {
  568.         return this.contagiousContainerOptimization;
  569.     }

  570.     @StringSetter(CONTAGIOUS_CONTAINER_OPTIMIZATION)
  571.     public void setContagiousOptimization(ContagiousOptimization contagiousOptimization) {
  572.         this.contagiousContainerOptimization = contagiousOptimization;
  573.     }

  574.     @StringGetter(SINGLE_EVENT_FILE)
  575.     public SingleEventFile getSingleEventFile() {
  576.         return singleEventFile;
  577.     }

  578.     @StringSetter(SINGLE_EVENT_FILE)
  579.     public void setSingleEventFile(SingleEventFile singleEventFile) {
  580.         this.singleEventFile = singleEventFile;
  581.     }

  582.     @StringGetter(REPORT_TIME_USE)
  583.     public ReportTimeUse getReportTimeUse() {
  584.         return reportTimeUse;
  585.     }

  586.     @StringSetter(REPORT_TIME_USE)
  587.     public void setReportTimeUse(ReportTimeUse reportTimeUse) {
  588.         this.reportTimeUse = reportTimeUse;
  589.     }


  590.     @Override
  591.     public void addParameterSet(final ConfigGroup set) {
  592.         // this is, I think, necessary for the automatic reading from file, and possibly for the commandline stuff.
  593.         switch (set.getName()) {
  594.             case InfectionParams.SET_TYPE:
  595.                 addContainerParams((InfectionParams) set);
  596.                 break;
  597.             case EventFileParams.SET_TYPE:
  598.                 super.addParameterSet(set);
  599.                 break;
  600.             default:
  601.                 throw new IllegalArgumentException(set.getName());
  602.         }
  603.     }

  604.     @Override
  605.     public ConfigGroup createParameterSet(final String type) {
  606.         switch (type) {
  607.             case InfectionParams.SET_TYPE:
  608.                 return new InfectionParams();
  609.             case EventFileParams.SET_TYPE:
  610.                 return new EventFileParams();
  611.             default:
  612.                 throw new IllegalArgumentException(type);
  613.         }
  614.     }

  615.     @Override
  616.     protected void checkParameterSet(final ConfigGroup module) {
  617.         switch (module.getName()) {
  618.             case InfectionParams.SET_TYPE:
  619.                 if (!(module instanceof InfectionParams)) {
  620.                     throw new IllegalArgumentException("unexpected class for module " + module);
  621.                 }
  622.                 break;
  623.             case EventFileParams.SET_TYPE:
  624.                 if (!(module instanceof EventFileParams)) {
  625.                     throw new IllegalArgumentException("unexpected class for module " + module);
  626.                 }
  627.                 break;
  628.             default:
  629.                 throw new IllegalArgumentException(module.getName());
  630.         }
  631.     }

  632.     /**
  633.      * Adds given params to the parameter set, replacing existing ones.
  634.      */
  635.     public void addContainerParams(final InfectionParams params) {
  636.         final InfectionParams previous = this.getContainerParams().get(params.getContainerName());

  637.         Optional<String> match = params.mappedNames.stream().filter(s -> paramsTrie.get(s, TrieMatch.STARTS_WITH) != null).findAny();
  638.         if (match.isPresent()) {
  639.             throw new IllegalArgumentException("New param for " + match.get() + " matches one of the already present params. Try to define the params in different order.");
  640.         }

  641.         params.mappedNames.forEach(name -> paramsTrie.put(name, params));

  642.         if (previous != null) {
  643.             log.info("scoring parameters for activityType=" + previous.getContainerName() + " were just replaced.");

  644.             params.mappedNames.forEach(paramsTrie::remove);

  645.             final boolean removed = removeParameterSet(previous);
  646.             if (!removed)
  647.                 throw new IllegalStateException("problem replacing params");
  648.         }

  649.         super.addParameterSet(params);
  650.     }

  651.     /**
  652.      * Returns a container from the parameter set if it exists or creates a new one.
  653.      */
  654.     public InfectionParams getOrAddContainerParams(final String containerName, String... mappedNames) {
  655.         InfectionParams params = this.getContainerParams().get(containerName);

  656.         if (params != null)
  657.             return params;

  658.         params = new InfectionParams(containerName, mappedNames);

  659.         addParameterSet(params);
  660.         return params;
  661.     }

  662.     /**
  663.      * Adds an event file to the config.
  664.      */
  665.     public EventFileParams addInputEventsFile(final String path) {

  666.         for (EventFileParams f : getInputEventsFiles()) {
  667.             if (f.path.equals(path)) throw new IllegalArgumentException("Input file already defined: " + path);
  668.         }

  669.         EventFileParams params = new EventFileParams(path);
  670.         addParameterSet(params);
  671.         return params;
  672.     }

  673.     /**
  674.      * Removes all defined input files.
  675.      */
  676.     public void clearInputEventsFiles() {
  677.         clearParameterSetsForType(EventFileParams.SET_TYPE);
  678.     }

  679.     /**
  680.      * Get a copy of container params. Don't use this heavily, it is slow because a new map is created every time.
  681.      */
  682.     Map<String, InfectionParams> getContainerParams() {
  683.         @SuppressWarnings("unchecked") final Collection<InfectionParams> parameters = (Collection<InfectionParams>) getParameterSets(InfectionParams.SET_TYPE);
  684.         final Map<String, InfectionParams> map = new LinkedHashMap<>();

  685.         for (InfectionParams pars : parameters) {
  686.             if (this.isLocked()) {
  687.                 pars.setLocked();
  688.             }
  689.             map.put(pars.getContainerName(), pars);
  690.         }

  691.         return Collections.unmodifiableMap(map);
  692.     }

  693.     /**
  694.      * Lookup which infection param is relevant for an activity. Throws exception when none was found.
  695.      *
  696.      * @param activity full activity identifier (including id etc.)
  697.      * @return matched infection param
  698.      * @throws NoSuchElementException when no param could be matched
  699.      */
  700.     @NotNull
  701.     public InfectionParams selectInfectionParams(String activity) {

  702.         InfectionParams params = paramsTrie.get(activity, TrieMatch.STARTS_WITH);
  703.         if (params != null)
  704.             return params;

  705.         throw new NoSuchElementException(String.format("No params known for activity %s. Please add prefix to one infection parameter.", activity));
  706.     }

  707.     /**
  708.      * Get the {@link InfectionParams} of a container by its name.
  709.      */
  710.     public InfectionParams getInfectionParam(String containerName) {
  711.         return this.getContainerParams().get(containerName);
  712.     }

  713.     /**
  714.      * All defined infection parameter.
  715.      */
  716.     public Collection<InfectionParams> getInfectionParams() {
  717.         return (Collection<InfectionParams>) getParameterSets(InfectionParams.SET_TYPE);
  718.     }

  719.     /**
  720.      * All defined input event files.
  721.      */
  722.     public Collection<EventFileParams> getInputEventsFiles() {
  723.         return (Collection<EventFileParams>) getParameterSets(EventFileParams.SET_TYPE);
  724.     }

  725.     /**
  726.      * Whether simulation ends when there are no further infected persons.
  727.      */
  728.     @StringGetter(END_EARLY)
  729.     public boolean isEndEarly() {
  730.         return endEarly;
  731.     }

  732.     @StringSetter(END_EARLY)
  733.     public void setEndEarly(boolean endEarly) {
  734.         this.endEarly = endEarly;
  735.     }

  736.     /**
  737.      * Defines how facilities should be handled.
  738.      */
  739.     public enum FacilitiesHandling {
  740.         /**
  741.          * A facility id will be constructed using the link id where the activity is performed.
  742.          */
  743.         bln,
  744.         /**
  745.          * Facilities ids of activities will be used directly.
  746.          */
  747.         snz
  748.     }

  749.     /**
  750.      * Defines which events will be written.
  751.      */
  752.     public enum WriteEvents {
  753.         /**
  754.          * Disable event writing completely.
  755.          */
  756.         none,
  757.         /**
  758.          * Write basic events like infections or disease status change.
  759.          */
  760.         episim,

  761.         /**
  762.          * Write the input event each day.
  763.          */
  764.         input,

  765.         /**
  766.          * Write additional contact tracing events.
  767.          */
  768.         tracing,
  769.         /**
  770.          * Write all, including input events.
  771.          */
  772.         all
  773.     }

  774.     /**
  775.      * Defines how the snapshot seed should be processed.
  776.      */
  777.     public enum SnapshotSeed {
  778.         /**
  779.          * Restore rng state from the snapshot and continue as before.
  780.          */
  781.         restore,

  782.         /**
  783.          * Overwrite the rng state with a new seed taken from config.
  784.          */
  785.         reseed,
  786.     }

  787.     /**
  788.      * Defines how activity participation is handled.
  789.      */
  790.     public enum ActivityHandling {

  791.         /**
  792.          * Activity participation is randdom during each contact.
  793.          */
  794.         duringContact,

  795.         /**
  796.          * Activity participation is fixed at start of the day.
  797.          */
  798.         startOfDay

  799.     }


  800.     /**
  801.      * Decides whether location based restrictions should be implemented
  802.      */
  803.     public enum DistrictLevelRestrictions {
  804.         yes,
  805.         no
  806.     }


  807.     /**
  808.      * In the case that this optimization is enabled, the infectionDynamics
  809.      * methods are only called, if a contagious person is in the container
  810.      */
  811.     public enum ContagiousOptimization {
  812.         yes,
  813.         no
  814.     }

  815.     /**
  816.      * The used time tracking costs a lot of CPU cycles, so this
  817.      * can be disabled with
  818.      */
  819.     public enum ReportTimeUse {
  820.         yes,
  821.         no
  822.     }


  823.     /**
  824.      * Whether to write all events into a single file.
  825.      */
  826.     public enum SingleEventFile {
  827.         yes,
  828.         no
  829.     }

  830.     /**
  831.      * Parameter set for one activity type.
  832.      */
  833.     public static final class InfectionParams extends ReflectiveConfigGroup {
  834.         public static final String ACTIVITY_TYPE = "activityType";
  835.         public static final String CONTACT_INTENSITY = "contactIntensity";
  836.         public static final String SPACES_PER_FACILITY = "nSpacesPerFacility";
  837.         public static final String SEASONALITY = "seasonal";
  838.         public static final String MAPPED_NAMES = "mappedNames";

  839.         static final String SET_TYPE = "infectionParams";
  840.         /**
  841.          * Name of the container as reference by {@link ShutdownPolicy}.
  842.          */
  843.         private String containerName;

  844.         /**
  845.          * Prefixes of activity names that will be associated with this container type.
  846.          */
  847.         private Set<String> mappedNames;
  848.         private double contactIntensity = 1.;

  849.         /**
  850.          * Typical number of distinct spaces per facility.
  851.          */
  852.         private double spacesPerFacility = 20.;

  853.         /**
  854.          * If 1.0, activity type is seasonal; 0.0 means it is not.
  855.          */
  856.         private double seasonality = 0.0;

  857.         /**
  858.          * See {@link #InfectionParams(String, String...)}. Name itself will also be used as prefix.
  859.          */
  860.         InfectionParams(final String containerName) {
  861.             this();
  862.             this.containerName = containerName;
  863.             this.mappedNames = Sets.newHashSet(containerName);
  864.         }

  865.         /**
  866.          * Constructor.
  867.          *
  868.          * @param containerName name of this activity type
  869.          * @param mappedNames   activity prefixes that will also be mapped to this container
  870.          */
  871.         InfectionParams(final String containerName, String... mappedNames) {
  872.             this();
  873.             this.containerName = containerName;
  874.             this.mappedNames = mappedNames.length == 0 ?
  875.                     Sets.newHashSet(containerName) : Sets.newHashSet(mappedNames);
  876.         }

  877.         private InfectionParams() {
  878.             super(SET_TYPE);
  879.         }

  880.         @StringGetter(MAPPED_NAMES)
  881.         public String getMappedNames() {
  882.             return Joiner.on(",").join(mappedNames);
  883.         }

  884.         @StringSetter(MAPPED_NAMES)
  885.         void setMappedNames(String mappedNames) {
  886.             this.mappedNames = Sets.newHashSet(mappedNames.split(","));
  887.         }

  888.         @StringGetter(ACTIVITY_TYPE)
  889.         public String getContainerName() {
  890.             return containerName;
  891.         }

  892.         @StringSetter(ACTIVITY_TYPE)
  893.         void setContainerName(String actType) {
  894.             this.containerName = actType;
  895.         }

  896.         /**
  897.          * this is from iteration 0!
  898.          */
  899.         @StringGetter(CONTACT_INTENSITY)
  900.         public double getContactIntensity() {
  901.             return contactIntensity;
  902.         }

  903.         /**
  904.          * this is from iteration 0!
  905.          **/
  906.         @StringSetter(CONTACT_INTENSITY)
  907.         public InfectionParams setContactIntensity(double contactIntensity) {
  908.             this.contactIntensity = contactIntensity;
  909.             return this;
  910.         }

  911.         /**
  912.          * Returns the spaces for facilities.
  913.          *
  914.          * @implNote Don't use this yet, may be removed or renamed.
  915.          */
  916.         @Beta
  917.         @StringGetter(SPACES_PER_FACILITY)
  918.         public double getSpacesPerFacility() {
  919.             return spacesPerFacility;
  920.         }

  921.         @Beta
  922.         @StringSetter(SPACES_PER_FACILITY)
  923.         public InfectionParams setSpacesPerFacility(double nSpacesPerFacility) {
  924.             this.spacesPerFacility = nSpacesPerFacility;
  925.             return this;
  926.         }

  927.         @StringSetter(SEASONALITY)
  928.         public InfectionParams setSeasonality(double seasonality) {
  929.             this.seasonality = seasonality;
  930.             return this;
  931.         }

  932.         /**
  933.          * The extent of an activity's seasonal effects
  934.          */
  935.         @StringGetter(SEASONALITY)
  936.         public double getSeasonality() {
  937.             return seasonality;
  938.         }

  939.         /**
  940.          * Check whether an activity belong to this container group.
  941.          */
  942.         public boolean includesActivity(String actType) {
  943.             for (String mapped : mappedNames)
  944.                 if (actType.startsWith(mapped))
  945.                     return true;

  946.             return false;
  947.         }

  948.     }

  949.     /**
  950.      * Event file configuration for certain weekdays.
  951.      */
  952.     public static final class EventFileParams extends ReflectiveConfigGroup {

  953.         public static final String DAYS = "days";
  954.         public static final String PATH = "path";
  955.         static final String SET_TYPE = "eventFiles";
  956.         private final Set<DayOfWeek> days = EnumSet.noneOf(DayOfWeek.class);
  957.         private String path;

  958.         EventFileParams(String path) {
  959.             this();
  960.             this.path = path;
  961.         }

  962.         private EventFileParams() {
  963.             super(SET_TYPE);
  964.         }

  965.         @StringGetter(PATH)
  966.         public String getPath() {
  967.             return path;
  968.         }

  969.         @StringSetter(PATH)
  970.         void setPath(String path) {
  971.             this.path = path;
  972.         }

  973.         /**
  974.          * Adds week days when this event file should be used.
  975.          */
  976.         public void addDays(DayOfWeek... days) {
  977.             this.days.addAll(Arrays.asList(days));
  978.         }

  979.         @StringGetter(DAYS)
  980.         public Set<DayOfWeek> getDays() {
  981.             return days;
  982.         }

  983.         @StringSetter(DAYS)
  984.         public void setDays(String days) {
  985.             String str = days.replace("[", "").replace(" ", "").replace("]", "");

  986.             this.days.addAll(
  987.                     Arrays.stream(str.split(",")).map(DayOfWeek::valueOf).collect(Collectors.toSet())
  988.             );
  989.         }

  990.         @Override
  991.         public boolean equals(Object o) {
  992.             if (this == o) return true;
  993.             if (o == null || getClass() != o.getClass()) return false;
  994.             EventFileParams that = (EventFileParams) o;
  995.             return path.equals(that.path) &&
  996.                     days.equals(that.days);
  997.         }

  998.         @Override
  999.         public int hashCode() {
  1000.             return Objects.hash(path, days);
  1001.         }
  1002.     }
  1003. }