codex/function-image-preimage-subset-flow.py

function-image-preimage-subset-flow.py
"""Generate function-image-preimage-subset-flow.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 diagram(ax, selected_inputs, selected_outputs, heading, footer):
    ys = [3, 2, 1, 0]
    mapping = [0, 0, 1, 2]
    blue, green, gray = "#2875ad", "#23815b", "#c4c9ce"
    for i, out in enumerate(mapping):
        active = i in selected_inputs and out in selected_outputs
        ax.add_patch(FancyArrowPatch((0.16, ys[i]), (2.82, ys[out]),
                                    arrowstyle="-|>", mutation_scale=14,
                                    color=blue if active else gray,
                                    linewidth=2.0 if active else 1.2,
                                    zorder=2 if active else 1, shrinkA=5, shrinkB=5))
    for x, labels, selected, color in [
        (0, ["a", "b", "c", "d"], selected_inputs, blue),
        (3, ["0", "1", "2", "3"], selected_outputs, green),
    ]:
        for i, label in enumerate(labels):
            ax.add_patch(Circle((x, ys[i]), 0.18,
                                facecolor=color if i in selected else "white",
                                edgecolor=color if i in selected else "#777777",
                                linewidth=1.6, zorder=3))
            ax.text(x, ys[i], label, ha="center", va="center", fontsize=13,
                    color="white" if i in selected else "#333333", zorder=4)
    ax.text(0, 3.52, "Input set A", ha="center", fontsize=12)
    ax.text(3, 3.52, "Codomain B", ha="center", fontsize=12)
    ax.text(1.5, 3.30, "f", ha="center", fontsize=15, style="italic")
    ax.set_title(heading, fontsize=14, pad=18)
    ax.text(1.5, -0.62, footer, ha="center", fontsize=12, linespacing=1.5)
    ax.set(xlim=(-0.65, 3.65), ylim=(-1.25, 3.85))
    ax.axis("off")


def main():
    fig, axes = plt.subplots(1, 2, figsize=(10.6, 4.6), dpi=120,
                             facecolor="white")
    diagram(axes[0], {0, 2}, {0, 1}, "Image: follow selected inputs",
            "E = {a, c}   →   f(E) = {0, 1}")
    diagram(axes[1], {0, 1, 3}, {0, 2}, "Preimage: select outputs, find every input",
            "U = {0, 2}   →   f⁻¹(U) = {a, b, d}")
    fig.suptitle("Images move forward; preimages collect inputs",
                 fontsize=16, y=0.98)
    fig.subplots_adjust(left=0.04, right=0.98, top=0.83, bottom=0.13, wspace=0.23)
    fig.text(0.5, 0.045,
             "Output 0 has two inputs.  Output 3 is allowed but never reached.",
             ha="center", fontsize=12)
    fig.savefig(Path.cwd() / "function-image-preimage-subset-flow.png",
                facecolor="white")
    plt.close(fig)


if __name__ == "__main__":
    main()