codex/characteristic-function-weighted-phasors.py

characteristic-function-weighted-phasors.py
"""Render a Bernoulli characteristic function as weighted complex arrows."""
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np


def main():
    plt.rcParams.update({"font.size": 12, "axes.spines.top": False,
                         "axes.spines.right": False})
    fig, axes = plt.subplots(1, 2, figsize=(9.6, 4.8), layout="constrained")
    fig.set_facecolor("white")
    angle = 2 * np.pi / 3
    z = np.exp(1j * angle)
    mean = 2 / 3 + z / 3
    circle = np.exp(1j * np.linspace(0, 2 * np.pi, 600))
    blue, orange, green = "#2864a5", "#c05b16", "#08765b"
    for ax in axes:
        ax.set_facecolor("white")
        ax.plot(circle.real, circle.imag, "--", color="0.7", lw=1)
        ax.axhline(0, color="0.75", lw=0.8)
        ax.axvline(0, color="0.75", lw=0.8)
        ax.set(xlim=(-1.15, 1.25), ylim=(-1.15, 1.2),
               xlabel="Real part", ylabel="Imaginary part", aspect="equal")
        ax.set_xticks([-1, 0, 1])
        ax.set_yticks([-1, 0, 1])

    def arrow(ax, start, end, color, style="-"):
        ax.annotate("", (end.real, end.imag), (start.real, start.imag),
                    arrowprops={"arrowstyle": "->", "color": color,
                                "lw": 2.4, "linestyle": style})

    ax = axes[0]
    ax.set_title("Unit arrows, then average", pad=14)
    arrow(ax, 0j, 1 + 0j, blue)
    arrow(ax, 0j, z, orange)
    ax.plot([1, z.real], [0, z.imag], color="0.45", lw=1.4)
    ax.scatter([1, z.real], [0, z.imag], color=[blue, orange], s=40)
    ax.scatter(mean.real, mean.imag, color=green, s=65, zorder=5)
    ax.annotate(r"$1$ (weight $2/3$)", (1, 0), xytext=(0.38, -0.27), color=blue)
    ax.annotate(r"$e^{2\pi i/3}$ (weight $1/3$)", (z.real, z.imag),
                xytext=(-1.1, 1.05), color=orange)
    ax.annotate(r"$\varphi_B(2\pi/3)$", (mean.real, mean.imag),
                xytext=(0.45, 0.65), color=green,
                arrowprops={"arrowstyle": "-", "color": green})
    ax = axes[1]
    ax.set_title("Weighted arrows add head to tail", pad=14)
    arrow(ax, 0j, 2 / 3 + 0j, blue)
    arrow(ax, 2 / 3 + 0j, mean, orange)
    arrow(ax, 0j, mean, green)
    ax.scatter(mean.real, mean.imag, color=green, s=45, zorder=5)
    ax.text(0.28, -0.2, r"$2/3$", color=blue)
    ax.text(0.76, 0.2, r"$e^{2\pi i/3}/3$", color=orange, fontsize=11)
    ax.text(-0.98, 0.69, r"$\varphi_B=\frac{1}{2}+\frac{\sqrt{3}}{6}i$", color=green, fontsize=16)
    ax.text(-0.98, -0.89, r"$|\varphi_B|=1/\sqrt{3}<1$", fontsize=14)
    fig.suptitle(r"$P(B=0)=2/3,\quad P(B=1)=1/3,\quad t=2\pi/3$", fontsize=15)
    fig.savefig(Path(__file__).stem + ".png", dpi=100, facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()