diff --git a/mbtrack2/instability/__init__.py b/mbtrack2/instability/__init__.py index f7a1516f07a5ba85219041527106425accf715ce..6d59c921aa754f11e2d190f8b1a3bda1f0bf1625 100644 --- a/mbtrack2/instability/__init__.py +++ b/mbtrack2/instability/__init__.py @@ -14,3 +14,5 @@ from mbtrack2.instability.ions import ( ion_frequency, plot_critical_mass, ) +from mbtrack2.instability.space_charge import ( + transverse_space_charge_tune_shift, ) diff --git a/mbtrack2/instability/space_charge.py b/mbtrack2/instability/space_charge.py new file mode 100644 index 0000000000000000000000000000000000000000..bd763e319da49195876d22d001dabac54974e185 --- /dev/null +++ b/mbtrack2/instability/space_charge.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +""" +General calculations about space charge such as tune shift. +""" + +import numpy as np +from scipy.constants import c, epsilon_0, pi + + +def transverse_space_charge_tune_shift(ring, bunch_current, **kwargs): + """ + Return the (maximum) transverse space charge tune shift for a Gaussian + bunch in the linear approximation, see Eq.(1) of [1]. + + Parameters + ---------- + ring : Synchrotron object + Ring parameters. + bunch_current : float + Bunch current in [A]. + sigma_s : float, optional + RMS bunch length in [s]. + Default is ring.sigma_0. + emit_x : float, optional + Horizontal emittance in [m.rad]. + Default is ring.emit[0]. + emit_y : float, optional + Vertical emittance in [m.rad]. + Default is ring.emit[1]. + use_lattice : bool, optional + If True, use beta fonctions along the lattice. + If False, local values of beta fonctions are used. + Default is ring.optics.use_local_values. + sigma_delta : float, optional + Relative energy spread. + Default is ring.sigma_delta. + gamma : float, optional + Relativistic Lorentz gamma. + Default is ring.gamma. + + Returns + ------- + tune_shift : array of shape (2,) + Horizontal and vertical space charge tune shift. + + Reference + --------- + [1] : Antipov, S. A., Gubaidulin, V., Agapov, I., Garcia, E. C., & + Gamelin, A. (2024). Space Charge and Future Light Sources. + arXiv preprint arXiv:2409.08637. + + """ + sigma_s = kwargs.get('sigma_s', ring.sigma_0) + emit_x = kwargs.get('emit_x', ring.emit[0]) + emit_y = kwargs.get('emit_y', ring.emit[1]) + use_lattice = kwargs.get('use_lattice', not ring.optics.use_local_values) + sigma_delta = kwargs.get('sigma_delta', ring.sigma_delta) + gamma = kwargs.get('gamma', ring.gamma) + + q = np.abs(ring.particle.charge) + m = ring.particle.mass + r_0 = 1 / (4*pi*epsilon_0) * q**2 / (m * c**2) + N = bunch_current / ring.f0 / q + sigma_z = sigma_s * c + + if use_lattice: + s = np.linspace(0, ring.L, 1000) + beta = ring.optics.beta(s) + sig_x = (emit_x * beta[0] + + ring.optics.dispersion(s)[0]**2 * sigma_delta**2)**0.5 + sig_y = (emit_y * beta[1] + + ring.optics.dispersion(s)[2]**2 * sigma_delta**2)**0.5 + sig_xy = np.array([sig_x, sig_y]) + return -r_0 * N / ((2 * pi)**1.5 * gamma**3 * sigma_z) * np.trapz( + beta / (sig_xy * (sig_y+sig_x)), s) + else: + beta = ring.optics.local_beta + sig_x = np.sqrt(beta[0] * emit_x) + sig_y = np.sqrt(beta[1] * emit_y) + sig_xy = np.array([sig_x, sig_y]) + return -r_0 * N * ring.L / ( + (2 * pi)**1.5 * gamma**3 * sigma_z) * beta / (sig_xy * + (sig_y+sig_x))