def test_multiple_particles(): """ Function that checks if the energy is conserved for many particles. Is seen from the printed outputs. """ # B.3 Many particles N = 100 xi = 1 v_0 = 0.15 positions, min_radius = uf.random_positions_and_radius(N) random_angles = np.random.random(N) * (2 * np.pi) velocities = np.zeros((N, 2)) velocities[:, 0], velocities[:, 1] = np.cos(random_angles), np.sin(random_angles) velocities *= v_0 radii = np.ones(N) * min_radius mass = np.ones(N) box_of_particles = ParticleBox(number_of_particles=N, restitution_coefficient=xi, initial_positions=positions, initial_velocities=velocities, masses=mass, radii=radii) simulation = Simulation(box_of_particles, stopping_criterion=0.5) simulation.simulate_until_given_number_of_collisions('testManyParticles', output_timestep=0.1)
def simulate_and_get_crater_size(number_of_particles, restitution_coefficient, initial_positions, initial_velocities, masses, radii, simulation): """ Help function to initial a ParticleBox and simulate a crater formation by solving problem 5. After simulation the function computes the size of the newly made crater. :param number_of_particles: number of particles in the box :param restitution_coefficient: restitution coefficient of the system. Tells how much energy is kept after colliding :param initial_positions: array with shape (numb_particles, 2) to indicate starting point for particles :param initial_velocities: array with shape (numb_particles, 2) to indicate starting velocity for particles :param masses: array with shape (numb_particles) to indicate the mass of each particle :param radii: array with shape (numb_particles) to indicate the radius of each particle :param simulation: Simulation object to be used in the simulation :return: crater_size """ # initialize system box_of_particles = ParticleBox( number_of_particles=number_of_particles, restitution_coefficient=restitution_coefficient, initial_positions=initial_positions, initial_velocities=initial_velocities, masses=masses, radii=radii) # update simulation object with the given system simulation.box_of_particles = box_of_particles simulation.time_at_previous_collision = np.zeros(box_of_particles.N) # simulate simulation.simulate_until_given_energy('craterFormation', output_timestep=0.01) # compute crater size crater_size = get_crater_size(initial_positions[1:, :], simulation.box_of_particles.positions[1:, :], radii[-1]) return crater_size
def test_two_particles(): """ Function to check the system for tests with two particles. Can both check that two particles hitting each other head on with opposite speeds change speeds, go back to the wall and then collide again. Can also check for a certain impact parameter that the scattering angle will be 90 degrees for similar particles. """ # B.2 Two particles # first test of two particles hitting each other, bouncing and then colling again. N = 2 xi = 1 positions = np.array([[0.15, 0.5], [0.75, 0.5]]) velocities = np.array([[0.05, 0], [-0.05, 0]]) mass = np.ones(N) radius = np.ones(N) * 0.05 # create system object box_of_particles = ParticleBox(number_of_particles=N, restitution_coefficient=xi, initial_positions=positions, initial_velocities=velocities, masses=mass, radii=radius) # create simulation object simulation = Simulation(box_of_particles, stopping_criterion=5) # simulate until the average number of collisions is equal to 5 simulation.simulate_until_given_number_of_collisions('testTwoParticles', output_timestep=0.5)
def create_fractal_from_sticky_particles(): """ Function that creates fractal properties by allowing particles to stick together if a particle collides with a particle at rest. Initially the particle closest to the center is at rest and a fractal builds in time """ N = 5000 # number of particles v_0 = 0.2 # initial speed of all particles xi = 1 radius = 0.004 # radius of all particles positions = np.load( os.path.join(init_folder, f'uniform_pos_N_{N}_rad_{radius}.npy')) distance_to_center = norm(positions - np.tile([0.5, 0.5], reps=(len(positions), 1)), axis=1) closest_particle = np.argmin(distance_to_center) velocities = random_uniformly_distributed_velocities(N, v_0) velocities[closest_particle, :] = [ 0, 0 ] # particle closest to center is at rest radii = np.ones(N) * radius # all particles have the same radius mass = np.ones(N) # all particles have the same mass initially mass[ closest_particle] = 10**10 # increase mass of particle at rest in order to identify them in a collision # initialize a ParticleBox with given parameters box_of_particles = ParticleBox(number_of_particles=N, restitution_coefficient=xi, initial_positions=positions, initial_velocities=velocities, masses=mass, radii=radii) # initialize simulation energy_stop = 1e-9 simulation = Simulation(box_of_particles=box_of_particles, stopping_criterion=energy_stop) # simulate until all particles is at rest simulation.simulate_until_given_energy('fractalCreation', output_timestep=1, sticky_particles=True) # save the system at rest with all particle positions to file np.save(file=os.path.join(results_folder, f'fractalPositions_N_{N}_rad_{radius}'), arr=simulation.box_of_particles.positions)
def test_one_particle(): """ Function to check that for a system wih one particle, the particle bounces of all walls in a endless loop """ # B.1 One particle N = 1 xi = 1 position = np.array([0.9, 0.4]).reshape(1, 2) velocity = np.array([-0.5, -0.5]).reshape(1, 2) mass = np.ones(1).reshape(1, 1) radius = np.ones(1).reshape(1, 1) * 10**(-2) # create system object box_of_particles = ParticleBox(number_of_particles=N, restitution_coefficient=xi, initial_positions=position, initial_velocities=velocity, masses=mass, radii=radius) # create simulation object simulation = Simulation(box_of_particles, stopping_criterion=5) # simulate until the average number of collisions is >= 5 simulation.simulate_until_given_number_of_collisions('testOneParticle', output_timestep=0.2)
def compute_diffusion_properties_from_mask_particles(single_bp_particle=False, eq_start=False, bigger_radius=False): """ Functionality to compute the distance of particles starting close to the center of the box as a function of time in order to study diffusion properties. Function will save the results to file. """ N = 2000 # number of particles v_0 = 0.2 # initial speed of all particles xi = 1 radius = 0.007 # radius of all particles if bigger_radius: positions = np.load( os.path.join( init_folder, f'uniform_pos_around_bp_N_{N}_rad_{radius}_bp_rad_{radius * 3}.npy' )) else: positions = np.load( os.path.join(init_folder, f'uniform_pos_N_{N}_rad_{radius}.npy')) if eq_start: velocities = np.load( os.path.join(init_folder, f'eq_vel_N_{N}_rad_{radius}.npy')) else: velocities = random_uniformly_distributed_velocities(N, v_0) radii = np.ones(N) * radius # all particles have the same radius mass = np.ones(N) # all particles have the same mass distance_to_center = norm(positions - np.tile([0.5, 0.5], reps=(len(positions), 1)), axis=1) closest_particle = np.argmin(distance_to_center) if single_bp_particle: if not bigger_radius: mass[closest_particle] *= 10 else: radii[closest_particle] *= 3 validate_positions(positions, radius) number_of_runs = 10 t_stop = 5 timestep = 0.02 simulation = Simulation(box_of_particles=None, stopping_criterion=t_stop) if single_bp_particle: simulation.mask = distance_to_center == distance_to_center[ closest_particle] matrix_to_file = np.zeros((int(t_stop / timestep) + 1, 3)) if single_bp_particle: matrix_to_file = np.zeros((int(t_stop / timestep) + 1, 5)) for i in range(number_of_runs): print(f'Run number: {i+1}') if eq_start: np.random.shuffle(velocities) else: velocities = random_uniformly_distributed_velocities(N, v_0) if single_bp_particle: if not bigger_radius: velocities[closest_particle] *= 1 # initialize a ParticleBox with given parameters box_of_particles = ParticleBox(number_of_particles=N, restitution_coefficient=xi, initial_positions=positions, initial_velocities=velocities, masses=mass, radii=radii) # update simulation object with the given system simulation.box_of_particles = box_of_particles simulation.time_at_previous_collision = np.zeros(box_of_particles.N) if single_bp_particle: time_array, bp_position_array, bp_velocity_array = \ simulation.simulate_until_given_time_bp('brownianParticle', output_timestep=timestep, save_positions=True) matrix_to_file[:, 0] = time_array matrix_to_file[:, 1] = bp_position_array[:, 0] matrix_to_file[:, 2] = bp_position_array[:, 1] matrix_to_file[:, 3] = bp_velocity_array[:, 0] matrix_to_file[:, 4] = bp_velocity_array[:, 1] if bigger_radius: np.save(file=os.path.join( results_folder, f'diffProperties_3r0m0_particle_tmax_{t_stop}_run_{i+200}' ), arr=matrix_to_file) else: np.save(file=os.path.join( results_folder, f'diffProperties_r010m0_particle_tmax_{t_stop}_run_{i}_eq_speed' ), arr=matrix_to_file) else: time_array, mean_quadratic_distance_array, mean_quadratic_speed_array = \ simulation.simulate_until_given_time_mask_quantities('diffusionProperties', output_timestep=timestep, save_positions=True) matrix_to_file[:, 0] = time_array matrix_to_file[:, 1] = mean_quadratic_distance_array matrix_to_file[:, 2] = mean_quadratic_speed_array if eq_start: np.save(file=os.path.join( results_folder, f'diffProperties_r0m0_particle_tmax_{t_stop}_run_{i}_eq_start' ), arr=matrix_to_file) else: np.save(file=os.path.join( results_folder, f'diffProperties_r0m0_particle_tmax_{t_stop}_run_{i+30}'), arr=matrix_to_file) simulation.reset()
def compute_mean_free_path(): """ Function that computes the mean free path in a simulation for the particles inside a radius of 0.1 from the center of the box. The results is computed for a different set of radius at given radius and below to see how the numerical results change as a function of the packing fraction/radius. """ N = 2000 # number of particles v_0 = 0.2 # initial speed of all particles xi = 1 initial_radius = 0.007 # radius of all particles positions = np.load( os.path.join(init_folder, f'uniform_pos_N_{N}_rad_{initial_radius}.npy')) velocities = random_uniformly_distributed_velocities(N, v_0) mass = np.ones(N) # all particles have the same mass number_of_parameters = 10 radius_parameter = np.linspace(0.1, 1, number_of_parameters) * initial_radius number_of_runs = 10 t_stop = 5 simulation = Simulation(box_of_particles=None, stopping_criterion=t_stop) matrix_to_file = np.zeros((number_of_parameters, 2)) for i in range(number_of_runs): print(f"Run: {i}") for counter, radius in enumerate(radius_parameter): # velocities = random_uniformly_distributed_velocities(N, v_0) np.random.shuffle(velocities) radii = np.ones(N) * radius # all particles have the same radius # initialize a ParticleBox with given parameters box_of_particles = ParticleBox(number_of_particles=N, restitution_coefficient=xi, initial_positions=positions, initial_velocities=velocities, masses=mass, radii=radii) # update simulation object with the given system simulation.box_of_particles = box_of_particles simulation.time_at_previous_collision = np.zeros( box_of_particles.N) # simulate simulation.simulate_until_given_time_mask_quantities( 'mfp', output_timestep=0.1, update_positions=True, save_positions=False) computed_mean_free_path =\ simulation.box_of_particles.distance_to_collisions / simulation.box_of_particles.collision_count_particles mean_free_path_mask = np.mean( computed_mean_free_path[simulation.mask]) matrix_to_file[counter, 0] = radius matrix_to_file[counter, 1] = mean_free_path_mask simulation.reset() # save results to file np.save(file=os.path.join( results_folder, f'mean_free_path_N_{N}_func_radius_run_{i}_eq_start'), arr=matrix_to_file)
def compute_avg_energy_development_after_time(): """ Function to mainly solve problem 4 in the exam by saving how the energy develop at a short output step. Will save the average kinetic energy of all particles(together and separate for m0 and m particles) at all outputs and save to file. """ N = 2000 # number of particles v_0 = 0.2 # initial speed of all particles radius = 0.007 # radius of all particles positions = np.load( os.path.join(init_folder, f'uniform_pos_N_{N}_rad_{radius}.npy')) # velocities = random_uniformly_distributed_velocities(N, v_0) radii = np.ones(N) * radius # all particles have the same radius # first half will be m0 particles and second half is m particles with m=4*m0. mass = np.ones(N) mass[int(N / 2):] *= 4 xi_list = [1, 0.9, 0.8 ] # will do for multiple values of the restitution coefficient number_of_realizations = 5 # create simulation object t_stop = 1 dt = 0.01 # timestep simulation = Simulation(box_of_particles=None, stopping_criterion=t_stop) for xi in xi_list: matrix_to_file = np.zeros( (int(t_stop / dt) + 1, 4) ) # matrix used to save information. Size given by stop and output for i in range(number_of_realizations): print(f"Run number: {i+1}") # new velocity vectors for each realization, but same initial positions velocities = random_uniformly_distributed_velocities(N, v_0) # initialize a ParticleBox with given parameters box_of_particles = ParticleBox(number_of_particles=N, restitution_coefficient=xi, initial_positions=positions, initial_velocities=velocities, masses=mass, radii=radii) # update simulation object with the given system simulation.box_of_particles = box_of_particles simulation.time_at_previous_collision = np.zeros( box_of_particles.N) time_array, energy_array_all, energy_array_m0, energy_array_m, mean_speed_array =\ simulation.simulate_statistics_until_given_time('energyDevNEqParticles', output_timestep=dt, equal_particles=False) # add results in a matrix with given form: time, avg_energy, avg_energy_m0 and avg_energy_m matrix_to_file[:, 0] += time_array matrix_to_file[:, 1] += energy_array_all matrix_to_file[:, 2] += energy_array_m0 matrix_to_file[:, 3] += energy_array_m # reset simulation simulation.reset() matrix_to_file /= number_of_realizations # get average values for multiple realizations # save to file np.save(file=os.path.join(results_folder, f'energyDevNEqParticles_N_{N}_xi_{xi}'), arr=matrix_to_file)
def speed_distribution(use_equal_particles=True, number_of_runs=1): """ Function to get speed of every particle after the system has reached equilibrium. Mainly solves problem 2 and 3 in the exam. The procedure is based on initializing a system of many particles with the same initial speed. Then you start the simulation and let them collide until equilibrium have been reached. One then return the speed of each particle in order to create speed distribution plots. :param use_equal_particles: bool value that separates problem 2 and 3. If not equal: Second half will have m=4*m0 :param number_of_runs: In order to achieve better statistics, take averages over multiple runs. """ N = 2000 # number of particles xi = 1 # restitution coefficient v_0 = 0.2 # initial speed radius = 0.007 # radius of all particles positions = np.load( os.path.join(init_folder, f'uniform_pos_N_{N}_rad_{radius}.npy')) radii = np.ones(N) * radius # all particles have the same radius mass = np.ones(N) # all particles get initially the same mass energy_matrix = np.zeros( (N, 2)) # array with mass and speed to save to file if not use_equal_particles: # Problem 3: Second half of particles will have an mass which is bigger than the first half by a factor 4. mass[int(N / 2):] *= 4 energy_matrix[:, 0] = mass # mass does not change through the simulation # use stopping criterion that the avg_numb_collision should be equal to 2% of numb_particles. average_number_of_collisions_stop = N * 0.02 simulation = Simulation( box_of_particles=None, stopping_criterion=average_number_of_collisions_stop) for run_number in range(number_of_runs): # use same initial positions but new initial velocities every run # update velocities by getting new random angles, taking cos and sin and multiply with the initial speed velocities = random_uniformly_distributed_velocities(N, v_0) if run_number == 0: # save initial speed as a reference point initial_speeds = norm(velocities, axis=1) energy_matrix[:, 1] = initial_speeds if use_equal_particles: np.save(file=os.path.join( results_folder, f'distributionEqParticles_N_{N}_init_energy_matrix'), arr=energy_matrix) else: np.save(file=os.path.join( results_folder, f'distributionNEqParticles_N_{N}_init_energy_matrix'), arr=energy_matrix) # initialize system box_of_particles = ParticleBox(number_of_particles=N, restitution_coefficient=xi, initial_positions=positions, initial_velocities=velocities, masses=mass, radii=radii) # update simulation object simulation.box_of_particles = box_of_particles simulation.time_at_previous_collision = np.zeros(box_of_particles.N) # simulate system until given stopping criteria, save speed of particles, reset simulation object and repeat if use_equal_particles: simulation.simulate_until_given_number_of_collisions( 'distributionEqParticles', output_timestep=0.1) energy_matrix[:, 1] = norm(simulation.box_of_particles.velocities, axis=1) np.save(file=os.path.join( results_folder, f'distributionEqParticles_N_{N}_eq_energy_matrix_{run_number}' ), arr=energy_matrix) else: simulation.simulate_until_given_number_of_collisions( 'distributionNEqParticles', output_timestep=0.1) energy_matrix[:, 1] = norm(simulation.box_of_particles.velocities, axis=1) np.save(file=os.path.join( results_folder, f'distributionNEqParticles_N_{N}_eq_energy_matrix_{run_number}' ), arr=energy_matrix) simulation.reset()
def scattering_angle_func_impact_parameter(): """ Function to compute scattering angle as a function of the impact parameter b. Solves problem 1 from exam. Involves two particles, one big and massive, and see how the velocity changes for the smaller particle by colliding with the bigger particle. """ N = 2 xi = 1 initial_position_big_particle = [0.5, 0.5] initial_velocity_small_particle = [0.2, 0] initial_velocity_big_particle = [0, 0] initial_positions = np.zeros((N, 2)) initial_positions[1, :] = initial_position_big_particle initial_velocities = np.zeros_like(initial_positions) initial_velocities[0, :] = initial_velocity_small_particle initial_velocities[1, :] = initial_velocity_big_particle # given initial values from exam in numerical physics mass_array = np.array([1, 10**6]) radius_array = np.array([0.001, 0.1]) # initialize a simulation, where the box_of_particles is set for each parameter average_number_of_collisions_stop = 0.5 simulation = Simulation( box_of_particles=None, stopping_criterion=average_number_of_collisions_stop) impact_parameters = np.linspace(-radius_array[1] * 1.5, radius_array[1] * 1.5, 500) scattering_angles = np.zeros_like(impact_parameters) for counter, impact_parameter in enumerate(impact_parameters): # update initial position of small particle initial_position_small_particle = [0.1, 0.5 + impact_parameter] initial_positions[0, :] = initial_position_small_particle # create new system of particles box_of_particles = ParticleBox(number_of_particles=N, restitution_coefficient=xi, initial_positions=initial_positions, initial_velocities=initial_velocities, masses=mass_array, radii=radius_array) # update the system in the simulation simulation.box_of_particles = box_of_particles simulation.time_at_previous_collision = np.zeros(box_of_particles.N) simulation.simulate_until_given_number_of_collisions( 'scatteringAngle', output_timestep=1.0) velocity_small_particle_after_collision = simulation.box_of_particles.velocities[ 0, :] scattering_angles[counter] = compute_scattering_angle( np.array(initial_velocity_small_particle), velocity_small_particle_after_collision) simulation.reset( ) # set time and some parameters equal to zero to make simulation ready for new realization # save results in a matrix where each row gives impact parameter and scattering angle matrix_to_file = np.zeros((len(impact_parameters), 2)) matrix_to_file[:, 0] = impact_parameters / np.sum(radius_array) matrix_to_file[:, 1] = scattering_angles # save to file np.save(file=os.path.join(results_folder, 'scattering_angle_func_impact_parameter'), arr=matrix_to_file)