codex/kalman-measurement-uncertainty.py

kalman-measurement-uncertainty.py
"""Plot an exact scalar Gaussian update for two measurement variances.

Run from the desired output directory; writes a white, opaque PNG there.
Tested with the Python/numpy/matplotlib versions in pyproject.toml.
"""

from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np


def density(x, mean, variance):
    return np.exp(-0.5 * (x - mean) ** 2 / variance) / np.sqrt(
        2 * np.pi * variance
    )


def main():
    plt.rcParams.update({"font.size": 11, "axes.titlesize": 13})
    x = np.linspace(0, 23, 2401)
    fig, axes = plt.subplots(1, 2, figsize=(11.8, 5.0), dpi=100, sharey=True)
    prior_mean, prior_variance, observation = 10.0, 4.0, 14.0
    for ax, measurement_variance in zip(axes, (1.0, 16.0), strict=True):
        gain = prior_variance / (prior_variance + measurement_variance)
        posterior_mean = prior_mean + gain * (observation - prior_mean)
        posterior_variance = (1 - gain) * prior_variance
        ax.plot(x, density(x, prior_mean, prior_variance), color="#2867a0",
                linewidth=2.3, label="Prediction: N(10, 4)")
        ax.plot(x, density(x, observation, measurement_variance),
                color="#27835f", linewidth=2.0, linestyle="--",
                label="Normalized likelihood")
        ax.plot(x, density(x, posterior_mean, posterior_variance),
                color="#cc6b20", linewidth=2.7,
                label=f"Posterior: N({posterior_mean:g}, {posterior_variance:g})")
        ax.axvline(observation, color="#666666", linestyle=":", linewidth=1.4)
        ax.set_title(f"Measurement variance R = {measurement_variance:g}"
                     f"\nGain K = {gain:g}")
        ax.set_xlim(0, 23)
        ax.set_ylim(0, 0.49)
        ax.set_xlabel("Scalar state x (illustrative units)")
        ax.grid(alpha=0.16)
        ax.legend(loc="upper left", fontsize=9.5, framealpha=0.95)
        ax.annotate(f"Posterior mean {posterior_mean:g}",
                    xy=(posterior_mean, 0), xytext=(posterior_mean, 0.055),
                    ha="center", color="#995017",
                    arrowprops={"arrowstyle": "->", "color": "#995017"})
    axes[0].set_ylabel("Density per state unit")
    fig.suptitle("The value 14 matters more when its measurement is precise",
                 fontsize=15, y=0.98)
    fig.text(0.5, 0.04,
             "The observation is not a known true state. "
             "Variances are in squared state units.",
             ha="center", fontsize=10)
    fig.subplots_adjust(left=0.075, right=0.98, top=0.80, bottom=0.19, wspace=0.13)
    output = Path.cwd() / (Path(__file__).stem + ".png")
    fig.savefig(output, facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()