def generate_6D_Gaussian_bunch_matched( self, n_macroparticles, intensity, epsn_x, epsn_y, sigma_z=None, epsn_z=None): '''Generate a 6D Gaussian distribution of particles which is transversely as well as longitudinally matched. The distribution is found iteratively to exactly yield the given bunch length while at the same time being stationary in the non-linear bucket. Thus, the bunch length should amount to the one specificed and should not change significantly during the synchrotron motion. Requires self.longitudinal_mode == 'non-linear' for the bucket. ''' assert self.longitudinal_mode == 'non-linear' epsx_geo = epsn_x/self.betagamma epsy_geo = epsn_y/self.betagamma injection_optics = self.transverse_map.get_injection_optics() bunch = gen.ParticleGenerator(macroparticlenumber=n_macroparticles, intensity=intensity, charge=self.charge, mass=self.mass, circumference=self.circumference, gamma=self.gamma, distribution_x = gen.gaussian2D(epsx_geo), alpha_x=injection_optics['alpha_x'], beta_x=injection_optics['beta_x'], D_x=injection_optics['D_x'], distribution_y = gen.gaussian2D(epsy_geo), alpha_y=injection_optics['alpha_y'], beta_y=injection_optics['beta_y'], D_y=injection_optics['D_y'], distribution_z = gen.RF_bucket_distribution(self.longitudinal_map.get_bucket(gamma=self.gamma), sigma_z=sigma_z, epsn_z=epsn_z), ).generate() return bunch
def generate_6D_Gaussian_bunch_matched( self, n_macroparticles, intensity, epsn_x, epsn_y, sigma_z=None, epsn_z=None, margin=0): '''Generate a 6D Gaussian distribution of particles which is transversely as well as longitudinally matched. The distribution is found iteratively to exactly yield the given bunch length while at the same time being stationary in the non-linear bucket. Thus, the bunch length should amount to the one specificed and should not change significantly during the synchrotron motion. Requires self.longitudinal_focusing == 'non-linear' for the bucket. ''' assert self.longitudinal_focusing == 'non-linear' epsx_geo = epsn_x/self.betagamma epsy_geo = epsn_y/self.betagamma bunch = gen.ParticleGenerator(macroparticlenumber=n_macroparticles, intensity=intensity, charge=self.charge, mass=self.mass, circumference=self.circumference, gamma=self.gamma, distribution_x=gen.gaussian2D(epsx_geo), alpha_x=self.alpha_x[0], beta_x=self.beta_x[0], D_x=self.D_x[0], distribution_y=gen.gaussian2D(epsy_geo), alpha_y=self.alpha_y[0], beta_y=self.beta_y[0], D_y=self.D_y[0], distribution_z=gen.RF_bucket_distribution( rfbucket=self.longitudinal_map.get_bucket(gamma=self.gamma), sigma_z=sigma_z, epsn_z=epsn_z, margin=margin, printer=self._printer) ).generate() return bunch
def generate_6D_Gaussian_bunch(self, n_macroparticles, intensity, epsn_x, epsn_y, sigma_z): '''Generate a 6D Gaussian distribution of particles which is transversely matched to the Synchrotron. Longitudinally, the distribution is matched only in terms of linear focusing. For a non-linear bucket, the Gaussian distribution is cut along the separatrix (with some margin). It will gradually filament into the bucket. This will change the specified bunch length. ''' if self.longitudinal_mode == 'linear': check_inside_bucket = lambda z,dp : np.array(len(z)*[True]) elif self.longitudinal_mode == 'non-linear': check_inside_bucket = self.longitudinal_map.get_bucket( gamma=self.gamma).make_is_accepted(margin=0.05) else: raise NotImplementedError( 'Something wrong with self.longitudinal_mode') eta = self.longitudinal_map.alpha_array[0] - self.gamma**-2 beta_z = np.abs(eta)*self.circumference/2./np.pi/self.longitudinal_map.Qs sigma_dp = sigma_z/beta_z epsx_geo = epsn_x/self.betagamma epsy_geo = epsn_y/self.betagamma injection_optics = self.transverse_map.get_injection_optics() bunch = gen.ParticleGenerator(macroparticlenumber=n_macroparticles, intensity=intensity, charge=self.charge, mass=self.mass, circumference=self.circumference, gamma=self.gamma, distribution_x = gen.gaussian2D(epsx_geo), alpha_x=injection_optics['alpha_x'], beta_x=injection_optics['beta_x'], D_x=injection_optics['D_x'], distribution_y = gen.gaussian2D(epsy_geo), alpha_y=injection_optics['alpha_y'], beta_y=injection_optics['beta_y'], D_y=injection_optics['D_y'], distribution_z = gen.cut_distribution(gen.gaussian2D_asymmetrical(sigma_u=sigma_z, sigma_up=sigma_dp),is_accepted=check_inside_bucket), ).generate() return bunch
def generate_6D_Gaussian_bunch( self, n_macroparticles, intensity, epsn_x, epsn_y, sigma_z ): """Generate a 6D Gaussian distribution of particles which is transversely matched to the Synchrotron. Longitudinally, the distribution is matched only in terms of linear focusing. For a non-linear bucket, the Gaussian distribution is cut along the separatrix (with some margin). It will gradually filament into the bucket. This will change the specified bunch length. """ if self.longitudinal_mode == "linear": check_inside_bucket = lambda z, dp: np.array(len(z) * [True]) Q_s = self.longitudinal_map.Q_s elif self.longitudinal_mode == "non-linear": bucket = self.longitudinal_map.get_bucket( gamma=self.gamma, mass=self.mass, charge=self.charge ) check_inside_bucket = bucket.make_is_accepted(margin=0.05) Q_s = bucket.Q_s else: raise NotImplementedError("Something wrong with self.longitudinal_mode") eta = self.longitudinal_map.alpha_array[0] - self.gamma ** -2 beta_z = np.abs(eta) * self.circumference / 2.0 / np.pi / Q_s sigma_dp = sigma_z / beta_z epsx_geo = epsn_x / self.betagamma epsy_geo = epsn_y / self.betagamma injection_optics = self.transverse_map.get_injection_optics() bunch = generators.ParticleGenerator( macroparticlenumber=n_macroparticles, intensity=intensity, charge=self.charge, mass=self.mass, circumference=self.circumference, gamma=self.gamma, distribution_x=generators.gaussian2D(epsx_geo), alpha_x=injection_optics["alpha_x"], beta_x=injection_optics["beta_x"], D_x=injection_optics["D_x"], distribution_y=generators.gaussian2D(epsy_geo), alpha_y=injection_optics["alpha_y"], beta_y=injection_optics["beta_y"], D_y=injection_optics["D_y"], distribution_z=generators.cut_distribution( generators.gaussian2D_asymmetrical(sigma_u=sigma_z, sigma_up=sigma_dp), is_accepted=check_inside_bucket, ), ).generate() return bunch
def generate_6D_Gaussian_bunch_matched( self, n_macroparticles, intensity, epsn_x, epsn_y, sigma_z=None, epsn_z=None ): """Generate a 6D Gaussian distribution of particles which is transversely as well as longitudinally matched. The distribution is found iteratively to exactly yield the given bunch length while at the same time being stationary in the non-linear bucket. Thus, the bunch length should amount to the one specificed and should not change significantly during the synchrotron motion. Requires self.longitudinal_mode == 'non-linear' for the bucket. """ if self.longitudinal_mode == 'linear': assert(sigma_z is not None) bunch = self.generate_6D_Gaussian_bunch(n_macroparticles, intensity, epsn_x, epsn_y, sigma_z) elif self.longitudinal_mode == "non-linear": epsx_geo = epsn_x / self.betagamma epsy_geo = epsn_y / self.betagamma injection_optics = self.transverse_map.get_injection_optics() bunch = generators.ParticleGenerator( macroparticlenumber=n_macroparticles, intensity=intensity, charge=self.charge, mass=self.mass, circumference=self.circumference, gamma=self.gamma, distribution_x=generators.gaussian2D(epsx_geo), alpha_x=injection_optics["alpha_x"], beta_x=injection_optics["beta_x"], D_x=injection_optics["D_x"], distribution_y=generators.gaussian2D(epsy_geo), alpha_y=injection_optics["alpha_y"], beta_y=injection_optics["beta_y"], D_y=injection_optics["D_y"], distribution_z=generators.RF_bucket_distribution( self.longitudinal_map.get_bucket(gamma=self.gamma), sigma_z=sigma_z, epsn_z=epsn_z, ), ).generate() else: raise ValueError('Unknown longitudinal mode!') return bunch
def setUp(self): np.random.seed(0) self.nparticles = 1000 self.epsx = 0.5 self.intensity = 1e11 self.charge = constants.e self.mass = constants.m_p self.circumference = 99 self.gamma = 27.1 self.generator = gf.ParticleGenerator( self.nparticles, self.intensity, self.charge, self.mass, self.circumference, self.gamma, distribution_x=gf.gaussian2D(0.5),alpha_x=-0.7, beta_x=4, D_x=0, distribution_z=gf.gaussian2D(3.0), printer=SilentPrinter()) self.beam = self.generator.generate()
def test_cut_bucket_distribution(self): '''Tests functionality of the cut-bucket matchor ''' nparticles = 100 h1 = 4620 h2 = 4*4620 V1 = 10e6 V2 = 1e6 dphi1 = 0 dphi2 = 0 alpha = 0.00308 p_increment = 0 long_map = RFSystems(self.circumference, [h1, h2], [V1, V2], [dphi1, dphi2], [alpha], self.gamma, p_increment, charge=self.charge, mass=self.mass) bucket = long_map.get_bucket(gamma=self.gamma) is_accepted_fn = bucket.make_is_accepted(margin=0.) bunch = gf.ParticleGenerator( nparticles, 11, constants.e, constants.m_p, self.circumference, self.gamma, distribution_z=gf.cut_distribution( is_accepted=is_accepted_fn, distribution=gf.gaussian2D(0.01))).generate() self.assertEqual(nparticles, len(bunch.z), 'bucket_cut_distribution loses particles') self.assertTrue(np.sum(is_accepted_fn(bunch.z, bunch.dp)) == nparticles, 'not all particles generated with the cut RF matcher' + ' lie inside the specified separatrix')
def generate_6D_Gaussian_bunch(self, n_macroparticles, intensity, epsn_x, epsn_y, sigma_z): '''Generate a 6D Gaussian distribution of particles which is transversely matched to the Synchrotron. Longitudinally, the distribution is matched only in terms of linear focusing. For a non-linear bucket, the Gaussian distribution is cut along the separatrix (with some margin). It will gradually filament into the bucket. This will change the specified bunch length. ''' if self.longitudinal_focusing == 'linear': check_inside_bucket = lambda z, dp: np.array(len(z) * [True]) elif self.longitudinal_focusing == 'non-linear': check_inside_bucket = self.longitudinal_map.get_bucket( gamma=self.gamma).make_is_accepted(margin=0.05) else: raise NotImplementedError( 'Something wrong with self.longitudinal_focusing') beta_z = np.abs(self.eta) * self.circumference / 2. / np.pi / self.Q_s sigma_dp = sigma_z / beta_z epsx_geo = epsn_x / self.betagamma epsy_geo = epsn_y / self.betagamma bunch = gen.ParticleGenerator( macroparticlenumber=n_macroparticles, intensity=intensity, charge=self.charge, mass=self.mass, circumference=self.circumference, gamma=self.gamma, distribution_x=gen.gaussian2D(epsx_geo), alpha_x=self.alpha_x[0], beta_x=self.beta_x[0], D_x=self.D_x[0], distribution_y=gen.gaussian2D(epsy_geo), alpha_y=self.alpha_y[0], beta_y=self.beta_y[0], D_y=self.D_y[0], distribution_z=gen.cut_distribution( gen.gaussian2D_asymmetrical(sigma_u=sigma_z, sigma_up=sigma_dp), is_accepted=check_inside_bucket)).generate() return bunch
def generate_6D_Gaussian_bunch_matched_parabolic(self, n_macroparticles, intensity, epsn_x, epsn_y, sigma_z=None, epsn_z=None, margin=0): ''' Modified version to use parabolic longitudinal profile instead of the gaussian one Added 06.04.2018 ''' assert self.longitudinal_focusing == 'non-linear' epsx_geo = epsn_x / self.betagamma epsy_geo = epsn_y / self.betagamma bunch = gen.ParticleGenerator( macroparticlenumber=n_macroparticles, intensity=intensity, charge=self.charge, mass=self.mass, circumference=self.circumference, gamma=self.gamma, distribution_x=gen.gaussian2D(epsx_geo), alpha_x=self.alpha_x[0], beta_x=self.beta_x[0], D_x=self.D_x[0], distribution_y=gen.gaussian2D(epsy_geo), alpha_y=self.alpha_y[0], beta_y=self.beta_y[0], D_y=self.D_y[0], distribution_z=gen.RF_bucket_distribution( rfbucket=self.longitudinal_map.get_bucket(gamma=self.gamma), distribution_type=ParabolicDistribution, sigma_z=sigma_z, epsn_z=epsn_z, margin=margin, printer=self._printer)).generate() return bunch
def test_distributions(self): '''Tests whether the specified distributions return the coords in the correct format (dimensions). If new distributions are added, add them to the test here! ''' # Gaussian dist = gf.gaussian2D(0.1) self.distribution_testing_implementation(dist) # Uniform dist = gf.uniform2D(-2., 3.) self.distribution_testing_implementation(dist)
def test_update_beam_with_new_coords(self): '''Tests whether adding new coordinates to the beam works as expected ''' x_copy = self.beam.x.copy() longitudinal_generator = gf.ParticleGenerator( self.nparticles, self.intensity, self.charge, self.mass, self.circumference, self.gamma, distribution_z=gf.gaussian2D(3.0)) longitudinal_generator.update(self.beam) self.assertEqual(self.beam.dp.size, self.nparticles, 'Updating the beam with new coordinates leads to' + 'faulty coordinates') for n in range(self.nparticles): self.assertAlmostEqual(x_copy[n], self.beam.x[n], msg='Updating the beam with new coordinates invalidates' + 'existing coordinates')
def prepareDis(self, twiss, closed_orbit): if closed_orbit is not None: x_co = twiss[0]['x'] y_co = twiss[0]['y'] else: x_co = 0 y_co = 0 np.random.seed(0) D_x_0 = twiss[0]['dx'] * self.beta D_y_0 = twiss[0]['dy'] * self.beta Dp_x_0 = twiss[0]['dpx'] * self.beta Dp_y_0 = twiss[0]['dpy'] * self.beta bx_0 = twiss[0]['betx'] by_0 = twiss[0]['bety'] s0 = twiss[-1]['s'] circumference = s0 alfx_0 = twiss[0]['alfx'] alfy_0 = twiss[0]['alfy'] pyht_beam = generators.generate_Gaussian6DTwiss( self.npart, 1, self.charge, self.mass, s0, self.gamma, alfx_0, alfy_0, bx_0, by_0, 1, self.epsn_x, self.epsn_y, 1, dispersion_x=None, dispersion_y=None, limit_n_rms_x=self.limit_n_rms_x**2, limit_n_rms_y=self.limit_n_rms_y**2, limit_n_rms_z=self.limit_n_rms_z**2, ) distribution_z_uncut = generators.gaussian2D(self.sig_z**2) is_accepted = generators.make_is_accepted_within_n_sigma( epsn_rms=self.sig_z, limit_n_rms=2.5, ) distribution_z_cut = generators.cut_distribution( distribution_z_uncut, is_accepted) z, dp = distribution_z_cut(self.npart) pyht_beam.z, pyht_beam.dp = z, dp / self.beta_z # recentre on 0 to avoid dipolar motion: pyht_beam.x -= pyht_beam.mean_x() pyht_beam.xp -= pyht_beam.mean_xp() pyht_beam.y -= pyht_beam.mean_y() pyht_beam.yp -= pyht_beam.mean_yp() pyht_beam.z -= pyht_beam.mean_z() pyht_beam.dp -= pyht_beam.mean_dp() # PyHT generates around 0, need to offset with closed orbit: pyht_beam.x += x_co pyht_beam.y += y_co # add dispersive contribution to coordinates: pyht_beam.x += D_x_0 * pyht_beam.dp pyht_beam.y += D_y_0 * pyht_beam.dp # also need to add D'_{x,y} to momenta: pyht_beam.xp += Dp_x_0 * pyht_beam.dp pyht_beam.yp += Dp_y_0 * pyht_beam.dp return pyht_beam
def characterize_impedances(wake_dipolar_element, wake_quadrupolar_element, n_samples_hh_kk, test_amplitude, intensity, sigma_z, circumference, particle_charge, particle_mass, particle_gamma, z_cut, n_tail_cut, detuning_fit_order): # Build response matrix for dipolar impedance slicer_for_harmonicresponse = UniformBinSlicer(n_samples_hh_kk, z_cuts=(-z_cut, z_cut)) # Make a test bunch (I need only longitudinal profile and energy to be correct) bunch = generators.ParticleGenerator( macroparticlenumber=n_samples_hh_kk * 1000, intensity=intensity, charge=particle_charge, mass=particle_mass, circumference=circumference, gamma=particle_gamma, distribution_x=generators.gaussian2D(1e-9), # Dummy not really used alpha_x=0, beta_x=100, D_x=0., distribution_y=generators.gaussian2D(1e-9), alpha_y=0., beta_y=100., D_y=0., distribution_z=generators.cut_distribution( generators.gaussian2D_asymmetrical(sigma_u=sigma_z, sigma_up=1e-4), is_accepted=(lambda z, dp: np.array(len(z) * [True]))), ).generate() bunch.x *= 0 bunch.xp *= 0 bunch.y *= 0 bunch.yp *= 0 # Generate configurations assert (n_samples_hh_kk % 2 == 0) cos_ampl_list = [] sin_ampl_list = [] n_osc_list = [] for ii in range(n_samples_hh_kk // 2): cos_ampl_list.append(test_amplitude) sin_ampl_list.append(0.) n_osc_list.append(ii) cos_ampl_list.append(0.) sin_ampl_list.append(test_amplitude) n_osc_list.append(ii + 1) # cos_ampl_list = [100*1e-4] # sin_ampl_list = [0] # n_osc_list = [3] # Measure responses x_meas_mat = [] x_mat = [] dpx_mat = [] y_meas_mat = [] y_mat = [] dpy_mat = [] for itest in range(len(cos_ampl_list)): N_oscillations = n_osc_list[itest] sin_amplitude = sin_ampl_list[itest] cos_amplitude = cos_ampl_list[itest] # Recenter all slices slices_set = bunch.get_slices(slicer_for_harmonicresponse, statistics=True) for ii in range(slices_set.n_slices): ix = slices_set.particle_indices_of_slice(ii) if len(ix) > 0: bunch.x[ix] -= np.mean(bunch.x[ix]) bunch.xp[ix] -= np.mean(bunch.xp[ix]) bunch.y[ix] -= np.mean(bunch.y[ix]) bunch.yp[ix] -= np.mean(bunch.yp[ix]) # Get slice centers z_slices = slices_set.z_centers N_slices = len(z_slices) # Get z_step beween slices and define z_range z_step = z_slices[1] - z_slices[0] z_range = z_slices[-1] - z_slices[0] + z_step # Last term is to make # sinusoids numerically # orthogonal # Generate ideal sinusoidal distortion x_y_ideal = (sin_amplitude * np.sin(2 * np.pi * N_oscillations * z_slices / z_range) + cos_amplitude * np.cos(2 * np.pi * N_oscillations * z_slices / z_range)) # Add sinusoidal distortion to particles bunch.x += sin_amplitude * np.sin( 2 * np.pi * N_oscillations * bunch.z / z_range) bunch.x += cos_amplitude * np.cos( 2 * np.pi * N_oscillations * bunch.z / z_range) bunch.y += sin_amplitude * np.sin( 2 * np.pi * N_oscillations * bunch.z / z_range) bunch.y += cos_amplitude * np.cos( 2 * np.pi * N_oscillations * bunch.z / z_range) # Measure bunch.clean_slices() slices_set = bunch.get_slices(slicer_for_harmonicresponse, statistics=True) x_slices = slices_set.mean_x y_slices = slices_set.mean_y int_slices = slices_set.lambda_bins() / qe bunch.clean_slices() # Apply impedance wake_dipolar_element.track(bunch) # Measure kicks bunch.clean_slices() slices_set = bunch.get_slices(slicer_for_harmonicresponse, statistics=True) dpx_slices = slices_set.mean_xp dpy_slices = slices_set.mean_yp # Store results x_mat.append(x_y_ideal.copy()) x_meas_mat.append(x_slices.copy()) dpx_mat.append(dpx_slices.copy()) y_mat.append(x_y_ideal.copy()) y_meas_mat.append(y_slices.copy()) dpy_mat.append(dpy_slices.copy()) x_mat = np.array(x_mat) x_meas_mat = np.array(x_meas_mat) dpx_mat = np.array(dpx_mat) y_mat = np.array(y_mat) y_meas_mat = np.array(y_meas_mat) dpy_mat = np.array(dpy_mat) HH_x = x_mat KK_x = dpx_mat HH_y = y_mat KK_y = dpy_mat if n_tail_cut > 0: KK_x[:, :n_tail_cut] = 0. KK_x[:, -n_tail_cut:] = 0. KK_y[:, :n_tail_cut] = 0. KK_y[:, -n_tail_cut:] = 0. ############################# # Detuning characterization # ############################# if wake_quadrupolar_element is not None: bunch.x *= 0 bunch.xp *= 0 bunch.y *= 0 bunch.yp *= 0 bunch.x += test_amplitude bunch.y += test_amplitude bunch.clean_slices() wake_quadrupolar_element.track(bunch) bunch.clean_slices() slices_set = bunch.get_slices(slicer_for_harmonicresponse, statistics=True) dpx_slices = slices_set.mean_xp dpy_slices = slices_set.mean_yp k_quadx = slices_set.mean_xp / test_amplitude k_quady = slices_set.mean_yp / test_amplitude px = np.polyfit(z_slices, k_quadx, deg=detuning_fit_order) py = np.polyfit(z_slices, k_quady, deg=detuning_fit_order) alpha_Nx = px[::-1] alpha_Ny = py[::-1] wake_characterization = { 'HH_x': HH_x, 'KK_x': KK_x, 'HH_y': HH_y, 'KK_y': KK_y, 'z_slices': z_slices, 'alpha_Nx': alpha_Nx, 'alpha_Ny': alpha_Ny } return wake_characterization