Exemplo n.º 1
0
    def reprocess(self, new_slicing):
        '''
        *Reprocess the impedance contributions with respect to the new_slicing.*
        '''

        self.slices = new_slicing

        time_resolution = (self.slices.bin_centers[1] -
                           self.slices.bin_centers[0])

        if self.frequency_resolution_input == None:
            self.n_fft_sampling = self.slices.n_slices
        else:
            if self.freq_res_option is 'round':
                self.n_fft_sampling = next_regular(
                    int(
                        np.round(1 / (self.frequency_resolution_input *
                                      time_resolution))))
            elif self.freq_res_option is 'ceil':
                self.n_fft_sampling = next_regular(
                    int(
                        np.ceil(1 / (self.frequency_resolution_input *
                                     time_resolution))))
            elif self.freq_res_option is 'floor':
                self.n_fft_sampling = next_regular(
                    int(
                        np.floor(1 / (self.frequency_resolution_input *
                                      time_resolution))))
            else:
                raise RuntimeError(
                    'The input freq_res_option is not recognized')

            if self.n_fft_sampling < self.slices.n_slices:
                print(
                    'The input frequency resolution step is too big, and the whole \
                       bunch is not sliced... The number of sampling points for the \
                       FFT is corrected in order to sample the whole bunch (and \
                       you might consider changing the input in order to have \
                       a finer resolution).')
                self.n_fft_sampling = next_regular(self.slices.n_slices)

        #: *Real frequency resolution in [Hz], according to the obtained n_fft_sampling.*
        self.frequency_resolution = 1 / (self.n_fft_sampling * time_resolution)

        self.slices.beam_spectrum_generation(self.n_fft_sampling,
                                             only_rfft=True)
        #: *Frequency array of the impedance in [Hz]*
        self.frequency_array = self.slices.beam_spectrum_freq

        #: *Total impedance array of all sources in* [:math:`\Omega`]
        self.total_impedance = 0
        self.sum_impedances(self.frequency_array)
Exemplo n.º 2
0
    def reprocess(self, new_slicing):
        '''
        *Reprocess the wake contributions with respect to the new_slicing.*
        '''

        self.slices = new_slicing

        self.time_array = self.slices.bin_centers - self.slices.bin_centers[0]
        self.sum_wakes(self.time_array)

        self.cut = len(self.time_array) + len(self.slices.n_macroparticles) - 1
        self.fshape = next_regular(self.cut)
Exemplo n.º 3
0
    def process(self):
        '''
        *Reprocess the impedance contributions. To be run when slices changes*
        '''

        _InducedVoltage.process(self)

        # Number of points for the FFT, equal to the length of the induced
        # voltage array + number of slices -1 to calculate a linear convolution
        # in the frequency domain. The next regular number is used for speed,
        # therefore the frequency resolution is always equal or finer than
        # the input value
        self.n_fft = next_regular(
            int(self.n_induced_voltage) + int(self.slices.n_slices) - 1)

        #: *Time array of the wake in s*
        self.time = np.arange(0, self.wake_length,
                              self.wake_length / self.n_induced_voltage)

        # Processing the wakes
        self.sum_wakes(self.time)
Exemplo n.º 4
0
    def __init__(self,
                 Slices,
                 wake_source_list,
                 n_turns_memory=0,
                 time_or_freq='freq',
                 main_harmonic_number=1):

        #: *Copy of the Slices object in order to access the profile info.*
        self.slices = Slices

        #: *Wake sources inputed as a list (eg: list of BBResonators objects)*
        self.wake_source_list = wake_source_list

        #: *Time array of the wake in [s]*
        self.time_array = 0

        #: *Total wake array of all sources in* [:math:`\Omega / s`]
        self.total_wake = 0

        #: *Induced voltage from the sum of the wake sources in [V]*
        self.induced_voltage = 0

        # Pre-processing the wakes
        self.time_array = self.slices.bin_centers - self.slices.bin_centers[0]
        self.sum_wakes(self.time_array)

        self.cut = len(self.time_array) + len(self.slices.n_macroparticles) - 1
        self.fshape = next_regular(self.cut)

        self.time_or_freq = time_or_freq

        if n_turns_memory > 0:
            self.main_harmonic_number = main_harmonic_number
            delta_t = self.slices.bin_centers[1] - self.slices.bin_centers[0]
            self.n_points_wake = self.slices.n_slices * main_harmonic_number * (
                n_turns_memory + 1)
            self.time_array_wake_extended = np.linspace(
                0, delta_t * (self.n_points_wake - 1), self.n_points_wake)
            self.sum_wakes(self.time_array_wake_extended)
            self.time_array_memory = self.time_array_wake_extended + delta_t / 2
Exemplo n.º 5
0
    def process(self):
        '''
        *Reprocess the impedance contributions. To be run when slices changes*
        '''

        _InducedVoltage.process(self)

        # Number of points for the FFT. The next regular number is used for
        # speed, therefore the frequency resolution is always equal or finer
        # than the input value
        self.n_fft = next_regular(self.n_induced_voltage)

        self.slices.beam_spectrum_freq_generation(self.n_fft)

        #: *Frequency array of the impedance in Hz*
        self.freq = self.slices.beam_spectrum_freq

        # Length of the front wake in frequency domain calculations
        if self.front_wake_length:
            self.front_wake_buffer = int(
                np.ceil(np.max(self.front_wake_length) / self.slices.bin_size))

        # Processing the impedances
        self.sum_impedances(self.freq)
Exemplo n.º 6
0
    def generate(self):
       
        for i in range(0, int(self.n_turns/self.corr)):
        
            # Scale amplitude to keep area (phase noise amplitude) constant
            k = i*self.corr       # current time step
            ampl = self.A_i*self.fs[0]/self.fs[k]
            
            # Calculate the frequency step
            f_max = self.f0[k]/2
            n_points_pos_f_incl_zero = np.ceil(f_max/self.delta_f) + 1
            nt = 2*(n_points_pos_f_incl_zero - 1)
            nt_regular = next_regular(int(nt))
            if nt_regular%2!=0 or nt_regular < self.corr:
                raise RuntimeError('Error in noise generation!')
            n_points_pos_f_incl_zero = nt_regular/2 + 1  
            freq = np.linspace(0, f_max, n_points_pos_f_incl_zero)
            delta_f = f_max/(n_points_pos_f_incl_zero-1) 

            # Construct spectrum   
            nmin = np.floor(self.fmin_s0*self.fs[k]/delta_f)  
            nmax = np.ceil(self.fmax_s0*self.fs[k]/delta_f)    
            
            # To compensate the notch due to PL at central frequency
            if self.predistortion == 'exponential':
                
                spectrum = np.concatenate((np.zeros(nmin), ampl*np.exp(
                    np.log(100.)*np.arange(0,nmax-nmin+1)/(nmax-nmin) ), 
                                           np.zeros(n_points_pos_f_incl_zero-nmax-1) ))
             
            elif self.predistortion == 'linear':
                
                spectrum = np.concatenate((np.zeros(nmin), 
                    np.linspace(0, ampl, nmax-nmin+1), np.zeros(n_points_pos_f_incl_zero-nmax-1)))   
                
            elif self.predistortion == 'hyperbolic':

                spectrum = np.concatenate((np.zeros(nmin), 
                    ampl*np.ones(nmax-nmin+1)* \
                    1/(1 + 0.99*(nmin - np.arange(nmin,nmax+1))
                       /(nmax-nmin)), np.zeros(n_points_pos_f_incl_zero-nmax-1) ))

            elif self.predistortion == 'weightfunction':

                frel = freq[nmin:nmax+1]/self.fs[k] # frequency relative to fs0
                frel[np.where(frel > 0.999)[0]] = 0.999 # truncate center freqs
                sigma = 0.754 # rms bunch length in rad corresponding to 1.2 ns
                gamma = 0.577216
                weight = (4.*np.pi*frel/sigma**2)**2 * \
                    np.exp(-16.*(1. - frel)/sigma**2) + \
                    0.25*( 1 + 8.*frel/sigma**2 * 
                           np.exp(-8.*(1. - frel)/sigma**2) * 
                           ( gamma + np.log(8.*(1. - frel)/sigma**2) + 
                             8.*(1. - frel)/sigma**2 ) )**2
                weight /= weight[0] # normalise to have 1 at fmin
                spectrum = np.concatenate((np.zeros(nmin), ampl*weight, 
                                            np.zeros(n_points_pos_f_incl_zero-nmax-1)))

            else:
    
                spectrum = np.concatenate((np.zeros(nmin), 
                    ampl*np.ones(nmax-nmin+1), np.zeros(n_points_pos_f_incl_zero-nmax-1)))               
            
            
            # Fill phase noise array
            if i < int(self.n_turns/self.corr) - 1:
                kmax = (i + 1)*self.corr
            else:
                kmax = self.n_turns + 1
            
            
            self.spectrum_to_phase_noise(freq, spectrum)
            self.seed1 +=239
            self.seed2 +=158
            self.dphi[k:kmax] = self.dphi_output[0:(kmax-k)]
            
            if self.continuous_phase:
                if i==0:
                    self.spectrum_to_phase_noise(freq, spectrum)
                    self.seed1 +=239
                    self.seed2 +=158
                    self.dphi2[:self.corr/4] = self.dphi_output[:self.corr/4]
                    
                self.spectrum_to_phase_noise(freq, spectrum)
                self.seed1 +=239
                self.seed2 +=158
                self.dphi2[(k+self.corr/4):(kmax+self.corr/4)] = self.dphi_output[0:(kmax-k)]
            
            if self.folder_plots != None:
                fig_folder(self.folder_plots)
                plot_noise_spectrum(freq, spectrum, sampling=1, figno=i, 
                                    dirname = self.folder_plots)
                plot_phase_noise(self.t[0:(kmax-k)], self.dphi_output[0:(kmax-k)], 
                                 sampling=1, figno=i, dirname = self.folder_plots)
                
            rms_noise = np.std(self.dphi_output)
            if self.print_option:
                print("RF noise for time step %.4e s (iter %d) has r.m.s. phase %.4e rad (%.3e deg)" \
                    %(self.t[1], i, rms_noise, rms_noise*180/np.pi))
                
        if self.continuous_phase:
            psi = np.arange(0, self.n_turns+1)*2*np.pi/self.corr
            self.dphi = self.dphi*np.sin(psi[:self.n_turns+1]) + self.dphi2[:(self.n_turns+1)]*np.cos(psi[:self.n_turns+1])
        
        if self.initial_final_turns[0]>0 or self.initial_final_turns[1]<self.total_n_turns+1:
            self.dphi = np.concatenate((np.zeros(self.initial_final_turns[0]), self.dphi, np.zeros(1+self.total_n_turns-self.initial_final_turns[1])))
Exemplo n.º 7
0
    def process(self):
        '''
        *Reprocess the impedance contributions. To be run when slices changes*
        '''

        if (self.wake_length_input != None
                and self.frequency_resolution_input == None):
            # Number of points of the induced voltage array
            self.n_induced_voltage = int(
                np.ceil(self.wake_length_input / self.slices.bin_size))
            if self.n_induced_voltage < self.slices.n_slices:
                raise RuntimeError('Error: too short wake length. ' +
                                   'Increase it above {0:1.2e} s.'.format(
                                       self.slices.n_slices *
                                       self.slices.bin_size))
            #: *Wake length in s, rounded up to the next multiple of bin size*
            self.wake_length = self.n_induced_voltage * self.slices.bin_size
            self.frequency_resolution = 1 / self.wake_length
        elif (self.frequency_resolution_input != None
              and self.wake_length_input == None):
            self.n_induced_voltage = int(
                np.ceil(
                    1 /
                    (self.slices.bin_size * self.frequency_resolution_input)))
            if self.n_induced_voltage < self.slices.n_slices:
                raise RuntimeError(
                    'Error: too large frequency_resolution. ' +
                    'Reduce it below {0:1.2e} Hz.'.format(
                        1 / (self.slices.cut_right - self.slices.cut_left)))
            self.wake_length = self.n_induced_voltage * self.slices.bin_size
            #: *Frequency resolution in Hz*
            self.frequency_resolution = 1 / self.wake_length
        elif (self.wake_length_input == None
              and self.frequency_resolution_input == None):
            # By default the wake_length is the slicing frame length
            self.wake_length = (self.slices.cut_right - self.slices.cut_left)
            self.frequency_resolution = 1 / self.wake_length
            self.n_induced_voltage = self.slices.n_slices
        else:
            raise RuntimeError('Error: only one of wake_length or ' +
                               'frequency_resolution can be specified.')

        if self.multi_turn_wake:
            # Number of points of the memory array for multi-turn wake
            self.n_mtw_memory = self.n_induced_voltage

            self.front_wake_buffer = 0

            if self.mtw_mode == 'freq':
                # In frequency domain, an extra buffer for a revolution turn is
                # needed due to the circular time shift in frequency domain
                self.buffer_size = \
                    np.ceil(np.max(self.RFParams.t_rev) / self.slices.bin_size)
                # Extending the buffer to reduce the effect of the front wake
                self.buffer_size += \
                    np.ceil(np.max(self.buffer_extra) / self.slices.bin_size)
                self.n_mtw_memory += int(self.buffer_size)
                # Using next regular for FFTs speedup
                self.n_mtw_fft = next_regular(self.n_mtw_memory)
                # Frequency and omega arrays
                self.freq_mtw = \
                    rfftfreq(self.n_mtw_fft, d=self.slices.bin_size)
                self.omegaj_mtw = 2.0j * np.pi * self.freq_mtw
                # Selecting time-shift method
                self.shift_trev = self.shift_trev_freq
            else:
                # Selecting time-shift method
                self.shift_trev = self.shift_trev_time
                # Time array
                self.time_mtw = np.arange(0, self.wake_length,
                                          self.wake_length / self.n_mtw_memory)

            # Array to add and shift in time the multi-turn wake over the turns
            self.mtw_memory = np.zeros(self.n_mtw_memory)

            # Select induced voltage generation method to be used
            self.induced_voltage_generation = self.induced_voltage_mtw
        else:
            self.induced_voltage_generation = self.induced_voltage_1turn
Exemplo n.º 8
0
    def __init__(self,
                 Slices,
                 impedance_source_list,
                 frequency_resolution_input=None,
                 freq_res_option='round',
                 n_turns_memory=0,
                 recalculation_impedance=False,
                 index_save_individual_voltage=-1,
                 n_windows_before=0,
                 main_harmonic_number=1,
                 overwrite_n_fft_sampling=None):

        #: *Copy of the Slices object in order to access the profile info.*
        self.slices = Slices

        #: *Impedance sources inputed as a list (eg: list of BBResonators objects)*
        self.impedance_source_list = impedance_source_list

        #: *Input frequency resolution in [Hz], the beam profile sampling for the spectrum
        #: will be adapted according to the freq_res_option.*
        self.frequency_resolution_input = frequency_resolution_input

        #: *Number of turns to be considered as memory for induced voltage calculation.*
        self.n_turns_memory = n_turns_memory

        #: *Length of one slice.*
        time_resolution = self.slices.bin_centers[1] - self.slices.bin_centers[
            0]

        self.recalculation_impedance = recalculation_impedance

        self.index_save_individual_voltage = index_save_individual_voltage

        if n_turns_memory == 0:

            if self.frequency_resolution_input == None:
                self.n_fft_sampling = self.slices.n_slices
            else:
                self.freq_res_option = freq_res_option
                if self.freq_res_option is 'round':
                    self.n_fft_sampling = next_regular(
                        int(
                            np.round(1 / (self.frequency_resolution_input *
                                          time_resolution))))
                elif self.freq_res_option is 'ceil':
                    self.n_fft_sampling = next_regular(
                        int(
                            np.ceil(1 / (self.frequency_resolution_input *
                                         time_resolution))))
                elif self.freq_res_option is 'floor':
                    self.n_fft_sampling = next_regular(
                        int(
                            np.floor(1 / (self.frequency_resolution_input *
                                          time_resolution))))
                else:
                    raise RuntimeError(
                        'The input freq_res_option is not recognized')

                if self.n_fft_sampling < self.slices.n_slices:
                    print(
                        'The input frequency resolution step is too big, and the whole \
                           bunch is not sliced... The number of sampling points for the \
                           FFT is corrected in order to sample the whole bunch (and \
                           you might consider changing the input in order to have \
                           a finer resolution).')
                    self.n_fft_sampling = next_regular(self.slices.n_slices)

            if overwrite_n_fft_sampling != None:
                self.n_fft_sampling = overwrite_n_fft_sampling

            #: *Real frequency resolution in [Hz], according to the obtained n_fft_sampling.*
            self.frequency_resolution = 1 / (self.n_fft_sampling *
                                             time_resolution)

            #: *Frequency array of the impedance in [Hz]*
            self.frequency_array = rfftfreq(
                self.n_fft_sampling,
                self.slices.bin_centers[1] - self.slices.bin_centers[0])

            #: *Total impedance array of all sources in* [:math:`\Omega`]
            self.total_impedance = 0

            self.sum_impedances(self.frequency_array)

            #: *Induced voltage from the sum of the wake sources in [V]*
            self.induced_voltage = 0

        else:

            self.n_windows_before = n_windows_before
            self.n_turns_memory = n_turns_memory
            self.main_harmonic_number = main_harmonic_number
            self.len_array_memory = (
                self.n_turns_memory + 1 +
                n_windows_before) * self.slices.n_slices * main_harmonic_number
            self.n_fft_sampling = next_regular(self.len_array_memory)
            self.frequency_array_memory = rfftfreq(self.n_fft_sampling,
                                                   time_resolution)
            self.total_impedance_memory = np.zeros(
                self.frequency_array_memory.shape) + 0j
            for imped_object in self.impedance_source_list:
                imped_object.imped_calc(self.frequency_array_memory)
                self.total_impedance_memory += imped_object.impedance