codex/von-mangoldt-divisor-contributions.py

von-mangoldt-divisor-contributions.py
"""Plot the von Mangoldt divisor weights of 12; Python 3.14, Matplotlib 3.10.7.

Run from _media; the opaque white PNG is written to the current directory.
"""
from math import isclose, log
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np


def prime_power_base(n):
    """Return the prime base of a positive-exponent prime power, or None."""
    if n < 2:
        return None
    base = 2
    while n % base:
        base += 1
    while n % base == 0:
        n //= base
    return base if n == 1 else None


def divisor_data(n):
    divisors = [d for d in range(1, n + 1) if n % d == 0]
    bases = [prime_power_base(d) for d in divisors]
    weights = np.array([log(p) if p else 0.0 for p in bases])
    return divisors, bases, weights


def main():
    divisors, bases, weights = divisor_data(12)
    assert divisors == [1, 2, 3, 4, 6, 12]
    assert bases == [None, 2, 3, 2, None, None]
    assert isclose(float(weights.sum()), log(12), abs_tol=1e-14)
    colors = {2: "#256aa5", 3: "#ce731e", None: "#79818a"}
    labels = ["0", r"$\log 2$", r"$\log 3$", r"$\log 2$", "0", "0"]
    plt.rcParams.update({"font.size": 12, "axes.spines.top": False,
                         "axes.spines.right": False})
    fig, axes = plt.subplots(1, 2, figsize=(10, 4.9), dpi=120,
                             gridspec_kw={"width_ratios": [3, 1]}, layout="constrained")
    fig.patch.set_facecolor("white")
    fig.suptitle(r"Divisor weights recover $\log 12$", fontsize=19)
    ax, total_ax = axes
    ax.set_facecolor("white")
    total_ax.set_facecolor("white")
    positions = np.arange(len(divisors))
    ax.bar(positions, weights, color=[colors[p] for p in bases], width=0.64, zorder=3)
    for pos, weight, label in zip(positions, weights, labels):
        ax.text(pos, weight + 0.045, label, ha="center", va="bottom", fontsize=13)
        if weight == 0:
            ax.plot(pos, 0, "o", color=colors[None], markersize=5, clip_on=False, zorder=4)
    ax.set_xticks(positions, divisors)
    ax.set(xlabel=r"Positive divisor $d$ of 12", ylabel=r"Weight $\Lambda(d)$",
           ylim=(0, 1.45), title=r"Only $2$, $3$, and $4$ contribute")
    ax.grid(axis="y", color="#e0e4e8", zorder=0)
    bottom = 0.0
    for p in (2, 2, 3):
        weight = log(p)
        total_ax.bar(0, weight, bottom=bottom, color=colors[p], width=0.7,
                     edgecolor="white", linewidth=1.5, zorder=3)
        total_ax.text(0, bottom + weight / 2, rf"$\log {p}$", ha="center",
                      va="center", color="white", fontsize=14)
        bottom += weight
    total_ax.set_xticks([0], [r"$2\log 2+\log 3$"])
    total_ax.set(xlim=(-0.65, 0.65), ylim=(0, 2.9), ylabel="Total weight",
                 title=r"$12=2^2\cdot3$")
    total_ax.text(0, bottom + 0.07, r"$\log 12$", ha="center", fontsize=15)
    total_ax.grid(axis="y", color="#e0e4e8", zorder=0)
    fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), facecolor="white",
                transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()