Merge branch 'feature/concentration_cumulative_dose' into 'master'
Concentration profile plot with cumulative dose Closes #164 See merge request cara/cara!227
This commit is contained in:
commit
fdffee20b5
5 changed files with 201 additions and 98 deletions
|
|
@ -108,10 +108,15 @@ def calculate_report_data(model: models.ExposureModel):
|
||||||
er = np.array(model.concentration_model.infected.emission_rate_when_present()).mean()
|
er = np.array(model.concentration_model.infected.emission_rate_when_present()).mean()
|
||||||
exposed_occupants = model.exposed.number
|
exposed_occupants = model.exposed.number
|
||||||
expected_new_cases = np.array(model.expected_new_cases()).mean()
|
expected_new_cases = np.array(model.expected_new_cases()).mean()
|
||||||
|
cumulative_doses = np.cumsum([
|
||||||
|
np.array(model.exposure_between_bounds(float(time1), float(time2))).mean()
|
||||||
|
for time1, time2 in zip(times[:-1], times[1:])
|
||||||
|
])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"times": list(times),
|
"times": list(times),
|
||||||
"exposed_presence_intervals": [list(interval) for interval in model.exposed.presence.boundaries()],
|
"exposed_presence_intervals": [list(interval) for interval in model.exposed.presence.boundaries()],
|
||||||
|
"cumulative_doses": list(cumulative_doses),
|
||||||
"concentrations": concentrations,
|
"concentrations": concentrations,
|
||||||
"highest_const": highest_const,
|
"highest_const": highest_const,
|
||||||
"prob_inf": prob,
|
"prob_inf": prob,
|
||||||
|
|
@ -303,11 +308,11 @@ class ReportGenerator:
|
||||||
context['permalink'] = generate_permalink(base_url, self.calculator_prefix, form)
|
context['permalink'] = generate_permalink(base_url, self.calculator_prefix, form)
|
||||||
context['calculator_prefix'] = self.calculator_prefix
|
context['calculator_prefix'] = self.calculator_prefix
|
||||||
context['scale_warning'] = {
|
context['scale_warning'] = {
|
||||||
'level': 'yellow-2',
|
'level': 'yellow-2',
|
||||||
'incidence_rate': 'lower than 25 new cases per 100 000 inhabitants',
|
'incidence_rate': 'lower than 25 new cases per 100 000 inhabitants',
|
||||||
'onsite_access': 'of about 8000',
|
'onsite_access': 'of about 8000',
|
||||||
'threshold': ''
|
'threshold': ''
|
||||||
}
|
}
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def _template_environment(self) -> jinja2.Environment:
|
def _template_environment(self) -> jinja2.Environment:
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,40 @@
|
||||||
/* Generate the concentration plot using d3 library. */
|
/* Generate the concentration plot using d3 library. */
|
||||||
function draw_concentration_plot(svg_id, times, concentrations, exposed_presence_intervals) {
|
function draw_concentration_plot(svg_id, times, concentrations, cumulative_doses, exposed_presence_intervals) {
|
||||||
|
|
||||||
var visBoundingBox = d3.select(svg_id)
|
var visBoundingBox = d3.select(svg_id)
|
||||||
.node()
|
.node()
|
||||||
.getBoundingClientRect();
|
.getBoundingClientRect();
|
||||||
|
|
||||||
var time_format = d3.timeFormat('%H:%M');
|
var time_format = d3.timeFormat('%H:%M');
|
||||||
|
|
||||||
var data = []
|
var data_for_graphs = {
|
||||||
times.map((time, index) => data.push({ 'time': time, 'hour': new Date().setHours(Math.trunc(time), (time - Math.trunc(time)) * 60), 'concentration': concentrations[index] }))
|
'concentrations': [],
|
||||||
|
'cumulative_doses': [],
|
||||||
|
}
|
||||||
|
times.map((time, index) => data_for_graphs.concentrations.push({ 'time': time, 'hour': new Date().setHours(Math.trunc(time), (time - Math.trunc(time)) * 60), 'concentration': concentrations[index]}));
|
||||||
|
times.map((time, index) => data_for_graphs.cumulative_doses.push({ 'time': time, 'hour': new Date().setHours(Math.trunc(time), (time - Math.trunc(time)) * 60), 'concentration': cumulative_doses[index]}));
|
||||||
|
|
||||||
var vis = d3.select(svg_id),
|
var vis = d3.select(svg_id),
|
||||||
width = visBoundingBox.width - 300,
|
width = visBoundingBox.width - 400,
|
||||||
height = visBoundingBox.height,
|
height = visBoundingBox.height,
|
||||||
margins = { top: 30, right: 20, bottom: 50, left: 50 },
|
margins = { top: 30, right: 20, bottom: 50, left: 50 },
|
||||||
|
|
||||||
// H:M time format for x axis.
|
// H:M time format for x axis.
|
||||||
xRange = d3.scaleTime().range([margins.left, width - margins.right]).domain([data[0].hour, data[data.length - 1].hour]),
|
xRange = d3.scaleTime().range([margins.left, width - margins.right]).domain([data_for_graphs.concentrations[0].hour, data_for_graphs.concentrations[data_for_graphs.concentrations.length - 1].hour]),
|
||||||
xTimeRange = d3.scaleLinear().range([margins.left, width - margins.right]).domain([data[0].time, data[data.length - 1].time]),
|
xTimeRange = d3.scaleLinear().range([margins.left, width - margins.right]).domain([data_for_graphs.concentrations[0].time, data_for_graphs.concentrations[data_for_graphs.concentrations.length - 1].time]),
|
||||||
bisecHour = d3.bisector((d) => { return d.hour; }).left,
|
bisecHour = d3.bisector((d) => { return d.hour; }).left,
|
||||||
|
|
||||||
yRange = d3.scaleLinear().range([height - margins.bottom, margins.top]).domain([0., Math.max(...concentrations)]),
|
yRange = d3.scaleLinear().range([height - margins.bottom, margins.top]).domain([0., Math.max(...concentrations)]),
|
||||||
|
yCumulatedRange = d3.scaleLinear().range([height - margins.bottom, margins.top]).domain([0., Math.max(...cumulative_doses)*1.1]),
|
||||||
|
|
||||||
xAxis = d3.axisBottom(xRange).tickFormat(d => time_format(d)),
|
xAxis = d3.axisBottom(xRange).tickFormat(d => time_format(d)),
|
||||||
yAxis = d3.axisLeft(yRange);
|
yAxis = d3.axisLeft(yRange).ticks(4),
|
||||||
|
yCumulatedAxis = d3.axisRight(yCumulatedRange).ticks(4);
|
||||||
// Plot tittle.
|
|
||||||
plot_title(vis, width, margins.top, 'Mean concentration of virions');
|
|
||||||
|
|
||||||
// Line representing the mean concentration.
|
// Line representing the mean concentration.
|
||||||
plot_scenario_data(vis, data, xTimeRange, yRange, '#1f77b4');
|
plot_scenario_data(vis, data_for_graphs.concentrations, xTimeRange, yRange, '#1f77b4');
|
||||||
|
// Line representing the cumulative concentration.
|
||||||
|
plot_cumulative_data(vis, data_for_graphs.cumulative_doses, xTimeRange, yCumulatedRange, '#1f77b4');
|
||||||
|
|
||||||
// X axis.
|
// X axis.
|
||||||
plot_x_axis(vis, height, width, margins, xAxis, 'Time of day');
|
plot_x_axis(vis, height, width, margins, xAxis, 'Time of day');
|
||||||
|
|
@ -36,6 +42,24 @@ function draw_concentration_plot(svg_id, times, concentrations, exposed_presence
|
||||||
// Y axis
|
// Y axis
|
||||||
plot_y_axis(vis, height, width, margins, yAxis, 'Mean concentration (virions/m³)')
|
plot_y_axis(vis, height, width, margins, yAxis, 'Mean concentration (virions/m³)')
|
||||||
|
|
||||||
|
// Y cumulative concentration axis declaration.
|
||||||
|
vis.append('svg:g')
|
||||||
|
.attr('class', 'y axis')
|
||||||
|
.style('font-size', 14)
|
||||||
|
.style("stroke-dasharray", "5 5")
|
||||||
|
.attr('transform', 'translate(' + (width - margins.right) + ',0)')
|
||||||
|
.call(yCumulatedAxis);
|
||||||
|
|
||||||
|
// Y cumulated concentration axis label.
|
||||||
|
vis.append('svg:text')
|
||||||
|
.attr('class', 'y label')
|
||||||
|
.attr('fill', 'black')
|
||||||
|
.attr('transform', 'rotate(-90, 0,' + height + ')')
|
||||||
|
.attr('text-anchor', 'middle')
|
||||||
|
.attr('x', (height + margins.bottom) / 2)
|
||||||
|
.attr('y', 1.71 * width)
|
||||||
|
.text('Mean cumulative dose (virions)');
|
||||||
|
|
||||||
// Area representing the presence of exposed person(s).
|
// Area representing the presence of exposed person(s).
|
||||||
exposed_presence_intervals.forEach(b => {
|
exposed_presence_intervals.forEach(b => {
|
||||||
var curveFunc = d3.area()
|
var curveFunc = d3.area()
|
||||||
|
|
@ -44,7 +68,7 @@ function draw_concentration_plot(svg_id, times, concentrations, exposed_presence
|
||||||
.y1(d => yRange(d.concentration));
|
.y1(d => yRange(d.concentration));
|
||||||
|
|
||||||
vis.append('svg:path')
|
vis.append('svg:path')
|
||||||
.attr('d', curveFunc(data.filter(d => {
|
.attr('d', curveFunc(data_for_graphs.concentrations.filter(d => {
|
||||||
return d.time >= b[0] && d.time <= b[1]
|
return d.time >= b[0] && d.time <= b[1]
|
||||||
})))
|
})))
|
||||||
.attr('fill', '#1f77b4')
|
.attr('fill', '#1f77b4')
|
||||||
|
|
@ -54,39 +78,56 @@ function draw_concentration_plot(svg_id, times, concentrations, exposed_presence
|
||||||
// Legend for the plot elements - line and area.
|
// Legend for the plot elements - line and area.
|
||||||
var size = 20
|
var size = 20
|
||||||
vis.append('rect')
|
vis.append('rect')
|
||||||
.attr('x', width + size)
|
.attr('x', width + size + 50)
|
||||||
.attr('y', margins.top + size)
|
.attr('y', margins.top + size)
|
||||||
.attr('width', 20)
|
.attr('width', 20)
|
||||||
.attr('height', 3)
|
.attr('height', 3)
|
||||||
.style('fill', '#1f77b4');
|
.style('fill', '#1f77b4');
|
||||||
|
|
||||||
|
vis.append('line')
|
||||||
|
.attr("x1", width + size + 50)
|
||||||
|
.attr("x2", width + 2 * size + 52)
|
||||||
|
.attr("y1", 3.5 * size)
|
||||||
|
.attr("y2", 3.5 * size)
|
||||||
|
.style("stroke-dasharray", "5 5") //dashed array for line
|
||||||
|
.attr('stroke-width', '2')
|
||||||
|
.style("stroke", '#1f77b4');
|
||||||
|
|
||||||
vis.append('rect')
|
vis.append('rect')
|
||||||
.attr('x', width + size)
|
.attr('x', width + size + 50)
|
||||||
.attr('y', 3 * size)
|
.attr('y', 4 * size)
|
||||||
.attr('width', 20)
|
.attr('width', 20)
|
||||||
.attr('height', 20)
|
.attr('height', 20)
|
||||||
.attr('fill', '#1f77b4')
|
.attr('fill', '#1f77b4')
|
||||||
.attr('fill-opacity', '0.1');
|
.attr('fill-opacity', '0.1');
|
||||||
|
|
||||||
vis.append('text')
|
vis.append('text')
|
||||||
.attr('x', width + 3 * size)
|
.attr('x', width + 3 * size + 50)
|
||||||
.attr('y', margins.top + size)
|
.attr('y', margins.top + size)
|
||||||
.text('Mean concentration')
|
.text('Viral concentration')
|
||||||
.style('font-size', '15px')
|
.style('font-size', '15px')
|
||||||
.attr('alignment-baseline', 'central');
|
.attr('alignment-baseline', 'central');
|
||||||
|
|
||||||
vis.append('text')
|
vis.append('text')
|
||||||
.attr('x', width + 3 * size)
|
.attr('x', width + 3 * size + 50)
|
||||||
.attr('y', margins.top + 2 * size)
|
.attr('y', margins.top + 2 * size)
|
||||||
|
.text('Cumulative dose')
|
||||||
|
.style('font-size', '15px')
|
||||||
|
.attr('alignment-baseline', 'central');
|
||||||
|
|
||||||
|
vis.append('text')
|
||||||
|
.attr('x', width + 3 * size + 50)
|
||||||
|
.attr('y', margins.top + 3 * size)
|
||||||
.text('Presence of exposed person(s)')
|
.text('Presence of exposed person(s)')
|
||||||
.style('font-size', '15px')
|
.style('font-size', '15px')
|
||||||
.attr('alignment-baseline', 'central');
|
.attr('alignment-baseline', 'central');
|
||||||
|
|
||||||
|
|
||||||
// Legend bounding box.
|
// Legend bounding box.
|
||||||
vis.append('rect')
|
vis.append('rect')
|
||||||
.attr('width', 275)
|
.attr('width', 270)
|
||||||
.attr('height', 50)
|
.attr('height', 70)
|
||||||
.attr('x', width * 1.005)
|
.attr('x', width * 1.1)
|
||||||
.attr('y', margins.top + 5)
|
.attr('y', margins.top + 5)
|
||||||
.attr('stroke', 'lightgrey')
|
.attr('stroke', 'lightgrey')
|
||||||
.attr('stroke-width', '2')
|
.attr('stroke-width', '2')
|
||||||
|
|
@ -96,50 +137,80 @@ function draw_concentration_plot(svg_id, times, concentrations, exposed_presence
|
||||||
.attr('fill', 'none');
|
.attr('fill', 'none');
|
||||||
|
|
||||||
// Tooltip.
|
// Tooltip.
|
||||||
var focus = vis.append('svg:g')
|
var focus = {}, tooltip_rect = {}, tooltip_time = {}, tooltip_concentration = {}, toolBox = {};
|
||||||
.style('display', 'none');
|
for (const [concentration, data] of Object.entries(data_for_graphs)) {
|
||||||
|
|
||||||
focus.append('circle')
|
focus[concentration] = vis.append('svg:g')
|
||||||
.attr('r', 3);
|
.style('display', 'none');
|
||||||
|
|
||||||
focus.append('rect')
|
focus[concentration].append('circle')
|
||||||
.attr('fill', 'white')
|
.attr('r', 3);
|
||||||
.attr('stroke', '#000')
|
|
||||||
.attr('width', 80)
|
|
||||||
.attr('height', 50)
|
|
||||||
.attr('x', 10)
|
|
||||||
.attr('y', -22)
|
|
||||||
.attr('rx', 4)
|
|
||||||
.attr('ry', 4);
|
|
||||||
|
|
||||||
focus.append('text')
|
tooltip_rect[concentration] = focus[concentration].append('rect')
|
||||||
.attr('id', 'tooltip-time')
|
.attr('fill', 'white')
|
||||||
.attr('x', 18)
|
.attr('stroke', '#000')
|
||||||
.attr('y', -2);
|
.attr('width', 85)
|
||||||
|
.attr('height', 50)
|
||||||
|
.attr('x', 10)
|
||||||
|
.attr('y', -22)
|
||||||
|
.attr('rx', 4)
|
||||||
|
.attr('ry', 4);
|
||||||
|
|
||||||
focus.append('text')
|
tooltip_time[concentration] = focus[concentration].append('text')
|
||||||
.attr('id', 'tooltip-concentration')
|
.attr('id', 'tooltip-time')
|
||||||
.attr('x', 18)
|
.attr('x', 18)
|
||||||
.attr('y', 18);
|
.attr('y', -2);
|
||||||
|
|
||||||
vis.append('rect')
|
tooltip_concentration[concentration] = focus[concentration].append('text')
|
||||||
.attr('fill', 'none')
|
.attr('id', 'tooltip-concentration')
|
||||||
.attr('pointer-events', 'all')
|
.attr('x', 18)
|
||||||
.attr('width', width - margins.right)
|
.attr('y', 18);
|
||||||
.attr('height', height)
|
|
||||||
.on('mouseover', () => { focus.style('display', null); })
|
toolBox[concentration] = vis.append('rect')
|
||||||
.on('mouseout', () => { focus.style('display', 'none'); })
|
.attr('fill', 'none')
|
||||||
.on('mousemove', mousemove);
|
.attr('pointer-events', 'all')
|
||||||
|
.attr('width', width - margins.right)
|
||||||
|
.attr('height', height)
|
||||||
|
.on('mouseover', () => { for (const [concentration, data] of Object.entries(focus)) focus[concentration].style('display', null); })
|
||||||
|
.on('mouseout', () => { for (const [concentration, data] of Object.entries(focus)) focus[concentration].style('display', 'none'); })
|
||||||
|
.on('mousemove', mousemove);
|
||||||
|
}
|
||||||
|
|
||||||
function mousemove() {
|
function mousemove() {
|
||||||
|
for (const [scenario, data] of Object.entries(data_for_graphs)) {
|
||||||
|
if (d3.pointer(event)[0] < width / 2) {
|
||||||
|
tooltip_rect[scenario].attr('x', 10)
|
||||||
|
tooltip_time[scenario].attr('x', 18)
|
||||||
|
tooltip_concentration[scenario].attr('x', 18);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tooltip_rect[scenario].attr('x', -90)
|
||||||
|
tooltip_time[scenario].attr('x', -82)
|
||||||
|
tooltip_concentration[scenario].attr('x', -82)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Concentration line
|
||||||
var x0 = xRange.invert(d3.pointer(event, this)[0]),
|
var x0 = xRange.invert(d3.pointer(event, this)[0]),
|
||||||
i = bisecHour(data, x0, 1),
|
i = bisecHour(data_for_graphs.concentrations, x0, 1),
|
||||||
d0 = data[i - 1],
|
d0 = data_for_graphs.concentrations[i - 1],
|
||||||
d1 = data[i],
|
d1 = data_for_graphs.concentrations[i];
|
||||||
d = x0 - d0.hour > d1.hour - x0 ? d1 : d0;
|
if (d1) {
|
||||||
focus.attr('transform', 'translate(' + xRange(d.hour) + ',' + yRange(d.concentration) + ')');
|
var d = x0 - d0.hour > d1.hour - x0 ? d1 : d0;
|
||||||
focus.select('#tooltip-time').text('x = ' + time_format(d.hour));
|
focus.concentrations.attr('transform', 'translate(' + xRange(d.hour) + ',' + yRange(d.concentration) + ')');
|
||||||
focus.select('#tooltip-concentration').text('y = ' + d.concentration.toFixed(2));
|
focus.concentrations.select('#tooltip-time').text('x = ' + time_format(d.hour));
|
||||||
|
focus.concentrations.select('#tooltip-concentration').text('y = ' + d.concentration.toFixed(2));
|
||||||
|
}
|
||||||
|
// Cumulative line
|
||||||
|
var x0 = xRange.invert(d3.pointer(event, this)[0]),
|
||||||
|
i = bisecHour(data_for_graphs.cumulative_doses, x0, 1),
|
||||||
|
d0 = data_for_graphs.cumulative_doses[i - 1],
|
||||||
|
d1 = data_for_graphs.cumulative_doses[i];
|
||||||
|
if (d1 && d1.concentration) {
|
||||||
|
var d = x0 - d0.hour > d1.hour - x0 ? d1 : d0;
|
||||||
|
focus.cumulative_doses.attr('transform', 'translate(' + xRange(d.hour) + ',' + yCumulatedRange(d.concentration) + ')');
|
||||||
|
focus.cumulative_doses.select('#tooltip-time').text('x = ' + time_format(d.hour));
|
||||||
|
focus.cumulative_doses.select('#tooltip-concentration').text('y = ' + d.concentration.toFixed(2));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -155,7 +226,7 @@ function draw_alternative_scenarios_plot(svg_id, width, height, alternative_scen
|
||||||
// Variable for the highest concentration for all the scenarios
|
// Variable for the highest concentration for all the scenarios
|
||||||
var highest_concentration = 0.
|
var highest_concentration = 0.
|
||||||
|
|
||||||
var data_for_scenarios = {}
|
var data_for_graphs = {}
|
||||||
for (scenario in alternative_scenarios) {
|
for (scenario in alternative_scenarios) {
|
||||||
scenario_concentrations = alternative_scenarios[scenario].concentrations
|
scenario_concentrations = alternative_scenarios[scenario].concentrations
|
||||||
|
|
||||||
|
|
@ -165,11 +236,11 @@ function draw_alternative_scenarios_plot(svg_id, width, height, alternative_scen
|
||||||
times.map((time, index) => data.push({ 'time': time, 'hour': new Date().setHours(Math.trunc(time), (time - Math.trunc(time)) * 60), 'concentration': scenario_concentrations[index] }))
|
times.map((time, index) => data.push({ 'time': time, 'hour': new Date().setHours(Math.trunc(time), (time - Math.trunc(time)) * 60), 'concentration': scenario_concentrations[index] }))
|
||||||
|
|
||||||
// Add data into lines dictionary
|
// Add data into lines dictionary
|
||||||
data_for_scenarios[scenario] = data
|
data_for_graphs[scenario] = data
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need one scenario to get the time range
|
// We need one scenario to get the time range
|
||||||
var first_scenario = Object.values(data_for_scenarios)[0]
|
var first_scenario = Object.values(data_for_graphs)[0]
|
||||||
|
|
||||||
var vis = d3.select(svg_id),
|
var vis = d3.select(svg_id),
|
||||||
width = width,
|
width = width,
|
||||||
|
|
@ -185,12 +256,9 @@ function draw_alternative_scenarios_plot(svg_id, width, height, alternative_scen
|
||||||
xAxis = d3.axisBottom(xRange).tickFormat(d => time_format(d)),
|
xAxis = d3.axisBottom(xRange).tickFormat(d => time_format(d)),
|
||||||
yAxis = d3.axisLeft(yRange);
|
yAxis = d3.axisLeft(yRange);
|
||||||
|
|
||||||
// Plot title.
|
|
||||||
plot_title(vis, width, margins.top, 'Mean concentration of virions');
|
|
||||||
|
|
||||||
// Line representing the mean concentration for each scenario.
|
// Line representing the mean concentration for each scenario.
|
||||||
for (const [scenario_name, data] of Object.entries(data_for_scenarios)) {
|
for (const [scenario_name, data] of Object.entries(data_for_graphs)) {
|
||||||
var scenario_index = Object.keys(data_for_scenarios).indexOf(scenario_name)
|
var scenario_index = Object.keys(data_for_graphs).indexOf(scenario_name)
|
||||||
|
|
||||||
// Line representing the mean concentration.
|
// Line representing the mean concentration.
|
||||||
plot_scenario_data(vis, data, xTimeRange, yRange, colors[scenario_index])
|
plot_scenario_data(vis, data, xTimeRange, yRange, colors[scenario_index])
|
||||||
|
|
@ -235,7 +303,7 @@ function draw_alternative_scenarios_plot(svg_id, width, height, alternative_scen
|
||||||
// Legend bounding box.
|
// Legend bounding box.
|
||||||
vis.append('rect')
|
vis.append('rect')
|
||||||
.attr('width', 275)
|
.attr('width', 275)
|
||||||
.attr('height', 25 * (Object.keys(data_for_scenarios).length))
|
.attr('height', 25 * (Object.keys(data_for_graphs).length))
|
||||||
.attr('x', width * 1.005)
|
.attr('x', width * 1.005)
|
||||||
.attr('y', margins.top + 5)
|
.attr('y', margins.top + 5)
|
||||||
.attr('stroke', 'lightgrey')
|
.attr('stroke', 'lightgrey')
|
||||||
|
|
@ -249,22 +317,11 @@ function draw_alternative_scenarios_plot(svg_id, width, height, alternative_scen
|
||||||
|
|
||||||
// Functions used to build the plots' components
|
// Functions used to build the plots' components
|
||||||
|
|
||||||
function plot_title(vis, width, margin_top, title) {
|
|
||||||
vis.append('svg:foreignObject')
|
|
||||||
.attr('width', width)
|
|
||||||
.attr('height', margin_top)
|
|
||||||
.attr('fill', 'none')
|
|
||||||
.append('xhtml:div')
|
|
||||||
.style('text-align', 'center')
|
|
||||||
.html(title);
|
|
||||||
|
|
||||||
return vis;
|
|
||||||
}
|
|
||||||
|
|
||||||
function plot_x_axis(vis, height, width, margins, xAxis, label) {
|
function plot_x_axis(vis, height, width, margins, xAxis, label) {
|
||||||
// X axis declaration
|
// X axis declaration
|
||||||
vis.append('svg:g')
|
vis.append('svg:g')
|
||||||
.attr('class', 'x axis')
|
.attr('class', 'x axis')
|
||||||
|
.style('font-size', 14)
|
||||||
.attr('transform', 'translate(0,' + (height - margins.bottom) + ')')
|
.attr('transform', 'translate(0,' + (height - margins.bottom) + ')')
|
||||||
.call(xAxis);
|
.call(xAxis);
|
||||||
|
|
||||||
|
|
@ -284,6 +341,7 @@ function plot_y_axis(vis, height, width, margins, yAxis, label) {
|
||||||
// Y axis declaration.
|
// Y axis declaration.
|
||||||
vis.append('svg:g')
|
vis.append('svg:g')
|
||||||
.attr('class', 'y axis')
|
.attr('class', 'y axis')
|
||||||
|
.style('font-size', 14)
|
||||||
.attr('transform', 'translate(' + margins.left + ',0)')
|
.attr('transform', 'translate(' + margins.left + ',0)')
|
||||||
.call(yAxis);
|
.call(yAxis);
|
||||||
|
|
||||||
|
|
@ -314,5 +372,22 @@ function plot_scenario_data(vis, data, xTimeRange, yRange, line_color) {
|
||||||
.attr('stroke-width', 2)
|
.attr('stroke-width', 2)
|
||||||
.attr('fill', 'none');
|
.attr('fill', 'none');
|
||||||
|
|
||||||
|
return vis;
|
||||||
|
}
|
||||||
|
|
||||||
|
function plot_cumulative_data(vis, data, xTimeRange, yCumulativeRange, line_color) {
|
||||||
|
var lineCumulativeFunc = d3.line()
|
||||||
|
.defined(d => !isNaN(d.concentration))
|
||||||
|
.x(d => xTimeRange(d.time))
|
||||||
|
.y(d => yCumulativeRange(d.concentration))
|
||||||
|
.curve(d3.curveBasis);
|
||||||
|
|
||||||
|
vis.append('svg:path')
|
||||||
|
.attr('d', lineCumulativeFunc(data))
|
||||||
|
.attr('stroke', line_color)
|
||||||
|
.attr('stroke-width', 2)
|
||||||
|
.style("stroke-dasharray", "5 5")
|
||||||
|
.attr('fill', 'none');
|
||||||
|
|
||||||
return vis;
|
return vis;
|
||||||
}
|
}
|
||||||
|
|
@ -85,12 +85,13 @@
|
||||||
{% endblock report_summary_footnote %}
|
{% endblock report_summary_footnote %}
|
||||||
</div>
|
</div>
|
||||||
<p id="section1">* The results are based on the parameters and assumptions published in the CERN Open Report <a href="https://cds.cern.ch/record/2756083"> CERN-OPEN-2021-004</a>.</p>
|
<p id="section1">* The results are based on the parameters and assumptions published in the CERN Open Report <a href="https://cds.cern.ch/record/2756083"> CERN-OPEN-2021-004</a>.</p>
|
||||||
<svg id="result_plot" width="900" height="400"></svg>
|
<svg id="result_plot" width="1000" height="400"></svg>
|
||||||
<script type="application/javascript">
|
<script type="application/javascript">
|
||||||
var times = {{ times | JSONify }}
|
var times = {{ times | JSONify }}
|
||||||
var concentrations = {{ concentrations | JSONify }}
|
var concentrations = {{ concentrations | JSONify }}
|
||||||
|
var cumulative_doses = {{ cumulative_doses | JSONify }}
|
||||||
var exposed_presence_intervals = {{ exposed_presence_intervals | JSONify }}
|
var exposed_presence_intervals = {{ exposed_presence_intervals | JSONify }}
|
||||||
draw_concentration_plot("#result_plot", times, concentrations, exposed_presence_intervals);
|
draw_concentration_plot("#result_plot", times, concentrations, cumulative_doses, exposed_presence_intervals);
|
||||||
</script>
|
</script>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,6 @@ class Interval:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class SpecificInterval(Interval):
|
class SpecificInterval(Interval):
|
||||||
#: A sequence of times (start, stop), in hours, that the infected person
|
#: A sequence of times (start, stop), in hours, that the infected person
|
||||||
|
|
@ -922,9 +921,33 @@ class ExposureModel:
|
||||||
#: The fraction of viruses actually deposited in the respiratory tract
|
#: The fraction of viruses actually deposited in the respiratory tract
|
||||||
fraction_deposited: _VectorisedFloat = 0.6
|
fraction_deposited: _VectorisedFloat = 0.6
|
||||||
|
|
||||||
|
def _normed_exposure_between_bounds(self, time1: float, time2: float) -> _VectorisedFloat:
|
||||||
|
"""The number of virions per meter^3 between any two times, normalized
|
||||||
|
by the emission rate of the infected population"""
|
||||||
|
exposure = 0.
|
||||||
|
for start, stop in self.exposed.presence.boundaries():
|
||||||
|
if stop < time1:
|
||||||
|
continue
|
||||||
|
elif start > time2:
|
||||||
|
break
|
||||||
|
elif start <= time1 and time2<= stop:
|
||||||
|
exposure += self.concentration_model.normed_integrated_concentration(time1, time2)
|
||||||
|
elif start <= time1 and stop < time2:
|
||||||
|
exposure += self.concentration_model.normed_integrated_concentration(time1, stop)
|
||||||
|
elif time1 < start and time2 <= stop:
|
||||||
|
exposure += self.concentration_model.normed_integrated_concentration(start, time2)
|
||||||
|
elif time1 <= start and stop < time2:
|
||||||
|
exposure += self.concentration_model.normed_integrated_concentration(start, stop)
|
||||||
|
return exposure
|
||||||
|
|
||||||
|
def exposure_between_bounds(self, time1: float, time2: float) -> _VectorisedFloat:
|
||||||
|
"""The number of virions per meter^3 between any two times."""
|
||||||
|
return (self._normed_exposure_between_bounds(time1, time2) *
|
||||||
|
self.concentration_model.infected.emission_rate_when_present())
|
||||||
|
|
||||||
def _normed_exposure(self) -> _VectorisedFloat:
|
def _normed_exposure(self) -> _VectorisedFloat:
|
||||||
"""
|
"""
|
||||||
The number of virus per meter^3, normalized by the emission rate
|
The number of virions per meter^3, normalized by the emission rate
|
||||||
of the infected population.
|
of the infected population.
|
||||||
"""
|
"""
|
||||||
normed_exposure = 0.0
|
normed_exposure = 0.0
|
||||||
|
|
@ -935,7 +958,7 @@ class ExposureModel:
|
||||||
return normed_exposure * self.repeats
|
return normed_exposure * self.repeats
|
||||||
|
|
||||||
def exposure(self) -> _VectorisedFloat:
|
def exposure(self) -> _VectorisedFloat:
|
||||||
"""The number of virus per meter^3."""
|
"""The number of virions per meter^3."""
|
||||||
return (self._normed_exposure() *
|
return (self._normed_exposure() *
|
||||||
self.concentration_model.infected.emission_rate_when_present())
|
self.concentration_model.infected.emission_rate_when_present())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,6 @@ populations = [
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def known_concentrations(func):
|
def known_concentrations(func):
|
||||||
dummy_room = models.Room(50, 0.5)
|
dummy_room = models.Room(50, 0.5)
|
||||||
dummy_ventilation = models._VentilationBase()
|
dummy_ventilation = models._VentilationBase()
|
||||||
|
|
@ -73,21 +72,21 @@ def known_concentrations(func):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"population, cm, f_dep, expected_exposure, expected_probability", [
|
"population, cm, f_dep, expected_exposure, expected_probability",[
|
||||||
[populations[1], known_concentrations(lambda t: 36.), 1.,
|
[populations[1], known_concentrations(lambda t: 36.), 1.,
|
||||||
np.array([432, 432]), np.array([99.6803184113, 99.5181053773])],
|
np.array([432, 432]), np.array([99.6803184113, 99.5181053773])],
|
||||||
|
|
||||||
[populations[2], known_concentrations(lambda t: 36.), 1.,
|
[populations[2], known_concentrations(lambda t: 36.), 1.,
|
||||||
np.array([432, 432]), np.array([97.4574432074, 98.3493482895])],
|
np.array([432, 432]), np.array([97.4574432074, 98.3493482895])],
|
||||||
|
|
||||||
[populations[0], known_concentrations(lambda t: np.array([36., 72.])), 1.,
|
[populations[0], known_concentrations(lambda t: np.array([36., 72.])), 1.,
|
||||||
np.array([432, 864]), np.array([98.3493482895, 99.9727534893])],
|
np.array([432, 864]), np.array([98.3493482895, 99.9727534893])],
|
||||||
|
|
||||||
[populations[1], known_concentrations(lambda t: np.array([36., 72.])), 1.,
|
[populations[1], known_concentrations(lambda t: np.array([36., 72.])), 1.,
|
||||||
np.array([432, 864]), np.array([99.6803184113, 99.9976777757])],
|
np.array([432, 864]), np.array([99.6803184113, 99.9976777757])],
|
||||||
|
|
||||||
[populations[0], known_concentrations(lambda t: 72.), np.array([0.5, 1.]),
|
[populations[0], known_concentrations(lambda t: 72.), np.array([0.5, 1.]),
|
||||||
864, np.array([98.3493482895, 99.9727534893])],
|
864, np.array([98.3493482895, 99.9727534893])],
|
||||||
])
|
])
|
||||||
def test_exposure_model_ndarray(population, cm, f_dep,
|
def test_exposure_model_ndarray(population, cm, f_dep,
|
||||||
expected_exposure, expected_probability):
|
expected_exposure, expected_probability):
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue