<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://rabatinb.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://rabatinb.com/" rel="alternate" type="text/html" /><updated>2026-07-12T13:41:20+00:00</updated><id>https://rabatinb.com/feed.xml</id><title type="html">Braňo’s playground</title><subtitle>My personal portfolio.</subtitle><author><name>Branislav Rabatin</name></author><entry><title type="html">Fractals I: when iteration becomes art</title><link href="https://rabatinb.com/blog/fractals-part-i/" rel="alternate" type="text/html" title="Fractals I: when iteration becomes art" /><published>2026-07-05T10:00:00+00:00</published><updated>2026-07-05T10:00:00+00:00</updated><id>https://rabatinb.com/blog/fractals1</id><content type="html" xml:base="https://rabatinb.com/blog/fractals-part-i/"><![CDATA[<p>Laws of physics are often shockingly compact. A force law, an equation of motion, a conservation rule: sometimes, the exact mathematical principle governing millions of real-world applications fits on one line. And yet the world built from those rules is all but compact or simple. Coastlines wrinkle. Ferns repeat themselves leaf inside leaf. Clouds and turbulent gas form structure inside structure. Even DNA, branching trees, lightning, river networks, and snowflakes seem to share the same feature: repeat a simple instruction many times, and simplicity starts to look complicated.</p>

<p>So let’s begin with the least glamorous toy model possible. Pick a number $C$, start at $x_0 = 0$, and repeat</p>

<div class="math-center">
$$
    x_{n+1} = x_n^2 + C.
$$
</div>

<p>That is the whole machine. No geometry yet, no colors, no complex numbers, no pretty fractal boundary. Just one real number being fed back into the same rule over and over again. But already, something interesting happens: for some values of $C$, the sequence calmly settles down; for others it stays bounded but refuses to settle into one value; and beyond a certain range it runs away to infinity.</p>

<p>A few sample values make the split visible:</p>

<table class="centered-table">
  <thead>
    <tr>
      <th style="text-align: right">$C$</th>
      <th style="text-align: right">$x_{1}$</th>
      <th style="text-align: right">$x_{5}$</th>
      <th style="text-align: right">$x_{10}$</th>
      <th style="text-align: right">$x_{20}$</th>
      <th style="text-align: right">$x_{30}$</th>
      <th style="text-align: right">$x_{31}$</th>
      <th style="text-align: left">What it is doing</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: right">$-2.1$</td>
      <td style="text-align: right">$-2.1$</td>
      <td style="text-align: right">$68$</td>
      <td style="text-align: right">$4.33 \times 10^{58}$</td>
      <td style="text-align: right">$\infty$</td>
      <td style="text-align: right">$\infty$</td>
      <td style="text-align: right">$\infty$</td>
      <td style="text-align: left">diverges quickly</td>
    </tr>
    <tr>
      <td style="text-align: right">$-2.01$</td>
      <td style="text-align: right">$-2.01$</td>
      <td style="text-align: right">$3.98$</td>
      <td style="text-align: right">$1.66 \times 10^{18}$</td>
      <td style="text-align: right">$\infty$</td>
      <td style="text-align: right">$\infty$</td>
      <td style="text-align: right">$\infty$</td>
      <td style="text-align: left">diverges quickly</td>
    </tr>
    <tr>
      <td style="text-align: right">$-2$</td>
      <td style="text-align: right">$-2$</td>
      <td style="text-align: right">$2$</td>
      <td style="text-align: right">$2$</td>
      <td style="text-align: right">$2$</td>
      <td style="text-align: right">$2$</td>
      <td style="text-align: right">$2$</td>
      <td style="text-align: left">converges quickly</td>
    </tr>
    <tr>
      <td style="text-align: right">$-1.5$</td>
      <td style="text-align: right">$-1.5$</td>
      <td style="text-align: right">$-1.11$</td>
      <td style="text-align: right">$-0.0767$</td>
      <td style="text-align: right">$-0.493$</td>
      <td style="text-align: right">$0.71$</td>
      <td style="text-align: right">$-0.996$</td>
      <td style="text-align: left">bounded, oscillatory</td>
    </tr>
    <tr>
      <td style="text-align: right">$-1$</td>
      <td style="text-align: right">$-1$</td>
      <td style="text-align: right">$-1$</td>
      <td style="text-align: right">$0$</td>
      <td style="text-align: right">$0$</td>
      <td style="text-align: right">$0$</td>
      <td style="text-align: right">$-1$</td>
      <td style="text-align: left">bounded, oscillatory</td>
    </tr>
    <tr>
      <td style="text-align: right">$-0.75$</td>
      <td style="text-align: right">$-0.75$</td>
      <td style="text-align: right">$-0.693$</td>
      <td style="text-align: right">$-0.308$</td>
      <td style="text-align: right">$-0.354$</td>
      <td style="text-align: right">$-0.377$</td>
      <td style="text-align: right">$-0.608$</td>
      <td style="text-align: left">converges slowly</td>
    </tr>
    <tr>
      <td style="text-align: right">$-0.5$</td>
      <td style="text-align: right">$-0.5$</td>
      <td style="text-align: right">$-0.405$</td>
      <td style="text-align: right">$-0.358$</td>
      <td style="text-align: right">$-0.366$</td>
      <td style="text-align: right">$-0.366$</td>
      <td style="text-align: right">$-0.366$</td>
      <td style="text-align: left">converges</td>
    </tr>
    <tr>
      <td style="text-align: right">$0$</td>
      <td style="text-align: right">$0$</td>
      <td style="text-align: right">$0$</td>
      <td style="text-align: right">$0$</td>
      <td style="text-align: right">$0$</td>
      <td style="text-align: right">$0$</td>
      <td style="text-align: right">$0$</td>
      <td style="text-align: left">converges quickly</td>
    </tr>
    <tr>
      <td style="text-align: right">$0.25$</td>
      <td style="text-align: right">$0.25$</td>
      <td style="text-align: right">$0.388$</td>
      <td style="text-align: right">$0.431$</td>
      <td style="text-align: right">$0.46$</td>
      <td style="text-align: right">$0.472$</td>
      <td style="text-align: right">$0.472$</td>
      <td style="text-align: left">converges slowly</td>
    </tr>
    <tr>
      <td style="text-align: right">$0.26$</td>
      <td style="text-align: right">$0.26$</td>
      <td style="text-align: right">$0.416$</td>
      <td style="text-align: right">$0.483$</td>
      <td style="text-align: right">$0.602$</td>
      <td style="text-align: right">$4$</td>
      <td style="text-align: right">$16.3$</td>
      <td style="text-align: left">diverges slowly</td>
    </tr>
    <tr>
      <td style="text-align: right">$0.3$</td>
      <td style="text-align: right">$0.3$</td>
      <td style="text-align: right">$0.554$</td>
      <td style="text-align: right">$1.04$</td>
      <td style="text-align: right">$1.02 \times 10^{91}$</td>
      <td style="text-align: right">$\infty$</td>
      <td style="text-align: right">$\infty$</td>
      <td style="text-align: left">diverges quickly</td>
    </tr>
  </tbody>
</table>

<p>Instead of checking a few hand-picked values of $C$, we can scan a whole interval.
In the next picture, the horizontal axis is the parameter $C$, the vertical axis is the
iteration number $n$, and the color shows the value of $x_n$. The bright saturated
regions are where the orbit has escaped so hard that the plot has to clip the value just
to remain readable.</p>

<figure style="text-align: center;">
    <img src="/fractal/2D/real_quadratic_density.png" alt="Density plot of the real iteration x_{n+1} = x_n^2 + C" style="width: 100%; display: block; margin: auto;" />
    <figcaption style="font-size: smaller; color: gray;">
        Iterating $x_{n+1}=x_n^2+C$ from $x_0=0$. The dashed lines mark
        $C=-2$, $C=-3/4$, and $C=1/4$. Outside $[-2,1/4]$, the orbit escapes.
        Between $-3/4$ and $1/4$, it settles to a fixed value. Between $-2$ and
        $-3/4$, it remains bounded but starts to split into oscillatory and chaotic behavior.
    </figcaption>
</figure>

<p>This picture is already suspiciously complicated on the left. The region
$C &lt; -2$ runs away, the region $C &gt; 1/4$ runs away, but the strip between
$-2$ and $-3/4$ is not boringly convergent. It contains bands, splits, and thin
structure: the orbit is bounded, but it keeps oscillating between several stable values.</p>

<p>Another way to see the same transition is to plot $x_n$ as a function of $C$ for
several different iteration numbers. Early iterates are smooth curves. Later iterates
fold, split, and pile up. By the time we compare consecutive late iterates like
$x_{200}$, $x_{201}$, and $x_{202}$, the convergent region has collapsed onto a
single curve, while the oscillatory region has separated into different branches.</p>

<figure style="text-align: center;">
    <img src="/fractal/2D/real_quadratic_iterates.png" alt="Selected iterates x_n(C) of the real quadratic map" style="width: 100%; display: block; margin: auto;" />
    <figcaption style="font-size: smaller; color: gray;">
        Selected functions $C \mapsto x_n(C)$. In the fixed-point region, late iterates
        lie almost on top of one another. In the bounded oscillatory region, consecutive
        late iterates separate into branches, exposing the period-doubling structure.
    </figcaption>
</figure>

<p>The table is only a handful of snapshots. The plots show the structure behind them. For real $C$, the orbit of $0$ stays bounded exactly on $[-2,1/4]$. The right-hand part, $[-3/4,1/4]$, is the tame region where the sequence settles to a fixed point. The left-hand part, $[-2,-3/4)$, is still bounded, but it is no longer tame: the orbit can cycle, double its period, and eventually wander chaotically.</p>

<details class="aside-box aside-box--proof">
  <summary class="aside-box__summary">
    <span class="aside-box__heading">
      <span class="aside-box__kicker">Aside</span><span class="aside-box__title">Why these real intervals appear</span></span>
    <span class="aside-box__toggle" aria-hidden="true">
      <span class="aside-box__toggle-text aside-box__toggle-text--closed">Show proof</span>
      <span class="aside-box__toggle-text aside-box__toggle-text--open">Hide proof</span>
    </span>
  </summary>
  <div class="aside-box__body">
    
<p>Let</p>

<div class="math-center">
$$
    f_C(x) = x^2 + C, \qquad x_0 = 0, \qquad x_{n+1} = f_C(x_n).
$$
</div>

<p>A convergent orbit must approach a fixed point $L$, which must satisfy $L = f_C (L)$, so</p>

<div class="math-center">
$$
    L = L^2 + C.
$$
</div>

<p>In other words, if the orbit lands exactly on $L$, it stays there forever.</p>

<p>We can rewrite this relation for $C$,</p>

<div class="math-center">
$$
    C = L - L^2.
$$
</div>

<p>The convergence towards the fixed point can be examined by considering a value slightly off $L$:</p>

<div class="math-center">
$$
    x_n = L + \varepsilon_n
$$
</div>

<p>where $\varepsilon_n$ is a tiny error from the fixed point $L$.</p>

<p>The next iterate is</p>

<div class="math-center">
$$
    x_{n+1} = f_C (L + \varepsilon_n)
$$
</div>

<p>Which, for small $\varepsilon_n$ can be expanded into Taylor series in $\varepsilon_n$:</p>

<div class="math-center">
$$
    x_{n+1} \approx f_C (L) + f^\prime_C (L) \varepsilon_n
$$
</div>

<p>However, since $L$ is a fixed point of $f_C$, then $f_C (L) = L$ and thus</p>

<div class="math-center">
$$
    x_{n+1} \approx L + f^\prime_C (L) \varepsilon_n
$$
</div>

<p>Now, we assume that $x_{n+1}$ can also be written as $L$ with a small correction, $x_{n+1} = L + \varepsilon_{n+1}$. This leaves us with the following equation for the error propagation:</p>

<div class="math-center">
$$
    \varepsilon_{n+1} \approx f^\prime_C (L) \varepsilon_n
$$
</div>

<p>For the convergence to happen, the fixed point needs to be attractive, i.e. $\lvert \varepsilon_{n+1} \rvert &lt; \lvert \varepsilon_n \rvert$, which happens when</p>

<div class="math-center">
$$
    \lvert f_C'(L) \rvert = \lvert 2L \rvert &lt; 1,
$$
</div>

<p>with the endpoint cases $\lvert 2L \rvert=1$ giving the slower borderline behavior. Thus the attracting range is governed by</p>

<div class="math-center">
$$
    -\frac{1}{2} \leq L \leq \frac{1}{2}.
$$
</div>

<p>On that interval, the function $C=L-L^2$ ranges from $-3/4$ to $1/4$. That gives the fixed-point convergence interval</p>

<div class="math-result">
$$
    -\frac{3}{4} \leq C \leq \frac{1}{4}.
$$
</div>

<p>This explains the fixed-point convergence interval, but boundedness is slightly wider.
The fixed-point argument already gives boundedness on $[-3/4,1/4]$, so it remains to check the left interval $[-2,-3/4]$.</p>

<p>Assume that $-2 \leq C \leq -3/4$. We will show, by induction, that for all $n\geq 1$,</p>

<div class="math-center">
$$
    C \leq x_n \leq C^2+C.
$$
</div>

<p>The base case holds because</p>

<div class="math-center">
$$
    x_1=C,
    \qquad
    x_2=C^2+C.
$$
</div>

<p>Now assume that</p>

<div class="math-center">
$$
    C \leq x_n \leq C^2+C.
$$
</div>

<p>Then</p>

<div class="math-center">
$$
    0 \leq x_n^2 \leq \max\left(C^2,\,(C^2+C)^2\right).
$$
</div>

<p>But</p>

<div class="math-center">
$$
    (C^2+C)^2
    = C^4+2C^3+C^2
    = C^2 + C^3(C+2).
$$
</div>

<p>For $-2\leq C\leq -3/4$, we have $C^3\leq 0$ and $C+2\geq 0$, so</p>

<div class="math-center">
$$
    C^3(C+2)\leq 0.
$$
</div>

<p>Therefore</p>

<div class="math-center">
$$
    (C^2+C)^2 \leq C^2,
$$
</div>

<p>and hence</p>

<div class="math-center">
$$
    0 \leq x_n^2 \leq C^2.
$$
</div>

<p>Adding $C$ everywhere gives</p>

<div class="math-center">
$$
    C \leq x_n^2+C \leq C^2+C.
$$
</div>

<p>Since $x_{n+1}=x_n^2+C$, this is exactly</p>

<div class="math-center">
$$
    C \leq x_{n+1} \leq C^2+C.
$$
</div>

<p>So the interval $[C,C^2+C]$ traps the orbit, and the orbit is bounded for every $C\in[-2,-3/4]$.</p>

<p>Outside $[-2,1/4]$, the orbit escapes. If $C&gt;1/4$, then the equation
$L=L^2+C$ has no real solution, so the orbit cannot settle to a real fixed point;
starting from $0$, it eventually increases without bound. If $C&lt;-2$, then
$x_1=C&lt;-2$ and $x_2=C^2+C&gt;2$, after which the squaring term drives the orbit to
larger and larger positive values.</p>

<p>So, for the real one-dimensional map:</p>

<div class="math-result">
$$
    \text{bounded orbit from }0: \quad -2 \leq C \leq \frac{1}{4},
$$
$$
    \text{fixed-point convergence:} \quad -\frac{3}{4} \leq C \leq \frac{1}{4}.
$$
</div>

<p>The interval $[-2,-3/4)$ is the fun warning sign: bounded does not necessarily mean convergent.</p>

  </div>
</details>

<p>This result is intriguing, but it is still not very visual. A real number can only move left and right on a line. To get to the fun picture everyone is surely waiting on by now, we let both the starting value and the parameter live in the complex plane. The same little rule,</p>

<div class="math-center">
$$
    z_{n+1} = z_n^2 + c,
$$
</div>

<p>now acts as a sorting machine on the complex plane: for which values of $c$ does the orbit of $z=0$ remain bounded, and for which does it escape? The boundary between those two outcomes is where the plain recurrence turns into the Mandelbrot set.</p>

<p>Since $z$ and $c$ are now complex numbers, we can represent them in the complex plane using their real and imaginary parts. Writing</p>

<div class="math-center">
$$
\begin{aligned}
    z_n &amp;= a_n + i b_n \\
    c &amp;= x + i y
\end{aligned}
$$
</div>

<p>we can rewrite the iteration equation for $z$ as follows</p>

<div class="math-center">
$$
\begin{aligned}
    a_{n+1} &amp;= a_n^2 - b_n^2 + x \\
    b_{n+1} &amp;= 2 a_n b_n + y
\end{aligned}
$$
</div>

<p>So every point $c=x+iy$ in the complex plane gives us a different orbit of the starting value $z_0=0$. To decide whether that orbit is escaping, we watch its modulus,</p>

<div class="math-center">
$$
    \lvert z_n \rvert = \sqrt{a_n^2+b_n^2}.
$$
</div>

<p>For this map, once an iterate satisfies $\lvert z_n \rvert &gt; 2$, the orbit is guaranteed to escape to infinity. If no iterate crosses that threshold after many steps, we treat the point as bounded for the purposes of the picture.</p>

<p>The fine structure of the boundary is not something we can simply draw from a closed-form formula. So we use a crude and naive approach: take a grid of points in the complex plane, treat each point as a candidate value of $c$, iterate the map from $z_0=0$, and check whether the orbit appears bounded or escapes. Then we color the point accordingly. Even this crude experiment is enough for the Mandelbrot set to begin revealing itself.</p>

<p>At first, we keep the experiment deliberately brutal: no colors or special treatment of the boundary. A point is black if its orbit survives the chosen number of iterations, and white if it escapes. This is not yet a proof of membership in the Mandelbrot set, because a point that survives many iterations could still escape later. But it is a perfectly good numerical first pass: the black region is our current approximation to the set.</p>

<p>The carousel below shows the same region of the complex plane sampled on finer and finer grids. The shape does not change; only our sampling of it improves. At low resolution, the set looks like a blocky silhouette. As the grid gets finer, the main cardioid, the large bulb on the left, the smaller satellite bulbs, and the rough boundary become much clearer.</p>

<figure style="text-align: center;">
    <div class="plot-carousel">
        <button class="carousel-btn left" onclick="changeMandelbrotResolution(-1)" title="Click to see previous grid size">&#8592;</button>

        <img id="mandelbrot-resolution-carousel-img" src="/fractal/mandelbrot/mandelbrot_0064.png" alt="Mandelbrot set sampled on a 64 by 64 grid" />

        <button class="carousel-btn right" onclick="changeMandelbrotResolution(1)" title="Click to see next grid size">&#8594;</button>
    </div>

    <figcaption style="font-size: smaller; color: gray;">
        Black/white escape test for the same region of the complex plane.
        Black points did not escape within the iteration limit; white points did.
        Current grid: <span id="mandelbrot-resolution-carousel-label">64 × 64</span>.
    </figcaption>
</figure>

<p>The black-and-white version is useful because it shows the essential yes/no question: bounded or escaping? But it throws away a lot of information. Escaping points do not all behave the same way. Some fly away almost immediately, while others hover near the boundary for many iterations before finally leaving. Likewise, the bounded-looking interior is not uniform. Some regions settle to one attracting value, while others settle into repeating cycles.</p>

<p>So we can reuse the same computation, but color it more honestly. Outside the set, the color records escape speed: pale regions escape quickly and are dynamically far from the interesting boundary, while stronger colors mark points that escape slowly. Inside the set, the color records the detected attracting period after the orbit has settled: dark blue means fixed-point convergence, blue means period-2 oscillation, teal means period-3, green means period-4, yellow-green means period-5, orange means period-6, red means period-7, and purple means period-8. Black interior points are bounded in this numerical test, but were not classified into one of those periods; they may be higher-period, boundary-adjacent, or simply unresolved at this iteration depth.</p>

<figure style="text-align: center;">
    <div class="plot-carousel">
        <button class="carousel-btn left" onclick="changeMandelbrotColorResolution(-1)" title="Click to see previous grid size">&#8592;</button>

        <img id="mandelbrot-color-resolution-carousel-img" src="/fractal/mandelbrot/mandelbrot_color_0064.png" alt="Colored Mandelbrot set sampled on a 64 by 64 grid" />

        <button class="carousel-btn right" onclick="changeMandelbrotColorResolution(1)" title="Click to see next grid size">&#8594;</button>
    </div>

    <figcaption style="font-size: smaller; color: gray;">
        Colored escape-time and interior-period view of the same grid experiment.
        Outside colors show escape speed; inside colors show the detected attracting period.
        Current grid: <span id="mandelbrot-color-resolution-carousel-label">64 × 64</span>.
    </figcaption>
</figure>

<p>In the colored version we can see that the points far away from the set escape quickly and fade toward the background. Points near the boundary escape slowly, which is why the exterior develops colored halos and filaments. Inside, the large regions of stable behavior are the complex-plane relatives of what we already saw on the real line: convergence, oscillation, period-doubling, and more complicated bounded behavior.</p>

<p>It is not an accident that the interior segments of our Mandelbrot set feature unique periodic behavior per segment. The interior of the Mandelbrot set contains infinitely many bulb-like regions, called hyperbolic components, each organized around an attracting periodic cycle. For example, the main cardioid (bulb-looking thing) is a 1-period component, meaning the iterations $z \to z^2 + c$ settle to a single value (depending on $c$) within the interior of the cardioid. Such limiting value can then be obtained by solving</p>

<div class="math-center">
$$
    z = z^2 + c
$$
</div>

<p>for $z$.</p>

<details class="aside-box aside-box--proof">
  <summary class="aside-box__summary">
    <span class="aside-box__heading">
      <span class="aside-box__kicker">Aside</span><span class="aside-box__title">Single-valued cardioid</span></span>
    <span class="aside-box__toggle" aria-hidden="true">
      <span class="aside-box__toggle-text aside-box__toggle-text--closed">Show</span>
      <span class="aside-box__toggle-text aside-box__toggle-text--open">Hide</span>
    </span>
  </summary>
  <div class="aside-box__body">
    
<p>Let’s assume, that the iteration $z \to z^2 + c$ has an attracting fixed point $z$. In that case, the value can be obtained by solving</p>

<div class="math-center">
$$
    z = z^2 + c
$$
</div>

<p>Moreover, similar to the real case, once the orbit is close to this fixed point, small errors shrink if</p>

<div class="math-center">
$$
    \lvert f^\prime_c(z) \rvert = \lvert 2z \rvert &lt; 1
$$
</div>

<p>The region of values $z$ that satisfy $2 \lvert z \rvert &lt; 1$ can be parameterized as</p>

<div class="math-center">
$$
z = \frac{r}{2} e^{i \phi}, \qquad
\begin{aligned}
    r &amp;\in [0, 1) \\
    \phi &amp;\in [0, 2 \pi)
\end{aligned}
$$
</div>

<p>In that case, we have:</p>

<div class="math-center">
$$
    c = z - z^2 = \frac{r}{2} e^{i \phi} - \frac{r^2}{4} e^{2 i \phi}
$$
</div>

<p>The boundary of this region can be obtained by setting $r = 1$, which results in the famous cardioid shape.</p>

<p>The single value for each $c$ can be obtained by solving the quadratic equation for $z$</p>

<div class="math-center">
$$
    z = \frac{1}{2} \left( 1 \pm \sqrt{1 - 4 c} \right)
$$
</div>

<p>To pick the correct branch we recall, that the actual attracting limit satisfies $2 \lvert z \rvert &lt; 1$. Here we see, that for $c = 0$ only the negative branch satisfies $2 \lvert z \rvert &lt; 1$. To prove that the correct branch is</p>

<div class="math-center">
$$
    z = \frac{1}{2} \left( 1 - \sqrt{1 - 4 c} \right)
$$
</div>

<p>throughout the entire interior of the cardioid, we look at the relationship between the two roots. If one root is $z$, the other root must be $1 - z$, because the two roots of $z^2 - z + c = 0$ sum to 1. Therefore, if one root satisfies $\lvert z \rvert &lt; 1/2$, the other, by the reverse triangle inequality, satisfies</p>

<div class="math-center">
$$
    \lvert 1 - z \rvert \geq \lvert 1 - \lvert z \rvert \rvert = 1 - \lvert z \rvert &gt; 1/2
$$
</div>

<p>So at most one fixed point can be attracting. Since the negative branch gives $z = 0$ at $c = 0$, it is the attracting branch throughout the interior of the cardioid.</p>

<p>The two roots could only meet if</p>

<div class="math-center">
$$
    z_+ = z_- \quad \implies \quad \sqrt{1 - 4 c} = 0
$$
</div>

<p>which gives $c = 1/4$, the cusp of the cardioid. This point lies on the boundary, not inside the region.</p>

<p>To sum up, we have shown, that the region where the map has an attracting fixed point is described parameterically as</p>

<div class="math-center">
$$
    c = \frac{r}{2} e^{i \phi} - \frac{r^2}{4} e^{2 i \phi}, \qquad
    \begin{aligned}
        r &amp;\in [0, 1) \\
        \phi &amp;\in [0, 2 \pi)
    \end{aligned}
$$
</div>

<p>And the value it converges to is</p>

<div class="math-center">
$$
    z = \frac{1}{2} \left( 1 - \sqrt{1 - 4 c} \right)
$$
</div>

<p>This is the main cardioid of the Mandelbrot set.</p>


  </div>
</details>

<p>Within the disk located to the left of the main cardioid, the iterations eventually settle into two repeating values:</p>

<div class="math-center">
$$
    z_1 (c) \to z_2 (c) \to z_1 (c) \to \cdots
$$
</div>

<p>In this case, the limiting values can be obtained by solving</p>

<div class="math-center">
$$
    z = (z^2 + c)^2 + c,
$$
</div>

<p>i.e. nesting the iteration twice.</p>

<details class="aside-box aside-box--proof">
  <summary class="aside-box__summary">
    <span class="aside-box__heading">
      <span class="aside-box__kicker">Aside</span><span class="aside-box__title">Double-valued circle</span></span>
    <span class="aside-box__toggle" aria-hidden="true">
      <span class="aside-box__toggle-text aside-box__toggle-text--closed">Show</span>
      <span class="aside-box__toggle-text aside-box__toggle-text--open">Hide</span>
    </span>
  </summary>
  <div class="aside-box__body">
    
<p>Let’s now assume, that the iteration $z \to z^2 + c$ leads to two alternating values,</p>

<div class="math-center">
$$
    z_1 (c) \to z_2 (c) \to z_1 (c) \to \cdots
$$
</div>

<p>In that case, we have to obtain back the same value $z$ after nesting the iteration twice:</p>

<div class="math-center">
$$
    z = f_c(f_c(z)) = (z^2 + c)^2 + c
$$
</div>

<p>However, care must be taken when analyzing this equation. In addition to true 2-periodic behavior, this equation also contains the single-valued case, as certainly if $z = z^2 + c$ then also $z = (z^2 + c)^2 + c$ (the inverse is not true). We can easily get rid of the trivial, single-valued case by factoring out $z^2 - z + c$ from the polynomial. This can be done with a simple guess:</p>

<div class="math-center">
$$
    z^4 + 2 c z^2 - z + c^2 + c = (z^2 - z + c) (z^2 + z + c + 1)
$$
</div>

<p>We know, that the first part, $z^2 - z + c$ hides the single-valued cardioid, so the 2-period region must be described by</p>

<div class="math-center">
$$
    z^2 + z + c + 1 = 0.
$$
</div>

<p>The two roots of this equation are the two values of the alternating cycle. Let us call them $z_1$ and $z_2$. By Vieta’s formulas applied to</p>

<div class="math-center">
$$
    z^2 + z + c + 1 = 0,
$$
</div>

<p>we have</p>

<div class="math-center">
$$
    z_1 + z_2 = -1,
    \qquad
    z_1 z_2 = c + 1.
$$
</div>

<p>Also, from the same equation,</p>

<div class="math-center">
$$
    z^2 + c = -z - 1.
$$
</div>

<p>Since $f_c(z) = z^2 + c$, this means</p>

<div class="math-center">
$$
    f_c(z) = -z - 1.
$$
</div>

<p>So if one point of the cycle is $z_1$, the other one is</p>

<div class="math-center">
$$
    z_2 = -z_1 - 1,
$$
</div>

<p>and applying the map again returns us to $z_1$.</p>

<p>Now we need to ask when this two-cycle is attracting. Since one full cycle takes two iterations, the relevant multiplier is</p>

<div class="math-center">
$$
    \lambda = f_c'(z_1) f_c'(z_2).
$$
</div>

<p>Because $f_c’(z) = 2z$, we get</p>

<div class="math-center">
$$
    \lambda = (2z_1)(2z_2) = 4 z_1 z_2.
$$
</div>

<p>Using $z_1 z_2 = c + 1$, this becomes</p>

<div class="math-center">
$$
    \lambda = 4(c+1).
$$
</div>

<p>The two-cycle is attracting when small errors shrink after a full cycle, so</p>

<div class="math-center">
$$
    |\lambda| &lt; 1.
$$
</div>

<p>Therefore,</p>

<div class="math-center">
$$
    |4(c+1)| &lt; 1,
$$
</div>

<p>or equivalently</p>

<div class="math-center">
$$
    |c+1| &lt; \frac{1}{4}.
$$
</div>

<p>This is exactly a circle centered at $c=-1$ with radius $1/4$. Thus the double-valued component can be parametrized as</p>

<div class="math-center">
$$
    c = -1 + \frac{r}{4}e^{i\phi},
    \qquad
    r \in [0,1),
    \qquad
    \phi \in [0,2\pi).
$$
</div>

<p>The boundary is obtained by setting $r=1$.</p>


  </div>
</details>

<p>Generally, $n$-periodic regions can be explored by considering the $n$-fold nesting</p>

<div class="math-center">
$$
    z = f_c(f_c(f_c(\cdots))) \equiv f_c^n (z)
$$
</div>

<p>At this point the algebra starts sticking out its nasty beak and talons. For period 1, the equations collapse into the cardioid. For period 2, they collapse into a circle. For period 3 and above, the same idea still works, but the expanded polynomials quickly become too unpleasant to write by hand.</p>

<p>So instead of trying to draw these components by sampling a dense grid of pixels, we can build them more directly.</p>

<p>Each attracting period-$n$ component has a special center. At that center, the critical point $0$ lies exactly on the cycle:</p>

<div class="math-center">
$$
    f_c^n(0)=0.
$$
</div>

<p>This gives us the centers of the components.</p>

<p>Then, to fill the component, we use the multiplier of the attracting cycle,</p>

<div class="math-center">
$$
    \lambda = (f_c^n)'(z).
$$
</div>

<p>For $f_c(z)=z^2+c$, this is</p>

<div class="math-center">
$$
    \lambda = (2z_0)(2z_1)\cdots(2z_{n-1}).
$$
</div>

<p>The component interior corresponds to</p>

<div class="math-center">
$$
    |\lambda|&lt;1.
$$
</div>

<p>So instead of sampling the Mandelbrot plane directly, we sample a clean disk</p>

<div class="math-center">
$$
    \lambda = r e^{i\phi},
    \qquad 0\leq r&lt;1,
$$
</div>

<p>and numerically solve for the corresponding value of $c$.</p>

<p>This gives a clean polygon mesh for each component. For every sampled $c$, the attracting cycle gives $n$ values</p>

<div class="math-center">
$$
    z_0,\ z_1,\ \dots,\ z_{n-1}.
$$
</div>

<p>Those become the $n$ sheets in the final visualization. The height is $\lvert z_j\rvert$, and the color is the phase $\arg(z_j)$.</p>

<details class="aside-box aside-box--proof">
  <summary class="aside-box__summary">
    <span class="aside-box__heading">
      <span class="aside-box__kicker">Aside</span><span class="aside-box__title">How the component atlas is generated</span></span>
    <span class="aside-box__toggle" aria-hidden="true">
      <span class="aside-box__toggle-text aside-box__toggle-text--closed">Show method</span>
      <span class="aside-box__toggle-text aside-box__toggle-text--open">Hide method</span>
    </span>
  </summary>
  <div class="aside-box__body">
    
<p>The numerical construction has two stages.</p>

<p>First, we find the centers. Define</p>

<div class="math-center">
$$
    P_n(c)=f_c^n(0).
$$
</div>

<p>The roots of $P_n(c)$ contain centers whose exact periods divide $n$. To keep only exact period $n$, we divide out the lower-period center polynomials:</p>

<div class="math-center">
$$
    \Psi_n(c)
    =
    \frac{P_n(c)}
    {\prod_{d\mid n,\ d&lt;n}\Psi_d(c)}.
$$
</div>

<p>For example,</p>

<div class="math-center">
$$
    \Psi_3(c)=c^3+2c^2+c+1,
$$
</div>

<p>whose three roots are the centers of the three period-3 components.</p>

<p>Second, for each center $c_i$, we fill the component using multiplier coordinates. For a chosen multiplier</p>

<div class="math-center">
$$
    \lambda = r e^{i\phi},
    \qquad r&lt;1,
$$
</div>

<p>we solve the two equations</p>

<div class="math-center">
$$
    f_c^n(z)=z,
$$
$$
    (f_c^n)'(z)=\lambda.
$$
</div>

<p>At the center, the starting solution is especially simple:</p>

<div class="math-center">
$$
    \lambda=0,\qquad c=c_i,\qquad z=0.
$$
</div>

<p>Then we move outward in small steps in $\lambda$-space, using each previous solution as the starting guess for the next one. This avoids trying to recover the component shape from a dense pixel grid.</p>

<p>Once one point $z_0$ of the attracting cycle is found, the rest are obtained for free:</p>

<div class="math-center">
$$
    z_1=z_0^2+c,\qquad
    z_2=z_1^2+c,\qquad
    \dots
$$
</div>

<p>For a period-$n$ component, this gives $n$ sheets. Each sheet is plotted over the same component in the $c$-plane, with height $\lvert z_j\rvert$ and color $\arg(z_j)$.</p>

  </div>
</details>

<p>The final experiment below shows an atlas of the stable interior pieces: the attracting periodic components. Each component is sampled in its own multiplier coordinate, then lifted into 3D.</p>

<p>The base of each colored sheet lives in the familiar $c$-plane. The height is $\lvert z_j\rvert$, where $z_j$ is one point of the attracting cycle. The color records the phase $\arg(z_j)$. So a period-$n$ component does not become one surface, but $n$ related sheets stacked over the same region. The thin vertical guide lines mark component centers, where the critical point $0$ lies exactly on the periodic cycle.</p>

<figure class="lazy-demo" data-lazy-demo="" data-src="/fractal/mandelbrot/mandelbrot_pure.html" data-title="Mandelbrot component atlas" data-button-text="Load demo" data-loading-text="Loading atlas…" style="--lazy-demo-height: min(760px, 78vh); --lazy-demo-mobile-height: min(560px, 68vh);">
  <div class="lazy-demo__header">
    <div class="lazy-demo__heading">
      <span class="lazy-demo__kicker">Interactive demo</span>
      <span class="lazy-demo__title">Mandelbrot component atlas</span>

      
        <span class="lazy-demo__note">Large interactive WebGL visualization. The preview is static; the full atlas loads only after clicking.</span>
      
    </div>
  </div>

  <div class="lazy-demo__shell">
    <div class="lazy-demo__preview" data-lazy-demo-preview="">
      
        <img class="lazy-demo__preview-image" src="/fractal/mandelbrot/mandelbrot_atlas_preview.png" alt="Preview of the Mandelbrot component atlas" loading="lazy" />
      

      <div class="lazy-demo__preview-overlay">
        <button class="lazy-demo__button lazy-demo__button--large" type="button" data-lazy-demo-button="">
          Load demo
        </button>

        <span class="lazy-demo__preview-hint">
          Static preview; the interactive WebGL version loads only after clicking.
        </span>
      </div>
    </div>

    <div class="lazy-demo__loader" aria-live="polite">
      <span class="lazy-demo__loader-title">Loading atlas…</span>
      <span class="lazy-demo__progress" aria-hidden="true"></span>
      <span class="lazy-demo__loader-note">This can take a few seconds.</span>
    </div>

    <div class="lazy-demo__mount" data-lazy-demo-mount=""></div>
  </div>

  <p class="lazy-demo__links">
    <a href="/fractal/mandelbrot/mandelbrot_pure.html" target="_blank" rel="noopener">
      Open full screen
    </a>
  </p>

  
    <figcaption class="lazy-demo__caption">
      <p>Drag to rotate, scroll or pinch to zoom. On phones, opening the atlas full screen may work better than using it inside the page.</p>

    </figcaption>
  

  <noscript>
    <p class="lazy-demo__noscript">
      JavaScript is disabled, so the embedded viewer cannot load here.
      <a href="/fractal/mandelbrot/mandelbrot_pure.html">Open the atlas directly</a>.
    </p>
  </noscript>
</figure>

<p>While this render does not show the full Mandelbrot story (such as exotic behavior at the boundary), it builds a stable skeleton: the places where the iteration settles into attracting cycles, unfolded into branching colored sheets.</p>

<script>
const mandelbrotResolutionCarousel = {
    index: 0,
    images: [
        {
            src: "/fractal/mandelbrot/mandelbrot_0064.png",
            label: "64 × 64",
            alt: "Mandelbrot set sampled on a 64 by 64 grid"
        },
        {
            src: "/fractal/mandelbrot/mandelbrot_0128.png",
            label: "128 × 128",
            alt: "Mandelbrot set sampled on a 128 by 128 grid"
        },
        {
            src: "/fractal/mandelbrot/mandelbrot_0256.png",
            label: "256 × 256",
            alt: "Mandelbrot set sampled on a 256 by 256 grid"
        },
        {
            src: "/fractal/mandelbrot/mandelbrot_0512.png",
            label: "512 × 512",
            alt: "Mandelbrot set sampled on a 512 by 512 grid"
        },
        {
            src: "/fractal/mandelbrot/mandelbrot_1024.png",
            label: "1024 × 1024",
            alt: "Mandelbrot set sampled on a 1024 by 1024 grid"
        },
        {
            src: "/fractal/mandelbrot/mandelbrot_2048.png",
            label: "2048 × 2048",
            alt: "Mandelbrot set sampled on a 2048 by 2048 grid"
        }
    ]
};

const mandelbrotColorResolutionCarousel = {
    index: 0,
    images: [
        {
            src: "/fractal/mandelbrot/mandelbrot_color_0064.png",
            label: "64 × 64",
            alt: "Colored Mandelbrot set sampled on a 64 by 64 grid"
        },
        {
            src: "/fractal/mandelbrot/mandelbrot_color_0128.png",
            label: "128 × 128",
            alt: "Colored Mandelbrot set sampled on a 128 by 128 grid"
        },
        {
            src: "/fractal/mandelbrot/mandelbrot_color_0256.png",
            label: "256 × 256",
            alt: "Colored Mandelbrot set sampled on a 256 by 256 grid"
        },
        {
            src: "/fractal/mandelbrot/mandelbrot_color_0512.png",
            label: "512 × 512",
            alt: "Colored Mandelbrot set sampled on a 512 by 512 grid"
        },
        {
            src: "/fractal/mandelbrot/mandelbrot_color_1024.png",
            label: "1024 × 1024",
            alt: "Colored Mandelbrot set sampled on a 1024 by 1024 grid"
        },
        {
            src: "/fractal/mandelbrot/mandelbrot_color_2048.png",
            label: "2048 × 2048",
            alt: "Colored Mandelbrot set sampled on a 2048 by 2048 grid"
        }
    ]
};

function updateMandelbrotCarousel(data, imageId, labelId) {
    const current = data.images[data.index];
    const img = document.getElementById(imageId);
    const label = document.getElementById(labelId);

    if (img) {
        img.src = current.src;
        img.alt = current.alt;
    }

    if (label) {
        label.textContent = current.label;
    }

    if (img && img.parentElement) {
        const leftBtn = img.parentElement.querySelector(".carousel-btn.left");
        const rightBtn = img.parentElement.querySelector(".carousel-btn.right");

        const previous = data.images[(data.index - 1 + data.images.length) % data.images.length];
        const next = data.images[(data.index + 1) % data.images.length];

        if (leftBtn) {
            leftBtn.title = `Click to see ${previous.label}`;
        }

        if (rightBtn) {
            rightBtn.title = `Click to see ${next.label}`;
        }
    }
}

function changeMandelbrotResolution(direction) {
    const data = mandelbrotResolutionCarousel;
    data.index = (data.index + direction + data.images.length) % data.images.length;
    updateMandelbrotCarousel(
        data,
        "mandelbrot-resolution-carousel-img",
        "mandelbrot-resolution-carousel-label"
    );
}

function changeMandelbrotColorResolution(direction) {
    const data = mandelbrotColorResolutionCarousel;
    data.index = (data.index + direction + data.images.length) % data.images.length;
    updateMandelbrotCarousel(
        data,
        "mandelbrot-color-resolution-carousel-img",
        "mandelbrot-color-resolution-carousel-label"
    );
}
</script>]]></content><author><name>Branislav Rabatin</name></author><category term="article" /><summary type="html"><![CDATA[Laws of physics are often shockingly compact. A force law, an equation of motion, a conservation rule: sometimes, the exact mathematical principle governing millions of real-world applications fits on one line. And yet the world built from those rules is all but compact or simple. Coastlines wrinkle. Ferns repeat themselves leaf inside leaf. Clouds and turbulent gas form structure inside structure. Even DNA, branching trees, lightning, river networks, and snowflakes seem to share the same feature: repeat a simple instruction many times, and simplicity starts to look complicated.]]></summary></entry><entry><title type="html">Website Overhaul</title><link href="https://rabatinb.com/updates/2026/07/04/website-overhaul.html" rel="alternate" type="text/html" title="Website Overhaul" /><published>2026-07-04T01:00:00+00:00</published><updated>2026-07-04T01:00:00+00:00</updated><id>https://rabatinb.com/updates/2026/07/04/website-overhaul</id><content type="html" xml:base="https://rabatinb.com/updates/2026/07/04/website-overhaul.html"><![CDATA[<p>The website finally got a proper makeover.</p>

<p>What started as a fairly plain GitHub Pages site has now been reorganized into something cleaner, nicer to use, and much easier to maintain. The goal was to turn it into something more intentional and maintainable: better spacing, nicer typography, cleaner cards, reusable layouts, and a more coherent structure across the whole site.</p>

<p>The biggest visible change is the new design. The homepage now has a clearer introduction, section cards for research, BeamNG projects, and writing, plus a cleaner preview of recent posts. Project and post cards now behave consistently: the whole card is clickable, the hover effect is shared, and only the title is highlighted instead of the entire text turning into one giant link.</p>

<p>Under the hood, the site is now much more modular. Repeated pieces of layout have been moved into reusable Jekyll includes, so I do not have to copy the same chunks of HTML everywhere. Project pages, galleries, stats boxes, figures, post lists, and cards can now be controlled from a smaller number of shared components. This should make future changes much less painful.</p>

<p>The BeamNG section also got reorganized into proper project-style pages. Instead of each page being a pile of custom markup, the structure is now more predictable: metadata lives in front matter, content lives in Markdown, and reusable includes handle the repeated visual pieces.</p>

<p>There were also a few quality-of-life fixes: cleaner local development with live reload, better handling of custom post summaries, improved card previews, less fragile styling, and a clearer separation between website content and local-only files like notebooks, raw data, and generated assets.</p>

<p>This is still a personal site, so it will keep evolving. But now it finally feels like a better foundation; flexible enough to grow, and just polished enough that I am no longer embarrassed to put it on a proper domain.</p>]]></content><author><name>Branislav Rabatin</name></author><category term="updates" /><summary type="html"><![CDATA[The website finally got a proper makeover.]]></summary></entry><entry><title type="html">Why Gaussian? Part III: not all processes are additive</title><link href="https://rabatinb.com/blog/why-gaussian-part-iii/" rel="alternate" type="text/html" title="Why Gaussian? Part III: not all processes are additive" /><published>2025-05-02T10:00:00+00:00</published><updated>2025-05-02T10:00:00+00:00</updated><id>https://rabatinb.com/blog/CLT3</id><content type="html" xml:base="https://rabatinb.com/blog/why-gaussian-part-iii/"><![CDATA[<div style="flex: 1; text-align: justify;">
    <h3>See <a href="/blog/why-gaussian-part-i/">Why Gaussian? Part I: tossing dice</a> for part 1!</h3>
    <h3>See <a href="/blog/why-gaussian-part-ii/">Why Gaussian? Part II: continuous additive processes</a> for part 2!</h3>
    <p>
        In the first part of this series, we saw how a discrete random variable (like a roll of a die), when accumulated across independent trials, converges toward a Gaussian distribution. In the second part, we extended this to continuous variables - showing how, under mild conditions on their moments, the same convergence occurs. 
    </p>
    <p>
        In this post, we take a different path. Not all natural processes are additive. Some involve multiplication, cyclic variables, or nonlinear transformations. Yet, remarkably, the Gaussian often still emerges-sometimes not in the variable itself, but in a transformed version like its logarithm or phase. Let's explore these less-obvious routes to Gaussian behavior.
    </p>
    <h4>Multiplicative random processes</h4>
    <p>
        While the central limit theorem is traditionally associated with additive processes, many real-world systems operate multiplicatively. Fortunately, multiplication becomes addition in logarithmic space, so a wide array of CLT-like results still apply, just one transformation away. In fact, many systems that produce <strong>log-normal</strong> statistics are fundamentally multiplicative at their core.
    </p>
    <p>
        Consider a few well-known examples: compound interest in finance, population growth with random fluctuations, or the cascade of returns in stock markets - all evolve by repeated multiplication of random factors. In such cases, taking the logarithm converts the product into a sum, and the central limit theorem applies to the log-transformed variable. As a result, the original variable becomes log-normally distributed.
    </p>
    <p>
        One particularly elegant example comes from astrophysical fluid dynamics: the density structure in isothermal, supersonic turbulence. In such flows, density fluctuations are largely governed by shock waves. Each shock compresses the local density by a factor determined by the velocity field, so the density doesn't evolve additively, but multiplicatively. For isothermal shocks (where the adiabatic index $\gamma = 1$), the Rankine–Hugoniot jump condition simplifies to a density jump $\rho_2 / \rho_1 = m^2$, where (dimensionless) $m$ is the upstream Mach number; the speed of the shock reduced by the speed of sound. As gas elements traverse a sequence of shocks, the resulting density is repeatedly multiplied by factors drawn from the local velocity dispersion. To be clear, if a parcel of gas is affected by $n$ shocks passing through it, each propagating with some random local Mach number $m$, the resulting density will be
    </p>
    <p style="text-align: center;">
        $$
            \rho = \rho_0 \, m_1 \, m_2 \cdots m_n
        $$
    </p>
    <p>
        Naturally, to understand the distribution of final densities, we must know how the local Mach number itself is distributed. In a turbulent flow, these are not fixed, but fluctuate based on the local velocity field. The precise form of the Mach number distribution depends on the turbulence driving and thermodynamic conditions, but it's generally broad and continuous, with most values clustered near the mean turbulent Mach number.
    </p>
    <p>
        If we assume the Mach numbers $m_i$ for each shock are drawn independently from such a distribution (or more realistically, weakly correlated), then the logarithm of the final density becomes a sum of random terms:
    </p>
    <p style="text-align: center;">
        $$
            \log (\rho / \rho_0) + \log m_1 + \log m_2 + \cdots + \log m_n.
        $$
    </p>
    <p>
        This is precisely the setup where the central limit theorem applies. Under fairly weak assumptions about the shape and variance of the $\log m$ distribution, the sum converges toward a Gaussian. Therefore, the <strong>logarithm of density</strong>, $s \equiv \log (\rho / \rho_0)$ follows a normal distribution, and the density itself follows a <strong>lognormal</strong> distribution:
    </p>
    <div class="math-desktop">
        $$
            f (s) \sim \mathcal{N}(\mu, \sigma^2), \quad \text{so} \quad f (\rho) \sim \frac{1}{\rho} \exp\left(-\frac{(\log (\rho / \rho_0) - \mu)^2}{2\sigma^2} \right).
        $$
    </div>
    <div class="math-mobile">
        $$
            f (s) \sim \mathcal{N}(\mu, \sigma^2)
        $$
        <p>
            so,
        </p>
        $$
            f (\rho) \sim \frac{1}{\rho} \exp\left(-\frac{(\log (\rho / \rho_0) - \mu)^2}{2\sigma^2} \right)
        $$
    </div>
    <p>
        But what does the distribution of local Mach numbers actually look like in a turbulent flow? Since the Mach number $m = v / c_s$ is just the velocity normalized by the sound speed, this really comes down to the statistics of local velocity magnitudes.
    </p>
    <p>
        In an isothermal turbulent medium with no large-scale bulk motion, the three Cartesian components of velocity can be reasonably modeled as independent random variables drawn from a Gaussian distribution centered at zero-thanks to the central limit effect of countless random driving forces. When this is the case, the <strong>magnitude</strong> of the velocity (and hence the local Mach number) follows a <strong>Maxwell–Boltzmann distribution</strong>.
    </p>
    <p>
        In three dimensions, the Maxwellian PDF for the Mach number $m$ takes the form:
    </p>
    <p style="text-align: center;">
        $$
            f (m) = \frac{4 \pi m^2}{(2 \pi \sigma^2)^{3/2}} \exp \left( - \frac{m^2}{2 \sigma^2} \right)
        $$
    </p>
    <p>
        where $\sigma$ is the standard deviation of the underlying 1D velocity components (in units of the sound speed). This shape arises naturally when you compute the distribution of the magnitude of a 3D Gaussian vector. Most of the probability mass lies around the typical turbulent Mach number, but there's a non-negligible tail at high $m$, allowing for occasional strong shocks.
    </p>
    <p>
        There's one important complication we need to address before we go further: real shock waves only occur when the flow is <strong>supersonic</strong>, that is, when the Mach number $m &gt; 1$. If the flow is subsonic, information travels fast enough upstream to smooth out any discontinuity before a shock can form. In our setup, however, we're drawing Mach numbers from a continuous distribution (like the Maxwellian), which includes both <strong>subsonic</strong> and <strong>supersonic</strong> values.
    </p>
    <p>
        So what happens to those <em>subsonic</em> samples in our model? Strictly speaking, they shouldn't correspond to shocks at all. But in turbulent flows, not every compressive or expansive feature is a perfect shock. There are also <strong>rarefaction waves</strong>, which are smooth expansions rather than discontinuous jumps. Unlike shocks, which <em>compress</em> the gas and increase the density, rarefactions <em>decrease</em> the density across a smooth gradient.
    </p>
    <p>
        In our simplified picture, we can treat these rarefactions as "anti-shocks"; they still change the density by a multiplicative factor, but one that's <strong>less than one</strong>. A convenient way to model both compression and expansion statistically is to let each event (shock or rarefaction) multiply the density by a factor of $m^2$, even when $m &lt; 1$. This may not match every microscopic detail, but it captures the essential multiplicative character of density evolution in turbulent media.
    </p>
    <p>
        This Maxwellian form gives us a physically motivated, continuous distribution from which to draw the multiplicative shock factors $m^2$. With that foundation in place, we're finally ready to run a small numerical experiment.
    </p>
    <h4>Shocks as a random multiplicative process: numerical experiment</h4>
    <p>
        We'll simulate a parcel of gas undergoing a series of random isothermal shocks. Each shock multiplies the current density by $m^2$, where the Mach number $m$ is drawn from the Maxwellian distribution with some pre-defined $\sigma$. After a few such multiplicative steps, we'll examine the distribution of the resulting density.
    </p>
    <p>
        Here's a Python code that will give us some distributions:
    </p>
<button onclick="toggleCode()">Show Code</button>
<div id="codeBlock" class="code-container" style="
  display: none;
  max-width: 100%;
  max-height: 400px;
  overflow: auto;
  white-space: pre;
">

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="n">np</span>
<span class="kn">import</span> <span class="nn">matplotlib.pyplot</span> <span class="k">as</span> <span class="n">plt</span>
<span class="kn">from</span> <span class="nn">scipy.stats</span> <span class="kn">import</span> <span class="n">maxwell</span><span class="p">,</span> <span class="n">norm</span><span class="p">,</span> <span class="n">lognorm</span>
<span class="kn">from</span> <span class="nn">tqdm</span> <span class="kn">import</span> <span class="n">tqdm</span>
<span class="kn">import</span> <span class="nn">os</span>

<span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="n">np</span>
<span class="kn">import</span> <span class="nn">matplotlib.pyplot</span> <span class="k">as</span> <span class="n">plt</span>
<span class="kn">from</span> <span class="nn">scipy.stats</span> <span class="kn">import</span> <span class="n">maxwell</span><span class="p">,</span> <span class="n">norm</span><span class="p">,</span> <span class="n">lognorm</span>
<span class="kn">import</span> <span class="nn">os</span>

<span class="k">def</span> <span class="nf">multiplicative_clt_demo</span><span class="p">(</span>
    <span class="n">n</span><span class="o">=</span><span class="mi">5</span><span class="p">,</span>
    <span class="n">N</span><span class="o">=</span><span class="mi">10000</span><span class="p">,</span>
    <span class="n">bins</span><span class="o">=</span><span class="mi">100</span><span class="p">,</span>
    <span class="n">base_dir</span><span class="o">=</span><span class="s">""</span><span class="p">,</span>
    <span class="n">title</span><span class="o">=</span><span class="s">"Multiplicative CLT Demo"</span><span class="p">,</span>
    <span class="n">filename</span><span class="o">=</span><span class="s">"lognormal_plot.png"</span><span class="p">,</span>
    <span class="n">sigma</span><span class="o">=</span><span class="mf">2.0</span><span class="p">,</span>
    <span class="n">toggle_gaussian</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
    <span class="n">toggle_lognormal</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
    <span class="n">threshold</span><span class="o">=</span><span class="mf">0.005</span>
<span class="p">):</span>
    <span class="c1"># sample mach numbers from maxwellian
</span>    <span class="n">log_density</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">zeros</span><span class="p">(</span><span class="n">N</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="n">tqdm</span><span class="p">(</span><span class="nb">range</span><span class="p">(</span><span class="n">n</span><span class="p">),</span> <span class="n">desc</span><span class="o">=</span><span class="s">"Applying shocks"</span><span class="p">,</span> <span class="nb">ascii</span><span class="o">=</span><span class="bp">True</span><span class="p">):</span>
        <span class="n">mach</span> <span class="o">=</span> <span class="n">maxwell</span><span class="p">.</span><span class="n">rvs</span><span class="p">(</span><span class="n">scale</span><span class="o">=</span><span class="n">sigma</span><span class="p">,</span> <span class="n">size</span><span class="o">=</span><span class="n">N</span><span class="p">)</span>
        <span class="n">log_density</span> <span class="o">+=</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">np</span><span class="p">.</span><span class="n">log</span><span class="p">(</span><span class="n">mach</span><span class="p">)</span>  <span class="c1"># because shock_factors = mach^2
</span>
    <span class="c1"># normalize the ensemble towards zero mean and unit variance
</span>    <span class="n">mu</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">mean</span><span class="p">(</span><span class="n">log_density</span><span class="p">)</span>
    <span class="n">std</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">std</span><span class="p">(</span><span class="n">log_density</span><span class="p">)</span>
    <span class="n">log_density_norm</span> <span class="o">=</span> <span class="p">(</span><span class="n">log_density</span> <span class="o">-</span> <span class="n">mu</span><span class="p">)</span> <span class="o">/</span> <span class="n">std</span>

    <span class="c1"># we also need density for plotting
</span>    <span class="n">density</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">exp</span><span class="p">(</span><span class="n">log_density_norm</span><span class="p">)</span>
    <span class="n">mu</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="n">std</span> <span class="o">=</span> <span class="mi">1</span>

    <span class="c1"># --- LOG-DENSITY PLOT ---
</span>    <span class="n">plt</span><span class="p">.</span><span class="n">figure</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">8</span><span class="p">,</span> <span class="mi">5</span><span class="p">))</span>
    <span class="n">counts</span><span class="p">,</span> <span class="n">bin_edges</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">histogram</span><span class="p">(</span><span class="n">log_density_norm</span><span class="p">,</span> <span class="n">bins</span><span class="o">=</span><span class="n">bins</span><span class="p">,</span> <span class="n">density</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
    <span class="n">bin_width</span> <span class="o">=</span> <span class="n">bin_edges</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">-</span> <span class="n">bin_edges</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
    <span class="n">bin_centers</span> <span class="o">=</span> <span class="n">bin_edges</span><span class="p">[:</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>

    <span class="n">plt</span><span class="p">.</span><span class="n">bar</span><span class="p">(</span>
        <span class="n">bin_centers</span><span class="p">,</span> <span class="n">counts</span><span class="p">,</span> <span class="n">width</span><span class="o">=</span><span class="n">bin_width</span><span class="p">,</span>
        <span class="n">color</span><span class="o">=</span><span class="s">"skyblue"</span><span class="p">,</span> <span class="n">edgecolor</span><span class="o">=</span><span class="s">"none"</span><span class="p">,</span>
        <span class="n">align</span><span class="o">=</span><span class="s">"edge"</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="s">"Normalized log-density"</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">x</span><span class="p">,</span> <span class="n">height</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">bin_centers</span><span class="p">,</span> <span class="n">counts</span><span class="p">):</span>
        <span class="n">plt</span><span class="p">.</span><span class="n">plot</span><span class="p">([</span><span class="n">x</span><span class="p">,</span> <span class="n">x</span> <span class="o">+</span> <span class="n">bin_width</span><span class="p">],</span> <span class="p">[</span><span class="n">height</span><span class="p">,</span> <span class="n">height</span><span class="p">],</span>
        <span class="n">color</span><span class="o">=</span><span class="s">"black"</span><span class="p">,</span> <span class="n">linewidth</span><span class="o">=</span><span class="mf">0.5</span><span class="p">)</span>

    <span class="k">if</span> <span class="n">toggle_gaussian</span><span class="p">:</span>
        <span class="n">significant</span> <span class="o">=</span> <span class="n">counts</span> <span class="o">&gt;</span> <span class="n">threshold</span> <span class="o">*</span> <span class="n">np</span><span class="p">.</span><span class="nb">max</span><span class="p">(</span><span class="n">counts</span><span class="p">)</span>
        <span class="n">x_min</span> <span class="o">=</span> <span class="n">bin_edges</span><span class="p">[</span><span class="n">np</span><span class="p">.</span><span class="n">argmax</span><span class="p">(</span><span class="n">significant</span><span class="p">)]</span>
        <span class="n">x_max</span> <span class="o">=</span> <span class="n">bin_edges</span><span class="p">[::</span><span class="o">-</span><span class="mi">1</span><span class="p">][</span><span class="n">np</span><span class="p">.</span><span class="n">argmax</span><span class="p">(</span><span class="n">significant</span><span class="p">[::</span><span class="o">-</span><span class="mi">1</span><span class="p">])]</span>
        <span class="n">x_range</span> <span class="o">=</span> <span class="n">x_max</span> <span class="o">-</span> <span class="n">x_min</span>
        <span class="n">x</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">linspace</span><span class="p">(</span><span class="n">x_min</span> <span class="o">-</span> <span class="mf">0.1</span> <span class="o">*</span> <span class="n">x_range</span><span class="p">,</span> <span class="n">x_max</span> <span class="o">+</span> <span class="mf">0.1</span> <span class="o">*</span> <span class="n">x_range</span><span class="p">,</span> <span class="mi">1000</span><span class="p">)</span>
        <span class="n">y</span> <span class="o">=</span> <span class="n">norm</span><span class="p">.</span><span class="n">pdf</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">loc</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
        <span class="n">plt</span><span class="p">.</span><span class="n">plot</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="s">'k--'</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="s">"Ideal Normal Distribution"</span><span class="p">)</span>

    <span class="n">plt</span><span class="p">.</span><span class="n">title</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">title</span><span class="si">}</span><span class="se">\n</span><span class="s">Log-density (n=</span><span class="si">{</span><span class="n">n</span><span class="si">}</span><span class="s">, N=</span><span class="si">{</span><span class="n">N</span><span class="si">}</span><span class="s">, σ=</span><span class="si">{</span><span class="n">sigma</span><span class="si">}</span><span class="s">)"</span><span class="p">)</span>
    <span class="n">plt</span><span class="p">.</span><span class="n">xlabel</span><span class="p">(</span><span class="s">"Normalized log-density"</span><span class="p">)</span>
    <span class="n">plt</span><span class="p">.</span><span class="n">ylabel</span><span class="p">(</span><span class="s">"Probability Density"</span><span class="p">)</span>
    <span class="n">plt</span><span class="p">.</span><span class="n">grid</span><span class="p">(</span><span class="bp">True</span><span class="p">)</span>
    <span class="n">plt</span><span class="p">.</span><span class="n">legend</span><span class="p">(</span><span class="n">loc</span><span class="o">=</span><span class="s">"upper right"</span><span class="p">)</span>
    <span class="n">plt</span><span class="p">.</span><span class="n">ylim</span><span class="p">(</span><span class="n">top</span><span class="o">=</span><span class="nb">max</span><span class="p">(</span><span class="n">counts</span><span class="p">.</span><span class="nb">max</span><span class="p">(),</span> <span class="n">y</span><span class="p">.</span><span class="nb">max</span><span class="p">()</span> <span class="k">if</span> <span class="n">toggle_gaussian</span> <span class="k">else</span> <span class="mi">0</span><span class="p">)</span> <span class="o">*</span> <span class="mf">1.2</span><span class="p">)</span>
    <span class="n">plt</span><span class="p">.</span><span class="n">xlim</span><span class="p">(</span><span class="n">x</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">x</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span>

    <span class="n">path_log</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">path</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="n">base_dir</span><span class="p">,</span> <span class="s">"log_"</span> <span class="o">+</span> <span class="n">filename</span><span class="p">)</span>
    <span class="n">plt</span><span class="p">.</span><span class="n">savefig</span><span class="p">(</span><span class="n">path_log</span><span class="p">,</span> <span class="n">dpi</span><span class="o">=</span><span class="mi">150</span><span class="p">)</span>
    <span class="n">plt</span><span class="p">.</span><span class="n">close</span><span class="p">()</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Saved log-density plot to </span><span class="si">{</span><span class="n">path_log</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>

    <span class="c1"># --- DENSITY PLOT ---
</span>    <span class="n">plt</span><span class="p">.</span><span class="n">figure</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">8</span><span class="p">,</span> <span class="mi">5</span><span class="p">))</span>

    <span class="c1"># Fix histogram x-axis range
</span>    <span class="n">density_min</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="n">density_max</span> <span class="o">=</span> <span class="mi">10</span>  <span class="c1"># adjust as needed
</span>
    <span class="c1"># Manually define bin edges and bin centers
</span>    <span class="n">bin_edges_d</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">linspace</span><span class="p">(</span><span class="n">density_min</span><span class="p">,</span> <span class="n">density_max</span><span class="p">,</span> <span class="n">bins</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
    <span class="n">bin_width_d</span> <span class="o">=</span> <span class="n">bin_edges_d</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">-</span> <span class="n">bin_edges_d</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
    <span class="n">bin_centers_d</span> <span class="o">=</span> <span class="n">bin_edges_d</span><span class="p">[:</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>

    <span class="c1"># Clip data to range
</span>    <span class="n">density_clipped</span> <span class="o">=</span> <span class="n">density</span><span class="p">[</span>
        <span class="p">(</span><span class="n">density</span> <span class="o">&gt;=</span> <span class="n">density_min</span><span class="p">)</span> <span class="o">&amp;</span>
        <span class="p">(</span><span class="n">density</span> <span class="o">&lt;=</span> <span class="n">density_max</span><span class="p">)]</span>
    <span class="n">counts_d</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">histogram</span><span class="p">(</span><span class="n">density_clipped</span><span class="p">,</span>
        <span class="n">bins</span><span class="o">=</span><span class="n">bin_edges_d</span><span class="p">,</span> <span class="n">density</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>

    <span class="c1"># Plot bars
</span>    <span class="n">plt</span><span class="p">.</span><span class="n">bar</span><span class="p">(</span>
        <span class="n">bin_centers_d</span><span class="p">,</span>
        <span class="n">counts_d</span><span class="p">,</span>
        <span class="n">width</span><span class="o">=</span><span class="n">bin_width_d</span><span class="p">,</span>
        <span class="n">color</span><span class="o">=</span><span class="s">"lightcoral"</span><span class="p">,</span>
        <span class="n">edgecolor</span><span class="o">=</span><span class="s">"none"</span><span class="p">,</span>
        <span class="n">align</span><span class="o">=</span><span class="s">"edge"</span><span class="p">,</span>
        <span class="n">label</span><span class="o">=</span><span class="s">"Density"</span>
    <span class="p">)</span>

    <span class="k">for</span> <span class="n">x</span><span class="p">,</span> <span class="n">height</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">bin_centers_d</span><span class="p">,</span> <span class="n">counts_d</span><span class="p">):</span>
        <span class="n">plt</span><span class="p">.</span><span class="n">plot</span><span class="p">([</span><span class="n">x</span><span class="p">,</span> <span class="n">x</span> <span class="o">+</span> <span class="n">bin_width_d</span><span class="p">],</span> <span class="p">[</span><span class="n">height</span><span class="p">,</span> <span class="n">height</span><span class="p">],</span>
        <span class="n">color</span><span class="o">=</span><span class="s">"black"</span><span class="p">,</span> <span class="n">linewidth</span><span class="o">=</span><span class="mf">0.5</span><span class="p">)</span>

    <span class="c1"># Overlay lognormal
</span>    <span class="k">if</span> <span class="n">toggle_lognormal</span><span class="p">:</span>
        <span class="n">x_d</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">linspace</span><span class="p">(</span><span class="n">density_min</span><span class="p">,</span> <span class="n">density_max</span><span class="p">,</span> <span class="mi">1000</span><span class="p">)</span>
        <span class="n">y</span> <span class="o">=</span> <span class="n">lognorm</span><span class="p">.</span><span class="n">pdf</span><span class="p">(</span><span class="n">x_d</span><span class="p">,</span> <span class="n">s</span><span class="o">=</span><span class="n">std</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="n">np</span><span class="p">.</span><span class="n">exp</span><span class="p">(</span><span class="n">mu</span><span class="p">))</span>
        <span class="n">plt</span><span class="p">.</span><span class="n">plot</span><span class="p">(</span><span class="n">x_d</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="s">'k--'</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="s">"Ideal Lognormal Distribution"</span><span class="p">)</span>

    <span class="c1"># Finalize plot
</span>    <span class="n">plt</span><span class="p">.</span><span class="n">title</span><span class="p">(</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">title</span><span class="si">}</span><span class="se">\n</span><span class="s">Density (n=</span><span class="si">{</span><span class="n">n</span><span class="si">}</span><span class="s">, N=</span><span class="si">{</span><span class="n">N</span><span class="si">}</span><span class="s">, σ=</span><span class="si">{</span><span class="n">sigma</span><span class="si">}</span><span class="s">)"</span><span class="p">)</span>
    <span class="n">plt</span><span class="p">.</span><span class="n">xlabel</span><span class="p">(</span><span class="s">"Density"</span><span class="p">)</span>
    <span class="n">plt</span><span class="p">.</span><span class="n">ylabel</span><span class="p">(</span><span class="s">"Probability Density"</span><span class="p">)</span>
    <span class="n">plt</span><span class="p">.</span><span class="n">grid</span><span class="p">(</span><span class="bp">True</span><span class="p">)</span>
    <span class="n">plt</span><span class="p">.</span><span class="n">legend</span><span class="p">(</span><span class="n">loc</span><span class="o">=</span><span class="s">"upper right"</span><span class="p">)</span>
    <span class="n">plt</span><span class="p">.</span><span class="n">ylim</span><span class="p">(</span><span class="n">top</span><span class="o">=</span><span class="nb">max</span><span class="p">(</span><span class="n">counts_d</span><span class="p">.</span><span class="nb">max</span><span class="p">(),</span> <span class="n">y</span><span class="p">.</span><span class="nb">max</span><span class="p">()</span> <span class="k">if</span> <span class="n">toggle_lognormal</span> <span class="k">else</span> <span class="mi">0</span><span class="p">)</span> <span class="o">*</span> <span class="mf">1.2</span><span class="p">)</span>
    <span class="n">plt</span><span class="p">.</span><span class="n">xlim</span><span class="p">(</span><span class="n">density_min</span><span class="p">,</span> <span class="n">density_max</span><span class="p">)</span>

    <span class="c1"># Save
</span>    <span class="n">path_density</span> <span class="o">=</span> <span class="n">os</span><span class="p">.</span><span class="n">path</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="n">base_dir</span><span class="p">,</span> <span class="s">"density_"</span> <span class="o">+</span> <span class="n">filename</span><span class="p">)</span>
    <span class="n">plt</span><span class="p">.</span><span class="n">savefig</span><span class="p">(</span><span class="n">path_density</span><span class="p">,</span> <span class="n">dpi</span><span class="o">=</span><span class="mi">150</span><span class="p">)</span>
    <span class="n">plt</span><span class="p">.</span><span class="n">close</span><span class="p">()</span>
    <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Saved density plot to </span><span class="si">{</span><span class="n">path_density</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>

<span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">30</span><span class="p">,</span> <span class="mi">40</span><span class="p">,</span> <span class="mi">50</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">200</span><span class="p">,</span> <span class="mi">300</span><span class="p">,</span> <span class="mi">400</span><span class="p">,</span> <span class="mi">500</span><span class="p">]:</span>
    <span class="n">multiplicative_clt_demo</span><span class="p">(</span>
        <span class="n">n</span><span class="o">=</span><span class="n">n</span><span class="p">,</span>
        <span class="n">N</span><span class="o">=</span><span class="mi">10000000</span><span class="p">,</span>
        <span class="n">bins</span><span class="o">=</span><span class="mi">200</span><span class="p">,</span>
        <span class="n">base_dir</span><span class="o">=</span><span class="s">"CLT_plots"</span><span class="p">,</span>
        <span class="n">title</span><span class="o">=</span><span class="s">"Multiplicative Central Limit Theorem"</span><span class="p">,</span>
        <span class="n">filename</span><span class="o">=</span><span class="s">"mach_lognormal_n"</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">n</span><span class="p">)</span> <span class="o">+</span> <span class="s">".png"</span><span class="p">,</span>
        <span class="n">sigma</span><span class="o">=</span><span class="mf">2.0</span>
    <span class="p">)</span></code></pre></figure>

</div>
<script>
    function toggleCode() {
        var x = document.getElementById("codeBlock");
        var button = event.target;
        if (x.style.display === "none") {
            x.style.display = "block";
            button.innerText = "Hide Code";
        } else {
            x.style.display = "none";
            button.innerText = "Show Code";
        }
    }
    </script>
    <p>
        Let's watch how the distribution of density converges to the ideal lognormal distribution as the number of shocks $n$ increases:
    </p>
    <div class="plot-carousel">
        <button class="carousel-btn left" onclick="changePlot('carousel1', -1)" title="Click to see previous n">&#8592;</button>
        <img id="carousel1-img" src="/CLT_plots/density_mach_lognormal_n1.png" alt="Plot for n = 1" />
        <button class="carousel-btn right" onclick="changePlot('carousel1', 1)" title="Click to see next n">&#8594;</button>
    </div>
    <p>
        And now let's see the same effect in the logarithmic space:
    </p>
    <div class="plot-carousel">
    <button class="carousel-btn left" onclick="changePlot('carousel2', -1)" title="Click to see previous n">&#8592;</button>
    <img id="carousel2-img" src="/CLT_plots/log_mach_lognormal_n1.png" alt="Plot for n = 1" />
    <button class="carousel-btn right" onclick="changePlot('carousel2', 1)" title="Click to see next n">&#8594;</button>
    </div>
    <p>
        We see that while the convergence happens, it happens quite slow; we will see shortly why.
    </p>
    <h4>Analytic exploration</h4>
    <p>
        We saw that when $n$ is low, starting from $n = 1$, the resulting distribution for $s = \log (\rho / \rho_0)$ is heavily skewed to the right. Increasing $n$ makes the distribution for $s$ converge to the standard Gaussian, but it does so slowly - visible deviations persist even for large $n$, such as $n = 100$. This is exactly what we'd expect based on the results derived in <a href="/blog/why-gaussian-part-ii/">Part II</a>: a finite, nonzero third moment in the source distribution leads to convergence at a rate of $1/\sqrt{n}$.
    </p>
    <p>
        In fact, the distribution of the logarithm of the Mach number squared - i.e., the fundamental building block in our multiplicative model - is significantly skewed. Its right tail decays exponentially, while the left tail only falls off quadratically. Since the $n = 1$ case directly samples from this source distribution its asymmetry is immediately visible. Let's now explore this analytically.
    </p>
    <p>
        We begin by introducing a change of variables. Let the shock Mach number $m$ follow a Maxwellian distribution:
    </p>
    <p style="text-align: center;">
        $$
            f_M(m) = \frac{4 \pi m^2}{(2 \pi \sigma^2)^{3/2}} \exp\left( -\frac{m^2}{2\sigma^2} \right),
        $$
    </p>
    <p>
        where $\sigma$ is the one-dimensional velocity dispersion (in units of sound speed). We now define a new variable $y = \log(m^2)$. This transformation gives:
    </p>
    <p style="text-align: center;">
        $$
            m = e^{y/2}, \quad \frac{dm}{dy} = \frac{1}{2} e^{y/2}.
        $$
    </p>
    <p>
        The distribution of $y$ can be obtained by a simple variable transform
    </p>
    <div class="math-desktop">
        $$
            f_Y(y) \, \mathrm{d} y = f_M (m) \, \mathrm{d} m \quad \implies \quad f_M(m(y)) \left| \frac{dm}{dy} \right| 
            = \frac{1}{\sqrt{2\pi} \sigma^3} e^{\frac{3y}{2}} \exp\left( -\frac{e^y}{2 \sigma^2} \right).
        $$
    </div>
    <div class="math-mobile">
        $$
            \begin{aligned}
                f_Y(y) \, \mathrm{d} y &amp;= f_M (m) \, \mathrm{d} m \; \implies \; f_M(m(y)) \left| \frac{dm}{dy} \right| = \\
                &amp;= \frac{1}{\sqrt{2\pi} \sigma^3} e^{\frac{3y}{2}} \exp\left( -\frac{e^y}{2 \sigma^2} \right).
            \end{aligned}
        $$
    </div>
    <p>
        This is the exact shape of the log-distribution from which each shock multiplicative factor is drawn. While it superficially resembles a lognormal, it is not symmetric and possesses significant skewness. To explore this in more detail, we can compute its moments.
    </p>
    <p>
        From this, we can compute the central moments analytically. The mean of $y$ is:
    </p>
    <p style="text-align: center;">
        $$
            y_0 \equiv \langle y \rangle = 2 - \gamma - \log 2 + 2 \log \sigma,
        $$
    </p>
    <p>
        where $\gamma \approx 0.5772$ is the Euler–Mascheroni constant. The variance is:
    </p>
    <p style="text-align: center;">
        $$
            \Sigma^2 = \left\langle (y - y_0)^2 \right\rangle = \frac{1}{2} (\pi^2 - 8),
        $$
    </p>
    <p>
        and the third central moment is:
    </p>
    <p style="text-align: center;">
        $$
            \left\langle (y - y_0)^3 \right\rangle = 2(8 - 7\zeta(3)) \approx -0.8288,
        $$
    </p>
    <p>
        which confirms the heavy negative skew we observed in our numerical experiments. More specifically, Fischer's coefficient of skewness relates the third central moment to the standard deviation:
    </p>
    <p style="text-align: center;">
        $$
            \bar{\mu}_3 = \frac{\left\langle (y - y_0)^3 \right\rangle}{\Sigma^3} \approx -0.9170
        $$
    </p>
    <p>
        Indicating a high negative skewness. Importantly, these moments - particularly the variance and skewness - are <em>independent</em> of $\sigma$. The only effect of $\sigma$ is to shift the distribution along the $y$-axis. In other words, the strength of the turbulence affects the <em>mean</em> density in log-space, but not the statistical structure of the fluctuations.
    </p>
    <p>
        This has a deep implication: the convergence of $s = \log \rho$ to a Gaussian is governed by a universal source distribution whose shape is unaffected by the Mach number. The only thing turbulence strength (through $\sigma$) does is translate the curve - it does not change how skewed or wide it is. Thus, in this model, the physics of the turbulence gets “washed out” in the fluctuation statistics, leaving behind a shape that is universal and determined entirely by the structure of the multiplicative process.
    </p>
    <p>
        To obtain the distribution for some specific $n$, we employ the characteristic function again, starting from the characteristic function of the underlying Maxwellain in the log-space. First, we normalize the distribution to zero mean and unit variance by subtracting and rescaling the random variable $y \to \Sigma (y - y_0)$:
    </p>
    <p style="text-align: center;">
        $$
            f_Y (y) = \frac{\Sigma}{\sqrt{2 \pi}} \exp \left( \frac{3 \Sigma}{2} (y + y_0) - \frac{1}{2} e^{\Sigma (y + y_0)} \right)
        $$
    </p>
    <p>
        where we can take $\sigma = 1$ without the loss of generality, since we already established that the overall shape does not depend on $\sigma$. Our task is now to obtain the characteristic function $\phi_y (t) = \langle e^{i t y} \rangle$. This means we have to calculated the following integral:
    </p>
    <p style="text-align: center;">
        $$
            \phi_y (t) = \int \limits_{- \infty}^{\infty} \mathrm{d} y \, e^{i t y} f_Y (y)
        $$
    </p>
    <p>
        This integral can be performed analytically (using substitution $u = (1/2) e^{\Sigma y}$ and the integral definition of Gamma function/factorial) and the result is:
    </p>
    <p style="text-align: center;">
        $$
            \phi_y (t) = \left( 4 e^{\gamma - 2} \right)^{i t / \Sigma} \frac{2}{\sqrt{\pi}} \Gamma \left( \frac{3}{2} + \frac{i t}{\Sigma} \right)
        $$
    </p>
    <p>
        As we saw before, the characteristic function of log-density $s = (y_1 + \cdots + y_n) / \sqrt{n}$ thus is:
    </p>
    <div class="math-desktop">
        $$
            \phi (t; n) = \left( \phi_y (t / \sqrt{n}) \right)^n = \left( 4 e^{\gamma - 2} \right)^{\sqrt{n} i t / \Sigma} \left[ \frac{2}{\sqrt{\pi}} \Gamma \left( \frac{3}{2} + \frac{i t}{\sqrt{n} \Sigma} \right) \right]^n
        $$
    </div>
    <div class="math-mobile">
        $$
            \begin{aligned}
                \phi (t; n) &amp;= \left( \phi_y (t / \sqrt{n}) \right)^n = \\
                = \left( 4 e^{\gamma - 2} \right)^{\sqrt{n} i t / \Sigma} &amp;\left[ \frac{2}{\sqrt{\pi}} \Gamma \left( \frac{3}{2} + \frac{i t}{\sqrt{n} \Sigma} \right) \right]^n
            \end{aligned}
        $$
    </div>
    <p>
        From which the PDF of $s$ can be obtained by the inverse Fourier transform:
    </p>
    <p style="text-align: center;">
        $$
            f(s; n) = \frac{1}{2 \pi} \int \limits_{-\infty}^{\infty} \mathrm{d} t \, e^{- i t s} \phi (t; n)
        $$
    </p>
    <p>
        Unfortunately, for a general value of $n$ the result cannot be expressed via any known functions. Nevertheless, we can obtain the distribution numerically and plot the results:
    </p>
    <div class="plot-carousel">
        <button class="carousel-btn left" onclick="changePlot('carousel3', -1)" title="Click to see previous n">&#8592;</button>
        <img id="carousel3-img" src="/CLT_plots/logrho_ideal_n1.png" alt="Plot for n = 1" />
        <button class="carousel-btn right" onclick="changePlot('carousel3', 1)" title="Click to see next n">&#8594;</button>
    </div>
    <p>
        This construction not only confirms that the central limit behavior applies to a sequence of multiplicative shocks in log-space, but also provides a concrete route to compute the resulting distribution of log-density for any finite number of events. In particular, the characteristic function formalism highlights the slow convergence to Gaussianity when the underlying distribution is skewed - as is the case here, due to the asymmetric shape of the Maxwellian in logarithmic coordinates.
    </p>
    <p>
        More analytical results and applications of this model can be found in our paper <a href="https://academic.oup.com/mnrasl/article/521/1/L64/6760005" target="_blank">Rabatin &amp; Collins (2022)</a>, which this post is based on.
    </p>
    <script>
    const carouselData = {
        carousel1: {
        index: 0,
        images: [
            { src: "/CLT_plots/density_mach_lognormal_n1.png", label: "n = 1" },
            { src: "/CLT_plots/density_mach_lognormal_n2.png", label: "n = 2" },
            { src: "/CLT_plots/density_mach_lognormal_n3.png", label: "n = 3" },
            { src: "/CLT_plots/density_mach_lognormal_n5.png", label: "n = 5" },
            { src: "/CLT_plots/density_mach_lognormal_n10.png", label: "n = 10" },
            { src: "/CLT_plots/density_mach_lognormal_n20.png", label: "n = 20" },
            { src: "/CLT_plots/density_mach_lognormal_n30.png", label: "n = 30" },
            { src: "/CLT_plots/density_mach_lognormal_n50.png", label: "n = 50" },
            { src: "/CLT_plots/density_mach_lognormal_n100.png", label: "n = 100" },
            { src: "/CLT_plots/density_mach_lognormal_n200.png", label: "n = 200" },
            { src: "/CLT_plots/density_mach_lognormal_n300.png", label: "n = 300" },
            { src: "/CLT_plots/density_mach_lognormal_n500.png", label: "n = 500" }
        ]
        },
        carousel2: {
        index: 0,
        images: [
            { src: "/CLT_plots/log_mach_lognormal_n1.png", label: "n = 1" },
            { src: "/CLT_plots/log_mach_lognormal_n2.png", label: "n = 2" },
            { src: "/CLT_plots/log_mach_lognormal_n3.png", label: "n = 3" },
            { src: "/CLT_plots/log_mach_lognormal_n5.png", label: "n = 5" },
            { src: "/CLT_plots/log_mach_lognormal_n10.png", label: "n = 10" },
            { src: "/CLT_plots/log_mach_lognormal_n20.png", label: "n = 20" },
            { src: "/CLT_plots/log_mach_lognormal_n30.png", label: "n = 30" },
            { src: "/CLT_plots/log_mach_lognormal_n50.png", label: "n = 50" },
            { src: "/CLT_plots/log_mach_lognormal_n100.png", label: "n = 100" },
            { src: "/CLT_plots/log_mach_lognormal_n200.png", label: "n = 200" },
            { src: "/CLT_plots/log_mach_lognormal_n300.png", label: "n = 300" },
            { src: "/CLT_plots/log_mach_lognormal_n500.png", label: "n = 500" }
        ]
        },
        carousel3: {
        index: 0,
        images: [
            { src: "/CLT_plots/logrho_ideal_n1.png", label: "n = 1" },
            { src: "/CLT_plots/logrho_ideal_n2.png", label: "n = 2" },
            { src: "/CLT_plots/logrho_ideal_n3.png", label: "n = 3" },
            { src: "/CLT_plots/logrho_ideal_n5.png", label: "n = 5" },
            { src: "/CLT_plots/logrho_ideal_n10.png", label: "n = 10" },
            { src: "/CLT_plots/logrho_ideal_n20.png", label: "n = 20" },
            { src: "/CLT_plots/logrho_ideal_n30.png", label: "n = 30" },
            { src: "/CLT_plots/logrho_ideal_n50.png", label: "n = 50" },
            { src: "/CLT_plots/logrho_ideal_n100.png", label: "n = 100" },
            { src: "/CLT_plots/logrho_ideal_n200.png", label: "n = 200" },
            { src: "/CLT_plots/logrho_ideal_n300.png", label: "n = 300" },
            { src: "/CLT_plots/logrho_ideal_n500.png", label: "n = 500" }
        ]
        }
    };
    function changePlot(carouselId, direction) {
        const data = carouselData[carouselId];
        data.index = (data.index + direction + data.images.length) % data.images.length;
        const img = document.getElementById(`${carouselId}-img`);
        img.src = data.images[data.index].src;
        img.alt = `Plot for ${data.images[data.index].label}`;
        const container = img.parentElement;
        const leftBtn = container.querySelector(".carousel-btn.left");
        const rightBtn = container.querySelector(".carousel-btn.right");
        leftBtn.title = `Click to see ${data.images[(data.index - 1 + data.images.length) % data.images.length].label}`;
        rightBtn.title = `Click to see ${data.images[(data.index + 1) % data.images.length].label}`;
    }
    </script>
</div>]]></content><author><name>Branislav Rabatin</name></author><category term="article" /><summary type="html"><![CDATA[See Why Gaussian? Part I: tossing dice for part 1! See Why Gaussian? Part II: continuous additive processes for part 2! In the first part of this series, we saw how a discrete random variable (like a roll of a die), when accumulated across independent trials, converges toward a Gaussian distribution. In the second part, we extended this to continuous variables - showing how, under mild conditions on their moments, the same convergence occurs. In this post, we take a different path. Not all natural processes are additive. Some involve multiplication, cyclic variables, or nonlinear transformations. Yet, remarkably, the Gaussian often still emerges-sometimes not in the variable itself, but in a transformed version like its logarithm or phase. Let's explore these less-obvious routes to Gaussian behavior. Multiplicative random processes While the central limit theorem is traditionally associated with additive processes, many real-world systems operate multiplicatively. Fortunately, multiplication becomes addition in logarithmic space, so a wide array of CLT-like results still apply, just one transformation away. In fact, many systems that produce log-normal statistics are fundamentally multiplicative at their core. Consider a few well-known examples: compound interest in finance, population growth with random fluctuations, or the cascade of returns in stock markets - all evolve by repeated multiplication of random factors. In such cases, taking the logarithm converts the product into a sum, and the central limit theorem applies to the log-transformed variable. As a result, the original variable becomes log-normally distributed. One particularly elegant example comes from astrophysical fluid dynamics: the density structure in isothermal, supersonic turbulence. In such flows, density fluctuations are largely governed by shock waves. Each shock compresses the local density by a factor determined by the velocity field, so the density doesn't evolve additively, but multiplicatively. For isothermal shocks (where the adiabatic index $\gamma = 1$), the Rankine–Hugoniot jump condition simplifies to a density jump $\rho_2 / \rho_1 = m^2$, where (dimensionless) $m$ is the upstream Mach number; the speed of the shock reduced by the speed of sound. As gas elements traverse a sequence of shocks, the resulting density is repeatedly multiplied by factors drawn from the local velocity dispersion. To be clear, if a parcel of gas is affected by $n$ shocks passing through it, each propagating with some random local Mach number $m$, the resulting density will be $$ \rho = \rho_0 \, m_1 \, m_2 \cdots m_n $$ Naturally, to understand the distribution of final densities, we must know how the local Mach number itself is distributed. In a turbulent flow, these are not fixed, but fluctuate based on the local velocity field. The precise form of the Mach number distribution depends on the turbulence driving and thermodynamic conditions, but it's generally broad and continuous, with most values clustered near the mean turbulent Mach number. If we assume the Mach numbers $m_i$ for each shock are drawn independently from such a distribution (or more realistically, weakly correlated), then the logarithm of the final density becomes a sum of random terms: $$ \log (\rho / \rho_0) + \log m_1 + \log m_2 + \cdots + \log m_n. $$ This is precisely the setup where the central limit theorem applies. Under fairly weak assumptions about the shape and variance of the $\log m$ distribution, the sum converges toward a Gaussian. Therefore, the logarithm of density, $s \equiv \log (\rho / \rho_0)$ follows a normal distribution, and the density itself follows a lognormal distribution: $$ f (s) \sim \mathcal{N}(\mu, \sigma^2), \quad \text{so} \quad f (\rho) \sim \frac{1}{\rho} \exp\left(-\frac{(\log (\rho / \rho_0) - \mu)^2}{2\sigma^2} \right). $$ $$ f (s) \sim \mathcal{N}(\mu, \sigma^2) $$ so, $$ f (\rho) \sim \frac{1}{\rho} \exp\left(-\frac{(\log (\rho / \rho_0) - \mu)^2}{2\sigma^2} \right) $$ But what does the distribution of local Mach numbers actually look like in a turbulent flow? Since the Mach number $m = v / c_s$ is just the velocity normalized by the sound speed, this really comes down to the statistics of local velocity magnitudes. In an isothermal turbulent medium with no large-scale bulk motion, the three Cartesian components of velocity can be reasonably modeled as independent random variables drawn from a Gaussian distribution centered at zero-thanks to the central limit effect of countless random driving forces. When this is the case, the magnitude of the velocity (and hence the local Mach number) follows a Maxwell–Boltzmann distribution. In three dimensions, the Maxwellian PDF for the Mach number $m$ takes the form: $$ f (m) = \frac{4 \pi m^2}{(2 \pi \sigma^2)^{3/2}} \exp \left( - \frac{m^2}{2 \sigma^2} \right) $$ where $\sigma$ is the standard deviation of the underlying 1D velocity components (in units of the sound speed). This shape arises naturally when you compute the distribution of the magnitude of a 3D Gaussian vector. Most of the probability mass lies around the typical turbulent Mach number, but there's a non-negligible tail at high $m$, allowing for occasional strong shocks. There's one important complication we need to address before we go further: real shock waves only occur when the flow is supersonic, that is, when the Mach number $m &gt; 1$. If the flow is subsonic, information travels fast enough upstream to smooth out any discontinuity before a shock can form. In our setup, however, we're drawing Mach numbers from a continuous distribution (like the Maxwellian), which includes both subsonic and supersonic values. So what happens to those subsonic samples in our model? Strictly speaking, they shouldn't correspond to shocks at all. But in turbulent flows, not every compressive or expansive feature is a perfect shock. There are also rarefaction waves, which are smooth expansions rather than discontinuous jumps. Unlike shocks, which compress the gas and increase the density, rarefactions decrease the density across a smooth gradient. In our simplified picture, we can treat these rarefactions as "anti-shocks"; they still change the density by a multiplicative factor, but one that's less than one. A convenient way to model both compression and expansion statistically is to let each event (shock or rarefaction) multiply the density by a factor of $m^2$, even when $m &lt; 1$. This may not match every microscopic detail, but it captures the essential multiplicative character of density evolution in turbulent media. This Maxwellian form gives us a physically motivated, continuous distribution from which to draw the multiplicative shock factors $m^2$. With that foundation in place, we're finally ready to run a small numerical experiment. Shocks as a random multiplicative process: numerical experiment We'll simulate a parcel of gas undergoing a series of random isothermal shocks. Each shock multiplies the current density by $m^2$, where the Mach number $m$ is drawn from the Maxwellian distribution with some pre-defined $\sigma$. After a few such multiplicative steps, we'll examine the distribution of the resulting density. Here's a Python code that will give us some distributions: Show Code import numpy as np import matplotlib.pyplot as plt from scipy.stats import maxwell, norm, lognorm from tqdm import tqdm import os]]></summary></entry><entry><title type="html">Why Gaussian? Part II: continuous additive processes</title><link href="https://rabatinb.com/blog/why-gaussian-part-ii/" rel="alternate" type="text/html" title="Why Gaussian? Part II: continuous additive processes" /><published>2025-04-29T16:00:00+00:00</published><updated>2025-04-29T16:00:00+00:00</updated><id>https://rabatinb.com/blog/CLT2</id><content type="html" xml:base="https://rabatinb.com/blog/why-gaussian-part-ii/"><![CDATA[<div style="flex: 1; text-align: justify;">
    <h3>See <a href="/blog/why-gaussian-part-i/">Why Gaussian? Part I: tossing dice</a> for part 1!</h3>
    <h3>See <a href="/blog/why-gaussian-part-iii/">Why Gaussian? Part III: not all processes are additive</a> for part 3!</h3>
    <p>
        In the first part on this topic, <a href="/blog/why-gaussian-part-i/">Why Gaussian? Part I: tossing dice</a>, we explored how tossing more and more dice and summing the result of all tosses gives us a number that's distributed closer and closer to a Gaussian. In the second part, we will observe the same thing happening for continuous processes, where the underlying numbers come from a distribution over real numbers (or a continuous interval).
    </p>
    <p>
        We start by defining a <strong>source distribution</strong> - it can be anything not too pathological (it should have well-defined mean and variance), $\mathcal{D} (x)$. We can verify the CLT for a process, in which we don't get a number directly from the source distribution, rather, we get an average of $n$ values generated by this random process
    </p>
    <p style="text-align: center;">
            $$
                X = \frac{1}{n} \sum_{i = 1}^n x_i, \quad x_i \sim \mathcal{D}
            $$
        </p>
    <p>
        We will see, that value $X$ follows a distribution closer and closer to Gaussian if $n$ is large. We will see this by taking an <strong>ensemble</strong> of $N$ samples $(X_1, X_2, \dots, X_N)$. Since the overall mean and variance might, over many samples $n$, drift far away from the origin (especially if the source distribution does not have zero mean), we <strong>renormalize</strong> the samples by subtracting the ensemble mean and dividing by variance:
    </p>
    <div class="math-desktop">
        $$
            Y_k = \frac{X_k - \mu_X}{\sigma_X}, \quad \mu_X = \frac{1}{N} \sum_{k = 1}^N X_k, \quad \sigma_X^2 = \frac{1}{N} \sum_{k = 1}^N \left( X_k - \mu \right)^2
        $$
    </div>
    <div class="math-mobile">
        $$
            Y_k = \frac{X_k - \mu_X}{\sigma_X}, \quad 
            \begin{aligned}
                \mu_X &amp;= \frac{1}{N} \sum_{k = 1}^N X_k \\
                \sigma_X^2 &amp;= \frac{1}{N} \sum_{k = 1}^N \left( X_k - \mu \right)^2
            \end{aligned}
        $$
    </div>
    <p>
        Now we (or rather the central limit theorem) claim that the distribution of $Y_k$ will tend to the normal distribution if $n$ is large enough. Let's verify this using Python! We start by importing all the required modules:
    </p>
    <pre><code class="language-python">import numpy as np
import matplotlib.pyplot as plt
import os</code></pre>
    <p>
        Here numpy is required to generate the random samples, matplotlib for making neat plots and os for I/O. Having done that, we now proceed with the function that will generate the samples, renormalize them, create histograms and save the histograms. Thinking ahead, we know we will need several inputs: the random distribution, $n$, $N$. Additionally, we will also control the number of bins to create the resulting distribution. We can include more arguments to make plotting more modular, but ultimately, the core of the function is captured with the first 4 arguments.
    </p>
    <pre><code class="language-python">def clt_demo(
    dist_func, # source random distribution
    n=5, # baseline number of random variables to sum
    N=10000, # size of the ensemble
    bins=50, # number of bins for visualization
    base_dir="",
    title="CLT Demo",
    filename="plot.png"
    bounds=[-10,10] # cutoff if the distribution is weird
):</code></pre>
    <p>
        The neat thing about Python is its vectorization capabilities; almost all built-in functions are capable of taking in an array of arbitrary shape which then results in output of the same shape, where the function is applied to each element. We can utilize this by generating $N \times n$ values all at once in a big $N \times n$ array, which we then sum over the last index. Specifically,
    </p>
    <pre><code class="language-python">samples = dist_func(size=(N, n))
sums = np.sum(samples, axis=1)</code></pre>
    <p>
        For really high $n$ and $N$, we would implement a different strategy where the sum is being performed on the fly to save the memory. Having done this, the rest of the code simply normalizes the ensemble, plots the histogram and saves it. Full code with some examples:
    </p>
    <button onclick="toggleCode()" style="margin-bottom: 10px;">Show Code</button>

<div id="codeBlock" style="display: none;">
  <pre><code class="language-python">import numpy as np
from tqdm import tqdm
import matplotlib.pyplot as plt
import os
from scipy.stats import norm

def clt*demo(
dist_func, # function to draw random samples
n=5, # number of random variables to sum
N=10000, # how many sums to create
bins=100, # number of bins for the histogram
base_dir="",
title="CLT Demo",
filename="plot.png", # filename to save
bounds=[-4.5,4.5] # cutoff if the distribution is weird
): # create the ensemble. Tracking progress with tqdm
sums = np.zeros(N)
for * in tqdm(range(n), desc="Adding random samples"):
sums += dist_func(size=N)

    # mean and variance
    mean = np.mean(sums)
    std = np.std(sums)

    # normalize ensemble
    normalized = (sums - mean) / std

    normalized = normalized[(normalized &gt; bounds[0]) &amp;
     (normalized &lt; bounds[1])]

    # plot and save
    plt.figure(figsize=(8, 5))

    # histogram
    counts, bins_edges, _ = plt.hist(
        normalized, bins=bins, density=True, alpha=0.7,
        color="skyblue", edgecolor="black", label="Ensemble histogram"
    )

    # overplot ideal normal distribution
    x = np.linspace(bounds[0], bounds[1], 1000)
    y = norm.pdf(x, loc=0, scale=1)  # zero mean, unit variance
    plt.plot(x, y, 'k--', label="Ideal Normal Distribution")

    plt.title(f"{title}\n(n={n}, N={N})")
    plt.xlabel("Normalized Sum")
    plt.ylabel("Probability Density")
    plt.grid(True)
    plt.legend()

    # save figure
    full_path = os.path.join(base_dir, filename)
    plt.savefig(full_path, dpi=150)
    plt.close()
    print(f"Saved plot to {full_path}")

# fun distributions

def uniform_distribution(size):
return np.random.uniform(low=0.0, high=1.0, size=size)

def exponential_distribution(size):
return np.random.exponential(scale=1.0, size=size)

def bernoulli_distribution(size):
return np.random.choice([0, 1], size=size)

def heavy_tail_distribution(size):
return np.random.standard_cauchy(size=size)

def sample*custom_tail(alpha, size):
u = np.random.uniform(low=0.0, high=1.0, size=size)
s = np.sign(2 * u - 1)
transformed = ( (np.pi / 2)\*\*(1/(1+alpha)) \_ np.abs(2*u - 1) )\*\*(1+alpha)
x = s * (np.tan(transformed))\*\*(1/(1+alpha))
return x

spiky1 = lambda size: sample_custom_tail(alpha=0, size=size)
spiky2 = lambda size: sample_custom_tail(alpha=0.5, size=size)
spiky3 = lambda size: sample_custom_tail(alpha=1, size=size)
spiky4 = lambda size: sample_custom_tail(alpha=1.5, size=size)
spiky5 = lambda size: sample_custom_tail(alpha=2, size=size)
spiky6 = lambda size: sample_custom_tail(alpha=3, size=size)

Npts = 10000000

dir = 'CLT_plots'
os.makedirs(dir, exist_ok=True)

# Example runs

for n in [1,2,3,4,5,10,50,100,1000]:
    clt_demo(spiky1, n=n, N=Npts, base_dir=dir, filename="Cauchy1_n"+str(n)+".png", title="Gen. Cauchy, alpha ~ 2")
    clt_demo(spiky2, n=n, N=Npts, base_dir=dir, filename="Cauchy2_n"+str(n)+".png", title="Gen. Cauchy, alpha ~ 2.5")
    clt_demo(spiky3, n=n, N=Npts, base_dir=dir, filename="Cauchy3_n"+str(n)+".png", title="Gen. Cauchy, alpha ~ 3")
    clt_demo(spiky4, n=n, N=Npts, base_dir=dir, filename="Cauchy4_n"+str(n)+".png", title="Gen. Cauchy, alpha ~ 3.5")
    clt_demo(spiky5, n=n, N=Npts, base_dir=dir, filename="Cauchy5_n"+str(n)+".png", title="Gen. Cauchy, alpha ~ 4")
    clt_demo(spiky6, n=n, N=Npts, base_dir=dir, filename="Cauchy6_n"+str(n)+".png", title="Gen. Cauchy, alpha ~ 5")
    clt_demo(uniform_distribution, n=n, N=Npts, base_dir=dir, filename="uniform_n"+str(n)+".png", title="Uniform Distribution")
    clt_demo(exponential_distribution, n=n, N=Npts, base_dir=dir, filename="exponential_n"+str(n)+".png", title="Exponential Distribution")
    clt_demo(bernoulli_distribution, n=n, N=Npts, base_dir=dir, filename="bernoulli_n"+str(n)+".png", title="Bernoulli Distribution")
</code></pre>

</div>
<script>
    function toggleCode() {
        var x = document.getElementById("codeBlock");
        var button = event.target;
        if (x.style.display === "none") {
            x.style.display = "block";
            button.innerText = "Hide Code";
        } else {
            x.style.display = "none";
            button.innerText = "Show Code";
        }
    }
    </script>
        <p>
            We can now look at some examples. We start with the uniform baseline distribution that generates numbers between 0 and 1 with equal probability. With that, the expected histogram for $n = 1$ is flat:
        </p>
        <figure style="text-align: center;">
            <img src="/CLT_plots/uniform_n1.png" alt="Uniform Distribution" width="100%" />
            <figcaption style="font-size: smaller; color: gray;">Uniform Distribution.</figcaption>
        </figure>
        <p>
            The range changed due to the normalization to zero mean and unit variance. Now, if we increase $n$ to $2, 3, 4, 5$, we get closer and closer to the Gaussian distribution:
        </p>
        <!-- Carousel 1: Uniform Distribution -->
    <div class="plot-carousel">
    <button class="carousel-btn left" onclick="changePlot('carousel1', -1)" title="Click to see previous n">&#8592;</button>
    <img id="carousel1-img" src="/CLT_plots/uniform_n1.png" alt="Plot for n = 1" />
    <button class="carousel-btn right" onclick="changePlot('carousel1', 1)" title="Click to see next n">&#8594;</button>
    </div>
    <p>
        Convergence towards a distribution is a very rich and interesting topic; not all distributions will lead to the Gaussian; a heavy-tailed Cauchy distribution will converge to another Cauchy distribution, for example. However, as long as the distribution is "well-behaved", convergence is assured. Berry-Esseen's theorem guarantees a relatively slow convergence (the difference between Gaussian and the distribution after summing $n$ terms is on the order of $1/\sqrt{n}$) provided the third moment $\langle x^3 \rangle$ exists and is finite. We saw that the convergence for the uniform distribution is rather quick; this is because the third moment is actually zero, in which case the convergence is slightly faster ($1/n$). The behavior for the Cauchy distribution can be observed here:
    </p>
    <!-- Carousel 2: Cauchy Distribution -->
    <div class="plot-carousel">
    <button class="carousel-btn left" onclick="changePlot('carousel2', -1)" title="Click to see previous n">&#8592;</button>
    <img id="carousel2-img" src="/CLT_plots/Cauchy1_n1.png" alt="Plot for n = 1" />
    <button class="carousel-btn right" onclick="changePlot('carousel2', 1)" title="Click to see next n">&#8594;</button>
    </div>
    <script>
    const carouselData = {
        carousel1: {
        index: 0,
        images: [
            { src: "/CLT_plots/uniform_n1.png", label: "n = 1" },
            { src: "/CLT_plots/uniform_n2.png", label: "n = 2" },
            { src: "/CLT_plots/uniform_n3.png", label: "n = 3" },
            { src: "/CLT_plots/uniform_n5.png", label: "n = 5" },
            { src: "/CLT_plots/uniform_n10.png", label: "n = 10" },
            { src: "/CLT_plots/uniform_n20.png", label: "n = 20" },
            { src: "/CLT_plots/uniform_n30.png", label: "n = 30" },
            { src: "/CLT_plots/uniform_n40.png", label: "n = 40" },
            { src: "/CLT_plots/uniform_n50.png", label: "n = 50" },
            { src: "/CLT_plots/uniform_n100.png", label: "n = 100" }
        ]
        },
        carousel2: {
        index: 0,
        images: [
            { src: "/CLT_plots/Cauchy1_n1.png", label: "n = 1" },
            { src: "/CLT_plots/Cauchy1_n2.png", label: "n = 2" },
            { src: "/CLT_plots/Cauchy1_n3.png", label: "n = 3" },
            { src: "/CLT_plots/Cauchy1_n5.png", label: "n = 5" },
            { src: "/CLT_plots/Cauchy1_n10.png", label: "n = 10" },
            { src: "/CLT_plots/Cauchy1_n20.png", label: "n = 20" },
            { src: "/CLT_plots/Cauchy1_n30.png", label: "n = 30" },
            { src: "/CLT_plots/Cauchy1_n40.png", label: "n = 40" },
            { src: "/CLT_plots/Cauchy1_n50.png", label: "n = 50" },
            { src: "/CLT_plots/Cauchy1_n100.png", label: "n = 100" }
        ]
        }
    };
    function changePlot(carouselId, direction) {
        const data = carouselData[carouselId];
        data.index = (data.index + direction + data.images.length) % data.images.length;
        const img = document.getElementById(`${carouselId}-img`);
        img.src = data.images[data.index].src;
        img.alt = `Plot for ${data.images[data.index].label}`;
        const container = img.parentElement;
        const leftBtn = container.querySelector(".carousel-btn.left");
        const rightBtn = container.querySelector(".carousel-btn.right");
        leftBtn.title = `Click to see ${data.images[(data.index - 1 + data.images.length) % data.images.length].label}`;
        rightBtn.title = `Click to see ${data.images[(data.index + 1) % data.images.length].label}`;
    }
    </script>
    <p>
        As we can see, the Cauchy distribution never truly strays from its original shape. With its very heavy tails ($f(x) \sim x^{-2}$), the Cauchy distribution lacks a well-defined mean or variance, and therefore doesn't satisfy the conditions of the central limit theorem. Nevertheless, for a carefully chosen width, its behavior under averaging can still stabilize in a meaningful way.
    </p>
    <h3>Why does the convergence towards a Gaussian happen analytically?</h3>
    <p>
        So far, we've explored the central limit theorem through numerical experiments - summing random values and watching the distribution approach a Gaussian. This analytical derivation complements our earlier Python demonstration, where we plotted the distribution of normalized averages for increasing $n$. Now, let's understand why the Gaussian appears so universally from a mathematical perspective. A powerful tool to approach such problem is the <strong>characteristic function</strong>.
    </p>
    <h4>What is a characteristic function?</h4>
    <p>
        For a random variable $x$, the characteristic function is defined as:
    </p>
    <p style="text-align: center;">
        $$
            \phi (t) = \left\langle e^{i t x} \right\rangle
        $$
    </p>
    <p>
        This expression might be confusing, but it essentially means: given a random variable $x$, take the complex-valued expression $e^{i t x}$ where $t$ is some (real, for now) number and compute the average value of such expression. Since $t$ is in there, this will be a function of $t$ - the characteristic function! It captures the entire distribution and behaves nicely under addition of independent random variables, which is extremely useful for our purposes here.
    </p>
    
    <details class="aside-box aside-box--note" open="">
  <summary class="aside-box__summary">
    <span class="aside-box__heading">
      <span class="aside-box__kicker">Aside</span><span class="aside-box__title">Characteristic function of the Gaussian</span></span>
    <span class="aside-box__toggle" aria-hidden="true">
      <span class="aside-box__toggle-text aside-box__toggle-text--closed">Show</span>
      <span class="aside-box__toggle-text aside-box__toggle-text--open">Hide</span>
    </span>
  </summary>
  <div class="aside-box__body">
    
<p>Suppose $x \sim \mathcal{N}(0, 1)$, so the distribution of $x$ is $(2 \pi)^{-1/2} \exp ( - x^2 / 2)$. Its characteristic function is</p>

<div class="math-center">
$$
    \phi(t) = \left\langle e^{i t x} \right\rangle = \int_{-\infty}^{\infty} e^{i t x} \frac{1}{\sqrt{2\pi}} e^{-x^2/2} \, dx
$$
</div>

<p>We complete the square in the exponent:</p>

<div class="math-center">
$$
    e^{i t x - x^2/2} = e^{- (x - i t)^2/2} \cdot e^{-t^2/2}
$$
</div>

<p>So the integral becomes:</p>

<div class="math-center">
$$
    \phi(t) = e^{-t^2/2} \int_{-\infty}^{\infty} \frac{1}{\sqrt{2\pi}} e^{- (x - i t)^2 / 2} \, dx = e^{-t^2/2}
$$
</div>

<p>since the integral evaluates to 1 (just a shift of a normalized Gaussian). So:</p>

<div class="math-result">
$$
    \phi(t) = e^{-t^2 / 2}
$$
</div>

<p>This is the characteristic function of the standard normal distribution.</p>


  </div>
</details>

    <h4>Step-by-step: what happens when we average?</h4>
    <p>
        Let's say $x$ is drawn from a distribution $\mathcal{D}(x)$ with mean zero and variance 1 (we can always shift and rescale to make this true). Now, define
    </p>
    <p style="text-align: center;">
        $$
            X = \frac{1}{\sqrt{n}} \left( x_1 + x_2 + \dots + x_n \right)
        $$
    </p>
    <p>
        This is the average of $n$ independent samples, rescaled so that its variance stays equal to 1 no matter the value of $n$ (if you have a hard time believing that specifically factor $1/\sqrt{n}$ is needed to make this work, keep reading). We are interested in the characteristic function of $X$
    </p>
    <p style="text-align: center;">
        $$
            \phi_n (t) = \left\langle e^{i t X} \right\rangle
        $$
    </p>
    <p>
        Since the $x_i$ are independent, this becomes:
    </p>
    <div class="math-desktop">
        $$
            \begin{aligned}
                \phi_n(t) &amp;= \left\langle e^{i t X} \right\rangle 
                = \left\langle e^{i t (x_1 + x_2 + \dots + x_n)/\sqrt{n}} \right\rangle = \\
                &amp;= \left\langle e^{i t x_1 / \sqrt{n}} \right\rangle \left\langle e^{i t x_2 / \sqrt{n}} \right\rangle \cdots \left\langle e^{i t x_n / \sqrt{n}} \right\rangle
                = \left( \phi_1(t / \sqrt{n}) \right)^n
            \end{aligned}
        $$
    </div>
    <div class="math-mobile">
        $$
            \begin{aligned}
                &amp;\; \phi_n(t) = \left\langle e^{i t X} \right\rangle = \left\langle e^{i t (x_1 + x_2 + \dots + x_n)/\sqrt{n}} \right\rangle \\
                &amp;= \left\langle e^{i t x_1 / \sqrt{n}} \right\rangle \left\langle e^{i t x_2 / \sqrt{n}} \right\rangle \cdots \left\langle e^{i t x_n / \sqrt{n}} \right\rangle \\
                &amp;\quad \quad= \left( \phi_1(t / \sqrt{n}) \right)^n
            \end{aligned}
        $$
    </div>
    <p>
        where $\phi_1 (t)$ is the characteristic function of $x$.
    </p>
    <h4>Expanding the characteristic function</h4>
    <p>
        Let's expand the individual characteristic function $\phi_1(t)$ into a Taylor series. Assuming all moments exist, we get:
    </p>
    <div class="math-desktop">
        $$
            \begin{aligned}
                &amp;\; \phi_1(t) = \left\langle e^{i t x} \right\rangle = \\
                &amp;= \left\langle 1 + i t x - \frac{1}{2!} t^2 x^2 - \frac{i}{3!} t^3 x^3 - \frac{1}{4!} t^4 x^4 + \cdots \right\rangle = \\
                &amp;= 1 + i \langle x \rangle t - \frac{1}{2!} \langle x^2 \rangle t^2 - \frac{i}{3!} \langle x^3 \rangle t^3 - \frac{1}{4!} \langle x^4 \rangle t^4 + \cdots = \\
                &amp;= 1 - \frac{t^2}{2} - \frac{i \langle x^3 \rangle}{3!} t^3 + \frac{\langle x^4 \rangle}{4!} t^4 + \frac{i \langle x^5 \rangle}{5!} t^5 - \cdots
            \end{aligned}
        $$
    </div>
    <div class="math-mobile">
        $$
            \begin{aligned}
                &amp;\quad \phi_1(t) = \left\langle e^{i t x} \right\rangle = \\
                &amp;{\scriptstyle = \left\langle 1 + i t x - \frac{1}{2!} t^2 x^2 - \frac{i}{3!} t^3 x^3 - \frac{1}{4!} t^4 x^4 + \cdots \right\rangle} \\
                &amp;{\scriptstyle = 1 + i \langle x \rangle t - \frac{1}{2!} \langle x^2 \rangle t^2 - \frac{i}{3!} \langle x^3 \rangle t^3 - \frac{1}{4!} \langle x^4 \rangle t^4 + \cdots} \\
                &amp;= 1 - \frac{t^2}{2} - \frac{i \langle x^3 \rangle}{3!} t^3 + \frac{\langle x^4 \rangle}{4!} t^4 + \frac{i \langle x^5 \rangle}{5!} t^5 - \scriptstyle\cdots
            \end{aligned}
        $$
    </div>
    <p>
        Where we used the fact that $\langle x \rangle = 0$ (zero mean) and $\langle x^2 \rangle = 1$ (unit variance). Recall we assumed this earlier for generality - any distribution with finite mean and variance can be rescaled to meet this. Now plug in $t/\sqrt{n}$ instead of $t$, and raise the whole thing to the power of $n$:
    </p>
    <div class="math-desktop">
        $$
            \phi_n(t) = \left( \phi_1 \left( \frac{t}{\sqrt{n}} \right) \right)^n
            = \left( 1 - \frac{t^2}{2n} - \frac{i \langle x^3 \rangle}{3! \, n^{3/2}} t^3
            + \frac{\langle x^4 \rangle}{4! \, n^2} t^4 + \dots \right)^n
        $$
    </div>
    <div class="math-mobile">
        $$
            \begin{aligned}
                &amp;\phi_n(t) = \left( \phi_1 \left( \frac{t}{\sqrt{n}} \right) \right)^n \\
                &amp;= \left( 1 - \frac{t^2}{2n}- \frac{i \langle x^3 \rangle}{3! \, n^{3/2}} t^3 + \frac{\langle x^4 \rangle}{4! \, n^2} t^4 + \dots \right)^n
            \end{aligned}
        $$
    </div>
    <h4>Watch the limit work</h4>
    <p>
        As $n \to \infty$, this product starts to resemble an exponential. To see that, recall this famous limit:
    </p>
    <p style="text-align: center;">
        $$
            \lim_{n \to \infty} \left( 1 + \frac{x}{n} \right)^n = e^x
        $$
    </p>
    <p>
        Our expansion now becomes:
    </p>
    <p style="text-align: center;">
        $$
            \phi_{n \to \infty} (t) = \lim_{n \to \infty} \left( 1 - \frac{t^2}{2 n} + o (1/n) \right)^n = e^{-t^2 / 2}
        $$
    </p>
    <p>
        And that is <strong>exactly</strong> the characteristic function of the standard Gaussian distribution. We can also see that the factor $1/\sqrt{n}$ was needed to exactly balance out the power of $n$, leaving a nicely behaving function behind.
    </p>
    <p>
        This simple but powerful argument shows us why the Gaussian distribution emerges from summing up many independent contributions: the higher-order terms vanish in the limit, leaving only the second moment (the variance), which controls the width of the bell curve.
    </p>
    <h4>Beyond the limit: rate of convergence</h4>
    <p>
        We just showed why so many random additive processes converge to a Gaussian. We might, however, be interested in the rate of convergence towards this universal distribution. How high $n$ is high enough? For that we need to go back to the expression for $\phi_n (t)$ and its relation to $\phi_1 (t)$
    </p>
    <p style="text-align: center;">
        $$
            \begin{aligned}
                &amp;\phi_n (t) = \left( \phi_1 (t / \sqrt{n}) \right)^n = \\
                = &amp;\left( 1 - \frac{t^2}{2n} - \frac{i \langle x^3 \rangle}{3! n^{3/2}} t^3 + \frac{\langle x^4 \rangle}{4! n^2} t^4 + \dots \right) \times \cdots \\
                \times &amp;\left( 1 - \frac{t^2}{2 n } - \frac{i \langle x^3 \rangle}{3! n^{3/2}} t^3 + \frac{\langle x^4 \rangle}{4! n^2} t^4 + \dots \right)
            \end{aligned}
        $$
    </p>
    <p>
        To obtain the term-by-term expansion of this product, we'll start from the lowest power in $t$, building up. First, the only way we get a constant independent of $t$ is to take factor of $1$ from each bracket, which gives us again a factor of 1. The next lowest possible power in $t$ is $t^2$, which can be obtained by taking a term $t^2$ from one single bracket and $1$ from all others. Since there are $n$ total brackets, there will be $n$ such terms. This gives us the start of the expansion:
    </p>
    <p style="text-align: center;">
        $$
            \phi_n (t) = 1 - n\frac{t^2}{2 n} + \cdots = 1 - \frac{t^2}{2} + \cdots
        $$
    </p>
    <p>
        As expected, we are starting to see the expansion for $\exp (- t^2 / 2)$ and $n$ has not appeared yet - the first two terms are independent of $n$. We can continue with the cubic term. Since none of the brackets contains a term linear in $t$, the only way to get a cubic term is to again take a single cubic term from one of the brackets and $1$ from all other brackets, resulting in
    </p>
    <p style="text-align: center;">
        $$
            \phi_n (t) = 1 - \frac{t^2}{2} - \frac{i \langle x^3 \rangle}{3! \sqrt{n}} t^3 + \cdots
        $$
    </p>
    <p>
        We can see that in the limit $n \to \infty$, the cubic term vanishes, because it is proportional to $1/\sqrt{n}$. This is consistent with the limit being the characteristic function of the standard Gaussian, $\exp (- t^2 / 2)$, as it does not have skewness. The quartic term can be derived by collecting all the possible ways $t^4$ can occur in the result ($n(n-1) / 2$ ways to take the quadratic term from exactly 2 brackets + quartic term from a single bracket), we get
    </p>
    <div class="math-desktop">
        $$
            \phi_n(t) = 1 - \frac{t^2}{2} - \frac{i \langle x^3 \rangle}{3! \sqrt{n}} t^3 + \left( \frac{1}{8} + \frac{\langle x^4 \rangle - 3}{4! \, n} \right) t^4 + \cdots
        $$
    </div>
    <div class="math-mobile">
        $$
            \begin{aligned}
                \phi_n (t) &amp;= 1 - \frac{t^2}{2} - \frac{i \langle x^3 \rangle}{3! \sqrt{n}} t^3 \\
                &amp;\quad + \left( \frac{1}{8} + \frac{\langle x^4 \rangle - 3}{4! \, n} \right) t^4 + \cdots
            \end{aligned}
        $$
    </div>
    <p>
        Here we see that when $n \to \infty$ the quartic term converges to $t^4 / 8$, which is the next expansion term for $\exp (- t^2 / 2)$.
    </p>
    <p>
        We can see a very definitive result in these expansions: the rate of convergence toward a Gaussian depends on how quickly the higher-order terms vanish. If the original distribution has a nonzero skewness $\langle x^3 \rangle \neq 0$, the leading correction is of the order $1/\sqrt{n}$. If the skewness vanishes, the next correction (from $\langle x^4 \rangle - 3$) is of order $1/n$. If the distribution shares more moments with the Gaussian (zero odd moments, even moments $\langle x^{2k} \rangle = (2k-1)!!$), convergence can be even faster, though such cases occuring naturally are increasingly rare in practice.
    </p>
    <p>
        This derivation reveals just how fundamental the Gaussian distribution is: averaging independent effects suppresses the contribution of all but the second moment, leaving behind the familiar bell curve. The speed at which this convergence occurs depends on the structure of the original distribution — symmetry and finite moments help the process along, but even asymmetric or jagged sources eventually yield.
    </p>
    <p>
        But not all processes in nature are additive. Some involve multiplication, feedback, or nonlinearity, yet the Gaussian still shows up, sometimes where we least expect it. In <a href="/blog/why-gaussian-part-iii/">part 3</a> we look at a few such examples and look for the Gaussian hiding under the hood.
    </p>
</div>]]></content><author><name>Branislav Rabatin</name></author><category term="article" /><summary type="html"><![CDATA[See Why Gaussian? Part I: tossing dice for part 1! See Why Gaussian? Part III: not all processes are additive for part 3! In the first part on this topic, Why Gaussian? Part I: tossing dice, we explored how tossing more and more dice and summing the result of all tosses gives us a number that's distributed closer and closer to a Gaussian. In the second part, we will observe the same thing happening for continuous processes, where the underlying numbers come from a distribution over real numbers (or a continuous interval). We start by defining a source distribution - it can be anything not too pathological (it should have well-defined mean and variance), $\mathcal{D} (x)$. We can verify the CLT for a process, in which we don't get a number directly from the source distribution, rather, we get an average of $n$ values generated by this random process $$ X = \frac{1}{n} \sum_{i = 1}^n x_i, \quad x_i \sim \mathcal{D} $$ We will see, that value $X$ follows a distribution closer and closer to Gaussian if $n$ is large. We will see this by taking an ensemble of $N$ samples $(X_1, X_2, \dots, X_N)$. Since the overall mean and variance might, over many samples $n$, drift far away from the origin (especially if the source distribution does not have zero mean), we renormalize the samples by subtracting the ensemble mean and dividing by variance: $$ Y_k = \frac{X_k - \mu_X}{\sigma_X}, \quad \mu_X = \frac{1}{N} \sum_{k = 1}^N X_k, \quad \sigma_X^2 = \frac{1}{N} \sum_{k = 1}^N \left( X_k - \mu \right)^2 $$ $$ Y_k = \frac{X_k - \mu_X}{\sigma_X}, \quad \begin{aligned} \mu_X &amp;= \frac{1}{N} \sum_{k = 1}^N X_k \\ \sigma_X^2 &amp;= \frac{1}{N} \sum_{k = 1}^N \left( X_k - \mu \right)^2 \end{aligned} $$ Now we (or rather the central limit theorem) claim that the distribution of $Y_k$ will tend to the normal distribution if $n$ is large enough. Let's verify this using Python! We start by importing all the required modules: import numpy as np import matplotlib.pyplot as plt import os Here numpy is required to generate the random samples, matplotlib for making neat plots and os for I/O. Having done that, we now proceed with the function that will generate the samples, renormalize them, create histograms and save the histograms. Thinking ahead, we know we will need several inputs: the random distribution, $n$, $N$. Additionally, we will also control the number of bins to create the resulting distribution. We can include more arguments to make plotting more modular, but ultimately, the core of the function is captured with the first 4 arguments. def clt_demo( dist_func, # source random distribution n=5, # baseline number of random variables to sum N=10000, # size of the ensemble bins=50, # number of bins for visualization base_dir="", title="CLT Demo", filename="plot.png" bounds=[-10,10] # cutoff if the distribution is weird ): The neat thing about Python is its vectorization capabilities; almost all built-in functions are capable of taking in an array of arbitrary shape which then results in output of the same shape, where the function is applied to each element. We can utilize this by generating $N \times n$ values all at once in a big $N \times n$ array, which we then sum over the last index. Specifically, samples = dist_func(size=(N, n)) sums = np.sum(samples, axis=1) For really high $n$ and $N$, we would implement a different strategy where the sum is being performed on the fly to save the memory. Having done this, the rest of the code simply normalizes the ensemble, plots the histogram and saves it. Full code with some examples: Show Code]]></summary></entry><entry><title type="html">BeamNG mods section update</title><link href="https://rabatinb.com/updates/2025/04/29/new-section-S1203.html" rel="alternate" type="text/html" title="BeamNG mods section update" /><published>2025-04-29T10:00:00+00:00</published><updated>2025-04-29T10:00:00+00:00</updated><id>https://rabatinb.com/updates/2025/04/29/new-section-S1203</id><content type="html" xml:base="https://rabatinb.com/updates/2025/04/29/new-section-S1203.html"><![CDATA[<div style="flex: 1; text-align: justify;">
    <p>
        The section <a href="/beamng/">BeamNG</a> mods got a makeover with interactive tabs! So far I only added 2 cars (Skoda 1203 and Saab 92). More content coming soon!
    </p>
</div>]]></content><author><name>Branislav Rabatin</name></author><category term="updates" /><summary type="html"><![CDATA[The section BeamNG mods got a makeover with interactive tabs! So far I only added 2 cars (Skoda 1203 and Saab 92). More content coming soon!]]></summary></entry><entry><title type="html">Why Gaussian? Part I: tossing dice</title><link href="https://rabatinb.com/blog/why-gaussian-part-i/" rel="alternate" type="text/html" title="Why Gaussian? Part I: tossing dice" /><published>2025-04-28T16:00:00+00:00</published><updated>2025-04-28T16:00:00+00:00</updated><id>https://rabatinb.com/blog/CLT</id><content type="html" xml:base="https://rabatinb.com/blog/why-gaussian-part-i/"><![CDATA[<div style="flex: 1; text-align: justify;">
    <h3>See <a href="/blog/why-gaussian-part-ii/">Why Gaussian? Part II: continuous additive processes</a> for part 2!</h3>
    <h3>See <a href="/blog/why-gaussian-part-iii/">Why Gaussian? Part III: not all processes are additive</a> for part 3!</h3>
    <p>
        Ever wonder why the famous bell-shaped normal distribution (also known as a Gaussian) shows up so often in nature? There are all kinds of sources of randomness out there-so why do so many different processes somehow end up with the same general shape? The answer is a very universal <strong>Central Limit Theorem</strong> (CLT). Roughly speaking, the CLT says that under the right conditions, the average of a bunch of random things tends to follow a normal distribution.
    </p>
    <p>
        If that sounds a little abstract, don't worry. The easiest way to get an intuition for it is to start simple-with a roll of a die.
    </p>
    <h4>Rolling a single die</h4>
    <p>
        When you roll a single die, you get a number from the set $[1,2,3,4,5,6]$, each with an equal probability of $1/6$. There's nothing remotely bell-shaped about this—it's flat, symmetric, and completely uniform across six outcomes. No hint of a Gaussian curve here. But things will change as we add more dice.
    </p>
    <p>
        In this post, we'll build intuition for the Central Limit Theorem by starting with something concrete; rolling dice. We'll begin with single and multiple tosses, introduce a clever algebraic tool to count outcomes, derive a closed-form formula for the distribution, and finally show how the familiar bell curve naturally arises in the limit of many rolls.
    </p>
    <figure style="text-align: center;">
        <img src="/CLT_plots/dice1.png" alt="PMF - single die toss" style="width: 65%; display: block; margin: auto;" />
        <figcaption style="font-size: smaller; color: gray;">Probability mass function of a single die toss.</figcaption>
    </figure>
    <h4>Rolling two dies</h4>
    <p>
        Now, let's roll two dice instead. This time, we add the two numbers we roll together. (Rolling one die twice is basically the same idea - the important part is independence of individual throws). What numbers can we get? Since each die shows a value between 1 and 6, their sum can range from 2 to 12.
    </p>
    <p>
        But the important thing is: not all sums are equally likely. For example, there's only one way to get a 2 (rolling 1 and 1), but there are several ways to get a 7 (like 1+6, 2+5, 3+4, etc.).
    </p>
    <p>
        Why? Because we're summing two independent variables. Each possible combination (1–6 for each die) is equally likely, but their <em>sums</em> cluster around the middle values more often.
    </p>
    <p>
        Here's a breakdown:
    </p>
    <table>
        <thead> <tr> <th>Sum</th> <th>Ways to get it</th> <th>Probability</th> </tr> </thead>
        <tbody> <tr><td>2</td><td>(1,1)</td><td>1/36</td></tr> <tr><td>3</td><td>(1,2), (2,1)</td><td>2/36</td></tr> <tr><td>4</td><td>(1,3), (2,2), (3,1)</td><td>3/36</td></tr> <tr><td>5</td><td>(1,4), (2,3), (3,2), (4,1)</td><td>4/36</td></tr> <tr><td>6</td><td>(1,5), (2,4), (3,3), (4,2), (5,1)</td><td>5/36</td></tr> <tr><td>7</td><td>(1,6), (2,5), (3,4), (4,3), (5,2), (6,1)</td><td>6/36</td></tr> <tr><td>8</td><td>(2,6), (3,5), (4,4), (5,3), (6,2)</td><td>5/36</td></tr> <tr><td>9</td><td>(3,6), (4,5), (5,4), (6,3)</td><td>4/36</td></tr> <tr><td>10</td><td>(4,6), (5,5), (6,4)</td><td>3/36</td></tr> <tr><td>11</td><td>(5,6), (6,5)</td><td>2/36</td></tr> <tr><td>12</td><td>(6,6)</td><td>1/36</td></tr> </tbody>
    </table>
    <p> We can neatly visualize the <strong>probability mass function</strong> (PMF) for the sum of two dice like this:
    </p>
    <figure style="text-align: center;">
        <img src="/CLT_plots/dice2.png" alt="PMF - sum of two dice toss" style="width: 65%; display: block; margin: auto;" />
        <figcaption style="font-size: smaller; color: gray;">Probability mass function for the sum of two dice tosses.</figcaption>
    </figure>
    <p>
        Now, one might wonder: are we doomed to manually count the number of possibilities for each outcome whenever we want to bring in more dice? Well, yes and no. Ideally, we'd like a formula that gives us, for the number of dice $n$ and a possible outcome $k$, the number of possibilities $N(k; n)$, or the probability $p(k; n) = N(k; n) / (6^n)$. To do that, we use a cute little math trick.
    </p>
    <h4>Dice tosses: formula for probability</h4>
    <p>
    This trick uses polynomials to sort out the possible outcomes of dice tosses. First, notice that multiplying two terms, each in the form $x^{\text{some power}}$, simply causes the powers to add:
    </p>
    <p style="text-align: center;">
        $$
            x^a x^b = x^{a + b}
        $$
    </p>
    <p>
        Multiplying powers of $x$ adds exponents. So $x^3 \cdot x^5 = x^8$ symbolically corresponds to rolling a 3 and a 5 and summing the result.
    </p>
    <p style="text-align: center;">
        $$
            P_1(x) \equiv x^1 + x^2 + x^3 + x^4 + x^5 + x^6
        $$
    </p>
    <p>
        In a sense, this polynomial hides all possible outcomes of a single die toss in its terms. How about tossing two dice? We simply multiply two copies of this polynomial. Here's the neat part: when we multiply these polynomials, the coefficient of each $x^k$ in the expansion tells us how many ways we can get a sum of $k$ from $n$ dice.
    </p>
    <div class="math-desktop">
        $$
            \begin{aligned}
                P_2(x) &amp;= \left( P_1 (x) \right)^2 = \left( x^1 + x^2 + x^3 + x^4 + x^5 + x^6 \right) = \\
                &amp;= x^2 + 2 x^3 + 3 x^4 + 4 x^5 + 5 x^6 + 6 x^7 + 5 x^8 + 4 x^9 + 3 x^{10} + 2 x^{11} + x^{12}
            \end{aligned}
        $$
    </div>
    <div class="math-mobile">
        $$
            \begin{aligned}
                &amp;P_2(x) = \left( P_1 (x) \right)^2 = \\
                &amp;= \left( x^1 + x^2 + x^3 + x^4 + x^5 + x^6 \right) = \\
                &amp;= x^2 + 2 x^3 + 3 x^4 + 4 x^5 + 5 x^6 + \\
                &amp;+ 6 x^7 + 5 x^8 + 4 x^9 + 3 x^{10} + 2 x^{11} + x^{12}
            \end{aligned}
        $$
    </div>
    <p>
        The numbers 1, 2, 3, ... we see here are exactly the number of ways to achieve sums of 2, 3, 4, ... ! When we expand the polynomials and group together terms with the same power of $x$, the prefactor corresponds to the number of ways that power appears in the product. This gives us the general expression:
    </p>
    <div class="math-desktop">
        $$
            N (k; n) = \text{coefficient of $x^k$ in } P_n (x), \quad P_n (x) \equiv \left( P_1 (x) \right)^n
        $$
    </div>
    <div class="math-mobile">
        $$
            \begin{aligned}
                &amp;N (k; n) = \text{coefficient of $x^k$ in } P_n (x), \\
                &amp;\quad \text{where} \; P_n (x) \equiv \left( P_1 (x) \right)^n
            \end{aligned}
        $$
    </div>
    <p>
        In other words, the frequencies $N (k; n)$ appear as the coefficients of the expansion of the function $P_n (x)$,
    </p>
    <p style="text-align: center;">
        $$
            P_n (x) = \sum_{k = n}^{6n} N (k; n) x^k
        $$
    </p>
    <p>
        In this sense, $P_n (x)$ is the <strong>generating function</strong> of our frequency coefficients of $n$ dice toss sum. To move towards a more useful formula, we now look at the expression $P_1 (x)$ again. To simplify $P_1(x)$, we factor it to a more useful form. This lets us handle large powers more elegantly. We start by taking out a common factor of x
    </p>
    <p style="text-align: center;">
        $$
            P_1 (x) = x \left( 1 + x + x^2 + x^3 + x^4 + x^5 \right)
        $$
    </p>
    <p>
        Now we use a well known fact about polynomial series to simplify the expression $S \equiv 1 + x + \cdots + x^5$ (or other general power, since the logic is not dependent on the last coefficient being specifically $x^5$). We compare $S$ and $x S$:
    </p>
    <p style="text-align: center;">
        $$
            \begin{aligned}
                S &amp;= 1 + x + x^2 + x^3 + x^4 + x^5 \\
                x S &amp;= x + x^2 + x^3 + x^4 + x^5 + x^6
            \end{aligned}
        $$
    </p>
    <p>
        We see, that the two differ only by the first term in $S$ (1) and last term in $x S$ ($x^6$). Thus, their difference is
    </p>
    <div class="math-desktop">
        $$
            S - x S = (1 - x) S = 1 - x^6 \quad \implies \quad 1 + x + x^2 + x^3 + x^4 + x^5 = S = \frac{1 - x^6}{1 - x}
        $$
    </div>
    <div class="math-mobile">
        $$
            \begin{aligned}
                &amp;S - x S = (1 - x) S = 1 - x^6 \\
                &amp;\implies S = \frac{1 - x^6}{1 - x}
            \end{aligned}
        $$
    </div>
    <p>
        Going back to the generating function $P_n (x)$, we now see
    </p>
    <p style="text-align: center;">
        $$
            P_n (x) = \left( P_1 (x) \right)^n = x^n \left( \frac{1 - x^6}{1 - x} \right)^n
        $$
    </p>
    <p>
        The reason we brought the generating function to this form is that the resulting expansion is somewhat simpler than using the multinomial theorem on the sum $1 + x + \cdots + x^5$. We now need to expand all pieces of $P_n$ and put them together. We start with the denominator, which is given by a simple geometric series
    </p>
    <p style="text-align: center;">
        $$
            \frac{1}{1 - x} = \sum_{j = 0}^\infty x^j = 1 + x + x^2 + \cdots
        $$
    </p>
    <p>
        Luckily, the $n$-th power of this series can be handled by using derivatives instead of a complicated multinomial expansion. First, we notice a pattern
    </p>
    <p style="text-align: center;">
        $$
            \begin{aligned}
                \frac{\mathrm{d}}{\mathrm{d} x} \frac{1}{1 - x} &amp;= \frac{1}{(1 - x)^2} \\
                \frac{\mathrm{d}}{\mathrm{d} x} \frac{1}{(1 - x)^2} &amp;= \frac{2}{(1 - x)^3} \\
                \frac{\mathrm{d}}{\mathrm{d} x} \frac{1}{(1 - x)^3} &amp;= \frac{2 \cdot 3}{(1 - x)^4} \\
                \frac{\mathrm{d}}{\mathrm{d} x} \frac{1}{(1 - x)^4} &amp;= \frac{2 \cdot 3 \cdot 4}{(1 - x)^5}
            \end{aligned}
        $$
    </p>
    <p>
        From this pattern, we can generalize (proof left as a gentle exercise for the reader):
    </p>
    <div class="math-desktop">
        $$
            \frac{\mathrm{d}^{n - 1}}{\mathrm{d} x^{n - 1}} \frac{1}{1 - x} = \frac{(n - 1)!}{(1 - x)^n} \quad \implies \quad \frac{1}{(1 - x)^n} = \frac{1}{(n - 1)!} \frac{\mathrm{d}^{n - 1}}{\mathrm{d} x^{n - 1}} \frac{1}{1 - x}
        $$
    </div>
    <div class="math-mobile">
        $$
            \begin{aligned}
                &amp;\frac{\mathrm{d}^{n - 1}}{\mathrm{d} x^{n - 1}} \frac{1}{1 - x} = \frac{(n - 1)!}{(1 - x)^n} \\
                &amp;\implies \frac{1}{(1 - x)^n} = \frac{1}{(n - 1)!} \frac{\mathrm{d}^{n - 1}}{\mathrm{d} x^{n - 1}} \frac{1}{1 - x}
            \end{aligned}
        $$
    </div>
    <p>
        Let's unleash this on our geometric series. We know, that due to the $n-1$ derivatives with respect to $x$, the first non-vanishing term in the geometric series will be $x^{n - 1}$
    </p>
    <div class="math-desktop">
        $$
            \begin{aligned}
                \frac{1}{(1 - x)^n} &amp;= \frac{1}{(n - 1)!} \frac{\mathrm{d}^{n - 1}}{\mathrm{d} x^{n - 1}} \left( x^{n - 1} + x^n + x^{n + 1} + \cdots \right) \\
                &amp;= \frac{1}{(n - 1)!} \left( (n - 1)! + \frac{n!}{1!} x + \frac{(n+1)!}{2!} x^2 + \cdots \right) = \\
                &amp;= 1 + \frac{n!}{(n - 1)! \; 1!} x + \frac{(n + 1)!}{(n - 1)! \; 2!} x^2 + \cdots = \sum_{j = 0}^\infty \frac{(n + j - 1)!}{(n-1)! \; j!} x^j
            \end{aligned}
        $$
    </div>
    <div class="math-mobile">
        $$
            \begin{aligned}
                &amp;\frac{1}{(1 - x)^n} = \frac{1}{(n - 1)!} \frac{\mathrm{d}^{n - 1}}{\mathrm{d} x^{n - 1}} \sum_{k = n-1}^\infty x^k = \\
                = 1 &amp;+ \frac{n!}{(n - 1)! \; 1!} x + \frac{(n + 1)!}{(n - 1)! \; 2!} x^2 + \cdots = \\
                &amp;= \sum_{j = 0}^\infty \frac{(n + j - 1)!}{(n-1)! \; j!} x^j
            \end{aligned}
        $$
    </div>
    <p>
        Notice, that the term in the last expression is equivalent to the following binomial coefficient
    </p>
    <p style="text-align: center;">
        $$
            \frac{(n + j - 1)!}{(n-1)! \; j!} = \binom{n + j - 1}{n - 1}
        $$
    </p>
    <p>
        Now we express the numerator $(1 - x^6)^n$ in terms of a sum using binomial theorem
    </p>
    <p style="text-align: center;">
        $$
            (1 - x^6)^n = \sum_{\ell = 0}^n (-1)^\ell \binom{n}{\ell} x^{6 \ell}
        $$
    </p>
    <p>
        Finally, putting everything together,
    </p>
    <p style="text-align: center;">
        $$
            P_n (x) = x^n \sum_{j = 0}^\infty \binom{n + j - 1}{n - 1} x^j \sum_{\ell = 0}^n (-1)^\ell \binom{n}{\ell} x^{6 \ell}
        $$
    </p>
    <p>
        Now that we've derived the full expansion, we can finally extract the coefficient of $x^k$, giving us the number of ways to get the sum $k$ with $n$ dice. For a given $n \leq k \leq 6n$, the coefficient of $x^k$ in $P_n (x)$ is given by a condition $n + j + 6 \ell = k$, from which we can fix $j$ and thus get rid of one of the sums
    </p>
    <p style="text-align: center;">
        $$
            N (k;n) = \sum_{\ell = 0}^{\ell_{\text{max}}} (-1)^\ell \binom{k - 6 \ell - 1}{n - 1} \binom{n}{\ell}
        $$
    </p>
    <p>
        The upper limit of this sum can be fixed by demanding that $k - 6 \ell - 1 \geq n - 1$, and thus $\ell \leq (k - n) / 6$. If $(k - n) / 6$ is not an integer, then the upper limit is given by the largest integer not exceeding $(k - n) / 6$, i.e. the floor of $(k - n) / 6$, $\ell_{\text{max}} = \lfloor (k - n) / 6 \rfloor$.
    </p>
    <p>
        We can now use our new formula for $N(k;n)$ to find the probability distribution for $n = 3, 4, ...$
    </p>
    <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px;">
        <figure style="margin: 0;">
            <img src="/CLT_plots/dice3.png" alt="Plot 1" style="width: 100%;" />
        </figure>
        <figure style="margin: 0;">
            <img src="/CLT_plots/dice4.png" alt="Plot 2" style="width: 100%;" />
        </figure>
        <figure style="margin: 0;">
            <img src="/CLT_plots/dice5.png" alt="Plot 3" style="width: 100%;" />
        </figure>
        <figure style="margin: 0;">
            <img src="/CLT_plots/dice6.png" alt="Plot 3" style="width: 100%;" />
        </figure>
    </div>
    <h4>Many, many dice tosses: marching towards the Gaussian limit</h4>
    <p>
        While elegant, this formula isn't very helpful when $n$ becomes large. Summing over so many terms quickly becomes tedious. So how do we handle <strong>thousands</strong> of dice tosses? We need to zoom out—to stop caring about exact numbers and start looking at the big picture. That's where normalization and limiting behavior come in. First, we introduce a modified random variable, $y$, that is corrected for the mean and standard deviation in $N (k; n)$. The mean is simple; we know, that the mean value of the outcome of $n$ dice is $((1 + 6) / 2) n = (7/2) n$. As for the variance, we recall our generating function for $N (k; n)$ and how it relates to the probability distribution
    </p>
    <p style="text-align: center;">
        $$
            P_n (x) = \sum_{k = n}^{6n} N (k; n) x^k
        $$
    </p>
    <p>
        Using $p (k; n) = N (k; n) / 6^n$ to rewrite the above expression for probability instead of frequency, we find
    </p>
    <p style="text-align: center;">
        $$
            G_n (x) \equiv \frac{1}{6^n} P_n (x) = \sum_{k = n}^{6n} p (k; n) x^k = \left\langle x^k \right\rangle
        $$
    </p>
    <p>
        where we realized that the sum over probabilities corresponds to a certain, special, parametric, mean value (here $x$ is the parameter, while $k$ is a random variable we sum over). That's right, the function $G_n (x)$ corresponds to the <strong>probability-generating function</strong> of the dice toss distribution! Knowing this, we can calculate various expectation values (average quantities). For example, we already know that all probabilities should sum up to 1, but still,
    </p>
    <div class="math-desktop">
        $$
            \sum_{k = n}^{6n} p (k; n) = 1 = G_n (1) = \frac{\left( 1^1 + 1^2 + 1^3 + 1^4 + 1^5 + 1^6 \right)^n}{6^n} = \frac{6^n}{6^n} = 1
        $$
    </div>
    <div class="math-mobile">
        $$
            \begin{aligned}
                \sum_{k = n}^{6n} p (k; n) = 1 = G_n (1) &amp;= \\
                = \frac{\left( 1^1 + 1^2 + 1^3 + 1^4 + 1^5 + 1^6 \right)^n}{6^n} &amp;= \frac{6^n}{6^n} = 1
            \end{aligned}
        $$
    </div>
    <p>
        What about the mean value? We need to calculate the following sum:
    </p>
    <p style="text-align: center;">
        $$
            \left\langle k \right\rangle = \sum_{k = n}^{6n} p (k; n) \, k
        $$
    </p>
    <p>
        But our original probability-generating function contains terms $x^k$, not $k$. However, we can use the following neat derivative trick:
    </p>
    <p style="text-align: center;">
        $$
            \frac{\mathrm{d}}{\mathrm{d} x} x^k = k x^{k-1} \quad \to \quad k = \left. \frac{\mathrm{d}}{\mathrm{d} x} x^k \right|_{x = 1}
        $$
    </p>
    <p>
        We can now apply this to the probability-generating function. Since the sum is finite, we can just differentiate it term by term (note - the coefficients do not contain $x$, so the derivative passes through them like Taco Bell passes through my digestive track on Friday evening) and obtain
    </p>
    <div class="math-desktop">
        $$
            \left\langle k \right\rangle = \sum_{k = n}^{6n} p (k; n) \, \left. \frac{\mathrm{d}}{\mathrm{d} x} x^k \right|_{x = 1} = \left. \frac{\mathrm{d}}{\mathrm{d} x} \sum_{k = n}^{6n} p (k; n) \, x^k \right|_{x = 1}
        $$
    </div>
    <div class="math-mobile">
        $$
            \begin{aligned}
                \left\langle k \right\rangle &amp;= \sum_{k = n}^{6n} p (k; n) \, \left. \frac{\mathrm{d}}{\mathrm{d} x} x^k \right|_{x = 1} = \\
                &amp;= \left. \frac{\mathrm{d}}{\mathrm{d} x} \sum_{k = n}^{6n} p (k; n) \, x^k \right|_{x = 1}
            \end{aligned}
        $$
    </div>
    <p>
        At this point, we can replace the sum with the probability-generating function and get
    </p>
    <p style="text-align: center;">
        $$
            \left\langle k \right\rangle = \left. \frac{\mathrm{d}}{\mathrm{d} x} G_n (x) \right|_{x = 1} = G_n^\prime (1)
        $$
    </p>
    <p>
        Now, what is $G_n^\prime (1)$? Glad you asked, calculating mean values was never easier:
    </p>
    <div class="math-desktop">
        $$
            \begin{aligned}
                G_n^\prime (1) &amp;= \left. \frac{\mathrm{d}}{\mathrm{d} x} \left[ \left( x + x^2 + x^3 + x^4 + x^5 + x^6 \right)^n \right] \right|_{x = 1} = \\
                &amp;= n \left( x + x^2 + x^3 + x^4 + x^5 + x^6 \right)^{n - 1} \times \\
                &amp;\left. \times \left( 1 + 2 x + 3 x + 4 x + 5 x + 6 x \right) \right|_{x = 1} = \\
                &amp;= 21 \cdot 6^{n - 1} n
            \end{aligned}
        $$
    </div>
    <div class="math-mobile">
        $$
            \begin{aligned}
                G_n^\prime (1) &amp;= \\
                = \frac{\mathrm{d}}{\mathrm{d} x} &amp; \left. \left[ \left( x + x^2 + x^3 + x^4 + x^5 + x^6 \right)^n \right] \right|_{x = 1} = \\
                = n &amp;\left( x + x^2 + x^3 + x^4 + x^5 + x^6 \right)^{n - 1} \times \\
                \times &amp; \left. \left( 1 + 2 x + 3 x + 4 x + 5 x + 6 x \right) \right|_{x = 1} = \\
                = 21 \cdot &amp;6^{n - 1} n
            \end{aligned}
        $$
    </div>
    <p>
        Putting back the factor $1 / 6^n$, we get
    </p>
    <p style="text-align: center;">
        $$
            \mu = \left\langle k \right\rangle = \frac{21 \cdot 6^{n - 1}}{6^n} n = \frac{21}{6} n = \frac{7}{2} n
        $$
    </p>
    <p>
        as expected. Now that we know the method works, we can calculate another mean required for variance; $\langle k^2 \rangle$. To do that, we use derivatives again. First, we express $k^2$ in terms of $x^k$ as follows
    </p>
    <div class="math-desktop">
        $$
            \left. \begin{aligned}
                \frac{\mathrm{d}}{\mathrm{d} x} x^k &amp;= k x^{k-1} \\
                \frac{\mathrm{d}}{\mathrm{d} x} \left( x \frac{\mathrm{d}}{\mathrm{d} x} x^k \right) &amp;= k^2 x^{k-1}
            \end{aligned} \right\} \quad \implies \quad
            k^2 = \left. \frac{\mathrm{d}}{\mathrm{d} x} \left( x \frac{\mathrm{d}}{\mathrm{d} x} x^k \right) \right|_{x = 1}
        $$
    </div>
    <div class="math-mobile">
        $$
            \begin{aligned}
                \frac{\mathrm{d}}{\mathrm{d} x} x^k &amp;= k x^{k-1} \\
                \frac{\mathrm{d}}{\mathrm{d} x} \left( x \frac{\mathrm{d}}{\mathrm{d} x} x^k \right) &amp;= k^2 x^{k-1} \\
                \implies k^2 &amp;= \left. \frac{\mathrm{d}}{\mathrm{d} x} \left( x \frac{\mathrm{d}}{\mathrm{d} x} x^k \right) \right|_{x = 1}
            \end{aligned}
        $$
    </div>
    <p>
        This time, the calculation gives us variance (left as a homework exercise for your favorite algebra software)
    </p>
    <p style="text-align: center;">
        $$
            \sigma^2 = \left\langle k^2 \right\rangle - \left\langle k \right\rangle^2 = \frac{35}{12} n
        $$
    </p>
    <p>
        Now, let's define a normalized random variable
    </p>
    <p style="text-align: center;">
        $$
            y = \frac{k - \mu}{\sigma}
        $$
    </p>
    <h4>Normalized dice toss statistics</h4>
    <p>
        Our goal is to figure out the <strong>probability density function</strong> (PDF) of $y$ in the limit of large $n$. While the variable $k$ is discrete (only having integer values), in the limit of large $n$, variable $y$ becomes "more and more continuous", to the point we can speak of a probability distribution, instead of a probability mass function. In the next part of the series we will prove this result for any random process, but for now, we will use the probability-generating function we derived earlier
    </p>
    <div class="math-desktop">
        $$
            \left\langle x^k \right\rangle = \sum_{k = n}^{6n} p (k; n) x^k = G_n (x) = \left( \frac{x}{6} \frac{1 - x^6}{1 - x} \right)^n
        $$
    </div>
    <div class="math-mobile">
        $$
            \begin{aligned}
                \left\langle x^k \right\rangle &amp;= \sum_{k = n}^{6n} p (k; n) x^k = \\
                &amp;= G_n (x) = \left( \frac{x}{6} \frac{1 - x^6}{1 - x} \right)^n
            \end{aligned}
        $$
    </div>
    <p>
        We will now normalize the distribution to zero mean and unit variance. We already found that the original distribution $p (k; n)$ has mean $\mu = 7n / 2$ and variance $\sigma^2 = 35 n / 12$. Changing $k \to k^\prime = \sigma \, k + \mu$ and $p (k;n) \to \sigma \, p (k^\prime ; n)$ normalizes the distribution. For a linear transformation, it can be easily shown that the probability-generating function transforms as follows
    </p>
    <div class="math-desktop">
        $$
            G_n (x) \to \sigma \, x^{- \mu / \sigma} G_n (x^{1 / \sigma}) = x^{ - \mu / \sigma} \left( \frac{x^{1/\sigma}}{6} \frac{1 - x^{6/\sigma}}{1 - x^{1/\sigma}} \right)^n
        $$
    </div>
    <div class="math-mobile">
        $$
            \begin{aligned}
                G_n (x) &amp;\to \sigma \, x^{- \mu / \sigma} G_n (x^{1 / \sigma}) = \\
                &amp;= x^{ - \mu / \sigma} \left( \frac{x^{1/\sigma}}{6} \frac{1 - x^{6/\sigma}}{1 - x^{1/\sigma}} \right)^n
            \end{aligned}
        $$
    </div>
    <p>
        Now we want to show the limit $n \to \infty$, which becomes significantly easier if we show it for $\log G_n (x)$ rather than $G_n (x)$ itself. Logarithm of the transformed $G_n (x)$ reads
    </p>
    <div class="math-desktop">
        $$
            \log G_n (x) = - \frac{\mu}{\sigma} \log x + n \log \left( \frac{x^{1/\sigma}}{6} \frac{1 - x^{6/\sigma}}{1 - x^{1/\sigma}} \right)
        $$
    </div>
    <div class="math-mobile">
        $$
            \begin{aligned}
                \log G_n (x) &amp;= \\
                = - \frac{\mu}{\sigma} \log x &amp;+ n \log \left( \frac{x^{1/\sigma}}{6} \frac{1 - x^{6/\sigma}}{1 - x^{1/\sigma}} \right)
            \end{aligned}
        $$
    </div>
    <p>
        To obtain the limit when $n \to \infty$, we can replace $t = 1 / n$ and use Taylor series. A tricky calculation later (given as a homework to a bored reader) reveals
    </p>
    <div class="math-desktop">
        $$
            \log G_n (x) = \frac{\log^2 x}{2} - \frac{37}{700 n} \log^4 x + \frac{1333}{128,\!625 n^2} \log^6 x + \cdots
        $$
    </div>
    <div class="math-mobile">
        $$
            \begin{aligned}
                \log G_n (x) = \frac{\log^2 x}{2} &amp;- \\
                - \frac{37}{700 n} \log^4 x &amp;+ \frac{1333}{128,\!625 n^2} \log^6 x + \cdots
            \end{aligned}
        $$
    </div>
    <p>
        All right—so far we've expanded the probability-generating function and seen that it reduces to a single term as $n$ increases. But does its corresponding distribution really converge to a Gaussian? To show that it does, we need to compute $\log G_{\mathcal{N}} (x)$ for the perfect Gaussian and show it is equivalent to the first term in our expansion. This is a matter of a simple integral and completing the square
    </p>
    <p style="text-align: center;">
        $$
            \begin{aligned}
                G_\mathcal{N} (x) &amp;= \frac{1}{\sqrt{2 \pi}} \int \limits_{-\infty}^\infty \mathrm{d} u \, x^u \, \exp \left( - \frac{u^2}{2} \right) = \\
                &amp;= \frac{1}{\sqrt{2 \pi}} \int \limits_{-\infty}^\infty \mathrm{d} u \, \exp \left( - \frac{u^2}{2} + u \log x \right) = \\
                \frac{1}{\sqrt{2 \pi}} &amp;\int \limits_{-\infty}^\infty \mathrm{d} u \, \exp \left[ - \frac{1}{2} \left( u - \log x \right)^2 + \frac{\log^2 x}{2} \right] = \\
                &amp;= \exp \left( \frac{\log^2 x}{2} \right)
            \end{aligned}
        $$
    </p>
    <p>
        which is, after applying logarithm, exactly the first term of the expansion of $G_n (x)$ for large $n$.
    </p>
    <p>
        As we can see, the limit $n \to \infty$ truly converges towards a perfect Gaussian. We can check the histograms for actual dice tosses for larger $n$ and see that the process converges really quickly (here I also added the corresponding Gaussian curve for comparison):
    </p>
    <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px;">
        <figure style="margin: 0;">
            <img src="/CLT_plots/dice10.png" alt="Plot 1" style="width: 100%;" />
        </figure>
        <figure style="margin: 0;">
            <img src="/CLT_plots/dice20.png" alt="Plot 2" style="width: 100%;" />
        </figure>
        <figure style="margin: 0;">
            <img src="/CLT_plots/dice30.png" alt="Plot 3" style="width: 100%;" />
        </figure>
        <figure style="margin: 0;">
            <img src="/CLT_plots/dice40.png" alt="Plot 3" style="width: 100%;" />
        </figure>
    </div>
    <div style="height: 30px;"></div>
    <h4>Conclusion: why the bell curve appears</h4>
    <p>
        We've seen that even something as simple as rolling dice already contains the seeds of the Gaussian. As the number of dice increases, the distribution of their normalized sum smoothly morphs into a bell curve. This is the Central Limit Theorem at work: the average of many independent, finite-variance variables converges to the normal distribution.
    </p>
    <p>
        In the <a href="/blog/why-gaussian-part-ii/">next post</a>, we'll explore how this logic extends to <strong>continuous processes</strong>, and why the Gaussian remains such a dominant force in probability, statistics, and physics.
    </p>
    <p>
        Bonus: this whole analysis can be repeated for a $s$-sided coin. If you wonder whether the convergence to a Gaussian is faster or slower, it's neither; changing the number of sides barely changes anything about the speed of convergence. The coefficients next to $t^4$ and $t^6$ can be obtained through expansion:
    </p>
    <p style="text-align: center;">
        $$
            \begin{aligned}
                &amp;\frac{1}{8} - \frac{s^2 + 1}{s^2 - 1} \frac{1}{n} \\
                &amp;\frac{1}{48} + \frac{s^2 + 1}{40 (s^2 - 1)} \frac{1}{n} - \frac{s^4 + s^2 + 1}{105 (s^2 - 1)^2} \frac{1}{n^2}
            \end{aligned}
        $$
    </p>
    <p>
    and as you can see, increasing $s$ has very little effect on the size of the terms containing $n$. Thus, increasing the number of sides on a dices has virtually no effect on the speed of the convergence towards a Gaussian.
    </p>
</div>]]></content><author><name>Branislav Rabatin</name></author><category term="article" /><summary type="html"><![CDATA[See Why Gaussian? Part II: continuous additive processes for part 2! See Why Gaussian? Part III: not all processes are additive for part 3! Ever wonder why the famous bell-shaped normal distribution (also known as a Gaussian) shows up so often in nature? There are all kinds of sources of randomness out there-so why do so many different processes somehow end up with the same general shape? The answer is a very universal Central Limit Theorem (CLT). Roughly speaking, the CLT says that under the right conditions, the average of a bunch of random things tends to follow a normal distribution. If that sounds a little abstract, don't worry. The easiest way to get an intuition for it is to start simple-with a roll of a die. Rolling a single die When you roll a single die, you get a number from the set $[1,2,3,4,5,6]$, each with an equal probability of $1/6$. There's nothing remotely bell-shaped about this—it's flat, symmetric, and completely uniform across six outcomes. No hint of a Gaussian curve here. But things will change as we add more dice. In this post, we'll build intuition for the Central Limit Theorem by starting with something concrete; rolling dice. We'll begin with single and multiple tosses, introduce a clever algebraic tool to count outcomes, derive a closed-form formula for the distribution, and finally show how the familiar bell curve naturally arises in the limit of many rolls. Probability mass function of a single die toss. Rolling two dies Now, let's roll two dice instead. This time, we add the two numbers we roll together. (Rolling one die twice is basically the same idea - the important part is independence of individual throws). What numbers can we get? Since each die shows a value between 1 and 6, their sum can range from 2 to 12. But the important thing is: not all sums are equally likely. For example, there's only one way to get a 2 (rolling 1 and 1), but there are several ways to get a 7 (like 1+6, 2+5, 3+4, etc.). Why? Because we're summing two independent variables. Each possible combination (1–6 for each die) is equally likely, but their sums cluster around the middle values more often. Here's a breakdown: Sum Ways to get it Probability 2(1,1)1/36 3(1,2), (2,1)2/36 4(1,3), (2,2), (3,1)3/36 5(1,4), (2,3), (3,2), (4,1)4/36 6(1,5), (2,4), (3,3), (4,2), (5,1)5/36 7(1,6), (2,5), (3,4), (4,3), (5,2), (6,1)6/36 8(2,6), (3,5), (4,4), (5,3), (6,2)5/36 9(3,6), (4,5), (5,4), (6,3)4/36 10(4,6), (5,5), (6,4)3/36 11(5,6), (6,5)2/36 12(6,6)1/36 We can neatly visualize the probability mass function (PMF) for the sum of two dice like this: Probability mass function for the sum of two dice tosses. Now, one might wonder: are we doomed to manually count the number of possibilities for each outcome whenever we want to bring in more dice? Well, yes and no. Ideally, we'd like a formula that gives us, for the number of dice $n$ and a possible outcome $k$, the number of possibilities $N(k; n)$, or the probability $p(k; n) = N(k; n) / (6^n)$. To do that, we use a cute little math trick. Dice tosses: formula for probability This trick uses polynomials to sort out the possible outcomes of dice tosses. First, notice that multiplying two terms, each in the form $x^{\text{some power}}$, simply causes the powers to add: $$ x^a x^b = x^{a + b} $$ Multiplying powers of $x$ adds exponents. So $x^3 \cdot x^5 = x^8$ symbolically corresponds to rolling a 3 and a 5 and summing the result. $$ P_1(x) \equiv x^1 + x^2 + x^3 + x^4 + x^5 + x^6 $$ In a sense, this polynomial hides all possible outcomes of a single die toss in its terms. How about tossing two dice? We simply multiply two copies of this polynomial. Here's the neat part: when we multiply these polynomials, the coefficient of each $x^k$ in the expansion tells us how many ways we can get a sum of $k$ from $n$ dice. $$ \begin{aligned} P_2(x) &amp;= \left( P_1 (x) \right)^2 = \left( x^1 + x^2 + x^3 + x^4 + x^5 + x^6 \right) = \\ &amp;= x^2 + 2 x^3 + 3 x^4 + 4 x^5 + 5 x^6 + 6 x^7 + 5 x^8 + 4 x^9 + 3 x^{10} + 2 x^{11} + x^{12} \end{aligned} $$ $$ \begin{aligned} &amp;P_2(x) = \left( P_1 (x) \right)^2 = \\ &amp;= \left( x^1 + x^2 + x^3 + x^4 + x^5 + x^6 \right) = \\ &amp;= x^2 + 2 x^3 + 3 x^4 + 4 x^5 + 5 x^6 + \\ &amp;+ 6 x^7 + 5 x^8 + 4 x^9 + 3 x^{10} + 2 x^{11} + x^{12} \end{aligned} $$ The numbers 1, 2, 3, ... we see here are exactly the number of ways to achieve sums of 2, 3, 4, ... ! When we expand the polynomials and group together terms with the same power of $x$, the prefactor corresponds to the number of ways that power appears in the product. This gives us the general expression: $$ N (k; n) = \text{coefficient of $x^k$ in } P_n (x), \quad P_n (x) \equiv \left( P_1 (x) \right)^n $$ $$ \begin{aligned} &amp;N (k; n) = \text{coefficient of $x^k$ in } P_n (x), \\ &amp;\quad \text{where} \; P_n (x) \equiv \left( P_1 (x) \right)^n \end{aligned} $$ In other words, the frequencies $N (k; n)$ appear as the coefficients of the expansion of the function $P_n (x)$, $$ P_n (x) = \sum_{k = n}^{6n} N (k; n) x^k $$ In this sense, $P_n (x)$ is the generating function of our frequency coefficients of $n$ dice toss sum. To move towards a more useful formula, we now look at the expression $P_1 (x)$ again. To simplify $P_1(x)$, we factor it to a more useful form. This lets us handle large powers more elegantly. We start by taking out a common factor of x $$ P_1 (x) = x \left( 1 + x + x^2 + x^3 + x^4 + x^5 \right) $$ Now we use a well known fact about polynomial series to simplify the expression $S \equiv 1 + x + \cdots + x^5$ (or other general power, since the logic is not dependent on the last coefficient being specifically $x^5$). We compare $S$ and $x S$: $$ \begin{aligned} S &amp;= 1 + x + x^2 + x^3 + x^4 + x^5 \\ x S &amp;= x + x^2 + x^3 + x^4 + x^5 + x^6 \end{aligned} $$ We see, that the two differ only by the first term in $S$ (1) and last term in $x S$ ($x^6$). Thus, their difference is $$ S - x S = (1 - x) S = 1 - x^6 \quad \implies \quad 1 + x + x^2 + x^3 + x^4 + x^5 = S = \frac{1 - x^6}{1 - x} $$ $$ \begin{aligned} &amp;S - x S = (1 - x) S = 1 - x^6 \\ &amp;\implies S = \frac{1 - x^6}{1 - x} \end{aligned} $$ Going back to the generating function $P_n (x)$, we now see $$ P_n (x) = \left( P_1 (x) \right)^n = x^n \left( \frac{1 - x^6}{1 - x} \right)^n $$ The reason we brought the generating function to this form is that the resulting expansion is somewhat simpler than using the multinomial theorem on the sum $1 + x + \cdots + x^5$. We now need to expand all pieces of $P_n$ and put them together. We start with the denominator, which is given by a simple geometric series $$ \frac{1}{1 - x} = \sum_{j = 0}^\infty x^j = 1 + x + x^2 + \cdots $$ Luckily, the $n$-th power of this series can be handled by using derivatives instead of a complicated multinomial expansion. First, we notice a pattern $$ \begin{aligned} \frac{\mathrm{d}}{\mathrm{d} x} \frac{1}{1 - x} &amp;= \frac{1}{(1 - x)^2} \\ \frac{\mathrm{d}}{\mathrm{d} x} \frac{1}{(1 - x)^2} &amp;= \frac{2}{(1 - x)^3} \\ \frac{\mathrm{d}}{\mathrm{d} x} \frac{1}{(1 - x)^3} &amp;= \frac{2 \cdot 3}{(1 - x)^4} \\ \frac{\mathrm{d}}{\mathrm{d} x} \frac{1}{(1 - x)^4} &amp;= \frac{2 \cdot 3 \cdot 4}{(1 - x)^5} \end{aligned} $$ From this pattern, we can generalize (proof left as a gentle exercise for the reader): $$ \frac{\mathrm{d}^{n - 1}}{\mathrm{d} x^{n - 1}} \frac{1}{1 - x} = \frac{(n - 1)!}{(1 - x)^n} \quad \implies \quad \frac{1}{(1 - x)^n} = \frac{1}{(n - 1)!} \frac{\mathrm{d}^{n - 1}}{\mathrm{d} x^{n - 1}} \frac{1}{1 - x} $$ $$ \begin{aligned} &amp;\frac{\mathrm{d}^{n - 1}}{\mathrm{d} x^{n - 1}} \frac{1}{1 - x} = \frac{(n - 1)!}{(1 - x)^n} \\ &amp;\implies \frac{1}{(1 - x)^n} = \frac{1}{(n - 1)!} \frac{\mathrm{d}^{n - 1}}{\mathrm{d} x^{n - 1}} \frac{1}{1 - x} \end{aligned} $$ Let's unleash this on our geometric series. We know, that due to the $n-1$ derivatives with respect to $x$, the first non-vanishing term in the geometric series will be $x^{n - 1}$ $$ \begin{aligned} \frac{1}{(1 - x)^n} &amp;= \frac{1}{(n - 1)!} \frac{\mathrm{d}^{n - 1}}{\mathrm{d} x^{n - 1}} \left( x^{n - 1} + x^n + x^{n + 1} + \cdots \right) \\ &amp;= \frac{1}{(n - 1)!} \left( (n - 1)! + \frac{n!}{1!} x + \frac{(n+1)!}{2!} x^2 + \cdots \right) = \\ &amp;= 1 + \frac{n!}{(n - 1)! \; 1!} x + \frac{(n + 1)!}{(n - 1)! \; 2!} x^2 + \cdots = \sum_{j = 0}^\infty \frac{(n + j - 1)!}{(n-1)! \; j!} x^j \end{aligned} $$ $$ \begin{aligned} &amp;\frac{1}{(1 - x)^n} = \frac{1}{(n - 1)!} \frac{\mathrm{d}^{n - 1}}{\mathrm{d} x^{n - 1}} \sum_{k = n-1}^\infty x^k = \\ = 1 &amp;+ \frac{n!}{(n - 1)! \; 1!} x + \frac{(n + 1)!}{(n - 1)! \; 2!} x^2 + \cdots = \\ &amp;= \sum_{j = 0}^\infty \frac{(n + j - 1)!}{(n-1)! \; j!} x^j \end{aligned} $$ Notice, that the term in the last expression is equivalent to the following binomial coefficient $$ \frac{(n + j - 1)!}{(n-1)! \; j!} = \binom{n + j - 1}{n - 1} $$ Now we express the numerator $(1 - x^6)^n$ in terms of a sum using binomial theorem $$ (1 - x^6)^n = \sum_{\ell = 0}^n (-1)^\ell \binom{n}{\ell} x^{6 \ell} $$ Finally, putting everything together, $$ P_n (x) = x^n \sum_{j = 0}^\infty \binom{n + j - 1}{n - 1} x^j \sum_{\ell = 0}^n (-1)^\ell \binom{n}{\ell} x^{6 \ell} $$ Now that we've derived the full expansion, we can finally extract the coefficient of $x^k$, giving us the number of ways to get the sum $k$ with $n$ dice. For a given $n \leq k \leq 6n$, the coefficient of $x^k$ in $P_n (x)$ is given by a condition $n + j + 6 \ell = k$, from which we can fix $j$ and thus get rid of one of the sums $$ N (k;n) = \sum_{\ell = 0}^{\ell_{\text{max}}} (-1)^\ell \binom{k - 6 \ell - 1}{n - 1} \binom{n}{\ell} $$ The upper limit of this sum can be fixed by demanding that $k - 6 \ell - 1 \geq n - 1$, and thus $\ell \leq (k - n) / 6$. If $(k - n) / 6$ is not an integer, then the upper limit is given by the largest integer not exceeding $(k - n) / 6$, i.e. the floor of $(k - n) / 6$, $\ell_{\text{max}} = \lfloor (k - n) / 6 \rfloor$. We can now use our new formula for $N(k;n)$ to find the probability distribution for $n = 3, 4, ...$ Many, many dice tosses: marching towards the Gaussian limit While elegant, this formula isn't very helpful when $n$ becomes large. Summing over so many terms quickly becomes tedious. So how do we handle thousands of dice tosses? We need to zoom out—to stop caring about exact numbers and start looking at the big picture. That's where normalization and limiting behavior come in. First, we introduce a modified random variable, $y$, that is corrected for the mean and standard deviation in $N (k; n)$. The mean is simple; we know, that the mean value of the outcome of $n$ dice is $((1 + 6) / 2) n = (7/2) n$. As for the variance, we recall our generating function for $N (k; n)$ and how it relates to the probability distribution $$ P_n (x) = \sum_{k = n}^{6n} N (k; n) x^k $$ Using $p (k; n) = N (k; n) / 6^n$ to rewrite the above expression for probability instead of frequency, we find $$ G_n (x) \equiv \frac{1}{6^n} P_n (x) = \sum_{k = n}^{6n} p (k; n) x^k = \left\langle x^k \right\rangle $$ where we realized that the sum over probabilities corresponds to a certain, special, parametric, mean value (here $x$ is the parameter, while $k$ is a random variable we sum over). That's right, the function $G_n (x)$ corresponds to the probability-generating function of the dice toss distribution! Knowing this, we can calculate various expectation values (average quantities). For example, we already know that all probabilities should sum up to 1, but still, $$ \sum_{k = n}^{6n} p (k; n) = 1 = G_n (1) = \frac{\left( 1^1 + 1^2 + 1^3 + 1^4 + 1^5 + 1^6 \right)^n}{6^n} = \frac{6^n}{6^n} = 1 $$ $$ \begin{aligned} \sum_{k = n}^{6n} p (k; n) = 1 = G_n (1) &amp;= \\ = \frac{\left( 1^1 + 1^2 + 1^3 + 1^4 + 1^5 + 1^6 \right)^n}{6^n} &amp;= \frac{6^n}{6^n} = 1 \end{aligned} $$ What about the mean value? We need to calculate the following sum: $$ \left\langle k \right\rangle = \sum_{k = n}^{6n} p (k; n) \, k $$ But our original probability-generating function contains terms $x^k$, not $k$. However, we can use the following neat derivative trick: $$ \frac{\mathrm{d}}{\mathrm{d} x} x^k = k x^{k-1} \quad \to \quad k = \left. \frac{\mathrm{d}}{\mathrm{d} x} x^k \right|_{x = 1} $$ We can now apply this to the probability-generating function. Since the sum is finite, we can just differentiate it term by term (note - the coefficients do not contain $x$, so the derivative passes through them like Taco Bell passes through my digestive track on Friday evening) and obtain $$ \left\langle k \right\rangle = \sum_{k = n}^{6n} p (k; n) \, \left. \frac{\mathrm{d}}{\mathrm{d} x} x^k \right|_{x = 1} = \left. \frac{\mathrm{d}}{\mathrm{d} x} \sum_{k = n}^{6n} p (k; n) \, x^k \right|_{x = 1} $$ $$ \begin{aligned} \left\langle k \right\rangle &amp;= \sum_{k = n}^{6n} p (k; n) \, \left. \frac{\mathrm{d}}{\mathrm{d} x} x^k \right|_{x = 1} = \\ &amp;= \left. \frac{\mathrm{d}}{\mathrm{d} x} \sum_{k = n}^{6n} p (k; n) \, x^k \right|_{x = 1} \end{aligned} $$ At this point, we can replace the sum with the probability-generating function and get $$ \left\langle k \right\rangle = \left. \frac{\mathrm{d}}{\mathrm{d} x} G_n (x) \right|_{x = 1} = G_n^\prime (1) $$ Now, what is $G_n^\prime (1)$? Glad you asked, calculating mean values was never easier: $$ \begin{aligned} G_n^\prime (1) &amp;= \left. \frac{\mathrm{d}}{\mathrm{d} x} \left[ \left( x + x^2 + x^3 + x^4 + x^5 + x^6 \right)^n \right] \right|_{x = 1} = \\ &amp;= n \left( x + x^2 + x^3 + x^4 + x^5 + x^6 \right)^{n - 1} \times \\ &amp;\left. \times \left( 1 + 2 x + 3 x + 4 x + 5 x + 6 x \right) \right|_{x = 1} = \\ &amp;= 21 \cdot 6^{n - 1} n \end{aligned} $$ $$ \begin{aligned} G_n^\prime (1) &amp;= \\ = \frac{\mathrm{d}}{\mathrm{d} x} &amp; \left. \left[ \left( x + x^2 + x^3 + x^4 + x^5 + x^6 \right)^n \right] \right|_{x = 1} = \\ = n &amp;\left( x + x^2 + x^3 + x^4 + x^5 + x^6 \right)^{n - 1} \times \\ \times &amp; \left. \left( 1 + 2 x + 3 x + 4 x + 5 x + 6 x \right) \right|_{x = 1} = \\ = 21 \cdot &amp;6^{n - 1} n \end{aligned} $$ Putting back the factor $1 / 6^n$, we get $$ \mu = \left\langle k \right\rangle = \frac{21 \cdot 6^{n - 1}}{6^n} n = \frac{21}{6} n = \frac{7}{2} n $$ as expected. Now that we know the method works, we can calculate another mean required for variance; $\langle k^2 \rangle$. To do that, we use derivatives again. First, we express $k^2$ in terms of $x^k$ as follows $$ \left. \begin{aligned} \frac{\mathrm{d}}{\mathrm{d} x} x^k &amp;= k x^{k-1} \\ \frac{\mathrm{d}}{\mathrm{d} x} \left( x \frac{\mathrm{d}}{\mathrm{d} x} x^k \right) &amp;= k^2 x^{k-1} \end{aligned} \right\} \quad \implies \quad k^2 = \left. \frac{\mathrm{d}}{\mathrm{d} x} \left( x \frac{\mathrm{d}}{\mathrm{d} x} x^k \right) \right|_{x = 1} $$ $$ \begin{aligned} \frac{\mathrm{d}}{\mathrm{d} x} x^k &amp;= k x^{k-1} \\ \frac{\mathrm{d}}{\mathrm{d} x} \left( x \frac{\mathrm{d}}{\mathrm{d} x} x^k \right) &amp;= k^2 x^{k-1} \\ \implies k^2 &amp;= \left. \frac{\mathrm{d}}{\mathrm{d} x} \left( x \frac{\mathrm{d}}{\mathrm{d} x} x^k \right) \right|_{x = 1} \end{aligned} $$ This time, the calculation gives us variance (left as a homework exercise for your favorite algebra software) $$ \sigma^2 = \left\langle k^2 \right\rangle - \left\langle k \right\rangle^2 = \frac{35}{12} n $$ Now, let's define a normalized random variable $$ y = \frac{k - \mu}{\sigma} $$ Normalized dice toss statistics Our goal is to figure out the probability density function (PDF) of $y$ in the limit of large $n$. While the variable $k$ is discrete (only having integer values), in the limit of large $n$, variable $y$ becomes "more and more continuous", to the point we can speak of a probability distribution, instead of a probability mass function. In the next part of the series we will prove this result for any random process, but for now, we will use the probability-generating function we derived earlier $$ \left\langle x^k \right\rangle = \sum_{k = n}^{6n} p (k; n) x^k = G_n (x) = \left( \frac{x}{6} \frac{1 - x^6}{1 - x} \right)^n $$ $$ \begin{aligned} \left\langle x^k \right\rangle &amp;= \sum_{k = n}^{6n} p (k; n) x^k = \\ &amp;= G_n (x) = \left( \frac{x}{6} \frac{1 - x^6}{1 - x} \right)^n \end{aligned} $$ We will now normalize the distribution to zero mean and unit variance. We already found that the original distribution $p (k; n)$ has mean $\mu = 7n / 2$ and variance $\sigma^2 = 35 n / 12$. Changing $k \to k^\prime = \sigma \, k + \mu$ and $p (k;n) \to \sigma \, p (k^\prime ; n)$ normalizes the distribution. For a linear transformation, it can be easily shown that the probability-generating function transforms as follows $$ G_n (x) \to \sigma \, x^{- \mu / \sigma} G_n (x^{1 / \sigma}) = x^{ - \mu / \sigma} \left( \frac{x^{1/\sigma}}{6} \frac{1 - x^{6/\sigma}}{1 - x^{1/\sigma}} \right)^n $$ $$ \begin{aligned} G_n (x) &amp;\to \sigma \, x^{- \mu / \sigma} G_n (x^{1 / \sigma}) = \\ &amp;= x^{ - \mu / \sigma} \left( \frac{x^{1/\sigma}}{6} \frac{1 - x^{6/\sigma}}{1 - x^{1/\sigma}} \right)^n \end{aligned} $$ Now we want to show the limit $n \to \infty$, which becomes significantly easier if we show it for $\log G_n (x)$ rather than $G_n (x)$ itself. Logarithm of the transformed $G_n (x)$ reads $$ \log G_n (x) = - \frac{\mu}{\sigma} \log x + n \log \left( \frac{x^{1/\sigma}}{6} \frac{1 - x^{6/\sigma}}{1 - x^{1/\sigma}} \right) $$ $$ \begin{aligned} \log G_n (x) &amp;= \\ = - \frac{\mu}{\sigma} \log x &amp;+ n \log \left( \frac{x^{1/\sigma}}{6} \frac{1 - x^{6/\sigma}}{1 - x^{1/\sigma}} \right) \end{aligned} $$ To obtain the limit when $n \to \infty$, we can replace $t = 1 / n$ and use Taylor series. A tricky calculation later (given as a homework to a bored reader) reveals $$ \log G_n (x) = \frac{\log^2 x}{2} - \frac{37}{700 n} \log^4 x + \frac{1333}{128,\!625 n^2} \log^6 x + \cdots $$ $$ \begin{aligned} \log G_n (x) = \frac{\log^2 x}{2} &amp;- \\ - \frac{37}{700 n} \log^4 x &amp;+ \frac{1333}{128,\!625 n^2} \log^6 x + \cdots \end{aligned} $$ All right—so far we've expanded the probability-generating function and seen that it reduces to a single term as $n$ increases. But does its corresponding distribution really converge to a Gaussian? To show that it does, we need to compute $\log G_{\mathcal{N}} (x)$ for the perfect Gaussian and show it is equivalent to the first term in our expansion. This is a matter of a simple integral and completing the square $$ \begin{aligned} G_\mathcal{N} (x) &amp;= \frac{1}{\sqrt{2 \pi}} \int \limits_{-\infty}^\infty \mathrm{d} u \, x^u \, \exp \left( - \frac{u^2}{2} \right) = \\ &amp;= \frac{1}{\sqrt{2 \pi}} \int \limits_{-\infty}^\infty \mathrm{d} u \, \exp \left( - \frac{u^2}{2} + u \log x \right) = \\ \frac{1}{\sqrt{2 \pi}} &amp;\int \limits_{-\infty}^\infty \mathrm{d} u \, \exp \left[ - \frac{1}{2} \left( u - \log x \right)^2 + \frac{\log^2 x}{2} \right] = \\ &amp;= \exp \left( \frac{\log^2 x}{2} \right) \end{aligned} $$ which is, after applying logarithm, exactly the first term of the expansion of $G_n (x)$ for large $n$. As we can see, the limit $n \to \infty$ truly converges towards a perfect Gaussian. We can check the histograms for actual dice tosses for larger $n$ and see that the process converges really quickly (here I also added the corresponding Gaussian curve for comparison): Conclusion: why the bell curve appears We've seen that even something as simple as rolling dice already contains the seeds of the Gaussian. As the number of dice increases, the distribution of their normalized sum smoothly morphs into a bell curve. This is the Central Limit Theorem at work: the average of many independent, finite-variance variables converges to the normal distribution. In the next post, we'll explore how this logic extends to continuous processes, and why the Gaussian remains such a dominant force in probability, statistics, and physics. Bonus: this whole analysis can be repeated for a $s$-sided coin. If you wonder whether the convergence to a Gaussian is faster or slower, it's neither; changing the number of sides barely changes anything about the speed of convergence. The coefficients next to $t^4$ and $t^6$ can be obtained through expansion: $$ \begin{aligned} &amp;\frac{1}{8} - \frac{s^2 + 1}{s^2 - 1} \frac{1}{n} \\ &amp;\frac{1}{48} + \frac{s^2 + 1}{40 (s^2 - 1)} \frac{1}{n} - \frac{s^4 + s^2 + 1}{105 (s^2 - 1)^2} \frac{1}{n^2} \end{aligned} $$ and as you can see, increasing $s$ has very little effect on the size of the terms containing $n$. Thus, increasing the number of sides on a dices has virtually no effect on the speed of the convergence towards a Gaussian.]]></summary></entry><entry><title type="html">Launching My Personal Website!</title><link href="https://rabatinb.com/updates/2025/04/26/launching-webside.html" rel="alternate" type="text/html" title="Launching My Personal Website!" /><published>2025-04-26T20:00:00+00:00</published><updated>2025-04-26T20:00:00+00:00</updated><id>https://rabatinb.com/updates/2025/04/26/launching-webside</id><content type="html" xml:base="https://rabatinb.com/updates/2025/04/26/launching-webside.html"><![CDATA[<p>Welcome! I’m kicking off this site to share my research in astrophysical turbulence, computer simulations, and my adventures into BeamNG modding.</p>

<p>I’ll be sharing a turbulent mix; coding, 3D modeling, and occasional off-topic posts when inspiration strikes.</p>

<p>Stay tuned for more!</p>]]></content><author><name>Branislav Rabatin</name></author><category term="updates" /><summary type="html"><![CDATA[Welcome! I’m kicking off this site to share my research in astrophysical turbulence, computer simulations, and my adventures into BeamNG modding. I’ll be sharing a turbulent mix; coding, 3D modeling, and occasional off-topic posts when inspiration strikes. Stay tuned for more!]]></summary></entry></feed>