codex/minimum-drag-and-power-speeds.py

minimum-drag-and-power-speeds.py
"""Compare exact optima of an original dimensionless parabolic-polar model.

Run from _media using the repository Python 3.14 / NumPy / Matplotlib setup.
The curves are constructed calculations, not measured aircraft performance.
"""
from pathlib import Path

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np


def drag_ratio(x):
    x = np.asarray(x, dtype=float)
    return (x**2 + x**-2) / 2


def power_ratio(x):
    x = np.asarray(x, dtype=float)
    return (x**3 + x**-1) / 2


def main():
    x_mp = 3**(-0.25)
    assert np.isclose(drag_ratio(1), 1)
    assert np.isclose(power_ratio(x_mp), 2 * 3**(-0.75))
    assert np.isclose(3 * x_mp**2 - x_mp**-2, 0)
    x = np.linspace(0.48, 1.75, 601)
    fig, axes = plt.subplots(1, 2, figsize=(8.4, 4.6), dpi=100, facecolor="white")
    specs = [(axes[0], drag_ratio, "#245f8e", "Drag required", r"$D/D_{\min}$", 1.0),
             (axes[1], power_ratio, "#ba5c24", "Propulsive power required",
              r"$P/(D_{\min}V_{\rm MD})$", x_mp)]
    for ax, curve, color, title, ylabel, optimum in specs:
        ax.set_facecolor("white")
        ax.plot(x, curve(x), color=color, lw=2.5)
        ax.axvline(x_mp, color="#ba5c24", ls=":", lw=1.2)
        ax.axvline(1, color="#245f8e", ls="--", lw=1.2)
        y_min = float(curve(optimum))
        ax.scatter([optimum], [y_min], color=color, s=35, zorder=5)
        ax.annotate("Minimum", (optimum, y_min), xytext=(12, 30),
                    textcoords="offset points", fontsize=10,
                    arrowprops={"arrowstyle": "->", "color": color})
        ax.set(xlim=(0.48, 1.75), ylim=(0.65, 3.05), title=title,
               xlabel=r"Speed ratio $V/V_{\rm MD}$", ylabel=ylabel)
        ax.grid(alpha=0.17)
    axes[0].text(0.50, 2.65, r"$D=aV^2+b/V^2$", fontsize=11)
    axes[1].text(0.50, 2.65, r"$P=DV=aV^3+b/V$", fontsize=11)
    fig.suptitle("Different speeds minimize drag and required power", fontsize=14, y=0.98)
    fig.text(0.5, 0.083, r"Dotted: $V_{\rm MP}/V_{\rm MD}=3^{-1/4}\simeq0.760$"
             "     Dashed: drag minimum at 1", ha="center", fontsize=10)
    fig.text(0.5, 0.027, "Constructed model: steady level flight, fixed weight and density, constant polar coefficients.",
             ha="center", fontsize=9, color="#444444")
    fig.tight_layout(rect=(0, 0.12, 1, 0.94))
    fig.savefig(Path("minimum-drag-and-power-speeds.png"), facecolor="white", dpi=100)
    plt.close(fig)


if __name__ == "__main__":
    main()