{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Coupling to Ideal Loads\n", "In this notebook, we investigate the WEST ICRH antenna behavior when the front-face is considered as the combination of ideal (and independent) loads made of impedances all equal to $Z_s=R_c+j X_s$, where $R_c$ corresponds to the coupling resistance and $X_s$ is the strap reactance. \n", "\n", "\n", "\n", "In such case, the power delivered to the plasma/front-face is then:\n", "\n", "$$\n", "P_t \n", "= \\frac{1}{2} \\sum_{i=1}^4 \\Re[V_i I_i^* ] \n", "= \\frac{1}{2} \\sum_{i=1}^4 \\Re[Z_i] |I_i|^2\n", "= \\frac{1}{2} R_c \\sum_{i=1}^4 |I_i|^2\n", "$$\n", "Hence, we have defined the coupling resistance as:\n", "\n", "$$\n", "R_c = \\frac{\\sum_{i=1}^4 \\Re[Z_i] |I_i|^2}{\\sum_{i=1}^4 |I_i|^2}\n", "$$\n", "\n", "Inversely, the coupling resistance can be determined from:\n", "\n", "$$\n", "R_c = \\frac{2 P_t}{\\sum_{i=1}^4 |I_i|^2}\n", "$$\n", "\n", "In practice however, it is easier to measure RF voltages than currents. \n", "\n", "$$\n", "I = \\frac{V}{Z_s} = \\frac{V}{R_c + j X_s} \n", "\\rightarrow \n", "|I|^2 = \\frac{|V|^2}{|R_c + j X_s|}\n", "\\approx\n", "\\frac{|V|^2}{|X_s|^2}\n", "$$\n", "since in $|X_s|>>|R_c|$.\n", "\n", "The antenna model allows to calculate the coupling resistance from currents (`.Rc()` method) or from the voltage (`.Rc_WEST()` method).\n", "\n", "The strap reactance $X_s$ depends on the strap geometry and varies with the frequency. So, let's find how the strap reactance from the realistic CAD model. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%matplotlib inline" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import numpy as np\n", "import skrf as rf\n", "from tqdm.notebook import tqdm\n", "\n", "# WEST ICRH Antenna package\n", "import sys; sys.path.append('..')\n", "from west_ic_antenna import WestIcrhAntenna\n", "\n", "# styling the figures\n", "rf.stylely()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Coupling to an ideal front-face\n", "Coupling to an ideal front face of coupling resistance $R_c$ is easy using the the `.load()` method of the `WestIcrhAntenna` class. This method takes into account the strap reactance frequency fit (derived in [Strap Reactance Frequency Fit](./strap_reactance.ipynb)) " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "freq = rf.Frequency(30, 70, npoints=1001, unit='MHz')\n", "ant_ideal = WestIcrhAntenna(frequency=freq)\n", "ant_ideal.load(Rc=1) # 1 Ohm coupling resistance front-face" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# matching left and right sides : note that the solutions are (almost) the same\n", "f_match = 55.5e6\n", "C_left = ant_ideal.match_one_side(f_match=f_match, side='left')\n", "C_right = ant_ideal.match_one_side(f_match=f_match, side='right')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "At the difference of the \"real\" situation (see the [Matching](./matching.ipynb) or the [Coupling to a TOPICA plasma](./coupling_to_plasma_from_TOPICA.ipynb)), here is no poloidal neither toroidal coupling of the straps in this front-face model. This leads to:\n", "* Match soluitions are the same for both sides (within $10^{-3}$ pF). \n", "* Using the match solutions for each sides does not require to shift the operating frequency:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# dipole excitation\n", "power = [1, 1]\n", "phase = [0, np.pi]\n", "\n", "# active S-parameter for the match point:\n", "C_match = [C_left[0], C_left[1], C_right[2], C_right[3]]\n", "s_act = ant_ideal.s_act(power, phase, Cs=C_match)\n", "\n", "fig, ax = plt.subplots()\n", "ax.plot(ant_ideal.f_scaled, 20*np.log10(np.abs(s_act)), lw=2)\n", "ax.legend((r'$S_{act,1}$', r'$S_{act,2}$'))\n", "ax.grid(True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Match Points vs Coupling Resistance" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's determine the match points for a range of coupling resistance at a given frequency" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "f_match = 55e6\n", "Rcs = np.r_[0.01, 0.05, np.arange(0.1, 2.0, 0.2)]\n", "\n", "C_matchs = []\n", "ant = WestIcrhAntenna()\n", "for Rc in tqdm(Rcs):\n", " ant.load(Rc)\n", " C_match = ant.match_one_side(f_match=f_match, verbose=False, decimals=2)\n", " C_matchs.append(C_match)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As the coupling resistance increases, the distance between capacitances (Top vs Bottom) increases: " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots()\n", "ax.plot(Rcs, np.array(C_matchs)[:,0:2], lw=2, marker='o')\n", "ax.axhline(C_matchs[0][0], ls='--', color='C0')\n", "ax.axhline(C_matchs[0][1], ls='--', color='C1')\n", "ax.set_xlabel('Rc [Ohm]')\n", "ax.set_ylabel('C [pF]')\n", "ax.legend(('Top', 'Bot'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Displayed differently, the distance between capacitances (Top - Bottom) versus coupling resistance is:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "delta_C_pos = np.array(C_matchs)[:,0] - C_matchs[0][0]\n", "delta_C_neg = C_matchs[0][1] - np.array(C_matchs)[:,1]\n", "\n", "fig, ax = plt.subplots()\n", "ax.plot(Rcs, delta_C_pos, label='Top: + $\\Delta C$', lw=2)\n", "ax.plot(Rcs, delta_C_neg, label='Bot: - $\\Delta C$', lw=2)\n", "ax.set_xlabel('Rc [Ohm]')\n", "ax.set_ylabel('$\\Delta C$ [pF]')\n", "ax.set_ylim(bottom=0)\n", "ax.set_xlim(left=0)\n", "ax.legend()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Load Resilience Curves\n", "Ideal loads is usefull to study the behaviour of the load tolerance property of the antenna and the capacitance match points. It is only necessary to work on half-antenna here, because there is no coupling between toroidal elements. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that we have figured out the match points, let's vary the coupling resistances for a fixed match point and look to the return power (or VSWR): this will highlight the load resilience property of the antenna." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# create a single frequency point antenna to speed-up calculations\n", "ant = WestIcrhAntenna(frequency=rf.Frequency.from_f(f_match, unit='Hz'))\n", "\n", "fig, ax = plt.subplots()\n", "power = [1, 1]\n", "phase = [0, np.pi]\n", "\n", "for C_match in tqdm(C_matchs[0:8]):\n", " SWRs = []\n", " ant.Cs = [C_match[0], C_match[1], 150, 150]\n", " for Rc in Rcs:\n", " ant.load(Rc)\n", " SWR = ant.circuit().network.s_vswr.squeeze()[0,0]\n", " SWRs.append(SWR)\n", " ax.plot(Rcs, np.array(SWRs), lw=2)\n", "\n", "ax.set_xlabel('Rc [Ohm]')\n", "ax.set_ylabel('VSWR')\n", "ax.set_ylim(1, 8)\n", "ax.axhline(2, color='r')\n", "ax.legend(np.round(Rcs, 2))" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "## VSWR(C_top, C_bot) plane\n", "A symetrical Resonant Double Loop (RDL) without mutual coupling between top and bottom strap, has two sets of (symmetrical) solutions. Let's visualize these solution on a $(C_{top}, C_{bot})$ map." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "C_top, C_bot = np.meshgrid(np.arange(40, 60, 1), np.arange(40, 60, 1))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Cs_plane = zip(C_top.flatten(), C_bot.flatten(), C_top.flatten(), C_bot.flatten())\n", "\n", "power, phase = [1, 1], [0, np.pi] # dipole\n", "antenna = WestIcrhAntenna(frequency=rf.Frequency(55, 55, npoints=1, unit='MHz'))\n", "\n", "# initiate arrays\n", "results = {}\n", "loads = [0.2, .5, 1, 2]\n", "for load in loads:\n", " results[load] = []\n", "\n", "# calculate VSWR for all (C_top, C_bot) and for different loading cases\n", "for Cs in tqdm(Cs_plane):\n", " for load in loads:\n", " antenna.load(load)\n", " _vswr = antenna.vswr_act(power, phase, Cs=Cs)\n", " results[load].append(_vswr)\n", "\n", "# convert data into arrays\n", "for load in loads:\n", " results[load] = np.array(results[load])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax = plt.subplots()\n", "\n", "#ax.contourf(C_top, C_bot, np.ones_like(C_top), colors='white')\n", "ax.set_facecolor('white')\n", "ax.grid(True, ls='--', color='gray', alpha=0.5)\n", "\n", "for idx, load in enumerate(loads[-1::-1]):\n", " #map = ax.pcolormesh(C_top, C_bot, results[load][:,0].reshape(C_top.shape),\n", " # vmin=1, vmax=1.25, shading='gouraud', cmap='Blues_r')\n", " \n", " cs = ax.contourf(C_top, C_bot, results[load][:,0].reshape(C_top.shape),\n", " levels=np.arange(1, 2 ,0.1), alpha=0.7)\n", " #ax.clabel(cs, inline=True, fontsize=7)\n", "fig.colorbar(cs)\n", "ax.plot([40, 59], [40, 59], ls='--', color='k')\n", "ax.axis([40, 60, 40, 60])\n", "ax.axis('tight')\n", "ax.plot(np.array(C_matchs)[:,0], np.array(C_matchs)[:,1], color='red', ls=':')\n", "ax.set_xlabel('$C_{top}$')\n", "ax.set_ylabel('$C_{bot}$')\n", "ax.set_title('VSWR Map')" ] }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "We refer in this package as:\n", "- \"Solution 1\" the case when $C_{top} > C_{bot}$\n", "- \"Solution 2\" the case when $C_{top} < C_{bot}$\n" ] }, { "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" } }, "nbformat": 4, "nbformat_minor": 4 }