def solve_B_field(particle, B, final_time, num_pts=1000): """ Solve for the velocity function with respect to time """ assert isinstance(B, np.ndarray) and B.shape[0] == 3 and len( B.shape) == 1, "B must be a 3D vector" v = particle.velocity[0] omega = particle.charge * magnitude(B) / particle.mass v_parallel = vector_projection(v, B) v_perpendicular = v - v_parallel F = cross(v_perpendicular, B) * particle.charge radius = (particle.mass * dot(v_perpendicular, v_perpendicular)) / magnitude(F) centre_of_rotation = F * radius / magnitude(F) + particle.position[0] relative_position = particle.position[0] - centre_of_rotation def parallel_motion(t): return v_parallel * t def perpendicular_motion(t): angle = -omega * t return arbitrary_axis_rotation_3d(relative_position, B, angle) times = np.linspace(0.0, final_time, num_pts) positions = np.zeros((num_pts, 3)) for i, t in enumerate(times): positions[i, :] = centre_of_rotation + parallel_motion( t) + perpendicular_motion(t) return times, positions
def test_electric_field(self): """ Function to test the electric field generated PointField class :return: """ field = PointField(1.0, 1.0, np.zeros(3)) radius = field.radius rho = field.rho total_charge = rho * 4.0 / 3.0 * np.pi * radius**3 # Sample at random points seed = 1 num_tests = 1000 np.random.seed(seed) sample_point = np.random.uniform( low=1.0, high=100.0, size=(3, num_tests)) * radius for i in range(sample_point.shape[1]): E = field.e_field(sample_point[:, i]) E_mag = magnitude(E) self.assertAlmostEqual( total_charge / (4.0 * np.pi * magnitude(sample_point[:, i])**2 * PointField.epsilon), E_mag)
def b_field(self, field_point): """ Calculate the B field at an arbitrary point from the loop """ assert isinstance(field_point, np.ndarray) permeability_constant = CurrentLoop.mu_0 / (4 * np.pi) d_arc_length = self.__radius * self.d_theta integral_constant = permeability_constant * d_arc_length * self.__I # Integrate vector contributions b_field_contributions = np.zeros((self.theta.shape[0], 3)) for i, rotation_angle in enumerate(self.theta): # Get b field direction loop_to_point = self.radial_locations[i, :] - field_point b_field_direction = cross(loop_to_point, self.current_direction[i, :]) b_unit = b_field_direction / magnitude(b_field_direction) loop_distance = magnitude(loop_to_point) b_field_contributions[i, :] = b_unit / loop_distance**2 b_field_contributions *= integral_constant b_field = np.sum(b_field_contributions, axis=0) return b_field
def test_interpolated_b_field(self): """ Function to test interpolated B field behaviour is as expected :return: """ # Generate Interpolated field I = 1e6 loop_pts = 40 domain_pts = 50 dom_size = 0.2 radius = 0.15 file_name = "current_loop_{}_{}_{}_{}".format(I * 1e-6, loop_pts, domain_pts, dom_size) file_path = os.path.join(file_name) interp_field = InterpolatedBField(file_path) Z = np.linspace(-dom_size, dom_size, 50) for i, z in enumerate(Z): analytic_z_field = CurrentLoop.mu_0 / 2 * I * radius**2 / ( (radius**2 + z**2)**(3.0 / 2.0)) b = interp_field.b_field(np.asarray([[0.0, 0.0, z]])) B = magnitude(b[0]) self.assertAlmostEqual(B / analytic_z_field, 1.0, 2)
def compare_fields(dom_size, numerical_pts, b_field, radius, current): b_factor = current / radius unit_field = load_field(1.0, 1.0) min_dom = -dom_size max_dom = dom_size X = np.linspace(min_dom, max_dom, numerical_pts) Y = np.linspace(min_dom, max_dom, numerical_pts) Z = np.linspace(min_dom, max_dom, numerical_pts) B = np.zeros((numerical_pts, numerical_pts, numerical_pts, 4)) for i, x in enumerate(X): for j, y in enumerate(Y): for k, z in enumerate(Z): print(i, j, k) field_point = np.zeros((1, 3)) field_point[0, 0] = x field_point[0, 1] = y field_point[0, 2] = z b = np.abs(b_field.b_field(field_point)) gummersall_field = np.abs( unit_field.b_field(field_point / radius) * b_factor) b = gummersall_field - b B[i, j, k, 0] = b[0, 0] B[i, j, k, 1] = b[0, 1] B[i, j, k, 2] = b[0, 2] B[i, j, k, 3] = magnitude(b[0]) fig, ax = plt.subplots(1) X_1, Y_1 = np.meshgrid(X, Y, indexing='ij') im = ax.contourf(X_1, Y_1, B[:, :, numerical_pts // 2, 3], 100) fig.colorbar(im, ax=ax) ax.quiver(X_1, Y_1, B[:, :, numerical_pts // 2, 0], B[:, :, numerical_pts // 2, 1]) plt.savefig("magnetic_field_sample_xy") plt.show() fig, ax = plt.subplots(1) X_2, Z_2 = np.meshgrid(X, Z, indexing='ij') im = ax.contourf(X_2, Z_2, B[:, numerical_pts // 2, :, 3], 100) fig.colorbar(im, ax=ax) ax.quiver(X_2, Z_2, B[:, numerical_pts // 2, :, 0], B[:, numerical_pts // 2, :, 2]) plt.savefig("magnetic_field_sample_xz") plt.show() fig, ax = plt.subplots(1) Y_3, Z_3 = np.meshgrid(X, Y, indexing='ij') im = ax.contourf(Y_3, Z_3, B[numerical_pts // 2, :, :, 3], 100) fig.colorbar(im, ax=ax) ax.quiver(Y_3, Z_3, B[numerical_pts // 2, :, :, 1], B[numerical_pts // 2, :, :, 2]) plt.savefig("magnetic_field_sample_yz") plt.show()
def calculate_post_collision_velocities(self, v_1, v_2, dt, idx_1, idx_2): """ Calculate the post collisional velocities of a given pair of particles """ # Step 1 - Get relative velocity u and perpendicular velocity u_xy u_rel = v_1 - v_2 u = vector_ops.magnitude(u_rel) u_xy = np.sqrt(u_rel[0]**2 + u_rel[1]**2) u = 1e-16 if u < 1e-16 else u # Step 2 - Get scattering angles THETA and PHI PHI = np.random.uniform(0.0, 2.0 * np.pi) c_phi = np.cos(PHI) s_phi = np.sin(PHI) delta_squared = self.__q_1**2 * self.__q_2**2 * max( self.__n_1, self.__n_2) delta_squared *= dt * self.__coulomb_logarithm delta_squared /= 8.0 * np.pi * u**3 * self.__m_eff**2 * PhysicalConstants.epsilon_0**2 assert not np.isnan(delta_squared) and not np.isinf( delta_squared), "{}, {}".format(delta_squared, u) delta = np.random.normal(0.0, np.sqrt(delta_squared)) s_theta = 2 * delta / (1 + delta**2) one_minus_c_theta = 2 * delta**2 / (1 + delta**2) # Step 3 - Calculate du du = np.zeros((3, )) if u_xy != 0.0: du[0] = u_rel[0] / u_xy * u_rel[2] * s_theta * c_phi - \ u_rel[1] / u_xy * u * s_theta * s_phi - \ u_rel[0] * one_minus_c_theta du[1] = u_rel[1] / u_xy * u_rel[2] * s_theta * c_phi + \ u_rel[0] / u_xy * u * s_theta * s_phi - \ u_rel[1] * one_minus_c_theta du[2] = -u_xy * s_theta * c_phi - u_rel[2] * one_minus_c_theta else: du[0] = u * s_theta * c_phi du[1] = u * s_theta * s_phi du[2] = -u * one_minus_c_theta assert not np.any(np.isnan(du)), "{}, {}, {}, {}".format( du, delta, s_theta, one_minus_c_theta) # Step 4 - Update velocities P_1 = 1.0 if np.random.uniform( 0, 1) <= self.__collision_threshold_1 else 0.0 P_2 = 1.0 if np.random.uniform( 0, 1) <= self.__collision_threshold_2 else 0.0 new_v_1 = v_1 + P_1 * self.__m_eff / self.__m_1 * du new_v_2 = v_2 - P_2 * self.__m_eff / self.__m_2 * du return new_v_1, new_v_2
def full_b_field(): I = 1e6 radius = 2.0 loop_pts = 20 loop = CurrentLoop(I, radius, np.asarray([0.0, 0.0, 0.0]), np.asarray([0.0, 0.0, 1.0]), loop_pts) numerical_pts = 40 min_dom = -2.5 max_dom = 2.5 X = np.linspace(min_dom, max_dom, numerical_pts) Y = np.linspace(min_dom, max_dom, numerical_pts) Z = np.linspace(min_dom, max_dom, numerical_pts) B = np.zeros((numerical_pts, numerical_pts, numerical_pts, 4)) for i, x in enumerate(X): for j, y in enumerate(Y): for k, z in enumerate(Z): b = loop.b_field(np.asarray([x, y, z])) B[i, j, k, 0] = b[0] B[i, j, k, 1] = b[1] B[i, j, k, 2] = b[2] B[i, j, k, 3] = magnitude(b) fig, ax = plt.subplots(3, figsize=(5, 5)) X_1, Y_1 = np.meshgrid(X, Y, indexing='ij') im = ax[0].contourf(X_1, Y_1, B[:, :, numerical_pts // 2, 3], 100) fig.colorbar(im, ax=ax[0]) ax[0].quiver(X_1, Y_1, B[:, :, numerical_pts // 2, 0], B[:, :, numerical_pts // 2, 1], headlength=7) X_2, Z_2 = np.meshgrid(X, Z, indexing='ij') im = ax[1].contourf(X_2, Z_2, B[:, numerical_pts // 2, :, 3], 100) fig.colorbar(im, ax=ax[1]) ax[1].quiver(X_2, Z_2, B[:, numerical_pts // 2, :, 0], B[:, numerical_pts // 2, :, 2], headlength=7) Y_3, Z_3 = np.meshgrid(X, Y, indexing='ij') im = ax[2].contourf(Y_3, Z_3, B[numerical_pts // 2, :, :, 3], 100) fig.colorbar(im, ax=ax[2]) ax[2].quiver(Y_3, Z_3, B[numerical_pts // 2, :, :, 1], B[numerical_pts // 2, :, :, 2], headlength=7) plt.show()
def generate_domain(dom_size, numerical_pts, b_field): min_dom = -dom_size max_dom = dom_size X = np.linspace(min_dom, max_dom, numerical_pts) Y = np.linspace(min_dom, max_dom, numerical_pts) Z = np.linspace(min_dom, max_dom, numerical_pts) B = np.zeros((numerical_pts, numerical_pts, numerical_pts, 4)) for i, x in enumerate(X): for j, y in enumerate(Y): for k, z in enumerate(Z): print(i, j, k) field_point = np.zeros((1, 3)) field_point[0, 0] = x field_point[0, 1] = y field_point[0, 2] = z b = b_field.b_field(field_point) B[i, j, k, 0] = b[0, 0] B[i, j, k, 1] = b[0, 1] B[i, j, k, 2] = b[0, 2] B[i, j, k, 3] = magnitude(b[0]) fig, ax = plt.subplots(1) X_1, Y_1 = np.meshgrid(X, Y, indexing='ij') im = ax.contourf(X_1, Y_1, np.log10(B[:, :, numerical_pts // 2, 3]), 100) fig.colorbar(im, ax=ax) ax.quiver(X_1, Y_1, B[:, :, numerical_pts // 2, 0], B[:, :, numerical_pts // 2, 1]) plt.savefig("magnetic_field_sample_xy") plt.show() fig, ax = plt.subplots(1) X_2, Z_2 = np.meshgrid(X, Z, indexing='ij') im = ax.contourf(X_2, Z_2, np.log10(B[:, numerical_pts // 2, :, 3]), 100) fig.colorbar(im, ax=ax) ax.quiver(X_2, Z_2, B[:, numerical_pts // 2, :, 0], B[:, numerical_pts // 2, :, 2]) plt.savefig("magnetic_field_sample_xz") plt.show() fig, ax = plt.subplots(1) Y_3, Z_3 = np.meshgrid(X, Y, indexing='ij') im = ax.contourf(Y_3, Z_3, np.log10(B[numerical_pts // 2, :, :, 3]), 100) fig.colorbar(im, ax=ax) ax.quiver(Y_3, Z_3, B[numerical_pts // 2, :, :, 1], B[numerical_pts // 2, :, :, 2]) plt.savefig("magnetic_field_sample_yz") plt.show()
def v_field(self, field_point): """" Calculated the potential difference at a given point field_point: point at which the field is being calculated """ radial_distance = field_point - self.__centre radial_magnitude = magnitude(radial_distance) if radial_magnitude < self.__radius: V = self.total_charge / ( 8.0 * np.pi * self.__radius * PointField.epsilon) * ( 3.0 - radial_magnitude**2 / self.__radius**2) return V else: V = self.total_charge / (4.0 * np.pi * radial_magnitude * PointField.epsilon) return V
def e_field(self, field_point): """" Calculated the electric field at a given point field_point: point at which the field is being calculated """ radial_distance = field_point - self.__centre radial_magnitude = magnitude(radial_distance.flatten()) radial_direction = radial_distance / radial_magnitude if radial_magnitude < self.__radius: E = self.total_charge * radial_magnitude / ( 4.0 * np.pi * self.__radius**3 * PointField.epsilon) return E * radial_distance else: E = self.total_charge / (4.0 * np.pi * radial_magnitude**2 * PointField.epsilon) return E * radial_direction
def __init__(self, I, radius, centre, normal, num_pts): """" Initialise primary and secondary variables of the class I: Current in loop radius: Radius of the loop normal: Normal to the loop. This also defines the direction of the current, as the normal is in the direction of the B field. num_pts: Number of points that are used to integrate the biot savart law across the loop """ assert isinstance(I, float) assert isinstance(radius, float) assert isinstance(centre, np.ndarray) assert isinstance(normal, np.ndarray) assert isinstance(num_pts, int) self.__I = I self.__radius = radius self.__centre = centre self.__normal = normal self.__num_pts = num_pts # Discretise the loop by angle self.d_theta = 2.0 * np.pi / num_pts self.theta = np.linspace(0.0, 2 * np.pi - self.d_theta, num_pts) # Find an arbitrary parallel unit vector unit_vector = np.asarray([1.0, 1.0, 1.0]) self.r = cross(unit_vector, normal) self.r_unit = self.r / magnitude(self.r) self.R = self.r_unit * self.__radius self.radial_locations = np.zeros((num_pts, 3)) self.current_direction = np.zeros((num_pts, 3)) for i, rotation_angle in enumerate(self.theta): self.radial_locations[i, :] = arbitrary_axis_rotation_3d( self.R, self.__normal, rotation_angle) - self.__centre self.current_direction[i, :] = cross( self.__normal, self.radial_locations[i, :] - self.__centre)
def test_b_field(self): """ Function to test B field along axis is as expected from analytic solution :return: """ for offset in [1.0, 0.0, -1.0]: I = 1e6 radius = 0.15 loop_pts = 500 loop = CurrentLoop(I, radius, np.asarray([0.0, 0.0, offset]), np.asarray([0.0, 0.0, 1.0]), loop_pts) Z = np.linspace(-5.0, 5.0, 50) for i, z in enumerate(Z): analytic_z_field = CurrentLoop.mu_0 / 2 * I * radius**2 / ( (radius**2 + (z + offset)**2)**(3.0 / 2.0)) b = loop.b_field(np.asarray([0.0, 0.0, z])) B = magnitude(b) self.assertAlmostEqual(analytic_z_field, B, 5)
def generate_current_loop_field(): I = 1e6 radius = 0.15 loop_offset = 0.0 loop_pts = 40 loop = CurrentLoop(I, radius, np.asarray([-loop_offset, 0.0, 0.0]), np.asarray([0.0, 0.0, 1.0]), loop_pts) # Calculate current loop field at all points domain_pts = 50 dom_size = 0.2 min_dom = -dom_size max_dom = dom_size X = np.linspace(min_dom, max_dom, domain_pts) Y = np.linspace(min_dom, max_dom, domain_pts) Z = np.linspace(min_dom, max_dom, domain_pts) B_x = np.zeros((domain_pts, domain_pts, domain_pts)) B_y = np.zeros((domain_pts, domain_pts, domain_pts)) B_z = np.zeros((domain_pts, domain_pts, domain_pts)) B = np.zeros((domain_pts, domain_pts, domain_pts)) for i, x in enumerate(X): for j, y in enumerate(Y): for k, z in enumerate(Z): print(i, j, k) b = loop.b_field(np.asarray([x, y, z])) B_x[i, j, k] = b[0] B_y[i, j, k] = b[1] B_z[i, j, k] = b[2] B[i, j, k] = magnitude(b) # Write output files file_name = "../../../testing/algo/fields/magnetic_fields/current_loop_{}_{}_{}_{}".format(I * 1e-6, loop_pts, domain_pts, dom_size) np.savetxt("{}_x".format(file_name), B_x.reshape((domain_pts, domain_pts ** 2))) np.savetxt("{}_y".format(file_name), B_y.reshape((domain_pts, domain_pts ** 2))) np.savetxt("{}_z".format(file_name), B_z.reshape((domain_pts, domain_pts ** 2))) np.savetxt(file_name, B.reshape((domain_pts, domain_pts ** 2))) write_vti_file(B, file_name)
num_pts = 200 X = np.linspace(-5.0, 5.0, num_pts) Y = np.linspace(-5.0, 5.0, num_pts) E = np.zeros((num_pts, num_pts)) E_vector = np.zeros((num_pts, num_pts, 2)) V = np.zeros((num_pts, num_pts)) for i, x in enumerate(X): for j, y in enumerate(Y): point = np.asarray([x, y]) e = field.e_field(point) v = field.v_field(point) E_vector[i, j, 0] = e[0] E_vector[i, j, 1] = e[1] E[i, j] = magnitude(e) V[i, j] = v # Plot results X, Y = np.meshgrid(X, Y) X = X.transpose() Y = Y.transpose() num_contours = 100 fig, axes = plt.subplots(2, figsize=(10, 10)) im = axes[0].contourf(X, Y, E, num_contours) quiv = axes[0].quiver(X, Y, E_vector[:, :, 0], E_vector[:, :, 1]) fig.colorbar(im, ax=axes[0]) axes[0].set_title("Electric Field (V/m)") im = axes[1].contourf(X, Y, V, num_contours) fig.colorbar(im, ax=axes[1]) axes[1].set_title("Potential Field (V)")
def generate_polywell_fields(current_offset_factor=1.0, plot_fields=False): assert 0.0 <= current_offset_factor <= 1.0 # Generate Polywell field I = 1e6 radius = 0.15 loop_offset = 0.175 loop_pts = 20 comp_loops = list() comp_loops.append(CurrentLoop(I, radius, np.asarray([-loop_offset, 0.0, 0.0]), np.asarray([1.0, 0.0, 0.0]), loop_pts)) comp_loops.append(CurrentLoop(I, radius, np.asarray([loop_offset, 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, 0.0]), np.asarray([0.0, 1.0, 0.0]), loop_pts)) comp_loops.append(CurrentLoop(I, radius, np.asarray([0.0, loop_offset, 0.0]), np.asarray([0.0, -1.0, 0.0]), loop_pts)) comp_loops.append(CurrentLoop(current_offset_factor * I, radius, np.asarray([0.0, 0.0, -loop_offset]), np.asarray([0.0, 0.0, 1.0]), loop_pts)) comp_loops.append(CurrentLoop(I, radius, np.asarray([0.0, 0.0, loop_offset]), np.asarray([0.0, 0.0, -1.0]), loop_pts)) combined_field = CombinedField(comp_loops) # Calculate polywell field at all points domain_pts = 20 dom_size = 0.2 min_dom = -dom_size max_dom = dom_size X = np.linspace(min_dom, max_dom, domain_pts) Y = np.linspace(min_dom, max_dom, domain_pts) Z = np.linspace(min_dom, max_dom, domain_pts) B_x = np.zeros((domain_pts, domain_pts, domain_pts)) B_y = np.zeros((domain_pts, domain_pts, domain_pts)) B_z = np.zeros((domain_pts, domain_pts, domain_pts)) B = np.zeros((domain_pts, domain_pts, domain_pts)) for i, x in enumerate(X): for j, y in enumerate(Y): for k, z in enumerate(Z): print(i, j, k) b = combined_field.b_field(np.asarray([x, y, z])) B_x[i, j, k] = b[0] B_y[i, j, k] = b[1] B_z[i, j, k] = b[2] B[i, j, k] = magnitude(b) # Write output files file_name = "b_field_{}_{}_{}_{}_{}".format(I * 1e-6, loop_pts, domain_pts, dom_size, current_offset_factor) np.savetxt("{}_x".format(file_name), B_x.reshape((domain_pts, domain_pts ** 2))) np.savetxt("{}_y".format(file_name), B_y.reshape((domain_pts, domain_pts ** 2))) np.savetxt("{}_z".format(file_name), B_z.reshape((domain_pts, domain_pts ** 2))) np.savetxt(file_name, B.reshape((domain_pts, domain_pts ** 2))) write_vti_file(B, file_name) if plot_fields: # Plots overall field B = np.log10(B) fig, ax = plt.subplots(3, figsize=(15, 15)) X_1, Y_1 = np.meshgrid(X, Y, indexing='ij') im = ax[0].contourf(X_1, Y_1, B[:, :, domain_pts // 2], 100) fig.colorbar(im, ax=ax[0]) ax[0].quiver(X_1, Y_1, B_x[:, :, domain_pts // 2], B_y[:, :, domain_pts // 2], 100) ax[0].set_title("XY Plane") X_2, Z_2 = np.meshgrid(X, Z, indexing='ij') im = ax[1].contourf(X_2, Z_2, B[:, domain_pts // 2, :], 100) fig.colorbar(im, ax=ax[1]) ax[1].quiver(X_2, Z_2, B_x[:, :, domain_pts // 2], B_z[:, :, domain_pts // 2], 100) ax[1].set_title("XZ Plane") Y_3, Z_3 = np.meshgrid(X, Y, indexing='ij') im = ax[2].contourf(Y_3, Z_3, B[domain_pts // 2, :, :], 100) fig.colorbar(im, ax=ax[2]) ax[2].quiver(Y_3, Z_3, B_y[:, :, domain_pts // 2], B_z[:, :, domain_pts // 2], 100) ax[2].set_title("YZ Plane") plt.show()
def generate_interpolated_fields(current_offset_factor, compare_fields=False): # Generate Interpolated field I = 1e6 loop_pts = 20 domain_pts = 20 dom_size = 0.2 file_name = "b_field_{}_{}_{}_{}_{}".format(I * 1e-6, loop_pts, domain_pts, dom_size, current_offset_factor) # file_path = os.path.join("mesh_data", file_name) interp_field = InterpolatedBField(file_name) if compare_fields: # Generate Polywell field radius = 0.15 loop_offset = 0.175 comp_loops = list() comp_loops.append(CurrentLoop(I, radius, np.asarray([-loop_offset, 0.0, 0.0]), np.asarray([1.0, 0.0, 0.0]), loop_pts)) comp_loops.append(CurrentLoop(I, radius, np.asarray([loop_offset, 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, 0.0]), np.asarray([0.0, 1.0, 0.0]), loop_pts)) comp_loops.append(CurrentLoop(I, radius, np.asarray([0.0, loop_offset, 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]), np.asarray([0.0, 0.0, 1.0]), loop_pts)) comp_loops.append(CurrentLoop(I, radius, np.asarray([0.0, 0.0, loop_offset]), np.asarray([0.0, 0.0, -1.0]), loop_pts)) combined_field = CombinedField(comp_loops) B_poly = np.zeros((domain_pts, domain_pts, domain_pts)) X = np.linspace(-dom_size, dom_size, domain_pts) Y = np.linspace(-dom_size, dom_size, domain_pts) Z = np.linspace(-dom_size, dom_size, domain_pts) B_interp = np.zeros((domain_pts, domain_pts, domain_pts)) for i, x in enumerate(X): for j, y in enumerate(Y): for k, z in enumerate(Z): print(i, j, k) b_interp = interp_field.b_field(np.asarray([[x, y, z]])) B_interp[i, j, k] = magnitude(b_interp[0]) if compare_fields: b_poly = combined_field.b_field(np.asarray([x, y, z])) B_poly[i, j, k] = magnitude(b_poly) if compare_fields: B_comp = B_poly - B_interp plot_fields = [B_poly, B_interp, B_comp] else: plot_fields = [B_interp] # Plots overall field for B in plot_fields: fig, ax = plt.subplots(3, figsize=(5, 5)) X_1, Y_1 = np.meshgrid(X, Y) im = ax[0].contourf(X_1, Y_1, B[:, :, domain_pts // 2], 100) fig.colorbar(im, ax=ax[0]) ax[0].set_title("XY Plane") X_2, Z_2 = np.meshgrid(X, Z) im = ax[1].contourf(X_2, Z_2, B[:, domain_pts // 2, :], 100) fig.colorbar(im, ax=ax[1]) ax[1].set_title("XZ Plane") Y_3, Z_3 = np.meshgrid(X, Y) im = ax[2].contourf(Y_3, Z_3, B[domain_pts // 2, :, :], 100) fig.colorbar(im, ax=ax[2]) ax[2].set_title("YZ Plane") plt.show()
def run_simulation(params): b_field, particle, radius, domain_size, I, dI_dt = params print_output = False plot_sim = False # There is no E field in the simulations def e_field(x): B = b_field.b_field(x) dB_dt = B / I * dI_dt return -dB_dt def b_field_func(x): B = b_field.b_field(x / radius) B *= I / radius return B X = particle.position V = particle.velocity Q = np.asarray([particle.charge]) M = np.asarray([particle.mass]) # Set timestep according to Gummersall approximation max_dt = 1e-9 * radius min_dt = 1e-3 * max_dt final_time = 1e5 * max_dt max_steps = int(1e7) times = [] positions = [] velocities = [] t = 0.0 ts = 0 times.append(t) positions.append(X) velocities.append(V) # Calculate fields while t < final_time and ts < max_steps: if print_output: print(t / final_time) # Get fields E = e_field(X) B = b_field_func(X) # Calculate time step dt = 0.2 * particle.mass / (magnitude(B[0]) * particle.charge) dt = min(max_dt, dt) dt = max(min_dt, dt) # Update time step ts += 1 t += dt # Move particles x, v = boris_solver_internal(E, B, X, V, Q, M, dt) if np.any(x[0, :] < -domain_size) or np.any(x[0, :] > domain_size): if print_output: print("PARTICLE ESCAPED! - {}, {}, {}".format(ts, t, X[0])) x = np.asarray(positions)[:, :, 0].flatten() y = np.asarray(positions)[:, :, 1].flatten() z = np.asarray(positions)[:, :, 2].flatten() v_x = np.asarray(velocities)[:, :, 0].flatten() v_y = np.asarray(velocities)[:, :, 1].flatten() v_z = np.asarray(velocities)[:, :, 2].flatten() if plot_sim: # Plot 3D motion fig = plt.figure(figsize=(20, 10)) ax = fig.add_subplot('111', projection='3d') ax.plot(x, y, z, label='numerical') ax.set_xlim([-1.25 * radius, 1.25 * radius]) ax.set_ylim([-1.25 * radius, 1.25 * radius]) ax.set_zlim([-1.25 * radius, 1.25 * radius]) 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() return times, x, y, z, v_x, v_y, v_z, True times.append(t) positions.append(x) velocities.append(v) X = x V = v # Convert points to x, y and z locations x = np.asarray(positions)[:, :, 0].flatten() y = np.asarray(positions)[:, :, 1].flatten() z = np.asarray(positions)[:, :, 2].flatten() v_x = np.asarray(velocities)[:, :, 0].flatten() v_y = np.asarray(velocities)[:, :, 1].flatten() v_z = np.asarray(velocities)[:, :, 2].flatten() if plot_sim: # Plot 3D motion fig = plt.figure(figsize=(20, 10)) ax = fig.add_subplot('111', projection='3d') ax.plot(x, y, z, label='numerical') ax.set_xlim([-1.25 * radius, 1.25 * radius]) ax.set_ylim([-1.25 * radius, 1.25 * radius]) ax.set_zlim([-1.25 * radius, 1.25 * radius]) 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() return times, x, y, z, v_x, v_y, v_z, False
def generate_polywell_fields(params): """ Generic function to plot a polywell field given geometry and current I: Current in coils radius: radius of coil loop_offset: spacing of coils as a ratio of the radius loop_pts: Number of loop segments used to solve Biot Savart law """ I, radius, loop_offset, domain_pts, loop_pts = params assert loop_offset >= 1.0 convert_to_kA = 1e-3 dom_size = 1.1 * loop_offset * radius file_dir = os.path.join("data", "radius-{}m".format(radius), "current-{}kA".format(I * convert_to_kA), "domres-{}".format(domain_pts)) if not os.path.exists(file_dir): os.makedirs(file_dir) file_name = "b_field_{}_{}_{}_{}_{}_{}".format(I * convert_to_kA, radius, loop_offset, domain_pts, loop_pts, dom_size) print("Starting mesh {}".format(file_name)) # Generate Polywell field 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)) combined_field = CombinedField(comp_loops) # Calculate polywell field at all points min_dom = -dom_size max_dom = dom_size X = np.linspace(min_dom, max_dom, domain_pts) Y = np.linspace(min_dom, max_dom, domain_pts) Z = np.linspace(min_dom, max_dom, domain_pts) B_x = np.zeros((domain_pts, domain_pts, domain_pts)) B_y = np.zeros((domain_pts, domain_pts, domain_pts)) B_z = np.zeros((domain_pts, domain_pts, domain_pts)) B = np.zeros((domain_pts, domain_pts, domain_pts)) for i, x in enumerate(X): for j, y in enumerate(Y): for k, z in enumerate(Z): b = combined_field.b_field(np.asarray([[x, y, z]])) B_x[i, j, k] = b[0, 0] B_y[i, j, k] = b[0, 1] B_z[i, j, k] = b[0, 2] B[i, j, k] = magnitude(b[0]) # Write output files np.savetxt(os.path.join(file_dir, "{}_x".format(file_name)), B_x.reshape((domain_pts, domain_pts**2))) np.savetxt(os.path.join(file_dir, "{}_y".format(file_name)), B_y.reshape((domain_pts, domain_pts**2))) np.savetxt(os.path.join(file_dir, "{}_z".format(file_name)), B_z.reshape((domain_pts, domain_pts**2))) np.savetxt(os.path.join(file_dir, file_name), B.reshape((domain_pts, domain_pts**2))) write_vti_file(B, os.path.join(file_dir, file_name))