Changed label on short range activity and added BR on dilution factor
This commit is contained in:
parent
b51848b755
commit
5720f0340a
7 changed files with 126 additions and 83 deletions
|
|
@ -13,7 +13,7 @@ from cara import data
|
|||
import cara.data.weather
|
||||
import cara.monte_carlo as mc
|
||||
from .. import calculator
|
||||
from cara.monte_carlo.data import activity_distributions, virus_distributions, mask_distributions, dilution_factor
|
||||
from cara.monte_carlo.data import activity_distributions, virus_distributions, mask_distributions, short_range_distances
|
||||
from cara.monte_carlo.data import expiration_distribution, expiration_BLO_factors, expiration_distributions, short_range_expiration_distributions
|
||||
|
||||
|
||||
|
|
@ -245,29 +245,31 @@ class FormData:
|
|||
else:
|
||||
humidity = 0.5
|
||||
room = models.Room(volume=volume, humidity=humidity)
|
||||
|
||||
infected_population = self.infected_population()
|
||||
|
||||
if self.short_range_option == "short_range_yes":
|
||||
short_range_expirations = tuple(short_range_expiration_distributions[interaction['expiration']] for interaction in self.short_range_interactions)
|
||||
short_range_activities = tuple([infected_population.activity for _ in self.short_range_interactions])
|
||||
sr_presence=self.short_range_intervals()
|
||||
sr_activities=self.short_range_activities()
|
||||
short_range_expirations = tuple(short_range_expiration_distributions[activity] for activity in sr_activities)
|
||||
dilutions=dilution_factor(activities=sr_activities)
|
||||
else:
|
||||
sr_presence=()
|
||||
short_range_expirations=()
|
||||
dilutions=()
|
||||
|
||||
short_range_activities=()
|
||||
sr_presence=()
|
||||
|
||||
# Initializes and returns a model with the attributes defined above
|
||||
return mc.ExposureModel(
|
||||
concentration_model=mc.ConcentrationModel(
|
||||
room=room,
|
||||
ventilation=self.ventilation(),
|
||||
infected=self.infected_population(),
|
||||
infected=infected_population,
|
||||
evaporation_factor=0.3,
|
||||
),
|
||||
short_range = mc.ShortRangeModel(
|
||||
presence=sr_presence,
|
||||
expirations=short_range_expirations,
|
||||
dilutions=dilutions,
|
||||
activities=short_range_activities,
|
||||
presence=sr_presence,
|
||||
distances=short_range_distances,
|
||||
),
|
||||
exposed=self.exposed_population(),
|
||||
)
|
||||
|
|
@ -655,7 +657,7 @@ class FormData:
|
|||
for interaction in self.short_range_interactions:
|
||||
start_time = time_string_to_minutes(interaction['start_time'])
|
||||
duration = float(interaction['duration'])
|
||||
short_range_intervals.append((interaction['activity'], models.SpecificInterval((start_time/60, (start_time + duration)/60))))
|
||||
short_range_intervals.append((interaction['expiration'], models.SpecificInterval((start_time/60, (start_time + duration)/60))))
|
||||
return tuple(short_range_intervals)
|
||||
|
||||
|
||||
|
|
@ -665,10 +667,6 @@ class FormData:
|
|||
breaks=self.exposed_lunch_break_times() + self.exposed_coffee_break_times(),
|
||||
)
|
||||
|
||||
def short_range_activities(self) -> typing.List[str]:
|
||||
return ([interaction['activity'] for interaction in self.short_range_interactions]
|
||||
if self.short_range_interactions else [])
|
||||
|
||||
|
||||
def build_expiration(expiration_definition) -> mc._ExpirationBase:
|
||||
if isinstance(expiration_definition, str):
|
||||
|
|
|
|||
|
|
@ -408,7 +408,7 @@ function validate_form(form) {
|
|||
$(".form_field_outer_row").each(function (index, element){
|
||||
let obj = {};
|
||||
const $element = $(element);
|
||||
obj.activity = $element.find("[name='short_range_activity']").val();
|
||||
obj.expiration = $element.find("[name='short_range_expiration']").val();
|
||||
obj.start_time = $element.find("[name='short_range_start_time']").val();
|
||||
obj.duration = $element.find("[name='short_range_duration']").val();
|
||||
short_range_interactions.push(JSON.stringify(obj));
|
||||
|
|
@ -656,8 +656,8 @@ $(document).ready(function () {
|
|||
let index = 1;
|
||||
for (const interaction of JSON.parse(value)) {
|
||||
$("#dialog_sr").append(inject_sr_interaction(index, value = interaction, is_validated="row_validated"))
|
||||
$('#sr_activity_no_' + String(index)).val(interaction.activity).change();
|
||||
document.getElementById('sr_activity_no_' + String(index)).disabled = true;
|
||||
$('#sr_expiration_no_' + String(index)).val(interaction.expiration).change();
|
||||
document.getElementById('sr_expiration_no_' + String(index)).disabled = true;
|
||||
document.getElementById('sr_start_no_' + String(index)).disabled = true;
|
||||
document.getElementById('sr_duration_no_' + String(index)).disabled = true;
|
||||
document.getElementById('edit_row_no_' + String(index)).style.cssText = 'display:inline !important';
|
||||
|
|
@ -838,35 +838,35 @@ $(document).ready(function () {
|
|||
|
||||
function inject_sr_interaction(index, value, is_validated) {
|
||||
return `<div class="col-md-12 form_field_outer p-0">
|
||||
<div class="form_field_outer_row split ${is_validated}">
|
||||
<div class="split" style="flex: 3">
|
||||
<div class="form_field_outer_row ${is_validated} split">
|
||||
|
||||
<div class='form-group row'>
|
||||
<div class="col-sm-4"><label class="col-form-label"> Activity: </label></br></div>
|
||||
<div class="col-sm-7"><select id="sr_activity_no_${index}" name="short_range_activity" class="form-control" onchange="validate_sr_parameter(this)" form="not-submitted">
|
||||
<div class="col-sm-4"><label class="col-form-label col-form-label-sm"> Expiration: </label><br></div>
|
||||
<div class="col-sm-8"><select id="sr_expiration_no_${index}" name="short_range_expiration" class="form-control form-control-sm" onchange="validate_sr_parameter(this)" form="not-submitted">
|
||||
<option value="" selected disabled>Select type</option>
|
||||
<option value="Breathing">Breathing</option>
|
||||
<option value="Speaking">Speaking</option>
|
||||
<option value="Shouting">Shouting</option>
|
||||
</select></br>
|
||||
</select><br>
|
||||
</div>
|
||||
</div>
|
||||
<div class='form-group row'>
|
||||
<div class="col-sm-4"><label class="col-form-label"> Start: </label></div>
|
||||
<div class="col-sm-7"><input type="time" class="form-control short_range_option" name="short_range_start_time" id="sr_start_no_${index}" value="${value.start_time}" onchange="validate_sr_time(this)" form="not-submitted"></br></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="split" style="flex: 2">
|
||||
<div class='form-group row' style="flex: 2">
|
||||
<div class="col-sm-4"><label class="col-form-label"> Duration:</label></div>
|
||||
<div class="col-sm-8"><input type="number" id="sr_duration_no_${index}" value="${value.duration}" class="form-control short_range_option" name="short_range_duration" min=1 placeholder="Minutes" onchange="validate_sr_time(this)" form="not-submitted"></br></div>
|
||||
|
||||
<div class='form-group row'>
|
||||
<div class="col-sm-4"><label class="col-form-label col-form-label-sm"> Start: </label></div>
|
||||
<div class="col-sm-8"><input type="time" class="form-control form-control-sm short_range_option" name="short_range_start_time" id="sr_start_no_${index}" value="${value.start_time}" onchange="validate_sr_time(this)" form="not-submitted"><br></div>
|
||||
</div>
|
||||
<div class="form-group mt-1" style="flex: 1">
|
||||
|
||||
<div class='form-group row'>
|
||||
<div class="col-sm-4"><label class="col-form-label col-form-label-sm"> Duration:</label></div>
|
||||
<div class="col-sm-8"><input type="number" id="sr_duration_no_${index}" value="${value.duration}" class="form-control form-control-sm short_range_option" name="short_range_duration" min=1 placeholder="Minutes" onchange="validate_sr_time(this)" form="not-submitted"><br></div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="max-width: 8rem">
|
||||
<button type="button" id="edit_row_no_${index}" class="edit_node_btn_frm_field btn btn-success btn-sm d-none">Edit</button>
|
||||
<button type="button" id="validate_row_no_${index}" class="validate_node_btn_frm_field btn btn-success btn-sm">Save</button>
|
||||
<button type="button" class="remove_node_btn_frm_field btn btn-danger btn-sm">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
|
||||
|
|
@ -889,12 +889,12 @@ $(document).ready(function () {
|
|||
// Validate row button (Save button)
|
||||
$("body").on("click", ".validate_node_btn_frm_field", function() {
|
||||
var index = $(this).attr('id').split('_').slice(-1)[0];
|
||||
let activity = validate_sr_parameter('#sr_activity_no_' + String(index)[0], "Required input.");
|
||||
let activity = validate_sr_parameter('#sr_expiration_no_' + String(index)[0], "Required input.");
|
||||
let start = validate_sr_parameter('#sr_start_no_' + String(index)[0], "Required input.");
|
||||
let duration = validate_sr_parameter('#sr_duration_no_' + String(index)[0], "Required input.");
|
||||
if (activity && start && duration) {
|
||||
if (validate_sr_time('#sr_start_no_' + String(index)) && validate_sr_time('#sr_start_no_' + String(index))) {
|
||||
document.getElementById('sr_activity_no_' + String(index)).disabled = true;
|
||||
document.getElementById('sr_expiration_no_' + String(index)).disabled = true;
|
||||
document.getElementById('sr_start_no_' + String(index)).disabled = true;
|
||||
document.getElementById('sr_duration_no_' + String(index)).disabled = true;
|
||||
document.getElementById('edit_row_no_' + String(index)).style.cssText = 'display:inline !important';
|
||||
|
|
@ -915,7 +915,7 @@ $(document).ready(function () {
|
|||
$("body").on("click", ".edit_node_btn_frm_field", function() {
|
||||
$(this).hide();
|
||||
let id = $(this).attr('id').split('_').slice(-1)[0];
|
||||
document.getElementById('sr_activity_no_' + String(id)).disabled = false;
|
||||
document.getElementById('sr_expiration_no_' + String(id)).disabled = false;
|
||||
document.getElementById('sr_start_no_' + String(id)).disabled = false;
|
||||
document.getElementById('sr_duration_no_' + String(id)).disabled = false;
|
||||
document.getElementById('validate_row_no_' + String(id)).style.cssText = 'display:inline !important';
|
||||
|
|
|
|||
|
|
@ -504,9 +504,10 @@ baseline_model = models.ExposureModel(
|
|||
evaporation_factor=0.3,
|
||||
),
|
||||
short_range=models.ShortRangeModel(
|
||||
presence=(),
|
||||
expirations=(),
|
||||
dilutions=(),
|
||||
activities=(),
|
||||
presence=(),
|
||||
distances=(),
|
||||
),
|
||||
exposed=models.Population(
|
||||
number=10,
|
||||
|
|
|
|||
|
|
@ -391,11 +391,12 @@
|
|||
<h5 class="modal-title" id="short_range_dialogTitle">Short-range interactions</h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="col-md-12 p-0 form-group" id="dialog_sr">
|
||||
</div>
|
||||
|
||||
<div class="col-md-12 p-0 form-group" id="dialog_sr"></div>
|
||||
<div class="text-center"><button type="button" class="add_node_btn_frm_field btn btn-primary btn-sm">Add row</button></div>
|
||||
<input type="text" class="form-control d-none" name="short_range_interactions">
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary dismiss_btn_frm_field" data-dismiss="modal">Clear all</button>
|
||||
<button type="button" class="btn btn-primary close_btn_frm_field">Save all</button>
|
||||
|
|
|
|||
|
|
@ -335,7 +335,7 @@
|
|||
</p></li>
|
||||
<ul>
|
||||
{% for interaction in form.short_range_interactions %}
|
||||
<li>Expiratory activity {{ loop.index if form.short_range_interactions|length > 1 }}: {{ interaction.activity }} </li>
|
||||
<li>Expiratory activity {{ loop.index if form.short_range_interactions|length > 1 }}: {{ interaction.expiration }} </li>
|
||||
<li>Start time {{ loop.index if form.short_range_interactions|length > 1 }}: {{ interaction.start_time }} </li>
|
||||
<li>Duration {{ loop.index if form.short_range_interactions|length > 1 }}: {{ interaction.duration }} {{ "minutes" if interaction.duration|float > 1 else "minute" }}</li>
|
||||
{% endfor %}
|
||||
|
|
|
|||
|
|
@ -1074,14 +1074,52 @@ class ConcentrationModel:
|
|||
|
||||
@dataclass(frozen=True)
|
||||
class ShortRangeModel:
|
||||
#: Short-range activities and respective interactions
|
||||
presence: typing.Tuple[typing.Tuple[str, SpecificInterval], ...]
|
||||
|
||||
#: Expiration types
|
||||
expirations: typing.Tuple[_ExpirationBase, ...]
|
||||
|
||||
#: The dilution factors for each of the expiratory activity
|
||||
dilutions: typing.Tuple[_VectorisedFloat, ...]
|
||||
#: Activity types
|
||||
activities: typing.Tuple[Activity, ...]
|
||||
|
||||
#: Short-range expirtory activities and respective interactions
|
||||
presence: typing.Tuple[typing.Tuple[str, SpecificInterval], ...]
|
||||
|
||||
#: Interpersonal distances
|
||||
distances: _VectorisedFloat
|
||||
|
||||
def dilution_factor(self, BR: _VectorisedFloat) -> _VectorisedFloat:
|
||||
'''
|
||||
The dilution factor for the respective expiratory activity type.
|
||||
'''
|
||||
# Average mouth diameter
|
||||
D = 0.02
|
||||
# Convert Breathing rate from m3/h to m3/s
|
||||
BR_s = BR/3600
|
||||
# Area of the mouth assuming a perfect circle
|
||||
Am = np.pi*(D**2)/4
|
||||
# Initial velocity from the division of the Breathing rate with the area
|
||||
u0 = BR_s/Am
|
||||
|
||||
tstar = 2.0
|
||||
Cr1 = 0.18
|
||||
Cr2 = 0.2
|
||||
Cx1 = 2.4
|
||||
|
||||
# The expired flow rate during the expiration period, m^3/s
|
||||
Q0 = u0 * np.pi/4*D**2
|
||||
# Parameters in the jet-like stage
|
||||
x01 = D/2/Cr1
|
||||
# Time of virtual origin
|
||||
t01 = (x01/Cx1)**2 * (Q0*u0)**(-0.5)
|
||||
# The transition point, m
|
||||
xstar = np.mean(Cx1*(Q0*u0)**0.25*(tstar + t01)**0.5 - x01)
|
||||
# Dilution factor at the transition point xstar
|
||||
Sxstar = np.mean(2*Cr1*(xstar+x01)/D)
|
||||
|
||||
return np.piecewise(self.distances, [self.distances < xstar, self.distances >= xstar],
|
||||
[
|
||||
lambda distance: 2*Cr1*(distance + x01)/D,
|
||||
lambda distance: Sxstar*(1 + Cr2*(distance - xstar)/Cr1/(xstar + x01))**3
|
||||
])
|
||||
|
||||
def _normed_concentration(self, concentration_model: ConcentrationModel, time: float) -> _VectorisedFloat:
|
||||
"""
|
||||
|
|
@ -1095,16 +1133,18 @@ class ShortRangeModel:
|
|||
start, stop = tuple(period.boundaries())
|
||||
# Verifies if the given time falls within a short-range interaction
|
||||
if start < time <= stop:
|
||||
dilution = self.dilutions[index]
|
||||
dilution = self.dilution_factor(self.activities[index].exhalation_rate)
|
||||
jet_origin_concentration = self.expirations[index].jet_origin_concentration()
|
||||
# Long-range concentration normalized by the virus viral load
|
||||
long_range_normed_concentration = concentration_model.concentration(time) / concentration_model.virus.viral_load_in_sputum
|
||||
long_range_normed_concentration = (concentration_model.concentration(time) /
|
||||
concentration_model.virus.viral_load_in_sputum)
|
||||
|
||||
# The long-range concentration values are then approximated using interpolation:
|
||||
# The set of points where we want the interpolated values are the short-range particle diameters (given the current expiration);
|
||||
# The set of points with a known value are the long-range particle diameters (given the initial expiration);
|
||||
# The set of known values are the long-range concentration values normalized by the viral load.
|
||||
long_range_normed_concentration_interpolated=np.interp(self.expirations[index].particle.diameter, concentration_model.infected.particle.diameter, long_range_normed_concentration)
|
||||
long_range_normed_concentration_interpolated=np.interp(self.expirations[index].particle.diameter,
|
||||
concentration_model.infected.particle.diameter, long_range_normed_concentration)
|
||||
|
||||
# Short-range concentration formula. The long-range concentration is added in the concentration method (ExposureModel).
|
||||
return ((1/dilution)*(jet_origin_concentration - long_range_normed_concentration_interpolated))
|
||||
|
|
@ -1146,7 +1186,7 @@ class ShortRangeModel:
|
|||
start_bound, stop_bound = start, stop
|
||||
|
||||
jet_origin_integrated = self.expirations[index].jet_origin_concentration()
|
||||
dilution = self.dilutions[index]
|
||||
dilution = self.dilution_factor(self.activities[index].exhalation_rate)
|
||||
|
||||
total_normed_concentration = -(concentration_model.integrated_concentration(start_bound, stop_bound)/concentration_model.virus.viral_load_in_sputum/dilution)
|
||||
total_normed_concentration_interpolated = np.interp(self.expirations[index].particle.diameter, concentration_model.infected.particle.diameter, total_normed_concentration)
|
||||
|
|
|
|||
|
|
@ -182,39 +182,39 @@ def expiration_distribution(
|
|||
)
|
||||
|
||||
|
||||
def dilution_factor(activities):
|
||||
D = 0.02
|
||||
# From https://www.mdpi.com/1660-4601/17/4/1445/htm
|
||||
distance = LogNormal(0.8542127255693238, 0.42755967248106513).generate_samples(250_000)
|
||||
# def dilution_factor(activities):
|
||||
# D = 0.02
|
||||
# # Fit from Fig 8 a) "stand-stand" in https://www.mdpi.com/1660-4601/17/4/1445/htm
|
||||
# distance = LogNormal(-0.269359136417347, 0.4728300188814934).generate_samples(250_000)
|
||||
|
||||
factors = []
|
||||
for activity in activities:
|
||||
u0 = 0.98 if activity == "Breathing" else 3.9
|
||||
tstar = 2.0
|
||||
Cr1 = 0.18
|
||||
Cr2 = 0.2
|
||||
Cx1 = 2.4
|
||||
# The expired flow rate during the expiration period, m^3/s
|
||||
Q0 = u0 * np.pi/4*D**2
|
||||
# Parameters in the jet-like stage
|
||||
x01 = D/2/Cr1
|
||||
# Time of virtual origin
|
||||
t01 = (x01/Cx1)**2 * (Q0*u0)**(-0.5)
|
||||
# The transition point, m
|
||||
xstar = Cx1*(Q0*u0)**0.25*(tstar + t01)**0.5 - x01
|
||||
# Dilution factor at the transition point xstar
|
||||
Sxstar = 2*Cr1*(xstar+x01)/D
|
||||
factors.append(
|
||||
np.piecewise(
|
||||
distance,
|
||||
[distance < xstar, distance >= xstar],
|
||||
[
|
||||
lambda distance: 2*Cr1*(distance + x01)/D,
|
||||
lambda distance: Sxstar*(1 + Cr2*(distance - xstar)/Cr1/(xstar + x01))**3
|
||||
]
|
||||
)
|
||||
)
|
||||
return factors
|
||||
# factors = []
|
||||
# for activity in activities:
|
||||
# u0 = 0.98 if activity == "Breathing" else 3.9
|
||||
# tstar = 2.0
|
||||
# Cr1 = 0.18
|
||||
# Cr2 = 0.2
|
||||
# Cx1 = 2.4
|
||||
# # The expired flow rate during the expiration period, m^3/s
|
||||
# Q0 = u0 * np.pi/4*D**2
|
||||
# # Parameters in the jet-like stage
|
||||
# x01 = D/2/Cr1
|
||||
# # Time of virtual origin
|
||||
# t01 = (x01/Cx1)**2 * (Q0*u0)**(-0.5)
|
||||
# # The transition point, m
|
||||
# xstar = Cx1*(Q0*u0)**0.25*(tstar + t01)**0.5 - x01
|
||||
# # Dilution factor at the transition point xstar
|
||||
# Sxstar = 2*Cr1*(xstar+x01)/D
|
||||
# factors.append(
|
||||
# np.piecewise(
|
||||
# distance,
|
||||
# [distance < xstar, distance >= xstar],
|
||||
# [
|
||||
# lambda distance: 2*Cr1*(distance + x01)/D,
|
||||
# lambda distance: Sxstar*(1 + Cr2*(distance - xstar)/Cr1/(xstar + x01))**3
|
||||
# ]
|
||||
# )
|
||||
# )
|
||||
# return factors
|
||||
|
||||
|
||||
expiration_BLO_factors = {
|
||||
|
|
@ -235,3 +235,6 @@ short_range_expiration_distributions = {
|
|||
exp_type: expiration_distribution(BLO_factors, d_max=100)
|
||||
for exp_type, BLO_factors in expiration_BLO_factors.items()
|
||||
}
|
||||
|
||||
|
||||
short_range_distances = LogNormal(-0.269359136417347, 0.4728300188814934)
|
||||
|
|
|
|||
Loading…
Reference in a new issue