예제 #1
0
 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
예제 #2
0
    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
예제 #3
0
 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
예제 #4
0
    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
예제 #5
0
    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
예제 #6
0
 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()
예제 #7
0
 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')
예제 #8
0
    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
예제 #9
0
    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
예제 #10
0
    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)
예제 #11
0
 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')
예제 #12
0
    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