aerosol_functions API documentation
Description: Collection of functions to analyze atmospheric aerosol data

# 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` or `numpy.array`
    air temperature, unit: K

  ## Returns

  `float` or `numpy.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` 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
  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` 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`

  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` 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
  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` 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
  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` 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
  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` 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

  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` or `None`
    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` 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
  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` 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
  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` 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
  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` 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
  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` 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
  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` 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
  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` 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
  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` 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
  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