From 860d68583d26788b70eaa942993d4b39fd0c1511 Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Fri, 6 Nov 2020 13:18:01 +0100 Subject: [PATCH 1/6] Adding tests for mechanical ventilation in test_model_generator --- .../apps/calculator/test_model_generator.py | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/cara/tests/apps/calculator/test_model_generator.py b/cara/tests/apps/calculator/test_model_generator.py index 06a0dde2..cd74b0ac 100644 --- a/cara/tests/apps/calculator/test_model_generator.py +++ b/cara/tests/apps/calculator/test_model_generator.py @@ -20,7 +20,7 @@ def test_model_from_dict(baseline_form_data): # assert model.ventilation == cara.models.Ventilation() -def test_ventilation(baseline_form): +def test_ventilation_window(baseline_form): room = models.Room(75) window = models.WindowOpening( active=models.PeriodicInterval(period=120, duration=10), @@ -40,6 +40,36 @@ def test_ventilation(baseline_form): [baseline_form.ventilation().air_exchange(room, t) for t in ts]) +def test_ventilation_mechanical(baseline_form): + room = models.Room(75) + mech = models.HVACMechanical( + active=models.PeriodicInterval(period=120, duration=120), + q_air_mech=500., + ) + baseline_form.ventilation_type = 'mechanical' + baseline_form.mechanical_ventilation_type = 'mechanical' + baseline_form.air_supply = 500. + + ts = np.linspace(8, 16, 100) + np.testing.assert_allclose([mech.air_exchange(room, t) for t in ts], + [baseline_form.ventilation().air_exchange(room, t) for t in ts]) + + +def test_ventilation_airchanges(baseline_form): + room = models.Room(75) + airchange = models.AirChange( + active=models.PeriodicInterval(period=120, duration=120), + air_exch=3., + ) + baseline_form.ventilation_type = 'mechanical' + baseline_form.mechanical_ventilation_type = 'air_changes' + baseline_form.air_changes = 3. + + ts = np.linspace(8, 16, 100) + np.testing.assert_allclose([airchange.air_exchange(room, t) for t in ts], + [baseline_form.ventilation().air_exchange(room, t) for t in ts]) + + def test_present_intervals(baseline_form): baseline_form.coffee_duration = 15 baseline_form.coffee_option = True From c5e65404068d7d94dfacba39b182757ae31d7a0d Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Fri, 6 Nov 2020 13:19:17 +0100 Subject: [PATCH 2/6] Adding a test for models (checking that until the first presence time of an infected person, the concentration remains at zero) --- cara/tests/test_known_quantities.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/cara/tests/test_known_quantities.py b/cara/tests/test_known_quantities.py index df91b12a..10c7bfdd 100644 --- a/cara/tests/test_known_quantities.py +++ b/cara/tests/test_known_quantities.py @@ -288,7 +288,8 @@ def test_windowopening(time, expected_value): expected_value,rtol=1e-5) -def build_hourly_dependent_model(month, intervals_open=((7.5, 8.5),)): +def build_hourly_dependent_model(month, intervals_open=((7.5, 8.5),), + intervals_presence_infected=((0, 4), (5, 7.5))): model = models.Model( room=models.Room(volume=75), ventilation=models.WindowOpening( @@ -299,7 +300,7 @@ def build_hourly_dependent_model(month, intervals_open=((7.5, 8.5),)): ), infected=models.InfectedPerson( virus=models.Virus.types['SARS_CoV_2'], - presence=models.SpecificInterval(((0, 4), (5, 7.5))), + presence=models.SpecificInterval(intervals_presence_infected), mask=models.Mask.types['No mask'], activity=models.Activity.types['Light exercise'], expiration=models.Expiration.types['Unmodulated Vocalization'], @@ -371,13 +372,28 @@ def build_hourly_dependent_model_multipleventilation(month, intervals_open=((7.5 "time", [0.5, 1.2, 2., 3.5, 5., 6.5, 7.5, 7.9, 8.], ) -def test_concentrations_hourly_dep_startup(month, temperatures, time): +def test_concentrations_hourly_dep_temp_vs_constant(month, temperatures, time): # The concentrations should be the same up to 8 AM (time when the # temperature changes DURING the window opening). m1 = build_hourly_dependent_model(month) m2 = build_constant_temp_model(temperatures[7]+273.15) npt.assert_allclose(m1.concentration(time), m2.concentration(time), rtol=1e-5) +@pytest.mark.parametrize( + "month, temperatures", + models.Geneva_hourly_temperatures_celsius_per_hour.items(), +) +@pytest.mark.parametrize( + "time", + [0.5, 1.2, 2., 3.5, 5., 6.5, 7.5, 7.9, 8.], +) +def test_concentrations_hourly_dep_temp_startup(month, temperatures, time): + # The concentrations should be the zero up to the first presence time + # of an infecter person. + m = build_hourly_dependent_model(month,((0.,0.5),(1,1.5),(4,4.5),(7.5,8)), + ((8,12.),)) + assert m.concentration(time) == 0. + def test_concentrations_hourly_dep_multipleventilation(): m = build_hourly_dependent_model_multipleventilation('Jan') From 98f2b77350bae07cf0ba934121ca60a9114a4f4f Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Fri, 6 Nov 2020 13:31:19 +0100 Subject: [PATCH 3/6] Adding tests for window ventilation with HEPA in test_model_generator --- .../apps/calculator/test_model_generator.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/cara/tests/apps/calculator/test_model_generator.py b/cara/tests/apps/calculator/test_model_generator.py index cd74b0ac..8fa0c453 100644 --- a/cara/tests/apps/calculator/test_model_generator.py +++ b/cara/tests/apps/calculator/test_model_generator.py @@ -70,6 +70,33 @@ def test_ventilation_airchanges(baseline_form): [baseline_form.ventilation().air_exchange(room, t) for t in ts]) +def test_ventilation_window_hepa(baseline_form): + room = models.Room(75) + window = models.WindowOpening( + active=models.PeriodicInterval(period=120, duration=10), + inside_temp=models.PiecewiseConstant((0, 24), (293,)), + outside_temp=models.GenevaTemperatures['Dec'], + cd_b=0.6, window_height=1.6, opening_length=0.6, + ) + hepa = models.HEPAFilter( + active=models.PeriodicInterval(period=120, duration=120), + q_air_mech=250., + ) + ventilation = models.MultipleVentilation((window,hepa)) + + baseline_form.ventilation_type = 'natural' + baseline_form.windows_open = '10 min / 2h' + baseline_form.event_type = 'recurrent_event' + baseline_form.recurrent_event_month = 'December' + baseline_form.window_height = 1.6 + baseline_form.opening_distance = 0.6 + baseline_form.hepa_option = '1' + + ts = np.linspace(8, 16, 100) + np.testing.assert_allclose([ventilation.air_exchange(room, t) for t in ts], + [baseline_form.ventilation().air_exchange(room, t) for t in ts]) + + def test_present_intervals(baseline_form): baseline_form.coffee_duration = 15 baseline_form.coffee_option = True From 4ce57f6491523ef745bd40f55cd55110a66ea3a4 Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Fri, 6 Nov 2020 13:32:40 +0100 Subject: [PATCH 4/6] Adding hepa_option in FormData class (model_generator.py) --- cara/apps/calculator/model_generator.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cara/apps/calculator/model_generator.py b/cara/apps/calculator/model_generator.py index a945bd72..cfa854f6 100644 --- a/cara/apps/calculator/model_generator.py +++ b/cara/apps/calculator/model_generator.py @@ -22,6 +22,7 @@ class FormData: coffee_option: bool event_type: str floor_area: float + hepa_option: bool infected_people: int lunch_option: bool mask_wearing: str @@ -59,6 +60,7 @@ class FormData: coffee_option=(form_data['coffee_option'] == '1'), event_type=form_data['event_type'], floor_area=float(form_data['floor_area']), + hepa_option=(form_data['hepa_option'] == '1'), infected_people=int(form_data['infected_people']), lunch_finish=time_string_to_minutes(form_data['lunch_finish']), lunch_option=(form_data['lunch_option'] == '1'), @@ -232,6 +234,7 @@ def baseline_raw_form_data(): 'coffee_option': '1', 'event_type': 'single_event', 'floor_area': '', + 'hepa_option': '0', 'infected_people': '1', 'lunch_finish': '13:30', 'lunch_option': '1', From 2710daacd789516f82d8dc9aa4669b340a04083f Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Fri, 6 Nov 2020 13:37:46 +0100 Subject: [PATCH 5/6] Clarifying the use of HVAC (instead of HEPA) or manual air change, in model_generator (transparent change) --- cara/apps/calculator/model_generator.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cara/apps/calculator/model_generator.py b/cara/apps/calculator/model_generator.py index cfa854f6..7436dfd3 100644 --- a/cara/apps/calculator/model_generator.py +++ b/cara/apps/calculator/model_generator.py @@ -108,10 +108,12 @@ class FormData: window_height=self.window_height, opening_length=self.opening_distance * self.windows_number) else: - q_air_mech = (self.air_changes * self.room_volume if self.mechanical_ventilation_type == 'air_changes' - else self.air_supply) - ventilation = models.HEPAFilter(active=models.PeriodicInterval(period=120, duration=120), - q_air_mech=q_air_mech) + if self.mechanical_ventilation_type == 'air_changes': + ventilation = models.AirChange(active=models.PeriodicInterval(period=120, duration=120), + air_exch=self.air_changes) + else: + ventilation = models.HVACMechanical(active=models.PeriodicInterval(period=120, duration=120), + q_air_mech=self.air_supply) return ventilation From 314a29d271272862f64466bb4cfb50921b2c67cf Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Fri, 6 Nov 2020 13:41:06 +0100 Subject: [PATCH 6/6] HEPA addition now fully supported in model_generator.py --- cara/apps/calculator/model_generator.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cara/apps/calculator/model_generator.py b/cara/apps/calculator/model_generator.py index 7436dfd3..3280aba0 100644 --- a/cara/apps/calculator/model_generator.py +++ b/cara/apps/calculator/model_generator.py @@ -113,9 +113,14 @@ class FormData: air_exch=self.air_changes) else: ventilation = models.HVACMechanical(active=models.PeriodicInterval(period=120, duration=120), - q_air_mech=self.air_supply) + q_air_mech=self.air_supply) - return ventilation + if self.hepa_option: + hepa = models.HEPAFilter(active=models.PeriodicInterval(period=120, duration=120), + q_air_mech=250.) + return models.MultipleVentilation((ventilation,hepa)) + else: + return ventilation def present_interval(self) -> models.Interval: coffee_period = (self.activity_finish - self.activity_start) // self.coffee_breaks