Settling of Road-Deposited Sediment: Influence of Particle Density, Shape, Low Temperatures, and De-Icing Salt
Steffen H. Rommel1, Laura Gelhardt2, Antje Welker2 and Brigitte Helmreich1,*
1 Chair of Urban Water Systems Engineering, Technical University of Munich, Am Coulombwall 3, 85748 Garching, Germany; sww@tum.de
2 Fachgebiet Siedlungswasserwirtschaft und Hydromechanik , Frankfurt University of Applied Sciences, Nibelungenplatz 1, 60318 Frankfurt am Main, Germany; antje.welker@fb1.fra-uas.de
* Correspondence: b.helmreich@tum.de
This code is written in Python 3 using the packages Pandas, Matplotlib, Numpy, SciPy, and Seaborn.
%matplotlib inline
Import necessary packages
import pandas as pd
import seaborn as sns
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.ticker
from matplotlib.ticker import (MultipleLocator, FormatStrFormatter,
AutoMinorLocator)
import numpy as np
from scipy import stats
from scipy import optimize
from scipy import interpolate
Plot settings
plt.style.use('default')
sns.set_palette("muted")
sns.set_context("paper")
linewidth_annotation = 2
plt.rcParams['legend.frameon'] = False
plt.rcParams['text.usetex'] = False
plt.rcParams.update({'mathtext.default': 'regular'})
sns.set_palette('deep')
sns.set_style({'grid.linestyle': 'dotted'})
Plot sizes
single_col = 8.3 / 2.54
double_col = 17.1 / 2.54
plt.rcParams["figure.figsize"] = single_col, single_col
d_cdf = [250, 200, 160, 125, 100, 63, 40]
c_weight = [1, .96, .90, .78, .70, .49, .34]
PSD_df = pd.DataFrame()
PSD_df['d (µm)'] = d_cdf
PSD_df['m_fraction (-)'] = c_weight
def weibull(d, lamb, kappa):
return (1 - np.exp(-(d/lamb) ** kappa))
PSD_weibull_df = pd.DataFrame()
PSD_weibull_df['d (µm)'] = np.arange(0,251,1)
Weibull fit parameters used to describe the PSD, the parameters were determined using the non-linear least square algorithm
kappa = 1.247
lamb = 85.09
PSD_weibull_df['m_fraction (-)'] = \
PSD_weibull_df.apply(lambda x: weibull(x['d (µm)'], lamb, kappa), axis=1)
fig, (ax1) = plt.subplots(figsize=(single_col, 2), nrows=1, ncols=1)
sns.scatterplot(x=d_cdf, y=c_weight, marker='x', label='Data', ax=ax1)
plt.xlabel('d ($\mu$m)')
plt.ylabel('Cumulative mass fraction (-)')
PSD_weibull_df['m_fraction (-)'].plot(x='d (µm)',
label='Weibull fit, $R^2 = 0.99$',
ax=ax1)
ax1.set_ylim(0,1.05)
ax1.set_xlim(0,250*1.05)
plt.legend()
according to Laliberté and Cooper (2004), https://doi.org/10.1021/je0498659
def rho_w(t):
# rho_w = Densities of water ρw (kg m−3)
# t = temperature in °C
return (((((- 2.8054253 * 10**(-10) * t + 1.0556302 * 10**(-7))*t - 4.6170461 * 10**(-5))*t - 0.0079870401)*t \
+ 16.945176)*t + 999.83952)/(1+0.01687985*t)
def v_app_nacl(t,w_nacl):
# eq is valid for w_nacl > 0.00006
# v_app_nacl = NaCl specific volume (m3 kg−1)
# w_nacl = mass fraction of the NaCl in solution (-)
return (w_nacl + 1.01660 + 0.014624 * t)/((-0.00433 * w_nacl + 0.06471) * np.exp(0.000001 * (t + 3315.6)**2))
def rho_f(t,w_nacl):
# rho_f = Densities of densities of solutions (kg m−3)
# t = temperature in °C
# w_nacl = mass fraction of NaCl in solution
# w_h2o = mass fraction of H2O in solution
w_h2o = 1 - w_nacl
if w_nacl <= 0.00006:
return rho_w(t)
else:
return 1 / (w_h2o / rho_w(t) + w_nacl * v_app_nacl(t,w_nacl))
according to Laliberté (2007), https://doi.org/10.1021/je0604075
def eta_w(t):
# eta_w = dynamic viscosity of water ηw (kg m−1 s−1)
return ((t + 246) * 10**(-3))/((0.05594 * t + 5.2842) * t + 137.37)
def eta_f(t,w_nacl):
# eta_f = dynamic viscosity of NaCl solution (kg m−1 s−1)
# eta_w = dynamic viscosity of water (kg m−1 s−1)
# t = temperature (°C), valid for >=5 °C
w_h2o = 1- w_nacl
if t >=5:
return eta_w(t)**w_h2o * (10**(-3) * np.exp((16.222 * (1 - w_h2o)**1.3229 + 1.4849)/\
((0.0074691 * t + 1) * (30.78 * (1 - w_h2o)**2.0583 + 1))))**w_nacl
else:
return np.nan
according to Cheng (2009), https://doi.org/10.1016/j.powtec.2008.07.006
def d_dimless(d, eta_f, rho_f, rho_s):
# d_dimless = dimionsenless grain diamter
# ny = kinematic viscosity of fluid
g = 9.81
ny_f = eta_f / rho_f
delta_rho = (rho_s - rho_f) / rho_f
return (delta_rho * g / ny_f**2)**(1/3) * d
def C_D(d_dimless):
# C_D = drag coefficient
return 432 / (d_dimless**3) * (1 + 0.022 * d_dimless**3)**0.54 + 0.47 * (1 - np.exp(-0.15*d_dimless**0.45))
def w_dimless(d_dimless, C_D):
return np.sqrt(4 * d_dimless / (3 * C_D))
def w(w_dimless, rho_f, rho_s, eta_f):
return w_dimless * ((rho_s - rho_f) / rho_f * 9.81 * eta_f / rho_f)**(1/3)
def w_complete(t, w_nacl, d, rho_s):
# INPUT:
# t in °C
# w_nacl without unit
# d in µm
# rho_s in kg m-3
d_local = d * 10**-6
# calc rho_f
rho_f_local = rho_f(t, w_nacl)
# calc eta_f
eta_f_local = eta_f(t, w_nacl)
# calc ny_f
ny_f_local = eta_f_local / rho_f_local
# calc d_dimless
d_dimless_local = d_dimless(d_local, eta_f_local, rho_f_local, rho_s)
# calc C_D
C_D_local = C_D(d_dimless_local)
# calc w_dimless
w_dimless_local = w_dimless(d_dimless_local, C_D_local)
# calc w
w_local = w(w_dimless_local, rho_f_local, rho_s, eta_f_local)
return w_local
fig, (ax1) = plt.subplots(figsize=(double_col, single_col), nrows=1, ncols=1)
ax1.plot(PSD_weibull_df['d (µm)'], w_complete(20, 0.0, PSD_weibull_df['d (µm)'], 2650),
label=r't=20°C, w$_{NaCl}$=0.00, $\rho_s$=2650 kg $m^{-3}$')
ax1.plot(PSD_weibull_df['d (µm)'], w_complete(5, 0.02, PSD_weibull_df['d (µm)'], 2650),
'-.', label=r't=5°C, w$_{NaCl}$=0.02, $\rho_s$=2650 kg $m^{-3}$')
ax1.set_xlim(0,250)
ax1.set_ylim(0,0.04)
ax1.set_xlabel(r'$d\/(\mu m)$')
ax1.set_ylabel(r'$w\/(m\/s^{-1})$')
plt.legend()
according to Haider and Levenspiel (1989), https://doi.org/10.1016/0032-5910(89)80008-7
def w_nonspherical(d, t, w_nacl, rho_s, phi):
# phi = sphericity (-)
d_local = d * 10**-6
# calc rho_f
rho_f_local = rho_f(t, w_nacl)
# calc eta_f
eta_f_local = eta_f(t, w_nacl)
d_dimless_local = d_local * ((9.81 * rho_f_local*(rho_s - rho_f_local))/eta_f_local**2)**(1/3)
A = ((rho_f_local**2)/(9.81*eta_f_local * (rho_s-rho_f_local)))**(1/3)
if 0.5 <= phi <= 1:
return (18/(d_dimless_local**2) + (2.3348-1.7439*phi)/(d_dimless_local**0.5))**(-1) / A
else:
return np.nan
fig, (ax1) = plt.subplots(figsize=(single_col, 2.5), nrows=1, ncols=1)
d = np.arange(1,250,.1)
for phi in [0.5, 0.6, 0.7, 0.8, 0.9, 1.0]:
ax1.plot(d, w_nonspherical(d, 20, 0, 2600, phi), label=phi)
plt.legend(title="$\phi$", loc='upper left')
plt.xlabel('d ($\mu$m)')
plt.ylabel('w (m s$^{-1}$)')
Import experimental results: Insert file path to the excel file, containg the experimental results or modeling input, here. If the column names are modified the consecutive code must be adapted.
results_path = #INSERT PATH HERE e.g. C:/Users/USERNAME/Desktop/Supplementary Material B - Modeling Input Data.csv'
df_sim_results = pd.read_csv(results_path, na_values='-')
df_sim_results
Specify wcrit with respect to the sampling time
w_crit = 0.76 / (4 * 60) # m/s
def settling_velocity_dist_exp(t_exp_tmp, w_nacl_exp_tmp, rho_s_exp_tmp):
df_sim_tmp = PSD_weibull_df.copy()
df_sim_tmp['w'] = \
df_sim_tmp.apply(lambda x: 0 if x['d (µm)'] == 0 else w_complete(t_exp_tmp, w_nacl_exp_tmp, x['d (µm)'],
rho_s_exp_tmp), axis=1)
return df_sim_tmp
def settled_frac_func(df_sim_tmp):
f_sim_tmp = interpolate.interp1d(df_sim_tmp['w'], df_sim_tmp['m_fraction (-)'], kind='linear')
return 1- f_sim_tmp(w_crit)
def settled_frac_func_apply(t_exp, w_nacl_exp, rho_s_exp):
df_sim_tmp = settling_velocity_dist_exp(t_exp, w_nacl_exp, rho_s_exp)
return settled_frac_func(df_sim_tmp)
df_sim_results['settled model A (-)'] = \
df_sim_results.apply(lambda x: settled_frac_func_apply(x['t (°C)'], x['w_nacl (-)'], x['rho_s (kg m-3)']),
axis=1)
def h_func(V):
# V = Withdrawn sample volume in mL
# h_cone = height of the truncated cone (hcone) with the same shape like
# the utilized glass columns and volume of the withdrawn sample in cm
if V != 0:
return 5**(2/3) * (12 * V / np.pi + 5)**(1/3) - 5 # in cm
else:
return np.nan
def w_crit_exp_func(V):
return (76 - h_func(V)) / 100 / (4*60)
df_sim_results['w_crit_exp (m s-1)'] = df_sim_results.apply(lambda x: w_crit_exp_func(x['sample volume (mL)']),
axis=1)
def settled_frac_exp_func(df_sim_tmp, w_crit_exp):
f_sim_tmp = interpolate.interp1d(df_sim_tmp['w'], df_sim_tmp['m_fraction (-)'], kind='linear')
return 1- f_sim_tmp(w_crit_exp)
def settled_frac_exp_func_apply(t_exp, w_nacl_exp, rho_s_exp, w_crit_exp):
df_sim_tmp = settling_velocity_dist_exp(t_exp, w_nacl_exp, rho_s_exp)
return settled_frac_exp_func(df_sim_tmp, w_crit_exp)
mask = ~df_sim_results['sample volume (mL)'].isnull()
df_sim_results['settled model B (-)'] = \
df_sim_results[mask].apply(lambda x: settled_frac_exp_func_apply(x['t (°C)'], x['w_nacl (-)'],
x['rho_s (kg m-3)'], x['w_crit_exp (m s-1)'])
,axis=1)
def settling_velocity_dist_exp_nonspherical(t_exp_tmp, w_nacl_exp_tmp, rho_s_exp_tmp, phi_exp_tmp):
df_sim_tmp = PSD_weibull_df.copy()
df_sim_tmp['w'] = \
df_sim_tmp.apply(lambda x: 0 if x['d (µm)'] == 0 else w_nonspherical(x['d (µm)'],
t_exp_tmp,
w_nacl_exp_tmp,
rho_s_exp_tmp,
phi_exp_tmp),axis=1)
return df_sim_tmp
def settled_frac_func(df_sim_tmp):
f_sim_tmp = interpolate.interp1d(df_sim_tmp['w'], df_sim_tmp['m_fraction (-)'], kind='linear')
return 1- f_sim_tmp(w_crit)
def settled_frac_func_apply_nonspherical(t_exp, w_nacl_exp, rho_s_exp, phi_exp):
df_sim_tmp = settling_velocity_dist_exp_nonspherical(t_exp, w_nacl_exp, rho_s_exp, phi_exp)
return settled_frac_func(df_sim_tmp)
df_sim_results['settled model C (-)'] =\
df_sim_results.apply(lambda x: settled_frac_func_apply_nonspherical(x['t (°C)'], x['w_nacl (-)'],
x['rho_s (kg m-3)'], x['circularity (-)']),
axis=1)
fig, (ax1) = plt.subplots(figsize=(double_col, 2.5), nrows=1, ncols=1)
sns.barplot(data=df_sim_results, x='experiment', y='settled exp (-)', ax=ax1, ci='sd', errwidth=0.75,
capsize=0.25)
for label in ax1.get_xticklabels():
label.set_ha("center")
label.set_rotation(90)
sns.swarmplot(x='experiment', y='settled model A (-)', data=df_sim_results,
size=4, color=".3", linewidth=0, label='Model A')
sns.swarmplot(x='experiment', y='settled model C (-)', data=df_sim_results,
size=4, color=".3", linewidth=0, marker='d', label='Model C')
plt.ylim(0,.8)
plt.ylabel('Settled mass fraction (-)')
plt.xlabel('Experiment')
ax1.yaxis.set_minor_locator(MultipleLocator(0.05))
plt.xticks(rotation=45, ha='right')
plt.grid(axis='y')
Overview of the data
df_sim_results