1+ """
2+ Minimalistic example to load an xsuite line, generate a particle distribution,
3+ install space charge, IBS, and track the particles.
4+ """
5+ import xtrack as xt
6+ import xpart as xp
7+ import xfields as xf
8+ import xobjects as xo
9+ from records import Records
10+ import time
11+
12+ # Tracking parameters and context
13+ n_turns = 20
14+ n_particles = 20
15+ context = xo .ContextCpu (omp_num_threads = 'auto' )
16+
17+ # Beam parameters
18+ exn = 1.0e-6 # horizontal emittance in m
19+ eyn = 1.0e-6 # vertical emittance in m
20+ sigma_z = 0.2 # bunch length in m
21+ Nb = 1e8 # number of particles in the bunch
22+
23+ # Space charge and IBS parameters
24+ q_val = 1.0 # q-value of longitudinal profile, for space charge
25+ num_spacecharge_interactions = 1080
26+ ibs_step = 100 # number of turns between recomputing the IBS coefficients
27+
28+ # Load the line from a file
29+ line = xt .Line .from_json ('SPS_2021_Pb_nominal_deferred_exp.json' )
30+ harmonic_nb = 4653
31+
32+ # Set RF voltage to correct value
33+ line ['actcse.31632' ].voltage = 3.0e6
34+ print ('RF voltage set to {:.3e} V\n ' .format (line ['actcse.31632' ].voltage ))
35+
36+ # Twiss command, inspect tunes and reference particle
37+ tw = line .twiss ()
38+ print ('Tunes: Qx = {:.6f}, Qy = {:.6f}' .format (tw .qx , tw .qy ))
39+ print ('Reference particle: {}' .format (line .particle_ref .show ()))
40+
41+ # Add longitudinal limit rectangle - to kill particles that fall out of bucket
42+ bucket_length = line .get_length ()/ harmonic_nb
43+ print ('\n Bucket length is {:.4f} m' .format (bucket_length ))
44+ line .unfreeze () # if you had already build the tracker
45+ line .append_element (element = xt .LongitudinalLimitRect (min_zeta = - bucket_length / 2 , max_zeta = bucket_length / 2 ), name = 'long_limit' )
46+ line .build_tracker (_context = context )
47+
48+ # Generate a particle distribution
49+ particles = xp .generate_matched_gaussian_bunch (_context = context ,
50+ num_particles = n_particles ,
51+ total_intensity_particles = Nb ,
52+ nemitt_x = exn ,
53+ nemitt_y = eyn ,
54+ sigma_z = sigma_z ,
55+ particle_ref = line .particle_ref ,
56+ line = line )
57+
58+ # Initialize the dataclasses to store particle values
59+ tbt = Records .init_zeroes (n_turns ) # only emittances and bunch intensity
60+ tbt .update_at_turn (0 , particles , tw )
61+ tbt .store_initial_particles (particles )
62+ tbt .store_twiss (tw .to_pandas ())
63+
64+ ######### Frozen space charge #########
65+
66+ # Store the initial buffer of the line
67+ _buffer = line ._buffer
68+ line .discard_tracker ()
69+
70+ # Install space charge
71+ lprofile = xf .LongitudinalProfileQGaussian (
72+ number_of_particles = Nb ,
73+ sigma_z = sigma_z ,
74+ z0 = 0. ,
75+ q_parameter = q_val )
76+
77+ # Install frozen space charge as base
78+ xf .install_spacecharge_frozen (line = line ,
79+ particle_ref = line .particle_ref ,
80+ longitudinal_profile = lprofile ,
81+ nemitt_x = exn , nemitt_y = eyn ,
82+ sigma_z = sigma_z ,
83+ num_spacecharge_interactions = num_spacecharge_interactions )
84+ line .build_tracker (_buffer = _buffer )
85+
86+ ######### IBS kinetic kicks #########
87+
88+ # friction and diffusion terms of the kinetic theory of gases
89+ ibs_kick = xf .IBSKineticKick (num_slices = 50 )
90+
91+ ### Install the IBS kinetic kick element ###
92+ #line.configure_intrabeam_scattering(
93+ # element=ibs_kick, name="ibskick", index=-1, update_every=ibs_step
94+ #)
95+
96+ # THESE LINES ABOVE WILL NOT WORK if space charge is already installed
97+ # Instead, follow manual steps Felix Soubelet's tips
98+ # Directly copy steps from https://github.com/xsuite/xfields/blob/6882e0d03bb6772f873ce57ef6cf2592e5779359/xfields/ibs/_api.py
99+ _buffer = line ._buffer
100+ line .discard_tracker ()
101+ line .insert_element (element = ibs_kick , name = "ibskick" , index = - 1 )
102+ line .build_tracker (_buffer = _buffer )
103+
104+ line_sc_off = line .filter_elements (exclude_types_starting_with = 'SpaceCh' )
105+ twiss_no_sc = line_sc_off .twiss (method = "4d" )
106+
107+ # Figure out the IBS kick element and its name in the line
108+ only_ibs_kicks = {name : element for name , element in line .element_dict .items () if isinstance (element , xf .ibs ._kicks .IBSKick )}
109+ assert len (only_ibs_kicks ) == 1 , "Only one 'IBSKick' element should be present in the line"
110+ name , element = only_ibs_kicks .popitem ()
111+
112+ # Set necessary (private) attributes for the kick to function
113+ element .update_every = ibs_step
114+ element ._name = name
115+ element ._twiss = twiss_no_sc
116+ element ._scale_strength = 1 # element is now ON, will track
117+
118+ print ('\n Fixed IBS coefficient recomputation at interval = {} steps\n ' .format (ibs_step ))
119+
120+ #### Track the particles ####
121+ time00 = time .time ()
122+
123+ for turn in range (1 , n_turns ):
124+
125+ # Print out info at specified intervals
126+ if turn % 5 == 0 :
127+ print ('\n Tracking turn {}' .format (turn ))
128+
129+ # ----- Track and update records for tracked particles ----- #
130+ line .track (particles , num_turns = 1 )
131+
132+ tbt .update_at_turn (turn , particles , tw )
133+
134+ time01 = time .time ()
135+ dt0 = time01 - time00
136+ print ('\n Tracking time: {:.1f} s = {:.1f} h' .format (dt0 , dt0 / 3600 ))
137+
138+ # Final turn-by-turn records
139+ tbt .store_final_particles (particles )
140+ tbt .to_dict (convert_to_numpy = True )
141+
142+ # then save tbt dict if desired
0 commit comments