diff --git a/mbtrack2/utilities/misc.py b/mbtrack2/utilities/misc.py index c31af03d547338323c8ca1d98e886eefd02fe853..6aaabef166b458927b868cfa816dd967fb711cba 100644 --- a/mbtrack2/utilities/misc.py +++ b/mbtrack2/utilities/misc.py @@ -8,6 +8,7 @@ from pathlib import Path import numpy as np import pandas as pd from scipy.interpolate import interp1d +from scipy.special import gamma, factorial, hyp2f1 from mbtrack2.impedance.wakefield import Impedance from mbtrack2.utilities.spectrum import spectral_density @@ -168,7 +169,6 @@ def tune_shift_from_effective_impedance(Zeff): def yokoya_elliptic(x_radius, y_radius): """ Compute Yokoya factors for an elliptic beam pipe. - Function adapted from N. Mounet IW2D. Parameters ---------- @@ -189,38 +189,156 @@ def yokoya_elliptic(x_radius, y_radius): Yokoya factor for the quadrupolar horizontal impedance. yokyquad : float Yokoya factor for the quadrupolar vertical impedance. + + References + ---------- + [1] : M. Migliorati, L. Palumbo, C. Zannini, N. Biancacci, and + V. G. Vaccaro, "Resistive wall impedance in elliptical multilayer vacuum + chambers." Phys. Rev. Accel. Beams 22, 121001 (2019). + [2] : R.L. Gluckstern, J. van Zeijts, and B. Zotter, "Coupling impedance + of beam pipes of general cross section." Phys. Rev. E 47, 656 (1993). + """ - if y_radius < x_radius: - small_semiaxis = y_radius - large_semiaxis = x_radius - else: - small_semiaxis = x_radius - large_semiaxis = y_radius - - path_to_file = Path(__file__).parent - file = path_to_file / "data" / "Yokoya_elliptic_from_Elias_USPAS.csv" - - # read Yokoya factors interpolation file - # BEWARE: columns are ratio, dipy, dipx, quady, quadx - yokoya_file = pd.read_csv(file) - 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"]) + + if (x_radius <= 0) or (y_radius <= 0): + raise ValueError("Both radii must be non-zero positive values.") + elif (x_radius == np.inf) and (y_radius == np.inf): + raise ValueError("Both radii have infinite values.") + elif x_radius == np.inf: + yoklong = 1.0 + yokxdip = np.pi**2/24 + yokydip = np.pi**2/12 + yokxquad = -np.pi**2/24 + yokyquad = np.pi**2/24 + elif y_radius == np.inf: + yoklong = 1.0 + yokxdip = np.pi**2/12 + yokydip = np.pi**2/24 + yokxquad = np.pi**2/24 + yokyquad = -np.pi**2/24 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"]) + if y_radius < x_radius: + small_semiaxis = y_radius + large_semiaxis = x_radius + else: + small_semiaxis = x_radius + large_semiaxis = y_radius + + qr = (large_semiaxis - small_semiaxis) / (large_semiaxis + small_semiaxis) + if qr == 0: + yoklong = 1.0 + yokxdip = 1.0 + yokydip = 1.0 + yokxquad = 0.0 + yokyquad = 0.0 + + else: + # Define form factor functions + def function_ff(small_semiaxis, F, mu_b, ip, il): + coeff_fflong = 2*np.sqrt(2)*small_semiaxis/(np.pi*F) + coeff_fftrans = np.sqrt(2)*small_semiaxis**3/(np.pi*F**3) + + fflong = ( coeff_fflong*(-1)**ip/np.cosh(2*ip*mu_b) + * (-1)**il/np.cosh(2*il*mu_b) ) + ffdx = ( coeff_fftrans*(-1)**ip*(2*ip+1)/np.cosh((2*ip+1)*mu_b) + * (-1)**il*(2*il+1)/np.cosh((2*il+1)*mu_b) ) + ffdy = ( coeff_fftrans*(-1)**ip*(2*ip+1)/np.sinh((2*ip+1)*mu_b) + * (-1)**il*(2*il+1)/np.sinh((2*il+1)*mu_b) ) + ffquad = ( coeff_fftrans*(-1)**ip*(2*ip)**2/np.cosh(2*ip*mu_b) + * (-1)**il/np.cosh(2*il*mu_b) ) + + return (fflong, ffdx, ffdy, ffquad) + + def function_L(mu_b, ip, il): + common_L = ( np.sqrt(2)*np.pi*np.exp(-(2*abs(ip-il)+1)*mu_b) + * gamma(0.5+abs(ip-il))/(gamma(0.5)*factorial(abs(ip-il))) + * hyp2f1(0.5, abs(ip-il)+0.5, abs(ip-il)+1, np.exp(-4*mu_b)) ) + val_m = ( np.sqrt(2)*np.pi*np.exp(-(2*ip+2*il+1)*mu_b) + * gamma(0.5+ip+il)/(gamma(0.5)*factorial(ip+il)) + * hyp2f1(0.5, ip+il+0.5, ip+il+1, np.exp(-4*mu_b)) ) + val_d = ( np.sqrt(2)*np.pi*np.exp(-(2*ip+2*il+3)*mu_b) + * gamma(1.5+ip+il)/(gamma(0.5)*factorial(ip+il+1)) + * hyp2f1(0.5, ip+il+1.5, ip+il+2, np.exp(-4*mu_b)) ) + + Lm = common_L + val_m + Ldx = common_L + val_d + Ldy = common_L - val_d + + return (Lm, Ldx, Ldy) + + ip_range = np.arange(51) + il_range = np.arange(51) + ip, il = np.meshgrid(ip_range, il_range, indexing="ij") + + coeff_long = np.where( (ip == 0) & (il == 0), 0.25, + np.where((ip == 0) | (il == 0), 0.5, 1.0) ) + coeff_quad = np.where(il == 0, 0.5, 1.0) + + # Our equations are approximately valid only for qr (ratio) values + # less than or equal to 0.8. + qr_th = 0.8 + + if qr < qr_th: + F = np.sqrt(large_semiaxis**2 - small_semiaxis**2) + mu_b = np.arccosh(large_semiaxis/F) + + ff_values = np.array(function_ff(small_semiaxis, F, mu_b, ip, il)) + L_values = np.array(function_L(mu_b, ip, il)) + + yoklong = np.sum(coeff_long * ff_values[0] * L_values[0]) + yokxdip = np.sum(ff_values[1] * L_values[1]) + yokydip = np.sum(ff_values[2] * L_values[2]) + yokxquad = -np.sum(coeff_quad * ff_values[3] * L_values[0]) + yokyquad = -yokxquad + + if y_radius > x_radius: + yokxdip, yokydip = yokydip, yokxdip + yokxquad, yokyquad = yokyquad, yokxquad + + # Beyond the threshold (qr >= qr_th), they may not be valid, + # but should converge to Yokoya form factors of parallel plates. + # Fortunately, beyond this threshold, they should show asymptotic behavior, + # so we perform linear interpolation. + else: + yoklong_pp = 1.0 + yokxdip_pp = np.pi**2/24 + yokydip_pp = np.pi**2/12 + yokxquad_pp = -np.pi**2/24 + yokyquad_pp = np.pi**2/24 + + small_semiaxis_th = large_semiaxis*(1-qr_th)/(1+qr_th) + F_th = np.sqrt(large_semiaxis**2 - small_semiaxis_th**2) + mu_b_th = np.arccosh(large_semiaxis/F_th) + + ff_values_th = np.array(function_ff(small_semiaxis_th, F_th, mu_b_th, ip, il)) + L_values_th = np.array(function_L(mu_b_th, ip, il)) + + yoklong_th = np.sum(coeff_long * ff_values_th[0] * L_values_th[0]) + yokxdip_th = np.sum(ff_values_th[1] * L_values_th[1]) + yokydip_th = np.sum(ff_values_th[2] * L_values_th[2]) + yokxquad_th = -np.sum(coeff_quad * ff_values_th[3] * L_values_th[0]) + yokyquad_th = -yokxquad_th + + if y_radius > x_radius: + yokxdip_th, yokydip_th = yokydip_th, yokxdip_th + yokxquad_th, yokyquad_th = yokyquad_th, yokxquad_th + yokxdip_pp, yokydip_pp = yokydip_pp, yokxdip_pp + yokxquad_pp, yokyquad_pp = yokyquad_pp, yokxquad_pp + + qr_array = np.array([qr_th, 1.0]) + yoklong_array = np.array([yoklong_th, yoklong_pp]) + yokxdip_array = np.array([yokxdip_th, yokxdip_pp]) + yokydip_array = np.array([yokydip_th, yokydip_pp]) + yokxquad_array = np.array([yokxquad_th, yokxquad_pp]) + yokyquad_array = np.array([yokyquad_th, yokyquad_pp]) + + yoklong = np.interp(qr, qr_array, yoklong_array) + yokxdip = np.interp(qr, qr_array, yokxdip_array) + yokydip = np.interp(qr, qr_array, yokydip_array) + yokxquad = np.interp(qr, qr_array, yokxquad_array) + yokyquad = np.interp(qr, qr_array, yokyquad_array) + return (yoklong, yokxdip, yokydip, yokxquad, yokyquad)