From 306e7fc31d5f5cdc67ff248ba4665c4e57d33250 Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Thu, 12 Nov 2020 18:40:03 +0100 Subject: [PATCH 1/4] tests for modele_generator: replace test on presence intervals by two tests on infected and exposed presence intervals --- .../apps/calculator/test_model_generator.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/cara/tests/apps/calculator/test_model_generator.py b/cara/tests/apps/calculator/test_model_generator.py index 1dab481b..c20495ef 100644 --- a/cara/tests/apps/calculator/test_model_generator.py +++ b/cara/tests/apps/calculator/test_model_generator.py @@ -98,7 +98,7 @@ def test_ventilation_window_hepa(baseline_form): [baseline_form.ventilation().air_exchange(room, t) for t in ts]) -def test_present_intervals(baseline_form): +def test_infected_present_intervals(baseline_form): baseline_form.coffee_duration = 15 baseline_form.coffee_breaks = 2 baseline_form.activity_start = 9 * 60 @@ -108,9 +108,21 @@ def test_present_intervals(baseline_form): baseline_form.infected_start = 10 * 60 baseline_form.infected_finish = 15 * 60 correct = ((10, 11), (11.25, 12.5), (13.5, 15.0)) - assert baseline_form.present_interval().present_times == correct + assert baseline_form.infected_present_interval().present_times == correct +def test_exposed_present_intervals(baseline_form): + baseline_form.coffee_duration = 15 + baseline_form.coffee_breaks = 2 + baseline_form.activity_start = 9 * 60 + baseline_form.activity_finish = 17 * 60 + baseline_form.lunch_start = 12 * 60 + 30 + baseline_form.lunch_finish = 13 * 60 + 30 + baseline_form.infected_start = 10 * 60 + baseline_form.infected_finish = 15 * 60 + correct = ((9, 11), (11.25, 12.5), (13.5, 17.0)) + assert baseline_form.exposed_present_interval().present_times == correct + def test_key_validation(baseline_form_data): baseline_form_data['activity_type'] = 'invalid key' with pytest.raises(ValueError): From a9e07f77f61ebb7e0d6f318a04e2c9e1882deda1 Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Thu, 12 Nov 2020 18:50:14 +0100 Subject: [PATCH 2/4] Extending the time range for the plot in the report_generator --- cara/apps/calculator/report_generator.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cara/apps/calculator/report_generator.py b/cara/apps/calculator/report_generator.py index b78d5069..6a9f4648 100644 --- a/cara/apps/calculator/report_generator.py +++ b/cara/apps/calculator/report_generator.py @@ -24,8 +24,10 @@ class RepeatEvents: def calculate_report_data(model: models.ExposureModel): resolution = 600 - t_start = model.exposed.presence.boundaries()[0][0] - t_end = model.exposed.presence.boundaries()[-1][1] + t_start = min(model.exposed.presence.boundaries()[0][0], + model.concentration_model.infected.presence.boundaries()[0][0]) + t_end = max(model.exposed.presence.boundaries()[-1][1], + model.concentration_model.infected.presence.boundaries()[-1][1]) times = list(np.linspace(t_start, t_end, resolution)) concentrations = [model.concentration_model.concentration(time) for time in times] From 3860e39959de9e2624410256ff6a228f0614e3fd Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Thu, 12 Nov 2020 18:58:25 +0100 Subject: [PATCH 3/4] Adding second coffee break in test on exposed presence interval (test_model_generator.py) --- cara/apps/calculator/model_generator.py | 55 ++++++++++++++++++- .../apps/calculator/test_model_generator.py | 2 +- 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/cara/apps/calculator/model_generator.py b/cara/apps/calculator/model_generator.py index 27f10ad5..ab866b6f 100644 --- a/cara/apps/calculator/model_generator.py +++ b/cara/apps/calculator/model_generator.py @@ -174,7 +174,7 @@ class FormData: coffee_times.append((start, end)) return tuple(coffee_times) - def present_interval(self) -> models.Interval: + def infected_present_interval(self) -> models.Interval: leave_times = [] enter_times = [] if self.lunch_option: @@ -223,6 +223,55 @@ class FormData: return models.SpecificInterval(tuple(present_intervals)) + def exposed_present_interval(self) -> models.Interval: + leave_times = [] + enter_times = [] + if self.lunch_option: + leave_times.append(self.lunch_start) + enter_times.append(self.lunch_finish) + + for coffee_start, coffee_end in self.coffee_break_times(): + leave_times.append(coffee_start) + enter_times.append(coffee_end) + + # These lists represent the times where the infected person leaves or enters the room, respectively, sorted in + # reverse order. Note that these lists allows the person to "leave" when they should not even be present in the + # room. The following loop handles this. + leave_times.sort(reverse=True) + enter_times.sort(reverse=True) + + # This loop iterates through the lists above, populating present_intervals with (enter, leave) intervals + # representing the infected person entering and leaving the room. Note that if one of the evenly spaced coffee- + # breaks happens to coincide with the lunch-break, it is simply ignored. + present_intervals = [] + time = self.activity_start + is_present = True + while time < self.activity_finish: + if is_present: + if not leave_times: + present_intervals.append((time / 60, self.activity_finish / 60)) + break + + if leave_times[-1] <= time: + leave_times.pop() + else: + new_time = leave_times.pop() + present_intervals.append((time / 60, min(new_time, self.activity_finish) / 60)) + is_present = False + time = new_time + + else: + if not enter_times: + break + + if enter_times[-1] < time: + enter_times.pop() + else: + is_present = True + time = enter_times.pop() + + return models.SpecificInterval(tuple(present_intervals)) + def model_from_form(form: FormData) -> models.ExposureModel: # Initializes room with volume either given directly or as product of area and height @@ -264,7 +313,7 @@ def model_from_form(form: FormData) -> models.ExposureModel: infected=models.InfectedPopulation( number=infected_occupants, virus=virus, - presence=form.present_interval(), + presence=form.infected_present_interval(), mask=mask, activity=infected_activity, expiration=infected_expiration @@ -272,7 +321,7 @@ def model_from_form(form: FormData) -> models.ExposureModel: ), exposed=models.Population( number=exposed_occupants, - presence=form.present_interval(), + presence=form.exposed_present_interval(), activity=exposed_activity, mask=mask, ) diff --git a/cara/tests/apps/calculator/test_model_generator.py b/cara/tests/apps/calculator/test_model_generator.py index c20495ef..dda64995 100644 --- a/cara/tests/apps/calculator/test_model_generator.py +++ b/cara/tests/apps/calculator/test_model_generator.py @@ -120,7 +120,7 @@ def test_exposed_present_intervals(baseline_form): baseline_form.lunch_finish = 13 * 60 + 30 baseline_form.infected_start = 10 * 60 baseline_form.infected_finish = 15 * 60 - correct = ((9, 11), (11.25, 12.5), (13.5, 17.0)) + correct = ((9, 11), (11.25, 12.5), (13.5, 15), (15.25, 17.0)) assert baseline_form.exposed_present_interval().present_times == correct def test_key_validation(baseline_form_data): From fe9fde0e865fa6f6f87aecf61878c21bdad7abc9 Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Fri, 13 Nov 2020 14:58:28 +0100 Subject: [PATCH 4/4] model_generator: putting the common part of exposed_present_interval and infected_present_interval into a separate method (code cleanout) --- cara/apps/calculator/model_generator.py | 61 ++++--------------------- 1 file changed, 9 insertions(+), 52 deletions(-) diff --git a/cara/apps/calculator/model_generator.py b/cara/apps/calculator/model_generator.py index ab866b6f..70bf1e03 100644 --- a/cara/apps/calculator/model_generator.py +++ b/cara/apps/calculator/model_generator.py @@ -174,7 +174,7 @@ class FormData: coffee_times.append((start, end)) return tuple(coffee_times) - def infected_present_interval(self) -> models.Interval: + def present_interval(self, start, finish) -> models.Interval: leave_times = [] enter_times = [] if self.lunch_option: @@ -195,19 +195,19 @@ class FormData: # representing the infected person entering and leaving the room. Note that if one of the evenly spaced coffee- # breaks happens to coincide with the lunch-break, it is simply ignored. present_intervals = [] - time = self.infected_start + time = start is_present = True - while time < self.infected_finish: + while time < finish: if is_present: if not leave_times: - present_intervals.append((time / 60, self.infected_finish / 60)) + present_intervals.append((time / 60, finish / 60)) break if leave_times[-1] <= time: leave_times.pop() else: new_time = leave_times.pop() - present_intervals.append((time / 60, min(new_time, self.infected_finish) / 60)) + present_intervals.append((time / 60, min(new_time, finish) / 60)) is_present = False time = new_time @@ -223,54 +223,11 @@ class FormData: return models.SpecificInterval(tuple(present_intervals)) + def infected_present_interval(self) -> models.Interval: + return self.present_interval(self.infected_start, self.infected_finish) + def exposed_present_interval(self) -> models.Interval: - leave_times = [] - enter_times = [] - if self.lunch_option: - leave_times.append(self.lunch_start) - enter_times.append(self.lunch_finish) - - for coffee_start, coffee_end in self.coffee_break_times(): - leave_times.append(coffee_start) - enter_times.append(coffee_end) - - # These lists represent the times where the infected person leaves or enters the room, respectively, sorted in - # reverse order. Note that these lists allows the person to "leave" when they should not even be present in the - # room. The following loop handles this. - leave_times.sort(reverse=True) - enter_times.sort(reverse=True) - - # This loop iterates through the lists above, populating present_intervals with (enter, leave) intervals - # representing the infected person entering and leaving the room. Note that if one of the evenly spaced coffee- - # breaks happens to coincide with the lunch-break, it is simply ignored. - present_intervals = [] - time = self.activity_start - is_present = True - while time < self.activity_finish: - if is_present: - if not leave_times: - present_intervals.append((time / 60, self.activity_finish / 60)) - break - - if leave_times[-1] <= time: - leave_times.pop() - else: - new_time = leave_times.pop() - present_intervals.append((time / 60, min(new_time, self.activity_finish) / 60)) - is_present = False - time = new_time - - else: - if not enter_times: - break - - if enter_times[-1] < time: - enter_times.pop() - else: - is_present = True - time = enter_times.pop() - - return models.SpecificInterval(tuple(present_intervals)) + return self.present_interval(self.activity_start, self.activity_finish) def model_from_form(form: FormData) -> models.ExposureModel: