From 60e84c42e5fdaaa62127a5207a8e8fbab8ed6b73 Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Wed, 4 Nov 2020 13:22:56 +0100 Subject: [PATCH 01/19] Adding tests for class defining a piecewise constant function (to be used for temperature dependency) --- cara/tests/test_known_quantities.py | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/cara/tests/test_known_quantities.py b/cara/tests/test_known_quantities.py index 82c3ac71..a2cefc06 100644 --- a/cara/tests/test_known_quantities.py +++ b/cara/tests/test_known_quantities.py @@ -148,3 +148,35 @@ def test_expiration_aerosols(): exp2 = models.Expiration((0.059, 0.0139, 0.751, 0.139), particle_sizes = (5.5e-4, 3.5e-4, 0.8e-4, 1.8e-4)) npt.assert_allclose(exp1.aerosols(mask), exp2.aerosols(mask), rtol=1e-5) + + +def test_piecewiseconstantfunction_wrongarguments(): + pytest.raises(ValueError,models.PiecewiseconstantFunction([0,1],[0,0])) + pytest.raises(ValueError,models.PiecewiseconstantFunction([0,2,2],[0,0])) + pytest.raises(ValueError,models.PiecewiseconstantFunction([0],[0,0])) + + +def test_piecewiseconstantfunction(): + transitions = [0,8,16,24] + values = [2,5,8] + fun = models.PiecewiseconstantFunction(transitions,values) + assert (fun.value(10) == 5) and (fun.value(20.5) == 8) and \ + (fun.value(8) == 2) and (fun.value(0) == 2) and (fun.value(24) == 8) + + +def test_constantfunction(): + transitions = [0,24] + values = [20] + fun = models.PiecewiseconstantFunction(transitions,values) + for t in [0,1,8,10,16,20.1,24]: + assert (fun.value(t) == 20) + + +def test_piecewiseconstantfunction_vs_interval(): + transitions = [0,8,16,24] + values = [0,1,0] + fun = models.PiecewiseconstantFunction(transitions,values) + interval = models.SpecificInterval(present_times=(8,16)) + for t in [0,1,8,10,16,20.1,24]: + assert fun.interval().triggered(t) == interval().triggered(t) + From 4bdb23d26bc4fb9db4b3dcd45d5a3aa099c849b2 Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Wed, 4 Nov 2020 13:49:51 +0100 Subject: [PATCH 02/19] Few improvements on the tests for picewise constant functions --- cara/tests/test_known_quantities.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/cara/tests/test_known_quantities.py b/cara/tests/test_known_quantities.py index a2cefc06..498e3ace 100644 --- a/cara/tests/test_known_quantities.py +++ b/cara/tests/test_known_quantities.py @@ -151,32 +151,37 @@ def test_expiration_aerosols(): def test_piecewiseconstantfunction_wrongarguments(): + # number of values should be 1+number of transition times pytest.raises(ValueError,models.PiecewiseconstantFunction([0,1],[0,0])) - pytest.raises(ValueError,models.PiecewiseconstantFunction([0,2,2],[0,0])) pytest.raises(ValueError,models.PiecewiseconstantFunction([0],[0,0])) + # two transition times cannot be equal + pytest.raises(ValueError,models.PiecewiseconstantFunction([0,2,2],[0,0])) + # unsorted transition times are not allowed + pytest.raises(ValueError,models.PiecewiseconstantFunction([2,0],[0,0])) def test_piecewiseconstantfunction(): - transitions = [0,8,16,24] + transition_times = [0,8,16,24] values = [2,5,8] - fun = models.PiecewiseconstantFunction(transitions,values) + fun = models.PiecewiseconstantFunction(transitions_times,values) assert (fun.value(10) == 5) and (fun.value(20.5) == 8) and \ (fun.value(8) == 2) and (fun.value(0) == 2) and (fun.value(24) == 8) def test_constantfunction(): - transitions = [0,24] + transition_times = [0,24] values = [20] - fun = models.PiecewiseconstantFunction(transitions,values) + fun = models.PiecewiseconstantFunction(transition_times,values) for t in [0,1,8,10,16,20.1,24]: assert (fun.value(t) == 20) def test_piecewiseconstantfunction_vs_interval(): - transitions = [0,8,16,24] + transition_times = [0,8,16,24] values = [0,1,0] - fun = models.PiecewiseconstantFunction(transitions,values) + fun = models.PiecewiseconstantFunction(transition_times,values) interval = models.SpecificInterval(present_times=(8,16)) + assert interval.transition_times() == set(transition_times) for t in [0,1,8,10,16,20.1,24]: assert fun.interval().triggered(t) == interval().triggered(t) From 4609e2ada9ecd79204b101b57cbfe6a50fdcdac0 Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Wed, 4 Nov 2020 15:58:04 +0100 Subject: [PATCH 03/19] Still some fixes on the tests for piecewise constant functions --- cara/tests/test_known_quantities.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cara/tests/test_known_quantities.py b/cara/tests/test_known_quantities.py index 498e3ace..10243f91 100644 --- a/cara/tests/test_known_quantities.py +++ b/cara/tests/test_known_quantities.py @@ -152,18 +152,18 @@ def test_expiration_aerosols(): def test_piecewiseconstantfunction_wrongarguments(): # number of values should be 1+number of transition times - pytest.raises(ValueError,models.PiecewiseconstantFunction([0,1],[0,0])) - pytest.raises(ValueError,models.PiecewiseconstantFunction([0],[0,0])) + pytest.raises(ValueError,models.PiecewiseconstantFunction,[0,1],[0,0]) + pytest.raises(ValueError,models.PiecewiseconstantFunction,[0],[0,0]) # two transition times cannot be equal - pytest.raises(ValueError,models.PiecewiseconstantFunction([0,2,2],[0,0])) + pytest.raises(ValueError,models.PiecewiseconstantFunction,[0,2,2],[0,0]) # unsorted transition times are not allowed - pytest.raises(ValueError,models.PiecewiseconstantFunction([2,0],[0,0])) + pytest.raises(ValueError,models.PiecewiseconstantFunction,[2,0],[0,0]) def test_piecewiseconstantfunction(): transition_times = [0,8,16,24] values = [2,5,8] - fun = models.PiecewiseconstantFunction(transitions_times,values) + fun = models.PiecewiseconstantFunction(transition_times,values) assert (fun.value(10) == 5) and (fun.value(20.5) == 8) and \ (fun.value(8) == 2) and (fun.value(0) == 2) and (fun.value(24) == 8) @@ -180,8 +180,8 @@ def test_piecewiseconstantfunction_vs_interval(): transition_times = [0,8,16,24] values = [0,1,0] fun = models.PiecewiseconstantFunction(transition_times,values) - interval = models.SpecificInterval(present_times=(8,16)) - assert interval.transition_times() == set(transition_times) + interval = models.SpecificInterval(present_times=[(8,16)]) + assert interval.transition_times() == fun.interval().transition_times() for t in [0,1,8,10,16,20.1,24]: assert fun.interval().triggered(t) == interval().triggered(t) From c5dc9d9a4f9c4fcee94863183348571174085142 Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Wed, 4 Nov 2020 20:57:49 +0100 Subject: [PATCH 04/19] Adding PiecewiseconstantFunction class in models.py (for temperature) --- cara/models.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/cara/models.py b/cara/models.py index 95aabe08..e19b9fec 100644 --- a/cara/models.py +++ b/cara/models.py @@ -6,7 +6,6 @@ from abc import abstractmethod from dataclasses import dataclass - @dataclass(frozen=True) class Room: # The total volume of the room @@ -72,6 +71,38 @@ class PeriodicInterval(Interval): return tuple(result) +@dataclass(frozen=True) +class PiecewiseconstantFunction: + #: transition times at which the function changes value (hours). + transition_times: typing.Tuple[float, ...] + + #: values of the function between transitions + values: typing.Tuple[float, ...] + + def __post_init__(self): + if len(self.transition_times) != len(self.values)+1: + raise ValueError("transition_times should contain one more element than values") + if list(set(self.transition_times)) != self.transition_times: + raise ValueError("transition_times should not contain duplicated elements and should be sorted") + + def value(self,time) -> float: + if self.transition_times[0] == time: + return self.values[0] + for t1,t2,value in zip(self.transition_times[:-1], + self.transition_times[1:],self.values): + if time > t1 and time <= t2: + return value + + def interval(self) -> Interval: + # build an Interval object + present_times = [] + for t1,t2,value in zip(self.transition_times[:-1], + self.transition_times[1:],self.values): + if value: + present_times.append((t1,t2)) + return SpecificInterval(present_times=present_times) + + @dataclass(frozen=True) class Ventilation: """ From 76c4364e8facce1bc1d2f54610f653a314507d6c Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Wed, 4 Nov 2020 21:09:55 +0100 Subject: [PATCH 05/19] Adding Geneva hourly temperatures in models.py --- cara/models.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/cara/models.py b/cara/models.py index e19b9fec..3b2c5534 100644 --- a/cara/models.py +++ b/cara/models.py @@ -6,6 +6,35 @@ from abc import abstractmethod from dataclasses import dataclass +# average temperature of each month, hour per hour (from midnight to 11 pm) +Geneva_hourly_temperatures_per_hour = { + 'Jan': [-0.3, -0.5, -0.9, -1.1, -1.4, -1.5, -1.5, -1.1, 0.1, 1.5, 2.8, + 3.8, 4.4, 4.5, 4.4, 4.4, 3.9, 3.1, 2.7, 2.2, 1.7, 1.5, 1.1], + 'Feb': [0.3, 0.0, -0.5, -0.7, -1.1, -1.2, -1.1, -0.7, 0.8, 2.5, 4.2, + 5.4, 6.2, 6.3, 6.2, 6.1, 5.5, 4.5, 4.1, 3.5, 2.8, 2.5, 2.0], + 'Mar': [3.5, 3.1, 2.5, 2.1, 1.6, 1.5, 1.6, 2.2, 4.0, 6.3, 8.4, 10.0, + 11.1, 11.2, 11.1, 11.0, 10.2, 8.9, 8.3, 7.5, 6.7, 6.3, 5.6], + 'Apr': [6.7, 6.2, 5.5, 5.2, 4.7, 4.5, 4.6, 5.3, 7.2, 9.6, 11.9, 13.7, + 14.8, 14.9, 14.8, 14.7, 13.8, 12.4, 11.8, 10.9, 10.1, 9.6, 8.9], + 'May': [11.1, 10.6, 9.9, 9.5, 8.9, 8.8, 8.9, 9.6, 11.6, 14.2, 16.6, + 18.4, 19.6, 19.7, 19.6, 19.4, 18.6, 17.1, 16.5, 15.6, 14.6, 14.2, 13.4], + 'Jun': [14.4, 13.9, 13.2, 12.7, 12.2, 12.0, 12.1, 12.8, 15.0, 17.7, + 20.2, 22.1, 23.3, 23.5, 23.4, 23.2, 22.3, 20.8, 20.1, 19.1, 18.2, 17.7, 16.9], + 'Jul': [16.7, 16.1, 15.3, 14.9, 14.3, 14.1, 14.2, 15.0, 17.3, 20.2, + 23.0, 25.0, 26.3, 26.5, 26.4, 26.2, 25.2, 23.6, 22.8, 21.8, 20.8, 20.2, 19.4], + 'Aug': [16.2, 15.7, 14.9, 14.5, 13.9, 13.7, 13.8, 14.6, 16.9, 19.7, + 22.4, 24.4, 25.6, 25.8, 25.7, 25.5, 24.5, 22.9, 22.2, 21.2, 20.2, 19.7, 18.9], + 'Sep': [12.7, 12.2, 11.5, 11.2, 10.7, 10.5, 10.6, 11.3, 13.2, 15.6, + 17.9, 19.6, 20.8, 20.9, 20.8, 20.7, 19.8, 18.4, 17.8, 16.9, 16.1, 15.6, 14.9], + 'Oct': [8.8, 8.5, 7.9, 7.6, 7.2, 7.1, 7.2, 7.7, 9.3, 11.2, 13.0, 14.4, + 15.3, 15.4, 15.3, 15.2, 14.5, 13.4, 12.9, 12.2, 11.6, 11.2, 10.6], + 'Nov': [3.6, 3.3, 2.9, 2.6, 2.3, 2.2, 2.2, 2.7, 3.9, 5.5, 6.9, 8.0, + 8.7, 8.8, 8.7, 8.7, 8.1, 7.2, 6.8, 6.3, 5.7, 5.5, 5.0], + 'Dec': [1.0, 0.8, 0.4, 0.2, -0.0, -0.1, -0.1, 0.3, 1.3, 2.6, 3.8, 4.7, + 5.2, 5.3, 5.2, 5.2, 4.7, 4.0, 3.7, 3.2, 2.8, 2.6, 2.2] + } + + @dataclass(frozen=True) class Room: # The total volume of the room @@ -103,6 +132,11 @@ class PiecewiseconstantFunction: return SpecificInterval(present_times=present_times) +GenevaTemperatures = { + month: PiecewiseconstantFunction(list(range(24)),temperatures) + for month,temperatures in Geneva_hourly_temperatures_per_hour.items() +} + @dataclass(frozen=True) class Ventilation: """ From 71c032cf0115a3d64782681035901c5715af5afe Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Wed, 4 Nov 2020 21:35:20 +0100 Subject: [PATCH 06/19] Converting Geneva temperatures to Kelvin --- cara/models.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cara/models.py b/cara/models.py index 3b2c5534..b8be7c1e 100644 --- a/cara/models.py +++ b/cara/models.py @@ -7,7 +7,7 @@ from abc import abstractmethod from dataclasses import dataclass # average temperature of each month, hour per hour (from midnight to 11 pm) -Geneva_hourly_temperatures_per_hour = { +Geneva_hourly_temperatures_celsius_per_hour = { 'Jan': [-0.3, -0.5, -0.9, -1.1, -1.4, -1.5, -1.5, -1.1, 0.1, 1.5, 2.8, 3.8, 4.4, 4.5, 4.4, 4.4, 3.9, 3.1, 2.7, 2.2, 1.7, 1.5, 1.1], 'Feb': [0.3, 0.0, -0.5, -0.7, -1.1, -1.2, -1.1, -0.7, 0.8, 2.5, 4.2, @@ -132,11 +132,14 @@ class PiecewiseconstantFunction: return SpecificInterval(present_times=present_times) +# Geneva hourly temperatures as piecewise constant function (in Kelvin) GenevaTemperatures = { - month: PiecewiseconstantFunction(list(range(24)),temperatures) - for month,temperatures in Geneva_hourly_temperatures_per_hour.items() + month: PiecewiseconstantFunction(list(range(24)), + (273.15+np.array(temperatures)).tolist()) + for month,temperatures in Geneva_hourly_temperatures_celsius_per_hour.items() } + @dataclass(frozen=True) class Ventilation: """ From e49579d3fe22b4ae6c7eb7bf04728107515e865b Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Wed, 4 Nov 2020 22:03:31 +0100 Subject: [PATCH 07/19] Bug fixed in piecewise constant function test; adding a test on ACH computation with windows opened --- cara/tests/test_known_quantities.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/cara/tests/test_known_quantities.py b/cara/tests/test_known_quantities.py index 10243f91..fc563071 100644 --- a/cara/tests/test_known_quantities.py +++ b/cara/tests/test_known_quantities.py @@ -183,5 +183,18 @@ def test_piecewiseconstantfunction_vs_interval(): interval = models.SpecificInterval(present_times=[(8,16)]) assert interval.transition_times() == fun.interval().transition_times() for t in [0,1,8,10,16,20.1,24]: - assert fun.interval().triggered(t) == interval().triggered(t) + assert fun.interval().triggered(t) == interval.triggered(t) + + +def test_windowopening(): + w1 = models.WindowOpening(active=models.SpecificInterval([(0,24)]), + inside_temp=293.15,outside_temp=283.15, + window_height=1.,opening_length=0.6) + w2 = models.WindowOpening(active=models.SpecificInterval([(0,24)]), + inside_temp=293.15,outside_temp=273.15, + window_height=1.,opening_length=0.6) + npt.assert_allclose(w1.air_exchange(models.Room(volume=68),10.), + 3.7393925,rtol=1e-5) + npt.assert_allclose(w2.air_exchange(models.Room(volume=68),10.), + 5.3842316,rtol=1e-5) From ec2a54c0a6ab1a6b9bcb6a31e05a34dd45c297d1 Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Wed, 4 Nov 2020 22:42:01 +0100 Subject: [PATCH 08/19] Bug fix in PiecewiseconstantFunction (models.py) --- cara/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cara/models.py b/cara/models.py index b8be7c1e..cec5f9c6 100644 --- a/cara/models.py +++ b/cara/models.py @@ -111,7 +111,7 @@ class PiecewiseconstantFunction: def __post_init__(self): if len(self.transition_times) != len(self.values)+1: raise ValueError("transition_times should contain one more element than values") - if list(set(self.transition_times)) != self.transition_times: + if sorted(list(set(self.transition_times))) != self.transition_times: raise ValueError("transition_times should not contain duplicated elements and should be sorted") def value(self,time) -> float: From b070bb28996ff60fda49d447849d04d19b372341 Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Wed, 4 Nov 2020 22:44:27 +0100 Subject: [PATCH 09/19] Modifying the test on the WindowOpening class, using time-dependent temperature --- cara/tests/test_known_quantities.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/cara/tests/test_known_quantities.py b/cara/tests/test_known_quantities.py index fc563071..dae53343 100644 --- a/cara/tests/test_known_quantities.py +++ b/cara/tests/test_known_quantities.py @@ -187,14 +187,13 @@ def test_piecewiseconstantfunction_vs_interval(): def test_windowopening(): - w1 = models.WindowOpening(active=models.SpecificInterval([(0,24)]), - inside_temp=293.15,outside_temp=283.15, + tempOutside = models.PiecewiseconstantFunction([0,10,24],[273.15,283.15]) + tempInside = models.PiecewiseconstantFunction([0,24],[293.15]) + w = models.WindowOpening(active=models.SpecificInterval([(0,24)]), + inside_temp=tempInside,outside_temp=tempOutside, window_height=1.,opening_length=0.6) - w2 = models.WindowOpening(active=models.SpecificInterval([(0,24)]), - inside_temp=293.15,outside_temp=273.15, - window_height=1.,opening_length=0.6) - npt.assert_allclose(w1.air_exchange(models.Room(volume=68),10.), + npt.assert_allclose(w.air_exchange(models.Room(volume=68),16.), 3.7393925,rtol=1e-5) - npt.assert_allclose(w2.air_exchange(models.Room(volume=68),10.), + npt.assert_allclose(w.air_exchange(models.Room(volume=68),8.), 5.3842316,rtol=1e-5) From d1246c463e2e5635891a86852ba079a6f8ee183e Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Wed, 4 Nov 2020 22:54:51 +0100 Subject: [PATCH 10/19] Replacing inside and outside temperature by piecewise constant functions, in WindowOpening class (models.py) --- cara/models.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cara/models.py b/cara/models.py index cec5f9c6..7ea4668e 100644 --- a/cara/models.py +++ b/cara/models.py @@ -174,8 +174,8 @@ class WindowOpening(Ventilation): #: The interval in which the window is open. active: Interval - inside_temp: float #: The temperature inside the room (Kelvin) - outside_temp: float #: The temperature outside of the window (Kelvin) + inside_temp: PiecewiseconstantFunction #: The temperature inside the room (Kelvin) + outside_temp: PiecewiseconstantFunction #: The temperature outside of the window (Kelvin) window_height: float #: The height of the window @@ -190,7 +190,8 @@ class WindowOpening(Ventilation): # Reminder, no dependence on time in the resulting calculation. - temp_delta = abs(self.inside_temp - self.outside_temp) / self.outside_temp + temp_delta = abs(self.inside_temp.value(time) - + self.outside_temp.value(time)) / self.outside_temp.value(time) root = np.sqrt(9.81 * self.window_height * temp_delta) return (3600 / (3 * room.volume)) * self.cd_b * self.window_height * self.opening_length * root From 15aee76ca998892e0f0968cde0aae70dcfec9eff Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Wed, 4 Nov 2020 23:35:06 +0100 Subject: [PATCH 11/19] Manipulating tuples rather than lists in PiecewiseconstantFunction objects (also in tests), for hashing purposes --- cara/models.py | 6 +++--- cara/tests/test_known_quantities.py | 24 ++++++++++++------------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cara/models.py b/cara/models.py index 7ea4668e..c876e3af 100644 --- a/cara/models.py +++ b/cara/models.py @@ -111,7 +111,7 @@ class PiecewiseconstantFunction: def __post_init__(self): if len(self.transition_times) != len(self.values)+1: raise ValueError("transition_times should contain one more element than values") - if sorted(list(set(self.transition_times))) != self.transition_times: + if tuple(sorted(set(self.transition_times))) != self.transition_times: raise ValueError("transition_times should not contain duplicated elements and should be sorted") def value(self,time) -> float: @@ -134,8 +134,8 @@ class PiecewiseconstantFunction: # Geneva hourly temperatures as piecewise constant function (in Kelvin) GenevaTemperatures = { - month: PiecewiseconstantFunction(list(range(24)), - (273.15+np.array(temperatures)).tolist()) + month: PiecewiseconstantFunction(tuple(range(24)), + tuple(273.15+np.array(temperatures))) for month,temperatures in Geneva_hourly_temperatures_celsius_per_hour.items() } diff --git a/cara/tests/test_known_quantities.py b/cara/tests/test_known_quantities.py index dae53343..350efa68 100644 --- a/cara/tests/test_known_quantities.py +++ b/cara/tests/test_known_quantities.py @@ -152,33 +152,33 @@ def test_expiration_aerosols(): def test_piecewiseconstantfunction_wrongarguments(): # number of values should be 1+number of transition times - pytest.raises(ValueError,models.PiecewiseconstantFunction,[0,1],[0,0]) - pytest.raises(ValueError,models.PiecewiseconstantFunction,[0],[0,0]) + pytest.raises(ValueError,models.PiecewiseconstantFunction,(0,1),(0,0)) + pytest.raises(ValueError,models.PiecewiseconstantFunction,(0,),(0,0)) # two transition times cannot be equal - pytest.raises(ValueError,models.PiecewiseconstantFunction,[0,2,2],[0,0]) + pytest.raises(ValueError,models.PiecewiseconstantFunction,(0,2,2),(0,0)) # unsorted transition times are not allowed - pytest.raises(ValueError,models.PiecewiseconstantFunction,[2,0],[0,0]) + pytest.raises(ValueError,models.PiecewiseconstantFunction,(2,0),(0,0)) def test_piecewiseconstantfunction(): - transition_times = [0,8,16,24] - values = [2,5,8] + transition_times = (0,8,16,24) + values = (2,5,8) fun = models.PiecewiseconstantFunction(transition_times,values) assert (fun.value(10) == 5) and (fun.value(20.5) == 8) and \ (fun.value(8) == 2) and (fun.value(0) == 2) and (fun.value(24) == 8) def test_constantfunction(): - transition_times = [0,24] - values = [20] + transition_times = (0,24) + values = (20,) fun = models.PiecewiseconstantFunction(transition_times,values) for t in [0,1,8,10,16,20.1,24]: assert (fun.value(t) == 20) def test_piecewiseconstantfunction_vs_interval(): - transition_times = [0,8,16,24] - values = [0,1,0] + transition_times = (0,8,16,24) + values = (0,1,0) fun = models.PiecewiseconstantFunction(transition_times,values) interval = models.SpecificInterval(present_times=[(8,16)]) assert interval.transition_times() == fun.interval().transition_times() @@ -187,8 +187,8 @@ def test_piecewiseconstantfunction_vs_interval(): def test_windowopening(): - tempOutside = models.PiecewiseconstantFunction([0,10,24],[273.15,283.15]) - tempInside = models.PiecewiseconstantFunction([0,24],[293.15]) + tempOutside = models.PiecewiseconstantFunction((0,10,24),(273.15,283.15)) + tempInside = models.PiecewiseconstantFunction((0,24),(293.15,)) w = models.WindowOpening(active=models.SpecificInterval([(0,24)]), inside_temp=tempInside,outside_temp=tempOutside, window_height=1.,opening_length=0.6) From ae5cb9536bc82b2040e23ffa2c2e281924046ca7 Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Wed, 4 Nov 2020 23:39:41 +0100 Subject: [PATCH 12/19] baseline_model in tests and expert.py, now use piecewise constant temperatures --- cara/apps/expert.py | 5 +++-- cara/tests/test_known_quantities.py | 10 ++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/cara/apps/expert.py b/cara/apps/expert.py index c6ee010e..e553aac4 100644 --- a/cara/apps/expert.py +++ b/cara/apps/expert.py @@ -288,8 +288,9 @@ baseline_model = models.Model( room=models.Room(volume=75), ventilation=models.WindowOpening( active=models.PeriodicInterval(period=120, duration=120), - inside_temp=293, outside_temp=283, cd_b=0.6, - window_height=1.6, opening_length=0.6, + inside_temp=models.PiecewiseconstantFunction((0,24),(293,)), + outside_temp=models.PiecewiseconstantFunction((0,24),(283,)), + cd_b=0.6, window_height=1.6, opening_length=0.6, ), infected=models.InfectedPerson( virus=models.Virus.types['SARS_CoV_2'], diff --git a/cara/tests/test_known_quantities.py b/cara/tests/test_known_quantities.py index 350efa68..9a9342be 100644 --- a/cara/tests/test_known_quantities.py +++ b/cara/tests/test_known_quantities.py @@ -29,8 +29,9 @@ def baseline_model(): room=models.Room(volume=75), ventilation=models.WindowOpening( active=models.PeriodicInterval(period=120, duration=120), - inside_temp=293, outside_temp=283, cd_b=0.6, - window_height=1.6, opening_length=0.6, + inside_temp=models.PiecewiseconstantFunction((0,24),(293,)), + outside_temp=models.PiecewiseconstantFunction((0,24),(283,)), + cd_b=0.6, window_height=1.6, opening_length=0.6, ), infected=models.InfectedPerson( virus=models.Virus.types['SARS_CoV_2'], @@ -50,8 +51,9 @@ def baseline_model(): def baseline_periodic_window(): return models.WindowOpening( active=models.PeriodicInterval(period=120, duration=15), - inside_temp=293, outside_temp=283, cd_b=0.6, - window_height=1.6, opening_length=0.6, + inside_temp=models.PiecewiseconstantFunction((0,24),(293,)), + outside_temp=models.PiecewiseconstantFunction((0,24),(283,)), + cd_b=0.6, window_height=1.6, opening_length=0.6, ) From 3604c6c91c8949c7382d218a701a2bf374117395 Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Thu, 5 Nov 2020 01:04:32 +0100 Subject: [PATCH 13/19] Adding two tests for the hourly dependent model --- cara/tests/test_known_quantities.py | 65 +++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/cara/tests/test_known_quantities.py b/cara/tests/test_known_quantities.py index 9a9342be..e32197e3 100644 --- a/cara/tests/test_known_quantities.py +++ b/cara/tests/test_known_quantities.py @@ -199,3 +199,68 @@ def test_windowopening(): npt.assert_allclose(w.air_exchange(models.Room(volume=68),8.), 5.3842316,rtol=1e-5) + +def build_hourly_dependent_model(month, intervals_open=((7.5, 8.5),)): + model = models.Model( + room=models.Room(volume=75), + ventilation=models.WindowOpening( + active=models.SpecificInterval(intervals_open), + inside_temp=models.PiecewiseconstantFunction((0,24),(293,)), + outside_temp=models.GenevaTemperatures[month], + cd_b=0.6, window_height=1.6, opening_length=0.6, + ), + infected=models.InfectedPerson( + virus=models.Virus.types['SARS_CoV_2'], + presence=models.SpecificInterval(((0, 4), (5, 7.5))), + mask=models.Mask.types['No mask'], + activity=models.Activity.types['Light exercise'], + expiration=models.Expiration.types['Unmodulated Vocalization'], + ), + infected_occupants=1, + exposed_occupants=10, + exposed_activity=models.Activity.types['Light exercise'], + ) + return model + + +def build_constant_temp_model(outside_temp, intervals_open=((7.5, 8.5),)): + model = models.Model( + room=models.Room(volume=75), + ventilation=models.WindowOpening( + active=models.SpecificInterval(intervals_open), + inside_temp=models.PiecewiseconstantFunction((0,24),(293,)), + outside_temp=models.PiecewiseconstantFunction((0,24),(outside_temp,)), + cd_b=0.6, window_height=1.6, opening_length=0.6, + ), + infected=models.InfectedPerson( + virus=models.Virus.types['SARS_CoV_2'], + presence=models.SpecificInterval(((0, 4), (5, 7.5))), + mask=models.Mask.types['No mask'], + activity=models.Activity.types['Light exercise'], + expiration=models.Expiration.types['Unmodulated Vocalization'], + ), + infected_occupants=1, + exposed_occupants=10, + exposed_activity=models.Activity.types['Light exercise'], + ) + return model + + +def test_concentrations_hourly_dep_startup(): + # The concentrations should be the same up to 8 AM (time when the + # temperature changes DURING the window opening). + for month,temperatures in models.Geneva_hourly_temperatures_celsius_per_hour.items(): + m1 = build_hourly_dependent_model(month) + m2 = build_constant_temp_model(temperatures[7]+273.15) + for t in [0.5, 1.2, 2., 3.5, 5., 6.5, 7.5, 7.9, 8.]: + npt.assert_allclose(m1.concentration(t), m2.concentration(t), rtol=1e-5) + + +def test_concentrations_hourly_dep_adding_artificial_transitions(): + # Adding a second opening inside the first one should not change anything + for month,temperatures in models.Geneva_hourly_temperatures_celsius_per_hour.items(): + m1 = build_hourly_dependent_model(month,intervals_open=((7.5, 8.5),)) + m2 = build_hourly_dependent_model(month,intervals_open=((7.5, 8.5),(8.,8.1))) + for t in [0.5, 1.2, 2., 3.5, 5., 6.5, 7.5, 7.9, 8., 8.5, 9., 12.]: + npt.assert_allclose(m1.concentration(t), m2.concentration(t), rtol=1e-5) + From 4254c87ac2107e4846417924050c93de9031038b Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Thu, 5 Nov 2020 01:06:04 +0100 Subject: [PATCH 14/19] Correcting missing first hour in Geneva temperatures --- cara/models.py | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/cara/models.py b/cara/models.py index c876e3af..32f1fa65 100644 --- a/cara/models.py +++ b/cara/models.py @@ -8,30 +8,30 @@ from dataclasses import dataclass # average temperature of each month, hour per hour (from midnight to 11 pm) Geneva_hourly_temperatures_celsius_per_hour = { - 'Jan': [-0.3, -0.5, -0.9, -1.1, -1.4, -1.5, -1.5, -1.1, 0.1, 1.5, 2.8, - 3.8, 4.4, 4.5, 4.4, 4.4, 3.9, 3.1, 2.7, 2.2, 1.7, 1.5, 1.1], - 'Feb': [0.3, 0.0, -0.5, -0.7, -1.1, -1.2, -1.1, -0.7, 0.8, 2.5, 4.2, - 5.4, 6.2, 6.3, 6.2, 6.1, 5.5, 4.5, 4.1, 3.5, 2.8, 2.5, 2.0], - 'Mar': [3.5, 3.1, 2.5, 2.1, 1.6, 1.5, 1.6, 2.2, 4.0, 6.3, 8.4, 10.0, - 11.1, 11.2, 11.1, 11.0, 10.2, 8.9, 8.3, 7.5, 6.7, 6.3, 5.6], - 'Apr': [6.7, 6.2, 5.5, 5.2, 4.7, 4.5, 4.6, 5.3, 7.2, 9.6, 11.9, 13.7, - 14.8, 14.9, 14.8, 14.7, 13.8, 12.4, 11.8, 10.9, 10.1, 9.6, 8.9], - 'May': [11.1, 10.6, 9.9, 9.5, 8.9, 8.8, 8.9, 9.6, 11.6, 14.2, 16.6, - 18.4, 19.6, 19.7, 19.6, 19.4, 18.6, 17.1, 16.5, 15.6, 14.6, 14.2, 13.4], - 'Jun': [14.4, 13.9, 13.2, 12.7, 12.2, 12.0, 12.1, 12.8, 15.0, 17.7, - 20.2, 22.1, 23.3, 23.5, 23.4, 23.2, 22.3, 20.8, 20.1, 19.1, 18.2, 17.7, 16.9], - 'Jul': [16.7, 16.1, 15.3, 14.9, 14.3, 14.1, 14.2, 15.0, 17.3, 20.2, - 23.0, 25.0, 26.3, 26.5, 26.4, 26.2, 25.2, 23.6, 22.8, 21.8, 20.8, 20.2, 19.4], - 'Aug': [16.2, 15.7, 14.9, 14.5, 13.9, 13.7, 13.8, 14.6, 16.9, 19.7, - 22.4, 24.4, 25.6, 25.8, 25.7, 25.5, 24.5, 22.9, 22.2, 21.2, 20.2, 19.7, 18.9], - 'Sep': [12.7, 12.2, 11.5, 11.2, 10.7, 10.5, 10.6, 11.3, 13.2, 15.6, - 17.9, 19.6, 20.8, 20.9, 20.8, 20.7, 19.8, 18.4, 17.8, 16.9, 16.1, 15.6, 14.9], - 'Oct': [8.8, 8.5, 7.9, 7.6, 7.2, 7.1, 7.2, 7.7, 9.3, 11.2, 13.0, 14.4, - 15.3, 15.4, 15.3, 15.2, 14.5, 13.4, 12.9, 12.2, 11.6, 11.2, 10.6], - 'Nov': [3.6, 3.3, 2.9, 2.6, 2.3, 2.2, 2.2, 2.7, 3.9, 5.5, 6.9, 8.0, - 8.7, 8.8, 8.7, 8.7, 8.1, 7.2, 6.8, 6.3, 5.7, 5.5, 5.0], - 'Dec': [1.0, 0.8, 0.4, 0.2, -0.0, -0.1, -0.1, 0.3, 1.3, 2.6, 3.8, 4.7, - 5.2, 5.3, 5.2, 5.2, 4.7, 4.0, 3.7, 3.2, 2.8, 2.6, 2.2] + 'Jan': [0.2, -0.3, -0.5, -0.9, -1.1, -1.4, -1.5, -1.5, -1.1, 0.1, 1.5, + 2.8, 3.8, 4.4, 4.5, 4.4, 4.4, 3.9, 3.1, 2.7, 2.2, 1.7, 1.5, 1.1], + 'Feb': [0.9, 0.3, 0.0, -0.5, -0.7, -1.1, -1.2, -1.1, -0.7, 0.8, 2.5, + 4.2, 5.4, 6.2, 6.3, 6.2, 6.1, 5.5, 4.5, 4.1, 3.5, 2.8, 2.5, 2.0], + 'Mar': [4.2, 3.5, 3.1, 2.5, 2.1, 1.6, 1.5, 1.6, 2.2, 4.0, 6.3, 8.4, + 10.0, 11.1, 11.2, 11.1, 11.0, 10.2, 8.9, 8.3, 7.5, 6.7, 6.3, 5.6], + 'Apr': [7.4, 6.7, 6.2, 5.5, 5.2, 4.7, 4.5, 4.6, 5.3, 7.2, 9.6, 11.9, + 13.7, 14.8, 14.9, 14.8, 14.7, 13.8, 12.4, 11.8, 10.9, 10.1, 9.6, 8.9], + 'May': [11.8, 11.1, 10.6, 9.9, 9.5, 8.9, 8.8, 8.9, 9.6, 11.6, 14.2, 16.6, + 18.4, 19.6, 19.7, 19.6, 19.4, 18.6, 17.1, 16.5, 15.6, 14.6, 14.2, 13.4], + 'Jun': [15.2, 14.4, 13.9, 13.2, 12.7, 12.2, 12.0, 12.1, 12.8, 15.0, 17.7, + 20.2, 22.1, 23.3, 23.5, 23.4, 23.2, 22.3, 20.8, 20.1, 19.1, 18.2, 17.7, 16.9], + 'Jul': [17.6, 16.7, 16.1, 15.3, 14.9, 14.3, 14.1, 14.2, 15.0, 17.3, 20.2, + 23.0, 25.0, 26.3, 26.5, 26.4, 26.2, 25.2, 23.6, 22.8, 21.8, 20.8, 20.2, 19.4], + 'Aug': [17.1, 16.2, 15.7, 14.9, 14.5, 13.9, 13.7, 13.8, 14.6, 16.9, 19.7, + 22.4, 24.4, 25.6, 25.8, 25.7, 25.5, 24.5, 22.9, 22.2, 21.2, 20.2, 19.7, 18.9], + 'Sep': [13.4, 12.7, 12.2, 11.5, 11.2, 10.7, 10.5, 10.6, 11.3, 13.2, 15.6, + 17.9, 19.6, 20.8, 20.9, 20.8, 20.7, 19.8, 18.4, 17.8, 16.9, 16.1, 15.6, 14.9], + 'Oct': [9.4, 8.8, 8.5, 7.9, 7.6, 7.2, 7.1, 7.2, 7.7, 9.3, 11.2, 13.0, + 14.4, 15.3, 15.4, 15.3, 15.2, 14.5, 13.4, 12.9, 12.2, 11.6, 11.2, 10.6], + 'Nov': [4.0, 3.6, 3.3, 2.9, 2.6, 2.3, 2.2, 2.2, 2.7, 3.9, 5.5, 6.9, 8.0, + 8.7, 8.8, 8.7, 8.7, 8.1, 7.2, 6.8, 6.3, 5.7, 5.5, 5.0], + 'Dec': [1.4, 1.0, 0.8, 0.4, 0.2, -0.0, -0.1, -0.1, 0.3, 1.3, 2.6, 3.8, + 4.7, 5.2, 5.3, 5.2, 5.2, 4.7, 4.0, 3.7, 3.2, 2.8, 2.6, 2.2] } @@ -134,7 +134,7 @@ class PiecewiseconstantFunction: # Geneva hourly temperatures as piecewise constant function (in Kelvin) GenevaTemperatures = { - month: PiecewiseconstantFunction(tuple(range(24)), + month: PiecewiseconstantFunction(tuple(np.arange(25.)), tuple(273.15+np.array(temperatures))) for month,temperatures in Geneva_hourly_temperatures_celsius_per_hour.items() } From 71c6f08cb6db75031b5f2d4e3b19b560f2f629a7 Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Thu, 5 Nov 2020 01:06:36 +0100 Subject: [PATCH 15/19] Adding hours of temperature changes to the state_change_times in the Model class --- cara/models.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cara/models.py b/cara/models.py index 32f1fa65..0f404dfa 100644 --- a/cara/models.py +++ b/cara/models.py @@ -391,6 +391,10 @@ class Model: state_change_times = set() state_change_times.update(self.infected.presence.transition_times()) state_change_times.update(self.ventilation.active.transition_times()) + if isinstance(self.ventilation,WindowOpening): + state_change_times.update(self.ventilation.inside_temp.interval().transition_times()) + state_change_times.update(self.ventilation.outside_temp.interval().transition_times()) + return sorted(state_change_times) def last_state_change(self, time: float): From f9335710b6966ee74f015e6fe85db72b7b1a29a4 Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Thu, 5 Nov 2020 09:10:07 +0100 Subject: [PATCH 16/19] Renaming PiecewiseconstantFunction class into PiecewiseConstant --- cara/apps/expert.py | 4 ++-- cara/models.py | 8 ++++---- cara/tests/test_known_quantities.py | 32 ++++++++++++++--------------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/cara/apps/expert.py b/cara/apps/expert.py index e553aac4..c02c56df 100644 --- a/cara/apps/expert.py +++ b/cara/apps/expert.py @@ -288,8 +288,8 @@ baseline_model = models.Model( room=models.Room(volume=75), ventilation=models.WindowOpening( active=models.PeriodicInterval(period=120, duration=120), - inside_temp=models.PiecewiseconstantFunction((0,24),(293,)), - outside_temp=models.PiecewiseconstantFunction((0,24),(283,)), + inside_temp=models.PiecewiseConstant((0,24),(293,)), + outside_temp=models.PiecewiseConstant((0,24),(283,)), cd_b=0.6, window_height=1.6, opening_length=0.6, ), infected=models.InfectedPerson( diff --git a/cara/models.py b/cara/models.py index 0f404dfa..94bc47ea 100644 --- a/cara/models.py +++ b/cara/models.py @@ -101,7 +101,7 @@ class PeriodicInterval(Interval): @dataclass(frozen=True) -class PiecewiseconstantFunction: +class PiecewiseConstant: #: transition times at which the function changes value (hours). transition_times: typing.Tuple[float, ...] @@ -134,7 +134,7 @@ class PiecewiseconstantFunction: # Geneva hourly temperatures as piecewise constant function (in Kelvin) GenevaTemperatures = { - month: PiecewiseconstantFunction(tuple(np.arange(25.)), + month: PiecewiseConstant(tuple(np.arange(25.)), tuple(273.15+np.array(temperatures))) for month,temperatures in Geneva_hourly_temperatures_celsius_per_hour.items() } @@ -174,8 +174,8 @@ class WindowOpening(Ventilation): #: The interval in which the window is open. active: Interval - inside_temp: PiecewiseconstantFunction #: The temperature inside the room (Kelvin) - outside_temp: PiecewiseconstantFunction #: The temperature outside of the window (Kelvin) + inside_temp: PiecewiseConstant #: The temperature inside the room (Kelvin) + outside_temp: PiecewiseConstant #: The temperature outside of the window (Kelvin) window_height: float #: The height of the window diff --git a/cara/tests/test_known_quantities.py b/cara/tests/test_known_quantities.py index e32197e3..aa5df350 100644 --- a/cara/tests/test_known_quantities.py +++ b/cara/tests/test_known_quantities.py @@ -29,8 +29,8 @@ def baseline_model(): room=models.Room(volume=75), ventilation=models.WindowOpening( active=models.PeriodicInterval(period=120, duration=120), - inside_temp=models.PiecewiseconstantFunction((0,24),(293,)), - outside_temp=models.PiecewiseconstantFunction((0,24),(283,)), + inside_temp=models.PiecewiseConstant((0,24),(293,)), + outside_temp=models.PiecewiseConstant((0,24),(283,)), cd_b=0.6, window_height=1.6, opening_length=0.6, ), infected=models.InfectedPerson( @@ -51,8 +51,8 @@ def baseline_model(): def baseline_periodic_window(): return models.WindowOpening( active=models.PeriodicInterval(period=120, duration=15), - inside_temp=models.PiecewiseconstantFunction((0,24),(293,)), - outside_temp=models.PiecewiseconstantFunction((0,24),(283,)), + inside_temp=models.PiecewiseConstant((0,24),(293,)), + outside_temp=models.PiecewiseConstant((0,24),(283,)), cd_b=0.6, window_height=1.6, opening_length=0.6, ) @@ -154,18 +154,18 @@ def test_expiration_aerosols(): def test_piecewiseconstantfunction_wrongarguments(): # number of values should be 1+number of transition times - pytest.raises(ValueError,models.PiecewiseconstantFunction,(0,1),(0,0)) - pytest.raises(ValueError,models.PiecewiseconstantFunction,(0,),(0,0)) + pytest.raises(ValueError,models.PiecewiseConstant,(0,1),(0,0)) + pytest.raises(ValueError,models.PiecewiseConstant,(0,),(0,0)) # two transition times cannot be equal - pytest.raises(ValueError,models.PiecewiseconstantFunction,(0,2,2),(0,0)) + pytest.raises(ValueError,models.PiecewiseConstant,(0,2,2),(0,0)) # unsorted transition times are not allowed - pytest.raises(ValueError,models.PiecewiseconstantFunction,(2,0),(0,0)) + pytest.raises(ValueError,models.PiecewiseConstant,(2,0),(0,0)) def test_piecewiseconstantfunction(): transition_times = (0,8,16,24) values = (2,5,8) - fun = models.PiecewiseconstantFunction(transition_times,values) + fun = models.PiecewiseConstant(transition_times,values) assert (fun.value(10) == 5) and (fun.value(20.5) == 8) and \ (fun.value(8) == 2) and (fun.value(0) == 2) and (fun.value(24) == 8) @@ -173,7 +173,7 @@ def test_piecewiseconstantfunction(): def test_constantfunction(): transition_times = (0,24) values = (20,) - fun = models.PiecewiseconstantFunction(transition_times,values) + fun = models.PiecewiseConstant(transition_times,values) for t in [0,1,8,10,16,20.1,24]: assert (fun.value(t) == 20) @@ -181,7 +181,7 @@ def test_constantfunction(): def test_piecewiseconstantfunction_vs_interval(): transition_times = (0,8,16,24) values = (0,1,0) - fun = models.PiecewiseconstantFunction(transition_times,values) + fun = models.PiecewiseConstant(transition_times,values) interval = models.SpecificInterval(present_times=[(8,16)]) assert interval.transition_times() == fun.interval().transition_times() for t in [0,1,8,10,16,20.1,24]: @@ -189,8 +189,8 @@ def test_piecewiseconstantfunction_vs_interval(): def test_windowopening(): - tempOutside = models.PiecewiseconstantFunction((0,10,24),(273.15,283.15)) - tempInside = models.PiecewiseconstantFunction((0,24),(293.15,)) + tempOutside = models.PiecewiseConstant((0,10,24),(273.15,283.15)) + tempInside = models.PiecewiseConstant((0,24),(293.15,)) w = models.WindowOpening(active=models.SpecificInterval([(0,24)]), inside_temp=tempInside,outside_temp=tempOutside, window_height=1.,opening_length=0.6) @@ -205,7 +205,7 @@ def build_hourly_dependent_model(month, intervals_open=((7.5, 8.5),)): room=models.Room(volume=75), ventilation=models.WindowOpening( active=models.SpecificInterval(intervals_open), - inside_temp=models.PiecewiseconstantFunction((0,24),(293,)), + inside_temp=models.PiecewiseConstant((0,24),(293,)), outside_temp=models.GenevaTemperatures[month], cd_b=0.6, window_height=1.6, opening_length=0.6, ), @@ -228,8 +228,8 @@ def build_constant_temp_model(outside_temp, intervals_open=((7.5, 8.5),)): room=models.Room(volume=75), ventilation=models.WindowOpening( active=models.SpecificInterval(intervals_open), - inside_temp=models.PiecewiseconstantFunction((0,24),(293,)), - outside_temp=models.PiecewiseconstantFunction((0,24),(outside_temp,)), + inside_temp=models.PiecewiseConstant((0,24),(293,)), + outside_temp=models.PiecewiseConstant((0,24),(outside_temp,)), cd_b=0.6, window_height=1.6, opening_length=0.6, ), infected=models.InfectedPerson( From ec98687233440e7df060d4df0676db166fd99dcf Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Thu, 5 Nov 2020 09:52:58 +0100 Subject: [PATCH 17/19] Adding transition_times method to Ventilation, and changing Model class accordingly --- cara/models.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/cara/models.py b/cara/models.py index 94bc47ea..04d8d2af 100644 --- a/cara/models.py +++ b/cara/models.py @@ -155,6 +155,9 @@ class Ventilation: #: The times at which the air exchange is taking place. active: Interval + def transition_times(self): + return self.active.transition_times() + @abstractmethod def air_exchange(self, room: Room, time: float) -> float: """ @@ -183,6 +186,12 @@ class WindowOpening(Ventilation): cd_b: float = 0.6 #: Discharge coefficient: what portion effective area is used to exchange air (0 <= cd_b <= 1) + def transition_times(self): + transitions = super().transition_times() + transitions.update(self.inside_temp.interval().transition_times()) + transitions.update(self.outside_temp.interval().transition_times()) + return transitions + def air_exchange(self, room: Room, time: float) -> float: # If the window is shut, no air is being exchanged. if not self.active.triggered(time): @@ -390,10 +399,7 @@ class Model: """ state_change_times = set() state_change_times.update(self.infected.presence.transition_times()) - state_change_times.update(self.ventilation.active.transition_times()) - if isinstance(self.ventilation,WindowOpening): - state_change_times.update(self.ventilation.inside_temp.interval().transition_times()) - state_change_times.update(self.ventilation.outside_temp.interval().transition_times()) + state_change_times.update(self.ventilation.transition_times()) return sorted(state_change_times) From 4ea8b70079881197b27e5edd8a0970109571b129 Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Thu, 5 Nov 2020 12:07:26 +0100 Subject: [PATCH 18/19] Adding tests on values beyond the interval of definition, for piecwise constant functions; some tests renaming --- cara/tests/test_known_quantities.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cara/tests/test_known_quantities.py b/cara/tests/test_known_quantities.py index aa5df350..4069bacf 100644 --- a/cara/tests/test_known_quantities.py +++ b/cara/tests/test_known_quantities.py @@ -162,12 +162,13 @@ def test_piecewiseconstantfunction_wrongarguments(): pytest.raises(ValueError,models.PiecewiseConstant,(2,0),(0,0)) -def test_piecewiseconstantfunction(): +def test_piecewiseconstant(): transition_times = (0,8,16,24) values = (2,5,8) fun = models.PiecewiseConstant(transition_times,values) assert (fun.value(10) == 5) and (fun.value(20.5) == 8) and \ - (fun.value(8) == 2) and (fun.value(0) == 2) and (fun.value(24) == 8) + (fun.value(8) == 2) and (fun.value(0) == 2) and \ + (fun.value(24) == 8) and (fun.value(-1) == 2) and (fun.value(25) == 8) def test_constantfunction(): @@ -178,7 +179,7 @@ def test_constantfunction(): assert (fun.value(t) == 20) -def test_piecewiseconstantfunction_vs_interval(): +def test_piecewiseconstant_vs_interval(): transition_times = (0,8,16,24) values = (0,1,0) fun = models.PiecewiseConstant(transition_times,values) From 4e31a411a0d35e12b2cb4a0578535622c479ff6e Mon Sep 17 00:00:00 2001 From: Nicolas Mounet Date: Thu, 5 Nov 2020 12:08:08 +0100 Subject: [PATCH 19/19] Implementing default value beyond the interval of definition, for PiecewiseConstant objects (models.py) --- cara/models.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cara/models.py b/cara/models.py index 04d8d2af..805993ac 100644 --- a/cara/models.py +++ b/cara/models.py @@ -102,6 +102,7 @@ class PeriodicInterval(Interval): @dataclass(frozen=True) class PiecewiseConstant: + #: transition times at which the function changes value (hours). transition_times: typing.Tuple[float, ...] @@ -115,8 +116,11 @@ class PiecewiseConstant: raise ValueError("transition_times should not contain duplicated elements and should be sorted") def value(self,time) -> float: - if self.transition_times[0] == time: + if time <= self.transition_times[0]: return self.values[0] + if time > self.transition_times[-1]: + return self.values[-1] + for t1,t2,value in zip(self.transition_times[:-1], self.transition_times[1:],self.values): if time > t1 and time <= t2: