From 6c2a843e67cd3d478d19052274cf8ea516338106 Mon Sep 17 00:00:00 2001 From: markus Date: Thu, 5 Nov 2020 21:23:52 +0100 Subject: [PATCH 1/7] move ventilation to dedicated function and use piecewise temperature function --- cara/apps/calculator/model_generator.py | 49 ++++++++++++++----------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/cara/apps/calculator/model_generator.py b/cara/apps/calculator/model_generator.py index 5a26afd0..ddaca9c9 100644 --- a/cara/apps/calculator/model_generator.py +++ b/cara/apps/calculator/model_generator.py @@ -83,8 +83,32 @@ class FormData: return model_from_form(self, tmp_raw_form_data) def ventilation(self) -> models.Ventilation: - # TODO - pass + # Initializes a ventilation instance as a window if 'natural' is selected, or as a HEPA-filter otherwise + if self.ventilation_type == 'natural': + if self.windows_open == 'always': + period, duration = 120, 120 + else: + period, duration = 15, 120 + # I multiply the opening width by the number of windows to simulate the correct window area + if self.event_type == 'single_event': + month_number = int(self.single_event_date.split('/')[1]) + month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][month_number] + else: + month = self.recurrent_event_month[:3] + + inside_temp = models.PiecewiseConstant((0, 24), (293,)) + outside_temp = models.GenevaTemperatures[month] + + ventilation = models.WindowOpening(active=models.PeriodicInterval(period=period, duration=duration), + inside_temp=inside_temp, outside_temp=outside_temp, cd_b=0.6, + window_height=self.window_height, + opening_length=self.opening_distance * self.windows_number) + else: + q_air_mech = self.air_changes + self.air_supply + ventilation = models.HEPAFilter(active=models.PeriodicInterval(period=120, duration=120), + q_air_mech=q_air_mech) + + return ventilation def present_interval(self) -> models.Interval: coffee_period = (self.activity_finish - self.activity_start) // self.coffee_breaks @@ -149,25 +173,6 @@ def model_from_form(form: FormData, tmp_raw_form_data) -> models.Model: volume = int(float(d['floor_area']) * form.ceiling_height) room = models.Room(volume=volume) - # Initializes a ventilation instance as a window if 'natural' is selected, or as a HEPA-filter otherwise - if d['ventilation_type'] == 'natural': - if d['windows_open'] == 'always': - period, duration = 120, 120 - else: - period, duration = 15, 120 - # I multiply the opening width by the number of windows to simulate the correct window area - ventilation = models.WindowOpening(active=models.PeriodicInterval(period=period, duration=duration), - inside_temp=models.PiecewiseConstant((0, 24), (293, )), - # TODO: This should be based on the month etc. - outside_temp=models.PiecewiseConstant((0, 24), (283, )), - cd_b=0.6, - window_height=float(d['window_height']), - opening_length=float(d['opening_distance']) * int(d['windows_number'])) - else: - q_air_mech = float(d['air_changes']) if d['air_type'] == 'air_changes' else float(d['air_supply']) - ventilation = models.HEPAFilter(active=models.PeriodicInterval(period=120, duration=120), - q_air_mech=q_air_mech) - # Initializes the virus as SARS_Cov_2 virus = models.Virus.types['SARS_CoV_2'] @@ -195,7 +200,7 @@ def model_from_form(form: FormData, tmp_raw_form_data) -> models.Model: # Initializes and returns a model with the attributes defined above return models.Model( room=room, - ventilation=ventilation, + ventilation=form.ventilation(), infected=models.InfectedPerson( virus=virus, presence=form.present_interval(), From 7860ae60740152a85c0f0f17fe7d7ac16f25c207 Mon Sep 17 00:00:00 2001 From: markus Date: Thu, 5 Nov 2020 21:24:26 +0100 Subject: [PATCH 2/7] transition from dictionary to FormData --- cara/apps/calculator/model_generator.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/cara/apps/calculator/model_generator.py b/cara/apps/calculator/model_generator.py index ddaca9c9..72c90967 100644 --- a/cara/apps/calculator/model_generator.py +++ b/cara/apps/calculator/model_generator.py @@ -167,10 +167,10 @@ def model_from_form(form: FormData, tmp_raw_form_data) -> models.Model: d['lunch_finish'] = '13:00' # Initializes room with volume either given directly or as product of area and height - if d['volume_type'] == 'room_volume': - volume = int(d['room_volume']) + if form.volume_type == 'room_volume': + volume = form.room_volume else: - volume = int(float(d['floor_area']) * form.ceiling_height) + volume = form.floor_area * form.ceiling_height room = models.Room(volume=volume) # Initializes the virus as SARS_Cov_2 @@ -178,6 +178,7 @@ def model_from_form(form: FormData, tmp_raw_form_data) -> models.Model: # Initializes a mask of type 1 if mask wearing is "continuous", otherwise instantiates the mask attribute as # the "No mask"-mask + # TODO: figure out the possible values of mask_wearing in the form mask = models.Mask.types['Type I' if d['mask_wearing'] == "Continuous" else 'No mask'] # A dictionary containing the mapping of activities listed in the UI to the activity level and expiration level @@ -188,14 +189,14 @@ def model_from_form(form: FormData, tmp_raw_form_data) -> models.Model: 'Training': (('Light exercise', 'Talking'), ('Seated', 'Whispering')), 'Workshop': (('Light exercise', 'Talking'), ('Light exercise', 'Talking'))} - (infected_activity, infected_expiration), (exposed_activity, exposed_expiration) = activity_dict[d['activity_type']] + (infected_activity, infected_expiration), (exposed_activity, exposed_expiration) = activity_dict[form.activity_type] # Converts these strings to Activity and Expiration instances infected_activity, exposed_activity = models.Activity.types[infected_activity], models.Activity.types[exposed_activity] infected_expiration, exposed_expiration = models.Expiration.types[infected_expiration], models.Expiration.types[exposed_expiration] - infected_occupants = int(d['infected_people']) + infected_occupants = form.infected_people # Defines the number of exposed occupants as the total number of occupants minus the number of infected occupants - exposed_occupants = int(d['total_people']) - infected_occupants + exposed_occupants = form.total_people - infected_occupants # Initializes and returns a model with the attributes defined above return models.Model( From 61ab69b41fe056cae08d2b10d71dd4a305766e44 Mon Sep 17 00:00:00 2001 From: markus Date: Fri, 6 Nov 2020 09:52:34 +0100 Subject: [PATCH 3/7] fix typo --- cara/apps/calculator/model_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cara/apps/calculator/model_generator.py b/cara/apps/calculator/model_generator.py index 72c90967..9cdd21fe 100644 --- a/cara/apps/calculator/model_generator.py +++ b/cara/apps/calculator/model_generator.py @@ -92,7 +92,7 @@ class FormData: # I multiply the opening width by the number of windows to simulate the correct window area if self.event_type == 'single_event': month_number = int(self.single_event_date.split('/')[1]) - month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][month_number] + month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][month_number - 1] else: month = self.recurrent_event_month[:3] From 978402c701b33e6efeab0bec64388a5207f0c7ae Mon Sep 17 00:00:00 2001 From: markus Date: Fri, 6 Nov 2020 09:52:53 +0100 Subject: [PATCH 4/7] change default window periodicity --- cara/apps/calculator/model_generator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cara/apps/calculator/model_generator.py b/cara/apps/calculator/model_generator.py index 9cdd21fe..11396d62 100644 --- a/cara/apps/calculator/model_generator.py +++ b/cara/apps/calculator/model_generator.py @@ -85,10 +85,10 @@ class FormData: def ventilation(self) -> models.Ventilation: # Initializes a ventilation instance as a window if 'natural' is selected, or as a HEPA-filter otherwise if self.ventilation_type == 'natural': - if self.windows_open == 'always': - period, duration = 120, 120 + if self.windows_open == '10 min / 2h': + period, duration = 120, 10 else: - period, duration = 15, 120 + period, duration = 120, 120 # I multiply the opening width by the number of windows to simulate the correct window area if self.event_type == 'single_event': month_number = int(self.single_event_date.split('/')[1]) From a2512e2798e81c37f20b775e6829612738a8b857 Mon Sep 17 00:00:00 2001 From: markus Date: Fri, 6 Nov 2020 09:53:06 +0100 Subject: [PATCH 5/7] add test_ventilation --- .../apps/calculator/test_model_generator.py | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/cara/tests/apps/calculator/test_model_generator.py b/cara/tests/apps/calculator/test_model_generator.py index a13018cc..06a0dde2 100644 --- a/cara/tests/apps/calculator/test_model_generator.py +++ b/cara/tests/apps/calculator/test_model_generator.py @@ -1,7 +1,8 @@ import pytest from cara.apps.calculator import model_generator - +from cara import models +import numpy as np @pytest.fixture def baseline_form_data(): @@ -20,9 +21,23 @@ def test_model_from_dict(baseline_form_data): def test_ventilation(baseline_form): - ventilation = baseline_form.ventilation() - # TODO: - # assert ventilation == cara.models.Ventilation() + 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, + ) + 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 + + ts = np.linspace(8, 16, 100) + np.testing.assert_allclose([window.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): From 4e7d341ae853508a0dde9d9541d218fe98865e17 Mon Sep 17 00:00:00 2001 From: markus Date: Fri, 6 Nov 2020 09:54:33 +0100 Subject: [PATCH 6/7] multiply air_changes by room_volume --- cara/apps/calculator/model_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cara/apps/calculator/model_generator.py b/cara/apps/calculator/model_generator.py index 11396d62..1c0190c7 100644 --- a/cara/apps/calculator/model_generator.py +++ b/cara/apps/calculator/model_generator.py @@ -104,7 +104,7 @@ class FormData: window_height=self.window_height, opening_length=self.opening_distance * self.windows_number) else: - q_air_mech = self.air_changes + self.air_supply + q_air_mech = self.air_changes * self.room_volume + self.air_supply ventilation = models.HEPAFilter(active=models.PeriodicInterval(period=120, duration=120), q_air_mech=q_air_mech) From 56124c552bf7430d524ac4fa72ab5252e03d0b3c Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Fri, 6 Nov 2020 10:06:29 +0100 Subject: [PATCH 7/7] Normalize the input activity. --- cara/apps/calculator/model_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cara/apps/calculator/model_generator.py b/cara/apps/calculator/model_generator.py index 1c0190c7..6946d44f 100644 --- a/cara/apps/calculator/model_generator.py +++ b/cara/apps/calculator/model_generator.py @@ -189,7 +189,7 @@ def model_from_form(form: FormData, tmp_raw_form_data) -> models.Model: 'Training': (('Light exercise', 'Talking'), ('Seated', 'Whispering')), 'Workshop': (('Light exercise', 'Talking'), ('Light exercise', 'Talking'))} - (infected_activity, infected_expiration), (exposed_activity, exposed_expiration) = activity_dict[form.activity_type] + (infected_activity, infected_expiration), (exposed_activity, exposed_expiration) = activity_dict[form.activity_type.capitalize()] # Converts these strings to Activity and Expiration instances infected_activity, exposed_activity = models.Activity.types[infected_activity], models.Activity.types[exposed_activity] infected_expiration, exposed_expiration = models.Expiration.types[infected_expiration], models.Expiration.types[exposed_expiration]