codex/image-preimage-intersection-asymmetry.py

image-preimage-intersection-asymmetry.py
"""Generate image-preimage-intersection-asymmetry.png in the current directory."""

from pathlib import Path

import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.patches import Circle, FancyArrowPatch


def node(ax, xy, label, color):
    ax.add_patch(Circle(xy, 0.22, facecolor=color, edgecolor="white",
                        linewidth=1.0, zorder=3))
    ax.text(*xy, label, ha="center", va="center", color="white",
            fontsize=14, zorder=4)


def arrow(ax, start, end, color):
    ax.add_patch(FancyArrowPatch(start, end, arrowstyle="-|>", mutation_scale=16,
                                linewidth=2, color=color, shrinkA=15, shrinkB=15))


def main():
    blue, orange, green = "#2875ad", "#d4652b", "#23815b"
    fig, axes = plt.subplots(1, 2, figsize=(10.6, 4.6), dpi=120,
                             facecolor="white")
    left, right = axes
    node(left, (0, 2), "a", blue)
    node(left, (0, 0.8), "b", orange)
    node(left, (2.5, 1.4), "0", green)
    arrow(left, (0, 2), (2.5, 1.4), blue)
    arrow(left, (0, 0.8), (2.5, 1.4), orange)
    left.text(-0.1, 2.55, "E = {a}", color=blue, ha="center", fontsize=12)
    left.text(-0.1, 0.20, "F = {b}", color=orange, ha="center", fontsize=12)
    left.text(1.15, -0.55, "E ∩ F = ∅\nf(E ∩ F) = ∅\nf(E) ∩ f(F) = {0}",
              ha="center", fontsize=13, linespacing=1.7)
    left.set_title("Images can merge disjoint inputs", fontsize=14, pad=16)
    node(right, (0, 2), "a", blue)
    node(right, (0, 0.8), "b", blue)
    node(right, (2.5, 1.4), "0", green)
    arrow(right, (0, 2), (2.5, 1.4), blue)
    arrow(right, (0, 0.8), (2.5, 1.4), blue)
    right.text(2.5, 2.20, "U ∩ V = {0}", ha="center", fontsize=12, color=green)
    right.text(1.2, -0.45, "U = {0, 1},   V = {0, 2}\nf⁻¹(U ∩ V) = {a, b}\nf⁻¹(U) ∩ f⁻¹(V) = {a, b}",
               ha="center", fontsize=13, linespacing=1.7)
    right.set_title("Preimages test both conditions on one input", fontsize=14, pad=16)
    for ax in axes:
        ax.set(xlim=(-0.65, 3.25), ylim=(-1.35, 3.05))
        ax.axis("off")
    fig.suptitle("One collision explains the intersection asymmetry",
                 fontsize=16, y=0.98)
    fig.subplots_adjust(left=0.04, right=0.98, top=0.83, bottom=0.13, wspace=0.24)
    fig.text(0.5, 0.04,
             "Same function: f(a) = f(b) = 0, f(c) = 1, f(d) = 2.",
             ha="center", fontsize=12)
    fig.savefig(Path.cwd() / "image-preimage-intersection-asymmetry.png",
                facecolor="white")
    plt.close(fig)


if __name__ == "__main__":
    main()