DefaultAntibodyModel.java

package org.matsim.episim.model;


import com.google.inject.Inject;
import org.matsim.episim.EpisimConfigGroup;
import org.matsim.episim.EpisimPerson;
import org.matsim.episim.EpisimUtils;

import java.util.Collection;
import java.util.SplittableRandom;

public class DefaultAntibodyModel implements AntibodyModel {

	public static final double HALF_LIFE_DAYS = 60; // todo: would 40 work better?

	// optimistic: 46
	//

	private final AntibodyModel.Config antibodyConfig;
	private final SplittableRandom localRnd;

	private final EpisimConfigGroup episimConfig;


	@Inject
	DefaultAntibodyModel(AntibodyModel.Config antibodyConfig, EpisimConfigGroup episimConfigGroup) {
		this.antibodyConfig = antibodyConfig;
		this.episimConfig = episimConfigGroup;
		localRnd = new SplittableRandom(2938); // todo: should it be a fixed seed, i.e not change btwn snapshots


	}

	@Override
	public void init(Collection<EpisimPerson> persons, int iteration) {

		for (EpisimPerson person : persons) {

			// mu = log(median); log(1)=0


			// we assume immune response multiplier follows log-normal distribution, bounded by 0.01 and 10.
			double immuneResponseMultiplier = 0;
			while (immuneResponseMultiplier < 0.1 || immuneResponseMultiplier > 10) {
				immuneResponseMultiplier = EpisimUtils.nextLogNormal(localRnd, 0, antibodyConfig.getImmuneReponseSigma());
			}

			person.setImmuneResponseMultiplier(immuneResponseMultiplier);

		}


	}

	@Override
	public void recalculateAntibodiesAfterSnapshot(Collection<EpisimPerson> persons, int iteration) {
		for (EpisimPerson person : persons) {

			// reset to 0.0
			for (VirusStrain strain : VirusStrain.values()) {
				person.setAntibodies(strain, 0.0);
			}

			// recalculate antibodies from immune history
			for (int it = 1; it < iteration; it++) {
				updateAntibodies(person, it);

			}
		}
	}

	/**
	 * Updates the antibody levels for person. If an immunity event occurs (vaccination or infection) on the previous
	 * day, antibodies will increase. If not, they will decrease. This method was designed to also recalculate antibodies
	 * when the simulation is started from snapshot.
	 *
	 * @param person person whose antibodies to update
	 * @param day    current day / iteration
	 */
	@Override
	public void updateAntibodies(EpisimPerson person, int day) {

		//handle vaccination
		if (person.getVaccinationDates().contains(day - 1)) {
			int vaccinationIndex = person.getVaccinationDates().indexOf(day - 1);
			VaccinationType vaccinationType = person.getVaccinationType(vaccinationIndex);
			handleImmunization(person, vaccinationType);
			return;
		}

		// handle infection
		for (int infectionIndex = 0; infectionIndex < person.getNumInfections(); infectionIndex++) {
			if (person.daysSinceInfection(infectionIndex, day) == 1) {
				VirusStrain virusStrain = person.getVirusStrain(infectionIndex);
				handleImmunization(person, virusStrain);
				return;
			}
		}

		double halflifeDays = HALF_LIFE_DAYS;

		if ((person.getNumInfections() > 0 && person.getNumVaccinations() > 0)
			|| person.getNumInfections() > 4
			|| person.getNumVaccinations() > 3) {

			halflifeDays *= antibodyConfig.hlMultiForInfected; // 1, 2, 5
		}

		// if no immunity event: exponential decay, day by day:
		for (VirusStrain strain : VirusStrain.values()) {
			double oldAntibodyLevel = person.getAntibodies(strain);
			person.setAntibodies(strain, oldAntibodyLevel * Math.pow(0.5, 1 / halflifeDays));
		}

	}

	private void handleImmunization(EpisimPerson person, ImmunityEvent immunityEventType) {

		boolean firstImmunization = checkFirstImmunization(person);
		// 1st immunization:
		if (firstImmunization) {

			for (VirusStrain strain2 : VirusStrain.values()) {
				double antibodies = antibodyConfig.initialAntibodies.get(immunityEventType).get(strain2);

				antibodies = Math.min(150., antibodies * person.getImmuneResponseMultiplier());

				person.setAntibodies(strain2, antibodies);

				// if antibodies against a strain2 are higher than previous maximum, replace maximum
				// should always be the case for initial immunization
				person.updateMaxAntibodies(strain2, antibodies);
			}


		} else {
			for (VirusStrain strain2 : VirusStrain.values()) {
				double refreshFactor = antibodyConfig.antibodyRefreshFactors.get(immunityEventType).get(strain2);

				// antibodies before refresh
				double antibodies = person.getAntibodies(strain2);

				// refresh antibodies
				antibodies = antibodies * refreshFactor;

				// check that new antibody level at least as high as initial antibodies
				double initialAntibodies = antibodyConfig.initialAntibodies.get(immunityEventType).get(strain2) * person.getImmuneResponseMultiplier();
				antibodies = Math.max(antibodies, initialAntibodies);

				// check that new antibody level is at most 150
				antibodies = Math.min(150., antibodies);

				person.setAntibodies(strain2, antibodies);

				// if antibodies against a strain2 are higher than previous maximum, replace maximum
				person.updateMaxAntibodies(strain2, antibodies);
			}
		}
	}

	private boolean checkFirstImmunization(EpisimPerson person) {
		boolean firstImmunization = true;
		for (double abLevel : person.getAntibodies().values()) {
			if (abLevel > 0) {
				firstImmunization = false;
				break;
			}
		}
		return firstImmunization;
	}

}