|
| 1 | +#--------------------------------------------------------- |
| 2 | +# This example tracks the particles uniformly distributed |
| 3 | +# inside the sphere in the bunch rest coordinate system. |
| 4 | +# After tracking through the drift one or several times |
| 5 | +# the ratios of x,y,z rms sizes should be the same. |
| 6 | +# Here we use 2.5D Rick Baartman's Space Charge model. |
| 7 | +#--------------------------------------------------------- |
| 8 | +# 2.5D Rick Baartman's Space Charge model should not work |
| 9 | +# for the linac case here it is just as example of setting |
| 10 | +# another SC model. |
| 11 | +# N.B. Model is wrong! |
| 12 | +#--------------------------------------------------------- |
| 13 | + |
| 14 | +import sys |
| 15 | +import math |
| 16 | +import random |
| 17 | + |
| 18 | +from orbit.core.bunch import Bunch, BunchTwissAnalysis |
| 19 | + |
| 20 | +from orbit.lattice import AccLattice, AccNode, AccActionsContainer |
| 21 | +from orbit.teapot import teapot |
| 22 | + |
| 23 | +from orbit.core.spacecharge import SpaceChargeCalc3D |
| 24 | +from orbit.space_charge.sc3d import setSC3DAccNodes |
| 25 | + |
| 26 | +from orbit.space_charge.sc3d import setUniformEllipsesSCAccNodes |
| 27 | +from orbit.core.spacecharge import SpaceChargeCalcUnifEllipse |
| 28 | + |
| 29 | +from orbit.space_charge.sc2p5d import setSC2p5DrbAccNodes |
| 30 | +from orbit.core.spacecharge import SpaceChargeCalc2p5Drb |
| 31 | + |
| 32 | +#------------------------------ |
| 33 | +# Let's make everything random |
| 34 | +#------------------------------ |
| 35 | +random.seed(10) |
| 36 | + |
| 37 | +#------------------------------ |
| 38 | +# Auxilary functions |
| 39 | +#------------------------------ |
| 40 | + |
| 41 | +def func_theory(x): |
| 42 | + """ |
| 43 | + The function used in the charged sphere expansion dynamics calculation. |
| 44 | + See the presentation. |
| 45 | + """ |
| 46 | + val = math.sqrt(abs(x**2-1)) |
| 47 | + val = math.log(x+val)/2 + x*val/2 |
| 48 | + return val |
| 49 | + |
| 50 | +def getR_Theory(s_path,r_init,R,Ntot,bunch): |
| 51 | + """ |
| 52 | + The root finding in the charged sphere expansion dynamics calculation. |
| 53 | + See the presentation. |
| 54 | + s_path - path length / drift length [] |
| 55 | + r_init - initial distance from the sphere center [m] |
| 56 | + R - bunch sphere radius [m] |
| 57 | + Ntot - total macro-size of the bunch |
| 58 | + beta - relativistic beta |
| 59 | + r_classic - classical radius of particle |
| 60 | + """ |
| 61 | + r_classic = bunch.classicalRadius() |
| 62 | + gamma = bunch.getSyncParticle().gamma() |
| 63 | + beta = bunch.getSyncParticle().beta() |
| 64 | + val = math.sqrt((r_classic*Ntot/R**3)/2)*s_path/(gamma*beta) |
| 65 | + #---- Now we solve equation val = func_theory(x) for x |
| 66 | + x_min = 1.000000001 |
| 67 | + x_max = 10. |
| 68 | + x = (x_min + x_max)/2. |
| 69 | + #print ("debug s_path=",s_path," val=",val) |
| 70 | + n_iter = 15 |
| 71 | + count = 0 |
| 72 | + f_min = func_theory(x_min) |
| 73 | + f_max = func_theory(x_max) |
| 74 | + f_val = func_theory((x_min + x_max)/2.) |
| 75 | + while(count < n_iter): |
| 76 | + x = (x_min + x_max)/2. |
| 77 | + f_val = func_theory(x) |
| 78 | + if(val <= f_val): |
| 79 | + f_max = f_val |
| 80 | + x_max = x |
| 81 | + count += 1 |
| 82 | + continue |
| 83 | + if(val >= f_val): |
| 84 | + f_min = f_val |
| 85 | + x_min = x |
| 86 | + count += 1 |
| 87 | + continue |
| 88 | + r_res = 1000.*r_init*x**2 |
| 89 | + #print ("debug s_path=",s_path," val=",val," f_val=",f_val," x=",x," r_res=",r_res) |
| 90 | + return r_res |
| 91 | + |
| 92 | +#---------------------------------------- |
| 93 | +# Uniform sphere distribution functions |
| 94 | +#---------------------------------------- |
| 95 | + |
| 96 | +def getUniformXYZ(radius,gamma): |
| 97 | + """ |
| 98 | + Uniform distribution inside the sphere. |
| 99 | + Z-direction is contracted by relativistic gamma |
| 100 | + """ |
| 101 | + (x,y,z) = (radius,radius,radius) |
| 102 | + r = math.sqrt(x**2 + y**2 + z**2) |
| 103 | + while(r > radius): |
| 104 | + x = radius*(1.0 - 2*random.random()) |
| 105 | + y = radius*(1.0 - 2*random.random()) |
| 106 | + z = radius*(1.0 - 2*random.random()) |
| 107 | + r = math.sqrt(x**2 + y**2 + z**2) |
| 108 | + return (x,y,z/gamma) |
| 109 | + |
| 110 | +print ("Start.") |
| 111 | +#--------------------------------- |
| 112 | +#make an initial bunch |
| 113 | +#--------------------------------- |
| 114 | +b_init = Bunch() |
| 115 | +bunch_radius = 0.005 # m |
| 116 | +bunch_length = 10.0 # m |
| 117 | +nParts = 1000000 |
| 118 | +total_macroSize = 1.0e+11 |
| 119 | +energy = 0.4 # GeV |
| 120 | + |
| 121 | +syncPart = b_init.getSyncParticle() |
| 122 | +syncPart.kinEnergy(energy) |
| 123 | +beta = syncPart.beta() |
| 124 | +gamma = syncPart.gamma() |
| 125 | +print ("==================================================") |
| 126 | +print ("Mass[MeV] = %8.3f "%(b_init.mass()*1000.)) |
| 127 | +print ("Kinetic energy[MeV] = %8.3f "%(energy*1000.)) |
| 128 | +print ("gamma = %8.5f "%gamma) |
| 129 | +print ("Total macrosize = %12.5e "%total_macroSize) |
| 130 | +print ("==================================================") |
| 131 | + |
| 132 | +#------------------------------------------------ |
| 133 | +#---- generate an uniform sphere distribution |
| 134 | +#---- without energy or momentum spread |
| 135 | +#------------------------------------------------ |
| 136 | +for ip in range(nParts): |
| 137 | + (x,y,z) = getUniformXYZ(bunch_radius,gamma) |
| 138 | + b_init.addParticle(x,0.,y,0.,z,0.) |
| 139 | + |
| 140 | +#------------------------------- |
| 141 | +# set bunch parameters |
| 142 | +#------------------------------- |
| 143 | +nParticlesGlobal = b_init.getSizeGlobal() |
| 144 | +b_init.macroSize(total_macroSize/nParticlesGlobal) |
| 145 | +print ("Total numbers of macro-particles = ",nParticlesGlobal) |
| 146 | + |
| 147 | +#---------------------------------------------- |
| 148 | +#---- Analysis of the initial bunch |
| 149 | +#---------------------------------------------- |
| 150 | +twiss_analysis = BunchTwissAnalysis() |
| 151 | +twiss_analysis.analyzeBunch(b_init) |
| 152 | + |
| 153 | +x_rms = math.sqrt(twiss_analysis.getCorrelation(0,0)) * 1000.0 |
| 154 | +y_rms = math.sqrt(twiss_analysis.getCorrelation(2,2)) * 1000.0 |
| 155 | +z_rms = math.sqrt(twiss_analysis.getCorrelation(4,4)) * 1000.0 |
| 156 | + |
| 157 | +print ("==========Before Traking=====================") |
| 158 | +print ("Initial x_rms[mm] = %6.5f x_rms/y_rms = %6.5f "%(x_rms,x_rms/y_rms)) |
| 159 | +print ("Initial y_rms[mm] = %6.5f x_rms/(z_rms*gamma) = %6.5f "%(y_rms,x_rms/(z_rms*gamma))) |
| 160 | +print ("Initial z_rms[mm] = %6.5f x_rms/(z_rms*gamma) = %6.5f "%(z_rms,x_rms/(z_rms*gamma))) |
| 161 | +print ("=============================================") |
| 162 | +#------------------------------------------ |
| 163 | +# Initial Bunch is ready |
| 164 | +# Now let's make a lattice |
| 165 | +#------------------------------------------ |
| 166 | + |
| 167 | +def getLattice(lattice_length,n_parts,calc3d,pipe_radius): |
| 168 | + elem = teapot.DriftTEAPOT("my_drift") |
| 169 | + elem.setLength(lattice_length) |
| 170 | + elem.setnParts(n_parts) |
| 171 | + teapot_lattice = teapot.TEAPOT_Lattice("teapot_lattice") |
| 172 | + teapot_lattice.addNode(elem) |
| 173 | + teapot_lattice.initialize() |
| 174 | + # we will put SC nodes as frequently as possible. |
| 175 | + # In this case one for each part of the Drift node |
| 176 | + sc_path_length_min = 0.1 |
| 177 | + scNodes_arr = setSC2p5DrbAccNodes(teapot_lattice, sc_path_length_min, calc3d,pipe_radius) |
| 178 | + #print ("debug n sc nodes=",len(scNodes_arr)) |
| 179 | + return teapot_lattice |
| 180 | + |
| 181 | +#------------------------------------------------------------- |
| 182 | +# set 2.5D Rick Baartman's Space Charge model. |
| 183 | +# This model should not work for linac case |
| 184 | +# here it is just as example of setting another SC model |
| 185 | +#------------------------------------------------------------- |
| 186 | +sizeX = 64 |
| 187 | +sizeY = 64 |
| 188 | +sizeZ = 64 |
| 189 | +pipe_radius = 0.100 |
| 190 | +calc3d = SpaceChargeCalc2p5Drb(sizeX,sizeY,sizeZ) |
| 191 | + |
| 192 | +lattice_length = 0.5 # the length of the drift |
| 193 | +n_parts = 30 # number of parts on what the drift will be chopped, or the number of SC nodes |
| 194 | +lattice = getLattice(lattice_length,n_parts,calc3d,pipe_radius) |
| 195 | + |
| 196 | +print ("Lattice length[m] = %6.3f "%lattice_length) |
| 197 | +print ("Number of parts in the lattice = ",n_parts) |
| 198 | + |
| 199 | +#-------------------------------------------------------------------- |
| 200 | +# Create a new bunch, copy everything from b_init to this new bunch |
| 201 | +#-------------------------------------------------------------------- |
| 202 | +bunch = Bunch() |
| 203 | +b_init.copyBunchTo(bunch) |
| 204 | + |
| 205 | +#------------------------------------------------------------------- |
| 206 | +#---- Here we add three particles - along x,y,z axises at |
| 207 | +#---- r_bunch/2 disstance from the center. We will use them to |
| 208 | +#---- compare analytical solution with the PyORBIT tracking, |
| 209 | +#---- N.B. : adding 3 particles did not change total charge much. |
| 210 | +#------------------------------------------------------------------- |
| 211 | +ip_arr = [nParticlesGlobal,nParticlesGlobal+1,nParticlesGlobal+2] |
| 212 | +r_in = 0.5*bunch_radius |
| 213 | +bunch.addParticle(-r_in,0.,0.,0.,0.,0.) |
| 214 | +bunch.addParticle(0.,0.,r_in,0.,0.,0.) |
| 215 | +bunch.addParticle(0.,0.,0.,0.,r_in/gamma,0.) |
| 216 | +r_in *= 1000 # now in [mm] |
| 217 | + |
| 218 | +#------------------------------------------------------- |
| 219 | +#---- Let's track bunch through the drift several times |
| 220 | +#------------------------------------------------------- |
| 221 | +s_path = 0. |
| 222 | + |
| 223 | +print ("s[m] r_rms[mm] r1[mm] r2[mm] r3[mm] r_theory[mm] ") |
| 224 | + |
| 225 | +for ind in range(20): |
| 226 | + #--------------------------------------- |
| 227 | + #track the bunch through the lattice |
| 228 | + #--------------------------------------- |
| 229 | + lattice.trackBunch(bunch) |
| 230 | + |
| 231 | + s_path += lattice_length |
| 232 | + |
| 233 | + #---------------------------------------------- |
| 234 | + #---- Analysis of the final bunch |
| 235 | + #---------------------------------------------- |
| 236 | + twiss_analysis.analyzeBunch(bunch) |
| 237 | + |
| 238 | + x_rms = math.sqrt(twiss_analysis.getCorrelation(0,0)) * 1000.0 |
| 239 | + y_rms = math.sqrt(twiss_analysis.getCorrelation(2,2)) * 1000.0 |
| 240 | + z_rms = math.sqrt(twiss_analysis.getCorrelation(4,4)) * 1000.0 |
| 241 | + r_rms = math.sqrt(x_rms**2 + y_rms**2 + (z_rms*gamma)**2) |
| 242 | + |
| 243 | + st = "" |
| 244 | + r_out = 0. |
| 245 | + for ip in ip_arr: |
| 246 | + z = bunch.z(ip)*gamma |
| 247 | + x = bunch.x(ip) |
| 248 | + y = bunch.y(ip) |
| 249 | + xp = bunch.xp(ip)*1000 |
| 250 | + r_out = 1000.*math.sqrt(x**2 + y**2 + z**2) |
| 251 | + st += " %6.3f "%(r_out) |
| 252 | + |
| 253 | + |
| 254 | + r_out_th = getR_Theory(s_path,0.5*bunch_radius,bunch_radius,total_macroSize,bunch) |
| 255 | + |
| 256 | + #r_classic = bunch.classicalRadius() |
| 257 | + #gamma = bunch.getSyncParticle().gamma() |
| 258 | + #beta = bunch.getSyncParticle().beta() |
| 259 | + |
| 260 | + #xp_th = r_classic*total_macroSize*s_path*r_in/(bunch_radius**3)/(gamma*beta)**2 |
| 261 | + |
| 262 | + print (" %4.3f %6.3f "%(s_path,r_rms),st," %6.3f "%r_out_th) |
| 263 | + |
| 264 | +print ("==========After Traking=======================") |
| 265 | +print ("x_rms[mm] = %6.5f x_rms/y_rms = %6.5f "%(x_rms,x_rms/y_rms)) |
| 266 | +print ("y_rms[mm] = %6.5f y_rms/(z_rms*gamma) = %6.5f "%(y_rms,y_rms/(z_rms*gamma))) |
| 267 | +print ("z_rms[mm] = %6.5f x_rms/(z_rms*gamma) = %6.5f "%(z_rms,x_rms/(z_rms*gamma))) |
| 268 | +print ("=============================================") |
| 269 | + |
| 270 | +print("Finish.") |
| 271 | +sys.exit(0) |
0 commit comments