Skip to content

Distributions

Size distribution classes and pre-built aerosol climatology collections.

Distribution classes

Lognorm

Lognorm(mu: float, sigma: float, N: float = 1.0, base: float = np.e)

Bases: BaseDistribution

Lognormal size distribution.

An instance of Lognorm contains a construction of a lognormal distribution and the utilities necessary for computing statistical functions associated with that distribution. The parameters of the constructor are invariant with respect to what length and concentration unit you choose; that is, if you use meters for mu and cm**-3 for N, then you should keep these in mind when evaluating the pdf and cdf functions and when interpreting moments.

Parameters:

Name Type Description Default
mu float

Median/geometric mean radius, length unit.

required
sigma float

Geometric standard deviation, unitless.

required
N (float, optional(default=1.0))

Total number concentration, concentration unit.

1.0

Attributes:

Name Type Description
median float

Geometric mean (= mu).

mean float

Arithmetic mean: mu * exp(0.5 * ln(sigma)**2).

Methods:

Name Description
pdf

Evaluate size distribution density dN/dx (not logarithmic) at a particular radius x

pdfloge

Evaluate size distribution logarithmic density dN/dln(x) at a particular radius x

pdflog10

Evaluate size distribution logarithmic density dN/dlog(x) at a particular radius x

cdf

Evaluate cumulative concentration up to a particular radius x

moment

Compute the k-th moment of the lognormal distribution.

Source code in pyrcel/distributions.py
def __init__(self, mu: float, sigma: float, N: float = 1.0, base: float = np.e) -> None:
    self.mu = mu
    self.sigma = sigma
    self.N = N

    self.median = self.mu
    # Arithmetic mean of the underlying normal: E[r] = mu * exp(0.5 * ln(sigma)^2)
    self.mean = self.mu * np.exp(0.5 * np.log(self.sigma) ** 2)

invcdf

invcdf(y: FloatND) -> FloatND

Inverse of cumulative density function.

Parameters:

Name Type Description Default
y float

CDF value, between 0 and N (cumulative number concentration).

required

Returns:

Type Description
float

Radius (same unit as mu) corresponding to the given CDF value.

Source code in pyrcel/distributions.py
def invcdf(self, y: FloatND) -> FloatND:
    """Inverse of cumulative density function.

    Parameters
    ----------
    y : float
        CDF value, between 0 and ``N`` (cumulative number concentration).

    Returns
    -------
    float
        Radius (same unit as ``mu``) corresponding to the given CDF value.

    """

    if np.any(y < 0) or np.any(y > self.N):
        raise ValueError(f"y must be between 0 and N={self.N}")

    erfinv_arg = 2.0 * y / self.N - 1.0
    return self.mu * np.exp(np.log(self.sigma) * np.sqrt(2.0) * erfinv(erfinv_arg))

cdf

cdf(x: FloatND) -> FloatND

Cumulative density function

\[ \text{CDF} = \frac{N}{2}\left(1.0 + \text{erf}\left(\frac{\ln(x/\mu)}{\sqrt{2}\ln{\sigma}}\right) \right) \]

Parameters:

Name Type Description Default
x float

Radius (must match unit of mu).

required

Returns:

Type Description
float

Cumulative concentration up to radius x.

Source code in pyrcel/distributions.py
def cdf(self, x: FloatND) -> FloatND:
    """Cumulative density function

    $$
    \\text{CDF} = \\frac{N}{2}\\left(1.0 + \\text{erf}\\left(\\frac{\\ln(x/\\mu)}{\\sqrt{2}\\ln{\\sigma}}\\right) \\right)
    $$

    Parameters
    ----------
    x : float
        Radius (must match unit of ``mu``).

    Returns
    -------
    float
        Cumulative concentration up to radius ``x``.

    """
    erf_arg = (np.log(x / self.mu)) / (np.sqrt(2.0) * np.log(self.sigma))
    return (self.N / 2.0) * (1.0 + erf(erf_arg))

pdf

pdf(x: FloatND) -> FloatND

Distribution density function dN/dx (not logarithmic).

\[ \text{pdf} = \frac{N}{\sqrt{2\pi}\ln(\sigma) x}\exp\left( -\frac{\ln^2(x/\mu)}{2\ln^2\sigma} \right) \]

Parameters:

Name Type Description Default
x float

Radius (must match unit of mu).

required

Returns:

Type Description
float

Distribution density value at radius x.

Source code in pyrcel/distributions.py
def pdf(self, x: FloatND) -> FloatND:
    """Distribution density function dN/dx (not logarithmic).

    $$
    \\text{pdf} = \\frac{N}{\\sqrt{2\\pi}\\ln(\\sigma) x}\\exp\\left( -\\frac{\\ln^2(x/\\mu)}{2\\ln^2\\sigma} \\right)
    $$

    Parameters
    ----------
    x : float
        Radius (must match unit of ``mu``).

    Returns
    -------
    float
        Distribution density value at radius ``x``.

    """
    scaling = self.N / (np.sqrt(2.0 * np.pi) * np.log(self.sigma))
    exponent = ((np.log(x / self.mu)) ** 2) / (2.0 * (np.log(self.sigma)) ** 2)
    return (scaling / x) * np.exp(-exponent)

pdfloge

pdfloge(x: FloatND) -> FloatND

Distribution density function dN/dln(x) (natural logarithm).

\[ \text{pdf} = \frac{N}{\sqrt{2\pi}\ln\sigma}\exp\left( -\frac{\ln^2(x/\mu)}{2\ln^2\sigma} \right) \]

Parameters:

Name Type Description Default
x float

Radius (must match unit of mu).

required

Returns:

Type Description
float

Logarithmic distribution density value at radius x.

Source code in pyrcel/distributions.py
def pdfloge(self, x: FloatND) -> FloatND:
    """Distribution density function dN/dln(x) (natural logarithm).

    $$
    \\text{pdf} = \\frac{N}{\\sqrt{2\\pi}\\ln\\sigma}\\exp\\left( -\\frac{\\ln^2(x/\\mu)}{2\\ln^2\\sigma} \\right)
    $$

    Parameters
    ----------
    x : float
        Radius (must match unit of ``mu``).

    Returns
    -------
    float
        Logarithmic distribution density value at radius ``x``.

    """
    scaling = self.N / (np.sqrt(2.0 * np.pi) * np.log(self.sigma))
    exponent = ((np.log(x / self.mu)) ** 2) / (2.0 * (np.log(self.sigma)) ** 2)
    return scaling * np.exp(-exponent)

pdflog10

pdflog10(x: FloatND) -> FloatND

Distribution density function dN/dlog(x) (base 10 logarithm).

\[ \text{pdf} = \frac{N\ln 10}{\sqrt{2\pi}\ln\sigma}\exp\left( -\frac{\ln^2(x/\mu)}{2\ln^2\sigma} \right) \]

Parameters:

Name Type Description Default
x float

Radius (must match unit of mu).

required

Returns:

Type Description
float

Base-10 logarithmic distribution density value at radius x.

Source code in pyrcel/distributions.py
def pdflog10(self, x: FloatND) -> FloatND:
    """Distribution density function dN/dlog(x) (base 10 logarithm).

    $$
    \\text{pdf} = \\frac{N\\ln 10}{\\sqrt{2\\pi}\\ln\\sigma}\\exp\\left( -\\frac{\\ln^2(x/\\mu)}{2\\ln^2\\sigma} \\right)
    $$

    Parameters
    ----------
    x : float
        Radius (must match unit of ``mu``).

    Returns
    -------
    float
        Base-10 logarithmic distribution density value at radius ``x``.

    """
    scaling = self.N / (np.sqrt(2.0 * np.pi) * np.log(self.sigma))
    exponent = ((np.log(x / self.mu)) ** 2) / (2.0 * (np.log(self.sigma)) ** 2)
    return np.log(10) * scaling * np.exp(-exponent)

moment

moment(k: int | float) -> float

Compute the k-th moment of the lognormal distribution.

\[ F(k) = N\mu^k\exp\left( \frac{k^2}{2} \ln^2 \sigma \right) \]

Parameters:

Name Type Description Default
k int

Moment to evaluate.

required

Returns:

Type Description
float

The k-th moment of the distribution.

Source code in pyrcel/distributions.py
def moment(self, k: int | float) -> float:
    """Compute the k-th moment of the lognormal distribution.

    $$
    F(k) = N\\mu^k\\exp\\left( \\frac{k^2}{2} \\ln^2 \\sigma \\right)
    $$

    Parameters
    ----------
    k : int
        Moment to evaluate.

    Returns
    -------
    float
        The k-th moment of the distribution.

    """
    scaling = (self.mu**k) * self.N
    exponent = ((k**2) / 2.0) * (np.log(self.sigma)) ** 2
    return scaling * np.exp(exponent)

stats

stats() -> dict[str, float]

Compute useful statistics for a lognormal distribution

Returns:

Type Description
dict

Dictionary containing the stats mean_radius, total_diameter, total_surface_area, total_volume, mean_surface_area, mean_volume, and effective_radius

Source code in pyrcel/distributions.py
def stats(self) -> dict[str, float]:
    """Compute useful statistics for a lognormal distribution

    Returns
    -------
    dict
        Dictionary containing the stats ``mean_radius``, ``total_diameter``,
        ``total_surface_area``, ``total_volume``, ``mean_surface_area``,
        ``mean_volume``, and ``effective_radius``

    """
    stats_dict = dict()
    stats_dict["mean_radius"] = self.mu * np.exp(0.5 * (np.log(self.sigma)) ** 2)

    stats_dict["total_diameter"] = self.N * stats_dict["mean_radius"]
    stats_dict["total_surface_area"] = 4.0 * np.pi * self.moment(2.0)
    stats_dict["total_volume"] = (4.0 * np.pi / 3.0) * self.moment(3.0)

    stats_dict["mean_surface_area"] = stats_dict["total_surface_area"] / self.N
    stats_dict["mean_volume"] = stats_dict["total_volume"] / self.N

    stats_dict["effective_radius"] = (
        stats_dict["total_volume"] / stats_dict["total_surface_area"]
    )

    return stats_dict

MultiModeLognorm

MultiModeLognorm(
    mus: tuple[float, ...] | list[float],
    sigmas: tuple[float, ...] | list[float],
    Ns: tuple[float, ...] | list[float],
    base: float = np.e,
)

Bases: BaseDistribution

Multimode lognormal distribution class.

Container for multiple Lognorm classes representing a full aerosol size distribution.

Source code in pyrcel/distributions.py
def __init__(
    self,
    mus: tuple[float, ...] | list[float],
    sigmas: tuple[float, ...] | list[float],
    Ns: tuple[float, ...] | list[float],
    base: float = np.e,
) -> None:
    dist_params = list(zip(mus, sigmas, Ns))
    from operator import itemgetter

    dist_params = sorted(dist_params, key=itemgetter(0))

    self.mus, self.sigmas, self.Ns = list(zip(*dist_params))

    self.base = np.e

    self.lognorms = []
    for mu, sigma, N in zip(self.mus, self.sigmas, self.Ns):
        mode_dist = Lognorm(mu, sigma, N)
        self.lognorms.append(mode_dist)

stats

stats() -> dict[str, float]

Not implemented for multi-mode distributions.

Call lognorm.stats() on each constituent Lognorm mode (accessible via self.lognorms) and combine manually.

Source code in pyrcel/distributions.py
def stats(self) -> dict[str, float]:
    """Not implemented for multi-mode distributions.

    Call ``lognorm.stats()`` on each constituent [Lognorm][pyrcel.distributions.Lognorm] mode
    (accessible via ``self.lognorms``) and combine manually.
    """
    raise NotImplementedError()

Gamma distribution

pyrcel.distributions.Gamma is a placeholder class and is not yet implemented.

Pre-built collections

Four published aerosol size distribution climatologies are available as module-level dicts of Lognorm or MultiModeLognorm objects:

Name Source Keys
pyrcel.distributions.FN2005_single_modes Fountoukis & Nenes (2005) SM1SM5
pyrcel.distributions.NS2003_single_modes Nenes & Seinfeld (2003) SM1SM5
pyrcel.distributions.whitby_distributions Whitby (1978) marine, continental, background, urban
pyrcel.distributions.jaenicke_distributions Jaenicke (1993) Polar, Urban, Background, Maritime, Remote Continental, Rural

whitby_distributions values are lists of Lognorm objects (nucleation, accumulation, coarse modes). jaenicke_distributions values are MultiModeLognorm objects.

from pyrcel.distributions import whitby_distributions, jaenicke_distributions

marine_modes = whitby_distributions["marine"]  # [Lognorm, Lognorm, Lognorm]
urban = jaenicke_distributions["Urban"]        # MultiModeLognorm