codexlogistic-growth-trajectories.py
logistic-growth-trajectories.py
"""Plot logistic growth; tested with Python 3.14, NumPy 2.3, Matplotlib 3.10.
Run from the desired output directory; writes logistic-growth-trajectories.png.
"""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
def main():
capacity, rate = 1000.0, 0.4
t = np.linspace(0.0, 20.0, 1001)
def population(time, initial):
return capacity / (1.0 + (capacity / initial - 1.0) * np.exp(-rate * time))
plt.rcParams.update({"font.size": 11, "axes.spines.top": False,
"axes.spines.right": False})
fig, ax = plt.subplots(figsize=(8.0, 4.8), dpi=120,
constrained_layout=True, facecolor="white")
colors = {100: "#2176a5", 500: "#258568", 1200: "#b74a25"}
for initial, color in colors.items():
ax.plot(t, population(t, initial), color=color, lw=2.7,
label=rf"$N_0={initial}$")
ax.scatter([0], [initial], color=color, s=32, zorder=5, clip_on=False)
ax.axhline(capacity, color="#665078", ls="--", lw=1.6)
ax.text(19.6, 1027, r"carrying capacity $K=1000$", ha="right",
color="#665078", fontsize=10)
ax.axhline(0, color="#555555", ls="--", lw=1.4)
ax.text(0.5, 28, r"equilibrium $N=0$", color="#555555", fontsize=10)
ax.axhline(capacity / 2, color="#bbbbbb", ls=":", lw=1.2)
ax.text(19.6, 519, r"$K/2=500$", ha="right", color="#777777", fontsize=10)
inflection_time = np.log(9.0) / rate
ax.plot([inflection_time, inflection_time], [0, 500], color=colors[100],
ls=":", lw=1.2)
ax.scatter([inflection_time], [500], color=colors[100], marker="D",
s=44, zorder=6)
ax.annotate("future inflection\n5.49306 days; N = 500",
xy=(inflection_time, 500), xytext=(7.5, 295),
arrowprops={"arrowstyle": "->", "color": colors[100]},
color=colors[100], fontsize=10,
bbox={"facecolor": "white", "edgecolor": "none", "pad": 2})
for time, label_position in [(5, (1.0, 705)), (10, (11.6, 725))]:
value = float(population(time, 100))
ax.scatter([time], [value], color=colors[100], s=30, zorder=6)
ax.annotate(f"N({time}) = {value:.2f}", xy=(time, value),
xytext=label_position, fontsize=10, color=colors[100],
arrowprops={"arrowstyle": "->", "color": colors[100]},
bbox={"facecolor": "white", "edgecolor": "none", "pad": 2})
ax.set(title="One growth law, three initial populations\n"
r"$N'=0.4N(1-N/1000)$",
xlabel="time t (days)", ylabel="population N (individuals)",
xlim=(0, 20), ylim=(-40, 1280), xticks=np.arange(0, 21, 5))
ax.set_facecolor("white")
ax.grid(alpha=0.14)
ax.legend(loc="lower right", framealpha=1, fontsize=10)
fig.savefig(Path.cwd() / "logistic-growth-trajectories.png",
facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()