From ab95e54b32be637d3ef29fdc868da5ea5de5f6d7 Mon Sep 17 00:00:00 2001 From: Watanyu Foosang <watanyu.f@gmail.com> Date: Fri, 25 Mar 2022 13:58:35 +0100 Subject: [PATCH] Add RFBucket class --- tracking/rfbucket.py | 231 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 tracking/rfbucket.py diff --git a/tracking/rfbucket.py b/tracking/rfbucket.py new file mode 100644 index 0000000..47f1b41 --- /dev/null +++ b/tracking/rfbucket.py @@ -0,0 +1,231 @@ +# -*- coding: utf-8 -*- +""" +Created on Wed Mar 16 17:54:36 2022 + +@author: foosang +""" + +import numpy as np +import matplotlib.pyplot as plt +from scipy.optimize import fsolve + +class RFBucket: + """ + Calculate the RF bucket for a given synchronous phase. Valid for both + sine and cosine RF convention, but limited to the following intervals: + sine convention : [0,180°], + cosine convention : [-90°,90°]. + + Parameters + ---------- + phi_s : float + Synchronous phase in [rad]. + convention : {"cos", "sin"}, optional + RF convention corresponding to input phi_s. Use "cos" or "sin" for a + cosine or sine convention, respectively. The default is "cos". + + Attributes + ---------- + phi_s : float + Synchronous phase in [rad]. + convention : str + RF convention. + above_transition : bool + Depending on phi_s and convention, above_transition is True when the + system is above the transition energy and is False if the system is + below the transition energy. + """ + + def __init__(self, phi_s, convention="cos"): + if convention == "sin": + if phi_s<0 or phi_s>np.pi: + raise ValueError("The synchronous phase is outside the valid range.") + else: + if phi_s>=0 and phi_s<=np.pi/2: + self.above_transition = False + else: + self.above_transition = True + elif convention == "cos": + if phi_s<-np.pi/2 or phi_s>np.pi/2: + raise ValueError("The synchronous phase is outside the valid range.") + else: + if phi_s>=-np.pi/2 and phi_s<=0: + self.above_transition = False + else: + self.above_transition = True + else: + raise ValueError("Invalid RF convention.") + + self.phi_s = phi_s + self.convention = convention + if self.convention == "cos": + if self.above_transition is True: + self._phi_s = np.pi - (np.pi/2 - phi_s) + else: + self._phi_s = np.pi/2 - phi_s + + elif self.convention == "sin": + if self.above_transition is True: + self._phi_s = np.pi - phi_s + else: + self._phi_s = phi_s + + def separatrix(self, phi): + """ + Calculate the separatrix in longitudinal phase space. + + Parameters + ---------- + phi : array of float + Phase interval on which the separatrix will be calculated in [rad] + + Returns + ------- + y1 : array + The positive half of the separatrix in the unit of (dphi/dt)/Omega_s, + where dphi/dt is phase velocity and Omega_s is synchrotron + oscillation frequency. + y2 : array + The negative half of the separatrix in the same unit as y1. + + """ + + y1 = np.sqrt(2/np.cos(self._phi_s)*(np.cos(phi)+phi*np.sin(self._phi_s)- + np.cos(np.pi-self._phi_s)-(np.pi-self._phi_s)*np.sin(self._phi_s))) + + y2 = -1*y1 + + return y1, y2 + + def separatrix_root(self, x): + """ + Function used for determining the root of the separatrix equation. + + """ + + value = np.cos(x) + x*np.sin(self._phi_s) - \ + np.cos(np.pi-self._phi_s) - (np.pi-self._phi_s)*np.sin(self._phi_s) + return value + + def plot_separatrix(self, phi, unit="deg"): + """ + Plot separatrix + + Parameters + ---------- + phi : array of float + Phase interval on which the separatrix will be calculated in [rad] + unit : {"deg", "rad"}, optional + Unit of horizontal axis. Use "deg" for degree or "rad" for radian. + The default is "deg". + + Returns + ------- + fig : Figure + Figure object with the separatrix plotted on it. + + """ + + y1, y2 = self.separatrix(phi) + if self.convention == "cos": + if self.above_transition is True: + phi = phi - np.pi/2 + else: + phi = -1*(phi - np.pi/2) + + elif self.convention == "sin": + if self.above_transition is True: + phi = np.pi - phi + + fig, ax = plt.subplots() + + if unit == "deg": + phi = phi/np.pi * 180 + xlabel = "$\\phi$ (deg)" + elif unit == "rad": + xlabel = "$\\phi$ (rad)" + + ax.plot(phi, y1, color="tab:blue") + ax.plot(phi, y2, color="tab:blue") + ax.set_xlabel(xlabel) + ax.set_ylabel('$\\dot{\\phi} / \\Omega_s$') + + return fig + + def bucket_height(self, ring, Vrf, E0, plane): + """ + Calculate the height of the RF bucket which corresponds to the maximum + energy deviation. + + Parameters + ---------- + ring : Synchrotron object + Vrf : float + RF voltage in [V]. + E0 : float + Beam energy in [eV]. + plane : {"x", "y"} + The considered plane of motion of the beam. Use "x" for the + horizontal plane or "y" for the vertical plane. + + Returns + ------- + delta_max : float + Maximum energy deviation. + + """ + G = np.sqrt(0.5* abs(2*np.cos(self._phi_s)-(np.pi-2*self._phi_s)* + np.sin(self._phi_s))) + + plane_dict = {'x':0, 'y':1} + index = plane_dict[plane] + beta = ring.optics.local_beta[index] + h = ring.h + eta = ring.eta + + delta_max = G * np.sqrt(2*beta**2*E0*Vrf/(np.pi*h*abs(eta))) / E0 + + return delta_max + + def extreme_phases(self): + """ + Calculate the two extreme phases on the separatrix. + + Returns + ------- + phi_min : float + The minimum phase in [rad]. + phi_max : float + The maximum phase in [rad]. + + """ + + if self._phi_s > np.pi/2 and self._phi_s <= np.pi: + x0 = 270 / 180 * np.pi # Iinitial estimate of the root + phi_min0 = np.pi - self._phi_s + phi_max0 = fsolve(self.separatrix_root, x0=x0)[0] + + elif self._phi_s >= 0 and self._phi_s <= np.pi/2: + x0 = -90 / 180 * np.pi # Initial estimate of the root + phi_max0 = np.pi - self._phi_s + phi_min0 = fsolve(self.separatrix_root, x0=x0)[0] + + if self.convention == "cos": + if self.above_transition is True: + phi_min = phi_min0 - np.pi/2 + phi_max = phi_max0 - np.pi/2 + else: + phi_min = -1*(phi_max0 - np.pi/2) + phi_max = -1*(phi_min0 - np.pi/2) + + elif self.convention == "sin": + if self.above_transition is True: + phi_min = np.pi - phi_max0 + phi_max = np.pi - phi_min0 + else: + phi_min = phi_min0 + phi_max = phi_max0 + + return phi_min, phi_max + + \ No newline at end of file -- GitLab