{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "e4c5eb34-e32a-471f-923f-e572c25e893e",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import math\n",
    "import matplotlib\n",
    "from matplotlib import pyplot as plt\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "adb7bd21-de07-4f61-8edf-dfb04b99cef5",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'$\\\\frac{2\\\\pi}{3}$'"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def fmt_angle(a):\n",
    "    a /= np.pi\n",
    "    k = 2**6 * 3**5 * 5**3 * 7**2\n",
    "    n = round(a*k)\n",
    "    f = math.gcd(n, k)\n",
    "    n, k = n//f, k//f\n",
    "    if n == 0:\n",
    "        return '$0$'\n",
    "    elif n == 1:\n",
    "        n = ''\n",
    "\n",
    "    if k == 1:\n",
    "        return fr'${n}\\pi$'\n",
    "    else:\n",
    "        return fr'$\\frac{{{n}\\pi}}{{{k}}}$'\n",
    "\n",
    "def plural(n, s, s_plural=None):\n",
    "    if s_plural is None:\n",
    "        s_plural = s + 's'\n",
    "    if not n or n >= 2:\n",
    "        s = s_plural\n",
    "\n",
    "    return f'{int(n)} {s}'\n",
    "\n",
    "fmt_angle(2*np.pi/3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "4887090a-7b69-4cc5-9954-13667a59ff57",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 105080 characters]",
      "text/plain": [
       "<Figure size 1200x600 with 8 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 149068 characters]",
      "text/plain": [
       "<Figure size 1200x600 with 8 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, axs = plt.subplots(2, 4, figsize=(12, 6))\n",
    "\n",
    "r1, r2 = 10, 30\n",
    "f = 1.1 # border factor around top plots\n",
    "\n",
    "for axi, n in zip(axs.T, [1, 2, 3, 5]):\n",
    "    axi[0].set_title(f'{n} turns')\n",
    "    circle_r1 = plt.Circle((0, 0), r1, color='black', fill=False)\n",
    "    circle_r2 = plt.Circle((0, 0), r2, color='black', fill=False)\n",
    "    axi[0].add_patch(circle_r1)\n",
    "    axi[0].add_patch(circle_r2)\n",
    "    axi[0].set_xlim([-r2*f, r2*f])\n",
    "    axi[0].set_ylim([-r2*f, r2*f])\n",
    "    axi[0].set_aspect('equal')\n",
    "    t = np.linspace(0, 1, 1000)\n",
    "    phi = 2*np.pi*t*n\n",
    "    r = r1 + (r2 - r1)*t\n",
    "    x = r*np.cos(phi)\n",
    "    y = -r*np.sin(phi)\n",
    "    axi[0].plot(x, y, color='red')\n",
    "    axi[0].scatter(x[[0, -1]], y[[0, -1]], marker='x', color='red')\n",
    "fig, axs = plt.subplots(2, 4, figsize=(12, 6))\n",
    "\n",
    "r1, r2 = 10, 30\n",
    "f = 1.1 # border factor around top plots\n",
    "\n",
    "for axi, n in zip(axs.T, [1, 2, 3, 5]):\n",
    "    axi[0].set_title(f'{n} turns')\n",
    "    circle_r1 = plt.Circle((0, 0), r1, color='black', fill=False)\n",
    "    circle_r2 = plt.Circle((0, 0), r2, color='black', fill=False)\n",
    "    axi[0].add_patch(circle_r1)\n",
    "    axi[0].add_patch(circle_r2)\n",
    "    axi[0].set_xlim([-r2*f, r2*f])\n",
    "    axi[0].set_ylim([-r2*f, r2*f])\n",
    "    axi[0].set_aspect('equal')\n",
    "    t = np.linspace(0, 1, 1000)\n",
    "    phi = 2*np.pi*t*n\n",
    "    r = r1 + (r2 - r1)*t\n",
    "    x = r*np.cos(phi)\n",
    "    y = -r*np.sin(phi)\n",
    "    axi[0].plot(x, y, color='red')\n",
    "    axi[0].scatter(x[[0, -1]], y[[0, -1]], marker='x', color='red')\n",
    "\n",
    "    axi[1].set_xlim([0, 2*np.pi])\n",
    "    axi[1].set_ylim([r1, r2])\n",
    "    phi_m = phi % (2*np.pi)\n",
    "    discontinuities, = np.where(phi_m[1:] < phi_m[:-1])\n",
    "    phi_m = np.insert(phi_m, discontinuities + 1, np.nan)\n",
    "    r_m = np.insert(r, discontinuities + 1, np.nan)\n",
    "    axi[1].plot(phi_m, r_m)\n",
    "    axi[1].set_xlabel(r'$\\phi$')\n",
    "    \n",
    "    ticks = np.linspace(0, 2*np.pi, 5)\n",
    "    axi[1].set_xticks(ticks)\n",
    "    def fmt_angle(a):\n",
    "        a /= np.pi\n",
    "        k = 128\n",
    "        n = round(a*k)\n",
    "        f = math.gcd(n, k)\n",
    "        n, k = n//f, k//f\n",
    "        if n == 0:\n",
    "            return '$0$'\n",
    "        elif n == 1:\n",
    "            n = ''\n",
    "\n",
    "        if k == 1:\n",
    "            return fr'${n}\\pi$'\n",
    "        else:\n",
    "            return fr'$\\frac{{{n}\\pi}}{{{k}}}$'\n",
    "        \n",
    "    axi[1].set_xticklabels(map(fmt_angle, ticks))\n",
    "axs[0][0].set_xlabel('x')\n",
    "axs[0][0].set_ylabel('y')\n",
    "\n",
    "axs[1][0].set_ylabel('r')\n",
    "\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "770fa539-d148-4284-b3e2-70f839871033",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 211056 characters]",
      "text/plain": [
       "<Figure size 1200x600 with 8 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, axs = plt.subplots(2, 4, figsize=(12, 6))\n",
    "\n",
    "r1, r2 = 10, 30\n",
    "f = 1.1 # border factor around top plots\n",
    "\n",
    "for axi, k in zip(axs.T, [1, 2, 3, 5]):\n",
    "    axi[0].set_title(f'{n} spirals')\n",
    "    circle_r1 = plt.Circle((0, 0), r1, color='black', fill=False)\n",
    "    circle_r2 = plt.Circle((0, 0), r2, color='black', fill=False)\n",
    "    axi[0].add_patch(circle_r1)\n",
    "    axi[0].add_patch(circle_r2)\n",
    "    axi[0].set_xlim([-r2*f, r2*f])\n",
    "    axi[0].set_ylim([-r2*f, r2*f])\n",
    "    axi[0].set_aspect('equal')\n",
    "    t = np.linspace(0, 1, 1000)\n",
    "    phi = 2*np.pi*t\n",
    "    r = r1 + (r2 - r1)*t\n",
    "\n",
    "    for i in range(k):\n",
    "        phase = i*2*np.pi/k\n",
    "        color = plt.cm.viridis(i/(k-1) if k > 1 else 0)\n",
    "        x = r*np.cos(phi + phase)\n",
    "        y = -r*np.sin(phi + phase)\n",
    "        axi[0].plot(x, y, color=color)\n",
    "        axi[0].scatter(x[[0, -1]], y[[0, -1]], marker='.', color='black', zorder=2)\n",
    "\n",
    "    axi[1].set_xlim([0, 2*np.pi])\n",
    "    axi[1].set_ylim([r1, r2])\n",
    "\n",
    "    for i in range(k):\n",
    "        phase = i*2*np.pi/k\n",
    "        color = plt.cm.viridis(i/(k-1) if k > 1 else 0)\n",
    "        phi_m = (phi + phase) % (2*np.pi)\n",
    "        discontinuities, = np.where(phi_m[1:] < phi_m[:-1])\n",
    "        phi_m = np.insert(phi_m, discontinuities + 1, np.nan)\n",
    "        r_m = np.insert(r, discontinuities + 1, np.nan)\n",
    "        axi[1].plot(phi_m, r_m, color=color)\n",
    "        axi[1].set_xlabel(r'$\\phi$')\n",
    "    \n",
    "    ticks = np.linspace(0, 2*np.pi, 5)\n",
    "    axi[1].set_xticks(ticks)\n",
    "    def fmt_angle(a):\n",
    "        a /= np.pi\n",
    "        k = 128\n",
    "        n = round(a*k)\n",
    "        f = math.gcd(n, k)\n",
    "        n, k = n//f, k//f\n",
    "        if n == 0:\n",
    "            return '$0$'\n",
    "        elif n == 1:\n",
    "            n = ''\n",
    "\n",
    "        if k == 1:\n",
    "            return fr'${n}\\pi$'\n",
    "        else:\n",
    "            return fr'$\\frac{{{n}\\pi}}{{{k}}}$'\n",
    "        \n",
    "    axi[1].set_xticklabels(map(fmt_angle, ticks))\n",
    "\n",
    "axs[0][0].set_xlabel('x')\n",
    "axs[0][0].set_ylabel('y')\n",
    "\n",
    "axs[1][0].set_ylabel('r')\n",
    "\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "30d432f5-7caf-4675-8063-92b0d30a36e9",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 178324 characters]",
      "text/plain": [
       "<Figure size 1200x600 with 8 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, axs = plt.subplots(2, 4, figsize=(12, 6))\n",
    "\n",
    "r1, r2 = 10, 30\n",
    "f = 1.1 # border factor around top plots\n",
    "\n",
    "k = 1\n",
    "for axi, n in zip(axs.T, [1, 2, 3, 5]):\n",
    "    axi[0].set_title(f'{n} turns')\n",
    "    circle_r1 = plt.Circle((0, 0), r1, color='black', fill=False)\n",
    "    circle_r2 = plt.Circle((0, 0), r2, color='black', fill=False)\n",
    "    axi[0].add_patch(circle_r1)\n",
    "    axi[0].add_patch(circle_r2)\n",
    "    axi[0].set_xlim([-r2*f, r2*f])\n",
    "    axi[0].set_ylim([-r2*f, r2*f])\n",
    "    axi[0].set_aspect('equal')\n",
    "    t = np.linspace(0, 1, 1000)\n",
    "    phi = 2*np.pi*n*t\n",
    "    r = r1 + (r2 - r1)*t\n",
    "\n",
    "    for i in range(k):\n",
    "        phase = i*2*np.pi/k\n",
    "        color = plt.cm.viridis(i/(k-1) if k > 1 else 0)\n",
    "        x = r*np.cos(phi + phase)\n",
    "        y = -r*np.sin(phi + phase)\n",
    "        axi[0].plot(x, y, color=color)\n",
    "        axi[0].scatter(x[[0, -1]], y[[0, -1]], marker='.', color='black', zorder=2)\n",
    "\n",
    "    axi[1].set_xlim([0, 2*np.pi])\n",
    "    axi[1].set_ylim([r1, r2])\n",
    "\n",
    "    for i in range(k):\n",
    "        phase = i*2*np.pi/k\n",
    "        color = plt.cm.viridis(i/(k-1) if k > 1 else 0)\n",
    "        phi_m = (phi + phase) % (2*np.pi)\n",
    "        discontinuities, = np.where(phi_m[1:] < phi_m[:-1])\n",
    "        phi_m = np.insert(phi_m, discontinuities + 1, np.nan)\n",
    "        r_m = np.insert(r, discontinuities + 1, np.nan)\n",
    "        axi[1].plot(phi_m, r_m, color=color)\n",
    "        axi[1].set_xlabel(r'$\\phi$')\n",
    "    \n",
    "    ticks = np.linspace(0, 2*np.pi, 5)\n",
    "    axi[1].set_xticks(ticks)\n",
    "    def fmt_angle(a):\n",
    "        a /= np.pi\n",
    "        k = 128\n",
    "        n = round(a*k)\n",
    "        f = math.gcd(n, k)\n",
    "        n, k = n//f, k//f\n",
    "        if n == 0:\n",
    "            return '$0$'\n",
    "        elif n == 1:\n",
    "            n = ''\n",
    "\n",
    "        if k == 1:\n",
    "            return fr'${n}\\pi$'\n",
    "        else:\n",
    "            return fr'$\\frac{{{n}\\pi}}{{{k}}}$'\n",
    "        \n",
    "    axi[1].set_xticklabels(map(fmt_angle, ticks))\n",
    "\n",
    "axs[0][0].set_xlabel('x')\n",
    "axs[0][0].set_ylabel('y')\n",
    "\n",
    "axs[1][0].set_ylabel('r')\n",
    "\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "6b6f8204-fd90-41b9-b0c7-a48ee9db5922",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 290316 characters]",
      "text/plain": [
       "<Figure size 1200x600 with 8 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, axs = plt.subplots(2, 4, figsize=(12, 6))\n",
    "\n",
    "r1, r2 = 10, 30\n",
    "f = 1.1 # border factor around top plots\n",
    "\n",
    "n = 2\n",
    "for axi, k in zip(axs.T, [1, 2, 3, 5]):\n",
    "    axi[0].set_title(f'{k} spirals')\n",
    "    circle_r1 = plt.Circle((0, 0), r1, color='black', fill=False)\n",
    "    circle_r2 = plt.Circle((0, 0), r2, color='black', fill=False)\n",
    "    axi[0].add_patch(circle_r1)\n",
    "    axi[0].add_patch(circle_r2)\n",
    "    axi[0].set_xlim([-r2*f, r2*f])\n",
    "    axi[0].set_ylim([-r2*f, r2*f])\n",
    "    axi[0].set_aspect('equal')\n",
    "    t = np.linspace(0, 1, 1000)\n",
    "    phi = 2*np.pi*n*t\n",
    "    r = r1 + (r2 - r1)*t\n",
    "\n",
    "    for i in range(k):\n",
    "        phase = i*2*np.pi/k\n",
    "        color = plt.cm.viridis(i/(k-1) if k > 1 else 0)\n",
    "        x = r*np.cos(phi + phase)\n",
    "        y = -r*np.sin(phi + phase)\n",
    "        axi[0].plot(x, y, color=color)\n",
    "        axi[0].scatter(x[[0, -1]], y[[0, -1]], marker='.', color='black', zorder=2)\n",
    "\n",
    "    axi[1].set_xlim([0, 2*np.pi])\n",
    "    axi[1].set_ylim([r1, r2])\n",
    "\n",
    "    for i in range(k):\n",
    "        phase = i*2*np.pi/k\n",
    "        color = plt.cm.viridis(i/(k-1) if k > 1 else 0)\n",
    "        phi_m = (phi + phase) % (2*np.pi)\n",
    "        discontinuities, = np.where(phi_m[1:] < phi_m[:-1])\n",
    "        phi_m = np.insert(phi_m, discontinuities + 1, np.nan)\n",
    "        r_m = np.insert(r, discontinuities + 1, np.nan)\n",
    "        axi[1].plot(phi_m, r_m, color=color)\n",
    "        axi[1].set_xlabel(r'$\\phi$')\n",
    "    \n",
    "    ticks = np.linspace(0, 2*np.pi, 5)\n",
    "    axi[1].set_xticks(ticks)\n",
    "    def fmt_angle(a):\n",
    "        a /= np.pi\n",
    "        k = 128\n",
    "        n = round(a*k)\n",
    "        f = math.gcd(n, k)\n",
    "        n, k = n//f, k//f\n",
    "        if n == 0:\n",
    "            return '$0$'\n",
    "        elif n == 1:\n",
    "            n = ''\n",
    "\n",
    "        if k == 1:\n",
    "            return fr'${n}\\pi$'\n",
    "        else:\n",
    "            return fr'$\\frac{{{n}\\pi}}{{{k}}}$'\n",
    "        \n",
    "    axi[1].set_xticklabels(map(fmt_angle, ticks))\n",
    "\n",
    "axs[0][0].set_xlabel('x')\n",
    "axs[0][0].set_ylabel('y')\n",
    "\n",
    "axs[1][0].set_ylabel('r')\n",
    "\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "0a0c9ef0-0bda-4c4a-bddd-6a8dd1b3014f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 161308 characters]",
      "text/plain": [
       "<Figure size 1200x600 with 8 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, axs = plt.subplots(2, 4, figsize=(12, 6))\n",
    "\n",
    "r1, r2 = 10, 30\n",
    "f = 1.1 # border factor around top plots\n",
    "\n",
    "for axi, (n, k) in zip(axs.T, [(1, 1), (1, 2), (2, 1), (3, 2)]):\n",
    "    axi[0].set_title(f'{k} spirals')\n",
    "    circle_r1 = plt.Circle((0, 0), r1, color='black', fill=False)\n",
    "    circle_r2 = plt.Circle((0, 0), r2, color='black', fill=False)\n",
    "    axi[0].add_patch(circle_r1)\n",
    "    axi[0].add_patch(circle_r2)\n",
    "    axi[0].set_xlim([-r2*f, r2*f])\n",
    "    axi[0].set_ylim([-r2*f, r2*f])\n",
    "    axi[0].set_aspect('equal')\n",
    "\n",
    "    k *= 2\n",
    "    n /= k\n",
    "    \n",
    "    t = np.linspace(0, k, 1000)\n",
    "    t_mod = t%2-1\n",
    "    phi = 2*np.pi*n*t\n",
    "    r = r1 + (r2 - r1)*np.abs(t_mod)\n",
    "\n",
    "    x = r*np.cos(phi)\n",
    "    y = -r*np.sin(phi)\n",
    "    axi[0].plot(x, y)\n",
    "    axi[0].scatter(x[[0, -1]], y[[0, -1]], marker='.', color='black', zorder=2)\n",
    "\n",
    "    axi[1].set_xlim([0, 2*np.pi])\n",
    "    axi[1].set_ylim([-1, 1])\n",
    "\n",
    "    phi_m = phi % (2*np.pi)\n",
    "    discontinuities, = np.where((phi_m[1:] < phi_m[:-1]) | (t_mod[1:] < t_mod[:-1]))\n",
    "    phi_m = np.insert(phi_m, discontinuities + 1, np.nan)\n",
    "    r_m = np.insert(t_mod, discontinuities + 1, np.nan)\n",
    "    axi[1].plot(phi_m, r_m)\n",
    "    axi[1].set_xlabel(r'$\\phi$')\n",
    "    \n",
    "    ticks = np.linspace(0, 2*np.pi, 5)\n",
    "    axi[1].set_xticks(ticks)\n",
    "        \n",
    "    axi[1].set_xticklabels(map(fmt_angle, ticks))\n",
    "\n",
    "axs[0][0].set_xlabel('x')\n",
    "axs[0][0].set_ylabel('y')\n",
    "\n",
    "axs[1][0].set_ylabel('r')\n",
    "\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "915fdab0-e361-4061-a73d-6df09e34c98e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 191204 characters]",
      "text/plain": [
       "<Figure size 1200x600 with 8 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, axs = plt.subplots(2, 4, figsize=(12, 6))\n",
    "\n",
    "r1, r2 = 10, 30\n",
    "f = 1.1 # border factor around top plots\n",
    "\n",
    "for axi, (n, k) in zip(axs.T, [(2, 1), (3, 2), (4, 3), (5, 4)]):\n",
    "    axi[0].set_title(f'{n} turns, {k} reflections')\n",
    "    circle_r1 = plt.Circle((0, 0), r1, color='black', fill=False)\n",
    "    circle_r2 = plt.Circle((0, 0), r2, color='black', fill=False)\n",
    "    axi[0].add_patch(circle_r1)\n",
    "    axi[0].add_patch(circle_r2)\n",
    "    axi[0].set_xlim([-r2*f, r2*f])\n",
    "    axi[0].set_ylim([-r2*f, r2*f])\n",
    "    axi[0].set_aspect('equal')\n",
    "\n",
    "    k *= 2\n",
    "    n /= k\n",
    "    \n",
    "    t = np.linspace(0, k, 1000)\n",
    "    t_mod = -(t%2-1)\n",
    "    phi = 2*np.pi*n*t\n",
    "    r = r1 + (r2 - r1)*np.abs(t_mod)\n",
    "\n",
    "    x = r*np.cos(phi)\n",
    "    y = -r*np.sin(phi)\n",
    "    axi[0].plot(x, y)\n",
    "    axi[0].scatter(x[[0, -1]], y[[0, -1]], marker='.', color='black', zorder=2)\n",
    "\n",
    "    axi[1].set_xlim([0, 2*np.pi])\n",
    "    axi[1].set_ylim([-1, 1])\n",
    "    # matplotlib aspect ratio calc is based on absolute axis values, not normalized to displayed range.\n",
    "    # y axis has range [-1, 1] -> |2|, x axis has range [0, 2π] -> |2π|, 2π/2 = π\n",
    "    axi[1].set_aspect(np.pi)\n",
    "\n",
    "    axi[1].axhline(y=0, color='black', alpha=0.4, linewidth=1)\n",
    "\n",
    "    phi_m = phi % (2*np.pi)\n",
    "    discontinuities, = np.where((phi_m[1:] < phi_m[:-1]) | (t_mod[1:] > t_mod[:-1]))\n",
    "    phi_m = np.insert(phi_m, discontinuities + 1, np.nan)\n",
    "    r_m = np.insert(t_mod, discontinuities + 1, np.nan)\n",
    "    axi[1].plot(phi_m, r_m)\n",
    "    axi[1].set_xlabel(r'$\\phi$')\n",
    "    \n",
    "    ticks = np.linspace(0, 2*np.pi, 5)\n",
    "    axi[1].set_xticks(ticks)\n",
    "    axi[1].set_xticklabels(map(fmt_angle, ticks))\n",
    "\n",
    "axs[0][0].set_xlabel('x')\n",
    "axs[0][0].set_ylabel('y')\n",
    "\n",
    "axs[1][0].set_ylabel('r')\n",
    "\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "5dfc3fd9-6966-4760-b168-d18469788df2",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 135640 characters]",
      "text/plain": [
       "<Figure size 1200x600 with 8 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, axs = plt.subplots(2, 4, figsize=(12, 6))\n",
    "\n",
    "r1, r2 = 10, 30\n",
    "f = 1.1 # border factor around top plots\n",
    "\n",
    "for axi, (n, k) in zip(axs.T, [(1, 1), (1, 2), (2, 1), (3, 2)]):\n",
    "    axi[0].set_title(f'{n} turns, {k} reflections')\n",
    "    circle_r1 = plt.Circle((0, 0), r1, color='black', fill=False)\n",
    "    circle_r2 = plt.Circle((0, 0), r2, color='black', fill=False)\n",
    "    axi[0].add_patch(circle_r1)\n",
    "    axi[0].add_patch(circle_r2)\n",
    "    axi[0].set_xlim([-r2*f, r2*f])\n",
    "    axi[0].set_ylim([-r2*f, r2*f])\n",
    "    axi[0].set_aspect('equal')\n",
    "\n",
    "    k *= 2\n",
    "    n /= k\n",
    "    \n",
    "    t = np.linspace(0, k, 1000)\n",
    "    t_mod = -(t%2-1)\n",
    "    phi = 2*np.pi*n*t\n",
    "    r = r1 + (r2 - r1)*np.abs(t_mod)\n",
    "\n",
    "    x = r*np.cos(phi)\n",
    "    y = -r*np.sin(phi)\n",
    "    axi[0].plot(x, y)\n",
    "    axi[0].scatter(x[[0, -1]], y[[0, -1]], marker='.', color='black', zorder=2)\n",
    "\n",
    "    axi[1].set_xlim([0, 2*np.pi])\n",
    "    axi[1].set_ylim([-1, 1])\n",
    "    # matplotlib aspect ratio calc is based on absolute axis values, not normalized to displayed range.\n",
    "    # y axis has range [-1, 1] -> |2|, x axis has range [0, 2π] -> |2π|, 2π/2 = π\n",
    "    axi[1].set_aspect(np.pi)\n",
    "\n",
    "    axi[1].axhline(y=0, color='black', alpha=0.4, linewidth=1)\n",
    "\n",
    "    phi_m = phi % (2*np.pi)\n",
    "    discontinuities, = np.where((phi_m[1:] < phi_m[:-1]) | (t_mod[1:] > t_mod[:-1]))\n",
    "    phi_m = np.insert(phi_m, discontinuities + 1, np.nan)\n",
    "    r_m = np.insert(t_mod, discontinuities + 1, np.nan)\n",
    "    axi[1].plot(phi_m, r_m)\n",
    "    axi[1].set_xlabel(r'$\\phi$')\n",
    "    \n",
    "    ticks = np.linspace(0, 2*np.pi, 5)\n",
    "    axi[1].set_xticks(ticks)\n",
    "    axi[1].set_xticklabels(map(fmt_angle, ticks))\n",
    "\n",
    "axs[0][0].set_xlabel('x')\n",
    "axs[0][0].set_ylabel('y')\n",
    "\n",
    "axs[1][0].set_ylabel('r')\n",
    "\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "be552943-be95-42dc-bc3f-363e678c5309",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 116800 characters]",
      "text/plain": [
       "<Figure size 1200x600 with 8 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, axs = plt.subplots(2, 4, figsize=(12, 6))\n",
    "\n",
    "r1, r2 = 10, 30\n",
    "f = 1.1 # border factor around top plots\n",
    "\n",
    "for axi, (n, k) in zip(axs.T, [(1, 1), (1, 2), (2, 1), (2, 3)]):\n",
    "    axi[0].set_title(f'{n} turn(s), {k} trace pair(s)')\n",
    "    circle_r1 = plt.Circle((0, 0), r1, color='black', fill=False)\n",
    "    circle_r2 = plt.Circle((0, 0), r2, color='black', fill=False)\n",
    "    axi[0].add_patch(circle_r1)\n",
    "    axi[0].add_patch(circle_r2)\n",
    "    axi[0].set_xlim([-r2*f, r2*f])\n",
    "    axi[0].set_ylim([-r2*f, r2*f])\n",
    "    axi[0].set_aspect('equal')\n",
    "    \n",
    "    t = np.linspace(0, 1, 1000)\n",
    "    t_n = 2*k*t\n",
    "    t_mod = -(t_n%2-1)\n",
    "    phi = 2*np.pi*n/(2*k)*t_n\n",
    "    r = r1 + (r2 - r1)*np.abs(t_mod)\n",
    "\n",
    "    x = r*np.cos(phi)\n",
    "    y = -r*np.sin(phi)\n",
    "\n",
    "    current_segment = []\n",
    "    current_t = None\n",
    "    for xi, yi, ti in zip(x, y, t_mod):\n",
    "        if current_t is None:\n",
    "            current_t = ti\n",
    "        \n",
    "        if np.sign(current_t) == np.sign(ti):\n",
    "            current_segment.append((xi, yi))\n",
    "            \n",
    "        else:\n",
    "            color = 'red' if ti >= 0 else 'blue'\n",
    "            xy = np.array(current_segment).T\n",
    "            axi[0].plot(xy[0], xy[1], color=color)\n",
    "            \n",
    "            current_segment = [tuple(xy[:,-1])]\n",
    "            current_t = ti\n",
    "    \n",
    "    axi[0].scatter(x[[0, -1]], y[[0, -1]], marker='.', color='black', zorder=2)\n",
    "\n",
    "    axi[1].set_xlim([0, 2*np.pi])\n",
    "    axi[1].set_ylim([-1, 1])\n",
    "    # matplotlib aspect ratio calc is based on absolute axis values, not normalized to displayed range.\n",
    "    # y axis has range [-1, 1] -> |2|, x axis has range [0, 2π] -> |2π|, 2π/2 = π\n",
    "    axi[1].set_aspect(np.pi)\n",
    "\n",
    "    axi[1].axhline(y=0, color='black', alpha=0.4, linewidth=1)\n",
    "\n",
    "    phi_m = phi % (2*np.pi)\n",
    "    discontinuities, = np.where((phi_m[1:] < phi_m[:-1]) | (t_mod[1:] >  t_mod[:-1]))\n",
    "    phi_m = np.insert(phi_m, discontinuities + 1, np.nan)\n",
    "    r_m = np.insert(t_mod, discontinuities + 1, np.nan)\n",
    "    axi[1].plot(phi_m, r_m, color='black')\n",
    "    axi[1].set_xlabel(r'$\\phi$')\n",
    "    \n",
    "    ticks = np.linspace(0, 2*np.pi, 5)\n",
    "    axi[1].set_xticks(ticks)\n",
    "    axi[1].set_xticklabels(map(fmt_angle, ticks))\n",
    "\n",
    "axs[0][0].set_xlabel('x')\n",
    "axs[0][0].set_ylabel('y')\n",
    "\n",
    "axs[1][0].set_ylabel('r')\n",
    "\n",
    "axs[1][1].axvline(x=np.pi, color='black', alpha=0.4, linewidth=1)\n",
    "\n",
    "axs[1][3].axvline(x=2*np.pi/3, color='black', alpha=0.4, linewidth=1)\n",
    "axs[1][3].axvline(x=4*np.pi/3, color='black', alpha=0.4, linewidth=1)\n",
    "\n",
    "fig.tight_layout()\n",
    "fig.savefig('paper/figures/nk_simple_illust.pdf')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "id": "90f389af-0acb-461c-bc78-aa9797e4ef3b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 67332 characters]",
      "text/plain": [
       "<Figure size 400x400 with 8 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, axs = plt.subplots(2, 2, figsize=(4, 4), layout='constrained')\n",
    "\n",
    "r1, r2 = 10, 30\n",
    "f = 1.1 # border factor around top plots\n",
    "\n",
    "for axi, (n, k) in zip(axs.T, [(2, 3), (5, 4)]):\n",
    "    axi[0].set_title(f'{n} turn(s),\\n{k} trace pair(s)', fontsize=8)\n",
    "    circle_r1 = plt.Circle((0, 0), r1, color='black', fill=False)\n",
    "    circle_r2 = plt.Circle((0, 0), r2, color='black', fill=False)\n",
    "    axi[0].add_patch(circle_r1)\n",
    "    axi[0].add_patch(circle_r2)\n",
    "    axi[0].set_xlim([-r2*f, r2*f])\n",
    "    axi[0].set_ylim([-r2*f, r2*f])\n",
    "    axi[0].set_aspect('equal')\n",
    "    \n",
    "    t = np.linspace(0, 1, 1000)\n",
    "    t_n = 2*k*t\n",
    "    t_mod = -(t_n%2-1)\n",
    "    phi = 2*np.pi*n/(2*k)*t_n\n",
    "    r = r1 + (r2 - r1)*np.abs(t_mod)\n",
    "\n",
    "    x = r*np.cos(phi)\n",
    "    y = -r*np.sin(phi)\n",
    "\n",
    "    current_segment = []\n",
    "    current_t = None\n",
    "    for xi, yi, ti in zip(x, y, t_mod):\n",
    "        if current_t is None:\n",
    "            current_t = ti\n",
    "        \n",
    "        if np.sign(current_t) == np.sign(ti):\n",
    "            current_segment.append((xi, yi))\n",
    "            \n",
    "        else:\n",
    "            color = 'red' if ti >= 0 else 'blue'\n",
    "            xy = np.array(current_segment).T\n",
    "            axi[0].plot(xy[0], xy[1], color=color)\n",
    "            \n",
    "            current_segment = [tuple(xy[:,-1])]\n",
    "            current_t = ti\n",
    "    \n",
    "    axi[0].scatter(x[[0, -1]], y[[0, -1]], marker='.', color='black', zorder=2)\n",
    "\n",
    "    for i in range(n):\n",
    "        axi[1].axhline(y=i*2/n-1, color='black', alpha=0.4, linewidth=1)\n",
    "\n",
    "    phi_m = phi % (2*np.pi)\n",
    "    discontinuities, = np.where((phi_m[1:] < phi_m[:-1]) | (t_mod[1:] >  t_mod[:-1]))\n",
    "    phi_m = np.insert(phi_m, discontinuities + 1, np.nan)\n",
    "    r_m = np.insert(t_mod, discontinuities + 1, np.nan)\n",
    "    axi[1].plot(phi_m, r_m, color='black')\n",
    "    axi[1].set_xlabel(r'$\\varphi$', labelpad=1)\n",
    "    \n",
    "    ticks = np.linspace(0, 2*np.pi, k+1)\n",
    "    axi[1].set_xticks(ticks)\n",
    "    axi[1].set_xticklabels(map(fmt_angle, ticks))\n",
    "\n",
    "    tx = axi[1].twinx()\n",
    "    ticks = np.arange(n+1)\n",
    "    tx.set_yticks(ticks)\n",
    "    ticks = list(map(str, reversed(ticks)))\n",
    "    ticks[0] = f'n={n}'\n",
    "    tx.set_yticklabels(ticks)\n",
    "    tx.set_ylim([0, n])\n",
    "\n",
    "    ty = axi[1].twiny()\n",
    "    ticks = np.arange(k+1)\n",
    "    ty.set_xticks(ticks)\n",
    "    ticks = list(map(str, ticks))\n",
    "    ticks[-1] = f'k={k}'\n",
    "    ty.set_xticklabels(ticks)\n",
    "    ty.set_xlim([0, k])\n",
    "    ty.set_xlabel('$j$', labelpad=4)\n",
    "\n",
    "    axi[1].set_xlim([0, 2*np.pi])\n",
    "    axi[1].set_ylim([-1, 1])\n",
    "    \n",
    "    for i in range(k):\n",
    "        axi[1].axvline(x=i*2*np.pi/k, color='black', alpha=0.4, linewidth=1)\n",
    "    \n",
    "    ty.spines['top'].set_color('red')\n",
    "    ty.spines['right'].set_color('red')\n",
    "    tx.yaxis.label.set_color('red')\n",
    "    tx.tick_params(axis='y', colors='red')\n",
    "    ty.xaxis.label.set_color('red')\n",
    "    ty.tick_params(axis='x', colors='red')\n",
    "    \n",
    "    #axi[0].set_xlabel('x', labelpad=1)\n",
    "    \n",
    "#axs[0][0].set_ylabel('y', rotation='horizontal', labelpad=1)\n",
    "axs[1][0].set_ylabel('r', rotation='horizontal', labelpad=1)\n",
    "tx.set_ylabel('$i$', rotation='horizontal', labelpad=1)\n",
    "\n",
    "#fig.tight_layout()\n",
    "fig.savefig('paper/figures/nk_chinese_remainder_illust.pdf')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "f5ce5169-3bf3-43ee-a1d0-5e3e72f6ae25",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 66756 characters]",
      "text/plain": [
       "<Figure size 600x250 with 3 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, axs = plt.subplots(1, 3, figsize=(6, 2.5), sharey=True)\n",
    "\n",
    "r1, r2 = 10, 30\n",
    "f = 1.1 # border factor around top plots\n",
    "\n",
    "for axi, (n, k), extra_title in zip(axs.T, [(3, 0.5), (3, 1), (3, 2)], ['Simple Planar Spiral', 'Two-Layer Planar Spiral', 'Twisted Inductor']):\n",
    "    axi = [axi]\n",
    "    axi[0].set_title(f'{plural(n, 'turn')}\\n{plural(round(k), 'trace pair')}\\n{extra_title}', fontsize=10)\n",
    "    circle_r1 = plt.Circle((0, 0), r1, color='black', fill=False)\n",
    "    circle_r2 = plt.Circle((0, 0), r2, color='black', fill=False)\n",
    "    axi[0].add_patch(circle_r1)\n",
    "    axi[0].add_patch(circle_r2)\n",
    "    axi[0].set_xlim([-r2*f, r2*f])\n",
    "    axi[0].set_ylim([-r2*f, r2*f])\n",
    "    axi[0].set_aspect('equal')\n",
    "    \n",
    "    t = np.linspace(0, 1, 1000)\n",
    "    t_n = 2*k*t\n",
    "    t_mod = -(t_n%2-1)\n",
    "    phi = 2*np.pi*n/(2*k)*t_n\n",
    "    r = r1 + (r2 - r1)*np.abs(t_mod)\n",
    "\n",
    "    x = r*np.cos(phi)\n",
    "    y = -r*np.sin(phi)\n",
    "\n",
    "    current_segment = []\n",
    "    current_t = None\n",
    "    for xi, yi, ti in zip(x, y, t_mod):\n",
    "        if current_t is None:\n",
    "            current_t = ti\n",
    "        \n",
    "        if np.sign(current_t) == np.sign(ti):\n",
    "            current_segment.append((xi, yi))\n",
    "            \n",
    "        else:\n",
    "            color = 'red' if ti >= 0 else 'blue'\n",
    "            xy = np.array(current_segment).T\n",
    "            axi[0].plot(xy[0], xy[1], color=color)\n",
    "            \n",
    "            current_segment = [tuple(xy[:,-1])]\n",
    "            current_t = ti\n",
    "    \n",
    "    axi[0].scatter(x[[0, -1]], y[[0, -1]], marker='.', color='black', zorder=2)\n",
    "\n",
    "\n",
    "axs[0].set_xlabel('x')\n",
    "axs[1].set_xlabel('x')\n",
    "axs[0].set_ylabel('y', labelpad=-5)\n",
    "\n",
    "fig.tight_layout()\n",
    "fig.savefig('paper/figures/nk_interleave_illust.pdf')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "e56f803d-bd8e-41f6-a061-ab0af288d16a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 471188 characters]",
      "text/plain": [
       "<Figure size 1200x1200 with 16 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, axs = plt.subplots(4, 4, figsize=(12, 12))\n",
    "\n",
    "r1, r2 = 10, 30\n",
    "f = 1.1 # border factor around top plots\n",
    "\n",
    "for axj, k in zip(axs, [1, 2, 3, 5]):\n",
    "    for axi, n in zip(axj, [1, 2, 3, 5]):\n",
    "        axi.set_title(f'{n} turns, {k} reflections')\n",
    "        circle_r1 = plt.Circle((0, 0), r1, color='black', fill=False)\n",
    "        circle_r2 = plt.Circle((0, 0), r2, color='black', fill=False)\n",
    "        axi.add_patch(circle_r1)\n",
    "        axi.add_patch(circle_r2)\n",
    "        axi.set_xlim([-r2*f, r2*f])\n",
    "        axi.set_ylim([-r2*f, r2*f])\n",
    "        axi.set_aspect('equal')\n",
    "\n",
    "        coprime = math.gcd(k, n) == 1\n",
    "        kmul = k*2\n",
    "        n /= kmul\n",
    "        \n",
    "        t = np.linspace(0, kmul, 1000)\n",
    "        t_mod = -(t%2-1)\n",
    "        phi = 2*np.pi*n*t\n",
    "        r = r1 + (r2 - r1)*np.abs(t_mod)\n",
    "    \n",
    "        x = r*np.cos(phi)\n",
    "        y = -r*np.sin(phi)\n",
    "        #axi.plot(x, y)\n",
    "\n",
    "        current_segment = []\n",
    "        current_t = None\n",
    "        for xi, yi, ti in zip(x, y, t_mod):\n",
    "            if current_t is None:\n",
    "                current_t = ti\n",
    "            \n",
    "            if np.sign(current_t) == np.sign(ti):\n",
    "                current_segment.append((xi, yi))\n",
    "                \n",
    "            else:\n",
    "                color = 'red' if ti >= 0 else 'blue'\n",
    "                xy = np.array(current_segment).T\n",
    "                axi.plot(xy[0], xy[1], color=color)\n",
    "                \n",
    "                current_segment = [tuple(xy[:,-1])]\n",
    "                current_t = ti\n",
    "        \n",
    "        axi.scatter(x[[0, -1]], y[[0, -1]], marker='.', color='black', zorder=2)\n",
    "\n",
    "        if not coprime:\n",
    "            axi.axvspan(-100*r2, 100*r2, color='red', alpha=0.2)\n",
    "\n",
    "#axs[0][0].set_xlabel('x')\n",
    "#axs[0][0].set_ylabel('y')\n",
    "\n",
    "\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "88ea6509-bcc9-4462-845e-9f30a7bd9252",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 677976 characters]",
      "text/plain": [
       "<Figure size 1200x1200 with 16 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, axs = plt.subplots(4, 4, figsize=(12, 12))\n",
    "\n",
    "r1, r2 = 10, 30\n",
    "f = 1.1 # border factor around top plots\n",
    "\n",
    "for axj, k in zip(axs, [1, 3, 9, 13]):\n",
    "    for axi, n in zip(axj, [2, 5, 7, 11]):\n",
    "        axi.set_title(f'{plural(n, 'turn')}, {plural(k, 'trace pair')}')\n",
    "        circle_r1 = plt.Circle((0, 0), r1, color='black', fill=False)\n",
    "        circle_r2 = plt.Circle((0, 0), r2, color='black', fill=False)\n",
    "        axi.add_patch(circle_r1)\n",
    "        axi.add_patch(circle_r2)\n",
    "        axi.set_xlim([-r2*f, r2*f])\n",
    "        axi.set_ylim([-r2*f, r2*f])\n",
    "        axi.set_aspect('equal')\n",
    "\n",
    "        coprime = math.gcd(k, n) == 1\n",
    "        kmul = k*2\n",
    "        n /= kmul\n",
    "        \n",
    "        t = np.linspace(0, kmul, 3000)\n",
    "        t_mod = -(t%2-1)\n",
    "        phi = 2*np.pi*n*t\n",
    "        r = r1 + (r2 - r1)*np.abs(t_mod)\n",
    "    \n",
    "        x = r*np.cos(phi)\n",
    "        y = -r*np.sin(phi)\n",
    "        #axi.plot(x, y)\n",
    "\n",
    "        current_segment = []\n",
    "        current_t = None\n",
    "        for xi, yi, ti in zip(x, y, t_mod):\n",
    "            if current_t is None:\n",
    "                current_t = ti\n",
    "            \n",
    "            if np.sign(current_t) == np.sign(ti):\n",
    "                current_segment.append((xi, yi))\n",
    "                \n",
    "            else:\n",
    "                color = 'red' if ti >= 0 else 'blue'\n",
    "                xy = np.array(current_segment).T\n",
    "                axi.plot(xy[0], xy[1], color=color)\n",
    "                \n",
    "                current_segment = [tuple(xy[:,-1])]\n",
    "                current_t = ti\n",
    "        \n",
    "        axi.scatter(x[[0, -1]], y[[0, -1]], marker='.', color='black', zorder=2)\n",
    "\n",
    "        if not coprime:\n",
    "            axi.axvspan(-100*r2, 100*r2, color='red', alpha=0.2)\n",
    "\n",
    "#axs[0][0].set_xlabel('x')\n",
    "#axs[0][0].set_ylabel('y')\n",
    "\n",
    "\n",
    "fig.tight_layout()\n",
    "fig.savefig('paper/figures/nk_complex_illust.pdf')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "7a73480a-15a4-4be5-aa7a-2fda130fd637",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 888740 characters]",
      "text/plain": [
       "<Figure size 1200x1200 with 36 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, axs = plt.subplots(6, 6, figsize=(12, 12), sharex=True, sharey=True)\n",
    "\n",
    "r1, r2 = 10, 30\n",
    "f = 1.1 # border factor around top plots\n",
    "\n",
    "for axj, k in zip(axs, [1, 2, 3, 4, 5, 17]):\n",
    "    for axi, n in zip(axj, [1, 2, 3, 4, 5, 11]):\n",
    "        #axi.set_title(f'{n} turns, {k} reflections')\n",
    "        circle_r1 = plt.Circle((0, 0), r1, color='black', fill=False)\n",
    "        circle_r2 = plt.Circle((0, 0), r2, color='black', fill=False)\n",
    "        axi.add_patch(circle_r1)\n",
    "        axi.add_patch(circle_r2)\n",
    "        axi.set_xlim([-r2*f, r2*f])\n",
    "        axi.set_ylim([-r2*f, r2*f])\n",
    "        axi.set_aspect('equal')\n",
    "        axi.text(0, 0, f'n={n}\\nk={k}', horizontalalignment='center', verticalalignment='center')\n",
    "\n",
    "        coprime = math.gcd(k, n) == 1\n",
    "        kmul = k*2\n",
    "        n /= kmul\n",
    "        \n",
    "        t = np.linspace(0, kmul, 1000)\n",
    "        t_mod = -(t%2-1)\n",
    "        phi = 2*np.pi*n*t\n",
    "        r = r1 + (r2 - r1)*np.abs(t_mod)\n",
    "\n",
    "        x = r*np.cos(phi)\n",
    "        y = -r*np.sin(phi)\n",
    "        #axi.plot(x, y, color='red' if i%2 else 'blue')\n",
    "        axi.scatter(x[[0, -1]], y[[0, -1]], marker='.', color='black', zorder=2)\n",
    "\n",
    "        # This line collection thingy is slow and produces garbage output both on screen and as pdf/svg.\n",
    "        #points = np.vstack([x, y]).T\n",
    "        #segments = np.dstack([points[:-1], points[1:]])\n",
    "        #segments = np.swapaxes(segments, 1, 2)\n",
    "        #lc = matplotlib.collections.LineCollection(segments, cmap=matplotlib.colors.ListedColormap(['blue', 'red']))\n",
    "        #lc.set_array(t_mod)\n",
    "        #axi.add_collection(lc)\n",
    "\n",
    "        current_segment = []\n",
    "        current_t = None\n",
    "        for xi, yi, ti in zip(x, y, t_mod):\n",
    "            if current_t is None:\n",
    "                current_t = ti\n",
    "            \n",
    "            if np.sign(current_t) == np.sign(ti):\n",
    "                current_segment.append((xi, yi))\n",
    "                \n",
    "            else:\n",
    "                color = 'red' if ti >= 0 else 'blue'\n",
    "                xy = np.array(current_segment).T\n",
    "                axi.plot(xy[0], xy[1], color=color)\n",
    "                \n",
    "                current_segment = [tuple(xy[:,-1])]\n",
    "                current_t = ti\n",
    "\n",
    "        if not coprime:\n",
    "            axi.axvspan(-100*r2, 100*r2, color='red', alpha=0.2)\n",
    "\n",
    "#axs[0][0].set_xlabel('x')\n",
    "#axs[0][0].set_ylabel('y')\n",
    "\n",
    "#fig.savefig('/tmp/foo.svg')\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "414cabfa-e814-497a-95d7-cdb26d0c529b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 166776 characters]",
      "text/plain": [
       "<Figure size 1200x600 with 8 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, axs = plt.subplots(2, 4, figsize=(12, 6))\n",
    "\n",
    "r1, r2 = 10, 30\n",
    "f = 1.1 # border factor around top plots\n",
    "\n",
    "for axi, n in zip(axs.T, [1, 2, 3, 5]):\n",
    "    axi[0].set_title(f'{n} turns, {k} reflections')\n",
    "    circle_r1 = plt.Circle((0, 0), r1, color='black', fill=False)\n",
    "    circle_r2 = plt.Circle((0, 0), r2, color='black', fill=False)\n",
    "    axi[0].add_patch(circle_r1)\n",
    "    axi[0].add_patch(circle_r2)\n",
    "    axi[0].set_xlim([-r2*f, r2*f])\n",
    "    axi[0].set_ylim([-r2*f, r2*f])\n",
    "    axi[0].set_aspect('equal')\n",
    "    \n",
    "    t = np.linspace(0, 1, 1000)\n",
    "    phi = 2*np.pi*n*t\n",
    "    r_t = 1 - t\n",
    "    r = r1 + (r2 - r1)*r_t\n",
    "\n",
    "    x = r*np.cos(phi)\n",
    "    y = r*np.sin(phi)\n",
    "    axi[0].plot(x, y)\n",
    "    \n",
    "    axi[0].scatter(x[[0, -1]], y[[0, -1]], marker='.', color='black', zorder=2)\n",
    "\n",
    "    axi[1].set_xlim([0, 2*np.pi])\n",
    "    axi[1].set_ylim([0, 1])\n",
    "    # matplotlib aspect ratio calc is based on absolute axis values, not normalized to displayed range.\n",
    "    # y axis has range [-1, 1] -> |2|, x axis has range [0, 2π] -> |2π|, 2π/2 = π\n",
    "    axi[1].set_aspect(2*np.pi)\n",
    "\n",
    "    axi[1].axhline(y=0, color='black', alpha=0.4, linewidth=1)\n",
    "\n",
    "    phi_m = phi % (2*np.pi)\n",
    "    discontinuities, = np.where(phi_m[1:] < phi_m[:-1])\n",
    "    phi_m = np.insert(phi_m, discontinuities + 1, np.nan)\n",
    "    r_m = np.insert(r_t, discontinuities + 1, np.nan)\n",
    "    axi[1].plot(phi_m, r_m, color='black')\n",
    "    axi[1].set_xlabel(r'$\\phi$')\n",
    "    \n",
    "    ticks = np.linspace(0, 2*np.pi, 5)\n",
    "    axi[1].set_xticks(ticks)\n",
    "    axi[1].set_xticklabels(map(fmt_angle, ticks))\n",
    "\n",
    "axs[0][0].set_xlabel('x')\n",
    "axs[0][0].set_ylabel('y')\n",
    "\n",
    "axs[1][0].set_ylabel('r')\n",
    "\n",
    "axs[1][1].axvline(x=np.pi, color='black', alpha=0.4, linewidth=1)\n",
    "\n",
    "axs[1][3].axvline(x=2*np.pi/3, color='black', alpha=0.4, linewidth=1)\n",
    "axs[1][3].axvline(x=4*np.pi/3, color='black', alpha=0.4, linewidth=1)\n",
    "\n",
    "fig.tight_layout()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "d1daeb19-bb91-45ee-bc5d-f1ae7b8f8308",
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_colorseg(axi, x, y, t_mod):\n",
    "    current_segment = []\n",
    "    current_t = None\n",
    "    for xi, yi, ti in zip(x, y, t_mod):\n",
    "        if current_t is None:\n",
    "            current_t = ti\n",
    "        \n",
    "        if np.sign(current_t) == np.sign(ti):\n",
    "            current_segment.append((xi, yi))\n",
    "            \n",
    "        else:\n",
    "            color = 'red' if ti < 0 else 'blue'\n",
    "            xy = np.array(current_segment).T\n",
    "            axi.plot(xy[0], xy[1], color=color)\n",
    "            \n",
    "            current_segment = [tuple(xy[:,-1])]\n",
    "            current_t = ti\n",
    "    \n",
    "    color = 'red' if current_t > 0 else 'blue'\n",
    "    xy = np.array(current_segment).T\n",
    "    axi.plot(xy[0], xy[1], color=color)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "79411405-c80d-4c79-94a7-ff52b4879340",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 35824 characters]",
      "text/plain": [
       "<Figure size 400x220 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, axs = plt.subplots(1, 2, figsize=(4, 2.2), sharey=True)\n",
    "\n",
    "r1, r2 = 10, 30\n",
    "f = 1.1 # border factor around top plots\n",
    "n = 3\n",
    "\n",
    "t = np.linspace(0, 1, 1000)\n",
    "r_t = 1 - t\n",
    "phi = 2*np.pi*n*t\n",
    "r = r1 + (r2 - r1)*r_t\n",
    "\n",
    "x = r*np.cos(phi)\n",
    "y = r*np.sin(phi)\n",
    "axs[0].plot([r1, r2], [0, 0], color='blue')\n",
    "axs[0].plot(x, y, color='red')\n",
    "\n",
    "t = np.linspace(0, 1, 1000)\n",
    "t_m = 1 - 2*t\n",
    "phi = 2*np.pi*n*t\n",
    "r = r1 + (r2 - r1)*np.abs(t_m)\n",
    "\n",
    "x = r*np.cos(phi)\n",
    "y = r*np.sin(phi)\n",
    "plot_colorseg(axs[1], x, y, t_m)\n",
    "\n",
    "axs[0].scatter(x[[0, -1]], y[[0, -1]], marker='.', color='black', zorder=2)\n",
    "\n",
    "axs[0].set_xlabel('x')\n",
    "axs[1].set_xlabel('x')\n",
    "axs[0].set_ylabel('y')\n",
    "\n",
    "for i in range(2):\n",
    "    circle_r1 = plt.Circle((0, 0), r1, color='black', fill=False)\n",
    "    circle_r2 = plt.Circle((0, 0), r2, color='black', fill=False)\n",
    "    axs[i].add_patch(circle_r1)\n",
    "    axs[i].add_patch(circle_r2)\n",
    "    axs[i].set_xlim([-r2*f, r2*f])\n",
    "    axs[i].set_ylim([-r2*f, r2*f])\n",
    "    axs[i].set_aspect('equal')\n",
    "\n",
    "fig.tight_layout()\n",
    "fig.savefig('paper/figures/twolayer_spiral.pdf')"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
