From 46d99c4f323c25f2bbea1ee92b3ba23481865df4 Mon Sep 17 00:00:00 2001 From: Luis Aleixo Date: Thu, 17 Feb 2022 17:31:24 +0100 Subject: [PATCH] Added documentation on the adapted methods --- cara/models.py | 79 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 58 insertions(+), 21 deletions(-) diff --git a/cara/models.py b/cara/models.py index 444782b6..e1ab9809 100644 --- a/cara/models.py +++ b/cara/models.py @@ -1001,7 +1001,7 @@ class ConcentrationModel: def _normed_concentration(self, time: float) -> _VectorisedFloat: """ - Virus exposure concentration, as a function of time, and + Virus long range exposure concentration, as a function of time, and normalized by the emission rate. The formulas used here assume that all parameters (ventilation, emission rate) are constant between two state changes - only @@ -1027,18 +1027,18 @@ class ConcentrationModel: def concentration(self, time: float) -> _VectorisedFloat: """ - Virus exposure concentration, as a function of time. + Virus long range exposure concentration, as a function of time. Note that time is not vectorised. You can only pass a single float to this method. """ return (self._normed_concentration(time) * - self.infected.emission_rate_when_present()) + self.infected.emission_rate_when_present()) @method_cache def normed_integrated_concentration(self, start: float, stop: float) -> _VectorisedFloat: """ - Get the integrated concentration of viruses in the air between the times start and stop, + Get the integrated long range concentration of viruses in the air between the times start and stop, normalized by the emission rate. """ if stop <= self._first_presence_time(): @@ -1078,32 +1078,53 @@ class ShortRangeModel: #: Short range interactions presence: typing.List[SpecificInterval] - #: Expiration type + #: Expiration types expirations: typing.List[Expiration] - #: The dilution factors for each of the expiratory activity in the short range interactions + #: The dilution factors for each of the expiratory activity dilutions: typing.List[_VectorisedFloat] - def _normed_concentration(self, concentration_model: ConcentrationModel, time: float) -> _VectorisedFloat: - # normalized only by the viral load + def _normed_concentration(self, concentration_model: ConcentrationModel, time: float) -> _VectorisedFloat: + """ + Virus short range exposure concentration, as a function of time. + + If the given time falls within a short range interval it returns the + short range concentration normalized by the virus viral load. Otherwise + it returns 0. + """ for index, period in enumerate(self.presence): start, finish = tuple(period.boundaries()) + # Verifies if the given time falls within a short range interaction if start < time <= finish: dilution = self.dilutions[index] jet_origin_concentration = concentration_model.infected.expiration.jet_origin_concentration() + # Long range concentration normalized by the virus viral load + long_range_normed_concentration = concentration_model.concentration(time) / concentration_model.virus.viral_load_in_sputum - background_concentration = concentration_model.concentration(time) / concentration_model.virus.viral_load_in_sputum - long_range_normed_concentration=np.interp(self.expirations[index].particle.diameter, concentration_model.infected.particle.diameter, background_concentration) + # The long range concentration values are then approximated using interpolation: + # The set of points where we want the interpolated values are the short range particle diameters (given the current expiration); + # The set of points with a known value are the long range particle diameters (given the initial expiration); + # The set of known values are the long range concentration values normalized by the viral load. + long_range_normed_concentration=np.interp(self.expirations[index].particle.diameter, concentration_model.infected.particle.diameter, long_range_normed_concentration) + # Short range concentration formula. The long range concentration is added in the concentration method (ExposureModel). return ((1/dilution)*(jet_origin_concentration - long_range_normed_concentration)) return 0. def short_range_concentration(self, concentration_model: ConcentrationModel, time: float): + """ + Virus short range exposure concentration, as a function of time. + """ return (self._normed_concentration(concentration_model, time) * concentration_model.virus.viral_load_in_sputum) def normed_exposure_between_bounds(self, concentration_model, time1, time2): + """ + Get the integrated short range concentration of viruses in the air between the times start and stop, + normalized by the virus viral load. + """ + # TODO Implement the integration without using scipy. return scipy.integrate.quad_vec(lambda t: self._normed_concentration(concentration_model, t), time1, time2)[0] @@ -1119,7 +1140,7 @@ class ExposureModel: #: The virus concentration model which this exposure model should consider. concentration_model: ConcentrationModel - #: The short range model which this simulation model should consider. + #: The short range model which this exposure model should consider. short_range: ShortRangeModel #: The population of non-infected people to be used in the model. @@ -1156,11 +1177,26 @@ class ExposureModel: exposure += self.concentration_model.normed_integrated_concentration(start, stop) return exposure - def concentration(self, time: float) -> _VectorisedFloat: + def concentration(self, time: float) -> _VectorisedFloat: + """ + Virus exposure concentration, as a function of time. + + It considers the long range concentration with the + contribution of the short range concentration. + """ return (self.concentration_model.concentration(time) + self.short_range.short_range_concentration(self.concentration_model, time)) def deposited_exposure_between_bounds(self, time1: float, time2: float) -> _VectorisedFloat: + """ + The number of virus per m^3 deposited on the respiratory tract + between any two times. + + Considers a contribution between the short range and long range exposures: + It calculates the deposited exposure given a short range interaction (if any). + Then, the deposited exposure given the long range interactions is added to the + initial deposited exposure. + """ deposited_exposure = 0. for index, period in enumerate(self.short_range.presence): start, stop = tuple(period.boundaries()) @@ -1180,48 +1216,50 @@ class ExposureModel: fdep = self.short_range.expirations[index].particle.fraction_deposited(evaporation_factor=1.0) #ASK for evaporation factor diameter = self.short_range.expirations[index].particle.diameter + # Aerosols not considered given the formula for the initial concentration at mouth/nose. if not np.isscalar(diameter) and diameter is not None: # we compute first the mean of all diameter-dependent quantities # to perform properly the Monte-Carlo integration over # particle diameters (doing things in another order would # lead to wrong results). deposited_exposure += np.array(short_range_exposure * - #aerosols * fdep).mean() else: # in the case of a single diameter or no diameter defined, # one should not take any mean at this stage. - deposited_exposure += short_range_exposure*fdep#*aerosols + deposited_exposure += short_range_exposure*fdep + # then we multiply by the diameter-independent quantity virus viral load deposited_exposure *= self.concentration_model.virus.viral_load_in_sputum - # Background concentration + # Long range concentration emission_rate_per_aerosol = self.concentration_model.infected.emission_rate_per_aerosol_when_present() f_inf = self.concentration_model.infected.fraction_of_infectious_virus() aerosols = self.concentration_model.infected.aerosols() fdep = self.long_range_fraction_deposited() diameter = self.concentration_model.infected.particle.diameter - + if not np.isscalar(diameter) and diameter is not None: # we compute first the mean of all diameter-dependent quantities # to perform properly the Monte-Carlo integration over # particle diameters (doing things in another order would # lead to wrong results). dep_exposure_integrated = np.array(self._long_range_normed_exposure_between_bounds(time1, time2) * - aerosols * - fdep).mean() + aerosols * + fdep).mean() else: # in the case of a single diameter or no diameter defined, # one should not take any mean at this stage. dep_exposure_integrated = self._long_range_normed_exposure_between_bounds(time1, time2)*aerosols*fdep # then we multiply by the diameter-independent quantity emission_rate_per_aerosol, - # and parameters of the vD equation (i.e. f_inf, BR_k and n_in). + # and parameters of the vD equation (i.e. BR_k and n_in). deposited_exposure += (dep_exposure_integrated * emission_rate_per_aerosol * self.exposed.activity.inhalation_rate * (1 - self.exposed.mask.inhale_efficiency())) + # In the end we multiply the final results by the fraction of infectious virus of the vD equation. return f_inf * deposited_exposure def deposited_exposure(self) -> _VectorisedFloat: @@ -1266,5 +1304,4 @@ class ExposureModel: self, {'concentration_model.infected.number': 1} ) - return single_exposure_model.expected_new_cases() - \ No newline at end of file + return single_exposure_model.expected_new_cases() \ No newline at end of file