def numerical_loop(real_time, pos, vel, Ie, W_elec, idx, B, E_int, q_dens, Ji, Ve, Te, DT, max_inc, data_iter, ch_flag): ''' Does the number crunching for a short snippet. Logs number of time variable changes in ch_flag as powers of 2 (-1 = half, 2 = 4 times slower) Array values are mutable: Don't have to be returned. Only integer values ''' qq = 0 while qq < data_iter: # Check timestep used for this iteration vel, qq, DT, max_inc, data_iter, ch_flag = aux.check_timestep( qq, DT, pos, vel, B, E_int, q_dens, Ie, W_elec, max_inc, data_iter, idx) # Add timestep to counter real_time += DT # Main loop pos, vel, Ie, W_elec, q_dens_adv, Ji = particles.advance_particles_and_moments( pos, vel, Ie, W_elec, idx, B, E_int, DT) q_dens = 0.5 * (q_dens + q_dens_adv) B = fields.push_B(B, E_int, DT) E_half, Ve, Te = fields.calculate_E(B, Ji, q_dens) q_dens = q_dens_adv.copy() # Predictor-Corrector: Advance fields to start of next timestep E_int, B = fields.predictor_corrector(B, E_int, E_half, pos, vel, q_dens_adv, Ie, W_elec, idx, DT) # Increment loop variable qq += 1 return DT, ch_flag, max_inc, data_iter, real_time
def predictor_corrector(B, E_int, E_half, pos, vel, q_dens, Ie, W_elec, idx, DT): ''' Isolated predictor-corrector method for easy debugging/coding. Predicts the electric and magnetic fields at the full timestep by using a pretend push of the particles. Computationally expensive, but accurate. INPUT: B -- Magnetic field at N + 1/2 E_int -- Electric field at N E_half -- Electric field at N + 1/2 pos -- Particle positions at N + 1 vel -- Particle velocities at N + 1/2 q_dens_adv -- Charge density at N + 1 (advanced density) Ji -- Current density at N + 1/2 Ie -- Nearest nodes for each particle W_elec -- Node weightings for each particle OUTPUT: B -- The magnetic field at N + 1 E -- The electric field at N + 1 Note: Because position and velocity advance subroutines depend on directly modifying previous values, they must be copied and restored in order to ensure they don't actually move. ''' E_pred = 2.0 * E_half - 1.0 * E_int B_pred = push_B(B, E_pred, DT) P, V, I, W, Q, J = advance_particles_and_moments(pos.copy(), vel.copy(), Ie.copy(), W_elec.copy(), idx, B_pred, E_pred, DT, pc=1) q_dens = 0.5 * (q_dens + Q) B_pred = push_B(B_pred, E_pred, DT) E_pred, Ve, Te = calculate_E(B_pred, J, q_dens) E_corr = 0.5 * (E_half + E_pred) B_corr = push_B(B, E_corr, DT) return E_corr, B_corr
def main_loop(pos, vel, idx, Ie, W_elec, Ib, W_mag, Ep, Bp, v_prime, S, T,temp_N, \ B, E_int, E_half, q_dens, q_dens_adv, Ji, ni, nu, mp_flux, \ Ve, Te, Te0, temp3De, temp3Db, temp1D, old_particles, old_fields,\ B_damping_array, E_damping_array, qq, DT, max_inc, part_save_iter, field_save_iter): ''' Main loop separated from __main__ function, since this is the actual computation bit. Could probably be optimized further, but I wanted to njit() it. The only reason everything's not njit() is because of the output functions. Future: Come up with some way to loop until next save point Thoughts: declare a variable steps_to_go. Output all time variables at return to resync everything, and calculate steps to next stop. If no saves, steps_to_go = max_inc ''' # Check timestep qq, DT, max_inc, part_save_iter, field_save_iter, damping_array \ = check_timestep(pos, vel, B, E_int, q_dens, Ie, W_elec, Ib, W_mag, temp3De, Ep, Bp, v_prime, S, T,temp_N,\ qq, DT, max_inc, part_save_iter, field_save_iter, idx, B_damping_array) # ============================================================================= # # Check number of spare particles every 25 steps # if qq%25 == 0 and particle_open == 1: # num_spare = (idx < 0).sum() # if num_spare < inject_rate.sum() * DT * 5.0: # # Change this to dynamically expand particle arrays later on (adding more particles) # print('WARNING :: Less than 5 time increments of spare particles remaining. Widening arrays...') # pos, vel, idx, Ie, W_elec, Ib, W_mag, Ep, Bp, v_prime, S, T, temp_N, old_particles = \ # increase_particle_array_size(pos, vel, idx, Ie, W_elec, Ib, W_mag, Ep, Bp, v_prime, # S, T, temp_N, old_particles) # ============================================================================= # Move particles, collect moments particles.advance_particles_and_moments(pos, vel, Ie, W_elec, Ib, W_mag, idx, Ep, Bp, v_prime, S, T,temp_N,\ B, E_int, DT, q_dens_adv, Ji, ni, nu, mp_flux) # Average N, N + 1 densities (q_dens at N + 1/2) q_dens *= 0.5 q_dens += 0.5 * q_dens_adv # Push B from N to N + 1/2 fields.push_B(B, E_int, temp3Db, DT, qq, B_damping_array, half_flag=1) # Calculate E at N + 1/2 fields.calculate_E(B, Ji, q_dens, E_half, Ve, Te, Te0, temp3De, temp3Db, temp1D, E_damping_array) ################################### ### PREDICTOR CORRECTOR SECTION ### ################################### # Store old values mp_flux_old = mp_flux.copy() old_particles[0, :] = pos old_particles[1:4, :] = vel old_particles[4, :] = Ie old_particles[5:8, :] = W_elec old_particles[8, :] = idx old_fields[:, 0:3] = B old_fields[:NC, 3:6] = Ji old_fields[:NC, 6:9] = Ve old_fields[:NC, 9] = Te # Predict fields E_int *= -1.0 E_int += 2.0 * E_half fields.push_B(B, E_int, temp3Db, DT, qq, B_damping_array, half_flag=0) # Advance particles to obtain source terms at N + 3/2 particles.advance_particles_and_moments(pos, vel, Ie, W_elec, Ib, W_mag, idx, Ep, Bp, v_prime, S, T,temp_N,\ B, E_int, DT, q_dens, Ji, ni, nu, mp_flux, pc=1) q_dens *= 0.5 q_dens += 0.5 * q_dens_adv # Compute predicted fields at N + 3/2 fields.push_B(B, E_int, temp3Db, DT, qq + 1, B_damping_array, half_flag=1) fields.calculate_E(B, Ji, q_dens, E_int, Ve, Te, Te0, temp3De, temp3Db, temp1D, E_damping_array) # Determine corrected fields at N + 1 E_int *= 0.5 E_int += 0.5 * E_half # Restore old values: [:] allows reference to same memory (instead of creating new, local instance) pos[:] = old_particles[0, :] vel[:] = old_particles[1:4, :] Ie[:] = old_particles[4, :] W_elec[:] = old_particles[5:8, :] idx[:] = old_particles[8, :] B[:] = old_fields[:, 0:3] Ji[:] = old_fields[:NC, 3:6] Ve[:] = old_fields[:NC, 6:9] Te[:] = old_fields[:NC, 9] fields.push_B(B, E_int, temp3Db, DT, qq, B_damping_array, half_flag=0) # Advance the original B q_dens[:] = q_dens_adv mp_flux = mp_flux_old.copy() return qq, DT, max_inc, part_save_iter, field_save_iter
def main_loop(pos, vel, idx, Ie, W_elec, Ib, W_mag, \ B, E_int, E_half, q_dens, q_dens_adv, Ji, ni, nu, \ Ve, Te, temp3D, temp3D2, temp1D, old_particles, old_fields,\ qq, DT, max_inc, part_save_iter, field_save_iter): ''' Main loop separated from __main__ function, since this is the actual computation bit. Could probably be optimized further, but I wanted to njit() it. The only reason everything's not njit() is because of the output functions. Future: Come up with some way to loop until next save point Thoughts: declare a variable steps_to_go. Output all time variables at return to resync everything, and calculate steps to next stop. If no saves, steps_to_go = max_inc ''' # Check timestep qq, DT, max_inc, part_save_iter, field_save_iter \ = check_timestep(pos, vel, B, E_int, q_dens, Ie, W_elec, Ib, W_mag, temp3D, \ qq, DT, max_inc, part_save_iter, field_save_iter, idx) # Move particles, collect moments particles.advance_particles_and_moments(pos, vel, Ie, W_elec, Ib, W_mag, idx, \ B, E_int, DT, q_dens_adv, Ji, ni, nu, temp1D) # Average N, N + 1 densities (q_dens at N + 1/2) q_dens *= 0.5 q_dens += 0.5 * q_dens_adv # Push B from N to N + 1/2 fields.push_B(B, E_int, temp3D, DT, qq, half_flag=1) # Calculate E at N + 1/2 fields.calculate_E(B, Ji, q_dens, E_half, Ve, Te, temp3D, temp3D2, temp1D) ################################### ### PREDICTOR CORRECTOR SECTION ### ################################### # Store old values old_particles[0, :] = pos old_particles[1:4, :] = vel old_particles[4, :] = Ie old_particles[5:8, :] = W_elec old_fields[:, 0:3] = B old_fields[:, 3:6] = Ji old_fields[:, 6:9] = Ve old_fields[:, 9] = Te # Predict fields E_int *= -1.0 E_int += 2.0 * E_half fields.push_B(B, E_int, temp3D, DT, qq, half_flag=0) # Advance particles to obtain source terms at N + 3/2 particles.advance_particles_and_moments(pos, vel, Ie, W_elec, Ib, W_mag, idx, \ B, E_int, DT, q_dens, Ji, ni, nu, temp1D, pc=1) q_dens *= 0.5 q_dens += 0.5 * q_dens_adv # Compute predicted fields at N + 3/2 fields.push_B(B, E_int, temp3D, DT, qq + 1, half_flag=1) fields.calculate_E(B, Ji, q_dens, E_int, Ve, Te, temp3D, temp3D2, temp1D) # Determine corrected fields at N + 1 E_int *= 0.5 E_int += 0.5 * E_half # Restore old values: [:] allows reference to same memory (instead of creating new, local instance) pos[:] = old_particles[0, :] vel[:] = old_particles[1:4, :] Ie[:] = old_particles[4, :] W_elec[:] = old_particles[5:8, :] B[:] = old_fields[:, 0:3] Ji[:] = old_fields[:, 3:6] Ve[:] = old_fields[:, 6:9] Te[:] = old_fields[:, 9] fields.push_B(B, E_int, temp3D, DT, qq, half_flag=0) # Advance the original B q_dens[:] = q_dens_adv return qq, DT, max_inc, part_save_iter, field_save_iter
qq = 0 while qq < max_inc: # Check timestep vel, qq, DT, max_inc, data_iter, ch_flag \ = aux.check_timestep(qq, DT, pos, vel, B, E_int, q_dens, Ie, W_elec, max_inc, data_iter, idx) if ch_flag == 1: print 'Timestep halved. Syncing particle velocity with DT = {}'.format( DT) elif ch_flag == 2: print 'Timestep Doubled. Syncing particle velocity with DT = {}'.format( DT) # Main loop pos, vel, Ie, W_elec, q_dens_adv, Ji = particles.advance_particles_and_moments( pos, vel, Ie, W_elec, idx, B, E_int, DT) q_dens = 0.5 * (q_dens + q_dens_adv) B = fields.push_B(B, E_int, DT) E_half, Ve, Te = fields.calculate_E(B, Ji, q_dens) q_dens = q_dens_adv.copy() # Predictor-Corrector: Advance fields to start of next timestep E_int, B = fields.predictor_corrector(B, E_int, E_half, pos, vel, q_dens_adv, Ie, W_elec, idx, DT) if generate_data == 1: if qq % data_iter == 0: save.save_data(DT, data_iter, qq, pos, vel, Ji, E_int, B, Ve, Te, q_dens) if (qq + 1) % 25 == 0:
if True: qq = 1 sim_time = DT print('Starting main loop...') while qq < max_inc: ########################### ####### MAIN LOOP ######### ########################### # Check timestep qq, DT, max_inc, part_save_iter, field_save_iter, damping_array \ = aux.check_timestep(pos, vel, B, E_int, q_dens, Ie, W_elec, Ib, W_mag, temp3De, Ep, Bp, v_prime, S, T,temp_N,\ qq, DT, max_inc, part_save_iter, field_save_iter, idx, B_damping_array) # Move particles, collect moments, delete or inject new particles particles.advance_particles_and_moments(pos, vel, Ie, W_elec, Ib, W_mag, idx, Ep, Bp, v_prime, S, T,temp_N,\ B, E_int, DT, q_dens_adv, Ji, ni, nu, flux_rem) # Average N, N + 1 densities (q_dens at N + 1/2) q_dens *= 0.5 q_dens += 0.5 * q_dens_adv if disable_waves == False: # Push B from N to N + 1/2 fields.push_B(B, E_int, temp3Db, DT, qq, B_damping_array, half_flag=1)
def main_loop(pos, vel, idx, Ie, W_elec, Ib, W_mag, Ep, Bp, v_prime, S, T,temp_N, \ B, E_int, E_half, q_dens, q_dens_adv, Ji, ni, nu, \ Ve, Te, Te0, temp3De, temp3Db, temp1D, old_particles, old_fields,\ B_damping_array, E_damping_array, qq, DT, max_inc, part_save_iter, field_save_iter): ''' Main loop separated from __main__ function, since this is the actual computation bit. Could probably be optimized further, but I wanted to njit() it. The only reason everything's not njit() is because of the output functions. Future: Come up with some way to loop until next save point Thoughts: declare a variable steps_to_go. Output all time variables at return to resync everything, and calculate steps to next stop. If no saves, steps_to_go = max_inc ''' NC = const.NC # Check timestep #print('MAIN Checking timestep') qq, DT, max_inc, part_save_iter, field_save_iter, B_damping_array, E_damping_array \ = check_timestep(pos, vel, B, E_int, q_dens, Ie, W_elec, Ib, W_mag, temp3De, Ep, Bp, \ v_prime, S, T,temp_N, qq, DT, max_inc, part_save_iter, \ field_save_iter, idx, B_damping_array, E_damping_array) # Move particles, collect moments #print('MAIN Advancing particles/moments') particles.advance_particles_and_moments(pos, vel, Ie, W_elec, Ib, W_mag, idx, Ep, Bp, v_prime, S, T,temp_N,\ B, E_int, DT, q_dens_adv, Ji, ni, nu) # Average N, N + 1 densities (q_dens at N + 1/2) #print('MAIN Averaging density') q_dens *= 0.5 q_dens += 0.5 * q_dens_adv if const.disable_waves == 0: # Push B from N to N + 1/2 fields.push_B(B, E_int, temp3Db, DT, qq, B_damping_array, half_flag=1) # Calculate E at N + 1/2 fields.calculate_E(B, Ji, q_dens, E_half, Ve, Te, Te0, temp3De, temp3Db, temp1D, E_damping_array, qq, DT, half_flag=1) ################################### ### PREDICTOR CORRECTOR SECTION ### ################################### # Store old values old_particles[0:3, :] = pos old_particles[3:6, :] = vel old_particles[6, :] = Ie old_particles[7:10, :] = W_elec old_particles[10, :] = idx old_fields[:, 0:3] = B old_fields[:NC, 3:6] = Ji old_fields[:NC, 6:9] = Ve old_fields[:NC, 9] = Te # Predict fields E_int *= -1.0 E_int += 2.0 * E_half fields.push_B(B, E_int, temp3Db, DT, qq, B_damping_array, half_flag=0) # Advance particles to obtain source terms at N + 3/2 particles.advance_particles_and_moments(pos, vel, Ie, W_elec, Ib, W_mag, idx, Ep, Bp, v_prime, S, T,temp_N,\ B, E_int, DT, q_dens, Ji, ni, nu, pc=1) # Average N + 1, N + 2 densities (q_dens at N + 3/2) q_dens *= 0.5 q_dens += 0.5 * q_dens_adv # Compute predicted fields at N + 3/2 fields.push_B(B, E_int, temp3Db, DT, qq + 1, B_damping_array, half_flag=1) fields.calculate_E(B, Ji, q_dens, E_int, Ve, Te, Te0, temp3De, temp3Db, temp1D, E_damping_array, qq + 1, DT, half_flag=1) # Determine corrected fields at N + 1 E_int *= 0.5 E_int += 0.5 * E_half # Restore old values: [:] allows reference to same memory (instead of creating new, local instance) pos[:] = old_particles[0:3, :] vel[:] = old_particles[3:6, :] Ie[:] = old_particles[6, :] W_elec[:] = old_particles[7:10, :] idx[:] = old_particles[10, :] B[:] = old_fields[:, 0:3] Ji[:] = old_fields[:NC, 3:6] Ve[:] = old_fields[:NC, 6:9] Te[:] = old_fields[:NC, 9] fields.push_B(B, E_int, temp3Db, DT, qq, B_damping_array, half_flag=0) # Advance the original B q_dens[:] = q_dens_adv return qq, DT, max_inc, part_save_iter, field_save_iter