codexhyperbolic-cosine-catenary.py
hyperbolic-cosine-catenary.py
"""Catenary examples, vertical shift and scale; PNG goes to CWD.
Tested with Python 3.14, NumPy 2.3.5 and Matplotlib 3.10.7.
"""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
def main():
x = np.linspace(-1.65, 1.65, 501)
blue = "#176b9c"
fig, axes = plt.subplots(1, 2, figsize=(10.8, 4.9), dpi=120)
fig.patch.set_facecolor("white")
ax = axes[0]
ax.plot(x, np.cosh(x), color=blue, lw=2.6, label=r"$y=\cosh x$")
nodes = np.array([-1., 0., 1.])
ax.scatter(nodes, np.cosh(nodes), color=blue, s=45, zorder=4)
ax.scatter([-1, 1], [np.cosh(1)]*2, marker="s", color="#444444",
s=85, zorder=3, label="Supports at x = ±1")
for value in nodes:
text = "(0, 1)" if value == 0 else f"({int(value)}, 1.543)"
offset = (0, -24) if value == 0 else (0, 13)
ax.annotate(text, (value, np.cosh(value)), xytext=offset,
textcoords="offset points", ha="center", fontsize=11)
ax.axhline(1, color="0.65", ls=":", lw=1)
ax.text(-1.58, .18, r"$L_{[-1,1]}=2\sinh1\approx2.350$", fontsize=11)
ax.set(xlim=(-1.7, 1.7), ylim=(0, 2.95), xlabel="Horizontal position x",
ylabel="Height y", title="a = 1: lowest point at height 1")
ax.legend(loc="upper center", fontsize=10, framealpha=1)
ax = axes[1]
for a, color in [(.5, "#bf5a15"), (1, blue), (2, "#33844a")]:
ax.plot(x, a*(np.cosh(x/a)-1), color=color, lw=2.4,
label=f"a = {a:g}")
ax.plot(x, x*x/2, color="0.4", ls="--", lw=1.5,
label=r"Parabola $x^2/2$ (a = 1 locally)")
ax.scatter(nodes, np.cosh(nodes)-1, color=blue, s=35, zorder=4)
ax.annotate("(0, 0)", (0, 0), xytext=(15, 12),
textcoords="offset points", fontsize=11)
ax.set(xlim=(-1.7, 1.7), ylim=(-.12, 3.0), xlabel="Horizontal position x",
ylabel="Height above the lowest point",
title=r"Shifted: $y=a[\cosh(x/a)-1]$")
ax.legend(loc="upper center", fontsize=9.5, framealpha=1)
for ax in axes:
ax.set_facecolor("white")
ax.axhline(0, color="0.5", lw=.8, zorder=0)
ax.axvline(0, color="0.5", lw=.8, zorder=0)
ax.grid(alpha=.17)
ax.set_xticks([-1, 0, 1])
ax.spines[["top", "right"]].set_visible(False)
fig.suptitle("A catenary is a scaled hyperbolic cosine", fontsize=15)
fig.text(.5, .035, "Uniform flexible, inextensible chain under its own weight. "
"Larger a = H/(μg) gives less sag over a fixed horizontal span.",
ha="center", fontsize=10)
fig.subplots_adjust(left=.075, right=.98, bottom=.17, top=.83, wspace=.29)
fig.savefig(Path.cwd() / "hyperbolic-cosine-catenary.png",
facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()