diff --git a/README.md b/README.md deleted file mode 100644 index a33e1c283e3c898242e67be804cb883ffed2b624..0000000000000000000000000000000000000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# mbtrack2 - diff --git a/SOLEIL_U_74BA_HOA_SYM02_V0313_cleaned.mat b/SOLEIL_U_74BA_HOA_SYM02_V0313_cleaned.mat deleted file mode 100644 index f021674c7c458deeff2a876be27f7853ba1a1b3b..0000000000000000000000000000000000000000 Binary files a/SOLEIL_U_74BA_HOA_SYM02_V0313_cleaned.mat and /dev/null differ diff --git a/collective_effects/__init__.py b/collective_effects/__init__.py index 27c993fd532ba8a41fdb6ab902ed422559965666..867d0d105cb7a1ffa8141c96eb0c45f72a3d7975 100644 --- a/collective_effects/__init__.py +++ b/collective_effects/__init__.py @@ -5,3 +5,10 @@ Created on Tue Jan 14 12:25:30 2020 @author: gamelina """ +from mbtrack2.collective_effects.instabilities import MBI_threshold +from mbtrack2.collective_effects.resistive_wall import skin_depth, CircularResistiveWall +from mbtrack2.collective_effects.tapers import StupakovRectangularTaper, StupakovCircularTaper +from mbtrack2.collective_effects.wakefield import ComplexData, Impedance, WakeFunction, ImpedanceModel, Wakefield +from mbtrack2.collective_effects.wakefield import read_CST, read_IW2D, spectral_density +from mbtrack2.collective_effects.wakefield import gaussian_bunch_spectrum, beam_spectrum, effective_impedance +from mbtrack2.collective_effects.wakefield import yokoya_elliptic, beam_loss_factor \ No newline at end of file diff --git a/collective_effects/resistive_wall.py b/collective_effects/resistive_wall.py index 95ae59f68698a0d0bc8f51374bd0b33d66493714..240e30e268dfa956dd1a2dbc005e88cfe6897dca 100644 --- a/collective_effects/resistive_wall.py +++ b/collective_effects/resistive_wall.py @@ -8,7 +8,7 @@ Define resistive wall elements based on the Wakefield class. import numpy as np from scipy.constants import mu_0, epsilon_0, c -from collective_effects.wakefield import Wakefield, Impedance +from mbtrack2.collective_effects.wakefield import Wakefield, Impedance def skin_depth(omega, rho, mu_r = 1, epsilon_r = 1): """ General formula for the skin depth diff --git a/collective_effects/tapers.py b/collective_effects/tapers.py index 0e09b36bd476ef0d97915b6d92b861bc800375d2..34dde1855a4ab3b3b2fcb0f220da80010286ab3b 100644 --- a/collective_effects/tapers.py +++ b/collective_effects/tapers.py @@ -8,7 +8,7 @@ Created on Tue Mar 31 13:15:36 2020 from scipy.constants import mu_0, epsilon_0, c, pi import numpy as np from scipy.integrate import trapz -from collective_effects.wakefield import Wakefield, Impedance +from mbtrack2.collective_effects.wakefield import Wakefield, Impedance class StupakovRectangularTaper(Wakefield): """ diff --git a/collective_effects/tools.py b/collective_effects/tools.py deleted file mode 100644 index 44c0efc84bb475d8d23b34794ab52449bbcb2335..0000000000000000000000000000000000000000 --- a/collective_effects/tools.py +++ /dev/null @@ -1,412 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Tools and utilities functions for the Wakefield and Impedances classes. - -@author: Alexis Gamelin -@date: 24/04/2020 -""" - -import pandas as pd -import numpy as np -from scipy.integrate import trapz -from scipy.interpolate import interp1d -from collective_effects.wakefield import Wakefield, Impedance, WakeFunction - -def read_CST(file, impedance_type='long', divide_by=None): - """ - Read CST file format into an Impedance object. - - Parameters - ---------- - file : str - Path to the file to read. - impedance_type : str, optional - Type of the Impedance object. - divide_by : float, optional - Divide the impedance by a value. Mainly used to normalize transverse - impedance by displacement. - - Returns - ------- - result : Impedance object - Data from file. - """ - df = pd.read_csv(file, comment="#", header = None, sep = "\t", - names = ["Frequency","Real","Imaginary"]) - df["Frequency"] = df["Frequency"]*1e9 - if divide_by is not None: - df["Real"] = df["Real"]/divide_by - df["Imaginary"] = df["Imaginary"]/divide_by - if impedance_type == "long": - df["Real"] = np.abs(df["Real"]) - df.set_index("Frequency", inplace = True) - result = Impedance(variable = df.index, - function = df["Real"] + 1j*df["Imaginary"], - impedance_type=impedance_type) - return result - -def read_IW2D(file, impedance_type='long'): - """ - Read IW2D file format into an Impedance object. - - Parameters - ---------- - file : str - Path to the file to read. - impedance_type : str, optional - Type of the Impedance object. - - Returns - ------- - result : Impedance object - Data from file. - """ - df = pd.read_csv(file, sep = ' ', header = None, - names = ["Frequency","Real","Imaginary"], skiprows=1) - df.set_index("Frequency", inplace = True) - result = Impedance(variable = df.index, - function = df["Real"] + 1j*df["Imaginary"], - impedance_type=impedance_type) - return result - -def loss_factor(wake, sigma): - """ - Compute the loss factor or the kick factor of a Wakefield, Impedance or - WakeFunction for a Gaussian bunch. Formulas from Eq. (2) p239 and - Eq.(7) p240 of [1]. - - Parameters - ---------- - wake : Wakefield, Impedance or WakeFunction object. - sigma : float - RMS bunch length in [s] - - Returns - ------- - kloss: float - Loss factor in [V/C] or kick factor in [V/C/m] depanding on the - impedance type. - - References - ---------- - [1] : Handbook of accelerator physics and engineering, 3rd printing. - """ - - if isinstance(wake, Impedance): - - positive_index = wake.data.index > 0 - frequency = wake.data.index[positive_index] - sd = spectral_density(frequency, sigma, m=0) - - if(wake.impedance_type == "long"): - kloss = trapz(x = frequency, - y = 2*wake.data["real"][positive_index]*sd) - elif(wake.impedance_type == "xdip" or wake.impedance_type == "ydip"): - kloss = trapz(x = frequency, - y = 2*wake.data["imag"][positive_index]*sd) - elif(wake.impedance_type == "xquad" or wake.impedance_type == "yquad"): - kloss = trapz(x = frequency, - y = 2*wake.data["imag"][positive_index]*sd) - else: - raise TypeError("Impedance type not recognized.") - - if isinstance(wake, WakeFunction): - # To be implemented. - pass - - if isinstance(wake, Wakefield): - # To be implemented. Create field in wakefield with values ? - pass - - return kloss - -def spectral_density(frequency, sigma, m = 1, mode="Hermite"): - """ - Compute the spectral density of different modes for various values of the - head-tail mode number, based on Table 1 p238 of [1]. - - Parameters - ---------- - frequency : list or numpy array - sample points of the spectral density in [Hz] - sigma : float - RMS bunch length in [s] - m : int, optional - head-tail (or azimutal/synchrotron) mode number - mode: str, optional - type of the mode taken into account for the computation: - -"Hermite" modes for Gaussian bunches - - Returns - ------- - numpy array - - References - ---------- - [1] : Handbook of accelerator physics and engineering, 3rd printing. - """ - - if mode == "Hermite": - return (2*np.pi*frequency*sigma)**(2*m)*np.exp( - -1*(2*np.pi*frequency*sigma)**2) - else: - raise NotImplementedError("Not implemanted yet.") - -def Gaussian_bunch_spectrum(frequency, sigma): - """ - Compute a Gaussian bunch spectrum [1]. - - Parameters - ---------- - frequency : array - sample points of the beam spectrum in [Hz]. - sigma : float - RMS bunch length in [s]. - - Returns - ------- - bunch_spectrum : array - Bunch spectrum sampled at points given in frequency. - - References - ---------- - [1] : Gamelin, A. (2018). Collective effects in a transient microbunching - regime and ion cloud mitigation in ThomX. p86, Eq. 4.19 - """ - return np.exp(-1/2*(2*np.pi*frequency)**2*sigma**2) - - -def beam_spectrum(frequency, M, bunch_spacing, sigma=None, - bunch_spectrum=None): - """ - Compute the beam spectrum assuming constant spacing between bunches [1]. - - Parameters - ---------- - frequency : list or numpy array - sample points of the beam spectrum in [Hz]. - M : int - Number of bunches. - bunch_spacing : float - Time between two bunches in [s]. - sigma : float, optional - If bunch_spectrum is None then a Gaussian bunch with sigma RMS bunch - length in [s] is assumed. - bunch_spectrum : array, optional - Bunch spectrum sampled at points given in frequency. - - Returns - ------- - beam_spectrum : array - - References - ---------- - [1] Rumolo, G - Beam Instabilities - CAS - CERN Accelerator School: - Advanced Accelerator Physics Course - 2014, Eq. 9 - """ - - if bunch_spectrum is None: - bunch_spectrum = Gaussian_bunch_spectrum(frequency, sigma) - - beam_spectrum = (bunch_spectrum * np.exp(1j*np.pi*frequency * - bunch_spacing*(M-1)) * - np.sin(M*np.pi*frequency*bunch_spacing) / - np.sin(np.pi*frequency*bunch_spacing)) - - return beam_spectrum - - -def effective_impedance(ring, imp, m, mu, sigma, xi=None, mode="Hermite"): - """ - Compute the effective (longitudinal or transverse) impedance. - Formulas from Eq. (1) and (2) p238 of [1]. - - Parameters - ---------- - ring : Synchrotron object - imp : Impedance object - mu : int - coupled bunch mode number, goes from 0 to (M-1) where M is the - number of bunches - m : int - head-tail (or azimutal/synchrotron) mode number - sigma : float - RMS bunch length in [s] - xi : float, optional - (non-normalized) chromaticity - mode: str, optional - type of the mode taken into account for the computation: - -"Hermite" modes for Gaussian bunches - - Returns - ------- - Zeff : float - effective impedance in [ohm] or in [ohm/m] depanding on the impedance - type. - - [1] Handbook of accelerator physics and engineering, 3rd printing. - """ - - if not isinstance(imp, Impedance): - raise TypeError("{} should be an Impedance object.".format(imp)) - - fmin = imp.data.index.min() - fmax = imp.data.index.max() - if fmin > 0: - raise ValueError("A double sided impedance spectrum, with positive and" - " negative frequencies, is needed to compute the " - "effective impedance.") - - if mode == "Hermite": - def h(f): - return spectral_density(frequency=f, sigma=sigma, m=m, - mode="Hermite") - else: - raise NotImplementedError("Not implemanted yet.") - - M = 416 - - pmax = fmax/(ring.f0 * M) - 50 - pmin = fmin/(ring.f0 * M) + 50 - - p = np.arange(pmin,pmax+1) - - if imp.impedance_type == "long": - fp = ring.f0*(p*M + mu + m*ring.tune[2]) - fp = fp[np.nonzero(fp)] # Avoid division by 0 - num = np.sum( imp(fp) * h(fp) / (fp*2*np.pi) ) - den = np.sum( h(fp) ) - Zeff = num/den - - elif imp.impedance_type == "xdip" or imp.impedance_type == "ydip": - if imp.impedance_type == "xdip": - tuneXY = ring.tune[0] - xi = ring.chro[0] - elif imp.impedance_type == "ydip": - tuneXY = ring.tune[1] - xi = ring.chro[1] - fp = ring.f0*(p*M + mu + tuneXY + m*ring.tuneS) - f_xi = xi/ring.eta*ring.f0 - num = np.sum( imp(fp) * h(fp - f_xi) ) - den = np.sum( h(fp - f_xi) ) - Zeff = num/den - else: - raise TypeError("Effective impedance is only defined for long, xdip" - " and ydip impedance type.") - - return Zeff - - -def Yokoya_elliptic(x_radius , y_radius): - """ - Compute Yokoya factors for an elliptic beam pipe. - Function adapted from N. Mounet IW2D. - - Parameters - ---------- - x_radius : float - Horizontal semi-axis of the ellipse in [m]. - y_radius : float - Vertical semi-axis of the ellipse in [m]. - - Returns - ------- - yoklong : float - Yokoya factor for the longitudinal impedance. - yokxdip : float - Yokoya factor for the dipolar horizontal impedance. - yokydip : float - Yokoya factor for the dipolar vertical impedance. - yokxquad : float - Yokoya factor for the quadrupolar horizontal impedance. - yokyquad : float - Yokoya factor for the quadrupolar vertical impedance. - """ - if y_radius < x_radius: - small_semiaxis = y_radius - large_semiaxis = x_radius - else: - small_semiaxis = x_radius - large_semiaxis = y_radius - - # read Yokoya factors interpolation file - # BEWARE: columns are ratio, dipy, dipx, quady, quadx - yokoya_file = pd.read_csv("collective_effects/data/" + - "Yokoya_elliptic_from_Elias_USPAS.csv") - ratio_col = yokoya_file["x"] - # compute semi-axes ratio (first column of this file) - ratio = (large_semiaxis - small_semiaxis)/(large_semiaxis + small_semiaxis) - - # interpolate Yokoya file at the correct ratio - yoklong = 1 - - if y_radius < x_radius: - yokydip = np.interp(ratio, ratio_col, yokoya_file["dipy"]) - yokxdip = np.interp(ratio, ratio_col, yokoya_file["dipx"]) - yokyquad = np.interp(ratio, ratio_col, yokoya_file["quady"]) - yokxquad = np.interp(ratio, ratio_col, yokoya_file["quadx"]) - else: - yokxdip = np.interp(ratio, ratio_col, yokoya_file["dipy"]) - yokydip = np.interp(ratio, ratio_col, yokoya_file["dipx"]) - yokxquad = np.interp(ratio, ratio_col, yokoya_file["quady"]) - yokyquad = np.interp(ratio, ratio_col, yokoya_file["quadx"]) - - return (yoklong, yokxdip, yokydip, yokxquad, yokyquad) - -def beam_loss_factor(impedance, frequency, spectrum, ring): - """ - Compute "beam" loss factor using the beam spectrum, uses a sum instead of - integral compared to loss_factor [1]. - - Parameters - ---------- - impedance : Impedance of type "long" - frequency : array - Sample points of spectrum. - spectrum : array - Beam spectrum to consider. - ring : Synchrotron object - - Returns - ------- - kloss_beam : float - Beam loss factor in [V/C]. - - References - ---------- - [1] : Handbook of accelerator physics and engineering, 3rd printing. - Eq (3) p239. - """ - pmax = np.floor(impedance.data.index.max()/ring.f0) - pmin = np.floor(impedance.data.index.min()/ring.f0) - - if pmin >= 0: - # Add negative part - negative_index = impedance.data.index*-1 - negative_data = impedance.data.set_index(negative_index) - negative_data["imag"] = -1*negative_data["imag"] - try: - negative_data = negative_data.drop(0) - except KeyError: - pass - - all_data = impedance.data.append(negative_data) - all_data = all_data.sort_index() - impedance.data = all_data - - pmin = -1*pmax - - p = np.arange(pmin+1,pmax) - pf0 = p*ring.f0 - ReZ = np.real(impedance(pf0)) - spectral_density = np.abs(spectrum)**2 - # interpolation of the spectrum is needed to avoid problems liked to - # division by 0 - # computing the spectrum directly to the frequency points gives - # wrong results - spect = interp1d(frequency, spectral_density) - kloss_beam = ring.f0 * np.sum(ReZ*spect(pf0)) - - return kloss_beam - - diff --git a/collective_effects/wakefield.py b/collective_effects/wakefield.py index 4994c4a7a07e53e3660fa20e0c76ed0163d018b6..095090b7c6509cf443b943494c1efd738086d0b0 100644 --- a/collective_effects/wakefield.py +++ b/collective_effects/wakefield.py @@ -12,11 +12,11 @@ import re import pandas as pd import numpy as np import matplotlib.pyplot as plt -import collective_effects.tools as tools from scipy.interpolate import interp1d -from tracking.element import Element +from mbtrack2.tracking.element import Element from copy import deepcopy from scipy.integrate import trapz +from scipy.interpolate import interp1d class ComplexData: """ @@ -327,6 +327,45 @@ class Impedance(ComplexData): power_y += 1./2. return power_y + def loss_factor(self, sigma): + """ + Compute the loss factor or the kick factor for a Gaussian bunch. + Formulas from Eq. (2) p239 and Eq.(7) p240 of [1]. + + Parameters + ---------- + sigma : float + RMS bunch length in [s] + + Returns + ------- + kloss: float + Loss factor in [V/C] or kick factor in [V/C/m] depanding on the + impedance type. + + References + ---------- + [1] : Handbook of accelerator physics and engineering, 3rd printing. + """ + + positive_index = self.data.index > 0 + frequency = self.data.index[positive_index] + sd = spectral_density(frequency, sigma, m=0) + + if(self.impedance_type == "long"): + kloss = trapz(x = frequency, + y = 2*self.data["real"][positive_index]*sd) + elif(self.impedance_type == "xdip" or self.impedance_type == "ydip"): + kloss = trapz(x = frequency, + y = 2*self.data["imag"][positive_index]*sd) + elif(self.impedance_type == "xquad" or self.impedance_type == "yquad"): + kloss = trapz(x = frequency, + y = 2*self.data["imag"][positive_index]*sd) + else: + raise TypeError("Impedance type not recognized.") + + return kloss + class Wakefield: """ Defines a Wakefield which corresponds to a single physical element which @@ -703,7 +742,7 @@ class ImpedanceModel(Element): legend.append(attr) if sigma is not None: - spect = tools.spectral_density(zero_impedance.data.index, sigma) + spect = spectral_density(zero_impedance.data.index, sigma) spect = spect/spect.max()*total_imp.max() ax.plot(sum_imp.data.index*1e-9, spect, 'r', linewidth=2.5) @@ -731,7 +770,7 @@ class ImpedanceModel(Element): for j, component_name in enumerate(list_components): try: impedance = getattr(getattr(self, attr), component_name) - loss_array[j,i] = tools.loss_factor(impedance, sigma) + loss_array[j,i] = impedance.loss_factor(sigma) except AttributeError: pass @@ -800,5 +839,351 @@ class ImpedanceModel(Element): return summary + +def read_CST(file, impedance_type='long', divide_by=None): + """ + Read CST file format into an Impedance object. + + Parameters + ---------- + file : str + Path to the file to read. + impedance_type : str, optional + Type of the Impedance object. + divide_by : float, optional + Divide the impedance by a value. Mainly used to normalize transverse + impedance by displacement. + + Returns + ------- + result : Impedance object + Data from file. + """ + df = pd.read_csv(file, comment="#", header = None, sep = "\t", + names = ["Frequency","Real","Imaginary"]) + df["Frequency"] = df["Frequency"]*1e9 + if divide_by is not None: + df["Real"] = df["Real"]/divide_by + df["Imaginary"] = df["Imaginary"]/divide_by + if impedance_type == "long": + df["Real"] = np.abs(df["Real"]) + df.set_index("Frequency", inplace = True) + result = Impedance(variable = df.index, + function = df["Real"] + 1j*df["Imaginary"], + impedance_type=impedance_type) + return result + +def read_IW2D(file, impedance_type='long'): + """ + Read IW2D file format into an Impedance object. + + Parameters + ---------- + file : str + Path to the file to read. + impedance_type : str, optional + Type of the Impedance object. + + Returns + ------- + result : Impedance object + Data from file. + """ + df = pd.read_csv(file, sep = ' ', header = None, + names = ["Frequency","Real","Imaginary"], skiprows=1) + df.set_index("Frequency", inplace = True) + result = Impedance(variable = df.index, + function = df["Real"] + 1j*df["Imaginary"], + impedance_type=impedance_type) + return result + +def spectral_density(frequency, sigma, m = 1, mode="Hermite"): + """ + Compute the spectral density of different modes for various values of the + head-tail mode number, based on Table 1 p238 of [1]. + + Parameters + ---------- + frequency : list or numpy array + sample points of the spectral density in [Hz] + sigma : float + RMS bunch length in [s] + m : int, optional + head-tail (or azimutal/synchrotron) mode number + mode: str, optional + type of the mode taken into account for the computation: + -"Hermite" modes for Gaussian bunches + + Returns + ------- + numpy array + + References + ---------- + [1] : Handbook of accelerator physics and engineering, 3rd printing. + """ + + if mode == "Hermite": + return (2*np.pi*frequency*sigma)**(2*m)*np.exp( + -1*(2*np.pi*frequency*sigma)**2) + else: + raise NotImplementedError("Not implemanted yet.") + +def gaussian_bunch_spectrum(frequency, sigma): + """ + Compute a Gaussian bunch spectrum [1]. + + Parameters + ---------- + frequency : array + sample points of the beam spectrum in [Hz]. + sigma : float + RMS bunch length in [s]. + + Returns + ------- + bunch_spectrum : array + Bunch spectrum sampled at points given in frequency. + + References + ---------- + [1] : Gamelin, A. (2018). Collective effects in a transient microbunching + regime and ion cloud mitigation in ThomX. p86, Eq. 4.19 + """ + return np.exp(-1/2*(2*np.pi*frequency)**2*sigma**2) + + +def beam_spectrum(frequency, M, bunch_spacing, sigma=None, + bunch_spectrum=None): + """ + Compute the beam spectrum assuming constant spacing between bunches [1]. + + Parameters + ---------- + frequency : list or numpy array + sample points of the beam spectrum in [Hz]. + M : int + Number of bunches. + bunch_spacing : float + Time between two bunches in [s]. + sigma : float, optional + If bunch_spectrum is None then a Gaussian bunch with sigma RMS bunch + length in [s] is assumed. + bunch_spectrum : array, optional + Bunch spectrum sampled at points given in frequency. + + Returns + ------- + beam_spectrum : array + + References + ---------- + [1] Rumolo, G - Beam Instabilities - CAS - CERN Accelerator School: + Advanced Accelerator Physics Course - 2014, Eq. 9 + """ + + if bunch_spectrum is None: + bunch_spectrum = Gaussian_bunch_spectrum(frequency, sigma) + + beam_spectrum = (bunch_spectrum * np.exp(1j*np.pi*frequency * + bunch_spacing*(M-1)) * + np.sin(M*np.pi*frequency*bunch_spacing) / + np.sin(np.pi*frequency*bunch_spacing)) + + return beam_spectrum + + +def effective_impedance(ring, imp, m, mu, sigma, xi=None, mode="Hermite"): + """ + Compute the effective (longitudinal or transverse) impedance. + Formulas from Eq. (1) and (2) p238 of [1]. + + Parameters + ---------- + ring : Synchrotron object + imp : Impedance object + mu : int + coupled bunch mode number, goes from 0 to (M-1) where M is the + number of bunches + m : int + head-tail (or azimutal/synchrotron) mode number + sigma : float + RMS bunch length in [s] + xi : float, optional + (non-normalized) chromaticity + mode: str, optional + type of the mode taken into account for the computation: + -"Hermite" modes for Gaussian bunches + + Returns + ------- + Zeff : float + effective impedance in [ohm] or in [ohm/m] depanding on the impedance + type. + + [1] Handbook of accelerator physics and engineering, 3rd printing. + """ + + if not isinstance(imp, Impedance): + raise TypeError("{} should be an Impedance object.".format(imp)) + + fmin = imp.data.index.min() + fmax = imp.data.index.max() + if fmin > 0: + raise ValueError("A double sided impedance spectrum, with positive and" + " negative frequencies, is needed to compute the " + "effective impedance.") + + if mode == "Hermite": + def h(f): + return spectral_density(frequency=f, sigma=sigma, m=m, + mode="Hermite") + else: + raise NotImplementedError("Not implemanted yet.") + + M = 416 + + pmax = fmax/(ring.f0 * M) - 50 + pmin = fmin/(ring.f0 * M) + 50 + + p = np.arange(pmin,pmax+1) + + if imp.impedance_type == "long": + fp = ring.f0*(p*M + mu + m*ring.tune[2]) + fp = fp[np.nonzero(fp)] # Avoid division by 0 + num = np.sum( imp(fp) * h(fp) / (fp*2*np.pi) ) + den = np.sum( h(fp) ) + Zeff = num/den + + elif imp.impedance_type == "xdip" or imp.impedance_type == "ydip": + if imp.impedance_type == "xdip": + tuneXY = ring.tune[0] + xi = ring.chro[0] + elif imp.impedance_type == "ydip": + tuneXY = ring.tune[1] + xi = ring.chro[1] + fp = ring.f0*(p*M + mu + tuneXY + m*ring.tuneS) + f_xi = xi/ring.eta*ring.f0 + num = np.sum( imp(fp) * h(fp - f_xi) ) + den = np.sum( h(fp - f_xi) ) + Zeff = num/den + else: + raise TypeError("Effective impedance is only defined for long, xdip" + " and ydip impedance type.") + + return Zeff + + +def yokoya_elliptic(x_radius , y_radius): + """ + Compute Yokoya factors for an elliptic beam pipe. + Function adapted from N. Mounet IW2D. + + Parameters + ---------- + x_radius : float + Horizontal semi-axis of the ellipse in [m]. + y_radius : float + Vertical semi-axis of the ellipse in [m]. + + Returns + ------- + yoklong : float + Yokoya factor for the longitudinal impedance. + yokxdip : float + Yokoya factor for the dipolar horizontal impedance. + yokydip : float + Yokoya factor for the dipolar vertical impedance. + yokxquad : float + Yokoya factor for the quadrupolar horizontal impedance. + yokyquad : float + Yokoya factor for the quadrupolar vertical impedance. + """ + if y_radius < x_radius: + small_semiaxis = y_radius + large_semiaxis = x_radius + else: + small_semiaxis = x_radius + large_semiaxis = y_radius + + # read Yokoya factors interpolation file + # BEWARE: columns are ratio, dipy, dipx, quady, quadx + yokoya_file = pd.read_csv("collective_effects/data/" + + "Yokoya_elliptic_from_Elias_USPAS.csv") + ratio_col = yokoya_file["x"] + # compute semi-axes ratio (first column of this file) + ratio = (large_semiaxis - small_semiaxis)/(large_semiaxis + small_semiaxis) + + # interpolate Yokoya file at the correct ratio + yoklong = 1 + + if y_radius < x_radius: + yokydip = np.interp(ratio, ratio_col, yokoya_file["dipy"]) + yokxdip = np.interp(ratio, ratio_col, yokoya_file["dipx"]) + yokyquad = np.interp(ratio, ratio_col, yokoya_file["quady"]) + yokxquad = np.interp(ratio, ratio_col, yokoya_file["quadx"]) + else: + yokxdip = np.interp(ratio, ratio_col, yokoya_file["dipy"]) + yokydip = np.interp(ratio, ratio_col, yokoya_file["dipx"]) + yokxquad = np.interp(ratio, ratio_col, yokoya_file["quady"]) + yokyquad = np.interp(ratio, ratio_col, yokoya_file["quadx"]) + + return (yoklong, yokxdip, yokydip, yokxquad, yokyquad) + +def beam_loss_factor(impedance, frequency, spectrum, ring): + """ + Compute "beam" loss factor using the beam spectrum, uses a sum instead of + integral compared to loss_factor [1]. + + Parameters + ---------- + impedance : Impedance of type "long" + frequency : array + Sample points of spectrum. + spectrum : array + Beam spectrum to consider. + ring : Synchrotron object + + Returns + ------- + kloss_beam : float + Beam loss factor in [V/C]. + + References + ---------- + [1] : Handbook of accelerator physics and engineering, 3rd printing. + Eq (3) p239. + """ + pmax = np.floor(impedance.data.index.max()/ring.f0) + pmin = np.floor(impedance.data.index.min()/ring.f0) + + if pmin >= 0: + # Add negative part + negative_index = impedance.data.index*-1 + negative_data = impedance.data.set_index(negative_index) + negative_data["imag"] = -1*negative_data["imag"] + try: + negative_data = negative_data.drop(0) + except KeyError: + pass + + all_data = impedance.data.append(negative_data) + all_data = all_data.sort_index() + impedance.data = all_data + + pmin = -1*pmax + + p = np.arange(pmin+1,pmax) + pf0 = p*ring.f0 + ReZ = np.real(impedance(pf0)) + spectral_density = np.abs(spectrum)**2 + # interpolation of the spectrum is needed to avoid problems liked to + # division by 0 + # computing the spectrum directly to the frequency points gives + # wrong results + spect = interp1d(frequency, spectral_density) + kloss_beam = ring.f0 * np.sum(ReZ*spect(pf0)) + + return kloss_beam \ No newline at end of file diff --git a/machines/__init__.py b/machines/__init__.py deleted file mode 100644 index c67145e06e038f4a48ceda33f888dc60ab733eea..0000000000000000000000000000000000000000 --- a/machines/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Tue Jan 14 18:11:33 2020 - -@author: gamelina -""" - diff --git a/machines/soleil.py b/machines/soleil.py deleted file mode 100644 index c8ac723c058f7c2d1860dc7859939cd7b0adbf55..0000000000000000000000000000000000000000 --- a/machines/soleil.py +++ /dev/null @@ -1,63 +0,0 @@ -# -*- coding: utf-8 -*- -""" -SOLEIL synchrotron parameters script. - -@author: Alexis Gamelin -@date: 14/01/2020 -""" - -import numpy as np -from tracking.synchrotron import Synchrotron -from tracking.optics import Optics -from tracking.particles import Electron, Beam - -def soleil(mode = 'Uniform'): - """ - - """ - - h = 416 - L = 3.540969742590899e+02 - E0 = 2.75e9 - particle = Electron() - ac = 1.47e-4 - U0 = 0.310e6 - tau = np.array([6.56e-3, 6.56e-3, 3.27e-3]) - tune = np.array([18.15687, 10.22824, 0.00502]) - emit = np.array([4.5e-9, 4.5e-9*0.01]) - sigma_0 = 8e-12 - sigma_delta = 8.6e-4 - chro = [2,3] - - # mean values - beta = np.array([3, 1.3]) - alpha = np.array([0, 0]) - dispersion = np.array([0, 0, 0, 0]) - optics = Optics(local_beta=beta, local_alpha=alpha, - local_dispersion=dispersion) - - ring = Synchrotron(h, optics, particle, L=L, E0=E0, ac=ac, U0=U0, tau=tau, - emit=emit, tune=tune, sigma_delta=sigma_delta, - sigma_0=sigma_0, chro=chro) - - return ring - -def v0313(): - """ - - """ - - h = 416 - particle = Electron() - tau = np.array([7.3e-3, 13.1e-3, 11.7e-3]) - emit = np.array([53.47e-12, 53.47e-12]) - sigma_0 = 9.2e-12 - sigma_delta = 9e-4 - - lattice_file = "SOLEIL_U_74BA_HOA_SYM02_V0313_cleaned.mat" - optics = Optics(lattice_file=lattice_file) - - ring = Synchrotron(h, optics, particle, tau=tau, emit=emit, - sigma_0=sigma_0, sigma_delta=sigma_delta) - - return ring \ No newline at end of file diff --git a/plotting.py b/plotting.py deleted file mode 100644 index f03c40eefa605069541e65ebb0ad359a86036018..0000000000000000000000000000000000000000 --- a/plotting.py +++ /dev/null @@ -1,74 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Running script to test getplot method in Bunch class, particles module - -@author: Watanyu Foosang -@date: 27/03/2020 -""" - -import numpy as np -import matplotlib.pyplot as plt -import seaborn as sns -import pandas as pd -from tracking.particles import Bunch, Beam -from machines.soleil import soleil -from tracking.rf import * -from scipy.constants import e - -ring = soleil() # Define the properties of the storage ring -mybunch = Bunch(ring,mp_number=1000,current=1e-3) # Define bunch parameters - -mybunch.init_gaussian() # Initialize 6D bunch parameters - - -v_rf = 1.1e6 # RF peak voltage [v] -sync_phase = np.arccos(ring.U0/v_rf) # Determine the synchronous phase - -rf = RFCavity(ring, m=1, Vc=v_rf, theta=sync_phase) - -rev = [] -mean_tau = [] -mean_delta = [] -def track_long(bunch,ring,rf,turn): - """ - Track longitudinal beam parameters for one turn - """ - - rf.track(bunch) # update "delta" for one turn - bunch.particles["tau"] = bunch.particles["tau"] - bunch.particles["delta"]\ - *ring.T0*ring.ac - turn += 1 - rev.append(turn) - mean_tau.append(bunch.particles["tau"].mean()) - mean_delta.append(bunch.particles["delta"].mean()) - return turn - -def get_meanplot(ini,fin,step,val): - """ - Plot the evolution of a mean value over turns. - (For now, this function only works subject to plotting module.) - - Parameters - ---------- - ini: int, starting revolution - fin: int, final revolution - step: int, step size on the revolution axis - val: str, "tau" for maen values of tau or "del" for mean values of delta - - """ - - x = rev[ini-1:fin:step] - - if val == "tau": - val = mean_tau[ini-1:fin:step] - else: val = mean_delta[ini-1:fin:step] - - plt.plot(x,val) - - - - - - - - diff --git a/tracking/__init__.py b/tracking/__init__.py index c67145e06e038f4a48ceda33f888dc60ab733eea..45db00132ecab8571c819849c041e992ca8826fe 100644 --- a/tracking/__init__.py +++ b/tracking/__init__.py @@ -5,3 +5,14 @@ Created on Tue Jan 14 18:11:33 2020 @author: gamelina """ +from mbtrack2.tracking.particles import (Electron, Proton, Bunch, Beam, + Particle) +from mbtrack2.tracking.synchrotron import Synchrotron +from mbtrack2.tracking.rf import RFCavity +from mbtrack2.tracking.parallel import Mpi +from mbtrack2.tracking.optics import Optics, PhyisicalModel +from mbtrack2.tracking.element import (Element, LongitudinalMap, + TransverseMap, SynchrotronRadiation) +from mbtrack2.tracking.aperture import (CircularAperture, ElipticalAperture, + RectangularAperture) + diff --git a/tracking/aperture.py b/tracking/aperture.py index 4cba4b3de7d395777e340a22104b3cd8d9308498..177184563831d4ef5a93bce825127240c6d7a6c0 100644 --- a/tracking/aperture.py +++ b/tracking/aperture.py @@ -7,7 +7,7 @@ This module defines aperture elements for tracking. """ import numpy as np -from tracking.element import Element +from mbtrack2.tracking.element import Element class CircularAperture(Element): """ diff --git a/tracking/element.py b/tracking/element.py index bee031ce02323679e53832f4745196ef33dc1666..8692856c1c54e9f17f7a40fbed11bc693b0273f9 100644 --- a/tracking/element.py +++ b/tracking/element.py @@ -11,7 +11,7 @@ included in the tracking. import numpy as np from abc import ABCMeta, abstractmethod from functools import wraps -from tracking.particles import Beam +from mbtrack2.tracking.particles import Beam class Element(metaclass=ABCMeta): """ diff --git a/tracking/monitors/__init__.py b/tracking/monitors/__init__.py index c67145e06e038f4a48ceda33f888dc60ab733eea..028472c4c7fe270d4434391af894d9fcc56a35eb 100644 --- a/tracking/monitors/__init__.py +++ b/tracking/monitors/__init__.py @@ -5,3 +5,9 @@ Created on Tue Jan 14 18:11:33 2020 @author: gamelina """ +from mbtrack2.tracking.monitors.monitors import (Monitor, BunchMonitor, + PhaseSpaceMonitor, + BeamMonitor, + ProfileMonitor) +from mbtrack2.tracking.monitors.plotting import (plot_bunchdata, + plot_phasespacedata) \ No newline at end of file diff --git a/tracking/monitors/monitors.py b/tracking/monitors/monitors.py index e6234651dc30eed6693b8200f86ded7b96d7fbe9..c7c6ac86173bda7087ac8e529879671b9ff32544 100644 --- a/tracking/monitors/monitors.py +++ b/tracking/monitors/monitors.py @@ -10,8 +10,8 @@ during tracking. import numpy as np import h5py as hp -from tracking.element import Element -from tracking.particles import Bunch, Beam +from mbtrack2.tracking.element import Element +from mbtrack2.tracking.particles import Bunch, Beam from abc import ABCMeta from mpi4py import MPI diff --git a/tracking/particles.py b/tracking/particles.py index cb4c0468306f63b81fb9d45fc56f0127713b6f83..d3e7f171ab827c2aeec740a19c7cc17c940ee47d 100644 --- a/tracking/particles.py +++ b/tracking/particles.py @@ -10,7 +10,7 @@ Module where particles, bunches and beams are described as objects. import numpy as np import pandas as pd import matplotlib.pyplot as plt -from tracking.parallel import Mpi +from mbtrack2.tracking.parallel import Mpi import seaborn as sns from scipy.constants import c, m_e, m_p, e diff --git a/tracking/rf.py b/tracking/rf.py index 24928d33841f52697dccbf31a5ccf970ea8d889a..451d220c88b6bda6ea0aaafa7f00775a29699909 100644 --- a/tracking/rf.py +++ b/tracking/rf.py @@ -7,7 +7,7 @@ This module handles radio-frequency (RF) cavitiy elements. """ import numpy as np -from tracking.element import Element +from mbtrack2.tracking.element import Element class RFCavity(Element): """