codexrelative-openness-and-boundary.py
relative-openness-and-boundary.py
"""Compare relative openness with interior, closure, and boundary on R.
Tested with Python 3.14, NumPy 2.3.5, and Matplotlib 3.10.7.
Run via make _media/relative-openness-and-boundary.png; save to cwd.
"""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
BLUE = "#176b9c"
GREEN = "#21805e"
ORANGE = "#b94e13"
GREY = "#b8c1cb"
def segment(ax, y, right=1, endpoints=(True, True), color=BLUE):
ax.plot([0, right], [y, y], color=color, lw=5, solid_capstyle="butt")
for x, included in zip((0, right), endpoints):
ax.scatter([x], [y], s=85, facecolors=color if included else "white",
edgecolors=color, linewidths=1.8, zorder=5)
def axis_line(ax, y, relative=False, half_tick=False):
if relative:
segment(ax, y, color=GREY)
else:
ax.annotate("", xy=(1.2, y), xytext=(-0.2, y), zorder=1,
arrowprops={"arrowstyle": "<->", "color": GREY, "lw": 1.2})
ticks = (0, 0.5, 1) if half_tick else (0, 1)
for x in ticks:
ax.text(x, y - 0.13, "1/2" if x == 0.5 else str(int(x)),
ha="center", va="top", fontsize=10)
def main():
fig, panels = plt.subplots(3, 1, figsize=(9.8, 8.2), facecolor="white",
gridspec_kw={"height_ratios": [1.25, 0.95, 1.65]})
fig.subplots_adjust(left=0.035, right=0.97, top=0.88, bottom=0.045, hspace=0.48)
fig.suptitle("The ambient space changes the answer", fontsize=19, y=0.975)
for ax in panels:
ax.set(xlim=(-0.94, 1.24))
ax.axis("off")
ax = panels[0]
ax.set(ylim=(-0.5, 1.65))
ax.set_title(r"1. Ambient space $X=[0,1]$: $A=[0,1/2)$ is open in $X$", fontsize=14, loc="left")
ax.axvspan(-0.21, -0.025, color="#f1f3f5", zorder=0)
ax.axvspan(1.025, 1.2, color="#f1f3f5", zorder=0)
ax.text(-0.115, 1.22, "outside\nX", ha="center", fontsize=10, color="#5a6470")
ax.text(1.115, 1.22, "outside\nX", ha="center", fontsize=10, color="#5a6470")
ax.text(-0.89, 0.82, r"$A=[0,1/2)$", fontsize=13, va="center")
axis_line(ax, 0.82, relative=True, half_tick=True)
segment(ax, 0.82, right=0.5, endpoints=(True, False))
radius = 0.25
ax.text(-0.89, 0.05, r"$B_X(0,r)=[0,r)$", fontsize=13, va="center")
axis_line(ax, 0.05, relative=True)
segment(ax, 0.05, right=radius, endpoints=(True, False), color=GREEN)
ax.text(radius, -0.08, r"$r=1/4$", ha="center", va="top", fontsize=10, color=GREEN)
ax.text(0.62, 0.26, "The relative ball stays in A.", ha="center", fontsize=11, color=GREEN)
ax = panels[1]
ax.set(ylim=(-0.6, 1.03))
ax.set_title(r"2. Ambient space $X=[0,1]$: $X$ itself is open and closed", fontsize=14, loc="left")
ax.text(-0.89, 0.4, r"$X=[0,1]$", fontsize=13, va="center")
axis_line(ax, 0.4, relative=True)
segment(ax, 0.4)
ax.text(0.5, -0.40, r"$\operatorname{int}_X X=X$ $\operatorname{cl}_X X=X$ $\partial_X X=\varnothing$",
ha="center", fontsize=12)
ax = panels[2]
ax.set(ylim=(-0.43, 3.5))
ax.set_title(r"3. Ambient space $\mathbb{R}$: $S=[0,1)$", fontsize=14, loc="left")
rows = np.array([2.95, 2.05, 1.15, 0.25])
labels = (r"$S=[0,1)$", r"Interior: $(0,1)$",
r"Closure: $[0,1]$", r"Boundary: $\{0,1\}$")
for y, label in zip(rows, labels):
ax.text(-0.89, y, label, fontsize=13, va="center")
axis_line(ax, y)
segment(ax, rows[0], endpoints=(True, False))
segment(ax, rows[1], endpoints=(False, False), color=GREEN)
segment(ax, rows[2], endpoints=(True, True))
ax.scatter([0, 1], [rows[3], rows[3]], s=90, color=ORANGE, zorder=5)
ax.text(0.5, -0.39, "Both endpoints belong to the boundary; only 0 belongs to S.",
ha="center", fontsize=11, color=ORANGE)
fig.savefig(Path.cwd() / f"{Path(__file__).stem}.png", dpi=125,
facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()