Merge branch 'develop/expert_app_ventilation_temp' into 'master'

Improving expert app - choice of temperature/month

See merge request cara/cara!77
This commit is contained in:
Philip James Elson 2020-11-11 09:31:41 +00:00
commit 30dc63fc1b

View file

@ -10,6 +10,7 @@ import matplotlib.figure
from cara import models
from cara import state
from cara import data
def collapsible(widgets_to_collapse: typing.List, title: str, start_collapsed=True):
@ -35,7 +36,8 @@ class ConcentrationFigure:
def update(self, model: models.ConcentrationModel):
resolution = 600
ts = np.linspace(0, 10, resolution)
ts = np.linspace(sorted(model.infected.presence.transition_times())[0],
sorted(model.infected.presence.transition_times())[-1], resolution)
concentration = [model.concentration(t) for t in ts]
if self.line is None:
[self.line] = self.ax.plot(ts, concentration)
@ -55,7 +57,7 @@ class ConcentrationFigure:
# Update the top limit based on the concentration if it exceeds 5
# (rare but possible).
top = max([3, max(concentration)])
self.ax.set_ylim(bottom=1e-4, top=top)
self.ax.set_ylim(bottom=0., top=top)
self.figure.canvas.draw()
@ -126,10 +128,10 @@ class WidgetView:
self.widget.children += (self._build_exposed(node),)
def _build_exposed(self, node):
return collapsible(
[self._build_activity(node.exposed.activity)],
title="Exposed"
)
return collapsible([widgets.HBox([
self._build_mask(node.exposed.mask),
self._build_activity(node.exposed.activity),
])], title="Exposed")
def _build_infected(self, node):
return collapsible([widgets.HBox([
@ -158,9 +160,21 @@ class WidgetView:
)
return widget
def _build_outsidetemp(self,node):
outside_temp = widgets.IntSlider(value=10., min=-10., max=30.)
def outsidetemp_change(change):
node.values = (change['new']+273.15,)
outside_temp.observe(outsidetemp_change, names=['value'])
return widgets.VBox([
widgets.HBox([widgets.Label('Outside temperature',
layout=widgets.Layout(width='150px')), outside_temp]),
])
def _build_window(self, node):
period = widgets.IntSlider(value=node.active.period, min=0, max=240)
interval = widgets.IntSlider(value=node.active.duration, min=0, max=240)
inside_temp = widgets.IntSlider(value=node.inside_temp.values[0]-273.15, min=15., max=25.)
def on_period_change(change):
node.active.period = change['new']
@ -168,20 +182,54 @@ class WidgetView:
def on_interval_change(change):
node.active.duration = change['new']
def insidetemp_change(change):
node.inside_temp.values = (change['new']+273.15,)
# TODO: Link the state back to the widget, not just the other way around.
period.observe(on_period_change, names=['value'])
interval.observe(on_interval_change, names=['value'])
inside_temp.observe(insidetemp_change, names=['value'])
return widget_group(
outsidetemp_widgets = {
'Fixed': self._build_outsidetemp(node.outside_temp),
'Daily variation': self._build_month(node),
}
for name, widget in outsidetemp_widgets.items():
widget.layout.visible = False
outsidetemp_w = widgets.ToggleButtons(
options=outsidetemp_widgets.keys(),
)
def toggle_outsidetemp(value):
for name, widget in outsidetemp_widgets.items():
widget.layout.display = 'none'
#node.dcs_select(value)
widget = outsidetemp_widgets[value]
widget.layout.visible = True
widget.layout.display = 'block'
outsidetemp_w.observe(lambda event: toggle_outsidetemp(event['new']), 'value')
toggle_outsidetemp(outsidetemp_w.value)
return widgets.VBox(
[
[widgets.Label('Open every n minutes'), period],
[widgets.Label('For how long'), interval],
]
widgets.HBox([widgets.Label('Open every n minutes',
layout=widgets.Layout(width='150px')), period]),
widgets.HBox([widgets.Label('For how long',
layout=widgets.Layout(width='150px')), interval]),
widgets.HBox([widgets.Label('Inside temperature',
layout=widgets.Layout(width='150px')), inside_temp]),
widget_group([[widgets.Label('Outside temp.'), outsidetemp_w]])
] + list(outsidetemp_widgets.values())
)
def _build_hepa(self, node):
def _build_mechanical(self, node):
period = widgets.IntSlider(value=node.active.period, min=0, max=240, step=5)
interval = widgets.IntSlider(value=node.active.duration, min=0, max=240, step=5)
q_air_mech = widgets.IntSlider(value=node.q_air_mech, min=0, max=1000, step=5)
def on_period_change(change):
node.active.period = change['new']
@ -189,17 +237,37 @@ class WidgetView:
def on_interval_change(change):
node.active.duration = change['new']
def q_air_mech_change(change):
node.q_air_mech = change['new']
# TODO: Link the state back to the widget, not just the other way around.
period.observe(on_period_change, names=['value'])
interval.observe(on_interval_change, names=['value'])
q_air_mech.observe(q_air_mech_change, names=['value'])
return widget_group(
return widgets.VBox(
[
[widgets.Label('On every n minutes'), period],
[widgets.Label('For how long'), interval],
widgets.HBox([widgets.Label('On every n minutes',
layout=widgets.Layout(width='150px')), period]),
widgets.HBox([widgets.Label('For how long',
layout=widgets.Layout(width='150px')), interval]),
widgets.HBox([widgets.Label('Flow rate (m^3/h)',
layout=widgets.Layout(width='150px')), q_air_mech]),
]
)
def _build_month(self, node):
month_choice = widgets.Select(options=list(data.GenevaTemperatures.keys()), value='Jan')
def on_month_change(change):
node.outside_temp = data.GenevaTemperatures[change['new']]
month_choice.observe(on_month_change, names=['value'])
return widget_group(
[[widgets.Label("Month"), month_choice]]
)
def _build_activity(self, node):
activity = node.dcs_instance()
for name, activity_ in models.Activity.types.items():
@ -250,7 +318,7 @@ class WidgetView:
def _build_ventilation(self, node):
ventilation_widgets = {
'Natural': self._build_window(node._states['Natural']),
'HEPA': self._build_hepa(node._states['HEPA']),
'Mechanical': self._build_mechanical(node._states['Mechanical']),
}
for name, widget in ventilation_widgets.items():
widget.layout.visible = False
@ -288,14 +356,14 @@ baseline_model = models.ExposureModel(
room=models.Room(volume=75),
ventilation=models.WindowOpening(
active=models.PeriodicInterval(period=120, duration=120),
inside_temp=models.PiecewiseConstant((0,24),(293,)),
outside_temp=models.PiecewiseConstant((0,24),(283,)),
inside_temp=models.PiecewiseConstant((0,24),(293.15,)),
outside_temp=models.PiecewiseConstant((0,24),(283.15,)),
cd_b=0.6, window_height=1.6, opening_length=0.6,
),
infected=models.InfectedPopulation(
number=1,
virus=models.Virus.types['SARS_CoV_2'],
presence=models.SpecificInterval(((0, 4), (5, 8))),
presence=models.SpecificInterval(((8, 12), (13, 17))),
mask=models.Mask.types['No mask'],
activity=models.Activity.types['Light exercise'],
expiration=models.Expiration.types['Unmodulated Vocalization'],
@ -303,7 +371,7 @@ baseline_model = models.ExposureModel(
),
exposed=models.Population(
number=10,
presence=models.SpecificInterval(((0, 4), (5, 8))),
presence=models.SpecificInterval(((8, 12), (13, 17))),
activity=models.Activity.types['Light exercise'],
mask=models.Mask.types['No mask'],
),
@ -321,13 +389,13 @@ class CARAStateBuilder(state.StateBuilder):
s = state.DataclassStateNamed(
states={
'Natural': self.build_generic(models.WindowOpening),
'HEPA': self.build_generic(models.HEPAFilter),
'Mechanical': self.build_generic(models.HVACMechanical),
},
state_builder=self,
)
# Initialise the HEPA state
s._states['HEPA'].dcs_update_from(
models.HEPAFilter(models.PeriodicInterval(120, 120), 500.)
# Initialise the HVAC state
s._states['Mechanical'].dcs_update_from(
models.HVACMechanical(models.PeriodicInterval(120, 120), 500.)
)
return s