Skip to content

Commit 0218bec

Browse files
author
mheie
committed
multiple concentration models
1 parent 792b48c commit 0218bec

1 file changed

Lines changed: 62 additions & 9 deletions

File tree

  • caimira/src/caimira/calculator/models

caimira/src/caimira/calculator/models/models.py

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1327,6 +1327,59 @@ def normalization_factor(self) -> _VectorisedFloat:
13271327
# CO2 concentration given in ppm, hence the 1e6 factor.
13281328
return (1e6*self.population.activity.exhalation_rate
13291329
*self.CO2_fraction_exhaled)
1330+
1331+
class CO2ConcentrationModelGroup:
1332+
"""
1333+
CO2 model with dynamic occupancy, one CO2ConcentrationModel per group.
1334+
"""
1335+
1336+
concentration_models = list[CO2ConcentrationModel]
1337+
1338+
def __post_init__(self):
1339+
self.data_registry = set([model.data_registry for model in self.concentration_models])
1340+
self.room = set([model.data_registry for model in self.concentration_models])
1341+
self.ventilation = set([model.data_registry for model in self.concentration_models])
1342+
self.background_concentration = set([model.min_background_concentration() for model in self.concentration_models])
1343+
1344+
if len(self.data_registry) != 1:
1345+
raise ValueError("All concentration models must have the same data_registry.")
1346+
if len(self.room) != 1:
1347+
raise ValueError("All concentration models must have the same room.")
1348+
if len(self.ventilation) != 1:
1349+
raise ValueError("All concentration models must have the same ventilation.")
1350+
if len(self.background_concentration) != 1:
1351+
raise ValueError("All concentration models must have the same background_concentration.")
1352+
1353+
@property
1354+
def population(self) -> MultiplePopulations:
1355+
return MultiplePopulations(
1356+
populations=[model.population for model in self.concentration_models]
1357+
)
1358+
1359+
@method_cache
1360+
def state_change_times(self) -> typing.List[float]:
1361+
"""
1362+
All time dependent entities on this model must provide information about
1363+
the times at which their state changes.
1364+
"""
1365+
# state_change_times = {0.}
1366+
# for model in self.concentration_models:
1367+
# state_change_times.update(model.state_change_times())
1368+
# return sorted(state_change_times)
1369+
state_change_times = {0.}
1370+
state_change_times.update(self.population.transition_times())
1371+
state_change_times.update(self.ventilation.transition_times(self.room))
1372+
return sorted(state_change_times)
1373+
1374+
def _align_state_change_times(self):
1375+
for i, model in enumerate(self.concentration_models):
1376+
"""if state change not in model add it to the model"""#PROBLEM: calculations stop when group leaves
1377+
pass
1378+
1379+
def concentration(self):
1380+
self._align_state_change_times()
1381+
return np.sum([model.concentration for model in self.concentration_models]) - self.background_concentration * (len(self.concentration_models) - 1)
1382+
13301383

13311384

13321385
@dataclass(frozen=True)
@@ -1706,7 +1759,7 @@ def population_state_change_times(self) -> typing.List[float]:
17061759
All time dependent population entities on this model must provide information
17071760
about the times at which their state changes.
17081761
"""
1709-
state_change_times = set(self.concentration_model.infected.presence_interval().transition_times())
1762+
state_change_times = set(self.concentration_model.infected.presence_interval().transition_times()) #update with all concentration_models
17101763
state_change_times.update(self.exposed.presence_interval().transition_times())
17111764

17121765
return sorted(state_change_times)
@@ -1718,9 +1771,9 @@ def long_range_fraction_deposited(self) -> _VectorisedFloat:
17181771
particle diameter.
17191772
"""
17201773
return self.concentration_model.infected.particle.fraction_deposited(
1721-
self.concentration_model.evaporation_factor)
1774+
self.concentration_model.evaporation_factor) #sum over all concentration_models
17221775

1723-
def _long_range_normed_exposure_between_bounds(self, time1: float, time2: float) -> _VectorisedFloat:
1776+
def _long_range_normed_exposure_between_bounds(self, time1: float, time2: float) -> _VectorisedFloat:#sum over all concentration_models
17241777
"""
17251778
The number of virions per meter^3 between any two times, normalized
17261779
by the emission rate of the infected population
@@ -1748,7 +1801,7 @@ def concentration(self, time: float) -> _VectorisedFloat:
17481801
It considers the long-range concentration with the
17491802
contribution of the short-range concentration.
17501803
"""
1751-
concentration = self.concentration_model.concentration(time)
1804+
concentration = self.concentration_model.concentration(time)#sum over all concentration_models
17521805
for interaction in self.short_range:
17531806
concentration += interaction.short_range_concentration(self.concentration_model, time)
17541807
return concentration
@@ -1758,7 +1811,7 @@ def long_range_deposited_exposure_between_bounds(self, time1: float, time2: floa
17581811

17591812
emission_rate_per_aerosol_per_person = \
17601813
self.concentration_model.infected.emission_rate_per_aerosol_per_person_when_present()
1761-
aerosols = self.concentration_model.infected.aerosols()
1814+
aerosols = self.concentration_model.infected.aerosols()#sum over all concentration_models
17621815
fdep = self.long_range_fraction_deposited()
17631816

17641817
diameter = self.concentration_model.infected.particle.diameter
@@ -1861,18 +1914,18 @@ def _infection_probability_list(self):
18611914
vD_list = self._deposited_exposure_list()
18621915

18631916
# oneoverln2 multiplied by ID_50 corresponds to ID_63.
1864-
infectious_dose = oneoverln2 * self.concentration_model.virus.infectious_dose
1917+
infectious_dose = oneoverln2 * self.concentration_model.virus.infectious_dose#all concentration_models with same virus
18651918

18661919
# Probability of infection.
18671920
return [(1 - np.exp(-((vD * (1 - self.exposed.host_immunity))/(infectious_dose *
1868-
self.concentration_model.virus.transmissibility_factor)))) for vD in vD_list]
1921+
self.concentration_model.virus.transmissibility_factor)))) for vD in vD_list] #all concentration_models with same virus (if several viruses, calculate infection risk for each of them)
18691922

18701923
@method_cache
18711924
def infection_probability(self) -> _VectorisedFloat:
18721925
return (1 - np.prod([1 - prob for prob in self._infection_probability_list()], axis = 0)) * 100
18731926

18741927
def total_probability_rule(self) -> _VectorisedFloat:
1875-
if (isinstance(self.concentration_model.infected.number, IntPiecewiseConstant)):
1928+
if (isinstance(self.concentration_model.infected.number, IntPiecewiseConstant)):#change test
18761929
raise NotImplementedError("Cannot compute total probability "
18771930
"(including incidence rate) with dynamic occupancy")
18781931

@@ -1918,7 +1971,7 @@ def reproduction_number(self) -> _VectorisedFloat:
19181971
The reproduction number can be thought of as the expected number of
19191972
cases directly generated by one infected case in a population.
19201973
"""
1921-
infected_population: InfectedPopulation = self.concentration_model.infected
1974+
infected_population: InfectedPopulation = self.concentration_model.infected#sum over all concentration_models
19221975
if isinstance(infected_population.number, int) and infected_population.number == 1:
19231976
return self.expected_new_cases()
19241977

0 commit comments

Comments
 (0)