diff --git a/tracking/monitors/__init__.py b/tracking/monitors/__init__.py index 3ff31947a254f22aecfdd614e555637ef5b6462a..ffff2a982845826ee31b220f00633f247934d3a6 100644 --- a/tracking/monitors/__init__.py +++ b/tracking/monitors/__init__.py @@ -11,7 +11,8 @@ from mbtrack2.tracking.monitors.monitors import (Monitor, BunchMonitor, ProfileMonitor, WakePotentialMonitor, TuneMonitor, - CavityMonitor) + CavityMonitor, + BunchSpectrumMonitor) from mbtrack2.tracking.monitors.plotting import (plot_bunchdata, plot_phasespacedata, plot_profiledata, diff --git a/tracking/monitors/monitors.py b/tracking/monitors/monitors.py index 0d3b9a4cde683c0c301c793ed69d36bbc70a226c..5b70c9bb35b038cc64607da042955c19062e7f05 100644 --- a/tracking/monitors/monitors.py +++ b/tracking/monitors/monitors.py @@ -1052,6 +1052,203 @@ class TuneMonitor(Monitor): fourier_tau_avg = np.mean(abs(fourier_tau),axis=0) return (fourier_x_avg, fourier_y_avg, fourier_tau_avg) + +class BunchSpectrumMonitor(Monitor): + """ + Monitor the coherent and incoherent bunch spectrums. + + Parameters + ---------- + ring : Synchrotron object + bunch_number : int + Bunch to monitor + mp_number : int or float + Total number of macro-particles in the bunch. + sample_size : int or float + Number of macro-particles to be used for tune and FFT computation. + This number cannot exceed mp_number. + n_fft : int or float, optional + The number of points used for FFT computation, if n_fft is bigger than + save_every zero-padding is applied. + file_name : string, optional + Name of the HDF5 where the data will be stored. Must be specified + the first time a subclass of Monitor is instancied and must be None + the following times. + save_every : int or float, optional + Set the frequency of the save. The spectrums are computed every + save_every call of the montior. + buffer_size : int or float, optional + Size of the save buffer. + total_size : int or float, optional + Total size of the save. The following relationships between the + parameters must exist: + total_size % buffer_size == 0 + number of call to track / save_every == total_size - 1 + mpi_mode : bool, optional + If True, open the HDF5 file in parallel mode, which is needed to + allow several cores to write in the same file at the same time. + If False, open the HDF5 file in standard mode. + + Methods + ------- + track(bunch): + Save spectrum data. + + """ + + def __init__(self, ring, bunch_number, mp_number, sample_size, n_fft=10000, + file_name=None, save_every=10, + buffer_size=5, total_size=10, mpi_mode=True, + dim="all"): + + self.n_fft = int(n_fft) + self.sample_size = int(sample_size) + self.store_dict = {"x":0,"y":1,"tau":2} + + if dim == "all": + self.track_dict = {"x":0,"y":1,"tau":2} + self.mean_index = [True, False, True, False, True, False] + elif dim == "tau": + self.track_dict = {"tau":0} + self.mean_index = [False, False, False, False, True, False] + elif dim == "x": + self.track_dict = {"x":0} + self.mean_index = [True, False, False, False, False, False] + elif dim == "y": + self.track_dict = {"y":0} + self.mean_index = [False, False, True, False, False, False] + + self.size_list = len(self.track_dict) + + self.ring = ring + self.bunch_number = bunch_number + group_name = "BunchSpectrum_" + str(self.bunch_number) + + dict_buffer = {"incoherent":(3, n_fft//2+1, buffer_size), + "coherent":(3, n_fft//2+1, buffer_size)} + dict_file = {"incoherent":(3, n_fft//2+1, total_size), + "coherent":(3, n_fft//2+1, total_size)} + + self.monitor_init(group_name, save_every, buffer_size, total_size, + dict_buffer, dict_file, file_name, mpi_mode) + + self.dict_buffer = dict_buffer + self.dict_file = dict_file + + self.save_count = 0 + + self.positions = np.zeros((self.size_list, self.sample_size, save_every+1)) + self.mean = np.zeros((self.size_list, save_every+1)) + + index = np.arange(0, int(mp_number)) + self.index_sample = sorted(random.sample(list(index), self.sample_size)) + + self.incoherent = np.zeros((3, self.n_fft//2+1, buffer_size)) + self.coherent = np.zeros((3, self.n_fft//2+1, buffer_size)) + + def track(self, object_to_save): + """ + Save positions and mean data. + + Parameters + ---------- + object_to_save : Beam or Bunch object + + """ + skip = False + if isinstance(object_to_save, Beam): + if (object_to_save.mpi_switch == True): + if object_to_save.mpi.bunch_num == self.bunch_number: + bunch = object_to_save[object_to_save.mpi.bunch_num] + else: + skip = True + else: + bunch = object_to_save[self.bunch_number] + elif isinstance(object_to_save, Bunch): + bunch = object_to_save + else: + raise TypeError("object_to_save should be a Beam or Bunch object.") + + if skip is False: + for key, value in self.track_dict.items(): + self.positions[value, :, self.save_count] = bunch[key][self.index_sample] + + self.mean[:, self.save_count] = bunch.mean[self.mean_index] + + self.save_count += 1 + + if self.track_count > 0 and self.track_count % self.save_every == 0: + self.to_buffer(bunch) + self.save_count = 0 + + self.track_count += 1 + + def to_buffer(self, bunch): + """ + A method to hold saved data before writing it to the output file. + + """ + + self.time[self.buffer_count] = self.track_count + + for key, value in self.track_dict.items(): + self.incoherent[self.store_dict[key],:,self.buffer_count] = self.get_incoherent_spectrum(self.positions[value,:,:]) + self.coherent[self.store_dict[key],:,self.buffer_count] = self.get_coherent_spectrum(self.mean[value]) + + self.buffer_count += 1 + + if self.buffer_count == self.buffer_size: + self.write() + self.buffer_count = 0 + + def write(self): + """ + Write data from buffer to output file. + + """ + self.file[self.group_name]["time"][self.write_count*self.buffer_size:( + self.write_count+1)*self.buffer_size] = self.time + + self.file[self.group_name]["incoherent"][:,:, + self.write_count * self.buffer_size:(self.write_count+1) * + self.buffer_size] = self.incoherent + self.file[self.group_name]["coherent"][:,:, + self.write_count * self.buffer_size:(self.write_count+1) * + self.buffer_size] = self.coherent + + self.file.flush() + self.write_count += 1 + + def get_incoherent_spectrum(self, positions): + """ + Compute the incoherent spectrum i.e. the average of the absolute value + of the FT of the position of every particule of the bunch. + + Returns + ------- + incoherent : array + The average of the transformed input in each plane. + + """ + fourier = rfft(positions, n=self.n_fft) + incoherent = np.mean(np.abs(fourier), axis=0) + + return incoherent + + def get_coherent_spectrum(self, mean): + """ + Compute the coherent spectrum i.e. the absolute value of the FT of the + mean position of the bunch. + + Returns + ------- + coherent : array + The average of the transformed input in each plane. + + """ + coherent = np.abs(rfft(mean, n=self.n_fft)) + + return coherent class CavityMonitor(Monitor): """