VaccinationConfigGroup.java

  1. package org.matsim.episim;

  2. import com.google.common.base.Joiner;
  3. import com.google.common.base.Splitter;
  4. import org.matsim.core.config.ConfigGroup;
  5. import org.matsim.core.config.ReflectiveConfigGroup;
  6. import org.matsim.episim.model.VaccinationType;
  7. import org.matsim.episim.model.VirusStrain;
  8. import org.matsim.episim.model.vaccination.VaccinationModel;

  9. import java.time.LocalDate;
  10. import java.util.EnumMap;
  11. import java.util.Map;
  12. import java.util.NavigableMap;
  13. import java.util.TreeMap;
  14. import java.util.stream.Collectors;

  15. /**
  16.  * Config option specific to vaccination and measures performed in {@link VaccinationModel}.
  17.  */
  18. public class VaccinationConfigGroup extends ReflectiveConfigGroup {

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

  21.     private static final String COMPLIANCE = "compliance";
  22.     private static final String CAPACITY = "vaccinationCapacity";
  23.     private static final String RECAPACITY = "reVaccinationCapacity";
  24.     private static final String SHARE = "vaccinationShare";
  25.     private static final String FROM_FILE = "vaccinationFile";
  26.     private static final String DAYS_VALID = "daysValid";
  27.     private static final String BETA = "beta";
  28.     private static final String IGA = "IGA";
  29.     private static final String TIME_PERIOD_IGA = "timePeriodIgA";
  30.     private static final String VALID_DEADLINE = "validDeadline";

  31.     private static final String GROUPNAME = "episimVaccination";

  32.     /**
  33.      * Amount of vaccinations available per day.
  34.      */
  35.     private final NavigableMap<LocalDate, Integer> vaccinationCapacity = new TreeMap<>();

  36.     /**
  37.      * Amount of re-vaccinations available per day.
  38.      */
  39.     private final NavigableMap<LocalDate, Integer> reVaccinationCapacity = new TreeMap<>();

  40.     /**
  41.      * Share of vaccination for the different {@link VaccinationType}.
  42.      */
  43.     private final NavigableMap<LocalDate, Map<VaccinationType, Double>> vaccinationShare = new TreeMap<>(Map.of(LocalDate.EPOCH, Map.of(VaccinationType.generic, 1d)));

  44.     /**
  45.      * Load vaccinations from file instead.
  46.      */
  47.     private String fromFile;

  48.     /**
  49.      * Validity of vaccination in days.
  50.      */
  51.     private int daysValid = 180;
  52.     /**
  53.      * Needed for antibody model.
  54.      */
  55.     private double beta = 1.0;

  56.     /**
  57.      * Needed for antibody model.
  58.      */
  59.     private boolean useIgA = false;
  60.     private double timePeriodIgA = 120.;

  61.     /**
  62.      * Deadline after which days valid is in effect.
  63.      */
  64.     private LocalDate validDeadline = LocalDate.of(2022, 2, 1);

  65.     /**
  66.      * Vaccination compliance by age groups. Keys are the left bounds of age group intervals.
  67.      * -1 is used as lookup when no age is present.
  68.      */
  69.     private final NavigableMap<Integer, Double> compliance = new TreeMap<>(Map.of(-1, 1.0));

  70.     /**
  71.      * Holds all specific vaccination params.
  72.      */
  73.     private final Map<VaccinationType, VaccinationParams> params = new EnumMap<>(VaccinationType.class);


  74.     /**
  75.      * Default constructor.
  76.      */
  77.     public VaccinationConfigGroup() {
  78.         super(GROUPNAME);

  79.         // add default params
  80.         getOrAddParams(VaccinationType.generic);
  81.     }

  82.     /**
  83.      * Get config parameter for a specific vaccination type.
  84.      */
  85.     public VaccinationParams getParams(VaccinationType type) {
  86.         if (!params.containsKey(type))
  87.             throw new IllegalStateException("Vaccination type " + type + " is not configured.");

  88.         return params.get(type);
  89.     }

  90.     /**
  91.      * Whether config contains certain type.
  92.      */
  93.     public boolean hasParams(VaccinationType type) {
  94.         return params.containsKey(type);
  95.     }

  96.     /**
  97.      * Get an existing or add new parameter set.
  98.      */
  99.     public VaccinationParams getOrAddParams(VaccinationType type) {
  100.         if (!params.containsKey(type)) {
  101.             VaccinationParams p = new VaccinationParams();
  102.             p.type = type;
  103.             addParameterSet(p);
  104.             return p;
  105.         }

  106.         return params.get(type);
  107.     }

  108.     @Override
  109.     public ConfigGroup createParameterSet(String type) {
  110.         if (VaccinationParams.SET_TYPE.equals(type)) {
  111.             return new VaccinationParams();
  112.         }
  113.         throw new IllegalArgumentException("Unknown type " + type);
  114.     }

  115.     @Override
  116.     public void addParameterSet(final ConfigGroup set) {
  117.         if (VaccinationParams.SET_TYPE.equals(set.getName())) {
  118.             VaccinationParams p = (VaccinationParams) set;
  119.             params.put(p.type, p);
  120.             super.addParameterSet(set);

  121.         } else
  122.             throw new IllegalStateException("Unknown set type " + set.getName());
  123.     }

  124.     /**
  125.      * Set vaccination compliance by age.
  126.      *
  127.      * @see #compliance
  128.      */
  129.     public void setCompliancePerAge(Map<Integer, Double> compliance) {
  130.         this.compliance.clear();
  131.         this.compliance.putAll(compliance);
  132.     }

  133.     /**
  134.      * Get vaccination compliance by age.
  135.      */
  136.     public NavigableMap<Integer, Double> getCompliancePerAge() {
  137.         return compliance;
  138.     }

  139.     @StringSetter(COMPLIANCE)
  140.     void setCompliance(String compliance) {
  141.         Map<String, String> map = SPLITTER.split(compliance);
  142.         setCompliancePerAge(map.entrySet().stream().collect(Collectors.toMap(
  143.                 e -> Integer.parseInt(e.getKey()), e -> Double.parseDouble(e.getValue())
  144.         )));
  145.     }

  146.     @StringGetter(COMPLIANCE)
  147.     String getComplianceString() {
  148.         return JOINER.join(compliance);
  149.     }


  150.     /**
  151.      * Sets the vaccination capacity for individual days. If a day has no entry the previous will be still valid.
  152.      * If empty, default is 0.
  153.      *
  154.      * @param capacity map of dates to changes in capacity.
  155.      */
  156.     public void setVaccinationCapacity_pers_per_day(Map<LocalDate, Integer> capacity) {
  157.         vaccinationCapacity.clear();
  158.         vaccinationCapacity.putAll(capacity);
  159.     }

  160.     public NavigableMap<LocalDate, Integer> getVaccinationCapacity() {
  161.         return vaccinationCapacity;
  162.     }

  163.     @StringSetter(CAPACITY)
  164.     void setVaccinationCapacity(String capacity) {

  165.         if (capacity.isBlank())
  166.             return;

  167.         Map<String, String> map = SPLITTER.split(capacity);
  168.         setVaccinationCapacity_pers_per_day(map.entrySet().stream().collect(Collectors.toMap(
  169.                 e -> LocalDate.parse(e.getKey()), e -> Integer.parseInt(e.getValue())
  170.         )));
  171.     }

  172.     @StringGetter(CAPACITY)
  173.     String getVaccinationCapacityString() {
  174.         return JOINER.join(vaccinationCapacity);
  175.     }

  176.     @StringSetter(FROM_FILE)
  177.     public void setFromFile(String fromFile) {
  178.         this.fromFile = fromFile;
  179.     }

  180.     @StringGetter(FROM_FILE)
  181.     public String getFromFile() {
  182.         return fromFile;
  183.     }

  184.     @StringSetter(DAYS_VALID)
  185.     public void setDaysValid(int daysValid) {
  186.         this.daysValid = daysValid;
  187.     }

  188.     @StringGetter(DAYS_VALID)
  189.     int getDaysValid() {
  190.         return daysValid;
  191.     }

  192.     @StringSetter(BETA)
  193.     public void setBeta(double beta) {
  194.         this.beta = beta;
  195.     }

  196.     @StringGetter(BETA)
  197.     public double getBeta() {
  198.         return beta;
  199.     }

  200.     @StringSetter(IGA)
  201.     public void setUseIgA(boolean useIgA) {
  202.         this.useIgA = useIgA;
  203.     }

  204.     @StringGetter(IGA)
  205.     public boolean getUseIgA() {
  206.         return useIgA;
  207.     }

  208.     @StringSetter(TIME_PERIOD_IGA)
  209.     public void setTimePeriodIgA(double timePeriodIgA) {
  210.         this.timePeriodIgA = timePeriodIgA;
  211.     }

  212.     @StringGetter(TIME_PERIOD_IGA)
  213.     public double getTimePeriodIgA() {
  214.         return this.timePeriodIgA;
  215.     }

  216.     @StringSetter(VALID_DEADLINE)
  217.     public void setValidDeadline(String validDeadline) {
  218.         this.validDeadline = LocalDate.parse(validDeadline);
  219.     }

  220.     public void setValidDeadline(LocalDate validDeadline) {
  221.         this.validDeadline = validDeadline;
  222.     }

  223.     @StringGetter(VALID_DEADLINE)
  224.     public LocalDate getValidDeadline() {
  225.         return validDeadline;
  226.     }

  227.     /**
  228.      * Check if person is recently recovered or vaccinated.
  229.      */
  230.     public boolean hasGreenPass(EpisimPerson person, int day, LocalDate date) {
  231.         return hasGreenPass(person, day, date, daysValid);
  232.     }

  233.     /**
  234.      * Check 2G plus status, but use given {@code daysValid}.
  235.      */
  236.     public boolean hasGreenPass(EpisimPerson person, int day, LocalDate date, int daysValid) {
  237.         return hasRecoveredStatus(person, day, date, daysValid > -1 ? daysValid : this.daysValid) || hasValidVaccination(person, day, date, daysValid > -1 ? daysValid : this.daysValid);
  238.     }

  239.     /**
  240.      * Special type of green pass with separate setting for boostered or equivalent status.
  241.      */
  242.     public boolean hasGreenPassForBooster(EpisimPerson p, int day, LocalDate date, int greenPassValidDays, int greenPassBoosterValidDays) {
  243.         int valid = greenPassValidDays;

  244.         // infected and vaccinated count as booster
  245.         if (p.getReVaccinationStatus() == EpisimPerson.VaccinationStatus.yes || (p.getNumInfections() >= 1 && p.getVaccinationStatus() == EpisimPerson.VaccinationStatus.yes))
  246.             valid = greenPassBoosterValidDays;

  247.         return hasGreenPass(p, day, date, valid);
  248.     }

  249.     /**
  250.      * Check whether person has the recovered status.
  251.      */
  252.     private boolean hasRecoveredStatus(EpisimPerson person, int day, LocalDate date, int daysValid) {
  253.         // Initial the threshold was 180 days, this setting is adjusted to the threshold after the deadline
  254.         return date.isBefore(validDeadline) ? person.isRecentlyRecovered(day, 180) : person.isRecentlyRecovered(day, daysValid);
  255.     }

  256.     /**
  257.      * Check if person has a valid vaccination card.
  258.      *
  259.      * @param person person to check
  260.      * @param day    current simulation day
  261.      * @param date   simulation date
  262.      */
  263.     public boolean hasValidVaccination(EpisimPerson person, int day, LocalDate date) {
  264.         return hasValidVaccination(person, day, date, getDaysValid());
  265.     }

  266.     public boolean hasValidVaccination(EpisimPerson person, int day, LocalDate date, int daysValid) {
  267.         if (person.getVaccinationStatus() == EpisimPerson.VaccinationStatus.no)
  268.             return false;

  269.         boolean fullyVaccinated = person.daysSince(EpisimPerson.VaccinationStatus.yes, day) > getParams(person.getVaccinationType()).getDaysBeforeFullEffect();
  270.         boolean booster = person.getReVaccinationStatus() == EpisimPerson.VaccinationStatus.yes;

  271.         if (date.isBefore(validDeadline))
  272.             return fullyVaccinated || booster;

  273.         return (fullyVaccinated || booster) && person.daysSince(EpisimPerson.VaccinationStatus.yes, day) <= daysValid;

  274.     }

  275.     /**
  276.      * Computes the minimum factor over all vaccinations.
  277.      * @param person person
  278.      * @param day current iteration
  279.      * @param f function of VaccinationParams to retrieve the desired factor
  280.      * @return minimum factor or 1 if not vaccinated
  281.      */
  282.     public double getMinFactor(EpisimPerson person, int day, VaccinationFactorFunction f) {

  283.         if (person.getNumVaccinations() == 0)
  284.             return 1;

  285.         double factor = 1d;
  286.         for (int i = 0; i < person.getNumVaccinations(); i++) {

  287.             VaccinationType type = person.getVaccinationType(i);

  288.             factor = Math.min(factor, f.getFactor(getParams(type), person.getVirusStrain(), person.daysSince(EpisimPerson.VaccinationStatus.yes, day)));
  289.         }

  290.         return factor;
  291.     }

  292.     /**
  293.      * @see #setVaccinationCapacity_pers_per_day(Map)
  294.      */
  295.     public void setReVaccinationCapacity_pers_per_day(Map<LocalDate, Integer> capacity) {
  296.         reVaccinationCapacity.clear();
  297.         reVaccinationCapacity.putAll(capacity);
  298.     }

  299.     public NavigableMap<LocalDate, Integer> getReVaccinationCapacity() {
  300.         return reVaccinationCapacity;
  301.     }

  302.     @StringSetter(RECAPACITY)
  303.     void setReVaccinationCapacity(String capacity) {

  304.         if (capacity.isBlank())
  305.             return;

  306.         Map<String, String> map = SPLITTER.split(capacity);
  307.         setReVaccinationCapacity_pers_per_day(map.entrySet().stream().collect(Collectors.toMap(
  308.                 e -> LocalDate.parse(e.getKey()), e -> Integer.parseInt(e.getValue())
  309.         )));
  310.     }

  311.     @StringGetter(RECAPACITY)
  312.     String getReVaccinationCapacityString() {
  313.         return JOINER.join(reVaccinationCapacity);
  314.     }


  315.     /**
  316.      * Set the vaccination share per date.
  317.      */
  318.     public void setVaccinationShare(Map<LocalDate, Map<VaccinationType, Double>> share) {
  319.         for (Map<VaccinationType, Double> v : share.values()) {
  320.             double total = v.values().stream().sorted().mapToDouble(Double::doubleValue).sum();
  321.             if (total > 1) throw new IllegalArgumentException("Sum of shares must be < 1");
  322.         }

  323.         this.vaccinationShare.clear();
  324.         this.vaccinationShare.putAll(share);
  325.     }

  326.     /**
  327.      * Return vaccination share per date.
  328.      */
  329.     public NavigableMap<LocalDate, Map<VaccinationType, Double>> getVaccinationShare() {
  330.         return vaccinationShare;
  331.     }

  332.     /**
  333.      * Return the cumulative probability for all vaccination types, based on {@link #getVaccinationShare()}.
  334.      *
  335.      * @param date date to lookup
  336.      */
  337.     public Map<VaccinationType, Double> getVaccinationTypeProb(LocalDate date) {

  338.         EnumMap<VaccinationType, Double> prob = new EnumMap<>(VaccinationType.class);

  339.         Map<VaccinationType, Double> share = EpisimUtils.findValidEntry(vaccinationShare, null, date);

  340.         if (share == null)
  341.             share = Map.of(VaccinationType.generic, 1d);

  342.         double total = share.values().stream().sorted().mapToDouble(Double::doubleValue).sum();

  343.         double sum = 1 - total;
  344.         for (VaccinationType t : VaccinationType.values()) {
  345.             sum += share.getOrDefault(t, 0d);
  346.             prob.put(t, sum);
  347.         }

  348.         return prob;
  349.     }

  350.     @StringGetter(SHARE)
  351.     String getVaccinationShareString() {
  352.         Map<LocalDate, String> collect =
  353.                 vaccinationShare.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> JOINER.join(e.getValue())));

  354.         return Joiner.on("|").withKeyValueSeparator(">").join(collect);
  355.     }

  356.     @StringSetter(SHARE)
  357.     void setVaccinationShare(String value) {

  358.         Map<String, String> share = Splitter.on("|").withKeyValueSeparator(">").split(value);
  359.         Map<LocalDate, Map<VaccinationType, Double>> collect = share.entrySet().stream().collect(Collectors.toMap(
  360.                 e -> LocalDate.parse(e.getKey()),
  361.                 e -> SPLITTER.split(e.getValue()).entrySet().stream().collect(Collectors.toMap(k -> VaccinationType.valueOf(k.getKey()), k -> Double.parseDouble(k.getValue())))
  362.         ));

  363.         setVaccinationShare(collect);
  364.     }

  365.     /**
  366.      * Holds strain specific options.
  367.      */
  368.     public static final class VaccinationParams extends ReflectiveConfigGroup {

  369.         static final String SET_TYPE = "vaccinationParams";

  370.         private static final String TYPE = "type";
  371.         private static final String DAYS_BEFORE_FULL_EFFECT = "daysBeforeFullEffect";
  372.         private static final String EFFECTIVENESS = "effectiveness";
  373.         private static final String INFECTIVITY = "infectivity";
  374.         private static final String BOOST_EFFECTIVENESS = "boostEffectiveness";
  375.         private static final String BOOST_INFECTIVITY = "boostInfectivity";
  376.         private static final String BOOST_WAIT_PERIOD = "boostWaitPeriod";
  377.         private static final String FACTOR_SHOWINGS_SYMPTOMS = "factorShowingSymptoms";
  378.         private static final String FACTOR_SERIOUSLY_SICK = "factorSeriouslySick";
  379.         private static final String FACTOR_CRITICAL = "factorCritical";

  380.         private VaccinationType type;

  381.         /**
  382.          * Number of days until vaccination goes into full effect.
  383.          */
  384.         private int daysBeforeFullEffect = 28;

  385.         /**
  386.          * Wait period before boost can be applied.
  387.          */
  388.         private int boostWaitPeriod = 5 * 30;

  389.         /**
  390.          * Effectiveness, i.e. how much susceptibility is reduced.
  391.          */
  392.         private Map<VirusStrain, Parameter> effectiveness = new EnumMap<>(Map.of(VirusStrain.SARS_CoV_2,
  393.                 forStrain(VirusStrain.SARS_CoV_2)
  394.                         .atDay(4, 0)
  395.                         .atDay(5, 0.45)
  396.                         .atFullEffect(0.9)
  397.         ));

  398.         /**
  399.          * Infectivity of a vaccinated person towards others.
  400.          */
  401.         private Map<VirusStrain, Parameter> infectivity = new EnumMap<>(Map.of(VirusStrain.SARS_CoV_2,
  402.                 forStrain(VirusStrain.SARS_CoV_2)
  403.                         .atDay(0, 1)
  404.                         .atFullEffect(1.0)
  405.         ));

  406.         /**
  407.          * Effectiveness after booster shot.
  408.          */
  409.         private Map<VirusStrain, Parameter> boostEffectiveness = new EnumMap<>(VirusStrain.class);

  410.         /**
  411.          * Infectivity of a vaccinated person towards others.
  412.          */
  413.         private Map<VirusStrain, Parameter> boostInfectivity = new EnumMap<>(Map.of(VirusStrain.SARS_CoV_2,
  414.                 forStrain(VirusStrain.SARS_CoV_2)
  415.                         .atDay(0, 1)
  416.                         .atFullEffect(1.0)
  417.         ));

  418.         /**
  419.          * Factor for probability if person is vaccinated.
  420.          */
  421.         private Map<VirusStrain, Parameter> factorShowingSymptoms = new EnumMap<>(Map.of(VirusStrain.SARS_CoV_2,
  422.                 forStrain(VirusStrain.SARS_CoV_2)
  423.                         .atDay(5, 0.5)
  424.         ));

  425.         /**
  426.          * Factor for probability if person is vaccinated.
  427.          */
  428.         private Map<VirusStrain, Parameter> factorSeriouslySick = new EnumMap<>(Map.of(VirusStrain.SARS_CoV_2,
  429.                 forStrain(VirusStrain.SARS_CoV_2)
  430.                         .atDay(5, 0.5)
  431.         ));

  432.         /**
  433.          * Factor for probability if person is vaccinated.
  434.          */
  435.         private Map<VirusStrain, Parameter> factorCritical = new EnumMap<>(Map.of(VirusStrain.SARS_CoV_2,
  436.                 forStrain(VirusStrain.SARS_CoV_2)
  437.                         .atDay(0, 1)
  438.         ));

  439.         VaccinationParams() {
  440.             super(SET_TYPE);
  441.         }

  442.         @StringGetter(TYPE)
  443.         public VaccinationType getType() {
  444.             return type;
  445.         }

  446.         @StringSetter(TYPE)
  447.         public void setType(VaccinationType type) {
  448.             this.type = type;
  449.         }

  450.         @StringGetter(DAYS_BEFORE_FULL_EFFECT)
  451.         public int getDaysBeforeFullEffect() {
  452.             return daysBeforeFullEffect;
  453.         }

  454.         @StringSetter(DAYS_BEFORE_FULL_EFFECT)
  455.         public VaccinationParams setDaysBeforeFullEffect(int daysBeforeFullEffect) {
  456.             this.daysBeforeFullEffect = daysBeforeFullEffect;
  457.             return this;
  458.         }

  459.         @StringSetter(BOOST_WAIT_PERIOD)
  460.         public VaccinationParams setBoostWaitPeriod(int boostWaitPeriod) {
  461.             this.boostWaitPeriod = boostWaitPeriod;
  462.             return this;
  463.         }

  464.         @StringGetter(BOOST_WAIT_PERIOD)
  465.         public int getBoostWaitPeriod() {
  466.             return boostWaitPeriod;
  467.         }

  468.         private VaccinationParams setParamsInternal(Map<VirusStrain, Parameter> map, Parameter[] params) {
  469.             for (Parameter p : params) {
  470.                 for (VirusStrain s : p.strain) {
  471.                     p.setDaysBeforeFullEffect(getDaysBeforeFullEffect());
  472.                     map.put(s, p);
  473.                 }
  474.             }
  475.             return this;
  476.         }

  477.         /**
  478.          * Interpolate parameter for day after vaccination.
  479.          *
  480.          * @param map    map for lookup
  481.          * @param strain virus strain
  482.          * @param day    days since vaccination
  483.          * @return interpolated factor
  484.          */
  485.         private double getParamsInternal(Map<VirusStrain, Parameter> map, VirusStrain strain, int day) {
  486.             Parameter p = map.getOrDefault(strain, map.get(VirusStrain.SARS_CoV_2));
  487.             return p.get(day);
  488.         }

  489.         public VaccinationParams setEffectiveness(Parameter... parameters) {
  490.             return setParamsInternal(effectiveness, parameters);
  491.         }

  492.         public VaccinationParams setInfectivity(Parameter... parameters) {
  493.             return setParamsInternal(infectivity, parameters);
  494.         }

  495.         public VaccinationParams setBoostEffectiveness(Parameter... parameters) {
  496.             return setParamsInternal(boostEffectiveness, parameters);
  497.         }

  498.         public VaccinationParams setBoostInfectivity(Parameter... parameters) {
  499.             return setParamsInternal(boostInfectivity, parameters);
  500.         }

  501.         public VaccinationParams setFactorShowingSymptoms(Parameter... parameters) {
  502.             return setParamsInternal(factorShowingSymptoms, parameters);
  503.         }

  504.         public VaccinationParams setFactorSeriouslySick(Parameter... parameters) {
  505.             return setParamsInternal(factorSeriouslySick, parameters);
  506.         }

  507.         public VaccinationParams setFactorCritical(Parameter... parameters) {
  508.             return setParamsInternal(factorCritical, parameters);
  509.         }

  510.         public double getEffectiveness(VirusStrain strain, int day) {
  511.             return getParamsInternal(effectiveness, strain, day);
  512.         }

  513.         public double getInfectivity(VirusStrain strain, int day) {
  514.             return getParamsInternal(infectivity, strain, day);
  515.         }

  516.         public double getBoostInfectivity(VirusStrain strain, int day) {
  517.             return getParamsInternal(boostInfectivity, strain, day);
  518.         }

  519.         public double getBoostEffectiveness(VirusStrain strain, int day) {
  520.             return getParamsInternal(boostEffectiveness.containsKey(strain) ? boostEffectiveness : effectiveness, strain, day);
  521.         }

  522.         public double getFactorShowingSymptoms(VirusStrain strain, int day) {
  523.             return getParamsInternal(factorShowingSymptoms, strain, day);
  524.         }

  525.         public double getFactorSeriouslySick(VirusStrain strain, int day) {
  526.             return getParamsInternal(factorSeriouslySick, strain, day);
  527.         }

  528.         public double getFactorCritical(VirusStrain strain, int day) {
  529.             return getParamsInternal(factorCritical, strain, day);
  530.         }

  531.         /**
  532.          * Load serialized parameters
  533.          */
  534.         private void setParamsInternal(Map<VirusStrain, Parameter> map, String value) {
  535.             map.clear();
  536.             if (value.isBlank()) return;

  537.             map.clear();
  538.             for (Map.Entry<String, String> e : SPLITTER.split(value).entrySet()) {
  539.                 map.put(VirusStrain.valueOf(e.getKey()), Parameter.parse(e.getValue()));
  540.             }
  541.         }

  542.         private String getParamsInternal(Map<VirusStrain, Parameter> map) {

  543.             Map<VirusStrain, String> result = map.entrySet().stream().collect(Collectors.toMap(
  544.                     Map.Entry::getKey,
  545.                     e -> e.getValue().toString()
  546.             ));

  547.             return JOINER.join(result);
  548.         }

  549.         @StringSetter(EFFECTIVENESS)
  550.         void setEffectiveness(String value) {
  551.             setParamsInternal(effectiveness, value);
  552.         }

  553.         @StringGetter(EFFECTIVENESS)
  554.         String getEffectivenessString() {
  555.             return getParamsInternal(effectiveness);
  556.         }

  557.         @StringSetter(BOOST_EFFECTIVENESS)
  558.         void setBoostEffectiveness(String value) {
  559.             setParamsInternal(boostEffectiveness, value);
  560.         }

  561.         @StringGetter(BOOST_EFFECTIVENESS)
  562.         String getBoostEffectivenessString() {
  563.             return getParamsInternal(boostEffectiveness);
  564.         }

  565.         @StringSetter(FACTOR_SHOWINGS_SYMPTOMS)
  566.         void setFactorShowingSymptoms(String value) {
  567.             setParamsInternal(factorShowingSymptoms, value);
  568.         }

  569.         @StringGetter(FACTOR_SHOWINGS_SYMPTOMS)
  570.         String getFactorShowingSymptoms() {
  571.             return getParamsInternal(factorShowingSymptoms);
  572.         }

  573.         @StringSetter(FACTOR_SERIOUSLY_SICK)
  574.         void setFactorSeriouslySick(String value) {
  575.             setParamsInternal(factorSeriouslySick, value);
  576.         }

  577.         @StringGetter(FACTOR_SERIOUSLY_SICK)
  578.         String getFactorSeriouslySick() {
  579.             return getParamsInternal(factorSeriouslySick);
  580.         }

  581.         @StringSetter(FACTOR_CRITICAL)
  582.         void setFactorCritical(String value) {
  583.             setParamsInternal(factorCritical, value);
  584.         }

  585.         @StringGetter(FACTOR_CRITICAL)
  586.         public String getFactorCritical() {
  587.             return getParamsInternal(factorCritical);
  588.         }

  589.         @StringSetter(INFECTIVITY)
  590.         void setInfectivity(String value) {
  591.             setParamsInternal(infectivity, value);
  592.         }

  593.         @StringGetter(INFECTIVITY)
  594.         public String getInfectivity() {
  595.             return getParamsInternal(infectivity);
  596.         }

  597.         @StringSetter(BOOST_INFECTIVITY)
  598.         void setBoostInfectivity(String value) {
  599.             setParamsInternal(boostInfectivity, value);
  600.         }

  601.         @StringGetter(BOOST_INFECTIVITY)
  602.         public String getBoostInfectivity() {
  603.             return getParamsInternal(boostInfectivity);
  604.         }

  605.         /**
  606.          * Return effectiveness against base variant.
  607.          *
  608.          * @deprecated use {@link #getEffectiveness(VirusStrain, int)}
  609.          */
  610.         @Deprecated
  611.         public double getEffectiveness() {
  612.             return getEffectiveness(VirusStrain.SARS_CoV_2, getDaysBeforeFullEffect());
  613.         }

  614.         /**
  615.          * Return effectiveness against base variant.
  616.          *
  617.          * @deprecated use {@link #setEffectiveness(Parameter...)}
  618.          */
  619.         @Deprecated
  620.         public void setEffectiveness(double effectiveness) {
  621.             throw new UnsupportedOperationException("Use .setEffectiveness(Parameter...)");
  622.         }

  623.         @Deprecated
  624.         public VaccinationParams setFactorSeriouslySick(double factorSeriouslySick) {
  625.             throw new UnsupportedOperationException("Use .setFactorSeriouslySick(Parameter...)");
  626.         }

  627.         @Deprecated
  628.         public VaccinationParams setFactorShowingSymptoms(double factorShowingSymptoms) {
  629.             throw new UnsupportedOperationException("Use .setFactorShowingSymptoms(Parameter...)");
  630.         }

  631.     }

  632.     /**
  633.      * Creates an empty {@link Parameter} progression for one or multiple strain.
  634.      */
  635.     public static Parameter forStrain(VirusStrain... strain) {
  636.         return new Parameter(strain);
  637.     }

  638.     /**
  639.      * Holds the temporal progression of certain value for each virus strains.
  640.      */
  641.     public static final class Parameter {

  642.         private static final Splitter.MapSplitter SPLITTER = Splitter.on("|").withKeyValueSeparator(">");
  643.         private static final Joiner.MapJoiner JOINER = Joiner.on("|").withKeyValueSeparator(">");

  644.         private final VirusStrain[] strain;
  645.         private final NavigableMap<Integer, Double> map = new TreeMap<>();

  646.         private Parameter(VirusStrain[] strain) {
  647.             this.strain = strain;
  648.         }

  649.         private Parameter(Map<String, String> map) {
  650.             this.strain = new VirusStrain[0];
  651.             for (Map.Entry<String, String> e : map.entrySet()) {
  652.                 this.map.put(Integer.parseInt(e.getKey()), Double.parseDouble(e.getValue()));
  653.             }

  654.         }

  655.         /**
  656.          * Sets the value for a parameter at a specific day.
  657.          */
  658.         public Parameter atDay(int day, double value) {
  659.             map.put(day, value);
  660.             return this;
  661.         }


  662.         /**
  663.          * Sets the value for parameter for the day of full effect.
  664.          * {@link VaccinationParams#setDaysBeforeFullEffect(int)} has to be set before calling this method!
  665.          */
  666.         public Parameter atFullEffect(double value) {
  667.             map.put(Integer.MAX_VALUE, value);
  668.             return this;
  669.         }


  670.         /**
  671.          * Interpolate for given day.
  672.          */
  673.         private double get(int day) {

  674.             Map.Entry<Integer, Double> floor = map.floorEntry(day);

  675.             if (floor == null)
  676.                 return map.firstEntry().getValue();

  677.             if (floor.getKey().equals(day))
  678.                 return floor.getValue();

  679.             Map.Entry<Integer, Double> ceil = map.ceilingEntry(day);

  680.             // there is no higher entry to interpolate
  681.             if (ceil == null)
  682.                 return floor.getValue();

  683.             double between = ceil.getKey() - floor.getKey();
  684.             double diff = day - floor.getKey();
  685.             return floor.getValue() + diff * (ceil.getValue() - floor.getValue()) / between;
  686.         }

  687.         private void setDaysBeforeFullEffect(int daysBeforeFullEffect) {
  688.             if (map.containsKey(Integer.MAX_VALUE))
  689.                 map.put(daysBeforeFullEffect, map.remove(Integer.MAX_VALUE));
  690.         }

  691.         @Override
  692.         public String toString() {
  693.             return JOINER.join(map);
  694.         }

  695.         private static Parameter parse(String value) {
  696.             Map<String, String> m = SPLITTER.split(value);
  697.             return new Parameter(m);
  698.         }
  699.     }

  700. }