|
| 1 | +import numpy as np |
| 2 | +from scipy import integrate |
| 3 | +from scipy.constants import c as clight |
| 4 | +from scipy.constants import e as qe |
| 5 | + |
| 6 | + |
| 7 | +def beta(z, beta0, alpha_z0): |
| 8 | + '''Beta function in drift space''' |
| 9 | + return beta0-2*alpha_z0*z+(1+alpha_z0**2)/beta0*z**2 |
| 10 | + |
| 11 | +def dispersion(z, d0, dp0): |
| 12 | + '''Dispersion in drift space''' |
| 13 | + return d0+z*dp0 |
| 14 | + |
| 15 | +def sigma(beta, epsilon0, betagamma): |
| 16 | + '''Betatronic sigma''' |
| 17 | + return np.sqrt(beta*epsilon0/betagamma) |
| 18 | + |
| 19 | +def L(f, nb, |
| 20 | + N1, N2, |
| 21 | + x_1, x_2, |
| 22 | + y_1, y_2, |
| 23 | + px_1, px_2, |
| 24 | + py_1, py_2, |
| 25 | + energy_tot1, energy_tot2, |
| 26 | + deltap_p0_1, deltap_p0_2, |
| 27 | + epsilon_x1, epsilon_x2, |
| 28 | + epsilon_y1, epsilon_y2, |
| 29 | + sigma_z1, sigma_z2, |
| 30 | + beta_x1, beta_x2, |
| 31 | + beta_y1, beta_y2, |
| 32 | + alpha_x1, alpha_x2, |
| 33 | + alpha_y1, alpha_y2, |
| 34 | + dx_1, dx_2, |
| 35 | + dy_1, dy_2, |
| 36 | + dpx_1, dpx_2, |
| 37 | + dpy_1, dpy_2, |
| 38 | + CC_V_x_1=0, CC_f_x_1=0, CC_phase_x_1=0, |
| 39 | + CC_V_x_2=0, CC_f_x_2=0, CC_phase_x_2=0, |
| 40 | + CC_V_y_1=0, CC_f_y_1=0, CC_phase_y_1=0, |
| 41 | + CC_V_y_2=0, CC_f_y_2=0, CC_phase_y_2=0, |
| 42 | + R12_1=0, R22_1=0, R34_1=0, R44_1=0, |
| 43 | + R12_2=0, R22_2=0, R34_2=0, R44_2=0, |
| 44 | + verbose=False, sigma_integration=3): |
| 45 | + ''' |
| 46 | + Returns luminosity in Hz/cm^2. |
| 47 | + f: revolution frequency |
| 48 | + nb: number of colliding bunch per beam in the specific Interaction Point (IP). |
| 49 | + N1,2: B1,2 number of particle per bunch |
| 50 | + x,y,1,2: horizontal/vertical position at the IP of B1,2, as defined in MADX [m] |
| 51 | + px,y,1,2: px,py at the IP of B1,2, as defined in MADX |
| 52 | + energy_tot1,2: total energy of the B1,2 [GeV] |
| 53 | + deltap_p0_1,2: rms momentum spread of B1,2 (formulas assume Gaussian off-momentum distribution) |
| 54 | + epsilon_x,y,1,2: horizontal/vertical normalized emittances of B1,2 [m rad] |
| 55 | + sigma_z1,2: rms longitudinal spread in z of B1,2 [m] |
| 56 | + beta_x,y,1,2: horizontal/vertical beta-function at IP of B1,2 [m] |
| 57 | + alpha_x,y,1,2: horizontal/vertical alpha-function at IP of B1,2 |
| 58 | + dx,y_1,2: horizontal/vertical dispersion-function at IP of B1,2, as defined in MADX [m] |
| 59 | + dpx,y_1,2: horizontal/vertical differential-dispersion-function IP of B1,2, as defined in MADX |
| 60 | + CC_V_x,y,1,2: B1,2 H/V CC total of the cavities that the beam sees before reaching the IP [V] |
| 61 | + CC_f_x,y,1,2: B1,2 H/V CC frequency of cavities that the beam sees before reaching the IP [Hz] |
| 62 | + CC_phase_1,2: B1,2 H/V CC phase of cavities that the beam sees before reaching the IP. |
| 63 | + Sinusoidal function with respect to the center of the bunch is assumed. |
| 64 | + Therefore 0 rad means no kick for the central longitudinal slice [rad] |
| 65 | + RAB_1,2: B1,2 equivalent H/V linear transfer matrix coefficients between the CC |
| 66 | + that the beam sees before reaching the IP and IP itself [SI units] |
| 67 | + verbose: to have verbose output |
| 68 | + sigma_integration: the number of sigma consider for the integration |
| 69 | + (taken into account only if CC(s) is/are present) |
| 70 | + In MAD-X px is p_x/p_0 (p_x is the x-component of the momentum and p_0 is the design momentum). |
| 71 | + In our approximation we use the paraxial approximation: p_0~p_z so px is an angle. |
| 72 | + Similar arguments holds for py. |
| 73 | + In MAD-X, dx and dpx are the literature dispersion and is derivative in s divided by the relatistic beta. |
| 74 | + In fact, since pt=beta*deltap, where beta is the relativistic Lorentz factor, |
| 75 | + those functions given by MAD-X must be multiplied by beta a number of times equal to the order of |
| 76 | + the derivative to find the functions given in the literature. |
| 77 | + To note that dpx is normalized by the reference momentum (p_s) and not the design momentum (p_0), |
| 78 | + ps = p0(1+deltap). We assume that dpx is the z-derivative of the px. |
| 79 | + ''' |
| 80 | + |
| 81 | + gamma1 = energy_tot1 / 0.93827231 # To be generalized for ions |
| 82 | + br_1 = np.sqrt(1-1/gamma1**2) |
| 83 | + betagamma_1 = br_1*gamma1 |
| 84 | + |
| 85 | + gamma2 = energy_tot2 / 0.93827231 # To be generalized for ions |
| 86 | + br_2 = np.sqrt(1-1/gamma2**2) |
| 87 | + betagamma_2 = br_2*gamma2 |
| 88 | + |
| 89 | + q = qe # To be generalized for ions |
| 90 | + |
| 91 | + # module of B1 speed |
| 92 | + v0_1=br_1*clight |
| 93 | + # paraxial hypothesis |
| 94 | + vx_1=v0_1*px_1 |
| 95 | + vy_1=v0_1*py_1 |
| 96 | + vz_1=v0_1*np.sqrt(1-px_1**2-py_1**2) |
| 97 | + v_1=np.array([vx_1, vy_1, vz_1]) |
| 98 | + |
| 99 | + v0_2=br_2*clight # module of B2 speed |
| 100 | + # Assuming counter rotating B2 ('-' sign) |
| 101 | + vx_2=-v0_2*px_2 |
| 102 | + vy_2=-v0_2*py_2 |
| 103 | + # assuming px_2**2+py_2**2 < 1 |
| 104 | + vz_2=-v0_2*np.sqrt(1-px_2**2-py_2**2) |
| 105 | + v_2=np.array([vx_2, vy_2, vz_2]) |
| 106 | + |
| 107 | + if verbose: |
| 108 | + print(f'B1 velocity vector:{v_1}') |
| 109 | + print(f'B2 velocity vector:{v_2}') |
| 110 | + |
| 111 | + diff_v = v_1-v_2 |
| 112 | + cross_v= np.cross(v_1, v_2) |
| 113 | + |
| 114 | + # normalized to get 1 for the ideal case |
| 115 | + # NB we assume px_1 and py_1 constant along the z-slices |
| 116 | + # NOT TRUE FOR CC! In any case the Moeller efficiency is almost equal to 1 in most cases... |
| 117 | + Moeller_efficiency=np.sqrt(clight**2*np.dot(diff_v,diff_v)-np.dot(cross_v,cross_v))/clight**2/2 |
| 118 | + |
| 119 | + def sx1(z): |
| 120 | + '''The sigma_x of B1, quadratic sum of betatronic and dispersive sigma''' |
| 121 | + return np.sqrt(sigma(beta(z, beta_x1, alpha_x1), epsilon_x1, betagamma_1)**2 \ |
| 122 | + + (dispersion(z, br_1*dx_1, br_1*dpx_1)*deltap_p0_1)**2) |
| 123 | + |
| 124 | + def sy1(z): |
| 125 | + '''The sigma_y of B1, quadratic sum of betatronic and dispersive sigma''' |
| 126 | + return np.sqrt(sigma(beta(z, beta_y1, alpha_y1), epsilon_y1, betagamma_1)**2 \ |
| 127 | + + (dispersion(z, br_1*dy_1, br_1*dpy_1)*deltap_p0_1)**2) |
| 128 | + |
| 129 | + def sx2(z): |
| 130 | + '''The sigma_x of B2, quadratic sum of betatronic and dispersive sigma''' |
| 131 | + return np.sqrt(sigma(beta(z, beta_x2, alpha_x2), epsilon_x2, betagamma_2)**2 \ |
| 132 | + + (dispersion(z, br_2*dx_2, br_2*dpx_2)*deltap_p0_2)**2) |
| 133 | + |
| 134 | + def sy2(z): |
| 135 | + '''The sigma_y of B2, quadratic sum of betatronic and dispersive sigma''' |
| 136 | + return np.sqrt(sigma(beta(z, beta_y2, alpha_y2), epsilon_y2, betagamma_2)**2 \ |
| 137 | + + (dispersion(z, br_2*dy_2, br_2*dpy_2)*deltap_p0_2)**2) |
| 138 | + |
| 139 | + sigma_z=np.max([sigma_z1, sigma_z2]) |
| 140 | + |
| 141 | + if not [CC_V_x_1, CC_V_y_1, CC_V_x_2, CC_V_y_2]==[0,0,0,0]: |
| 142 | + def theta_x_1(delta_z): |
| 143 | + # Eq. 3 of https://espace.cern.ch/acc-tec-sector/Chamonix/Chamx2012/papers/RC_9_04.pdf |
| 144 | + return CC_V_x_1/energy_tot1/1e9*np.sin(CC_phase_x_1 + 2*np.pi*CC_f_x_1/c*delta_z) |
| 145 | + |
| 146 | + def theta_y_1(delta_z): |
| 147 | + return CC_V_y_1/energy_tot1/1e9*np.sin(CC_phase_y_1 + 2*np.pi*CC_f_y_1/c*delta_z) |
| 148 | + |
| 149 | + def theta_x_2(delta_z): |
| 150 | + return CC_V_x_2/energy_tot2/1e9*np.sin(CC_phase_x_2 + 2*np.pi*CC_f_x_2/c*delta_z) |
| 151 | + |
| 152 | + def theta_y_2(delta_z): |
| 153 | + return CC_V_y_2/energy_tot2/1e9*np.sin(CC_phase_y_2 + 2*np.pi*CC_f_y_2/c*delta_z) |
| 154 | + |
| 155 | + def mx1(z, t): |
| 156 | + '''The mu_x of B1 as straight line''' |
| 157 | + return x_1 + R12_1*theta_x_1(z-c*t) + (px_1+R22_1*theta_x_1(z-c*t))*z |
| 158 | + |
| 159 | + def my1(z, t): |
| 160 | + '''The mu_y of B1 as straight line''' |
| 161 | + return y_1 + R34_1*theta_y_1(z-c*t) + (py_1+R44_1*theta_y_1(z-c*t))*z |
| 162 | + |
| 163 | + def mx2(z, t): |
| 164 | + '''The mu_x of B2 as straight line''' |
| 165 | + return x_2 + R12_2*theta_x_2(z+c*t) + (px_2+R22_2*theta_x_2(z+c*t))*z |
| 166 | + |
| 167 | + def my2(z, t): |
| 168 | + '''The mu_y of B2 as straight line''' |
| 169 | + return y_2 + R34_2*theta_y_2(z+c*t) + (py_2+R44_2*theta_y_2(z+c*t))*z |
| 170 | + |
| 171 | + def kernel_double_integral(t, z): |
| 172 | + return np.exp(0.5*(-(mx1(z, t) - mx2(z, t))**2/(sx1(z)**2 + sx2(z)**2) \ |
| 173 | + -(my1(z, t) - my2(z, t))**2/(sy1(z)**2 + sy2(z)**2) \ |
| 174 | + -(-br_1*c*t+z)**2/(sigma_z1**2) \ |
| 175 | + -( br_2*c*t+z)**2/(sigma_z2**2))) \ |
| 176 | + /np.sqrt((sx1(z)**2 + sx2(z)**2)*(sy1(z)**2 + sy2(z)**2))/sigma_z1/sigma_z2 |
| 177 | + |
| 178 | + integral=integrate.dblquad((lambda t, z: kernel_double_integral(t, z)), |
| 179 | + -sigma_integration*sigma_z, sigma_integration*sigma_z,-sigma_integration*sigma_z/c, sigma_integration*sigma_z/c) |
| 180 | + L0=f*N1*N2*nb*c/2/np.pi**(2)*integral[0] |
| 181 | + else: |
| 182 | + def mx1(z): |
| 183 | + '''The mu_x of B1 as straight line''' |
| 184 | + return x_1 + px_1*z |
| 185 | + |
| 186 | + def my1(z): |
| 187 | + '''The mu_y of B1 as straight line''' |
| 188 | + return y_1 + py_1*z |
| 189 | + |
| 190 | + def mx2(z): |
| 191 | + '''The mu_x of B2 as straight line''' |
| 192 | + return x_2 + px_2*z |
| 193 | + |
| 194 | + def my2(z): |
| 195 | + '''The mu_y of B2 as straight line''' |
| 196 | + return y_2 + py_2*z |
| 197 | + |
| 198 | + def kernel_single_integral(z): |
| 199 | + return np.exp(0.5*(-(mx1(z) - mx2(z))**2/(sx1(z)**2 + sx2(z)**2) \ |
| 200 | + -(my1(z) - my2(z))**2/(sy1(z)**2 + sy2(z)**2) \ |
| 201 | + -((br_1+br_2)**2*z**2)/(br_2**2*sigma_z1**2 + br_1**2*sigma_z2**2))) \ |
| 202 | + /np.sqrt((sx1(z)**2 + sx2(z)**2)*(sy1(z)**2 + sy2(z)**2)*(sigma_z1**2 + sigma_z2**2)) |
| 203 | + |
| 204 | + integral=integrate.quad(lambda z: kernel_single_integral(z), -20*sigma_z, 20*sigma_z) |
| 205 | + L0=f*N1*N2*nb/np.sqrt(2)/np.pi**(3/2)*integral[0] |
| 206 | + result= L0*Moeller_efficiency/1e4 |
| 207 | + if verbose: |
| 208 | + print(f'Moeller efficiency: {Moeller_efficiency}') |
| 209 | + print(f'Integral Relative Error: {integral[1]/integral[0]}') |
| 210 | + print(f'==> Luminosity [Hz/cm^2]: {result}') |
| 211 | + return result |
| 212 | + |
| 213 | +def get_luminosity_dict(mad, twiss_dfs, ip_name, number_of_ho_collisions): |
| 214 | + b1=mad.sequence.lhcb1.beam |
| 215 | + b2=mad.sequence.lhcb2.beam |
| 216 | + assert b1.freq0==b2.freq0 |
| 217 | + ip_b1=twiss_dfs['lhcb1'].loc[f'{ip_name}:1'] |
| 218 | + ip_b2=twiss_dfs['lhcb2'].loc[f'{ip_name}:1'] |
| 219 | + return { |
| 220 | + 'f':b1.freq0*1e6, 'nb':number_of_ho_collisions, |
| 221 | + 'N1':b1.npart, 'N2':b2.npart, |
| 222 | + 'energy_tot1':b1.energy, 'energy_tot2':b2.energy, |
| 223 | + 'deltap_p0_1':b1.sige, 'deltap_p0_2':b2.sige, |
| 224 | + 'epsilon_x1':b1.exn, 'epsilon_x2':b2.exn, |
| 225 | + 'epsilon_y1':b1.eyn, 'epsilon_y2':b2.eyn, |
| 226 | + 'sigma_z1':b1.sigt, 'sigma_z2':b2.sigt, |
| 227 | + 'beta_x1':ip_b1.betx, 'beta_x2':ip_b2.betx, |
| 228 | + 'beta_y1':ip_b1.bety, 'beta_y2':ip_b2.bety, |
| 229 | + 'alpha_x1':ip_b1.alfx, 'alpha_x2':ip_b2.alfx, |
| 230 | + 'alpha_y1':ip_b1.alfy, 'alpha_y2':ip_b2.alfy, |
| 231 | + 'dx_1':ip_b1.dx, 'dx_2':ip_b2.dx, |
| 232 | + 'dpx_1':ip_b1.dpx, 'dpx_2':ip_b2.dpx, |
| 233 | + 'dy_1':ip_b1.dy, 'dy_2':ip_b2.dy, |
| 234 | + 'dpy_1':ip_b1.dpy, 'dpy_2':ip_b2.dpy, |
| 235 | + 'x_1':ip_b1.x, 'x_2':ip_b2.x, |
| 236 | + 'px_1':ip_b1.px, 'px_2':ip_b2.px, |
| 237 | + 'y_1':ip_b1.y, 'y_2':ip_b2.y, |
| 238 | + 'py_1':ip_b1.py, 'py_2':ip_b2.py, 'verbose':False} |
| 239 | + |
| 240 | +def compute_luminosity(mad, twiss_dfs, ip_name, number_of_ho_collisions): |
| 241 | + return L(**get_luminosity_dict(mad, twiss_dfs, ip_name, number_of_ho_collisions)) |
| 242 | + |
| 243 | +def print_luminosity(mad, twiss_dfs, nco_IP1, nco_IP2, nco_IP5, nco_IP8): |
| 244 | + for ip, number_of_ho_collisions in zip(['ip1', 'ip2', 'ip5', 'ip8'], |
| 245 | + [nco_IP1, nco_IP2, nco_IP5, nco_IP8]): |
| 246 | + myL=compute_luminosity(mad, twiss_dfs, ip, number_of_ho_collisions) |
| 247 | + print(f'L in {ip} is {myL} Hz/cm^2') |
0 commit comments