Compare commits
8 commits
master
...
feature/ex
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5103df874e | ||
|
|
6e292908ea | ||
|
|
214ee14c42 | ||
|
|
440e022152 | ||
|
|
dabed4fde0 | ||
|
|
1bd2d37fa4 | ||
|
|
5d6f248e67 | ||
|
|
e4cc8bf023 |
3 changed files with 180 additions and 71 deletions
|
|
@ -4,6 +4,7 @@ import uuid
|
||||||
|
|
||||||
import ipympl.backend_nbagg
|
import ipympl.backend_nbagg
|
||||||
import ipywidgets as widgets
|
import ipywidgets as widgets
|
||||||
|
from ipywidgets.widgets import widget_templates
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import matplotlib
|
import matplotlib
|
||||||
import matplotlib.figure
|
import matplotlib.figure
|
||||||
|
|
@ -108,7 +109,7 @@ def ipympl_canvas(figure):
|
||||||
|
|
||||||
class ExposureModelResult(View):
|
class ExposureModelResult(View):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.figure = matplotlib.figure.Figure(figsize=(9, 6))
|
self.figure = matplotlib.figure.Figure(figsize=(6, 5))
|
||||||
ipympl_canvas(self.figure)
|
ipympl_canvas(self.figure)
|
||||||
self.html_output = widgets.HTML()
|
self.html_output = widgets.HTML()
|
||||||
self.ax = self.figure.add_subplot(1, 1, 1)
|
self.ax = self.figure.add_subplot(1, 1, 1)
|
||||||
|
|
@ -154,23 +155,23 @@ class ExposureModelResult(View):
|
||||||
def update_textual_result(self, model: models.ExposureModel):
|
def update_textual_result(self, model: models.ExposureModel):
|
||||||
lines = []
|
lines = []
|
||||||
P = np.array(model.infection_probability()).mean()
|
P = np.array(model.infection_probability()).mean()
|
||||||
lines.append(f'Emission rate (virus/hr): {np.round(model.concentration_model.infected.emission_rate_when_present(),0)}')
|
# lines.append(f'Emission rate (virus/hr): {np.round(model.concentration_model.infected.emission_rate_when_present(),0)}')
|
||||||
lines.append(f'Probability of infection: {np.round(P, 0)}%')
|
lines.append(f'<b>Probability of infection: </b>{np.round(P, 0)}%')
|
||||||
|
|
||||||
lines.append(f'Number of exposed: {model.exposed.number}')
|
lines.append(f'<b>Number of exposed: </b>{model.exposed.number}')
|
||||||
|
|
||||||
new_cases = np.round(np.array(model.expected_new_cases()).mean(), 1)
|
new_cases = np.round(np.array(model.expected_new_cases()).mean(), 1)
|
||||||
lines.append(f'Number of expected new cases: {new_cases}')
|
lines.append(f'<b>Number of expected new cases: </b>{new_cases}')
|
||||||
|
|
||||||
R0 = np.round(np.array(model.reproduction_number()).mean(), 1)
|
R0 = np.round(np.array(model.reproduction_number()).mean(), 1)
|
||||||
lines.append(f'Reproduction number (R0): {R0}')
|
lines.append(f'<b>Reproduction number (R0): </b>{R0}')
|
||||||
|
|
||||||
self.html_output.value = '<br>\n'.join(lines)
|
self.html_output.value = '<br>\n'.join(lines)
|
||||||
|
|
||||||
|
|
||||||
class ExposureComparissonResult(View):
|
class ExposureComparissonResult(View):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.figure = matplotlib.figure.Figure(figsize=(9, 6))
|
self.figure = matplotlib.figure.Figure(figsize=(6, 5))
|
||||||
ipympl_canvas(self.figure)
|
ipympl_canvas(self.figure)
|
||||||
self.ax = self.initialize_axes()
|
self.ax = self.initialize_axes()
|
||||||
|
|
||||||
|
|
@ -215,6 +216,7 @@ class ModelWidgets(View):
|
||||||
def __init__(self, model_state: state.DataclassState):
|
def __init__(self, model_state: state.DataclassState):
|
||||||
#: The widgets that this view produces (inputs and outputs together)
|
#: The widgets that this view produces (inputs and outputs together)
|
||||||
self.widget = widgets.VBox([])
|
self.widget = widgets.VBox([])
|
||||||
|
self.min_layout = widgets.Layout(min_width='228px')
|
||||||
self.construct_widgets(model_state)
|
self.construct_widgets(model_state)
|
||||||
|
|
||||||
def construct_widgets(self, model_state: state.DataclassState):
|
def construct_widgets(self, model_state: state.DataclassState):
|
||||||
|
|
@ -229,13 +231,13 @@ class ModelWidgets(View):
|
||||||
self.widget.children += (self._build_infectivity(node.concentration_model.infected),)
|
self.widget.children += (self._build_infectivity(node.concentration_model.infected),)
|
||||||
|
|
||||||
def _build_exposed(self, node):
|
def _build_exposed(self, node):
|
||||||
return collapsible([widgets.HBox([
|
return collapsible([widgets.VBox([
|
||||||
self._build_mask(node.exposed.mask),
|
self._build_mask(node.exposed.mask),
|
||||||
self._build_activity(node.exposed.activity),
|
self._build_activity(node.exposed.activity),
|
||||||
])], title="Exposed")
|
])], title="Exposed")
|
||||||
|
|
||||||
def _build_infected(self, node):
|
def _build_infected(self, node):
|
||||||
return collapsible([widgets.HBox([
|
return collapsible([widgets.VBox([
|
||||||
self._build_mask(node.mask),
|
self._build_mask(node.mask),
|
||||||
self._build_activity(node.activity),
|
self._build_activity(node.activity),
|
||||||
self._build_expiration(node.expiration),
|
self._build_expiration(node.expiration),
|
||||||
|
|
@ -254,9 +256,7 @@ class ModelWidgets(View):
|
||||||
node.dcs_observe(on_state_change)
|
node.dcs_observe(on_state_change)
|
||||||
|
|
||||||
widget = collapsible(
|
widget = collapsible(
|
||||||
[widget_group(
|
[widget_group([[widgets.Label('Room volume (m³):', layout=self.min_layout), room_volume]])],
|
||||||
[[widgets.Label('Room volume (m³)'), room_volume]]
|
|
||||||
)],
|
|
||||||
title='Specification of workplace',
|
title='Specification of workplace',
|
||||||
)
|
)
|
||||||
return widget
|
return widget
|
||||||
|
|
@ -278,11 +278,57 @@ class ModelWidgets(View):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _build_window_width(self, node):
|
||||||
|
window_width = widgets.FloatSlider(value=node.window_width, min=0, max=2, step=0.1)
|
||||||
|
|
||||||
|
def window_width_change(change):
|
||||||
|
node.window_width = change['new']
|
||||||
|
|
||||||
|
window_width.observe(window_width_change, names=['value'])
|
||||||
|
|
||||||
|
return widgets.HBox([widgets.Label('Width of window (m)', layout=widgets.Layout(min_width='228px')), window_width])
|
||||||
|
|
||||||
def _build_window(self, node) -> WidgetGroup:
|
def _build_window(self, node) -> WidgetGroup:
|
||||||
|
window_type_widgets = {
|
||||||
|
'SlidingWindow': widgets.Box([]), # No widgets for SlidingWindow default value
|
||||||
|
'HingedWindow': self._build_window_width(node._states['HingedWindow'])
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, widget in window_type_widgets.items():
|
||||||
|
widget.layout.visible = False
|
||||||
|
|
||||||
|
window_type_w = widgets.ToggleButtons(
|
||||||
|
options=[('Sliding/Side-Hung', 'SlidingWindow'), ('Top-or Bottom-Hung', 'HingedWindow')],
|
||||||
|
button_style='info',
|
||||||
|
style={'button_width':'120px'},
|
||||||
|
)
|
||||||
|
|
||||||
|
def toggle_window_type(value):
|
||||||
|
for name, widget in window_type_widgets.items():
|
||||||
|
widget.layout.visible = False
|
||||||
|
widget.layout.display = 'none'
|
||||||
|
|
||||||
|
node.dcs_select(value)
|
||||||
|
|
||||||
|
widget = window_type_widgets[value]
|
||||||
|
widget.layout.visible = True
|
||||||
|
widget.layout.display = 'flex'
|
||||||
|
|
||||||
|
window_type_w.observe(lambda event: toggle_window_type(event['new']), 'value')
|
||||||
|
toggle_window_type(window_type_w.value)
|
||||||
|
|
||||||
|
window_height = widgets.FloatSlider(value=node.window_height, min=0, max=2)
|
||||||
|
opening_distance = widgets.FloatSlider(value=node.opening_length, min=0, max=2)
|
||||||
period = widgets.IntSlider(value=node.active.period, min=0, max=240)
|
period = widgets.IntSlider(value=node.active.period, min=0, max=240)
|
||||||
interval = widgets.IntSlider(value=node.active.duration, 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.)
|
inside_temp = widgets.IntSlider(value=node.inside_temp.values[0]-273.15, min=15., max=25.)
|
||||||
|
|
||||||
|
def on_window_height_change(change):
|
||||||
|
node.window_height = change['new']
|
||||||
|
|
||||||
|
def on_opening_distance_change(change):
|
||||||
|
node.opening_length = change['new']
|
||||||
|
|
||||||
def on_period_change(change):
|
def on_period_change(change):
|
||||||
node.active.period = change['new']
|
node.active.period = change['new']
|
||||||
|
|
||||||
|
|
@ -293,6 +339,8 @@ class ModelWidgets(View):
|
||||||
node.inside_temp.values = (change['new']+273.15,)
|
node.inside_temp.values = (change['new']+273.15,)
|
||||||
|
|
||||||
# TODO: Link the state back to the widget, not just the other way around.
|
# TODO: Link the state back to the widget, not just the other way around.
|
||||||
|
window_height.observe(on_window_height_change, names=['value'])
|
||||||
|
opening_distance.observe(on_opening_distance_change, names=['value'])
|
||||||
period.observe(on_period_change, names=['value'])
|
period.observe(on_period_change, names=['value'])
|
||||||
interval.observe(on_interval_change, names=['value'])
|
interval.observe(on_interval_change, names=['value'])
|
||||||
inside_temp.observe(insidetemp_change, names=['value'])
|
inside_temp.observe(insidetemp_change, names=['value'])
|
||||||
|
|
@ -304,6 +352,9 @@ class ModelWidgets(View):
|
||||||
|
|
||||||
outsidetemp_w = widgets.ToggleButtons(
|
outsidetemp_w = widgets.ToggleButtons(
|
||||||
options=outsidetemp_widgets.keys(),
|
options=outsidetemp_widgets.keys(),
|
||||||
|
button_style='info',
|
||||||
|
style={'button_width':'120px'}
|
||||||
|
# tooltips=[],
|
||||||
)
|
)
|
||||||
|
|
||||||
def toggle_outsidetemp(value):
|
def toggle_outsidetemp(value):
|
||||||
|
|
@ -320,28 +371,36 @@ class ModelWidgets(View):
|
||||||
result = WidgetGroup(
|
result = WidgetGroup(
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
widgets.Label('Interval between openings (minutes)', layout=auto_width),
|
widgets.Label('Height of window (m):'),
|
||||||
|
window_height,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
widgets.Label('Opening distance (m):'),
|
||||||
|
opening_distance,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
widgets.Label('Interval between openings (minutes):'),
|
||||||
period,
|
period,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
widgets.Label('Duration of opening (minutes)', layout=auto_width),
|
widgets.Label('Duration of opening (minutes):', layout=auto_width),
|
||||||
interval,
|
interval,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
widgets.Label('Inside temperature (℃)', layout=auto_width),
|
widgets.Label('Inside temperature (℃):', layout=auto_width),
|
||||||
inside_temp,
|
inside_temp,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
widgets.Label('Outside temperature scheme', layout=auto_width),
|
widgets.Label('Outside temperature scheme:', layout=auto_width),
|
||||||
outsidetemp_w,
|
outsidetemp_w,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
for sub_group in outsidetemp_widgets.values():
|
for sub_group in outsidetemp_widgets.values():
|
||||||
result.add_pairs(sub_group.pairs())
|
result.add_pairs(sub_group.pairs())
|
||||||
return result
|
return widgets.VBox([widgets.VBox([widgets.HBox([widgets.Label('Window type:', layout=widgets.Layout(min_width='228px')), window_type_w]), widgets.HBox(list(window_type_widgets.values()))]), result.build()])
|
||||||
|
|
||||||
def _build_mechanical(self, node):
|
def _build_q_air_mech(self, node):
|
||||||
q_air_mech = widgets.IntSlider(value=node.q_air_mech, min=0, max=1000, step=5)
|
q_air_mech = widgets.IntSlider(value=node.q_air_mech, min=0, max=1000, step=5)
|
||||||
|
|
||||||
def q_air_mech_change(change):
|
def q_air_mech_change(change):
|
||||||
|
|
@ -350,17 +409,52 @@ class ModelWidgets(View):
|
||||||
# TODO: Link the state back to the widget, not just the other way around.
|
# TODO: Link the state back to the widget, not just the other way around.
|
||||||
q_air_mech.observe(q_air_mech_change, names=['value'])
|
q_air_mech.observe(q_air_mech_change, names=['value'])
|
||||||
|
|
||||||
auto_width = widgets.Layout(width='auto')
|
return widgets.HBox([q_air_mech, widgets.Label('m³/h')])
|
||||||
return widgets.VBox([widget_group([
|
|
||||||
[
|
def _build_ach(self, node):
|
||||||
widgets.Label('Flow rate (m³/h)', layout=auto_width),
|
air_exch = widgets.IntSlider(value=node.air_exch, min=0, max=50, step=5)
|
||||||
q_air_mech,
|
|
||||||
],
|
def air_exch_change(change):
|
||||||
])])
|
node.air_exch = change['new']
|
||||||
|
|
||||||
|
# TODO: Link the state back to the widget, not just the other way around.
|
||||||
|
air_exch.observe(air_exch_change, names=['value'])
|
||||||
|
|
||||||
|
return widgets.HBox([air_exch, widgets.Label('h⁻¹')])
|
||||||
|
|
||||||
|
def _build_mechanical(self, node):
|
||||||
|
mechanical_widgets = {
|
||||||
|
'HVACMechanical': self._build_q_air_mech(node._states['HVACMechanical']),
|
||||||
|
'AirChange': self._build_ach(node._states['AirChange']),
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, widget in mechanical_widgets.items():
|
||||||
|
widget.layout.visible = False
|
||||||
|
|
||||||
|
mechanival_w = widgets.RadioButtons(
|
||||||
|
options=list(zip(['Air supply flow rate (m³/h)', 'Air changes per hour (h⁻¹)'], mechanical_widgets.keys())),
|
||||||
|
# button_style='info',
|
||||||
|
)
|
||||||
|
|
||||||
|
def toggle_mechanical(value):
|
||||||
|
for name, widget in mechanical_widgets.items():
|
||||||
|
widget.layout.visible = False
|
||||||
|
widget.layout.display = 'none'
|
||||||
|
|
||||||
|
node.dcs_select(value)
|
||||||
|
|
||||||
|
widget = mechanical_widgets[value]
|
||||||
|
widget.layout.visible = True
|
||||||
|
widget.layout.display = 'flex'
|
||||||
|
|
||||||
|
mechanival_w.observe(lambda event: toggle_mechanical(event['new']), 'value')
|
||||||
|
toggle_mechanical(mechanival_w.value)
|
||||||
|
|
||||||
|
return widgets.VBox([mechanival_w, widgets.HBox(list(mechanical_widgets.values()))])
|
||||||
|
|
||||||
def _build_month(self, node) -> WidgetGroup:
|
def _build_month(self, node) -> WidgetGroup:
|
||||||
|
|
||||||
month_choice = widgets.Select(options=list(data.GenevaTemperatures.keys()), value='Jan')
|
month_choice = widgets.Dropdown(options=list(data.GenevaTemperatures.keys()), value='Jan')
|
||||||
|
|
||||||
def on_month_change(change):
|
def on_month_change(change):
|
||||||
node.outside_temp = data.GenevaTemperatures[change['new']]
|
node.outside_temp = data.GenevaTemperatures[change['new']]
|
||||||
|
|
@ -368,7 +462,7 @@ class ModelWidgets(View):
|
||||||
|
|
||||||
return WidgetGroup(
|
return WidgetGroup(
|
||||||
(
|
(
|
||||||
(widgets.Label("Month"), month_choice),
|
(widgets.Label("Month:"), month_choice),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -377,7 +471,7 @@ class ModelWidgets(View):
|
||||||
for name, activity_ in models.Activity.types.items():
|
for name, activity_ in models.Activity.types.items():
|
||||||
if activity == activity_:
|
if activity == activity_:
|
||||||
break
|
break
|
||||||
activity = widgets.Select(options=list(models.Activity.types.keys()), value=name)
|
activity = widgets.Dropdown(options=list(models.Activity.types.keys()), value=name)
|
||||||
|
|
||||||
def on_activity_change(change):
|
def on_activity_change(change):
|
||||||
act = models.Activity.types[change['new']]
|
act = models.Activity.types[change['new']]
|
||||||
|
|
@ -385,7 +479,7 @@ class ModelWidgets(View):
|
||||||
activity.observe(on_activity_change, names=['value'])
|
activity.observe(on_activity_change, names=['value'])
|
||||||
|
|
||||||
return widget_group(
|
return widget_group(
|
||||||
[[widgets.Label("Activity"), activity]]
|
[[widgets.Label("Activity:", layout=self.min_layout), activity]]
|
||||||
)
|
)
|
||||||
|
|
||||||
def _build_mask(self, node):
|
def _build_mask(self, node):
|
||||||
|
|
@ -393,14 +487,14 @@ class ModelWidgets(View):
|
||||||
for name, mask_ in models.Mask.types.items():
|
for name, mask_ in models.Mask.types.items():
|
||||||
if mask == mask_:
|
if mask == mask_:
|
||||||
break
|
break
|
||||||
mask_choice = widgets.Select(options=list(models.Mask.types.keys()), value=name)
|
mask_choice = widgets.Dropdown(options=list(models.Mask.types.keys()), value=name)
|
||||||
|
|
||||||
def on_mask_change(change):
|
def on_mask_change(change):
|
||||||
node.dcs_select(change['new'])
|
node.dcs_select(change['new'])
|
||||||
mask_choice.observe(on_mask_change, names=['value'])
|
mask_choice.observe(on_mask_change, names=['value'])
|
||||||
|
|
||||||
return widget_group(
|
return widget_group(
|
||||||
[[widgets.Label("Mask"), mask_choice]]
|
[[widgets.Label("Mask:", layout=self.min_layout), mask_choice]]
|
||||||
)
|
)
|
||||||
|
|
||||||
def _build_expiration(self, node):
|
def _build_expiration(self, node):
|
||||||
|
|
@ -408,7 +502,7 @@ class ModelWidgets(View):
|
||||||
for name, expiration_ in models.Expiration.types.items():
|
for name, expiration_ in models.Expiration.types.items():
|
||||||
if expiration == expiration_:
|
if expiration == expiration_:
|
||||||
break
|
break
|
||||||
expiration_choice = widgets.Select(options=list(models.Expiration.types.keys()), value=name)
|
expiration_choice = widgets.Dropdown(options=list(models.Expiration.types.keys()), value=name)
|
||||||
|
|
||||||
def on_expiration_change(change):
|
def on_expiration_change(change):
|
||||||
expiration = models.Expiration.types[change['new']]
|
expiration = models.Expiration.types[change['new']]
|
||||||
|
|
@ -416,7 +510,7 @@ class ModelWidgets(View):
|
||||||
expiration_choice.observe(on_expiration_change, names=['value'])
|
expiration_choice.observe(on_expiration_change, names=['value'])
|
||||||
|
|
||||||
return widget_group(
|
return widget_group(
|
||||||
[[widgets.Label("Expiration"), expiration_choice]]
|
[[widgets.Label("Expiration:", layout=self.min_layout), expiration_choice]]
|
||||||
)
|
)
|
||||||
|
|
||||||
def _build_ventilation(
|
def _build_ventilation(
|
||||||
|
|
@ -427,14 +521,17 @@ class ModelWidgets(View):
|
||||||
],
|
],
|
||||||
) -> widgets.Widget:
|
) -> widgets.Widget:
|
||||||
ventilation_widgets = {
|
ventilation_widgets = {
|
||||||
'Natural': self._build_window(node._states['Natural']).build(),
|
'SlidingWindow': self._build_window(node),
|
||||||
'Mechanical': self._build_mechanical(node._states['Mechanical']),
|
'HVACMechanical': self._build_mechanical(node),
|
||||||
}
|
}
|
||||||
for name, widget in ventilation_widgets.items():
|
for name, widget in ventilation_widgets.items():
|
||||||
widget.layout.visible = False
|
widget.layout.visible = False
|
||||||
|
|
||||||
ventilation_w = widgets.ToggleButtons(
|
ventilation_w = widgets.Dropdown(
|
||||||
options=ventilation_widgets.keys(),
|
options=[('Natural', 'SlidingWindow'), ('Mechanical', 'HVACMechanical'), ('No ventilation', 'No ventilation')],
|
||||||
|
button_style='info',
|
||||||
|
style={'button_width':'100px'}
|
||||||
|
# tooltips=[],
|
||||||
)
|
)
|
||||||
|
|
||||||
def toggle_ventilation(value):
|
def toggle_ventilation(value):
|
||||||
|
|
@ -452,30 +549,28 @@ class ModelWidgets(View):
|
||||||
toggle_ventilation(ventilation_w.value)
|
toggle_ventilation(ventilation_w.value)
|
||||||
|
|
||||||
w = collapsible(
|
w = collapsible(
|
||||||
[widget_group([[widgets.Label('Ventilation type'), ventilation_w]])]
|
[widget_group([[widgets.Label('Ventilation type: ', layout=widgets.Layout(min_width='228px')), ventilation_w]])]
|
||||||
+ list(ventilation_widgets.values()),
|
+ list(ventilation_widgets.values()),
|
||||||
title='Ventilation scheme',
|
title='Ventilation scheme',
|
||||||
)
|
)
|
||||||
return w
|
return w
|
||||||
|
|
||||||
def _build_infectivity(self,node):
|
def _build_infectivity(self,node):
|
||||||
return collapsible([widgets.HBox([
|
return collapsible([self._build_virus(node.virus)], title="Virus variant")
|
||||||
self._build_virus(node.virus),
|
|
||||||
])], title="Virus variant")
|
|
||||||
|
|
||||||
def _build_virus(self, node):
|
def _build_virus(self, node):
|
||||||
virus = node.dcs_instance()
|
virus = node.dcs_instance()
|
||||||
for name, virus_ in models.Virus.types.items():
|
for name, virus_ in models.Virus.types.items():
|
||||||
if virus == virus_:
|
if virus == virus_:
|
||||||
break
|
break
|
||||||
virus_choice = widgets.Select(options=list(models.Virus.types.keys()), value=name)
|
virus_choice = widgets.Dropdown(options=list(models.Virus.types.keys()), value=name)
|
||||||
|
|
||||||
def on_virus_change(change):
|
def on_virus_change(change):
|
||||||
node.dcs_select(change['new'])
|
node.dcs_select(change['new'])
|
||||||
virus_choice.observe(on_virus_change, names=['value'])
|
virus_choice.observe(on_virus_change, names=['value'])
|
||||||
|
|
||||||
return widget_group(
|
return widget_group(
|
||||||
[[widgets.Label("Virus"), virus_choice]]
|
[[widgets.Label("Virus:", layout=self.min_layout), virus_choice]]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -530,15 +625,33 @@ class CARAStateBuilder(state.StateBuilder):
|
||||||
def build_type__VentilationBase(self, _: dataclasses.Field):
|
def build_type__VentilationBase(self, _: dataclasses.Field):
|
||||||
s: state.DataclassStateNamed = state.DataclassStateNamed(
|
s: state.DataclassStateNamed = state.DataclassStateNamed(
|
||||||
states={
|
states={
|
||||||
'Natural': self.build_generic(models.WindowOpening),
|
'SlidingWindow': self.build_generic(models.SlidingWindow),
|
||||||
'Mechanical': self.build_generic(models.HVACMechanical),
|
'HingedWindow': self.build_generic(models.HingedWindow),
|
||||||
|
'HVACMechanical': self.build_generic(models.HVACMechanical),
|
||||||
|
'AirChange': self.build_generic(models.AirChange),
|
||||||
|
'No ventilation': self.build_generic(models.AirChange),
|
||||||
},
|
},
|
||||||
state_builder=self,
|
state_builder=self,
|
||||||
)
|
)
|
||||||
|
# Initialise the HingedWindow state
|
||||||
|
s._states['HingedWindow'].dcs_update_from(
|
||||||
|
models.HingedWindow(active=models.PeriodicInterval(period=120, duration=15),
|
||||||
|
inside_temp=models.PiecewiseConstant((0., 24.), (293.15,)),
|
||||||
|
outside_temp=models.PiecewiseConstant((0., 24.), (283.15,)),
|
||||||
|
window_height=1.6, opening_length=0.6, window_width=.5)
|
||||||
|
)
|
||||||
# Initialise the HVAC state
|
# Initialise the HVAC state
|
||||||
s._states['Mechanical'].dcs_update_from(
|
s._states['HVACMechanical'].dcs_update_from(
|
||||||
models.HVACMechanical(models.PeriodicInterval(period=24*60, duration=24*60), 500.)
|
models.HVACMechanical(models.PeriodicInterval(period=24*60, duration=24*60), 500.)
|
||||||
)
|
)
|
||||||
|
# Initialise the AirChange state
|
||||||
|
s._states['AirChange'].dcs_update_from(
|
||||||
|
models.AirChange(models.PeriodicInterval(period=24*60, duration=24*60), 10.)
|
||||||
|
)
|
||||||
|
# Initialise the No ventilation state
|
||||||
|
s._states['No ventilation'].dcs_update_from(
|
||||||
|
models.AirChange(active=models.PeriodicInterval(period=120, duration=120), air_exch=0.)
|
||||||
|
)
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -560,13 +673,13 @@ class ExpertApplication(Controller):
|
||||||
))
|
))
|
||||||
for i, title in enumerate(['Current scenario', 'Scenario comparison', "Debug"]):
|
for i, title in enumerate(['Current scenario', 'Scenario comparison', "Debug"]):
|
||||||
self._results_tab.set_title(i, title)
|
self._results_tab.set_title(i, title)
|
||||||
self.widget = widgets.HBox(
|
|
||||||
children=(
|
|
||||||
self.multi_model_view.widget,
|
|
||||||
self._results_tab,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
self.add_scenario('Scenario 1')
|
self.add_scenario('Scenario 1')
|
||||||
|
self.widget = widgets.AppLayout(header=None,
|
||||||
|
left_sidebar=self.multi_model_view.widget,
|
||||||
|
center=None,
|
||||||
|
right_sidebar=self._results_tab,
|
||||||
|
footer=None,
|
||||||
|
pane_widths=["610px", 0, 1])
|
||||||
|
|
||||||
def build_new_model(self) -> state.DataclassInstanceState[models.ExposureModel]:
|
def build_new_model(self) -> state.DataclassInstanceState[models.ExposureModel]:
|
||||||
default_model = state.DataclassInstanceState(
|
default_model = state.DataclassInstanceState(
|
||||||
|
|
@ -604,7 +717,7 @@ class ExpertApplication(Controller):
|
||||||
index, _, model = self._find_model_id(model_id)
|
index, _, model = self._find_model_id(model_id)
|
||||||
self._model_scenarios.pop(index)
|
self._model_scenarios.pop(index)
|
||||||
if self._active_scenario >= index:
|
if self._active_scenario >= index:
|
||||||
self._active_scenario = max(self._active_scenario - 1, 0)
|
self._active_scenario = 0
|
||||||
self.notify_scenarios_changed()
|
self.notify_scenarios_changed()
|
||||||
|
|
||||||
def set_active_scenario(self, model_id):
|
def set_active_scenario(self, model_id):
|
||||||
|
|
@ -686,8 +799,8 @@ class MultiModelView(View):
|
||||||
self._tab_model_ids.pop(tab_index)
|
self._tab_model_ids.pop(tab_index)
|
||||||
self._tab_widgets.pop(tab_index)
|
self._tab_widgets.pop(tab_index)
|
||||||
self._tab_model_views.pop(tab_index)
|
self._tab_model_views.pop(tab_index)
|
||||||
if self._active_tab_index >= tab_index:
|
# if self._active_tab_index >= tab_index:
|
||||||
self._active_tab_index = max(0, self._active_tab_index - 1)
|
# self._active_tab_index = max(0, self._active_tab_index - 1)
|
||||||
self.update_tab_widget()
|
self.update_tab_widget()
|
||||||
|
|
||||||
def update_tab_widget(self):
|
def update_tab_widget(self):
|
||||||
|
|
@ -702,7 +815,7 @@ class MultiModelView(View):
|
||||||
delete_button = widgets.Button(description='Delete Scenario', button_style='danger')
|
delete_button = widgets.Button(description='Delete Scenario', button_style='danger')
|
||||||
rename_text_field = widgets.Text(description='Rename Scenario:', value=name,
|
rename_text_field = widgets.Text(description='Rename Scenario:', value=name,
|
||||||
style={'description_width': 'auto'})
|
style={'description_width': 'auto'})
|
||||||
duplicate_button = widgets.Button(description='Duplicate Scenario', button_style='success')
|
duplicate_button = widgets.Button(description='Duplicate Scenario', button_style='info')
|
||||||
model_id = id(model)
|
model_id = id(model)
|
||||||
|
|
||||||
def on_delete_click(b):
|
def on_delete_click(b):
|
||||||
|
|
@ -723,8 +836,7 @@ class MultiModelView(View):
|
||||||
# last scenario, so this should be controlled in the remove_tab method.
|
# last scenario, so this should be controlled in the remove_tab method.
|
||||||
buttons_w_delete = widgets.HBox(children=(duplicate_button, delete_button))
|
buttons_w_delete = widgets.HBox(children=(duplicate_button, delete_button))
|
||||||
buttons = duplicate_button if len(self._tab_model_ids) < 2 else buttons_w_delete
|
buttons = duplicate_button if len(self._tab_model_ids) < 2 else buttons_w_delete
|
||||||
return widgets.VBox(children=(buttons, rename_text_field))
|
return widgets.VBox(children=(rename_text_field, buttons))
|
||||||
|
|
||||||
|
|
||||||
def models_start_end(models: typing.Sequence[models.ConcentrationModel]) -> typing.Tuple[float, float]:
|
def models_start_end(models: typing.Sequence[models.ConcentrationModel]) -> typing.Tuple[float, float]:
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,15 @@
|
||||||
"cells": [
|
"cells": [
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {
|
||||||
|
"pycharm": {
|
||||||
|
"name": "#%%\n"
|
||||||
|
},
|
||||||
|
"scrolled": false
|
||||||
|
},
|
||||||
"source": [
|
"source": [
|
||||||
"<div style=\"text-align: center;\" align=\"center\">\n",
|
"<div style=\"text-align: center;\" align=\"center\">\n",
|
||||||
"<img src=\"../files/static/images/cara_logo_text_cern.png\" align=\"center\" style=\"text-align: center; height: 12em;\" /></div>\n",
|
"<img src=\"files/cara_full_text.png\" align=\"center\" style=\"text-align: center; height: 5em;\" /></div>\n",
|
||||||
"<p style=\"color: blue; font-weight: bold; font-size: x-large; text-align: center;\" align=\"center\">\n",
|
|
||||||
"Airborne Transmission of SARS-CoV-2\n",
|
|
||||||
"</p>\n",
|
|
||||||
"<p style=\"text-align: center;\">\n",
|
"<p style=\"text-align: center;\">\n",
|
||||||
"Please see the <a href=\"https://cara.web.cern.ch/\">CARA homepage</a> for details on the methodology, assumptions and limitations of CARA.</p>"
|
"Please see the <a href=\"https://cara.web.cern.ch/\">CARA homepage</a> for details on the methodology, assumptions and limitations of CARA.</p>"
|
||||||
]
|
]
|
||||||
|
|
@ -16,12 +18,7 @@
|
||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"execution_count": null,
|
"execution_count": null,
|
||||||
"metadata": {
|
"metadata": {},
|
||||||
"pycharm": {
|
|
||||||
"name": "#%%\n"
|
|
||||||
},
|
|
||||||
"scrolled": false
|
|
||||||
},
|
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"import cara.apps\n",
|
"import cara.apps\n",
|
||||||
|
|
@ -33,7 +30,7 @@
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"kernelspec": {
|
"kernelspec": {
|
||||||
"display_name": "Python 3",
|
"display_name": "Python 3 (ipykernel)",
|
||||||
"language": "python",
|
"language": "python",
|
||||||
"name": "python3"
|
"name": "python3"
|
||||||
},
|
},
|
||||||
|
|
@ -47,7 +44,7 @@
|
||||||
"name": "python",
|
"name": "python",
|
||||||
"nbconvert_exporter": "python",
|
"nbconvert_exporter": "python",
|
||||||
"pygments_lexer": "ipython3",
|
"pygments_lexer": "ipython3",
|
||||||
"version": "3.6.12"
|
"version": "3.9.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nbformat": 4,
|
"nbformat": 4,
|
||||||
|
|
|
||||||
BIN
cara/apps/expert/cara_full_text.png
Normal file
BIN
cara/apps/expert/cara_full_text.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 65 KiB |
Loading…
Reference in a new issue