DM_LOGSUM_NORM

This function uses a log-domain normalization step to transform unnormalized log-scores into categorical probabilities. It is useful for Bayesian workflows where posterior quantities are accumulated in log space.

Given log-values \ell_1,\ldots,\ell_K, it computes:

\log Z = \log\left(\sum_{i=1}^K e^{\ell_i}\right), \qquad p_i = \frac{e^{\ell_i}}{\sum_{j=1}^K e^{\ell_j}}

Computing \log Z with a stable log-sum-exp routine prevents overflow and underflow.

Excel Usage

=DM_LOGSUM_NORM(log_values)
  • log_values (list[list], required): Unnormalized log-scores as a 2D numeric range.

Returns (list[list]): 2D array with one row for log normalizer and one row for normalized probabilities.

Example 1: Three-category log-scores normalize to probabilities

Inputs:

log_values
0 -1 -2

Excel formula:

=DM_LOGSUM_NORM({0,-1,-2})

Expected output:

Result
0.407606
0.665241 0.244728 0.0900306
Example 2: Mixed-scale scores remain numerically stable

Inputs:

log_values
15 2 -5 -10

Excel formula:

=DM_LOGSUM_NORM({15,2,-5,-10})

Expected output:

Result
15
0.999998 0.00000226032 2.06115e-9 1.38879e-11
Example 3: Matrix-shaped log-values are flattened for normalization

Inputs:

log_values
1.2 0.4
-0.6 -2.5

Excel formula:

=DM_LOGSUM_NORM({1.2,0.4;-0.6,-2.5})

Expected output:

Result
1.6943
0.609997 0.274089 0.100832 0.0150813
Example 4: Very negative log-values avoid underflow issues

Inputs:

log_values
-900 -901 -903

Excel formula:

=DM_LOGSUM_NORM({-900,-901,-903})

Expected output:

Result
-899.651
0.705385 0.259496 0.035119

Python Code

import numpy as np
from scipy.special import logsumexp as scipy_logsumexp

def dm_logsum_norm(log_values):
    """
    Compute a stable log normalizer and normalized probabilities from log-values.

    See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.special.logsumexp.html

    This example function is provided as-is without any representation of accuracy.

    Args:
        log_values (list[list]): Unnormalized log-scores as a 2D numeric range.

    Returns:
        list[list]: 2D array with one row for log normalizer and one row for normalized probabilities.
    """
    try:
        def to2d(v):
            return [[v]] if not isinstance(v, list) else v

        log_values = to2d(log_values)

        if not isinstance(log_values, list) or not all(isinstance(row, list) for row in log_values):
            return "Error: log_values must be a 2D list"

        flat = []
        for row in log_values:
            for val in row:
                try:
                    flat.append(float(val))
                except (TypeError, ValueError):
                    continue

        if len(flat) == 0:
            return "Error: log_values must contain at least one numeric value"

        arr = np.asarray(flat, dtype=float)
        log_z = float(scipy_logsumexp(arr))
        probs = np.exp(arr - log_z).tolist()

        width = len(probs)
        first_row = [log_z] + [""] * max(0, width - 1)

        return [first_row, probs]
    except Exception as e:
        return f"Error: {str(e)}"

Online Calculator

Unnormalized log-scores as a 2D numeric range.