/ concept-collection / ans-visualizer
Sign in
concept-collection / ans-visualizer
ans-visualizer / src / explanation.md
6.1 KBPreviewCodeBlameHistoryRaw

Explanation of Asymmetric Numeral Systems (ANS)#

The core idea of ANS is to encode a sequence of symbols into a single integer state, with each symbol affecting the state in a way that reflects its probability of occurrence. This encoding is reversible, allowing us to recover the original sequence from the coded integer.

Suppose that {0,1,...,S1}\{0, 1, ..., S-1\} are the symbols we want to encode (in this UI these are denoted as A,B,C,...A, B, C, ...), and assume that they occur with relative frequencies f0,...,fS1f_0, ..., f_{S-1} (positive integers). The goal is to encode a sequence of symbols (s1,s2,...,sk)(s_1, s_2, ..., s_k) into a single integer state xx.

We assume that the symbols are sampled randomly and independently of one another. Let

L=i=0S1fiL = \sum_{i=0}^{S-1} f_i

be the sum of the symbol frequencies.

ANS Encoding/Decoding#

The Symbol Table#

To encode our symbols efficiently, we need a way to map between states and symbols that reflects their frequencies. We accomplish this using an infinite table TT that maps natural numbers to symbols: T:N{0,...,S1}T: \mathbb{N} \to \{0,...,S-1\}

We construct this table with a periodic pattern where:

The Encoding/Decoding Process#

The encoding process works by maintaining a state value xx that evolves as we process each symbol. Starting from state x0x_0:

  1. To encode symbol s1s_1:
    • Find the x0x_0-th occurrence of symbol s1s_1 (counting from 0)
    • This position index becomes our new state x1x_1
  2. Repeat this process for each new symbol, using the previous state to find the next one

This process effectively "pushes" each symbol onto our state value in a way that can be reversed.

With the periodic structured table, the formula for this is

xi+1=(xi//fsi)L+C(si)+(xi mod fsi)x_{i+1} = (x_i // f_{s_i}) \cdot L + C(s_i) + (x_i \text{ mod } f_{s_i})

where //// denotes integer division and  mod \text{ mod } is the modulo operation.

Notice that for large xix_i,

xi+1xips1x_{i+1} \approx x_i \cdot p_s^{-1}

where ps=fs/Lp_s = f_s / L is the probability of symbol ss. This means that the expected value of log(xi)\log(x_i) grows by log(ps)-\log(p_s) at each step, leading to:

limkE(1klog(xk))=s=0S1pslog(ps).\lim_{k \to \infty} E(\frac{1}{k}\log(x_k)) = -\sum_{s=0}^{S-1} p_s \log(p_s).

Or in other words, if HH is the average number of bits required to encode a single symbol in the sequence, then

H=s=0S1pslog(ps)H = -\sum_{s=0}^{S-1} p_s \log(p_s)

which matches the Shannon entropy of the symbol distribution. In other words, the integer xkx_k encodes the state using the theoretically optimal number of bits.

Now for decoding. Given a final state xkx_k, we can recover the original sequence as follows:

  1. The last symbol sks_k is simply T[xk]T[x_k] (the symbol at position xkx_k)
  2. To get the previous state xk1x_{k-1}:
    • Count how many times sks_k appeared before position xkx_k
    • This count is our previous state xk1x_{k-1}
  3. Continue this process to recover all symbols in reverse order

The formula for this decoding is

xi=(xi+1//L)fsi+(xi+1 mod L)C(si)x_i = (x_{i+1} // L) \cdot f_{s_i} + (x_{i+1} \text{ mod } L) - C(s_i)

Practical ANS Implementation#

The Need for Bounded State#

In practice, we can't work with arbitrarily large integers. Our state xx would grow indefinitely as we encode more symbols, and operating on integers with arbitrarily large precision is very inefficient. We therefore need a way to keep the state within a manageable range while preserving the reversibility of the encoding.

Word-Based Streaming with Conditional Normalization#

Instead of normalizing at every step, we use a more efficient approach that only normalizes when necessary. We maintain our state as a 64-bit integer and stream out 32-bit words when the state would overflow.

Let WW be an array of 32-bit words, initialized to empty. We define:

For efficiency of calculations we assume that LL is a power of 2:

L=2lL = 2^{l}

Encoding Process#

When encoding symbol ss with current state xx:

  1. Check for normalization: If (x>>(64l))fs(x >> (64 - l)) \geq f_s, then:

    • Extract the lower 32 bits: w=x mod 232w = x \text{ mod } 2^{32}
    • Append ww to word array WW
    • Update state: x=x>>32x = x >> 32
  2. Encode the symbol:

    • Compute: remainder=x mod fsremainder = x \text{ mod } f_s
    • Compute: prefix=x//fsprefix = x // f_s
    • Update state: x=(prefix<<l)(Cs+remainder)x = (prefix << l) | (C_s + remainder)

The key insight is that normalization only occurs when the next encoding step would cause overflow, making the algorithm more efficient.

Decoding Process#

To decode from final state xx and word array WW:

  1. Extract symbol:

    • quantile=x mod Lquantile = x \text{ mod } L
    • Find symbol ss such that Csquantile<Cs+fsC_s \leq quantile < C_s + f_s
  2. Compute previous state:

    • prefix=x>>lprefix = x >> l
    • previous_state=prefixfs+(quantileCs)previous\_state = prefix \cdot f_s + (quantile - C_s)
  3. Check for denormalization: If previous_state<THRESHOLDprevious\_state < THRESHOLD and words remain:

    • Pop word ww from end of WW
    • Update: previous_state=(previous_state<<32)wprevious\_state = (previous\_state << 32) | w
  4. Continue: Set x=previous_statex = previous\_state and repeat

This approach maintains the state above the threshold while efficiently managing memory by streaming out words only when necessary.

Optimality of the Compression#

In the above, there are two sources of loss in the compression efficiency:

  1. The assumption that the frequencies sum to a power of two
  2. The need to normalize the state

The first of these can be addressed by choosing L=2lL=2^l large enough so that the proportions pi=fi/Lp_i = f_i / L are accurate enough so that the loss is negligible.

The second source of loss is more difficult to predict but can be examined empirically. (not yet explored)

Source code for the ANS Visualizer

simple_ans: A simple and efficient Python implementation of ANS

moveopenescclose