{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "051c8f59-a034-447f-80f5-d6d43310a241",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from scipy import fft\n",
    "from scipy import signal as sig\n",
    "from scipy import interpolate as interp\n",
    "from io import StringIO\n",
    "from matplotlib import pyplot as plt\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "85426504-2fd6-4544-a800-2c17f1eb2853",
   "metadata": {},
   "outputs": [],
   "source": [
    "def load_keysight_csv(fn):\n",
    "    with open(fn, 'r') as f:\n",
    "        header = {}\n",
    "        while (line := f.readline().strip()) != 'DATA':\n",
    "            if ',' in line:\n",
    "                key, _, value = line.partition(',')\n",
    "                header[key] = value\n",
    "        data = f.read()\n",
    "        data = np.genfromtxt(StringIO(data), delimiter=',')\n",
    "        return header, data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "0d48166b-a117-4221-bcfb-97563bac0b61",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Fundamental peak: 7996500.0 -27.0907135009766\n",
      "Frequency spacing is 49.973751 kHz\n",
      "3 dB frequency from spline: 0.530 GHz, equiv t_r = 0.660 ns\n",
      "Reconstruction sample spacing: 0.025 ns\n",
      "edge 4985:5009: t_r=0.600 ns, equiv f_3dB = 0.583 GHz\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 117960 characters]",
      "text/plain": [
       "<Figure size 1200x900 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "def plot_freq(fn):\n",
    "    header_amp, data_amp = load_keysight_csv(fn)\n",
    "    freq = data_amp[:,0]\n",
    "    amp = data_amp[:,1]\n",
    "    start_freq = float(header_amp['Start Frequency'])\n",
    "    stop_freq = float(header_amp['Stop Frequency'])\n",
    "    freq_spacing = (stop_freq - start_freq) / float(header_amp['Number of Points'])\n",
    "    \n",
    "    peaks, props = sig.find_peaks(amp, threshold=40)\n",
    "    fundamental_idx = peaks[0]\n",
    "    print('Fundamental peak:', freq[fundamental_idx], amp[fundamental_idx])\n",
    "    \n",
    "    fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 9))\n",
    "    ax1.plot(freq/1e6, amp)\n",
    "    ax1.set_xlabel('f [MHz]')\n",
    "    ax1.set_ylabel('[dB]')\n",
    "    ax1.annotate(f'Fundamental @{freq[fundamental_idx]/1e6:.3f} Mhz, {amp[fundamental_idx]:.2f} dB',\n",
    "                (freq[fundamental_idx]/1e6, amp[fundamental_idx]),\n",
    "                xytext=(5, 0),\n",
    "                xycoords='data',\n",
    "                textcoords='offset pixels',\n",
    "               )\n",
    "    \n",
    "    freq_space = np.linspace(freq[fundamental_idx], stop_freq, 1000)\n",
    "    sq_amp = np.sqrt(10**(amp[fundamental_idx]/10))\n",
    "    sq_approx = 10*np.log10((sq_amp / (freq_space/freq[fundamental_idx]))**2)\n",
    "    ax1.plot(freq_space/1e6, sq_approx)\n",
    "\n",
    "    print(f'Frequency spacing is {freq_spacing/1e3:.6f} kHz')\n",
    "\n",
    "    peaks, props = sig.find_peaks(amp, threshold=20, prominence=40, )\n",
    "    peaks = [idx for idx in peaks if amp[idx] - 10*np.log10((sq_amp / (freq[idx]/freq[fundamental_idx]))**2) > -10]\n",
    "    peak_freq = freq[peaks]\n",
    "    peak_amp = amp[peaks]\n",
    "    spl = interp.make_smoothing_spline(peak_freq, peak_amp, lam=1e20)\n",
    "    ax1.scatter(peak_freq/1e6, peak_amp, color='red')\n",
    "    freq_space_cut = freq_space[:np.argmin(freq_space < max(peak_freq))]\n",
    "    ax1.plot(freq_space_cut/1e6, spl(freq_space_cut))\n",
    "    kc = [(freq[idx]/freq[fundamental_idx], np.sqrt(10**(amp[idx]/10))) for idx in peaks]\n",
    "\n",
    "    idx_3db = np.argmin(abs(spl(freq_space_cut) - sq_approx[:len(freq_space_cut)]) < 3.00)\n",
    "    print(f'3 dB frequency from spline: {freq_space[idx_3db]/1e9:.3f} GHz, equiv t_r = {0.35 / freq_space[idx_3db] * 1e9:.3f} ns')\n",
    "    \n",
    "    t = np.linspace(0, 2, 10000)\n",
    "    ts = 1/freq[fundamental_idx]\n",
    "    tf = t * ts\n",
    "    td_data = 4/np.pi * sum(c * np.sin(2*np.pi*k*t) for k, c in kc)\n",
    "    ax2.plot(tf, td_data)\n",
    "    \n",
    "    lvl_high = kc[0][1]*0.8\n",
    "    lvl_low = kc[0][1]*-0.8\n",
    "    ax2.axhline(lvl_low)\n",
    "    ax2.axhline(lvl_high)\n",
    "    print(f'Reconstruction sample spacing: {(tf[1]-tf[0])*1e9:.3f} ns')\n",
    "\n",
    "    state = None\n",
    "    edge_start, edge_end = None, None\n",
    "    for i, (dp, dn) in enumerate(zip(td_data, td_data[1:])):\n",
    "        if dp <= lvl_low and dn >= lvl_low:\n",
    "            if state != 'edge':\n",
    "                state = 'edge'\n",
    "                edge_start = i\n",
    "        elif dp <= lvl_high and dn >= lvl_high:\n",
    "            if state == 'edge':\n",
    "                edge_end = i\n",
    "                tr = tf[edge_end]-tf[edge_start]\n",
    "                print(f'edge {edge_start}:{edge_end}: t_r={tr*1e9:.3f} ns, equiv f_3dB = {0.35/tr/1e9:.3f} GHz')\n",
    "            state = 'high'\n",
    "        elif dp <= lvl_low and dn <= lvl_low:\n",
    "            state = 'low'\n",
    "        elif dp >= lvl_high and dn >= lvl_high:\n",
    "            state = 'high'\n",
    "\n",
    "    #fft_data = np.hstack([\n",
    "    #    np.zeros(round(start_freq / freq_spacing)),\n",
    "    #    10**(amp/20)])\n",
    "    #fft_data = np.hstack([fft_data, fft_data[1::-1]])\n",
    "    \n",
    "    #time_domain_amp = fft.ifft(fft_data)\n",
    "    \n",
    "    #ax2.plot(time_domain_amp)\n",
    "    #ax2.set_xlim([1000, 4000])\n",
    "    #ax2.set_ylim([-0.5e-5, 0.5e-5])\n",
    "\n",
    "plot_freq('amp_out_span_2g_rbw_10k_40kpt_0000.csv')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "07c80933-ae27-4b3a-9b61-49d6d08b77c5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Fundamental peak: 7996500.0 -38.6481513977051\n",
      "Frequency spacing is 49.973751 kHz\n",
      "3 dB frequency from spline: 0.564 GHz, equiv t_r = 0.620 ns\n",
      "Reconstruction sample spacing: 0.025 ns\n",
      "edge 4985:5010: t_r=0.625 ns, equiv f_3dB = 0.560 GHz\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 120156 characters]",
      "text/plain": [
       "<Figure size 1200x900 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plot_freq('board_out_span_2g_rbw_10k_40kpt.csv')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "3403fb0f-8368-4d1f-ab3e-b53e4f3739d8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Fundamental peak: 8009960.0 -25.7375087738037\n",
      "Frequency spacing is 1997.992008 kHz\n",
      "3 dB frequency from spline: 0.245 GHz, equiv t_r = 1.427 ns\n",
      "Reconstruction sample spacing: 0.025 ns\n",
      "edge 4995:5014: t_r=0.474 ns, equiv f_3dB = 0.738 GHz\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 118296 characters]",
      "text/plain": [
       "<Figure size 1200x900 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plot_freq('../data_spectrum/pi3hdx_bent_2.csv')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "8622de5b-d39b-4c5f-93d9-fdb69e165ba1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Fundamental peak: 88009780.0 -30.2440490722656\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/lib/python3.13/site-packages/matplotlib/cbook.py:1709: ComplexWarning: Casting complex values to real discards the imaginary part\n",
      "  return math.isfinite(val)\n",
      "/usr/lib/python3.13/site-packages/matplotlib/cbook.py:1345: ComplexWarning: Casting complex values to real discards the imaginary part\n",
      "  return np.asarray(x, float)\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 113728 characters]",
      "text/plain": [
       "<Figure size 1200x900 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "def plot_freq(fn):\n",
    "    header_amp, data_amp = load_keysight_csv(fn)\n",
    "    freq = data_amp[:,0]\n",
    "    amp = data_amp[:,1]\n",
    "    start_freq = float(header_amp['Start Frequency'])\n",
    "    stop_freq = float(header_amp['Stop Frequency'])\n",
    "    freq_spacing = (stop_freq - start_freq) / float(header_amp['Number of Points'])\n",
    "    \n",
    "    peaks, props = sig.find_peaks(amp, threshold=60)\n",
    "    fundamental_idx = peaks[0]\n",
    "    print('Fundamental peak:', freq[fundamental_idx], amp[fundamental_idx])\n",
    "    \n",
    "    fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 9))\n",
    "    ax1.plot(freq/1e6, amp)\n",
    "    ax1.set_xlabel('f [MHz]')\n",
    "    ax1.set_ylabel('[dB]')\n",
    "    ax1.annotate(f'Fundamental @{freq[fundamental_idx]/1e6:.3f} Mhz, {amp[fundamental_idx]:.2f} dB',\n",
    "                (freq[fundamental_idx]/1e6, amp[fundamental_idx]),\n",
    "                xytext=(5, 0),\n",
    "                xycoords='data',\n",
    "                textcoords='offset pixels',\n",
    "               )\n",
    "\n",
    "    amp[amp < -80] = 0\n",
    "    sq_amp = 10**(amp/10)\n",
    "    td_data = fft.ifft(sq_amp)\n",
    "    ax2.plot(td_data[1:])\n",
    "\n",
    "plot_freq('../data_spectrum/tdp0604_bent_3.csv')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "26c0debc-4d24-4c8d-aa2e-4b0c9e1a4f5c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Fundamental peak: 88009780.0 -30.2440490722656\n",
      "Frequency spacing is 3995.994006 kHz\n",
      "3 dB frequency from spline: 0.088 GHz, equiv t_r = 3.977 ns\n",
      "Reconstruction sample spacing: 11.362 ns\n",
      "edge 9852:10145: t_r=0.266 ns, equiv f_3dB = 1.314 GHz\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 148016 characters]",
      "text/plain": [
       "<Figure size 1200x900 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "def plot_freq(fn):\n",
    "    with plt.rc_context({\n",
    "        'legend.fontsize': 8,\n",
    "        # RC config from ChatGPT:\n",
    "        \"font.size\": 8,  # General font size\n",
    "        \"axes.labelsize\": 8,  # Axis labels\n",
    "        \"axes.titlesize\": 8,  # Axis titles\n",
    "        \"xtick.labelsize\": 8,  # X-axis tick labels\n",
    "        \"ytick.labelsize\": 8,  # Y-axis tick labels\n",
    "        \"legend.fontsize\": 8,  # Legend font size\n",
    "        \"figure.titlesize\": 12,  # Overall figure title\n",
    "        \"mathtext.fontset\": \"cm\",  # Use Computer Modern for math\n",
    "    \n",
    "        # Line and Marker settings\n",
    "        \"lines.linewidth\": 1,\n",
    "        \"lines.markersize\": 3,\n",
    "    \n",
    "        # Tick settings\n",
    "        \"xtick.major.size\": 4,\n",
    "        \"xtick.minor.size\": 2,\n",
    "        \"ytick.major.size\": 4,\n",
    "        \"ytick.minor.size\": 2,\n",
    "        \"xtick.direction\": \"in\",\n",
    "        \"ytick.direction\": \"in\",\n",
    "    \n",
    "        # Axes settings\n",
    "        \"axes.linewidth\": 0.8,\n",
    "        \"axes.grid\": True,\n",
    "        \"grid.linewidth\": 0.5,\n",
    "        \"grid.linestyle\": \"--\",\n",
    "    \n",
    "        # Legend settings\n",
    "        \"legend.frameon\": False,\n",
    "    \n",
    "        # Figure size for single-column figures in print (inches)\n",
    "    #    \"figure.figsize\": (3.5, 2.5),  # Adjust based on journal specifications\n",
    "    #    \"figure.dpi\": 300,  # High resolution for print\n",
    "    \n",
    "        # Savefig settings for high-quality output\n",
    "        \"savefig.dpi\": 600,  # Ensures high-resolution images\n",
    "        \"savefig.bbox\": \"tight\",  # Trim extra whitespace\n",
    "        \"savefig.transparent\": True,  # Avoid background color\n",
    "    }):\n",
    "        header_amp, data_amp = load_keysight_csv(fn)\n",
    "        freq = data_amp[:,0]\n",
    "        amp = data_amp[:,1]\n",
    "        start_freq = float(header_amp['Start Frequency'])\n",
    "        stop_freq = float(header_amp['Stop Frequency'])\n",
    "        freq_spacing = (stop_freq - start_freq) / float(header_amp['Number of Points'])\n",
    "        \n",
    "        fundamental_idx = 1 + amp[1:].argmax()\n",
    "        print('Fundamental peak:', freq[fundamental_idx], amp[fundamental_idx])\n",
    "        \n",
    "        fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 9), gridspec_kw={'height_ratios': [2, 1]})\n",
    "        ax1.plot(freq/1e9, amp, color='black', alpha=0.3)\n",
    "        ax1.set_xlabel(r'$f [\\mathrm{GHz}]$')\n",
    "        ax1.set_ylabel(r'$[\\mathrm{dB}]$')\n",
    "        #ax1.annotate(f'Fundamental @{freq[fundamental_idx]/1e6:.3f} Mhz, {amp[fundamental_idx]:.2f} dB',\n",
    "        #            (freq[fundamental_idx]/1e6, amp[fundamental_idx]),\n",
    "        #            xytext=(5, 0),\n",
    "        #            xycoords='data',\n",
    "        #            textcoords='offset pixels',\n",
    "        #           )\n",
    "        \n",
    "        freq_space = np.linspace(freq[fundamental_idx], stop_freq, 1000)\n",
    "        sq_amp = np.sqrt(10**(amp[fundamental_idx]/10))\n",
    "        sq_approx = 10*np.log10((sq_amp / (freq_space/freq[fundamental_idx]))**2)\n",
    "        ax1.plot(freq_space/1e9, sq_approx, color='blue')\n",
    "    \n",
    "        print(f'Frequency spacing is {freq_spacing/1e3:.6f} kHz')\n",
    "    \n",
    "        peaks, _props = sig.find_peaks(amp, prominence=20)\n",
    "        peaks = [idx for idx in peaks]\n",
    "                #if amp[idx] - 10*np.log10((sq_amp / (freq[idx]/freq[fundamental_idx]))**2) > -10]\n",
    "                #and abs((freq[idx] / freq[fundamental_idx]) % 1.0 - 0.5) > 0.4]\n",
    "        peak_freq = freq[peaks]\n",
    "        peak_amp = amp[peaks]\n",
    "        spl = interp.make_smoothing_spline(peak_freq, peak_amp, lam=1e20)\n",
    "        #ax1.scatter(peak_freq/1e6, peak_amp, color='red')\n",
    "        freq_space_cut = freq_space[:np.argmin(freq_space < max(peak_freq))]\n",
    "        ax1.plot(freq_space_cut/1e9, spl(freq_space_cut), color='red')\n",
    "        kc = [(freq[idx]/freq[fundamental_idx], np.sqrt(10**(amp[idx]/10))) for idx in peaks]\n",
    "    \n",
    "        idx_3db = np.argmin(abs(spl(freq_space_cut) - sq_approx[:len(freq_space_cut)]) < 3.00)\n",
    "        print(f'3 dB frequency from spline: {freq_space[idx_3db]/1e9:.3f} GHz, equiv t_r = {0.35 / freq_space[idx_3db] * 1e9:.3f} ns')\n",
    "    \n",
    "        plot_width = 0.2\n",
    "        t = np.linspace(0, plot_width, 10000)\n",
    "        ts = 1/freq[fundamental_idx]\n",
    "        tf = t * ts\n",
    "        td_data = 4/np.pi * sum(c * np.sin(2*np.pi*k*t) for k, c in kc)\n",
    "        #td_data = fft.ifft([c for k, c in kc])\n",
    "        td_data = np.hstack([-td_data[::-1], td_data[1:]])\n",
    "        lvl_low = td_data.min()\n",
    "        lvl_high = td_data.max()\n",
    "        lvl_amp = lvl_high - lvl_low\n",
    "        td_data = -1 + (td_data - lvl_low) / lvl_amp * 2\n",
    "        ax2.plot(np.linspace(0, 2*plot_width*ts*1e9, 10000*2-1), td_data)\n",
    "        \n",
    "        lvl_low = -0.4\n",
    "        lvl_high = 0.4\n",
    "        ax2.axhline(lvl_low, color='gray')\n",
    "        ax2.axhline(lvl_high, color='gray')\n",
    "        ax2.grid()\n",
    "        ax1.grid()\n",
    "        ax1.set_xlim([start_freq/1e9, stop_freq/1e9])\n",
    "        ax2.set_xlabel(r'$t [\\mathrm{ns}]$')\n",
    "        print(f'Reconstruction sample spacing: {ts*1e9:.3f} ns')\n",
    "    \n",
    "        state = None\n",
    "        edge_start, edge_end = None, None\n",
    "        for i, (dp, dn) in enumerate(zip(td_data, td_data[1:])):\n",
    "            if dp <= lvl_low and dn >= lvl_low:\n",
    "                if state != 'edge':\n",
    "                    state = 'edge'\n",
    "                    edge_start = i\n",
    "            elif dp <= lvl_high and dn >= lvl_high:\n",
    "                if state == 'edge':\n",
    "                    edge_end = i\n",
    "                    tr = 0.4*2*ts/10000*(edge_end-edge_start)\n",
    "                    print(f'edge {edge_start}:{edge_end}: t_r={tr*1e9:.3f} ns, equiv f_3dB = {0.35/tr/1e9:.3f} GHz')\n",
    "                state = 'high'\n",
    "            elif dp <= lvl_low and dn <= lvl_low:\n",
    "                state = 'low'\n",
    "            elif dp >= lvl_high and dn >= lvl_high:\n",
    "                state = 'high'\n",
    "    \n",
    "        #fft_data = np.hstack([\n",
    "        #    np.zeros(round(start_freq / freq_spacing)),\n",
    "        #    10**(amp/20)])\n",
    "        #fft_data = np.hstack([fft_data, fft_data[1::-1]])\n",
    "        \n",
    "        #time_domain_amp = fft.ifft(fft_data)\n",
    "        \n",
    "        #ax2.plot(time_domain_amp)\n",
    "        #ax2.set_xlim([1000, 4000])\n",
    "        #ax2.set_ylim([-0.5e-5, 0.5e-5])\n",
    "        fig.tight_layout()\n",
    "\n",
    "plot_freq('../data_spectrum/tdp0604_bent_3.csv')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 108,
   "id": "a00a8413-5fe3-4a70-a437-ddc907647126",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Fundamental peak: 88009780.0 -30.2440490722656\n",
      "Frequency spacing is 3995.994006 kHz\n",
      "Reconstruction sample spacing: 11.362 ns\n",
      "edge 9736:10261: t_r=0.119 ns, equiv f_3dB = 2.934 GHz\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 23628 characters]",
      "text/plain": [
       "<Figure size 400x250 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "def plot_freq(fn):\n",
    "    with plt.rc_context({\n",
    "        'legend.fontsize': 8,\n",
    "        # RC config from ChatGPT:\n",
    "        \"font.size\": 8,  # General font size\n",
    "        \"axes.labelsize\": 8,  # Axis labels\n",
    "        \"axes.titlesize\": 8,  # Axis titles\n",
    "        \"xtick.labelsize\": 8,  # X-axis tick labels\n",
    "        \"ytick.labelsize\": 8,  # Y-axis tick labels\n",
    "        \"legend.fontsize\": 8,  # Legend font size\n",
    "        \"figure.titlesize\": 12,  # Overall figure title\n",
    "        \"mathtext.fontset\": \"cm\",  # Use Computer Modern for math\n",
    "    \n",
    "        # Line and Marker settings\n",
    "        \"lines.linewidth\": 1,\n",
    "        \"lines.markersize\": 3,\n",
    "    \n",
    "        # Tick settings\n",
    "        \"xtick.major.size\": 4,\n",
    "        \"xtick.minor.size\": 2,\n",
    "        \"ytick.major.size\": 4,\n",
    "        \"ytick.minor.size\": 2,\n",
    "        \"xtick.direction\": \"in\",\n",
    "        \"ytick.direction\": \"in\",\n",
    "    \n",
    "        # Axes settings\n",
    "        \"axes.linewidth\": 0.8,\n",
    "        \"axes.grid\": True,\n",
    "        \"grid.linewidth\": 0.5,\n",
    "        \"grid.linestyle\": \"--\",\n",
    "    \n",
    "        # Legend settings\n",
    "        \"legend.frameon\": False,\n",
    "    \n",
    "        # Figure size for single-column figures in print (inches)\n",
    "    #    \"figure.figsize\": (3.5, 2.5),  # Adjust based on journal specifications\n",
    "    #    \"figure.dpi\": 300,  # High resolution for print\n",
    "    \n",
    "        # Savefig settings for high-quality output\n",
    "        \"savefig.dpi\": 600,  # Ensures high-resolution images\n",
    "        \"savefig.bbox\": \"tight\",  # Trim extra whitespace\n",
    "        \"savefig.transparent\": True,  # Avoid background color\n",
    "    }):\n",
    "        header_amp, data_amp = load_keysight_csv(fn)\n",
    "        freq = data_amp[:,0]\n",
    "        amp = data_amp[:,1]\n",
    "        start_freq = float(header_amp['Start Frequency'])\n",
    "        stop_freq = float(header_amp['Stop Frequency'])\n",
    "        freq_spacing = (stop_freq - start_freq) / float(header_amp['Number of Points'])\n",
    "        \n",
    "        fundamental_idx = 1 + amp[1:].argmax()\n",
    "        print('Fundamental peak:', freq[fundamental_idx], amp[fundamental_idx])\n",
    "        \n",
    "        fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(4, 2.5), gridspec_kw={'height_ratios': [2, 1]})\n",
    "        ax1.plot(freq/1e9, amp, color='red', alpha=1.0)\n",
    "        ax1.set_xlabel(r'$f [\\mathrm{GHz}]$')\n",
    "        ax1.set_ylabel(r'$[\\mathrm{dB}]$')\n",
    "        #ax1.annotate(f'Fundamental @{freq[fundamental_idx]/1e6:.3f} Mhz, {amp[fundamental_idx]:.2f} dB',\n",
    "        #            (freq[fundamental_idx]/1e6, amp[fundamental_idx]),\n",
    "        #            xytext=(5, 0),\n",
    "        #            xycoords='data',\n",
    "        #            textcoords='offset pixels',\n",
    "        #           )\n",
    "        \n",
    "        freq_space = np.linspace(freq[fundamental_idx], stop_freq, 1000)\n",
    "        sq_amp = np.sqrt(10**(amp[fundamental_idx]/10))\n",
    "        sq_approx = 10*np.log10((sq_amp / (freq_space/freq[fundamental_idx]))**2)\n",
    "        ax1.plot(freq_space/1e9, sq_approx, color='black', alpha=0.4)\n",
    "    \n",
    "        print(f'Frequency spacing is {freq_spacing/1e3:.6f} kHz')\n",
    "\n",
    "        kc = zip(freq/freq[fundamental_idx], np.sqrt(10**(amp/10)))\n",
    "        plot_width = .2\n",
    "        t = np.linspace(0, plot_width, 10000)\n",
    "        ts = 1/freq[fundamental_idx]\n",
    "        tf = t * ts\n",
    "        td_data = 4/np.pi * sum(c * np.sin(2*np.pi*k*t) for k, c in kc)\n",
    "        #td_data = fft.ifft([c for k, c in kc])\n",
    "        td_data = np.hstack([-td_data[::-1], td_data[1:]])\n",
    "        td_data_sort = np.array(sorted(td_data))\n",
    "        lvl_low = td_data_sort[:len(td_data_sort)//5].max()\n",
    "        lvl_high = td_data_sort[-len(td_data_sort)//5:].min()\n",
    "        lvl_amp = lvl_high - lvl_low\n",
    "        td_data = -1 + (td_data - lvl_low) / lvl_amp * 2\n",
    "        ax2.plot(np.linspace(-plot_width*ts*1e9, plot_width*ts*1e9, 10000*2-1), td_data)\n",
    "        ax2.set_xlim([-plot_width*ts*1e9, plot_width*ts*1e9])\n",
    "        \n",
    "        lvl_low = -0.7\n",
    "        lvl_high = 0.7\n",
    "        ax2.axhline(lvl_low, color='gray')\n",
    "        ax2.axhline(lvl_high, color='gray')\n",
    "        ax1.set_xlim([start_freq/1e9, stop_freq/1e9])\n",
    "        ax2.set_xlabel(r'$t [\\mathrm{ns}]$')\n",
    "        ax1.set_ylim([-130, 0])\n",
    "        print(f'Reconstruction sample spacing: {ts*1e9:.3f} ns')\n",
    "        \n",
    "        state = None\n",
    "        edge_start, edge_end = None, None\n",
    "        for i, (dp, dn) in enumerate(zip(td_data, td_data[1:])):\n",
    "            if dp <= lvl_low and dn >= lvl_low:\n",
    "                if state != 'edge':\n",
    "                    state = 'edge'\n",
    "                    edge_start = i\n",
    "            elif dp <= lvl_high and dn >= lvl_high:\n",
    "                if state == 'edge':\n",
    "                    edge_end = i\n",
    "                    tr = plot_width*ts/10000*(edge_end-edge_start)\n",
    "                    print(f'edge {edge_start}:{edge_end}: t_r={tr*1e9:.3f} ns, equiv f_3dB = {0.35/tr/1e9:.3f} GHz')\n",
    "                    break\n",
    "                state = 'high'\n",
    "            elif dp <= lvl_low and dn <= lvl_low:\n",
    "                state = 'low'\n",
    "            elif dp >= lvl_high and dn >= lvl_high:\n",
    "                state = 'high'\n",
    "\n",
    "        tr_scaled, prefix = tr*1e9, 'n'\n",
    "        if tr_scaled < 1:\n",
    "            tr_scaled, prefix = tr*1e12, 'p'\n",
    "        ax2.annotate(fr'$t_r = {tr_scaled:3.0f}\\;\\mathrm{{{prefix}s}}$',\n",
    "                    (0, 0),\n",
    "                    xytext=(5, 0),\n",
    "                    xycoords='data',\n",
    "                    textcoords='offset pixels',\n",
    "                   )\n",
    "        \n",
    "        #fft_data = np.hstack([\n",
    "        #    np.zeros(round(start_freq / freq_spacing)),\n",
    "        #    10**(amp/20)])\n",
    "        #fft_data = np.hstack([fft_data, fft_data[1::-1]])\n",
    "        \n",
    "        #time_domain_amp = fft.ifft(fft_data)\n",
    "        \n",
    "        #ax2.plot(time_domain_amp)\n",
    "        #ax2.set_xlim([1000, 4000])\n",
    "        #ax2.set_ylim([-0.5e-5, 0.5e-5])\n",
    "        fig.tight_layout()\n",
    "        return fig\n",
    "\n",
    "plot_freq('../data_spectrum/tdp0604_bent_3.csv').savefig('../paper/fig_spec_risetime_tdp0604.pdf')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 109,
   "id": "a72b9a76-2e88-44bb-9bdf-4890c7c5d2d4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Fundamental peak: 88009780.0 -26.0266227722168\n",
      "Frequency spacing is 3995.994006 kHz\n",
      "Reconstruction sample spacing: 11.362 ns\n",
      "edge 9579:10418: t_r=0.191 ns, equiv f_3dB = 1.836 GHz\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 23768 characters]",
      "text/plain": [
       "<Figure size 400x250 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plot_freq('../data_spectrum/pi3hdx_bent_3.csv').savefig('../paper/fig_spec_risetime_pi3hdx.pdf')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 110,
   "id": "2b356eec-fe06-4331-92bb-3e95110868ba",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Fundamental peak: 88009780.0 -31.5218181610107\n",
      "Frequency spacing is 3995.994006 kHz\n",
      "Reconstruction sample spacing: 11.362 ns\n",
      "edge 9722:10274: t_r=0.125 ns, equiv f_3dB = 2.790 GHz\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 23908 characters]",
      "text/plain": [
       "<Figure size 400x250 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plot_freq('../data_spectrum/max3748_bent_3.csv').savefig('../paper/fig_spec_risetime_max3748.pdf')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 111,
   "id": "b5ed9f7b-19e1-4b94-9bea-7e4a62d1c2d1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Fundamental peak: 88009780.0 -15.3417797088623\n",
      "Frequency spacing is 3995.994006 kHz\n",
      "Reconstruction sample spacing: 11.362 ns\n",
      "edge 8738:11259: t_r=0.573 ns, equiv f_3dB = 0.611 GHz\n"
     ]
    },
    {
     "data": {
      "image/png": "[encoded data, 23492 characters]",
      "text/plain": [
       "<Figure size 400x250 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plot_freq('../data_spectrum/74lvc_bent_3.csv').savefig('../paper/fig_spec_risetime_74lvc.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.13.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
