diff --git a/__EXAMPLES/main_files/EX_23_single_particle_with_oneturnmatrix_lhc.py b/__EXAMPLES/main_files/EX_23_single_particle_with_oneturnmatrix_lhc.py new file mode 100644 index 0000000000000000000000000000000000000000..6edecffd1809620fdd5be4e3b9dddb62a806a441 --- /dev/null +++ b/__EXAMPLES/main_files/EX_23_single_particle_with_oneturnmatrix_lhc.py @@ -0,0 +1,225 @@ +''' +LHC simulation of a single particle. + +:Author: **Thom Arnoldus van Rijswijk** +''' + +# Imports +import os +import numpy as np +import matplotlib as mpl +import matplotlib.pyplot as plt + +# Xsuite imports +import xpart as xp +import xtrack as xt +import xplt + +# BLonD objects +from blond.beam.beam import Beam, Proton +from blond.input_parameters.ring import Ring +from blond.input_parameters.rf_parameters import RFStation +from blond.trackers.tracker import RingAndRFTracker + +# Interface +from blond.interfaces.xsuite import (BlondElement, BlondObserver, + blond_to_xsuite_transform, + xsuite_to_blond_transform) + +# Monitor objects +from blond.monitors.monitors import BunchMonitor +from blond.plots.plot import Plot + +# Directory to save files +this_directory = os.path.dirname(os.path.realpath(__file__)) + '/' +results_directory = this_directory + '../output_files/EX_23' +os.makedirs(results_directory, exist_ok=True) + +# Parameters ---------------------------------------------------------------------------------------------------------- +# Accelerator parameters +C = 26658.8832 # Machine circumference [m] +p_s = 450e9 # Synchronous momentum [eV/c] +p_f = 450.0e9 # Synchronous momentum, final +h = 35640 # Harmonic number [-] +alpha = 0.00034849575112251314 # First order mom. comp. factor [-] +V = 5e6 # RF voltage [V] +dphi = 0 # Phase modulation/offset [rad] + +# Bunch parameters +N_bunches = 1 # Number of bunches [-] +N_m = 1 # Number of macroparticles [-] +N_p = 1.15e11 # Intensity +blen = 1.25e-9 # Bunch length [s] +energy_err = 100e6 # Beamenergy error [eV/c] + +# Simulation parameters +N_t = 481 # Number of (tracked) turns [-] +N_buckets = 2 # Number of buckets [-] +dt_plt = 50 # Timestep between plots [-] +input_dt = 2 * blen - 0.1e-9 # Input particles dt [s] +input_dE = 0.0 # Input particles dE [eV] + +# Preparing BLonD items ----------------------------------------------------------------------------------------------- +print(f"\nPreparing BLonD items...") +# --- BLonD objects --- +ring = Ring(C, alpha, np.linspace(p_s, p_f, N_t + 1), Proton(), n_turns=N_t) +rfstation = RFStation(ring, [h], [V], [dphi]) +beam = Beam(ring, N_m, N_p) + +# --- Insert particle --- +beam.dt = np.array([input_dt]) +beam.dE = np.array([input_dE]) + +# --- RF tracker object --- +blond_tracker = RingAndRFTracker(rfstation, beam, Profile=None, interpolation=False, + CavityFeedback=None, BeamFeedback=None, + TotalInducedVoltage=None, + with_xsuite=True) + +# --- Bunch monitor --- +# Print parameters for plotting +print(f"\n Calculating plotting parameters...") + +t_u = np.pi / rfstation.omega_rf[0, 0] +Y_phi_s = np.sqrt(abs(-np.cos(rfstation.phi_s[0]) + (np.pi - 2 * rfstation.phi_s[0]) / 2 * np.sin(rfstation.phi_s[0]))) +dE_sep = np.sqrt(2 * abs(rfstation.charge) * rfstation.voltage[0, 0] * rfstation.beta[0] * rfstation.beta[0] + * rfstation.energy[0] / (np.pi * rfstation.harmonic[0, 0] * abs(ring.eta_0[0, 0]))) * Y_phi_s + +print('t_u: ' + str(2 * t_u)) +print('dE_sep,m: ' + str(dE_sep)) + +# Make bunchmonitor +bunchmonitor = BunchMonitor(ring, rfstation, beam, + results_directory + '/EX_23_output_data', Profile=None) + +format_options = {'dirname': results_directory + '/EX_23_fig'} +plots = Plot(ring, rfstation, beam, dt_plt, N_t, 0, 2 * t_u, + -1.05 * dE_sep, 1.05 * dE_sep, xunit='s', separatrix_plot=True, + Profile=None, h5file=results_directory + '/EX_23_output_data', + format_options=format_options) + +# --- Creating interface elements --- +# Blond tracker +cavity = BlondElement(blond_tracker, beam) +# BLonD monitor +beam_monitor = BlondElement(bunchmonitor, beam) +beam_plots = BlondElement(plots, beam) + +# Preparing Xsuite items ----------------------------------------------------------------------------------------------- +print(f"\nPreparing Xsuite items...") +# --- Setup matrix --- +# Make First order matrix map (takes care of drift in Xsuite) +matrix = xt.LineSegmentMap( + longitudinal_mode='nonlinear', + qx=1.1, qy=1.2, + betx=1., + bety=1., + voltage_rf=0, + frequency_rf=0, + lag_rf=0, + momentum_compaction_factor=alpha, + length=C +) + +# Create line +line = xt.Line(elements=[matrix], element_names={'matrix'} ) +line['matrix'].length = C + +# Insert the BLonD elements +line.insert_element(index=0, element=cavity, name='blond_cavity') +line.append_element(element=beam_monitor, name='blond_monitor') +line.append_element(element=beam_plots, name='blond_plots') + +# Add particles to line and build tracker +line.particle_ref = xp.Particles(p0c=p_s, mass0=xp.PROTON_MASS_EV, q0=1.) +line.build_tracker() + +# Show table +line.get_table().show() +# prints: + +# name s element_type isthick isreplica parent_name iscollective +# blond_cavity 0 BlondElement False False None True +# matrix 0 LineSegmentMap True False None False +# blond_monitor 26658.9 BlondObserver False False None True +# blond_plots 26658.9 BlondElement False False None True +# _end_point 26658.9 False False None False + +# Simulating ---------------------------------------------------------------------------------------------------------- +print(f"\nSetting up simulation...") + +# --- Convert the initial BLonD distribution to xsuite coordinates --- +zeta, ptau = blond_to_xsuite_transform( + beam.dt, beam.dE, line.particle_ref.beta0[0], line.particle_ref.energy0[0], + phi_s=rfstation.phi_s[0], omega_rf=rfstation.omega_rf[0, 0] +) + +# --- Track matrix --- +particles = line.build_particles(x=0, y=0, px=0, py=0, zeta=np.copy(zeta), ptau=np.copy(ptau)) +line.track(particles, num_turns=N_t, turn_by_turn_monitor=True, with_progress=True) +mon = line.record_last_track + +# Saving turn-by-turn particle coordinates +test_string = '' +test_string += '{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\n'.format( + 'dE', 'dt', 'momentum', 'gamma', 'beta', 'energy', 'x', 'px', 'y', 'py') +test_string += ('{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}' + '\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\n').format( + beam.dE[0], beam.dt[0], rfstation.momentum[0], rfstation.gamma[0], rfstation.beta[0], + rfstation.energy[0], mon.x[0, 0].T, mon.px[0, 0].T, mon.y[0, 0].T, mon.py[0, 0].T) + +# Saving turn-by-turn Xsuite particle coordinates +xsuite_string = '' +xsuite_string += '{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\n'.format( + 'ptau', 'zeta', 'momentum', 'gamma', 'beta') +xsuite_string += '{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\n'.format( + mon.ptau[0, 0].T, mon.zeta[0, 0].T, np.float64(mon.p0c[0, 0]), np.float64(mon.gamma0[0, 0]), + np.float64(mon.beta0[0, 0])) + +# Convert the xsuite particle coordinates back to BLonD +for i in range(N_t): + dt, dE = xsuite_to_blond_transform( + mon.zeta[:, i].T, mon.ptau[:, i].T, + rfstation.beta[i], rfstation.energy[i], + phi_s=rfstation.phi_s[i] - rfstation.phi_rf[0, 0], # Correct for RF phase + omega_rf=rfstation.omega_rf[0, i] + ) + + # Statistics + test_string += ('{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}' + '\t{:+10.10e}\t{:+10.10e}\n').format( + dE[0], dt[0], rfstation.momentum[i], rfstation.gamma[i], rfstation.beta[i], + rfstation.energy[i], mon.x[0, i].T, mon.px[0, i].T, mon.y[0, i].T, mon.py[0, i].T) + xsuite_string += '{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\n'.format( + mon.ptau[0, i].T, mon.zeta[0, i].T, np.float64(mon.p0c[0, i]), np.float64(mon.gamma0[0, i]), + np.float64(mon.beta0[0, i])) + +with open(results_directory + '/EX_23_output_data.txt', 'w') as f: + f.write(test_string) +with open(results_directory + '/EX_23_output_data_xsuite.txt', 'w') as f: + f.write(xsuite_string) + +# Results -------------------------------------------------------------------------------------------------------------- +print(f"\nPlotting result...") + +# Plot Phasespace + +plt.figure() +plt.plot(mon.zeta.T, mon.ptau.T) +plt.scatter(np.mean(mon.zeta.T), np.mean(mon.ptau.T), label='mon') +plt.grid() +plt.title('Phasespace') +plt.xlabel(r'$\zeta$') +plt.ylabel(r'$p_{\tau}$') +plt.savefig(results_directory + '/figure_Phasespace.png') + +# Use Xplt to plot Phasespace + +plot = xplt.PhaseSpacePlot( + mon +) +plot.fig.suptitle("Particle distribution for a single turn") +plot.fig.savefig(results_directory + '/figure_ps_mon.png') + +# Show plots +plt.show() diff --git a/__EXAMPLES/main_files/EX_24_single_particle_ramp_lhc.py b/__EXAMPLES/main_files/EX_24_single_particle_ramp_lhc.py new file mode 100644 index 0000000000000000000000000000000000000000..cfaa4644e1ee8134658a55112fc0b85dae37277c --- /dev/null +++ b/__EXAMPLES/main_files/EX_24_single_particle_ramp_lhc.py @@ -0,0 +1,237 @@ +''' +LHC simulation of a single particle with a ramp. + +: Author: **Birk Emil Karlsen-Baeck**, **Thom Arnoldus van Rijswijk** +''' + +# Imports +import os +import numpy as np +import matplotlib as mpl +import matplotlib.pyplot as plt +#mpl.use('Agg') + +# Xsuite imports +import xpart as xp +import xtrack as xt +import xplt + +# BLonD objects +from blond.beam.beam import Beam, Proton +from blond.input_parameters.ring import Ring +from blond.input_parameters.rf_parameters import RFStation +from blond.trackers.tracker import RingAndRFTracker + +# Interface +from blond.interfaces.xsuite import (BlondElement, BlondObserver, EnergyUpdate, + blond_to_xsuite_transform, + xsuite_to_blond_transform) + +# Monitor objects +from blond.monitors.monitors import BunchMonitor +from blond.plots.plot import Plot + +# Directory to save files +this_directory = os.path.dirname(os.path.realpath(__file__)) + '/' +results_directory = this_directory + '../output_files/EX_24' +os.makedirs(results_directory, exist_ok=True) + +# Parameters ---------------------------------------------------------------------------------------------------------- +# Accelerator parameters +C = 26658.8832 # Machine circumference [m] +p_s = 450e9 # Synchronous momentum [eV/c] +p_f = 450.1e9 # Synchronous momentum, final +h = 35640 # Harmonic number [-] +alpha = 0.00034849575112251314 # First order mom. comp. factor [-] +V = 5e6 # RF voltage [V] +dphi = 0 # Phase modulation/offset [rad] + +# Bunch parameters +N_bunches = 1 # Number of bunches [-] +N_m = 1 # Number of macroparticles [-] +N_p = 1.15e11 # Intensity +blen = 1.25e-9 # Bunch length [s] +energy_err = 100e6 # Beamenergy error [eV/c] + +# Simulation parameters +N_t = 330 # Number of (tracked) turns [-] +N_buckets = 2 # Number of buckets [-] +dt_plt = 30 # Timestep between plots [-] +input_dt = 2 * blen - 0.4e-9 # Input particles dt [s] +input_dE = 0.0 # Input particles dE [eV] + +# Xsuite parameters +cavity_name = 'acsca.d5l4.b1' + +# Preparing BLonD items ----------------------------------------------------------------------------------------------- +print(f"\nPreparing BLonD items...") +# --- BLonD objects --- +ring = Ring(C, alpha, np.linspace(p_s, p_f, N_t + 1), Proton(), n_turns=N_t) +rfstation = RFStation(ring, [h], [V], [dphi]) +beam = Beam(ring, N_m, N_p) + +# --- Insert particle --- +beam.dt = np.array([input_dt]) +beam.dE = np.array([input_dE]) + +# --- RF tracker object --- +blond_tracker = RingAndRFTracker(rfstation, beam, Profile=None, interpolation=False, + CavityFeedback=None, BeamFeedback=None, + TotalInducedVoltage=None, + with_xsuite=True) + +# --- Bunch monitor --- +# Print parameters for plotting +print(f"\n Calculating plotting parameters...") + +t_u = np.pi / rfstation.omega_rf[0, 0] +Y_phi_s = np.sqrt(abs(-np.cos(rfstation.phi_s[0]) + (np.pi - 2 * rfstation.phi_s[0]) / 2 * np.sin(rfstation.phi_s[0]))) +dE_sep = np.sqrt(2 * abs(rfstation.charge) * rfstation.voltage[0, 0] * rfstation.beta[0] * rfstation.beta[0] + * rfstation.energy[0] / (np.pi * rfstation.harmonic[0, 0] * abs(ring.eta_0[0, 0]))) * Y_phi_s + +print('t_u: ' + str(2 * t_u)) +print('dE_sep,m: ' + str(dE_sep)) + +# Make bunchmonitor +bunchmonitor = BunchMonitor(ring, rfstation, beam, + results_directory + '/EX_24_output_data', Profile=None) + +format_options = {'dirname': results_directory + '/EX_24_fig'} +plots = Plot(ring, rfstation, beam, dt_plt, N_t, 0, 2*t_u, + -1.05 * dE_sep, 1.05 * dE_sep, xunit='s', separatrix_plot=True, + Profile=None, h5file=results_directory + '/EX_24_output_data', + format_options=format_options) + +# --- Creating interface elements --- +# Blond tracker +cavity = BlondElement(blond_tracker, beam) +# BLonD monitor +beam_monitor = BlondElement(bunchmonitor, beam) +beam_plots = BlondElement(plots, beam) + +# Preparing Xsuite items ----------------------------------------------------------------------------------------------- +print(f"\nPreparing Xsuite items...") +# --- Setup matrix --- +# Make First order matrix map (takes care of drift in Xsuite) +matrix = xt.LineSegmentMap( + longitudinal_mode='nonlinear', + qx=1.1, qy=1.2, + betx=1., + bety=1., + voltage_rf=0, + frequency_rf=0, + lag_rf=0, + momentum_compaction_factor=alpha, + length=C) + +# Create line +line = xt.Line(elements=[matrix], element_names={'matrix'}) +line['matrix'].length = C + +# Insert the BLonD elements +line.insert_element(index=0, element=cavity, name='blond_cavity') +line.append_element(element=beam_monitor, name='blond_monitor') +line.append_element(element=beam_plots, name='blond_plots') + +# Insert energy ramp +energy_update = EnergyUpdate(ring.momentum[0, :]) +line.insert_element(index='matrix', element=energy_update, name='energy_update') + +# Add particles to line and build tracker +line.particle_ref = xp.Particles(p0c=p_s, mass0=xp.PROTON_MASS_EV, q0=1.) +line.build_tracker() + +# Show table +line.get_table().show() +# prints: + +# name s element_type isthick isreplica parent_name iscollective +# energy_update 0 EnergyUpdate False False None True +# blond_cavity 0 BlondElement False False None True +# matrix 0 LineSegmentMap True False None False +# blond_monitor 26658.9 BlondObserver False False None True +# blond_plots 26658.9 BlondElement False False None True +# _end_point 26658.9 False False None False + +# Simulating ---------------------------------------------------------------------------------------------------------- +print(f"\nSetting up simulation...") + +# --- Convert the initial BLonD distribution to xsuite coordinates --- +zeta, ptau = blond_to_xsuite_transform( + beam.dt, beam.dE, line.particle_ref.beta0[0], + line.particle_ref.energy0[0], phi_s=rfstation.phi_s[0], + omega_rf=rfstation.omega_rf[0, 0] +) + +# --- Track matrix --- +particles = line.build_particles(x=0, y=0, px=0, py=0, zeta=np.copy(zeta), ptau=np.copy(ptau)) +line.track(particles, num_turns=N_t, turn_by_turn_monitor=True, with_progress=True) +mon = line.record_last_track + +# Saving turn-by-turn particle coordinates +test_string = '' +test_string += '{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\n'.format( + 'dE', 'dt', 'momentum', 'gamma', 'beta', 'energy', 'x', 'px', 'y', 'py') +test_string += ('{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}' + '\t{:+10.10e}\t{:+10.10e}\n').format(beam.dE[0], beam.dt[0], rfstation.momentum[0], + rfstation.gamma[0], rfstation.beta[0], rfstation.energy[0], + mon.x[0, 0].T, mon.px[0, 0].T, mon.y[0, 0].T, mon.py[0, 0].T) + +# Saving turn-by-turn Xsuite particle coordinates +xsuite_string = '' +xsuite_string += '{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\n'.format( + 'ptau', 'zeta', 'momentum', 'gamma', 'beta') +xsuite_string += '{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\n'.format( + mon.ptau[0, 0].T, mon.zeta[0, 0].T, np.float64(mon.p0c[0, 0]), np.float64(mon.gamma0[0, 0]), + np.float64(mon.beta0[0, 0]) +) + +# Convert the xsuite particle coordinates back to BLonD +for i in range(N_t): + dt, dE = xsuite_to_blond_transform( + mon.zeta[:, i].T, mon.ptau[:, i].T, + rfstation.beta[i], rfstation.energy[i], + phi_s=rfstation.phi_s[i] - rfstation.phi_rf[0, 0], # Correct for RF phase + omega_rf=rfstation.omega_rf[0, i] + ) + + # Statistics + test_string += ('{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}' + '\t{:+10.10e}\t{:+10.10e}\n').format(dE[0], dt[0], rfstation.momentum[i], rfstation.gamma[i], + rfstation.beta[i], rfstation.energy[i], mon.x[0, i].T, + mon.px[0, i].T, mon.y[0, i].T, mon.py[0, i].T) + + xsuite_string += '{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\n'.format( + mon.ptau[0, i].T, mon.zeta[0, i].T, np.float64(mon.p0c[0, i]), np.float64(mon.gamma0[0, i]), + np.float64(mon.beta0[0, i]) + ) + +with open(results_directory + '/EX_24_output_data.txt', 'w') as f: + f.write(test_string) +with open(results_directory + '/EX_24_output_data_xsuite.txt', 'w') as f: + f.write(xsuite_string) + +# Results -------------------------------------------------------------------------------------------------------------- +print(f"\nPlotting result...") + +# Plot Phasespace + +plt.figure() +plt.plot(mon.zeta.T,mon.ptau.T) +plt.scatter(np.mean(mon.zeta.T), np.mean(mon.ptau.T), label='mon') +plt.grid() +plt.title('Phasespace') +plt.xlabel(r'$\zeta$') +plt.ylabel(r'$p_{\tau}$') +plt.savefig(results_directory + '/figure_Phasespace.png') + +# Use Xplt to plot Phasespace + +plot = xplt.PhaseSpacePlot( + mon +) +plot.fig.suptitle("Particle distribution for a single turn") +plot.fig.savefig(results_directory + '/figure_ps_mon.png') + +# Show plots +plt.show() diff --git a/__EXAMPLES/main_files/EX_25_single_particle_two_rfstations_lhc.py b/__EXAMPLES/main_files/EX_25_single_particle_two_rfstations_lhc.py new file mode 100644 index 0000000000000000000000000000000000000000..9103a32f0d2c00928389cd0445ae94ddde72eb24 --- /dev/null +++ b/__EXAMPLES/main_files/EX_25_single_particle_two_rfstations_lhc.py @@ -0,0 +1,256 @@ +''' +LHC simulation of a single particle with two RF stations. + +: Author: **Thom Arnoldus van Rijswijk** +''' + +# Imports +import os +import numpy as np +import matplotlib.pyplot as plt + +# Xsuite imports +import xpart as xp +import xtrack as xt +import xplt + +# BLonD objects +from blond.beam.beam import Beam, Proton +from blond.input_parameters.ring import Ring +from blond.input_parameters.rf_parameters import RFStation +from blond.trackers.tracker import RingAndRFTracker + +# Interface +from blond.interfaces.xsuite import (BlondElement, + blond_to_xsuite_transform, + xsuite_to_blond_transform) + +# Monitor objects +from blond.monitors.monitors import BunchMonitor +from blond.plots.plot import Plot + +# Directory to save files +this_directory = os.path.dirname(os.path.realpath(__file__)) + '/' +results_directory = this_directory + '../output_files/EX_25' +os.makedirs(results_directory, exist_ok=True) + +# Parameters ---------------------------------------------------------------------------------------------------------- +# Accelerator parameters +C = 26658.8832 # Machine circumference [m] +p_s = 450e9 # Synchronous momentum [eV/c] +p_f = 450.0e9 # Synchronous momentum, final +h = 35640 # Harmonic number [-] +alpha = 0.00034849575112251314 # First order mom. comp. factor [-] +V = 5e6 # RF voltage [V] +dphi = 0 # Phase modulation/offset [rad] + +# Bunch parameters +N_bunches = 1 # Number of bunches [-] +N_m = 1 # Number of macroparticles [-] +N_p = 1.15e11 # Intensity +blen = 1.25e-9 # Bunch length [s] +energy_err = 100e6 # Beamenergy error [eV/c] + +# Simulation parameters +N_t = 481 # Number of (tracked) turns [-] +N_buckets = 2 # Number of buckets [-] +dt_plt = 50 # Timestep between plots [-] +input_dt = 2 * blen - 0.1e-9 # Input particles dt [s] +input_dE = 0.0 # Input particles dE [eV] + +# Additional input +C_part = 0.3 +V_part = 0.5 + +# Preparing BLonD items ----------------------------------------------------------------------------------------------- +print(f"\nPreparing BLonD items...") +# --- BLonD objects --- +ring = Ring([C_part * C, (1 - C_part) * C], + [[alpha], [alpha]], + [p_s, p_s], + Proton(), n_turns=N_t, n_sections=2) +rfstation_1 = RFStation(ring, [h], [V * V_part], [dphi], section_index=1) +rfstation_2 = RFStation(ring, [h], [V * (1 - V_part)], [dphi], section_index=2) +beam = Beam(ring, N_m, N_p) + +# --- Insert particle --- +beam.dt = np.array([input_dt]) +beam.dE = np.array([input_dE]) + +# --- RF tracker object --- +rftracker_1 = RingAndRFTracker(rfstation_1, beam, Profile=None, interpolation=False, + CavityFeedback=None, BeamFeedback=None, + TotalInducedVoltage=None, + with_xsuite=True) +rftracker_2 = RingAndRFTracker(rfstation_2, beam, Profile=None, interpolation=False, + CavityFeedback=None, BeamFeedback=None, + TotalInducedVoltage=None, + with_xsuite=True) + +# --- Bunch monitor --- +# Print parameters for plotting +print(f"\n Calculating plotting parameters...") + +t_u = np.pi / rfstation_1.omega_rf[0,0] +Y_phi_s = np.sqrt(abs(-np.cos(rfstation_1.phi_s[0]) + (np.pi - 2*rfstation_1.phi_s[0]) + / 2 * np.sin(rfstation_1.phi_s[0]))) +dE_sep = np.sqrt(2 * abs(rfstation_1.charge) * V * rfstation_1.beta[0] * rfstation_1.beta[0] * rfstation_1.energy[0] + / (np.pi * rfstation_1.harmonic[0, 0] * abs(ring.eta_0[0, 0]))) * Y_phi_s + +print('t_u: ' + str(2 * t_u)) +print('dE_sep,m: ' + str(dE_sep)) + +# Make bunchmonitor +bunchmonitor = BunchMonitor(ring, rfstation_1, beam, + results_directory + '/EX_25_output_data', Profile=None) + +format_options = {'dirname': results_directory + '/EX_25_fig'} +plots = Plot(ring, rfstation_1, beam, dt_plt, N_t, 0, 2 * t_u, + -1.05 * dE_sep, 1.05 * dE_sep, xunit='s', separatrix_plot=True, + Profile=None, h5file=results_directory + '/EX_25_output_data', + format_options=format_options) + +# --- Creating interface elements --- +# Blond tracker +cavity_1 = BlondElement(rftracker_1, beam) +cavity_2 = BlondElement(rftracker_2, beam) +# BLonD monitor +beam_monitor = BlondElement(bunchmonitor, beam) +beam_plots = BlondElement(plots, beam) + +# Xsuite initialization ----------------------------------------------------------------------------------------------- +print(f"\nXsuite initialization...") +# --- Setup matrices --- +# Make First order matrix maps (takes care of drift in Xsuite) +matrix_1 = xt.LineSegmentMap( + longitudinal_mode='nonlinear', + qx=1.1, qy=1.2, + betx=1., + bety=1., + voltage_rf=0, + frequency_rf=0, + lag_rf=0, + momentum_compaction_factor=alpha, + length=C * C_part) +matrix_2 = xt.LineSegmentMap( + longitudinal_mode='nonlinear', + qx=1.1, qy=1.2, + betx=1., + bety=1., + voltage_rf=0, + frequency_rf=0, + lag_rf=0, + momentum_compaction_factor=alpha, + length=C * (1 - C_part)) + +# Create new line +line = xt.Line(elements=[matrix_1], element_names={'matrix_1'} ) +line['matrix_1'].length = C_part * C +line.append_element(element=matrix_2, name='matrix_2') +line['matrix_2'].length = (1 - C_part) * C + +# Insert the BLonD elements +line.insert_element(index='matrix_1', element=cavity_1, name='blond_cavity_1') +line.insert_element(index='matrix_2', element=cavity_2, name='blond_cavity_2') +line.append_element(element=beam_monitor, name='blond_monitor') +line.append_element(element=beam_plots, name='blond_plots') + +# Add particles to line and build tracker +line.particle_ref = xp.Particles(p0c=p_s, mass0=xp.PROTON_MASS_EV, q0=1.) +line.build_tracker() + +# Show table +line.get_table().show() +# prints: + +# name s element_type isthick isreplica parent_name iscollective +# blond_cavity_1 0 BlondElement False False None True +# matrix_1 0 LineSegmentMap True False None False +# blond_cavity_2 7997.66 BlondElement False False None True +# matrix_2 7997.66 LineSegmentMap True False None False +# blond_monitor 26658.9 BlondObserver False False None True +# blond_plots 26658.9 BlondElement False False None True +# _end_point 26658.9 False False None False + +# Simulating ---------------------------------------------------------------------------------------------------------- +print(f"\nSetting up simulation...") + +# --- Convert the initial BLonD distribution to xsuite coordinates --- +zeta, ptau = blond_to_xsuite_transform( + beam.dt, beam.dE, line.particle_ref.beta0[0], line.particle_ref.energy0[0], + phi_s=rfstation_1.phi_s[0], omega_rf=rfstation_1.omega_rf[0, 0] +) + +# --- Track matrix --- +particles = line.build_particles(x=0, y=0, px=0, py=0, zeta=np.copy(zeta), ptau=np.copy(ptau)) +line.track(particles, num_turns=N_t, turn_by_turn_monitor=True, with_progress=True) +mon = line.record_last_track + +# Saving turn-by-turn particle coordinates +test_string = '' +test_string += '{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\n'.format( + 'dE', 'dt', 'momentum', 'gamma', 'beta', 'energy', 'x', 'px', 'y', 'py') +test_string += ('{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}' + '\t{:+10.10e}\t{:+10.10e}\n').format(beam.dE[0], beam.dt[0], rfstation_1.momentum[0], + rfstation_1.gamma[0], rfstation_1.beta[0], rfstation_1.energy[0], + mon.x[0, 0].T, mon.px[0, 0].T, mon.y[0, 0].T, mon.py[0, 0].T) + +# Saving turn-by-turn Xsuite particle coordinates +xsuite_string = '' +xsuite_string += '{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\n'.format( + 'ptau', 'zeta', 'momentum', 'gamma', 'beta') +xsuite_string += '{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\n'.format( + mon.ptau[0, 0].T, mon.zeta[0, 0].T, np.float64(mon.p0c[0, 0]), np.float64(mon.gamma0[0, 0]), + np.float64(mon.beta0[0, 0]) +) + +# Convert the xsuite particle coordinates back to BLonD +for i in range(N_t): + dt, dE = xsuite_to_blond_transform( + mon.zeta[:,i].T, mon.ptau[:,i].T, + rfstation_1.beta[i], rfstation_1.energy[i], + phi_s=rfstation_1.phi_s[i] - rfstation_1.phi_rf[0, 0], + omega_rf=rfstation_1.omega_rf[0, i] + ) + + # Statistics + test_string += ('{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}' + '\t{:+10.10e}\t{:+10.10e}\n').format(dE[0], dt[0], rfstation_1.momentum[i], + rfstation_1.gamma[i], rfstation_1.beta[i], + rfstation_1.energy[i], mon.x[0, i].T, mon.px[0, i].T, + mon.y[0, i].T, mon.py[0, i].T) + + xsuite_string += '{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\n'.format( + mon.ptau[0, i].T, mon.zeta[0, i].T, np.float64(mon.p0c[0, i]), np.float64(mon.gamma0[0, i]), + np.float64(mon.beta0[0, i]) + ) + +with open(results_directory + '/EX_25_output_data.txt', 'w') as f: + f.write(test_string) +with open(results_directory + '/EX_25_output_data_xsuite.txt', 'w') as f: + f.write(xsuite_string) + +# Results -------------------------------------------------------------------------------------------------------------- +print(f"\nPlotting result...") + +# Plot Phasespace + +plt.figure() +plt.plot(mon.zeta.T,mon.ptau.T) +plt.scatter(np.mean(mon.zeta.T), np.mean(mon.ptau.T), label='mon') +plt.grid() +plt.title('Phasespace') +plt.xlabel(r'$\zeta$') +plt.ylabel(r'$p_{\tau}$') +plt.savefig(results_directory + '/figure_Phasespace.png') + +# Use Xplt to plot Phasespace + +plot = xplt.PhaseSpacePlot( + mon +) +plot.fig.suptitle("Particle distribution for a single turn") +plot.fig.savefig(results_directory + '/figure_ps_mon.png') + +# Show plots +plt.show() diff --git a/__EXAMPLES/main_files/EX_26_single_particle_oneturnmatrix_psb.py b/__EXAMPLES/main_files/EX_26_single_particle_oneturnmatrix_psb.py new file mode 100644 index 0000000000000000000000000000000000000000..845ffa3c5fe5830780c9e7ba90d14a202d255ab1 --- /dev/null +++ b/__EXAMPLES/main_files/EX_26_single_particle_oneturnmatrix_psb.py @@ -0,0 +1,233 @@ +''' +PSB simulation of a single particle. + +:Author: **Thom Arnoldus van Rijswijk** +''' + +# Imports +import os +import numpy as np +from scipy.constants import c, e, m_p +import matplotlib.pyplot as plt + +# Xsuite imports +import xpart as xp +import xtrack as xt +import xplt + +# BLonD objects +from blond.beam.beam import Beam, Proton +from blond.input_parameters.ring import Ring +from blond.input_parameters.rf_parameters import RFStation +from blond.trackers.tracker import RingAndRFTracker + +# Interface +from blond.interfaces.xsuite import (BlondElement, + blond_to_xsuite_transform, + xsuite_to_blond_transform) + +# Monitor objects +from blond.monitors.monitors import BunchMonitor +from blond.plots.plot import Plot + +# Directory to save files +this_directory = os.path.dirname(os.path.realpath(__file__)) + '/' +results_directory = this_directory + '../output_files/EX_26' +os.makedirs(results_directory, exist_ok=True) + +# Parameters ---------------------------------------------------------------------------------------------------------- +# Accelerator parameters +C = 2 * np.pi * 25.000057945260654 # Machine circumference, radius 25 [m] +gamma_t = 4.11635447373496 # Transition gamma [-] +alpha = 1./gamma_t/gamma_t # First order mom. comp. factor [-] +h = 1 # Harmonic number [-] +V = 8e3 # RF voltage [V] +dphi = np.pi # Phase modulation/offset [rad] + +# Beam parameters +N_m = 1 # Number of macroparticles [-] +N_p = 1e11 # Intensity / number of particles +sigma_dt = 180e-9 / 4 # [s] +blen = sigma_dt * 4 # Initial bunch length, 4 sigma [s] +kin_beam_energy = 1.4e9 # [eV] + + +# Simulation parameters +N_t = 6800 # Number of (tracked) turns [-] +N_buckets = 1 # Number of buckets [-] +dt_plt = 500 # Timestep between plots [-] +input_dt = 5.720357923415153e-07 - sigma_dt # Input particles dt [s] +input_dE = 0.0 # Input particles dE [eV] + +# Derived parameters +E_0 = m_p * c**2 / e # [eV] +tot_beam_energy = E_0 + kin_beam_energy # [eV] +sync_momentum = np.sqrt(tot_beam_energy**2 - E_0**2) # [eV / c] + +# Preparing BLonD items ----------------------------------------------------------------------------------------------- +print(f"\nPreparing BLonD items...") +# --- BLonD objects --- +ring = Ring(C, alpha, np.linspace(sync_momentum, sync_momentum, N_t + 1), Proton(), n_turns=N_t) +rfstation = RFStation(ring, [h], [V], [dphi]) +beam = Beam(ring, N_m, N_p) + +# --- Insert particle --- +beam.dt = np.array([input_dt]) +beam.dE = np.array([input_dE]) + +# --- RF tracker object --- +blond_tracker = RingAndRFTracker(rfstation, beam, Profile=None, interpolation=False, + CavityFeedback=None, BeamFeedback=None, + TotalInducedVoltage=None, + with_xsuite=True) + +# --- Bunch monitor --- +# Print parameters for plotting +print(f"\n Calculating plotting parameters...") + +t_u = np.pi / rfstation.omega_rf[0, 0] +Y_phi_s = np.sqrt(abs(-np.cos(rfstation.phi_s[0]) + (np.pi - 2 * rfstation.phi_s[0]) / 2 * np.sin(rfstation.phi_s[0]))) +dE_sep = np.sqrt(2 * abs(rfstation.charge) * rfstation.voltage[0, 0] * rfstation.beta[0] * rfstation.beta[0] + * rfstation.energy[0] / (np.pi * rfstation.harmonic[0, 0] * abs(ring.eta_0[0, 0]))) * Y_phi_s + +print('t_u: ' + str(2 * t_u)) +print('dE_sep,m: ' + str(dE_sep)) + +# Make bunchmonitor +bunchmonitor = BunchMonitor(ring, rfstation, beam, + results_directory + '/EX_26_output_data', Profile=None) + +format_options = {'dirname': results_directory + '/EX_26_fig'} +plots = Plot(ring, rfstation, beam, dt_plt, N_t, 0, 2 * t_u, + -1.05 * dE_sep, 1.05 * dE_sep, xunit='s', separatrix_plot=True, + Profile=None, h5file=results_directory + '/EX_26_output_data', + format_options=format_options) + +# --- Creating interface elements --- +# Blond tracker +cavity = BlondElement(blond_tracker, beam) +# BLonD monitor +beam_monitor = BlondElement(bunchmonitor, beam) +beam_plots = BlondElement(plots, beam) + +# Preparing Xsuite items ----------------------------------------------------------------------------------------------- +print(f"\nPreparing Xsuite items...") +# --- Setup matrix --- +# Make First order matrix map (takes care of drift in Xsuite) +matrix = xt.LineSegmentMap( + longitudinal_mode='nonlinear', + qx=1.1, qy=1.2, + betx=1., + bety=1., + voltage_rf=0, + frequency_rf=0, + lag_rf=0, + momentum_compaction_factor=alpha, + length=C) + +# Create line +line = xt.Line(elements=[matrix], element_names={'matrix'}) +line['matrix'].length = C + +# Insert the BLonD elements +line.insert_element(index=0, element=cavity, name='blond_cavity') +line.append_element(element=beam_monitor, name='blond_monitor') +line.append_element(element=beam_plots, name='blond_plots') + +# Add particles to line and build tracker +line.particle_ref = xp.Particles(p0c=sync_momentum, mass0=xp.PROTON_MASS_EV, q0=1.) +line.build_tracker() + +# Show table +line.get_table().show() +# prints: + +# name s element_type isthick isreplica parent_name iscollective +# blond_cavity 0 BlondElement False False None True +# matrix 0 LineSegmentMap True False None False +# blond_monitor 157.08 BlondObserver False False None True +# blond_plots 157.08 BlondElement False False None True +# _end_point 157.08 False False None False + +# Simulating ---------------------------------------------------------------------------------------------------------- +print(f"\nSetting up simulation...") + +# --- Convert the initial BLonD distribution to xsuite coordinates --- +zeta, ptau = blond_to_xsuite_transform( + beam.dt, beam.dE, line.particle_ref.beta0[0], + line.particle_ref.energy0[0], phi_s=rfstation.phi_s[0] - rfstation.phi_rf[0, 0], # Correct for RF phase + omega_rf=rfstation.omega_rf[0, 0] +) + +# --- Track matrix --- +particles = line.build_particles(x=0, y=0, px=0, py=0, zeta=np.copy(zeta), ptau=np.copy(ptau)) +line.track(particles, num_turns=N_t, turn_by_turn_monitor=True, with_progress=True) +mon = line.record_last_track + +# Saving turn-by-turn particle coordinates +test_string = '' +test_string += '{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\n'.format( + 'dE', 'dt', 'momentum', 'gamma', 'beta', 'energy', 'x', 'px', 'y', 'py') +test_string += ('{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}' + '\t{:+10.10e}\t{:+10.10e}\n').format(beam.dE[0], beam.dt[0], rfstation.momentum[0], + rfstation.gamma[0], rfstation.beta[0], rfstation.energy[0], + mon.x[0, 0].T, mon.px[0, 0].T, mon.y[0, 0].T, mon.py[0, 0].T) + +# Saving turn-by-turn Xsuite particle coordinates +xsuite_string = '' +xsuite_string += '{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\n'.format( + 'ptau', 'zeta', 'momentum', 'gamma', 'beta') +xsuite_string += '{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\n'.format( + mon.ptau[0, 0].T, mon.zeta[0, 0].T, np.float64(mon.p0c[0, 0]), np.float64(mon.gamma0[0, 0]), + np.float64(mon.beta0[0, 0]) +) + +# Convert the xsuite particle coordinates back to BLonD +for i in range(N_t): + dt, dE = xsuite_to_blond_transform( + mon.zeta[:, i].T, mon.ptau[:, i].T, + rfstation.beta[i], rfstation.energy[i], + phi_s=rfstation.phi_s[i] - rfstation.phi_rf[0, 0], # Correct for RF phase + omega_rf=rfstation.omega_rf[0, i] + ) + + # Statistics + test_string += ('{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}' + '\t{:+10.10e}\t{:+10.10e}\n').format(dE[0], dt[0], rfstation.momentum[i], rfstation.gamma[i], + rfstation.beta[i], rfstation.energy[i], mon.x[0, i].T, + mon.px[0, i].T, mon.y[0, i].T, mon.py[0, i].T) + + xsuite_string += '{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\n'.format( + mon.ptau[0, i].T, mon.zeta[0, i].T, np.float64(mon.p0c[0, i]), np.float64(mon.gamma0[0, i]), + np.float64(mon.beta0[0, i]) + ) + +with open(results_directory + '/EX_26_output_data.txt', 'w') as f: + f.write(test_string) +with open(results_directory + '/EX_26_output_data_xsuite.txt', 'w') as f: + f.write(xsuite_string) + +# Results -------------------------------------------------------------------------------------------------------------- +print(f"\nPlotting result...") + +# Plot Phasespace + +plt.figure() +plt.plot(mon.zeta.T,mon.ptau.T) +plt.scatter(np.mean(mon.zeta.T), np.mean(mon.ptau.T), label='mon') +plt.grid() +plt.title('Phasespace') +plt.xlabel(r'$\zeta$') +plt.ylabel(r'$p_{\tau}$') +plt.savefig(results_directory + '/figure_Phasespace.png') + +# Use Xplt to plot Phasespace + +plot = xplt.PhaseSpacePlot( + mon +) +plot.fig.suptitle("Particle distribution for a single turn") +plot.fig.savefig(results_directory + '/figure_ps_mon.png') + +# Show plots +plt.show() diff --git a/__EXAMPLES/main_files/EX_27_single_particle_ramp_psb.py b/__EXAMPLES/main_files/EX_27_single_particle_ramp_psb.py new file mode 100644 index 0000000000000000000000000000000000000000..dd82280ebed2cf979bbd7dda6af67df4d44312f8 --- /dev/null +++ b/__EXAMPLES/main_files/EX_27_single_particle_ramp_psb.py @@ -0,0 +1,239 @@ +''' +PSB simulation of a single particle with a ramp. + +:Author: **Thom Arnoldus van Rijswijk** +''' + +# Imports +import os +import numpy as np +from scipy.constants import c, e, m_p +import matplotlib.pyplot as plt + +# Xsuite imports +import xpart as xp +import xtrack as xt +import xplt + +# BLonD objects +from blond.beam.beam import Beam, Proton +from blond.input_parameters.ring import Ring +from blond.input_parameters.rf_parameters import RFStation +from blond.trackers.tracker import RingAndRFTracker + +# Interface +from blond.interfaces.xsuite import (BlondElement, EnergyUpdate, + blond_to_xsuite_transform, + xsuite_to_blond_transform) + +# Monitor objects +from blond.monitors.monitors import BunchMonitor +from blond.plots.plot import Plot + +# Directory to save files +this_directory = os.path.dirname(os.path.realpath(__file__)) + '/' +results_directory = this_directory + '../output_files/EX_27' +os.makedirs(results_directory, exist_ok=True) + +# Parameters ---------------------------------------------------------------------------------------------------------- +# Accelerator parameters +C = 2 * np.pi * 25.000057945260654 # Machine circumference, radius 25 [m] +gamma_t = 4.11635447373496 # Transition gamma [-] +alpha = 1./gamma_t/gamma_t # First order mom. comp. factor [-] +h = 1 # Harmonic number [-] +V = 8e3 # RF voltage [V] +dphi = np.pi # Phase modulation/offset [rad] + +# Beam parameters +N_m = 1 # Number of macroparticles [-] +N_p = 1e11 # Intensity / number of particles +sigma_dt = 180e-9 / 4 # [s] +blen = sigma_dt * 4 # Initial bunch length, 4 sigma [s] +kin_beam_energy = 1.4e9 # Kinetic energy [eV] + + +# Simulation parameters +N_t = 6000 # Number of (tracked) turns [-] +N_buckets = 1 # Number of buckets [-] +dt_plt = 500 # Timestep between plots [-] +input_dt = 5.720357923415153e-07 - sigma_dt # Input particles dt [s] +input_dE = 0.0 # Input particles dE [eV] + +# Derived parameters +E_0 = m_p * c**2 / e # [eV] +tot_beam_energy = E_0 + kin_beam_energy # [eV] +sync_momentum = np.sqrt(tot_beam_energy**2 - E_0**2) # [eV / c] +momentum_increase = 0.001e9 # [eV / c] + +# Preparing BLonD items ----------------------------------------------------------------------------------------------- +print(f"\nPreparing BLonD items...") +# --- BLonD objects --- +ring = Ring(C, alpha, np.linspace(sync_momentum, sync_momentum + momentum_increase, N_t + 1), Proton(), n_turns=N_t) +rfstation = RFStation(ring, [h], [V], [dphi]) +beam = Beam(ring, N_m, N_p) + +# --- Insert particle --- +beam.dt = np.array([input_dt]) +beam.dE = np.array([input_dE]) + +# --- RF tracker object --- +blond_tracker = RingAndRFTracker(rfstation, beam, Profile=None, interpolation=False, + CavityFeedback=None, BeamFeedback=None, + TotalInducedVoltage=None, + with_xsuite=True) + +# --- Bunch monitor --- +# Print parameters for plotting +print(f"\n Calculating plotting parameters...") + +t_u = np.pi / rfstation.omega_rf[0, 0] +Y_phi_s = np.sqrt(abs(-np.cos(rfstation.phi_s[0]) + (np.pi - 2 * rfstation.phi_s[0]) / 2 * np.sin(rfstation.phi_s[0]))) +dE_sep = np.sqrt(2 * abs(rfstation.charge) * rfstation.voltage[0, 0] * rfstation.beta[0] * rfstation.beta[0] + * rfstation.energy[0] / (np.pi * rfstation.harmonic[0, 0] * abs(ring.eta_0[0, 0]))) * Y_phi_s + +print('t_u: ' + str(2 * t_u)) +print('dE_sep,m: ' + str(dE_sep)) + +# Make bunchmonitor +bunchmonitor = BunchMonitor(ring, rfstation, beam, + results_directory + '/EX_27_output_data', Profile=None) + +format_options = {'dirname': results_directory + '/EX_27_fig'} +plots = Plot(ring, rfstation, beam, dt_plt, N_t, 0, 2 * t_u, + -1.05 * dE_sep, 1.05 * dE_sep, xunit='s', separatrix_plot=True, + Profile=None, h5file=results_directory + '/EX_27_output_data', + format_options=format_options) + +# --- Creating interface elements --- +# Blond tracker +cavity = BlondElement(blond_tracker, beam) +# BLonD monitor +beam_monitor = BlondElement(bunchmonitor, beam) +beam_plots = BlondElement(plots, beam) + +# Preparing Xsuite items ----------------------------------------------------------------------------------------------- +print(f"\nPreparing Xsuite items...") +# --- Setup matrix --- +# Make First order matrix map (takes care of drift in Xsuite) +matrix = xt.LineSegmentMap( + longitudinal_mode='nonlinear', + qx=1.1, qy=1.2, + betx=1., + bety=1., + voltage_rf=0, + frequency_rf=0, + lag_rf=0, + momentum_compaction_factor=alpha, + length=C) + +# Create line +line = xt.Line(elements=[matrix], element_names={'matrix'}) +line['matrix'].length = C + +# Insert the BLonD elements +line.insert_element(index=0, element=cavity, name='blond_cavity') +line.append_element(element=beam_monitor, name='blond_monitor') +line.append_element(element=beam_plots, name='blond_plots') + +# Insert energy ramp +energy_update = EnergyUpdate(ring.momentum[0, :]) +line.insert_element(index=0, element=energy_update, name='energy_update') + +# Add particles to line and build tracker +line.particle_ref = xp.Particles(p0c=sync_momentum, mass0=xp.PROTON_MASS_EV, q0=1.) +line.build_tracker() + +# Show table +line.get_table().show() +# prints: + +# name s element_type isthick isreplica parent_name iscollective +# energy_update 0 EnergyUpdate False False None True +# blond_cavity 0 BlondElement False False None True +# matrix 0 LineSegmentMap True False None False +# blond_monitor 157.08 BlondObserver False False None True +# blond_plots 157.08 BlondElement False False None True +# _end_point 157.08 False False None False + +# Simulating ---------------------------------------------------------------------------------------------------------- +print(f"\nSetting up simulation...") + +# --- Convert the initial BLonD distribution to xsuite coordinates --- +zeta, ptau = blond_to_xsuite_transform( + beam.dt, beam.dE, line.particle_ref.beta0[0], + line.particle_ref.energy0[0], phi_s=rfstation.phi_s[0] - rfstation.phi_rf[0, 0], # Correct for RF phase + omega_rf=rfstation.omega_rf[0, 0] +) + +# --- Track matrix --- +particles = line.build_particles(x=0, y=0, px=0, py=0, zeta=np.copy(zeta), ptau=np.copy(ptau)) +line.track(particles, num_turns=N_t, turn_by_turn_monitor=True, with_progress=True) +mon = line.record_last_track + +# Saving turn-by-turn particle coordinates +test_string = '' +test_string += '{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\n'.format( + 'dE', 'dt', 'momentum', 'gamma', 'beta', 'energy', 'x', 'px', 'y', 'py') +test_string += ('{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}' + '\t{:+10.10e}\t{:+10.10e}\n').format(beam.dE[0], beam.dt[0], rfstation.momentum[0], + rfstation.gamma[0], rfstation.beta[0], rfstation.energy[0], + mon.x[0, 0].T, mon.px[0, 0].T, mon.y[0, 0].T, mon.py[0, 0].T) + +# Saving turn-by-turn Xsuite particle coordinates +xsuite_string = '' +xsuite_string += '{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\n'.format( + 'ptau', 'zeta', 'momentum', 'gamma', 'beta') +xsuite_string += '{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\n'.format( + mon.ptau[0, 0].T, mon.zeta[0, 0].T, np.float64(mon.p0c[0, 0]), np.float64(mon.gamma0[0, 0]), + np.float64(mon.beta0[0, 0]) +) + +# Convert the xsuite particle coordinates back to BLonD +for i in range(N_t): + dt, dE = xsuite_to_blond_transform( + mon.zeta[:, i].T, mon.ptau[:, i].T, + rfstation.beta[i], rfstation.energy[i], + phi_s=rfstation.phi_s[i] - rfstation.phi_rf[0, 0], # Correct for RF phase + omega_rf=rfstation.omega_rf[0, i] + ) + + # Statistics + test_string += ('{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}' + '\t{:+10.10e}\t{:+10.10e}\n').format(dE[0], dt[0], rfstation.momentum[i], rfstation.gamma[i], + rfstation.beta[i], rfstation.energy[i], mon.x[0, i].T, + mon.px[0, i].T, mon.y[0, i].T, mon.py[0, i].T) + + xsuite_string += '{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\n'.format( + mon.ptau[0, i].T, mon.zeta[0, i].T, np.float64(mon.p0c[0, i]), np.float64(mon.gamma0[0, i]), + np.float64(mon.beta0[0, i]) + ) + +with open(results_directory + '/EX_27_output_data.txt', 'w') as f: + f.write(test_string) +with open(results_directory + '/EX_27_output_data_xsuite.txt', 'w') as f: + f.write(xsuite_string) + +# Results -------------------------------------------------------------------------------------------------------------- +print(f"\nPlotting result...") + +# Plot Phasespace + +plt.figure() +plt.plot(mon.zeta.T,mon.ptau.T) +plt.scatter(np.mean(mon.zeta.T), np.mean(mon.ptau.T), label='mon') +plt.grid() +plt.title('Phasespace') +plt.xlabel(r'$\zeta$') +plt.ylabel(r'$p_{\tau}$') +plt.savefig(results_directory + '/figure_Phasespace.png') + +# Use Xplt to plot Phasespace + +plot = xplt.PhaseSpacePlot( + mon +) +plot.fig.suptitle("Particle distribution for a single turn") +plot.fig.savefig(results_directory + '/figure_ps_mon.png') + +# Show plots +plt.show() diff --git a/__EXAMPLES/main_files/EX_28_phase_loop_lhc.py b/__EXAMPLES/main_files/EX_28_phase_loop_lhc.py new file mode 100644 index 0000000000000000000000000000000000000000..bcb349ff863f0551d6386602af3dc35c3e1043b3 --- /dev/null +++ b/__EXAMPLES/main_files/EX_28_phase_loop_lhc.py @@ -0,0 +1,247 @@ +''' +LHC simulation using the LHC phase loop. + +:Author: **Birk Emil Karlsen-Baeck**, **Thom Arnoldus van Rijswijk** +''' + +# Imports +import os +import numpy as np +import matplotlib.pyplot as plt + +# Xsuite imports +import xpart as xp +import xtrack as xt +import xplt + +# BLonD objects +from blond.beam.beam import Beam, Proton +from blond.beam.profile import Profile, CutOptions, FitOptions +from blond.beam.distributions import parabolic +from blond.input_parameters.ring import Ring +from blond.input_parameters.rf_parameters import RFStation +from blond.trackers.tracker import RingAndRFTracker +from blond.llrf.beam_feedback import BeamFeedback + +# Interface +from blond.interfaces.xsuite import (BlondElement, BlondObserver, + blond_to_xsuite_transform, + xsuite_to_blond_transform) + +# Monitor objects +from blond.monitors.monitors import BunchMonitor +from blond.plots.plot import Plot + +# Directory to save files +this_directory = os.path.dirname(os.path.realpath(__file__)) + '/' +results_directory = this_directory + '../output_files/EX_28' +os.makedirs(results_directory, exist_ok=True) + +# Parameters ---------------------------------------------------------------------------------------------------------- +# Accelerator parameters +C = 26658.8832 # Machine circumference [m] +p_s = 450e9 # Synchronous momentum [eV/c] +p_f = 450.0e9 # Synchronous momentum, final +h = 35640 # Harmonic number [-] +alpha = 0.00034849575112251314 # First order mom. comp. factor [-] +V = 5e6 # RF voltage [V] +dphi = 0 # Phase modulation/offset [rad] + +# Bunch parameters +N_bunches = 1 # Number of bunches [-] +N_m = int(1e3) # Number of macroparticles [-] +N_p = 1.15e11 # Intensity +blen = 1.25e-9 # Bunch length [s] +energy_err = 100e6 # Beamenergy error [eV/c] + +# Simulation parameters +N_t = 2 * 481 # Number of (tracked) turns [-] +N_buckets = 2 # Number of buckets [-] +dt_plt = 50 # Timestep between plots [-] + +# Preparing BLonD items ----------------------------------------------------------------------------------------------- +print(f"\nPreparing BLonD items...") +# --- BLonD objects --- +ring = Ring(C, alpha, np.linspace(p_s, p_f, N_t + 1), Proton(), n_turns=N_t) +rfstation = RFStation(ring, [h], [V], [dphi]) +beam = Beam(ring, N_m, N_p) + +# --- Insert particles --- +parabolic(ring, rfstation, beam, bunch_length=blen) +beam.dE += energy_err + +# Create Profile +profile = Profile(beam, + CutOptions(cut_left=-1.5 * rfstation.t_rf[0, 0], + cut_right=(N_buckets + 1.5) * rfstation.t_rf[0, 0], + n_slices=(N_buckets + 3) * 2**5), + FitOptions(fit_option='gaussian')) +profile.track() + +# --- LHC beam-phase loop and synchronization loop --- +PL_gain = 1 / (5 * ring.t_rev[0]) +SL_gain = PL_gain / 10 + +bl_config = {'machine': 'LHC', + 'PL_gain': PL_gain, + 'SL_gain': SL_gain} +BL = BeamFeedback(ring, rfstation, profile, bl_config) + +# --- RF tracker object --- +blond_tracker = RingAndRFTracker(rfstation, beam, Profile=profile, interpolation=False, + CavityFeedback=None, BeamFeedback=BL, + TotalInducedVoltage=None, + with_xsuite=True) + +# --- Bunch monitor --- +# Print parameters for plotting +print(f"\n Calculating plotting parameters...") + +t_u = np.pi / rfstation.omega_rf[0, 0] +Y_phi_s = np.sqrt(abs(-np.cos(rfstation.phi_s[0]) + (np.pi - 2 * rfstation.phi_s[0]) / 2 * np.sin(rfstation.phi_s[0]))) +dE_sep = np.sqrt(2 * abs(rfstation.charge) * rfstation.voltage[0, 0] * rfstation.beta[0] * rfstation.beta[0] + * rfstation.energy[0] / (np.pi * rfstation.harmonic[0, 0] * abs(ring.eta_0[0, 0]))) * Y_phi_s + +print('t_u: ' + str(2 * t_u)) +print('dE_sep,m: ' + str(dE_sep)) + +# Make bunchmonitor +bunchmonitor = BunchMonitor(ring, rfstation, beam, + results_directory + '/EX_28_output_data', Profile=profile, PhaseLoop=BL) + +format_options = {'dirname': results_directory + '/EX_28_fig'} +plots = Plot(ring, rfstation, beam, dt_plt, N_t, 0, 2*t_u, + -1.05 * dE_sep, 1.05 * dE_sep, xunit='s', separatrix_plot=True, + Profile=profile, PhaseLoop=BL, h5file=results_directory + '/EX_28_output_data', + format_options=format_options) + +# --- Creating interface elements --- +# Blond tracker +cavity = BlondElement(blond_tracker, beam) +# BLonD monitor +beam_monitor = BlondElement(bunchmonitor, beam) +beam_profile = BlondElement(profile, beam) +beam_plots = BlondElement(plots, beam) + +# Preparing Xsuite items ----------------------------------------------------------------------------------------------- +print(f"\nPreparing Xsuite items...") +# --- Setup matrix --- +# Make First order matrix map (takes care of drift in Xsuite) +matrix = xt.LineSegmentMap( + longitudinal_mode='nonlinear', + qx=1.1, qy=1.2, + betx=1., + bety=1., + voltage_rf=0, + frequency_rf=0, + lag_rf=0, + momentum_compaction_factor=alpha, + length=C) + +# Create line +line = xt.Line(elements=[matrix], element_names={'matrix'}) +line['matrix'].length = C + +# Insert the BLonD elements +line.insert_element(index=0, element=cavity, name='blond_cavity') +line.append_element(element=beam_monitor, name='blond_monitor') +line.append_element(element=beam_profile, name='blond_profile') +line.append_element(element=beam_plots, name='blond_plots') + +# Add particles to line and build tracker +line.particle_ref = xp.Particles(p0c=p_s, mass0=xp.PROTON_MASS_EV, q0=1.) +line.build_tracker() + +# Show table +line.get_table().show() +# prints: + +# name s element_type isthick isreplica parent_name iscollective +# blond_cavity 0 BlondElement False False None True +# matrix 0 LineSegmentMap True False None False +# blond_monitor 26658.9 BlondObserver False False None True +# blond_plots 26658.9 BlondElement False False None True +# _end_point 26658.9 False False None False + +# Simulating ---------------------------------------------------------------------------------------------------------- +print(f"\nSetting up simulation...") + +# --- Convert the initial BLonD distribution to xsuite coordinates --- +zeta, ptau = blond_to_xsuite_transform( + beam.dt, beam.dE, line.particle_ref.beta0[0], + line.particle_ref.energy0[0], phi_s=rfstation.phi_s[0], + omega_rf=rfstation.omega_rf[0, 0] +) + +# --- Track matrix --- +particles = line.build_particles(x=0, y=0, px=0, py=0, zeta=np.copy(zeta), ptau=np.copy(ptau)) +line.track(particles, num_turns=N_t, turn_by_turn_monitor=True, with_progress=True) +mon = line.record_last_track + +# Saving turn-by-turn particle coordinates +test_string = '' +test_string += '{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\n'.format( + 'dE', 'dt', 'momentum', 'gamma', 'beta', 'energy', 'x', 'px', 'y', 'py') +test_string += ('{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}' + '\t{:+10.10e}\t{:+10.10e}\n').format(beam.dE[0], beam.dt[0], rfstation.momentum[0], + rfstation.gamma[0], rfstation.beta[0], rfstation.energy[0], + mon.x[0, 0].T, mon.px[0, 0].T, mon.y[0, 0].T, mon.py[0, 0].T) + +# Saving turn-by-turn Xsuite particle coordinates +xsuite_string = '' +xsuite_string += '{:<17}\t{:<17}\t{:<17}\t{:<17}\t{:<17}\n'.format( + 'ptau', 'zeta', 'momentum', 'gamma', 'beta') +xsuite_string += '{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\n'.format( + mon.ptau[0, 0].T, mon.zeta[0, 0].T, np.float64(mon.p0c[0, 0]), np.float64(mon.gamma0[0, 0]), + np.float64(mon.beta0[0, 0]) +) + +# Convert the xsuite particle coordinates back to BLonD +for i in range(N_t): + dt, dE = xsuite_to_blond_transform( + mon.zeta[:, i].T, mon.ptau[:, i].T, + rfstation.beta[i], rfstation.energy[i], + phi_s=rfstation.phi_s[i] - rfstation.phi_rf[0, 0], # Correct for RF phase + omega_rf=rfstation.omega_rf[0, i] + ) + + # Statistics + test_string += ('{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}' + '\t{:+10.10e}\t{:+10.10e}\n').format(dE[0], dt[0], rfstation.momentum[i], rfstation.gamma[i], + rfstation.beta[i], rfstation.energy[i], mon.x[0, i].T, + mon.px[0, i].T, mon.y[0, i].T, mon.py[0, i].T) + + xsuite_string += '{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\t{:+10.10e}\n'.format( + mon.ptau[0, i].T, mon.zeta[0, i].T, np.float64(mon.p0c[0, i]), np.float64(mon.gamma0[0, i]), + np.float64(mon.beta0[0, i]) + ) + +with open(results_directory + '/EX_28_output_data.txt', 'w') as f: + f.write(test_string) +with open(results_directory + '/EX_28_output_data_xsuite.txt', 'w') as f: + f.write(xsuite_string) + +# Results -------------------------------------------------------------------------------------------------------------- +print(f"\nPlotting result...") + +# Plot Phasespace + +plt.figure() +plt.plot(mon.zeta.T,mon.ptau.T) +plt.scatter(np.mean(mon.zeta.T), np.mean(mon.ptau.T), label='mon') +plt.grid() +plt.title('Phasespace') +plt.xlabel(r'$\zeta$') +plt.ylabel(r'$p_{\tau}$') +plt.savefig(results_directory + '/figure_Phasespace.png') + +# Use Xplt to plot Phasespace + +plot = xplt.PhaseSpacePlot( + mon +) +plot.fig.suptitle("Particle distribution for a single turn") +plot.fig.savefig(results_directory + '/figure_ps_mon.png') + +# Show plots +plt.show() diff --git a/blond/interfaces/__init__.py b/blond/interfaces/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/blond/interfaces/xsuite.py b/blond/interfaces/xsuite.py new file mode 100644 index 0000000000000000000000000000000000000000..bd30be38886831b8d2597239353660936382ec73 --- /dev/null +++ b/blond/interfaces/xsuite.py @@ -0,0 +1,404 @@ +''' +Functions and classes to interface BLonD with xsuite. + +:Authors: **Birk Emil Karlsen-Baeck**, **Thom Arnoldus van Rijswijk**, **Helga Timko** +''' + +from __future__ import annotations +from typing import TYPE_CHECKING +import numpy as np +from scipy.constants import c as clight + +from xtrack import ReferenceEnergyIncrease, ZetaShift +from blond.trackers.tracker import RingAndRFTracker +from blond.impedances.impedance import TotalInducedVoltage + +if TYPE_CHECKING: + from xtrack import Particles + from xtrack import Line + from blond.beam.beam import Beam + from blond.beam.profile import Profile + from typing import Any, Union + + +def blond_to_xsuite_transform(dt: Union[float, np.ndarray], de: Union[float, np.ndarray], beta0: float, + energy0: float, omega_rf: float, phi_s: float = 0): + r''' + Coordinate transformation from Xsuite to BLonD at a given turn or multiple turns if numpy arrays ar given. + + :param dt: float or NDArray. + The deviation in time from the reference clock in BLonD. + :param de: float or NDArray. + The deviation in energy from the synchronous particle. + :param beta0: float. + Synchronous beta. + :param energy0: float. + Synchronous energy. + :param omega_rf: float. + The rf angular frequency. + :param phi_s: float. + Synchronous phase in radians equivalent to Xsuite's phi_rf (below transition energy input should be phi_s-phi_rf). + The default value is 0. + + :return zeta, ptau: as numpy-arrays (or single variable) + ''' + + ptau = de / (beta0 * energy0) + zeta = -(dt - phi_s / omega_rf) * beta0 * clight + return zeta, ptau + + +def xsuite_to_blond_transform(zeta: Union[float, np.ndarray], ptau: Union[float, np.ndarray], + beta0: float, energy0: float, omega_rf: float, phi_s: float = 0): + r''' + Coordinate transformation from Xsuite to BLonD. + + :param zeta: float or numpy-array. + The zeta coordinate as defined in Xsuite. + :param ptau: float or numpy-array. + The ptau coordinate as defined in Xsuite. + :param beta0: float. + The synchronous beta. + :param energy0: float. + The synchronous energy in eV. + :param omega_rf: float. + The rf angular frequency. + :param phi_s: float. + The synchronous phase in radians equivalent to Xsuite's phi_rf + (below transition energy input should be phi_s-phi_rf) + + :return dt, dE: as numpy-arrays (or single variable) + ''' + + dE = ptau * beta0 * energy0 + dt = -zeta / (beta0 * clight) + phi_s / omega_rf + return dt, dE + + +class BlondElement: + + def __init__(self, trackable: Any, beam: Beam, update_zeta: bool = False) -> None: + r""" + The BlondElement class contains a trackable object from the BLonD simulation suite and the Beam object from + BLonD. The class is intended to be an element to be added to the Line in XTrack instead of the default + RF cavities in XTrack. + + The behavior of the tracking of the class depends on what trackable object is passed. + If the RingAndRFTracker class is passed then a coordinate transformation will be performed and the energy + deviation of the particles will be updated. If the TotalInducedVoltage class is passed then the + TotalInducedVoltage.induced_voltage_sum() method will be called. Lastly, if any other class is passed then + the tracking of BlondElement will track this class. + + trackable : BLonD trackable object + BLonD object to be tracked, e.g. the RingAndRFTracker. + beam : blond.beam.beam.Beam class + BLonD Beam class used for tracking. + """ + + self.trackable = trackable + self.beam = beam + self.update_zeta = update_zeta + + # Initialize time- and orbit-shift to BLonD coordinates + self._dt_shift = None + self.orbit_shift = ZetaShift(dzeta=0) + + # Check what BLonD trackable has been passed and track the object accordingly + if isinstance(self.trackable, RingAndRFTracker): + self.track = getattr(self, "rf_track") + elif isinstance(self.trackable, TotalInducedVoltage): + self.track = getattr(self, "ind_track") + else: + self.track = getattr(self, "obs_track") + + def rf_track(self, particles: Particles) -> None: + r""" + Tracking method which is called if the trackable BLonD class is the RingAndRFTracker. + + particles : xtrack.Particles + Particles class from xtrack + """ + + # Compute the shift to BLonD coordinates + self._get_time_shift() + + # Convert the Xsuite coordinates to BLonD coordinates + self.xsuite_part_to_blond_beam(particles) + + # Track with only the energy kick + self.trackable.track() + + # Convert the BLonD energy coordinate to the equivalent Xsuite coordinate + self.blond_beam_to_xsuite_part(particles, self.update_zeta) + + # Update the zeta shift due to potential frequency shifts in the RF + self._orbit_shift(particles) + + def ind_track(self, particles: Particles) -> None: + r""" + Tracking method which is called if the trackable BLonD class is the TotalInducedVoltage. + + particles : xtrack.Particles + Particles class from xtrack + """ + self.trackable.induced_voltage_sum() + + def obs_track(self, particles: Particles) -> None: + r""" + Tracking method which is called if the trackable BLonD class is not the RingAndRFTracker or TotalInducedVoltage. + + particles : xtrack.Particles + Particles class from xtrack + """ + self.trackable.track() + + def xsuite_part_to_blond_beam(self, particles: Particles) -> None: + r""" + Coordinate transformation from Xsuite to BLonD. + It uses the initial particle coordinates and beam properties in stored in the Particles class from xtrack. + + particles : xtrack.Particles + Particles class from xtrack + """ + # Convert Xsuite momentum to BLonD energy deviation + self.beam.dE[:] = particles.beta0 * particles.energy0 * particles.ptau + + # Convert Xsuite zeta coordinate to BLonD time deviation + self.beam.dt[:] = -particles.zeta / particles.beta0 / clight + self._dt_shift + + # Check what particles are still alive + self.beam.id[:] = np.int_(particles.state > 0) + + def blond_beam_to_xsuite_part(self, particles: Particles, update_zeta: bool = False) -> None: + r""" + Coordinate transformation from BLonD to Xsuite. + It uses the particle coordinates stored in Beam class of BLonD + It uses the beam properties in stored in the Particles class from xtrack + + particles : xtrack.Particles + Particles class from xtrack + """ + # Subtract the given acceleration kick in BLonD, in Xsuite this is dealt with differently + if isinstance(self.trackable, RingAndRFTracker): + self.beam.dE = self.beam.dE - self.trackable.acceleration_kick[self.trackable.counter[0] - 1] + + # Convert BLonD energy deviation to Xsuite momentum + particles.ptau = self.beam.dE / (particles.beta0 * particles.energy0) + + # Convert BLonD time deviation to Xsuite zeta. + # This step is not needed usually because the BLonD simulation only does the kick, so dt is not changed. + if update_zeta: + particles.zeta = - (self.beam.dt - self._dt_shift) * particles.beta0 * clight + + # Check what particles are still alive after the BLonD track + mask_lost = (self.beam.id <= 0) & particles.state > 0 + + # If the particle is lost its state is set to -500 by convention + particles.state[mask_lost] = -500 + + def _get_time_shift(self) -> None: + r''' + Computes the time-shift between the Xsuite and BLonD coordinate systems. + ''' + # Get turn counter from the RingAndRFTracker + counter = self.trackable.rf_params.counter[0] + + # Compute the time-shift based on the synchronous phase + self._dt_shift = ((self.trackable.rf_params.phi_s[counter] - self.trackable.rf_params.phi_rf[0, counter]) + / self.trackable.rf_params.omega_rf[0, counter]) + + def _orbit_shift(self, particles) -> None: + r''' + Computes the radial steering due to rf periods which are not an integer multiple of the revolution period. + This is for example needed when tracking with global LLRF feedback loops. + + particles : xtrack.Particles + Particles class from xtrack + ''' + # Get turn counter from the RingAndRFTracker + counter = self.trackable.counter[0] + + # Compute the orbit shift due to the difference in rf frequency + dzeta = self.trackable.rf_params.ring_circumference + omega_rf = self.trackable.rf_params.omega_rf[:, counter] + omega_rf_design = (2 * np.pi * self.trackable.rf_params.harmonic[:, counter] + / self.trackable.rf_params.t_rev[counter]) + domega = omega_rf - omega_rf_design + + dzeta *= domega / omega_rf_design + + # Apply the shift + self.orbit_shift.dzeta = dzeta + + # Track the shift + self.orbit_shift.track(particles) + + +class EnergyUpdate: + + def __init__(self, momentum: Union[list, np.ndarray]) -> None: + r""" + Class to update the synchronous energy from the momentum program in BLonD. + + :param momentum: list or numpy-array. + Momentum program from BLonD in units of eV + """ + + # Load momentum program + self.momentum = momentum + + # Find initial momentum update + init_p0c = self.momentum[1] - self.momentum[0] + + # Enter the initial momentum update in the ReferenceEnergyIncrease class in xsuite + self.xsuite_energy_update = ReferenceEnergyIncrease(Delta_p0c=init_p0c) + + def track(self, particles: Particles) -> None: + r''' + Track method for the class to update the synchronous energy. + + particles : xtrack.Particles + Particles class from xtrack + ''' + # Check for particles which are still alive + mask_alive = particles.state > 0 + + # Use the still alive particles to find the current turn momentum + p0c_before = particles.p0c[mask_alive] + + # Find the momentum for the next turn + p0c_after = self.momentum[particles.at_turn[mask_alive][0]] + + # Update the energy increment + self.xsuite_energy_update.Delta_p0c = p0c_after - p0c_before[0] + + # Apply the energy increment to the particles + self.xsuite_energy_update.track(particles) + + +class EnergyFrequencyUpdate: + + def __init__(self, momentum: np.ndarray, f_rf: np.ndarray, line: Line, cavity_name: str) -> None: + r""" + Class to update energy of Particles class turn-by-turn with the ReferenceEnergyIncrease function + from xtrack. Additionally it updates the frequency of the xtrack cavity in the line. + Intended to be used without BLonD-Xsuite interface. + + :param momentum: numpy-array + The momentum program from BLonD in eV + :param f_rf: numpy-array + The frequency program from BLonD in Hz. + particles : xtrack.Line + Line class from xtrack + :param cavity_name: string + Name of cavity to update frequency. + """ + + # Load the parameters + self.momentum = momentum + self.f_rf = f_rf + self.line = line + self.cavity_name = cavity_name + + # Find initial momentum update + init_p0c = self.momentum[1] - self.momentum[0] + + # Enter the initial momentum update in the ReferenceEnergyIncrease class in xsuite + self.xsuite_energy_update = ReferenceEnergyIncrease(Delta_p0c=init_p0c) + + def track(self, particles: Particles) -> None: + r''' + Track-method from for the class. This method updates the synchronous momentum and the rf frequency. + + particles : xtrack.Particles + Particles class from xtrack + ''' + # Check for particles which are still alive + mask_alive = particles.state > 0 + + # Use the still alive particles to find the current turn momentum + p0c_before = particles.p0c[mask_alive] + + # Find the momentum for the next turn + p0c_after = self.momentum[particles.at_turn[mask_alive][0]] + + # Update the energy increment + self.xsuite_energy_update.Delta_p0c = p0c_after - p0c_before[0] + + # Apply the energy increment to the particles + self.xsuite_energy_update.track(particles) + + # Update the rf frequency + self.line[self.cavity_name].frequency = self.f_rf[particles.at_turn[mask_alive][0]] + + +class BlondObserver(BlondElement): + + def __init__(self, trackable: Any, beam: Beam, blond_cavity: bool, update_zeta: bool = False, + profile: Profile = None) -> None: + r''' + Child-class of the BlondElement, except that it updates the coordinates + in BLonD when an observing element is used such as BunchMonitor. + + :param trackable: BLonD trackable object + BLonD object to be tracked, e.g. the RingAndRFTracker. + :param beam: blond.beam.beam.Beam class + BLonD Beam class used for tracking. + :param blond_cavity: bool. + If there is no BlondCavity (bool = False), it updates its own turn-counter. + :param update_zeta: bool. + Boolean that decides whether zeta is converter back to dt after tracking object or not. + Usually not necessary so default is False. + :param profile: blond.beam.profile.Profile class + BLonD Profile class used for tracking. + ''' + # Initialize the parent class + super().__init__(trackable, beam, update_zeta) + + # Load the parameters + self.blond_cavity = blond_cavity + # For bunch monitoring we need the profile separately added + self.profile = profile + + # Initializing arrays for storing some turn-by-turn properties of xtrack.Particles class + self.xsuite_ref_energy = np.zeros(self.trackable.rf_params.n_turns + 1) + self.xsuite_trev = np.zeros(self.trackable.rf_params.n_turns + 1) + + def obs_track(self, particles: Particles) -> None: + r''' + observation tracker which performs the coordinate transformations. + + particles : xtrack.Particles + Particles class from xtrack + ''' + # Compute the shift to BLonD coordinates + self._get_time_shift() + + # Convert the Xsuite coordinates to BLonD coordinates + self.xsuite_part_to_blond_beam(particles) + + # Track profile if given + if self.profile is not None: + self.profile.track() + + # Track + self.trackable.track() + + if not self.blond_cavity: + # Updating the beam synchronous momentum etc. + turn = self.trackable.rf_params.counter[0] + + self.beam.beta = self.trackable.rf_params.beta[turn + 1] + self.beam.gamma = self.trackable.rf_params.gamma[turn + 1] + self.beam.energy = self.trackable.rf_params.energy[turn + 1] + self.beam.momentum = self.trackable.rf_params.momentum[turn + 1] + + # Update custom counter + self.trackable.rf_params.counter[0] += 1 + + # Convert the BLonD energy coordinate to the equivalent Xsuite coordinate + self.blond_beam_to_xsuite_part(particles, self.update_zeta) + + # Track properties of xtrack.Particles + self.xsuite_ref_energy[self.trackable.rf_params.counter[0] - 1] = particles.energy0[0] + self.xsuite_trev[self.trackable.rf_params.counter[0] - 1] = particles.t_sim # Does not update per turn! diff --git a/unittests/interfaces/__init__.py b/unittests/interfaces/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/unittests/interfaces/test_xsuite.py b/unittests/interfaces/test_xsuite.py new file mode 100644 index 0000000000000000000000000000000000000000..c5a0e57d4e8ed6721ee5a53220c14107a23c51d7 --- /dev/null +++ b/unittests/interfaces/test_xsuite.py @@ -0,0 +1,629 @@ +# coding: utf8 +# Copyright 2014-2017 CERN. This software is distributed under the +# terms of the GNU General Public Licence version 3 (GPL Version 3), +# copied verbatim in the file LICENCE.md. +# In applying this licence, CERN does not waive the privileges and immunities +# granted to it by virtue of its status as an Intergovernmental Organization or +# submit itself to any jurisdiction. +# Project website: http://blond.web.cern.ch/ + +""" +Unittest for interfaces.xsuite + +:Authors: **Birk Emil Karlsen-Bæck**, **Thom Arnoldus van Rijswijk** +""" + +import os +import unittest + +import numpy as np +from scipy.constants import c, e, m_p + +import xpart as xp +import xtrack as xt + +from blond.beam.beam import Beam, Proton +from blond.beam.profile import CutOptions, Profile +from blond.input_parameters.rf_parameters import RFStation +from blond.input_parameters.ring import Ring +from blond.trackers.tracker import RingAndRFTracker, FullRingAndRF +from blond.monitors.monitors import BunchMonitor + +from blond.interfaces.xsuite import (BlondElement, BlondObserver, + EnergyUpdate, + blond_to_xsuite_transform, + xsuite_to_blond_transform) + +# TODO: Make a test of the EnergyFrequencyUpdate and BlondObserver classes + + +class TestXsuiteBLonDTransforms(unittest.TestCase): + + def setUp(self): + # Accelerator parameters + C = 26658.8832 + p_s = 450e9 + h = 35640 + alpha = 0.00034849575112251314 + V = 5e6 + dphi = 0 + + # BLonD ring and RF station + self.ring = Ring(C, alpha, p_s, Proton(), n_turns=1) + self.rfstation = RFStation(self.ring, [h], [V], [dphi]) + + def testBlondToXsuiteTransform(self): + # Arrays of time and energy offsets + dts = np.linspace( + self.rfstation.t_rf[0, 0] * 1e-3, + self.rfstation.t_rf[0, 0], 10 + ) + dEs = np.linspace(-200e6, 200e6, 10) + + # Transform BLonD coordinates to Xsuite + zeta, ptau = blond_to_xsuite_transform( + dts, dEs, self.ring.beta[0, 0], self.ring.energy[0, 0], + self.rfstation.omega_rf[0, 0], self.rfstation.phi_s[0] + ) + + # Correct values + act_zeta = np.array( + [0.37325428, 0.29022578, 0.20719727, 0.12416876, 0.04114025, + -0.04188826, -0.12491676, -0.20794527, -0.29097378, -0.37400229] + ) + act_ptau = np.array( + [-4.44444444e-04, -3.45679012e-04, -2.46913580e-04, -1.48148148e-04, -4.93827160e-05, + 4.93827160e-05, 1.48148148e-04, 2.46913580e-04, 3.45679012e-04, 4.44444444e-04] + ) + + # Check if the coordinates are the same + np.testing.assert_allclose( + act_zeta, zeta, + err_msg='In testBlondToXsuiteTransform the zeta-coordinate differs' + ) + np.testing.assert_allclose( + act_ptau, ptau, + err_msg='In testBlondToXsuiteTransform the ptau-coordinate differs' + ) + + def testXsuiteToBlondTransform(self): + # Arrays of z and ptau from xsuite + zeta = np.linspace( + -0.37, 0.37, 10 + ) + ptau = np.linspace( + -4e-4, 4e-4, 10 + ) + + # Transform BLonD coordinates to Xsuite + dt, de = xsuite_to_blond_transform( + zeta, ptau, self.ring.beta[0, 0], self.ring.energy[0, 0], + self.rfstation.omega_rf[0, 0], self.rfstation.phi_s[0] + ) + + # Correct values + act_dt = np.array( + [2.48172990e-09, 2.20746549e-09, 1.93320108e-09, 1.65893668e-09, 1.38467227e-09, + 1.11040786e-09, 8.36143453e-10, 5.61879046e-10, 2.87614638e-10, 1.33502300e-11] + ) + act_de = np.array( + [-1.8e+08, -1.4e+08, -1.0e+08, -6.0e+07, -2.0e+07, + 2.0e+07, 6.0e+07, 1.0e+08, 1.4e+08, 1.8e+08] + ) + + # Check if the coordinates are the same + np.testing.assert_allclose( + act_dt, dt, + err_msg='In testXsuiteToBlondTransform the time-coordinate differs' + ) + np.testing.assert_allclose( + act_de, de, + err_msg='In testXsuiteToBlondTransform the energy-coordinate differs' + ) + + def testTransformInverse(self): + # Arrays of time and energy offsets + dts = np.linspace( + self.rfstation.t_rf[0, 0] * 1e-3, + self.rfstation.t_rf[0, 0], 20 + ) + dEs = np.linspace(-200e6, 200e6, 20) + + # Perform transforms to xsuite coordinates and back + zeta, ptau = blond_to_xsuite_transform( + dts, dEs, self.ring.beta[0, 0], self.ring.energy[0, 0], + self.rfstation.omega_rf[0, 0], self.rfstation.phi_s[0] + ) + + _dts, _dEs = xsuite_to_blond_transform( + zeta, ptau, self.ring.beta[0, 0], self.ring.energy[0, 0], + self.rfstation.omega_rf[0, 0], self.rfstation.phi_s[0] + ) + + # Check if the coordinates are the same + np.testing.assert_allclose( + dts, _dts, + err_msg='In testTransformInverse the time-coordinate differs' + ) + np.testing.assert_allclose( + dEs, _dEs, + err_msg='In testTransformInverse the energy-coordinate differs' + ) + +class TestXsuiteLHC(unittest.TestCase): + + def setUp(self): + # Accelerator parameters + self.C = 26658.8832 # Machine circumference [m] + self.p_s = 450e9 # Synchronous momentum [eV/c] + self.p_f = 450.1e9 # Synchronous momentum, final + self.h = 35640 # Harmonic number [-] + self.alpha = 0.00034849575112251314 # First order mom. comp. factor [-] + self.V = 5e6 # RF voltage [V] + self.dphi = 0 # Phase modulation/offset [rad] + + def testSingleParticle(self): + r'''Test of a single particle simulation in the LHC at constant energy''' + rtol = 4e-9 # relative tolerance + atol = 5e-9 # absolute tolerance + + # ----- Interface Simulation ----- + # Initialize the BLonD cavity + blond_tracker, beam = self.singleParticleBLonDSimulation() + cavity = BlondElement(blond_tracker, beam) + + line = self.initializeXsuiteLine() + + # Insert the BLonD elements + line.insert_element(index=0, element=cavity, name='blond_cavity') + + dt_inter, dE_inter = self.performSingleParticleInterfaceSimulation(line, blond_tracker, beam) + dt_inter = dt_inter / np.max(dt_inter) + dE_inter = dE_inter / np.max(dE_inter) + + # ----- Pure BLonD Simulation ----- + blond_tracker, beam = self.singleParticleBLonDSimulation() + dt_blond, dE_blond = self.performSingleParticleBLonDSimulation(blond_tracker, beam) + dt_blond = dt_blond / np.max(dt_blond) + dE_blond = dE_blond / np.max(dE_blond) + + # ----- Perform test ----- + np.testing.assert_allclose( + dt_inter, dt_blond, rtol=rtol, atol=atol, + err_msg='In testSingleParticle the time-coordinate differs' + ) + np.testing.assert_allclose( + dE_inter, dE_blond, rtol=rtol, atol=atol, + err_msg='In testSingleParticle the energy-coordinate differs' + ) + + def testSingleParticleRamp(self): + r'''Test of acceleration of a single particle simulation in the LHC''' + rtol = 4e-9 # relative tolerance + atol = 5e-9 # absolute tolerance + + # Initialize the BLonD cavity + blond_tracker, beam = self.singleParticleBLonDSimulation(ramp=True) + cavity = BlondElement(blond_tracker, beam) + + line = self.initializeXsuiteLine() + + # Insert the BLonD elements + line.insert_element(index=0, element=cavity, name='blond_cavity') + + # Insert energy ramp + energy_update = EnergyUpdate(blond_tracker.rf_params.momentum) + line.insert_element(index='blond_cavity', element=energy_update, name='energy_update') + + dt_inter, dE_inter = self.performSingleParticleInterfaceSimulation(line, blond_tracker, beam) + dt_inter = dt_inter / np.max(dt_inter) + dE_inter = dE_inter / np.max(dE_inter) + line.get_table().show() + + # ----- Pure BLonD Simulation ----- + blond_tracker, beam = self.singleParticleBLonDSimulation(ramp=True, pure_blond=True) + dt_blond, dE_blond = self.performSingleParticleBLonDSimulation(blond_tracker, beam) + dt_blond = dt_blond / np.max(dt_blond) + dE_blond = dE_blond / np.max(dE_blond) + + # ----- Perform test ----- + np.testing.assert_allclose( + dt_inter, dt_blond, rtol=rtol, atol=atol, + err_msg='In testSingleParticleRamp the time-coordinate differs' + ) + np.testing.assert_allclose( + dE_inter, dE_blond, rtol=rtol, atol=atol, + err_msg='In testSingleParticleRamp the energy-coordinate differs' + ) + + def testSingleParticleTwoRFStations(self): + r'''Test of a single particle simulation in the LHC at constant energy with two RF stations''' + rtol = 4e-9 # relative tolerance + atol = 5e-9 # absolute tolerance + + # Initialize the BLonD cavities + blond_tracker_1, blond_tracker_2, beam = self.singleParticleBLonDSimulation(two_rfstations=True) + cavity_1 = BlondElement(blond_tracker_1, beam) + cavity_2 = BlondElement(blond_tracker_2, beam) + + line = self.initializeXsuiteLine(two_rfstations=True) + + # Insert the BLonD elements + line.insert_element(index='matrix', element=cavity_1, name='blond_cavity_1') + line.insert_element(index='matrix_2', element=cavity_2, name='blond_cavity_2') + + dt_inter, dE_inter = self.performSingleParticleInterfaceSimulation(line, blond_tracker_1, beam) + dt_inter = dt_inter / np.max(dt_inter) + dE_inter = dE_inter / np.max(dE_inter) + + # ----- Pure BLonD Simulation ----- + blond_tracker_1, blond_tracker_2, beam = self.singleParticleBLonDSimulation(two_rfstations=True) + dt_blond, dE_blond = self.performSingleParticleBLonDSimulation(blond_tracker_1, beam, blond_tracker_2) + dt_blond = dt_blond / np.max(dt_blond) + dE_blond = dE_blond / np.max(dE_blond) + + # ----- Perform test ----- + np.testing.assert_allclose( + dt_inter, dt_blond, rtol=rtol, atol=atol, + err_msg='In testSingleParticleTwoRFStations the time-coordinate differs' + ) + np.testing.assert_allclose( + dE_inter, dE_blond, rtol=rtol, atol=atol, + err_msg='In testSingleParticleTwoRFStations the energy-coordinate differs' + ) + + def singleParticleBLonDSimulation(self, ramp: bool = False, two_rfstations: bool = False, + pure_blond: bool = False): + r''' + Method to generate a BLonD simulation in the LHC with a single particle with a time offset with respect to the + RF bucket. + ''' + # Bunch parameters + N_m = 1 # Number of macroparticles [-] + N_p = 1.15e11 # Intensity + + # Simulation parameters + N_t = 481 # Number of (tracked) turns [-] + dt_offset = 0.15 # Input particles dt [s] + v_part = 1 + + # --- BLonD objects --- + if ramp: + cycle = np.linspace(self.p_s, self.p_f, N_t + 1) + if pure_blond: + cycle = np.concatenate( + (np.array([self.p_s]), np.linspace(self.p_s, self.p_f, N_t + 1)[:-1]) + ) + ring = Ring(self.C, self.alpha, cycle, Proton(), n_turns=N_t) + else: + if two_rfstations: + # Additional input + c_part = 0.3 + v_part = 0.5 + + ring = Ring( + [self.C * c_part, self.C * (1 - c_part)], + [[self.alpha], [self.alpha]], + [self.p_s, self.p_s], + Proton(), n_turns=N_t, n_sections=2 + ) + else: + ring = Ring(self.C, self.alpha, self.p_s, Proton(), n_turns=N_t) + + beam = Beam(ring, N_m, N_p) + rfstation = RFStation(ring, [self.h], [self.V * v_part], [self.dphi]) + + # --- Insert particle --- + beam.dt = np.array([rfstation.t_rf[0, 0] * (1 - dt_offset)]) + beam.dE = np.array([0.0]) + + if two_rfstations: + second_rfstation = RFStation(ring, [self.h], [self.V * (1 - v_part)], + [self.dphi], section_index=2) + + return (RingAndRFTracker(rfstation, beam), + RingAndRFTracker(second_rfstation, beam), + beam) + + return RingAndRFTracker(rfstation, beam), beam + + def initializeXsuiteLine(self, two_rfstations: bool = False): + r''' + Method to generate an Xsuite Line using the one-turn matrix. + ''' + c_part = 1 + + if two_rfstations: + c_part = 0.3 + matrix_2 = xt.LineSegmentMap( + longitudinal_mode='nonlinear', + qx=1.1, qy=1.2, + betx=1., + bety=1., + voltage_rf=0, + frequency_rf=0, + lag_rf=0, + momentum_compaction_factor=self.alpha, + length=self.C * (1 - c_part) + ) + + matrix = xt.LineSegmentMap( + longitudinal_mode='nonlinear', + qx=1.1, qy=1.2, + betx=1., + bety=1., + voltage_rf=0, + frequency_rf=0, + lag_rf=0, + momentum_compaction_factor=self.alpha, + length=self.C * c_part + ) + + # Create line + line = xt.Line(elements=[matrix], element_names={'matrix'}) + line['matrix'].length = self.C * c_part + + if two_rfstations: + line.append_element(element=matrix_2, name='matrix_2') + line['matrix_2'].length = (1 - c_part) * self.C + + return line + + def performSingleParticleInterfaceSimulation(self, line, blond_track, beam): + r''' + Perform simulation with the interface. + ''' + # Add particles to line and build tracker + line.particle_ref = xp.Particles(p0c=self.p_s, mass0=xp.PROTON_MASS_EV, q0=1.) + line.build_tracker() + + # --- Convert the initial BLonD distribution to xsuite coordinates --- + zeta, ptau = blond_to_xsuite_transform( + beam.dt, beam.dE, line.particle_ref.beta0[0], + line.particle_ref.energy0[0], phi_s=blond_track.rf_params.phi_s[0], + omega_rf=blond_track.rf_params.omega_rf[0, 0] + ) + + # --- Track matrix --- + N_t = len(blond_track.rf_params.phi_s) - 1 + particles = line.build_particles(x=0, y=0, px=0, py=0, zeta=np.copy(zeta), ptau=np.copy(ptau)) + line.track(particles, num_turns=N_t, turn_by_turn_monitor=True, with_progress=False) + mon = line.record_last_track + + dt_array = np.zeros(N_t) + dE_array = np.zeros(N_t) + # Convert the xsuite particle coordinates back to BLonD + for i in range(N_t): + dt_array[i], dE_array[i] = xsuite_to_blond_transform( + mon.zeta[:, i].T, mon.ptau[:, i].T, blond_track.rf_params.beta[i], blond_track.rf_params.energy[i], + phi_s=blond_track.rf_params.phi_s[i] - blond_track.rf_params.phi_rf[0, 0], + omega_rf=blond_track.rf_params.omega_rf[0, i] + ) + + return dt_array, dE_array + + @staticmethod + def performSingleParticleBLonDSimulation(blond_track, beam, second_blond_tracker: RingAndRFTracker = None): + r''' + Perform simulation in pure BLonD. + ''' + # The number of turns to track + N_t = len(blond_track.rf_params.phi_s) - 1 + + # Save particle coordinates + dt_array = np.zeros(N_t) + dE_array = np.zeros(N_t) + + if second_blond_tracker is not None: + full_tracker = FullRingAndRF([blond_track, second_blond_tracker]) + else: + full_tracker = blond_track + + for i in range(N_t): + dt_array[i] = beam.dt[0] + dE_array[i] = beam.dE[0] + full_tracker.track() + + return dt_array, dE_array + + +class TestXsuitePSB(unittest.TestCase): + + def setUp(self): + # Accelerator parameters + self.C = 2 * np.pi * 25.00005794526065 # Machine circumference, radius 25 [m] + gamma_t = 4.11635447373496 # Transition gamma [-] + self.alpha = 1. / gamma_t / gamma_t # First order mom. comp. factor [-] + self.h = 1 # Harmonic number [-] + self.V = 8e3 # RF voltage [V] + self.dphi = np.pi # Phase modulation/offset [rad] + + # Derived parameters + kin_energy = 1.4e9 # Kinetic energy [eV] + E_0 = m_p * c ** 2 / e # [eV] + tot_energy = E_0 + kin_energy # [eV] + + self.p_s = np.sqrt(tot_energy ** 2 - E_0 ** 2) + self.p_f = self.p_s + 0.001e9 + + def testSingleParticle(self): + r'''Test of a single particle simulation in the PSB at constant energy''' + rtol = 4e-4 # relative tolerance + atol = 4.1e-4 # absolute tolerance + + # ----- Interface Simulation ----- + # Initialize the BLonD cavity + blond_tracker, beam = self.singleParticleBLonDSimulation() + cavity = BlondElement(blond_tracker, beam) + + line = self.initializeXsuiteLine() + + # Insert the BLonD elements + line.insert_element(index=0, element=cavity, name='blond_cavity') + + dt_inter, dE_inter = self.performSingleParticleInterfaceSimulation(line, blond_tracker, beam) + dt_inter = dt_inter / np.max(dt_inter) + dE_inter = dE_inter / np.max(dE_inter) + + # ----- Pure BLonD Simulation ----- + blond_tracker, beam = self.singleParticleBLonDSimulation() + dt_blond, dE_blond = self.performSingleParticleBLonDSimulation(blond_tracker, beam) + dt_blond = dt_blond / np.max(dt_blond) + dE_blond = dE_blond / np.max(dE_blond) + + # ----- Perform test ----- + np.testing.assert_allclose( + dt_inter, dt_blond, rtol=rtol, atol=atol, + err_msg='In testSingleParticle the time-coordinate differs' + ) + np.testing.assert_allclose( + dE_inter, dE_blond, rtol=rtol, atol=atol, + err_msg='In testSingleParticle the energy-coordinate differs' + ) + + def testSingleParticleRamp(self): + r'''Test of acceleration of a single particle simulation in the PSB''' + rtol = 4e-4 # relative tolerance + atol = 4.1e-4 # absolute tolerance + + # Initialize the BLonD cavity + blond_tracker, beam = self.singleParticleBLonDSimulation(ramp=True) + cavity = BlondElement(blond_tracker, beam) + + line = self.initializeXsuiteLine() + + # Insert the BLonD elements + line.insert_element(index=0, element=cavity, name='blond_cavity') + + # Insert energy ramp + energy_update = EnergyUpdate(blond_tracker.rf_params.momentum) + line.insert_element(index='matrix', element=energy_update, name='energy_update') + + dt_inter, dE_inter = self.performSingleParticleInterfaceSimulation(line, blond_tracker, beam) + dt_inter = dt_inter / np.max(dt_inter) + dE_inter = dE_inter / np.max(dE_inter) + + # ----- Pure BLonD Simulation ----- + blond_tracker, beam = self.singleParticleBLonDSimulation(ramp=True, pure_blond=True) + dt_blond, dE_blond = self.performSingleParticleBLonDSimulation(blond_tracker, beam) + dt_blond = dt_blond / np.max(dt_blond) + dE_blond = dE_blond / np.max(dE_blond) + + # ----- Perform test ----- + np.testing.assert_allclose( + dt_inter, dt_blond, rtol=rtol, atol=atol, + err_msg='In testSingleParticleRamp the time-coordinate differs' + ) + np.testing.assert_allclose( + dE_inter, dE_blond, rtol=rtol, atol=atol, + err_msg='In testSingleParticleRamp the energy-coordinate differs' + ) + + def singleParticleBLonDSimulation(self, ramp: bool = False, pure_blond: bool = False): + r''' + Method to generate a BLonD simulation in the LHC with a single particle with a time offset with respect to the + RF bucket. + ''' + # Bunch parameters + N_m = 1 # Number of macroparticles [-] + N_p = 1e11 # Intensity + + # Simulation parameters + N_t = 6000 # Number of (tracked) turns [-] + dt_offset = 0.15 # Input particles dt [s] + + # --- BLonD objects --- + if ramp: + cycle = np.linspace(self.p_s, self.p_f, N_t + 1) + if pure_blond: + cycle = np.concatenate( + (np.array([self.p_s]), np.linspace(self.p_s, self.p_f, N_t + 1)[:-1]) + ) + ring = Ring(self.C, self.alpha, cycle, Proton(), n_turns=N_t) + else: + ring = Ring(self.C, self.alpha, self.p_s, Proton(), n_turns=N_t) + + beam = Beam(ring, N_m, N_p) + rfstation = RFStation(ring, [self.h], [self.V], [self.dphi]) + + # --- Insert particle --- + beam.dt = np.array([rfstation.t_rf[0, 0] * (1 - dt_offset)]) + beam.dE = np.array([0.0]) + + return RingAndRFTracker(rfstation, beam), beam + + def initializeXsuiteLine(self): + r''' + Method to generate an Xsuite Line using the one-turn matrix. + ''' + matrix = xt.LineSegmentMap( + longitudinal_mode='nonlinear', + qx=1.1, qy=1.2, + betx=1., + bety=1., + voltage_rf=0, + frequency_rf=0, + lag_rf=0, + momentum_compaction_factor=self.alpha, + length=self.C + ) + + # Create line + line = xt.Line(elements=[matrix], element_names={'matrix'}) + line['matrix'].length = self.C + + return line + + def performSingleParticleInterfaceSimulation(self, line, blond_track, beam): + r''' + Perform simulation with the interface. + ''' + # Add particles to line and build tracker + line.particle_ref = xp.Particles(p0c=self.p_s, mass0=xp.PROTON_MASS_EV, q0=1.) + line.build_tracker() + + # --- Convert the initial BLonD distribution to xsuite coordinates --- + zeta, ptau = blond_to_xsuite_transform( + beam.dt, beam.dE, line.particle_ref.beta0[0], line.particle_ref.energy0[0], + phi_s=blond_track.rf_params.phi_s[0] - blond_track.rf_params.phi_rf[0, 0], + omega_rf=blond_track.rf_params.omega_rf[0, 0] + ) + + # --- Track matrix --- + N_t = len(blond_track.rf_params.phi_s) - 1 + particles = line.build_particles(x=0, y=0, px=0, py=0, zeta=np.copy(zeta), ptau=np.copy(ptau)) + line.track(particles, num_turns=N_t, turn_by_turn_monitor=True, with_progress=False) + mon = line.record_last_track + + dt_array = np.zeros(N_t) + dE_array = np.zeros(N_t) + # Convert the xsuite particle coordinates back to BLonD + for i in range(N_t): + dt_array[i], dE_array[i] = xsuite_to_blond_transform( + mon.zeta[:, i].T, mon.ptau[:, i].T, blond_track.rf_params.beta[i], blond_track.rf_params.energy[i], + phi_s=blond_track.rf_params.phi_s[i] - blond_track.rf_params.phi_rf[0, 0], + omega_rf=blond_track.rf_params.omega_rf[0, i] + ) + + return dt_array, dE_array + + @staticmethod + def performSingleParticleBLonDSimulation(blond_track, beam): + r''' + Perform simulation in pure BLonD. + ''' + # The number of turns to track + N_t = len(blond_track.rf_params.phi_s) - 1 + + # Save particle coordinates + dt_array = np.zeros(N_t) + dE_array = np.zeros(N_t) + + full_tracker = blond_track + + for i in range(N_t): + dt_array[i] = beam.dt[0] + dE_array[i] = beam.dE[0] + full_tracker.track() + + return dt_array, dE_array \ No newline at end of file