def collect_moments(pos, vel, Ie, W_elec, idx, ni, nu_plus, nu_minus, rho, J_minus, J_plus, L, G, dt): ''' Moment collection and position advance function. INPUT: pos -- Particle positions (x) vel -- Particle 3-velocities Ie -- Particle leftmost to nearest E-node W_elec -- Particle TSC weighting across nearest, left, and right nodes idx -- Particle species identifier DT -- Timestep for position advance OUTPUT: pos -- Advanced particle positions Ie -- Updated leftmost to nearest E-nodes W_elec -- Updated TSC weighting coefficients rho -- Charge density at +0.5 timestep J_plus -- Current density at +0.5 timestep J_minus -- Current density at initial time (J0) G -- "Gamma" MHD variable for current advance L -- "Lambda" MHD variable for current advance ''' ni *= 0.0 rho *= 0.0 nu_minus *= 0.0 nu_plus *= 0.0 J_minus *= 0.0 J_plus *= 0.0 L *= 0.0 G *= 0.0 deposit_velocity_moments(vel, Ie, W_elec, idx, nu_minus) particles.position_update(pos, vel, Ie, W_elec, dt) deposit_both_moments(pos, vel, Ie, W_elec, idx, ni, nu_plus) if smooth_sources == 1: for jj in range(Nj): ni[:, jj] = smooth(ni[:, jj]) for kk in range(3): nu_plus[:, jj, kk] = smooth(nu_plus[:, jj, kk]) nu_minus[:, jj, kk] = smooth(nu_minus[:, jj, kk]) for jj in range(Nj): rho += ni[:, jj] * n_contr[jj] * charge[jj] L += ni[:, jj] * n_contr[jj] * charge[jj]**2 / mass[jj] for kk in range(3): J_minus[:, kk] += nu_minus[:, jj, kk] * n_contr[jj] * charge[jj] J_plus[:, kk] += nu_plus[:, jj, kk] * n_contr[jj] * charge[jj] G[:, kk] += nu_plus[:, jj, kk] * n_contr[jj] * charge[jj]**2 / mass[jj] for ii in range(rho.shape[0]): if rho[ii] < min_dens * ne * q: rho[ii] = min_dens * ne * q return
def init_collect_moments(part, DT): '''Primary moment collection and position advance function. INPUT: part -- Particle array weights -- Weights array DT -- Timestep init -- Flag to indicate if this is the first time the function is called** ** At first initialization of this function, J- is equivalent to J0 and rho_0 is initial density (usually not returned) ''' size = NX + 2 rho_0 = np.zeros(size) rho = np.zeros(size) J_plus = np.zeros((size, 3)) J_minus = np.zeros((size, 3)) L = np.zeros(size) G = np.zeros((size, 3)) ni_init, nu_minus = collect_both_moments(part) part = particles.position_update(part, DT) ni, nu_plus = collect_both_moments(part) if smooth_sources == 1: for jj in range(Nj): ni[:, jj] = smooth(ni[:, jj]) for kk in range(3): nu_plus[:, jj, kk] = smooth(nu_plus[:, jj, kk]) nu_minus[:, jj, kk] = smooth(nu_minus[:, jj, kk]) for jj in range(Nj): rho_0 += ni_init[:, jj] * charge[jj] rho += ni[:, jj] * charge[jj] L += ni[:, jj] * charge[jj]**2 / mass[jj] for kk in range(3): J_minus[:, kk] += nu_minus[:, jj, kk] * charge[jj] J_plus[:, kk] += nu_plus[:, jj, kk] * charge[jj] G[:, kk] += nu_plus[:, jj, kk] * charge[jj]**2 / mass[jj] return part, rho_0, rho, J_plus, J_minus, G, L
def animate_moving_weight(): fig = plt.figure(figsize=(12, 8)) ax = fig.add_subplot(1,1,1) x = np.arange(const.NX + 3) dt = 0.1 vel = np.array([[0.3 * const.dx / dt], [ 0.], [ 0.]]) position = np.array([0.0]) E_nodes = (np.arange(const.NX + 3) - 0.5) #* const.dx B_nodes = (np.arange(const.NX + 3) - 1.0) #* const.dx for ii in range(150): dns_test = np.zeros(const.NX + 3) pos, left_nodes, weights = particles.position_update(position, vel, dt) for jj in range(3): dns_test[left_nodes + jj] = weights[jj] y = dns_test ax.clear() ax.plot(x, y) ax.set_xlim(-1.5, const.NX + 2) ax.set_ylim(0, 1.5) ax.text(1, 1.4, 'Total Weight: {}'.format(dns_test.sum())) ax.scatter(pos/const.dx, 1.0, c='r') for kk in range(const.NX + 3): ax.axvline(E_nodes[kk], linestyle='--', c='r', alpha=0.2) ax.axvline(B_nodes[kk], linestyle='--', c='b', alpha=0.2) ax.axvline(const.xmin/const.dx, linestyle='-', c='k', alpha=0.2) ax.axvline(const.xmax/const.dx, linestyle='-', c='k', alpha=0.2) plt.pause(0.05) plt.show()
def check_timestep(qq, DT, part, B, E, dns, maxtime, data_dump_iter, plot_dump_iter): max_V = np.max(part[3, :]) #k_wave = np.pi / dx B_cent = np.zeros((NX + 2, 3)) for ii in range(3): B_cent[1:-1, ii] = 0.5 * (B[:-1, ii] + B[1:, ii]) B_tot = np.sqrt(B_cent[:, 0]**2 + B_cent[:, 1]**2 + B_cent[:, 2]**2) high_rat = np.max(charge / mass) gyfreq = high_rat * max(abs(B_tot)) / (2 * np.pi) # Gyrofrequency #elecfreq = high_rat*max(abs(E[:, 0] / max_V)) # Electron acceleration "frequency" #dispfreq = (k_wave ** 2) * np.max(B_tot / (mu0 * dns)) # Dispersion frequency ion_ts = orbit_res / gyfreq # Timestep to resolve gyromotion #acc_ts = orbit_res / elecfreq # Timestep to resolve electric field acceleration #dis_ts = orbit_res / dispfreq # Timestep to resolve magnetic field dispersion vel_ts = 0.60 * dx / max_V # Timestep to satisfy CFL condition: Fastest particle doesn't traverse more than 'half' a cell in one time step # Slightly larger than half to stop automatically halving DT at start DT_new = min(ion_ts, vel_ts) # Smallest of the two (four, later) change_flag = 0 if DT_new < DT: part = particles.position_update( part, -0.5 * DT) # Roll back particle position before halving timestep change_flag = 1 DT *= 0.5 maxtime *= 2 qq *= 2 if plot_dump_iter != None: plot_dump_iter *= 2 if data_dump_iter != None: data_dump_iter *= 2 return part, qq, DT, maxtime, data_dump_iter, plot_dump_iter, change_flag
def check_timestep(qq, DT, pos, vel, Ie, W_elec, B, E, dns, max_inc, part_save_iter, field_save_iter, subcycles): max_Vx = vel[0, :].max() max_V = vel.max() B_cent = interpolate_to_center_cspline3D(B) B_tot = np.sqrt(B_cent[:, 0]**2 + B_cent[:, 1]**2 + B_cent[:, 2]**2) high_rat = const.qm_ratios.max() gyfreq = high_rat * np.abs(B_tot).max() / (2 * np.pi) ion_ts = const.orbit_res * 1. / gyfreq if E.max() != 0: elecfreq = high_rat * (np.abs(E[:, 0] / max_V)).max() freq_ts = const.freq_res / elecfreq else: freq_ts = ion_ts vel_ts = 0.75 * const.dx / max_Vx DT_part = min(freq_ts, vel_ts, ion_ts) # Reduce timestep change_flag = 0 if DT_part < 0.9 * DT: particles.position_update(pos, vel, Ie, W_elec, -0.5 * DT) change_flag = 1 DT *= 0.5 max_inc *= 2 qq *= 2 part_save_iter *= 2 field_save_iter *= 2 print('Timestep halved. Syncing particle velocity/position with DT =', DT) # Increase timestep (only if previously decreased, or everything's even - saves wonky cuts) elif DT_part >= 4.0 * DT and qq % 2 == 0 and part_save_iter % 2 == 0 and field_save_iter % 2 == 0 and max_inc % 2 == 0: particles.position_update(pos, vel, Ie, W_elec, -0.5 * DT) change_flag = 1 DT *= 2.0 max_inc //= 2 qq //= 2 part_save_iter //= 2 field_save_iter //= 2 print('Timestep doubled. Syncing particle velocity/position with DT =', DT) if const.adaptive_subcycling == 1: k_max = np.pi / const.dx dispfreq = (k_max** 2) * (B_tot / (const.mu0 * dns)).max() # Dispersion frequency dt_sc = const.freq_res / dispfreq new_subcycles = int(DT / dt_sc + 1) if subcycles < 0.75 * new_subcycles: subcycles *= 2 print('Number of subcycles per timestep doubled to', subcycles) if subcycles > 3.0 * new_subcycles and subcycles % 2 == 0: subcycles //= 2 print('Number of subcycles per timestep halved to', subcycles) if subcycles >= 1000: subcycles = 1000 print('Maxmimum number of subcycles reached - she gon\' blow') return qq, DT, max_inc, part_save_iter, field_save_iter, change_flag, subcycles
def check_timestep(qq, DT, pos, vel, B, E, dns, max_inc, data_iter, plot_iter, subcycles): max_Vx = np.max(vel[0, :]) max_V = np.max(vel) k_max = np.pi / const.dx B_cent = interpolate_to_center_cspline3D(B) B_tot = np.sqrt(B_cent[:, 0]**2 + B_cent[:, 1]**2 + B_cent[:, 2]**2) high_rat = (const.charge / const.mass).max() gyfreq = high_rat * np.abs(B_tot).max() / (2 * np.pi) ion_ts = const.orbit_res * 1. / gyfreq if E.max() != 0: elecfreq = high_rat * max(abs( E[:, 0] / max_V)) # Electron acceleration "frequency" freq_ts = const.freq_res / elecfreq else: freq_ts = ion_ts vel_ts = 0.75 * const.dx / max_Vx # Timestep to satisfy CFL condition: Fastest particle doesn't traverse more than 'half' a cell in one time step DT_part = min(freq_ts, vel_ts, ion_ts) # Smallest of the particle conditions change_flag = 0 if DT_part < 0.9 * DT: pos, Ie, W_elec = particles.position_update( pos, vel, -0.5 * DT) # Roll back particle position before halving timestep change_flag = 1 DT *= 0.5 max_inc *= 2 qq *= 2 if plot_iter != None: plot_iter *= 2 if data_iter != None: data_iter *= 2 elif DT_part >= 4.0 * DT and qq % 2 == 0: pos, Ie, W_elec = particles.position_update( pos, vel, -0.5 * DT) # Roll back particle position before halving timestep change_flag = 2 DT *= 2.0 max_inc = ( max_inc + 1 ) / 2 # This ensures timesteps aren't cut off by halving (more likely to be added, which isn't bad) qq /= 2 if plot_iter == None or plot_iter == 1: plot_iter = 1 else: plot_iter /= 2 if data_iter == None or data_iter == 1: data_iter = 1 else: data_iter /= 2 if const.adaptive_subcycling == True: dispfreq = (k_max** 2) * (B_tot / (const.mu0 * dns)).max() # Dispersion frequency dt_sc = const.freq_res * 1. / dispfreq new_subcycles = int(DT / dt_sc + 1) if subcycles < 0.75 * new_subcycles: subcycles *= 2 print 'Number of subcycles per timestep doubled to {}'.format( subcycles) if subcycles > 3.0 * new_subcycles: subcycles = (subcycles + 1) / 2 print 'Number of subcycles per timestep halved to {}'.format( subcycles) if subcycles > 1000: const.adaptive_subcycling = False subcycles = 1000 else: pass return pos, qq, DT, max_inc, data_iter, plot_iter, change_flag, subcycles
def check_timestep(qq, DT, pos, vel, B, E, dns, max_inc, part_save_iter, field_save_iter, subcycles): max_Vx = np.max(vel[0, :]) max_V = np.max(vel) B_cent = interpolate_to_center_cspline3D(B) B_tot = np.sqrt(B_cent[:, 0] ** 2 + B_cent[:, 1] ** 2 + B_cent[:, 2] ** 2) high_rat = (const.charge/const.mass).max() gyfreq = high_rat * np.abs(B_tot).max() / (2 * np.pi) ion_ts = const.orbit_res * 1./gyfreq if E.max() != 0: elecfreq = high_rat*max(abs(E[:, 0] / max_V)) # Electron acceleration "frequency" freq_ts = const.freq_res / elecfreq else: freq_ts = ion_ts vel_ts = 0.75*const.dx / max_Vx # Timestep to satisfy CFL condition: Fastest particle doesn't traverse more than 'half' a cell in one time step DT_part = min(freq_ts, vel_ts, ion_ts) # Smallest of the particle conditions # Reduce timestep if DT_part < 0.9*DT: pos, Ie, W_elec = particles.position_update(pos, vel, -0.5*DT) change_flag = 1 DT *= 0.5 max_inc *= 2 qq *= 2 part_save_iter *= 2 field_save_iter *= 2 # Increase timestep (only if previously decreased, or everything's even - saves wonky cuts) elif DT_part >= 4.0*DT and qq%2 == 0 and part_save_iter%2 == 0 and field_save_iter%2 == 0 and max_inc%2 == 0: pos, Ie, W_elec = particles.position_update(pos, vel, -0.5*DT) change_flag = 2 DT *= 2.0 max_inc //= 2 qq //= 2 part_save_iter //= 2 field_save_iter //= 2 else: change_flag = 0 if const.adaptive_subcycling == True: k_max = np.pi / const.dx dispfreq = (k_max ** 2) * (B_tot / (const.mu0 * dns)).max() # Dispersion frequency dt_sc = const.freq_res * 1./dispfreq new_subcycles = int(DT / dt_sc + 1) if subcycles < 0.75*new_subcycles: subcycles *= 2 print('Number of subcycles per timestep doubled to {}'.format(subcycles)) if subcycles > 3.0*new_subcycles and subcycles%2 == 0: subcycles //= 2 print('Number of subcycles per timestep halved to {}'.format(subcycles)) if subcycles > 1000: const.adaptive_subcycling = False subcycles = 1000 print('Maxmimum number of subcycles reached - potentially unstable solutions...') return pos, qq, DT, max_inc, part_save_iter, field_save_iter, change_flag, subcycles
def init_collect_moments(pos, vel, Ie, W_elec, idx, DT): '''Moment collection and position advance function. Specifically used at initialization or after timestep synchronization. INPUT: pos -- Particle positions (x) vel -- Particle 3-velocities Ie -- Particle leftmost to nearest E-node W_elec -- Particle TSC weighting across nearest, left, and right nodes idx -- Particle species identifier DT -- Timestep for position advance OUTPUT: pos -- Advanced particle positions Ie -- Updated leftmost to nearest E-nodes W_elec -- Updated TSC weighting coefficients rho_0 -- Charge density at initial time (p0) rho -- Charge density at +0.5 timestep J_init -- Current density at initial time (J0) J_plus -- Current density at +0.5 timestep G -- "Gamma" MHD variable for current advance : Current-like L -- "Lambda" MHD variable for current advance : Charge-like ''' size = NX + 3 rho_0 = np.zeros(size) rho = np.zeros(size) J_plus = np.zeros((size, 3)) J_init = np.zeros((size, 3)) L = np.zeros(size) G = np.zeros((size, 3)) ni_init, nu_init = deposit_both_moments( pos, vel, Ie, W_elec, idx) # Collects sim_particles/cell/species pos, Ie, W_elec = particles.position_update(pos, vel, DT) ni, nu_plus = deposit_both_moments(pos, vel, Ie, W_elec, idx) if smooth_sources == 1: for jj in range(Nj): ni[:, jj] = smooth(ni[:, jj]) for kk in range(3): nu_plus[:, jj, kk] = smooth(nu_plus[:, jj, kk]) nu_init[:, jj, kk] = smooth(nu_init[:, jj, kk]) for jj in range(Nj): rho_0 += ni_init[:, jj] * n_contr[jj] * charge[jj] rho += ni[:, jj] * n_contr[jj] * charge[jj] L += ni[:, jj] * n_contr[jj] * charge[jj]**2 / mass[jj] for kk in range(3): J_init[:, kk] += nu_init[:, jj, kk] * n_contr[jj] * charge[jj] J_plus[:, kk] += nu_plus[:, jj, kk] * n_contr[jj] * charge[jj] G[:, kk] += nu_plus[:, jj, kk] * n_contr[jj] * charge[jj]**2 / mass[jj] for ii in range(size): if rho_0[ii] < min_dens * ne * q: rho_0[ii] = min_dens * ne * q if rho[ii] < min_dens * ne * q: rho[ii] = min_dens * ne * q return pos, Ie, W_elec, rho_0, rho, J_plus, J_init, G, L