diff --git a/tracking/monitors/monitors.py b/tracking/monitors/monitors.py index 1d66539280b71ec7c5c08db334155c1c2c345d0c..1c61966aa0b2730851399723d63c6bdf4f177a4c 100644 --- a/tracking/monitors/monitors.py +++ b/tracking/monitors/monitors.py @@ -17,6 +17,7 @@ from mbtrack2.tracking.particles import Bunch, Beam from mbtrack2.tracking.rf import CavityResonator from scipy.interpolate import interp1d from abc import ABCMeta +from scipy.fft import rfft, rfftfreq from mpi4py import MPI class Monitor(Element, metaclass=ABCMeta): @@ -812,7 +813,8 @@ class WakePotentialMonitor(Monitor): class TuneMonitor(Monitor): """ - Monitor tunes in horizontal, vertical, and longitudinal plane. + Monitor tunes and the Fourier transform (using FFT algorithm) of the + osciallation in horizontal, vertical, and longitudinal plane. Parameters ---------- @@ -822,8 +824,14 @@ class TuneMonitor(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 computation. This number - needs not to exceed mp_number. + Number of macro-particles to be used for tune and FFT computation. + This number cannot exceed mp_number. + save_tune : bool, optional + If True, tune data is saved. + save_fft : bool, optional + If True, FFT data is saved. + n_fft : int or float, optional + The number of points used for FFT computation. 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 @@ -846,19 +854,22 @@ class TuneMonitor(Monitor): Methods ------- track(bunch): - Save tune data. + Save tune and/or FFT data. """ - def __init__(self, ring, bunch_number, mp_number, sample_size, file_name=None, - save_every=10, buffer_size=5, total_size=10, mpi_mode=True): + def __init__(self, ring, bunch_number, mp_number, sample_size, save_tune=True, + save_fft=False, n_fft=10000, file_name=None, save_every=10, + buffer_size=5, total_size=10, mpi_mode=True): self.ring = ring self.bunch_number = bunch_number group_name = "TuneData_" + str(self.bunch_number) - - dict_buffer = {"tune":(3, buffer_size), "tune_spread":(3, buffer_size,)} - dict_file = {"tune":(3, total_size), "tune_spread":(3, total_size,)} + + dict_buffer = {"tune":(3, buffer_size), "tune_spread":(3, buffer_size,), + "fft":(3, n_fft//2+1, buffer_size)} + dict_file = {"tune":(3, total_size), "tune_spread":(3, total_size,), + "fft":(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) @@ -874,11 +885,17 @@ class TuneMonitor(Monitor): index = np.arange(0, int(mp_number)) self.index_sample = sorted(random.sample(list(index), self.sample_size)) - self.save_every = save_every self.save_count = 0 - self.buffer_count = 1 + self.save_tune = save_tune + self.save_fft = save_fft + self.save_every = save_every + + if self.save_fft is True : + self.n_fft = n_fft + self.fourier_save = np.zeros((3, self.n_fft//2+1, buffer_size)) + def track(self, object_to_save): """ Save tune data. @@ -909,10 +926,9 @@ class TuneMonitor(Monitor): self.save_count += 1 - if self.track_count > 0: - if self.track_count % self.save_every == 0: - self.to_buffer(bunch) - self.save_count = 0 + 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 @@ -921,10 +937,19 @@ class TuneMonitor(Monitor): A method to hold saved data before writing it to the output file. """ - mean, spread = self.get_tune(bunch) + self.time[self.buffer_count] = self.track_count - self.tune[:, self.buffer_count] = mean - self.tune_spread[:, self.buffer_count] = spread + + if self.save_tune is True: + mean, spread = self.get_tune(bunch) + self.tune[:, self.buffer_count] = mean + self.tune_spread[:, self.buffer_count] = spread + + if self.save_fft is True: + fx, fy, ftau = self.get_fft() + self.fourier_save[0,:,self.buffer_count] = fx + self.fourier_save[1,:,self.buffer_count] = fy + self.fourier_save[2,:,self.buffer_count] = ftau self.buffer_count += 1 @@ -940,12 +965,18 @@ class TuneMonitor(Monitor): 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]["tune"][:, - self.write_count * self.buffer_size:(self.write_count+1) * - self.buffer_size] = self.tune - self.file[self.group_name]["tune_spread"][:, - self.write_count * self.buffer_size:(self.write_count+1) * - self.buffer_size] = self.tune_spread + if self.save_tune is True: + self.file[self.group_name]["tune"][:, + self.write_count * self.buffer_size:(self.write_count+1) * + self.buffer_size] = self.tune + self.file[self.group_name]["tune_spread"][:, + self.write_count * self.buffer_size:(self.write_count+1) * + self.buffer_size] = self.tune_spread + + if self.save_fft is True: + self.file[self.group_name]["fft"][:,:, + self.write_count * self.buffer_size:(self.write_count+1) * + self.buffer_size] = self.fourier_save self.file.flush() self.write_count += 1 @@ -989,6 +1020,27 @@ class TuneMonitor(Monitor): return (mean, spread) + def get_fft(self): + """ + Compute the Fourier transform (using FFT algorithm) of the + osciallation in horizontal, vertical, and longitudinal plane. + + Returns + ------- + fourier_x_avg, fourier_y_avg, fourier_tau_avg : ndarray + The average of the transformed input in each plane. + + """ + fourier_x = rfft(self.x, n=self.n_fft) + fourier_y = rfft(self.y, n=self.n_fft) + fourier_tau = rfft(self.tau, n=self.n_fft) + + fourier_x_avg = np.mean(abs(fourier_x),axis=0) + fourier_y_avg = np.mean(abs(fourier_y),axis=0) + fourier_tau_avg = np.mean(abs(fourier_tau),axis=0) + + return (fourier_x_avg, fourier_y_avg, fourier_tau_avg) + class CavityMonitor(Monitor): """ Monitor a CavityResonator object and save attributes (mean, std, emit and current). @@ -1051,5 +1103,4 @@ class CavityMonitor(Monitor): self.to_buffer(cavity) else: raise TypeError("cavity should be a CavityResonator object.") - self.track_count += 1 - \ No newline at end of file + self.track_count += 1 diff --git a/tracking/monitors/plotting.py b/tracking/monitors/plotting.py index f2f247c2f832306677d645ad2f21df998d0e8878..066fc8af1d1001c405a0f658c2448d48b5f74370 100644 --- a/tracking/monitors/plotting.py +++ b/tracking/monitors/plotting.py @@ -13,6 +13,7 @@ import matplotlib as mpl import seaborn as sns import h5py as hp import random +from scipy.fft import rfftfreq def plot_beamdata(filename, dataset, dimension=None, stat_var=None, x_var="time"): """ @@ -492,7 +493,9 @@ def plot_wakedata(filename, bunch_number, wake_type="Wlong", start=0, elif streak_plot is True: return fig2 -def plot_tunedata(filename, bunch_number): +def plot_tunedata(filename, bunch_number, ring=None, plot_tune=True, plot_fft=False, + dimension='x', min_tune=0, max_tune=0.5, min_turn=None, + max_turn=None, streak_plot=True, profile_plot=False): """ Plot data recorded by TuneMonitor. @@ -503,6 +506,24 @@ def plot_tunedata(filename, bunch_number): bunch_number : int Bunch to plot. This has to be identical to 'bunch_number' parameter in 'BunchMonitor' object. + ring : Synchrotron object, optional + The ring configuration that is used in TuneMonitor. If None, the default + value of the revolution period and the revolution frequency are used, + which are 1.183 us and 0.845 MHz, respectively. + plot_tune : bool, optional + If True, tune data is plotted. + plot_fft : bool, optional + If True, FFT data is plotted. + dimension : {'x', 'y', 's'} + Option to plot FFT data in horizontal, vertical, or longitudinal plane. + min_tune, max_tune : int or float, optional + The minimum and the maximum tune values to plot FFT data. + min_turn, max_turn : int or float, optional + The minimum and the maximum number of turns to plot FFT data. + streak_plot : bool, optional + If True, the FFT data is plotted as a streak plot. + bunch_profile : bool, optional. + If True, the FFT data is plotted as line profiles. Return ------ @@ -515,20 +536,93 @@ def plot_tunedata(filename, bunch_number): group = "TuneData_{0}".format(bunch_number) # Data group of the HDF5 file time = file[group]["time"] - tune = file[group]["tune"] - tune_spread = file[group]["tune_spread"] - - fig1, ax1 = plt.subplots() - ax1.errorbar(x=time[1:], y=tune[0,1:], yerr=tune_spread[0,1:]) - ax1.errorbar(x=time[1:], y=tune[1,1:], yerr=tune_spread[1,1:]) - ax1.set_xlabel("Turn number") - ax1.set_ylabel("Transverse tunes") - plt.legend(["x","y"]) - fig2, ax2 = plt.subplots() - ax2.errorbar(x=time[1:], y=tune[2,1:], yerr=tune_spread[2,1:]) - ax2.set_xlabel("Turn number") - ax2.set_ylabel("Synchrotron tune") + if plot_tune is True: + tune = file[group]["tune"] + tune_spread = file[group]["tune_spread"] + + fig1, ax1 = plt.subplots() + ax1.errorbar(x=time[1:], y=tune[0,1:], yerr=tune_spread[0,1:]) + ax1.errorbar(x=time[1:], y=tune[1,1:], yerr=tune_spread[1,1:]) + ax1.set_xlabel("Turn number") + ax1.set_ylabel("Transverse tunes") + plt.legend(["x","y"]) + + fig2, ax2 = plt.subplots() + ax2.errorbar(x=time[1:], y=tune[2,1:], yerr=tune_spread[2,1:]) + ax2.set_xlabel("Turn number") + ax2.set_ylabel("Synchrotron tune") + + if plot_fft is True: + if ring is None: + T0 = 1.183e-06 + f0 = 0.845e6 + else: + T0 = ring.T0 + f0 = ring.f0 + + n_freq = file[group]['fft'].shape[1] + freq = rfftfreq((n_freq-1)*2, T0) + tune_fft = freq / f0 + + dimension_dict = {'x':0, 'y':1, 's':2} + axis = dimension_dict[dimension] + + fourier_save = file[group]['fft'][axis] + + if max_turn is None: + max_turn = time[-1] + if min_turn is None: + min_turn = time[1] + + min_tune_iloc = np.where(tune_fft >= min_tune)[0][0] + max_tune_iloc = np.where(tune_fft <= max_tune)[0][-1] + save_every = int(time[1] - time[0]) + min_turn_iloc = min_turn // save_every + max_turn_iloc = max_turn // save_every + + if streak_plot is True: + fig3, ax3 = plt.subplots() + cmap = plt.get_cmap('Blues') + + c = ax3.imshow(np.transpose(np.log( + fourier_save[min_tune_iloc:max_tune_iloc+1, + min_turn_iloc:max_turn_iloc+1])), + cmap=cmap, origin='lower' , aspect='auto', + extent=[min_tune, max_tune, min_turn, max_turn]) + ax3.set_xlabel('$Q_{}$'.format(dimension)) + ax3.set_ylabel("Turns") + cbar = fig3.colorbar(c, ax=ax3) + cbar.set_label("log FFT amplitude") + + if profile_plot is True: + fig4, ax4 = plt.subplots() + ax4.plot(tune_fft[min_tune_iloc:max_tune_iloc+1], + fourier_save[min_tune_iloc:max_tune_iloc+1, + min_turn_iloc:max_turn_iloc+1]) + ax4.set_xlabel('$Q_{}$'.format(dimension)) + ax4.set_ylabel("FFT amplitude") + ax4.legend(time[min_turn_iloc:max_turn_iloc+1]) + file.close() - return (fig1, fig2) \ No newline at end of file + + if plot_tune is True and plot_fft is True: + if streak_plot is True and profile_plot is True: + return fig1, fig2, fig3, fig4 + elif streak_plot is True: + return fig1, fig2, fig3 + elif profile_plot is True: + return fig1, fig2, fig4 + + elif plot_tune is True: + return fig1, fig2 + + elif plot_fft is True: + if streak_plot is True and profile_plot is True: + return fig3, fig4 + elif streak_plot is True: + return fig3 + elif profile_plot is True: + return fig4 + \ No newline at end of file