{"spec_id":"nyquist-basic","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nnyquist-basic: Nyquist Plot for Control Systems\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 85/100 | Updated: 2026-06-17\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nfrom scipy import signal\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\"\n\n# Imprint palette — categorical positions\nBRAND = \"#009E73\"  # position 1 — positive-frequency branch\nIMPRINT_PURPLE = \"#C475FD\"  # position 2 — negative-frequency (mirror) branch\nCRIT_RED = \"#AE3030\"  # semantic anchor — critical stability threshold\n\n# Data — open-loop transfer function: G(s) = 10 / ((s+1)(0.5s+1)(0.2s+1))\nnum = [10.0]\nden = np.polymul(np.polymul([1.0, 1.0], [0.5, 1.0]), [0.2, 1.0])\nsystem = signal.TransferFunction(num, den)\n\nomega = np.logspace(-2, 2, 800)\n_, H = signal.freqresp(system, omega)\n\nreal_part = H.real\nimag_part = H.imag\n\n# Build DataFrame for seaborn-idiomatic plotting\ndf_pos = pd.DataFrame({\"Real\": real_part, \"Imaginary\": imag_part, \"Branch\": \"G(jω), ω ≥ 0\"})\ndf_neg = pd.DataFrame({\"Real\": real_part, \"Imaginary\": -imag_part, \"Branch\": \"G(jω), ω < 0\"})\ndf = pd.concat([df_pos, df_neg], ignore_index=True)\n\n# Seaborn theme — ticks style with theme-adaptive chrome\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.12,\n        \"grid.linewidth\": 0.8,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\n# Canvas — square 2400×2400 for equal-aspect Nyquist plot\nfig, ax = plt.subplots(figsize=(6, 6), dpi=400)\n\n# Both branches via seaborn lineplot with hue + dashes\nsns.lineplot(\n    data=df,\n    x=\"Real\",\n    y=\"Imaginary\",\n    hue=\"Branch\",\n    palette=[BRAND, IMPRINT_PURPLE],\n    linewidth=2.5,\n    sort=False,\n    estimator=None,\n    style=\"Branch\",\n    dashes={\"G(jω), ω ≥ 0\": \"\", \"G(jω), ω < 0\": (5, 3)},\n    ax=ax,\n    legend=True,\n)\n\n# De-emphasize the mirror (negative-frequency) branch\nfor line in ax.get_lines():\n    if line.get_linestyle() != \"-\":\n        line.set_alpha(0.4)\n\n# Unit circle for reference\ntheta = np.linspace(0, 2 * np.pi, 200)\nax.plot(np.cos(theta), np.sin(theta), color=INK_SOFT, linewidth=1.0, linestyle=\":\", alpha=0.5, zorder=1)\n\n# Axis reference lines\nax.axhline(y=0, color=INK_SOFT, linewidth=0.7, zorder=0, alpha=0.4)\nax.axvline(x=0, color=INK_SOFT, linewidth=0.7, zorder=0, alpha=0.4)\n\n# Critical point (−1, 0) — semantic red anchor marks instability threshold\nax.plot(-1, 0, marker=\"x\", color=CRIT_RED, markersize=16, markeredgewidth=3, zorder=5)\nax.annotate(\n    \"Critical point\\n(−1, 0)\",\n    xy=(-1, 0),\n    xytext=(-1.8, 2.8),\n    fontsize=8,\n    color=CRIT_RED,\n    fontweight=\"bold\",\n    arrowprops={\"arrowstyle\": \"->\", \"color\": CRIT_RED, \"lw\": 1.5},\n)\n\n# Direction arrows along positive-frequency branch\narrow_indices = [80, 250, 450]\nfor idx in arrow_indices:\n    ax.annotate(\n        \"\",\n        xy=(real_part[idx + 8], imag_part[idx + 8]),\n        xytext=(real_part[idx], imag_part[idx]),\n        arrowprops={\"arrowstyle\": \"->\", \"color\": BRAND, \"lw\": 2.2},\n    )\n\n# Frequency annotations — spread offsets to minimise crowding near origin\nfreq_labels = [0.1, 0.5, 1.0, 3.0, 10.0]\nfreq_indices = [np.argmin(np.abs(omega - f)) for f in freq_labels]\nfreq_df = pd.DataFrame(\n    {\n        \"Real\": [real_part[i] for i in freq_indices],\n        \"Imaginary\": [imag_part[i] for i in freq_indices],\n        \"Label\": [f\"ω={f}\" for f in freq_labels],\n    }\n)\n\nsns.scatterplot(\n    data=freq_df,\n    x=\"Real\",\n    y=\"Imaginary\",\n    color=BRAND,\n    s=110,\n    zorder=4,\n    ax=ax,\n    legend=False,\n    edgecolor=PAGE_BG,\n    linewidth=1.5,\n)\n\n# Text offsets chosen to spread labels away from crowded low-frequency region\noffsets = {0.1: (0.0, -1.4), 0.5: (1.2, 0.0), 1.0: (0.9, -0.7), 3.0: (-2.0, -0.4), 10.0: (0.8, 0.6)}\nfor i, (_, row) in enumerate(freq_df.iterrows()):\n    x, y = row[\"Real\"], row[\"Imaginary\"]\n    f = freq_labels[i]\n    ox, oy = offsets[f]\n    ax.annotate(\n        row[\"Label\"],\n        xy=(x, y),\n        xytext=(x + ox, y + oy),\n        fontsize=8,\n        color=INK_SOFT,\n        arrowprops={\"arrowstyle\": \"->\", \"color\": INK_SOFT, \"lw\": 0.9},\n    )\n\n# Style\ntitle = \"nyquist-basic · python · seaborn · anyplot.ai\"\nax.set_title(title, fontsize=12, fontweight=\"medium\", color=INK)\nax.set_xlabel(\"Real Part  Re[G(jω)]\", fontsize=10, color=INK)\nax.set_ylabel(\"Imaginary Part  Im[G(jω)]\", fontsize=10, color=INK)\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT)\nax.set_aspect(\"equal\")\nsns.despine(ax=ax)\n\n# Grid — subtle both-axis for complex-plane orientation\nax.grid(True, alpha=0.12, linewidth=0.8, color=INK)\n\n# Legend\nhandles, labels = ax.get_legend_handles_labels()\nif handles:\n    ax.legend(handles, labels, loc=\"upper right\", framealpha=0.9, edgecolor=INK_SOFT, fontsize=8, facecolor=ELEVATED_BG)\n\nplt.tight_layout()\n\n# Save — no bbox_inches to preserve exact 2400×2400 canvas\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}