def get_linear_growth(plot=False): ''' Calculates an exponential growth rate based on transverse magnetic field energy. ''' import pdb by = cf.get_array('By') * 1e9 bz = cf.get_array('Bz') * 1e9 mu0 = (4e-7) * np.pi # Magnetic Permeability of Free Space (SI units) print('Fitting magnetic energy') bt = np.sqrt(by ** 2 + bz ** 2) U_B = np.square(bt[:, 0])#.sum(axis=1)# * cf.NX * cf.dx / mu0 * 0.5 #dU = bk.get_derivative(U_B) #linear_cutoff = np.where(dU == dU.max())[0][0] #time_fit = cf.time_seconds_field[:linear_cutoff] plt.plot(cf.time_radperiods_field, by, marker='o') plt.xlim(0, 200) # ============================================================================= # fit_params = lmf.Parameters() # fit_params.add('amp' , value=1.0 , min=None , max=None) # fit_params.add('growth', value=0.001*cf.gyfreq, min=0.0 , max=None) # # fit_output = lmf.minimize(residual_exp, fit_params, args=(time_fit,), kws={'data': U_B[:linear_cutoff]}, # method='leastsq') # fit_function = residual_exp(fit_output.params, time_fit) # # fit_dict = fit_output.params.valuesdict() # ============================================================================= # ============================================================================= # if plot == True: # plt.ioff() # plt.figure() # plt.plot(cf.time_seconds_field[:linear_cutoff], U_B[:linear_cutoff], color='green', marker='o', label='Energy') # plt.plot(cf.time_seconds_field[:linear_cutoff], fit_function, color='b', label='Exp. fit') # plt.figtext(0.135, 0.725, r'$\gamma = %.3fs^{-1}$' % (fit_dict['growth'] / (2 * np.pi))) # plt.title('Transverse magnetic field energy') # plt.xlabel('Time (s)') # plt.ylabel('Energy (J)') # plt.legend() # # # ============================================================================= # # plt.figure() # # plt.plot(time_seconds[:linear_cutoff], dU[:linear_cutoff]) # # ============================================================================= # # if plot == 'save': # save_path = cf.anal_dir + 'magnetic_energy_expfit.png' # plt.savefig(save_path) # plt.close('all') # elif plot == 'show': # plt.show() # else: # pass # ============================================================================= return
def get_helical_components(overwrite=False, field='B'): temp_dir = cf.temp_dir print('Getting helical components for {} field'.format(field)) if os.path.exists(temp_dir + '{}_positive_helicity.npy'.format(field) ) == False or overwrite == True: ftime, Fy = cf.get_array('{}y'.format(field)) ftime, Fz = cf.get_array('{}z'.format(field)) Ft_pos = np.zeros((ftime.shape[0], cf.NX), dtype=np.complex128) Ft_neg = np.zeros((ftime.shape[0], cf.NX), dtype=np.complex128) for ii in range(Fy.shape[0]): print('Calculating helicity for field file', ii) Ft_pos[ii, :], Ft_neg[ii, :] = calculate_helicity(Fy[ii], Fz[ii]) print('Saving {}-helicities to file'.format(field)) np.save(temp_dir + '{}_positive_helicity.npy'.format(field), Ft_pos) np.save(temp_dir + '{}_negative_helicity.npy'.format(field), Ft_neg) else: print('Loading {}-elicities from file'.format(field)) ftime, Fy = cf.get_array('{}y'.format(field)) Ft_pos = np.load(temp_dir + '{}_positive_helicity.npy'.format(field)) Ft_neg = np.load(temp_dir + '{}_negative_helicity.npy'.format(field)) return ftime, Ft_pos, Ft_neg
def get_FB_waves(overwrite=False, field='B', st=None, en=None): ''' Use method of Shoji et al. (2011) to separate magnetic wave field into backwards and forwards components -- Should be equivalent to helicity thing -- Doesn't generalise well, can't tell between propagation direction and polarisation -- Only works for this because EMICs are generated in the L-mode and have single polarisation -- Don't cut off damping regions, she'll be right Fields are (time, space) FFT Positions # [0] - Zero frequency term # [1:n/2] - Positive k terms # [n/2+1:] - Negative k terms # n/2 either both pos/neg nyquist (even) or # (odd) largest positive/negative frequency is on either side -- Do I have to account for that? -- Zeroing the A[0] term will probably just demean the resulting timeseries STILL NEEDS VALIDATION - Conservation: If I add the results, do I get the original waveform back? - Compare against existing helicity code, are they equivalent? The sign in F_perp (I think) just determines which is the +/- wave ''' print('Calculating positive/negative helicities') ftime, Fy = cf.get_array('{}y'.format(field)) ftime, Fz = cf.get_array('{}z'.format(field)) F_perp = Fy[:, st: en] + 1j * Fz[:, st: en] # Should this be a +ve or -ve? Defines polarisation Nk = F_perp.shape[1] F_fwd = np.zeros(F_perp.shape, dtype=np.complex128) F_bwd = np.zeros(F_perp.shape, dtype=np.complex128) for ii in range(F_perp.shape[0]): Fk = np.fft.fft(F_perp[ii]) Fk0 = 0.5 * Fk[0] fwd_fft = Fk.copy() # Copy coefficients bwd_fft = Fk.copy() fwd_fft[Nk // 2 + 1:] *= 0.0 # Zero positive/negative wavenumbers bwd_fft[1:Nk // 2] *= 0.0 fwd_fft[ 0] = Fk0 # Not sure what to do with the zero term. Split evenly? bwd_fft[0] = Fk0 F_fwd[ii] = np.fft.ifft( fwd_fft) # Transform back into spatial-domain data F_bwd[ii] = np.fft.ifft(bwd_fft) return ftime, F_fwd, F_bwd, F_perp
def autopower_spectra(component='By', overlap=0.5, win_idx=None, slide_idx=None, df=50, cell_idx=None): if 'B' in component.upper(): ftime, y_arr = cf.get_array(component) # Field component in nT y_arr = y_arr[:, cell_idx] * 1e9 elif 'E' in component.upper(): ftime, y_arr = cf.get_array(component) # Field component in mV/m y_arr = y_arr[:, cell_idx] * 1e6 else: sys.exit( 'Field loading error for kwarg: component={}'.format(component)) dt = cf.dt_field if win_idx is None: t_win = 1000. / df # Window length (in seconds) for desired frequency resolution win_idx = int(np.ceil(t_win / cf.dt_field)) # Window size in indexes hlen = ( win_idx - 1 ) // 2 # Index of first mid-window, starting from idx 0. After this, displaced by slide_idx. if win_idx % 2 == 0: win_idx += 1 # Force window length to be odd number (for window-halving in FFT: Center values are always center values) if slide_idx is None: if overlap > 100: overlap /= 100. # Check for accidental percentage instead of decimal overlap slide_idx = int( win_idx * (1. - overlap) ) # Calculate slide length in index values from overlap percentage num_slides = ( y_arr.shape[0] - win_idx ) // slide_idx # Count number of slides required. Unless multiple of idx_length, last value will not explicitly be computed FFT_output = do_stft(y_arr, win_idx, slide_idx, num_slides) # Do dynamic FFT FFT_times = (np.arange(num_slides) * slide_idx + hlen) * dt # Collect times for each FFT slice df = 1. / (win_idx * cf.dt_field) # Frequency increment (in mHz) freq = np.asarray([df * jj for jj in range(win_idx // 2 + 1) ]) # Frequency array up to Nyquist power = np.real(FFT_output * np.conj(FFT_output)) return power, FFT_times, freq
def plot_kt_winske(component='by'): qi = 1.602e-19 # Elementary charge (C) c = 3e8 # Speed of light (m/s) mp = 1.67e-27 # Mass of proton (kg) e0 = 8.854e-12 # Epsilon naught - permittivity of free space ftime, arr = cf.get_array(component) radperiods = ftime * cf.gyfreq gperiods = ftime / cf.gyperiod ts_folder = cf.anal_dir + '//winske_fourier_modes//' if os.path.exists(ts_folder) == False: os.makedirs(ts_folder) # Get first/last indices for FFT range and k-space array st = cf.ND if component[0].upper() == 'B': en = cf.ND + cf.NX k = np.fft.fftfreq(cf.NX, cf.dx) else: en = cf.ND + cf.NX k = np.fft.fftfreq(cf.NX, cf.dx) # Normalize to c/wpi cwpi = c / np.sqrt(cf.ne * qi**2 / (mp * e0)) k *= cwpi k = k[k >= 0] kmax = k.shape[0] fft_matrix = np.zeros((arr.shape[0], en - st), dtype='complex128') for ii in range(arr.shape[0]): # Take spatial FFT at each time, ii fft_matrix[ii, :] = np.fft.fft(arr[ii, st:en] - arr[ii, st:en].mean()) kt = (fft_matrix[:, :k.shape[0]] * np.conj(fft_matrix[:, :k.shape[0]])).real plt.ioff() for ii in range(ftime.shape[0]): fig, ax = plt.subplots() ax.semilogy(k[1:kmax], kt[ii, 1:kmax], ds='steps-mid') ax.set_title('IT={:04d} :: T={:5.2f} :: GP={:5.2f}'.format( ii, radperiods[ii], gperiods[ii]), family='monospace') ax.set_xlabel('K') ax.set_ylabel('BYK**2') ax.set_xlim(k[1], k[kmax - 1]) fig.savefig(ts_folder + 'winske_fourier_{}_{}.png'.format(component, ii), edgecolor='none') plt.close('all') sys.stdout.write( '\rCreating fourier mode plot for timestep {}'.format(ii)) sys.stdout.flush() print('\n') return
def get_growth_rates(do_plot=None): ''' Extract the magnetic linear wave growth rate from: -- Fitting an exponential to the magnetic energy -- Fitting a growing sine wave to the field components at each cell The linear regime is calculated as all times before the maximum energy derivative, i.e. the growth is assumed exponential until the rate of energy transfer decreases. One could also take the min/max (i.e. abs) of the field through time and fit an exponential to that, but it should be roughly equivalent to the energy fit. INPUT: -- do_plot : 'show', 'save' or 'None'. 'save' will also output growth rates to a text file. ''' by = cf.get_array('By') * 1e9 bz = cf.get_array('Bz') * 1e9 linear_cutoff, gr_rate_energy = fit_magnetic_energy(by, bz, plot=do_plot) freqs, power, max_idx = get_max_frequency(by, plot=do_plot) growth_rate_kt(by, linear_cutoff, freqs[max_idx]) by_wamps, by_wfreqs, by_gr_rate = fit_field_component(by, freqs[max_idx], 'By', linear_cutoff, plot=do_plot) bz_wamps, bz_wfreqs, bz_gr_rate = fit_field_component(bz, freqs[max_idx], 'Bz', linear_cutoff, plot=do_plot) if do_plot == 'save': txt_path = cf.anal_dir + 'growth_rates.txt' text_file = open(txt_path, 'w') else: text_file = None print('Energy growth rate: {}'.format(gr_rate_energy), file=text_file) print('By av. growth rate: {}'.format(by_gr_rate.mean()), file=text_file) print('Bz av. growth rate: {}'.format(bz_gr_rate.mean()), file=text_file) print('By min growth rate: {}'.format(by_gr_rate.min()), file=text_file) print('Bz min growth rate: {}'.format(bz_gr_rate.min()), file=text_file) print('By max growth rate: {}'.format(by_gr_rate.max()), file=text_file) print('Bz max growth rate: {}'.format(bz_gr_rate.max()), file=text_file) return
def get_helical_components(overwrite): temp_dir = cf.temp_dir if os.path.exists(temp_dir + 'B_positive_helicity.npy') == False or overwrite == True: By = cf.get_array('By') Bz = cf.get_array('Bz') Bt_pos = np.zeros(By.shape, dtype=np.complex128) Bt_neg = np.zeros(By.shape, dtype=np.complex128) for ii in range(By.shape[0]): print('Analysing time step {}'.format(ii)) Bt_pos[ii, :], Bt_neg[ii, :] = calculate_helicity(By[ii], Bz[ii], cf.NX, cf.dx) print('Saving helicities to file...') np.save(temp_dir + 'B_positive_helicity.npy', Bt_pos) np.save(temp_dir + 'B_negative_helicity.npy', Bt_neg) else: print('Loading helicities from file...') Bt_pos = np.load(temp_dir + 'B_positive_helicity.npy') Bt_neg = np.load(temp_dir + 'B_negative_helicity.npy') return Bt_pos, Bt_neg
def get_wx(component): ftime, arr = cf.get_array(component) if component[0] == 'B': ncells = cf.NC + 1 else: ncells = cf.NC if arr.shape[0] % 2 == 0: fft_matrix = np.zeros((arr.shape[0] // 2 + 1, ncells), dtype='complex128') else: fft_matrix = np.zeros(((arr.shape[0] + 1) // 2, ncells), dtype='complex128') for ii in range(arr.shape[1]): fft_matrix[:, ii] = np.fft.rfft(arr[:, ii] - arr[:, ii].mean()) wx = (fft_matrix * np.conj(fft_matrix)).real return ftime, wx
def get_kt(component): ftime, arr = cf.get_array(component) # Get first/last indices for FFT range and k-space array st = cf.ND if component[0].upper() == 'B': en = cf.ND + cf.NX + 1 k = np.fft.fftfreq(cf.NX + 1, cf.dx) else: en = cf.ND + cf.NX k = np.fft.fftfreq(cf.NX, cf.dx) k = k[k >= 0] fft_matrix = np.zeros((arr.shape[0], en - st), dtype='complex128') for ii in range(arr.shape[0]): # Take spatial FFT at each time, ii fft_matrix[ii, :] = np.fft.fft(arr[ii, st:en] - arr[ii, st:en].mean()) kt = (fft_matrix[:, :k.shape[0]] * np.conj(fft_matrix[:, :k.shape[0]])).real return k, ftime, kt, st, en
def get_wx(component, fac=1.0, normalize=True): ftime, arr = cf.get_array(component) arr *= fac if component[0].upper() == 'B': ncells = cf.NC + 1 else: ncells = cf.NC if arr.shape[0] % 2 == 0: fft_matrix = np.zeros((arr.shape[0] // 2 + 1, ncells), dtype='complex128') else: fft_matrix = np.zeros(((arr.shape[0] + 1) // 2, ncells), dtype='complex128') for ii in range(arr.shape[1]): fft_matrix[:, ii] = np.fft.rfft(arr[:, ii] - arr[:, ii].mean()) if normalize: fft_matrix *= 2.0 / ftime.shape[0] wx = (fft_matrix * np.conj(fft_matrix)).real return ftime, wx
def get_wk(component): ''' Spatial boundaries start at index ''' ftime, arr = cf.get_array(component) if component.upper()[0] == 'B': st = cf.ND en = cf.ND + cf.NX + 1 else: st = cf.ND en = cf.ND + cf.NX num_times = arr.shape[0] df = 1. / (num_times * cf.dt_field) dk = 1. / (cf.NX * cf.dx) f = np.arange(0, 1. / (2 * cf.dt_field), df) k = np.arange(0, 1. / (2 * cf.dx), dk) fft_matrix = np.zeros(arr[:, st:en].shape, dtype='complex128') fft_matrix2 = np.zeros(arr[:, st:en].shape, dtype='complex128') # Take spatial FFT at each time for ii in range(arr.shape[0]): fft_matrix[ii, :] = np.fft.fft(arr[ii, st:en] - arr[ii, st:en].mean()) # Take temporal FFT at each position (now k) for ii in range(fft_matrix.shape[1]): fft_matrix2[:, ii] = np.fft.fft(fft_matrix[:, ii] - fft_matrix[:, ii].mean()) wk = fft_matrix2[:f.shape[0], :k.shape[0]] * np.conj( fft_matrix2[:f.shape[0], :k.shape[0]]) return k, f, wk
def get_wk(component, linear_only=True, norm_z=False, centre_only=False): ''' Spatial boundaries start at index. linear_only is kwarg to either take Boolean type or a number specifying up to what time to FFT up to (in units of wcinv) norm_z normalizes the field to the background magnetic field, if the input component is magnetic. centre_only :: kwarg to only FFT the middle __ % of cells. Calculated by (1.0-centre_only)/2 indices on each side subtracted. ''' ftime, arr = cf.get_array(component) if norm_z == True and component.upper()[0] == 'B': arr /= cf.B_eq t0 = 0 if linear_only == True: # Find the point where magnetic energy peaks, FFT only up to 120% of that index yftime, by = cf.get_array('By') zftime, bz = cf.get_array('Bz') bt = np.sqrt(by**2 + bz**2) mu0 = (4e-7) * np.pi U_B = 0.5 / mu0 * np.square(bt[:, :]).sum(axis=1) * cf.dx max_idx = np.argmax(U_B) t1 = int(1.2 * max_idx) tf = ftime[t1] elif linear_only == False: t1 = ftime.shape[0] tf = ftime[t1 - 1] else: # Assume linear_only is a number end_sec = linear_only * 1. / cf.gyfreq diff_sec = np.abs(end_sec - ftime) t1 = np.where(diff_sec == diff_sec.min())[0][0] tf = ftime[t1] if centre_only != False: fraction_cut = (1.0 - centre_only) / 2.0 if component.upper()[0] == 'B': cut = int((cf.x1B - cf.x0B) * fraction_cut) else: cut = int((cf.x1E - cf.x0E) * fraction_cut) print(f'Cutting {cut} indices from each side') else: cut = 0 if component.upper()[0] == 'B': st = cf.x0B + cut en = cf.x1B - cut else: st = cf.x0E + cut en = cf.x1E - cut num_times = t1 - t0 num_points = en - st df = 1. / (num_times * cf.dt_field) dk = 1. / (num_points * cf.dx) f = np.arange(0, 1. / (2 * cf.dt_field), df) k = np.arange(0, 1. / (2 * cf.dx), dk) * 2 * np.pi fft_matrix = np.zeros(arr[t0:t1, st:en].shape, dtype='complex128') fft_matrix2 = np.zeros(arr[t0:t1, st:en].shape, dtype='complex128') # Take spatial FFT at each time for ii in range(fft_matrix.shape[0]): fft_matrix[ii, :] = np.fft.fft(arr[t0 + ii, st:en] - arr[t0 + ii, st:en].mean()) # Take temporal FFT at each position (now k) for ii in range(fft_matrix.shape[1]): fft_matrix2[:, ii] = np.fft.fft(fft_matrix[:, ii] - fft_matrix[:, ii].mean()) wk = fft_matrix2[:f.shape[0], :k.shape[0]] * np.conj( fft_matrix2[:f.shape[0], :k.shape[0]]) return k, f, wk, tf
def plot_fourier_mode_timeseries(it_max=None): ''' Load helical components Bt pos/neg, extract By_pos For each snapshot in time, take spatial FFT of By_pos (similar to how helicity is done) Save these snapshots in array Plot single mode timeseries from this 2D array Test run: Seems relatively close qualitatively, with a smaller growth rate and a few of the modes not quite as large. This could be any number of reasons - from the simulation method to the helicity. Will be interesting to compare direct to linear theory via the method outlined in Munoz et al. (2018). ''' ftime, By_raw = cf.get_array('By') ftime, Bz_raw = cf.get_array('Bz') radperiods = ftime * cf.gyfreq if it_max is None: it_max = ftime.shape[0] ftime, Bt_pos, Bt_neg = bk.get_helical_components() By_pos = Bt_pos.real x = np.linspace(0, cf.NX * cf.dx, cf.NX) k_modes = np.fft.rfftfreq(x.shape[0], d=cf.dx) Byk_2 = np.zeros((ftime.shape[0], k_modes.shape[0]), dtype=np.float64) # Do time loop here. Could also put normalization flag for ii in range(ftime.shape[0]): Byk = (1 / k_modes.shape[0]) * np.fft.rfft(By_pos[ii]) Byk_2[ii, :] = (Byk * np.conj(Byk)).real / cf.B_eq**2 plt.ioff() fig, axes = plt.subplots(ncols=2, nrows=3, sharex=True, figsize=(15, 10)) axes[0, 0].semilogy(radperiods, Byk_2[:, 1]) axes[0, 0].set_title('m = 1') axes[0, 0].set_xlim(0, 100) axes[0, 0].set_ylim(1e-7, 1e-3) axes[1, 0].semilogy(radperiods, Byk_2[:, 2]) axes[1, 0].set_title('m = 2') axes[1, 0].set_xlim(0, 100) axes[1, 0].set_ylim(1e-6, 1e-1) axes[2, 0].semilogy(radperiods, Byk_2[:, 3]) axes[2, 0].set_title('m = 3') axes[2, 0].set_xlim(0, 100) axes[2, 0].set_ylim(1e-6, 1e-1) axes[0, 1].semilogy(radperiods, Byk_2[:, 4]) axes[0, 1].set_title('m = 4') axes[0, 1].set_xlim(0, 100) axes[0, 1].set_ylim(1e-6, 1e-0) axes[1, 1].semilogy(radperiods, Byk_2[:, 5]) axes[1, 1].set_title('m = 5') axes[1, 1].set_xlim(0, 100) axes[1, 1].set_ylim(1e-6, 1e-0) axes[2, 1].semilogy(radperiods, Byk_2[:, 6]) axes[2, 1].set_title('m = 6') axes[2, 1].set_xlim(0, 100) axes[2, 1].set_ylim(1e-6, 1e-0) fig.savefig(cf.anal_dir + 'fourier_modes.png') plt.close('all') #axes[ii].set_xlim(0, 100) # #axes[ii].set_xlabel('$\Omega_i t$') return
def straight_line_fit(save=True, normalized_output=True, normfit_min=0.2, normfit_max=0.6): ''' To do: Check units. Get growth rate from amplitude only? How to do across space -- Is wave power averaged/summed across space analogous to a single point? Or do I have to do single point? -- How to calculate growth rate of energy summed across space? -- Start with the simple and go from there. Saturation amplitudes have to match too? -- How do the Winske saturation amplitudes look? It might just be the Fu thing being fucky. ''' print('Calculating growth rate...') ftime, by = cf.get_array('By') ftime, bz = cf.get_array('Bz') bt = np.sqrt(by ** 2 + bz ** 2) btn = np.square(bt[:, :]).sum(axis=1) / cf.B_eq ** 2 mu0 = (4e-7) * np.pi U_B = 0.5 / mu0 * np.square(bt[:, :]).sum(axis=1) * cf.dx max_idx = np.argmax(U_B) st = int(normfit_min * max_idx) en = int(normfit_max * max_idx) # Data to fit straight line to linear_xvals = ftime[st:en] linear_yvals = np.log(U_B[st:en]) gradient, y_intercept = np.polyfit(linear_xvals, linear_yvals, 1) # Calculate growth rate and normalize H to cyclotron frequency gamma = 0.5*gradient normalized_gr = gamma / cf.gyfreq # Create line data to plot what we fitted linear_yfit = gradient * linear_xvals + y_intercept # Returns y-values on log sscale log_yfit = np.exp(linear_yfit) # Convert to linear values to use with semilogy() # Plot to check plt.ioff() fig, ax = plt.subplots(nrows=2, figsize=(15, 10), sharex=True) ax[0].set_title('Growth Rate :: $\gamma / \Omega_H$ = %.4f' % normalized_gr, fontsize=20) ax[0].plot(ftime, btn) ax[0].set_ylabel(r'$\frac{B^2}{B_0^2}$', rotation=0, labelpad=30, fontsize=18) ax[0].set_xlim(0, ftime[-1]) ax[0].set_ylim(0, None) # Plot energy log scale ax[1].semilogy(ftime, U_B) ax[1].set_xlabel('Time (s)', fontsize=18) ax[1].set_ylabel('$U_B$', rotation=0, labelpad=30, fontsize=18) ax[1].set_xlim(0, ftime[-1]) ax[1].set_ylim(None, None) # Mark growth rate indicators ax[1].scatter(ftime[max_idx], U_B[max_idx], c='r', s=20, marker='x') ax[1].scatter(ftime[st] , U_B[st], c='r', s=20, marker='o') ax[1].scatter(ftime[en] , U_B[en], c='r', s=20, marker='o') ax[1].semilogy(linear_xvals, log_yfit, c='r', ls='--', lw=2.0) if save == True: fig.savefig(cf.anal_dir + 'growth_rate_energy.png') plt.close('all') else: plt.show() if normalized_output == True: return normalized_gr else: return gamma
def straight_line_fit_old(save=True, normalize_output=True, normalize_time=False, normfit_min=0.2, normfit_max=0.6, plot_growth=True, plot_LT=True, growth_only=True, klim=None, glim=0.25): ''' To do: Check units. Get growth rate from amplitude only? How to do across space -- Is wave power averaged/summed across space analogous to a single point? Or do I have to do single point? -- How to calculate growth rate of energy summed across space? -- Start with the simple and go from there. Saturation amplitudes have to match too? -- How do the Winske saturation amplitudes look? It might just be the Fu thing being fucky. Idea : Sliding window for growth rates, calculate with 5 points and created instantaneous GR timeseries Why did all the growths turn green? ''' if normalize_time == True: tfac = cf.gyfreq tlab = '$t \Omega_H$' else: tfac = 1.0 tlab = 'Time (s)' print('Calculating growth rate...') ftime, by = cf.get_array('By') ftime, bz = cf.get_array('Bz') bt = np.sqrt(by ** 2 + bz ** 2) btn = np.square(bt[:, :]).sum(axis=1) / cf.B_eq ** 2 mu0 = (4e-7) * np.pi U_B = 0.5 / mu0 * np.square(bt[:, :]).sum(axis=1) * cf.dx if plot_growth == True: max_idx = np.argmax(U_B) st = int(normfit_min * max_idx) en = int(normfit_max * max_idx) # Data to fit straight line to linear_xvals = ftime[st:en] linear_yvals = np.log(U_B[st:en]) gradient, y_intercept = np.polyfit(linear_xvals, linear_yvals, 1) # Calculate growth rate and normalize H to cyclotron frequency gamma = 0.5*gradient normalized_gr = gamma / cf.gyfreq # Create line data to plot what we fitted linear_yfit = gradient * linear_xvals * tfac + y_intercept # Returns y-values on log sscale log_yfit = np.exp(linear_yfit) # Convert to linear values to use with semilogy() else: gamma = np.nan normalized_gr = np.nan # Plot showing magnetic field and energy with growth rate line superposed plt.ioff() fig, ax = plt.subplots(nrows=2, figsize=(15, 10), sharex=True) ofs = 5 ax[0].set_title('Growth Rate :: $\gamma / \Omega_H$ = %.4f' % normalized_gr, fontsize=20) ax[0].plot(ftime*tfac, btn) ax[0].set_ylabel(r'$\frac{B^2}{B_0^2}$', rotation=0, labelpad=30, fontsize=18) ax[0].set_xlim(0, tfac*ftime[-1]) ax[0].set_ylim(btn[ofs], None) # Plot energy log scale ax[1].semilogy(ftime*tfac, btn) ax[1].set_xlabel(tlab, fontsize=18) ax[1].set_ylabel(r'$\log_{10} \left(\frac{B^2}{B_0^2}\right)$', rotation=0, labelpad=30, fontsize=18) ax[1].set_xlim(0, tfac*ftime[-1]) ax[1].set_ylim(U_B[ofs], None) if plot_growth == True: # Mark growth rate indicators ax[1].scatter(tfac*ftime[max_idx], U_B[max_idx], c='r', s=20, marker='x') ax[1].scatter(tfac*ftime[st] , U_B[st], c='r', s=20, marker='o') ax[1].scatter(tfac*ftime[en] , U_B[en], c='r', s=20, marker='o') ax[1].semilogy(linear_xvals, log_yfit, c='r', ls='--', lw=2.0) if plot_LT == True: # Calculate linear growth rates from simulation to compare # This could go into calculating gamma(k) later dk = 1. / (cf.NX * cf.dx) k = np.arange(0, 1. / (2*cf.dx), dk) * 2*np.pi k_vals, CPDR_solns, WPDR_solns, HPDR_solns = disp.get_linear_dispersion_from_sim(k, zero_cold=False) k_vals *= 3e8 / cf.wpi CPDR_solns *= 2*np.pi / cf.gyfreq WPDR_solns *= 2*np.pi / cf.gyfreq HPDR_solns *= 2*np.pi / cf.gyfreq # Comparison of growth rate to linear theory clr = ['r', 'g', 'b'] fig0, axes = plt.subplots(3, figsize=(15, 10), sharex=True) axes[0].set_title('Theoretical growth rates (Dashed: Simulation GR)') for ii in range(CPDR_solns.shape[1]): axes[0].plot(k_vals, CPDR_solns[:, ii].imag, c=clr[ii], label='Cold') axes[1].plot(k_vals, WPDR_solns[:, ii].imag, c=clr[ii], label='Warm') axes[2].plot(k_vals, HPDR_solns[:, ii].imag, c=clr[ii], label='Hot') for ax in axes: if np.isnan(normalized_gr) == False: ax.axhline(normalized_gr, ls='--', c='k', alpha=0.5) ax.set_xlim(0, klim) ax.legend() if growth_only == True: ax.set_ylim(0, None) if ax.get_ylim()[1] > glim: ax.set_ylim(0, glim) ax.set_ylabel('$\gamma$', rotation=0) axes[-1].set_xlabel('$kc/\omega_{pi}$') if save == True: fig.savefig( cf.anal_dir + 'growth_rate_energy.png') fig0.savefig(cf.anal_dir + 'growth_rate_energy_LT.png') plt.close('all') else: plt.show() return