def run_sims(): radius = 0.1 electron_energy = 1000.0 I = 1e4 batch_num = 1 dI_dt = 0.0 to_kA = 1e-3 # Generate Polywell field loop_pts = 200 domain_pts = 130 loop_offset = 1.25 dom_size = 1.1 * loop_offset * radius file_name = "b_field_{}_{}_{}_{}_{}_{}".format(I * to_kA, radius, loop_offset, domain_pts, loop_pts, dom_size) file_path = os.path.join("..", "mesh_generation", "data", "radius-{}m".format(radius), "current-{}kA".format(I * to_kA), "domres-{}".format(domain_pts), file_name) b_field = InterpolatedBField(file_path, dom_pts_idx=6, dom_size_idx=8) seed = batch_num np.random.seed(seed) # Run simulations vel = np.sqrt(2.0 * electron_energy * PhysicalConstants.electron_charge / PhysicalConstants.electron_mass) num_sims = 1 for i in range(num_sims): # Define particle velocity z_unit = np.random.uniform(-1.0, 1.0) xy_plane = np.sqrt(1 - z_unit**2) phi = np.random.uniform(0.0, 2 * np.pi) velocity = np.asarray( [xy_plane * np.cos(phi), xy_plane * np.sin(phi), z_unit]) * vel # Generate particle position z_unit = np.random.uniform(-1.0, 1.0) xy_plane = np.sqrt(1 - z_unit**2) phi = np.random.uniform(0.0, 2 * np.pi) position = np.asarray([ xy_plane * np.cos(phi), xy_plane * np.sin(phi), z_unit ]) * np.random.uniform(0.0, 1.0 * radius / 16.0) # Generate particle particle = PICParticle(9.1e-31, 1.6e-19, position, velocity) t, x, y, z, v_x, v_y, v_z, final_idx = run_simulation( (b_field, particle, radius, loop_offset * radius, I, dI_dt))
def E_field_example(): """ Example E field solution :return: """ particle = PICParticle(1.6e-27, 1.6e-19, np.asarray([0.1, 0.0, 0.0]), np.asarray([0.0, 0.1, 0.0])) field = PointField(-1.6e-19, 0.1, np.zeros(3)) def B_field(x): B = np.zeros(x.shape) return B X = particle.position V = particle.velocity Q = np.asarray([particle.charge]) M = np.asarray([particle.mass]) times = np.linspace(0.0, 1, 10000) positions = np.zeros((times.shape[0], X.shape[0], X.shape[1])) velocities = np.zeros((times.shape[0], X.shape[0], X.shape[1])) for i, t in enumerate(times): if i == 0: positions[i, :, :] = X velocities[i, :, :] = V continue dt = times[i] - times[i - 1] x, v = boris_solver(field.e_field, B_field, X, V, Q, M, dt) positions[i, :, :] = x velocities[i, :, :] = v X = x V = v x = positions[:, :, 0].flatten() y = positions[:, :, 1].flatten() z = positions[:, :, 2].flatten() fig = plt.figure(figsize=(20, 10)) ax = fig.add_subplot('111', projection='3d') ax.plot(x, y, z, label='numerical') ax.set_xlabel('X') ax.set_ylabel('Y') ax.set_zlabel('Z') ax.legend(loc='best') ax.set_title("Analytic and Numerical Particle Motion") plt.show()
def E_field_example(): """ Example E field solution :return: """ particle = PICParticle(1.0, 2.0, np.asarray([1.0, 0.0, 0.0]), np.asarray([0.0, 1.0, 0.0])) E = np.asarray([0.0, 0.0, 3.0]) times, positions = solve_E_field(particle, E, 3.0) fig = plt.figure() ax = fig.add_subplot('111', projection='3d') ax.plot(positions[:, 0], positions[:, 1], positions[:, 2]) plt.show()
def aligned_fields_examples(): """ Example of parallel fields solution :return: """ particle = PICParticle(1.0, 2.0, np.asarray([1.0, 0.0, 0.0]), np.asarray([0.0, 1.0, 0.0])) E = np.asarray([0.0, 0.0, 3.0]) B = np.asarray([0.0, 0.0, 3.0]) times, positions = solve_aligned_fields(particle, E, B, 3.0) fig = plt.figure() ax = fig.add_subplot('111', projection='3d') ax.plot(positions[:, 0], positions[:, 1], positions[:, 2]) plt.show()
t_results = [] x_results = [] y_results = [] z_results = [] dt_factors = [100.0, 10.0, 1.0, 0.1, 0.01] electron_energy = 1000.0 max_vel = np.sqrt(2.0 * electron_energy * PhysicalConstants.electron_charge / PhysicalConstants.electron_mass) for dt in dt_factors: seed = 1 np.random.seed(seed) # Define charge particle vel = np.random.uniform(low=-1.0, high=1.0, size=(3, )) * max_vel particle = PICParticle(9.1e-31, 1.6e-19, np.asarray([0.0, 0.0, 0.0]), vel) t, x, y, z = run_sim(b_field, particle, dt) t_results.append(t) x_results.append(x) y_results.append(y) z_results.append(z) fig, ax = plt.subplots(4) x_interp = scipy.interpolate.interp1d(t_results[-1], x_results[-1]) y_interp = scipy.interpolate.interp1d(t_results[-1], y_results[-1]) z_interp = scipy.interpolate.interp1d(t_results[-1], z_results[-1]) for i in range(len(dt_factors) - 1): dx = np.abs(x_interp(t_results[i]) - x_results[i]) dy = np.abs(y_interp(t_results[i]) - y_results[i])
def run_1d_electrostatic_well(radius, num_particles=int(1e4)): """ Function to run simulation - for this simulation, in a spherically symmetric potential well radius: size of potential well """ # Define particles number_density = 1e12 charge = 1.602e-19 weight = int(number_density / num_particles) pic_particle = PICParticle(3.344496935079999e-27 * weight, charge * weight, np.asarray([radius, 0.0, 0.0]), np.asarray([0.0, 0.0, 0.0])) collision_particle = ChargedParticle(3.344496935079999e-27 * weight, charge * weight) # Define fields electron_charge_density = 1e20 * PhysicalConstants.electron_charge def e_field(x): E_field = np.zeros(x.shape) for i in range(x.shape[0]): r = magnitude(x[i]) if r <= radius: e_field = electron_charge_density * r / ( 3.0 * PhysicalConstants.epsilon_0) else: e_field = electron_charge_density * radius**3 / ( 3.0 * PhysicalConstants.epsilon_0 * r**2) e_field *= -normalise(x[i]) E_field[i, :] = e_field return E_field # Define time step and final time total_V = radius**3 * electron_charge_density total_V /= 2.0 * PhysicalConstants.epsilon_0 * radius energy = total_V * pic_particle.charge velocity = np.sqrt(2 * energy / pic_particle.mass) dt = 0.01 * radius / velocity final_time = 50.0 * radius / velocity num_steps = int(final_time / dt) # Define collision model temperature = 298.3 thermal_velocity = np.sqrt(PhysicalConstants.boltzmann_constant * temperature / pic_particle.mass) c = CoulombCollision(collision_particle, collision_particle, 1.0, velocity) debye_length = PhysicalConstants.epsilon_0 * temperature debye_length /= number_density * PhysicalConstants.electron_charge**2 debye_length = np.sqrt(debye_length) coulomb_logarithm = np.log(debye_length / c.b_90) r = RelaxationProcess(c) v_K = r.kinetic_loss_stationary_frequency(number_density, temperature, velocity) print("Thermal velocity: {}".format(thermal_velocity / 3e8)) print("Peak velocity: {}".format(velocity / 3e8)) print("Debye Length: {}".format(debye_length)) print("Coulomb Logarithm: {}".format(coulomb_logarithm)) print("Kinetic Loss Time: {}".format(1.0 / v_K)) print("Simulation Time Step: {}".format(dt)) assert dt < 0.01 * 1.0 / v_K collision_model = AbeCoulombCollisionModel( num_particles, collision_particle, weight, coulomb_logarithm=coulomb_logarithm) # Set up initial conditions np.random.seed(1) X = np.zeros((num_particles, 3)) for i in range(num_particles): x_theta = np.random.uniform(0.0, 2 * np.pi) y_theta = np.random.uniform(0.0, 2 * np.pi) z_theta = np.random.uniform(0.0, 2 * np.pi) X[i, :] = rotate_3d(np.asarray([1.0, 0.0, 0.0]), np.asarray([x_theta, y_theta, z_theta])) V = np.random.normal(0.0, thermal_velocity, size=X.shape) V = np.zeros(X.shape) # Run simulation times = np.linspace(0.0, dt * num_steps, num_steps) positions = np.zeros((times.shape[0], X.shape[0], X.shape[1])) velocities = np.zeros((times.shape[0], X.shape[0], X.shape[1])) for i, t in enumerate(times): # Set first position and velocity if i == 0: positions[i, :, :] = X velocities[i, :, :] = V continue # Update position and velocity due to field dt = times[i] - times[i - 1] x = X + V + 0.5 * dt if i == 1 else X + V * dt E = e_field(x) v = V + E * pic_particle.charge / pic_particle.mass * dt # Update velocity due to collisions new_v = collision_model.single_time_step(v, dt) positions[i, :, :] = x velocities[i, :, :] = new_v X = x V = new_v print("Timestep: {}".format(i)) return times, positions, velocities
def run_parallel_sims(params): radius, electron_energy, I, n, batch_num = params to_kA = 1e-3 use_cartesian_reference_frame = False # Get output directory res_dir = "results" if not os.path.exists(res_dir): os.makedirs(res_dir) output_dir = os.path.join(res_dir, "radius-{}m".format(radius)) if not os.path.exists(output_dir): os.makedirs(output_dir) output_dir = os.path.join(output_dir, "current-{}kA".format(I * to_kA)) if not os.path.exists(output_dir): os.makedirs(output_dir) # Get process name process_name = "current-{}-radius-{}-energy-{}-n-{:.2E}-batch-{}".format( I, radius, electron_energy, n, batch_num) print("Starting process: {}".format(process_name)) # Generate Polywell field loop_pts = 200 domain_pts = 130 loop_offset = 1.25 dom_size = 1.1 * loop_offset * 1.0 file_name = "b_field_{}_{}_{}_{}_{}_{}".format(1.0 * to_kA, 1.0, loop_offset, domain_pts, loop_pts, dom_size) file_path = os.path.join("..", "mesh_generation", "data", "radius-1.0m", "current-0.001kA", "domres-{}".format(domain_pts), file_name) b_field = InterpolatedBField(file_path, dom_pts_idx=6, dom_size_idx=8) seed = batch_num np.random.seed(seed) # Run simulations num_radial_bins = 200 num_velocity_bins = 250 total_particle_position_count = np.zeros((num_radial_bins, )) total_particle_velocity_count_x = np.zeros( (num_radial_bins, num_velocity_bins - 1)) total_particle_velocity_count_y = np.zeros( (num_radial_bins, num_velocity_bins - 1)) total_particle_velocity_count_z = np.zeros( (num_radial_bins, num_velocity_bins - 1)) radial_bins = np.linspace(0.0, np.sqrt(3) * loop_offset * radius, num_radial_bins) vel = np.sqrt(2.0 * electron_energy * PhysicalConstants.electron_charge / PhysicalConstants.electron_mass) velocity_bins = np.linspace(-vel, vel, num_velocity_bins) num_sims = 400 final_positions = [] for i in range(num_sims): # Define particle velocity and 100eV charge particle z_unit = np.random.uniform(-1.0, 1.0) xy_plane = np.sqrt(1 - z_unit**2) phi = np.random.uniform(0.0, 2 * np.pi) velocity = np.asarray( [xy_plane * np.cos(phi), xy_plane * np.sin(phi), z_unit]) * vel particle = PICParticle( 9.1e-31, 1.6e-19, np.random.uniform(-3.0 * radius / 16.0, 3.0 * radius / 16.0, size=(3, )), velocity) t, x, y, z, v_x, v_y, v_z, final_idx = run_sim( (b_field, particle, radius, loop_offset * radius, I, n)) # Add results_remote_run_15_08 to list escaped = False if final_idx is None else True final_idx = final_idx if escaped else -1 # Save final position output final_positions.append( [t[final_idx], x[final_idx], y[final_idx], z[final_idx], escaped]) # Change coordinate system radial_position = np.sqrt(x[:final_idx]**2 + y[:final_idx]**2 + z[:final_idx]**2) if use_cartesian_reference_frame: particle_position_count, particle_velocity_count = get_particle_count( radial_bins, velocity_bins, radial_position, v_x, v_y, v_z) else: r_unit = np.zeros((3, x[:final_idx].shape[0])) r_unit[0, :] = x[:final_idx] r_unit[1, :] = y[:final_idx] r_unit[2, :] = z[:final_idx] r_unit /= np.sqrt(x[:final_idx]**2 + y[:final_idx]**2 + z[:final_idx]**2) xy_unit = np.zeros((3, x[:final_idx].shape[0])) xy_unit[0, :] = x[:final_idx] xy_unit[1, :] = y[:final_idx] xy_unit /= np.sqrt(np.sum(xy_unit**2, axis=0)) latitude_unit = np.zeros(xy_unit.shape) latitude_unit[0] = xy_unit[1, :] latitude_unit[1] = -xy_unit[0, :] latitude_unit[2] = 0.0 longitude_unit = np.zeros((3, x[:final_idx].shape[0])) longitude_unit[0, :] = r_unit[1, :] * latitude_unit[ 2, :] - r_unit[2] * latitude_unit[1] longitude_unit[1, :] = r_unit[2, :] * latitude_unit[ 0, :] - r_unit[0] * latitude_unit[2] longitude_unit[2, :] = r_unit[0, :] * latitude_unit[ 1, :] - r_unit[1] * latitude_unit[0] v_r = v_x[:final_idx] * r_unit[0, :] + v_y[:final_idx] * r_unit[ 1, :] + v_z[:final_idx] * r_unit[2, :] v_lat = v_x[:final_idx] * latitude_unit[ 0, :] + v_y[:final_idx] * latitude_unit[ 1, :] + v_z[:final_idx] * latitude_unit[2, :] v_long = v_x[:final_idx] * longitude_unit[ 0, :] + v_y[:final_idx] * longitude_unit[ 1, :] + v_z[:final_idx] * longitude_unit[2, :] particle_position_count, particle_velocity_count = get_particle_count( radial_bins, velocity_bins, radial_position, v_r, v_lat, v_long) # Get probability of electron in radial spacings in sim total_particle_position_count += particle_position_count total_particle_velocity_count_x += particle_velocity_count[0, :, :] total_particle_velocity_count_y += particle_velocity_count[1, :, :] total_particle_velocity_count_z += particle_velocity_count[2, :, :] # Save results_remote_run_15_08 to file position_output_path = os.path.join( output_dir, "radial_distribution-{}.txt".format(process_name)) velocity_output_path = os.path.join( output_dir, "velocity_distribution-{}".format(process_name)) final_state_output_path = os.path.join( output_dir, "final_state-current-{}.txt".format(process_name)) np.savetxt(position_output_path, np.stack((radial_bins, total_particle_position_count))) np.savetxt("{}_x.txt".format(velocity_output_path), total_particle_velocity_count_x) np.savetxt("{}_y.txt".format(velocity_output_path), total_particle_velocity_count_y) np.savetxt("{}_z.txt".format(velocity_output_path), total_particle_velocity_count_z) np.savetxt(final_state_output_path, np.asarray(final_positions)) print("Finished process: {}".format(process_name))
def test_uniform_e_field(self): """ This test considers a particle moving in a uniform electric field :return: """ seed = 1 num_tests = 100 np.random.seed(seed) Es = np.random.uniform(low=0.0, high=1.0, size=(3, num_tests)) X_0s = np.random.uniform(low=-1.0, high=1.0, size=(3, num_tests)) V_0s = np.random.uniform(low=-1.0, high=1.0, size=(3, num_tests)) for idx in range(num_tests): e_field = Es[:, idx] X_0 = X_0s[:, idx] V_0 = V_0s[:, idx] def B_field(x): B = np.zeros(x.shape) return B def E_field(x): E = np.zeros(x.shape) for i, b in enumerate(E): E[i, :] = e_field return E X = X_0.reshape((1, 3)) V = V_0.reshape((1, 3)) Q = np.asarray([1.0]) M = np.asarray([1.0]) final_time = 4.0 num_pts = 1000 times = np.linspace(0.0, final_time, num_pts) positions = np.zeros((times.shape[0], X.shape[0], X.shape[1])) velocities = np.zeros((times.shape[0], X.shape[0], X.shape[1])) for i, t in enumerate(times): if i == 0: positions[i, :, :] = X velocities[i, :, :] = V continue dt = times[i] - times[i - 1] x, v = boris_solver(E_field, B_field, X, V, Q, M, dt) positions[i, :, :] = x velocities[i, :, :] = v X = x V = v particle = PICParticle(1.0, Q[0], X_0, V_0) E = e_field analytic_times, analytic_positions = solve_E_field(particle, E, final_time, num_pts=num_pts) x = positions[:, :, 0].flatten() y = positions[:, :, 1].flatten() z = positions[:, :, 2].flatten() self.assertLess(np.absolute( np.average(x - analytic_positions[:, 0])), 0.01, msg="{}, {}, {}".format(X_0, V_0, e_field)) self.assertLess(np.absolute( np.average(y - analytic_positions[:, 1])), 0.01, msg="{}, {}, {}".format(X_0, V_0, e_field)) self.assertLess(np.absolute( np.average(z - analytic_positions[:, 2])), 0.01, msg="{}, {}, {}".format(X_0, V_0, e_field))
def test_uniform_B_field(self): """ This test considers a particle in a uniform B field, and aims to compare analytic and numerical solution. The particle can have an arbitrary velocity, and the B field is relatively in an arbitrary direction :return: """ seed = 1 num_tests = 100 np.random.seed(seed) Bs = np.random.uniform(low=0.0, high=10.0, size=(3, num_tests)) X_0s = np.random.uniform(low=-1.0, high=1.0, size=(3, num_tests)) V_0s = np.random.uniform(low=-1.0, high=1.0, size=(3, num_tests)) for idx in range(num_tests): b_field = Bs[:, idx] X_0 = X_0s[:, idx] V_0 = V_0s[:, idx] def B_field(x): B = np.zeros(x.shape) for i, b in enumerate(B): B[i, :] = b_field return B def E_field(x): E = np.zeros(x.shape) return E X = X_0.reshape((1, 3)) V = V_0.reshape((1, 3)) Q = np.asarray([1.0]) M = np.asarray([1.0]) final_time = 4.0 num_pts = 500 times = np.linspace(0.0, final_time, num_pts) positions = np.zeros((times.shape[0], X.shape[0], X.shape[1])) velocities = np.zeros((times.shape[0], X.shape[0], X.shape[1])) for i, t in enumerate(times): if i == 0: positions[i, :, :] = X velocities[i, :, :] = V continue dt = times[i] - times[i - 1] x, v = boris_solver(E_field, B_field, X, V, Q, M, dt) positions[i, :, :] = x velocities[i, :, :] = v X = x V = v particle = PICParticle(1.0, Q[0], X_0, V_0) B = b_field analytic_times, analytic_positions = solve_B_field(particle, B, final_time, num_pts=num_pts) x = positions[:, :, 0].flatten() y = positions[:, :, 1].flatten() z = positions[:, :, 2].flatten() self.assertLess( np.absolute(np.average(x - analytic_positions[:, 0])), 0.01) self.assertLess( np.absolute(np.average(y - analytic_positions[:, 1])), 0.01) self.assertLess( np.absolute(np.average(z - analytic_positions[:, 2])), 0.01)
def test_aligned_fields(self): """ This test considers a particle moving in a uniform orthogonal electric and magnetic field :return: """ seed = 1 np.random.seed(seed) for direction in [ np.asarray([1.0, 0.0, 0.0]), np.asarray([0.0, 1.0, 0.0]), np.asarray([0.0, 0.0, 1.0]) ]: for sign in [-1, 1]: # randomise initial conditions B_mag = np.random.uniform(low=0.0, high=1.0) E_mag = np.random.uniform(low=0.0, high=1.0) X_0 = np.random.uniform(low=-1.0, high=1.0, size=(1, 3)) V_0 = np.random.uniform(low=-1.0, high=1.0, size=(1, 3)) def B_field(x): B = np.zeros(x.shape) for i, b in enumerate(B): B[i, :] = B_mag * direction * sign return B def E_field(x): E = np.zeros(x.shape) for i, b in enumerate(E): E[i, :] = E_mag * direction * sign return E X = X_0.reshape((1, 3)) V = V_0.reshape((1, 3)) Q = np.asarray([1.0]) M = np.asarray([1.0]) final_time = 4.0 num_pts = 1000 times = np.linspace(0.0, final_time, num_pts) positions = np.zeros((times.shape[0], X.shape[0], X.shape[1])) velocities = np.zeros((times.shape[0], X.shape[0], X.shape[1])) for i, t in enumerate(times): if i == 0: positions[i, :, :] = X velocities[i, :, :] = V continue dt = times[i] - times[i - 1] x, v = boris_solver(E_field, B_field, X, V, Q, M, dt) positions[i, :, :] = x velocities[i, :, :] = v X = x V = v particle = PICParticle(1.0, Q[0], X_0[0], V_0[0]) E = E_mag * direction * sign B = B_mag * direction * sign analytic_times, analytic_positions = solve_aligned_fields( particle, E, B, final_time, num_pts=num_pts) x = positions[:, :, 0].flatten() y = positions[:, :, 1].flatten() z = positions[:, :, 2].flatten() self.assertLess(np.absolute( np.average(x - analytic_positions[:, 0])), 0.01, msg="{}, {}, {}".format(X_0, V_0, E, B)) self.assertLess(np.absolute( np.average(y - analytic_positions[:, 1])), 0.01, msg="{}, {}, {}".format(X_0, V_0, E, B)) self.assertLess(np.absolute( np.average(z - analytic_positions[:, 2])), 0.01, msg="{}, {}, {}".format(X_0, V_0, E, B))
def run_parallel_sims(params): electron_energy_eV = params[0] use_interpolation = True dI_dt = 0.0 print("Starting process: electron energy {}eV".format(electron_energy_eV)) # Generate Polywell field I = 1e4 radius = 1.0 to_kA = 1e-3 loop_pts = 200 domain_pts = 130 loop_offset = 1.25 dom_size = 1.1 * loop_offset * radius if use_interpolation: file_name = "b_field_{}_{}_{}_{}_{}_{}".format(I * to_kA, radius, loop_offset, domain_pts, loop_pts, dom_size) file_path = os.path.join("..", "mesh_generation", "data", "radius-{}m".format(radius), "current-{}kA".format(I * to_kA), "domres-{}".format(domain_pts), file_name) b_field = InterpolatedBField(file_path, dom_pts_idx=6, dom_size_idx=8) else: comp_loops = list() comp_loops.append( CurrentLoop(I, radius, np.asarray([-loop_offset * radius, 0.0, 0.0]), np.asarray([1.0, 0.0, 0.0]), loop_pts)) comp_loops.append( CurrentLoop(I, radius, np.asarray([loop_offset * radius, 0.0, 0.0]), np.asarray([-1.0, 0.0, 0.0]), loop_pts)) comp_loops.append( CurrentLoop(I, radius, np.asarray([0.0, -loop_offset * radius, 0.0]), np.asarray([0.0, 1.0, 0.0]), loop_pts)) comp_loops.append( CurrentLoop(I, radius, np.asarray([0.0, loop_offset * radius, 0.0]), np.asarray([0.0, -1.0, 0.0]), loop_pts)) comp_loops.append( CurrentLoop(I, radius, np.asarray([0.0, 0.0, -loop_offset * radius]), np.asarray([0.0, 0.0, 1.0]), loop_pts)) comp_loops.append( CurrentLoop(I, radius, np.asarray([0.0, 0.0, loop_offset * radius]), np.asarray([0.0, 0.0, -1.0]), loop_pts)) b_field = CombinedField(comp_loops, domain_size=dom_size) seed = 1 np.random.seed(seed) # Run simulations num_sims = 500 final_positions = [] vel = np.sqrt(2.0 * electron_energy_eV * PhysicalConstants.electron_charge / PhysicalConstants.electron_mass) for i in range(num_sims): # Define particle velocity and 100eV charge particle z_unit = np.random.uniform(-1.0, 1.0) xy_plane = np.sqrt(1 - z_unit**2) phi = np.random.uniform(0.0, 2 * np.pi) velocity = np.asarray( [xy_plane * np.cos(phi), xy_plane * np.sin(phi), z_unit]) * vel particle = PICParticle( 9.1e-31, 1.6e-19, np.random.uniform(-3.0 * radius / 16.0, 3.0 * radius / 16.0, size=(3, )), velocity) t, x, y, z, final_idx = run_sim( (b_field, particle, radius, loop_offset * radius, I, dI_dt)) # Add results to list escaped = False if final_idx is None else True final_idx = final_idx if escaped else -1 final_positions.append( [t[final_idx], x[final_idx], y[final_idx], z[final_idx], escaped]) # Save to file if not os.path.exists("results"): os.makedirs("results") output_path = os.path.join( "results", "mean_confinement-10kA-1.0m-{}.txt".format(electron_energy_eV)) np.savetxt(output_path, np.asarray(final_positions)) print("Finished process: electron energy {}eV".format(electron_energy_eV))
def run_parallel_sims(params): electron_energy, I, batch_num = params dI_dt = 0.0 to_kA = 1e-3 process_name = "electron_energy-{}eV-current-{}kA-batch-{}".format( electron_energy, I * to_kA, batch_num) print("Starting process: {}".format(process_name)) # Generate Polywell field radius = 1.0 loop_pts = 200 domain_pts = 130 loop_offset = 1.25 dom_size = 1.1 * loop_offset * radius file_name = "b_field_{}_{}_{}_{}_{}_{}".format(I * to_kA, radius, loop_offset, domain_pts, loop_pts, dom_size) file_path = os.path.join("..", "mesh_generation", "data", "radius-{}m".format(radius), "current-{}kA".format(I * to_kA), "domres-{}".format(domain_pts), file_name) b_field = InterpolatedBField(file_path, dom_pts_idx=6, dom_size_idx=8) seed = batch_num np.random.seed(seed) # Run simulations num_bins = 100 particle_distribution_count = np.zeros((num_bins, )) radial_bins = np.linspace(0.0, np.sqrt(3) * loop_offset * radius, num_bins) num_sims = 200 final_positions = [] vel = np.sqrt(2.0 * electron_energy * PhysicalConstants.electron_charge / PhysicalConstants.electron_mass) for i in range(num_sims): # Define particle velocity and 100eV charge particle z_unit = np.random.uniform(-1.0, 1.0) xy_plane = np.sqrt(1 - z_unit**2) phi = np.random.uniform(0.0, 2 * np.pi) velocity = np.asarray( [xy_plane * np.cos(phi), xy_plane * np.sin(phi), z_unit]) * vel particle = PICParticle( 9.1e-31, 1.6e-19, np.random.uniform(-3.0 * radius / 16.0, 3.0 * radius / 16.0, size=(3, )), velocity) t, x, y, z, final_idx = run_sim( (b_field, particle, radius, loop_offset * radius, I, dI_dt)) # Add results to list escaped = False if final_idx is None else True final_idx = final_idx if escaped else -1 # Get probability of electron in 50 radial spacings in sim radial_position = np.sqrt(x[:final_idx]**2 + y[:final_idx]**2 + z[:final_idx]**2) for i, r in enumerate(radial_bins): if i == 0.0: continue points_in_range = np.where( np.logical_and(radial_position >= radial_bins[i - 1], radial_position <= r)) particle_distribution_count[i - 1] += points_in_range[0].shape[0] # Save to file if not os.path.exists("results"): os.makedirs("results") output_dir = os.path.join("results", "radius-{}m".format(radius)) if not os.path.exists(output_dir): os.makedirs(output_dir) output_dir = os.path.join(output_dir, "current-{}kA".format(I * to_kA)) if not os.path.exists(output_dir): os.makedirs(output_dir) output_path = os.path.join( output_dir, "radial_distribution-current-{}-radius-{}-energy-{}-batch-{}.txt". format(I, radius, electron_energy, batch_num)) np.savetxt(output_path, np.stack( (radial_bins, particle_distribution_count))) print("Finished process: {}".format(process_name))
def test_aligned_fields(): """ This test considers a particle moving in a uniform orthogonal electric and magnetic field :return: """ seed = 1 np.random.seed(seed) for direction in [ np.asarray([1.0, 0.0, 0.0]), np.asarray([0.0, 1.0, 0.0]), np.asarray([0.0, 0.0, 1.0]) ]: for sign in [-1, 1]: # randomise initial conditions B_mag = np.random.uniform(low=0.0, high=1.0) E_mag = np.random.uniform(low=0.0, high=1.0) X_0 = np.random.uniform(low=-1.0, high=1.0, size=(1, 3)) V_0 = np.random.uniform(low=-1.0, high=1.0, size=(1, 3)) def B_field(x): B = np.zeros(x.shape) for i, b in enumerate(B): B[i, :] = B_mag * direction * sign return B def E_field(x): E = np.zeros(x.shape) for i, b in enumerate(E): E[i, :] = E_mag * direction * sign return E X = X_0.reshape((1, 3)) V = V_0.reshape((1, 3)) Q = np.asarray([1.0]) M = np.asarray([1.0]) final_time = 4.0 num_pts = 1000 times = np.linspace(0.0, final_time, num_pts) positions = np.zeros((times.shape[0], X.shape[0], X.shape[1])) velocities = np.zeros((times.shape[0], X.shape[0], X.shape[1])) for i, t in enumerate(times): if i == 0: positions[i, :, :] = X velocities[i, :, :] = V continue dt = times[i] - times[i - 1] x, v = boris_solver(E_field, B_field, X, V, Q, M, dt) positions[i, :, :] = x velocities[i, :, :] = v X = x V = v particle = PICParticle(1.0, Q[0], X_0[0], V_0[0]) E = E_mag * direction * sign B = B_mag * direction * sign analytic_times, analytic_positions = solve_aligned_fields( particle, E, B, final_time, num_pts=num_pts) x = positions[:, :, 0].flatten() y = positions[:, :, 1].flatten() z = positions[:, :, 2].flatten() fig = plt.figure(figsize=(20, 10)) ax = fig.add_subplot('111', projection='3d') ax.plot(x, y, z, label='numerical') ax.plot(analytic_positions[:, 0], analytic_positions[:, 1], analytic_positions[:, 2], label='analytic') ax.set_xlabel('X') ax.set_ylabel('Y') ax.set_zlabel('Z') ax.legend(loc='best') ax.set_title("Analytic and Numerical Particle Motion") plt.show()
def single_particle_example(b_field=np.asarray([0.0, 0.0, 1.0]), X_0=np.asarray([[0.0, 1.0, 0.0]]), V_0=np.asarray([[-2.0, 0.0, 1.0]])): """ Example solution to simple magnetic field case :return: """ def B_field(x): B = np.zeros(x.shape) for i, b in enumerate(B): B[i, :] = b_field return B def E_field(x): E = np.zeros(x.shape) return E X = X_0 V = V_0 Q = np.asarray([1.0]) M = np.asarray([1.0]) times = np.linspace(0.0, 4.0, 1000) positions = np.zeros((times.shape[0], X.shape[0], X.shape[1])) velocities = np.zeros((times.shape[0], X.shape[0], X.shape[1])) for i, t in enumerate(times): if i == 0: positions[i, :, :] = X velocities[i, :, :] = V continue dt = times[i] - times[i - 1] x, v = boris_solver(E_field, B_field, X, V, Q, M, dt) positions[i, :, :] = x velocities[i, :, :] = v X = x V = v particle = PICParticle(1.0, Q[0], X_0[0], V_0[0]) B = b_field analytic_times, analytic_positions = solve_B_field(particle, B, 4.0) x = positions[:, :, 0].flatten() y = positions[:, :, 1].flatten() z = positions[:, :, 2].flatten() fig = plt.figure(figsize=(20, 10)) ax = fig.add_subplot('111', projection='3d') ax.plot(x, y, z, label='numerical') ax.plot(analytic_positions[:, 0], analytic_positions[:, 1], analytic_positions[:, 2], label='analytic') ax.set_xlabel('X') ax.set_ylabel('Y') ax.set_zlabel('Z') ax.legend(loc='best') ax.set_title("Analytic and Numerical Particle Motion") plt.show() fig, axes = plt.subplots(3, figsize=(20, 10)) axes[0].plot(x) axes[1].plot(y) axes[2].plot(z) fig.suptitle("Numerical Solution") plt.show() fig, axes = plt.subplots(3, figsize=(20, 10)) axes[0].plot(x - analytic_positions[:, 0]) axes[1].plot(y - analytic_positions[:, 1]) axes[2].plot(z - analytic_positions[:, 2]) fig.suptitle("Deviation of Numerical Solution from the Analytic") plt.show()