Module aerosol_functions
Collection of functions to analyze atmospheric aerosol data
Expand source code
"""
Collection of functions to analyze atmospheric aerosol data
"""
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as dts
from matplotlib.ticker import LogLocator
from matplotlib import colors
from datetime import datetime, timedelta, timezone
from scipy.optimize import minimize
from scipy.integrate import trapezoid
def datenum2datetime(datenum,tz=None):
"""
Convert from matlab datenum to python datetime
Parameters
----------
datenum : `float`
A serial date number representing the whole and
fractional number of days from 1-Jan-0000 to a
specific date (MATLAB datenum)
tz : `int` or `None`
Timezone offset in minutes from UTC
`None` implies timezone unaware
Returns
-------
`pandas.Timestamp`
"""
dt = (datetime.fromordinal(int(datenum)) + timedelta(days=datenum%1) - timedelta(days = 366))
if tz is not None:
tz_offset = timezone(timedelta(minutes=tz))
dt = dt.replace(tzinfo=tz_offset)
return pd.to_datetime(dt.isoformat())
def datetime2datenum(dt):
"""
Convert from python datetime to matlab datenum
Parameters
----------
dt : datetime object
Returns
-------
float
A serial date number representing the whole and
fractional number of days from 1-Jan-0000 to a
specific date (MATLAB datenum)
"""
ord = dt.toordinal()
mdn = dt + timedelta(days = 366)
frac = (dt-datetime(dt.year,dt.month,dt.day,0,0,0)).seconds / (24.0 * 60.0 * 60.0)
return mdn.toordinal() + frac
def bin_df(df, t_min, t_max, reso, q=0.5):
""" Utility function for binning timeseries data
Parameters
----------
df : pandas.DataFrame
Aerosol number size distribution
`df.index` time
`df.columns` particle diameter (m)
`df.values` normalized concentrations (dN/dlogDp)
t_min : datetime or str
first bin lower limit
t_max : datetime or str
last bin upper limit
reso : int or str
desired time resolution in minutes
or pandas time offset alias
q : float
quintile of data calculated per bin
default is the median (0.5)
Returns
-------
pandas.DataFrame
Binned aerosol number size distribution
All bins have constant width determined by reso and they
share edges. If a bin has no values it is given a value of `NaN`
"""
if isinstance(reso,int):
reso = pd.Timedelta(minutes=reso)
if isinstance(reso,str):
pass
ix = pd.date_range(t_min,t_max,freq=reso)
half_step = (ix[1] - ix[0])/2.
data = []
index = []
for i in range(len(ix)-1):
df_block = df.iloc[((df.index>=ix[i]) & (df.index<ix[i+1])),:].median().values.flatten()
if len(df_block)==0:
df_block = np.nan*np.ones(len(df.columns))
data.append(df_block)
index.append(ix[i] + half_step)
return pd.DataFrame(index = index, data = data, columns = df.columns)
def generate_log_ticks(min_exp,max_exp):
"""
Generate ticks and ticklabels for log axis
Parameters:
-----------
min_exp : int
The exponent in the smallest power of ten
max_exp : int
The exponent in the largest power of ten
Returns:
--------
numpy.array
tick values
list of str
tick labels for each power of ten
"""
x=np.arange(1,10)
y=np.arange(min_exp,max_exp).astype(float)
log_ticks=[]
log_tick_labels=[]
for j in y:
for i in x:
log_ticks.append(np.log10(np.round(i*10**j,int(np.abs(j)))))
if i==1:
log_tick_labels.append("10$^{%d}$"%j)
else:
log_tick_labels.append('')
log_ticks=np.array(log_ticks)
return log_ticks,log_tick_labels
def plot_sumfile(
v,
ax=None,
vmin=10,
vmax=100000,
time_reso=2,
time_formatter="%H:%M"):
"""
Plot aerosol particle number-size distribution surface plot
Parameters
----------
v : pandas.DataFrame
Aerosol number size distribution
time (index) should be have constant resolution,
otherwise the time axis will not be correct
ax : axes object
axis on which to plot the data
if `None` the axis are created
vmin : float or int
color scale lower limit
vmax : float or int
color scale upper limit
time_reso : `int`
Time resolution of ticks given in hours
time_formatter : `str`
Define the format of time ticklabels
"""
if ax is None:
fig,handle = plt.subplots(figsize=(10,4))
else:
handle=ax
dp = v.columns.values.astype(float)
dndlogdp = v.values.astype(float)
log_ticks,log_tick_labels = generate_log_ticks(-10,-4)
norm = colors.LogNorm(vmin=vmin,vmax=vmax)
color_ticks = LogLocator(subs=range(10))
handle.set_yticks(log_ticks)
handle.set_yticklabels(log_tick_labels)
if v.index[0].utcoffset() is None:
t1=dts.date2num(v.index[0])+v.index[0].second/(60.*60.*24.)
t2=dts.date2num(v.index[-1])+v.index[-1].second/(60.*60.*24.)
else:
t1=dts.date2num(v.index[0])+v.index[0].utcoffset().seconds/(60.*60.*24.)
t2=dts.date2num(v.index[-1])+v.index[-1].utcoffset().seconds/(60.*60.*24.)
dp1=np.log10(dp.min())
dp2=np.log10(dp.max())
img = handle.imshow(
np.flipud(dndlogdp.T),
origin="upper",
aspect="auto",
cmap="turbo",
norm=norm,
extent=(t1,t2,dp1,dp2)
)
handle.xaxis.set_major_locator(dts.HourLocator(interval=time_reso))
handle.xaxis.set_major_formatter(dts.DateFormatter(time_formatter))
plt.setp(handle.get_xticklabels(),rotation=80)
box = handle.get_position()
c_handle = plt.axes([box.x0*1.025 + box.width * 1.025, box.y0, 0.01, box.height])
cbar = plt.colorbar(img,cax=c_handle,ticks=color_ticks)
handle.set_ylabel('Dp, [m]')
handle.set_xlabel('Time')
cbar.set_label('dN/dlogDp, [cm-3]')
if ax is None:
plt.show()
def dndlogdp2dn(df):
"""
Convert from normalized number concentrations to
unnormalized number concentrations assuming that
the size channels have common edges.
Parameters
----------
df : pandas.DataFrame
Aerosol number-size distribution (dN/dlogDp)
Returns
-------
pandas.DataFrame
Aerosol number size distribution (dN)
"""
logdp_mid = np.log10(df.columns.values.astype(float))
logdp = (logdp_mid[:-1]+logdp_mid[1:])/2.0
logdp = np.append(logdp,logdp_mid.max()+(logdp_mid.max()-logdp.max()))
logdp = np.insert(logdp,0,logdp_mid.min()-(logdp.min()-logdp_mid.min()))
dlogdp = np.diff(logdp)
return df*dlogdp
def air_viscosity(temp):
"""
Calculate air viscosity
using Enskog-Chapman theory
Parameters
----------
temp : float or numpy.array
air temperature, unit: K
Returns
-------
float or numpy.array
viscosity of air, unit: m2 s-1
"""
nyy_ref=18.203e-6
S=110.4
temp_ref=293.15
return nyy_ref*((temp_ref+S)/(temp+S))*((temp/temp_ref)**(3./2.))
def mean_free_path(temp,pres):
"""
Calculate mean free path in air
Parameters
----------
temp : float or numpy.array
air temperature, unit: K
pres : float or numpy.array
air pressure, unit: Pa
Returns
-------
float or numpy.array
mean free path in air, unit: m
"""
R=8.3143
Mair=0.02897
mu=air_viscosity(temp)
return (mu/pres)*((np.pi*R*temp)/(2.*Mair))**0.5
def slipcorr(dp,temp,pres):
"""
Slip correction factor in air
Parameters
----------
dp : float or numpy array (m,)
particle diameter, unit m
temp : float or numpy.array (n,1)
air temperature, unit K
pres : float or numpy.array (n,1)
air pressure, unit Pa
Returns
-------
float or numpy.array (m,) or (n,m)
Cunningham slip correction factor for each particle diameter,
if temperature and pressure and arrays then for each particle
diameter at different pressure/temperature values.
unit dimensionless
"""
l = mean_free_path(temp,pres)
return 1.+((2.*l)/dp)*(1.257+0.4*np.exp(-(1.1*dp)/(2.*l)))
def particle_diffusivity(dp,temp,pres):
"""
Particle brownian diffusivity in air
Parameters
----------
dp : float or numpy.array (m,)
particle diameter, unit: m
temp : float or numpy.array (n,1)
air temperature, unit: K
pres : float or numpy.array (n,1)
air pressure, unit: Pa
Returns
-------
float or numpy.array (m,) or (n,m)
Brownian diffusivity in air for particles of size dp,
and at each temperature/pressure value
unit m2 s-1
"""
k=1.381e-23
cc=slipcorr(dp,temp,pres)
mu=air_viscosity(temp)
return (k*temp*cc)/(3.*np.pi*mu*dp)
def particle_thermal_speed(dp,temp):
"""
Particle thermal speed
Parameters
----------
dp : float or numpy.array (m,)
particle diameter, unit: m
temp : float or numpy.array (n,1)
air temperature, unit: K
Returns
-------
float or numpy.array (m,) or (n,m)
Particle thermal speed for each dp at each temperature
point, unit: m s-1
"""
k=1.381e-23
rho_p=1000.0
mp=rho_p*(1./6.)*np.pi*dp**3.
return ((8.*k*temp)/(np.pi*mp))**(1./2.)
def particle_mean_free_path(dp,temp,pres):
"""
Particle mean free path in air
Parameters
----------
dp : float or numpy.array (m,)
particle diameter, unit: m
temp : float or numpy.array (n,1)
air temperature, unit: K
pres : float or numpy.array (n,1)
air pressure, unit: Pa
Returns
-------
float or numpy.array (m,) or (n,m)
Particle mean free path for each dp, unit: m
"""
D=particle_diffusivity(dp,temp,pres)
c=particle_thermal_speed(dp,temp)
return (8.*D)/(np.pi*c)
def coagulation_coef(dp1,dp2,temp,pres):
"""
Calculate Brownian coagulation coefficient (Fuchs)
Parameters
----------
dp1 : float or numpy.array (m,)
first particle diameter, unit: m
dp2 : float or numpy.array (m,)
second particle diameter, unit: m
temp : float or numpy.array (n,1)
air temperature, unit: K
pres : float or numpy.array (n,1)
air pressure, unit: Pa
Returns
-------
float or numpy.array
Brownian coagulation coefficient (Fuchs),
for example if all parameters are arrays
the function returns a 2d array where
the entry at i,j correspoinds to the
coagulation coefficient for particle sizes
dp1[i] and dp2[i] at temp[j] and pres[j].
unit m3 s-1
"""
def particle_g(dp,temp,pres):
l = particle_mean_free_path(dp,temp,pres)
return 1./(3.*dp*l)*((dp+l)**3.-(dp**2.+l**2.)**(3./2.))-dp
D1 = particle_diffusivity(dp1,temp,pres)
D2 = particle_diffusivity(dp2,temp,pres)
g1 = particle_g(dp1,temp,pres)
g2 = particle_g(dp2,temp,pres)
c1 = particle_thermal_speed(dp1,temp)
c2 = particle_thermal_speed(dp2,temp)
return 2.*np.pi*(D1+D2)*(dp1+dp2) \
* ( (dp1+dp2)/(dp1+dp2+2.*(g1**2.+g2**2.)**0.5) + \
+ (8.*(D1+D2))/((c1**2.+c2**2.)**0.5*(dp1+dp2)) )
def calc_coags(df,Dp,dp,temp,pres):
"""
Calculate coagulation sink
Kulmala et al (2012): doi:10.1038/nprot.2012.091
Parameters
----------
df : pandas.DataFrame
Aerosol number size distribution
Dp : float
Particle diameter for which you want to calculate the CoagS,
unit: m
temp : pandas.DataFrame
Ambient temperature timeseries
unit: K
pres : pandas.DataFrame
Ambient pressure timeseries
unit: Pa
Returns
-------
pandas.DataFrame
Coagulation sink time series,
unit: s-1
"""
df = df.loc[:,df.columns>=Dp]
temp = temp.reindex(df.index, method="nearest")
pres = pres.reindex(df.index, method="nearest")
a = dndlogdp2dn(df)
b = 1e6*coagulation_coef(Dp,df.columns.values,temp.values,pres.values)
return (a*b).sum(axis=1,min_count=1)
def diam2mob(dp,temp,pres,ne):
"""
Convert electrical mobility diameter to electrical mobility in air
Parameters
----------
dp : float or numpy.array (m,)
particle diameter(s),
unit : m
temp : float or numpy.array (n,1)
ambient temperature,
unit: K
pres : float or numpy.array (n,1)
ambient pressure,
unit: Pa
ne : int
number of charges on the aerosol particle
Returns
-------
float or numpy.array
particle electrical mobility or mobilities,
unit: m2 s-1 V-1
"""
e = 1.60217662e-19
cc = slipcorr(dp,temp,pres)
mu = air_viscosity(temp)
Zp = (ne*e*cc)/(3.*np.pi*mu*dp)
return Zp
def mob2diam(Zp,temp,pres,ne):
"""
Convert electrical mobility to electrical mobility diameter in air
Parameters
----------
Zp : float
particle electrical mobility or mobilities,
unit: m2 s-1 V-1
temp : float
ambient temperature,
unit: K
pres : float
ambient pressure,
unit: Pa
ne : integer
number of charges on the aerosol particle
Returns
-------
float
particle diameter, unit: m
"""
def minimize_this(dp,Z):
return np.abs(diam2mob(dp,temp,pres,ne)-Z)
dp0 = 0.0001
result = minimize(minimize_this, dp0, args=(Zp,), tol=1e-20, method='Nelder-Mead').x[0]
return result
def binary_diffusivity(temp,pres,Ma,Mb,Va,Vb):
"""
Binary diffusivity in a mixture of gases a and b
Fuller et al. (1966): https://doi.org/10.1021/ie50677a007
Parameters
----------
temp : float or numpy.array
temperature,
unit: K
pres : float or numpy.array
pressure,
unit: Pa
Ma : float
relative molecular mass of gas a,
unit: dimensionless
Mb : float
relative molecular mass of gas b,
unit: dimensionless
Va : float
diffusion volume of gas a,
unit: dimensionless
Vb : float
diffusion volume of gas b,
unit: dimensionless
Returns
-------
float or numpy.array
binary diffusivity,
unit: m2 s-1
"""
diffusivity = (1.013e-2*(temp**1.75)*np.sqrt((1./Ma)+(1./Mb)))/(pres*(Va**(1./3.)+Vb**(1./3.))**2)
return diffusivity
def beta(dp,temp,pres,diffusivity,molar_mass):
"""
Calculate Fuchs Sutugin correction factor
Sutugin et al. (1971): https://doi.org/10.1016/0021-8502(71)90061-9
Parameters
----------
dp : float or numpy.array (m,)
aerosol particle diameter(s),
unit: m
temp : float or numpy.array (n,1)
temperature,
unit: K
pres : float or numpy.array (n,1)
pressure,
unit: Pa
diffusivity : float or numpy.array (n,1)
diffusivity of the gas that is condensing,
unit: m2/s
molar_mass : float
molar mass of the condensing gas,
unit: g/mol
Returns
-------
float or numpy.array
Fuchs Sutugin correction factor for each particle diameter and
temperature/pressure
unit: m2/s
"""
R = 8.314
l = 3.*diffusivity/((8.*R*temp)/(np.pi*molar_mass*0.001))**0.5
knud = 2.*l/dp
return (1. + knud)/(1. + 1.677*knud + 1.333*knud**2)
def calc_cs(df,temp,pres):
"""
Calculate condensation sink, assuming that the condensing gas is sulfuric acid in air
with aerosol particles.
Kulmala et al (2012): doi:10.1038/nprot.2012.091
Parameters
----------
df : pandas.DataFrame
aerosol number size distribution (dN/dlogDp)
temp : pandas.DataFrame or float
Ambient temperature corresponding to the data, unit: K
If single value given it is used for all data
pres : pandas.DataFrame or float
Ambient pressure corresponding to the data, unit: Pa
If single value given it is used for all data
Returns
-------
pandas.DataFrame
condensation sink time series, unit: s-1
"""
if isinstance(temp,float):
temp = pd.DataFrame(index = df.index, columns=["Temperature"], data=temp)
else:
temp = temp.reindex(df.index, method="nearest")
if isinstance(pres,float):
pres = pd.DataFrame(index = df.index, columns=["Pressure"], data=pres)
else:
pres = pres.reindex(df.index, method="nearest")
M_h2so4 = 98.08
M_air = 28.965
V_air = 19.7
V_h2so4 = 51.96
dn = dndlogdp2dn(df)
dp = df.columns.values.astype(float)
diffu = binary_diffusivity(temp.values,pres.values,M_h2so4,M_air,V_h2so4,V_air)
b = beta(dp,temp.values,pres.values,diffu,M_h2so4)
df2 = (1e6*dn*(b*dp)).sum(axis=1,min_count=1)
cs = (4.*np.pi*diffu.flatten())*df2.values
return pd.DataFrame(index=df.index,columns=["CS"],data=cs)
def calc_conc(df,dmin,dmax):
"""
Calculate particle number concentration from aerosol
number-size distribution
Parameters
----------
df : pandas.DataFrame
Aerosol number-size distribution
dmin : float
Size range lower diameter, unit: m
dmax : float
Size range upper diameter, unit: m
Returns
-------
pandas.DataFrame
Number concentration in the given size range, unit: cm-3
"""
dp = df.columns.values.astype(float)
findex=np.argwhere((dp<=dmax)&(dp>=dmin)).flatten()
if len(findex)==0:
return np.nan*np.ones(df.shape[0])
dp=dp[findex]
conc=df.iloc[:,findex]
logdp_mid=np.log10(dp)
logdp=(logdp_mid[:-1]+logdp_mid[1:])/2.0
logdp=np.append(logdp,logdp_mid.max()+(logdp_mid.max()-logdp.max()))
logdp=np.insert(logdp,0,logdp_mid.min()-(logdp.min()-logdp_mid.min()))
dlogdp=np.diff(logdp)
conc = np.nansum(conc*dlogdp,axis=1)
return pd.DataFrame(index=df.index,data=conc,columns=["conc"])
# dp = df.columns.values.astype(float)
# df2 = df.loc[:,((dp>=dmin) & (dp<=dmax))]
# dp2 = df2.columns.values.astype(float)
# df2.columns = np.log10(dp2)
# dense_dp = np.full(len(df2.columns)*10,np.nan)[:-9]
# dense_dp[::10] = df2.columns.values
# dense_dp = pd.DataFrame(dense_dp).interpolate().values.flatten()
# df3 = df2.reindex(dense_dp,axis=1)
# df4 = df3.interpolate(axis=1)
# conc = df4.apply(trapezoid,args=(df4.columns,),axis=1)
# return pd.DataFrame(index = df.index, data = conc)
def calc_formation_rate(dp1,dp2,conc,coags,gr):
"""
Calculate particle formation rate
Kulmala et al (2012): doi:10.1038/nprot.2012.091
Parameters
----------
dp1 : float
Lower diameter of the size range, unit: m
dp2 : float
Upper diameter of the size range, unit m
conc : pandas.DataFrame
particle number concentration timeseries
in the size range dp1...dp2, unit cm-3
coags : pandas.DataFrame
Coagulation sink timeseries for particles
in the size range dp1...dp2. unit s-1
Usually approximated as coagulation sink for particle size dp1,
unit s-1
gr : float
Growth rate for particles out of the size range dp1...dp2,
unit nm h-1
Returns
-------
pandas.DataFrame
particle formation rate for diameter dp1, unit: cm3 s-1
"""
# Fit the coags to the conc index
coags = coags.reindex(conc.index,method="nearest")
time_diff = pd.DataFrame(conc.index).diff().astype('datetime64[s]').values
conc_term = np.diff(conc.values)/time_diff
sink_term = (coags.values[1:] + coags.values[:-1])/2. * (conc.values[1:] + conc.values[:-1])/2.
gr_term = (2.778e-13*gr)/(dp2-dp1) * (conc.values[1:] + conc.values[:-1])/2.
formation_rate = conc_term + sink_term + gr_term
return pd.DataFrame(index = conc.index, data = formation_rate)
def calc_ion_formation_rate(
time,
dp1,
dp2,
conc_pos,
conc_neg,
conc_pos_small,
conc_neg_small,
conc,
coags,
gr):
"""
Calculate ion formation rate
Kulmala et al (2012): doi:10.1038/nprot.2012.091
Parameters
----------
time : numpy.array of datetime objects
Time associated with the measurements
dp1 : float
Lower diameter of the size range, unit: m
dp2 : float
Upper diameter of the size range, unit: m
conc_pos : numpy.array
Positive ion number concentration in the size range dp1...dp2, unit: cm-3
conc_neg : numpy.array
Negative ion number concentration in the size range dp1...dp2, unit: cm-3
conc_pos_small : numpy.array
Positive ion number concentration for ions smaller than dp1, unit: cm-3
conc_neg_small : numpy.array
Negative ion number concentration for ions smaller than dp1, unit: cm-3
conc : numpy.array
Particle number concentration in the size range dp1...dp2, unit: cm-3
coags : numpy.array
Coagulation sink for particles in the size range dp1...dp2.
Usually approximated as coagulation sink for particle size dp1,
unit: s-1
gr : float
Growth rate for particles out of the size range dp1...dp2, unit: nm h-1
Returns
-------
pandas.DataFrame
Time as index
Negative ion formation rate for diameter dp1, unit : cm3 s-1
pandas.DataFrame
Time as index
Positive ion formation rate for diameter dp1, unit: cm3 s-1
"""
# Reindex everything to conc_neg
coags = coags.reindex(conc_neg.index,method="nearest")
conc_pos = conc_pos.reindex(conc_neg.index,method="nearest")
conc = conc.reindex(conc_neg.index,method="nearest")
conc_neg_small = conc_neg_small.reindex(conc_neg.index,method="nearest")
conc_pos_small = conc_pos_small.reindex(conc_neg.index,method="nearest")
time_diff=pd.DataFrame(conc_neg.index).diff().astype('datetime64[s]').values
alpha = 1.6e-6 # cm3 s-1
Xi = 0.01e-6 # cm3 s-1
coags = (coags[1:] + coags[:-1])/2.
conc_pos = (conc_pos[1:] + conc_pos[:-1])/2.
conc_neg = (conc_neg[1:] + conc_neg[:-1])/2.
conc_pos_small = (conc_pos_small[1:] + conc_pos_small[:-1])/2.
conc_neg_small = (conc_neg_small[1:] + conc_neg_small[:-1])/2.
conc = (conc[1:] + conc[:-1])/2.
pos_conc_term = np.diff(conc_pos)/time_diff
pos_sink_term = coags * conc_pos
pos_gr_term = (2.778e-13*gr)/(dp2-dp1) * conc_pos
pos_recombination_term = alpha * conc_pos * conc_neg_small
pos_charging_term = Xi * conc * conc_pos_small
pos_formation_rate = pos_conc_term + pos_sink_term + pos_gr_term + pos_recombination_term - pos_charging_term
neg_conc_term = np.diff(conc_neg)/time_diff
neg_sink_term = coags * conc_neg
neg_gr_term = (2.778e-13*gr)/(dp2-dp1) * conc_neg
neg_recombination_term = alpha * conc_neg * conc_pos_small
neg_charging_term = Xi * conc * conc_neg_small
neg_formation_rate = neg_conc_term + neg_sink_term + neg_gr_term + neg_recombination_term - neg_charging_term
neg_J = pd.DataFrame(index = time, data = neg_formation_rate)
pos_J = pd.DataFrame(index = time, data = pos_formation_rate)
return neg_J, pos_J
def stack_data(filenames,t_min,t_max,reso):
"""
Concatenate data
If multiple data tables share the same header
and have time as index then this function can be
used to concatenate the data into a single large
data frame.
Parameters
----------
filenames : list of strings
filenames of the data to be combined
t_min : datetime or str
t_max : datetime or str
reso : int or str
desired time resolution of the output in minutes
or pandas time offset alias
Returns
-------
pandas.DataFrame
stacked data
"""
df_combined = pd.DataFrame([])
for f in filenames:
print(f)
df = pd.read_csv(f,index_col=0,parse_dates=True)
df_combined = pd.concat((df_combined,df),axis=0)
if len(df_combined)==0:
return df_combined
else:
df_combined = df_combined.sort_index()
df_combined = bin_df(df_combined,t_min,t_max,reso)
return df_combined
Functions
def air_viscosity(temp)
-
Calculate air viscosity using Enskog-Chapman theory
Parameters
temp
:float
ornumpy.array
- air temperature, unit: K
Returns
float
ornumpy.array
- viscosity of air, unit: m2 s-1
Expand source code
def air_viscosity(temp): """ Calculate air viscosity using Enskog-Chapman theory Parameters ---------- temp : float or numpy.array air temperature, unit: K Returns ------- float or numpy.array viscosity of air, unit: m2 s-1 """ nyy_ref=18.203e-6 S=110.4 temp_ref=293.15 return nyy_ref*((temp_ref+S)/(temp+S))*((temp/temp_ref)**(3./2.))
def beta(dp, temp, pres, diffusivity, molar_mass)
-
Calculate Fuchs Sutugin correction factor
Sutugin et al. (1971): https://doi.org/10.1016/0021-8502(71)90061-9
Parameters
dp
:float
ornumpy.array (m,)
- aerosol particle diameter(s), unit: m
temp
:float
ornumpy.array (n,1)
- temperature, unit: K
pres
:float
ornumpy.array (n,1)
- pressure, unit: Pa
diffusivity
:float
ornumpy.array (n,1)
- diffusivity of the gas that is condensing, unit: m2/s
molar_mass
:float
- molar mass of the condensing gas, unit: g/mol
Returns
float
ornumpy.array
- Fuchs Sutugin correction factor for each particle diameter and temperature/pressure unit: m2/s
Expand source code
def beta(dp,temp,pres,diffusivity,molar_mass): """ Calculate Fuchs Sutugin correction factor Sutugin et al. (1971): https://doi.org/10.1016/0021-8502(71)90061-9 Parameters ---------- dp : float or numpy.array (m,) aerosol particle diameter(s), unit: m temp : float or numpy.array (n,1) temperature, unit: K pres : float or numpy.array (n,1) pressure, unit: Pa diffusivity : float or numpy.array (n,1) diffusivity of the gas that is condensing, unit: m2/s molar_mass : float molar mass of the condensing gas, unit: g/mol Returns ------- float or numpy.array Fuchs Sutugin correction factor for each particle diameter and temperature/pressure unit: m2/s """ R = 8.314 l = 3.*diffusivity/((8.*R*temp)/(np.pi*molar_mass*0.001))**0.5 knud = 2.*l/dp return (1. + knud)/(1. + 1.677*knud + 1.333*knud**2)
def bin_df(df, t_min, t_max, reso, q=0.5)
-
Utility function for binning timeseries data
Parameters
df
:pandas.DataFrame
-
Aerosol number size distribution
df.index
time
df.columns
particle diameter (m)df.values
normalized concentrations (dN/dlogDp) t_min
:datetime
orstr
- first bin lower limit
t_max
:datetime
orstr
- last bin upper limit
reso
:int
orstr
- desired time resolution in minutes or pandas time offset alias
q
:float
-
quintile of data calculated per bin
default is the median (0.5)
Returns
pandas.DataFrame
-
Binned aerosol number size distribution
All bins have constant width determined by reso and they share edges. If a bin has no values it is given a value of
NaN
Expand source code
def bin_df(df, t_min, t_max, reso, q=0.5): """ Utility function for binning timeseries data Parameters ---------- df : pandas.DataFrame Aerosol number size distribution `df.index` time `df.columns` particle diameter (m) `df.values` normalized concentrations (dN/dlogDp) t_min : datetime or str first bin lower limit t_max : datetime or str last bin upper limit reso : int or str desired time resolution in minutes or pandas time offset alias q : float quintile of data calculated per bin default is the median (0.5) Returns ------- pandas.DataFrame Binned aerosol number size distribution All bins have constant width determined by reso and they share edges. If a bin has no values it is given a value of `NaN` """ if isinstance(reso,int): reso = pd.Timedelta(minutes=reso) if isinstance(reso,str): pass ix = pd.date_range(t_min,t_max,freq=reso) half_step = (ix[1] - ix[0])/2. data = [] index = [] for i in range(len(ix)-1): df_block = df.iloc[((df.index>=ix[i]) & (df.index<ix[i+1])),:].median().values.flatten() if len(df_block)==0: df_block = np.nan*np.ones(len(df.columns)) data.append(df_block) index.append(ix[i] + half_step) return pd.DataFrame(index = index, data = data, columns = df.columns)
def binary_diffusivity(temp, pres, Ma, Mb, Va, Vb)
-
Binary diffusivity in a mixture of gases a and b
Fuller et al. (1966): https://doi.org/10.1021/ie50677a007
Parameters
temp
:float
ornumpy.array
- temperature, unit: K
pres
:float
ornumpy.array
- pressure, unit: Pa
Ma
:float
- relative molecular mass of gas a, unit: dimensionless
Mb
:float
- relative molecular mass of gas b, unit: dimensionless
Va
:float
- diffusion volume of gas a, unit: dimensionless
Vb
:float
- diffusion volume of gas b, unit: dimensionless
Returns
float
ornumpy.array
- binary diffusivity, unit: m2 s-1
Expand source code
def binary_diffusivity(temp,pres,Ma,Mb,Va,Vb): """ Binary diffusivity in a mixture of gases a and b Fuller et al. (1966): https://doi.org/10.1021/ie50677a007 Parameters ---------- temp : float or numpy.array temperature, unit: K pres : float or numpy.array pressure, unit: Pa Ma : float relative molecular mass of gas a, unit: dimensionless Mb : float relative molecular mass of gas b, unit: dimensionless Va : float diffusion volume of gas a, unit: dimensionless Vb : float diffusion volume of gas b, unit: dimensionless Returns ------- float or numpy.array binary diffusivity, unit: m2 s-1 """ diffusivity = (1.013e-2*(temp**1.75)*np.sqrt((1./Ma)+(1./Mb)))/(pres*(Va**(1./3.)+Vb**(1./3.))**2) return diffusivity
def calc_coags(df, Dp, dp, temp, pres)
-
Calculate coagulation sink
Kulmala et al (2012): doi:10.1038/nprot.2012.091
Parameters
df
:pandas.DataFrame
- Aerosol number size distribution
Dp
:float
- Particle diameter for which you want to calculate the CoagS, unit: m
temp
:pandas.DataFrame
- Ambient temperature timeseries unit: K
pres
:pandas.DataFrame
- Ambient pressure timeseries unit: Pa
Returns
pandas.DataFrame
- Coagulation sink time series, unit: s-1
Expand source code
def calc_coags(df,Dp,dp,temp,pres): """ Calculate coagulation sink Kulmala et al (2012): doi:10.1038/nprot.2012.091 Parameters ---------- df : pandas.DataFrame Aerosol number size distribution Dp : float Particle diameter for which you want to calculate the CoagS, unit: m temp : pandas.DataFrame Ambient temperature timeseries unit: K pres : pandas.DataFrame Ambient pressure timeseries unit: Pa Returns ------- pandas.DataFrame Coagulation sink time series, unit: s-1 """ df = df.loc[:,df.columns>=Dp] temp = temp.reindex(df.index, method="nearest") pres = pres.reindex(df.index, method="nearest") a = dndlogdp2dn(df) b = 1e6*coagulation_coef(Dp,df.columns.values,temp.values,pres.values) return (a*b).sum(axis=1,min_count=1)
def calc_conc(df, dmin, dmax)
-
Calculate particle number concentration from aerosol number-size distribution
Parameters
df
:pandas.DataFrame
- Aerosol number-size distribution
dmin
:float
- Size range lower diameter, unit: m
dmax
:float
- Size range upper diameter, unit: m
Returns
pandas.DataFrame
- Number concentration in the given size range, unit: cm-3
Expand source code
def calc_conc(df,dmin,dmax): """ Calculate particle number concentration from aerosol number-size distribution Parameters ---------- df : pandas.DataFrame Aerosol number-size distribution dmin : float Size range lower diameter, unit: m dmax : float Size range upper diameter, unit: m Returns ------- pandas.DataFrame Number concentration in the given size range, unit: cm-3 """ dp = df.columns.values.astype(float) findex=np.argwhere((dp<=dmax)&(dp>=dmin)).flatten() if len(findex)==0: return np.nan*np.ones(df.shape[0]) dp=dp[findex] conc=df.iloc[:,findex] logdp_mid=np.log10(dp) logdp=(logdp_mid[:-1]+logdp_mid[1:])/2.0 logdp=np.append(logdp,logdp_mid.max()+(logdp_mid.max()-logdp.max())) logdp=np.insert(logdp,0,logdp_mid.min()-(logdp.min()-logdp_mid.min())) dlogdp=np.diff(logdp) conc = np.nansum(conc*dlogdp,axis=1) return pd.DataFrame(index=df.index,data=conc,columns=["conc"])
def calc_cs(df, temp, pres)
-
Calculate condensation sink, assuming that the condensing gas is sulfuric acid in air with aerosol particles.
Kulmala et al (2012): doi:10.1038/nprot.2012.091
Parameters
df
:pandas.DataFrame
- aerosol number size distribution (dN/dlogDp)
temp
:pandas.DataFrame
orfloat
- Ambient temperature corresponding to the data, unit: K If single value given it is used for all data
pres
:pandas.DataFrame
orfloat
- Ambient pressure corresponding to the data, unit: Pa If single value given it is used for all data
Returns
pandas.DataFrame
- condensation sink time series, unit: s-1
Expand source code
def calc_cs(df,temp,pres): """ Calculate condensation sink, assuming that the condensing gas is sulfuric acid in air with aerosol particles. Kulmala et al (2012): doi:10.1038/nprot.2012.091 Parameters ---------- df : pandas.DataFrame aerosol number size distribution (dN/dlogDp) temp : pandas.DataFrame or float Ambient temperature corresponding to the data, unit: K If single value given it is used for all data pres : pandas.DataFrame or float Ambient pressure corresponding to the data, unit: Pa If single value given it is used for all data Returns ------- pandas.DataFrame condensation sink time series, unit: s-1 """ if isinstance(temp,float): temp = pd.DataFrame(index = df.index, columns=["Temperature"], data=temp) else: temp = temp.reindex(df.index, method="nearest") if isinstance(pres,float): pres = pd.DataFrame(index = df.index, columns=["Pressure"], data=pres) else: pres = pres.reindex(df.index, method="nearest") M_h2so4 = 98.08 M_air = 28.965 V_air = 19.7 V_h2so4 = 51.96 dn = dndlogdp2dn(df) dp = df.columns.values.astype(float) diffu = binary_diffusivity(temp.values,pres.values,M_h2so4,M_air,V_h2so4,V_air) b = beta(dp,temp.values,pres.values,diffu,M_h2so4) df2 = (1e6*dn*(b*dp)).sum(axis=1,min_count=1) cs = (4.*np.pi*diffu.flatten())*df2.values return pd.DataFrame(index=df.index,columns=["CS"],data=cs)
def calc_formation_rate(dp1, dp2, conc, coags, gr)
-
Calculate particle formation rate
Kulmala et al (2012): doi:10.1038/nprot.2012.091
Parameters
dp1
:float
- Lower diameter of the size range, unit: m
dp2
:float
- Upper diameter of the size range, unit m
conc
:pandas.DataFrame
- particle number concentration timeseries in the size range dp1…dp2, unit cm-3
coags
:pandas.DataFrame
-
Coagulation sink timeseries for particles in the size range dp1…dp2. unit s-1
Usually approximated as coagulation sink for particle size dp1, unit s-1
gr
:float
- Growth rate for particles out of the size range dp1…dp2, unit nm h-1
Returns
pandas.DataFrame
- particle formation rate for diameter dp1, unit: cm3 s-1
Expand source code
def calc_formation_rate(dp1,dp2,conc,coags,gr): """ Calculate particle formation rate Kulmala et al (2012): doi:10.1038/nprot.2012.091 Parameters ---------- dp1 : float Lower diameter of the size range, unit: m dp2 : float Upper diameter of the size range, unit m conc : pandas.DataFrame particle number concentration timeseries in the size range dp1...dp2, unit cm-3 coags : pandas.DataFrame Coagulation sink timeseries for particles in the size range dp1...dp2. unit s-1 Usually approximated as coagulation sink for particle size dp1, unit s-1 gr : float Growth rate for particles out of the size range dp1...dp2, unit nm h-1 Returns ------- pandas.DataFrame particle formation rate for diameter dp1, unit: cm3 s-1 """ # Fit the coags to the conc index coags = coags.reindex(conc.index,method="nearest") time_diff = pd.DataFrame(conc.index).diff().astype('datetime64[s]').values conc_term = np.diff(conc.values)/time_diff sink_term = (coags.values[1:] + coags.values[:-1])/2. * (conc.values[1:] + conc.values[:-1])/2. gr_term = (2.778e-13*gr)/(dp2-dp1) * (conc.values[1:] + conc.values[:-1])/2. formation_rate = conc_term + sink_term + gr_term return pd.DataFrame(index = conc.index, data = formation_rate)
def calc_ion_formation_rate(time, dp1, dp2, conc_pos, conc_neg, conc_pos_small, conc_neg_small, conc, coags, gr)
-
Calculate ion formation rate
Kulmala et al (2012): doi:10.1038/nprot.2012.091
Parameters
time
:numpy.array
ofdatetime objects
- Time associated with the measurements
dp1
:float
- Lower diameter of the size range, unit: m
dp2
:float
- Upper diameter of the size range, unit: m
conc_pos
:numpy.array
- Positive ion number concentration in the size range dp1…dp2, unit: cm-3
conc_neg
:numpy.array
- Negative ion number concentration in the size range dp1…dp2, unit: cm-3
conc_pos_small
:numpy.array
- Positive ion number concentration for ions smaller than dp1, unit: cm-3
conc_neg_small
:numpy.array
- Negative ion number concentration for ions smaller than dp1, unit: cm-3
conc
:numpy.array
- Particle number concentration in the size range dp1…dp2, unit: cm-3
coags
:numpy.array
- Coagulation sink for particles in the size range dp1…dp2. Usually approximated as coagulation sink for particle size dp1, unit: s-1
gr
:float
- Growth rate for particles out of the size range dp1…dp2, unit: nm h-1
Returns
pandas.DataFrame
- Time as index Negative ion formation rate for diameter dp1, unit : cm3 s-1
pandas.DataFrame
- Time as index Positive ion formation rate for diameter dp1, unit: cm3 s-1
Expand source code
def calc_ion_formation_rate( time, dp1, dp2, conc_pos, conc_neg, conc_pos_small, conc_neg_small, conc, coags, gr): """ Calculate ion formation rate Kulmala et al (2012): doi:10.1038/nprot.2012.091 Parameters ---------- time : numpy.array of datetime objects Time associated with the measurements dp1 : float Lower diameter of the size range, unit: m dp2 : float Upper diameter of the size range, unit: m conc_pos : numpy.array Positive ion number concentration in the size range dp1...dp2, unit: cm-3 conc_neg : numpy.array Negative ion number concentration in the size range dp1...dp2, unit: cm-3 conc_pos_small : numpy.array Positive ion number concentration for ions smaller than dp1, unit: cm-3 conc_neg_small : numpy.array Negative ion number concentration for ions smaller than dp1, unit: cm-3 conc : numpy.array Particle number concentration in the size range dp1...dp2, unit: cm-3 coags : numpy.array Coagulation sink for particles in the size range dp1...dp2. Usually approximated as coagulation sink for particle size dp1, unit: s-1 gr : float Growth rate for particles out of the size range dp1...dp2, unit: nm h-1 Returns ------- pandas.DataFrame Time as index Negative ion formation rate for diameter dp1, unit : cm3 s-1 pandas.DataFrame Time as index Positive ion formation rate for diameter dp1, unit: cm3 s-1 """ # Reindex everything to conc_neg coags = coags.reindex(conc_neg.index,method="nearest") conc_pos = conc_pos.reindex(conc_neg.index,method="nearest") conc = conc.reindex(conc_neg.index,method="nearest") conc_neg_small = conc_neg_small.reindex(conc_neg.index,method="nearest") conc_pos_small = conc_pos_small.reindex(conc_neg.index,method="nearest") time_diff=pd.DataFrame(conc_neg.index).diff().astype('datetime64[s]').values alpha = 1.6e-6 # cm3 s-1 Xi = 0.01e-6 # cm3 s-1 coags = (coags[1:] + coags[:-1])/2. conc_pos = (conc_pos[1:] + conc_pos[:-1])/2. conc_neg = (conc_neg[1:] + conc_neg[:-1])/2. conc_pos_small = (conc_pos_small[1:] + conc_pos_small[:-1])/2. conc_neg_small = (conc_neg_small[1:] + conc_neg_small[:-1])/2. conc = (conc[1:] + conc[:-1])/2. pos_conc_term = np.diff(conc_pos)/time_diff pos_sink_term = coags * conc_pos pos_gr_term = (2.778e-13*gr)/(dp2-dp1) * conc_pos pos_recombination_term = alpha * conc_pos * conc_neg_small pos_charging_term = Xi * conc * conc_pos_small pos_formation_rate = pos_conc_term + pos_sink_term + pos_gr_term + pos_recombination_term - pos_charging_term neg_conc_term = np.diff(conc_neg)/time_diff neg_sink_term = coags * conc_neg neg_gr_term = (2.778e-13*gr)/(dp2-dp1) * conc_neg neg_recombination_term = alpha * conc_neg * conc_pos_small neg_charging_term = Xi * conc * conc_neg_small neg_formation_rate = neg_conc_term + neg_sink_term + neg_gr_term + neg_recombination_term - neg_charging_term neg_J = pd.DataFrame(index = time, data = neg_formation_rate) pos_J = pd.DataFrame(index = time, data = pos_formation_rate) return neg_J, pos_J
def coagulation_coef(dp1, dp2, temp, pres)
-
Calculate Brownian coagulation coefficient (Fuchs)
Parameters
dp1
:float
ornumpy.array (m,)
- first particle diameter, unit: m
dp2
:float
ornumpy.array (m,)
- second particle diameter, unit: m
temp
:float
ornumpy.array (n,1)
- air temperature, unit: K
pres
:float
ornumpy.array (n,1)
- air pressure, unit: Pa
Returns
float
ornumpy.array
-
Brownian coagulation coefficient (Fuchs),
for example if all parameters are arrays the function returns a 2d array where the entry at i,j correspoinds to the coagulation coefficient for particle sizes dp1[i] and dp2[i] at temp[j] and pres[j].
unit m3 s-1
Expand source code
def coagulation_coef(dp1,dp2,temp,pres): """ Calculate Brownian coagulation coefficient (Fuchs) Parameters ---------- dp1 : float or numpy.array (m,) first particle diameter, unit: m dp2 : float or numpy.array (m,) second particle diameter, unit: m temp : float or numpy.array (n,1) air temperature, unit: K pres : float or numpy.array (n,1) air pressure, unit: Pa Returns ------- float or numpy.array Brownian coagulation coefficient (Fuchs), for example if all parameters are arrays the function returns a 2d array where the entry at i,j correspoinds to the coagulation coefficient for particle sizes dp1[i] and dp2[i] at temp[j] and pres[j]. unit m3 s-1 """ def particle_g(dp,temp,pres): l = particle_mean_free_path(dp,temp,pres) return 1./(3.*dp*l)*((dp+l)**3.-(dp**2.+l**2.)**(3./2.))-dp D1 = particle_diffusivity(dp1,temp,pres) D2 = particle_diffusivity(dp2,temp,pres) g1 = particle_g(dp1,temp,pres) g2 = particle_g(dp2,temp,pres) c1 = particle_thermal_speed(dp1,temp) c2 = particle_thermal_speed(dp2,temp) return 2.*np.pi*(D1+D2)*(dp1+dp2) \ * ( (dp1+dp2)/(dp1+dp2+2.*(g1**2.+g2**2.)**0.5) + \ + (8.*(D1+D2))/((c1**2.+c2**2.)**0.5*(dp1+dp2)) )
def datenum2datetime(datenum, tz=None)
-
Convert from matlab datenum to python datetime
Parameters
datenum
:float
- A serial date number representing the whole and fractional number of days from 1-Jan-0000 to a specific date (MATLAB datenum)
tz
:int
orNone
- Timezone offset in minutes from UTC
None
implies timezone unaware
Returns
pandas.Timestamp
Expand source code
def datenum2datetime(datenum,tz=None): """ Convert from matlab datenum to python datetime Parameters ---------- datenum : `float` A serial date number representing the whole and fractional number of days from 1-Jan-0000 to a specific date (MATLAB datenum) tz : `int` or `None` Timezone offset in minutes from UTC `None` implies timezone unaware Returns ------- `pandas.Timestamp` """ dt = (datetime.fromordinal(int(datenum)) + timedelta(days=datenum%1) - timedelta(days = 366)) if tz is not None: tz_offset = timezone(timedelta(minutes=tz)) dt = dt.replace(tzinfo=tz_offset) return pd.to_datetime(dt.isoformat())
def datetime2datenum(dt)
-
Convert from python datetime to matlab datenum
Parameters
dt
:datetime object
Returns
float
- A serial date number representing the whole and fractional number of days from 1-Jan-0000 to a specific date (MATLAB datenum)
Expand source code
def datetime2datenum(dt): """ Convert from python datetime to matlab datenum Parameters ---------- dt : datetime object Returns ------- float A serial date number representing the whole and fractional number of days from 1-Jan-0000 to a specific date (MATLAB datenum) """ ord = dt.toordinal() mdn = dt + timedelta(days = 366) frac = (dt-datetime(dt.year,dt.month,dt.day,0,0,0)).seconds / (24.0 * 60.0 * 60.0) return mdn.toordinal() + frac
def diam2mob(dp, temp, pres, ne)
-
Convert electrical mobility diameter to electrical mobility in air
Parameters
dp
:float
ornumpy.array (m,)
- particle diameter(s), unit : m
temp
:float
ornumpy.array (n,1)
- ambient temperature, unit: K
pres
:float
ornumpy.array (n,1)
- ambient pressure, unit: Pa
ne
:int
- number of charges on the aerosol particle
Returns
float
ornumpy.array
- particle electrical mobility or mobilities, unit: m2 s-1 V-1
Expand source code
def diam2mob(dp,temp,pres,ne): """ Convert electrical mobility diameter to electrical mobility in air Parameters ---------- dp : float or numpy.array (m,) particle diameter(s), unit : m temp : float or numpy.array (n,1) ambient temperature, unit: K pres : float or numpy.array (n,1) ambient pressure, unit: Pa ne : int number of charges on the aerosol particle Returns ------- float or numpy.array particle electrical mobility or mobilities, unit: m2 s-1 V-1 """ e = 1.60217662e-19 cc = slipcorr(dp,temp,pres) mu = air_viscosity(temp) Zp = (ne*e*cc)/(3.*np.pi*mu*dp) return Zp
def dndlogdp2dn(df)
-
Convert from normalized number concentrations to unnormalized number concentrations assuming that the size channels have common edges.
Parameters
df
:pandas.DataFrame
- Aerosol number-size distribution (dN/dlogDp)
Returns
pandas.DataFrame
- Aerosol number size distribution (dN)
Expand source code
def dndlogdp2dn(df): """ Convert from normalized number concentrations to unnormalized number concentrations assuming that the size channels have common edges. Parameters ---------- df : pandas.DataFrame Aerosol number-size distribution (dN/dlogDp) Returns ------- pandas.DataFrame Aerosol number size distribution (dN) """ logdp_mid = np.log10(df.columns.values.astype(float)) logdp = (logdp_mid[:-1]+logdp_mid[1:])/2.0 logdp = np.append(logdp,logdp_mid.max()+(logdp_mid.max()-logdp.max())) logdp = np.insert(logdp,0,logdp_mid.min()-(logdp.min()-logdp_mid.min())) dlogdp = np.diff(logdp) return df*dlogdp
def generate_log_ticks(min_exp, max_exp)
-
Generate ticks and ticklabels for log axis
Parameters:
min_exp : int The exponent in the smallest power of ten
max_exp : int The exponent in the largest power of ten
Returns:
numpy.array tick values
list of str tick labels for each power of ten
Expand source code
def generate_log_ticks(min_exp,max_exp): """ Generate ticks and ticklabels for log axis Parameters: ----------- min_exp : int The exponent in the smallest power of ten max_exp : int The exponent in the largest power of ten Returns: -------- numpy.array tick values list of str tick labels for each power of ten """ x=np.arange(1,10) y=np.arange(min_exp,max_exp).astype(float) log_ticks=[] log_tick_labels=[] for j in y: for i in x: log_ticks.append(np.log10(np.round(i*10**j,int(np.abs(j))))) if i==1: log_tick_labels.append("10$^{%d}$"%j) else: log_tick_labels.append('') log_ticks=np.array(log_ticks) return log_ticks,log_tick_labels
def mean_free_path(temp, pres)
-
Calculate mean free path in air
Parameters
temp
:float
ornumpy.array
- air temperature, unit: K
pres
:float
ornumpy.array
- air pressure, unit: Pa
Returns
float
ornumpy.array
- mean free path in air, unit: m
Expand source code
def mean_free_path(temp,pres): """ Calculate mean free path in air Parameters ---------- temp : float or numpy.array air temperature, unit: K pres : float or numpy.array air pressure, unit: Pa Returns ------- float or numpy.array mean free path in air, unit: m """ R=8.3143 Mair=0.02897 mu=air_viscosity(temp) return (mu/pres)*((np.pi*R*temp)/(2.*Mair))**0.5
def mob2diam(Zp, temp, pres, ne)
-
Convert electrical mobility to electrical mobility diameter in air
Parameters
Zp
:float
- particle electrical mobility or mobilities, unit: m2 s-1 V-1
temp
:float
- ambient temperature, unit: K
pres
:float
- ambient pressure, unit: Pa
ne
:integer
- number of charges on the aerosol particle
Returns
float
- particle diameter, unit: m
Expand source code
def mob2diam(Zp,temp,pres,ne): """ Convert electrical mobility to electrical mobility diameter in air Parameters ---------- Zp : float particle electrical mobility or mobilities, unit: m2 s-1 V-1 temp : float ambient temperature, unit: K pres : float ambient pressure, unit: Pa ne : integer number of charges on the aerosol particle Returns ------- float particle diameter, unit: m """ def minimize_this(dp,Z): return np.abs(diam2mob(dp,temp,pres,ne)-Z) dp0 = 0.0001 result = minimize(minimize_this, dp0, args=(Zp,), tol=1e-20, method='Nelder-Mead').x[0] return result
def particle_diffusivity(dp, temp, pres)
-
Particle brownian diffusivity in air
Parameters
dp
:float
ornumpy.array (m,)
- particle diameter, unit: m
temp
:float
ornumpy.array (n,1)
- air temperature, unit: K
pres
:float
ornumpy.array (n,1)
- air pressure, unit: Pa
Returns
float
ornumpy.array (m,)
or(n,m)
- Brownian diffusivity in air for particles of size dp, and at each temperature/pressure value unit m2 s-1
Expand source code
def particle_diffusivity(dp,temp,pres): """ Particle brownian diffusivity in air Parameters ---------- dp : float or numpy.array (m,) particle diameter, unit: m temp : float or numpy.array (n,1) air temperature, unit: K pres : float or numpy.array (n,1) air pressure, unit: Pa Returns ------- float or numpy.array (m,) or (n,m) Brownian diffusivity in air for particles of size dp, and at each temperature/pressure value unit m2 s-1 """ k=1.381e-23 cc=slipcorr(dp,temp,pres) mu=air_viscosity(temp) return (k*temp*cc)/(3.*np.pi*mu*dp)
def particle_mean_free_path(dp, temp, pres)
-
Particle mean free path in air
Parameters
dp
:float
ornumpy.array (m,)
- particle diameter, unit: m
temp
:float
ornumpy.array (n,1)
- air temperature, unit: K
pres
:float
ornumpy.array (n,1)
- air pressure, unit: Pa
Returns
float
ornumpy.array (m,)
or(n,m)
- Particle mean free path for each dp, unit: m
Expand source code
def particle_mean_free_path(dp,temp,pres): """ Particle mean free path in air Parameters ---------- dp : float or numpy.array (m,) particle diameter, unit: m temp : float or numpy.array (n,1) air temperature, unit: K pres : float or numpy.array (n,1) air pressure, unit: Pa Returns ------- float or numpy.array (m,) or (n,m) Particle mean free path for each dp, unit: m """ D=particle_diffusivity(dp,temp,pres) c=particle_thermal_speed(dp,temp) return (8.*D)/(np.pi*c)
def particle_thermal_speed(dp, temp)
-
Particle thermal speed
Parameters
dp
:float
ornumpy.array (m,)
- particle diameter, unit: m
temp
:float
ornumpy.array (n,1)
- air temperature, unit: K
Returns
float
ornumpy.array (m,)
or(n,m)
- Particle thermal speed for each dp at each temperature point, unit: m s-1
Expand source code
def particle_thermal_speed(dp,temp): """ Particle thermal speed Parameters ---------- dp : float or numpy.array (m,) particle diameter, unit: m temp : float or numpy.array (n,1) air temperature, unit: K Returns ------- float or numpy.array (m,) or (n,m) Particle thermal speed for each dp at each temperature point, unit: m s-1 """ k=1.381e-23 rho_p=1000.0 mp=rho_p*(1./6.)*np.pi*dp**3. return ((8.*k*temp)/(np.pi*mp))**(1./2.)
def plot_sumfile(v, ax=None, vmin=10, vmax=100000, time_reso=2, time_formatter='%H:%M')
-
Plot aerosol particle number-size distribution surface plot
Parameters
v
:pandas.DataFrame
-
Aerosol number size distribution
time (index) should be have constant resolution, otherwise the time axis will not be correct
ax
:axes object
- axis on which to plot the data
if
None
the axis are created vmin
:float
orint
- color scale lower limit
vmax
:float
orint
- color scale upper limit
time_reso
:int
- Time resolution of ticks given in hours
time_formatter
:str
- Define the format of time ticklabels
Expand source code
def plot_sumfile( v, ax=None, vmin=10, vmax=100000, time_reso=2, time_formatter="%H:%M"): """ Plot aerosol particle number-size distribution surface plot Parameters ---------- v : pandas.DataFrame Aerosol number size distribution time (index) should be have constant resolution, otherwise the time axis will not be correct ax : axes object axis on which to plot the data if `None` the axis are created vmin : float or int color scale lower limit vmax : float or int color scale upper limit time_reso : `int` Time resolution of ticks given in hours time_formatter : `str` Define the format of time ticklabels """ if ax is None: fig,handle = plt.subplots(figsize=(10,4)) else: handle=ax dp = v.columns.values.astype(float) dndlogdp = v.values.astype(float) log_ticks,log_tick_labels = generate_log_ticks(-10,-4) norm = colors.LogNorm(vmin=vmin,vmax=vmax) color_ticks = LogLocator(subs=range(10)) handle.set_yticks(log_ticks) handle.set_yticklabels(log_tick_labels) if v.index[0].utcoffset() is None: t1=dts.date2num(v.index[0])+v.index[0].second/(60.*60.*24.) t2=dts.date2num(v.index[-1])+v.index[-1].second/(60.*60.*24.) else: t1=dts.date2num(v.index[0])+v.index[0].utcoffset().seconds/(60.*60.*24.) t2=dts.date2num(v.index[-1])+v.index[-1].utcoffset().seconds/(60.*60.*24.) dp1=np.log10(dp.min()) dp2=np.log10(dp.max()) img = handle.imshow( np.flipud(dndlogdp.T), origin="upper", aspect="auto", cmap="turbo", norm=norm, extent=(t1,t2,dp1,dp2) ) handle.xaxis.set_major_locator(dts.HourLocator(interval=time_reso)) handle.xaxis.set_major_formatter(dts.DateFormatter(time_formatter)) plt.setp(handle.get_xticklabels(),rotation=80) box = handle.get_position() c_handle = plt.axes([box.x0*1.025 + box.width * 1.025, box.y0, 0.01, box.height]) cbar = plt.colorbar(img,cax=c_handle,ticks=color_ticks) handle.set_ylabel('Dp, [m]') handle.set_xlabel('Time') cbar.set_label('dN/dlogDp, [cm-3]') if ax is None: plt.show()
def slipcorr(dp, temp, pres)
-
Slip correction factor in air
Parameters
dp
:float
ornumpy array (m,)
- particle diameter, unit m
temp
:float
ornumpy.array (n,1)
- air temperature, unit K
pres
:float
ornumpy.array (n,1)
- air pressure, unit Pa
Returns
float
ornumpy.array (m,)
or(n,m)
- Cunningham slip correction factor for each particle diameter, if temperature and pressure and arrays then for each particle diameter at different pressure/temperature values. unit dimensionless
Expand source code
def slipcorr(dp,temp,pres): """ Slip correction factor in air Parameters ---------- dp : float or numpy array (m,) particle diameter, unit m temp : float or numpy.array (n,1) air temperature, unit K pres : float or numpy.array (n,1) air pressure, unit Pa Returns ------- float or numpy.array (m,) or (n,m) Cunningham slip correction factor for each particle diameter, if temperature and pressure and arrays then for each particle diameter at different pressure/temperature values. unit dimensionless """ l = mean_free_path(temp,pres) return 1.+((2.*l)/dp)*(1.257+0.4*np.exp(-(1.1*dp)/(2.*l)))
def stack_data(filenames, t_min, t_max, reso)
-
Concatenate data
If multiple data tables share the same header and have time as index then this function can be used to concatenate the data into a single large data frame.
Parameters
filenames
:list
ofstrings
- filenames of the data to be combined
t_min
:datetime
orstr
t_max
:datetime
orstr
reso
:int
orstr
- desired time resolution of the output in minutes or pandas time offset alias
Returns
pandas.DataFrame
- stacked data
Expand source code
def stack_data(filenames,t_min,t_max,reso): """ Concatenate data If multiple data tables share the same header and have time as index then this function can be used to concatenate the data into a single large data frame. Parameters ---------- filenames : list of strings filenames of the data to be combined t_min : datetime or str t_max : datetime or str reso : int or str desired time resolution of the output in minutes or pandas time offset alias Returns ------- pandas.DataFrame stacked data """ df_combined = pd.DataFrame([]) for f in filenames: print(f) df = pd.read_csv(f,index_col=0,parse_dates=True) df_combined = pd.concat((df_combined,df),axis=0) if len(df_combined)==0: return df_combined else: df_combined = df_combined.sort_index() df_combined = bin_df(df_combined,t_min,t_max,reso) return df_combined