Skip to content

Commit 0d19cf5

Browse files
committed
implemented IBS effect and covered with unit tests
1 parent 8df57b8 commit 0d19cf5

File tree

7 files changed

+183
-2
lines changed

7 files changed

+183
-2
lines changed

ocelot/cpbd/physics_proc.py

Lines changed: 117 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from ocelot.cpbd.io import save_particle_array
2-
from ocelot.common.globals import h_eV_s, m_e_eV, m_e_GeV, ro_e, speed_of_light
3-
from ocelot.cpbd.beam import Twiss, beam_matching
2+
from ocelot.common.globals import h_eV_s, m_e_eV, m_e_GeV, ro_e, speed_of_light, q_e
3+
from ocelot.cpbd.beam import Twiss, beam_matching, global_slice_analysis, s_to_cur, get_envelope
44
from ocelot.utils.acc_utils import slice_bunching
55
from ocelot.common.ocelog import *
66
from ocelot.cpbd.beam import ParticleArray
@@ -638,3 +638,118 @@ def apply(self, p_array, dz=0):
638638
p_array.E = self.Eref
639639
p_array.p()[:] = p_new[:]
640640

641+
642+
class IBS(PhysProc):
643+
"""
644+
Intrabeam Scattering (IBS) Physics Process.
645+
646+
This class models the intrabeam scattering process in particle beams. Two methods are implemented based on different formulations:
647+
648+
1. **Huang Method:** Based on Z. Huang's work: *Intrabeam Scattering in an X-ray FEL Driver* (LCLS-TN-02-8, 2002).
649+
URL: https://www-ssrl.slac.stanford.edu/lcls/technotes/LCLS-TN-02-8.pdf
650+
2. **Nagaitsev Method:** Based on S. Nagaitsev's work: *Intrabeam scattering formulas for fast numerical evaluation*
651+
(PRAB 8, 064403, 2005). URL: https://journals.aps.org/prab/abstract/10.1103/PhysRevSTAB.8.064403
652+
653+
The methods can be selected using `self.method`, which accepts "Huang" or "Nagaitsev".
654+
655+
Key assumptions and features:
656+
- A **round beam approximation** is used, where `sigma_xy = (sigma_x + sigma_y) / 2`.
657+
- Beam parameters are calculated within a slice of width ±0.5 `sigma_tau` relative to the slice with maximum current
658+
(`self.slice = "Imax"` by default).
659+
- The Coulomb logarithm (`self.Clog`) is a configurable parameter, defaulting to 8 (based on Z. Huang's work).
660+
- A factor of 2 is applied to the Nagaitsev formula to align high-energy results with the Huang formula.
661+
662+
Attributes:
663+
- `method` (str): Selected method for IBS calculation ("Huang" or "Nagaitsev").
664+
- `Clog` (float): Coulomb logarithm (default: 8) is used constant if update_Clog = False
665+
- `update_Clog` (bool): recalculate Clog on each step using Huang's formula without cut off.
666+
- `bounds` (list): Range of bounds for slice analysis in units of `sigma_tau` (default: `[-0.5, 0.5]`).
667+
- `slice` (str): Reference slice ("Imax" for maximum current by default).
668+
669+
Methods:
670+
- `get_beam_params(p_array)`: Computes beam parameters such as `sigma_xy`, `sigma_z`, and normalized emittance.
671+
- `apply(p_array, dz)`: Applies the IBS process to a particle array over a specified distance.
672+
673+
Raises:
674+
- `ValueError`: If an invalid method is specified.
675+
"""
676+
677+
def __init__(self, method="Huang"):
678+
PhysProc.__init__(self)
679+
self.method = method
680+
self.Clog = 8
681+
self.update_Clog = True
682+
self.bounds = [-0.5, 0.5]
683+
self.slice = "Imax"
684+
685+
self.emit_n = None
686+
self.sigma_xy = None
687+
self.sigma_z = None
688+
self.sigma_dgamma0 = None
689+
690+
def get_beam_params(self, p_array):
691+
"""
692+
Compute beam parameters such as sigma_xy, sigma_z, and normalized emittance.
693+
694+
:param p_array: ParticleArray
695+
Input particle array containing particle properties.
696+
"""
697+
tws = get_envelope(p_array, bounds=self.bounds, slice=self.slice)
698+
self.sigma_z = np.std(p_array.tau())
699+
self.sigma_xy = (np.sqrt(tws.xx) + np.sqrt(tws.yy)) / 2.
700+
self.emit_n = (tws.emit_xn + tws.emit_yn) / 2.
701+
pc = np.sqrt(p_array.E ** 2 - m_e_GeV ** 2)
702+
self.sigma_dgamma0 = np.sqrt(tws.pp)*pc / m_e_GeV
703+
704+
def estimate_Clog(self):
705+
"""
706+
Used formula from Hunag's paper without cut offs
707+
708+
:return: float - Coulomb logarithm
709+
"""
710+
Clog = np.log(self.emit_n * self.emit_n / (ro_e * self.sigma_xy))
711+
return Clog
712+
713+
def apply(self, p_array, dz):
714+
"""
715+
Apply the IBS process to a particle array over a given path length.
716+
717+
:param p_array: ParticleArray
718+
The particle array to be processed.
719+
:param dz: float
720+
The path length over which the IBS process is applied.
721+
722+
Raises:
723+
- ValueError: If an invalid method is specified.
724+
"""
725+
# Number of particles
726+
Nb = np.sum(p_array.q_array) / q_e
727+
728+
# Update beam parameters
729+
self.get_beam_params(p_array)
730+
731+
# estimate C log
732+
if self.update_Clog:
733+
Clog = self.estimate_Clog()
734+
else:
735+
Clog = self.Clog
736+
737+
# Particle energy and momentum
738+
gamma = p_array.E / m_e_GeV
739+
pc = np.sqrt(p_array.E**2 - m_e_GeV**2)
740+
741+
# Calculate sigma_gamma based on the selected method
742+
if self.method == "Huang":
743+
sigma_gamma = np.sqrt(Clog * ro_e**2 * Nb / (self.sigma_xy * self.emit_n * self.sigma_z) * dz / 4)
744+
745+
elif self.method == "Nagaitsev":
746+
xi = (self.sigma_dgamma0 * self.sigma_xy / (gamma * self.emit_n))**2
747+
F = (1 - xi**0.25) * np.log(xi + 1) / xi
748+
sigma_gamma = np.sqrt(Clog / 4 * ro_e**2 * Nb / (self.sigma_xy * self.emit_n * self.sigma_z) * F * dz)
749+
else:
750+
raise ValueError(f"Invalid method '{self.method}'. Choose 'Huang' or 'Nagaitsev'.")
751+
752+
# Energy spread and update particle momenta
753+
sigma_e = sigma_gamma * m_e_GeV / pc
754+
p_array.p()[:] += np.random.randn(p_array.n) * sigma_e
755+

unit_tests/ebeam_test/phys_proc/phys_proc_conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from ocelot.utils.acc_utils import chicane_RTU
99
from ocelot.cpbd.sc import LSC
1010
from ocelot.cpbd.wake3D import s2current
11+
from ocelot.cpbd.physics_proc import IBS
1112

1213
"""Lattice elements definition"""
1314

unit_tests/ebeam_test/phys_proc/phys_proc_test.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,65 @@ def test_phase_space_aperture(lattice, p_array, parameter, update_ref_values=Fal
479479
assert check_result(result1 + result2)
480480

481481

482+
@pytest.mark.parametrize('parameter', [0, 1, 2, 3])
483+
def test_ibs(lattice, p_array, parameter, update_ref_values=False):
484+
"""
485+
test PhysicsProc WakeTable
486+
487+
0 - tracking of the electron beam with positive energy chirp trough undulator
488+
1 - tracking of the electron beam with negative energy chirp trough undulator
489+
"""
490+
emit_x = 1e-6 # m
491+
sigma_x = 250e-6 # m
492+
sigma_z = 1e-3 # m
493+
E = 0.1
494+
gamma = E / m_e_GeV
495+
parray_init = generate_parray(sigma_x=sigma_x, sigma_px=emit_x / gamma / sigma_x, sigma_y=sigma_x,
496+
sigma_py=emit_x / gamma / sigma_x,
497+
sigma_tau=sigma_z, sigma_p=1e-3 / 100, chirp=0.00, charge=0.25e-9, nparticles=2000000,
498+
energy=E,
499+
tau_trunc=None, tws=None, shape="gauss")
500+
parray = copy.deepcopy(parray_init)
501+
ibs = IBS()
502+
ibs.Clog = 8
503+
if parameter == 0:
504+
ibs.method = "Nagaitsev"
505+
ibs.update_Clog = True
506+
if parameter == 1:
507+
ibs.method = "Nagaitsev"
508+
ibs.update_Clog = False
509+
if parameter == 2:
510+
ibs.method = "Huang"
511+
ibs.update_Clog = True
512+
else:
513+
ibs.method = "Huang"
514+
ibs.update_Clog = False
515+
tws = get_envelope(parray, bounds=[-0.5, 0.5], slice="Imax")
516+
517+
pc = np.sqrt(parray.E ** 2 - m_e_GeV ** 2)
518+
519+
sigma_p = np.sqrt(tws.pp) * pc
520+
521+
Sigma_e_ocl = [sigma_p * 1e6]
522+
s_ocl = [1]
523+
s_pos = 1
524+
for i in range(29):
525+
ibs.apply(parray, dz=1)
526+
527+
tws = get_envelope(parray, bounds=[-0.5, 0.5], slice="Imax")
528+
sigma_p = np.sqrt(tws.pp) * pc
529+
Sigma_e_ocl.append(sigma_p * 1e6)
530+
s_pos += 1
531+
s_ocl.append(s_pos)
532+
B = np.column_stack((s_ocl, Sigma_e_ocl))
533+
if update_ref_values:
534+
return numpy2json(B)
535+
536+
B_ref = json2numpy(json_read(REF_RES_DIR + sys._getframe().f_code.co_name + str(parameter) + '.json'))
537+
538+
result = check_matrix(B, B_ref, tolerance=1.0e-2, tolerance_type='relative', assert_info=' current - ')
539+
assert check_result(result)
540+
482541
def setup_module(module):
483542

484543
f = open(pytest.TEST_RESULTS_FILE, 'a')
@@ -525,12 +584,14 @@ def test_update_ref_values(lattice, p_array, cmdopt):
525584
update_functions.append('test_track_spontan_rad_effects')
526585
update_functions.append("test_dechirper_offaxis")
527586
update_functions.append("test_phase_space_aperture")
587+
update_functions.append("test_ibs")
528588

529589
update_function_parameters = {}
530590
update_function_parameters['test_track_smooth'] = [0, 1]
531591
update_function_parameters['test_track_aperture'] = [0, 1, 2]
532592
update_function_parameters['test_track_ellipt_aperture'] = [0, 1, 2]
533593
update_function_parameters['test_phase_space_aperture'] = [0, 1, 2]
594+
update_function_parameters['test_ibs'] = [0, 1, 2, 3]
534595

535596
parameter = update_function_parameters[cmdopt] if cmdopt in update_function_parameters.keys() else ['']
536597

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[{"0": 1.0, "1": 1.0003445810416542}, {"0": 2.0, "1": 1.0132264582504005}, {"0": 3.0, "1": 1.0261418348455744}, {"0": 4.0, "1": 1.038839600921569}, {"0": 5.0, "1": 1.051433924828487}, {"0": 6.0, "1": 1.0637691897022543}, {"0": 7.0, "1": 1.0759164336677531}, {"0": 8.0, "1": 1.0877526417501853}, {"0": 9.0, "1": 1.0996930020999416}, {"0": 10.0, "1": 1.1111914024224365}, {"0": 11.0, "1": 1.1227622635463177}, {"0": 12.0, "1": 1.1342028375672764}, {"0": 13.0, "1": 1.1450911445886964}, {"0": 14.0, "1": 1.1565376880003095}, {"0": 15.0, "1": 1.1679669259529066}, {"0": 16.0, "1": 1.1789980627090422}, {"0": 17.0, "1": 1.1899054119658774}, {"0": 18.0, "1": 1.2007118798803684}, {"0": 19.0, "1": 1.211069534012699}, {"0": 20.0, "1": 1.22181904712469}, {"0": 21.0, "1": 1.2325695487418533}, {"0": 22.0, "1": 1.2429672837146108}, {"0": 23.0, "1": 1.2531420379287972}, {"0": 24.0, "1": 1.2632975093964207}, {"0": 25.0, "1": 1.2736676968584473}, {"0": 26.0, "1": 1.2836114078375374}, {"0": 27.0, "1": 1.2938845568653625}, {"0": 28.0, "1": 1.3039373571748745}, {"0": 29.0, "1": 1.3138357800093694}, {"0": 30.0, "1": 1.3235118415419846}]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[{"0": 1.0, "1": 0.9997025703785725}, {"0": 2.0, "1": 1.012395903762503}, {"0": 3.0, "1": 1.0251126088319444}, {"0": 4.0, "1": 1.0376056618449674}, {"0": 5.0, "1": 1.0499048940657663}, {"0": 6.0, "1": 1.06225375386254}, {"0": 7.0, "1": 1.0740669180810403}, {"0": 8.0, "1": 1.0862370443699896}, {"0": 9.0, "1": 1.0979075093330777}, {"0": 10.0, "1": 1.1098043464756342}, {"0": 11.0, "1": 1.121319362226039}, {"0": 12.0, "1": 1.132884804762504}, {"0": 13.0, "1": 1.144198017262989}, {"0": 14.0, "1": 1.1554318737881544}, {"0": 15.0, "1": 1.1664870514941097}, {"0": 16.0, "1": 1.1776092358083947}, {"0": 17.0, "1": 1.1885954628388211}, {"0": 18.0, "1": 1.199411196630008}, {"0": 19.0, "1": 1.2100810668961404}, {"0": 20.0, "1": 1.2206573312518325}, {"0": 21.0, "1": 1.2312257284671644}, {"0": 22.0, "1": 1.241806660485859}, {"0": 23.0, "1": 1.2523288788252158}, {"0": 24.0, "1": 1.2626353888570516}, {"0": 25.0, "1": 1.2728536428570845}, {"0": 26.0, "1": 1.2829232218752427}, {"0": 27.0, "1": 1.29329744289136}, {"0": 28.0, "1": 1.3030613265594209}, {"0": 29.0, "1": 1.3129054994902707}, {"0": 30.0, "1": 1.322368482665442}]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[{"0": 1.0, "1": 0.9998725663150522}, {"0": 2.0, "1": 1.0223093672819514}, {"0": 3.0, "1": 1.044489859534837}, {"0": 4.0, "1": 1.0665051846968936}, {"0": 5.0, "1": 1.087622957458697}, {"0": 6.0, "1": 1.108630064808506}, {"0": 7.0, "1": 1.1292257039728177}, {"0": 8.0, "1": 1.149530533920015}, {"0": 9.0, "1": 1.16927330577773}, {"0": 10.0, "1": 1.189006692964033}, {"0": 11.0, "1": 1.2082081415488588}, {"0": 12.0, "1": 1.2265741595986799}, {"0": 13.0, "1": 1.2451696400193075}, {"0": 14.0, "1": 1.263567835036204}, {"0": 15.0, "1": 1.2815975529900088}, {"0": 16.0, "1": 1.299439824803275}, {"0": 17.0, "1": 1.3168757154359425}, {"0": 18.0, "1": 1.3343874934934168}, {"0": 19.0, "1": 1.351039740496107}, {"0": 20.0, "1": 1.3676166982793703}, {"0": 21.0, "1": 1.3842125314468827}, {"0": 22.0, "1": 1.400807921453688}, {"0": 23.0, "1": 1.416748713155206}, {"0": 24.0, "1": 1.4325431350905102}, {"0": 25.0, "1": 1.4481730809665256}, {"0": 26.0, "1": 1.4639326081421193}, {"0": 27.0, "1": 1.479480820344132}, {"0": 28.0, "1": 1.4946609183628063}, {"0": 29.0, "1": 1.5096723391311508}, {"0": 30.0, "1": 1.5245596506212855}]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[{"0": 1.0, "1": 0.9997366834643983}, {"0": 2.0, "1": 1.0128116957460225}, {"0": 3.0, "1": 1.0255280772289488}, {"0": 4.0, "1": 1.0380603533558583}, {"0": 5.0, "1": 1.0502672024469313}, {"0": 6.0, "1": 1.0623469884300116}, {"0": 7.0, "1": 1.074269895880912}, {"0": 8.0, "1": 1.0860745441892916}, {"0": 9.0, "1": 1.0977684088851027}, {"0": 10.0, "1": 1.1096538520734391}, {"0": 11.0, "1": 1.1213326628111062}, {"0": 12.0, "1": 1.1330678189367918}, {"0": 13.0, "1": 1.144540165744393}, {"0": 14.0, "1": 1.1559297948264675}, {"0": 15.0, "1": 1.1672267365600004}, {"0": 16.0, "1": 1.1781902814931788}, {"0": 17.0, "1": 1.1891458979140237}, {"0": 18.0, "1": 1.19967018490185}, {"0": 19.0, "1": 1.2105303479325875}, {"0": 20.0, "1": 1.2209897685203805}, {"0": 21.0, "1": 1.2315624567594723}, {"0": 22.0, "1": 1.2420008342560112}, {"0": 23.0, "1": 1.2522537838660226}, {"0": 24.0, "1": 1.2626586925321777}, {"0": 25.0, "1": 1.272933815016061}, {"0": 26.0, "1": 1.2830779924354359}, {"0": 27.0, "1": 1.2933493056384662}, {"0": 28.0, "1": 1.3032151019868898}, {"0": 29.0, "1": 1.313314500259722}, {"0": 30.0, "1": 1.3230406742204555}]

0 commit comments

Comments
 (0)