{"spec_id":"curve-power-duration","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\ncurve-power-duration: Mean-Maximal Power Duration Curve\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 87/100 | Created: 2026-06-13\n\"\"\"\n\nimport os\nimport sys\n\n\n# Remove the script's directory from sys.path so 'import seaborn' resolves\n# to the installed package rather than this file.\nif sys.path and sys.path[0] in (\"\", os.path.dirname(os.path.abspath(__file__))):\n    sys.path.pop(0)\n\nimport matplotlib.pyplot as plt\nimport matplotlib.ticker as ticker\nimport numpy as np\nimport seaborn as sns\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint palette — canonical order, first series always #009E73\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\nBRAND = IMPRINT_PALETTE[0]  # empirical MMP curve — position 1\nMODEL_COLOR = IMPRINT_PALETTE[1]  # CP model fit — position 2 (lavender)\n\nsns.set_theme(\n    style=\"ticks\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.edgecolor\": INK_SOFT,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"grid.color\": INK,\n        \"grid.alpha\": 0.15,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\nsns.set_context(\"notebook\", font_scale=0.85)\n\n# Data — well-trained cyclist: CP ≈ 280 W, W′ ≈ 21 kJ\nnp.random.seed(42)\nCP = 280  # critical power (W) — aerobic asymptote\nW_PRIME = 21000  # anaerobic work capacity (J)\nNM_CAP = 1100  # neuromuscular peak power cap (W) at very short efforts\n\n# Empirical MMP: CP model with neuromuscular cap + realistic scatter\nemp_durations = np.logspace(0, np.log10(18000), 50)\nemp_base = np.minimum(CP + W_PRIME / emp_durations, NM_CAP)\nemp_power = emp_base + np.random.normal(0, 9, size=len(emp_durations))\nfor i in range(1, len(emp_power)):\n    if emp_power[i] > emp_power[i - 1]:\n        emp_power[i] = emp_power[i - 1]\n\n# CP model line: smooth curve from 30 s to 5 h (valid fitting range)\nmod_durations = np.logspace(np.log10(30), np.log10(18000), 250)\nmod_power = CP + W_PRIME / mod_durations\n\n# Plot\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\n# Empirical MMP curve (primary series — Imprint brand green)\nsns.lineplot(\n    x=emp_durations, y=emp_power, ax=ax, errorbar=None, color=BRAND, linewidth=2.5, label=\"Mean-maximal power (MMP)\"\n)\n\n# CP model overlay — dashed, Imprint blue\nax.plot(\n    mod_durations,\n    mod_power,\n    color=MODEL_COLOR,\n    linewidth=1.8,\n    linestyle=\"--\",\n    label=f\"CP model  (CP = {CP} W, W′ = {W_PRIME // 1000} kJ)\",\n    zorder=3,\n)\n\n# CP asymptote reference line\nax.axhline(CP, color=INK_MUTED, linewidth=1.0, linestyle=\":\", alpha=0.75, zorder=2)\nax.text(16500, CP + 8, f\"CP = {CP} W\", color=INK_MUTED, fontsize=7.5, ha=\"right\", va=\"bottom\")\n\n# Reference duration markers (spec: 5 s, 1 min, 5 min, 20 min)\nref_markers = {5: \"5 s sprint\", 60: \"1 min\", 300: \"5 min\", 1200: \"20 min\"}\nfor t, label in ref_markers.items():\n    ax.axvline(t, color=INK_SOFT, linewidth=0.9, linestyle=\"--\", alpha=0.5, zorder=1)\n    ax.text(t, 1210, label, color=INK_SOFT, fontsize=8, ha=\"center\", va=\"bottom\")\n\n# X-axis — log scale with human-readable duration labels\nax.set_xscale(\"log\")\nxtick_pos = [1, 5, 30, 60, 300, 1200, 3600, 18000]\nxtick_lbl = [\"1 s\", \"5 s\", \"30 s\", \"1 min\", \"5 min\", \"20 min\", \"1 h\", \"5 h\"]\nax.set_xticks(xtick_pos)\nax.set_xticklabels(xtick_lbl)\nax.xaxis.set_minor_locator(ticker.NullLocator())\nax.set_xlim(0.8, 22000)\nax.set_ylim(220, 1300)\n\n# Style\ntitle = \"curve-power-duration · python · seaborn · anyplot.ai\"\nax.set_title(title, fontsize=12, fontweight=\"medium\", color=INK, pad=12)\nax.set_xlabel(\"Duration\", fontsize=10, color=INK)\nax.set_ylabel(\"Power (W)\", fontsize=10, color=INK)\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT)\n\nsns.despine(ax=ax)\nax.spines[\"left\"].set_color(INK_SOFT)\nax.spines[\"bottom\"].set_color(INK_SOFT)\n\nax.yaxis.grid(True, alpha=0.15, linewidth=0.8, color=INK)\n\n# Legend — lower left keeps it clear of the reference-duration labels at the top\nlegend = ax.legend(fontsize=8, loc=\"lower left\", facecolor=ELEVATED_BG, edgecolor=INK_SOFT, framealpha=0.9)\nfor text in legend.get_texts():\n    text.set_color(INK)\n\nfig.subplots_adjust(left=0.10, right=0.97, top=0.92, bottom=0.14)\n\n# Save — bbox_inches must stay default (None) per seaborn canvas rule\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\nplt.close()\n"}