AntibodyDependentTransitionModel.java

package org.matsim.episim.model.progression;

import com.google.inject.Inject;
import org.matsim.episim.EpisimPerson;
import org.matsim.episim.EpisimPerson.DiseaseStatus;
import org.matsim.episim.Immunizable;
import org.matsim.episim.VaccinationConfigGroup;
import org.matsim.episim.VirusStrainConfigGroup;
import org.matsim.episim.model.VirusStrain;

import java.util.SplittableRandom;

public class AntibodyDependentTransitionModel implements DiseaseStatusTransitionModel {

	private final SplittableRandom rnd;
	private final VaccinationConfigGroup vaccinationConfig;
	private final VirusStrainConfigGroup strainConfig;

	@Inject
	public AntibodyDependentTransitionModel(SplittableRandom rnd, VaccinationConfigGroup vaccinationConfig,
	                                           VirusStrainConfigGroup strainConfigGroup) {
		this.rnd = rnd;
		this.vaccinationConfig = vaccinationConfig;
		this.strainConfig = strainConfigGroup;
	}

	@Override
	public final EpisimPerson.DiseaseStatus decideNextState(EpisimPerson person, EpisimPerson.DiseaseStatus status, int day) {

		switch (status) {
			case infectedButNotContagious:
				return EpisimPerson.DiseaseStatus.contagious;
			case contagious:

				if (rnd.nextDouble() < getProbaOfTransitioningToShowingSymptoms(person) * getShowingSymptomsFactor(person, vaccinationConfig, day))
					return EpisimPerson.DiseaseStatus.showingSymptoms;
				else
					return EpisimPerson.DiseaseStatus.recovered;

			case showingSymptoms:
				if (rnd.nextDouble() < getProbaOfTransitioningToSeriouslySick(person)
						* (person.getVaccinationStatus() == EpisimPerson.VaccinationStatus.yes ?
						strainConfig.getParams(person.getVirusStrain()).getFactorSeriouslySickVaccinated() :
						strainConfig.getParams(person.getVirusStrain()).getFactorSeriouslySick())
						* getSeriouslySickFactor(person, vaccinationConfig, day))
//						* (person.getNumInfections() > 1 ? getFactorRecovered(person, day) : 1.0))
					return EpisimPerson.DiseaseStatus.seriouslySick;
				else
					return EpisimPerson.DiseaseStatus.recovered;

			case seriouslySick:
				if (!person.hadDiseaseStatus(EpisimPerson.DiseaseStatus.critical)
						&& (rnd.nextDouble() < getProbaOfTransitioningToCritical(person) * strainConfig.getParams(person.getVirusStrain()).getFactorCritical()
						* getCriticalFactor(person, vaccinationConfig, day)))
					return EpisimPerson.DiseaseStatus.critical;
				else
					return EpisimPerson.DiseaseStatus.recovered;

			case critical:
				double proba = getProbaOfTransitioningToDeceased(person);
				if (proba != 0 && rnd.nextDouble() < proba)
					return DiseaseStatus.deceased;
				else
					return EpisimPerson.DiseaseStatus.seriouslySickAfterCritical;

			case seriouslySickAfterCritical:
				return EpisimPerson.DiseaseStatus.recovered;

			case recovered:
				return EpisimPerson.DiseaseStatus.susceptible;

			default:
				throw new IllegalStateException("No state transition defined for " + person.getDiseaseStatus());
		}
	}

	/**
	 * Probability that a person transitions from {@code showingSymptoms} to {@code seriouslySick} when person was already infected.
	 */
	protected double getFactorRecovered(EpisimPerson person, int day) {

		int daysSince = person.daysSince(DiseaseStatus.recovered, day);

		//we assume about 20% loss of protection against severe progression every year
		return Math.min(0.2 * (daysSince / 365), 1.0);
	}

	/**
	 * Probability that a persons transitions from {@code showingSymptoms} to {@code seriouslySick}.
	 */
	public double getProbaOfTransitioningToSeriouslySick(Immunizable person) {
		return 0.05625;
	}

	/**
	 * Probability that a persons transitions from {@code seriouslySick} to {@code critical}.
	 */
	public double getProbaOfTransitioningToCritical(Immunizable person) {
		return 0.25;
	}

	protected double getProbaOfTransitioningToShowingSymptoms(EpisimPerson person) {
		return 0.8;
	}

	@Override
	public double getShowingSymptomsFactor(EpisimPerson person, VaccinationConfigGroup vaccinationConfig, int day) {
		return 1.0;
	}

	@Override
	public double getSeriouslySickFactor(Immunizable person, VaccinationConfigGroup vaccinationConfig, int day) {


		int numVaccinations = person.getNumVaccinations();
		int numInfections = person.getNumInfections() - 1;

//		if (numVaccinations == 0 && numInfections == 0)
//			return 1.0;

		VirusStrain strain = person.getVirusStrain();

		double abNoWaning = person.getMaxAntibodies(strain);

		// Two modifications to antibody level below:
		// a) we multiply the antibody level by 4 if the agent is boostered
		if (numVaccinations > 1) {
			abNoWaning *= 4;
		}
		// b) if strain is omicron, an additional factor of 3.7 is applied
		if (strain.equals(VirusStrain.OMICRON_BA1) ||
			strain.equals(VirusStrain.OMICRON_BA2) ||
			strain.equals(VirusStrain.OMICRON_BA5) ||
			strain.equals(VirusStrain.BQ) ||
			strain.equals(VirusStrain.EG) ||
			strain.equals(VirusStrain.XBB_15) ||
			strain.equals(VirusStrain.XBB_19) ||
			strain.equals(VirusStrain.STRAIN_A) ||
			strain.equals(VirusStrain.STRAIN_B) ||
			strain.toString().startsWith("A_") ||
			strain.toString().startsWith("B_")) {
			abNoWaning *= 3.7;
		}

		// returns remaining risk of infection (1 is full risk, 0 is no risk), opposite of vaccine effectiveness
		return 1. / (1. + Math.pow(abNoWaning,vaccinationConfig.getBeta()));

	}

	@Override
	public double getCriticalFactor(Immunizable person, VaccinationConfigGroup vaccinationConfig, int day) {

		int numVaccinations = person.getNumVaccinations();
		int numInfections = person.getNumInfections() - 1;

		if (numVaccinations == 0 && numInfections == 0)
			return 1.0;

		VirusStrain strain = person.getVirusStrain();

		double abNoWaning = person.getMaxAntibodies(strain);

		// Two modifications to antibody level below:
		// a) we multiply the antibody level by 4 if the agent is boostered
		if (numVaccinations > 1) {
			abNoWaning *= 4;
		}
		// b) if strain is omicron, an additional factor of 3.7 is applied
		if (strain.equals(VirusStrain.OMICRON_BA1) || strain.equals(VirusStrain.OMICRON_BA2)) {
			abNoWaning *= 3.7;
		}

		return 1. / (1. + Math.pow(abNoWaning, vaccinationConfig.getBeta()));

	}


	protected double getProbaOfTransitioningToDeceased(EpisimPerson person) {
		return 0.0;
	}


}