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
    from scipy.optimize import minimize
    from collections.abc import Iterable

    def datenum2datetime(datenum):
        """
        Convert from matlab datenum to python datetime

        Parameters
        ----------

        datenum : float or array of floats
            A serial date number representing the whole and
            fractional number of days from 1-Jan-0000 to a
            specific date (MATLAB datenum)

        Returns
        -------

        datetime or array of datetimes

        """

        if (isinstance(datenum,Iterable)):
            return np.array([datetime.fromordinal(int(x)) + timedelta(days=x%1) - timedelta(days = 366) for x in datenum])
        else:
            return datetime.fromordinal(int(datenum)) + timedelta(days=datenum%1) - timedelta(days = 366)

    def datetime2datenum(dt):
        """
        Convert from python datetime to matlab datenum

        Parameters
        ----------

        datetime or array of datetimes

        Returns
        -------

        float or array of floats
            A serial date number representing the whole and
            fractional number of days from 1-Jan-0000 to a
            specific date (MATLAB datenum)

        """

        if (isinstance(dt,Iterable)):
            out=[]
            for t in dt:
                ord = t.toordinal()
                mdn = t + timedelta(days = 366)
                frac = (t-datetime(t.year,t.month,t.day,0,0,0)).seconds \
                       / (24.0 * 60.0 * 60.0)
                out.append(mdn.toordinal() + frac)
            return np.array(out)
        else:
            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 bin1d(x, y, step_x, min_bin=None, max_bin=None, ppb=1):
        """ Utility function for binning data

        Parameters
        ----------

        x : 1-d array of size n
            1-d array along which the bins are calculated.

        y : 1-d array of size n or 2-d array of size (n,m)
            2-d array with m columns, the rows correspond to values in `x`

        step_x : float
            resolution, or distance between bin centers.

        min_bin : float
            lower edge of minimum bin

        max_bin : float
            upper edge of maximum bin

        ppb : int
            number of values per bin, if bin has too few values then it will
            be `NaN`.

        Returns
        -------

        1-d array of size k
            bin centers

        1-d array of size k or 2-d array of size (k,m)
            median values in the bins

        1-d array of size k or 2-d array of size (k,m)
            25th percentile values in the bins

        1-d array of size k or 2-d array of size (k,m)
            75th percentile values in the bins

        """

        # By default use the minimum and maximum values as the limits
        if min_bin is None:
            min_bin=np.nanmin(x)
        if max_bin is None:
            max_bin=np.nanmax(x)

        temp_x = np.arange(min_bin, max_bin+step_x, step_x)

        data_x = (temp_x[:-1] + temp_x[1:])/2.

        if len(y.shape)==1:
            data_25 = np.nan*np.ones(len(data_x))
            data_50 = np.nan*np.ones(len(data_x))
            data_75 = np.nan*np.ones(len(data_x))

            for i in range(0,len(data_x)):

                y_block = y[((x>temp_x[i]) & (x<=temp_x[i+1]))]
                y_block[np.isinf(y_block)] = np.nan

                if len(y_block)>=ppb:
                    data_25[i],data_50[i],data_75[i] = np.nanpercentile(y_block,[25,50,75],axis=0)
                else:
                    continue

        else:
            data_25 = np.nan*np.ones((len(data_x),y.shape[1]))
            data_50 = np.nan*np.ones((len(data_x),y.shape[1]))
            data_75 = np.nan*np.ones((len(data_x),y.shape[1]))

            for i in range(0,len(data_x)):

                y_block = y[((x>temp_x[i]) & (x<=temp_x[i+1])),:]
                y_block[np.isinf(y_block)] = np.nan

                if len(y_block)>=ppb:
                    data_25[i,:],data_50[i,:],data_75[i,:] = np.nanpercentile(y_block,[25,50,75],axis=0)
                else:
                    continue

        return data_x, data_50, data_25, data_75

    def generate_log_ticks():
        """
        Generate ticks and ticklabels for log axis
        """
        x=np.arange(1,10)
        y=np.arange(-10,-4).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(
        time,
        dp,
        dndlogdp,
        ax=None,
        vmin=10,
        vmax=100000,
        cmap='turbo',
        interp='none',
        time_reso=2,
        time_formatter="%H:%M"):
        """
        Plot aerosol particle number-size distribution surface plot

        Parameters
        ----------

        time : numpy 1d array, size n
            measurement times (MATLAB datenum)

        dp : numpy 1d array, size m
            particle diameters

        dndlogdp : numpy 2d array, size (n,m)
            number-size distribution matrix

        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

        clim : iterable with two numerical elements
            color limits

        cmap : `str`
            colormap to be used

        interp : `str`
            interpolation method passed to imshow, default `'none'`

        time_reso : `int`
            Resolution on the time axis given in hours

        time_formatter : `str`
            Define the format of time ticklabels

        """

        if ax is None:
            fig,handle = plt.subplots()
        else:
            handle=ax

        log_ticks,log_tick_labels = generate_log_ticks()

        norm = colors.LogNorm(vmin=vmin,vmax=vmax)
        color_ticks = LogLocator(subs=range(10))

        handle.set_yticks(log_ticks)
        handle.set_yticklabels(log_tick_labels)

        t1=dts.date2num(datenum2datetime(time.min()))
        t2=dts.date2num(datenum2datetime(time.max()))

        img = handle.imshow(
            np.flipud(dndlogdp.T),
            origin="upper",
            aspect="auto",
            interpolation=interp,
            cmap=cmap,
            norm=norm,
            extent=(t1,t2,np.log10(dp.min()),np.log10(dp.max()))
        )

        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(dp,dndlogdp):
        """
        Convert from normalized number concentrations to
        unnormalized number concentrations assuming that
        the size channels have common edges.

        Parameters
        ----------

        dp : numpy 1d array
            Geometric mean diameters for the size channels

        dndlogdp : numpy 2d array
            Number size distribution with normalized concentrations
            i.e. dN/dlogDp

        Returns
        -------

        2-d array
            The number size distribution with unnormalized concentrations
            i.e. dN

        """

        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)
        return dndlogdp*dlogdp

    def air_viscosity(temp):
        """
        Calculate air viscosity
        using Enskog-Chapman theory

        Parameters
        ----------

        temp : float or array
            air temperature, unit: K

        Returns
        -------

        float or 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
            air temperature, unit: K

        pres : float
            air pressure, unit: Pa

        Returns
        -------

        float
            mean free path in air, unit: m

        """

        R=8.3143
        Mair=0.02897
        mu=air_viscosity(temp)
        return (2.*mu)/(pres*(8.*Mair/(np.pi*R*temp))**(1./2.))

    def slipcorr(dp,temp,pres):
        """
        Slip correction factor in air

        Parameters
        ----------

        dp : float or numpy array
            particle diameter, unit: m

        temp : float
            air temperature, unit: K

        pres : float
            air pressure, unit: Pa

        Returns
        -------

        float or numpy array
            Cunningham slip correction factor for each particle diameter,
            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 array
            particle diameter, unit: m

        temp : float
            air temperature, unit: K

        pres : float
            air pressure, unit: Pa

        Returns
        -------

        float or array
            Brownian diffusivity in air for particles of size dp,
            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 array
            particle diameter, unit: m

        temp : float
            air temperature, unit: K

        Returns
        -------

        float or array
            Particle thermal speed for each dp, 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 array
            particle diameter, unit: m

        temp : float
            air temperature, unit: K

        pres : float
            air pressure, unit: Pa

        Returns
        -------

        float or array
            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
            first particle diameter, unit: m

        dp2 : float
            second particle diameter, unit: m

        temp : float
            air temperature, unit: K

        pres : float
            air pressure, unit: Pa

        Returns
        -------

        float or array
            Brownian coagulation coefficient (Fuchs), 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(Dp,dp,dndlogdp,temp,pres):
        """
        Calculate coagulation sink

        Kulmala et al (2012): doi:10.1038/nprot.2012.091

        Parameters
        ----------

        Dp : float
            Particle diameter for which you want to calculate the CoagS,
            unit: m

        dp : numpy 1d array, size m
            diameter in the data, unit: meters,
            unit: m

        dndlogdp : numpy 2d array, size (n,m)
            dN/dlogDp matrix,
            unit: cm-3

        temp : float or numpy 1d array of size n
            Ambient temperature corresponding to the data,
            unit: K

        pres : float or numpy 1d array of size n
            Ambient pressure corresponding to the data,
            unit: Pa

        Returns
        -------

        numpy 1d array, size n
            Coagulation sink time series,
            unit: s-1

        """

        n = dndlogdp.shape[0]

        if not isinstance(temp,Iterable):
            temp = temp*np.ones(n)

        if not isinstance(pres,Iterable):
            pres = pres*np.ones(n)

        dn = dndlogdp2dn(dp,dndlogdp)
        dp = dp[dp>=Dp]
        dn = dn[:,dp>=Dp]

        coags = np.nan*np.ones(n)

        for i in range(n):
            # multiply by 1e6 to make [K] = cm3 s-1
            coags[i] = np.nansum(1e6*coagulation_coef(Dp,dp,temp[i],pres[i])*dn[i,:])

        return coags

    def diam2mob(dp,temp,pres,ne):
        """
        Convert electrical mobility diameter to electrical mobility in air

        Parameters
        ----------

        dp : float or numpy 1d array
            particle diameter(s),
            unit : m

        temp : float
            ambient temperature,
            unit: K

        pres : float
            ambient pressure,
            unit: Pa

        ne : int
            number of charges on the aerosol particle

        Returns
        -------

        float or numpy 1d 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 or numpy 1d array
            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 or numpy 1d array
            particle diameter(s), 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
            temperature,
            unit: K

        pres : float
            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
            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 1d array
            aerosol particle diameter(s),
            unit: m

        temp : float
            temperature,
            unit: K

        pres : float
            pressure,
            unit: Pa

        diffusivity : float
            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 1-d numpy array
            Fuchs Sutugin correction factor for each particle diameter,
            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(dp,dndlogdp,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
        ----------

        dp : numpy 1d array, size m
            diameter in the data, unit: m

        dndlogdp : numpy 2d array, size (n,m)
            dN/dlogDp matrix, unit: cm-3

        temp : numpy 1d array, size n
            Ambient temperature corresponding to the data, unit: K

        pres : numpy 1d array, size n
            Ambient pressure corresponding to the data, unit: Pa

        Returns
        -------

        numpy 1d array, size n
            condensation sink time series, unit: s-1

        """

        n = dndlogdp.shape[0]

        if not isinstance(temp,Iterable):
            temp=temp*np.ones(n)

        if not isinstance(pres,Iterable):
            pres=pres*np.ones(n)

        M_h2so4 = 98.08
        M_air = 28.965
        V_air = 19.7
        V_h2so4 = 51.96

        dn = dndlogdp2dn(dp,dndlogdp)
        cs = np.nan*np.ones(n)

        for i in range(n):
            diffusivity = binary_diffusivity(temp[i],pres[i],M_h2so4,M_air,V_h2so4,V_air)
            b = beta(dp,temp[i],pres[i],diffusivity,M_h2so4)

            cs[i] = (4.*np.pi*diffusivity)*np.nansum(1e6*dn[i,:]*b*dp)

        return cs

    def calc_conc(dp,dndlogdp,dmin,dmax):
        """
        Calculate particle number concentration from aerosol
        number-size distribution

        Parameters
        ----------

        dp : numpy 1d array, size m
            diameter in the data, unit: m

        dndlogdp : numpy 2d array, size (n,m)
            dN/dlogDp matrix, unit: cm-3

        dmin : float
            Size range lower diameter, unit: m

        dmax : float
            Size range upper diameter, unit: m

        Returns
        -------

        numpy 1d array, size n
            Number concentration in the given size range, unit: cm-3

        """

        findex = np.argwhere((dp<=dmax)&(dp>=dmin)).flatten()
        dp = dp[findex]
        dndlogdp = dndlogdp[:,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)
        return np.nansum(dndlogdp*dlogdp,axis=1)

    def calc_formation_rate(time,dp1,dp2,conc,coags,gr):
        """
        Calculate particle formation rate

        Kulmala et al (2012): doi:10.1038/nprot.2012.091

        Parameters
        ----------

        time : numpy 1d array
            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 : numpy 1d array
            Particle number concentration in the size range dp1...dp2, unit: cm-3

        coags : numpy 1d 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
        -------

        numpy 1d array
            particle formation rate for diameter dp1, unit: cm3 s-1

        """

        conc_term = np.diff(conc)/np.diff(time*1.157e5)
        sink_term = (coags[1:] + coags[:-1])/2. * (conc[1:] + conc[:-1])/2.
        gr_term = (2.778e-13*gr)/(dp2-dp1) * (conc[1:] + conc[:-1])/2.
        formation_rate = conc_term + sink_term + gr_term

        return 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 1d array
            Time associated with the measurements, unit: days

        dp1 : float
            Lower diameter of the size range, unit: m

        dp2 : float
            Upper diameter of the size range, unit: m

        conc_pos : numpy 1d array
            Positive ion number concentration in the size range dp1...dp2, unit: cm-3

        conc_neg : numpy 1d array
            Negative ion number concentration in the size range dp1...dp2, unit: cm-3

        conc_pos_small : numpy 1d array
            Positive ion number concentration for ions smaller than dp1, unit: cm-3

        conc_neg_small : numpy 1d array
            Negative ion number concentration for ions smaller than dp1, unit: cm-3

        conc : numpy 1d array
            Particle number concentration in the size range dp1...dp2, unit: cm-3

        coags : numpy 1d 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
        -------

        numpy 1d array
            Positive ion formation rate for diameter dp1, unit : cm3 s-1

        numpy 1d array
            Negative ion formation rate for diameter dp1, unit: cm3 s-1

        """

        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)/np.diff(time*1.157e5)
        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)/np.diff(time*1.157e5)
        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

        return pos_formation_rate, neg_formation_rate

## Functions

`def air_viscosity(temp)`

  Calculate air viscosity using Enskog-Chapman theory

  ## Parameters

  `temp` :`float` or `array`
    air temperature, unit: K

  ## Returns

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

          Returns
          -------

          float or 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 1d array`
    aerosol particle diameter(s), unit: m
  `temp` :`float`
    temperature, unit: K
  `pres` :`float`
    pressure, unit: Pa
  `diffusivity` :`float`
    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 `1-d numpy array`
    Fuchs Sutugin correction factor for each particle diameter, 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 1d array
              aerosol particle diameter(s),
              unit: m

          temp : float
              temperature,
              unit: K

          pres : float
              pressure,
              unit: Pa

          diffusivity : float
              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 1-d numpy array
              Fuchs Sutugin correction factor for each particle diameter,
              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 bin1d(x, y, step_x, min_bin=None, max_bin=None, ppb=1)`

  Utility function for binning data

  ## Parameters

  `x` :`1-d array` of `size n`
    1-d array along which the bins are calculated.
  `y` :`1-d array` of `size n` or `2-d array` of `size (n,m)`
    2-d array with m columns, the rows correspond to values in `x`
  `step_x` :`float`
    resolution, or distance between bin centers.
  `min_bin` :`float`
    lower edge of minimum bin
  `max_bin` :`float`
    upper edge of maximum bin
  `ppb` :`int`
    number of values per bin, if bin has too few values then it will be `NaN`.

  ## Returns

  `1-d array` of `size k`
    bin centers
  `1-d array` of `size k` or `2-d array` of `size (k,m)`
    median values in the bins
  `1-d array` of `size k` or `2-d array` of `size (k,m)`
    25th percentile values in the bins
  `1-d array` of `size k` or `2-d array` of `size (k,m)`
    75th percentile values in the bins
  Expand source code

      def bin1d(x, y, step_x, min_bin=None, max_bin=None, ppb=1):
          """ Utility function for binning data

          Parameters
          ----------

          x : 1-d array of size n
              1-d array along which the bins are calculated.

          y : 1-d array of size n or 2-d array of size (n,m)
              2-d array with m columns, the rows correspond to values in `x`

          step_x : float
              resolution, or distance between bin centers.

          min_bin : float
              lower edge of minimum bin

          max_bin : float
              upper edge of maximum bin

          ppb : int
              number of values per bin, if bin has too few values then it will
              be `NaN`.

          Returns
          -------

          1-d array of size k
              bin centers

          1-d array of size k or 2-d array of size (k,m)
              median values in the bins

          1-d array of size k or 2-d array of size (k,m)
              25th percentile values in the bins

          1-d array of size k or 2-d array of size (k,m)
              75th percentile values in the bins

          """

          # By default use the minimum and maximum values as the limits
          if min_bin is None:
              min_bin=np.nanmin(x)
          if max_bin is None:
              max_bin=np.nanmax(x)

          temp_x = np.arange(min_bin, max_bin+step_x, step_x)

          data_x = (temp_x[:-1] + temp_x[1:])/2.

          if len(y.shape)==1:
              data_25 = np.nan*np.ones(len(data_x))
              data_50 = np.nan*np.ones(len(data_x))
              data_75 = np.nan*np.ones(len(data_x))

              for i in range(0,len(data_x)):

                  y_block = y[((x>temp_x[i]) & (x<=temp_x[i+1]))]
                  y_block[np.isinf(y_block)] = np.nan

                  if len(y_block)>=ppb:
                      data_25[i],data_50[i],data_75[i] = np.nanpercentile(y_block,[25,50,75],axis=0)
                  else:
                      continue

          else:
              data_25 = np.nan*np.ones((len(data_x),y.shape[1]))
              data_50 = np.nan*np.ones((len(data_x),y.shape[1]))
              data_75 = np.nan*np.ones((len(data_x),y.shape[1]))

              for i in range(0,len(data_x)):

                  y_block = y[((x>temp_x[i]) & (x<=temp_x[i+1])),:]
                  y_block[np.isinf(y_block)] = np.nan

                  if len(y_block)>=ppb:
                      data_25[i,:],data_50[i,:],data_75[i,:] = np.nanpercentile(y_block,[25,50,75],axis=0)
                  else:
                      continue

          return data_x, data_50, data_25, data_75

`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`
    temperature, unit: K
  `pres` :`float`
    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`
    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
              temperature,
              unit: K

          pres : float
              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
              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(Dp, dp, dndlogdp, temp, pres)`

  Calculate coagulation sink

  Kulmala et al (2012): doi:10.1038/nprot.2012.091

  ## Parameters

  `Dp` :`float`
    Particle diameter for which you want to calculate the CoagS, unit: m
  `dp` :`numpy 1d array, size m`
    diameter in the data, unit: meters, unit: m
  `dndlogdp` :`numpy 2d array, size (n,m)`
    dN/dlogDp matrix, unit: cm-3
  `temp` :`float` or `numpy 1d array` of `size n`
    Ambient temperature corresponding to the data, unit: K
  `pres` :`float` or `numpy 1d array` of `size n`
    Ambient pressure corresponding to the data, unit: Pa

  ## Returns

  `numpy 1d array, size n`
    Coagulation sink time series, unit: s-1
  Expand source code

      def calc_coags(Dp,dp,dndlogdp,temp,pres):
          """
          Calculate coagulation sink

          Kulmala et al (2012): doi:10.1038/nprot.2012.091

          Parameters
          ----------

          Dp : float
              Particle diameter for which you want to calculate the CoagS,
              unit: m

          dp : numpy 1d array, size m
              diameter in the data, unit: meters,
              unit: m

          dndlogdp : numpy 2d array, size (n,m)
              dN/dlogDp matrix,
              unit: cm-3

          temp : float or numpy 1d array of size n
              Ambient temperature corresponding to the data,
              unit: K

          pres : float or numpy 1d array of size n
              Ambient pressure corresponding to the data,
              unit: Pa

          Returns
          -------

          numpy 1d array, size n
              Coagulation sink time series,
              unit: s-1

          """

          n = dndlogdp.shape[0]

          if not isinstance(temp,Iterable):
              temp = temp*np.ones(n)

          if not isinstance(pres,Iterable):
              pres = pres*np.ones(n)

          dn = dndlogdp2dn(dp,dndlogdp)
          dp = dp[dp>=Dp]
          dn = dn[:,dp>=Dp]

          coags = np.nan*np.ones(n)

          for i in range(n):
              # multiply by 1e6 to make [K] = cm3 s-1
              coags[i] = np.nansum(1e6*coagulation_coef(Dp,dp,temp[i],pres[i])*dn[i,:])

          return coags

`def calc_conc(dp, dndlogdp, dmin, dmax)`

  Calculate particle number concentration from aerosol number-size distribution

  ## Parameters

  `dp` :`numpy 1d array, size m`
    diameter in the data, unit: m
  `dndlogdp` :`numpy 2d array, size (n,m)`
    dN/dlogDp matrix, unit: cm-3
  `dmin` :`float`
    Size range lower diameter, unit: m
  `dmax` :`float`
    Size range upper diameter, unit: m

  ## Returns

  `numpy 1d array, size n`
    Number concentration in the given size range, unit: cm-3
  Expand source code

      def calc_conc(dp,dndlogdp,dmin,dmax):
          """
          Calculate particle number concentration from aerosol
          number-size distribution

          Parameters
          ----------

          dp : numpy 1d array, size m
              diameter in the data, unit: m

          dndlogdp : numpy 2d array, size (n,m)
              dN/dlogDp matrix, unit: cm-3

          dmin : float
              Size range lower diameter, unit: m

          dmax : float
              Size range upper diameter, unit: m

          Returns
          -------

          numpy 1d array, size n
              Number concentration in the given size range, unit: cm-3

          """

          findex = np.argwhere((dp<=dmax)&(dp>=dmin)).flatten()
          dp = dp[findex]
          dndlogdp = dndlogdp[:,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)
          return np.nansum(dndlogdp*dlogdp,axis=1)

`def calc_cs(dp, dndlogdp, 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

  `dp` :`numpy 1d array, size m`
    diameter in the data, unit: m
  `dndlogdp` :`numpy 2d array, size (n,m)`
    dN/dlogDp matrix, unit: cm-3
  `temp` :`numpy 1d array, size n`
    Ambient temperature corresponding to the data, unit: K
  `pres` :`numpy 1d array, size n`
    Ambient pressure corresponding to the data, unit: Pa

  ## Returns

  `numpy 1d array, size n`
    condensation sink time series, unit: s-1
  Expand source code

      def calc_cs(dp,dndlogdp,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
          ----------

          dp : numpy 1d array, size m
              diameter in the data, unit: m

          dndlogdp : numpy 2d array, size (n,m)
              dN/dlogDp matrix, unit: cm-3

          temp : numpy 1d array, size n
              Ambient temperature corresponding to the data, unit: K

          pres : numpy 1d array, size n
              Ambient pressure corresponding to the data, unit: Pa

          Returns
          -------

          numpy 1d array, size n
              condensation sink time series, unit: s-1

          """

          n = dndlogdp.shape[0]

          if not isinstance(temp,Iterable):
              temp=temp*np.ones(n)

          if not isinstance(pres,Iterable):
              pres=pres*np.ones(n)

          M_h2so4 = 98.08
          M_air = 28.965
          V_air = 19.7
          V_h2so4 = 51.96

          dn = dndlogdp2dn(dp,dndlogdp)
          cs = np.nan*np.ones(n)

          for i in range(n):
              diffusivity = binary_diffusivity(temp[i],pres[i],M_h2so4,M_air,V_h2so4,V_air)
              b = beta(dp,temp[i],pres[i],diffusivity,M_h2so4)

              cs[i] = (4.*np.pi*diffusivity)*np.nansum(1e6*dn[i,:]*b*dp)

          return cs

`def calc_formation_rate(time, dp1, dp2, conc, coags, gr)`

  Calculate particle formation rate

  Kulmala et al (2012): doi:10.1038/nprot.2012.091

  ## Parameters

  `time` :`numpy 1d array`
    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` :`numpy 1d array`
    Particle number concentration in the size range dp1…dp2, unit: cm-3
  `coags` :`numpy 1d 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

  `numpy 1d array`
    particle formation rate for diameter dp1, unit: cm3 s-1
  Expand source code

      def calc_formation_rate(time,dp1,dp2,conc,coags,gr):
          """
          Calculate particle formation rate

          Kulmala et al (2012): doi:10.1038/nprot.2012.091

          Parameters
          ----------

          time : numpy 1d array
              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 : numpy 1d array
              Particle number concentration in the size range dp1...dp2, unit: cm-3

          coags : numpy 1d 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
          -------

          numpy 1d array
              particle formation rate for diameter dp1, unit: cm3 s-1

          """

          conc_term = np.diff(conc)/np.diff(time*1.157e5)
          sink_term = (coags[1:] + coags[:-1])/2. * (conc[1:] + conc[:-1])/2.
          gr_term = (2.778e-13*gr)/(dp2-dp1) * (conc[1:] + conc[:-1])/2.
          formation_rate = conc_term + sink_term + gr_term

          return 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 1d array`
    Time associated with the measurements, unit: days
  `dp1` :`float`
    Lower diameter of the size range, unit: m
  `dp2` :`float`
    Upper diameter of the size range, unit: m
  `conc_pos` :`numpy 1d array`
    Positive ion number concentration in the size range dp1…dp2, unit: cm-3
  `conc_neg` :`numpy 1d array`
    Negative ion number concentration in the size range dp1…dp2, unit: cm-3
  `conc_pos_small` :`numpy 1d array`
    Positive ion number concentration for ions smaller than dp1, unit: cm-3
  `conc_neg_small` :`numpy 1d array`
    Negative ion number concentration for ions smaller than dp1, unit: cm-3
  `conc` :`numpy 1d array`
    Particle number concentration in the size range dp1…dp2, unit: cm-3
  `coags` :`numpy 1d 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

  `numpy 1d array`
    Positive ion formation rate for diameter dp1, unit : cm3 s-1
  `numpy 1d array`
    Negative 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 1d array
              Time associated with the measurements, unit: days

          dp1 : float
              Lower diameter of the size range, unit: m

          dp2 : float
              Upper diameter of the size range, unit: m

          conc_pos : numpy 1d array
              Positive ion number concentration in the size range dp1...dp2, unit: cm-3

          conc_neg : numpy 1d array
              Negative ion number concentration in the size range dp1...dp2, unit: cm-3

          conc_pos_small : numpy 1d array
              Positive ion number concentration for ions smaller than dp1, unit: cm-3

          conc_neg_small : numpy 1d array
              Negative ion number concentration for ions smaller than dp1, unit: cm-3

          conc : numpy 1d array
              Particle number concentration in the size range dp1...dp2, unit: cm-3

          coags : numpy 1d 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
          -------

          numpy 1d array
              Positive ion formation rate for diameter dp1, unit : cm3 s-1

          numpy 1d array
              Negative ion formation rate for diameter dp1, unit: cm3 s-1

          """

          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)/np.diff(time*1.157e5)
          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)/np.diff(time*1.157e5)
          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

          return pos_formation_rate, neg_formation_rate

`def coagulation_coef(dp1, dp2, temp, pres)`

  Calculate Brownian coagulation coefficient (Fuchs)

  ## Parameters

  `dp1` :`float`
    first particle diameter, unit: m
  `dp2` :`float`
    second particle diameter, unit: m
  `temp` :`float`
    air temperature, unit: K
  `pres` :`float`
    air pressure, unit: Pa

  ## Returns

  `float` or `array`
    Brownian coagulation coefficient (Fuchs), unit: m3 s-1
  Expand source code

      def coagulation_coef(dp1,dp2,temp,pres):
          """
          Calculate Brownian coagulation coefficient (Fuchs)

          Parameters
          ----------

          dp1 : float
              first particle diameter, unit: m

          dp2 : float
              second particle diameter, unit: m

          temp : float
              air temperature, unit: K

          pres : float
              air pressure, unit: Pa

          Returns
          -------

          float or array
              Brownian coagulation coefficient (Fuchs), 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)`

  Convert from matlab datenum to python datetime

  ## Parameters

  `datenum` :`float` or `array` of `floats`
    A serial date number representing the whole and fractional number of days from 1-Jan-0000 to a specific date (MATLAB datenum)

  ## Returns

  `datetime` or `array` of `datetimes`
  Expand source code

      def datenum2datetime(datenum):
          """
          Convert from matlab datenum to python datetime

          Parameters
          ----------

          datenum : float or array of floats
              A serial date number representing the whole and
              fractional number of days from 1-Jan-0000 to a
              specific date (MATLAB datenum)

          Returns
          -------

          datetime or array of datetimes

          """

          if (isinstance(datenum,Iterable)):
              return np.array([datetime.fromordinal(int(x)) + timedelta(days=x%1) - timedelta(days = 366) for x in datenum])
          else:
              return datetime.fromordinal(int(datenum)) + timedelta(days=datenum%1) - timedelta(days = 366)

`def datetime2datenum(dt)`

  Convert from python datetime to matlab datenum

  ## Parameters

  datetime or array of datetimes

  ## Returns

  `float` or `array` of `floats`
    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
          ----------

          datetime or array of datetimes

          Returns
          -------

          float or array of floats
              A serial date number representing the whole and
              fractional number of days from 1-Jan-0000 to a
              specific date (MATLAB datenum)

          """

          if (isinstance(dt,Iterable)):
              out=[]
              for t in dt:
                  ord = t.toordinal()
                  mdn = t + timedelta(days = 366)
                  frac = (t-datetime(t.year,t.month,t.day,0,0,0)).seconds \
                         / (24.0 * 60.0 * 60.0)
                  out.append(mdn.toordinal() + frac)
              return np.array(out)
          else:
              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 1d array`
    particle diameter(s), unit : m
  `temp` :`float`
    ambient temperature, unit: K
  `pres` :`float`
    ambient pressure, unit: Pa
  `ne` :`int`
    number of charges on the aerosol particle

  ## Returns

  `float` or `numpy 1d 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 1d array
              particle diameter(s),
              unit : m

          temp : float
              ambient temperature,
              unit: K

          pres : float
              ambient pressure,
              unit: Pa

          ne : int
              number of charges on the aerosol particle

          Returns
          -------

          float or numpy 1d 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(dp, dndlogdp)`

  Convert from normalized number concentrations to unnormalized number concentrations assuming that the size channels have common edges.

  ## Parameters

  `dp` :`numpy 1d array`
    Geometric mean diameters for the size channels
  `dndlogdp` :`numpy 2d array`
    Number size distribution with normalized concentrations i.e. dN/dlogDp

  ## Returns

  `2-d array`
    The number size distribution with unnormalized concentrations i.e. dN
  Expand source code

      def dndlogdp2dn(dp,dndlogdp):
          """
          Convert from normalized number concentrations to
          unnormalized number concentrations assuming that
          the size channels have common edges.

          Parameters
          ----------

          dp : numpy 1d array
              Geometric mean diameters for the size channels

          dndlogdp : numpy 2d array
              Number size distribution with normalized concentrations
              i.e. dN/dlogDp

          Returns
          -------

          2-d array
              The number size distribution with unnormalized concentrations
              i.e. dN

          """

          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)
          return dndlogdp*dlogdp

`def generate_log_ticks()`

  Generate ticks and ticklabels for log axis

  Expand source code

      def generate_log_ticks():
          """
          Generate ticks and ticklabels for log axis
          """
          x=np.arange(1,10)
          y=np.arange(-10,-4).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`
    air temperature, unit: K
  `pres` :`float`
    air pressure, unit: Pa

  ## Returns

  `float`
    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
              air temperature, unit: K

          pres : float
              air pressure, unit: Pa

          Returns
          -------

          float
              mean free path in air, unit: m

          """

          R=8.3143
          Mair=0.02897
          mu=air_viscosity(temp)
          return (2.*mu)/(pres*(8.*Mair/(np.pi*R*temp))**(1./2.))

`def mob2diam(Zp, temp, pres, ne)`

  Convert electrical mobility to electrical mobility diameter in air

  ## Parameters

  `Zp` :`float` or `numpy 1d array`
    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` or `numpy 1d array`
    particle diameter(s), unit: m
  Expand source code

      def mob2diam(Zp,temp,pres,ne):
          """
          Convert electrical mobility to electrical mobility diameter in air

          Parameters
          ----------

          Zp : float or numpy 1d array
              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 or numpy 1d array
              particle diameter(s), 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 `array`
    particle diameter, unit: m
  `temp` :`float`
    air temperature, unit: K
  `pres` :`float`
    air pressure, unit: Pa

  ## Returns

  `float` or `array`
    Brownian diffusivity in air for particles of size dp, unit: m2 s-1
  Expand source code

      def particle_diffusivity(dp,temp,pres):
          """
          Particle brownian diffusivity in air

          Parameters
          ----------

          dp : float or array
              particle diameter, unit: m

          temp : float
              air temperature, unit: K

          pres : float
              air pressure, unit: Pa

          Returns
          -------

          float or array
              Brownian diffusivity in air for particles of size dp,
              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 `array`
    particle diameter, unit: m
  `temp` :`float`
    air temperature, unit: K
  `pres` :`float`
    air pressure, unit: Pa

  ## Returns

  `float` or `array`
    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 array
              particle diameter, unit: m

          temp : float
              air temperature, unit: K

          pres : float
              air pressure, unit: Pa

          Returns
          -------

          float or array
              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 `array`
    particle diameter, unit: m
  `temp` :`float`
    air temperature, unit: K

  ## Returns

  `float` or `array`
    Particle thermal speed for each dp, unit: m s-1
  Expand source code

      def particle_thermal_speed(dp,temp):
          """
          Particle thermal speed

          Parameters
          ----------

          dp : float or array
              particle diameter, unit: m

          temp : float
              air temperature, unit: K

          Returns
          -------

          float or array
              Particle thermal speed for each dp, 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(time, dp, dndlogdp, ax=None, vmin=10, vmax=100000, cmap='turbo', interp='none', time_reso=2, time_formatter='%H:%M')`

  Plot aerosol particle number-size distribution surface plot

  ## Parameters

  `time` :`numpy 1d array, size n`
    measurement times (MATLAB datenum)
  `dp` :`numpy 1d array, size m`
    particle diameters
  `dndlogdp` :`numpy 2d array, size (n,m)`
    number-size distribution matrix
  `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
  `clim` :`iterable with two numerical elements`
    color limits
  `cmap` :`str`
    colormap to be used
  `interp` :`str`
    interpolation method passed to imshow, default `'none'`
  `time_reso` :`int`
    Resolution on the time axis given in hours
  `time_formatter` :`str`
    Define the format of time ticklabels
  Expand source code

      def plot_sumfile(
          time,
          dp,
          dndlogdp,
          ax=None,
          vmin=10,
          vmax=100000,
          cmap='turbo',
          interp='none',
          time_reso=2,
          time_formatter="%H:%M"):
          """
          Plot aerosol particle number-size distribution surface plot

          Parameters
          ----------

          time : numpy 1d array, size n
              measurement times (MATLAB datenum)

          dp : numpy 1d array, size m
              particle diameters

          dndlogdp : numpy 2d array, size (n,m)
              number-size distribution matrix

          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

          clim : iterable with two numerical elements
              color limits

          cmap : `str`
              colormap to be used

          interp : `str`
              interpolation method passed to imshow, default `'none'`

          time_reso : `int`
              Resolution on the time axis given in hours

          time_formatter : `str`
              Define the format of time ticklabels

          """

          if ax is None:
              fig,handle = plt.subplots()
          else:
              handle=ax

          log_ticks,log_tick_labels = generate_log_ticks()

          norm = colors.LogNorm(vmin=vmin,vmax=vmax)
          color_ticks = LogLocator(subs=range(10))

          handle.set_yticks(log_ticks)
          handle.set_yticklabels(log_tick_labels)

          t1=dts.date2num(datenum2datetime(time.min()))
          t2=dts.date2num(datenum2datetime(time.max()))

          img = handle.imshow(
              np.flipud(dndlogdp.T),
              origin="upper",
              aspect="auto",
              interpolation=interp,
              cmap=cmap,
              norm=norm,
              extent=(t1,t2,np.log10(dp.min()),np.log10(dp.max()))
          )

          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`
    particle diameter, unit: m
  `temp` :`float`
    air temperature, unit: K
  `pres` :`float`
    air pressure, unit: Pa

  ## Returns

  `float` or `numpy array`
    Cunningham slip correction factor for each particle diameter, unit: dimensionless
  Expand source code

      def slipcorr(dp,temp,pres):
          """
          Slip correction factor in air

          Parameters
          ----------

          dp : float or numpy array
              particle diameter, unit: m

          temp : float
              air temperature, unit: K

          pres : float
              air pressure, unit: Pa

          Returns
          -------

          float or numpy array
              Cunningham slip correction factor for each particle diameter,
              unit: dimensionless

          """

          l = mean_free_path(temp,pres)
          return 1.+((2.*l)/dp)*(1.257+0.4*np.exp(-(1.1*dp)/(2.*l)))