Prepare the repository for an Apache 2.0 license.
This commit is contained in:
parent
2a43eea709
commit
597a27eadc
11 changed files with 187 additions and 107 deletions
13
LICENSE
Normal file
13
LICENSE
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
Copyright 2020-2021 CERN
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
@ -85,9 +85,7 @@
|
||||||
{% block disclaimer %}
|
{% block disclaimer %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
<p>
|
<p>
|
||||||
CARA is made available for internal CERN use only.
|
At CERN, CARA is intended for Members of Personnel with roles related to Supervision, Health & Safety or Space Management, in order to simulate the concerned workplaces on CERN sites.
|
||||||
It is intended for Members of Personnel with roles related to Supervision, Health & Safety or Space Management, in order to simulate the concerned workplaces on CERN sites.
|
|
||||||
For use outside of this scope, it has to be performed under a pre-defined framework and licence agreement issued by CERN Knowledge Transfer (<a href="mailto:kt@cern.ch">kt@cern.ch</a>).
|
|
||||||
</p>
|
</p>
|
||||||
{% endblock disclaimer %}
|
{% endblock disclaimer %}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -193,7 +193,6 @@
|
||||||
policy issues. Any initiative is conducted on a best effort and as-is basis, without liability or
|
policy issues. Any initiative is conducted on a best effort and as-is basis, without liability or
|
||||||
warranty.</em></span></p>
|
warranty.</em></span></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -231,9 +230,22 @@
|
||||||
</path>
|
</path>
|
||||||
</svg></a>
|
</svg></a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://gitlab.cern.ch/cara/cara" title="CARA code repository" data-extlink="">
|
||||||
|
Source code
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
<p style="font-size:10px;">
|
||||||
|
CARA is <a href="https://gitlab.cern.ch/cara/cara/-/blob/master/LICENSE">Apache 2.0 licensed</a> open-source
|
||||||
|
software developed at CERN.
|
||||||
|
You can find the source code at <a href="https://gitlab.cern.ch/cara/cara">https://gitlab.cern.ch/cara/cara</a>,
|
||||||
|
where we welcome contributions, feature requests and issue reports.
|
||||||
|
</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- general info -->
|
<!-- general info -->
|
||||||
|
|
@ -283,7 +295,7 @@
|
||||||
</path>
|
</path>
|
||||||
</svg></a>
|
</svg></a>
|
||||||
©
|
©
|
||||||
2020
|
2020 - <script>document.write(new Date().getFullYear());</script>
|
||||||
CERN</div>
|
CERN</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,11 @@ the same for all parameters of a single model.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import numpy as np
|
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from scipy.interpolate import interp1d
|
||||||
|
|
||||||
if not typing.TYPE_CHECKING:
|
if not typing.TYPE_CHECKING:
|
||||||
from memoization import cached
|
from memoization import cached
|
||||||
else:
|
else:
|
||||||
|
|
@ -133,15 +135,18 @@ class PiecewiseConstant:
|
||||||
transition_times: typing.Tuple[float, ...]
|
transition_times: typing.Tuple[float, ...]
|
||||||
|
|
||||||
#: values of the function between transitions
|
#: values of the function between transitions
|
||||||
values: typing.Tuple[float, ...]
|
values: typing.Tuple[_VectorisedFloat, ...]
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
if len(self.transition_times) != len(self.values)+1:
|
if len(self.transition_times) != len(self.values)+1:
|
||||||
raise ValueError("transition_times should contain one more element than values")
|
raise ValueError("transition_times should contain one more element than values")
|
||||||
if tuple(sorted(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")
|
raise ValueError("transition_times should not contain duplicated elements and should be sorted")
|
||||||
|
shapes = [np.array(v).shape for v in self.values]
|
||||||
|
if not all(shapes[0] == shape for shape in shapes):
|
||||||
|
raise ValueError("All values must have the same shape")
|
||||||
|
|
||||||
def value(self, time) -> float:
|
def value(self, time) -> _VectorisedFloat:
|
||||||
if time <= self.transition_times[0]:
|
if time <= self.transition_times[0]:
|
||||||
return self.values[0]
|
return self.values[0]
|
||||||
elif time > self.transition_times[-1]:
|
elif time > self.transition_times[-1]:
|
||||||
|
|
@ -162,18 +167,18 @@ class PiecewiseConstant:
|
||||||
present_times.append((t1, t2))
|
present_times.append((t1, t2))
|
||||||
return SpecificInterval(present_times=tuple(present_times))
|
return SpecificInterval(present_times=tuple(present_times))
|
||||||
|
|
||||||
def refine(self, refine_factor=10):
|
def refine(self, refine_factor=10) -> "PiecewiseConstant":
|
||||||
# build a new PiecewiseConstant object with a refined mesh,
|
# build a new PiecewiseConstant object with a refined mesh,
|
||||||
# using a linear interpolation in-between the initial mesh points
|
# using a linear interpolation in-between the initial mesh points
|
||||||
refined_times = np.linspace(self.transition_times[0], self.transition_times[-1],
|
refined_times = np.linspace(self.transition_times[0], self.transition_times[-1],
|
||||||
(len(self.transition_times)-1) * refine_factor+1)
|
(len(self.transition_times)-1) * refine_factor+1)
|
||||||
|
interpolator = interp1d(
|
||||||
|
self.transition_times,
|
||||||
|
np.concatenate([self.values, self.values[-1:]], axis=0),
|
||||||
|
axis=0)
|
||||||
return PiecewiseConstant(
|
return PiecewiseConstant(
|
||||||
tuple(refined_times),
|
tuple(refined_times),
|
||||||
tuple(np.interp(
|
tuple(interpolator(refined_times)[:-1]),
|
||||||
refined_times[:-1],
|
|
||||||
self.transition_times,
|
|
||||||
self.values + (self.values[-1], ),
|
|
||||||
)),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -254,10 +259,10 @@ class WindowOpening(Ventilation):
|
||||||
outside_temp: PiecewiseConstant
|
outside_temp: PiecewiseConstant
|
||||||
|
|
||||||
#: The height of the window (m).
|
#: The height of the window (m).
|
||||||
window_height: float
|
window_height: _VectorisedFloat
|
||||||
|
|
||||||
#: The length of the opening-gap when the window is open (m).
|
#: The length of the opening-gap when the window is open (m).
|
||||||
opening_length: float
|
opening_length: _VectorisedFloat
|
||||||
|
|
||||||
#: The number of windows of the given dimensions.
|
#: The number of windows of the given dimensions.
|
||||||
number_of_windows: int = 1
|
number_of_windows: int = 1
|
||||||
|
|
@ -266,7 +271,7 @@ class WindowOpening(Ventilation):
|
||||||
min_deltaT: float = 0.1
|
min_deltaT: float = 0.1
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def discharge_coefficient(self) -> float:
|
def discharge_coefficient(self) -> _VectorisedFloat:
|
||||||
"""
|
"""
|
||||||
Discharge coefficient (or cd_b): what portion effective area is
|
Discharge coefficient (or cd_b): what portion effective area is
|
||||||
used to exchange air (0 <= discharge_coefficient <= 1).
|
used to exchange air (0 <= discharge_coefficient <= 1).
|
||||||
|
|
@ -286,14 +291,14 @@ class WindowOpening(Ventilation):
|
||||||
return 0.
|
return 0.
|
||||||
|
|
||||||
# Reminder, no dependence on time in the resulting calculation.
|
# Reminder, no dependence on time in the resulting calculation.
|
||||||
inside_temp = self.inside_temp.value(time)
|
inside_temp: _VectorisedFloat = self.inside_temp.value(time)
|
||||||
outside_temp = self.outside_temp.value(time)
|
outside_temp: _VectorisedFloat = self.outside_temp.value(time)
|
||||||
|
|
||||||
# The inside_temperature is forced to be always at least min_deltaT degree
|
# The inside_temperature is forced to be always at least min_deltaT degree
|
||||||
# warmer than the outside_temperature. Further research needed to
|
# warmer than the outside_temperature. Further research needed to
|
||||||
# handle the buoyancy driven ventilation when the temperature gradient
|
# handle the buoyancy driven ventilation when the temperature gradient
|
||||||
# is inverted.
|
# is inverted.
|
||||||
inside_temp = max(inside_temp, outside_temp + self.min_deltaT)
|
inside_temp = np.maximum(inside_temp, outside_temp + self.min_deltaT) # type: ignore
|
||||||
temp_gradient = (inside_temp - outside_temp) / outside_temp
|
temp_gradient = (inside_temp - outside_temp) / outside_temp
|
||||||
root = np.sqrt(9.81 * self.window_height * temp_gradient)
|
root = np.sqrt(9.81 * self.window_height * temp_gradient)
|
||||||
window_area = self.window_height * self.opening_length * self.number_of_windows
|
window_area = self.window_height * self.opening_length * self.number_of_windows
|
||||||
|
|
@ -307,7 +312,7 @@ class SlidingWindow(WindowOpening):
|
||||||
the horizontal plane).
|
the horizontal plane).
|
||||||
"""
|
"""
|
||||||
@property
|
@property
|
||||||
def discharge_coefficient(self) -> float:
|
def discharge_coefficient(self) -> _VectorisedFloat:
|
||||||
"""
|
"""
|
||||||
Average measured value of discharge coefficient for sliding or
|
Average measured value of discharge coefficient for sliding or
|
||||||
side-hung windows.
|
side-hung windows.
|
||||||
|
|
@ -322,14 +327,14 @@ class HingedWindow(WindowOpening):
|
||||||
horizontal plane).
|
horizontal plane).
|
||||||
"""
|
"""
|
||||||
#: Window width (m).
|
#: Window width (m).
|
||||||
window_width: float = 0.0
|
window_width: _VectorisedFloat = 0.0
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
if not self.window_width > 0:
|
if self.window_width is 0.0:
|
||||||
raise ValueError('window_width must be set')
|
raise ValueError('window_width must be set')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def discharge_coefficient(self) -> float:
|
def discharge_coefficient(self) -> _VectorisedFloat:
|
||||||
"""
|
"""
|
||||||
Simple model to compute discharge coefficient for top or bottom
|
Simple model to compute discharge coefficient for top or bottom
|
||||||
hung hinged windows, in the absence of empirical test results
|
hung hinged windows, in the absence of empirical test results
|
||||||
|
|
@ -339,19 +344,15 @@ class HingedWindow(WindowOpening):
|
||||||
see Section 8.3 of BB101 and Section 11.3 of
|
see Section 8.3 of BB101 and Section 11.3 of
|
||||||
ESFA Output Specification Annex 2F on Ventilation opening areas.
|
ESFA Output Specification Annex 2F on Ventilation opening areas.
|
||||||
"""
|
"""
|
||||||
window_ratio = self.window_width / self.window_height
|
window_ratio = np.array(self.window_width / self.window_height)
|
||||||
if window_ratio < 0.5:
|
coefs = np.empty(window_ratio.shape + (2, ), dtype=np.float64)
|
||||||
M = 0.06
|
|
||||||
cd_max = 0.612
|
coefs[window_ratio < 0.5] = (0.06, 0.612)
|
||||||
elif window_ratio < 1:
|
coefs[np.bitwise_and(0.5 <= window_ratio, window_ratio < 1)] = (0.048, 0.589)
|
||||||
M = 0.048
|
coefs[np.bitwise_and(1 <= window_ratio, window_ratio < 2)] = (0.04, 0.563)
|
||||||
cd_max = 0.589
|
coefs[window_ratio >= 2] = (0.038, 0.548)
|
||||||
elif window_ratio < 2:
|
M, cd_max = coefs.T
|
||||||
M = 0.04
|
|
||||||
cd_max = 0.563
|
|
||||||
else:
|
|
||||||
M = 0.038
|
|
||||||
cd_max = 0.548
|
|
||||||
window_angle = 2.*np.rad2deg(np.arcsin(self.opening_length/(2.*self.window_height)))
|
window_angle = 2.*np.rad2deg(np.arcsin(self.opening_length/(2.*self.window_height)))
|
||||||
return cd_max*(1-np.exp(-M*window_angle))
|
return cd_max*(1-np.exp(-M*window_angle))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import cara.models
|
||||||
{'coefficient_of_infectivity': np.array([0.02, 0.05])},
|
{'coefficient_of_infectivity': np.array([0.02, 0.05])},
|
||||||
{'η_exhale': np.array([0.92, 0.95])},
|
{'η_exhale': np.array([0.92, 0.95])},
|
||||||
{'η_leaks': np.array([0.15, 0.20])},
|
{'η_leaks': np.array([0.15, 0.20])},
|
||||||
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
def test_concentration_model_vectorisation(override_params):
|
def test_concentration_model_vectorisation(override_params):
|
||||||
|
|
|
||||||
85
cara/tests/models/test_piecewiseconstant.py
Normal file
85
cara/tests/models/test_piecewiseconstant.py
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
import numpy as np
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from cara import models
|
||||||
|
from cara import data
|
||||||
|
|
||||||
|
|
||||||
|
def test_piecewiseconstantfunction_wrongarguments():
|
||||||
|
# number of values should be 1+number of transition times
|
||||||
|
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.PiecewiseConstant, (0, 2, 2), (0, 0))
|
||||||
|
# unsorted transition times are not allowed
|
||||||
|
pytest.raises(ValueError, models.PiecewiseConstant, (2, 0), (0, 0))
|
||||||
|
|
||||||
|
# If vectors, must all be same length.
|
||||||
|
with pytest.raises(ValueError, match="All values must have the same shape"):
|
||||||
|
models.PiecewiseConstant(
|
||||||
|
(0, 8, 16), (np.array([5, 7]), np.array([8, 9, 10])),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"time, expected_value",
|
||||||
|
[
|
||||||
|
[10, 5],
|
||||||
|
[20.5, 8],
|
||||||
|
[8, 2],
|
||||||
|
[0, 2],
|
||||||
|
[24, 8],
|
||||||
|
[-1, 2],
|
||||||
|
[25, 8],
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_piecewiseconstant(time, expected_value):
|
||||||
|
transition_times = (0, 8, 16, 24)
|
||||||
|
values = (2, 5, 8)
|
||||||
|
fun = models.PiecewiseConstant(transition_times, values)
|
||||||
|
assert fun.value(time) == expected_value
|
||||||
|
|
||||||
|
|
||||||
|
def test_piecewiseconstant_interp():
|
||||||
|
transition_times = (0, 8, 16, 24)
|
||||||
|
values = (2, 5, 8)
|
||||||
|
refined_fun = models.PiecewiseConstant(transition_times, values).refine(refine_factor=2)
|
||||||
|
assert refined_fun.transition_times == (0, 4, 8, 12, 16, 20, 24)
|
||||||
|
assert refined_fun.values == (2, 3.5, 5, 6.5, 8, 8)
|
||||||
|
|
||||||
|
|
||||||
|
def test_piecewiseconstant_interp_vectorised():
|
||||||
|
transition_times = (0, 8, 16, 24)
|
||||||
|
values = (np.array([2, 3]), np.array([5, 7]), np.array([8, 9]))
|
||||||
|
refined_fun = models.PiecewiseConstant(transition_times, values).refine(refine_factor=2)
|
||||||
|
assert refined_fun.transition_times == (0, 4, 8, 12, 16, 20, 24)
|
||||||
|
np.testing.assert_almost_equal(
|
||||||
|
refined_fun.values, ((2, 3), (3.5, 5), (5, 7), (6.5, 8), (8, 9), (8, 9)),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_constantfunction():
|
||||||
|
transition_times = (0, 24)
|
||||||
|
values = (20,)
|
||||||
|
fun = models.PiecewiseConstant(transition_times, values)
|
||||||
|
for t in [0, 1, 8, 10, 16, 20.1, 24]:
|
||||||
|
assert (fun.value(t) == 20)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"time",
|
||||||
|
[0, 1, 8, 10, 16, 20.1, 24],
|
||||||
|
)
|
||||||
|
def test_piecewiseconstant_vs_interval(time):
|
||||||
|
transition_times = (0, 8, 16, 24)
|
||||||
|
values = (0, 1, 0)
|
||||||
|
fun = models.PiecewiseConstant(transition_times, values)
|
||||||
|
interval = models.SpecificInterval(present_times=[(8,16)])
|
||||||
|
assert interval.transition_times() == fun.interval().transition_times()
|
||||||
|
assert fun.interval().triggered(time) == interval.triggered(time)
|
||||||
|
|
||||||
|
|
||||||
|
def test_piecewiseconstant_transition_times():
|
||||||
|
outside_temp = data.GenevaTemperatures['Jan']
|
||||||
|
assert set(outside_temp.transition_times) == outside_temp.interval().transition_times()
|
||||||
|
|
@ -190,70 +190,6 @@ def test_expiration_aerosols():
|
||||||
npt.assert_allclose(exp1.aerosols(mask), exp2.aerosols(mask), rtol=1e-5)
|
npt.assert_allclose(exp1.aerosols(mask), exp2.aerosols(mask), rtol=1e-5)
|
||||||
|
|
||||||
|
|
||||||
def test_piecewiseconstantfunction_wrongarguments():
|
|
||||||
# number of values should be 1+number of transition times
|
|
||||||
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.PiecewiseConstant,(0,2,2),(0,0))
|
|
||||||
# unsorted transition times are not allowed
|
|
||||||
pytest.raises(ValueError,models.PiecewiseConstant,(2,0),(0,0))
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"time, expected_value",
|
|
||||||
[
|
|
||||||
[10, 5],
|
|
||||||
[20.5, 8],
|
|
||||||
[8, 2],
|
|
||||||
[0, 2],
|
|
||||||
[24, 8],
|
|
||||||
[-1, 2],
|
|
||||||
[25, 8],
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_piecewiseconstant(time, expected_value):
|
|
||||||
transition_times = (0, 8, 16, 24)
|
|
||||||
values = (2, 5, 8)
|
|
||||||
fun = models.PiecewiseConstant(transition_times,values)
|
|
||||||
assert fun.value(time) == expected_value
|
|
||||||
|
|
||||||
|
|
||||||
def test_piecewiseconstant_interp():
|
|
||||||
transition_times = (0, 8, 16, 24)
|
|
||||||
values = (2, 5, 8)
|
|
||||||
fun = models.PiecewiseConstant(transition_times,values)
|
|
||||||
refined_fun = models.PiecewiseConstant(transition_times,values).refine(refine_factor=2)
|
|
||||||
assert refined_fun.transition_times == (0, 4, 8, 12, 16, 20 ,24)
|
|
||||||
assert refined_fun.values == (2, 3.5, 5, 6.5, 8, 8)
|
|
||||||
|
|
||||||
|
|
||||||
def test_constantfunction():
|
|
||||||
transition_times = (0,24)
|
|
||||||
values = (20,)
|
|
||||||
fun = models.PiecewiseConstant(transition_times,values)
|
|
||||||
for t in [0,1,8,10,16,20.1,24]:
|
|
||||||
assert (fun.value(t) == 20)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"time",
|
|
||||||
[0,1,8,10,16,20.1,24],
|
|
||||||
)
|
|
||||||
def test_piecewiseconstant_vs_interval(time):
|
|
||||||
transition_times = (0,8,16,24)
|
|
||||||
values = (0,1,0)
|
|
||||||
fun = models.PiecewiseConstant(transition_times,values)
|
|
||||||
interval = models.SpecificInterval(present_times=[(8,16)])
|
|
||||||
assert interval.transition_times() == fun.interval().transition_times()
|
|
||||||
assert fun.interval().triggered(time) == interval.triggered(time)
|
|
||||||
|
|
||||||
|
|
||||||
def test_piecewiseconstant_transition_times():
|
|
||||||
outside_temp=data.GenevaTemperatures['Jan']
|
|
||||||
assert set(outside_temp.transition_times) == outside_temp.interval().transition_times()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"time, expected_value",
|
"time, expected_value",
|
||||||
[
|
[
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,6 @@ def test_number_of_windows(baseline_slidingwindow):
|
||||||
)
|
)
|
||||||
def test_hinged_window(baseline_hingedwindow, window_width,
|
def test_hinged_window(baseline_hingedwindow, window_width,
|
||||||
expected_discharge_coefficient):
|
expected_discharge_coefficient):
|
||||||
room = models.Room(75)
|
|
||||||
hinged_window = dataclasses.replace(baseline_hingedwindow,
|
hinged_window = dataclasses.replace(baseline_hingedwindow,
|
||||||
window_width=window_width)
|
window_width=window_width)
|
||||||
|
|
||||||
|
|
@ -56,9 +55,40 @@ def test_hinged_window(baseline_hingedwindow,window_width,
|
||||||
expected_discharge_coefficient, rtol=1e-2)
|
expected_discharge_coefficient, rtol=1e-2)
|
||||||
|
|
||||||
|
|
||||||
def test_sliding_window(baseline_slidingwindow):
|
@pytest.mark.parametrize(
|
||||||
room = models.Room(75)
|
"override_params", [
|
||||||
|
{'window_height': np.array([0.15, 0.20, 0.25])},
|
||||||
|
{'window_width': np.array([0.15, 0.20, 0.25])},
|
||||||
|
{'opening_length': np.array([0.15, 0.20, 0.25])},
|
||||||
|
{'outside_temp': models.PiecewiseConstant(
|
||||||
|
(0, 2, 3), (np.array([20, 30, 28]), np.array([25, 30, 27]))
|
||||||
|
)},
|
||||||
|
{'inside_temp': models.PiecewiseConstant(
|
||||||
|
(0, 20), (np.array([20, 30, 25]), )
|
||||||
|
)},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_HingedWindow_vectorisation(override_params):
|
||||||
|
defaults = {
|
||||||
|
'window_height': 0.15,
|
||||||
|
'window_width': 0.15,
|
||||||
|
'opening_length': 0.15,
|
||||||
|
'inside_temp': models.PiecewiseConstant((0, 2, 3), (20, 25)),
|
||||||
|
'outside_temp': models.PiecewiseConstant((0, 2, 3), (10, 15)),
|
||||||
|
}
|
||||||
|
defaults.update(override_params)
|
||||||
|
room = models.Room(volume=75)
|
||||||
|
t = 0.5
|
||||||
|
window = models.HingedWindow(models.PeriodicInterval(60, 30), **defaults)
|
||||||
|
if {'window_height', 'opening_length', 'window_width'}.intersection(override_params):
|
||||||
|
assert isinstance(window.discharge_coefficient, np.ndarray)
|
||||||
|
else:
|
||||||
|
assert isinstance(window.discharge_coefficient, float)
|
||||||
|
|
||||||
|
assert isinstance(window.air_exchange(room, t), np.ndarray)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sliding_window(baseline_slidingwindow):
|
||||||
assert baseline_slidingwindow.discharge_coefficient == 0.6
|
assert baseline_slidingwindow.discharge_coefficient == 0.6
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ pyrsistent==0.17.3
|
||||||
python-dateutil==2.8.1
|
python-dateutil==2.8.1
|
||||||
pyzmq==22.0.3
|
pyzmq==22.0.3
|
||||||
qrcode==6.1
|
qrcode==6.1
|
||||||
|
scipy==1.5.4
|
||||||
Send2Trash==1.5.0
|
Send2Trash==1.5.0
|
||||||
six==1.15.0
|
six==1.15.0
|
||||||
sniffio==1.2.0
|
sniffio==1.2.0
|
||||||
|
|
|
||||||
|
|
@ -19,3 +19,6 @@ ignore_missing_imports = True
|
||||||
|
|
||||||
[mypy-qrcode.*]
|
[mypy-qrcode.*]
|
||||||
ignore_missing_imports = True
|
ignore_missing_imports = True
|
||||||
|
|
||||||
|
[mypy-scipy.*]
|
||||||
|
ignore_missing_imports = True
|
||||||
|
|
|
||||||
2
setup.py
2
setup.py
|
|
@ -26,6 +26,7 @@ REQUIREMENTS: dict = {
|
||||||
'mistune',
|
'mistune',
|
||||||
'numpy',
|
'numpy',
|
||||||
'qrcode[pil]',
|
'qrcode[pil]',
|
||||||
|
'scipy',
|
||||||
'tornado',
|
'tornado',
|
||||||
'voila >=0.2.4',
|
'voila >=0.2.4',
|
||||||
],
|
],
|
||||||
|
|
@ -58,6 +59,7 @@ setup(
|
||||||
classifiers=[
|
classifiers=[
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
|
"License :: OSI Approved :: Apache Software License", # Apache 2.0
|
||||||
],
|
],
|
||||||
|
|
||||||
install_requires=REQUIREMENTS['core'],
|
install_requires=REQUIREMENTS['core'],
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue