Isotope distribution calculation is a common task in mass spectrometry, analytical chemistry, and molecular formula analysis.

Given a molecular formula such as C6H12O6, the goal is to predict the masses and relative abundances of all possible isotopologues.

This post summarizes the main algorithms used to calculate isotope distributions, including:

  • Direct enumeration
  • Multinomial distributions
  • Polynomial representation
  • Convolution
  • Peak merging and pruning
  • Exponentiation by squaring
  • FFT-based convolution
  • Sparse and dense representations

1. The Basic Problem

Each chemical element consists of naturally occurring isotopes.

For example, carbon has two major stable isotopes:

Isotope Exact Mass Natural Abundance
12C 12.000000 98.93%
13C 13.003355 1.07%

For a molecule containing multiple carbon atoms, each atom can independently take one of these isotope states.

For two carbon atoms, the possible isotope compositions are:

12C-12C
12C-13C
13C-13C

If the natural abundances are p12 and p13, their probabilities are:

P(12C2)    = p12²
P(12C 13C) = 2 × p12 × p13
P(13C2)    = p13²

This is simply a binomial distribution.

When an element contains more than two isotopes, the problem becomes a multinomial distribution.


2. Multinomial Enumeration

Suppose an element contains k isotopes and appears n times in a molecule.

An isotope composition can be represented as:

(n1, n2, ..., nk)

with:

n1 + n2 + ... + nk = n

The probability of the composition is:

P =
    n! / (n1! × n2! × ... × nk!)
    × p1^n1
    × p2^n2
    × ...
    × pk^nk

where pi is the natural abundance of isotope i.

The exact mass is:

M =
    n1 × m1
    + n2 × m2
    + ...
    + nk × mk

where mi is the exact mass of isotope i.

The number of possible isotope compositions is:

C(n + k - 1, k - 1)

Therefore, exhaustive enumeration becomes expensive when n or k becomes large.

Advantages

  • Exact and easy to understand
  • Provides explicit isotope compositions
  • Useful for small molecules

Disadvantages

  • Combinatorial growth
  • High memory usage for large molecules
  • Poor scalability

3. Polynomial Representation

A more general way to understand isotope distributions is through polynomials.

For an element with isotopes:

(m1, p1)
(m2, p2)
...
(mk, pk)

we can define an isotope polynomial:

P(x) =
    p1 × x^m1
    + p2 × x^m2
    + ...
    + pk × x^mk

For n identical atoms:

P_n(x) = P(x)^n

For a molecule such as:

C6H12O6

the complete isotope distribution can conceptually be written as:

P_molecule(x)
    = P_C(x)^6
    × P_H(x)^12
    × P_O(x)^6

Each resulting term represents:

probability × x^mass

Therefore, isotope distribution calculation can be viewed as a polynomial multiplication problem.


4. Convolution

Polynomial multiplication is equivalent to convolution.

Suppose two isotope distributions are:

A = {(m_i, p_i)}
B = {(m_j, p_j)}

Their convolution is:

A * B =
{
    (m_i + m_j, p_i × p_j)
}

for every pair of peaks from A and B.

A simple Python implementation is:

def convolve(a, b):
    result = []

    for mass_a, prob_a in a:
        for mass_b, prob_b in b:
            result.append(
                (
                    mass_a + mass_b,
                    prob_a * prob_b
                )
            )

    return result

A molecular distribution can then be calculated using repeated convolution:

distribution = [(0.0, 1.0)]

for atom in atoms:
    distribution = convolve(
        distribution,
        isotope_distribution(atom)
    )

Conceptually:

Atom 1
   ↓
convolution
   ↓
Atom 2
   ↓
convolution
   ↓
Atom 3
   ↓
...
   ↓
Molecular isotope distribution

The major problem is that the number of states can grow extremely quickly.


5. Peak Merging

Different isotope combinations can produce identical or nearly identical masses.

Instead of storing every state independently, nearby peaks can be merged.

For a group of peaks:

(m1, p1)
(m2, p2)
...
(mn, pn)

the total probability is:

P = Σ pi

A probability-weighted centroid mass can be calculated as:

M = Σ(mi × pi) / Σpi

A practical isotope algorithm therefore often follows:

convolution
    ↓
merge nearby peaks
    ↓
remove insignificant peaks
    ↓
next convolution

For example:

distribution = convolve(a, b)
distribution = merge_peaks(distribution, tolerance)
distribution = prune(distribution, threshold)

The mass tolerance should depend on the required resolution.

For a low-resolution isotope envelope, aggressive merging may be acceptable.

For high-resolution mass spectrometry, fine isotope structures may need to remain separated.


6. Probability Pruning

Most theoretically possible isotopologues have extremely small probabilities.

They can often be safely removed.

For example:

if probability >= 1e-12:
    keep_peak()

This is known as probability pruning.

Another strategy is to preserve a target fraction of the total probability:

Σ P_kept >= 0.999999

A third strategy is to retain only the largest K peaks:

peaks = sorted(
    peaks,
    key=lambda x: x[1],
    reverse=True
)

peaks = peaks[:K]

Pruning can dramatically reduce memory usage and computational complexity.

However, pruning introduces approximation.

There is therefore a trade-off:

smaller threshold
    ↓
higher accuracy
    ↓
more peaks
    ↓
higher computational cost

7. Exponentiation by Squaring

Consider a molecular formula containing:

C1000

A naive algorithm would perform carbon convolution approximately 1000 times.

This is unnecessary.

The distribution can instead be calculated as:

P_C(x)^1000

using exponentiation by squaring.

The general idea is:

x^8 = ((x²)²)²

instead of multiplying x eight times.

A simplified implementation is:

def power_distribution(base, n):
    result = [(0.0, 1.0)]

    while n > 0:

        if n % 2 == 1:
            result = convolve(result, base)

        base = convolve(base, base)

        n //= 2

    return result

In practice, merging and pruning should be applied after convolution:

result = convolve(result, base)
result = merge_peaks(result)
result = prune(result)

The number of exponentiation stages is reduced from approximately:

O(n)

to:

O(log n)

although the total complexity still depends on the number of surviving isotope peaks.


8. FFT-Based Convolution

If masses are discretized onto a uniform grid, isotope distributions can be represented as arrays.

For example:

array index → mass bin
array value → isotope probability

Then convolution can be calculated using the Fast Fourier Transform (FFT).

The convolution theorem states:

A * B = IFFT(
    FFT(A) × FFT(B)
)

A direct dense convolution typically requires approximately:

O(N²)

operations.

FFT-based convolution reduces this to approximately:

O(N log N)

which can be significantly faster for large distributions.

A conceptual implementation is:

FA = fft(A)
FB = fft(B)

FC = FA * FB

C = ifft(FC)

However, FFT methods require a discretized mass axis.

For example:

bin width = 0.001 Da

This introduces a trade-off between:

  • Mass accuracy
  • Memory consumption
  • Computational speed

Smaller bins provide better mass accuracy but require larger arrays.

Therefore, FFT methods are especially attractive for large and dense isotope distributions.


9. Sparse vs. Dense Representations

There are two common ways to store isotope distributions.

Sparse Representation

Only existing peaks are stored:

peaks = [
    (180.06339, 0.922),
    (181.06675, 0.063),
    (182.06760, 0.014),
]

This representation is useful when the distribution contains relatively few peaks.

Advantages include:

  • Low memory usage
  • Exact isotope masses can be preserved
  • Easy probability pruning

It is particularly suitable for high-resolution isotope calculations.

Dense Representation

A dense representation stores the entire mass axis:

intensity[mass_bin] = probability

For example:

0     → 0
1     → 0
2     → 0.0002
3     → 0.0041
4     → 0.0315
...

Advantages include:

  • Efficient vectorized operations
  • Easy FFT convolution
  • Good performance for dense distributions

A useful rule of thumb is:

few peaks + exact mass
        ↓
sparse representation

many peaks + discretized mass
        ↓
dense / FFT representation

Hybrid implementations can also switch representations dynamically.


10. Nominal Mass vs. Fine Isotope Structure

The required mass resolution strongly affects the algorithm.

At low resolution, peaks are often represented simply as:

M
M+1
M+2
M+3
...

For example, several isotopic substitutions may contribute to the same nominal M+2 peak.

At high resolution, however, the M+2 region can contain contributions from:

two 13C substitutions
one 18O substitution
one 34S substitution
...

These compositions do not have exactly the same mass.

Therefore:

nominal isotope distribution
            ≠
fine isotope structure

An algorithm intended for high-resolution mass spectrometry must preserve isotope mass defects and use carefully controlled peak merging.


11. Numerical Stability

For large molecules, isotope probabilities can become extremely small.

Directly calculating:

p1^n1 × p2^n2 × ...

may cause floating-point underflow.

A more stable solution is to calculate probabilities in log space.

Instead of:

P =
    n! / (n1! n2! ...)
    × p1^n1
    × p2^n2
    × ...

calculate:

log(P)
    =
    log(n!)
    - Σ log(ni!)
    + Σ ni × log(pi)

The factorial terms can be calculated using the log-gamma function:

log(n!) = lgamma(n + 1)

For example:

from math import lgamma, log

log_p = lgamma(n + 1)

for ni, pi in zip(counts, probabilities):
    log_p -= lgamma(ni + 1)
    log_p += ni * log(pi)

The final probabilities can then be normalized:

P_i = P_i / ΣP_i

Floating-point masses also require care.

Avoid relying on exact comparisons such as:

mass1 == mass2

Instead, use an explicit mass tolerance or mass-bin definition.


12. A Practical Algorithm

For most general-purpose applications, a good implementation combines:

Sparse representation
        +
Convolution
        +
Exponentiation by squaring
        +
Peak merging
        +
Probability pruning

The high-level workflow is:

Molecular Formula
       ↓
Parse Elements
       ↓
Load Isotope Data
       ↓
Calculate Element Distribution
       ↓
Exponentiation by Squaring
       ↓
Convolution
       ↓
Peak Merging
       ↓
Probability Pruning
       ↓
Combine Element Distributions
       ↓
Normalize
       ↓
Final Isotope Distribution

Pseudo-code:

distribution = [(0.0, 1.0)]

for element, count in formula.items():

    base = isotope_table[element]

    element_distribution = power_distribution(
        base,
        count
    )

    distribution = convolve(
        distribution,
        element_distribution
    )

    distribution = merge_peaks(
        distribution,
        tolerance
    )

    distribution = prune(
        distribution,
        threshold
    )

distribution = normalize(distribution)

distribution.sort(
    key=lambda peak: peak[0]
)

13. Converting Mass to m/z

Mass spectrometers generally measure mass-to-charge ratio (m/z), rather than neutral molecular mass.

For an ion with charge z:

m/z = ion_mass / |z|

The ion mass may also need to include adducts.

For example:

[M + H]+
[M + Na]+
[M - H]-
[M + 2H]2+

Therefore, a complete calculation pipeline may be:

Molecular formula
      ↓
Neutral isotope distribution
      ↓
Add/remove adduct composition
      ↓
Calculate ion masses
      ↓
Apply charge
      ↓
Calculate m/z
      ↓
Merge according to instrument resolution
      ↓
Normalize intensity

This distinction is important because isotope-distribution calculation and ion m/z calculation are related but separate steps.


14. Algorithm Comparison

The major approaches can be summarized as follows:

Algorithm Accuracy Performance Best Use Case
Direct enumeration Exact Poor for large systems Small molecules
Multinomial enumeration Exact Moderate/Poor Explicit isotopologues
Sparse convolution High Good General-purpose calculation
Convolution + pruning Approximate Very good Large molecules
Exponentiation by squaring High Very good Large atom counts
FFT convolution Grid-dependent Excellent Large dense distributions
Nominal-mass DP Nominal only Excellent Low-resolution envelopes

There is no universally optimal algorithm.

The best approach depends on:

  • Molecular size
  • Number of isotope species
  • Required mass accuracy
  • Instrument resolution
  • Probability cutoff
  • Whether explicit isotopologues are required
  • Available CPU time and memory

15. Recommended General Architecture

A practical isotope calculator can use the following architecture:

                    ┌──────────────────┐
                    │ Molecular Formula│
                    └────────┬─────────┘
                             │
                             ▼
                    ┌──────────────────┐
                    │  Formula Parser  │
                    └────────┬─────────┘
                             │
                             ▼
                    ┌──────────────────┐
                    │  Isotope Table   │
                    └────────┬─────────┘
                             │
                             ▼
                 ┌───────────────────────┐
                 │ Element Distributions │
                 └───────────┬───────────┘
                             │
                             ▼
                 ┌───────────────────────┐
                 │ Fast Exponentiation   │
                 └───────────┬───────────┘
                             │
                             ▼
                 ┌───────────────────────┐
                 │     Convolution       │
                 └───────────┬───────────┘
                             │
                   ┌─────────┴─────────┐
                   ▼                   ▼
             Peak Merging          Pruning
                   │                   │
                   └─────────┬─────────┘
                             ▼
                    ┌──────────────────┐
                    │  Normalization   │
                    └────────┬─────────┘
                             │
                             ▼
                    ┌──────────────────┐
                    │ Isotope Spectrum │
                    └──────────────────┘

This design provides a good balance between:

accuracy, performance, memory usage, and implementation complexity.


Conclusion

Isotope distribution calculation is fundamentally a probabilistic convolution problem.

Each atom contributes a discrete probability distribution:

isotope mass → natural abundance

and the molecular distribution is obtained by combining these atomic distributions:

Molecular Distribution
    =
Atomic Distribution 1
    *
Atomic Distribution 2
    *
...
    *
Atomic Distribution N

The mathematically simplest solution is direct enumeration, but this becomes computationally expensive for large molecules.

In practice, efficient isotope-distribution engines typically combine:

polynomial representation
        +
convolution
        +
sparse storage
        +
fast exponentiation
        +
peak merging
        +
probability pruning

For very large or dense distributions, FFT-based convolution can provide additional performance improvements.

The key engineering challenge is not generating isotope combinations themselves, but controlling the rapid growth of the state space while preserving the mass accuracy and probability information required by the application.