{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Matching the WEST ICRH Antenna" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this notebook we investigate the various method to match a WEST ICRH antenna. By matching the antenna we mean to find 4 capacitances values $C_1$, $C_2$, $C_3$ and $C_4$ in order for the antenna to be operated at a given frequency $f_0$. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%load_ext autoreload\n", "%autoreload 2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import skrf as rf\n", "import numpy as np\n", "import sys\n", "from tqdm.notebook import tqdm\n", "\n", "sys.path.append('..')\n", "from west_ic_antenna import WestIcrhAntenna" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "rf.stylely()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from west_ic_antenna import WestIcrhAntenna" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Matching Each Sides Separately\n", "Here, each side of the antenna is matched separatly, which leads to two set of capacitances $(C_1, C_2)$ and $(C_3,C_4)$.\n", "\n", "In the following example, both sides of the antenna are matched at the frequency $f_0$, keeping opposite side unmatched (C=150pF):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f0 = 55.5e6\n", "freq = rf.Frequency(54.5, 56.5, npoints=1001, unit='MHz')\n", "ant = WestIcrhAntenna(frequency=freq) # default is vacuum coupling" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "C_match_left = ant.match_one_side(f_match=f0, side='left', solution_number=1)\n", "C_match_right = ant.match_one_side(f_match=f0, side='right', solution_number=1)\n", "print('Left side matching point: ', C_match_left)\n", "print('Right side matching point: ', C_match_right)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's have a look to the RF reflection coefficient of each sides:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots()\n", "ant.circuit(Cs=C_match_left).network.plot_s_db(m=0, n=0, lw=2, ax=ax)\n", "ant.circuit(Cs=C_match_right).network.plot_s_db(m=1, n=1, lw=2, ls='--', ax=ax)\n", "ax.legend(('Left side matched (right unmatched)', \n", " 'Right side matched (left side unmatched)'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In reality, the precision at which one can tune the capacitance is not better than 1/100 pF, so one have to consider rounding optimal solutions to such precision :\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "C_match_left = ant.match_one_side(f_match=f0, side='left', solution_number=1, decimals=2)\n", "C_match_right = ant.match_one_side(f_match=f0, side='right', solution_number=1, decimals=2)\n", "print('Left side matching point: ', C_match_left)\n", "print('Right side matching point: ', C_match_right)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that the performances are slightly degraded, but, it's real life! " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots()\n", "ant.circuit(Cs=C_match_left).network.plot_s_db(m=0, n=0, lw=2, ax=ax)\n", "ant.circuit(Cs=C_match_right).network.plot_s_db(m=1, n=1, lw=2, ls='--', ax=ax)\n", "ax.legend(('Left side matched (right unmatched)', \n", " 'Right side matched (left side unmatched)'))\n", "ax.set_ylabel('$|S_{ii}|$ [dB]')\n", "ax.set_xlabel('f [MHz]')\n", "ax.legend(('$S_{11}$', '$S_{22}$'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Frequency Shift for Dipole Excitation\n", "The coupling between antenna's sides requires shifting the frequency with respect to the matching frequency used for each side separately.\n", "\n", "Thus, if the optimal capacitor set for both side are of the same kind (i.e. either both C_top>C_bot or both C_top {delta_f/1e6} MHz shift' )" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots()\n", "ax.plot(freq.f_scaled, 20*np.log10(np.abs(s_act)), lw=2)\n", "ax.axvline(f0/1e6, ls='--', color='gray')\n", "ax.axvline(f_opt/1e6, ls='--', color='k')\n", "ax.set_ylabel('$|S_{act}|$ [dB]')\n", "ax.set_xlabel('f [MHz]')\n", "ax.legend(('$S_{act,1}$', '$S_{act,2}$'))\n", "ax.set_title('Dipole excitation')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However, the reflection coefficient is not that low (-15 to -20 dB at best). That means that there is certainly a sweeter spot of the capacitor set." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "C_opt = ant.match_both_sides(f_match=55.728e6, decimals=2)\n", "print(C_opt)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The difference is:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.array(C_opt) - np.array(C_match)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "That is, one should:\n", "- decrease slightly the top capacitors\n", "- increase slightly the bottom capacitors" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As a rule of thumb, at 55.5 MHz, we can remember to do -0.1pF for top and +0.1pF for bottom capacitors. Doing, so, we get a better match point (between -20 and -30 dB)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "C_match_improved = [C_match[0] - 0.1,\n", " C_match[1] + 0.1,\n", " C_match[2] - 0.1,\n", " C_match[3] + 0.1]\n", "fig, ax = plt.subplots()\n", "ax.plot(freq.f_scaled, ant.s_act_db(Cs=C_match_improved, power=[1,1], phase=[0,np.pi]), lw=2)\n", "ax.axvline(f0/1e6, ls='--', color='gray')\n", "ax.axvline(f_opt/1e6, ls='--', color='k')\n", "ax.set_ylabel('$|S_{act}|$ [dB]')\n", "ax.set_xlabel('f [MHz]')\n", "ax.legend(('$S_{act,1}$', '$S_{act,2}$'))\n", "ax.set_title('Dipole excitation')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Monopole excitation (phase $(0,0)$) at the contrary requires shifting the operating frequency toward lower frequencies:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# monopole excitation\n", "power = [1, 1]\n", "phase = [0, 0]\n", "# combine both separate solutions\n", "C_match = [C_match_left[0], C_match_left[1], C_match_right[2], C_match_right[3]]\n", "# looking to the active s parameters:\n", "s_act = ant.s_act(power, phase, Cs=C_match)\n", "# finding the optimum frequency\n", "idx_f_opt = np.argmin(np.abs(s_act[:,0]))\n", "f_opt = freq.f[idx_f_opt]\n", "delta_f = f_opt - f0\n", "print(f'Optimum frequency is f_opt={f_opt/1e6} MHz --> {delta_f/1e6} MHz shift' )" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots()\n", "ax.plot(freq.f_scaled, 20*np.log10(np.abs(s_act)), lw=2)\n", "ax.axvline(f0/1e6, ls='--', color='gray')\n", "ax.axvline(f_opt/1e6, ls='--', color='k')\n", "ax.set_ylabel('$|S_{act}|$ [dB]')\n", "ax.set_xlabel('f [MHz]')\n", "ax.legend(('$S_{act,1}$', '$S_{act,2}$'))\n", "ax.set_title('Monopole excitation')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The matching can also be improved:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "C_opt_mono = ant.match_both_sides(f_match=55.27e6, decimals=2, power=[1,1], phase=[0,0])\n", "print(C_opt_mono)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So a difference of:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.array(C_opt_mono) - np.array(C_match)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "That is the opposite trend than for dipole:\n", "- increase slightly top capacitors (~ +0.1 pF as a rule of thumb)\n", "- decrease slightly bot capacitors (~ -0.1 pF as a rule of thumb)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Matching Both Sides at the same time\n", "It is also possible to optimize the antenna directly for the target frequency and for a given excitation. Matching both sides at the same time in fact matches each side separately, then find the optimum points using these solutions as starting point to help the convergence of the optimization.\n", "\n", "Two methods are available to match both sides of the antenna: `match_both_sides` which use the NumPy optimisation routines, and `match_both_sides_iterative` which uses the feedback matching algorithm (see [matching_automatic.ipynb](matching_automatic.ipynb) for more details). The primer is more robust in finding a solution, eventually at the price of a longer calculation. The later method can indeed be \"lost\" if the ideal solution is not close enough (or if the optimal region is too narrow, like under vacuum)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f0 = 55e6\n", "freq = rf.Frequency(54, 56, npoints=1001, unit='MHz')\n", "ant = WestIcrhAntenna(frequency=freq) # default is vacuum coupling\n", "\n", "# antenna excitation to match for\n", "power = [1, 1] # W\n", "phase = [0, np.pi] # dipole\n", "\n", "ant.DEBUG=True # display additional messages\n", "# Providing an initial guess C0 skip the search on both sides separately\n", "C0 = [C_match_left[0], C_match_left[1], C_match_right[2], C_match_right[3]]\n", "C_opt_vacuum_dipole = ant.match_both_sides(f_match=f0, power=power, phase=phase, C0=C0, decimals=2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f0 = 55e6\n", "freq = rf.Frequency(54, 56, npoints=1001, unit='MHz')\n", "ant = WestIcrhAntenna(frequency=freq) # default is vacuum coupling\n", "\n", "# looking to the active s parameters:\n", "s_act = ant.s_act(power, phase, Cs=C_opt_vacuum_dipole)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots()\n", "ax.plot(freq.f_scaled, 20*np.log10(np.abs(s_act)), lw=2)\n", "ax.axvline(f0/1e6, ls='--', color='gray')\n", "ax.set_ylabel('$|S_{act}|$ [dB]')\n", "ax.set_xlabel('f [MHz]')\n", "ax.legend(('$S_{act,1}$', '$S_{act,2}$'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Manual Matching on Plasma\n", "When the antenna is facing the plasma, the coupling resistance increases and the antenna matching is affected.\n", "\n", "If the operator keeps the matchpoint obtained on vacuum (in dipole for example), the antenna will have the following behaviour:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# changing the front-face of the antenna to a plasma case\n", "freq = rf.Frequency(54, 56, npoints=1001, unit='MHz')\n", "front_face_plasma = WestIcrhAntenna.interpolate_front_face(Rc=1, source='TOPICA-L-mode')\n", "ant = WestIcrhAntenna(frequency=freq, front_face=front_face_plasma) \n", "\n", "# looking to the active s parameters in dipole:\n", "powers = [1 ,1]\n", "phases = [0, np.pi] \n", "s_act = ant.s_act(powers, phases , Cs=C_opt_vacuum_dipole)\n", "\n", "fig, ax = plt.subplots()\n", "ax.plot(freq.f_scaled, 20*np.log10(np.abs(s_act)), lw=2)\n", "ax.axvline(f0/1e6, ls='--', color='gray')\n", "ax.set_ylabel('$|S_{act}|$ [dB]')\n", "ax.set_xlabel('f [MHz]')\n", "ax.legend(('$S_{act,1}$', '$S_{act,2}$'))\n", "ax.set_title('Reflection on plasma using vacuum matching setpoint')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Thus, it is necessary to re-adapt the capacitors to improve the matching of the antenna on plasma:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "C_match_plasma = ant.match_both_sides(f0, power=powers, phase=phases, C0=C_opt_vacuum_dipole)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "s_act = ant.s_act(powers, phases , Cs=C_match_plasma)\n", "\n", "fig, ax = plt.subplots()\n", "ax.plot(freq.f_scaled, 20*np.log10(np.abs(s_act)), lw=2)\n", "ax.axvline(f0/1e6, ls='--', color='gray')\n", "ax.set_ylabel('$|S_{act}|$ [dB]')\n", "ax.set_xlabel('f [MHz]')\n", "ax.legend(('$S_{act,1}$', '$S_{act,2}$'))\n", "ax.set_title('Reflection on plasma after rematching')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we compare the differences between capacitances setpoint between vacuum and plasma cases, we see that the difference between the values go like this:\n", "* top capacitances are increased \n", "* bottom capacitances are decreased\n", "\n", "Thus, the \"distance\" between top and bottom capacitance is increased. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "np.array(C_match_plasma) - np.array(C_opt_vacuum_dipole)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The capacitance shift to apply with respect to the vacuum setpoint depends of the coupling resistance of the plasma.\n", "\n", "We can use this property to deduce the set of capacitors to use during plasma operation. Below we generate a series of various plasma loading cases, of increasing coupling resistance $R_c$. For each case we search for and we store the setpoints." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "C_matchs = []\n", "\n", "# note: on plasma loads, the iterative method is faster\n", "# important : increase the gap between capacitances start point for the algorith to converge for high Rc cases\n", "C0 = np.array(C_opt_vacuum_dipole) + np.array([+5, -5, +5, -5]) \n", "\n", "Rcs1 = np.linspace(0.39, 1.7, 10) # TOPICA \"H-mode\" (low coupling)\n", "for Rc in tqdm(Rcs1):\n", " _plasma = WestIcrhAntenna.interpolate_front_face(Rc=Rc, source='TOPICA-H-mode')\n", " _ant = WestIcrhAntenna(frequency=freq, front_face=_plasma)\n", " _C_match = _ant.match_both_sides_iterative(f0, power=powers, phase=phases, Cs=C_opt_vacuum_dipole)\n", " C_matchs.append(_C_match)\n", "\n", "\n", "Rcs2 = np.linspace(1, 2.9, 10) # TOPICA \"L-mode\" (higher coupling)\n", "for Rc in tqdm(Rcs2):\n", " _plasma = WestIcrhAntenna.interpolate_front_face(Rc=Rc, source='TOPICA-L-mode')\n", " _ant = WestIcrhAntenna(frequency=freq, front_face=_plasma)\n", " _C_match = _ant.match_both_sides_iterative(f0, power=powers, phase=phases, Cs=C0)\n", " C_matchs.append(_C_match)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's plot the average capacitance shift to apply versus the coupling resistance of the plasma loading scenario:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Rcs3 = np.linspace(0.3, 1.6, 10)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "diff_C = np.array(C_matchs) - np.array(C_opt_vacuum_dipole)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Rcs = np.concatenate([Rcs1, Rcs2])\n", "fig, ax = plt.subplots()\n", "ax.plot(Rcs, diff_C, 'o')\n", "ax.set_xlabel('Coupling Resistance $R_c$ [Ohm]')\n", "ax.set_ylabel('Capacitance Shift $\\Delta C$ [pF]')\n", "ax.set_title('Capacitance Shift to add from Vacuum Match Points (55 MHz)')\n", "ax.legend(['Left Top', 'Left Bot', 'Right Top', 'Right Bot'])\n", "ax.set_ylim(-10, 10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Automatic Matching\n", "Here is just a glimpse of the automatic matching capabilities. You will find more on automatic matching in the [automatic matching section](tutorial_matching_automatic.ipynb)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's assume we start from a non-optimal situation, that is we enlarge the capacitance differences from a vacuum matching from a rather arbitrary value: " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Cs = list(np.array(C_opt_vacuum_dipole) + np.array([+3, -3, +3, -3]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then, using the capacitor predictor, we calculate the capacitance set to reach:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Evaluate this cell a few times to see the convergence to the optimal matching\n", "C_left, C_right, err = ant.capacitor_predictor(powers, phases, Cs=Cs)\n", "idx_f = np.argmin(np.abs(ant.frequency.f - 55e6))\n", "Cs = [*C_left[idx_f], *C_right[idx_f]]\n", "\n", "print(Cs)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "s_act = ant.s_act(powers, phases , Cs=Cs)\n", "\n", "fig, ax = plt.subplots()\n", "ax.plot(freq.f_scaled, 20*np.log10(np.abs(s_act)), lw=2)\n", "ax.axvline(f0/1e6, ls='--', color='gray')\n", "ax.set_ylabel('$|S_{act}|$ [dB]')\n", "ax.set_xlabel('f [MHz]')\n", "ax.legend(('$S_{act,1}$', '$S_{act,2}$'))\n", "ax.set_title('Reflection on plasma after rematching')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This plot should be benchmarked against plasma measurements." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from IPython.core.display import HTML\n", "def _set_css_style(css_file_path):\n", " \"\"\"\n", " Read the custom CSS file and load it into Jupyter\n", " Pass the file path to the CSS file\n", " \"\"\"\n", " styles = open(css_file_path, \"r\").read()\n", " s = '' % styles\n", " return HTML(s)\n", "\n", "_set_css_style('custom.css')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "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.7" }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": true, "sideBar": true, "skip_h1_title": true, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": false, "toc_position": {}, "toc_section_display": true, "toc_window_display": false } }, "nbformat": 4, "nbformat_minor": 4 }